diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
| -rw-r--r-- | drivers/usb/core/hub.c | 61 | 
1 files changed, 45 insertions, 16 deletions
| diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 86658a81d284..47a1c8bddf86 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -1110,7 +1110,10 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)  		} else {  			hub_power_on(hub, true);  		} -	} +	/* Give some time on remote wakeup to let links to transit to U0 */ +	} else if (hub_is_superspeed(hub->hdev)) +		msleep(20); +   init2:  	/* @@ -1225,7 +1228,7 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)  			 */  			if (portchange || (hub_is_superspeed(hub->hdev) &&  						port_resumed)) -				set_bit(port1, hub->change_bits); +				set_bit(port1, hub->event_bits);  		} else if (udev->persist_enabled) {  #ifdef CONFIG_PM @@ -2777,6 +2780,8 @@ static unsigned hub_is_wusb(struct usb_hub *hub)  #define PORT_INIT_TRIES		4  #endif	/* CONFIG_USB_FEW_INIT_RETRIES */ +#define DETECT_DISCONNECT_TRIES 5 +  #define HUB_ROOT_RESET_TIME	60	/* times are in msec */  #define HUB_SHORT_RESET_TIME	10  #define HUB_BH_RESET_TIME	50 @@ -3570,7 +3575,7 @@ static int finish_port_resume(struct usb_device *udev)   * This routine should only be called when persist is enabled.   */  static int wait_for_connected(struct usb_device *udev, -		struct usb_hub *hub, int *port1, +		struct usb_hub *hub, int port1,  		u16 *portchange, u16 *portstatus)  {  	int status = 0, delay_ms = 0; @@ -3584,7 +3589,7 @@ static int wait_for_connected(struct usb_device *udev,  		}  		msleep(20);  		delay_ms += 20; -		status = hub_port_status(hub, *port1, portstatus, portchange); +		status = hub_port_status(hub, port1, portstatus, portchange);  	}  	dev_dbg(&udev->dev, "Waited %dms for CONNECT\n", delay_ms);  	return status; @@ -3690,7 +3695,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)  	}  	if (udev->persist_enabled) -		status = wait_for_connected(udev, hub, &port1, &portchange, +		status = wait_for_connected(udev, hub, port1, &portchange,  				&portstatus);  	status = check_port_resume_type(udev, @@ -4700,8 +4705,6 @@ hub_port_init(struct usb_hub *hub, struct usb_device *udev, int port1,  	if (oldspeed == USB_SPEED_LOW)  		delay = HUB_LONG_RESET_TIME; -	mutex_lock(hcd->address0_mutex); -  	/* Reset the device; full speed may morph to high speed */  	/* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */  	retval = hub_port_reset(hub, port1, udev, delay, false); @@ -5016,7 +5019,6 @@ fail:  		hub_port_disable(hub, port1, 0);  		update_devnum(udev, devnum);	/* for disconnect processing */  	} -	mutex_unlock(hcd->address0_mutex);  	return retval;  } @@ -5191,6 +5193,7 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,  	struct usb_port *port_dev = hub->ports[port1 - 1];  	struct usb_device *udev = port_dev->child;  	static int unreliable_port = -1; +	bool retry_locked;  	/* Disconnect any existing devices under this port */  	if (udev) { @@ -5246,8 +5249,11 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,  		unit_load = 100;  	status = 0; -	for (i = 0; i < PORT_INIT_TRIES; i++) { +	for (i = 0; i < PORT_INIT_TRIES; i++) { +		usb_lock_port(port_dev); +		mutex_lock(hcd->address0_mutex); +		retry_locked = true;  		/* reallocate for each attempt, since references  		 * to the previous one can escape in various ways  		 */ @@ -5255,6 +5261,8 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,  		if (!udev) {  			dev_err(&port_dev->dev,  					"couldn't allocate usb_device\n"); +			mutex_unlock(hcd->address0_mutex); +			usb_unlock_port(port_dev);  			goto done;  		} @@ -5276,12 +5284,14 @@ static void hub_port_connect(struct usb_hub *hub, int port1, u16 portstatus,  		}  		/* reset (non-USB 3.0 devices) and get descriptor */ -		usb_lock_port(port_dev);  		status = hub_port_init(hub, udev, port1, i); -		usb_unlock_port(port_dev);  		if (status < 0)  			goto loop; +		mutex_unlock(hcd->address0_mutex); +		usb_unlock_port(port_dev); +		retry_locked = false; +  		if (udev->quirks & USB_QUIRK_DELAY_INIT)  			msleep(2000); @@ -5374,6 +5384,10 @@ loop:  		usb_ep0_reinit(udev);  		release_devnum(udev);  		hub_free_dev(udev); +		if (retry_locked) { +			mutex_unlock(hcd->address0_mutex); +			usb_unlock_port(port_dev); +		}  		usb_put_dev(udev);  		if ((status == -ENOTCONN) || (status == -ENOTSUPP))  			break; @@ -5534,6 +5548,7 @@ static void port_event(struct usb_hub *hub, int port1)  	struct usb_device *udev = port_dev->child;  	struct usb_device *hdev = hub->hdev;  	u16 portstatus, portchange; +	int i = 0;  	connect_change = test_bit(port1, hub->change_bits);  	clear_bit(port1, hub->event_bits); @@ -5610,17 +5625,27 @@ static void port_event(struct usb_hub *hub, int port1)  		connect_change = 1;  	/* -	 * Warm reset a USB3 protocol port if it's in -	 * SS.Inactive state. +	 * Avoid trying to recover a USB3 SS.Inactive port with a warm reset if +	 * the device was disconnected. A 12ms disconnect detect timer in +	 * SS.Inactive state transitions the port to RxDetect automatically. +	 * SS.Inactive link error state is common during device disconnect.  	 */ -	if (hub_port_warm_reset_required(hub, port1, portstatus)) { -		dev_dbg(&port_dev->dev, "do warm reset\n"); -		if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION) +	while (hub_port_warm_reset_required(hub, port1, portstatus)) { +		if ((i++ < DETECT_DISCONNECT_TRIES) && udev) { +			u16 unused; + +			msleep(20); +			hub_port_status(hub, port1, &portstatus, &unused); +			dev_dbg(&port_dev->dev, "Wait for inactive link disconnect detect\n"); +			continue; +		} else if (!udev || !(portstatus & USB_PORT_STAT_CONNECTION)  				|| udev->state == USB_STATE_NOTATTACHED) { +			dev_dbg(&port_dev->dev, "do warm reset, port only\n");  			if (hub_port_reset(hub, port1, NULL,  					HUB_BH_RESET_TIME, true) < 0)  				hub_port_disable(hub, port1, 1);  		} else { +			dev_dbg(&port_dev->dev, "do warm reset, full device\n");  			usb_unlock_port(port_dev);  			usb_lock_device(udev);  			usb_reset_device(udev); @@ -5628,6 +5653,7 @@ static void port_event(struct usb_hub *hub, int port1)  			usb_lock_port(port_dev);  			connect_change = 0;  		} +		break;  	}  	if (connect_change) @@ -5915,6 +5941,8 @@ static int usb_reset_and_verify_device(struct usb_device *udev)  	bos = udev->bos;  	udev->bos = NULL; +	mutex_lock(hcd->address0_mutex); +  	for (i = 0; i < PORT_INIT_TRIES; ++i) {  		/* ep0 maxpacket size may change; let the HCD know about it. @@ -5924,6 +5952,7 @@ static int usb_reset_and_verify_device(struct usb_device *udev)  		if (ret >= 0 || ret == -ENOTCONN || ret == -ENODEV)  			break;  	} +	mutex_unlock(hcd->address0_mutex);  	if (ret < 0)  		goto re_enumerate; |