Browse Source

Initial import

Suren A. Chilingaryan 13 years ago
commit
b72e856772
24 changed files with 3850 additions and 0 deletions
  1. 9 0
      .bzrignore
  2. 26 0
      Makefile
  3. 55 0
      common.mk
  4. 27 0
      driver/Makefile
  5. 675 0
      driver/base.c
  6. 89 0
      driver/base.h
  7. 114 0
      driver/common.h
  8. 192 0
      driver/compat.h
  9. 23 0
      driver/config.h
  10. 297 0
      driver/int.c
  11. 9 0
      driver/int.h
  12. 446 0
      driver/ioctl.c
  13. 1 0
      driver/ioctl.h
  14. 321 0
      driver/kmem.c
  15. 7 0
      driver/kmem.h
  16. 180 0
      driver/pciDriver.h
  17. 295 0
      driver/sysfs.c
  18. 23 0
      driver/sysfs.h
  19. 438 0
      driver/umem.c
  20. 5 0
      driver/umem.h
  21. 1 0
      misc/50-pcidriver.rules
  22. 569 0
      pci.c
  23. 43 0
      tools.c
  24. 5 0
      tools.h

+ 9 - 0
.bzrignore

@@ -0,0 +1,9 @@
+*.cmd
+pciDriver.ko
+pciDriver.mod.c
+pci.d
+tools.d
+modules.order
+Module.symvers
+./pci
+.tmp_versions

+ 26 - 0
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)

+ 55 - 0
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 $@ $<

+ 27 - 0
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

+ 675 - 0
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 */
+}

+ 89 - 0
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

+ 114 - 0
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

+ 192 - 0
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

+ 23 - 0
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

+ 297 - 0
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;
+}

+ 9 - 0
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

+ 446 - 0
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;
+	}
+}

+ 1 - 0
driver/ioctl.h

@@ -0,0 +1 @@
+int pcidriver_ioctl(struct inode  *inode, struct file *filp, unsigned int cmd, unsigned long arg);

+ 321 - 0
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;
+}

+ 7 - 0
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 );

+ 180 - 0
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

+ 295 - 0
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);
+}

+ 23 - 0
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

+ 438 - 0
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(&current->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(&current->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;
+}

+ 5 - 0
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 );

+ 1 - 0
misc/50-pcidriver.rules

@@ -0,0 +1 @@
+KERNEL=="fpga*", NAME="%k", GROUP="users", MODE="0660"

+ 569 - 0
pci.c

@@ -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);
+}

+ 43 - 0
tools.c

@@ -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;
+}

+ 5 - 0
tools.h

@@ -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();