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

Commit 102ee001 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman
Browse files

Merge tag 'for-usb-next-2013-01-03' of...

Merge tag 'for-usb-next-2013-01-03' of git://git.kernel.org/pub/scm/linux/kernel/git/sarah/xhci into usb-next

Sarah writes:
	usb-next: Further warm reset improvements

	Hi Greg,

	Here's some patches for 3.9.  They further improve the warm reset
	error handling, but they're too big to go into stable.  There's also a
	patch to remove an unused variable in the xHCI driver.

	As I mentioned, you'll need to merge usb-linus into usb-next before
	applying these patches.

	Sarah Sharp
parents 962426e0 026630d0
Loading
Loading
Loading
Loading
+107 −100
Original line number Diff line number Diff line
@@ -2535,55 +2535,42 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
			return ret;

		/* The port state is unknown until the reset completes. */
		if ((portstatus & USB_PORT_STAT_RESET))
			goto delay;
		if (!(portstatus & USB_PORT_STAT_RESET))
			break;

		/*
		 * 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_warm_reset_required(hub, portstatus)) {
				int ret;
		/* switch to the long delay after two short delay failures */
		if (delay_time >= 2 * HUB_SHORT_RESET_TIME)
			delay = HUB_LONG_RESET_TIME;

				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;
		dev_dbg (hub->intfdev,
			"port %d not %sreset yet, waiting %dms\n",
			port1, warm ? "warm " : "", delay);
	}

	if ((portstatus & USB_PORT_STAT_RESET))
		return -EBUSY;

	if (hub_port_warm_reset_required(hub, portstatus))
		return -ENOTCONN;

	/* 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))
	/* bomb out completely if the connection bounced.  A USB 3.0
	 * connection may bounce if multiple warm resets were issued,
	 * but the device may have successfully re-connected. Ignore it.
	 */
	if (!hub_is_superspeed(hub->hdev) &&
			(portchange & USB_PORT_STAT_C_CONNECTION))
		return -ENOTCONN;

			if ((portstatus & USB_PORT_STAT_ENABLE)) {
	if (!(portstatus & USB_PORT_STAT_ENABLE))
		return -EBUSY;

	if (!udev)
		return 0;

	if (hub_is_wusb(hub))
		udev->speed = USB_SPEED_WIRELESS;
	else if (hub_is_superspeed(hub->hdev))
@@ -2596,39 +2583,18 @@ static int hub_port_wait_reset(struct usb_hub *hub, int port1,
		udev->speed = USB_SPEED_FULL;
	return 0;
}
		} else {
			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;

		dev_dbg (hub->intfdev,
			"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)
			struct usb_device *udev, int *status)
{
	switch (*status) {
	case 0:
		if (!warm) {
			struct usb_hcd *hcd;
		/* TRSTRCY = 10 ms; plus some extra */
		msleep(10 + 40);
		if (udev) {
			struct usb_hcd *hcd = bus_to_hcd(udev->bus);

			update_devnum(udev, 0);
			hcd = bus_to_hcd(udev->bus);
			/* The xHC may think the device is already reset,
			 * so ignore the status.
			 */
@@ -2640,14 +2606,15 @@ static void hub_port_finish_reset(struct usb_hub *hub, int port1,
	case -ENODEV:
		clear_port_feature(hub->hdev,
				port1, USB_PORT_FEAT_C_RESET);
		/* FIXME need disconnect() for NOTATTACHED device */
		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);
			clear_port_feature(hub->hdev, port1,
					USB_PORT_FEAT_C_CONNECTION);
		}
		if (!warm)
		if (udev)
			usb_set_device_state(udev, *status
					? USB_STATE_NOTATTACHED
					: USB_STATE_DEFAULT);
@@ -2660,18 +2627,30 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
			struct usb_device *udev, unsigned int delay, bool warm)
{
	int i, status;
	u16 portchange, portstatus;

	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)) {
		if (warm) {
			dev_err(hub->intfdev, "only USB3 hub support "
						"warm reset\n");
			return -EINVAL;
		}
		/* 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 (!warm) {
		/*
		 * If the caller hasn't explicitly requested a warm reset,
		 * double check and see if one is needed.
		 */
		status = hub_port_status(hub, port1,
					&portstatus, &portchange);
		if (status < 0)
			goto done;

		if (hub_port_warm_reset_required(hub, portstatus))
			warm = true;
	}

	/* Reset the port */
@@ -2692,10 +2671,33 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
						status);
		}

		/* return on disconnect or reset */
		/* Check for disconnect or reset */
		if (status == 0 || status == -ENOTCONN || status == -ENODEV) {
			hub_port_finish_reset(hub, port1, udev, &status, warm);
			hub_port_finish_reset(hub, port1, udev, &status);

			if (!hub_is_superspeed(hub->hdev))
				goto done;

			/*
			 * If a USB 3.0 device migrates from reset to an error
			 * state, re-issue the warm reset.
			 */
			if (hub_port_status(hub, port1,
					&portstatus, &portchange) < 0)
				goto done;

			if (!hub_port_warm_reset_required(hub, portstatus))
				goto done;

			/*
			 * If the port is in SS.Inactive or Compliance Mode, the
			 * hot or warm reset failed.  Try another warm reset.
			 */
			if (!warm) {
				dev_dbg(hub->intfdev, "hot reset failed, warm reset port %d\n",
						port1);
				warm = true;
			}
		}

		dev_dbg (hub->intfdev,
@@ -2709,7 +2711,7 @@ static int hub_port_reset(struct usb_hub *hub, int port1,
		port1);

done:
	if (!warm)
	if (!hub_is_superspeed(hub->hdev))
		up_read(&ehci_cf_port_reset_rwsem);

	return status;
@@ -2945,9 +2947,7 @@ int usb_port_suspend(struct usb_device *udev, pm_message_t msg)

	/* see 7.1.7.6 */
	if (hub_is_superspeed(hub->hdev))
		status = set_port_feature(hub->hdev,
				port1 | (USB_SS_PORT_LS_U3 << 3),
				USB_PORT_FEAT_LINK_STATE);
		status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U3);
	else
		status = set_port_feature(hub->hdev, port1,
						USB_PORT_FEAT_SUSPEND);
@@ -3117,9 +3117,7 @@ int usb_port_resume(struct usb_device *udev, pm_message_t msg)

	/* see 7.1.7.7; affects power usage, but not budgeting */
	if (hub_is_superspeed(hub->hdev))
		status = set_port_feature(hub->hdev,
				port1 | (USB_SS_PORT_LS_U0 << 3),
				USB_PORT_FEAT_LINK_STATE);
		status = hub_set_port_link_state(hub, port1, USB_SS_PORT_LS_U0);
	else
		status = clear_port_feature(hub->hdev,
				port1, USB_PORT_FEAT_SUSPEND);
@@ -4700,12 +4698,21 @@ static void hub_events(void)
			 */
			if (hub_port_warm_reset_required(hub, portstatus)) {
				int status;
				struct usb_device *udev =
					hub->ports[i - 1]->child;

				dev_dbg(hub_dev, "warm reset port %d\n", i);
				status = hub_port_reset(hub, i, NULL,
						HUB_BH_RESET_TIME, true);
				if (!udev) {
					status = hub_port_reset(hub, i,
							NULL, HUB_BH_RESET_TIME,
							true);
					if (status < 0)
						hub_port_disable(hub, i, 1);
				} else {
					usb_lock_device(udev);
					status = usb_reset_device(udev);
					usb_unlock_device(udev);
				}
				connect_change = 0;
			}

+0 −2
Original line number Diff line number Diff line
@@ -2706,13 +2706,11 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
{
	struct xhci_hcd *xhci = hcd_to_xhci(hcd);
	u32 status;
	union xhci_trb *trb;
	u64 temp_64;
	union xhci_trb *event_ring_deq;
	dma_addr_t deq;

	spin_lock(&xhci->lock);
	trb = xhci->event_ring->dequeue;
	/* Check if the xHC generated the interrupt, or the irq is shared */
	status = xhci_readl(xhci, &xhci->op_regs->status);
	if (status == 0xffffffff)