OSDN Git Service

ANDROID: arm64: Support for extracting EAS energy costs from DT
authorChris Redpath <chris.redpath@arm.com>
Sat, 16 Dec 2017 14:32:21 +0000 (14:32 +0000)
committerChris Redpath <chris.redpath@arm.com>
Tue, 19 Dec 2017 16:48:34 +0000 (16:48 +0000)
This patch implements support for extracting energy cost data from DT.
The data should conform to the DT bindings for energy cost data needed
by EAS (energy aware scheduling).

This patch supercedes the previous EAS patches:

 arm64, topology: Updates to use DT bindings for EAS costing data
 sched: Support for extracting EAS energy costs from DT
 arm64: use cpu scale value derived from energy model
 arm64: define hikey620 sys sd energy model
 arm64: introduce sys sd energy model infrastructure
 arm64: factor out energy model from topology shim layer
 arm64, topology: Define JUNO energy and provide it to the scheduler

There is no need to introduce code and replace it with the Android
expression of the same code in this stack.

Note that if sched-energy-costs is present at runtime, you can no longer
write cpu_capacity.

Some platforms may not provide capacity-dmips-mhz, but instead provide
an energy model in sched-energy-costs format. In this case, ensure that
the max capacity defined in the energy model is used as the raw capacity
value and that the arch_topology driver can still be loaded.
This ensures that the topology details are still available in sysfs and
also that the required flags are set.
Reported-by: Quentin Perret <quentin.perret@arm.com>
Further note that the arm support is still using a built-in energy
model, i.e. only arm64 platforms are able to provide energy model
data through the sched-energy-costs node in DT.

Change-Id: Id617b08eaf08cff3a099f35aeedbda72bb826ce6
Signed-off-by: Juri Lelli <juri.lelli@arm.com>
Signed-off-by: Dietmar Eggemann <dietmar.eggemann@arm.com>
Signed-off-by: Robin Randhawa <robin.randhawa@arm.com>
(modified to apply to 4.14 and updated to override dmips-mhz)
Signed-off-by: Chris Redpath <chris.redpath@arm.com>
arch/arm64/kernel/topology.c
drivers/base/arch_topology.c
include/linux/sched_energy.h [new file with mode: 0644]
kernel/sched/Makefile
kernel/sched/energy.c [new file with mode: 0644]
kernel/sched/topology.c

index de70e21..35dbdfa 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/of.h>
 #include <linux/sched.h>
 #include <linux/sched/topology.h>
+#include <linux/sched_energy.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 
@@ -187,6 +188,8 @@ static int __init parse_dt_topology(void)
        if (!map)
                goto out;
 
+       init_sched_energy_costs();
+
        ret = parse_cluster(map, 0);
        if (ret != 0)
                goto out_map;
@@ -302,14 +305,64 @@ static int cpu_flags(void)
        return topology_cpu_flags();
 }
 
+static inline
+const struct sched_group_energy * const cpu_core_energy(int cpu)
+{
+       struct sched_group_energy *sge = sge_array[cpu][SD_LEVEL0];
+       unsigned long capacity;
+       int max_cap_idx;
+
+       if (!sge) {
+               pr_warn("Invalid sched_group_energy for CPU%d\n", cpu);
+               return NULL;
+       }
+
+       max_cap_idx = sge->nr_cap_states - 1;
+       capacity = sge->cap_states[max_cap_idx].cap;
+
+       printk_deferred("cpu=%d set cpu scale %lu from energy model\n",
+                       cpu, capacity);
+
+       topology_set_cpu_scale(cpu, capacity);
+
+       return sge;
+}
+
+static inline
+const struct sched_group_energy * const cpu_cluster_energy(int cpu)
+{
+       struct sched_group_energy *sge = sge_array[cpu][SD_LEVEL1];
+
+       if (!sge) {
+               pr_warn("Invalid sched_group_energy for Cluster%d\n", cpu);
+               return NULL;
+       }
+
+       return sge;
+}
+
+static inline
+const struct sched_group_energy * const cpu_system_energy(int cpu)
+{
+       struct sched_group_energy *sge = sge_array[cpu][SD_LEVEL2];
+
+       if (!sge) {
+               pr_warn("Invalid sched_group_energy for System%d\n", cpu);
+               return NULL;
+       }
+
+       return sge;
+}
+
 static struct sched_domain_topology_level arm64_topology[] = {
 #ifdef CONFIG_SCHED_SMT
        { cpu_smt_mask, smt_flags, SD_INIT_NAME(SMT) },
 #endif
 #ifdef CONFIG_SCHED_MC
-       { cpu_coregroup_mask, core_flags, SD_INIT_NAME(MC) },
+       { cpu_coregroup_mask, core_flags, cpu_core_energy, SD_INIT_NAME(MC) },
 #endif
-       { cpu_cpu_mask, cpu_flags, SD_INIT_NAME(DIE) },
+       { cpu_cpu_mask, cpu_flags, cpu_cluster_energy, SD_INIT_NAME(DIE) },
+       { cpu_cpu_mask, NULL, cpu_system_energy, SD_INIT_NAME(SYS) },
        { NULL, }
 };
 
index d780075..8fe035a 100644 (file)
@@ -22,6 +22,7 @@
 #include <linux/string.h>
 #include <linux/sched/topology.h>
 #include <linux/cpuset.h>
+#include <linux/sched_energy.h>
 
 DEFINE_PER_CPU(unsigned long, freq_scale) = SCHED_CAPACITY_SCALE;
 
@@ -72,6 +73,10 @@ static ssize_t cpu_capacity_store(struct device *dev,
        if (!count)
                return 0;
 
+       /* don't allow changes if sched-group-energy is installed */
+       if(sched_energy_installed(this_cpu))
+               return -EINVAL;
+
        ret = kstrtoul(buf, 0, &new_capacity);
        if (ret)
                return ret;
@@ -352,14 +357,19 @@ void topology_normalize_cpu_scale(void)
 bool __init topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu)
 {
        static bool cap_parsing_failed;
-       int ret;
+       int ret = 0;
        u32 cpu_capacity;
 
        if (cap_parsing_failed)
                return false;
 
-       ret = of_property_read_u32(cpu_node, "capacity-dmips-mhz",
+       /* override capacity-dmips-mhz if we have sched-energy-costs */
+       if (of_find_property(cpu_node, "sched-energy-costs", NULL))
+               cpu_capacity = topology_get_cpu_scale(NULL, cpu);
+       else
+               ret = of_property_read_u32(cpu_node, "capacity-dmips-mhz",
                                   &cpu_capacity);
+
        if (!ret) {
                if (!raw_capacity) {
                        raw_capacity = kcalloc(num_possible_cpus(),
diff --git a/include/linux/sched_energy.h b/include/linux/sched_energy.h
new file mode 100644 (file)
index 0000000..83d7178
--- /dev/null
@@ -0,0 +1,41 @@
+#ifndef _LINUX_SCHED_ENERGY_H
+#define _LINUX_SCHED_ENERGY_H
+
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+/*
+ * There doesn't seem to be an NR_CPUS style max number of sched domain
+ * levels so here's an arbitrary constant one for the moment.
+ *
+ * The levels alluded to here correspond to entries in struct
+ * sched_domain_topology_level that are meant to be populated by arch
+ * specific code (topology.c).
+ */
+#define NR_SD_LEVELS 8
+
+#define SD_LEVEL0   0
+#define SD_LEVEL1   1
+#define SD_LEVEL2   2
+#define SD_LEVEL3   3
+#define SD_LEVEL4   4
+#define SD_LEVEL5   5
+#define SD_LEVEL6   6
+#define SD_LEVEL7   7
+
+/*
+ * Convenience macro for iterating through said sd levels.
+ */
+#define for_each_possible_sd_level(level)                  \
+       for (level = 0; level < NR_SD_LEVELS; level++)
+
+extern struct sched_group_energy *sge_array[NR_CPUS][NR_SD_LEVELS];
+
+#ifdef CONFIG_GENERIC_ARCH_TOPOLOGY
+void init_sched_energy_costs(void);
+int sched_energy_installed(int cpu);
+#else
+void init_sched_energy_costs(void) {}
+#endif
+
+#endif
index a9ee16b..e0bceb3 100644 (file)
@@ -20,6 +20,7 @@ obj-y += core.o loadavg.o clock.o cputime.o
 obj-y += idle_task.o fair.o rt.o deadline.o
 obj-y += wait.o wait_bit.o swait.o completion.o idle.o
 obj-$(CONFIG_SMP) += cpupri.o cpudeadline.o topology.o stop_task.o
+obj-$(CONFIG_GENERIC_ARCH_TOPOLOGY) += energy.o
 obj-$(CONFIG_SCHED_AUTOGROUP) += autogroup.o
 obj-$(CONFIG_SCHEDSTATS) += stats.o
 obj-$(CONFIG_SCHED_DEBUG) += debug.o
diff --git a/kernel/sched/energy.c b/kernel/sched/energy.c
new file mode 100644 (file)
index 0000000..e82248a
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Obtain energy cost data from DT and populate relevant scheduler data
+ * structures.
+ *
+ * Copyright (C) 2015 ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#define pr_fmt(fmt) "sched-energy: " fmt
+
+#define DEBUG
+
+#include <linux/gfp.h>
+#include <linux/of.h>
+#include <linux/printk.h>
+#include <linux/sched.h>
+#include <linux/sched/topology.h>
+#include <linux/sched_energy.h>
+#include <linux/stddef.h>
+#include <linux/arch_topology.h>
+
+struct sched_group_energy *sge_array[NR_CPUS][NR_SD_LEVELS];
+
+static void free_resources(void)
+{
+       int cpu, sd_level;
+       struct sched_group_energy *sge;
+
+       for_each_possible_cpu(cpu) {
+               for_each_possible_sd_level(sd_level) {
+                       sge = sge_array[cpu][sd_level];
+                       if (sge) {
+                               kfree(sge->cap_states);
+                               kfree(sge->idle_states);
+                               kfree(sge);
+                       }
+               }
+       }
+}
+
+static inline unsigned long cpu_max_capacity(int cpu)
+{
+       if (!sge_array[cpu][0]->cap_states)
+               return 1024;
+       if (!sge_array[cpu][0]->nr_cap_states)
+               return 1024;
+
+       return sge_array[cpu][0]->cap_states[sge_array[cpu][0]->nr_cap_states-1].cap;
+}
+
+int sched_energy_installed(int cpu)
+{
+       return (sge_array[cpu][0]->cap_states != NULL);
+}
+
+void init_sched_energy_costs(void)
+{
+       struct device_node *cn, *cp;
+       struct capacity_state *cap_states;
+       struct idle_state *idle_states;
+       struct sched_group_energy *sge;
+       const struct property *prop;
+       int sd_level, i, nstates, cpu;
+       const __be32 *val;
+
+       for_each_possible_cpu(cpu) {
+               cn = of_get_cpu_node(cpu, NULL);
+               if (!cn) {
+                       pr_warn("CPU device node missing for CPU %d\n", cpu);
+                       return;
+               }
+
+               if (!of_find_property(cn, "sched-energy-costs", NULL)) {
+                       pr_warn("CPU device node has no sched-energy-costs\n");
+                       return;
+               }
+
+               for_each_possible_sd_level(sd_level) {
+                       cp = of_parse_phandle(cn, "sched-energy-costs", sd_level);
+                       if (!cp)
+                               break;
+
+                       prop = of_find_property(cp, "busy-cost-data", NULL);
+                       if (!prop || !prop->value) {
+                               pr_warn("No busy-cost data, skipping sched_energy init\n");
+                               goto out;
+                       }
+
+                       sge = kcalloc(1, sizeof(struct sched_group_energy),
+                                     GFP_NOWAIT);
+
+                       nstates = (prop->length / sizeof(u32)) / 2;
+                       cap_states = kcalloc(nstates,
+                                            sizeof(struct capacity_state),
+                                            GFP_NOWAIT);
+
+                       for (i = 0, val = prop->value; i < nstates; i++) {
+                               cap_states[i].cap = be32_to_cpup(val++);
+                               cap_states[i].power = be32_to_cpup(val++);
+                       }
+
+                       sge->nr_cap_states = nstates;
+                       sge->cap_states = cap_states;
+
+                       prop = of_find_property(cp, "idle-cost-data", NULL);
+                       if (!prop || !prop->value) {
+                               pr_warn("No idle-cost data, skipping sched_energy init\n");
+                               goto out;
+                       }
+
+                       nstates = (prop->length / sizeof(u32));
+                       idle_states = kcalloc(nstates,
+                                             sizeof(struct idle_state),
+                                             GFP_NOWAIT);
+
+                       for (i = 0, val = prop->value; i < nstates; i++)
+                               idle_states[i].power = be32_to_cpup(val++);
+
+                       sge->nr_idle_states = nstates;
+                       sge->idle_states = idle_states;
+
+                       sge_array[cpu][sd_level] = sge;
+
+                       /* populate cpu scale so that flags get set correctly */
+                       if (sd_level == 0)
+                               topology_set_cpu_scale(cpu, cpu_max_capacity(cpu));
+               }
+       }
+
+       pr_info("Sched-energy-costs installed from DT\n");
+       return;
+
+out:
+       free_resources();
+}
index e2055e5..8b072be 100644 (file)
@@ -964,6 +964,41 @@ next:
        update_group_capacity(sd, cpu);
 }
 
+#define cap_state_power(s,i) (s->cap_states[i].power)
+#define cap_state_cap(s,i) (s->cap_states[i].cap)
+#define idle_state_power(s,i) (s->idle_states[i].power)
+
+static inline int sched_group_energy_equal(const struct sched_group_energy *a,
+               const struct sched_group_energy *b)
+{
+       int i;
+
+       /* check pointers first */
+       if (a == b)
+               return true;
+
+       /* check contents are equivalent */
+       if (a->nr_cap_states != b->nr_cap_states)
+               return false;
+       if (a->nr_idle_states != b->nr_idle_states)
+               return false;
+       for (i=0;i<a->nr_cap_states;i++){
+               if (cap_state_power(a,i) !=
+                       cap_state_power(b,i))
+                       return false;
+               if (cap_state_cap(a,i) !=
+                       cap_state_cap(b,i))
+                       return false;
+       }
+       for (i=0;i<a->nr_idle_states;i++){
+               if (idle_state_power(a,i) !=
+                       idle_state_power(b,i))
+                       return false;
+       }
+
+       return true;
+}
+
 #define energy_eff(e, n) \
     ((e->cap_states[n].cap << SCHED_CAPACITY_SHIFT)/e->cap_states[n].power)
 
@@ -1009,7 +1044,7 @@ static void init_sched_groups_energy(int cpu, struct sched_domain *sd,
                cpumask_xor(&mask, sched_group_span(sg), get_cpu_mask(cpu));
 
                for_each_cpu(i, &mask)
-                       BUG_ON(sge != fn(i));
+                       BUG_ON(!sched_group_energy_equal(sge,fn(i)));
        }
 
        /* Check that energy efficiency (capacity/power) is monotonically