Re: [PATCH v3 2/2] ACPI: CPPC: Add ospm_nominal_perf support
From: Sumit Gupta
Date: Tue May 26 2026 - 14:24:36 EST
On 21/05/26 22:28, Pierre Gondois wrote:
External email: Use caution opening links or attachments
Hello Sumit,
I copy-pasted and attached a complementary patch just to show
what I meant by caching the ospm_nominal_perf value at driver
init/exit.
This should also allow to handle the cases where a user tries
to store a new value for a policy with multiple CPUs,
one of the CPUs and we need to rollback.
I think another issue that should be solved is that:
- we don't the value of ospm_nominal_perf when loading the driver
- we leave the register in an unknown state if we unload/reload
the driver.
Note:
IMO caching values like in the patch would also benefit to the
auto_sel register.
------
If you think the patch helps, feel free to take it. Otherwise
please let me know the issues it would raise.
Regards,
Pierre
Hi Pierre,
Thanks for the review and the complementary patch.
Going point by point:
1. Rollback for a partially applied multiple CPU write in
store_ospm_nominal_freq(): Agreed, will add into v4.
2. cppc_get_ospm_nominal_perf() and the show/init/exit coherence
checks that rely on it: I'd skip these as the register is write-only
as per spec.
3. Initializing the register at startup and restoring at exit: In v3, we
dropped the unconditional cpu_init write so user values would
survive CPU hotplug. The spec also makes the explicit init
unnecessary: "If this register is not provided, then OSPM must
assume that the OSPM Nominal Performance value is equal to
the Nominal Performance value.". The unwritten default already
looks well defined.
4. pr_warn() in show_ospm_nominal_freq() on HW vs cache
mismatch: Skipping it since it relies on (2).
So in v4, will pick up the rollback and leave the rest as is.
Happy to discuss further if you think differently.
Thank you,
Sumit Gupta
On 5/14/26 21:48, Sumit Gupta wrote:
Expose the OSPM Nominal Performance register (ACPI 6.6, Section
8.4.6.1.2.6), which conveys the desired nominal performance level
at which the platform may run. Unlike the existing read-only
Nominal Performance register, it is writable and lets OSPM
request a lower nominal level than the platform-reported nominal.
The platform classifies performance above this level as boosted
and below as throttled for its power/thermal decisions.
It is exposed as a per-policy cpufreq sysfs attribute in kHz, to
match the cpufreq sysfs unit convention:
/sys/devices/system/cpu/cpufreq/policyN/ospm_nominal_freq
The attribute is documented in
Documentation/ABI/testing/sysfs-devices-system-cpu.
Writes are converted to perf via cppc_khz_to_perf(), validated
against [Lowest Performance, Nominal Performance], and applied to
every CPU in policy->cpus.
The register is write-only; the kernel caches the last written
value in struct cppc_cpudata for sysfs readback (returns 0 until
userspace writes a value).
Signed-off-by: Sumit Gupta <sumitg@xxxxxxxxxx>
---
.../ABI/testing/sysfs-devices-system-cpu | 17 ++++++++
drivers/acpi/cppc_acpi.c | 35 ++++++++++++++++
drivers/cpufreq/cppc_cpufreq.c | 40 +++++++++++++++++++
include/acpi/cppc_acpi.h | 7 ++++
4 files changed, 99 insertions(+)
diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu
index 82d10d556cc8..ac1bf1b89ac4 100644
--- a/Documentation/ABI/testing/sysfs-devices-system-cpu
+++ b/Documentation/ABI/testing/sysfs-devices-system-cpu
@@ -346,6 +346,23 @@ Description: Performance Limited
This file is only present if the cppc-cpufreq driver is in use.
+What: /sys/devices/system/cpu/cpuX/cpufreq/ospm_nominal_freq
+Date: May 2026
+Contact: linux-pm@xxxxxxxxxxxxxxx
+Description: OSPM Nominal Performance (kHz)
+
+ OSPM uses this attribute to request a nominal performance
+ level lower than the platform-reported nominal. The
+ platform treats performance above this level as boost
+ and below as throttle for power and thermal decisions.
+
+ Read returns the last written value in kHz, or 0 if no
+ value has been written. Write a kHz value in the range
+ [lowest_freq, nominal_freq].
+
+ This file is only present if the cppc-cpufreq driver is
+ in use.
+
What: /sys/devices/system/cpu/cpu*/cache/index3/cache_disable_{0,1}
Date: August 2008
KernelVersion: 2.6.27
diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c
index c76cfafa3589..ad6ece16c30d 100644
--- a/drivers/acpi/cppc_acpi.c
+++ b/drivers/acpi/cppc_acpi.c
@@ -1682,6 +1682,41 @@ int cppc_set_epp(int cpu, u64 epp_val)
}
EXPORT_SYMBOL_GPL(cppc_set_epp);
+/**
+ * cppc_set_ospm_nominal_perf() - Write OSPM Nominal Performance register.
+ * @cpu: CPU on which to write register.
+ * @ospm_nominal_perf: Value to write to the OSPM Nominal Performance register.
+ *
+ * OSPM Nominal Performance conveys the desired nominal performance level
+ * at which the platform may run. Per ACPI 6.6, s8.4.6.1.2.6, the value
+ * must lie within [Lowest Performance, Nominal Performance] and may be
+ * set independently of Minimum, Maximum and Desired performance.
+ *
+ * Return: 0 on success or negative error code.
+ */
+int cppc_set_ospm_nominal_perf(int cpu, u64 ospm_nominal_perf)
+{
+ struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
+ struct cppc_perf_caps caps;
+ int ret;
+
+ if (!cpc_desc) {
+ pr_debug("No CPC descriptor for CPU:%d\n", cpu);
+ return -ENODEV;
+ }
+
+ ret = cppc_get_perf_caps(cpu, &caps);
+ if (ret)
+ return ret;
+
+ if (ospm_nominal_perf < caps.lowest_perf ||
+ ospm_nominal_perf > caps.nominal_perf)
+ return -EINVAL;
+
+ return cppc_set_reg_val(cpu, OSPM_NOMINAL_PERF, ospm_nominal_perf);
+}
+EXPORT_SYMBOL_GPL(cppc_set_ospm_nominal_perf);
+
/**
* cppc_get_auto_act_window() - Read autonomous activity window register.
* @cpu: CPU from which to read register.
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 7e7f9dfb7a24..6379b7ceee34 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -985,11 +985,50 @@ store_energy_performance_preference_val(struct cpufreq_policy *policy,
CPPC_CPUFREQ_ATTR_RW_U64(perf_limited, cppc_get_perf_limited,
cppc_set_perf_limited)
+static ssize_t show_ospm_nominal_freq(struct cpufreq_policy *policy, char *buf)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ unsigned int freq_khz;
+
+ if (!cpu_data->ospm_nominal_perf)
+ return sysfs_emit(buf, "0\n");
+
+ freq_khz = cppc_perf_to_khz(&cpu_data->perf_caps,
+ cpu_data->ospm_nominal_perf);
+ return sysfs_emit(buf, "%u\n", freq_khz);
+}
+
+static ssize_t store_ospm_nominal_freq(struct cpufreq_policy *policy,
+ const char *buf, size_t count)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ unsigned int sib;
+ u64 freq_khz;
+ u32 perf;
+ int ret;
+
+ ret = kstrtou64(buf, 0, &freq_khz);
+ if (ret)
+ return ret;
+
+ perf = cppc_khz_to_perf(&cpu_data->perf_caps, freq_khz);
+
+ for_each_cpu(sib, policy->cpus) {
+ ret = cppc_set_ospm_nominal_perf(sib, perf);
+ if (ret)
+ return ret;
+ }
+
+ cpu_data->ospm_nominal_perf = perf;
+ return count;
+}
+
cpufreq_freq_attr_ro(freqdomain_cpus);
cpufreq_freq_attr_rw(auto_select);
cpufreq_freq_attr_rw(auto_act_window);
cpufreq_freq_attr_rw(energy_performance_preference_val);
cpufreq_freq_attr_rw(perf_limited);
+cpufreq_freq_attr_rw(ospm_nominal_freq);
static struct freq_attr *cppc_cpufreq_attr[] = {
&freqdomain_cpus,
@@ -997,6 +1036,7 @@ static struct freq_attr *cppc_cpufreq_attr[] = {
&auto_act_window,
&energy_performance_preference_val,
&perf_limited,
+ &ospm_nominal_freq,
NULL,
};
diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h
index 8693890a7275..0b1dcdbea10a 100644
--- a/include/acpi/cppc_acpi.h
+++ b/include/acpi/cppc_acpi.h
@@ -153,6 +153,8 @@ struct cppc_cpudata {
struct cppc_perf_fb_ctrs perf_fb_ctrs;
unsigned int shared_type;
cpumask_var_t shared_cpu_map;
+ /* Cached OSPM Nominal Performance value (write-only register). */
+ u32 ospm_nominal_perf;
};
#ifdef CONFIG_ACPI_CPPC_LIB
@@ -180,6 +182,7 @@ extern int cpc_write_ffh(int cpunum, struct cpc_reg *reg, u64 val);
extern int cppc_get_epp_perf(int cpunum, u64 *epp_perf);
extern int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable);
extern int cppc_set_epp(int cpu, u64 epp_val);
+extern int cppc_set_ospm_nominal_perf(int cpu, u64 ospm_nominal_perf);
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);
@@ -266,6 +269,10 @@ static inline int cppc_set_epp(int cpu, u64 epp_val)
{
return -EOPNOTSUPP;
}
+static inline int cppc_set_ospm_nominal_perf(int cpu, u64 ospm_nominal_perf)
+{
+ return -EOPNOTSUPP;
+}
static inline int cppc_get_auto_act_window(int cpu, u64 *auto_act_window)
{
return -EOPNOTSUPP;
diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu
b/Documentation/ABI/testing/sysfs-devices-system-cpu
index ac1bf1b89ac4d..f3d357917242a 100644
--- a/Documentation/ABI/testing/sysfs-devices-system-cpu
+++ b/Documentation/ABI/testing/sysfs-devices-system-cpu
@@ -356,9 +356,7 @@Description: OSPM Nominal Performance (kHz)
platform treats performance above this level as boost
and below as throttle for power and thermal decisions.
- Read returns the last written value in kHz, or 0 if no
- value has been written. Write a kHz value in the range
- [lowest_freq, nominal_freq].
+Values are in kHz and in the range [lowest_freq, nominal_freq].
This file is only present if the cppc-cpufreq driver is
in use.
diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c
index 97218c4ac162b..01542184e69b2 100644
--- a/drivers/acpi/cppc_acpi.c
+++ b/drivers/acpi/cppc_acpi.c
@@ -1717,6 +1717,19 @@int cppc_set_ospm_nominal_perf(int cpu, u64
ospm_nominal_perf)
}
EXPORT_SYMBOL_GPL(cppc_set_ospm_nominal_perf);
+/**
+ * cppc_get_ospm_nominal_perf() - Read OSPM Nominal Performance register.
+ * @cpu: CPU from which to read register.
+ * @ospm_nominal_perf: Pointer to store the OSPM Nominal Performance value.
+ *
+ * Return: 0 on success or negative error code.
+ */
+int cppc_get_ospm_nominal_perf(int cpu, u64 *ospm_nominal_perf)
+{
+return cppc_get_reg_val(cpu, OSPM_NOMINAL_PERF, ospm_nominal_perf);
+}
+EXPORT_SYMBOL_GPL(cppc_get_ospm_nominal_perf);
+
/**
* cppc_get_auto_act_window() - Read autonomous activity window register.
* @cpu: CPU from which to read register.
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 6379b7ceee347..1a51017a81630 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -28,6 +28,9 @@
static struct cpufreq_driver cppc_cpufreq_driver;
+/* Values at driver init. */
+static u64 scratch_ospm_nominal_perf[NR_CPUS];
+
#ifdef CONFIG_ACPI_CPPC_CPUFREQ_FIE
static enum {
FIE_UNSET = -1,
@@ -642,7 +645,7 @@static void cppc_cpufreq_put_cpu_data(struct
cpufreq_policy *policy)
static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
{
- unsigned int cpu = policy->cpu;
+unsigned int sib, cpu = policy->cpu;
struct cppc_cpudata *cpu_data;
struct cppc_perf_caps *caps;
int ret;
@@ -704,6 +707,34 @@static int cppc_cpufreq_cpu_init(struct
cpufreq_policy *policy)
if (caps->highest_perf > caps->nominal_perf)
policy->boost_supported = true;
+/*
+ * CPUs are expected to have:
+ * ospm_nominal_perf = nominal_perf
+ * at policy init.
+ */
+cpu_data->ospm_nominal_perf = caps->nominal_perf;
+
+for_each_cpu(sib, policy->cpus) {
+u64 ospm_nominal_perf;
+
+ret = cppc_get_ospm_nominal_perf(sib, &ospm_nominal_perf);
+if (ret == -EOPNOTSUPP) {
+continue;
+} else if (ret) {
+pr_err("CPU%d: Could not read ospm_nominal_perf value\n",
+sib);
+goto out;
+}
+
+if (ospm_nominal_perf != caps->nominal_perf) {
+pr_err("Incoherent ospm_nominal_perf values: "
+"scratch_value=%llu expected_value=%u\n",
+scratch_ospm_nominal_perf[sib],
+caps->nominal_perf);
+goto out;
+}
+}
+
/* 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;
@@ -727,9 +758,17 @@static void cppc_cpufreq_cpu_exit(struct
cpufreq_policy *policy)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
struct cppc_perf_caps *caps = &cpu_data->perf_caps;
- unsigned int cpu = policy->cpu;
+unsigned int sib, cpu = policy->cpu;
int ret;
+/*
+ * CPUs are expected to have:
+ * ospm_nominal_perf = nominal_perf
+ * at policy init, so come back to that state.
+ */
+for_each_cpu(sib, policy->cpus)
+cppc_set_ospm_nominal_perf(sib, caps->nominal_perf);
+
cppc_cpufreq_cpu_fie_exit(policy);
cpu_data->perf_ctrls.desired_perf = caps->lowest_perf;
@@ -989,9 +1028,20 @@static ssize_t show_ospm_nominal_freq(struct
cpufreq_policy *policy, char *buf)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
unsigned int freq_khz;
+u64 perf;
+int ret;
+
+ret = cppc_get_ospm_nominal_perf(policy->cpu, &perf);
+if (ret == -EOPNOTSUPP)
+return sysfs_emit(buf, "<unsupported>\n");
+if (ret)
+return ret;
- if (!cpu_data->ospm_nominal_perf)
- return sysfs_emit(buf, "0\n");
+if (perf != cpu_data->ospm_nominal_perf) {
+pr_warn("ospm_nominal_freq value is not coherent: "
+"register=%llu scratch_value=%u\n",
+perf, cpu_data->ospm_nominal_perf);
+}
freq_khz = cppc_perf_to_khz(&cpu_data->perf_caps,
cpu_data->ospm_nominal_perf);
@@ -1002,7 +1052,7 @@static ssize_t store_ospm_nominal_freq(struct
cpufreq_policy *policy,
const char *buf, size_t count)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
- unsigned int sib;
+unsigned int sib, failing_cpu;
u64 freq_khz;
u32 perf;
int ret;
@@ -1015,12 +1065,23 @@static ssize_t store_ospm_nominal_freq(struct
cpufreq_policy *policy,
for_each_cpu(sib, policy->cpus) {
ret = cppc_set_ospm_nominal_perf(sib, perf);
- if (ret)
- return ret;
+if (ret) {
+failing_cpu = sib;
+goto error;
+}
}
cpu_data->ospm_nominal_perf = perf;
return count;
+
+error:
+for_each_cpu(sib, policy->cpus) {
+if (sib == failing_cpu)
+break;
+cppc_set_ospm_nominal_perf(sib, cpu_data->ospm_nominal_perf);
+}
+
+return ret;
}
cpufreq_freq_attr_ro(freqdomain_cpus);
@@ -1053,6 +1114,64 @@static struct cpufreq_driver cppc_cpufreq_driver = {
.name = "cppc_cpufreq",
};
+static void __exit cppc_cpufreq_ospm_nominal_perf_exit(void)
+{
+int cpu;
+
+for_each_present_cpu(cpu) {
+if (scratch_ospm_nominal_perf[cpu] == U64_MAX)
+continue;
+
+/* Reset ospm_nominal_perf to the value present at init. */
+cppc_set_ospm_nominal_perf(cpu, scratch_ospm_nominal_perf[cpu]);
+}
+}
+
+static int __init cppc_cpufreq_ospm_nominal_perf_init(void)
+{
+int ret, cpu;
+
+ret = 0;
+
+for_each_present_cpu(cpu) {
+u64 nominal_perf;
+
+/*
+ * If one CPU failed, scratch_ospm_nominal_perf still needs
+ * to be initialized.
+ */
+if (ret) {
+scratch_ospm_nominal_perf[cpu] = U64_MAX;
+continue;
+}
+
+ret = cppc_get_ospm_nominal_perf(cpu, &scratch_ospm_nominal_perf[cpu]);
+if (ret) {
+if (ret == -EOPNOTSUPP) {
+/* Valid reason to fail. Continue to iterate. */
+ret = 0;
+}
+
+scratch_ospm_nominal_perf[cpu] = U64_MAX;
+continue;
+}
+
+/* Set ospm_nominal_perf to nominal_perf before policy init. */
+ret = cppc_get_nominal_perf(cpu, &nominal_perf);
+if (ret)
+continue;
+
+ret = cppc_set_ospm_nominal_perf(cpu, nominal_perf);
+if (ret)
+continue;
+}
+
+if (ret)
+cppc_cpufreq_ospm_nominal_perf_exit();
+
+return ret;
+}
+
static int __init cppc_cpufreq_init(void)
{
int ret;
@@ -1063,9 +1182,17 @@static int __init cppc_cpufreq_init(void)
cppc_freq_invariance_init();
populate_efficiency_class();
+ret = cppc_cpufreq_ospm_nominal_perf_init();
+if (ret) {
+cppc_freq_invariance_exit();
+return ret;
+}
+
ret = cpufreq_register_driver(&cppc_cpufreq_driver);
- if (ret)
+if (ret) {
+cppc_cpufreq_ospm_nominal_perf_exit();
cppc_freq_invariance_exit();
+}
return ret;
}
@@ -1074,6 +1201,7 @@static void __exit cppc_cpufreq_exit(void)
{
cpufreq_unregister_driver(&cppc_cpufreq_driver);
cppc_freq_invariance_exit();
+cppc_cpufreq_ospm_nominal_perf_exit();
}
module_exit(cppc_cpufreq_exit);
diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h
index 0b1dcdbea10a2..07e6598fa837a 100644
--- a/include/acpi/cppc_acpi.h
+++ b/include/acpi/cppc_acpi.h
@@ -183,6 +183,7 @@extern int cppc_get_epp_perf(int cpunum, u64 *epp_perf);
extern int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls
*perf_ctrls, bool enable);
extern int cppc_set_epp(int cpu, u64 epp_val);
extern int cppc_set_ospm_nominal_perf(int cpu, u64 ospm_nominal_perf);
+extern int cppc_get_ospm_nominal_perf(int cpu, u64 *ospm_nominal_perf);
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);
@@ -273,6 +274,10 @@static inline int cppc_set_ospm_nominal_perf(int
cpu, u64 ospm_nominal_perf)
{
return -EOPNOTSUPP;
}
+static inline int cppc_get_ospm_nominal_perf(int cpu, u64
*ospm_nominal_perf)
+{
+return -EOPNOTSUPP;
+}
static inline int cppc_get_auto_act_window(int cpu, u64 *auto_act_window)
{
return -EOPNOTSUPP;