Skip to content

Commit cd0ed03

Browse files
ionela-voinescuctmarinas
authored andcommitted
arm64: use activity monitors for frequency invariance
The Frequency Invariance Engine (FIE) is providing a frequency scaling correction factor that helps achieve more accurate load-tracking. So far, for arm and arm64 platforms, this scale factor has been obtained based on the ratio between the current frequency and the maximum supported frequency recorded by the cpufreq policy. The setting of this scale factor is triggered from cpufreq drivers by calling arch_set_freq_scale. The current frequency used in computation is the frequency requested by a governor, but it may not be the frequency that was implemented by the platform. This correction factor can also be obtained using a core counter and a constant counter to get information on the performance (frequency based only) obtained in a period of time. This will more accurately reflect the actual current frequency of the CPU, compared with the alternative implementation that reflects the request of a performance level from the OS. Therefore, implement arch_scale_freq_tick to use activity monitors, if present, for the computation of the frequency scale factor. The use of AMU counters depends on: - CONFIG_ARM64_AMU_EXTN - depents on the AMU extension being present - CONFIG_CPU_FREQ - the current frequency obtained using counter information is divided by the maximum frequency obtained from the cpufreq policy. While it is possible to have a combination of CPUs in the system with and without support for activity monitors, the use of counters for frequency invariance is only enabled for a CPU if all related CPUs (CPUs in the same frequency domain) support and have enabled the core and constant activity monitor counters. In this way, there is a clear separation between the policies for which arch_set_freq_scale (cpufreq based FIE) is used, and the policies for which arch_scale_freq_tick (counter based FIE) is used to set the frequency scale factor. For this purpose, a late_initcall_sync is registered to trigger validation work for policies that will enable or disable the use of AMU counters for frequency invariance. If CONFIG_CPU_FREQ is not defined, the use of counters is enabled on all CPUs only if all possible CPUs correctly support the necessary counters. Signed-off-by: Ionela Voinescu <[email protected]> Reviewed-by: Lukasz Luba <[email protected]> Acked-by: Sudeep Holla <[email protected]> Cc: Sudeep Holla <[email protected]> Cc: Will Deacon <[email protected]> Cc: Catalin Marinas <[email protected]> Signed-off-by: Catalin Marinas <[email protected]>
1 parent bbce8ea commit cd0ed03

File tree

5 files changed

+207
-0
lines changed

5 files changed

+207
-0
lines changed

arch/arm64/include/asm/topology.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,15 @@ int pcibus_to_node(struct pci_bus *bus);
1616

1717
#include <linux/arch_topology.h>
1818

19+
#ifdef CONFIG_ARM64_AMU_EXTN
20+
/*
21+
* Replace task scheduler's default counter-based
22+
* frequency-invariance scale factor setting.
23+
*/
24+
void topology_scale_freq_tick(void);
25+
#define arch_scale_freq_tick topology_scale_freq_tick
26+
#endif /* CONFIG_ARM64_AMU_EXTN */
27+
1928
/* Replace task scheduler's default frequency-invariant accounting */
2029
#define arch_scale_freq_capacity topology_get_freq_scale
2130

arch/arm64/kernel/cpufeature.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,12 +1241,16 @@ bool cpu_has_amu_feat(int cpu)
12411241
return cpumask_test_cpu(cpu, &amu_cpus);
12421242
}
12431243

1244+
/* Initialize the use of AMU counters for frequency invariance */
1245+
extern void init_cpu_freq_invariance_counters(void);
1246+
12441247
static void cpu_amu_enable(struct arm64_cpu_capabilities const *cap)
12451248
{
12461249
if (has_cpuid_feature(cap, SCOPE_LOCAL_CPU)) {
12471250
pr_info("detected CPU%d: Activity Monitors Unit (AMU)\n",
12481251
smp_processor_id());
12491252
cpumask_set_cpu(smp_processor_id(), &amu_cpus);
1253+
init_cpu_freq_invariance_counters();
12501254
}
12511255
}
12521256

arch/arm64/kernel/topology.c

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <linux/acpi.h>
1515
#include <linux/arch_topology.h>
1616
#include <linux/cacheinfo.h>
17+
#include <linux/cpufreq.h>
1718
#include <linux/init.h>
1819
#include <linux/percpu.h>
1920

@@ -120,4 +121,183 @@ int __init parse_acpi_topology(void)
120121
}
121122
#endif
122123

124+
#ifdef CONFIG_ARM64_AMU_EXTN
123125

126+
#undef pr_fmt
127+
#define pr_fmt(fmt) "AMU: " fmt
128+
129+
static DEFINE_PER_CPU_READ_MOSTLY(unsigned long, arch_max_freq_scale);
130+
static DEFINE_PER_CPU(u64, arch_const_cycles_prev);
131+
static DEFINE_PER_CPU(u64, arch_core_cycles_prev);
132+
static cpumask_var_t amu_fie_cpus;
133+
134+
/* Initialize counter reference per-cpu variables for the current CPU */
135+
void init_cpu_freq_invariance_counters(void)
136+
{
137+
this_cpu_write(arch_core_cycles_prev,
138+
read_sysreg_s(SYS_AMEVCNTR0_CORE_EL0));
139+
this_cpu_write(arch_const_cycles_prev,
140+
read_sysreg_s(SYS_AMEVCNTR0_CONST_EL0));
141+
}
142+
143+
static int validate_cpu_freq_invariance_counters(int cpu)
144+
{
145+
u64 max_freq_hz, ratio;
146+
147+
if (!cpu_has_amu_feat(cpu)) {
148+
pr_debug("CPU%d: counters are not supported.\n", cpu);
149+
return -EINVAL;
150+
}
151+
152+
if (unlikely(!per_cpu(arch_const_cycles_prev, cpu) ||
153+
!per_cpu(arch_core_cycles_prev, cpu))) {
154+
pr_debug("CPU%d: cycle counters are not enabled.\n", cpu);
155+
return -EINVAL;
156+
}
157+
158+
/* Convert maximum frequency from KHz to Hz and validate */
159+
max_freq_hz = cpufreq_get_hw_max_freq(cpu) * 1000;
160+
if (unlikely(!max_freq_hz)) {
161+
pr_debug("CPU%d: invalid maximum frequency.\n", cpu);
162+
return -EINVAL;
163+
}
164+
165+
/*
166+
* Pre-compute the fixed ratio between the frequency of the constant
167+
* counter and the maximum frequency of the CPU.
168+
*
169+
* const_freq
170+
* arch_max_freq_scale = ---------------- * SCHED_CAPACITY_SCALE²
171+
* cpuinfo_max_freq
172+
*
173+
* We use a factor of 2 * SCHED_CAPACITY_SHIFT -> SCHED_CAPACITY_SCALE²
174+
* in order to ensure a good resolution for arch_max_freq_scale for
175+
* very low arch timer frequencies (down to the KHz range which should
176+
* be unlikely).
177+
*/
178+
ratio = (u64)arch_timer_get_rate() << (2 * SCHED_CAPACITY_SHIFT);
179+
ratio = div64_u64(ratio, max_freq_hz);
180+
if (!ratio) {
181+
WARN_ONCE(1, "System timer frequency too low.\n");
182+
return -EINVAL;
183+
}
184+
185+
per_cpu(arch_max_freq_scale, cpu) = (unsigned long)ratio;
186+
187+
return 0;
188+
}
189+
190+
static inline bool
191+
enable_policy_freq_counters(int cpu, cpumask_var_t valid_cpus)
192+
{
193+
struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
194+
195+
if (!policy) {
196+
pr_debug("CPU%d: No cpufreq policy found.\n", cpu);
197+
return false;
198+
}
199+
200+
if (cpumask_subset(policy->related_cpus, valid_cpus))
201+
cpumask_or(amu_fie_cpus, policy->related_cpus,
202+
amu_fie_cpus);
203+
204+
cpufreq_cpu_put(policy);
205+
206+
return true;
207+
}
208+
209+
static DEFINE_STATIC_KEY_FALSE(amu_fie_key);
210+
#define amu_freq_invariant() static_branch_unlikely(&amu_fie_key)
211+
212+
static int __init init_amu_fie(void)
213+
{
214+
cpumask_var_t valid_cpus;
215+
bool have_policy = false;
216+
int ret = 0;
217+
int cpu;
218+
219+
if (!zalloc_cpumask_var(&valid_cpus, GFP_KERNEL))
220+
return -ENOMEM;
221+
222+
if (!zalloc_cpumask_var(&amu_fie_cpus, GFP_KERNEL)) {
223+
ret = -ENOMEM;
224+
goto free_valid_mask;
225+
}
226+
227+
for_each_present_cpu(cpu) {
228+
if (validate_cpu_freq_invariance_counters(cpu))
229+
continue;
230+
cpumask_set_cpu(cpu, valid_cpus);
231+
have_policy |= enable_policy_freq_counters(cpu, valid_cpus);
232+
}
233+
234+
/*
235+
* If we are not restricted by cpufreq policies, we only enable
236+
* the use of the AMU feature for FIE if all CPUs support AMU.
237+
* Otherwise, enable_policy_freq_counters has already enabled
238+
* policy cpus.
239+
*/
240+
if (!have_policy && cpumask_equal(valid_cpus, cpu_present_mask))
241+
cpumask_or(amu_fie_cpus, amu_fie_cpus, valid_cpus);
242+
243+
if (!cpumask_empty(amu_fie_cpus)) {
244+
pr_info("CPUs[%*pbl]: counters will be used for FIE.",
245+
cpumask_pr_args(amu_fie_cpus));
246+
static_branch_enable(&amu_fie_key);
247+
}
248+
249+
free_valid_mask:
250+
free_cpumask_var(valid_cpus);
251+
252+
return ret;
253+
}
254+
late_initcall_sync(init_amu_fie);
255+
256+
bool arch_freq_counters_available(struct cpumask *cpus)
257+
{
258+
return amu_freq_invariant() &&
259+
cpumask_subset(cpus, amu_fie_cpus);
260+
}
261+
262+
void topology_scale_freq_tick(void)
263+
{
264+
u64 prev_core_cnt, prev_const_cnt;
265+
u64 core_cnt, const_cnt, scale;
266+
int cpu = smp_processor_id();
267+
268+
if (!amu_freq_invariant())
269+
return;
270+
271+
if (!cpumask_test_cpu(cpu, amu_fie_cpus))
272+
return;
273+
274+
const_cnt = read_sysreg_s(SYS_AMEVCNTR0_CONST_EL0);
275+
core_cnt = read_sysreg_s(SYS_AMEVCNTR0_CORE_EL0);
276+
prev_const_cnt = this_cpu_read(arch_const_cycles_prev);
277+
prev_core_cnt = this_cpu_read(arch_core_cycles_prev);
278+
279+
if (unlikely(core_cnt <= prev_core_cnt ||
280+
const_cnt <= prev_const_cnt))
281+
goto store_and_exit;
282+
283+
/*
284+
* /\core arch_max_freq_scale
285+
* scale = ------- * --------------------
286+
* /\const SCHED_CAPACITY_SCALE
287+
*
288+
* See validate_cpu_freq_invariance_counters() for details on
289+
* arch_max_freq_scale and the use of SCHED_CAPACITY_SHIFT.
290+
*/
291+
scale = core_cnt - prev_core_cnt;
292+
scale *= this_cpu_read(arch_max_freq_scale);
293+
scale = div64_u64(scale >> SCHED_CAPACITY_SHIFT,
294+
const_cnt - prev_const_cnt);
295+
296+
scale = min_t(unsigned long, scale, SCHED_CAPACITY_SCALE);
297+
this_cpu_write(freq_scale, (unsigned long)scale);
298+
299+
store_and_exit:
300+
this_cpu_write(arch_core_cycles_prev, core_cnt);
301+
this_cpu_write(arch_const_cycles_prev, const_cnt);
302+
}
303+
#endif /* CONFIG_ARM64_AMU_EXTN */

drivers/base/arch_topology.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@
2121
#include <linux/sched.h>
2222
#include <linux/smp.h>
2323

24+
__weak bool arch_freq_counters_available(struct cpumask *cpus)
25+
{
26+
return false;
27+
}
2428
DEFINE_PER_CPU(unsigned long, freq_scale) = SCHED_CAPACITY_SCALE;
2529

2630
void arch_set_freq_scale(struct cpumask *cpus, unsigned long cur_freq,
@@ -29,6 +33,14 @@ void arch_set_freq_scale(struct cpumask *cpus, unsigned long cur_freq,
2933
unsigned long scale;
3034
int i;
3135

36+
/*
37+
* If the use of counters for FIE is enabled, just return as we don't
38+
* want to update the scale factor with information from CPUFREQ.
39+
* Instead the scale factor will be updated from arch_scale_freq_tick.
40+
*/
41+
if (arch_freq_counters_available(cpus))
42+
return;
43+
3244
scale = (cur_freq << SCHED_CAPACITY_SHIFT) / max_freq;
3345

3446
for_each_cpu(i, cpus)

include/linux/arch_topology.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ unsigned long topology_get_freq_scale(int cpu)
3333
return per_cpu(freq_scale, cpu);
3434
}
3535

36+
bool arch_freq_counters_available(struct cpumask *cpus);
37+
3638
struct cpu_topology {
3739
int thread_id;
3840
int core_id;

0 commit comments

Comments
 (0)