diff options
-rw-r--r-- | drivers/cpuidle/Kconfig | 11 | ||||
-rw-r--r-- | drivers/cpuidle/governors/Makefile | 1 | ||||
-rw-r--r-- | drivers/cpuidle/governors/mobile.c | 151 |
3 files changed, 162 insertions, 1 deletions
diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index 8caccbbd7353..547cfc204b37 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -4,7 +4,7 @@ config CPU_IDLE bool "CPU idle PM support" default y if ACPI || PPC_PSERIES select CPU_IDLE_GOV_LADDER if (!NO_HZ && !NO_HZ_IDLE) - select CPU_IDLE_GOV_MENU if (NO_HZ || NO_HZ_IDLE) && !CPU_IDLE_GOV_TEO + select CPU_IDLE_GOV_MENU if (NO_HZ || NO_HZ_IDLE) && !CPU_IDLE_GOV_TEO && !CPU_IDLE_GOV_MOBILE help CPU idle is a generic framework for supporting software-controlled idle processor power management. It includes modular cross-platform @@ -32,6 +32,15 @@ config CPU_IDLE_GOV_TEO Some workloads benefit from using it and it generally should be safe to use. Say Y here if you are not happy with the alternatives. +config CPU_IDLE_GOV_MOBILE + bool "Mobile governor" + select IRQ_TIMINGS + help + The mobile governor is based on irq timings measurements and + pattern research combined with the next timer. This governor + suits very well on embedded systems where the interrupts are + grouped on a single core and the power is the priority. + config DT_IDLE_STATES bool diff --git a/drivers/cpuidle/governors/Makefile b/drivers/cpuidle/governors/Makefile index 4d8aff5248a8..1e1feebf27d8 100644 --- a/drivers/cpuidle/governors/Makefile +++ b/drivers/cpuidle/governors/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_CPU_IDLE_GOV_LADDER) += ladder.o obj-$(CONFIG_CPU_IDLE_GOV_MENU) += menu.o obj-$(CONFIG_CPU_IDLE_GOV_TEO) += teo.o +obj-$(CONFIG_CPU_IDLE_GOV_MOBILE) += mobile.o diff --git a/drivers/cpuidle/governors/mobile.c b/drivers/cpuidle/governors/mobile.c new file mode 100644 index 000000000000..85cb2155d5ca --- /dev/null +++ b/drivers/cpuidle/governors/mobile.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019, Linaro Ltd + * Author: Daniel Lezcano <daniel.lezcano@linaro.org> + */ +#include <linux/cpuidle.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/tick.h> +#include <linux/interrupt.h> +#include <linux/sched/clock.h> + +struct mobile_device { + u64 idle_ema_avg; + u64 idle_total; + unsigned long last_jiffies; +}; + +#define EMA_ALPHA_VAL 64 +#define EMA_ALPHA_SHIFT 7 +#define MAX_RESCHED_INTERVAL_MS 100 + +static DEFINE_PER_CPU(struct mobile_device, mobile_devices); + +static int mobile_ema_new(s64 value, s64 ema_old) +{ + if (likely(ema_old)) + return ema_old + (((value - ema_old) * EMA_ALPHA_VAL) >> + EMA_ALPHA_SHIFT); + return value; +} + +static void mobile_reflect(struct cpuidle_device *dev, int index) +{ + struct mobile_device *mobile_dev = this_cpu_ptr(&mobile_devices); + struct cpuidle_driver *drv = cpuidle_get_cpu_driver(dev); + struct cpuidle_state *s = &drv->states[index]; + int residency; + + /* + * The idle task was not rescheduled since + * MAX_RESCHED_INTERVAL_MS, let's consider the duration is + * long enough to clear our stats. + */ + if (time_after(jiffies, mobile_dev->last_jiffies + + msecs_to_jiffies(MAX_RESCHED_INTERVAL_MS))) + mobile_dev->idle_ema_avg = 0; + + /* + * Sum all the residencies in order to compute the total + * duration of the idle task. + */ + residency = dev->last_residency - s->exit_latency; + if (residency > 0) + mobile_dev->idle_total += residency; + + /* + * We exited the idle state with the need_resched() flag, the + * idle task will be rescheduled, so store the duration the + * idle task was scheduled in an exponential moving average and + * reset the total of the idle duration. + */ + if (need_resched()) { + mobile_dev->idle_ema_avg = mobile_ema_new(mobile_dev->idle_total, + mobile_dev->idle_ema_avg); + mobile_dev->idle_total = 0; + mobile_dev->last_jiffies = jiffies; + } +} + +static int mobile_select(struct cpuidle_driver *drv, struct cpuidle_device *dev, + bool *stop_tick) +{ + struct mobile_device *mobile_dev = this_cpu_ptr(&mobile_devices); + int latency_req = cpuidle_governor_latency_req(dev->cpu); + int i, index = 0; + ktime_t delta_next; + u64 now, irq_length, timer_length; + u64 idle_duration_us; + + /* + * Get the present time as reference for the next steps + */ + now = local_clock(); + + /* + * Get the next interrupt event giving the 'now' as a + * reference, if the next event appears to have already + * expired then we get the 'now' returned which ends up with a + * zero duration. + */ + irq_length = irq_timings_next_event(now) - now; + + /* + * Get the timer duration before expiration. + */ + timer_length = ktime_to_ns(tick_nohz_get_sleep_length(&delta_next)); + + /* + * Get the smallest duration between the timer and the irq next event. + */ + idle_duration_us = min_t(u64, irq_length, timer_length) / NSEC_PER_USEC; + + /* + * Get the idle task duration average if the information is + * available. + */ + if (mobile_dev->idle_ema_avg) + idle_duration_us = min_t(u64, idle_duration_us, + mobile_dev->idle_ema_avg); + + for (i = 0; i < drv->state_count; i++) { + struct cpuidle_state *s = &drv->states[i]; + struct cpuidle_state_usage *su = &dev->states_usage[i]; + + if (s->disabled || su->disable) + continue; + + if (s->exit_latency > latency_req) + break; + + if (s->exit_latency >= idle_duration_us) + break; + + if (s->target_residency > (idle_duration_us - s->exit_latency)) + break; + + index = i; + } + + if (!index) + *stop_tick = false; + + return index; +} + +static struct cpuidle_governor mobile_governor = { + .name = "mobile", + .rating = 20, + .select = mobile_select, + .reflect = mobile_reflect, +}; + +static int __init init_governor(void) +{ + irq_timings_enable(); + return cpuidle_register_governor(&mobile_governor); +} + +postcore_initcall(init_governor); |