| From: Keiya Nobuta <nobuta.keiya@fujitsu.com> |
| Date: Thu, 9 Jan 2020 14:14:48 +0900 |
| Subject: usb: core: hub: Improved device recognition on remote wakeup |
| |
| commit 9c06ac4c83df6d6fbdbf7488fbad822b4002ba19 upstream. |
| |
| If hub_activate() is called before D+ has stabilized after remote |
| wakeup, the following situation might occur: |
| |
| __ ___________________ |
| / \ / |
| D+ __/ \__/ |
| |
| Hub _______________________________ |
| | ^ ^ ^ |
| | | | | |
| Host _____v__|___|___________|______ |
| | | | | |
| | | | \-- Interrupt Transfer (*3) |
| | | \-- ClearPortFeature (*2) |
| | \-- GetPortStatus (*1) |
| \-- Host detects remote wakeup |
| |
| - D+ goes high, Host starts running by remote wakeup |
| - D+ is not stable, goes low |
| - Host requests GetPortStatus at (*1) and gets the following hub status: |
| - Current Connect Status bit is 0 |
| - Connect Status Change bit is 1 |
| - D+ stabilizes, goes high |
| - Host requests ClearPortFeature and thus Connect Status Change bit is |
| cleared at (*2) |
| - After waiting 100 ms, Host starts the Interrupt Transfer at (*3) |
| - Since the Connect Status Change bit is 0, Hub returns NAK. |
| |
| In this case, port_event() is not called in hub_event() and Host cannot |
| recognize device. To solve this issue, flag change_bits even if only |
| Connect Status Change bit is 1 when got in the first GetPortStatus. |
| |
| This issue occurs rarely because it only if D+ changes during a very |
| short time between GetPortStatus and ClearPortFeature. However, it is |
| fatal if it occurs in embedded system. |
| |
| Signed-off-by: Keiya Nobuta <nobuta.keiya@fujitsu.com> |
| Acked-by: Alan Stern <stern@rowland.harvard.edu> |
| Link: https://lore.kernel.org/r/20200109051448.28150-1-nobuta.keiya@fujitsu.com |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Signed-off-by: Ben Hutchings <ben@decadent.org.uk> |
| --- |
| drivers/usb/core/hub.c | 1 + |
| 1 file changed, 1 insertion(+) |
| |
| --- a/drivers/usb/core/hub.c |
| +++ b/drivers/usb/core/hub.c |
| @@ -1125,6 +1125,7 @@ static void hub_activate(struct usb_hub |
| * PORT_OVER_CURRENT is not. So check for any of them. |
| */ |
| if (udev || (portstatus & USB_PORT_STAT_CONNECTION) || |
| + (portchange & USB_PORT_STAT_C_CONNECTION) || |
| (portstatus & USB_PORT_STAT_OVERCURRENT) || |
| (portchange & USB_PORT_STAT_C_OVERCURRENT)) |
| set_bit(port1, hub->change_bits); |