From tglx@linutronix.de Thu Jul 20 15:33:29 2006 Date: Fri, 21 Jul 2006 00:33:11 +0200 Message-ID: <874pxb52l4.wl%tglx@linutronix.de> From: Thomas Gleixner To: Greg KH Subject: UIO subsystem From: Thomas Gleixner This allows userspace PCI drivers to be written in a sane manner. From: Thomas Gleixner Signed-off-by: Greg Kroah-Hartman --- Documentation/DocBook/kernel-api.tmpl | 5 drivers/Kconfig | 2 drivers/Makefile | 1 drivers/uio/Kconfig | 15 + drivers/uio/Makefile | 4 drivers/uio/uio_base.c | 438 ++++++++++++++++++++++++++++++++++ drivers/uio/uio_dev.c | 366 ++++++++++++++++++++++++++++ drivers/uio/uio_dummy.c | 268 ++++++++++++++++++++ include/linux/uio_driver.h | 106 ++++++++ 9 files changed, 1205 insertions(+) --- gregkh-2.6.orig/Documentation/DocBook/kernel-api.tmpl +++ gregkh-2.6/Documentation/DocBook/kernel-api.tmpl @@ -393,6 +393,11 @@ X!Edrivers/pnp/system.c !Edrivers/pnp/manager.c !Edrivers/pnp/support.c + Industrial IO device driver +!Edrivers/uio/uio_base.c +!Edrivers/uio/uio_dev.c +!Iinclude/linux/uio.h + --- gregkh-2.6.orig/drivers/Kconfig +++ gregkh-2.6/drivers/Kconfig @@ -78,4 +78,6 @@ source "drivers/rtc/Kconfig" source "drivers/dma/Kconfig" +source "drivers/uio/Kconfig" + endmenu --- gregkh-2.6.orig/drivers/Makefile +++ gregkh-2.6/drivers/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_SCSI) += scsi/ obj-$(CONFIG_ATA) += ata/ obj-$(CONFIG_FUSION) += message/ obj-$(CONFIG_IEEE1394) += ieee1394/ +obj-$(CONFIG_UIO) += uio/ obj-y += cdrom/ obj-$(CONFIG_MTD) += mtd/ obj-$(CONFIG_SPI) += spi/ --- /dev/null +++ gregkh-2.6/drivers/uio/Kconfig @@ -0,0 +1,15 @@ +menu "Userspace I/O" +config UIO + tristate "Userspace I/O drivers" + help + Enable this to allow the userspace driver core code to be + built. This code allows userspace programs easy access to + kernel interrupts and memory locations, allowing some drivers + to be written in userspace. Note that a small kernel driver + is also required for interrupt handling to work properly. + +#config UIO_DUMMY +# tristate "Industrial IO dummy driver" +# help +# example/dummy driver for industrial IO +endmenu --- /dev/null +++ gregkh-2.6/drivers/uio/Makefile @@ -0,0 +1,4 @@ +obj-$(CONFIG_UIO) += uio.o +obj-$(CONFIG_UIO_DUMMY) += uio_dummy.o + +uio-objs := uio_base.o uio_dev.o --- /dev/null +++ gregkh-2.6/drivers/uio/uio_base.c @@ -0,0 +1,438 @@ +/* + * driver/uio/uio_base.c + * + * Copyright(C) 2005, Benedikt Spranger + * Copyright(C) 2005, Thomas Gleixner + * + * Industrial IO + * + * Base Functions + * + * Licensed under the GPLv2 only. + */ + +#define DEBUG + +#include +#include +#include +#include +#include +#include +#include + +/* + * The uio class. + */ + +static struct class *uio_class; + +static const char version[] = "0.00"; +static struct list_head uio_devices = LIST_HEAD_INIT(uio_devices); +static struct rw_semaphore uio_list_sem; + +static const char *uio_dev_ext[] = { + "", + "_in", + "_out", + "_event" +}; + +struct uio_class_dev { + struct list_head list; + int dev_nr; + struct class_device *cdev [4]; + dev_t dev; +}; + +extern struct file_operations uio_event_fops; + +/* + * attributes + */ +static ssize_t show_uio_version(struct class *cd, char *buf) +{ + sprintf(buf, "%s\n", version); + return strlen(buf) + 1; +} +static CLASS_ATTR(version, S_IRUGO, show_uio_version, NULL); + +static ssize_t show_addr(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = to_uio_device(dev); + return sprintf(buf, "0x%lx\n", idev->physaddr); +} +static DEVICE_ATTR(physaddr, S_IRUGO, show_addr, NULL); + +static ssize_t show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = to_uio_device(dev); + return sprintf(buf, "%s\n", idev->name); +} +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static ssize_t show_read_offset(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = to_uio_device(dev); + return sprintf(buf, "0x%lx\n", idev->read_offset); +} +static DEVICE_ATTR(read_offset, S_IRUGO, show_read_offset, NULL); + +static ssize_t show_size(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = to_uio_device(dev); + return sprintf(buf, "0x%lx\n", idev->size); +} +static DEVICE_ATTR(size, S_IRUGO, show_size, NULL); + +static ssize_t show_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = to_uio_device(dev); + return sprintf(buf, "%s\n", idev->version); +} +static DEVICE_ATTR(version, S_IRUGO, show_version, NULL); + +static ssize_t show_write_offset(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = to_uio_device(dev); + return sprintf(buf, "0x%lx\n", idev->write_offset); +} +static DEVICE_ATTR(write_offset, S_IRUGO, show_write_offset, NULL); + +static ssize_t show_listener(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = to_uio_device(dev); + return sprintf(buf, "%ld\n", idev->event_listener); +} +static DEVICE_ATTR(listener, S_IRUGO, show_listener, NULL); + +static ssize_t show_event(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = to_uio_device(dev); + return sprintf(buf, "%d\n", atomic_read(&idev->event)); +} +static DEVICE_ATTR(event, S_IRUGO, show_event, NULL); + +/* + * device functions + */ +static void uio_dev_release(struct device *dev) +{ + pr_debug("%s\n", __FUNCTION__); +} + +static void uio_dev_create(dev_t dev, struct uio_device *idev) +{ + int ret; + pr_debug("%s\n", __FUNCTION__); + + cdev_init(&idev->cdev, idev->fops); + idev->cdev.owner = THIS_MODULE; + idev->cdev.ops = idev->fops; + ret = cdev_add(&idev->cdev, dev, 3); + if (ret) + printk(KERN_ERR "%s: cdev_add failed (%d)\n", __FUNCTION__, ret); + + cdev_init(&idev->event_cdev, &uio_event_fops); + idev->event_cdev.owner = THIS_MODULE; + idev->event_cdev.ops = &uio_event_fops; + ret = cdev_add(&idev->event_cdev, dev + 3, 1); + if (ret) + printk(KERN_ERR "%s: cdev_add failed (%d)\n", __FUNCTION__, ret); +} + +static void uio_dev_remove(struct uio_device *idev) +{ + pr_debug("%s\n", __FUNCTION__); + + cdev_del(&idev->cdev); + cdev_del(&idev->event_cdev); +} + +static void uio_dev_add_attributes(struct uio_device *idev) +{ + int ret; + + pr_debug("%s\n", __FUNCTION__); + + ret = device_create_file(&idev->dev, &dev_attr_name); + ret |= device_create_file(&idev->dev, &dev_attr_version); + ret |= device_create_file(&idev->dev, &dev_attr_listener); + ret |= device_create_file(&idev->dev, &dev_attr_event); + if (idev->physaddr) + ret |= device_create_file(&idev->dev, &dev_attr_physaddr); + if (idev->size) + ret |= device_create_file(&idev->dev, &dev_attr_size); + if (idev->read_offset >= 0) + ret |= device_create_file(&idev->dev, &dev_attr_read_offset); + if (idev->write_offset >= 0) + ret |= device_create_file(&idev->dev, &dev_attr_write_offset); + if (ret) + pr_debug("%s: error creating sysfs files\n", __FUNCTION__); +} + +static void uio_dev_del_attributes(struct uio_device *idev) +{ + pr_debug("%s\n", __FUNCTION__); + + device_remove_file(&idev->dev, &dev_attr_name); + device_remove_file(&idev->dev, &dev_attr_version); + device_remove_file(&idev->dev, &dev_attr_listener); + device_remove_file(&idev->dev, &dev_attr_event); + if (idev->physaddr) + device_remove_file(&idev->dev, &dev_attr_physaddr); + if (idev->size) + device_remove_file(&idev->dev, &dev_attr_size); + if (idev->read_offset >= 0) + device_remove_file(&idev->dev, &dev_attr_read_offset); + if (idev->write_offset >= 0) + device_remove_file(&idev->dev, &dev_attr_write_offset); +} + +/* + * polling mode + */ + +static void uio_do_poll(unsigned long data) +{ + struct uio_device *idev = (struct uio_device *)data; + + uio_interrupt(-1, idev); + mod_timer(&idev->poll_timer, jiffies + idev->freq); +} + + +#ifdef CONFIG_HIGH_RES_TIMERS +/* + * one shot mode + */ + +int uio_do_oneshot(void *data) +{ + struct uio_device *idev = (struct uio_device *)data; +// struct timespec next; + + atomic_inc(&idev->running); + daemonize("uio_oneshot"); + set_user_nice(current, -10); + current->flags |= PF_NOFREEZE; +//FIXME: Bene: new API +# if 0 + while (!atomic_read(&idev->terminate)) { + while (ktime_cmp_val(idev->next_event, == , KTIME_ZERO)) + schedule(); + ktime_to_timespec(&next, idev->next_event); + ktimer_nanosleep(&next, NULL, KTIMER_ABS); + ktime_set_scalar(idev->next_event, KTIME_ZERO); + uio_interrupt(-2, idev); + } +# endif + atomic_dec(&idev->running); + return 0; +} +#endif +/* + * class device functions + */ + +static int uio_assign_free_devnr(void) +{ + int dev_nr = 0; + struct uio_class_dev *entry; + + pr_debug("%s\n", __FUNCTION__); + + if (list_empty(&uio_devices)) + goto out; + + list_for_each_entry(entry, &uio_devices, list) { + if ((entry->dev_nr > dev_nr + 1) && (dev_nr != 0)) + break; + dev_nr = entry->dev_nr; + } +out: + return dev_nr + 1; +} + +/** + * uio_register_device - register a new industrial IO device + * + * @parent: parent device + * @idev: device capabilities + * + * returns UIO device driver identifier + */ + +int __devinit uio_register_device(struct device *parent, + struct uio_device *idev) { + int i, ret = -ENOMEM; + struct uio_class_dev *cdev; + + pr_debug("%s\n", __FUNCTION__); + + if (!idev->name || !idev->version) + return -EINVAL; + + cdev = kmalloc(sizeof(struct uio_class_dev), GFP_KERNEL); + if (!cdev) + goto out; + + if (parent) { + idev->dev.bus = parent->bus; + idev->dev.parent = parent; + } + idev->dev.release = uio_dev_release; + + down_write(&uio_list_sem); + cdev->dev_nr = uio_assign_free_devnr(); + + sprintf(idev->dev.bus_id, "uio%d", cdev->dev_nr); + if ((ret = device_register(&idev->dev))) + goto no_dev; + + alloc_chrdev_region(&cdev->dev, 0, 4, idev->dev.bus_id); + uio_dev_create(cdev->dev, idev); + uio_dev_add_attributes(idev); + list_add_tail(&cdev->list, &uio_devices); + + for (i = 0; i < 4; i++) { + cdev->cdev [i] = class_device_create(uio_class, NULL, + cdev->dev + i, + &idev->dev, "uio%d%s", + cdev->dev_nr, uio_dev_ext [i]); + if (IS_ERR(cdev->cdev [i])) { + printk(KERN_ERR "UIO: device register_failed\n"); + device_unregister(&idev->dev); + uio_dev_remove(idev); + unregister_chrdev_region(cdev->dev, 4); + ret = PTR_ERR(cdev->cdev [i]); + kfree(cdev); + break; + } + } + +no_dev: + up_write(&uio_list_sem); + + idev->dev.driver_data = (void *)cdev->dev_nr; + atomic_set(&idev->running, 0); + atomic_set(&idev->terminate, 0); + + if (idev->irq && idev->handler) { + init_waitqueue_head(&idev->wait); + atomic_set(&idev->event, 0); + + switch (idev->irq) { +#ifdef CONFIG_HIGH_RES_TIMERS + case -2: /* one shot mode */ + kernel_thread(uio_do_oneshot, idev, CLONE_KERNEL); + break; +#endif + case -1: /* cyclic */ + init_timer(&idev->poll_timer); + idev->poll_timer.data = (unsigned long)idev; + idev->poll_timer.function = uio_do_poll; + mod_timer(&idev->poll_timer, jiffies + idev->freq); + break; + + default: + /* Make this configurable !!! */ + ret = request_irq(idev->irq, uio_interrupt, + IRQF_SHARED, idev->name, idev); + } + } + + if (!ret) + ret = cdev->dev_nr; +out: + return ret; +} +EXPORT_SYMBOL_GPL(uio_register_device); + +/** + * uio_unregister_device - unregister a industrial IO device + * @dev_nr: UIO device driver identifier + * + * returns 0 on success + */ + +int __devexit uio_unregister_device(int dev_nr) +{ + int i, ret = -ENODEV; + struct uio_class_dev *entry, *tmp; + struct uio_device *idev; + + pr_debug("%s (%d)\n", __FUNCTION__, dev_nr); + + down_write(&uio_list_sem); + + list_for_each_entry_safe(entry, tmp, &uio_devices, list) { + if (entry->dev_nr == dev_nr) { + + idev = to_uio_device(entry->cdev [0]->dev); + + atomic_inc(&idev->terminate); + while (atomic_read(&idev->running)) + schedule(); + + if (idev->irq == -1) + del_timer_sync(&idev->poll_timer); + + uio_dev_remove(idev); + unregister_chrdev_region(entry->dev, 4); + uio_dev_del_attributes(idev); + for (i = 0; i < 4; i++) { + class_device_unregister(entry->cdev [i]); + } + device_unregister(&idev->dev); + list_del(&entry->list); + + kfree(entry); + break; + } + } + + up_write(&uio_list_sem); + return ret; +} +EXPORT_SYMBOL_GPL(uio_unregister_device); + +/* + * module + */ + +static int __init uio_init(void) +{ + int ret; + + init_rwsem(&uio_list_sem); + uio_class = class_create(THIS_MODULE, "uio"); + + if (IS_ERR(uio_class)) + return PTR_ERR(uio_class); + + ret = class_create_file(uio_class, &class_attr_version); + if (ret) + class_destroy(uio_class); + return ret; +} + +void __exit uio_exit(void) +{ + class_remove_file(uio_class, &class_attr_version); + class_destroy(uio_class); +} + +module_init(uio_init) +module_exit(uio_exit) + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Benedikt Spranger"); --- /dev/null +++ gregkh-2.6/drivers/uio/uio_dev.c @@ -0,0 +1,366 @@ +/* + * drivers/uio/uio_dev.c + * + * Copyright(C) 2005, Benedikt Spranger + * Copyright(C) 2005, Thomas Gleixner + * + * Industrial IO + * + * Character Device related functions + * + * Licensed under the GPLv2 only. + */ + +#define DEBUG 1 + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * uio_lseek - UIO framework lseek implementation + * + */ +loff_t uio_lseek(struct file *filep, loff_t offset, int orig) +{ + struct uio_device *idev = filep->private_data; + + pr_debug("%s\n", __FUNCTION__); + + switch (orig) { + case 0: + filep->f_pos = offset; + break; + case 1: + filep->f_pos += offset; + break; + case 2: + filep->f_pos = idev->size + offset; + break; + } + + return (filep->f_pos >= idev->size) ? -EINVAL : filep->f_pos; +} +EXPORT_SYMBOL_GPL(uio_lseek); + +/** + * uio_read - UIO framework read implementation + * + */ +ssize_t uio_read(struct file *filep, char __user *buf, + size_t count, loff_t *ppos) +{ + unsigned int minor = iminor(filep->f_dentry->d_inode); + struct uio_device *idev = filep->private_data; + int offset; + + pr_debug("%s\n", __FUNCTION__); + + switch (minor) { + case 0: if (filep->f_pos + count >= idev->size) + count = idev->size - filep->f_pos; + offset = 0; + break; + case 1: if (filep->f_pos + count >= idev->read_len) + count = idev->read_len - filep->f_pos; + offset = idev->read_offset; + break; + case 2: if (filep->f_pos + count >= idev->write_len) + count = idev->write_len - filep->f_pos; + offset = idev->write_offset; + break; + default: return -EFAULT; + } + + if (count < 0) + return 0; + + if (!idev->virtaddr) { + /* IO Ports */ + size_t i; + + for (i = 0; i < count; i++) + if (__put_user(inb(idev->physaddr + offset + i), + buf + i) < 0) + return -EFAULT; + } else { + /* IO Mem */ + if (copy_to_user(buf, idev->virtaddr + offset + + filep->f_pos, count)) + return -EFAULT; + } + + *ppos += count; + return count; +} +EXPORT_SYMBOL_GPL(uio_read); + +/** + * uio_write - UIO framework write implementation + * + */ +ssize_t uio_write(struct file *filep, const char __user * buf, size_t count, + loff_t *ppos) +{ + unsigned int minor = iminor(filep->f_dentry->d_inode); + struct uio_device *idev = filep->private_data; + int offset; + + pr_debug("%s\n", __FUNCTION__); + + switch (minor) { + case 0: if (filep->f_pos + count >= idev->size) + count = idev->size - filep->f_pos; + offset = 0; + break; + case 1: if (filep->f_pos + count >= idev->read_len) + count = idev->read_len - filep->f_pos; + offset = idev->read_offset; + break; + case 2: if (filep->f_pos + count >= idev->write_len) + count = idev->write_len - filep->f_pos; + offset = idev->write_offset; + break; + default: return -EFAULT; + } + + if (count < 0) + return 0; + + if (!idev->virtaddr) { + /* IO Ports */ + size_t i; + char c; + + for (i = 0; i < count; i++) { + if (__get_user(c, buf + i)) + return -EFAULT; + outb(c, idev->physaddr + offset + i); + } + } else { + /* IO Mem */ + if (copy_from_user(idev->virtaddr + offset + filep->f_pos, + buf, count)) + return -EFAULT; + } + + *ppos += count; + return count; +} +EXPORT_SYMBOL_GPL(uio_write); + +/** + * uio_open - UIO framework open implementation + * + */ +int uio_open(struct inode *inode, struct file *filep) +{ + struct uio_device *idev; + + pr_debug("%s\n", __FUNCTION__); + + idev = container_of(inode->i_cdev, struct uio_device, cdev); + filep->private_data = idev; + + return 0; +} +EXPORT_SYMBOL_GPL(uio_open); + +/** + * uio_mmap - UIO framework mmap implementation + * + */ +int uio_mmap(struct file *filep, struct vm_area_struct *vma) +{ + unsigned int minor = iminor(filep->f_dentry->d_inode); + struct uio_device *idev = filep->private_data; + unsigned long size = (unsigned long)(vma->vm_end - vma->vm_start); + + pr_debug("%s\n", __FUNCTION__); + + switch (minor) { + case 0: if (size > idev->size) + return -EINVAL; + vma->vm_pgoff = idev->physaddr; + break; + case 1: if (size > idev->read_len) + return -EINVAL; + vma->vm_pgoff = idev->physaddr + idev->read_offset; + break; + case 2: if (size > idev->write_len) + return -EINVAL; + vma->vm_pgoff = idev->physaddr + idev->write_offset; + break; + default: return -EINVAL; + } + + vma->vm_flags |= VM_LOCKED | VM_IO; + + return remap_pfn_range(vma, + vma->vm_start, + idev->physaddr >> PAGE_SHIFT, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); +} +EXPORT_SYMBOL_GPL(uio_mmap); + +/** + * uio_release - UIO framework release implementation + * + */ +int uio_release(struct inode *inode, struct file *filep) +{ + pr_debug("%s\n", __FUNCTION__); + + return 0; +} +EXPORT_SYMBOL_GPL(uio_release); + + +/* + * event interface + */ +int uio_event_open(struct inode *inode, struct file *filep) +{ + struct uio_device *idev; + + pr_debug("%s\n", __FUNCTION__); + + idev = container_of(inode->i_cdev, struct uio_device, event_cdev); + filep->private_data = idev; + idev->event_listener++; + + return 0; +} + +int uio_event_release(struct inode *inode, struct file *filep) +{ + struct uio_device *idev = filep->private_data; + + pr_debug("%s\n", __FUNCTION__); + + idev->event_listener--; + if (filep->f_flags & FASYNC) { + if (idev->fops->fasync) + idev->fops->fasync(-1, filep, 0); + } + + return 0; +} + +irqreturn_t uio_interrupt(int irq, void *dev_id) +{ + struct uio_device *idev = dev_id; + + if (idev->handler) + idev->handler(irq, dev_id); + + if (idev->event_listener) { + atomic_inc(&idev->event); + wake_up_interruptible(&idev->wait); + kill_fasync(&idev->async_queue, SIGIO, POLL_IN); + } + + return IRQ_HANDLED; +} + +static int uio_event_fasync(int fd, struct file *filep, int on) +{ + struct uio_device *idev = filep->private_data; + + return fasync_helper(fd, filep, on, &idev->async_queue); +} + +static unsigned int uio_event_poll(struct file *filep, poll_table *wait) +{ + struct uio_device *idev = filep->private_data; + + pr_debug("%s\n", __FUNCTION__); + + if (!idev->irq) + return -EIO; + + poll_wait(filep, &idev->wait, wait); + + return POLLIN | POLLRDNORM; +} + +static ssize_t uio_event_read(struct file *filep, char __user *buf, + size_t count, loff_t *ppos) +{ + struct uio_device *idev = filep->private_data; + DECLARE_WAITQUEUE(wait, current); + ssize_t retval; + int event_count; + + if (!idev->irq) + return -EIO; + + if (count > sizeof(int)) + count = sizeof(int); + + add_wait_queue(&idev->wait, &wait); + + do { + __set_current_state(TASK_INTERRUPTIBLE); + + event_count = atomic_read(&idev->event); + if (event_count) + break; + + if (filep->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto out; + } + + if (signal_pending(current)) { + retval = -ERESTARTSYS; + goto out; + } + schedule(); + } while (1); + + atomic_dec(&idev->event); + + if (copy_to_user(buf, &event_count, count)) + retval = -EFAULT; + else + retval = sizeof(int); + +out: + current->state = TASK_RUNNING; + remove_wait_queue(&idev->wait, &wait); + + return retval; +} + +ssize_t uio_event_write(struct file *filep, const char __user * buf, + size_t count, loff_t *ppos) +{ + struct uio_device *idev = filep->private_data; + + pr_debug("%s\n", __FUNCTION__); + + if (!idev->event_write) + return -EFAULT; + + return idev->event_write(filep, buf, count, ppos); +} + +struct file_operations uio_event_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = uio_event_read, + .write = uio_event_write, + .poll = uio_event_poll, + .open = uio_event_open, + .release = uio_event_release, + .fasync = uio_event_fasync, +}; --- /dev/null +++ gregkh-2.6/drivers/uio/uio_dummy.c @@ -0,0 +1,268 @@ +/* + * UIO dummy driver + * + * GPLv2 only. + */ + +#define DEBUG 1 + +#include +#include +#include + +#include + +#define MEMSIZE 8192 + +static irqreturn_t uio_dummy_handler(int irq, void *dev_id); + +static struct file_operations uio_dummy_fops = { + .open = uio_open, + .release = uio_release, + .read = uio_read, + .write = uio_write, + .llseek = uio_lseek, + .mmap = uio_mmap, +}; + +static struct uio_device uio_dummy_idev = { + .name = "UIO_dummy", + .version = "0.00", + .fops = &uio_dummy_fops, + .size = MEMSIZE, + .read_offset = 0, + .read_len = MEMSIZE/2, + .write_offset = MEMSIZE/2, + .write_len = MEMSIZE/2, + .irq = -1, /* cyclic timer */ + .freq = HZ, + .irq_type = UIO_EVENT, + .handler = uio_dummy_handler, +}; + +struct uio_dummy_sig { + long pid; + int it_sigev_notify; /* notify word of sigevent struct */ + int it_sigev_signo; /* signo word of sigevent struct */ + sigval_t it_sigev_value; /* value word of sigevent struct */ + struct task_struct *it_process; /* process to send signal to */ + struct sigqueue *sigq; /* signal queue entry. */ +}; + +static struct uio_dummy_sig uio_dummy_signal; +static long uio_dummy_count; + +static ssize_t show_count(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%ld\n", uio_dummy_count); +} + +static ssize_t store_count(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + uio_dummy_count = simple_strtol(buf, NULL, 10); + return count; +} +static DEVICE_ATTR(count, S_IRUGO|S_IWUSR|S_IWGRP, show_count, store_count); + +static ssize_t show_freq(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%ld\n", uio_dummy_idev.freq); +} +static ssize_t store_freq(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + uio_dummy_idev.freq = simple_strtol(buf, NULL, 10); + return count; +} +static DEVICE_ATTR(freq, S_IRUGO|S_IWUSR|S_IWGRP, show_freq, store_freq); + +static ssize_t show_sig_pid(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%ld\n", uio_dummy_signal.pid); +} + +static ssize_t store_sig_pid(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + uio_dummy_signal.pid = simple_strtol(buf, NULL, 10); + if (uio_dummy_signal.pid == 0) { + if (uio_dummy_signal.it_process) { + put_task_struct(uio_dummy_signal.it_process); + uio_dummy_signal.it_process = NULL; + } + + uio_dummy_signal.pid = 0; + return count; + } + + if (uio_dummy_signal.pid == 1) + goto out; + + uio_dummy_signal.it_process = find_task_by_pid(uio_dummy_signal.pid); + if (uio_dummy_signal.it_process) { + get_task_struct(uio_dummy_signal.it_process); + uio_dummy_signal.it_sigev_notify = SIGEV_SIGNAL; + uio_dummy_signal.it_sigev_signo = SIGALRM; + uio_dummy_signal.it_sigev_value.sival_int = 0; + + return count; + } +out: + uio_dummy_signal.pid = 0; + return -EINVAL; +} + +static DEVICE_ATTR(sig_pid, S_IRUGO|S_IWUSR|S_IWGRP, show_sig_pid, store_sig_pid); + +static int uio_dummy_irqsig(struct uio_dummy_sig *io_sig, int si_private) +{ + int ret; + struct task_struct *leader; + + if (!io_sig->it_process || !io_sig->sigq) + return 0; + + memset(&io_sig->sigq->info, 0, sizeof(siginfo_t)); + io_sig->sigq->info.si_sys_private = si_private; + + /* + * Send signal to a process waiting for an interrupt + */ + io_sig->sigq->info.si_signo = io_sig->it_sigev_signo; + io_sig->sigq->info.si_errno = 0; + io_sig->sigq->info.si_code = SI_TIMER; + io_sig->sigq->info.si_value = io_sig->it_sigev_value; + + ret = send_sigqueue(io_sig->it_sigev_signo, io_sig->sigq, + io_sig->it_process); + + if (likely(ret >= 0)) + return ret; + + io_sig->it_sigev_notify = SIGEV_SIGNAL; + leader = io_sig->it_process->group_leader; + put_task_struct(io_sig->it_process); + io_sig->it_process = leader; + + return send_group_sigqueue(io_sig->it_sigev_signo, io_sig->sigq, + io_sig->it_process); +} + +static irqreturn_t uio_dummy_handler(int irq, void *dev_id) +{ + struct uio_device *idev = dev_id; + uio_dummy_count++; + + *((long *) idev->physaddr) = uio_dummy_count; + uio_dummy_irqsig(&uio_dummy_signal, 0); + + return IRQ_HANDLED; +} + +static int __devinit uio_dummy_probe(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + + pr_debug("%s\n", __FUNCTION__); + + uio_dummy_idev.physaddr = (unsigned long)kmalloc(MEMSIZE, GFP_KERNEL); + if (!uio_dummy_idev.physaddr) + return -ENOMEM; + + pdev->dev.driver_data = (void *)uio_register_device(NULL, &uio_dummy_idev); + + uio_dummy_signal.sigq = sigqueue_alloc(); + + return (pdev->dev.driver_data) ? 0 : -ENODEV; +} + +static int __devexit uio_dummy_remove(struct device *dev) +{ + pr_debug("%s\n", __FUNCTION__); + sigqueue_free(uio_dummy_signal.sigq); + uio_dummy_signal.sigq = NULL; + + return 0; +} + +static void uio_dummy_shutdown(struct device *dev) +{ + pr_debug("%s\n", __FUNCTION__); +} + +struct platform_device *uio_dummy_device; + +static struct device_driver uio_dummy_driver = { + .name = "uio_dummy", + .bus = &platform_bus_type, + .probe = uio_dummy_probe, + .remove = __devexit_p(uio_dummy_remove), + .shutdown = uio_dummy_shutdown, +}; + +/* + * Main initialization/remove routines + */ + +static int __init uio_dummy_init(void) +{ + int ret; + + pr_debug("%s\n", __FUNCTION__); + + uio_dummy_device = platform_device_register_simple("uio_dummy", -1, + NULL, 0); + if (IS_ERR(uio_dummy_device)) { + ret = PTR_ERR(uio_dummy_device); + goto out; + } + + ret = device_create_file(&uio_dummy_device->dev, &dev_attr_count); + if (ret) + goto error_register; + ret = device_create_file(&uio_dummy_device->dev, &dev_attr_freq); + if (ret) + goto error_file_count; + ret = device_create_file(&uio_dummy_device->dev, &dev_attr_sig_pid); + if (ret) + goto error_file_freq; + + ret = driver_register(&uio_dummy_driver); + if (ret) + goto error_file_sig; + + goto out; + +error_file_sig: + device_remove_file(&uio_dummy_device->dev, &dev_attr_sig_pid); +error_file_freq: + device_remove_file(&uio_dummy_device->dev, &dev_attr_freq); +error_file_count: + device_remove_file(&uio_dummy_device->dev, &dev_attr_count); +error_register: + platform_device_unregister(uio_dummy_device); +out: + return ret; +} + +void __exit uio_dummy_exit(void) +{ + pr_debug("%s\n", __FUNCTION__); + + uio_unregister_device((int) uio_dummy_device->dev.driver_data); + kfree((void *) uio_dummy_idev.physaddr); + + platform_device_unregister(uio_dummy_device); + driver_unregister(&uio_dummy_driver); +} + +module_init(uio_dummy_init); +module_exit(uio_dummy_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Benedikt Spranger"); +MODULE_DESCRIPTION("UIO dummy driver"); --- /dev/null +++ gregkh-2.6/include/linux/uio_driver.h @@ -0,0 +1,106 @@ +/* + * include/linux/uio_driver.h + * + * Copyright(C) 2005, Benedikt Spranger + * Copyright(C) 2005, Thomas Gleixner + * + * Userspace IO driver. + * + * Licensed under the GPLv2 only. + */ + +#ifndef _UIO_DRIVER_H_ +#define _UIO_DRIVER_H_ +#include +#include +#include +#include +#include +#include + +enum uio_irq_type { + UIO_EVENT, + UIO_THREADED, + UIO_NODELAY, +}; + +/* + * uio_device - UIO device capabilities + * + * @name: device name + * @version: device driver version + * @physaddr: physical address + * @virtaddr: iomaped address (optional) + * @size: size of IO + * read_offset: offset to inputs + * read_len: input length + * write_offset: offset to outputs + * write_len: output length + * irq: interrupt, -1 for cyclic mode, -2 for oneshot mode + * freq: frequency for cyclic mode + * next_event: next event for oneshot mode + * irq_type: interrupt type + */ + +struct uio_device { + /* internals */ + struct device dev; + struct cdev cdev; + struct cdev event_cdev; + long event_listener; + atomic_t event; + struct timer_list poll_timer; + struct fasync_struct *async_queue; + wait_queue_head_t wait; + atomic_t event_count; + atomic_t waiters; + atomic_t running; + atomic_t terminate; + + /* attributes */ + char *name; + char *version; + unsigned long physaddr; + void *virtaddr; + unsigned long size; + long read_offset; + long read_len; + long write_offset; + long write_len; + long irq; + long freq; + ktime_t next_event; + enum uio_irq_type irq_type; + + /* callbacks */ + irqreturn_t (*handler)(int irq, void *dev_id); + ssize_t (*event_write)(struct file *filep, const char __user * buf, + size_t count, loff_t *ppos); + + /* fops */ + struct file_operations *fops; + + /* driver private */ + void *priv; +}; + +int uio_register_device(struct device *parent, struct uio_device *idev); +int uio_unregister_device(int dev_nr); + +#define to_uio_device(n) container_of(n, struct uio_device, dev) + +/* char device funktions */ + +loff_t uio_lseek(struct file *filep, loff_t offset, int orig); +ssize_t uio_read(struct file *filep, char __user *buf, size_t count, loff_t *ppos); +ssize_t uio_write(struct file *filep, const char __user * buf, size_t count, + loff_t *ppos); +int uio_open(struct inode *inode, struct file *filep); +int uio_mmap(struct file *filep, struct vm_area_struct *vma); +int uio_release(struct inode *inode, struct file *filep); + +/* event */ +irqreturn_t uio_interrupt(int irq, void *dev_id); +extern struct file_operations uio_event_fops; + +#endif /* _LINUX_UIO_DRIVER_H_ */