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);

Reply via email to