* [PATCH v1 0/2] arm64/cpufreq: report and track frequencies above 4.19 GHz
@ 2026-09-06 16:37 Oleg Keri
2026-09-06 16:37 ` [PATCH v1 1/2] arm64: topology: fix arch_freq_get_on_cpu() overflow " Oleg Keri
2026-09-06 16:37 ` [PATCH v1 2/2] cpufreq: update capacity_freq_ref when the boost state changes Oleg Keri
0 siblings, 2 replies; 5+ messages in thread
From: Oleg Keri @ 2026-09-06 16:37 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Mark Rutland, Beata Michalska,
Sumit Gupta, Prasanna Kumar T S M, Russell King, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Sudeep Holla,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Viresh Kumar
Cc: linux-arm-kernel, linux-kernel, linux-riscv, driver-core, linux-pm
The Snapdragon X2 Elite (Glymur) is the first arm64 laptop part I have
seen whose boost OPP, 4723200 kHz, sits above 4194304 kHz. Two
independent problems become visible there, both of which make the
kernel believe a boosted CPU is running slower than it is.
Patch 1 fixes an overflow in arch_freq_get_on_cpu(): the u64 product of
the frequency scale and the reference frequency is truncated to
unsigned int before being shifted back down, which wraps for any
reference frequency above 2^32 / SCHED_CAPACITY_SCALE = 4194304 kHz.
Patch 2 makes capacity_freq_ref follow the boost state. It is latched
once on CPUFREQ_CREATE_POLICY, and boost frequencies are excluded from
policy->cpuinfo.max_freq while boost is off, so on a machine that boots
with boost disabled it keeps the sustained maximum forever. On arm64
that saturates the AMU frequency scale at SCHED_CAPACITY_SCALE, so the
scheduler cannot distinguish a boosted CPU from one at the sustained
maximum, and arch_freq_get_on_cpu() cannot report above it.
The order matters: patch 2 is what raises capacity_freq_ref past
4194304 kHz on this machine, so patch 1 has to land with or before it.
Measured on a Lenovo Yoga Slim 7x Gen 11 (Glymur, 4032000 kHz
sustained, 4723200 kHz boost), pinning a policy to a single OPP and
timing a fixed workload on one of its CPUs:
requested OPP time cpuinfo_avg_freq
---------------------------------------------------------
4032000 kHz 2.011s 4031325 (0.02% low)
4723200 kHz 1.726s 524283 before
4723200 kHz 1.726s 4032000 with patch 1 only
4723200 kHz 1.726s 4718587 with both (0.10% low)
The timings never change: 2.011 / 1.726 = 1.165 against a frequency
ratio of 4723200 / 4032000 = 1.171, so the hardware was running at the
requested frequency throughout. Only the kernel's view of it was wrong.
Note that cpuinfo_cur_freq still reports 4032000 kHz at the boost OPP
on this machine. That is a separate path -- scmi_dvfs_freq_get()
asking firmware for the current performance level -- with no clamp in
the kernel, and it is not addressed here.
Oleg Keri (2):
arm64: topology: fix arch_freq_get_on_cpu() overflow above 4.19 GHz
cpufreq: update capacity_freq_ref when the boost state changes
arch/arm/include/asm/topology.h | 1 +
arch/arm64/include/asm/topology.h | 1 +
arch/arm64/kernel/topology.c | 5 +++--
arch/riscv/include/asm/topology.h | 1 +
drivers/base/arch_topology.c | 19 +++++++++++++------
drivers/cpufreq/cpufreq.c | 2 ++
include/linux/arch_topology.h | 1 +
include/linux/cpufreq.h | 7 +++++++
8 files changed, 29 insertions(+), 8 deletions(-)
base-commit: 9d80aa4617b32f5054c5aa471d06b66704854935
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v1 1/2] arm64: topology: fix arch_freq_get_on_cpu() overflow above 4.19 GHz
2026-09-06 16:37 [PATCH v1 0/2] arm64/cpufreq: report and track frequencies above 4.19 GHz Oleg Keri
@ 2026-09-06 16:37 ` Oleg Keri
2026-09-09 18:36 ` Jonathan Cameron
2026-09-06 16:37 ` [PATCH v1 2/2] cpufreq: update capacity_freq_ref when the boost state changes Oleg Keri
1 sibling, 1 reply; 5+ messages in thread
From: Oleg Keri @ 2026-09-06 16:37 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Mark Rutland, Beata Michalska,
Sumit Gupta, Prasanna Kumar T S M, Russell King, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Sudeep Holla,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Viresh Kumar
Cc: linux-arm-kernel, linux-kernel, linux-riscv, driver-core, linux-pm
arch_freq_get_on_cpu() computes the product of the frequency scale and
the reference frequency as a u64, but assigns it to an unsigned int
before shifting it back down:
freq = scale * arch_scale_freq_ref(cpu);
freq >>= SCHED_CAPACITY_SHIFT;
The product is truncated to 32 bits before the shift, so the result
wraps once arch_scale_freq_ref() exceeds 2^32 / SCHED_CAPACITY_SCALE,
i.e. 4194304 kHz.
On a Snapdragon X2 Elite (Glymur) laptop, whose boost OPP is 4723200
kHz, cpuinfo_avg_freq reports 524283 kHz instead of ~4723200 kHz while
the CPU demonstrably runs at the boost frequency: a fixed workload
completes in 1.72 s at the 4723200 kHz OPP versus 2.01 s at 4032000
kHz, matching the 1.171 frequency ratio.
Keep the arithmetic in 64 bits until after the shift.
Fixes: 16d1e27475f6 ("arm64: Provide an AMU-based version of arch_freq_get_on_cpu")
Signed-off-by: Oleg Keri <okerixx@gmail.com>
---
arch/arm64/kernel/topology.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c
index d28438f8b83f..804758eeb63a 100644
--- a/arch/arm64/kernel/topology.c
+++ b/arch/arm64/kernel/topology.c
@@ -245,8 +245,9 @@ int arch_freq_get_on_cpu(int cpu)
* (see amu_scale_freq_tick for details)
*/
scale = arch_scale_freq_capacity(cpu);
- freq = scale * arch_scale_freq_ref(cpu);
- freq >>= SCHED_CAPACITY_SHIFT;
+ scale *= arch_scale_freq_ref(cpu);
+ scale >>= SCHED_CAPACITY_SHIFT;
+ freq = scale;
return freq;
}
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v1 2/2] cpufreq: update capacity_freq_ref when the boost state changes
2026-09-06 16:37 [PATCH v1 0/2] arm64/cpufreq: report and track frequencies above 4.19 GHz Oleg Keri
2026-09-06 16:37 ` [PATCH v1 1/2] arm64: topology: fix arch_freq_get_on_cpu() overflow " Oleg Keri
@ 2026-09-06 16:37 ` Oleg Keri
1 sibling, 0 replies; 5+ messages in thread
From: Oleg Keri @ 2026-09-06 16:37 UTC (permalink / raw)
To: Catalin Marinas, Will Deacon, Mark Rutland, Beata Michalska,
Sumit Gupta, Prasanna Kumar T S M, Russell King, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Sudeep Holla,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Viresh Kumar
Cc: linux-arm-kernel, linux-kernel, linux-riscv, driver-core, linux-pm
capacity_freq_ref is latched from policy->cpuinfo.max_freq by
init_cpu_capacity_callback() on CPUFREQ_CREATE_POLICY, and never
updated afterwards.
cpufreq_frequency_table_cpuinfo() excludes CPUFREQ_BOOST_FREQ entries
while boost is disabled, so on a system that boots with boost off the
latched value is the non-boost maximum. Enabling boost later raises
policy->cpuinfo.max_freq but leaves capacity_freq_ref behind.
Two things then go wrong on arm64, where the AMU drives frequency
invariance. amu_scale_freq_tick() caps the computed scale at
SCHED_CAPACITY_SCALE, so a CPU running above capacity_freq_ref
saturates at 1024: the scheduler cannot tell a boosted CPU from one at
the sustained maximum, and utilisation is underestimated. And
arch_freq_get_on_cpu(), which reverses that computation, cannot report
more than capacity_freq_ref, so cpuinfo_avg_freq is pinned to the
non-boost maximum.
On a Snapdragon X2 Elite (Glymur) laptop with a 4032000 kHz sustained
and a 4723200 kHz boost OPP, cpuinfo_avg_freq reads exactly 4032000
while the CPU runs at 4723200; a fixed workload completes in 1.72 s
rather than the 2.01 s that frequency would imply.
Factor the update out of init_cpu_capacity_callback() into
topology_update_freq_ref() and call it from policy_set_boost(), which
is the common path for the global boost knob, the per-policy one, and
the CPU online path.
Signed-off-by: Oleg Keri <okerixx@gmail.com>
---
arch/arm/include/asm/topology.h | 1 +
arch/arm64/include/asm/topology.h | 1 +
arch/riscv/include/asm/topology.h | 1 +
drivers/base/arch_topology.c | 19 +++++++++++++------
drivers/cpufreq/cpufreq.c | 2 ++
include/linux/arch_topology.h | 1 +
include/linux/cpufreq.h | 7 +++++++
7 files changed, 26 insertions(+), 6 deletions(-)
diff --git a/arch/arm/include/asm/topology.h b/arch/arm/include/asm/topology.h
index ad36b6570067..a776a79885ee 100644
--- a/arch/arm/include/asm/topology.h
+++ b/arch/arm/include/asm/topology.h
@@ -11,6 +11,7 @@
#ifndef CONFIG_BL_SWITCHER
/* Replace task scheduler's default frequency-invariant accounting */
#define arch_set_freq_scale topology_set_freq_scale
+#define arch_update_freq_ref topology_update_freq_ref
#define arch_scale_freq_capacity topology_get_freq_scale
#define arch_scale_freq_invariant topology_scale_freq_invariant
#define arch_scale_freq_ref topology_get_freq_ref
diff --git a/arch/arm64/include/asm/topology.h b/arch/arm64/include/asm/topology.h
index b9eaf4ad7085..a4b96a7ee4a5 100644
--- a/arch/arm64/include/asm/topology.h
+++ b/arch/arm64/include/asm/topology.h
@@ -22,6 +22,7 @@ void update_freq_counters_refs(void);
/* Replace task scheduler's default frequency-invariant accounting */
#define arch_scale_freq_tick topology_scale_freq_tick
#define arch_set_freq_scale topology_set_freq_scale
+#define arch_update_freq_ref topology_update_freq_ref
#define arch_scale_freq_capacity topology_get_freq_scale
#define arch_scale_freq_invariant topology_scale_freq_invariant
#define arch_scale_freq_ref topology_get_freq_ref
diff --git a/arch/riscv/include/asm/topology.h b/arch/riscv/include/asm/topology.h
index fe1a8bf6902d..a363d72c174f 100644
--- a/arch/riscv/include/asm/topology.h
+++ b/arch/riscv/include/asm/topology.h
@@ -11,6 +11,7 @@
/* Replace task scheduler's default frequency-invariant accounting */
#define arch_scale_freq_tick topology_scale_freq_tick
#define arch_set_freq_scale topology_set_freq_scale
+#define arch_update_freq_ref topology_update_freq_ref
#define arch_scale_freq_capacity topology_get_freq_scale
#define arch_scale_freq_invariant topology_scale_freq_invariant
#define arch_scale_freq_ref topology_get_freq_ref
diff --git a/drivers/base/arch_topology.c b/drivers/base/arch_topology.c
index 8c5e47c28d9a..79eb1681065d 100644
--- a/drivers/base/arch_topology.c
+++ b/drivers/base/arch_topology.c
@@ -317,6 +317,18 @@ bool __init topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu)
return !ret;
}
+void topology_update_freq_ref(const struct cpumask *cpus, unsigned int max_freq)
+{
+ int cpu;
+
+ for_each_cpu(cpu, cpus) {
+ per_cpu(capacity_freq_ref, cpu) = max_freq;
+ freq_inv_set_max_ratio(cpu,
+ per_cpu(capacity_freq_ref, cpu) * HZ_PER_KHZ);
+ }
+}
+EXPORT_SYMBOL_GPL(topology_update_freq_ref);
+
void __weak freq_inv_set_max_ratio(int cpu, u64 max_rate)
{
}
@@ -392,7 +404,6 @@ init_cpu_capacity_callback(struct notifier_block *nb,
void *data)
{
struct cpufreq_policy *policy = data;
- int cpu;
if (val != CPUFREQ_CREATE_POLICY)
return 0;
@@ -403,11 +414,7 @@ init_cpu_capacity_callback(struct notifier_block *nb,
cpumask_andnot(cpus_to_visit, cpus_to_visit, policy->related_cpus);
- for_each_cpu(cpu, policy->related_cpus) {
- per_cpu(capacity_freq_ref, cpu) = policy->cpuinfo.max_freq;
- freq_inv_set_max_ratio(cpu,
- per_cpu(capacity_freq_ref, cpu) * HZ_PER_KHZ);
- }
+ topology_update_freq_ref(policy->related_cpus, policy->cpuinfo.max_freq);
if (cpumask_empty(cpus_to_visit)) {
if (raw_capacity) {
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index 0d0df986fa3d..289649baa061 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -594,6 +594,8 @@ static int policy_set_boost(struct cpufreq_policy *policy, bool enable)
return ret;
}
+ arch_update_freq_ref(policy->related_cpus, policy->cpuinfo.max_freq);
+
return 0;
}
diff --git a/include/linux/arch_topology.h b/include/linux/arch_topology.h
index ebd7f8935f96..4974f5e1a7fe 100644
--- a/include/linux/arch_topology.h
+++ b/include/linux/arch_topology.h
@@ -31,6 +31,7 @@ static inline unsigned long topology_get_freq_scale(int cpu)
void topology_set_freq_scale(const struct cpumask *cpus, unsigned long cur_freq,
unsigned long max_freq);
+void topology_update_freq_ref(const struct cpumask *cpus, unsigned int max_freq);
bool topology_scale_freq_invariant(void);
enum scale_freq_source {
diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h
index 35ce665edfd8..5b904e13eb21 100644
--- a/include/linux/cpufreq.h
+++ b/include/linux/cpufreq.h
@@ -1235,6 +1235,13 @@ void arch_set_freq_scale(const struct cpumask *cpus,
}
#endif
+#ifndef arch_update_freq_ref
+static __always_inline
+void arch_update_freq_ref(const struct cpumask *cpus, unsigned int max_freq)
+{
+}
+#endif
+
/* the following are really really optional */
extern struct freq_attr cpufreq_freq_attr_scaling_available_freqs;
extern struct freq_attr cpufreq_freq_attr_scaling_boost_freqs;
--
2.55.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v1 1/2] arm64: topology: fix arch_freq_get_on_cpu() overflow above 4.19 GHz
2026-09-06 16:37 ` [PATCH v1 1/2] arm64: topology: fix arch_freq_get_on_cpu() overflow " Oleg Keri
@ 2026-09-09 18:36 ` Jonathan Cameron
2026-09-09 19:18 ` Oleg Keri
0 siblings, 1 reply; 5+ messages in thread
From: Jonathan Cameron @ 2026-09-09 18:36 UTC (permalink / raw)
To: Oleg Keri
Cc: Catalin Marinas, Will Deacon, Mark Rutland, Beata Michalska,
Sumit Gupta, Prasanna Kumar T S M, Russell King, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Sudeep Holla,
Greg Kroah-Hartman, Rafael J. Wysocki, Danilo Krummrich,
Viresh Kumar, linux-arm-kernel, linux-kernel, linux-riscv,
driver-core, linux-pm
On Sun, 6 Sep 2026 18:37:14 +0200
Oleg Keri <okerixx@gmail.com> wrote:
> arch_freq_get_on_cpu() computes the product of the frequency scale and
> the reference frequency as a u64, but assigns it to an unsigned int
> before shifting it back down:
>
> freq = scale * arch_scale_freq_ref(cpu);
> freq >>= SCHED_CAPACITY_SHIFT;
>
> The product is truncated to 32 bits before the shift, so the result
> wraps once arch_scale_freq_ref() exceeds 2^32 / SCHED_CAPACITY_SCALE,
> i.e. 4194304 kHz.
>
> On a Snapdragon X2 Elite (Glymur) laptop, whose boost OPP is 4723200
> kHz, cpuinfo_avg_freq reports 524283 kHz instead of ~4723200 kHz while
> the CPU demonstrably runs at the boost frequency: a fixed workload
> completes in 1.72 s at the 4723200 kHz OPP versus 2.01 s at 4032000
> kHz, matching the 1.171 frequency ratio.
>
> Keep the arithmetic in 64 bits until after the shift.
>
> Fixes: 16d1e27475f6 ("arm64: Provide an AMU-based version of arch_freq_get_on_cpu")
> Signed-off-by: Oleg Keri <okerixx@gmail.com>
> ---
> arch/arm64/kernel/topology.c | 5 +++--
> 1 file changed, 3 insertions(+), 2 deletions(-)
>
> diff --git a/arch/arm64/kernel/topology.c b/arch/arm64/kernel/topology.c
> index d28438f8b83f..804758eeb63a 100644
> --- a/arch/arm64/kernel/topology.c
> +++ b/arch/arm64/kernel/topology.c
> @@ -245,8 +245,9 @@ int arch_freq_get_on_cpu(int cpu)
> * (see amu_scale_freq_tick for details)
> */
> scale = arch_scale_freq_capacity(cpu);
> - freq = scale * arch_scale_freq_ref(cpu);
> - freq >>= SCHED_CAPACITY_SHIFT;
> + scale *= arch_scale_freq_ref(cpu);
> + scale >>= SCHED_CAPACITY_SHIFT;
Maybe just do it all one line?
freq = (scale * arch_scale_freq_ref(cpu)) >> SCHED_CAPACITY_SHIFT;
I was going to suggest that you roll in the arch_scale_freq_capacity() as well
but then we'd need a cast to ensure maths was done in 64 bits and that was uglier.
I think this should end up the same as what you have.
Thanks,
Jonathan
> + freq = scale;
> return freq;
> }
>
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v1 1/2] arm64: topology: fix arch_freq_get_on_cpu() overflow above 4.19 GHz
2026-09-09 18:36 ` Jonathan Cameron
@ 2026-09-09 19:18 ` Oleg Keri
0 siblings, 0 replies; 5+ messages in thread
From: Oleg Keri @ 2026-09-09 19:18 UTC (permalink / raw)
To: Jonathan Cameron, Catalin Marinas, Will Deacon, Mark Rutland,
Beata Michalska, Sumit Gupta, Prasanna Kumar T S M, Russell King,
Paul Walmsley, Palmer Dabbelt, Albert Ou, Alexandre Ghiti,
Sudeep Holla, Greg Kroah-Hartman, Rafael J. Wysocki,
Danilo Krummrich, Viresh Kumar
Cc: linux-arm-kernel, linux-kernel, linux-riscv, driver-core, linux-pm
Good point - that reads much better than reusing scale as scratch. Taken
for v2:
scale = arch_scale_freq_capacity(cpu);
freq = (scale * arch_scale_freq_ref(cpu)) >> SCHED_CAPACITY_SHIFT;
Same semantics, and it makes it obvious that both the multiply and the
shift stay in 64 bits and only the final value is narrowed - which is the
whole point of the fix.
Agreed on leaving arch_scale_freq_capacity() out of the expression too.
This file is arm64 only, so unsigned long is 64 bits and it would be safe
here, but it stops being self-evident to the reader and the cast that
would make it evident is worse than keeping the temporary.
Thanks for the review.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-09-09 19:18 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-06 16:37 [PATCH v1 0/2] arm64/cpufreq: report and track frequencies above 4.19 GHz Oleg Keri
2026-09-06 16:37 ` [PATCH v1 1/2] arm64: topology: fix arch_freq_get_on_cpu() overflow " Oleg Keri
2026-09-09 18:36 ` Jonathan Cameron
2026-09-09 19:18 ` Oleg Keri
2026-09-06 16:37 ` [PATCH v1 2/2] cpufreq: update capacity_freq_ref when the boost state changes Oleg Keri
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®