xref: /OK3568_Linux_fs/kernel/drivers/media/mc/mc-devnode.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0-only
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun  * Media device node
4*4882a593Smuzhiyun  *
5*4882a593Smuzhiyun  * Copyright (C) 2010 Nokia Corporation
6*4882a593Smuzhiyun  *
7*4882a593Smuzhiyun  * Based on drivers/media/video/v4l2_dev.c code authored by
8*4882a593Smuzhiyun  *	Mauro Carvalho Chehab <mchehab@kernel.org> (version 2)
9*4882a593Smuzhiyun  *	Alan Cox, <alan@lxorguk.ukuu.org.uk> (version 1)
10*4882a593Smuzhiyun  *
11*4882a593Smuzhiyun  * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
12*4882a593Smuzhiyun  *	     Sakari Ailus <sakari.ailus@iki.fi>
13*4882a593Smuzhiyun  *
14*4882a593Smuzhiyun  * --
15*4882a593Smuzhiyun  *
16*4882a593Smuzhiyun  * Generic media device node infrastructure to register and unregister
17*4882a593Smuzhiyun  * character devices using a dynamic major number and proper reference
18*4882a593Smuzhiyun  * counting.
19*4882a593Smuzhiyun  */
20*4882a593Smuzhiyun 
21*4882a593Smuzhiyun #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
22*4882a593Smuzhiyun 
23*4882a593Smuzhiyun #include <linux/errno.h>
24*4882a593Smuzhiyun #include <linux/init.h>
25*4882a593Smuzhiyun #include <linux/module.h>
26*4882a593Smuzhiyun #include <linux/kernel.h>
27*4882a593Smuzhiyun #include <linux/kmod.h>
28*4882a593Smuzhiyun #include <linux/slab.h>
29*4882a593Smuzhiyun #include <linux/mm.h>
30*4882a593Smuzhiyun #include <linux/string.h>
31*4882a593Smuzhiyun #include <linux/types.h>
32*4882a593Smuzhiyun #include <linux/uaccess.h>
33*4882a593Smuzhiyun 
34*4882a593Smuzhiyun #include <media/media-devnode.h>
35*4882a593Smuzhiyun #include <media/media-device.h>
36*4882a593Smuzhiyun 
37*4882a593Smuzhiyun #define MEDIA_NUM_DEVICES	256
38*4882a593Smuzhiyun #define MEDIA_NAME		"media"
39*4882a593Smuzhiyun 
40*4882a593Smuzhiyun static dev_t media_dev_t;
41*4882a593Smuzhiyun 
42*4882a593Smuzhiyun /*
43*4882a593Smuzhiyun  *	Active devices
44*4882a593Smuzhiyun  */
45*4882a593Smuzhiyun static DEFINE_MUTEX(media_devnode_lock);
46*4882a593Smuzhiyun static DECLARE_BITMAP(media_devnode_nums, MEDIA_NUM_DEVICES);
47*4882a593Smuzhiyun 
48*4882a593Smuzhiyun /* Called when the last user of the media device exits. */
media_devnode_release(struct device * cd)49*4882a593Smuzhiyun static void media_devnode_release(struct device *cd)
50*4882a593Smuzhiyun {
51*4882a593Smuzhiyun 	struct media_devnode *devnode = to_media_devnode(cd);
52*4882a593Smuzhiyun 
53*4882a593Smuzhiyun 	mutex_lock(&media_devnode_lock);
54*4882a593Smuzhiyun 	/* Mark device node number as free */
55*4882a593Smuzhiyun 	clear_bit(devnode->minor, media_devnode_nums);
56*4882a593Smuzhiyun 	mutex_unlock(&media_devnode_lock);
57*4882a593Smuzhiyun 
58*4882a593Smuzhiyun 	/* Release media_devnode and perform other cleanups as needed. */
59*4882a593Smuzhiyun 	if (devnode->release)
60*4882a593Smuzhiyun 		devnode->release(devnode);
61*4882a593Smuzhiyun 
62*4882a593Smuzhiyun 	kfree(devnode);
63*4882a593Smuzhiyun 	pr_debug("%s: Media Devnode Deallocated\n", __func__);
64*4882a593Smuzhiyun }
65*4882a593Smuzhiyun 
66*4882a593Smuzhiyun static struct bus_type media_bus_type = {
67*4882a593Smuzhiyun 	.name = MEDIA_NAME,
68*4882a593Smuzhiyun };
69*4882a593Smuzhiyun 
media_read(struct file * filp,char __user * buf,size_t sz,loff_t * off)70*4882a593Smuzhiyun static ssize_t media_read(struct file *filp, char __user *buf,
71*4882a593Smuzhiyun 		size_t sz, loff_t *off)
72*4882a593Smuzhiyun {
73*4882a593Smuzhiyun 	struct media_devnode *devnode = media_devnode_data(filp);
74*4882a593Smuzhiyun 
75*4882a593Smuzhiyun 	if (!devnode->fops->read)
76*4882a593Smuzhiyun 		return -EINVAL;
77*4882a593Smuzhiyun 	if (!media_devnode_is_registered(devnode))
78*4882a593Smuzhiyun 		return -EIO;
79*4882a593Smuzhiyun 	return devnode->fops->read(filp, buf, sz, off);
80*4882a593Smuzhiyun }
81*4882a593Smuzhiyun 
media_write(struct file * filp,const char __user * buf,size_t sz,loff_t * off)82*4882a593Smuzhiyun static ssize_t media_write(struct file *filp, const char __user *buf,
83*4882a593Smuzhiyun 		size_t sz, loff_t *off)
84*4882a593Smuzhiyun {
85*4882a593Smuzhiyun 	struct media_devnode *devnode = media_devnode_data(filp);
86*4882a593Smuzhiyun 
87*4882a593Smuzhiyun 	if (!devnode->fops->write)
88*4882a593Smuzhiyun 		return -EINVAL;
89*4882a593Smuzhiyun 	if (!media_devnode_is_registered(devnode))
90*4882a593Smuzhiyun 		return -EIO;
91*4882a593Smuzhiyun 	return devnode->fops->write(filp, buf, sz, off);
92*4882a593Smuzhiyun }
93*4882a593Smuzhiyun 
media_poll(struct file * filp,struct poll_table_struct * poll)94*4882a593Smuzhiyun static __poll_t media_poll(struct file *filp,
95*4882a593Smuzhiyun 			       struct poll_table_struct *poll)
96*4882a593Smuzhiyun {
97*4882a593Smuzhiyun 	struct media_devnode *devnode = media_devnode_data(filp);
98*4882a593Smuzhiyun 
99*4882a593Smuzhiyun 	if (!media_devnode_is_registered(devnode))
100*4882a593Smuzhiyun 		return EPOLLERR | EPOLLHUP;
101*4882a593Smuzhiyun 	if (!devnode->fops->poll)
102*4882a593Smuzhiyun 		return DEFAULT_POLLMASK;
103*4882a593Smuzhiyun 	return devnode->fops->poll(filp, poll);
104*4882a593Smuzhiyun }
105*4882a593Smuzhiyun 
106*4882a593Smuzhiyun static long
__media_ioctl(struct file * filp,unsigned int cmd,unsigned long arg,long (* ioctl_func)(struct file * filp,unsigned int cmd,unsigned long arg))107*4882a593Smuzhiyun __media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg,
108*4882a593Smuzhiyun 	      long (*ioctl_func)(struct file *filp, unsigned int cmd,
109*4882a593Smuzhiyun 				 unsigned long arg))
110*4882a593Smuzhiyun {
111*4882a593Smuzhiyun 	struct media_devnode *devnode = media_devnode_data(filp);
112*4882a593Smuzhiyun 
113*4882a593Smuzhiyun 	if (!ioctl_func)
114*4882a593Smuzhiyun 		return -ENOTTY;
115*4882a593Smuzhiyun 
116*4882a593Smuzhiyun 	if (!media_devnode_is_registered(devnode))
117*4882a593Smuzhiyun 		return -EIO;
118*4882a593Smuzhiyun 
119*4882a593Smuzhiyun 	return ioctl_func(filp, cmd, arg);
120*4882a593Smuzhiyun }
121*4882a593Smuzhiyun 
media_ioctl(struct file * filp,unsigned int cmd,unsigned long arg)122*4882a593Smuzhiyun static long media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
123*4882a593Smuzhiyun {
124*4882a593Smuzhiyun 	struct media_devnode *devnode = media_devnode_data(filp);
125*4882a593Smuzhiyun 
126*4882a593Smuzhiyun 	return __media_ioctl(filp, cmd, arg, devnode->fops->ioctl);
127*4882a593Smuzhiyun }
128*4882a593Smuzhiyun 
129*4882a593Smuzhiyun #ifdef CONFIG_COMPAT
130*4882a593Smuzhiyun 
media_compat_ioctl(struct file * filp,unsigned int cmd,unsigned long arg)131*4882a593Smuzhiyun static long media_compat_ioctl(struct file *filp, unsigned int cmd,
132*4882a593Smuzhiyun 			       unsigned long arg)
133*4882a593Smuzhiyun {
134*4882a593Smuzhiyun 	struct media_devnode *devnode = media_devnode_data(filp);
135*4882a593Smuzhiyun 
136*4882a593Smuzhiyun 	return __media_ioctl(filp, cmd, arg, devnode->fops->compat_ioctl);
137*4882a593Smuzhiyun }
138*4882a593Smuzhiyun 
139*4882a593Smuzhiyun #endif /* CONFIG_COMPAT */
140*4882a593Smuzhiyun 
141*4882a593Smuzhiyun /* Override for the open function */
media_open(struct inode * inode,struct file * filp)142*4882a593Smuzhiyun static int media_open(struct inode *inode, struct file *filp)
143*4882a593Smuzhiyun {
144*4882a593Smuzhiyun 	struct media_devnode *devnode;
145*4882a593Smuzhiyun 	int ret;
146*4882a593Smuzhiyun 
147*4882a593Smuzhiyun 	/* Check if the media device is available. This needs to be done with
148*4882a593Smuzhiyun 	 * the media_devnode_lock held to prevent an open/unregister race:
149*4882a593Smuzhiyun 	 * without the lock, the device could be unregistered and freed between
150*4882a593Smuzhiyun 	 * the media_devnode_is_registered() and get_device() calls, leading to
151*4882a593Smuzhiyun 	 * a crash.
152*4882a593Smuzhiyun 	 */
153*4882a593Smuzhiyun 	mutex_lock(&media_devnode_lock);
154*4882a593Smuzhiyun 	devnode = container_of(inode->i_cdev, struct media_devnode, cdev);
155*4882a593Smuzhiyun 	/* return ENXIO if the media device has been removed
156*4882a593Smuzhiyun 	   already or if it is not registered anymore. */
157*4882a593Smuzhiyun 	if (!media_devnode_is_registered(devnode)) {
158*4882a593Smuzhiyun 		mutex_unlock(&media_devnode_lock);
159*4882a593Smuzhiyun 		return -ENXIO;
160*4882a593Smuzhiyun 	}
161*4882a593Smuzhiyun 	/* and increase the device refcount */
162*4882a593Smuzhiyun 	get_device(&devnode->dev);
163*4882a593Smuzhiyun 	mutex_unlock(&media_devnode_lock);
164*4882a593Smuzhiyun 
165*4882a593Smuzhiyun 	filp->private_data = devnode;
166*4882a593Smuzhiyun 
167*4882a593Smuzhiyun 	if (devnode->fops->open) {
168*4882a593Smuzhiyun 		ret = devnode->fops->open(filp);
169*4882a593Smuzhiyun 		if (ret) {
170*4882a593Smuzhiyun 			put_device(&devnode->dev);
171*4882a593Smuzhiyun 			filp->private_data = NULL;
172*4882a593Smuzhiyun 			return ret;
173*4882a593Smuzhiyun 		}
174*4882a593Smuzhiyun 	}
175*4882a593Smuzhiyun 
176*4882a593Smuzhiyun 	return 0;
177*4882a593Smuzhiyun }
178*4882a593Smuzhiyun 
179*4882a593Smuzhiyun /* Override for the release function */
media_release(struct inode * inode,struct file * filp)180*4882a593Smuzhiyun static int media_release(struct inode *inode, struct file *filp)
181*4882a593Smuzhiyun {
182*4882a593Smuzhiyun 	struct media_devnode *devnode = media_devnode_data(filp);
183*4882a593Smuzhiyun 
184*4882a593Smuzhiyun 	if (devnode->fops->release)
185*4882a593Smuzhiyun 		devnode->fops->release(filp);
186*4882a593Smuzhiyun 
187*4882a593Smuzhiyun 	filp->private_data = NULL;
188*4882a593Smuzhiyun 
189*4882a593Smuzhiyun 	/* decrease the refcount unconditionally since the release()
190*4882a593Smuzhiyun 	   return value is ignored. */
191*4882a593Smuzhiyun 	put_device(&devnode->dev);
192*4882a593Smuzhiyun 
193*4882a593Smuzhiyun 	pr_debug("%s: Media Release\n", __func__);
194*4882a593Smuzhiyun 	return 0;
195*4882a593Smuzhiyun }
196*4882a593Smuzhiyun 
197*4882a593Smuzhiyun static const struct file_operations media_devnode_fops = {
198*4882a593Smuzhiyun 	.owner = THIS_MODULE,
199*4882a593Smuzhiyun 	.read = media_read,
200*4882a593Smuzhiyun 	.write = media_write,
201*4882a593Smuzhiyun 	.open = media_open,
202*4882a593Smuzhiyun 	.unlocked_ioctl = media_ioctl,
203*4882a593Smuzhiyun #ifdef CONFIG_COMPAT
204*4882a593Smuzhiyun 	.compat_ioctl = media_compat_ioctl,
205*4882a593Smuzhiyun #endif /* CONFIG_COMPAT */
206*4882a593Smuzhiyun 	.release = media_release,
207*4882a593Smuzhiyun 	.poll = media_poll,
208*4882a593Smuzhiyun 	.llseek = no_llseek,
209*4882a593Smuzhiyun };
210*4882a593Smuzhiyun 
media_devnode_register(struct media_device * mdev,struct media_devnode * devnode,struct module * owner)211*4882a593Smuzhiyun int __must_check media_devnode_register(struct media_device *mdev,
212*4882a593Smuzhiyun 					struct media_devnode *devnode,
213*4882a593Smuzhiyun 					struct module *owner)
214*4882a593Smuzhiyun {
215*4882a593Smuzhiyun 	int minor;
216*4882a593Smuzhiyun 	int ret;
217*4882a593Smuzhiyun 
218*4882a593Smuzhiyun 	/* Part 1: Find a free minor number */
219*4882a593Smuzhiyun 	mutex_lock(&media_devnode_lock);
220*4882a593Smuzhiyun 	minor = find_next_zero_bit(media_devnode_nums, MEDIA_NUM_DEVICES, 0);
221*4882a593Smuzhiyun 	if (minor == MEDIA_NUM_DEVICES) {
222*4882a593Smuzhiyun 		mutex_unlock(&media_devnode_lock);
223*4882a593Smuzhiyun 		pr_err("could not get a free minor\n");
224*4882a593Smuzhiyun 		kfree(devnode);
225*4882a593Smuzhiyun 		return -ENFILE;
226*4882a593Smuzhiyun 	}
227*4882a593Smuzhiyun 
228*4882a593Smuzhiyun 	set_bit(minor, media_devnode_nums);
229*4882a593Smuzhiyun 	mutex_unlock(&media_devnode_lock);
230*4882a593Smuzhiyun 
231*4882a593Smuzhiyun 	devnode->minor = minor;
232*4882a593Smuzhiyun 	devnode->media_dev = mdev;
233*4882a593Smuzhiyun 
234*4882a593Smuzhiyun 	/* Part 1: Initialize dev now to use dev.kobj for cdev.kobj.parent */
235*4882a593Smuzhiyun 	devnode->dev.bus = &media_bus_type;
236*4882a593Smuzhiyun 	devnode->dev.devt = MKDEV(MAJOR(media_dev_t), devnode->minor);
237*4882a593Smuzhiyun 	devnode->dev.release = media_devnode_release;
238*4882a593Smuzhiyun 	if (devnode->parent)
239*4882a593Smuzhiyun 		devnode->dev.parent = devnode->parent;
240*4882a593Smuzhiyun 	dev_set_name(&devnode->dev, "media%d", devnode->minor);
241*4882a593Smuzhiyun 	device_initialize(&devnode->dev);
242*4882a593Smuzhiyun 
243*4882a593Smuzhiyun 	/* Part 2: Initialize the character device */
244*4882a593Smuzhiyun 	cdev_init(&devnode->cdev, &media_devnode_fops);
245*4882a593Smuzhiyun 	devnode->cdev.owner = owner;
246*4882a593Smuzhiyun 	kobject_set_name(&devnode->cdev.kobj, "media%d", devnode->minor);
247*4882a593Smuzhiyun 
248*4882a593Smuzhiyun 	/* Part 3: Add the media and char device */
249*4882a593Smuzhiyun 	ret = cdev_device_add(&devnode->cdev, &devnode->dev);
250*4882a593Smuzhiyun 	if (ret < 0) {
251*4882a593Smuzhiyun 		pr_err("%s: cdev_device_add failed\n", __func__);
252*4882a593Smuzhiyun 		goto cdev_add_error;
253*4882a593Smuzhiyun 	}
254*4882a593Smuzhiyun 
255*4882a593Smuzhiyun 	/* Part 4: Activate this minor. The char device can now be used. */
256*4882a593Smuzhiyun 	set_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);
257*4882a593Smuzhiyun 
258*4882a593Smuzhiyun 	return 0;
259*4882a593Smuzhiyun 
260*4882a593Smuzhiyun cdev_add_error:
261*4882a593Smuzhiyun 	mutex_lock(&media_devnode_lock);
262*4882a593Smuzhiyun 	clear_bit(devnode->minor, media_devnode_nums);
263*4882a593Smuzhiyun 	devnode->media_dev = NULL;
264*4882a593Smuzhiyun 	mutex_unlock(&media_devnode_lock);
265*4882a593Smuzhiyun 
266*4882a593Smuzhiyun 	put_device(&devnode->dev);
267*4882a593Smuzhiyun 	return ret;
268*4882a593Smuzhiyun }
269*4882a593Smuzhiyun 
media_devnode_unregister_prepare(struct media_devnode * devnode)270*4882a593Smuzhiyun void media_devnode_unregister_prepare(struct media_devnode *devnode)
271*4882a593Smuzhiyun {
272*4882a593Smuzhiyun 	/* Check if devnode was ever registered at all */
273*4882a593Smuzhiyun 	if (!media_devnode_is_registered(devnode))
274*4882a593Smuzhiyun 		return;
275*4882a593Smuzhiyun 
276*4882a593Smuzhiyun 	mutex_lock(&media_devnode_lock);
277*4882a593Smuzhiyun 	clear_bit(MEDIA_FLAG_REGISTERED, &devnode->flags);
278*4882a593Smuzhiyun 	mutex_unlock(&media_devnode_lock);
279*4882a593Smuzhiyun }
280*4882a593Smuzhiyun 
media_devnode_unregister(struct media_devnode * devnode)281*4882a593Smuzhiyun void media_devnode_unregister(struct media_devnode *devnode)
282*4882a593Smuzhiyun {
283*4882a593Smuzhiyun 	mutex_lock(&media_devnode_lock);
284*4882a593Smuzhiyun 	/* Delete the cdev on this minor as well */
285*4882a593Smuzhiyun 	cdev_device_del(&devnode->cdev, &devnode->dev);
286*4882a593Smuzhiyun 	devnode->media_dev = NULL;
287*4882a593Smuzhiyun 	mutex_unlock(&media_devnode_lock);
288*4882a593Smuzhiyun 
289*4882a593Smuzhiyun 	put_device(&devnode->dev);
290*4882a593Smuzhiyun }
291*4882a593Smuzhiyun 
292*4882a593Smuzhiyun /*
293*4882a593Smuzhiyun  *	Initialise media for linux
294*4882a593Smuzhiyun  */
media_devnode_init(void)295*4882a593Smuzhiyun static int __init media_devnode_init(void)
296*4882a593Smuzhiyun {
297*4882a593Smuzhiyun 	int ret;
298*4882a593Smuzhiyun 
299*4882a593Smuzhiyun 	pr_info("Linux media interface: v0.10\n");
300*4882a593Smuzhiyun 	ret = alloc_chrdev_region(&media_dev_t, 0, MEDIA_NUM_DEVICES,
301*4882a593Smuzhiyun 				  MEDIA_NAME);
302*4882a593Smuzhiyun 	if (ret < 0) {
303*4882a593Smuzhiyun 		pr_warn("unable to allocate major\n");
304*4882a593Smuzhiyun 		return ret;
305*4882a593Smuzhiyun 	}
306*4882a593Smuzhiyun 
307*4882a593Smuzhiyun 	ret = bus_register(&media_bus_type);
308*4882a593Smuzhiyun 	if (ret < 0) {
309*4882a593Smuzhiyun 		unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES);
310*4882a593Smuzhiyun 		pr_warn("bus_register failed\n");
311*4882a593Smuzhiyun 		return -EIO;
312*4882a593Smuzhiyun 	}
313*4882a593Smuzhiyun 
314*4882a593Smuzhiyun 	return 0;
315*4882a593Smuzhiyun }
316*4882a593Smuzhiyun 
media_devnode_exit(void)317*4882a593Smuzhiyun static void __exit media_devnode_exit(void)
318*4882a593Smuzhiyun {
319*4882a593Smuzhiyun 	bus_unregister(&media_bus_type);
320*4882a593Smuzhiyun 	unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES);
321*4882a593Smuzhiyun }
322*4882a593Smuzhiyun 
323*4882a593Smuzhiyun subsys_initcall(media_devnode_init);
324*4882a593Smuzhiyun module_exit(media_devnode_exit)
325*4882a593Smuzhiyun 
326*4882a593Smuzhiyun MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
327*4882a593Smuzhiyun MODULE_DESCRIPTION("Device node registration for media drivers");
328*4882a593Smuzhiyun MODULE_LICENSE("GPL");
329