Here's an initial whack at a Linux-USB test framework.  I've found
it useful so far; it's turned up real bugs we'll want to fix.  It's
worth a look from you if you're concerned with robustness, stability,
or correctness of the USB core/hcd code.  Or just wanted some more
involved USB sanity checks, to give questionable hardware a once-over.

It supports a hands-off automated test mode, and I imagine it gaining
more tests (easy to add them!) and helping regression-test new kernels.
It also spits out some performance numbers that could sometimes help,
like most tools that act as traffic generators.

The framework has three parts:

   * Test Devices (Firmware).  A lot of widely available products can
     be used by downloading EZ-USB test firmware.
        (not provided here)

   * Kernel driver(s).  I keep thinking this would best be put in
     the standard distribution, so it stays up to date and serves as
     a more executable version of the Linux-USB API spec.
        ATTACHED as 'usbtest-930.patch'

   * Test harness.  User mode programs to talk to the kernel driver,
     running tests, making whatever reports are needed.
        ATTACHED as 'testusb.c'

Unlike the old 'usbstress', this uses usbfs only to locate devices (and
that could be changed); it's not using it to do I/O.  It's testing with
normal driver APIs, with URBs and so on.

More information is at the end of this note, including sample test runs
and test output.  (For slightly older versions of what I've attached.)

Some questions I have:

   * Do you also think we should have a test framework?

   * What else should it do, if you want one?

   * Does anyone know of another one of these (not 'usbstress-0.3')?
     I'm under the impression most testing is whether a particular
     driver behaves, which can leave huge gaps in test coverage.

   * Is this the right way to factor such a framework?

   * Who will help write test cases?

   * Who can help write test firmware?

   * Where should the different harness components live?

I think one plausible scheme would put the kernel driver into the 2.5
tree, and the other stuff in some linux-usb page at SourceForge along
with documentation ("how to set it up and use it") and so on.

- Dave


DEVICE FIRMWARE

The test driver currently needs device firmware that just sinks or sources
data ... the stuff of basic chip SDK examples.

Right now I'm using a tweaked version of some firmware that's available with
the Cypress FX2 SDK, since it works at either full or high speed.  This is
what its descriptors look like at full speed; for high speed the maximum
packet sizes are of course 512 bytes (except for ep0, which is still 64).

   T:  Bus=03 Lev=01 Prnt=01 Port=00 Cnt=01 Dev#=  2 Spd=12  MxCh= 0
   D:  Ver= 2.00 Cls=00(>ifc ) Sub=00 Prot=00 MxPS=64 #Cfgs=  1
   P:  Vendor=0547 ProdID=1002 Rev= 0.00
   S:  Manufacturer=Cypress
   S:  Product=Bulk Source: EZ-USB FX2
   C:* #Ifs= 1 Cfg#= 1 Atr=a0 MxPwr=100mA
   I:  If#= 0 Alt= 0 #EPs= 4 Cls=ff(vend.) Sub=00 Prot=00 Driver=usbtest
   E:  Ad=02(O) Atr=02(Bulk) MxPS=  64 Ivl=0ms
   E:  Ad=04(O) Atr=02(Bulk) MxPS=  64 Ivl=0ms
   E:  Ad=86(I) Atr=02(Bulk) MxPS=  64 Ivl=0ms
   E:  Ad=88(I) Atr=02(Bulk) MxPS=  64 Ivl=0ms

It's likely that GPL'd test firmware will appear at some point.  It should
be easy to make use SA-1100 based PDAs to do this, for example.  And even
with all its quirks, I'm sure SDCC is up to the job of supporting that much
for FX or FX2 devices; anyone with an FX-based serial adapter can run such
test software.  And that over time we'll accumulate more aggressive test
cases, that need more functional firmware.  After the basics work ... :)


USBTEST DRIVER and TEST HARNESS

The kernel "usbtest" driver is extensible (add to a switch statement) and
is driven from user mode using ioctls.  I've provided a fairly dumb test
harness (named 'testusb' just to confuse y'all) that calls that driver from
shell commands.  There are some tweakable options:

   [root@krypton misc]# testusb
   must specify '-a' or '-D dev'
   usage: testusb [-a] [-D dev] [-n] [-c iterations] [-s packetsize] [-g sglen]
   [root@krypton misc]#

Use 'usbtest -a' to test all recognized devices in parallel (one thread
per device).  I did this on a uniprocessor, there's lots of I/O parallism,
but likely these would be good SMP test modes too:

   [root@krypton misc]# usbtree
   /:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=ehci-hcd/5p, 480M
       |__ Port 1: Dev 2, If 0, Class=vend., Driver=usbtest, 480M
       |__ Port 2: Dev 3, If 0, Class=vend., Driver=usbtest, 480M
   [root@krypton misc]#
   [root@krypton misc]# testusb -a
   unknown speed        /proc/bus/usb/001/003
   unknown speed        /proc/bus/usb/001/002
   /proc/bus/usb/001/002 test 0 took 0.000011 sec
   /proc/bus/usb/001/003 test 0 took 0.000006 sec
   /proc/bus/usb/001/003 test 1 took 0.201934 sec
   /proc/bus/usb/001/002 test 2 took 0.226852 sec
   /proc/bus/usb/001/003 test 3 took 0.211918 sec
   /proc/bus/usb/001/002 test 4 took 0.222404 sec
   /proc/bus/usb/001/003 test 5 took 2.137454 sec
   /proc/bus/usb/001/002 test 6 took 2.133821 sec
   /proc/bus/usb/001/003 test 7 took 2.125387 sec
   /proc/bus/usb/001/002 test 8 took 2.115402 sec
   [root@krypton misc]#

What did the tests do?  UTSL, or for the short version:

   [root@krypton misc]# dmesg | tail -10
   usbtest.c: 02:00.2-1 TEST 0:  NOP
   usbtest.c: 02:00.2-1 TEST 2:  read 512 bytes 1000 times
   usbtest.c: 02:00.2-2 TEST 0:  NOP
   usbtest.c: 02:00.2-2 TEST 1:  write 512 bytes 1000 times
   usbtest.c: 02:00.2-2 TEST 3:  write 0..512 bytes 1000 times
   usbtest.c: 02:00.2-1 TEST 4:  read 0..512 bytes 1000 times
   usbtest.c: 02:00.2-2 TEST 5:  write 1000 sglists, 32 entries of 512 bytes
   usbtest.c: 02:00.2-1 TEST 6:  read 1000 sglists, 32 entries of 512 bytes
   usbtest.c: 02:00.2-2 TEST 7:  write 1000 sglists, 32 entries 0..512 bytes
   usbtest.c: 02:00.2-1 TEST 8:  read 1000 sglists, 32 entries 0..512 bytes
   [root@krypton misc]#

There are a bunch of other tests that could be done with this same simple
firmware; cancelation (with and without a queue...) would be good to add.

For that firmware, I've set up 'usbtest' so it doesn't try to read data,
since there seems to be a read side problem in that tweaked firmware.
You'll notice another device was used to support the read side tests;
devices don't need to offer the same test capabilities.

The default settings happen to make many of those test pairs identical
since they don't vary the buffer sizes:  test1/test3 and test5/test7 for
writing; test2/test4 and test6/test8 for reading.  Right now that can be
changed by passing '-s 4096', so some buffers vary from 512..4096 bytes,
or '-v 64' (fullspeed only!) to vary write sizes in units of full speed
packets, not high speed ones.


OHCI-HCD RESULTS

These are pretty consistent, and at least the write side seems willing to go
on forever without any problems.  The "one buffer" tests each take less than
a frame, so a thousand iterations takes about a second (maybe less, if the
test starts during the right part of a frame).

The queued transfers all seem to need about eighteen seconds, while I'd have
expected a bit under 16 seconds based on just bandwidth.  I'm not sure yet
why the difference appears.

   PASS 200
   unknown speed        /proc/bus/usb/003/002
   /proc/bus/usb/003/002 test 0 took 0.000012 sec
   /proc/bus/usb/003/002 test 1 took 1.011343 sec
   /proc/bus/usb/003/002 test 3 took 1.005735 sec
   /proc/bus/usb/003/002 test 5 took 17.996584 sec
   /proc/bus/usb/003/002 test 7 took 18.001348 sec

   PASS 201
   unknown speed        /proc/bus/usb/003/002
   /proc/bus/usb/003/002 test 0 took 0.000011 sec
   /proc/bus/usb/003/002 test 1 took 0.999980 sec
   /proc/bus/usb/003/002 test 3 took 0.999745 sec
   /proc/bus/usb/003/002 test 5 took 17.996581 sec
   /proc/bus/usb/003/002 test 7 took 17.996406 sec

   PASS 202
   unknown speed        /proc/bus/usb/003/002
   /proc/bus/usb/003/002 test 0 took 0.000011 sec
   /proc/bus/usb/003/002 test 1 took 0.999947 sec
   /proc/bus/usb/003/002 test 3 took 0.999743 sec
   /proc/bus/usb/003/002 test 5 took 17.996603 sec
   /proc/bus/usb/003/002 test 7 took 17.996392 sec

Just from those numbers, you won't notice the reduction in interrupt rates.
The OHCI driver takes advantage of a hint given by the usb_sg_*() code: an
URB_NO_INTERRUPT flag suggests to the HCD that only the last URB in the
scatterlist really needs an IRQ.  OHCI uses that to delay interrupts for
most of the queued urbs (for up to six frames), and most of the other HCDs
could use it in much the same way.

Here's a snapshot of the OHCI 'async' schedule list (in 'driverfs') for that
controller while it's running test5.  There are 20 tds still queued from the
scatterlist provided, so 12 urbs completed already.  But the ED (think "QH"
in UHCI or EHCI terms) completed eight already, and is part way through a
ninth.  Likely this queue batch of 32 urbs will manage to complete with
just three interrupts total.

   ed/c0005040 fs dev2 ep2-out max 64 00400902 DATA0
        td c0346300 out 0 cc=0 urb c57e31fc (02cc0000)
        td c0346340 out 0 cc=0 urb c57e37ac (02cc0000)
        td c0346380 out 0 cc=0 urb c57e32cc (02cc0000)
        td c03463c0 out 0 cc=0 urb c74f5674 (02cc0000)
        td c0346400 out 0 cc=0 urb c74f5c8c (02cc0000)
        td c0346440 out 0 cc=0 urb c74f5aec (02cc0000)
        td c0346480 out 0 cc=0 urb c74f58e4 (02cc0000)
        td c03464c0 out 0 cc=0 urb c74f5d5c (02cc0000)
        td c0346500 out 128 cc=0 urb c74f5814 (02cc0000)
        td c0346540 out 512 cc=f urb c74f5b54 (f0cc0000)
        td c0346580 out 512 cc=f urb c74f5a1c (f0cc0000)
        td c03465c0 out 512 cc=f urb c74f5bbc (f0cc0000)
        td c0346600 out 512 cc=f urb c74f59b4 (f0cc0000)
        td c0346640 out 512 cc=f urb c74f5dc4 (f0cc0000)
        td c0346680 out 512 cc=f urb c74f54d4 (f0cc0000)
        td c03466c0 out 512 cc=f urb c74f5264 (f0cc0000)
        td c0346700 out 512 cc=f urb c74f5a84 (f0cc0000)
        td c0346740 out 512 cc=f urb c74f5e2c (f0cc0000)
        td c0346780 out 512 cc=f urb c74f594c (f0cc0000)
        td c03467c0 out 512 cc=f urb c74f553c (f00c0000)

Presumably different OHCI implementations (that was NEC) would never
show behavioral differences that Linux would need to care about...


RESULTS FOR OTHER HOST CONTROLLER DRIVERS

The EHCI driver is of course faster than OHCI, as you'll notice from
the example earlier where it ran two concurrent tests (read/write) in
much less time than the OHCI driver took for just write tests.  I sent
in some small patches for its urb queueing, but think there are still
some issues there to address.

Unfortunately I've seen the "uhci-hcd" driver lock up my 2.5.39 system
with the queuing test cases.  Luckily that's now easily reproduced ... :)
The queueing sometimes behaves, and the non-queued tests seem fine.

Nothing in this test framework knows anything about how the HCDs work,
so it should be possible to use this to test the sl811hs and (eventually)
cris controller drivers, as well as the various out-of-kernel HCDs that
are floating around.
--- ./drivers/usb-dist/Makefile Tue Aug 27 15:46:58 2002
+++ ./drivers/usb/Makefile      Mon Sep 30 11:47:42 2002
@@ -61,4 +61,5 @@
 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/
--- ./drivers/usb-dist/misc/Config.in   Sun Sep 15 19:57:44 2002
+++ ./drivers/usb/misc/Config.in        Mon Sep 30 11:47:43 2002
@@ -10,2 +10,3 @@
 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
--- ./drivers/usb-dist/misc/Config.help Fri Sep 27 17:35:32 2002
+++ ./drivers/usb/misc/Config.help      Mon Sep 30 11:47:43 2002
@@ -113,4 +113,10 @@
   <http://linux-usb.sourceforge.net/SpeedTouch/>.
 
+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
--- ./drivers/usb-dist/misc/Makefile    Tue Aug 27 15:46:58 2002
+++ ./drivers/usb/misc/Makefile Mon Sep 30 11:47:43 2002
@@ -12,4 +12,5 @@
 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
--- ./drivers/usb-dist/misc/usbtest.c   Wed Dec 31 16:00:00 1969
+++ ./drivers/usb/misc/usbtest.c        Mon Sep 30 11:47:43 2002
@@ -0,0 +1,570 @@
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+//#include <linux/time.h>
+#include <asm/scatterlist.h>
+
+#if !defined (DEBUG) && defined (CONFIG_USB_DEBUG)
+#   define DEBUG
+#endif
+#include <linux/usb.h>
+
+
+/*-------------------------------------------------------------------------*/
+
+// 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 (&param->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");
+
/* cc -Wall -g -lpthread -o testusb testusb.c */

#include <stdio.h>
#include <string.h>
#include <ftw.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <sys/ioctl.h>
#include <linux/usbdevice_fs.h>

/*-------------------------------------------------------------------------*/

#define TEST_CASES      20

// 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)

/*-------------------------------------------------------------------------*/

/* SHOULD be exported from <linux/usb.h>, isn't */
struct usb_device_descriptor {
        __u8  bLength;
        __u8  bDescriptorType;
        __u16 bcdUSB;
        __u8  bDeviceClass;
        __u8  bDeviceSubClass;
        __u8  bDeviceProtocol;
        __u8  bMaxPacketSize0;
        __u16 idVendor;
        __u16 idProduct;
        __u16 bcdDevice;
        __u8  iManufacturer;
        __u8  iProduct;
        __u8  iSerialNumber;
        __u8  bNumConfigurations;
} __attribute__ ((packed));

enum usb_device_speed {
        USB_SPEED_UNKNOWN = 0,                  /* enumerating */
        USB_SPEED_LOW, USB_SPEED_FULL,          /* usb 1.1 */
        USB_SPEED_HIGH                          /* usb 2.0 */
};

/*-------------------------------------------------------------------------*/

static char *speed (enum usb_device_speed s)
{
        switch (s) {
        case USB_SPEED_UNKNOWN: return "unknown";
        case USB_SPEED_LOW:     return "low";
        case USB_SPEED_FULL:    return "full";
        case USB_SPEED_HIGH:    return "high";
        default:                return "??";
        }
}

struct testdev {
        struct testdev          *next;
        char                    *name;
        pthread_t               thread;
        enum usb_device_speed   speed;

        struct usbtest_param    param;
};
static struct testdev           *testdevs;

static int is_testdev (struct usb_device_descriptor *dev)
{
        /* FX2 with (tweaked) bulksrc firmware */
        if (dev->idVendor == 0x0547 && dev->idProduct == 0x1002)
                return 1;

        /* iBOT2 high speed webcam */
        if (dev->idVendor == 0x0b62 && dev->idProduct == 0x0059)
                return 1;

        return 0;
}

static int find_testdev (const char *name, const struct stat *sb, int flag)
{
        int                             fd;
        struct usb_device_descriptor    dev;

        if (flag != FTW_F)
                return 0;
        /* ignore /proc/bus/usb/{devices,drivers} */
        if (strrchr (name, '/')[1] == 'd')
                return 0;

        if ((fd = open (name, O_RDONLY)) < 0) {
                perror ("can't open dev file r/o");
                return 0;
        }
        if (read (fd, &dev, sizeof dev) != sizeof dev)
                fputs ("short devfile read!\n", stderr);
        else if (is_testdev (&dev)) {
                struct testdev          *entry;

                if ((entry = malloc (sizeof *entry)) != 0) {
                        entry->next = testdevs;
                        entry->name = strdup (name);
                        if (!entry->name) {
                                free (entry);
                                entry = 0;
                        } else
                                testdevs = entry;
                }

                // FIXME ask usbfs what speed; update USBDEVFS_CONNECTINFO
                // so it tells about high speed etc

                if (!entry)
                        fputs ("no mem!\n", stderr);
                else
                        fprintf (stderr, "%s speed\t%s\n",
                                        speed (entry->speed), entry->name);
        }
        close (fd);
        return 0;
}

static int
usbdev_ioctl (int fd, int ifno, unsigned request, void *param)
{
        struct usbdevfs_ioctl   wrapper;

        wrapper.ifno = ifno;
        wrapper.ioctl_code = request;
        wrapper.data = param;

        return ioctl (fd, USBDEVFS_IOCTL, &wrapper);
}

static void *handle_testdev (void *arg)
{
        struct testdev          *dev = arg;
        int                     fd, i;
        int                     status;

        if ((fd = open (dev->name, O_RDWR)) < 0) {
                perror ("can't open dev file r/w");
                return 0;
        }

        for (i = 0; i < TEST_CASES; i++) {
                dev->param.test_num = i;

                // FIXME don't hardwire interface #0
                status = usbdev_ioctl (fd, 0, USBTEST_REQUEST, &dev->param);
                if (status < 0 && errno == EOPNOTSUPP)
                        continue;

                /* NOTE: each thread emits complete lines; no fragments! */
                if (status < 0)
                        printf ("%s test %d --> %d (%s)\n",
                                dev->name, i, errno,
                                (errno < sys_nerr)
                                        ? sys_errlist [errno]
                                        : "??");
                else
                        printf ("%s test %d, %4d.%.06d secs\n", dev->name, i,
                                (int) dev->param.duration.tv_sec,
                                (int) dev->param.duration.tv_usec);

                fflush (stdout);
        }

        close (fd);
        return arg;
}

int main (int argc, char **argv)
{
        int                     c;
        struct testdev          *entry;
        char                    *device = 0;
        int                     all = 0, not = 0;
        struct usbtest_param    param;

        /* pick defaults that works with all speeds, without short packets.
         *
         * Best per-frame data rates:
         *     high speed, bulk       512 * 13 * 8 = 53248
         *                 interrupt 1024 *  3 * 8 = 24576
         *     full speed, bulk/intr   64 * 19     =  1216
         *                 interrupt   64 *  1     =    64
         *      low speed, interrupt    8 *  1     =     8
         */
        param.iterations = 1000;
        param.length = 512;
        param.vary = 512;
        param.sglen = 32;

        while ((c = getopt (argc, argv, "D:ac:g:ns:v:")) != EOF)
        switch (c) {
        case 'D':       /* device, if only one */
                device = optarg;
                continue;
        case 'a':       /* use all devices */
                all = 1;
                continue;
        case 'c':       /* count iterations */
                param.iterations = atoi (optarg);
                if (param.iterations < 0)
                        goto usage;
                continue;
        case 'g':       /* scatter/gather entries */
                param.sglen = atoi (optarg);
                if (param.sglen < 0)
                        goto usage;
                continue;
        case 'n':       /* no test running! */
                not = 1;
                continue;
        case 's':       /* size of packet */
                param.length = atoi (optarg);
                if (param.length < 0)
                        goto usage;
                continue;
        case 'v':       /* vary packet size by ... */
                param.vary = atoi (optarg);
                if (param.vary < 0)
                        goto usage;
                continue;
        default:
usage:
                fprintf (stderr, "usage: %s "
                        "[-an] [-D dev] "
                        "[-c iterations] [-s packetsize] [-g sglen] [-v vary]"
                        "\n",
                        argv [0]);
                return 1;
        }
        if (optind != argc)
                goto usage;
        if (!all && !device) {
                fprintf (stderr, "must specify '-a' or '-D dev'\n");
                goto usage;
        }

        /* collect and list the test devices */
        if (ftw ("/proc/bus/usb", find_testdev, 3) != 0) {
                fputs ("ftw failed; is usbfs missing?\n", stderr);
                return -1;
        }

        /* quit, run single test, or create test threads */
        if (not)
                return 0;
        if (!testdevs) {
                fputs ("no test devices recognized\n", stderr);
                return -1;
        }
        if (testdevs->next == 0 && !device)
                device = testdevs->name;
        for (entry = testdevs; entry; entry = entry->next) {
                int     status;

                entry->param = param;

                if (device) {
                        if (strcmp (entry->name, device))
                                continue;
                        return handle_testdev (entry) != entry;
                }
                status = pthread_create (&entry->thread, 0, handle_testdev, entry);
                if (status) {
                        perror ("pthread_create");
                        continue;
                }
        }
        if (device) {
                fprintf (stderr, "%s: %s not recognized as testable\n",
                                argv [0], device);
                return 1;
        }

        /* wait for tests to complete */
        for (entry = testdevs; entry; entry = entry->next) {
                void    *retval;

                if (pthread_join (entry->thread, &retval))
                        perror ("pthread_join");
                /* testing errors discarded! */
        }

        return 0;
}

Reply via email to