Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 04b61628 authored by Chris Redpath's avatar Chris Redpath
Browse files

ANDROID: arm64: Support for extracting EAS energy costs from DT



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: default avatarQuentin 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: default avatarJuri Lelli <juri.lelli@arm.com>
Signed-off-by: default avatarDietmar Eggemann <dietmar.eggemann@arm.com>
Signed-off-by: default avatarRobin Randhawa <robin.randhawa@arm.com>
(modified to apply to 4.14 and updated to override dmips-mhz)
Signed-off-by: default avatarChris Redpath <chris.redpath@arm.com>
parent 1973fddf
Loading
Loading
Loading
Loading
+55 −2
Original line number Diff line number Diff line
@@ -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, }
};

+12 −2
Original line number Diff line number Diff line
@@ -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;

	/* 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(),
+41 −0
Original line number Diff line number Diff line
#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
+1 −0
Original line number Diff line number Diff line
@@ -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

kernel/sched/energy.c

0 → 100644
+145 −0
Original line number Diff line number Diff line
/*
 * 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();
}
Loading