2005-04-17 02:20:36 +04:00
/*
* Video capture interface for Linux
*
* A generic video device interface for the LINUX operating system
* using a set of device structures / vectors for low level operations .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*
* Author : Alan Cox , < alan @ redhat . com >
*
* Fixes : 20000516 Claudio Matsuoka < claudio @ conectiva . com >
* - Added procfs support
*/
# include <linux/module.h>
# include <linux/types.h>
# include <linux/kernel.h>
# include <linux/sched.h>
# include <linux/smp_lock.h>
# include <linux/mm.h>
# include <linux/string.h>
# include <linux/errno.h>
# include <linux/init.h>
# include <linux/kmod.h>
# include <linux/slab.h>
# include <linux/devfs_fs_kernel.h>
# include <asm/uaccess.h>
# include <asm/system.h>
# include <linux/videodev.h>
# define VIDEO_NUM_DEVICES 256
# define VIDEO_NAME "video4linux"
/*
* sysfs stuff
*/
static ssize_t show_name ( struct class_device * cd , char * buf )
{
struct video_device * vfd = container_of ( cd , struct video_device , class_dev ) ;
return sprintf ( buf , " %.*s \n " , ( int ) sizeof ( vfd - > name ) , vfd - > name ) ;
}
static CLASS_DEVICE_ATTR ( name , S_IRUGO , show_name , NULL ) ;
struct video_device * video_device_alloc ( void )
{
struct video_device * vfd ;
2006-01-12 00:40:56 +03:00
vfd = kzalloc ( sizeof ( * vfd ) , GFP_KERNEL ) ;
2005-04-17 02:20:36 +04:00
return vfd ;
}
void video_device_release ( struct video_device * vfd )
{
kfree ( vfd ) ;
}
static void video_release ( struct class_device * cd )
{
struct video_device * vfd = container_of ( cd , struct video_device , class_dev ) ;
2006-01-09 20:25:21 +03:00
# if 1
2006-01-09 20:25:34 +03:00
/* needed until all drivers are fixed */
2005-04-17 02:20:36 +04:00
if ( ! vfd - > release )
return ;
# endif
vfd - > release ( vfd ) ;
}
static struct class video_class = {
2005-12-01 11:51:19 +03:00
. name = VIDEO_NAME ,
2005-04-17 02:20:36 +04:00
. release = video_release ,
} ;
/*
2005-12-01 11:51:19 +03:00
* Active devices
2005-04-17 02:20:36 +04:00
*/
2005-12-01 11:51:19 +03:00
2005-04-17 02:20:36 +04:00
static struct video_device * video_device [ VIDEO_NUM_DEVICES ] ;
2006-01-15 12:52:23 +03:00
static DEFINE_MUTEX ( videodev_lock ) ;
2005-04-17 02:20:36 +04:00
struct video_device * video_devdata ( struct file * file )
{
return video_device [ iminor ( file - > f_dentry - > d_inode ) ] ;
}
/*
* Open a video device .
*/
static int video_open ( struct inode * inode , struct file * file )
{
unsigned int minor = iminor ( inode ) ;
int err = 0 ;
struct video_device * vfl ;
2006-03-28 13:56:41 +04:00
const struct file_operations * old_fops ;
2005-12-01 11:51:19 +03:00
2005-04-17 02:20:36 +04:00
if ( minor > = VIDEO_NUM_DEVICES )
return - ENODEV ;
2006-01-15 12:52:23 +03:00
mutex_lock ( & videodev_lock ) ;
2005-04-17 02:20:36 +04:00
vfl = video_device [ minor ] ;
if ( vfl = = NULL ) {
2006-01-15 12:52:23 +03:00
mutex_unlock ( & videodev_lock ) ;
2005-04-17 02:20:36 +04:00
request_module ( " char-major-%d-%d " , VIDEO_MAJOR , minor ) ;
2006-01-15 12:52:23 +03:00
mutex_lock ( & videodev_lock ) ;
2005-04-17 02:20:36 +04:00
vfl = video_device [ minor ] ;
if ( vfl = = NULL ) {
2006-01-15 12:52:23 +03:00
mutex_unlock ( & videodev_lock ) ;
2005-04-17 02:20:36 +04:00
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 ) ;
2006-01-15 12:52:23 +03:00
mutex_unlock ( & videodev_lock ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
/*
* helper function - - handles userspace copying for ioctl arguments
*/
static unsigned int
video_fix_command ( unsigned int cmd )
{
switch ( cmd ) {
case VIDIOC_OVERLAY_OLD :
cmd = VIDIOC_OVERLAY ;
break ;
case VIDIOC_S_PARM_OLD :
cmd = VIDIOC_S_PARM ;
break ;
case VIDIOC_S_CTRL_OLD :
cmd = VIDIOC_S_CTRL ;
break ;
case VIDIOC_G_AUDIO_OLD :
cmd = VIDIOC_G_AUDIO ;
break ;
case VIDIOC_G_AUDOUT_OLD :
cmd = VIDIOC_G_AUDOUT ;
break ;
case VIDIOC_CROPCAP_OLD :
cmd = VIDIOC_CROPCAP ;
break ;
}
return cmd ;
}
int
video_usercopy ( struct inode * inode , struct file * file ,
unsigned int cmd , unsigned long arg ,
int ( * func ) ( struct inode * inode , struct file * file ,
unsigned int cmd , void * arg ) )
{
char sbuf [ 128 ] ;
void * mbuf = NULL ;
void * parg = NULL ;
int err = - EINVAL ;
cmd = video_fix_command ( cmd ) ;
/* Copy arguments into temp kernel buffer */
switch ( _IOC_DIR ( cmd ) ) {
case _IOC_NONE :
parg = NULL ;
break ;
case _IOC_READ :
case _IOC_WRITE :
case ( _IOC_WRITE | _IOC_READ ) :
if ( _IOC_SIZE ( cmd ) < = sizeof ( sbuf ) ) {
parg = sbuf ;
} else {
/* too big to allocate from stack */
mbuf = kmalloc ( _IOC_SIZE ( cmd ) , GFP_KERNEL ) ;
if ( NULL = = mbuf )
return - ENOMEM ;
parg = mbuf ;
}
2005-12-01 11:51:19 +03:00
2005-04-17 02:20:36 +04:00
err = - EFAULT ;
if ( _IOC_DIR ( cmd ) & _IOC_WRITE )
if ( copy_from_user ( parg , ( void __user * ) arg , _IOC_SIZE ( cmd ) ) )
goto out ;
break ;
}
/* call driver */
err = func ( inode , file , cmd , parg ) ;
if ( err = = - ENOIOCTLCMD )
err = - EINVAL ;
if ( err < 0 )
goto out ;
/* Copy results into user buffer */
switch ( _IOC_DIR ( cmd ) )
{
case _IOC_READ :
case ( _IOC_WRITE | _IOC_READ ) :
if ( copy_to_user ( ( void __user * ) arg , parg , _IOC_SIZE ( cmd ) ) )
err = - EFAULT ;
break ;
}
out :
2005-11-07 12:01:31 +03:00
kfree ( mbuf ) ;
2005-04-17 02:20:36 +04:00
return err ;
}
/*
* open / release helper functions - - handle exclusive opens
*/
int video_exclusive_open ( struct inode * inode , struct file * file )
{
struct video_device * vfl = video_devdata ( file ) ;
int retval = 0 ;
2006-02-07 11:49:14 +03:00
mutex_lock ( & vfl - > lock ) ;
2005-04-17 02:20:36 +04:00
if ( vfl - > users ) {
retval = - EBUSY ;
} else {
vfl - > users + + ;
}
2006-02-07 11:49:14 +03:00
mutex_unlock ( & vfl - > lock ) ;
2005-04-17 02:20:36 +04:00
return retval ;
}
int video_exclusive_release ( struct inode * inode , struct file * file )
{
struct video_device * vfl = video_devdata ( file ) ;
2005-12-01 11:51:19 +03:00
2005-04-17 02:20:36 +04:00
vfl - > users - - ;
return 0 ;
}
static struct file_operations video_fops ;
/**
* video_register_device - register video4linux devices
* @ vfd : video device structure we want to register
* @ type : type of device to register
* @ nr : which device number ( 0 = = / dev / video0 , 1 = = / dev / video1 , . . .
* - 1 = = first free )
2005-12-01 11:51:19 +03:00
*
2005-04-17 02:20:36 +04:00
* The registration code assigns minor numbers based on the type
* requested . - ENFILE is returned in all the device slots for this
* category are full . If not then the minor field is set and the
* driver initialize function is called ( if non % NULL ) .
*
* Zero is returned on success .
*
* Valid types are
*
* % VFL_TYPE_GRABBER - A frame grabber
*
* % VFL_TYPE_VTX - A teletext device
*
* % VFL_TYPE_VBI - Vertical blank data ( undecoded )
*
2005-12-01 11:51:19 +03:00
* % VFL_TYPE_RADIO - A radio card
2005-04-17 02:20:36 +04:00
*/
int video_register_device ( struct video_device * vfd , int type , int nr )
{
int i = 0 ;
int base ;
int end ;
char * name_base ;
2005-12-01 11:51:19 +03:00
2005-04-17 02:20:36 +04:00
switch ( type )
{
case VFL_TYPE_GRABBER :
2006-01-23 22:11:07 +03:00
base = MINOR_VFL_TYPE_GRABBER_MIN ;
end = MINOR_VFL_TYPE_GRABBER_MAX + 1 ;
2005-04-17 02:20:36 +04:00
name_base = " video " ;
break ;
case VFL_TYPE_VTX :
2006-01-23 22:11:07 +03:00
base = MINOR_VFL_TYPE_VTX_MIN ;
end = MINOR_VFL_TYPE_VTX_MAX + 1 ;
2005-04-17 02:20:36 +04:00
name_base = " vtx " ;
break ;
case VFL_TYPE_VBI :
2006-01-23 22:11:07 +03:00
base = MINOR_VFL_TYPE_VBI_MIN ;
end = MINOR_VFL_TYPE_VBI_MAX + 1 ;
2005-04-17 02:20:36 +04:00
name_base = " vbi " ;
break ;
case VFL_TYPE_RADIO :
2006-01-23 22:11:07 +03:00
base = MINOR_VFL_TYPE_RADIO_MIN ;
end = MINOR_VFL_TYPE_RADIO_MAX + 1 ;
2005-04-17 02:20:36 +04:00
name_base = " radio " ;
break ;
default :
return - 1 ;
}
/* pick a minor number */
2006-01-15 12:52:23 +03:00
mutex_lock ( & videodev_lock ) ;
2005-04-17 02:20:36 +04:00
if ( nr > = 0 & & nr < end - base ) {
/* use the one the driver asked for */
i = base + nr ;
if ( NULL ! = video_device [ i ] ) {
2006-01-15 12:52:23 +03:00
mutex_unlock ( & videodev_lock ) ;
2005-04-17 02:20:36 +04:00
return - ENFILE ;
}
} else {
/* use first free */
for ( i = base ; i < end ; i + + )
if ( NULL = = video_device [ i ] )
break ;
if ( i = = end ) {
2006-01-15 12:52:23 +03:00
mutex_unlock ( & videodev_lock ) ;
2005-04-17 02:20:36 +04:00
return - ENFILE ;
}
}
video_device [ i ] = vfd ;
vfd - > minor = i ;
2006-01-15 12:52:23 +03:00
mutex_unlock ( & videodev_lock ) ;
2005-04-17 02:20:36 +04:00
sprintf ( vfd - > devfs_name , " v4l/%s%d " , name_base , i - base ) ;
devfs_mk_cdev ( MKDEV ( VIDEO_MAJOR , vfd - > minor ) ,
S_IFCHR | S_IRUSR | S_IWUSR , vfd - > devfs_name ) ;
2006-02-07 11:49:14 +03:00
mutex_init ( & vfd - > lock ) ;
2005-04-17 02:20:36 +04:00
/* sysfs class */
2005-12-01 11:51:19 +03:00
memset ( & vfd - > class_dev , 0x00 , sizeof ( vfd - > class_dev ) ) ;
2005-04-17 02:20:36 +04:00
if ( vfd - > dev )
vfd - > class_dev . dev = vfd - > dev ;
vfd - > class_dev . class = & video_class ;
2006-01-09 20:25:34 +03:00
vfd - > class_dev . devt = MKDEV ( VIDEO_MAJOR , vfd - > minor ) ;
2005-04-17 02:20:36 +04:00
strlcpy ( vfd - > class_dev . class_id , vfd - > devfs_name + 4 , BUS_ID_SIZE ) ;
class_device_register ( & vfd - > class_dev ) ;
class_device_create_file ( & vfd - > class_dev ,
2006-01-09 20:25:34 +03:00
& class_device_attr_name ) ;
2005-04-17 02:20:36 +04:00
2006-01-09 20:25:21 +03:00
# if 1
/* needed until all drivers are fixed */
2005-04-17 02:20:36 +04:00
if ( ! vfd - > release )
printk ( KERN_WARNING " videodev: \" %s \" has no release callback. "
" Please fix your driver for proper sysfs support, see "
" http://lwn.net/Articles/36850/ \n " , vfd - > name ) ;
# endif
return 0 ;
}
/**
* video_unregister_device - unregister a video4linux device
* @ vfd : the device to unregister
*
* This unregisters the passed device and deassigns the minor
* number . Future open calls will be met with errors .
*/
2005-12-01 11:51:19 +03:00
2005-04-17 02:20:36 +04:00
void video_unregister_device ( struct video_device * vfd )
{
2006-01-15 12:52:23 +03:00
mutex_lock ( & videodev_lock ) ;
2005-04-17 02:20:36 +04:00
if ( video_device [ vfd - > minor ] ! = vfd )
panic ( " videodev: bad unregister " ) ;
devfs_remove ( vfd - > devfs_name ) ;
video_device [ vfd - > minor ] = NULL ;
class_device_unregister ( & vfd - > class_dev ) ;
2006-01-15 12:52:23 +03:00
mutex_unlock ( & videodev_lock ) ;
2005-04-17 02:20:36 +04:00
}
static struct file_operations video_fops =
{
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. open = video_open ,
} ;
/*
* Initialise video for linux
*/
2005-12-01 11:51:19 +03:00
2005-04-17 02:20:36 +04:00
static int __init videodev_init ( void )
{
int ret ;
printk ( KERN_INFO " Linux video capture interface: v1.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 = class_register ( & video_class ) ;
if ( ret < 0 ) {
unregister_chrdev ( VIDEO_MAJOR , VIDEO_NAME ) ;
printk ( KERN_WARNING " video_dev: class_register failed \n " ) ;
return - EIO ;
}
return 0 ;
}
static void __exit videodev_exit ( void )
{
class_unregister ( & video_class ) ;
unregister_chrdev ( VIDEO_MAJOR , VIDEO_NAME ) ;
}
module_init ( videodev_init )
module_exit ( videodev_exit )
EXPORT_SYMBOL ( video_register_device ) ;
EXPORT_SYMBOL ( video_unregister_device ) ;
EXPORT_SYMBOL ( video_devdata ) ;
EXPORT_SYMBOL ( video_usercopy ) ;
EXPORT_SYMBOL ( video_exclusive_open ) ;
EXPORT_SYMBOL ( video_exclusive_release ) ;
EXPORT_SYMBOL ( video_device_alloc ) ;
EXPORT_SYMBOL ( video_device_release ) ;
MODULE_AUTHOR ( " Alan Cox " ) ;
MODULE_DESCRIPTION ( " Device registrar for Video4Linux drivers " ) ;
MODULE_LICENSE ( " GPL " ) ;
/*
* Local variables :
* c - basic - offset : 8
* End :
*/