Skip to content

Commit

Permalink
USB: check port changes before hub runtime suspend for some bug device
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Ming Lei authored and Greg Kroah-Hartman committed Oct 25, 2012
1 parent b717727 commit e6f30de
Showing 1 changed file with 38 additions and 0 deletions.
38 changes: 38 additions & 0 deletions drivers/usb/core/hub.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
#endif
#endif

#define USB_VENDOR_GENESYS_LOGIC 0x05e3
#define HUB_QUIRK_CHECK_PORT_AUTOSUSPEND 0x01

struct usb_port {
struct usb_device *child;
struct device dev;
Expand Down Expand Up @@ -86,6 +89,8 @@ struct usb_hub {
unsigned quiescing:1;
unsigned disconnected:1;

unsigned quirk_check_port_auto_suspend:1;

unsigned has_indicators:1;
u8 indicator[USB_MAXCHILDREN];
struct delayed_work leds;
Expand Down Expand Up @@ -1667,6 +1672,9 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id)
if (hdev->speed == USB_SPEED_HIGH)
highspeed_hubs++;

if (id->driver_info & HUB_QUIRK_CHECK_PORT_AUTOSUSPEND)
hub->quirk_check_port_auto_suspend = 1;

if (hub_configure(hub, endpoint) >= 0)
return 0;

Expand Down Expand Up @@ -3125,6 +3133,21 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)

#endif

static int check_ports_changed(struct usb_hub *hub)
{
int port1;

for (port1 = 1; port1 <= hub->hdev->maxchild; ++port1) {
u16 portstatus, portchange;
int status;

status = hub_port_status(hub, port1, &portstatus, &portchange);
if (!status && portchange)
return 1;
}
return 0;
}

static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
{
struct usb_hub *hub = usb_get_intfdata (intf);
Expand All @@ -3143,6 +3166,16 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
return -EBUSY;
}
}

if (hdev->do_remote_wakeup && hub->quirk_check_port_auto_suspend) {
/* check if there are changes pending on hub ports */
if (check_ports_changed(hub)) {
if (PMSG_IS_AUTO(msg))
return -EBUSY;
pm_wakeup_event(&hdev->dev, 2000);
}
}

if (hub_is_superspeed(hdev) && hdev->do_remote_wakeup) {
/* Enable hub to send remote wakeup for all ports. */
for (port1 = 1; port1 <= hdev->maxchild; port1++) {
Expand Down Expand Up @@ -4647,6 +4680,11 @@ static int hub_thread(void *__unused)
}

static const struct usb_device_id hub_id_table[] = {
{ .match_flags = USB_DEVICE_ID_MATCH_VENDOR
| USB_DEVICE_ID_MATCH_INT_CLASS,
.idVendor = USB_VENDOR_GENESYS_LOGIC,
.bInterfaceClass = USB_CLASS_HUB,
.driver_info = HUB_QUIRK_CHECK_PORT_AUTOSUSPEND},
{ .match_flags = USB_DEVICE_ID_MATCH_DEV_CLASS,
.bDeviceClass = USB_CLASS_HUB},
{ .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS,
Expand Down

0 comments on commit e6f30de

Please sign in to comment.