Port power testing sometimes result in a port being powered-off while C_PORT_RESET and C_PORT_LINK_STATE are active. Per the spec these bits should be automatically cleared by being in the logical powered off state. Handle this for hubs that do not follow the spec. Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx> --- drivers/usb/core/hub.c | 68 ++++++++++++++++++++++++++++++------------------ 1 files changed, 42 insertions(+), 26 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 0fa13a52b52a..80661e20de9e 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4663,6 +4663,34 @@ static void put_port(struct usb_port *port_dev, int *port1) (*port1)++; } +static void hub_clear_misc_change(struct usb_hub *hub, int port1, + u16 portstatus, u16 portchange) +{ + struct device *hub_dev = hub->intfdev; + struct usb_device *hdev = hub->hdev; + + if (portchange & USB_PORT_STAT_C_RESET) { + dev_dbg(hub_dev, "reset change on port %d\n", port1); + usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_RESET); + } + if ((portchange & USB_PORT_STAT_C_BH_RESET) + && hub_is_superspeed(hdev)) { + dev_dbg(hub_dev, "warm reset change on port %d\n", port1); + usb_clear_port_feature(hdev, port1, + USB_PORT_FEAT_C_BH_PORT_RESET); + } + if (portchange & USB_PORT_STAT_C_LINK_STATE) { + dev_dbg(hub_dev, "link state change on port %d\n", port1); + usb_clear_port_feature(hdev, port1, + USB_PORT_FEAT_C_PORT_LINK_STATE); + } + if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) { + dev_warn(hub_dev, "config error on port %d\n", port1); + usb_clear_port_feature(hdev, port1, + USB_PORT_FEAT_C_PORT_CONFIG_ERROR); + } +} + static struct usb_port *next_active_port(struct usb_hub *hub, int *port1) { struct usb_device *hdev = hub->hdev; @@ -4674,6 +4702,19 @@ static struct usb_port *next_active_port(struct usb_hub *hub, int *port1) pm_runtime_barrier(&port_dev->dev); if (pm_runtime_active(&port_dev->dev)) return port_dev; + + /* handle out of spec hubs that continue to signal reset and + * link-state change events while the port is powered-off + */ + if (test_bit(*port1, hub->event_bits)) { + u16 stat, change; + int ret; + + ret = hub_port_status(hub, *port1, &stat, &change); + if (ret == 0) + hub_clear_misc_change(hub, *port1, stat, + change); + } put_port(port_dev, port1); } @@ -4835,32 +4876,7 @@ static void hub_events(void) "condition on port %d\n", i); } - if (portchange & USB_PORT_STAT_C_RESET) { - dev_dbg (hub_dev, - "reset change on port %d\n", - i); - usb_clear_port_feature(hdev, i, - USB_PORT_FEAT_C_RESET); - } - if ((portchange & USB_PORT_STAT_C_BH_RESET) && - hub_is_superspeed(hub->hdev)) { - dev_dbg(hub_dev, - "warm reset change on port %d\n", - i); - usb_clear_port_feature(hdev, i, - USB_PORT_FEAT_C_BH_PORT_RESET); - } - if (portchange & USB_PORT_STAT_C_LINK_STATE) { - usb_clear_port_feature(hub->hdev, i, - USB_PORT_FEAT_C_PORT_LINK_STATE); - } - if (portchange & USB_PORT_STAT_C_CONFIG_ERROR) { - dev_warn(hub_dev, - "config error on port %d\n", - i); - usb_clear_port_feature(hub->hdev, i, - USB_PORT_FEAT_C_PORT_CONFIG_ERROR); - } + hub_clear_misc_change(hub, i, portstatus, portchange); /* Warm reset a USB3 protocol port if it's in * SS.Inactive state. -- 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