Commit 9b790915450e2e2eb9a8df7fe32f41e895de9da1

Authored by Julius Werner
Committed by Greg Kroah-Hartman
1 parent f23eb0ea9a

usb: ehci: Only sleep for post-resume handover if devices use persist

The current EHCI code sleeps a flat 110ms in the resume path if there
was a USB 1.1 device connected to its companion controller during
suspend, waiting for the device to reappear and reset so that it can be
handed back to the companion. This is necessary if the device uses
persist, so that the companion controller can actually see it during its
own resume path.

However, if the device doesn't use persist, this is entirely
unnecessary. We might just as well ignore it and have the normal device
detection/reset/handoff code handle it asynchronously when it eventually
shows up. As USB 1.1 devices are almost exclusively HIDs these days (for
which persist has no value), this can allow distros to shave another
tenth of a second off their resume time.

In order to enable this optimization, the patch also adds a new
usb_for_each_dev() iterator that is exported by the USB core and wraps
bus_for_each_dev() with the logic to differentiate between struct
usb_device and struct usb_interface on the usb_bus_type bus.

Signed-off-by: Julius Werner <jwerner@chromium.org>
Acked-by: Alan Stern <stern@rowland.harvard.edu>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

Showing 3 changed files with 50 additions and 0 deletions Side-by-side Diff

drivers/usb/core/usb.c
... ... @@ -209,6 +209,39 @@
209 209 }
210 210 EXPORT_SYMBOL_GPL(usb_find_interface);
211 211  
  212 +struct each_dev_arg {
  213 + void *data;
  214 + int (*fn)(struct usb_device *, void *);
  215 +};
  216 +
  217 +static int __each_dev(struct device *dev, void *data)
  218 +{
  219 + struct each_dev_arg *arg = (struct each_dev_arg *)data;
  220 +
  221 + /* There are struct usb_interface on the same bus, filter them out */
  222 + if (!is_usb_device(dev))
  223 + return 0;
  224 +
  225 + return arg->fn(container_of(dev, struct usb_device, dev), arg->data);
  226 +}
  227 +
  228 +/**
  229 + * usb_for_each_dev - iterate over all USB devices in the system
  230 + * @data: data pointer that will be handed to the callback function
  231 + * @fn: callback function to be called for each USB device
  232 + *
  233 + * Iterate over all USB devices and call @fn for each, passing it @data. If it
  234 + * returns anything other than 0, we break the iteration prematurely and return
  235 + * that value.
  236 + */
  237 +int usb_for_each_dev(void *data, int (*fn)(struct usb_device *, void *))
  238 +{
  239 + struct each_dev_arg arg = {data, fn};
  240 +
  241 + return bus_for_each_dev(&usb_bus_type, NULL, &arg, __each_dev);
  242 +}
  243 +EXPORT_SYMBOL_GPL(usb_for_each_dev);
  244 +
212 245 /**
213 246 * usb_release_dev - free a usb device structure when all users of it are finished.
214 247 * @dev: device that's been disconnected
drivers/usb/host/ehci-hub.c
... ... @@ -42,6 +42,12 @@
42 42 u16 wLength
43 43 );
44 44  
  45 +static int persist_enabled_on_companion(struct usb_device *udev, void *unused)
  46 +{
  47 + return !udev->maxchild && udev->persist_enabled &&
  48 + udev->bus->root_hub->speed < USB_SPEED_HIGH;
  49 +}
  50 +
45 51 /* After a power loss, ports that were owned by the companion must be
46 52 * reset so that the companion can still own them.
47 53 */
... ... @@ -54,6 +60,16 @@
54 60 struct usb_hcd *hcd = ehci_to_hcd(ehci);
55 61  
56 62 if (!ehci->owned_ports)
  63 + return;
  64 +
  65 + /*
  66 + * USB 1.1 devices are mostly HIDs, which don't need to persist across
  67 + * suspends. If we ensure that none of our companion's devices have
  68 + * persist_enabled (by looking through all USB 1.1 buses in the system),
  69 + * we can skip this and avoid slowing resume down. Devices without
  70 + * persist will just get reenumerated shortly after resume anyway.
  71 + */
  72 + if (!usb_for_each_dev(NULL, persist_enabled_on_companion))
57 73 return;
58 74  
59 75 /* Make sure the ports are powered */
... ... @@ -717,6 +717,7 @@
717 717 extern int usb_match_one_id(struct usb_interface *interface,
718 718 const struct usb_device_id *id);
719 719  
  720 +extern int usb_for_each_dev(void *data, int (*fn)(struct usb_device *, void *));
720 721 extern struct usb_interface *usb_find_interface(struct usb_driver *drv,
721 722 int minor);
722 723 extern struct usb_interface *usb_ifnum_to_if(const struct usb_device *dev,