Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 1 | /* |
| 2 | * LIRC base driver |
| 3 | * |
| 4 | * by Artur Lipowski <alipowski@interia.pl> |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published by |
| 8 | * the Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | * |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 16 | */ |
| 17 | |
Andi Shyti | 3fac031 | 2016-07-06 06:01:16 -0300 | [diff] [blame] | 18 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| 19 | |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 20 | #include <linux/module.h> |
Ingo Molnar | 174cd4b | 2017-02-02 19:15:33 +0100 | [diff] [blame] | 21 | #include <linux/sched/signal.h> |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 22 | #include <linux/ioctl.h> |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 23 | #include <linux/poll.h> |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 24 | #include <linux/mutex.h> |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 25 | #include <linux/device.h> |
| 26 | #include <linux/cdev.h> |
David Härdeman | 46c8f47 | 2017-06-25 09:32:05 -0300 | [diff] [blame^] | 27 | #include <linux/idr.h> |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 28 | |
Srinivas Kandagatla | ca7a722 | 2013-07-22 04:23:07 -0300 | [diff] [blame] | 29 | #include <media/rc-core.h> |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 30 | #include <media/lirc.h> |
Jarod Wilson | 5690085 | 2010-07-16 14:25:33 -0300 | [diff] [blame] | 31 | #include <media/lirc_dev.h> |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 32 | |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 33 | #define LOGHEAD "lirc_dev (%s[%d]): " |
| 34 | |
| 35 | static dev_t lirc_base_dev; |
| 36 | |
| 37 | struct irctl { |
| 38 | struct lirc_driver d; |
David Härdeman | 3bce557 | 2017-06-25 09:31:55 -0300 | [diff] [blame] | 39 | bool attached; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 40 | int open; |
| 41 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 42 | struct mutex mutex; /* protect from simultaneous accesses */ |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 43 | struct lirc_buffer *buf; |
David Härdeman | 0f7c406 | 2017-05-01 10:32:34 -0300 | [diff] [blame] | 44 | bool buf_internal; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 45 | |
Sean Young | 74c839b | 2017-01-30 13:49:58 -0200 | [diff] [blame] | 46 | struct device dev; |
| 47 | struct cdev cdev; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 48 | }; |
| 49 | |
David Härdeman | 46c8f47 | 2017-06-25 09:32:05 -0300 | [diff] [blame^] | 50 | /* Used to keep track of allocated lirc devices */ |
| 51 | #define LIRC_MAX_DEVICES 256 |
| 52 | static DEFINE_IDA(lirc_ida); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 53 | |
| 54 | /* Only used for sysfs but defined to void otherwise */ |
| 55 | static struct class *lirc_class; |
| 56 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 57 | static void lirc_free_buffer(struct irctl *ir) |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 58 | { |
Sean Young | a607f51 | 2017-08-04 10:12:03 -0400 | [diff] [blame] | 59 | put_device(ir->dev.parent); |
| 60 | |
David Härdeman | 0f7c406 | 2017-05-01 10:32:34 -0300 | [diff] [blame] | 61 | if (ir->buf_internal) { |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 62 | lirc_buffer_free(ir->buf); |
| 63 | kfree(ir->buf); |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 64 | ir->buf = NULL; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 65 | } |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 66 | } |
| 67 | |
| 68 | static void lirc_release(struct device *ld) |
| 69 | { |
| 70 | struct irctl *ir = container_of(ld, struct irctl, dev); |
Sean Young | 74c839b | 2017-01-30 13:49:58 -0200 | [diff] [blame] | 71 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 72 | lirc_free_buffer(ir); |
Sean Young | 74c839b | 2017-01-30 13:49:58 -0200 | [diff] [blame] | 73 | kfree(ir); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 74 | } |
| 75 | |
Andi Shyti | 6fa99e1 | 2016-07-06 06:01:13 -0300 | [diff] [blame] | 76 | static int lirc_allocate_buffer(struct irctl *ir) |
| 77 | { |
Andi Shyti | 7014398 | 2016-07-06 06:01:14 -0300 | [diff] [blame] | 78 | int err = 0; |
Andi Shyti | 6fa99e1 | 2016-07-06 06:01:13 -0300 | [diff] [blame] | 79 | struct lirc_driver *d = &ir->d; |
| 80 | |
Andi Shyti | 6fa99e1 | 2016-07-06 06:01:13 -0300 | [diff] [blame] | 81 | if (d->rbuf) { |
| 82 | ir->buf = d->rbuf; |
David Härdeman | 0f7c406 | 2017-05-01 10:32:34 -0300 | [diff] [blame] | 83 | ir->buf_internal = false; |
Andi Shyti | 6fa99e1 | 2016-07-06 06:01:13 -0300 | [diff] [blame] | 84 | } else { |
| 85 | ir->buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); |
Andi Shyti | 7014398 | 2016-07-06 06:01:14 -0300 | [diff] [blame] | 86 | if (!ir->buf) { |
| 87 | err = -ENOMEM; |
| 88 | goto out; |
| 89 | } |
Andi Shyti | 6fa99e1 | 2016-07-06 06:01:13 -0300 | [diff] [blame] | 90 | |
David Härdeman | b145ef9 | 2017-06-25 09:31:45 -0300 | [diff] [blame] | 91 | err = lirc_buffer_init(ir->buf, d->chunk_size, d->buffer_size); |
Andi Shyti | 6fa99e1 | 2016-07-06 06:01:13 -0300 | [diff] [blame] | 92 | if (err) { |
| 93 | kfree(ir->buf); |
David Härdeman | 0f7c406 | 2017-05-01 10:32:34 -0300 | [diff] [blame] | 94 | ir->buf = NULL; |
Andi Shyti | 7014398 | 2016-07-06 06:01:14 -0300 | [diff] [blame] | 95 | goto out; |
Andi Shyti | 6fa99e1 | 2016-07-06 06:01:13 -0300 | [diff] [blame] | 96 | } |
David Härdeman | 0f7c406 | 2017-05-01 10:32:34 -0300 | [diff] [blame] | 97 | |
| 98 | ir->buf_internal = true; |
David Härdeman | 56481f0 | 2017-05-01 13:04:11 -0300 | [diff] [blame] | 99 | d->rbuf = ir->buf; |
Andi Shyti | 6fa99e1 | 2016-07-06 06:01:13 -0300 | [diff] [blame] | 100 | } |
Andi Shyti | 6fa99e1 | 2016-07-06 06:01:13 -0300 | [diff] [blame] | 101 | |
Andi Shyti | 7014398 | 2016-07-06 06:01:14 -0300 | [diff] [blame] | 102 | out: |
Andi Shyti | 7014398 | 2016-07-06 06:01:14 -0300 | [diff] [blame] | 103 | return err; |
Andi Shyti | 6fa99e1 | 2016-07-06 06:01:13 -0300 | [diff] [blame] | 104 | } |
| 105 | |
David Härdeman | 56481f0 | 2017-05-01 13:04:11 -0300 | [diff] [blame] | 106 | int lirc_register_driver(struct lirc_driver *d) |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 107 | { |
| 108 | struct irctl *ir; |
David Härdeman | 46c8f47 | 2017-06-25 09:32:05 -0300 | [diff] [blame^] | 109 | int minor; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 110 | int err; |
| 111 | |
| 112 | if (!d) { |
Andi Shyti | 3fac031 | 2016-07-06 06:01:16 -0300 | [diff] [blame] | 113 | pr_err("driver pointer must be not NULL!\n"); |
Andi Shyti | 54fceca | 2016-07-06 06:01:17 -0300 | [diff] [blame] | 114 | return -EBADRQC; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 115 | } |
| 116 | |
Jarod Wilson | 715d29a7 | 2010-10-18 12:02:01 -0300 | [diff] [blame] | 117 | if (!d->dev) { |
Andi Shyti | 3fac031 | 2016-07-06 06:01:16 -0300 | [diff] [blame] | 118 | pr_err("dev pointer not filled in!\n"); |
Andi Shyti | 54fceca | 2016-07-06 06:01:17 -0300 | [diff] [blame] | 119 | return -EINVAL; |
Jarod Wilson | 715d29a7 | 2010-10-18 12:02:01 -0300 | [diff] [blame] | 120 | } |
| 121 | |
David Härdeman | 712551f | 2017-05-01 13:04:21 -0300 | [diff] [blame] | 122 | if (!d->fops) { |
| 123 | pr_err("fops pointer not filled in!\n"); |
| 124 | return -EINVAL; |
| 125 | } |
| 126 | |
David Härdeman | b145ef9 | 2017-06-25 09:31:45 -0300 | [diff] [blame] | 127 | if (!d->rbuf && d->chunk_size < 1) { |
| 128 | pr_err("chunk_size must be set!\n"); |
| 129 | return -EINVAL; |
| 130 | } |
| 131 | |
| 132 | if (!d->rbuf && d->buffer_size < 1) { |
| 133 | pr_err("buffer_size must be set!\n"); |
| 134 | return -EINVAL; |
| 135 | } |
| 136 | |
Andi Shyti | 9675ee5 | 2016-07-06 06:01:23 -0300 | [diff] [blame] | 137 | if (d->code_length < 1 || d->code_length > (BUFLEN * 8)) { |
Andi Shyti | 3fac031 | 2016-07-06 06:01:16 -0300 | [diff] [blame] | 138 | dev_err(d->dev, "code length must be less than %d bits\n", |
| 139 | BUFLEN * 8); |
Andi Shyti | 54fceca | 2016-07-06 06:01:17 -0300 | [diff] [blame] | 140 | return -EBADRQC; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 141 | } |
| 142 | |
David Härdeman | c3104e1 | 2017-05-01 13:03:56 -0300 | [diff] [blame] | 143 | if (!d->rbuf && !(d->fops && d->fops->read && |
| 144 | d->fops->poll && d->fops->unlocked_ioctl)) { |
Andi Shyti | 14db9fc | 2016-07-06 06:01:21 -0300 | [diff] [blame] | 145 | dev_err(d->dev, "undefined read, poll, ioctl\n"); |
Andi Shyti | 54fceca | 2016-07-06 06:01:17 -0300 | [diff] [blame] | 146 | return -EBADRQC; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 147 | } |
| 148 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 149 | /* some safety check 8-) */ |
| 150 | d->name[sizeof(d->name) - 1] = '\0'; |
| 151 | |
| 152 | if (d->features == 0) |
| 153 | d->features = LIRC_CAN_REC_LIRCCODE; |
| 154 | |
| 155 | ir = kzalloc(sizeof(*ir), GFP_KERNEL); |
| 156 | if (!ir) |
| 157 | return -ENOMEM; |
| 158 | |
| 159 | mutex_init(&ir->mutex); |
| 160 | ir->d = *d; |
| 161 | |
| 162 | if (LIRC_CAN_REC(d->features)) { |
| 163 | err = lirc_allocate_buffer(ir); |
| 164 | if (err) { |
| 165 | kfree(ir); |
| 166 | return err; |
| 167 | } |
| 168 | d->rbuf = ir->buf; |
| 169 | } |
| 170 | |
David Härdeman | 46c8f47 | 2017-06-25 09:32:05 -0300 | [diff] [blame^] | 171 | minor = ida_simple_get(&lirc_ida, 0, LIRC_MAX_DEVICES, GFP_KERNEL); |
| 172 | if (minor < 0) { |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 173 | lirc_free_buffer(ir); |
| 174 | kfree(ir); |
David Härdeman | 46c8f47 | 2017-06-25 09:32:05 -0300 | [diff] [blame^] | 175 | return minor; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 176 | } |
| 177 | |
David Härdeman | c3c6dd7 | 2017-06-25 09:31:24 -0300 | [diff] [blame] | 178 | d->irctl = ir; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 179 | d->minor = minor; |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 180 | ir->d.minor = minor; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 181 | |
David Härdeman | 2582322 | 2017-05-01 13:04:01 -0300 | [diff] [blame] | 182 | device_initialize(&ir->dev); |
Sean Young | 74c839b | 2017-01-30 13:49:58 -0200 | [diff] [blame] | 183 | ir->dev.devt = MKDEV(MAJOR(lirc_base_dev), ir->d.minor); |
| 184 | ir->dev.class = lirc_class; |
| 185 | ir->dev.parent = d->dev; |
| 186 | ir->dev.release = lirc_release; |
| 187 | dev_set_name(&ir->dev, "lirc%d", ir->d.minor); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 188 | |
David Härdeman | 712551f | 2017-05-01 13:04:21 -0300 | [diff] [blame] | 189 | cdev_init(&ir->cdev, d->fops); |
| 190 | ir->cdev.owner = ir->d.owner; |
David Härdeman | 3bce557 | 2017-06-25 09:31:55 -0300 | [diff] [blame] | 191 | ir->attached = true; |
Sean Young | 74c839b | 2017-01-30 13:49:58 -0200 | [diff] [blame] | 192 | |
David Härdeman | 0510d81 | 2017-06-25 09:31:35 -0300 | [diff] [blame] | 193 | err = cdev_device_add(&ir->cdev, &ir->dev); |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 194 | if (err) { |
David Härdeman | 46c8f47 | 2017-06-25 09:32:05 -0300 | [diff] [blame^] | 195 | ida_simple_remove(&lirc_ida, minor); |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 196 | put_device(&ir->dev); |
| 197 | return err; |
| 198 | } |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 199 | |
Sean Young | a607f51 | 2017-08-04 10:12:03 -0400 | [diff] [blame] | 200 | get_device(ir->dev.parent); |
| 201 | |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 202 | dev_info(ir->d.dev, "lirc_dev: driver %s registered at minor = %d\n", |
| 203 | ir->d.name, ir->d.minor); |
David Härdeman | 56481f0 | 2017-05-01 13:04:11 -0300 | [diff] [blame] | 204 | |
David Härdeman | c3c6dd7 | 2017-06-25 09:31:24 -0300 | [diff] [blame] | 205 | return 0; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 206 | } |
| 207 | EXPORT_SYMBOL(lirc_register_driver); |
| 208 | |
David Härdeman | c3c6dd7 | 2017-06-25 09:31:24 -0300 | [diff] [blame] | 209 | void lirc_unregister_driver(struct lirc_driver *d) |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 210 | { |
| 211 | struct irctl *ir; |
| 212 | |
David Härdeman | c3c6dd7 | 2017-06-25 09:31:24 -0300 | [diff] [blame] | 213 | if (!d || !d->irctl) |
| 214 | return; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 215 | |
David Härdeman | c3c6dd7 | 2017-06-25 09:31:24 -0300 | [diff] [blame] | 216 | ir = d->irctl; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 217 | |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 218 | dev_dbg(ir->d.dev, "lirc_dev: driver %s unregistered from minor = %d\n", |
David Härdeman | c3c6dd7 | 2017-06-25 09:31:24 -0300 | [diff] [blame] | 219 | d->name, d->minor); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 220 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 221 | cdev_device_del(&ir->cdev, &ir->dev); |
| 222 | |
| 223 | mutex_lock(&ir->mutex); |
| 224 | |
David Härdeman | 3bce557 | 2017-06-25 09:31:55 -0300 | [diff] [blame] | 225 | ir->attached = false; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 226 | if (ir->open) { |
| 227 | dev_dbg(ir->d.dev, LOGHEAD "releasing opened driver\n", |
David Härdeman | c3c6dd7 | 2017-06-25 09:31:24 -0300 | [diff] [blame] | 228 | d->name, d->minor); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 229 | wake_up_interruptible(&ir->buf->wait_poll); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 230 | } |
| 231 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 232 | mutex_unlock(&ir->mutex); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 233 | |
David Härdeman | 46c8f47 | 2017-06-25 09:32:05 -0300 | [diff] [blame^] | 234 | ida_simple_remove(&lirc_ida, d->minor); |
Sean Young | 74c839b | 2017-01-30 13:49:58 -0200 | [diff] [blame] | 235 | put_device(&ir->dev); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 236 | } |
| 237 | EXPORT_SYMBOL(lirc_unregister_driver); |
| 238 | |
| 239 | int lirc_dev_fop_open(struct inode *inode, struct file *file) |
| 240 | { |
David Härdeman | 615cd3f | 2017-06-25 09:31:40 -0300 | [diff] [blame] | 241 | struct irctl *ir = container_of(inode->i_cdev, struct irctl, cdev); |
David Härdeman | de226ec | 2017-06-25 09:31:19 -0300 | [diff] [blame] | 242 | int retval; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 243 | |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 244 | dev_dbg(ir->d.dev, LOGHEAD "open called\n", ir->d.name, ir->d.minor); |
| 245 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 246 | retval = mutex_lock_interruptible(&ir->mutex); |
| 247 | if (retval) |
| 248 | return retval; |
| 249 | |
| 250 | if (!ir->attached) { |
| 251 | retval = -ENODEV; |
| 252 | goto out; |
| 253 | } |
| 254 | |
| 255 | if (ir->open) { |
| 256 | retval = -EBUSY; |
| 257 | goto out; |
| 258 | } |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 259 | |
Srinivas Kandagatla | ca7a722 | 2013-07-22 04:23:07 -0300 | [diff] [blame] | 260 | if (ir->d.rdev) { |
| 261 | retval = rc_open(ir->d.rdev); |
| 262 | if (retval) |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 263 | goto out; |
Srinivas Kandagatla | ca7a722 | 2013-07-22 04:23:07 -0300 | [diff] [blame] | 264 | } |
| 265 | |
David Härdeman | 2c5a1f44 | 2017-05-01 13:03:46 -0300 | [diff] [blame] | 266 | if (ir->buf) |
| 267 | lirc_buffer_clear(ir->buf); |
| 268 | |
Sean Young | 74c839b | 2017-01-30 13:49:58 -0200 | [diff] [blame] | 269 | ir->open++; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 270 | |
David Härdeman | 615cd3f | 2017-06-25 09:31:40 -0300 | [diff] [blame] | 271 | lirc_init_pdata(inode, file); |
Arnd Bergmann | d9d2e9d | 2010-08-15 18:51:56 +0200 | [diff] [blame] | 272 | nonseekable_open(inode, file); |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 273 | mutex_unlock(&ir->mutex); |
Arnd Bergmann | d9d2e9d | 2010-08-15 18:51:56 +0200 | [diff] [blame] | 274 | |
David Härdeman | de226ec | 2017-06-25 09:31:19 -0300 | [diff] [blame] | 275 | return 0; |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 276 | |
| 277 | out: |
| 278 | mutex_unlock(&ir->mutex); |
| 279 | return retval; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 280 | } |
| 281 | EXPORT_SYMBOL(lirc_dev_fop_open); |
| 282 | |
| 283 | int lirc_dev_fop_close(struct inode *inode, struct file *file) |
| 284 | { |
David Härdeman | 615cd3f | 2017-06-25 09:31:40 -0300 | [diff] [blame] | 285 | struct irctl *ir = file->private_data; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 286 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 287 | mutex_lock(&ir->mutex); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 288 | |
Markus Elfring | 3dd94f0 | 2014-11-20 09:01:32 -0300 | [diff] [blame] | 289 | rc_close(ir->d.rdev); |
Jarod Wilson | 715d29a7 | 2010-10-18 12:02:01 -0300 | [diff] [blame] | 290 | ir->open--; |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 291 | |
| 292 | mutex_unlock(&ir->mutex); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 293 | |
| 294 | return 0; |
| 295 | } |
| 296 | EXPORT_SYMBOL(lirc_dev_fop_close); |
| 297 | |
| 298 | unsigned int lirc_dev_fop_poll(struct file *file, poll_table *wait) |
| 299 | { |
David Härdeman | 615cd3f | 2017-06-25 09:31:40 -0300 | [diff] [blame] | 300 | struct irctl *ir = file->private_data; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 301 | unsigned int ret; |
| 302 | |
Dan Carpenter | 5c769a6 | 2010-11-17 02:12:23 -0300 | [diff] [blame] | 303 | if (!ir->attached) |
David Härdeman | 29debf3 | 2017-05-01 13:04:37 -0300 | [diff] [blame] | 304 | return POLLHUP | POLLERR; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 305 | |
Andy Shevchenko | 3656cdd | 2015-01-06 22:53:37 -0300 | [diff] [blame] | 306 | if (ir->buf) { |
| 307 | poll_wait(file, &ir->buf->wait_poll, wait); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 308 | |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 309 | if (lirc_buffer_empty(ir->buf)) |
| 310 | ret = 0; |
| 311 | else |
| 312 | ret = POLLIN | POLLRDNORM; |
Andy Shevchenko | 3656cdd | 2015-01-06 22:53:37 -0300 | [diff] [blame] | 313 | } else |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 314 | ret = POLLERR; |
| 315 | |
| 316 | dev_dbg(ir->d.dev, LOGHEAD "poll result = %d\n", |
| 317 | ir->d.name, ir->d.minor, ret); |
| 318 | |
| 319 | return ret; |
| 320 | } |
| 321 | EXPORT_SYMBOL(lirc_dev_fop_poll); |
| 322 | |
Arnd Bergmann | 044e587 | 2010-08-02 15:43:35 -0300 | [diff] [blame] | 323 | long lirc_dev_fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 324 | { |
David Härdeman | 615cd3f | 2017-06-25 09:31:40 -0300 | [diff] [blame] | 325 | struct irctl *ir = file->private_data; |
Jarod Wilson | be1f985 | 2010-10-08 17:24:21 -0300 | [diff] [blame] | 326 | __u32 mode; |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 327 | int result; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 328 | |
| 329 | dev_dbg(ir->d.dev, LOGHEAD "ioctl called (0x%x)\n", |
| 330 | ir->d.name, ir->d.minor, cmd); |
| 331 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 332 | result = mutex_lock_interruptible(&ir->mutex); |
| 333 | if (result) |
| 334 | return result; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 335 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 336 | if (!ir->attached) { |
| 337 | result = -ENODEV; |
| 338 | goto out; |
| 339 | } |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 340 | |
| 341 | switch (cmd) { |
| 342 | case LIRC_GET_FEATURES: |
Hans Verkuil | 60519af | 2014-08-20 19:41:03 -0300 | [diff] [blame] | 343 | result = put_user(ir->d.features, (__u32 __user *)arg); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 344 | break; |
| 345 | case LIRC_GET_REC_MODE: |
Sean Young | bd29120 | 2016-12-02 15:16:08 -0200 | [diff] [blame] | 346 | if (!LIRC_CAN_REC(ir->d.features)) { |
Andi Shyti | b408809 | 2016-07-06 06:01:24 -0300 | [diff] [blame] | 347 | result = -ENOTTY; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 348 | break; |
| 349 | } |
| 350 | |
| 351 | result = put_user(LIRC_REC2MODE |
| 352 | (ir->d.features & LIRC_CAN_REC_MASK), |
Hans Verkuil | 60519af | 2014-08-20 19:41:03 -0300 | [diff] [blame] | 353 | (__u32 __user *)arg); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 354 | break; |
| 355 | case LIRC_SET_REC_MODE: |
Sean Young | bd29120 | 2016-12-02 15:16:08 -0200 | [diff] [blame] | 356 | if (!LIRC_CAN_REC(ir->d.features)) { |
Andi Shyti | b408809 | 2016-07-06 06:01:24 -0300 | [diff] [blame] | 357 | result = -ENOTTY; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 358 | break; |
| 359 | } |
| 360 | |
Hans Verkuil | 60519af | 2014-08-20 19:41:03 -0300 | [diff] [blame] | 361 | result = get_user(mode, (__u32 __user *)arg); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 362 | if (!result && !(LIRC_MODE2REC(mode) & ir->d.features)) |
| 363 | result = -EINVAL; |
| 364 | /* |
| 365 | * FIXME: We should actually set the mode somehow but |
| 366 | * for now, lirc_serial doesn't support mode changing either |
| 367 | */ |
| 368 | break; |
| 369 | case LIRC_GET_LENGTH: |
Hans Verkuil | 60519af | 2014-08-20 19:41:03 -0300 | [diff] [blame] | 370 | result = put_user(ir->d.code_length, (__u32 __user *)arg); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 371 | break; |
| 372 | case LIRC_GET_MIN_TIMEOUT: |
| 373 | if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) || |
| 374 | ir->d.min_timeout == 0) { |
Andi Shyti | b408809 | 2016-07-06 06:01:24 -0300 | [diff] [blame] | 375 | result = -ENOTTY; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 376 | break; |
| 377 | } |
| 378 | |
Hans Verkuil | 60519af | 2014-08-20 19:41:03 -0300 | [diff] [blame] | 379 | result = put_user(ir->d.min_timeout, (__u32 __user *)arg); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 380 | break; |
| 381 | case LIRC_GET_MAX_TIMEOUT: |
| 382 | if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) || |
| 383 | ir->d.max_timeout == 0) { |
Andi Shyti | b408809 | 2016-07-06 06:01:24 -0300 | [diff] [blame] | 384 | result = -ENOTTY; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 385 | break; |
| 386 | } |
| 387 | |
Hans Verkuil | 60519af | 2014-08-20 19:41:03 -0300 | [diff] [blame] | 388 | result = put_user(ir->d.max_timeout, (__u32 __user *)arg); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 389 | break; |
| 390 | default: |
Sean Young | 5c862758 | 2017-01-26 15:19:33 -0200 | [diff] [blame] | 391 | result = -ENOTTY; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 392 | } |
| 393 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 394 | out: |
| 395 | mutex_unlock(&ir->mutex); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 396 | return result; |
| 397 | } |
| 398 | EXPORT_SYMBOL(lirc_dev_fop_ioctl); |
| 399 | |
| 400 | ssize_t lirc_dev_fop_read(struct file *file, |
Dan Carpenter | 0e83508 | 2010-11-17 02:13:39 -0300 | [diff] [blame] | 401 | char __user *buffer, |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 402 | size_t length, |
| 403 | loff_t *ppos) |
| 404 | { |
David Härdeman | 615cd3f | 2017-06-25 09:31:40 -0300 | [diff] [blame] | 405 | struct irctl *ir = file->private_data; |
Jarod Wilson | 715d29a7 | 2010-10-18 12:02:01 -0300 | [diff] [blame] | 406 | unsigned char *buf; |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 407 | int ret, written = 0; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 408 | DECLARE_WAITQUEUE(wait, current); |
| 409 | |
| 410 | dev_dbg(ir->d.dev, LOGHEAD "read called\n", ir->d.name, ir->d.minor); |
| 411 | |
David Härdeman | b145ef9 | 2017-06-25 09:31:45 -0300 | [diff] [blame] | 412 | buf = kzalloc(ir->buf->chunk_size, GFP_KERNEL); |
Jarod Wilson | 715d29a7 | 2010-10-18 12:02:01 -0300 | [diff] [blame] | 413 | if (!buf) |
| 414 | return -ENOMEM; |
| 415 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 416 | ret = mutex_lock_interruptible(&ir->mutex); |
| 417 | if (ret) { |
| 418 | kfree(buf); |
| 419 | return ret; |
Dan Carpenter | 250f7a5 | 2010-11-17 02:20:15 -0300 | [diff] [blame] | 420 | } |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 421 | |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 422 | if (!ir->attached) { |
Dan Carpenter | 250f7a5 | 2010-11-17 02:20:15 -0300 | [diff] [blame] | 423 | ret = -ENODEV; |
| 424 | goto out_locked; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 425 | } |
| 426 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 427 | if (!LIRC_CAN_REC(ir->d.features)) { |
| 428 | ret = -EINVAL; |
| 429 | goto out_locked; |
| 430 | } |
| 431 | |
David Härdeman | b145ef9 | 2017-06-25 09:31:45 -0300 | [diff] [blame] | 432 | if (length % ir->buf->chunk_size) { |
Dan Carpenter | 250f7a5 | 2010-11-17 02:20:15 -0300 | [diff] [blame] | 433 | ret = -EINVAL; |
| 434 | goto out_locked; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 435 | } |
| 436 | |
| 437 | /* |
| 438 | * we add ourselves to the task queue before buffer check |
| 439 | * to avoid losing scan code (in case when queue is awaken somewhere |
| 440 | * between while condition checking and scheduling) |
| 441 | */ |
| 442 | add_wait_queue(&ir->buf->wait_poll, &wait); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 443 | |
| 444 | /* |
| 445 | * while we didn't provide 'length' bytes, device is opened in blocking |
| 446 | * mode and 'copy_to_user' is happy, wait for data. |
| 447 | */ |
| 448 | while (written < length && ret == 0) { |
| 449 | if (lirc_buffer_empty(ir->buf)) { |
| 450 | /* According to the read(2) man page, 'written' can be |
| 451 | * returned as less than 'length', instead of blocking |
| 452 | * again, returning -EWOULDBLOCK, or returning |
Andi Shyti | 62e9268 | 2016-07-06 06:01:25 -0300 | [diff] [blame] | 453 | * -ERESTARTSYS |
| 454 | */ |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 455 | if (written) |
| 456 | break; |
| 457 | if (file->f_flags & O_NONBLOCK) { |
| 458 | ret = -EWOULDBLOCK; |
| 459 | break; |
| 460 | } |
| 461 | if (signal_pending(current)) { |
| 462 | ret = -ERESTARTSYS; |
| 463 | break; |
| 464 | } |
| 465 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 466 | mutex_unlock(&ir->mutex); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 467 | set_current_state(TASK_INTERRUPTIBLE); |
Sean Young | 12accdc | 2016-10-31 15:52:25 -0200 | [diff] [blame] | 468 | schedule(); |
| 469 | set_current_state(TASK_RUNNING); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 470 | |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 471 | ret = mutex_lock_interruptible(&ir->mutex); |
| 472 | if (ret) { |
Jarod Wilson | 69c271f | 2010-07-07 11:29:44 -0300 | [diff] [blame] | 473 | remove_wait_queue(&ir->buf->wait_poll, &wait); |
Jarod Wilson | 69c271f | 2010-07-07 11:29:44 -0300 | [diff] [blame] | 474 | goto out_unlocked; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 475 | } |
| 476 | |
| 477 | if (!ir->attached) { |
| 478 | ret = -ENODEV; |
Sean Young | c77d17c0 | 2016-10-31 15:52:27 -0200 | [diff] [blame] | 479 | goto out_locked; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 480 | } |
| 481 | } else { |
| 482 | lirc_buffer_read(ir->buf, buf); |
Hans Verkuil | 60519af | 2014-08-20 19:41:03 -0300 | [diff] [blame] | 483 | ret = copy_to_user((void __user *)buffer+written, buf, |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 484 | ir->buf->chunk_size); |
Dan Carpenter | 250f7a5 | 2010-11-17 02:20:15 -0300 | [diff] [blame] | 485 | if (!ret) |
| 486 | written += ir->buf->chunk_size; |
| 487 | else |
| 488 | ret = -EFAULT; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 489 | } |
| 490 | } |
| 491 | |
| 492 | remove_wait_queue(&ir->buf->wait_poll, &wait); |
Dan Carpenter | 250f7a5 | 2010-11-17 02:20:15 -0300 | [diff] [blame] | 493 | |
| 494 | out_locked: |
David Härdeman | 3381b77 | 2017-06-30 05:41:57 -0300 | [diff] [blame] | 495 | mutex_unlock(&ir->mutex); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 496 | |
Jarod Wilson | 69c271f | 2010-07-07 11:29:44 -0300 | [diff] [blame] | 497 | out_unlocked: |
Jarod Wilson | 715d29a7 | 2010-10-18 12:02:01 -0300 | [diff] [blame] | 498 | kfree(buf); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 499 | |
| 500 | return ret ? ret : written; |
| 501 | } |
| 502 | EXPORT_SYMBOL(lirc_dev_fop_read); |
| 503 | |
David Härdeman | 615cd3f | 2017-06-25 09:31:40 -0300 | [diff] [blame] | 504 | void lirc_init_pdata(struct inode *inode, struct file *file) |
| 505 | { |
| 506 | struct irctl *ir = container_of(inode->i_cdev, struct irctl, cdev); |
| 507 | |
| 508 | file->private_data = ir; |
| 509 | } |
| 510 | EXPORT_SYMBOL(lirc_init_pdata); |
| 511 | |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 512 | void *lirc_get_pdata(struct file *file) |
| 513 | { |
David Härdeman | 615cd3f | 2017-06-25 09:31:40 -0300 | [diff] [blame] | 514 | struct irctl *ir = file->private_data; |
| 515 | |
| 516 | return ir->d.data; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 517 | } |
| 518 | EXPORT_SYMBOL(lirc_get_pdata); |
| 519 | |
| 520 | |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 521 | static int __init lirc_dev_init(void) |
| 522 | { |
| 523 | int retval; |
| 524 | |
| 525 | lirc_class = class_create(THIS_MODULE, "lirc"); |
| 526 | if (IS_ERR(lirc_class)) { |
Andi Shyti | 3fac031 | 2016-07-06 06:01:16 -0300 | [diff] [blame] | 527 | pr_err("class_create failed\n"); |
Andi Shyti | 54fceca | 2016-07-06 06:01:17 -0300 | [diff] [blame] | 528 | return PTR_ERR(lirc_class); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 529 | } |
| 530 | |
David Härdeman | 46c8f47 | 2017-06-25 09:32:05 -0300 | [diff] [blame^] | 531 | retval = alloc_chrdev_region(&lirc_base_dev, 0, LIRC_MAX_DEVICES, |
David Härdeman | 463015d | 2017-05-01 13:04:47 -0300 | [diff] [blame] | 532 | "BaseRemoteCtl"); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 533 | if (retval) { |
| 534 | class_destroy(lirc_class); |
Andi Shyti | 3fac031 | 2016-07-06 06:01:16 -0300 | [diff] [blame] | 535 | pr_err("alloc_chrdev_region failed\n"); |
Andi Shyti | 54fceca | 2016-07-06 06:01:17 -0300 | [diff] [blame] | 536 | return retval; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 537 | } |
| 538 | |
Andi Shyti | 3fac031 | 2016-07-06 06:01:16 -0300 | [diff] [blame] | 539 | pr_info("IR Remote Control driver registered, major %d\n", |
| 540 | MAJOR(lirc_base_dev)); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 541 | |
Andi Shyti | 54fceca | 2016-07-06 06:01:17 -0300 | [diff] [blame] | 542 | return 0; |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 543 | } |
| 544 | |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 545 | static void __exit lirc_dev_exit(void) |
| 546 | { |
| 547 | class_destroy(lirc_class); |
David Härdeman | 46c8f47 | 2017-06-25 09:32:05 -0300 | [diff] [blame^] | 548 | unregister_chrdev_region(lirc_base_dev, LIRC_MAX_DEVICES); |
Andi Shyti | 3fac031 | 2016-07-06 06:01:16 -0300 | [diff] [blame] | 549 | pr_info("module unloaded\n"); |
Jarod Wilson | 4a62a5a | 2010-07-03 01:06:57 -0300 | [diff] [blame] | 550 | } |
| 551 | |
| 552 | module_init(lirc_dev_init); |
| 553 | module_exit(lirc_dev_exit); |
| 554 | |
| 555 | MODULE_DESCRIPTION("LIRC base driver module"); |
| 556 | MODULE_AUTHOR("Artur Lipowski"); |
| 557 | MODULE_LICENSE("GPL"); |