Re: [PATCH v3 1/2] arch_topology: seed capacity_freq_ref with highest available frequency

From: Zhongqiu Han

Date: Sun Oct 11 2026 - 12:33:37 EST


On 9/29/2026 7:38 PM, Ananthu C V wrote:
capacity_freq_ref, the per-CPU frequency-invariance reference used by
schedutil, is seeded from policy->cpuinfo.max_freq at policy creation.
If boost isn't enabled yet at boot, max_freq reflects only the
non-boost ceiling, so capacity_freq_ref never learns about boost
frequencies for the policy's lifetime. Enabling boost later raises
policy->max, but capacity_freq_ref stays stale, leaving schedutil
unable to scale utilization or target a frequency above the non-boost
maximum.

Seed capacity_freq_ref with the highest available frequency regardless
of whether boost is enabled. Runtime enforcement, still handled by
policy->max, is unaffected.

Suggested-by: Vincent Guittot <vincent.guittot@xxxxxxxxxx>
Signed-off-by: Ananthu C V <ananthu.cv@xxxxxxxxxxxxxxxx>
---
drivers/base/arch_topology.c | 3 ++-
include/linux/cpufreq.h | 13 +++++++++++++
2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/drivers/base/arch_topology.c b/drivers/base/arch_topology.c
index 8c5e47c28d9a..92966be712d5 100644
--- a/drivers/base/arch_topology.c
+++ b/drivers/base/arch_topology.c
@@ -404,7 +404,8 @@ 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;
+ per_cpu(capacity_freq_ref, cpu) = max(policy->cpuinfo.max_freq,
+ cpufreq_frequency_table_max(policy));
freq_inv_set_max_ratio(cpu,
per_cpu(capacity_freq_ref, cpu) * HZ_PER_KHZ);
}
diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h
index 35ce665edfd8..f048c925fd98 100644
--- a/include/linux/cpufreq.h
+++ b/include/linux/cpufreq.h
@@ -1107,6 +1107,19 @@ static inline int cpufreq_frequency_table_target(struct cpufreq_policy *policy,
return idx;
}
+static inline unsigned int cpufreq_frequency_table_max(struct cpufreq_policy *policy)
+{
+ int idx;
+
+ if (!policy->freq_table)
+ return policy->cpuinfo.max_freq;
+
+ idx = cpufreq_frequency_table_target(policy, UINT_MAX, 0, UINT_MAX,
+ CPUFREQ_RELATION_H);
+
+ return policy->freq_table[idx].frequency;
+}
+

The current implementation looks correct and works well. That said, we
might be able to optimize it with a simpler implementation as shown
below:

+/**
+ * cpufreq_frequency_table_max - Get the highest frequency in a policy's table
+ * @policy: the &struct cpufreq_policy to inspect
+ *
+ * Boost entries (%CPUFREQ_BOOST_FREQ) are taken into account regardless of
+ * whether or not boost is currently enabled.
+ *
+ * Return: the highest valid frequency in @policy->freq_table, or 0 if there
+ * is no frequency table.
+ */
+static inline unsigned int
+cpufreq_frequency_table_max(const struct cpufreq_policy *policy)
+{
+ struct cpufreq_frequency_table *pos;
+ unsigned int max_freq = 0;
+
+ if (!policy->freq_table)
+ return 0;
+
+ cpufreq_for_each_valid_entry(pos, policy->freq_table)
+ max_freq = max(max_freq, pos->frequency);
+
+ return max_freq;
+}
+


static inline int cpufreq_table_count_valid_entries(const struct cpufreq_policy *policy)
{
struct cpufreq_frequency_table *pos;



Besides, could you comment patch v2 Dietmar's comments? It seems that patch 1/2 exposes a pre-existing ordering issue. On DT systems, cpu_scale is normalized only from the CPUFREQ_CREATE_POLICY notification of the last policy, so the cpufreq pressure of every other policy is computed in cpufreq_init_policy() from the raw, not yet normalized, capacity, and stays cached until the next cpufreq_set_policy() for that policy. So far this was harmless because capacity_freq_ref == policy->max at boot, so the pressure was 0 regardless of the capacity. With 1/2, capacity_freq_ref covers the boost frequencies while boost is disabled at boot, so policy->max < capacity_freq_ref becomes the common case and the stale value becomes visible (102 vs 79 on Juno R0, as reported by Dietmar). Perhaps the limits of all policies could be refreshed once from parsing_done_workfn(), after the capacities have been normalized?

--- a/drivers/base/arch_topology.c
+++ b/drivers/base/arch_topology.c
@@ -454,9 +454,27 @@ core_initcall(register_cpufreq_notifier);

static void parsing_done_workfn(struct work_struct *work)
{
+ int cpu;
+
cpufreq_unregister_notifier(&init_cpu_capacity_notifier,
CPUFREQ_POLICY_NOTIFIER);
free_cpumask_var(cpus_to_visit);
+
+ /*
+ * The cpufreq pressure of all policies but the last one has been
+ * computed before topology_normalize_cpu_scale(), so refresh it now
+ * that cpu_scale is final.
+ */
+ for_each_possible_cpu(cpu) {
+ struct cpufreq_policy *policy __free(put_cpufreq_policy) =
+ cpufreq_cpu_get(cpu);
+
+ if (!policy || cpu != policy->cpu)
+ continue;
+
+ guard(cpufreq_policy_write)(policy);
+ refresh_frequency_limits(policy);
+ }



--
Thx and BRs,
Zhongqiu Han