* [PATCH v4 0/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload
@ 2026-08-06 20:08 Sumit Gupta
2026-08-06 20:08 ` [PATCH v4 1/4] cpufreq: CPPC: Keep the policy across CPU hotplug Sumit Gupta
` (3 more replies)
0 siblings, 4 replies; 5+ messages in thread
From: Sumit Gupta @ 2026-08-06 20:08 UTC (permalink / raw)
To: rafael, viresh.kumar, pierre.gondois, christian.loehle,
ionela.voinescu, zhenglifeng1, zhanjie9, lenb, saket.dumbre,
ray.huang, mario.limonciello, perry.yuan, kprateek.nayak,
linux-kernel, linux-pm, linux-acpi, acpica-devel, linux-tegra
Cc: treding, jonathanh, vsethi, ksitaraman, sanjayc, mochs, bbasu,
sumitg
This series keeps the CPPC cpufreq policy alive across CPU hotplug and
preserves the OSPM-set CPPC registers (Energy Performance Preference,
Autonomous Activity Window, Autonomous Selection - set via sysfs).
Without online()/offline() callbacks, the core tears a policy down when
its last CPU goes offline and rebuilds it on the way back, re-reading the
CPPC capabilities each time. The values written to these registers can
be lost:
- Across CPU hotplug or suspend/resume: the platform may reset them
while the CPU is offline.
- On driver unload: the driver-written value is left in the register
instead of returning to its pre-driver state.
Handle these with:
- Patch 1: adds online()/offline() callbacks so the core keeps policy
alive across CPU hotplug instead of tearing it down and rebuilding it.
- Patch 2: makes the autonomous selection register helpers take a u64.
- Patch 3: adds a table-driven mechanism that captures each register's
firmware value at init(), restores it from offline(), and reapplies
the OSPM-set value from online().
- Patch 4: extends the same save/restore to system suspend/resume.
v3[3] -> v4:
- Patch 1:
- offline() parks the perf request at lowest_perf, as exit() did.
- online() resyncs the frequency invariance counters.
- raise MAX before the perf restore when the saved MIN is above it.
- Patch 3:
- write auto_sel first when enabling it and last when disabling it.
- replace the four save/restore helpers into save_regs() and
apply_saved_regs(), each taking the firmware or requested type.
- keep the per-policy saved values in one struct, and name each
register for the pr_debug diagnostics.
- Patch 4:
- suspend() also restores the firmware values and flags it, so
offline() skips them and resume() only handles still-online policies.
Sumit Gupta (4):
cpufreq: CPPC: Keep the policy across CPU hotplug
ACPI: CPPC: Make autonomous selection helpers take a u64
cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload
cpufreq: CPPC: Preserve OSPM-set registers across suspend/resume
drivers/acpi/cppc_acpi.c | 20 +-
drivers/cpufreq/amd-pstate.c | 2 +-
drivers/cpufreq/cppc_cpufreq.c | 369 ++++++++++++++++++++++++++++++++-
include/acpi/cppc_acpi.h | 8 +-
4 files changed, 376 insertions(+), 23 deletions(-)
[1] v1: https://lore.kernel.org/lkml/20260623095403.3407436-1-sumitg@nvidia.com/
[2] v2: https://lore.kernel.org/lkml/20260716153820.2007095-1-sumitg@nvidia.com/
[3] v3: https://lore.kernel.org/lkml/20260724215937.3368276-1-sumitg@nvidia.com/
--
2.34.1
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v4 1/4] cpufreq: CPPC: Keep the policy across CPU hotplug
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 ` Sumit Gupta
2026-08-06 20:08 ` [PATCH v4 2/4] ACPI: CPPC: Make autonomous selection helpers take a u64 Sumit Gupta
` (2 subsequent siblings)
3 siblings, 0 replies; 5+ messages in thread
From: Sumit Gupta @ 2026-08-06 20:08 UTC (permalink / raw)
To: rafael, viresh.kumar, pierre.gondois, christian.loehle,
ionela.voinescu, zhenglifeng1, zhanjie9, lenb, saket.dumbre,
ray.huang, mario.limonciello, perry.yuan, kprateek.nayak,
linux-kernel, linux-pm, linux-acpi, acpica-devel, linux-tegra
Cc: treding, jonathanh, vsethi, ksitaraman, sanjayc, mochs, bbasu,
sumitg
Without online()/offline() callbacks, the cpufreq core fully tears
down a policy during exit() when its last online CPU is offlined, and
rebuilds it during init() when it comes back.
Add lightweight online()/offline() callbacks so the core instead keeps
the policy live and reuses the driver's cpu_data across CPU hotplug.
This avoids re-reading the CPPC capabilities on every offline/online,
making CPU hotplug faster.
Move what init() and exit() did on hotplug into the new callbacks:
- offline() requests the lowest desired performance, as exit() did.
- online() re-enables CPPC and restores the performance controls, as
the platform may have reset them. Failures are logged, not returned,
as the core would free the policy.
- online() also resyncs the frequency invariance counters, so that the
first tick does not measure across the offline window.
The restore in online() uses cppc_set_perf(), which writes MIN before
MAX. If the platform lowered MAX while the CPU was offline, writing the
saved MIN could briefly leave MIN above MAX on registers not accessed
through PCC, as PCC delivers the writes in one transaction. Raise MAX
ahead of the restore when the saved MIN is above it.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/cpufreq/cppc_cpufreq.c | 128 +++++++++++++++++++++++++++++++++
1 file changed, 128 insertions(+)
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 80893844353c..4b3da9a3e122 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -211,6 +211,29 @@ static void cppc_cpufreq_cpu_fie_exit(struct cpufreq_policy *policy)
}
}
+/*
+ * Resync the counter snapshot, as the policy is kept across CPU hotplug and
+ * the first tick after online would otherwise span the offline window.
+ */
+static void cppc_cpufreq_cpu_fie_resync(struct cpufreq_policy *policy)
+{
+ struct cppc_freq_invariance *cppc_fi;
+ int cpu, ret;
+
+ if (fie_disabled)
+ return;
+
+ /* policy->cpus still holds related_cpus here, so skip offline CPUs. */
+ for_each_cpu_and(cpu, policy->cpus, cpu_online_mask) {
+ cppc_fi = &per_cpu(cppc_freq_inv, cpu);
+
+ ret = cppc_get_perf_ctrs(cpu, &cppc_fi->prev_perf_fb_ctrs);
+ if (ret)
+ pr_debug("%s: failed to read perf counters for cpu:%d: %d\n",
+ __func__, cpu, ret);
+ }
+}
+
static void cppc_fie_kworker_init(void)
{
struct sched_attr attr = {
@@ -281,6 +304,10 @@ static inline void cppc_cpufreq_cpu_fie_exit(struct cpufreq_policy *policy)
{
}
+static inline void cppc_cpufreq_cpu_fie_resync(struct cpufreq_policy *policy)
+{
+}
+
static inline void cppc_freq_invariance_init(void)
{
}
@@ -735,6 +762,105 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
return ret;
}
+/*
+ * With offline() defined, the cpufreq core keeps the policy alive when
+ * a CPU is hotplugged out.
+ */
+static int cppc_cpufreq_cpu_offline(struct cpufreq_policy *policy)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ struct cppc_perf_ctrls perf_ctrls = cpu_data->perf_ctrls;
+ unsigned int cpu = policy->cpu;
+ int ret;
+
+ /*
+ * Request the lowest desired performance while the policy has no online
+ * CPU. Zeroing MIN and MAX makes cppc_set_perf() leave them unchanged.
+ */
+ perf_ctrls.desired_perf = cpu_data->perf_caps.lowest_perf;
+ perf_ctrls.min_perf = 0;
+ perf_ctrls.max_perf = 0;
+
+ ret = cppc_set_perf(cpu, &perf_ctrls);
+ if (ret)
+ pr_debug("Err setting perf value:%u on CPU:%u. ret:%d\n",
+ cpu_data->perf_caps.lowest_perf, cpu, ret);
+
+ return 0;
+}
+
+/*
+ * Raise MAX ahead of the full restore when the requested MIN is above the
+ * current MAX. cppc_set_perf() writes MIN before MAX, so the platform would
+ * otherwise briefly see MIN above MAX on registers not accessed through PCC.
+ * Lowering MAX is safe, as the MIN written first is never above it.
+ */
+static int
+cppc_cpufreq_prepare_perf_restore(unsigned int cpu,
+ const struct cppc_perf_ctrls *target)
+{
+ struct cppc_perf_ctrls cur = {}, prep = {};
+ int ret;
+
+ ret = cppc_get_perf(cpu, &cur);
+ if (ret)
+ return ret;
+
+ if (!cur.max_perf || target->min_perf <= cur.max_perf)
+ return 0;
+
+ prep.desired_perf = target->desired_perf;
+ prep.min_perf = 0; /* Zero leaves MIN unchanged. */
+ prep.max_perf = target->max_perf;
+
+ return cppc_set_perf(cpu, &prep);
+}
+
+/*
+ * Restore what the CPU may have lost while offline, as the platform may have
+ * disabled CPPC and reset the performance controls. Never fail the callback,
+ * 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.
+ */
+static int cppc_cpufreq_cpu_online(struct cpufreq_policy *policy)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ unsigned int cpu = policy->cpu;
+ int ret;
+
+ cppc_cpufreq_cpu_fie_resync(policy);
+
+ ret = cppc_set_enable(cpu, true);
+ if (ret && ret != -EOPNOTSUPP) {
+ pr_warn("Failed to re-enable CPPC for CPU%u (%d)\n", cpu, ret);
+ return 0;
+ }
+
+ /*
+ * The platform may reset the controls while the CPU is offline, so
+ * recompute min/max, clamp desired_perf into range, and reprogram them.
+ */
+ cppc_cpufreq_update_perf_limits(cpu_data, policy);
+
+ cpu_data->perf_ctrls.desired_perf =
+ clamp_t(u32, cpu_data->perf_ctrls.desired_perf,
+ cpu_data->perf_ctrls.min_perf,
+ cpu_data->perf_ctrls.max_perf);
+
+ ret = cppc_cpufreq_prepare_perf_restore(cpu, &cpu_data->perf_ctrls);
+ if (ret)
+ pr_debug("Failed to reorder perf restore on CPU%u (%d)\n",
+ cpu, ret);
+
+ ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls);
+ if (ret)
+ pr_debug("Failed to reapply perf request on CPU%u (%d)\n",
+ cpu, ret);
+
+ return 0;
+}
+
static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
@@ -1047,6 +1173,8 @@ static struct cpufreq_driver cppc_cpufreq_driver = {
.fast_switch = cppc_cpufreq_fast_switch,
.init = cppc_cpufreq_cpu_init,
.exit = cppc_cpufreq_cpu_exit,
+ .online = cppc_cpufreq_cpu_online,
+ .offline = cppc_cpufreq_cpu_offline,
.set_boost = cppc_cpufreq_set_boost,
.attr = cppc_cpufreq_attr,
.name = "cppc_cpufreq",
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 2/4] ACPI: CPPC: Make autonomous selection helpers take a u64
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 ` Sumit Gupta
2026-08-06 20:08 ` [PATCH v4 3/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload Sumit Gupta
2026-08-06 20:08 ` [PATCH v4 4/4] cpufreq: CPPC: Preserve OSPM-set registers across suspend/resume Sumit Gupta
3 siblings, 0 replies; 5+ messages in thread
From: Sumit Gupta @ 2026-08-06 20:08 UTC (permalink / raw)
To: rafael, viresh.kumar, pierre.gondois, christian.loehle,
ionela.voinescu, zhenglifeng1, zhanjie9, lenb, saket.dumbre,
ray.huang, mario.limonciello, perry.yuan, kprateek.nayak,
linux-kernel, linux-pm, linux-acpi, acpica-devel, linux-tegra
Cc: treding, jonathanh, vsethi, ksitaraman, sanjayc, mochs, bbasu,
sumitg
cppc_get_auto_sel()/cppc_set_auto_sel() use a bool, unlike the other
CPPC register get/set helpers which use a u64.
The next patch in this series saves and restores the OSPM-set registers
across CPU hotplug and driver unload through a common table of register
get/set helpers that all take a u64. The bool autonomous selection
helpers cannot be added to that table.
Change cppc_get_auto_sel()/cppc_set_auto_sel() to take a u64 so the
autonomous selection register fits alongside the others, and update
their callers.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/acpi/cppc_acpi.c | 20 ++++----------------
drivers/cpufreq/amd-pstate.c | 2 +-
drivers/cpufreq/cppc_cpufreq.c | 4 ++--
include/acpi/cppc_acpi.h | 8 ++++----
4 files changed, 11 insertions(+), 23 deletions(-)
diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c
index fef54fcd00b7..9e882b3911e6 100644
--- a/drivers/acpi/cppc_acpi.c
+++ b/drivers/acpi/cppc_acpi.c
@@ -1904,23 +1904,11 @@ EXPORT_SYMBOL_GPL(cppc_set_auto_act_window);
/**
* cppc_get_auto_sel() - Read autonomous selection register.
* @cpu: CPU from which to read register.
- * @enable: Return address.
+ * @enable: Return address, set to 0 or 1.
*/
-int cppc_get_auto_sel(int cpu, bool *enable)
+int cppc_get_auto_sel(int cpu, u64 *enable)
{
- u64 auto_sel;
- int ret;
-
- if (enable == NULL)
- return -EINVAL;
-
- ret = cppc_get_reg_val(cpu, AUTO_SEL_ENABLE, &auto_sel);
- if (ret)
- return ret;
-
- *enable = (bool)auto_sel;
-
- return 0;
+ return cppc_get_reg_val(cpu, AUTO_SEL_ENABLE, enable);
}
EXPORT_SYMBOL_GPL(cppc_get_auto_sel);
@@ -1929,7 +1917,7 @@ EXPORT_SYMBOL_GPL(cppc_get_auto_sel);
* @cpu : CPU to which to write register.
* @enable : the desired value of autonomous selection resiter to be updated.
*/
-int cppc_set_auto_sel(int cpu, bool enable)
+int cppc_set_auto_sel(int cpu, u64 enable)
{
return cppc_set_reg_val(cpu, AUTO_SEL_ENABLE, enable);
}
diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c
index d4ff8b228f86..de0b29bfad61 100644
--- a/drivers/cpufreq/amd-pstate.c
+++ b/drivers/cpufreq/amd-pstate.c
@@ -499,7 +499,7 @@ static int shmem_init_perf(struct amd_cpudata *cpudata)
struct cppc_perf_caps cppc_perf;
union perf_cached perf = READ_ONCE(cpudata->perf);
u64 numerator;
- bool auto_sel;
+ u64 auto_sel;
int ret = cppc_get_perf_caps(cpudata->cpu, &cppc_perf);
if (ret)
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 4b3da9a3e122..b50d3f893b1d 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -990,7 +990,7 @@ static ssize_t show_freqdomain_cpus(struct cpufreq_policy *policy, char *buf)
static ssize_t show_auto_select(struct cpufreq_policy *policy, char *buf)
{
- bool val;
+ u64 val;
int ret;
ret = cppc_get_auto_sel(policy->cpu, &val);
@@ -1002,7 +1002,7 @@ static ssize_t show_auto_select(struct cpufreq_policy *policy, char *buf)
if (ret)
return ret;
- return sysfs_emit(buf, "%d\n", val);
+ return sysfs_emit(buf, "%llu\n", val);
}
static ssize_t store_auto_select(struct cpufreq_policy *policy,
diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h
index 94a6277edab2..3394e1b208be 100644
--- a/include/acpi/cppc_acpi.h
+++ b/include/acpi/cppc_acpi.h
@@ -187,8 +187,8 @@ extern int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool e
extern int cppc_set_epp(int cpu, u64 epp_val);
extern int cppc_get_auto_act_window(int cpu, u64 *auto_act_window);
extern int cppc_set_auto_act_window(int cpu, u64 auto_act_window);
-extern int cppc_get_auto_sel(int cpu, bool *enable);
-extern int cppc_set_auto_sel(int cpu, bool enable);
+extern int cppc_get_auto_sel(int cpu, u64 *enable);
+extern int cppc_set_auto_sel(int cpu, u64 enable);
extern int cppc_get_perf_limited(int cpu, u64 *perf_limited);
extern int cppc_set_perf_limited(int cpu, u64 bits_to_clear);
extern int amd_get_highest_perf(unsigned int cpu, u32 *highest_perf);
@@ -285,11 +285,11 @@ static inline int cppc_set_auto_act_window(int cpu, u64 auto_act_window)
{
return -EOPNOTSUPP;
}
-static inline int cppc_get_auto_sel(int cpu, bool *enable)
+static inline int cppc_get_auto_sel(int cpu, u64 *enable)
{
return -EOPNOTSUPP;
}
-static inline int cppc_set_auto_sel(int cpu, bool enable)
+static inline int cppc_set_auto_sel(int cpu, u64 enable)
{
return -EOPNOTSUPP;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 3/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload
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
2026-08-06 20:08 ` [PATCH v4 4/4] cpufreq: CPPC: Preserve OSPM-set registers across suspend/resume Sumit Gupta
3 siblings, 0 replies; 5+ messages in thread
From: Sumit Gupta @ 2026-08-06 20:08 UTC (permalink / raw)
To: rafael, viresh.kumar, pierre.gondois, christian.loehle,
ionela.voinescu, zhenglifeng1, zhanjie9, lenb, saket.dumbre,
ray.huang, mario.limonciello, perry.yuan, kprateek.nayak,
linux-kernel, linux-pm, linux-acpi, acpica-devel, linux-tegra
Cc: treding, jonathanh, vsethi, ksitaraman, sanjayc, mochs, bbasu,
sumitg
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
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v4 4/4] cpufreq: CPPC: Preserve OSPM-set registers across suspend/resume
2026-08-06 20:08 [PATCH v4 0/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload Sumit Gupta
` (2 preceding siblings ...)
2026-08-06 20:08 ` [PATCH v4 3/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload Sumit Gupta
@ 2026-08-06 20:08 ` Sumit Gupta
3 siblings, 0 replies; 5+ messages in thread
From: Sumit Gupta @ 2026-08-06 20:08 UTC (permalink / raw)
To: rafael, viresh.kumar, pierre.gondois, christian.loehle,
ionela.voinescu, zhenglifeng1, zhanjie9, lenb, saket.dumbre,
ray.huang, mario.limonciello, perry.yuan, kprateek.nayak,
linux-kernel, linux-pm, linux-acpi, acpica-devel, linux-tegra
Cc: treding, jonathanh, vsethi, ksitaraman, sanjayc, mochs, bbasu,
sumitg
The driver preserves the OSPM-set registers across CPU hotplug, but system
suspend/resume is a separate path. On platforms that reset those registers
or the performance controls across suspend, the values are lost.
Reuse the same save/restore mechanism for suspend/resume:
- suspend() saves the current OSPM-set values, restores the firmware
values and sets a per-policy flag. It runs before devices are
suspended and before the secondary CPUs go offline, while CPPC access
is still safe. It also covers a policy whose CPUs stay online, for
which offline() never runs.
- offline() sees the flag and leaves those register accesses alone,
parking only the performance request.
- resume() needs the same steps as online(). The core has already run
online() for a policy whose CPUs were offlined and brought back.
So, resume() calls it only for a policy that still has the flag set.
- online() clears the flag, so that a later offline() takes a fresh
snapshot.
Suggested-by: Christian Loehle <christian.loehle@arm.com>
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/cpufreq/cppc_cpufreq.c | 54 ++++++++++++++++++++++++++++++++--
1 file changed, 51 insertions(+), 3 deletions(-)
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index f8628b1fc6b7..32f38b0c492b 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -87,6 +87,12 @@ struct cppc_saved_vals {
struct cppc_policy_state {
struct cppc_saved_vals regs[CPPC_NR_SAVED_REGS];
+ /*
+ * Set by suspend() after it saves the OSPM-set values and restores the
+ * firmware ones, so a later offline() does not repeat those accesses.
+ * Cleared at init() and by online().
+ */
+ bool suspend_regs_handled;
};
static DEFINE_PER_CPU(struct cppc_policy_state, cppc_policy_state);
@@ -125,6 +131,9 @@ static void cppc_cpufreq_save_regs(struct cpufreq_policy *policy,
u64 val;
int i;
+ if (saved_type == CPPC_SAVED_FIRMWARE)
+ st->suspend_regs_handled = false;
+
for (i = 0; i < CPPC_NR_SAVED_REGS; i++) {
if (cppc_saved_regs[i].get(cpu, &val))
val = U64_MAX;
@@ -952,9 +961,14 @@ 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);
+ /*
+ * Leave the platform in its pre-driver state while offline, unless
+ * suspend() already did so earlier in this suspend cycle.
+ */
+ if (!cppc_cpufreq_policy_state(policy)->suspend_regs_handled) {
+ 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
@@ -1014,6 +1028,8 @@ static int cppc_cpufreq_cpu_online(struct cpufreq_policy *policy)
unsigned int cpu = policy->cpu;
int ret;
+ cppc_cpufreq_policy_state(policy)->suspend_regs_handled = false;
+
cppc_cpufreq_cpu_fie_resync(policy);
ret = cppc_set_enable(cpu, true);
@@ -1050,6 +1066,36 @@ static int cppc_cpufreq_cpu_online(struct cpufreq_policy *policy)
return 0;
}
+/*
+ * Save the OSPM-set values and restore the firmware values here, while CPPC
+ * access is still safe. Secondary CPUs go offline much later, with devices
+ * already suspended, so offline() is too late for these accesses and leaves
+ * them alone until resume. Doing it here covers every policy, including one
+ * whose CPUs stay online, for which offline() never runs.
+ */
+static int cppc_cpufreq_cpu_suspend(struct cpufreq_policy *policy)
+{
+ cppc_cpufreq_save_regs(policy, CPPC_SAVED_REQUESTED);
+ cppc_cpufreq_apply_saved_regs(policy, CPPC_SAVED_FIRMWARE);
+ cppc_cpufreq_policy_state(policy)->suspend_regs_handled = true;
+
+ return 0;
+}
+
+/*
+ * CPUs offlined during suspend come back before the core calls resume(), so
+ * online() has already run for their policies and cleared the flag. It remains
+ * set only for a policy whose CPUs stayed online, and only that policy needs
+ * online() here.
+ */
+static int cppc_cpufreq_cpu_resume(struct cpufreq_policy *policy)
+{
+ if (!cppc_cpufreq_policy_state(policy)->suspend_regs_handled)
+ return 0;
+
+ return cppc_cpufreq_cpu_online(policy);
+}
+
static void cppc_cpufreq_cpu_exit(struct cpufreq_policy *policy)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
@@ -1364,6 +1410,8 @@ static struct cpufreq_driver cppc_cpufreq_driver = {
.exit = cppc_cpufreq_cpu_exit,
.online = cppc_cpufreq_cpu_online,
.offline = cppc_cpufreq_cpu_offline,
+ .suspend = cppc_cpufreq_cpu_suspend,
+ .resume = cppc_cpufreq_cpu_resume,
.set_boost = cppc_cpufreq_set_boost,
.attr = cppc_cpufreq_attr,
.name = "cppc_cpufreq",
--
2.34.1
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-06 20:10 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
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 ` [PATCH v4 3/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload Sumit Gupta
2026-08-06 20:08 ` [PATCH v4 4/4] cpufreq: CPPC: Preserve OSPM-set registers across suspend/resume Sumit Gupta
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox