/** * * @file base.c * @author Guillermo Marcus * @date 2009-04-05 * @brief Contains the main code which connects all the different parts and does * basic driver tasks like initialization. * * This is a full rewrite of the pciDriver. * New default is to support kernel 2.6, using kernel 2.6 APIs. * */ /* * Change History: * * $Log: not supported by cvs2svn $ * Revision 1.13 2008-05-30 11:38:15 marcus * Added patches for kernel 2.6.24 * * Revision 1.12 2008-01-24 14:21:36 marcus * Added a CLEAR_INTERRUPT_QUEUE ioctl. * Added a sysfs attribute to show the outstanding IRQ queues. * * Revision 1.11 2008-01-24 12:53:11 marcus * Corrected wait_event condition in waiti_ioctl. Improved the loop too. * * Revision 1.10 2008-01-14 10:39:39 marcus * Set some messages as debug instead of normal. * * Revision 1.9 2008-01-11 10:18:28 marcus * Modified interrupt mechanism. Added atomic functions and queues, to address race conditions. Removed unused interrupt code. * * Revision 1.8 2007-07-17 13:15:55 marcus * Removed Tasklets. * Using newest map for the ABB interrupts. * * Revision 1.7 2007-07-06 15:56:04 marcus * Change default status for OLD_REGISTERS to not defined. * * Revision 1.6 2007-07-05 15:29:59 marcus * Corrected issue with the bar mapping for interrupt handling. * Added support up to kernel 2.6.20 * * Revision 1.5 2007-05-29 07:50:18 marcus * Split code into 2 files. May get merged in the future again.... * * Revision 1.4 2007/03/01 17:47:34 marcus * Fixed bug when the kernel memory was less than one page, it was not locked properly, recalling an old mapping issue in this case. * * Revision 1.3 2007/03/01 17:01:22 marcus * comment fix (again). * * Revision 1.2 2007/03/01 17:00:25 marcus * Changed some comment in the log. * * Revision 1.1 2007/03/01 16:57:43 marcus * Divided driver file to ease the interrupt hooks for the user of the driver. * Modified Makefile accordingly. * * From pciDriver.c: * Revision 1.11 2006/12/11 16:15:43 marcus * Fixed kernel buffer mmapping, and driver crash when application crashes. * Buffer memory is now marked reserved during allocation, and mmaped with * remap_xx_range. * * Revision 1.10 2006/11/21 09:50:49 marcus * Added PROGRAPE4 vendor/device IDs. * * Revision 1.9 2006/11/17 18:47:36 marcus * Removed MERGE_SGENTRIES flag, now it is selected at runtime with 'type'. * Removed noncached in non-prefetchable areas, to allow the use of MTRRs. * * Revision 1.8 2006/11/17 16:41:21 marcus * Added slot number to the PCI info IOctl. * * Revision 1.7 2006/11/13 12:30:34 marcus * Added a IOctl call, to confiure the interrupt response. (testing pending). * Basic interrupts are now supported, using a Tasklet and Completions. * * Revision 1.6 2006/11/08 21:30:02 marcus * Added changes after compile tests in kernel 2.6.16 * * Revision 1.5 2006/10/31 07:57:38 marcus * Improved the pfn calculation in nopage(), to deal with some possible border * conditions. It was really no issue, because they are normally page-aligned * anyway, but to be on the safe side. * * Revision 1.4 2006/10/30 19:37:40 marcus * Solved bug on kernel memory not mapping properly. * * Revision 1.3 2006/10/18 11:19:20 marcus * Added kernel 2.6.8 support based on comments from Joern Adamczewski (GSI). * * Revision 1.2 2006/10/18 11:04:15 marcus * Bus Master is only activated when we detect a specific board. * * Revision 1.1 2006/10/10 14:46:51 marcus * Initial commit of the new pciDriver for kernel 2.6 * * Revision 1.9 2006/10/05 11:30:46 marcus * Prerelease. Added bus and devfn to pciInfo for compatibility. * * Revision 1.8 2006/09/25 16:51:07 marcus * Added PCI config IOctls, and implemented basic mmap functions. * * Revision 1.7 2006/09/20 11:12:41 marcus * Added Merge SG entries * * Revision 1.6 2006/09/19 17:22:18 marcus * backup commit. * * Revision 1.5 2006/09/18 17:13:11 marcus * backup commit. * * Revision 1.4 2006/09/15 15:44:41 marcus * backup commit. * * Revision 1.3 2006/08/15 11:40:02 marcus * backup commit. * * Revision 1.2 2006/08/12 18:28:42 marcus * Sync with the laptop * * Revision 1.1 2006/08/11 15:30:46 marcus * Sync with the laptop * */ #include /* Check macros and kernel version first */ #ifndef KERNEL_VERSION #error "No KERNEL_VERSION macro! Stopping." #endif #ifndef LINUX_VERSION_CODE #error "No LINUX_VERSION_CODE macro! Stopping." #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,8) #error "This driver has been tested only for Kernel 2.6.8 or above." #endif /* Required includes */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Configuration for the driver (what should be compiled in, module name, etc...) */ #include "config.h" /* Compatibility functions/definitions (provides functions which are not available on older kernels) */ #include "compat.h" /* External interface for the driver */ #include "pciDriver.h" /* Internal definitions for all parts (prototypes, data, macros) */ #include "common.h" /* Internal definitions for the base part */ #include "base.h" /* Internal definitions of the IRQ handling part */ #include "int.h" /* Internal definitions for kernel memory */ #include "kmem.h" /* Internal definitions for user space memory */ #include "umem.h" #include "ioctl.h" /*************************************************************************/ /* Module device table associated with this driver */ MODULE_DEVICE_TABLE(pci, pcidriver_ids); /* Module init and exit points */ module_init(pcidriver_init); module_exit(pcidriver_exit); /* Module info */ MODULE_AUTHOR("Guillermo Marcus"); MODULE_DESCRIPTION("Simple PCI Driver"); MODULE_LICENSE("GPL v2"); /* Module class */ static struct class_compat *pcidriver_class; /** * * Called when loading the driver * */ static int __init pcidriver_init(void) { int err; /* Initialize the device count */ atomic_set(&pcidriver_deviceCount, 0); /* Allocate character device region dynamically */ if ((err = alloc_chrdev_region(&pcidriver_devt, MINORNR, MAXDEVICES, NODENAME)) != 0) { mod_info("Couldn't allocate chrdev region. Module not loaded.\n"); goto init_alloc_fail; } mod_info("Major %d allocated to nodename '%s'\n", MAJOR(pcidriver_devt), NODENAME); /* Register driver class */ pcidriver_class = class_create(THIS_MODULE, NODENAME); if (IS_ERR(pcidriver_class)) { mod_info("No sysfs support. Module not loaded.\n"); goto init_class_fail; } /* Register PCI driver. This function returns the number of devices on some * systems, therefore check for errors as < 0. */ if ((err = pci_register_driver(&pcidriver_driver)) < 0) { mod_info("Couldn't register PCI driver. Module not loaded.\n"); goto init_pcireg_fail; } mod_info("Module loaded\n"); return 0; init_pcireg_fail: class_destroy(pcidriver_class); init_class_fail: unregister_chrdev_region(pcidriver_devt, MAXDEVICES); init_alloc_fail: return err; } /** * * Called when unloading the driver * */ static void pcidriver_exit(void) { if (pcidriver_class != NULL) class_destroy(pcidriver_class); pci_unregister_driver(&pcidriver_driver); unregister_chrdev_region(pcidriver_devt, MAXDEVICES); mod_info("Module unloaded\n"); } /*************************************************************************/ /* Driver functions */ /** * * This struct defines the PCI entry points. * Will be registered at module init. * */ static struct pci_driver pcidriver_driver = { .name = MODNAME, .id_table = pcidriver_ids, .probe = pcidriver_probe, .remove = pcidriver_remove, }; /** * * This function is called when installing the driver for a device * @param pdev Pointer to the PCI device * */ static int __devinit pcidriver_probe(struct pci_dev *pdev, const struct pci_device_id *id) { int err; int devno; pcidriver_privdata_t *privdata; int devid; /* At the moment there is no difference between these boards here, other than * printing a different message in the log. * * However, there is some difference in the interrupt handling functions. */ if (id->vendor == PCIE_XILINX_VENDOR_ID) { if (id->device == PCIE_ML605_DEVICE_ID) { mod_info("Found ML605 board at %s\n", dev_name(&pdev->dev)); } else if (id->device == PCIE_IPECAMERA_DEVICE_ID) { mod_info("Found IPE Camera at %s\n", dev_name(&pdev->dev)); } else { mod_info("Found unknown Xilinx device (%x) at %s\n", id->device, dev_name(&pdev->dev)); } } else { /* It is something else */ mod_info( "Found unknown board (%x:%x) at %s\n", id->vendor, id->device, dev_name(&pdev->dev)); } /* Enable the device */ if ((err = pci_enable_device(pdev)) != 0) { mod_info("Couldn't enable device\n"); goto probe_pcien_fail; } /* Bus master & dma */ if ((id->vendor == PCIE_XILINX_VENDOR_ID)&&(id->device == PCIE_IPECAMERA_DEVICE_ID)) { pci_set_master(pdev); err = pci_set_dma_mask(pdev, DMA_32BIT_MASK); if (err < 0) { printk(KERN_ERR "pci_set_dma_mask failed\n"); goto probe_dma_fail; } } /* Set Memory-Write-Invalidate support */ if ((err = pci_set_mwi(pdev)) != 0) mod_info("MWI not supported. Continue without enabling MWI.\n"); /* Get / Increment the device id */ devid = atomic_inc_return(&pcidriver_deviceCount) - 1; if (devid >= MAXDEVICES) { mod_info("Maximum number of devices reached! Increase MAXDEVICES.\n"); err = -ENOMSG; goto probe_maxdevices_fail; } /* Allocate and initialize the private data for this device */ if ((privdata = kcalloc(1, sizeof(*privdata), GFP_KERNEL)) == NULL) { err = -ENOMEM; goto probe_nomem; } INIT_LIST_HEAD(&(privdata->kmem_list)); spin_lock_init(&(privdata->kmemlist_lock)); atomic_set(&privdata->kmem_count, 0); INIT_LIST_HEAD(&(privdata->umem_list)); spin_lock_init(&(privdata->umemlist_lock)); atomic_set(&privdata->umem_count, 0); pci_set_drvdata( pdev, privdata ); privdata->pdev = pdev; /* Device add to sysfs */ devno = MKDEV(MAJOR(pcidriver_devt), MINOR(pcidriver_devt) + devid); privdata->devno = devno; if (pcidriver_class != NULL) { /* FIXME: some error checking missing here */ privdata->class_dev = class_device_create(pcidriver_class, NULL, devno, &(pdev->dev), NODENAMEFMT, MINOR(pcidriver_devt) + devid, privdata); class_set_devdata( privdata->class_dev, privdata ); mod_info("Device /dev/%s%d added\n",NODENAME,MINOR(pcidriver_devt) + devid); } /* Setup mmaped BARs into kernel space */ if ((err = pcidriver_probe_irq(privdata)) != 0) goto probe_irq_probe_fail; /* Populate sysfs attributes for the class device */ /* TODO: correct errorhandling. ewww. must remove the files in reversed order :-( */ #define sysfs_attr(name) do { \ if (class_device_create_file(sysfs_attr_def_pointer, &sysfs_attr_def_name(name)) != 0) \ goto probe_device_create_fail; \ } while (0) #ifdef ENABLE_IRQ sysfs_attr(irq_count); sysfs_attr(irq_queues); #endif sysfs_attr(mmap_mode); sysfs_attr(mmap_area); sysfs_attr(kmem_count); sysfs_attr(kmem_alloc); sysfs_attr(kmem_free); sysfs_attr(kbuffers); sysfs_attr(umappings); sysfs_attr(umem_unmap); #undef sysfs_attr /* Register character device */ cdev_init( &(privdata->cdev), &pcidriver_fops ); privdata->cdev.owner = THIS_MODULE; privdata->cdev.ops = &pcidriver_fops; err = cdev_add( &privdata->cdev, devno, 1 ); if (err) { mod_info( "Couldn't add character device.\n" ); goto probe_cdevadd_fail; } return 0; probe_device_create_fail: probe_cdevadd_fail: probe_irq_probe_fail: pcidriver_irq_unmap_bars(privdata); kfree(privdata); probe_nomem: atomic_dec(&pcidriver_deviceCount); probe_maxdevices_fail: probe_dma_fail: pci_disable_device(pdev); probe_pcien_fail: return err; } /** * * This function is called when disconnecting a device * */ static void __devexit pcidriver_remove(struct pci_dev *pdev) { pcidriver_privdata_t *privdata; /* Get private data from the device */ privdata = pci_get_drvdata(pdev); /* Removing sysfs attributes from class device */ #define sysfs_attr(name) do { \ class_device_remove_file(sysfs_attr_def_pointer, &sysfs_attr_def_name(name)); \ } while (0) #ifdef ENABLE_IRQ sysfs_attr(irq_count); sysfs_attr(irq_queues); #endif sysfs_attr(mmap_mode); sysfs_attr(mmap_area); sysfs_attr(kmem_count); sysfs_attr(kmem_alloc); sysfs_attr(kmem_free); sysfs_attr(kbuffers); sysfs_attr(umappings); sysfs_attr(umem_unmap); #undef sysfs_attr /* Free all allocated kmem buffers before leaving */ pcidriver_kmem_free_all( privdata ); #ifdef ENABLE_IRQ pcidriver_remove_irq(privdata); #endif /* Removing Character device */ cdev_del(&(privdata->cdev)); /* Removing the device from sysfs */ class_device_destroy(pcidriver_class, privdata->devno); /* Releasing privdata */ kfree(privdata); /* Disabling PCI device */ pci_disable_device(pdev); mod_info("Device at %s removed\n", dev_name(&pdev->dev)); } /*************************************************************************/ /* File operations */ /*************************************************************************/ /** * This struct defines the file operation entry points. * * @see pcidriver_ioctl * @see pcidriver_mmap * @see pcidriver_open * @see pcidriver_release * */ static struct file_operations pcidriver_fops = { .owner = THIS_MODULE, #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,11) .ioctl = pcidriver_ioctl, #else .unlocked_ioctl = pcidriver_ioctl, #endif .mmap = pcidriver_mmap, .open = pcidriver_open, .release = pcidriver_release, }; void pcidriver_module_get(pcidriver_privdata_t *privdata) { try_module_get(THIS_MODULE); atomic_inc(&(privdata->refs)); // mod_info("Ref: %i\n", atomic_read(&(privdata->refs))); } void pcidriver_module_put(pcidriver_privdata_t *privdata) { if (atomic_add_negative(-1, &(privdata->refs))) { atomic_inc(&(privdata->refs)); mod_info("Reference counting error..."); } else { module_put(THIS_MODULE); // mod_info("Unref: %i\n", atomic_read(&(privdata->refs))); } } /** * * Called when an application open()s a /dev/fpga*, attaches the private data * with the file pointer. * */ int pcidriver_open(struct inode *inode, struct file *filp) { pcidriver_privdata_t *privdata; /* Set the private data area for the file */ privdata = container_of( inode->i_cdev, pcidriver_privdata_t, cdev); filp->private_data = privdata; pcidriver_module_get(privdata); return 0; } /** * * Called when the application close()s the file descriptor. Does nothing at * the moment. * */ int pcidriver_release(struct inode *inode, struct file *filp) { pcidriver_privdata_t *privdata; /* Get the private data area */ privdata = filp->private_data; pcidriver_module_put(privdata); return 0; } /** * * This function is the entry point for mmap() and calls either pcidriver_mmap_pci * or pcidriver_mmap_kmem * * @see pcidriver_mmap_pci * @see pcidriver_mmap_kmem * */ int pcidriver_mmap(struct file *filp, struct vm_area_struct *vma) { pcidriver_privdata_t *privdata; int ret = 0, bar; mod_info_dbg("Entering mmap\n"); /* Get the private data area */ privdata = filp->private_data; /* Check the current mmap mode */ switch (privdata->mmap_mode) { case PCIDRIVER_MMAP_PCI: /* Mmap a PCI region */ switch (privdata->mmap_area) { case PCIDRIVER_BAR0: bar = 0; break; case PCIDRIVER_BAR1: bar = 1; break; case PCIDRIVER_BAR2: bar = 2; break; case PCIDRIVER_BAR3: bar = 3; break; case PCIDRIVER_BAR4: bar = 4; break; case PCIDRIVER_BAR5: bar = 5; break; default: mod_info("Attempted to mmap a PCI area with the wrong mmap_area value: %d\n",privdata->mmap_area); return -EINVAL; /* invalid parameter */ break; } ret = pcidriver_mmap_pci(privdata, vma, bar); break; case PCIDRIVER_MMAP_KMEM: /* mmap a Kernel buffer */ ret = pcidriver_mmap_kmem(privdata, vma); break; default: mod_info( "Invalid mmap_mode value (%d)\n",privdata->mmap_mode ); return -EINVAL; /* Invalid parameter (mode) */ } return ret; } /*************************************************************************/ /* Internal driver functions */ int pcidriver_mmap_pci(pcidriver_privdata_t *privdata, struct vm_area_struct *vmap, int bar) { int ret = 0; unsigned long bar_addr; unsigned long bar_length, vma_size; unsigned long bar_flags; mod_info_dbg("Entering mmap_pci\n"); /* Get info of the BAR to be mapped */ bar_addr = pci_resource_start(privdata->pdev, bar); bar_length = pci_resource_len(privdata->pdev, bar); bar_flags = pci_resource_flags(privdata->pdev, bar); /* Check sizes */ vma_size = (vmap->vm_end - vmap->vm_start); if ((vma_size != bar_length) && ((bar_length < PAGE_SIZE) && (vma_size != PAGE_SIZE))) { mod_info( "mmap size is not correct! bar: %lu - vma: %lu\n", bar_length, vma_size ); return -EINVAL; } if (bar_flags & IORESOURCE_IO) { /* Unlikely case, we will mmap a IO region */ /* IO regions are never cacheable */ #ifdef pgprot_noncached vmap->vm_page_prot = pgprot_noncached(vmap->vm_page_prot); #endif /* Map the BAR */ ret = io_remap_pfn_range_compat( vmap, vmap->vm_start, bar_addr, bar_length, vmap->vm_page_prot); } else { /* Normal case, mmap a memory region */ /* Ensure this VMA is non-cached, if it is not flaged as prefetchable. * If it is prefetchable, caching is allowed and will give better performance. * This should be set properly by the BIOS, but we want to be sure. */ /* adapted from drivers/char/mem.c, mmap function. */ #ifdef pgprot_noncached /* Setting noncached disables MTRR registers, and we want to use them. * So we take this code out. This can lead to caching problems if and only if * the System BIOS set something wrong. Check LDDv3, page 425. */ // if (!(bar_flags & IORESOURCE_PREFETCH)) // vmap->vm_page_prot = pgprot_noncached(vmap->vm_page_prot); #endif /* Map the BAR */ ret = remap_pfn_range_compat( vmap, vmap->vm_start, bar_addr, bar_length, vmap->vm_page_prot); } if (ret) { mod_info("remap_pfn_range failed\n"); return -EAGAIN; } return 0; /* success */ }