ANDROID: sched: Add support for frequency/power energy model
authorPuja Gupta <pujag@codeaurora.org>
Fri, 11 May 2018 21:05:37 +0000 (14:05 -0700)
committerTodd Kjos <tkjos@google.com>
Thu, 16 Aug 2018 16:26:15 +0000 (16:26 +0000)
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 <pujag@codeaurora.org>
Documentation/devicetree/bindings/scheduler/sched-energy-costs.txt
include/linux/sched/topology.h
kernel/sched/energy.c
kernel/sched/fair.c

index 2ceb202462cbfe3a9bcedddad472f4338fe166c7..447cc32b3f214cfba2fe1dcff2b1a9f77083219a 100644 (file)
@@ -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
 ===========================================================
index e0161c3da0da6c002920aee12dbe33fa7feea76b..5fcb7dd360481ef81f1ccdcc9184a86722063e78 100644 (file)
@@ -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
 
index be470c695293062bdee1234dad1b35da7a650289..8342de041e114750975c1405fc6504194eef57cd 100644 (file)
@@ -18,8 +18,6 @@
  */
 #define pr_fmt(fmt) "sched-energy: " fmt
 
-#define DEBUG
-
 #include <linux/gfp.h>
 #include <linux/of.h>
 #include <linux/printk.h>
 #include <linux/sched/energy.h>
 #include <linux/stddef.h>
 #include <linux/arch_topology.h>
+#include <linux/cpu.h>
+#include <linux/pm_opp.h>
+#include <linux/platform_device.h>
+
+#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);
index 4816cd549cde6ba109c4ebc267bc8709ee050de5..d10b39326d3084a308332c76b75cb21a67763dff 100644 (file)
@@ -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);
 }