The reset_resume implementation is in place to recover lost power sessions. Testing has already shown that simply waiting for USB_PORT_STAT_C_CONNECTION is not sufficient for recovering a powered down port. Replace recovery actions in usb_port_runtime_resume() with a deferral to recovery via usb_port_resume(). >From the point the port is powered down to the point that the port is recovered we do not want khubd or hub_activate() to take action on the disabled port. Introduce hub->poweroff_bits to communicate that the port is not to be serviced until it has been through recovery. The existing ->power_is_on flag gates hub_activate() from setting change bits. Placing the evalaution of the power state there is insufficient for guaranteeing that khubd does not receive a connection change event. Also, hub_activate() in this case is only running on hub_resume() we want to manage change events as individual ports resume. ->power_is_on will be removed in a subsequent patch. Signed-off-by: Dan Williams <dan.j.williams@xxxxxxxxx> --- drivers/usb/core/hub.c | 22 +++++++++++++++++----- drivers/usb/core/hub.h | 16 +--------------- drivers/usb/core/port.c | 29 +++++++++-------------------- 3 files changed, 27 insertions(+), 40 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 06cec635e703..6f2cf4672942 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1092,6 +1092,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) struct usb_device *udev = hub->ports[port1 - 1]->child; u16 portstatus, portchange; + if (test_bit(port1, hub->poweroff_bits)) + continue; + portstatus = portchange = 0; status = hub_port_status(hub, port1, &portstatus, &portchange); if (udev || (portstatus & USB_PORT_STAT_CONNECTION)) @@ -2783,8 +2786,14 @@ static int check_port_resume_type(struct usb_device *udev, struct usb_hub *hub, int port1, int status, unsigned portchange, unsigned portstatus) { + /* port power recovery, proceed to resetting the device */ + if (test_bit(port1, hub->poweroff_bits)) { + status = 0; + udev->reset_resume = 1; + } + /* Is the device still present? */ - if (status || port_is_suspended(hub, portstatus) || + else if (status || port_is_suspended(hub, portstatus) || !port_is_power_on(hub, portstatus) || !(portstatus & USB_PORT_STAT_CONNECTION)) { if (status >= 0) @@ -3871,7 +3880,7 @@ EXPORT_SYMBOL_GPL(usb_enable_ltm); * every 25ms for transient disconnects. When the port status has been * unchanged for 100ms it returns the port status. */ -int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected) +int hub_port_debounce(struct usb_hub *hub, int port1) { int ret; int total_time, stable_time = 0; @@ -3885,8 +3894,7 @@ int hub_port_debounce(struct usb_hub *hub, int port1, bool must_be_connected) if (!(portchange & USB_PORT_STAT_C_CONNECTION) && (portstatus & USB_PORT_STAT_CONNECTION) == connection) { - if (!must_be_connected || - (connection == USB_PORT_STAT_CONNECTION)) + if (connection == USB_PORT_STAT_CONNECTION) stable_time += HUB_DEBOUNCE_STEP; if (stable_time >= HUB_DEBOUNCE_STABLE) break; @@ -4437,7 +4445,7 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, if (portchange & (USB_PORT_STAT_C_CONNECTION | USB_PORT_STAT_C_ENABLE)) { - status = hub_port_debounce_be_stable(hub, port1); + status = hub_port_debounce(hub, port1); if (status < 0) { if (status != -ENODEV && printk_ratelimit()) dev_err(hub_dev, "connect-debounce failed, " @@ -4749,6 +4757,9 @@ static void hub_events(void) connect_change = 1; } + if (test_bit(i, hub->poweroff_bits)) + continue; + if (portchange & USB_PORT_STAT_C_ENABLE) { if (!connect_change) dev_dbg (hub_dev, @@ -5155,6 +5166,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev) if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV) break; } + clear_bit(port1, parent_hub->poweroff_bits); clear_bit(port1, parent_hub->busy_bits); if (ret < 0) diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 4e4790dea343..0b9bde67ff8b 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h @@ -47,6 +47,7 @@ struct usb_hub { status change */ unsigned long busy_bits[1]; /* ports being reset or resumed */ + unsigned long poweroff_bits[1]; /* ports logically off */ unsigned long removed_bits[1]; /* ports with a "removed" device present */ unsigned long wakeup_bits[1]; /* ports that have signaled @@ -106,20 +107,5 @@ extern void usb_hub_remove_port_device(struct usb_hub *hub, extern int usb_hub_set_port_power(struct usb_device *hdev, struct usb_hub *hub, int port1, bool set); extern struct usb_hub *usb_hub_to_struct_hub(struct usb_device *hdev); -extern int hub_port_debounce(struct usb_hub *hub, int port1, - bool must_be_connected); extern int usb_clear_port_feature(struct usb_device *hdev, int port1, int feature); - -static inline int hub_port_debounce_be_connected(struct usb_hub *hub, - int port1) -{ - return hub_port_debounce(hub, port1, true); -} - -static inline int hub_port_debounce_be_stable(struct usb_hub *hub, - int port1) -{ - return hub_port_debounce(hub, port1, false); -} - diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 51542f852393..0ecff7d85739 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c @@ -83,26 +83,13 @@ static int usb_port_runtime_resume(struct device *dev) return -EINVAL; usb_autopm_get_interface(intf); - set_bit(port1, hub->busy_bits); - retval = usb_hub_set_port_power(hdev, hub, port1, true); - if (port_dev->child && !retval) { - /* - * Attempt to wait for usb hub port to be reconnected in order - * to make the resume procedure successful. The device may have - * disconnected while the port was powered off, so ignore the - * return status. - */ - retval = hub_port_debounce_be_connected(hub, port1); - if (retval < 0) - dev_dbg(&port_dev->dev, "can't get reconnection after setting port power on, status %d\n", - retval); - usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); - retval = 0; - } - - clear_bit(port1, hub->busy_bits); usb_autopm_put_interface(intf); + + /* no child? we're done recovering this port */ + if (!port_dev->child) + clear_bit(port1, hub->poweroff_bits); + return retval; } @@ -122,13 +109,15 @@ static int usb_port_runtime_suspend(struct device *dev) == PM_QOS_FLAGS_ALL) return -EAGAIN; + set_bit(port1, hub->poweroff_bits); usb_autopm_get_interface(intf); - set_bit(port1, hub->busy_bits); retval = usb_hub_set_port_power(hdev, hub, port1, false); + if (retval) + clear_bit(port1, hub->poweroff_bits); usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_CONNECTION); usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); - clear_bit(port1, hub->busy_bits); usb_autopm_put_interface(intf); + 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