mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
litepcie: add linux driver + utilities (sysfs + dma)
This commit is contained in:
parent
d22d58c7cc
commit
b4b37fb10e
12 changed files with 1285 additions and 0 deletions
16
misoclib/com/litepcie/software/linux/kernel/Makefile
Normal file
16
misoclib/com/litepcie/software/linux/kernel/Makefile
Normal file
|
@ -0,0 +1,16 @@
|
|||
# Makefile for kernel module
|
||||
|
||||
KERNEL_VERSION:=$(shell uname -r)
|
||||
KERNEL_PATH:=/lib/modules/$(KERNEL_VERSION)/build
|
||||
|
||||
obj-m = litepcie.o
|
||||
litepcie-objs = main.o
|
||||
|
||||
all: litepcie.ko
|
||||
|
||||
litepcie.ko: main.c
|
||||
make -C $(KERNEL_PATH) M=$(PWD) modules
|
||||
|
||||
clean:
|
||||
make -C $(KERNEL_PATH) M=$(PWD) clean
|
||||
rm -f *~
|
9
misoclib/com/litepcie/software/linux/kernel/README
Normal file
9
misoclib/com/litepcie/software/linux/kernel/README
Normal file
|
@ -0,0 +1,9 @@
|
|||
- Use 'make' to build the driver
|
||||
|
||||
- Install the driver and create the device with :
|
||||
|
||||
./init.sh
|
||||
|
||||
- Remove driver with
|
||||
|
||||
rmmod litepcie
|
13
misoclib/com/litepcie/software/linux/kernel/config.h
Normal file
13
misoclib/com/litepcie/software/linux/kernel/config.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
#ifndef __HW_CONFIG_H
|
||||
#define __HW_CONFIG_H
|
||||
|
||||
/* pci */
|
||||
#define PCI_FPGA_VENDOR_ID 0x10ee
|
||||
#define PCI_FPGA_DEVICE_ID 0x7022
|
||||
#define PCI_FPGA_BAR0_SIZE 0xa000
|
||||
|
||||
/* dma */
|
||||
#define DMA_BUFFER_COUNT 128
|
||||
|
||||
|
||||
#endif /* __HW_CONFIG_H */
|
7
misoclib/com/litepcie/software/linux/kernel/flags.h
Normal file
7
misoclib/com/litepcie/software/linux/kernel/flags.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#ifndef __HW_FLAGS_H
|
||||
#define __HW_FLAGS_H
|
||||
|
||||
/* dma */
|
||||
#define DMA_LOOPBACK_ENABLE 0x1
|
||||
|
||||
#endif /* __HW_FLAGS_H */
|
7
misoclib/com/litepcie/software/linux/kernel/init.sh
Normal file
7
misoclib/com/litepcie/software/linux/kernel/init.sh
Normal file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
# TODO: use udev instead
|
||||
|
||||
insmod litepcie.ko
|
||||
|
||||
major=$(awk '/ litepcie$/{print $1}' /proc/devices)
|
||||
mknod -m 666 /dev/litepcie0 c $major 0
|
50
misoclib/com/litepcie/software/linux/kernel/litepcie.h
Normal file
50
misoclib/com/litepcie/software/linux/kernel/litepcie.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* LitePCIe driver
|
||||
*
|
||||
*/
|
||||
#ifndef _LINUX_LITEPCIE_H
|
||||
#define _LINUX_LITEPCIE_H
|
||||
|
||||
#include <linux/types.h>
|
||||
|
||||
struct litepcie_ioctl_mmap_info {
|
||||
unsigned long reg_offset;
|
||||
unsigned long reg_size;
|
||||
|
||||
unsigned long dma_tx_buf_offset;
|
||||
unsigned long dma_tx_buf_size;
|
||||
unsigned long dma_tx_buf_count;
|
||||
|
||||
unsigned long dma_rx_buf_offset;
|
||||
unsigned long dma_rx_buf_size;
|
||||
unsigned long dma_rx_buf_count;
|
||||
};
|
||||
|
||||
struct litepcie_ioctl_dma_start {
|
||||
__u32 dma_flags; /* see LITEPCIE_DMA_FLAGS_x */
|
||||
__u32 tx_buf_size; /* in bytes, must be < dma_buf_pitch. 0 means no TX */
|
||||
__u32 tx_buf_count;
|
||||
__u32 rx_buf_size; /* in bytes, must be < dma_buf_pitch. 0 means no RX */
|
||||
__u32 rx_buf_count;
|
||||
};
|
||||
|
||||
/* if tx_wait is true, wait until the current TX bufffer is
|
||||
different from tx_buf_num. If tx_wait is false, wait until the
|
||||
current RX buffer is different from rx_buf_num. Return the last
|
||||
TX buffer in tx_buf_num and the last RX buffer in
|
||||
rx_buf_num. */
|
||||
struct litepcie_ioctl_dma_wait {
|
||||
__s32 timeout; /* in ms. Return -EAGAIN if timeout occured without event */
|
||||
__u32 tx_wait;
|
||||
__u32 tx_buf_num; /* read/write */
|
||||
__u32 rx_buf_num; /* read/write */
|
||||
};
|
||||
|
||||
#define LITEPCIE_IOCTL 'S'
|
||||
|
||||
#define LITEPCIE_IOCTL_GET_MMAP_INFO _IOR(LITEPCIE_IOCTL, 0, struct litepcie_ioctl_mmap_info)
|
||||
#define LITEPCIE_IOCTL_DMA_START _IOW(LITEPCIE_IOCTL, 1, struct litepcie_ioctl_dma_start)
|
||||
#define LITEPCIE_IOCTL_DMA_STOP _IO(LITEPCIE_IOCTL, 2)
|
||||
#define LITEPCIE_IOCTL_DMA_WAIT _IOWR(LITEPCIE_IOCTL, 3, struct litepcie_ioctl_dma_wait)
|
||||
|
||||
#endif /* _LINUX_LITEPCIE_H */
|
639
misoclib/com/litepcie/software/linux/kernel/main.c
Normal file
639
misoclib/com/litepcie/software/linux/kernel/main.c
Normal file
|
@ -0,0 +1,639 @@
|
|||
/*
|
||||
* LitePCIe driver
|
||||
*
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mmtimer.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/posix-timers.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/pci_regs.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#include "litepcie.h"
|
||||
#include "config.h"
|
||||
#include "csr.h"
|
||||
#include "flags.h"
|
||||
|
||||
#define LITEPCIE_NAME "litepcie"
|
||||
#define LITEPCIE_MINOR_COUNT 4
|
||||
|
||||
#define DMA_BUFFER_SIZE PAGE_ALIGN(32768)
|
||||
#define DMA_BUFFER_MAP_SIZE (DMA_BUFFER_SIZE * DMA_BUFFER_COUNT)
|
||||
|
||||
#define IRQ_MASK_DMA_READER (1 << DMA_READER_INTERRUPT)
|
||||
#define IRQ_MASK_DMA_WRITER (1 << DMA_WRITER_INTERRUPT)
|
||||
|
||||
typedef struct {
|
||||
int minor;
|
||||
struct pci_dev *dev;
|
||||
|
||||
phys_addr_t bar0_phys_addr;
|
||||
uint8_t *bar0_addr; /* virtual address of BAR0 */
|
||||
|
||||
uint8_t *dma_tx_bufs[DMA_BUFFER_COUNT];
|
||||
unsigned long dma_tx_bufs_addr[DMA_BUFFER_COUNT];
|
||||
uint8_t *dma_rx_bufs[DMA_BUFFER_COUNT];
|
||||
unsigned long dma_rx_bufs_addr[DMA_BUFFER_COUNT];
|
||||
uint8_t tx_dma_started;
|
||||
uint8_t rx_dma_started;
|
||||
wait_queue_head_t dma_waitqueue;
|
||||
} LitePCIeState;
|
||||
|
||||
static dev_t litepcie_cdev;
|
||||
static struct cdev litepcie_cdev_struct;
|
||||
static LitePCIeState *litepcie_minor_table[LITEPCIE_MINOR_COUNT];
|
||||
|
||||
static void litepcie_end(struct pci_dev *dev, LitePCIeState *s);
|
||||
static int litepcie_dma_stop(LitePCIeState *s);
|
||||
|
||||
static inline uint32_t litepcie_readl(LitePCIeState *s, uint32_t addr)
|
||||
{
|
||||
return readl(s->bar0_addr + addr);
|
||||
}
|
||||
|
||||
static inline void litepcie_writel(LitePCIeState *s, uint32_t addr, uint32_t val)
|
||||
{
|
||||
return writel(val, s->bar0_addr + addr);
|
||||
}
|
||||
|
||||
static void litepcie_enable_interrupt(LitePCIeState *s, int irq_num)
|
||||
{
|
||||
uint32_t v;
|
||||
v = litepcie_readl(s, CSR_IRQ_CONTROLLER_ENABLE_ADDR);
|
||||
v |= (1 << irq_num);
|
||||
litepcie_writel(s, CSR_IRQ_CONTROLLER_ENABLE_ADDR, v);
|
||||
}
|
||||
|
||||
static void litepcie_disable_interrupt(LitePCIeState *s, int irq_num)
|
||||
{
|
||||
uint32_t v;
|
||||
v = litepcie_readl(s, CSR_IRQ_CONTROLLER_ENABLE_ADDR);
|
||||
v &= ~(1 << irq_num);
|
||||
litepcie_writel(s, CSR_IRQ_CONTROLLER_ENABLE_ADDR, v);
|
||||
}
|
||||
|
||||
static int litepcie_open(struct inode *inode, struct file *file)
|
||||
{
|
||||
LitePCIeState *s;
|
||||
int minor;
|
||||
|
||||
/* find PCI device */
|
||||
minor = iminor(inode);
|
||||
if (minor < 0 || minor >= LITEPCIE_MINOR_COUNT)
|
||||
return -ENODEV;
|
||||
s = litepcie_minor_table[minor];
|
||||
if (!s)
|
||||
return -ENODEV;
|
||||
file->private_data = s;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* mmap the DMA buffers and registers to user space */
|
||||
static int litepcie_mmap(struct file *file, struct vm_area_struct *vma)
|
||||
{
|
||||
LitePCIeState *s = file->private_data;
|
||||
unsigned long pfn;
|
||||
int is_tx, i;
|
||||
|
||||
if (vma->vm_pgoff == 0) {
|
||||
if (vma->vm_end - vma->vm_start != DMA_BUFFER_MAP_SIZE)
|
||||
return -EINVAL;
|
||||
is_tx = 1;
|
||||
goto remap_ram;
|
||||
} else if (vma->vm_pgoff == (DMA_BUFFER_MAP_SIZE >> PAGE_SHIFT)) {
|
||||
if (vma->vm_end - vma->vm_start != DMA_BUFFER_MAP_SIZE)
|
||||
return -EINVAL;
|
||||
is_tx = 0;
|
||||
remap_ram:
|
||||
for(i = 0; i < DMA_BUFFER_COUNT; i++) {
|
||||
if (is_tx)
|
||||
pfn = __pa(s->dma_tx_bufs[i]) >> PAGE_SHIFT;
|
||||
else
|
||||
pfn = __pa(s->dma_rx_bufs[i]) >> PAGE_SHIFT;
|
||||
/* Note: the memory is cached, so the user must explicitly
|
||||
flush the CPU caches on architectures which require it. */
|
||||
if (remap_pfn_range(vma, vma->vm_start + i * DMA_BUFFER_SIZE, pfn,
|
||||
DMA_BUFFER_SIZE, vma->vm_page_prot)) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " remap_pfn_range failed\n");
|
||||
return -EAGAIN;
|
||||
}
|
||||
}
|
||||
} else if (vma->vm_pgoff == ((2 * DMA_BUFFER_MAP_SIZE) >> PAGE_SHIFT)) {
|
||||
if (vma->vm_end - vma->vm_start != PCI_FPGA_BAR0_SIZE)
|
||||
return -EINVAL;
|
||||
pfn = s->bar0_phys_addr >> PAGE_SHIFT;
|
||||
/* not cached */
|
||||
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
|
||||
vma->vm_flags |= VM_IO;
|
||||
if (io_remap_pfn_range(vma, vma->vm_start, pfn,
|
||||
vma->vm_end - vma->vm_start,
|
||||
vma->vm_page_prot)) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " io_remap_pfn_range failed\n");
|
||||
return -EAGAIN;
|
||||
}
|
||||
} else {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int litepcie_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
LitePCIeState *s = file->private_data;
|
||||
|
||||
litepcie_dma_stop(s); /* just in case: stop the DMA */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static irqreturn_t litepcie_interrupt(int irq, void *data)
|
||||
{
|
||||
LitePCIeState *s = data;
|
||||
uint32_t clear_mask, irq_vector;
|
||||
|
||||
irq_vector = litepcie_readl(s, CSR_IRQ_CONTROLLER_VECTOR_ADDR);
|
||||
clear_mask = 0;
|
||||
if (irq_vector & (IRQ_MASK_DMA_READER | IRQ_MASK_DMA_WRITER)) {
|
||||
/* wake up processes waiting on dma_wait() */
|
||||
wake_up_interruptible(&s->dma_waitqueue);
|
||||
clear_mask |= (IRQ_MASK_DMA_READER | IRQ_MASK_DMA_WRITER);
|
||||
}
|
||||
|
||||
litepcie_writel(s, CSR_IRQ_CONTROLLER_CLEAR_ADDR, clear_mask);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int litepcie_dma_start(LitePCIeState *s, struct litepcie_ioctl_dma_start *m)
|
||||
{
|
||||
int i, val;
|
||||
|
||||
if (s->tx_dma_started || s->rx_dma_started)
|
||||
return -EIO;
|
||||
|
||||
if (m->tx_buf_size == 0 && m->rx_buf_size == 0)
|
||||
return -EINVAL;
|
||||
/* check alignment (XXX: what is the exact constraint ?) */
|
||||
if ((m->tx_buf_size & 7) != 0 ||
|
||||
(m->rx_buf_size & 7) != 0 ||
|
||||
m->tx_buf_size > DMA_BUFFER_SIZE ||
|
||||
m->rx_buf_size > DMA_BUFFER_SIZE)
|
||||
return -EINVAL;
|
||||
|
||||
/* check buffer count */
|
||||
if (m->tx_buf_count > DMA_BUFFER_COUNT)
|
||||
return -EINVAL;
|
||||
if (m->rx_buf_count > DMA_BUFFER_COUNT)
|
||||
return -EINVAL;
|
||||
|
||||
val = ((m->dma_flags & DMA_LOOPBACK_ENABLE) != 0);
|
||||
litepcie_writel(s, CSR_DMA_LOOPBACK_ENABLE_ADDR, val);
|
||||
|
||||
/* init DMA write */
|
||||
if (m->rx_buf_size != 0) {
|
||||
litepcie_writel(s, CSR_DMA_WRITER_ENABLE_ADDR, 0);
|
||||
litepcie_writel(s, CSR_DMA_WRITER_TABLE_FLUSH_ADDR, 1);
|
||||
litepcie_writel(s, CSR_DMA_WRITER_TABLE_LOOP_PROG_N_ADDR, 0);
|
||||
for(i = 0; i < m->rx_buf_count; i++) {
|
||||
litepcie_writel(s, CSR_DMA_WRITER_TABLE_VALUE_ADDR, m->rx_buf_size);
|
||||
litepcie_writel(s, CSR_DMA_WRITER_TABLE_VALUE_ADDR + 4,
|
||||
s->dma_rx_bufs_addr[i]);
|
||||
litepcie_writel(s, CSR_DMA_WRITER_TABLE_WE_ADDR, 1);
|
||||
}
|
||||
litepcie_writel(s, CSR_DMA_WRITER_TABLE_LOOP_PROG_N_ADDR, 1);
|
||||
}
|
||||
|
||||
/* init DMA read */
|
||||
if (m->tx_buf_size != 0) {
|
||||
litepcie_writel(s, CSR_DMA_READER_ENABLE_ADDR, 0);
|
||||
litepcie_writel(s, CSR_DMA_READER_TABLE_FLUSH_ADDR, 1);
|
||||
litepcie_writel(s, CSR_DMA_READER_TABLE_LOOP_PROG_N_ADDR, 0);
|
||||
for(i = 0; i < m->tx_buf_count; i++) {
|
||||
litepcie_writel(s, CSR_DMA_READER_TABLE_VALUE_ADDR, m->tx_buf_size);
|
||||
litepcie_writel(s, CSR_DMA_READER_TABLE_VALUE_ADDR + 4,
|
||||
s->dma_tx_bufs_addr[i]);
|
||||
litepcie_writel(s, CSR_DMA_READER_TABLE_WE_ADDR, 1);
|
||||
}
|
||||
litepcie_writel(s, CSR_DMA_READER_TABLE_LOOP_PROG_N_ADDR, 1);
|
||||
}
|
||||
|
||||
/* start DMA */
|
||||
if (m->rx_buf_size != 0) {
|
||||
litepcie_writel(s, CSR_DMA_WRITER_ENABLE_ADDR, 1);
|
||||
s->rx_dma_started = 1;
|
||||
}
|
||||
if (m->tx_buf_size != 0) {
|
||||
litepcie_writel(s, CSR_DMA_READER_ENABLE_ADDR, 1);
|
||||
s->tx_dma_started = 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int litepcie_dma_wait(LitePCIeState *s, struct litepcie_ioctl_dma_wait *m)
|
||||
{
|
||||
unsigned long timeout;
|
||||
int ret, last_buf_num;
|
||||
DECLARE_WAITQUEUE(wait, current);
|
||||
|
||||
if (m->tx_wait) {
|
||||
if (!s->tx_dma_started)
|
||||
return -EIO;
|
||||
last_buf_num = m->tx_buf_num;
|
||||
litepcie_enable_interrupt(s, DMA_READER_INTERRUPT);
|
||||
} else {
|
||||
if (!s->rx_dma_started)
|
||||
return -EIO;
|
||||
last_buf_num = m->rx_buf_num;
|
||||
litepcie_enable_interrupt(s, DMA_WRITER_INTERRUPT);
|
||||
}
|
||||
|
||||
add_wait_queue(&s->dma_waitqueue, &wait);
|
||||
|
||||
timeout = jiffies + msecs_to_jiffies(m->timeout);
|
||||
for (;;) {
|
||||
/* set current buffer */
|
||||
if (s->tx_dma_started) {
|
||||
m->tx_buf_num = litepcie_readl(s, CSR_DMA_READER_TABLE_INDEX_ADDR);
|
||||
} else {
|
||||
m->tx_buf_num = 0;
|
||||
}
|
||||
if (s->rx_dma_started) {
|
||||
m->rx_buf_num = litepcie_readl(s, CSR_DMA_WRITER_TABLE_INDEX_ADDR);
|
||||
} else {
|
||||
m->rx_buf_num = 0;
|
||||
}
|
||||
if (m->tx_wait) {
|
||||
if (m->tx_buf_num != last_buf_num)
|
||||
break;
|
||||
} else {
|
||||
if (m->rx_buf_num != last_buf_num)
|
||||
break;
|
||||
}
|
||||
if ((long)(jiffies - timeout) > 0) {
|
||||
ret = -EAGAIN;
|
||||
goto done;
|
||||
}
|
||||
set_current_state(TASK_INTERRUPTIBLE);
|
||||
if (signal_pending(current)) {
|
||||
ret = -EINTR;
|
||||
goto done;
|
||||
}
|
||||
schedule();
|
||||
}
|
||||
ret = 0;
|
||||
done:
|
||||
if (m->tx_wait) {
|
||||
litepcie_disable_interrupt(s, DMA_READER_INTERRUPT);
|
||||
} else {
|
||||
litepcie_disable_interrupt(s, DMA_WRITER_INTERRUPT);
|
||||
}
|
||||
|
||||
__set_current_state(TASK_RUNNING);
|
||||
remove_wait_queue(&s->dma_waitqueue, &wait);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int litepcie_dma_stop(LitePCIeState *s)
|
||||
{
|
||||
/* just to be sure, we disable the interrupts */
|
||||
litepcie_disable_interrupt(s, DMA_READER_INTERRUPT);
|
||||
litepcie_disable_interrupt(s, DMA_WRITER_INTERRUPT);
|
||||
|
||||
s->tx_dma_started = 0;
|
||||
litepcie_writel(s, CSR_DMA_READER_TABLE_LOOP_PROG_N_ADDR, 0);
|
||||
litepcie_writel(s, CSR_DMA_READER_TABLE_FLUSH_ADDR, 1);
|
||||
udelay(100);
|
||||
litepcie_writel(s, CSR_DMA_READER_ENABLE_ADDR, 0);
|
||||
|
||||
s->rx_dma_started = 0;
|
||||
litepcie_writel(s, CSR_DMA_WRITER_TABLE_LOOP_PROG_N_ADDR, 0);
|
||||
litepcie_writel(s, CSR_DMA_WRITER_TABLE_FLUSH_ADDR, 1);
|
||||
udelay(100);
|
||||
litepcie_writel(s, CSR_DMA_WRITER_ENABLE_ADDR, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long litepcie_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
||||
{
|
||||
LitePCIeState *s = file->private_data;
|
||||
long ret;
|
||||
|
||||
switch(cmd) {
|
||||
case LITEPCIE_IOCTL_GET_MMAP_INFO:
|
||||
{
|
||||
struct litepcie_ioctl_mmap_info m;
|
||||
m.dma_tx_buf_offset = 0;
|
||||
m.dma_tx_buf_size = DMA_BUFFER_SIZE;
|
||||
m.dma_tx_buf_count = DMA_BUFFER_COUNT;
|
||||
|
||||
m.dma_rx_buf_offset = DMA_BUFFER_MAP_SIZE;
|
||||
m.dma_rx_buf_size = DMA_BUFFER_SIZE;
|
||||
m.dma_rx_buf_count = DMA_BUFFER_COUNT;
|
||||
|
||||
m.reg_offset = 2 * DMA_BUFFER_MAP_SIZE;
|
||||
m.reg_size = PCI_FPGA_BAR0_SIZE;
|
||||
if (copy_to_user((void *)arg, &m, sizeof(m))) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
ret = 0;
|
||||
}
|
||||
break;
|
||||
case LITEPCIE_IOCTL_DMA_START:
|
||||
{
|
||||
struct litepcie_ioctl_dma_start m;
|
||||
|
||||
if (copy_from_user(&m, (void *)arg, sizeof(m))) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
ret = litepcie_dma_start(s, &m);
|
||||
}
|
||||
break;
|
||||
case LITEPCIE_IOCTL_DMA_STOP:
|
||||
{
|
||||
ret = litepcie_dma_stop(s);
|
||||
}
|
||||
break;
|
||||
case LITEPCIE_IOCTL_DMA_WAIT:
|
||||
{
|
||||
struct litepcie_ioctl_dma_wait m;
|
||||
|
||||
if (copy_from_user(&m, (void *)arg, sizeof(m))) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
ret = litepcie_dma_wait(s, &m);
|
||||
if (ret == 0) {
|
||||
if (copy_to_user((void *)arg, &m, sizeof(m))) {
|
||||
ret = -EFAULT;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ret = -ENOIOCTLCMD;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct file_operations litepcie_fops = {
|
||||
.owner = THIS_MODULE,
|
||||
.unlocked_ioctl = litepcie_ioctl,
|
||||
.open = litepcie_open,
|
||||
.release = litepcie_release,
|
||||
.mmap = litepcie_mmap,
|
||||
.llseek = no_llseek,
|
||||
};
|
||||
|
||||
static int litepcie_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
|
||||
{
|
||||
LitePCIeState *s = NULL;
|
||||
uint8_t rev_id;
|
||||
int ret, minor, i;
|
||||
|
||||
printk(KERN_INFO LITEPCIE_NAME " Probing device\n");
|
||||
|
||||
/* find available minor */
|
||||
for(minor = 0; minor < LITEPCIE_MINOR_COUNT; minor++) {
|
||||
if (!litepcie_minor_table[minor])
|
||||
break;
|
||||
}
|
||||
if (minor == LITEPCIE_MINOR_COUNT) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Cannot allocate a minor\n");
|
||||
ret = -ENODEV;
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
s = kzalloc(sizeof(LitePCIeState), GFP_KERNEL);
|
||||
if (!s) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Cannot allocate memory\n");
|
||||
ret = -ENOMEM;
|
||||
goto fail1;
|
||||
}
|
||||
s->minor = minor;
|
||||
s->dev = dev;
|
||||
pci_set_drvdata(dev, s);
|
||||
|
||||
ret = pci_enable_device(dev);
|
||||
if (ret != 0) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Cannot enable device\n");
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
/* check device version */
|
||||
pci_read_config_byte(dev, PCI_REVISION_ID, &rev_id);
|
||||
if (rev_id != 1) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Unsupported device version %d\n", rev_id);
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
if (pci_request_regions(dev, LITEPCIE_NAME) < 0) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Could not request regions\n");
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
/* check BAR0 config */
|
||||
if (!(pci_resource_flags(dev, 0) & IORESOURCE_MEM)) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Invalid BAR0 config\n");
|
||||
goto fail3;
|
||||
}
|
||||
|
||||
s->bar0_phys_addr = pci_resource_start(dev, 0);
|
||||
s->bar0_addr = pci_ioremap_bar(dev, 0);
|
||||
if (!s->bar0_addr) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Could not map BAR0\n");
|
||||
goto fail3;
|
||||
}
|
||||
|
||||
pci_set_master(dev);
|
||||
ret = pci_set_dma_mask(dev, DMA_BIT_MASK(32));
|
||||
if (ret) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Failed to set DMA mask\n");
|
||||
goto fail4;
|
||||
};
|
||||
|
||||
ret = pci_enable_msi(dev);
|
||||
if (ret) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Failed to enable MSI\n");
|
||||
goto fail4;
|
||||
}
|
||||
|
||||
if (request_irq(dev->irq, litepcie_interrupt, IRQF_SHARED, LITEPCIE_NAME, s) < 0) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Failed to allocate irq %d\n", dev->irq);
|
||||
goto fail5;
|
||||
}
|
||||
|
||||
/* soft reset */
|
||||
litepcie_writel(s, CSR_CRG_SOFT_RST_ADDR, 1);
|
||||
udelay(5);
|
||||
|
||||
/* allocate DMA buffers */
|
||||
for(i = 0; i < DMA_BUFFER_COUNT; i++) {
|
||||
s->dma_tx_bufs[i] = kzalloc(DMA_BUFFER_SIZE, GFP_KERNEL | GFP_DMA32);
|
||||
if (!s->dma_tx_bufs[i]) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Failed to allocate dma_tx_buf\n");
|
||||
goto fail6;
|
||||
}
|
||||
s->dma_tx_bufs_addr[i] = pci_map_single(dev, s->dma_tx_bufs[i],
|
||||
DMA_BUFFER_SIZE,
|
||||
DMA_TO_DEVICE);
|
||||
if (!s->dma_tx_bufs_addr[i]) {
|
||||
ret = -ENOMEM;
|
||||
goto fail6;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 0; i < DMA_BUFFER_COUNT; i++) {
|
||||
s->dma_rx_bufs[i] = kzalloc(DMA_BUFFER_SIZE, GFP_KERNEL | GFP_DMA32);
|
||||
if (!s->dma_rx_bufs[i]) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Failed to allocate dma_rx_buf\n");
|
||||
goto fail6;
|
||||
}
|
||||
|
||||
s->dma_rx_bufs_addr[i] = pci_map_single(dev, s->dma_rx_bufs[i],
|
||||
DMA_BUFFER_SIZE,
|
||||
DMA_FROM_DEVICE);
|
||||
if (!s->dma_rx_bufs_addr[i]) {
|
||||
ret = -ENOMEM;
|
||||
goto fail6;
|
||||
}
|
||||
}
|
||||
|
||||
init_waitqueue_head(&s->dma_waitqueue);
|
||||
|
||||
litepcie_minor_table[minor] = s;
|
||||
printk(KERN_INFO LITEPCIE_NAME " Assigned to minor %d\n", minor);
|
||||
return 0;
|
||||
|
||||
fail6:
|
||||
litepcie_end(dev, s);
|
||||
free_irq(dev->irq, s);
|
||||
fail5:
|
||||
pci_disable_msi(dev);
|
||||
fail4:
|
||||
pci_iounmap(dev, s->bar0_addr);
|
||||
fail3:
|
||||
pci_release_regions(dev);
|
||||
fail2:
|
||||
pci_disable_device(dev);
|
||||
ret = -EIO;
|
||||
fail1:
|
||||
kfree(s);
|
||||
printk(KERN_ERR LITEPCIE_NAME " Error while probing device\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void litepcie_end(struct pci_dev *dev, LitePCIeState *s)
|
||||
{
|
||||
int i;
|
||||
|
||||
for(i = 0; i < DMA_BUFFER_COUNT; i++) {
|
||||
if (s->dma_tx_bufs_addr[i]) {
|
||||
dma_unmap_single(&dev->dev, s->dma_tx_bufs_addr[i],
|
||||
DMA_BUFFER_SIZE, DMA_TO_DEVICE);
|
||||
}
|
||||
kfree(s->dma_tx_bufs[i]);
|
||||
}
|
||||
|
||||
for(i = 0; i < DMA_BUFFER_COUNT; i++) {
|
||||
if (s->dma_rx_bufs_addr[i]) {
|
||||
dma_unmap_single(&dev->dev, s->dma_rx_bufs_addr[i],
|
||||
DMA_BUFFER_SIZE, DMA_FROM_DEVICE);
|
||||
}
|
||||
kfree(s->dma_rx_bufs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static void litepcie_pci_remove(struct pci_dev *dev)
|
||||
{
|
||||
LitePCIeState *s = pci_get_drvdata(dev);
|
||||
|
||||
printk(KERN_INFO LITEPCIE_NAME " Removing device\n");
|
||||
litepcie_minor_table[s->minor] = NULL;
|
||||
|
||||
litepcie_end(dev, s);
|
||||
free_irq(dev->irq, s);
|
||||
pci_disable_msi(dev);
|
||||
pci_iounmap(dev, s->bar0_addr);
|
||||
pci_disable_device(dev);
|
||||
pci_release_regions(dev);
|
||||
kfree(s);
|
||||
};
|
||||
|
||||
static const struct pci_device_id litepcie_pci_ids[] = {
|
||||
{ PCI_DEVICE(PCI_FPGA_VENDOR_ID, PCI_FPGA_DEVICE_ID), },
|
||||
{ 0, }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, litepcie_pci_ids);
|
||||
|
||||
|
||||
static struct pci_driver litepcie_pci_driver = {
|
||||
.name = LITEPCIE_NAME,
|
||||
.id_table = litepcie_pci_ids,
|
||||
.probe = litepcie_pci_probe,
|
||||
.remove = litepcie_pci_remove,
|
||||
};
|
||||
|
||||
static int __init litepcie_module_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = pci_register_driver(&litepcie_pci_driver);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Error while registering PCI driver\n");
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
ret = alloc_chrdev_region(&litepcie_cdev, 0, LITEPCIE_MINOR_COUNT, LITEPCIE_NAME);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Could not allocate char device\n");
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
cdev_init(&litepcie_cdev_struct, &litepcie_fops);
|
||||
ret = cdev_add(&litepcie_cdev_struct, litepcie_cdev, LITEPCIE_MINOR_COUNT);
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR LITEPCIE_NAME " Could not register char device\n");
|
||||
goto fail3;
|
||||
}
|
||||
return 0;
|
||||
fail3:
|
||||
unregister_chrdev_region(litepcie_cdev, LITEPCIE_MINOR_COUNT);
|
||||
fail2:
|
||||
pci_unregister_driver(&litepcie_pci_driver);
|
||||
fail1:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit litepcie_module_exit(void)
|
||||
{
|
||||
cdev_del(&litepcie_cdev_struct);
|
||||
unregister_chrdev_region(litepcie_cdev, LITEPCIE_MINOR_COUNT);
|
||||
|
||||
pci_unregister_driver(&litepcie_pci_driver);
|
||||
}
|
||||
|
||||
|
||||
module_init(litepcie_module_init);
|
||||
module_exit(litepcie_module_exit);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
19
misoclib/com/litepcie/software/linux/user/Makefile
Normal file
19
misoclib/com/litepcie/software/linux/user/Makefile
Normal file
|
@ -0,0 +1,19 @@
|
|||
CFLAGS=-O2 -Wall -g -I../kernel -MMD
|
||||
LDFLAGS=-g
|
||||
CC=gcc
|
||||
AR=ar
|
||||
|
||||
PROGS=litepcie_util
|
||||
|
||||
all: $(PROGS)
|
||||
|
||||
litepcie_util: litepcie_util.o litepcie_lib.o
|
||||
$(CC) $(LDFLAGS) -o $@ $^ -lrt -lm
|
||||
|
||||
clean:
|
||||
rm -f $(PROGS) *.o *.a *.d *~
|
||||
|
||||
%.o: %.c
|
||||
$(CC) -c $(CFLAGS) -o $@ $<
|
||||
|
||||
-include $(wildcard *.d)
|
31
misoclib/com/litepcie/software/linux/user/cutils.h
Normal file
31
misoclib/com/litepcie/software/linux/user/cutils.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
#include <inttypes.h>
|
||||
#include <math.h>
|
||||
#include <immintrin.h>
|
||||
|
||||
#ifndef _BOOL_defined
|
||||
#define _BOOL_defined
|
||||
#undef FALSE
|
||||
#undef TRUE
|
||||
|
||||
typedef int BOOL;
|
||||
enum {
|
||||
FALSE = 0,
|
||||
TRUE = 1,
|
||||
};
|
||||
#endif
|
||||
|
||||
static inline int sub_mod_int(int a, int b, int m)
|
||||
{
|
||||
a -= b;
|
||||
if (a < 0)
|
||||
a += m;
|
||||
return a;
|
||||
}
|
||||
|
||||
static inline int add_mod_int(int a, int b, int m)
|
||||
{
|
||||
a += b;
|
||||
if (a >= m)
|
||||
a -= m;
|
||||
return a;
|
||||
}
|
182
misoclib/com/litepcie/software/linux/user/litepcie_lib.c
Normal file
182
misoclib/com/litepcie/software/linux/user/litepcie_lib.c
Normal file
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* LitePCIe library
|
||||
*
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include "litepcie.h"
|
||||
#include "cutils.h"
|
||||
#include "config.h"
|
||||
#include "csr.h"
|
||||
#include "flags.h"
|
||||
|
||||
#include "litepcie_lib.h"
|
||||
|
||||
/*
|
||||
TODO:
|
||||
- DMA overflow/underflow detection
|
||||
*/
|
||||
|
||||
void *litepcie_malloc(int size)
|
||||
{
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
void *litepcie_mallocz(int size)
|
||||
{
|
||||
void *ptr;
|
||||
ptr = litepcie_malloc(size);
|
||||
if (!ptr)
|
||||
return NULL;
|
||||
memset(ptr, 0, size);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void litepcie_free(void *ptr)
|
||||
{
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
void __attribute__((format(printf, 2, 3))) litepcie_log(LitePCIeState *s, const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* in ms */
|
||||
int64_t litepcie_get_time_ms(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
return (int64_t)ts.tv_sec * 1000 + (ts.tv_nsec / 1000000U);
|
||||
}
|
||||
|
||||
LitePCIeState *litepcie_open(const char *device_name)
|
||||
{
|
||||
LitePCIeState *s;
|
||||
|
||||
s = litepcie_mallocz(sizeof(LitePCIeState));
|
||||
if (!s)
|
||||
return NULL;
|
||||
|
||||
s->litepcie_fd = open(device_name, O_RDWR);
|
||||
if (s->litepcie_fd < 0) {
|
||||
perror(device_name);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* map the DMA buffers */
|
||||
if (ioctl(s->litepcie_fd, LITEPCIE_IOCTL_GET_MMAP_INFO, &s->mmap_info) != 0) {
|
||||
perror("LITEPCIE_IOCTL_GET_MMAP_INFO");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
s->dma_tx_buf = mmap(NULL, s->mmap_info.dma_tx_buf_size *
|
||||
s->mmap_info.dma_tx_buf_count,
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED, s->litepcie_fd,
|
||||
s->mmap_info.dma_tx_buf_offset);
|
||||
if (s->dma_tx_buf == MAP_FAILED) {
|
||||
perror("mmap1");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
s->dma_rx_buf = mmap(NULL, s->mmap_info.dma_rx_buf_size *
|
||||
s->mmap_info.dma_rx_buf_count,
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED, s->litepcie_fd,
|
||||
s->mmap_info.dma_rx_buf_offset);
|
||||
if (s->dma_rx_buf == MAP_FAILED) {
|
||||
perror("mmap2");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* map the registers */
|
||||
s->reg_buf = mmap(NULL, s->mmap_info.reg_size,
|
||||
PROT_READ | PROT_WRITE, MAP_SHARED, s->litepcie_fd,
|
||||
s->mmap_info.reg_offset);
|
||||
if (s->reg_buf == MAP_FAILED) {
|
||||
perror("mmap2");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
s->dma_tx_buf_size = s->mmap_info.dma_tx_buf_size;
|
||||
s->dma_rx_buf_size = s->mmap_info.dma_rx_buf_size;
|
||||
|
||||
pthread_mutex_init(&s->fifo_mutex, NULL);
|
||||
|
||||
return s;
|
||||
fail:
|
||||
litepcie_close(s);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void litepcie_dma_start(LitePCIeState *s, int buf_size, int buf_count, BOOL is_loopback)
|
||||
{
|
||||
struct litepcie_ioctl_dma_start dma_start;
|
||||
|
||||
if (buf_count > DMA_BUFFER_COUNT) {
|
||||
litepcie_log(s, "unsupported buf_count\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
s->tx_buf_size = s->rx_buf_size = buf_size;
|
||||
s->tx_buf_count = s->rx_buf_count = buf_count;
|
||||
|
||||
dma_start.dma_flags = 0;
|
||||
if (is_loopback)
|
||||
dma_start.dma_flags |= DMA_LOOPBACK_ENABLE;
|
||||
dma_start.tx_buf_size = s->tx_buf_size;
|
||||
dma_start.tx_buf_count = s->tx_buf_count;
|
||||
dma_start.rx_buf_size = s->rx_buf_size;
|
||||
dma_start.rx_buf_count = s->rx_buf_count;
|
||||
if (ioctl(s->litepcie_fd, LITEPCIE_IOCTL_DMA_START, &dma_start) < 0) {
|
||||
perror("LITEPCIE_IOCTL_DMA_START");
|
||||
}
|
||||
}
|
||||
|
||||
void litepcie_dma_stop(LitePCIeState *s)
|
||||
{
|
||||
if (ioctl(s->litepcie_fd, LITEPCIE_IOCTL_DMA_STOP, NULL) < 0) {
|
||||
perror("LITEPCIE_IOCTL_DMA_STOP");
|
||||
}
|
||||
}
|
||||
|
||||
void litepcie_writel(LitePCIeState *s, uint32_t addr, uint32_t val)
|
||||
{
|
||||
*(volatile uint32_t *)(s->reg_buf + addr) = val;
|
||||
}
|
||||
|
||||
uint32_t litepcie_readl(LitePCIeState *s, uint32_t addr)
|
||||
{
|
||||
return *(volatile uint32_t *)(s->reg_buf + addr);
|
||||
}
|
||||
|
||||
void litepcie_close(LitePCIeState *s)
|
||||
{
|
||||
pthread_mutex_destroy(&s->fifo_mutex);
|
||||
|
||||
if (s->dma_tx_buf) {
|
||||
munmap(s->dma_tx_buf, s->mmap_info.dma_tx_buf_size *
|
||||
s->mmap_info.dma_tx_buf_count);
|
||||
}
|
||||
if (s->dma_rx_buf) {
|
||||
munmap(s->dma_rx_buf, s->mmap_info.dma_rx_buf_size *
|
||||
s->mmap_info.dma_rx_buf_count);
|
||||
}
|
||||
if (s->reg_buf)
|
||||
munmap(s->reg_buf, s->mmap_info.reg_size);
|
||||
if (s->litepcie_fd >= 0)
|
||||
close(s->litepcie_fd);
|
||||
litepcie_free(s);
|
||||
}
|
53
misoclib/com/litepcie/software/linux/user/litepcie_lib.h
Normal file
53
misoclib/com/litepcie/software/linux/user/litepcie_lib.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* LitePCIe library
|
||||
*
|
||||
*/
|
||||
#ifndef LITEPCIE_LIB_H
|
||||
#define LITEPCIE_LIB_H
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <pthread.h>
|
||||
|
||||
#define LITEPCIE_FILENAME "/dev/litepcie0"
|
||||
|
||||
typedef struct {
|
||||
int litepcie_fd;
|
||||
struct litepcie_ioctl_mmap_info mmap_info;
|
||||
uint8_t *dma_tx_buf;
|
||||
int dma_tx_buf_size;
|
||||
uint8_t *dma_rx_buf;
|
||||
int dma_rx_buf_size;
|
||||
uint8_t *reg_buf;
|
||||
|
||||
unsigned int tx_buf_size; /* in bytes */
|
||||
unsigned int tx_buf_count; /* number of buffers */
|
||||
unsigned int rx_buf_size; /* in bytes */
|
||||
unsigned int rx_buf_count; /* number of buffers */
|
||||
|
||||
unsigned int tx_buf_len; /* in samples */
|
||||
unsigned int rx_buf_len; /* in samples */
|
||||
|
||||
pthread_mutex_t fifo_mutex;
|
||||
int64_t rx_timestamp; /* timestamp (in samples) of the current RX buffer */
|
||||
unsigned int rx_buf_index; /* index of the current RX buffer */
|
||||
unsigned int rx_buf_next; /* index of the next buffer after the
|
||||
last received buffer */
|
||||
BOOL has_rx_timestamp; /* true if received at least one buffer */
|
||||
|
||||
int64_t tx_underflow_count; /* TX too late */
|
||||
int64_t rx_overflow_count; /* RX too late */
|
||||
} LitePCIeState;
|
||||
|
||||
void *litepcie_malloc(int size);
|
||||
void *litepcie_mallocz(int size);
|
||||
void litepcie_free(void *ptr);
|
||||
void __attribute__((format(printf, 2, 3))) litepcie_log(LitePCIeState *s, const char *fmt, ...);
|
||||
int64_t litepcie_get_time_ms(void);
|
||||
LitePCIeState *litepcie_open(const char *device_name);
|
||||
void litepcie_close(LitePCIeState *s);
|
||||
void litepcie_dma_start(LitePCIeState *s, int buf_size, int buf_count, BOOL is_loopback);
|
||||
void litepcie_dma_stop(LitePCIeState *s);
|
||||
void litepcie_writel(LitePCIeState *s, uint32_t addr, uint32_t val);
|
||||
uint32_t litepcie_readl(LitePCIeState *s, uint32_t addr);
|
||||
|
||||
#endif /* LITEPCIE_LIB_H */
|
259
misoclib/com/litepcie/software/linux/user/litepcie_util.c
Normal file
259
misoclib/com/litepcie/software/linux/user/litepcie_util.c
Normal file
|
@ -0,0 +1,259 @@
|
|||
/*
|
||||
* LitePCIe utilities
|
||||
*
|
||||
*/
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "litepcie.h"
|
||||
#include "cutils.h"
|
||||
#include "config.h"
|
||||
#include "csr.h"
|
||||
#include "flags.h"
|
||||
#include "litepcie_lib.h"
|
||||
|
||||
static inline uint32_t seed_to_data(uint32_t seed)
|
||||
{
|
||||
#if 1
|
||||
/* more random but slower */
|
||||
return seed * 0x31415976 + 1;
|
||||
#else
|
||||
/* simplify debug: just copy the counter */
|
||||
return seed;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void write_pn_data(uint32_t *dst, int count, uint32_t *pseed)
|
||||
{
|
||||
int i;
|
||||
uint32_t seed;
|
||||
|
||||
seed = *pseed;
|
||||
for(i = 0; i < count; i++) {
|
||||
dst[i] = seed_to_data(seed);
|
||||
seed++;
|
||||
}
|
||||
*pseed = seed;
|
||||
}
|
||||
|
||||
/* Return the number of errors */
|
||||
static int check_pn_data(const uint32_t *tab, int count,
|
||||
uint32_t *pseed)
|
||||
{
|
||||
int i, errors;
|
||||
uint32_t seed;
|
||||
|
||||
errors = 0;
|
||||
seed = *pseed;
|
||||
for(i = 0; i < count; i++) {
|
||||
if (tab[i] != seed_to_data(seed)) {
|
||||
errors++;
|
||||
}
|
||||
seed++;
|
||||
}
|
||||
*pseed = seed;
|
||||
return errors;
|
||||
}
|
||||
|
||||
#define MAX_SHIFT_OFFSET 128
|
||||
|
||||
/* test DMA with a buffer size of buf_size bytes in loopback
|
||||
mode. */
|
||||
void dma_test(LitePCIeState *s, int buf_size, int buf_count, BOOL is_loopback)
|
||||
{
|
||||
int is_first, tx_buf_num, buf_num_cur, buf_num_next;
|
||||
struct litepcie_ioctl_dma_wait dma_wait;
|
||||
int buf_stats_count; /* statistics */
|
||||
int64_t last_time;
|
||||
uint32_t tx_seed, rx_seed;
|
||||
int buf_rx_count, first_rx_buf, rx_errors, shift, d, tx_underflows;
|
||||
|
||||
litepcie_dma_start(s, buf_size, buf_count, is_loopback);
|
||||
|
||||
is_first = 1;
|
||||
buf_num_cur = 0; /* next buffer to receive */
|
||||
/* PN data TX and RX state */
|
||||
tx_seed = MAX_SHIFT_OFFSET;
|
||||
rx_seed = 0;
|
||||
buf_rx_count = 0;
|
||||
first_rx_buf = 1;
|
||||
|
||||
/* statistics */
|
||||
buf_stats_count = 0;
|
||||
last_time = litepcie_get_time_ms();
|
||||
rx_errors = 0;
|
||||
shift = 0;
|
||||
tx_underflows = 0;
|
||||
|
||||
for(;;) {
|
||||
/* wait until at least one buffer is received */
|
||||
dma_wait.timeout = 1000; /* 1 second timeout */
|
||||
dma_wait.tx_wait = FALSE;
|
||||
dma_wait.tx_buf_num = -1; /* not used */
|
||||
if (is_first) {
|
||||
dma_wait.rx_buf_num = -1; /* don't wait, just get the last
|
||||
received buffer number */
|
||||
} else {
|
||||
dma_wait.rx_buf_num = sub_mod_int(buf_num_cur, 1, buf_count);
|
||||
}
|
||||
/* wait until the current buffer number is different from
|
||||
dma_wait.buf_num */
|
||||
if (ioctl(s->litepcie_fd, LITEPCIE_IOCTL_DMA_WAIT, &dma_wait) < 0) {
|
||||
perror("LITEPCIE_IOCTL_DMA_WAIT");
|
||||
}
|
||||
if (is_first) {
|
||||
buf_num_cur = dma_wait.rx_buf_num;
|
||||
is_first = 0;
|
||||
}
|
||||
buf_num_next = add_mod_int(dma_wait.rx_buf_num, 1, buf_count);
|
||||
|
||||
while (buf_num_cur != buf_num_next) {
|
||||
|
||||
/* write the TX data 4/10 of a DMA cycle in the future */
|
||||
tx_buf_num = add_mod_int(buf_num_cur, 4*buf_count/10, buf_count);
|
||||
d = sub_mod_int(tx_buf_num, buf_num_next, buf_count);
|
||||
if (d >= (buf_count / 2)) {
|
||||
/* we are too late in writing data, which necessarily
|
||||
gives read errors. */
|
||||
tx_underflows++;
|
||||
}
|
||||
|
||||
write_pn_data((uint32_t *)(s->dma_tx_buf +
|
||||
tx_buf_num * s->dma_tx_buf_size),
|
||||
s->tx_buf_size >> 2, &tx_seed);
|
||||
|
||||
if (buf_rx_count >= 4*buf_count/10) {
|
||||
const uint32_t *rx_buf;
|
||||
int rx_buf_len;
|
||||
|
||||
rx_buf = (uint32_t *)(s->dma_rx_buf + buf_num_cur * s->dma_rx_buf_size);
|
||||
rx_buf_len = s->rx_buf_size >> 2;
|
||||
|
||||
if (first_rx_buf) {
|
||||
uint32_t seed;
|
||||
|
||||
/* find the initial shift */
|
||||
for(shift = 0; shift < 2 * MAX_SHIFT_OFFSET; shift++) {
|
||||
seed = rx_seed + shift;
|
||||
rx_errors = check_pn_data(rx_buf, rx_buf_len, &seed);
|
||||
if (rx_errors <= (rx_buf_len / 2)) {
|
||||
rx_seed = seed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (shift == 2 * MAX_SHIFT_OFFSET) {
|
||||
printf("Cannot find initial data\n");
|
||||
exit(1);
|
||||
} else {
|
||||
printf("RX shift = %d\n",
|
||||
-(shift - MAX_SHIFT_OFFSET));
|
||||
}
|
||||
first_rx_buf = 0;
|
||||
} else {
|
||||
/* count the number of errors */
|
||||
rx_errors += check_pn_data(rx_buf, rx_buf_len, &rx_seed);
|
||||
}
|
||||
} else {
|
||||
buf_rx_count++;
|
||||
}
|
||||
|
||||
buf_num_cur = add_mod_int(buf_num_cur, 1, buf_count);
|
||||
|
||||
/* statistics */
|
||||
if (++buf_stats_count == 10000) {
|
||||
int64_t duration;
|
||||
duration = litepcie_get_time_ms() - last_time;
|
||||
printf("%0.1f Gb/sec %0.1f bufs/sec tx_underflows=%d errors=%d\n",
|
||||
(double)buf_stats_count * buf_size * 8 / ((double)duration * 1e6),
|
||||
(double)buf_stats_count * 1000 / (double)duration,
|
||||
tx_underflows, rx_errors);
|
||||
last_time = litepcie_get_time_ms();
|
||||
buf_stats_count = 0;
|
||||
tx_underflows = 0;
|
||||
rx_errors = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
litepcie_dma_stop(s);
|
||||
}
|
||||
|
||||
void dma_loopback_test(void)
|
||||
{
|
||||
LitePCIeState *s;
|
||||
|
||||
s = litepcie_open(LITEPCIE_FILENAME);
|
||||
if (!s) {
|
||||
fprintf(stderr, "Could not init driver\n");
|
||||
exit(1);
|
||||
}
|
||||
dma_test(s, 16*1024, DMA_BUFFER_COUNT, TRUE);
|
||||
|
||||
litepcie_close(s);
|
||||
}
|
||||
|
||||
void dump_version(void)
|
||||
{
|
||||
LitePCIeState *s;
|
||||
|
||||
s = litepcie_open(LITEPCIE_FILENAME);
|
||||
if (!s) {
|
||||
fprintf(stderr, "Could not init driver\n");
|
||||
exit(1);
|
||||
}
|
||||
printf("sysid=0x%x\n", litepcie_readl(s, CSR_IDENTIFIER_SYSID_ADDR));
|
||||
printf("frequency=%d\n", litepcie_readl(s, CSR_IDENTIFIER_FREQUENCY_ADDR));
|
||||
|
||||
litepcie_close(s);
|
||||
}
|
||||
|
||||
void help(void)
|
||||
{
|
||||
printf("usage: litepcie_util cmd [args...]\n"
|
||||
"\n"
|
||||
"available commands:\n"
|
||||
"dma_loopback_test test DMA loopback operation\n"
|
||||
"version return fpga version\n"
|
||||
);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
const char *cmd;
|
||||
int c;
|
||||
|
||||
for(;;) {
|
||||
c = getopt(argc, argv, "h");
|
||||
if (c == -1)
|
||||
break;
|
||||
switch(c) {
|
||||
case 'h':
|
||||
help();
|
||||
break;
|
||||
default:
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind >= argc)
|
||||
help();
|
||||
cmd = argv[optind++];
|
||||
|
||||
if (!strcmp(cmd, "dma_loopback_test")) {
|
||||
dma_loopback_test();
|
||||
} else if (!strcmp(cmd, "version")) {
|
||||
dump_version();
|
||||
} else {
|
||||
help();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in a new issue