xref: /openbmc/linux/drivers/media/rc/lirc_dev.c (revision 615cd3fe)
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>
2732cf86f6SMauro Carvalho Chehab 
28ca7a722dSSrinivas Kandagatla #include <media/rc-core.h>
2932cf86f6SMauro Carvalho Chehab #include <media/lirc.h>
3032cf86f6SMauro Carvalho Chehab #include <media/lirc_dev.h>
3132cf86f6SMauro Carvalho Chehab 
3232cf86f6SMauro Carvalho Chehab #define LOGHEAD		"lirc_dev (%s[%d]): "
3332cf86f6SMauro Carvalho Chehab 
3432cf86f6SMauro Carvalho Chehab static dev_t lirc_base_dev;
3532cf86f6SMauro Carvalho Chehab 
3632cf86f6SMauro Carvalho Chehab struct irctl {
3732cf86f6SMauro Carvalho Chehab 	struct lirc_driver d;
3832cf86f6SMauro Carvalho Chehab 	int attached;
3932cf86f6SMauro Carvalho Chehab 	int open;
4032cf86f6SMauro Carvalho Chehab 
4132cf86f6SMauro Carvalho Chehab 	struct mutex irctl_lock;
4232cf86f6SMauro Carvalho Chehab 	struct lirc_buffer *buf;
430f7c4063SDavid Härdeman 	bool buf_internal;
4432cf86f6SMauro Carvalho Chehab 	unsigned int chunk_size;
4532cf86f6SMauro Carvalho Chehab 
4674c839b2SSean Young 	struct device dev;
4774c839b2SSean Young 	struct cdev cdev;
4832cf86f6SMauro Carvalho Chehab };
4932cf86f6SMauro Carvalho Chehab 
5032cf86f6SMauro Carvalho Chehab static DEFINE_MUTEX(lirc_dev_lock);
5132cf86f6SMauro Carvalho Chehab 
5232cf86f6SMauro Carvalho Chehab static struct irctl *irctls[MAX_IRCTL_DEVICES];
5332cf86f6SMauro Carvalho Chehab 
5432cf86f6SMauro Carvalho Chehab /* Only used for sysfs but defined to void otherwise */
5532cf86f6SMauro Carvalho Chehab static struct class *lirc_class;
5632cf86f6SMauro Carvalho Chehab 
5774c839b2SSean Young static void lirc_release(struct device *ld)
5832cf86f6SMauro Carvalho Chehab {
5974c839b2SSean Young 	struct irctl *ir = container_of(ld, struct irctl, dev);
6074c839b2SSean Young 
61a607f51eSSean Young 	put_device(ir->dev.parent);
62a607f51eSSean Young 
630f7c4063SDavid Härdeman 	if (ir->buf_internal) {
6432cf86f6SMauro Carvalho Chehab 		lirc_buffer_free(ir->buf);
6532cf86f6SMauro Carvalho Chehab 		kfree(ir->buf);
6632cf86f6SMauro Carvalho Chehab 	}
6774c839b2SSean Young 
6874c839b2SSean Young 	mutex_lock(&lirc_dev_lock);
6974c839b2SSean Young 	irctls[ir->d.minor] = NULL;
7074c839b2SSean Young 	mutex_unlock(&lirc_dev_lock);
7174c839b2SSean Young 	kfree(ir);
7232cf86f6SMauro Carvalho Chehab }
7332cf86f6SMauro Carvalho Chehab 
746fa99e1aSAndi Shyti static int lirc_allocate_buffer(struct irctl *ir)
756fa99e1aSAndi Shyti {
7670143984SAndi Shyti 	int err = 0;
776fa99e1aSAndi Shyti 	int bytes_in_key;
786fa99e1aSAndi Shyti 	unsigned int chunk_size;
796fa99e1aSAndi Shyti 	unsigned int buffer_size;
806fa99e1aSAndi Shyti 	struct lirc_driver *d = &ir->d;
816fa99e1aSAndi Shyti 
826fa99e1aSAndi Shyti 	bytes_in_key = BITS_TO_LONGS(d->code_length) +
836fa99e1aSAndi Shyti 						(d->code_length % 8 ? 1 : 0);
846fa99e1aSAndi Shyti 	buffer_size = d->buffer_size ? d->buffer_size : BUFLEN / bytes_in_key;
856fa99e1aSAndi Shyti 	chunk_size  = d->chunk_size  ? d->chunk_size  : bytes_in_key;
866fa99e1aSAndi Shyti 
876fa99e1aSAndi Shyti 	if (d->rbuf) {
886fa99e1aSAndi Shyti 		ir->buf = d->rbuf;
890f7c4063SDavid Härdeman 		ir->buf_internal = false;
906fa99e1aSAndi Shyti 	} else {
916fa99e1aSAndi Shyti 		ir->buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL);
9270143984SAndi Shyti 		if (!ir->buf) {
9370143984SAndi Shyti 			err = -ENOMEM;
9470143984SAndi Shyti 			goto out;
9570143984SAndi Shyti 		}
966fa99e1aSAndi Shyti 
976fa99e1aSAndi Shyti 		err = lirc_buffer_init(ir->buf, chunk_size, buffer_size);
986fa99e1aSAndi Shyti 		if (err) {
996fa99e1aSAndi Shyti 			kfree(ir->buf);
1000f7c4063SDavid Härdeman 			ir->buf = NULL;
10170143984SAndi Shyti 			goto out;
1026fa99e1aSAndi Shyti 		}
1030f7c4063SDavid Härdeman 
1040f7c4063SDavid Härdeman 		ir->buf_internal = true;
10556481f00SDavid Härdeman 		d->rbuf = ir->buf;
1066fa99e1aSAndi Shyti 	}
1076fa99e1aSAndi Shyti 	ir->chunk_size = ir->buf->chunk_size;
1086fa99e1aSAndi Shyti 
10970143984SAndi Shyti out:
11070143984SAndi Shyti 	return err;
1116fa99e1aSAndi Shyti }
1126fa99e1aSAndi Shyti 
11356481f00SDavid Härdeman int lirc_register_driver(struct lirc_driver *d)
11432cf86f6SMauro Carvalho Chehab {
11532cf86f6SMauro Carvalho Chehab 	struct irctl *ir;
116c3c6dd75SDavid Härdeman 	unsigned int minor;
11732cf86f6SMauro Carvalho Chehab 	int err;
11832cf86f6SMauro Carvalho Chehab 
11932cf86f6SMauro Carvalho Chehab 	if (!d) {
1203fac0314SAndi Shyti 		pr_err("driver pointer must be not NULL!\n");
12154fcecafSAndi Shyti 		return -EBADRQC;
12232cf86f6SMauro Carvalho Chehab 	}
12332cf86f6SMauro Carvalho Chehab 
12432cf86f6SMauro Carvalho Chehab 	if (!d->dev) {
1253fac0314SAndi Shyti 		pr_err("dev pointer not filled in!\n");
12654fcecafSAndi Shyti 		return -EINVAL;
12732cf86f6SMauro Carvalho Chehab 	}
12832cf86f6SMauro Carvalho Chehab 
129712551f0SDavid Härdeman 	if (!d->fops) {
130712551f0SDavid Härdeman 		pr_err("fops pointer not filled in!\n");
131712551f0SDavid Härdeman 		return -EINVAL;
132712551f0SDavid Härdeman 	}
133712551f0SDavid Härdeman 
1349675ee5aSAndi Shyti 	if (d->code_length < 1 || d->code_length > (BUFLEN * 8)) {
1353fac0314SAndi Shyti 		dev_err(d->dev, "code length must be less than %d bits\n",
1363fac0314SAndi Shyti 								BUFLEN * 8);
13754fcecafSAndi Shyti 		return -EBADRQC;
13832cf86f6SMauro Carvalho Chehab 	}
13932cf86f6SMauro Carvalho Chehab 
140c3104e1bSDavid Härdeman 	if (!d->rbuf && !(d->fops && d->fops->read &&
14114db9fc2SAndi Shyti 			  d->fops->poll && d->fops->unlocked_ioctl)) {
1423fac0314SAndi Shyti 		dev_err(d->dev, "undefined read, poll, ioctl\n");
14354fcecafSAndi Shyti 		return -EBADRQC;
14432cf86f6SMauro Carvalho Chehab 	}
14532cf86f6SMauro Carvalho Chehab 
14632cf86f6SMauro Carvalho Chehab 	mutex_lock(&lirc_dev_lock);
14732cf86f6SMauro Carvalho Chehab 
14832cf86f6SMauro Carvalho Chehab 	/* find first free slot for driver */
14932cf86f6SMauro Carvalho Chehab 	for (minor = 0; minor < MAX_IRCTL_DEVICES; minor++)
15032cf86f6SMauro Carvalho Chehab 		if (!irctls[minor])
15132cf86f6SMauro Carvalho Chehab 			break;
152c3c6dd75SDavid Härdeman 
1539675ee5aSAndi Shyti 	if (minor == MAX_IRCTL_DEVICES) {
1543fac0314SAndi Shyti 		dev_err(d->dev, "no free slots for drivers!\n");
15532cf86f6SMauro Carvalho Chehab 		err = -ENOMEM;
15632cf86f6SMauro Carvalho Chehab 		goto out_lock;
15732cf86f6SMauro Carvalho Chehab 	}
15832cf86f6SMauro Carvalho Chehab 
15932cf86f6SMauro Carvalho Chehab 	ir = kzalloc(sizeof(struct irctl), GFP_KERNEL);
16032cf86f6SMauro Carvalho Chehab 	if (!ir) {
16132cf86f6SMauro Carvalho Chehab 		err = -ENOMEM;
16232cf86f6SMauro Carvalho Chehab 		goto out_lock;
16332cf86f6SMauro Carvalho Chehab 	}
164712551f0SDavid Härdeman 
165712551f0SDavid Härdeman 	mutex_init(&ir->irctl_lock);
16632cf86f6SMauro Carvalho Chehab 	irctls[minor] = ir;
167c3c6dd75SDavid Härdeman 	d->irctl = ir;
16832cf86f6SMauro Carvalho Chehab 	d->minor = minor;
16932cf86f6SMauro Carvalho Chehab 
17032cf86f6SMauro Carvalho Chehab 	/* some safety check 8-) */
17132cf86f6SMauro Carvalho Chehab 	d->name[sizeof(d->name)-1] = '\0';
17232cf86f6SMauro Carvalho Chehab 
17332cf86f6SMauro Carvalho Chehab 	if (d->features == 0)
17432cf86f6SMauro Carvalho Chehab 		d->features = LIRC_CAN_REC_LIRCCODE;
17532cf86f6SMauro Carvalho Chehab 
17632cf86f6SMauro Carvalho Chehab 	ir->d = *d;
17732cf86f6SMauro Carvalho Chehab 
17856481f00SDavid Härdeman 	if (LIRC_CAN_REC(d->features)) {
17956481f00SDavid Härdeman 		err = lirc_allocate_buffer(irctls[minor]);
18056481f00SDavid Härdeman 		if (err) {
18156481f00SDavid Härdeman 			kfree(ir);
18256481f00SDavid Härdeman 			goto out_lock;
18356481f00SDavid Härdeman 		}
18456481f00SDavid Härdeman 		d->rbuf = ir->buf;
18556481f00SDavid Härdeman 	}
18656481f00SDavid Härdeman 
18725823226SDavid Härdeman 	device_initialize(&ir->dev);
18874c839b2SSean Young 	ir->dev.devt = MKDEV(MAJOR(lirc_base_dev), ir->d.minor);
18974c839b2SSean Young 	ir->dev.class = lirc_class;
19074c839b2SSean Young 	ir->dev.parent = d->dev;
19174c839b2SSean Young 	ir->dev.release = lirc_release;
19274c839b2SSean Young 	dev_set_name(&ir->dev, "lirc%d", ir->d.minor);
19332cf86f6SMauro Carvalho Chehab 
194712551f0SDavid Härdeman 	cdev_init(&ir->cdev, d->fops);
195712551f0SDavid Härdeman 	ir->cdev.owner = ir->d.owner;
19632cf86f6SMauro Carvalho Chehab 	ir->attached = 1;
19774c839b2SSean Young 
1980510d810SDavid Härdeman 	err = cdev_device_add(&ir->cdev, &ir->dev);
19974c839b2SSean Young 	if (err)
2000510d810SDavid Härdeman 		goto out_dev;
20174c839b2SSean Young 
20232cf86f6SMauro Carvalho Chehab 	mutex_unlock(&lirc_dev_lock);
20332cf86f6SMauro Carvalho Chehab 
204a607f51eSSean Young 	get_device(ir->dev.parent);
205a607f51eSSean Young 
20632cf86f6SMauro Carvalho Chehab 	dev_info(ir->d.dev, "lirc_dev: driver %s registered at minor = %d\n",
20732cf86f6SMauro Carvalho Chehab 		 ir->d.name, ir->d.minor);
20856481f00SDavid Härdeman 
209c3c6dd75SDavid Härdeman 	return 0;
21056481f00SDavid Härdeman 
2110510d810SDavid Härdeman out_dev:
21274c839b2SSean Young 	put_device(&ir->dev);
21332cf86f6SMauro Carvalho Chehab out_lock:
21432cf86f6SMauro Carvalho Chehab 	mutex_unlock(&lirc_dev_lock);
21554fcecafSAndi Shyti 
21632cf86f6SMauro Carvalho Chehab 	return err;
21732cf86f6SMauro Carvalho Chehab }
21832cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_register_driver);
21932cf86f6SMauro Carvalho Chehab 
220c3c6dd75SDavid Härdeman void lirc_unregister_driver(struct lirc_driver *d)
22132cf86f6SMauro Carvalho Chehab {
22232cf86f6SMauro Carvalho Chehab 	struct irctl *ir;
22332cf86f6SMauro Carvalho Chehab 
224c3c6dd75SDavid Härdeman 	if (!d || !d->irctl)
225c3c6dd75SDavid Härdeman 		return;
22632cf86f6SMauro Carvalho Chehab 
227c3c6dd75SDavid Härdeman 	ir = d->irctl;
22832cf86f6SMauro Carvalho Chehab 
22932cf86f6SMauro Carvalho Chehab 	mutex_lock(&lirc_dev_lock);
23032cf86f6SMauro Carvalho Chehab 
23132cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, "lirc_dev: driver %s unregistered from minor = %d\n",
232c3c6dd75SDavid Härdeman 		d->name, d->minor);
23332cf86f6SMauro Carvalho Chehab 
23432cf86f6SMauro Carvalho Chehab 	ir->attached = 0;
23532cf86f6SMauro Carvalho Chehab 	if (ir->open) {
23632cf86f6SMauro Carvalho Chehab 		dev_dbg(ir->d.dev, LOGHEAD "releasing opened driver\n",
237c3c6dd75SDavid Härdeman 			d->name, d->minor);
23832cf86f6SMauro Carvalho Chehab 		wake_up_interruptible(&ir->buf->wait_poll);
23974c839b2SSean Young 	}
24074c839b2SSean Young 
24132cf86f6SMauro Carvalho Chehab 	mutex_unlock(&lirc_dev_lock);
24232cf86f6SMauro Carvalho Chehab 
2430510d810SDavid Härdeman 	cdev_device_del(&ir->cdev, &ir->dev);
24474c839b2SSean Young 	put_device(&ir->dev);
24532cf86f6SMauro Carvalho Chehab }
24632cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_unregister_driver);
24732cf86f6SMauro Carvalho Chehab 
24832cf86f6SMauro Carvalho Chehab int lirc_dev_fop_open(struct inode *inode, struct file *file)
24932cf86f6SMauro Carvalho Chehab {
250615cd3feSDavid Härdeman 	struct irctl *ir = container_of(inode->i_cdev, struct irctl, cdev);
251de226ec8SDavid Härdeman 	int retval;
25232cf86f6SMauro Carvalho Chehab 
25332cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "open called\n", ir->d.name, ir->d.minor);
25432cf86f6SMauro Carvalho Chehab 
255615cd3feSDavid Härdeman 	if (ir->open)
256615cd3feSDavid Härdeman 		return -EBUSY;
25732cf86f6SMauro Carvalho Chehab 
258ca7a722dSSrinivas Kandagatla 	if (ir->d.rdev) {
259ca7a722dSSrinivas Kandagatla 		retval = rc_open(ir->d.rdev);
260ca7a722dSSrinivas Kandagatla 		if (retval)
261615cd3feSDavid Härdeman 			return retval;
262ca7a722dSSrinivas Kandagatla 	}
263ca7a722dSSrinivas Kandagatla 
26474c839b2SSean Young 	if (ir->buf)
26532cf86f6SMauro Carvalho Chehab 		lirc_buffer_clear(ir->buf);
2662c5a1f44SDavid Härdeman 
2672c5a1f44SDavid Härdeman 	ir->open++;
26832cf86f6SMauro Carvalho Chehab 
269615cd3feSDavid Härdeman 	lirc_init_pdata(inode, file);
27032cf86f6SMauro Carvalho Chehab 	nonseekable_open(inode, file);
27132cf86f6SMauro Carvalho Chehab 
272de226ec8SDavid Härdeman 	return 0;
27332cf86f6SMauro Carvalho Chehab }
27432cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_open);
27532cf86f6SMauro Carvalho Chehab 
27632cf86f6SMauro Carvalho Chehab int lirc_dev_fop_close(struct inode *inode, struct file *file)
27732cf86f6SMauro Carvalho Chehab {
278615cd3feSDavid Härdeman 	struct irctl *ir = file->private_data;
279b64e10f3SMauro Carvalho Chehab 	int ret;
28032cf86f6SMauro Carvalho Chehab 
281b64e10f3SMauro Carvalho Chehab 	ret = mutex_lock_killable(&lirc_dev_lock);
282b64e10f3SMauro Carvalho Chehab 	WARN_ON(ret);
28332cf86f6SMauro Carvalho Chehab 
284ca7a722dSSrinivas Kandagatla 	rc_close(ir->d.rdev);
285ca7a722dSSrinivas Kandagatla 
28632cf86f6SMauro Carvalho Chehab 	ir->open--;
287b64e10f3SMauro Carvalho Chehab 	if (!ret)
28832cf86f6SMauro Carvalho Chehab 		mutex_unlock(&lirc_dev_lock);
28932cf86f6SMauro Carvalho Chehab 
29032cf86f6SMauro Carvalho Chehab 	return 0;
29132cf86f6SMauro Carvalho Chehab }
29232cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_close);
29332cf86f6SMauro Carvalho Chehab 
29432cf86f6SMauro Carvalho Chehab unsigned int lirc_dev_fop_poll(struct file *file, poll_table *wait)
29532cf86f6SMauro Carvalho Chehab {
296615cd3feSDavid Härdeman 	struct irctl *ir = file->private_data;
29732cf86f6SMauro Carvalho Chehab 	unsigned int ret;
29832cf86f6SMauro Carvalho Chehab 
29932cf86f6SMauro Carvalho Chehab 	if (!ir->attached)
30029debf3dSDavid Härdeman 		return POLLHUP | POLLERR;
30132cf86f6SMauro Carvalho Chehab 
3023656cdddSAndy Shevchenko 	if (ir->buf) {
30332cf86f6SMauro Carvalho Chehab 		poll_wait(file, &ir->buf->wait_poll, wait);
30432cf86f6SMauro Carvalho Chehab 
30532cf86f6SMauro Carvalho Chehab 		if (lirc_buffer_empty(ir->buf))
30632cf86f6SMauro Carvalho Chehab 			ret = 0;
30732cf86f6SMauro Carvalho Chehab 		else
30832cf86f6SMauro Carvalho Chehab 			ret = POLLIN | POLLRDNORM;
3093656cdddSAndy Shevchenko 	} else
31032cf86f6SMauro Carvalho Chehab 		ret = POLLERR;
31132cf86f6SMauro Carvalho Chehab 
31232cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "poll result = %d\n",
31332cf86f6SMauro Carvalho Chehab 		ir->d.name, ir->d.minor, ret);
31432cf86f6SMauro Carvalho Chehab 
31532cf86f6SMauro Carvalho Chehab 	return ret;
31632cf86f6SMauro Carvalho Chehab }
31732cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_poll);
31832cf86f6SMauro Carvalho Chehab 
31932cf86f6SMauro Carvalho Chehab long lirc_dev_fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
32032cf86f6SMauro Carvalho Chehab {
321615cd3feSDavid Härdeman 	struct irctl *ir = file->private_data;
32232cf86f6SMauro Carvalho Chehab 	__u32 mode;
32332cf86f6SMauro Carvalho Chehab 	int result = 0;
32432cf86f6SMauro Carvalho Chehab 
32532cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "ioctl called (0x%x)\n",
32632cf86f6SMauro Carvalho Chehab 		ir->d.name, ir->d.minor, cmd);
32732cf86f6SMauro Carvalho Chehab 
328c3c6dd75SDavid Härdeman 	if (!ir->attached) {
3293fac0314SAndi Shyti 		dev_err(ir->d.dev, LOGHEAD "ioctl result = -ENODEV\n",
33032cf86f6SMauro Carvalho Chehab 			ir->d.name, ir->d.minor);
33132cf86f6SMauro Carvalho Chehab 		return -ENODEV;
33232cf86f6SMauro Carvalho Chehab 	}
33332cf86f6SMauro Carvalho Chehab 
33432cf86f6SMauro Carvalho Chehab 	mutex_lock(&ir->irctl_lock);
33532cf86f6SMauro Carvalho Chehab 
33632cf86f6SMauro Carvalho Chehab 	switch (cmd) {
33732cf86f6SMauro Carvalho Chehab 	case LIRC_GET_FEATURES:
33860519af3SHans Verkuil 		result = put_user(ir->d.features, (__u32 __user *)arg);
33932cf86f6SMauro Carvalho Chehab 		break;
34032cf86f6SMauro Carvalho Chehab 	case LIRC_GET_REC_MODE:
341bd291208SSean Young 		if (!LIRC_CAN_REC(ir->d.features)) {
342b4088094SAndi Shyti 			result = -ENOTTY;
34332cf86f6SMauro Carvalho Chehab 			break;
34432cf86f6SMauro Carvalho Chehab 		}
34532cf86f6SMauro Carvalho Chehab 
34632cf86f6SMauro Carvalho Chehab 		result = put_user(LIRC_REC2MODE
34732cf86f6SMauro Carvalho Chehab 				  (ir->d.features & LIRC_CAN_REC_MASK),
34860519af3SHans Verkuil 				  (__u32 __user *)arg);
34932cf86f6SMauro Carvalho Chehab 		break;
35032cf86f6SMauro Carvalho Chehab 	case LIRC_SET_REC_MODE:
351bd291208SSean Young 		if (!LIRC_CAN_REC(ir->d.features)) {
352b4088094SAndi Shyti 			result = -ENOTTY;
35332cf86f6SMauro Carvalho Chehab 			break;
35432cf86f6SMauro Carvalho Chehab 		}
35532cf86f6SMauro Carvalho Chehab 
35660519af3SHans Verkuil 		result = get_user(mode, (__u32 __user *)arg);
35732cf86f6SMauro Carvalho Chehab 		if (!result && !(LIRC_MODE2REC(mode) & ir->d.features))
35832cf86f6SMauro Carvalho Chehab 			result = -EINVAL;
35932cf86f6SMauro Carvalho Chehab 		/*
36032cf86f6SMauro Carvalho Chehab 		 * FIXME: We should actually set the mode somehow but
36132cf86f6SMauro Carvalho Chehab 		 * for now, lirc_serial doesn't support mode changing either
36232cf86f6SMauro Carvalho Chehab 		 */
36332cf86f6SMauro Carvalho Chehab 		break;
36432cf86f6SMauro Carvalho Chehab 	case LIRC_GET_LENGTH:
36560519af3SHans Verkuil 		result = put_user(ir->d.code_length, (__u32 __user *)arg);
36632cf86f6SMauro Carvalho Chehab 		break;
36732cf86f6SMauro Carvalho Chehab 	case LIRC_GET_MIN_TIMEOUT:
36832cf86f6SMauro Carvalho Chehab 		if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) ||
36932cf86f6SMauro Carvalho Chehab 		    ir->d.min_timeout == 0) {
370b4088094SAndi Shyti 			result = -ENOTTY;
37132cf86f6SMauro Carvalho Chehab 			break;
37232cf86f6SMauro Carvalho Chehab 		}
37332cf86f6SMauro Carvalho Chehab 
37460519af3SHans Verkuil 		result = put_user(ir->d.min_timeout, (__u32 __user *)arg);
37532cf86f6SMauro Carvalho Chehab 		break;
37632cf86f6SMauro Carvalho Chehab 	case LIRC_GET_MAX_TIMEOUT:
37732cf86f6SMauro Carvalho Chehab 		if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) ||
37832cf86f6SMauro Carvalho Chehab 		    ir->d.max_timeout == 0) {
379b4088094SAndi Shyti 			result = -ENOTTY;
38032cf86f6SMauro Carvalho Chehab 			break;
38132cf86f6SMauro Carvalho Chehab 		}
38232cf86f6SMauro Carvalho Chehab 
38360519af3SHans Verkuil 		result = put_user(ir->d.max_timeout, (__u32 __user *)arg);
38432cf86f6SMauro Carvalho Chehab 		break;
38532cf86f6SMauro Carvalho Chehab 	default:
3865c862758SSean Young 		result = -ENOTTY;
38732cf86f6SMauro Carvalho Chehab 	}
38832cf86f6SMauro Carvalho Chehab 
38932cf86f6SMauro Carvalho Chehab 	mutex_unlock(&ir->irctl_lock);
39032cf86f6SMauro Carvalho Chehab 
39132cf86f6SMauro Carvalho Chehab 	return result;
39232cf86f6SMauro Carvalho Chehab }
39332cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_ioctl);
39432cf86f6SMauro Carvalho Chehab 
39532cf86f6SMauro Carvalho Chehab ssize_t lirc_dev_fop_read(struct file *file,
3960e835087SDan Carpenter 			  char __user *buffer,
39732cf86f6SMauro Carvalho Chehab 			  size_t length,
39832cf86f6SMauro Carvalho Chehab 			  loff_t *ppos)
39932cf86f6SMauro Carvalho Chehab {
400615cd3feSDavid Härdeman 	struct irctl *ir = file->private_data;
40132cf86f6SMauro Carvalho Chehab 	unsigned char *buf;
40232cf86f6SMauro Carvalho Chehab 	int ret = 0, written = 0;
40332cf86f6SMauro Carvalho Chehab 	DECLARE_WAITQUEUE(wait, current);
40432cf86f6SMauro Carvalho Chehab 
40532002f72SSean Young 	if (!LIRC_CAN_REC(ir->d.features))
40632002f72SSean Young 		return -EINVAL;
40732002f72SSean Young 
40832cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "read called\n", ir->d.name, ir->d.minor);
40932cf86f6SMauro Carvalho Chehab 
41032cf86f6SMauro Carvalho Chehab 	buf = kzalloc(ir->chunk_size, GFP_KERNEL);
41132cf86f6SMauro Carvalho Chehab 	if (!buf)
41232cf86f6SMauro Carvalho Chehab 		return -ENOMEM;
41332cf86f6SMauro Carvalho Chehab 
41432cf86f6SMauro Carvalho Chehab 	if (mutex_lock_interruptible(&ir->irctl_lock)) {
41532cf86f6SMauro Carvalho Chehab 		ret = -ERESTARTSYS;
41632cf86f6SMauro Carvalho Chehab 		goto out_unlocked;
41732cf86f6SMauro Carvalho Chehab 	}
41832cf86f6SMauro Carvalho Chehab 	if (!ir->attached) {
41932cf86f6SMauro Carvalho Chehab 		ret = -ENODEV;
42032cf86f6SMauro Carvalho Chehab 		goto out_locked;
42132cf86f6SMauro Carvalho Chehab 	}
42232cf86f6SMauro Carvalho Chehab 
42332cf86f6SMauro Carvalho Chehab 	if (length % ir->chunk_size) {
42432cf86f6SMauro Carvalho Chehab 		ret = -EINVAL;
42532cf86f6SMauro Carvalho Chehab 		goto out_locked;
42632cf86f6SMauro Carvalho Chehab 	}
42732cf86f6SMauro Carvalho Chehab 
42832cf86f6SMauro Carvalho Chehab 	/*
42932cf86f6SMauro Carvalho Chehab 	 * we add ourselves to the task queue before buffer check
43032cf86f6SMauro Carvalho Chehab 	 * to avoid losing scan code (in case when queue is awaken somewhere
43132cf86f6SMauro Carvalho Chehab 	 * between while condition checking and scheduling)
43232cf86f6SMauro Carvalho Chehab 	 */
43332cf86f6SMauro Carvalho Chehab 	add_wait_queue(&ir->buf->wait_poll, &wait);
43432cf86f6SMauro Carvalho Chehab 
43532cf86f6SMauro Carvalho Chehab 	/*
43632cf86f6SMauro Carvalho Chehab 	 * while we didn't provide 'length' bytes, device is opened in blocking
43732cf86f6SMauro Carvalho Chehab 	 * mode and 'copy_to_user' is happy, wait for data.
43832cf86f6SMauro Carvalho Chehab 	 */
43932cf86f6SMauro Carvalho Chehab 	while (written < length && ret == 0) {
44032cf86f6SMauro Carvalho Chehab 		if (lirc_buffer_empty(ir->buf)) {
44132cf86f6SMauro Carvalho Chehab 			/* According to the read(2) man page, 'written' can be
44232cf86f6SMauro Carvalho Chehab 			 * returned as less than 'length', instead of blocking
44332cf86f6SMauro Carvalho Chehab 			 * again, returning -EWOULDBLOCK, or returning
44462e92682SAndi Shyti 			 * -ERESTARTSYS
44562e92682SAndi Shyti 			 */
44632cf86f6SMauro Carvalho Chehab 			if (written)
44732cf86f6SMauro Carvalho Chehab 				break;
44832cf86f6SMauro Carvalho Chehab 			if (file->f_flags & O_NONBLOCK) {
44932cf86f6SMauro Carvalho Chehab 				ret = -EWOULDBLOCK;
45032cf86f6SMauro Carvalho Chehab 				break;
45132cf86f6SMauro Carvalho Chehab 			}
45232cf86f6SMauro Carvalho Chehab 			if (signal_pending(current)) {
45332cf86f6SMauro Carvalho Chehab 				ret = -ERESTARTSYS;
45432cf86f6SMauro Carvalho Chehab 				break;
45532cf86f6SMauro Carvalho Chehab 			}
45632cf86f6SMauro Carvalho Chehab 
45732cf86f6SMauro Carvalho Chehab 			mutex_unlock(&ir->irctl_lock);
45832cf86f6SMauro Carvalho Chehab 			set_current_state(TASK_INTERRUPTIBLE);
45912accdcbSSean Young 			schedule();
46012accdcbSSean Young 			set_current_state(TASK_RUNNING);
46132cf86f6SMauro Carvalho Chehab 
46232cf86f6SMauro Carvalho Chehab 			if (mutex_lock_interruptible(&ir->irctl_lock)) {
46332cf86f6SMauro Carvalho Chehab 				ret = -ERESTARTSYS;
46432cf86f6SMauro Carvalho Chehab 				remove_wait_queue(&ir->buf->wait_poll, &wait);
46532cf86f6SMauro Carvalho Chehab 				goto out_unlocked;
46632cf86f6SMauro Carvalho Chehab 			}
46732cf86f6SMauro Carvalho Chehab 
46832cf86f6SMauro Carvalho Chehab 			if (!ir->attached) {
46932cf86f6SMauro Carvalho Chehab 				ret = -ENODEV;
470c77d17c0SSean Young 				goto out_locked;
47132cf86f6SMauro Carvalho Chehab 			}
47232cf86f6SMauro Carvalho Chehab 		} else {
47332cf86f6SMauro Carvalho Chehab 			lirc_buffer_read(ir->buf, buf);
47460519af3SHans Verkuil 			ret = copy_to_user((void __user *)buffer+written, buf,
47532cf86f6SMauro Carvalho Chehab 					   ir->buf->chunk_size);
47632cf86f6SMauro Carvalho Chehab 			if (!ret)
47732cf86f6SMauro Carvalho Chehab 				written += ir->buf->chunk_size;
47832cf86f6SMauro Carvalho Chehab 			else
47932cf86f6SMauro Carvalho Chehab 				ret = -EFAULT;
48032cf86f6SMauro Carvalho Chehab 		}
48132cf86f6SMauro Carvalho Chehab 	}
48232cf86f6SMauro Carvalho Chehab 
48332cf86f6SMauro Carvalho Chehab 	remove_wait_queue(&ir->buf->wait_poll, &wait);
48432cf86f6SMauro Carvalho Chehab 
48532cf86f6SMauro Carvalho Chehab out_locked:
48632cf86f6SMauro Carvalho Chehab 	mutex_unlock(&ir->irctl_lock);
48732cf86f6SMauro Carvalho Chehab 
48832cf86f6SMauro Carvalho Chehab out_unlocked:
48932cf86f6SMauro Carvalho Chehab 	kfree(buf);
49032cf86f6SMauro Carvalho Chehab 
49132cf86f6SMauro Carvalho Chehab 	return ret ? ret : written;
49232cf86f6SMauro Carvalho Chehab }
49332cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_read);
49432cf86f6SMauro Carvalho Chehab 
495615cd3feSDavid Härdeman void lirc_init_pdata(struct inode *inode, struct file *file)
496615cd3feSDavid Härdeman {
497615cd3feSDavid Härdeman 	struct irctl *ir = container_of(inode->i_cdev, struct irctl, cdev);
498615cd3feSDavid Härdeman 
499615cd3feSDavid Härdeman 	file->private_data = ir;
500615cd3feSDavid Härdeman }
501615cd3feSDavid Härdeman EXPORT_SYMBOL(lirc_init_pdata);
502615cd3feSDavid Härdeman 
50332cf86f6SMauro Carvalho Chehab void *lirc_get_pdata(struct file *file)
50432cf86f6SMauro Carvalho Chehab {
505615cd3feSDavid Härdeman 	struct irctl *ir = file->private_data;
506615cd3feSDavid Härdeman 
507615cd3feSDavid Härdeman 	return ir->d.data;
50832cf86f6SMauro Carvalho Chehab }
50932cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_get_pdata);
51032cf86f6SMauro Carvalho Chehab 
51132cf86f6SMauro Carvalho Chehab 
51232cf86f6SMauro Carvalho Chehab static int __init lirc_dev_init(void)
51332cf86f6SMauro Carvalho Chehab {
51432cf86f6SMauro Carvalho Chehab 	int retval;
51532cf86f6SMauro Carvalho Chehab 
51632cf86f6SMauro Carvalho Chehab 	lirc_class = class_create(THIS_MODULE, "lirc");
51732cf86f6SMauro Carvalho Chehab 	if (IS_ERR(lirc_class)) {
5183fac0314SAndi Shyti 		pr_err("class_create failed\n");
51954fcecafSAndi Shyti 		return PTR_ERR(lirc_class);
52032cf86f6SMauro Carvalho Chehab 	}
52132cf86f6SMauro Carvalho Chehab 
52232cf86f6SMauro Carvalho Chehab 	retval = alloc_chrdev_region(&lirc_base_dev, 0, MAX_IRCTL_DEVICES,
523463015ddSDavid Härdeman 				     "BaseRemoteCtl");
52432cf86f6SMauro Carvalho Chehab 	if (retval) {
52532cf86f6SMauro Carvalho Chehab 		class_destroy(lirc_class);
5263fac0314SAndi Shyti 		pr_err("alloc_chrdev_region failed\n");
52754fcecafSAndi Shyti 		return retval;
52832cf86f6SMauro Carvalho Chehab 	}
52932cf86f6SMauro Carvalho Chehab 
5303fac0314SAndi Shyti 	pr_info("IR Remote Control driver registered, major %d\n",
5313fac0314SAndi Shyti 						MAJOR(lirc_base_dev));
53232cf86f6SMauro Carvalho Chehab 
53354fcecafSAndi Shyti 	return 0;
53432cf86f6SMauro Carvalho Chehab }
53532cf86f6SMauro Carvalho Chehab 
53632cf86f6SMauro Carvalho Chehab static void __exit lirc_dev_exit(void)
53732cf86f6SMauro Carvalho Chehab {
53832cf86f6SMauro Carvalho Chehab 	class_destroy(lirc_class);
53932cf86f6SMauro Carvalho Chehab 	unregister_chrdev_region(lirc_base_dev, MAX_IRCTL_DEVICES);
5403fac0314SAndi Shyti 	pr_info("module unloaded\n");
54132cf86f6SMauro Carvalho Chehab }
54232cf86f6SMauro Carvalho Chehab 
54332cf86f6SMauro Carvalho Chehab module_init(lirc_dev_init);
54432cf86f6SMauro Carvalho Chehab module_exit(lirc_dev_exit);
54532cf86f6SMauro Carvalho Chehab 
54632cf86f6SMauro Carvalho Chehab MODULE_DESCRIPTION("LIRC base driver module");
54732cf86f6SMauro Carvalho Chehab MODULE_AUTHOR("Artur Lipowski");
54832cf86f6SMauro Carvalho Chehab MODULE_LICENSE("GPL");
549