diff options
Diffstat (limited to 'drivers/usb/misc')
-rw-r--r-- | drivers/usb/misc/Kconfig | 10 | ||||
-rw-r--r-- | drivers/usb/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/misc/idmouse.c | 149 | ||||
-rw-r--r-- | drivers/usb/misc/ldusb.c | 794 | ||||
-rw-r--r-- | drivers/usb/misc/usbtest.c | 60 |
5 files changed, 945 insertions, 69 deletions
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 3a896954b3a..6649531fa82 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -139,6 +139,16 @@ config USB_IDMOUSE source "drivers/usb/misc/sisusbvga/Kconfig" +config USB_LD + tristate "USB LD driver" + depends on USB && EXPERIMENTAL + help + This driver is for generic USB devices that use interrupt transfers, + like LD Didactic's USB devices. + + To compile this driver as a module, choose M here: the + module will be called ldusb. + config USB_TEST tristate "USB testing driver (DEVELOPMENT)" depends on USB && USB_DEVICEFS && EXPERIMENTAL diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 4a3814cbd48..862e40a8368 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -9,6 +9,7 @@ obj-$(CONFIG_USB_EMI26) += emi26.o obj-$(CONFIG_USB_EMI62) += emi62.o obj-$(CONFIG_USB_IDMOUSE) += idmouse.o obj-$(CONFIG_USB_LCD) += usblcd.o +obj-$(CONFIG_USB_LD) += ldusb.o obj-$(CONFIG_USB_LED) += usbled.o obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o obj-$(CONFIG_USB_PHIDGETKIT) += phidgetkit.o diff --git a/drivers/usb/misc/idmouse.c b/drivers/usb/misc/idmouse.c index ce030d1f1c1..733acc21372 100644 --- a/drivers/usb/misc/idmouse.c +++ b/drivers/usb/misc/idmouse.c @@ -1,4 +1,4 @@ -/* Siemens ID Mouse driver v0.5 +/* Siemens ID Mouse driver v0.6 This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as @@ -11,6 +11,9 @@ Derived from the USB Skeleton driver 1.1, Copyright (C) 2003 Greg Kroah-Hartman (greg@kroah.com) + Additional information provided by Martin Reising + <Martin.Reising@natural-computing.de> + */ #include <linux/config.h> @@ -25,29 +28,44 @@ #include <asm/uaccess.h> #include <linux/usb.h> +/* image constants */ #define WIDTH 225 -#define HEIGHT 288 -#define HEADER "P5 225 288 255 " +#define HEIGHT 289 +#define HEADER "P5 225 289 255 " #define IMGSIZE ((WIDTH * HEIGHT) + sizeof(HEADER)-1) -/* Version Information */ -#define DRIVER_VERSION "0.5" +/* version information */ +#define DRIVER_VERSION "0.6" #define DRIVER_SHORT "idmouse" #define DRIVER_AUTHOR "Florian 'Floe' Echtler <echtler@fs.tum.de>" #define DRIVER_DESC "Siemens ID Mouse FingerTIP Sensor Driver" -/* Siemens ID Mouse */ -#define USB_IDMOUSE_VENDOR_ID 0x0681 -#define USB_IDMOUSE_PRODUCT_ID 0x0005 - -/* we still need a minor number */ +/* minor number for misc USB devices */ #define USB_IDMOUSE_MINOR_BASE 132 +/* vendor and device IDs */ +#define ID_SIEMENS 0x0681 +#define ID_IDMOUSE 0x0005 +#define ID_CHERRY 0x0010 + +/* device ID table */ static struct usb_device_id idmouse_table[] = { - {USB_DEVICE(USB_IDMOUSE_VENDOR_ID, USB_IDMOUSE_PRODUCT_ID)}, - {} /* null entry at the end */ + {USB_DEVICE(ID_SIEMENS, ID_IDMOUSE)}, /* Siemens ID Mouse (Professional) */ + {USB_DEVICE(ID_SIEMENS, ID_CHERRY )}, /* Cherry FingerTIP ID Board */ + {} /* terminating null entry */ }; +/* sensor commands */ +#define FTIP_RESET 0x20 +#define FTIP_ACQUIRE 0x21 +#define FTIP_RELEASE 0x22 +#define FTIP_BLINK 0x23 /* LSB of value = blink pulse width */ +#define FTIP_SCROLL 0x24 + +#define ftip_command(dev, command, value, index) \ + usb_control_msg (dev->udev, usb_sndctrlpipe (dev->udev, 0), command, \ + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, value, index, NULL, 0, 1000) + MODULE_DEVICE_TABLE(usb, idmouse_table); /* structure to hold all of our device specific stuff */ @@ -57,7 +75,8 @@ struct usb_idmouse { struct usb_interface *interface; /* the interface for this device */ unsigned char *bulk_in_buffer; /* the buffer to receive data */ - size_t bulk_in_size; /* the size of the receive buffer */ + size_t bulk_in_size; /* the maximum bulk packet size */ + size_t orig_bi_size; /* same as above, but reported by the device */ __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */ int open; /* if the port is open or not */ @@ -103,7 +122,7 @@ static struct usb_driver idmouse_driver = { .id_table = idmouse_table, }; -// prevent races between open() and disconnect() +/* prevent races between open() and disconnect() */ static DECLARE_MUTEX(disconnect_sem); static int idmouse_create_image(struct usb_idmouse *dev) @@ -112,42 +131,34 @@ static int idmouse_create_image(struct usb_idmouse *dev) int bulk_read = 0; int result = 0; - if (dev->bulk_in_size < sizeof(HEADER)) - return -ENOMEM; - - memcpy(dev->bulk_in_buffer,HEADER,sizeof(HEADER)-1); + memcpy(dev->bulk_in_buffer, HEADER, sizeof(HEADER)-1); bytes_read += sizeof(HEADER)-1; - /* Dump the setup packets. Yes, they are uncommented, simply - because they were sniffed under Windows using SnoopyPro. - I _guess_ that 0x22 is a kind of reset command and 0x21 - means init.. - */ - result = usb_control_msg (dev->udev, usb_sndctrlpipe (dev->udev, 0), - 0x21, 0x42, 0x0001, 0x0002, NULL, 0, 1000); - if (result < 0) - return result; - result = usb_control_msg (dev->udev, usb_sndctrlpipe (dev->udev, 0), - 0x20, 0x42, 0x0001, 0x0002, NULL, 0, 1000); + /* reset the device and set a fast blink rate */ + result = ftip_command(dev, FTIP_RELEASE, 0, 0); if (result < 0) - return result; - result = usb_control_msg (dev->udev, usb_sndctrlpipe (dev->udev, 0), - 0x22, 0x42, 0x0000, 0x0002, NULL, 0, 1000); + goto reset; + result = ftip_command(dev, FTIP_BLINK, 1, 0); if (result < 0) - return result; + goto reset; - result = usb_control_msg (dev->udev, usb_sndctrlpipe (dev->udev, 0), - 0x21, 0x42, 0x0001, 0x0002, NULL, 0, 1000); + /* initialize the sensor - sending this command twice */ + /* significantly reduces the rate of failed reads */ + result = ftip_command(dev, FTIP_ACQUIRE, 0, 0); if (result < 0) - return result; - result = usb_control_msg (dev->udev, usb_sndctrlpipe (dev->udev, 0), - 0x20, 0x42, 0x0001, 0x0002, NULL, 0, 1000); + goto reset; + result = ftip_command(dev, FTIP_ACQUIRE, 0, 0); if (result < 0) - return result; - result = usb_control_msg (dev->udev, usb_sndctrlpipe (dev->udev, 0), - 0x20, 0x42, 0x0000, 0x0002, NULL, 0, 1000); + goto reset; + + /* start the readout - sending this command twice */ + /* presumably enables the high dynamic range mode */ + result = ftip_command(dev, FTIP_RESET, 0, 0); if (result < 0) - return result; + goto reset; + result = ftip_command(dev, FTIP_RESET, 0, 0); + if (result < 0) + goto reset; /* loop over a blocking bulk read to get data from the device */ while (bytes_read < IMGSIZE) { @@ -155,22 +166,40 @@ static int idmouse_create_image(struct usb_idmouse *dev) usb_rcvbulkpipe (dev->udev, dev->bulk_in_endpointAddr), dev->bulk_in_buffer + bytes_read, dev->bulk_in_size, &bulk_read, 5000); - if (result < 0) - return result; - if (signal_pending(current)) - return -EINTR; + if (result < 0) { + /* Maybe this error was caused by the increased packet size? */ + /* Reset to the original value and tell userspace to retry. */ + if (dev->bulk_in_size != dev->orig_bi_size) { + dev->bulk_in_size = dev->orig_bi_size; + result = -EAGAIN; + } + break; + } + if (signal_pending(current)) { + result = -EINTR; + break; + } bytes_read += bulk_read; } /* reset the device */ - result = usb_control_msg (dev->udev, usb_sndctrlpipe (dev->udev, 0), - 0x22, 0x42, 0x0000, 0x0002, NULL, 0, 1000); - if (result < 0) - return result; +reset: + ftip_command(dev, FTIP_RELEASE, 0, 0); + + /* check for valid image */ + /* right border should be black (0x00) */ + for (bytes_read = sizeof(HEADER)-1 + WIDTH-1; bytes_read < IMGSIZE; bytes_read += WIDTH) + if (dev->bulk_in_buffer[bytes_read] != 0x00) + return -EAGAIN; - /* should be IMGSIZE == 64815 */ + /* lower border should be white (0xFF) */ + for (bytes_read = IMGSIZE-WIDTH; bytes_read < IMGSIZE-1; bytes_read++) + if (dev->bulk_in_buffer[bytes_read] != 0xFF) + return -EAGAIN; + + /* should be IMGSIZE == 65040 */ dbg("read %d bytes fingerprint data", bytes_read); - return 0; + return result; } static inline void idmouse_delete(struct usb_idmouse *dev) @@ -282,10 +311,10 @@ static ssize_t idmouse_read(struct file *file, char __user *buffer, size_t count dev = (struct usb_idmouse *) file->private_data; - // lock this object + /* lock this object */ down (&dev->sem); - // verify that the device wasn't unplugged + /* verify that the device wasn't unplugged */ if (!dev->present) { up (&dev->sem); return -ENODEV; @@ -296,8 +325,7 @@ static ssize_t idmouse_read(struct file *file, char __user *buffer, size_t count return 0; } - if (count > IMGSIZE - *ppos) - count = IMGSIZE - *ppos; + count = min ((loff_t)count, IMGSIZE - (*ppos)); if (copy_to_user (buffer, dev->bulk_in_buffer + *ppos, count)) { result = -EFAULT; @@ -306,7 +334,7 @@ static ssize_t idmouse_read(struct file *file, char __user *buffer, size_t count *ppos += count; } - // unlock the device + /* unlock the device */ up(&dev->sem); return result; } @@ -318,7 +346,6 @@ static int idmouse_probe(struct usb_interface *interface, struct usb_idmouse *dev = NULL; struct usb_host_interface *iface_desc; struct usb_endpoint_descriptor *endpoint; - size_t buffer_size; int result; /* check if we have gotten the data or the hid interface */ @@ -344,11 +371,11 @@ static int idmouse_probe(struct usb_interface *interface, USB_ENDPOINT_XFER_BULK)) { /* we found a bulk in endpoint */ - buffer_size = le16_to_cpu(endpoint->wMaxPacketSize); - dev->bulk_in_size = buffer_size; + dev->orig_bi_size = le16_to_cpu(endpoint->wMaxPacketSize); + dev->bulk_in_size = 0x200; /* works _much_ faster */ dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; dev->bulk_in_buffer = - kmalloc(IMGSIZE + buffer_size, GFP_KERNEL); + kmalloc(IMGSIZE + dev->bulk_in_size, GFP_KERNEL); if (!dev->bulk_in_buffer) { err("Unable to allocate input buffer."); diff --git a/drivers/usb/misc/ldusb.c b/drivers/usb/misc/ldusb.c new file mode 100644 index 00000000000..66ec88354b9 --- /dev/null +++ b/drivers/usb/misc/ldusb.c @@ -0,0 +1,794 @@ +/** + * Generic USB driver for report based interrupt in/out devices + * like LD Didactic's USB devices. LD Didactic's USB devices are + * HID devices which do not use HID report definitons (they use + * raw interrupt in and our reports only for communication). + * + * This driver uses a ring buffer for time critical reading of + * interrupt in reports and provides read and write methods for + * raw interrupt reports (similar to the Windows HID driver). + * Devices based on the book USB COMPLETE by Jan Axelson may need + * such a compatibility to the Windows HID driver. + * + * Copyright (C) 2005 Michael Hund <mhund@ld-didactic.de> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * Derived from Lego USB Tower driver + * Copyright (C) 2003 David Glance <advidgsf@sourceforge.net> + * 2001-2004 Juergen Stuber <starblue@users.sourceforge.net> + * + * V0.1 (mh) Initial version + * V0.11 (mh) Added raw support for HID 1.0 devices (no interrupt out endpoint) + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include <asm/uaccess.h> +#include <linux/input.h> +#include <linux/usb.h> +#include <linux/poll.h> + +/* Define these values to match your devices */ +#define USB_VENDOR_ID_LD 0x0f11 /* USB Vendor ID of LD Didactic GmbH */ +#define USB_DEVICE_ID_CASSY 0x1000 /* USB Product ID for all CASSY-S modules */ +#define USB_DEVICE_ID_POCKETCASSY 0x1010 /* USB Product ID for Pocket-CASSY */ +#define USB_DEVICE_ID_MOBILECASSY 0x1020 /* USB Product ID for Mobile-CASSY */ +#define USB_DEVICE_ID_JWM 0x1080 /* USB Product ID for Joule and Wattmeter */ +#define USB_DEVICE_ID_DMMP 0x1081 /* USB Product ID for Digital Multimeter P (reserved) */ +#define USB_DEVICE_ID_UMIP 0x1090 /* USB Product ID for UMI P */ +#define USB_DEVICE_ID_VIDEOCOM 0x1200 /* USB Product ID for VideoCom */ +#define USB_DEVICE_ID_COM3LAB 0x2000 /* USB Product ID for COM3LAB */ +#define USB_DEVICE_ID_TELEPORT 0x2010 /* USB Product ID for Terminal Adapter */ +#define USB_DEVICE_ID_NETWORKANALYSER 0x2020 /* USB Product ID for Network Analyser */ +#define USB_DEVICE_ID_POWERCONTROL 0x2030 /* USB Product ID for Controlling device for Power Electronics */ + +#define USB_VENDOR_ID_VERNIER 0x08f7 +#define USB_DEVICE_ID_VERNIER_LABPRO 0x0001 +#define USB_DEVICE_ID_VERNIER_GOTEMP 0x0002 +#define USB_DEVICE_ID_VERNIER_SKIP 0x0003 +#define USB_DEVICE_ID_VERNIER_CYCLOPS 0x0004 + + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define USB_LD_MINOR_BASE 0 +#else +#define USB_LD_MINOR_BASE 176 +#endif + +/* table of devices that work with this driver */ +static struct usb_device_id ld_usb_table [] = { + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_CASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_POCKETCASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_MOBILECASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_JWM) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_DMMP) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_UMIP) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_VIDEOCOM) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_COM3LAB) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_TELEPORT) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_NETWORKANALYSER) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_POWERCONTROL) }, + { USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_LABPRO) }, + { USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_GOTEMP) }, + { USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_SKIP) }, + { USB_DEVICE(USB_VENDOR_ID_VERNIER, USB_DEVICE_ID_VERNIER_CYCLOPS) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, ld_usb_table); +MODULE_VERSION("V0.11"); +MODULE_AUTHOR("Michael Hund <mhund@ld-didactic.de>"); +MODULE_DESCRIPTION("LD USB Driver"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("LD USB Devices"); + +#ifdef CONFIG_USB_DEBUG + static int debug = 1; +#else + static int debug = 0; +#endif + +/* Use our own dbg macro */ +#define dbg_info(dev, format, arg...) do { if (debug) dev_info(dev , format , ## arg); } while (0) + +/* Module parameters */ +module_param(debug, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +/* All interrupt in transfers are collected in a ring buffer to + * avoid racing conditions and get better performance of the driver. + */ +static int ring_buffer_size = 128; +module_param(ring_buffer_size, int, 0); +MODULE_PARM_DESC(ring_buffer_size, "Read ring buffer size in reports"); + +/* The write_buffer can contain more than one interrupt out transfer. + */ +static int write_buffer_size = 10; +module_param(write_buffer_size, int, 0); +MODULE_PARM_DESC(write_buffer_size, "Write buffer size in reports"); + +/* As of kernel version 2.6.4 ehci-hcd uses an + * "only one interrupt transfer per frame" shortcut + * to simplify the scheduling of periodic transfers. + * This conflicts with our standard 1ms intervals for in and out URBs. + * We use default intervals of 2ms for in and 2ms for out transfers, + * which should be fast enough. + * Increase the interval to allow more devices that do interrupt transfers, + * or set to 1 to use the standard interval from the endpoint descriptors. + */ +static int min_interrupt_in_interval = 2; +module_param(min_interrupt_in_interval, int, 0); +MODULE_PARM_DESC(min_interrupt_in_interval, "Minimum interrupt in interval in ms"); + +static int min_interrupt_out_interval = 2; +module_param(min_interrupt_out_interval, int, 0); +MODULE_PARM_DESC(min_interrupt_out_interval, "Minimum interrupt out interval in ms"); + +/* Structure to hold all of our device specific stuff */ +struct ld_usb { + struct semaphore sem; /* locks this structure */ + struct usb_interface* intf; /* save off the usb interface pointer */ + + int open_count; /* number of times this port has been opened */ + + char* ring_buffer; + unsigned int ring_head; + unsigned int ring_tail; + + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + + char* interrupt_in_buffer; + struct usb_endpoint_descriptor* interrupt_in_endpoint; + struct urb* interrupt_in_urb; + int interrupt_in_interval; + size_t interrupt_in_endpoint_size; + int interrupt_in_running; + int interrupt_in_done; + + char* interrupt_out_buffer; + struct usb_endpoint_descriptor* interrupt_out_endpoint; + struct urb* interrupt_out_urb; + int interrupt_out_interval; + size_t interrupt_out_endpoint_size; + int interrupt_out_busy; +}; + +/* prevent races between open() and disconnect() */ +static DECLARE_MUTEX(disconnect_sem); + +static struct usb_driver ld_usb_driver; + +/** + * ld_usb_abort_transfers + * aborts transfers and frees associated data structures + */ +static void ld_usb_abort_transfers(struct ld_usb *dev) +{ + /* shutdown transfer */ + if (dev->interrupt_in_running) { + dev->interrupt_in_running = 0; + if (dev->intf) + usb_kill_urb(dev->interrupt_in_urb); + } + if (dev->interrupt_out_busy) + if (dev->intf) + usb_kill_urb(dev->interrupt_out_urb); +} + +/** + * ld_usb_delete + */ +static void ld_usb_delete(struct ld_usb *dev) +{ + ld_usb_abort_transfers(dev); + + /* free data structures */ + usb_free_urb(dev->interrupt_in_urb); + usb_free_urb(dev->interrupt_out_urb); + kfree(dev->ring_buffer); + kfree(dev->interrupt_in_buffer); + kfree(dev->interrupt_out_buffer); + kfree(dev); +} + +/** + * ld_usb_interrupt_in_callback + */ +static void ld_usb_interrupt_in_callback(struct urb *urb, struct pt_regs *regs) +{ + struct ld_usb *dev = urb->context; + size_t *actual_buffer; + unsigned int next_ring_head; + int retval; + + if (urb->status) { + if (urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN) { + goto exit; + } else { + dbg_info(&dev->intf->dev, "%s: nonzero status received: %d\n", + __FUNCTION__, urb->status); + goto resubmit; /* maybe we can recover */ + } + } + + if (urb->actual_length > 0) { + next_ring_head = (dev->ring_head+1) % ring_buffer_size; + if (next_ring_head != dev->ring_tail) { + actual_buffer = (size_t*)(dev->ring_buffer + dev->ring_head*(sizeof(size_t)+dev->interrupt_in_endpoint_size)); + /* actual_buffer gets urb->actual_length + interrupt_in_buffer */ + *actual_buffer = urb->actual_length; + memcpy(actual_buffer+1, dev->interrupt_in_buffer, urb->actual_length); + dev->ring_head = next_ring_head; + dbg_info(&dev->intf->dev, "%s: received %d bytes\n", + __FUNCTION__, urb->actual_length); + } else + dev_warn(&dev->intf->dev, + "Ring buffer overflow, %d bytes dropped\n", + urb->actual_length); + } + +resubmit: + /* resubmit if we're still running */ + if (dev->interrupt_in_running && dev->intf) { + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->intf->dev, + "usb_submit_urb failed (%d)\n", retval); + } + +exit: + dev->interrupt_in_done = 1; + wake_up_interruptible(&dev->read_wait); +} + +/** + * ld_usb_interrupt_out_callback + */ +static void ld_usb_interrupt_out_callback(struct urb *urb, struct pt_regs *regs) +{ + struct ld_usb *dev = urb->context; + + /* sync/async unlink faults aren't errors */ + if (urb->status && !(urb->status == -ENOENT || + urb->status == -ECONNRESET || + urb->status == -ESHUTDOWN)) + dbg_info(&dev->intf->dev, + "%s - nonzero write interrupt status received: %d\n", + __FUNCTION__, urb->status); + + dev->interrupt_out_busy = 0; + wake_up_interruptible(&dev->write_wait); +} + +/** + * ld_usb_open + */ +static int ld_usb_open(struct inode *inode, struct file *file) +{ + struct ld_usb *dev; + int subminor; + int retval = 0; + struct usb_interface *interface; + + nonseekable_open(inode, file); + subminor = iminor(inode); + + down(&disconnect_sem); + + interface = usb_find_interface(&ld_usb_driver, subminor); + + if (!interface) { + err("%s - error, can't find device for minor %d\n", + __FUNCTION__, subminor); + retval = -ENODEV; + goto unlock_disconnect_exit; + } + + dev = usb_get_intfdata(interface); + + if (!dev) { + retval = -ENODEV; + goto unlock_disconnect_exit; + } + + /* lock this device */ + if (down_interruptible(&dev->sem)) { + retval = -ERESTARTSYS; + goto unlock_disconnect_exit; + } + + /* allow opening only once */ + if (dev->open_count) { + retval = -EBUSY; + goto unlock_exit; + } + dev->open_count = 1; + + /* initialize in direction */ + dev->ring_head = 0; + dev->ring_tail = 0; + usb_fill_int_urb(dev->interrupt_in_urb, + interface_to_usbdev(interface), + usb_rcvintpipe(interface_to_usbdev(interface), + dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + dev->interrupt_in_endpoint_size, + ld_usb_interrupt_in_callback, + dev, + dev->interrupt_in_interval); + + dev->interrupt_in_running = 1; + dev->interrupt_in_done = 0; + + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&interface->dev, "Couldn't submit interrupt_in_urb %d\n", retval); + dev->interrupt_in_running = 0; + dev->open_count = 0; + goto unlock_exit; + } + + /* save device in the file's private structure */ + file->private_data = dev; + +unlock_exit: + up(&dev->sem); + +unlock_disconnect_exit: + up(&disconnect_sem); + + return retval; +} + +/** + * ld_usb_release + */ +static int ld_usb_release(struct inode *inode, struct file *file) +{ + struct ld_usb *dev; + int retval = 0; + + dev = file->private_data; + + if (dev == NULL) { + retval = -ENODEV; + goto exit; + } + + if (down_interruptible(&dev->sem)) { + retval = -ERESTARTSYS; + goto exit; + } + + if (dev->open_count != 1) { + retval = -ENODEV; + goto unlock_exit; + } + if (dev->intf == NULL) { + /* the device was unplugged before the file was released */ + up(&dev->sem); + /* unlock here as ld_usb_delete frees dev */ + ld_usb_delete(dev); + goto exit; + } + + /* wait until write transfer is finished */ + if (dev->interrupt_out_busy) + wait_event_interruptible_timeout(dev->write_wait, !dev->interrupt_out_busy, 2 * HZ); + ld_usb_abort_transfers(dev); + dev->open_count = 0; + +unlock_exit: + up(&dev->sem); + +exit: + return retval; +} + +/** + * ld_usb_poll + */ +static unsigned int ld_usb_poll(struct file *file, poll_table *wait) +{ + struct ld_usb *dev; + unsigned int mask = 0; + + dev = file->private_data; + + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + + if (dev->ring_head != dev->ring_tail) + mask |= POLLIN | POLLRDNORM; + if (!dev->interrupt_out_busy) + mask |= POLLOUT | POLLWRNORM; + + return mask; +} + +/** + * ld_usb_read + */ +static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count, + loff_t *ppos) +{ + struct ld_usb *dev; + size_t *actual_buffer; + size_t bytes_to_read; + int retval = 0; + + dev = file->private_data; + + /* verify that we actually have some data to read */ + if (count == 0) + goto exit; + + /* lock this object */ + if (down_interruptible(&dev->sem)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->intf == NULL) { + retval = -ENODEV; + err("No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + /* wait for data */ + if (dev->ring_head == dev->ring_tail) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible(dev->read_wait, dev->interrupt_in_done); + if (retval < 0) + goto unlock_exit; + } + + /* actual_buffer contains actual_length + interrupt_in_buffer */ + actual_buffer = (size_t*)(dev->ring_buffer + dev->ring_tail*(sizeof(size_t)+dev->interrupt_in_endpoint_size)); + bytes_to_read = min(count, *actual_buffer); + if (bytes_to_read < *actual_buffer) + dev_warn(&dev->intf->dev, "Read buffer overflow, %d bytes dropped\n", + *actual_buffer-bytes_to_read); + + /* copy one interrupt_in_buffer from ring_buffer into userspace */ + if (copy_to_user(buffer, actual_buffer+1, bytes_to_read)) { + retval = -EFAULT; + goto unlock_exit; + } + dev->ring_tail = (dev->ring_tail+1) % ring_buffer_size; + + retval = bytes_to_read; + +unlock_exit: + /* unlock the device */ + up(&dev->sem); + +exit: + return retval; +} + +/** + * ld_usb_write + */ +static ssize_t ld_usb_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct ld_usb *dev; + size_t bytes_to_write; + int retval = 0; + + dev = file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + /* lock this object */ + if (down_interruptible(&dev->sem)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->intf == NULL) { + retval = -ENODEV; + err("No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + /* wait until previous transfer is finished */ + if (dev->interrupt_out_busy) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible(dev->write_wait, !dev->interrupt_out_busy); + if (retval < 0) { + goto unlock_exit; + } + } + + /* write the data into interrupt_out_buffer from userspace */ + bytes_to_write = min(count, write_buffer_size*dev->interrupt_out_endpoint_size); + if (bytes_to_write < count) + dev_warn(&dev->intf->dev, "Write buffer overflow, %d bytes dropped\n",count-bytes_to_write); + dbg_info(&dev->intf->dev, "%s: count = %d, bytes_to_write = %d\n", __FUNCTION__, count, bytes_to_write); + + if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write)) { + retval = -EFAULT; + goto unlock_exit; + } + + if (dev->interrupt_out_endpoint == NULL) { + /* try HID_REQ_SET_REPORT=9 on control_endpoint instead of interrupt_out_endpoint */ + retval = usb_control_msg(interface_to_usbdev(dev->intf), + usb_sndctrlpipe(interface_to_usbdev(dev->intf), 0), + 9, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 1 << 8, 0, + dev->interrupt_out_buffer, + bytes_to_write, + USB_CTRL_SET_TIMEOUT * HZ); + if (retval < 0) + err("Couldn't submit HID_REQ_SET_REPORT %d\n", retval); + goto unlock_exit; + } + + /* send off the urb */ + usb_fill_int_urb(dev->interrupt_out_urb, + interface_to_usbdev(dev->intf), + usb_sndintpipe(interface_to_usbdev(dev->intf), + dev->interrupt_out_endpoint->bEndpointAddress), + dev->interrupt_out_buffer, + bytes_to_write, + ld_usb_interrupt_out_callback, + dev, + dev->interrupt_out_interval); + + dev->interrupt_out_busy = 1; + wmb(); + + retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); + if (retval) { + dev->interrupt_out_busy = 0; + err("Couldn't submit interrupt_out_urb %d\n", retval); + goto unlock_exit; + } + retval = bytes_to_write; + +unlock_exit: + /* unlock the device */ + up(&dev->sem); + +exit: + return retval; +} + +/* file operations needed when we register this driver */ +static struct file_operations ld_usb_fops = { + .owner = THIS_MODULE, + .read = ld_usb_read, + .write = ld_usb_write, + .open = ld_usb_open, + .release = ld_usb_release, + .poll = ld_usb_poll, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with devfs and the driver core + */ +static struct usb_class_driver ld_usb_class = { + .name = "ldusb%d", + .fops = &ld_usb_fops, + .minor_base = USB_LD_MINOR_BASE, +}; + +/** + * ld_usb_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct ld_usb *dev = NULL; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + char *buffer; + int i; + int retval = -ENOMEM; + + /* allocate memory for our device state and intialize it */ + + dev = kmalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&intf->dev, "Out of memory\n"); + goto exit; + } + memset(dev, 0x00, sizeof(*dev)); + init_MUTEX(&dev->sem); + dev->intf = intf; + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + /* workaround for early firmware versions on fast computers */ + if ((le16_to_cpu(udev->descriptor.idVendor) == USB_VENDOR_ID_LD) && + ((le16_to_cpu(udev->descriptor.idProduct) == USB_DEVICE_ID_CASSY) || + (le16_to_cpu(udev->descriptor.idProduct) == USB_DEVICE_ID_COM3LAB)) && + (le16_to_cpu(udev->descriptor.bcdDevice) <= 0x103)) { + buffer = kmalloc(256, GFP_KERNEL); + /* usb_string makes SETUP+STALL to leave always ControlReadLoop */ + usb_string(udev, 255, buffer, 256); + kfree(buffer); + } + + iface_desc = intf->cur_altsetting; + + /* set up the endpoint information */ + for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_IN) && + ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)) { + dev->interrupt_in_endpoint = endpoint; + } + + if (((endpoint->bEndpointAddress & USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT) && + ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_INT)) { + dev->interrupt_out_endpoint = endpoint; + } + } + if (dev->interrupt_in_endpoint == NULL) { + dev_err(&intf->dev, "Interrupt in endpoint not found\n"); + goto error; + } + if (dev->interrupt_out_endpoint == NULL) + dev_warn(&intf->dev, "Interrupt out endpoint not found (using control endpoint instead)\n"); + + dev->interrupt_in_endpoint_size = le16_to_cpu(dev->interrupt_in_endpoint->wMaxPacketSize); + dev->ring_buffer = kmalloc(ring_buffer_size*(sizeof(size_t)+dev->interrupt_in_endpoint_size), GFP_KERNEL); + if (!dev->ring_buffer) { + dev_err(&intf->dev, "Couldn't allocate ring_buffer\n"); + goto error; + } + dev->interrupt_in_buffer = kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL); + if (!dev->interrupt_in_buffer) { + dev_err(&intf->dev, "Couldn't allocate interrupt_in_buffer\n"); + goto error; + } + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) { + dev_err(&intf->dev, "Couldn't allocate interrupt_in_urb\n"); + goto error; + } + dev->interrupt_out_endpoint_size = dev->interrupt_out_endpoint ? le16_to_cpu(dev->interrupt_out_endpoint->wMaxPacketSize) : + udev->descriptor.bMaxPacketSize0; + dev->interrupt_out_buffer = kmalloc(write_buffer_size*dev->interrupt_out_endpoint_size, GFP_KERNEL); + if (!dev->interrupt_out_buffer) { + dev_err(&intf->dev, "Couldn't allocate interrupt_out_buffer\n"); + goto error; + } + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) { + dev_err(&intf->dev, "Couldn't allocate interrupt_out_urb\n"); + goto error; + } + dev->interrupt_in_interval = min_interrupt_in_interval > dev->interrupt_in_endpoint->bInterval ? min_interrupt_in_interval : dev->interrupt_in_endpoint->bInterval; + if (dev->interrupt_out_endpoint) + dev->interrupt_out_interval = min_interrupt_out_interval > dev->interrupt_out_endpoint->bInterval ? min_interrupt_out_interval : dev->interrupt_out_endpoint->bInterval; + + /* we can register the device now, as it is ready */ + usb_set_intfdata(intf, dev); + + retval = usb_register_dev(intf, &ld_usb_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&intf->dev, "Not able to get a minor for this device.\n"); + usb_set_intfdata(intf, NULL); + goto error; + } + + /* let the user know what node this device is now attached to */ + dev_info(&intf->dev, "LD USB Device #%d now attached to major %d minor %d\n", + (intf->minor - USB_LD_MINOR_BASE), USB_MAJOR, intf->minor); + +exit: + return retval; + +error: + ld_usb_delete(dev); + + return retval; +} + +/** + * ld_usb_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void ld_usb_disconnect(struct usb_interface *intf) +{ + struct ld_usb *dev; + int minor; + + down(&disconnect_sem); + + dev = usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + + down(&dev->sem); + + minor = intf->minor; + + /* give back our minor */ + usb_deregister_dev(intf, &ld_usb_class); + + /* if the device is not opened, then we clean up right now */ + if (!dev->open_count) { + up(&dev->sem); + ld_usb_delete(dev); + } else { + dev->intf = NULL; + up(&dev->sem); + } + + up(&disconnect_sem); + + dev_info(&intf->dev, "LD USB Device #%d now disconnected\n", + (minor - USB_LD_MINOR_BASE)); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver ld_usb_driver = { + .owner = THIS_MODULE, + .name = "ldusb", + .probe = ld_usb_probe, + .disconnect = ld_usb_disconnect, + .id_table = ld_usb_table, +}; + +/** + * ld_usb_init + */ +static int __init ld_usb_init(void) +{ + int retval; + + /* register this driver with the USB subsystem */ + retval = usb_register(&ld_usb_driver); + if (retval) + err("usb_register failed for the "__FILE__" driver. Error number %d\n", retval); + + return retval; +} + +/** + * ld_usb_exit + */ +static void __exit ld_usb_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&ld_usb_driver); +} + +module_init(ld_usb_init); +module_exit(ld_usb_exit); + diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index 3104f28f6aa..cda7249a90b 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -461,7 +461,7 @@ static int perform_sglist ( static unsigned realworld = 1; module_param (realworld, uint, 0); -MODULE_PARM_DESC (realworld, "clear to demand stricter ch9 compliance"); +MODULE_PARM_DESC (realworld, "clear to demand stricter spec compliance"); static int get_altsetting (struct usbtest_dev *dev) { @@ -604,9 +604,8 @@ static int ch9_postconfig (struct usbtest_dev *dev) USB_DIR_IN | USB_RECIP_DEVICE, 0, 0, dev->buf, 1, USB_CTRL_GET_TIMEOUT); if (retval != 1 || dev->buf [0] != expected) { - dev_dbg (&iface->dev, - "get config --> %d (%d)\n", retval, - expected); + dev_dbg (&iface->dev, "get config --> %d %d (1 %d)\n", + retval, dev->buf[0], expected); return (retval < 0) ? retval : -EDOM; } } @@ -1243,7 +1242,7 @@ static int ctrl_out (struct usbtest_dev *dev, char *what = "?"; struct usb_device *udev; - if (length > 0xffff || vary >= length) + if (length < 1 || length > 0xffff || vary >= length) return -EINVAL; buf = kmalloc(length, SLAB_KERNEL); @@ -1266,6 +1265,11 @@ static int ctrl_out (struct usbtest_dev *dev, 0, 0, buf, len, USB_CTRL_SET_TIMEOUT); if (retval != len) { what = "write"; + if (retval >= 0) { + INFO(dev, "ctrl_out, wlen %d (expected %d)\n", + retval, len); + retval = -EBADMSG; + } break; } @@ -1275,6 +1279,11 @@ static int ctrl_out (struct usbtest_dev *dev, 0, 0, buf, len, USB_CTRL_GET_TIMEOUT); if (retval != len) { what = "read"; + if (retval >= 0) { + INFO(dev, "ctrl_out, rlen %d (expected %d)\n", + retval, len); + retval = -EBADMSG; + } break; } @@ -1293,8 +1302,13 @@ static int ctrl_out (struct usbtest_dev *dev, } len += vary; + + /* [real world] the "zero bytes IN" case isn't really used. + * hardware can easily trip up in this wierd case, since its + * status stage is IN, not OUT like other ep0in transfers. + */ if (len > length) - len = 0; + len = realworld ? 1 : 0; } if (retval < 0) @@ -1519,6 +1533,11 @@ usbtest_ioctl (struct usb_interface *intf, unsigned int code, void *buf) if (down_interruptible (&dev->sem)) return -ERESTARTSYS; + if (intf->dev.power.power_state != PMSG_ON) { + up (&dev->sem); + return -EHOSTUNREACH; + } + /* some devices, like ez-usb default devices, need a non-default * altsetting to have any active endpoints. some tests change * altsettings; force a default so most tests don't need to check. @@ -1762,8 +1781,10 @@ usbtest_ioctl (struct usb_interface *intf, unsigned int code, void *buf) case 14: if (!dev->info->ctrl_out) break; - dev_dbg (&intf->dev, "TEST 14: %d ep0out, 0..%d vary %d\n", - param->iterations, param->length, param->vary); + dev_dbg (&intf->dev, "TEST 14: %d ep0out, %d..%d vary %d\n", + param->iterations, + realworld ? 1 : 0, param->length, + param->vary); retval = ctrl_out (dev, param->iterations, param->length, param->vary); break; @@ -1927,6 +1948,27 @@ usbtest_probe (struct usb_interface *intf, const struct usb_device_id *id) return 0; } +static int usbtest_suspend (struct usb_interface *intf, pm_message_t message) +{ + struct usbtest_dev *dev = usb_get_intfdata (intf); + + down (&dev->sem); + intf->dev.power.power_state = PMSG_SUSPEND; + up (&dev->sem); + return 0; +} + +static int usbtest_resume (struct usb_interface *intf) +{ + struct usbtest_dev *dev = usb_get_intfdata (intf); + + down (&dev->sem); + intf->dev.power.power_state = PMSG_ON; + up (&dev->sem); + return 0; +} + + static void usbtest_disconnect (struct usb_interface *intf) { struct usbtest_dev *dev = usb_get_intfdata (intf); @@ -2115,6 +2157,8 @@ static struct usb_driver usbtest_driver = { .probe = usbtest_probe, .ioctl = usbtest_ioctl, .disconnect = usbtest_disconnect, + .suspend = usbtest_suspend, + .resume = usbtest_resume, }; /*-------------------------------------------------------------------------*/ |