/***************************************************************************** * 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 #include #include #include #include #include #include #include #include #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)) { 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");