usb: vstusb.c : new driver for spectrometers used by Vernier Software & Technology...
authorStephen Ware <stephen.ware@eqware.net>
Tue, 30 Sep 2008 18:39:38 +0000 (11:39 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 17 Oct 2008 21:41:01 +0000 (14:41 -0700)
This patch adds the vstusb driver to the drivers/usb/misc directory.
This driver provides support for Vernier Software & Technology
spectrometers, all made by Ocean Optics. The driver provides both IOCTL
and read()/write() methods for sending raw data to spectrometers across
the bulk channel. Each method allows for a configured timeout.

From: Stephen Ware <stephen.ware@eqware.net>
Signed-off-by: Dennis O'Brien <dennis.obrien@eqware.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
Documentation/ioctl-number.txt
drivers/usb/misc/Kconfig
drivers/usb/misc/Makefile
drivers/usb/misc/vstusb.c [new file with mode: 0644]
include/linux/usb/Kbuild
include/linux/usb/vstusb.h [new file with mode: 0644]

index f8deb85eef6e4b7fd45a9865486841ce876aa8f5..b880ce5dbd33f2b08b5bd0affbb685b6ec59759b 100644 (file)
@@ -92,6 +92,7 @@ Code  Seq#    Include File            Comments
 'J'    00-1F   drivers/scsi/gdth_ioctl.h
 'K'    all     linux/kd.h
 'L'    00-1F   linux/loop.h
+'L'    20-2F   driver/usb/misc/vstusb.h
 'L'    E0-FF   linux/ppdd.h            encrypted disk device driver
                                        <http://linux01.gwdg.de/~alatham/ppdd.html>
 'M'    all     linux/soundcard.h
index 25e1157ab176b6ba827aaf60ce9d7fbd399625e1..e463db5d818849860a23cb855e82a5cfd86624f4 100644 (file)
@@ -280,3 +280,18 @@ config USB_ISIGHTFW
          The firmware for this driver must be extracted from the MacOS
          driver beforehand. Tools for doing so are available at
          http://bersace03.free.fr
+
+config USB_VST
+       tristate "USB VST driver"
+       depends on USB
+       help
+         This driver is intended for Vernier Software Technologies
+         bulk usb devices such as their Ocean-Optics spectrometers or
+         Labquest.
+         It is a bulk channel driver with configurable read and write
+         timeouts.
+
+         To compile this driver as a module, choose M here: the
+         module will be called vstusb.
+
+
index 39ce4a16b3d416ed978a8a64b32e05c5ce7ce7a7..1334f7bdd7bef16020f767a60133cef3b2654c4e 100644 (file)
@@ -27,6 +27,7 @@ obj-$(CONFIG_USB_TEST)                += usbtest.o
 obj-$(CONFIG_USB_TRANCEVIBRATOR)       += trancevibrator.o
 obj-$(CONFIG_USB_USS720)       += uss720.o
 obj-$(CONFIG_USB_SEVSEG)       += usbsevseg.o
+obj-$(CONFIG_USB_VST)          += vstusb.o
 
 obj-$(CONFIG_USB_SISUSBVGA)    += sisusbvga/
 
diff --git a/drivers/usb/misc/vstusb.c b/drivers/usb/misc/vstusb.c
new file mode 100644 (file)
index 0000000..5ad75e4
--- /dev/null
@@ -0,0 +1,768 @@
+/*****************************************************************************
+ *  File: drivers/usb/misc/vstusb.c
+ *
+ *  Purpose: Support for the bulk USB Vernier Spectrophotometers
+ *
+ *  Author:     Johnnie Peters
+ *              Axian Consulting
+ *              Beaverton, OR, USA 97005
+ *
+ *  Modified by:     EQware Engineering, Inc.
+ *                   Oregon City, OR, USA 97045
+ *
+ *  Copyright:  2007, 2008
+ *              Vernier Software & Technology
+ *              Beaverton, OR, USA 97005
+ *
+ *  Web:        www.vernier.com
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *****************************************************************************/
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+
+#include <linux/usb/vstusb.h>
+
+#define DRIVER_VERSION "VST USB Driver Version 1.5"
+#define DRIVER_DESC "Vernier Software Technology Bulk USB Driver"
+
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+       #define VSTUSB_MINOR_BASE       0
+#else
+       #define VSTUSB_MINOR_BASE       199
+#endif
+
+#define USB_VENDOR_OCEANOPTICS 0x2457
+#define USB_VENDOR_VERNIER     0x08F7  /* Vernier Software & Technology */
+
+#define USB_PRODUCT_USB2000    0x1002
+#define USB_PRODUCT_ADC1000_FW 0x1003  /* firmware download (renumerates) */
+#define USB_PRODUCT_ADC1000    0x1004
+#define USB_PRODUCT_HR2000_FW  0x1009  /* firmware download (renumerates) */
+#define USB_PRODUCT_HR2000     0x100A
+#define USB_PRODUCT_HR4000_FW  0x1011  /* firmware download (renumerates) */
+#define USB_PRODUCT_HR4000     0x1012
+#define USB_PRODUCT_USB650     0x1014  /* "Red Tide" */
+#define USB_PRODUCT_QE65000    0x1018
+#define USB_PRODUCT_USB4000    0x1022
+#define USB_PRODUCT_USB325     0x1024  /* "Vernier Spectrometer" */
+
+#define USB_PRODUCT_LABPRO     0x0001
+#define USB_PRODUCT_LABQUEST   0x0005
+
+static struct usb_device_id id_table[] = {
+       { USB_DEVICE(USB_VENDOR_OCEANOPTICS, USB_PRODUCT_USB2000)},
+       { USB_DEVICE(USB_VENDOR_OCEANOPTICS, USB_PRODUCT_HR4000)},
+       { USB_DEVICE(USB_VENDOR_OCEANOPTICS, USB_PRODUCT_USB650)},
+       { USB_DEVICE(USB_VENDOR_OCEANOPTICS, USB_PRODUCT_USB4000)},
+       { USB_DEVICE(USB_VENDOR_OCEANOPTICS, USB_PRODUCT_USB325)},
+       { USB_DEVICE(USB_VENDOR_VERNIER, USB_PRODUCT_LABQUEST)},
+       { USB_DEVICE(USB_VENDOR_VERNIER, USB_PRODUCT_LABPRO)},
+       {},
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+struct vstusb_device {
+       struct mutex            lock;
+       struct usb_device       *usb_dev;
+       char                    present;
+       char                    isopen;
+       struct usb_anchor       submitted;
+       int                     rd_pipe;
+       int                     rd_timeout_ms;
+       int                     wr_pipe;
+       int                     wr_timeout_ms;
+};
+
+static struct usb_driver vstusb_driver;
+
+static int vstusb_open(struct inode *inode, struct file *file)
+{
+       struct vstusb_device *vstdev;
+       struct usb_interface *interface;
+
+       interface = usb_find_interface(&vstusb_driver, iminor(inode));
+
+       if (!interface) {
+               printk(KERN_ERR KBUILD_MODNAME
+                      ": %s - error, can't find device for minor %d\n",
+                      __func__, iminor(inode));
+               return -ENODEV;
+       }
+
+       vstdev = usb_get_intfdata(interface);
+
+       if (!vstdev)
+               return -ENODEV;
+
+       /* lock this device */
+       mutex_lock(&vstdev->lock);
+
+       /* can only open one time */
+       if ((!vstdev->present) || (vstdev->isopen)) {
+               mutex_unlock(&vstdev->lock);
+               return -EBUSY;
+       }
+
+       vstdev->isopen = 1;
+
+       /* save device in the file's private structure */
+       file->private_data = vstdev;
+
+       dev_dbg(&vstdev->usb_dev->dev, "%s: opened\n", __func__);
+
+       mutex_unlock(&vstdev->lock);
+
+       return 0;
+}
+
+static int vstusb_close(struct inode *inode, struct file *file)
+{
+       struct vstusb_device *vstdev;
+
+       vstdev = file->private_data;
+
+       if (vstdev == NULL)
+               return -ENODEV;
+
+       mutex_lock(&vstdev->lock);
+
+       vstdev->isopen = 0;
+       file->private_data = NULL;
+
+       /* if device is no longer present */
+       if (!vstdev->present) {
+               mutex_unlock(&vstdev->lock);
+               kfree(vstdev);
+       } else
+               mutex_unlock(&vstdev->lock);
+
+       return 0;
+}
+
+static void usb_api_blocking_completion(struct urb *urb)
+{
+       struct completion *completeit = urb->context;
+
+       complete(completeit);
+}
+
+static int vstusb_fill_and_send_urb(struct urb *urb,
+                                   struct usb_device *usb_dev,
+                                   unsigned int pipe, void *data,
+                                   unsigned int len, struct completion *done)
+{
+       struct usb_host_endpoint *ep;
+       struct usb_host_endpoint **hostep;
+       unsigned int pipend;
+
+       int status;
+
+       hostep = usb_pipein(pipe) ? usb_dev->ep_in : usb_dev->ep_out;
+       pipend = usb_pipeendpoint(pipe);
+       ep = hostep[pipend];
+
+       if (!ep || (len == 0))
+               return -EINVAL;
+
+       if ((ep->desc.bmAttributes & USB_ENDPOINT_XFERTYPE_MASK)
+           == USB_ENDPOINT_XFER_INT) {
+               pipe = (pipe & ~(3 << 30)) | (PIPE_INTERRUPT << 30);
+               usb_fill_int_urb(urb, usb_dev, pipe, data, len,
+                                (usb_complete_t)usb_api_blocking_completion,
+                                NULL, ep->desc.bInterval);
+       } else
+               usb_fill_bulk_urb(urb, usb_dev, pipe, data, len,
+                                 (usb_complete_t)usb_api_blocking_completion,
+                                 NULL);
+
+       init_completion(done);
+       urb->context = done;
+       urb->actual_length = 0;
+       status = usb_submit_urb(urb, GFP_KERNEL);
+
+       return status;
+}
+
+static int vstusb_complete_urb(struct urb *urb, struct completion *done,
+                              int timeout, int *actual_length)
+{
+       unsigned long expire;
+       int status;
+
+       expire = timeout ? msecs_to_jiffies(timeout) : MAX_SCHEDULE_TIMEOUT;
+       if (!wait_for_completion_interruptible_timeout(done, expire)) {
+               usb_kill_urb(urb);
+               status = urb->status == -ENOENT ? -ETIMEDOUT : urb->status;
+
+               dev_dbg(&urb->dev->dev,
+                       "%s timed out on ep%d%s len=%d/%d, urb status = %d\n",
+                       current->comm,
+                       usb_pipeendpoint(urb->pipe),
+                       usb_pipein(urb->pipe) ? "in" : "out",
+                       urb->actual_length,
+                       urb->transfer_buffer_length,
+                       urb->status);
+
+       } else {
+               if (signal_pending(current)) {
+                       /* if really an error */
+                       if (urb->status && !((urb->status == -ENOENT)     ||
+                                            (urb->status == -ECONNRESET) ||
+                                            (urb->status == -ESHUTDOWN))) {
+                               status = -EINTR;
+                               usb_kill_urb(urb);
+                       } else {
+                               status = 0;
+                       }
+
+                       dev_dbg(&urb->dev->dev,
+                               "%s: signal pending on ep%d%s len=%d/%d,"
+                               "urb status = %d\n",
+                               current->comm,
+                               usb_pipeendpoint(urb->pipe),
+                               usb_pipein(urb->pipe) ? "in" : "out",
+                               urb->actual_length,
+                               urb->transfer_buffer_length,
+                               urb->status);
+
+               } else {
+                       status = urb->status;
+               }
+       }
+
+       if (actual_length)
+               *actual_length = urb->actual_length;
+
+       return status;
+}
+
+static ssize_t vstusb_read(struct file *file, char __user *buffer,
+                          size_t count, loff_t *ppos)
+{
+       struct vstusb_device *vstdev;
+       int cnt = -1;
+       void *buf;
+       int retval = 0;
+
+       struct urb              *urb;
+       struct usb_device       *dev;
+       unsigned int            pipe;
+       int                     timeout;
+
+       DECLARE_COMPLETION_ONSTACK(done);
+
+       vstdev = file->private_data;
+
+       if (vstdev == NULL)
+               return -ENODEV;
+
+       /* verify that we actually want to read some data */
+       if (count == 0)
+               return -EINVAL;
+
+       /* lock this object */
+       if (mutex_lock_interruptible(&vstdev->lock))
+               return -ERESTARTSYS;
+
+       /* anyone home */
+       if (!vstdev->present) {
+               mutex_unlock(&vstdev->lock);
+               printk(KERN_ERR KBUILD_MODNAME
+                      ": %s: device not present\n", __func__);
+               return -ENODEV;
+       }
+
+       /* pull out the necessary data */
+       dev =     vstdev->usb_dev;
+       pipe =    usb_rcvbulkpipe(dev, vstdev->rd_pipe);
+       timeout = vstdev->rd_timeout_ms;
+
+       buf = kmalloc(count, GFP_KERNEL);
+       if (buf == NULL) {
+               mutex_unlock(&vstdev->lock);
+               return -ENOMEM;
+       }
+
+       urb = usb_alloc_urb(0, GFP_KERNEL);
+       if (!urb) {
+               kfree(buf);
+               mutex_unlock(&vstdev->lock);
+               return -ENOMEM;
+       }
+
+       usb_anchor_urb(urb, &vstdev->submitted);
+       retval = vstusb_fill_and_send_urb(urb, dev, pipe, buf, count, &done);
+       mutex_unlock(&vstdev->lock);
+       if (retval) {
+               usb_unanchor_urb(urb);
+               dev_err(&dev->dev, "%s: error %d filling and sending urb %d\n",
+                       __func__, retval, pipe);
+               goto exit;
+       }
+
+       retval = vstusb_complete_urb(urb, &done, timeout, &cnt);
+       if (retval) {
+               dev_err(&dev->dev, "%s: error %d completing urb %d\n",
+                       __func__, retval, pipe);
+               goto exit;
+       }
+
+       if (copy_to_user(buffer, buf, cnt)) {
+               dev_err(&dev->dev, "%s: can't copy_to_user\n", __func__);
+               retval = -EFAULT;
+       } else {
+               retval = cnt;
+               dev_dbg(&dev->dev, "%s: read %d bytes from pipe %d\n",
+                       __func__, cnt, pipe);
+       }
+
+exit:
+       usb_free_urb(urb);
+       kfree(buf);
+       return retval;
+}
+
+static ssize_t vstusb_write(struct file *file, const char __user *buffer,
+                           size_t count, loff_t *ppos)
+{
+       struct vstusb_device *vstdev;
+       int cnt = -1;
+       void *buf;
+       int retval = 0;
+
+       struct urb              *urb;
+       struct usb_device       *dev;
+       unsigned int            pipe;
+       int                     timeout;
+
+       DECLARE_COMPLETION_ONSTACK(done);
+
+       vstdev = file->private_data;
+
+       if (vstdev == NULL)
+               return -ENODEV;
+
+       /* verify that we actually have some data to write */
+       if (count == 0)
+               return retval;
+
+       /* lock this object */
+       if (mutex_lock_interruptible(&vstdev->lock))
+               return -ERESTARTSYS;
+
+       /* anyone home */
+       if (!vstdev->present) {
+               mutex_unlock(&vstdev->lock);
+               printk(KERN_ERR KBUILD_MODNAME
+                      ": %s: device not present\n", __func__);
+               return -ENODEV;
+       }
+
+       /* pull out the necessary data */
+       dev =     vstdev->usb_dev;
+       pipe =    usb_sndbulkpipe(dev, vstdev->wr_pipe);
+       timeout = vstdev->wr_timeout_ms;
+
+       buf = kmalloc(count, GFP_KERNEL);
+       if (buf == NULL) {
+               mutex_unlock(&vstdev->lock);
+               return -ENOMEM;
+       }
+
+       urb = usb_alloc_urb(0, GFP_KERNEL);
+       if (!urb) {
+               kfree(buf);
+               mutex_unlock(&vstdev->lock);
+               return -ENOMEM;
+       }
+
+       if (copy_from_user(buf, buffer, count)) {
+               dev_err(&dev->dev, "%s: can't copy_from_user\n", __func__);
+               retval = -EFAULT;
+               goto exit;
+       }
+
+       usb_anchor_urb(urb, &vstdev->submitted);
+       retval = vstusb_fill_and_send_urb(urb, dev, pipe, buf, count, &done);
+       mutex_unlock(&vstdev->lock);
+       if (retval) {
+               usb_unanchor_urb(urb);
+               dev_err(&dev->dev, "%s: error %d filling and sending urb %d\n",
+                       __func__, retval, pipe);
+               goto exit;
+       }
+
+       retval = vstusb_complete_urb(urb, &done, timeout, &cnt);
+       if (retval) {
+               dev_err(&dev->dev, "%s: error %d completing urb %d\n",
+                       __func__, retval, pipe);
+               goto exit;
+       } else {
+               retval = cnt;
+               dev_dbg(&dev->dev, "%s: sent %d bytes to pipe %d\n",
+                       __func__, cnt, pipe);
+       }
+
+exit:
+       usb_free_urb(urb);
+       kfree(buf);
+       return retval;
+}
+
+static long vstusb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+       int retval = 0;
+       int cnt = -1;
+       void __user *data = (void __user *)arg;
+       struct vstusb_args usb_data;
+
+       struct vstusb_device *vstdev;
+       void *buffer = NULL; /* must be initialized. buffer is
+                             * referenced on exit but not all
+                             * ioctls allocate it */
+
+       struct urb              *urb = NULL; /* must be initialized. urb is
+                                             * referenced on exit but not all
+                                             * ioctls allocate it */
+       struct usb_device       *dev;
+       unsigned int            pipe;
+       int                     timeout;
+
+       DECLARE_COMPLETION_ONSTACK(done);
+
+       vstdev = file->private_data;
+
+       if (_IOC_TYPE(cmd) != VST_IOC_MAGIC) {
+               dev_warn(&vstdev->usb_dev->dev,
+                        "%s: ioctl command %x, bad ioctl magic %x, "
+                        "expected %x\n", __func__, cmd,
+                        _IOC_TYPE(cmd), VST_IOC_MAGIC);
+               return -EINVAL;
+       }
+
+       if (vstdev == NULL)
+               return -ENODEV;
+
+       if (copy_from_user(&usb_data, data, sizeof(struct vstusb_args))) {
+               dev_err(&vstdev->usb_dev->dev, "%s: can't copy_from_user\n",
+                       __func__);
+               return -EFAULT;
+       }
+
+       /* lock this object */
+       if (mutex_lock_interruptible(&vstdev->lock)) {
+               retval = -ERESTARTSYS;
+               goto exit;
+       }
+
+       /* anyone home */
+       if (!vstdev->present) {
+               mutex_unlock(&vstdev->lock);
+               dev_err(&vstdev->usb_dev->dev, "%s: device not present\n",
+                       __func__);
+               retval = -ENODEV;
+               goto exit;
+       }
+
+       /* pull out the necessary data */
+       dev = vstdev->usb_dev;
+
+       switch (cmd) {
+
+       case IOCTL_VSTUSB_CONFIG_RW:
+
+               vstdev->rd_pipe = usb_data.rd_pipe;
+               vstdev->rd_timeout_ms = usb_data.rd_timeout_ms;
+               vstdev->wr_pipe = usb_data.wr_pipe;
+               vstdev->wr_timeout_ms = usb_data.wr_timeout_ms;
+
+               mutex_unlock(&vstdev->lock);
+
+               dev_dbg(&dev->dev, "%s: setting pipes/timeouts, "
+                       "rdpipe = %d, rdtimeout = %d, "
+                       "wrpipe = %d, wrtimeout = %d\n", __func__,
+                       vstdev->rd_pipe, vstdev->rd_timeout_ms,
+                       vstdev->wr_pipe, vstdev->wr_timeout_ms);
+               break;
+
+       case IOCTL_VSTUSB_SEND_PIPE:
+
+               if (usb_data.count == 0) {
+                       mutex_unlock(&vstdev->lock);
+                       retval = -EINVAL;
+                       goto exit;
+               }
+
+               buffer = kmalloc(usb_data.count, GFP_KERNEL);
+               if (buffer == NULL) {
+                       mutex_unlock(&vstdev->lock);
+                       retval = -ENOMEM;
+                       goto exit;
+               }
+
+               urb = usb_alloc_urb(0, GFP_KERNEL);
+               if (!urb) {
+                       mutex_unlock(&vstdev->lock);
+                       retval = -ENOMEM;
+                       goto exit;
+               }
+
+               timeout = usb_data.timeout_ms;
+
+               pipe = usb_sndbulkpipe(dev, usb_data.pipe);
+
+               if (copy_from_user(buffer, usb_data.buffer, usb_data.count)) {
+                       dev_err(&dev->dev, "%s: can't copy_from_user\n",
+                               __func__);
+                       mutex_unlock(&vstdev->lock);
+                       retval = -EFAULT;
+                       goto exit;
+               }
+
+               usb_anchor_urb(urb, &vstdev->submitted);
+               retval = vstusb_fill_and_send_urb(urb, dev, pipe, buffer,
+                                                 usb_data.count, &done);
+               mutex_unlock(&vstdev->lock);
+               if (retval) {
+                       usb_unanchor_urb(urb);
+                       dev_err(&dev->dev,
+                               "%s: error %d filling and sending urb %d\n",
+                               __func__, retval, pipe);
+                       goto exit;
+               }
+
+               retval = vstusb_complete_urb(urb, &done, timeout, &cnt);
+               if (retval) {
+                       dev_err(&dev->dev, "%s: error %d completing urb %d\n",
+                               __func__, retval, pipe);
+               }
+
+               break;
+       case IOCTL_VSTUSB_RECV_PIPE:
+
+               if (usb_data.count == 0) {
+                       mutex_unlock(&vstdev->lock);
+                       retval = -EINVAL;
+                       goto exit;
+               }
+
+               buffer = kmalloc(usb_data.count, GFP_KERNEL);
+               if (buffer == NULL) {
+                       mutex_unlock(&vstdev->lock);
+                       retval = -ENOMEM;
+                       goto exit;
+               }
+
+               urb = usb_alloc_urb(0, GFP_KERNEL);
+               if (!urb) {
+                       mutex_unlock(&vstdev->lock);
+                       retval = -ENOMEM;
+                       goto exit;
+               }
+
+               timeout = usb_data.timeout_ms;
+
+               pipe = usb_rcvbulkpipe(dev, usb_data.pipe);
+
+               usb_anchor_urb(urb, &vstdev->submitted);
+               retval = vstusb_fill_and_send_urb(urb, dev, pipe, buffer,
+                                                 usb_data.count, &done);
+               mutex_unlock(&vstdev->lock);
+               if (retval) {
+                       usb_unanchor_urb(urb);
+                       dev_err(&dev->dev,
+                               "%s: error %d filling and sending urb %d\n",
+                               __func__, retval, pipe);
+                       goto exit;
+               }
+
+               retval = vstusb_complete_urb(urb, &done, timeout, &cnt);
+               if (retval) {
+                       dev_err(&dev->dev, "%s: error %d completing urb %d\n",
+                               __func__, retval, pipe);
+                       goto exit;
+               }
+
+               if (copy_to_user(usb_data.buffer, buffer, cnt)) {
+                       dev_err(&dev->dev, "%s: can't copy_to_user\n",
+                               __func__);
+                       retval = -EFAULT;
+                       goto exit;
+               }
+
+               usb_data.count = cnt;
+               if (copy_to_user(data, &usb_data, sizeof(struct vstusb_args))) {
+                       dev_err(&dev->dev, "%s: can't copy_to_user\n",
+                               __func__);
+                       retval = -EFAULT;
+               } else {
+                       dev_dbg(&dev->dev, "%s: recv %d bytes from pipe %d\n",
+                               __func__, usb_data.count, usb_data.pipe);
+               }
+
+               break;
+
+       default:
+               mutex_unlock(&vstdev->lock);
+               dev_warn(&dev->dev, "ioctl_vstusb: invalid ioctl cmd %x\n",
+                        cmd);
+               return -EINVAL;
+               break;
+       }
+exit:
+       usb_free_urb(urb);
+       kfree(buffer);
+       return retval;
+}
+
+static const struct file_operations vstusb_fops = {
+       .owner =                THIS_MODULE,
+       .read =                 vstusb_read,
+       .write =                vstusb_write,
+       .unlocked_ioctl =       vstusb_ioctl,
+       .compat_ioctl =         vstusb_ioctl,
+       .open =                 vstusb_open,
+       .release =              vstusb_close,
+};
+
+static struct usb_class_driver usb_vstusb_class = {
+       .name =         "usb/vstusb%d",
+       .fops =         &vstusb_fops,
+       .minor_base =   VSTUSB_MINOR_BASE,
+};
+
+static int vstusb_probe(struct usb_interface *intf,
+                       const struct usb_device_id *id)
+{
+       struct usb_device *dev = interface_to_usbdev(intf);
+       struct vstusb_device *vstdev;
+       int i;
+       int retval = 0;
+
+       /* allocate memory for our device state and intialize it */
+
+       vstdev = kzalloc(sizeof(*vstdev), GFP_KERNEL);
+       if (vstdev == NULL)
+               return -ENOMEM;
+
+       mutex_init(&vstdev->lock);
+
+       i = dev->descriptor.bcdDevice;
+
+       dev_dbg(&intf->dev, "Version %1d%1d.%1d%1d found at address %d\n",
+               (i & 0xF000) >> 12, (i & 0xF00) >> 8,
+               (i & 0xF0) >> 4, (i & 0xF), dev->devnum);
+
+       vstdev->present = 1;
+       vstdev->isopen = 0;
+       vstdev->usb_dev = dev;
+       init_usb_anchor(&vstdev->submitted);
+
+       usb_set_intfdata(intf, vstdev);
+       retval = usb_register_dev(intf, &usb_vstusb_class);
+       if (retval) {
+               dev_err(&intf->dev,
+                       "%s: Not able to get a minor for this device.\n",
+                       __func__);
+               usb_set_intfdata(intf, NULL);
+               kfree(vstdev);
+               return retval;
+       }
+
+       /* let the user know what node this device is now attached to */
+       dev_info(&intf->dev,
+                "VST USB Device #%d now attached to major %d minor %d\n",
+                (intf->minor - VSTUSB_MINOR_BASE), USB_MAJOR, intf->minor);
+
+       dev_info(&intf->dev, "%s, %s\n", DRIVER_DESC, DRIVER_VERSION);
+
+       return retval;
+}
+
+static void vstusb_disconnect(struct usb_interface *intf)
+{
+       struct vstusb_device *vstdev = usb_get_intfdata(intf);
+
+       usb_deregister_dev(intf, &usb_vstusb_class);
+       usb_set_intfdata(intf, NULL);
+
+       if (vstdev) {
+
+               mutex_lock(&vstdev->lock);
+               vstdev->present = 0;
+
+               usb_kill_anchored_urbs(&vstdev->submitted);
+
+               /* if the device is not opened, then we clean up right now */
+               if (!vstdev->isopen) {
+                       mutex_unlock(&vstdev->lock);
+                       kfree(vstdev);
+               } else
+                       mutex_unlock(&vstdev->lock);
+
+       }
+}
+
+static int vstusb_suspend(struct usb_interface *intf, pm_message_t message)
+{
+       struct vstusb_device *vstdev = usb_get_intfdata(intf);
+       int time;
+       if (!vstdev)
+               return 0;
+
+       mutex_lock(&vstdev->lock);
+       time = usb_wait_anchor_empty_timeout(&vstdev->submitted, 1000);
+       if (!time)
+               usb_kill_anchored_urbs(&vstdev->submitted);
+       mutex_unlock(&vstdev->lock);
+
+       return 0;
+}
+
+static int vstusb_resume(struct usb_interface *intf)
+{
+       return 0;
+}
+
+static struct usb_driver vstusb_driver = {
+       .name =         "vstusb",
+       .probe =        vstusb_probe,
+       .disconnect =   vstusb_disconnect,
+       .suspend =      vstusb_suspend,
+       .resume =       vstusb_resume,
+       .id_table = id_table,
+};
+
+static int __init vstusb_init(void)
+{
+       int rc;
+
+       rc = usb_register(&vstusb_driver);
+       if (rc)
+               printk(KERN_ERR "%s: failed to register (%d)", __func__, rc);
+
+       return rc;
+}
+
+static void __exit vstusb_exit(void)
+{
+       usb_deregister(&vstusb_driver);
+}
+
+module_init(vstusb_init);
+module_exit(vstusb_exit);
+
+MODULE_AUTHOR("Dennis O'Brien/Stephen Ware");
+MODULE_DESCRIPTION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
index 29fd73b0bffc97bb56528a2f7fa4194def8623c6..54c446309a2afb7d2cb8ed80298d48d61530fee9 100644 (file)
@@ -5,3 +5,4 @@ header-y += gadgetfs.h
 header-y += midi.h
 header-y += g_printer.h
 header-y += tmc.h
+header-y += vstusb.h
diff --git a/include/linux/usb/vstusb.h b/include/linux/usb/vstusb.h
new file mode 100644 (file)
index 0000000..1cfac67
--- /dev/null
@@ -0,0 +1,71 @@
+/*****************************************************************************
+ *  File: drivers/usb/misc/vstusb.h
+ *
+ *  Purpose: Support for the bulk USB Vernier Spectrophotometers
+ *
+ *  Author:     EQware Engineering, Inc.
+ *              Oregon City, OR, USA 97045
+ *
+ *  Copyright:  2007, 2008
+ *              Vernier Software & Technology
+ *              Beaverton, OR, USA 97005
+ *
+ *  Web:        www.vernier.com
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *****************************************************************************/
+/*****************************************************************************
+ *
+ *  The vstusb module is a standard usb 'client' driver running on top of the
+ *  standard usb host controller stack.
+ *
+ *  In general, vstusb supports standard bulk usb pipes.  It supports multiple
+ *  devices and multiple pipes per device.
+ *
+ *  The vstusb driver supports two interfaces:
+ *  1 - ioctl SEND_PIPE/RECV_PIPE - a general bulk write/read msg
+ *     interface to any pipe with timeout support;
+ *  2 - standard read/write with ioctl config - offers standard read/write
+ *     interface with ioctl configured pipes and timeouts.
+ *
+ *  Both interfaces can be signal from other process and will abort its i/o
+ *  operation.
+ *
+ *  A timeout of 0 means NO timeout.  The user can still terminate the read via
+ *  signal.
+ *
+ *  If using multiple threads with this driver, the user should ensure that
+ *  any reads, writes, or ioctls are complete before closing the device.
+ *  Changing read/write timeouts or pipes takes effect on next read/write.
+ *
+ *****************************************************************************/
+
+struct vstusb_args {
+       union {
+               /* this struct is used for IOCTL_VSTUSB_SEND_PIPE,      *
+                * IOCTL_VSTUSB_RECV_PIPE, and read()/write() fops      */
+               struct {
+                       void __user     *buffer;
+                       size_t          count;
+                       unsigned int    timeout_ms;
+                       int             pipe;
+               };
+
+               /* this one is used for IOCTL_VSTUSB_CONFIG_RW          */
+               struct {
+                       int rd_pipe;
+                       int rd_timeout_ms;
+                       int wr_pipe;
+                       int wr_timeout_ms;
+               };
+       };
+};
+
+#define VST_IOC_MAGIC 'L'
+#define VST_IOC_FIRST 0x20
+#define IOCTL_VSTUSB_SEND_PIPE _IO(VST_IOC_MAGIC, VST_IOC_FIRST)
+#define IOCTL_VSTUSB_RECV_PIPE _IO(VST_IOC_MAGIC, VST_IOC_FIRST + 1)
+#define IOCTL_VSTUSB_CONFIG_RW _IO(VST_IOC_MAGIC, VST_IOC_FIRST + 2)