diff options
author | Dan Williams <dan.j.williams@intel.com> | 2014-05-20 21:08:57 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2014-05-27 19:38:53 -0400 |
commit | 7ad3c47088f9faec463f5226e5e968a5c3b0e593 (patch) | |
tree | c0712608896ada6eef80671212b3d766d823618f /drivers/usb/core | |
parent | d5c3834e4af3acc4d7fc52faba2711c666655632 (diff) |
usb: block suspension of superspeed port while hispeed peer is active
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.
By default the latency between peer power-on events is 0. In order for
the device to not see usb2 active while usb3 is still powering up inject
the hub recommended power_on_good delay. In support of satisfying the
power_on_good delay outside of hub_power_on() refactor the places where
the delay is consumed to call a new hub_power_on_good_delay() helper.
Finally, because this introduces several new checks for whether a port
is_superspeed, cache that disctinction at port creation so that we don't
need to keep looking up the parent hub device.
Acked-by: Alan Stern <stern@rowland.harvard.edu>
[alan]: add a 'superspeed' flag to the port
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')
-rw-r--r-- | drivers/usb/core/hub.c | 22 | ||||
-rw-r--r-- | drivers/usb/core/hub.h | 15 | ||||
-rw-r--r-- | drivers/usb/core/port.c | 73 |
3 files changed, 93 insertions, 17 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 31a492a4a881..e492bca74425 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c | |||
@@ -36,11 +36,6 @@ | |||
36 | #define USB_VENDOR_GENESYS_LOGIC 0x05e3 | 36 | #define USB_VENDOR_GENESYS_LOGIC 0x05e3 |
37 | #define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND 0x01 | 37 | #define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND 0x01 |
38 | 38 | ||
39 | static inline int hub_is_superspeed(struct usb_device *hdev) | ||
40 | { | ||
41 | return (hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS); | ||
42 | } | ||
43 | |||
44 | /* Protect struct usb_device->state and ->children members | 39 | /* Protect struct usb_device->state and ->children members |
45 | * Note: Both are also protected by ->dev.sem, except that ->state can | 40 | * Note: Both are also protected by ->dev.sem, except that ->state can |
46 | * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */ | 41 | * change to USB_STATE_NOTATTACHED even when the semaphore isn't held. */ |
@@ -822,14 +817,9 @@ int usb_hub_clear_tt_buffer(struct urb *urb) | |||
822 | } | 817 | } |
823 | EXPORT_SYMBOL_GPL(usb_hub_clear_tt_buffer); | 818 | EXPORT_SYMBOL_GPL(usb_hub_clear_tt_buffer); |
824 | 819 | ||
825 | /* If do_delay is false, return the number of milliseconds the caller | 820 | static void hub_power_on(struct usb_hub *hub, bool do_delay) |
826 | * needs to delay. | ||
827 | */ | ||
828 | static unsigned hub_power_on(struct usb_hub *hub, bool do_delay) | ||
829 | { | 821 | { |
830 | int port1; | 822 | int port1; |
831 | unsigned pgood_delay = hub->descriptor->bPwrOn2PwrGood * 2; | ||
832 | unsigned delay; | ||
833 | 823 | ||
834 | /* Enable power on each port. Some hubs have reserved values | 824 | /* Enable power on each port. Some hubs have reserved values |
835 | * of LPSM (> 2) in their descriptors, even though they are | 825 | * of LPSM (> 2) in their descriptors, even though they are |
@@ -848,12 +838,8 @@ static unsigned hub_power_on(struct usb_hub *hub, bool do_delay) | |||
848 | else | 838 | else |
849 | usb_clear_port_feature(hub->hdev, port1, | 839 | usb_clear_port_feature(hub->hdev, port1, |
850 | USB_PORT_FEAT_POWER); | 840 | USB_PORT_FEAT_POWER); |
851 | |||
852 | /* Wait at least 100 msec for power to become stable */ | ||
853 | delay = max(pgood_delay, (unsigned) 100); | ||
854 | if (do_delay) | 841 | if (do_delay) |
855 | msleep(delay); | 842 | msleep(hub_power_on_good_delay(hub)); |
856 | return delay; | ||
857 | } | 843 | } |
858 | 844 | ||
859 | static int hub_hub_status(struct usb_hub *hub, | 845 | static int hub_hub_status(struct usb_hub *hub, |
@@ -1057,7 +1043,9 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type) | |||
1057 | * for HUB_POST_RESET, but it's easier not to. | 1043 | * for HUB_POST_RESET, but it's easier not to. |
1058 | */ | 1044 | */ |
1059 | if (type == HUB_INIT) { | 1045 | if (type == HUB_INIT) { |
1060 | delay = hub_power_on(hub, false); | 1046 | unsigned delay = hub_power_on_good_delay(hub); |
1047 | |||
1048 | hub_power_on(hub, false); | ||
1061 | INIT_DELAYED_WORK(&hub->init_work, hub_init_func2); | 1049 | INIT_DELAYED_WORK(&hub->init_work, hub_init_func2); |
1062 | queue_delayed_work(system_power_efficient_wq, | 1050 | queue_delayed_work(system_power_efficient_wq, |
1063 | &hub->init_work, | 1051 | &hub->init_work, |
diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h index 3ef1c2e435cc..906c355e0631 100644 --- a/drivers/usb/core/hub.h +++ b/drivers/usb/core/hub.h | |||
@@ -89,6 +89,7 @@ struct usb_hub { | |||
89 | * @connect_type: port's connect type | 89 | * @connect_type: port's connect type |
90 | * @location: opaque representation of platform connector location | 90 | * @location: opaque representation of platform connector location |
91 | * @portnum: port index num based one | 91 | * @portnum: port index num based one |
92 | * @is_superspeed cache super-speed status | ||
92 | */ | 93 | */ |
93 | struct usb_port { | 94 | struct usb_port { |
94 | struct usb_device *child; | 95 | struct usb_device *child; |
@@ -98,6 +99,7 @@ struct usb_port { | |||
98 | enum usb_port_connect_type connect_type; | 99 | enum usb_port_connect_type connect_type; |
99 | usb_port_location_t location; | 100 | usb_port_location_t location; |
100 | u8 portnum; | 101 | u8 portnum; |
102 | unsigned int is_superspeed:1; | ||
101 | }; | 103 | }; |
102 | 104 | ||
103 | #define to_usb_port(_dev) \ | 105 | #define to_usb_port(_dev) \ |
@@ -125,6 +127,19 @@ static inline bool hub_is_port_power_switchable(struct usb_hub *hub) | |||
125 | return (le16_to_cpu(hcs) & HUB_CHAR_LPSM) < HUB_CHAR_NO_LPSM; | 127 | return (le16_to_cpu(hcs) & HUB_CHAR_LPSM) < HUB_CHAR_NO_LPSM; |
126 | } | 128 | } |
127 | 129 | ||
130 | static inline int hub_is_superspeed(struct usb_device *hdev) | ||
131 | { | ||
132 | return hdev->descriptor.bDeviceProtocol == USB_HUB_PR_SS; | ||
133 | } | ||
134 | |||
135 | static inline unsigned hub_power_on_good_delay(struct usb_hub *hub) | ||
136 | { | ||
137 | unsigned delay = hub->descriptor->bPwrOn2PwrGood * 2; | ||
138 | |||
139 | /* Wait at least 100 msec for power to become stable */ | ||
140 | return max(delay, 100U); | ||
141 | } | ||
142 | |||
128 | static inline int hub_port_debounce_be_connected(struct usb_hub *hub, | 143 | static inline int hub_port_debounce_be_connected(struct usb_hub *hub, |
129 | int port1) | 144 | int port1) |
130 | { | 145 | { |
diff --git a/drivers/usb/core/port.c b/drivers/usb/core/port.c index 795778c71e31..827b0d38f73d 100644 --- a/drivers/usb/core/port.c +++ b/drivers/usb/core/port.c | |||
@@ -76,6 +76,7 @@ static int usb_port_runtime_resume(struct device *dev) | |||
76 | struct usb_device *hdev = to_usb_device(dev->parent->parent); | 76 | struct usb_device *hdev = to_usb_device(dev->parent->parent); |
77 | struct usb_interface *intf = to_usb_interface(dev->parent); | 77 | struct usb_interface *intf = to_usb_interface(dev->parent); |
78 | struct usb_hub *hub = usb_hub_to_struct_hub(hdev); | 78 | struct usb_hub *hub = usb_hub_to_struct_hub(hdev); |
79 | struct usb_port *peer = port_dev->peer; | ||
79 | int port1 = port_dev->portnum; | 80 | int port1 = port_dev->portnum; |
80 | int retval; | 81 | int retval; |
81 | 82 | ||
@@ -86,10 +87,18 @@ static int usb_port_runtime_resume(struct device *dev) | |||
86 | return 0; | 87 | return 0; |
87 | } | 88 | } |
88 | 89 | ||
90 | /* | ||
91 | * Power on our usb3 peer before this usb2 port to prevent a usb3 | ||
92 | * device from degrading to its usb2 connection | ||
93 | */ | ||
94 | if (!port_dev->is_superspeed && peer) | ||
95 | pm_runtime_get_sync(&peer->dev); | ||
96 | |||
89 | usb_autopm_get_interface(intf); | 97 | usb_autopm_get_interface(intf); |
90 | set_bit(port1, hub->busy_bits); | 98 | set_bit(port1, hub->busy_bits); |
91 | 99 | ||
92 | retval = usb_hub_set_port_power(hdev, hub, port1, true); | 100 | retval = usb_hub_set_port_power(hdev, hub, port1, true); |
101 | msleep(hub_power_on_good_delay(hub)); | ||
93 | if (port_dev->child && !retval) { | 102 | if (port_dev->child && !retval) { |
94 | /* | 103 | /* |
95 | * Attempt to wait for usb hub port to be reconnected in order | 104 | * Attempt to wait for usb hub port to be reconnected in order |
@@ -107,6 +116,7 @@ static int usb_port_runtime_resume(struct device *dev) | |||
107 | 116 | ||
108 | clear_bit(port1, hub->busy_bits); | 117 | clear_bit(port1, hub->busy_bits); |
109 | usb_autopm_put_interface(intf); | 118 | usb_autopm_put_interface(intf); |
119 | |||
110 | return retval; | 120 | return retval; |
111 | } | 121 | } |
112 | 122 | ||
@@ -116,6 +126,7 @@ static int usb_port_runtime_suspend(struct device *dev) | |||
116 | struct usb_device *hdev = to_usb_device(dev->parent->parent); | 126 | struct usb_device *hdev = to_usb_device(dev->parent->parent); |
117 | struct usb_interface *intf = to_usb_interface(dev->parent); | 127 | struct usb_interface *intf = to_usb_interface(dev->parent); |
118 | struct usb_hub *hub = usb_hub_to_struct_hub(hdev); | 128 | struct usb_hub *hub = usb_hub_to_struct_hub(hdev); |
129 | struct usb_port *peer = port_dev->peer; | ||
119 | int port1 = port_dev->portnum; | 130 | int port1 = port_dev->portnum; |
120 | int retval; | 131 | int retval; |
121 | 132 | ||
@@ -135,6 +146,15 @@ static int usb_port_runtime_suspend(struct device *dev) | |||
135 | usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); | 146 | usb_clear_port_feature(hdev, port1, USB_PORT_FEAT_C_ENABLE); |
136 | clear_bit(port1, hub->busy_bits); | 147 | clear_bit(port1, hub->busy_bits); |
137 | usb_autopm_put_interface(intf); | 148 | usb_autopm_put_interface(intf); |
149 | |||
150 | /* | ||
151 | * Our peer usb3 port may now be able to suspend, so | ||
152 | * asynchronously queue a suspend request to observe that this | ||
153 | * usb2 port is now off. | ||
154 | */ | ||
155 | if (!port_dev->is_superspeed && peer) | ||
156 | pm_runtime_put(&peer->dev); | ||
157 | |||
138 | return retval; | 158 | return retval; |
139 | } | 159 | } |
140 | #endif | 160 | #endif |
@@ -159,6 +179,7 @@ static struct device_driver usb_port_driver = { | |||
159 | 179 | ||
160 | static int link_peers(struct usb_port *left, struct usb_port *right) | 180 | static int link_peers(struct usb_port *left, struct usb_port *right) |
161 | { | 181 | { |
182 | struct usb_port *ss_port, *hs_port; | ||
162 | int rc; | 183 | int rc; |
163 | 184 | ||
164 | if (left->peer == right && right->peer == left) | 185 | if (left->peer == right && right->peer == left) |
@@ -184,9 +205,36 @@ static int link_peers(struct usb_port *left, struct usb_port *right) | |||
184 | return rc; | 205 | return rc; |
185 | } | 206 | } |
186 | 207 | ||
208 | /* | ||
209 | * We need to wake the HiSpeed port to make sure we don't race | ||
210 | * setting ->peer with usb_port_runtime_suspend(). Otherwise we | ||
211 | * may miss a suspend event for the SuperSpeed port. | ||
212 | */ | ||
213 | if (left->is_superspeed) { | ||
214 | ss_port = left; | ||
215 | WARN_ON(right->is_superspeed); | ||
216 | hs_port = right; | ||
217 | } else { | ||
218 | ss_port = right; | ||
219 | WARN_ON(!right->is_superspeed); | ||
220 | hs_port = left; | ||
221 | } | ||
222 | pm_runtime_get_sync(&hs_port->dev); | ||
223 | |||
187 | left->peer = right; | 224 | left->peer = right; |
188 | right->peer = left; | 225 | right->peer = left; |
189 | 226 | ||
227 | /* | ||
228 | * The SuperSpeed reference is dropped when the HiSpeed port in | ||
229 | * this relationship suspends, i.e. when it is safe to allow a | ||
230 | * SuperSpeed connection to drop since there is no risk of a | ||
231 | * device degrading to its powered-off HiSpeed connection. | ||
232 | * | ||
233 | * Also, drop the HiSpeed ref taken above. | ||
234 | */ | ||
235 | pm_runtime_get_sync(&ss_port->dev); | ||
236 | pm_runtime_put(&hs_port->dev); | ||
237 | |||
190 | return 0; | 238 | return 0; |
191 | } | 239 | } |
192 | 240 | ||
@@ -206,14 +254,37 @@ static void link_peers_report(struct usb_port *left, struct usb_port *right) | |||
206 | 254 | ||
207 | static void unlink_peers(struct usb_port *left, struct usb_port *right) | 255 | static void unlink_peers(struct usb_port *left, struct usb_port *right) |
208 | { | 256 | { |
257 | struct usb_port *ss_port, *hs_port; | ||
258 | |||
209 | WARN(right->peer != left || left->peer != right, | 259 | WARN(right->peer != left || left->peer != right, |
210 | "%s and %s are not peers?\n", | 260 | "%s and %s are not peers?\n", |
211 | dev_name(&left->dev), dev_name(&right->dev)); | 261 | dev_name(&left->dev), dev_name(&right->dev)); |
212 | 262 | ||
263 | /* | ||
264 | * We wake the HiSpeed port to make sure we don't race its | ||
265 | * usb_port_runtime_resume() event which takes a SuperSpeed ref | ||
266 | * when ->peer is !NULL. | ||
267 | */ | ||
268 | if (left->is_superspeed) { | ||
269 | ss_port = left; | ||
270 | hs_port = right; | ||
271 | } else { | ||
272 | ss_port = right; | ||
273 | hs_port = left; | ||
274 | } | ||
275 | |||
276 | pm_runtime_get_sync(&hs_port->dev); | ||
277 | |||
213 | sysfs_remove_link(&left->dev.kobj, "peer"); | 278 | sysfs_remove_link(&left->dev.kobj, "peer"); |
214 | right->peer = NULL; | 279 | right->peer = NULL; |
215 | sysfs_remove_link(&right->dev.kobj, "peer"); | 280 | sysfs_remove_link(&right->dev.kobj, "peer"); |
216 | left->peer = NULL; | 281 | left->peer = NULL; |
282 | |||
283 | /* Drop the SuperSpeed ref held on behalf of the active HiSpeed port */ | ||
284 | pm_runtime_put(&ss_port->dev); | ||
285 | |||
286 | /* Drop the ref taken above */ | ||
287 | pm_runtime_put(&hs_port->dev); | ||
217 | } | 288 | } |
218 | 289 | ||
219 | /* | 290 | /* |
@@ -325,6 +396,8 @@ int usb_hub_create_port_device(struct usb_hub *hub, int port1) | |||
325 | port_dev->dev.groups = port_dev_group; | 396 | port_dev->dev.groups = port_dev_group; |
326 | port_dev->dev.type = &usb_port_device_type; | 397 | port_dev->dev.type = &usb_port_device_type; |
327 | port_dev->dev.driver = &usb_port_driver; | 398 | port_dev->dev.driver = &usb_port_driver; |
399 | if (hub_is_superspeed(hub->hdev)) | ||
400 | port_dev->is_superspeed = 1; | ||
328 | dev_set_name(&port_dev->dev, "%s-port%d", dev_name(&hub->hdev->dev), | 401 | dev_set_name(&port_dev->dev, "%s-port%d", dev_name(&hub->hdev->dev), |
329 | port1); | 402 | port1); |
330 | retval = device_register(&port_dev->dev); | 403 | retval = device_register(&port_dev->dev); |