From: Sumit Gupta <sumitg@nvidia.com>
To: <rafael@kernel.org>, <viresh.kumar@linaro.org>,
<pierre.gondois@arm.com>, <christian.loehle@arm.com>,
<ionela.voinescu@arm.com>, <zhenglifeng1@huawei.com>,
<zhanjie9@hisilicon.com>, <lenb@kernel.org>, <ray.huang@amd.com>,
<mario.limonciello@amd.com>, <perry.yuan@amd.com>,
<kprateek.nayak@amd.com>, <maciej.wieczor-retman@intel.com>,
<pawel.chmielewski@intel.com>, <linux-pm@vger.kernel.org>,
<linux-acpi@vger.kernel.org>, <acpica-devel@lists.linux.dev>,
<linux-kernel@vger.kernel.org>
Cc: <linux-tegra@vger.kernel.org>, <treding@nvidia.com>,
<jonathanh@nvidia.com>, <vsethi@nvidia.com>,
<ksitaraman@nvidia.com>, <sanjayc@nvidia.com>, <mochs@nvidia.com>,
<bbasu@nvidia.com>, <sumitg@nvidia.com>
Subject: [PATCH v6 1/5] ACPI: CPPC: Order performance-control updates
Date: Tue, 20 Oct 2026 17:48:42 +0530 [thread overview]
Message-ID: <20261020121846.691218-2-sumitg@nvidia.com> (raw)
In-Reply-To: <20261020121846.691218-1-sumitg@nvidia.com>
From: Christian Loehle <christian.loehle@arm.com>
The ACPI specification requires Minimum Performance to remain below
Maximum Performance. When autonomous selection is disabled, Desired
Performance must also remain within those limits.
cppc_set_perf() writes the three controls in a fixed order. This can expose
an invalid tuple after firmware resets the controls or while policy bounds
move across the current desired value.
Read the live limits. Apply non-atomic updates in three phases: widen the
interval, update Desired Performance, then narrow it. Submit PCC controls
in each phase together. Keep the existing single-command path when the
complete tuple is in PCC, reject invalid requests, and stop after the first
failed access.
Make cppc_cpufreq fast switches include the limits only when they differ
from the last successful request. Invalidate the cache across resume and
after a failed request, since firmware may have reset the controls or an
earlier phase of a failed update may have reached the platform. Publish
slow-path limits only after the complete request succeeds.
At policy teardown, lower Minimum Performance together with Desired
Performance so the final request remains valid. On initialization, clamp
Desired Performance to the live interval left by firmware or an earlier
driver instance.
Fixes: 76531df5e13b ("ACPI: CPPC: Add min and max perf register writing support")
Signed-off-by: Christian Loehle <christian.loehle@arm.com>
[sumitg:
- In cppc_read_perf_limits(), skip an unimplemented MIN or MAX register,
and do not fail when the live MIN is above MAX, as the requested limits
replace it.
- In init(), request the full range when the live MAX is zero or below
MIN, to avoid a cppc_cpufreq probe failure.
- Clamp Desired to MIN and MAX in set_target(), fast_switch() and
store_auto_select(), as cppc_set_perf() rejects it outside them.
cppc_khz_to_perf() can round the minimum frequency to lowest_perf - 1.]
Link: https://lore.kernel.org/all/01d7f07e-839c-428b-833d-713ed2128f4d@nvidia.com/
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/acpi/cppc_acpi.c | 294 ++++++++++++++++++++++++++-------
drivers/cpufreq/cppc_cpufreq.c | 118 +++++++++++--
include/acpi/cppc_acpi.h | 1 +
3 files changed, 337 insertions(+), 76 deletions(-)
diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c
index 80e2e6b32ce3..8d38924e0750 100644
--- a/drivers/acpi/cppc_acpi.c
+++ b/drivers/acpi/cppc_acpi.c
@@ -3471,11 +3471,212 @@ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
}
EXPORT_SYMBOL_GPL(cppc_get_perf);
+enum cppc_perf_update {
+ CPPC_UPDATE_DESIRED = BIT(0),
+ CPPC_UPDATE_MIN = BIT(1),
+ CPPC_UPDATE_MAX = BIT(2),
+};
+
+/* The caller holds pcc_lock for write if either limit is in PCC. */
+static int cppc_read_perf_limits(int cpu, u32 *min_perf, u32 *max_perf)
+{
+ struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
+ struct cpc_register_resource *min_reg = &cpc_desc->cpc_regs[MIN_PERF];
+ struct cpc_register_resource *max_reg = &cpc_desc->cpc_regs[MAX_PERF];
+ u64 min = 0, max = U32_MAX;
+ int ret;
+
+ if (!cpc_is_readable(min_reg) || !cpc_is_readable(max_reg))
+ return -EOPNOTSUPP;
+
+ /* Firmware may have reset the controls, so read the live limits. */
+ if (CPC_IN_PCC(min_reg) || CPC_IN_PCC(max_reg)) {
+ ret = send_pcc_cmd(per_cpu(cpu_pcc_subspace_idx, cpu), CMD_READ);
+ if (ret)
+ return ret;
+ }
+
+ /* An absent optional limit leaves that side of the interval open. */
+ if (CPC_SUPPORTED(min_reg)) {
+ ret = cpc_read(cpu, min_reg, &min);
+ if (ret)
+ return ret;
+ }
+ if (CPC_SUPPORTED(max_reg)) {
+ ret = cpc_read(cpu, max_reg, &max);
+ if (ret)
+ return ret;
+ }
+ if (min > U32_MAX || max > U32_MAX)
+ return -EFAULT;
+ if (min > max)
+ pr_debug("CPU:%d live MIN_PERF %llu above MAX_PERF %llu\n",
+ cpu, min, max);
+
+ *min_perf = min;
+ *max_perf = max;
+ return 0;
+}
+
+static bool cppc_perf_updates_in_pcc(int cpu, unsigned int updates)
+{
+ struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
+
+ return ((updates & CPPC_UPDATE_DESIRED) &&
+ CPC_IN_PCC(&cpc_desc->cpc_regs[DESIRED_PERF])) ||
+ ((updates & CPPC_UPDATE_MIN) &&
+ CPC_IN_PCC(&cpc_desc->cpc_regs[MIN_PERF])) ||
+ ((updates & CPPC_UPDATE_MAX) &&
+ CPC_IN_PCC(&cpc_desc->cpc_regs[MAX_PERF]));
+}
+
+/* The caller holds pcc_lock for write if an updated control is in PCC. */
+static int cppc_write_perf_controls(int cpu,
+ struct cppc_perf_ctrls *perf_ctrls,
+ unsigned int updates, bool sync_desired)
+{
+ struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
+ struct cpc_register_resource *desired_reg =
+ &cpc_desc->cpc_regs[DESIRED_PERF];
+ struct cpc_register_resource *min_reg = &cpc_desc->cpc_regs[MIN_PERF];
+ struct cpc_register_resource *max_reg = &cpc_desc->cpc_regs[MAX_PERF];
+ struct cppc_pcc_data *pcc_ss_data;
+ int pcc_ss_id, ret;
+ bool pcc_update = false;
+
+ /* Direct writes precede the PCC command in each ordered phase. */
+ if ((updates & CPPC_UPDATE_DESIRED) && !CPC_IN_PCC(desired_reg)) {
+ ret = cpc_write(cpu, desired_reg, perf_ctrls->desired_perf);
+ if (ret)
+ return ret;
+ }
+ if ((updates & CPPC_UPDATE_MIN) && !CPC_IN_PCC(min_reg)) {
+ ret = cpc_write(cpu, min_reg, perf_ctrls->min_perf);
+ if (ret)
+ return ret;
+ }
+ if ((updates & CPPC_UPDATE_MAX) && !CPC_IN_PCC(max_reg)) {
+ ret = cpc_write(cpu, max_reg, perf_ctrls->max_perf);
+ if (ret)
+ return ret;
+ }
+
+ /* Keep Desired Performance valid in every intermediate PCC command. */
+ if (((updates & CPPC_UPDATE_DESIRED) || sync_desired) &&
+ CPC_IN_PCC(desired_reg)) {
+ ret = cpc_write(cpu, desired_reg, perf_ctrls->desired_perf);
+ if (ret)
+ return ret;
+ pcc_update = true;
+ }
+ if ((updates & CPPC_UPDATE_MIN) && CPC_IN_PCC(min_reg)) {
+ ret = cpc_write(cpu, min_reg, perf_ctrls->min_perf);
+ if (ret)
+ return ret;
+ pcc_update = true;
+ }
+ if ((updates & CPPC_UPDATE_MAX) && CPC_IN_PCC(max_reg)) {
+ ret = cpc_write(cpu, max_reg, perf_ctrls->max_perf);
+ if (ret)
+ return ret;
+ pcc_update = true;
+ }
+ if (!pcc_update)
+ return 0;
+
+ pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu);
+ pcc_ss_data = pcc_data[pcc_ss_id];
+ WRITE_ONCE(pcc_ss_data->pending_pcc_write_cmd, true);
+ cpc_desc->write_cmd_id = pcc_ss_data->pcc_write_cnt;
+ cpc_desc->write_cmd_status = 0;
+ return send_pcc_cmd(pcc_ss_id, CMD_WRITE);
+}
+
+/*
+ * Move from the live interval to the requested interval without exposing an
+ * invalid control tuple: widen, update Desired Performance, then narrow.
+ */
+static int cppc_write_ordered_perf(int cpu,
+ struct cppc_perf_ctrls *perf_ctrls,
+ bool desired_update, bool min_update,
+ bool max_update)
+{
+ struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
+ struct cpc_register_resource *desired_reg =
+ &cpc_desc->cpc_regs[DESIRED_PERF];
+ unsigned int widen = 0, narrow = 0;
+ u32 current_min, current_max;
+ u32 target_min, target_max;
+ bool desired_done = false;
+ bool sync_desired;
+ int ret;
+
+ ret = cppc_read_perf_limits(cpu, ¤t_min, ¤t_max);
+ if (ret)
+ return ret;
+
+ target_min = min_update ? perf_ctrls->min_perf : current_min;
+ target_max = max_update ? perf_ctrls->max_perf : current_max;
+
+ /*
+ * Check only the new interval. If the live one is inverted, this
+ * update fixes it.
+ */
+ if (target_min > target_max)
+ return -EINVAL;
+
+ if (desired_update && perf_ctrls->desired_perf &&
+ (perf_ctrls->desired_perf < target_min ||
+ perf_ctrls->desired_perf > target_max))
+ return -EINVAL;
+
+ if (min_update) {
+ if (perf_ctrls->min_perf < current_min)
+ widen |= CPPC_UPDATE_MIN;
+ else
+ narrow |= CPPC_UPDATE_MIN;
+ }
+ if (max_update) {
+ if (perf_ctrls->max_perf > current_max)
+ widen |= CPPC_UPDATE_MAX;
+ else
+ narrow |= CPPC_UPDATE_MAX;
+ }
+
+ if (widen) {
+ sync_desired = desired_update && CPC_IN_PCC(desired_reg) &&
+ cppc_perf_updates_in_pcc(cpu, widen);
+ ret = cppc_write_perf_controls(cpu, perf_ctrls, widen,
+ sync_desired);
+ if (ret)
+ return ret;
+ desired_done = sync_desired;
+ }
+
+ if (desired_update && !desired_done) {
+ ret = cppc_write_perf_controls(cpu, perf_ctrls,
+ CPPC_UPDATE_DESIRED, false);
+ if (ret)
+ return ret;
+ }
+
+ if (!narrow)
+ return 0;
+
+ sync_desired = desired_update && CPC_IN_PCC(desired_reg) &&
+ cppc_perf_updates_in_pcc(cpu, narrow);
+ return cppc_write_perf_controls(cpu, perf_ctrls, narrow, sync_desired);
+}
+
/**
* cppc_set_perf - Set a CPU's performance controls.
* @cpu: CPU for which to set performance controls.
* @perf_ctrls: ptr to cppc_perf_ctrls. See cppc_acpi.h
*
+ * Callers must serialize updates to the same CPU's performance controls.
+ * Limit updates preserve MIN <= MAX, including intermediate states on
+ * directly accessed registers. An error may leave an earlier write applied.
+ *
* Return: 0 for success, -ERRNO otherwise.
*/
int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
@@ -3502,10 +3703,22 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
(perf_ctrls->min_perf || perf_ctrls->min_perf_valid);
max_update = cpc_is_writable(max_perf_reg) &&
perf_ctrls->max_perf;
+
+ if (min_update && max_update &&
+ perf_ctrls->min_perf > perf_ctrls->max_perf)
+ return -EINVAL;
+
+ if (desired_update && perf_ctrls->desired_perf &&
+ min_update && max_update &&
+ (perf_ctrls->desired_perf < perf_ctrls->min_perf ||
+ perf_ctrls->desired_perf > perf_ctrls->max_perf))
+ return -EINVAL;
+
desired_pcc = desired_update && CPC_IN_PCC(desired_reg);
min_pcc = min_update && CPC_IN_PCC(min_perf_reg);
max_pcc = max_update && CPC_IN_PCC(max_perf_reg);
pcc_update = desired_pcc || min_pcc || max_pcc;
+
pcc_layout = (cpc_is_writable(desired_reg) && CPC_IN_PCC(desired_reg)) ||
(cpc_is_writable(min_perf_reg) && CPC_IN_PCC(min_perf_reg)) ||
(cpc_is_writable(max_perf_reg) && CPC_IN_PCC(max_perf_reg));
@@ -3531,9 +3744,10 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
* A mixed layout cannot batch fallible direct writes safely: another
* CPU's staged PCC values may no longer match if a direct write fails.
* Serialize the complete mixed transaction and drain an older batch
- * before changing a direct control.
+ * before changing a direct control. A single PCC limit update also
+ * needs the exclusive lock to read and check the unchanged limit.
*/
- if (mixed_layout) {
+ if (mixed_layout || (pcc_update && min_update != max_update)) {
down_write(&pcc_ss_data->pcc_lock);
if (pcc_ss_data->pending_pcc_write_cmd) {
ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE);
@@ -3547,50 +3761,14 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
goto out_mixed_unlock;
}
- if (desired_update && !desired_pcc) {
- ret = cpc_write(cpu, desired_reg,
- perf_ctrls->desired_perf);
- if (ret)
- goto out_mixed_unlock;
- }
- if (min_update && !min_pcc) {
- ret = cpc_write(cpu, min_perf_reg,
- perf_ctrls->min_perf);
- if (ret)
- goto out_mixed_unlock;
- }
- if (max_update && !max_pcc) {
- ret = cpc_write(cpu, max_perf_reg,
- perf_ctrls->max_perf);
- if (ret)
- goto out_mixed_unlock;
- }
-
- if (desired_pcc) {
- ret = cpc_write(cpu, desired_reg,
- perf_ctrls->desired_perf);
- if (ret)
- goto out_mixed_unlock;
- }
- if (min_pcc) {
- ret = cpc_write(cpu, min_perf_reg,
- perf_ctrls->min_perf);
- if (ret)
- goto out_mixed_unlock;
- }
- if (max_pcc) {
- ret = cpc_write(cpu, max_perf_reg,
- perf_ctrls->max_perf);
- if (ret)
- goto out_mixed_unlock;
- }
-
- if (pcc_update) {
- WRITE_ONCE(pcc_ss_data->pending_pcc_write_cmd, true);
- cpc_desc->write_cmd_id = pcc_ss_data->pcc_write_cnt;
- cpc_desc->write_cmd_status = 0;
- ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE);
- }
+ if (min_update || max_update)
+ ret = cppc_write_ordered_perf(cpu, perf_ctrls,
+ desired_update,
+ min_update, max_update);
+ else if (desired_update)
+ ret = cppc_write_perf_controls(cpu, perf_ctrls,
+ CPPC_UPDATE_DESIRED,
+ false);
out_mixed_unlock:
up_write(&pcc_ss_data->pcc_lock);
@@ -3599,22 +3777,14 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls)
/* A request without PCC updates has no payload to coordinate. */
if (!pcc_update) {
- if (desired_update) {
- ret = cpc_write(cpu, desired_reg,
- perf_ctrls->desired_perf);
- if (ret)
- return ret;
- }
- if (min_update) {
- ret = cpc_write(cpu, min_perf_reg,
- perf_ctrls->min_perf);
- if (ret)
- return ret;
- }
- if (max_update)
- ret = cpc_write(cpu, max_perf_reg,
- perf_ctrls->max_perf);
- return ret;
+ if (min_update || max_update)
+ return cppc_write_ordered_perf(cpu, perf_ctrls,
+ desired_update,
+ min_update, max_update);
+ if (desired_update)
+ return cpc_write(cpu, desired_reg,
+ perf_ctrls->desired_perf);
+ return 0;
}
down_read(&pcc_ss_data->pcc_lock); /* BEGIN Phase-I */
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 4ea444ff889e..709363e6d98d 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -335,47 +335,92 @@ static int cppc_cpufreq_set_target(struct cpufreq_policy *policy,
unsigned int relation)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
+ struct cppc_perf_ctrls perf_ctrls = {};
unsigned int cpu = policy->cpu;
struct cpufreq_freqs freqs;
+ u32 desired_perf;
int ret = 0;
- cpu_data->perf_ctrls.desired_perf =
- cppc_khz_to_perf(&cpu_data->perf_caps, target_freq);
- cppc_cpufreq_update_perf_limits(cpu_data, policy);
+ desired_perf = cppc_khz_to_perf(&cpu_data->perf_caps, target_freq);
+ cppc_cpufreq_get_perf_limits(cpu_data, policy, &perf_ctrls.min_perf,
+ &perf_ctrls.max_perf);
+ perf_ctrls.min_perf_valid = true;
+
+ /*
+ * cppc_set_perf() rejects a Desired value outside [MIN, MAX].
+ * Rounding can put it just outside at the ends of the range. The
+ * policy limits may also change after the governor picked
+ * target_freq.
+ */
+ perf_ctrls.desired_perf = clamp(desired_perf, perf_ctrls.min_perf,
+ perf_ctrls.max_perf);
freqs.old = policy->cur;
freqs.new = target_freq;
cpufreq_freq_transition_begin(policy, &freqs);
- ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
+ ret = cppc_set_perf(cpu, &perf_ctrls);
cpufreq_freq_transition_end(policy, &freqs, ret != 0);
- if (ret)
+ if (ret) {
+ WRITE_ONCE(cpu_data->perf_limits_valid, false);
pr_debug("Failed to set target on CPU:%d. ret:%d\n",
cpu, ret);
+ return ret;
+ }
- return ret;
+ WRITE_ONCE(cpu_data->perf_ctrls.desired_perf,
+ perf_ctrls.desired_perf);
+ WRITE_ONCE(cpu_data->perf_ctrls.min_perf, perf_ctrls.min_perf);
+ WRITE_ONCE(cpu_data->perf_ctrls.max_perf, perf_ctrls.max_perf);
+ WRITE_ONCE(cpu_data->perf_limits_valid, true);
+
+ return 0;
}
static unsigned int cppc_cpufreq_fast_switch(struct cpufreq_policy *policy,
unsigned int target_freq)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
+ struct cppc_perf_ctrls perf_ctrls = {};
unsigned int cpu = policy->cpu;
- u32 desired_perf;
+ u32 desired_perf, min_perf, max_perf;
+ bool update_limits;
int ret;
desired_perf = cppc_khz_to_perf(&cpu_data->perf_caps, target_freq);
- cpu_data->perf_ctrls.desired_perf = desired_perf;
- cppc_cpufreq_update_perf_limits(cpu_data, policy);
+ cppc_cpufreq_get_perf_limits(cpu_data, policy, &min_perf, &max_perf);
- ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
+ /*
+ * cppc_set_perf() rejects a Desired value outside [MIN, MAX], and
+ * rounding or a policy limit change can put it there.
+ */
+ desired_perf = clamp(desired_perf, min_perf, max_perf);
+ perf_ctrls.desired_perf = desired_perf;
+ update_limits = !READ_ONCE(cpu_data->perf_limits_valid) ||
+ min_perf != READ_ONCE(cpu_data->perf_ctrls.min_perf) ||
+ max_perf != READ_ONCE(cpu_data->perf_ctrls.max_perf);
+ if (update_limits) {
+ perf_ctrls.min_perf = min_perf;
+ perf_ctrls.max_perf = max_perf;
+ perf_ctrls.min_perf_valid = true;
+ }
+
+ ret = cppc_set_perf(cpu, &perf_ctrls);
if (ret) {
+ WRITE_ONCE(cpu_data->perf_limits_valid, false);
pr_debug("Failed to set target on CPU:%d. ret:%d\n",
cpu, ret);
return 0;
}
+ WRITE_ONCE(cpu_data->perf_ctrls.desired_perf, desired_perf);
+ if (update_limits) {
+ WRITE_ONCE(cpu_data->perf_ctrls.min_perf, min_perf);
+ WRITE_ONCE(cpu_data->perf_ctrls.max_perf, max_perf);
+ WRITE_ONCE(cpu_data->perf_limits_valid, true);
+ }
+
return target_freq;
}
@@ -670,6 +715,7 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
unsigned int cpu = policy->cpu;
struct cppc_cpudata *cpu_data;
struct cppc_perf_caps *caps;
+ u32 desired_perf;
int ret;
cpu_data = cppc_cpufreq_get_cpu_data(cpu);
@@ -728,16 +774,39 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
if (caps->highest_perf > caps->nominal_perf)
policy->boost_supported = true;
- /* Set policy->cur to max now. The governors will adjust later. */
- policy->cur = cppc_perf_to_khz(caps, caps->highest_perf);
- cpu_data->perf_ctrls.desired_perf = caps->highest_perf;
+ /*
+ * The request starts from the live limits, which may be unusable.
+ * Firmware may leave them zeroed. An interrupted earlier update may
+ * have left MIN above MAX. cppc_set_perf() would reject either, so
+ * request the full range instead.
+ */
+ if (!cpu_data->perf_ctrls.max_perf ||
+ cpu_data->perf_ctrls.min_perf > cpu_data->perf_ctrls.max_perf) {
+ cpu_data->perf_ctrls.min_perf = caps->lowest_perf;
+ cpu_data->perf_ctrls.min_perf_valid = true;
+ cpu_data->perf_ctrls.max_perf = caps->highest_perf;
+ }
+
+ /*
+ * Keep the initial request within controls left by an earlier driver
+ * instance or firmware. The governor will update the full tuple later.
+ */
+ desired_perf = caps->highest_perf;
+ if (cpu_data->perf_ctrls.max_perf)
+ desired_perf = min(desired_perf, cpu_data->perf_ctrls.max_perf);
+ if (cpu_data->perf_ctrls.min_perf_valid)
+ desired_perf = max(desired_perf, cpu_data->perf_ctrls.min_perf);
+
+ policy->cur = cppc_perf_to_khz(caps, desired_perf);
+ cpu_data->perf_ctrls.desired_perf = desired_perf;
ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
if (ret) {
pr_debug("Err setting perf value:%d on CPU:%d. ret:%d\n",
- caps->highest_perf, cpu, ret);
+ desired_perf, cpu, ret);
goto out;
}
+ cpu_data->perf_limits_valid = true;
cppc_cpufreq_cpu_fie_init(policy);
return 0;
@@ -757,6 +826,8 @@ static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
cppc_cpufreq_cpu_fie_exit(policy);
cpu_data->perf_ctrls.desired_perf = caps->lowest_perf;
+ cpu_data->perf_ctrls.min_perf = caps->lowest_perf;
+ cpu_data->perf_ctrls.min_perf_valid = true;
ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
if (ret)
@@ -766,6 +837,16 @@ static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
cppc_cpufreq_put_cpu_data(policy);
}
+static int cppc_cpufreq_cpu_resume(struct cpufreq_policy *policy)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+
+ /* The platform may have reset the controls during suspend. */
+ WRITE_ONCE(cpu_data->perf_limits_valid, false);
+
+ return 0;
+}
+
static inline u64 get_delta(u64 t1, u64 t0)
{
if (t1 > t0 || t0 > ~(u32)0)
@@ -910,6 +991,7 @@ static ssize_t store_auto_select(struct cpufreq_policy *policy,
cpu_data->perf_ctrls.auto_sel = val;
if (val) {
+ u32 old_desired_perf = cpu_data->perf_ctrls.desired_perf;
u32 old_min_perf = cpu_data->perf_ctrls.min_perf;
u32 old_max_perf = cpu_data->perf_ctrls.max_perf;
@@ -919,15 +1001,22 @@ static ssize_t store_auto_select(struct cpufreq_policy *policy,
* uses the correct performance bounds immediately.
*/
cppc_cpufreq_update_perf_limits(cpu_data, policy);
+ /* The cached Desired may be outside the new MIN and MAX. */
+ cpu_data->perf_ctrls.desired_perf =
+ clamp(old_desired_perf, cpu_data->perf_ctrls.min_perf,
+ cpu_data->perf_ctrls.max_perf);
ret = cppc_set_perf(policy->cpu, &cpu_data->perf_ctrls);
if (ret) {
+ WRITE_ONCE(cpu_data->perf_limits_valid, false);
+ cpu_data->perf_ctrls.desired_perf = old_desired_perf;
cpu_data->perf_ctrls.min_perf = old_min_perf;
cpu_data->perf_ctrls.max_perf = old_max_perf;
cppc_set_auto_sel(policy->cpu, old_auto_sel);
cpu_data->perf_ctrls.auto_sel = old_auto_sel;
return ret;
}
+ WRITE_ONCE(cpu_data->perf_limits_valid, true);
}
return count;
@@ -1060,6 +1149,7 @@ static struct cpufreq_driver cppc_cpufreq_driver = {
.fast_switch = cppc_cpufreq_fast_switch,
.init = cppc_cpufreq_cpu_init,
.exit = cppc_cpufreq_cpu_exit,
+ .resume = cppc_cpufreq_cpu_resume,
.set_boost = cppc_cpufreq_set_boost,
.attr = cppc_cpufreq_attr,
.name = "cppc_cpufreq",
diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h
index 5bcdb0e553b9..4495ee4ab71e 100644
--- a/include/acpi/cppc_acpi.h
+++ b/include/acpi/cppc_acpi.h
@@ -165,6 +165,7 @@ struct cppc_cpudata {
struct cppc_perf_fb_ctrs perf_fb_ctrs;
unsigned int shared_type;
cpumask_var_t shared_cpu_map;
+ bool perf_limits_valid;
};
#ifdef CONFIG_ACPI_CPPC_LIB
--
2.34.1
next prev parent reply other threads:[~2026-10-20 12:19 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-20 12:18 [PATCH v6 0/5] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload Sumit Gupta
2026-10-20 12:18 ` Sumit Gupta [this message]
2026-10-20 12:18 ` [PATCH v6 2/5] cpufreq: CPPC: Keep the policy across CPU hotplug Sumit Gupta
2026-10-20 12:18 ` [PATCH v6 3/5] ACPI: CPPC: Make autonomous selection helpers take a u64 Sumit Gupta
2026-10-20 12:18 ` [PATCH v6 4/5] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload Sumit Gupta
2026-10-20 12:18 ` [PATCH v6 5/5] cpufreq: CPPC: Preserve OSPM-set registers across suspend/resume Sumit Gupta
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261020121846.691218-2-sumitg@nvidia.com \
--to=sumitg@nvidia.com \
--cc=acpica-devel@lists.linux.dev \
--cc=bbasu@nvidia.com \
--cc=christian.loehle@arm.com \
--cc=ionela.voinescu@arm.com \
--cc=jonathanh@nvidia.com \
--cc=kprateek.nayak@amd.com \
--cc=ksitaraman@nvidia.com \
--cc=lenb@kernel.org \
--cc=linux-acpi@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-pm@vger.kernel.org \
--cc=linux-tegra@vger.kernel.org \
--cc=maciej.wieczor-retman@intel.com \
--cc=mario.limonciello@amd.com \
--cc=mochs@nvidia.com \
--cc=pawel.chmielewski@intel.com \
--cc=perry.yuan@amd.com \
--cc=pierre.gondois@arm.com \
--cc=rafael@kernel.org \
--cc=ray.huang@amd.com \
--cc=sanjayc@nvidia.com \
--cc=treding@nvidia.com \
--cc=viresh.kumar@linaro.org \
--cc=vsethi@nvidia.com \
--cc=zhanjie9@hisilicon.com \
--cc=zhenglifeng1@huawei.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox