diff options
Diffstat (limited to 'drivers/uio')
-rw-r--r-- | drivers/uio/Kconfig | 13 | ||||
-rw-r--r-- | drivers/uio/Makefile | 1 | ||||
-rw-r--r-- | drivers/uio/uio.c | 40 | ||||
-rw-r--r-- | drivers/uio/uio_pdrv.c | 2 | ||||
-rw-r--r-- | drivers/uio/uio_sercos3.c | 243 |
5 files changed, 287 insertions, 12 deletions
diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig index 4190be64917..04b954cfce7 100644 --- a/drivers/uio/Kconfig +++ b/drivers/uio/Kconfig @@ -58,4 +58,17 @@ config UIO_SMX If you compile this as a module, it will be called uio_smx. +config UIO_SERCOS3 + tristate "Automata Sercos III PCI card driver" + default n + help + Userspace I/O interface for the Sercos III PCI card from + Automata GmbH. The userspace part of this driver will be + available for download from the Automata GmbH web site. + + Automata GmbH: http://www.automataweb.com + Sercos III interface: http://www.sercos.com + + If you compile this as a module, it will be called uio_sercos3. + endif diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile index 8667bbdef90..e6955814985 100644 --- a/drivers/uio/Makefile +++ b/drivers/uio/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_UIO_CIF) += uio_cif.o obj-$(CONFIG_UIO_PDRV) += uio_pdrv.o obj-$(CONFIG_UIO_PDRV_GENIRQ) += uio_pdrv_genirq.o obj-$(CONFIG_UIO_SMX) += uio_smx.o +obj-$(CONFIG_UIO_SERCOS3) += uio_sercos3.o diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c index 3a6934bf713..f9b4647255a 100644 --- a/drivers/uio/uio.c +++ b/drivers/uio/uio.c @@ -47,6 +47,9 @@ static struct uio_class { struct class *class; } *uio_class; +/* Protect idr accesses */ +static DEFINE_MUTEX(minor_lock); + /* * attributes */ @@ -67,6 +70,11 @@ static ssize_t map_size_show(struct uio_mem *mem, char *buf) return sprintf(buf, "0x%lx\n", mem->size); } +static ssize_t map_offset_show(struct uio_mem *mem, char *buf) +{ + return sprintf(buf, "0x%lx\n", mem->addr & ~PAGE_MASK); +} + struct uio_sysfs_entry { struct attribute attr; ssize_t (*show)(struct uio_mem *, char *); @@ -77,10 +85,13 @@ static struct uio_sysfs_entry addr_attribute = __ATTR(addr, S_IRUGO, map_addr_show, NULL); static struct uio_sysfs_entry size_attribute = __ATTR(size, S_IRUGO, map_size_show, NULL); +static struct uio_sysfs_entry offset_attribute = + __ATTR(offset, S_IRUGO, map_offset_show, NULL); static struct attribute *attrs[] = { &addr_attribute.attr, &size_attribute.attr, + &offset_attribute.attr, NULL, /* need to NULL terminate the list of attributes */ }; @@ -231,7 +242,6 @@ static void uio_dev_del_attributes(struct uio_device *idev) static int uio_get_minor(struct uio_device *idev) { - static DEFINE_MUTEX(minor_lock); int retval = -ENOMEM; int id; @@ -253,7 +263,9 @@ exit: static void uio_free_minor(struct uio_device *idev) { + mutex_lock(&minor_lock); idr_remove(&uio_idr, idev->minor); + mutex_unlock(&minor_lock); } /** @@ -297,8 +309,9 @@ static int uio_open(struct inode *inode, struct file *filep) struct uio_listener *listener; int ret = 0; - lock_kernel(); + mutex_lock(&minor_lock); idev = idr_find(&uio_idr, iminor(inode)); + mutex_unlock(&minor_lock); if (!idev) { ret = -ENODEV; goto out; @@ -324,18 +337,15 @@ static int uio_open(struct inode *inode, struct file *filep) if (ret) goto err_infoopen; } - unlock_kernel(); return 0; err_infoopen: - kfree(listener); -err_alloc_listener: +err_alloc_listener: module_put(idev->owner); out: - unlock_kernel(); return ret; } @@ -482,15 +492,23 @@ static int uio_vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf) { struct uio_device *idev = vma->vm_private_data; struct page *page; + unsigned long offset; int mi = uio_find_mem_index(vma); if (mi < 0) return VM_FAULT_SIGBUS; + /* + * We need to subtract mi because userspace uses offset = N*PAGE_SIZE + * to use mem[N]. + */ + offset = (vmf->pgoff - mi) << PAGE_SHIFT; + if (idev->info->mem[mi].memtype == UIO_MEM_LOGICAL) - page = virt_to_page(idev->info->mem[mi].addr); + page = virt_to_page(idev->info->mem[mi].addr + offset); else - page = vmalloc_to_page((void*)idev->info->mem[mi].addr); + page = vmalloc_to_page((void *)idev->info->mem[mi].addr + + offset); get_page(page); vmf->page = page; return 0; @@ -682,9 +700,9 @@ int __uio_register_device(struct module *owner, if (ret) goto err_get_minor; - idev->dev = device_create_drvdata(uio_class->class, parent, - MKDEV(uio_major, idev->minor), idev, - "uio%d", idev->minor); + idev->dev = device_create(uio_class->class, parent, + MKDEV(uio_major, idev->minor), idev, + "uio%d", idev->minor); if (IS_ERR(idev->dev)) { printk(KERN_ERR "UIO: device register failed\n"); ret = PTR_ERR(idev->dev); diff --git a/drivers/uio/uio_pdrv.c b/drivers/uio/uio_pdrv.c index 0b4ef39cd85..d494ce9288c 100644 --- a/drivers/uio/uio_pdrv.c +++ b/drivers/uio/uio_pdrv.c @@ -12,7 +12,7 @@ #include <linux/uio_driver.h> #include <linux/stringify.h> -#define DRIVER_NAME "uio" +#define DRIVER_NAME "uio_pdrv" struct uio_platdata { struct uio_info *uioinfo; diff --git a/drivers/uio/uio_sercos3.c b/drivers/uio/uio_sercos3.c new file mode 100644 index 00000000000..a6d1b2bc47f --- /dev/null +++ b/drivers/uio/uio_sercos3.c @@ -0,0 +1,243 @@ +/* sercos3: UIO driver for the Automata Sercos III PCI card + + Copyright (C) 2008 Linutronix GmbH + Author: John Ogness <john.ogness@linutronix.de> + + This is a straight-forward UIO driver, where interrupts are disabled + by the interrupt handler and re-enabled via a write to the UIO device + by the userspace-part. + + The only part that may seem odd is the use of a logical OR when + storing and restoring enabled interrupts. This is done because the + userspace-part could directly modify the Interrupt Enable Register + at any time. To reduce possible conflicts, the kernel driver uses + a logical OR to make more controlled changes (rather than blindly + overwriting previous values). + + Race conditions exist if the userspace-part directly modifies the + Interrupt Enable Register while in operation. The consequences are + that certain interrupts would fail to be enabled or disabled. For + this reason, the userspace-part should only directly modify the + Interrupt Enable Register at the beginning (to get things going). + The userspace-part can safely disable interrupts at any time using + a write to the UIO device. +*/ + +#include <linux/device.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/uio_driver.h> +#include <linux/io.h> + +/* ID's for SERCOS III PCI card (PLX 9030) */ +#define SERCOS_SUB_VENDOR_ID 0x1971 +#define SERCOS_SUB_SYSID_3530 0x3530 +#define SERCOS_SUB_SYSID_3535 0x3535 +#define SERCOS_SUB_SYSID_3780 0x3780 + +/* Interrupt Enable Register */ +#define IER0_OFFSET 0x08 + +/* Interrupt Status Register */ +#define ISR0_OFFSET 0x18 + +struct sercos3_priv { + u32 ier0_cache; + spinlock_t ier0_cache_lock; +}; + +/* this function assumes ier0_cache_lock is locked! */ +static void sercos3_disable_interrupts(struct uio_info *info, + struct sercos3_priv *priv) +{ + void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET; + + /* add enabled interrupts to cache */ + priv->ier0_cache |= ioread32(ier0); + + /* disable interrupts */ + iowrite32(0, ier0); +} + +/* this function assumes ier0_cache_lock is locked! */ +static void sercos3_enable_interrupts(struct uio_info *info, + struct sercos3_priv *priv) +{ + void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET; + + /* restore previously enabled interrupts */ + iowrite32(ioread32(ier0) | priv->ier0_cache, ier0); + priv->ier0_cache = 0; +} + +static irqreturn_t sercos3_handler(int irq, struct uio_info *info) +{ + struct sercos3_priv *priv = info->priv; + void __iomem *isr0 = info->mem[3].internal_addr + ISR0_OFFSET; + void __iomem *ier0 = info->mem[3].internal_addr + IER0_OFFSET; + + if (!(ioread32(isr0) & ioread32(ier0))) + return IRQ_NONE; + + spin_lock(&priv->ier0_cache_lock); + sercos3_disable_interrupts(info, priv); + spin_unlock(&priv->ier0_cache_lock); + + return IRQ_HANDLED; +} + +static int sercos3_irqcontrol(struct uio_info *info, s32 irq_on) +{ + struct sercos3_priv *priv = info->priv; + + spin_lock_irq(&priv->ier0_cache_lock); + if (irq_on) + sercos3_enable_interrupts(info, priv); + else + sercos3_disable_interrupts(info, priv); + spin_unlock_irq(&priv->ier0_cache_lock); + + return 0; +} + +static int sercos3_setup_iomem(struct pci_dev *dev, struct uio_info *info, + int n, int pci_bar) +{ + info->mem[n].addr = pci_resource_start(dev, pci_bar); + if (!info->mem[n].addr) + return -1; + info->mem[n].internal_addr = ioremap(pci_resource_start(dev, pci_bar), + pci_resource_len(dev, pci_bar)); + if (!info->mem[n].internal_addr) + return -1; + info->mem[n].size = pci_resource_len(dev, pci_bar); + info->mem[n].memtype = UIO_MEM_PHYS; + return 0; +} + +static int __devinit sercos3_pci_probe(struct pci_dev *dev, + const struct pci_device_id *id) +{ + struct uio_info *info; + struct sercos3_priv *priv; + int i; + + info = kzalloc(sizeof(struct uio_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + priv = kzalloc(sizeof(struct sercos3_priv), GFP_KERNEL); + if (!priv) + goto out_free; + + if (pci_enable_device(dev)) + goto out_free_priv; + + if (pci_request_regions(dev, "sercos3")) + goto out_disable; + + /* we only need PCI BAR's 0, 2, 3, 4, 5 */ + if (sercos3_setup_iomem(dev, info, 0, 0)) + goto out_unmap; + if (sercos3_setup_iomem(dev, info, 1, 2)) + goto out_unmap; + if (sercos3_setup_iomem(dev, info, 2, 3)) + goto out_unmap; + if (sercos3_setup_iomem(dev, info, 3, 4)) + goto out_unmap; + if (sercos3_setup_iomem(dev, info, 4, 5)) + goto out_unmap; + + spin_lock_init(&priv->ier0_cache_lock); + info->priv = priv; + info->name = "Sercos_III_PCI"; + info->version = "0.0.1"; + info->irq = dev->irq; + info->irq_flags = IRQF_DISABLED | IRQF_SHARED; + info->handler = sercos3_handler; + info->irqcontrol = sercos3_irqcontrol; + + pci_set_drvdata(dev, info); + + if (uio_register_device(&dev->dev, info)) + goto out_unmap; + + return 0; + +out_unmap: + for (i = 0; i < 5; i++) { + if (info->mem[i].internal_addr) + iounmap(info->mem[i].internal_addr); + } + pci_release_regions(dev); +out_disable: + pci_disable_device(dev); +out_free_priv: + kfree(priv); +out_free: + kfree(info); + return -ENODEV; +} + +static void sercos3_pci_remove(struct pci_dev *dev) +{ + struct uio_info *info = pci_get_drvdata(dev); + int i; + + uio_unregister_device(info); + pci_release_regions(dev); + pci_disable_device(dev); + pci_set_drvdata(dev, NULL); + for (i = 0; i < 5; i++) { + if (info->mem[i].internal_addr) + iounmap(info->mem[i].internal_addr); + } + kfree(info->priv); + kfree(info); +} + +static struct pci_device_id sercos3_pci_ids[] __devinitdata = { + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9030, + .subvendor = SERCOS_SUB_VENDOR_ID, + .subdevice = SERCOS_SUB_SYSID_3530, + }, + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9030, + .subvendor = SERCOS_SUB_VENDOR_ID, + .subdevice = SERCOS_SUB_SYSID_3535, + }, + { + .vendor = PCI_VENDOR_ID_PLX, + .device = PCI_DEVICE_ID_PLX_9030, + .subvendor = SERCOS_SUB_VENDOR_ID, + .subdevice = SERCOS_SUB_SYSID_3780, + }, + { 0, } +}; + +static struct pci_driver sercos3_pci_driver = { + .name = "sercos3", + .id_table = sercos3_pci_ids, + .probe = sercos3_pci_probe, + .remove = sercos3_pci_remove, +}; + +static int __init sercos3_init_module(void) +{ + return pci_register_driver(&sercos3_pci_driver); +} + +static void __exit sercos3_exit_module(void) +{ + pci_unregister_driver(&sercos3_pci_driver); +} + +module_init(sercos3_init_module); +module_exit(sercos3_exit_module); + +MODULE_DESCRIPTION("UIO driver for the Automata Sercos III PCI card"); +MODULE_AUTHOR("John Ogness <john.ogness@linutronix.de>"); +MODULE_LICENSE("GPL v2"); |