diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
| -rw-r--r-- | drivers/usb/core/hub.c | 120 | 
1 files changed, 96 insertions, 24 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index a815fd2cc5e..957ed2c4148 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;  	}  } @@ -2939,7 +3000,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,7 +3045,13 @@ 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) { +	/* +	 * 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) {  		le16_to_cpus(&devstatus);  		if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) {  			status = usb_control_msg(udev, @@ -4638,9 +4705,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)  |