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

[PATCH] USB: add CONFIG_USB_SUSPEND

This is the core of the USB_SUSPEND functionality.  Please merge.

This adds an experimental CONFIG_USB_SUSPEND option, which supports the
USB "suspend" state.  Linux-USB hosts have previously ignored that state.

    -	New driver API calls, usb_suspend_device() and its
	sibling usb_resume_device().

    -	Access to those calls through sysfs, such as
		echo -n 2 > power/state
		echo -n 0 > power/state

That can be used to reduce the power consumption of any given USB device,
then re-activate it later.  Eventually, most USB device drivers should
probably suspend idle USB devices.

One problem with this patch:  USB drivers without suspend() callbacks
may badly misbehave.  Right now only hub drivers know suspend().  If the
driver core didn't self-deadlock when we try it, unbinding those drivers
from those devices (then re-enumerating on resume) would be perfect...
the current compromise is just to emit a warning message.

In conjunction with host controller driver support (already merged for
OHCI and EHCI), PCI host controllers will issue the PME# wakeup signal
when a USB keyboard starts remote wakeup signaling.  (But the keyboard
wasn't usable later, since HID doesn't try to suspend.)

I understand some ACPI patches are circulating, and maybe already in
the MM tree, to make a suspended system wake up given PME# signaling.
It'd be great if someone made that work transparently with USB, but
for now I'm told it'll need some sysfs setup first.
Signed-off-by: default avatarDavid Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: default avatarGreg Kroah-Hartman <greg@kroah.com>
parent 8d784571
......@@ -251,6 +251,7 @@
!Edrivers/usb/core/message.c
!Edrivers/usb/core/file.c
!Edrivers/usb/core/usb.c
!Edrivers/usb/core/hub.c
</chapter>
<chapter><title>Host Controller APIs</title>
......
......@@ -60,3 +60,14 @@ config USB_DYNAMIC_MINORS
If you are unsure about this, say N here.
config USB_SUSPEND
bool "USB suspend/resume (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.
If you are unsure about this, say N here.
......@@ -1239,7 +1239,490 @@ static int hub_port_disable(struct usb_device *hdev, int port)
#ifdef CONFIG_USB_SUSPEND
/* no USB_SUSPEND yet! */
/*
* Selective port suspend reduces power; most suspended devices draw
* less than 500 uA. It's also used in OTG, along with remote wakeup.
* All devices below the suspended port are also suspended.
*
* Devices leave suspend state when the host wakes them up. Some devices
* also support "remote wakeup", where the device can activate the USB
* tree above them to deliver data, such as a keypress or packet. In
* some cases, this wakes the USB host.
*/
static int hub_port_suspend(struct usb_device *hdev, int port)
{
int status;
struct usb_device *udev;
udev = hdev->children[port - 1];
// dev_dbg(hubdev(hdev), "suspend port %d\n", port);
/* enable remote wakeup when appropriate; this lets the device
* wake up the upstream hub (including maybe the root hub).
*
* NOTE: OTG devices may issue remote wakeup (or SRP) even when
* we don't explicitly enable it here.
*/
if (udev->actconfig
// && FIXME (remote wakeup enabled on this bus)
// ... currently assuming it's always appropriate
&& (udev->actconfig->desc.bmAttributes
& USB_CONFIG_ATT_WAKEUP) != 0) {
status = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
USB_DEVICE_REMOTE_WAKEUP, 0,
NULL, 0,
USB_CTRL_SET_TIMEOUT);
if (status)
dev_dbg(&udev->dev,
"won't remote wakeup, status %d\n",
status);
}
/* see 7.1.7.6 */
status = set_port_feature(hdev, port, USB_PORT_FEAT_SUSPEND);
if (status) {
dev_dbg(hubdev(hdev),
"can't suspend port %d, status %d\n",
port, status);
/* paranoia: "should not happen" */
(void) usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
USB_REQ_CLEAR_FEATURE, USB_RECIP_DEVICE,
USB_DEVICE_REMOTE_WAKEUP, 0,
NULL, 0,
USB_CTRL_SET_TIMEOUT);
} else {
/* device has up to 10 msec to fully suspend */
dev_dbg(&udev->dev, "usb suspend\n");
udev->state = USB_STATE_SUSPENDED;
msleep(10);
}
return status;
}
/*
* Devices on USB hub ports have only one "suspend" state, corresponding
* to ACPI D2 (PM_SUSPEND_MEM), "may cause the device to lose some context".
* State transitions include:
*
* - suspend, resume ... when the VBUS power link stays live
* - suspend, disconnect ... VBUS lost
*
* Once VBUS drop breaks the circuit, the port it's using has to go through
* normal re-enumeration procedures, starting with enabling VBUS power.
* Other than re-initializing the hub (plug/unplug, except for root hubs),
* Linux (2.6) currently has NO mechanisms to initiate that: no khubd
* timer, no SRP, no requests through sysfs.
*/
static int __usb_suspend_device (struct usb_device *udev, int port, u32 state)
{
int status;
if (port < 0)
return port;
/* NOTE: udev->serialize released on all real returns! */
if (state <= udev->dev.power.power_state
|| state < PM_SUSPEND_MEM
|| udev->state == USB_STATE_SUSPENDED
|| udev->state == USB_STATE_NOTATTACHED) {
up(&udev->serialize);
return 0;
}
/* suspend interface drivers; if this is a hub, it
* suspends the child devices
*/
if (udev->actconfig) {
int i;
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
struct usb_interface *intf;
struct usb_driver *driver;
intf = udev->actconfig->interface[i];
if (state <= intf->dev.power.power_state)
continue;
if (!intf->dev.driver)
continue;
driver = to_usb_driver(intf->dev.driver);
if (driver->suspend) {
status = driver->suspend(intf, state);
if (intf->dev.power.power_state != state
|| status)
dev_err(&intf->dev,
"suspend %d fail, code %d\n",
state, status);
}
/* only drivers with suspend() can ever resume();
* and after power loss, even they won't.
* bus_rescan_devices() can rebind drivers later.
*
* FIXME the PM core self-deadlocks when unbinding
* drivers during suspend/resume ... everything grabs
* dpm_sem (not a spinlock, ugh). we want to unbind,
* since we know every driver's probe/disconnect works
* even for drivers that can't suspend.
*/
if (!driver->suspend || state > PM_SUSPEND_MEM) {
#if 1
dev_warn(&intf->dev, "resume is unsafe!\n");
#else
down_write(&usb_bus_type.rwsem);
device_release_driver(&intf->dev);
up_write(&usb_bus_type.rwsem);
#endif
}
}
}
/*
* FIXME this needs port power off call paths too, to help force
* USB into the "generic" PM model. At least for devices on
* ports that aren't using ganged switching (usually root hubs).
*
* NOTE: SRP-capable links should adopt more aggressive poweroff
* policies (when HNP doesn't apply) once we have mechanisms to
* turn power back on! (Likely not before 2.7...)
*/
if (state > PM_SUSPEND_MEM) {
dev_warn(&udev->dev, "no poweroff yet, suspending instead\n");
state = PM_SUSPEND_MEM;
}
/* "global suspend" of the HC-to-USB interface (root hub), or
* "selective suspend" of just one hub-device link.
*/
if (!udev->parent) {
struct usb_bus *bus = udev->bus;
if (bus && bus->op->hub_suspend)
status = bus->op->hub_suspend (bus);
else
status = -EOPNOTSUPP;
} else
status = hub_port_suspend(udev->parent, port + 1);
if (status == 0)
udev->dev.power.power_state = state;
up(&udev->serialize);
return status;
}
/**
* usb_suspend_device - suspend a usb device
* @udev: device that's no longer in active use
* @state: PM_SUSPEND_MEM to suspend
* Context: must be able to sleep; device not locked
*
* Suspends a USB device that isn't in active use, conserving power.
* Devices may wake out of a suspend, if anything important happens,
* using the remote wakeup mechanism. They may also be taken out of
* suspend by the host, using usb_resume_device(). It's also routine
* to disconnect devices while they are suspended.
*
* Suspending OTG devices may trigger HNP, if that's been enabled
* between a pair of dual-role devices. That will change roles, such
* as from A-Host to A-Peripheral or from B-Host back to B-Peripheral.
*
* Returns 0 on success, else negative errno.
*/
int usb_suspend_device(struct usb_device *udev, u32 state)
{
return __usb_suspend_device(udev, locktree(udev), state);
}
/*
* hardware resume signaling is finished, either because of selective
* 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)
{
int status;
u16 devstatus;
/* caller owns udev->serialize */
dev_dbg(&udev->dev, "usb resume\n");
udev->dev.power.power_state = PM_SUSPEND_ON;
/* usb ch9 identifies four variants of SUSPENDED, based on what
* state the device resumes to. Linux currently won't see the
* first two on the host side; they'd be inside hub_port_init()
* during many timeouts, but khubd can't suspend until later.
*/
udev->state = udev->actconfig
? USB_STATE_CONFIGURED
: USB_STATE_ADDRESS;
/* 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,
* and device drivers will know about any resume quirks.
*/
status = usb_get_status(udev, USB_RECIP_DEVICE, 0, &devstatus);
if (status < 0)
dev_dbg(&udev->dev,
"gone after usb resume? status %d\n",
status);
else if (udev->actconfig) {
unsigned i;
le16_to_cpus(&devstatus);
if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) {
status = usb_control_msg(udev,
usb_sndctrlpipe(udev, 0),
USB_REQ_CLEAR_FEATURE,
USB_RECIP_DEVICE,
USB_DEVICE_REMOTE_WAKEUP, 0,
NULL, 0,
USB_CTRL_SET_TIMEOUT);
if (status) {
dev_dbg(&udev->dev, "disable remote "
"wakeup, status %d\n", status);
status = 0;
}
}
/* resume interface drivers; if this is a hub, it
* resumes the child devices
*/
for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) {
struct usb_interface *intf;
struct usb_driver *driver;
intf = udev->actconfig->interface[i];
if (intf->dev.power.power_state == PM_SUSPEND_ON)
continue;
if (!intf->dev.driver) {
/* FIXME maybe force to alt 0 */
continue;
}
driver = to_usb_driver(intf->dev.driver);
/* bus_rescan_devices() may rebind drivers */
if (!driver->resume)
continue;
/* can we do better than just logging errors? */
status = driver->resume(intf);
if (intf->dev.power.power_state != PM_SUSPEND_ON
|| status)
dev_dbg(&intf->dev,
"resume fail, state %d code %d\n",
intf->dev.power.power_state, status);
}
status = 0;
} else if (udev->devnum <= 0) {
dev_dbg(&udev->dev, "bogus resume!\n");
status = -EINVAL;
}
return status;
}
static int
hub_port_resume(struct usb_device *hdev, int port)
{
int status;
struct usb_device *udev;
udev = hdev->children[port - 1];
// dev_dbg(hubdev(hdev), "resume port %d\n", port);
/* see 7.1.7.7; affects power usage, but not budgeting */
status = clear_port_feature(hdev, port, USB_PORT_FEAT_SUSPEND);
if (status) {
dev_dbg(&hdev->actconfig->interface[0]->dev,
"can't resume port %d, status %d\n",
port, status);
} else {
u16 devstatus;
u16 portchange;
/* drive resume for at least 20 msec */
dev_dbg(&udev->dev, "RESUME\n");
msleep(25);
#define LIVE_FLAGS ( USB_PORT_STAT_POWER \
| USB_PORT_STAT_ENABLE \
| USB_PORT_STAT_CONNECTION)
/* Virtual root hubs can trigger on GET_PORT_STATUS to
* stop resume signaling. Then finish the resume
* sequence.
*/
devstatus = portchange = 0;
status = hub_port_status(hdev, port - 1,
&devstatus, &portchange);
if (status < 0
|| (devstatus & LIVE_FLAGS) != LIVE_FLAGS
|| (devstatus & USB_PORT_STAT_SUSPEND) != 0
) {
dev_dbg(&hdev->actconfig->interface[0]->dev,
"port %d status %04x.%04x after resume, %d\n",
port, portchange, devstatus, status);
} else {
/* TRSMRCY = 10 msec */
msleep(10);
status = finish_port_resume(udev);
}
}
if (status < 0)
status = hub_port_disable(hdev, port);
return status;
}
static int hub_resume (struct usb_interface *intf);
/**
* usb_resume_device - re-activate a suspended usb device
* @udev: device to re-activate
* Context: must be able to sleep; device not locked
*
* This will re-activate the suspended device, increasing power usage
* while letting drivers communicate again with its endpoints.
* USB resume explicitly guarantees that the power session between
* the host and the device is the same as it was when the device
* suspended.
*
* Returns 0 on success, else negative errno.
*/
int usb_resume_device(struct usb_device *udev)
{
int port, status;
port = locktree(udev);
if (port < 0)
return port;
/* "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) {
/* TRSMRCY = 10 msec */
msleep(10);
status = hub_resume (bus->root_hub
->actconfig->interface[0]);
}
} else if (udev->state == USB_STATE_SUSPENDED) {
status = hub_port_resume(udev->parent, port + 1);
} else {
status = 0;
udev->dev.power.power_state = PM_SUSPEND_ON;
}
if (status < 0) {
dev_dbg(&udev->dev, "can't resume, status %d\n",
status);
}
up(&udev->serialize);
/* rebind drivers that had no suspend() */
bus_rescan_devices(&usb_bus_type);
return status;
}
static int remote_wakeup(struct usb_device *udev)
{
int status = 0;
/* don't repeat RESUME sequence if this device
* was already woken up by some other task
*/
down(&udev->serialize);
if (udev->state == USB_STATE_SUSPENDED) {
dev_dbg(&udev->dev, "RESUME (wakeup)\n");
/* TRSMRCY = 10 msec */
msleep(10);
status = finish_port_resume(udev);
}
up(&udev->serialize);
return status;
}
static int hub_suspend(struct usb_interface *intf, u32 state)
{
struct usb_hub *hub = usb_get_intfdata (intf);
struct usb_device *hdev = hub->hdev;
unsigned port;
int status;
/* stop khubd and related activity */
hub_quiesce(hub);
/* then suspend every port */
for (port = 0; port < hdev->maxchild; port++) {
struct usb_device *udev;
udev = hdev->children [port];
if (!udev)
continue;
down(&udev->serialize);
status = __usb_suspend_device(udev, port, state);
if (status < 0)
dev_dbg(&intf->dev, "suspend port %d --> %d\n",
port, status);
}
intf->dev.power.power_state = state;
return 0;
}
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 port;
int status;
for (port = 0; port < hdev->maxchild; port++) {
struct usb_device *udev;
u16 portstat, portchange;
udev = hdev->children [port];
status = hub_port_status(hdev, port, &portstat, &portchange);
if (status == 0) {
if (portchange & USB_PORT_STAT_C_SUSPEND) {
clear_port_feature(hdev, port + 1,
USB_PORT_FEAT_C_SUSPEND);
portchange &= ~USB_PORT_STAT_C_SUSPEND;
}
/* let khubd handle disconnects etc */
if (portchange)
continue;
}
if (!udev)
continue;
down (&udev->serialize);
if (portstat & USB_PORT_STAT_SUSPEND)
status = hub_port_resume(hdev, port + 1);
else {
status = finish_port_resume(udev);
if (status < 0)
status = hub_port_disable(hdev, port);
if (status < 0)
dev_dbg(&intf->dev, "resume port %d --> %d\n",
port, status);
}
up(&udev->serialize);
}
intf->dev.power.power_state = PM_SUSPEND_ON;
hub_reactivate(hub);
return 0;
}
#else /* !CONFIG_USB_SUSPEND */
......
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