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 <dan.j.williams@xxxxxxxxx> --- drivers/usb/core/port.c | 22 ++++++++++++++++++++++ 1 files changed, 22 insertions(+), 0 deletions(-) diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index d3aacf093aa1..217a3c6df29e 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -77,12 +77,16 @@ static int usb_port_runtime_resume(struct device *dev) struct usb_device *hdev = to_usb_device(dev->parent->parent); struct usb_interface *intf = to_usb_interface(dev->parent); struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + struct usb_port *peer = port_dev->peer; int port1 = port_dev->portnum; int retval; if (!hub) return -EINVAL; + if (!hub_is_superspeed(hdev) && peer) + pm_runtime_get_sync(&peer->dev); + usb_autopm_get_interface(intf); set_bit(port1, hub->busy_bits); @@ -104,6 +108,10 @@ static int usb_port_runtime_resume(struct device *dev) clear_bit(port1, hub->busy_bits); usb_autopm_put_interface(intf); + + if (!hub_is_superspeed(hdev) && peer) + pm_runtime_put(&peer->dev); + return retval; } @@ -113,6 +121,7 @@ static int usb_port_runtime_suspend(struct device *dev) struct usb_device *hdev = to_usb_device(dev->parent->parent); struct usb_interface *intf = to_usb_interface(dev->parent); struct usb_hub *hub = usb_hub_to_struct_hub(hdev); + struct usb_port *peer = port_dev->peer; int port1 = port_dev->portnum; int retval; @@ -123,6 +132,12 @@ static int usb_port_runtime_suspend(struct device *dev) == PM_QOS_FLAGS_ALL) return -EAGAIN; + /* block poweroff of superspeed ports while highspeed peer is on */ + dev_WARN_ONCE(&hdev->dev, hub_is_superspeed(hdev) && !peer, + "port%d missing peer info\n", port1); + if (hub_is_superspeed(hdev) && (!peer || peer->power_is_on)) + return -EBUSY; + usb_autopm_get_interface(intf); set_bit(port1, hub->busy_bits); retval = usb_hub_set_port_power(hdev, hub, port1, false); @@ -130,6 +145,13 @@ 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); + + /* bounce our peer now that we are down */ + if (!hub_is_superspeed(hdev) && peer) { + pm_runtime_get(&peer->dev); + pm_runtime_put(&peer->dev); + } + return retval; } #endif -- 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