linux/drivers/usb/misc/vstusb.c
Stephen Ware 84dcd59495 USB: fix up problems in the vtusb driver
Add range check on buffer sizes passed in from user space
(max is 8*PAGE_SIZE) which will work for the most common
spectrometers even at pages as small as 1K.

Add kref to vst device structure to preserve reference to the
usb object until we truly are done with it.

From: Stephen Ware <stephen.ware@eqware.net>
From: Dennis O'Brien <dennis.obrien@eqware.net>
Signed-off-by: Dennis O'Brien <dennis.obrien@eqware.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
2008-10-17 14:41:04 -07:00

782 lines
18 KiB
C

/*****************************************************************************
* 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)) {
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 %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_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");