PM / Runtime: Add synchronous runtime interface for interrupt handlers (v3)
This patch (as1431c) makes the synchronous runtime-PM interface suitable for use in interrupt handlers. Subsystems can call the new pm_runtime_irq_safe() function to tell the PM core that a device's runtime_suspend and runtime_resume callbacks should be invoked with interrupts disabled and the spinlock held. This permits the pm_runtime_get_sync() and the new pm_runtime_put_sync_suspend() routines to be called from within interrupt handlers. When a device is declared irq-safe in this way, the PM core increments the parent's usage count, so the parent will never be runtime suspended. This prevents difficult situations in which an irq-safe device can't resume because it is forced to wait for its non-irq-safe parent. Signed-off-by: Alan Stern <stern@rowland.harvard.edu> Signed-off-by: Rafael J. Wysocki <rjw@sisk.pl>
This commit is contained in:
parent
5262a47502
commit
c7b61de5b7
|
@ -50,6 +50,15 @@ type's callbacks are not defined) of given device. The bus type, device type
|
||||||
and device class callbacks are referred to as subsystem-level callbacks in what
|
and device class callbacks are referred to as subsystem-level callbacks in what
|
||||||
follows.
|
follows.
|
||||||
|
|
||||||
|
By default, the callbacks are always invoked in process context with interrupts
|
||||||
|
enabled. However, subsystems can use the pm_runtime_irq_safe() helper function
|
||||||
|
to tell the PM core that a device's ->runtime_suspend() and ->runtime_resume()
|
||||||
|
callbacks should be invoked in atomic context with interrupts disabled
|
||||||
|
(->runtime_idle() is still invoked the default way). This implies that these
|
||||||
|
callback routines must not block or sleep, but it also means that the
|
||||||
|
synchronous helper functions listed at the end of Section 4 can be used within
|
||||||
|
an interrupt handler or in an atomic context.
|
||||||
|
|
||||||
The subsystem-level suspend callback is _entirely_ _responsible_ for handling
|
The subsystem-level suspend callback is _entirely_ _responsible_ for handling
|
||||||
the suspend of the device as appropriate, which may, but need not include
|
the suspend of the device as appropriate, which may, but need not include
|
||||||
executing the device driver's own ->runtime_suspend() callback (from the
|
executing the device driver's own ->runtime_suspend() callback (from the
|
||||||
|
@ -237,6 +246,10 @@ defined in include/linux/pm.h:
|
||||||
Section 8); it may be modified only by the pm_runtime_no_callbacks()
|
Section 8); it may be modified only by the pm_runtime_no_callbacks()
|
||||||
helper function
|
helper function
|
||||||
|
|
||||||
|
unsigned int irq_safe;
|
||||||
|
- indicates that the ->runtime_suspend() and ->runtime_resume() callbacks
|
||||||
|
will be invoked with the spinlock held and interrupts disabled
|
||||||
|
|
||||||
unsigned int use_autosuspend;
|
unsigned int use_autosuspend;
|
||||||
- indicates that the device's driver supports delayed autosuspend (see
|
- indicates that the device's driver supports delayed autosuspend (see
|
||||||
Section 9); it may be modified only by the
|
Section 9); it may be modified only by the
|
||||||
|
@ -344,6 +357,10 @@ drivers/base/power/runtime.c and include/linux/pm_runtime.h:
|
||||||
- decrement the device's usage counter; if the result is 0 then run
|
- decrement the device's usage counter; if the result is 0 then run
|
||||||
pm_runtime_idle(dev) and return its result
|
pm_runtime_idle(dev) and return its result
|
||||||
|
|
||||||
|
int pm_runtime_put_sync_suspend(struct device *dev);
|
||||||
|
- decrement the device's usage counter; if the result is 0 then run
|
||||||
|
pm_runtime_suspend(dev) and return its result
|
||||||
|
|
||||||
int pm_runtime_put_sync_autosuspend(struct device *dev);
|
int pm_runtime_put_sync_autosuspend(struct device *dev);
|
||||||
- decrement the device's usage counter; if the result is 0 then run
|
- decrement the device's usage counter; if the result is 0 then run
|
||||||
pm_runtime_autosuspend(dev) and return its result
|
pm_runtime_autosuspend(dev) and return its result
|
||||||
|
@ -397,6 +414,11 @@ drivers/base/power/runtime.c and include/linux/pm_runtime.h:
|
||||||
PM attributes from /sys/devices/.../power (or prevent them from being
|
PM attributes from /sys/devices/.../power (or prevent them from being
|
||||||
added when the device is registered)
|
added when the device is registered)
|
||||||
|
|
||||||
|
void pm_runtime_irq_safe(struct device *dev);
|
||||||
|
- set the power.irq_safe flag for the device, causing the runtime-PM
|
||||||
|
suspend and resume callbacks (but not the idle callback) to be invoked
|
||||||
|
with interrupts disabled
|
||||||
|
|
||||||
void pm_runtime_mark_last_busy(struct device *dev);
|
void pm_runtime_mark_last_busy(struct device *dev);
|
||||||
- set the power.last_busy field to the current time
|
- set the power.last_busy field to the current time
|
||||||
|
|
||||||
|
@ -438,6 +460,15 @@ pm_runtime_suspended()
|
||||||
pm_runtime_mark_last_busy()
|
pm_runtime_mark_last_busy()
|
||||||
pm_runtime_autosuspend_expiration()
|
pm_runtime_autosuspend_expiration()
|
||||||
|
|
||||||
|
If pm_runtime_irq_safe() has been called for a device then the following helper
|
||||||
|
functions may also be used in interrupt context:
|
||||||
|
|
||||||
|
pm_runtime_suspend()
|
||||||
|
pm_runtime_autosuspend()
|
||||||
|
pm_runtime_resume()
|
||||||
|
pm_runtime_get_sync()
|
||||||
|
pm_runtime_put_sync_suspend()
|
||||||
|
|
||||||
5. Run-time PM Initialization, Device Probing and Removal
|
5. Run-time PM Initialization, Device Probing and Removal
|
||||||
|
|
||||||
Initially, the run-time PM is disabled for all devices, which means that the
|
Initially, the run-time PM is disabled for all devices, which means that the
|
||||||
|
|
|
@ -250,13 +250,16 @@ static int rpm_callback(int (*cb)(struct device *), struct device *dev)
|
||||||
if (!cb)
|
if (!cb)
|
||||||
return -ENOSYS;
|
return -ENOSYS;
|
||||||
|
|
||||||
spin_unlock_irq(&dev->power.lock);
|
if (dev->power.irq_safe) {
|
||||||
|
retval = cb(dev);
|
||||||
|
} else {
|
||||||
|
spin_unlock_irq(&dev->power.lock);
|
||||||
|
|
||||||
retval = cb(dev);
|
retval = cb(dev);
|
||||||
|
|
||||||
spin_lock_irq(&dev->power.lock);
|
spin_lock_irq(&dev->power.lock);
|
||||||
|
}
|
||||||
dev->power.runtime_error = retval;
|
dev->power.runtime_error = retval;
|
||||||
|
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,7 +407,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parent && !parent->power.ignore_children) {
|
if (parent && !parent->power.ignore_children && !dev->power.irq_safe) {
|
||||||
spin_unlock_irq(&dev->power.lock);
|
spin_unlock_irq(&dev->power.lock);
|
||||||
|
|
||||||
pm_request_idle(parent);
|
pm_request_idle(parent);
|
||||||
|
@ -527,10 +530,13 @@ static int rpm_resume(struct device *dev, int rpmflags)
|
||||||
|
|
||||||
if (!parent && dev->parent) {
|
if (!parent && dev->parent) {
|
||||||
/*
|
/*
|
||||||
* Increment the parent's resume counter and resume it if
|
* Increment the parent's usage counter and resume it if
|
||||||
* necessary.
|
* necessary. Not needed if dev is irq-safe; then the
|
||||||
|
* parent is permanently resumed.
|
||||||
*/
|
*/
|
||||||
parent = dev->parent;
|
parent = dev->parent;
|
||||||
|
if (dev->power.irq_safe)
|
||||||
|
goto skip_parent;
|
||||||
spin_unlock(&dev->power.lock);
|
spin_unlock(&dev->power.lock);
|
||||||
|
|
||||||
pm_runtime_get_noresume(parent);
|
pm_runtime_get_noresume(parent);
|
||||||
|
@ -553,6 +559,7 @@ static int rpm_resume(struct device *dev, int rpmflags)
|
||||||
goto out;
|
goto out;
|
||||||
goto repeat;
|
goto repeat;
|
||||||
}
|
}
|
||||||
|
skip_parent:
|
||||||
|
|
||||||
if (dev->power.no_callbacks)
|
if (dev->power.no_callbacks)
|
||||||
goto no_callback; /* Assume success. */
|
goto no_callback; /* Assume success. */
|
||||||
|
@ -584,7 +591,7 @@ static int rpm_resume(struct device *dev, int rpmflags)
|
||||||
rpm_idle(dev, RPM_ASYNC);
|
rpm_idle(dev, RPM_ASYNC);
|
||||||
|
|
||||||
out:
|
out:
|
||||||
if (parent) {
|
if (parent && !dev->power.irq_safe) {
|
||||||
spin_unlock_irq(&dev->power.lock);
|
spin_unlock_irq(&dev->power.lock);
|
||||||
|
|
||||||
pm_runtime_put(parent);
|
pm_runtime_put(parent);
|
||||||
|
@ -1065,7 +1072,6 @@ EXPORT_SYMBOL_GPL(pm_runtime_allow);
|
||||||
* Set the power.no_callbacks flag, which tells the PM core that this
|
* 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
|
* 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.
|
* callbacks of its own. The run-time sysfs attributes will be removed.
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
void pm_runtime_no_callbacks(struct device *dev)
|
void pm_runtime_no_callbacks(struct device *dev)
|
||||||
{
|
{
|
||||||
|
@ -1077,6 +1083,27 @@ void pm_runtime_no_callbacks(struct device *dev)
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(pm_runtime_no_callbacks);
|
EXPORT_SYMBOL_GPL(pm_runtime_no_callbacks);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* pm_runtime_irq_safe - Leave interrupts disabled during callbacks.
|
||||||
|
* @dev: Device to handle
|
||||||
|
*
|
||||||
|
* Set the power.irq_safe flag, which tells the PM core that the
|
||||||
|
* ->runtime_suspend() and ->runtime_resume() callbacks for this device should
|
||||||
|
* always be invoked with the spinlock held and interrupts disabled. It also
|
||||||
|
* causes the parent's usage counter to be permanently incremented, preventing
|
||||||
|
* the parent from runtime suspending -- otherwise an irq-safe child might have
|
||||||
|
* to wait for a non-irq-safe parent.
|
||||||
|
*/
|
||||||
|
void pm_runtime_irq_safe(struct device *dev)
|
||||||
|
{
|
||||||
|
if (dev->parent)
|
||||||
|
pm_runtime_get_sync(dev->parent);
|
||||||
|
spin_lock_irq(&dev->power.lock);
|
||||||
|
dev->power.irq_safe = 1;
|
||||||
|
spin_unlock_irq(&dev->power.lock);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(pm_runtime_irq_safe);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* update_autosuspend - Handle a change to a device's autosuspend settings.
|
* update_autosuspend - Handle a change to a device's autosuspend settings.
|
||||||
* @dev: Device to handle.
|
* @dev: Device to handle.
|
||||||
|
@ -1199,4 +1226,6 @@ void pm_runtime_remove(struct device *dev)
|
||||||
/* Change the status back to 'suspended' to match the initial status. */
|
/* Change the status back to 'suspended' to match the initial status. */
|
||||||
if (dev->power.runtime_status == RPM_ACTIVE)
|
if (dev->power.runtime_status == RPM_ACTIVE)
|
||||||
pm_runtime_set_suspended(dev);
|
pm_runtime_set_suspended(dev);
|
||||||
|
if (dev->power.irq_safe && dev->parent)
|
||||||
|
pm_runtime_put_sync(dev->parent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -486,6 +486,7 @@ struct dev_pm_info {
|
||||||
unsigned int run_wake:1;
|
unsigned int run_wake:1;
|
||||||
unsigned int runtime_auto:1;
|
unsigned int runtime_auto:1;
|
||||||
unsigned int no_callbacks:1;
|
unsigned int no_callbacks:1;
|
||||||
|
unsigned int irq_safe:1;
|
||||||
unsigned int use_autosuspend:1;
|
unsigned int use_autosuspend:1;
|
||||||
unsigned int timer_autosuspends:1;
|
unsigned int timer_autosuspends:1;
|
||||||
enum rpm_request request;
|
enum rpm_request request;
|
||||||
|
|
|
@ -40,6 +40,7 @@ extern int pm_generic_runtime_idle(struct device *dev);
|
||||||
extern int pm_generic_runtime_suspend(struct device *dev);
|
extern int pm_generic_runtime_suspend(struct device *dev);
|
||||||
extern int pm_generic_runtime_resume(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_no_callbacks(struct device *dev);
|
||||||
|
extern void pm_runtime_irq_safe(struct device *dev);
|
||||||
extern void __pm_runtime_use_autosuspend(struct device *dev, bool use);
|
extern void __pm_runtime_use_autosuspend(struct device *dev, bool use);
|
||||||
extern void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);
|
extern void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);
|
||||||
extern unsigned long pm_runtime_autosuspend_expiration(struct device *dev);
|
extern unsigned long pm_runtime_autosuspend_expiration(struct device *dev);
|
||||||
|
@ -124,6 +125,7 @@ static inline int pm_generic_runtime_idle(struct device *dev) { return 0; }
|
||||||
static inline int pm_generic_runtime_suspend(struct device *dev) { return 0; }
|
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 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_no_callbacks(struct device *dev) {}
|
||||||
|
static inline void pm_runtime_irq_safe(struct device *dev) {}
|
||||||
|
|
||||||
static inline void pm_runtime_mark_last_busy(struct device *dev) {}
|
static inline void pm_runtime_mark_last_busy(struct device *dev) {}
|
||||||
static inline void __pm_runtime_use_autosuspend(struct device *dev,
|
static inline void __pm_runtime_use_autosuspend(struct device *dev,
|
||||||
|
@ -196,6 +198,11 @@ static inline int pm_runtime_put_sync(struct device *dev)
|
||||||
return __pm_runtime_idle(dev, RPM_GET_PUT);
|
return __pm_runtime_idle(dev, RPM_GET_PUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline int pm_runtime_put_sync_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
return __pm_runtime_suspend(dev, RPM_GET_PUT);
|
||||||
|
}
|
||||||
|
|
||||||
static inline int pm_runtime_put_sync_autosuspend(struct device *dev)
|
static inline int pm_runtime_put_sync_autosuspend(struct device *dev)
|
||||||
{
|
{
|
||||||
return __pm_runtime_suspend(dev, RPM_GET_PUT | RPM_AUTO);
|
return __pm_runtime_suspend(dev, RPM_GET_PUT | RPM_AUTO);
|
||||||
|
|
Loading…
Reference in New Issue