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>,
<saket.dumbre@intel.com>, <ray.huang@amd.com>,
<mario.limonciello@amd.com>, <perry.yuan@amd.com>,
<kprateek.nayak@amd.com>, <linux-kernel@vger.kernel.org>,
<linux-pm@vger.kernel.org>, <linux-acpi@vger.kernel.org>,
<acpica-devel@lists.linux.dev>, <linux-tegra@vger.kernel.org>
Cc: <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 v4 3/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload
Date: Fri, 7 Aug 2026 01:38:56 +0530 [thread overview]
Message-ID: <20260806200857.601152-4-sumitg@nvidia.com> (raw)
In-Reply-To: <20260806200857.601152-1-sumitg@nvidia.com>
Values written to OSPM-set CPPC registers via sysfs can be lost in two
ways:
- Across CPU hotplug: the platform may reset a CPU's registers while it
is offline.
- On driver unload: the value the driver wrote is left in the register
instead of returning to its pre-driver state.
Add a small table-driven mechanism that handles both:
- On init(), capture each register's firmware value before the
driver programs anything.
- On offline(), read back each register's current value (whatever was
last set via sysfs) so it can be reapplied, then restore the firmware
value.
- On online(), reapply the value captured at offline() after the
performance request is re-established.
- On exit(), nothing is needed, as the core calls offline() first, which
already restored the firmware values.
Cover the Autonomous Selection (auto_sel), Energy Performance Preference
(EPP) and Autonomous Activity Window (auto_act_window) registers. Writes
to EPP and auto_act_window only have meaning while auto_sel is enabled,
so write auto_sel before them when enabling it and after them when
disabling it. If autonomous selection is already disabled, those writes
may be ignored.
Suggested-by: Pierre Gondois <pierre.gondois@arm.com>
Link: https://lore.kernel.org/all/86780f97-29ee-4a72-b311-38c89434b707@arm.com/
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/cpufreq/cppc_cpufreq.c | 191 ++++++++++++++++++++++++++++++++-
1 file changed, 190 insertions(+), 1 deletion(-)
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index b50d3f893b1d..f8628b1fc6b7 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -28,6 +28,183 @@
static struct cpufreq_driver cppc_cpufreq_driver;
+/*
+ * OSPM-set CPPC registers tracked for save/restore. A value set via sysfs is
+ * reapplied from online() across CPU hotplug, and the firmware value is
+ * restored from offline().
+ *
+ * Autonomous Selection (auto_sel) is kept first, as writes to the registers
+ * listed after it only have meaning while autonomous selection is enabled.
+ */
+enum cppc_saved_reg_id {
+ CPPC_SAVED_AUTO_SEL,
+ CPPC_SAVED_EPP,
+ CPPC_SAVED_AUTO_ACT_WINDOW,
+ CPPC_NR_SAVED_REGS,
+};
+
+struct cppc_saved_reg {
+ const char *name;
+ int (*get)(int cpu, u64 *val);
+ int (*set)(int cpu, u64 val);
+};
+
+static const struct cppc_saved_reg cppc_saved_regs[CPPC_NR_SAVED_REGS] = {
+ [CPPC_SAVED_AUTO_SEL] = {
+ .name = "auto_sel",
+ .get = cppc_get_auto_sel,
+ .set = cppc_set_auto_sel,
+ },
+ [CPPC_SAVED_EPP] = {
+ .name = "epp",
+ .get = cppc_get_epp_perf,
+ .set = cppc_set_epp,
+ },
+ [CPPC_SAVED_AUTO_ACT_WINDOW] = {
+ .name = "auto_act_window",
+ .get = cppc_get_auto_act_window,
+ .set = cppc_set_auto_act_window,
+ },
+};
+
+enum cppc_saved_type {
+ CPPC_SAVED_FIRMWARE,
+ CPPC_SAVED_REQUESTED,
+};
+
+/*
+ * Per-policy values saved for each register in cppc_saved_regs[]:
+ * firmware_val - value before the driver touched it, captured at init()
+ * and restored while the policy is offline. U64_MAX if it
+ * could not be read
+ * requested_val - value in effect when the policy last went offline,
+ * reapplied at online(). U64_MAX if none
+ */
+struct cppc_saved_vals {
+ u64 firmware_val;
+ u64 requested_val;
+};
+
+struct cppc_policy_state {
+ struct cppc_saved_vals regs[CPPC_NR_SAVED_REGS];
+};
+
+static DEFINE_PER_CPU(struct cppc_policy_state, cppc_policy_state);
+
+/*
+ * Per-policy state is kept in the per-CPU variable of the first CPU the policy
+ * manages. related_cpus (the policy's full set of CPUs) never changes while the
+ * policy exists, so this CPU (unlike policy->cpu) stays the same across CPU
+ * hotplug, and every callback reaches the same copy.
+ */
+static struct cppc_policy_state *
+cppc_cpufreq_policy_state(struct cpufreq_policy *policy)
+{
+ const struct cpumask *policy_cpus = policy->related_cpus;
+
+ /*
+ * related_cpus is empty until the core fills it in after init(), so
+ * fall back to policy->cpus, which has the same first CPU.
+ */
+ if (cpumask_empty(policy_cpus))
+ policy_cpus = policy->cpus;
+
+ return &per_cpu(cppc_policy_state, cpumask_first(policy_cpus));
+}
+
+/*
+ * Save each register's current value. init() saves the firmware value, before
+ * the driver programs anything, and offline() saves the requested value, to
+ * reapply at online().
+ */
+static void cppc_cpufreq_save_regs(struct cpufreq_policy *policy,
+ enum cppc_saved_type saved_type)
+{
+ struct cppc_policy_state *st = cppc_cpufreq_policy_state(policy);
+ unsigned int cpu = policy->cpu;
+ u64 val;
+ int i;
+
+ for (i = 0; i < CPPC_NR_SAVED_REGS; i++) {
+ if (cppc_saved_regs[i].get(cpu, &val))
+ val = U64_MAX;
+
+ if (saved_type == CPPC_SAVED_FIRMWARE) {
+ st->regs[i].firmware_val = val;
+ st->regs[i].requested_val = U64_MAX;
+ } else {
+ st->regs[i].requested_val = val;
+ }
+ }
+}
+
+static u64 cppc_cpufreq_saved_reg_value(const struct cppc_saved_vals *st,
+ enum cppc_saved_reg_id reg,
+ enum cppc_saved_type saved_type)
+{
+ if (saved_type == CPPC_SAVED_FIRMWARE)
+ return st[reg].firmware_val;
+
+ return st[reg].requested_val;
+}
+
+/*
+ * Write one tracked register, skipping values that were never saved and
+ * registers the platform does not allow writing.
+ */
+static void cppc_cpufreq_write_saved_reg(unsigned int cpu,
+ enum cppc_saved_reg_id reg, u64 val,
+ enum cppc_saved_type saved_type)
+{
+ const char *op = (saved_type == CPPC_SAVED_FIRMWARE) ?
+ "restore firmware" : "reapply saved";
+ int ret;
+
+ if (val == U64_MAX)
+ return;
+
+ ret = cppc_saved_regs[reg].set(cpu, val);
+ if (ret == -EOPNOTSUPP)
+ return;
+ if (ret)
+ pr_debug("Failed to %s %s=%llu on CPU%u (%d)\n", op,
+ cppc_saved_regs[reg].name, val, cpu, ret);
+}
+
+/*
+ * Apply the saved firmware or requested value to each tracked register.
+ *
+ * Write auto_sel first when the value being applied enables autonomous
+ * selection and last when it disables it, so the writes to the dependent
+ * registers can still take effect. If autonomous selection is already
+ * disabled, those writes are best effort. Do not enable it temporarily to
+ * force them through.
+ */
+static void cppc_cpufreq_apply_saved_regs(struct cpufreq_policy *policy,
+ enum cppc_saved_type saved_type)
+{
+ const struct cppc_saved_vals *st = cppc_cpufreq_policy_state(policy)->regs;
+ unsigned int cpu = policy->cpu;
+ u64 auto_sel, val;
+ int i;
+
+ auto_sel = cppc_cpufreq_saved_reg_value(st, CPPC_SAVED_AUTO_SEL,
+ saved_type);
+
+ if (auto_sel)
+ cppc_cpufreq_write_saved_reg(cpu, CPPC_SAVED_AUTO_SEL, auto_sel,
+ saved_type);
+
+ for (i = CPPC_SAVED_AUTO_SEL + 1; i < CPPC_NR_SAVED_REGS; i++) {
+ val = cppc_cpufreq_saved_reg_value(st, i, saved_type);
+ cppc_cpufreq_write_saved_reg(cpu, i, val, saved_type);
+ }
+
+ if (!auto_sel)
+ cppc_cpufreq_write_saved_reg(cpu, CPPC_SAVED_AUTO_SEL, auto_sel,
+ saved_type);
+}
+
#ifdef CONFIG_ACPI_CPPC_CPUFREQ_FIE
static enum {
FIE_UNSET = -1,
@@ -747,6 +924,8 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
policy->cur = cppc_perf_to_khz(caps, caps->highest_perf);
cpu_data->perf_ctrls.desired_perf = caps->highest_perf;
+ cppc_cpufreq_save_regs(policy, CPPC_SAVED_FIRMWARE);
+
ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
if (ret) {
pr_debug("Err setting perf value:%d on CPU:%d. ret:%d\n",
@@ -773,6 +952,10 @@ static int cppc_cpufreq_cpu_offline(struct cpufreq_policy *policy)
unsigned int cpu = policy->cpu;
int ret;
+ /* Leave the platform in its pre-driver state while offline. */
+ cppc_cpufreq_save_regs(policy, CPPC_SAVED_REQUESTED);
+ cppc_cpufreq_apply_saved_regs(policy, CPPC_SAVED_FIRMWARE);
+
/*
* Request the lowest desired performance while the policy has no online
* CPU. Zeroing MIN and MAX makes cppc_set_perf() leave them unchanged.
@@ -822,6 +1005,8 @@ cppc_cpufreq_prepare_perf_restore(unsigned int cpu,
* or the core would free the policy and leave the CPU without cpufreq. The
* governor redoes the control writes, so they are best effort, unlike the
* enable, which only a later online() can retry.
+ *
+ * Also reapply the OSPM-set registers that offline() reset to firmware values.
*/
static int cppc_cpufreq_cpu_online(struct cpufreq_policy *policy)
{
@@ -854,9 +1039,13 @@ static int cppc_cpufreq_cpu_online(struct cpufreq_policy *policy)
cpu, ret);
ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
- if (ret)
+ if (ret) {
pr_debug("Failed to reapply perf request on CPU%u (%d)\n",
cpu, ret);
+ return 0;
+ }
+
+ cppc_cpufreq_apply_saved_regs(policy, CPPC_SAVED_REQUESTED);
return 0;
}
--
2.34.1
next prev parent reply other threads:[~2026-08-06 20:10 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-06 20:08 [PATCH v4 0/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload Sumit Gupta
2026-08-06 20:08 ` [PATCH v4 1/4] cpufreq: CPPC: Keep the policy across CPU hotplug Sumit Gupta
2026-08-06 20:08 ` [PATCH v4 2/4] ACPI: CPPC: Make autonomous selection helpers take a u64 Sumit Gupta
2026-08-06 20:08 ` Sumit Gupta [this message]
2026-08-06 20:08 ` [PATCH v4 4/4] 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=20260806200857.601152-4-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=mario.limonciello@amd.com \
--cc=mochs@nvidia.com \
--cc=perry.yuan@amd.com \
--cc=pierre.gondois@arm.com \
--cc=rafael@kernel.org \
--cc=ray.huang@amd.com \
--cc=saket.dumbre@intel.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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.