If a port is powered-off, or in the process of being powered-off, prevent khubd from operating on it. Otherwise, the following sequence of events leading to an unintended disconnect may occur: Events: (0) <set pm_qos_no_poweroff to '0' for port1> (1) hub 2-2:1.0: hub_resume (2) hub 2-2:1.0: port 1: status 0301 change 0000 (3) hub 2-2:1.0: state 7 ports 4 chg 0002 evt 0000 (4) hub 2-2:1.0: port 1, power off status 0000, change 0000, 12 Mb/s (5) usb 2-2.1: USB disconnect, device number 5 Description: (1) hub is resumed before sending a ClearPortFeature request (2) hub_activate() notices the port is connected and sets hub->change_bits for the port (3) hub_events() starts, but at the same time the port suspends (4) hub_connect_change() sees the disabled port and triggers disconnect Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx> --- drivers/usb/core/hub.c | 55 ++++++++++++++++++++++++++++-------------------- 1 files changed, 32 insertions(+), 23 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 8da2aa706005..0217299a2402 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -4728,32 +4728,41 @@ static void port_event(struct usb_hub *hub, int port1) USB_PORT_FEAT_C_PORT_CONFIG_ERROR); } - if (hub_handle_remote_wakeup(hub, port1, portstatus, portchange)) - connect_change = 1; - - /* Warm reset a USB3 protocol port if it's in - * SS.Inactive state. - */ - if (hub_port_warm_reset_required(hub, portstatus)) { - int status; + /* take port actions that require the port to be powered on */ + pm_runtime_get_noresume(&port_dev->dev); + pm_runtime_barrier(&port_dev->dev); + if (pm_runtime_active(&port_dev->dev)) { + if (hub_handle_remote_wakeup(hub, port1, + portstatus, portchange)) + connect_change = 1; - dev_dbg(hub_dev, "port%d: do warm reset\n", port1); - if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) - || udev->state == USB_STATE_NOTATTACHED) { - status = hub_port_reset(hub, port1, NULL, - HUB_BH_RESET_TIME, true); - if (status < 0) - hub_port_disable(hub, port1, 1); - } else { - usb_lock_device(udev); - status = usb_reset_device(udev); - usb_unlock_device(udev); - connect_change = 0; + /* Warm reset a USB3 protocol port if it's in + * SS.Inactive state. + */ + if (hub_port_warm_reset_required(hub, portstatus)) { + int status; + + dev_dbg(hub_dev, "port%d: do warm reset\n", port1); + if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) + || udev->state == USB_STATE_NOTATTACHED) { + status = hub_port_reset(hub, port1, NULL, + HUB_BH_RESET_TIME, + true); + if (status < 0) + hub_port_disable(hub, port1, 1); + } else { + usb_lock_device(udev); + status = usb_reset_device(udev); + usb_unlock_device(udev); + connect_change = 0; + } } - } - if (connect_change) - hub_port_connect_change(hub, port1, portstatus, portchange); + if (connect_change) + hub_port_connect_change(hub, port1, + portstatus, portchange); + } + pm_runtime_put_sync(&port_dev->dev); } -- 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