Commit 74c839b2 authored by Sean Young's avatar Sean Young Committed by Mauro Carvalho Chehab
Browse files

[media] lirc: use refcounting for lirc devices



If a lirc device is unplugged, the struct rc_dev is freed even though
userspace can still have a file descriptor open on the lirc chardev. The
rc_dev structure can be used in a subsequent, or even currently executing
ioctl, read or write.

Signed-off-by: default avatarSean Young <sean@mess.org>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@s-opensource.com>
parent 069f3b10
Loading
Loading
Loading
Loading
+51 −69
Original line number Diff line number Diff line
@@ -54,7 +54,8 @@ struct irctl {
	struct lirc_buffer *buf;
	unsigned int chunk_size;

	struct cdev *cdev;
	struct device dev;
	struct cdev cdev;

	struct task_struct *task;
	long jiffies_to_wait;
@@ -76,15 +77,21 @@ static void lirc_irctl_init(struct irctl *ir)
	ir->d.minor = NOPLUG;
}

static void lirc_irctl_cleanup(struct irctl *ir)
static void lirc_release(struct device *ld)
{
	device_destroy(lirc_class, MKDEV(MAJOR(lirc_base_dev), ir->d.minor));
	struct irctl *ir = container_of(ld, struct irctl, dev);

	put_device(ir->dev.parent);

	if (ir->buf != ir->d.rbuf) {
		lirc_buffer_free(ir->buf);
		kfree(ir->buf);
	}
	ir->buf = NULL;

	mutex_lock(&lirc_dev_lock);
	irctls[ir->d.minor] = NULL;
	mutex_unlock(&lirc_dev_lock);
	kfree(ir);
}

/*  helper function
@@ -157,32 +164,21 @@ static int lirc_cdev_add(struct irctl *ir)
	struct cdev *cdev;
	int retval;

	cdev = cdev_alloc();
	if (!cdev)
		return -ENOMEM;
	cdev = &ir->cdev;

	if (d->fops) {
		cdev->ops = d->fops;
		cdev_init(cdev, d->fops);
		cdev->owner = d->owner;
	} else {
		cdev->ops = &lirc_dev_fops;
		cdev_init(cdev, &lirc_dev_fops);
		cdev->owner = THIS_MODULE;
	}
	retval = kobject_set_name(&cdev->kobj, "lirc%d", d->minor);
	if (retval)
		goto err_out;

	retval = cdev_add(cdev, MKDEV(MAJOR(lirc_base_dev), d->minor), 1);
	if (retval)
		goto err_out;

	ir->cdev = cdev;

	return 0;

err_out:
	cdev_del(cdev);
		return retval;

	cdev->kobj.parent = &ir->dev.kobj;
	return cdev_add(cdev, ir->dev.devt, 1);
}

static int lirc_allocate_buffer(struct irctl *ir)
@@ -304,9 +300,12 @@ static int lirc_allocate_driver(struct lirc_driver *d)

	ir->d = *d;

	device_create(lirc_class, ir->d.dev,
		      MKDEV(MAJOR(lirc_base_dev), ir->d.minor), NULL,
		      "lirc%u", ir->d.minor);
	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);
	device_initialize(&ir->dev);

	if (d->sample_rate) {
		ir->jiffies_to_wait = HZ / d->sample_rate;
@@ -329,14 +328,22 @@ static int lirc_allocate_driver(struct lirc_driver *d)
		goto out_sysfs;

	ir->attached = 1;

	err = device_add(&ir->dev);
	if (err)
		goto out_cdev;

	mutex_unlock(&lirc_dev_lock);

	get_device(ir->dev.parent);

	dev_info(ir->d.dev, "lirc_dev: driver %s registered at minor = %d\n",
		 ir->d.name, ir->d.minor);
	return minor;

out_cdev:
	cdev_del(&ir->cdev);
out_sysfs:
	device_destroy(lirc_class, MKDEV(MAJOR(lirc_base_dev), ir->d.minor));
	put_device(&ir->dev);
out_lock:
	mutex_unlock(&lirc_dev_lock);

@@ -364,7 +371,6 @@ EXPORT_SYMBOL(lirc_register_driver);
int lirc_unregister_driver(int minor)
{
	struct irctl *ir;
	struct cdev *cdev;

	if (minor < 0 || minor >= MAX_IRCTL_DEVICES) {
		pr_err("minor (%d) must be between 0 and %d!\n",
@@ -378,8 +384,6 @@ int lirc_unregister_driver(int minor)
		return -ENOENT;
	}

	cdev = ir->cdev;

	mutex_lock(&lirc_dev_lock);

	if (ir->d.minor != minor) {
@@ -401,22 +405,20 @@ int lirc_unregister_driver(int minor)
		dev_dbg(ir->d.dev, LOGHEAD "releasing opened driver\n",
			ir->d.name, ir->d.minor);
		wake_up_interruptible(&ir->buf->wait_poll);
	}

	mutex_lock(&ir->irctl_lock);

	if (ir->d.set_use_dec)
		ir->d.set_use_dec(ir->d.data);

		module_put(cdev->owner);
	mutex_unlock(&ir->irctl_lock);
	} else {
		lirc_irctl_cleanup(ir);
		cdev_del(cdev);
		kfree(ir);
		irctls[minor] = NULL;
	}

	mutex_unlock(&lirc_dev_lock);

	device_del(&ir->dev);
	cdev_del(&ir->cdev);
	put_device(&ir->dev);

	return 0;
}
EXPORT_SYMBOL(lirc_unregister_driver);
@@ -424,7 +426,6 @@ EXPORT_SYMBOL(lirc_unregister_driver);
int lirc_dev_fop_open(struct inode *inode, struct file *file)
{
	struct irctl *ir;
	struct cdev *cdev;
	int retval = 0;

	if (iminor(inode) >= MAX_IRCTL_DEVICES) {
@@ -459,18 +460,14 @@ int lirc_dev_fop_open(struct inode *inode, struct file *file)
			goto error;
	}

	cdev = ir->cdev;
	if (try_module_get(cdev->owner)) {
	ir->open++;
	if (ir->d.set_use_inc)
		retval = ir->d.set_use_inc(ir->d.data);

	if (retval) {
			module_put(cdev->owner);
		ir->open--;
		} else if (ir->buf) {
	} else {
		if (ir->buf)
			lirc_buffer_clear(ir->buf);
		}
		if (ir->task)
			wake_up_process(ir->task);
	}
@@ -487,7 +484,6 @@ EXPORT_SYMBOL(lirc_dev_fop_open);
int lirc_dev_fop_close(struct inode *inode, struct file *file)
{
	struct irctl *ir = irctls[iminor(inode)];
	struct cdev *cdev;
	int ret;

	if (!ir) {
@@ -495,25 +491,14 @@ int lirc_dev_fop_close(struct inode *inode, struct file *file)
		return -EINVAL;
	}

	cdev = ir->cdev;

	ret = mutex_lock_killable(&lirc_dev_lock);
	WARN_ON(ret);

	rc_close(ir->d.rdev);

	ir->open--;
	if (ir->attached) {
	if (ir->d.set_use_dec)
		ir->d.set_use_dec(ir->d.data);
		module_put(cdev->owner);
	} else {
		lirc_irctl_cleanup(ir);
		cdev_del(cdev);
		irctls[ir->d.minor] = NULL;
		kfree(ir);
	}

	if (!ret)
		mutex_unlock(&lirc_dev_lock);

@@ -780,15 +765,12 @@ static int __init lirc_dev_init(void)
		return retval;
	}


	pr_info("IR Remote Control driver registered, major %d\n",
						MAJOR(lirc_base_dev));

	return 0;
}



static void __exit lirc_dev_exit(void)
{
	class_destroy(lirc_class);