From 9001b8f1a599b974ca8f2f925c210a31c0c70d60 Mon Sep 17 00:00:00 2001 From: Puja Gupta Date: Fri, 11 May 2018 14:05:37 -0700 Subject: [PATCH] ANDROID: sched: Add support for frequency/power energy model This patch provides support to pass frequency/power values from energy model instead of capacity/power. It is required to support multiple speed bin devices i.e. devices which have different max frequency based on hardware. User can provide all supported frequencies across all versions of the device in device tree energy model. Max frequency of the cpu is read at runtime from OPP driver and the capacity values are populated using that. Energy model proc file will display cap_states as a tuple of (capacity, frequency, power), in which frequency will be 0 when not using frequency energy model. Change-Id: I25aec1f91aa8f09bcf67f0575792693808847618 Signed-off-by: Puja Gupta --- .../bindings/scheduler/sched-energy-costs.txt | 5 + include/linux/sched/topology.h | 2 + kernel/sched/energy.c | 176 +++++++++++++++++- kernel/sched/fair.c | 2 +- 4 files changed, 178 insertions(+), 7 deletions(-) diff --git a/Documentation/devicetree/bindings/scheduler/sched-energy-costs.txt b/Documentation/devicetree/bindings/scheduler/sched-energy-costs.txt index 2ceb202462cb..447cc32b3f21 100644 --- a/Documentation/devicetree/bindings/scheduler/sched-energy-costs.txt +++ b/Documentation/devicetree/bindings/scheduler/sched-energy-costs.txt @@ -237,6 +237,11 @@ Property added to the cpu node: Any other configuration is invalid. +- freq-energy-model + Description: Optional. Must be declared if the energy model + represents frequency/power values. If absent, energy model is + by default considered as capacity/power. + =========================================================== 5 - Example dts =========================================================== diff --git a/include/linux/sched/topology.h b/include/linux/sched/topology.h index e0161c3da0da..5fcb7dd36048 100644 --- a/include/linux/sched/topology.h +++ b/include/linux/sched/topology.h @@ -69,6 +69,7 @@ extern int sched_domain_level_max; struct capacity_state { unsigned long cap; /* compute capacity */ + unsigned long frequency;/* frequency */ unsigned long power; /* power consumption at this compute capacity */ }; @@ -194,6 +195,7 @@ typedef const struct cpumask *(*sched_domain_mask_f)(int cpu); typedef int (*sched_domain_flags_f)(void); typedef const struct sched_group_energy * const(*sched_domain_energy_f)(int cpu); +extern bool energy_aware(void); #define SDTL_OVERLAP 0x01 diff --git a/kernel/sched/energy.c b/kernel/sched/energy.c index be470c695293..8342de041e11 100644 --- a/kernel/sched/energy.c +++ b/kernel/sched/energy.c @@ -18,8 +18,6 @@ */ #define pr_fmt(fmt) "sched-energy: " fmt -#define DEBUG - #include #include #include @@ -28,6 +26,11 @@ #include #include #include +#include +#include +#include + +#include "sched.h" struct sched_group_energy *sge_array[NR_CPUS][NR_SD_LEVELS]; @@ -47,6 +50,8 @@ static void free_resources(void) } } } +static bool sge_ready; +static bool freq_energy_model; void check_max_cap_vs_cpu_scale(int cpu, struct sched_group_energy *sge) { @@ -83,6 +88,9 @@ void init_sched_energy_costs(void) pr_warn("CPU device node has no sched-energy-costs\n"); return; } + /* Check if the energy model contains frequency/power values */ + if (of_find_property(cn, "freq-energy-model", NULL)) + freq_energy_model = true; for_each_possible_sd_level(sd_level) { cp = of_parse_phandle(cn, "sched-energy-costs", sd_level); @@ -104,7 +112,18 @@ void init_sched_energy_costs(void) GFP_NOWAIT); for (i = 0, val = prop->value; i < nstates; i++) { - cap_states[i].cap = be32_to_cpup(val++); + if (freq_energy_model) { + /* + * Capacity values will be calculated later using + * frequency reported by OPP driver and cpu_uarch_scale + * values. + */ + cap_states[i].frequency = be32_to_cpup(val++); + cap_states[i].cap = 0; + } else { + cap_states[i].frequency = 0; + cap_states[i].cap = be32_to_cpup(val++); + } cap_states[i].power = be32_to_cpup(val++); } @@ -130,13 +149,158 @@ void init_sched_energy_costs(void) sge_array[cpu][sd_level] = sge; } - - check_max_cap_vs_cpu_scale(cpu, sge_array[cpu][SD_LEVEL0]); + if (!freq_energy_model) + check_max_cap_vs_cpu_scale(cpu, sge_array[cpu][SD_LEVEL0]); } - + sge_ready = true; pr_info("Sched-energy-costs installed from DT\n"); return; out: free_resources(); } + +static int sched_energy_probe(struct platform_device *pdev) +{ + int cpu; + unsigned long *max_frequencies = NULL; + int ret; + + if (!sge_ready) + return -EPROBE_DEFER; + + if (!energy_aware() || !freq_energy_model) + return 0; + + max_frequencies = kmalloc_array(nr_cpu_ids, sizeof(unsigned long), + GFP_KERNEL); + if (!max_frequencies) { + ret = -ENOMEM; + goto exit; + } + + /* + * Find system max possible frequency and max frequencies for each + * CPUs. + */ + for_each_possible_cpu(cpu) { + struct device *cpu_dev; + struct dev_pm_opp *opp; + + cpu_dev = get_cpu_device(cpu); + if (IS_ERR_OR_NULL(cpu_dev)) { + if (!cpu_dev) + ret = -EINVAL; + else + ret = PTR_ERR(cpu_dev); + goto exit; + } + + max_frequencies[cpu] = ULONG_MAX; + + opp = dev_pm_opp_find_freq_floor(cpu_dev, + &max_frequencies[cpu]); + if (IS_ERR_OR_NULL(opp)) { + if (!opp || PTR_ERR(opp) == -ENODEV) + ret = -EPROBE_DEFER; + else + ret = PTR_ERR(opp); + goto exit; + } + + /* Convert HZ to KHZ */ + max_frequencies[cpu] /= 1000; + } + + /* update capacity in energy model */ + for_each_possible_cpu(cpu) { + unsigned long cpu_max_cap; + struct sched_group_energy *sge_l0, *sge; + cpu_max_cap = topology_get_cpu_scale(NULL, cpu); + + /* + * All the cap_states have same frequency table so use + * SD_LEVEL0's. + */ + sge_l0 = sge_array[cpu][SD_LEVEL0]; + if (sge_l0 && sge_l0->nr_cap_states > 0) { + int i; + int ncapstates = sge_l0->nr_cap_states; + + for (i = 0; i < ncapstates; i++) { + int sd_level; + unsigned long freq, cap; + + /* + * Energy model can contain more frequency + * steps than actual for multiple speedbin + * support. Ceil the max capacity with actual + * one. + */ + freq = min(sge_l0->cap_states[i].frequency, + max_frequencies[cpu]); + cap = DIV_ROUND_UP(cpu_max_cap * freq, + max_frequencies[cpu]); + + for_each_possible_sd_level(sd_level) { + sge = sge_array[cpu][sd_level]; + if (!sge) + break; + sge->cap_states[i].cap = cap; + } + dev_dbg(&pdev->dev, + "cpu=%d freq=%ld cap=%ld power_d0=%ld\n", + cpu, freq, sge_l0->cap_states[i].cap, + sge_l0->cap_states[i].power); + } + + dev_info(&pdev->dev, + "cpu=%d [freq=%ld cap=%ld power_d0=%ld] -> [freq=%ld cap=%ld power_d0=%ld]\n", + cpu, + sge_l0->cap_states[0].frequency, + sge_l0->cap_states[0].cap, + sge_l0->cap_states[0].power, + sge_l0->cap_states[ncapstates - 1].frequency, + sge_l0->cap_states[ncapstates - 1].cap, + sge_l0->cap_states[ncapstates - 1].power + ); + } + } + + kfree(max_frequencies); + dev_info(&pdev->dev, "Sched-energy-costs capacity updated\n"); + return 0; + +exit: + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, "error=%d\n", ret); + kfree(max_frequencies); + return ret; +} + +static struct platform_driver energy_driver = { + .driver = { + .name = "sched-energy", + }, + .probe = sched_energy_probe, +}; + +static struct platform_device energy_device = { + .name = "sched-energy", +}; + +static int __init sched_energy_init(void) +{ + int ret; + + ret = platform_device_register(&energy_device); + if (ret) + pr_err("%s device_register failed:%d\n", __func__, ret); + ret = platform_driver_register(&energy_driver); + if (ret) { + pr_err("%s driver_register failed:%d\n", __func__, ret); + platform_device_unregister(&energy_device); + } + return ret; +} +subsys_initcall(sched_energy_init); diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c index 4816cd549cde..d10b39326d30 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -5686,7 +5686,7 @@ unsigned long capacity_curr_of(int cpu) return cap_scale(max_cap, scale_freq); } -static inline bool energy_aware(void) +inline bool energy_aware(void) { return sched_feat(ENERGY_AWARE); } -- 2.20.1