Merge tag 'for-usb-next-2012-02-14' of git://git.kernel.org/pub/scm/linux/kernel...
authorGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 14 Feb 2012 22:43:27 +0000 (14:43 -0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Tue, 14 Feb 2012 22:43:27 +0000 (14:43 -0800)
Support for USB 3.0 hub suspend.

This patchset adds support for suspending external USB 3.0 hubs, and fixes the
USB 3.0 device remote wakeup enabling.  Hubs are the only USB 3.0 devices on
the market right now that do remote wakeup, and they will only send a remote
wakeup if they are placed into suspend, so it's not necessary to backport this
patchset to stable kernels.

drivers/usb/core/hub.c
drivers/usb/host/xhci-hub.c
drivers/usb/host/xhci-mem.c
drivers/usb/host/xhci-ring.c
drivers/usb/host/xhci.h
include/linux/usb/ch11.h
include/linux/usb/hcd.h

index 2d773cbe191cde5c1d3c3f5d68a9f28ed27a1e3a..d4f062472796d8c2a6b07c023077060b29605518 100644 (file)
@@ -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;
@@ -834,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) {
@@ -1289,14 +1315,8 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
        desc = intf->cur_altsetting;
        hdev = interface_to_usbdev(intf);
 
-       /* Hubs have proper suspend/resume support.  USB 3.0 device suspend is
-        * different from USB 2.0/1.1 device suspend, and unfortunately we
-        * don't support it yet.  So leave autosuspend disabled for USB 3.0
-        * external hubs for now.  Enable autosuspend for USB 3.0 roothubs,
-        * since that isn't a "real" hub.
-        */
-       if (!hub_is_superspeed(hdev) || !hdev->parent)
-               usb_enable_autosuspend(hdev);
+       /* Hubs have proper suspend/resume support. */
+       usb_enable_autosuspend(hdev);
 
        if (hdev->level == MAX_TOPO_LEVEL) {
                dev_err(&intf->dev,
@@ -2421,11 +2441,27 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
         * we don't explicitly enable it here.
         */
        if (udev->do_remote_wakeup) {
-               status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
-                               USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
-                               USB_DEVICE_REMOTE_WAKEUP, 0,
-                               NULL, 0,
-                               USB_CTRL_SET_TIMEOUT);
+               if (!hub_is_superspeed(hub->hdev)) {
+                       status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                                       USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
+                                       USB_DEVICE_REMOTE_WAKEUP, 0,
+                                       NULL, 0,
+                                       USB_CTRL_SET_TIMEOUT);
+               } else {
+                       /* Assume there's only one function on the USB 3.0
+                        * device and enable remote wake for the first
+                        * interface. FIXME if the interface association
+                        * descriptor shows there's more than one function.
+                        */
+                       status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+                                       USB_REQ_SET_FEATURE,
+                                       USB_RECIP_INTERFACE,
+                                       USB_INTRF_FUNC_SUSPEND,
+                                       USB_INTRF_FUNC_SUSPEND_RW |
+                                       USB_INTRF_FUNC_SUSPEND_LP,
+                                       NULL, 0,
+                                       USB_CTRL_SET_TIMEOUT);
+               }
                if (status) {
                        dev_dbg(&udev->dev, "won't remote wakeup, status %d\n",
                                        status);
@@ -2715,6 +2751,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
        struct usb_hub          *hub = usb_get_intfdata (intf);
        struct usb_device       *hdev = hub->hdev;
        unsigned                port1;
+       int                     status;
 
        /* Warn if children aren't already suspended */
        for (port1 = 1; port1 <= hdev->maxchild; port1++) {
@@ -2727,6 +2764,17 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
                                return -EBUSY;
                }
        }
+       if (hub_is_superspeed(hdev) && hdev->do_remote_wakeup) {
+               /* Enable hub to send remote wakeup for all ports. */
+               for (port1 = 1; port1 <= hdev->maxchild; port1++) {
+                       status = set_port_feature(hdev,
+                                       port1 |
+                                       USB_PORT_FEAT_REMOTE_WAKE_CONNECT |
+                                       USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT |
+                                       USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT,
+                                       USB_PORT_FEAT_REMOTE_WAKE_MASK);
+               }
+       }
 
        dev_dbg(&intf->dev, "%s\n", __func__);
 
@@ -3460,6 +3508,46 @@ done:
                hcd->driver->relinquish_port(hcd, port1);
 }
 
+/* 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 portstatus, u16 portchange)
+{
+       struct usb_device *hdev;
+       struct usb_device *udev;
+       int connect_change = 0;
+       int ret;
+
+       hdev = hub->hdev;
+       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 ||
+                                (portstatus & USB_PORT_STAT_LINK_STATE) !=
+                                USB_SS_PORT_LS_U0)
+                       return 0;
+       }
+
+       if (udev) {
+               /* TRSMRCY = 10 msec */
+               msleep(10);
+
+               usb_lock_device(udev);
+               ret = usb_remote_wakeup(udev);
+               usb_unlock_device(udev);
+               if (ret < 0)
+                       connect_change = 1;
+       } else {
+               ret = -ENODEV;
+               hub_port_disable(hub, port, 1);
+       }
+       dev_dbg(hub->intfdev, "resume on port %d, status %d\n",
+                       port, ret);
+       return connect_change;
+}
+
 static void hub_events(void)
 {
        struct list_head *tmp;
@@ -3472,7 +3560,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
@@ -3551,8 +3639,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_and_clear_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,
@@ -3593,31 +3682,10 @@ static void hub_events(void)
                                }
                        }
 
-                       if (portchange & USB_PORT_STAT_C_SUSPEND) {
-                               struct usb_device *udev;
+                       if (hub_handle_remote_wakeup(hub, i,
+                                               portstatus, portchange))
+                               connect_change = 1;
 
-                               clear_port_feature(hdev, i,
-                                       USB_PORT_FEAT_C_SUSPEND);
-                               udev = hdev->children[i-1];
-                               if (udev) {
-                                       /* TRSMRCY = 10 msec */
-                                       msleep(10);
-
-                                       usb_lock_device(udev);
-                                       ret = usb_remote_wakeup(hdev->
-                                                       children[i-1]);
-                                       usb_unlock_device(udev);
-                                       if (ret < 0)
-                                               connect_change = 1;
-                               } else {
-                                       ret = -ENODEV;
-                                       hub_port_disable(hub, i, 1);
-                               }
-                               dev_dbg (hub_dev,
-                                       "resume on port %d, status %d\n",
-                                       i, ret);
-                       }
-                       
                        if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
                                u16 status = 0;
                                u16 unused;
index 557b6f32db86730af004eec3b2ad889bf9457f65..673ad120c43e2f7e363ff385960215ed85153ebc 100644 (file)
@@ -422,6 +422,32 @@ void xhci_set_link_state(struct xhci_hcd *xhci, __le32 __iomem **port_array,
        xhci_writel(xhci, temp, port_array[port_id]);
 }
 
+void xhci_set_remote_wake_mask(struct xhci_hcd *xhci,
+               __le32 __iomem **port_array, int port_id, u16 wake_mask)
+{
+       u32 temp;
+
+       temp = xhci_readl(xhci, port_array[port_id]);
+       temp = xhci_port_state_to_neutral(temp);
+
+       if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_CONNECT)
+               temp |= PORT_WKCONN_E;
+       else
+               temp &= ~PORT_WKCONN_E;
+
+       if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT)
+               temp |= PORT_WKDISC_E;
+       else
+               temp &= ~PORT_WKDISC_E;
+
+       if (wake_mask & USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT)
+               temp |= PORT_WKOC_E;
+       else
+               temp &= ~PORT_WKOC_E;
+
+       xhci_writel(xhci, temp, port_array[port_id]);
+}
+
 /* Test and clear port RWC bit */
 void xhci_test_and_clear_bit(struct xhci_hcd *xhci, __le32 __iomem **port_array,
                                int port_id, u32 port_bit)
@@ -448,6 +474,7 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
        int slot_id;
        struct xhci_bus_state *bus_state;
        u16 link_state = 0;
+       u16 wake_mask = 0;
 
        max_ports = xhci_get_ports(hcd, &port_array);
        bus_state = &xhci->bus_state[hcd_index(hcd)];
@@ -593,6 +620,8 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
        case SetPortFeature:
                if (wValue == USB_PORT_FEAT_LINK_STATE)
                        link_state = (wIndex & 0xff00) >> 3;
+               if (wValue == USB_PORT_FEAT_REMOTE_WAKE_MASK)
+                       wake_mask = wIndex & 0xff00;
                wIndex &= 0xff;
                if (!wIndex || wIndex > max_ports)
                        goto error;
@@ -703,6 +732,14 @@ int xhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
                        temp = xhci_readl(xhci, port_array[wIndex]);
                        xhci_dbg(xhci, "set port reset, actual port %d status  = 0x%x\n", wIndex, temp);
                        break;
+               case USB_PORT_FEAT_REMOTE_WAKE_MASK:
+                       xhci_set_remote_wake_mask(xhci, port_array,
+                                       wIndex, wake_mask);
+                       temp = xhci_readl(xhci, port_array[wIndex]);
+                       xhci_dbg(xhci, "set port remote wake mask, "
+                                       "actual port %d status  = 0x%x\n",
+                                       wIndex, temp);
+                       break;
                case USB_PORT_FEAT_BH_PORT_RESET:
                        temp |= PORT_WR;
                        xhci_writel(xhci, temp, port_array[wIndex]);
@@ -883,6 +920,10 @@ int xhci_bus_suspend(struct usb_hcd *hcd)
                        t2 |= PORT_LINK_STROBE | XDEV_U3;
                        set_bit(port_index, &bus_state->bus_suspended);
                }
+               /* USB core sets remote wake mask for USB 3.0 hubs,
+                * including the USB 3.0 roothub, but only if CONFIG_USB_SUSPEND
+                * is enabled, so also enable remote wake here.
+                */
                if (hcd->self.root_hub->do_remote_wakeup) {
                        if (t1 & PORT_CONNECT) {
                                t2 |= PORT_WKOC_E | PORT_WKDISC_E;
index 36cbe2226a44e46b3acd6c8269ede7ab57fc03ad..6b70e7fb484c4c4ce33aace27b842f73dc097eab 100644 (file)
@@ -2141,7 +2141,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
        unsigned int    val, val2;
        u64             val_64;
        struct xhci_segment     *seg;
-       u32 page_size;
+       u32 page_size, temp;
        int i;
 
        page_size = xhci_readl(xhci, &xhci->op_regs->page_size);
@@ -2324,6 +2324,15 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
 
        INIT_LIST_HEAD(&xhci->lpm_failed_devs);
 
+       /* Enable USB 3.0 device notifications for function remote wake, which
+        * is necessary for allowing USB 3.0 devices to do remote wakeup from
+        * U3 (device suspend).
+        */
+       temp = xhci_readl(xhci, &xhci->op_regs->dev_notification);
+       temp &= ~DEV_NOTE_MASK;
+       temp |= DEV_NOTE_FWAKE;
+       xhci_writel(xhci, temp, &xhci->op_regs->dev_notification);
+
        return 0;
 
 fail:
index b62037bff688c07c38f0ad06e22ab8cb964bf62f..3a033240ec64625f81ecc422d5e7a6b3afe9ea37 100644 (file)
@@ -1237,6 +1237,26 @@ static unsigned int find_faked_portnum_from_hw_portnum(struct usb_hcd *hcd,
        return num_similar_speed_ports;
 }
 
+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]) {
+               xhci_warn(xhci, "Device Notification event for "
+                               "unused slot %u\n", slot_id);
+               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,
                union xhci_trb *event)
 {
@@ -1321,20 +1341,21 @@ static void handle_port_status(struct xhci_hcd *xhci,
                }
 
                if (DEV_SUPERSPEED(temp)) {
-                       xhci_dbg(xhci, "resume SS port %d\n", port_id);
+                       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,
                                                XDEV_U0);
-                       slot_id = xhci_find_slot_id_by_port(hcd, xhci,
-                                       faked_port_index + 1);
-                       if (!slot_id) {
-                               xhci_dbg(xhci, "slot_id is zero\n");
-                               goto cleanup;
-                       }
-                       xhci_ring_device(xhci, slot_id);
-                       xhci_dbg(xhci, "resume SS port %d finished\n", port_id);
-                       /* Clear PORT_PLC */
-                       xhci_test_and_clear_bit(xhci, port_array,
-                                               faked_port_index, PORT_PLC);
+                       /* Need to wait until the next link state change
+                        * indicates the device is actually in U0.
+                        */
+                       bogus_port_status = true;
+                       goto cleanup;
                } else {
                        xhci_dbg(xhci, "resume HS port %d\n", port_id);
                        bus_state->resume_done[faked_port_index] = jiffies +
@@ -1345,6 +1366,32 @@ 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)
                xhci_test_and_clear_bit(xhci, port_array, faked_port_index,
                                        PORT_PLC);
@@ -2277,6 +2324,9 @@ static int xhci_handle_event(struct xhci_hcd *xhci)
                else
                        update_ptrs = 0;
                break;
+       case TRB_TYPE(TRB_DEV_NOTE):
+               handle_device_notification(xhci, event);
+               break;
        default:
                if ((le32_to_cpu(event->event_cmd.flags) & TRB_TYPE_BITMASK) >=
                    TRB_TYPE(48))
index fb99c8379142cf01e03ea08590c8de60cb804beb..0f493695610374ad0c84bdb480d6adaa979350c4 100644 (file)
@@ -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];
 };
 
index 0b83acd3360a1e52042850fff29ca0b4d1827c5f..f1d26b6067f17f205f2d8b174ff5641136a94335 100644 (file)
 #define USB_PORT_FEAT_C_BH_PORT_RESET          29
 #define USB_PORT_FEAT_FORCE_LINKPM_ACCEPT      30
 
+/* USB 3.0 hub remote wake mask bits, see table 10-14 */
+#define USB_PORT_FEAT_REMOTE_WAKE_CONNECT      (1 << 8)
+#define USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT   (1 << 9)
+#define USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT (1 << 10)
+
 /*
  * Hub Status and Hub Change results
  * See USB 2.0 spec Table 11-19 and Table 11-20
index b2f62f3a32af990fb931d68983958a4669ffa938..2e6071efbfb7657540fe3734a062a27d52f9919b 100644 (file)
@@ -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)