diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r-- | drivers/usb/core/hub.c | 70 |
1 files changed, 61 insertions, 9 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 6bdff0ad3930..3c9b22eb78ca 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -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) |