Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit cbc30118 authored by Stephen Ware's avatar Stephen Ware Committed by Greg Kroah-Hartman
Browse files

usb: vstusb.c : new driver for spectrometers used by Vernier Software & Technology, Inc.



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: default avatarDennis O'Brien <dennis.obrien@eqware.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent 29bac7b7
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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
+15 −0
Original line number Diff line number Diff line
@@ -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.

+1 −0
Original line number Diff line number Diff line
@@ -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/

+768 −0
Original line number Diff line number Diff line
/*****************************************************************************
 *  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");
+1 −0
Original line number Diff line number Diff line
@@ -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
Loading