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 192907541265a82
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 71577732119884
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>
This commit is contained in:
parent
7566ec393f
commit
8234f6734c
|
@ -8,6 +8,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <linux/sched/mm.h>
|
#include <linux/sched/mm.h>
|
||||||
|
#include <linux/ktime.h>
|
||||||
|
#include <linux/hrtimer.h>
|
||||||
#include <linux/export.h>
|
#include <linux/export.h>
|
||||||
#include <linux/pm_runtime.h>
|
#include <linux/pm_runtime.h>
|
||||||
#include <linux/pm_wakeirq.h>
|
#include <linux/pm_wakeirq.h>
|
||||||
|
@ -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)
|
static void pm_runtime_deactivate_timer(struct device *dev)
|
||||||
{
|
{
|
||||||
if (dev->power.timer_expires > 0) {
|
if (dev->power.timer_expires > 0) {
|
||||||
del_timer(&dev->power.suspend_timer);
|
hrtimer_cancel(&dev->power.suspend_timer);
|
||||||
dev->power.timer_expires = 0;
|
dev->power.timer_expires = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.
|
* 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.
|
* 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;
|
int autosuspend_delay;
|
||||||
long elapsed;
|
u64 last_busy, expires = 0;
|
||||||
unsigned long last_busy;
|
u64 now = ktime_to_ns(ktime_get());
|
||||||
unsigned long expires = 0;
|
|
||||||
|
|
||||||
if (!dev->power.use_autosuspend)
|
if (!dev->power.use_autosuspend)
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -139,19 +140,9 @@ unsigned long pm_runtime_autosuspend_expiration(struct device *dev)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
last_busy = READ_ONCE(dev->power.last_busy);
|
last_busy = READ_ONCE(dev->power.last_busy);
|
||||||
elapsed = jiffies - last_busy;
|
|
||||||
if (elapsed < 0)
|
|
||||||
goto out; /* jiffies has wrapped around. */
|
|
||||||
|
|
||||||
/*
|
expires = last_busy + autosuspend_delay * NSEC_PER_MSEC;
|
||||||
* If the autosuspend_delay is >= 1 second, align the timer by rounding
|
if (expires <= now)
|
||||||
* 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. */
|
expires = 0; /* Already expired. */
|
||||||
|
|
||||||
out:
|
out:
|
||||||
|
@ -515,7 +506,7 @@ static int rpm_suspend(struct device *dev, int rpmflags)
|
||||||
/* If the autosuspend_delay time hasn't expired yet, reschedule. */
|
/* If the autosuspend_delay time hasn't expired yet, reschedule. */
|
||||||
if ((rpmflags & RPM_AUTO)
|
if ((rpmflags & RPM_AUTO)
|
||||||
&& dev->power.runtime_status != RPM_SUSPENDING) {
|
&& dev->power.runtime_status != RPM_SUSPENDING) {
|
||||||
unsigned long expires = pm_runtime_autosuspend_expiration(dev);
|
u64 expires = pm_runtime_autosuspend_expiration(dev);
|
||||||
|
|
||||||
if (expires != 0) {
|
if (expires != 0) {
|
||||||
/* Pending requests need to be canceled. */
|
/* Pending requests need to be canceled. */
|
||||||
|
@ -528,10 +519,20 @@ static int rpm_suspend(struct device *dev, int rpmflags)
|
||||||
* expire; pm_suspend_timer_fn() will take care of the
|
* expire; pm_suspend_timer_fn() will take care of the
|
||||||
* rest.
|
* rest.
|
||||||
*/
|
*/
|
||||||
if (!(dev->power.timer_expires && time_before_eq(
|
if (!(dev->power.timer_expires &&
|
||||||
dev->power.timer_expires, 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;
|
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;
|
dev->power.timer_autosuspends = 1;
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -895,23 +896,25 @@ static void pm_runtime_work(struct work_struct *work)
|
||||||
*
|
*
|
||||||
* Check if the time is right and queue a suspend request.
|
* 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 flags;
|
||||||
unsigned long expires;
|
u64 expires;
|
||||||
|
|
||||||
spin_lock_irqsave(&dev->power.lock, flags);
|
spin_lock_irqsave(&dev->power.lock, flags);
|
||||||
|
|
||||||
expires = dev->power.timer_expires;
|
expires = dev->power.timer_expires;
|
||||||
/* If 'expire' is after 'jiffies' we've been called too early. */
|
/* 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;
|
dev->power.timer_expires = 0;
|
||||||
rpm_suspend(dev, dev->power.timer_autosuspends ?
|
rpm_suspend(dev, dev->power.timer_autosuspends ?
|
||||||
(RPM_ASYNC | RPM_AUTO) : RPM_ASYNC);
|
(RPM_ASYNC | RPM_AUTO) : RPM_ASYNC);
|
||||||
}
|
}
|
||||||
|
|
||||||
spin_unlock_irqrestore(&dev->power.lock, flags);
|
spin_unlock_irqrestore(&dev->power.lock, flags);
|
||||||
|
|
||||||
|
return HRTIMER_NORESTART;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -922,6 +925,7 @@ static void pm_suspend_timer_fn(struct timer_list *t)
|
||||||
int pm_schedule_suspend(struct device *dev, unsigned int delay)
|
int pm_schedule_suspend(struct device *dev, unsigned int delay)
|
||||||
{
|
{
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
ktime_t expires;
|
||||||
int retval;
|
int retval;
|
||||||
|
|
||||||
spin_lock_irqsave(&dev->power.lock, flags);
|
spin_lock_irqsave(&dev->power.lock, flags);
|
||||||
|
@ -938,10 +942,10 @@ int pm_schedule_suspend(struct device *dev, unsigned int delay)
|
||||||
/* Other scheduled or pending requests need to be canceled. */
|
/* Other scheduled or pending requests need to be canceled. */
|
||||||
pm_runtime_cancel_pending(dev);
|
pm_runtime_cancel_pending(dev);
|
||||||
|
|
||||||
dev->power.timer_expires = jiffies + msecs_to_jiffies(delay);
|
expires = ktime_add(ktime_get(), ms_to_ktime(delay));
|
||||||
dev->power.timer_expires += !dev->power.timer_expires;
|
dev->power.timer_expires = ktime_to_ns(expires);
|
||||||
dev->power.timer_autosuspends = 0;
|
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:
|
out:
|
||||||
spin_unlock_irqrestore(&dev->power.lock, flags);
|
spin_unlock_irqrestore(&dev->power.lock, flags);
|
||||||
|
@ -1491,7 +1495,8 @@ void pm_runtime_init(struct device *dev)
|
||||||
INIT_WORK(&dev->power.work, pm_runtime_work);
|
INIT_WORK(&dev->power.work, pm_runtime_work);
|
||||||
|
|
||||||
dev->power.timer_expires = 0;
|
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);
|
init_waitqueue_head(&dev->power.wait_queue);
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
#include <linux/spinlock.h>
|
#include <linux/spinlock.h>
|
||||||
#include <linux/wait.h>
|
#include <linux/wait.h>
|
||||||
#include <linux/timer.h>
|
#include <linux/timer.h>
|
||||||
|
#include <linux/hrtimer.h>
|
||||||
#include <linux/completion.h>
|
#include <linux/completion.h>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -608,7 +609,7 @@ struct dev_pm_info {
|
||||||
unsigned int should_wakeup:1;
|
unsigned int should_wakeup:1;
|
||||||
#endif
|
#endif
|
||||||
#ifdef CONFIG_PM
|
#ifdef CONFIG_PM
|
||||||
struct timer_list suspend_timer;
|
struct hrtimer suspend_timer;
|
||||||
unsigned long timer_expires;
|
unsigned long timer_expires;
|
||||||
struct work_struct work;
|
struct work_struct work;
|
||||||
wait_queue_head_t wait_queue;
|
wait_queue_head_t wait_queue;
|
||||||
|
@ -631,7 +632,7 @@ struct dev_pm_info {
|
||||||
enum rpm_status runtime_status;
|
enum rpm_status runtime_status;
|
||||||
int runtime_error;
|
int runtime_error;
|
||||||
int autosuspend_delay;
|
int autosuspend_delay;
|
||||||
unsigned long last_busy;
|
u64 last_busy;
|
||||||
unsigned long active_jiffies;
|
unsigned long active_jiffies;
|
||||||
unsigned long suspended_jiffies;
|
unsigned long suspended_jiffies;
|
||||||
unsigned long accounting_timestamp;
|
unsigned long accounting_timestamp;
|
||||||
|
|
|
@ -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_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 u64 pm_runtime_autosuspend_expiration(struct device *dev);
|
||||||
extern void pm_runtime_update_max_time_suspended(struct device *dev,
|
extern void pm_runtime_update_max_time_suspended(struct device *dev,
|
||||||
s64 delta_ns);
|
s64 delta_ns);
|
||||||
extern void pm_runtime_set_memalloc_noio(struct device *dev, bool enable);
|
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)
|
||||||
|
|
||||||
static inline void pm_runtime_mark_last_busy(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)
|
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,
|
||||||
bool use) {}
|
bool use) {}
|
||||||
static inline void pm_runtime_set_autosuspend_delay(struct device *dev,
|
static inline void pm_runtime_set_autosuspend_delay(struct device *dev,
|
||||||
int delay) {}
|
int delay) {}
|
||||||
static inline unsigned long pm_runtime_autosuspend_expiration(
|
static inline u64 pm_runtime_autosuspend_expiration(
|
||||||
struct device *dev) { return 0; }
|
struct device *dev) { return 0; }
|
||||||
static inline void pm_runtime_set_memalloc_noio(struct device *dev,
|
static inline void pm_runtime_set_memalloc_noio(struct device *dev,
|
||||||
bool enable){}
|
bool enable){}
|
||||||
|
|
Loading…
Reference in New Issue