Hi,

I had the pleasure of debugging a USB mass storage device that showed
interesting behaviour when used with our stack and in combination with
the blink(1) usb device.

The blink(1)-tool depends on libusb.  Libusb calls ioctl(USB_DEVICEINFO)
plenty of times to get a map of the USB tree.  Per ioctl, three USB
requests are created (to read: vendor, product, serial).  This kind of
floods all connected USB devices.

I have a hotplugd script that waits for usb mass storage devices, reads
the disklabel, fscks and tries to mount a partition.  Due to the ioctl,
one of those calls, which mostly only read from the storage device,
freeze until they later time out.  The flood of read requests caused
by the ioctl seem to make the mass storage halt its operation.

The same issue can be reproduced by calling usbdevs(8) in a loop.  It
uses the same ioctl and "breaks" this usb mass storage.

An idea is to cache those three strings, so that the ioctl does not
create any requests.  NetBSD seems to have implemented a caching
mechanism to fix a similar issue.  Though they have not enabled the
caching for that specific ioctl.

The idea of this diff, adapted from NetBSD, is that we read out the
strings early on when the device is found, store a copy of those per
device.  Then whenever it's needed it can be used.

ok? Comments?

Patrick

diff --git sys/dev/usb/usb.c sys/dev/usb/usb.c
index 732ecfd..c198bf0 100644
--- sys/dev/usb/usb.c
+++ sys/dev/usb/usb.c
@@ -520,7 +520,7 @@ usb_fill_di_task(void *arg)
        if (dev == NULL)
                return;
 
-       usbd_fill_deviceinfo(dev, di, 1);
+       usbd_fill_deviceinfo(dev, di, 0);
 }
 
 void
diff --git sys/dev/usb/usb_subr.c sys/dev/usb/usb_subr.c
index a255793..fef6d52 100644
--- sys/dev/usb/usb_subr.c
+++ sys/dev/usb/usb_subr.c
@@ -63,6 +63,7 @@ usbd_status   usbd_set_config(struct usbd_device *, int);
 void           usbd_devinfo(struct usbd_device *, int, char *, size_t);
 void           usbd_devinfo_vp(struct usbd_device *, char *, size_t,
                    char *, size_t, int);
+void           usbd_get_device_string(struct usbd_device *, uByte, char **);
 char           *usbd_get_string(struct usbd_device *, int, char *, size_t);
 int            usbd_getnewaddr(struct usbd_bus *);
 int            usbd_print(void *, const char *);
@@ -212,6 +213,28 @@ usbd_trim_spaces(char *p)
 }
 
 void
+usbd_get_device_string(struct usbd_device *dev, uByte index, char **buf)
+{
+       char *b = malloc(USB_MAX_STRING_LEN, M_USB, M_NOWAIT);
+       if (b != NULL) {
+               usbd_get_string(dev, index, b, USB_MAX_STRING_LEN);
+               usbd_trim_spaces(b);
+       }
+       *buf = b;
+}
+
+void
+usbd_get_device_strings(struct usbd_device *dev)
+{
+       usbd_get_device_string(dev, dev->ddesc.iManufacturer,
+           &dev->vendor);
+       usbd_get_device_string(dev, dev->ddesc.iProduct,
+           &dev->product);
+       usbd_get_device_string(dev, dev->ddesc.iSerialNumber,
+           &dev->serial);
+}
+
+void
 usbd_devinfo_vp(struct usbd_device *dev, char *v, size_t vl,
     char *p, size_t pl, int usedev)
 {
@@ -232,6 +255,11 @@ usbd_devinfo_vp(struct usbd_device *dev, char *v, size_t 
vl,
                usbd_trim_spaces(vendor);
                product = usbd_get_string(dev, udd->iProduct, p, pl);
                usbd_trim_spaces(product);
+       } else {
+               if (dev->vendor != NULL)
+                       vendor = dev->vendor;
+               if (dev->product != NULL)
+                       product = dev->product;
        }
 #ifdef USBVERBOSE
        if (vendor == NULL || product == NULL) {
@@ -294,7 +322,7 @@ usbd_devinfo(struct usbd_device *dev, int showclass, char 
*base, size_t len)
        char *cp = base;
        int bcdDevice, bcdUSB;
 
-       usbd_devinfo_vp(dev, vendor, sizeof vendor, product, sizeof product, 1);
+       usbd_devinfo_vp(dev, vendor, sizeof vendor, product, sizeof product, 0);
        snprintf(cp, len, "\"%s %s\"", vendor, product);
        cp += strlen(cp);
        if (showclass) {
@@ -1188,6 +1216,8 @@ usbd_new_device(struct device *parent, struct usbd_bus 
*bus, int depth,
        DPRINTF(("usbd_new_device: new dev (addr %d), dev=%p, parent=%p\n",
                 addr, dev, parent));
 
+       usbd_get_device_strings(dev);
+
        err = usbd_probe_and_attach(parent, dev, port, addr);
        if (err) {
                usb_free_device(dev);
@@ -1304,8 +1334,13 @@ usbd_fill_deviceinfo(struct usbd_device *dev, struct 
usb_device_info *di,
                di->udi_nports = 0;
 
        bzero(di->udi_serial, sizeof(di->udi_serial));
-       usbd_get_string(dev, dev->ddesc.iSerialNumber, di->udi_serial,
-           sizeof(di->udi_serial));
+       if (!usedev && dev->serial != NULL) {
+               strlcpy(di->udi_serial, dev->serial,
+                   sizeof(di->udi_serial));
+       } else {
+               usbd_get_string(dev, dev->ddesc.iSerialNumber,
+                   di->udi_serial, sizeof(di->udi_serial));
+       }
 }
 
 /* Retrieve a complete descriptor for a certain device and index. */
@@ -1368,6 +1403,13 @@ usb_free_device(struct usbd_device *dev)
                free(dev->subdevs, M_USB, 0);
        dev->bus->devices[dev->address] = NULL;
 
+       if (dev->vendor != NULL)
+               free(dev->vendor, M_USB, USB_MAX_STRING_LEN);
+       if (dev->product != NULL)
+               free(dev->product, M_USB, USB_MAX_STRING_LEN);
+       if (dev->serial != NULL)
+               free(dev->serial, M_USB, USB_MAX_STRING_LEN);
+
        free(dev, M_USB, 0);
 }
 
diff --git sys/dev/usb/usbdivar.h sys/dev/usb/usbdivar.h
index 3a7bac3..912234f 100644
--- sys/dev/usb/usbdivar.h
+++ sys/dev/usb/usbdivar.h
@@ -150,6 +150,10 @@ struct usbd_device {
        struct usbd_hub        *hub;           /* only if this is a hub */
        struct device         **subdevs;       /* sub-devices, 0 terminated */
        int                     ndevs;         /* # of subdevs */
+
+       char                   *serial;        /* serial number, can be NULL */
+       char                   *vendor;        /* vendor string, can be NULL */
+       char                   *product;       /* product string, can be NULL */
 };
 
 struct usbd_interface {
@@ -229,6 +233,7 @@ void usbd_dump_pipe(struct usbd_pipe *);
 
 /* Routines from usb_subr.c */
 int            usbctlprint(void *, const char *);
+void           usbd_get_device_strings(struct usbd_device *);
 void           usb_delay_ms(struct usbd_bus *, u_int);
 usbd_status    usbd_port_disown_to_1_1(struct usbd_device *, int);
 int            usbd_reset_port(struct usbd_device *, int);

Reply via email to