diff vloopback.c @ 0:5f21a4dddc0c

Initial checkin
author KennethLavrsen
date Sun, 01 Apr 2007 05:22:43 +0000
parents
children dc1f4ad7010c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vloopback.c	Sun Apr 01 05:22:43 2007 +0000
@@ -0,0 +1,1136 @@
+/*
+ *	vloopback.c
+ *
+ *	Copyright Jeroen Vreeken (pe1rxq@amsat.org), 2000
+ *	Additional copyright by the contributing authors in the
+ *	change history below, 2000-2007
+ *
+ *	Published under the GNU Public License.
+ *
+ *	The Video Loopback Device is no longer systematically maintained.
+ *	The project is a secondary project for the project "motion" found at
+ *	http://motion.sourceforge.net/ and
+ *	http://www.lavrsen.dk/twiki/bin/view/Motion/WebHome
+ *	and with the vloopback stored at
+ *	http://www.lavrsen.dk/twiki/bin/view/Motion/VideoFourLinuxLoopbackDevice
+ *
+ *	CHANGE HISTORY
+ *
+ *	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
+ *			Set dumped count to zero at open.
+ *			Modified /proc layout (added vloopbacks entry)
+ *
+ * 05.10.00 (MTS)	Added Linux 2.2 support
+ * 06.10.00 (J Vreeken)	Fixed 2.2 support to make things work under 2.4 again.
+ * 17.10.00 (J Vreeken)	Added zero copy mode
+ * 19.10.00 (J Vreeken) Added SIGIO on device close.
+ * 24.10.00 (J Vreeken) Modified 2.2 stuff and removed spinlock.h
+ *			released 0.81
+ * 27.10.00 (J Vreeken) Implemented poll
+ *			released 0.82
+ * 17.01.01 (J Vreeken) support for xawtv
+ * 			Implemented VIDIOCGFBUF
+ *			Additional checks on framebuffer freeing.
+ *			released 0.83
+ * 31.01.01 (J Vreeken)	Removed need for 'struct ioctl', use _IOC_SIZE() and
+ *			IOC_IN instead.
+ *			Change the ioctlnr passing to 'unsigned long int'
+ *			Instead of just one byte.
+ *			THIS BREAKS COMPATIBILITY WITH PREVIOUS VERSIONS!!! 
+ * 29.06.01 (J Vreeken)	Added dev_offset module option
+ *			Made vloopback_template sane
+ *			Added double buffering support
+ *			Made vloopback less verbose
+ * 20.11.01	(tibit)	Made dev_offset option sane
+ *			"Fixed" zerocopy mode by defining the ioctl 
+ *			VIDIOCSINVALID. An application which provides data
+ *			has to issue it when it encounters an error in
+ *			ioctl processing. See dummy.c for examples.
+ * 26.11.03	(Kenneth Lavrsen)
+ *			released 0.91
+ *			0.91 is the combination of the 0.90-tibit by
+ *			Tilmann Bitterberg and an update of the Makefile by
+ *			Roberto Carvajal.
+ * 23.01.05	(W Brack)
+ *			(don't know what happened to the comments for 0.92
+ *			 and 0.93, but I tentatively named this one as 0.99)
+ *			enhanced for linux-2.6, with #ifdef to keep it
+ *			compatible with linux-2.4.  For linux versions
+ *			> 2.5, I changed the memory management
+ *			routines to the "more modern" way, most of it
+ *			shamelessly copied from other drivers.  I also
+ *			added in the code necessary to avoid the "videodev
+ *			has no release callback" message when installing.
+ *			For versions < 2.5, I updated the routines to be
+ *			closer to several other drivers.
+ *
+ * 04.02.05	(Angel Carpintero)
+ *			Fixed version number to 0.93-pre1.
+ *			Fixed warning for interruptible_sleep_on() deprecated and added 
+ *			wait_event_interruptible compatible with 2.6.x and 2.7.
+ *			Fixed memory manager for kernel version > 2.6.9.
+ *
+ * 07.02.05	(Kenneth Lavrsen)
+ *			Changed version to 0.94.
+ *			Released as formal released version
+ *
+ * 20.02.05	(W Brack)
+ *			Fixed error with wait_event_interruptible.
+ *			Fixed crash when pipe source was stopped before dest.
+ *
+ * 20.02.05	(Angel Carpintero)     
+ *			Added install and uninstall in Makefile.
+ *
+ *
+ * 25.04.05	(Angel Carpintero)
+ *			Included Samuel Audet's patch, it checks if the input is already
+ *			opened in write mode.
+ *
+ * 02.05.05	(Kenneth Lavrsen)
+ *			Released 0.95-snap2 formerly as 0.95
+ *	
+ * 10.05.05	(Angel Carpintero)
+ *			Added MODULE_VERSION(), fixed create_pipes when video_register_device() returns
+ *			-ENFILE . 
+ *			Fix warnings about checking return value from copy_to_user() and copy_from_user() functions.
+ *
+ * 14.11.05	(Angel Carpintero)
+ *			Added <linux/version.h> that includes LINUX_VERSION_CODE and KERNEL_VERSION to fix 
+ *			compilation agains kernel 2.6.14 , change version to 0.97-snap1
+ *
+ * 19.12.05	(Angel Carpintero)
+ *			Added to example option to choose between rgb24 or yuv420p palettes.
+ *
+ * 31.12.05	(Angel Carpintero)
+ * 			Fixed examples, remove perror calls and add support to dummy.c for sysfs.
+ * 			
+ * 04.06.06	(Angel Carpintero)
+ * 			Add module_param() for kernel > 2.5 because MODULE_PARAM() macro is obsolete.
+ *
+ * 17.06.06	(Angel Carpintero)
+ *			Release version 1.0 with some fixes and code clean up. Added a Jack Bates contribution
+ *			to allow build a kernel module in debian way.
+ *
+ * 26.06.06	(Angel Carpintero)
+ *			Added some improvements in Makefile. Fix a problem to compile in Suse.
+ *
+ *
+ * 02.11.06	(Angel Carpintero)
+ * 			Make compatible with new kernel stable version 2.6.18, Many functions and declarations has
+ * 			been moved to media/v42l-dev.h and remove from videodev.h/videodev2.h
+ *
+ * 18.01.07	(Angel Carpintero)	
+ * 			Change -ENOIOCTLCMD by more appropiate error -ENOTTY.					
+ */
+
+
+#define VLOOPBACK_VERSION "1.1-rc1"
+
+/* Include files common to 2.4 and 2.6 versions */
+#include <linux/version.h>	/* >= 2.6.14 LINUX_VERSION_CODE */ 
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/pagemap.h>
+
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,18)
+#include <media/v4l2-common.h>
+#endif
+
+#include <linux/videodev.h>
+#include <linux/vmalloc.h>
+#include <linux/wait.h>
+
+/* Include files which are unique to versions */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+ #include <asm/ioctl.h>
+ #include <asm/page.h> 
+ #include <asm/pgtable.h>
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,10)
+  #ifndef	remap_pfn_range
+    #define	remap_pfn_range(a,b,c,d,e) \
+	 remap_page_range((a),(b),(c)<<PAGE_SHIFT,(d),(e))
+  #endif
+  #ifndef	vmalloc_to_pfn
+    #define	vmalloc_to_pfn(a) page_to_pfn(vmalloc_to_page((a)))
+  #endif
+ #endif
+ #include <asm/uaccess.h>
+ #include <linux/init.h>
+ #include <linux/device.h>
+#else
+ #include <linux/mm.h>
+ #include <linux/slab.h>
+ #include <linux/wrapper.h>
+ #include <asm/io.h>
+#endif
+ 
+#define VIDIOCSINVALID	_IO('v',BASE_VIDIOCPRIVATE+1)
+
+#define info(format, arg...) printk(KERN_INFO __FILE__ ": " format "\n" "", ## arg)
+
+struct vloopback_private {
+	int pipenr;
+	int in; /* bool */
+};
+typedef struct vloopback_private *priv_ptr;
+
+struct vloopback_pipe {
+	struct video_device *vloopin;
+ 	struct video_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 semaphore lock;
+	wait_queue_head_t wait;
+	unsigned int frame;
+	unsigned int pid;
+	unsigned int zerocopy;
+	unsigned long int ioctlnr;
+	unsigned int invalid_ioctl; /* 0 .. none invalid; 1 .. invalid */
+	unsigned int ioctllength;
+	char *ioctldata;
+	char *ioctlretdata;
+};
+
+#define MAX_PIPES 16
+#define	N_BUFFS	2	/* Number of buffers used for pipes */
+
+static struct vloopback_pipe *loops[MAX_PIPES];
+static int nr_o_pipes=0;
+static int pipes=-1;
+static int spares=0;
+static int pipesused=0;
+static int dev_offset=-1;
+
+/**********************************************************************
+ *
+ * Memory management - revised for 2.6 kernels
+ *
+ **********************************************************************/
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+/* 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 kva;
+
+	kva = (unsigned long)page_address(vmalloc_to_page((void *)adr));
+	kva |= adr & (PAGE_SIZE-1); /* restore the offset */
+	return __pa(kva);
+}
+#endif
+
+static void *rvmalloc(unsigned long size)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+	struct page *page;
+#endif
+	void *mem;
+	unsigned long adr;
+
+	size = PAGE_ALIGN(size);
+	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) {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+		page = vmalloc_to_page((void *)adr);
+		mem_map_reserve(page);
+#else
+		SetPageReserved(vmalloc_to_page((void *)adr));
+#endif
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+
+	return mem;
+}
+
+static void rvfree(void *mem, unsigned long size)
+{
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+	struct page *page;
+#endif
+	unsigned long adr;
+
+	if (!mem)
+		return;
+
+	adr = (unsigned long) mem;
+	while ((long) size > 0) {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,5,0)
+		page = vmalloc_to_page((void *)adr);
+		mem_map_unreserve(page);
+#else
+		ClearPageReserved(vmalloc_to_page((void *)adr));
+#endif
+		adr += PAGE_SIZE;
+		size -= PAGE_SIZE;
+	}
+	vfree(mem);
+}
+
+
+static int create_pipe(int nr);
+
+static int fake_ioctl(int nr, unsigned long int cmd, void *arg)
+{
+	unsigned long fw;
+	
+	loops[nr]->ioctlnr=cmd;
+	memcpy(loops[nr]->ioctldata, arg, _IOC_SIZE(cmd));
+	loops[nr]->ioctllength=_IOC_SIZE(cmd);
+	kill_proc(loops[nr]->pid, SIGIO, 1);	/* Signal the pipe feeder */
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+	fw = loops[nr]->frameswrite;
+	wait_event_interruptible(loops[nr]->wait, fw!=loops[nr]->frameswrite);
+#else
+	interruptible_sleep_on(&loops[nr]->wait);
+#endif	
+	if (cmd & IOC_IN) {
+		if (memcmp (arg, loops[nr]->ioctlretdata, _IOC_SIZE(cmd)))
+			return 1;
+	} else {
+		memcpy (arg, loops[nr]->ioctlretdata, _IOC_SIZE(cmd));
+	}
+	return 0;
+}
+
+static int vloopback_open(struct inode *inod, struct file *f)
+{	
+	struct video_device *loopdev=video_devdata(f);
+	priv_ptr ptr=(priv_ptr)loopdev->priv;
+	int nr=ptr->pipenr;
+	
+
+	/* Only allow a output to be opened if there is someone feeding
+	 * the pipe.
+	 */
+	if (!ptr->in) {
+		if (loops[nr]->buffer==NULL) {
+			return -EINVAL;
+		}
+		loops[nr]->framesread=0;
+		loops[nr]->ropen=1;
+	} else {
+		if (loops[nr]->ropen || loops[nr]->wopen) 
+			return -EBUSY;
+		loops[nr]->framesdumped=0;
+		loops[nr]->frameswrite=0;
+		loops[nr]->wopen=1;
+		loops[nr]->zerocopy=0;
+		loops[nr]->ioctlnr=-1;
+		pipesused++;
+		if (nr_o_pipes-pipesused<spares) {
+			if (!create_pipe(nr_o_pipes)) {
+				info("Creating extra spare pipe");
+				info("Loopback %d registered, input: video%d, output: video%d",
+				    nr_o_pipes,
+				    loops[nr_o_pipes]->vloopin->minor,
+				    loops[nr_o_pipes]->vloopout->minor
+				);
+				nr_o_pipes++;
+			}
+		}
+		loops[nr]->pid=current->pid;
+	}
+	return 0;
+}
+
+static int vloopback_release(struct inode * inod, struct file *f)
+{
+	struct video_device *loopdev=video_devdata(f);
+	priv_ptr ptr=(priv_ptr)loopdev->priv;
+	int nr=ptr->pipenr;
+	
+	if (ptr->in) {
+		down(&loops[nr]->lock);
+		if (loops[nr]->buffer && !loops[nr]->ropen) {
+			rvfree(loops[nr]->buffer,
+			    loops[nr]->buflength*N_BUFFS);
+			loops[nr]->buffer=NULL;
+		}
+		up(&loops[nr]->lock);
+		loops[nr]->frameswrite++;
+		if (waitqueue_active(&loops[nr]->wait))
+			wake_up(&loops[nr]->wait);
+
+		loops[nr]->width=0;
+		loops[nr]->height=0;
+		loops[nr]->palette=0;
+		loops[nr]->wopen=0;
+		pipesused--;
+	} else {
+		down(&loops[nr]->lock);
+		if (loops[nr]->buffer && !loops[nr]->wopen) {
+			rvfree(loops[nr]->buffer,
+			    loops[nr]->buflength*N_BUFFS);
+			loops[nr]->buffer=NULL;
+		}
+		up(&loops[nr]->lock);
+		loops[nr]->ropen=0;
+		if (loops[nr]->zerocopy && loops[nr]->buffer) {
+			loops[nr]->ioctlnr=0;
+			loops[nr]->ioctllength=0;
+			kill_proc(loops[nr]->pid, SIGIO, 1);
+		}
+	}
+
+	return 0;
+}
+
+static ssize_t vloopback_write(struct file *f, const char *buf,
+		size_t count, loff_t *offset)
+{
+	struct video_device *loopdev=video_devdata(f);
+	priv_ptr ptr=(priv_ptr)loopdev->priv;
+	int nr=ptr->pipenr;
+	unsigned long realcount=count;
+	
+	if (!ptr->in)
+		return -EINVAL;
+	if (loops[nr]->zerocopy)
+		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;
+		info("Too much data! Only %ld bytes used.", realcount);
+	}
+	
+	if (copy_from_user(
+	    loops[nr]->buffer+loops[nr]->frame*loops[nr]->buflength,
+	    buf, realcount
+	)) return -EFAULT;
+
+	loops[nr]->frame=0;
+	up(&loops[nr]->lock);
+
+	loops[nr]->frameswrite++;
+	wake_up(&loops[nr]->wait);
+
+	return realcount;
+}
+
+static ssize_t vloopback_read (struct file * f, char * buf, size_t count, loff_t *offset)
+{
+	struct video_device *loopdev=video_devdata(f);
+	priv_ptr ptr=(priv_ptr)loopdev->priv;
+	int nr=ptr->pipenr;
+	unsigned long realcount=count;
+
+	if (loops[nr]->zerocopy) {
+		if (ptr->in) {
+			if (realcount > loops[nr]->ioctllength+sizeof(unsigned long int))
+				realcount=loops[nr]->ioctllength+sizeof(unsigned long int);
+			if (copy_to_user(buf , &loops[nr]->ioctlnr, sizeof(unsigned long int)))
+				return -EFAULT;
+			if (copy_to_user(buf+sizeof(unsigned long int) , loops[nr]->ioctldata, 
+					realcount-sizeof(unsigned long int)))
+				return -EFAULT;	
+			if (loops[nr]->ioctlnr==0)
+				loops[nr]->ioctlnr=-1;
+			return realcount;
+		} else {
+			struct video_window vidwin;
+			struct video_mmap vidmmap;
+			struct video_picture vidpic;
+			
+			fake_ioctl(nr, VIDIOCGWIN, &vidwin);
+			fake_ioctl(nr, VIDIOCGPICT, &vidpic);
+
+			vidmmap.height=vidwin.height;
+			vidmmap.width=vidwin.width;
+			vidmmap.format=vidpic.palette;
+			vidmmap.frame=0;
+			if (fake_ioctl(nr, VIDIOCMCAPTURE, &vidmmap))
+				return 0;
+			if (fake_ioctl(nr, VIDIOCSYNC, &vidmmap))
+				return 0;
+			realcount=vidwin.height*vidwin.width*vidpic.depth;
+		}
+	}
+	if (ptr->in)
+		return -EINVAL;
+
+	if (realcount > loops[nr]->buflength) {
+		realcount = loops[nr]->buflength;
+		info("Not so much data in buffer!");
+	}
+
+	if (!loops[nr]->zerocopy) {
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+		unsigned long fw=loops[nr]->frameswrite;
+		
+		wait_event_interruptible(loops[nr]->wait, fw!=loops[nr]->frameswrite);
+#else
+		interruptible_sleep_on(&loops[nr]->wait);
+#endif
+	}
+
+	down(&loops[nr]->lock);
+	if (!loops[nr]->buffer) {
+		up(&loops[nr]->lock);
+		return 0;
+	}
+	if (copy_to_user(buf, loops[nr]->buffer, realcount))
+		return -EFAULT;
+	up(&loops[nr]->lock);
+
+	loops[nr]->framesread++;
+	return realcount;
+}
+
+static int vloopback_mmap(struct file *f, struct vm_area_struct *vma)
+{
+	struct video_device *loopdev=video_devdata(f);
+	priv_ptr ptr=(priv_ptr)loopdev->priv;
+	int nr=ptr->pipenr;
+	unsigned long start = (unsigned long)vma->vm_start;
+	long size = vma->vm_end - vma->vm_start;
+	unsigned long page, pos;
+
+	down(&loops[nr]->lock);
+	if (ptr->in) {
+		loops[nr]->zerocopy=1;
+		if (loops[nr]->ropen) {
+			info("Can't change size while opened for read");
+			up(&loops[nr]->lock);
+			return -EINVAL;
+		}
+		if (!size) {
+			up(&loops[nr]->lock);
+			return -EINVAL;
+		}
+		if (loops[nr]->buffer)
+			rvfree(loops[nr]->buffer, loops[nr]->buflength*N_BUFFS);
+		loops[nr]->buflength=size;
+		loops[nr]->buffer=rvmalloc(loops[nr]->buflength*N_BUFFS);
+	}
+        if (loops[nr]->buffer == NULL) {
+		up(&loops[nr]->lock);
+                return -EINVAL;
+	}
+
+        if (size > (((N_BUFFS * loops[nr]->buflength) + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1))) {
+		up(&loops[nr]->lock);
+                return -EINVAL;
+	}
+
+        pos = (unsigned long)loops[nr]->buffer;
+        while (size > 0) {
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,9)
+                page = kvirt_to_pa(pos);
+                if (remap_page_range(vma,start, page, PAGE_SIZE, PAGE_SHARED)) {
+#else
+		page = vmalloc_to_pfn((void *)pos);
+		if (remap_pfn_range(vma, start, page, PAGE_SIZE,
+				PAGE_SHARED)) {
+#endif
+			up(&loops[nr]->lock);
+                        return -EAGAIN;
+		}
+                start += PAGE_SIZE;
+                pos += PAGE_SIZE;
+		size -= PAGE_SIZE;
+        }
+	up(&loops[nr]->lock);
+
+	return 0;
+}
+
+static int vloopback_ioctl(struct inode *inod, struct file *f, unsigned int cmd, unsigned long arg)
+{
+	struct video_device *loopdev=video_devdata(f);
+	priv_ptr ptr=(priv_ptr)loopdev->priv;
+	int nr=ptr->pipenr;
+	int i;
+
+	if (loops[nr]->zerocopy) {
+		if (!ptr->in) {
+			loops[nr]->ioctlnr=cmd;
+			loops[nr]->ioctllength=_IOC_SIZE(cmd);
+			/* info("DEBUG: vl_ioctl: !loop->in"); */
+			/* info("DEBUG: vl_ioctl: cmd %lu", cmd); */
+			/* info("DEBUG: vl_ioctl: len %lu", loops[nr]->ioctllength); */
+			if(copy_from_user(loops[nr]->ioctldata, (void*)arg, _IOC_SIZE(cmd)))
+				return -EFAULT;
+			kill_proc(loops[nr]->pid, SIGIO, 1);
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+			wait_event_interruptible(loops[nr]->wait, loops[nr]->ioctlnr==-1);
+#else
+			interruptible_sleep_on(&loops[nr]->wait);
+#endif			
+			
+			if (loops[nr]->invalid_ioctl) {
+				//info ("DEBUG: There was an invalid ioctl");
+				loops[nr]->invalid_ioctl = 0;
+				return -ENOTTY;
+			}
+			if (cmd & IOC_IN && !(cmd & IOC_OUT)) {
+				//info("DEBUG: vl_ioctl: cmd & IOC_IN 1"); 
+				if (memcmp(loops[nr]->ioctlretdata, loops[nr]->ioctldata, _IOC_SIZE(cmd))) {
+					return -EINVAL;
+				}
+			 	//info("DEBUG: vl_ioctl: cmd & IOC_IN 2"); 
+				return 0;
+			} else {
+				if (copy_to_user((void*)arg, loops[nr]->ioctlretdata, _IOC_SIZE(cmd)))
+					return -EFAULT;
+				//info("DEBUG: vl_ioctl: !(cmd & IOC_IN) 1");
+				return 0;
+			}
+		} else {
+			if ( (loops[nr]->ioctlnr!=cmd) && (cmd != (VIDIOCSINVALID))) {
+				/* wrong ioctl */
+				info("DEBUG: vo_ioctl: Wrong IOCTL");
+				return 0;
+			}
+			if (cmd == VIDIOCSINVALID) {
+				loops[nr]->invalid_ioctl = 1;
+			} else {
+				if (copy_from_user(loops[nr]->ioctlretdata, (void*)arg, loops[nr]->ioctllength))
+					return -EFAULT;
+			}
+			loops[nr]->ioctlnr=-1;
+			if (waitqueue_active(&loops[nr]->wait))
+				wake_up(&loops[nr]->wait);
+			return 0;
+		}
+	}
+	switch(cmd)
+	{
+		/* Get capabilities */
+		case VIDIOCGCAP:
+		{
+			struct video_capability b;
+			if (ptr->in) {
+				sprintf(b.name, "Video loopback %d input",
+				    ptr->pipenr);
+				b.type = 0;
+			} else {
+				sprintf(b.name, "Video loopback %d output",
+				    ptr->pipenr);
+				b.type = VID_TYPE_CAPTURE;
+			}
+			b.channels=1;
+			b.audios=0;
+			b.maxwidth=loops[nr]->width;
+			b.maxheight=loops[nr]->height;
+			b.minwidth=20;
+			b.minheight=20;
+			if(copy_to_user((void*)arg, &b, sizeof(b)))
+				return -EFAULT;
+			return 0;
+		}
+		/* Get channel info (sources) */
+		case VIDIOCGCHAN:
+		{
+			struct video_channel v;
+			if(copy_from_user(&v, (void*)arg, sizeof(v)))
+				return -EFAULT;
+			if(v.channel!=0) {
+				info("VIDIOCGCHAN: Invalid Channel, was %d", v.channel);
+				v.channel=0;
+				//return -EINVAL;
+			}
+			v.flags=0;
+			v.tuners=0;
+			v.norm=0;
+			v.type = VIDEO_TYPE_CAMERA;
+			/*strcpy(v.name, "Loopback"); -- tibit */
+			strcpy(v.name, "Composite1");
+			if(copy_to_user((void*)arg, &v, sizeof(v)))
+				return -EFAULT;
+			return 0;
+		}
+		/* Set channel 	*/
+		case VIDIOCSCHAN:
+		{
+			int v;
+			if(copy_from_user(&v, (void*)arg, sizeof(v)))
+				return -EFAULT;
+			if(v!=0) {
+				info("VIDIOCSCHAN: Invalid Channel, was %d", v);
+				return -EINVAL;
+			}
+			return 0;
+		}
+		/* Get tuner abilities */
+		case VIDIOCGTUNER:
+		{
+			struct video_tuner v;
+			if(copy_from_user(&v, (void*)arg, sizeof(v))!=0)
+				return -EFAULT;
+			if(v.tuner) {
+				info("VIDIOCGTUNER: Invalid Tuner, was %d", 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((void*)arg,&v, sizeof(v))!=0)
+				return -EFAULT;
+			return 0;
+		}
+		/* Get picture properties */
+		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((void*)arg, &p, sizeof(p)))
+				return -EFAULT;
+			return 0;
+
+		}
+		/* Set picture properties */
+		case VIDIOCSPICT:
+		{
+			struct video_picture p;
+			if(copy_from_user(&p, (void*)arg, sizeof(p)))
+				return -EFAULT;
+			if (!ptr->in) {
+				if (p.palette!=loops[nr]->palette)
+					return -EINVAL;
+			} else
+				loops[nr]->palette=p.palette;
+			return 0;
+		}
+		/* Get the video overlay window */
+		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((void*)arg, &vw, sizeof(vw)))
+				return -EFAULT;
+			return 0;
+		}
+		/* Set the video overlay window - passes clip list for hardware smarts , chromakey etc */
+		case VIDIOCSWIN:
+		{
+			struct video_window vw;
+			
+			if(copy_from_user(&vw, (void*)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(!ptr->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) {
+					info("Can't change size while opened for read");
+					up(&loops[nr]->lock);
+					return -EINVAL;
+				}
+				if (loops[nr]->buffer)
+					rvfree(loops[nr]->buffer, loops[nr]->buflength*N_BUFFS);
+				loops[nr]->buflength=vw.width*vw.height*4;
+				loops[nr]->buffer=rvmalloc(loops[nr]->buflength*N_BUFFS);
+				up(&loops[nr]->lock);
+			}
+			return 0;
+		}
+		/* Memory map buffer info */
+		case VIDIOCGMBUF:
+		{
+			struct video_mbuf vm;
+			
+			vm.size=loops[nr]->buflength*N_BUFFS;
+			vm.frames=N_BUFFS;
+			for (i=0; i<vm.frames; i++)
+				vm.offsets[i]=i*loops[nr]->buflength;
+			if(copy_to_user((void*)arg, &vm, sizeof(vm)))
+				return -EFAULT;
+			return 0;
+		}
+		/* Grab frames */
+		case VIDIOCMCAPTURE:
+		{
+			struct video_mmap vm;
+
+			if (ptr->in)
+				return -EINVAL;
+			if (!loops[nr]->buffer)
+				return -EINVAL;
+			if (copy_from_user(&vm, (void*)arg, sizeof(vm)))
+				return -EFAULT;
+			if (vm.format!=loops[nr]->palette)
+				return -EINVAL;
+			if (vm.frame > N_BUFFS)
+				return -EINVAL;
+			return 0;
+		}
+		/* Sync with mmap grabbing */
+		case VIDIOCSYNC:
+		{
+			int frame;
+			unsigned long fw;
+
+			if (copy_from_user((void *)&frame, (void*)arg, sizeof(int)))
+				return -EFAULT;
+			if (ptr->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' */
+			if (frame > 1)
+				return -EINVAL;
+			loops[nr]->frame=frame;
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+			fw = loops[nr]->frameswrite;
+			wait_event_interruptible(loops[nr]->wait, fw!=loops[nr]->frameswrite);
+#else
+			interruptible_sleep_on(&loops[nr]->wait);
+#endif			
+			if (!loops[nr]->buffer)		/* possibly released during sleep */
+				return -EINVAL;
+			loops[nr]->framesread++;
+			return 0;
+		}
+		/* Get attached units */
+		case VIDIOCGUNIT:
+		{
+			struct video_unit vu;
+			
+			if (ptr->in)
+				vu.video=loops[nr]->vloopout->minor;
+			else
+				vu.video=loops[nr]->vloopin->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((void*)arg, &vu, sizeof(vu)))
+				return -EFAULT;
+			return 0;
+		}
+		/* Get frame buffer */
+		case VIDIOCGFBUF:
+		{
+			struct video_buffer vb;
+
+			memset(&vb, 0, sizeof(vb));
+			vb.base=NULL;
+
+			if(copy_to_user((void *)arg, (void *)&vb, sizeof(vb)))
+				return -EFAULT;
+
+			return 0;
+		}
+		/* Start, end capture */
+		case VIDIOCCAPTURE:
+		{
+			int start;
+			if (copy_from_user(&start, (void*)arg, sizeof(int)))
+				return -EFAULT;
+			if (start) info ("Capture started");
+			else info ("Capture stopped");
+
+			return 0;
+		}
+
+		case VIDIOCGFREQ:
+		case VIDIOCSFREQ:
+		case VIDIOCGAUDIO:
+		case VIDIOCSAUDIO:
+			return -EINVAL;
+		case VIDIOCKEY:
+			return 0;
+		default:
+			return -ENOTTY;
+			//return -ENOIOCTLCMD;
+	}
+	return 0;
+}
+
+static unsigned int vloopback_poll(struct file *f, struct poll_table_struct *wait)
+{
+	struct video_device *loopdev=video_devdata(f);
+	priv_ptr ptr=(priv_ptr)loopdev->priv;
+	int nr=ptr->pipenr;
+
+	if (loopdev==NULL)
+		return -EFAULT;
+	if (!ptr->in)
+		return 0;
+
+	if (loops[nr]->ioctlnr!=-1) {
+		if (loops[nr]->zerocopy) {
+			return (POLLIN | POLLPRI | POLLOUT | POLLRDNORM);
+		} else {
+			return (POLLOUT);
+		}
+	}
+	return 0;
+}
+
+static struct file_operations fileops_template=
+{
+	owner:		THIS_MODULE,
+	open:		vloopback_open,
+	release:	vloopback_release,
+	read:		vloopback_read,
+	write:		vloopback_write,
+	poll:		vloopback_poll,
+	ioctl:		vloopback_ioctl,
+	mmap:		vloopback_mmap,
+};
+
+static struct video_device vloopback_template=
+{
+	owner:		THIS_MODULE,
+	name:		"Video Loopback",
+	type:		VID_TYPE_CAPTURE,
+	fops:		&fileops_template,
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+	release:	video_device_release,
+#endif
+};
+
+static int create_pipe(int nr)
+{
+	int minor_in, minor_out , ret;
+		
+	if (dev_offset == -1)
+		minor_in  = minor_out = -1; /* autoassign */
+	else {
+		minor_in  = 2*nr   + dev_offset;
+		minor_out = 2*nr+1 + dev_offset;
+	}
+
+	/* allocate space for this pipe */
+	loops[nr]= kmalloc(sizeof(struct vloopback_pipe), GFP_KERNEL);
+	if (!loops[nr])
+		return -ENOMEM;
+	/* set up a new video device plus our private area */
+	loops[nr]->vloopin= video_device_alloc();
+	if (loops[nr]->vloopin == NULL)
+		return -ENOMEM;
+	*loops[nr]->vloopin = vloopback_template;
+	loops[nr]->vloopin->priv= kmalloc(sizeof(struct vloopback_private),
+			GFP_KERNEL);
+	if (loops[nr]->vloopin->priv == NULL) {
+		kfree(loops[nr]->vloopin);
+		return -ENOMEM;
+	}
+	/* repeat for the output device */
+	loops[nr]->vloopout= video_device_alloc();
+	if (loops[nr]->vloopout == NULL) {
+		kfree(loops[nr]->vloopin->priv);
+		kfree(loops[nr]->vloopin);
+		return -ENOMEM;
+	}
+	*loops[nr]->vloopout = vloopback_template;
+	loops[nr]->vloopout->priv= kmalloc(sizeof(struct vloopback_private),
+			GFP_KERNEL);
+	if (loops[nr]->vloopout->priv == NULL) {
+		kfree(loops[nr]->vloopin->priv);
+		kfree(loops[nr]->vloopin);
+		kfree(loops[nr]->vloopout);
+		return -ENOMEM;
+	}
+
+	((priv_ptr)loops[nr]->vloopin->priv)->pipenr=nr;
+	((priv_ptr)loops[nr]->vloopout->priv)->pipenr=nr;
+	loops[nr]->invalid_ioctl = 0; /* tibit */
+	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;
+	loops[nr]->frame=0;
+	
+	((priv_ptr)loops[nr]->vloopin->priv)->in=1;
+	((priv_ptr)loops[nr]->vloopout->priv)->in=0;
+	loops[nr]->vloopin->type=0;
+	sprintf(loops[nr]->vloopin->name, "Video loopback %d input", nr);
+	loops[nr]->vloopout->type=VID_TYPE_CAPTURE;
+	sprintf(loops[nr]->vloopout->name, "Video loopback %d output", nr);
+	init_waitqueue_head(&loops[nr]->wait);
+	init_MUTEX(&loops[nr]->lock);
+	
+	ret = video_register_device(loops[nr]->vloopin, VFL_TYPE_GRABBER,minor_in);
+	
+	if ((ret == -1 ) || ( ret == -23 )) {
+		info("error registering device %s",loops[nr]->vloopin->name);
+		kfree(loops[nr]->vloopin->priv);
+		kfree(loops[nr]->vloopin);
+		kfree(loops[nr]->vloopout->priv);
+		kfree(loops[nr]->vloopout);
+		kfree(loops[nr]);
+		loops[nr]=NULL;
+		return ret;
+	}
+	
+	ret = video_register_device(loops[nr]->vloopout, VFL_TYPE_GRABBER,minor_out);
+	
+	if ((ret ==-1) || (ret == -23)) {
+		info("error registering device %s", loops[nr]->vloopout->name);
+		kfree(loops[nr]->vloopin->priv);
+		video_unregister_device(loops[nr]->vloopin);
+		kfree(loops[nr]->vloopout->priv);
+		kfree(loops[nr]->vloopout);
+		kfree(loops[nr]);
+		loops[nr]=NULL;
+		return ret;
+	}
+	
+	loops[nr]->ioctldata=kmalloc(1024, GFP_KERNEL);
+	loops[nr]->ioctlretdata=kmalloc(1024, GFP_KERNEL);
+	return 0;
+}
+
+
+/****************************************************************************
+ *	init stuff
+ ****************************************************************************/
+
+
+MODULE_AUTHOR("J.B. Vreeken (pe1rxq@amsat.org)");
+MODULE_DESCRIPTION("Video4linux loopback device.");
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+module_param(pipes, int, 000);
+#else
+MODULE_PARM(pipes, "i");
+#endif
+
+MODULE_PARM_DESC(pipes, "Nr of pipes to create (each pipe uses two video devices)");
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+module_param(spares, int, 000);
+#else
+MODULE_PARM(spares, "i");
+#endif
+
+MODULE_PARM_DESC(spares, "Nr of spare pipes that should be created");
+
+#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
+module_param(dev_offset, int, 000);
+#else
+MODULE_PARM(dev_offset_param, "i");
+#endif
+
+MODULE_PARM_DESC(dev_offset, "Prefered offset for video device numbers");
+MODULE_LICENSE("GPL");
+MODULE_VERSION( VLOOPBACK_VERSION );
+
+static int __init vloopback_init(void)
+{
+	int i,ret;
+
+	info("Video4linux loopback driver v"VLOOPBACK_VERSION);
+
+	if (pipes==-1) pipes=1;
+	if (pipes > MAX_PIPES) {
+		pipes=MAX_PIPES;
+		info("Nr of pipes is limited to: %d", MAX_PIPES);
+	}
+
+	for (i=0; i<pipes; i++) {
+		
+		ret = create_pipe(i);
+
+		if (ret == 0) {
+			info("Loopback %d registered, input: video%d,"
+			     "output: video%d",
+			     i, loops[i]->vloopin->minor,
+			     loops[i]->vloopout->minor);
+			nr_o_pipes=i+1;
+		}else{
+			return ret;
+		}
+	}
+	return 0;
+}
+
+static void __exit cleanup_vloopback_module(void)
+{
+	int i;
+
+	info("Unregistering video4linux loopback devices");
+	for (i=0; i<nr_o_pipes; i++) if (loops[i]) {
+		kfree(loops[i]->vloopin->priv);
+		video_unregister_device(loops[i]->vloopin);
+		kfree(loops[i]->vloopout->priv);
+		video_unregister_device(loops[i]->vloopout);
+		if (loops[i]->buffer) rvfree(loops[i]->buffer, loops[i]->buflength*N_BUFFS);
+		kfree(loops[i]->ioctldata);
+		kfree(loops[i]->ioctlretdata);
+		kfree(loops[i]);
+	}
+}
+
+module_init(vloopback_init);
+module_exit(cleanup_vloopback_module);