USB: add driver for iowarrior devices.
Greg Kroah-Hartman [Wed, 14 Feb 2007 21:40:14 +0000 (13:40 -0800)]
The ioctl is commented out for now, until we verify some userspace
application issues.

Cc: Christian Lucht <lucht@codemercs.com>
Cc: Robert Marquardt <marquardt@codemercs.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

drivers/usb/misc/Kconfig
drivers/usb/misc/Makefile
drivers/usb/misc/iowarrior.c [new file with mode: 0644]
include/linux/usb/iowarrior.h [new file with mode: 0644]

index 4907e8b..9c7eb61 100644 (file)
@@ -244,6 +244,20 @@ config USB_TRANCEVIBRATOR
          To compile this driver as a module, choose M here: the
          module will be called trancevibrator.
 
+config USB_IOWARRIOR
+       tristate "IO Warrior driver support"
+       depends on USB
+       help
+         Say Y here if you want to support the IO Warrior devices from Code
+         Mercenaries.  This includes support for the following devices:
+               IO Warrior 40
+               IO Warrior 24
+               IO Warrior 56
+               IO Warrior 24 Power Vampire
+
+         To compile this driver as a module, choose M here: the
+         module will be called iowarrior.
+
 config USB_TEST
        tristate "USB testing driver (DEVELOPMENT)"
        depends on USB && USB_DEVICEFS && EXPERIMENTAL
index dac2d5b..b68e6b7 100644 (file)
@@ -13,6 +13,7 @@ obj-$(CONFIG_USB_EMI26)               += emi26.o
 obj-$(CONFIG_USB_EMI62)                += emi62.o
 obj-$(CONFIG_USB_FTDI_ELAN)    += ftdi-elan.o
 obj-$(CONFIG_USB_IDMOUSE)      += idmouse.o
+obj-$(CONFIG_USB_IOWARRIOR)    += iowarrior.o
 obj-$(CONFIG_USB_LCD)          += usblcd.o
 obj-$(CONFIG_USB_LD)           += ldusb.o
 obj-$(CONFIG_USB_LED)          += usbled.o
diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c
new file mode 100644 (file)
index 0000000..d69665c
--- /dev/null
@@ -0,0 +1,925 @@
+/*
+ *  Native support for the I/O-Warrior USB devices
+ *
+ *  Copyright (c) 2003-2005  Code Mercenaries GmbH
+ *  written by Christian Lucht <lucht@codemercs.com>
+ *
+ *  based on
+
+ *  usb-skeleton.c by Greg Kroah-Hartman  <greg@kroah.com>
+ *  brlvger.c by Stephane Dalton  <sdalton@videotron.ca>
+ *           and St�hane Doyon   <s.doyon@videotron.ca>
+ *
+ *  Released under the GPLv2.
+ */
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/poll.h>
+#include <linux/version.h>
+#include <linux/usb/iowarrior.h>
+
+/* Version Information */
+#define DRIVER_VERSION "v0.4.0"
+#define DRIVER_AUTHOR "Christian Lucht <lucht@codemercs.com>"
+#define DRIVER_DESC "USB IO-Warrior driver (Linux 2.6.x)"
+
+#define USB_VENDOR_ID_CODEMERCS                1984
+/* low speed iowarrior */
+#define USB_DEVICE_ID_CODEMERCS_IOW40  0x1500
+#define USB_DEVICE_ID_CODEMERCS_IOW24  0x1501
+#define USB_DEVICE_ID_CODEMERCS_IOWPV1 0x1511
+#define USB_DEVICE_ID_CODEMERCS_IOWPV2 0x1512
+/* full speed iowarrior */
+#define USB_DEVICE_ID_CODEMERCS_IOW56  0x1503
+
+/* Get a minor range for your devices from the usb maintainer */
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+#define IOWARRIOR_MINOR_BASE   0
+#else
+#define IOWARRIOR_MINOR_BASE   208     // SKELETON_MINOR_BASE 192 + 16, not offical yet
+#endif
+
+/* interrupt input queue size */
+#define MAX_INTERRUPT_BUFFER 16
+/*
+   maximum number of urbs that are submitted for writes at the same time,
+   this applies to the IOWarrior56 only!
+   IOWarrior24 and IOWarrior40 use synchronous usb_control_msg calls.
+*/
+#define MAX_WRITES_IN_FLIGHT 4
+
+/* Use our own dbg macro */
+#undef dbg
+#define dbg( format, arg... ) do { if( debug ) printk( KERN_DEBUG __FILE__ ": " format "\n" , ## arg ); } while ( 0 )
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+/* Module parameters */
+static int debug = 0;
+module_param(debug, bool, 0644);
+MODULE_PARM_DESC(debug, "debug=1 enables debugging messages");
+
+static struct usb_driver iowarrior_driver;
+
+/*--------------*/
+/*     data     */
+/*--------------*/
+
+/* Structure to hold all of our device specific stuff */
+struct iowarrior {
+       struct mutex mutex;                     /* locks this structure */
+       struct usb_device *udev;                /* save off the usb device pointer */
+       struct usb_interface *interface;        /* the interface for this device */
+       unsigned char minor;                    /* the starting minor number for this device */
+       struct usb_endpoint_descriptor *int_out_endpoint;       /* endpoint for reading (needed for IOW56 only) */
+       struct usb_endpoint_descriptor *int_in_endpoint;        /* endpoint for reading */
+       struct urb *int_in_urb;         /* the urb for reading data */
+       unsigned char *int_in_buffer;   /* buffer for data to be read */
+       unsigned char serial_number;    /* to detect lost packages */
+       unsigned char *read_queue;      /* size is MAX_INTERRUPT_BUFFER * packet size */
+       wait_queue_head_t read_wait;
+       wait_queue_head_t write_wait;   /* wait-queue for writing to the device */
+       atomic_t write_busy;            /* number of write-urbs submitted */
+       atomic_t read_idx;
+       atomic_t intr_idx;
+       spinlock_t intr_idx_lock;       /* protects intr_idx */
+       atomic_t overflow_flag;         /* signals an index 'rollover' */
+       int present;                    /* this is 1 as long as the device is connected */
+       int opened;                     /* this is 1 if the device is currently open */
+       char chip_serial[9];            /* the serial number string of the chip connected */
+       int report_size;                /* number of bytes in a report */
+       u16 product_id;
+};
+
+/*--------------*/
+/*    globals   */
+/*--------------*/
+/* prevent races between open() and disconnect() */
+static DECLARE_MUTEX(disconnect_sem);
+
+/*
+ *  USB spec identifies 5 second timeouts.
+ */
+#define GET_TIMEOUT 5
+#define USB_REQ_GET_REPORT  0x01
+//#if 0
+static int usb_get_report(struct usb_device *dev,
+                         struct usb_host_interface *inter, unsigned char type,
+                         unsigned char id, void *buf, int size)
+{
+       return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+                              USB_REQ_GET_REPORT,
+                              USB_DIR_IN | USB_TYPE_CLASS |
+                              USB_RECIP_INTERFACE, (type << 8) + id,
+                              inter->desc.bInterfaceNumber, buf, size,
+                              GET_TIMEOUT);
+}
+//#endif
+
+#define USB_REQ_SET_REPORT 0x09
+
+static int usb_set_report(struct usb_interface *intf, unsigned char type,
+                         unsigned char id, void *buf, int size)
+{
+       return usb_control_msg(interface_to_usbdev(intf),
+                              usb_sndctrlpipe(interface_to_usbdev(intf), 0),
+                              USB_REQ_SET_REPORT,
+                              USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+                              (type << 8) + id,
+                              intf->cur_altsetting->desc.bInterfaceNumber, buf,
+                              size, 1);
+}
+
+/*---------------------*/
+/* driver registration */
+/*---------------------*/
+/* table of devices that work with this driver */
+static struct usb_device_id iowarrior_ids[] = {
+       {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW40)},
+       {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW24)},
+       {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOWPV1)},
+       {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOWPV2)},
+       {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW56)},
+       {}                      /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, iowarrior_ids);
+
+/*
+ * USB callback handler for reading data
+ */
+static void iowarrior_callback(struct urb *urb)
+{
+       struct iowarrior *dev = (struct iowarrior *)urb->context;
+       int intr_idx;
+       int read_idx;
+       int aux_idx;
+       int offset;
+       int status;
+
+       switch (urb->status) {
+       case 0:
+               /* success */
+               break;
+       case -ECONNRESET:
+       case -ENOENT:
+       case -ESHUTDOWN:
+               return;
+       default:
+               goto exit;
+       }
+
+       spin_lock(&dev->intr_idx_lock);
+       intr_idx = atomic_read(&dev->intr_idx);
+       /* aux_idx become previous intr_idx */
+       aux_idx = (intr_idx == 0) ? (MAX_INTERRUPT_BUFFER - 1) : (intr_idx - 1);
+       read_idx = atomic_read(&dev->read_idx);
+
+       /* queue is not empty and it's interface 0 */
+       if ((intr_idx != read_idx)
+           && (dev->interface->cur_altsetting->desc.bInterfaceNumber == 0)) {
+               /* + 1 for serial number */
+               offset = aux_idx * (dev->report_size + 1);
+               if (!memcmp
+                   (dev->read_queue + offset, urb->transfer_buffer,
+                    dev->report_size)) {
+                       /* equal values on interface 0 will be ignored */
+                       spin_unlock(&dev->intr_idx_lock);
+                       goto exit;
+               }
+       }
+
+       /* aux_idx become next intr_idx */
+       aux_idx = (intr_idx == (MAX_INTERRUPT_BUFFER - 1)) ? 0 : (intr_idx + 1);
+       if (read_idx == aux_idx) {
+               /* queue full, dropping oldest input */
+               read_idx = (++read_idx == MAX_INTERRUPT_BUFFER) ? 0 : read_idx;
+               atomic_set(&dev->read_idx, read_idx);
+               atomic_set(&dev->overflow_flag, 1);
+       }
+
+       /* +1 for serial number */
+       offset = intr_idx * (dev->report_size + 1);
+       memcpy(dev->read_queue + offset, urb->transfer_buffer,
+              dev->report_size);
+       *(dev->read_queue + offset + (dev->report_size)) = dev->serial_number++;
+
+       atomic_set(&dev->intr_idx, aux_idx);
+       spin_unlock(&dev->intr_idx_lock);
+       /* tell the blocking read about the new data */
+       wake_up_interruptible(&dev->read_wait);
+
+exit:
+       status = usb_submit_urb(urb, GFP_ATOMIC);
+       if (status)
+               dev_err(&dev->interface->dev, "%s - usb_submit_urb failed with result %d",
+                       __FUNCTION__, status);
+
+}
+
+/*
+ * USB Callback handler for write-ops
+ */
+static void iowarrior_write_callback(struct urb *urb)
+{
+       struct iowarrior *dev;
+       dev = (struct iowarrior *)urb->context;
+       /* sync/async unlink faults aren't errors */
+       if (urb->status &&
+           !(urb->status == -ENOENT ||
+             urb->status == -ECONNRESET || urb->status == -ESHUTDOWN)) {
+               dbg("%s - nonzero write bulk status received: %d",
+                   __func__, urb->status);
+       }
+       /* free up our allocated buffer */
+       usb_buffer_free(urb->dev, urb->transfer_buffer_length,
+                       urb->transfer_buffer, urb->transfer_dma);
+       /* tell a waiting writer the interrupt-out-pipe is available again */
+       atomic_dec(&dev->write_busy);
+       wake_up_interruptible(&dev->write_wait);
+}
+
+/**
+ *     iowarrior_delete
+ */
+static inline void iowarrior_delete(struct iowarrior *dev)
+{
+       dbg("%s - minor %d", __func__, dev->minor);
+       kfree(dev->int_in_buffer);
+       usb_free_urb(dev->int_in_urb);
+       kfree(dev->read_queue);
+       kfree(dev);
+}
+
+/*---------------------*/
+/* fops implementation */
+/*---------------------*/
+
+static int read_index(struct iowarrior *dev)
+{
+       int intr_idx, read_idx;
+
+       read_idx = atomic_read(&dev->read_idx);
+       intr_idx = atomic_read(&dev->intr_idx);
+
+       return (read_idx == intr_idx ? -1 : read_idx);
+}
+
+/**
+ *  iowarrior_read
+ */
+static ssize_t iowarrior_read(struct file *file, char __user *buffer,
+                             size_t count, loff_t *ppos)
+{
+       struct iowarrior *dev;
+       int read_idx;
+       int offset;
+
+       dev = (struct iowarrior *)file->private_data;
+
+       /* verify that the device wasn't unplugged */
+       if (dev == NULL || !dev->present)
+               return -ENODEV;
+
+       dbg("%s - minor %d, count = %zd", __func__, dev->minor, count);
+
+       /* read count must be packet size (+ time stamp) */
+       if ((count != dev->report_size)
+           && (count != (dev->report_size + 1)))
+               return -EINVAL;
+
+       /* repeat until no buffer overrun in callback handler occur */
+       do {
+               atomic_set(&dev->overflow_flag, 0);
+               if ((read_idx = read_index(dev)) == -1) {
+                       /* queue emty */
+                       if (file->f_flags & O_NONBLOCK)
+                               return -EAGAIN;
+                       else {
+                               //next line will return when there is either new data, or the device is unplugged
+                               int r = wait_event_interruptible(dev->read_wait,
+                                                                (!dev->present
+                                                                 || (read_idx =
+                                                                     read_index
+                                                                     (dev)) !=
+                                                                 -1));
+                               if (r) {
+                                       //we were interrupted by a signal
+                                       return -ERESTART;
+                               }
+                               if (!dev->present) {
+                                       //The device was unplugged
+                                       return -ENODEV;
+                               }
+                               if (read_idx == -1) {
+                                       // Can this happen ???
+                                       return 0;
+                               }
+                       }
+               }
+
+               offset = read_idx * (dev->report_size + 1);
+               if (copy_to_user(buffer, dev->read_queue + offset, count)) {
+                       return -EFAULT;
+               }
+       } while (atomic_read(&dev->overflow_flag));
+
+       read_idx = ++read_idx == MAX_INTERRUPT_BUFFER ? 0 : read_idx;
+       atomic_set(&dev->read_idx, read_idx);
+       return count;
+}
+
+/*
+ * iowarrior_write
+ */
+static ssize_t iowarrior_write(struct file *file,
+                              const char __user *user_buffer,
+                              size_t count, loff_t *ppos)
+{
+       struct iowarrior *dev;
+       int retval = 0;
+       char *buf = NULL;       /* for IOW24 and IOW56 we need a buffer */
+       struct urb *int_out_urb = NULL;
+
+       dev = (struct iowarrior *)file->private_data;
+
+       mutex_lock(&dev->mutex);
+       /* verify that the device wasn't unplugged */
+       if (dev == NULL || !dev->present) {
+               retval = -ENODEV;
+               goto exit;
+       }
+       dbg("%s - minor %d, count = %zd", __func__, dev->minor, count);
+       /* if count is 0 we're already done */
+       if (count == 0) {
+               retval = 0;
+               goto exit;
+       }
+       /* We only accept full reports */
+       if (count != dev->report_size) {
+               retval = -EINVAL;
+               goto exit;
+       }
+       switch (dev->product_id) {
+       case USB_DEVICE_ID_CODEMERCS_IOW24:
+       case USB_DEVICE_ID_CODEMERCS_IOWPV1:
+       case USB_DEVICE_ID_CODEMERCS_IOWPV2:
+       case USB_DEVICE_ID_CODEMERCS_IOW40:
+               /* IOW24 and IOW40 use a synchronous call */
+               buf = kmalloc(8, GFP_KERNEL);   /* 8 bytes are enough for both products */
+               if (!buf) {
+                       retval = -ENOMEM;
+                       goto exit;
+               }
+               if (copy_from_user(buf, user_buffer, count)) {
+                       retval = -EFAULT;
+                       kfree(buf);
+                       goto exit;
+               }
+               retval = usb_set_report(dev->interface, 2, 0, buf, count);
+               kfree(buf);
+               goto exit;
+               break;
+       case USB_DEVICE_ID_CODEMERCS_IOW56:
+               /* The IOW56 uses asynchronous IO and more urbs */
+               if (atomic_read(&dev->write_busy) == MAX_WRITES_IN_FLIGHT) {
+                       /* Wait until we are below the limit for submitted urbs */
+                       if (file->f_flags & O_NONBLOCK) {
+                               retval = -EAGAIN;
+                               goto exit;
+                       } else {
+                               retval = wait_event_interruptible(dev->write_wait,
+                                                                 (!dev->present || (atomic_read (&dev-> write_busy) < MAX_WRITES_IN_FLIGHT)));
+                               if (retval) {
+                                       /* we were interrupted by a signal */
+                                       retval = -ERESTART;
+                                       goto exit;
+                               }
+                               if (!dev->present) {
+                                       /* The device was unplugged */
+                                       retval = -ENODEV;
+                                       goto exit;
+                               }
+                               if (!dev->opened) {
+                                       /* We were closed while waiting for an URB */
+                                       retval = -ENODEV;
+                                       goto exit;
+                               }
+                       }
+               }
+               atomic_inc(&dev->write_busy);
+               int_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+               if (!int_out_urb) {
+                       retval = -ENOMEM;
+                       dbg("%s Unable to allocate urb ", __func__);
+                       goto error;
+               }
+               buf = usb_buffer_alloc(dev->udev, dev->report_size,
+                                      GFP_KERNEL, &int_out_urb->transfer_dma);
+               if (!buf) {
+                       retval = -ENOMEM;
+                       dbg("%s Unable to allocate buffer ", __func__);
+                       goto error;
+               }
+               usb_fill_int_urb(int_out_urb, dev->udev,
+                                usb_sndintpipe(dev->udev,
+                                               dev->int_out_endpoint->bEndpointAddress),
+                                buf, dev->report_size,
+                                iowarrior_write_callback, dev,
+                                dev->int_out_endpoint->bInterval);
+               int_out_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+               if (copy_from_user(buf, user_buffer, count)) {
+                       retval = -EFAULT;
+                       goto error;
+               }
+               retval = usb_submit_urb(int_out_urb, GFP_KERNEL);
+               if (retval) {
+                       dbg("%s submit error %d for urb nr.%d", __func__,
+                           retval, atomic_read(&dev->write_busy));
+                       goto error;
+               }
+               /* submit was ok */
+               retval = count;
+               usb_free_urb(int_out_urb);
+               goto exit;
+               break;
+       default:
+               /* what do we have here ? An unsupported Product-ID ? */
+               dev_err(&dev->interface->dev, "%s - not supported for product=0x%x",
+                       __FUNCTION__, dev->product_id);
+               retval = -EFAULT;
+               goto exit;
+               break;
+       }
+error:
+       usb_buffer_free(dev->udev, dev->report_size, buf,
+                       int_out_urb->transfer_dma);
+       usb_free_urb(int_out_urb);
+       atomic_dec(&dev->write_busy);
+       wake_up_interruptible(&dev->write_wait);
+exit:
+       mutex_unlock(&dev->mutex);
+       return retval;
+}
+
+/**
+ *     iowarrior_ioctl
+ */
+static int iowarrior_ioctl(struct inode *inode, struct file *file,
+                          unsigned int cmd, unsigned long arg)
+{
+       struct iowarrior *dev = NULL;
+       __u8 *buffer;
+       __u8 __user *user_buffer;
+       int retval;
+       int io_res;             /* checks for bytes read/written and copy_to/from_user results */
+
+       dev = (struct iowarrior *)file->private_data;
+       if (dev == NULL) {
+               return -ENODEV;
+       }
+
+       buffer = kzalloc(dev->report_size, GFP_KERNEL);
+       if (!buffer)
+               return -ENOMEM;
+
+       /* lock this object */
+       mutex_lock(&dev->mutex);
+
+       /* verify that the device wasn't unplugged */
+       if (!dev->present) {
+               mutex_unlock(&dev->mutex);
+               return -ENODEV;
+       }
+
+       dbg("%s - minor %d, cmd 0x%.4x, arg %ld", __func__, dev->minor, cmd,
+           arg);
+
+       retval = 0;
+       io_res = 0;
+       switch (cmd) {
+       case IOW_WRITE:
+               if (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW24 ||
+                   dev->product_id == USB_DEVICE_ID_CODEMERCS_IOWPV1 ||
+                   dev->product_id == USB_DEVICE_ID_CODEMERCS_IOWPV2 ||
+                   dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW40) {
+                       user_buffer = (__u8 __user *)arg;
+                       io_res = copy_from_user(buffer, user_buffer,
+                                               dev->report_size);
+                       if (io_res) {
+                               retval = -EFAULT;
+                       } else {
+                               io_res = usb_set_report(dev->interface, 2, 0,
+                                                       buffer,
+                                                       dev->report_size);
+                               if (io_res < 0)
+                                       retval = io_res;
+                       }
+               } else {
+                       retval = -EINVAL;
+                       dev_err(&dev->interface->dev,
+                               "ioctl 'IOW_WRITE' is not supported for product=0x%x.",
+                               dev->product_id);
+               }
+               break;
+       case IOW_READ:
+               user_buffer = (__u8 __user *)arg;
+               io_res = usb_get_report(dev->udev,
+                                       dev->interface->cur_altsetting, 1, 0,
+                                       buffer, dev->report_size);
+               if (io_res < 0)
+                       retval = io_res;
+               else {
+                       io_res = copy_to_user(user_buffer, buffer, dev->report_size);
+                       if (io_res < 0)
+                               retval = -EFAULT;
+               }
+               break;
+       case IOW_GETINFO:
+               {
+                       /* Report available information for the device */
+                       struct iowarrior_info info;
+                       /* needed for power consumption */
+                       struct usb_config_descriptor *cfg_descriptor = &dev->udev->actconfig->desc;
+
+                       /* directly from the descriptor */
+                       info.vendor = le16_to_cpu(dev->udev->descriptor.idVendor);
+                       info.product = dev->product_id;
+                       info.revision = le16_to_cpu(dev->udev->descriptor.bcdDevice);
+
+                       /* 0==UNKNOWN, 1==LOW(usb1.1) ,2=FULL(usb1.1), 3=HIGH(usb2.0) */
+                       info.speed = le16_to_cpu(dev->udev->speed);
+                       info.if_num = dev->interface->cur_altsetting->desc.bInterfaceNumber;
+                       info.report_size = dev->report_size;
+
+                       /* serial number string has been read earlier 8 chars or empty string */
+                       memcpy(info.serial, dev->chip_serial,
+                              sizeof(dev->chip_serial));
+                       if (cfg_descriptor == NULL) {
+                               info.power = -1;        /* no information available */
+                       } else {
+                               /* the MaxPower is stored in units of 2mA to make it fit into a byte-value */
+                               info.power = cfg_descriptor->bMaxPower * 2;
+                       }
+                       io_res = copy_to_user((struct iowarrior_info __user *)arg, &info,
+                                        sizeof(struct iowarrior_info));
+                       if (io_res < 0)
+                               retval = -EFAULT;
+                       break;
+               }
+       default:
+               /* return that we did not understand this ioctl call */
+               retval = -ENOTTY;
+               break;
+       }
+
+       /* unlock the device */
+       mutex_unlock(&dev->mutex);
+       return retval;
+}
+
+/**
+ *     iowarrior_open
+ */
+static int iowarrior_open(struct inode *inode, struct file *file)
+{
+       struct iowarrior *dev = NULL;
+       struct usb_interface *interface;
+       int subminor;
+       int retval = 0;
+
+       dbg("%s", __func__);
+
+       subminor = iminor(inode);
+
+       /* prevent disconnects */
+       down(&disconnect_sem);
+
+       interface = usb_find_interface(&iowarrior_driver, subminor);
+       if (!interface) {
+               err("%s - error, can't find device for minor %d", __FUNCTION__,
+                   subminor);
+               retval = -ENODEV;
+               goto out;
+       }
+
+       dev = usb_get_intfdata(interface);
+       if (!dev) {
+               retval = -ENODEV;
+               goto out;
+       }
+
+       /* Only one process can open each device, no sharing. */
+       if (dev->opened) {
+               retval = -EBUSY;
+               goto out;
+       }
+
+       /* setup interrupt handler for receiving values */
+       if ((retval = usb_submit_urb(dev->int_in_urb, GFP_KERNEL)) < 0) {
+               dev_err(&interface->dev, "Error %d while submitting URB\n", retval);
+               retval = -EFAULT;
+               goto out;
+       }
+       /* increment our usage count for the driver */
+       ++dev->opened;
+       /* save our object in the file's private structure */
+       file->private_data = dev;
+       retval = 0;
+
+out:
+       up(&disconnect_sem);
+       return retval;
+}
+
+/**
+ *     iowarrior_release
+ */
+static int iowarrior_release(struct inode *inode, struct file *file)
+{
+       struct iowarrior *dev;
+       int retval = 0;
+
+       dev = (struct iowarrior *)file->private_data;
+       if (dev == NULL) {
+               return -ENODEV;
+       }
+
+       dbg("%s - minor %d", __func__, dev->minor);
+
+       /* lock our device */
+       mutex_lock(&dev->mutex);
+
+       if (dev->opened <= 0) {
+               retval = -ENODEV;       /* close called more than once */
+               mutex_unlock(&dev->mutex);
+       } else {
+               dev->opened = 0;        /* we're closeing now */
+               retval = 0;
+               if (dev->present) {
+                       /*
+                          The device is still connected so we only shutdown
+                          pending read-/write-ops.
+                        */
+                       usb_kill_urb(dev->int_in_urb);
+                       wake_up_interruptible(&dev->read_wait);
+                       wake_up_interruptible(&dev->write_wait);
+                       mutex_unlock(&dev->mutex);
+               } else {
+                       /* The device was unplugged, cleanup resources */
+                       mutex_unlock(&dev->mutex);
+                       iowarrior_delete(dev);
+               }
+       }
+       return retval;
+}
+
+static unsigned iowarrior_poll(struct file *file, poll_table * wait)
+{
+       struct iowarrior *dev = file->private_data;
+       unsigned int mask = 0;
+
+       if (!dev->present)
+               return POLLERR | POLLHUP;
+
+       poll_wait(file, &dev->read_wait, wait);
+       poll_wait(file, &dev->write_wait, wait);
+
+       if (!dev->present)
+               return POLLERR | POLLHUP;
+
+       if (read_index(dev) != -1)
+               mask |= POLLIN | POLLRDNORM;
+
+       if (atomic_read(&dev->write_busy) < MAX_WRITES_IN_FLIGHT)
+               mask |= POLLOUT | POLLWRNORM;
+       return mask;
+}
+
+/*
+ * File operations needed when we register this driver.
+ * This assumes that this driver NEEDS file operations,
+ * of course, which means that the driver is expected
+ * to have a node in the /dev directory. If the USB
+ * device were for a network interface then the driver
+ * would use "struct net_driver" instead, and a serial
+ * device would use "struct tty_driver".
+ */
+static struct file_operations iowarrior_fops = {
+       .owner = THIS_MODULE,
+       .write = iowarrior_write,
+       .read = iowarrior_read,
+       .ioctl = iowarrior_ioctl,
+       .open = iowarrior_open,
+       .release = iowarrior_release,
+       .poll = iowarrior_poll,
+};
+
+/*
+ * 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 iowarrior_class = {
+       .name = "iowarrior%d",
+       .fops = &iowarrior_fops,
+       .minor_base = IOWARRIOR_MINOR_BASE,
+};
+
+/*---------------------------------*/
+/*  probe and disconnect functions */
+/*---------------------------------*/
+/**
+ *     iowarrior_probe
+ *
+ *     Called by the usb core when a new device is connected that it thinks
+ *     this driver might be interested in.
+ */
+static int iowarrior_probe(struct usb_interface *interface,
+                          const struct usb_device_id *id)
+{
+       struct usb_device *udev = interface_to_usbdev(interface);
+       struct iowarrior *dev = NULL;
+       struct usb_host_interface *iface_desc;
+       struct usb_endpoint_descriptor *endpoint;
+       int i;
+       int retval = -ENOMEM;
+       int idele = 0;
+
+       /* allocate memory for our device state and intialize it */
+       dev = kzalloc(sizeof(struct iowarrior), GFP_KERNEL);
+       if (dev == NULL) {
+               dev_err(&interface->dev, "Out of memory");
+               return retval;
+       }
+
+       mutex_init(&dev->mutex);
+
+       atomic_set(&dev->intr_idx, 0);
+       atomic_set(&dev->read_idx, 0);
+       spin_lock_init(&dev->intr_idx_lock);
+       atomic_set(&dev->overflow_flag, 0);
+       init_waitqueue_head(&dev->read_wait);
+       atomic_set(&dev->write_busy, 0);
+       init_waitqueue_head(&dev->write_wait);
+
+       dev->udev = udev;
+       dev->interface = interface;
+
+       iface_desc = interface->cur_altsetting;
+       dev->product_id = le16_to_cpu(udev->descriptor.idProduct);
+
+       /* set up the endpoint information */
+       for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+               endpoint = &iface_desc->endpoint[i].desc;
+
+               if (usb_endpoint_is_int_in(endpoint))
+                       dev->int_in_endpoint = endpoint;
+               if (usb_endpoint_is_int_out(endpoint))
+                       /* this one will match for the IOWarrior56 only */
+                       dev->int_out_endpoint = endpoint;
+       }
+       /* we have to check the report_size often, so remember it in the endianess suitable for our machine */
+       dev->report_size = le16_to_cpu(dev->int_in_endpoint->wMaxPacketSize);
+       if ((dev->interface->cur_altsetting->desc.bInterfaceNumber == 0) &&
+           (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW56))
+               /* IOWarrior56 has wMaxPacketSize different from report size */
+               dev->report_size = 7;
+
+       /* create the urb and buffer for reading */
+       dev->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+       if (!dev->int_in_urb) {
+               dev_err(&interface->dev, "Couldn't allocate interrupt_in_urb\n");
+               goto error;
+       }
+       dev->int_in_buffer = kmalloc(dev->report_size, GFP_KERNEL);
+       if (!dev->int_in_buffer) {
+               dev_err(&interface->dev, "Couldn't allocate int_in_buffer\n");
+               goto error;
+       }
+       usb_fill_int_urb(dev->int_in_urb, dev->udev,
+                        usb_rcvintpipe(dev->udev,
+                                       dev->int_in_endpoint->bEndpointAddress),
+                        dev->int_in_buffer, dev->report_size,
+                        iowarrior_callback, dev,
+                        dev->int_in_endpoint->bInterval);
+       /* create an internal buffer for interrupt data from the device */
+       dev->read_queue =
+           kmalloc(((dev->report_size + 1) * MAX_INTERRUPT_BUFFER),
+                   GFP_KERNEL);
+       if (!dev->read_queue) {
+               dev_err(&interface->dev, "Couldn't allocate read_queue\n");
+               goto error;
+       }
+       /* Get the serial-number of the chip */
+       memset(dev->chip_serial, 0x00, sizeof(dev->chip_serial));
+       usb_string(udev, udev->descriptor.iSerialNumber, dev->chip_serial,
+                  sizeof(dev->chip_serial));
+       if (strlen(dev->chip_serial) != 8)
+               memset(dev->chip_serial, 0x00, sizeof(dev->chip_serial));
+
+       /* Set the idle timeout to 0, if this is interface 0 */
+       if (dev->interface->cur_altsetting->desc.bInterfaceNumber == 0) {
+               idele = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                                       0x0A,
+                                       USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0,
+                                       0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+               dbg("idele = %d", idele);
+       }
+       /* allow device read and ioctl */
+       dev->present = 1;
+
+       /* we can register the device now, as it is ready */
+       usb_set_intfdata(interface, dev);
+
+       retval = usb_register_dev(interface, &iowarrior_class);
+       if (retval) {
+               /* something prevented us from registering this driver */
+               dev_err(&interface->dev, "Not able to get a minor for this device.\n");
+               usb_set_intfdata(interface, NULL);
+               goto error;
+       }
+
+       dev->minor = interface->minor;
+
+       /* let the user know what node this device is now attached to */
+       dev_info(&interface->dev, "IOWarrior product=0x%x, serial=%s interface=%d "
+                "now attached to iowarrior%d\n", dev->product_id, dev->chip_serial,
+                iface_desc->desc.bInterfaceNumber, dev->minor - IOWARRIOR_MINOR_BASE);
+       return retval;
+
+error:
+       iowarrior_delete(dev);
+       return retval;
+}
+
+/**
+ *     iowarrior_disconnect
+ *
+ *     Called by the usb core when the device is removed from the system.
+ */
+static void iowarrior_disconnect(struct usb_interface *interface)
+{
+       struct iowarrior *dev;
+       int minor;
+
+       /* prevent races with open() */
+       down(&disconnect_sem);
+
+       dev = usb_get_intfdata(interface);
+       usb_set_intfdata(interface, NULL);
+
+       mutex_lock(&dev->mutex);
+
+       minor = dev->minor;
+
+       /* give back our minor */
+       usb_deregister_dev(interface, &iowarrior_class);
+
+       /* prevent device read, write and ioctl */
+       dev->present = 0;
+
+       mutex_unlock(&dev->mutex);
+
+       if (dev->opened) {
+               /* There is a process that holds a filedescriptor to the device ,
+                  so we only shutdown read-/write-ops going on.
+                  Deleting the device is postponed until close() was called.
+                */
+               usb_kill_urb(dev->int_in_urb);
+               wake_up_interruptible(&dev->read_wait);
+               wake_up_interruptible(&dev->write_wait);
+       } else {
+               /* no process is using the device, cleanup now */
+               iowarrior_delete(dev);
+       }
+       up(&disconnect_sem);
+
+       dev_info(&interface->dev, "I/O-Warror #%d now disconnected\n",
+                minor - IOWARRIOR_MINOR_BASE);
+}
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver iowarrior_driver = {
+       .name = "iowarrior",
+       .probe = iowarrior_probe,
+       .disconnect = iowarrior_disconnect,
+       .id_table = iowarrior_ids,
+};
+
+static int __init iowarrior_init(void)
+{
+       return usb_register(&iowarrior_driver);
+}
+
+static void __exit iowarrior_exit(void)
+{
+       usb_deregister(&iowarrior_driver);
+}
+
+module_init(iowarrior_init);
+module_exit(iowarrior_exit);
diff --git a/include/linux/usb/iowarrior.h b/include/linux/usb/iowarrior.h
new file mode 100644 (file)
index 0000000..cbbe020
--- /dev/null
@@ -0,0 +1,33 @@
+#ifndef _IOWARRIOR_H_
+#define _IOWARRIOR_H_
+
+#define CODEMERCS_MAGIC_NUMBER 0xC0    /* like COde Mercenaries */
+
+/* Define the ioctl commands for reading and writing data */
+#define IOW_WRITE      _IOW(CODEMERCS_MAGIC_NUMBER, 1, __u8 *)
+#define IOW_READ       _IOW(CODEMERCS_MAGIC_NUMBER, 2, __u8 *)
+
+/*
+   A struct for available device info which is read
+   with the ioctl IOW_GETINFO.
+   To be compatible with 2.4 userspace which didn't have an easy way to get
+   this information.
+*/
+struct iowarrior_info {
+       __u32 vendor;           /* vendor id : supposed to be USB_VENDOR_ID_CODEMERCS in all cases */
+       __u32 product;          /* product id : depends on type of chip (USB_DEVICE_ID_CODEMERCS_XXXXX) */
+       __u8 serial[9];         /* the serial number of our chip (if a serial-number is not available this is empty string) */
+       __u32 revision;         /* revision number of the chip */
+       __u32 speed;            /* USB-speed of the device (0=UNKNOWN, 1=LOW, 2=FULL 3=HIGH) */
+       __u32 power;            /* power consumption of the device in mA */
+       __u32 if_num;           /* the number of the endpoint */
+       __u32 report_size;      /* size of the data-packets on this interface */
+};
+
+/*
+  Get some device-information (product-id , serial-number etc.)
+  in order to identify a chip.
+*/
+#define IOW_GETINFO _IOR(CODEMERCS_MAGIC_NUMBER, 3, struct iowarrior_info)
+
+#endif  /* _IOWARRIOR_H_ */