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.willi...@intel.com>
---
 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 majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to