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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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