summaryrefslogtreecommitdiff
path: root/drivers/usb/core/hub.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2012-05-22 15:50:46 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2012-05-22 15:50:46 -0700
commita481991467d38afb43c3921d5b5b59ccb61b04ba (patch)
treea4b0b9a14da6fd5ef7b9b512bb32dbfcfcf2cd71 /drivers/usb/core/hub.c
parentf6a26ae7699416d86bea8cb68ce413571e9cab3c (diff)
parentcda4db53e9c28061c100400e1a4d273ea61dfba9 (diff)
Merge tag 'usb-3.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb
Pull USB 3.5-rc1 changes from Greg Kroah-Hartman: "Here is the big USB 3.5-rc1 pull request for the 3.5-rc1 merge window. It's touches a lot of different parts of the kernel, all USB drivers, due to some API cleanups (getting rid of the ancient err() macro) and some changes that are needed for USB 3.0 power management updates. There are also lots of new drivers, pimarily gadget, but others as well. We deleted a staging driver, which was nice, and finally dropped the obsolete usbfs code, which will make Al happy to never have to touch that again. There were some build errors in the tree that linux-next found a few days ago, but those were fixed by the most recent changes (all were due to us not building with CONFIG_PM disabled.) Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>" * tag 'usb-3.5-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/usb: (477 commits) xhci: Fix DIV_ROUND_UP compile error. xhci: Fix compile with CONFIG_USB_SUSPEND=n USB: Fix core compile with CONFIG_USB_SUSPEND=n brcm80211: Fix compile error for .disable_hub_initiated_lpm. Revert "USB: EHCI: work around bug in the Philips ISP1562 controller" MAINTAINERS: Add myself as maintainer to the USB PHY Layer USB: EHCI: fix command register configuration lost problem USB: Remove races in devio.c USB: ehci-platform: remove update_device USB: Disable hub-initiated LPM for comms devices. xhci: Add Intel U1/U2 timeout policy. xhci: Add infrastructure for host-specific LPM policies. USB: Add macros for interrupt endpoint types. xhci: Reserve one command for USB3 LPM disable. xhci: Some Evaluate Context commands must succeed. USB: Disable USB 3.0 LPM in critical sections. USB: Add support to enable/disable USB3 link states. USB: Allow drivers to disable hub-initiated LPM. USB: Calculate USB 3.0 exit latencies for LPM. USB: Refactor code to set LPM support flag. ... Conflicts: arch/arm/mach-exynos/mach-nuri.c arch/arm/mach-exynos/mach-universal_c210.c drivers/net/wireless/ath/ath6kl/usb.c
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r--drivers/usb/core/hub.c676
1 files changed, 673 insertions, 3 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index ec6c97dadbe4..04fb834c3fa1 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -177,6 +177,228 @@ static struct usb_hub *hdev_to_hub(struct usb_device *hdev)
return usb_get_intfdata(hdev->actconfig->interface[0]);
}
+static int usb_device_supports_lpm(struct usb_device *udev)
+{
+ /* USB 2.1 (and greater) devices indicate LPM support through
+ * their USB 2.0 Extended Capabilities BOS descriptor.
+ */
+ if (udev->speed == USB_SPEED_HIGH) {
+ if (udev->bos->ext_cap &&
+ (USB_LPM_SUPPORT &
+ le32_to_cpu(udev->bos->ext_cap->bmAttributes)))
+ return 1;
+ return 0;
+ }
+
+ /* All USB 3.0 must support LPM, but we need their max exit latency
+ * information from the SuperSpeed Extended Capabilities BOS descriptor.
+ */
+ if (!udev->bos->ss_cap) {
+ dev_warn(&udev->dev, "No LPM exit latency info found. "
+ "Power management will be impacted.\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");
+ return 0;
+}
+
+/*
+ * Set the Maximum Exit Latency (MEL) for the host to initiate a transition from
+ * either U1 or U2.
+ */
+static void usb_set_lpm_mel(struct usb_device *udev,
+ struct usb3_lpm_parameters *udev_lpm_params,
+ unsigned int udev_exit_latency,
+ struct usb_hub *hub,
+ struct usb3_lpm_parameters *hub_lpm_params,
+ unsigned int hub_exit_latency)
+{
+ unsigned int total_mel;
+ unsigned int device_mel;
+ unsigned int hub_mel;
+
+ /*
+ * Calculate the time it takes to transition all links from the roothub
+ * to the parent hub into U0. The parent hub must then decode the
+ * packet (hub header decode latency) to figure out which port it was
+ * bound for.
+ *
+ * The Hub Header decode latency is expressed in 0.1us intervals (0x1
+ * means 0.1us). Multiply that by 100 to get nanoseconds.
+ */
+ total_mel = hub_lpm_params->mel +
+ (hub->descriptor->u.ss.bHubHdrDecLat * 100);
+
+ /*
+ * How long will it take to transition the downstream hub's port into
+ * U0? The greater of either the hub exit latency or the device exit
+ * latency.
+ *
+ * The BOS U1/U2 exit latencies are expressed in 1us intervals.
+ * Multiply that by 1000 to get nanoseconds.
+ */
+ device_mel = udev_exit_latency * 1000;
+ hub_mel = hub_exit_latency * 1000;
+ if (device_mel > hub_mel)
+ total_mel += device_mel;
+ else
+ total_mel += hub_mel;
+
+ udev_lpm_params->mel = total_mel;
+}
+
+/*
+ * Set the maximum Device to Host Exit Latency (PEL) for the device to initiate
+ * a transition from either U1 or U2.
+ */
+static void usb_set_lpm_pel(struct usb_device *udev,
+ struct usb3_lpm_parameters *udev_lpm_params,
+ unsigned int udev_exit_latency,
+ struct usb_hub *hub,
+ struct usb3_lpm_parameters *hub_lpm_params,
+ unsigned int hub_exit_latency,
+ unsigned int port_to_port_exit_latency)
+{
+ unsigned int first_link_pel;
+ unsigned int hub_pel;
+
+ /*
+ * First, the device sends an LFPS to transition the link between the
+ * device and the parent hub into U0. The exit latency is the bigger of
+ * the device exit latency or the hub exit latency.
+ */
+ if (udev_exit_latency > hub_exit_latency)
+ first_link_pel = udev_exit_latency * 1000;
+ else
+ first_link_pel = hub_exit_latency * 1000;
+
+ /*
+ * When the hub starts to receive the LFPS, there is a slight delay for
+ * it to figure out that one of the ports is sending an LFPS. Then it
+ * will forward the LFPS to its upstream link. The exit latency is the
+ * delay, plus the PEL that we calculated for this hub.
+ */
+ hub_pel = port_to_port_exit_latency * 1000 + hub_lpm_params->pel;
+
+ /*
+ * According to figure C-7 in the USB 3.0 spec, the PEL for this device
+ * is the greater of the two exit latencies.
+ */
+ if (first_link_pel > hub_pel)
+ udev_lpm_params->pel = first_link_pel;
+ else
+ udev_lpm_params->pel = hub_pel;
+}
+
+/*
+ * Set the System Exit Latency (SEL) to indicate the total worst-case time from
+ * when a device initiates a transition to U0, until when it will receive the
+ * first packet from the host controller.
+ *
+ * Section C.1.5.1 describes the four components to this:
+ * - t1: device PEL
+ * - t2: time for the ERDY to make it from the device to the host.
+ * - t3: a host-specific delay to process the ERDY.
+ * - t4: time for the packet to make it from the host to the device.
+ *
+ * t3 is specific to both the xHCI host and the platform the host is integrated
+ * into. The Intel HW folks have said it's negligible, FIXME if a different
+ * vendor says otherwise.
+ */
+static void usb_set_lpm_sel(struct usb_device *udev,
+ struct usb3_lpm_parameters *udev_lpm_params)
+{
+ struct usb_device *parent;
+ unsigned int num_hubs;
+ unsigned int total_sel;
+
+ /* t1 = device PEL */
+ total_sel = udev_lpm_params->pel;
+ /* How many external hubs are in between the device & the root port. */
+ for (parent = udev->parent, num_hubs = 0; parent->parent;
+ parent = parent->parent)
+ num_hubs++;
+ /* t2 = 2.1us + 250ns * (num_hubs - 1) */
+ if (num_hubs > 0)
+ total_sel += 2100 + 250 * (num_hubs - 1);
+
+ /* t4 = 250ns * num_hubs */
+ total_sel += 250 * num_hubs;
+
+ udev_lpm_params->sel = total_sel;
+}
+
+static void usb_set_lpm_parameters(struct usb_device *udev)
+{
+ struct usb_hub *hub;
+ unsigned int port_to_port_delay;
+ unsigned int udev_u1_del;
+ unsigned int udev_u2_del;
+ unsigned int hub_u1_del;
+ unsigned int hub_u2_del;
+
+ if (!udev->lpm_capable || udev->speed != USB_SPEED_SUPER)
+ return;
+
+ hub = hdev_to_hub(udev->parent);
+ /* It doesn't take time to transition the roothub into U0, since it
+ * doesn't have an upstream link.
+ */
+ if (!hub)
+ return;
+
+ udev_u1_del = udev->bos->ss_cap->bU1devExitLat;
+ udev_u2_del = udev->bos->ss_cap->bU2DevExitLat;
+ hub_u1_del = udev->parent->bos->ss_cap->bU1devExitLat;
+ hub_u2_del = udev->parent->bos->ss_cap->bU2DevExitLat;
+
+ usb_set_lpm_mel(udev, &udev->u1_params, udev_u1_del,
+ hub, &udev->parent->u1_params, hub_u1_del);
+
+ usb_set_lpm_mel(udev, &udev->u2_params, udev_u2_del,
+ hub, &udev->parent->u2_params, hub_u2_del);
+
+ /*
+ * Appendix C, section C.2.2.2, says that there is a slight delay from
+ * when the parent hub notices the downstream port is trying to
+ * transition to U0 to when the hub initiates a U0 transition on its
+ * upstream port. The section says the delays are tPort2PortU1EL and
+ * tPort2PortU2EL, but it doesn't define what they are.
+ *
+ * The hub chapter, sections 10.4.2.4 and 10.4.2.5 seem to be talking
+ * about the same delays. Use the maximum delay calculations from those
+ * sections. For U1, it's tHubPort2PortExitLat, which is 1us max. For
+ * U2, it's tHubPort2PortExitLat + U2DevExitLat - U1DevExitLat. I
+ * assume the device exit latencies they are talking about are the hub
+ * exit latencies.
+ *
+ * What do we do if the U2 exit latency is less than the U1 exit
+ * latency? It's possible, although not likely...
+ */
+ port_to_port_delay = 1;
+
+ usb_set_lpm_pel(udev, &udev->u1_params, udev_u1_del,
+ hub, &udev->parent->u1_params, hub_u1_del,
+ port_to_port_delay);
+
+ if (hub_u2_del > hub_u1_del)
+ port_to_port_delay = 1 + hub_u2_del - hub_u1_del;
+ else
+ port_to_port_delay = 1 + hub_u1_del;
+
+ usb_set_lpm_pel(udev, &udev->u2_params, udev_u2_del,
+ hub, &udev->parent->u2_params, hub_u2_del,
+ port_to_port_delay);
+
+ /* Now that we've got PEL, calculate SEL. */
+ usb_set_lpm_sel(udev, &udev->u1_params);
+ usb_set_lpm_sel(udev, &udev->u2_params);
+}
+
/* USB 2.0 spec Section 11.24.4.5 */
static int get_hub_descriptor(struct usb_device *hdev, void *data)
{
@@ -2480,6 +2702,12 @@ 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_unlocked_disable_lpm(udev)) {
+ dev_err(&udev->dev, "%s Failed to disable LPM before suspend\n.",
+ __func__);
+ return -ENOMEM;
+ }
+
/* see 7.1.7.6 */
if (hub_is_superspeed(hub->hdev))
status = set_port_feature(hub->hdev,
@@ -2499,6 +2727,13 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)
NULL, 0,
USB_CTRL_SET_TIMEOUT);
+ /* Try to enable USB2 hardware LPM again */
+ if (udev->usb2_hw_lpm_capable == 1)
+ usb_set_usb2_hardware_lpm(udev, 1);
+
+ /* Try to enable USB3 LPM again */
+ usb_unlocked_enable_lpm(udev);
+
/* System sleep transitions should never fail */
if (!PMSG_IS_AUTO(msg))
status = 0;
@@ -2696,6 +2931,9 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
/* Try to enable USB2 hardware LPM */
if (udev->usb2_hw_lpm_capable == 1)
usb_set_usb2_hardware_lpm(udev, 1);
+
+ /* Try to enable USB3 LPM */
+ usb_unlocked_enable_lpm(udev);
}
return status;
@@ -2824,11 +3062,429 @@ void usb_root_hub_lost_power(struct usb_device *rhdev)
}
EXPORT_SYMBOL_GPL(usb_root_hub_lost_power);
+static const char * const usb3_lpm_names[] = {
+ "U0",
+ "U1",
+ "U2",
+ "U3",
+};
+
+/*
+ * Send a Set SEL control transfer to the device, prior to enabling
+ * device-initiated U1 or U2. This lets the device know the exit latencies from
+ * the time the device initiates a U1 or U2 exit, to the time it will receive a
+ * packet from the host.
+ *
+ * This function will fail if the SEL or PEL values for udev are greater than
+ * the maximum allowed values for the link state to be enabled.
+ */
+static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state)
+{
+ struct usb_set_sel_req *sel_values;
+ unsigned long long u1_sel;
+ unsigned long long u1_pel;
+ unsigned long long u2_sel;
+ unsigned long long u2_pel;
+ int ret;
+
+ /* Convert SEL and PEL stored in ns to us */
+ u1_sel = DIV_ROUND_UP(udev->u1_params.sel, 1000);
+ u1_pel = DIV_ROUND_UP(udev->u1_params.pel, 1000);
+ u2_sel = DIV_ROUND_UP(udev->u2_params.sel, 1000);
+ u2_pel = DIV_ROUND_UP(udev->u2_params.pel, 1000);
+
+ /*
+ * Make sure that the calculated SEL and PEL values for the link
+ * state we're enabling aren't bigger than the max SEL/PEL
+ * value that will fit in the SET SEL control transfer.
+ * Otherwise the device would get an incorrect idea of the exit
+ * latency for the link state, and could start a device-initiated
+ * U1/U2 when the exit latencies are too high.
+ */
+ if ((state == USB3_LPM_U1 &&
+ (u1_sel > USB3_LPM_MAX_U1_SEL_PEL ||
+ u1_pel > USB3_LPM_MAX_U1_SEL_PEL)) ||
+ (state == USB3_LPM_U2 &&
+ (u2_sel > USB3_LPM_MAX_U2_SEL_PEL ||
+ u2_pel > USB3_LPM_MAX_U2_SEL_PEL))) {
+ dev_dbg(&udev->dev, "Device-initiated %s disabled due "
+ "to long SEL %llu ms or PEL %llu ms\n",
+ usb3_lpm_names[state], u1_sel, u1_pel);
+ return -EINVAL;
+ }
+
+ /*
+ * If we're enabling device-initiated LPM for one link state,
+ * but the other link state has a too high SEL or PEL value,
+ * just set those values to the max in the Set SEL request.
+ */
+ if (u1_sel > USB3_LPM_MAX_U1_SEL_PEL)
+ u1_sel = USB3_LPM_MAX_U1_SEL_PEL;
+
+ if (u1_pel > USB3_LPM_MAX_U1_SEL_PEL)
+ u1_pel = USB3_LPM_MAX_U1_SEL_PEL;
+
+ if (u2_sel > USB3_LPM_MAX_U2_SEL_PEL)
+ u2_sel = USB3_LPM_MAX_U2_SEL_PEL;
+
+ if (u2_pel > USB3_LPM_MAX_U2_SEL_PEL)
+ u2_pel = USB3_LPM_MAX_U2_SEL_PEL;
+
+ /*
+ * usb_enable_lpm() can be called as part of a failed device reset,
+ * which may be initiated by an error path of a mass storage driver.
+ * Therefore, use GFP_NOIO.
+ */
+ sel_values = kmalloc(sizeof *(sel_values), GFP_NOIO);
+ if (!sel_values)
+ return -ENOMEM;
+
+ sel_values->u1_sel = u1_sel;
+ sel_values->u1_pel = u1_pel;
+ sel_values->u2_sel = cpu_to_le16(u2_sel);
+ sel_values->u2_pel = cpu_to_le16(u2_pel);
+
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_SEL,
+ USB_RECIP_DEVICE,
+ 0, 0,
+ sel_values, sizeof *(sel_values),
+ USB_CTRL_SET_TIMEOUT);
+ kfree(sel_values);
+ return ret;
+}
+
+/*
+ * Enable or disable device-initiated U1 or U2 transitions.
+ */
+static int usb_set_device_initiated_lpm(struct usb_device *udev,
+ enum usb3_link_state state, bool enable)
+{
+ int ret;
+ int feature;
+
+ switch (state) {
+ case USB3_LPM_U1:
+ feature = USB_DEVICE_U1_ENABLE;
+ break;
+ case USB3_LPM_U2:
+ feature = USB_DEVICE_U2_ENABLE;
+ break;
+ default:
+ dev_warn(&udev->dev, "%s: Can't %s non-U1 or U2 state.\n",
+ __func__, enable ? "enable" : "disable");
+ return -EINVAL;
+ }
+
+ if (udev->state != USB_STATE_CONFIGURED) {
+ dev_dbg(&udev->dev, "%s: Can't %s %s state "
+ "for unconfigured device.\n",
+ __func__, enable ? "enable" : "disable",
+ usb3_lpm_names[state]);
+ return 0;
+ }
+
+ if (enable) {
+ /*
+ * First, let the device know about the exit latencies
+ * associated with the link state we're about to enable.
+ */
+ ret = usb_req_set_sel(udev, state);
+ if (ret < 0) {
+ dev_warn(&udev->dev, "Set SEL for device-initiated "
+ "%s failed.\n", usb3_lpm_names[state]);
+ return -EBUSY;
+ }
+ /*
+ * Now send the control transfer to enable device-initiated LPM
+ * for either U1 or U2.
+ */
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ USB_REQ_SET_FEATURE,
+ USB_RECIP_DEVICE,
+ feature,
+ 0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ } else {
+ ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ USB_REQ_CLEAR_FEATURE,
+ USB_RECIP_DEVICE,
+ feature,
+ 0, NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ }
+ if (ret < 0) {
+ dev_warn(&udev->dev, "%s of device-initiated %s failed.\n",
+ enable ? "Enable" : "Disable",
+ usb3_lpm_names[state]);
+ return -EBUSY;
+ }
+ return 0;
+}
+
+static int usb_set_lpm_timeout(struct usb_device *udev,
+ enum usb3_link_state state, int timeout)
+{
+ int ret;
+ int feature;
+
+ switch (state) {
+ case USB3_LPM_U1:
+ feature = USB_PORT_FEAT_U1_TIMEOUT;
+ break;
+ case USB3_LPM_U2:
+ feature = USB_PORT_FEAT_U2_TIMEOUT;
+ break;
+ default:
+ dev_warn(&udev->dev, "%s: Can't set timeout for non-U1 or U2 state.\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (state == USB3_LPM_U1 && timeout > USB3_LPM_U1_MAX_TIMEOUT &&
+ timeout != USB3_LPM_DEVICE_INITIATED) {
+ dev_warn(&udev->dev, "Failed to set %s timeout to 0x%x, "
+ "which is a reserved value.\n",
+ usb3_lpm_names[state], timeout);
+ return -EINVAL;
+ }
+
+ ret = set_port_feature(udev->parent,
+ USB_PORT_LPM_TIMEOUT(timeout) | udev->portnum,
+ feature);
+ if (ret < 0) {
+ dev_warn(&udev->dev, "Failed to set %s timeout to 0x%x,"
+ "error code %i\n", usb3_lpm_names[state],
+ timeout, ret);
+ return -EBUSY;
+ }
+ if (state == USB3_LPM_U1)
+ udev->u1_params.timeout = timeout;
+ else
+ udev->u2_params.timeout = timeout;
+ return 0;
+}
+
+/*
+ * Enable the hub-initiated U1/U2 idle timeouts, and enable device-initiated
+ * U1/U2 entry.
+ *
+ * We will attempt to enable U1 or U2, but there are no guarantees that the
+ * control transfers to set the hub timeout or enable device-initiated U1/U2
+ * will be successful.
+ *
+ * If we cannot set the parent hub U1/U2 timeout, we attempt to let the xHCI
+ * driver know about it. If that call fails, it should be harmless, and just
+ * take up more slightly more bus bandwidth for unnecessary U1/U2 exit latency.
+ */
+static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
+ enum usb3_link_state state)
+{
+ int timeout;
+
+ /* We allow the host controller to set the U1/U2 timeout internally
+ * first, so that it can change its schedule to account for the
+ * additional latency to send data to a device in a lower power
+ * link state.
+ */
+ timeout = hcd->driver->enable_usb3_lpm_timeout(hcd, udev, state);
+
+ /* xHCI host controller doesn't want to enable this LPM state. */
+ if (timeout == 0)
+ return;
+
+ if (timeout < 0) {
+ dev_warn(&udev->dev, "Could not enable %s link state, "
+ "xHCI error %i.\n", usb3_lpm_names[state],
+ timeout);
+ return;
+ }
+
+ if (usb_set_lpm_timeout(udev, state, timeout))
+ /* If we can't set the parent hub U1/U2 timeout,
+ * device-initiated LPM won't be allowed either, so let the xHCI
+ * host know that this link state won't be enabled.
+ */
+ hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state);
+
+ /* Only a configured device will accept the Set Feature U1/U2_ENABLE */
+ else if (udev->actconfig)
+ usb_set_device_initiated_lpm(udev, state, true);
+
+}
+
+/*
+ * Disable the hub-initiated U1/U2 idle timeouts, and disable device-initiated
+ * U1/U2 entry.
+ *
+ * If this function returns -EBUSY, the parent hub will still allow U1/U2 entry.
+ * If zero is returned, the parent will not allow the link to go into U1/U2.
+ *
+ * If zero is returned, device-initiated U1/U2 entry may still be enabled, but
+ * it won't have an effect on the bus link state because the parent hub will
+ * still disallow device-initiated U1/U2 entry.
+ *
+ * If zero is returned, the xHCI host controller may still think U1/U2 entry is
+ * possible. The result will be slightly more bus bandwidth will be taken up
+ * (to account for U1/U2 exit latency), but it should be harmless.
+ */
+static int usb_disable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
+ enum usb3_link_state state)
+{
+ int feature;
+
+ switch (state) {
+ case USB3_LPM_U1:
+ feature = USB_PORT_FEAT_U1_TIMEOUT;
+ break;
+ case USB3_LPM_U2:
+ feature = USB_PORT_FEAT_U2_TIMEOUT;
+ break;
+ default:
+ dev_warn(&udev->dev, "%s: Can't disable non-U1 or U2 state.\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ if (usb_set_lpm_timeout(udev, state, 0))
+ return -EBUSY;
+
+ usb_set_device_initiated_lpm(udev, state, false);
+
+ if (hcd->driver->disable_usb3_lpm_timeout(hcd, udev, state))
+ dev_warn(&udev->dev, "Could not disable xHCI %s timeout, "
+ "bus schedule bandwidth may be impacted.\n",
+ usb3_lpm_names[state]);
+ return 0;
+}
+
+/*
+ * Disable hub-initiated and device-initiated U1 and U2 entry.
+ * Caller must own the bandwidth_mutex.
+ *
+ * This will call usb_enable_lpm() on failure, which will decrement
+ * lpm_disable_count, and will re-enable LPM if lpm_disable_count reaches zero.
+ */
+int usb_disable_lpm(struct usb_device *udev)
+{
+ struct usb_hcd *hcd;
+
+ if (!udev || !udev->parent ||
+ udev->speed != USB_SPEED_SUPER ||
+ !udev->lpm_capable)
+ return 0;
+
+ hcd = bus_to_hcd(udev->bus);
+ if (!hcd || !hcd->driver->disable_usb3_lpm_timeout)
+ return 0;
+
+ udev->lpm_disable_count++;
+ if ((udev->u1_params.timeout == 0 && udev->u1_params.timeout == 0))
+ return 0;
+
+ /* If LPM is enabled, attempt to disable it. */
+ if (usb_disable_link_state(hcd, udev, USB3_LPM_U1))
+ goto enable_lpm;
+ if (usb_disable_link_state(hcd, udev, USB3_LPM_U2))
+ goto enable_lpm;
+
+ return 0;
+
+enable_lpm:
+ usb_enable_lpm(udev);
+ return -EBUSY;
+}
+EXPORT_SYMBOL_GPL(usb_disable_lpm);
+
+/* Grab the bandwidth_mutex before calling usb_disable_lpm() */
+int usb_unlocked_disable_lpm(struct usb_device *udev)
+{
+ struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+ int ret;
+
+ if (!hcd)
+ return -EINVAL;
+
+ mutex_lock(hcd->bandwidth_mutex);
+ ret = usb_disable_lpm(udev);
+ mutex_unlock(hcd->bandwidth_mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm);
+
+/*
+ * Attempt to enable device-initiated and hub-initiated U1 and U2 entry. The
+ * xHCI host policy may prevent U1 or U2 from being enabled.
+ *
+ * Other callers may have disabled link PM, so U1 and U2 entry will be disabled
+ * until the lpm_disable_count drops to zero. Caller must own the
+ * bandwidth_mutex.
+ */
+void usb_enable_lpm(struct usb_device *udev)
+{
+ struct usb_hcd *hcd;
+
+ if (!udev || !udev->parent ||
+ udev->speed != USB_SPEED_SUPER ||
+ !udev->lpm_capable)
+ return;
+
+ udev->lpm_disable_count--;
+ hcd = bus_to_hcd(udev->bus);
+ /* Double check that we can both enable and disable LPM.
+ * Device must be configured to accept set feature U1/U2 timeout.
+ */
+ if (!hcd || !hcd->driver->enable_usb3_lpm_timeout ||
+ !hcd->driver->disable_usb3_lpm_timeout)
+ return;
+
+ if (udev->lpm_disable_count > 0)
+ return;
+
+ usb_enable_link_state(hcd, udev, USB3_LPM_U1);
+ usb_enable_link_state(hcd, udev, USB3_LPM_U2);
+}
+EXPORT_SYMBOL_GPL(usb_enable_lpm);
+
+/* Grab the bandwidth_mutex before calling usb_enable_lpm() */
+void usb_unlocked_enable_lpm(struct usb_device *udev)
+{
+ struct usb_hcd *hcd = bus_to_hcd(udev->bus);
+
+ if (!hcd)
+ return;
+
+ mutex_lock(hcd->bandwidth_mutex);
+ usb_enable_lpm(udev);
+ mutex_unlock(hcd->bandwidth_mutex);
+}
+EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm);
+
+
#else /* CONFIG_PM */
#define hub_suspend NULL
#define hub_resume NULL
#define hub_reset_resume NULL
+
+int usb_disable_lpm(struct usb_device *udev)
+{
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_disable_lpm);
+
+void usb_enable_lpm(struct usb_device *udev) { }
+EXPORT_SYMBOL_GPL(usb_enable_lpm);
+
+int usb_unlocked_disable_lpm(struct usb_device *udev)
+{
+ return 0;
+}
+EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm);
+
+void usb_unlocked_enable_lpm(struct usb_device *udev) { }
+EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm);
#endif
@@ -3208,9 +3864,8 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,
if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) {
retval = usb_get_bos_descriptor(udev);
if (!retval) {
- if (udev->bos->ext_cap && (USB_LPM_SUPPORT &
- le32_to_cpu(udev->bos->ext_cap->bmAttributes)))
- udev->lpm_capable = 1;
+ udev->lpm_capable = usb_device_supports_lpm(udev);
+ usb_set_lpm_parameters(udev);
}
}
@@ -4042,11 +4697,22 @@ 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;
}
@@ -4058,6 +4724,7 @@ 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;
}
@@ -4096,10 +4763,13 @@ 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:
return 0;