Commit 15bcb91d authored by Alan Stern's avatar Alan Stern Committed by Rafael J. Wysocki

PM / Runtime: Implement autosuspend support

This patch (as1427) implements the "autosuspend" facility for runtime
PM.  A few new fields are added to the dev_pm_info structure and
several new PM helper functions are defined, for telling the PM core
whether or not a device uses autosuspend, for setting the autosuspend
delay, and for marking periods of device activity.

Drivers that do not want to use autosuspend can continue using the
same helper functions as before; their behavior will not change.  In
addition, drivers supporting autosuspend can also call the old helper
functions to get the old behavior.

The details are all explained in Documentation/power/runtime_pm.txt
and Documentation/ABI/testing/sysfs-devices-power.
Signed-off-by: default avatarAlan Stern <stern@rowland.harvard.edu>
Signed-off-by: default avatarRafael J. Wysocki <rjw@sisk.pl>
parent 7490e442
......@@ -147,3 +147,21 @@ Description:
milliseconds. This attribute is read-only. If the device is
not enabled to wake up the system from sleep states, this
attribute is empty.
What: /sys/devices/.../power/autosuspend_delay_ms
Date: September 2010
Contact: Alan Stern <stern@rowland.harvard.edu>
Description:
The /sys/devices/.../power/autosuspend_delay_ms attribute
contains the autosuspend delay value (in milliseconds). Some
drivers do not want their device to suspend as soon as it
becomes idle at run time; they want the device to remain
inactive for a certain minimum period of time first. That
period is called the autosuspend delay. Negative values will
prevent the device from being suspended at run time (similar
to writing "on" to the power/control attribute). Values >=
1000 will cause the autosuspend timer expiration to be rounded
up to the nearest second.
Not all drivers support this attribute. If it isn't supported,
attempts to read or write it will yield I/O errors.
This diff is collapsed.
......@@ -9,7 +9,6 @@
#include <linux/sched.h>
#include <linux/pm_runtime.h>
#include <linux/jiffies.h>
#include "power.h"
static int rpm_resume(struct device *dev, int rpmflags);
......@@ -79,6 +78,53 @@ static void pm_runtime_cancel_pending(struct device *dev)
dev->power.request = RPM_REQ_NONE;
}
/*
* pm_runtime_autosuspend_expiration - Get a device's autosuspend-delay expiration time.
* @dev: Device to handle.
*
* Compute the autosuspend-delay expiration time based on the device's
* power.last_busy time. If the delay has already expired or is disabled
* (negative) or the power.use_autosuspend flag isn't set, return 0.
* Otherwise return the expiration time in jiffies (adjusted to be nonzero).
*
* This function may be called either with or without dev->power.lock held.
* Either way it can be racy, since power.last_busy may be updated at any time.
*/
unsigned long pm_runtime_autosuspend_expiration(struct device *dev)
{
int autosuspend_delay;
long elapsed;
unsigned long last_busy;
unsigned long expires = 0;
if (!dev->power.use_autosuspend)
goto out;
autosuspend_delay = ACCESS_ONCE(dev->power.autosuspend_delay);
if (autosuspend_delay < 0)
goto out;
last_busy = ACCESS_ONCE(dev->power.last_busy);
elapsed = jiffies - last_busy;
if (elapsed < 0)
goto out; /* jiffies has wrapped around. */
/*
* If the autosuspend_delay is >= 1 second, align the timer by rounding
* up to the nearest second.
*/
expires = last_busy + msecs_to_jiffies(autosuspend_delay);
if (autosuspend_delay >= 1000)
expires = round_jiffies(expires);
expires += !expires;
if (elapsed >= expires - last_busy)
expires = 0; /* Already expired. */
out:
return expires;
}
EXPORT_SYMBOL_GPL(pm_runtime_autosuspend_expiration);
/**
* rpm_check_suspend_allowed - Test whether a device may be suspended.
* @dev: Device to test.
......@@ -234,6 +280,32 @@ static int rpm_suspend(struct device *dev, int rpmflags)
if (retval)
goto out;
/* If the autosuspend_delay time hasn't expired yet, reschedule. */
if ((rpmflags & RPM_AUTO)
&& dev->power.runtime_status != RPM_SUSPENDING) {
unsigned long expires = pm_runtime_autosuspend_expiration(dev);
if (expires != 0) {
/* Pending requests need to be canceled. */
dev->power.request = RPM_REQ_NONE;
/*
* Optimization: If the timer is already running and is
* set to expire at or before the autosuspend delay,
* avoid the overhead of resetting it. Just let it
* expire; pm_suspend_timer_fn() will take care of the
* rest.
*/
if (!(dev->power.timer_expires && time_before_eq(
dev->power.timer_expires, expires))) {
dev->power.timer_expires = expires;
mod_timer(&dev->power.suspend_timer, expires);
}
dev->power.timer_autosuspends = 1;
goto out;
}
}
/* Other scheduled or pending requests need to be canceled. */
pm_runtime_cancel_pending(dev);
......@@ -268,7 +340,8 @@ static int rpm_suspend(struct device *dev, int rpmflags)
/* Carry out an asynchronous or a synchronous suspend. */
if (rpmflags & RPM_ASYNC) {
dev->power.request = RPM_REQ_SUSPEND;
dev->power.request = (rpmflags & RPM_AUTO) ?
RPM_REQ_AUTOSUSPEND : RPM_REQ_SUSPEND;
if (!dev->power.request_pending) {
dev->power.request_pending = true;
queue_work(pm_wq, &dev->power.work);
......@@ -383,8 +456,15 @@ static int rpm_resume(struct device *dev, int rpmflags)
if (retval)
goto out;
/* Other scheduled or pending requests need to be canceled. */
pm_runtime_cancel_pending(dev);
/*
* Other scheduled or pending requests need to be canceled. Small
* optimization: If an autosuspend timer is running, leave it running
* rather than cancelling it now only to restart it again in the near
* future.
*/
dev->power.request = RPM_REQ_NONE;
if (!dev->power.timer_autosuspends)
pm_runtime_deactivate_timer(dev);
if (dev->power.runtime_status == RPM_ACTIVE) {
retval = 1;
......@@ -568,6 +648,9 @@ static void pm_runtime_work(struct work_struct *work)
case RPM_REQ_SUSPEND:
rpm_suspend(dev, RPM_NOWAIT);
break;
case RPM_REQ_AUTOSUSPEND:
rpm_suspend(dev, RPM_NOWAIT | RPM_AUTO);
break;
case RPM_REQ_RESUME:
rpm_resume(dev, RPM_NOWAIT);
break;
......@@ -595,7 +678,8 @@ static void pm_suspend_timer_fn(unsigned long data)
/* If 'expire' is after 'jiffies' we've been called too early. */
if (expires > 0 && !time_after(expires, jiffies)) {
dev->power.timer_expires = 0;
rpm_suspend(dev, RPM_ASYNC);
rpm_suspend(dev, dev->power.timer_autosuspends ?
(RPM_ASYNC | RPM_AUTO) : RPM_ASYNC);
}
spin_unlock_irqrestore(&dev->power.lock, flags);
......@@ -627,6 +711,7 @@ int pm_schedule_suspend(struct device *dev, unsigned int delay)
dev->power.timer_expires = jiffies + msecs_to_jiffies(delay);
dev->power.timer_expires += !dev->power.timer_expires;
dev->power.timer_autosuspends = 0;
mod_timer(&dev->power.suspend_timer, dev->power.timer_expires);
out:
......@@ -670,7 +755,9 @@ EXPORT_SYMBOL_GPL(__pm_runtime_idle);
* @dev: Device to suspend.
* @rpmflags: Flag bits.
*
* Carry out a suspend, either synchronous or asynchronous.
* If the RPM_GET_PUT flag is set, decrement the device's usage count and
* return immediately if it is larger than zero. Then carry out a suspend,
* either synchronous or asynchronous.
*
* This routine may be called in atomic context if the RPM_ASYNC flag is set.
*/
......@@ -679,6 +766,11 @@ int __pm_runtime_suspend(struct device *dev, int rpmflags)
unsigned long flags;
int retval;
if (rpmflags & RPM_GET_PUT) {
if (!atomic_dec_and_test(&dev->power.usage_count))
return 0;
}
spin_lock_irqsave(&dev->power.lock, flags);
retval = rpm_suspend(dev, rpmflags);
spin_unlock_irqrestore(&dev->power.lock, flags);
......@@ -980,7 +1072,7 @@ void pm_runtime_allow(struct device *dev)
dev->power.runtime_auto = true;
if (atomic_dec_and_test(&dev->power.usage_count))
rpm_idle(dev, 0);
rpm_idle(dev, RPM_AUTO);
out:
spin_unlock_irq(&dev->power.lock);
......@@ -1006,6 +1098,86 @@ void pm_runtime_no_callbacks(struct device *dev)
}
EXPORT_SYMBOL_GPL(pm_runtime_no_callbacks);
/**
* update_autosuspend - Handle a change to a device's autosuspend settings.
* @dev: Device to handle.
* @old_delay: The former autosuspend_delay value.
* @old_use: The former use_autosuspend value.
*
* Prevent runtime suspend if the new delay is negative and use_autosuspend is
* set; otherwise allow it. Send an idle notification if suspends are allowed.
*
* This function must be called under dev->power.lock with interrupts disabled.
*/
static void update_autosuspend(struct device *dev, int old_delay, int old_use)
{
int delay = dev->power.autosuspend_delay;
/* Should runtime suspend be prevented now? */
if (dev->power.use_autosuspend && delay < 0) {
/* If it used to be allowed then prevent it. */
if (!old_use || old_delay >= 0) {
atomic_inc(&dev->power.usage_count);
rpm_resume(dev, 0);
}
}
/* Runtime suspend should be allowed now. */
else {
/* If it used to be prevented then allow it. */
if (old_use && old_delay < 0)
atomic_dec(&dev->power.usage_count);
/* Maybe we can autosuspend now. */
rpm_idle(dev, RPM_AUTO);
}
}
/**
* pm_runtime_set_autosuspend_delay - Set a device's autosuspend_delay value.
* @dev: Device to handle.
* @delay: Value of the new delay in milliseconds.
*
* Set the device's power.autosuspend_delay value. If it changes to negative
* and the power.use_autosuspend flag is set, prevent run-time suspends. If it
* changes the other way, allow run-time suspends.
*/
void pm_runtime_set_autosuspend_delay(struct device *dev, int delay)
{
int old_delay, old_use;
spin_lock_irq(&dev->power.lock);
old_delay = dev->power.autosuspend_delay;
old_use = dev->power.use_autosuspend;
dev->power.autosuspend_delay = delay;
update_autosuspend(dev, old_delay, old_use);
spin_unlock_irq(&dev->power.lock);
}
EXPORT_SYMBOL_GPL(pm_runtime_set_autosuspend_delay);
/**
* __pm_runtime_use_autosuspend - Set a device's use_autosuspend flag.
* @dev: Device to handle.
* @use: New value for use_autosuspend.
*
* Set the device's power.use_autosuspend flag, and allow or prevent run-time
* suspends as needed.
*/
void __pm_runtime_use_autosuspend(struct device *dev, bool use)
{
int old_delay, old_use;
spin_lock_irq(&dev->power.lock);
old_delay = dev->power.autosuspend_delay;
old_use = dev->power.use_autosuspend;
dev->power.use_autosuspend = use;
update_autosuspend(dev, old_delay, old_use);
spin_unlock_irq(&dev->power.lock);
}
EXPORT_SYMBOL_GPL(__pm_runtime_use_autosuspend);
/**
* pm_runtime_init - Initialize run-time PM fields in given device object.
* @dev: Device object to initialize.
......
......@@ -75,6 +75,18 @@
* attribute is set to "enabled" by bus type code or device drivers and in
* that cases it should be safe to leave the default value.
*
* autosuspend_delay_ms - Report/change a device's autosuspend_delay value
*
* Some drivers don't want to carry out a runtime suspend as soon as a
* device becomes idle; they want it always to remain idle for some period
* of time before suspending it. This period is the autosuspend_delay
* value (expressed in milliseconds) and it can be controlled by the user.
* If the value is negative then the device will never be runtime
* suspended.
*
* NOTE: The autosuspend_delay_ms attribute and the autosuspend_delay
* value are used only if the driver calls pm_runtime_use_autosuspend().
*
* wakeup_count - Report the number of wakeup events related to the device
*/
......@@ -173,6 +185,33 @@ static ssize_t rtpm_status_show(struct device *dev,
}
static DEVICE_ATTR(runtime_status, 0444, rtpm_status_show, NULL);
static ssize_t autosuspend_delay_ms_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
if (!dev->power.use_autosuspend)
return -EIO;
return sprintf(buf, "%d\n", dev->power.autosuspend_delay);
}
static ssize_t autosuspend_delay_ms_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t n)
{
long delay;
if (!dev->power.use_autosuspend)
return -EIO;
if (strict_strtol(buf, 10, &delay) != 0 || delay != (int) delay)
return -EINVAL;
pm_runtime_set_autosuspend_delay(dev, delay);
return n;
}
static DEVICE_ATTR(autosuspend_delay_ms, 0644, autosuspend_delay_ms_show,
autosuspend_delay_ms_store);
#endif
static ssize_t
......@@ -428,6 +467,7 @@ static struct attribute *runtime_attrs[] = {
&dev_attr_control.attr,
&dev_attr_runtime_suspended_time.attr,
&dev_attr_runtime_active_time.attr,
&dev_attr_autosuspend_delay_ms.attr,
NULL,
};
static struct attribute_group pm_runtime_attr_group = {
......
......@@ -444,6 +444,9 @@ enum rpm_status {
*
* RPM_REQ_SUSPEND Run the device bus type's ->runtime_suspend() callback
*
* RPM_REQ_AUTOSUSPEND Same as RPM_REQ_SUSPEND, but not until the device has
* been inactive for as long as power.autosuspend_delay
*
* RPM_REQ_RESUME Run the device bus type's ->runtime_resume() callback
*/
......@@ -451,6 +454,7 @@ enum rpm_request {
RPM_REQ_NONE = 0,
RPM_REQ_IDLE,
RPM_REQ_SUSPEND,
RPM_REQ_AUTOSUSPEND,
RPM_REQ_RESUME,
};
......@@ -482,9 +486,13 @@ struct dev_pm_info {
unsigned int run_wake:1;
unsigned int runtime_auto:1;
unsigned int no_callbacks:1;
unsigned int use_autosuspend:1;
unsigned int timer_autosuspends:1;
enum rpm_request request;
enum rpm_status runtime_status;
int runtime_error;
int autosuspend_delay;
unsigned long last_busy;
unsigned long active_jiffies;
unsigned long suspended_jiffies;
unsigned long accounting_timestamp;
......
......@@ -12,12 +12,15 @@
#include <linux/device.h>
#include <linux/pm.h>
#include <linux/jiffies.h>
/* Runtime PM flag argument bits */
#define RPM_ASYNC 0x01 /* Request is asynchronous */
#define RPM_NOWAIT 0x02 /* Don't wait for concurrent
state change */
#define RPM_GET_PUT 0x04 /* Increment/decrement the
usage_count */
#define RPM_AUTO 0x08 /* Use autosuspend_delay */
#ifdef CONFIG_PM_RUNTIME
......@@ -37,6 +40,9 @@ extern int pm_generic_runtime_idle(struct device *dev);
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_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);
static inline bool pm_children_suspended(struct device *dev)
{
......@@ -74,6 +80,11 @@ static inline bool pm_runtime_suspended(struct device *dev)
return dev->power.runtime_status == RPM_SUSPENDED;
}
static inline void pm_runtime_mark_last_busy(struct device *dev)
{
ACCESS_ONCE(dev->power.last_busy) = jiffies;
}
#else /* !CONFIG_PM_RUNTIME */
static inline int __pm_runtime_idle(struct device *dev, int rpmflags)
......@@ -113,6 +124,14 @@ 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_mark_last_busy(struct device *dev) {}
static inline void __pm_runtime_use_autosuspend(struct device *dev,
bool use) {}
static inline void pm_runtime_set_autosuspend_delay(struct device *dev,
int delay) {}
static inline unsigned long pm_runtime_autosuspend_expiration(
struct device *dev) { return 0; }
#endif /* !CONFIG_PM_RUNTIME */
static inline int pm_runtime_idle(struct device *dev)
......@@ -125,6 +144,11 @@ static inline int pm_runtime_suspend(struct device *dev)
return __pm_runtime_suspend(dev, 0);
}
static inline int pm_runtime_autosuspend(struct device *dev)
{
return __pm_runtime_suspend(dev, RPM_AUTO);
}
static inline int pm_runtime_resume(struct device *dev)
{
return __pm_runtime_resume(dev, 0);
......@@ -155,11 +179,22 @@ static inline int pm_runtime_put(struct device *dev)
return __pm_runtime_idle(dev, RPM_GET_PUT | RPM_ASYNC);
}
static inline int pm_runtime_put_autosuspend(struct device *dev)
{
return __pm_runtime_suspend(dev,
RPM_GET_PUT | RPM_ASYNC | RPM_AUTO);
}
static inline int pm_runtime_put_sync(struct device *dev)
{
return __pm_runtime_idle(dev, RPM_GET_PUT);
}
static inline int pm_runtime_put_sync_autosuspend(struct device *dev)
{
return __pm_runtime_suspend(dev, RPM_GET_PUT | RPM_AUTO);
}
static inline int pm_runtime_set_active(struct device *dev)
{
return __pm_runtime_set_status(dev, RPM_ACTIVE);
......@@ -175,4 +210,14 @@ static inline void pm_runtime_disable(struct device *dev)
__pm_runtime_disable(dev, true);
}
static inline void pm_runtime_use_autosuspend(struct device *dev)
{
__pm_runtime_use_autosuspend(dev, true);
}
static inline void pm_runtime_dont_use_autosuspend(struct device *dev)
{
__pm_runtime_use_autosuspend(dev, false);
}
#endif
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment