On 05/17/2012 05:55 AM, Sarah Sharp wrote: > There are various functions within the USB core that will need to > disable USB 3.0 link power states. For example, when a USB device > driver is being bound to an interface, we need to disable USB 3.0 LPM > until we know if the driver will allow hub-initiated LPM transitions. > Another example is when the USB core is switching alternate interface > settings. The USB 3.0 timeout values are dependent on what endpoints > are enabled, so we want to ensure that LPM is disabled until the new alt > setting is fully installed. > > Multiple functions need to disable LPM, and those functions can even be > nested. For example, usb_bind_interface() could disable LPM, and then > call into the driver probe function, which may attempt to switch to a > different alt setting. Therefore, we need to keep a count of the number > of functions that require LPM to be disabled at any point in time. > > Introduce two new USB core API calls, usb_disable_lpm() and > usb_enable_lpm(). These functions increment and decrement a new > variable in the usb_device, lpm_disable_count. These two function calls > should always be paired. Even if usb_disable_lpm() fails, > usb_enable_lpm() must be called to balance the lpm_disable_count. The > bandwidth_mutex must be held when these new functions are called. > > Introduce a new variable (timeout) in the usb3_lpm_params structure to > keep track of the currently enabled U1/U2 timeout values. When > usb_disable_lpm() is called, and the USB device has the U1 or U2 > timeouts set to a non-zero value (meaning either device-initiated or > hub-initiated LPM is enabled), attempt to disable LPM, regardless of the > state of the lpm_disable_count. We want to ensure that all callers can > be guaranteed that LPM is disabled if usb_disable_lpm() returns zero. > > Otherwise the following scenario could occur: > > 1. Driver A is being bound to interface 1. usb_probe_interface() > disables LPM. Driver A doesn't care if hub-initiated LPM is enabled, so > even though usb_disable_lpm() fails, the probe of the driver continues, > and the bandwidth mutex is dropped. > > 2. Meanwhile, Driver B is being bound to interface 2. > usb_probe_interface() grabs the bandwidth mutex and calls > usb_disable_lpm(). That call should attempt to disable LPM, even > though the lpm_disable_count is set to 1 by Driver A. > > For usb_enable_lpm(), we attempt to enable LPM only when the > lpm_disable_count is zero. If some step in enabling LPM fails, it will > only have a minimal impact on power consumption, and all USB device > drivers should still work properly. Therefore don't bother to return > any error codes. > > Don't enable device-initiated LPM if the device is unconfigured. The > USB device will only accept the U1/U2_ENABLE control transfers in the > configured state. Do enable hub-initiated LPM in that case, since > devices are allowed to accept the LGO_Ux link commands in any state. > > Don't enable or disable LPM if the device is marked as not being LPM > capable. This can happen if: > - the USB device doesn't have a SS BOS descriptor, > - the device's parent hub has a zeroed bHeaderDecodeLatency value, or > - the xHCI host doesn't support LPM. > > Signed-off-by: Sarah Sharp <sarah.a.sharp@xxxxxxxxxxxxxxx> > --- > drivers/usb/core/hub.c | 365 ++++++++++++++++++++++++++++++++++++++++++++++ > include/linux/usb.h | 9 + > include/linux/usb/ch11.h | 2 + > include/linux/usb/ch9.h | 45 ++++++ > include/linux/usb/hcd.h | 9 + > 5 files changed, 430 insertions(+), 0 deletions(-) > > diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c > index 5219507..b170505 100644 > --- a/drivers/usb/core/hub.c > +++ b/drivers/usb/core/hub.c > @@ -3050,11 +3050,376 @@ 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; > + > + /* 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); > + > + return 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); sel_values is allocated but not freed? Thanks, Andiry > +} > + > +/* > + * 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. > + */ > +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)) > + return -EBUSY; > + if (usb_disable_link_state(hcd, udev, USB3_LPM_U2)) > + return -EBUSY; > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(usb_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); > + > #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; > +} > + > +void usb_enable_lpm(struct usb_device *udev) { } > #endif > > > diff --git a/include/linux/usb.h b/include/linux/usb.h > index 998c276..6ef80e8 100644 > --- a/include/linux/usb.h > +++ b/include/linux/usb.h > @@ -409,6 +409,12 @@ struct usb3_lpm_parameters { > * it will get data. > */ > unsigned long long sel; > + /* > + * The idle timeout value that is currently programmed into the parent > + * hub for this device. When the timer counts to zero, the parent hub > + * will initiate an LPM transition to either U1 or U2. > + */ > + int timeout; > }; > > /** > @@ -542,6 +548,7 @@ struct usb_device { > enum usb_device_removable removable; > struct usb3_lpm_parameters u1_params; > struct usb3_lpm_parameters u2_params; > + unsigned lpm_disable_count; > }; > #define to_usb_device(d) container_of(d, struct usb_device, dev) > > @@ -576,6 +583,8 @@ extern int usb_autopm_get_interface_async(struct usb_interface *intf); > extern void usb_autopm_put_interface_async(struct usb_interface *intf); > extern void usb_autopm_get_interface_no_resume(struct usb_interface *intf); > extern void usb_autopm_put_interface_no_suspend(struct usb_interface *intf); > +extern int usb_disable_lpm(struct usb_device *udev); > +extern void usb_enable_lpm(struct usb_device *udev); > > static inline void usb_mark_last_busy(struct usb_device *udev) > { > diff --git a/include/linux/usb/ch11.h b/include/linux/usb/ch11.h > index f1d26b6..b6c2863 100644 > --- a/include/linux/usb/ch11.h > +++ b/include/linux/usb/ch11.h > @@ -76,6 +76,8 @@ > #define USB_PORT_FEAT_C_BH_PORT_RESET 29 > #define USB_PORT_FEAT_FORCE_LINKPM_ACCEPT 30 > > +#define USB_PORT_LPM_TIMEOUT(p) (((p) & 0xff) << 8) > + > /* USB 3.0 hub remote wake mask bits, see table 10-14 */ > #define USB_PORT_FEAT_REMOTE_WAKE_CONNECT (1 << 8) > #define USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT (1 << 9) > diff --git a/include/linux/usb/ch9.h b/include/linux/usb/ch9.h > index e785d85..43bce9d 100644 > --- a/include/linux/usb/ch9.h > +++ b/include/linux/usb/ch9.h > @@ -935,6 +935,51 @@ enum usb_device_state { > */ > }; > > +enum usb3_link_state { > + USB3_LPM_U0 = 0, > + USB3_LPM_U1, > + USB3_LPM_U2, > + USB3_LPM_U3 > +}; > + > +/* > + * A U1 timeout of 0x0 means the parent hub will reject any transitions to U1. > + * 0xff means the parent hub will accept transitions to U1, but will not > + * initiate a transition. > + * > + * A U1 timeout of 0x1 to 0x7F also causes the hub to initiate a transition to > + * U1 after that many microseconds. Timeouts of 0x80 to 0xFE are reserved > + * values. > + * > + * A U2 timeout of 0x0 means the parent hub will reject any transitions to U2. > + * 0xff means the parent hub will accept transitions to U2, but will not > + * initiate a transition. > + * > + * A U2 timeout of 0x1 to 0xFE also causes the hub to initiate a transition to > + * U2 after N*256 microseconds. Therefore a U2 timeout value of 0x1 means a U2 > + * idle timer of 256 microseconds, 0x2 means 512 microseconds, 0xFE means > + * 65.024ms. > + */ > +#define USB3_LPM_DISABLED 0x0 > +#define USB3_LPM_U1_MAX_TIMEOUT 0x7F > +#define USB3_LPM_U2_MAX_TIMEOUT 0xFE > +#define USB3_LPM_DEVICE_INITIATED 0xFF > + > +struct usb_set_sel_req { > + __u8 u1_sel; > + __u8 u1_pel; > + __le16 u2_sel; > + __le16 u2_pel; > +} __attribute__ ((packed)); > + > +/* > + * The Set System Exit Latency control transfer provides one byte each for > + * U1 SEL and U1 PEL, so the max exit latency is 0xFF. U2 SEL and U2 PEL each > + * are two bytes long. > + */ > +#define USB3_LPM_MAX_U1_SEL_PEL 0xFF > +#define USB3_LPM_MAX_U2_SEL_PEL 0xFFFF > + > /*-------------------------------------------------------------------------*/ > > /* > diff --git a/include/linux/usb/hcd.h b/include/linux/usb/hcd.h > index bbb9464..7f855d5 100644 > --- a/include/linux/usb/hcd.h > +++ b/include/linux/usb/hcd.h > @@ -344,6 +344,15 @@ struct hc_driver { > */ > int (*update_device)(struct usb_hcd *, struct usb_device *); > int (*set_usb2_hw_lpm)(struct usb_hcd *, struct usb_device *, int); > + /* USB 3.0 Link Power Management */ > + /* Returns the USB3 hub-encoded value for the U1/U2 timeout. */ > + int (*enable_usb3_lpm_timeout)(struct usb_hcd *, > + struct usb_device *, enum usb3_link_state state); > + /* The xHCI host controller can still fail the command to > + * disable the LPM timeouts, so this can return an error code. > + */ > + int (*disable_usb3_lpm_timeout)(struct usb_hcd *, > + struct usb_device *, enum usb3_link_state state); > }; > > extern int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb); -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html