* [PATCH v5 0/3] cpufreq: cppc: Handle Highest Performance changes at runtime
@ 2026-08-07 6:08 Xueqin Luo
2026-08-07 6:08 ` [PATCH v5 1/3] cpufreq: cppc: Add update_limits support for Highest Performance changes Xueqin Luo
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Xueqin Luo @ 2026-08-07 6:08 UTC (permalink / raw)
To: Sudeep Holla, Greg Kroah-Hartman, Rafael J . Wysocki,
Danilo Krummrich, Viresh Kumar
Cc: Jie Zhan, Lifeng Zheng, Pierre Gondois, Sumit Gupta, linux-kernel,
driver-core, linux-pm, Xueqin Luo
Hi Rafael, Pierre,
This series adds support for handling ACPI CPPC Highest Performance
register changes at runtime, triggered by Notify(0x85) on a processor
device.
When the platform changes the Highest Performance value (e.g. due to
thermal or power budget adjustments), the OSPM must re-evaluate the
cached capability and propagate the change to the cpufreq policy,
the scheduler's CPU capacity model, and the frequency invariance
engine.
The series is split into three patches:
Patch 1: Core update_limits callback with boost QoS handling.
Patch 2: Refactor autonomous perf bounds into a reusable helper
and wire it into update_limits with error logging.
Patch 3: Topology subsystem runtime capacity updates with
validation, early-return optimization, and concurrent-safe
normalization.
v4 -> v5:
- Move cpu_data and caps pointer assignments after
guard(cpufreq_policy_write) in cppc_cpufreq_update_limits() to
avoid potential use-after-free if concurrent CPU offline frees
driver_data before the lock is acquired (Sashiko)
- Extract cppc_cpufreq_sync_boost_limits() from update_limits to
handle boost_supported re-evaluation, cpuinfo.max_freq tracking,
and boost_freq_req QoS update in a dedicated helper. The helper
always updates the QoS request (even when boost becomes
unsupported) to clear stale constraint values, and forcibly
disables boost_enabled when boost disappears at runtime
- Add highest_perf validation in topology_update_cpu_capacity():
reject values below lowest_perf to prevent corrupted register
reads from propagating into the scheduler capacity model
- Add early-return in topology_update_cpu_capacity() when
raw_capacity[cpu] is unchanged, avoiding redundant normalization
and schedule_work() calls
- Add zero capacity_scale guard in topology_update_cpu_capacity()
to prevent division by zero in the normalization loop
- Move guard(mutex) before the raw_capacity NULL check so that
the pointer read is protected by the lock
- Add CONFIG_GENERIC_ARCH_TOPOLOGY guard with no-op stub in
arch_topology.h for configs where the topology subsystem is
disabled
Xueqin Luo (3):
cpufreq: cppc: Add update_limits support for Highest Performance
changes
cpufreq: cppc: Refactor autonomous perf bounds into helper
arch_topology: Add topology_update_cpu_capacity() for runtime updates
drivers/base/arch_topology.c | 76 ++++++++++++++++
drivers/cpufreq/cppc_cpufreq.c | 157 ++++++++++++++++++++++++++++++---
include/linux/arch_topology.h | 13 +++
3 files changed, 233 insertions(+), 13 deletions(-)
--
2.43.0
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v5 1/3] cpufreq: cppc: Add update_limits support for Highest Performance changes
2026-08-07 6:08 [PATCH v5 0/3] cpufreq: cppc: Handle Highest Performance changes at runtime Xueqin Luo
@ 2026-08-07 6:08 ` Xueqin Luo
2026-08-07 6:08 ` [PATCH v5 2/3] cpufreq: cppc: Refactor autonomous perf bounds into helper Xueqin Luo
2026-08-07 6:08 ` [PATCH v5 3/3] arch_topology: Add topology_update_cpu_capacity() for runtime updates Xueqin Luo
2 siblings, 0 replies; 5+ messages in thread
From: Xueqin Luo @ 2026-08-07 6:08 UTC (permalink / raw)
To: Sudeep Holla, Greg Kroah-Hartman, Rafael J . Wysocki,
Danilo Krummrich, Viresh Kumar
Cc: Jie Zhan, Lifeng Zheng, Pierre Gondois, Sumit Gupta, linux-kernel,
driver-core, linux-pm, Xueqin Luo
ACPI CPPC specification requires OSPM to re-evaluate the Highest
Performance register when Notify(0x85) is received for a processor
device.
Implement cppc_cpufreq_update_limits() to refresh the cached
highest_perf capability through cppc_get_highest_perf() and update
policy->cpuinfo.max_freq from Highest Performance. Use
refresh_frequency_limits() so policy->max follows the standard
cpufreq_set_policy() path.
cpuinfo.max_freq always tracks Highest Performance. When boost is
disabled but still supported, constrain policy->max by updating the
existing boost_freq_req (created in cpufreq_policy_init_qos) to
nominal, instead of hiding the hardware maximum in cpuinfo.
Signed-off-by: Xueqin Luo <luoxueqin@kylinos.cn>
---
drivers/cpufreq/cppc_cpufreq.c | 98 ++++++++++++++++++++++++++++++++++
1 file changed, 98 insertions(+)
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 6fe0e972952a..089f734f851b 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -855,6 +855,103 @@ static int cppc_cpufreq_set_boost(struct cpufreq_policy *policy, int state)
return 0;
}
+/**
+ * cppc_cpufreq_sync_boost_limits - Sync boost flag, cpuinfo max and boost QoS
+ * @policy: cpufreq policy
+ * @boost_supported: whether highest_perf currently exceeds nominal_perf
+ *
+ * Highest Performance can appear or disappear at runtime via Notify(0x85).
+ *
+ * cpuinfo.max_freq always tracks the hardware maximum derived from Highest
+ * Performance so that sysfs reflects Notify(0x85) updates. Boost being off
+ * is enforced by updating the existing boost_freq_req to nominal (capping
+ * policy->max) rather than by hiding the hardware max in cpuinfo.
+ * boost_freq_req itself is only created at policy init, not here.
+ */
+static void cppc_cpufreq_sync_boost_limits(struct cpufreq_policy *policy,
+ bool boost_supported)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ struct cppc_perf_caps *caps = &cpu_data->perf_caps;
+ unsigned int highest_freq, nominal_freq, qos_freq;
+ int ret;
+
+ if (!boost_supported && policy->boost_enabled)
+ policy->boost_enabled = false;
+
+ policy->boost_supported = boost_supported;
+
+ highest_freq = cppc_perf_to_khz(caps, caps->highest_perf);
+ nominal_freq = cppc_perf_to_khz(caps, caps->nominal_perf);
+
+ /*
+ * Report the current hardware maximum. If Highest dropped below
+ * Nominal (unusual, but possible with test overrides), never
+ * advertise more than Highest allows.
+ */
+ policy->cpuinfo.max_freq = highest_freq;
+
+ if (boost_supported && !policy->boost_enabled)
+ qos_freq = min(nominal_freq, highest_freq);
+ else
+ qos_freq = highest_freq;
+
+ /*
+ * boost_freq_req is created in cpufreq_policy_init_qos() when
+ * boost_supported is true at policy init. Runtime Highest changes
+ * only update that existing request; they do not add or remove it.
+ */
+ if (freq_qos_request_active(&policy->boost_freq_req)) {
+ ret = freq_qos_update_request(&policy->boost_freq_req,
+ qos_freq);
+ if (ret < 0)
+ pr_debug("CPU%d: failed to sync boost QoS: %d\n",
+ policy->cpu, ret);
+ }
+
+ pr_debug("CPU%d: highest_perf=%u boost_en=%d cpuinfo_max=%u qos_max=%u\n",
+ policy->cpu, caps->highest_perf, policy->boost_enabled,
+ policy->cpuinfo.max_freq, qos_freq);
+}
+
+static void cppc_cpufreq_update_limits(struct cpufreq_policy *policy)
+{
+ struct cppc_cpudata *cpu_data;
+ struct cppc_perf_caps *caps;
+ u64 prev_highest_perf;
+ u64 highest_perf;
+ int ret;
+
+ guard(cpufreq_policy_write)(policy);
+
+ cpu_data = policy->driver_data;
+ caps = &cpu_data->perf_caps;
+
+ prev_highest_perf = caps->highest_perf;
+
+ ret = cppc_get_highest_perf(policy->cpu, &highest_perf);
+ if (ret)
+ return;
+
+ if (highest_perf == prev_highest_perf)
+ return;
+
+ caps->highest_perf = highest_perf;
+
+ /*
+ * Re-evaluate boost capability/status based on the updated Highest
+ * Performance. Boost is supported when highest_perf exceeds
+ * nominal_perf.
+ */
+ cppc_cpufreq_sync_boost_limits(policy,
+ highest_perf > caps->nominal_perf);
+
+ refresh_frequency_limits(policy);
+
+ pr_debug("CPU%d: highest_perf updated %llu -> %llu\n",
+ policy->cpu, prev_highest_perf, highest_perf);
+}
+
static ssize_t show_freqdomain_cpus(struct cpufreq_policy *policy, char *buf)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
@@ -1048,6 +1145,7 @@ static struct cpufreq_driver cppc_cpufreq_driver = {
.init = cppc_cpufreq_cpu_init,
.exit = cppc_cpufreq_cpu_exit,
.set_boost = cppc_cpufreq_set_boost,
+ .update_limits = cppc_cpufreq_update_limits,
.attr = cppc_cpufreq_attr,
.name = "cppc_cpufreq",
};
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v5 2/3] cpufreq: cppc: Refactor autonomous perf bounds into helper
2026-08-07 6:08 [PATCH v5 0/3] cpufreq: cppc: Handle Highest Performance changes at runtime Xueqin Luo
2026-08-07 6:08 ` [PATCH v5 1/3] cpufreq: cppc: Add update_limits support for Highest Performance changes Xueqin Luo
@ 2026-08-07 6:08 ` Xueqin Luo
2026-08-07 6:08 ` [PATCH v5 3/3] arch_topology: Add topology_update_cpu_capacity() for runtime updates Xueqin Luo
2 siblings, 0 replies; 5+ messages in thread
From: Xueqin Luo @ 2026-08-07 6:08 UTC (permalink / raw)
To: Sudeep Holla, Greg Kroah-Hartman, Rafael J . Wysocki,
Danilo Krummrich, Viresh Kumar
Cc: Jie Zhan, Lifeng Zheng, Pierre Gondois, Sumit Gupta, linux-kernel,
driver-core, linux-pm, Xueqin Luo
Extract the autonomous selection performance bounds programming from
store_auto_select() into a reusable cppc_cpufreq_set_autonomous_perf()
helper. The helper saves and restores MIN/MAX performance on failure,
keeping the rollback logic co-located with the programming sequence.
Reuse the helper in cppc_cpufreq_update_limits() so that when
Highest Performance changes at runtime, the autonomous MIN/MAX
envelope is re-programmed against the updated capability. Add a
pr_debug message on failure for diagnostics.
Signed-off-by: Xueqin Luo <luoxueqin@kylinos.cn>
---
drivers/cpufreq/cppc_cpufreq.c | 57 ++++++++++++++++++++++++++--------
1 file changed, 44 insertions(+), 13 deletions(-)
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 089f734f851b..09d7745a609f 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -868,6 +868,37 @@ static int cppc_cpufreq_set_boost(struct cpufreq_policy *policy, int state)
* policy->max) rather than by hiding the hardware max in cpuinfo.
* boost_freq_req itself is only created at policy init, not here.
*/
+
+/**
+ * cppc_cpufreq_set_autonomous_perf - Configure performance bounds for
+ * autonomous mode
+ * @policy: cpufreq policy structure
+ *
+ * When autonomous selection is enabled, program MIN_PERF and MAX_PERF
+ * from current policy limits so that the platform uses the correct
+ * performance bounds immediately.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+static int cppc_cpufreq_set_autonomous_perf(struct cpufreq_policy *policy)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ u32 old_min_perf = cpu_data->perf_ctrls.min_perf;
+ u32 old_max_perf = cpu_data->perf_ctrls.max_perf;
+ int ret;
+
+ cppc_cpufreq_update_perf_limits(cpu_data, policy);
+
+ ret = cppc_set_perf(policy->cpu, &cpu_data->perf_ctrls);
+ if (ret) {
+ cpu_data->perf_ctrls.min_perf = old_min_perf;
+ cpu_data->perf_ctrls.max_perf = old_max_perf;
+ return ret;
+ }
+
+ return 0;
+}
+
static void cppc_cpufreq_sync_boost_limits(struct cpufreq_policy *policy,
bool boost_supported)
{
@@ -948,6 +979,18 @@ static void cppc_cpufreq_update_limits(struct cpufreq_policy *policy)
refresh_frequency_limits(policy);
+ /*
+ * Autonomous selection mode uses MIN/MAX performance as runtime
+ * hardware control bounds. Re-program them when highest_perf
+ * changes so that the platform uses the updated bounds.
+ */
+ if (cpu_data->perf_ctrls.auto_sel) {
+ ret = cppc_cpufreq_set_autonomous_perf(policy);
+ if (ret)
+ pr_debug("CPU%d: failed to update autonomous perf: %d\n",
+ policy->cpu, ret);
+ }
+
pr_debug("CPU%d: highest_perf updated %llu -> %llu\n",
policy->cpu, prev_highest_perf, highest_perf);
}
@@ -994,20 +1037,8 @@ static ssize_t store_auto_select(struct cpufreq_policy *policy,
cpu_data->perf_ctrls.auto_sel = val;
if (val) {
- u32 old_min_perf = cpu_data->perf_ctrls.min_perf;
- u32 old_max_perf = cpu_data->perf_ctrls.max_perf;
-
- /*
- * When enabling autonomous selection, program MIN_PERF and
- * MAX_PERF from current policy limits so that the platform
- * uses the correct performance bounds immediately.
- */
- cppc_cpufreq_update_perf_limits(cpu_data, policy);
-
- ret = cppc_set_perf(policy->cpu, &cpu_data->perf_ctrls);
+ ret = cppc_cpufreq_set_autonomous_perf(policy);
if (ret) {
- cpu_data->perf_ctrls.min_perf = old_min_perf;
- cpu_data->perf_ctrls.max_perf = old_max_perf;
cppc_set_auto_sel(policy->cpu, false);
cpu_data->perf_ctrls.auto_sel = false;
return ret;
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v5 3/3] arch_topology: Add topology_update_cpu_capacity() for runtime updates
2026-08-07 6:08 [PATCH v5 0/3] cpufreq: cppc: Handle Highest Performance changes at runtime Xueqin Luo
2026-08-07 6:08 ` [PATCH v5 1/3] cpufreq: cppc: Add update_limits support for Highest Performance changes Xueqin Luo
2026-08-07 6:08 ` [PATCH v5 2/3] cpufreq: cppc: Refactor autonomous perf bounds into helper Xueqin Luo
@ 2026-08-07 6:08 ` Xueqin Luo
2026-08-10 14:33 ` Christian Loehle
2 siblings, 1 reply; 5+ messages in thread
From: Xueqin Luo @ 2026-08-07 6:08 UTC (permalink / raw)
To: Sudeep Holla, Greg Kroah-Hartman, Rafael J . Wysocki,
Danilo Krummrich, Viresh Kumar
Cc: Jie Zhan, Lifeng Zheng, Pierre Gondois, Sumit Gupta, linux-kernel,
driver-core, linux-pm, Xueqin Luo
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 <luoxueqin@kylinos.cn>
---
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
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v5 3/3] arch_topology: Add topology_update_cpu_capacity() for runtime updates
2026-08-07 6:08 ` [PATCH v5 3/3] arch_topology: Add topology_update_cpu_capacity() for runtime updates Xueqin Luo
@ 2026-08-10 14:33 ` Christian Loehle
0 siblings, 0 replies; 5+ messages in thread
From: Christian Loehle @ 2026-08-10 14:33 UTC (permalink / raw)
To: Xueqin Luo, Sudeep Holla, Greg Kroah-Hartman, Rafael J . Wysocki,
Danilo Krummrich, Viresh Kumar, Beata Michalska, Ionela Voinescu,
Dietmar Eggemann
Cc: Jie Zhan, Lifeng Zheng, Pierre Gondois, Sumit Gupta, linux-kernel,
driver-core, linux-pm
On 8/7/26 07:08, Xueqin Luo wrote:
> 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 <luoxueqin@kylinos.cn>
> ---
> 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;
Does this actually work if highest_perf would now be the equivalent for >1024?
> + 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);
>
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-10 14:33 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-07 6:08 [PATCH v5 0/3] cpufreq: cppc: Handle Highest Performance changes at runtime Xueqin Luo
2026-08-07 6:08 ` [PATCH v5 1/3] cpufreq: cppc: Add update_limits support for Highest Performance changes Xueqin Luo
2026-08-07 6:08 ` [PATCH v5 2/3] cpufreq: cppc: Refactor autonomous perf bounds into helper Xueqin Luo
2026-08-07 6:08 ` [PATCH v5 3/3] arch_topology: Add topology_update_cpu_capacity() for runtime updates Xueqin Luo
2026-08-10 14:33 ` Christian Loehle
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox