# 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");
+