videodev.c 9 KB
Newer Older
Linus Torvalds's avatar
Linus Torvalds committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/*
 * 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>
Gerd Knorr's avatar
Gerd Knorr committed
27
#include <linux/kmod.h>
28
#include <linux/slab.h>
29
#include <linux/devfs_fs_kernel.h>
Linus Torvalds's avatar
Linus Torvalds committed
30 31
#include <asm/uaccess.h>
#include <asm/system.h>
Linus Torvalds's avatar
Linus Torvalds committed
32
#include <asm/semaphore.h>
Linus Torvalds's avatar
Linus Torvalds committed
33

Gerd Knorr's avatar
Gerd Knorr committed
34
#include <linux/videodev.h>
Linus Torvalds's avatar
Linus Torvalds committed
35

Gerd Knorr's avatar
Gerd Knorr committed
36 37
#define VIDEO_NUM_DEVICES	256
#define VIDEO_NAME              "video4linux"
Linus Torvalds's avatar
Linus Torvalds committed
38 39

/*
Gerd Knorr's avatar
Gerd Knorr committed
40
 *	sysfs stuff
Linus Torvalds's avatar
Linus Torvalds committed
41 42
 */

Gerd Knorr's avatar
Gerd Knorr committed
43 44 45 46 47
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);
}
Linus Torvalds's avatar
Linus Torvalds committed
48

Gerd Knorr's avatar
Gerd Knorr committed
49 50 51 52
static ssize_t show_dev(struct class_device *cd, char *buf)
{
	struct video_device *vfd = container_of(cd, struct video_device, class_dev);
	dev_t dev = MKDEV(VIDEO_MAJOR, vfd->minor);
Andrew Morton's avatar
Andrew Morton committed
53
	return print_dev_t(buf,dev);
Gerd Knorr's avatar
Gerd Knorr committed
54
}
Linus Torvalds's avatar
Linus Torvalds committed
55

Gerd Knorr's avatar
Gerd Knorr committed
56 57
static CLASS_DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
static CLASS_DEVICE_ATTR(dev,  S_IRUGO, show_dev, NULL);
Linus Torvalds's avatar
Linus Torvalds committed
58

Gerd Knorr's avatar
Gerd Knorr committed
59 60 61
struct video_device *video_device_alloc(void)
{
	struct video_device *vfd;
Linus Torvalds's avatar
Linus Torvalds committed
62

Gerd Knorr's avatar
Gerd Knorr committed
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
	vfd = kmalloc(sizeof(*vfd),GFP_KERNEL);
	if (NULL == vfd)
		return NULL;
	memset(vfd,0,sizeof(*vfd));
	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);

#if 1 /* needed until all drivers are fixed */
	if (!vfd->release)
		return;
#endif
	vfd->release(vfd);
}

static struct class video_class = {
        .name    = VIDEO_NAME,
	.release = video_release,
};
Linus Torvalds's avatar
Linus Torvalds committed
90

Gerd Knorr's avatar
Gerd Knorr committed
91 92 93 94 95 96
/*
 *	Active devices 
 */
 
static struct video_device *video_device[VIDEO_NUM_DEVICES];
static DECLARE_MUTEX(videodev_lock);
Linus Torvalds's avatar
Linus Torvalds committed
97

Gerd Knorr's avatar
Gerd Knorr committed
98
struct video_device* video_devdata(struct file *file)
Linus Torvalds's avatar
Linus Torvalds committed
99
{
100
	return video_device[iminor(file->f_dentry->d_inode)];
Linus Torvalds's avatar
Linus Torvalds committed
101 102 103 104 105 106 107
}

/*
 *	Open a video device.
 */
static int video_open(struct inode *inode, struct file *file)
{
108
	unsigned int minor = iminor(inode);
Gerd Knorr's avatar
Gerd Knorr committed
109
	int err = 0;
Linus Torvalds's avatar
Linus Torvalds committed
110
	struct video_device *vfl;
Gerd Knorr's avatar
Gerd Knorr committed
111
	struct file_operations *old_fops;
Linus Torvalds's avatar
Linus Torvalds committed
112 113 114
	
	if(minor>=VIDEO_NUM_DEVICES)
		return -ENODEV;
Gerd Knorr's avatar
Gerd Knorr committed
115
	down(&videodev_lock);
Linus Torvalds's avatar
Linus Torvalds committed
116 117
	vfl=video_device[minor];
	if(vfl==NULL) {
Gerd Knorr's avatar
Gerd Knorr committed
118
		up(&videodev_lock);
119
		request_module("char-major-%d-%d", VIDEO_MAJOR, minor);
Gerd Knorr's avatar
Gerd Knorr committed
120
		down(&videodev_lock);
Linus Torvalds's avatar
Linus Torvalds committed
121 122
		vfl=video_device[minor];
		if (vfl==NULL) {
Gerd Knorr's avatar
Gerd Knorr committed
123 124
			up(&videodev_lock);
			return -ENODEV;
Linus Torvalds's avatar
Linus Torvalds committed
125 126
		}
	}
Gerd Knorr's avatar
Gerd Knorr committed
127 128 129 130 131 132 133
	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);
Linus Torvalds's avatar
Linus Torvalds committed
134
	}
Gerd Knorr's avatar
Gerd Knorr committed
135 136 137
	fops_put(old_fops);
	up(&videodev_lock);
	return err;
Linus Torvalds's avatar
Linus Torvalds committed
138 139 140
}

/*
141
 * helper function -- handles userspace copying for ioctl arguments
Linus Torvalds's avatar
Linus Torvalds committed
142
 */
Gerd Knorr's avatar
Gerd Knorr committed
143
int
144 145 146 147
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))
Linus Torvalds's avatar
Linus Torvalds committed
148
{
Gerd Knorr's avatar
Gerd Knorr committed
149 150 151 152
	char	sbuf[128];
	void    *mbuf = NULL;
	void	*parg = NULL;
	int	err  = -EINVAL;
Linus Torvalds's avatar
Linus Torvalds committed
153

Gerd Knorr's avatar
Gerd Knorr committed
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176
	/*  Copy arguments into temp kernel buffer  */
	switch (_IOC_DIR(cmd)) {
	case _IOC_NONE:
		parg = (void *)arg;
		break;
	case _IOC_READ: /* some v4l ioctls are marked wrong ... */
	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;
		}
		
		err = -EFAULT;
		if (copy_from_user(parg, (void *)arg, _IOC_SIZE(cmd)))
			goto out;
		break;
	}
Linus Torvalds's avatar
Linus Torvalds committed
177

Gerd Knorr's avatar
Gerd Knorr committed
178
	/* call driver */
179
	err = func(inode, file, cmd, parg);
Gerd Knorr's avatar
Gerd Knorr committed
180 181 182 183 184 185 186
	if (err == -ENOIOCTLCMD)
		err = -EINVAL;
	if (err < 0)
		goto out;

	/*  Copy results into user buffer  */
	switch (_IOC_DIR(cmd))
Linus Torvalds's avatar
Linus Torvalds committed
187
	{
Gerd Knorr's avatar
Gerd Knorr committed
188 189 190 191 192
	case _IOC_READ:
	case (_IOC_WRITE | _IOC_READ):
		if (copy_to_user((void *)arg, parg, _IOC_SIZE(cmd)))
			err = -EFAULT;
		break;
Linus Torvalds's avatar
Linus Torvalds committed
193
	}
Gerd Knorr's avatar
Gerd Knorr committed
194 195 196 197 198

out:
	if (mbuf)
		kfree(mbuf);
	return err;
Linus Torvalds's avatar
Linus Torvalds committed
199 200 201
}

/*
Gerd Knorr's avatar
Gerd Knorr committed
202
 * open/release helper functions -- handle exclusive opens
Linus Torvalds's avatar
Linus Torvalds committed
203
 */
Gerd Knorr's avatar
Gerd Knorr committed
204
extern int video_exclusive_open(struct inode *inode, struct file *file)
Linus Torvalds's avatar
Linus Torvalds committed
205
{
Gerd Knorr's avatar
Gerd Knorr committed
206 207 208 209 210 211 212 213
	struct  video_device *vfl = video_devdata(file);
	int retval = 0;

	down(&vfl->lock);
	if (vfl->users) {
		retval = -EBUSY;
	} else {
		vfl->users++;
Linus Torvalds's avatar
Linus Torvalds committed
214
	}
Gerd Knorr's avatar
Gerd Knorr committed
215 216 217 218 219 220 221 222 223 224
	up(&vfl->lock);
	return retval;
}

extern int video_exclusive_release(struct inode *inode, struct file *file)
{
	struct  video_device *vfl = video_devdata(file);
	
	vfl->users--;
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
225 226 227 228 229 230
}

extern struct file_operations video_fops;

/**
 *	video_register_device - register video4linux devices
Linus Torvalds's avatar
Linus Torvalds committed
231
 *	@vfd:  video device structure we want to register
Linus Torvalds's avatar
Linus Torvalds committed
232
 *	@type: type of device to register
Linus Torvalds's avatar
Linus Torvalds committed
233 234
 *	@nr:   which device number (0 == /dev/video0, 1 == /dev/video1, ...
 *             -1 == first free)
Linus Torvalds's avatar
Linus Torvalds committed
235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252
 *	
 *	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)
 *
 *	%VFL_TYPE_RADIO - A radio card	
 */
Linus Torvalds's avatar
Linus Torvalds committed
253 254

int video_register_device(struct video_device *vfd, int type, int nr)
Linus Torvalds's avatar
Linus Torvalds committed
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
{
	int i=0;
	int base;
	int end;
	char *name_base;
	
	switch(type)
	{
		case VFL_TYPE_GRABBER:
			base=0;
			end=64;
			name_base = "video";
			break;
		case VFL_TYPE_VTX:
			base=192;
			end=224;
			name_base = "vtx";
			break;
		case VFL_TYPE_VBI:
			base=224;
			end=240;
			name_base = "vbi";
			break;
		case VFL_TYPE_RADIO:
			base=64;
			end=128;
			name_base = "radio";
			break;
		default:
			return -1;
	}

Linus Torvalds's avatar
Linus Torvalds committed
287
	/* pick a minor number */
Gerd Knorr's avatar
Gerd Knorr committed
288
	down(&videodev_lock);
Linus Torvalds's avatar
Linus Torvalds committed
289 290 291 292 293 294
	if (-1 == nr) {
		/* use first free */
		for(i=base;i<end;i++)
			if (NULL == video_device[i])
				break;
		if (i == end) {
Gerd Knorr's avatar
Gerd Knorr committed
295
			up(&videodev_lock);
Linus Torvalds's avatar
Linus Torvalds committed
296 297 298 299 300 301
			return -ENFILE;
		}
	} else {
		/* use the one the driver asked for */
		i = base+nr;
		if (NULL != video_device[i]) {
Gerd Knorr's avatar
Gerd Knorr committed
302
			up(&videodev_lock);
Linus Torvalds's avatar
Linus Torvalds committed
303 304 305 306 307
			return -ENFILE;
		}
	}
	video_device[i]=vfd;
	vfd->minor=i;
Gerd Knorr's avatar
Gerd Knorr committed
308 309
	up(&videodev_lock);

Christoph Hellwig's avatar
Christoph Hellwig committed
310
	sprintf(vfd->devfs_name, "v4l/%s%d", name_base, i - base);
311 312
	devfs_mk_cdev(MKDEV(VIDEO_MAJOR, vfd->minor),
			S_IFCHR | S_IRUSR | S_IWUSR, vfd->devfs_name);
Gerd Knorr's avatar
Gerd Knorr committed
313
	init_MUTEX(&vfd->lock);
Christoph Hellwig's avatar
Christoph Hellwig committed
314

Gerd Knorr's avatar
Gerd Knorr committed
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
	/* sysfs class */
        memset(&vfd->class_dev, 0x00, sizeof(vfd->class_dev));
	if (vfd->dev)
		vfd->class_dev.dev = vfd->dev;
	vfd->class_dev.class       = &video_class;
	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,
				 &class_device_attr_name);
	class_device_create_file(&vfd->class_dev,
				 &class_device_attr_dev);

#if 1 /* needed until all drivers are fixed */
	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
Linus Torvalds's avatar
Linus Torvalds committed
333
	return 0;
Linus Torvalds's avatar
Linus Torvalds committed
334 335 336 337 338 339 340 341 342 343 344 345
}

/**
 *	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.
 */
 
void video_unregister_device(struct video_device *vfd)
{
Gerd Knorr's avatar
Gerd Knorr committed
346
	down(&videodev_lock);
Linus Torvalds's avatar
Linus Torvalds committed
347
	if(video_device[vfd->minor]!=vfd)
Gerd Knorr's avatar
Gerd Knorr committed
348
		panic("videodev: bad unregister");
Linus Torvalds's avatar
Linus Torvalds committed
349

Christoph Hellwig's avatar
Christoph Hellwig committed
350
	devfs_remove(vfd->devfs_name);
Linus Torvalds's avatar
Linus Torvalds committed
351
	video_device[vfd->minor]=NULL;
352
	class_device_unregister(&vfd->class_dev);
Gerd Knorr's avatar
Gerd Knorr committed
353
	up(&videodev_lock);
Linus Torvalds's avatar
Linus Torvalds committed
354 355 356 357 358
}


static struct file_operations video_fops=
{
359 360 361
	.owner		= THIS_MODULE,
	.llseek		= no_llseek,
	.open		= video_open,
Linus Torvalds's avatar
Linus Torvalds committed
362 363 364 365 366 367
};

/*
 *	Initialise video for linux
 */
 
Linus Torvalds's avatar
Linus Torvalds committed
368
static int __init videodev_init(void)
Linus Torvalds's avatar
Linus Torvalds committed
369 370
{
	printk(KERN_INFO "Linux video capture interface: v1.00\n");
Gerd Knorr's avatar
Gerd Knorr committed
371
	if (register_chrdev(VIDEO_MAJOR,VIDEO_NAME, &video_fops)) {
Linus Torvalds's avatar
Linus Torvalds committed
372 373 374
		printk("video_dev: unable to get major %d\n", VIDEO_MAJOR);
		return -EIO;
	}
Gerd Knorr's avatar
Gerd Knorr committed
375
	class_register(&video_class);
Linus Torvalds's avatar
Linus Torvalds committed
376 377 378
	return 0;
}

Linus Torvalds's avatar
Linus Torvalds committed
379
static void __exit videodev_exit(void)
Linus Torvalds's avatar
Linus Torvalds committed
380
{
Gerd Knorr's avatar
Gerd Knorr committed
381 382
	class_unregister(&video_class);
	unregister_chrdev(VIDEO_MAJOR, VIDEO_NAME);
Linus Torvalds's avatar
Linus Torvalds committed
383 384
}

Linus Torvalds's avatar
Linus Torvalds committed
385 386
module_init(videodev_init)
module_exit(videodev_exit)
Linus Torvalds's avatar
Linus Torvalds committed
387 388 389

EXPORT_SYMBOL(video_register_device);
EXPORT_SYMBOL(video_unregister_device);
Gerd Knorr's avatar
Gerd Knorr committed
390
EXPORT_SYMBOL(video_devdata);
391
EXPORT_SYMBOL(video_usercopy);
Gerd Knorr's avatar
Gerd Knorr committed
392 393
EXPORT_SYMBOL(video_exclusive_open);
EXPORT_SYMBOL(video_exclusive_release);
Gerd Knorr's avatar
Gerd Knorr committed
394 395
EXPORT_SYMBOL(video_device_alloc);
EXPORT_SYMBOL(video_device_release);
Linus Torvalds's avatar
Linus Torvalds committed
396 397 398

MODULE_AUTHOR("Alan Cox");
MODULE_DESCRIPTION("Device registrar for Video4Linux drivers");
Linus Torvalds's avatar
Linus Torvalds committed
399 400
MODULE_LICENSE("GPL");

Linus Torvalds's avatar
Linus Torvalds committed
401 402 403 404 405 406

/*
 * Local variables:
 * c-basic-offset: 8
 * End:
 */