[PATCH v5 3/3] arch_topology: Add topology_update_cpu_capacity() for runtime updates
Xueqin Luo <[email protected]>
| Newsgroups | dev.linux.lists.driver-core,org.kernel.vger.linux-kernel,org.kernel.vger.linux-pm |
|---|---|
| Message-ID | <[email protected]> |
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. Provide a no-op stub when GENERIC_ARCH_TOPOLOGY is disabled so cppc_cpufreq can link on those configs. 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 <[email protected]> --- drivers/base/arch_topology.c | 76 ++++++++++++++++++++++++++++++++++ drivers/cpufreq/cppc_cpufreq.c | 2 + include/linux/arch_topology.h | 13 ++++++ 3 files changed, 91 insertions(+) diff --git a/drivers/base/arch_topology.c b/drivers/base/arch_topology.c index 8c5e47c28d9a..27f2bfa9f326 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) { @@ -372,13 +373,88 @@ 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->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 09d7745a609f..1480be537eaa 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -979,6 +979,8 @@ static void cppc_cpufreq_update_limits(struct cpufreq_policy *policy) refresh_frequency_limits(policy); + topology_update_cpu_capacity(policy->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