diff options
| -rw-r--r-- | .bzrignore | 9 | ||||
| -rw-r--r-- | Makefile | 26 | ||||
| -rw-r--r-- | common.mk | 55 | ||||
| -rw-r--r-- | driver/Makefile | 27 | ||||
| -rw-r--r-- | driver/base.c | 675 | ||||
| -rw-r--r-- | driver/base.h | 89 | ||||
| -rw-r--r-- | driver/common.h | 114 | ||||
| -rw-r--r-- | driver/compat.h | 192 | ||||
| -rw-r--r-- | driver/config.h | 23 | ||||
| -rw-r--r-- | driver/int.c | 297 | ||||
| -rw-r--r-- | driver/int.h | 9 | ||||
| -rw-r--r-- | driver/ioctl.c | 446 | ||||
| -rw-r--r-- | driver/ioctl.h | 1 | ||||
| -rw-r--r-- | driver/kmem.c | 321 | ||||
| -rw-r--r-- | driver/kmem.h | 7 | ||||
| -rw-r--r-- | driver/pciDriver.h | 180 | ||||
| -rw-r--r-- | driver/sysfs.c | 295 | ||||
| -rw-r--r-- | driver/sysfs.h | 23 | ||||
| -rw-r--r-- | driver/umem.c | 438 | ||||
| -rw-r--r-- | driver/umem.h | 5 | ||||
| -rw-r--r-- | misc/50-pcidriver.rules | 1 | ||||
| -rw-r--r-- | pci.c | 569 | ||||
| -rw-r--r-- | tools.c | 43 | ||||
| -rw-r--r-- | tools.h | 5 | 
24 files changed, 3850 insertions, 0 deletions
diff --git a/.bzrignore b/.bzrignore new file mode 100644 index 0000000..1e7ea6f --- /dev/null +++ b/.bzrignore @@ -0,0 +1,9 @@ +*.cmd +pciDriver.ko +pciDriver.mod.c +pci.d +tools.d +modules.order +Module.symvers +./pci +.tmp_versions diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..41af90e --- /dev/null +++ b/Makefile @@ -0,0 +1,26 @@ +BINARIES += pci + +INCDIR +=  +LDINC += $(addprefix -L ,$(LIBDIR)) +LDFLAGS +=  + +all: $(BINARIES) + +.PHONY: all depend clean + +include common.mk + + +############################################################### +# Target definitions + + +pci: pci.o tools.o +	echo -e "LD \t$@" +	$(Q)$(CC) $(LDINC) $(LDFLAGS) $(CFLAGS) -o $@ $< tools.o + +clean: +	@echo -e "CLEAN \t$(shell pwd)" +	-$(Q)rm -f $(addprefix $(BINDIR)/,$(BINARIES)) +	-$(Q)rm -f $(OBJ) +	-$(Q)rm -f $(DEPEND) diff --git a/common.mk b/common.mk new file mode 100644 index 0000000..1758acd --- /dev/null +++ b/common.mk @@ -0,0 +1,55 @@ +# Compiler and default flags +CC ?= gcc +CFLAGS ?= -O2 + + +# Defaults for directories +ROOTDIR ?= $(shell pwd) + +INCDIR ?= $(ROOTDIR) +BINDIR ?= $(ROOTDIR) +LIBDIR ?= $(ROOTDIR) +OBJDIR ?= $(ROOTDIR) +DEPENDDIR ?= $(ROOTDIR) + +CXXFLAGS += $(addprefix -I ,$(INCDIR)) +CFLAGS += $(addprefix -I ,$(INCDIR)) + +# Source files in this directory +SRC = $(wildcard *.cpp) +SRCC = $(wildcard *.c) + +# Corresponding object files  +OBJ = $(addprefix $(OBJDIR)/,$(SRC:.cpp=.o)) +OBJ += $(addprefix $(OBJDIR)/,$(SRCC:.c=.o)) + +# Corresponding dependency files +DEPEND = $(addprefix $(DEPENDDIR)/,$(SRC:.cpp=.d))  +DEPEND += $(addprefix $(DEPENDDIR)/,$(SRCC:.c=.d))  + +# This makes Verbose easier. Just prefix $(Q) to any command +ifdef VERBOSE +	Q ?=  +else +	Q ?= @ +endif + +############################################################### +# Target definitions + +# Target for automatic dependency generation +depend: $(DEPEND) $(DEPENDC); + +# This rule generates a dependency makefile for each source +$(DEPENDDIR)/%.d: %.c +	@echo -e "DEPEND \t$<" +	$(Q)$(CC) $(addprefix -I ,$(INCDIR)) -MM -MF $@ \ +		-MT $(OBJDIR)/$(<:.c=.o) -MT $@ $<  + +# This includes the automatically  +# generated dependency files +-include $(DEPEND) + +$(OBJDIR)/%.o: %.c +	@echo -e "CC \t$<" +	$(Q)@$(CC) $(CFLAGS) -c -o $@ $< diff --git a/driver/Makefile b/driver/Makefile new file mode 100644 index 0000000..9f8918a --- /dev/null +++ b/driver/Makefile @@ -0,0 +1,27 @@ + +obj-m := pciDriver.o +pciDriver-objs := base.o int.o umem.o kmem.o sysfs.o ioctl.o + +KERNELDIR ?= /lib/modules/$(shell uname -r)/build +INSTALLDIR ?= /lib/modules/$(shell uname -r)/extra +PWD := $(shell pwd) + +default: +	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules + +install: +	@mkdir -p $(INSTALLDIR) +	@echo "INSTALL $(INSTALLDIR)/pciDriver.ko" +	@install -m 755 pciDriver.ko $(INSTALLDIR) +	@echo "INSTALL /usr/include/pciDriver/driver/pciDriver.h" +	@mkdir -p /usr/include/pciDriver/driver +	@install -m 644 pciDriver.h /usr/include/pciDriver/driver + +uninstall: +	@echo "UNINSTALL $(INSTALLDIR)/pciDriver.ko" +	@rm -f $(INSTALLDIR)/pciDriver.ko +	@echo "UNINSTALL /usr/include/pciDriver/driver/pciDriver.h" +	@rm -rf /usr/include/pciDriver/driver + +clean: +	rm -rf *.o *.ko *.mod.c .*.o.cmd .*.o.tmp .*.ko.cmd  .*.o *.symvers modules.order .tmp_versions diff --git a/driver/base.c b/driver/base.c new file mode 100644 index 0000000..ae77966 --- /dev/null +++ b/driver/base.c @@ -0,0 +1,675 @@ +/** + * + * @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 <linux/version.h> + +/* 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 <linux/string.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/sysfs.h> +#include <asm/atomic.h> +#include <linux/pagemap.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <asm/scatterlist.h> +#include <linux/vmalloc.h> +#include <linux/stat.h> +#include <linux/interrupt.h> +#include <linux/wait.h> + +/* 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 == MPRACE1_VENDOR_ID) && +		(id->device == MPRACE1_DEVICE_ID)) +	{ +		/* It is a mpRACE-1 */ +		mod_info( "Found mpRACE-1 at %s\n", dev_name(&pdev->dev)); +		/* Set bus master */ +		pci_set_master(pdev); +	} +	else if ((id->vendor == PCIXTEST_VENDOR_ID) && +		(id->device == PCIXTEST_DEVICE_ID)) +	{ +		/* It is a PCI-X Test board */ +		mod_info( "Found PCI-X test board at %s\n", dev_name(&pdev->dev)); +	} +	else if ((id->vendor == PCIEPLDA_VENDOR_ID) && +		(id->device == PCIEPLDA_DEVICE_ID)) +	{ +		/* It is a PCI-X Test board */ +		mod_info( "Found PCIe PLDA test board at %s\n", dev_name(&pdev->dev)); +	} +	else if ((id->vendor == PCIEABB_VENDOR_ID) && +		(id->device == PCIEABB_DEVICE_ID)) +	{ +		/* It is a PCI-X Test board */ +		mod_info( "Found PCIe ABB test board at %s\n", dev_name(&pdev->dev)); +	} +	else if ((id->vendor == PCIXPG4_VENDOR_ID) && +		(id->device == PCIXPG4_DEVICE_ID)) +	{ +		/* It is a PCI-X PROGRAPE4 board */ +		mod_info( "Found PCI-X PROGRAPE-4 board at %s\n", dev_name(&pdev->dev)); +	} +	else if ((id->vendor == PCI64PG4_VENDOR_ID) && +		(id->device == PCI64PG4_DEVICE_ID)) +	{ +		/* It is a PCI-64 PROGRAPE4 board */ +		mod_info( "Found PCI-64b/66 PROGRAPE-4 board at %s\n", dev_name(&pdev->dev)); +	} +	else if ((id->vendor == PCIE_XILINX_VENDOR_ID) && +		(id->device == PCIE_ML605_DEVICE_ID)) +	{ +                /* It is a PCI-E Xilinx ML605 evaluation board */ +		mod_info("Found ML605 board at %s\n", 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; +	} + +	/* 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: +	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, +	.ioctl = pcidriver_ioctl, +	.mmap = pcidriver_mmap, +	.open = pcidriver_open, +	.release = pcidriver_release, +}; + +/** + * + * 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; + +	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; + +	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 */ +} diff --git a/driver/base.h b/driver/base.h new file mode 100644 index 0000000..c0dacd0 --- /dev/null +++ b/driver/base.h @@ -0,0 +1,89 @@ +#ifndef _PCIDRIVER_BASE_H +#define _PCIDRIVER_BASE_H + +#include "sysfs.h" + +/** + * + * This file contains prototypes and data structures for internal use of the pciDriver. + * + * + */ + +/* prototypes for file_operations */ +static struct file_operations pcidriver_fops; +int pcidriver_mmap( struct file *filp, struct vm_area_struct *vmap ); +int pcidriver_open(struct inode *inode, struct file *filp ); +int pcidriver_release(struct inode *inode, struct file *filp); + +/* prototypes for device operations */ +static struct pci_driver pcidriver_driver; +static int __devinit pcidriver_probe(struct pci_dev *pdev, const struct pci_device_id *id); +static void __devexit pcidriver_remove(struct pci_dev *pdev); + + + +/* prototypes for module operations */ +static int __init pcidriver_init(void); +static void pcidriver_exit(void); + +/* + * This is the table of PCI devices handled by this driver by default + * If you want to add devices dynamically to this list, do: + * + *   echo "vendor device" > /sys/bus/pci/drivers/pciDriver/new_id + * where vendor and device are in hex, without leading '0x'. + * + * The IDs themselves can be found in common.h + * + * For more info, see <kernel-source>/Documentation/pci.txt + * + * __devinitdata is applied because the kernel does not need those + * tables any more after boot is finished on systems which don't + * support hotplug. + * + */ + +static const __devinitdata struct pci_device_id pcidriver_ids[] = { +	{ PCI_DEVICE( MPRACE1_VENDOR_ID , MPRACE1_DEVICE_ID ) },		// mpRACE-1 +	{ PCI_DEVICE( PCIXTEST_VENDOR_ID , PCIXTEST_DEVICE_ID ) },		// pcixTest +	{ PCI_DEVICE( PCIEPLDA_VENDOR_ID , PCIEPLDA_DEVICE_ID ) },		// PCIePLDA +	{ PCI_DEVICE( PCIEABB_VENDOR_ID , PCIEABB_DEVICE_ID ) },		// PCIeABB +	{ PCI_DEVICE( PCIXPG4_VENDOR_ID , PCIXPG4_DEVICE_ID ) },		// PCI-X PROGRAPE 4 +	{ PCI_DEVICE( PCI64PG4_VENDOR_ID , PCI64PG4_DEVICE_ID ) },		// PCI-64 PROGRAPE 4 +	{ PCI_DEVICE( PCIE_XILINX_VENDOR_ID, PCIE_ML605_DEVICE_ID ) },          // PCI-E Xilinx ML605 +	{0,0,0,0}, +}; + +/* prototypes for internal driver functions */ +int pcidriver_pci_read( pcidriver_privdata_t *privdata, pci_cfg_cmd *pci_cmd ); +int pcidriver_pci_write( pcidriver_privdata_t *privdata, pci_cfg_cmd *pci_cmd ); +int pcidriver_pci_info( pcidriver_privdata_t *privdata, pci_board_info *pci_info ); + +int pcidriver_mmap_pci( pcidriver_privdata_t *privdata, struct vm_area_struct *vmap , int bar ); +int pcidriver_mmap_kmem( pcidriver_privdata_t *privdata, struct vm_area_struct *vmap ); + +/*************************************************************************/ +/* Static data */ +/* Hold the allocated major & minor numbers */ +static dev_t pcidriver_devt; + +/* Number of devices allocated */ +static atomic_t pcidriver_deviceCount; + +/* Sysfs attributes */ +static DEVICE_ATTR(mmap_mode, (S_IRUGO | S_IWUGO), pcidriver_show_mmap_mode, pcidriver_store_mmap_mode); +static DEVICE_ATTR(mmap_area, (S_IRUGO | S_IWUGO), pcidriver_show_mmap_area, pcidriver_store_mmap_area); +static DEVICE_ATTR(kmem_count, S_IRUGO, pcidriver_show_kmem_count, NULL); +static DEVICE_ATTR(kbuffers, S_IRUGO, pcidriver_show_kbuffers, NULL); +static DEVICE_ATTR(kmem_alloc, S_IWUGO, NULL, pcidriver_store_kmem_alloc); +static DEVICE_ATTR(kmem_free, S_IWUGO, NULL, pcidriver_store_kmem_free); +static DEVICE_ATTR(umappings, S_IRUGO, pcidriver_show_umappings, NULL); +static DEVICE_ATTR(umem_unmap, S_IWUGO, NULL, pcidriver_store_umem_unmap); + +#ifdef ENABLE_IRQ +static DEVICE_ATTR(irq_count, S_IRUGO, pcidriver_show_irq_count, NULL); +static DEVICE_ATTR(irq_queues, S_IRUGO, pcidriver_show_irq_queues, NULL); +#endif + +#endif diff --git a/driver/common.h b/driver/common.h new file mode 100644 index 0000000..f386b49 --- /dev/null +++ b/driver/common.h @@ -0,0 +1,114 @@ +#ifndef _PCIDRIVER_COMMON_H +#define _PCIDRIVER_COMMON_H + +/*************************************************************************/ +/* Private data types and structures */ + +/* Define an entry in the kmem list (this list is per device) */ +/* This list keeps references to the allocated kernel buffers */ +typedef struct { +	int id; +	struct list_head list; +	dma_addr_t dma_handle; +	unsigned long cpua; +	unsigned long size; +	struct class_device_attribute sysfs_attr;	/* initialized when adding the entry */ +} pcidriver_kmem_entry_t; + +/* Define an entry in the umem list (this list is per device) */ +/* This list keeps references to the SG lists for each mapped userspace region */ +typedef struct { +	int id; +	struct list_head list; +	unsigned int nr_pages;		/* number of pages for this user memeory area */ +	struct page **pages;		/* list of pointers to the pages */ +	unsigned int nents;			/* actual entries in the scatter/gatter list (NOT nents for the map function, but the result) */ +	struct scatterlist *sg;		/* list of sg entries */ +	struct class_device_attribute sysfs_attr;	/* initialized when adding the entry */ +} pcidriver_umem_entry_t; + +/* Hold the driver private data */ +typedef struct  { +	dev_t devno;						/* device number (major and minor) */ +	struct pci_dev *pdev;				/* PCI device */ +	struct class_device *class_dev;		/* Class device */ +	struct cdev cdev;					/* char device struct */ +	int mmap_mode;						/* current mmap mode */ +	int mmap_area;						/* current PCI mmap area */ + +#ifdef ENABLE_IRQ +	int irq_enabled;					/* Non-zero if IRQ is enabled */ +	int irq_count;						/* Just an IRQ counter */ + +	wait_queue_head_t irq_queues[ PCIDRIVER_INT_MAXSOURCES ]; +										/* One queue per interrupt source */ +	atomic_t irq_outstanding[ PCIDRIVER_INT_MAXSOURCES ]; +										/* Outstanding interrupts per queue */ +	volatile unsigned int *bars_kmapped[6];		/* PCI BARs mmapped in kernel space */ + +#endif +	 +	spinlock_t kmemlist_lock;			/* Spinlock to lock kmem list operations */ +	struct list_head kmem_list;			/* List of 'kmem_list_entry's associated with this device */ +	atomic_t kmem_count;				/* id for next kmem entry */ + +	spinlock_t umemlist_lock;			/* Spinlock to lock umem list operations */ +	struct list_head umem_list;			/* List of 'umem_list_entry's associated with this device */ +	atomic_t umem_count;				/* id for next umem entry */ + +	 +} pcidriver_privdata_t; + +/* Identifies the mpRACE-1 boards */ +#define MPRACE1_VENDOR_ID 0x10b5 +#define MPRACE1_DEVICE_ID 0x9656 + +/* Identifies the PCI-X Test boards */ +#define PCIXTEST_VENDOR_ID 0x10dc +#define PCIXTEST_DEVICE_ID 0x0156 + +/* Identifies the PCIe-PLDA Test board */ +#define PCIEPLDA_VENDOR_ID 0x1556 +#define PCIEPLDA_DEVICE_ID 0x1100 + +/* Identifies the PCIe-ABB Test board */ +#define PCIEABB_VENDOR_ID 0x10dc +#define PCIEABB_DEVICE_ID 0x0153 + +/* Identifies the PCI-X PROGRAPE4 */ +#define PCIXPG4_VENDOR_ID 0x1679 +#define PCIXPG4_DEVICE_ID 0x0001 + +/* Identifies the PCI-64 PROGRAPE4 */ +#define PCI64PG4_VENDOR_ID 0x1679 +#define PCI64PG4_DEVICE_ID 0x0005 + +/* Identifies the PCI-E Xilinx ML605 */ +#define PCIE_XILINX_VENDOR_ID 0x10ee +#define PCIE_ML605_DEVICE_ID 0x04a0 + +/*************************************************************************/ +/* Some nice defines that make code more readable */ +/* This is to print nice info in the log */ + +#ifdef DEBUG + #define mod_info( args... ) \ +    do { printk( KERN_INFO "%s - %s : ", MODNAME , __FUNCTION__ );\ +    printk( args ); } while(0) + #define mod_info_dbg( args... ) \ +    do { printk( KERN_INFO "%s - %s : ", MODNAME , __FUNCTION__ );\ +    printk( args ); } while(0) +#else + #define mod_info( args... ) \ +    do { printk( KERN_INFO "%s: ", MODNAME );\ +    printk( args ); } while(0) + #define mod_info_dbg( args... )  +#endif + +#define mod_crit( args... ) \ +    do { printk( KERN_CRIT "%s: ", MODNAME );\ +    printk( args ); } while(0) + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +#endif diff --git a/driver/compat.h b/driver/compat.h new file mode 100644 index 0000000..80f232b --- /dev/null +++ b/driver/compat.h @@ -0,0 +1,192 @@ +/** + * + * @file compat.h + * @author Michael Stapelberg + * @date 2009-04-05 + * @brief Contains compatibility definitions for the different linux kernel versions to avoid + * putting ifdefs all over the driver code. + * + */ +#ifndef _COMPAT_H +#define _COMPAT_H + +/* dev_name is the wrapper one needs to use to access what was formerly called + * bus_id in struct device. However, before 2.6.27, direct access was necessary, + * so we provide our own version. */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +static inline const char *dev_name(struct device *dev) { +	return dev->bus_id; +} +#endif + +/* SetPageLocked disappeared in v2.6.27 */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27) +	#define compat_lock_page SetPageLocked +	#define compat_unlock_page ClearPageLocked +#else +	/* in v2.6.28, __set_page_locked and __clear_page_locked was introduced */ +	#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,28) +		#define compat_lock_page __set_page_locked +		#define compat_unlock_page __clear_page_locked +	#else +		/* However, in v2.6.27 itself, neither of them is there, so +		 * we need to use our own function fiddling with bits inside +		 * the page struct :-\ */ +		static inline void compat_lock_page(struct page *page) { +			__set_bit(PG_locked, &page->flags); +		} + +		static inline void compat_unlock_page(struct page *page) { +			__clear_bit(PG_locked, &page->flags); +		} +	#endif +#endif + +/* Before 2.6.13, simple_class was the standard interface. Nowadays, it's just called class */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,13) + +	#define class_compat class_simple + +	/* These functions are redirected to their old corresponding functions */ +	#define class_create(module, name) class_simple_create(module, name) +	#define class_destroy(type) class_simple_destroy(type) +	#define class_device_destroy(unused, devno) class_simple_device_remove(devno) +	#define class_device_create(type, unused, devno, devpointer, nameformat, minor, unused) \ +		class_simple_device_add(type, devno, devpointer, nameformat, minor) +	#define class_set_devdata(classdev, privdata) classdev->class_data = privdata +	#define DEVICE_ATTR_COMPAT +	#define sysfs_attr_def_name(name) class_device_attr_##name +	#define sysfs_attr_def_pointer privdata->class_dev +	#define SYSFS_GET_FUNCTION(name) ssize_t name(struct class_device *cls, char *buf) +	#define SYSFS_SET_FUNCTION(name) ssize_t name(struct class_device *cls, const char *buf, size_t count) +	#define SYSFS_GET_PRIVDATA (pcidriver_privdata_t*)cls->class_data + +#else + +/* In 2.6.26, device.h was changed quite significantly. Luckily, it only affected +   type/function names, for the most part. */ +//#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) +	#define class_device_attribute device_attribute +	#define CLASS_DEVICE_ATTR DEVICE_ATTR +	#define class_device device +	#define class_data dev +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) +	#define class_device_create(type, parent, devno, devpointer, nameformat, minor, privdata) \ +		device_create(type, parent, devno, privdata, nameformat, minor) +#else +	#define class_device_create(type, parent, devno, devpointer, nameformat, minor, unused) \ +		device_create(type, parent, devno, nameformat, minor) +#endif +	#define class_device_create_file device_create_file +	#define class_device_remove_file device_remove_file +	#define class_device_destroy device_destroy +	#define DEVICE_ATTR_COMPAT struct device_attribute *attr, +	#define class_set_devdata dev_set_drvdata + +	#define sysfs_attr_def_name(name) dev_attr_##name +	#define sysfs_attr_def_pointer privdata->class_dev +	#define SYSFS_GET_FUNCTION(name) ssize_t name(struct device *dev, struct device_attribute *attr, char *buf) +	#define SYSFS_SET_FUNCTION(name) ssize_t name(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +	#define SYSFS_GET_PRIVDATA dev_get_drvdata(dev) + +//#endif + +#define class_compat class + +#endif + +/* The arguments of IRQ handlers have been changed in 2.6.19. It's very likely that +   int irq will disappear somewhen in the future (current is 2.6.29), too. */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,19) +	#define IRQ_HANDLER_FUNC(name) irqreturn_t name(int irq, void *dev_id) +#else +	#define IRQ_HANDLER_FUNC(name) irqreturn_t name(int irq, void *dev_id, struct pt_regs *regs) +#endif + +/* atomic_inc_return appeared in 2.6.9, at least in CERN scientific linux, provide +   compatibility wrapper for older kernels */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,9) +static int atomic_inc_return(atomic_t *variable) { +	atomic_inc(variable); +	return atomic_read(variable); +} +#endif + +/* sg_set_page is available starting at 2.6.24 */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) + +#define sg_set_page(sg, set_page, set_length, set_offset) do { \ +	(sg)->page = set_page; \ +	(sg)->length = set_length; \ +	(sg)->offset = set_offset; \ +} while (0) + +#endif + +/* Before 2.6.20, disable was not an atomic counter, so this check was needed */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,20) +#define pci_disable_device(pdev) do { \ +	if (pdev->is_enabled) \ +		pci_disable_device(pdev); \ +} while (0) +#endif + +/* Before 2.6.24, scatter/gather lists did not need to be initialized */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,24) +	#define sg_init_table(sg, nr_pages) +#endif + +/* SA_SHIRQ was renamed to IRQF_SHARED in 2.6.24 */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) +	#define request_irq(irq, irq_handler, modname, privdata) request_irq(irq, irq_handler, IRQF_SHARED, modname, privdata) +#else +	#define request_irq(irq, irq_handler, modname, privdata) request_irq(irq, irq_handler, SA_SHIRQ, modname, privdata) +#endif + +/* In 2.6.13, io_remap_page_range was removed in favor for io_remap_pfn_range which works on +   more platforms and allows more memory space */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) +#define io_remap_pfn_range_compat(vmap, vm_start, bar_addr, bar_length, vm_page_prot) \ +	io_remap_pfn_range(vmap, vm_start, (bar_addr >> PAGE_SHIFT), bar_length, vm_page_prot) +#else +#define io_remap_pfn_range_compat(vmap, vm_start, bar_addr, bar_length, vm_page_prot) \ +	io_remap_page_range(vmap, vm_start, bar_addr, bar_length, vm_page_prot) +#endif + +/* In 2.6.10, remap_pfn_range was introduced, see io_remap_pfn_range_compat */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) +#define remap_pfn_range_compat(vmap, vm_start, bar_addr, bar_length, vm_page_prot) \ +	remap_pfn_range(vmap, vm_start, (bar_addr >> PAGE_SHIFT), bar_length, vm_page_prot) + +#define remap_pfn_range_cpua_compat(vmap, vm_start, cpua, size, vm_page_prot) \ +	remap_pfn_range(vmap, vm_start, page_to_pfn(virt_to_page((void*)cpua)), size, vm_page_prot) + +#else +#define remap_pfn_range_compat(vmap, vm_start, bar_addr, bar_length, vm_page_prot) \ +	remap_page_range(vmap, vm_start, bar_addr, bar_length, vm_page_prot) + +#define remap_pfn_range_cpua_compat(vmap, vm_start, cpua, size, vm_page_prot) \ +	remap_page_range(vmap, vm_start, virt_to_phys((void*)cpua), size, vm_page_prot) +#endif + +/** + * Go over the pages of the kmem buffer, and mark them as reserved. + * This is needed, otherwise mmaping the kernel memory to user space + * will fail silently (mmaping /dev/null) when using remap_xx_range. + */ +static inline void set_pages_reserved_compat(unsigned long cpua, unsigned long size) +{ +	/* Starting in 2.6.15, the PG_RESERVED bit was removed. +	   See also http://lwn.net/Articles/161204/ */ +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15) +	struct page *page, *last_page; + +	page = virt_to_page(cpua); +	last_page = virt_to_page(cpua + size - 1); + +	for (; page <= last_page; page++) +               SetPageReserved(page); +#endif +} + +#endif diff --git a/driver/config.h b/driver/config.h new file mode 100644 index 0000000..c217afd --- /dev/null +++ b/driver/config.h @@ -0,0 +1,23 @@ +/*******************************/ +/* Configuration of the driver */ +/*******************************/ + +/* Debug messages */ +//#define DEBUG + +/* Enable/disable IRQ handling */ +#define ENABLE_IRQ + +/* The name of the module */ +#define MODNAME "pciDriver" + +/* Major number is allocated dynamically */ +/* Minor number */ +#define MINORNR 0 + +/* Node name of the char device */ +#define NODENAME "fpga" +#define NODENAMEFMT "fpga%d" + +/* Maximum number of devices*/ +#define MAXDEVICES 4 diff --git a/driver/int.c b/driver/int.c new file mode 100644 index 0000000..1cca7b2 --- /dev/null +++ b/driver/int.c @@ -0,0 +1,297 @@ +/** + * + * @file int.c + * @author Guillermo Marcus + * @date 2009-04-05 + * @brief Contains the interrupt handler. + * + */ + +/* + * Change History: + *  + * $Log: not supported by cvs2svn $ + * Revision 1.7  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.6  2007-11-04 20:58:22  marcus + * Added interrupt generator acknowledge. + * Fixed wrong operator. + * + * Revision 1.5  2007-10-31 15:42:21  marcus + * Added IG ack for testing, may be removed later. + * + * Revision 1.4  2007-07-17 13:15:56  marcus + * Removed Tasklets. + * Using newest map for the ABB interrupts. + * + * Revision 1.3  2007-07-05 15:30:30  marcus + * Added support for both register maps of the ABB. + * + * Revision 1.2  2007-05-29 07:50:18  marcus + * Split code into 2 files. May get merged in the future again.... + * + * 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. + * + */ + +#include <linux/version.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/cdev.h> +#include <linux/wait.h> +#include <linux/sched.h> +#include <stdbool.h> + +#include "config.h" + +#include "compat.h" + +#include "pciDriver.h" + +#include "common.h" + +#include "int.h" + +/* + * The ID between IRQ_SOURCE in irq_outstanding and the actual source is arbitrary. + * Therefore, be careful when communicating with multiple implementations.  + */ + +/* IRQ_SOURCES */ +#define ABB_IRQ_CH0 	        0 +#define ABB_IRQ_CH1 	        1 +#define ABB_IRQ_IG 	        2 + +/* See ABB user’s guide, register definitions (3.1) */ +#define ABB_INT_ENABLE 	        (0x0010 >> 2) +#define ABB_INT_STAT 	        (0x0008 >> 2) + +#define ABB_INT_CH1_TIMEOUT     (1 << 4) +#define ABB_INT_CH0_TIMEOUT     (1 << 5) +#define ABB_INT_IG  	        (1 << 2) +#define ABB_INT_CH0 	        (1 << 1) /* downstream */ +#define ABB_INT_CH1 	        (1)     /* upstream */ + +#define ABB_CH0_CTRL  	        (108 >> 2) +#define ABB_CH1_CTRL  	        (72 >> 2) +#define ABB_CH_RESET 	        (0x0201000A) +#define ABB_IG_CTRL 	        (0x0080 >> 2) +#define ABB_IG_ACK 	        (0x00F0) + +/** + * + * If IRQ-handling is enabled, this function will be called from pcidriver_probe + * to initialize the IRQ handling (maps the BARs) + * + */ +int pcidriver_probe_irq(pcidriver_privdata_t *privdata) +{ +	unsigned char int_pin, int_line; +	unsigned long bar_addr, bar_len, bar_flags; +	int i; +	int err; + +	for (i = 0; i < 6; i++) +		privdata->bars_kmapped[i] = NULL; + +	for (i = 0; i < 6; i++) { +		bar_addr = pci_resource_start(privdata->pdev, i); +		bar_len = pci_resource_len(privdata->pdev, i); +		bar_flags = pci_resource_flags(privdata->pdev, i); + +		/* check if it is a valid BAR, skip if not */ +		if ((bar_addr == 0) || (bar_len == 0)) +			continue; + +		/* Skip IO regions (map only mem regions) */ +		if (bar_flags & IORESOURCE_IO) +			continue; + +		/* Check if the region is available */ +		if ((err = pci_request_region(privdata->pdev, i, NULL)) != 0) { +			mod_info( "Failed to request BAR memory region.\n" ); +			return err; +		} + +		/* Map it into kernel space. */ +		/* For x86 this is just a dereference of the pointer, but that is +		 * not portable. So we need to do the portable way. Thanks Joern! +		 */ + +		/* respect the cacheable-bility of the region */ +		if (bar_flags & IORESOURCE_PREFETCH) +			privdata->bars_kmapped[i] = ioremap(bar_addr, bar_len); +		else +			privdata->bars_kmapped[i] = ioremap_nocache(bar_addr, bar_len); + +		/* check for error */ +		if (privdata->bars_kmapped[i] == NULL) { +			mod_info( "Failed to remap BAR%d into kernel space.\n", i ); +			return -EIO; +		} +	} + +	/* Initialize the interrupt handler for this device */ +	/* Initialize the wait queues */ +	for (i = 0; i < PCIDRIVER_INT_MAXSOURCES; i++) { +		init_waitqueue_head(&(privdata->irq_queues[i])); +		atomic_set(&(privdata->irq_outstanding[i]), 0); +	} + +	/* Initialize the irq config */ +	if ((err = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_PIN, &int_pin)) != 0) { +		/* continue without interrupts */ +		int_pin = 0; +		mod_info("Error getting the interrupt pin. Disabling interrupts for this device\n"); +	} + +	/* Disable interrupts and activate them if everything can be set up properly */ +	privdata->irq_enabled = 0; + +	if (int_pin == 0) +		return 0; + +	if ((err = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_LINE, &int_line)) != 0) { +		mod_info("Error getting the interrupt line. Disabling interrupts for this device\n"); +		return 0; +	} + +	/* register interrupt handler */ +	if ((err = request_irq(privdata->pdev->irq, pcidriver_irq_handler, MODNAME, privdata)) != 0) { +		mod_info("Error registering the interrupt handler. Disabling interrupts for this device\n"); +		return 0; +	} + +	privdata->irq_enabled = 1; +	mod_info("Registered Interrupt Handler at pin %i, line %i, IRQ %i\n", int_pin, int_line, privdata->pdev->irq ); + +	return 0; +} + +/** + * + * Frees/cleans up the data structures, called from pcidriver_remove() + * + */ +void pcidriver_remove_irq(pcidriver_privdata_t *privdata) +{ +	/* Release the IRQ handler */ +	if (privdata->irq_enabled != 0) +		free_irq(privdata->pdev->irq, privdata); + +	pcidriver_irq_unmap_bars(privdata); +} + +/** + * + * Unmaps the BARs and releases them + * + */ +void pcidriver_irq_unmap_bars(pcidriver_privdata_t *privdata) +{ +	int i; + +	for (i = 0; i < 6; i++) { +		if (privdata->bars_kmapped[i] == NULL) +			continue; + +		iounmap((void*)privdata->bars_kmapped[i]); +		pci_release_region(privdata->pdev, i); +	} +} + +/** + * + * Acknowledge the interrupt by ACKing the interrupt generator. + * + * @returns true if the channel was acknowledged and the interrupt handler is done + * + */ +static bool check_acknowlegde_channel(pcidriver_privdata_t *privdata, int interrupt, +				      int channel, volatile unsigned int *bar) +{ +	if (!(bar[ABB_INT_STAT] & interrupt)) +		return false; + +	bar[ABB_INT_ENABLE] &= !interrupt; +	if (interrupt == ABB_INT_IG) +		bar[ABB_IG_CTRL] = ABB_IG_ACK; + +        /* Wake up the waiting loop in ioctl.c:ioctl_wait_interrupt() */ +	atomic_inc(&(privdata->irq_outstanding[channel])); +	wake_up_interruptible(&(privdata->irq_queues[channel])); +	return true; +} + +/** + * + * Acknowledges the receival of an interrupt to the card. + * + * @returns true if the card was acknowledget + * @returns false if the interrupt was not for one of our cards + * + * @see check_acknowlegde_channel + * + */ +static bool pcidriver_irq_acknowledge(pcidriver_privdata_t *privdata) +{ +	volatile unsigned int *bar; + +	/* TODO: add subvendor / subsystem ids */ +	/* FIXME: guillermo: which ones? all? */ + +	/* Test if we have to handle this interrupt */ +	if ((privdata->pdev->vendor != PCIEABB_VENDOR_ID) || +	    (privdata->pdev->device != PCIEABB_DEVICE_ID)) +		return false; + +	/* Acknowledge the device */ +	/* this is for ABB / wenxue DMA engine */ +	bar = privdata->bars_kmapped[0]; + +	mod_info_dbg("interrupt registers. ISR: %x, IER: %x\n", bar[ABB_INT_STAT], bar[ABB_INT_ENABLE]); + +	if (check_acknowlegde_channel(privdata, ABB_INT_CH0, ABB_IRQ_CH0, bar)) +		return true; + +	if (check_acknowlegde_channel(privdata, ABB_INT_CH1, ABB_IRQ_CH1, bar)) +		return true; + +	if (check_acknowlegde_channel(privdata, ABB_INT_IG, ABB_IRQ_IG, bar)) +		return true; + +        if (check_acknowlegde_channel(privdata, ABB_INT_CH0_TIMEOUT, ABB_IRQ_CH0, bar)) +                return true; + +        if (check_acknowlegde_channel(privdata, ABB_INT_CH1_TIMEOUT, ABB_IRQ_CH1, bar)) +                return true; + +	mod_info_dbg("err: interrupt registers. ISR: %x, IER: %x\n", bar[ ABB_INT_STAT ], bar[ ABB_INT_ENABLE ] ); + +	return false; +} + +/** + * + * Handles IRQs. At the moment, this acknowledges the card that this IRQ + * was received and then increases the driver's IRQ counter. + * + * @see pcidriver_irq_acknowledge + * + */ +IRQ_HANDLER_FUNC(pcidriver_irq_handler) +{ +	pcidriver_privdata_t *privdata = (pcidriver_privdata_t *)dev_id; + +	if (!pcidriver_irq_acknowledge(privdata)) +		return IRQ_NONE; + +	privdata->irq_count++; +	return IRQ_HANDLED; +} diff --git a/driver/int.h b/driver/int.h new file mode 100644 index 0000000..4a834c7 --- /dev/null +++ b/driver/int.h @@ -0,0 +1,9 @@ +#if !defined(_PCIDRIVER_INT_H) && defined(ENABLE_IRQ) +#define _PCIDRIVER_INT_H + +int pcidriver_probe_irq(pcidriver_privdata_t *privdata); +void pcidriver_remove_irq(pcidriver_privdata_t *privdata); +void pcidriver_irq_unmap_bars(pcidriver_privdata_t *privdata); +IRQ_HANDLER_FUNC(pcidriver_irq_handler); + +#endif diff --git a/driver/ioctl.c b/driver/ioctl.c new file mode 100644 index 0000000..59d9245 --- /dev/null +++ b/driver/ioctl.c @@ -0,0 +1,446 @@ +/** + * + * @file ioctl.c + * @author Guillermo Marcus + * @date 2009-04-05 + * @brief Contains the functions handling the different ioctl calls. + * + */ +#include <linux/version.h> +#include <linux/string.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/cdev.h> +#include <linux/sysfs.h> +#include <asm/atomic.h> +#include <linux/pagemap.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <asm/scatterlist.h> +#include <linux/vmalloc.h> +#include <linux/stat.h> +#include <linux/interrupt.h> +#include <linux/wait.h> +#include <linux/sched.h> + +#include "config.h" 			/* Configuration for the driver */ +#include "compat.h" 			/* Compatibility functions/definitions */ +#include "pciDriver.h" 			/* External interface for the driver */ +#include "common.h" 			/* Internal definitions for all parts */ +#include "kmem.h" 			/* Internal definitions for kernel memory */ +#include "umem.h" 			/* Internal definitions for user space memory */ +#include "ioctl.h"			/* Internal definitions for the ioctl part */ + +/** Declares a variable of the given type with the given name and copies it from userspace */ +#define READ_FROM_USER(type, name) \ +	type name; \ +	if ((ret = copy_from_user(&name, (type*)arg, sizeof(name))) != 0) \ +		return -EFAULT; + +/** Writes back the given variable with the given type to userspace */ +#define WRITE_TO_USER(type, name) \ +	if ((ret = copy_to_user((type*)arg, &name, sizeof(name))) != 0) \ +		return -EFAULT; + +/** + * + * Sets the mmap mode for following mmap() calls. + * + * @param arg Not a pointer, but either PCIDRIVER_MMAP_PCI or PCIDRIVER_MMAP_KMEM + * + */ +static int ioctl_mmap_mode(pcidriver_privdata_t *privdata, unsigned long arg) +{ +	if ((arg != PCIDRIVER_MMAP_PCI) && (arg != PCIDRIVER_MMAP_KMEM)) +		return -EINVAL; + +	/* change the mode */ +	privdata->mmap_mode = arg; + +	return 0; +} + +/** + * + * Sets the mmap area (BAR) for following mmap() calls. + * + */ +static int ioctl_mmap_area(pcidriver_privdata_t *privdata, unsigned long arg) +{ +	/* validate input */ +	if ((arg < PCIDRIVER_BAR0) || (arg > PCIDRIVER_BAR5)) +		return -EINVAL; + +	/* change the PCI area to mmap */ +	privdata->mmap_area = arg; + +	return 0; +} + +/** + * + * Reads/writes a byte/word/dword of the device's PCI config. + * + * @see pcidriver_pci_read + * @see pcidriver_pci_write + * + */ +static int ioctl_pci_config_read_write(pcidriver_privdata_t *privdata, unsigned int cmd, unsigned long arg) +{ +	int ret; +	READ_FROM_USER(pci_cfg_cmd, pci_cmd); + +	if (cmd == PCIDRIVER_IOC_PCI_CFG_RD) { +		switch (pci_cmd.size) { +			case PCIDRIVER_PCI_CFG_SZ_BYTE: +				ret = pci_read_config_byte( privdata->pdev, pci_cmd.addr, &(pci_cmd.val.byte) ); +				break; +			case PCIDRIVER_PCI_CFG_SZ_WORD: +				ret = pci_read_config_word( privdata->pdev, pci_cmd.addr, &(pci_cmd.val.word) ); +				break; +			case PCIDRIVER_PCI_CFG_SZ_DWORD: +				ret = pci_read_config_dword( privdata->pdev, pci_cmd.addr, &(pci_cmd.val.dword) ); +				break; +			default: +				return -EINVAL;		/* Wrong size setting */ +		} +	} else { +		switch (pci_cmd.size) { +			case PCIDRIVER_PCI_CFG_SZ_BYTE: +				ret = pci_write_config_byte( privdata->pdev, pci_cmd.addr, pci_cmd.val.byte ); +				break; +			case PCIDRIVER_PCI_CFG_SZ_WORD: +				ret = pci_write_config_word( privdata->pdev, pci_cmd.addr, pci_cmd.val.word ); +				break; +			case PCIDRIVER_PCI_CFG_SZ_DWORD: +				ret = pci_write_config_dword( privdata->pdev, pci_cmd.addr, pci_cmd.val.dword ); +				break; +			default: +				return -EINVAL;		/* Wrong size setting */ +				break; +		} +	} + +	WRITE_TO_USER(pci_cfg_cmd, pci_cmd); + +	return 0; +} + +/** + * + * Gets the PCI information for the device. + * + * @see pcidriver_pci_info + * + */ +static int ioctl_pci_info(pcidriver_privdata_t *privdata, unsigned long arg) +{ +	int ret; +	int bar; +	READ_FROM_USER(pci_board_info, pci_info); + +	pci_info.vendor_id = privdata->pdev->vendor; +	pci_info.device_id = privdata->pdev->device; +	pci_info.bus = privdata->pdev->bus->number; +	pci_info.slot = PCI_SLOT(privdata->pdev->devfn); +	pci_info.devfn = privdata->pdev->devfn; + +	if ((ret = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_PIN, &(pci_info.interrupt_pin))) != 0) +		return ret; + +	if ((ret = pci_read_config_byte(privdata->pdev, PCI_INTERRUPT_LINE, &(pci_info.interrupt_line))) != 0) +		return ret; + +	for (bar = 0; bar < 6; bar++) { +		pci_info.bar_start[bar] = pci_resource_start(privdata->pdev, bar); +		pci_info.bar_length[bar] = pci_resource_len(privdata->pdev, bar); +	} + +	WRITE_TO_USER(pci_board_info, pci_info); + +	return 0; +} + +/** + * + * Allocates kernel memory. + * + * @see pcidriver_kmem_alloc + * + */ +static int ioctl_kmem_alloc(pcidriver_privdata_t *privdata, unsigned long arg) +{ +	int ret; +	READ_FROM_USER(kmem_handle_t, khandle); + +	if ((ret = pcidriver_kmem_alloc(privdata, &khandle)) != 0) +		return ret; + +	WRITE_TO_USER(kmem_handle_t, khandle); + +	return 0; +} + +/** + * + * Frees kernel memory. + * + * @see pcidriver_kmem_free + * + */ +static int ioctl_kmem_free(pcidriver_privdata_t *privdata, unsigned long arg) +{ +	int ret; +	READ_FROM_USER(kmem_handle_t, khandle); + +	if ((ret = pcidriver_kmem_free(privdata, &khandle)) != 0) +		return ret; + +	return 0; +} + +/** + * + * Syncs kernel memory. + * + * @see pcidriver_kmem_sync + * + */ +static int ioctl_kmem_sync(pcidriver_privdata_t *privdata, unsigned long arg) +{ +	int ret; +	READ_FROM_USER(kmem_sync_t, ksync); + +	return pcidriver_kmem_sync(privdata, &ksync); +} + +/* + * + * Maps the given scatter/gather list from memory to PCI bus addresses. + * + * @see pcidriver_umem_sgmap + * + */ +static int ioctl_umem_sgmap(pcidriver_privdata_t *privdata, unsigned long arg) +{ +	int ret; +	READ_FROM_USER(umem_handle_t, uhandle); + +	if ((ret = pcidriver_umem_sgmap(privdata, &uhandle)) != 0) +		return ret; + +	WRITE_TO_USER(umem_handle_t, uhandle); + +	return 0; +} + +/** + * + * Unmaps the given scatter/gather list. + * + * @see pcidriver_umem_sgunmap + * + */ +static int ioctl_umem_sgunmap(pcidriver_privdata_t *privdata, unsigned long arg) +{ +	int ret; +	pcidriver_umem_entry_t *umem_entry; +	READ_FROM_USER(umem_handle_t, uhandle); + +	/* Find the associated umem_entry for this buffer, +	 * return -EINVAL if the specified handle id is invalid */ +	if ((umem_entry = pcidriver_umem_find_entry_id(privdata, uhandle.handle_id)) == NULL) +		return -EINVAL; + +	if ((ret = pcidriver_umem_sgunmap(privdata, umem_entry)) != 0) +		return ret; + +	return 0; +} + +/** + * + * Copies the scatter/gather list from kernelspace to userspace. + * + * @see pcidriver_umem_sgget + * + */ +static int ioctl_umem_sgget(pcidriver_privdata_t *privdata, unsigned long arg) +{ +	int ret; +	READ_FROM_USER(umem_sglist_t, usglist); + +	/* The umem_sglist_t has a pointer to the scatter/gather list itself which +	 * needs to be copied separately. The number of elements is stored in ->nents. +	 * As the list can get very big, we need to use vmalloc. */ +	if ((usglist.sg = vmalloc(usglist.nents * sizeof(umem_sgentry_t))) == NULL) +		return -ENOMEM; + +	/* copy array to kernel structure */ +	ret = copy_from_user(usglist.sg, ((umem_sglist_t *)arg)->sg, (usglist.nents)*sizeof(umem_sgentry_t)); +	if (ret) return -EFAULT; + +	if ((ret = pcidriver_umem_sgget(privdata, &usglist)) != 0) +		return ret; + +	/* write data to user space */ +	ret = copy_to_user(((umem_sglist_t *)arg)->sg, usglist.sg, (usglist.nents)*sizeof(umem_sgentry_t)); +	if (ret) return -EFAULT; + +	/* free array memory */ +	vfree(usglist.sg); + +	/* restore sg pointer to vma address in user space before copying */ +	usglist.sg = ((umem_sglist_t *)arg)->sg; + +	WRITE_TO_USER(umem_sglist_t, usglist); + +	return 0; +} + +/** + * + * Syncs user memory. + * + * @see pcidriver_umem_sync + * + */ +static int ioctl_umem_sync(pcidriver_privdata_t *privdata, unsigned long arg) +{ +	int ret; +	READ_FROM_USER(umem_handle_t, uhandle); + +	return pcidriver_umem_sync( privdata, &uhandle ); +} + +/** + * + * Waits for an interrupt + * + * @param arg Not a pointer, but the irq source to wait for (unsigned int) + * + */ +static int ioctl_wait_interrupt(pcidriver_privdata_t *privdata, unsigned long arg) +{ +#ifdef ENABLE_IRQ +	unsigned int irq_source; +	int temp; + +	if (arg >= PCIDRIVER_INT_MAXSOURCES) +		return -EFAULT;						/* User tried to overrun the IRQ_SOURCES array */ + +	irq_source = arg; + +	/* Thanks to Joern for the correction and tips! */ +	/* done this way to avoid wrong behaviour (endless loop) of the compiler in AMD platforms */ +	temp=1; +	while (temp) { +		/* We wait here with an interruptible timeout. This will be interrupted +                 * by int.c:check_acknowledge_channel() as soon as in interrupt for +                 * the specified source arrives. */ +		wait_event_interruptible_timeout( (privdata->irq_queues[irq_source]), (atomic_read(&(privdata->irq_outstanding[irq_source])) > 0), (10*HZ/1000) ); + +		if (atomic_add_negative( -1, &(privdata->irq_outstanding[irq_source])) ) +			atomic_inc( &(privdata->irq_outstanding[irq_source]) ); +		else +			temp =0; +	} + +	return 0; +#else +	mod_info("Asked to wait for interrupt but interrupts are not enabled in the driver\n"); +	return -EFAULT; +#endif +} + +/** + * + * Clears the interrupt wait queue. + * + * @param arg Not a pointer, but the irq source (unsigned int) + * @returns -EFAULT if the user specified an irq source out of range + * + */ +static int ioctl_clear_ioq(pcidriver_privdata_t *privdata, unsigned long arg) +{ +#ifdef ENABLE_IRQ +	unsigned int irq_source; + +	if (arg >= PCIDRIVER_INT_MAXSOURCES) +		return -EFAULT; + +	irq_source = arg; +	atomic_set(&(privdata->irq_outstanding[irq_source]), 0); + +	return 0; +#else +	mod_info("Asked to wait for interrupt but interrupts are not enabled in the driver\n"); +	return -EFAULT; +#endif +} + +/** + * + * This function handles all ioctl file operations. + * Generally, the data of the ioctl is copied from userspace to kernelspace, a separate + * function is called to handle the ioctl itself, then the data is copied back to userspace. + * + * @returns -EFAULT when an invalid memory pointer is passed + * + */ +int pcidriver_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) +{ +	pcidriver_privdata_t *privdata = filp->private_data; + +	/* Select the appropiate command */ +	switch (cmd) { +		case PCIDRIVER_IOC_MMAP_MODE: +			return ioctl_mmap_mode(privdata, arg); + +		case PCIDRIVER_IOC_MMAP_AREA: +			return ioctl_mmap_area(privdata, arg); + +		case PCIDRIVER_IOC_PCI_CFG_RD: +		case PCIDRIVER_IOC_PCI_CFG_WR: +			return ioctl_pci_config_read_write(privdata, cmd, arg); + +		case PCIDRIVER_IOC_PCI_INFO: +			return ioctl_pci_info(privdata, arg); + +		case PCIDRIVER_IOC_KMEM_ALLOC: +			return ioctl_kmem_alloc(privdata, arg); + +		case PCIDRIVER_IOC_KMEM_FREE: +			return ioctl_kmem_free(privdata, arg); + +		case PCIDRIVER_IOC_KMEM_SYNC: +			return ioctl_kmem_sync(privdata, arg); + +		case PCIDRIVER_IOC_UMEM_SGMAP: +			return ioctl_umem_sgmap(privdata, arg); + +		case PCIDRIVER_IOC_UMEM_SGUNMAP: +			return ioctl_umem_sgunmap(privdata, arg); + +		case PCIDRIVER_IOC_UMEM_SGGET: +			return ioctl_umem_sgget(privdata, arg); + +		case PCIDRIVER_IOC_UMEM_SYNC: +			return ioctl_umem_sync(privdata, arg); + +		case PCIDRIVER_IOC_WAITI: +			return ioctl_wait_interrupt(privdata, arg); + +		case PCIDRIVER_IOC_CLEAR_IOQ: +			return ioctl_clear_ioq(privdata, arg); + +		default: +			return -EINVAL; +	} +} diff --git a/driver/ioctl.h b/driver/ioctl.h new file mode 100644 index 0000000..a0e9300 --- /dev/null +++ b/driver/ioctl.h @@ -0,0 +1 @@ +int pcidriver_ioctl(struct inode  *inode, struct file *filp, unsigned int cmd, unsigned long arg); diff --git a/driver/kmem.c b/driver/kmem.c new file mode 100644 index 0000000..737b74d --- /dev/null +++ b/driver/kmem.c @@ -0,0 +1,321 @@ +/** + * + * @file kmem.c + * @brief This file contains all functions dealing with kernel memory. + * @author Guillermo Marcus + * @date 2009-04-05 + * + */ +#include <linux/version.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/cdev.h> +#include <linux/wait.h> +#include <linux/mm.h> +#include <linux/pagemap.h> + +#include "config.h"			/* compile-time configuration */ +#include "compat.h"			/* compatibility definitions for older linux */ +#include "pciDriver.h"			/* external interface for the driver */ +#include "common.h"			/* internal definitions for all parts */ +#include "kmem.h"			/* prototypes for kernel memory */ +#include "sysfs.h"			/* prototypes for sysfs */ + +/** + * + * Allocates new kernel memory including the corresponding management structure, makes + * it available via sysfs if possible. + * + */ +int pcidriver_kmem_alloc(pcidriver_privdata_t *privdata, kmem_handle_t *kmem_handle) +{ +	pcidriver_kmem_entry_t *kmem_entry; +	void *retptr; + +	/* First, allocate zeroed memory for the kmem_entry */ +	if ((kmem_entry = kcalloc(1, sizeof(pcidriver_kmem_entry_t), GFP_KERNEL)) == NULL) +		goto kmem_alloc_entry_fail; + +	/* Initialize the kmem_entry */ +	kmem_entry->id = atomic_inc_return(&privdata->kmem_count) - 1; +	kmem_entry->size = kmem_handle->size; +	kmem_handle->handle_id = kmem_entry->id; + +	/* Initialize sysfs if possible */ +	if (pcidriver_sysfs_initialize_kmem(privdata, kmem_entry->id, &(kmem_entry->sysfs_attr)) != 0) +		goto kmem_alloc_mem_fail; + +	/* ...and allocate the DMA memory */ +	/* note this is a memory pair, referencing the same area: the cpu address (cpua) +	 * and the PCI bus address (pa). The CPU and PCI addresses may not be the same. +	 * The CPU sees only CPU addresses, while the device sees only PCI addresses. +	 * CPU address is used for the mmap (internal to the driver), and +	 * PCI address is the address passed to the DMA Controller in the device. +	 */ +	retptr = pci_alloc_consistent( privdata->pdev, kmem_handle->size, &(kmem_entry->dma_handle) ); +	if (retptr == NULL) +		goto kmem_alloc_mem_fail; +	kmem_entry->cpua = (unsigned long)retptr; +	kmem_handle->pa = (unsigned long)(kmem_entry->dma_handle); + +	set_pages_reserved_compat(kmem_entry->cpua, kmem_entry->size); + +	/* Add the kmem_entry to the list of the device */ +	spin_lock( &(privdata->kmemlist_lock) ); +	list_add_tail( &(kmem_entry->list), &(privdata->kmem_list) ); +	spin_unlock( &(privdata->kmemlist_lock) ); + +	return 0; + +kmem_alloc_mem_fail: +		kfree(kmem_entry); +kmem_alloc_entry_fail: +		return -ENOMEM; +} + +/** + * + * Called via sysfs, frees kernel memory and the corresponding management structure + * + */ +int pcidriver_kmem_free( pcidriver_privdata_t *privdata, kmem_handle_t *kmem_handle ) +{ +	pcidriver_kmem_entry_t *kmem_entry; + +	/* Find the associated kmem_entry for this buffer */ +	if ((kmem_entry = pcidriver_kmem_find_entry(privdata, kmem_handle)) == NULL) +		return -EINVAL;					/* kmem_handle is not valid */ + +	return pcidriver_kmem_free_entry(privdata, kmem_entry); +} + +/** + * + * Called when cleaning up, frees all kernel memory and their corresponding management structure + * + */ +int pcidriver_kmem_free_all(pcidriver_privdata_t *privdata) +{ +	struct list_head *ptr, *next; +	pcidriver_kmem_entry_t *kmem_entry; + +	/* iterate safely over the entries and delete them */ +	list_for_each_safe(ptr, next, &(privdata->kmem_list)) { +		kmem_entry = list_entry(ptr, pcidriver_kmem_entry_t, list); +		pcidriver_kmem_free_entry(privdata, kmem_entry); 		/* spin lock inside! */ +	} + +	return 0; +} + +/** + * + * Synchronize memory to/from the device (or in both directions). + * + */ +int pcidriver_kmem_sync( pcidriver_privdata_t *privdata, kmem_sync_t *kmem_sync ) +{ +	pcidriver_kmem_entry_t *kmem_entry; + +	/* Find the associated kmem_entry for this buffer */ +	if ((kmem_entry = pcidriver_kmem_find_entry(privdata, &(kmem_sync->handle))) == NULL) +		return -EINVAL;					/* kmem_handle is not valid */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11) +	switch (kmem_sync->dir) { +		case PCIDRIVER_DMA_TODEVICE: +			pci_dma_sync_single_for_device( privdata->pdev, kmem_entry->dma_handle, kmem_entry->size, PCI_DMA_TODEVICE ); +			break; +		case PCIDRIVER_DMA_FROMDEVICE: +			pci_dma_sync_single_for_cpu( privdata->pdev, kmem_entry->dma_handle, kmem_entry->size, PCI_DMA_FROMDEVICE ); +			break; +		case PCIDRIVER_DMA_BIDIRECTIONAL: +			pci_dma_sync_single_for_device( privdata->pdev, kmem_entry->dma_handle, kmem_entry->size, PCI_DMA_BIDIRECTIONAL ); +			pci_dma_sync_single_for_cpu( privdata->pdev, kmem_entry->dma_handle, kmem_entry->size, PCI_DMA_BIDIRECTIONAL ); +			break; +		default: +			return -EINVAL;				/* wrong direction parameter */ +	} +#else +	switch (kmem_sync->dir) { +		case PCIDRIVER_DMA_TODEVICE: +			pci_dma_sync_single( privdata->pdev, kmem_entry->dma_handle, kmem_entry->size, PCI_DMA_TODEVICE ); +			break; +		case PCIDRIVER_DMA_FROMDEVICE: +			pci_dma_sync_single( privdata->pdev, kmem_entry->dma_handle, kmem_entry->size, PCI_DMA_FROMDEVICE ); +			break; +		case PCIDRIVER_DMA_BIDIRECTIONAL: +			pci_dma_sync_single( privdata->pdev, kmem_entry->dma_handle, kmem_entry->size, PCI_DMA_BIDIRECTIONAL ); +			break; +		default: +			return -EINVAL;				/* wrong direction parameter */ +	} +#endif + +	return 0;	/* success */ +} + +/** + * + * Free the given kmem_entry and its memory. + * + */ +int pcidriver_kmem_free_entry(pcidriver_privdata_t *privdata, pcidriver_kmem_entry_t *kmem_entry) +{ +	pcidriver_sysfs_remove(privdata, &(kmem_entry->sysfs_attr)); + +	/* Go over the pages of the kmem buffer, and mark them as not reserved */ +#if 0 +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,15) +	/* +	 * This code is DISABLED. +	 * Apparently, it is not needed to unreserve them. Doing so here +	 * hangs the machine. Why? +	 * +	 * Uhm.. see links: +	 * +	 * http://lwn.net/Articles/161204/ +	 * http://lists.openfabrics.org/pipermail/general/2007-March/034101.html +	 * +	 * I insist, this should be enabled, but doing so hangs the machine. +	 * Literature supports the point, and there is even a similar problem (see link) +	 * But this is not the case. It seems right to me. but obviously is not. +	 * +	 * Anyway, this goes away in kernel >=2.6.15. +	 */ +	unsigned long start = __pa(kmem_entry->cpua) >> PAGE_SHIFT; +	unsigned long end = __pa(kmem_entry->cpua + kmem_entry->size) >> PAGE_SHIFT; +	unsigned long i; +	for(i=start;i<end;i++) { +		struct page *kpage = pfn_to_page(i); +		ClearPageReserved(kpage); +	} +#endif +#endif + +	/* Release DMA memory */ +	pci_free_consistent( privdata->pdev, kmem_entry->size, (void *)(kmem_entry->cpua), kmem_entry->dma_handle ); + +	/* Remove the kmem list entry */ +	spin_lock( &(privdata->kmemlist_lock) ); +	list_del( &(kmem_entry->list) ); +	spin_unlock( &(privdata->kmemlist_lock) ); + +	/* Release kmem_entry memory */ +	kfree(kmem_entry); + +	return 0; +} + +/** + * + * Find the corresponding kmem_entry for the given kmem_handle. + * + */ +pcidriver_kmem_entry_t *pcidriver_kmem_find_entry(pcidriver_privdata_t *privdata, kmem_handle_t *kmem_handle) +{ +	struct list_head *ptr; +	pcidriver_kmem_entry_t *entry, *result = NULL; + +	/* should I implement it better using the handle_id? */ + +	spin_lock(&(privdata->kmemlist_lock)); +	list_for_each(ptr, &(privdata->kmem_list)) { +		entry = list_entry(ptr, pcidriver_kmem_entry_t, list); + +		if (entry->dma_handle == kmem_handle->pa) { +			result = entry; +			break; +		} +	} + +	spin_unlock(&(privdata->kmemlist_lock)); +	return result; +} + +/** + * + * find the corresponding kmem_entry for the given id. + * + */ +pcidriver_kmem_entry_t *pcidriver_kmem_find_entry_id(pcidriver_privdata_t *privdata, int id) +{ +	struct list_head *ptr; +	pcidriver_kmem_entry_t *entry, *result = NULL; + +	spin_lock(&(privdata->kmemlist_lock)); +	list_for_each(ptr, &(privdata->kmem_list)) { +		entry = list_entry(ptr, pcidriver_kmem_entry_t, list); + +		if (entry->id == id) { +			result = entry; +			break; +		} +	} + +	spin_unlock(&(privdata->kmemlist_lock)); +	return result; +} + +/** + * + * mmap() kernel memory to userspace. + * + */ +int pcidriver_mmap_kmem(pcidriver_privdata_t *privdata, struct vm_area_struct *vma) +{ +	unsigned long vma_size; +	pcidriver_kmem_entry_t *kmem_entry; +	int ret; + +	mod_info_dbg("Entering mmap_kmem\n"); + +	/* FIXME: Is this really right? Always just the latest one? Can't we identify one? */ +	/* Get latest entry on the kmem_list */ +	spin_lock(&(privdata->kmemlist_lock)); +	if (list_empty(&(privdata->kmem_list))) { +		spin_unlock(&(privdata->kmemlist_lock)); +		mod_info("Trying to mmap a kernel memory buffer without creating it first!\n"); +		return -EFAULT; +	} +	kmem_entry = list_entry(privdata->kmem_list.prev, pcidriver_kmem_entry_t, list); +	spin_unlock(&(privdata->kmemlist_lock)); + +	mod_info_dbg("Got kmem_entry with id: %d\n", kmem_entry->id); + +	/* Check sizes */ +	vma_size = (vma->vm_end - vma->vm_start); +	if ((vma_size != kmem_entry->size) && +		((kmem_entry->size < PAGE_SIZE) && (vma_size != PAGE_SIZE))) { +		mod_info("kem_entry size(%lu) and vma size do not match(%lu)\n", kmem_entry->size, vma_size); +		return -EINVAL; +	} + +	vma->vm_flags |= (VM_RESERVED); + +#ifdef pgprot_noncached +	// This is coherent memory, so it must not be cached. +	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); +#endif + +	mod_info_dbg("Mapping address %08lx / PFN %08lx\n", +			virt_to_phys((void*)kmem_entry->cpua), +			page_to_pfn(virt_to_page((void*)kmem_entry->cpua))); + +	ret = remap_pfn_range_cpua_compat( +					vma, +					vma->vm_start, +					kmem_entry->cpua, +					kmem_entry->size, +					vma->vm_page_prot ); + +	if (ret) { +		mod_info("kmem remap failed: %d (%lx)\n", ret,kmem_entry->cpua); +		return -EAGAIN; +	} + +	return ret; +} diff --git a/driver/kmem.h b/driver/kmem.h new file mode 100644 index 0000000..c9e2fb9 --- /dev/null +++ b/driver/kmem.h @@ -0,0 +1,7 @@ +int pcidriver_kmem_alloc( pcidriver_privdata_t *privdata, kmem_handle_t *kmem_handle ); +int pcidriver_kmem_free(  pcidriver_privdata_t *privdata, kmem_handle_t *kmem_handle ); +int pcidriver_kmem_sync(  pcidriver_privdata_t *privdata, kmem_sync_t *kmem_sync ); +int pcidriver_kmem_free_all(  pcidriver_privdata_t *privdata ); +pcidriver_kmem_entry_t *pcidriver_kmem_find_entry( pcidriver_privdata_t *privdata, kmem_handle_t *kmem_handle ); +pcidriver_kmem_entry_t *pcidriver_kmem_find_entry_id( pcidriver_privdata_t *privdata, int id ); +int pcidriver_kmem_free_entry( pcidriver_privdata_t *privdata, pcidriver_kmem_entry_t *kmem_entry ); diff --git a/driver/pciDriver.h b/driver/pciDriver.h new file mode 100644 index 0000000..9abf3ab --- /dev/null +++ b/driver/pciDriver.h @@ -0,0 +1,180 @@ +#ifndef PCIDRIVER_H_ +#define PCIDRIVER_H_ + +/** + * This is a full rewrite of the pciDriver. + * New default is to support kernel 2.6, using kernel 2.6 APIs. + *  + * This header defines the interface to the outside world. + *  + * $Revision: 1.6 $ + * $Date: 2008-01-24 14:21:36 $ + *  + */ + +/* + * Change History: + *  + * $Log: not supported by cvs2svn $ + * Revision 1.5  2008-01-11 10:15:14  marcus + * Removed unused interrupt code. + * Added intSource to the wait interrupt call. + * + * Revision 1.4  2006/11/17 18:44:42  marcus + * Type of SG list can now be selected at runtime. Added type to sglist. + * + * Revision 1.3  2006/11/17 16:23:02  marcus + * Added slot number to the PCI info IOctl. + * + * Revision 1.2  2006/11/13 12:29:09  marcus + * Added a IOctl call, to confiure the interrupt response. (testing pending). + * Basic interrupts are now supported. + * + * Revision 1.1  2006/10/10 14:46:52  marcus + * Initial commit of the new pciDriver for kernel 2.6 + * + * Revision 1.7  2006/10/06 15:18:06  marcus + * Updated PCI info and PCI cmd + * + * Revision 1.6  2006/09/25 16:51:07  marcus + * Added PCI config IOctls, and implemented basic mmap functions. + * + * Revision 1.5  2006/09/18 17:13:12  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 <linux/ioctl.h> + +/* Possible values for ioctl commands */ + +/* PCI mmap areas */ +#define	PCIDRIVER_BAR0		0 +#define	PCIDRIVER_BAR1		1 +#define	PCIDRIVER_BAR2		2 +#define	PCIDRIVER_BAR3		3 +#define	PCIDRIVER_BAR4		4 +#define	PCIDRIVER_BAR5		5 + +/* mmap mode of the device */ +#define PCIDRIVER_MMAP_PCI	0 +#define PCIDRIVER_MMAP_KMEM 1 + +/* Direction of a DMA operation */ +#define PCIDRIVER_DMA_BIDIRECTIONAL 0 +#define	PCIDRIVER_DMA_TODEVICE		1 +#define PCIDRIVER_DMA_FROMDEVICE	2 + +/* Possible sizes in a PCI command */ +#define PCIDRIVER_PCI_CFG_SZ_BYTE  1 +#define PCIDRIVER_PCI_CFG_SZ_WORD  2 +#define PCIDRIVER_PCI_CFG_SZ_DWORD 3 + +/* Possible types of SG lists */ +#define PCIDRIVER_SG_NONMERGED 0 +#define PCIDRIVER_SG_MERGED 1 + +/* Maximum number of interrupt sources */ +#define PCIDRIVER_INT_MAXSOURCES 16 + +/* Types */ +typedef struct { +	unsigned long pa; +	unsigned long size; +	int handle_id; +} kmem_handle_t; + +typedef struct { +	unsigned long addr; +	unsigned long size; +} umem_sgentry_t; + +typedef struct { +	int handle_id; +	int type; +	int nents; +	umem_sgentry_t *sg; +} umem_sglist_t; + +typedef struct { +	unsigned long vma; +	unsigned long size; +	int handle_id; +	int dir; +} umem_handle_t; + +typedef struct { +	kmem_handle_t handle; +	int dir; +} kmem_sync_t; + + +typedef struct { +	int size; +	int addr; +	union { +		unsigned char byte; +		unsigned short word; +		unsigned int dword; 	/* not strict C, but if not can have problems */ +	} val; +} pci_cfg_cmd; + +typedef struct { +	unsigned short vendor_id; +	unsigned short device_id; +	unsigned short bus; +	unsigned short slot; +	unsigned short devfn; +	unsigned char interrupt_pin; +	unsigned char interrupt_line; +	unsigned int irq; +	unsigned long bar_start[6]; +	unsigned long bar_length[6]; +} pci_board_info; + + +/* ioctl interface */ +/* See documentation for a detailed usage explanation */ + +/*  + * one of the problems of ioctl, is that requires a type definition. + * This type is only 8-bits wide, and half-documented in  + * <linux-src>/Documentation/ioctl-number.txt. + * previous SHL -> 'S' definition, conflicts with several devices, + * so I changed it to be pci -> 'p', in the range 0xA0-AF + */ +#define PCIDRIVER_IOC_MAGIC 'p' +#define PCIDRIVER_IOC_BASE  0xA0 + +#define PCIDRIVER_IOC_MMAP_MODE  _IO(  PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 0 ) +#define PCIDRIVER_IOC_MMAP_AREA  _IO(  PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 1 ) +#define PCIDRIVER_IOC_KMEM_ALLOC _IOWR( PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 2, kmem_handle_t * ) +#define PCIDRIVER_IOC_KMEM_FREE  _IOW(  PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 3, kmem_handle_t * ) +#define PCIDRIVER_IOC_KMEM_SYNC  _IOW(  PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 4, kmem_sync_t * ) +#define PCIDRIVER_IOC_UMEM_SGMAP _IOWR( PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 5, umem_handle_t * ) +#define PCIDRIVER_IOC_UMEM_SGUNMAP _IOW(  PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 6, umem_handle_t * ) +#define PCIDRIVER_IOC_UMEM_SGGET _IOWR( PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 7, umem_sglist_t * ) +#define PCIDRIVER_IOC_UMEM_SYNC  _IOW(  PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 8, umem_handle_t * ) +#define PCIDRIVER_IOC_WAITI      _IO(   PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 9 ) + +/* And now, the methods to access the PCI configuration area */ +#define PCIDRIVER_IOC_PCI_CFG_RD  _IOWR(  PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 10, pci_cfg_cmd * ) +#define PCIDRIVER_IOC_PCI_CFG_WR  _IOWR(  PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 11, pci_cfg_cmd * ) +#define PCIDRIVER_IOC_PCI_INFO    _IOWR(  PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 12, pci_board_info * ) + +/* Clear interrupt queues */ +#define PCIDRIVER_IOC_CLEAR_IOQ   _IO(   PCIDRIVER_IOC_MAGIC, PCIDRIVER_IOC_BASE + 13 ) + +#endif diff --git a/driver/sysfs.c b/driver/sysfs.c new file mode 100644 index 0000000..b082d3c --- /dev/null +++ b/driver/sysfs.c @@ -0,0 +1,295 @@ +/** + * + * @file sysfs.c + * @brief This file contains the functions providing the SysFS-interface. + * @author Guillermo Marcus + * @date 2010-03-01 + * + */ +#include <linux/version.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/cdev.h> +#include <linux/wait.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/kernel.h> + +#include "compat.h" +#include "config.h" +#include "pciDriver.h" +#include "common.h" +#include "umem.h" +#include "kmem.h" +#include "sysfs.h" + +static SYSFS_GET_FUNCTION(pcidriver_show_kmem_entry); +static SYSFS_GET_FUNCTION(pcidriver_show_umem_entry); + +/** + * + * Initializes the sysfs attributes for an kmem/umem-entry + * + */ +static int _pcidriver_sysfs_initialize(pcidriver_privdata_t *privdata, +					int id, +					struct class_device_attribute *sysfs_attr, +					const char *fmtstring, +					SYSFS_GET_FUNCTION((*callback))) +{ +	/* sysfs attributes for kmem buffers don’t make sense before 2.6.13, as +	   we have no mmap support before */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) +	char namebuffer[16]; + +	/* allocate space for the name of the attribute */ +	snprintf(namebuffer, sizeof(namebuffer), fmtstring, id); + +	if ((sysfs_attr->attr.name = kstrdup(namebuffer, GFP_KERNEL)) == NULL) +		return -ENOMEM; + +	sysfs_attr->attr.mode = S_IRUGO; +	sysfs_attr->attr.owner = THIS_MODULE; +	sysfs_attr->show = callback; +	sysfs_attr->store = NULL; +			 +	/* name and add attribute */ +	if (class_device_create_file(privdata->class_dev, sysfs_attr) != 0) +		return -ENXIO; /* Device not configured. Not the really best choice, but hm. */ +#endif + +	return 0; +} + +int pcidriver_sysfs_initialize_kmem(pcidriver_privdata_t *privdata, int id, struct class_device_attribute *sysfs_attr) +{ +	return _pcidriver_sysfs_initialize(privdata, id, sysfs_attr, "kbuf%d", pcidriver_show_kmem_entry); +} + +int pcidriver_sysfs_initialize_umem(pcidriver_privdata_t *privdata, int id, struct class_device_attribute *sysfs_attr) +{ +	return _pcidriver_sysfs_initialize(privdata, id, sysfs_attr, "umem%d", pcidriver_show_umem_entry); +} + +/** + * + * Removes the file from sysfs and frees the allocated (kstrdup()) memory. + * + */ +void pcidriver_sysfs_remove(pcidriver_privdata_t *privdata, struct class_device_attribute *sysfs_attr) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) +	class_device_remove_file(privdata->class_dev, sysfs_attr); +	kfree(sysfs_attr->attr.name); +#endif +} + +static SYSFS_GET_FUNCTION(pcidriver_show_kmem_entry) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) +//	pcidriver_privdata_t *privdata = (pcidriver_privdata_t *)cls->class_data; + +        /* As we can be sure that attr.name contains a filename which we +         * created (see _pcidriver_sysfs_initialize), we do not need to have +         * sanity checks but can directly call simple_strtol() */ +        int id = simple_strtol(attr->attr.name + strlen("kbuf"), NULL, 10); + +	return snprintf(buf, PAGE_SIZE, "I am in the kmem_entry show function for buffer %d\n", id); +#else +	return 0; +#endif +} + +static SYSFS_GET_FUNCTION(pcidriver_show_umem_entry) +{ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) +#if 0 +	pcidriver_privdata_t *privdata = (pcidriver_privdata_t *)cls->class_data; + +	return snprintf(buf, PAGE_SIZE, "I am in the umem_entry show function, class_device_kobj_name: %s\n", cls->kobj.name); +#endif +	return 0; +#else +	return 0; +#endif +} + +#ifdef ENABLE_IRQ +SYSFS_GET_FUNCTION(pcidriver_show_irq_count) +{ +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; + +	return snprintf(buf, PAGE_SIZE, "%d\n", privdata->irq_count); +} + +SYSFS_GET_FUNCTION(pcidriver_show_irq_queues) +{ +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; +	int i, offset; + +	/* output will be truncated to PAGE_SIZE */ +	offset = snprintf(buf, PAGE_SIZE, "Queue\tOutstanding IRQs\n"); +	for (i = 0; i < PCIDRIVER_INT_MAXSOURCES; i++) +		offset += snprintf(buf+offset, PAGE_SIZE-offset, "%d\t%d\n", i, atomic_read(&(privdata->irq_outstanding[i])) ); + +	return (offset > PAGE_SIZE ? PAGE_SIZE : offset+1); +} +#endif + +SYSFS_GET_FUNCTION(pcidriver_show_mmap_mode) +{ +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; + +	return snprintf(buf, PAGE_SIZE, "%d\n", privdata->mmap_mode); +} + +SYSFS_SET_FUNCTION(pcidriver_store_mmap_mode) +{ +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; +	int mode = -1; + +	/* Set the mmap-mode if it is either PCIDRIVER_MMAP_PCI or PCIDRIVER_MMAP_KMEM */ +	if (sscanf(buf, "%d", &mode) == 1 && +	    (mode == PCIDRIVER_MMAP_PCI || mode == PCIDRIVER_MMAP_KMEM)) +		privdata->mmap_mode = mode; + +	return strlen(buf); +} + +SYSFS_GET_FUNCTION(pcidriver_show_mmap_area) +{ +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; + +	return snprintf(buf, PAGE_SIZE, "%d\n", privdata->mmap_area); +} + +SYSFS_SET_FUNCTION(pcidriver_store_mmap_area) +{ +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; +	int temp = -1; + +	sscanf(buf, "%d", &temp); + +	if ((temp >= PCIDRIVER_BAR0) && (temp <= PCIDRIVER_BAR5)) +		privdata->mmap_area = temp; + +	return strlen(buf); +} + +SYSFS_GET_FUNCTION(pcidriver_show_kmem_count) +{ +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; + +	return snprintf(buf, PAGE_SIZE, "%d\n", atomic_read(&(privdata->kmem_count))); +} + +SYSFS_SET_FUNCTION(pcidriver_store_kmem_alloc) +{ +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; +	kmem_handle_t kmem_handle; + +	/* FIXME: guillermo: is validation of parsing an unsigned int enough? */ +	if (sscanf(buf, "%lu", &kmem_handle.size) == 1) +		pcidriver_kmem_alloc(privdata, &kmem_handle); + +	return strlen(buf); +} + +SYSFS_SET_FUNCTION(pcidriver_store_kmem_free) +{ +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; +	unsigned int id; +	pcidriver_kmem_entry_t *kmem_entry; + +	/* Parse the ID of the kernel memory to be freed, check bounds */ +	if (sscanf(buf, "%u", &id) != 1 || +	    (id >= atomic_read(&(privdata->kmem_count)))) +		goto err; + +	if ((kmem_entry = pcidriver_kmem_find_entry_id(privdata,id)) == NULL) +		goto err; + +	pcidriver_kmem_free_entry(privdata, kmem_entry ); +err: +	return strlen(buf); +} + +SYSFS_GET_FUNCTION(pcidriver_show_kbuffers) +{ +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; +	int offset = 0; +	struct list_head *ptr; +	pcidriver_kmem_entry_t *entry; + +	/* print the header */ +	offset += snprintf(buf, PAGE_SIZE, "kbuf#\tcpu addr\tsize\n"); + +	spin_lock(&(privdata->kmemlist_lock)); +	list_for_each(ptr, &(privdata->kmem_list)) { +		entry = list_entry(ptr, pcidriver_kmem_entry_t, list); + +		/* print entry info */ +		if (offset > PAGE_SIZE) { +			spin_unlock( &(privdata->kmemlist_lock) ); +			return PAGE_SIZE; +		} + +		offset += snprintf(buf+offset, PAGE_SIZE-offset, "%3d\t%08lx\t%lu\n", entry->id, (unsigned long)(entry->dma_handle), entry->size ); +	} + +	spin_unlock(&(privdata->kmemlist_lock)); + +	/* output will be truncated to PAGE_SIZE */ +	return (offset > PAGE_SIZE ? PAGE_SIZE : offset+1); +} + +SYSFS_GET_FUNCTION(pcidriver_show_umappings) +{ +	int offset = 0; +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; +	struct list_head *ptr; +	pcidriver_umem_entry_t *entry; + +	/* print the header */ +	offset += snprintf(buf, PAGE_SIZE, "umap#\tn_pages\tsg_ents\n"); + +	spin_lock( &(privdata->umemlist_lock) ); +	list_for_each( ptr, &(privdata->umem_list) ) { +		entry = list_entry(ptr, pcidriver_umem_entry_t, list ); + +		/* print entry info */ +		if (offset > PAGE_SIZE) { +			spin_unlock( &(privdata->umemlist_lock) ); +			return PAGE_SIZE; +		} + +		offset += snprintf(buf+offset, PAGE_SIZE-offset, "%3d\t%lu\t%lu\n", entry->id, +				(unsigned long)(entry->nr_pages), (unsigned long)(entry->nents)); +	} + +	spin_unlock( &(privdata->umemlist_lock) ); + +	/* output will be truncated to PAGE_SIZE */ +	return (offset > PAGE_SIZE ? PAGE_SIZE : offset+1); +} + +SYSFS_SET_FUNCTION(pcidriver_store_umem_unmap) +{ +	pcidriver_privdata_t *privdata = SYSFS_GET_PRIVDATA; +	pcidriver_umem_entry_t *umem_entry; +	unsigned int id; + +	if (sscanf(buf, "%u", &id) != 1 || +	    (id >= atomic_read(&(privdata->umem_count)))) +		goto err; + +	if ((umem_entry = pcidriver_umem_find_entry_id(privdata, id)) == NULL) +		goto err; + +	pcidriver_umem_sgunmap(privdata, umem_entry); +err: +	return strlen(buf); +} diff --git a/driver/sysfs.h b/driver/sysfs.h new file mode 100644 index 0000000..4c413f0 --- /dev/null +++ b/driver/sysfs.h @@ -0,0 +1,23 @@ +#ifndef _PCIDRIVER_SYSFS_H +#define _PCIDRIVER_SYSFS_H +int pcidriver_sysfs_initialize_kmem(pcidriver_privdata_t *privdata, int id, struct class_device_attribute *sysfs_attr); +int pcidriver_sysfs_initialize_umem(pcidriver_privdata_t *privdata, int id, struct class_device_attribute *sysfs_attr); +void pcidriver_sysfs_remove(pcidriver_privdata_t *privdata, struct class_device_attribute *sysfs_attr); + +#ifdef ENABLE_IRQ +SYSFS_GET_FUNCTION(pcidriver_show_irq_count); +SYSFS_GET_FUNCTION(pcidriver_show_irq_queues); +#endif + +/* prototypes for sysfs operations */ +SYSFS_GET_FUNCTION(pcidriver_show_mmap_mode); +SYSFS_SET_FUNCTION(pcidriver_store_mmap_mode); +SYSFS_GET_FUNCTION(pcidriver_show_mmap_area); +SYSFS_SET_FUNCTION(pcidriver_store_mmap_area); +SYSFS_GET_FUNCTION(pcidriver_show_kmem_count); +SYSFS_GET_FUNCTION(pcidriver_show_kbuffers); +SYSFS_SET_FUNCTION(pcidriver_store_kmem_alloc); +SYSFS_SET_FUNCTION(pcidriver_store_kmem_free); +SYSFS_GET_FUNCTION(pcidriver_show_umappings); +SYSFS_SET_FUNCTION(pcidriver_store_umem_unmap); +#endif diff --git a/driver/umem.c b/driver/umem.c new file mode 100644 index 0000000..7a8dcc1 --- /dev/null +++ b/driver/umem.c @@ -0,0 +1,438 @@ +/** + * + * @file umem.c + * @brief This file contains the functions handling user space memory. + * @author Guillermo Marcus + * @date 2009-04-05 + * + */ +#include <linux/version.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/pci.h> +#include <linux/cdev.h> +#include <linux/wait.h> +#include <linux/mm.h> +#include <linux/pagemap.h> +#include <linux/sched.h> + +#include "config.h"			/* compile-time configuration */ +#include "compat.h"			/* compatibility definitions for older linux */ +#include "pciDriver.h"			/* external interface for the driver */ +#include "common.h"		/* internal definitions for all parts */ +#include "umem.h"		/* prototypes for kernel memory */ +#include "sysfs.h"		/* prototypes for sysfs */ + +/** + * + * Reserve a new scatter/gather list and map it from memory to PCI bus addresses. + * + */ +int pcidriver_umem_sgmap(pcidriver_privdata_t *privdata, umem_handle_t *umem_handle) +{ +	int i, res, nr_pages; +	struct page **pages; +	struct scatterlist *sg = NULL; +	pcidriver_umem_entry_t *umem_entry; +	unsigned int nents; +	unsigned long count,offset,length; + +	/* +	 * We do some checks first. Then, the following is necessary to create a +	 * Scatter/Gather list from a user memory area: +	 *  - Determine the number of pages +	 *  - Get the pages for the memory area +	 * 	- Lock them. +	 *  - Create a scatter/gather list of the pages +	 *  - Map the list from memory to PCI bus addresses +	 * +	 * Then, we: +	 *  - Create an entry on the umem list of the device, to cache the mapping. +	 *  - Create a sysfs attribute that gives easy access to the SG list +	 */ + +	/* zero-size?? */ +	if (umem_handle->size == 0) +		return -EINVAL; + +	/* Direction is better ignoring during mapping. */ +	/* We assume bidirectional buffers always, except when sync'ing */ + +	/* calculate the number of pages */ +	nr_pages = ((umem_handle->vma & ~PAGE_MASK) + umem_handle->size + ~PAGE_MASK) >> PAGE_SHIFT; + +	mod_info_dbg("nr_pages computed: %u\n", nr_pages); + +	/* Allocate space for the page information */ +	/* This can be very big, so we use vmalloc */ +	if ((pages = vmalloc(nr_pages * sizeof(*pages))) == NULL) +		return -ENOMEM; + +	mod_info_dbg("allocated space for the pages.\n"); + +	/* Allocate space for the scatterlist */ +	/* We do not know how many entries will be, but the maximum is nr_pages. */ +	/* This can be very big, so we use vmalloc */ +	if ((sg = vmalloc(nr_pages * sizeof(*sg))) == NULL) +		goto umem_sgmap_pages; + +	sg_init_table(sg, nr_pages); + +	mod_info_dbg("allocated space for the SG list.\n"); + +	/* Get the page information */ +	down_read(¤t->mm->mmap_sem); +	res = get_user_pages( +				current, +				current->mm, +				umem_handle->vma, +				nr_pages, +				1, +				0,  /* do not force, FIXME: shall I? */ +				pages, +				NULL ); +	up_read(¤t->mm->mmap_sem); + +	/* Error, not all pages mapped */ +	if (res < (int)nr_pages) { +		mod_info("Could not map all user pages (%d of %d)\n", res, nr_pages); +		/* If only some pages could be mapped, we release those. If a real +		 * error occured, we set nr_pages to 0 */ +		nr_pages = (res > 0 ? res : 0); +		goto umem_sgmap_unmap; +	} + +	mod_info_dbg("Got the pages (%d).\n", res); + +	/* Lock the pages, then populate the SG list with the pages */ +	/* page0 is different */ +	if ( !PageReserved(pages[0]) ) +		compat_lock_page(pages[0]); + +	offset = (umem_handle->vma & ~PAGE_MASK); +	length = (umem_handle->size > (PAGE_SIZE-offset) ? (PAGE_SIZE-offset) : umem_handle->size); + +	sg_set_page(&sg[0], pages[0], length, offset); + +	count = umem_handle->size - length; +	for(i=1;i<nr_pages;i++) { +		/* Lock page first */ +		if ( !PageReserved(pages[i]) ) +			compat_lock_page(pages[i]); + +		/* Populate the list */ +		sg_set_page(&sg[i], pages[i], ((count > PAGE_SIZE) ? PAGE_SIZE : count), 0); +		count -= sg[i].length; +	} + +	/* Use the page list to populate the SG list */ +	/* SG entries may be merged, res is the number of used entries */ +	/* We have originally nr_pages entries in the sg list */ +	if ((nents = pci_map_sg(privdata->pdev, sg, nr_pages, PCI_DMA_BIDIRECTIONAL)) == 0) +		goto umem_sgmap_unmap; + +	mod_info_dbg("Mapped SG list (%d entries).\n", nents); + +	/* Add an entry to the umem_list of the device, and update the handle with the id */ +	/* Allocate space for the new umem entry */ +	if ((umem_entry = kmalloc(sizeof(*umem_entry), GFP_KERNEL)) == NULL) +		goto umem_sgmap_entry; + +	/* Fill entry to be added to the umem list */ +	umem_entry->id = atomic_inc_return(&privdata->umem_count) - 1; +	umem_entry->nr_pages = nr_pages;	/* Will be needed when unmapping */ +	umem_entry->pages = pages; +	umem_entry->nents = nents; +	umem_entry->sg = sg; + +	if (pcidriver_sysfs_initialize_umem(privdata, umem_entry->id, &(umem_entry->sysfs_attr)) != 0) +		goto umem_sgmap_name_fail; + +	/* Add entry to the umem list */ +	spin_lock( &(privdata->umemlist_lock) ); +	list_add_tail( &(umem_entry->list), &(privdata->umem_list) ); +	spin_unlock( &(privdata->umemlist_lock) ); + +	/* Update the Handle with the Handle ID of the entry */ +	umem_handle->handle_id = umem_entry->id; + +	return 0; + +umem_sgmap_name_fail: +	kfree(umem_entry); +umem_sgmap_entry: +	pci_unmap_sg( privdata->pdev, sg, nr_pages, PCI_DMA_BIDIRECTIONAL ); +umem_sgmap_unmap: +	/* release pages */ +	if (nr_pages > 0) { +		for(i=0;i<nr_pages;i++) { +			if (PageLocked(pages[i])) +				compat_unlock_page(pages[i]); +			if (!PageReserved(pages[i])) +				set_page_dirty(pages[i]); +			page_cache_release(pages[i]); +		} +	} +	vfree(sg); +umem_sgmap_pages: +	vfree(pages); +	return -ENOMEM; + +} + +/** + * + * Unmap a scatter/gather list + * + */ +int pcidriver_umem_sgunmap(pcidriver_privdata_t *privdata, pcidriver_umem_entry_t *umem_entry) +{ +	int i; +	pcidriver_sysfs_remove(privdata, &(umem_entry->sysfs_attr)); + +	/* Unmap user memory */ +	pci_unmap_sg( privdata->pdev, umem_entry->sg, umem_entry->nr_pages, PCI_DMA_BIDIRECTIONAL ); + +	/* Release the pages */ +	if (umem_entry->nr_pages > 0) { +		for(i=0;i<(umem_entry->nr_pages);i++) { +			/* Mark pages as Dirty and unlock it */ +			if ( !PageReserved( umem_entry->pages[i] )) { +				SetPageDirty( umem_entry->pages[i] ); +				compat_unlock_page(umem_entry->pages[i]); +			} +			/* and release it from the cache */ +			page_cache_release( umem_entry->pages[i] ); +		} +	} + +	/* Remove the umem list entry */ +	spin_lock( &(privdata->umemlist_lock) ); +	list_del( &(umem_entry->list) ); +	spin_unlock( &(privdata->umemlist_lock) ); + +	/* Release SG list and page list memory */ +	/* These two are in the vm area of the kernel */ +	vfree(umem_entry->pages); +	vfree(umem_entry->sg); + +	/* Release umem_entry memory */ +	kfree(umem_entry); + +	return 0; +} + +/** + * + * Unmap all scatter/gather lists. + * + */ +int pcidriver_umem_sgunmap_all(pcidriver_privdata_t *privdata) +{ +	struct list_head *ptr, *next; +	pcidriver_umem_entry_t *umem_entry; + +	/* iterate safely over the entries and delete them */ +	list_for_each_safe( ptr, next, &(privdata->umem_list) ) { +		umem_entry = list_entry(ptr, pcidriver_umem_entry_t, list ); +		pcidriver_umem_sgunmap( privdata, umem_entry ); 		/* spin lock inside! */ +	} + +	return 0; +} + +/** + * + * Copies the scatter/gather list from kernelspace to userspace. + * + */ +int pcidriver_umem_sgget(pcidriver_privdata_t *privdata, umem_sglist_t *umem_sglist) +{ +	int i; +	pcidriver_umem_entry_t *umem_entry; +	struct scatterlist *sg; +	int idx = 0; +	dma_addr_t cur_addr; +	unsigned int cur_size; + +	/* Find the associated umem_entry for this buffer */ +	umem_entry = pcidriver_umem_find_entry_id( privdata, umem_sglist->handle_id ); +	if (umem_entry == NULL) +		return -EINVAL;					/* umem_handle is not valid */ + +	/* Check if passed SG list is enough */ +	if (umem_sglist->nents < umem_entry->nents) +		return -EINVAL;					/* sg has not enough entries */ + +	/* Copy the SG list to the user format */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24) +	if (umem_sglist->type == PCIDRIVER_SG_MERGED) { +		for_each_sg(umem_entry->sg, sg, umem_entry->nents, i ) { +			if (i==0) { +				umem_sglist->sg[0].addr = sg_dma_address( sg ); +				umem_sglist->sg[0].size = sg_dma_len( sg ); +				idx = 0; +			} +			else { +				cur_addr = sg_dma_address( sg ); +				cur_size = sg_dma_len( sg ); + +				/* Check if entry fits after current entry */ +				if (cur_addr == (umem_sglist->sg[idx].addr + umem_sglist->sg[idx].size)) { +					umem_sglist->sg[idx].size += cur_size; +					continue; +				} + +				/* Skip if the entry is zero-length (yes, it can happen.... at the end of the list) */ +				if (cur_size == 0) +					continue; + +				/* None of the above, add new entry */ +				idx++; +				umem_sglist->sg[idx].addr = cur_addr; +				umem_sglist->sg[idx].size = cur_size; +			} +		} +		/* Set the used size of the SG list */ +		umem_sglist->nents = idx+1; +	} else { +		for_each_sg(umem_entry->sg, sg, umem_entry->nents, i ) { +			mod_info("entry: %d\n",i); +			umem_sglist->sg[i].addr = sg_dma_address( sg ); +			umem_sglist->sg[i].size = sg_dma_len( sg ); +		} + +		/* Set the used size of the SG list */ +		/* Check if the last one is zero-length */ +		if ( umem_sglist->sg[ umem_entry->nents - 1].size == 0) +			umem_sglist->nents = umem_entry->nents -1; +		else +			umem_sglist->nents = umem_entry->nents; +	} +#else +	if (umem_sglist->type == PCIDRIVER_SG_MERGED) { +		/* Merge entries that are contiguous into a single entry */ +		/* Non-optimal but fast for most cases */ +		/* First one always true */ +		sg=umem_entry->sg; +		umem_sglist->sg[0].addr = sg_dma_address( sg ); +		umem_sglist->sg[0].size = sg_dma_len( sg ); +		sg++; +		idx = 0; + +		/* Iterate over the SG entries */ +		for(i=1; i< umem_entry->nents; i++, sg++ ) { +			cur_addr = sg_dma_address( sg ); +			cur_size = sg_dma_len( sg ); + +			/* Check if entry fits after current entry */ +			if (cur_addr == (umem_sglist->sg[idx].addr + umem_sglist->sg[idx].size)) { +				umem_sglist->sg[idx].size += cur_size; +				continue; +			} + +			/* Skip if the entry is zero-length (yes, it can happen.... at the end of the list) */ +			if (cur_size == 0) +				continue; + +			/* None of the above, add new entry */ +			idx++; +			umem_sglist->sg[idx].addr = cur_addr; +			umem_sglist->sg[idx].size = cur_size; +		} +		/* Set the used size of the SG list */ +		umem_sglist->nents = idx+1; +	} else { +		/* Assume pci_map_sg made a good job (ehem..) and just copy it. +		 * actually, now I assume it just gives them plainly to me. */ +		for(i=0, sg=umem_entry->sg ; i< umem_entry->nents; i++, sg++ ) { +			umem_sglist->sg[i].addr = sg_dma_address( sg ); +			umem_sglist->sg[i].size = sg_dma_len( sg ); +		} +		/* Set the used size of the SG list */ +		/* Check if the last one is zero-length */ +		if ( umem_sglist->sg[ umem_entry->nents - 1].size == 0) +			umem_sglist->nents = umem_entry->nents -1; +		else +			umem_sglist->nents = umem_entry->nents; +	} +#endif + +	return 0; +} + +/** + * + * Sync user space memory from/to device + * + */ +int pcidriver_umem_sync( pcidriver_privdata_t *privdata, umem_handle_t *umem_handle ) +{ +	pcidriver_umem_entry_t *umem_entry; + +	/* Find the associated umem_entry for this buffer */ +	umem_entry = pcidriver_umem_find_entry_id( privdata, umem_handle->handle_id ); +	if (umem_entry == NULL) +		return -EINVAL;					/* umem_handle is not valid */ + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,11) +	switch (umem_handle->dir) { +		case PCIDRIVER_DMA_TODEVICE: +			pci_dma_sync_sg_for_device( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_TODEVICE ); +			break; +		case PCIDRIVER_DMA_FROMDEVICE: +			pci_dma_sync_sg_for_cpu( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_FROMDEVICE ); +			break; +		case PCIDRIVER_DMA_BIDIRECTIONAL: +			pci_dma_sync_sg_for_device( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_BIDIRECTIONAL ); +			pci_dma_sync_sg_for_cpu( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_BIDIRECTIONAL ); +			break; +		default: +			return -EINVAL;				/* wrong direction parameter */ +	} +#else +	switch (umem_handle->dir) { +		case PCIDRIVER_DMA_TODEVICE: +			pci_dma_sync_sg( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_TODEVICE ); +			break; +		case PCIDRIVER_DMA_FROMDEVICE: +			pci_dma_sync_sg( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_FROMDEVICE ); +			break; +		case PCIDRIVER_DMA_BIDIRECTIONAL: +			pci_dma_sync_sg( privdata->pdev, umem_entry->sg, umem_entry->nents, PCI_DMA_BIDIRECTIONAL ); +			break; +		default: +			return -EINVAL;				/* wrong direction parameter */ +	} +#endif + +	return 0; +} + +/* + * + * Get the pcidriver_umem_entry_t structure for the given id. + * + * @param id ID of the umem entry to search for + * + */ +pcidriver_umem_entry_t *pcidriver_umem_find_entry_id(pcidriver_privdata_t *privdata, int id) +{ +	struct list_head *ptr; +	pcidriver_umem_entry_t *entry; + +	spin_lock(&(privdata->umemlist_lock)); +	list_for_each(ptr, &(privdata->umem_list)) { +		entry = list_entry(ptr, pcidriver_umem_entry_t, list ); + +		if (entry->id == id) { +			spin_unlock( &(privdata->umemlist_lock) ); +			return entry; +		} +	} + +	spin_unlock(&(privdata->umemlist_lock)); +	return NULL; +} diff --git a/driver/umem.h b/driver/umem.h new file mode 100644 index 0000000..d16c466 --- /dev/null +++ b/driver/umem.h @@ -0,0 +1,5 @@ +int pcidriver_umem_sgmap( pcidriver_privdata_t *privdata, umem_handle_t *umem_handle ); +int pcidriver_umem_sgunmap( pcidriver_privdata_t *privdata, pcidriver_umem_entry_t *umem_entry ); +int pcidriver_umem_sgget( pcidriver_privdata_t *privdata, umem_sglist_t *umem_sglist ); +int pcidriver_umem_sync( pcidriver_privdata_t *privdata, umem_handle_t *umem_handle ); +pcidriver_umem_entry_t *pcidriver_umem_find_entry_id( pcidriver_privdata_t *privdata, int id ); diff --git a/misc/50-pcidriver.rules b/misc/50-pcidriver.rules new file mode 100644 index 0000000..1283c3b --- /dev/null +++ b/misc/50-pcidriver.rules @@ -0,0 +1 @@ +KERNEL=="fpga*", NAME="%k", GROUP="users", MODE="0660" @@ -0,0 +1,569 @@ +/******************************************************************* + * This is a test program for the IOctl interface of the  + * pciDriver. + *  + * $Revision: 1.3 $ + * $Date: 2006-11-17 18:49:01 $ + *  + *******************************************************************/ + +/******************************************************************* + * Change History: + *  + * $Log: not supported by cvs2svn $ + * Revision 1.2  2006/10/16 16:56:09  marcus + * Added nice comment at the start. + * + *******************************************************************/ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdarg.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <errno.h> +#include <alloca.h> + +#include <getopt.h> + +#include "driver/pciDriver.h" + +#include "tools.h" + +/* defines */ +#define MAX_KBUF 14 +//#define BIGBUFSIZE (512*1024*1024) +#define BIGBUFSIZE (1024*1024) + + +#define DEFAULT_FPGA_DEVICE "/dev/fpga0" +#define MAX_BANKS 6 + +#define LINE_WIDTH 80 +#define SEPARATOR_WIDTH 2 +#define BLOCK_SEPARATOR_WIDTH 2 +#define BLOCK_SIZE 8 +#define BENCHMARK_ITERATIONS 128 + +//#define FILE_IO + +typedef enum { +    MODE_INVALID, +    MODE_INFO, +    MODE_LIST, +    MODE_BENCHMARK, +    MODE_READ, +    MODE_WRITE +} MODE; + +typedef enum { +    OPT_DEVICE = 'd', +    OPT_BAR = 'b', +    OPT_ACCESS = 'a', +    OPT_SIZE = 's', +    OPT_INFO = 'i', +    OPT_BENCHMARK = 'p', +    OPT_LIST = 'l', +    OPT_READ = 'r', +    OPT_WRITE = 'w', +    OPT_HELP = 'h', +} OPTIONS; + +static struct option long_options[] = { +    {"device",			required_argument, 0, OPT_DEVICE }, +    {"bar",			required_argument, 0, OPT_BAR }, +    {"access",			required_argument, 0, OPT_ACCESS }, +    {"size",			required_argument, 0, OPT_SIZE }, +    {"info",			no_argument, 0, OPT_INFO }, +    {"list",			no_argument, 0, OPT_LIST }, +    {"benchmark",		no_argument, 0, OPT_BENCHMARK }, +    {"read",			optional_argument, 0, OPT_READ }, +    {"write",			optional_argument, 0, OPT_WRITE }, +    {"help",			no_argument, 0, OPT_HELP }, +    { 0, 0, 0, 0 } +}; + + +void Usage(int argc, char *argv[], const char *format, ...) { +    if (format) { +	va_list ap; +     +	va_start(ap, format); +	printf("Error %i: ", errno); +	vprintf(format, ap); +	printf("\n"); +	va_end(ap); +     +        printf("\n"); +    } + + +    printf( +"Usage:\n" +" %s <mode> [options] [hex data]\n" +"  Modes:\n" +"	-i		- Device Info\n" +"	-l		- List Data Banks\n" +"	-p		- Performance Evaluation\n" +"	-r <addr>	- Read Data\n" +"	-w <addr>	- Write Data\n" +"	--help		- Help message\n" +"\n" +"  Addressing:\n" +"	-d <device>	- FPGA device (/dev/fpga0)\n" +"	-b <bank>	- Data bank (autodetected)\n" +"\n" +"  Options:\n" +"	-s <size>	- Number of words (default: 1)\n" +"	-a <bitness>	- Bits per word (default: 32)\n" +"\n\n", +argv[0]); + +    exit(0); +} + +void Error(const char *format, ...) { +    va_list ap; +     +    va_start(ap, format); +    printf("Error %i: ", errno); +    vprintf(format, ap); +    if (errno) printf("\n errno: %s", strerror(errno)); +    printf("\n\n"); +    va_end(ap); +     +    exit(-1); +} + +static pci_board_info board_info; +static page_mask = -1; + +void GetBoardInfo(int handle) { +    int ret; +     +    if (page_mask < 0) { +	ret = ioctl( handle, PCIDRIVER_IOC_PCI_INFO, &board_info ); +	if (ret) Error("PCIDRIVER_IOC_PCI_INFO ioctl have failed"); +	 +	page_mask = get_page_mask(); +    } +} + +void List(int handle) { +    int i; + +    GetBoardInfo(handle); + +    for (i = 0; i < MAX_BANKS; i++) { +	if (board_info.bar_length[i] > 0) { +	    printf(" BAR %d - Start: 0x%x, Length: 0x%x\n", i, board_info.bar_start[i], board_info.bar_length[i] ); +	} +    } +} + +void Info(int handle) { +    GetBoardInfo(handle); + +    printf("Vendor: %lx, Device: %lx, Interrupt Pin: %i, Interrupt Line: %i\n", board_info.vendor_id, board_info.device_id, board_info.interrupt_pin, board_info.interrupt_line); +    List(handle); +} + + +int DetectBar(int handle, unsigned long addr, int size) { +    int ret,i; +	 +    GetBoardInfo(handle); +		 +    for (i = 0; i < MAX_BANKS; i++) { +	if ((addr >= board_info.bar_start[i])&&((board_info.bar_start[i] + board_info.bar_length[i]) >= (addr + size))) return i; +    } +	 +    return -1; +} + +void *DetectAddress(int handle, int *bar, unsigned long *addr, int size) { +    if (*bar < 0) { +	*bar = DetectBar(handle, *addr, size); +	if (*bar < 0) Error("The requested data block at address 0x%x with size 0x%x does not belongs to any available memory bank", *addr, size); +    } else { +	GetBoardInfo(handle); +	 +	if ((*addr < board_info.bar_start[*bar])||((board_info.bar_start[*bar] + board_info.bar_length[*bar]) < (((uintptr_t)*addr) + size))) { +	    if ((board_info.bar_length[*bar]) >= (((uintptr_t)*addr) + size))  +		*addr += board_info.bar_start[*bar]; +	    else +		Error("The requested data block at address 0x%x with size 0x%x does not belong the specified memory bank (Bar %i: starting at 0x%x with size 0x%x)", *addr, size, *bar, board_info.bar_start[*bar], board_info.bar_length[*bar]); +	} +    } +     +    *addr -= board_info.bar_start[*bar]; +    *addr += board_info.bar_start[*bar] & page_mask; +} + + +#ifdef FILE_IO +int file_io_handle; +#endif /* FILE_IO */ + +void *MapBar(int handle, int bar) { +    void *res; +    int ret;  + +    GetBoardInfo(handle); +     +    ret = ioctl( handle, PCIDRIVER_IOC_MMAP_MODE, PCIDRIVER_MMAP_PCI ); +    if (ret) Error("PCIDRIVER_IOC_MMAP_MODE ioctl have failed", bar); + +    ret = ioctl( handle, PCIDRIVER_IOC_MMAP_AREA, PCIDRIVER_BAR0 + bar ); +    if (ret) Error("PCIDRIVER_IOC_MMAP_AREA ioctl have failed for bank %i", bar); + +#ifdef FILE_IO +    file_io_handle = open("/root/drivers/pciDriver/data", O_RDWR); +    res = mmap( 0, board_info.bar_length[bar], PROT_WRITE | PROT_READ, MAP_SHARED, file_io_handle, 0 ); +#else +    res = mmap( 0, board_info.bar_length[bar], PROT_WRITE | PROT_READ, MAP_SHARED, handle, 0 ); +#endif +    if ((!res)||(res == MAP_FAILED)) Error("Failed to mmap data bank %i", bar); + +     +    return res; +} + +void UnmapBar(int handle, int bar, void *data) { +    munmap(data, board_info.bar_length[bar]); +#ifdef FILE_IO +    close(file_io_handle); +#endif +} + + + +int Read(void *buf, int handle, int bar, unsigned long addr, int size) { +    int i; +    void *data; +    unsigned int offset; +    char local_buf[size]; +     + + +    DetectAddress(handle, &bar, &addr, size); +    data = MapBar(handle, bar); + +/* +    for (i = 0; i < size/4; i++)  { +	((uint32_t*)((char*)data+addr))[i] = 0x100 * i + 1; +    } +*/ + +    memcpy0(buf, data + addr, size); +     +    UnmapBar(handle, bar, data);     +} + +int Write(void *buf, int handle, int bar, unsigned long addr, int size) { +    int i; +    void *data; +    unsigned int offset; +    char local_buf[size]; +     + +    DetectAddress(handle, &bar, &addr, size); +    data = MapBar(handle, bar); + +    memcpy0(data + addr, buf, size); +     +    UnmapBar(handle, bar, data);     +} + + +int Benchmark(int handle, int bar) { +    int i; +    void *data, *buf, *check; +    struct timeval start, end; +    unsigned long time; +    unsigned int size, max_size; +     +    GetBoardInfo(handle); +		 +    if (bar < 0) { +	for (i = 0; i < MAX_BANKS; i++) { +	    if (board_info.bar_length[i] > 0) { +		bar = i; +		break; +	    } +	} +	 +	if (bar < 0) Error("Data banks are not available"); +    } +     + +    max_size = board_info.bar_length[bar]; +     +    posix_memalign( (void**)&buf, 256, max_size ); +    posix_memalign( (void**)&check, 256, max_size ); +    if ((!buf)||(!check)) Error("Allocation of %i bytes of memory have failed", max_size); + +    printf("Transfer time:\n");     +    data = MapBar(handle, bar); +     +    for (size = 4 ; size < max_size; size *= 8) { +	gettimeofday(&start,NULL); +	for (i = 0; i < BENCHMARK_ITERATIONS; i++) { +	    memcpy0(buf, data, size); +	} +	gettimeofday(&end,NULL); + +	time = (end.tv_sec - start.tv_sec)*1000000 + (end.tv_usec - start.tv_usec); +	printf("%8i bytes - read: %8.2lf MB/s", size, 1000000. * size * BENCHMARK_ITERATIONS / (time * 1024 * 1024)); +	 +	fflush(0); + +	gettimeofday(&start,NULL); +	for (i = 0; i < BENCHMARK_ITERATIONS; i++) { +	    memcpy0(data, buf, size); +	} +	gettimeofday(&end,NULL); + +	time = (end.tv_sec - start.tv_sec)*1000000 + (end.tv_usec - start.tv_usec); +	printf(", write: %8.2lf MB/s\n", 1000000. * size * BENCHMARK_ITERATIONS / (time * 1024 * 1024)); +    } +     +    UnmapBar(handle, bar, data); + +    printf("\n\nOpen-Transfer-Close time: \n"); +     +    for (size = 4 ; size < max_size; size *= 8) { +	gettimeofday(&start,NULL); +	for (i = 0; i < BENCHMARK_ITERATIONS; i++) { +	    Read(buf, handle, bar, 0, size); +	} +	gettimeofday(&end,NULL); + +	time = (end.tv_sec - start.tv_sec)*1000000 + (end.tv_usec - start.tv_usec); +	printf("%8i bytes - read: %8.2lf MB/s", size, 1000000. * size * BENCHMARK_ITERATIONS / (time * 1024 * 1024)); +	 +	fflush(0); + +	gettimeofday(&start,NULL); +	for (i = 0; i < BENCHMARK_ITERATIONS; i++) { +	    Write(buf, handle, bar, 0, size); +	} +	gettimeofday(&end,NULL); + +	time = (end.tv_sec - start.tv_sec)*1000000 + (end.tv_usec - start.tv_usec); +	printf(", write: %8.2lf MB/s", 1000000. * size * BENCHMARK_ITERATIONS / (time * 1024 * 1024)); + +	gettimeofday(&start,NULL); +	for (i = 0; i < BENCHMARK_ITERATIONS; i++) { +	    Write(buf, handle, bar, 0, size); +	    Read(check, handle, bar, 0, size); +	    memcmp(buf, check, size); +	} +	gettimeofday(&end,NULL); + +	time = (end.tv_sec - start.tv_sec)*1000000 + (end.tv_usec - start.tv_usec); +	printf(", write-verify: %8.2lf MB/s\n", 1000000. * size * BENCHMARK_ITERATIONS / (time * 1024 * 1024)); +    } +     +    printf("\n\n"); + +    free(check); +    free(buf); +} + +int ReadData(int handle, int bar, unsigned long addr, int n, int access) { +    void *buf; +    int i; +    int size = n * abs(access); +    int block_width, blocks_per_line; +    int numbers_per_block, numbers_per_line;  +     +    numbers_per_block = BLOCK_SIZE / access; + +    block_width = numbers_per_block * ((access * 2) +  SEPARATOR_WIDTH); +    blocks_per_line = (LINE_WIDTH - 10) / (block_width +  BLOCK_SEPARATOR_WIDTH); +    if ((blocks_per_line > 1)&&(blocks_per_line % 2)) --blocks_per_line; +    numbers_per_line = blocks_per_line * numbers_per_block; + +//    buf = alloca(size); +    posix_memalign( (void**)&buf, 256, size ); + +    if (!buf) Error("Allocation of %i bytes of memory have failed", size); +     +    Read(buf, handle, bar, addr, size); + +    for (i = 0; i < n; i++) { +	if ((i)&&(i%numbers_per_line == 0)) printf("\n"); +	else if ((i)&&(i%numbers_per_block == 0)) printf("%*s", BLOCK_SEPARATOR_WIDTH, ""); + +	if (i%numbers_per_line == 0) printf("%8lx: ", addr + i * abs(access)); + +	switch (access) { +	    case 1: printf("% *hhx", access * 2 +  SEPARATOR_WIDTH, ((uint8_t*)buf)[i]); break; +	    case 2: printf("% *hx", access * 2 +  SEPARATOR_WIDTH, ((uint16_t*)buf)[i]); break; +	    case 4: printf("% *x", access * 2 +  SEPARATOR_WIDTH, ((uint32_t*)buf)[i]); break; +	    case 8: printf("% *lx", access * 2 +  SEPARATOR_WIDTH, ((uint64_t*)buf)[i]); break; +	} +    } +    printf("\n\n"); + +     +    free(buf); +} + +int WriteData(int handle, int bar, unsigned long addr, int n, int access, char ** data) { +    void *buf, *check; +    int res, i; +    int size = n * abs(access); +     +    posix_memalign( (void**)&buf, 256, size ); +    posix_memalign( (void**)&check, 256, size ); +    if ((!buf)||(!check)) Error("Allocation of %i bytes of memory have failed", size); + +    for (i = 0; i < n; i++) { +	switch (access) { +	    case 1: res = sscanf(data[i], "%hhx", ((uint8_t*)buf)+i); break; +	    case 2: res = sscanf(data[i], "%hx", ((uint16_t*)buf)+i); break; +	    case 4: res = sscanf(data[i], "%x", ((uint32_t*)buf)+i); break; +	    case 8: res = sscanf(data[i], "%lx", ((uint64_t*)buf)+i); break; +	} +	 +	if (res != 1) Error("Can't parse data value at poition %i, (%s) is not valid hex number", i, data[i]); +    } + +    Write(buf, handle, bar, addr, size); +//    ReadData(handle, bar, addr, n, access); +    Read(check, handle, bar, addr, size); +     +    if (memcmp(buf, check, size)) Error("Write failed, the data written and read differ"); + +    free(check); +    free(buf); +} + + + +int main(int argc, char **argv) { +    unsigned char c; + +    MODE mode = MODE_INVALID; +    const char *fpga_device = DEFAULT_FPGA_DEVICE; +    int bar = -1; +    const char *addr = NULL; +    unsigned long start = -1; +    int size = 1; +    int access = 4; +    int skip = 0; + +    int handle; +     +    while ((c = getopt_long(argc, argv, "hilpr::w::d:b:a:s:", long_options, NULL)) != (unsigned char)-1) { +	extern int optind; +	switch (c) { +	    case OPT_HELP: +		Usage(argc, argv, NULL); +	    break; +	    case OPT_INFO: +		if (mode != MODE_INVALID) Usage(argc, argv, "Multiple operations are not supported"); + +		mode = MODE_INFO; +	    break; +	    case OPT_LIST: +		if (mode != MODE_INVALID) Usage(argc, argv, "Multiple operations are not supported"); + +		mode = MODE_LIST; +	    break; +	    case OPT_BENCHMARK: +		if (mode != MODE_INVALID) Usage(argc, argv, "Multiple operations are not supported"); + +		mode = MODE_BENCHMARK; +	    break; +	    case OPT_READ: +		if (mode != MODE_INVALID) Usage(argc, argv, "Multiple operations are not supported"); +		 +		mode = MODE_READ; +		if (optarg) addr = optarg; +		else if ((optind < argc)&&(argv[optind][0] != '-')) addr = argv[optind++]; +	    break; +	    case OPT_WRITE: +		if (mode != MODE_INVALID) Usage(argc, argv, "Multiple operations are not supported"); + +		mode = MODE_WRITE; +		if (optarg) addr = optarg; +		else if ((optind < argc)&&(argv[optind][0] != '-')) addr = argv[optind++]; +	    break; +	    case OPT_DEVICE: +		fpga_device = optarg; +	    break; +	    case OPT_BAR: +		if ((sscanf(optarg,"%u", &bar) != 1)||(bar < 0)||(bar >= MAX_BANKS)) Usage(argc, argv, "Invalid data bank (%s) is specified", optarg); +	    break; +	    case OPT_ACCESS: +		if (sscanf(optarg, "%i", &access) != 1) access = 0; +		switch (access) { +		    case 8: access = 1; break; +		    case 16: access = 2; break; +		    case 32: access = 4; break; +		    case 64: access = 8; break; +		    default: Usage(argc, argv, "Invalid data width (%s) is specified", optarg); +		}	 +	    break; +	    case OPT_SIZE: +		if (sscanf(optarg, "%u", &size) != 1) +		    Usage(argc, argv, "Invalid size is specified (%s)", optarg); +	    break; +	    default: +		Usage(argc, argv, "Unknown option (%s)", argv[optind]); +	} +    } + +    if (mode == MODE_INVALID) { +	if (argc > 1) Usage(argc, argv, "Operation is not specified"); +	else Usage(argc, argv, NULL); +    } + +    if (addr) { +	if (sscanf(addr, "%lx", &start) != 1) Usage(argc, argv, "Invalid address (%s) is specified", addr); +    } +     +    switch (mode) { +     case MODE_WRITE: +        if ((argc - optind) != size) Usage(argc, argv, "The %i data values is specified, but %i required", argc - optind, size); +     case MODE_READ: +        if (!addr) Usage(argc, argv, "The address is not specified"); +     break; +     default: +        if (argc > optind) Usage(argc, argv, "Invalid non-option parameters are supplied"); +    } + +    handle = open(fpga_device, O_RDWR); +    if (handle < 0) Error("Failed to open FPGA device: %s", fpga_device); +     +    switch (mode) { +     case MODE_INFO: +        Info(handle); +     break; +     case MODE_LIST: +        List(handle); +     break; +     case MODE_BENCHMARK: +        Benchmark(handle, bar); +     break; +     case MODE_READ: +        if (addr) { +	    ReadData(handle, bar, start, size, access); +	} else { +	    Error("Address to read is not specified"); +	} +     break; +     case MODE_WRITE: +	WriteData(handle, bar, start, size, access, argv + optind); +     break; +    } + +    close(handle); +} @@ -0,0 +1,43 @@ +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <stdint.h> + +void *memcpy8(void * dst, void const * src, size_t len) { +    int i; +    for (i = 0; i < len; i++) ((char*)dst)[i] = ((char*)src)[i]; +    return dst; +} + + +void *memcpy32(void * dst, void const * src, size_t len) { +    uint32_t * plDst = (uint32_t *) dst; +    uint32_t const * plSrc = (uint32_t const *) src; + +    while (len >= 4) { +        *plDst++ = *plSrc++; +        len -= 4; +    } + +    char * pcDst = (char *) plDst; +    char const * pcSrc = (char const *) plSrc; + +    while (len--) { +        *pcDst++ = *pcSrc++; +    } + +    return (dst); +}  + + +int get_page_mask() { +    int pagesize,pagemask,temp; + +    pagesize = getpagesize(); + +    for( pagemask=0, temp = pagesize; temp != 1; ) { +	temp = (temp >> 1); +	pagemask = (pagemask << 1)+1; +    } +    return pagemask; +} @@ -0,0 +1,5 @@ +#define memcpy0 memcpy32 + +void * memcpy8(void * dst, void const * src, size_t len); +void * memcpy32(void * dst, void const * src, size_t len); +int get_page_mask();  | 
