[PATCH v7 15/20] ACPI: CPPC: Keep Performance Limited clearable on NVIDIA T41
From: Christian Loehle
Date: Wed Sep 16 2026 - 13:59:26 EST
From: Sumit Gupta <sumitg@xxxxxxxxxx>
NVIDIA T41 firmware describes Performance Limited as a two-bit field at
offset zero in a DWord SystemMemory access unit. Generic CPPC code must
keep such a field read-only because preserving the remainder when clearing
it requires a read-modify-write which cannot be interlocked with platform
updates.
The remaining bits of this access unit are unimplemented on T41: they read
as zero, writes have no side effects, and no other register uses them. The
Performance Limited register therefore owns the complete access unit, but
shipped firmware does not describe that property accurately.
Add a CPPC platform-quirk table keyed by the DSDT header and carry quirk
behavior through explicit flags. Cache a successful table lookup, copy each
GAS into the driver's private descriptor, and apply fixups before layout
validation, mapping and overlap registration.
Distinguish a genuine non-match from a table-header lookup failure in
acpi_match_platform_list(). Propagate lookup errors from CPPC probe without
caching them, so a transient mapping failure cannot disable the workaround
for every later processor. Existing matcher callers still treat all
negative results as no match.
For the known T41 layout only, widen a two-bit Performance Limited field at
offset zero to its 32-bit access width. Clearing both status bits can then
be issued as one DWord write of zero without a stale read. Corrected
firmware which reports the full width is unchanged. The workaround
therefore lapses automatically when corrected firmware ships.
Link: https://lore.kernel.org/lkml/d5f1ea9b-53b7-4db2-983a-b5be8e71a371@xxxxxxx/
Signed-off-by: Sumit Gupta <sumitg@xxxxxxxxxx>
[ Rework quirk matching and fixup placement; propagate lookup failures
without caching them. ]
Signed-off-by: Christian Loehle <christian.loehle@xxxxxxx>
---
drivers/acpi/cppc_acpi.c | 72 ++++++++++++++++++++++++++++++++++++++++
drivers/acpi/utils.c | 13 ++++++--
2 files changed, 82 insertions(+), 3 deletions(-)
diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c
index 6d130381245e..0e218f2be0fe 100644
--- a/drivers/acpi/cppc_acpi.c
+++ b/drivers/acpi/cppc_acpi.c
@@ -330,6 +330,68 @@ static unsigned int cpc_reg_access_width(const struct cpc_reg *reg)
return reg->bit_width;
}
+enum cpc_platform_quirk {
+ CPC_QUIRK_PERF_LIMITED_OWNS_UNIT = BIT(0),
+};
+
+static const struct acpi_platform_list cpc_platform_quirk_list[] = {
+ {
+ .oem_id = "NVIDIA",
+ .oem_table_id = "T41",
+ .table = ACPI_SIG_DSDT,
+ .pred = all_versions,
+ .reason = "Performance Limited owns its access unit",
+ .data = CPC_QUIRK_PERF_LIMITED_OWNS_UNIT,
+ },
+ { }
+};
+
+static DEFINE_MUTEX(cpc_platform_quirk_lock);
+static bool cpc_platform_quirks_initialized;
+static u32 cpc_platform_quirks;
+
+static int cpc_get_platform_quirks(u32 *quirks)
+{
+ int idx, ret = 0;
+
+ mutex_lock(&cpc_platform_quirk_lock);
+ if (!cpc_platform_quirks_initialized) {
+ idx = acpi_match_platform_list(cpc_platform_quirk_list);
+ if (idx < 0 && idx != -ENODEV) {
+ ret = idx;
+ goto out;
+ }
+ if (idx >= 0)
+ cpc_platform_quirks = cpc_platform_quirk_list[idx].data;
+ cpc_platform_quirks_initialized = true;
+ }
+ *quirks = cpc_platform_quirks;
+out:
+ mutex_unlock(&cpc_platform_quirk_lock);
+
+ return ret;
+}
+
+static void cpc_apply_platform_quirks(struct cpc_reg *reg,
+ unsigned int reg_idx, u32 quirks)
+{
+ unsigned int access_width;
+
+ if (!(quirks & CPC_QUIRK_PERF_LIMITED_OWNS_UNIT) ||
+ reg_idx != PERF_LIMITED ||
+ reg->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY ||
+ reg->bit_width != 2 || reg->bit_offset)
+ return;
+
+ access_width = cpc_reg_access_width(reg);
+ if (access_width != 32)
+ return;
+
+ reg->bit_width = access_width;
+ pr_info_once("firmware quirk: Performance Limited owns its access unit, using Bit Width %u\n",
+ access_width);
+}
+
static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg)
{
unsigned int width = cpc_reg_access_width(®->cpc_entry.reg);
@@ -1857,6 +1919,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr)
acpi_handle handle = pr->handle;
unsigned int num_ent, i, cpc_rev;
u32 unsupported_regs = 0;
+ u32 platform_quirks;
int pcc_subspace_id = -1;
bool pcc_data_ref = false;
bool cpc_present = false;
@@ -1866,6 +1929,12 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr)
if (per_cpu(cpc_desc_ptr, pr->id))
return 0;
+ ret = cpc_get_platform_quirks(&platform_quirks);
+ if (ret) {
+ pr_err("CPU%d: failed to match CPPC platform quirks: %d\n",
+ pr->id, ret);
+ return ret;
+ }
per_cpu(cpu_pcc_subspace_idx, pr->id) = -1;
if (!osc_sb_cppc2_support_acked) {
@@ -2002,6 +2071,9 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr)
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));
+ gas_t = &cpc_ptr->cpc_regs[i - 2].cpc_entry.reg;
+ cpc_apply_platform_quirks(gas_t, i - 2,
+ platform_quirks);
/*
* The PCC Subspace index is encoded inside
diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c
index d499b72574ab..7c584d490d6a 100644
--- a/drivers/acpi/utils.c
+++ b/drivers/acpi/utils.c
@@ -1069,19 +1069,26 @@ EXPORT_SYMBOL(acpi_dev_is_video_device);
* @plat: pointer to acpi_platform_list table terminated by a NULL entry
*
* Return the matched index if the system is found in the platform list.
- * Otherwise, return a negative error code.
+ * Return -ENODEV for no match, or another negative error code if a table
+ * header could not be read and no entry matched.
*/
int acpi_match_platform_list(const struct acpi_platform_list *plat)
{
struct acpi_table_header hdr;
+ acpi_status status;
+ int ret = -ENODEV;
int idx = 0;
if (acpi_disabled)
return -ENODEV;
for (; plat->oem_id[0]; plat++, idx++) {
- if (ACPI_FAILURE(acpi_get_table_header(plat->table, 0, &hdr)))
+ status = acpi_get_table_header(plat->table, 0, &hdr);
+ if (ACPI_FAILURE(status)) {
+ if (status != AE_NOT_FOUND)
+ ret = status == AE_NO_MEMORY ? -ENOMEM : -EIO;
continue;
+ }
if (strncmp(plat->oem_id, hdr.oem_id, ACPI_OEM_ID_SIZE))
continue;
@@ -1096,6 +1103,6 @@ int acpi_match_platform_list(const struct acpi_platform_list *plat)
return idx;
}
- return -ENODEV;
+ return ret;
}
EXPORT_SYMBOL(acpi_match_platform_list);
--
2.34.1