diff options
Diffstat (limited to 'drivers/staging/altpciechdma/altpciechdma.c')
-rw-r--r-- | drivers/staging/altpciechdma/altpciechdma.c | 1184 |
1 files changed, 1184 insertions, 0 deletions
diff --git a/drivers/staging/altpciechdma/altpciechdma.c b/drivers/staging/altpciechdma/altpciechdma.c new file mode 100644 index 00000000000..8e2b4ca0651 --- /dev/null +++ b/drivers/staging/altpciechdma/altpciechdma.c @@ -0,0 +1,1184 @@ +/** + * Driver for Altera PCIe core chaining DMA reference design. + * + * Copyright (C) 2008 Leon Woestenberg <leon.woestenberg@axon.tv> + * Copyright (C) 2008 Nickolas Heppermann <heppermannwdt@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * + * Rationale: This driver exercises the chaining DMA read and write engine + * in the reference design. It is meant as a complementary reference + * driver that can be used for testing early designs as well as a basis to + * write your custom driver. + * + * Status: Test results from Leon Woestenberg <leon.woestenberg@axon.tv>: + * + * Sendero Board w/ Cyclone II EP2C35F672C6N, PX1011A PCIe x1 PHY on a + * Dell Precision 370 PC, x86, kernel 2.6.20 from Ubuntu 7.04. + * + * Sendero Board w/ Cyclone II EP2C35F672C6N, PX1011A PCIe x1 PHY on a + * Freescale MPC8313E-RDB board, PowerPC, 2.6.24 w/ Freescale patches. + * + * Driver tests passed with PCIe Compiler 8.1. With PCIe 8.0 the DMA + * loopback test had reproducable compare errors. I assume a change + * in the compiler or reference design, but could not find evidence nor + * documentation on a change or fix in that direction. + * + * The reference design does not have readable locations and thus a + * dummy read, used to flush PCI posted writes, cannot be performed. + * + */ + +#include <linux/kernel.h> +#include <linux/cdev.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/pci.h> + + +/* by default do not build the character device interface */ +/* XXX It is non-functional yet */ +#ifndef ALTPCIECHDMA_CDEV +# define ALTPCIECHDMA_CDEV 0 +#endif + +/* build the character device interface? */ +#if ALTPCIECHDMA_CDEV +# define MAX_CHDMA_SIZE (8 * 1024 * 1024) +# include "mapper_user_to_sg.h" +#endif + +/** driver name, mimicks Altera naming of the reference design */ +#define DRV_NAME "altpciechdma" +/** number of BARs on the device */ +#define APE_BAR_NUM (6) +/** BAR number where the RCSLAVE memory sits */ +#define APE_BAR_RCSLAVE (0) +/** BAR number where the Descriptor Header sits */ +#define APE_BAR_HEADER (2) + +/** maximum size in bytes of the descriptor table, chdma logic limit */ +#define APE_CHDMA_TABLE_SIZE (4096) +/* single transfer must not exceed 255 table entries. worst case this can be + * achieved by 255 scattered pages, with only a single byte in the head and + * tail pages. 253 * PAGE_SIZE is a safe upper bound for the transfer size. + */ +#define APE_CHDMA_MAX_TRANSFER_LEN (253 * PAGE_SIZE) + +/** + * Specifies those BARs to be mapped and the length of each mapping. + * + * Zero (0) means do not map, otherwise specifies the BAR lengths to be mapped. + * If the actual BAR length is less, this is considered an error; then + * reconfigure your PCIe core. + * + * @see ug_pci_express 8.0, table 7-2 at page 7-13. + */ +static const unsigned long bar_min_len[APE_BAR_NUM] = + { 32768, 0, 256, 0, 32768, 0 }; + +/** + * Descriptor Header, controls the DMA read engine or write engine. + * + * The descriptor header is the main data structure for starting DMA transfers. + * + * It sits in End Point (FPGA) memory BAR[2] for 32-bit or BAR[3:2] for 64-bit. + * It references a descriptor table which exists in Root Complex (PC) memory. + * Writing the rclast field starts the DMA operation, thus all other structures + * and fields must be setup before doing so. + * + * @see ug_pci_express 8.0, tables 7-3, 7-4 and 7-5 at page 7-14. + * @note This header must be written in four 32-bit (PCI DWORD) writes. + */ +struct ape_chdma_header { + /** + * w0 consists of two 16-bit fields: + * lsb u16 number; number of descriptors in ape_chdma_table + * msb u16 control; global control flags + */ + u32 w0; + /* bus address to ape_chdma_table in Root Complex memory */ + u32 bdt_addr_h; + u32 bdt_addr_l; + /** + * w3 consists of two 16-bit fields: + * - lsb u16 rclast; last descriptor number available in Root Complex + * - zero (0) means the first descriptor is ready, + * - one (1) means two descriptors are ready, etc. + * - msb u16 reserved; + * + * @note writing to this memory location starts the DMA operation! + */ + u32 w3; +} __attribute__ ((packed)); + +/** + * Descriptor Entry, describing a (non-scattered) single memory block transfer. + * + * There is one descriptor for each memory block involved in the transfer, a + * block being a contiguous address range on the bus. + * + * Multiple descriptors are chained by means of the ape_chdma_table data + * structure. + * + * @see ug_pci_express 8.0, tables 7-6, 7-7 and 7-8 at page 7-14 and page 7-15. + */ +struct ape_chdma_desc { + /** + * w0 consists of two 16-bit fields: + * number of DWORDS to transfer + * - lsb u16 length; + * global control + * - msb u16 control; + */ + u32 w0; + /* address of memory in the End Point */ + u32 ep_addr; + /* bus address of source or destination memory in the Root Complex */ + u32 rc_addr_h; + u32 rc_addr_l; +} __attribute__ ((packed)); + +/** + * Descriptor Table, an array of descriptors describing a chained transfer. + * + * An array of descriptors, preceded by workspace for the End Point. + * It exists in Root Complex memory. + * + * The End Point can update its last completed descriptor number in the + * eplast field if requested by setting the EPLAST_ENA bit either + * globally in the header's or locally in any descriptor's control field. + * + * @note this structure may not exceed 4096 bytes. This results in a + * maximum of 4096 / (4 * 4) - 1 = 255 descriptors per chained transfer. + * + * @see ug_pci_express 8.0, tables 7-9, 7-10 and 7-11 at page 7-17 and page 7-18. + */ +struct ape_chdma_table { + /* workspace 0x00-0x0b, reserved */ + u32 reserved1[3]; + /* workspace 0x0c-0x0f, last descriptor handled by End Point */ + u32 w3; + /* the actual array of descriptors + * 0x10-0x1f, 0x20-0x2f, ... 0xff0-0xfff (255 entries) + */ + struct ape_chdma_desc desc[255]; +} __attribute__ ((packed)); + +/** + * Altera PCI Express ('ape') board specific book keeping data + * + * Keeps state of the PCIe core and the Chaining DMA controller + * application. + */ +struct ape_dev { + /** the kernel pci device data structure provided by probe() */ + struct pci_dev *pci_dev; + /** + * kernel virtual address of the mapped BAR memory and IO regions of + * the End Point. Used by map_bars()/unmap_bars(). + */ + void * __iomem bar[APE_BAR_NUM]; + /** kernel virtual address for Descriptor Table in Root Complex memory */ + struct ape_chdma_table *table_virt; + /** + * bus address for the Descriptor Table in Root Complex memory, in + * CPU-native endianess + */ + dma_addr_t table_bus; + /* if the device regions could not be allocated, assume and remember it + * is in use by another driver; this driver must not disable the device. + */ + int in_use; + /* whether this driver enabled msi for the device */ + int msi_enabled; + /* whether this driver could obtain the regions */ + int got_regions; + /* irq line succesfully requested by this driver, -1 otherwise */ + int irq_line; + /* board revision */ + u8 revision; + /* interrupt count, incremented by the interrupt handler */ + int irq_count; +#if ALTPCIECHDMA_CDEV + /* character device */ + dev_t cdevno; + struct cdev cdev; + /* user space scatter gather mapper */ + struct sg_mapping_t *sgm; +#endif +}; + +/** + * Using the subsystem vendor id and subsystem id, it is possible to + * distinguish between different cards bases around the same + * (third-party) logic core. + * + * Default Altera vendor and device ID's, and some (non-reserved) + * ID's are now used here that are used amongst the testers/developers. + */ +static const struct pci_device_id ids[] = { + { PCI_DEVICE(0x1172, 0xE001), }, + { PCI_DEVICE(0x2071, 0x2071), }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, ids); + +#if ALTPCIECHDMA_CDEV +/* prototypes for character device */ +static int sg_init(struct ape_dev *ape); +static void sg_exit(struct ape_dev *ape); +#endif + +/** + * altpciechdma_isr() - Interrupt handler + * + */ +static irqreturn_t altpciechdma_isr(int irq, void *dev_id) +{ + struct ape_dev *ape = (struct ape_dev *)dev_id; + if (!ape) + return IRQ_NONE; + ape->irq_count++; + return IRQ_HANDLED; +} + +static int __devinit scan_bars(struct ape_dev *ape, struct pci_dev *dev) +{ + int i; + for (i = 0; i < APE_BAR_NUM; i++) { + unsigned long bar_start = pci_resource_start(dev, i); + if (bar_start) { + unsigned long bar_end = pci_resource_end(dev, i); + unsigned long bar_flags = pci_resource_flags(dev, i); + printk(KERN_DEBUG "BAR%d 0x%08lx-0x%08lx flags 0x%08lx\n", + i, bar_start, bar_end, bar_flags); + } + } + return 0; +} + +/** + * Unmap the BAR regions that had been mapped earlier using map_bars() + */ +static void unmap_bars(struct ape_dev *ape, struct pci_dev *dev) +{ + int i; + for (i = 0; i < APE_BAR_NUM; i++) { + /* is this BAR mapped? */ + if (ape->bar[i]) { + /* unmap BAR */ + pci_iounmap(dev, ape->bar[i]); + ape->bar[i] = NULL; + } + } +} + +/** + * Map the device memory regions into kernel virtual address space after + * verifying their sizes respect the minimum sizes needed, given by the + * bar_min_len[] array. + */ +static int __devinit map_bars(struct ape_dev *ape, struct pci_dev *dev) +{ + int rc; + int i; + /* iterate through all the BARs */ + for (i = 0; i < APE_BAR_NUM; i++) { + unsigned long bar_start = pci_resource_start(dev, i); + unsigned long bar_end = pci_resource_end(dev, i); + unsigned long bar_length = bar_end - bar_start + 1; + ape->bar[i] = NULL; + /* do not map, and skip, BARs with length 0 */ + if (!bar_min_len[i]) + continue; + /* do not map BARs with address 0 */ + if (!bar_start || !bar_end) { + printk(KERN_DEBUG "BAR #%d is not present?!\n", i); + rc = -1; + goto fail; + } + bar_length = bar_end - bar_start + 1; + /* BAR length is less than driver requires? */ + if (bar_length < bar_min_len[i]) { + printk(KERN_DEBUG "BAR #%d length = %lu bytes but driver " + "requires at least %lu bytes\n", i, bar_length, bar_min_len[i]); + rc = -1; + goto fail; + } + /* map the device memory or IO region into kernel virtual + * address space */ + ape->bar[i] = pci_iomap(dev, i, bar_min_len[i]); + if (!ape->bar[i]) { + printk(KERN_DEBUG "Could not map BAR #%d.\n", i); + rc = -1; + goto fail; + } + printk(KERN_DEBUG "BAR[%d] mapped at 0x%p with length %lu(/%lu).\n", i, + ape->bar[i], bar_min_len[i], bar_length); + } + /* succesfully mapped all required BAR regions */ + rc = 0; + goto success; +fail: + /* unmap any BARs that we did map */ + unmap_bars(ape, dev); +success: + return rc; +} + +#if 0 /* not yet implemented fully FIXME add opcode */ +static void __devinit rcslave_test(struct ape_dev *ape, struct pci_dev *dev) +{ + u32 *rcslave_mem = (u32 *)ape->bar[APE_BAR_RCSLAVE]; + u32 result = 0; + /** this number is assumed to be different each time this test runs */ + u32 seed = (u32)jiffies; + u32 value = seed; + int i; + + /* write loop */ + value = seed; + for (i = 1024; i < 32768 / 4 ; i++) { + printk(KERN_DEBUG "Writing 0x%08x to 0x%p.\n", + (u32)value, (void *)rcslave_mem + i); + iowrite32(value, rcslave_mem + i); + value++; + } + /* read-back loop */ + value = seed; + for (i = 1024; i < 32768 / 4; i++) { + result = ioread32(rcslave_mem + i); + if (result != value) { + printk(KERN_DEBUG "Wrote 0x%08x to 0x%p, but read back 0x%08x.\n", + (u32)value, (void *)rcslave_mem + i, (u32)result); + break; + } + value++; + } +} +#endif + +/* obtain the 32 most significant (high) bits of a 32-bit or 64-bit address */ +#define pci_dma_h(addr) ((addr >> 16) >> 16) +/* obtain the 32 least significant (low) bits of a 32-bit or 64-bit address */ +#define pci_dma_l(addr) (addr & 0xffffffffUL) + +/* ape_fill_chdma_desc() - Fill a Altera PCI Express Chaining DMA descriptor + * + * @desc pointer to descriptor to be filled + * @addr root complex address + * @ep_addr end point address + * @len number of bytes, must be a multiple of 4. + */ +static inline void ape_chdma_desc_set(struct ape_chdma_desc *desc, dma_addr_t addr, u32 ep_addr, int len) +{ + BUG_ON(len & 3); + desc->w0 = cpu_to_le32(len / 4); + desc->ep_addr = cpu_to_le32(ep_addr); + desc->rc_addr_h = cpu_to_le32(pci_dma_h(addr)); + desc->rc_addr_l = cpu_to_le32(pci_dma_l(addr)); +} + +/* + * ape_sg_to_chdma_table() - Create a device descriptor table from a scatterlist. + * + * The scatterlist must have been mapped by pci_map_sg(sgm->sgl). + * + * @sgl scatterlist. + * @nents Number of entries in the scatterlist. + * @first Start index in the scatterlist sgm->sgl. + * @ep_addr End Point address for the scatter/gather transfer. + * @desc pointer to first descriptor + * + * Returns Number of entries in the table on success, -1 on error. + */ +static int ape_sg_to_chdma_table(struct scatterlist *sgl, int nents, int first, struct ape_chdma_desc *desc, u32 ep_addr) +{ + int i = first, j = 0; + /* inspect first entry */ + dma_addr_t addr = sg_dma_address(&sgl[i]); + unsigned int len = sg_dma_len(&sgl[i]); + /* contiguous block */ + dma_addr_t cont_addr = addr; + unsigned int cont_len = len; + /* iterate over remaining entries */ + for (; j < 25 && i < nents - 1; i++) { + /* bus address of next entry i + 1 */ + dma_addr_t next = sg_dma_address(&sgl[i + 1]); + /* length of this entry i */ + len = sg_dma_len(&sgl[i]); + printk(KERN_DEBUG "%04d: addr=0x%08x length=0x%08x\n", i, addr, len); + /* entry i + 1 is non-contiguous with entry i? */ + if (next != addr + len) { + /* TODO create entry here (we could overwrite i) */ + printk(KERN_DEBUG "%4d: cont_addr=0x%08x cont_len=0x%08x\n", j, cont_addr, cont_len); + /* set descriptor for contiguous transfer */ + ape_chdma_desc_set(&desc[j], cont_addr, ep_addr, cont_len); + /* next end point memory address */ + ep_addr += cont_len; + /* start new contiguous block */ + cont_addr = next; + cont_len = 0; + j++; + } + /* add entry i + 1 to current contiguous block */ + cont_len += len; + /* goto entry i + 1 */ + addr = next; + } + /* TODO create entry here (we could overwrite i) */ + printk(KERN_DEBUG "%04d: addr=0x%08x length=0x%08x\n", i, addr, len); + printk(KERN_DEBUG "%4d: cont_addr=0x%08x length=0x%08x\n", j, cont_addr, cont_len); + j++; + return j; +} + +/* compare buffers */ +static inline int compare(u32 *p, u32 *q, int len) +{ + int result = -1; + int fail = 0; + int i; + for (i = 0; i < len / 4; i++) { + if (*p == *q) { + /* every so many u32 words, show equals */ + if ((i & 255) == 0) + printk(KERN_DEBUG "[%p] = 0x%08x [%p] = 0x%08x\n", p, *p, q, *q); + } else { + fail++; + /* show the first few miscompares */ + if (fail < 10) { + printk(KERN_DEBUG "[%p] = 0x%08x != [%p] = 0x%08x ?!\n", p, *p, q, *q); + /* but stop after a while */ + } else if (fail == 10) { + printk(KERN_DEBUG "---more errors follow! not printed---\n"); + } else { + /* stop compare after this many errors */ + break; + } + } + p++; + q++; + } + if (!fail) + result = 0; + return result; +} + +/* dma_test() - Perform DMA loop back test to end point and back to root complex. + * + * Allocate a cache-coherent buffer in host memory, consisting of four pages. + * + * Fill the four memory pages such that each 32-bit word contains its own address. + * + * Now perform a loop back test, have the end point device copy the first buffer + * half to end point memory, then have it copy back into the second half. + * + * Create a descriptor table to copy the first buffer half into End Point + * memory. Instruct the End Point to do a DMA read using that table. + * + * Create a descriptor table to copy End Point memory to the second buffer + * half. Instruct the End Point to do a DMA write using that table. + * + * Compare results, fail or pass. + * + */ +static int __devinit dma_test(struct ape_dev *ape, struct pci_dev *dev) +{ + /* test result; guilty until proven innocent */ + int result = -1; + /* the DMA read header sits at address 0x00 of the DMA engine BAR */ + struct ape_chdma_header *write_header = (struct ape_chdma_header *)ape->bar[APE_BAR_HEADER]; + /* the write DMA header sits after the read header at address 0x10 */ + struct ape_chdma_header *read_header = write_header + 1; + /* virtual address of the allocated buffer */ + u8 *buffer_virt = 0; + /* bus address of the allocated buffer */ + dma_addr_t buffer_bus = 0; + int i, n = 0, irq_count; + + /* temporary value used to construct 32-bit data words */ + u32 w; + + printk(KERN_DEBUG "bar_tests(), PAGE_SIZE = 0x%0x\n", (int)PAGE_SIZE); + printk(KERN_DEBUG "write_header = 0x%p.\n", write_header); + printk(KERN_DEBUG "read_header = 0x%p.\n", read_header); + printk(KERN_DEBUG "&write_header->w3 = 0x%p\n", &write_header->w3); + printk(KERN_DEBUG "&read_header->w3 = 0x%p\n", &read_header->w3); + printk(KERN_DEBUG "ape->table_virt = 0x%p.\n", ape->table_virt); + + if (!write_header || !read_header || !ape->table_virt) + goto fail; + + /* allocate and map coherently-cached memory for a DMA-able buffer */ + /* @see 2.6.26.2/Documentation/DMA-mapping.txt line 318 */ + buffer_virt = (u8 *)pci_alloc_consistent(dev, PAGE_SIZE * 4, &buffer_bus); + if (!buffer_virt) { + printk(KERN_DEBUG "Could not allocate coherent DMA buffer.\n"); + goto fail; + } + printk(KERN_DEBUG "Allocated cache-coherent DMA buffer (virtual address = 0x%016llx, bus address = 0x%016llx).\n", + (u64)buffer_virt, (u64)buffer_bus); + + /* fill first half of buffer with its virtual address as data */ + for (i = 0; i < 4 * PAGE_SIZE; i += 4) +#if 0 + *(u32 *)(buffer_virt + i) = i / PAGE_SIZE + 1; +#else + *(u32 *)(buffer_virt + i) = (buffer_virt + i); +#endif +#if 0 + compare((u32 *)buffer_virt, (u32 *)(buffer_virt + 2 * PAGE_SIZE), 8192); +#endif + +#if 0 + /* fill second half of buffer with zeroes */ + for (i = 2 * PAGE_SIZE; i < 4 * PAGE_SIZE; i += 4) + *(u32 *)(buffer_virt + i) = 0; +#endif + + /* invalidate EPLAST, outside 0-255, 0xFADE is from the testbench */ + ape->table_virt->w3 = cpu_to_le32(0x0000FADE); + + /* fill in first descriptor */ + n = 0; + /* read 8192 bytes from RC buffer to EP address 4096 */ + ape_chdma_desc_set(&ape->table_virt->desc[n], buffer_bus, 4096, 2 * PAGE_SIZE); +#if 1 + for (i = 0; i < 255; i++) { + ape_chdma_desc_set(&ape->table_virt->desc[i], buffer_bus, 4096, 2 * PAGE_SIZE); + } + /* index of last descriptor */ + n = i - 1; +#endif +#if 0 + /* fill in next descriptor */ + n++; + /* read 1024 bytes from RC buffer to EP address 4096 + 1024 */ + ape_chdma_desc_set(&ape->table_virt->desc[n], buffer_bus + 1024, 4096 + 1024, 1024); +#endif + +#if 1 + /* enable MSI after the last descriptor is completed */ + if (ape->msi_enabled) + ape->table_virt->desc[n].w0 |= cpu_to_le32(1UL << 16)/*local MSI*/; +#endif +#if 0 + /* dump descriptor table for debugging */ + printk(KERN_DEBUG "Descriptor Table (Read, in Root Complex Memory, # = %d)\n", n + 1); + for (i = 0; i < 4 + (n + 1) * 4; i += 4) { + u32 *p = (u32 *)ape->table_virt; + p += i; + printk(KERN_DEBUG "0x%08x/0x%02x: 0x%08x (LEN=0x%x)\n", (u32)p, (u32)p & 15, *p, 4 * le32_to_cpu(*p)); + p++; + printk(KERN_DEBUG "0x%08x/0x%02x: 0x%08x (EPA=0x%x)\n", (u32)p, (u32)p & 15, *p, le32_to_cpu(*p)); + p++; + printk(KERN_DEBUG "0x%08x/0x%02x: 0x%08x (RCH=0x%x)\n", (u32)p, (u32)p & 15, *p, le32_to_cpu(*p)); + p++; + printk(KERN_DEBUG "0x%08x/0x%02x: 0x%08x (RCL=0x%x)\n", (u32)p, (u32)p & 15, *p, le32_to_cpu(*p)); + } +#endif + /* set available number of descriptors in table */ + w = (u32)(n + 1); + w |= (1UL << 18)/*global EPLAST_EN*/; +#if 0 + if (ape->msi_enabled) + w |= (1UL << 17)/*global MSI*/; +#endif + printk(KERN_DEBUG "writing 0x%08x to 0x%p\n", w, (void *)&read_header->w0); + iowrite32(w, &read_header->w0); + + /* write table address (higher 32-bits) */ + printk(KERN_DEBUG "writing 0x%08x to 0x%p\n", (u32)((ape->table_bus >> 16) >> 16), (void *)&read_header->bdt_addr_h); + iowrite32(pci_dma_h(ape->table_bus), &read_header->bdt_addr_h); + + /* write table address (lower 32-bits) */ + printk(KERN_DEBUG "writing 0x%08x to 0x%p\n", (u32)(ape->table_bus & 0xffffffffUL), (void *)&read_header->bdt_addr_l); + iowrite32(pci_dma_l(ape->table_bus), &read_header->bdt_addr_l); + + /* memory write barrier */ + wmb(); + printk(KERN_DEBUG "Flush posted writes\n"); + /** FIXME Add dummy read to flush posted writes but need a readable location! */ +#if 0 + (void)ioread32(); +#endif + + /* remember IRQ count before the transfer */ + irq_count = ape->irq_count; + /* write number of descriptors - this starts the DMA */ + printk(KERN_DEBUG "\nStart DMA read\n"); + printk(KERN_DEBUG "writing 0x%08x to 0x%p\n", (u32)n, (void *)&read_header->w3); + iowrite32(n, &read_header->w3); + printk(KERN_DEBUG "EPLAST = %lu\n", le32_to_cpu(*(u32 *)&ape->table_virt->w3) & 0xffffUL); + + /** memory write barrier */ + wmb(); + /* dummy read to flush posted writes */ + /* FIXME Need a readable location! */ +#if 0 + (void)ioread32(); +#endif + printk(KERN_DEBUG "POLL FOR READ:\n"); + /* poll for chain completion, 1000 times 1 millisecond */ + for (i = 0; i < 100; i++) { + volatile u32 *p = &ape->table_virt->w3; + u32 eplast = le32_to_cpu(*p) & 0xffffUL; + printk(KERN_DEBUG "EPLAST = %u, n = %d\n", eplast, n); + if (eplast == n) { + printk(KERN_DEBUG "DONE\n"); + /* print IRQ count before the transfer */ + printk(KERN_DEBUG "#IRQs during transfer: %d\n", ape->irq_count - irq_count); + break; + } + udelay(100); + } + + /* invalidate EPLAST, outside 0-255, 0xFADE is from the testbench */ + ape->table_virt->w3 = cpu_to_le32(0x0000FADE); + + /* setup first descriptor */ + n = 0; + ape_chdma_desc_set(&ape->table_virt->desc[n], buffer_bus + 8192, 4096, 2 * PAGE_SIZE); +#if 1 + for (i = 0; i < 255; i++) { + ape_chdma_desc_set(&ape->table_virt->desc[i], buffer_bus + 8192, 4096, 2 * PAGE_SIZE); + } + /* index of last descriptor */ + n = i - 1; +#endif +#if 1 /* test variable, make a module option later */ + if (ape->msi_enabled) + ape->table_virt->desc[n].w0 |= cpu_to_le32(1UL << 16)/*local MSI*/; +#endif +#if 0 + /* dump descriptor table for debugging */ + printk(KERN_DEBUG "Descriptor Table (Write, in Root Complex Memory, # = %d)\n", n + 1); + for (i = 0; i < 4 + (n + 1) * 4; i += 4) { + u32 *p = (u32 *)ape->table_virt; + p += i; + printk(KERN_DEBUG "0x%08x/0x%02x: 0x%08x (LEN=0x%x)\n", (u32)p, (u32)p & 15, *p, 4 * le32_to_cpu(*p)); + p++; + printk(KERN_DEBUG "0x%08x/0x%02x: 0x%08x (EPA=0x%x)\n", (u32)p, (u32)p & 15, *p, le32_to_cpu(*p)); + p++; + printk(KERN_DEBUG "0x%08x/0x%02x: 0x%08x (RCH=0x%x)\n", (u32)p, (u32)p & 15, *p, le32_to_cpu(*p)); + p++; + printk(KERN_DEBUG "0x%08x/0x%02x: 0x%08x (RCL=0x%x)\n", (u32)p, (u32)p & 15, *p, le32_to_cpu(*p)); + } +#endif + + /* set number of available descriptors in the table */ + w = (u32)(n + 1); + /* enable updates of eplast for each descriptor completion */ + w |= (u32)(1UL << 18)/*global EPLAST_EN*/; +#if 0 // test variable, make a module option later + /* enable MSI for each descriptor completion */ + if (ape->msi_enabled) + w |= (1UL << 17)/*global MSI*/; +#endif + iowrite32(w, &write_header->w0); + iowrite32(pci_dma_h(ape->table_bus), &write_header->bdt_addr_h); + iowrite32(pci_dma_l(ape->table_bus), &write_header->bdt_addr_l); + + /** memory write barrier and flush posted writes */ + wmb(); + /* dummy read to flush posted writes */ + /* FIXME Need a readable location! */ +#if 0 + (void)ioread32(); +#endif + irq_count = ape->irq_count; + + printk(KERN_DEBUG "\nStart DMA write\n"); + iowrite32(n, &write_header->w3); + + /** memory write barrier */ + wmb(); + /** dummy read to flush posted writes */ + //(void)ioread32(); + + printk(KERN_DEBUG "POLL FOR WRITE:\n"); + /* poll for completion, 1000 times 1 millisecond */ + for (i = 0; i < 100; i++) { + volatile u32 *p = &ape->table_virt->w3; + u32 eplast = le32_to_cpu(*p) & 0xffffUL; + printk(KERN_DEBUG "EPLAST = %u, n = %d\n", eplast, n); + if (eplast == n) { + printk(KERN_DEBUG "DONE\n"); + /* print IRQ count before the transfer */ + printk(KERN_DEBUG "#IRQs during transfer: %d\n", ape->irq_count - irq_count); + break; + } + udelay(100); + } + /* soft-reset DMA write engine */ + iowrite32(0x0000ffffUL, &write_header->w0); + /* soft-reset DMA read engine */ + iowrite32(0x0000ffffUL, &read_header->w0); + + /** memory write barrier */ + wmb(); + /* dummy read to flush posted writes */ + /* FIXME Need a readable location! */ +#if 0 + (void)ioread32(); +#endif + /* compare first half of buffer with second half, should be identical */ + result = compare((u32 *)buffer_virt, (u32 *)(buffer_virt + 2 * PAGE_SIZE), 8192); + printk(KERN_DEBUG "DMA loop back test %s.\n", result ? "FAILED" : "PASSED"); + + pci_free_consistent(dev, 4 * PAGE_SIZE, buffer_virt, buffer_bus); +fail: + printk(KERN_DEBUG "bar_tests() end, result %d\n", result); + return result; +} + +/* Called when the PCI sub system thinks we can control the given device. + * Inspect if we can support the device and if so take control of it. + * + * Return 0 when we have taken control of the given device. + * + * - allocate board specific bookkeeping + * - allocate coherently-mapped memory for the descriptor table + * - enable the board + * - verify board revision + * - request regions + * - query DMA mask + * - obtain and request irq + * - map regions into kernel address space + */ +static int __devinit probe(struct pci_dev *dev, const struct pci_device_id *id) +{ + int rc = 0; + struct ape_dev *ape = NULL; + u8 irq_pin, irq_line; + printk(KERN_DEBUG "probe(dev = 0x%p, pciid = 0x%p)\n", dev, id); + + /* allocate memory for per-board book keeping */ + ape = kzalloc(sizeof(struct ape_dev), GFP_KERNEL); + if (!ape) { + printk(KERN_DEBUG "Could not kzalloc()ate memory.\n"); + goto err_ape; + } + ape->pci_dev = dev; + dev->dev.driver_data = (void *)ape; + printk(KERN_DEBUG "probe() ape = 0x%p\n", ape); + + printk(KERN_DEBUG "sizeof(struct ape_chdma_table) = %d.\n", + (int)sizeof(struct ape_chdma_table)); + /* the reference design has a size restriction on the table size */ + BUG_ON(sizeof(struct ape_chdma_table) > APE_CHDMA_TABLE_SIZE); + + /* allocate and map coherently-cached memory for a descriptor table */ + /* @see LDD3 page 446 */ + ape->table_virt = (struct ape_chdma_table *)pci_alloc_consistent(dev, + APE_CHDMA_TABLE_SIZE, &ape->table_bus); + /* could not allocate table? */ + if (!ape->table_virt) { + printk(KERN_DEBUG "Could not dma_alloc()ate_coherent memory.\n"); + goto err_table; + } + + printk(KERN_DEBUG "table_virt = 0x%16llx, table_bus = 0x%16llx.\n", + (u64)ape->table_virt, (u64)ape->table_bus); + + /* enable device */ + rc = pci_enable_device(dev); + if (rc) { + printk(KERN_DEBUG "pci_enable_device() failed\n"); + goto err_enable; + } + + /* enable bus master capability on device */ + pci_set_master(dev); + /* enable message signaled interrupts */ + rc = pci_enable_msi(dev); + /* could not use MSI? */ + if (rc) { + /* resort to legacy interrupts */ + printk(KERN_DEBUG "Could not enable MSI interrupting.\n"); + ape->msi_enabled = 0; + /* MSI enabled, remember for cleanup */ + } else { + printk(KERN_DEBUG "Enabled MSI interrupting.\n"); + ape->msi_enabled = 1; + } + + pci_read_config_byte(dev, PCI_REVISION_ID, &ape->revision); +#if 0 /* example */ + /* (for example) this driver does not support revision 0x42 */ + if (ape->revision == 0x42) { + printk(KERN_DEBUG "Revision 0x42 is not supported by this driver.\n"); + rc = -ENODEV; + goto err_rev; + } +#endif + /** XXX check for native or legacy PCIe endpoint? */ + + rc = pci_request_regions(dev, DRV_NAME); + /* could not request all regions? */ + if (rc) { + /* assume device is in use (and do not disable it later!) */ + ape->in_use = 1; + goto err_regions; + } + ape->got_regions = 1; + +#if 1 // @todo For now, disable 64-bit, because I do not understand the implications (DAC!) + /* query for DMA transfer */ + /* @see Documentation/DMA-mapping.txt */ + if (!pci_set_dma_mask(dev, DMA_64BIT_MASK)) { + pci_set_consistent_dma_mask(dev, DMA_64BIT_MASK); + /* use 64-bit DMA */ + printk(KERN_DEBUG "Using a 64-bit DMA mask.\n"); + } else +#endif + if (!pci_set_dma_mask(dev, DMA_32BIT_MASK)) { + printk(KERN_DEBUG "Could not set 64-bit DMA mask.\n"); + pci_set_consistent_dma_mask(dev, DMA_32BIT_MASK); + /* use 32-bit DMA */ + printk(KERN_DEBUG "Using a 32-bit DMA mask.\n"); + } else { + printk(KERN_DEBUG "No suitable DMA possible.\n"); + /** @todo Choose proper error return code */ + rc = -1; + goto err_mask; + } + + rc = pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &irq_pin); + /* could not read? */ + if (rc) + goto err_irq; + printk(KERN_DEBUG "IRQ pin #%d (0=none, 1=INTA#...4=INTD#).\n", irq_pin); + + /* @see LDD3, page 318 */ + rc = pci_read_config_byte(dev, PCI_INTERRUPT_LINE, &irq_line); + /* could not read? */ + if (rc) { + printk(KERN_DEBUG "Could not query PCI_INTERRUPT_LINE, error %d\n", rc); + goto err_irq; + } + printk(KERN_DEBUG "IRQ line #%d.\n", irq_line); +#if 1 + irq_line = dev->irq; + /* @see LDD3, page 259 */ + rc = request_irq(irq_line, altpciechdma_isr, IRQF_SHARED, DRV_NAME, (void *)ape); + if (rc) { + printk(KERN_DEBUG "Could not request IRQ #%d, error %d\n", irq_line, rc); + ape->irq_line = -1; + goto err_irq; + } + /* remember which irq we allocated */ + ape->irq_line = (int)irq_line; + printk(KERN_DEBUG "Succesfully requested IRQ #%d with dev_id 0x%p\n", irq_line, ape); +#endif + /* show BARs */ + scan_bars(ape, dev); + /* map BARs */ + rc = map_bars(ape, dev); + if (rc) + goto err_map; +#if ALTPCIECHDMA_CDEV + /* initialize character device */ + rc = sg_init(ape); + if (rc) + goto err_cdev; +#endif + /* perform DMA engines loop back test */ + rc = dma_test(ape, dev); + (void)rc; + /* succesfully took the device */ + rc = 0; + printk(KERN_DEBUG "probe() successful.\n"); + goto end; +err_cdev: + /* unmap the BARs */ + unmap_bars(ape, dev); +err_map: + /* free allocated irq */ + if (ape->irq_line >= 0) + free_irq(ape->irq_line, (void *)ape); +err_irq: + if (ape->msi_enabled) + pci_disable_msi(dev); + /* disable the device iff it is not in use */ + if (!ape->in_use) + pci_disable_device(dev); + if (ape->got_regions) + pci_release_regions(dev); +err_mask: +err_regions: +err_rev: +/* clean up everything before device enable() */ +err_enable: + if (ape->table_virt) + pci_free_consistent(dev, APE_CHDMA_TABLE_SIZE, ape->table_virt, ape->table_bus); +/* clean up everything before allocating descriptor table */ +err_table: + if (ape) + kfree(ape); +err_ape: +end: + return rc; +} + +static void __devexit remove(struct pci_dev *dev) +{ + struct ape_dev *ape; + printk(KERN_DEBUG "remove(0x%p)\n", dev); + if ((dev == 0) || (dev->dev.driver_data == 0)) { + printk(KERN_DEBUG "remove(dev = 0x%p) dev->dev.driver_data = 0x%p\n", dev, dev->dev.driver_data); + return; + } + ape = (struct ape_dev *)dev->dev.driver_data; + printk(KERN_DEBUG "remove(dev = 0x%p) where dev->dev.driver_data = 0x%p\n", dev, ape); + if (ape->pci_dev != dev) { + printk(KERN_DEBUG "dev->dev.driver_data->pci_dev (0x%08lx) != dev (0x%08lx)\n", + (unsigned long)ape->pci_dev, (unsigned long)dev); + } + /* remove character device */ +#if ALTPCIECHDMA_CDEV + sg_exit(ape); +#endif + + if (ape->table_virt) + pci_free_consistent(dev, APE_CHDMA_TABLE_SIZE, ape->table_virt, ape->table_bus); + + /* free IRQ + * @see LDD3 page 279 + */ + if (ape->irq_line >= 0) { + printk(KERN_DEBUG "Freeing IRQ #%d for dev_id 0x%08lx.\n", + ape->irq_line, (unsigned long)ape); + free_irq(ape->irq_line, (void *)ape); + } + /* MSI was enabled? */ + if (ape->msi_enabled) { + /* Disable MSI @see Documentation/MSI-HOWTO.txt */ + pci_disable_msi(dev); + ape->msi_enabled = 0; + } + /* unmap the BARs */ + unmap_bars(ape, dev); + if (!ape->in_use) + pci_disable_device(dev); + if (ape->got_regions) + /* to be called after device disable */ + pci_release_regions(dev); +} + +#if ALTPCIECHDMA_CDEV + +/* + * Called when the device goes from unused to used. + */ +static int sg_open(struct inode *inode, struct file *file) +{ + struct ape_dev *ape; + printk(KERN_DEBUG DRV_NAME "_open()\n"); + /* pointer to containing data structure of the character device inode */ + ape = container_of(inode->i_cdev, struct ape_dev, cdev); + /* create a reference to our device state in the opened file */ + file->private_data = ape; + /* create virtual memory mapper */ + ape->sgm = sg_create_mapper(MAX_CHDMA_SIZE); + return 0; +} + +/* + * Called when the device goes from used to unused. + */ +static int sg_close(struct inode *inode, struct file *file) +{ + /* fetch device specific data stored earlier during open */ + struct ape_dev *ape = (struct ape_dev *)file->private_data; + printk(KERN_DEBUG DRV_NAME "_close()\n"); + /* destroy virtual memory mapper */ + sg_destroy_mapper(ape->sgm); + return 0; +} + +static ssize_t sg_read(struct file *file, char __user *buf, size_t count, loff_t *pos) +{ + /* fetch device specific data stored earlier during open */ + struct ape_dev *ape = (struct ape_dev *)file->private_data; + (void)ape; + printk(KERN_DEBUG DRV_NAME "_read(buf=0x%p, count=%lld, pos=%llu)\n", buf, (s64)count, (u64)*pos); + return count; +} + +/* sg_write() - Write to the device + * + * @buf userspace buffer + * @count number of bytes in the userspace buffer + * + * Iterate over the userspace buffer, taking at most 255 * PAGE_SIZE bytes for + * each DMA transfer. + * For each transfer, get the user pages, build a sglist, map, build a + * descriptor table. submit the transfer. wait for the interrupt handler + * to wake us on completion. + */ +static ssize_t sg_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) +{ + int hwnents, tents; + size_t transfer_len, remaining = count, done = 0; + u64 transfer_addr = (u64)buf; + /* fetch device specific data stored earlier during open */ + struct ape_dev *ape = (struct ape_dev *)file->private_data; + printk(KERN_DEBUG DRV_NAME "_write(buf=0x%p, count=%lld, pos=%llu)\n", + buf, (s64)count, (u64)*pos); + /* TODO transfer boundaries at PAGE_SIZE granularity */ + while (remaining > 0) + { + /* limit DMA transfer size */ + transfer_len = (remaining < APE_CHDMA_MAX_TRANSFER_LEN)? remaining: + APE_CHDMA_MAX_TRANSFER_LEN; + /* get all user space buffer pages and create a scattergather list */ + sgm_map_user_pages(ape->sgm, transfer_addr, transfer_len, 0/*read from userspace*/); + printk(KERN_DEBUG DRV_NAME "mapped_pages=%d\n", ape->sgm->mapped_pages); + /* map all entries in the scattergather list */ + hwnents = pci_map_sg(ape->pci_dev, ape->sgm->sgl, ape->sgm->mapped_pages, DMA_TO_DEVICE); + printk(KERN_DEBUG DRV_NAME "hwnents=%d\n", hwnents); + /* build device descriptor tables and submit them to the DMA engine */ + tents = ape_sg_to_chdma_table(ape->sgm->sgl, hwnents, 0, &ape->table_virt->desc[0], 4096); + printk(KERN_DEBUG DRV_NAME "tents=%d\n", hwnents); +#if 0 + while (tables) { + /* TODO build table */ + /* TODO submit table to the device */ + /* if engine stopped and unfinished work then start engine */ + } + put ourselves on wait queue +#endif + + dma_unmap_sg(NULL, ape->sgm->sgl, ape->sgm->mapped_pages, DMA_TO_DEVICE); + /* dirty and free the pages */ + sgm_unmap_user_pages(ape->sgm, 1/*dirtied*/); + /* book keeping */ + transfer_addr += transfer_len; + remaining -= transfer_len; + done += transfer_len; + } + return done; +} + +/* + * character device file operations + */ +static struct file_operations sg_fops = { + .owner = THIS_MODULE, + .open = sg_open, + .release = sg_close, + .read = sg_read, + .write = sg_write, +}; + +/* sg_init() - Initialize character device + * + * XXX Should ideally be tied to the device, on device probe, not module init. + */ +static int sg_init(struct ape_dev *ape) +{ + int rc; + printk(KERN_DEBUG DRV_NAME " sg_init()\n"); + /* allocate a dynamically allocated character device node */ + rc = alloc_chrdev_region(&ape->cdevno, 0/*requested minor*/, 1/*count*/, DRV_NAME); + /* allocation failed? */ + if (rc < 0) { + printk("alloc_chrdev_region() = %d\n", rc); + goto fail_alloc; + } + /* couple the device file operations to the character device */ + cdev_init(&ape->cdev, &sg_fops); + ape->cdev.owner = THIS_MODULE; + /* bring character device live */ + rc = cdev_add(&ape->cdev, ape->cdevno, 1/*count*/); + if (rc < 0) { + printk("cdev_add() = %d\n", rc); + goto fail_add; + } + printk(KERN_DEBUG "altpciechdma = %d:%d\n", MAJOR(ape->cdevno), MINOR(ape->cdevno)); + return 0; +fail_add: + /* free the dynamically allocated character device node */ + unregister_chrdev_region(ape->cdevno, 1/*count*/); +fail_alloc: + return -1; +} + +/* sg_exit() - Cleanup character device + * + * XXX Should ideally be tied to the device, on device remove, not module exit. + */ + +static void sg_exit(struct ape_dev *ape) +{ + printk(KERN_DEBUG DRV_NAME " sg_exit()\n"); + /* remove the character device */ + cdev_del(&ape->cdev); + /* free the dynamically allocated character device node */ + unregister_chrdev_region(ape->cdevno, 1/*count*/); +} + +#endif /* ALTPCIECHDMA_CDEV */ + +/* used to register the driver with the PCI kernel sub system + * @see LDD3 page 311 + */ +static struct pci_driver pci_driver = { + .name = DRV_NAME, + .id_table = ids, + .probe = probe, + .remove = remove, + /* resume, suspend are optional */ +}; + +/** + * alterapciechdma_init() - Module initialization, registers devices. + */ +static int __init alterapciechdma_init(void) +{ + int rc = 0; + printk(KERN_DEBUG DRV_NAME " init(), built at " __DATE__ " " __TIME__ "\n"); + /* register this driver with the PCI bus driver */ + rc = pci_register_driver(&pci_driver); + if (rc < 0) + return rc; + return 0; +} + +/** + * alterapciechdma_init() - Module cleanup, unregisters devices. + */ +static void __exit alterapciechdma_exit(void) +{ + printk(KERN_DEBUG DRV_NAME " exit(), built at " __DATE__ " " __TIME__ "\n"); + /* unregister this driver from the PCI bus driver */ + pci_unregister_driver(&pci_driver); +} + +MODULE_LICENSE("GPL"); + +module_init(alterapciechdma_init); +module_exit(alterapciechdma_exit); + |