aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/core/hub.c
diff options
context:
space:
mode:
authorDan Williams <dan.j.williams@intel.com>2014-05-20 21:09:36 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2014-05-27 20:25:37 -0400
commit7027df36e41836b11f01b0d30eee40c55df7d013 (patch)
tree569e9374988609c0d591b3de9c48fd8184498059 /drivers/usb/core/hub.c
parent7e73be227b1510a2ba1391185be7cc357e2226ef (diff)
usb: resume child device when port is powered on
Unconditionally wake up the child device when the power session is recovered. This addresses the following scenarios: 1/ The device may need a reset on power-session loss, without this change port power-on recovery exposes khubd to scenarios that usb_port_resume() is set to handle. Prior to port power control the only time a power session would be lost is during dpm_suspend of the hub. In that scenario usb_port_resume() is guaranteed to be called prior to khubd running for that port. With this change we wakeup the child device as soon as possible (prior to khubd running again for this port). Although khubd has facilities to wake a child device it will only do so if the portstatus / portchange indicates a suspend state. In the case of port power control we are not coming from a hub-port-suspend state. This implementation simply uses pm_request_resume() to wake the device and relies on the port_dev->status_lock to prevent any collisions between khubd and usb_port_resume(). 2/ This mechanism rate limits port power toggling. The minimum port power on/off period is now gated by the child device suspend/resume latency. Empirically this mitigates devices downgrading their connection on perceived instability of the host connection. This ratelimiting is really only relevant to port power control testing, but it is a nice side effect of closing the above race. Namely, the race of khubd for the given port running while a usb_port_resume() event is pending. 3/ Going forward we are finding that power-session recovery requires warm-resets (http://marc.info/?t=138659232900003&r=1&w=2). This mechanism allows for warm-resets to be requested at the same point in the resume path for hub dpm_suspend power session losses, or port rpm_suspend power session losses. 4/ If the device *was* disconnected the only time we'll know for sure is after a failed resume, so it's necessary for usb_port_runtime_resume() to expedite a usb_port_resume() to clean up the removed device. The reasoning for this is "least surprise" for the user. Turning on a port means that hotplug detection is again enabled for the port, it is surprising that devices that were removed while the port was off are not disconnected until they are attempted to be used. As a user "why would I try to use a device I removed from the system?" 1, 2, and 4 are not a problem in the system dpm_resume() case because, although the power-session is lost, khubd is frozen until after device resume. For the rpm_resume() case pm_request_resume() is used to request re-validation of the device, and if it happens to collide with a khubd run we rely on the port_dev->status_lock to synchronize those operations. Besides testing, the primary scenario where this mechanism is expected to be triggered is when the user changes the port power policy (control/pm_qos_no_poweroff, or power/control). Each time power is enabled want to revalidate the child device, where the revalidation is handled by usb_port_resume(). Given that this arranges for port_dev->child to be de-referenced in usb_port_runtime_resume() we need to make sure not to collide with usb_disconnect() that frees the usb_device. To this end we hold the port active with the "child_usage" reference across the disconnect event. Subsequently, the need to access hub->child_usage_bits lead to the creation of hub_disconnect_children() to remove any ambiguity of which "hub" is being acted on in usb_disconnect() (prompted-by sharp eyes from Alan). Cc: Rafael J. Wysocki <rjw@rjwysocki.net> Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Dan Williams <dan.j.williams@intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/core/hub.c')
-rw-r--r--drivers/usb/core/hub.c42
1 files changed, 29 insertions, 13 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index 28f5bbae35e0..6346fb2acbd7 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -2039,6 +2039,18 @@ static void hub_free_dev(struct usb_device *udev)
2039 hcd->driver->free_dev(hcd, udev); 2039 hcd->driver->free_dev(hcd, udev);
2040} 2040}
2041 2041
2042static void hub_disconnect_children(struct usb_device *udev)
2043{
2044 struct usb_hub *hub = usb_hub_to_struct_hub(udev);
2045 int i;
2046
2047 /* Free up all the children before we remove this device */
2048 for (i = 0; i < udev->maxchild; i++) {
2049 if (hub->ports[i]->child)
2050 usb_disconnect(&hub->ports[i]->child);
2051 }
2052}
2053
2042/** 2054/**
2043 * usb_disconnect - disconnect a device (usbcore-internal) 2055 * usb_disconnect - disconnect a device (usbcore-internal)
2044 * @pdev: pointer to device being disconnected 2056 * @pdev: pointer to device being disconnected
@@ -2057,9 +2069,10 @@ static void hub_free_dev(struct usb_device *udev)
2057 */ 2069 */
2058void usb_disconnect(struct usb_device **pdev) 2070void usb_disconnect(struct usb_device **pdev)
2059{ 2071{
2060 struct usb_device *udev = *pdev; 2072 struct usb_port *port_dev = NULL;
2061 struct usb_hub *hub = usb_hub_to_struct_hub(udev); 2073 struct usb_device *udev = *pdev;
2062 int i; 2074 struct usb_hub *hub;
2075 int port1;
2063 2076
2064 /* mark the device as inactive, so any further urb submissions for 2077 /* mark the device as inactive, so any further urb submissions for
2065 * this device (and any of its children) will fail immediately. 2078 * this device (and any of its children) will fail immediately.
@@ -2071,11 +2084,7 @@ void usb_disconnect(struct usb_device **pdev)
2071 2084
2072 usb_lock_device(udev); 2085 usb_lock_device(udev);
2073 2086
2074 /* Free up all the children before we remove this device */ 2087 hub_disconnect_children(udev);
2075 for (i = 0; i < udev->maxchild; i++) {
2076 if (hub->ports[i]->child)
2077 usb_disconnect(&hub->ports[i]->child);
2078 }
2079 2088
2080 /* deallocate hcd/hardware state ... nuking all pending urbs and 2089 /* deallocate hcd/hardware state ... nuking all pending urbs and
2081 * cleaning up all state associated with the current configuration 2090 * cleaning up all state associated with the current configuration
@@ -2086,15 +2095,19 @@ void usb_disconnect(struct usb_device **pdev)
2086 usb_hcd_synchronize_unlinks(udev); 2095 usb_hcd_synchronize_unlinks(udev);
2087 2096
2088 if (udev->parent) { 2097 if (udev->parent) {
2089 int port1 = udev->portnum; 2098 port1 = udev->portnum;
2090 struct usb_hub *hub = usb_hub_to_struct_hub(udev->parent); 2099 hub = usb_hub_to_struct_hub(udev->parent);
2091 struct usb_port *port_dev = hub->ports[port1 - 1]; 2100 port_dev = hub->ports[port1 - 1];
2092 2101
2093 sysfs_remove_link(&udev->dev.kobj, "port"); 2102 sysfs_remove_link(&udev->dev.kobj, "port");
2094 sysfs_remove_link(&port_dev->dev.kobj, "device"); 2103 sysfs_remove_link(&port_dev->dev.kobj, "device");
2095 2104
2096 if (test_and_clear_bit(port1, hub->child_usage_bits)) 2105 /*
2097 pm_runtime_put(&port_dev->dev); 2106 * As usb_port_runtime_resume() de-references udev, make
2107 * sure no resumes occur during removal
2108 */
2109 if (!test_and_set_bit(port1, hub->child_usage_bits))
2110 pm_runtime_get_sync(&port_dev->dev);
2098 } 2111 }
2099 2112
2100 usb_remove_ep_devs(&udev->ep0); 2113 usb_remove_ep_devs(&udev->ep0);
@@ -2116,6 +2129,9 @@ void usb_disconnect(struct usb_device **pdev)
2116 *pdev = NULL; 2129 *pdev = NULL;
2117 spin_unlock_irq(&device_state_lock); 2130 spin_unlock_irq(&device_state_lock);
2118 2131
2132 if (port_dev && test_and_clear_bit(port1, hub->child_usage_bits))
2133 pm_runtime_put(&port_dev->dev);
2134
2119 hub_free_dev(udev); 2135 hub_free_dev(udev);
2120 2136
2121 put_device(&udev->dev); 2137 put_device(&udev->dev);