[PATCH v2 10/15] ACPI: CPPC: Reject reads and RMW of write-only controls

Christian Loehle <[email protected]> Sat, 8 Aug 2026 09:26:39 +0100
Newsgroups gmane.linux.kernel,gmane.linux.power-management.general,gmane.linux.acpi.devel
Message-ID <[email protected]>
Between _CPC revision 3 and revision 4, Desired Performance changed from
Read/Write to Write.  Revision 4 also added the write-only OSPM Nominal
Performance control.  ACPI 6.6 section 4.6.3 says reads of write-only bit
positions produce undefined results.

The public Desired Performance helper already rejects revision-4 readback,
but the common register accessor still permits either write-only control to
be read.  Reject both centrally so new callers cannot bypass the revision
rule.

A partial SystemMemory field would also make cpc_write() read its complete
access unit to preserve bits outside the field.  Reject revision-4
descriptions of either write-only control when their geometry requires RMW.
A full-width description remains supported and is written without a
preceding read.

OSPM Nominal Performance is optional, so an inaccessible description of it
must not disable otherwise usable CPPC.  Mark that control unsupported and
continue probing.  Desired Performance remains subject to the mandatory or
autonomous-only control rules.

Fixes: 71e1815113f7 ("ACPI: CPPC: Add support for CPPC v4")
Reported-by: Sashiko <[email protected]>
Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loehle%40arm.com
Signed-off-by: Christian Loehle <[email protected]>
---
 drivers/acpi/cppc_acpi.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c
index ddfc04d275d5..4cc0e69bb397 100644
--- a/drivers/acpi/cppc_acpi.c
+++ b/drivers/acpi/cppc_acpi.c
@@ -333,6 +333,21 @@ static bool cpc_reg_is_writable(unsigned int reg_idx)
 	}
 }
 
+static bool cpc_reg_is_write_only(const struct cpc_desc *cpc_desc,
+				  unsigned int reg_idx)
+{
+	return cpc_desc->version >= CPPC_V4_REV &&
+	       (reg_idx == DESIRED_PERF || reg_idx == OSPM_NOMINAL_PERF);
+}
+
+static void cpc_disable_reg(struct cpc_desc *cpc_desc, unsigned int reg_idx)
+{
+	struct cpc_register_resource *reg = &cpc_desc->cpc_regs[reg_idx];
+
+	reg->type = ACPI_TYPE_INTEGER;
+	reg->cpc_entry.int_value = 0;
+}
+
 static bool cpc_sysmem_reg_needs_rmw(const struct cpc_register_resource *reg)
 {
 	const struct cpc_reg *gas = &reg->cpc_entry.reg;
@@ -363,6 +378,17 @@ static int cpc_validate_sysmem_reg(const struct cpc_desc *cpc_desc,
 	if (gas->address & (access_size - 1))
 		goto invalid;
 
+	if (cpc_reg_is_write_only(cpc_desc, reg_idx) &&
+	    (gas->bit_offset || gas->bit_width != access_width)) {
+		const char *name = reg_idx == DESIRED_PERF ?
+				   "Desired Performance" :
+				   "OSPM Nominal Performance";
+
+		pr_err("CPU%d: _CPC v%d %s register requires unsupported read-modify-write\n",
+		       cpc_desc->cpu_id, cpc_desc->version, name);
+		return -EINVAL;
+	}
+
 	return 0;
 
 invalid:
@@ -1290,6 +1316,12 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr)
 					size_t access_width;
 
 					err = cpc_validate_sysmem_reg(cpc_ptr, gas_t, i - 2);
+					if (err && i - 2 == OSPM_NOMINAL_PERF) {
+						pr_warn("CPU%d: disabling inaccessible OSPM Nominal Performance register\n",
+							pr->id);
+						cpc_disable_reg(cpc_ptr, i - 2);
+						continue;
+					}
 					if (err) {
 						ret = err;
 						goto out_free;
@@ -1758,6 +1790,8 @@ static int cppc_get_reg_val(int cpu, enum cppc_regs reg_idx, u64 *val)
 		pr_debug("No CPC descriptor for CPU:%d\n", cpu);
 		return -ENODEV;
 	}
+	if (cpc_reg_is_write_only(cpc_desc, reg_idx))
+		return -EOPNOTSUPP;
 
 	reg = &cpc_desc->cpc_regs[reg_idx];
 
-- 
2.34.1