xref: /openbmc/linux/drivers/media/rc/lirc_dev.c (revision 0510d810)
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 {
25032cf86f6SMauro Carvalho Chehab 	struct irctl *ir;
251de226ec8SDavid Härdeman 	int retval;
25232cf86f6SMauro Carvalho Chehab 
25332cf86f6SMauro Carvalho Chehab 	if (iminor(inode) >= MAX_IRCTL_DEVICES) {
2543fac0314SAndi Shyti 		pr_err("open result for %d is -ENODEV\n", iminor(inode));
25532cf86f6SMauro Carvalho Chehab 		return -ENODEV;
25632cf86f6SMauro Carvalho Chehab 	}
25732cf86f6SMauro Carvalho Chehab 
25832cf86f6SMauro Carvalho Chehab 	if (mutex_lock_interruptible(&lirc_dev_lock))
25932cf86f6SMauro Carvalho Chehab 		return -ERESTARTSYS;
26032cf86f6SMauro Carvalho Chehab 
26132cf86f6SMauro Carvalho Chehab 	ir = irctls[iminor(inode)];
262db5b15b7SSean Young 	mutex_unlock(&lirc_dev_lock);
263db5b15b7SSean Young 
26432cf86f6SMauro Carvalho Chehab 	if (!ir) {
26532cf86f6SMauro Carvalho Chehab 		retval = -ENODEV;
26632cf86f6SMauro Carvalho Chehab 		goto error;
26732cf86f6SMauro Carvalho Chehab 	}
26832cf86f6SMauro Carvalho Chehab 
26932cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "open called\n", ir->d.name, ir->d.minor);
27032cf86f6SMauro Carvalho Chehab 
27132cf86f6SMauro Carvalho Chehab 	if (ir->open) {
27232cf86f6SMauro Carvalho Chehab 		retval = -EBUSY;
27332cf86f6SMauro Carvalho Chehab 		goto error;
27432cf86f6SMauro Carvalho Chehab 	}
27532cf86f6SMauro Carvalho Chehab 
276ca7a722dSSrinivas Kandagatla 	if (ir->d.rdev) {
277ca7a722dSSrinivas Kandagatla 		retval = rc_open(ir->d.rdev);
278ca7a722dSSrinivas Kandagatla 		if (retval)
279ca7a722dSSrinivas Kandagatla 			goto error;
280ca7a722dSSrinivas Kandagatla 	}
281ca7a722dSSrinivas Kandagatla 
28274c839b2SSean Young 	if (ir->buf)
28332cf86f6SMauro Carvalho Chehab 		lirc_buffer_clear(ir->buf);
2842c5a1f44SDavid Härdeman 
2852c5a1f44SDavid Härdeman 	ir->open++;
28632cf86f6SMauro Carvalho Chehab 
28732cf86f6SMauro Carvalho Chehab 	nonseekable_open(inode, file);
28832cf86f6SMauro Carvalho Chehab 
289de226ec8SDavid Härdeman 	return 0;
290de226ec8SDavid Härdeman 
291de226ec8SDavid Härdeman error:
29232cf86f6SMauro Carvalho Chehab 	return retval;
29332cf86f6SMauro Carvalho Chehab }
29432cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_open);
29532cf86f6SMauro Carvalho Chehab 
29632cf86f6SMauro Carvalho Chehab int lirc_dev_fop_close(struct inode *inode, struct file *file)
29732cf86f6SMauro Carvalho Chehab {
29832cf86f6SMauro Carvalho Chehab 	struct irctl *ir = irctls[iminor(inode)];
299b64e10f3SMauro Carvalho Chehab 	int ret;
30032cf86f6SMauro Carvalho Chehab 
30132cf86f6SMauro Carvalho Chehab 	if (!ir) {
3023fac0314SAndi Shyti 		pr_err("called with invalid irctl\n");
30332cf86f6SMauro Carvalho Chehab 		return -EINVAL;
30432cf86f6SMauro Carvalho Chehab 	}
30532cf86f6SMauro Carvalho Chehab 
306b64e10f3SMauro Carvalho Chehab 	ret = mutex_lock_killable(&lirc_dev_lock);
307b64e10f3SMauro Carvalho Chehab 	WARN_ON(ret);
30832cf86f6SMauro Carvalho Chehab 
309ca7a722dSSrinivas Kandagatla 	rc_close(ir->d.rdev);
310ca7a722dSSrinivas Kandagatla 
31132cf86f6SMauro Carvalho Chehab 	ir->open--;
312b64e10f3SMauro Carvalho Chehab 	if (!ret)
31332cf86f6SMauro Carvalho Chehab 		mutex_unlock(&lirc_dev_lock);
31432cf86f6SMauro Carvalho Chehab 
31532cf86f6SMauro Carvalho Chehab 	return 0;
31632cf86f6SMauro Carvalho Chehab }
31732cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_close);
31832cf86f6SMauro Carvalho Chehab 
31932cf86f6SMauro Carvalho Chehab unsigned int lirc_dev_fop_poll(struct file *file, poll_table *wait)
32032cf86f6SMauro Carvalho Chehab {
321496ad9aaSAl Viro 	struct irctl *ir = irctls[iminor(file_inode(file))];
32232cf86f6SMauro Carvalho Chehab 	unsigned int ret;
32332cf86f6SMauro Carvalho Chehab 
32432cf86f6SMauro Carvalho Chehab 	if (!ir) {
3253fac0314SAndi Shyti 		pr_err("called with invalid irctl\n");
32632cf86f6SMauro Carvalho Chehab 		return POLLERR;
32732cf86f6SMauro Carvalho Chehab 	}
32832cf86f6SMauro Carvalho Chehab 
32932cf86f6SMauro Carvalho Chehab 	if (!ir->attached)
33029debf3dSDavid Härdeman 		return POLLHUP | POLLERR;
33132cf86f6SMauro Carvalho Chehab 
3323656cdddSAndy Shevchenko 	if (ir->buf) {
33332cf86f6SMauro Carvalho Chehab 		poll_wait(file, &ir->buf->wait_poll, wait);
33432cf86f6SMauro Carvalho Chehab 
33532cf86f6SMauro Carvalho Chehab 		if (lirc_buffer_empty(ir->buf))
33632cf86f6SMauro Carvalho Chehab 			ret = 0;
33732cf86f6SMauro Carvalho Chehab 		else
33832cf86f6SMauro Carvalho Chehab 			ret = POLLIN | POLLRDNORM;
3393656cdddSAndy Shevchenko 	} else
34032cf86f6SMauro Carvalho Chehab 		ret = POLLERR;
34132cf86f6SMauro Carvalho Chehab 
34232cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "poll result = %d\n",
34332cf86f6SMauro Carvalho Chehab 		ir->d.name, ir->d.minor, ret);
34432cf86f6SMauro Carvalho Chehab 
34532cf86f6SMauro Carvalho Chehab 	return ret;
34632cf86f6SMauro Carvalho Chehab }
34732cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_poll);
34832cf86f6SMauro Carvalho Chehab 
34932cf86f6SMauro Carvalho Chehab long lirc_dev_fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
35032cf86f6SMauro Carvalho Chehab {
35132cf86f6SMauro Carvalho Chehab 	__u32 mode;
35232cf86f6SMauro Carvalho Chehab 	int result = 0;
353496ad9aaSAl Viro 	struct irctl *ir = irctls[iminor(file_inode(file))];
35432cf86f6SMauro Carvalho Chehab 
35532cf86f6SMauro Carvalho Chehab 	if (!ir) {
3563fac0314SAndi Shyti 		pr_err("no irctl found!\n");
35732cf86f6SMauro Carvalho Chehab 		return -ENODEV;
35832cf86f6SMauro Carvalho Chehab 	}
35932cf86f6SMauro Carvalho Chehab 
36032cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "ioctl called (0x%x)\n",
36132cf86f6SMauro Carvalho Chehab 		ir->d.name, ir->d.minor, cmd);
36232cf86f6SMauro Carvalho Chehab 
363c3c6dd75SDavid Härdeman 	if (!ir->attached) {
3643fac0314SAndi Shyti 		dev_err(ir->d.dev, LOGHEAD "ioctl result = -ENODEV\n",
36532cf86f6SMauro Carvalho Chehab 			ir->d.name, ir->d.minor);
36632cf86f6SMauro Carvalho Chehab 		return -ENODEV;
36732cf86f6SMauro Carvalho Chehab 	}
36832cf86f6SMauro Carvalho Chehab 
36932cf86f6SMauro Carvalho Chehab 	mutex_lock(&ir->irctl_lock);
37032cf86f6SMauro Carvalho Chehab 
37132cf86f6SMauro Carvalho Chehab 	switch (cmd) {
37232cf86f6SMauro Carvalho Chehab 	case LIRC_GET_FEATURES:
37360519af3SHans Verkuil 		result = put_user(ir->d.features, (__u32 __user *)arg);
37432cf86f6SMauro Carvalho Chehab 		break;
37532cf86f6SMauro Carvalho Chehab 	case LIRC_GET_REC_MODE:
376bd291208SSean Young 		if (!LIRC_CAN_REC(ir->d.features)) {
377b4088094SAndi Shyti 			result = -ENOTTY;
37832cf86f6SMauro Carvalho Chehab 			break;
37932cf86f6SMauro Carvalho Chehab 		}
38032cf86f6SMauro Carvalho Chehab 
38132cf86f6SMauro Carvalho Chehab 		result = put_user(LIRC_REC2MODE
38232cf86f6SMauro Carvalho Chehab 				  (ir->d.features & LIRC_CAN_REC_MASK),
38360519af3SHans Verkuil 				  (__u32 __user *)arg);
38432cf86f6SMauro Carvalho Chehab 		break;
38532cf86f6SMauro Carvalho Chehab 	case LIRC_SET_REC_MODE:
386bd291208SSean Young 		if (!LIRC_CAN_REC(ir->d.features)) {
387b4088094SAndi Shyti 			result = -ENOTTY;
38832cf86f6SMauro Carvalho Chehab 			break;
38932cf86f6SMauro Carvalho Chehab 		}
39032cf86f6SMauro Carvalho Chehab 
39160519af3SHans Verkuil 		result = get_user(mode, (__u32 __user *)arg);
39232cf86f6SMauro Carvalho Chehab 		if (!result && !(LIRC_MODE2REC(mode) & ir->d.features))
39332cf86f6SMauro Carvalho Chehab 			result = -EINVAL;
39432cf86f6SMauro Carvalho Chehab 		/*
39532cf86f6SMauro Carvalho Chehab 		 * FIXME: We should actually set the mode somehow but
39632cf86f6SMauro Carvalho Chehab 		 * for now, lirc_serial doesn't support mode changing either
39732cf86f6SMauro Carvalho Chehab 		 */
39832cf86f6SMauro Carvalho Chehab 		break;
39932cf86f6SMauro Carvalho Chehab 	case LIRC_GET_LENGTH:
40060519af3SHans Verkuil 		result = put_user(ir->d.code_length, (__u32 __user *)arg);
40132cf86f6SMauro Carvalho Chehab 		break;
40232cf86f6SMauro Carvalho Chehab 	case LIRC_GET_MIN_TIMEOUT:
40332cf86f6SMauro Carvalho Chehab 		if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) ||
40432cf86f6SMauro Carvalho Chehab 		    ir->d.min_timeout == 0) {
405b4088094SAndi Shyti 			result = -ENOTTY;
40632cf86f6SMauro Carvalho Chehab 			break;
40732cf86f6SMauro Carvalho Chehab 		}
40832cf86f6SMauro Carvalho Chehab 
40960519af3SHans Verkuil 		result = put_user(ir->d.min_timeout, (__u32 __user *)arg);
41032cf86f6SMauro Carvalho Chehab 		break;
41132cf86f6SMauro Carvalho Chehab 	case LIRC_GET_MAX_TIMEOUT:
41232cf86f6SMauro Carvalho Chehab 		if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) ||
41332cf86f6SMauro Carvalho Chehab 		    ir->d.max_timeout == 0) {
414b4088094SAndi Shyti 			result = -ENOTTY;
41532cf86f6SMauro Carvalho Chehab 			break;
41632cf86f6SMauro Carvalho Chehab 		}
41732cf86f6SMauro Carvalho Chehab 
41860519af3SHans Verkuil 		result = put_user(ir->d.max_timeout, (__u32 __user *)arg);
41932cf86f6SMauro Carvalho Chehab 		break;
42032cf86f6SMauro Carvalho Chehab 	default:
4215c862758SSean Young 		result = -ENOTTY;
42232cf86f6SMauro Carvalho Chehab 	}
42332cf86f6SMauro Carvalho Chehab 
42432cf86f6SMauro Carvalho Chehab 	mutex_unlock(&ir->irctl_lock);
42532cf86f6SMauro Carvalho Chehab 
42632cf86f6SMauro Carvalho Chehab 	return result;
42732cf86f6SMauro Carvalho Chehab }
42832cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_ioctl);
42932cf86f6SMauro Carvalho Chehab 
43032cf86f6SMauro Carvalho Chehab ssize_t lirc_dev_fop_read(struct file *file,
4310e835087SDan Carpenter 			  char __user *buffer,
43232cf86f6SMauro Carvalho Chehab 			  size_t length,
43332cf86f6SMauro Carvalho Chehab 			  loff_t *ppos)
43432cf86f6SMauro Carvalho Chehab {
435496ad9aaSAl Viro 	struct irctl *ir = irctls[iminor(file_inode(file))];
43632cf86f6SMauro Carvalho Chehab 	unsigned char *buf;
43732cf86f6SMauro Carvalho Chehab 	int ret = 0, written = 0;
43832cf86f6SMauro Carvalho Chehab 	DECLARE_WAITQUEUE(wait, current);
43932cf86f6SMauro Carvalho Chehab 
44032cf86f6SMauro Carvalho Chehab 	if (!ir) {
4413fac0314SAndi Shyti 		pr_err("called with invalid irctl\n");
44232cf86f6SMauro Carvalho Chehab 		return -ENODEV;
44332cf86f6SMauro Carvalho Chehab 	}
44432cf86f6SMauro Carvalho Chehab 
44532002f72SSean Young 	if (!LIRC_CAN_REC(ir->d.features))
44632002f72SSean Young 		return -EINVAL;
44732002f72SSean Young 
44832cf86f6SMauro Carvalho Chehab 	dev_dbg(ir->d.dev, LOGHEAD "read called\n", ir->d.name, ir->d.minor);
44932cf86f6SMauro Carvalho Chehab 
45032cf86f6SMauro Carvalho Chehab 	buf = kzalloc(ir->chunk_size, GFP_KERNEL);
45132cf86f6SMauro Carvalho Chehab 	if (!buf)
45232cf86f6SMauro Carvalho Chehab 		return -ENOMEM;
45332cf86f6SMauro Carvalho Chehab 
45432cf86f6SMauro Carvalho Chehab 	if (mutex_lock_interruptible(&ir->irctl_lock)) {
45532cf86f6SMauro Carvalho Chehab 		ret = -ERESTARTSYS;
45632cf86f6SMauro Carvalho Chehab 		goto out_unlocked;
45732cf86f6SMauro Carvalho Chehab 	}
45832cf86f6SMauro Carvalho Chehab 	if (!ir->attached) {
45932cf86f6SMauro Carvalho Chehab 		ret = -ENODEV;
46032cf86f6SMauro Carvalho Chehab 		goto out_locked;
46132cf86f6SMauro Carvalho Chehab 	}
46232cf86f6SMauro Carvalho Chehab 
46332cf86f6SMauro Carvalho Chehab 	if (length % ir->chunk_size) {
46432cf86f6SMauro Carvalho Chehab 		ret = -EINVAL;
46532cf86f6SMauro Carvalho Chehab 		goto out_locked;
46632cf86f6SMauro Carvalho Chehab 	}
46732cf86f6SMauro Carvalho Chehab 
46832cf86f6SMauro Carvalho Chehab 	/*
46932cf86f6SMauro Carvalho Chehab 	 * we add ourselves to the task queue before buffer check
47032cf86f6SMauro Carvalho Chehab 	 * to avoid losing scan code (in case when queue is awaken somewhere
47132cf86f6SMauro Carvalho Chehab 	 * between while condition checking and scheduling)
47232cf86f6SMauro Carvalho Chehab 	 */
47332cf86f6SMauro Carvalho Chehab 	add_wait_queue(&ir->buf->wait_poll, &wait);
47432cf86f6SMauro Carvalho Chehab 
47532cf86f6SMauro Carvalho Chehab 	/*
47632cf86f6SMauro Carvalho Chehab 	 * while we didn't provide 'length' bytes, device is opened in blocking
47732cf86f6SMauro Carvalho Chehab 	 * mode and 'copy_to_user' is happy, wait for data.
47832cf86f6SMauro Carvalho Chehab 	 */
47932cf86f6SMauro Carvalho Chehab 	while (written < length && ret == 0) {
48032cf86f6SMauro Carvalho Chehab 		if (lirc_buffer_empty(ir->buf)) {
48132cf86f6SMauro Carvalho Chehab 			/* According to the read(2) man page, 'written' can be
48232cf86f6SMauro Carvalho Chehab 			 * returned as less than 'length', instead of blocking
48332cf86f6SMauro Carvalho Chehab 			 * again, returning -EWOULDBLOCK, or returning
48462e92682SAndi Shyti 			 * -ERESTARTSYS
48562e92682SAndi Shyti 			 */
48632cf86f6SMauro Carvalho Chehab 			if (written)
48732cf86f6SMauro Carvalho Chehab 				break;
48832cf86f6SMauro Carvalho Chehab 			if (file->f_flags & O_NONBLOCK) {
48932cf86f6SMauro Carvalho Chehab 				ret = -EWOULDBLOCK;
49032cf86f6SMauro Carvalho Chehab 				break;
49132cf86f6SMauro Carvalho Chehab 			}
49232cf86f6SMauro Carvalho Chehab 			if (signal_pending(current)) {
49332cf86f6SMauro Carvalho Chehab 				ret = -ERESTARTSYS;
49432cf86f6SMauro Carvalho Chehab 				break;
49532cf86f6SMauro Carvalho Chehab 			}
49632cf86f6SMauro Carvalho Chehab 
49732cf86f6SMauro Carvalho Chehab 			mutex_unlock(&ir->irctl_lock);
49832cf86f6SMauro Carvalho Chehab 			set_current_state(TASK_INTERRUPTIBLE);
49912accdcbSSean Young 			schedule();
50012accdcbSSean Young 			set_current_state(TASK_RUNNING);
50132cf86f6SMauro Carvalho Chehab 
50232cf86f6SMauro Carvalho Chehab 			if (mutex_lock_interruptible(&ir->irctl_lock)) {
50332cf86f6SMauro Carvalho Chehab 				ret = -ERESTARTSYS;
50432cf86f6SMauro Carvalho Chehab 				remove_wait_queue(&ir->buf->wait_poll, &wait);
50532cf86f6SMauro Carvalho Chehab 				goto out_unlocked;
50632cf86f6SMauro Carvalho Chehab 			}
50732cf86f6SMauro Carvalho Chehab 
50832cf86f6SMauro Carvalho Chehab 			if (!ir->attached) {
50932cf86f6SMauro Carvalho Chehab 				ret = -ENODEV;
510c77d17c0SSean Young 				goto out_locked;
51132cf86f6SMauro Carvalho Chehab 			}
51232cf86f6SMauro Carvalho Chehab 		} else {
51332cf86f6SMauro Carvalho Chehab 			lirc_buffer_read(ir->buf, buf);
51460519af3SHans Verkuil 			ret = copy_to_user((void __user *)buffer+written, buf,
51532cf86f6SMauro Carvalho Chehab 					   ir->buf->chunk_size);
51632cf86f6SMauro Carvalho Chehab 			if (!ret)
51732cf86f6SMauro Carvalho Chehab 				written += ir->buf->chunk_size;
51832cf86f6SMauro Carvalho Chehab 			else
51932cf86f6SMauro Carvalho Chehab 				ret = -EFAULT;
52032cf86f6SMauro Carvalho Chehab 		}
52132cf86f6SMauro Carvalho Chehab 	}
52232cf86f6SMauro Carvalho Chehab 
52332cf86f6SMauro Carvalho Chehab 	remove_wait_queue(&ir->buf->wait_poll, &wait);
52432cf86f6SMauro Carvalho Chehab 
52532cf86f6SMauro Carvalho Chehab out_locked:
52632cf86f6SMauro Carvalho Chehab 	mutex_unlock(&ir->irctl_lock);
52732cf86f6SMauro Carvalho Chehab 
52832cf86f6SMauro Carvalho Chehab out_unlocked:
52932cf86f6SMauro Carvalho Chehab 	kfree(buf);
53032cf86f6SMauro Carvalho Chehab 
53132cf86f6SMauro Carvalho Chehab 	return ret ? ret : written;
53232cf86f6SMauro Carvalho Chehab }
53332cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_dev_fop_read);
53432cf86f6SMauro Carvalho Chehab 
53532cf86f6SMauro Carvalho Chehab void *lirc_get_pdata(struct file *file)
53632cf86f6SMauro Carvalho Chehab {
5370990a97aSAl Viro 	return irctls[iminor(file_inode(file))]->d.data;
53832cf86f6SMauro Carvalho Chehab }
53932cf86f6SMauro Carvalho Chehab EXPORT_SYMBOL(lirc_get_pdata);
54032cf86f6SMauro Carvalho Chehab 
54132cf86f6SMauro Carvalho Chehab 
54232cf86f6SMauro Carvalho Chehab static int __init lirc_dev_init(void)
54332cf86f6SMauro Carvalho Chehab {
54432cf86f6SMauro Carvalho Chehab 	int retval;
54532cf86f6SMauro Carvalho Chehab 
54632cf86f6SMauro Carvalho Chehab 	lirc_class = class_create(THIS_MODULE, "lirc");
54732cf86f6SMauro Carvalho Chehab 	if (IS_ERR(lirc_class)) {
5483fac0314SAndi Shyti 		pr_err("class_create failed\n");
54954fcecafSAndi Shyti 		return PTR_ERR(lirc_class);
55032cf86f6SMauro Carvalho Chehab 	}
55132cf86f6SMauro Carvalho Chehab 
55232cf86f6SMauro Carvalho Chehab 	retval = alloc_chrdev_region(&lirc_base_dev, 0, MAX_IRCTL_DEVICES,
553463015ddSDavid Härdeman 				     "BaseRemoteCtl");
55432cf86f6SMauro Carvalho Chehab 	if (retval) {
55532cf86f6SMauro Carvalho Chehab 		class_destroy(lirc_class);
5563fac0314SAndi Shyti 		pr_err("alloc_chrdev_region failed\n");
55754fcecafSAndi Shyti 		return retval;
55832cf86f6SMauro Carvalho Chehab 	}
55932cf86f6SMauro Carvalho Chehab 
5603fac0314SAndi Shyti 	pr_info("IR Remote Control driver registered, major %d\n",
5613fac0314SAndi Shyti 						MAJOR(lirc_base_dev));
56232cf86f6SMauro Carvalho Chehab 
56354fcecafSAndi Shyti 	return 0;
56432cf86f6SMauro Carvalho Chehab }
56532cf86f6SMauro Carvalho Chehab 
56632cf86f6SMauro Carvalho Chehab static void __exit lirc_dev_exit(void)
56732cf86f6SMauro Carvalho Chehab {
56832cf86f6SMauro Carvalho Chehab 	class_destroy(lirc_class);
56932cf86f6SMauro Carvalho Chehab 	unregister_chrdev_region(lirc_base_dev, MAX_IRCTL_DEVICES);
5703fac0314SAndi Shyti 	pr_info("module unloaded\n");
57132cf86f6SMauro Carvalho Chehab }
57232cf86f6SMauro Carvalho Chehab 
57332cf86f6SMauro Carvalho Chehab module_init(lirc_dev_init);
57432cf86f6SMauro Carvalho Chehab module_exit(lirc_dev_exit);
57532cf86f6SMauro Carvalho Chehab 
57632cf86f6SMauro Carvalho Chehab MODULE_DESCRIPTION("LIRC base driver module");
57732cf86f6SMauro Carvalho Chehab MODULE_AUTHOR("Artur Lipowski");
57832cf86f6SMauro Carvalho Chehab MODULE_LICENSE("GPL");
579