From 1f412bad5d7bfc35156c227b63a6e112df4ef6ef Mon Sep 17 00:00:00 2001 From: Park Bumgyu Date: Thu, 5 Apr 2018 20:01:19 +0900 Subject: [PATCH] sched: ems: build simplified energy table The simplified energy table only considers the compute capacity and power of the cpu. When the frequency domain is registered, an energy table is created dynamically. Change-Id: Ifbd466dbd6ba0e496f98f17e3dd3b2850ab3ceea Signed-off-by: Park Bumgyu --- drivers/cpufreq/exynos-acme.c | 4 + include/linux/ems.h | 8 ++ kernel/sched/ems/core.c | 176 +++++++++++++++++++++++++++++++++- 3 files changed, 186 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/exynos-acme.c b/drivers/cpufreq/exynos-acme.c index a5af5b602c81..011950c1942f 100644 --- a/drivers/cpufreq/exynos-acme.c +++ b/drivers/cpufreq/exynos-acme.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -1228,6 +1229,9 @@ static __init int init_table(struct exynos_cpufreq_domain *domain) domain->freq_table[index].driver_data = index; domain->freq_table[index].frequency = CPUFREQ_TABLE_END; + init_sched_energy_table(&domain->cpus, domain->table_size, table, volt_table, + domain->max_freq, domain->min_freq); + kfree(volt_table); free_table: diff --git a/include/linux/ems.h b/include/linux/ems.h index d35dba1f38f6..e09f91efa331 100644 --- a/include/linux/ems.h +++ b/include/linux/ems.h @@ -61,6 +61,10 @@ extern void update_lbt_overutil(int cpu, unsigned long capacity); extern void gb_qos_update_request(struct gb_qos_request *req, u32 new_value); extern void request_kernel_prefer_perf(int grp_idx, int enable); + +extern void init_sched_energy_table(struct cpumask *cpus, int table_size, + unsigned long *f_table, unsigned int *v_table, + int max_f, int min_f); #else static inline int exynos_estimate_idle_state(int cpu_idx, struct cpumask *mask, int state, int cpus) { return 0; } @@ -92,4 +96,8 @@ static inline void update_lbt_overutil(int cpu, unsigned long capacity) { } static inline void gb_qos_update_request(struct gb_qos_request *req, u32 new_value) { } static inline void request_kernel_prefer_perf(int grp_idx, int enable) { } + +static inline void init_sched_energy_table(struct cpumask *cpus, int table_size, + unsigned long *f_table, unsigned int *v_table, + int max_f, int min_f) { } #endif /* CONFIG_SCHED_EMS */ diff --git a/kernel/sched/ems/core.c b/kernel/sched/ems/core.c index f75c9b2a96c0..f09b875a82cd 100644 --- a/kernel/sched/ems/core.c +++ b/kernel/sched/ems/core.c @@ -32,15 +32,36 @@ static int cpu_util_wake(int cpu, struct task_struct *p) return (util >= capacity) ? capacity : util; } +/* + * The compute capacity, power consumption at this compute capacity and + * frequency of state. The cap and power are used to find the energy + * efficiency cpu, and the frequency is used to create the capacity table. + */ +struct energy_state { + unsigned long cap; + unsigned long power; + unsigned long frequency; +}; + +/* + * Each cpu can have its own mips, coefficient and energy table. Generally, + * cpus in the same frequency domain have the same mips, coefficient and + * energy table. + */ struct energy_table { unsigned int mips; unsigned int coefficient;; - struct capacity_state *states; + + struct energy_state *states; unsigned int nr_states; }; - DEFINE_PER_CPU(struct energy_table, energy_table); +/* + * When choosing cpu considering energy efficiency, decide best cpu and + * backup cpu according to policy, and then choose cpu which consumes the + * least energy including prev cpu. + */ struct eco_env { struct task_struct *p; @@ -182,6 +203,157 @@ static int __init init_sched_energy_data(void) } pure_initcall(init_sched_energy_data); +static void +fill_power_table(struct energy_table *table, int table_size, + unsigned long *f_table, unsigned int *v_table, + int max_f, int min_f) +{ + int i, index = 0; + int c = table->coefficient, v; + unsigned long f, power; + + /* energy table and frequency table are inverted */ + for (i = table_size - 1; i >= 0; i--) { + if (f_table[i] > max_f || f_table[i] < min_f) + continue; + + f = f_table[i] / 1000; /* KHz -> MHz */ + v = v_table[i] / 1000; /* uV -> mV */ + + /* + * power = coefficent * frequency * voltage^2 + */ + power = c * f * v * v; + + /* + * Generally, frequency is more than treble figures in MHz and + * voltage is also more then treble figures in mV, so the + * calculated power is larger than 10^9. For convenience of + * calculation, divide the value by 10^9. + */ + do_div(power, 1000000000); + table->states[index].power = power; + + /* save frequency to energy table */ + table->states[index].frequency = f_table[i]; + index++; + } +} + +static void +fill_cap_table(struct energy_table *table, int max_mips, unsigned long max_mips_freq) +{ + int i, m = table->mips; + unsigned long f; + + for (i = 0; i < table->nr_states; i++) { + f = table->states[i].frequency; + + /* + * capacity = freq/max_freq * mips/max_mips * 1024 + */ + table->states[i].cap = f * m * 1024 / max_mips_freq / max_mips; + } +} + +static void show_energy_table(struct energy_table *table, int cpu) +{ + int i; + + pr_info("[Energy Table : cpu%d]\n", cpu); + for (i = 0; i < table->nr_states; i++) { + pr_info("[%d] .cap=%lu .power=%lu\n", i, + table->states[i].cap, table->states[i].power); + } +} + +/* + * Whenever frequency domain is registered, and energy table corresponding to + * the domain is created. Because cpu in the same frequency domain has the same + * energy table. Capacity is calculated based on the max frequency of the fastest + * cpu, so once the frequency domain of the faster cpu is regsitered, capacity + * is recomputed. + */ +void init_sched_energy_table(struct cpumask *cpus, int table_size, + unsigned long *f_table, unsigned int *v_table, + int max_f, int min_f) +{ + struct energy_table *table; + int cpu, i, mips, valid_table_size = 0; + int max_mips = 0; + unsigned long max_mips_freq = 0; + + mips = per_cpu(energy_table, cpumask_any(cpus)).mips; + for_each_cpu(cpu, cpus) { + /* + * All cpus in a frequency domain must have the smae capacity. + * Otherwise, it does not create an energy table because it + * is likely to be a human error. + */ + if (mips != per_cpu(energy_table, cpu).mips) { + pr_warn("cpu%d has different cpacity!!\n", cpu); + return; + } + } + + /* get size of valid frequency table to allocate energy table */ + for (i = 0; i < table_size; i++) { + if (f_table[i] > max_f || f_table[i] < min_f) + continue; + + valid_table_size++; + } + + /* there is no valid row in the table, energy table is not created */ + if (!valid_table_size) + return; + + /* allocate memory for energy table and fill power table */ + for_each_cpu(cpu, cpus) { + table = &per_cpu(energy_table, cpu); + table->states = kcalloc(valid_table_size, + sizeof(struct energy_state), GFP_KERNEL); + if (unlikely(!table->states)) + return; + + table->nr_states = valid_table_size; + fill_power_table(table, table_size, f_table, v_table, max_f, min_f); + } + + /* + * Find fastest cpu among the cpu to which the energy table is allocated. + * The mips and max frequency of fastest cpu are needed to calculate + * capacity. + */ + for_each_possible_cpu(cpu) { + table = &per_cpu(energy_table, cpu); + if (!table->states) + continue; + + if (table->mips > max_mips) { + int last_state = table->nr_states - 1; + + max_mips = table->mips; + max_mips_freq = table->states[last_state].frequency; + } + } + + /* + * Calculate and fill capacity table. + * Recalculate the capacity whenever frequency domain changes because + * the fastest cpu may have changed and the capacity needs to be + * recalculated. + */ + for_each_possible_cpu(cpu) { + table = &per_cpu(energy_table, cpu); + if (!table->states) + continue; + + fill_cap_table(table, max_mips, max_mips_freq); + show_energy_table(table, cpu); + } +} + static unsigned int calculate_energy(struct task_struct *p, int target_cpu) { unsigned long util[NR_CPUS] = {0, }; -- 2.20.1