On 18/05/16(Wed) 18:28, Patrick Wildt wrote:
> 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.
They did this caching because their stack call the function multiple
times during the attachment of a device.
> 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?
ok mpi@
I'd also like to see if we can always use the cache. Because it is
unlikely that a vendor or product name will change.
> 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);
>