Skip to content

Commit 8234f67

Browse files
vingu-linarorafaeljw
authored andcommitted
PM-runtime: Switch autosuspend over to using hrtimers
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>
1 parent 7566ec3 commit 8234f67

File tree

3 files changed

+40
-34
lines changed

3 files changed

+40
-34
lines changed

drivers/base/power/runtime.c

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
*/
99

1010
#include <linux/sched/mm.h>
11+
#include <linux/ktime.h>
12+
#include <linux/hrtimer.h>
1113
#include <linux/export.h>
1214
#include <linux/pm_runtime.h>
1315
#include <linux/pm_wakeirq.h>
@@ -93,7 +95,7 @@ static void __update_runtime_status(struct device *dev, enum rpm_status status)
9395
static void pm_runtime_deactivate_timer(struct device *dev)
9496
{
9597
if (dev->power.timer_expires > 0) {
96-
del_timer(&dev->power.suspend_timer);
98+
hrtimer_cancel(&dev->power.suspend_timer);
9799
dev->power.timer_expires = 0;
98100
}
99101
}
@@ -124,12 +126,11 @@ static void pm_runtime_cancel_pending(struct device *dev)
124126
* This function may be called either with or without dev->power.lock held.
125127
* Either way it can be racy, since power.last_busy may be updated at any time.
126128
*/
127-
unsigned long pm_runtime_autosuspend_expiration(struct device *dev)
129+
u64 pm_runtime_autosuspend_expiration(struct device *dev)
128130
{
129131
int autosuspend_delay;
130-
long elapsed;
131-
unsigned long last_busy;
132-
unsigned long expires = 0;
132+
u64 last_busy, expires = 0;
133+
u64 now = ktime_to_ns(ktime_get());
133134

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

141142
last_busy = READ_ONCE(dev->power.last_busy);
142-
elapsed = jiffies - last_busy;
143-
if (elapsed < 0)
144-
goto out; /* jiffies has wrapped around. */
145143

146-
/*
147-
* If the autosuspend_delay is >= 1 second, align the timer by rounding
148-
* up to the nearest second.
149-
*/
150-
expires = last_busy + msecs_to_jiffies(autosuspend_delay);
151-
if (autosuspend_delay >= 1000)
152-
expires = round_jiffies(expires);
153-
expires += !expires;
154-
if (elapsed >= expires - last_busy)
144+
expires = last_busy + autosuspend_delay * NSEC_PER_MSEC;
145+
if (expires <= now)
155146
expires = 0; /* Already expired. */
156147

157148
out:
@@ -515,7 +506,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)
515506
/* If the autosuspend_delay time hasn't expired yet, reschedule. */
516507
if ((rpmflags & RPM_AUTO)
517508
&& dev->power.runtime_status != RPM_SUSPENDING) {
518-
unsigned long expires = pm_runtime_autosuspend_expiration(dev);
509+
u64 expires = pm_runtime_autosuspend_expiration(dev);
519510

520511
if (expires != 0) {
521512
/* Pending requests need to be canceled. */
@@ -528,10 +519,20 @@ static int rpm_suspend(struct device *dev, int rpmflags)
528519
* expire; pm_suspend_timer_fn() will take care of the
529520
* rest.
530521
*/
531-
if (!(dev->power.timer_expires && time_before_eq(
532-
dev->power.timer_expires, expires))) {
522+
if (!(dev->power.timer_expires &&
523+
dev->power.timer_expires <= expires)) {
524+
/*
525+
* We add a slack of 25% to gather wakeups
526+
* without sacrificing the granularity.
527+
*/
528+
u64 slack = READ_ONCE(dev->power.autosuspend_delay) *
529+
(NSEC_PER_MSEC >> 2);
530+
533531
dev->power.timer_expires = expires;
534-
mod_timer(&dev->power.suspend_timer, expires);
532+
hrtimer_start_range_ns(&dev->power.suspend_timer,
533+
ns_to_ktime(expires),
534+
slack,
535+
HRTIMER_MODE_ABS);
535536
}
536537
dev->power.timer_autosuspends = 1;
537538
goto out;
@@ -895,23 +896,25 @@ static void pm_runtime_work(struct work_struct *work)
895896
*
896897
* Check if the time is right and queue a suspend request.
897898
*/
898-
static void pm_suspend_timer_fn(struct timer_list *t)
899+
static enum hrtimer_restart pm_suspend_timer_fn(struct hrtimer *timer)
899900
{
900-
struct device *dev = from_timer(dev, t, power.suspend_timer);
901+
struct device *dev = container_of(timer, struct device, power.suspend_timer);
901902
unsigned long flags;
902-
unsigned long expires;
903+
u64 expires;
903904

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

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

914915
spin_unlock_irqrestore(&dev->power.lock, flags);
916+
917+
return HRTIMER_NORESTART;
915918
}
916919

917920
/**
@@ -922,6 +925,7 @@ static void pm_suspend_timer_fn(struct timer_list *t)
922925
int pm_schedule_suspend(struct device *dev, unsigned int delay)
923926
{
924927
unsigned long flags;
928+
ktime_t expires;
925929
int retval;
926930

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

941-
dev->power.timer_expires = jiffies + msecs_to_jiffies(delay);
942-
dev->power.timer_expires += !dev->power.timer_expires;
945+
expires = ktime_add(ktime_get(), ms_to_ktime(delay));
946+
dev->power.timer_expires = ktime_to_ns(expires);
943947
dev->power.timer_autosuspends = 0;
944-
mod_timer(&dev->power.suspend_timer, dev->power.timer_expires);
948+
hrtimer_start(&dev->power.suspend_timer, expires, HRTIMER_MODE_ABS);
945949

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

14931497
dev->power.timer_expires = 0;
1494-
timer_setup(&dev->power.suspend_timer, pm_suspend_timer_fn, 0);
1498+
hrtimer_init(&dev->power.suspend_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
1499+
dev->power.suspend_timer.function = pm_suspend_timer_fn;
14951500

14961501
init_waitqueue_head(&dev->power.wait_queue);
14971502
}

include/linux/pm.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include <linux/spinlock.h>
2727
#include <linux/wait.h>
2828
#include <linux/timer.h>
29+
#include <linux/hrtimer.h>
2930
#include <linux/completion.h>
3031

3132
/*
@@ -608,7 +609,7 @@ struct dev_pm_info {
608609
unsigned int should_wakeup:1;
609610
#endif
610611
#ifdef CONFIG_PM
611-
struct timer_list suspend_timer;
612+
struct hrtimer suspend_timer;
612613
unsigned long timer_expires;
613614
struct work_struct work;
614615
wait_queue_head_t wait_queue;
@@ -631,7 +632,7 @@ struct dev_pm_info {
631632
enum rpm_status runtime_status;
632633
int runtime_error;
633634
int autosuspend_delay;
634-
unsigned long last_busy;
635+
u64 last_busy;
635636
unsigned long active_jiffies;
636637
unsigned long suspended_jiffies;
637638
unsigned long accounting_timestamp;

include/linux/pm_runtime.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ extern void pm_runtime_no_callbacks(struct device *dev);
5151
extern void pm_runtime_irq_safe(struct device *dev);
5252
extern void __pm_runtime_use_autosuspend(struct device *dev, bool use);
5353
extern void pm_runtime_set_autosuspend_delay(struct device *dev, int delay);
54-
extern unsigned long pm_runtime_autosuspend_expiration(struct device *dev);
54+
extern u64 pm_runtime_autosuspend_expiration(struct device *dev);
5555
extern void pm_runtime_update_max_time_suspended(struct device *dev,
5656
s64 delta_ns);
5757
extern void pm_runtime_set_memalloc_noio(struct device *dev, bool enable);
@@ -105,7 +105,7 @@ static inline bool pm_runtime_callbacks_present(struct device *dev)
105105

106106
static inline void pm_runtime_mark_last_busy(struct device *dev)
107107
{
108-
WRITE_ONCE(dev->power.last_busy, jiffies);
108+
WRITE_ONCE(dev->power.last_busy, ktime_to_ns(ktime_get()));
109109
}
110110

111111
static inline bool pm_runtime_is_irq_safe(struct device *dev)
@@ -168,7 +168,7 @@ static inline void __pm_runtime_use_autosuspend(struct device *dev,
168168
bool use) {}
169169
static inline void pm_runtime_set_autosuspend_delay(struct device *dev,
170170
int delay) {}
171-
static inline unsigned long pm_runtime_autosuspend_expiration(
171+
static inline u64 pm_runtime_autosuspend_expiration(
172172
struct device *dev) { return 0; }
173173
static inline void pm_runtime_set_memalloc_noio(struct device *dev,
174174
bool enable){}

0 commit comments

Comments
 (0)