[PATCH v6 1/3] cpufreq: cppc: Add update_limits support for Highest Performance changes
From: Xueqin Luo
Date: Wed Nov 11 2026 - 04:01:50 EST
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.
Cap runtime changes at the Highest Performance probed at policy
init: any value above it is ignored, as it would allow boost support
to appear after policy init, which the cpufreq core cannot handle
because the boost QoS request is only created at policy init. Within
that bound both decreases and increases are accepted, e.g. a
platform switching back to a performance mode restores the previous
value. A policy initialized without boost cannot gain it at runtime
without core support for creating the QoS request later. Values
that would drop Highest Performance below Nominal are ignored too:
they violate the CPPC invariant and handling them would require
special-casing throughout the driver.
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@xxxxxxxxxx>
---
drivers/cpufreq/cppc_cpufreq.c | 100 +++++++++++++++++++++++++++++++++
include/acpi/cppc_acpi.h | 1 +
2 files changed, 101 insertions(+)
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 957fa7775260..3bca30f5c0d5 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -639,6 +639,8 @@ static struct cppc_cpudata *cppc_cpufreq_get_cpu_data(unsigned int cpu)
goto free_mask;
}
+ cpu_data->init_highest_perf = cpu_data->perf_caps.highest_perf;
+
ret = cppc_get_perf(cpu, &cpu_data->perf_ctrls);
if (ret) {
pr_debug("Err reading CPU%d perf ctrls: ret:%d\n", cpu, ret);
@@ -866,6 +868,103 @@ static int cppc_cpufreq_set_boost(struct cpufreq_policy *policy, int state)
return 0;
}
+/*
+ * Boost being off is enforced through the boost_freq_req QoS request
+ * (capping policy->max) rather than by hiding the hardware maximum in
+ * cpuinfo, so sysfs keeps reflecting Notify(0x85) updates.
+ */
+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);
+
+ policy->cpuinfo.max_freq = highest_freq;
+
+ if (boost_supported && !policy->boost_enabled)
+ qos_freq = nominal_freq;
+ else
+ qos_freq = highest_freq;
+
+ /* boost_freq_req only exists if boost was supported at policy init. */
+ if (freq_qos_request_active(&policy->boost_freq_req)) {
+ ret = freq_qos_update_request(&policy->boost_freq_req,
+ qos_freq);
+ if (ret < 0)
+ pr_err("CPU%d: failed to sync boost QoS: %pe\n",
+ policy->cpu, ERR_PTR(ret));
+ }
+}
+
+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);
+
+ if (policy_is_inactive(policy))
+ return;
+
+ 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;
+
+ /*
+ * Cap increases at the Highest Performance probed at policy
+ * init: a value above it would let boost support appear on
+ * policies that started without boost, which the core cannot
+ * handle (the boost QoS request is created at init only).
+ * Within that bound both directions are accepted, e.g. a
+ * platform switching back to a performance mode.
+ */
+ if (highest_perf > cpu_data->init_highest_perf)
+ return;
+
+ /* A value below Nominal violates the CPPC invariant; ignore it. */
+ if (highest_perf < caps->nominal_perf) {
+ pr_warn("CPU%d: ignoring highest_perf %llu below nominal %u\n",
+ policy->cpu, highest_perf, caps->nominal_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;
@@ -1060,6 +1159,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",
};
diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h
index 5bcdb0e553b9..df43f2f2e79e 100644
--- a/include/acpi/cppc_acpi.h
+++ b/include/acpi/cppc_acpi.h
@@ -163,6 +163,7 @@ struct cppc_cpudata {
struct cppc_perf_caps perf_caps;
struct cppc_perf_ctrls perf_ctrls;
struct cppc_perf_fb_ctrs perf_fb_ctrs;
+ u32 init_highest_perf;
unsigned int shared_type;
cpumask_var_t shared_cpu_map;
};
--
2.43.0