diff options
Diffstat (limited to 'drivers/media/rc/lirc_dev.c')
-rw-r--r-- | drivers/media/rc/lirc_dev.c | 515 |
1 files changed, 206 insertions, 309 deletions
diff --git a/drivers/media/rc/lirc_dev.c b/drivers/media/rc/lirc_dev.c index 9080e39ea391..e16d1138ca48 100644 --- a/drivers/media/rc/lirc_dev.c +++ b/drivers/media/rc/lirc_dev.c @@ -24,96 +24,91 @@ #include <linux/mutex.h> #include <linux/device.h> #include <linux/cdev.h> +#include <linux/idr.h> #include <media/rc-core.h> #include <media/lirc.h> #include <media/lirc_dev.h> -#define NOPLUG -1 #define LOGHEAD "lirc_dev (%s[%d]): " static dev_t lirc_base_dev; -struct irctl { - struct lirc_driver d; - int attached; - int open; +/* Used to keep track of allocated lirc devices */ +#define LIRC_MAX_DEVICES 256 +static DEFINE_IDA(lirc_ida); - struct mutex irctl_lock; - struct lirc_buffer *buf; - bool buf_internal; - unsigned int chunk_size; - - struct device dev; - struct cdev cdev; -}; +/* Only used for sysfs but defined to void otherwise */ +static struct class *lirc_class; -static DEFINE_MUTEX(lirc_dev_lock); +static void lirc_release_device(struct device *ld) +{ + struct lirc_dev *d = container_of(ld, struct lirc_dev, dev); -static struct irctl *irctls[MAX_IRCTL_DEVICES]; + put_device(d->dev.parent); -/* Only used for sysfs but defined to void otherwise */ -static struct class *lirc_class; + if (d->buf_internal) { + lirc_buffer_free(d->buf); + kfree(d->buf); + d->buf = NULL; + } + kfree(d); + module_put(THIS_MODULE); +} -static void lirc_release(struct device *ld) +static int lirc_allocate_buffer(struct lirc_dev *d) { - struct irctl *ir = container_of(ld, struct irctl, dev); + int err; - put_device(ir->dev.parent); + if (d->buf) { + d->buf_internal = false; + return 0; + } - if (ir->buf_internal) { - lirc_buffer_free(ir->buf); - kfree(ir->buf); + d->buf = kmalloc(sizeof(*d->buf), GFP_KERNEL); + if (!d->buf) + return -ENOMEM; + + err = lirc_buffer_init(d->buf, d->chunk_size, d->buffer_size); + if (err) { + kfree(d->buf); + d->buf = NULL; + return err; } - mutex_lock(&lirc_dev_lock); - irctls[ir->d.minor] = NULL; - mutex_unlock(&lirc_dev_lock); - kfree(ir); + d->buf_internal = true; + return 0; } -static int lirc_allocate_buffer(struct irctl *ir) +struct lirc_dev * +lirc_allocate_device(void) { - int err = 0; - int bytes_in_key; - unsigned int chunk_size; - unsigned int buffer_size; - struct lirc_driver *d = &ir->d; - - bytes_in_key = BITS_TO_LONGS(d->code_length) + - (d->code_length % 8 ? 1 : 0); - buffer_size = d->buffer_size ? d->buffer_size : BUFLEN / bytes_in_key; - chunk_size = d->chunk_size ? d->chunk_size : bytes_in_key; - - if (d->rbuf) { - ir->buf = d->rbuf; - ir->buf_internal = false; - } else { - ir->buf = kmalloc(sizeof(struct lirc_buffer), GFP_KERNEL); - if (!ir->buf) { - err = -ENOMEM; - goto out; - } + struct lirc_dev *d; - err = lirc_buffer_init(ir->buf, chunk_size, buffer_size); - if (err) { - kfree(ir->buf); - ir->buf = NULL; - goto out; - } - - ir->buf_internal = true; - d->rbuf = ir->buf; + d = kzalloc(sizeof(*d), GFP_KERNEL); + if (d) { + mutex_init(&d->mutex); + device_initialize(&d->dev); + d->dev.class = lirc_class; + d->dev.release = lirc_release_device; + __module_get(THIS_MODULE); } - ir->chunk_size = ir->buf->chunk_size; -out: - return err; + return d; } +EXPORT_SYMBOL(lirc_allocate_device); -int lirc_register_driver(struct lirc_driver *d) +void lirc_free_device(struct lirc_dev *d) +{ + if (!d) + return; + + put_device(&d->dev); +} +EXPORT_SYMBOL(lirc_free_device); + +int lirc_register_device(struct lirc_dev *d) { - struct irctl *ir; int minor; int err; @@ -122,8 +117,8 @@ int lirc_register_driver(struct lirc_driver *d) return -EBADRQC; } - if (!d->dev) { - pr_err("dev pointer not filled in!\n"); + if (!d->dev.parent) { + pr_err("dev parent pointer not filled in!\n"); return -EINVAL; } @@ -132,226 +127,146 @@ int lirc_register_driver(struct lirc_driver *d) return -EINVAL; } - if (d->minor >= MAX_IRCTL_DEVICES) { - dev_err(d->dev, "minor must be between 0 and %d!\n", - MAX_IRCTL_DEVICES - 1); - return -EBADRQC; + if (!d->buf && d->chunk_size < 1) { + pr_err("chunk_size must be set!\n"); + return -EINVAL; } - if (d->code_length < 1 || d->code_length > (BUFLEN * 8)) { - dev_err(d->dev, "code length must be less than %d bits\n", - BUFLEN * 8); - return -EBADRQC; + if (!d->buf && d->buffer_size < 1) { + pr_err("buffer_size must be set!\n"); + return -EINVAL; } - if (!d->rbuf && !(d->fops && d->fops->read && - d->fops->poll && d->fops->unlocked_ioctl)) { - dev_err(d->dev, "undefined read, poll, ioctl\n"); + if (d->code_length < 1 || d->code_length > (BUFLEN * 8)) { + dev_err(&d->dev, "code length must be less than %d bits\n", + BUFLEN * 8); return -EBADRQC; } - mutex_lock(&lirc_dev_lock); - - minor = d->minor; - - if (minor < 0) { - /* find first free slot for driver */ - for (minor = 0; minor < MAX_IRCTL_DEVICES; minor++) - if (!irctls[minor]) - break; - if (minor == MAX_IRCTL_DEVICES) { - dev_err(d->dev, "no free slots for drivers!\n"); - err = -ENOMEM; - goto out_lock; - } - } else if (irctls[minor]) { - dev_err(d->dev, "minor (%d) just registered!\n", minor); - err = -EBUSY; - goto out_lock; - } - - ir = kzalloc(sizeof(struct irctl), GFP_KERNEL); - if (!ir) { - err = -ENOMEM; - goto out_lock; + if (!d->buf && !(d->fops && d->fops->read && + d->fops->poll && d->fops->unlocked_ioctl)) { + dev_err(&d->dev, "undefined read, poll, ioctl\n"); + return -EBADRQC; } - mutex_init(&ir->irctl_lock); - irctls[minor] = ir; - d->minor = minor; - /* some safety check 8-) */ - d->name[sizeof(d->name)-1] = '\0'; + d->name[sizeof(d->name) - 1] = '\0'; if (d->features == 0) d->features = LIRC_CAN_REC_LIRCCODE; - ir->d = *d; - if (LIRC_CAN_REC(d->features)) { - err = lirc_allocate_buffer(irctls[minor]); - if (err) { - kfree(ir); - goto out_lock; - } - d->rbuf = ir->buf; + err = lirc_allocate_buffer(d); + if (err) + return err; } - device_initialize(&ir->dev); - ir->dev.devt = MKDEV(MAJOR(lirc_base_dev), ir->d.minor); - ir->dev.class = lirc_class; - ir->dev.parent = d->dev; - ir->dev.release = lirc_release; - dev_set_name(&ir->dev, "lirc%d", ir->d.minor); + minor = ida_simple_get(&lirc_ida, 0, LIRC_MAX_DEVICES, GFP_KERNEL); + if (minor < 0) + return minor; - cdev_init(&ir->cdev, d->fops); - ir->cdev.owner = ir->d.owner; - ir->cdev.kobj.parent = &ir->dev.kobj; - - err = cdev_add(&ir->cdev, ir->dev.devt, 1); - if (err) - goto out_free_dev; - - ir->attached = 1; - - err = device_add(&ir->dev); - if (err) - goto out_cdev; - - mutex_unlock(&lirc_dev_lock); + d->minor = minor; + d->dev.devt = MKDEV(MAJOR(lirc_base_dev), d->minor); + dev_set_name(&d->dev, "lirc%d", d->minor); - get_device(ir->dev.parent); + cdev_init(&d->cdev, d->fops); + d->cdev.owner = d->owner; + d->attached = true; - dev_info(ir->d.dev, "lirc_dev: driver %s registered at minor = %d\n", - ir->d.name, ir->d.minor); + err = cdev_device_add(&d->cdev, &d->dev); + if (err) { + ida_simple_remove(&lirc_ida, minor); + return err; + } - return minor; + get_device(d->dev.parent); -out_cdev: - cdev_del(&ir->cdev); -out_free_dev: - put_device(&ir->dev); -out_lock: - mutex_unlock(&lirc_dev_lock); + dev_info(&d->dev, "lirc_dev: driver %s registered at minor = %d\n", + d->name, d->minor); - return err; + return 0; } -EXPORT_SYMBOL(lirc_register_driver); +EXPORT_SYMBOL(lirc_register_device); -int lirc_unregister_driver(int minor) +void lirc_unregister_device(struct lirc_dev *d) { - struct irctl *ir; + if (!d) + return; - if (minor < 0 || minor >= MAX_IRCTL_DEVICES) { - pr_err("minor (%d) must be between 0 and %d!\n", - minor, MAX_IRCTL_DEVICES - 1); - return -EBADRQC; - } - - ir = irctls[minor]; - if (!ir) { - pr_err("failed to get irctl\n"); - return -ENOENT; - } + dev_dbg(&d->dev, "lirc_dev: driver %s unregistered from minor = %d\n", + d->name, d->minor); - mutex_lock(&lirc_dev_lock); + mutex_lock(&d->mutex); - if (ir->d.minor != minor) { - dev_err(ir->d.dev, "lirc_dev: minor %d device not registered\n", - minor); - mutex_unlock(&lirc_dev_lock); - return -ENOENT; + d->attached = false; + if (d->open) { + dev_dbg(&d->dev, LOGHEAD "releasing opened driver\n", + d->name, d->minor); + wake_up_interruptible(&d->buf->wait_poll); } - dev_dbg(ir->d.dev, "lirc_dev: driver %s unregistered from minor = %d\n", - ir->d.name, ir->d.minor); - - ir->attached = 0; - if (ir->open) { - dev_dbg(ir->d.dev, LOGHEAD "releasing opened driver\n", - ir->d.name, ir->d.minor); - wake_up_interruptible(&ir->buf->wait_poll); - } + mutex_unlock(&d->mutex); - mutex_unlock(&lirc_dev_lock); - - device_del(&ir->dev); - cdev_del(&ir->cdev); - put_device(&ir->dev); - - return 0; + cdev_device_del(&d->cdev, &d->dev); + ida_simple_remove(&lirc_ida, d->minor); + put_device(&d->dev); } -EXPORT_SYMBOL(lirc_unregister_driver); +EXPORT_SYMBOL(lirc_unregister_device); int lirc_dev_fop_open(struct inode *inode, struct file *file) { - struct irctl *ir; - int retval = 0; - - if (iminor(inode) >= MAX_IRCTL_DEVICES) { - pr_err("open result for %d is -ENODEV\n", iminor(inode)); - return -ENODEV; - } - - if (mutex_lock_interruptible(&lirc_dev_lock)) - return -ERESTARTSYS; - - ir = irctls[iminor(inode)]; - mutex_unlock(&lirc_dev_lock); + struct lirc_dev *d = container_of(inode->i_cdev, struct lirc_dev, cdev); + int retval; - if (!ir) { - retval = -ENODEV; - goto error; - } + dev_dbg(&d->dev, LOGHEAD "open called\n", d->name, d->minor); - dev_dbg(ir->d.dev, LOGHEAD "open called\n", ir->d.name, ir->d.minor); + retval = mutex_lock_interruptible(&d->mutex); + if (retval) + return retval; - if (ir->d.minor == NOPLUG) { + if (!d->attached) { retval = -ENODEV; - goto error; + goto out; } - if (ir->open) { + if (d->open) { retval = -EBUSY; - goto error; + goto out; } - if (ir->d.rdev) { - retval = rc_open(ir->d.rdev); + if (d->rdev) { + retval = rc_open(d->rdev); if (retval) - goto error; + goto out; } - if (ir->buf) - lirc_buffer_clear(ir->buf); + if (d->buf) + lirc_buffer_clear(d->buf); - ir->open++; + d->open++; -error: + lirc_init_pdata(inode, file); nonseekable_open(inode, file); + mutex_unlock(&d->mutex); + + return 0; +out: + mutex_unlock(&d->mutex); return retval; } EXPORT_SYMBOL(lirc_dev_fop_open); int lirc_dev_fop_close(struct inode *inode, struct file *file) { - struct irctl *ir = irctls[iminor(inode)]; - int ret; - - if (!ir) { - pr_err("called with invalid irctl\n"); - return -EINVAL; - } + struct lirc_dev *d = file->private_data; - ret = mutex_lock_killable(&lirc_dev_lock); - WARN_ON(ret); + mutex_lock(&d->mutex); - rc_close(ir->d.rdev); + rc_close(d->rdev); + d->open--; - ir->open--; - if (!ret) - mutex_unlock(&lirc_dev_lock); + mutex_unlock(&d->mutex); return 0; } @@ -359,29 +274,24 @@ EXPORT_SYMBOL(lirc_dev_fop_close); unsigned int lirc_dev_fop_poll(struct file *file, poll_table *wait) { - struct irctl *ir = irctls[iminor(file_inode(file))]; + struct lirc_dev *d = file->private_data; unsigned int ret; - if (!ir) { - pr_err("called with invalid irctl\n"); - return POLLERR; - } - - if (!ir->attached) + if (!d->attached) return POLLHUP | POLLERR; - if (ir->buf) { - poll_wait(file, &ir->buf->wait_poll, wait); + if (d->buf) { + poll_wait(file, &d->buf->wait_poll, wait); - if (lirc_buffer_empty(ir->buf)) + if (lirc_buffer_empty(d->buf)) ret = 0; else ret = POLLIN | POLLRDNORM; - } else + } else { ret = POLLERR; + } - dev_dbg(ir->d.dev, LOGHEAD "poll result = %d\n", - ir->d.name, ir->d.minor, ret); + dev_dbg(&d->dev, LOGHEAD "poll result = %d\n", d->name, d->minor, ret); return ret; } @@ -389,48 +299,44 @@ EXPORT_SYMBOL(lirc_dev_fop_poll); long lirc_dev_fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { + struct lirc_dev *d = file->private_data; __u32 mode; - int result = 0; - struct irctl *ir = irctls[iminor(file_inode(file))]; + int result; - if (!ir) { - pr_err("no irctl found!\n"); - return -ENODEV; - } + dev_dbg(&d->dev, LOGHEAD "ioctl called (0x%x)\n", + d->name, d->minor, cmd); - dev_dbg(ir->d.dev, LOGHEAD "ioctl called (0x%x)\n", - ir->d.name, ir->d.minor, cmd); + result = mutex_lock_interruptible(&d->mutex); + if (result) + return result; - if (ir->d.minor == NOPLUG || !ir->attached) { - dev_err(ir->d.dev, LOGHEAD "ioctl result = -ENODEV\n", - ir->d.name, ir->d.minor); - return -ENODEV; + if (!d->attached) { + result = -ENODEV; + goto out; } - mutex_lock(&ir->irctl_lock); - switch (cmd) { case LIRC_GET_FEATURES: - result = put_user(ir->d.features, (__u32 __user *)arg); + result = put_user(d->features, (__u32 __user *)arg); break; case LIRC_GET_REC_MODE: - if (!LIRC_CAN_REC(ir->d.features)) { + if (!LIRC_CAN_REC(d->features)) { result = -ENOTTY; break; } result = put_user(LIRC_REC2MODE - (ir->d.features & LIRC_CAN_REC_MASK), + (d->features & LIRC_CAN_REC_MASK), (__u32 __user *)arg); break; case LIRC_SET_REC_MODE: - if (!LIRC_CAN_REC(ir->d.features)) { + if (!LIRC_CAN_REC(d->features)) { result = -ENOTTY; break; } result = get_user(mode, (__u32 __user *)arg); - if (!result && !(LIRC_MODE2REC(mode) & ir->d.features)) + if (!result && !(LIRC_MODE2REC(mode) & d->features)) result = -EINVAL; /* * FIXME: We should actually set the mode somehow but @@ -438,32 +344,14 @@ long lirc_dev_fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) */ break; case LIRC_GET_LENGTH: - result = put_user(ir->d.code_length, (__u32 __user *)arg); - break; - case LIRC_GET_MIN_TIMEOUT: - if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) || - ir->d.min_timeout == 0) { - result = -ENOTTY; - break; - } - - result = put_user(ir->d.min_timeout, (__u32 __user *)arg); - break; - case LIRC_GET_MAX_TIMEOUT: - if (!(ir->d.features & LIRC_CAN_SET_REC_TIMEOUT) || - ir->d.max_timeout == 0) { - result = -ENOTTY; - break; - } - - result = put_user(ir->d.max_timeout, (__u32 __user *)arg); + result = put_user(d->code_length, (__u32 __user *)arg); break; default: result = -ENOTTY; } - mutex_unlock(&ir->irctl_lock); - +out: + mutex_unlock(&d->mutex); return result; } EXPORT_SYMBOL(lirc_dev_fop_ioctl); @@ -473,35 +361,34 @@ ssize_t lirc_dev_fop_read(struct file *file, size_t length, loff_t *ppos) { - struct irctl *ir = irctls[iminor(file_inode(file))]; + struct lirc_dev *d = file->private_data; unsigned char *buf; - int ret = 0, written = 0; + int ret, written = 0; DECLARE_WAITQUEUE(wait, current); - if (!ir) { - pr_err("called with invalid irctl\n"); - return -ENODEV; - } - - if (!LIRC_CAN_REC(ir->d.features)) - return -EINVAL; - - dev_dbg(ir->d.dev, LOGHEAD "read called\n", ir->d.name, ir->d.minor); - - buf = kzalloc(ir->chunk_size, GFP_KERNEL); + buf = kzalloc(d->buf->chunk_size, GFP_KERNEL); if (!buf) return -ENOMEM; - if (mutex_lock_interruptible(&ir->irctl_lock)) { - ret = -ERESTARTSYS; - goto out_unlocked; + dev_dbg(&d->dev, LOGHEAD "read called\n", d->name, d->minor); + + ret = mutex_lock_interruptible(&d->mutex); + if (ret) { + kfree(buf); + return ret; } - if (!ir->attached) { + + if (!d->attached) { ret = -ENODEV; goto out_locked; } - if (length % ir->chunk_size) { + if (!LIRC_CAN_REC(d->features)) { + ret = -EINVAL; + goto out_locked; + } + + if (length % d->buf->chunk_size) { ret = -EINVAL; goto out_locked; } @@ -511,14 +398,14 @@ ssize_t lirc_dev_fop_read(struct file *file, * to avoid losing scan code (in case when queue is awaken somewhere * between while condition checking and scheduling) */ - add_wait_queue(&ir->buf->wait_poll, &wait); + add_wait_queue(&d->buf->wait_poll, &wait); /* * while we didn't provide 'length' bytes, device is opened in blocking * mode and 'copy_to_user' is happy, wait for data. */ while (written < length && ret == 0) { - if (lirc_buffer_empty(ir->buf)) { + if (lirc_buffer_empty(d->buf)) { /* According to the read(2) man page, 'written' can be * returned as less than 'length', instead of blocking * again, returning -EWOULDBLOCK, or returning @@ -535,36 +422,36 @@ ssize_t lirc_dev_fop_read(struct file *file, break; } - mutex_unlock(&ir->irctl_lock); + mutex_unlock(&d->mutex); set_current_state(TASK_INTERRUPTIBLE); schedule(); set_current_state(TASK_RUNNING); - if (mutex_lock_interruptible(&ir->irctl_lock)) { - ret = -ERESTARTSYS; - remove_wait_queue(&ir->buf->wait_poll, &wait); + ret = mutex_lock_interruptible(&d->mutex); + if (ret) { + remove_wait_queue(&d->buf->wait_poll, &wait); goto out_unlocked; } - if (!ir->attached) { + if (!d->attached) { ret = -ENODEV; goto out_locked; } } else { - lirc_buffer_read(ir->buf, buf); + lirc_buffer_read(d->buf, buf); ret = copy_to_user((void __user *)buffer+written, buf, - ir->buf->chunk_size); + d->buf->chunk_size); if (!ret) - written += ir->buf->chunk_size; + written += d->buf->chunk_size; else ret = -EFAULT; } } - remove_wait_queue(&ir->buf->wait_poll, &wait); + remove_wait_queue(&d->buf->wait_poll, &wait); out_locked: - mutex_unlock(&ir->irctl_lock); + mutex_unlock(&d->mutex); out_unlocked: kfree(buf); @@ -573,9 +460,19 @@ out_unlocked: } EXPORT_SYMBOL(lirc_dev_fop_read); +void lirc_init_pdata(struct inode *inode, struct file *file) +{ + struct lirc_dev *d = container_of(inode->i_cdev, struct lirc_dev, cdev); + + file->private_data = d; +} +EXPORT_SYMBOL(lirc_init_pdata); + void *lirc_get_pdata(struct file *file) { - return irctls[iminor(file_inode(file))]->d.data; + struct lirc_dev *d = file->private_data; + + return d->data; } EXPORT_SYMBOL(lirc_get_pdata); @@ -590,7 +487,7 @@ static int __init lirc_dev_init(void) return PTR_ERR(lirc_class); } - retval = alloc_chrdev_region(&lirc_base_dev, 0, MAX_IRCTL_DEVICES, + retval = alloc_chrdev_region(&lirc_base_dev, 0, LIRC_MAX_DEVICES, "BaseRemoteCtl"); if (retval) { class_destroy(lirc_class); @@ -607,7 +504,7 @@ static int __init lirc_dev_init(void) static void __exit lirc_dev_exit(void) { class_destroy(lirc_class); - unregister_chrdev_region(lirc_base_dev, MAX_IRCTL_DEVICES); + unregister_chrdev_region(lirc_base_dev, LIRC_MAX_DEVICES); pr_info("module unloaded\n"); } |