From hjk@linutronix.de Thu Dec 7 01:58:37 2006 From: Hans J. Koch To: Greg KH Cc: tglx@linutronix.de, Benedikt Spranger Subject: UIO: Add the User IO core code Date: Thu, 7 Dec 2006 10:58:29 +0100 From: Hans J. Koch This interface allows the ability to write the majority of a driver in userspace with only a very small shell of a driver in the kernel itself. It uses a char device and sysfs to interact with a userspace process to process interrupts and control memory accesses. See the docbook documentation for more details on how to use this interface. From: Hans J. Koch Cc: Thomas Gleixner Cc: Benedikt Spranger Signed-off-by: Greg Kroah-Hartman --- drivers/Kconfig | 1 drivers/Makefile | 1 drivers/uio/Kconfig | 11 drivers/uio/Makefile | 1 drivers/uio/uio.c | 611 +++++++++++++++++++++++++++++++++++++++++++++ include/linux/uio_driver.h | 71 +++++ 6 files changed, 696 insertions(+) --- gregkh-2.6.orig/drivers/Kconfig +++ gregkh-2.6/drivers/Kconfig @@ -82,4 +82,5 @@ source "drivers/dma/Kconfig" source "drivers/kvm/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,11 @@ +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. + +endmenu --- /dev/null +++ gregkh-2.6/drivers/uio/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_UIO) += uio.o --- /dev/null +++ gregkh-2.6/drivers/uio/uio.c @@ -0,0 +1,611 @@ +/* + * driver/uio/uio.c + * + * Copyright(C) 2005, Benedikt Spranger + * Copyright(C) 2005, Thomas Gleixner + * Copyright(C) 2006, Hans J. Koch + * Copyright(C) 2006, Greg Kroah-Hartman + * + * Userspace IO + * + * Base Functions + * + * Licensed under the GPLv2 only. + */ + +#include +#include +#include +#include +#include +#include +#include + +#define UIO_MAX_DEVICES 255 + +struct uio_device { + struct module *owner; + struct device *dev; + int minor; + long event_listener; + atomic_t event; + struct fasync_struct *async_queue; + wait_queue_head_t wait; + int vma_count; + struct uio_info *info; +}; + +static int uio_major; +static DEFINE_IDR(uio_idr); +static struct file_operations uio_fops; + +/* UIO class infrastructure */ +static struct uio_class { + struct kref kref; + struct class *class; +} *uio_class; + +/* + * attributes + */ +static ssize_t show_addr(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = dev_get_drvdata(dev); + if (idev) + return sprintf(buf, "0x%lx\n", idev->info->addr); + else + return -ENODEV; +} +static DEVICE_ATTR(addr, S_IRUGO, show_addr, NULL); + +static ssize_t show_name(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = dev_get_drvdata(dev); + if (idev) + return sprintf(buf, "%s\n", idev->info->name); + else + return -ENODEV; +} +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +static ssize_t show_size(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = dev_get_drvdata(dev); + if (idev) + return sprintf(buf, "0x%lx\n", idev->info->size); + else + return -ENODEV; +} +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 = dev_get_drvdata(dev); + if (idev) + return sprintf(buf, "%s\n", idev->info->version); + else + return -ENODEV; +} +static DEVICE_ATTR(version, S_IRUGO, show_version, NULL); + +static ssize_t show_listener(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct uio_device *idev = dev_get_drvdata(dev); + if (idev) + return sprintf(buf, "%ld\n", idev->event_listener); + else + return -ENODEV; +} +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 = dev_get_drvdata(dev); + if (idev) + return sprintf(buf, "%d\n", atomic_read(&idev->event)); + else + return -ENODEV; +} +static DEVICE_ATTR(event, S_IRUGO, show_event, NULL); + +static struct attribute *uio_attrs[] = { + &dev_attr_name.attr, + &dev_attr_version.attr, + &dev_attr_listener.attr, + &dev_attr_event.attr, + NULL, +}; +static struct attribute_group uio_attr_grp = { + .attrs = uio_attrs, +}; + +/* + * device functions + */ +static int uio_dev_add_attributes(struct uio_device *idev) +{ + int ret; + + ret = sysfs_create_group(&idev->dev->kobj, &uio_attr_grp); + if (ret) + goto err_group; + if (idev->info->addr) { + ret = device_create_file(idev->dev, &dev_attr_addr); + if (ret) + goto err_addr; + } + if (idev->info->size) { + ret = device_create_file(idev->dev, &dev_attr_size); + if (ret) + goto err_size; + } + return 0; + +err_size: + if (idev->info->addr) + device_remove_file(idev->dev, &dev_attr_addr); +err_addr: + sysfs_remove_group(&idev->dev->kobj, &uio_attr_grp); +err_group: + dev_err(idev->dev, "error creating sysfs files (%d)\n", ret); + return ret; +} + +static void uio_dev_del_attributes(struct uio_device *idev) +{ + sysfs_remove_group(&idev->dev->kobj, &uio_attr_grp); + if (idev->info->addr) + device_remove_file(idev->dev, &dev_attr_addr); + if (idev->info->size) + device_remove_file(idev->dev, &dev_attr_size); +} + +static int uio_get_minor(struct uio_device *idev) +{ + static DEFINE_MUTEX(minor_lock); + int retval = -ENOMEM; + int id; + + mutex_lock(&minor_lock); + if (idr_pre_get(&uio_idr, GFP_KERNEL) == 0) + goto exit; + + retval = idr_get_new(&uio_idr, idev, &id); + if (retval < 0) { + if (retval == -EAGAIN) + retval = -ENOMEM; + goto exit; + } + idev->minor = id & MAX_ID_MASK; +exit: + mutex_unlock(&minor_lock); + return retval; +} + +static void uio_free_minor(struct uio_device *idev) +{ + idr_remove(&uio_idr, idev->minor); +} + +/** + * uio_event_notify - trigger an interrupt event + * @idev: UIO device where the event occured + */ +void uio_event_notify(struct uio_device *idev) +{ + if (idev->event_listener) { + atomic_inc(&idev->event); + wake_up_interruptible(&idev->wait); + kill_fasync(&idev->async_queue, SIGIO, POLL_IN); + } +} +EXPORT_SYMBOL_GPL(uio_event_notify); + +/** + * uio_interrupt - hardware interrupt handler + * @irq: IRQ number, can be UIO_IRQ_CYCLIC for cyclic timer + * @dev_id: Pointer to the devices uio_device structure + */ +static irqreturn_t uio_interrupt(int irq, void *dev_id) +{ + struct uio_device *idev = (struct uio_device *)dev_id; + irqreturn_t ret = idev->info->handler(irq, dev_id); + + if (ret == IRQ_HANDLED) + uio_event_notify(idev); + + return ret; +} + +static int uio_open(struct inode *inode, struct file *filep) +{ + struct uio_device *idev; + int ret = 0; + + idev = idr_find(&uio_idr, iminor(inode)); + if (!idev) + return -ENODEV; + + filep->private_data = idev; + idev->event_listener++; + if (idev->info->open) { + if (!try_module_get(idev->owner)) + return -ENODEV; + ret = idev->info->open(idev->info, inode); + module_put(idev->owner); + } + return ret; +} + +static int uio_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 int uio_release(struct inode *inode, struct file *filep) +{ + int ret = 0; + struct uio_device *idev = filep->private_data; + + if (idev->info->release) { + if (!try_module_get(idev->owner)) + return -ENODEV; + ret = idev->info->release(idev->info, inode); + module_put(idev->owner); + } + idev->event_listener--; + if (filep->f_flags & FASYNC) + ret = uio_fasync(-1, filep, 0); + return ret; +} + +static unsigned int uio_poll(struct file *filep, poll_table *wait) +{ + struct uio_device *idev = filep->private_data; + + if (idev->info->irq < 0) + return -EIO; + + poll_wait(filep, &idev->wait, wait); + + return POLLIN | POLLRDNORM; +} + +static ssize_t uio_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->info->irq == UIO_IRQ_NONE) + 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 = count; + +out: + current->state = TASK_RUNNING; + remove_wait_queue(&idev->wait, &wait); + + return retval; +} + +static void uio_vma_open_log(struct vm_area_struct *vma) +{ + struct uio_device *idev = vma->vm_private_data; + idev->vma_count++; +} + +static void uio_vma_close_log(struct vm_area_struct *vma) +{ + struct uio_device *idev = vma->vm_private_data; + idev->vma_count--; +} + +static struct page *uio_vma_nopage_log(struct vm_area_struct *vma, + unsigned long address, int *type) +{ + struct uio_device *idev = vma->vm_private_data; + struct page* page = NOPAGE_SIGBUS; + unsigned long offset; + offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT); + if (offset >= idev->info->size) + return page; + page = virt_to_page(idev->info->addr); + get_page(page); + if (type) + *type = VM_FAULT_MINOR; + return page; +} + +static struct vm_operations_struct uio_log_vm_ops = { + .open = uio_vma_open_log, + .close = uio_vma_close_log, + .nopage = uio_vma_nopage_log, +}; + +static int uio_mmap_physical(struct vm_area_struct *vma) +{ + struct uio_device *idev = vma->vm_private_data; + unsigned long off = vma->vm_pgoff << PAGE_SHIFT; + unsigned long vsize = vma->vm_end - vma->vm_start; + + if (vsize > (idev->info->size - off)) + return -EINVAL; + + vma->vm_flags |= VM_IO | VM_RESERVED; + + return remap_pfn_range(vma, + vma->vm_start, + idev->info->addr + off, + vsize, + vma->vm_page_prot); +} + +static int uio_mmap_logical(struct vm_area_struct *vma) +{ + vma->vm_flags |= VM_RESERVED; + vma->vm_ops = &uio_log_vm_ops; + uio_vma_open_log(vma); + return 0; +} + +static int uio_mmap(struct file *filep, struct vm_area_struct *vma) +{ + struct uio_device *idev = filep->private_data; + int ret = 0; + + if (vma->vm_end < vma->vm_start) + return -EINVAL; + + if ((unsigned long)(vma->vm_end - vma->vm_start) > idev->info->size) + return -EINVAL; + + vma->vm_private_data = filep->private_data; + + if (idev->info->mmap) { + if (!try_module_get(idev->owner)) + return -ENODEV; + ret = idev->info->mmap(idev->info, vma); + module_put(idev->owner); + return ret; + } + + switch (idev->info->memtype) { + case UIO_MEM_PHYS: + return uio_mmap_physical(vma); + case UIO_MEM_LOGICAL: + return uio_mmap_logical(vma); + default: + return -EINVAL; + } +} + +static struct file_operations uio_fops = { + .owner = THIS_MODULE, + .open = uio_open, + .release = uio_release, + .read = uio_read, + .mmap = uio_mmap, + .poll = uio_poll, + .fasync = uio_fasync, +}; + +static int uio_major_init(void) +{ + uio_major = register_chrdev(0, "uio", &uio_fops); + if (uio_major < 0) + return uio_major; + return 0; +} + +static void uio_major_cleanup(void) +{ + unregister_chrdev(uio_major, "uio"); +} + +static int init_uio_class(void) +{ + int ret = 0; + + if (uio_class != NULL) { + kref_get(&uio_class->kref); + goto exit; + } + + /* This is the first time in here, set everything up properly */ + ret = uio_major_init(); + if (ret) + goto exit; + + uio_class = kzalloc(sizeof(*uio_class), GFP_KERNEL); + if (!uio_class) { + ret = -ENOMEM; + goto err_kzalloc; + } + + kref_init(&uio_class->kref); + uio_class->class = class_create(THIS_MODULE, "uio"); + if (IS_ERR(uio_class->class)) { + ret = IS_ERR(uio_class->class); + printk(KERN_ERR "class_create failed for uio\n"); + goto err_class_create; + } + return 0; + +err_class_create: + kfree(uio_class); + uio_class = NULL; +err_kzalloc: + uio_major_cleanup(); +exit: + return ret; +} + +static void release_uio_class(struct kref *kref) +{ + /* Ok, we cheat as we know we only have one uio_class */ + class_destroy(uio_class->class); + kfree(uio_class); + uio_major_cleanup(); + uio_class = NULL; +} + +static void uio_class_destroy(void) +{ + if (uio_class) + kref_put(&uio_class->kref, release_uio_class); +} + +/** + * uio_register_device - register a new userspace IO device + * + * @parent: parent device + * @idev: device capabilities + * + * returns a positive UIO device driver identifier or a negative + * error code. + */ +struct uio_device *__uio_register_device(struct module *owner, + struct device *parent, + struct uio_info *info) +{ + struct uio_device *idev; + int ret = 0; + + if (!parent || !info || !info->name || !info->version) + return ERR_PTR(-EINVAL); + + ret = init_uio_class(); + if (ret) + return ERR_PTR(ret); + + idev = kzalloc(sizeof(*idev), GFP_KERNEL); + if (!idev) { + ret = -ENOMEM; + goto err_kzalloc; + } + + idev->owner = owner; + idev->info = info; + init_waitqueue_head(&idev->wait); + atomic_set(&idev->event, 0); + + ret = uio_get_minor(idev); + if (ret) + goto err_get_minor; + + idev->dev = device_create(uio_class->class, parent, + MKDEV(uio_major, idev->minor), + "uio%d", idev->minor); + if (IS_ERR(idev->dev)) { + printk(KERN_ERR "UIO: device register failed\n"); + ret = PTR_ERR(idev->dev); + goto err_device_create; + } + dev_set_drvdata(idev->dev, idev); + + ret = uio_dev_add_attributes(idev); + if (ret) + goto err_uio_dev_add_attributes; + + if (idev->info->irq >= 0) { + ret = request_irq(idev->info->irq, uio_interrupt, + idev->info->irq_flags, idev->info->name, idev); + if (ret) + goto err_request_irq; + } + + return idev; + +err_request_irq: + uio_dev_del_attributes(idev); +err_uio_dev_add_attributes: + device_destroy(uio_class->class, MKDEV(uio_major, idev->minor)); +err_device_create: + uio_free_minor(idev); +err_get_minor: + kfree(idev); +err_kzalloc: + uio_class_destroy(); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(__uio_register_device); + +/** + * uio_unregister_device - unregister a industrial IO device + * @uiodev: UIO device driver identifier + * + * returns 0 on success + */ +void uio_unregister_device(struct uio_device *idev) +{ + if (!idev) + return; + + uio_free_minor(idev); + + if (idev->info->irq >= 0) + free_irq(idev->info->irq, idev); + + uio_dev_del_attributes(idev); + + dev_set_drvdata(idev->dev, NULL); + device_destroy(uio_class->class, MKDEV(uio_major, idev->minor)); + kfree(idev); + uio_class_destroy(); + + return; +} +EXPORT_SYMBOL_GPL(uio_unregister_device); + +static int __init uio_init(void) +{ + return 0; +} + +void __exit uio_exit(void) +{ +} + +module_init(uio_init) +module_exit(uio_exit) +MODULE_LICENSE("GPL v2"); --- /dev/null +++ gregkh-2.6/include/linux/uio_driver.h @@ -0,0 +1,71 @@ +/* + * include/linux/uio_driver.h + * + * Copyright(C) 2005, Benedikt Spranger + * Copyright(C) 2005, Thomas Gleixner + * Copyright(C) 2006, Hans J. Koch + * Copyright(C) 2006, Greg Kroah-Hartman + * + * Userspace IO driver. + * + * Licensed under the GPLv2 only. + */ + +#ifndef _UIO_DRIVER_H_ +#define _UIO_DRIVER_H_ + +#include +#include +#include + +/** + * uio_info - UIO device capabilities + * @name: device name + * @version: device driver version + * @addr: address of the device's memory + * @size: size of IO + * @memtype: type of memory addr points to + * @irq: interrupt number or UIO_IRQ_CUSTOM + * @irq_flags flags for request_irq() + * @handler: the device's irq handler + * @fops: file operations for uio device + */ +struct uio_info { + struct module *owner; + char *name; + char *version; + unsigned long addr; + unsigned long size; + int memtype; + long irq; + unsigned long irq_flags; + irqreturn_t (*handler)(int irq, void *dev_id); + int (*mmap)(struct uio_info *info, struct vm_area_struct *vma); + int (*open)(struct uio_info *info, struct inode *inode); + int (*release)(struct uio_info *info, struct inode *inode); +}; + +struct uio_device; + +extern struct uio_device * __must_check + __uio_register_device(struct module *owner, + struct device *parent, + struct uio_info *info); +static inline struct uio_device * __must_check + uio_register_device(struct device *parent, struct uio_info *info) +{ + return __uio_register_device(THIS_MODULE, parent, info); +} +extern void uio_unregister_device(struct uio_device *idev); +extern void uio_event_notify(struct uio_device *idev); + +/* defines for uio_device->irq */ +#define UIO_IRQ_CUSTOM -1 +#define UIO_IRQ_NONE -2 + +/* defines for uio_device->memtype */ +#define UIO_MEM_NONE 0 +#define UIO_MEM_PHYS 1 +#define UIO_MEM_LOGICAL 2 + +#endif /* _LINUX_UIO_DRIVER_H_ */