# This is a BitKeeper generated patch for the following project: # Project Name: Linux kernel tree # This patch format is intended for GNU patch command version 2.5 or higher. # This patch includes the following deltas: # ChangeSet 1.679 -> 1.680 # drivers/usb/misc/Config.help 1.6 -> 1.7 # drivers/usb/misc/Makefile 1.9 -> 1.10 # drivers/usb/misc/Config.in 1.3 -> 1.4 # drivers/usb/Makefile 1.36 -> 1.37 # (new) -> 1.1 drivers/usb/misc/usbtest.c # # The following is the BitKeeper ChangeSet Log # -------------------------------------------- # 02/10/02 david-b@pacbell.net 1.680 # [PATCH] USB: framework for testing usbcore # # USB test driver # -------------------------------------------- # diff -Nru a/drivers/usb/Makefile b/drivers/usb/Makefile --- a/drivers/usb/Makefile Wed Oct 2 22:40:20 2002 +++ b/drivers/usb/Makefile Wed Oct 2 22:40:20 2002 @@ -60,6 +60,7 @@ obj-$(CONFIG_USB_LCD) += misc/ obj-$(CONFIG_USB_RIO500) += misc/ obj-$(CONFIG_USB_SPEEDTOUCH) += misc/ +obj-$(CONFIG_USB_TEST) += misc/ obj-$(CONFIG_USB_TIGL) += misc/ obj-$(CONFIG_USB_USS720) += misc/ diff -Nru a/drivers/usb/misc/Config.help b/drivers/usb/misc/Config.help --- a/drivers/usb/misc/Config.help Wed Oct 2 22:40:20 2002 +++ b/drivers/usb/misc/Config.help Wed Oct 2 22:40:20 2002 @@ -112,6 +112,12 @@ For more information, see Johan Verrept's webpages at . +CONFIG_USB_TEST + + This driver is for testing host controller software. It is used + with specialized device firmware for regression and stress testing, + to help prevent problems from cropping up with 'real" drivers. + CONFIG_USB_LCD Say Y here if you want to connect an USBLCD to your computer's USB port. The USBLCD is a small USB interface board for diff -Nru a/drivers/usb/misc/Config.in b/drivers/usb/misc/Config.in --- a/drivers/usb/misc/Config.in Wed Oct 2 22:40:20 2002 +++ b/drivers/usb/misc/Config.in Wed Oct 2 22:40:20 2002 @@ -9,3 +9,4 @@ dep_tristate ' Tieman Voyager USB Braille display support (EXPERIMENTAL)' CONFIG_USB_BRLVGER $CONFIG_USB $CONFIG_EXPERIMENTAL dep_tristate ' USB LCD driver support' CONFIG_USB_LCD $CONFIG_USB dep_tristate ' Alcatel Speedtouch ADSL USB Modem' CONFIG_USB_SPEEDTOUCH $CONFIG_USB $CONFIG_ATM +dep_tristate ' USB testing driver (DEVELOPMENT)' CONFIG_USB_TEST $CONFIG_USB_DEVICEFS $CONFIG_EXPERIMENTAL diff -Nru a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile --- a/drivers/usb/misc/Makefile Wed Oct 2 22:40:20 2002 +++ b/drivers/usb/misc/Makefile Wed Oct 2 22:40:20 2002 @@ -11,6 +11,7 @@ obj-$(CONFIG_USB_LCD) += usblcd.o obj-$(CONFIG_USB_RIO500) += rio500.o obj-$(CONFIG_USB_SPEEDTOUCH) += speedtouch.o atmsar.o +obj-$(CONFIG_USB_TEST) += usbtest.o obj-$(CONFIG_USB_TIGL) += tiglusb.o obj-$(CONFIG_USB_USS720) += uss720.o diff -Nru a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c --- /dev/null Wed Dec 31 16:00:00 1969 +++ b/drivers/usb/misc/usbtest.c Wed Oct 2 22:40:20 2002 @@ -0,0 +1,570 @@ +#include +#include +#include +#include +#include +#include +#include +//#include +#include + +#if !defined (DEBUG) && defined (CONFIG_USB_DEBUG) +# define DEBUG +#endif +#include + + +/*-------------------------------------------------------------------------*/ + +// FIXME make these public somewhere; usbdevfs.h? +// +struct usbtest_param { + // inputs + unsigned test_num; /* 0..(TEST_CASES-1) */ + int iterations; + int length; + int vary; + int sglen; + + // outputs + struct timeval duration; +}; +#define USBTEST_REQUEST _IOWR('U', 100, struct usbtest_param) + +/*-------------------------------------------------------------------------*/ + +/* this is accessed only through usbfs ioctl calls. + * one ioctl to issue a test ... no locking needed!!! + * tests create other threads if they need them. + * urbs and buffers are allocated dynamically, + * and data generated deterministically. + * + * there's a minor complication on disconnect(), since + * usbfs.disconnect() waits till our ioctl completes. + */ +struct usbtest_dev { + struct usb_interface *intf; + struct testdev_info *info; + char id [32]; + int in_pipe; + int out_pipe; +}; + +static struct usb_device *testdev_to_usbdev (struct usbtest_dev *test) +{ + return interface_to_usbdev (test->intf); +} + +/* set up all urbs so they can be used with either bulk or interrupt */ +#define INTERRUPT_RATE 1 /* msec/transfer */ + +/*-------------------------------------------------------------------------*/ + +/* Support for testing basic non-queued I/O streams. + * + * These just package urbs as requests that can be easily canceled. + * Each urb's data buffer is dynamically allocated; callers can fill + * them with non-zero test data (or test for it) when appropriate. + */ + +static void simple_callback (struct urb *urb) +{ + complete ((struct completion *) urb->context); +} + +static struct urb *simple_alloc_urb ( + struct usb_device *udev, + int pipe, + long bytes +) +{ + struct urb *urb; + + if (bytes < 0) + return 0; + urb = usb_alloc_urb (0, SLAB_KERNEL); + if (!urb) + return urb; + usb_fill_bulk_urb (urb, udev, pipe, 0, bytes, simple_callback, 0); + urb->interval = (udev->speed == USB_SPEED_HIGH) + ? (INTERRUPT_RATE << 3) + : INTERRUPT_RATE, + urb->transfer_flags = URB_NO_DMA_MAP; + urb->transfer_buffer = usb_buffer_alloc (udev, bytes, SLAB_KERNEL, + &urb->transfer_dma); + if (!urb->transfer_buffer) { + usb_free_urb (urb); + urb = 0; + } else + memset (urb->transfer_buffer, 0, bytes); + return urb; +} + +static void simple_free_urb (struct urb *urb) +{ + usb_buffer_free (urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + usb_free_urb (urb); +} + +static int simple_io ( + struct urb *urb, + int iterations, + int vary +) +{ + struct usb_device *udev = urb->dev; + int max = urb->transfer_buffer_length; + struct completion completion; + int retval = 0; + + urb->context = &completion; + while (iterations-- > 0 && retval == 0) { + init_completion (&completion); + if ((retval = usb_submit_urb (urb, SLAB_KERNEL)) != 0) + break; + + /* NOTE: no timeouts; can't be broken out of by interrupt */ + wait_for_completion (&completion); + retval = urb->status; + urb->dev = udev; + + if (vary) { + int len = urb->transfer_buffer_length; + + len += max; + len %= max; + if (len == 0) + len = (vary < max) ? vary : max; + urb->transfer_buffer_length = len; + } + + /* FIXME if endpoint halted, clear halt (and log) */ + } + urb->transfer_buffer_length = max; + + // FIXME for unlink or fault handling tests, don't report + // failure if retval is as we expected ... + if (retval) + dbg ("simple_io failed, iterations left %d, status %d", + iterations, retval); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* We use scatterlist primitives to test queued I/O. + * Yes, this also tests the scatterlist primitives. + */ + +static void free_sglist (struct scatterlist *sg, int nents) +{ + unsigned i; + + if (!sg) + return; + for (i = 0; i < nents; i++) { + if (!sg [i].page) + continue; + kfree (page_address (sg [i].page) + sg [i].offset); + } + kfree (sg); +} + +static struct scatterlist * +alloc_sglist (int nents, int max, int vary) +{ + struct scatterlist *sg; + unsigned i; + unsigned size = max; + + sg = kmalloc (nents * sizeof *sg, SLAB_KERNEL); + if (!sg) + return 0; + memset (sg, 0, nents * sizeof *sg); + + for (i = 0; i < nents; i++) { + char *buf; + + buf = kmalloc (size, SLAB_KERNEL); + if (!buf) { + free_sglist (sg, i); + return 0; + } + memset (buf, 0, size); + + /* kmalloc pages are always physically contiguous! */ + sg [i].page = virt_to_page (buf); + sg [i].offset = ((unsigned) buf) & ~PAGE_MASK; + sg [i].length = size; + + if (vary) { + size += vary; + size %= max; + if (size == 0) + size = (vary < max) ? vary : max; + } + } + + return sg; +} + +static int perform_sglist ( + struct usb_device *udev, + unsigned iterations, + int pipe, + struct usb_sg_request *req, + struct scatterlist *sg, + int nents +) +{ + int retval = 0; + + while (retval == 0 && iterations-- > 0) { + retval = usb_sg_init (req, udev, pipe, + (udev->speed == USB_SPEED_HIGH) + ? (INTERRUPT_RATE << 3) + : INTERRUPT_RATE, + sg, nents, 0, SLAB_KERNEL); + + if (retval) + break; + usb_sg_wait (req); + retval = req->status; + + /* FIXME if endpoint halted, clear halt (and log) */ + } + + // FIXME for unlink or fault handling tests, don't report + // failure if retval is as we expected ... + + if (retval) + dbg ("perform_sglist failed, iterations left %d, status %d", + iterations, retval); + return retval; +} + + +/*-------------------------------------------------------------------------*/ + +/* We only have this one interface to user space, through usbfs. + * User mode code can scan usbfs to find N different devices (maybe on + * different busses) to use when testing, and allocate one thread per + * test. So discovery is simplified, and we have no device naming issues. + * + * Don't use these only as stress/load tests. Use them along with with + * other USB bus activity: plugging, unplugging, mousing, mp3 playback, + * video capture, and so on. Run different tests at different times, in + * different sequences. Nothing here should interact with other devices, + * except indirectly by consuming USB bandwidth and CPU resources for test + * threads and request completion. + */ + +static int usbtest_ioctl (struct usb_interface *intf, unsigned int code, void *buf) +{ + struct usbtest_dev *dev = dev_get_drvdata (&intf->dev); + struct usb_device *udev = testdev_to_usbdev (dev); + struct usbtest_param *param = buf; + int retval = -EOPNOTSUPP; + struct urb *urb; + struct scatterlist *sg; + struct usb_sg_request req; + struct timeval start; + + // FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. + + if (code != USBTEST_REQUEST) + return -EOPNOTSUPP; + + if (param->iterations <= 0 || param->length < 0 + || param->sglen < 0 || param->vary < 0) + return -EINVAL; + + /* + * Just a bunch of test cases that every HCD is expected to handle. + * + * Some may need specific firmware, though it'd be good to have + * one firmware image to handle all the test cases. + * + * FIXME add more tests! cancel requests, verify the data, control + * requests, and so on. + */ + do_gettimeofday (&start); + switch (param->test_num) { + + case 0: + dbg ("%s TEST 0: NOP", dev->id); + retval = 0; + break; + + /* Simple non-queued bulk I/O tests */ + case 1: + if (dev->out_pipe == 0) + break; + dbg ("%s TEST 1: write %d bytes %u times", dev->id, + param->length, param->iterations); + urb = simple_alloc_urb (udev, dev->out_pipe, param->length); + if (!urb) { + retval = -ENOMEM; + break; + } + // FIRMWARE: bulk sink (maybe accepts short writes) + retval = simple_io (urb, param->iterations, 0); + simple_free_urb (urb); + break; + case 2: + if (dev->in_pipe == 0) + break; + dbg ("%s TEST 2: read %d bytes %u times", dev->id, + param->length, param->iterations); + urb = simple_alloc_urb (udev, dev->in_pipe, param->length); + if (!urb) { + retval = -ENOMEM; + break; + } + // FIRMWARE: bulk source (maybe generates short writes) + retval = simple_io (urb, param->iterations, 0); + simple_free_urb (urb); + break; + case 3: + if (dev->out_pipe == 0 || param->vary == 0) + break; + dbg ("%s TEST 3: write/%d 0..%d bytes %u times", dev->id, + param->vary, param->length, param->iterations); + urb = simple_alloc_urb (udev, dev->out_pipe, param->length); + if (!urb) { + retval = -ENOMEM; + break; + } + // FIRMWARE: bulk sink (maybe accepts short writes) + retval = simple_io (urb, param->iterations, param->vary); + simple_free_urb (urb); + break; + case 4: + if (dev->in_pipe == 0 || param->vary == 0) + break; + dbg ("%s TEST 3: read/%d 0..%d bytes %u times", dev->id, + param->vary, param->length, param->iterations); + urb = simple_alloc_urb (udev, dev->out_pipe, param->length); + if (!urb) { + retval = -ENOMEM; + break; + } + // FIRMWARE: bulk source (maybe generates short writes) + retval = simple_io (urb, param->iterations, param->vary); + simple_free_urb (urb); + break; + + /* Queued bulk I/O tests */ + case 5: + if (dev->out_pipe == 0 || param->sglen == 0) + break; + dbg ("%s TEST 5: write %d sglists, %d entries of %d bytes", + dev->id, param->iterations, + param->sglen, param->length); + sg = alloc_sglist (param->sglen, param->length, 0); + if (!sg) { + retval = -ENOMEM; + break; + } + // FIRMWARE: bulk sink (maybe accepts short writes) + retval = perform_sglist (udev, param->iterations, dev->out_pipe, + &req, sg, param->sglen); + free_sglist (sg, param->sglen); + break; + + case 6: + if (dev->in_pipe == 0 || param->sglen == 0) + break; + dbg ("%s TEST 6: read %d sglists, %d entries of %d bytes", + dev->id, param->iterations, + param->sglen, param->length); + sg = alloc_sglist (param->sglen, param->length, 0); + if (!sg) { + retval = -ENOMEM; + break; + } + // FIRMWARE: bulk source (maybe generates short writes) + retval = perform_sglist (udev, param->iterations, dev->in_pipe, + &req, sg, param->sglen); + free_sglist (sg, param->sglen); + break; + case 7: + if (dev->out_pipe == 0 || param->sglen == 0 || param->vary == 0) + break; + dbg ("%s TEST 7: write/%d %d sglists, %d entries 0..%d bytes", + dev->id, param->vary, param->iterations, + param->sglen, param->length); + sg = alloc_sglist (param->sglen, param->length, param->vary); + if (!sg) { + retval = -ENOMEM; + break; + } + // FIRMWARE: bulk sink (maybe accepts short writes) + retval = perform_sglist (udev, param->iterations, dev->out_pipe, + &req, sg, param->sglen); + free_sglist (sg, param->sglen); + break; + case 8: + if (dev->in_pipe == 0 || param->sglen == 0 || param->vary == 0) + break; + dbg ("%s TEST 8: read/%d %d sglists, %d entries 0..%d bytes", + dev->id, param->vary, param->iterations, + param->sglen, param->length); + sg = alloc_sglist (param->sglen, param->length, param->vary); + if (!sg) { + retval = -ENOMEM; + break; + } + // FIRMWARE: bulk source (maybe generates short writes) + retval = perform_sglist (udev, param->iterations, dev->in_pipe, + &req, sg, param->sglen); + free_sglist (sg, param->sglen); + break; + + /* test cases for the unlink/cancel codepaths need a thread to + * usb_unlink_urb() or usg_sg_cancel(), and a way to check if + * the urb/sg_request was properly canceled. + * + * for the unlink-queued cases, the usb_sg_*() code uses/tests + * the "streamed" cleanup mode, not the "packet" one + */ + + } + do_gettimeofday (¶m->duration); + param->duration.tv_sec -= start.tv_sec; + param->duration.tv_usec -= start.tv_usec; + if (param->duration.tv_usec < 0) { + param->duration.tv_usec += 1000 * 1000; + param->duration.tv_sec -= 1; + } + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* most programmable USB devices can be given firmware that will support the + * test cases above. one basic question is which endpoints to use for + * testing; endpoint numbers are not always firmware-selectable. + * + * for now, the driver_info in the device_id table entry just encodes the + * endpoint info for a pair of bulk-capable endpoints, which we can use + * for some interrupt transfer tests too. later this could get fancier. + */ +#define EP_PAIR(in,out) (((in)<<4)|(out)) + +static int force_interrupt = 0; +MODULE_PARM (force_interrupt, "i"); +MODULE_PARM_DESC (force_interrupt, "0 = test bulk (default), else interrupt"); + +static int +usbtest_probe (struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev; + struct usbtest_dev *dev; + unsigned long driver_info = id->driver_info; + + udev = interface_to_usbdev (intf); + + dev = kmalloc (sizeof *dev, SLAB_KERNEL); + if (!dev) + return -ENOMEM; + memset (dev, 0, sizeof *dev); + snprintf (dev->id, sizeof dev->id, "%s-%s", + udev->bus->bus_name, udev->devpath); + dev->intf = intf; + + /* NOTE this doesn't yet test the handful of difference that are + * visible with high speed devices: bigger maxpacket (1K) and + * "high bandwidth" modes (up to 3 packets/uframe). + */ + if (force_interrupt || udev->speed == USB_SPEED_LOW) { + if (driver_info & 0xf0) + dev->in_pipe = usb_rcvintpipe (udev, + (driver_info >> 4) & 0x0f); + if (driver_info & 0x0f) + dev->out_pipe = usb_sndintpipe (udev, + driver_info & 0x0f); + +#if 1 + // FIXME disabling this until we finally get rid of + // interrupt "automagic" resubmission + dbg ("%s: no interrupt transfers for now", dev->id); + kfree (dev); + return -ENODEV; +#endif + } else { + if (driver_info & 0xf0) + dev->in_pipe = usb_rcvbulkpipe (udev, + (driver_info >> 4) & 0x0f); + if (driver_info & 0x0f) + dev->out_pipe = usb_sndbulkpipe (udev, + driver_info & 0x0f); + } + + dev_set_drvdata (&intf->dev, dev); + info ("bound to %s ...%s%s", dev->id, + dev->out_pipe ? " writes" : "", + dev->in_pipe ? " reads" : ""); + return 0; +} + +static void usbtest_disconnect (struct usb_interface *intf) +{ + struct usbtest_dev *dev = dev_get_drvdata (&intf->dev); + + dev_set_drvdata (&intf->dev, 0); + info ("unbound %s", dev->id); + kfree (intf->private_data); +} + +/* Basic testing only needs a device that can source or sink bulk traffic. + */ +static struct usb_device_id id_table [] = { + + /* EZ-USB FX2 "bulksrc" or "bulkloop" firmware from Cypress + * reads disabled on this one, my version has some problem there + */ + { USB_DEVICE (0x0547, 0x1002), + .driver_info = EP_PAIR (0, 2), + }, +#if 1 + // this does not coexist with a real iBOT2 driver! + // it makes a nice source of high speed bulk-in data + { USB_DEVICE (0x0b62, 0x0059), + .driver_info = EP_PAIR (2, 0), + }, +#endif + + /* can that old "usbstress-0.3" firmware be used with this? */ + + { } +}; +MODULE_DEVICE_TABLE (usb, id_table); + +static struct usb_driver usbtest_driver = { + .owner = THIS_MODULE, + .name = "usbtest", + .id_table = id_table, + .probe = usbtest_probe, + .ioctl = usbtest_ioctl, + .disconnect = usbtest_disconnect, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init usbtest_init (void) +{ + return usb_register (&usbtest_driver); +} +module_init (usbtest_init); + +static void __exit usbtest_exit (void) +{ + usb_deregister (&usbtest_driver); +} +module_exit (usbtest_exit); + +MODULE_DESCRIPTION ("USB HCD Testing Driver"); +MODULE_LICENSE ("GPL"); +