| /***************************************************************************** |
| * 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 |
| |
| #define VST_MAXBUFFER (64*1024) |
| |
| 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 kref kref; |
| 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; |
| }; |
| #define to_vst_dev(d) container_of(d, struct vstusb_device, kref) |
| |
| static struct usb_driver vstusb_driver; |
| |
| static void vstusb_delete(struct kref *kref) |
| { |
| struct vstusb_device *vstdev = to_vst_dev(kref); |
| |
| usb_put_dev(vstdev->usb_dev); |
| kfree(vstdev); |
| } |
| |
| 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; |
| } |
| |
| /* increment our usage count */ |
| kref_get(&vstdev->kref); |
| |
| 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_release(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; |
| |
| dev_dbg(&vstdev->usb_dev->dev, "%s: released\n", __func__); |
| |
| mutex_unlock(&vstdev->lock); |
| |
| kref_put(&vstdev->kref, vstusb_delete); |
| |
| 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) || (count > VST_MAXBUFFER)) |
| 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) || (count > VST_MAXBUFFER)) |
| 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)) { |
| mutex_unlock(&vstdev->lock); |
| 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) || (usb_data.count > VST_MAXBUFFER)) { |
| 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) || (usb_data.count > VST_MAXBUFFER)) { |
| 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 %zd 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_release, |
| }; |
| |
| 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; |
| |
| /* must do usb_get_dev() prior to kref_init() since the kref_put() |
| * release function will do a usb_put_dev() */ |
| usb_get_dev(dev); |
| kref_init(&vstdev->kref); |
| 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); |
| kref_put(&vstdev->kref, vstusb_delete); |
| 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); |
| |
| mutex_unlock(&vstdev->lock); |
| |
| kref_put(&vstdev->kref, vstusb_delete); |
| } |
| |
| } |
| |
| 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"); |