This patch takes care of the race condition between the Function Wake Device Notification and the auto-suspend timeout for this situation: Roothub | (U3) hub A | (U3) hub B | (U3) device C When device C signals a resume, the xHCI driver will set the wakeup_bits for the roothub port that hub A is attached to. However, since USB 3.0 hubs do not set a link state change bit on device-initiated resume, hub A will not indicate a port event when polled. Without this patch, khubd will notice the wakeup-bits are set for the roothub port, it will resume hub A, and then it will poll the events bits for hub A and notice that nothing has changed. Then it will be suspended after 2 seconds. When we notice that a port has resumed on a USB 3.0 hub, and the device attached to that port is a hub, set the wakeup_bits for all the child hub's ports. That means we'll poll the status for all ports on hub B to find the one that woke up, but that's better than missing a device remote wakeup. Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx> --- drivers/usb/core/hub.c | 25 +++++++++++++++++++------ 1 files changed, 19 insertions(+), 6 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 657d6ad..b0c923d 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -3474,12 +3474,13 @@ done: /* Returns 1 if there was a remote wakeup and a connect status change. */ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, - u16 portchange) + u16 portstatus, u16 portchange) { struct usb_device *hdev; struct usb_device *udev; + struct usb_hub *child_hub; int connect_change = 0; - int ret; + int ret, i; hdev = hub->hdev; udev = hdev->children[port-1]; @@ -3489,8 +3490,8 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND); } else { if (!udev || udev->state != USB_STATE_SUSPENDED || - !test_and_clear_bit(udev->portnum, - hub->wakeup_bits)) + (portstatus & USB_PORT_STAT_LINK_STATE) != + USB_SS_PORT_LS_U0) return 0; } @@ -3499,6 +3500,17 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, msleep(10); usb_lock_device(udev); + /* USB 3.0 hubs don't report suspend changes due to device + * remote wakeups, so mark all the children as woken up. + */ + if (hub_is_superspeed(hdev) && udev->maxchild > 0) { + child_hub = hdev_to_hub(udev); + if (child_hub) { + for (i = 1; i < udev->maxchild; i++) + set_bit(i, child_hub->wakeup_bits); + } + kick_khubd(child_hub); + } ret = usb_remote_wakeup(udev); usb_unlock_device(udev); if (ret < 0) @@ -3603,7 +3615,7 @@ static void hub_events(void) if (test_bit(i, hub->busy_bits)) continue; connect_change = test_bit(i, hub->change_bits); - wakeup_change = test_bit(i, hub->wakeup_bits); + wakeup_change = test_and_clear_bit(i, hub->wakeup_bits); if (!test_and_clear_bit(i, hub->event_bits) && !connect_change && !wakeup_change) continue; @@ -3646,7 +3658,8 @@ static void hub_events(void) } } - if (hub_handle_remote_wakeup(hub, i, portchange)) + if (hub_handle_remote_wakeup(hub, i, + portstatus, portchange)) connect_change = 1; if (portchange & USB_PORT_STAT_C_OVERCURRENT) { -- 1.7.5.4 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html