xref: /openbmc/linux/drivers/uio/uio.c (revision f9cb074b)
1beafc54cSHans J. Koch /*
2beafc54cSHans J. Koch  * drivers/uio/uio.c
3beafc54cSHans J. Koch  *
4beafc54cSHans J. Koch  * Copyright(C) 2005, Benedikt Spranger <b.spranger@linutronix.de>
5beafc54cSHans J. Koch  * Copyright(C) 2005, Thomas Gleixner <tglx@linutronix.de>
6beafc54cSHans J. Koch  * Copyright(C) 2006, Hans J. Koch <hjk@linutronix.de>
7beafc54cSHans J. Koch  * Copyright(C) 2006, Greg Kroah-Hartman <greg@kroah.com>
8beafc54cSHans J. Koch  *
9beafc54cSHans J. Koch  * Userspace IO
10beafc54cSHans J. Koch  *
11beafc54cSHans J. Koch  * Base Functions
12beafc54cSHans J. Koch  *
13beafc54cSHans J. Koch  * Licensed under the GPLv2 only.
14beafc54cSHans J. Koch  */
15beafc54cSHans J. Koch 
16beafc54cSHans J. Koch #include <linux/module.h>
17beafc54cSHans J. Koch #include <linux/init.h>
18beafc54cSHans J. Koch #include <linux/poll.h>
19beafc54cSHans J. Koch #include <linux/device.h>
20beafc54cSHans J. Koch #include <linux/mm.h>
21beafc54cSHans J. Koch #include <linux/idr.h>
22beafc54cSHans J. Koch #include <linux/string.h>
23beafc54cSHans J. Koch #include <linux/kobject.h>
24beafc54cSHans J. Koch #include <linux/uio_driver.h>
25beafc54cSHans J. Koch 
26beafc54cSHans J. Koch #define UIO_MAX_DEVICES 255
27beafc54cSHans J. Koch 
28beafc54cSHans J. Koch struct uio_device {
29beafc54cSHans J. Koch 	struct module		*owner;
30beafc54cSHans J. Koch 	struct device		*dev;
31beafc54cSHans J. Koch 	int			minor;
32beafc54cSHans J. Koch 	atomic_t		event;
33beafc54cSHans J. Koch 	struct fasync_struct	*async_queue;
34beafc54cSHans J. Koch 	wait_queue_head_t	wait;
35beafc54cSHans J. Koch 	int			vma_count;
36beafc54cSHans J. Koch 	struct uio_info		*info;
3781e7c6a6SGreg Kroah-Hartman 	struct kobject		*map_dir;
38beafc54cSHans J. Koch };
39beafc54cSHans J. Koch 
40beafc54cSHans J. Koch static int uio_major;
41beafc54cSHans J. Koch static DEFINE_IDR(uio_idr);
42beafc54cSHans J. Koch static struct file_operations uio_fops;
43beafc54cSHans J. Koch 
44beafc54cSHans J. Koch /* UIO class infrastructure */
45beafc54cSHans J. Koch static struct uio_class {
46beafc54cSHans J. Koch 	struct kref kref;
47beafc54cSHans J. Koch 	struct class *class;
48beafc54cSHans J. Koch } *uio_class;
49beafc54cSHans J. Koch 
50beafc54cSHans J. Koch /*
51beafc54cSHans J. Koch  * attributes
52beafc54cSHans J. Koch  */
53beafc54cSHans J. Koch 
5481e7c6a6SGreg Kroah-Hartman struct uio_map {
5581e7c6a6SGreg Kroah-Hartman 	struct kobject kobj;
5681e7c6a6SGreg Kroah-Hartman 	struct uio_mem *mem;
57beafc54cSHans J. Koch };
5881e7c6a6SGreg Kroah-Hartman #define to_map(map) container_of(map, struct uio_map, kobj)
59beafc54cSHans J. Koch 
60beafc54cSHans J. Koch 
6181e7c6a6SGreg Kroah-Hartman static ssize_t map_attr_show(struct kobject *kobj, struct kobj_attribute *attr,
62beafc54cSHans J. Koch 			     char *buf)
63beafc54cSHans J. Koch {
6481e7c6a6SGreg Kroah-Hartman 	struct uio_map *map = to_map(kobj);
6581e7c6a6SGreg Kroah-Hartman 	struct uio_mem *mem = map->mem;
66beafc54cSHans J. Koch 
6781e7c6a6SGreg Kroah-Hartman 	if (strncmp(attr->attr.name, "addr", 4) == 0)
68beafc54cSHans J. Koch 		return sprintf(buf, "0x%lx\n", mem->addr);
69beafc54cSHans J. Koch 
7081e7c6a6SGreg Kroah-Hartman 	if (strncmp(attr->attr.name, "size", 4) == 0)
71beafc54cSHans J. Koch 		return sprintf(buf, "0x%lx\n", mem->size);
72beafc54cSHans J. Koch 
73beafc54cSHans J. Koch 	return -ENODEV;
74beafc54cSHans J. Koch }
75beafc54cSHans J. Koch 
7681e7c6a6SGreg Kroah-Hartman static struct kobj_attribute attr_attribute =
7781e7c6a6SGreg Kroah-Hartman 	__ATTR(addr, S_IRUGO, map_attr_show, NULL);
7881e7c6a6SGreg Kroah-Hartman static struct kobj_attribute size_attribute =
7981e7c6a6SGreg Kroah-Hartman 	__ATTR(size, S_IRUGO, map_attr_show, NULL);
80beafc54cSHans J. Koch 
8181e7c6a6SGreg Kroah-Hartman static struct attribute *attrs[] = {
8281e7c6a6SGreg Kroah-Hartman 	&attr_attribute.attr,
8381e7c6a6SGreg Kroah-Hartman 	&size_attribute.attr,
8481e7c6a6SGreg Kroah-Hartman 	NULL,	/* need to NULL terminate the list of attributes */
85beafc54cSHans J. Koch };
86beafc54cSHans J. Koch 
8781e7c6a6SGreg Kroah-Hartman static void map_release(struct kobject *kobj)
8881e7c6a6SGreg Kroah-Hartman {
8981e7c6a6SGreg Kroah-Hartman 	struct uio_map *map = to_map(kobj);
9081e7c6a6SGreg Kroah-Hartman 	kfree(map);
9181e7c6a6SGreg Kroah-Hartman }
9281e7c6a6SGreg Kroah-Hartman 
93beafc54cSHans J. Koch static struct kobj_type map_attr_type = {
9481e7c6a6SGreg Kroah-Hartman 	.release	= map_release,
9581e7c6a6SGreg Kroah-Hartman 	.default_attrs	= attrs,
96beafc54cSHans J. Koch };
97beafc54cSHans J. Koch 
98beafc54cSHans J. Koch static ssize_t show_name(struct device *dev,
99beafc54cSHans J. Koch 			 struct device_attribute *attr, char *buf)
100beafc54cSHans J. Koch {
101beafc54cSHans J. Koch 	struct uio_device *idev = dev_get_drvdata(dev);
102beafc54cSHans J. Koch 	if (idev)
103beafc54cSHans J. Koch 		return sprintf(buf, "%s\n", idev->info->name);
104beafc54cSHans J. Koch 	else
105beafc54cSHans J. Koch 		return -ENODEV;
106beafc54cSHans J. Koch }
107beafc54cSHans J. Koch static DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
108beafc54cSHans J. Koch 
109beafc54cSHans J. Koch static ssize_t show_version(struct device *dev,
110beafc54cSHans J. Koch 			    struct device_attribute *attr, char *buf)
111beafc54cSHans J. Koch {
112beafc54cSHans J. Koch 	struct uio_device *idev = dev_get_drvdata(dev);
113beafc54cSHans J. Koch 	if (idev)
114beafc54cSHans J. Koch 		return sprintf(buf, "%s\n", idev->info->version);
115beafc54cSHans J. Koch 	else
116beafc54cSHans J. Koch 		return -ENODEV;
117beafc54cSHans J. Koch }
118beafc54cSHans J. Koch static DEVICE_ATTR(version, S_IRUGO, show_version, NULL);
119beafc54cSHans J. Koch 
120beafc54cSHans J. Koch static ssize_t show_event(struct device *dev,
121beafc54cSHans J. Koch 			  struct device_attribute *attr, char *buf)
122beafc54cSHans J. Koch {
123beafc54cSHans J. Koch 	struct uio_device *idev = dev_get_drvdata(dev);
124beafc54cSHans J. Koch 	if (idev)
125beafc54cSHans J. Koch 		return sprintf(buf, "%u\n",
126beafc54cSHans J. Koch 				(unsigned int)atomic_read(&idev->event));
127beafc54cSHans J. Koch 	else
128beafc54cSHans J. Koch 		return -ENODEV;
129beafc54cSHans J. Koch }
130beafc54cSHans J. Koch static DEVICE_ATTR(event, S_IRUGO, show_event, NULL);
131beafc54cSHans J. Koch 
132beafc54cSHans J. Koch static struct attribute *uio_attrs[] = {
133beafc54cSHans J. Koch 	&dev_attr_name.attr,
134beafc54cSHans J. Koch 	&dev_attr_version.attr,
135beafc54cSHans J. Koch 	&dev_attr_event.attr,
136beafc54cSHans J. Koch 	NULL,
137beafc54cSHans J. Koch };
138beafc54cSHans J. Koch 
139beafc54cSHans J. Koch static struct attribute_group uio_attr_grp = {
140beafc54cSHans J. Koch 	.attrs = uio_attrs,
141beafc54cSHans J. Koch };
142beafc54cSHans J. Koch 
143beafc54cSHans J. Koch /*
144beafc54cSHans J. Koch  * device functions
145beafc54cSHans J. Koch  */
146beafc54cSHans J. Koch static int uio_dev_add_attributes(struct uio_device *idev)
147beafc54cSHans J. Koch {
148beafc54cSHans J. Koch 	int ret;
149beafc54cSHans J. Koch 	int mi;
150beafc54cSHans J. Koch 	int map_found = 0;
151beafc54cSHans J. Koch 	struct uio_mem *mem;
15281e7c6a6SGreg Kroah-Hartman 	struct uio_map *map;
153beafc54cSHans J. Koch 
154beafc54cSHans J. Koch 	ret = sysfs_create_group(&idev->dev->kobj, &uio_attr_grp);
155beafc54cSHans J. Koch 	if (ret)
156beafc54cSHans J. Koch 		goto err_group;
157beafc54cSHans J. Koch 
158beafc54cSHans J. Koch 	for (mi = 0; mi < MAX_UIO_MAPS; mi++) {
159beafc54cSHans J. Koch 		mem = &idev->info->mem[mi];
160beafc54cSHans J. Koch 		if (mem->size == 0)
161beafc54cSHans J. Koch 			break;
162beafc54cSHans J. Koch 		if (!map_found) {
163beafc54cSHans J. Koch 			map_found = 1;
16481e7c6a6SGreg Kroah-Hartman 			idev->map_dir = kobject_create_and_add("maps",
16581e7c6a6SGreg Kroah-Hartman 							&idev->dev->kobj);
16681e7c6a6SGreg Kroah-Hartman 			if (!idev->map_dir)
16781e7c6a6SGreg Kroah-Hartman 				goto err;
168beafc54cSHans J. Koch 		}
16981e7c6a6SGreg Kroah-Hartman 		map = kzalloc(sizeof(*map), GFP_KERNEL);
17081e7c6a6SGreg Kroah-Hartman 		if (!map)
17181e7c6a6SGreg Kroah-Hartman 			goto err;
172f9cb074bSGreg Kroah-Hartman 		kobject_init(&map->kobj, &map_attr_type);
17381e7c6a6SGreg Kroah-Hartman 		map->mem = mem;
17481e7c6a6SGreg Kroah-Hartman 		mem->map = map;
175b2d6db58SGreg Kroah-Hartman 		ret = kobject_add(&map->kobj, idev->map_dir, "map%d", mi);
176beafc54cSHans J. Koch 		if (ret)
17781e7c6a6SGreg Kroah-Hartman 			goto err;
17881e7c6a6SGreg Kroah-Hartman 		ret = kobject_uevent(&map->kobj, KOBJ_ADD);
17981e7c6a6SGreg Kroah-Hartman 		if (ret)
18081e7c6a6SGreg Kroah-Hartman 			goto err;
181beafc54cSHans J. Koch 	}
182beafc54cSHans J. Koch 
183beafc54cSHans J. Koch 	return 0;
184beafc54cSHans J. Koch 
18581e7c6a6SGreg Kroah-Hartman err:
186beafc54cSHans J. Koch 	for (mi--; mi>=0; mi--) {
187beafc54cSHans J. Koch 		mem = &idev->info->mem[mi];
18881e7c6a6SGreg Kroah-Hartman 		map = mem->map;
18981e7c6a6SGreg Kroah-Hartman 		kobject_unregister(&map->kobj);
190beafc54cSHans J. Koch 	}
19181e7c6a6SGreg Kroah-Hartman 	kobject_unregister(idev->map_dir);
192beafc54cSHans J. Koch 	sysfs_remove_group(&idev->dev->kobj, &uio_attr_grp);
193beafc54cSHans J. Koch err_group:
194beafc54cSHans J. Koch 	dev_err(idev->dev, "error creating sysfs files (%d)\n", ret);
195beafc54cSHans J. Koch 	return ret;
196beafc54cSHans J. Koch }
197beafc54cSHans J. Koch 
198beafc54cSHans J. Koch static void uio_dev_del_attributes(struct uio_device *idev)
199beafc54cSHans J. Koch {
200beafc54cSHans J. Koch 	int mi;
201beafc54cSHans J. Koch 	struct uio_mem *mem;
202beafc54cSHans J. Koch 	for (mi = 0; mi < MAX_UIO_MAPS; mi++) {
203beafc54cSHans J. Koch 		mem = &idev->info->mem[mi];
204beafc54cSHans J. Koch 		if (mem->size == 0)
205beafc54cSHans J. Koch 			break;
20681e7c6a6SGreg Kroah-Hartman 		kobject_unregister(&mem->map->kobj);
207beafc54cSHans J. Koch 	}
20881e7c6a6SGreg Kroah-Hartman 	kobject_unregister(idev->map_dir);
209beafc54cSHans J. Koch 	sysfs_remove_group(&idev->dev->kobj, &uio_attr_grp);
210beafc54cSHans J. Koch }
211beafc54cSHans J. Koch 
212beafc54cSHans J. Koch static int uio_get_minor(struct uio_device *idev)
213beafc54cSHans J. Koch {
214beafc54cSHans J. Koch 	static DEFINE_MUTEX(minor_lock);
215beafc54cSHans J. Koch 	int retval = -ENOMEM;
216beafc54cSHans J. Koch 	int id;
217beafc54cSHans J. Koch 
218beafc54cSHans J. Koch 	mutex_lock(&minor_lock);
219beafc54cSHans J. Koch 	if (idr_pre_get(&uio_idr, GFP_KERNEL) == 0)
220beafc54cSHans J. Koch 		goto exit;
221beafc54cSHans J. Koch 
222beafc54cSHans J. Koch 	retval = idr_get_new(&uio_idr, idev, &id);
223beafc54cSHans J. Koch 	if (retval < 0) {
224beafc54cSHans J. Koch 		if (retval == -EAGAIN)
225beafc54cSHans J. Koch 			retval = -ENOMEM;
226beafc54cSHans J. Koch 		goto exit;
227beafc54cSHans J. Koch 	}
228beafc54cSHans J. Koch 	idev->minor = id & MAX_ID_MASK;
229beafc54cSHans J. Koch exit:
230beafc54cSHans J. Koch 	mutex_unlock(&minor_lock);
231beafc54cSHans J. Koch 	return retval;
232beafc54cSHans J. Koch }
233beafc54cSHans J. Koch 
234beafc54cSHans J. Koch static void uio_free_minor(struct uio_device *idev)
235beafc54cSHans J. Koch {
236beafc54cSHans J. Koch 	idr_remove(&uio_idr, idev->minor);
237beafc54cSHans J. Koch }
238beafc54cSHans J. Koch 
239beafc54cSHans J. Koch /**
240beafc54cSHans J. Koch  * uio_event_notify - trigger an interrupt event
241beafc54cSHans J. Koch  * @info: UIO device capabilities
242beafc54cSHans J. Koch  */
243beafc54cSHans J. Koch void uio_event_notify(struct uio_info *info)
244beafc54cSHans J. Koch {
245beafc54cSHans J. Koch 	struct uio_device *idev = info->uio_dev;
246beafc54cSHans J. Koch 
247beafc54cSHans J. Koch 	atomic_inc(&idev->event);
248beafc54cSHans J. Koch 	wake_up_interruptible(&idev->wait);
249beafc54cSHans J. Koch 	kill_fasync(&idev->async_queue, SIGIO, POLL_IN);
250beafc54cSHans J. Koch }
251beafc54cSHans J. Koch EXPORT_SYMBOL_GPL(uio_event_notify);
252beafc54cSHans J. Koch 
253beafc54cSHans J. Koch /**
254beafc54cSHans J. Koch  * uio_interrupt - hardware interrupt handler
255beafc54cSHans J. Koch  * @irq: IRQ number, can be UIO_IRQ_CYCLIC for cyclic timer
256beafc54cSHans J. Koch  * @dev_id: Pointer to the devices uio_device structure
257beafc54cSHans J. Koch  */
258beafc54cSHans J. Koch static irqreturn_t uio_interrupt(int irq, void *dev_id)
259beafc54cSHans J. Koch {
260beafc54cSHans J. Koch 	struct uio_device *idev = (struct uio_device *)dev_id;
261beafc54cSHans J. Koch 	irqreturn_t ret = idev->info->handler(irq, idev->info);
262beafc54cSHans J. Koch 
263beafc54cSHans J. Koch 	if (ret == IRQ_HANDLED)
264beafc54cSHans J. Koch 		uio_event_notify(idev->info);
265beafc54cSHans J. Koch 
266beafc54cSHans J. Koch 	return ret;
267beafc54cSHans J. Koch }
268beafc54cSHans J. Koch 
269beafc54cSHans J. Koch struct uio_listener {
270beafc54cSHans J. Koch 	struct uio_device *dev;
271beafc54cSHans J. Koch 	s32 event_count;
272beafc54cSHans J. Koch };
273beafc54cSHans J. Koch 
274beafc54cSHans J. Koch static int uio_open(struct inode *inode, struct file *filep)
275beafc54cSHans J. Koch {
276beafc54cSHans J. Koch 	struct uio_device *idev;
277beafc54cSHans J. Koch 	struct uio_listener *listener;
278beafc54cSHans J. Koch 	int ret = 0;
279beafc54cSHans J. Koch 
280beafc54cSHans J. Koch 	idev = idr_find(&uio_idr, iminor(inode));
281beafc54cSHans J. Koch 	if (!idev)
282beafc54cSHans J. Koch 		return -ENODEV;
283beafc54cSHans J. Koch 
284beafc54cSHans J. Koch 	listener = kmalloc(sizeof(*listener), GFP_KERNEL);
285beafc54cSHans J. Koch 	if (!listener)
286beafc54cSHans J. Koch 		return -ENOMEM;
287beafc54cSHans J. Koch 
288beafc54cSHans J. Koch 	listener->dev = idev;
289beafc54cSHans J. Koch 	listener->event_count = atomic_read(&idev->event);
290beafc54cSHans J. Koch 	filep->private_data = listener;
291beafc54cSHans J. Koch 
292beafc54cSHans J. Koch 	if (idev->info->open) {
293beafc54cSHans J. Koch 		if (!try_module_get(idev->owner))
294beafc54cSHans J. Koch 			return -ENODEV;
295beafc54cSHans J. Koch 		ret = idev->info->open(idev->info, inode);
296beafc54cSHans J. Koch 		module_put(idev->owner);
297beafc54cSHans J. Koch 	}
298beafc54cSHans J. Koch 
299beafc54cSHans J. Koch 	if (ret)
300beafc54cSHans J. Koch 		kfree(listener);
301beafc54cSHans J. Koch 
302beafc54cSHans J. Koch 	return ret;
303beafc54cSHans J. Koch }
304beafc54cSHans J. Koch 
305beafc54cSHans J. Koch static int uio_fasync(int fd, struct file *filep, int on)
306beafc54cSHans J. Koch {
307beafc54cSHans J. Koch 	struct uio_listener *listener = filep->private_data;
308beafc54cSHans J. Koch 	struct uio_device *idev = listener->dev;
309beafc54cSHans J. Koch 
310beafc54cSHans J. Koch 	return fasync_helper(fd, filep, on, &idev->async_queue);
311beafc54cSHans J. Koch }
312beafc54cSHans J. Koch 
313beafc54cSHans J. Koch static int uio_release(struct inode *inode, struct file *filep)
314beafc54cSHans J. Koch {
315beafc54cSHans J. Koch 	int ret = 0;
316beafc54cSHans J. Koch 	struct uio_listener *listener = filep->private_data;
317beafc54cSHans J. Koch 	struct uio_device *idev = listener->dev;
318beafc54cSHans J. Koch 
319beafc54cSHans J. Koch 	if (idev->info->release) {
320beafc54cSHans J. Koch 		if (!try_module_get(idev->owner))
321beafc54cSHans J. Koch 			return -ENODEV;
322beafc54cSHans J. Koch 		ret = idev->info->release(idev->info, inode);
323beafc54cSHans J. Koch 		module_put(idev->owner);
324beafc54cSHans J. Koch 	}
325beafc54cSHans J. Koch 	if (filep->f_flags & FASYNC)
326beafc54cSHans J. Koch 		ret = uio_fasync(-1, filep, 0);
327beafc54cSHans J. Koch 	kfree(listener);
328beafc54cSHans J. Koch 	return ret;
329beafc54cSHans J. Koch }
330beafc54cSHans J. Koch 
331beafc54cSHans J. Koch static unsigned int uio_poll(struct file *filep, poll_table *wait)
332beafc54cSHans J. Koch {
333beafc54cSHans J. Koch 	struct uio_listener *listener = filep->private_data;
334beafc54cSHans J. Koch 	struct uio_device *idev = listener->dev;
335beafc54cSHans J. Koch 
336beafc54cSHans J. Koch 	if (idev->info->irq == UIO_IRQ_NONE)
337beafc54cSHans J. Koch 		return -EIO;
338beafc54cSHans J. Koch 
339beafc54cSHans J. Koch 	poll_wait(filep, &idev->wait, wait);
340beafc54cSHans J. Koch 	if (listener->event_count != atomic_read(&idev->event))
341beafc54cSHans J. Koch 		return POLLIN | POLLRDNORM;
342beafc54cSHans J. Koch 	return 0;
343beafc54cSHans J. Koch }
344beafc54cSHans J. Koch 
345beafc54cSHans J. Koch static ssize_t uio_read(struct file *filep, char __user *buf,
346beafc54cSHans J. Koch 			size_t count, loff_t *ppos)
347beafc54cSHans J. Koch {
348beafc54cSHans J. Koch 	struct uio_listener *listener = filep->private_data;
349beafc54cSHans J. Koch 	struct uio_device *idev = listener->dev;
350beafc54cSHans J. Koch 	DECLARE_WAITQUEUE(wait, current);
351beafc54cSHans J. Koch 	ssize_t retval;
352beafc54cSHans J. Koch 	s32 event_count;
353beafc54cSHans J. Koch 
354beafc54cSHans J. Koch 	if (idev->info->irq == UIO_IRQ_NONE)
355beafc54cSHans J. Koch 		return -EIO;
356beafc54cSHans J. Koch 
357beafc54cSHans J. Koch 	if (count != sizeof(s32))
358beafc54cSHans J. Koch 		return -EINVAL;
359beafc54cSHans J. Koch 
360beafc54cSHans J. Koch 	add_wait_queue(&idev->wait, &wait);
361beafc54cSHans J. Koch 
362beafc54cSHans J. Koch 	do {
363beafc54cSHans J. Koch 		set_current_state(TASK_INTERRUPTIBLE);
364beafc54cSHans J. Koch 
365beafc54cSHans J. Koch 		event_count = atomic_read(&idev->event);
366beafc54cSHans J. Koch 		if (event_count != listener->event_count) {
367beafc54cSHans J. Koch 			if (copy_to_user(buf, &event_count, count))
368beafc54cSHans J. Koch 				retval = -EFAULT;
369beafc54cSHans J. Koch 			else {
370beafc54cSHans J. Koch 				listener->event_count = event_count;
371beafc54cSHans J. Koch 				retval = count;
372beafc54cSHans J. Koch 			}
373beafc54cSHans J. Koch 			break;
374beafc54cSHans J. Koch 		}
375beafc54cSHans J. Koch 
376beafc54cSHans J. Koch 		if (filep->f_flags & O_NONBLOCK) {
377beafc54cSHans J. Koch 			retval = -EAGAIN;
378beafc54cSHans J. Koch 			break;
379beafc54cSHans J. Koch 		}
380beafc54cSHans J. Koch 
381beafc54cSHans J. Koch 		if (signal_pending(current)) {
382beafc54cSHans J. Koch 			retval = -ERESTARTSYS;
383beafc54cSHans J. Koch 			break;
384beafc54cSHans J. Koch 		}
385beafc54cSHans J. Koch 		schedule();
386beafc54cSHans J. Koch 	} while (1);
387beafc54cSHans J. Koch 
388beafc54cSHans J. Koch 	__set_current_state(TASK_RUNNING);
389beafc54cSHans J. Koch 	remove_wait_queue(&idev->wait, &wait);
390beafc54cSHans J. Koch 
391beafc54cSHans J. Koch 	return retval;
392beafc54cSHans J. Koch }
393beafc54cSHans J. Koch 
394beafc54cSHans J. Koch static int uio_find_mem_index(struct vm_area_struct *vma)
395beafc54cSHans J. Koch {
396beafc54cSHans J. Koch 	int mi;
397beafc54cSHans J. Koch 	struct uio_device *idev = vma->vm_private_data;
398beafc54cSHans J. Koch 
399beafc54cSHans J. Koch 	for (mi = 0; mi < MAX_UIO_MAPS; mi++) {
400beafc54cSHans J. Koch 		if (idev->info->mem[mi].size == 0)
401beafc54cSHans J. Koch 			return -1;
402beafc54cSHans J. Koch 		if (vma->vm_pgoff == mi)
403beafc54cSHans J. Koch 			return mi;
404beafc54cSHans J. Koch 	}
405beafc54cSHans J. Koch 	return -1;
406beafc54cSHans J. Koch }
407beafc54cSHans J. Koch 
408beafc54cSHans J. Koch static void uio_vma_open(struct vm_area_struct *vma)
409beafc54cSHans J. Koch {
410beafc54cSHans J. Koch 	struct uio_device *idev = vma->vm_private_data;
411beafc54cSHans J. Koch 	idev->vma_count++;
412beafc54cSHans J. Koch }
413beafc54cSHans J. Koch 
414beafc54cSHans J. Koch static void uio_vma_close(struct vm_area_struct *vma)
415beafc54cSHans J. Koch {
416beafc54cSHans J. Koch 	struct uio_device *idev = vma->vm_private_data;
417beafc54cSHans J. Koch 	idev->vma_count--;
418beafc54cSHans J. Koch }
419beafc54cSHans J. Koch 
420beafc54cSHans J. Koch static struct page *uio_vma_nopage(struct vm_area_struct *vma,
421beafc54cSHans J. Koch 				   unsigned long address, int *type)
422beafc54cSHans J. Koch {
423beafc54cSHans J. Koch 	struct uio_device *idev = vma->vm_private_data;
424beafc54cSHans J. Koch 	struct page* page = NOPAGE_SIGBUS;
425beafc54cSHans J. Koch 
426beafc54cSHans J. Koch 	int mi = uio_find_mem_index(vma);
427beafc54cSHans J. Koch 	if (mi < 0)
428beafc54cSHans J. Koch 		return page;
429beafc54cSHans J. Koch 
430beafc54cSHans J. Koch 	if (idev->info->mem[mi].memtype == UIO_MEM_LOGICAL)
431beafc54cSHans J. Koch 		page = virt_to_page(idev->info->mem[mi].addr);
432beafc54cSHans J. Koch 	else
433beafc54cSHans J. Koch 		page = vmalloc_to_page((void*)idev->info->mem[mi].addr);
434beafc54cSHans J. Koch 	get_page(page);
435beafc54cSHans J. Koch 	if (type)
436beafc54cSHans J. Koch 		*type = VM_FAULT_MINOR;
437beafc54cSHans J. Koch 	return page;
438beafc54cSHans J. Koch }
439beafc54cSHans J. Koch 
440beafc54cSHans J. Koch static struct vm_operations_struct uio_vm_ops = {
441beafc54cSHans J. Koch 	.open = uio_vma_open,
442beafc54cSHans J. Koch 	.close = uio_vma_close,
443beafc54cSHans J. Koch 	.nopage = uio_vma_nopage,
444beafc54cSHans J. Koch };
445beafc54cSHans J. Koch 
446beafc54cSHans J. Koch static int uio_mmap_physical(struct vm_area_struct *vma)
447beafc54cSHans J. Koch {
448beafc54cSHans J. Koch 	struct uio_device *idev = vma->vm_private_data;
449beafc54cSHans J. Koch 	int mi = uio_find_mem_index(vma);
450beafc54cSHans J. Koch 	if (mi < 0)
451beafc54cSHans J. Koch 		return -EINVAL;
452beafc54cSHans J. Koch 
453beafc54cSHans J. Koch 	vma->vm_flags |= VM_IO | VM_RESERVED;
454beafc54cSHans J. Koch 
455beafc54cSHans J. Koch 	return remap_pfn_range(vma,
456beafc54cSHans J. Koch 			       vma->vm_start,
457beafc54cSHans J. Koch 			       idev->info->mem[mi].addr >> PAGE_SHIFT,
458beafc54cSHans J. Koch 			       vma->vm_end - vma->vm_start,
459beafc54cSHans J. Koch 			       vma->vm_page_prot);
460beafc54cSHans J. Koch }
461beafc54cSHans J. Koch 
462beafc54cSHans J. Koch static int uio_mmap_logical(struct vm_area_struct *vma)
463beafc54cSHans J. Koch {
464beafc54cSHans J. Koch 	vma->vm_flags |= VM_RESERVED;
465beafc54cSHans J. Koch 	vma->vm_ops = &uio_vm_ops;
466beafc54cSHans J. Koch 	uio_vma_open(vma);
467beafc54cSHans J. Koch 	return 0;
468beafc54cSHans J. Koch }
469beafc54cSHans J. Koch 
470beafc54cSHans J. Koch static int uio_mmap(struct file *filep, struct vm_area_struct *vma)
471beafc54cSHans J. Koch {
472beafc54cSHans J. Koch 	struct uio_listener *listener = filep->private_data;
473beafc54cSHans J. Koch 	struct uio_device *idev = listener->dev;
474beafc54cSHans J. Koch 	int mi;
475beafc54cSHans J. Koch 	unsigned long requested_pages, actual_pages;
476beafc54cSHans J. Koch 	int ret = 0;
477beafc54cSHans J. Koch 
478beafc54cSHans J. Koch 	if (vma->vm_end < vma->vm_start)
479beafc54cSHans J. Koch 		return -EINVAL;
480beafc54cSHans J. Koch 
481beafc54cSHans J. Koch 	vma->vm_private_data = idev;
482beafc54cSHans J. Koch 
483beafc54cSHans J. Koch 	mi = uio_find_mem_index(vma);
484beafc54cSHans J. Koch 	if (mi < 0)
485beafc54cSHans J. Koch 		return -EINVAL;
486beafc54cSHans J. Koch 
487beafc54cSHans J. Koch 	requested_pages = (vma->vm_end - vma->vm_start) >> PAGE_SHIFT;
488beafc54cSHans J. Koch 	actual_pages = (idev->info->mem[mi].size + PAGE_SIZE -1) >> PAGE_SHIFT;
489beafc54cSHans J. Koch 	if (requested_pages > actual_pages)
490beafc54cSHans J. Koch 		return -EINVAL;
491beafc54cSHans J. Koch 
492beafc54cSHans J. Koch 	if (idev->info->mmap) {
493beafc54cSHans J. Koch 		if (!try_module_get(idev->owner))
494beafc54cSHans J. Koch 			return -ENODEV;
495beafc54cSHans J. Koch 		ret = idev->info->mmap(idev->info, vma);
496beafc54cSHans J. Koch 		module_put(idev->owner);
497beafc54cSHans J. Koch 		return ret;
498beafc54cSHans J. Koch 	}
499beafc54cSHans J. Koch 
500beafc54cSHans J. Koch 	switch (idev->info->mem[mi].memtype) {
501beafc54cSHans J. Koch 		case UIO_MEM_PHYS:
502beafc54cSHans J. Koch 			return uio_mmap_physical(vma);
503beafc54cSHans J. Koch 		case UIO_MEM_LOGICAL:
504beafc54cSHans J. Koch 		case UIO_MEM_VIRTUAL:
505beafc54cSHans J. Koch 			return uio_mmap_logical(vma);
506beafc54cSHans J. Koch 		default:
507beafc54cSHans J. Koch 			return -EINVAL;
508beafc54cSHans J. Koch 	}
509beafc54cSHans J. Koch }
510beafc54cSHans J. Koch 
511beafc54cSHans J. Koch static struct file_operations uio_fops = {
512beafc54cSHans J. Koch 	.owner		= THIS_MODULE,
513beafc54cSHans J. Koch 	.open		= uio_open,
514beafc54cSHans J. Koch 	.release	= uio_release,
515beafc54cSHans J. Koch 	.read		= uio_read,
516beafc54cSHans J. Koch 	.mmap		= uio_mmap,
517beafc54cSHans J. Koch 	.poll		= uio_poll,
518beafc54cSHans J. Koch 	.fasync		= uio_fasync,
519beafc54cSHans J. Koch };
520beafc54cSHans J. Koch 
521beafc54cSHans J. Koch static int uio_major_init(void)
522beafc54cSHans J. Koch {
523beafc54cSHans J. Koch 	uio_major = register_chrdev(0, "uio", &uio_fops);
524beafc54cSHans J. Koch 	if (uio_major < 0)
525beafc54cSHans J. Koch 		return uio_major;
526beafc54cSHans J. Koch 	return 0;
527beafc54cSHans J. Koch }
528beafc54cSHans J. Koch 
529beafc54cSHans J. Koch static void uio_major_cleanup(void)
530beafc54cSHans J. Koch {
531beafc54cSHans J. Koch 	unregister_chrdev(uio_major, "uio");
532beafc54cSHans J. Koch }
533beafc54cSHans J. Koch 
534beafc54cSHans J. Koch static int init_uio_class(void)
535beafc54cSHans J. Koch {
536beafc54cSHans J. Koch 	int ret = 0;
537beafc54cSHans J. Koch 
538beafc54cSHans J. Koch 	if (uio_class != NULL) {
539beafc54cSHans J. Koch 		kref_get(&uio_class->kref);
540beafc54cSHans J. Koch 		goto exit;
541beafc54cSHans J. Koch 	}
542beafc54cSHans J. Koch 
543beafc54cSHans J. Koch 	/* This is the first time in here, set everything up properly */
544beafc54cSHans J. Koch 	ret = uio_major_init();
545beafc54cSHans J. Koch 	if (ret)
546beafc54cSHans J. Koch 		goto exit;
547beafc54cSHans J. Koch 
548beafc54cSHans J. Koch 	uio_class = kzalloc(sizeof(*uio_class), GFP_KERNEL);
549beafc54cSHans J. Koch 	if (!uio_class) {
550beafc54cSHans J. Koch 		ret = -ENOMEM;
551beafc54cSHans J. Koch 		goto err_kzalloc;
552beafc54cSHans J. Koch 	}
553beafc54cSHans J. Koch 
554beafc54cSHans J. Koch 	kref_init(&uio_class->kref);
555beafc54cSHans J. Koch 	uio_class->class = class_create(THIS_MODULE, "uio");
556beafc54cSHans J. Koch 	if (IS_ERR(uio_class->class)) {
557beafc54cSHans J. Koch 		ret = IS_ERR(uio_class->class);
558beafc54cSHans J. Koch 		printk(KERN_ERR "class_create failed for uio\n");
559beafc54cSHans J. Koch 		goto err_class_create;
560beafc54cSHans J. Koch 	}
561beafc54cSHans J. Koch 	return 0;
562beafc54cSHans J. Koch 
563beafc54cSHans J. Koch err_class_create:
564beafc54cSHans J. Koch 	kfree(uio_class);
565beafc54cSHans J. Koch 	uio_class = NULL;
566beafc54cSHans J. Koch err_kzalloc:
567beafc54cSHans J. Koch 	uio_major_cleanup();
568beafc54cSHans J. Koch exit:
569beafc54cSHans J. Koch 	return ret;
570beafc54cSHans J. Koch }
571beafc54cSHans J. Koch 
572beafc54cSHans J. Koch static void release_uio_class(struct kref *kref)
573beafc54cSHans J. Koch {
574beafc54cSHans J. Koch 	/* Ok, we cheat as we know we only have one uio_class */
575beafc54cSHans J. Koch 	class_destroy(uio_class->class);
576beafc54cSHans J. Koch 	kfree(uio_class);
577beafc54cSHans J. Koch 	uio_major_cleanup();
578beafc54cSHans J. Koch 	uio_class = NULL;
579beafc54cSHans J. Koch }
580beafc54cSHans J. Koch 
581beafc54cSHans J. Koch static void uio_class_destroy(void)
582beafc54cSHans J. Koch {
583beafc54cSHans J. Koch 	if (uio_class)
584beafc54cSHans J. Koch 		kref_put(&uio_class->kref, release_uio_class);
585beafc54cSHans J. Koch }
586beafc54cSHans J. Koch 
587beafc54cSHans J. Koch /**
588beafc54cSHans J. Koch  * uio_register_device - register a new userspace IO device
589beafc54cSHans J. Koch  * @owner:	module that creates the new device
590beafc54cSHans J. Koch  * @parent:	parent device
591beafc54cSHans J. Koch  * @info:	UIO device capabilities
592beafc54cSHans J. Koch  *
593beafc54cSHans J. Koch  * returns zero on success or a negative error code.
594beafc54cSHans J. Koch  */
595beafc54cSHans J. Koch int __uio_register_device(struct module *owner,
596beafc54cSHans J. Koch 			  struct device *parent,
597beafc54cSHans J. Koch 			  struct uio_info *info)
598beafc54cSHans J. Koch {
599beafc54cSHans J. Koch 	struct uio_device *idev;
600beafc54cSHans J. Koch 	int ret = 0;
601beafc54cSHans J. Koch 
602beafc54cSHans J. Koch 	if (!parent || !info || !info->name || !info->version)
603beafc54cSHans J. Koch 		return -EINVAL;
604beafc54cSHans J. Koch 
605beafc54cSHans J. Koch 	info->uio_dev = NULL;
606beafc54cSHans J. Koch 
607beafc54cSHans J. Koch 	ret = init_uio_class();
608beafc54cSHans J. Koch 	if (ret)
609beafc54cSHans J. Koch 		return ret;
610beafc54cSHans J. Koch 
611beafc54cSHans J. Koch 	idev = kzalloc(sizeof(*idev), GFP_KERNEL);
612beafc54cSHans J. Koch 	if (!idev) {
613beafc54cSHans J. Koch 		ret = -ENOMEM;
614beafc54cSHans J. Koch 		goto err_kzalloc;
615beafc54cSHans J. Koch 	}
616beafc54cSHans J. Koch 
617beafc54cSHans J. Koch 	idev->owner = owner;
618beafc54cSHans J. Koch 	idev->info = info;
619beafc54cSHans J. Koch 	init_waitqueue_head(&idev->wait);
620beafc54cSHans J. Koch 	atomic_set(&idev->event, 0);
621beafc54cSHans J. Koch 
622beafc54cSHans J. Koch 	ret = uio_get_minor(idev);
623beafc54cSHans J. Koch 	if (ret)
624beafc54cSHans J. Koch 		goto err_get_minor;
625beafc54cSHans J. Koch 
626beafc54cSHans J. Koch 	idev->dev = device_create(uio_class->class, parent,
627beafc54cSHans J. Koch 				  MKDEV(uio_major, idev->minor),
628beafc54cSHans J. Koch 				  "uio%d", idev->minor);
629beafc54cSHans J. Koch 	if (IS_ERR(idev->dev)) {
630beafc54cSHans J. Koch 		printk(KERN_ERR "UIO: device register failed\n");
631beafc54cSHans J. Koch 		ret = PTR_ERR(idev->dev);
632beafc54cSHans J. Koch 		goto err_device_create;
633beafc54cSHans J. Koch 	}
634beafc54cSHans J. Koch 	dev_set_drvdata(idev->dev, idev);
635beafc54cSHans J. Koch 
636beafc54cSHans J. Koch 	ret = uio_dev_add_attributes(idev);
637beafc54cSHans J. Koch 	if (ret)
638beafc54cSHans J. Koch 		goto err_uio_dev_add_attributes;
639beafc54cSHans J. Koch 
640beafc54cSHans J. Koch 	info->uio_dev = idev;
641beafc54cSHans J. Koch 
642beafc54cSHans J. Koch 	if (idev->info->irq >= 0) {
643beafc54cSHans J. Koch 		ret = request_irq(idev->info->irq, uio_interrupt,
644beafc54cSHans J. Koch 				  idev->info->irq_flags, idev->info->name, idev);
645beafc54cSHans J. Koch 		if (ret)
646beafc54cSHans J. Koch 			goto err_request_irq;
647beafc54cSHans J. Koch 	}
648beafc54cSHans J. Koch 
649beafc54cSHans J. Koch 	return 0;
650beafc54cSHans J. Koch 
651beafc54cSHans J. Koch err_request_irq:
652beafc54cSHans J. Koch 	uio_dev_del_attributes(idev);
653beafc54cSHans J. Koch err_uio_dev_add_attributes:
654beafc54cSHans J. Koch 	device_destroy(uio_class->class, MKDEV(uio_major, idev->minor));
655beafc54cSHans J. Koch err_device_create:
656beafc54cSHans J. Koch 	uio_free_minor(idev);
657beafc54cSHans J. Koch err_get_minor:
658beafc54cSHans J. Koch 	kfree(idev);
659beafc54cSHans J. Koch err_kzalloc:
660beafc54cSHans J. Koch 	uio_class_destroy();
661beafc54cSHans J. Koch 	return ret;
662beafc54cSHans J. Koch }
663beafc54cSHans J. Koch EXPORT_SYMBOL_GPL(__uio_register_device);
664beafc54cSHans J. Koch 
665beafc54cSHans J. Koch /**
666beafc54cSHans J. Koch  * uio_unregister_device - unregister a industrial IO device
667beafc54cSHans J. Koch  * @info:	UIO device capabilities
668beafc54cSHans J. Koch  *
669beafc54cSHans J. Koch  */
670beafc54cSHans J. Koch void uio_unregister_device(struct uio_info *info)
671beafc54cSHans J. Koch {
672beafc54cSHans J. Koch 	struct uio_device *idev;
673beafc54cSHans J. Koch 
674beafc54cSHans J. Koch 	if (!info || !info->uio_dev)
675beafc54cSHans J. Koch 		return;
676beafc54cSHans J. Koch 
677beafc54cSHans J. Koch 	idev = info->uio_dev;
678beafc54cSHans J. Koch 
679beafc54cSHans J. Koch 	uio_free_minor(idev);
680beafc54cSHans J. Koch 
681beafc54cSHans J. Koch 	if (info->irq >= 0)
682beafc54cSHans J. Koch 		free_irq(info->irq, idev);
683beafc54cSHans J. Koch 
684beafc54cSHans J. Koch 	uio_dev_del_attributes(idev);
685beafc54cSHans J. Koch 
686beafc54cSHans J. Koch 	dev_set_drvdata(idev->dev, NULL);
687beafc54cSHans J. Koch 	device_destroy(uio_class->class, MKDEV(uio_major, idev->minor));
688beafc54cSHans J. Koch 	kfree(idev);
689beafc54cSHans J. Koch 	uio_class_destroy();
690beafc54cSHans J. Koch 
691beafc54cSHans J. Koch 	return;
692beafc54cSHans J. Koch }
693beafc54cSHans J. Koch EXPORT_SYMBOL_GPL(uio_unregister_device);
694beafc54cSHans J. Koch 
695beafc54cSHans J. Koch static int __init uio_init(void)
696beafc54cSHans J. Koch {
697beafc54cSHans J. Koch 	return 0;
698beafc54cSHans J. Koch }
699beafc54cSHans J. Koch 
700beafc54cSHans J. Koch static void __exit uio_exit(void)
701beafc54cSHans J. Koch {
702beafc54cSHans J. Koch }
703beafc54cSHans J. Koch 
704beafc54cSHans J. Koch module_init(uio_init)
705beafc54cSHans J. Koch module_exit(uio_exit)
706beafc54cSHans J. Koch MODULE_LICENSE("GPL v2");
707