[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: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>

 drivers/usb/core/Kconfig |   11 ++-
 drivers/usb/core/hub.c   |  163 +++++++++++++++++++++++++----------------------
 2 files changed, 97 insertions(+), 77 deletions(-)
This commit is contained in:
David Brownell 2005-09-22 22:37:29 -07:00 committed by Greg Kroah-Hartman
parent 979d5199fe
commit f3f3253d64
2 changed files with 97 additions and 77 deletions

View file

@ -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.

View file

@ -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);
#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
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) {
// NOTE this fails if parent is also suspended...
status = hub_port_resume(hdev_to_hub(udev->parent),
port1, udev);
} else {
status = 0;
}
if (status < 0) {
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);