Re: [PATCH 1/1] Add mkopci driver

From: Sergej Bauer
Date: Sat Mar 21 2015 - 10:41:37 EST


Richard, thanks for your review.

But I still have several notes about driver:

> - You add new proc files, which is not really welcomed. Please consider sysfs.
That will break a bunch of userspace applications, which use proc-files for several years (as long as from 2006
year)

> - In the debug prints you compare against current->comm, also not nice.
> - You use printk() in the interrupt handler.
That printks was only for driver tests, and does not need anymore, I think.
And you're absolutely right :)

> - Why is this a misc driver at all? Does it really not fit into any
> other device class?
I've don't think I can find an appropriate class. On the one hand, this is industrial bus but it has nothing in common with drivers/iio, or with drives/bus

> - I'm sure running checkpatch.pl against the patch would also not hurt. :)
Yes, but it only hurts with "line over 80 characters" warnings and hurts much :)
And a couple of such warnings still remain...

> BTW: Forgot to mention that this sounds like a job for UIO or VFIO.
And again, you are right. But, again, there a number of applications wich use /proc/mkopci/core

But, of course, there may be decided that the kernel main line - this is not the place for such a driver. :)
If the driver is suitable anyway, patch is at the end of this message

And thanks to Paul Bolle for noticing about license mismatch.

On Friday 20 March 2015 15:43:50 you wrote:
> Sergej,
>
> On Fri, Mar 20, 2015 at 1:10 PM, <sergej.bauer@xxxxxxxxx> wrote:
> > mkopci (MB11.xx) device (RC Module project) provides data transference through a serial bus bar according to MIL-STD-1553.
> > the driver used for operating devices, reads PCI configuration space and pass interrupts to user-space applications.
> >
> > Please consider adding this patch to the linux-next queue.
>
> From a quick look I'd suggest to get the driver first in shape.
>
> - You add new proc files, which is not really welcomed. Please consider sysfs.
> - In the debug prints you compare against current->comm, also not nice.
> - You use printk() in the interrupt handler.
> - Why is this a misc driver at all? Does it really not fit into any
> other device class?
> - I'm sure running checkpatch.pl against the patch would also not hurt. :)
>
>

diff --git a/Documentation/misc-devices/mkopci.txt b/Documentation/misc-devices/mkopci.txt
new file mode 100644
index 0000000..499bc29
--- /dev/null
+++ b/Documentation/misc-devices/mkopci.txt
@@ -0,0 +1,44 @@
+ PCI-based MKO bus driver.
+
+
+For dealing with driver without using of root's account it will be helpful
+to add group `mkopci' with appropriate users and put file, say 60-mkopci.rules to
+/etc/udev/rules.d in your system.
+--- cut 60-mkopci.rules ---
+# MKO devices
+KERNEL=="mkopci*", SUBSYSTEM=="mkopci", ACTION=="add", DRIVERS=="?*", ATTRS{idVendor}=="0x6403"
+GROUP="mkopci"
+---
+
+Kernel module parameters
+
+Kernel module can take parameters 'v' and 'omited'
+- 'v' is 0 to 3, and affects the amount of information
+output to the system log.
+0 - (default) comletely silent
+1 - prints only detected BARs, reports loading / unloading
+2 - + prints IRQ and memory mapping events
+3 - + prints ioctl events
+
+- 'plx9050bug_quirk' controls workaround controller PLX9050. Can
+the following values:
+0 - workaround is disabled (default)
+1 - workaround is enabled
+2 - forced bug
+
+- 'omited' tells the driver which device should not be initialized
+at load time.
+The value of this parameter to the kernel module 2.4 is a physical address
+device on the bus, such as "0x10800" without the quotes.
+In kernel versions 2.6+ can exclude multiple devices, transferring them
+values separated by commas, but not more than 4.
+
+The parameter values can be specified as follows:
+$ insmod/modprobe mkopci.[Ko/o] parameter1_name=value [parameter2_name=value]
+
+Record the physical address of the device in the file /proc/mkopci/core
+also controls "visibility" for user programs. Missed return device
+You can command 'echo ADDR > /proc/mkopci/core'. ADDR can be either a simple
+number of device, but always in hexadecimal, or (for Linux-2.6+)
+the number of devices in a standard format Linux kind NM:XY.z.
+
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 006242c..c571451 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -124,6 +124,18 @@ config PHANTOM
If you choose to build module, its name will be phantom. If unsure,
say N here.

+config MKOPCI
+ tristate "Module PCI bus driver"
+ depends on PCI && PROC_FS
+ help
+ MKOPCI (MB11.xx) device (by RC Module project) provides data transference
+ through a serial bus bar according to MIL-STD-1553.
+
+ Say Y here if you want to build a driver for Module(RC) MKOPCI devices.
+
+ If you choose to build module, its name will be mkopci. If unsure,
+ say N here.
+
config INTEL_MID_PTI
tristate "Parallel Trace Interface for MIPI P1149.7 cJTAG standard"
depends on PCI && TTY && (X86_INTEL_MID || COMPILE_TEST)
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 7d5c4cd..afb92b4 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -56,3 +56,4 @@ obj-$(CONFIG_GENWQE) += genwqe/
obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
+obj-$(CONFIG_MKOPCI) += mkopci.o
diff --git a/drivers/misc/mkopci.c b/drivers/misc/mkopci.c
new file mode 100644
index 0000000..1e2b77a
--- /dev/null
+++ b/drivers/misc/mkopci.c
@@ -0,0 +1,1272 @@
+/*
+ * MKOPCI driver
+ *
+ * Copyright (C) 2007-2015 Sergej Bauer <sergej.bauer@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, as published by
+ * the Free Software Foundation, version 2.
+*/
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/interrupt.h>
+#include <linux/cdev.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#include "misc/mkopci.h"
+
+#define MKO_VENDOR 0x6403
+#define MKO_DEVICE1 0x0430
+#define MKO_DEVICE2 0x0431
+#define MKO_DEVICE3 0x0434
+
+#define PLX9050BUG_BAR0 0x1
+#define PLX9050BUG_BAR1 0x2
+#define PLX9050BUG_INJECT 0x4
+
+#if !defined(__user)
+#define __user
+#endif
+
+static struct pci_device_id ids[] = {
+ {MKO_VENDOR, MKO_DEVICE1, PCI_ANY_ID, PCI_ANY_ID,},
+ {MKO_VENDOR, MKO_DEVICE2, PCI_ANY_ID, PCI_ANY_ID,},
+ {MKO_VENDOR, MKO_DEVICE3, PCI_ANY_ID, PCI_ANY_ID,},
+ {0, 0,}
+};
+
+MODULE_DEVICE_TABLE(pci, ids);
+
+#ifdef __LP64__
+#define PFMT "llx"
+#else
+#define PFMT "x"
+#endif
+
+unsigned char drv_version = 0x11;
+#define mko_pci_addr(bus, device, func, regoffs) (\
+ ((bus & 0xFF) << 16) | ((device & 0x1F) << 11) | \
+ ((func & 0x7) << 8) | (regoffs & 0xFC))
+
+static struct kmem_cache *mkopci_device_cache;
+static dev_t devp;
+static struct class *mkopci_class;
+static struct rw_semaphore devices_sem;
+static LIST_HEAD(devices);
+static atomic_t devices_nr = ATOMIC_INIT(0);
+
+/*** module parameters ***/
+static int plx9050bug_quirk = 1;
+/* verbosity level */
+static int v;
+module_param(plx9050bug_quirk, int, S_IRUGO);
+module_param(v, int, S_IRUGO);
+MODULE_PARM_DESC(plx9050bug_quirk, "PLX9050 bug quirk");
+MODULE_PARM_DESC(v, "verbosity level");
+
+/* only MAX_DEVICES_NR devices at once can be omited */
+static int omited[MAX_DEVICES_NR];
+static int omited_nr;
+module_param_array(omited, int, &omited_nr, S_IRUGO);
+MODULE_PARM_DESC(omited, "device(s) to omit");
+#ifndef VM_RESERVED
+#define VM_RESERVED (VM_DONTEXPAND | VM_DONTDUMP)
+#endif
+#define __unused (__attribute__ ((unused)))
+
+/***************************** Interrupt handling *****************************/
+static int mkopci_wait_irq(struct mkopci_device *dev)
+{
+ volatile unsigned long *LPCI_4C =
+ (unsigned long *)(dev->core.lin_base[0] + 0x4C);
+
+ *LPCI_4C |= 0x49;
+
+ if (wait_event_interruptible(dev->wq, *LPCI_4C & 0x24))
+ return -ERESTARTSYS;
+ if (v > 1)
+ pr_info("mkopci%d: irq received\n", dev->core.n_dev);
+ *LPCI_4C &= ~0x40;
+
+ return 0;
+}
+
+static irqreturn_t mkopci_int_handler(int __unused irq, void *dev)
+{
+ struct mkopci_device *mko_dev = (struct mkopci_device *)dev;
+ volatile unsigned long *LPCI_4C =
+ (unsigned long *)((mko_dev->core.lin_base[0] + 0x4C));
+
+ if (*LPCI_4C & 0x24) {
+ *LPCI_4C &= ~0x40;
+ wake_up_interruptible(&mko_dev->wq);
+ } else
+ return IRQ_NONE;
+
+ return IRQ_HANDLED;
+}
+
+static int mkopci_request_irq(struct mkopci_device *dev)
+{
+ int ret = 0;
+
+ if (dev->core.irq_requested) {
+ pr_err("mkopci%d: irq already requested\n", dev->core.n_dev);
+ return -EBUSY;
+ }
+ ret = request_irq(dev->core.irq, &mkopci_int_handler, IRQF_SHARED,
+ dev->core.name, dev);
+ if (ret) {
+ pr_err("mkopci%d: failed to request irq %d\n", dev->core.n_dev,
+ dev->core.irq);
+ return ret;
+ }
+
+ dev->core.irq_requested++;
+
+ pr_info("mkopci%d: irq %d requested (%s)\n", dev->core.n_dev,
+ dev->core.irq, current->comm);
+
+ return ret;
+}
+
+static void mkopci_free_irq(struct mkopci_device *dev)
+{
+ if (dev->core.irq_requested) {
+ synchronize_irq(dev->core.irq);
+ free_irq(dev->core.irq, dev);
+ dev->core.irq_requested--;
+
+ pr_info("mkopci%d: irq %d released\n", dev->core.n_dev,
+ dev->core.irq);
+ }
+}
+/************************* End of Interrupt handling **************************/
+
+/**************************** File operations *********************************/
+static int mkopci_open(struct inode *inode, struct file *filp)
+{
+ int ret = 0;
+ struct mkopci_device *dev =
+ container_of(inode->i_cdev, struct mkopci_device, cdev);
+
+ if (!try_module_get(THIS_MODULE))
+ return -EINVAL;
+
+ if (dev == NULL) {
+ module_put(THIS_MODULE);
+ return -ENODEV;
+ }
+
+ down_write(&dev->rwsem);
+ if (dev->omited) {
+ ret = -ENODEV;
+ goto err;
+ }
+ if (!(filp->f_flags & O_NONBLOCK)) {
+ if (dev->core.process) {
+ ret = -EBUSY;
+ goto err;
+ }
+ dev->backdoor = 0;
+ dev->core.process = current->pid;
+ } else {
+ dev->backdoor = current->pid;
+ dev->core.process = 0;
+ }
+
+ filp->private_data = dev;
+
+ pr_info("mkopci%d: device opened in %s mode (%s)\n",
+ dev->core.n_dev, dev->backdoor ? "backdoor" : "regular",
+ current->comm);
+ goto out;
+
+err:
+ module_put(THIS_MODULE);
+out:
+ up_write(&dev->rwsem);
+
+ return ret;
+}
+
+static int mkopci_release(struct inode __unused * inode, struct file *filp)
+{
+ struct mkopci_device *dev;
+
+ if (filp->private_data == NULL)
+ return 0;
+
+ dev = (struct mkopci_device *)filp->private_data;
+ down_write(&dev->rwsem);
+ dev->core.process = 0;
+ dev->backdoor = 0;
+ dev->core.c_bar = -1;
+ filp->private_data = NULL;
+ mkopci_free_irq(dev);
+
+ pr_info("mkopci%d: device released\n", dev->core.n_dev);
+ up_write(&dev->rwsem);
+ module_put(THIS_MODULE);
+
+ return 0;
+}
+
+static long mkopci_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ int ret = 0, n = 0;
+ struct mkopci_device *dev =
+ (struct mkopci_device *)filp->private_data, *d;
+
+ if (_IOC_TYPE(cmd) != MKO_IOC_MAGIC) {
+ pr_err("mkopci%d: _IOC_TYPE(cmd) != MKO_IOC_MAGIC\n",
+ dev->core.n_dev);
+ return -ENOTTY;
+ }
+
+ if (_IOC_NR(cmd) > MKO_IOC_MAXNR) {
+ pr_err("mkopci%d: _IOC_NR(cmd) > MKO_IOC_MAXNR\n",
+ dev->core.n_dev);
+ return -ENOTTY;
+ }
+
+ if (_IOC_DIR(cmd) & _IOC_READ) {
+ ret =
+ !access_ok(VERIFY_WRITE, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ } else if (_IOC_DIR(cmd) & _IOC_WRITE) {
+ ret =
+ !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
+ }
+
+ if (ret) {
+ pr_err("mkopci%d: mkopci_ioctl: access_ok failed\n",
+ dev->core.n_dev);
+ return -EIO;
+ }
+
+ switch (cmd) {
+ case MKOPCI_IOCTL_CWPID:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_CWPID ioctl\n",
+ dev->core.n_dev);
+ down_read(&dev->rwsem);
+ ret = dev->core.process;
+ up_read(&dev->rwsem);
+ break;
+ case MKOPCI_IOCTL_GET_VERSION:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_GET_VERSION ioctl\n",
+ dev->core.n_dev);
+ ret = put_user(drv_version, (int __user *)arg);
+ break;
+ case MKOPCI_IOCTL_GET_BOARDS_COUNT:
+ if (v > 2)
+ pr_info
+ ("mkopci%d: MKOPCI_IOCTL_GET_BOARDS_COUNT ioctl\n",
+ dev->core.n_dev);
+ down_read(&dev->rwsem);
+ ret =
+ put_user((unsigned short)atomic_read(&devices_nr),
+ (int __user *)arg);
+ up_read(&dev->rwsem);
+ break;
+ case MKOPCI_IOCTL_GET_DEVICE_TABLE:
+ if (v > 2)
+ pr_info
+ ("mkopci%d: MKOPCI_IOCTL_GET_DEVICE_TABLE ioctl\n",
+ dev->core.n_dev);
+ down_read(&devices_sem);
+ if (put_user
+ ((unsigned short)atomic_read(&devices_nr),
+ (int __user *)arg)) {
+ up_read(&devices_sem);
+ ret = -ERESTARTSYS;
+ break;
+ }
+ list_for_each_entry(d, &devices, list) {
+ down_read(&d->rwsem);
+ if (d->omited) {
+ up_read(&d->rwsem);
+ continue;
+ }
+ if (copy_to_user
+ ((void __user *)arg + sizeof(unsigned short) +
+ n * sizeof(struct mkopci_core), &d->core,
+ sizeof(struct mkopci_core))) {
+ up_read(&d->rwsem);
+ ret = -ERESTARTSYS;
+ break;
+ }
+ up_read(&d->rwsem);
+ n++;
+ }
+ up_read(&devices_sem);
+ break;
+ case MKOPCI_IOCTL_ATTACH_IRQ:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_ATTACH_IRQ ioctl\n",
+ dev->core.n_dev);
+ down_write(&dev->rwsem);
+ ret = mkopci_request_irq(dev);
+ up_write(&dev->rwsem);
+ break;
+ case MKOPCI_IOCTL_DETACH_IRQ:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_DETACH_IRQ ioctl\n",
+ dev->core.n_dev);
+ down_write(&dev->rwsem);
+ mkopci_free_irq(dev);
+ up_write(&dev->rwsem);
+ break;
+ case MKOPCI_IOCTL_WAIT_IRQ:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_WAIT_IRQ ioctl\n",
+ dev->core.n_dev);
+ ret = mkopci_wait_irq(dev);
+ break;
+ case MKOPCI_IOCTL_REQUEST_BAR:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_REQUEST_BAR ioctl\n",
+ dev->core.n_dev);
+ down_write(&dev->rwsem);
+ ret = get_user(dev->core.c_bar, (int __user *)arg);
+ up_write(&dev->rwsem);
+ break;
+ default:
+ pr_err("mkopci%d: invalid ioctl %d\n", dev->core.n_dev,
+ _IOC_NR(cmd));
+ ret = -EINVAL;
+ break;
+ };
+
+ return ret;
+}
+
+#ifndef pgprot_noncached
+static inline pgprot_t pgprot_noncached(pgprot_t _prot)
+{
+ unsigned long prot = pgprot_val(_prot);
+
+ if (boot_cpu_data.x86 > 3)
+ prot |= _PAGE_PCD | _PAGE_PWT;
+
+ return __pgprot(prot);
+}
+#endif
+
+static int mkopci_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct mkopci_device *dev = filp->private_data;
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+
+ if (dev->core.c_bar == -1) {
+ pr_err("mkopci%d: invalid c_bar number\n", dev->core.n_dev);
+ return -EINVAL;
+ }
+
+ if (vma->vm_pgoff != 0) {
+ pr_err("mkopci%d: vma->vm_pgoff != 0, aborting mapping\n",
+ dev->core.n_dev);
+ return -EINVAL;
+ }
+
+ if (PAGE_ALIGN(dev->core.mem_size[dev->core.c_bar]) !=
+ PAGE_ALIGN(vma->vm_end - vma->vm_start)) {
+ pr_err("mkopci%d: PAGE_ALIGN error\n", dev->core.n_dev);
+ return -EINVAL;
+ }
+
+ if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC))
+ vma->vm_flags |= VM_IO;
+ vma->vm_flags |= VM_RESERVED | VM_SHARED;
+
+ pr_info("mkopci%d: .mmap BAR%d: [0x%lx - 0x%lx], vma [0x%lx - 0x%lx]\n",
+ dev->core.n_dev, dev->core.c_bar,
+ dev->core.mem_base[dev->core.c_bar],
+ dev->core.mem_base[dev->core.c_bar] +
+ dev->core.mem_size[dev->core.c_bar], vma->vm_start,
+ vma->vm_end);
+
+ if (remap_pfn_range
+ (vma, vma->vm_start,
+ virt_to_phys(bus_to_virt(dev->core.mem_base[dev->core.c_bar])) >>
+ PAGE_SHIFT, dev->core.mem_size[dev->core.c_bar],
+ pgprot_noncached(vma->vm_page_prot))) {
+ pr_err("mkopci%d: memory remapping failed\n", dev->core.n_dev);
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static const struct file_operations mkopci_fops = {
+ .owner = THIS_MODULE,
+ .open = mkopci_open,
+ .release = mkopci_release,
+ .unlocked_ioctl = mkopci_ioctl,
+ .mmap = mkopci_mmap,
+};
+/************************ End of File operations ******************************/
+
+/*************************** pci_driver functions *****************************/
+static int mkopci_probe_cdevhelper(struct pci_dev *dev)
+{
+ struct mkopci_device *device =
+ (struct mkopci_device *)pci_get_drvdata(dev);
+ struct device *dev_;
+ int err = 0;
+
+ cdev_init(&device->cdev, &mkopci_fops);
+ device->cdev.owner = THIS_MODULE;
+
+ err = cdev_add(&device->cdev, MKDEV(MAJOR(devp),
+ MINOR(devp) + device->core.n_dev), 1);
+ if (err) {
+ pr_err("mkopci%d: error while cdev_add\n", device->core.n_dev);
+ return err;
+ }
+
+ dev_ =
+ device_create(mkopci_class, NULL,
+ MKDEV(MAJOR(devp), MINOR(devp) + device->core.n_dev),
+ NULL, device->core.name);
+ if (IS_ERR(dev_)) {
+ pr_err("mkopci%d: Unable to create device\n",
+ device->core.n_dev);
+ err = PTR_ERR(dev_);
+ cdev_del(&device->cdev);
+ }
+
+ return err;
+}
+
+static int mkopci_plx9050workaround(struct pci_dev *dev, int plx9050bug)
+{
+ struct mkopci_device *device;
+ int err = 0;
+ phys_addr_t plxphys = ~(phys_addr_t) 0;
+ resource_size_t len;
+ struct resource *res;
+
+ device = (struct mkopci_device *)pci_get_drvdata(dev);
+ if (!device) {
+ err = -ENODEV;
+ goto out;
+ }
+
+ if (plx9050bug & PLX9050BUG_INJECT || plx9050bug & PLX9050BUG_BAR0) {
+ plxphys =
+ (pci_resource_start(dev, 0) & ~0xff) +
+ ((plx9050bug & PLX9050BUG_INJECT) ? 0x80 : 0x0);
+ if (plx9050bug & PLX9050BUG_INJECT) {
+ pr_info
+ ("mkopci%d: [PLX9050 bug injection] mem start = 0x%"
+ PFMT "\n", device->core.n_dev, plxphys);
+ dev->resource[0].start |= 0x80;
+ dev->resource[0].end =
+ dev->resource[0].start + 0x80 - 1;
+ err = pci_request_region(dev, 0, device->core.name);
+ if (err) {
+ pr_err("failed to request region 0");
+ goto out;
+ }
+ } else {
+ pr_info
+ ("mkopci%d: [PLX9050 bug workaround] mem start = 0x%"
+ PFMT "\n", device->core.n_dev, plxphys);
+ res = &dev->resource[0];
+ res->start &= ~0xff;
+ res->end = res->start + 0x80 - 1;
+ err = pci_request_region(dev, 0, device->core.name);
+ if (err) {
+ err =
+ allocate_resource(dev->resource[0].parent,
+ res, 0x80,
+ res->parent->start,
+ res->parent->end - 0x80,
+ 0x100, NULL, NULL);
+ if (err)
+ err =
+ allocate_resource(&iomem_resource,
+ res, 0x80,
+ iomem_resource.
+ start,
+ iomem_resource.
+ end - 0x80, 0x100,
+ NULL, NULL);
+ if (err) {
+ pr_err("failed to allocate region");
+ goto out;
+ }
+ err =
+ pci_request_region(dev, 0,
+ device->core.name);
+ if (err) {
+ pr_err("failed to allocate region");
+ goto out;
+ }
+ }
+ }
+ len = pci_resource_len(dev, 0);
+ pci_write_config_dword(dev, PCI_BASE_ADDRESS_0,
+ dev->resource[0].start);
+
+ device->core.mem_base[0] = dev->resource[0].start;
+ device->core.mem_size[0] = len;
+ pr_info("mkopci%d: device->core.mem_size[0] = %d\n",
+ device->core.n_dev, device->core.mem_size[0]);
+ }
+
+ if (plx9050bug & PLX9050BUG_INJECT)
+ goto out;
+
+ if (plx9050bug & PLX9050BUG_BAR1) {
+ plxphys = pci_resource_start(dev, 1);
+ len = pci_resource_len(dev, 1);
+ plxphys = plxphys & (PAGE_MASK | 0xf00);
+ pci_write_config_dword(dev, PCI_BASE_ADDRESS_1, plxphys);
+ dev->resource[1].start = plxphys;
+ dev->resource[1].end = dev->resource[1].start + len - 1;
+
+ if (pci_request_region(dev, 1, device->core.name)) {
+ err = -EFAULT;
+ if (plx9050bug & PLX9050BUG_BAR0) {
+ iounmap((void *)device->core.lin_base[0]);
+ device->core.lin_base[0] = 0;
+ pci_release_region(dev, 0);
+ }
+ goto out;
+ }
+
+ if (v > 0)
+ pr_info
+ ("mkopci%d: BAR%d = 0x%lx I/O region [PLX9050 bug workaround]\n",
+ device->core.n_dev, 1,
+ (unsigned long)pci_resource_start(dev, 1));
+ }
+out:
+ return err;
+}
+
+static int mkopci_probe_helper(struct pci_dev *dev)
+{
+ int reg = 0, n = 0, err = 0, plx9050bug = 0;
+ struct mkopci_device *device;
+
+ device = (struct mkopci_device *)pci_get_drvdata(dev);
+ if (!device)
+ return -ENODEV;
+
+ if (dev->device == MKO_DEVICE1 || plx9050bug_quirk == 2 ||
+ plx9050bug_quirk == 1) {
+ if (plx9050bug_quirk == 2) {
+ err = mkopci_plx9050workaround(dev,
+ plx9050bug |
+ PLX9050BUG_INJECT);
+ if (err)
+ return err;
+ }
+
+ if (pci_resource_start(dev, 0) & 0x80)
+ plx9050bug = PLX9050BUG_BAR0;
+ if (pci_resource_start(dev, 1) & 0x80)
+ plx9050bug |= PLX9050BUG_BAR1;
+
+ if (plx9050bug && plx9050bug_quirk == 1) {
+ err = mkopci_plx9050workaround(dev, plx9050bug);
+ if (err)
+ return err;
+ }
+ }
+
+ for (; reg < MAX_MEM_WIN + 1; reg++) {
+ if (pci_resource_flags(dev, reg) & IORESOURCE_MEM) {
+ device->core.mem_base[n] = pci_resource_start(dev, reg);
+ if (!device->core.mem_base[n]) {
+ pr_err("mkopci%d: Unable to get BAR%d\n",
+ device->core.n_dev, n);
+ err = -EFAULT;
+ goto fail;
+ }
+
+ device->core.mem_size[n] = pci_resource_len(dev, reg);
+ if (!device->core.mem_size[n]) {
+ pr_err
+ ("mkopci%d: Unable to get size of BAR%d\n",
+ device->core.n_dev, n);
+ err = -EFAULT;
+ goto fail;
+ }
+
+ if ((reg != 0) || !(plx9050bug & PLX9050BUG_BAR0)) {
+ err = pci_request_region(dev, reg,
+ device->core.name);
+ if (err) {
+ pr_err
+ ("mkopci%d: couldn't request region 0x%x with size 0x%x\n",
+ device->core.n_dev,
+ (unsigned int)device->core.mem_base[n],
+ (unsigned int)device->core.mem_size[n]);
+
+ iounmap((void *)
+ device->core.lin_base[n]);
+ goto fail;
+ }
+ }
+
+ device->core.lin_base[n] =
+ (unsigned long)ioremap_nocache(device->core.
+ mem_base[n],
+ device->core.
+ mem_size[n]);
+ if (!device->core.lin_base[n]) {
+ pr_err
+ ("mkopci%d: Unable to remap BAR%d[0x%lx:0x%lx]\n",
+ device->core.n_dev, n,
+ device->core.mem_base[n],
+ device->core.mem_base[n] +
+ device->core.mem_size[n] - 1);
+ err = -EFAULT;
+ goto fail;
+ }
+ if (v > 0)
+ pr_info
+ ("mkopci%d: BAR%d = 0x%08lx, linear = 0x%08lx, len = 0x%x\n",
+ device->core.n_dev, reg,
+ device->core.mem_base[n],
+ device->core.lin_base[n],
+ device->core.mem_size[n]);
+ n++;
+ } else {
+ if (v > 0)
+ pr_info("mkopci%d: BAR%d = 0x%lx I/O region\n",
+ device->core.n_dev, reg,
+ (unsigned long)pci_resource_start(dev,
+ reg));
+ err = pci_request_region(dev, reg, device->core.name);
+ if (err) {
+ pr_err("mkopci%d: couldn't request region %d\n",
+ device->core.n_dev, reg);
+ goto fail;
+ }
+ }
+ }
+ device->core.mem_windows_nr = n;
+
+ device->core.ltype =
+ (*((unsigned short *)(device->core.lin_base[4]) + 3)) & 0xFF;
+ device->core.irq = dev->irq;
+ if (v > 0)
+ pr_info("mkopci%d: irq = %d\n", device->core.n_dev,
+ device->core.irq);
+ device->core.irq_requested = 0;
+ device->core.c_bar = -1;
+
+ err = mkopci_probe_cdevhelper(dev);
+ if (err) {
+ plx9050bug = 0;
+ goto fail2;
+ }
+ atomic_inc(&devices_nr);
+ goto out;
+
+fail:
+ reg--;
+ n--;
+fail2:
+ for (; reg >= 0; reg--) {
+ if (plx9050bug && reg < 2)
+ break;
+ if (pci_resource_flags(dev, reg) & IORESOURCE_MEM) {
+ iounmap((void *)device->core.lin_base[n]);
+ device->core.lin_base[n--] = 0;
+ }
+ pci_release_region(dev, reg);
+ }
+
+out:
+ return err;
+}
+
+static int mkopci_probe(struct pci_dev *dev,
+ const struct pci_device_id __unused * id)
+{
+ int n_prdev = 0, err = 0;
+ int n;
+ struct mkopci_device *device, *mdev = NULL;
+ struct list_head *it;
+
+ if (atomic_read(&devices_nr) == (1 << 8 * sizeof(unsigned char)) - 1)
+ return -EBUSY;
+
+ err = pci_enable_device(dev);
+ if (err) {
+ pr_err("mkopci: error = %d while enabling PCI device\n", err);
+ return err;
+ }
+
+ device = kmem_cache_zalloc(mkopci_device_cache, GFP_KERNEL);
+ if (!device) {
+ pr_err("mkopci: Unable to allocate memory\n");
+ pci_disable_device(dev);
+ return -ENOMEM;
+ }
+
+ down_write(&devices_sem);
+ if (!list_empty(&devices)) {
+ list_for_each(it, &devices) {
+ mdev = list_entry(it, struct mkopci_device, list);
+ if (n_prdev < mdev->core.n_dev) {
+ if (n_prdev)
+ device->core.n_dev = n_prdev - 1;
+ else
+ device->core.n_dev = n_prdev;
+ list_add(&device->list, mdev->list.prev);
+ break;
+ }
+ n_prdev++;
+ }
+ if (n_prdev >= mdev->core.n_dev) {
+ device->core.n_dev = atomic_read(&devices_nr);
+ list_add_tail(&device->list, &devices);
+ }
+ } else
+ list_add_tail(&device->list, &devices);
+ up_write(&devices_sem);
+
+ pci_set_drvdata(dev, device);
+ device->pci_dev = dev;
+ for (n = 0; n < omited_nr; n++) {
+ if (omited[n] ==
+ mko_pci_addr(dev->bus->number, PCI_SLOT(dev->devfn),
+ PCI_FUNC(dev->devfn), 0)) {
+ pci_disable_device(dev);
+ device->omited = 1;
+ }
+ }
+
+ device->core.vendor_id = dev->vendor;
+ device->core.device_id = dev->device;
+ device->core.subs_vendor_id = dev->subsystem_vendor;
+ device->core.subs_id = dev->subsystem_device;
+ device->core.instance =
+ mko_pci_addr(dev->bus->number, PCI_SLOT(dev->devfn),
+ PCI_FUNC(dev->devfn), 0);
+ sprintf(device->core.name, "mkopci%d", device->core.n_dev);
+
+ init_waitqueue_head(&device->wq);
+ init_rwsem(&device->rwsem);
+
+ down_write(&device->rwsem);
+ if (device->omited)
+ goto out;
+
+ err = mkopci_probe_helper(dev);
+ if (err) {
+ pci_disable_device(dev);
+ list_del(&device->list);
+ kmem_cache_free(mkopci_device_cache, device);
+ }
+
+out:
+ up_write(&device->rwsem);
+ return err;
+}
+
+static void mkopci_remove_helper(struct pci_dev *dev)
+{
+ int reg, n = 0;
+ unsigned char rom_base_reg = dev->rom_base_reg;
+ struct mkopci_device *device =
+ (struct mkopci_device *)pci_get_drvdata(dev);
+
+ rom_base_reg = dev->rom_base_reg / 8;
+ for (reg = 0; reg < DEVICE_COUNT_RESOURCE; reg++) {
+ if (reg == rom_base_reg)
+ continue;
+ if (pci_resource_flags(dev, reg) & IORESOURCE_MEM) {
+ if (v > 1)
+ pr_info("mkopci%d: unmapping BAR%d (0x%lx)\n",
+ device->core.n_dev, reg,
+ device->core.lin_base[n]);
+ iounmap((void *)device->core.lin_base[n]);
+ device->core.lin_base[n++] = 0;
+ }
+
+ pci_release_region(dev, reg);
+ }
+
+ mkopci_free_irq(device);
+ pci_disable_device(dev);
+ device_destroy(mkopci_class,
+ MKDEV(MAJOR(devp), MINOR(devp) + device->core.n_dev));
+ cdev_del(&device->cdev);
+ atomic_dec(&devices_nr);
+}
+
+static void mkopci_remove(struct pci_dev *dev)
+{
+ struct mkopci_device *device =
+ (struct mkopci_device *)pci_get_drvdata(dev);
+
+ down_write(&device->rwsem);
+ if (!device->omited)
+ mkopci_remove_helper(dev);
+ list_del(&device->list);
+ up_write(&device->rwsem);
+ kmem_cache_free(mkopci_device_cache, device);
+ if (v > 0)
+ pr_info("mkopci%d: removed\n", device->core.n_dev);
+}
+
+#ifdef CONFIG_PM
+static int mkopci_suspend(struct device __unused * device)
+{
+ return -ENOSYS;
+}
+
+static int mkopci_resume(struct device __unused * device)
+{
+ return 0;
+}
+#else
+#define mkopci_suspend NULL
+#define mkopci_resume NULL
+#endif
+
+/*********************** End of pci_driver functions **************************/
+
+/*************************** proc entries functions ***************************/
+static struct proc_dir_entry *mkopci_proc_file, *mkopci_proc_dir,
+ *mkopci_proc_core;
+
+static int mkopci_proc_show(struct seq_file *m, void __unused * vp)
+{
+ struct mkopci_device *mko_dev;
+
+ down_read(&devices_sem);
+ if (!atomic_read(&devices_nr))
+ seq_puts(m, "no devices detected\n");
+ else
+ seq_printf(m, "%d device(s) detected\n",
+ atomic_read(&devices_nr));
+
+ seq_printf(m, "plx9050bug_quirk = %d\n", plx9050bug_quirk);
+ seq_printf(m, "verbosity level = %d\n", v);
+
+ list_for_each_entry(mko_dev, &devices, list) {
+ down_read(&mko_dev->rwsem);
+ seq_printf(m,
+ "\ndevice\t\t\t/dev/%s\n"
+ "vendor_id =\t\t0x%04x\n"
+ "device_id =\t\t0x%04x\n"
+ "subs_vendor_id =\t0x%04x\n"
+ "subs_id =\t\t0x%04x\n"
+ "ltype =\t\t\t%d\n"
+ "instance =\t\t%02x:%02x.%x (0x%x)\n"
+ "process =\t\t%d\n"
+ "mem_base[0] =\t\t0x%08lx (linear = 0x%lx, size 0x%x)\n"
+ "mem_base[1] =\t\t0x%08lx (linear = 0x%lx, size 0x%x)\n"
+ "mem_base[2] =\t\t0x%08lx (linear = 0x%lx, size 0x%x)\n"
+ "mem_base[3] =\t\t0x%08lx (linear = 0x%lx, size 0x%x)\n"
+ "mem_base[4] =\t\t0x%08lx (linear = 0x%lx, size 0x%x)\n"
+ "irq_n =\t\t\t%d\n"
+ "irq_requested =\t\t%d\n",
+ mko_dev->omited ? "(OMITED)" : mko_dev->core.name,
+ mko_dev->core.vendor_id, mko_dev->core.device_id,
+ mko_dev->core.subs_vendor_id, mko_dev->core.subs_id,
+ mko_dev->core.ltype, mko_dev->core.instance >> 16,
+ mko_dev->core.instance >> 11 & 0x001f,
+ (mko_dev->core.instance >> 8) & 0x7,
+ mko_dev->core.instance, mko_dev->core.process,
+ mko_dev->core.mem_base[0], mko_dev->core.lin_base[0],
+ mko_dev->core.mem_size[0], mko_dev->core.mem_base[1],
+ mko_dev->core.lin_base[1], mko_dev->core.mem_size[1],
+ mko_dev->core.mem_base[2], mko_dev->core.lin_base[2],
+ mko_dev->core.mem_size[2], mko_dev->core.mem_base[3],
+ mko_dev->core.lin_base[3], mko_dev->core.mem_size[3],
+ mko_dev->core.mem_base[4], mko_dev->core.lin_base[4],
+ mko_dev->core.mem_size[4], mko_dev->core.irq,
+ mko_dev->core.irq_requested);
+ up_read(&mko_dev->rwsem);
+ }
+ up_read(&devices_sem);
+
+ return 0;
+}
+
+static int proc_text_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mkopci_proc_show, inode->i_cdev);
+}
+
+static const struct file_operations mkopci_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = proc_text_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static ssize_t mkopci_proc_core_read(struct file __unused * file,
+ char __user *buffer, size_t count,
+ loff_t *offset)
+{
+ const int MAX_CORE_LEN =
+ MAX_DEVICES_NR * sizeof(struct mkopci_core) +
+ sizeof(unsigned short);
+ int n, len = 0, ret = 0;
+ struct mkopci_device *d;
+
+ down_read(&devices_sem);
+ n = atomic_read(&devices_nr) * sizeof(struct mkopci_core) +
+ sizeof(unsigned short);
+ if (*offset >= n)
+ goto out;
+
+ if (*offset == 0) {
+ if (put_user((unsigned long)atomic_read(&devices_nr), buffer)) {
+ ret = -ERESTARTSYS;
+ goto out;
+ }
+ len = sizeof(unsigned long);
+ }
+
+ list_for_each_entry(d, &devices, list) {
+ down_read(&d->rwsem);
+ if (d->omited) {
+ up_read(&d->rwsem);
+ continue;
+ }
+ if (copy_to_user
+ (buffer + len, &d->core, sizeof(struct mkopci_core))) {
+ up_read(&d->rwsem);
+ ret = -ERESTARTSYS;
+ goto out;
+ }
+ up_read(&d->rwsem);
+ len += sizeof(struct mkopci_core);
+ }
+
+ if (count > len)
+ if (len < MAX_CORE_LEN) {
+ if (clear_user(buffer + len + 1, MAX_CORE_LEN - len)) {
+ ret = -ERESTARTSYS;
+ goto out;
+ }
+ }
+
+ *offset += len;
+ ret = len;
+
+out:
+ up_read(&devices_sem);
+ return ret;
+}
+
+static int hex2int(const char s[])
+{
+ static const char hexalpha[] = "aAbBcCdDeEfF";
+ unsigned int ret = 0;
+ int i = 0, k;
+ int err = 0;
+ int hexint = 0;
+
+ if (s[i] == '0') {
+ i++;
+ if (s[i] == 'x' || s[i] == 'X')
+ i++;
+ }
+
+ while (!err && s[i] != '\0') {
+ ret = ret << 4;
+ if (s[i] >= '0' && s[i] <= '9')
+ ret = ret + (s[i] - '0');
+ else {
+ for (k = 0; hexint == 0 && hexalpha[k] != '\0'; k++) {
+ if (hexalpha[k] == s[i])
+ hexint = 10 + k / 2;
+ }
+ if (hexint == 0) {
+ err = -EINVAL;
+ break;
+ }
+ ret = ret + hexint;
+ hexint = 0;
+ }
+ i++;
+ }
+
+ if (err)
+ ret = err;
+
+ return ret;
+}
+
+static ssize_t mkopci_proc_core_write(struct file __unused * file,
+ const char __user *buffer, size_t count,
+ loff_t __unused * offset)
+{
+ char buf[8];
+ int ret = 0, instance = 0, nodev = 1;
+ struct mkopci_device *d;
+ struct pci_dev *dev;
+
+ if (copy_from_user(buf, buffer, min(sizeof(buf), count))) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ if (count > sizeof(buf)) {
+ ret = -EINVAL;
+ goto out;
+ } else
+ buf[count - 1] = 0;
+
+ if (!strncmp(buf, "0x", 2) || !strncmp(buf, "0X", 2))
+ instance = hex2int(buf);
+ else if ((strlen(buf) == 7) && (buf[2] == ':') && (buf[5] == '.')) {
+ int a, b, c;
+
+ buf[2] = 0;
+ buf[5] = 0;
+ a = hex2int(buf);
+ b = hex2int(buf + 3);
+ c = hex2int(buf + 6);
+ buf[2] = ':';
+ buf[5] = '.';
+
+ instance = mko_pci_addr(a, b, c, 0);
+ } else
+ instance = -EINVAL;
+
+ if (instance == -EINVAL) {
+ pr_err("mkopci: inappropriate PCI address '%s'\n", buf);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ down_write(&devices_sem);
+ list_for_each_entry(d, &devices, list) {
+ down_write(&d->rwsem);
+ if (d->core.instance != instance) {
+ up_write(&d->rwsem);
+ continue;
+ }
+ nodev = 0;
+ if (d->core.process || d->backdoor) {
+ if (v > 0) {
+ if (d->core.process)
+ pr_info
+ ("mkopci%d: device is handled by process [%d]\n",
+ d->core.n_dev, d->core.process);
+ else
+ pr_info
+ ("mkopci%d: device is handled by process [%d] in backdoor mode\n",
+ d->core.n_dev, d->backdoor);
+ }
+ ret = -EBUSY;
+ goto out_up_sems;
+ }
+ dev = d->pci_dev;
+ if (d->omited) {
+ ret = pci_enable_device(dev);
+ if (ret) {
+ pr_err
+ ("mkopci: error = %d while enabling PCI device\n",
+ ret);
+ goto out_up_sems;
+ }
+
+ ret = mkopci_probe_helper(dev);
+ if (ret) {
+ pr_err
+ ("mkopci: error = %d in mkopci_probe_helper\n",
+ ret);
+ goto out_ph_fail;
+ }
+ d->omited = 0;
+ } else {
+ mkopci_remove_helper(dev);
+ d->omited = 1;
+ }
+ up_write(&d->rwsem);
+ break;
+ }
+ up_write(&devices_sem);
+
+ if (nodev) {
+ pr_info("mkopci: no device with address %s was found\n", buf);
+ ret = -ENODEV;
+ goto out;
+ }
+ ret = min(sizeof(buf), count);
+ goto out;
+
+out_ph_fail:
+ pci_disable_device(dev);
+out_up_sems:
+ up_write(&d->rwsem);
+ up_write(&devices_sem);
+out:
+ return ret;
+}
+
+static const struct file_operations mko_core_proc_fops = {
+ .owner = THIS_MODULE,
+ .read = mkopci_proc_core_read,
+ .write = mkopci_proc_core_write,
+};
+
+static int mkopci_create_proc_entry(void)
+{
+ mkopci_proc_dir = proc_mkdir("mkopci", NULL);
+ if (mkopci_proc_dir == NULL) {
+ remove_proc_entry("mkopci", NULL);
+ pr_err("mkopci: could not initialize /proc/mkopci/\n");
+ return -ENOMEM;
+ }
+
+ mkopci_proc_file =
+ proc_create("devices", S_IFREG | S_IRUGO, mkopci_proc_dir,
+ &mkopci_proc_fops);
+ if (mkopci_proc_file == NULL) {
+ remove_proc_entry("mkopci", NULL);
+ pr_err("mkopci: could not initialize /proc/mkopci/devices\n");
+ return -ENOMEM;
+ }
+ mkopci_proc_core =
+ proc_create("core", S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP,
+ mkopci_proc_dir, &mko_core_proc_fops);
+ if (mkopci_proc_core == NULL) {
+ remove_proc_entry("devices", mkopci_proc_dir);
+ remove_proc_entry("mkopci", NULL);
+ pr_err("mkopci: could not initialize /proc/mkopci/core\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+/************************* End of proc entries functions **********************/
+
+static SIMPLE_DEV_PM_OPS(mkopci_pm_ops, mkopci_suspend, mkopci_resume);
+
+static struct pci_driver mkopci_driver = {
+ .name = "mkopci",
+ .id_table = ids,
+ .probe = mkopci_probe,
+ .remove = mkopci_remove,
+ .driver.pm = &mkopci_pm_ops,
+};
+
+static int __init mkopci_init(void)
+{
+ int ret = 0;
+ struct mkopci_device *d;
+
+ if (v < 0 || v > 3 || omited_nr > MAX_DEVICES_NR) {
+ pr_err("mkopci: inappropriate parameters\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mkopci_device_cache =
+ kmem_cache_create("mkopci_dev_cache", sizeof(struct mkopci_device),
+ 0, 0, 0);
+
+ if (!mkopci_device_cache) {
+ pr_err("mkopci: Unable to allocate memory\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+ init_rwsem(&devices_sem);
+
+ mkopci_class = class_create(THIS_MODULE, "mkopci");
+ if ((IS_ERR(mkopci_class))) {
+ pr_err("mkopci: error creating class\n");
+ ret = PTR_ERR(mkopci_class);
+ goto destroy_cache;
+ }
+
+ ret = alloc_chrdev_region(&devp, 0, MAX_DEVICES_NR, "mkopci");
+ if (ret) {
+ pr_err("mkopci: failed to allocate chrdev region\n");
+ goto destoy_class;
+ }
+
+ if (v > 0)
+ pr_info("mkopci: major number = %d\n", MAJOR(devp));
+
+ ret = pci_register_driver(&mkopci_driver);
+ if (ret < 0) {
+ pr_err("mkopci: error (%d) while pci_register_driver\n", ret);
+ goto free_chr_reg;
+ }
+
+ ret = mkopci_create_proc_entry();
+ if (ret) {
+ pr_err("mkopci: error creating proc entries\n");
+ goto unreg_drv;
+ }
+ pr_info("mkopci: driver loaded\n");
+ goto out;
+
+unreg_drv:
+ pci_unregister_driver(&mkopci_driver);
+free_chr_reg:
+ unregister_chrdev_region(devp, MAX_DEVICES_NR);
+destoy_class:
+ class_destroy(mkopci_class);
+ while (!list_empty(&devices)) {
+ d = list_entry(devices.next, struct mkopci_device, list);
+ list_del(devices.next);
+ kmem_cache_free(mkopci_device_cache, d);
+ }
+destroy_cache:
+ kmem_cache_destroy(mkopci_device_cache);
+
+out:
+ return ret;
+}
+
+static void __exit mkopci_exit(void)
+{
+ struct mkopci_device *d;
+
+ remove_proc_entry("core", mkopci_proc_dir);
+ remove_proc_entry("devices", mkopci_proc_dir);
+ remove_proc_entry("mkopci", NULL);
+
+ pci_unregister_driver(&mkopci_driver);
+ unregister_chrdev_region(devp, MAX_DEVICES_NR);
+ class_destroy(mkopci_class);
+ while (!list_empty(&devices)) {
+ d = list_entry(devices.next, struct mkopci_device, list);
+ list_del(devices.next);
+ kmem_cache_free(mkopci_device_cache, d);
+ }
+ kmem_cache_destroy(mkopci_device_cache);
+
+ pr_info("mkopci: driver unloaded\n");
+}
+
+MODULE_DESCRIPTION("MKO PCI card driver");
+MODULE_AUTHOR("Sergej Bauer");
+MODULE_LICENSE("GPL v2");
+
+module_init(mkopci_init);
+module_exit(mkopci_exit);
+
diff --git a/include/misc/mkopci.h b/include/misc/mkopci.h
new file mode 100644
index 0000000..fdc5270
--- /dev/null
+++ b/include/misc/mkopci.h
@@ -0,0 +1,81 @@
+#ifndef MKOPCI_H
+#define MKOPCI_H
+
+#define MKO_IOC_MAGIC 'X'
+
+#define MAX_MEM_WIN 5
+#define MAX_DEVICES_NR 16
+
+#if !defined(__KERNEL__)
+#define MaxDeviceCount MAX_DEVICES_NR
+typedef struct {
+ unsigned short VendorId;
+ unsigned short DeviceId;
+ unsigned short SubsystemVendorId;
+ unsigned short SubsystemId;
+ unsigned int InstanceId;
+ unsigned int ProcessId;
+ unsigned int LType;
+ unsigned short NumMemWindows;
+ unsigned long MemBase[MAX_MEM_WIN];
+ unsigned long LinBase[MAX_MEM_WIN];
+ unsigned int MemSize[MAX_MEM_WIN];
+ unsigned short IRQ;
+ int handle;
+ char name[sizeof("mkopci00") + 1];
+ unsigned char irq_requested;
+ int c_bar;
+ unsigned char n_dev;
+} mkopcilnx_device_info_t;
+
+typedef struct {
+ unsigned short DeviceCount;
+ mkopcilnx_device_info_t DeviceInfo[MaxDeviceCount];
+} mkopcilnx_device_table_t;
+#else
+struct mkopci_core {
+ unsigned short vendor_id;
+ unsigned short device_id;
+ unsigned short subs_vendor_id;
+ unsigned short subs_id;
+ unsigned int instance;
+ unsigned int process;
+ unsigned int ltype;
+ unsigned short mem_windows_nr;
+ unsigned long mem_base[MAX_MEM_WIN];
+ unsigned long lin_base[MAX_MEM_WIN];
+ unsigned int mem_size[MAX_MEM_WIN];
+ unsigned short irq;
+ int handle;
+ char name[sizeof("mkopci00") + 1];
+ unsigned char irq_requested;
+ int c_bar;
+ unsigned char n_dev;
+};
+
+/* kernel's structure of the driver */
+struct mkopci_device {
+ struct mkopci_core core;
+ struct rw_semaphore rwsem;
+ wait_queue_head_t wq;
+ struct cdev cdev;
+ unsigned char omited;
+ unsigned int backdoor;
+ struct pci_dev *pci_dev;
+ struct list_head list;
+};
+#endif
+
+#define MKOPCI_IOCTL_GET_VERSION _IOR(MKO_IOC_MAGIC, 1, int)
+#define MKOPCI_IOCTL_GET_BOARDS_COUNT _IOR(MKO_IOC_MAGIC, 2, int)
+#define MKOPCI_IOCTL_GET_DEVICE_TABLE _IOR(MKO_IOC_MAGIC, 3, struct mkopci_core)
+#define MKOPCI_IOCTL_ATTACH_IRQ _IO(MKO_IOC_MAGIC, 4)
+#define MKOPCI_IOCTL_WAIT_IRQ _IO(MKO_IOC_MAGIC, 5)
+#define MKOPCI_IOCTL_DETACH_IRQ _IO(MKO_IOC_MAGIC, 6)
+#define MKOPCI_IOCTL_REQUEST_BAR _IOW(MKO_IOC_MAGIC, 7, int)
+#define MKOPCI_IOCTL_CWPID _IO(MKO_IOC_MAGIC, 8)
+
+#define MKO_IOC_MAXNR 8
+
+#endif
+
Signed-off-by: Sergej Bauer <sergej.bauer@xxxxxxxxx>

diff --git a/Documentation/misc-devices/mkopci.txt b/Documentation/misc-devices/mkopci.txt
new file mode 100644
index 0000000..499bc29
--- /dev/null
+++ b/Documentation/misc-devices/mkopci.txt
@@ -0,0 +1,44 @@
+ PCI-based MKO bus driver.
+
+
+For dealing with driver without using of root's account it will be helpful
+to add group `mkopci' with appropriate users and put file, say 60-mkopci.rules to
+/etc/udev/rules.d in your system.
+--- cut 60-mkopci.rules ---
+# MKO devices
+KERNEL=="mkopci*", SUBSYSTEM=="mkopci", ACTION=="add", DRIVERS=="?*", ATTRS{idVendor}=="0x6403"
+GROUP="mkopci"
+---
+
+Kernel module parameters
+
+Kernel module can take parameters 'v' and 'omited'
+- 'v' is 0 to 3, and affects the amount of information
+output to the system log.
+0 - (default) comletely silent
+1 - prints only detected BARs, reports loading / unloading
+2 - + prints IRQ and memory mapping events
+3 - + prints ioctl events
+
+- 'plx9050bug_quirk' controls workaround controller PLX9050. Can
+the following values:
+0 - workaround is disabled (default)
+1 - workaround is enabled
+2 - forced bug
+
+- 'omited' tells the driver which device should not be initialized
+at load time.
+The value of this parameter to the kernel module 2.4 is a physical address
+device on the bus, such as "0x10800" without the quotes.
+In kernel versions 2.6+ can exclude multiple devices, transferring them
+values separated by commas, but not more than 4.
+
+The parameter values can be specified as follows:
+$ insmod/modprobe mkopci.[Ko/o] parameter1_name=value [parameter2_name=value]
+
+Record the physical address of the device in the file /proc/mkopci/core
+also controls "visibility" for user programs. Missed return device
+You can command 'echo ADDR > /proc/mkopci/core'. ADDR can be either a simple
+number of device, but always in hexadecimal, or (for Linux-2.6+)
+the number of devices in a standard format Linux kind NM:XY.z.
+
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 006242c..c571451 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -124,6 +124,18 @@ config PHANTOM
If you choose to build module, its name will be phantom. If unsure,
say N here.

+config MKOPCI
+ tristate "Module PCI bus driver"
+ depends on PCI && PROC_FS
+ help
+ MKOPCI (MB11.xx) device (by RC Module project) provides data transference
+ through a serial bus bar according to MIL-STD-1553.
+
+ Say Y here if you want to build a driver for Module(RC) MKOPCI devices.
+
+ If you choose to build module, its name will be mkopci. If unsure,
+ say N here.
+
config INTEL_MID_PTI
tristate "Parallel Trace Interface for MIPI P1149.7 cJTAG standard"
depends on PCI && TTY && (X86_INTEL_MID || COMPILE_TEST)
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 7d5c4cd..afb92b4 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -56,3 +56,4 @@ obj-$(CONFIG_GENWQE) += genwqe/
obj-$(CONFIG_ECHO) += echo/
obj-$(CONFIG_VEXPRESS_SYSCFG) += vexpress-syscfg.o
obj-$(CONFIG_CXL_BASE) += cxl/
+obj-$(CONFIG_MKOPCI) += mkopci.o
diff --git a/drivers/misc/mkopci.c b/drivers/misc/mkopci.c
new file mode 100644
index 0000000..1e2b77a
--- /dev/null
+++ b/drivers/misc/mkopci.c
@@ -0,0 +1,1272 @@
+/*
+ * MKOPCI driver
+ *
+ * Copyright (C) 2007-2015 Sergej Bauer <sergej.bauer@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License, as published by
+ * the Free Software Foundation, version 2.
+*/
+
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/mm.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/interrupt.h>
+#include <linux/cdev.h>
+#include <linux/uaccess.h>
+#include <linux/io.h>
+
+#include "misc/mkopci.h"
+
+#define MKO_VENDOR 0x6403
+#define MKO_DEVICE1 0x0430
+#define MKO_DEVICE2 0x0431
+#define MKO_DEVICE3 0x0434
+
+#define PLX9050BUG_BAR0 0x1
+#define PLX9050BUG_BAR1 0x2
+#define PLX9050BUG_INJECT 0x4
+
+#if !defined(__user)
+#define __user
+#endif
+
+static struct pci_device_id ids[] = {
+ {MKO_VENDOR, MKO_DEVICE1, PCI_ANY_ID, PCI_ANY_ID,},
+ {MKO_VENDOR, MKO_DEVICE2, PCI_ANY_ID, PCI_ANY_ID,},
+ {MKO_VENDOR, MKO_DEVICE3, PCI_ANY_ID, PCI_ANY_ID,},
+ {0, 0,}
+};
+
+MODULE_DEVICE_TABLE(pci, ids);
+
+#ifdef __LP64__
+#define PFMT "llx"
+#else
+#define PFMT "x"
+#endif
+
+unsigned char drv_version = 0x11;
+#define mko_pci_addr(bus, device, func, regoffs) (\
+ ((bus & 0xFF) << 16) | ((device & 0x1F) << 11) | \
+ ((func & 0x7) << 8) | (regoffs & 0xFC))
+
+static struct kmem_cache *mkopci_device_cache;
+static dev_t devp;
+static struct class *mkopci_class;
+static struct rw_semaphore devices_sem;
+static LIST_HEAD(devices);
+static atomic_t devices_nr = ATOMIC_INIT(0);
+
+/*** module parameters ***/
+static int plx9050bug_quirk = 1;
+/* verbosity level */
+static int v;
+module_param(plx9050bug_quirk, int, S_IRUGO);
+module_param(v, int, S_IRUGO);
+MODULE_PARM_DESC(plx9050bug_quirk, "PLX9050 bug quirk");
+MODULE_PARM_DESC(v, "verbosity level");
+
+/* only MAX_DEVICES_NR devices at once can be omited */
+static int omited[MAX_DEVICES_NR];
+static int omited_nr;
+module_param_array(omited, int, &omited_nr, S_IRUGO);
+MODULE_PARM_DESC(omited, "device(s) to omit");
+#ifndef VM_RESERVED
+#define VM_RESERVED (VM_DONTEXPAND | VM_DONTDUMP)
+#endif
+#define __unused (__attribute__ ((unused)))
+
+/***************************** Interrupt handling *****************************/
+static int mkopci_wait_irq(struct mkopci_device *dev)
+{
+ volatile unsigned long *LPCI_4C =
+ (unsigned long *)(dev->core.lin_base[0] + 0x4C);
+
+ *LPCI_4C |= 0x49;
+
+ if (wait_event_interruptible(dev->wq, *LPCI_4C & 0x24))
+ return -ERESTARTSYS;
+ if (v > 1)
+ pr_info("mkopci%d: irq received\n", dev->core.n_dev);
+ *LPCI_4C &= ~0x40;
+
+ return 0;
+}
+
+static irqreturn_t mkopci_int_handler(int __unused irq, void *dev)
+{
+ struct mkopci_device *mko_dev = (struct mkopci_device *)dev;
+ volatile unsigned long *LPCI_4C =
+ (unsigned long *)((mko_dev->core.lin_base[0] + 0x4C));
+
+ if (*LPCI_4C & 0x24) {
+ *LPCI_4C &= ~0x40;
+ wake_up_interruptible(&mko_dev->wq);
+ } else
+ return IRQ_NONE;
+
+ return IRQ_HANDLED;
+}
+
+static int mkopci_request_irq(struct mkopci_device *dev)
+{
+ int ret = 0;
+
+ if (dev->core.irq_requested) {
+ pr_err("mkopci%d: irq already requested\n", dev->core.n_dev);
+ return -EBUSY;
+ }
+ ret = request_irq(dev->core.irq, &mkopci_int_handler, IRQF_SHARED,
+ dev->core.name, dev);
+ if (ret) {
+ pr_err("mkopci%d: failed to request irq %d\n", dev->core.n_dev,
+ dev->core.irq);
+ return ret;
+ }
+
+ dev->core.irq_requested++;
+
+ pr_info("mkopci%d: irq %d requested (%s)\n", dev->core.n_dev,
+ dev->core.irq, current->comm);
+
+ return ret;
+}
+
+static void mkopci_free_irq(struct mkopci_device *dev)
+{
+ if (dev->core.irq_requested) {
+ synchronize_irq(dev->core.irq);
+ free_irq(dev->core.irq, dev);
+ dev->core.irq_requested--;
+
+ pr_info("mkopci%d: irq %d released\n", dev->core.n_dev,
+ dev->core.irq);
+ }
+}
+/************************* End of Interrupt handling **************************/
+
+/**************************** File operations *********************************/
+static int mkopci_open(struct inode *inode, struct file *filp)
+{
+ int ret = 0;
+ struct mkopci_device *dev =
+ container_of(inode->i_cdev, struct mkopci_device, cdev);
+
+ if (!try_module_get(THIS_MODULE))
+ return -EINVAL;
+
+ if (dev == NULL) {
+ module_put(THIS_MODULE);
+ return -ENODEV;
+ }
+
+ down_write(&dev->rwsem);
+ if (dev->omited) {
+ ret = -ENODEV;
+ goto err;
+ }
+ if (!(filp->f_flags & O_NONBLOCK)) {
+ if (dev->core.process) {
+ ret = -EBUSY;
+ goto err;
+ }
+ dev->backdoor = 0;
+ dev->core.process = current->pid;
+ } else {
+ dev->backdoor = current->pid;
+ dev->core.process = 0;
+ }
+
+ filp->private_data = dev;
+
+ pr_info("mkopci%d: device opened in %s mode (%s)\n",
+ dev->core.n_dev, dev->backdoor ? "backdoor" : "regular",
+ current->comm);
+ goto out;
+
+err:
+ module_put(THIS_MODULE);
+out:
+ up_write(&dev->rwsem);
+
+ return ret;
+}
+
+static int mkopci_release(struct inode __unused * inode, struct file *filp)
+{
+ struct mkopci_device *dev;
+
+ if (filp->private_data == NULL)
+ return 0;
+
+ dev = (struct mkopci_device *)filp->private_data;
+ down_write(&dev->rwsem);
+ dev->core.process = 0;
+ dev->backdoor = 0;
+ dev->core.c_bar = -1;
+ filp->private_data = NULL;
+ mkopci_free_irq(dev);
+
+ pr_info("mkopci%d: device released\n", dev->core.n_dev);
+ up_write(&dev->rwsem);
+ module_put(THIS_MODULE);
+
+ return 0;
+}
+
+static long mkopci_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+ int ret = 0, n = 0;
+ struct mkopci_device *dev =
+ (struct mkopci_device *)filp->private_data, *d;
+
+ if (_IOC_TYPE(cmd) != MKO_IOC_MAGIC) {
+ pr_err("mkopci%d: _IOC_TYPE(cmd) != MKO_IOC_MAGIC\n",
+ dev->core.n_dev);
+ return -ENOTTY;
+ }
+
+ if (_IOC_NR(cmd) > MKO_IOC_MAXNR) {
+ pr_err("mkopci%d: _IOC_NR(cmd) > MKO_IOC_MAXNR\n",
+ dev->core.n_dev);
+ return -ENOTTY;
+ }
+
+ if (_IOC_DIR(cmd) & _IOC_READ) {
+ ret =
+ !access_ok(VERIFY_WRITE, (void __user *)arg,
+ _IOC_SIZE(cmd));
+ } else if (_IOC_DIR(cmd) & _IOC_WRITE) {
+ ret =
+ !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
+ }
+
+ if (ret) {
+ pr_err("mkopci%d: mkopci_ioctl: access_ok failed\n",
+ dev->core.n_dev);
+ return -EIO;
+ }
+
+ switch (cmd) {
+ case MKOPCI_IOCTL_CWPID:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_CWPID ioctl\n",
+ dev->core.n_dev);
+ down_read(&dev->rwsem);
+ ret = dev->core.process;
+ up_read(&dev->rwsem);
+ break;
+ case MKOPCI_IOCTL_GET_VERSION:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_GET_VERSION ioctl\n",
+ dev->core.n_dev);
+ ret = put_user(drv_version, (int __user *)arg);
+ break;
+ case MKOPCI_IOCTL_GET_BOARDS_COUNT:
+ if (v > 2)
+ pr_info
+ ("mkopci%d: MKOPCI_IOCTL_GET_BOARDS_COUNT ioctl\n",
+ dev->core.n_dev);
+ down_read(&dev->rwsem);
+ ret =
+ put_user((unsigned short)atomic_read(&devices_nr),
+ (int __user *)arg);
+ up_read(&dev->rwsem);
+ break;
+ case MKOPCI_IOCTL_GET_DEVICE_TABLE:
+ if (v > 2)
+ pr_info
+ ("mkopci%d: MKOPCI_IOCTL_GET_DEVICE_TABLE ioctl\n",
+ dev->core.n_dev);
+ down_read(&devices_sem);
+ if (put_user
+ ((unsigned short)atomic_read(&devices_nr),
+ (int __user *)arg)) {
+ up_read(&devices_sem);
+ ret = -ERESTARTSYS;
+ break;
+ }
+ list_for_each_entry(d, &devices, list) {
+ down_read(&d->rwsem);
+ if (d->omited) {
+ up_read(&d->rwsem);
+ continue;
+ }
+ if (copy_to_user
+ ((void __user *)arg + sizeof(unsigned short) +
+ n * sizeof(struct mkopci_core), &d->core,
+ sizeof(struct mkopci_core))) {
+ up_read(&d->rwsem);
+ ret = -ERESTARTSYS;
+ break;
+ }
+ up_read(&d->rwsem);
+ n++;
+ }
+ up_read(&devices_sem);
+ break;
+ case MKOPCI_IOCTL_ATTACH_IRQ:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_ATTACH_IRQ ioctl\n",
+ dev->core.n_dev);
+ down_write(&dev->rwsem);
+ ret = mkopci_request_irq(dev);
+ up_write(&dev->rwsem);
+ break;
+ case MKOPCI_IOCTL_DETACH_IRQ:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_DETACH_IRQ ioctl\n",
+ dev->core.n_dev);
+ down_write(&dev->rwsem);
+ mkopci_free_irq(dev);
+ up_write(&dev->rwsem);
+ break;
+ case MKOPCI_IOCTL_WAIT_IRQ:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_WAIT_IRQ ioctl\n",
+ dev->core.n_dev);
+ ret = mkopci_wait_irq(dev);
+ break;
+ case MKOPCI_IOCTL_REQUEST_BAR:
+ if (v > 2)
+ pr_info("mkopci%d: MKOPCI_IOCTL_REQUEST_BAR ioctl\n",
+ dev->core.n_dev);
+ down_write(&dev->rwsem);
+ ret = get_user(dev->core.c_bar, (int __user *)arg);
+ up_write(&dev->rwsem);
+ break;
+ default:
+ pr_err("mkopci%d: invalid ioctl %d\n", dev->core.n_dev,
+ _IOC_NR(cmd));
+ ret = -EINVAL;
+ break;
+ };
+
+ return ret;
+}
+
+#ifndef pgprot_noncached
+static inline pgprot_t pgprot_noncached(pgprot_t _prot)
+{
+ unsigned long prot = pgprot_val(_prot);
+
+ if (boot_cpu_data.x86 > 3)
+ prot |= _PAGE_PCD | _PAGE_PWT;
+
+ return __pgprot(prot);
+}
+#endif
+
+static int mkopci_mmap(struct file *filp, struct vm_area_struct *vma)
+{
+ struct mkopci_device *dev = filp->private_data;
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+
+ if (dev->core.c_bar == -1) {
+ pr_err("mkopci%d: invalid c_bar number\n", dev->core.n_dev);
+ return -EINVAL;
+ }
+
+ if (vma->vm_pgoff != 0) {
+ pr_err("mkopci%d: vma->vm_pgoff != 0, aborting mapping\n",
+ dev->core.n_dev);
+ return -EINVAL;
+ }
+
+ if (PAGE_ALIGN(dev->core.mem_size[dev->core.c_bar]) !=
+ PAGE_ALIGN(vma->vm_end - vma->vm_start)) {
+ pr_err("mkopci%d: PAGE_ALIGN error\n", dev->core.n_dev);
+ return -EINVAL;
+ }
+
+ if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC))
+ vma->vm_flags |= VM_IO;
+ vma->vm_flags |= VM_RESERVED | VM_SHARED;
+
+ pr_info("mkopci%d: .mmap BAR%d: [0x%lx - 0x%lx], vma [0x%lx - 0x%lx]\n",
+ dev->core.n_dev, dev->core.c_bar,
+ dev->core.mem_base[dev->core.c_bar],
+ dev->core.mem_base[dev->core.c_bar] +
+ dev->core.mem_size[dev->core.c_bar], vma->vm_start,
+ vma->vm_end);
+
+ if (remap_pfn_range
+ (vma, vma->vm_start,
+ virt_to_phys(bus_to_virt(dev->core.mem_base[dev->core.c_bar])) >>
+ PAGE_SHIFT, dev->core.mem_size[dev->core.c_bar],
+ pgprot_noncached(vma->vm_page_prot))) {
+ pr_err("mkopci%d: memory remapping failed\n", dev->core.n_dev);
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static const struct file_operations mkopci_fops = {
+ .owner = THIS_MODULE,
+ .open = mkopci_open,
+ .release = mkopci_release,
+ .unlocked_ioctl = mkopci_ioctl,
+ .mmap = mkopci_mmap,
+};
+/************************ End of File operations ******************************/
+
+/*************************** pci_driver functions *****************************/
+static int mkopci_probe_cdevhelper(struct pci_dev *dev)
+{
+ struct mkopci_device *device =
+ (struct mkopci_device *)pci_get_drvdata(dev);
+ struct device *dev_;
+ int err = 0;
+
+ cdev_init(&device->cdev, &mkopci_fops);
+ device->cdev.owner = THIS_MODULE;
+
+ err = cdev_add(&device->cdev, MKDEV(MAJOR(devp),
+ MINOR(devp) + device->core.n_dev), 1);
+ if (err) {
+ pr_err("mkopci%d: error while cdev_add\n", device->core.n_dev);
+ return err;
+ }
+
+ dev_ =
+ device_create(mkopci_class, NULL,
+ MKDEV(MAJOR(devp), MINOR(devp) + device->core.n_dev),
+ NULL, device->core.name);
+ if (IS_ERR(dev_)) {
+ pr_err("mkopci%d: Unable to create device\n",
+ device->core.n_dev);
+ err = PTR_ERR(dev_);
+ cdev_del(&device->cdev);
+ }
+
+ return err;
+}
+
+static int mkopci_plx9050workaround(struct pci_dev *dev, int plx9050bug)
+{
+ struct mkopci_device *device;
+ int err = 0;
+ phys_addr_t plxphys = ~(phys_addr_t) 0;
+ resource_size_t len;
+ struct resource *res;
+
+ device = (struct mkopci_device *)pci_get_drvdata(dev);
+ if (!device) {
+ err = -ENODEV;
+ goto out;
+ }
+
+ if (plx9050bug & PLX9050BUG_INJECT || plx9050bug & PLX9050BUG_BAR0) {
+ plxphys =
+ (pci_resource_start(dev, 0) & ~0xff) +
+ ((plx9050bug & PLX9050BUG_INJECT) ? 0x80 : 0x0);
+ if (plx9050bug & PLX9050BUG_INJECT) {
+ pr_info
+ ("mkopci%d: [PLX9050 bug injection] mem start = 0x%"
+ PFMT "\n", device->core.n_dev, plxphys);
+ dev->resource[0].start |= 0x80;
+ dev->resource[0].end =
+ dev->resource[0].start + 0x80 - 1;
+ err = pci_request_region(dev, 0, device->core.name);
+ if (err) {
+ pr_err("failed to request region 0");
+ goto out;
+ }
+ } else {
+ pr_info
+ ("mkopci%d: [PLX9050 bug workaround] mem start = 0x%"
+ PFMT "\n", device->core.n_dev, plxphys);
+ res = &dev->resource[0];
+ res->start &= ~0xff;
+ res->end = res->start + 0x80 - 1;
+ err = pci_request_region(dev, 0, device->core.name);
+ if (err) {
+ err =
+ allocate_resource(dev->resource[0].parent,
+ res, 0x80,
+ res->parent->start,
+ res->parent->end - 0x80,
+ 0x100, NULL, NULL);
+ if (err)
+ err =
+ allocate_resource(&iomem_resource,
+ res, 0x80,
+ iomem_resource.
+ start,
+ iomem_resource.
+ end - 0x80, 0x100,
+ NULL, NULL);
+ if (err) {
+ pr_err("failed to allocate region");
+ goto out;
+ }
+ err =
+ pci_request_region(dev, 0,
+ device->core.name);
+ if (err) {
+ pr_err("failed to allocate region");
+ goto out;
+ }
+ }
+ }
+ len = pci_resource_len(dev, 0);
+ pci_write_config_dword(dev, PCI_BASE_ADDRESS_0,
+ dev->resource[0].start);
+
+ device->core.mem_base[0] = dev->resource[0].start;
+ device->core.mem_size[0] = len;
+ pr_info("mkopci%d: device->core.mem_size[0] = %d\n",
+ device->core.n_dev, device->core.mem_size[0]);
+ }
+
+ if (plx9050bug & PLX9050BUG_INJECT)
+ goto out;
+
+ if (plx9050bug & PLX9050BUG_BAR1) {
+ plxphys = pci_resource_start(dev, 1);
+ len = pci_resource_len(dev, 1);
+ plxphys = plxphys & (PAGE_MASK | 0xf00);
+ pci_write_config_dword(dev, PCI_BASE_ADDRESS_1, plxphys);
+ dev->resource[1].start = plxphys;
+ dev->resource[1].end = dev->resource[1].start + len - 1;
+
+ if (pci_request_region(dev, 1, device->core.name)) {
+ err = -EFAULT;
+ if (plx9050bug & PLX9050BUG_BAR0) {
+ iounmap((void *)device->core.lin_base[0]);
+ device->core.lin_base[0] = 0;
+ pci_release_region(dev, 0);
+ }
+ goto out;
+ }
+
+ if (v > 0)
+ pr_info
+ ("mkopci%d: BAR%d = 0x%lx I/O region [PLX9050 bug workaround]\n",
+ device->core.n_dev, 1,
+ (unsigned long)pci_resource_start(dev, 1));
+ }
+out:
+ return err;
+}
+
+static int mkopci_probe_helper(struct pci_dev *dev)
+{
+ int reg = 0, n = 0, err = 0, plx9050bug = 0;
+ struct mkopci_device *device;
+
+ device = (struct mkopci_device *)pci_get_drvdata(dev);
+ if (!device)
+ return -ENODEV;
+
+ if (dev->device == MKO_DEVICE1 || plx9050bug_quirk == 2 ||
+ plx9050bug_quirk == 1) {
+ if (plx9050bug_quirk == 2) {
+ err = mkopci_plx9050workaround(dev,
+ plx9050bug |
+ PLX9050BUG_INJECT);
+ if (err)
+ return err;
+ }
+
+ if (pci_resource_start(dev, 0) & 0x80)
+ plx9050bug = PLX9050BUG_BAR0;
+ if (pci_resource_start(dev, 1) & 0x80)
+ plx9050bug |= PLX9050BUG_BAR1;
+
+ if (plx9050bug && plx9050bug_quirk == 1) {
+ err = mkopci_plx9050workaround(dev, plx9050bug);
+ if (err)
+ return err;
+ }
+ }
+
+ for (; reg < MAX_MEM_WIN + 1; reg++) {
+ if (pci_resource_flags(dev, reg) & IORESOURCE_MEM) {
+ device->core.mem_base[n] = pci_resource_start(dev, reg);
+ if (!device->core.mem_base[n]) {
+ pr_err("mkopci%d: Unable to get BAR%d\n",
+ device->core.n_dev, n);
+ err = -EFAULT;
+ goto fail;
+ }
+
+ device->core.mem_size[n] = pci_resource_len(dev, reg);
+ if (!device->core.mem_size[n]) {
+ pr_err
+ ("mkopci%d: Unable to get size of BAR%d\n",
+ device->core.n_dev, n);
+ err = -EFAULT;
+ goto fail;
+ }
+
+ if ((reg != 0) || !(plx9050bug & PLX9050BUG_BAR0)) {
+ err = pci_request_region(dev, reg,
+ device->core.name);
+ if (err) {
+ pr_err
+ ("mkopci%d: couldn't request region 0x%x with size 0x%x\n",
+ device->core.n_dev,
+ (unsigned int)device->core.mem_base[n],
+ (unsigned int)device->core.mem_size[n]);
+
+ iounmap((void *)
+ device->core.lin_base[n]);
+ goto fail;
+ }
+ }
+
+ device->core.lin_base[n] =
+ (unsigned long)ioremap_nocache(device->core.
+ mem_base[n],
+ device->core.
+ mem_size[n]);
+ if (!device->core.lin_base[n]) {
+ pr_err
+ ("mkopci%d: Unable to remap BAR%d[0x%lx:0x%lx]\n",
+ device->core.n_dev, n,
+ device->core.mem_base[n],
+ device->core.mem_base[n] +
+ device->core.mem_size[n] - 1);
+ err = -EFAULT;
+ goto fail;
+ }
+ if (v > 0)
+ pr_info
+ ("mkopci%d: BAR%d = 0x%08lx, linear = 0x%08lx, len = 0x%x\n",
+ device->core.n_dev, reg,
+ device->core.mem_base[n],
+ device->core.lin_base[n],
+ device->core.mem_size[n]);
+ n++;
+ } else {
+ if (v > 0)
+ pr_info("mkopci%d: BAR%d = 0x%lx I/O region\n",
+ device->core.n_dev, reg,
+ (unsigned long)pci_resource_start(dev,
+ reg));
+ err = pci_request_region(dev, reg, device->core.name);
+ if (err) {
+ pr_err("mkopci%d: couldn't request region %d\n",
+ device->core.n_dev, reg);
+ goto fail;
+ }
+ }
+ }
+ device->core.mem_windows_nr = n;
+
+ device->core.ltype =
+ (*((unsigned short *)(device->core.lin_base[4]) + 3)) & 0xFF;
+ device->core.irq = dev->irq;
+ if (v > 0)
+ pr_info("mkopci%d: irq = %d\n", device->core.n_dev,
+ device->core.irq);
+ device->core.irq_requested = 0;
+ device->core.c_bar = -1;
+
+ err = mkopci_probe_cdevhelper(dev);
+ if (err) {
+ plx9050bug = 0;
+ goto fail2;
+ }
+ atomic_inc(&devices_nr);
+ goto out;
+
+fail:
+ reg--;
+ n--;
+fail2:
+ for (; reg >= 0; reg--) {
+ if (plx9050bug && reg < 2)
+ break;
+ if (pci_resource_flags(dev, reg) & IORESOURCE_MEM) {
+ iounmap((void *)device->core.lin_base[n]);
+ device->core.lin_base[n--] = 0;
+ }
+ pci_release_region(dev, reg);
+ }
+
+out:
+ return err;
+}
+
+static int mkopci_probe(struct pci_dev *dev,
+ const struct pci_device_id __unused * id)
+{
+ int n_prdev = 0, err = 0;
+ int n;
+ struct mkopci_device *device, *mdev = NULL;
+ struct list_head *it;
+
+ if (atomic_read(&devices_nr) == (1 << 8 * sizeof(unsigned char)) - 1)
+ return -EBUSY;
+
+ err = pci_enable_device(dev);
+ if (err) {
+ pr_err("mkopci: error = %d while enabling PCI device\n", err);
+ return err;
+ }
+
+ device = kmem_cache_zalloc(mkopci_device_cache, GFP_KERNEL);
+ if (!device) {
+ pr_err("mkopci: Unable to allocate memory\n");
+ pci_disable_device(dev);
+ return -ENOMEM;
+ }
+
+ down_write(&devices_sem);
+ if (!list_empty(&devices)) {
+ list_for_each(it, &devices) {
+ mdev = list_entry(it, struct mkopci_device, list);
+ if (n_prdev < mdev->core.n_dev) {
+ if (n_prdev)
+ device->core.n_dev = n_prdev - 1;
+ else
+ device->core.n_dev = n_prdev;
+ list_add(&device->list, mdev->list.prev);
+ break;
+ }
+ n_prdev++;
+ }
+ if (n_prdev >= mdev->core.n_dev) {
+ device->core.n_dev = atomic_read(&devices_nr);
+ list_add_tail(&device->list, &devices);
+ }
+ } else
+ list_add_tail(&device->list, &devices);
+ up_write(&devices_sem);
+
+ pci_set_drvdata(dev, device);
+ device->pci_dev = dev;
+ for (n = 0; n < omited_nr; n++) {
+ if (omited[n] ==
+ mko_pci_addr(dev->bus->number, PCI_SLOT(dev->devfn),
+ PCI_FUNC(dev->devfn), 0)) {
+ pci_disable_device(dev);
+ device->omited = 1;
+ }
+ }
+
+ device->core.vendor_id = dev->vendor;
+ device->core.device_id = dev->device;
+ device->core.subs_vendor_id = dev->subsystem_vendor;
+ device->core.subs_id = dev->subsystem_device;
+ device->core.instance =
+ mko_pci_addr(dev->bus->number, PCI_SLOT(dev->devfn),
+ PCI_FUNC(dev->devfn), 0);
+ sprintf(device->core.name, "mkopci%d", device->core.n_dev);
+
+ init_waitqueue_head(&device->wq);
+ init_rwsem(&device->rwsem);
+
+ down_write(&device->rwsem);
+ if (device->omited)
+ goto out;
+
+ err = mkopci_probe_helper(dev);
+ if (err) {
+ pci_disable_device(dev);
+ list_del(&device->list);
+ kmem_cache_free(mkopci_device_cache, device);
+ }
+
+out:
+ up_write(&device->rwsem);
+ return err;
+}
+
+static void mkopci_remove_helper(struct pci_dev *dev)
+{
+ int reg, n = 0;
+ unsigned char rom_base_reg = dev->rom_base_reg;
+ struct mkopci_device *device =
+ (struct mkopci_device *)pci_get_drvdata(dev);
+
+ rom_base_reg = dev->rom_base_reg / 8;
+ for (reg = 0; reg < DEVICE_COUNT_RESOURCE; reg++) {
+ if (reg == rom_base_reg)
+ continue;
+ if (pci_resource_flags(dev, reg) & IORESOURCE_MEM) {
+ if (v > 1)
+ pr_info("mkopci%d: unmapping BAR%d (0x%lx)\n",
+ device->core.n_dev, reg,
+ device->core.lin_base[n]);
+ iounmap((void *)device->core.lin_base[n]);
+ device->core.lin_base[n++] = 0;
+ }
+
+ pci_release_region(dev, reg);
+ }
+
+ mkopci_free_irq(device);
+ pci_disable_device(dev);
+ device_destroy(mkopci_class,
+ MKDEV(MAJOR(devp), MINOR(devp) + device->core.n_dev));
+ cdev_del(&device->cdev);
+ atomic_dec(&devices_nr);
+}
+
+static void mkopci_remove(struct pci_dev *dev)
+{
+ struct mkopci_device *device =
+ (struct mkopci_device *)pci_get_drvdata(dev);
+
+ down_write(&device->rwsem);
+ if (!device->omited)
+ mkopci_remove_helper(dev);
+ list_del(&device->list);
+ up_write(&device->rwsem);
+ kmem_cache_free(mkopci_device_cache, device);
+ if (v > 0)
+ pr_info("mkopci%d: removed\n", device->core.n_dev);
+}
+
+#ifdef CONFIG_PM
+static int mkopci_suspend(struct device __unused * device)
+{
+ return -ENOSYS;
+}
+
+static int mkopci_resume(struct device __unused * device)
+{
+ return 0;
+}
+#else
+#define mkopci_suspend NULL
+#define mkopci_resume NULL
+#endif
+
+/*********************** End of pci_driver functions **************************/
+
+/*************************** proc entries functions ***************************/
+static struct proc_dir_entry *mkopci_proc_file, *mkopci_proc_dir,
+ *mkopci_proc_core;
+
+static int mkopci_proc_show(struct seq_file *m, void __unused * vp)
+{
+ struct mkopci_device *mko_dev;
+
+ down_read(&devices_sem);
+ if (!atomic_read(&devices_nr))
+ seq_puts(m, "no devices detected\n");
+ else
+ seq_printf(m, "%d device(s) detected\n",
+ atomic_read(&devices_nr));
+
+ seq_printf(m, "plx9050bug_quirk = %d\n", plx9050bug_quirk);
+ seq_printf(m, "verbosity level = %d\n", v);
+
+ list_for_each_entry(mko_dev, &devices, list) {
+ down_read(&mko_dev->rwsem);
+ seq_printf(m,
+ "\ndevice\t\t\t/dev/%s\n"
+ "vendor_id =\t\t0x%04x\n"
+ "device_id =\t\t0x%04x\n"
+ "subs_vendor_id =\t0x%04x\n"
+ "subs_id =\t\t0x%04x\n"
+ "ltype =\t\t\t%d\n"
+ "instance =\t\t%02x:%02x.%x (0x%x)\n"
+ "process =\t\t%d\n"
+ "mem_base[0] =\t\t0x%08lx (linear = 0x%lx, size 0x%x)\n"
+ "mem_base[1] =\t\t0x%08lx (linear = 0x%lx, size 0x%x)\n"
+ "mem_base[2] =\t\t0x%08lx (linear = 0x%lx, size 0x%x)\n"
+ "mem_base[3] =\t\t0x%08lx (linear = 0x%lx, size 0x%x)\n"
+ "mem_base[4] =\t\t0x%08lx (linear = 0x%lx, size 0x%x)\n"
+ "irq_n =\t\t\t%d\n"
+ "irq_requested =\t\t%d\n",
+ mko_dev->omited ? "(OMITED)" : mko_dev->core.name,
+ mko_dev->core.vendor_id, mko_dev->core.device_id,
+ mko_dev->core.subs_vendor_id, mko_dev->core.subs_id,
+ mko_dev->core.ltype, mko_dev->core.instance >> 16,
+ mko_dev->core.instance >> 11 & 0x001f,
+ (mko_dev->core.instance >> 8) & 0x7,
+ mko_dev->core.instance, mko_dev->core.process,
+ mko_dev->core.mem_base[0], mko_dev->core.lin_base[0],
+ mko_dev->core.mem_size[0], mko_dev->core.mem_base[1],
+ mko_dev->core.lin_base[1], mko_dev->core.mem_size[1],
+ mko_dev->core.mem_base[2], mko_dev->core.lin_base[2],
+ mko_dev->core.mem_size[2], mko_dev->core.mem_base[3],
+ mko_dev->core.lin_base[3], mko_dev->core.mem_size[3],
+ mko_dev->core.mem_base[4], mko_dev->core.lin_base[4],
+ mko_dev->core.mem_size[4], mko_dev->core.irq,
+ mko_dev->core.irq_requested);
+ up_read(&mko_dev->rwsem);
+ }
+ up_read(&devices_sem);
+
+ return 0;
+}
+
+static int proc_text_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, mkopci_proc_show, inode->i_cdev);
+}
+
+static const struct file_operations mkopci_proc_fops = {
+ .owner = THIS_MODULE,
+ .open = proc_text_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static ssize_t mkopci_proc_core_read(struct file __unused * file,
+ char __user *buffer, size_t count,
+ loff_t *offset)
+{
+ const int MAX_CORE_LEN =
+ MAX_DEVICES_NR * sizeof(struct mkopci_core) +
+ sizeof(unsigned short);
+ int n, len = 0, ret = 0;
+ struct mkopci_device *d;
+
+ down_read(&devices_sem);
+ n = atomic_read(&devices_nr) * sizeof(struct mkopci_core) +
+ sizeof(unsigned short);
+ if (*offset >= n)
+ goto out;
+
+ if (*offset == 0) {
+ if (put_user((unsigned long)atomic_read(&devices_nr), buffer)) {
+ ret = -ERESTARTSYS;
+ goto out;
+ }
+ len = sizeof(unsigned long);
+ }
+
+ list_for_each_entry(d, &devices, list) {
+ down_read(&d->rwsem);
+ if (d->omited) {
+ up_read(&d->rwsem);
+ continue;
+ }
+ if (copy_to_user
+ (buffer + len, &d->core, sizeof(struct mkopci_core))) {
+ up_read(&d->rwsem);
+ ret = -ERESTARTSYS;
+ goto out;
+ }
+ up_read(&d->rwsem);
+ len += sizeof(struct mkopci_core);
+ }
+
+ if (count > len)
+ if (len < MAX_CORE_LEN) {
+ if (clear_user(buffer + len + 1, MAX_CORE_LEN - len)) {
+ ret = -ERESTARTSYS;
+ goto out;
+ }
+ }
+
+ *offset += len;
+ ret = len;
+
+out:
+ up_read(&devices_sem);
+ return ret;
+}
+
+static int hex2int(const char s[])
+{
+ static const char hexalpha[] = "aAbBcCdDeEfF";
+ unsigned int ret = 0;
+ int i = 0, k;
+ int err = 0;
+ int hexint = 0;
+
+ if (s[i] == '0') {
+ i++;
+ if (s[i] == 'x' || s[i] == 'X')
+ i++;
+ }
+
+ while (!err && s[i] != '\0') {
+ ret = ret << 4;
+ if (s[i] >= '0' && s[i] <= '9')
+ ret = ret + (s[i] - '0');
+ else {
+ for (k = 0; hexint == 0 && hexalpha[k] != '\0'; k++) {
+ if (hexalpha[k] == s[i])
+ hexint = 10 + k / 2;
+ }
+ if (hexint == 0) {
+ err = -EINVAL;
+ break;
+ }
+ ret = ret + hexint;
+ hexint = 0;
+ }
+ i++;
+ }
+
+ if (err)
+ ret = err;
+
+ return ret;
+}
+
+static ssize_t mkopci_proc_core_write(struct file __unused * file,
+ const char __user *buffer, size_t count,
+ loff_t __unused * offset)
+{
+ char buf[8];
+ int ret = 0, instance = 0, nodev = 1;
+ struct mkopci_device *d;
+ struct pci_dev *dev;
+
+ if (copy_from_user(buf, buffer, min(sizeof(buf), count))) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ if (count > sizeof(buf)) {
+ ret = -EINVAL;
+ goto out;
+ } else
+ buf[count - 1] = 0;
+
+ if (!strncmp(buf, "0x", 2) || !strncmp(buf, "0X", 2))
+ instance = hex2int(buf);
+ else if ((strlen(buf) == 7) && (buf[2] == ':') && (buf[5] == '.')) {
+ int a, b, c;
+
+ buf[2] = 0;
+ buf[5] = 0;
+ a = hex2int(buf);
+ b = hex2int(buf + 3);
+ c = hex2int(buf + 6);
+ buf[2] = ':';
+ buf[5] = '.';
+
+ instance = mko_pci_addr(a, b, c, 0);
+ } else
+ instance = -EINVAL;
+
+ if (instance == -EINVAL) {
+ pr_err("mkopci: inappropriate PCI address '%s'\n", buf);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ down_write(&devices_sem);
+ list_for_each_entry(d, &devices, list) {
+ down_write(&d->rwsem);
+ if (d->core.instance != instance) {
+ up_write(&d->rwsem);
+ continue;
+ }
+ nodev = 0;
+ if (d->core.process || d->backdoor) {
+ if (v > 0) {
+ if (d->core.process)
+ pr_info
+ ("mkopci%d: device is handled by process [%d]\n",
+ d->core.n_dev, d->core.process);
+ else
+ pr_info
+ ("mkopci%d: device is handled by process [%d] in backdoor mode\n",
+ d->core.n_dev, d->backdoor);
+ }
+ ret = -EBUSY;
+ goto out_up_sems;
+ }
+ dev = d->pci_dev;
+ if (d->omited) {
+ ret = pci_enable_device(dev);
+ if (ret) {
+ pr_err
+ ("mkopci: error = %d while enabling PCI device\n",
+ ret);
+ goto out_up_sems;
+ }
+
+ ret = mkopci_probe_helper(dev);
+ if (ret) {
+ pr_err
+ ("mkopci: error = %d in mkopci_probe_helper\n",
+ ret);
+ goto out_ph_fail;
+ }
+ d->omited = 0;
+ } else {
+ mkopci_remove_helper(dev);
+ d->omited = 1;
+ }
+ up_write(&d->rwsem);
+ break;
+ }
+ up_write(&devices_sem);
+
+ if (nodev) {
+ pr_info("mkopci: no device with address %s was found\n", buf);
+ ret = -ENODEV;
+ goto out;
+ }
+ ret = min(sizeof(buf), count);
+ goto out;
+
+out_ph_fail:
+ pci_disable_device(dev);
+out_up_sems:
+ up_write(&d->rwsem);
+ up_write(&devices_sem);
+out:
+ return ret;
+}
+
+static const struct file_operations mko_core_proc_fops = {
+ .owner = THIS_MODULE,
+ .read = mkopci_proc_core_read,
+ .write = mkopci_proc_core_write,
+};
+
+static int mkopci_create_proc_entry(void)
+{
+ mkopci_proc_dir = proc_mkdir("mkopci", NULL);
+ if (mkopci_proc_dir == NULL) {
+ remove_proc_entry("mkopci", NULL);
+ pr_err("mkopci: could not initialize /proc/mkopci/\n");
+ return -ENOMEM;
+ }
+
+ mkopci_proc_file =
+ proc_create("devices", S_IFREG | S_IRUGO, mkopci_proc_dir,
+ &mkopci_proc_fops);
+ if (mkopci_proc_file == NULL) {
+ remove_proc_entry("mkopci", NULL);
+ pr_err("mkopci: could not initialize /proc/mkopci/devices\n");
+ return -ENOMEM;
+ }
+ mkopci_proc_core =
+ proc_create("core", S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP,
+ mkopci_proc_dir, &mko_core_proc_fops);
+ if (mkopci_proc_core == NULL) {
+ remove_proc_entry("devices", mkopci_proc_dir);
+ remove_proc_entry("mkopci", NULL);
+ pr_err("mkopci: could not initialize /proc/mkopci/core\n");
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+/************************* End of proc entries functions **********************/
+
+static SIMPLE_DEV_PM_OPS(mkopci_pm_ops, mkopci_suspend, mkopci_resume);
+
+static struct pci_driver mkopci_driver = {
+ .name = "mkopci",
+ .id_table = ids,
+ .probe = mkopci_probe,
+ .remove = mkopci_remove,
+ .driver.pm = &mkopci_pm_ops,
+};
+
+static int __init mkopci_init(void)
+{
+ int ret = 0;
+ struct mkopci_device *d;
+
+ if (v < 0 || v > 3 || omited_nr > MAX_DEVICES_NR) {
+ pr_err("mkopci: inappropriate parameters\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ mkopci_device_cache =
+ kmem_cache_create("mkopci_dev_cache", sizeof(struct mkopci_device),
+ 0, 0, 0);
+
+ if (!mkopci_device_cache) {
+ pr_err("mkopci: Unable to allocate memory\n");
+ ret = -ENOMEM;
+ goto out;
+ }
+ init_rwsem(&devices_sem);
+
+ mkopci_class = class_create(THIS_MODULE, "mkopci");
+ if ((IS_ERR(mkopci_class))) {
+ pr_err("mkopci: error creating class\n");
+ ret = PTR_ERR(mkopci_class);
+ goto destroy_cache;
+ }
+
+ ret = alloc_chrdev_region(&devp, 0, MAX_DEVICES_NR, "mkopci");
+ if (ret) {
+ pr_err("mkopci: failed to allocate chrdev region\n");
+ goto destoy_class;
+ }
+
+ if (v > 0)
+ pr_info("mkopci: major number = %d\n", MAJOR(devp));
+
+ ret = pci_register_driver(&mkopci_driver);
+ if (ret < 0) {
+ pr_err("mkopci: error (%d) while pci_register_driver\n", ret);
+ goto free_chr_reg;
+ }
+
+ ret = mkopci_create_proc_entry();
+ if (ret) {
+ pr_err("mkopci: error creating proc entries\n");
+ goto unreg_drv;
+ }
+ pr_info("mkopci: driver loaded\n");
+ goto out;
+
+unreg_drv:
+ pci_unregister_driver(&mkopci_driver);
+free_chr_reg:
+ unregister_chrdev_region(devp, MAX_DEVICES_NR);
+destoy_class:
+ class_destroy(mkopci_class);
+ while (!list_empty(&devices)) {
+ d = list_entry(devices.next, struct mkopci_device, list);
+ list_del(devices.next);
+ kmem_cache_free(mkopci_device_cache, d);
+ }
+destroy_cache:
+ kmem_cache_destroy(mkopci_device_cache);
+
+out:
+ return ret;
+}
+
+static void __exit mkopci_exit(void)
+{
+ struct mkopci_device *d;
+
+ remove_proc_entry("core", mkopci_proc_dir);
+ remove_proc_entry("devices", mkopci_proc_dir);
+ remove_proc_entry("mkopci", NULL);
+
+ pci_unregister_driver(&mkopci_driver);
+ unregister_chrdev_region(devp, MAX_DEVICES_NR);
+ class_destroy(mkopci_class);
+ while (!list_empty(&devices)) {
+ d = list_entry(devices.next, struct mkopci_device, list);
+ list_del(devices.next);
+ kmem_cache_free(mkopci_device_cache, d);
+ }
+ kmem_cache_destroy(mkopci_device_cache);
+
+ pr_info("mkopci: driver unloaded\n");
+}
+
+MODULE_DESCRIPTION("MKO PCI card driver");
+MODULE_AUTHOR("Sergej Bauer");
+MODULE_LICENSE("GPL v2");
+
+module_init(mkopci_init);
+module_exit(mkopci_exit);
+
diff --git a/include/misc/mkopci.h b/include/misc/mkopci.h
new file mode 100644
index 0000000..fdc5270
--- /dev/null
+++ b/include/misc/mkopci.h
@@ -0,0 +1,81 @@
+#ifndef MKOPCI_H
+#define MKOPCI_H
+
+#define MKO_IOC_MAGIC 'X'
+
+#define MAX_MEM_WIN 5
+#define MAX_DEVICES_NR 16
+
+#if !defined(__KERNEL__)
+#define MaxDeviceCount MAX_DEVICES_NR
+typedef struct {
+ unsigned short VendorId;
+ unsigned short DeviceId;
+ unsigned short SubsystemVendorId;
+ unsigned short SubsystemId;
+ unsigned int InstanceId;
+ unsigned int ProcessId;
+ unsigned int LType;
+ unsigned short NumMemWindows;
+ unsigned long MemBase[MAX_MEM_WIN];
+ unsigned long LinBase[MAX_MEM_WIN];
+ unsigned int MemSize[MAX_MEM_WIN];
+ unsigned short IRQ;
+ int handle;
+ char name[sizeof("mkopci00") + 1];
+ unsigned char irq_requested;
+ int c_bar;
+ unsigned char n_dev;
+} mkopcilnx_device_info_t;
+
+typedef struct {
+ unsigned short DeviceCount;
+ mkopcilnx_device_info_t DeviceInfo[MaxDeviceCount];
+} mkopcilnx_device_table_t;
+#else
+struct mkopci_core {
+ unsigned short vendor_id;
+ unsigned short device_id;
+ unsigned short subs_vendor_id;
+ unsigned short subs_id;
+ unsigned int instance;
+ unsigned int process;
+ unsigned int ltype;
+ unsigned short mem_windows_nr;
+ unsigned long mem_base[MAX_MEM_WIN];
+ unsigned long lin_base[MAX_MEM_WIN];
+ unsigned int mem_size[MAX_MEM_WIN];
+ unsigned short irq;
+ int handle;
+ char name[sizeof("mkopci00") + 1];
+ unsigned char irq_requested;
+ int c_bar;
+ unsigned char n_dev;
+};
+
+/* kernel's structure of the driver */
+struct mkopci_device {
+ struct mkopci_core core;
+ struct rw_semaphore rwsem;
+ wait_queue_head_t wq;
+ struct cdev cdev;
+ unsigned char omited;
+ unsigned int backdoor;
+ struct pci_dev *pci_dev;
+ struct list_head list;
+};
+#endif
+
+#define MKOPCI_IOCTL_GET_VERSION _IOR(MKO_IOC_MAGIC, 1, int)
+#define MKOPCI_IOCTL_GET_BOARDS_COUNT _IOR(MKO_IOC_MAGIC, 2, int)
+#define MKOPCI_IOCTL_GET_DEVICE_TABLE _IOR(MKO_IOC_MAGIC, 3, struct mkopci_core)
+#define MKOPCI_IOCTL_ATTACH_IRQ _IO(MKO_IOC_MAGIC, 4)
+#define MKOPCI_IOCTL_WAIT_IRQ _IO(MKO_IOC_MAGIC, 5)
+#define MKOPCI_IOCTL_DETACH_IRQ _IO(MKO_IOC_MAGIC, 6)
+#define MKOPCI_IOCTL_REQUEST_BAR _IOW(MKO_IOC_MAGIC, 7, int)
+#define MKOPCI_IOCTL_CWPID _IO(MKO_IOC_MAGIC, 8)
+
+#define MKO_IOC_MAXNR 8
+
+#endif
+
Signed-off-by: Sergej Bauer <sergej.bauer@xxxxxxxxx>