On Wed, 5 Mar 2014, Dan Williams wrote:
> 8<---------
> Subject: usb: block suspension of superspeed port while hispeed peer is active
>
> From: Dan Williams <[email protected]>
>
> ClearPortFeature(PORT_POWER) on a usb3 port places the port in either a
> DSPORT.Powered-off-detect / DSPORT.Powered-off-reset loop, or the
> DSPORT.Powered-off state. There is no way to ensure that RX
> terminations will persist in this state, so it is possible a device will
> degrade to its usb2 connection. Prevent this by blocking power-off of a
> usb3 port while its usb2 peer is active, and powering on a usb3 port
> before its usb2 peer.
>
> Signed-off-by: Dan Williams <[email protected]>
> @@ -129,6 +139,15 @@ static int usb_port_runtime_suspend(struct device *dev)
> usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE);
> clear_bit(port1, hub->busy_bits);
> usb_autopm_put_interface(intf);
> +
> + /*
> + * Our peer usb3 port may now be able to suspend, asynchronously
s/,/, so/
> + * queue a suspend request to observe that this usb2 peer port
s/peer //
> + * is now off.
> + */
> + if (!hub_is_superspeed(hdev) && peer)
> + pm_runtime_put(&peer->dev);
> +
> return retval;
> }
> #endif
> @@ -151,8 +170,26 @@ static struct device_driver usb_port_driver = {
> .owner = THIS_MODULE,
> };
>
> +/*
> + * Modifying ->peer affects usb_port_runtime_{suspend|resume} so make
> + * sure devices are active before the change and re-evaluate
> + * afterwards
> + */
> +static void pre_modify_peers(struct usb_port *left, struct usb_port *right)
> +{
> + pm_runtime_get_sync(&left->dev);
> + pm_runtime_get_sync(&right->dev);
> +}
> +
> +static void post_modify_peers(struct usb_port *left, struct usb_port *right)
> +{
> + pm_runtime_put(&left->dev);
> + pm_runtime_put(&right->dev);
> +}
You can put the contents of these routines directly inline; no need to
split them out.
> @@ -178,9 +215,26 @@ static int link_peers(struct usb_port *left, struct
> usb_port *right)
> return rc;
> }
>
> + pre_modify_peers(left, right);
> left->peer = right;
> right->peer = left;
>
> + /*
> + * Ports are peer linked, hold a reference on the superspeed
> + * port which the hispeed port drops when it suspends. This
> + * ensures that superspeed ports only suspend after their
> + * hispeed peer.
> + */
> + ldev = to_usb_device(left->dev.parent->parent);
> + rdev = to_usb_device(right->dev.parent->parent);
Please call these lhdev and rhdev.
> + if (hub_is_superspeed(ldev))
> + pm_runtime_get_noresume(&left->dev);
> + else {
> + WARN_ON(!hub_is_superspeed(rdev));
> + pm_runtime_get_noresume(&right->dev);
> + }
> + post_modify_peers(left, right);
In fact, do we need the pre/post_modify actions at all? Wouldn't it be
sufficient to do just a pm_runtime_get_sync on the SuperSpeed peer?
> @@ -200,14 +254,32 @@ static void link_peers_report(struct usb_port *left,
> struct usb_port *right)
>
> static void unlink_peers(struct usb_port *left, struct usb_port *right)
> {
> + struct usb_device *ldev, *rdev;
> +
> WARN(right->peer != left || left->peer != right,
> "%s and %s are not peers?\n",
> dev_name(&left->dev), dev_name(&right->dev));
>
> + pre_modify_peers(left, right);
I don't think this is necessary. If the peer is already suspended, why
power it up again?
> sysfs_remove_link(&left->dev.kobj, "peer");
> right->peer = NULL;
> sysfs_remove_link(&right->dev.kobj, "peer");
> left->peer = NULL;
> +
> + /*
> + * Ports are no longer peer linked, drop the reference that
> + * keeps the superspeed port (may be 'right' or 'left') powered
> + * when its peer is active
> + */
> + ldev = to_usb_device(left->dev.parent->parent);
> + rdev = to_usb_device(right->dev.parent->parent);
Again, lhdev and rhdev.
> + if (hub_is_superspeed(ldev))
> + pm_runtime_put_noidle(&left->dev);
> + else {
> + WARN_ON(!hub_is_superspeed(rdev));
The WARN_ON above should be sufficient; we don't need this one as well.
> + pm_runtime_put_noidle(&right->dev);
These calls should become pm_runtime_put().
> + }
> + post_modify_peers(left, right);
Not needed.
Alan Stern
--
To unsubscribe from this list: send the line "unsubscribe linux-usb" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html