Rev 603 | Rev 606 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed
Rev | Author | Line No. | Line |
---|---|---|---|
540 | giacomo | 1 | /* |
2 | * Video capture interface for Linux |
||
3 | * |
||
4 | * A generic video device interface for the LINUX operating system |
||
5 | * using a set of device structures/vectors for low level operations. |
||
6 | * |
||
7 | * This program is free software; you can redistribute it and/or |
||
8 | * modify it under the terms of the GNU General Public License |
||
9 | * as published by the Free Software Foundation; either version |
||
10 | * 2 of the License, or (at your option) any later version. |
||
11 | * |
||
12 | * Author: Alan Cox, <alan@redhat.com> |
||
13 | * |
||
14 | * Fixes: 20000516 Claudio Matsuoka <claudio@conectiva.com> |
||
15 | * - Added procfs support |
||
16 | */ |
||
17 | |||
18 | #include <linuxcomp.h> |
||
19 | |||
20 | #include <linux/module.h> |
||
21 | #include <linux/types.h> |
||
22 | #include <linux/kernel.h> |
||
23 | #include <linux/sched.h> |
||
24 | #include <linux/smp_lock.h> |
||
25 | #include <linux/mm.h> |
||
26 | #include <linux/string.h> |
||
27 | #include <linux/errno.h> |
||
28 | #include <linux/init.h> |
||
29 | #include <linux/kmod.h> |
||
30 | #include <linux/slab.h> |
||
31 | #include <linux/devfs_fs_kernel.h> |
||
32 | #include <asm/uaccess.h> |
||
33 | #include <asm/system.h> |
||
34 | #include <asm/semaphore.h> |
||
35 | |||
36 | #include <linux/videodev.h> |
||
37 | |||
38 | #define VIDEO_NUM_DEVICES 256 |
||
39 | #define VIDEO_NAME "video4linux" |
||
40 | |||
41 | /* |
||
42 | * sysfs stuff |
||
43 | */ |
||
44 | |||
45 | static ssize_t show_name(struct class_device *cd, char *buf) |
||
46 | { |
||
47 | struct video_device *vfd = container_of(cd, struct video_device, class_dev); |
||
48 | return sprintf26(buf,"%.*s\n",(int)sizeof(vfd->name),vfd->name); |
||
49 | } |
||
50 | |||
51 | static ssize_t show_dev(struct class_device *cd, char *buf) |
||
52 | { |
||
53 | struct video_device *vfd = container_of(cd, struct video_device, class_dev); |
||
54 | dev_t dev = MKDEV(VIDEO_MAJOR, vfd->minor); |
||
55 | return print_dev_t(buf,dev); |
||
56 | } |
||
57 | |||
58 | static CLASS_DEVICE_ATTR(name, S_IRUGO, show_name, NULL); |
||
59 | static CLASS_DEVICE_ATTR(dev, S_IRUGO, show_dev, NULL); |
||
60 | |||
61 | struct video_device *video_device_alloc(void) |
||
62 | { |
||
63 | struct video_device *vfd; |
||
64 | |||
65 | vfd = kmalloc(sizeof(*vfd),GFP_KERNEL); |
||
66 | if (NULL == vfd) |
||
67 | return NULL; |
||
68 | memset(vfd,0,sizeof(*vfd)); |
||
69 | return vfd; |
||
70 | } |
||
71 | |||
72 | void video_device_release(struct video_device *vfd) |
||
73 | { |
||
74 | kfree(vfd); |
||
75 | } |
||
76 | |||
77 | static void video_release(struct class_device *cd) |
||
78 | { |
||
79 | struct video_device *vfd = container_of(cd, struct video_device, class_dev); |
||
80 | |||
81 | #if 1 /* needed until all drivers are fixed */ |
||
82 | if (!vfd->release) |
||
83 | return; |
||
84 | #endif |
||
85 | vfd->release(vfd); |
||
86 | } |
||
87 | |||
88 | static struct class video_class = { |
||
89 | .name = VIDEO_NAME, |
||
90 | .release = video_release, |
||
91 | }; |
||
92 | |||
93 | /* |
||
94 | * Active devices |
||
95 | */ |
||
96 | |||
97 | static struct video_device *video_device[VIDEO_NUM_DEVICES]; |
||
98 | static DECLARE_MUTEX(videodev_lock); |
||
99 | |||
100 | struct video_device* video_devdata(struct file *file) |
||
101 | { |
||
102 | return video_device[iminor(file->f_dentry->d_inode)]; |
||
103 | } |
||
104 | |||
105 | /* |
||
106 | * Open a video device. |
||
107 | */ |
||
108 | static int video_open(struct inode *inode, struct file *file) |
||
109 | { |
||
110 | unsigned int minor = iminor(inode); |
||
111 | int err = 0; |
||
112 | struct video_device *vfl; |
||
113 | struct file_operations *old_fops; |
||
114 | |||
115 | if(minor>=VIDEO_NUM_DEVICES) |
||
116 | return -ENODEV; |
||
117 | //down(&videodev_lock); |
||
118 | vfl=video_device[minor]; |
||
119 | if(vfl==NULL) { |
||
120 | //up(&videodev_lock); |
||
121 | //request_module("char-major-%d-%d", VIDEO_MAJOR, minor); |
||
122 | //down(&videodev_lock); |
||
123 | vfl=video_device[minor]; |
||
124 | if (vfl==NULL) { |
||
125 | //up(&videodev_lock); |
||
126 | return -ENODEV; |
||
127 | } |
||
128 | } |
||
129 | old_fops = file->f_op; |
||
130 | file->f_op = fops_get(vfl->fops); |
||
131 | if(file->f_op->open) |
||
132 | err = file->f_op->open(inode,file); |
||
133 | if (err) { |
||
134 | fops_put(file->f_op); |
||
135 | file->f_op = fops_get(old_fops); |
||
136 | } |
||
137 | fops_put(old_fops); |
||
138 | //up(&videodev_lock); |
||
139 | return err; |
||
140 | } |
||
141 | |||
142 | /* |
||
143 | * helper function -- handles userspace copying for ioctl arguments |
||
144 | */ |
||
145 | int |
||
146 | video_usercopy(struct inode *inode, struct file *file, |
||
147 | unsigned int cmd, unsigned long arg, |
||
148 | int (*func)(struct inode *inode, struct file *file, |
||
149 | unsigned int cmd, void *arg)) |
||
150 | { |
||
151 | char sbuf[128]; |
||
152 | void *mbuf = NULL; |
||
153 | void *parg = NULL; |
||
154 | int err = -EINVAL; |
||
155 | |||
156 | /* Copy arguments into temp kernel buffer */ |
||
157 | switch (_IOC_DIR(cmd)) { |
||
158 | case _IOC_NONE: |
||
159 | parg = (void *)arg; |
||
160 | break; |
||
161 | case _IOC_READ: /* some v4l ioctls are marked wrong ... */ |
||
162 | case _IOC_WRITE: |
||
163 | case (_IOC_WRITE | _IOC_READ): |
||
164 | if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { |
||
165 | parg = sbuf; |
||
166 | } else { |
||
167 | /* too big to allocate from stack */ |
||
168 | mbuf = kmalloc(_IOC_SIZE(cmd),GFP_KERNEL); |
||
169 | if (NULL == mbuf) |
||
170 | return -ENOMEM; |
||
171 | parg = mbuf; |
||
172 | } |
||
173 | |||
174 | err = -EFAULT; |
||
175 | if (copy_from_user(parg, (void *)arg, _IOC_SIZE(cmd))) |
||
176 | goto out; |
||
177 | break; |
||
178 | } |
||
179 | |||
180 | /* call driver */ |
||
181 | err = func(inode, file, cmd, parg); |
||
182 | if (err == -ENOIOCTLCMD) |
||
183 | err = -EINVAL; |
||
184 | if (err < 0) |
||
185 | goto out; |
||
186 | |||
187 | /* Copy results into user buffer */ |
||
188 | switch (_IOC_DIR(cmd)) |
||
189 | { |
||
190 | case _IOC_READ: |
||
191 | case (_IOC_WRITE | _IOC_READ): |
||
192 | if (copy_to_user((void *)arg, parg, _IOC_SIZE(cmd))) |
||
193 | err = -EFAULT; |
||
194 | break; |
||
195 | } |
||
196 | |||
197 | out: |
||
198 | if (mbuf) |
||
199 | kfree(mbuf); |
||
200 | return err; |
||
201 | } |
||
202 | |||
203 | /* |
||
204 | * open/release helper functions -- handle exclusive opens |
||
205 | */ |
||
206 | extern int video_exclusive_open(struct inode *inode, struct file *file) |
||
207 | { |
||
208 | struct video_device *vfl = video_devdata(file); |
||
209 | int retval = 0; |
||
210 | |||
211 | //down(&vfl->lock); |
||
212 | if (vfl->users) { |
||
213 | retval = -EBUSY; |
||
214 | } else { |
||
215 | vfl->users++; |
||
216 | } |
||
217 | //up(&vfl->lock); |
||
218 | return retval; |
||
219 | } |
||
220 | |||
221 | extern int video_exclusive_release(struct inode *inode, struct file *file) |
||
222 | { |
||
223 | struct video_device *vfl = video_devdata(file); |
||
224 | |||
225 | vfl->users--; |
||
226 | return 0; |
||
227 | } |
||
228 | |||
229 | extern struct file_operations video_fops; |
||
230 | |||
231 | /** |
||
232 | * video_register_device - register video4linux devices |
||
233 | * @vfd: video device structure we want to register |
||
234 | * @type: type of device to register |
||
235 | * @nr: which device number (0 == /dev/video0, 1 == /dev/video1, ... |
||
236 | * -1 == first free) |
||
237 | * |
||
238 | * The registration code assigns minor numbers based on the type |
||
239 | * requested. -ENFILE is returned in all the device slots for this |
||
240 | * category are full. If not then the minor field is set and the |
||
241 | * driver initialize function is called (if non %NULL). |
||
242 | * |
||
243 | * Zero is returned on success. |
||
244 | * |
||
245 | * Valid types are |
||
246 | * |
||
247 | * %VFL_TYPE_GRABBER - A frame grabber |
||
248 | * |
||
249 | * %VFL_TYPE_VTX - A teletext device |
||
250 | * |
||
251 | * %VFL_TYPE_VBI - Vertical blank data (undecoded) |
||
252 | * |
||
253 | * %VFL_TYPE_RADIO - A radio card |
||
254 | */ |
||
255 | |||
256 | int video_register_device(struct video_device *vfd, int type, int nr) |
||
257 | { |
||
258 | int i=0; |
||
259 | int base; |
||
260 | int end; |
||
261 | char *name_base; |
||
262 | |||
263 | switch(type) |
||
264 | { |
||
265 | case VFL_TYPE_GRABBER: |
||
266 | base=0; |
||
267 | end=64; |
||
268 | name_base = "video"; |
||
269 | break; |
||
270 | case VFL_TYPE_VTX: |
||
271 | base=192; |
||
272 | end=224; |
||
273 | name_base = "vtx"; |
||
274 | break; |
||
275 | case VFL_TYPE_VBI: |
||
276 | base=224; |
||
277 | end=240; |
||
278 | name_base = "vbi"; |
||
279 | break; |
||
280 | case VFL_TYPE_RADIO: |
||
281 | base=64; |
||
282 | end=128; |
||
283 | name_base = "radio"; |
||
284 | break; |
||
285 | default: |
||
286 | return -1; |
||
287 | } |
||
288 | |||
289 | /* pick a minor number */ |
||
290 | //down(&videodev_lock); |
||
291 | if (-1 == nr) { |
||
292 | /* use first free */ |
||
293 | for(i=base;i<end;i++) |
||
294 | if (NULL == video_device[i]) |
||
295 | break; |
||
296 | if (i == end) { |
||
297 | //up(&videodev_lock); |
||
298 | return -ENFILE; |
||
299 | } |
||
300 | } else { |
||
301 | /* use the one the driver asked for */ |
||
302 | i = base+nr; |
||
303 | if (NULL != video_device[i]) { |
||
304 | //up(&videodev_lock); |
||
305 | return -ENFILE; |
||
306 | } |
||
307 | } |
||
308 | video_device[i]=vfd; |
||
309 | vfd->minor=i; |
||
310 | //up(&videodev_lock); |
||
311 | |||
312 | sprintf26(vfd->devfs_name, "v4l/%s%d", name_base, i - base); |
||
313 | devfs_mk_cdev(MKDEV(VIDEO_MAJOR, vfd->minor), |
||
314 | S_IFCHR | S_IRUSR | S_IWUSR, vfd->devfs_name); |
||
315 | //init_MUTEX(&vfd->lock); |
||
316 | |||
317 | /* sysfs class */ |
||
318 | memset(&vfd->class_dev, 0x00, sizeof(vfd->class_dev)); |
||
319 | if (vfd->dev) |
||
320 | vfd->class_dev.dev = vfd->dev; |
||
321 | vfd->class_dev.class = &video_class; |
||
322 | strncpy(vfd->class_dev.class_id, vfd->devfs_name + 4, BUS_ID_SIZE); |
||
323 | class_device_register(&vfd->class_dev); |
||
324 | class_device_create_file(&vfd->class_dev, |
||
325 | &class_device_attr_name); |
||
326 | class_device_create_file(&vfd->class_dev, |
||
327 | &class_device_attr_dev); |
||
328 | |||
329 | #if 1 /* needed until all drivers are fixed */ |
||
330 | if (!vfd->release) |
||
331 | printk(KERN_WARNING "videodev: \"%s\" has no release callback. " |
||
332 | "Please fix your driver for proper sysfs support, see " |
||
333 | "http://lwn.net/Articles/36850/\n", vfd->name); |
||
334 | #endif |
||
335 | return 0; |
||
336 | } |
||
337 | |||
338 | /** |
||
339 | * video_unregister_device - unregister a video4linux device |
||
340 | * @vfd: the device to unregister |
||
341 | * |
||
342 | * This unregisters the passed device and deassigns the minor |
||
343 | * number. Future open calls will be met with errors. |
||
344 | */ |
||
345 | |||
346 | void video_unregister_device(struct video_device *vfd) |
||
347 | { |
||
348 | //down(&videodev_lock); |
||
349 | if(video_device[vfd->minor]!=vfd) |
||
350 | panic("videodev: bad unregister"); |
||
351 | |||
352 | devfs_remove(vfd->devfs_name); |
||
353 | video_device[vfd->minor]=NULL; |
||
354 | class_device_unregister(&vfd->class_dev); |
||
355 | //up(&videodev_lock); |
||
356 | } |
||
357 | |||
358 | |||
359 | static struct file_operations video_fops= |
||
360 | { |
||
361 | .owner = THIS_MODULE, |
||
362 | .llseek = no_llseek, |
||
363 | .open = video_open, |
||
364 | }; |
||
365 | |||
366 | /* |
||
367 | * Initialise video for linux |
||
368 | */ |
||
369 | |||
370 | int __init videodev_init(void) |
||
371 | { |
||
372 | printk(KERN_INFO "Linux video capture interface: v1.00\n"); |
||
373 | if (register_chrdev(VIDEO_MAJOR,VIDEO_NAME, &video_fops)) { |
||
374 | printk("video_dev: unable to get major %d\n", VIDEO_MAJOR); |
||
375 | return -EIO; |
||
376 | } |
||
377 | class_register(&video_class); |
||
378 | return 0; |
||
379 | } |
||
380 | |||
381 | void __exit videodev_exit(void) |
||
382 | { |
||
383 | class_unregister(&video_class); |
||
384 | unregister_chrdev(VIDEO_MAJOR, VIDEO_NAME); |
||
385 | } |
||
386 | |||
603 | giacomo | 387 | extern int linuxcomp_setfd(struct inode *i, int i_rdev); |
388 | |||
389 | /* Shark Inode emulation */ |
||
390 | int videodev_open_inode(int num) { |
||
391 | |||
392 | struct inode *i; |
||
393 | |||
394 | i = (struct inode *)kmalloc(sizeof(struct inode),GFP_KERNEL); |
||
395 | |||
396 | linuxcomp_setfd(i,num); |
||
397 | |||
398 | if (video_open(i,NULL)) { |
||
399 | kfree(i); |
||
400 | return -1; |
||
401 | } |
||
402 | |||
403 | kfree(i); |
||
404 | return 0; |
||
405 | |||
406 | } |
||
604 | giacomo | 407 | |
408 | extern int bttv_ioctl(struct inode *inode, struct file *file, |
||
409 | unsigned int cmd, unsigned long arg); |
||
410 | |||
411 | int videodev_ioctl_inode(int num,unsigned int cmd,unsigned long arg) { |
||
412 | |||
413 | struct inode *i; |
||
414 | int res; |
||
603 | giacomo | 415 | |
604 | giacomo | 416 | i = (struct inode *)kmalloc(sizeof(struct inode),GFP_KERNEL); |
417 | |||
418 | linuxcomp_setfd(i,num); |
||
419 | |||
420 | res = bttv_ioctl(i,NULL,cmd,arg); |
||
421 | |||
422 | kfree(i); |
||
423 | return res; |
||
424 | |||
425 | } |
||
426 | |||
540 | giacomo | 427 | module_init(videodev_init) |
428 | module_exit(videodev_exit) |
||
429 | |||
430 | EXPORT_SYMBOL(video_register_device); |
||
431 | EXPORT_SYMBOL(video_unregister_device); |
||
432 | EXPORT_SYMBOL(video_devdata); |
||
433 | EXPORT_SYMBOL(video_usercopy); |
||
434 | EXPORT_SYMBOL(video_exclusive_open); |
||
435 | EXPORT_SYMBOL(video_exclusive_release); |
||
436 | EXPORT_SYMBOL(video_device_alloc); |
||
437 | EXPORT_SYMBOL(video_device_release); |
||
438 | |||
439 | MODULE_AUTHOR("Alan Cox"); |
||
440 | MODULE_DESCRIPTION("Device registrar for Video4Linux drivers"); |
||
441 | MODULE_LICENSE("GPL"); |
||
442 | |||
443 | |||
444 | /* |
||
445 | * Local variables: |
||
446 | * c-basic-offset: 8 |
||
447 | * End: |
||
448 | */ |