--- /dev/null
+/*
+ * 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");
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+}