From: Florent Kermarrec Date: Fri, 17 Apr 2015 11:48:34 +0000 (+0200) Subject: litepcie: add linux driver + utilities (sysfs + dma) X-Git-Tag: 24jan2021_ls180~2310 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=b4b37fb10e1a4dc910d2e551754f4dd7ce39929f;p=litex.git litepcie: add linux driver + utilities (sysfs + dma) --- diff --git a/misoclib/com/litepcie/software/linux/kernel/Makefile b/misoclib/com/litepcie/software/linux/kernel/Makefile new file mode 100644 index 00000000..d50f989b --- /dev/null +++ b/misoclib/com/litepcie/software/linux/kernel/Makefile @@ -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 index 00000000..9ec9bb60 --- /dev/null +++ b/misoclib/com/litepcie/software/linux/kernel/README @@ -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 index 00000000..787ae1d9 --- /dev/null +++ b/misoclib/com/litepcie/software/linux/kernel/config.h @@ -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 index 00000000..f548693f --- /dev/null +++ b/misoclib/com/litepcie/software/linux/kernel/flags.h @@ -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 index 00000000..82cb4533 --- /dev/null +++ b/misoclib/com/litepcie/software/linux/kernel/init.sh @@ -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 index 00000000..8fd80b70 --- /dev/null +++ b/misoclib/com/litepcie/software/linux/kernel/litepcie.h @@ -0,0 +1,50 @@ +/* + * LitePCIe driver + * + */ +#ifndef _LINUX_LITEPCIE_H +#define _LINUX_LITEPCIE_H + +#include + +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 index 00000000..d5ae258f --- /dev/null +++ b/misoclib/com/litepcie/software/linux/kernel/main.c @@ -0,0 +1,639 @@ +/* + * LitePCIe driver + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 00000000..3a68e9e5 --- /dev/null +++ b/misoclib/com/litepcie/software/linux/user/Makefile @@ -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 index 00000000..0d51daa9 --- /dev/null +++ b/misoclib/com/litepcie/software/linux/user/cutils.h @@ -0,0 +1,31 @@ +#include +#include +#include + +#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 index 00000000..e1d64758 --- /dev/null +++ b/misoclib/com/litepcie/software/linux/user/litepcie_lib.c @@ -0,0 +1,182 @@ +/* + * LitePCIe library + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 index 00000000..d4415ef0 --- /dev/null +++ b/misoclib/com/litepcie/software/linux/user/litepcie_lib.h @@ -0,0 +1,53 @@ +/* + * LitePCIe library + * + */ +#ifndef LITEPCIE_LIB_H +#define LITEPCIE_LIB_H + +#include +#include + +#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 index 00000000..031f34d8 --- /dev/null +++ b/misoclib/com/litepcie/software/linux/user/litepcie_util.c @@ -0,0 +1,259 @@ +/* + * LitePCIe utilities + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +}