From 72937e1e342f5631d08df4ef0629e55bdcf74c76 Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Tue, 24 Jan 2012 11:46:50 -0800 Subject: USB: Set wakeup bits for all children hubs. 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. Change hub_activate() to look at the port link state for each USB 3.0 hub port, and set hub->change_bits if the link state is U0, indicating the device has finished resume. Change the resume function called by hub_events(), hub_handle_remote_wakeup(), to check the link status for resume instead of just the port's wakeup_bits. Signed-off-by: Sarah Sharp --- drivers/usb/core/hub.c | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 1c32bbac986..994aa8853ba 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -853,12 +853,19 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) set_bit(port1, hub->change_bits); } else if (portstatus & USB_PORT_STAT_ENABLE) { + bool port_resumed = (portstatus & + USB_PORT_STAT_LINK_STATE) == + USB_SS_PORT_LS_U0; /* The power session apparently survived the resume. * If there was an overcurrent or suspend change * (i.e., remote wakeup request), have khubd - * take care of it. + * take care of it. Look at the port link state + * for USB 3.0 hubs, since they don't have a suspend + * change bit, and they don't set the port link change + * bit on device-initiated resume. */ - if (portchange) + if (portchange || (hub_is_superspeed(hub->hdev) && + port_resumed)) set_bit(port1, hub->change_bits); } else if (udev->persist_enabled) { @@ -3509,7 +3516,7 @@ 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; @@ -3524,8 +3531,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; } @@ -3638,7 +3645,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; @@ -3681,7 +3688,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) { -- cgit v1.2.3