[PATCH] PM: add synchronous runtime interface for interrupt handlers

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This patch (as1431) adds a synchronous runtime-PM interface suitable
for use in interrupt handlers.  Four new helper functions are defined:

	pm_runtime_suspend_irq(), pm_runtime_resume_irq(),
	pm_runtime_get_sync_irq(), pm_runtime_put_sync_irq(),

together with pm_runtime_callbacks_in_irq(), which subsystems use to
tell the PM core that the runtime callbacks should be invoked with
interrupts disabled.

Signed-off-by: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx>

---

In the end it turned out that a new RPM_IRQ call flag was needed along
with the callbacks_in_irq flag in dev_pm_info.  The latter is required
for the reasons I explained before, and RPM_IRQ tells the core whether
or not it must leave interrupts disabled while waiting for a concurrent
state change.

Kevin, this should be good enough to satisfy all your needs.  How does 
it look?

Alan Stern


Index: usb-2.6/include/linux/pm.h
===================================================================
--- usb-2.6.orig/include/linux/pm.h
+++ usb-2.6/include/linux/pm.h
@@ -485,6 +485,7 @@ struct dev_pm_info {
 	unsigned int		run_wake:1;
 	unsigned int		runtime_auto:1;
 	unsigned int		no_callbacks:1;
+	unsigned int		callbacks_in_irq:1;
 	unsigned int		use_autosuspend:1;
 	unsigned int		timer_autosuspends:1;
 	enum rpm_request	request;
Index: usb-2.6/include/linux/pm_runtime.h
===================================================================
--- usb-2.6.orig/include/linux/pm_runtime.h
+++ usb-2.6/include/linux/pm_runtime.h
@@ -21,6 +21,7 @@
 #define RPM_GET_PUT		0x04	/* Increment/decrement the
 					    usage_count */
 #define RPM_AUTO		0x08	/* Use autosuspend_delay */
+#define RPM_IRQ			0x10	/* Don't enable interrupts */
 
 #ifdef CONFIG_PM_RUNTIME
 
@@ -40,6 +41,7 @@ extern int pm_generic_runtime_idle(struc
 extern int pm_generic_runtime_suspend(struct device *dev);
 extern int pm_generic_runtime_resume(struct device *dev);
 extern void pm_runtime_no_callbacks(struct device *dev);
+extern void pm_runtime_callbacks_in_irq(struct device *dev);
 extern void __pm_runtime_use_autosuspend(struct device *dev, bool use);
 extern void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);
 extern unsigned long pm_runtime_autosuspend_expiration(struct device *dev);
@@ -123,6 +125,7 @@ static inline int pm_generic_runtime_idl
 static inline int pm_generic_runtime_suspend(struct device *dev) { return 0; }
 static inline int pm_generic_runtime_resume(struct device *dev) { return 0; }
 static inline void pm_runtime_no_callbacks(struct device *dev) {}
+static inline void pm_runtime_callbacks_in_irq(struct device *dev) {}
 
 static inline void pm_runtime_mark_last_busy(struct device *dev) {}
 static inline void __pm_runtime_use_autosuspend(struct device *dev,
@@ -144,6 +147,11 @@ static inline int pm_runtime_suspend(str
 	return __pm_runtime_suspend(dev, 0);
 }
 
+static inline int pm_runtime_suspend_irq(struct device *dev)
+{
+	return __pm_runtime_suspend(dev, RPM_IRQ);
+}
+
 static inline int pm_runtime_autosuspend(struct device *dev)
 {
 	return __pm_runtime_suspend(dev, RPM_AUTO);
@@ -154,6 +162,11 @@ static inline int pm_runtime_resume(stru
 	return __pm_runtime_resume(dev, 0);
 }
 
+static inline int pm_runtime_resume_irq(struct device *dev)
+{
+	return __pm_runtime_resume(dev, RPM_IRQ);
+}
+
 static inline int pm_request_idle(struct device *dev)
 {
 	return __pm_runtime_idle(dev, RPM_ASYNC);
@@ -179,6 +192,11 @@ static inline int pm_runtime_get_sync(st
 	return __pm_runtime_resume(dev, RPM_GET_PUT);
 }
 
+static inline int pm_runtime_get_sync_irq(struct device *dev)
+{
+	return __pm_runtime_resume(dev, RPM_GET_PUT | RPM_IRQ);
+}
+
 static inline int pm_runtime_put(struct device *dev)
 {
 	return __pm_runtime_idle(dev, RPM_GET_PUT | RPM_ASYNC);
@@ -195,6 +213,11 @@ static inline int pm_runtime_put_sync(st
 	return __pm_runtime_idle(dev, RPM_GET_PUT);
 }
 
+static inline int pm_runtime_put_sync_irq(struct device *dev)
+{
+	return __pm_runtime_idle(dev, RPM_GET_PUT | RPM_IRQ);
+}
+
 static inline int pm_runtime_put_sync_autosuspend(struct device *dev)
 {
 	return __pm_runtime_suspend(dev, RPM_GET_PUT | RPM_AUTO);
Index: usb-2.6/drivers/base/power/runtime.c
===================================================================
--- usb-2.6.orig/drivers/base/power/runtime.c
+++ usb-2.6/drivers/base/power/runtime.c
@@ -170,10 +170,13 @@ static int rpm_idle(struct device *dev, 
 	__releases(&dev->power.lock) __acquires(&dev->power.lock)
 {
 	int retval;
+	int (*func)(struct device *dev);
 
 	retval = rpm_check_suspend_allowed(dev);
 	if (retval < 0)
 		;	/* Conditions are wrong. */
+	else if ((rpmflags & RPM_IRQ) && !dev->power.callbacks_in_irq)
+		retval = -EWOULDBLOCK;
 
 	/* Idle notifications are allowed only in the RPM_ACTIVE state. */
 	else if (dev->power.runtime_status != RPM_ACTIVE)
@@ -214,25 +217,27 @@ static int rpm_idle(struct device *dev, 
 
 	dev->power.idle_notification = true;
 
-	if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_idle) {
-		spin_unlock_irq(&dev->power.lock);
-
-		dev->bus->pm->runtime_idle(dev);
-
-		spin_lock_irq(&dev->power.lock);
-	} else if (dev->type && dev->type->pm && dev->type->pm->runtime_idle) {
-		spin_unlock_irq(&dev->power.lock);
+	func = NULL;
+	if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_idle)
+		func = dev->bus->pm->runtime_idle;
+	else if (dev->type && dev->type->pm && dev->type->pm->runtime_idle)
+		func = dev->type->pm->runtime_idle;
+	else if (dev->class && dev->class->pm && dev->class->pm->runtime_idle)
+		func = dev->class->pm->runtime_idle;
+	if (func) {
+		if (dev->power.callbacks_in_irq) {
+			spin_unlock(&dev->power.lock);
 
-		dev->type->pm->runtime_idle(dev);
+			func(dev);
 
-		spin_lock_irq(&dev->power.lock);
-	} else if (dev->class && dev->class->pm
-	    && dev->class->pm->runtime_idle) {
-		spin_unlock_irq(&dev->power.lock);
+			spin_lock(&dev->power.lock);
+		} else {
+			spin_unlock_irq(&dev->power.lock);
 
-		dev->class->pm->runtime_idle(dev);
+			func(dev);
 
-		spin_lock_irq(&dev->power.lock);
+			spin_lock_irq(&dev->power.lock);
+		}
 	}
 
 	dev->power.idle_notification = false;
@@ -264,6 +269,7 @@ static int rpm_suspend(struct device *de
 	struct device *parent = NULL;
 	bool notify = false;
 	int retval;
+	int (*func)(struct device *dev);
 
 	dev_dbg(dev, "%s flags 0x%x\n", __func__, rpmflags);
 
@@ -272,6 +278,8 @@ static int rpm_suspend(struct device *de
 
 	if (retval < 0)
 		;	/* Conditions are wrong. */
+	else if ((rpmflags & RPM_IRQ) && !dev->power.callbacks_in_irq)
+		retval = -EWOULDBLOCK;
 
 	/* Synchronous suspends are not allowed in the RPM_RESUMING state. */
 	else if (dev->power.runtime_status == RPM_RESUMING &&
@@ -310,27 +318,35 @@ static int rpm_suspend(struct device *de
 	pm_runtime_cancel_pending(dev);
 
 	if (dev->power.runtime_status == RPM_SUSPENDING) {
-		DEFINE_WAIT(wait);
-
 		if (rpmflags & (RPM_ASYNC | RPM_NOWAIT)) {
 			retval = -EINPROGRESS;
 			goto out;
 		}
 
 		/* Wait for the other suspend running in parallel with us. */
-		for (;;) {
-			prepare_to_wait(&dev->power.wait_queue, &wait,
-					TASK_UNINTERRUPTIBLE);
-			if (dev->power.runtime_status != RPM_SUSPENDING)
-				break;
+		if (rpmflags & RPM_IRQ) {
+			spin_unlock(&dev->power.lock);
+
+			while (dev->power.runtime_status == RPM_SUSPENDING)
+				cpu_relax();
+
+			spin_lock(&dev->power.lock);
+		} else {
+			DEFINE_WAIT(wait);
 
 			spin_unlock_irq(&dev->power.lock);
 
-			schedule();
+			for (;;) {
+				prepare_to_wait(&dev->power.wait_queue, &wait,
+				    TASK_UNINTERRUPTIBLE);
+				if (dev->power.runtime_status != RPM_SUSPENDING)
+					break;
+				schedule();
+			}
+			finish_wait(&dev->power.wait_queue, &wait);
 
 			spin_lock_irq(&dev->power.lock);
 		}
-		finish_wait(&dev->power.wait_queue, &wait);
 		goto repeat;
 	}
 
@@ -351,28 +367,28 @@ static int rpm_suspend(struct device *de
 
 	__update_runtime_status(dev, RPM_SUSPENDING);
 
-	if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_suspend) {
-		spin_unlock_irq(&dev->power.lock);
-
-		retval = dev->bus->pm->runtime_suspend(dev);
+	func = NULL;
+	if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_suspend)
+		func = dev->bus->pm->runtime_suspend;
+	else if (dev->type && dev->type->pm && dev->type->pm->runtime_suspend)
+		func = dev->type->pm->runtime_suspend;
+	else if (dev->class && dev->class->pm &&
+	    dev->class->pm->runtime_suspend)
+		func = dev->class->pm->runtime_suspend;
+	if (func) {
+		if (dev->power.callbacks_in_irq) {
+			spin_unlock(&dev->power.lock);
 
-		spin_lock_irq(&dev->power.lock);
-		dev->power.runtime_error = retval;
-	} else if (dev->type && dev->type->pm
-	    && dev->type->pm->runtime_suspend) {
-		spin_unlock_irq(&dev->power.lock);
+			retval = func(dev);
 
-		retval = dev->type->pm->runtime_suspend(dev);
-
-		spin_lock_irq(&dev->power.lock);
-		dev->power.runtime_error = retval;
-	} else if (dev->class && dev->class->pm
-	    && dev->class->pm->runtime_suspend) {
-		spin_unlock_irq(&dev->power.lock);
+			spin_lock(&dev->power.lock);
+		} else {
+			spin_unlock_irq(&dev->power.lock);
 
-		retval = dev->class->pm->runtime_suspend(dev);
+			retval = func(dev);
 
-		spin_lock_irq(&dev->power.lock);
+			spin_lock_irq(&dev->power.lock);
+		}
 		dev->power.runtime_error = retval;
 	} else {
 		retval = -ENOSYS;
@@ -401,20 +417,20 @@ static int rpm_suspend(struct device *de
 	wake_up_all(&dev->power.wait_queue);
 
 	if (dev->power.deferred_resume) {
-		rpm_resume(dev, 0);
+		rpm_resume(dev, rpmflags);
 		retval = -EAGAIN;
 		goto out;
 	}
 
 	if (notify)
-		rpm_idle(dev, 0);
+		rpm_idle(dev, rpmflags);
 
 	if (parent && !parent->power.ignore_children) {
-		spin_unlock_irq(&dev->power.lock);
+		spin_unlock(&dev->power.lock);
 
 		pm_request_idle(parent);
 
-		spin_lock_irq(&dev->power.lock);
+		spin_lock(&dev->power.lock);
 	}
 
  out:
@@ -445,6 +461,7 @@ static int rpm_resume(struct device *dev
 {
 	struct device *parent = NULL;
 	int retval = 0;
+	int (*func)(struct device *dev);
 
 	dev_dbg(dev, "%s flags 0x%x\n", __func__, rpmflags);
 
@@ -453,6 +470,8 @@ static int rpm_resume(struct device *dev
 		retval = -EINVAL;
 	else if (dev->power.disable_depth > 0)
 		retval = -EAGAIN;
+	else if ((rpmflags & RPM_IRQ) && !dev->power.callbacks_in_irq)
+		retval = -EWOULDBLOCK;
 	if (retval)
 		goto out;
 
@@ -473,8 +492,6 @@ static int rpm_resume(struct device *dev
 
 	if (dev->power.runtime_status == RPM_RESUMING
 	    || dev->power.runtime_status == RPM_SUSPENDING) {
-		DEFINE_WAIT(wait);
-
 		if (rpmflags & (RPM_ASYNC | RPM_NOWAIT)) {
 			if (dev->power.runtime_status == RPM_SUSPENDING)
 				dev->power.deferred_resume = true;
@@ -484,20 +501,31 @@ static int rpm_resume(struct device *dev
 		}
 
 		/* Wait for the operation carried out in parallel with us. */
-		for (;;) {
-			prepare_to_wait(&dev->power.wait_queue, &wait,
-					TASK_UNINTERRUPTIBLE);
-			if (dev->power.runtime_status != RPM_RESUMING
-			    && dev->power.runtime_status != RPM_SUSPENDING)
-				break;
+		if (rpmflags & RPM_IRQ) {
+			spin_unlock(&dev->power.lock);
+
+			while (dev->power.runtime_status == RPM_SUSPENDING
+			    || dev->power.runtime_status == RPM_RESUMING)
+				cpu_relax();
+
+			spin_lock(&dev->power.lock);
+		} else {
+			DEFINE_WAIT(wait);
 
 			spin_unlock_irq(&dev->power.lock);
 
-			schedule();
+			for (;;) {
+				prepare_to_wait(&dev->power.wait_queue, &wait,
+				    TASK_UNINTERRUPTIBLE);
+				if (dev->power.runtime_status != RPM_SUSPENDING
+				    && dev->power.runtime_status != RPM_RESUMING)
+					break;
+				schedule();
+			}
+			finish_wait(&dev->power.wait_queue, &wait);
 
 			spin_lock_irq(&dev->power.lock);
 		}
-		finish_wait(&dev->power.wait_queue, &wait);
 		goto repeat;
 	}
 
@@ -546,7 +574,7 @@ static int rpm_resume(struct device *dev
 		 */
 		if (!parent->power.disable_depth
 		    && !parent->power.ignore_children) {
-			rpm_resume(parent, 0);
+			rpm_resume(parent, rpmflags & ~RPM_NOWAIT);
 			if (parent->power.runtime_status != RPM_ACTIVE)
 				retval = -EBUSY;
 		}
@@ -563,28 +591,28 @@ static int rpm_resume(struct device *dev
 
 	__update_runtime_status(dev, RPM_RESUMING);
 
-	if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume) {
-		spin_unlock_irq(&dev->power.lock);
-
-		retval = dev->bus->pm->runtime_resume(dev);
+	func = NULL;
+	if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume)
+		func = dev->bus->pm->runtime_resume;
+	else if (dev->type && dev->type->pm && dev->type->pm->runtime_resume)
+		func = dev->type->pm->runtime_resume;
+	else if (dev->class && dev->class->pm &&
+	    dev->class->pm->runtime_resume)
+		func = dev->class->pm->runtime_resume;
+	if (func) {
+		if (dev->power.callbacks_in_irq) {
+			spin_unlock(&dev->power.lock);
 
-		spin_lock_irq(&dev->power.lock);
-		dev->power.runtime_error = retval;
-	} else if (dev->type && dev->type->pm
-	    && dev->type->pm->runtime_resume) {
-		spin_unlock_irq(&dev->power.lock);
+			retval = func(dev);
 
-		retval = dev->type->pm->runtime_resume(dev);
-
-		spin_lock_irq(&dev->power.lock);
-		dev->power.runtime_error = retval;
-	} else if (dev->class && dev->class->pm
-	    && dev->class->pm->runtime_resume) {
-		spin_unlock_irq(&dev->power.lock);
+			spin_lock(&dev->power.lock);
+		} else {
+			spin_unlock_irq(&dev->power.lock);
 
-		retval = dev->class->pm->runtime_resume(dev);
+			retval = func(dev);
 
-		spin_lock_irq(&dev->power.lock);
+			spin_lock_irq(&dev->power.lock);
+		}
 		dev->power.runtime_error = retval;
 	} else {
 		retval = -ENOSYS;
@@ -602,15 +630,15 @@ static int rpm_resume(struct device *dev
 	wake_up_all(&dev->power.wait_queue);
 
 	if (!retval)
-		rpm_idle(dev, RPM_ASYNC);
+		rpm_idle(dev, rpmflags | RPM_ASYNC);
 
  out:
 	if (parent) {
-		spin_unlock_irq(&dev->power.lock);
+		spin_unlock(&dev->power.lock);
 
 		pm_runtime_put(parent);
 
-		spin_lock_irq(&dev->power.lock);
+		spin_lock(&dev->power.lock);
 	}
 
 	dev_dbg(dev, "%s returns %d\n", __func__, retval);
@@ -1086,7 +1114,6 @@ EXPORT_SYMBOL_GPL(pm_runtime_allow);
  * Set the power.no_callbacks flag, which tells the PM core that this
  * device is power-managed through its parent and has no run-time PM
  * callbacks of its own.  The run-time sysfs attributes will be removed.
- *
  */
 void pm_runtime_no_callbacks(struct device *dev)
 {
@@ -1099,6 +1126,22 @@ void pm_runtime_no_callbacks(struct devi
 EXPORT_SYMBOL_GPL(pm_runtime_no_callbacks);
 
 /**
+ * pm_runtime_callbacks_in_irq - Leave interrupts disabled during callbacks.
+ * @dev: Device to handle
+ *
+ * Set the power.callbacks_in_irq flag, which tells the PM core that the
+ * run-time PM callbacks for this device should always be invoked with
+ * interrupts disabled.
+ */
+void pm_runtime_callbacks_in_irq(struct device *dev)
+{
+	spin_lock_irq(&dev->power.lock);
+	dev->power.callbacks_in_irq = 1;
+	spin_unlock_irq(&dev->power.lock);
+}
+EXPORT_SYMBOL_GPL(pm_runtime_callbacks_in_irq);
+
+/**
  * update_autosuspend - Handle a change to a device's autosuspend settings.
  * @dev: Device to handle.
  * @old_delay: The former autosuspend_delay value.
Index: usb-2.6/Documentation/power/runtime_pm.txt
===================================================================
--- usb-2.6.orig/Documentation/power/runtime_pm.txt
+++ usb-2.6/Documentation/power/runtime_pm.txt
@@ -50,6 +50,18 @@ type's callbacks are not defined) of giv
 and device class callbacks are referred to as subsystem-level callbacks in what
 follows.
 
+By default, the callbacks are always invoked in process context with interrupts
+enabled.  However subsystems can tell the PM core that the callbacks for a
+device should be invoked with interrupts disabled, by calling
+pm_runtime_callbacks_in_irq().  This implies that the callback routines must
+not block or sleep, but it also means that the following synchronous helper
+functions can be used from within an interrupt handler:
+
+		pm_runtime_resume_irq(),
+		pm_runtime_suspend_irq(),
+		pm_runtime_get_sync_irq(),
+		pm_runtime_put_sync_irq().
+
 The subsystem-level suspend callback is _entirely_ _responsible_ for handling
 the suspend of the device as appropriate, which may, but need not include
 executing the device driver's own ->runtime_suspend() callback (from the
@@ -237,6 +249,10 @@ defined in include/linux/pm.h:
       Section 8); it may be modified only by the pm_runtime_no_callbacks()
       helper function
 
+  unsigned int callbacks_in_irq;
+    - indicates that the ->runtime_idle(), ->runtime_suspend(), and
+      ->runtime_resume() callbacks should be invoked with interrupts disabled.
+
   unsigned int use_autosuspend;
     - indicates that the device's driver supports delayed autosuspend (see
       Section 9); it may be modified only by the
@@ -285,6 +301,11 @@ drivers/base/power/runtime.c and include
       not yet expired then an autosuspend is scheduled for the appropriate time
       and 0 is returned
 
+  int pm_runtime_suspend_irq(struct device *dev);
+    - same as pm_runtime_suspend() except that this function may be called
+      in interrupt context; returns an error unless
+      pm_runtime_callbacks_in_irq(dev) was called previously
+
   int pm_runtime_resume(struct device *dev);
     - execute the subsystem-level resume callback for the device; returns 0 on
       success, 1 if the device's run-time PM status was already 'active' or
@@ -292,6 +313,11 @@ drivers/base/power/runtime.c and include
       resume the device again in future, but 'power.runtime_error' should be
       checked additionally
 
+  int pm_runtime_resume_irq(struct device *dev);
+    - same as pm_runtime_resume() except that this function may be called
+      in interrupt context; returns an error unless
+      pm_runtime_callbacks_in_irq(dev) was called previously
+
   int pm_request_idle(struct device *dev);
     - submit a request to execute the subsystem-level idle callback for the
       device (the request is represented by a work item in pm_wq); returns 0 on
@@ -329,6 +355,10 @@ drivers/base/power/runtime.c and include
     - increment the device's usage counter, run pm_runtime_resume(dev) and
       return its result
 
+  int pm_runtime_get_sync_irq(struct device *dev);
+    - increment the device's usage counter, run pm_runtime_resume_irq(dev) and
+      return its result
+
   void pm_runtime_put_noidle(struct device *dev);
     - decrement the device's usage counter
 
@@ -344,6 +374,10 @@ drivers/base/power/runtime.c and include
     - decrement the device's usage counter; if the result is 0 then run
       pm_runtime_idle(dev) and return its result
 
+  int pm_runtime_put_sync_irq(struct device *dev);
+    - decrement the device's usage counter; if the result is 0 then run
+      pm_runtime_idle(dev) and return its result
+
   int pm_runtime_put_sync_autosuspend(struct device *dev);
     - decrement the device's usage counter; if the result is 0 then run
       pm_runtime_autosuspend(dev) and return its result
@@ -397,6 +431,10 @@ drivers/base/power/runtime.c and include
       PM attributes from /sys/devices/.../power (or prevent them from being
       added when the device is registered)
 
+  void pm_runtime_callbacks_in_irq(struct device *dev);
+    - set the power.callbacks_in_irq flag for the device, causing all callbacks
+      to be invoked with interrupts disabled
+
   void pm_runtime_mark_last_busy(struct device *dev);
     - set the power.last_busy field to the current time
 
@@ -422,14 +460,18 @@ drivers/base/power/runtime.c and include
 It is safe to execute the following helper functions from interrupt context:
 
 pm_request_idle()
+pm_runtime_suspend_irq()
 pm_request_autosuspend()
 pm_schedule_suspend()
+pm_runtime_resume_irq()
 pm_request_resume()
 pm_runtime_get_noresume()
 pm_runtime_get()
+pm_runtime_get_sync_irq()
 pm_runtime_put_noidle()
 pm_runtime_put()
 pm_runtime_put_autosuspend()
+pm_runtime_put_sync_irq()
 pm_runtime_enable()
 pm_suspend_ignore_children()
 pm_runtime_set_active()

--
To unsubscribe from this list: send the line "unsubscribe linux-omap" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Arm (vger)]     [ARM Kernel]     [ARM MSM]     [Linux Tegra]     [Linux WPAN Networking]     [Linux Wireless Networking]     [Maemo Users]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Trails]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux