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

[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
...@@ -61,14 +61,17 @@ config USB_DYNAMIC_MINORS ...@@ -61,14 +61,17 @@ config USB_DYNAMIC_MINORS
If you are unsure about this, say N here. If you are unsure about this, say N here.
config USB_SUSPEND config USB_SUSPEND
bool "USB suspend/resume (EXPERIMENTAL)" bool "USB selective suspend/resume and wakeup (EXPERIMENTAL)"
depends on USB && PM && EXPERIMENTAL depends on USB && PM && EXPERIMENTAL
help help
If you say Y here, you can use driver calls or the sysfs If you say Y here, you can use driver calls or the sysfs
"power/state" file to suspend or resume individual USB "power/state" file to suspend or resume individual USB
peripherals. There are many related features, such as peripherals.
remote wakeup and driver-specific suspend processing, that
may not yet work as expected. 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. If you are unsure about this, say N here.
......
...@@ -1612,7 +1612,7 @@ static int hub_port_suspend(struct usb_hub *hub, int port1, ...@@ -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) static int __usb_suspend_device (struct usb_device *udev, int port1)
{ {
int status; int status = 0;
/* caller owns the udev device lock */ /* caller owns the udev device lock */
if (port1 < 0) if (port1 < 0)
...@@ -1638,21 +1638,10 @@ static int __usb_suspend_device (struct usb_device *udev, int port1) ...@@ -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 /* we only change a device's upstream USB link.
* "selective suspend" of just one hub-device link. * root hubs have no upstream USB link.
*/ */
if (!udev->parent) { 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
status = hub_port_suspend(hdev_to_hub(udev->parent), port1, status = hub_port_suspend(hdev_to_hub(udev->parent), port1,
udev); udev);
...@@ -1661,6 +1650,8 @@ static int __usb_suspend_device (struct usb_device *udev, int port1) ...@@ -1661,6 +1650,8 @@ static int __usb_suspend_device (struct usb_device *udev, int port1)
return status; return status;
} }
#endif
/** /**
* usb_suspend_device - suspend a usb device * usb_suspend_device - suspend a usb device
* @udev: device that's no longer in active use * @udev: device that's no longer in active use
...@@ -1683,6 +1674,7 @@ static int __usb_suspend_device (struct usb_device *udev, int port1) ...@@ -1683,6 +1674,7 @@ static int __usb_suspend_device (struct usb_device *udev, int port1)
*/ */
int usb_suspend_device(struct usb_device *udev) int usb_suspend_device(struct usb_device *udev)
{ {
#ifdef CONFIG_USB_SUSPEND
int port1, status; int port1, status;
port1 = locktree(udev); port1 = locktree(udev);
...@@ -1692,8 +1684,14 @@ int usb_suspend_device(struct usb_device *udev) ...@@ -1692,8 +1684,14 @@ int usb_suspend_device(struct usb_device *udev)
status = __usb_suspend_device(udev, port1); status = __usb_suspend_device(udev, port1);
usb_unlock_device(udev); usb_unlock_device(udev);
return status; 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"), * If the USB "suspend" state is in use (rather than "global suspend"),
* many devices will be individually taken out of suspend state using * many devices will be individually taken out of suspend state using
...@@ -1702,13 +1700,13 @@ int usb_suspend_device(struct usb_device *udev) ...@@ -1702,13 +1700,13 @@ int usb_suspend_device(struct usb_device *udev)
* resume (by host) or remote wakeup (by device) ... now see what changed * resume (by host) or remote wakeup (by device) ... now see what changed
* in the tree that's rooted at this device. * 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; int status;
u16 devstatus; u16 devstatus;
/* caller owns the udev device lock */ /* 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 /* usb ch9 identifies four variants of SUSPENDED, based on what
* state the device resumes to. Linux currently won't see the * state the device resumes to. Linux currently won't see the
...@@ -1718,7 +1716,6 @@ static int finish_port_resume(struct usb_device *udev) ...@@ -1718,7 +1716,6 @@ static int finish_port_resume(struct usb_device *udev)
usb_set_device_state(udev, udev->actconfig usb_set_device_state(udev, udev->actconfig
? USB_STATE_CONFIGURED ? USB_STATE_CONFIGURED
: USB_STATE_ADDRESS); : USB_STATE_ADDRESS);
udev->dev.power.power_state = PMSG_ON;
/* 10.5.4.5 says be sure devices in the tree are still there. /* 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, * 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) ...@@ -1734,7 +1731,8 @@ static int finish_port_resume(struct usb_device *udev)
int (*resume)(struct device *); int (*resume)(struct device *);
le16_to_cpus(&devstatus); 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, status = usb_control_msg(udev,
usb_sndctrlpipe(udev, 0), usb_sndctrlpipe(udev, 0),
USB_REQ_CLEAR_FEATURE, USB_REQ_CLEAR_FEATURE,
...@@ -1764,6 +1762,8 @@ static int finish_port_resume(struct usb_device *udev) ...@@ -1764,6 +1762,8 @@ static int finish_port_resume(struct usb_device *udev)
return status; return status;
} }
#ifdef CONFIG_USB_SUSPEND
static int static int
hub_port_resume(struct usb_hub *hub, int port1, struct usb_device *udev) 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) ...@@ -1809,7 +1809,7 @@ hub_port_resume(struct usb_hub *hub, int port1, struct usb_device *udev)
/* TRSMRCY = 10 msec */ /* TRSMRCY = 10 msec */
msleep(10); msleep(10);
if (udev) if (udev)
status = finish_port_resume(udev); status = finish_device_resume(udev);
} }
} }
if (status < 0) if (status < 0)
...@@ -1818,7 +1818,7 @@ hub_port_resume(struct usb_hub *hub, int port1, struct usb_device *udev) ...@@ -1818,7 +1818,7 @@ hub_port_resume(struct usb_hub *hub, int port1, struct usb_device *udev)
return status; return status;
} }
static int hub_resume (struct usb_interface *intf); #endif
/** /**
* usb_resume_device - re-activate a suspended usb device * usb_resume_device - re-activate a suspended usb device
...@@ -1841,35 +1841,22 @@ int usb_resume_device(struct usb_device *udev) ...@@ -1841,35 +1841,22 @@ int usb_resume_device(struct usb_device *udev)
if (port1 < 0) if (port1 < 0)
return port1; return port1;
/* "global resume" of the HC-to-USB interface (root hub), or #ifdef CONFIG_USB_SUSPEND
* selective resume of one hub-to-device port /* selective resume of one downstream hub-to-device port */
*/ if (udev->parent) {
if (!udev->parent) { if (udev->state == USB_STATE_SUSPENDED) {
struct usb_bus *bus = udev->bus; // NOTE swsusp may bork us, device state being wrong...
if (bus && bus->op->hub_resume) { // NOTE this fails if parent is also suspended...
status = bus->op->hub_resume (bus); status = hub_port_resume(hdev_to_hub(udev->parent),
port1, udev);
} else } else
status = -EOPNOTSUPP; status = 0;
if (status == 0) { } else
dev_dbg(&udev->dev, "usb resume\n"); #endif
/* TRSMRCY = 10 msec */ status = finish_device_resume(udev);
msleep(10); if (status < 0)
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) {
dev_dbg(&udev->dev, "can't resume, status %d\n", dev_dbg(&udev->dev, "can't resume, status %d\n",
status); status);
}
usb_unlock_device(udev); usb_unlock_device(udev);
...@@ -1886,6 +1873,8 @@ static int remote_wakeup(struct usb_device *udev) ...@@ -1886,6 +1873,8 @@ static int remote_wakeup(struct usb_device *udev)
{ {
int status = 0; int status = 0;
#ifdef CONFIG_USB_SUSPEND
/* don't repeat RESUME sequence if this device /* don't repeat RESUME sequence if this device
* was already woken up by some other task * was already woken up by some other task
*/ */
...@@ -1894,9 +1883,10 @@ static int remote_wakeup(struct usb_device *udev) ...@@ -1894,9 +1883,10 @@ static int remote_wakeup(struct usb_device *udev)
dev_dbg(&udev->dev, "RESUME (wakeup)\n"); dev_dbg(&udev->dev, "RESUME (wakeup)\n");
/* TRSMRCY = 10 msec */ /* TRSMRCY = 10 msec */
msleep(10); msleep(10);
status = finish_port_resume(udev); status = finish_device_resume(udev);
} }
up(&udev->serialize); up(&udev->serialize);
#endif
return status; return status;
} }
...@@ -1911,12 +1901,32 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg) ...@@ -1911,12 +1901,32 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg)
struct usb_device *udev; struct usb_device *udev;
udev = hdev->children [port1-1]; 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); dev_dbg(&intf->dev, "port %d nyet suspended\n", port1);
return -EBUSY; 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 */ /* stop khubd and related activity */
hub_quiesce(hub); hub_quiesce(hub);
return 0; return 0;
...@@ -1926,9 +1936,36 @@ static int hub_resume(struct usb_interface *intf) ...@@ -1926,9 +1936,36 @@ static int hub_resume(struct usb_interface *intf)
{ {
struct usb_device *hdev = interface_to_usbdev(intf); struct usb_device *hdev = interface_to_usbdev(intf);
struct usb_hub *hub = usb_get_intfdata (intf); struct usb_hub *hub = usb_get_intfdata (intf);
unsigned port1;
int status; 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++) { for (port1 = 1; port1 <= hdev->maxchild; port1++) {
struct usb_device *udev; struct usb_device *udev;
u16 portstat, portchange; u16 portstat, portchange;
...@@ -1953,7 +1990,7 @@ static int hub_resume(struct usb_interface *intf) ...@@ -1953,7 +1990,7 @@ static int hub_resume(struct usb_interface *intf)
if (portstat & USB_PORT_STAT_SUSPEND) if (portstat & USB_PORT_STAT_SUSPEND)
status = hub_port_resume(hub, port1, udev); status = hub_port_resume(hub, port1, udev);
else { else {
status = finish_port_resume(udev); status = finish_device_resume(udev);
if (status < 0) { if (status < 0) {
dev_dbg(&intf->dev, "resume port %d --> %d\n", dev_dbg(&intf->dev, "resume port %d --> %d\n",
port1, status); port1, status);
...@@ -1962,8 +1999,8 @@ static int hub_resume(struct usb_interface *intf) ...@@ -1962,8 +1999,8 @@ static int hub_resume(struct usb_interface *intf)
} }
up(&udev->serialize); up(&udev->serialize);
} }
hub->resume_root_hub = 0; }
hub_activate(hub); #endif
return 0; return 0;
} }
...@@ -1987,26 +2024,6 @@ void usb_resume_root_hub(struct usb_device *hdev) ...@@ -1987,26 +2024,6 @@ void usb_resume_root_hub(struct usb_device *hdev)
kick_khubd(hub); 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_suspend_device);
EXPORT_SYMBOL(usb_resume_device); EXPORT_SYMBOL(usb_resume_device);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment