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: Mike Isely <isely@isely.net>
Tested-by: Laurent Pinchart <laurent.pinchart@skynet.be>
Signed-off-by: Hans Verkuil <hverkuil@xs4all.nl>
Acked-by: David Ellingsworth <david@identd.dyndns.org>
Reviewed-by: Hans de Goede <j.w.r.degoede@hhs.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
diff --git a/drivers/media/video/v4l2-dev.c b/drivers/media/video/v4l2-dev.c
index 99f7ee4..02b9cc7 100644
--- a/drivers/media/video/v4l2-dev.c
+++ b/drivers/media/video/v4l2-dev.c
@@ -60,6 +60,12 @@
 	__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 @@
 }
 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 @@
 	.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 @@
 		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 @@
 		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 @@
 	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 @@
 
 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 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)