]> Pileus Git - ~andy/linux/blobdiff - drivers/usb/core/hub.c
USB: Set wakeup bits for all children hubs.
[~andy/linux] / drivers / usb / core / hub.c
index a0613d8f9be785bf0a52827acf2522b109bce1b2..994aa8853bac4cb120a709cda59aaff110868344 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) {
@@ -1838,6 +1864,37 @@ fail:
        return err;
 }
 
+static void set_usb_port_removable(struct usb_device *udev)
+{
+       struct usb_device *hdev = udev->parent;
+       struct usb_hub *hub;
+       u8 port = udev->portnum;
+       u16 wHubCharacteristics;
+       bool removable = true;
+
+       if (!hdev)
+               return;
+
+       hub = hdev_to_hub(udev->parent);
+
+       wHubCharacteristics = le16_to_cpu(hub->descriptor->wHubCharacteristics);
+
+       if (!(wHubCharacteristics & HUB_CHAR_COMPOUND))
+               return;
+
+       if (hub_is_superspeed(hdev)) {
+               if (hub->descriptor->u.ss.DeviceRemovable & (1 << port))
+                       removable = false;
+       } else {
+               if (hub->descriptor->u.hs.DeviceRemovable[port / 8] & (1 << (port % 8)))
+                       removable = false;
+       }
+
+       if (removable)
+               udev->removable = USB_DEVICE_REMOVABLE;
+       else
+               udev->removable = USB_DEVICE_FIXED;
+}
 
 /**
  * usb_new_device - perform initial device setup (usbcore-internal)
@@ -1896,6 +1953,15 @@ int usb_new_device(struct usb_device *udev)
        announce_device(udev);
 
        device_enable_async_suspend(&udev->dev);
+
+       /*
+        * check whether the hub marks this port as non-removable. Do it
+        * now so that platform-specific data can override it in
+        * device_add()
+        */
+       if (udev->parent)
+               set_usb_port_removable(udev);
+
        /* Register the device.  The device driver is responsible
         * for configuring the device and invoking the add-device
         * notifier chain (used by usbfs and possibly others).
@@ -2381,11 +2447,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);
@@ -2675,6 +2757,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++) {
@@ -2687,6 +2770,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__);
 
@@ -3420,6 +3514,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;
@@ -3432,7 +3566,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
@@ -3511,8 +3645,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,
@@ -3553,31 +3688,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;