view vloopback.c @ 4:dc1f4ad7010c

Change version to proper trunk
author AngelCarpintero
date Sun, 18 May 2008 18:17:13 +0000
parents 5f21a4dddc0c
children 2fce9e157b8d
line wrap: on
line source

/*
 *	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.
 * 								
 * 18.05.08	(Angel Carpintero)
 * 			Release 1.1-rc1 as 1.1 stable working with 2.6.24								
 */


#define VLOOPBACK_VERSION "1.2-trunk"

/* 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);