From 8234f6734c5d74ac794e5517437f51c57d65f865 Mon Sep 17 00:00:00 2001 From: Vincent Guittot Date: Fri, 14 Dec 2018 15:22:25 +0100 Subject: 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 Reviewed-by: Ulf Hansson Signed-off-by: Rafael J. Wysocki --- drivers/base/power/runtime.c | 63 ++++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 29 deletions(-) (limited to 'drivers/base/power') diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index beb85c31f3fa..70624695b6d5 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -8,6 +8,8 @@ */ #include +#include +#include #include #include #include @@ -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; } } @@ -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; @@ -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: @@ -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. */ @@ -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; @@ -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; } /** @@ -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); @@ -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); @@ -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); } -- cgit From d32dcc6c6970667e438e2b1fec92816d98dcb29f Mon Sep 17 00:00:00 2001 From: Yangtao Li Date: Sat, 15 Dec 2018 03:45:26 -0500 Subject: PM / Domains: remove define_genpd_open_function() and define_genpd_debugfs_fops() We already have the DEFINE_SHOW_ATTRIBUTE, There is no need to define such a macro, so remove define_genpd_open_function and define_genpd_debugfs_fops. Convert them to DEFINE_SHOW_ATTRIBUTE. Signed-off-by: Yangtao Li Acked-by: Ulf Hansson Signed-off-by: Rafael J. Wysocki --- drivers/base/power/domain.c | 71 +++++++++++++++------------------------------ 1 file changed, 24 insertions(+), 47 deletions(-) (limited to 'drivers/base/power') diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 7f38a92b444a..10a61d6147d0 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -2671,7 +2671,7 @@ exit: return 0; } -static int genpd_summary_show(struct seq_file *s, void *data) +static int summary_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd; int ret = 0; @@ -2694,7 +2694,7 @@ static int genpd_summary_show(struct seq_file *s, void *data) return ret; } -static int genpd_status_show(struct seq_file *s, void *data) +static int status_show(struct seq_file *s, void *data) { static const char * const status_lookup[] = { [GPD_STATE_ACTIVE] = "on", @@ -2721,7 +2721,7 @@ exit: return ret; } -static int genpd_sub_domains_show(struct seq_file *s, void *data) +static int sub_domains_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; struct gpd_link *link; @@ -2738,7 +2738,7 @@ static int genpd_sub_domains_show(struct seq_file *s, void *data) return ret; } -static int genpd_idle_states_show(struct seq_file *s, void *data) +static int idle_states_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; unsigned int i; @@ -2767,7 +2767,7 @@ static int genpd_idle_states_show(struct seq_file *s, void *data) return ret; } -static int genpd_active_time_show(struct seq_file *s, void *data) +static int active_time_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; ktime_t delta = 0; @@ -2787,7 +2787,7 @@ static int genpd_active_time_show(struct seq_file *s, void *data) return ret; } -static int genpd_total_idle_time_show(struct seq_file *s, void *data) +static int total_idle_time_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; ktime_t delta = 0, total = 0; @@ -2815,7 +2815,7 @@ static int genpd_total_idle_time_show(struct seq_file *s, void *data) } -static int genpd_devices_show(struct seq_file *s, void *data) +static int devices_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; struct pm_domain_data *pm_data; @@ -2841,7 +2841,7 @@ static int genpd_devices_show(struct seq_file *s, void *data) return ret; } -static int genpd_perf_state_show(struct seq_file *s, void *data) +static int perf_state_show(struct seq_file *s, void *data) { struct generic_pm_domain *genpd = s->private; @@ -2854,37 +2854,14 @@ static int genpd_perf_state_show(struct seq_file *s, void *data) return 0; } -#define define_genpd_open_function(name) \ -static int genpd_##name##_open(struct inode *inode, struct file *file) \ -{ \ - return single_open(file, genpd_##name##_show, inode->i_private); \ -} - -define_genpd_open_function(summary); -define_genpd_open_function(status); -define_genpd_open_function(sub_domains); -define_genpd_open_function(idle_states); -define_genpd_open_function(active_time); -define_genpd_open_function(total_idle_time); -define_genpd_open_function(devices); -define_genpd_open_function(perf_state); - -#define define_genpd_debugfs_fops(name) \ -static const struct file_operations genpd_##name##_fops = { \ - .open = genpd_##name##_open, \ - .read = seq_read, \ - .llseek = seq_lseek, \ - .release = single_release, \ -} - -define_genpd_debugfs_fops(summary); -define_genpd_debugfs_fops(status); -define_genpd_debugfs_fops(sub_domains); -define_genpd_debugfs_fops(idle_states); -define_genpd_debugfs_fops(active_time); -define_genpd_debugfs_fops(total_idle_time); -define_genpd_debugfs_fops(devices); -define_genpd_debugfs_fops(perf_state); +DEFINE_SHOW_ATTRIBUTE(summary); +DEFINE_SHOW_ATTRIBUTE(status); +DEFINE_SHOW_ATTRIBUTE(sub_domains); +DEFINE_SHOW_ATTRIBUTE(idle_states); +DEFINE_SHOW_ATTRIBUTE(active_time); +DEFINE_SHOW_ATTRIBUTE(total_idle_time); +DEFINE_SHOW_ATTRIBUTE(devices); +DEFINE_SHOW_ATTRIBUTE(perf_state); static int __init genpd_debug_init(void) { @@ -2897,7 +2874,7 @@ static int __init genpd_debug_init(void) return -ENOMEM; d = debugfs_create_file("pm_genpd_summary", S_IRUGO, - genpd_debugfs_dir, NULL, &genpd_summary_fops); + genpd_debugfs_dir, NULL, &summary_fops); if (!d) return -ENOMEM; @@ -2907,20 +2884,20 @@ static int __init genpd_debug_init(void) return -ENOMEM; debugfs_create_file("current_state", 0444, - d, genpd, &genpd_status_fops); + d, genpd, &status_fops); debugfs_create_file("sub_domains", 0444, - d, genpd, &genpd_sub_domains_fops); + d, genpd, &sub_domains_fops); debugfs_create_file("idle_states", 0444, - d, genpd, &genpd_idle_states_fops); + d, genpd, &idle_states_fops); debugfs_create_file("active_time", 0444, - d, genpd, &genpd_active_time_fops); + d, genpd, &active_time_fops); debugfs_create_file("total_idle_time", 0444, - d, genpd, &genpd_total_idle_time_fops); + d, genpd, &total_idle_time_fops); debugfs_create_file("devices", 0444, - d, genpd, &genpd_devices_fops); + d, genpd, &devices_fops); if (genpd->set_performance_state) debugfs_create_file("perf_state", 0444, - d, genpd, &genpd_perf_state_fops); + d, genpd, &perf_state_fops); } return 0; -- cgit