* [PATCH v7 1/3] ACPI: CPPC: Add ospm_nominal_perf support
2026-08-07 21:48 [PATCH v7 0/3] ACPI / cpufreq: CPPC: Add ospm_nominal_perf support Sumit Gupta
@ 2026-08-07 21:48 ` Sumit Gupta
2026-08-07 21:48 ` [PATCH v7 2/3] cpufreq: CPPC: Add ospm_nominal_freq attribute Sumit Gupta
2026-08-07 21:48 ` [PATCH v7 3/3] cpufreq: CPPC: Reflect the OSPM nominal in boost and limits Sumit Gupta
2 siblings, 0 replies; 4+ messages in thread
From: Sumit Gupta @ 2026-08-07 21:48 UTC (permalink / raw)
To: rafael, viresh.kumar, pierre.gondois, christian.loehle,
ionela.voinescu, zhenglifeng1, zhanjie9, lenb, saket.dumbre,
mario.limonciello, linux-kernel, linux-pm, linux-acpi,
acpica-devel, linux-tegra
Cc: treding, jonathanh, vsethi, ksitaraman, sanjayc, mochs, bbasu,
sumitg
Expose the OSPM Nominal Performance register (ACPI 6.6, Section
8.4.6.1.2.6), which conveys the nominal performance level at which the
platform may run. Unlike the read-only Nominal Performance register, it
is writable, so OSPM can ask for a nominal level below the
platform-reported one, which then becomes the boundary between boosted
and throttled operation for the platform's power and thermal decisions.
Add cppc_set_ospm_nominal_perf() to write the register. Per the spec,
the value must lie in [Lowest Performance, Nominal Performance], which
the caller is responsible for validating.
Add cppc_ospm_nominal_perf_supported() to report whether the platform
implements the register as writable, since a write-only register cannot
be probed by reading it. Factor the writable-register check out of
cppc_set_reg_val() into cpc_reg_writable() and use it for both, so
support is never reported for a register that a write would reject.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/acpi/cppc_acpi.c | 48 ++++++++++++++++++++++++++++++++++++++--
include/acpi/cppc_acpi.h | 10 +++++++++
2 files changed, 56 insertions(+), 2 deletions(-)
diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c
index 9e882b3911e6..b6bf46cb06cb 100644
--- a/drivers/acpi/cppc_acpi.c
+++ b/drivers/acpi/cppc_acpi.c
@@ -1388,6 +1388,13 @@ static int cppc_set_reg_val_in_pcc(int cpu, struct cpc_register_resource *reg, u
return ret;
}
+/* If a register is writeable, it must be a buffer and not null */
+static bool cpc_reg_writable(const struct cpc_register_resource *reg)
+{
+ return reg->type == ACPI_TYPE_BUFFER &&
+ !IS_NULL_REG(®->cpc_entry.reg);
+}
+
static int cppc_set_reg_val(int cpu, enum cppc_regs reg_idx, u64 val)
{
struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
@@ -1400,8 +1407,7 @@ static int cppc_set_reg_val(int cpu, enum cppc_regs reg_idx, u64 val)
reg = &cpc_desc->cpc_regs[reg_idx];
- /* if a register is writeable, it must be a buffer and not null */
- if ((reg->type != ACPI_TYPE_BUFFER) || IS_NULL_REG(®->cpc_entry.reg)) {
+ if (!cpc_reg_writable(reg)) {
pr_debug("CPC register is not supported\n");
return -EOPNOTSUPP;
}
@@ -1832,6 +1838,44 @@ 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. The
+ * caller is responsible for validating the range.
+ *
+ * Return: 0 on success or negative error code.
+ */
+int cppc_set_ospm_nominal_perf(int cpu, u64 ospm_nominal_perf)
+{
+ return cppc_set_reg_val(cpu, OSPM_NOMINAL_PERF, ospm_nominal_perf);
+}
+EXPORT_SYMBOL_GPL(cppc_set_ospm_nominal_perf);
+
+/**
+ * cppc_ospm_nominal_perf_supported() - Check OSPM Nominal Performance support.
+ * @cpu: CPU to query.
+ *
+ * The OSPM Nominal Performance register is write-only, so its value
+ * cannot be read back. This only reports whether the platform implements
+ * it as a writable register.
+ *
+ * Return: true if the register is supported, false otherwise.
+ */
+bool cppc_ospm_nominal_perf_supported(int cpu)
+{
+ struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu);
+
+ return cpc_desc &&
+ cpc_reg_writable(&cpc_desc->cpc_regs[OSPM_NOMINAL_PERF]);
+}
+EXPORT_SYMBOL_GPL(cppc_ospm_nominal_perf_supported);
+
/**
* cppc_get_auto_act_window() - Read autonomous activity window register.
* @cpu: CPU from which to read register.
diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h
index 3394e1b208be..a078ad5964c2 100644
--- a/include/acpi/cppc_acpi.h
+++ b/include/acpi/cppc_acpi.h
@@ -185,6 +185,8 @@ 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 bool cppc_ospm_nominal_perf_supported(int cpu);
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, u64 *enable);
@@ -277,6 +279,14 @@ 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 bool cppc_ospm_nominal_perf_supported(int cpu)
+{
+ return false;
+}
static inline int cppc_get_auto_act_window(int cpu, u64 *auto_act_window)
{
return -EOPNOTSUPP;
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH v7 2/3] cpufreq: CPPC: Add ospm_nominal_freq attribute
2026-08-07 21:48 [PATCH v7 0/3] ACPI / cpufreq: CPPC: Add ospm_nominal_perf support Sumit Gupta
2026-08-07 21:48 ` [PATCH v7 1/3] ACPI: " Sumit Gupta
@ 2026-08-07 21:48 ` Sumit Gupta
2026-08-07 21:48 ` [PATCH v7 3/3] cpufreq: CPPC: Reflect the OSPM nominal in boost and limits Sumit Gupta
2 siblings, 0 replies; 4+ messages in thread
From: Sumit Gupta @ 2026-08-07 21:48 UTC (permalink / raw)
To: rafael, viresh.kumar, pierre.gondois, christian.loehle,
ionela.voinescu, zhenglifeng1, zhanjie9, lenb, saket.dumbre,
mario.limonciello, linux-kernel, linux-pm, linux-acpi,
acpica-devel, linux-tegra
Cc: treding, jonathanh, vsethi, ksitaraman, sanjayc, mochs, bbasu,
sumitg
OSPM Nominal Performance (ACPI 6.6, Section 8.4.6.1.2.6) lets the OS
request a nominal performance level below the platform-reported one. The
platform treats performance above that level as boosted and below it as
throttled for its power and thermal decisions. A lower value moves that
boundary down, so sustained work runs at a lower point while the range
above it remains available as boost.
Expose it as a per-policy cpufreq attribute in kHz, matching the unit
convention of the other frequency attributes:
/sys/devices/system/cpu/cpuX/cpufreq/ospm_nominal_freq
The attribute is write-only as the register cannot be read back. Writes
are converted with cppc_khz_to_perf() and rejected unless they fall in
[Lowest Performance, Nominal Performance].
Also track the register in the OSPM-set save/restore table, so a
requested value survives CPU hotplug and suspend/resume. The store
handler records the request rather than init() capturing a firmware
value, and driver unload reverts the register to the platform-reported
Nominal Performance.
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
.../ABI/testing/sysfs-devices-system-cpu | 24 +++++++
drivers/cpufreq/cppc_cpufreq.c | 70 +++++++++++++++++--
2 files changed, 90 insertions(+), 4 deletions(-)
diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu
index 82d10d556cc8..59aafcb2af97 100644
--- a/Documentation/ABI/testing/sysfs-devices-system-cpu
+++ b/Documentation/ABI/testing/sysfs-devices-system-cpu
@@ -346,6 +346,30 @@ 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: August 2026
+Contact: linux-pm@vger.kernel.org
+Description: OSPM Nominal Performance (kHz), write-only
+
+ 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.
+
+ Write a value in kHz, between the frequencies corresponding to
+ Lowest Performance and Nominal Performance. The register cannot
+ be read back, so this attribute has no read side.
+
+ Note that tasks may be migrated from one CPU to another by the
+ scheduler's load-balancing algorithm, and if different OSPM
+ Nominal Performance values are set for those CPUs (through
+ different cpufreq policies), that may lead to undesirable
+ outcomes. To avoid such issues it is better to set the same
+ value across all policies, or to pin every task potentially
+ sensitive to it to a specific CPU.
+
+ 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/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index 32f38b0c492b..fe714e71826a 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -33,11 +33,13 @@ static struct cpufreq_driver cppc_cpufreq_driver;
* 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.
+ * Autonomous Selection (auto_sel) splits the list: the registers before it are
+ * independent of it, and those after it have meaning only while autonomous
+ * selection is enabled. Place a new register on the matching side.
*/
enum cppc_saved_reg_id {
- CPPC_SAVED_AUTO_SEL,
+ CPPC_SAVED_OSPM_NOMINAL_PERF,
+ CPPC_SAVED_AUTO_SEL, /* Entries below need auto_sel enabled. */
CPPC_SAVED_EPP,
CPPC_SAVED_AUTO_ACT_WINDOW,
CPPC_NR_SAVED_REGS,
@@ -50,6 +52,11 @@ struct cppc_saved_reg {
};
static const struct cppc_saved_reg cppc_saved_regs[CPPC_NR_SAVED_REGS] = {
+ /* Write-only: the requested value is tracked in software. */
+ [CPPC_SAVED_OSPM_NOMINAL_PERF] = {
+ .name = "ospm_nominal_perf",
+ .set = cppc_set_ospm_nominal_perf,
+ },
[CPPC_SAVED_AUTO_SEL] = {
.name = "auto_sel",
.get = cppc_get_auto_sel,
@@ -79,6 +86,9 @@ enum cppc_saved_type {
* could not be read
* requested_val - value in effect when the policy last went offline,
* reapplied at online(). U64_MAX if none
+ *
+ * A write-only register cannot be read back, so its store handler sets
+ * firmware_val and requested_val instead of init() and offline().
*/
struct cppc_saved_vals {
u64 firmware_val;
@@ -135,7 +145,17 @@ static void cppc_cpufreq_save_regs(struct cpufreq_policy *policy,
st->suspend_regs_handled = false;
for (i = 0; i < CPPC_NR_SAVED_REGS; i++) {
- if (cppc_saved_regs[i].get(cpu, &val))
+ const struct cppc_saved_reg *reg = &cppc_saved_regs[i];
+
+ /*
+ * A write-only register cannot be read back. It has no
+ * firmware value to capture, and its requested value comes
+ * from the store handler, so do not overwrite it here.
+ */
+ if (!reg->get && saved_type == CPPC_SAVED_REQUESTED)
+ continue;
+
+ if (!reg->get || reg->get(cpu, &val))
val = U64_MAX;
if (saved_type == CPPC_SAVED_FIRMWARE) {
@@ -197,6 +217,12 @@ static void cppc_cpufreq_apply_saved_regs(struct cpufreq_policy *policy,
u64 auto_sel, val;
int i;
+ /* Registers before auto_sel do not depend on it. */
+ for (i = 0; i < CPPC_SAVED_AUTO_SEL; i++) {
+ val = cppc_cpufreq_saved_reg_value(st, i, saved_type);
+ cppc_cpufreq_write_saved_reg(cpu, i, val, saved_type);
+ }
+
auto_sel = cppc_cpufreq_saved_reg_value(st, CPPC_SAVED_AUTO_SEL,
saved_type);
@@ -1385,11 +1411,46 @@ static int cppc_get_perf_limited_filtered(int cpu, u64 *perf_limited)
CPPC_CPUFREQ_ATTR_RW_U64(perf_limited, cppc_get_perf_limited_filtered,
cppc_set_perf_limited)
+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;
+ struct cppc_saved_vals *st;
+ unsigned int freq_khz;
+ u32 perf;
+ int ret;
+
+ ret = kstrtouint(buf, 0, &freq_khz);
+ if (ret)
+ return ret;
+
+ perf = cppc_khz_to_perf(&cpu_data->perf_caps, freq_khz);
+ if (perf < cpu_data->perf_caps.lowest_perf ||
+ perf > cpu_data->perf_caps.nominal_perf)
+ return -EINVAL;
+
+ ret = cppc_set_ospm_nominal_perf(policy->cpu, perf);
+ if (ret)
+ return ret;
+
+ /*
+ * Track the request in software: requested_val is reapplied across
+ * hotplug, and firmware_val makes the register revert to the platform
+ * Nominal on driver unload, since the value cannot be read back.
+ */
+ st = &cppc_cpufreq_policy_state(policy)->regs[CPPC_SAVED_OSPM_NOMINAL_PERF];
+ st->requested_val = perf;
+ st->firmware_val = cpu_data->perf_caps.nominal_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_wo(ospm_nominal_freq);
static struct freq_attr *cppc_cpufreq_attr[] = {
&freqdomain_cpus,
@@ -1397,6 +1458,7 @@ static struct freq_attr *cppc_cpufreq_attr[] = {
&auto_act_window,
&energy_performance_preference_val,
&perf_limited,
+ &ospm_nominal_freq,
NULL,
};
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread* [PATCH v7 3/3] cpufreq: CPPC: Reflect the OSPM nominal in boost and limits
2026-08-07 21:48 [PATCH v7 0/3] ACPI / cpufreq: CPPC: Add ospm_nominal_perf support Sumit Gupta
2026-08-07 21:48 ` [PATCH v7 1/3] ACPI: " Sumit Gupta
2026-08-07 21:48 ` [PATCH v7 2/3] cpufreq: CPPC: Add ospm_nominal_freq attribute Sumit Gupta
@ 2026-08-07 21:48 ` Sumit Gupta
2 siblings, 0 replies; 4+ messages in thread
From: Sumit Gupta @ 2026-08-07 21:48 UTC (permalink / raw)
To: rafael, viresh.kumar, pierre.gondois, christian.loehle,
ionela.voinescu, zhenglifeng1, zhanjie9, lenb, saket.dumbre,
mario.limonciello, linux-kernel, linux-pm, linux-acpi,
acpica-devel, linux-tegra
Cc: treding, jonathanh, vsethi, ksitaraman, sanjayc, mochs, bbasu,
sumitg
Boost is the performance range above nominal, so lowering the OSPM
Nominal Performance enlarges the boost range and drops the non-boost
ceiling. Keep the policy limits consistent with the register.
Add cppc_cpufreq_effective_nominal(), which returns the OSPM Nominal
Performance when set and the platform nominal otherwise. Use it for the
non-boost ceiling in set_boost(), and to update the policy limits when
ospm_nominal_freq is written.
The cpufreq core caps scaling_max_freq with a per-policy QoS request,
boost_freq_req, that it sets to cpuinfo.max_freq and refreshes only when
boost is toggled. A write to ospm_nominal_freq changes cpuinfo.max_freq
without toggling boost, so cppc_cpufreq_update_nominal_limits() updates
the request itself, rather than leaving scaling_max_freq at the old
nominal.
When boost is enabled the ceiling is highest_perf, not the nominal, so
the update is skipped. set_boost() applies the new nominal when boost is
turned off.
A platform with highest_perf == nominal_perf has no boost range at boot.
Lowering the OSPM nominal from sysfs can create one. The core, however,
adds its boost FREQ_QOS_MAX request only at policy setup, and only if
boost_supported is already set. Set boost_supported in init() when the
OSPM register is supported and highest_perf > lowest_perf. Boost can
then be enabled after the nominal is lowered.
Suggested-by: Pierre Gondois <pierre.gondois@arm.com>
Signed-off-by: Sumit Gupta <sumitg@nvidia.com>
---
drivers/cpufreq/cppc_cpufreq.c | 65 ++++++++++++++++++++++++++++++++--
1 file changed, 62 insertions(+), 3 deletions(-)
diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c
index fe714e71826a..bcac46ad25c3 100644
--- a/drivers/cpufreq/cppc_cpufreq.c
+++ b/drivers/cpufreq/cppc_cpufreq.c
@@ -892,6 +892,27 @@ static void cppc_cpufreq_put_cpu_data(struct cpufreq_policy *policy)
policy->driver_data = NULL;
}
+/*
+ * Return the non-boost performance ceiling: the OSPM Nominal Performance the
+ * driver last requested, or the platform-reported Nominal Performance if none
+ * was requested.
+ *
+ * The register is write-only, so the requested value comes from the
+ * software-tracked state rather than from hardware.
+ */
+static u32 cppc_cpufreq_effective_nominal(struct cpufreq_policy *policy)
+{
+ const struct cppc_saved_vals *st = cppc_cpufreq_policy_state(policy)->regs;
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ u64 ospm_nominal = st[CPPC_SAVED_OSPM_NOMINAL_PERF].requested_val;
+
+ /* U64_MAX means OSPM has not selected a nominal level. */
+ if (ospm_nominal == U64_MAX)
+ return cpu_data->perf_caps.nominal_perf;
+
+ return (u32)ospm_nominal;
+}
+
static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
{
unsigned int cpu = policy->cpu;
@@ -950,9 +971,14 @@ static int cppc_cpufreq_cpu_init(struct cpufreq_policy *policy)
/*
* If 'highest_perf' is greater than 'nominal_perf', we assume CPU Boost
- * is supported.
+ * is supported. A writable OSPM Nominal Performance register can also
+ * open a boost range at runtime by lowering the nominal, so assume
+ * boost is supported in that case too, letting the core register its
+ * QoS request up front.
*/
- if (caps->highest_perf > caps->nominal_perf)
+ if (caps->highest_perf > caps->nominal_perf ||
+ (caps->highest_perf > caps->lowest_perf &&
+ cppc_ospm_nominal_perf_supported(cpu)))
policy->boost_supported = true;
/* Set policy->cur to max now. The governors will adjust later. */
@@ -1233,11 +1259,12 @@ static int cppc_cpufreq_set_boost(struct cpufreq_policy *policy, int state)
{
struct cppc_cpudata *cpu_data = policy->driver_data;
struct cppc_perf_caps *caps = &cpu_data->perf_caps;
+ u32 nominal = cppc_cpufreq_effective_nominal(policy);
if (state)
policy->cpuinfo.max_freq = cppc_perf_to_khz(caps, caps->highest_perf);
else
- policy->cpuinfo.max_freq = cppc_perf_to_khz(caps, caps->nominal_perf);
+ policy->cpuinfo.max_freq = cppc_perf_to_khz(caps, nominal);
return 0;
}
@@ -1411,6 +1438,36 @@ static int cppc_get_perf_limited_filtered(int cpu, u64 *perf_limited)
CPPC_CPUFREQ_ATTR_RW_U64(perf_limited, cppc_get_perf_limited_filtered,
cppc_set_perf_limited)
+/*
+ * While boost is disabled, the nominal is the ceiling. Set cpuinfo.max_freq to
+ * it and update the core's boost_freq_req to match. The core only syncs
+ * boost_freq_req when boost is enabled or disabled, so a plain nominal change
+ * must update it here.
+ */
+static void cppc_cpufreq_update_nominal_limits(struct cpufreq_policy *policy)
+{
+ struct cppc_cpudata *cpu_data = policy->driver_data;
+ u32 nominal;
+ int ret;
+
+ if (policy->boost_enabled)
+ return;
+
+ nominal = cppc_cpufreq_effective_nominal(policy);
+ policy->cpuinfo.max_freq = cppc_perf_to_khz(&cpu_data->perf_caps,
+ nominal);
+
+ if (freq_qos_request_active(&policy->boost_freq_req)) {
+ ret = freq_qos_update_request(&policy->boost_freq_req,
+ policy->cpuinfo.max_freq);
+ if (ret < 0)
+ pr_debug("Failed to update boost limit on CPU%u (%d)\n",
+ policy->cpu, ret);
+ }
+
+ refresh_frequency_limits(policy);
+}
+
static ssize_t store_ospm_nominal_freq(struct cpufreq_policy *policy,
const char *buf, size_t count)
{
@@ -1442,6 +1499,8 @@ static ssize_t store_ospm_nominal_freq(struct cpufreq_policy *policy,
st->requested_val = perf;
st->firmware_val = cpu_data->perf_caps.nominal_perf;
+ cppc_cpufreq_update_nominal_limits(policy);
+
return count;
}
--
2.34.1
^ permalink raw reply related [flat|nested] 4+ messages in thread