/*
 * USB Skeleton driver - 2.0
 *
 * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
 *
 *	This program is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU General Public License as
 *	published by the Free Software Foundation, version 2.
 *
 * This driver is based on the 2.6.3 version of drivers/usb/usb-skeleton.c 
 * but has been rewritten to be easy to read and use, as no locks are now
 * needed anymore.
 *
 * Driver update:
 * 2006-10-20 Igor Farinic (igor.farinic@fanfi.sk)
 * Description: 
 * USB Skeleton driver was used to implement USB scanner driver.
 * This driver is supposed to handle USB requests from windows native twain32 scanner drivers.
 * 
 * 
 * Supports only one device to be connected and driven by this driver, this is because thitening this driver with wine's USB scanning functionality
 * Requests to USB scanners are handled synchronously
 */

#include <linux/config.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kref.h>
#include <linux/smp_lock.h>
#include <linux/usb.h>
#include <asm/uaccess.h>

/* Local includes */
#include "windows_usbscan.h"

MODULE_AUTHOR("Igor Farinic");
MODULE_DESCRIPTION("Emulates the Microsoft Windows USBSCAN.SYS, allowing Windows user-space scanner device drivers to run on Linux");

/* table of devices that work with this driver */
static struct usb_device_id skel_table [] = {
	{ .driver_info = 1 },
	{ }					/* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, skel_table);


/* Get a minor range for your devices from the usb maintainer */
#define USB_SKEL_MINOR_BASE	192
#define USB_SKEL_MAJOR_BASE	180

struct win_usbscan_buffer {
        void    *memory;
        int      size;
};

/* Structure to hold all of our device specific stuff */
struct usb_skel {
	struct usb_device *	udev;			/* the usb device for this device */
	struct usb_interface *	interface;		/* the interface for this device */
	__u8			bulk_in_endpointAddr;	/* the address of the bulk in endpoint */
	__u8			bulk_in_large_endpointAddr; /* the address of the bulk in endpoint for large transfers*/
	__u8			bulk_out_endpointAddr;	/* the address of the bulk out endpoint */
	struct kref		kref;

	/* windows usbscan private properties*/
        USBSCAN_PIPE_CONFIGURATION      pipeConfig;     /* Pipes on scanner */
        /* Pipe related fields */
        int                                             controlReadPipe;
        int                                             controlWritePipe;
        /* Buffers */
        struct win_usbscan_buffer                       readBuffer;
        struct win_usbscan_buffer                       writeBuffer;
        struct win_usbscan_buffer                       ioctlInputBuffer;
        struct win_usbscan_buffer                       ioctlOutputBuffer;

        /* Timeouts */
        int                                                     readTimeout;
        int                                                     writeTimeout;
        int                                                     interruptTimeout;
	struct semaphore                        semaphore;
};

#define to_skel_dev(d) container_of(d, struct usb_skel, kref)

static void skel_makeDeviceFiles(struct usb_skel *skel)
{
        int i;
        char *params[6];
        char *env[1];
        char name[100], major[100], minor[100];
        env[0] = 0;

        //printk(KERN_DEBUG "skel_makeDeviceFiles(skel->index = %d)\n", skel->index);
	printk(KERN_DEBUG "skel_makeDeviceFiles(skel->index = %d)\n", 0);

        snprintf(major, 100, "%d", USB_SKEL_MAJOR_BASE);

        // Make \\.\USBSCAN0
        //snprintf(name, 100, "/dev/USBSCAN%d", skel->index);
	snprintf(name, 100, "/dev/USBSCAN%d", 0);
        params[0] = "/bin/mknod";
        params[1] = name;
        params[2] = "c";
        params[3] = major;
        snprintf(minor, 100, "%d", USB_SKEL_MINOR_BASE);
        params[4] = minor;
        params[5] = 0;
        if (call_usermodehelper("/bin/mknod", params, env, 1))
                printk(KERN_WARNING "usermodehelper failed\n");

        printk(KERN_DEBUG "name = %s\n", name);
        printk(KERN_DEBUG "major = %s\n", major);
        printk(KERN_DEBUG "minor = %s\n", minor);
        printk(KERN_DEBUG "skel->pipeConfig.NumberOfPipes = %ld\n", skel->pipeConfig.NumberOfPipes );
        // Make \\.\USBSCAN0\0, \\.\USBSCAN0\1 etc.
        for (i = 0; i < skel->pipeConfig.NumberOfPipes; ++i) {
                //snprintf(name, 100, "/dev/USBSCAN%d\\%d", interfaceData->index, i);
		snprintf(name, 100, "/dev/USBSCAN%d\\%d", 0, i);
                params[1] = name;
                //snprintf(minor, 100, "%d", (interfaceData->index << 4) | (i + 1));
		snprintf(minor, 100, "%d", USB_SKEL_MINOR_BASE);
                params[4] = minor;
                call_usermodehelper("/bin/mknod", params, env, 1);

                printk(KERN_DEBUG "name = %s\n", name);
                printk(KERN_DEBUG "major = %s\n", major);
                printk(KERN_DEBUG "minor = %s\n", minor);

        }
	
	printk(KERN_DEBUG "skel_makeDeviceFiles(skel->index = %d) - end\n", 0);

}

static void skel_deleteDeviceFiles(struct usb_skel *skel)
{
        int i;
        char *params[3];
        char *env[1];
        char name[100];

        env[0] = 0;

        printk(KERN_DEBUG "skel_deleteDeviceFiles(interfaceData->index = %d)\n", 0);

        // Delete \\.\USBSCAN0
        //snprintf(name, 100, "/dev/USBSCAN%d", interfaceData->index);
	snprintf(name, 100, "/dev/USBSCAN%d", 0);
        params[0] = "/bin/rm";
        params[1] = name;
        params[2] = 0;
        call_usermodehelper("/bin/rm", params, env, 1);

        //Delete \\.\USBSCAN0\0, \\.\USBSCAN0\1 etc.
        for (i = 0; i < skel->pipeConfig.NumberOfPipes; ++i) {
                //snprintf(name, 100, "/dev/USBSCAN%d\\%d", interfaceData->index, i);
		snprintf(name, 100, "/dev/USBSCAN%d\\%d", 0, i);
                params[1] = name;
                call_usermodehelper("/bin/rm", params, env, 1);
        }

        printk(KERN_DEBUG "skel_deleteDeviceFiles(interfaceData->index = %d) - end\n", 0);
}


static void skel_releaseBuffers(struct usb_skel *dev)
{
        printk(KERN_DEBUG "skel_releaseBuffers() - begin\n");

	if (!dev) {
		printk(KERN_DEBUG "skel_releaseBuffers() - end\n");
		return;
	}

        if (dev->readBuffer.memory)
                kfree(dev->readBuffer.memory);
        if (dev->writeBuffer.memory)
                kfree(dev->writeBuffer.memory);
        if (dev->ioctlInputBuffer.memory)
                kfree(dev->ioctlInputBuffer.memory);
        if (dev->ioctlOutputBuffer.memory)
                kfree(dev->ioctlOutputBuffer.memory);

        printk(KERN_DEBUG "skel_releaseBuffers() - end\n");
}

static int skel_allocateBuffers(struct usb_skel *dev)
{

        printk(KERN_DEBUG "skel_allocateBuffers() - begin\n");

        dev->readBuffer.size = 10000;
        dev->writeBuffer.size = 10000;
        dev->ioctlInputBuffer.size = 1000;
        dev->ioctlOutputBuffer.size = 1000;
        dev->readBuffer.memory = kmalloc(dev->readBuffer.size, GFP_KERNEL);
        dev->writeBuffer.memory = kmalloc(dev->writeBuffer.size,

    GFP_KERNEL);
        dev->ioctlInputBuffer.memory = kmalloc(dev->ioctlInputBuffer.size,

    GFP_KERNEL);
        dev->ioctlOutputBuffer.memory = kmalloc(dev->ioctlOutputBuffer.size,

    GFP_KERNEL);

        if (!dev->readBuffer.memory
        || !dev->writeBuffer.memory
        || !dev->ioctlInputBuffer.memory
        || !dev->ioctlOutputBuffer.memory) {
                skel_releaseBuffers(dev);
                return -1;
        }

        printk(KERN_DEBUG "skel_allocateBuffers() - end\n");

        return 0;
}


static int skel_growBuffer(struct win_usbscan_buffer *buffer, int size)
{
        printk(KERN_DEBUG "skel_growBuffer() - begin\n");

        if (buffer->size < size) {
                kfree(buffer->memory);
                buffer->size = 0;
                buffer->memory = kmalloc(size, GFP_KERNEL);
                if (!buffer->memory)
                        return -1;
                buffer->size = size;
        }

        printk(KERN_DEBUG "skel_growBuffer() - end\n");
        return 0;

}


static int skel_ioctl(struct inode *inode, struct file *file,
        unsigned int code, unsigned long param)
{
        struct packed_ioctl data;
        int ret = 0;
	struct usb_skel *dev;

        printk(KERN_DEBUG "skel_ioctl() - begin\n");

	dev = (struct usb_skel *) file->private_data;

        if (copy_from_user(&data, (void*) param, sizeof(data)))
                return -EFAULT;

        printk(KERN_DEBUG "skel_ioctl: data = %p \n", &data);
	printk(KERN_DEBUG "skel_ioctl: code = %d\n", code);
	printk(KERN_DEBUG "skel_ioctl: param = %ld\n", param);
	
        if (down_interruptible(&dev->semaphore))
                return -ERESTARTSYS;

        switch (code) {
                case IOCTL_CANCEL_IO: {
                        // Always does nothing, the driver is synchronous :-)
                        break;
                }

                case IOCTL_GET_CHANNEL_ALIGN_RQST: {
                        CHANNEL_INFO channels;
                        USBSCAN_PIPE_CONFIGURATION *config;
                        int i;
                        config = &dev->pipeConfig;
                        for (i = 0; i < config->NumberOfPipes; ++i) {

                                if (config->PipeInfo[i].EndpointAddress ==
                                                        //usb_pipeendpoint(dev->bulkReadPipe))
							dev->bulk_in_endpointAddr)
                                {
                                        channels.uReadDataAlignment =
                                                                config->PipeInfo[i].MaximumPacketSize;
                                }
                                else if (config->PipeInfo[i].EndpointAddress ==
                                                        //usb_pipeendpoint(dev->bulkWritePipe))
							dev->bulk_out_endpointAddr)
                                {
                                        channels.uWriteDataAlignment =
                                                                config->PipeInfo[i].MaximumPacketSize;
                                }
                        }
                        if (copy_to_user(data.outputBuffer, &channels, sizeof(channels)))
                                ret = -EFAULT;
                        break;
                }


                case IOCTL_GET_DEVICE_DESCRIPTOR: {
                        DEVICE_DESCRIPTOR descriptor;
                        descriptor.usVendorId = dev->udev->descriptor.idVendor;
                        descriptor.usProductId = dev->udev->descriptor.idProduct;
                        descriptor.usBcdDevice = dev->udev->descriptor.bcdDevice;
                        descriptor.usLanguageId = dev->udev->string_langid;
                        if (copy_to_user(data.outputBuffer, &descriptor,
                                                                sizeof(descriptor)))
                                ret = -EFAULT;
                        break;
                }

               case IOCTL_GET_PIPE_CONFIGURATION: {
                        if (copy_to_user(data.outputBuffer,
                                                                &dev->pipeConfig,
                                                                sizeof(dev->pipeConfig)))
                                ret = -EFAULT;
                        break;
                }

                case IOCTL_GET_USB_DESCRIPTOR: {
                        USBSCAN_GET_DESCRIPTOR desc;
                        if (copy_from_user(&desc, data.inputBuffer, sizeof(desc)))
                                ret = -EFAULT;
                        else switch (desc.DescriptorType) {
                                case USB_DEVICE_DESCRIPTOR_TYPE: {
                                        USB_DEVICE_DESCRIPTOR target;
                                        struct usb_device_descriptor *source =
                                                                                &dev->udev->descriptor;
                                        target.bLength = source->bLength;
                                        target.bDescriptorType = source->bDescriptorType;
                                        target.bcdUSB = source->bcdUSB;
                                        target.bDeviceClass = source->bDeviceClass;
                                        target.bDeviceSubClass = source->bDeviceSubClass;
                                        target.bDeviceProtocol = source->bDeviceProtocol;
                                        target.bMaxPacketSize0 = source->bMaxPacketSize0;
                                        target.idVendor = source->idVendor;
                                        target.idProduct = source->idProduct;
                                        target.bcdDevice = source->bcdDevice;
                                        target.iManufacturer = source->iManufacturer;
                                        target.iProduct = source->iProduct;
                                        target.iSerialNumber = source->iSerialNumber;
                                        target.bNumConfigurations = source->bNumConfigurations;
                                        if (copy_to_user(data.outputBuffer, &target,
                                                                                sizeof(target)))
                                                ret = -EFAULT;
                                        break;
                                }
                              case USB_CONFIGURATION_DESCRIPTOR_TYPE: {
                                        USB_CONFIGURATION_DESCRIPTOR target;
                                        struct usb_config_descriptor *source =
                                                                        &dev->udev->actconfig->desc;
                                        target.bLength = source->bLength;
                                        target.bDescriptorType = source->bDescriptorType;
                                        target.wTotalLength = source->wTotalLength;
                                        target.bNumInterfaces = source->bNumInterfaces;
                                        target.iConfiguration = source->iConfiguration;
                                        target.bmAttributes = source->bmAttributes;
                                        target.MaxPower = source->bMaxPower;
                                        if (copy_to_user(data.outputBuffer, &target,
                                                                                sizeof(target)))
                                                ret = -EFAULT;
                                        break;
                                }
                                case USB_STRING_DESCRIPTOR_TYPE: {
                                        USB_STRING_DESCRIPTOR *target;
                                        if (skel_growBuffer(&dev->ioctlOutputBuffer,
                                                                                                data.outputBufferSize))
                                        {
                                                ret = -ENOMEM;
                                                break;
                                        }
                                        target = (PUSB_STRING_DESCRIPTOR)
                                                                        dev->ioctlOutputBuffer.memory;
                                        ret = usb_get_string(dev->udev, desc.LanguageId,
                                                                                desc.Index, target->bString,
                                                                                dev->ioctlOutputBuffer.size - 2);
                                        if (ret < 0)
                                                break;
                                        target->bLength = ret + 2;
                                        target->bDescriptorType = USB_STRING_DESCRIPTOR_TYPE;
                                        if (copy_to_user(data.outputBuffer, &target,
                                                                                target->bLength))
                                                ret = -EFAULT;
                                        break;
                                }
                              default:
                                        ret = -EINVAL;
                        }
                        break;
                }


                case IOCTL_GET_VERSION: {
                        DRV_VERSION version;
                        version.major = 0;
                        version.minor = 1;
                        version.internal = 0;
                        if (copy_to_user(data.outputBuffer, &version, sizeof(version)))
                                ret = -EFAULT;
                        break;
                }


               case IOCTL_READ_REGISTERS: {
                        IO_BLOCK ioBlock;
                        if (copy_from_user(&ioBlock, data.inputBuffer, sizeof(ioBlock))) {
                                ret = -EFAULT;
                                break;
                        }
                        if (skel_growBuffer(&dev->ioctlOutputBuffer,
                                        data.outputBufferSize)) {
                                ret = -ENOMEM;
                                break;
                        }
                        ret = usb_control_msg(dev->udev,
                                                                        dev->controlReadPipe,
                                                                        (ioBlock.uLength > 1) ? 0x04 : 0x0C,
                                                                        0xC0,
                                                                        ioBlock.uOffset,
                                                                        ioBlock.uIndex,
                                                                        dev->ioctlOutputBuffer.memory,
                                                                        data.outputBufferSize,
                                                                        0);
                        if (ret < 0)
                                break;
                        copy_to_user(data.outputBuffer, dev->ioctlOutputBuffer.memory,
                                data.outputBufferSize);
                        break;
                }


                case IOCTL_RESET_PIPE: {
                        PIPE_TYPE pipeType;
                        if (copy_from_user(&pipeType, data.inputBuffer, sizeof(pipeType))) {
                                ret = -EFAULT;
                                break;
                        }
                        switch (pipeType) {
                                case READ_DATA_PIPE:
                                        usb_clear_halt(dev->udev, usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr));
                                        break;
                                case WRITE_DATA_PIPE:
                                        usb_clear_halt(dev->udev, usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr));
                                        break;
                                case ALL_PIPE:
                                        usb_clear_halt(dev->udev, usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr));
                                        usb_clear_halt(dev->udev, usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr));
                                        break;
                                default:
                                        ret = -EINVAL;
                        }
                        break;
                }


            case IOCTL_SEND_USB_REQUEST: {
                        IO_BLOCK_EX ioBlockEx;
                        if (copy_from_user(&ioBlockEx, data.inputBuffer, sizeof(ioBlockEx)))
                        {
                                ret = -EFAULT;
                                break;
                        }
                        if (ioBlockEx.fTransferDirectionIn) {
                                if (skel_growBuffer(&dev->ioctlOutputBuffer,
                                                                                        data.outputBufferSize)) {
                                        ret = -ENOMEM;
                                        break;
                                }
                                ret = usb_control_msg(dev->udev,
                                                                                dev->controlReadPipe,
                                                                                ioBlockEx.bRequest,
                                                                                0xC0,
                                                                                ioBlockEx.uOffset,
                                                                                ioBlockEx.uIndex,
                                                                                dev->ioctlOutputBuffer.memory,
                                                                                data.outputBufferSize,
                                                                                0);
                                if (ret < 0)
                                        break;
                                copy_to_user(data.outputBuffer,
                                                                dev->ioctlOutputBuffer.memory,
                                                                data.outputBufferSize);
                        } else {
                                if (skel_growBuffer(&dev->ioctlInputBuffer,
                                                                                        ioBlockEx.uLength)) {
                                        ret = -ENOMEM;
                                        break;
                                }
                                if (copy_from_user(dev->ioctlInputBuffer.memory,
                                                                        ioBlockEx.pbyData, ioBlockEx.uLength)) {
                                        ret = -EFAULT;
                                        break;
                                }
                                ret = usb_control_msg(dev->udev,
                                                                                dev->controlWritePipe,
                                                                                ioBlockEx.bRequest,
                                                                                0x40,
                                                                                ioBlockEx.uOffset,
                                                                                ioBlockEx.uIndex,
                                                                                dev->ioctlInputBuffer.memory,
                                                                                ioBlockEx.uLength,
                                                                                0);
                        }
                        break;
                }


                case IOCTL_SET_TIMEOUT: {
                        USBSCAN_TIMEOUT timeout;
                        if (copy_from_user(&timeout, data.inputBuffer, sizeof(timeout))) {
                                ret = -EFAULT;
                                break;
                        }
                        dev->readTimeout = timeout.TimeoutRead * HZ;
                        dev->writeTimeout = timeout.TimeoutWrite * HZ;
                        dev->interruptTimeout =
                                timeout.TimeoutEvent * HZ;
                        break;
                }


                case IOCTL_WAIT_ON_DEVICE_EVENT: {
                        printk(KERN_WARNING "windows_usbscan: IOCTL_WAIT_ON_DEVICE_EVENT unimplemented!\n");
                        ret = -ENOTTY;
                        break;
                }


                case IOCTL_WRITE_REGISTERS: {
                        IO_BLOCK ioBlock;
                        if (copy_from_user(&ioBlock, data.inputBuffer, sizeof(ioBlock))) {
                                ret = -EFAULT;
                                break;
                        }
                        if (skel_growBuffer(&dev->ioctlInputBuffer,
                                                                                ioBlock.uLength)) {
                                ret = -ENOMEM;
                                break;
                        }
                        if (copy_from_user(dev->ioctlInputBuffer.memory,
                                                                ioBlock.pbyData, ioBlock.uLength)) {
                                ret = -EFAULT;
                                break;
                        }
                        ret = usb_control_msg(dev->udev,
                                                                        dev->controlWritePipe,
                                                                        (ioBlock.uLength > 1) ? 0x04 : 0x0C,
                                                                        0x40,
                                                                        ioBlock.uOffset,
                                                                        ioBlock.uIndex,
                                                                        dev->ioctlInputBuffer.memory,
                                                                        ioBlock.uLength,
                                                                        0);
                        break;
                }

                default:
                        ret = -ENOTTY;
        }

        up(&dev->semaphore);

        printk(KERN_DEBUG "skel_ioctl() - end\n");
        return ret;

}

static void skel_delete(struct kref *kref)
{	
	struct usb_skel *dev = to_skel_dev(kref);

	usb_put_dev(dev->udev);
	skel_releaseBuffers(dev);
	//kfree (dev->bulk_in_buffer);
	//kfree (dev->bulk_in_large_buffer);
	kfree (dev);
}



static struct usb_driver skel_driver;


static int skel_open(struct inode *inode, struct file *file)
{

	struct usb_skel *dev;
	struct usb_interface *interface;
	int subminor;
	int retval = 0;
	//int i;

	printk(KERN_DEBUG "skel_open() - begin\n");		

	subminor = iminor(inode);

	interface = usb_find_interface(&skel_driver, subminor);
	if (!interface) {
		err ("%s - error, can't find device for minor %d\n",
		     __FUNCTION__, subminor);
		retval = -ENODEV;
		goto exit;
	}

	dev = usb_get_intfdata(interface);
	if (!dev) {
		retval = -ENODEV;
		goto exit;
	}
	
	/* increment our usage count for the device */
	kref_get(&dev->kref);

        /* Select pipes */
/*        dev->controlReadPipe = usb_rcvctrlpipe(dev->udev, 0);
        dev->controlWritePipe = usb_sndctrlpipe(dev->udev, 0);
        dev->bulkReadPipe = -1;
        dev->bulkWritePipe = -1;
        dev->interruptPipe = -1;
        printk(KERN_DEBUG "dev->pipeConfig.NumberOfPipes = %ld\n", dev->pipeConfig.NumberOfPipes);
        for (i = 0; i < dev->pipeConfig.NumberOfPipes; ++i) {
                USBSCAN_PIPE_CONFIGURATION *pipeConfig =
                        &dev->pipeConfig;
                switch (pipeConfig->PipeInfo[i].PipeType)
                {
                        case USBSCAN_PIPE_BULK:
                                if (pipeConfig->PipeInfo[i].EndpointAddress & USB_DIR_IN) {
                                        dev->bulkReadPipe = usb_rcvbulkpipe(dev->udev,
                                                                pipeConfig->PipeInfo[i].EndpointAddress);
					
				}
                                else {
                                        dev->bulkWritePipe = usb_sndbulkpipe(dev->udev,
                                                                pipeConfig->PipeInfo[i].EndpointAddress);
					
				}	
                                break;
                        case USBSCAN_PIPE_INTERRUPT:
                                dev->interruptPipe = usb_rcvintpipe(dev->udev,
                                                                pipeConfig->PipeInfo[i].EndpointAddress);
                                break;
                }
        }
*/
     /*   printk(KERN_DEBUG "subminor = %d\n", subminor);
        if (subminor & 0xF) {
                // It wants a particular pipe 
                int wantedPipe = (subminor & 0xF) - 1 - USB_SKEL_MINOR_BASE;
		if ((wantedPipe > 0 )&&(wantedPipe < dev->pipeConfig.NumberOfPipes)) {
                printk(KERN_DEBUG "wantedPipe = %d\n", wantedPipe);
                USBSCAN_PIPE_CONFIGURATION *pipeConfig = &dev->pipeConfig;
                switch (pipeConfig->PipeInfo[wantedPipe].PipeType)
                {
                        case USBSCAN_PIPE_BULK:
                                if (pipeConfig->PipeInfo[wantedPipe].EndpointAddress & USB_DIR_IN)
                                        dev->bulkReadPipe = usb_rcvbulkpipe(dev->udev,
                                                pipeConfig->PipeInfo[wantedPipe].EndpointAddress);
                                else
                                        dev->bulkWritePipe = usb_sndbulkpipe(dev->udev,
                                                pipeConfig->PipeInfo[wantedPipe].EndpointAddress);
                                break;
                        case USBSCAN_PIPE_INTERRUPT:
                                dev->interruptPipe = usb_rcvintpipe(dev->udev,
                                        pipeConfig->PipeInfo[wantedPipe].EndpointAddress);
                                break;
                }
		}
        }
*/
       if (skel_allocateBuffers(dev)) {
                kfree(dev);
	        printk(KERN_DEBUG "skel_open() - end\n"); 	
                return -ENOMEM;
        }

	/* save our object in the file's private structure */
	file->private_data = dev;

exit:
	
	printk(KERN_DEBUG "skel_open() - end\n");		

	return retval;
}

static int skel_release(struct inode *inode, struct file *file)
{
	struct usb_skel *dev;

	printk(KERN_DEBUG "skel_release() - begin\n");		

	dev = (struct usb_skel *)file->private_data;
	if (dev == NULL)
		return -ENODEV;

	/* decrement the count on our device */
	kref_put(&dev->kref, skel_delete);
	

	printk(KERN_DEBUG "skel_release() - end\n");		
	return 0;
}

static ssize_t skel_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{

	struct usb_skel *dev;
	int retval = 0;
	int i;

	printk(KERN_DEBUG "skel_read() - begin\n");		

	//printk(KERN_DEBUG "skel_read: !!! count: %d\n", count);

	dev = (struct usb_skel *)file->private_data;

        if (down_interruptible(&dev->semaphore))
                return -ERESTARTSYS;

        if (skel_growBuffer(&dev->readBuffer, count)) {
                retval = -ENOMEM;
		printk(KERN_DEBUG "skel_read: not enough memory error\n");		
		goto done;
        }

	/*printk(KERN_DEBUG "skel_read: !!! usb_rcvbulkpipe(): 0x%02x\n", usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr));
	printk(KERN_DEBUG "skel_read: !!! dev->bulk_in_endpointAddr: 0x%02x\n", dev->bulk_in_endpointAddr );
	printk(KERN_DEBUG "skel_read: !!! dev->bulkReadPipe: %d\n", dev->bulkReadPipe );*/

	/* do a blocking bulk read to get data from the device */
	if (count < 100) {
	retval = usb_bulk_msg(dev->udev,
			      usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
			      dev->readBuffer.memory,
			      //dev->bulk_in_buffer,
			      count,
			      //min(dev->bulk_in_size, count),
			      &count, dev->readTimeout);
	} else {
	printk(KERN_DEBUG "CHECKPOINT: Using 0x%02x endpoint\n", dev->bulk_in_large_endpointAddr );
	retval = usb_bulk_msg(dev->udev,
			      usb_rcvbulkpipe(dev->udev, dev->bulk_in_large_endpointAddr),
		              dev->readBuffer.memory,
			      //dev->bulk_in_large_buffer,
			      count,
			      //min(dev->bulk_in_size, count),
			      &count, dev->readTimeout);
	}
	
	/* if the read was successful, copy the data to userspace */
	if (!retval) {
	//   if (count < 100) {
		if (copy_to_user(buffer, 
				 dev->readBuffer.memory, 
				 //dev->bulk_in_buffer,
				 count))
			retval = -EFAULT;
		else
			retval = count;
	 // } else {
	//	if (copy_to_user(buffer, 
	//			 dev->readBuffer.memory, 
				 //dev->bulk_in_large_buffer,
	//			 count))
	//		retval = -EFAULT;
	//	else
	//		retval = count;

	 // } 
	}

	printk(KERN_DEBUG "skel_read: buffer count: %d\n", count);
	printk(KERN_DEBUG "skel_read: buffer: ");
	for (i = 0; i < count; i++) {
		printk(KERN_DEBUG "0x%02x, ", buffer[i]);
	}
	printk(KERN_DEBUG "\n");

done:
        up(&dev->semaphore);

	printk(KERN_DEBUG "skel_read() - end\n");		

        return retval;


}

static ssize_t skel_write(struct file *file, const char __user *user_buffer, size_t count, loff_t *ppos)
{
        struct usb_skel *private;
        int bytesWritten;
        int ret;

	int i;

	int write_res;

        printk(KERN_DEBUG "skel_write() - begin\n");

	printk(KERN_DEBUG "skel_write: user_buffer count: %d\n", count);
	printk(KERN_DEBUG "skel_write: user_buffer: ");
	for (i = 0; i < count; i++) {
		printk(KERN_DEBUG "0x%02x, ", user_buffer[i]);
	}
	printk(KERN_DEBUG "\n");

        private = (struct usb_skel *) file->private_data;

        if (down_interruptible(&private->semaphore))
                return -ERESTARTSYS;

        if (skel_growBuffer(&private->writeBuffer, count)) {
                ret = -ENOMEM;
               goto done;
        }

        if (copy_from_user(private->writeBuffer.memory, user_buffer, count)) {
                ret = -EFAULT;
                goto done;
        }
	
//	printk(KERN_DEBUG "skel_write: usb_sndbulkpipe(): 0x%02x\n", usb_sndbulkpipe(private->udev, private->bulk_out_endpointAddr));
	
//	printk(KERN_DEBUG "skel_write: private->bulk_out_endpointAddr: 0x%02x\n", private->bulk_out_endpointAddr);
//	printk(KERN_DEBUG "skel_write: private->bulkWritePipe %d\n", private->bulkWritePipe);


	write_res = usb_bulk_msg(private->udev,
			 usb_sndbulkpipe(private->udev, private->bulk_out_endpointAddr),
                         private->writeBuffer.memory, 
			 count, 
			 &bytesWritten, private->writeTimeout); 
       
	 if ( write_res < 0) {
                ret = -EIO;
                goto done;
        }

        ret = bytesWritten;

done:
        up(&private->semaphore);
        printk(KERN_DEBUG "skel_write() - end\n");
        return ret;
}

static struct file_operations skel_fops = {
	.owner =	THIS_MODULE,
	.read =		skel_read,
	.write =	skel_write,
	.open =		skel_open,
	.release =	skel_release,
	.ioctl = 	skel_ioctl
};

/* 
 * usb class driver info in order to get a minor number from the usb core,
 * and to have the device registered with devfs and the driver core
 */
static struct usb_class_driver skel_class = {
	.name = "USBSCAN%d",
	.fops = &skel_fops,
	.minor_base = USB_SKEL_MINOR_BASE,
};


static int skel_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
	struct usb_skel *dev = NULL;
	struct usb_host_interface *iface_desc;
	struct usb_endpoint_descriptor *endpoint;
	//size_t buffer_size;
	int i;
	int retval = -ENOMEM;

	printk(KERN_DEBUG "skel_probe() - begin\n");

	/* allocate memory for our device state and initialize it */
	dev = kmalloc(sizeof(struct usb_skel), GFP_KERNEL);
	if (dev == NULL) {
		err("Out of memory\n");
		goto error;
	}
	memset(dev, 0x00, sizeof (*dev));
	kref_init(&dev->kref);

	dev->udev = usb_get_dev(interface_to_usbdev(interface));
	dev->interface = interface;

	//Use this driver only with Microtek scanners. Add other vendors, if their products were tested with this driver
        if (dev->udev->descriptor.idVendor != 0x05DA) {
                retval = -ENODEV;
		goto error;
	}

	/* set up the endpoint information */
	/* use only the first bulk-in and bulk-out endpoints */
        dev->controlReadPipe = usb_rcvctrlpipe(dev->udev, 0);
        dev->controlWritePipe = usb_sndctrlpipe(dev->udev, 0);


	iface_desc = interface->cur_altsetting;
	dev->pipeConfig.NumberOfPipes = iface_desc->desc.bNumEndpoints;
	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
		endpoint = &iface_desc->endpoint[i].desc;
		
		if (!dev->bulk_in_endpointAddr &&
		    (endpoint->bEndpointAddress & USB_DIR_IN) &&
		    ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
					== USB_ENDPOINT_XFER_BULK) &&
		    (endpoint->wMaxPacketSize == 32)) {
			/* we found a bulk in endpoint */
			//buffer_size = endpoint->wMaxPacketSize;
			//dev->bulk_in_size = buffer_size;
			dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
			//dev->bulk_in_buffer = kmalloc(buffer_size, GFP_KERNEL);
			//if (!dev->bulk_in_buffer) {
			//	err("Could not allocate bulk_in_buffer\n");
			//	goto error;
			//}
		}

	//	printk(KERN_DEBUG "endpoint->wMaxPacketSize = %d\n",  endpoint->wMaxPacketSize );
	//	printk(KERN_DEBUG "endpoint->bEndpointAddress = %d\n",  endpoint->bEndpointAddress );
		
		if (!dev->bulk_in_large_endpointAddr && 
		    (endpoint->bEndpointAddress & USB_DIR_IN) &&
		    ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
					== USB_ENDPOINT_XFER_BULK) &&
                    (endpoint->wMaxPacketSize == 64)) {
			dev->bulk_in_large_endpointAddr = endpoint->bEndpointAddress;	
		}
	

		if (!dev->bulk_out_endpointAddr &&
		    !(endpoint->bEndpointAddress & USB_DIR_IN) &&
		    ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
					== USB_ENDPOINT_XFER_BULK)) {
			/* we found a bulk out endpoint */
			dev->bulk_out_endpointAddr = endpoint->bEndpointAddress;
		}
	}

	if (!(dev->bulk_in_endpointAddr && dev->bulk_out_endpointAddr && dev->bulk_in_large_endpointAddr )) {
		err("Could not find bulk-in, bulk-in large and bulk-out endpoints\n");
		goto error;
	}

        dev->readTimeout = USB_CTRL_GET_TIMEOUT * HZ;
        dev->writeTimeout = USB_CTRL_SET_TIMEOUT * HZ;
        dev->interruptTimeout = USB_CTRL_GET_TIMEOUT * HZ;
        sema_init(&dev->semaphore, 1);

	/* save our data pointer in this interface device */
	usb_set_intfdata(interface, dev);

	/* we can register the device now, as it is ready */
	retval = usb_register_dev(interface, &skel_class);
	if (retval) {
		/* something prevented us from registering this driver */
		err("Not able to get a minor for this device.\n");
		usb_set_intfdata(interface, NULL);
		goto error;
	}

	skel_makeDeviceFiles(dev);

	/* let the user know what node this device is now attached to */
	info("USB Skeleton device now attached to USBSkel-%d\n", interface->minor);

	printk(KERN_DEBUG "skel_probe() - end\n");
	return 0;

error:
	if (dev)
		kref_put(&dev->kref, skel_delete);

	printk(KERN_DEBUG "skel_probe() - end\n");

	return retval;
}

static void skel_disconnect(struct usb_interface *interface)
{
	struct usb_skel *dev;
	int minor = interface->minor;

	/* prevent skel_open() from racing skel_disconnect() */
	lock_kernel();

	dev = usb_get_intfdata(interface);
	skel_deleteDeviceFiles(dev);
	usb_set_intfdata(interface, NULL);
	/* give back our minor */
	usb_deregister_dev(interface, &skel_class);
	unlock_kernel();
	/* decrement our usage count */
	kref_put(&dev->kref, skel_delete);

	info("USB Skeleton #%d now disconnected\n", minor);
}

static struct usb_driver skel_driver = {
	.name = "usbscan",
	.id_table = skel_table,
	.probe = skel_probe,
	.disconnect = skel_disconnect,
};


static int __init usb_skel_init(void)
{
	int result;

	/* register this driver with the USB subsystem */
	result = usb_register(&skel_driver);
	if (result)
		err("usb_register failed. Error number %d\n", result);

	return result;
}

static void __exit usb_skel_exit(void)
{
	/* deregister this driver with the USB subsystem */
	usb_deregister(&skel_driver);
}

module_init (usb_skel_init);
module_exit (usb_skel_exit);

MODULE_LICENSE("GPL");
