view vloopback.c @ 16:2dbe2489381e

updated to 2.6.32
author Yoshiki Yazawa <yaz@honeyplanet.jp>
date Fri, 06 Nov 2009 21:57:18 +0900
parents 1011e450be45
children 5afb0aa410ec
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 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/Video loopbackFourLinuxLoopbackDevice
 *
 *    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
 *
 * 17.08.08    (Angel Carpintero)
 *            kill_proc() deprecated ,pid API changed , type and owner not available in
 *            video_device struct, added param debug.
 *
 * 24.08.08    (Angel Carpintero)
 *            Added compat_iotcl32 init in fopsl, replace tabs by 4 spaces in source code,
 *            add number of buffers as module param.
 *
 * 13.10.08    (Stephan Berberig & Angel Carpintero)
 *            Release to work on 2.6.27 , allow v4l_compat_ioctl32 work in 2.6.27 and a little cleanup
 *            in Makefile.
 *
 * 22.12.08    (Angel Carpintero)
 *            Allow build with kernel 2.6.28 and 2.6.27.git ( struct video_dev has not priv member anymore).
 */


#define VLOOPBACK_VERSION "1.4"

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

#ifndef CONFIG_VIDEO_V4L1_COMPAT
 #error "need CONFIG_VIDEO_V4L1_COMPAT"
#endif

#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>

/* v4l_compat_ioctl32 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27)
 #include <media/v4l2-ioctl.h>
#endif

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
    #define video_get_drvdata(dev) (priv)
    #define video_set_drvdata(dev, data) (priv = (data))
#elseif LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,31)
    #define video_get_drvdata(dev) ((dev).driver_data)
    #define video_set_drvdata(dev, data) ((dev) = (data))
#endif

/* 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 verbose(format, arg...) if (printk_ratelimit()) \
        printk(KERN_INFO "[%s] %s: " format "\n" "", \
                __FUNCTION__, __FILE__,  ## arg)

#define info(format, arg...) if (printk_ratelimit()) \
        printk(KERN_INFO "[%s] : " format "\n" "", \
               __FUNCTION__, ## arg)

#define LOG_NODEBUG   0
#define LOG_FUNCTIONS 1
#define LOG_IOCTL     2
#define LOG_VERBOSE   3

struct vloopback_private {
    int pipenr;
    int in; /* bool , is being feed ? */
};

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;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
    unsigned int pid;
#else
    struct pid *pid;
#endif
    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 unsigned int num_buffers = N_BUFFS;
static int pipesused = 0;
static int dev_offset = -1;
static unsigned int debug = LOG_NODEBUG;

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

    if (debug > LOG_NODEBUG)
        info("Video loopback %d cmd %lu", nr, cmd);

    loops[nr]->ioctlnr = cmd;
    memcpy(loops[nr]->ioctldata, arg, _IOC_SIZE(cmd));
    loops[nr]->ioctllength = _IOC_SIZE(cmd);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
    kill_proc(loops[nr]->pid, SIGIO, 1);    /* Signal the pipe feeder */
#else
    kill_pid(loops[nr]->pid, SIGIO, 1);
#endif

#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;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
static int vloopback_open(struct inode *inod, struct file *f)
#else
static int vloopback_open(struct file *f)
#endif
{
    struct video_device *loopdev = video_devdata(f);
    priv_ptr ptr = (priv_ptr)video_get_drvdata(loopdev);
    int nr = ptr->pipenr;

    if (debug > LOG_NODEBUG)
        info("Video loopback %d", nr);

    /* 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++;
            }
        }
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
        loops[nr]->pid = current->pid;
#elseif LINUX_VERSION_CODE < KERNEL_VERSION(2,6,31)
        loops[nr]->pid = task_pid(find_task_by_vpid(current->pid));
#else
        loops[nr]->pid = task_pid(current);
#endif

        if (debug > LOG_NODEBUG)
            info("Current pid %d", current->pid);
    }
    return 0;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
static int vloopback_release(struct inode * inod, struct file *f)
#else
static int vloopback_release(struct file *f)
#endif
{
    struct video_device *loopdev = video_devdata(f);
    priv_ptr ptr = (priv_ptr)video_get_drvdata(loopdev);
    int nr = ptr->pipenr;

    if (debug > LOG_NODEBUG)
        info("Video loopback %d", nr);

    if (ptr->in) {
        down(&loops[nr]->lock);
        if (loops[nr]->buffer && !loops[nr]->ropen) {
            rvfree(loops[nr]->buffer, loops[nr]->buflength * num_buffers);
            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 * num_buffers);
            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;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
            kill_proc(loops[nr]->pid, SIGIO, 1);
#else
            kill_pid(loops[nr]->pid, SIGIO, 1);
#endif
        }
    }

    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)video_get_drvdata(loopdev);
    int nr = ptr->pipenr;
    unsigned long realcount = count;

    if (debug > LOG_IOCTL)
        info("Video loopback %d", nr);

    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 for Video loopback %d ! Only %ld bytes used.",
             nr, 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)video_get_drvdata(loopdev);
    int nr = ptr->pipenr;
    unsigned long realcount = count;

    if (debug > LOG_IOCTL)
        info("Video loopback %d", nr);

    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! for Video loopback %d", nr);
    }

    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)video_get_drvdata(loopdev);
    int nr = ptr->pipenr;
    unsigned long start = (unsigned long)vma->vm_start;
    long size = vma->vm_end - vma->vm_start;
    unsigned long page, pos;

    if (debug > LOG_NODEBUG)
        info("Video loopback %d", nr);

    down(&loops[nr]->lock);

    if (ptr->in) {
        loops[nr]->zerocopy = 1;

        if (loops[nr]->ropen) {
            info("Can't change size while opened for read in Video loopback %d",
                 nr);
            up(&loops[nr]->lock);
            return -EINVAL;
        }

        if (!size) {
            up(&loops[nr]->lock);
            info("Invalid size Video loopback %d", nr);
            return -EINVAL;
        }

        if (loops[nr]->buffer)
            rvfree(loops[nr]->buffer, loops[nr]->buflength * num_buffers);

        loops[nr]->buflength = size;
        loops[nr]->buffer = rvmalloc(loops[nr]->buflength * num_buffers);
    }

    if (loops[nr]->buffer == NULL) {
        up(&loops[nr]->lock);
        return -EINVAL;
    }

    if (size > (((num_buffers * 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;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
static int vloopback_ioctl(struct inode *inod, struct file *f, unsigned int cmd,
           unsigned long arg)
#else
static long vloopback_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
#endif
{
    struct video_device *loopdev = video_devdata(f);
    priv_ptr ptr = (priv_ptr)video_get_drvdata(loopdev);
    int nr = ptr->pipenr;
    int i;

    if (debug > LOG_NODEBUG)
        info("Video loopback %d cmd %u", nr, cmd);

    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;
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
            kill_proc(loops[nr]->pid, SIGIO, 1);
#else
            kill_pid(loops[nr]->pid, SIGIO, 1);
#endif

#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 ("There was an invalid ioctl in Video loopback %d", nr);
                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("Wrong IOCTL %u in Video loopback %d", cmd, nr);
                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", nr);
            b.type = 0;
        } else {
            sprintf(b.name, "Video loopback %d output", nr);
            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 * num_buffers);

            loops[nr]->buflength = vw.width * vw.height * 4;
            loops[nr]->buffer = rvmalloc(loops[nr]->buflength * num_buffers);
            up(&loops[nr]->lock);
        }
        return 0;
    }
    /* Memory map buffer info */
    case VIDIOCGMBUF:
    {
        struct video_mbuf vm;

        vm.size = loops[nr]->buflength * num_buffers;
        vm.frames = num_buffers;
        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 > num_buffers)
            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)
        if (frame > num_buffers-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 ("Video loopback %d Capture started", nr);
        } else {
            info ("Video loopback %d Capture stopped", nr);
        }

        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)video_get_drvdata(loopdev);
    int nr = ptr->pipenr;

    if (debug > LOG_NODEBUG)
        info("Video loopback %d", nr);

    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;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
static const struct file_operations fileops_template =
#else
static const struct v4l2_file_operations fileops_template =
#endif
{
    owner:        THIS_MODULE,
    open:        vloopback_open,
    release:    vloopback_release,
    read:        vloopback_read,
    write:        vloopback_write,
    poll:        vloopback_poll,
    ioctl:        vloopback_ioctl,
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,15) && LINUX_VERSION_CODE < KERNEL_VERSION(2,6,29)
    compat_ioctl:   v4l_compat_ioctl32,
#endif
    mmap:        vloopback_mmap,
};

static struct video_device vloopback_template =
{
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
    owner:      THIS_MODULE,
    type:       VID_TYPE_CAPTURE,
#endif
    minor:      -1,
    name:        "Video Loopback",
    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;
    priv_ptr ptr;

    if (debug > LOG_NODEBUG)
        info("Video loopback %d", nr);

    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;

    ptr = kmalloc(sizeof(struct vloopback_private), GFP_KERNEL);
    if(!ptr) {
        kfree(loops[nr]->vloopin);
        return -ENOMEM;
    }
    video_set_drvdata(loops[nr]->vloopin, ptr);
    /* repeat for the output device */
    loops[nr]->vloopout = video_device_alloc();

    if (loops[nr]->vloopout == NULL) {
        kfree(video_get_drvdata(loops[nr]->vloopin));
        kfree(loops[nr]->vloopin);
        loops[nr]->vloopin = NULL;
        return -ENOMEM;
    }
    *loops[nr]->vloopout = vloopback_template;
    ptr = kmalloc(sizeof(struct vloopback_private), GFP_KERNEL);
    if(!ptr) {
        kfree(video_get_drvdata(loops[nr]->vloopin));
        kfree(loops[nr]->vloopin);
        kfree(loops[nr]->vloopout);
        return -ENOMEM;
    }
    video_set_drvdata(loops[nr]->vloopout, ptr);

    ((priv_ptr)video_get_drvdata(loops[nr]->vloopin))->pipenr = nr;
    ((priv_ptr)video_get_drvdata(loops[nr]->vloopout))->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)video_get_drvdata(loops[nr]->vloopin))->in = 1;
    ((priv_ptr)video_get_drvdata(loops[nr]->vloopout))->in = 0;
    sprintf(loops[nr]->vloopin->name, "Video loopback %d input", nr);
    sprintf(loops[nr]->vloopout->name, "Video loopback %d output", nr);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,27)
    loops[nr]->vloopin->type = 0;
    loops[nr]->vloopout->type = VID_TYPE_CAPTURE;
#endif
    loops[nr]->vloopout->minor = minor_out;
    loops[nr]->vloopin->minor = minor_in;

    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(video_get_drvdata(loops[nr]->vloopin));
        kfree(loops[nr]->vloopin);
        kfree(video_get_drvdata(loops[nr]->vloopout));
        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(video_get_drvdata(loops[nr]->vloopin));
        video_unregister_device(loops[nr]->vloopin);
        kfree(video_get_drvdata(loops[nr]->vloopout));
        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(num_buffers, int, 000);
#else
MODULE_PARM(num_buffers, "i");
#endif

MODULE_PARM_DESC(num_buffers, " Prefered numbers of internal buffers to map (default 2)");


#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");


#if LINUX_VERSION_CODE > KERNEL_VERSION(2,5,0)
module_param(debug, int, 000);
#else
MODULE_PARM(debug_param, "i");
#endif

MODULE_PARM_DESC(debug, " Enable module debug level 0-3 (by default 0)");

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);
    }

    if (num_buffers < N_BUFFS) {
        num_buffers = N_BUFFS;
        info("Nr of buffer set to default value %d", N_BUFFS);
    }

    for (i = 0; i < pipes; i++) {

        ret = create_pipe(i);

        if (ret == 0) {
            info("Loopback %d registered, input: /dev/video%d minor %d,"
                 " output: /dev/video%d minor %d",
                 i, loops[i]->vloopin->num, loops[i]->vloopin->minor,
                 loops[i]->vloopout->num, loops[i]->vloopout->minor);
            info("Loopback %d , Using %d buffers", i, num_buffers);
            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(video_get_drvdata(loops[i]->vloopin));
            video_unregister_device(loops[i]->vloopin);
            kfree(video_get_drvdata(loops[i]->vloopout));
            video_unregister_device(loops[i]->vloopout);

            if (loops[i]->buffer)
                rvfree(loops[i]->buffer, loops[i]->buflength * num_buffers);

            kfree(loops[i]->ioctldata);
            kfree(loops[i]->ioctlretdata);
            kfree(loops[i]);
        }
    }
}

module_init(vloopback_init);
module_exit(cleanup_vloopback_module);