From: Pierre Gondois <pierre.gondois@arm.com>
To: Sumit Gupta <sumitg@nvidia.com>,
rafael@kernel.org, viresh.kumar@linaro.org,
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, 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
Subject: Re: [PATCH v5 3/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload
Date: Mon, 28 Sep 2026 14:21:00 +0200 [thread overview]
Message-ID: <9ffb913f-87cf-48da-b08e-544cfbda785f@arm.com> (raw)
In-Reply-To: <20260916103820.1760297-4-sumitg@nvidia.com>
On 9/16/26 12:38, Sumit Gupta wrote:
> 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
> reprogramming the performance controls. A failed write to the controls
> does not skip the reapply, as no other path restores these registers.
> - 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. While autonomous selection stays disabled, the platform may
> ignore those writes.
>
> 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 | 193 ++++++++++++++++++++++++++++++++-
> 1 file changed, 190 insertions(+), 3 deletions(-)
>
> diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
> index d7d96fe0da1b..ac315071a979 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 the OS wrote is
> + * reapplied from online() across CPU hotplug, and the firmware value is
> + * restored from offline().
> + *
> + * Autonomous Selection (auto_sel) is kept first, as the registers after it
> + * only have meaning while it 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 written back when the policy goes 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 {
With:
enum cppc_saved_type {
CPPC_SAVED_FIRMWARE,
CPPC_SAVED_REQUESTED,
CPPC_SAVED_MAX
};
This could be changed to:
struct cppc_saved_vals regs[CPPC_NR_SAVED_REGS][CPPC_SAVED_MAX];
this would simplify cppc_cpufreq_saved_reg_value() and
cppc_cpufreq_save_regs()
> + 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, either as the firmware value, captured
> + * before the driver programs anything, or as 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 it when there is no saved value.
> + * A register the platform does not allow writing is not an error.
> + */
> +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. While autonomous selection stays disabled,
> + * the platform may ignore those writes. Do not enable it temporarily to force
> + * them through.
The spec says:
`Writes to this register only have meaning when Autonomous Selection is
enabled.`
which is subject to interpretation. I'm not sure this should be taken
care of,
IMO the firmware should still store these values, but this is a personal
interpretation
so what you did might be safer.
If someone else has an opinion this might be useful
> + */
> +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,
> @@ -718,6 +895,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",
> @@ -791,12 +970,14 @@ cppc_cpufreq_prepare_perf_restore(unsigned int cpu,
> *
> * The platform may have disabled CPPC and reset the performance controls
> * (desired, min and max performance) while the CPU was offline, so re-enable
> - * CPPC and reprogram them.
> + * CPPC and reprogram them. Also reapply the OSPM-set registers that offline()
> + * reset to firmware values.
> *
> * Report failures without returning them, or the core would free the policy and
> * leave the CPU without cpufreq. A failed write to the performance controls is
> - * not fatal, as the governor's next request programs them again. A failed CPPC
> - * enable stops the restore, as the writes that follow may not reach the
> + * not fatal, as the governor's next request programs them again. The OSPM-set
> + * registers are reapplied even then, as no other path restores them. A failed
> + * CPPC enable skips both restores, as the writes that follow may not reach the
> * platform.
> */
> static int cppc_cpufreq_cpu_online(struct cpufreq_policy *policy)
> @@ -832,6 +1013,8 @@ static int cppc_cpufreq_cpu_online(struct cpufreq_policy *policy)
> pr_debug("Failed to restore perf controls on CPU%u (%d)\n",
> cpu, ret);
>
> + cppc_cpufreq_apply_saved_regs(policy, CPPC_SAVED_REQUESTED);
> +
> out_fie:
> /* Restart what offline() stopped, with a new counter snapshot. */
> cppc_cpufreq_cpu_fie_init(policy);
> @@ -851,6 +1034,10 @@ static int cppc_cpufreq_cpu_offline(struct cpufreq_policy *policy)
> unsigned int cpu = policy->cpu;
> int ret;
>
> + /* Save what the OS set, and leave the platform in its pre-driver state. */
> + cppc_cpufreq_save_regs(policy, CPPC_SAVED_REQUESTED);
> + cppc_cpufreq_apply_saved_regs(policy, CPPC_SAVED_FIRMWARE);
> +
> /*
> * Stop the frequency invariance updates and cancel the pending work, so
> * that no sample spans the offline window. online() restarts them with
next prev parent reply other threads:[~2026-09-28 13:33 UTC|newest]
Thread overview: 12+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-16 10:38 [PATCH v5 0/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload Sumit Gupta
2026-09-16 10:38 ` [PATCH v5 1/4] cpufreq: CPPC: Keep the policy across CPU hotplug Sumit Gupta
2026-09-28 12:20 ` Pierre Gondois
2026-09-16 10:38 ` [PATCH v5 2/4] ACPI: CPPC: Make autonomous selection helpers take a u64 Sumit Gupta
2026-09-16 18:48 ` K Prateek Nayak
2026-09-16 10:38 ` [PATCH v5 3/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload Sumit Gupta
2026-09-28 12:21 ` Pierre Gondois [this message]
2026-09-16 10:38 ` [PATCH v5 4/4] cpufreq: CPPC: Preserve OSPM-set registers across suspend/resume Sumit Gupta
2026-09-28 12:21 ` Pierre Gondois
2026-09-16 19:04 ` [PATCH v5 0/4] cpufreq: CPPC: Preserve OSPM-set registers across hotplug and unload K Prateek Nayak
2026-09-17 7:13 ` Sumit Gupta
2026-09-25 19:23 ` Rafael J. Wysocki (Intel)
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=9ffb913f-87cf-48da-b08e-544cfbda785f@arm.com \
--to=pierre.gondois@arm.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=rafael@kernel.org \
--cc=ray.huang@amd.com \
--cc=sanjayc@nvidia.com \
--cc=sumitg@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.