Commit 7f8ecfab authored by Hans Verkuil's avatar Hans Verkuil Committed by Mauro Carvalho Chehab
Browse files

V4L/DVB (8852): v4l2: use register_chrdev_region instead of register_chrdev



Replace the old register_chrdev with the more flexible register_chrdev_region.

Ensure that the release() is called when the very last chardev usage was
released, and not when the sysfs devices were removed. This should simplify
hotpluggable drivers considerably.

Tested-by: default avatarMike Isely <isely@isely.net>
Tested-by: default avatarLaurent Pinchart <laurent.pinchart@skynet.be>
Signed-off-by: default avatarHans Verkuil <hverkuil@xs4all.nl>
Acked-by: default avatarDavid Ellingsworth <david@identd.dyndns.org>
Reviewed-by: default avatarHans de Goede <j.w.r.degoede@hhs.nl>
Signed-off-by: default avatarMauro Carvalho Chehab <mchehab@redhat.com>
parent 187565c4
Loading
Loading
Loading
Loading
+63 −68
Original line number Diff line number Diff line
@@ -60,6 +60,12 @@ static struct device_attribute video_device_attrs[] = {
	__ATTR_NULL
};

/*
 *	Active devices
 */
static struct video_device *video_device[VIDEO_NUM_DEVICES];
static DEFINE_MUTEX(videodev_lock);

struct video_device *video_device_alloc(void)
{
	return kzalloc(sizeof(struct video_device), GFP_KERNEL);
@@ -79,11 +85,41 @@ void video_device_release_empty(struct video_device *vfd)
}
EXPORT_SYMBOL(video_device_release_empty);

/* Called when the last user of the character device is gone. */
static void v4l2_chardev_release(struct kobject *kobj)
{
	struct video_device *vfd = container_of(kobj, struct video_device, cdev.kobj);

	mutex_lock(&videodev_lock);
	if (video_device[vfd->minor] != vfd)
		panic("videodev: bad release");

	/* Free up this device for reuse */
	video_device[vfd->minor] = NULL;
	mutex_unlock(&videodev_lock);

	/* Release the character device */
	vfd->cdev_release(kobj);
	/* Release video_device and perform other
	   cleanups as needed. */
	if (vfd->release)
		vfd->release(vfd);
}

/* The new kobj_type for the character device */
static struct kobj_type v4l2_ktype_cdev_default = {
	.release = v4l2_chardev_release,
};

static void video_release(struct device *cd)
{
	struct video_device *vfd = container_of(cd, struct video_device, dev);

	vfd->release(vfd);
	/* It's now safe to delete the char device.
	   This will either trigger the v4l2_chardev_release immediately (if
	   the refcount goes to 0) or later when the last user of the
	   character device closes it. */
	cdev_del(&vfd->cdev);
}

static struct class video_class = {
@@ -92,56 +128,12 @@ static struct class video_class = {
	.dev_release = video_release,
};

/*
 *	Active devices
 */

static struct video_device *video_device[VIDEO_NUM_DEVICES];
static DEFINE_MUTEX(videodev_lock);

struct video_device *video_devdata(struct file *file)
{
	return video_device[iminor(file->f_path.dentry->d_inode)];
}
EXPORT_SYMBOL(video_devdata);

/*
 *	Open a video device - FIXME: Obsoleted
 */
static int video_open(struct inode *inode, struct file *file)
{
	unsigned int minor = iminor(inode);
	int err = 0;
	struct video_device *vfl;
	const struct file_operations *old_fops;

	if (minor >= VIDEO_NUM_DEVICES)
		return -ENODEV;
	mutex_lock(&videodev_lock);
	vfl = video_device[minor];
	if (vfl == NULL) {
		mutex_unlock(&videodev_lock);
		request_module("char-major-%d-%d", VIDEO_MAJOR, minor);
		mutex_lock(&videodev_lock);
		vfl = video_device[minor];
		if (vfl == NULL) {
			mutex_unlock(&videodev_lock);
			return -ENODEV;
		}
	}
	old_fops = file->f_op;
	file->f_op = fops_get(vfl->fops);
	if (file->f_op->open)
		err = file->f_op->open(inode, file);
	if (err) {
		fops_put(file->f_op);
		file->f_op = fops_get(old_fops);
	}
	fops_put(old_fops);
	mutex_unlock(&videodev_lock);
	return err;
}

/**
 * get_index - assign stream number based on parent device
 * @vdev: video_device to assign index number to, vdev->dev should be assigned
@@ -261,6 +253,9 @@ int video_register_device_index(struct video_device *vfd, int type, int nr,
		return -EINVAL;
	}

	/* Initialize the character device */
	cdev_init(&vfd->cdev, vfd->fops);
	vfd->cdev.owner = vfd->fops->owner;
	/* pick a minor number */
	mutex_lock(&videodev_lock);
	if (nr >= 0 && nr < end-base) {
@@ -294,6 +289,11 @@ int video_register_device_index(struct video_device *vfd, int type, int nr,
		goto fail_minor;
	}

	ret = cdev_add(&vfd->cdev, MKDEV(VIDEO_MAJOR, vfd->minor), 1);
	if (ret < 0) {
		printk(KERN_ERR "%s: cdev_add failed\n", __func__);
		goto fail_minor;
	}
	/* sysfs class */
	memset(&vfd->dev, 0, sizeof(vfd->dev));
	/* The memset above cleared the device's drvdata, so
@@ -307,11 +307,17 @@ int video_register_device_index(struct video_device *vfd, int type, int nr,
	ret = device_register(&vfd->dev);
	if (ret < 0) {
		printk(KERN_ERR "%s: device_register failed\n", __func__);
		goto fail_minor;
		goto del_cdev;
	}

	/* Remember the cdev's release function */
	vfd->cdev_release = vfd->cdev.kobj.ktype->release;
	/* Install our own */
	vfd->cdev.kobj.ktype = &v4l2_ktype_cdev_default;
	return 0;

del_cdev:
	cdev_del(&vfd->cdev);

fail_minor:
	mutex_lock(&videodev_lock);
	video_device[vfd->minor] = NULL;
@@ -331,42 +337,29 @@ EXPORT_SYMBOL(video_register_device_index);

void video_unregister_device(struct video_device *vfd)
{
	mutex_lock(&videodev_lock);
	if (video_device[vfd->minor] != vfd)
		panic("videodev: bad unregister");

	video_device[vfd->minor] = NULL;
	device_unregister(&vfd->dev);
	mutex_unlock(&videodev_lock);
}
EXPORT_SYMBOL(video_unregister_device);

/*
 * Video fs operations
 */
static const struct file_operations video_fops = {
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.open		= video_open,
};

/*
 *	Initialise video for linux
 */

static int __init videodev_init(void)
{
	dev_t dev = MKDEV(VIDEO_MAJOR, 0);
	int ret;

	printk(KERN_INFO "Linux video capture interface: v2.00\n");
	if (register_chrdev(VIDEO_MAJOR, VIDEO_NAME, &video_fops)) {
		printk(KERN_WARNING "video_dev: unable to get major %d\n", VIDEO_MAJOR);
		return -EIO;
	ret = register_chrdev_region(dev, VIDEO_NUM_DEVICES, VIDEO_NAME);
	if (ret < 0) {
		printk(KERN_WARNING "videodev: unable to get major %d\n",
				VIDEO_MAJOR);
		return ret;
	}

	ret = class_register(&video_class);
	if (ret < 0) {
		unregister_chrdev(VIDEO_MAJOR, VIDEO_NAME);
		unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
		printk(KERN_WARNING "video_dev: class_register failed\n");
		return -EIO;
	}
@@ -376,8 +369,10 @@ static int __init videodev_init(void)

static void __exit videodev_exit(void)
{
	dev_t dev = MKDEV(VIDEO_MAJOR, 0);

	class_unregister(&video_class);
	unregister_chrdev(VIDEO_MAJOR, VIDEO_NAME);
	unregister_chrdev_region(dev, VIDEO_NUM_DEVICES);
}

module_init(videodev_init)
+3 −0
Original line number Diff line number Diff line
@@ -12,6 +12,7 @@
#include <linux/poll.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/mutex.h>
#include <linux/videodev2.h>

@@ -47,6 +48,8 @@ struct video_device

	/* sysfs */
	struct device dev;		/* v4l device */
	struct cdev cdev;		/* character device */
	void (*cdev_release)(struct kobject *kobj);
	struct device *parent;		/* device parent */

	/* device info */