summaryrefslogtreecommitdiffstats
path: root/drivers/usb/core/hub.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r--drivers/usb/core/hub.c157
1 files changed, 120 insertions, 37 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 25a7422ee657..821126eb8176 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -20,6 +20,7 @@
#include <linux/usb.h>
#include <linux/usbdevice_fs.h>
#include <linux/usb/hcd.h>
+#include <linux/usb/otg.h>
#include <linux/usb/quirks.h>
#include <linux/kthread.h>
#include <linux/mutex.h>
@@ -81,7 +82,7 @@ struct usb_hub {
u8 indicator[USB_MAXCHILDREN];
struct delayed_work leds;
struct delayed_work init_work;
- void **port_owners;
+ struct dev_state **port_owners;
};
static inline int hub_is_superspeed(struct usb_device *hdev)
@@ -1271,7 +1272,8 @@ static int hub_configure(struct usb_hub *hub,
hdev->children = kzalloc(hdev->maxchild *
sizeof(struct usb_device *), GFP_KERNEL);
- hub->port_owners = kzalloc(hdev->maxchild * sizeof(void *), GFP_KERNEL);
+ hub->port_owners = kzalloc(hdev->maxchild * sizeof(struct dev_state *),
+ GFP_KERNEL);
if (!hdev->children || !hub->port_owners) {
ret = -ENOMEM;
goto fail;
@@ -1649,7 +1651,7 @@ hub_ioctl(struct usb_interface *intf, unsigned int code, void *user_data)
* to one of these "claimed" ports, the program will "own" the device.
*/
static int find_port_owner(struct usb_device *hdev, unsigned port1,
- void ***ppowner)
+ struct dev_state ***ppowner)
{
if (hdev->state == USB_STATE_NOTATTACHED)
return -ENODEV;
@@ -1664,10 +1666,11 @@ static int find_port_owner(struct usb_device *hdev, unsigned port1,
}
/* In the following three functions, the caller must hold hdev's lock */
-int usb_hub_claim_port(struct usb_device *hdev, unsigned port1, void *owner)
+int usb_hub_claim_port(struct usb_device *hdev, unsigned port1,
+ struct dev_state *owner)
{
int rc;
- void **powner;
+ struct dev_state **powner;
rc = find_port_owner(hdev, port1, &powner);
if (rc)
@@ -1678,10 +1681,11 @@ int usb_hub_claim_port(struct usb_device *hdev, unsigned port1, void *owner)
return rc;
}
-int usb_hub_release_port(struct usb_device *hdev, unsigned port1, void *owner)
+int usb_hub_release_port(struct usb_device *hdev, unsigned port1,
+ struct dev_state *owner)
{
int rc;
- void **powner;
+ struct dev_state **powner;
rc = find_port_owner(hdev, port1, &powner);
if (rc)
@@ -1692,10 +1696,10 @@ int usb_hub_release_port(struct usb_device *hdev, unsigned port1, void *owner)
return rc;
}
-void usb_hub_release_all_ports(struct usb_device *hdev, void *owner)
+void usb_hub_release_all_ports(struct usb_device *hdev, struct dev_state *owner)
{
int n;
- void **powner;
+ struct dev_state **powner;
n = find_port_owner(hdev, 1, &powner);
if (n == 0) {
@@ -2065,7 +2069,7 @@ static int usb_enumerate_device(struct usb_device *udev)
if (err < 0) {
dev_err(&udev->dev, "can't read configurations, error %d\n",
err);
- goto fail;
+ return err;
}
}
if (udev->wusb == 1 && udev->authorized == 0) {
@@ -2081,8 +2085,12 @@ static int usb_enumerate_device(struct usb_device *udev)
udev->serial = usb_cache_string(udev, udev->descriptor.iSerialNumber);
}
err = usb_enumerate_device_otg(udev);
-fail:
- return err;
+ if (err < 0)
+ return err;
+
+ usb_detect_interface_quirks(udev);
+
+ return 0;
}
static void set_usb_port_removable(struct usb_device *udev)
@@ -2324,12 +2332,16 @@ static unsigned hub_is_wusb(struct usb_hub *hub)
static int hub_port_reset(struct usb_hub *hub, int port1,
struct usb_device *udev, unsigned int delay, bool warm);
-/* Is a USB 3.0 port in the Inactive state? */
-static bool hub_port_inactive(struct usb_hub *hub, u16 portstatus)
+/* Is a USB 3.0 port in the Inactive or Complinance Mode state?
+ * Port worm reset is required to recover
+ */
+static bool hub_port_warm_reset_required(struct usb_hub *hub, u16 portstatus)
{
return hub_is_superspeed(hub->hdev) &&
- (portstatus & USB_PORT_STAT_LINK_STATE) ==
- USB_SS_PORT_LS_SS_INACTIVE;
+ (((portstatus & USB_PORT_STAT_LINK_STATE) ==
+ USB_SS_PORT_LS_SS_INACTIVE) ||
+ ((portstatus & USB_PORT_STAT_LINK_STATE) ==
+ USB_SS_PORT_LS_COMP_MOD)) ;
}
static int hub_port_wait_reset(struct usb_hub *hub, int port1,
@@ -2365,7 +2377,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
*
* See https://bugzilla.kernel.org/show_bug.cgi?id=41752
*/
- if (hub_port_inactive(hub, portstatus)) {
+ if (hub_port_warm_reset_required(hub, portstatus)) {
int ret;
if ((portchange & USB_PORT_STAT_C_CONNECTION))
@@ -2607,6 +2619,50 @@ static int check_port_resume_type(struct usb_device *udev,
return status;
}
+int usb_disable_ltm(struct usb_device *udev)
+{
+ struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+
+ /* Check if the roothub and device supports LTM. */
+ if (!usb_device_supports_ltm(hcd->self.root_hub) ||
+ !usb_device_supports_ltm(udev))
+ return 0;
+
+ /* Clear Feature LTM Enable can only be sent if the device is
+ * configured.
+ */
+ if (!udev->actconfig)
+ return 0;
+
+ return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE,
+ USB_DEVICE_LTM_ENABLE, 0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+}
+EXPORT_SYMBOL_GPL(usb_disable_ltm);
+
+void usb_enable_ltm(struct usb_device *udev)
+{
+ struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+
+ /* Check if the roothub and device supports LTM. */
+ if (!usb_device_supports_ltm(hcd->self.root_hub) ||
+ !usb_device_supports_ltm(udev))
+ return;
+
+ /* Set Feature LTM Enable can only be sent if the device is
+ * configured.
+ */
+ if (!udev->actconfig)
+ return;
+
+ usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
+ USB_DEVICE_LTM_ENABLE, 0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+}
+EXPORT_SYMBOL_GPL(usb_enable_ltm);
+
#ifdef CONFIG_USB_SUSPEND
/*
@@ -2702,6 +2758,11 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
if (udev->usb2_hw_lpm_enabled == 1)
usb_set_usb2_hardware_lpm(udev, 0);
+ if (usb_disable_ltm(udev)) {
+ dev_err(&udev->dev, "%s Failed to disable LTM before suspend\n.",
+ __func__);
+ return -ENOMEM;
+ }
if (usb_unlocked_disable_lpm(udev)) {
dev_err(&udev->dev, "%s Failed to disable LPM before suspend\n.",
__func__);
@@ -2731,7 +2792,8 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
if (udev->usb2_hw_lpm_capable == 1)
usb_set_usb2_hardware_lpm(udev, 1);
- /* Try to enable USB3 LPM again */
+ /* Try to enable USB3 LTM and LPM again */
+ usb_enable_ltm(udev);
usb_unlocked_enable_lpm(udev);
/* System sleep transitions should never fail */
@@ -2932,7 +2994,8 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
if (udev->usb2_hw_lpm_capable == 1)
usb_set_usb2_hardware_lpm(udev, 1);
- /* Try to enable USB3 LPM */
+ /* Try to enable USB3 LTM and LPM */
+ usb_enable_ltm(udev);
usb_unlocked_enable_lpm(udev);
}
@@ -3485,6 +3548,15 @@ EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm);
void usb_unlocked_enable_lpm(struct usb_device *udev) { }
EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm);
+
+int usb_disable_ltm(struct usb_device *udev)
+{
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_disable_ltm);
+
+void usb_enable_ltm(struct usb_device *udev) { }
+EXPORT_SYMBOL_GPL(usb_enable_ltm);
#endif
@@ -4034,6 +4106,13 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
}
}
+ if (hcd->phy && !hdev->parent) {
+ if (portstatus & USB_PORT_STAT_CONNECTION)
+ usb_phy_notify_connect(hcd->phy, port1);
+ else
+ usb_phy_notify_disconnect(hcd->phy, port1);
+ }
+
/* Return now if debouncing failed or nothing is connected or
* the device was "removed".
*/
@@ -4408,9 +4487,7 @@ static void hub_events(void)
/* Warm reset a USB3 protocol port if it's in
* SS.Inactive state.
*/
- if (hub_is_superspeed(hub->hdev) &&
- (portstatus & USB_PORT_STAT_LINK_STATE)
- == USB_SS_PORT_LS_SS_INACTIVE) {
+ if (hub_port_warm_reset_required(hub, portstatus)) {
dev_dbg(hub_dev, "warm reset port %d\n", i);
hub_port_reset(hub, i, NULL,
HUB_BH_RESET_TIME, true);
@@ -4670,6 +4747,23 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
}
parent_hub = hdev_to_hub(parent_hdev);
+ /* Disable LPM and LTM while we reset the device and reinstall the alt
+ * settings. Device-initiated LPM settings, and system exit latency
+ * settings are cleared when the device is reset, so we have to set
+ * them up again.
+ */
+ ret = usb_unlocked_disable_lpm(udev);
+ if (ret) {
+ dev_err(&udev->dev, "%s Failed to disable LPM\n.", __func__);
+ goto re_enumerate;
+ }
+ ret = usb_disable_ltm(udev);
+ if (ret) {
+ dev_err(&udev->dev, "%s Failed to disable LTM\n.",
+ __func__);
+ goto re_enumerate;
+ }
+
set_bit(port1, parent_hub->busy_bits);
for (i = 0; i < SET_CONFIG_TRIES; ++i) {
@@ -4697,22 +4791,11 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
goto done;
mutex_lock(hcd->bandwidth_mutex);
- /* Disable LPM while we reset the device and reinstall the alt settings.
- * Device-initiated LPM settings, and system exit latency settings are
- * cleared when the device is reset, so we have to set them up again.
- */
- ret = usb_disable_lpm(udev);
- if (ret) {
- dev_err(&udev->dev, "%s Failed to disable LPM\n.", __func__);
- mutex_unlock(hcd->bandwidth_mutex);
- goto done;
- }
ret = usb_hcd_alloc_bandwidth(udev, udev->actconfig, NULL, NULL);
if (ret < 0) {
dev_warn(&udev->dev,
"Busted HC? Not enough HCD resources for "
"old configuration.\n");
- usb_enable_lpm(udev);
mutex_unlock(hcd->bandwidth_mutex);
goto re_enumerate;
}
@@ -4724,7 +4807,6 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
dev_err(&udev->dev,
"can't restore configuration #%d (error=%d)\n",
udev->actconfig->desc.bConfigurationValue, ret);
- usb_enable_lpm(udev);
mutex_unlock(hcd->bandwidth_mutex);
goto re_enumerate;
}
@@ -4763,17 +4845,18 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
desc->bInterfaceNumber,
desc->bAlternateSetting,
ret);
- usb_unlocked_enable_lpm(udev);
goto re_enumerate;
}
}
- /* Now that the alt settings are re-installed, enable LPM. */
- usb_unlocked_enable_lpm(udev);
done:
+ /* Now that the alt settings are re-installed, enable LTM and LPM. */
+ usb_unlocked_enable_lpm(udev);
+ usb_enable_ltm(udev);
return 0;
re_enumerate:
+ /* LPM state doesn't matter when we're about to destroy the device. */
hub_port_logical_disconnect(parent_hub, port1);
return -ENODEV;
}