===================================================================
@@ -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;
===================================================================
@@ -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);
===================================================================
@@ -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.
===================================================================
@@ -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()