Hello, Attached to this message is a patch against 2.4.0-test8 with my video4linux loopback driver. I makes two video devices for each pipe created, one for input using write and one for output using read or mmap. Jeroen
diff -ruN linux/CREDITS linux-2.4.0-test8/CREDITS --- linux/CREDITS Fri Sep 8 21:38:00 2000 +++ linux-2.4.0-test8/CREDITS Mon Sep 11 17:10:51 2000 @@ -2734,6 +2734,14 @@ S: 1098 VA Amsterdam S: The Netherlands +N: Jeroen Vreeken +E: [EMAIL PROTECTED] +W: http://www.chello.nl/~j.vreeken/ +D: Video4linux loopback driver +S: Maastrichterweg 63 +S: 5554 GG Valkenswaard +S: The Netherlands + N: Peter Shaobo Wang E: [EMAIL PROTECTED] W: http://www.mmdcorp.com/pw/linux diff -ruN linux/drivers/media/video/Config.in linux-2.4.0-test8/drivers/media/video/Config.in --- linux/drivers/media/video/Config.in Wed Aug 23 23:59:55 2000 +++ linux-2.4.0-test8/drivers/media/video/Config.in Mon Sep 11 17:10:51 2000 @@ -38,6 +38,7 @@ fi dep_tristate ' Stradis 4:2:2 MPEG-2 video driver (EXPERIMENTAL)' CONFIG_VIDEO_STRADIS $CONFIG_VIDEO_DEV $CONFIG_PCI fi +dep_tristate ' Video For Linux loopback' CONFIG_VIDEO_VLOOPBACK $CONFIG_VIDEO_DEV dep_tristate ' Zoran ZR36057/36060 Video For Linux' CONFIG_VIDEO_ZORAN $CONFIG_VIDEO_DEV $CONFIG_PCI $CONFIG_I2C dep_tristate ' Include support for Iomega Buz' CONFIG_VIDEO_BUZ $CONFIG_VIDEO_ZORAN dep_tristate ' Zoran ZR36120/36125 Video For Linux' CONFIG_VIDEO_ZR36120 $CONFIG_VIDEO_DEV $CONFIG_PCI $CONFIG_I2C diff -ruN linux/drivers/media/video/Makefile linux-2.4.0-test8/drivers/media/video/Makefile --- linux/drivers/media/video/Makefile Tue Aug 22 20:29:02 2000 +++ linux-2.4.0-test8/drivers/media/video/Makefile Mon Sep 11 17:10:51 2000 @@ -57,6 +57,7 @@ obj-$(CONFIG_VIDEO_CPIA) += cpia.o obj-$(CONFIG_VIDEO_CPIA_PP) += cpia_pp.o obj-$(CONFIG_VIDEO_CPIA_USB) += cpia_usb.o +obj-$(CONFIG_VIDEO_VLOOPBACK) += vloopback.o obj-$(CONFIG_TUNER_3036) += tuner-3036.o # Extract lists of the multi-part drivers. diff -ruN linux/drivers/media/video/vloopback.c linux-2.4.0-test8/drivers/media/video/vloopback.c --- linux/drivers/media/video/vloopback.c Thu Jan 1 01:00:00 1970 +++ linux-2.4.0-test8/drivers/media/video/vloopback.c Mon Sep 11 17:11:12 2000 @@ -0,0 +1,816 @@ +/* + * vloopback.c + * + * Copyright Jeroen Vreeken ([EMAIL PROTECTED]), 2000 + * + * Published under the GNU Public License. + * + * + * UPDATED: Jeroen Vreeken. + * Added locks for smp machines. UNTESTED! + * Made the driver much more cpu friendly by using + * a wait queue. + * Went from vmalloc to rvmalloc (yes, I stole the code + * like everybody else) and implemented mmap. + * Implemented VIDIOCGUNIT and removed size/palette checks + * in VIDIOCSYNC. + * Cleaned up a lot of code. + * Changed locks to semaphores. + * Disabled changing size while somebody is using mmap + * Changed mapped check to open check, also don't allow + * a open for write while somebody is reading. + * Added /proc support + */ +#define VLNAME "vloopback: " +#define VLVER "0.7" + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/malloc.h> +#include <linux/vmalloc.h> +#include <linux/wrapper.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/videodev.h> +#include <linux/spinlock.h> +#include <linux/init.h> +#include <linux/proc_fs.h> + +struct vloopback_device { + struct video_device viddev; + int pipenr; + int in; +}; + +struct vloopback_pipe { + struct vloopback_device *vloopin; + struct vloopback_device *vloopout; + char *buffer; + unsigned long buflength; + unsigned int width, height; + unsigned int palette; + unsigned long frameswrite; + unsigned long framesread; + unsigned long framesdumped; + unsigned int wopen; + unsigned int ropen; + struct proc_dir_entry *proc_entry; + struct semaphore lock; + wait_queue_head_t wait; +}; + +#define MAX_PIPES 16 +static struct vloopback_pipe *loops[MAX_PIPES]; +static int nr_o_pipes=0; +static int pipes=-1; + + +/********************************************************************** + * + * Memory management + * + * This is a shameless copy from the USB-cpia driver (linux kernel + * version 2.3.29 or so, I have no idea what this code actually does ;). + * Actually it seems to be a copy of a shameless copy of the bttv-driver. + * Or that is a copy of a shameless copy of ... (To the powers: is there + * no generic kernel-function to do this sort of stuff?) + * + * Yes, it was a shameless copy from the bttv-driver. IIRC, Alan says + * there will be one, but apparentely not yet -jerdfelt + * + * So I copied it again for the OV511 driver -claudio + * + * And it gets copied and copied and copied..... -Jeroen + * + **********************************************************************/ + +/* Given PGD from the address space's page table, return the kernel + * virtual mapping of the physical memory mapped at ADR. + */ +static inline unsigned long uvirt_to_kva(pgd_t *pgd, unsigned long adr) +{ + unsigned long ret = 0UL; + pmd_t *pmd; + pte_t *ptep, pte; + + if (!pgd_none(*pgd)) { + pmd = pmd_offset(pgd, adr); + if (!pmd_none(*pmd)) { + ptep = pte_offset(pmd, adr); + pte = *ptep; + if (pte_present(pte)) { + ret = (unsigned long) page_address(pte_page(pte)); + ret |= (adr & (PAGE_SIZE - 1)); + } + } + } + + return ret; +} + +/* Here we want the physical address of the memory. + * This is used when initializing the contents of the + * area and marking the pages as reserved. + */ +static inline unsigned long kvirt_to_pa(unsigned long adr) +{ + unsigned long va, kva, ret; + + va = VMALLOC_VMADDR(adr); + kva = uvirt_to_kva(pgd_offset_k(va), va); + ret = __pa(kva); + return ret; +} + +static void *rvmalloc(unsigned long size) +{ + void *mem; + unsigned long adr, page; + + /* Round it off to PAGE_SIZE */ + size += (PAGE_SIZE - 1); + size &= ~(PAGE_SIZE - 1); + + mem = vmalloc_32(size); + if (!mem) + return NULL; + + memset(mem, 0, size); /* Clear the ram out, no junk to the user */ + adr = (unsigned long) mem; + while (size > 0) { + page = kvirt_to_pa(adr); + mem_map_reserve(virt_to_page(__va(page))); + adr += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + + return mem; +} + +static void rvfree(void *mem, unsigned long size) +{ + unsigned long adr, page; + + if (!mem) + return; + + size += (PAGE_SIZE - 1); + size &= ~(PAGE_SIZE - 1); + + adr=(unsigned long) mem; + while (size > 0) { + page = kvirt_to_pa(adr); + mem_map_unreserve(virt_to_page(__va(page))); + adr += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + vfree(mem); +} + +/********************************************************************** + * + * End of memory stuff, the rest is mine :) + * + **********************************************************************/ + + +static int vloopback_open(struct video_device *dev, int flags) +{ + struct vloopback_device *loopdev=(struct vloopback_device *)dev; + int nr=loopdev->pipenr; + + /* Only allow a output to be opened if there is someone feeding + * the pipe. + */ + if (!loopdev->in) { + if (loops[nr]->buffer==NULL) { + return -EINVAL; + } + loops[nr]->framesread=0; + loops[nr]->ropen=1; + } else { + if (loops[nr]->ropen) + return -EBUSY; + loops[nr]->frameswrite=0; + loops[nr]->wopen=1; + } + + MOD_INC_USE_COUNT; + return 0; +} + +static void vloopback_close(struct video_device *dev) +{ + struct vloopback_device *loopdev=(struct vloopback_device *)dev; + int nr=loopdev->pipenr; + + if (loopdev->in) { + down(&loops[nr]->lock); + if (loops[nr]->buffer) { + rvfree(loops[nr]->buffer, + loops[nr]->buflength); + loops[nr]->buffer=NULL; + } + up(&loops[nr]->lock); + if (waitqueue_active(&loops[nr]->wait)) + wake_up(&loops[nr]->wait); + + loops[nr]->width=0; + loops[nr]->height=0; + loops[nr]->palette=0; + printk (VLNAME "frames written: %ld, frames dumped: %ld\n", + loops[nr]->frameswrite, + loops[nr]->framesdumped); + loops[nr]->wopen=0; + } else { + loops[nr]->ropen=0; + printk (VLNAME "frames read: %ld\n", + loops[nr]->framesread); + } + + MOD_DEC_USE_COUNT; +} + +static int vloopback_init_done(struct video_device *dev) +{ + return 0; +} + +static long vloopback_write(struct video_device *v, const char *buf, unsigned long +count, int noblock) +{ + struct vloopback_device *loopdev=(struct vloopback_device *)v; + int nr=loopdev->pipenr; + unsigned long realcount=count; + + if (!loopdev->in) return -EINVAL; + + if (loops[nr]->buffer==NULL) { + return -EINVAL; + } + + /* Anybody want some pictures??? */ + if (!waitqueue_active(&loops[nr]->wait)) { + loops[nr]->framesdumped++; + return realcount; + } + + down(&loops[nr]->lock); + if (!loops[nr]->buffer) { + up(&loops[nr]->lock); + return -EINVAL; + } + if (realcount > loops[nr]->buflength) { + realcount = loops[nr]->buflength; + printk( VLNAME "To much data! Only %ld bytes used.\n", realcount); + } + + copy_from_user(loops[nr]->buffer, buf, realcount); + up(&loops[nr]->lock); + + wake_up(&loops[nr]->wait); + loops[nr]->frameswrite++; + + return realcount; +} + +static long vloopback_read(struct video_device *v, char *buf, unsigned long count, +int noblock) +{ + struct vloopback_device *loopdev=(struct vloopback_device *)v; + int nr=loopdev->pipenr; + unsigned long realcount=count; + + if (loopdev->in) return -EINVAL; + + if (realcount > loops[nr]->buflength) { + realcount = loops[nr]->buflength; + printk( VLNAME "Not so much data in buffer!\n"); + } + + interruptible_sleep_on(&loops[nr]->wait); + + down(&loops[nr]->lock); + if (!loops[nr]->buffer) { + up(&loops[nr]->lock); + return 0; + } + copy_to_user(buf, loops[nr]->buffer, realcount); + up(&loops[nr]->lock); + + loops[nr]->framesread++; + return realcount; +} + +static int vloopback_mmap(struct video_device *dev, const char *adr, unsigned long +size) +{ + struct vloopback_device *loopdev=(struct vloopback_device *)dev; + int nr=loopdev->pipenr; + unsigned long start = (unsigned long)adr; + unsigned long page, pos; + + down(&loops[nr]->lock); + if (loops[nr]->buffer == NULL) { + up(&loops[nr]->lock); + return -EINVAL; + } + + printk(VLNAME "mmap: %ld (%lX) bytes\n", size, size); + + if (size > (((2 * loops[nr]->buflength) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))) +{ + up(&loops[nr]->lock); + return -EINVAL; + } + + pos = (unsigned long)loops[nr]->buffer; + while (size > 0) { + page = kvirt_to_pa(pos); + if (remap_page_range(start, page, PAGE_SIZE, PAGE_SHARED)) { + up(&loops[nr]->lock); + return -EAGAIN; + } + start += PAGE_SIZE; + pos += PAGE_SIZE; + if (size > PAGE_SIZE) + size -= PAGE_SIZE; + else + size = 0; + } + up(&loops[nr]->lock); + + return 0; +} + +static int vloopback_ioctl(struct video_device *dev, unsigned int cmd, void *arg) +{ + struct vloopback_device *loopdev=(struct vloopback_device *)dev; + int nr=loopdev->pipenr; + + switch(cmd) + { + case VIDIOCGCAP: + { + struct video_capability b; + if (loopdev->in) { + sprintf(b.name, "Video loopback %d input", + loopdev->pipenr); + b.type = 0; + } else { + sprintf(b.name, "Video loopback %d output", + loopdev->pipenr); + b.type = VID_TYPE_CAPTURE; + } + b.channels=1; + b.audios=0; + b.maxwidth=loops[nr]->width; + b.maxheight=loops[nr]->height; + b.minwidth=loops[nr]->width; + b.minheight=loops[nr]->height; + if(copy_to_user(arg, &b, sizeof(b))) + return -EFAULT; + return 0; + } + case VIDIOCGCHAN: + { + struct video_channel v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v.channel!=0) + return -EINVAL; + v.flags=0; + v.tuners=0; + v.type = VIDEO_TYPE_CAMERA; + strcpy(v.name, "Loopback"); + if(copy_to_user(arg, &v, sizeof(v))) + return -EFAULT; + return 0; + } + case VIDIOCSCHAN: + { + int v; + if(copy_from_user(&v, arg, sizeof(v))) + return -EFAULT; + if(v!=0) + return -EINVAL; + return 0; + } + case VIDIOCGTUNER: + { + struct video_tuner v; + if(copy_from_user(&v, arg, sizeof(v))!=0) + return -EFAULT; + if(v.tuner) + return -EINVAL; + strcpy(v.name, "Format"); + v.rangelow=0; + v.rangehigh=0; + v.flags=0; + v.mode=VIDEO_MODE_AUTO; + if(copy_to_user(arg,&v, sizeof(v))!=0) + return -EFAULT; + return 0; + } + case VIDIOCGPICT: + { + struct video_picture p; + p.colour=0x8000; + p.hue=0x8000; + p.brightness=0x8000; + p.contrast=0x8000; + p.whiteness=0x8000; + p.depth=0x8000; + p.palette=loops[nr]->palette; + if(copy_to_user(arg, &p, sizeof(p))) + return -EFAULT; + return 0; + + } + case VIDIOCSPICT: + { + struct video_picture p; + if(copy_from_user(&p, arg, sizeof(p))) + return -EFAULT; + if (!loopdev->in) { + if (p.palette!=loops[nr]->palette) + return -EINVAL; + } else + loops[nr]->palette=p.palette; + return 0; + } + case VIDIOCSWIN: + { + struct video_window vw; + + if(copy_from_user(&vw, arg, sizeof(vw))) + return -EFAULT; + if(vw.flags) + return -EINVAL; + if(vw.clipcount) + return -EINVAL; + if (loops[nr]->height==vw.height && + loops[nr]->width==vw.width) + return 0; + if(!loopdev->in) { + return -EINVAL; + } else { + loops[nr]->height=vw.height; + loops[nr]->width=vw.width; + /* Make sure nobody is using the buffer while we + fool around with it. + We are also not allowing changes while + somebody using mmap has the output open. + */ + down(&loops[nr]->lock); + if (loops[nr]->ropen) { + printk(VLNAME "Can't change size while opened +for read\n"); + up(&loops[nr]->lock); + return -EINVAL; + } + if (loops[nr]->buffer) + rvfree(loops[nr]->buffer, +loops[nr]->buflength); + loops[nr]->buflength=vw.width*vw.height*4; + loops[nr]->buffer=rvmalloc(loops[nr]->buflength); + up(&loops[nr]->lock); + } + return 0; + } + case VIDIOCGWIN: + { + struct video_window vw; + vw.x=0; + vw.y=0; + vw.width=loops[nr]->width; + vw.height=loops[nr]->height; + vw.chromakey=0; + vw.flags=0; + vw.clipcount=0; + if(copy_to_user(arg, &vw, sizeof(vw))) + return -EFAULT; + return 0; + } + case VIDIOCGMBUF: + { + struct video_mbuf vm; + + if (loopdev->in) + return -EINVAL; + vm.size=loops[nr]->buflength; + vm.frames=1; + vm.offsets[0]=0; + if(copy_to_user(arg, &vm, sizeof(vm))) + return -EFAULT; + return 0; + } + case VIDIOCMCAPTURE: + { + struct video_mmap vm; + + if (loopdev->in) + return -EINVAL; + if (!loops[nr]->buffer) + return -EINVAL; + if (copy_from_user(&vm, arg, sizeof(vm))) + return -EFAULT; + if (vm.height!=loops[nr]->height || + vm.width!=loops[nr]->width || + vm.format!=loops[nr]->palette) { + printk (VLNAME "heigth: %d ioctl: %d\n" + VLNAME "width: %d ioctl: %d\n" + VLNAME "palette:%d ioctl: %d\n", + loops[nr]->height, vm.height, + loops[nr]->width, vm.width, + loops[nr]->palette, vm.format); + + return -EINVAL; + } + return 0; + } + case VIDIOCSYNC: + { + if (loopdev->in) + return -EINVAL; + if (!loops[nr]->buffer) + return -EINVAL; + /* Ok, everything should be alright since the program + should have called VIDIOMCAPTURE and we are ready to + do the 'capturing' */ + interruptible_sleep_on(&loops[nr]->wait); + loops[nr]->framesread++; + + return 0; + } + case VIDIOCGUNIT: + { + struct video_unit vu; + + if (loopdev->in) + vu.video=loops[nr]->vloopout->viddev.minor; + else + vu.video=loops[nr]->vloopin->viddev.minor; + vu.vbi=VIDEO_NO_UNIT; + vu.radio=VIDEO_NO_UNIT; + vu.audio=VIDEO_NO_UNIT; + vu.teletext=VIDEO_NO_UNIT; + if (copy_to_user(arg, &vu, sizeof(vu))) + return -EFAULT; + return 0; + } + case VIDIOCCAPTURE: + case VIDIOCGFBUF: + case VIDIOCGFREQ: + case VIDIOCSFREQ: + case VIDIOCGAUDIO: + case VIDIOCSAUDIO: + return -EINVAL; + case VIDIOCKEY: + return 0; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static struct video_device vloopback_template= +{ + "Video Loopback", + VID_TYPE_CAPTURE, + 0, + vloopback_open, + vloopback_close, + vloopback_read, + vloopback_write, + NULL, + vloopback_ioctl, + vloopback_mmap, + vloopback_init_done, + NULL, + 0, + 0 +}; + +/**************************************************************************** + * /proc interface + * Based on the ov511 driver + ****************************************************************************/ + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + +static struct proc_dir_entry *vloopback_proc_entry = NULL; +extern struct proc_dir_entry *video_proc_entry; + +#define YES_NO(x) ((x) ? "yes" : "no") + +static int vloopback_read_proc(char *page, char **start, off_t off, + int count, int *eof, void *data) +{ + char *out=page; + int len; + struct vloopback_pipe *pipe=data; + + out += sprintf (out, "driver : "VLVER"\n"); + out += sprintf (out, "input : video%d\n", pipe->vloopin->viddev.minor); + out += sprintf (out, "output : video%d\n", pipe->vloopout->viddev.minor); + out += sprintf (out, "writing : %s\n", YES_NO (pipe->wopen)); + if (pipe->wopen) { + out += sprintf (out, "written : %ld\n", pipe->frameswrite); + out += sprintf (out, "dumped : %ld\n", pipe->framesdumped); + out += sprintf (out, "reading : %s\n", YES_NO (pipe->ropen)); + if (pipe->ropen) + out += sprintf (out, "read : %ld\n", pipe->framesread); + } + + len = out - page; + len -= off; + if (len < count) { + *eof=1; + if (len <= 0) return 0; + } else + len = count; + + *start = page + off; + + return len; +} + +static int vloopback_write_proc(struct file *file, const char *buffer, + unsigned long count, void *data) +{ + return -EINVAL; +} + +static void create_proc_vloopback (int nr) +{ + char name[16]; + struct proc_dir_entry *ent; + + if (!vloopback_proc_entry) + return; + + sprintf(name, "vloopback%d", nr); + + ent=create_proc_entry(name, S_IFREG | S_IRUGO | S_IWUSR, vloopback_proc_entry); + + if (!ent) { + loops[nr]->proc_entry=NULL; + return; + } + + ent->data = loops[nr]; + ent->read_proc = vloopback_read_proc; + ent->write_proc = vloopback_write_proc; + loops[nr]->proc_entry = ent; +} + +static void destroy_proc_vloopback (int nr) +{ + char name[16]; + + if (!loops[nr]->proc_entry) + return; + + sprintf(name, "vloopback%d", nr); + remove_proc_entry(name, vloopback_proc_entry); + loops[nr]->proc_entry=NULL; +} + +static void proc_vloopback_create(void) +{ + if (video_proc_entry == NULL) { + printk(VLNAME "Unable to initialise /proc/video/vloopback"); + return; + } + + vloopback_proc_entry=create_proc_entry("vloopback", S_IFDIR, video_proc_entry); + + if (vloopback_proc_entry) + vloopback_proc_entry->owner = THIS_MODULE; + else + printk(VLNAME "Unable to initialise /proc/video/vloopback"); +} + +static void proc_vloopback_destroy(void) +{ + if (vloopback_proc_entry == NULL) + return; + + remove_proc_entry("vloopback", video_proc_entry); +} +#endif /* CONFIG_PROC_FS && CONFIG_VIDEO_PROC_FS */ + + +/**************************************************************************** + * init stuff + ****************************************************************************/ + +static int create_pipe(int nr) +{ + loops[nr]= kmalloc(sizeof(struct vloopback_pipe), GFP_KERNEL); + loops[nr]->vloopin= kmalloc(sizeof(struct vloopback_device), GFP_KERNEL); + loops[nr]->vloopout= kmalloc(sizeof(struct vloopback_device), GFP_KERNEL); + loops[nr]->vloopin->pipenr=nr; + loops[nr]->vloopout->pipenr=nr; + loops[nr]->buffer=NULL; + loops[nr]->width=0; + loops[nr]->height=0; + loops[nr]->palette=0; + loops[nr]->frameswrite=0; + loops[nr]->framesread=0; + loops[nr]->framesdumped=0; + loops[nr]->wopen=0; + loops[nr]->ropen=0; + memcpy(loops[nr]->vloopin, &vloopback_template, sizeof(vloopback_template)); + memcpy(loops[nr]->vloopout, &vloopback_template, sizeof(vloopback_template)); + loops[nr]->vloopin->in=1; + loops[nr]->vloopout->in=0; + ((struct video_device *)loops[nr]->vloopin)->type=0; + sprintf(((struct video_device *)loops[nr]->vloopin)->name, "Video loopback %d +input", nr); + ((struct video_device *)loops[nr]->vloopout)->type=VID_TYPE_CAPTURE; + sprintf(((struct video_device *)loops[nr]->vloopout)->name, "Video loopback %d +output", nr); + init_waitqueue_head(&loops[nr]->wait); + init_MUTEX(&loops[nr]->lock); + if (video_register_device(&loops[nr]->vloopin->viddev, VFL_TYPE_GRABBER)==-1) { + kfree(loops[nr]->vloopin); + kfree(loops[nr]->vloopout); + kfree(loops[nr]); + loops[nr]=NULL; + printk(VLNAME "error registering device\n"); + return -ENODEV; + } + if (video_register_device(&loops[nr]->vloopout->viddev, VFL_TYPE_GRABBER)==-1) +{ + video_unregister_device(&loops[nr]->vloopin->viddev); + kfree(loops[nr]->vloopin); + kfree(loops[nr]->vloopout); + kfree(loops[nr]); + loops[nr]=NULL; + printk(VLNAME "error registering device\n"); + return -ENODEV; + } +#if defined(CONFIG_PROC_FS) && defined (CONFIG_VIDEO_PROC_FS) + create_proc_vloopback(nr); +#endif + return 0; +} + +MODULE_AUTHOR("J.B. Vreeken ([EMAIL PROTECTED])"); +MODULE_DESCRIPTION("Video4linux loopback device."); +MODULE_PARM(pipes, "i"); +MODULE_PARM_DESC(pipes, "Nr of pipes to create (each pipe uses two video devices)"); + +EXPORT_NO_SYMBOLS; + +static int __init vloopback_init(void) +{ + int i; + + printk(VLNAME "Video4linux loopback driver v"VLVER"\n"); + printk(VLNAME "Written by Jeroen Vreeken, 2000 ([EMAIL PROTECTED])\n"); + + if (pipes==-1) pipes=1; + if (pipes > MAX_PIPES) { + pipes=MAX_PIPES; + printk(VLNAME "Nr of pipes is limited to: %d\n", MAX_PIPES); + } + +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + proc_vloopback_create(); +#endif + + printk(VLNAME "Registering devices\n"); + for (i=0; i<pipes; i++) { + if (!create_pipe(i)) { + printk(VLNAME "Loopback %d registered, input: video%d, output: +video%d\n", + i, loops[i]->vloopin->viddev.minor, +loops[i]->vloopout->viddev.minor); + nr_o_pipes=i+1; + } + } + return 0; +} + +static void __exit cleanup_vloopback_module(void) +{ + int i; + + printk(VLNAME "Unregistering video4linux loopback devices\n"); + for (i=0; i<nr_o_pipes; i++) if (loops[i]) { +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + destroy_proc_vloopback(i); +#endif + video_unregister_device(&loops[i]->vloopin->viddev); + kfree(loops[i]->vloopin); + video_unregister_device(&loops[i]->vloopout->viddev); + kfree(loops[i]->vloopout); + if (loops[i]->buffer) rvfree(loops[i]->buffer, loops[i]->buflength); + kfree(loops[i]); + } +#if defined(CONFIG_PROC_FS) && defined(CONFIG_VIDEO_PROC_FS) + proc_vloopback_destroy(); +#endif + printk(VLNAME "devices unregistered\n"); +} + +module_init(vloopback_init); +module_exit(cleanup_vloopback_module);