mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-12-10 20:44:09 +08:00
3102bc0e6a
In the absence of ACPI or DT topology data, we fallback to haphazardly
decoding *something* out of MPIDR. Sadly, the contents of that register are
mostly unusable due to the implementation leniancy and things like Aff0
having to be capped to 15 (despite being encoded on 8 bits).
Consider a simple system with a single package of 32 cores, all under the
same LLC. We ought to be shoving them in the same core_sibling mask, but
MPIDR is going to look like:
| CPU | 0 | ... | 15 | 16 | ... | 31 |
|------+---+-----+----+----+-----+----+
| Aff0 | 0 | ... | 15 | 0 | ... | 15 |
| Aff1 | 0 | ... | 0 | 1 | ... | 1 |
| Aff2 | 0 | ... | 0 | 0 | ... | 0 |
Which will eventually yield
core_sibling(0-15) == 0-15
core_sibling(16-31) == 16-31
NUMA woes
=========
If we try to play games with this and set up NUMA boundaries within those
groups of 16 cores via e.g. QEMU:
# Node0: 0-9; Node1: 10-19
$ qemu-system-aarch64 <blah> \
-smp 20 -numa node,cpus=0-9,nodeid=0 -numa node,cpus=10-19,nodeid=1
The scheduler's MC domain (all CPUs with same LLC) is going to be built via
arch_topology.c::cpu_coregroup_mask()
In there we try to figure out a sensible mask out of the topology
information we have. In short, here we'll pick the smallest of NUMA or
core sibling mask.
node_mask(CPU9) == 0-9
core_sibling(CPU9) == 0-15
MC mask for CPU9 will thus be 0-9, not a problem.
node_mask(CPU10) == 10-19
core_sibling(CPU10) == 0-15
MC mask for CPU10 will thus be 10-19, not a problem.
node_mask(CPU16) == 10-19
core_sibling(CPU16) == 16-19
MC mask for CPU16 will thus be 16-19... Uh oh. CPUs 16-19 are in two
different unique MC spans, and the scheduler has no idea what to make of
that. That triggers the WARN_ON() added by commit
ccf74128d6
("sched/topology: Assert non-NUMA topology masks don't (partially) overlap")
Fixing MPIDR-derived topology
=============================
We could try to come up with some cleverer scheme to figure out which of
the available masks to pick, but really if one of those masks resulted from
MPIDR then it should be discarded because it's bound to be bogus.
I was hoping to give MPIDR a chance for SMT, to figure out which threads are
in the same core using Aff1-3 as core ID, but Sudeep and Robin pointed out
to me that there are systems out there where *all* cores have non-zero
values in their higher affinity fields (e.g. RK3288 has "5" in all of its
cores' MPIDR.Aff1), which would expose a bogus core ID to userspace.
Stop using MPIDR for topology information. When no other source of topology
information is available, mark each CPU as its own core and its NUMA node
as its LLC domain.
Signed-off-by: Valentin Schneider <valentin.schneider@arm.com>
Reviewed-by: Sudeep Holla <sudeep.holla@arm.com>
Link: https://lore.kernel.org/r/20200829130016.26106-1-valentin.schneider@arm.com
Signed-off-by: Will Deacon <will@kernel.org>
306 lines
8.1 KiB
C
306 lines
8.1 KiB
C
/*
|
|
* arch/arm64/kernel/topology.c
|
|
*
|
|
* Copyright (C) 2011,2013,2014 Linaro Limited.
|
|
*
|
|
* Based on the arm32 version written by Vincent Guittot in turn based on
|
|
* arch/sh/kernel/topology.c
|
|
*
|
|
* This file is subject to the terms and conditions of the GNU General Public
|
|
* License. See the file "COPYING" in the main directory of this archive
|
|
* for more details.
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/arch_topology.h>
|
|
#include <linux/cacheinfo.h>
|
|
#include <linux/cpufreq.h>
|
|
#include <linux/init.h>
|
|
#include <linux/percpu.h>
|
|
|
|
#include <asm/cpu.h>
|
|
#include <asm/cputype.h>
|
|
#include <asm/topology.h>
|
|
|
|
void store_cpu_topology(unsigned int cpuid)
|
|
{
|
|
struct cpu_topology *cpuid_topo = &cpu_topology[cpuid];
|
|
u64 mpidr;
|
|
|
|
if (cpuid_topo->package_id != -1)
|
|
goto topology_populated;
|
|
|
|
mpidr = read_cpuid_mpidr();
|
|
|
|
/* Uniprocessor systems can rely on default topology values */
|
|
if (mpidr & MPIDR_UP_BITMASK)
|
|
return;
|
|
|
|
/*
|
|
* This would be the place to create cpu topology based on MPIDR.
|
|
*
|
|
* However, it cannot be trusted to depict the actual topology; some
|
|
* pieces of the architecture enforce an artificial cap on Aff0 values
|
|
* (e.g. GICv3's ICC_SGI1R_EL1 limits it to 15), leading to an
|
|
* artificial cycling of Aff1, Aff2 and Aff3 values. IOW, these end up
|
|
* having absolutely no relationship to the actual underlying system
|
|
* topology, and cannot be reasonably used as core / package ID.
|
|
*
|
|
* If the MT bit is set, Aff0 *could* be used to define a thread ID, but
|
|
* we still wouldn't be able to obtain a sane core ID. This means we
|
|
* need to entirely ignore MPIDR for any topology deduction.
|
|
*/
|
|
cpuid_topo->thread_id = -1;
|
|
cpuid_topo->core_id = cpuid;
|
|
cpuid_topo->package_id = cpu_to_node(cpuid);
|
|
|
|
pr_debug("CPU%u: cluster %d core %d thread %d mpidr %#016llx\n",
|
|
cpuid, cpuid_topo->package_id, cpuid_topo->core_id,
|
|
cpuid_topo->thread_id, mpidr);
|
|
|
|
topology_populated:
|
|
update_siblings_masks(cpuid);
|
|
}
|
|
|
|
#ifdef CONFIG_ACPI
|
|
static bool __init acpi_cpu_is_threaded(int cpu)
|
|
{
|
|
int is_threaded = acpi_pptt_cpu_is_thread(cpu);
|
|
|
|
/*
|
|
* if the PPTT doesn't have thread information, assume a homogeneous
|
|
* machine and return the current CPU's thread state.
|
|
*/
|
|
if (is_threaded < 0)
|
|
is_threaded = read_cpuid_mpidr() & MPIDR_MT_BITMASK;
|
|
|
|
return !!is_threaded;
|
|
}
|
|
|
|
/*
|
|
* Propagate the topology information of the processor_topology_node tree to the
|
|
* cpu_topology array.
|
|
*/
|
|
int __init parse_acpi_topology(void)
|
|
{
|
|
int cpu, topology_id;
|
|
|
|
if (acpi_disabled)
|
|
return 0;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
int i, cache_id;
|
|
|
|
topology_id = find_acpi_cpu_topology(cpu, 0);
|
|
if (topology_id < 0)
|
|
return topology_id;
|
|
|
|
if (acpi_cpu_is_threaded(cpu)) {
|
|
cpu_topology[cpu].thread_id = topology_id;
|
|
topology_id = find_acpi_cpu_topology(cpu, 1);
|
|
cpu_topology[cpu].core_id = topology_id;
|
|
} else {
|
|
cpu_topology[cpu].thread_id = -1;
|
|
cpu_topology[cpu].core_id = topology_id;
|
|
}
|
|
topology_id = find_acpi_cpu_topology_package(cpu);
|
|
cpu_topology[cpu].package_id = topology_id;
|
|
|
|
i = acpi_find_last_cache_level(cpu);
|
|
|
|
if (i > 0) {
|
|
/*
|
|
* this is the only part of cpu_topology that has
|
|
* a direct relationship with the cache topology
|
|
*/
|
|
cache_id = find_acpi_cpu_cache_topology(cpu, i);
|
|
if (cache_id > 0)
|
|
cpu_topology[cpu].llc_id = cache_id;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_ARM64_AMU_EXTN
|
|
|
|
#undef pr_fmt
|
|
#define pr_fmt(fmt) "AMU: " fmt
|
|
|
|
static DEFINE_PER_CPU_READ_MOSTLY(unsigned long, arch_max_freq_scale);
|
|
static DEFINE_PER_CPU(u64, arch_const_cycles_prev);
|
|
static DEFINE_PER_CPU(u64, arch_core_cycles_prev);
|
|
static cpumask_var_t amu_fie_cpus;
|
|
|
|
/* Initialize counter reference per-cpu variables for the current CPU */
|
|
void init_cpu_freq_invariance_counters(void)
|
|
{
|
|
this_cpu_write(arch_core_cycles_prev,
|
|
read_sysreg_s(SYS_AMEVCNTR0_CORE_EL0));
|
|
this_cpu_write(arch_const_cycles_prev,
|
|
read_sysreg_s(SYS_AMEVCNTR0_CONST_EL0));
|
|
}
|
|
|
|
static int validate_cpu_freq_invariance_counters(int cpu)
|
|
{
|
|
u64 max_freq_hz, ratio;
|
|
|
|
if (!cpu_has_amu_feat(cpu)) {
|
|
pr_debug("CPU%d: counters are not supported.\n", cpu);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (unlikely(!per_cpu(arch_const_cycles_prev, cpu) ||
|
|
!per_cpu(arch_core_cycles_prev, cpu))) {
|
|
pr_debug("CPU%d: cycle counters are not enabled.\n", cpu);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Convert maximum frequency from KHz to Hz and validate */
|
|
max_freq_hz = cpufreq_get_hw_max_freq(cpu) * 1000;
|
|
if (unlikely(!max_freq_hz)) {
|
|
pr_debug("CPU%d: invalid maximum frequency.\n", cpu);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/*
|
|
* Pre-compute the fixed ratio between the frequency of the constant
|
|
* counter and the maximum frequency of the CPU.
|
|
*
|
|
* const_freq
|
|
* arch_max_freq_scale = ---------------- * SCHED_CAPACITY_SCALE²
|
|
* cpuinfo_max_freq
|
|
*
|
|
* We use a factor of 2 * SCHED_CAPACITY_SHIFT -> SCHED_CAPACITY_SCALE²
|
|
* in order to ensure a good resolution for arch_max_freq_scale for
|
|
* very low arch timer frequencies (down to the KHz range which should
|
|
* be unlikely).
|
|
*/
|
|
ratio = (u64)arch_timer_get_rate() << (2 * SCHED_CAPACITY_SHIFT);
|
|
ratio = div64_u64(ratio, max_freq_hz);
|
|
if (!ratio) {
|
|
WARN_ONCE(1, "System timer frequency too low.\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
per_cpu(arch_max_freq_scale, cpu) = (unsigned long)ratio;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static inline bool
|
|
enable_policy_freq_counters(int cpu, cpumask_var_t valid_cpus)
|
|
{
|
|
struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
|
|
|
|
if (!policy) {
|
|
pr_debug("CPU%d: No cpufreq policy found.\n", cpu);
|
|
return false;
|
|
}
|
|
|
|
if (cpumask_subset(policy->related_cpus, valid_cpus))
|
|
cpumask_or(amu_fie_cpus, policy->related_cpus,
|
|
amu_fie_cpus);
|
|
|
|
cpufreq_cpu_put(policy);
|
|
|
|
return true;
|
|
}
|
|
|
|
static DEFINE_STATIC_KEY_FALSE(amu_fie_key);
|
|
#define amu_freq_invariant() static_branch_unlikely(&amu_fie_key)
|
|
|
|
static int __init init_amu_fie(void)
|
|
{
|
|
cpumask_var_t valid_cpus;
|
|
bool have_policy = false;
|
|
int ret = 0;
|
|
int cpu;
|
|
|
|
if (!zalloc_cpumask_var(&valid_cpus, GFP_KERNEL))
|
|
return -ENOMEM;
|
|
|
|
if (!zalloc_cpumask_var(&amu_fie_cpus, GFP_KERNEL)) {
|
|
ret = -ENOMEM;
|
|
goto free_valid_mask;
|
|
}
|
|
|
|
for_each_present_cpu(cpu) {
|
|
if (validate_cpu_freq_invariance_counters(cpu))
|
|
continue;
|
|
cpumask_set_cpu(cpu, valid_cpus);
|
|
have_policy |= enable_policy_freq_counters(cpu, valid_cpus);
|
|
}
|
|
|
|
/*
|
|
* If we are not restricted by cpufreq policies, we only enable
|
|
* the use of the AMU feature for FIE if all CPUs support AMU.
|
|
* Otherwise, enable_policy_freq_counters has already enabled
|
|
* policy cpus.
|
|
*/
|
|
if (!have_policy && cpumask_equal(valid_cpus, cpu_present_mask))
|
|
cpumask_or(amu_fie_cpus, amu_fie_cpus, valid_cpus);
|
|
|
|
if (!cpumask_empty(amu_fie_cpus)) {
|
|
pr_info("CPUs[%*pbl]: counters will be used for FIE.",
|
|
cpumask_pr_args(amu_fie_cpus));
|
|
static_branch_enable(&amu_fie_key);
|
|
}
|
|
|
|
free_valid_mask:
|
|
free_cpumask_var(valid_cpus);
|
|
|
|
return ret;
|
|
}
|
|
late_initcall_sync(init_amu_fie);
|
|
|
|
bool arch_freq_counters_available(struct cpumask *cpus)
|
|
{
|
|
return amu_freq_invariant() &&
|
|
cpumask_subset(cpus, amu_fie_cpus);
|
|
}
|
|
|
|
void topology_scale_freq_tick(void)
|
|
{
|
|
u64 prev_core_cnt, prev_const_cnt;
|
|
u64 core_cnt, const_cnt, scale;
|
|
int cpu = smp_processor_id();
|
|
|
|
if (!amu_freq_invariant())
|
|
return;
|
|
|
|
if (!cpumask_test_cpu(cpu, amu_fie_cpus))
|
|
return;
|
|
|
|
const_cnt = read_sysreg_s(SYS_AMEVCNTR0_CONST_EL0);
|
|
core_cnt = read_sysreg_s(SYS_AMEVCNTR0_CORE_EL0);
|
|
prev_const_cnt = this_cpu_read(arch_const_cycles_prev);
|
|
prev_core_cnt = this_cpu_read(arch_core_cycles_prev);
|
|
|
|
if (unlikely(core_cnt <= prev_core_cnt ||
|
|
const_cnt <= prev_const_cnt))
|
|
goto store_and_exit;
|
|
|
|
/*
|
|
* /\core arch_max_freq_scale
|
|
* scale = ------- * --------------------
|
|
* /\const SCHED_CAPACITY_SCALE
|
|
*
|
|
* See validate_cpu_freq_invariance_counters() for details on
|
|
* arch_max_freq_scale and the use of SCHED_CAPACITY_SHIFT.
|
|
*/
|
|
scale = core_cnt - prev_core_cnt;
|
|
scale *= this_cpu_read(arch_max_freq_scale);
|
|
scale = div64_u64(scale >> SCHED_CAPACITY_SHIFT,
|
|
const_cnt - prev_const_cnt);
|
|
|
|
scale = min_t(unsigned long, scale, SCHED_CAPACITY_SCALE);
|
|
this_cpu_write(freq_scale, (unsigned long)scale);
|
|
|
|
store_and_exit:
|
|
this_cpu_write(arch_core_cycles_prev, core_cnt);
|
|
this_cpu_write(arch_const_cycles_prev, const_cnt);
|
|
}
|
|
#endif /* CONFIG_ARM64_AMU_EXTN */
|