On Friday, September 24, 2010, Alan Stern wrote: > 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: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx> Appled to suspend-2.6/linux-next. Thanks, Rafael > --- > > Index: usb-2.6/include/linux/pm.h > =================================================================== > --- usb-2.6.orig/include/linux/pm.h > +++ usb-2.6/include/linux/pm.h > @@ -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, > }; > > @@ -481,9 +485,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; > 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 > @@ -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(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_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( > 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_sus > 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(str > 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 > 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(st > __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 > 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 > @@ -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(st > 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 *de > 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 *de > > /* 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 > 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_ > 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 > /* 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 *d > > 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 * > 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); > @@ -1007,6 +1099,86 @@ void pm_runtime_no_callbacks(struct devi > 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. > */ > Index: usb-2.6/drivers/base/power/sysfs.c > =================================================================== > --- usb-2.6.orig/drivers/base/power/sysfs.c > +++ usb-2.6/drivers/base/power/sysfs.c > @@ -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 d > } > > 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 > @@ -311,6 +350,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 = { > Index: usb-2.6/Documentation/ABI/testing/sysfs-devices-power > =================================================================== > --- usb-2.6.orig/Documentation/ABI/testing/sysfs-devices-power > +++ usb-2.6/Documentation/ABI/testing/sysfs-devices-power > @@ -77,3 +77,21 @@ Description: > devices this 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. > + > +What: /sys/devices/.../power/autosuspend_delay_ms > +Date: September 2010 > +Contact: Alan Stern <stern@xxxxxxxxxxxxxxxxxxx> > +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. > 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 > @@ -158,7 +158,8 @@ rules: > to execute it, the other callbacks will not be executed for the same device. > > * A request to execute ->runtime_resume() will cancel any pending or > - scheduled requests to execute the other callbacks for the same device. > + scheduled requests to execute the other callbacks for the same device, > + except for scheduled autosuspends. > > 3. Run-time PM Device Fields > > @@ -166,7 +167,7 @@ The following device run-time PM fields > defined in include/linux/pm.h: > > struct timer_list suspend_timer; > - - timer used for scheduling (delayed) suspend request > + - timer used for scheduling (delayed) suspend and autosuspend requests > > unsigned long timer_expires; > - timer expiration time, in jiffies (if this is different from zero, the > @@ -236,6 +237,23 @@ defined in include/linux/pm.h: > Section 8); it may be modified only by the pm_runtime_no_callbacks() > helper function > > + unsigned int use_autosuspend; > + - indicates that the device's driver supports delayed autosuspend (see > + Section 9); it may be modified only by the > + pm_runtime{_dont}_use_autosuspend() helper functions > + > + unsigned int timer_autosuspends; > + - indicates that the PM core should attempt to carry out an autosuspend > + when the timer expires rather than a normal suspend > + > + int autosuspend_delay; > + - the delay time (in milliseconds) to be used for autosuspend > + > + unsigned long last_busy; > + - the time (in jiffies) when the pm_runtime_mark_last_busy() helper > + function was last called for this device; used in calculating inactivity > + periods for autosuspend > + > All of the above fields are members of the 'power' member of 'struct device'. > > 4. Run-time PM Device Helper Functions > @@ -261,6 +279,12 @@ drivers/base/power/runtime.c and include > error code on failure, where -EAGAIN or -EBUSY means it is safe to attempt > to suspend the device again in future > > + int pm_runtime_autosuspend(struct device *dev); > + - same as pm_runtime_suspend() except that the autosuspend delay is taken > + into account; if pm_runtime_autosuspend_expiration() says the delay has > + not yet expired then an autosuspend is scheduled for the appropriate time > + and 0 is returned > + > 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 > @@ -273,6 +297,11 @@ drivers/base/power/runtime.c and include > device (the request is represented by a work item in pm_wq); returns 0 on > success or error code if the request has not been queued up > > + int pm_request_autosuspend(struct device *dev); > + - schedule the execution of the subsystem-level suspend callback for the > + device when the autosuspend delay has expired; if the delay has already > + expired then the work item is queued up immediately > + > int pm_schedule_suspend(struct device *dev, unsigned int delay); > - schedule the execution of the subsystem-level suspend callback for the > device in future, where 'delay' is the time to wait before queuing up a > @@ -304,12 +333,20 @@ drivers/base/power/runtime.c and include > - decrement the device's usage counter > > int pm_runtime_put(struct device *dev); > - - decrement the device's usage counter, run pm_request_idle(dev) and return > - its result > + - decrement the device's usage counter; if the result is 0 then run > + pm_request_idle(dev) and return its result > + > + int pm_runtime_put_autosuspend(struct device *dev); > + - decrement the device's usage counter; if the result is 0 then run > + pm_request_autosuspend(dev) and return its result > > int pm_runtime_put_sync(struct device *dev); > - - decrement the device's usage counter, run pm_runtime_idle(dev) and return > - its result > + - 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 > > void pm_runtime_enable(struct device *dev); > - enable the run-time PM helper functions to run the device bus type's > @@ -360,19 +397,46 @@ 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_mark_last_busy(struct device *dev); > + - set the power.last_busy field to the current time > + > + void pm_runtime_use_autosuspend(struct device *dev); > + - set the power.use_autosuspend flag, enabling autosuspend delays > + > + void pm_runtime_dont_use_autosuspend(struct device *dev); > + - clear the power.use_autosuspend flag, disabling autosuspend delays > + > + void pm_runtime_set_autosuspend_delay(struct device *dev, int delay); > + - set the power.autosuspend_delay value to 'delay' (expressed in > + milliseconds); if 'delay' is negative then run-time suspends are > + prevented > + > + unsigned long pm_runtime_autosuspend_expiration(struct device *dev); > + - calculate the time when the current autosuspend delay period will expire, > + based on power.last_busy and power.autosuspend_delay; if the delay time > + is 1000 ms or larger then the expiration time is rounded up to the > + nearest second; returns 0 if the delay period has already expired or > + power.use_autosuspend isn't set, otherwise returns the expiration time > + in jiffies > + > It is safe to execute the following helper functions from interrupt context: > > pm_request_idle() > +pm_request_autosuspend() > pm_schedule_suspend() > pm_request_resume() > pm_runtime_get_noresume() > pm_runtime_get() > pm_runtime_put_noidle() > pm_runtime_put() > +pm_runtime_put_autosuspend() > +pm_runtime_enable() > pm_suspend_ignore_children() > pm_runtime_set_active() > pm_runtime_set_suspended() > -pm_runtime_enable() > +pm_runtime_suspended() > +pm_runtime_mark_last_busy() > +pm_runtime_autosuspend_expiration() > > 5. Run-time PM Initialization, Device Probing and Removal > > @@ -561,3 +625,115 @@ As a consequence, the PM core will never > or driver about run-time power changes. Instead, the driver for the device's > parent must take responsibility for telling the device's driver when the > parent's power state changes. > + > +9. Autosuspend, or automatically-delayed suspends > + > +Changing a device's power state isn't free; it requires both time and energy. > +A device should be put in a low-power state only when there's some reason to > +think it will remain in that state for a substantial time. A common heuristic > +says that a device which hasn't been used for a while is liable to remain > +unused; following this advice, drivers should not allow devices to be suspended > +at run-time until they have been inactive for some minimum period. Even when > +the heuristic ends up being non-optimal, it will still prevent devices from > +"bouncing" too rapidly between low-power and full-power states. > + > +The term "autosuspend" is an historical remnant. It doesn't mean that the > +device is automatically suspended (the subsystem or driver still has to call > +the appropriate PM routines); rather it means that run-time suspends will > +automatically be delayed until the desired period of inactivity has elapsed. > + > +Inactivity is determined based on the power.last_busy field. Drivers should > +call pm_runtime_mark_last_busy() to update this field after carrying out I/O, > +typically just before calling pm_runtime_put_autosuspend(). The desired length > +of the inactivity period is a matter of policy. Subsystems can set this length > +initially by calling pm_runtime_set_autosuspend_delay(), but after device > +registration the length should be controlled by user space, using the > +/sys/devices/.../power/autosuspend_delay_ms attribute. > + > +In order to use autosuspend, subsystems or drivers must call > +pm_runtime_use_autosuspend() (preferably before registering the device), and > +thereafter they should use the various *_autosuspend() helper functions instead > +of the non-autosuspend counterparts: > + > + Instead of: pm_runtime_suspend use: pm_runtime_autosuspend; > + Instead of: pm_schedule_suspend use: pm_request_autosuspend; > + Instead of: pm_runtime_put use: pm_runtime_put_autosuspend; > + Instead of: pm_runtime_put_sync use: pm_runtime_put_sync_autosuspend. > + > +Drivers may also continue to use the non-autosuspend helper functions; they > +will behave normally, not taking the autosuspend delay into account. > +Similarly, if the power.use_autosuspend field isn't set then the autosuspend > +helper functions will behave just like the non-autosuspend counterparts. > + > +The implementation is well suited for asynchronous use in interrupt contexts. > +However such use inevitably involves races, because the PM core can't > +synchronize ->runtime_suspend() callbacks with the arrival of I/O requests. > +This synchronization must be handled by the driver, using its private lock. > +Here is a schematic pseudo-code example: > + > + foo_read_or_write(struct foo_priv *foo, void *data) > + { > + lock(&foo->private_lock); > + add_request_to_io_queue(foo, data); > + if (foo->num_pending_requests++ == 0) > + pm_runtime_get(&foo->dev); > + if (!foo->is_suspended) > + foo_process_next_request(foo); > + unlock(&foo->private_lock); > + } > + > + foo_io_completion(struct foo_priv *foo, void *req) > + { > + lock(&foo->private_lock); > + if (--foo->num_pending_requests == 0) { > + pm_runtime_mark_last_busy(&foo->dev); > + pm_runtime_put_autosuspend(&foo->dev); > + } else { > + foo_process_next_request(foo); > + } > + unlock(&foo->private_lock); > + /* Send req result back to the user ... */ > + } > + > + int foo_runtime_suspend(struct device *dev) > + { > + struct foo_priv foo = container_of(dev, ...); > + int ret = 0; > + > + lock(&foo->private_lock); > + if (foo->num_pending_requests > 0) { > + ret = -EBUSY; > + } else { > + /* ... suspend the device ... */ > + foo->is_suspended = 1; > + } > + unlock(&foo->private_lock); > + return ret; > + } > + > + int foo_runtime_resume(struct device *dev) > + { > + struct foo_priv foo = container_of(dev, ...); > + > + lock(&foo->private_lock); > + /* ... resume the device ... */ > + foo->is_suspended = 0; > + pm_runtime_mark_last_busy(&foo->dev); > + if (foo->num_pending_requests > 0) > + foo_process_requests(foo); > + unlock(&foo->private_lock); > + return 0; > + } > + > +The important point is that after foo_io_completion() asks for an autosuspend, > +the foo_runtime_suspend() callback may race with foo_read_or_write(). > +Therefore foo_runtime_suspend() has to check whether there are any pending I/O > +requests (while holding the private lock) before allowing the suspend to > +proceed. > + > +In addition, the power.autosuspend_delay field can be changed by user space at > +any time. If a driver cares about this, it can call > +pm_runtime_autosuspend_expiration() from within the ->runtime_suspend() > +callback while holding its private lock. If the function returns a nonzero > +value then the delay has not yet expired and the callback should return > +-EAGAIN. > > > _______________________________________________ linux-pm mailing list linux-pm@xxxxxxxxxxxxxxxxxxxxxxxxxx https://lists.linux-foundation.org/mailman/listinfo/linux-pm