diff options
Diffstat (limited to 'drivers/usb/core/hub.c')
| -rw-r--r-- | drivers/usb/core/hub.c | 166 | 
1 files changed, 129 insertions, 37 deletions
diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 25a7422ee657..128a804c42f4 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -20,10 +20,12 @@  #include <linux/usb.h>  #include <linux/usbdevice_fs.h>  #include <linux/usb/hcd.h> +#include <linux/usb/otg.h>  #include <linux/usb/quirks.h>  #include <linux/kthread.h>  #include <linux/mutex.h>  #include <linux/freezer.h> +#include <linux/random.h>  #include <asm/uaccess.h>  #include <asm/byteorder.h> @@ -81,7 +83,7 @@ struct usb_hub {  	u8			indicator[USB_MAXCHILDREN];  	struct delayed_work	leds;  	struct delayed_work	init_work; -	void			**port_owners; +	struct dev_state	**port_owners;  };  static inline int hub_is_superspeed(struct usb_device *hdev) @@ -1271,7 +1273,8 @@ static int hub_configure(struct usb_hub *hub,  	hdev->children = kzalloc(hdev->maxchild *  				sizeof(struct usb_device *), GFP_KERNEL); -	hub->port_owners = kzalloc(hdev->maxchild * sizeof(void *), GFP_KERNEL); +	hub->port_owners = kzalloc(hdev->maxchild * sizeof(struct dev_state *), +				GFP_KERNEL);  	if (!hdev->children || !hub->port_owners) {  		ret = -ENOMEM;  		goto fail; @@ -1649,7 +1652,7 @@ hub_ioctl(struct usb_interface *intf, unsigned int code, void *user_data)   * to one of these "claimed" ports, the program will "own" the device.   */  static int find_port_owner(struct usb_device *hdev, unsigned port1, -		void ***ppowner) +		struct dev_state ***ppowner)  {  	if (hdev->state == USB_STATE_NOTATTACHED)  		return -ENODEV; @@ -1664,10 +1667,11 @@ static int find_port_owner(struct usb_device *hdev, unsigned port1,  }  /* In the following three functions, the caller must hold hdev's lock */ -int usb_hub_claim_port(struct usb_device *hdev, unsigned port1, void *owner) +int usb_hub_claim_port(struct usb_device *hdev, unsigned port1, +		       struct dev_state *owner)  {  	int rc; -	void **powner; +	struct dev_state **powner;  	rc = find_port_owner(hdev, port1, &powner);  	if (rc) @@ -1678,10 +1682,11 @@ int usb_hub_claim_port(struct usb_device *hdev, unsigned port1, void *owner)  	return rc;  } -int usb_hub_release_port(struct usb_device *hdev, unsigned port1, void *owner) +int usb_hub_release_port(struct usb_device *hdev, unsigned port1, +			 struct dev_state *owner)  {  	int rc; -	void **powner; +	struct dev_state **powner;  	rc = find_port_owner(hdev, port1, &powner);  	if (rc) @@ -1692,10 +1697,10 @@ int usb_hub_release_port(struct usb_device *hdev, unsigned port1, void *owner)  	return rc;  } -void usb_hub_release_all_ports(struct usb_device *hdev, void *owner) +void usb_hub_release_all_ports(struct usb_device *hdev, struct dev_state *owner)  {  	int n; -	void **powner; +	struct dev_state **powner;  	n = find_port_owner(hdev, 1, &powner);  	if (n == 0) { @@ -2065,7 +2070,7 @@ static int usb_enumerate_device(struct usb_device *udev)  		if (err < 0) {  			dev_err(&udev->dev, "can't read configurations, error %d\n",  				err); -			goto fail; +			return err;  		}  	}  	if (udev->wusb == 1 && udev->authorized == 0) { @@ -2081,8 +2086,12 @@ static int usb_enumerate_device(struct usb_device *udev)  		udev->serial = usb_cache_string(udev, udev->descriptor.iSerialNumber);  	}  	err = usb_enumerate_device_otg(udev); -fail: -	return err; +	if (err < 0) +		return err; + +	usb_detect_interface_quirks(udev); + +	return 0;  }  static void set_usb_port_removable(struct usb_device *udev) @@ -2173,6 +2182,14 @@ int usb_new_device(struct usb_device *udev)  	/* Tell the world! */  	announce_device(udev); +	if (udev->serial) +		add_device_randomness(udev->serial, strlen(udev->serial)); +	if (udev->product) +		add_device_randomness(udev->product, strlen(udev->product)); +	if (udev->manufacturer) +		add_device_randomness(udev->manufacturer, +				      strlen(udev->manufacturer)); +  	device_enable_async_suspend(&udev->dev);  	/* @@ -2324,12 +2341,16 @@ static unsigned hub_is_wusb(struct usb_hub *hub)  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) +/* Is a USB 3.0 port in the Inactive or Complinance Mode state? + * Port worm reset is required to recover + */ +static bool hub_port_warm_reset_required(struct usb_hub *hub, u16 portstatus)  {  	return hub_is_superspeed(hub->hdev) && -		(portstatus & USB_PORT_STAT_LINK_STATE) == -		USB_SS_PORT_LS_SS_INACTIVE; +		(((portstatus & USB_PORT_STAT_LINK_STATE) == +		  USB_SS_PORT_LS_SS_INACTIVE) || +		 ((portstatus & USB_PORT_STAT_LINK_STATE) == +		  USB_SS_PORT_LS_COMP_MOD)) ;  }  static int hub_port_wait_reset(struct usb_hub *hub, int port1, @@ -2365,7 +2386,7 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,  			 *  			 * See https://bugzilla.kernel.org/show_bug.cgi?id=41752  			 */ -			if (hub_port_inactive(hub, portstatus)) { +			if (hub_port_warm_reset_required(hub, portstatus)) {  				int ret;  				if ((portchange & USB_PORT_STAT_C_CONNECTION)) @@ -2607,6 +2628,50 @@ static int check_port_resume_type(struct usb_device *udev,  	return status;  } +int usb_disable_ltm(struct usb_device *udev) +{ +	struct usb_hcd *hcd = bus_to_hcd(udev->bus); + +	/* Check if the roothub and device supports LTM. */ +	if (!usb_device_supports_ltm(hcd->self.root_hub) || +			!usb_device_supports_ltm(udev)) +		return 0; + +	/* Clear Feature LTM Enable can only be sent if the device is +	 * configured. +	 */ +	if (!udev->actconfig) +		return 0; + +	return usb_control_msg(udev, usb_sndctrlpipe(udev, 0), +			USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE, +			USB_DEVICE_LTM_ENABLE, 0, NULL, 0, +			USB_CTRL_SET_TIMEOUT); +} +EXPORT_SYMBOL_GPL(usb_disable_ltm); + +void usb_enable_ltm(struct usb_device *udev) +{ +	struct usb_hcd *hcd = bus_to_hcd(udev->bus); + +	/* Check if the roothub and device supports LTM. */ +	if (!usb_device_supports_ltm(hcd->self.root_hub) || +			!usb_device_supports_ltm(udev)) +		return; + +	/* Set Feature LTM Enable can only be sent if the device is +	 * configured. +	 */ +	if (!udev->actconfig) +		return; + +	usb_control_msg(udev, usb_sndctrlpipe(udev, 0), +			USB_REQ_SET_FEATURE, USB_RECIP_DEVICE, +			USB_DEVICE_LTM_ENABLE, 0, NULL, 0, +			USB_CTRL_SET_TIMEOUT); +} +EXPORT_SYMBOL_GPL(usb_enable_ltm); +  #ifdef	CONFIG_USB_SUSPEND  /* @@ -2702,6 +2767,11 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)  	if (udev->usb2_hw_lpm_enabled == 1)  		usb_set_usb2_hardware_lpm(udev, 0); +	if (usb_disable_ltm(udev)) { +		dev_err(&udev->dev, "%s Failed to disable LTM before suspend\n.", +				__func__); +		return -ENOMEM; +	}  	if (usb_unlocked_disable_lpm(udev)) {  		dev_err(&udev->dev, "%s Failed to disable LPM before suspend\n.",  				__func__); @@ -2731,7 +2801,8 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)  		if (udev->usb2_hw_lpm_capable == 1)  			usb_set_usb2_hardware_lpm(udev, 1); -		/* Try to enable USB3 LPM again */ +		/* Try to enable USB3 LTM and LPM again */ +		usb_enable_ltm(udev);  		usb_unlocked_enable_lpm(udev);  		/* System sleep transitions should never fail */ @@ -2932,7 +3003,8 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)  		if (udev->usb2_hw_lpm_capable == 1)  			usb_set_usb2_hardware_lpm(udev, 1); -		/* Try to enable USB3 LPM */ +		/* Try to enable USB3 LTM and LPM */ +		usb_enable_ltm(udev);  		usb_unlocked_enable_lpm(udev);  	} @@ -3485,6 +3557,15 @@ EXPORT_SYMBOL_GPL(usb_unlocked_disable_lpm);  void usb_unlocked_enable_lpm(struct usb_device *udev) { }  EXPORT_SYMBOL_GPL(usb_unlocked_enable_lpm); + +int usb_disable_ltm(struct usb_device *udev) +{ +	return 0; +} +EXPORT_SYMBOL_GPL(usb_disable_ltm); + +void usb_enable_ltm(struct usb_device *udev) { } +EXPORT_SYMBOL_GPL(usb_enable_ltm);  #endif @@ -4034,6 +4115,13 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1,  		}  	} +	if (hcd->phy && !hdev->parent) { +		if (portstatus & USB_PORT_STAT_CONNECTION) +			usb_phy_notify_connect(hcd->phy, port1); +		else +			usb_phy_notify_disconnect(hcd->phy, port1); +	} +  	/* Return now if debouncing failed or nothing is connected or  	 * the device was "removed".  	 */ @@ -4408,9 +4496,7 @@ static void hub_events(void)  			/* Warm reset a USB3 protocol port if it's in  			 * SS.Inactive state.  			 */ -			if (hub_is_superspeed(hub->hdev) && -				(portstatus & USB_PORT_STAT_LINK_STATE) -					== USB_SS_PORT_LS_SS_INACTIVE) { +			if (hub_port_warm_reset_required(hub, portstatus)) {  				dev_dbg(hub_dev, "warm reset port %d\n", i);  				hub_port_reset(hub, i, NULL,  						HUB_BH_RESET_TIME, true); @@ -4670,6 +4756,23 @@ static int usb_reset_and_verify_device(struct usb_device *udev)  	}  	parent_hub = hdev_to_hub(parent_hdev); +	/* Disable LPM and LTM while we reset the device and reinstall the alt +	 * settings.  Device-initiated LPM settings, and system exit latency +	 * settings are cleared when the device is reset, so we have to set +	 * them up again. +	 */ +	ret = usb_unlocked_disable_lpm(udev); +	if (ret) { +		dev_err(&udev->dev, "%s Failed to disable LPM\n.", __func__); +		goto re_enumerate; +	} +	ret = usb_disable_ltm(udev); +	if (ret) { +		dev_err(&udev->dev, "%s Failed to disable LTM\n.", +				__func__); +		goto re_enumerate; +	} +  	set_bit(port1, parent_hub->busy_bits);  	for (i = 0; i < SET_CONFIG_TRIES; ++i) { @@ -4697,22 +4800,11 @@ static int usb_reset_and_verify_device(struct usb_device *udev)  		goto done;  	mutex_lock(hcd->bandwidth_mutex); -	/* Disable LPM while we reset the device and reinstall the alt settings. -	 * Device-initiated LPM settings, and system exit latency settings are -	 * cleared when the device is reset, so we have to set them up again. -	 */ -	ret = usb_disable_lpm(udev); -	if (ret) { -		dev_err(&udev->dev, "%s Failed to disable LPM\n.", __func__); -		mutex_unlock(hcd->bandwidth_mutex); -		goto done; -	}  	ret = usb_hcd_alloc_bandwidth(udev, udev->actconfig, NULL, NULL);  	if (ret < 0) {  		dev_warn(&udev->dev,  				"Busted HC?  Not enough HCD resources for "  				"old configuration.\n"); -		usb_enable_lpm(udev);  		mutex_unlock(hcd->bandwidth_mutex);  		goto re_enumerate;  	} @@ -4724,7 +4816,6 @@ static int usb_reset_and_verify_device(struct usb_device *udev)  		dev_err(&udev->dev,  			"can't restore configuration #%d (error=%d)\n",  			udev->actconfig->desc.bConfigurationValue, ret); -		usb_enable_lpm(udev);  		mutex_unlock(hcd->bandwidth_mutex);  		goto re_enumerate;    	} @@ -4763,17 +4854,18 @@ static int usb_reset_and_verify_device(struct usb_device *udev)  				desc->bInterfaceNumber,  				desc->bAlternateSetting,  				ret); -			usb_unlocked_enable_lpm(udev);  			goto re_enumerate;  		}  	} -	/* Now that the alt settings are re-installed, enable LPM. */ -	usb_unlocked_enable_lpm(udev);  done: +	/* Now that the alt settings are re-installed, enable LTM and LPM. */ +	usb_unlocked_enable_lpm(udev); +	usb_enable_ltm(udev);  	return 0;  re_enumerate: +	/* LPM state doesn't matter when we're about to destroy the device. */  	hub_port_logical_disconnect(parent_hub, port1);  	return -ENODEV;  }  | 
