litepcie: add linux driver + utilities (sysfs + dma)
authorFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 17 Apr 2015 11:48:34 +0000 (13:48 +0200)
committerFlorent Kermarrec <florent@enjoy-digital.fr>
Fri, 17 Apr 2015 11:48:34 +0000 (13:48 +0200)
12 files changed:
misoclib/com/litepcie/software/linux/kernel/Makefile [new file with mode: 0644]
misoclib/com/litepcie/software/linux/kernel/README [new file with mode: 0644]
misoclib/com/litepcie/software/linux/kernel/config.h [new file with mode: 0644]
misoclib/com/litepcie/software/linux/kernel/flags.h [new file with mode: 0644]
misoclib/com/litepcie/software/linux/kernel/init.sh [new file with mode: 0644]
misoclib/com/litepcie/software/linux/kernel/litepcie.h [new file with mode: 0644]
misoclib/com/litepcie/software/linux/kernel/main.c [new file with mode: 0644]
misoclib/com/litepcie/software/linux/user/Makefile [new file with mode: 0644]
misoclib/com/litepcie/software/linux/user/cutils.h [new file with mode: 0644]
misoclib/com/litepcie/software/linux/user/litepcie_lib.c [new file with mode: 0644]
misoclib/com/litepcie/software/linux/user/litepcie_lib.h [new file with mode: 0644]
misoclib/com/litepcie/software/linux/user/litepcie_util.c [new file with mode: 0644]

diff --git a/misoclib/com/litepcie/software/linux/kernel/Makefile b/misoclib/com/litepcie/software/linux/kernel/Makefile
new file mode 100644 (file)
index 0000000..d50f989
--- /dev/null
@@ -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 *~
diff --git a/misoclib/com/litepcie/software/linux/kernel/README b/misoclib/com/litepcie/software/linux/kernel/README
new file mode 100644 (file)
index 0000000..9ec9bb6
--- /dev/null
@@ -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
diff --git a/misoclib/com/litepcie/software/linux/kernel/config.h b/misoclib/com/litepcie/software/linux/kernel/config.h
new file mode 100644 (file)
index 0000000..787ae1d
--- /dev/null
@@ -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 */
diff --git a/misoclib/com/litepcie/software/linux/kernel/flags.h b/misoclib/com/litepcie/software/linux/kernel/flags.h
new file mode 100644 (file)
index 0000000..f548693
--- /dev/null
@@ -0,0 +1,7 @@
+#ifndef __HW_FLAGS_H
+#define __HW_FLAGS_H
+
+/* dma */
+#define DMA_LOOPBACK_ENABLE 0x1
+
+#endif /* __HW_FLAGS_H */
diff --git a/misoclib/com/litepcie/software/linux/kernel/init.sh b/misoclib/com/litepcie/software/linux/kernel/init.sh
new file mode 100644 (file)
index 0000000..82cb453
--- /dev/null
@@ -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
diff --git a/misoclib/com/litepcie/software/linux/kernel/litepcie.h b/misoclib/com/litepcie/software/linux/kernel/litepcie.h
new file mode 100644 (file)
index 0000000..8fd80b7
--- /dev/null
@@ -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 */
diff --git a/misoclib/com/litepcie/software/linux/kernel/main.c b/misoclib/com/litepcie/software/linux/kernel/main.c
new file mode 100644 (file)
index 0000000..d5ae258
--- /dev/null
@@ -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");
diff --git a/misoclib/com/litepcie/software/linux/user/Makefile b/misoclib/com/litepcie/software/linux/user/Makefile
new file mode 100644 (file)
index 0000000..3a68e9e
--- /dev/null
@@ -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)
diff --git a/misoclib/com/litepcie/software/linux/user/cutils.h b/misoclib/com/litepcie/software/linux/user/cutils.h
new file mode 100644 (file)
index 0000000..0d51daa
--- /dev/null
@@ -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;
+}
diff --git a/misoclib/com/litepcie/software/linux/user/litepcie_lib.c b/misoclib/com/litepcie/software/linux/user/litepcie_lib.c
new file mode 100644 (file)
index 0000000..e1d6475
--- /dev/null
@@ -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);
+}
diff --git a/misoclib/com/litepcie/software/linux/user/litepcie_lib.h b/misoclib/com/litepcie/software/linux/user/litepcie_lib.h
new file mode 100644 (file)
index 0000000..d4415ef
--- /dev/null
@@ -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 */
diff --git a/misoclib/com/litepcie/software/linux/user/litepcie_util.c b/misoclib/com/litepcie/software/linux/user/litepcie_util.c
new file mode 100644 (file)
index 0000000..031f34d
--- /dev/null
@@ -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;
+}