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