From 4ee823b83bc9851743fab756c76b27d6a1e2472b Mon Sep 17 00:00:00 2001 From: Sarah Sharp Date: Mon, 14 Nov 2011 18:00:01 -0800 Subject: USB/xHCI: Support device-initiated USB 3.0 resume. USB 3.0 hubs don't have a port suspend change bit (that bit is now reserved). Instead, when a host-initiated resume finishes, the hub sets the port link state change bit. When a USB 3.0 device initiates remote wakeup, the parent hubs with their upstream links in U3 will pass the LFPS up the chain. The first hub that has an upstream link in U0 (which may be the roothub) will reflect that LFPS back down the path to the device. However, the parent hubs in the resumed path will not set their link state change bit. Instead, the device that initiated the resume has to send an asynchronous "Function Wake" Device Notification up to the host controller. Therefore, we need a way to notify the USB core of a device resume without going through the normal hub URB completion method. First, make the xHCI roothub act like an external USB 3.0 hub and not pass up the port link state change bit when a device-initiated resume finishes. Introduce a new xHCI bit field, port_remote_wakeup, so that we can tell the difference between a port coming out of the U3Exit state (host-initiated resume) and the RExit state (ending state of device-initiated resume). Since the USB core can't tell whether a port on a hub has resumed by looking at the Hub Status buffer, we need to introduce a bitfield, wakeup_bits, that indicates which ports have resumed. When the xHCI driver notices a port finishing a device-initiated resume, we call into a new USB core function, usb_wakeup_notification(), that will set the right bit in wakeup_bits, and kick khubd for that hub. We also call usb_wakeup_notification() when the Function Wake Device Notification is received by the xHCI driver. This covers the case where the link between the roothub and the first-tier hub is in U0, and the hub reflects the resume signaling back to the device without giving any indication it has done so until the device sends the Function Wake notification. Change the code in khubd that handles the remote wakeup to look at the state the USB core thinks the device is in, and handle the remote wakeup if the port's wakeup bit is set. This patch only takes care of the case where the device is attached directly to the roothub, or the USB 3.0 hub that is attached to the root hub is the device sending the Function Wake Device Notification (e.g. because a new USB device was attached). The other cases will be covered in a second patch. Signed-off-by: Sarah Sharp --- drivers/usb/core/hub.c | 51 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 12 deletions(-) (limited to 'drivers/usb/core/hub.c') diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index ba9509454ed..1c32bbac986 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -62,6 +62,8 @@ struct usb_hub { resumed */ unsigned long removed_bits[1]; /* ports with a "removed" device present */ + unsigned long wakeup_bits[1]; /* ports that have signaled + remote wakeup */ #if USB_MAXCHILDREN > 31 /* 8*sizeof(unsigned long) - 1 */ #error event_bits[] is too short! #endif @@ -411,6 +413,29 @@ void usb_kick_khubd(struct usb_device *hdev) kick_khubd(hub); } +/* + * Let the USB core know that a USB 3.0 device has sent a Function Wake Device + * Notification, which indicates it had initiated remote wakeup. + * + * USB 3.0 hubs do not report the port link state change from U3 to U0 when the + * device initiates resume, so the USB core will not receive notice of the + * resume through the normal hub interrupt URB. + */ +void usb_wakeup_notification(struct usb_device *hdev, + unsigned int portnum) +{ + struct usb_hub *hub; + + if (!hdev) + return; + + hub = hdev_to_hub(hdev); + if (hub) { + set_bit(portnum, hub->wakeup_bits); + kick_khubd(hub); + } +} +EXPORT_SYMBOL_GPL(usb_wakeup_notification); /* completion function, fires on port status changes and various faults */ static void hub_irq(struct urb *urb) @@ -807,12 +832,6 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) clear_port_feature(hub->hdev, port1, USB_PORT_FEAT_C_ENABLE); } - if (portchange & USB_PORT_STAT_C_LINK_STATE) { - need_debounce_delay = true; - clear_port_feature(hub->hdev, port1, - USB_PORT_FEAT_C_PORT_LINK_STATE); - } - if ((portchange & USB_PORT_STAT_C_BH_RESET) && hub_is_superspeed(hub->hdev)) { need_debounce_delay = true; @@ -3498,11 +3517,18 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port, int ret; hdev = hub->hdev; - if (!(portchange & USB_PORT_STAT_C_SUSPEND)) - return 0; - clear_port_feature(hdev, port, USB_PORT_FEAT_C_SUSPEND); - udev = hdev->children[port-1]; + if (!hub_is_superspeed(hdev)) { + if (!(portchange & USB_PORT_STAT_C_SUSPEND)) + return 0; + 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)) + return 0; + } + if (udev) { /* TRSMRCY = 10 msec */ msleep(10); @@ -3533,7 +3559,7 @@ static void hub_events(void) u16 portstatus; u16 portchange; int i, ret; - int connect_change; + int connect_change, wakeup_change; /* * We restart the list every time to avoid a deadlock with @@ -3612,8 +3638,9 @@ 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); if (!test_and_clear_bit(i, hub->event_bits) && - !connect_change) + !connect_change && !wakeup_change) continue; ret = hub_port_status(hub, i, -- cgit v1.2.3