diff options
author | Laurent Pinchart <laurent.pinchart@ideasonboard.com> | 2009-12-09 06:39:56 -0500 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2011-03-22 03:52:22 -0400 |
commit | cf4b9211b5680cd9ca004232e517fb7ec5bf5316 (patch) | |
tree | 5bb830aa4e362d4d9b5774ebfaca1eb79e7db22a /drivers/media | |
parent | 02adb1cc765b8c29dc83c6602bda19003cce62f1 (diff) |
[media] media: Media device node support
The media_devnode structure provides support for registering and
unregistering character devices using a dynamic major number. Reference
counting is handled internally, making device drivers easier to write
without having to solve the open/disconnect race condition issue over
and over again.
The code is based on video/v4l2-dev.c.
[mchehab@redhat.com: Remove linux/smp_lock.h include to not break compilation on bisect]
Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Acked-by: Hans Verkuil <hverkuil@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers/media')
-rw-r--r-- | drivers/media/Kconfig | 13 | ||||
-rw-r--r-- | drivers/media/Makefile | 6 | ||||
-rw-r--r-- | drivers/media/media-devnode.c | 320 |
3 files changed, 339 insertions, 0 deletions
diff --git a/drivers/media/Kconfig b/drivers/media/Kconfig index 81b3ba83cc65..2466f2b2ecfa 100644 --- a/drivers/media/Kconfig +++ b/drivers/media/Kconfig | |||
@@ -14,6 +14,19 @@ if MEDIA_SUPPORT | |||
14 | comment "Multimedia core support" | 14 | comment "Multimedia core support" |
15 | 15 | ||
16 | # | 16 | # |
17 | # Media controller | ||
18 | # | ||
19 | |||
20 | config MEDIA_CONTROLLER | ||
21 | bool "Media Controller API (EXPERIMENTAL)" | ||
22 | depends on EXPERIMENTAL | ||
23 | ---help--- | ||
24 | Enable the media controller API used to query media devices internal | ||
25 | topology and configure it dynamically. | ||
26 | |||
27 | This API is mostly used by camera interfaces in embedded platforms. | ||
28 | |||
29 | # | ||
17 | # V4L core and enabled API's | 30 | # V4L core and enabled API's |
18 | # | 31 | # |
19 | 32 | ||
diff --git a/drivers/media/Makefile b/drivers/media/Makefile index b603ea645ede..d56349f6103f 100644 --- a/drivers/media/Makefile +++ b/drivers/media/Makefile | |||
@@ -2,6 +2,12 @@ | |||
2 | # Makefile for the kernel multimedia device drivers. | 2 | # Makefile for the kernel multimedia device drivers. |
3 | # | 3 | # |
4 | 4 | ||
5 | media-objs := media-devnode.o | ||
6 | |||
7 | ifeq ($(CONFIG_MEDIA_CONTROLLER),y) | ||
8 | obj-$(CONFIG_MEDIA_SUPPORT) += media.o | ||
9 | endif | ||
10 | |||
5 | obj-y += common/ rc/ video/ | 11 | obj-y += common/ rc/ video/ |
6 | 12 | ||
7 | obj-$(CONFIG_VIDEO_DEV) += radio/ | 13 | obj-$(CONFIG_VIDEO_DEV) += radio/ |
diff --git a/drivers/media/media-devnode.c b/drivers/media/media-devnode.c new file mode 100644 index 000000000000..af5263c6625a --- /dev/null +++ b/drivers/media/media-devnode.c | |||
@@ -0,0 +1,320 @@ | |||
1 | /* | ||
2 | * Media device node | ||
3 | * | ||
4 | * Copyright (C) 2010 Nokia Corporation | ||
5 | * | ||
6 | * Based on drivers/media/video/v4l2_dev.c code authored by | ||
7 | * Mauro Carvalho Chehab <mchehab@infradead.org> (version 2) | ||
8 | * Alan Cox, <alan@lxorguk.ukuu.org.uk> (version 1) | ||
9 | * | ||
10 | * Contacts: Laurent Pinchart <laurent.pinchart@ideasonboard.com> | ||
11 | * Sakari Ailus <sakari.ailus@iki.fi> | ||
12 | * | ||
13 | * This program is free software; you can redistribute it and/or modify | ||
14 | * it under the terms of the GNU General Public License version 2 as | ||
15 | * published by the Free Software Foundation. | ||
16 | * | ||
17 | * This program is distributed in the hope that it will be useful, | ||
18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | * GNU General Public License for more details. | ||
21 | * | ||
22 | * You should have received a copy of the GNU General Public License | ||
23 | * along with this program; if not, write to the Free Software | ||
24 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
25 | * | ||
26 | * -- | ||
27 | * | ||
28 | * Generic media device node infrastructure to register and unregister | ||
29 | * character devices using a dynamic major number and proper reference | ||
30 | * counting. | ||
31 | */ | ||
32 | |||
33 | #include <linux/errno.h> | ||
34 | #include <linux/init.h> | ||
35 | #include <linux/module.h> | ||
36 | #include <linux/kernel.h> | ||
37 | #include <linux/kmod.h> | ||
38 | #include <linux/slab.h> | ||
39 | #include <linux/mm.h> | ||
40 | #include <linux/string.h> | ||
41 | #include <linux/types.h> | ||
42 | #include <linux/uaccess.h> | ||
43 | #include <asm/system.h> | ||
44 | |||
45 | #include <media/media-devnode.h> | ||
46 | |||
47 | #define MEDIA_NUM_DEVICES 256 | ||
48 | #define MEDIA_NAME "media" | ||
49 | |||
50 | static dev_t media_dev_t; | ||
51 | |||
52 | /* | ||
53 | * Active devices | ||
54 | */ | ||
55 | static DEFINE_MUTEX(media_devnode_lock); | ||
56 | static DECLARE_BITMAP(media_devnode_nums, MEDIA_NUM_DEVICES); | ||
57 | |||
58 | /* Called when the last user of the media device exits. */ | ||
59 | static void media_devnode_release(struct device *cd) | ||
60 | { | ||
61 | struct media_devnode *mdev = to_media_devnode(cd); | ||
62 | |||
63 | mutex_lock(&media_devnode_lock); | ||
64 | |||
65 | /* Delete the cdev on this minor as well */ | ||
66 | cdev_del(&mdev->cdev); | ||
67 | |||
68 | /* Mark device node number as free */ | ||
69 | clear_bit(mdev->minor, media_devnode_nums); | ||
70 | |||
71 | mutex_unlock(&media_devnode_lock); | ||
72 | |||
73 | /* Release media_devnode and perform other cleanups as needed. */ | ||
74 | if (mdev->release) | ||
75 | mdev->release(mdev); | ||
76 | } | ||
77 | |||
78 | static struct bus_type media_bus_type = { | ||
79 | .name = MEDIA_NAME, | ||
80 | }; | ||
81 | |||
82 | static ssize_t media_read(struct file *filp, char __user *buf, | ||
83 | size_t sz, loff_t *off) | ||
84 | { | ||
85 | struct media_devnode *mdev = media_devnode_data(filp); | ||
86 | |||
87 | if (!mdev->fops->read) | ||
88 | return -EINVAL; | ||
89 | if (!media_devnode_is_registered(mdev)) | ||
90 | return -EIO; | ||
91 | return mdev->fops->read(filp, buf, sz, off); | ||
92 | } | ||
93 | |||
94 | static ssize_t media_write(struct file *filp, const char __user *buf, | ||
95 | size_t sz, loff_t *off) | ||
96 | { | ||
97 | struct media_devnode *mdev = media_devnode_data(filp); | ||
98 | |||
99 | if (!mdev->fops->write) | ||
100 | return -EINVAL; | ||
101 | if (!media_devnode_is_registered(mdev)) | ||
102 | return -EIO; | ||
103 | return mdev->fops->write(filp, buf, sz, off); | ||
104 | } | ||
105 | |||
106 | static unsigned int media_poll(struct file *filp, | ||
107 | struct poll_table_struct *poll) | ||
108 | { | ||
109 | struct media_devnode *mdev = media_devnode_data(filp); | ||
110 | |||
111 | if (!media_devnode_is_registered(mdev)) | ||
112 | return POLLERR | POLLHUP; | ||
113 | if (!mdev->fops->poll) | ||
114 | return DEFAULT_POLLMASK; | ||
115 | return mdev->fops->poll(filp, poll); | ||
116 | } | ||
117 | |||
118 | static long media_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) | ||
119 | { | ||
120 | struct media_devnode *mdev = media_devnode_data(filp); | ||
121 | |||
122 | if (!mdev->fops->ioctl) | ||
123 | return -ENOTTY; | ||
124 | |||
125 | if (!media_devnode_is_registered(mdev)) | ||
126 | return -EIO; | ||
127 | |||
128 | return mdev->fops->ioctl(filp, cmd, arg); | ||
129 | } | ||
130 | |||
131 | /* Override for the open function */ | ||
132 | static int media_open(struct inode *inode, struct file *filp) | ||
133 | { | ||
134 | struct media_devnode *mdev; | ||
135 | int ret; | ||
136 | |||
137 | /* Check if the media device is available. This needs to be done with | ||
138 | * the media_devnode_lock held to prevent an open/unregister race: | ||
139 | * without the lock, the device could be unregistered and freed between | ||
140 | * the media_devnode_is_registered() and get_device() calls, leading to | ||
141 | * a crash. | ||
142 | */ | ||
143 | mutex_lock(&media_devnode_lock); | ||
144 | mdev = container_of(inode->i_cdev, struct media_devnode, cdev); | ||
145 | /* return ENXIO if the media device has been removed | ||
146 | already or if it is not registered anymore. */ | ||
147 | if (!media_devnode_is_registered(mdev)) { | ||
148 | mutex_unlock(&media_devnode_lock); | ||
149 | return -ENXIO; | ||
150 | } | ||
151 | /* and increase the device refcount */ | ||
152 | get_device(&mdev->dev); | ||
153 | mutex_unlock(&media_devnode_lock); | ||
154 | |||
155 | filp->private_data = mdev; | ||
156 | |||
157 | if (mdev->fops->open) { | ||
158 | ret = mdev->fops->open(filp); | ||
159 | if (ret) { | ||
160 | put_device(&mdev->dev); | ||
161 | return ret; | ||
162 | } | ||
163 | } | ||
164 | |||
165 | return 0; | ||
166 | } | ||
167 | |||
168 | /* Override for the release function */ | ||
169 | static int media_release(struct inode *inode, struct file *filp) | ||
170 | { | ||
171 | struct media_devnode *mdev = media_devnode_data(filp); | ||
172 | int ret = 0; | ||
173 | |||
174 | if (mdev->fops->release) | ||
175 | mdev->fops->release(filp); | ||
176 | |||
177 | /* decrease the refcount unconditionally since the release() | ||
178 | return value is ignored. */ | ||
179 | put_device(&mdev->dev); | ||
180 | filp->private_data = NULL; | ||
181 | return ret; | ||
182 | } | ||
183 | |||
184 | static const struct file_operations media_devnode_fops = { | ||
185 | .owner = THIS_MODULE, | ||
186 | .read = media_read, | ||
187 | .write = media_write, | ||
188 | .open = media_open, | ||
189 | .unlocked_ioctl = media_ioctl, | ||
190 | .release = media_release, | ||
191 | .poll = media_poll, | ||
192 | .llseek = no_llseek, | ||
193 | }; | ||
194 | |||
195 | /** | ||
196 | * media_devnode_register - register a media device node | ||
197 | * @mdev: media device node structure we want to register | ||
198 | * | ||
199 | * The registration code assigns minor numbers and registers the new device node | ||
200 | * with the kernel. An error is returned if no free minor number can be found, | ||
201 | * or if the registration of the device node fails. | ||
202 | * | ||
203 | * Zero is returned on success. | ||
204 | * | ||
205 | * Note that if the media_devnode_register call fails, the release() callback of | ||
206 | * the media_devnode structure is *not* called, so the caller is responsible for | ||
207 | * freeing any data. | ||
208 | */ | ||
209 | int __must_check media_devnode_register(struct media_devnode *mdev) | ||
210 | { | ||
211 | int minor; | ||
212 | int ret; | ||
213 | |||
214 | /* Part 1: Find a free minor number */ | ||
215 | mutex_lock(&media_devnode_lock); | ||
216 | minor = find_next_zero_bit(media_devnode_nums, 0, MEDIA_NUM_DEVICES); | ||
217 | if (minor == MEDIA_NUM_DEVICES) { | ||
218 | mutex_unlock(&media_devnode_lock); | ||
219 | printk(KERN_ERR "could not get a free minor\n"); | ||
220 | return -ENFILE; | ||
221 | } | ||
222 | |||
223 | set_bit(mdev->minor, media_devnode_nums); | ||
224 | mutex_unlock(&media_devnode_lock); | ||
225 | |||
226 | mdev->minor = minor; | ||
227 | |||
228 | /* Part 2: Initialize and register the character device */ | ||
229 | cdev_init(&mdev->cdev, &media_devnode_fops); | ||
230 | mdev->cdev.owner = mdev->fops->owner; | ||
231 | |||
232 | ret = cdev_add(&mdev->cdev, MKDEV(MAJOR(media_dev_t), mdev->minor), 1); | ||
233 | if (ret < 0) { | ||
234 | printk(KERN_ERR "%s: cdev_add failed\n", __func__); | ||
235 | goto error; | ||
236 | } | ||
237 | |||
238 | /* Part 3: Register the media device */ | ||
239 | mdev->dev.bus = &media_bus_type; | ||
240 | mdev->dev.devt = MKDEV(MAJOR(media_dev_t), mdev->minor); | ||
241 | mdev->dev.release = media_devnode_release; | ||
242 | if (mdev->parent) | ||
243 | mdev->dev.parent = mdev->parent; | ||
244 | dev_set_name(&mdev->dev, "media%d", mdev->minor); | ||
245 | ret = device_register(&mdev->dev); | ||
246 | if (ret < 0) { | ||
247 | printk(KERN_ERR "%s: device_register failed\n", __func__); | ||
248 | goto error; | ||
249 | } | ||
250 | |||
251 | /* Part 4: Activate this minor. The char device can now be used. */ | ||
252 | set_bit(MEDIA_FLAG_REGISTERED, &mdev->flags); | ||
253 | |||
254 | return 0; | ||
255 | |||
256 | error: | ||
257 | cdev_del(&mdev->cdev); | ||
258 | clear_bit(mdev->minor, media_devnode_nums); | ||
259 | return ret; | ||
260 | } | ||
261 | |||
262 | /** | ||
263 | * media_devnode_unregister - unregister a media device node | ||
264 | * @mdev: the device node to unregister | ||
265 | * | ||
266 | * This unregisters the passed device. Future open calls will be met with | ||
267 | * errors. | ||
268 | * | ||
269 | * This function can safely be called if the device node has never been | ||
270 | * registered or has already been unregistered. | ||
271 | */ | ||
272 | void media_devnode_unregister(struct media_devnode *mdev) | ||
273 | { | ||
274 | /* Check if mdev was ever registered at all */ | ||
275 | if (!media_devnode_is_registered(mdev)) | ||
276 | return; | ||
277 | |||
278 | mutex_lock(&media_devnode_lock); | ||
279 | clear_bit(MEDIA_FLAG_REGISTERED, &mdev->flags); | ||
280 | mutex_unlock(&media_devnode_lock); | ||
281 | device_unregister(&mdev->dev); | ||
282 | } | ||
283 | |||
284 | /* | ||
285 | * Initialise media for linux | ||
286 | */ | ||
287 | static int __init media_devnode_init(void) | ||
288 | { | ||
289 | int ret; | ||
290 | |||
291 | printk(KERN_INFO "Linux media interface: v0.10\n"); | ||
292 | ret = alloc_chrdev_region(&media_dev_t, 0, MEDIA_NUM_DEVICES, | ||
293 | MEDIA_NAME); | ||
294 | if (ret < 0) { | ||
295 | printk(KERN_WARNING "media: unable to allocate major\n"); | ||
296 | return ret; | ||
297 | } | ||
298 | |||
299 | ret = bus_register(&media_bus_type); | ||
300 | if (ret < 0) { | ||
301 | unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES); | ||
302 | printk(KERN_WARNING "media: bus_register failed\n"); | ||
303 | return -EIO; | ||
304 | } | ||
305 | |||
306 | return 0; | ||
307 | } | ||
308 | |||
309 | static void __exit media_devnode_exit(void) | ||
310 | { | ||
311 | bus_unregister(&media_bus_type); | ||
312 | unregister_chrdev_region(media_dev_t, MEDIA_NUM_DEVICES); | ||
313 | } | ||
314 | |||
315 | module_init(media_devnode_init) | ||
316 | module_exit(media_devnode_exit) | ||
317 | |||
318 | MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>"); | ||
319 | MODULE_DESCRIPTION("Device node registration for media drivers"); | ||
320 | MODULE_LICENSE("GPL"); | ||