xref: /openbmc/linux/drivers/media/rc/lirc_dev.c (revision a60d64b1)
132cf86f6SMauro Carvalho Chehab /*
232cf86f6SMauro Carvalho Chehab  * LIRC base driver
332cf86f6SMauro Carvalho Chehab  *
432cf86f6SMauro Carvalho Chehab  * by Artur Lipowski <alipowski@interia.pl>
532cf86f6SMauro Carvalho Chehab  *
632cf86f6SMauro Carvalho Chehab  *  This program is free software; you can redistribute it and/or modify
732cf86f6SMauro Carvalho Chehab  *  it under the terms of the GNU General Public License as published by
832cf86f6SMauro Carvalho Chehab  *  the Free Software Foundation; either version 2 of the License, or
932cf86f6SMauro Carvalho Chehab  *  (at your option) any later version.
1032cf86f6SMauro Carvalho Chehab  *
1132cf86f6SMauro Carvalho Chehab  *  This program is distributed in the hope that it will be useful,
1232cf86f6SMauro Carvalho Chehab  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
1332cf86f6SMauro Carvalho Chehab  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1432cf86f6SMauro Carvalho Chehab  *  GNU General Public License for more details.
1532cf86f6SMauro Carvalho Chehab  *
1632cf86f6SMauro Carvalho Chehab  */
1732cf86f6SMauro Carvalho Chehab 
183fac0314SAndi Shyti #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
193fac0314SAndi Shyti 
2032cf86f6SMauro Carvalho Chehab #include <linux/module.h>
21174cd4b1SIngo Molnar #include <linux/sched/signal.h>
2232cf86f6SMauro Carvalho Chehab #include <linux/ioctl.h>
2332cf86f6SMauro Carvalho Chehab #include <linux/poll.h>
2432cf86f6SMauro Carvalho Chehab #include <linux/mutex.h>
2532cf86f6SMauro Carvalho Chehab #include <linux/device.h>
2632cf86f6SMauro Carvalho Chehab #include <linux/cdev.h>
2746c8f477SDavid Härdeman #include <linux/idr.h>
2832cf86f6SMauro Carvalho Chehab 
29*a60d64b1SSean Young #include "rc-core-priv.h"
3032cf86f6SMauro Carvalho Chehab #include <media/lirc.h>
3132cf86f6SMauro Carvalho Chehab #include <media/lirc_dev.h>
3232cf86f6SMauro Carvalho Chehab 
3332cf86f6SMauro Carvalho Chehab #define LOGHEAD		"lirc_dev (%s[%d]): "
3432cf86f6SMauro Carvalho Chehab 
3532cf86f6SMauro Carvalho Chehab static dev_t lirc_base_dev;
3632cf86f6SMauro Carvalho Chehab 
3746c8f477SDavid Härdeman /* Used to keep track of allocated lirc devices */
3846c8f477SDavid Härdeman #define LIRC_MAX_DEVICES 256
3946c8f477SDavid Härdeman static DEFINE_IDA(lirc_ida);
4032cf86f6SMauro Carvalho Chehab 
4132cf86f6SMauro Carvalho Chehab /* Only used for sysfs but defined to void otherwise */
4232cf86f6SMauro Carvalho Chehab static struct class *lirc_class;
4332cf86f6SMauro Carvalho Chehab 
44b15e3937SDavid Härdeman static void lirc_release_device(struct device *ld)
4532cf86f6SMauro Carvalho Chehab {
46b15e3937SDavid Härdeman 	struct lirc_dev *d = container_of(ld, struct lirc_dev, dev);
47a607f51eSSean Young 
48b15e3937SDavid Härdeman 	put_device(d->dev.parent);
49b15e3937SDavid Härdeman 
50b15e3937SDavid Härdeman 	if (d->buf_internal) {
51b15e3937SDavid Härdeman 		lirc_buffer_free(d->buf);
52b15e3937SDavid Härdeman 		kfree(d->buf);
53b15e3937SDavid Härdeman 		d->buf = NULL;
5432cf86f6SMauro Carvalho Chehab 	}
55b15e3937SDavid Härdeman 	kfree(d);
56b15e3937SDavid Härdeman 	module_put(THIS_MODULE);
573381b779SDavid Härdeman }
583381b779SDavid Härdeman 
59b15e3937SDavid Härdeman static int lirc_allocate_buffer(struct lirc_dev *d)
603381b779SDavid Härdeman {
61b15e3937SDavid Härdeman 	int err;
6274c839b2SSean Young 
63b15e3937SDavid Härdeman 	if (d->buf) {
64b15e3937SDavid Härdeman 		d->buf_internal = false;
65b15e3937SDavid Härdeman 		return 0;
6632cf86f6SMauro Carvalho Chehab 	}
6732cf86f6SMauro Carvalho Chehab 
68b15e3937SDavid Härdeman 	d->buf = kmalloc(sizeof(*d->buf), GFP_KERNEL);
69b15e3937SDavid Härdeman 	if (!d->buf)
70b15e3937SDavid Härdeman 		return -ENOMEM;
716fa99e1aSAndi Shyti 
72b15e3937SDavid Härdeman 	err = lirc_buffer_init(d->buf, d->chunk_size, d->buffer_size);
736fa99e1aSAndi Shyti 	if (err) {
74b15e3937SDavid Härdeman 		kfree(d->buf);
75b15e3937SDavid Härdeman 		d->buf = NULL;
7670143984SAndi Shyti 		return err;
776fa99e1aSAndi Shyti 	}
786fa99e1aSAndi Shyti 
79b15e3937SDavid Härdeman 	d->buf_internal = true;
80b15e3937SDavid Härdeman 	return 0;
81b15e3937SDavid Härdeman }
82b15e3937SDavid Härdeman 
836ecccc37SDavid Härdeman struct lirc_dev *
846ecccc37SDavid Härdeman lirc_allocate_device(void)
856ecccc37SDavid Härdeman {
86b15e3937SDavid Härdeman 	struct lirc_dev *d;
87b15e3937SDavid Härdeman 
88b15e3937SDavid Härdeman 	d = kzalloc(sizeof(*d), GFP_KERNEL);
89b15e3937SDavid Härdeman 	if (d) {
90b15e3937SDavid Härdeman 		mutex_init(&d->mutex);
91b15e3937SDavid Härdeman 		device_initialize(&d->dev);
92b15e3937SDavid Härdeman 		d->dev.class = lirc_class;
93b15e3937SDavid Härdeman 		d->dev.release = lirc_release_device;
94b15e3937SDavid Härdeman 		__module_get(THIS_MODULE);
95b15e3937SDavid Härdeman 	}
96b15e3937SDavid Härdeman 
97b15e3937SDavid Härdeman 	return d;
986ecccc37SDavid Härdeman }
996ecccc37SDavid Härdeman EXPORT_SYMBOL(lirc_allocate_device);
1006ecccc37SDavid Härdeman 
1016ecccc37SDavid Härdeman void lirc_free_device(struct lirc_dev *d)
1026ecccc37SDavid Härdeman {
103b15e3937SDavid Härdeman 	if (!d)
104b15e3937SDavid Härdeman 		return;
105b15e3937SDavid Härdeman 
106b15e3937SDavid Härdeman 	put_device(&d->dev);
1076ecccc37SDavid Härdeman }
1086ecccc37SDavid Härdeman EXPORT_SYMBOL(lirc_free_device);
1096ecccc37SDavid Härdeman 
1105ddc9c09SDavid Härdeman int lirc_register_device(struct lirc_dev *d)
11132cf86f6SMauro Carvalho Chehab {
11246c8f477SDavid Härdeman 	int minor;
11332cf86f6SMauro Carvalho Chehab 	int err;
11432cf86f6SMauro Carvalho Chehab 
11532cf86f6SMauro Carvalho Chehab 	if (!d) {
1163fac0314SAndi Shyti 		pr_err("driver pointer must be not NULL!\n");
11754fcecafSAndi Shyti 		return -EBADRQC;
11832cf86f6SMauro Carvalho Chehab 	}
11932cf86f6SMauro Carvalho Chehab 
120b15e3937SDavid Härdeman 	if (!d->dev.parent) {
121b15e3937SDavid Härdeman 		pr_err("dev parent pointer not filled in!\n");
12254fcecafSAndi Shyti 		return -EINVAL;
12332cf86f6SMauro Carvalho Chehab 	}
12432cf86f6SMauro Carvalho Chehab 
125712551f0SDavid Härdeman 	if (!d->fops) {
126712551f0SDavid Härdeman 		pr_err("fops pointer not filled in!\n");
127712551f0SDavid Härdeman 		return -EINVAL;
128712551f0SDavid Härdeman 	}
129712551f0SDavid Härdeman 
130b15e3937SDavid Härdeman 	if (!d->buf && d->chunk_size < 1) {
131b145ef94SDavid Härdeman 		pr_err("chunk_size must be set!\n");
132b145ef94SDavid Härdeman 		return -EINVAL;
133b145ef94SDavid Härdeman 	}
134b145ef94SDavid Härdeman 
135b15e3937SDavid Härdeman 	if (!d->buf && d->buffer_size < 1) {
136b145ef94SDavid Härdeman 		pr_err("buffer_size must be set!\n");
137b145ef94SDavid Härdeman 		return -EINVAL;
138b145ef94SDavid Härdeman 	}
139b145ef94SDavid Härdeman 
140b15e3937SDavid Härdeman 	if (!d->buf && !(d->fops && d->fops->read &&
14114db9fc2SAndi Shyti 			 d->fops->poll && d->fops->unlocked_ioctl)) {
142b15e3937SDavid Härdeman 		dev_err(&d->dev, "undefined read, poll, ioctl\n");
14354fcecafSAndi Shyti 		return -EBADRQC;
14432cf86f6SMauro Carvalho Chehab 	}
14532cf86f6SMauro Carvalho Chehab 
1463381b779SDavid Härdeman 	/* some safety check 8-) */
1473381b779SDavid Härdeman 	d->name[sizeof(d->name) - 1] = '\0';
1483381b779SDavid Härdeman 
1493381b779SDavid Härdeman 	if (LIRC_CAN_REC(d->features)) {
150b15e3937SDavid Härdeman 		err = lirc_allocate_buffer(d);
151b15e3937SDavid Härdeman 		if (err)
1523381b779SDavid Härdeman 			return err;
1533381b779SDavid Härdeman 	}
1543381b779SDavid Härdeman 
15546c8f477SDavid Härdeman 	minor = ida_simple_get(&lirc_ida, 0, LIRC_MAX_DEVICES, GFP_KERNEL);
156b15e3937SDavid Härdeman 	if (minor < 0)
15746c8f477SDavid Härdeman 		return minor;
15832cf86f6SMauro Carvalho Chehab 
15932cf86f6SMauro Carvalho Chehab 	d->minor = minor;
160b15e3937SDavid Härdeman 	d->dev.devt = MKDEV(MAJOR(lirc_base_dev), d->minor);
161b15e3937SDavid Härdeman 	dev_set_name(&d->dev, "lirc%d", d->minor);
16232cf86f6SMauro Carvalho Chehab 
163b15e3937SDavid Härdeman 	cdev_init(&d->cdev, d->fops);
164b15e3937SDavid Härdeman 	d->cdev.owner = d->owner;
165b15e3937SDavid Härdeman 	d->attached = true;
16632cf86f6SMauro Carvalho Chehab 
167b15e3937SDavid Härdeman 	err = cdev_device_add(&d->cdev, &d->dev);
1683381b779SDavid Härdeman 	if (err) {
16946c8f477SDavid Härdeman 		ida_simple_remove(&lirc_ida, minor);
1703381b779SDavid Härdeman 		return err;
1713381b779SDavid Härdeman 	}
17232cf86f6SMauro Carvalho Chehab 
173b15e3937SDavid Härdeman 	get_device(d->dev.parent);
174a607f51eSSean Young 
175b15e3937SDavid Härdeman 	dev_info(&d->dev, "lirc_dev: driver %s registered at minor = %d\n",
176b15e3937SDavid Härdeman 		 d->name, d->minor);
17756481f00SDavid Härdeman 
178c3c6dd75SDavid Härdeman 	return 0;
17932cf86f6SMauro Carvalho Chehab }
1805ddc9c09SDavid Härdeman EXPORT_SYMBOL(lirc_register_device);
18132cf86f6SMauro Carvalho Chehab 
1825ddc9c09SDavid Härdeman void lirc_unregister_device(struct lirc_dev *d)
18332cf86f6SMauro Carvalho Chehab {
184b15e3937SDavid Härdeman 	if (!d)
185c3c6dd75SDavid Härdeman 		return;
18632cf86f6SMauro Carvalho Chehab 
187b15e3937SDavid Härdeman 	dev_dbg(&d->dev, "lirc_dev: driver %s unregistered from minor = %d\n",
188c3c6dd75SDavid Härdeman 		d->name, d->minor);
18932cf86f6SMauro Carvalho Chehab 
190b15e3937SDavid Härdeman 	mutex_lock(&d->mutex);
1913381b779SDavid Härdeman 
192b15e3937SDavid Härdeman 	d->attached = false;
193b15e3937SDavid Härdeman 	if (d->open) {
194b15e3937SDavid Härdeman 		dev_dbg(&d->dev, LOGHEAD "releasing opened driver\n",
195c3c6dd75SDavid Härdeman 			d->name, d->minor);
196b15e3937SDavid Härdeman 		wake_up_interruptible(&d->buf->wait_poll);
19774c839b2SSean Young 	}
19874c839b2SSean Young 
199b15e3937SDavid Härdeman 	mutex_unlock(&d->mutex);
20032cf86f6SMauro Carvalho Chehab 
201b15e3937SDavid Härdeman 	cdev_device_del(&d->cdev, &d->dev);
20246c8f477SDavid Härdeman 	ida_simple_remove(&lirc_ida, d->minor);
203b15e3937SDavid Härdeman 	put_device(&d->dev);
20432cf86f6SMauro Carvalho Chehab }
2055ddc9c09SDavid Härdeman EXPORT_SYMBOL(lirc_unregister_device);
20632cf86f6SMauro Carvalho Chehab 
20732cf86f6SMauro Carvalho Chehab int lirc_dev_fop_open(struct inode *inode, struct file *file)
20832cf86f6SMauro Carvalho Chehab {
209b15e3937SDavid Härdeman 	struct lirc_dev *d = container_of(inode->i_cdev, struct lirc_dev, cdev);
210de226ec8SDavid Härdeman 	int retval;
21132cf86f6SMauro Carvalho Chehab 
212b15e3937SDavid Härdeman 	dev_dbg(&d->dev, LOGHEAD "open called\n", d->name, d->minor);
21332cf86f6SMauro Carvalho Chehab 
214b15e3937SDavid Härdeman 	retval = mutex_lock_interruptible(&d->mutex);
2153381b779SDavid Härdeman 	if (retval)
2163381b779SDavid Härdeman 		return retval;
2173381b779SDavid Härdeman 
218b15e3937SDavid Härdeman 	if (!d->attached) {
2193381b779SDavid Härdeman 		retval = -ENODEV;
2203381b779SDavid Härdeman 		goto out;
2213381b779SDavid Härdeman 	}
2223381b779SDavid Härdeman 
223b15e3937SDavid Härdeman 	if (d->open) {
2243381b779SDavid Härdeman 		retval = -EBUSY;
2253381b779SDavid Härdeman 		goto out;
2263381b779SDavid Härdeman 	}
22732cf86f6SMauro Carvalho Chehab 
228b15e3937SDavid Härdeman 	if (d->rdev) {
229b15e3937SDavid Härdeman 		retval = rc_open(d->rdev);
230ca7a722dSSrinivas Kandagatla 		if (retval)
2313381b779SDavid Härdeman 			goto out;
232ca7a722dSSrinivas Kandagatla 	}
233ca7a722dSSrinivas Kandagatla 
234b15e3937SDavid Härdeman 	if (d->buf)
235b15e3937SDavid Härdeman 		lirc_buffer_clear(d->buf);
2362c5a1f44SDavid Härdeman 
237b15e3937SDavid Härdeman 	d->open++;
23832cf86f6SMauro Carvalho Chehab 
239*a60d64b1SSean Young 	file->private_data = d->rdev;
24032cf86f6SMauro Carvalho Chehab 	nonseekable_open(inode, file);
241b15e3937SDavid Härdeman 	mutex_unlock(&d->mutex);
24232cf86f6SMauro Carvalho Chehab 
243de226ec8SDavid Härdeman 	return 0;
2443381b779SDavid Härdeman 
2453381b779SDavid Härdeman out:
246b15e3937SDavid Härdeman 	mutex_unlock(&d->mutex);
2473381b779SDavid Härdeman 	return retval;
24832cf86f6SMauro Carvalho Chehab }
24932cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_open);
25032cf86f6SMauro Carvalho Chehab 
25132cf86f6SMauro Carvalho Chehab int lirc_dev_fop_close(struct inode *inode, struct file *file)
25232cf86f6SMauro Carvalho Chehab {
253*a60d64b1SSean Young 	struct rc_dev *rcdev = file->private_data;
254*a60d64b1SSean Young 	struct lirc_dev *d = rcdev->lirc_dev;
25532cf86f6SMauro Carvalho Chehab 
256b15e3937SDavid Härdeman 	mutex_lock(&d->mutex);
25732cf86f6SMauro Carvalho Chehab 
258*a60d64b1SSean Young 	rc_close(rcdev);
259b15e3937SDavid Härdeman 	d->open--;
2603381b779SDavid Härdeman 
261b15e3937SDavid Härdeman 	mutex_unlock(&d->mutex);
26232cf86f6SMauro Carvalho Chehab 
26332cf86f6SMauro Carvalho Chehab 	return 0;
26432cf86f6SMauro Carvalho Chehab }
26532cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_close);
26632cf86f6SMauro Carvalho Chehab 
26732cf86f6SMauro Carvalho Chehab unsigned int lirc_dev_fop_poll(struct file *file, poll_table *wait)
26832cf86f6SMauro Carvalho Chehab {
269*a60d64b1SSean Young 	struct rc_dev *rcdev = file->private_data;
270*a60d64b1SSean Young 	struct lirc_dev *d = rcdev->lirc_dev;
27132cf86f6SMauro Carvalho Chehab 	unsigned int ret;
27232cf86f6SMauro Carvalho Chehab 
273b15e3937SDavid Härdeman 	if (!d->attached)
27429debf3dSDavid Härdeman 		return POLLHUP | POLLERR;
27532cf86f6SMauro Carvalho Chehab 
276b15e3937SDavid Härdeman 	if (d->buf) {
277b15e3937SDavid Härdeman 		poll_wait(file, &d->buf->wait_poll, wait);
27832cf86f6SMauro Carvalho Chehab 
279b15e3937SDavid Härdeman 		if (lirc_buffer_empty(d->buf))
28032cf86f6SMauro Carvalho Chehab 			ret = 0;
28132cf86f6SMauro Carvalho Chehab 		else
28232cf86f6SMauro Carvalho Chehab 			ret = POLLIN | POLLRDNORM;
283b15e3937SDavid Härdeman 	} else {
28432cf86f6SMauro Carvalho Chehab 		ret = POLLERR;
285b15e3937SDavid Härdeman 	}
28632cf86f6SMauro Carvalho Chehab 
287b15e3937SDavid Härdeman 	dev_dbg(&d->dev, LOGHEAD "poll result = %d\n", d->name, d->minor, ret);
28832cf86f6SMauro Carvalho Chehab 
28932cf86f6SMauro Carvalho Chehab 	return ret;
29032cf86f6SMauro Carvalho Chehab }
29132cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_poll);
29232cf86f6SMauro Carvalho Chehab 
29332cf86f6SMauro Carvalho Chehab long lirc_dev_fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
29432cf86f6SMauro Carvalho Chehab {
295*a60d64b1SSean Young 	struct rc_dev *rcdev = file->private_data;
296*a60d64b1SSean Young 	struct lirc_dev *d = rcdev->lirc_dev;
29732cf86f6SMauro Carvalho Chehab 	__u32 mode;
2983381b779SDavid Härdeman 	int result;
29932cf86f6SMauro Carvalho Chehab 
300b15e3937SDavid Härdeman 	dev_dbg(&d->dev, LOGHEAD "ioctl called (0x%x)\n",
301b15e3937SDavid Härdeman 		d->name, d->minor, cmd);
30232cf86f6SMauro Carvalho Chehab 
303b15e3937SDavid Härdeman 	result = mutex_lock_interruptible(&d->mutex);
3043381b779SDavid Härdeman 	if (result)
3053381b779SDavid Härdeman 		return result;
30632cf86f6SMauro Carvalho Chehab 
307b15e3937SDavid Härdeman 	if (!d->attached) {
3083381b779SDavid Härdeman 		result = -ENODEV;
3093381b779SDavid Härdeman 		goto out;
3103381b779SDavid Härdeman 	}
31132cf86f6SMauro Carvalho Chehab 
31232cf86f6SMauro Carvalho Chehab 	switch (cmd) {
31332cf86f6SMauro Carvalho Chehab 	case LIRC_GET_FEATURES:
314b15e3937SDavid Härdeman 		result = put_user(d->features, (__u32 __user *)arg);
31532cf86f6SMauro Carvalho Chehab 		break;
31632cf86f6SMauro Carvalho Chehab 	case LIRC_GET_REC_MODE:
317b15e3937SDavid Härdeman 		if (!LIRC_CAN_REC(d->features)) {
318b4088094SAndi Shyti 			result = -ENOTTY;
31932cf86f6SMauro Carvalho Chehab 			break;
32032cf86f6SMauro Carvalho Chehab 		}
32132cf86f6SMauro Carvalho Chehab 
32232cf86f6SMauro Carvalho Chehab 		result = put_user(LIRC_REC2MODE
323b15e3937SDavid Härdeman 				  (d->features & LIRC_CAN_REC_MASK),
32460519af3SHans Verkuil 				  (__u32 __user *)arg);
32532cf86f6SMauro Carvalho Chehab 		break;
32632cf86f6SMauro Carvalho Chehab 	case LIRC_SET_REC_MODE:
327b15e3937SDavid Härdeman 		if (!LIRC_CAN_REC(d->features)) {
328b4088094SAndi Shyti 			result = -ENOTTY;
32932cf86f6SMauro Carvalho Chehab 			break;
33032cf86f6SMauro Carvalho Chehab 		}
33132cf86f6SMauro Carvalho Chehab 
33260519af3SHans Verkuil 		result = get_user(mode, (__u32 __user *)arg);
333b15e3937SDavid Härdeman 		if (!result && !(LIRC_MODE2REC(mode) & d->features))
33432cf86f6SMauro Carvalho Chehab 			result = -EINVAL;
33532cf86f6SMauro Carvalho Chehab 		/*
33632cf86f6SMauro Carvalho Chehab 		 * FIXME: We should actually set the mode somehow but
33732cf86f6SMauro Carvalho Chehab 		 * for now, lirc_serial doesn't support mode changing either
33832cf86f6SMauro Carvalho Chehab 		 */
33932cf86f6SMauro Carvalho Chehab 		break;
34032cf86f6SMauro Carvalho Chehab 	default:
3415c862758SSean Young 		result = -ENOTTY;
34232cf86f6SMauro Carvalho Chehab 	}
34332cf86f6SMauro Carvalho Chehab 
3443381b779SDavid Härdeman out:
345b15e3937SDavid Härdeman 	mutex_unlock(&d->mutex);
34632cf86f6SMauro Carvalho Chehab 	return result;
34732cf86f6SMauro Carvalho Chehab }
34832cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_ioctl);
34932cf86f6SMauro Carvalho Chehab 
35032cf86f6SMauro Carvalho Chehab ssize_t lirc_dev_fop_read(struct file *file,
3510e835087SDan Carpenter 			  char __user *buffer,
35232cf86f6SMauro Carvalho Chehab 			  size_t length,
35332cf86f6SMauro Carvalho Chehab 			  loff_t *ppos)
35432cf86f6SMauro Carvalho Chehab {
355*a60d64b1SSean Young 	struct rc_dev *rcdev = file->private_data;
356*a60d64b1SSean Young 	struct lirc_dev *d = rcdev->lirc_dev;
35732cf86f6SMauro Carvalho Chehab 	unsigned char *buf;
3583381b779SDavid Härdeman 	int ret, written = 0;
35932cf86f6SMauro Carvalho Chehab 	DECLARE_WAITQUEUE(wait, current);
36032cf86f6SMauro Carvalho Chehab 
361b15e3937SDavid Härdeman 	buf = kzalloc(d->buf->chunk_size, GFP_KERNEL);
36232cf86f6SMauro Carvalho Chehab 	if (!buf)
36332cf86f6SMauro Carvalho Chehab 		return -ENOMEM;
36432cf86f6SMauro Carvalho Chehab 
365b15e3937SDavid Härdeman 	dev_dbg(&d->dev, LOGHEAD "read called\n", d->name, d->minor);
366b15e3937SDavid Härdeman 
367b15e3937SDavid Härdeman 	ret = mutex_lock_interruptible(&d->mutex);
3683381b779SDavid Härdeman 	if (ret) {
3693381b779SDavid Härdeman 		kfree(buf);
3703381b779SDavid Härdeman 		return ret;
37132cf86f6SMauro Carvalho Chehab 	}
3723381b779SDavid Härdeman 
373b15e3937SDavid Härdeman 	if (!d->attached) {
37432cf86f6SMauro Carvalho Chehab 		ret = -ENODEV;
37532cf86f6SMauro Carvalho Chehab 		goto out_locked;
37632cf86f6SMauro Carvalho Chehab 	}
37732cf86f6SMauro Carvalho Chehab 
378b15e3937SDavid Härdeman 	if (!LIRC_CAN_REC(d->features)) {
3793381b779SDavid Härdeman 		ret = -EINVAL;
3803381b779SDavid Härdeman 		goto out_locked;
3813381b779SDavid Härdeman 	}
3823381b779SDavid Härdeman 
383b15e3937SDavid Härdeman 	if (length % d->buf->chunk_size) {
38432cf86f6SMauro Carvalho Chehab 		ret = -EINVAL;
38532cf86f6SMauro Carvalho Chehab 		goto out_locked;
38632cf86f6SMauro Carvalho Chehab 	}
38732cf86f6SMauro Carvalho Chehab 
38832cf86f6SMauro Carvalho Chehab 	/*
38932cf86f6SMauro Carvalho Chehab 	 * we add ourselves to the task queue before buffer check
39032cf86f6SMauro Carvalho Chehab 	 * to avoid losing scan code (in case when queue is awaken somewhere
39132cf86f6SMauro Carvalho Chehab 	 * between while condition checking and scheduling)
39232cf86f6SMauro Carvalho Chehab 	 */
393b15e3937SDavid Härdeman 	add_wait_queue(&d->buf->wait_poll, &wait);
39432cf86f6SMauro Carvalho Chehab 
39532cf86f6SMauro Carvalho Chehab 	/*
39632cf86f6SMauro Carvalho Chehab 	 * while we didn't provide 'length' bytes, device is opened in blocking
39732cf86f6SMauro Carvalho Chehab 	 * mode and 'copy_to_user' is happy, wait for data.
39832cf86f6SMauro Carvalho Chehab 	 */
39932cf86f6SMauro Carvalho Chehab 	while (written < length && ret == 0) {
400b15e3937SDavid Härdeman 		if (lirc_buffer_empty(d->buf)) {
40132cf86f6SMauro Carvalho Chehab 			/* According to the read(2) man page, 'written' can be
40232cf86f6SMauro Carvalho Chehab 			 * returned as less than 'length', instead of blocking
40332cf86f6SMauro Carvalho Chehab 			 * again, returning -EWOULDBLOCK, or returning
40462e92682SAndi Shyti 			 * -ERESTARTSYS
40562e92682SAndi Shyti 			 */
40632cf86f6SMauro Carvalho Chehab 			if (written)
40732cf86f6SMauro Carvalho Chehab 				break;
40832cf86f6SMauro Carvalho Chehab 			if (file->f_flags & O_NONBLOCK) {
40932cf86f6SMauro Carvalho Chehab 				ret = -EWOULDBLOCK;
41032cf86f6SMauro Carvalho Chehab 				break;
41132cf86f6SMauro Carvalho Chehab 			}
41232cf86f6SMauro Carvalho Chehab 			if (signal_pending(current)) {
41332cf86f6SMauro Carvalho Chehab 				ret = -ERESTARTSYS;
41432cf86f6SMauro Carvalho Chehab 				break;
41532cf86f6SMauro Carvalho Chehab 			}
41632cf86f6SMauro Carvalho Chehab 
417b15e3937SDavid Härdeman 			mutex_unlock(&d->mutex);
41832cf86f6SMauro Carvalho Chehab 			set_current_state(TASK_INTERRUPTIBLE);
41912accdcbSSean Young 			schedule();
42012accdcbSSean Young 			set_current_state(TASK_RUNNING);
42132cf86f6SMauro Carvalho Chehab 
422b15e3937SDavid Härdeman 			ret = mutex_lock_interruptible(&d->mutex);
4233381b779SDavid Härdeman 			if (ret) {
424b15e3937SDavid Härdeman 				remove_wait_queue(&d->buf->wait_poll, &wait);
42532cf86f6SMauro Carvalho Chehab 				goto out_unlocked;
42632cf86f6SMauro Carvalho Chehab 			}
42732cf86f6SMauro Carvalho Chehab 
428b15e3937SDavid Härdeman 			if (!d->attached) {
42932cf86f6SMauro Carvalho Chehab 				ret = -ENODEV;
430c77d17c0SSean Young 				goto out_locked;
43132cf86f6SMauro Carvalho Chehab 			}
43232cf86f6SMauro Carvalho Chehab 		} else {
433b15e3937SDavid Härdeman 			lirc_buffer_read(d->buf, buf);
43460519af3SHans Verkuil 			ret = copy_to_user((void __user *)buffer+written, buf,
435b15e3937SDavid Härdeman 					   d->buf->chunk_size);
43632cf86f6SMauro Carvalho Chehab 			if (!ret)
437b15e3937SDavid Härdeman 				written += d->buf->chunk_size;
43832cf86f6SMauro Carvalho Chehab 			else
43932cf86f6SMauro Carvalho Chehab 				ret = -EFAULT;
44032cf86f6SMauro Carvalho Chehab 		}
44132cf86f6SMauro Carvalho Chehab 	}
44232cf86f6SMauro Carvalho Chehab 
443b15e3937SDavid Härdeman 	remove_wait_queue(&d->buf->wait_poll, &wait);
44432cf86f6SMauro Carvalho Chehab 
44532cf86f6SMauro Carvalho Chehab out_locked:
446b15e3937SDavid Härdeman 	mutex_unlock(&d->mutex);
44732cf86f6SMauro Carvalho Chehab 
44832cf86f6SMauro Carvalho Chehab out_unlocked:
44932cf86f6SMauro Carvalho Chehab 	kfree(buf);
45032cf86f6SMauro Carvalho Chehab 
45132cf86f6SMauro Carvalho Chehab 	return ret ? ret : written;
45232cf86f6SMauro Carvalho Chehab }
45332cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_read);
45432cf86f6SMauro Carvalho Chehab 
455*a60d64b1SSean Young int __init lirc_dev_init(void)
45632cf86f6SMauro Carvalho Chehab {
45732cf86f6SMauro Carvalho Chehab 	int retval;
45832cf86f6SMauro Carvalho Chehab 
45932cf86f6SMauro Carvalho Chehab 	lirc_class = class_create(THIS_MODULE, "lirc");
46032cf86f6SMauro Carvalho Chehab 	if (IS_ERR(lirc_class)) {
4613fac0314SAndi Shyti 		pr_err("class_create failed\n");
46254fcecafSAndi Shyti 		return PTR_ERR(lirc_class);
46332cf86f6SMauro Carvalho Chehab 	}
46432cf86f6SMauro Carvalho Chehab 
46546c8f477SDavid Härdeman 	retval = alloc_chrdev_region(&lirc_base_dev, 0, LIRC_MAX_DEVICES,
466463015ddSDavid Härdeman 				     "BaseRemoteCtl");
46732cf86f6SMauro Carvalho Chehab 	if (retval) {
46832cf86f6SMauro Carvalho Chehab 		class_destroy(lirc_class);
4693fac0314SAndi Shyti 		pr_err("alloc_chrdev_region failed\n");
47054fcecafSAndi Shyti 		return retval;
47132cf86f6SMauro Carvalho Chehab 	}
47232cf86f6SMauro Carvalho Chehab 
4733fac0314SAndi Shyti 	pr_info("IR Remote Control driver registered, major %d\n",
4743fac0314SAndi Shyti 						MAJOR(lirc_base_dev));
47532cf86f6SMauro Carvalho Chehab 
47654fcecafSAndi Shyti 	return 0;
47732cf86f6SMauro Carvalho Chehab }
47832cf86f6SMauro Carvalho Chehab 
479*a60d64b1SSean Young void __exit lirc_dev_exit(void)
48032cf86f6SMauro Carvalho Chehab {
48132cf86f6SMauro Carvalho Chehab 	class_destroy(lirc_class);
48246c8f477SDavid Härdeman 	unregister_chrdev_region(lirc_base_dev, LIRC_MAX_DEVICES);
48332cf86f6SMauro Carvalho Chehab }
484