aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/usb/core
diff options
context:
space:
mode:
authorMing Lei <ming.lei@canonical.com>2012-10-23 23:59:24 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2012-10-25 14:45:32 -0400
commite6f30deafe61179df048ac9040fe2bdf73a996c3 (patch)
tree87522ed876e5ff6d3c8cc767d0d488af847e60d6 /drivers/usb/core
parentb717727ef25d4b73f73e3666341c07a034f908a6 (diff)
USB: check port changes before hub runtime suspend for some bug device
The hub status endpoint has a long 'bInterval', which is 255ms for FS/LS device and 256ms for HS device according to USB 2.0 spec, so the device connection change may be reported later more than 255ms via status pipe. The connection change in hub may have been happened already on the downstream ports, but no status URB completes when it is killed in hub_suspend(auto), so the connection change may be missed by some buggy hub devices, which won't generate remote wakeup signal after their remote wakeup is enabled and they are put into suspend state. The problem can be observed at least on the below Genesys Logic, Inc. hub devices: 0x05e3,0x0606 0x05e3,0x0608 In theory, there is no way to fix the problem completely, but we can make it less likely to occur by this patch. This patch introduces one quirk of HUB_QUIRK_CHECK_PORTS_AUTOSUSPEND to check ports' change during hub_suspend(auto) for the buggy devices. If ports' change is found, terminate the auto suspend and return to working state. So for the buggy hubs, if the connection change happend before the ports' check, it can be handled correctly. If it happens between the ports' check and enabling remote wakeup/entering suspend, it will be missed. Considered the interval is quite short, it is very less likely to happen during the window. Acked-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Ming Lei <ming.lei@canonical.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/usb/core')
-rw-r--r--drivers/usb/core/hub.c38
1 files changed, 38 insertions, 0 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c
index e729e94cb751..ff86355d0dfc 100644
--- a/drivers/usb/core/hub.c
+++ b/drivers/usb/core/hub.c
@@ -39,6 +39,9 @@
39#endif 39#endif
40#endif 40#endif
41 41
42#define USB_VENDOR_GENESYS_LOGIC 0x05e3
43#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND 0x01
44
42struct usb_port { 45struct usb_port {
43 struct usb_device *child; 46 struct usb_device *child;
44 struct device dev; 47 struct device dev;
@@ -86,6 +89,8 @@ struct usb_hub {
86 unsigned quiescing:1; 89 unsigned quiescing:1;
87 unsigned disconnected:1; 90 unsigned disconnected:1;
88 91
92 unsigned quirk_check_port_auto_suspend:1;
93
89 unsigned has_indicators:1; 94 unsigned has_indicators:1;
90 u8 indicator[USB_MAXCHILDREN]; 95 u8 indicator[USB_MAXCHILDREN];
91 struct delayed_work leds; 96 struct delayed_work leds;
@@ -1667,6 +1672,9 @@ descriptor_error:
1667 if (hdev->speed == USB_SPEED_HIGH) 1672 if (hdev->speed == USB_SPEED_HIGH)
1668 highspeed_hubs++; 1673 highspeed_hubs++;
1669 1674
1675 if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND)
1676 hub->quirk_check_port_auto_suspend = 1;
1677
1670 if (hub_configure(hub, endpoint) >= 0) 1678 if (hub_configure(hub, endpoint) >= 0)
1671 return 0; 1679 return 0;
1672 1680
@@ -3125,6 +3133,21 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)
3125 3133
3126#endif 3134#endif
3127 3135
3136static int check_ports_changed(struct usb_hub *hub)
3137{
3138 int port1;
3139
3140 for (port1 = 1; port1 <= hub->hdev->maxchild; ++port1) {
3141 u16 portstatus, portchange;
3142 int status;
3143
3144 status = hub_port_status(hub, port1, &portstatus, &portchange);
3145 if (!status && portchange)
3146 return 1;
3147 }
3148 return 0;
3149}
3150
3128static int hub_suspend(struct usb_interface *intf, pm_message_t msg) 3151static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
3129{ 3152{
3130 struct usb_hub *hub = usb_get_intfdata (intf); 3153 struct usb_hub *hub = usb_get_intfdata (intf);
@@ -3143,6 +3166,16 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
3143 return -EBUSY; 3166 return -EBUSY;
3144 } 3167 }
3145 } 3168 }
3169
3170 if (hdev->do_remote_wakeup && hub->quirk_check_port_auto_suspend) {
3171 /* check if there are changes pending on hub ports */
3172 if (check_ports_changed(hub)) {
3173 if (PMSG_IS_AUTO(msg))
3174 return -EBUSY;
3175 pm_wakeup_event(&hdev->dev, 2000);
3176 }
3177 }
3178
3146 if (hub_is_superspeed(hdev) && hdev->do_remote_wakeup) { 3179 if (hub_is_superspeed(hdev) && hdev->do_remote_wakeup) {
3147 /* Enable hub to send remote wakeup for all ports. */ 3180 /* Enable hub to send remote wakeup for all ports. */
3148 for (port1 = 1; port1 <= hdev->maxchild; port1++) { 3181 for (port1 = 1; port1 <= hdev->maxchild; port1++) {
@@ -4647,6 +4680,11 @@ static int hub_thread(void *__unused)
4647} 4680}
4648 4681
4649static const struct usb_device_id hub_id_table[] = { 4682static const struct usb_device_id hub_id_table[] = {
4683 { .match_flags = USB_DEVICE_ID_MATCH_VENDOR
4684 | USB_DEVICE_ID_MATCH_INT_CLASS,
4685 .idVendor = USB_VENDOR_GENESYS_LOGIC,
4686 .bInterfaceClass = USB_CLASS_HUB,
4687 .driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND},
4650 { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS, 4688 { .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
4651 .bDeviceClass = USB_CLASS_HUB}, 4689 .bDeviceClass = USB_CLASS_HUB},
4652 { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, 4690 { .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,