Skip to content

Commit

Permalink
PM-runtime: Switch autosuspend over to using hrtimers
Browse files Browse the repository at this point in the history
PM-runtime uses the timer infrastructure for autosuspend. This implies
that the minimum time before autosuspending a device is in the range
of 1 tick included to 2 ticks excluded
 -On arm64 this means between 4ms and 8ms with default jiffies
  configuration
 -And on arm, it is between 10ms and 20ms

These values are quite high for embedded systems which sometimes want
the duration to be in the range of 1 ms.

It is possible to switch autosuspend over to using hrtimers to get
finer granularity for short durations and take advantage of slack to
retain some margins and get long timeouts with minimum wakeups.

On an arm64 platform that uses 1ms for autosuspending timeout of its
GPU, idle power is reduced by 10% with hrtimer.

The latency impact on arm64 hikey octo cores is:
 - mark_last_busy: from 1.11 us to 1.25 us
 - rpm_suspend: from 15.54 us to 15.38 us
[Only the code path of rpm_suspend() that starts hrtimer has been
measured.]

arm64 image (arm64 default defconfig) decreases by around 3KB
with following details:

$ size vmlinux-timer
   text	   data	    bss	    dec	    hex	filename
12034646	6869268	 386840	19290754	1265a82	vmlinux

$ size vmlinux-hrtimer
   text	   data	    bss	    dec	    hex	filename
12030550	6870164	 387032	19287746	1264ec2	vmlinux

The latency impact on arm 32bits snowball dual cores is :
 - mark_last_busy: from 0.31 us usec to 0.77 us
 - rpm_suspend: from 6.83 us to 6.67 usec

The increase of the image for snowball platform that I used for
testing performance impact, is neglictable (244B).

$ size vmlinux-timer
   text	   data	    bss	    dec	    hex	filename
7157961	2119580	 264120	9541661	 91981d	build-ux500/vmlinux

size vmlinux-hrtimer
   text	   data	    bss	    dec	    hex	filename
7157773	2119884	 264248	9541905	 919911	vmlinux-hrtimer

And arm 32bits image (multi_v7_defconfig) increases by around 1.7KB
with following details:

$ size vmlinux-timer
   text	   data	    bss	    dec	    hex	filename
13304443	6803420	 402768	20510631	138f7a7	vmlinux

$ size vmlinux-hrtimer
   text	   data	    bss	    dec	    hex	filename
13304299	6805276	 402768	20512343	138fe57	vmlinux

Signed-off-by: Vincent Guittot <vincent.guittot@linaro.org>
Reviewed-by: Ulf Hansson <ulf.hansson@linaro.org>
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
  • Loading branch information
Vincent Guittot authored and Rafael J. Wysocki committed Dec 19, 2018
1 parent 7566ec3 commit 8234f67
Show file tree
Hide file tree
Showing 3 changed files with 40 additions and 34 deletions.
63 changes: 34 additions & 29 deletions drivers/base/power/runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/

#include <linux/sched/mm.h>
#include <linux/ktime.h>
#include <linux/hrtimer.h>
#include <linux/export.h>
#include <linux/pm_runtime.h>
#include <linux/pm_wakeirq.h>
Expand Down Expand Up @@ -93,7 +95,7 @@ static void __update_runtime_status(struct device *dev, enum rpm_status status)
static void pm_runtime_deactivate_timer(struct device *dev)
{
if (dev->power.timer_expires > 0) {
del_timer(&dev->power.suspend_timer);
hrtimer_cancel(&dev->power.suspend_timer);
dev->power.timer_expires = 0;
}
}
Expand Down Expand Up @@ -124,12 +126,11 @@ static void pm_runtime_cancel_pending(struct device *dev)
* 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)
u64 pm_runtime_autosuspend_expiration(struct device *dev)
{
int autosuspend_delay;
long elapsed;
unsigned long last_busy;
unsigned long expires = 0;
u64 last_busy, expires = 0;
u64 now = ktime_to_ns(ktime_get());

if (!dev->power.use_autosuspend)
goto out;
Expand All @@ -139,19 +140,9 @@ unsigned long pm_runtime_autosuspend_expiration(struct device *dev)
goto out;

last_busy = READ_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 = last_busy + autosuspend_delay * NSEC_PER_MSEC;
if (expires <= now)
expires = 0; /* Already expired. */

out:
Expand Down Expand Up @@ -515,7 +506,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)
/* 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);
u64 expires = pm_runtime_autosuspend_expiration(dev);

if (expires != 0) {
/* Pending requests need to be canceled. */
Expand All @@ -528,10 +519,20 @@ static int rpm_suspend(struct device *dev, int rpmflags)
* expire; pm_suspend_timer_fn() will take care of the
* rest.
*/
if (!(dev->power.timer_expires && time_before_eq(
dev->power.timer_expires, expires))) {
if (!(dev->power.timer_expires &&
dev->power.timer_expires <= expires)) {
/*
* We add a slack of 25% to gather wakeups
* without sacrificing the granularity.
*/
u64 slack = READ_ONCE(dev->power.autosuspend_delay) *
(NSEC_PER_MSEC >> 2);

dev->power.timer_expires = expires;
mod_timer(&dev->power.suspend_timer, expires);
hrtimer_start_range_ns(&dev->power.suspend_timer,
ns_to_ktime(expires),
slack,
HRTIMER_MODE_ABS);
}
dev->power.timer_autosuspends = 1;
goto out;
Expand Down Expand Up @@ -895,23 +896,25 @@ static void pm_runtime_work(struct work_struct *work)
*
* Check if the time is right and queue a suspend request.
*/
static void pm_suspend_timer_fn(struct timer_list *t)
static enum hrtimer_restart pm_suspend_timer_fn(struct hrtimer *timer)
{
struct device *dev = from_timer(dev, t, power.suspend_timer);
struct device *dev = container_of(timer, struct device, power.suspend_timer);
unsigned long flags;
unsigned long expires;
u64 expires;

spin_lock_irqsave(&dev->power.lock, flags);

expires = dev->power.timer_expires;
/* If 'expire' is after 'jiffies' we've been called too early. */
if (expires > 0 && !time_after(expires, jiffies)) {
if (expires > 0 && expires < ktime_to_ns(ktime_get())) {
dev->power.timer_expires = 0;
rpm_suspend(dev, dev->power.timer_autosuspends ?
(RPM_ASYNC | RPM_AUTO) : RPM_ASYNC);
}

spin_unlock_irqrestore(&dev->power.lock, flags);

return HRTIMER_NORESTART;
}

/**
Expand All @@ -922,6 +925,7 @@ static void pm_suspend_timer_fn(struct timer_list *t)
int pm_schedule_suspend(struct device *dev, unsigned int delay)
{
unsigned long flags;
ktime_t expires;
int retval;

spin_lock_irqsave(&dev->power.lock, flags);
Expand All @@ -938,10 +942,10 @@ int pm_schedule_suspend(struct device *dev, unsigned int delay)
/* Other scheduled or pending requests need to be canceled. */
pm_runtime_cancel_pending(dev);

dev->power.timer_expires = jiffies + msecs_to_jiffies(delay);
dev->power.timer_expires += !dev->power.timer_expires;
expires = ktime_add(ktime_get(), ms_to_ktime(delay));
dev->power.timer_expires = ktime_to_ns(expires);
dev->power.timer_autosuspends = 0;
mod_timer(&dev->power.suspend_timer, dev->power.timer_expires);
hrtimer_start(&dev->power.suspend_timer, expires, HRTIMER_MODE_ABS);

out:
spin_unlock_irqrestore(&dev->power.lock, flags);
Expand Down Expand Up @@ -1491,7 +1495,8 @@ void pm_runtime_init(struct device *dev)
INIT_WORK(&dev->power.work, pm_runtime_work);

dev->power.timer_expires = 0;
timer_setup(&dev->power.suspend_timer, pm_suspend_timer_fn, 0);
hrtimer_init(&dev->power.suspend_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
dev->power.suspend_timer.function = pm_suspend_timer_fn;

init_waitqueue_head(&dev->power.wait_queue);
}
Expand Down
5 changes: 3 additions & 2 deletions include/linux/pm.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/timer.h>
#include <linux/hrtimer.h>
#include <linux/completion.h>

/*
Expand Down Expand Up @@ -608,7 +609,7 @@ struct dev_pm_info {
unsigned int should_wakeup:1;
#endif
#ifdef CONFIG_PM
struct timer_list suspend_timer;
struct hrtimer suspend_timer;
unsigned long timer_expires;
struct work_struct work;
wait_queue_head_t wait_queue;
Expand All @@ -631,7 +632,7 @@ struct dev_pm_info {
enum rpm_status runtime_status;
int runtime_error;
int autosuspend_delay;
unsigned long last_busy;
u64 last_busy;
unsigned long active_jiffies;
unsigned long suspended_jiffies;
unsigned long accounting_timestamp;
Expand Down
6 changes: 3 additions & 3 deletions include/linux/pm_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ 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_set_autosuspend_delay(struct device *dev, int delay);
extern unsigned long pm_runtime_autosuspend_expiration(struct device *dev);
extern u64 pm_runtime_autosuspend_expiration(struct device *dev);
extern void pm_runtime_update_max_time_suspended(struct device *dev,
s64 delta_ns);
extern void pm_runtime_set_memalloc_noio(struct device *dev, bool enable);
Expand Down Expand Up @@ -105,7 +105,7 @@ static inline bool pm_runtime_callbacks_present(struct device *dev)

static inline void pm_runtime_mark_last_busy(struct device *dev)
{
WRITE_ONCE(dev->power.last_busy, jiffies);
WRITE_ONCE(dev->power.last_busy, ktime_to_ns(ktime_get()));
}

static inline bool pm_runtime_is_irq_safe(struct device *dev)
Expand Down Expand Up @@ -168,7 +168,7 @@ 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(
static inline u64 pm_runtime_autosuspend_expiration(
struct device *dev) { return 0; }
static inline void pm_runtime_set_memalloc_noio(struct device *dev,
bool enable){}
Expand Down

0 comments on commit 8234f67

Please sign in to comment.