UIO: Add the User IO core code

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 <hjk@linutronix.de>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Benedikt Spranger <b.spranger@linutronix.de>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

diff --git a/drivers/Kconfig b/drivers/Kconfig
index 7916f4b..ae01d86 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -84,4 +84,5 @@
 
 source "drivers/kvm/Kconfig"
 
+source "drivers/uio/Kconfig"
 endmenu
diff --git a/drivers/Makefile b/drivers/Makefile
index 6d9d7fa..c34c8ef 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -40,6 +40,7 @@
 obj-$(CONFIG_FUSION)		+= message/
 obj-$(CONFIG_FIREWIRE)		+= firewire/
 obj-$(CONFIG_IEEE1394)		+= ieee1394/
+obj-$(CONFIG_UIO)		+= uio/
 obj-y				+= cdrom/
 obj-y				+= auxdisplay/
 obj-$(CONFIG_MTD)		+= mtd/
diff --git a/drivers/uio/Kconfig b/drivers/uio/Kconfig
new file mode 100644
index 0000000..6b1a62d
--- /dev/null
+++ b/drivers/uio/Kconfig
@@ -0,0 +1,16 @@
+menu "Userspace I/O"
+	depends on !S390
+
+config UIO
+	tristate "Userspace I/O drivers"
+	default n
+	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.
+
+	  If you don't know what to do here, say N.
+
+endmenu
diff --git a/drivers/uio/Makefile b/drivers/uio/Makefile
new file mode 100644
index 0000000..9b7c830
--- /dev/null
+++ b/drivers/uio/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_UIO) += uio.o
diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c
new file mode 100644
index 0000000..865f32b6
--- /dev/null
+++ b/drivers/uio/uio.c
@@ -0,0 +1,701 @@
+/*
+ * drivers/uio/uio.c
+ *
+ * Copyright(C) 2005, Benedikt Spranger <b.spranger@linutronix.de>
+ * Copyright(C) 2005, Thomas Gleixner <tglx@linutronix.de>
+ * Copyright(C) 2006, Hans J. Koch <hjk@linutronix.de>
+ * Copyright(C) 2006, Greg Kroah-Hartman <greg@kroah.com>
+ *
+ * Userspace IO
+ *
+ * Base Functions
+ *
+ * Licensed under the GPLv2 only.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/poll.h>
+#include <linux/device.h>
+#include <linux/mm.h>
+#include <linux/idr.h>
+#include <linux/string.h>
+#include <linux/kobject.h>
+#include <linux/uio_driver.h>
+
+#define UIO_MAX_DEVICES 255
+
+struct uio_device {
+	struct module		*owner;
+	struct device		*dev;
+	int			minor;
+	atomic_t		event;
+	struct fasync_struct	*async_queue;
+	wait_queue_head_t	wait;
+	int			vma_count;
+	struct uio_info		*info;
+	struct kset 		map_attr_kset;
+};
+
+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 struct attribute attr_addr = {
+	.name  = "addr",
+	.mode  = S_IRUGO,
+};
+
+static struct attribute attr_size = {
+	.name  = "size",
+	.mode  = S_IRUGO,
+};
+
+static struct attribute* map_attrs[] = {
+	&attr_addr, &attr_size, NULL
+};
+
+static ssize_t map_attr_show(struct kobject *kobj, struct attribute *attr,
+			     char *buf)
+{
+	struct uio_mem *mem = container_of(kobj, struct uio_mem, kobj);
+
+	if (strncmp(attr->name,"addr",4) == 0)
+		return sprintf(buf, "0x%lx\n", mem->addr);
+
+	if (strncmp(attr->name,"size",4) == 0)
+		return sprintf(buf, "0x%lx\n", mem->size);
+
+	return -ENODEV;
+}
+
+static void map_attr_release(struct kobject *kobj)
+{
+	/* TODO ??? */
+}
+
+static struct sysfs_ops map_attr_ops = {
+	.show  = map_attr_show,
+};
+
+static struct kobj_type map_attr_type = {
+	.release	= map_attr_release,
+	.sysfs_ops	= &map_attr_ops,
+	.default_attrs	= map_attrs,
+};
+
+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_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_event(struct device *dev,
+			  struct device_attribute *attr, char *buf)
+{
+	struct uio_device *idev = dev_get_drvdata(dev);
+	if (idev)
+		return sprintf(buf, "%u\n",
+				(unsigned int)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_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;
+	int mi;
+	int map_found = 0;
+	struct uio_mem *mem;
+
+	ret = sysfs_create_group(&idev->dev->kobj, &uio_attr_grp);
+	if (ret)
+		goto err_group;
+
+	for (mi = 0; mi < MAX_UIO_MAPS; mi++) {
+		mem = &idev->info->mem[mi];
+		if (mem->size == 0)
+			break;
+		if (!map_found) {
+			map_found = 1;
+			kobject_set_name(&idev->map_attr_kset.kobj,"maps");
+			idev->map_attr_kset.ktype = &map_attr_type;
+			idev->map_attr_kset.kobj.parent = &idev->dev->kobj;
+			ret = kset_register(&idev->map_attr_kset);
+			if (ret)
+				goto err_remove_group;
+		}
+		kobject_init(&mem->kobj);
+		kobject_set_name(&mem->kobj,"map%d",mi);
+		mem->kobj.parent = &idev->map_attr_kset.kobj;
+		mem->kobj.kset = &idev->map_attr_kset;
+		ret = kobject_add(&mem->kobj);
+		if (ret)
+			goto err_remove_maps;
+	}
+
+	return 0;
+
+err_remove_maps:
+	for (mi--; mi>=0; mi--) {
+		mem = &idev->info->mem[mi];
+		kobject_unregister(&mem->kobj);
+	}
+	kset_unregister(&idev->map_attr_kset); /* Needed ? */
+err_remove_group:
+	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)
+{
+	int mi;
+	struct uio_mem *mem;
+	for (mi = 0; mi < MAX_UIO_MAPS; mi++) {
+		mem = &idev->info->mem[mi];
+		if (mem->size == 0)
+			break;
+		kobject_unregister(&mem->kobj);
+	}
+	kset_unregister(&idev->map_attr_kset);
+	sysfs_remove_group(&idev->dev->kobj, &uio_attr_grp);
+}
+
+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
+ * @info: UIO device capabilities
+ */
+void uio_event_notify(struct uio_info *info)
+{
+	struct uio_device *idev = info->uio_dev;
+
+	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, idev->info);
+
+	if (ret == IRQ_HANDLED)
+		uio_event_notify(idev->info);
+
+	return ret;
+}
+
+struct uio_listener {
+	struct uio_device *dev;
+	s32 event_count;
+};
+
+static int uio_open(struct inode *inode, struct file *filep)
+{
+	struct uio_device *idev;
+	struct uio_listener *listener;
+	int ret = 0;
+
+	idev = idr_find(&uio_idr, iminor(inode));
+	if (!idev)
+		return -ENODEV;
+
+	listener = kmalloc(sizeof(*listener), GFP_KERNEL);
+	if (!listener)
+		return -ENOMEM;
+
+	listener->dev = idev;
+	listener->event_count = atomic_read(&idev->event);
+	filep->private_data = listener;
+
+	if (idev->info->open) {
+		if (!try_module_get(idev->owner))
+			return -ENODEV;
+		ret = idev->info->open(idev->info, inode);
+		module_put(idev->owner);
+	}
+
+	if (ret)
+		kfree(listener);
+
+	return ret;
+}
+
+static int uio_fasync(int fd, struct file *filep, int on)
+{
+	struct uio_listener *listener = filep->private_data;
+	struct uio_device *idev = listener->dev;
+
+	return fasync_helper(fd, filep, on, &idev->async_queue);
+}
+
+static int uio_release(struct inode *inode, struct file *filep)
+{
+	int ret = 0;
+	struct uio_listener *listener = filep->private_data;
+	struct uio_device *idev = listener->dev;
+
+	if (idev->info->release) {
+		if (!try_module_get(idev->owner))
+			return -ENODEV;
+		ret = idev->info->release(idev->info, inode);
+		module_put(idev->owner);
+	}
+	if (filep->f_flags & FASYNC)
+		ret = uio_fasync(-1, filep, 0);
+	kfree(listener);
+	return ret;
+}
+
+static unsigned int uio_poll(struct file *filep, poll_table *wait)
+{
+	struct uio_listener *listener = filep->private_data;
+	struct uio_device *idev = listener->dev;
+
+	if (idev->info->irq == UIO_IRQ_NONE)
+		return -EIO;
+
+	poll_wait(filep, &idev->wait, wait);
+	if (listener->event_count != atomic_read(&idev->event))
+		return POLLIN | POLLRDNORM;
+	return 0;
+}
+
+static ssize_t uio_read(struct file *filep, char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	struct uio_listener *listener = filep->private_data;
+	struct uio_device *idev = listener->dev;
+	DECLARE_WAITQUEUE(wait, current);
+	ssize_t retval;
+	s32 event_count;
+
+	if (idev->info->irq == UIO_IRQ_NONE)
+		return -EIO;
+
+	if (count != sizeof(s32))
+		return -EINVAL;
+
+	add_wait_queue(&idev->wait, &wait);
+
+	do {
+		set_current_state(TASK_INTERRUPTIBLE);
+
+		event_count = atomic_read(&idev->event);
+		if (event_count != listener->event_count) {
+			if (copy_to_user(buf, &event_count, count))
+				retval = -EFAULT;
+			else {
+				listener->event_count = event_count;
+				retval = count;
+			}
+			break;
+		}
+
+		if (filep->f_flags & O_NONBLOCK) {
+			retval = -EAGAIN;
+			break;
+		}
+
+		if (signal_pending(current)) {
+			retval = -ERESTARTSYS;
+			break;
+		}
+		schedule();
+	} while (1);
+
+	__set_current_state(TASK_RUNNING);
+	remove_wait_queue(&idev->wait, &wait);
+
+	return retval;
+}
+
+static int uio_find_mem_index(struct vm_area_struct *vma)
+{
+	int mi;
+	struct uio_device *idev = vma->vm_private_data;
+
+	for (mi = 0; mi < MAX_UIO_MAPS; mi++) {
+		if (idev->info->mem[mi].size == 0)
+			return -1;
+		if (vma->vm_pgoff == mi)
+			return mi;
+	}
+	return -1;
+}
+
+static void uio_vma_open(struct vm_area_struct *vma)
+{
+	struct uio_device *idev = vma->vm_private_data;
+	idev->vma_count++;
+}
+
+static void uio_vma_close(struct vm_area_struct *vma)
+{
+	struct uio_device *idev = vma->vm_private_data;
+	idev->vma_count--;
+}
+
+static struct page *uio_vma_nopage(struct vm_area_struct *vma,
+				   unsigned long address, int *type)
+{
+	struct uio_device *idev = vma->vm_private_data;
+	struct page* page = NOPAGE_SIGBUS;
+
+	int mi = uio_find_mem_index(vma);
+	if (mi < 0)
+		return page;
+
+	if (idev->info->mem[mi].memtype == UIO_MEM_LOGICAL)
+		page = virt_to_page(idev->info->mem[mi].addr);
+	else
+		page = vmalloc_to_page((void*)idev->info->mem[mi].addr);
+	get_page(page);
+	if (type)
+		*type = VM_FAULT_MINOR;
+	return page;
+}
+
+static struct vm_operations_struct uio_vm_ops = {
+	.open = uio_vma_open,
+	.close = uio_vma_close,
+	.nopage = uio_vma_nopage,
+};
+
+static int uio_mmap_physical(struct vm_area_struct *vma)
+{
+	struct uio_device *idev = vma->vm_private_data;
+	int mi = uio_find_mem_index(vma);
+	if (mi < 0)
+		return -EINVAL;
+
+	vma->vm_flags |= VM_IO | VM_RESERVED;
+
+	return remap_pfn_range(vma,
+			       vma->vm_start,
+			       idev->info->mem[mi].addr >> PAGE_SHIFT,
+			       vma->vm_end - vma->vm_start,
+			       vma->vm_page_prot);
+}
+
+static int uio_mmap_logical(struct vm_area_struct *vma)
+{
+	vma->vm_flags |= VM_RESERVED;
+	vma->vm_ops = &uio_vm_ops;
+	uio_vma_open(vma);
+	return 0;
+}
+
+static int uio_mmap(struct file *filep, struct vm_area_struct *vma)
+{
+	struct uio_listener *listener = filep->private_data;
+	struct uio_device *idev = listener->dev;
+	int mi;
+	unsigned long requested_pages, actual_pages;
+	int ret = 0;
+
+	if (vma->vm_end < vma->vm_start)
+		return -EINVAL;
+
+	vma->vm_private_data = idev;
+
+	mi = uio_find_mem_index(vma);
+	if (mi < 0)
+		return -EINVAL;
+
+	requested_pages = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
+	actual_pages = (idev->info->mem[mi].size + PAGE_SIZE -1) >> PAGE_SHIFT;
+	if (requested_pages > actual_pages)
+		return -EINVAL;
+
+	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->mem[mi].memtype) {
+		case UIO_MEM_PHYS:
+			return uio_mmap_physical(vma);
+		case UIO_MEM_LOGICAL:
+		case UIO_MEM_VIRTUAL:
+			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
+ * @owner:	module that creates the new device
+ * @parent:	parent device
+ * @info:	UIO device capabilities
+ *
+ * returns zero on success or a negative error code.
+ */
+int __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 -EINVAL;
+
+	info->uio_dev = NULL;
+
+	ret = init_uio_class();
+	if (ret)
+		return 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;
+
+	info->uio_dev = idev;
+
+	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 0;
+
+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 ret;
+}
+EXPORT_SYMBOL_GPL(__uio_register_device);
+
+/**
+ * uio_unregister_device - unregister a industrial IO device
+ * @info:	UIO device capabilities
+ *
+ */
+void uio_unregister_device(struct uio_info *info)
+{
+	struct uio_device *idev;
+
+	if (!info || !info->uio_dev)
+		return;
+
+	idev = info->uio_dev;
+
+	uio_free_minor(idev);
+
+	if (info->irq >= 0)
+		free_irq(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;
+}
+
+static void __exit uio_exit(void)
+{
+}
+
+module_init(uio_init)
+module_exit(uio_exit)
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/uio_driver.h b/include/linux/uio_driver.h
new file mode 100644
index 0000000..44c28e9
--- /dev/null
+++ b/include/linux/uio_driver.h
@@ -0,0 +1,91 @@
+/*
+ * include/linux/uio_driver.h
+ *
+ * Copyright(C) 2005, Benedikt Spranger <b.spranger@linutronix.de>
+ * Copyright(C) 2005, Thomas Gleixner <tglx@linutronix.de>
+ * Copyright(C) 2006, Hans J. Koch <hjk@linutronix.de>
+ * Copyright(C) 2006, Greg Kroah-Hartman <greg@kroah.com>
+ *
+ * Userspace IO driver.
+ *
+ * Licensed under the GPLv2 only.
+ */
+
+#ifndef _UIO_DRIVER_H_
+#define _UIO_DRIVER_H_
+
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/interrupt.h>
+
+/**
+ * struct uio_mem - description of a UIO memory region
+ * @kobj:		kobject for this mapping
+ * @addr:		address of the device's memory
+ * @size:		size of IO
+ * @memtype:		type of memory addr points to
+ * @internal_addr:	ioremap-ped version of addr, for driver internal use
+ */
+struct uio_mem {
+	struct kobject		kobj;
+	unsigned long		addr;
+	unsigned long		size;
+	int			memtype;
+	void __iomem		*internal_addr;
+};
+
+#define MAX_UIO_MAPS 	5
+
+struct uio_device;
+
+/**
+ * struct uio_info - UIO device capabilities
+ * @uio_dev:		the UIO device this info belongs to
+ * @name:		device name
+ * @version:		device driver version
+ * @mem:		list of mappable memory regions, size==0 for end of list
+ * @irq:		interrupt number or UIO_IRQ_CUSTOM
+ * @irq_flags:		flags for request_irq()
+ * @priv:		optional private data
+ * @handler:		the device's irq handler
+ * @mmap:		mmap operation for this uio device
+ * @open:		open operation for this uio device
+ * @release:		release operation for this uio device
+ */
+struct uio_info {
+	struct uio_device	*uio_dev;
+	char			*name;
+	char			*version;
+	struct uio_mem		mem[MAX_UIO_MAPS];
+	long			irq;
+	unsigned long		irq_flags;
+	void			*priv;
+	irqreturn_t (*handler)(int irq, struct uio_info *dev_info);
+	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);
+};
+
+extern int __must_check
+	__uio_register_device(struct module *owner,
+			      struct device *parent,
+			      struct uio_info *info);
+static inline int __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_info *info);
+extern void uio_event_notify(struct uio_info *info);
+
+/* 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
+#define UIO_MEM_VIRTUAL 3
+
+#endif /* _LINUX_UIO_DRIVER_H_ */