Here's the latest version. It's not the all-singing, all-dancing version, but I think from the perspective of the hub driver it's pretty much functionally complete. You can use sysfs to suspend USB devices:
echo -n 1 > /sys/bus/usb/devices/$ID/power/state
where there's a /sys/bus/usb/devices/bDeviceClass file. If that device is capable of remote wakeup, it'll be able to un-suspend itself ... so long as its hooked up to an external hub, or to an OHCI (or, now, dummy_hcd!) root hub.
Changes since the previous version:
- When you suspend a device, suspend() methods for its interface
drivers are invoked. Similarly on resume. I decided it was
not practical to change the driver model at this time. - The hub driver suspend() method suspends any un-suspended
child devices. Yes, recursively ... potentially an issue,
although the recursion is limited (root hub, five external
hubs, and a final non-hub device). - The hub driver resume() method re-activates the hub status
URB, so you can suspend one, connect a device, and then see
the new device after the hub wakes up. - Top down tree locking. Yes, the need for this shows up pretty
quickly: "echo -n 0 > /sys/bus/usb/devices/$ID/power/state"
would otherwise have both khubd and your shell task both try
to resume the port. Locking ensures it's only the shell task
that's allowed to proceed. - Defines new HCD root hub methods to globally suspend/resume
the bus. So "echo -n 3 > /sys/bus/usb/devices/usb$N/power/state"
can suspend a root hub.- Addresses Oliver's feedback.
In short, it's worth taking a look at, and maybe starting to make some other USB drivers understand suspend/resume/wakeup. Examples:
* The HID driver should re-issue its urb on resume(), like HUB,
so that keyboards and mice will work after resume (which they
may well have initiated, using remote wakeup).* Various network drivers could start supporting wake-on-LAN.
* Many drivers could implement "suspend if not opened", for
devices that are single-function. For higher powered devices
that could easily save a couple Watts per device.* EHCI and UHCI don't yet understand how to do remote wakeup.
* All HCDs need patches before they'll enter global suspend/resume
based on sysfs requests.Note that having USB remote wakeup trigger PCI device wakeup (and hence maybe whole-system wakeup) is a separate issue ... though it relies on root hubs being able to enter global suspend (which only UHCI will do just now).
This is against Greg's current-as-of-this-morning BK tree.
- Dave
--- 1.3/drivers/usb/core/Kconfig Fri May 30 02:36:57 2003
+++ edited/drivers/usb/core/Kconfig Mon May 3 11:06:09 2004
@@ -60,3 +60,14 @@
If you are unsure about this, say N here.
+config USB_SUSPEND
+ bool "USB suspend/resume (EXPERIMENTAL)"
+ depends on USB && EXPERIMENTAL
+ help
+ If you say Y here, you can use the sysfs "power/state" file
+ to suspend or resume individual usb devices. There are many
+ related features, such as remote wakeup or suspending hubs
+ (particularly root hubs), that may not work properly yet.
+
+ If you are unsure about this, say N here.
+
--- 1.84/drivers/usb/core/hcd.c Wed Apr 21 03:46:29 2004
+++ edited/drivers/usb/core/hcd.c Mon May 3 11:06:09 2004
@@ -1432,6 +1432,41 @@
/*-------------------------------------------------------------------------*/
+#ifdef CONFIG_USB_SUSPEND
+
+static int hcd_hub_suspend (struct usb_bus *bus)
+{
+ struct usb_hcd *hcd;
+ unsigned i;
+
+ hcd = container_of (bus, struct usb_hcd, self);
+ for (i = 0; i < bus->root_hub->maxchild; i++) {
+ struct usb_device *dev;
+
+ dev = bus->root_hub->children[i];
+ if (!dev || dev->state == USB_STATE_SUSPENDED)
+ continue;
+ dev_warn (&dev->dev, "not yet suspended ...\n");
+ }
+ if (hcd->driver->hub_suspend)
+ return hcd->driver->hub_suspend (hcd);
+ return 0;
+}
+
+static int hcd_hub_resume (struct usb_bus *bus)
+{
+ struct usb_hcd *hcd;
+
+ hcd = container_of (bus, struct usb_hcd, self);
+ if (hcd->driver->hub_resume)
+ return hcd->driver->hub_resume (hcd);
+ return 0;
+}
+
+#endif
+
+/*-------------------------------------------------------------------------*/
+
/* called by khubd, rmmod, apmd, or other thread for hcd-private cleanup.
* we're guaranteed that the device is fully quiesced. also, that each
* endpoint has been hcd_endpoint_disabled.
@@ -1486,6 +1521,10 @@
.buffer_alloc = hcd_buffer_alloc,
.buffer_free = hcd_buffer_free,
.disable = hcd_endpoint_disable,
+#ifdef CONFIG_USB_SUSPEND
+ .hub_suspend = hcd_hub_suspend,
+ .hub_resume = hcd_hub_resume,
+#endif
};
EXPORT_SYMBOL (usb_hcd_operations);
--- 1.40/drivers/usb/core/hcd.h Wed Apr 28 07:01:03 2004
+++ edited/drivers/usb/core/hcd.h Mon May 3 11:06:09 2004
@@ -152,6 +152,10 @@
void *addr, dma_addr_t dma);
void (*disable)(struct usb_device *udev, int bEndpointAddress);
+
+ /* global suspend/resume of bus */
+ int (*hub_suspend)(struct usb_bus *);
+ int (*hub_resume)(struct usb_bus *);
};
/* each driver provides one of these, and hardware init support */
@@ -203,6 +207,8 @@
int (*hub_control) (struct usb_hcd *hcd,
u16 typeReq, u16 wValue, u16 wIndex,
char *buf, u16 wLength);
+ int (*hub_suspend)(struct usb_hcd *);
+ int (*hub_resume)(struct usb_hcd *);
};
extern void usb_hcd_giveback_urb (struct usb_hcd *hcd, struct urb *urb, struct
pt_regs *regs);
--- 1.95/drivers/usb/core/hub.c Tue Apr 27 01:19:29 2004
+++ edited/drivers/usb/core/hub.c Mon May 3 11:12:52 2004
@@ -9,6 +9,11 @@
*/
#include <linux/config.h>
+#ifdef CONFIG_USB_DEBUG
+ #define DEBUG
+#else
+ #undef DEBUG
+#endif
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/module.h>
@@ -19,11 +24,6 @@
#include <linux/slab.h>
#include <linux/smp_lock.h>
#include <linux/ioctl.h>
-#ifdef CONFIG_USB_DEBUG
- #define DEBUG
-#else
- #undef DEBUG
-#endif
#include <linux/usb.h>
#include <linux/usbdevice_fs.h>
#include <linux/suspend.h>
@@ -65,6 +65,8 @@
}
#endif
+#define MSEC_TO_JIFFIES(msec) ((HZ * (msec) + 999) / 1000)
+
/* for dev_info, dev_dbg, etc */
static inline struct device *hubdev (struct usb_device *dev)
{
@@ -267,10 +269,14 @@
}
resubmit:
+ if (hub->intf->dev.power.power_state) {
+ urb->status = -EHOSTUNREACH;
+ goto done;
+ }
if ((status = usb_submit_urb (hub->urb, GFP_ATOMIC)) != 0
/* ENODEV means we raced disconnect() */
&& status != -ENODEV)
- dev_err (&hub->intf->dev, "resubmit --> %d\n", urb->status);
+ dev_err (&hub->intf->dev, "resubmit --> %d\n", status);
if (status == 0)
hub->urb_active = 1;
done:
@@ -962,7 +968,7 @@
return -1;
}
-int hub_port_disable(struct usb_device *hub, int port)
+static int hub_port_disable(struct usb_device *hub, int port)
{
int ret;
@@ -970,10 +976,470 @@
if (ret)
dev_err(hubdev(hub), "cannot disable port %d (err = %d)\n",
port + 1, ret);
-
return ret;
}
+
+#ifdef CONFIG_USB_SUSPEND
+
+/* grab device/port lock, safely freezing its configuration
+ * return the index of that port (zero based).
+ */
+static int locktree (struct usb_device *dev)
+{
+ int t;
+ struct usb_device *hub;
+
+ if (!dev)
+ return -ENODEV;
+
+ /* root hub is always the first lock in the series */
+ hub = dev->parent;
+ if (!hub) {
+ down(&dev->serialize);
+ return 0;
+ }
+
+ /* on the path from root to us, lock everything from
+ * top down, dropping parent locks when not needed
+ */
+ t = locktree (hub);
+ if (t < 0)
+ return t;
+ for (t = 0; t < hub->maxchild; t++) {
+ if (hub->children[t] == dev) {
+ /* when everyone grabs locks top->bottom,
+ * non-overlapping work may be concurrent
+ */
+ down(&dev->serialize);
+ up(&hub->serialize);
+ return t;
+ }
+ }
+ up(&hub->serialize);
+ return -ENODEV;
+}
+
+/*
+ * 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.
+ *
+ * There are also various kinds of hub-wide suspend, sometimes with the
+ * ability to wake up the next hub (or the host itself). When root hubs
+ * suspend, "global" (it's a _really_ small world) suspend may be mentioned;
+ * that suggests using additional power saving techniques.
+ *
+ * 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 involves waking up the USB host.
+ */
+static int
+hub_port_suspend(struct usb_device *hubdev, int port)
+{
+ int status;
+ struct usb_device *dev;
+
+ dev = hubdev->children[port - 1];
+ // dev_dbg(&hub->intf->dev, "suspend port %d\n", port);
+
+ /* enable remote wakeup when appropriate; this lets the device
+ * wake up the upstream hub (including maybe the root hub).
+ */
+ if (dev->actconfig
+ && (dev->actconfig->desc.bmAttributes
+ & USB_CONFIG_ATT_WAKEUP) != 0) {
+ status = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
+ USB_REQ_SET_FEATURE, USB_RECIP_DEVICE,
+ USB_DEVICE_REMOTE_WAKEUP, 0,
+ NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (status)
+ dev_dbg(&dev->dev,
+ "won't remote wakeup, status %d\n",
+ status);
+ }
+
+ /* see 7.1.7.6 */
+ status = set_port_feature(hubdev, port, USB_PORT_FEAT_SUSPEND);
+ if (status) {
+ dev_dbg(&hubdev->actconfig->interface[0]->dev,
+ "can't suspend port %d, status %d\n",
+ port, status);
+ /* paranoia: "should not happen" */
+ (void) usb_control_msg(dev, usb_sndctrlpipe(dev, 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(&dev->dev, "usb suspend\n");
+ dev->state = USB_STATE_SUSPENDED;
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(MSEC_TO_JIFFIES(10));
+ }
+ return status;
+}
+
+static int __usb_suspend_device (struct usb_device *dev, int port, u32 state)
+{
+ int status;
+
+ if (port < 0)
+ return port;
+
+ /* NOTE: dev->serialize released on all real returns! */
+
+ if (dev->state == USB_STATE_SUSPENDED) {
+ up(&dev->serialize);
+ return 0;
+ }
+
+ /* suspend interface drivers; if this is a hub, it
+ * suspends the child devices
+ */
+ if (dev->actconfig) {
+ int i;
+
+ for (i = 0; i < dev->actconfig->desc.bNumInterfaces; i++) {
+ struct usb_interface *intf;
+ struct usb_driver *driver;
+
+ intf = dev->actconfig->interface[i];
+ if (intf->dev.power.power_state)
+ continue;
+ if (!intf->dev.driver)
+ continue;
+ driver = to_usb_driver(intf->dev.driver);
+ status = 0;
+
+ /* can we do better than just logging errors? */
+ if (driver->suspend)
+ status = driver->suspend(intf, state);
+ if (status || intf->dev.power.power_state != state)
+ dev_err(&intf->dev, "suspend fail, code %d\n",
+ status);
+ }
+ }
+
+ /* "global suspend" of the HC-to-USB interface (root hub), or
+ * "selective suspend" of just one hub-device link.
+ */
+ if (!dev->parent) {
+ struct usb_bus *bus = dev->bus;
+ if (bus && bus->op->hub_suspend)
+ status = bus->op->hub_suspend (bus);
+ else
+ status = -EOPNOTSUPP;
+ } else
+ status = hub_port_suspend(dev->parent, port + 1);
+ if (status < 0)
+ dev_dbg (&dev->dev, "can't suspend, status %d\n",
+ status);
+
+ up(&dev->serialize);
+ return status;
+}
+
+/**
+ * usb_suspend_device - suspend a usb device
+ * @dev: device that's no longer in active use
+ * Context: must be able to sleep; dev->serialize not held
+ *
+ * Suspends a USB device that isn't in active use, conserving power.
+ * Devices can wake out of a suspend, if anything important happens,
+ * using the remote wakeup mechanism.
+ *
+ * Returns 0 on success, else negative errno.
+ */
+int usb_suspend_device (struct usb_device *dev)
+{
+ return __usb_suspend_device (dev, locktree (dev), 3);
+}
+
+/*
+ * 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_resume (struct usb_device *dev)
+{
+ int status;
+ u16 devstatus;
+
+ /* caller owns dev->serialize */
+ dev_dbg(&dev->dev, "usb resume\n");
+
+ /* usb ch9 identifies four variants of SUSPENDED, based on
+ * what state the device resumes to. Linux currently won't
+ * use the first two (they'd be inside hub_port_init).
+ */
+ dev->state = dev->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 no subtle bugs...
+ */
+ status = usb_get_status(dev, USB_RECIP_DEVICE, 0, &devstatus);
+ if (status < 0)
+ dev_dbg(&dev->dev,
+ "gone after usb resume? status %d\n",
+ status);
+ else if (dev->actconfig) {
+ unsigned i;
+
+ le16_to_cpus(&devstatus);
+ if (devstatus & (1 << USB_DEVICE_REMOTE_WAKEUP)) {
+ status = usb_control_msg(dev,
+ usb_sndctrlpipe(dev, 0),
+ USB_REQ_CLEAR_FEATURE,
+ USB_RECIP_DEVICE,
+ USB_DEVICE_REMOTE_WAKEUP, 0,
+ NULL, 0,
+ USB_CTRL_SET_TIMEOUT);
+ if (status) {
+ dev_dbg(&dev->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 < dev->actconfig->desc.bNumInterfaces; i++) {
+ struct usb_interface *intf;
+ struct usb_driver *driver;
+
+ intf = dev->actconfig->interface[i];
+ if (!intf->dev.power.power_state)
+ continue;
+ if (!intf->dev.driver)
+ continue;
+ driver = to_usb_driver(intf->dev.driver);
+ status = 0;
+
+ /* can we do better than just logging errors? */
+ if (driver->resume)
+ status = driver->resume(intf);
+ if (status)
+ dev_dbg(&intf->dev, "resume fail, code %d\n",
+ status);
+ }
+ status = 0;
+
+ } else if (dev->devnum <= 0) {
+ dev_dbg(&dev->dev, "bogus resume!\n");
+ status = -EINVAL;
+ }
+ return status;
+}
+
+static int
+hub_port_resume(struct usb_device *hubdev, int port)
+{
+ int status;
+ struct usb_device *dev;
+
+ dev = hubdev->children[port - 1];
+ // dev_dbg(&hub->intf->dev, "resume port %d\n", port);
+
+ /* see 7.1.7.7; affects power usage, but not budgeting */
+ status = clear_port_feature(hubdev, port, USB_PORT_FEAT_SUSPEND);
+ if (status) {
+ dev_dbg(&hubdev->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(&dev->dev, "RESUME\n");
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(MSEC_TO_JIFFIES(25));
+
+#define LIVE_FLAGS ( USB_PORT_STAT_POWER \
+ | USB_PORT_STAT_ENABLE \
+ | USB_PORT_STAT_CONNECTION)
+
+ /* Root hubs can use the GET_PORT_STATUS request to
+ * stop resume signaling. Then finish the resume
+ * sequence.
+ */
+ devstatus = portchange = 0;
+ status = hub_port_status(hubdev, port - 1,
+ &devstatus, &portchange);
+ if (status < 0
+ || (devstatus & LIVE_FLAGS) != LIVE_FLAGS
+ || (devstatus & USB_PORT_STAT_SUSPEND) != 0
+ ) {
+ dev_dbg(&hubdev->actconfig->interface[0]->dev,
+ "port %d status %04x.%04x after resume, %d\n",
+ port, portchange, devstatus, status);
+ } else {
+ /* TRSMRCY = 10 msec */
+ dev->dev.power.power_state = 0;
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(MSEC_TO_JIFFIES(10));
+ status = finish_resume(dev);
+ }
+ }
+ if (status < 0)
+ status = hub_port_disable(hubdev, port);
+
+ return status;
+}
+
+/**
+ * usb_resume_device - re-activate a suspended usb device
+ * @dev: device to re-activate
+ * Context: must be able to sleep; dev->serialize not held
+ *
+ * This will re-activate the suspended device, increasing power usage
+ * while letting drivers communicate again with its endpoints.
+ *
+ * Returns 0 on success, else negative errno.
+ */
+int usb_resume_device (struct usb_device *dev)
+{
+ int port, status;
+
+ port = locktree (dev);
+ 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 (!dev->parent) {
+ struct usb_bus *bus = dev->bus;
+ if (bus && bus->op->hub_resume)
+ status = bus->op->hub_resume (bus);
+ else
+ status = -EOPNOTSUPP;
+ } else if (dev->state == USB_STATE_SUSPENDED) {
+ status = hub_port_resume(dev->parent, port + 1);
+ } else {
+ status = 0;
+ dev->dev.power.power_state = 0;
+ }
+ if (status < 0) {
+ dev_dbg (&dev->dev, "can't resume, status %d\n",
+ status);
+ }
+
+ up(&dev->serialize);
+ return status;
+}
+
+static inline int remote_wakeup (struct usb_device *dev)
+{
+ int status = 0;
+
+ /* don't repeat RESUME sequence if this device
+ * was already woken up by some other task
+ */
+ locktree(dev);
+ if (dev->state == USB_STATE_SUSPENDED) {
+ dev_dbg(&dev->dev, "RESUME (wakeup)\n");
+ /* TRSMRCY = 10 msec */
+ dev->dev.power.power_state = 0;
+ set_current_state(TASK_UNINTERRUPTIBLE);
+ schedule_timeout(MSEC_TO_JIFFIES(10));
+ status = finish_resume(dev);
+ }
+ up(&dev->serialize);
+ return status;
+}
+
+int hub_suspend (struct usb_interface *intf, u32 state)
+{
+ struct usb_device *hub = interface_to_usbdev (intf);
+ struct usb_device *dev;
+ unsigned port;
+ int status;
+
+ for (port = 0; port < hub->maxchild; port++) {
+ dev = hub->children [port];
+ if (!dev)
+ continue;
+ down (&dev->serialize);
+ status = __usb_suspend_device (dev, port, state);
+ if (status < 0)
+ dev_dbg(&intf->dev, "suspend port %d --> %d\n",
+ port, status);
+ }
+ intf->dev.power.power_state = state;
+ return 0;
+}
+
+int hub_resume (struct usb_interface *intf)
+{
+ struct usb_device *hub = interface_to_usbdev (intf);
+ struct usb_device *dev;
+ struct usb_hub *hubstate;
+ unsigned port;
+ int status;
+
+ for (port = 0; port < hub->maxchild; port++) {
+ dev = hub->children [port];
+ if (!dev)
+ continue;
+ down (&dev->serialize);
+ status = finish_resume (dev);
+ if (status < 0)
+ status = hub_port_disable(hub, port);
+ if (status < 0)
+ dev_dbg(&intf->dev, "resume port %d --> %d\n",
+ port, status);
+ up (&dev->serialize);
+ }
+ intf->dev.power.power_state = 0;
+
+ /* resubmit the urb (iff it's safe) */
+ hubstate = usb_get_intfdata (intf);
+ if (hubstate->urb->status == -EHOSTUNREACH) {
+ status = usb_submit_urb (hubstate->urb, GFP_KERNEL);
+ if (status < 0)
+ dev_err (&intf->dev,
+ "resume/resubmit --> %d\n",
+ status);
+ } else
+ status = -EHOSTUNREACH;
+ return 0;
+}
+
+#else /* !CONFIG_USB_SUSPEND */
+
+static inline int finish_resume (struct usb_device *dev)
+{
+ return 0;
+}
+
+int usb_suspend_device (struct usb_device *dev)
+{
+ return 0;
+}
+
+int usb_resume_device (struct usb_device *dev)
+{
+ return 0;
+}
+
+#define hub_suspend 0
+#define hub_resume 0
+#define remote_wakeup 0
+
+#endif /* CONFIG_USB_SUSPEND */
+
+EXPORT_SYMBOL(usb_suspend_device);
+EXPORT_SYMBOL(usb_resume_device);
+
+
/* USB 2.0 spec, 7.1.7.3 / fig 7-29:
*
* Between connect detection and reset signaling there must be a delay
@@ -1447,7 +1913,7 @@
hub_port_connect_change(hub, i, portstatus,
portchange);
} else if (portchange & USB_PORT_STAT_C_ENABLE) {
dev_dbg (hubdev (dev),
- "port %d enable change, status %x\n",
+ "port %d enable change, status %08x\n",
i + 1, portstatus);
clear_port_feature(dev,
i + 1, USB_PORT_FEAT_C_ENABLE);
@@ -1472,11 +1938,17 @@
}
if (portchange & USB_PORT_STAT_C_SUSPEND) {
+ clear_port_feature(dev, i + 1,
+ USB_PORT_FEAT_C_SUSPEND);
+ if (dev->children[i])
+ ret = remote_wakeup(dev->children[i]);
+ else
+ ret = -ENODEV;
dev_dbg (&hub->intf->dev,
- "suspend change on port %d\n",
- i + 1);
- clear_port_feature(dev,
- i + 1, USB_PORT_FEAT_C_SUSPEND);
+ "resume on port %d, status %d\n",
+ i + 1, ret);
+ if (ret < 0)
+ ret = hub_port_disable(dev, i);
}
if (portchange & USB_PORT_STAT_C_OVERCURRENT) {
@@ -1555,6 +2027,8 @@
.name = "hub",
.probe = hub_probe,
.disconnect = hub_disconnect,
+ .suspend = hub_suspend,
+ .resume = hub_resume,
.ioctl = hub_ioctl,
.id_table = hub_id_table,
};
--- 1.162/drivers/usb/core/usb.c Thu Apr 15 05:19:20 2004
+++ edited/drivers/usb/core/usb.c Mon May 3 11:06:09 2004
@@ -48,6 +48,7 @@
#include "hcd.h"
#include "usb.h"
+#include "hub.h"
extern int usb_hub_init(void);
extern void usb_hub_cleanup(void);
@@ -1441,13 +1442,16 @@
usb_pipein (pipe) ? DMA_FROM_DEVICE : DMA_TO_DEVICE);
}
-static int usb_device_suspend(struct device *dev, u32 state)
+static int usb_generic_suspend(struct device *dev, u32 state)
{
struct usb_interface *intf;
struct usb_driver *driver;
+ /* USB has just one active suspend state, using the hub */
+ if (dev->driver == &usb_generic_driver)
+ return usb_suspend_device (to_usb_device(dev));
+
if ((dev->driver == NULL) ||
- (dev->driver == &usb_generic_driver) ||
(dev->driver_data == &usb_generic_driver_data))
return 0;
@@ -1459,13 +1463,16 @@
return 0;
}
-static int usb_device_resume(struct device *dev)
+static int usb_generic_resume(struct device *dev)
{
struct usb_interface *intf;
struct usb_driver *driver;
+ /* devices resume through their hub */
+ if (dev->driver == &usb_generic_driver)
+ return usb_resume_device (to_usb_device(dev));
+
if ((dev->driver == NULL) ||
- (dev->driver == &usb_generic_driver) ||
(dev->driver_data == &usb_generic_driver_data))
return 0;
@@ -1481,8 +1488,8 @@
.name = "usb",
.match = usb_device_match,
.hotplug = usb_hotplug,
- .suspend = usb_device_suspend,
- .resume = usb_device_resume,
+ .suspend = usb_generic_suspend,
+ .resume = usb_generic_resume,
};
#ifndef MODULE
--- 1.106/include/linux/usb.h Thu Apr 15 07:43:58 2004
+++ edited/include/linux/usb.h Mon May 3 11:06:09 2004
@@ -941,6 +941,11 @@
void *data, int len, int *actual_length,
int timeout);
+/* selective suspend/resume */
+extern int usb_suspend_device(struct usb_device *dev);
+extern int usb_resume_device(struct usb_device *dev);
+
+
/* wrappers around usb_control_msg() for the most common standard requests */
extern int usb_get_descriptor(struct usb_device *dev, unsigned char desctype,
unsigned char descindex, void *buf, int size);
