mirror of
https://github.com/torvalds/linux.git
synced 2024-11-15 08:31:55 +00:00
27871f7a8a
Several subsystems in the kernel (task scheduler and/or thermal at the time of writing) can benefit from knowing about the energy consumed by CPUs. Yet, this information can come from different sources (DT or firmware for example), in different formats, hence making it hard to exploit without a standard API. As an attempt to address this, introduce a centralized Energy Model (EM) management framework which aggregates the power values provided by drivers into a table for each performance domain in the system. The power cost tables are made available to interested clients (e.g. task scheduler or thermal) via platform-agnostic APIs. The overall design is represented by the diagram below (focused on Arm-related drivers as an example, but applicable to any architecture): +---------------+ +-----------------+ +-------------+ | Thermal (IPA) | | Scheduler (EAS) | | Other | +---------------+ +-----------------+ +-------------+ | | em_pd_energy() | | | em_cpu_get() | +-----------+ | +--------+ | | | v v v +---------------------+ | | | Energy Model | | | | Framework | | | +---------------------+ ^ ^ ^ | | | em_register_perf_domain() +----------+ | +---------+ | | | +---------------+ +---------------+ +--------------+ | cpufreq-dt | | arm_scmi | | Other | +---------------+ +---------------+ +--------------+ ^ ^ ^ | | | +--------------+ +---------------+ +--------------+ | Device Tree | | Firmware | | ? | +--------------+ +---------------+ +--------------+ Drivers (typically, but not limited to, CPUFreq drivers) can register data in the EM framework using the em_register_perf_domain() API. The calling driver must provide a callback function with a standardized signature that will be used by the EM framework to build the power cost tables of the performance domain. This design should offer a lot of flexibility to calling drivers which are free of reading information from any location and to use any technique to compute power costs. Moreover, the capacity states registered by drivers in the EM framework are not required to match real performance states of the target. This is particularly important on targets where the performance states are not known by the OS. The power cost coefficients managed by the EM framework are specified in milli-watts. Although the two potential users of those coefficients (IPA and EAS) only need relative correctness, IPA specifically needs to compare the power of CPUs with the power of other components (GPUs, for example), which are still expressed in absolute terms in their respective subsystems. Hence, specifying the power of CPUs in milli-watts should help transitioning IPA to using the EM framework without introducing new problems by keeping units comparable across sub-systems. On the longer term, the EM of other devices than CPUs could also be managed by the EM framework, which would enable to remove the absolute unit. However, this is not absolutely required as a first step, so this extension of the EM framework is left for later. On the client side, the EM framework offers APIs to access the power cost tables of a CPU (em_cpu_get()), and to estimate the energy consumed by the CPUs of a performance domain (em_pd_energy()). Clients such as the task scheduler can then use these APIs to access the shared data structures holding the Energy Model of CPUs. Signed-off-by: Quentin Perret <quentin.perret@arm.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Mike Galbraith <efault@gmx.de> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Rafael J. Wysocki <rjw@rjwysocki.net> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: adharmap@codeaurora.org Cc: chris.redpath@arm.com Cc: currojerez@riseup.net Cc: dietmar.eggemann@arm.com Cc: edubezval@gmail.com Cc: gregkh@linuxfoundation.org Cc: javi.merino@kernel.org Cc: joel@joelfernandes.org Cc: juri.lelli@redhat.com Cc: morten.rasmussen@arm.com Cc: patrick.bellasi@arm.com Cc: pkondeti@codeaurora.org Cc: skannan@codeaurora.org Cc: smuckle@google.com Cc: srinivas.pandruvada@linux.intel.com Cc: thara.gopinath@linaro.org Cc: tkjos@google.com Cc: valentin.schneider@arm.com Cc: vincent.guittot@linaro.org Cc: viresh.kumar@linaro.org Link: https://lkml.kernel.org/r/20181203095628.11858-4-quentin.perret@arm.com Signed-off-by: Ingo Molnar <mingo@kernel.org>
202 lines
5.2 KiB
C
202 lines
5.2 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* Energy Model of CPUs
|
|
*
|
|
* Copyright (c) 2018, Arm ltd.
|
|
* Written by: Quentin Perret, Arm ltd.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "energy_model: " fmt
|
|
|
|
#include <linux/cpu.h>
|
|
#include <linux/cpumask.h>
|
|
#include <linux/energy_model.h>
|
|
#include <linux/sched/topology.h>
|
|
#include <linux/slab.h>
|
|
|
|
/* Mapping of each CPU to the performance domain to which it belongs. */
|
|
static DEFINE_PER_CPU(struct em_perf_domain *, em_data);
|
|
|
|
/*
|
|
* Mutex serializing the registrations of performance domains and letting
|
|
* callbacks defined by drivers sleep.
|
|
*/
|
|
static DEFINE_MUTEX(em_pd_mutex);
|
|
|
|
static struct em_perf_domain *em_create_pd(cpumask_t *span, int nr_states,
|
|
struct em_data_callback *cb)
|
|
{
|
|
unsigned long opp_eff, prev_opp_eff = ULONG_MAX;
|
|
unsigned long power, freq, prev_freq = 0;
|
|
int i, ret, cpu = cpumask_first(span);
|
|
struct em_cap_state *table;
|
|
struct em_perf_domain *pd;
|
|
u64 fmax;
|
|
|
|
if (!cb->active_power)
|
|
return NULL;
|
|
|
|
pd = kzalloc(sizeof(*pd) + cpumask_size(), GFP_KERNEL);
|
|
if (!pd)
|
|
return NULL;
|
|
|
|
table = kcalloc(nr_states, sizeof(*table), GFP_KERNEL);
|
|
if (!table)
|
|
goto free_pd;
|
|
|
|
/* Build the list of capacity states for this performance domain */
|
|
for (i = 0, freq = 0; i < nr_states; i++, freq++) {
|
|
/*
|
|
* active_power() is a driver callback which ceils 'freq' to
|
|
* lowest capacity state of 'cpu' above 'freq' and updates
|
|
* 'power' and 'freq' accordingly.
|
|
*/
|
|
ret = cb->active_power(&power, &freq, cpu);
|
|
if (ret) {
|
|
pr_err("pd%d: invalid cap. state: %d\n", cpu, ret);
|
|
goto free_cs_table;
|
|
}
|
|
|
|
/*
|
|
* We expect the driver callback to increase the frequency for
|
|
* higher capacity states.
|
|
*/
|
|
if (freq <= prev_freq) {
|
|
pr_err("pd%d: non-increasing freq: %lu\n", cpu, freq);
|
|
goto free_cs_table;
|
|
}
|
|
|
|
/*
|
|
* The power returned by active_state() is expected to be
|
|
* positive, in milli-watts and to fit into 16 bits.
|
|
*/
|
|
if (!power || power > EM_CPU_MAX_POWER) {
|
|
pr_err("pd%d: invalid power: %lu\n", cpu, power);
|
|
goto free_cs_table;
|
|
}
|
|
|
|
table[i].power = power;
|
|
table[i].frequency = prev_freq = freq;
|
|
|
|
/*
|
|
* The hertz/watts efficiency ratio should decrease as the
|
|
* frequency grows on sane platforms. But this isn't always
|
|
* true in practice so warn the user if a higher OPP is more
|
|
* power efficient than a lower one.
|
|
*/
|
|
opp_eff = freq / power;
|
|
if (opp_eff >= prev_opp_eff)
|
|
pr_warn("pd%d: hertz/watts ratio non-monotonically decreasing: em_cap_state %d >= em_cap_state%d\n",
|
|
cpu, i, i - 1);
|
|
prev_opp_eff = opp_eff;
|
|
}
|
|
|
|
/* Compute the cost of each capacity_state. */
|
|
fmax = (u64) table[nr_states - 1].frequency;
|
|
for (i = 0; i < nr_states; i++) {
|
|
table[i].cost = div64_u64(fmax * table[i].power,
|
|
table[i].frequency);
|
|
}
|
|
|
|
pd->table = table;
|
|
pd->nr_cap_states = nr_states;
|
|
cpumask_copy(to_cpumask(pd->cpus), span);
|
|
|
|
return pd;
|
|
|
|
free_cs_table:
|
|
kfree(table);
|
|
free_pd:
|
|
kfree(pd);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* em_cpu_get() - Return the performance domain for a CPU
|
|
* @cpu : CPU to find the performance domain for
|
|
*
|
|
* Return: the performance domain to which 'cpu' belongs, or NULL if it doesn't
|
|
* exist.
|
|
*/
|
|
struct em_perf_domain *em_cpu_get(int cpu)
|
|
{
|
|
return READ_ONCE(per_cpu(em_data, cpu));
|
|
}
|
|
EXPORT_SYMBOL_GPL(em_cpu_get);
|
|
|
|
/**
|
|
* em_register_perf_domain() - Register the Energy Model of a performance domain
|
|
* @span : Mask of CPUs in the performance domain
|
|
* @nr_states : Number of capacity states to register
|
|
* @cb : Callback functions providing the data of the Energy Model
|
|
*
|
|
* Create Energy Model tables for a performance domain using the callbacks
|
|
* defined in cb.
|
|
*
|
|
* If multiple clients register the same performance domain, all but the first
|
|
* registration will be ignored.
|
|
*
|
|
* Return 0 on success
|
|
*/
|
|
int em_register_perf_domain(cpumask_t *span, unsigned int nr_states,
|
|
struct em_data_callback *cb)
|
|
{
|
|
unsigned long cap, prev_cap = 0;
|
|
struct em_perf_domain *pd;
|
|
int cpu, ret = 0;
|
|
|
|
if (!span || !nr_states || !cb)
|
|
return -EINVAL;
|
|
|
|
/*
|
|
* Use a mutex to serialize the registration of performance domains and
|
|
* let the driver-defined callback functions sleep.
|
|
*/
|
|
mutex_lock(&em_pd_mutex);
|
|
|
|
for_each_cpu(cpu, span) {
|
|
/* Make sure we don't register again an existing domain. */
|
|
if (READ_ONCE(per_cpu(em_data, cpu))) {
|
|
ret = -EEXIST;
|
|
goto unlock;
|
|
}
|
|
|
|
/*
|
|
* All CPUs of a domain must have the same micro-architecture
|
|
* since they all share the same table.
|
|
*/
|
|
cap = arch_scale_cpu_capacity(NULL, cpu);
|
|
if (prev_cap && prev_cap != cap) {
|
|
pr_err("CPUs of %*pbl must have the same capacity\n",
|
|
cpumask_pr_args(span));
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
prev_cap = cap;
|
|
}
|
|
|
|
/* Create the performance domain and add it to the Energy Model. */
|
|
pd = em_create_pd(span, nr_states, cb);
|
|
if (!pd) {
|
|
ret = -EINVAL;
|
|
goto unlock;
|
|
}
|
|
|
|
for_each_cpu(cpu, span) {
|
|
/*
|
|
* The per-cpu array can be read concurrently from em_cpu_get().
|
|
* The barrier enforces the ordering needed to make sure readers
|
|
* can only access well formed em_perf_domain structs.
|
|
*/
|
|
smp_store_release(per_cpu_ptr(&em_data, cpu), pd);
|
|
}
|
|
|
|
pr_debug("Created perf domain %*pbl\n", cpumask_pr_args(span));
|
|
unlock:
|
|
mutex_unlock(&em_pd_mutex);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(em_register_perf_domain);
|