diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
| -rw-r--r-- | drivers/usb/core/hub.c | 332 | 
1 files changed, 196 insertions, 136 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index a428aa080a3..79781461eec 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -813,6 +813,12 @@ static void hub_activate(struct usb_hub *hub, enum hub_activation_type type)  					USB_PORT_FEAT_C_PORT_LINK_STATE);  		} +		if ((portchange & USB_PORT_STAT_C_BH_RESET) && +				hub_is_superspeed(hub->hdev)) { +			need_debounce_delay = true; +			clear_port_feature(hub->hdev, port1, +					USB_PORT_FEAT_C_BH_PORT_RESET); +		}  		/* We can forget about a "removed" device when there's a  		 * physical disconnect or the connect status changes.  		 */ @@ -1636,11 +1642,6 @@ void usb_disconnect(struct usb_device **pdev)  	int			i;  	struct usb_hcd		*hcd = bus_to_hcd(udev->bus); -	if (!udev) { -		pr_debug ("%s nodev\n", __func__); -		return; -	} -  	/* mark the device as inactive, so any further urb submissions for  	 * this device (and any of its children) will fail immediately.  	 * this quiesces everything except pending urbs. @@ -2030,11 +2031,23 @@ static unsigned hub_is_wusb(struct usb_hub *hub)  #define HUB_ROOT_RESET_TIME	50	/* times are in msec */  #define HUB_SHORT_RESET_TIME	10 +#define HUB_BH_RESET_TIME	50  #define HUB_LONG_RESET_TIME	200  #define HUB_RESET_TIMEOUT	500 +static int hub_port_reset(struct usb_hub *hub, int port1, +			struct usb_device *udev, unsigned int delay, bool warm); + +/* Is a USB 3.0 port in the Inactive state? */ +static bool hub_port_inactive(struct usb_hub *hub, u16 portstatus) +{ +	return hub_is_superspeed(hub->hdev) && +		(portstatus & USB_PORT_STAT_LINK_STATE) == +		USB_SS_PORT_LS_SS_INACTIVE; +} +  static int hub_port_wait_reset(struct usb_hub *hub, int port1, -				struct usb_device *udev, unsigned int delay) +			struct usb_device *udev, unsigned int delay, bool warm)  {  	int delay_time, ret;  	u16 portstatus; @@ -2051,28 +2064,71 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,  		if (ret < 0)  			return ret; -		/* Device went away? */ -		if (!(portstatus & USB_PORT_STAT_CONNECTION)) -			return -ENOTCONN; - -		/* bomb out completely if the connection bounced */ -		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 (hub_is_wusb(hub)) -				udev->speed = USB_SPEED_WIRELESS; -			else if (hub_is_superspeed(hub->hdev)) -				udev->speed = USB_SPEED_SUPER; -			else if (portstatus & USB_PORT_STAT_HIGH_SPEED) -				udev->speed = USB_SPEED_HIGH; -			else if (portstatus & USB_PORT_STAT_LOW_SPEED) -				udev->speed = USB_SPEED_LOW; -			else -				udev->speed = USB_SPEED_FULL; -			return 0; +		/* +		 * Some buggy devices require a warm reset to be issued even +		 * when the port appears not to be connected. +		 */ +		if (!warm) { +			/* +			 * Some buggy devices can cause an NEC host controller +			 * to transition to the "Error" state after a hot port +			 * reset.  This will show up as the port state in +			 * "Inactive", and the port may also report a +			 * disconnect.  Forcing a warm port reset seems to make +			 * the device work. +			 * +			 * See https://bugzilla.kernel.org/show_bug.cgi?id=41752 +			 */ +			if (hub_port_inactive(hub, portstatus)) { +				int ret; + +				if ((portchange & USB_PORT_STAT_C_CONNECTION)) +					clear_port_feature(hub->hdev, port1, +							USB_PORT_FEAT_C_CONNECTION); +				if (portchange & USB_PORT_STAT_C_LINK_STATE) +					clear_port_feature(hub->hdev, port1, +							USB_PORT_FEAT_C_PORT_LINK_STATE); +				if (portchange & USB_PORT_STAT_C_RESET) +					clear_port_feature(hub->hdev, port1, +							USB_PORT_FEAT_C_RESET); +				dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n", +						port1); +				ret = hub_port_reset(hub, port1, +						udev, HUB_BH_RESET_TIME, +						true); +				if ((portchange & USB_PORT_STAT_C_CONNECTION)) +					clear_port_feature(hub->hdev, port1, +							USB_PORT_FEAT_C_CONNECTION); +				return ret; +			} +			/* Device went away? */ +			if (!(portstatus & USB_PORT_STAT_CONNECTION)) +				return -ENOTCONN; + +			/* bomb out completely if the connection bounced */ +			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 (hub_is_wusb(hub)) +					udev->speed = USB_SPEED_WIRELESS; +				else if (hub_is_superspeed(hub->hdev)) +					udev->speed = USB_SPEED_SUPER; +				else if (portstatus & USB_PORT_STAT_HIGH_SPEED) +					udev->speed = USB_SPEED_HIGH; +				else if (portstatus & USB_PORT_STAT_LOW_SPEED) +					udev->speed = USB_SPEED_LOW; +				else +					udev->speed = USB_SPEED_FULL; +				return 0; +			} +		} else { +			if (portchange & USB_PORT_STAT_C_BH_RESET) +				return 0;  		}  		/* switch to the long delay after two short delay failures */ @@ -2080,35 +2136,84 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,  			delay = HUB_LONG_RESET_TIME;  		dev_dbg (hub->intfdev, -			"port %d not reset yet, waiting %dms\n", -			port1, delay); +			"port %d not %sreset yet, waiting %dms\n", +			port1, warm ? "warm " : "", delay);  	}  	return -EBUSY;  } +static void hub_port_finish_reset(struct usb_hub *hub, int port1, +			struct usb_device *udev, int *status, bool warm) +{ +	switch (*status) { +	case 0: +		if (!warm) { +			struct usb_hcd *hcd; +			/* TRSTRCY = 10 ms; plus some extra */ +			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; +				} +			} +		} +		/* FALL THROUGH */ +	case -ENOTCONN: +	case -ENODEV: +		clear_port_feature(hub->hdev, +				port1, USB_PORT_FEAT_C_RESET); +		/* FIXME need disconnect() for NOTATTACHED device */ +		if (warm) { +			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 { +			usb_set_device_state(udev, *status +					? USB_STATE_NOTATTACHED +					: USB_STATE_DEFAULT); +		} +		break; +	} +} + +/* Handle port reset and port warm(BH) reset (for USB3 protocol ports) */  static int hub_port_reset(struct usb_hub *hub, int port1, -				struct usb_device *udev, unsigned int delay) +			struct usb_device *udev, unsigned int delay, bool warm)  {  	int i, status; -	struct usb_hcd *hcd; -	hcd = bus_to_hcd(udev->bus); -	/* Block EHCI CF initialization during the port reset. -	 * Some companion controllers don't like it when they mix. -	 */ -	down_read(&ehci_cf_port_reset_rwsem); +	if (!warm) { +		/* Block EHCI CF initialization during the port reset. +		 * Some companion controllers don't like it when they mix. +		 */ +		down_read(&ehci_cf_port_reset_rwsem); +	} else { +		if (!hub_is_superspeed(hub->hdev)) { +			dev_err(hub->intfdev, "only USB3 hub support " +						"warm reset\n"); +			return -EINVAL; +		} +	}  	/* Reset the port */  	for (i = 0; i < PORT_RESET_TRIES; i++) { -		status = set_port_feature(hub->hdev, -				port1, USB_PORT_FEAT_RESET); -		if (status) +		status = set_port_feature(hub->hdev, port1, (warm ? +					USB_PORT_FEAT_BH_PORT_RESET : +					USB_PORT_FEAT_RESET)); +		if (status) {  			dev_err(hub->intfdev, -					"cannot reset port %d (err = %d)\n", -					port1, status); -		else { -			status = hub_port_wait_reset(hub, port1, udev, delay); +					"cannot %sreset port %d (err = %d)\n", +					warm ? "warm " : "", port1, status); +		} else { +			status = hub_port_wait_reset(hub, port1, udev, delay, +								warm);  			if (status && status != -ENOTCONN)  				dev_dbg(hub->intfdev,  						"port_wait_reset: err = %d\n", @@ -2116,34 +2221,14 @@ static int hub_port_reset(struct usb_hub *hub, int port1,  		}  		/* return on disconnect or reset */ -		switch (status) { -		case 0: -			/* TRSTRCY = 10 ms; plus some extra */ -			msleep(10 + 40); -			update_devnum(udev, 0); -			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; -				} -			} -			/* FALL THROUGH */ -		case -ENOTCONN: -		case -ENODEV: -			clear_port_feature(hub->hdev, -				port1, USB_PORT_FEAT_C_RESET); -			/* FIXME need disconnect() for NOTATTACHED device */ -			usb_set_device_state(udev, status -					? USB_STATE_NOTATTACHED -					: USB_STATE_DEFAULT); +		if (status == 0 || status == -ENOTCONN || status == -ENODEV) { +			hub_port_finish_reset(hub, port1, udev, &status, warm);  			goto done;  		}  		dev_dbg (hub->intfdev, -			"port %d not enabled, trying reset again...\n", -			port1); +			"port %d not enabled, trying %sreset again...\n", +			port1, warm ? "warm " : "");  		delay = HUB_LONG_RESET_TIME;  	} @@ -2151,45 +2236,11 @@ static int hub_port_reset(struct usb_hub *hub, int port1,  		"Cannot enable port %i.  Maybe the USB cable is bad?\n",  		port1); - done: -	up_read(&ehci_cf_port_reset_rwsem); -	return status; -} - -/* Warm reset a USB3 protocol port */ -static int hub_port_warm_reset(struct usb_hub *hub, int port) -{ -	int ret; -	u16 portstatus, portchange; - -	if (!hub_is_superspeed(hub->hdev)) { -		dev_err(hub->intfdev, "only USB3 hub support warm reset\n"); -		return -EINVAL; -	} - -	/* Warm reset the port */ -	ret = set_port_feature(hub->hdev, -				port, USB_PORT_FEAT_BH_PORT_RESET); -	if (ret) { -		dev_err(hub->intfdev, "cannot warm reset port %d\n", port); -		return ret; -	} - -	msleep(20); -	ret = hub_port_status(hub, port, &portstatus, &portchange); - -	if (portchange & USB_PORT_STAT_C_RESET) -		clear_port_feature(hub->hdev, port, USB_PORT_FEAT_C_RESET); - -	if (portchange & USB_PORT_STAT_C_BH_RESET) -		clear_port_feature(hub->hdev, port, -					USB_PORT_FEAT_C_BH_PORT_RESET); - -	if (portchange & USB_PORT_STAT_C_LINK_STATE) -		clear_port_feature(hub->hdev, port, -					USB_PORT_FEAT_C_PORT_LINK_STATE); +done: +	if (!warm) +		up_read(&ehci_cf_port_reset_rwsem); -	return ret; +	return status;  }  /* Check if a port is power on */ @@ -2324,8 +2375,6 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)  	int		port1 = udev->portnum;  	int		status; -	// dev_dbg(hub->intfdev, "suspend port %d\n", port1); -  	/* enable remote wakeup when appropriate; this lets the device  	 * wake up the upstream hub (including maybe the root hub).  	 * @@ -2342,11 +2391,15 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)  			dev_dbg(&udev->dev, "won't remote wakeup, status %d\n",  					status);  			/* bail if autosuspend is requested */ -			if (msg.event & PM_EVENT_AUTO) +			if (PMSG_IS_AUTO(msg))  				return status;  		}  	} +	/* disable USB2 hardware LPM */ +	if (udev->usb2_hw_lpm_enabled == 1) +		usb_set_usb2_hardware_lpm(udev, 0); +  	/* see 7.1.7.6 */  	if (hub_is_superspeed(hub->hdev))  		status = set_port_feature(hub->hdev, @@ -2367,12 +2420,13 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)  				USB_CTRL_SET_TIMEOUT);  		/* System sleep transitions should never fail */ -		if (!(msg.event & PM_EVENT_AUTO)) +		if (!PMSG_IS_AUTO(msg))  			status = 0;  	} else {  		/* device has up to 10 msec to fully suspend */ -		dev_dbg(&udev->dev, "usb %ssuspend\n", -				(msg.event & PM_EVENT_AUTO ? "auto-" : "")); +		dev_dbg(&udev->dev, "usb %ssuspend, wakeup %d\n", +				(PMSG_IS_AUTO(msg) ? "auto-" : ""), +				udev->do_remote_wakeup);  		usb_set_device_state(udev, USB_STATE_SUSPENDED);  		msleep(10);  	} @@ -2523,7 +2577,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)  	} else {  		/* drive resume for at least 20 msec */  		dev_dbg(&udev->dev, "usb %sresume\n", -				(msg.event & PM_EVENT_AUTO ? "auto-" : "")); +				(PMSG_IS_AUTO(msg) ? "auto-" : ""));  		msleep(25);  		/* Virtual root hubs can trigger on GET_PORT_STATUS to @@ -2558,7 +2612,12 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)  	if (status < 0) {  		dev_dbg(&udev->dev, "can't resume, status %d\n", status);  		hub_port_logical_disconnect(hub, port1); +	} else  { +		/* Try to enable USB2 hardware LPM */ +		if (udev->usb2_hw_lpm_capable == 1) +			usb_set_usb2_hardware_lpm(udev, 1);  	} +  	return status;  } @@ -2625,7 +2684,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)  		udev = hdev->children [port1-1];  		if (udev && udev->can_submit) {  			dev_warn(&intf->dev, "port %d nyet suspended\n", port1); -			if (msg.event & PM_EVENT_AUTO) +			if (PMSG_IS_AUTO(msg))  				return -EBUSY;  		}  	} @@ -2798,7 +2857,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,  	int			i, j, retval;  	unsigned		delay = HUB_SHORT_RESET_TIME;  	enum usb_device_speed	oldspeed = udev->speed; -	char 			*speed, *type; +	const char		*speed;  	int			devnum = udev->devnum;  	/* root hub ports have a slightly longer reset period @@ -2819,7 +2878,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,  	/* 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); +	retval = hub_port_reset(hub, port1, udev, delay, false);  	if (retval < 0)		/* error or disconnect */  		goto fail;  	/* success, speed is known */ @@ -2858,25 +2917,16 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,  	default:  		goto fail;  	} -  -	type = ""; -	switch (udev->speed) { -	case USB_SPEED_LOW:	speed = "low";	break; -	case USB_SPEED_FULL:	speed = "full";	break; -	case USB_SPEED_HIGH:	speed = "high";	break; -	case USB_SPEED_SUPER: -				speed = "super"; -				break; -	case USB_SPEED_WIRELESS: -				speed = "variable"; -				type = "Wireless "; -				break; -	default: 		speed = "?";	break; -	} + +	if (udev->speed == USB_SPEED_WIRELESS) +		speed = "variable speed Wireless"; +	else +		speed = usb_speed_string(udev->speed); +  	if (udev->speed != USB_SPEED_SUPER)  		dev_info(&udev->dev, -				"%s %s speed %sUSB device number %d using %s\n", -				(udev->config) ? "reset" : "new", speed, type, +				"%s %s USB device number %d using %s\n", +				(udev->config) ? "reset" : "new", speed,  				devnum, udev->bus->controller->driver->name);  	/* Set up TT records, if needed  */ @@ -2949,7 +2999,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,  					buf->bMaxPacketSize0;  			kfree(buf); -			retval = hub_port_reset(hub, port1, udev, delay); +			retval = hub_port_reset(hub, port1, udev, delay, false);  			if (retval < 0)		/* error or disconnect */  				goto fail;  			if (oldspeed != udev->speed) { @@ -3023,7 +3073,7 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,  		i = 512;  	else  		i = udev->descriptor.bMaxPacketSize0; -	if (le16_to_cpu(udev->ep0.desc.wMaxPacketSize) != i) { +	if (usb_endpoint_maxp(&udev->ep0.desc) != i) {  		if (udev->speed == USB_SPEED_LOW ||  				!(i == 8 || i == 16 || i == 32 || i == 64)) {  			dev_err(&udev->dev, "Invalid ep0 maxpacket: %d\n", i); @@ -3047,6 +3097,15 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1,  		goto fail;  	} +	if (udev->wusb == 0 && le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0201) { +		retval = usb_get_bos_descriptor(udev); +		if (!retval) { +			if (udev->bos->ext_cap && (USB_LPM_SUPPORT & +				le32_to_cpu(udev->bos->ext_cap->bmAttributes))) +					udev->lpm_capable = 1; +		} +	} +  	retval = 0;  	/* notify HCD that we have a device connected and addressed */  	if (hcd->driver->update_device) @@ -3570,7 +3629,8 @@ static void hub_events(void)  				(portstatus & USB_PORT_STAT_LINK_STATE)  					== USB_SS_PORT_LS_SS_INACTIVE) {  				dev_dbg(hub_dev, "warm reset port %d\n", i); -				hub_port_warm_reset(hub, i); +				hub_port_reset(hub, i, NULL, +						HUB_BH_RESET_TIME, true);  			}  			if (connect_change)  | 
