* [PATCH v1 0/2] arm64/cpufreq: report and track frequencies above 4.19 GHz @ 2026-09-06 16:37 ` Oleg Keri 0 siblings, 0 replies; 10+ 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] 10+ messages in thread
* [PATCH v1 0/2] arm64/cpufreq: report and track frequencies above 4.19 GHz @ 2026-09-06 16:37 ` Oleg Keri 0 siblings, 0 replies; 10+ 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 _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply [flat|nested] 10+ 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 ` Oleg Keri @ 2026-09-06 16:37 ` Oleg Keri -1 siblings, 0 replies; 10+ 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 related [flat|nested] 10+ 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 ` Oleg Keri 0 siblings, 0 replies; 10+ 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 _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply related [flat|nested] 10+ 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 ` Oleg Keri @ 2026-09-09 18:36 ` Jonathan Cameron -1 siblings, 0 replies; 10+ 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] 10+ 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 0 siblings, 0 replies; 10+ 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; > } > _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply [flat|nested] 10+ 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 -1 siblings, 0 replies; 10+ 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. _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v1 1/2] arm64: topology: fix arch_freq_get_on_cpu() overflow above 4.19 GHz @ 2026-09-09 19:18 ` Oleg Keri 0 siblings, 0 replies; 10+ 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] 10+ messages in thread
* [PATCH v1 2/2] cpufreq: update capacity_freq_ref when the boost state changes 2026-09-06 16:37 ` Oleg Keri @ 2026-09-06 16:37 ` Oleg Keri -1 siblings, 0 replies; 10+ 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 related [flat|nested] 10+ messages in thread
* [PATCH v1 2/2] cpufreq: update capacity_freq_ref when the boost state changes @ 2026-09-06 16:37 ` Oleg Keri 0 siblings, 0 replies; 10+ 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 _______________________________________________ linux-riscv mailing list linux-riscv@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-riscv ^ permalink raw reply related [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-09-09 19:18 UTC | newest] Thread overview: 10+ 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 ` 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 2026-09-09 18:36 ` Jonathan Cameron 2026-09-09 18:36 ` Jonathan Cameron 2026-09-09 19:18 ` Oleg Keri 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 2026-09-06 16:37 ` Oleg Keri
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.