Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 75d7cf72 authored by Andiry Xu's avatar Andiry Xu Committed by Greg Kroah-Hartman
Browse files

usbcore: refine warm reset logic



Current waiting time for warm(BH) reset in hub_port_warm_reset() is too short
for xHC host to complete the warm reset and report a BH reset change.

This patch increases the waiting time for warm reset and merges the function
into hub_port_reset(), so it can handle both cold reset and warm reset, and
factor out hub_port_finish_reset() to make the code looks cleaner.

This fixes the issue that driver fails to clear BH reset change and port is
"dead".

Signed-off-by: default avatarAndiry Xu <andiry.xu@amd.com>
Acked-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarSarah Sharp <sarah.a.sharp@linux.intel.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>
parent d7826599
Loading
Loading
Loading
Loading
+112 −104
Original line number Diff line number Diff line
@@ -2025,11 +2025,12 @@ 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_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;
@@ -2046,6 +2047,11 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
		if (ret < 0)
			return ret;

		/*
		 * Some buggy devices require a warm reset to be issued even
		 * when the port appears not to be connected.
		 */
		if (!warm) {
			/* Device went away? */
			if (!(portstatus & USB_PORT_STAT_CONNECTION))
				return -ENOTCONN;
@@ -2054,7 +2060,9 @@ 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 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))
@@ -2069,122 +2077,121 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
					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 */
		if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
			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 int hub_port_reset(struct usb_hub *hub, int port1,
				struct usb_device *udev, unsigned int delay)
static void hub_port_finish_reset(struct usb_hub *hub, int port1,
			struct usb_device *udev, int *status, 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);

	/* Reset the port */
	for (i = 0; i < PORT_RESET_TRIES; i++) {
		status = set_port_feature(hub->hdev,
				port1, 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);
			if (status && status != -ENOTCONN)
				dev_dbg(hub->intfdev,
						"port_wait_reset: err = %d\n",
						status);
		}

		/* return on disconnect or reset */
		switch (status) {
	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) {
				*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
		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);
			goto done;
		}

		dev_dbg (hub->intfdev,
			"port %d not enabled, trying reset again...\n",
			port1);
		delay = HUB_LONG_RESET_TIME;
		break;
	}

	dev_err (hub->intfdev,
		"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)
/* 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, bool warm)
{
	int ret;
	u16 portstatus, portchange;
	int i, status;

	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");
			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;
	/* Reset the port */
	for (i = 0; i < PORT_RESET_TRIES; i++) {
		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 %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",
						status);
		}

	msleep(20);
	ret = hub_port_status(hub, port, &portstatus, &portchange);
		/* return on disconnect or reset */
		if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
			hub_port_finish_reset(hub, port1, udev, &status, warm);
			goto done;
		}

	if (portchange & USB_PORT_STAT_C_RESET)
		clear_port_feature(hub->hdev, port, USB_PORT_FEAT_C_RESET);
		dev_dbg (hub->intfdev,
			"port %d not enabled, trying %sreset again...\n",
			port1, warm ? "warm " : "");
		delay = HUB_LONG_RESET_TIME;
	}

	if (portchange & USB_PORT_STAT_C_BH_RESET)
		clear_port_feature(hub->hdev, port,
					USB_PORT_FEAT_C_BH_PORT_RESET);
	dev_err (hub->intfdev,
		"Cannot enable port %i.  Maybe the USB cable is bad?\n",
		port1);

	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 */
@@ -2814,7 +2821,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 */
@@ -2935,7 +2942,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) {
@@ -3556,7 +3563,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)