aboutsummaryrefslogtreecommitdiffstats
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.c115
1 files changed, 84 insertions, 31 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 64ea21971be2..090469ebfcff 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -141,19 +141,27 @@ static int usb_device_supports_lpm(struct usb_device *udev)
return 0;
}
- /* All USB 3.0 must support LPM, but we need their max exit latency
- * information from the SuperSpeed Extended Capabilities BOS descriptor.
+ /*
+ * According to the USB 3.0 spec, all USB 3.0 devices must support LPM.
+ * However, there are some that don't, and they set the U1/U2 exit
+ * latencies to zero.
*/
if (!udev->bos->ss_cap) {
- dev_warn(&udev->dev, "No LPM exit latency info found. "
- "Power management will be impacted.\n");
+ dev_info(&udev->dev, "No LPM exit latency info found, disabling LPM.\n");
+ return 0;
+ }
+
+ if (udev->bos->ss_cap->bU1devExitLat == 0 &&
+ udev->bos->ss_cap->bU2DevExitLat == 0) {
+ if (udev->parent)
+ dev_info(&udev->dev, "LPM exit latency is zeroed, disabling LPM.\n");
+ else
+ dev_info(&udev->dev, "We don't know the algorithms for LPM for this host, disabling LPM.\n");
return 0;
}
- if (udev->parent->lpm_capable)
- return 1;
- dev_warn(&udev->dev, "Parent hub missing LPM exit latency info. "
- "Power management will be impacted.\n");
+ if (!udev->parent || udev->parent->lpm_capable)
+ return 1;
return 0;
}
@@ -499,7 +507,8 @@ static void led_work (struct work_struct *work)
changed++;
}
if (changed)
- schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD);
+ queue_delayed_work(system_power_efficient_wq,
+ &hub->leds, LED_CYCLE_PERIOD);
}
/* use a short timeout for hub/port status fetches */
@@ -1040,8 +1049,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
*/
if (type == HUB_INIT) {
delay = hub_power_on(hub, false);
- PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func2);
- schedule_delayed_work(&hub->init_work,
+ INIT_DELAYED_WORK(&hub->init_work, hub_init_func2);
+ queue_delayed_work(system_power_efficient_wq,
+ &hub->init_work,
msecs_to_jiffies(delay));
/* Suppress autosuspend until init is done */
@@ -1194,8 +1204,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
/* Don't do a long sleep inside a workqueue routine */
if (type == HUB_INIT2) {
- PREPARE_DELAYED_WORK(&hub->init_work, hub_init_func3);
- schedule_delayed_work(&hub->init_work,
+ INIT_DELAYED_WORK(&hub->init_work, hub_init_func3);
+ queue_delayed_work(system_power_efficient_wq,
+ &hub->init_work,
msecs_to_jiffies(delay));
return; /* Continues at init3: below */
} else {
@@ -1209,7 +1220,8 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)
if (status < 0)
dev_err(hub->intfdev, "activate --> %d\n", status);
if (hub->has_indicators && blinkenlights)
- schedule_delayed_work(&hub->leds, LED_CYCLE_PERIOD);
+ queue_delayed_work(system_power_efficient_wq,
+ &hub->leds, LED_CYCLE_PERIOD);
/* Scan all ports that need attention */
kick_khubd(hub);
@@ -1788,7 +1800,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,
- struct dev_state ***ppowner)
+ struct usb_dev_state ***ppowner)
{
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
@@ -1806,10 +1818,10 @@ 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,
- struct dev_state *owner)
+ struct usb_dev_state *owner)
{
int rc;
- struct dev_state **powner;
+ struct usb_dev_state **powner;
rc = find_port_owner(hdev, port1, &powner);
if (rc)
@@ -1819,12 +1831,13 @@ int usb_hub_claim_port(struct usb_device *hdev, unsigned port1,
*powner = owner;
return rc;
}
+EXPORT_SYMBOL_GPL(usb_hub_claim_port);
int usb_hub_release_port(struct usb_device *hdev, unsigned port1,
- struct dev_state *owner)
+ struct usb_dev_state *owner)
{
int rc;
- struct dev_state **powner;
+ struct usb_dev_state **powner;
rc = find_port_owner(hdev, port1, &powner);
if (rc)
@@ -1834,8 +1847,9 @@ int usb_hub_release_port(struct usb_device *hdev, unsigned port1,
*powner = NULL;
return rc;
}
+EXPORT_SYMBOL_GPL(usb_hub_release_port);
-void usb_hub_release_all_ports(struct usb_device *hdev, struct dev_state *owner)
+void usb_hub_release_all_ports(struct usb_device *hdev, struct usb_dev_state *owner)
{
struct usb_hub *hub = usb_hub_to_struct_hub(hdev);
int n;
@@ -3093,9 +3107,19 @@ static int finish_port_resume(struct usb_device *udev)
* operation is carried out here, after the port has been
* resumed.
*/
- if (udev->reset_resume)
+ if (udev->reset_resume) {
+ /*
+ * If the device morphs or switches modes when it is reset,
+ * we don't want to perform a reset-resume. We'll fail the
+ * resume, which will cause a logical disconnect, and then
+ * the device will be rediscovered.
+ */
retry_reset_resume:
- status = usb_reset_and_verify_device(udev);
+ if (udev->quirks & USB_QUIRK_RESET)
+ status = -ENODEV;
+ else
+ status = usb_reset_and_verify_device(udev);
+ }
/* 10.5.4.5 says be sure devices in the tree are still there.
* For now let's assume the device didn't go crazy on resume,
@@ -3958,7 +3982,7 @@ static void hub_set_initial_usb2_lpm_policy(struct usb_device *udev)
connect_type = usb_get_hub_port_connect_type(udev->parent,
udev->portnum);
- if ((udev->bos->ext_cap->bmAttributes & USB_BESL_SUPPORT) ||
+ if ((udev->bos->ext_cap->bmAttributes & cpu_to_le32(USB_BESL_SUPPORT)) ||
connect_type == USB_PORT_CONNECT_TYPE_HARD_WIRED) {
udev->usb2_hw_lpm_allowed = 1;
usb_set_usb2_hardware_lpm(udev, 1);
@@ -4107,8 +4131,12 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
did_new_scheme = true;
retval = hub_enable_device(udev);
- if (retval < 0)
+ if (retval < 0) {
+ dev_err(&udev->dev,
+ "hub failed to enable device, error %d\n",
+ retval);
goto fail;
+ }
#define GET_DESCRIPTOR_BUFSIZE 64
buf = kmalloc(GET_DESCRIPTOR_BUFSIZE, GFP_NOIO);
@@ -4311,7 +4339,8 @@ check_highspeed (struct usb_hub *hub, struct usb_device *udev, int port1)
/* hub LEDs are probably harder to miss than syslog */
if (hub->has_indicators) {
hub->indicator[port1-1] = INDICATOR_GREEN_BLINK;
- schedule_delayed_work (&hub->leds, 0);
+ queue_delayed_work(system_power_efficient_wq,
+ &hub->leds, 0);
}
}
kfree(qual);
@@ -4540,7 +4569,9 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,
if (hub->has_indicators) {
hub->indicator[port1-1] =
INDICATOR_AMBER_BLINK;
- schedule_delayed_work (&hub->leds, 0);
+ queue_delayed_work(
+ system_power_efficient_wq,
+ &hub->leds, 0);
}
status = -ENOTCONN; /* Don't retry */
goto loop_disable;
@@ -4739,6 +4770,8 @@ static void hub_events(void)
/* deal with port status changes */
for (i = 1; i <= hdev->maxchild; i++) {
+ struct usb_device *udev = hub->ports[i - 1]->child;
+
if (test_bit(i, hub->busy_bits))
continue;
connect_change = test_bit(i, hub->change_bits);
@@ -4837,8 +4870,6 @@ static void hub_events(void)
*/
if (hub_port_warm_reset_required(hub, portstatus)) {
int status;
- struct usb_device *udev =
- hub->ports[i - 1]->child;
dev_dbg(hub_dev, "warm reset port %d\n", i);
if (!udev ||
@@ -4855,6 +4886,24 @@ static void hub_events(void)
usb_unlock_device(udev);
connect_change = 0;
}
+ /*
+ * On disconnect USB3 protocol ports transit from U0 to
+ * SS.Inactive to Rx.Detect. If this happens a warm-
+ * reset is not needed, but a (re)connect may happen
+ * before khubd runs and sees the disconnect, and the
+ * device may be an unknown state.
+ *
+ * If the port went through SS.Inactive without khubd
+ * seeing it the C_LINK_STATE change flag will be set,
+ * and we reset the dev to put it in a known state.
+ */
+ } else if (udev && hub_is_superspeed(hub->hdev) &&
+ (portchange & USB_PORT_STAT_C_LINK_STATE) &&
+ (portstatus & USB_PORT_STAT_CONNECTION)) {
+ usb_lock_device(udev);
+ usb_reset_device(udev);
+ usb_unlock_device(udev);
+ connect_change = 0;
}
if (connect_change)
@@ -5112,7 +5161,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
struct usb_hcd *hcd = bus_to_hcd(udev->bus);
struct usb_device_descriptor descriptor = udev->descriptor;
struct usb_host_bos *bos;
- int i, ret = 0;
+ int i, j, ret = 0;
int port1 = udev->portnum;
if (udev->state == USB_STATE_NOTATTACHED ||
@@ -5238,6 +5287,9 @@ static int usb_reset_and_verify_device(struct usb_device *udev)
ret);
goto re_enumerate;
}
+ /* Resetting also frees any allocated streams */
+ for (j = 0; j < intf->cur_altsetting->desc.bNumEndpoints; j++)
+ intf->cur_altsetting->endpoint[j].streams = 0;
}
done:
@@ -5340,10 +5392,11 @@ int usb_reset_device(struct usb_device *udev)
else if (cintf->condition ==
USB_INTERFACE_BOUND)
rebind = 1;
+ if (rebind)
+ cintf->needs_binding = 1;
}
- if (ret == 0 && rebind)
- usb_rebind_intf(cintf);
}
+ usb_unbind_and_rebind_marked_interfaces(udev);
}
usb_autosuspend_device(udev);