From 82287a99415fbfa53ef4fd107dc6d16bbf86d6d1 Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Thu, 3 Sep 2026 20:17:42 +0530 Subject: [PATCH 1/1] ACPI: CPPC: Keep Performance Limited clearable where it owns its unit X-NVConfidentiality: public Firmware may describe Performance Limited as a field narrower than the access unit given by its Access Size. Clearing such a field needs a read-modify-write to preserve the rest of the unit. That cannot be interlocked against the platform setting further status bits. The clear is therefore disabled, and writes to the perf_limited attribute return -EOPNOTSUPP. The generic code cannot do better. Per ACPI 6.6 Section 5.2.3.2, Bit Width is the size of the register while Access Size only describes the transaction. Bits beyond Bit Width are not part of the register, so they may hold unrelated state and Table 8.26 says nothing about them. Some platforms do implement the register alone in its access unit, with the remaining bits unimplemented, reading as zero and without side effects when written. Firmware conveys that by declaring Bit Width 32, and _CPC offers no other way to express it. Describe the register as owning the unit on those platforms. The clear then becomes a single interlocked write with no read, and the status bits stay clearable. Add the NVIDIA platforms with that property, matched on the OEM ID and OEM Table ID of the DSDT. Only a descriptor narrower than its access unit is widened, so firmware which already describes the register accurately is left alone and the fixup lapses once such firmware ships. Reads now return the whole unit, which is correct here because those bits read as zero. Overlap validation is unaffected, since its range derives from Access Size and already covered the complete unit. Amend the descriptor before it is copied, so layout validation, the read-modify-write lock decision and overlap checking all see the corrected width. The descriptor lives in the _CPC output buffer, which this function allocates and frees, so amending it in place is safe. Change-Id: I4817d3d0a08a02603d925819b733428f96ecf0d8 Signed-off-by: Sumit Gupta --- drivers/acpi/cppc_acpi.c | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index a07440ed7f80..1985e19f9eb0 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -330,6 +330,44 @@ static unsigned int cpc_reg_access_width(const struct cpc_reg *reg) return reg->bit_width; } +/* + * Platforms which implement Performance Limited alone in its access unit, with + * the remaining bits unimplemented, reading as zero and without side effects + * when written. + */ +static const struct acpi_platform_list cpc_perf_limited_owns_unit[] = { + { "NVIDIA", "T41", 0, ACPI_SIG_DSDT, all_versions }, + { } +}; + +/* + * A Performance Limited field narrower than its access unit cannot be + * cleared, because preserving the rest of the unit needs a read-modify-write + * and an OSPM lock cannot interlock that against the platform. Where the + * register owns the whole unit, describe it that way so the clear becomes a + * single interlocked write. + * + * Widen only a field at Bit Offset 0, so the widened field still describes + * exactly the access unit. + */ +static void cpc_fixup_perf_limited_width(struct cpc_reg *gas, + unsigned int reg_idx) +{ + unsigned int access_width = cpc_reg_access_width(gas); + + if (reg_idx != PERF_LIMITED || + gas->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY || + gas->bit_offset || gas->bit_width >= access_width) + return; + + if (acpi_match_platform_list(cpc_perf_limited_owns_unit) < 0) + return; + + pr_info_once("Performance Limited owns its access unit, using Bit Width %u\n", + access_width); + gas->bit_width = access_width; +} + static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg) { unsigned int width = cpc_reg_access_width(®->cpc_entry.reg); @@ -1861,6 +1899,8 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) goto out_free; } + cpc_fixup_perf_limited_width(gas_t, i - 2); + cpc_ptr->cpc_regs[i - 2].type = ACPI_TYPE_BUFFER; memcpy(&cpc_ptr->cpc_regs[i - 2].cpc_entry.reg, gas_t, sizeof(*gas_t)); -- 2.34.1