mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
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

  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] " 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®