[PATCH v6 3/3] arch_topology: Add topology_update_cpu_capacity() for runtime updates
From: Xueqin Luo
Date: Wed Nov 11 2026 - 03:56:24 EST
When the CPPC Highest Performance register changes at runtime
(e.g. via ACPI Notify(0x85)), the scheduler's view of CPU capacity
and the frequency invariance engine's reference values become stale,
as topology_init_cpu_capacity_cppc() is only called once during boot.
Keep raw_capacity allocated after CPPC init instead of freeing it,
and introduce topology_update_cpu_capacity() to update per-CPU
raw_capacity, capacity_freq_ref, and the normalized CPU capacity
scale at runtime. Skip updates when the value is unchanged and
reject a zero capacity_scale to avoid division by zero.
Call this from cppc_cpufreq_update_limits() for every CPU in the
policy so shared-policy Notify(0x85) targeting a non-policy CPU
still refreshes the correct topology capacity.
Signed-off-by: Xueqin Luo <luoxueqin@xxxxxxxxxx>
---
drivers/base/arch_topology.c | 77 ++++++++++++++++++++++++++++++++++
drivers/cpufreq/cppc_cpufreq.c | 10 ++++-
include/linux/arch_topology.h | 13 ++++++
3 files changed, 99 insertions(+), 1 deletion(-)
diff --git a/drivers/base/arch_topology.c b/drivers/base/arch_topology.c
index e51fc9f5e976..f64f8e61f210 100644
--- a/drivers/base/arch_topology.c
+++ b/drivers/base/arch_topology.c
@@ -229,6 +229,7 @@ static void update_topology_flags_workfn(struct work_struct *work)
}
static u32 *raw_capacity;
+static DEFINE_MUTEX(raw_capacity_lock);
static int free_raw_capacity(void)
{
@@ -370,13 +371,89 @@ static inline void topology_init_cpu_capacity_cppc(void)
schedule_work(&update_topology_flags_work);
pr_debug("cpu_capacity: cpu_capacity initialization done\n");
+ /*
+ * Keep raw_capacity for runtime updates via
+ * topology_update_cpu_capacity().
+ */
+ return;
+
exit:
free_raw_capacity();
}
+
void acpi_processor_init_invariance_cppc(void)
{
topology_init_cpu_capacity_cppc();
}
+
+/**
+ * topology_update_cpu_capacity - Update CPU capacity after highest_perf change
+ * @cpu: CPU whose highest performance changed
+ * @perf_caps: Updated CPPC performance capabilities for @cpu
+ *
+ * When the CPPC Highest Performance register changes at runtime
+ * (e.g. via Notify(0x85)), the scheduler's view of CPU capacity
+ * and the frequency invariance engine's reference values become
+ * stale. This function updates the per-CPU raw_capacity,
+ * capacity_freq_ref and freq_inv max ratio, then re-normalizes the
+ * CPU capacity scale for all possible CPUs and triggers a sched
+ * domain rebuild. If the value is unchanged, everything is skipped.
+ */
+void topology_update_cpu_capacity(unsigned int cpu,
+ struct cppc_perf_caps *perf_caps)
+{
+ u32 highest_perf = perf_caps->highest_perf;
+ u64 capacity, capacity_scale = 0;
+ int c;
+
+ guard(mutex)(&raw_capacity_lock);
+
+ if (!raw_capacity || cpu >= num_possible_cpus())
+ return;
+
+ /*
+ * Validate: highest_perf must be >= nominal_perf and >= lowest_perf,
+ * consistent with the boot-time check in topology_init_cpu_capacity_cppc().
+ */
+ if (highest_perf < perf_caps->nominal_perf ||
+ highest_perf < perf_caps->lowest_perf) {
+ pr_warn("cpu_capacity: CPU%d invalid highest_perf=%u (nominal=%u, lowest=%u), skipping\n",
+ cpu, highest_perf, perf_caps->nominal_perf,
+ perf_caps->lowest_perf);
+ return;
+ }
+
+ if (raw_capacity[cpu] == highest_perf)
+ return;
+
+ pr_debug("cpu_capacity: CPU%d cpu_capacity=%u -> %u (raw)\n",
+ cpu, raw_capacity[cpu], highest_perf);
+
+ raw_capacity[cpu] = highest_perf;
+ per_cpu(capacity_freq_ref, cpu) =
+ cppc_perf_to_khz(perf_caps, highest_perf);
+ freq_inv_set_max_ratio(cpu,
+ per_cpu(capacity_freq_ref, cpu) * HZ_PER_KHZ);
+
+ /* Re-normalize all CPUs: capacity is relative. */
+ for_each_possible_cpu(c)
+ capacity_scale = max_t(u64, capacity_scale, raw_capacity[c]);
+
+ if (!capacity_scale)
+ return;
+
+ for_each_possible_cpu(c) {
+ capacity = raw_capacity[c];
+ capacity = div64_u64(capacity << SCHED_CAPACITY_SHIFT,
+ capacity_scale);
+ topology_set_cpu_scale(c, capacity);
+ pr_debug("cpu_capacity: CPU%d cpu_capacity=%lu\n",
+ c, topology_get_cpu_scale(c));
+ }
+
+ schedule_work(&update_topology_flags_work);
+}
+EXPORT_SYMBOL_GPL(topology_update_cpu_capacity);
#endif
#ifdef CONFIG_CPU_FREQ
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 7dab6e046b9f..a693ef611caa 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -935,7 +935,7 @@ static void cppc_cpufreq_update_limits(struct cpufreq_policy *policy)
struct cppc_perf_caps *caps;
u64 prev_highest_perf;
u64 highest_perf;
- int ret;
+ int cpu, ret;
guard(cpufreq_policy_write)(policy);
@@ -984,6 +984,14 @@ static void cppc_cpufreq_update_limits(struct cpufreq_policy *policy)
refresh_frequency_limits(policy);
+ /*
+ * All CPUs of the policy share perf_caps, and Notify(0x85) may
+ * target any CPU of a shared policy, so refresh the topology
+ * capacity for every CPU of the policy, including offline ones.
+ */
+ for_each_cpu(cpu, policy->related_cpus)
+ topology_update_cpu_capacity(cpu, caps);
+
/*
* Autonomous selection mode uses MIN/MAX performance as runtime
* hardware control bounds. Re-program them when highest_perf
diff --git a/include/linux/arch_topology.h b/include/linux/arch_topology.h
index ebd7f8935f96..9415cb6a6c2b 100644
--- a/include/linux/arch_topology.h
+++ b/include/linux/arch_topology.h
@@ -11,6 +11,19 @@
void topology_normalize_cpu_scale(void);
int topology_update_cpu_topology(void);
+#ifdef CONFIG_ACPI_CPPC_LIB
+struct cppc_perf_caps;
+#ifdef CONFIG_GENERIC_ARCH_TOPOLOGY
+void topology_update_cpu_capacity(unsigned int cpu,
+ struct cppc_perf_caps *perf_caps);
+#else
+static inline void
+topology_update_cpu_capacity(unsigned int cpu, struct cppc_perf_caps *perf_caps)
+{
+}
+#endif
+#endif
+
struct device_node;
bool topology_parse_cpu_capacity(struct device_node *cpu_node, int cpu);
--
2.43.0