USB: testusb: testusb compatibility with FunctionFS gadget
[linux-3.10.git] / tools / usb / testusb.c
index 1f37298..f08e894 100644 (file)
@@ -1,7 +1,9 @@
-/* $(CROSS_COMPILE)cc -Wall -g -lpthread -o testusb testusb.c */
+/* $(CROSS_COMPILE)cc -Wall -Wextra -g -lpthread -o testusb testusb.c */
 
 /*
  * Copyright (c) 2002 by David Brownell
+ * Copyright (c) 2010 by Samsung Electronics
+ * Author: Michal Nazarewicz <m.nazarewicz@samsung.com>
  *
  * 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
  * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  */
 
+/*
+ * This program issues ioctls to perform the tests implemented by the
+ * kernel driver.  It can generate a variety of transfer patterns; you
+ * should make sure to test both regular streaming and mixes of
+ * transfer sizes (including short transfers).
+ *
+ * For more information on how this can be used and on USB testing
+ * refer to <URL:http://www.linux-usb.org/usbtest/>.
+ */
+
 #include <stdio.h>
 #include <string.h>
 #include <ftw.h>
@@ -25,6 +37,7 @@
 #include <pthread.h>
 #include <unistd.h>
 #include <errno.h>
+#include <limits.h>
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -56,6 +69,13 @@ struct usbtest_param {
 
 /* #include <linux/usb_ch9.h> */
 
+#define USB_DT_DEVICE                  0x01
+#define USB_DT_INTERFACE               0x04
+
+#define USB_CLASS_PER_INTERFACE                0       /* for DeviceClass */
+#define USB_CLASS_VENDOR_SPEC          0xff
+
+
 struct usb_device_descriptor {
        __u8  bLength;
        __u8  bDescriptorType;
@@ -73,6 +93,19 @@ struct usb_device_descriptor {
        __u8  bNumConfigurations;
 } __attribute__ ((packed));
 
+struct usb_interface_descriptor {
+       __u8  bLength;
+       __u8  bDescriptorType;
+
+       __u8  bInterfaceNumber;
+       __u8  bAlternateSetting;
+       __u8  bNumEndpoints;
+       __u8  bInterfaceClass;
+       __u8  bInterfaceSubClass;
+       __u8  bInterfaceProtocol;
+       __u8  iInterface;
+} __attribute__ ((packed));
+
 enum usb_device_speed {
        USB_SPEED_UNKNOWN = 0,                  /* enumerating */
        USB_SPEED_LOW, USB_SPEED_FULL,          /* usb 1.1 */
@@ -105,11 +138,42 @@ struct testdev {
 };
 static struct testdev          *testdevs;
 
-static int is_testdev (struct usb_device_descriptor *dev)
+static int testdev_ffs_ifnum(FILE *fd)
 {
+       union {
+               char buf[255];
+               struct usb_interface_descriptor intf;
+       } u;
+
+       for (;;) {
+               if (fread(u.buf, 1, 1, fd) != 1)
+                       return -1;
+               if (fread(u.buf + 1, (unsigned char)u.buf[0] - 1, 1, fd) != 1)
+                       return -1;
+
+               if (u.intf.bLength == sizeof u.intf
+                && u.intf.bDescriptorType == USB_DT_INTERFACE
+                && u.intf.bNumEndpoints == 2
+                && u.intf.bInterfaceClass == USB_CLASS_VENDOR_SPEC
+                && u.intf.bInterfaceSubClass == 0
+                && u.intf.bInterfaceProtocol == 0)
+                       return (unsigned char)u.intf.bInterfaceNumber;
+       }
+}
+
+static int testdev_ifnum(FILE *fd)
+{
+       struct usb_device_descriptor dev;
+
+       if (fread(&dev, sizeof dev, 1, fd) != 1)
+               return -1;
+
+       if (dev.bLength != sizeof dev || dev.bDescriptorType != USB_DT_DEVICE)
+               return -1;
+
        /* FX2 with (tweaked) bulksrc firmware */
-       if (dev->idVendor == 0x0547 && dev->idProduct == 0x1002)
-               return 1;
+       if (dev.idVendor == 0x0547 && dev.idProduct == 0x1002)
+               return 0;
 
        /*----------------------------------------------------*/
 
@@ -124,95 +188,108 @@ static int is_testdev (struct usb_device_descriptor *dev)
         */
 
        /* generic EZ-USB FX controller */
-       if (dev->idVendor == 0x0547 && dev->idProduct == 0x2235)
-               return 1;
+       if (dev.idVendor == 0x0547 && dev.idProduct == 0x2235)
+               return 0;
 
        /* generic EZ-USB FX2 controller */
-       if (dev->idVendor == 0x04b4 && dev->idProduct == 0x8613)
-               return 1;
+       if (dev.idVendor == 0x04b4 && dev.idProduct == 0x8613)
+               return 0;
 
        /* CY3671 development board with EZ-USB FX */
-       if (dev->idVendor == 0x0547 && dev->idProduct == 0x0080)
-               return 1;
+       if (dev.idVendor == 0x0547 && dev.idProduct == 0x0080)
+               return 0;
 
        /* Keyspan 19Qi uses an21xx (original EZ-USB) */
-       if (dev->idVendor == 0x06cd && dev->idProduct == 0x010b)
-               return 1;
+       if (dev.idVendor == 0x06cd && dev.idProduct == 0x010b)
+               return 0;
 
        /*----------------------------------------------------*/
 
        /* "gadget zero", Linux-USB test software */
-       if (dev->idVendor == 0x0525 && dev->idProduct == 0xa4a0)
-               return 1;
+       if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a0)
+               return 0;
 
        /* user mode subset of that */
-       if (dev->idVendor == 0x0525 && dev->idProduct == 0xa4a4)
-               return 1;
+       if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a4)
+               return testdev_ffs_ifnum(fd);
+               /* return 0; */
 
        /* iso version of usermode code */
-       if (dev->idVendor == 0x0525 && dev->idProduct == 0xa4a3)
-               return 1;
+       if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a3)
+               return 0;
 
        /* some GPL'd test firmware uses these IDs */
 
-       if (dev->idVendor == 0xfff0 && dev->idProduct == 0xfff0)
-               return 1;
+       if (dev.idVendor == 0xfff0 && dev.idProduct == 0xfff0)
+               return 0;
 
        /*----------------------------------------------------*/
 
        /* iBOT2 high speed webcam */
-       if (dev->idVendor == 0x0b62 && dev->idProduct == 0x0059)
-               return 1;
+       if (dev.idVendor == 0x0b62 && dev.idProduct == 0x0059)
+               return 0;
 
-       return 0;
+       /*----------------------------------------------------*/
+
+       /* the FunctionFS gadget can have the source/sink interface
+        * anywhere.  We look for an interface descriptor that match
+        * what we expect.  We ignore configuratiens thou. */
+
+       if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4ac
+        && (dev.bDeviceClass == USB_CLASS_PER_INTERFACE
+         || dev.bDeviceClass == USB_CLASS_VENDOR_SPEC))
+               return testdev_ffs_ifnum(fd);
+
+       return -1;
 }
 
-static int find_testdev (const char *name, const struct stat *sb, int flag)
+static int find_testdev(const char *name, const struct stat *sb, int flag)
 {
-       int                             fd;
-       struct usb_device_descriptor    dev;
+       FILE                            *fd;
+       int                             ifnum;
+       struct testdev                  *entry;
+
+       (void)sb; /* unused */
 
        if (flag != FTW_F)
                return 0;
        /* ignore /proc/bus/usb/{devices,drivers} */
-       if (strrchr (name, '/')[1] == 'd')
+       if (strrchr(name, '/')[1] == 'd')
                return 0;
 
-       if ((fd = open (name, O_RDONLY)) < 0) {
-               perror ("can't open dev file r/o");
+       fd = fopen(name, "rb");
+       if (!fd) {
+               perror(name);
                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 = calloc (1, sizeof *entry)) == 0) {
-                       fputs ("no mem!\n", stderr);
-                       goto done;
-               }
-               entry->name = strdup (name);
-               if (!entry->name) {
-                       free (entry);
-                       goto done;
-               }
 
-               // FIXME better to look at each interface and ask if it's
-               // bound to 'usbtest', rather than assume interface 0
-               entry->ifnum = 0;
-
-               // FIXME ask usbfs what speed; update USBDEVFS_CONNECTINFO
-               // so it tells about high speed etc
+       ifnum = testdev_ifnum(fd);
+       fclose(fd);
+       if (ifnum < 0)
+               return 0;
 
-               fprintf (stderr, "%s speed\t%s\n",
-                               speed (entry->speed), entry->name);
+       entry = calloc(1, sizeof *entry);
+       if (!entry)
+               goto nomem;
 
-               entry->next = testdevs;
-               testdevs = entry;
+       entry->name = strdup(name);
+       if (!entry->name) {
+               free(entry);
+nomem:
+               perror("malloc");
+               return 0;
        }
 
-done:
-       close (fd);
+       entry->ifnum = ifnum;
+
+       /* FIXME ask usbfs what speed; update USBDEVFS_CONNECTINFO so
+        * it tells about high speed etc */
+
+       fprintf(stderr, "%s speed\t%s\t%u\n",
+               speed(entry->speed), entry->name, entry->ifnum);
+
+       entry->next = testdevs;
+       testdevs = entry;
        return 0;
 }
 
@@ -277,11 +354,51 @@ restart:
        return arg;
 }
 
+static const char *usbfs_dir_find(void)
+{
+       static char usbfs_path_0[] = "/dev/usb/devices";
+       static char usbfs_path_1[] = "/proc/bus/usb/devices";
+
+       static char *const usbfs_paths[] = {
+               usbfs_path_0, usbfs_path_1
+       };
+
+       static char *const *
+               end = usbfs_paths + sizeof usbfs_paths / sizeof *usbfs_paths;
+
+       char *const *it = usbfs_paths;
+       do {
+               int fd = open(*it, O_RDONLY);
+               close(fd);
+               if (fd >= 0) {
+                       strrchr(*it, '/')[0] = '\0';
+                       return *it;
+               }
+       } while (++it != end);
+
+       return NULL;
+}
+
+static int parse_num(unsigned *num, const char *str)
+{
+       unsigned long val;
+       char *end;
+
+       errno = 0;
+       val = strtoul(str, &end, 0);
+       if (errno || *end || val > UINT_MAX)
+               return -1;
+       *num = val;
+       return 0;
+}
+
 int main (int argc, char **argv)
 {
+
        int                     c;
        struct testdev          *entry;
        char                    *device;
+       const char              *usbfs_dir = NULL;
        int                     all = 0, forever = 0, not = 0;
        int                     test = -1 /* all */;
        struct usbtest_param    param;
@@ -303,23 +420,24 @@ int main (int argc, char **argv)
        /* for easy use when hotplugging */
        device = getenv ("DEVICE");
 
-       while ((c = getopt (argc, argv, "D:ac:g:hns:t:v:")) != EOF)
+       while ((c = getopt (argc, argv, "D:aA:c:g:hns:t:v:")) != EOF)
        switch (c) {
        case 'D':       /* device, if only one */
                device = optarg;
                continue;
+       case 'A':       /* use all devices with specified usbfs dir */
+               usbfs_dir = optarg;
+               /* FALL THROUGH */
        case 'a':       /* use all devices */
-               device = 0;
+               device = NULL;
                all = 1;
                continue;
        case 'c':       /* count iterations */
-               param.iterations = atoi (optarg);
-               if (param.iterations < 0)
+               if (parse_num(&param.iterations, optarg))
                        goto usage;
                continue;
        case 'g':       /* scatter/gather entries */
-               param.sglen = atoi (optarg);
-               if (param.sglen < 0)
+               if (parse_num(&param.sglen, optarg))
                        goto usage;
                continue;
        case 'l':       /* loop forever */
@@ -329,8 +447,7 @@ int main (int argc, char **argv)
                not = 1;
                continue;
        case 's':       /* size of packet */
-               param.length = atoi (optarg);
-               if (param.length < 0)
+               if (parse_num(&param.length, optarg))
                        goto usage;
                continue;
        case 't':       /* run just one test */
@@ -339,15 +456,14 @@ int main (int argc, char **argv)
                        goto usage;
                continue;
        case 'v':       /* vary packet size by ... */
-               param.vary = atoi (optarg);
-               if (param.vary < 0)
+               if (parse_num(&param.vary, optarg))
                        goto usage;
                continue;
        case '?':
        case 'h':
        default:
 usage:
-               fprintf (stderr, "usage: %s [-an] [-D dev]\n"
+               fprintf (stderr, "usage: %s [-n] [-D dev | -a | -A usbfs-dir]\n"
                        "\t[-c iterations]  [-t testnum]\n"
                        "\t[-s packetsize] [-g sglen] [-v vary]\n",
                        argv [0]);
@@ -361,13 +477,17 @@ usage:
                goto usage;
        }
 
-       if ((c = open ("/proc/bus/usb/devices", O_RDONLY)) < 0) {
-               fputs ("usbfs files are missing\n", stderr);
-               return -1;
+       /* Find usbfs mount point */
+       if (!usbfs_dir) {
+               usbfs_dir = usbfs_dir_find();
+               if (!usbfs_dir) {
+                       fputs ("usbfs files are missing\n", stderr);
+                       return -1;
+               }
        }
 
        /* collect and list the test devices */
-       if (ftw ("/proc/bus/usb", find_testdev, 3) != 0) {
+       if (ftw (usbfs_dir, find_testdev, 3) != 0) {
                fputs ("ftw failed; is usbfs missing?\n", stderr);
                return -1;
        }