[PATCH] root hub changes (lesser half)
authorDavid Brownell <david-b@pacbell.net>
Fri, 23 Sep 2005 05:32:24 +0000 (22:32 -0700)
committerGreg Kroah-Hartman <gregkh@suse.de>
Fri, 28 Oct 2005 23:47:40 +0000 (16:47 -0700)
This patch collects various small updates related to root hubs, to shrink
later patches which build on them.

  - For root hub suspend/resume support:
     * Make the existing usb_hcd_resume_root_hub() routine respect pmcore
       locking, exporting and using the dpm_runtime_resume() method.
     * Add a new usb_hcd_suspend_root_hub() to pair with that routine.
       (Essential to make OHCI autosuspend behave again...)
     * HC_SUSPENDED by itself only refers to the root hub's downstream ports.
       So let HCDs see root hub URBs unless the parent device is suspended.

  - Remove an assertion we no longer need (and now, also don't want).

  - Generic suspend/resume updates to work better with swsusp.
     * Ignore the FREEZE vs SUSPEND distinction for hardware; trying to
       use it breaks the swsusp snapshots it's supposed to help (sigh).
     * On resume, mark devices as resumed right away, but then
       do nothing else if the device is marked NOTATTACHED.

These changes shouldn't be very noticable by themselves.

Signed-off-by: David Brownell <dbrownell@users.sourceforge.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
 drivers/base/power/runtime.c |    1
 drivers/usb/core/hcd.c       |   64 ++++++++++++++++++++++++++++++++++++++-----
 drivers/usb/core/hcd.h       |    1
 drivers/usb/core/hub.c       |   45 ++++++++++++++++++++++++------
 drivers/usb/core/usb.c       |   20 +++++++++----
 drivers/usb/core/usb.h       |    1
 6 files changed, 111 insertions(+), 21 deletions(-)

drivers/base/power/runtime.c
drivers/usb/core/hcd.c
drivers/usb/core/hcd.h
drivers/usb/core/hub.c
drivers/usb/core/usb.c
drivers/usb/core/usb.h

index e8f0519..adbc314 100644 (file)
@@ -36,6 +36,7 @@ void dpm_runtime_resume(struct device * dev)
        runtime_resume(dev);
        up(&dpm_sem);
 }
+EXPORT_SYMBOL(dpm_runtime_resume);
 
 
 /**
index 375382f..de59bb5 100644 (file)
@@ -1143,10 +1143,20 @@ static int hcd_submit_urb (struct urb *urb, gfp_t mem_flags)
        else switch (hcd->state) {
        case HC_STATE_RUNNING:
        case HC_STATE_RESUMING:
+doit:
                usb_get_dev (urb->dev);
                list_add_tail (&urb->urb_list, &ep->urb_list);
                status = 0;
                break;
+       case HC_STATE_SUSPENDED:
+               /* HC upstream links (register access, wakeup signaling) can work
+                * even when the downstream links (and DMA etc) are quiesced; let
+                * usbcore talk to the root hub.
+                */
+               if (hcd->self.controller->power.power_state.event == PM_EVENT_ON
+                               && urb->dev->parent == NULL)
+                       goto doit;
+               /* FALL THROUGH */
        default:
                status = -ESHUTDOWN;
                break;
@@ -1294,12 +1304,6 @@ static int hcd_unlink_urb (struct urb *urb, int status)
                goto done;
        }
 
-       /* running ~= hc unlink handshake works (irq, timer, etc)
-        * halted ~= no unlink handshake is needed
-        * suspended, resuming == should never happen
-        */
-       WARN_ON (!HC_IS_RUNNING (hcd->state) && hcd->state != HC_STATE_HALT);
-
        /* insist the urb is still queued */
        list_for_each(tmp, &ep->urb_list) {
                if (tmp == &urb->urb_list)
@@ -1459,6 +1463,8 @@ static int hcd_hub_resume (struct usb_bus *bus)
        hcd = container_of (bus, struct usb_hcd, self);
        if (!hcd->driver->hub_resume)
                return -ENOENT;
+       if (hcd->state == HC_STATE_RUNNING)
+               return 0;
        hcd->state = HC_STATE_RESUMING;
        status = hcd->driver->hub_resume (hcd);
        if (status == 0)
@@ -1471,6 +1477,50 @@ static int hcd_hub_resume (struct usb_bus *bus)
        return status;
 }
 
+/*
+ * usb_hcd_suspend_root_hub - HCD autosuspends downstream ports
+ * @hcd: host controller for this root hub
+ *
+ * This call arranges that usb_hcd_resume_root_hub() is safe to call later;
+ * that the HCD's root hub polling is deactivated; and that the root's hub
+ * driver is suspended.  HCDs may call this to autosuspend when their root
+ * hub's downstream ports are all inactive:  unpowered, disconnected,
+ * disabled, or suspended.
+ *
+ * The HCD will autoresume on device connect change detection (using SRP
+ * or a D+/D- pullup).  The HCD also autoresumes on remote wakeup signaling
+ * from any ports that are suspended (if that is enabled).  In most cases,
+ * overcurrent signaling (on powered ports) will also start autoresume.
+ *
+ * Always called with IRQs blocked.
+ */
+void usb_hcd_suspend_root_hub (struct usb_hcd *hcd)
+{
+       struct urb      *urb;
+
+       spin_lock (&hcd_root_hub_lock);
+       usb_suspend_root_hub (hcd->self.root_hub);
+
+       /* force status urb to complete/unlink while suspended */
+       if (hcd->status_urb) {
+               urb = hcd->status_urb;
+               urb->status = -ECONNRESET;
+               urb->hcpriv = NULL;
+               urb->actual_length = 0;
+
+               del_timer (&hcd->rh_timer);
+               hcd->poll_pending = 0;
+               hcd->status_urb = NULL;
+       } else
+               urb = NULL;
+       spin_unlock (&hcd_root_hub_lock);
+       hcd->state = HC_STATE_SUSPENDED;
+
+       if (urb)
+               usb_hcd_giveback_urb (hcd, urb, NULL);
+}
+EXPORT_SYMBOL_GPL(usb_hcd_suspend_root_hub);
+
 /**
  * usb_hcd_resume_root_hub - called by HCD to resume its root hub 
  * @hcd: host controller for this root hub
@@ -1478,7 +1528,7 @@ static int hcd_hub_resume (struct usb_bus *bus)
  * The USB host controller calls this function when its root hub is
  * suspended (with the remote wakeup feature enabled) and a remote
  * wakeup request is received.  It queues a request for khubd to
- * resume the root hub.
+ * resume the root hub (that is, manage its downstream ports again).
  */
 void usb_hcd_resume_root_hub (struct usb_hcd *hcd)
 {
index 1f1ed62..eb21f13 100644 (file)
@@ -355,6 +355,7 @@ extern long usb_calc_bus_time (int speed, int is_input,
 
 extern struct usb_bus *usb_alloc_bus (struct usb_operations *);
 
+extern void usb_hcd_suspend_root_hub (struct usb_hcd *hcd);
 extern void usb_hcd_resume_root_hub (struct usb_hcd *hcd);
 
 extern void usb_set_device_state(struct usb_device *udev,
index 6600644..3c8d8d1 100644 (file)
@@ -449,11 +449,18 @@ static void hub_power_on(struct usb_hub *hub)
        msleep(max(pgood_delay, (unsigned) 100));
 }
 
-static void hub_quiesce(struct usb_hub *hub)
+static inline void __hub_quiesce(struct usb_hub *hub)
 {
-       /* stop khubd and related activity */
+       /* (nonblocking) khubd and related activity won't re-trigger */
        hub->quiescing = 1;
        hub->activating = 0;
+       hub->resume_root_hub = 0;
+}
+
+static void hub_quiesce(struct usb_hub *hub)
+{
+       /* (blocking) stop khubd and related activity */
+       __hub_quiesce(hub);
        usb_kill_urb(hub->urb);
        if (hub->has_indicators)
                cancel_delayed_work(&hub->leds);
@@ -467,6 +474,7 @@ static void hub_activate(struct usb_hub *hub)
 
        hub->quiescing = 0;
        hub->activating = 1;
+       hub->resume_root_hub = 0;
        status = usb_submit_urb(hub->urb, GFP_NOIO);
        if (status < 0)
                dev_err(hub->intfdev, "activate --> %d\n", status);
@@ -1959,6 +1967,18 @@ static int hub_resume(struct usb_interface *intf)
        return 0;
 }
 
+void usb_suspend_root_hub(struct usb_device *hdev)
+{
+       struct usb_hub *hub = hdev_to_hub(hdev);
+
+       /* This also makes any led blinker stop retriggering.  We're called
+        * from irq, so the blinker might still be scheduled.  Caller promises
+        * that the root hub status URB will be canceled.
+        */
+       __hub_quiesce(hub);
+       mark_quiesced(to_usb_interface(hub->intfdev));
+}
+
 void usb_resume_root_hub(struct usb_device *hdev)
 {
        struct usb_hub *hub = hdev_to_hub(hdev);
@@ -2616,21 +2636,30 @@ static void hub_events(void)
                intf = to_usb_interface(hub->intfdev);
                hub_dev = &intf->dev;
 
-               dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x\n",
+               i = hub->resume_root_hub;
+
+               dev_dbg(hub_dev, "state %d ports %d chg %04x evt %04x%s\n",
                                hdev->state, hub->descriptor
                                        ? hub->descriptor->bNbrPorts
                                        : 0,
                                /* NOTE: expects max 15 ports... */
                                (u16) hub->change_bits[0],
-                               (u16) hub->event_bits[0]);
+                               (u16) hub->event_bits[0],
+                               i ? ", resume root" : "");
 
                usb_get_intf(intf);
-               i = hub->resume_root_hub;
                spin_unlock_irq(&hub_event_lock);
 
-               /* Is this is a root hub wanting to be resumed? */
-               if (i)
-                       usb_resume_device(hdev);
+               /* Is this is a root hub wanting to reactivate the downstream
+                * ports?  If so, be sure the interface resumes even if its
+                * stub "device" node was never suspended.
+                */
+               if (i) {
+                       extern void dpm_runtime_resume(struct device *);
+
+                       dpm_runtime_resume(&hdev->dev);
+                       dpm_runtime_resume(&intf->dev);
+               }
 
                /* Lock the device, then check to see if we were
                 * disconnected while waiting for the lock to succeed. */
index e89dbd4..2493e7d 100644 (file)
@@ -1427,6 +1427,7 @@ static int usb_generic_suspend(struct device *dev, pm_message_t message)
 
        /* USB devices enter SUSPEND state through their hubs, but can be
         * marked for FREEZE as soon as their children are already idled.
+        * But those semantics are useless, so we equate the two (sigh).
         */
        if (dev->driver == &usb_generic_driver) {
                if (dev->power.power_state.event == message.event)
@@ -1435,10 +1436,6 @@ static int usb_generic_suspend(struct device *dev, pm_message_t message)
                status = device_for_each_child(dev, NULL, verify_suspended);
                if (status)
                        return status;
-               if (message.event == PM_EVENT_FREEZE) {
-                       dev->power.power_state = message;
-                       return 0;
-               }
                return usb_suspend_device (to_usb_device(dev));
        }
 
@@ -1471,14 +1468,22 @@ static int usb_generic_resume(struct device *dev)
 {
        struct usb_interface    *intf;
        struct usb_driver       *driver;
+       struct usb_device       *udev;
        int                     status;
 
        if (dev->power.power_state.event == PM_EVENT_ON)
                return 0;
 
+       /* mark things as "on" immediately, no matter what errors crop up */
+       dev->power.power_state.event = PM_EVENT_ON;
+
        /* devices resume through their hubs */
-       if (dev->driver == &usb_generic_driver)
+       if (dev->driver == &usb_generic_driver) {
+               udev = to_usb_device(dev);
+               if (udev->state == USB_STATE_NOTATTACHED)
+                       return 0;
                return usb_resume_device (to_usb_device(dev));
+       }
 
        if ((dev->driver == NULL) ||
            (dev->driver_data == &usb_generic_driver_data))
@@ -1487,11 +1492,14 @@ static int usb_generic_resume(struct device *dev)
        intf = to_usb_interface(dev);
        driver = to_usb_driver(dev->driver);
 
+       udev = interface_to_usbdev(intf);
+       if (udev->state == USB_STATE_NOTATTACHED)
+               return 0;
+
        /* if driver was suspended, it has a resume method;
         * however, sysfs can wrongly mark things as suspended
         * (on the "no suspend method" FIXME path above)
         */
-       mark_active(intf);
        if (driver->resume) {
                status = driver->resume(intf);
                if (status) {
index 3741a99..7add46e 100644 (file)
@@ -19,6 +19,7 @@ extern void usb_lock_all_devices(void);
 extern void usb_unlock_all_devices(void);
 
 extern void usb_kick_khubd(struct usb_device *dev);
+extern void usb_suspend_root_hub(struct usb_device *hdev);
 extern void usb_resume_root_hub(struct usb_device *dev);
 
 extern int  usb_hub_init(void);