diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
| -rw-r--r-- | drivers/usb/core/hub.c | 190 | 
1 files changed, 148 insertions, 42 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index a815fd2cc5e..cbf7168e3ce 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -877,6 +877,60 @@ static int hub_hub_status(struct usb_hub *hub,  	return ret;  } +static int hub_set_port_link_state(struct usb_hub *hub, int port1, +			unsigned int link_status) +{ +	return set_port_feature(hub->hdev, +			port1 | (link_status << 3), +			USB_PORT_FEAT_LINK_STATE); +} + +/* + * If USB 3.0 ports are placed into the Disabled state, they will no longer + * detect any device connects or disconnects.  This is generally not what the + * USB core wants, since it expects a disabled port to produce a port status + * change event when a new device connects. + * + * Instead, set the link state to Disabled, wait for the link to settle into + * that state, clear any change bits, and then put the port into the RxDetect + * state. + */ +static int hub_usb3_port_disable(struct usb_hub *hub, int port1) +{ +	int ret; +	int total_time; +	u16 portchange, portstatus; + +	if (!hub_is_superspeed(hub->hdev)) +		return -EINVAL; + +	ret = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_SS_DISABLED); +	if (ret) { +		dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n", +				port1, ret); +		return ret; +	} + +	/* Wait for the link to enter the disabled state. */ +	for (total_time = 0; ; total_time += HUB_DEBOUNCE_STEP) { +		ret = hub_port_status(hub, port1, &portstatus, &portchange); +		if (ret < 0) +			return ret; + +		if ((portstatus & USB_PORT_STAT_LINK_STATE) == +				USB_SS_PORT_LS_SS_DISABLED) +			break; +		if (total_time >= HUB_DEBOUNCE_TIMEOUT) +			break; +		msleep(HUB_DEBOUNCE_STEP); +	} +	if (total_time >= HUB_DEBOUNCE_TIMEOUT) +		dev_warn(hub->intfdev, "Could not disable port %d after %d ms\n", +				port1, total_time); + +	return hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_RX_DETECT); +} +  static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)  {  	struct usb_device *hdev = hub->hdev; @@ -885,8 +939,13 @@ static int hub_port_disable(struct usb_hub *hub, int port1, int set_state)  	if (hub->ports[port1 - 1]->child && set_state)  		usb_set_device_state(hub->ports[port1 - 1]->child,  				USB_STATE_NOTATTACHED); -	if (!hub->error && !hub_is_superspeed(hub->hdev)) -		ret = clear_port_feature(hdev, port1, USB_PORT_FEAT_ENABLE); +	if (!hub->error) { +		if (hub_is_superspeed(hub->hdev)) +			ret = hub_usb3_port_disable(hub, port1); +		else +			ret = clear_port_feature(hdev, port1, +					USB_PORT_FEAT_ENABLE); +	}  	if (ret)  		dev_err(hub->intfdev, "cannot disable port %d (err = %d)\n",  				port1, ret); @@ -2440,7 +2499,7 @@ static unsigned hub_is_wusb(struct usb_hub *hub)  #define HUB_SHORT_RESET_TIME	10  #define HUB_BH_RESET_TIME	50  #define HUB_LONG_RESET_TIME	200 -#define HUB_RESET_TIMEOUT	500 +#define HUB_RESET_TIMEOUT	800  static int hub_port_reset(struct usb_hub *hub, int port1,  			struct usb_device *udev, unsigned int delay, bool warm); @@ -2475,6 +2534,10 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,  		if (ret < 0)  			return ret; +		/* The port state is unknown until the reset completes. */ +		if ((portstatus & USB_PORT_STAT_RESET)) +			goto delay; +  		/*  		 * Some buggy devices require a warm reset to be issued even  		 * when the port appears not to be connected. @@ -2520,11 +2583,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,  			if ((portchange & USB_PORT_STAT_C_CONNECTION))  				return -ENOTCONN; -			/* if we`ve finished resetting, then break out of -			 * the loop -			 */ -			if (!(portstatus & USB_PORT_STAT_RESET) && -			    (portstatus & USB_PORT_STAT_ENABLE)) { +			if ((portstatus & USB_PORT_STAT_ENABLE)) {  				if (hub_is_wusb(hub))  					udev->speed = USB_SPEED_WIRELESS;  				else if (hub_is_superspeed(hub->hdev)) @@ -2538,10 +2597,15 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,  				return 0;  			}  		} else { -			if (portchange & USB_PORT_STAT_C_BH_RESET) -				return 0; +			if (!(portstatus & USB_PORT_STAT_CONNECTION) || +					hub_port_warm_reset_required(hub, +						portstatus)) +				return -ENOTCONN; + +			return 0;  		} +delay:  		/* switch to the long delay after two short delay failures */  		if (delay_time >= 2 * HUB_SHORT_RESET_TIME)  			delay = HUB_LONG_RESET_TIME; @@ -2565,14 +2629,11 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,  			msleep(10 + 40);  			update_devnum(udev, 0);  			hcd = bus_to_hcd(udev->bus); -			if (hcd->driver->reset_device) { -				*status = hcd->driver->reset_device(hcd, udev); -				if (*status < 0) { -					dev_err(&udev->dev, "Cannot reset " -							"HCD device state\n"); -					break; -				} -			} +			/* The xHC may think the device is already reset, +			 * so ignore the status. +			 */ +			if (hcd->driver->reset_device) +				hcd->driver->reset_device(hcd, udev);  		}  		/* FALL THROUGH */  	case -ENOTCONN: @@ -2580,16 +2641,16 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,  		clear_port_feature(hub->hdev,  				port1, USB_PORT_FEAT_C_RESET);  		/* FIXME need disconnect() for NOTATTACHED device */ -		if (warm) { +		if (hub_is_superspeed(hub->hdev)) {  			clear_port_feature(hub->hdev, port1,  					USB_PORT_FEAT_C_BH_PORT_RESET);  			clear_port_feature(hub->hdev, port1,  					USB_PORT_FEAT_C_PORT_LINK_STATE); -		} else { +		} +		if (!warm)  			usb_set_device_state(udev, *status  					? USB_STATE_NOTATTACHED  					: USB_STATE_DEFAULT); -		}  		break;  	}  } @@ -2777,6 +2838,23 @@ void usb_enable_ltm(struct usb_device *udev)  EXPORT_SYMBOL_GPL(usb_enable_ltm);  #ifdef	CONFIG_USB_SUSPEND +/* + * usb_disable_function_remotewakeup - disable usb3.0 + * device's function remote wakeup + * @udev: target device + * + * Assume there's only one function on the USB 3.0 + * device and disable remote wake for the first + * interface. FIXME if the interface association + * descriptor shows there's more than one function. + */ +static int usb_disable_function_remotewakeup(struct usb_device *udev) +{ +	return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), +				USB_REQ_CLEAR_FEATURE, USB_RECIP_INTERFACE, +				USB_INTRF_FUNC_SUSPEND,	0, NULL, 0, +				USB_CTRL_SET_TIMEOUT); +}  /*   * usb_port_suspend - suspend a usb device's upstream port @@ -2894,12 +2972,19 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)  		dev_dbg(hub->intfdev, "can't suspend port %d, status %d\n",  				port1, status);  		/* paranoia:  "should not happen" */ -		if (udev->do_remote_wakeup) -			(void) usb_control_msg(udev, usb_sndctrlpipe(udev, 0), -				USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE, -				USB_DEVICE_REMOTE_WAKEUP, 0, -				NULL, 0, -				USB_CTRL_SET_TIMEOUT); +		if (udev->do_remote_wakeup) { +			if (!hub_is_superspeed(hub->hdev)) { +				(void) usb_control_msg(udev, +						usb_sndctrlpipe(udev, 0), +						USB_REQ_CLEAR_FEATURE, +						USB_RECIP_DEVICE, +						USB_DEVICE_REMOTE_WAKEUP, 0, +						NULL, 0, +						USB_CTRL_SET_TIMEOUT); +			} else +				(void) usb_disable_function_remotewakeup(udev); + +		}  		/* Try to enable USB2 hardware LPM again */  		if (udev->usb2_hw_lpm_capable == 1) @@ -2939,7 +3024,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)  static int finish_port_resume(struct usb_device *udev)  {  	int	status = 0; -	u16	devstatus; +	u16	devstatus = 0;  	/* caller owns the udev device lock */  	dev_dbg(&udev->dev, "%s\n", @@ -2984,21 +3069,37 @@ static int finish_port_resume(struct usb_device *udev)  	if (status) {  		dev_dbg(&udev->dev, "gone after usb resume? status %d\n",  				status); -	} else if (udev->actconfig) { -		le16_to_cpus(&devstatus); -		if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) { -			status = usb_control_msg(udev, -					usb_sndctrlpipe(udev, 0), -					USB_REQ_CLEAR_FEATURE, +	/* +	 * There are a few quirky devices which violate the standard +	 * by claiming to have remote wakeup enabled after a reset, +	 * which crash if the feature is cleared, hence check for +	 * udev->reset_resume +	 */ +	} else if (udev->actconfig && !udev->reset_resume) { +		if (!hub_is_superspeed(udev->parent)) { +			le16_to_cpus(&devstatus); +			if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) +				status = usb_control_msg(udev, +						usb_sndctrlpipe(udev, 0), +						USB_REQ_CLEAR_FEATURE,  						USB_RECIP_DEVICE, -					USB_DEVICE_REMOTE_WAKEUP, 0, -					NULL, 0, -					USB_CTRL_SET_TIMEOUT); -			if (status) -				dev_dbg(&udev->dev, -					"disable remote wakeup, status %d\n", -					status); +						USB_DEVICE_REMOTE_WAKEUP, 0, +						NULL, 0, +						USB_CTRL_SET_TIMEOUT); +		} else { +			status = usb_get_status(udev, USB_RECIP_INTERFACE, 0, +					&devstatus); +			le16_to_cpus(&devstatus); +			if (!status && devstatus & (USB_INTRF_STAT_FUNC_RW_CAP +					| USB_INTRF_STAT_FUNC_RW)) +				status = +					usb_disable_function_remotewakeup(udev);  		} + +		if (status) +			dev_dbg(&udev->dev, +				"disable remote wakeup, status %d\n", +				status);  		status = 0;  	}  	return status; @@ -4638,9 +4739,14 @@ static void hub_events(void)  			 * SS.Inactive state.  			 */  			if (hub_port_warm_reset_required(hub, portstatus)) { +				int status; +  				dev_dbg(hub_dev, "warm reset port %d\n", i); -				hub_port_reset(hub, i, NULL, +				status = hub_port_reset(hub, i, NULL,  						HUB_BH_RESET_TIME, true); +				if (status < 0) +					hub_port_disable(hub, i, 1); +				connect_change = 0;  			}  			if (connect_change)  |