diff options
-rw-r--r-- | drivers/usb/core/hub.c | 51 | ||||
-rw-r--r-- | drivers/usb/host/xhci-ring.c | 40 | ||||
-rw-r--r-- | drivers/usb/host/xhci.h | 1 | ||||
-rw-r--r-- | include/linux/usb/hcd.h | 2 |
4 files changed, 74 insertions, 20 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index ba9509454ed5..1c32bbac9862 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, diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index ffe549338cec..3a033240ec64 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -1241,18 +1241,20 @@ static void handle_device_notification(struct xhci_hcd *xhci, union xhci_trb *event) { u32 slot_id; + struct usb_device *udev; slot_id = TRB_TO_SLOT_ID(event->generic.field[3]); - if (!xhci->devs[slot_id]) + if (!xhci->devs[slot_id]) { xhci_warn(xhci, "Device Notification event for " "unused slot %u\n", slot_id); - else - xhci_dbg(xhci, "Device Notification event for slot ID %u\n", - slot_id); - /* XXX should we kick khubd for the parent hub? It should have send an - * interrupt transfer when the port started signaling resume, so there's - * probably no need to do so. - */ + return; + } + + xhci_dbg(xhci, "Device Wake Notification event for slot ID %u\n", + slot_id); + udev = xhci->devs[slot_id]->udev; + if (udev && udev->parent) + usb_wakeup_notification(udev->parent, udev->portnum); } static void handle_port_status(struct xhci_hcd *xhci, @@ -1340,6 +1342,11 @@ static void handle_port_status(struct xhci_hcd *xhci, if (DEV_SUPERSPEED(temp)) { xhci_dbg(xhci, "remote wake SS port %d\n", port_id); + /* Set a flag to say the port signaled remote wakeup, + * so we can tell the difference between the end of + * device and host initiated resume. + */ + bus_state->port_remote_wakeup |= 1 << faked_port_index; xhci_test_and_clear_bit(xhci, port_array, faked_port_index, PORT_PLC); xhci_set_link_state(xhci, port_array, faked_port_index, @@ -1362,10 +1369,27 @@ static void handle_port_status(struct xhci_hcd *xhci, if ((temp & PORT_PLC) && (temp & PORT_PLS_MASK) == XDEV_U0 && DEV_SUPERSPEED(temp)) { xhci_dbg(xhci, "resume SS port %d finished\n", port_id); + /* We've just brought the device into U0 through either the + * Resume state after a device remote wakeup, or through the + * U3Exit state after a host-initiated resume. If it's a device + * initiated remote wake, don't pass up the link state change, + * so the roothub behavior is consistent with external + * USB 3.0 hub behavior. + */ slot_id = xhci_find_slot_id_by_port(hcd, xhci, faked_port_index + 1); if (slot_id && xhci->devs[slot_id]) xhci_ring_device(xhci, slot_id); + if (bus_state->port_remote_wakeup && (1 << faked_port_index)) { + bus_state->port_remote_wakeup &= + ~(1 << faked_port_index); + xhci_test_and_clear_bit(xhci, port_array, + faked_port_index, PORT_PLC); + usb_wakeup_notification(hcd->self.root_hub, + faked_port_index + 1); + bogus_port_status = true; + goto cleanup; + } } if (hcd->speed != HCD_USB3) diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index fb99c8379142..0f4936956103 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1344,6 +1344,7 @@ struct xhci_bus_state { /* ports suspend status arrays - max 31 ports for USB2, 15 for USB3 */ u32 port_c_suspend; u32 suspended_ports; + u32 port_remote_wakeup; unsigned long resume_done[USB_MAXCHILDREN]; }; diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h index b2f62f3a32af..2e6071efbfb7 100644 --- a/include/linux/usb/hcd.h +++ b/include/linux/usb/hcd.h @@ -412,6 +412,8 @@ extern irqreturn_t usb_hcd_irq(int irq, void *__hcd); extern void usb_hc_died(struct usb_hcd *hcd); extern void usb_hcd_poll_rh_status(struct usb_hcd *hcd); +extern void usb_wakeup_notification(struct usb_device *hdev, + unsigned int portnum); /* The D0/D1 toggle bits ... USE WITH CAUTION (they're almost hcd-internal) */ #define usb_gettoggle(dev, ep, out) (((dev)->toggle[out] >> (ep)) & 1) |