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

Commit f3f3253d authored by David Brownell's avatar David Brownell Committed by Greg Kroah-Hartman
Browse files

[PATCH] root hub updates (greater half)



This patch associates hub suspend and resume logic (including for root hubs)
with CONFIG_PM -- instead of CONFIG_USB_SUSPEND as before -- thereby unifying
two troublesome versions of suspend logic into just one.  It'll be easier to
keep things right from now on.

  - Now usbcore _always_ calls hcd->hub_suspend as needed, instead of
    only when USB_SUSPEND is enabled:
     * Those root hub methods are now called from hub suspend/resume;
       no more skipping between layers during device suspend/resume;
     * It now handles cases allowed by sysfs or autosuspended root hubs,
       by forcing the hub interface to resume too.

  - All devices, including virtual root hubs, now get the same treatment
    on their resume paths ... including re-activating all their interfaces.

Plus it gets rid of those stub copies of usb_{suspend,resume}_device(), and
updates the Kconfig to match the new definition of USB_SUSPEND:  it provides
(a) selective suspend, downstream from hubs; and (b) remote wakeup, upstream
from any device configuration which supports it.

This calls for minor followup patches for most HCDs (and their PCI glue).

Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@suse.de>

 drivers/usb/core/Kconfig |   11 ++-
 drivers/usb/core/hub.c   |  163 +++++++++++++++++++++++++----------------------
 2 files changed, 97 insertions(+), 77 deletions(-)
parent 979d5199
Loading
Loading
Loading
Loading
+7 −4
Original line number Diff line number Diff line
@@ -61,14 +61,17 @@ config USB_DYNAMIC_MINORS
	  If you are unsure about this, say N here.

config USB_SUSPEND
	bool "USB suspend/resume (EXPERIMENTAL)"
	bool "USB selective suspend/resume and wakeup (EXPERIMENTAL)"
	depends on USB && PM && EXPERIMENTAL
	help
	  If you say Y here, you can use driver calls or the sysfs
	  "power/state" file to suspend or resume individual USB
	  peripherals.  There are many related features, such as
	  remote wakeup and driver-specific suspend processing, that
	  may not yet work as expected.
	  peripherals.

	  Also, USB "remote wakeup" signaling is supported, whereby some
	  USB devices (like keyboards and network adapters) can wake up
	  their parent hub.  That wakeup cascades up the USB tree, and
	  could wake the system from states like suspend-to-RAM.

	  If you are unsure about this, say N here.

+90 −73
Original line number Diff line number Diff line
@@ -1612,7 +1612,7 @@ static int hub_port_suspend(struct usb_hub *hub, int port1,
 */
static int __usb_suspend_device (struct usb_device *udev, int port1)
{
	int	status;
	int	status = 0;

	/* caller owns the udev device lock */
	if (port1 < 0)
@@ -1638,21 +1638,10 @@ static int __usb_suspend_device (struct usb_device *udev, int port1)
		}
	}

	/* "global suspend" of the HC-to-USB interface (root hub), or
	 * "selective suspend" of just one hub-device link.
	/* we only change a device's upstream USB link.
	 * root hubs have no upstream USB link.
	 */
	if (!udev->parent) {
		struct usb_bus	*bus = udev->bus;
		if (bus && bus->op->hub_suspend) {
			status = bus->op->hub_suspend (bus);
			if (status == 0) {
				dev_dbg(&udev->dev, "usb suspend\n");
				usb_set_device_state(udev,
						USB_STATE_SUSPENDED);
			}
		} else
			status = -EOPNOTSUPP;
	} else
	if (udev->parent)
		status = hub_port_suspend(hdev_to_hub(udev->parent), port1,
				udev);

@@ -1661,6 +1650,8 @@ static int __usb_suspend_device (struct usb_device *udev, int port1)
	return status;
}

#endif

/**
 * usb_suspend_device - suspend a usb device
 * @udev: device that's no longer in active use
@@ -1683,6 +1674,7 @@ static int __usb_suspend_device (struct usb_device *udev, int port1)
 */
int usb_suspend_device(struct usb_device *udev)
{
#ifdef	CONFIG_USB_SUSPEND
	int	port1, status;

	port1 = locktree(udev);
@@ -1692,8 +1684,14 @@ int usb_suspend_device(struct usb_device *udev)
	status = __usb_suspend_device(udev, port1);
	usb_unlock_device(udev);
	return status;
#else
	/* NOTE:  udev->state unchanged, it's not lying ... */
	udev->dev.power.power_state = PMSG_SUSPEND;
	return 0;
#endif
}


/*
 * If the USB "suspend" state is in use (rather than "global suspend"),
 * many devices will be individually taken out of suspend state using
@@ -1702,13 +1700,13 @@ int usb_suspend_device(struct usb_device *udev)
 * resume (by host) or remote wakeup (by device) ... now see what changed
 * in the tree that's rooted at this device.
 */
static int finish_port_resume(struct usb_device *udev)
static int finish_device_resume(struct usb_device *udev)
{
	int	status;
	u16	devstatus;

	/* caller owns the udev device lock */
	dev_dbg(&udev->dev, "usb resume\n");
	dev_dbg(&udev->dev, "finish resume\n");

	/* usb ch9 identifies four variants of SUSPENDED, based on what
	 * state the device resumes to.  Linux currently won't see the
@@ -1718,7 +1716,6 @@ static int finish_port_resume(struct usb_device *udev)
	usb_set_device_state(udev, udev->actconfig
			? USB_STATE_CONFIGURED
			: USB_STATE_ADDRESS);
	udev->dev.power.power_state = PMSG_ON;

 	/* 10.5.4.5 says be sure devices in the tree are still there.
 	 * For now let's assume the device didn't go crazy on resume,
@@ -1734,7 +1731,8 @@ static int finish_port_resume(struct usb_device *udev)
		int		(*resume)(struct device *);

		le16_to_cpus(&devstatus);
		if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) {
		if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)
				&& udev->parent) {
			status = usb_control_msg(udev,
					usb_sndctrlpipe(udev, 0),
					USB_REQ_CLEAR_FEATURE,
@@ -1764,6 +1762,8 @@ static int finish_port_resume(struct usb_device *udev)
	return status;
}

#ifdef	CONFIG_USB_SUSPEND

static int
hub_port_resume(struct usb_hub *hub, int port1, struct usb_device *udev)
{
@@ -1809,7 +1809,7 @@ hub_port_resume(struct usb_hub *hub, int port1, struct usb_device *udev)
			/* TRSMRCY = 10 msec */
			msleep(10);
			if (udev)
				status = finish_port_resume(udev);
				status = finish_device_resume(udev);
		}
	}
	if (status < 0)
@@ -1818,7 +1818,7 @@ hub_port_resume(struct usb_hub *hub, int port1, struct usb_device *udev)
	return status;
}

static int hub_resume (struct usb_interface *intf);
#endif

/**
 * usb_resume_device - re-activate a suspended usb device
@@ -1841,35 +1841,22 @@ int usb_resume_device(struct usb_device *udev)
	if (port1 < 0)
		return port1;

	/* "global resume" of the HC-to-USB interface (root hub), or
	 * selective resume of one hub-to-device port
	 */
	if (!udev->parent) {
		struct usb_bus	*bus = udev->bus;
		if (bus && bus->op->hub_resume) {
			status = bus->op->hub_resume (bus);
		} else
			status = -EOPNOTSUPP;
		if (status == 0) {
			dev_dbg(&udev->dev, "usb resume\n");
			/* TRSMRCY = 10 msec */
			msleep(10);
			usb_set_device_state (udev, USB_STATE_CONFIGURED);
			udev->dev.power.power_state = PMSG_ON;
			status = hub_resume (udev
					->actconfig->interface[0]);
		}
	} else if (udev->state == USB_STATE_SUSPENDED) {
#ifdef	CONFIG_USB_SUSPEND
	/* selective resume of one downstream hub-to-device port */
	if (udev->parent) {
		if (udev->state == USB_STATE_SUSPENDED) {
			// NOTE swsusp may bork us, device state being wrong...
			// NOTE this fails if parent is also suspended...
			status = hub_port_resume(hdev_to_hub(udev->parent),
					port1, udev);
	} else {
		} else
			status = 0;
	}
	if (status < 0) {
	} else
#endif
		status = finish_device_resume(udev);
	if (status < 0)
		dev_dbg(&udev->dev, "can't resume, status %d\n",
			status);
	}

	usb_unlock_device(udev);

@@ -1886,6 +1873,8 @@ static int remote_wakeup(struct usb_device *udev)
{
	int	status = 0;

#ifdef	CONFIG_USB_SUSPEND

	/* don't repeat RESUME sequence if this device
	 * was already woken up by some other task
	 */
@@ -1894,9 +1883,10 @@ static int remote_wakeup(struct usb_device *udev)
		dev_dbg(&udev->dev, "RESUME (wakeup)\n");
		/* TRSMRCY = 10 msec */
		msleep(10);
		status = finish_port_resume(udev);
		status = finish_device_resume(udev);
	}
	up(&udev->serialize);
#endif
	return status;
}

@@ -1911,12 +1901,32 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
		struct usb_device	*udev;

		udev = hdev->children [port1-1];
		if (udev && udev->state != USB_STATE_SUSPENDED) {
		if (udev && (udev->dev.power.power_state.event
					== PM_EVENT_ON
#ifdef	CONFIG_USB_SUSPEND
				|| udev->state != USB_STATE_SUSPENDED
#endif
				)) {
			dev_dbg(&intf->dev, "port %d nyet suspended\n", port1);
			return -EBUSY;
		}
	}

	/* "global suspend" of the downstream HC-to-USB interface */
	if (!hdev->parent) {
		struct usb_bus	*bus = hdev->bus;
		if (bus && bus->op->hub_suspend) {
			int	status = bus->op->hub_suspend (bus);

			if (status != 0) {
				dev_dbg(&hdev->dev, "'global' suspend %d\n",
					status);
				return status;
			}
		} else
			return -EOPNOTSUPP;
	}

	/* stop khubd and related activity */
	hub_quiesce(hub);
	return 0;
@@ -1926,9 +1936,36 @@ static int hub_resume(struct usb_interface *intf)
{
	struct usb_device	*hdev = interface_to_usbdev(intf);
	struct usb_hub		*hub = usb_get_intfdata (intf);
	unsigned		port1;
	int			status;

	/* "global resume" of the downstream HC-to-USB interface */
	if (!hdev->parent) {
		struct usb_bus	*bus = hdev->bus;
		if (bus && bus->op->hub_resume) {
			status = bus->op->hub_resume (bus);
			if (status) {
				dev_dbg(&intf->dev, "'global' resume %d\n",
					status);
				return status;
			}
		} else
			return -EOPNOTSUPP;
		if (status == 0) {
			/* TRSMRCY = 10 msec */
			msleep(10);
		}
	}

	hub_activate(hub);

	/* REVISIT:  this recursion probably shouldn't exist.  Remove
	 * this code sometime, after retesting with different root and
	 * external hubs.
	 */
#ifdef	CONFIG_USB_SUSPEND
	{
	unsigned		port1;

	for (port1 = 1; port1 <= hdev->maxchild; port1++) {
		struct usb_device	*udev;
		u16			portstat, portchange;
@@ -1953,7 +1990,7 @@ static int hub_resume(struct usb_interface *intf)
		if (portstat & USB_PORT_STAT_SUSPEND)
			status = hub_port_resume(hub, port1, udev);
		else {
			status = finish_port_resume(udev);
			status = finish_device_resume(udev);
			if (status < 0) {
				dev_dbg(&intf->dev, "resume port %d --> %d\n",
					port1, status);
@@ -1962,8 +1999,8 @@ static int hub_resume(struct usb_interface *intf)
		}
		up(&udev->serialize);
	}
	hub->resume_root_hub = 0;
	hub_activate(hub);
	}
#endif
	return 0;
}

@@ -1987,26 +2024,6 @@ void usb_resume_root_hub(struct usb_device *hdev)
	kick_khubd(hub);
}

#else	/* !CONFIG_USB_SUSPEND */

int usb_suspend_device(struct usb_device *udev)
{
	/* state does NOT lie by saying it's USB_STATE_SUSPENDED! */
	return 0;
}

int usb_resume_device(struct usb_device *udev)
{
	udev->dev.power.power_state.event = PM_EVENT_ON;
	return 0;
}

#define	hub_suspend		NULL
#define	hub_resume		NULL
#define	remote_wakeup(x)	0

#endif	/* CONFIG_USB_SUSPEND */

EXPORT_SYMBOL(usb_suspend_device);
EXPORT_SYMBOL(usb_resume_device);