usb: Support USB 3.1 extended port status request
authorMathias Nyman <mathias.nyman@linux.intel.com>
Thu, 10 Dec 2015 07:59:29 +0000 (09:59 +0200)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 25 Jan 2016 04:16:52 +0000 (20:16 -0800)
usb 3.1 extend the hub get-port-status request by adding different
request types. the new request types return 4 additional bytes called
extended port status, these bytes are returned after the regular
portstatus and portchange values.

The extended port status contains a speed ID for the currently used
sublink speed. A table of supported Speed IDs with details about the link
is provided by the hub in the device descriptor BOS SuperSpeedPlus
device capability Sublink Speed Attributes.

Support this new request. Ask for the extended port status after port
reset if hub supports USB 3.1. If link is running at SuperSpeedPlus
set the device speed to USB_SPEED_SUPER_PLUS

Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/core/hcd.c
drivers/usb/core/hub.c
drivers/usb/core/hub.h
include/uapi/linux/usb/ch11.h

index bca13a0e03260592a925f2658811eebe1c1c9c34..9af9506352f3da0281bf787a151280ab50f0e6e9 100644 (file)
@@ -668,9 +668,15 @@ nongeneric:
                /* non-generic request */
                switch (typeReq) {
                case GetHubStatus:
-               case GetPortStatus:
                        len = 4;
                        break;
+               case GetPortStatus:
+                       if (wValue == HUB_PORT_STATUS)
+                               len = 4;
+                       else
+                               /* other port status types return 8 bytes */
+                               len = 8;
+                       break;
                case GetHubDescriptor:
                        len = sizeof (struct usb_hub_descriptor);
                        break;
index 6bdff0ad3930469f0817f948bc8dd620e76ecc1c..3c9b22eb78cacd1df0250d9b1c3347f7b4f367ef 100644 (file)
@@ -537,29 +537,34 @@ static int get_hub_status(struct usb_device *hdev,
 
 /*
  * USB 2.0 spec Section 11.24.2.7
+ * USB 3.1 takes into use the wValue and wLength fields, spec Section 10.16.2.6
  */
 static int get_port_status(struct usb_device *hdev, int port1,
-               struct usb_port_status *data)
+                          void *data, u16 value, u16 length)
 {
        int i, status = -ETIMEDOUT;
 
        for (i = 0; i < USB_STS_RETRIES &&
                        (status == -ETIMEDOUT || status == -EPIPE); i++) {
                status = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
-                       USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port1,
-                       data, sizeof(*data), USB_STS_TIMEOUT);
+                       USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, value,
+                       port1, data, length, USB_STS_TIMEOUT);
        }
        return status;
 }
 
-static int hub_port_status(struct usb_hub *hub, int port1,
-               u16 *status, u16 *change)
+static int hub_ext_port_status(struct usb_hub *hub, int port1, int type,
+                              u16 *status, u16 *change, u32 *ext_status)
 {
        int ret;
+       int len = 4;
+
+       if (type != HUB_PORT_STATUS)
+               len = 8;
 
        mutex_lock(&hub->status_mutex);
-       ret = get_port_status(hub->hdev, port1, &hub->status->port);
-       if (ret < 4) {
+       ret = get_port_status(hub->hdev, port1, &hub->status->port, type, len);
+       if (ret < len) {
                if (ret != -ENODEV)
                        dev_err(hub->intfdev,
                                "%s failed (err = %d)\n", __func__, ret);
@@ -568,13 +573,22 @@ static int hub_port_status(struct usb_hub *hub, int port1,
        } else {
                *status = le16_to_cpu(hub->status->port.wPortStatus);
                *change = le16_to_cpu(hub->status->port.wPortChange);
-
+               if (type != HUB_PORT_STATUS && ext_status)
+                       *ext_status = le32_to_cpu(
+                               hub->status->port.dwExtPortStatus);
                ret = 0;
        }
        mutex_unlock(&hub->status_mutex);
        return ret;
 }
 
+static int hub_port_status(struct usb_hub *hub, int port1,
+               u16 *status, u16 *change)
+{
+       return hub_ext_port_status(hub, port1, HUB_PORT_STATUS,
+                                  status, change, NULL);
+}
+
 static void kick_hub_wq(struct usb_hub *hub)
 {
        struct usb_interface *intf;
@@ -2612,6 +2626,32 @@ out_authorized:
        return result;
 }
 
+/*
+ * Return 1 if port speed is SuperSpeedPlus, 0 otherwise
+ * check it from the link protocol field of the current speed ID attribute.
+ * current speed ID is got from ext port status request. Sublink speed attribute
+ * table is returned with the hub BOS SSP device capability descriptor
+ */
+static int port_speed_is_ssp(struct usb_device *hdev, int speed_id)
+{
+       int ssa_count;
+       u32 ss_attr;
+       int i;
+       struct usb_ssp_cap_descriptor *ssp_cap = hdev->bos->ssp_cap;
+
+       if (!ssp_cap)
+               return 0;
+
+       ssa_count = le32_to_cpu(ssp_cap->bmAttributes) &
+               USB_SSP_SUBLINK_SPEED_ATTRIBS;
+
+       for (i = 0; i <= ssa_count; i++) {
+               ss_attr = le32_to_cpu(ssp_cap->bmSublinkSpeedAttr[i]);
+               if (speed_id == (ss_attr & USB_SSP_SUBLINK_SPEED_SSID))
+                       return !!(ss_attr & USB_SSP_SUBLINK_SPEED_LP);
+       }
+       return 0;
+}
 
 /* Returns 1 if @hub is a WUSB root hub, 0 otherwise */
 static unsigned hub_is_wusb(struct usb_hub *hub)
@@ -2676,6 +2716,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
        int delay_time, ret;
        u16 portstatus;
        u16 portchange;
+       u32 ext_portstatus = 0;
 
        for (delay_time = 0;
                        delay_time < HUB_RESET_TIMEOUT;
@@ -2684,7 +2725,14 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
                msleep(delay);
 
                /* read and decode port status */
-               ret = hub_port_status(hub, port1, &portstatus, &portchange);
+               if (hub_is_superspeedplus(hub->hdev))
+                       ret = hub_ext_port_status(hub, port1,
+                                                 HUB_EXT_PORT_STATUS,
+                                                 &portstatus, &portchange,
+                                                 &ext_portstatus);
+               else
+                       ret = hub_port_status(hub, port1, &portstatus,
+                                             &portchange);
                if (ret < 0)
                        return ret;
 
@@ -2727,6 +2775,10 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
 
        if (hub_is_wusb(hub))
                udev->speed = USB_SPEED_WIRELESS;
+       else if (hub_is_superspeedplus(hub->hdev) &&
+                port_speed_is_ssp(hub->hdev, ext_portstatus &
+                                  USB_EXT_PORT_STAT_RX_SPEED_ID))
+               udev->speed = USB_SPEED_SUPER_PLUS;
        else if (hub_is_superspeed(hub->hdev))
                udev->speed = USB_SPEED_SUPER;
        else if (portstatus & USB_PORT_STAT_HIGH_SPEED)
index 45d070dd1d0315cfca7b049e3e1d04e6e701645c..34c1a7e22aae020a2f48bce219a342a89d835650 100644 (file)
@@ -140,6 +140,13 @@ static inline int hub_is_superspeed(struct usb_device *hdev)
        return hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS;
 }
 
+static inline int hub_is_superspeedplus(struct usb_device *hdev)
+{
+       return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS &&
+               le16_to_cpu(hdev->descriptor.bcdUSB) >= 0x0310 &&
+               hdev->bos->ssp_cap);
+}
+
 static inline unsigned hub_power_on_good_delay(struct usb_hub *hub)
 {
        unsigned delay = hub->descriptor->bPwrOn2PwrGood * 2;
index 331499d597fa86cd941d99d5ad8ab9ec2120cb86..361297e96f5826360bb24f80de9089b83a9881ba 100644 (file)
 #define USB_RT_HUB     (USB_TYPE_CLASS | USB_RECIP_DEVICE)
 #define USB_RT_PORT    (USB_TYPE_CLASS | USB_RECIP_OTHER)
 
+/*
+ * Port status type for GetPortStatus requests added in USB 3.1
+ * See USB 3.1 spec Table 10-12
+ */
+#define HUB_PORT_STATUS                0
+#define HUB_PORT_PD_STATUS     1
+#define HUB_EXT_PORT_STATUS    2
+
 /*
  * Hub class requests
  * See USB 2.0 spec Table 11-16
 /*
  * Hub Status and Hub Change results
  * See USB 2.0 spec Table 11-19 and Table 11-20
+ * USB 3.1 extends the port status request and may return 4 additional bytes.
+ * See USB 3.1 spec section 10.16.2.6 Table 10-12 and 10-15
  */
 struct usb_port_status {
        __le16 wPortStatus;
        __le16 wPortChange;
+       __le32 dwExtPortStatus;
 } __attribute__ ((packed));
 
 /*
@@ -172,6 +183,16 @@ struct usb_port_status {
 #define USB_PORT_STAT_C_LINK_STATE     0x0040
 #define USB_PORT_STAT_C_CONFIG_ERROR   0x0080
 
+/*
+ * USB 3.1 dwExtPortStatus field masks
+ * See USB 3.1 spec 10.16.2.6.3 Table 10-15
+ */
+
+#define USB_EXT_PORT_STAT_RX_SPEED_ID  0x0000000f
+#define USB_EXT_PORT_STAT_TX_SPEED_ID  0x000000f0
+#define USB_EXT_PORT_STAT_RX_LANES     0x00000f00
+#define USB_EXT_PORT_STAT_TX_LANES     0x0000f000
+
 /*
  * wHubCharacteristics (masks)
  * See USB 2.0 spec Table 11-13, offset 3