summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/core/hub.c60
-rw-r--r--include/linux/usb.h2
2 files changed, 23 insertions, 39 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index ba406b8d688d..b7f66dcd1fe0 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -3947,7 +3947,7 @@ static const char * const usb3_lpm_names[] = {
* 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)
+static int usb_req_set_sel(struct usb_device *udev)
{
struct usb_set_sel_req *sel_values;
unsigned long long u1_sel;
@@ -3956,7 +3956,7 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state)
unsigned long long u2_pel;
int ret;
- if (udev->state != USB_STATE_CONFIGURED)
+ if (!udev->parent || udev->speed < USB_SPEED_SUPER || !udev->lpm_capable)
return 0;
/* Convert SEL and PEL stored in ns to us */
@@ -3973,35 +3973,15 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state)
* 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 us or PEL %llu us\n",
- usb3_lpm_names[state], u1_sel, u1_pel);
+ if (u1_sel > USB3_LPM_MAX_U1_SEL_PEL ||
+ u1_pel > USB3_LPM_MAX_U1_SEL_PEL ||
+ u2_sel > USB3_LPM_MAX_U2_SEL_PEL ||
+ u2_pel > USB3_LPM_MAX_U2_SEL_PEL) {
+ dev_dbg(&udev->dev, "Device-initiated U1/U2 disabled due to long SEL or PEL\n");
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.
@@ -4022,6 +4002,10 @@ static int usb_req_set_sel(struct usb_device *udev, enum usb3_link_state state)
sel_values, sizeof *(sel_values),
USB_CTRL_SET_TIMEOUT);
kfree(sel_values);
+
+ if (ret > 0)
+ udev->lpm_devinit_allow = 1;
+
return ret;
}
@@ -4137,6 +4121,9 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev,
unsigned int sel; /* us */
int i, j;
+ if (!udev->lpm_devinit_allow)
+ return false;
+
if (state == USB3_LPM_U1)
sel = DIV_ROUND_UP(udev->u1_params.sel, 1000);
else if (state == USB3_LPM_U2)
@@ -4185,7 +4172,7 @@ static bool usb_device_may_initiate_lpm(struct usb_device *udev,
static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
enum usb3_link_state state)
{
- int timeout, ret;
+ int timeout;
__u8 u1_mel = udev->bos->ss_cap->bU1devExitLat;
__le16 u2_mel = udev->bos->ss_cap->bU2DevExitLat;
@@ -4197,17 +4184,6 @@ static void usb_enable_link_state(struct usb_hcd *hcd, struct usb_device *udev,
(state == USB3_LPM_U2 && u2_mel == 0))
return;
- /*
- * 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;
- }
-
/* 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
@@ -4487,6 +4463,11 @@ static int hub_handle_remote_wakeup(struct usb_hub *hub, unsigned int port,
return 0;
}
+static int usb_req_set_sel(struct usb_device *udev)
+{
+ return 0;
+}
+
#endif /* CONFIG_PM */
/*
@@ -5012,6 +4993,7 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,
udev->lpm_capable = usb_device_supports_lpm(udev);
udev->lpm_disable_count = 1;
usb_set_lpm_parameters(udev);
+ usb_req_set_sel(udev);
}
}
diff --git a/include/linux/usb.h b/include/linux/usb.h
index 60bee864d897..f7a9914fc97f 100644
--- a/include/linux/usb.h
+++ b/include/linux/usb.h
@@ -584,6 +584,7 @@ struct usb3_lpm_parameters {
* @authenticated: Crypto authentication passed
* @wusb: device is Wireless USB
* @lpm_capable: device supports LPM
+ * @lpm_devinit_allow: Allow USB3 device initiated LPM, exit latency is in range
* @usb2_hw_lpm_capable: device can perform USB2 hardware LPM
* @usb2_hw_lpm_besl_capable: device can perform USB2 hardware BESL LPM
* @usb2_hw_lpm_enabled: USB2 hardware LPM is enabled
@@ -666,6 +667,7 @@ struct usb_device {
unsigned authenticated:1;
unsigned wusb:1;
unsigned lpm_capable:1;
+ unsigned lpm_devinit_allow:1;
unsigned usb2_hw_lpm_capable:1;
unsigned usb2_hw_lpm_besl_capable:1;
unsigned usb2_hw_lpm_enabled:1;