From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 85FD03002AB; Thu, 27 Aug 2026 06:31:12 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812274; cv=none; b=sw+AZQttRSyBq6VyiaL9zVA5XeTX5YWVjly9RgssDM5THc2AMtcmvnBkcbYc6kd+PWhJisXWJfUKhmIoS5aUZkRyljisNaF+DhdOMinGPd6aoEkBvrcjsX1eNdWc++oJTFSHhHAHZHzebUUVLVworhau097w+muZynMr6U41bVk= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812274; c=relaxed/simple; bh=y4x2hBtTKq/UTTtPQl7KCSgGM77FGT9bLH90ajPUoXw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Y0LUkXSs/vqBQ+F8utqyKBogJPeGZvdaYeHIeFY2Ewvk5D7YcAznpb+uVEYiDEB3ri4OWpwPQH7/J2xyLpG7tu3WAMxamuDTbFfr2RL0j+oYeO1Z7OufAGCnHuQh96qg8Acyot6tWZSaqrStT/j3ZZC6nsaDEeyoaekEk8yoTlo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=P66SAV1z; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="P66SAV1z" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id CEC3D169E; Wed, 26 Aug 2026 23:31:07 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 61CF53F85F; Wed, 26 Aug 2026 23:31:09 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812271; bh=y4x2hBtTKq/UTTtPQl7KCSgGM77FGT9bLH90ajPUoXw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=P66SAV1zSeA8YQEjgFEuUhiQ3qAUkSW04l5RFAhvNP46Wl0MGgTYj6BqYy+EgYMiq IfbxAfR/z2BixyVu7sWoxxTDpXckyp5JLgMx0ZkuE6iBdj8rK/ubbBfKGXOrgcwFjT nwgEEDFtZ7PiOYz+X2A4ZSHBrCvKoKfGgmUYiVM8= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle , Sashiko Subject: [PATCH v5 01/15] ACPI: CPPC: Validate the _CPC package header Date: Thu, 27 Aug 2026 07:30:46 +0100 Message-Id: <20260827063100.2741066-2-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The _CPC NumEntries field includes every package element, including NumEntries and Revision. acpi_cppc_processor_probe() nevertheless reads those first two elements before checking that they exist and trusts NumEntries when walking the remaining elements. Reject packages with fewer than two elements and NumEntries values which exceed the package count before accessing Revision or iterating over register descriptors. Although the specification defines NumEntries as the number of package elements, tolerate additional trailing elements because ignoring them is safe and avoids rejecting padded firmware. Revision is specified as a BYTE, but the parser assigns its 64-bit AML Integer to an unsigned int before validating it. Reject values above U8_MAX before conversion so, for example, 0x100000004 cannot truncate to revision 4. Fixes: 337aadff8e45 ("ACPI: Introduce CPU performance controls using CPPC") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loe= hle%40arm.com Link: https://sashiko.dev/#/patchset/20260808082644.1251332-1-christian.loe= hle%40arm.com Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index fef54fcd00b7..3b8cdf88e31d 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -793,6 +793,11 @@ int acpi_cppc_processor_probe(struct acpi_processor *p= r) } =20 out_obj =3D (union acpi_object *) output.pointer; + if (out_obj->package.count < 2) { + pr_debug("Unexpected _CPC package count (%u) for CPU:%d\n", + out_obj->package.count, pr->id); + goto out_buf_free; + } =20 cpc_ptr =3D kzalloc_obj(struct cpc_desc); if (!cpc_ptr) { @@ -803,12 +808,15 @@ int acpi_cppc_processor_probe(struct acpi_processor *= pr) /* First entry is NumEntries. */ cpc_obj =3D &out_obj->package.elements[0]; if (cpc_obj->type =3D=3D ACPI_TYPE_INTEGER) { - num_ent =3D cpc_obj->integer.value; - if (num_ent <=3D 1) { - pr_debug("Unexpected _CPC NumEntries value (%d) for CPU:%d\n", - num_ent, pr->id); + if (cpc_obj->integer.value < 2 || + cpc_obj->integer.value > out_obj->package.count) { + pr_debug("Invalid _CPC NumEntries (%llu) for package count (%u) on CPU:= %d\n", + cpc_obj->integer.value, out_obj->package.count, + pr->id); goto out_free; } + + num_ent =3D cpc_obj->integer.value; } else { pr_debug("Unexpected _CPC NumEntries entry type (%d) for CPU:%d\n", cpc_obj->type, pr->id); @@ -818,6 +826,12 @@ int acpi_cppc_processor_probe(struct acpi_processor *p= r) /* Second entry should be revision. */ cpc_obj =3D &out_obj->package.elements[1]; if (cpc_obj->type =3D=3D ACPI_TYPE_INTEGER) { + if (cpc_obj->integer.value > U8_MAX) { + pr_debug("Invalid _CPC Revision (%llu) for CPU:%d\n", + cpc_obj->integer.value, pr->id); + ret =3D -EINVAL; + goto out_free; + } cpc_rev =3D cpc_obj->integer.value; } else { pr_debug("Unexpected _CPC Revision entry type (%d) for CPU:%d\n", --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id D3C19357D00; Thu, 27 Aug 2026 06:31:14 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812277; cv=none; b=QgBgpMWH8PYoqXpRyv8mQSK+6gRxNPU64Dq8nn39Lke+WiyHQRVnabzI7FyTzJXe7RVmjB0v996O2CDbvBmOszhhfCJByVmzqtyktlsISqGufLUwRB3YbmlDDDEeJ5T1r4S3PNHcjsBfLzYBMaOPfkVVmHZ1kAvkdJBgyy0fAYs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812277; c=relaxed/simple; bh=Q9p0h3cSEJuLue32sY9m1AtHv9UYmZkLhU9AmCgvRho=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=rcPdstcxKvR1E8mZtQZ2ViB+j8OAVuY5jD9UvR3gukc1erlr5r1r9I99ps1uIy4yk15Qgro+gME9LPHyTiL3eNdiZZ/Ex2PJoMK+06yLQkjEf0KZ2g0+hjhJYeAItut0hf+Zrm04NmH72YgJEXusuhXceE/b0f/KktC5KO/PP6I= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=fWwopnIh; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="fWwopnIh" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 603A61BB0; Wed, 26 Aug 2026 23:31:10 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 0669E3F85F; Wed, 26 Aug 2026 23:31:11 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812274; bh=Q9p0h3cSEJuLue32sY9m1AtHv9UYmZkLhU9AmCgvRho=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=fWwopnIhU+jzmo0k8Neadw4gQiZ1pDc/DzU3KnfNMQJJst2oEAd/fc1kaMtk1ZfbA Ng8M3bCXnkZ8SghXWkgkBNCdcvYP3k8E9bS7lmEFDkjMFIbpDgRfu5j6rWR5UfFh26 /4dzo8DLXeOlr6kuFZ01bJ3hdfjKbMlpKe+RLmr4= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle , Sashiko Subject: [PATCH v5 02/15] ACPI: CPPC: Validate _CPC entry and control semantics Date: Thu, 27 Aug 2026 07:30:47 +0100 Message-Id: <20260827063100.2741066-3-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Writable _CPC controls are Register descriptors encoded as Buffer objects. Integer entries represent fixed values or unsupported optional registers; Autonomous Selection Integer 1 is the special immutable form which enables operation without Desired Performance. The parser accepts arbitrary object types and cpc_write() assumes that its argument contains a GAS. Malformed firmware can therefore make it interpret an Integer union member as a register. Validate the portion of each encoding consumed by Linux: bound Integer DWORD forms to 32 bits, and require Buffer entries to start with a complete Generic Register descriptor with the expected header. Continue tolerating Integer 0 for an absent optional register and retain type checks in cpc_write() as defense in depth. Capability registers are read into u64 temporaries but exposed through u32 fields. Reject values above U32_MAX instead of allowing them to be truncate= d; in particular, a truncated Highest Performance value can otherwise become a zero divisor in the performance-to-frequency conversion. Check mandatory object presence separately from the Integer-zero convention for absent optional fields. ACPI does not reserve zero in the abstract Lowest Performance scale, so accept a present Lowest Performance DWORD of zero and remove the matching runtime rejection. Performance Limited is listed as a required Buffer, but the interface does not depend on it to control performance and the specification permits a platform with no limiting indication to always report zero. Preserve Linux's compatibility with firmware that represents that case using a NULL register descriptor instead of disabling CPPC entirely. Emit an error when a present _CPC package fails parsing or initialization so such firmware and resource failures no longer silently suppress cpufreq. Fixes: 337aadff8e45 ("ACPI: Introduce CPU performance controls using CPPC") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loe= hle%40arm.com Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 124 +++++++++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 13 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 3b8cdf88e31d..d9d5c877b9fd 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -129,6 +129,21 @@ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); !!(cpc)->cpc_entry.int_value : \ !IS_NULL_REG(&(cpc)->cpc_entry.reg)) =20 +static bool cpc_is_writable(const struct cpc_register_resource *cpc) +{ + return cpc->type =3D=3D ACPI_TYPE_BUFFER && + !IS_NULL_REG(&cpc->cpc_entry.reg); +} + +static bool cpc_entry_present(const struct cpc_register_resource *cpc) +{ + if (cpc->type =3D=3D ACPI_TYPE_INTEGER) + return true; + + return cpc->type =3D=3D ACPI_TYPE_BUFFER && + !IS_NULL_REG(&cpc->cpc_entry.reg); +} + /* * Each bit indicates the optionality of the register in per-cpu * cpc_regs[] with the corresponding index. 0 means mandatory and 1 @@ -142,6 +157,29 @@ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); */ #define IS_OPTIONAL_CPC_REG(reg_idx) (REG_OPTIONAL & (1U << (reg_idx))) =20 +static bool cpc_integer_entry_valid(unsigned int reg_idx, u64 value) +{ + switch (reg_idx) { + case HIGHEST_PERF: + case NOMINAL_PERF: + case LOW_NON_LINEAR_PERF: + case LOWEST_PERF: + case CTR_WRAP_TIME: + case REFERENCE_PERF: + case LOWEST_FREQ: + case NOMINAL_FREQ: + return value <=3D U32_MAX; + case AUTO_SEL_ENABLE: + return value <=3D 1; + case DESIRED_PERF: + /* Validated against Autonomous Selection after parsing. */ + return value =3D=3D 0; + default: + /* Tolerate the customary Integer 0 for an absent option. */ + return value =3D=3D 0 && IS_OPTIONAL_CPC_REG(reg_idx); + } +} + /* * Arbitrary Retries in case the remote processor is slow to respond * to PCC commands. Keeping it high enough to cover emulators where @@ -150,6 +188,8 @@ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); #define NUM_RETRIES 500ULL =20 #define OVER_16BTS_MASK ~0xFFFFULL +#define CPC_GENERIC_REGISTER_DESCRIPTOR 0x82 +#define CPC_GENERIC_REGISTER_LENGTH (sizeof(struct cpc_reg) - 3) =20 #define define_one_cppc_ro(_name) \ static struct kobj_attribute _name =3D \ @@ -871,11 +911,32 @@ int acpi_cppc_processor_probe(struct acpi_processor *= pr) cpc_obj =3D &out_obj->package.elements[i]; =20 if (cpc_obj->type =3D=3D ACPI_TYPE_INTEGER) { - cpc_ptr->cpc_regs[i-2].type =3D ACPI_TYPE_INTEGER; - cpc_ptr->cpc_regs[i-2].cpc_entry.int_value =3D cpc_obj->integer.value; + if (!cpc_integer_entry_valid(i - 2, + cpc_obj->integer.value)) { + pr_debug("Invalid Integer _CPC register %u for CPU:%d\n", + i - 2, pr->id); + ret =3D -EINVAL; + goto out_free; + } + cpc_ptr->cpc_regs[i - 2].type =3D ACPI_TYPE_INTEGER; + cpc_ptr->cpc_regs[i - 2].cpc_entry.int_value =3D cpc_obj->integer.value; } else if (cpc_obj->type =3D=3D ACPI_TYPE_BUFFER) { + if (cpc_obj->buffer.length < sizeof(*gas_t)) { + pr_debug("Invalid register descriptor for CPU:%d\n", + pr->id); + ret =3D -EINVAL; + goto out_free; + } + gas_t =3D (struct cpc_reg *) cpc_obj->buffer.pointer; + if (gas_t->descriptor !=3D CPC_GENERIC_REGISTER_DESCRIPTOR || + gas_t->length !=3D CPC_GENERIC_REGISTER_LENGTH) { + pr_debug("Invalid register resource for CPU:%d\n", + pr->id); + ret =3D -EINVAL; + goto out_free; + } =20 /* * The PCC Subspace index is encoded inside @@ -961,15 +1022,35 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) } per_cpu(cpu_pcc_subspace_idx, pr->id) =3D pcc_subspace_id; =20 + /* + * Performance Limited is required by the specification, but tolerate a + * NULL descriptor used by firmware which cannot report limiting events. + * CPPC control does not depend on this status. + */ + for (i =3D 0; i < num_ent - 2; i++) { + if (i !=3D DESIRED_PERF && i !=3D PERF_LIMITED && + !IS_OPTIONAL_CPC_REG(i) && + !cpc_entry_present(&cpc_ptr->cpc_regs[i])) { + pr_debug("CPU:%d lacks mandatory _CPC register %u\n", + pr->id, i); + ret =3D -EINVAL; + goto out_free; + } + } + /* * In CPPC v1, DESIRED_PERF is mandatory. In CPPC v2, it is optional * only when AUTO_SEL_ENABLE is supported. */ - if (!CPC_SUPPORTED(&cpc_ptr->cpc_regs[DESIRED_PERF]) && + if (!cpc_is_writable(&cpc_ptr->cpc_regs[DESIRED_PERF]) && (!osc_sb_cppc2_support_acked || - !CPC_SUPPORTED(&cpc_ptr->cpc_regs[AUTO_SEL_ENABLE]))) - pr_warn("Desired perf. register is mandatory if CPPC v2 is not supported= " - "or autonomous selection is disabled\n"); + cpc_ptr->cpc_regs[AUTO_SEL_ENABLE].type !=3D ACPI_TYPE_INTEGER || + cpc_ptr->cpc_regs[AUTO_SEL_ENABLE].cpc_entry.int_value !=3D 1)) { + pr_debug("CPU:%d lacks a writable Desired Performance register\n", + pr->id); + ret =3D -EINVAL; + goto out_free; + } =20 /* * Initialize the remaining cpc_regs as unsupported. @@ -1027,6 +1108,8 @@ int acpi_cppc_processor_probe(struct acpi_processor *= pr) return 0; =20 out_free: + pr_err("CPU%d: failed to initialize _CPC: %d\n", pr->id, ret); + /* Free all the mapped sys mem areas for this CPU */ for (i =3D 2; i < cpc_ptr->num_entries; i++) { void __iomem *addr =3D cpc_ptr->cpc_regs[i-2].sys_mem_vaddr; @@ -1217,11 +1300,18 @@ static int cpc_write(int cpu, struct cpc_register_r= esource *reg_res, u64 val) u64 prev_val; void __iomem *vaddr =3D NULL; int pcc_ss_id =3D per_cpu(cpu_pcc_subspace_idx, cpu); - struct cpc_reg *reg =3D ®_res->cpc_entry.reg; + struct cpc_reg *reg; struct cpc_desc *cpc_desc; unsigned long flags; bool locked =3D false; =20 + if (reg_res->type !=3D ACPI_TYPE_BUFFER) + return -EOPNOTSUPP; + + reg =3D ®_res->cpc_entry.reg; + if (IS_NULL_REG(reg)) + return -EOPNOTSUPP; + size =3D GET_BIT_WIDTH(reg); =20 if (IS_ENABLED(CONFIG_HAS_IOPORT) && @@ -1364,7 +1454,9 @@ static int cppc_get_reg_val(int cpu, enum cppc_regs r= eg_idx, u64 *val) =20 reg =3D &cpc_desc->cpc_regs[reg_idx]; =20 - if ((reg->type =3D=3D ACPI_TYPE_INTEGER && IS_OPTIONAL_CPC_REG(reg_idx) && + /* Desired may be absent for immutable autonomous selection. */ + if ((reg->type =3D=3D ACPI_TYPE_INTEGER && + (IS_OPTIONAL_CPC_REG(reg_idx) || reg_idx =3D=3D DESIRED_PERF) && !reg->cpc_entry.int_value) || (reg->type !=3D ACPI_TYPE_INTEGER && IS_NULL_REG(®->cpc_entry.reg))) { pr_debug("CPC register is not supported\n"); @@ -1415,7 +1507,7 @@ static int cppc_set_reg_val(int cpu, enum cppc_regs r= eg_idx, u64 val) reg =3D &cpc_desc->cpc_regs[reg_idx]; =20 /* if a register is writeable, it must be a buffer and not null */ - if ((reg->type !=3D ACPI_TYPE_BUFFER) || IS_NULL_REG(®->cpc_entry.reg)= ) { + if (!cpc_is_writable(reg)) { pr_debug("CPC register is not supported\n"); return -EOPNOTSUPP; } @@ -1505,7 +1597,7 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_c= aps *perf_caps) struct cpc_register_resource *highest_reg, *lowest_reg, *lowest_non_linear_reg, *nominal_reg, *reference_reg, *guaranteed_reg, *low_freq_reg =3D NULL, *nom_freq_reg =3D NULL; - u64 high, low, guaranteed, nom, ref, min_nonlinear, + u64 high, low, guaranteed =3D 0, nom, ref, min_nonlinear, low_f =3D 0, nom_f =3D 0; int pcc_ss_id =3D per_cpu(cpu_pcc_subspace_idx, cpunum); struct cppc_pcc_data *pcc_ss_data =3D NULL; @@ -1588,7 +1680,9 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_c= aps *perf_caps) goto out_err; perf_caps->lowest_nonlinear_perf =3D min_nonlinear; =20 - if (!high || !low || !nom || !ref || !min_nonlinear) { + if (!high || !nom || !ref || !min_nonlinear || + high > U32_MAX || low > U32_MAX || guaranteed > U32_MAX || + nom > U32_MAX || ref > U32_MAX || min_nonlinear > U32_MAX) { ret =3D -EFAULT; goto out_err; } @@ -1605,6 +1699,10 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_= caps *perf_caps) if (ret) goto out_err; } + if (low_f > U32_MAX || nom_f > U32_MAX) { + ret =3D -EFAULT; + goto out_err; + } =20 perf_caps->lowest_freq =3D low_f; perf_caps->nominal_freq =3D nom_f; @@ -1791,13 +1889,13 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctr= ls *perf_ctrls, bool enable) return -ENODEV; } =20 - if (CPC_SUPPORTED(auto_sel_reg)) { + if (cpc_is_writable(auto_sel_reg)) { ret =3D cpc_write(cpu, auto_sel_reg, enable); if (ret) return ret; } =20 - if (CPC_SUPPORTED(epp_set_reg)) { + if (cpc_is_writable(epp_set_reg)) { ret =3D cpc_write(cpu, epp_set_reg, perf_ctrls->energy_perf); if (ret) return ret; --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id A47A733D4E4; Thu, 27 Aug 2026 06:31:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812283; cv=none; b=df9SqH3JdjCfzUodLbxmHmxP1fZ7gdTVF6wTp8obtNXsiL5RJc1YvrZf3Cr+X+RGuya133zC/sMmlabBfnikKbsz9JzQSj1Sj9wfWTvs+5NaOxVhjh4hbdqhTdcMbqbQtODy3t8V6TLdGWSXBBGcsMdtuaHGdeksYz1c4o+/8FI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812283; c=relaxed/simple; bh=9QzkUxNBz5ZQQplQi8yLgRlQWqlVR1d6iUUyRE357zY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=OveOv8BweDhKYZS752d0PquV3ixoj2RY2+DM5ElX1IVMRtnwtZ0WsNbvlXznQ+kETbfYGVQSa62jEMCdruFMVQ+faNchVYAa9Xzo9JsNiFbLOSDRXFFOZqJjDonplV4A/19brKJcO2OVCtYV3laj0C1aD5///tjXHBTm6VSBEpA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=VTBIcU7p; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="VTBIcU7p" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 47A281691; Wed, 26 Aug 2026 23:31:13 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 8426F3F85F; Wed, 26 Aug 2026 23:31:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812277; bh=9QzkUxNBz5ZQQplQi8yLgRlQWqlVR1d6iUUyRE357zY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=VTBIcU7pDXMry4EjC6VYzHagpFOlNsh6SzvjiKzsoMX3zWPnpzgGTUTfi/QkG8EZU z+MUvn5DFOYZM/aXnKNm2K9h4x2IXXO2nRHeWInr2ehNvEJb1tlQUL/tkCKVYOuFo3 JQlNTsuD5VMep/Wjb+bucAdIUoPMBqtNfAuBCzyQ= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle , Sashiko Subject: [PATCH v5 03/15] ACPI: CPPC: Propagate performance-control write errors Date: Thu, 27 Aug 2026 07:30:48 +0100 Message-Id: <20260827063100.2741066-4-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" cppc_set_perf() can skip malformed controls, discard cpc_write() failures, and report success without programming the requested performance tuple. Return every control-write error to the caller. For mixed PCC and non-PCC performance controls, complete all requested non-PCC writes before taking PCC ownership or changing its payload. A non-PCC failure therefore cannot submit only the PCC portion of a request. Once ownership is held, stage only PCC controls and mark the command pending after successful staging. Treat zero Minimum and Maximum Performance as values rather than omitted updates. Lowest Performance may legitimately be zero, and skipping such a request can leave a stale nonzero minimum programmed. All in-tree callers provide both bounds when calling cppc_set_perf(). Validate every requested PCC field before the first payload store. The current PCC accessor can fail only for a malformed type or unsupported width, and the descriptor type is already known to be writable. Checking all widths up front prevents a later malformed field from leaving an earlier value in shared memory for a subsequent doorbell to commit. Multiple Phase-I callers may set the pending flag to true while holding the shared side of pcc_lock. Mark this intentional same-value store with WRITE_ONCE(); transitions back to false remain protected by the exclusive side. If PCC staging fails while another CPU has already staged a request, take the exclusive PCC lock and abort the pending batch before returning. This advances the write generation and wakes Phase-II waiters which would otherwise wait indefinitely for a doorbell that no CPU will ring. Cross-address-space updates cannot be atomic, but this ordering ensures a known non-PCC failure never commits the PCC portion by itself. Fixes: 337aadff8e45 ("ACPI: Introduce CPU performance controls using CPPC") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loe= hle%40arm.com Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 143 ++++++++++++++++++++++++++++----------- 1 file changed, 104 insertions(+), 39 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index d9d5c877b9fd..3fbfb2eea6cf 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -233,6 +233,19 @@ show_cppc_data(cppc_get_perf_ctrs, cppc_perf_fb_ctrs, = wraparound_time); (reg)->space_id !=3D ACPI_ADR_SPACE_PLATFORM_COMM) ? \ (8 << ((reg)->access_width - 1)) : (reg)->bit_width) =20 +static bool cpc_pcc_write_supported(const struct cpc_register_resource *re= g) +{ + switch (GET_BIT_WIDTH(®->cpc_entry.reg)) { + case 8: + case 16: + case 32: + case 64: + return true; + default: + return false; + } +} + /* Shift and apply the mask for CPC reads/writes */ #define MASK_VAL_READ(reg, val) (((val) >> (reg)->bit_offset) & \ GENMASK(((reg)->bit_width) - 1, 0)) @@ -373,13 +386,45 @@ static int check_pcc_chan(int pcc_ss_id, bool chk_err= _bit) return ret; } =20 +static void cppc_complete_pcc_write(struct cppc_pcc_data *pcc_ss_data, + int ret) +{ + int i; + + if (unlikely(ret)) { + for_each_possible_cpu(i) { + struct cpc_desc *desc =3D per_cpu(cpc_desc_ptr, i); + + if (!desc) + continue; + + if (desc->write_cmd_id =3D=3D pcc_ss_data->pcc_write_cnt) + desc->write_cmd_status =3D ret; + } + } + + pcc_ss_data->pcc_write_cnt++; + wake_up_all(&pcc_ss_data->pcc_write_wait_q); +} + +/* The caller must hold pcc_lock for write. */ +static void cppc_abort_pending_pcc_write(struct cppc_pcc_data *pcc_ss_data, + int ret) +{ + if (!pcc_ss_data->pending_pcc_write_cmd) + return; + + pcc_ss_data->pending_pcc_write_cmd =3D false; + cppc_complete_pcc_write(pcc_ss_data, ret); +} + /* * This function transfers the ownership of the PCC to the platform * So it must be called while holding write_lock(pcc_lock) */ static int send_pcc_cmd(int pcc_ss_id, u16 cmd) { - int ret =3D -EIO, i; + int ret =3D -EIO; struct cppc_pcc_data *pcc_ss_data =3D pcc_data[pcc_ss_id]; struct acpi_pcct_shared_memory __iomem *generic_comm_base =3D pcc_ss_data->pcc_channel->shmem; @@ -471,21 +516,8 @@ static int send_pcc_cmd(int pcc_ss_id, u16 cmd) mbox_client_txdone(pcc_ss_data->pcc_channel->mchan, ret); =20 end: - if (cmd =3D=3D CMD_WRITE) { - if (unlikely(ret)) { - for_each_possible_cpu(i) { - struct cpc_desc *desc =3D per_cpu(cpc_desc_ptr, i); - - if (!desc) - continue; - - if (desc->write_cmd_id =3D=3D pcc_ss_data->pcc_write_cnt) - desc->write_cmd_status =3D ret; - } - } - pcc_ss_data->pcc_write_cnt++; - wake_up_all(&pcc_ss_data->pcc_write_wait_q); - } + if (cmd =3D=3D CMD_WRITE) + cppc_complete_pcc_write(pcc_ss_data, ret); =20 return ret; } @@ -2164,7 +2196,7 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *pe= rf_ctrls) struct cpc_register_resource *desired_reg, *min_perf_reg, *max_perf_reg; int pcc_ss_id =3D per_cpu(cpu_pcc_subspace_idx, cpu); struct cppc_pcc_data *pcc_ss_data =3D NULL; - bool regs_in_pcc; + bool desired_pcc, min_pcc, max_pcc, regs_in_pcc; int ret =3D 0; =20 if (!cpc_desc) { @@ -2175,8 +2207,31 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *p= erf_ctrls) desired_reg =3D &cpc_desc->cpc_regs[DESIRED_PERF]; min_perf_reg =3D &cpc_desc->cpc_regs[MIN_PERF]; max_perf_reg =3D &cpc_desc->cpc_regs[MAX_PERF]; - regs_in_pcc =3D CPC_IN_PCC(desired_reg) || CPC_IN_PCC(min_perf_reg) || - CPC_IN_PCC(max_perf_reg); + desired_pcc =3D cpc_is_writable(desired_reg) && CPC_IN_PCC(desired_reg); + min_pcc =3D cpc_is_writable(min_perf_reg) && CPC_IN_PCC(min_perf_reg); + max_pcc =3D cpc_is_writable(max_perf_reg) && CPC_IN_PCC(max_perf_reg); + regs_in_pcc =3D desired_pcc || min_pcc || max_pcc; + if ((desired_pcc && !cpc_pcc_write_supported(desired_reg)) || + (min_pcc && !cpc_pcc_write_supported(min_perf_reg)) || + (max_pcc && !cpc_pcc_write_supported(max_perf_reg))) + return -EFAULT; + + /* Do not stage PCC data if a fallible non-PCC write has failed. */ + if (cpc_is_writable(desired_reg) && !desired_pcc) { + ret =3D cpc_write(cpu, desired_reg, perf_ctrls->desired_perf); + if (ret) + return ret; + } + if (cpc_is_writable(min_perf_reg) && !min_pcc) { + ret =3D cpc_write(cpu, min_perf_reg, perf_ctrls->min_perf); + if (ret) + return ret; + } + if (cpc_is_writable(max_perf_reg) && !max_pcc) { + ret =3D cpc_write(cpu, max_perf_reg, perf_ctrls->max_perf); + if (ret) + return ret; + } =20 /* * This is Phase-I where we want to write to CPC registers @@ -2199,30 +2254,32 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *= perf_ctrls) return ret; } } - /* - * Update the pending_write to make sure a PCC CMD_READ will not - * arrive and steal the channel during the switch to write lock - */ - pcc_ss_data->pending_pcc_write_cmd =3D true; - cpc_desc->write_cmd_id =3D pcc_ss_data->pcc_write_cnt; - cpc_desc->write_cmd_status =3D 0; } =20 - if (CPC_SUPPORTED(desired_reg)) - cpc_write(cpu, desired_reg, perf_ctrls->desired_perf); + if (desired_pcc) { + ret =3D cpc_write(cpu, desired_reg, perf_ctrls->desired_perf); + if (ret) + goto out_pcc_read_unlock; + } =20 - /* - * Only write if min_perf and max_perf not zero. Some drivers pass zero - * value to min and max perf, but they don't mean to set the zero value, - * they just don't want to write to those registers. - */ - if (perf_ctrls->min_perf && CPC_SUPPORTED(min_perf_reg)) - cpc_write(cpu, min_perf_reg, perf_ctrls->min_perf); - if (perf_ctrls->max_perf && CPC_SUPPORTED(max_perf_reg)) - cpc_write(cpu, max_perf_reg, perf_ctrls->max_perf); + if (min_pcc) { + ret =3D cpc_write(cpu, min_perf_reg, perf_ctrls->min_perf); + if (ret) + goto out_pcc_read_unlock; + } + if (max_pcc) { + ret =3D cpc_write(cpu, max_perf_reg, perf_ctrls->max_perf); + if (ret) + goto out_pcc_read_unlock; + } =20 - if (regs_in_pcc) + if (regs_in_pcc) { + /* Block a PCC read until the staged payload has been submitted. */ + WRITE_ONCE(pcc_ss_data->pending_pcc_write_cmd, true); + cpc_desc->write_cmd_id =3D pcc_ss_data->pcc_write_cnt; + cpc_desc->write_cmd_status =3D 0; up_read(&pcc_ss_data->pcc_lock); /* END Phase-I */ + } /* * This is Phase-II where we transfer the ownership of PCC to Platform * @@ -2281,9 +2338,17 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *p= erf_ctrls) cpc_desc->write_cmd_id !=3D pcc_ss_data->pcc_write_cnt); =20 /* send_pcc_cmd updates the status in case of failure */ - ret =3D cpc_desc->write_cmd_status; + if (!ret) + ret =3D cpc_desc->write_cmd_status; } return ret; + +out_pcc_read_unlock: + up_read(&pcc_ss_data->pcc_lock); + down_write(&pcc_ss_data->pcc_lock); + cppc_abort_pending_pcc_write(pcc_ss_data, ret); + up_write(&pcc_ss_data->pcc_lock); + return ret; } EXPORT_SYMBOL_GPL(cppc_set_perf); =20 --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 81E5E13777E; Thu, 27 Aug 2026 06:31:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812284; cv=none; b=XhO26u79KNkCrhRVcM0a0VmmBSBKNE6XgfW+M4+mf8SuAyP7KCzXaFESK/r+P/gDMSLzxtxluRy/0X86fIWmC+iSrBJ4hOnRV4LzOPYCNhXRk44dtuSw/m4hZeQV5C+babvUMNYM3RVAk3recBnuslx7gZD32jDMTZieKCWq2t4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812284; c=relaxed/simple; bh=kR2CJ4g/1ICvY139wP+3YJ4emVdbHeu3Rzzr5fTwz1c=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=LjSAFBWlMn8PdHJLZuxE33ttsLmie7/st++8nMgnkHYpO43yL1ZGa5SYwzSNADBmLX8ZbBRoUjUg1AQXG4CIDjIXjJFXoPceUFcknn4dRL2gCy/u11SRhhrsPuK+UGu58zv0fR0DxgWo6x4S4QR8NI09ZSsNmEyjGan9uilDEC0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=QGP5pB+X; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="QGP5pB+X" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 13C9C1BB0; Wed, 26 Aug 2026 23:31:16 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 723B63F85F; Wed, 26 Aug 2026 23:31:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812279; bh=kR2CJ4g/1ICvY139wP+3YJ4emVdbHeu3Rzzr5fTwz1c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QGP5pB+XgHV3vaTUnUMsEhs1+zaY8sdCQeEhdZT1vj11RgPnBrHARjUsbWLyUbzWa hy5nK3z7skXmlVBND1iVf+pvpcuZTTafN6XF8j8D22sAt99/lazw6vUtrW83xsCcPS KgqCJL8n4REF9VASiBVkAQv2SoZVXWOhecZ2Ffw8= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle , Sashiko Subject: [PATCH v5 04/15] ACPI: CPPC: Use 64-bit masks for register fields Date: Thu, 27 Aug 2026 07:30:49 +0100 Message-Id: <20260827063100.2741066-5-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" MASK_VAL_READ() and MASK_VAL_WRITE() operate on u64 values but construct their masks with GENMASK(), whose type is unsigned long. On 32-bit kernels a field wider than 32 bits therefore produces an invalid shift or loses its upper bits. The Generic Address Structure permits QWord access units and the CPPC accessors implement 64-bit MMIO reads and writes. Use GENMASK_ULL() in both directions so the mask matches the value and supported access width on every architecture. Fixes: 60949b7b8054 ("ACPI: CPPC: Fix MASK_VAL() usage") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loe= hle%40arm.com Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 3fbfb2eea6cf..1caac71d3e00 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -248,10 +248,10 @@ static bool cpc_pcc_write_supported(const struct cpc_= register_resource *reg) =20 /* Shift and apply the mask for CPC reads/writes */ #define MASK_VAL_READ(reg, val) (((val) >> (reg)->bit_offset) & \ - GENMASK(((reg)->bit_width) - 1, 0)) + GENMASK_ULL(((reg)->bit_width) - 1, 0)) #define MASK_VAL_WRITE(reg, prev_val, val) \ - ((((val) & GENMASK(((reg)->bit_width) - 1, 0)) << (reg)->bit_offset) | \ - ((prev_val) & ~(GENMASK(((reg)->bit_width) - 1, 0) << (reg)->bit_offset))= ) \ + ((((val) & GENMASK_ULL(((reg)->bit_width) - 1, 0)) << (reg)->bit_offset) = | \ + ((prev_val) & ~(GENMASK_ULL(((reg)->bit_width) - 1, 0) << (reg)->bit_offs= et))) \ =20 static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg) { --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 4408C2C15AC; Thu, 27 Aug 2026 06:31:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812284; cv=none; b=pXi+EPx1jQ2JyS7GF0iNMr+g6HfXMxhfkC4RC029J2Jwm2ELzRdwA+UwEiJ/FbhBoxCAEya1fxZfoMsU2JnznLNuFF3zpgxTSHfyI/Ux68IHpgcqy8bcpVWJav65WwGWDfSpiwwEibEM4MX24a3wE6Y1g690qlNGhMPPcfZg4vM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812284; c=relaxed/simple; bh=VyvBlNJ8+5NGWPT0TeOwPz9DYpbQ4Wj1Uvy+rpRx9Fw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=JkWVTGxJg+GcgtYtHQQrt17qJ+bIQhIz1BJU4GfHvI+wj4d596kZONMF/nn/wKj1cp507SZk1FtkBUBIV0sQ8QBirPFvQMuCSQ+0T77+VxOMso8Ul7fjSXOZr47oHKR1IiXcgV3Mnfgfswoqovf8VwKK8p3+0j7tLrF1G/u8qkc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=EU2CyrR1; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="EU2CyrR1" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id C2E3E169E; Wed, 26 Aug 2026 23:31:18 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 4EE923FB7F; Wed, 26 Aug 2026 23:31:20 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812282; bh=VyvBlNJ8+5NGWPT0TeOwPz9DYpbQ4Wj1Uvy+rpRx9Fw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=EU2CyrR1FwdNlgtn22krkD/VG4Dd8oqTbLzMCAm3fc4HKpP/DPwChduazl9J3nYoc 97IqvIB07/Str24yCGOwdRMcj6PYo+yHr1YskJdHW+Mq3fupfWyeT+a0U9FSmkHFt3 pcgQICVSb+B3xVrB0M7ggW2QFxG+BVB2felP9cWM= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle , Sashiko Subject: [PATCH v5 05/15] ACPI: CPPC: Serialize PCC single-register payload updates Date: Thu, 27 Aug 2026 07:30:50 +0100 Message-Id: <20260827063100.2741066-6-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The PCC doorbell protocol requires OSPM to confirm ownership of the shared subspace before placing a command and its payload there. cppc_set_reg_val_in_pcc() instead modifies the payload before taking pcc_lock. A concurrent command can consequently overwrite or consume the staged value, and OSPM can write the shared region while the platform still owns it. Take the PCC write lock first, wait for the previous command to complete, and keep the lock held while staging the value and submitting CMD_WRITE. This follows the ownership sequence in ACPI 6.5 Section 14.5 and the existing contract documented by send_pcc_cmd(). If ownership acquisition or staging fails, abort any older performance batch before dropping the exclusive lock. This advances its generation and wakes cppc_set_perf() callers which otherwise wait indefinitely for a command this path did not submit. Fixes: e05c75072c2e ("ACPI: CPPC: Add cppc_set_reg_val()") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loe= hle%40arm.com Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loe= hle%40arm.com Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 1caac71d3e00..3f025cfa30f6 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1504,7 +1504,7 @@ static int cppc_get_reg_val(int cpu, enum cppc_regs r= eg_idx, u64 *val) static int cppc_set_reg_val_in_pcc(int cpu, struct cpc_register_resource *= reg, u64 val) { int pcc_ss_id =3D per_cpu(cpu_pcc_subspace_idx, cpu); - struct cppc_pcc_data *pcc_ss_data =3D NULL; + struct cppc_pcc_data *pcc_ss_data; int ret; =20 if (pcc_ss_id < 0) { @@ -1512,15 +1512,26 @@ static int cppc_set_reg_val_in_pcc(int cpu, struct = cpc_register_resource *reg, u return -ENODEV; } =20 - ret =3D cpc_write(cpu, reg, val); - if (ret) - return ret; - pcc_ss_data =3D pcc_data[pcc_ss_id]; + if (!pcc_ss_data) + return -ENODEV; =20 down_write(&pcc_ss_data->pcc_lock); + + ret =3D check_pcc_chan(pcc_ss_id, false); + if (ret) + goto out; + + ret =3D cpc_write(cpu, reg, val); + if (ret) + goto out; + /* after writing CPC, transfer the ownership of PCC to platform */ ret =3D send_pcc_cmd(pcc_ss_id, CMD_WRITE); + +out: + if (ret) + cppc_abort_pending_pcc_write(pcc_ss_data, ret); up_write(&pcc_ss_data->pcc_lock); =20 return ret; --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id BDFDC3624A8; Thu, 27 Aug 2026 06:31:25 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812293; cv=none; b=WgerzHPsNc18KFiimvBQjQaHgvqIbW3x8J9+EOmScTAajelTzNQR75Znqs0FEmmgESIgym+WUqsBOV3wMXBTp9QkjwyvpdYPZ1EB5LjCrGJeA7DonpZNcRSyDOKR7FAe41u8Sn4EfhmHsIaj0ZIydLN1nrgv15davJqZiXpS0UQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812293; c=relaxed/simple; bh=CIxZ8B9ZmnDyKGKJF5GwehKD7G1ho8BRwki8Ld1hSkI=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=GcVl2hPHoBmLTxy9i4ymPGJrzM9hln69RieZzpbj2GlOFtpV4/+lXkOJ6ayBoxJAY0IuAd0bZwEBlaMw2xIzdmfON3ebCyuOT1RXST/PRLEmNDvqjycS8fWcA10oZ1nn+J/Y7jC3dzcyUjmhxZkZ1ER0L/QGkIRunV4cG2CjkME= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=FHtPtq8L; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="FHtPtq8L" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 787891691; Wed, 26 Aug 2026 23:31:21 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id E55063F85F; Wed, 26 Aug 2026 23:31:22 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812285; bh=CIxZ8B9ZmnDyKGKJF5GwehKD7G1ho8BRwki8Ld1hSkI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=FHtPtq8LXylfTfv0hWLjJPrX3lVAt9StHgW0eFRUv9rVlBMhEvgskSv6YBFHrOpcw 70LrfCOojPuARV4kPrXQNncFxifJN2OVOVzj9Vg3mve/wSMLR/CCXXjBdDzeI9kjpu FIzJ9/6Us3n0lc23mrCKWHcYNL3YslHUnpujisJs= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle , Sashiko Subject: [PATCH v5 06/15] ACPI: CPPC: Serialize PCC EPP payload updates Date: Thu, 27 Aug 2026 07:30:51 +0100 Message-Id: <20260827063100.2741066-7-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" cppc_set_epp_perf() stages Autonomous Selection and Energy Performance Preference in the PCC shared region before taking pcc_lock. The platform may still own the subspace, or a concurrent command may consume or overwrite only part of the new payload. Take the PCC write lock and wait for OSPM ownership before staging either control. Keep the lock held until the complete payload has been submitted with CMD_WRITE, so firmware cannot observe a mixed transaction. For a mixed PCC/non-PCC description, complete every fallible non-PCC write before staging PCC data. Cross-address-space updates cannot be atomic, but a non-PCC failure can no longer leave an unsent value in shared memory for a later PCC command to consume. Classify every probe-validated writable control as either PCC or non-PCC. This covers SystemIO along with FFH and SystemMemory and avoids repeating a flexible-address-space _OSC decision that probe has already made. If ownership acquisition or PCC staging fails, abort any older pending performance batch before releasing the exclusive lock so its Phase-II waiters receive the error instead of sleeping indefinitely. This follows the PCC ownership sequence in ACPI 6.5 Section 14.5. Fixes: 7bc1fcd39901 ("ACPI: CPPC: Add AMD pstate energy performance prefere= nce cppc control") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loe= hle%40arm.com Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loe= hle%40arm.com Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 74 ++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 3f025cfa30f6..0fda08628c27 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1909,8 +1909,10 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrl= s *perf_ctrls, bool enable) struct cpc_register_resource *auto_sel_reg; struct cpc_desc *cpc_desc =3D per_cpu(cpc_desc_ptr, cpu); struct cppc_pcc_data *pcc_ss_data =3D NULL; - bool autosel_ffh_sysmem; - bool epp_ffh_sysmem; + bool auto_sel_pcc; + bool auto_sel_non_pcc; + bool epp_pcc; + bool epp_non_pcc; int ret; =20 if (!cpc_desc) { @@ -1921,52 +1923,64 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctr= ls *perf_ctrls, bool enable) auto_sel_reg =3D &cpc_desc->cpc_regs[AUTO_SEL_ENABLE]; epp_set_reg =3D &cpc_desc->cpc_regs[ENERGY_PERF]; =20 - epp_ffh_sysmem =3D CPC_SUPPORTED(epp_set_reg) && - (CPC_IN_FFH(epp_set_reg) || CPC_IN_SYSTEM_MEMORY(epp_set_reg)); - autosel_ffh_sysmem =3D CPC_SUPPORTED(auto_sel_reg) && - (CPC_IN_FFH(auto_sel_reg) || CPC_IN_SYSTEM_MEMORY(auto_sel_reg)); + auto_sel_pcc =3D cpc_is_writable(auto_sel_reg) && + CPC_IN_PCC(auto_sel_reg); + epp_pcc =3D cpc_is_writable(epp_set_reg) && CPC_IN_PCC(epp_set_reg); + auto_sel_non_pcc =3D cpc_is_writable(auto_sel_reg) && !auto_sel_pcc; + epp_non_pcc =3D cpc_is_writable(epp_set_reg) && !epp_pcc; =20 - if (CPC_IN_PCC(epp_set_reg) || CPC_IN_PCC(auto_sel_reg)) { + /* Complete fallible non-PCC writes before staging PCC data. */ + if (auto_sel_non_pcc) { + ret =3D cpc_write(cpu, auto_sel_reg, enable); + if (ret) + return ret; + } + if (epp_non_pcc) { + ret =3D cpc_write(cpu, epp_set_reg, perf_ctrls->energy_perf); + if (ret) + return ret; + } + + if (epp_pcc || auto_sel_pcc) { if (pcc_ss_id < 0) { pr_debug("Invalid pcc_ss_id for CPU:%d\n", cpu); return -ENODEV; } =20 - if (cpc_is_writable(auto_sel_reg)) { + pcc_ss_data =3D pcc_data[pcc_ss_id]; + if (!pcc_ss_data) + return -ENODEV; + + down_write(&pcc_ss_data->pcc_lock); + + ret =3D check_pcc_chan(pcc_ss_id, false); + if (ret) + goto out_unlock; + + if (auto_sel_pcc) { ret =3D cpc_write(cpu, auto_sel_reg, enable); if (ret) - return ret; + goto out_unlock; } =20 - if (cpc_is_writable(epp_set_reg)) { + if (epp_pcc) { ret =3D cpc_write(cpu, epp_set_reg, perf_ctrls->energy_perf); if (ret) - return ret; + goto out_unlock; } =20 - pcc_ss_data =3D pcc_data[pcc_ss_id]; - - down_write(&pcc_ss_data->pcc_lock); /* after writing CPC, transfer the ownership of PCC to platform */ ret =3D send_pcc_cmd(pcc_ss_id, CMD_WRITE); - up_write(&pcc_ss_data->pcc_lock); - } else if (osc_cpc_flexible_adr_space_confirmed && - (epp_ffh_sysmem || autosel_ffh_sysmem)) { - if (autosel_ffh_sysmem) { - ret =3D cpc_write(cpu, auto_sel_reg, enable); - if (ret) - return ret; - } =20 - if (epp_ffh_sysmem) { - ret =3D cpc_write(cpu, epp_set_reg, - perf_ctrls->energy_perf); - if (ret) - return ret; - } +out_unlock: + if (ret) + cppc_abort_pending_pcc_write(pcc_ss_data, ret); + up_write(&pcc_ss_data->pcc_lock); + } else if (epp_non_pcc || auto_sel_non_pcc) { + ret =3D 0; } else { - ret =3D -ENOTSUPP; - pr_debug("_CPC in PCC/FFH/SystemMemory are not supported\n"); + ret =3D -EOPNOTSUPP; + pr_debug("No writable EPP controls for CPU:%d\n", cpu); } =20 return ret; --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 55B4D303CAB; Thu, 27 Aug 2026 06:31:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812294; cv=none; b=Y6IsEyAvV8cyg5akff8wZBwNQ01IGLTSrtJZq0AL7EomylCwlCZofcGq2AVmP5cfCF27zuRRq5EVWMb5Xg6euHeOraMtDdbLwu2cL/NzXrT0np0mDkUPj9sCQmIOJ3Jli6Pwm1zlgGmzmkjil2AjS7xDXOAL8eeSR/MlbH3qbAQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812294; c=relaxed/simple; bh=kgT7HbsQTTEuGbH31fzxChvTf7/zr7ejraUuGK7rw1E=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Y9LJVEECcid4ojUrp7waf3qnTkVqHob4gfg8WpRuqvq5R9Wa3222OnmaQ49MNrNxV5SjLwQqkyNzqsX5X/5wdxl3MM8d9+kTYvb1n9Jw6lqbILCrZxdVzo7Lf8UCkYjgi/Me1IrGVRvznH2FIJ7/DmTGbs319+WUhmgmB3ARIac= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=gqLFIG/r; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="gqLFIG/r" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 1E717169E; Wed, 26 Aug 2026 23:31:24 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id AD4293F85F; Wed, 26 Aug 2026 23:31:25 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812287; bh=kgT7HbsQTTEuGbH31fzxChvTf7/zr7ejraUuGK7rw1E=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=gqLFIG/rgiLVcaWh9T1Hch/YKEtKkhc3fy8pVFwdNK5/aiF5IvVubMV+zmUsyiS0U GCKutUvBMn0/hNU+yHamN6EmeeK6DVhWl9Dw+ViEMkFsmAxnpGq2IQVyLUsMpp2DN3 D7lG7Gkw/MALNeUmA90jdNaLQcPpTRmXI7TjaRFk= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle , Sashiko Subject: [PATCH v5 07/15] ACPI: CPPC: Release CPC descriptors through kobject Date: Thu, 27 Aug 2026 07:30:52 +0100 Message-Id: <20260827063100.2741066-8-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The CPC descriptor embeds the kobject used for its sysfs directory, but it has no release callback and processor exit frees the descriptor immediately after kobject_put(). It also unmaps register resources and releases PCC state before sysfs removal has drained active attribute callbacks. Provide a release callback which unmaps and frees the descriptor, and use the same cleanup helper before kobject initialization. Once initialized, let kobject_put() own descriptor cleanup so its error path cannot free the object twice. On processor exit, first unpublish the per-CPU descriptor and remove its sysfs directory, then release PCC state and drop the kobject reference. This keeps all resources valid while sysfs callbacks are active and also works with delayed kobject release. Fixes: 158c998ea44b ("ACPI / CPPC: add sysfs support to compute delivered p= erformance") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loe= hle%40arm.com Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 50 +++++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 0fda08628c27..4bf17d954d87 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -350,7 +350,27 @@ static struct attribute *cppc_attrs[] =3D { }; ATTRIBUTE_GROUPS(cppc); =20 +static void cppc_free_desc(struct cpc_desc *cpc_ptr) +{ + unsigned int i; + + for (i =3D 2; i < cpc_ptr->num_entries; i++) { + void __iomem *addr =3D cpc_ptr->cpc_regs[i - 2].sys_mem_vaddr; + + if (addr) + iounmap(addr); + } + + kfree(cpc_ptr); +} + +static void cppc_kobj_release(struct kobject *kobj) +{ + cppc_free_desc(to_cpc_desc(kobj)); +} + static const struct kobj_type cppc_ktype =3D { + .release =3D cppc_kobj_release, .sysfs_ops =3D &kobj_sysfs_ops, .default_groups =3D cppc_groups, }; @@ -1133,7 +1153,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *= pr) if (ret) { per_cpu(cpc_desc_ptr, pr->id) =3D NULL; kobject_put(&cpc_ptr->kobj); - goto out_free; + goto out_buf_free; } =20 kfree(output.pointer); @@ -1141,15 +1161,7 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) =20 out_free: pr_err("CPU%d: failed to initialize _CPC: %d\n", pr->id, ret); - - /* Free all the mapped sys mem areas for this CPU */ - for (i =3D 2; i < cpc_ptr->num_entries; i++) { - void __iomem *addr =3D cpc_ptr->cpc_regs[i-2].sys_mem_vaddr; - - if (addr) - iounmap(addr); - } - kfree(cpc_ptr); + cppc_free_desc(cpc_ptr); =20 out_buf_free: kfree(output.pointer); @@ -1166,10 +1178,14 @@ EXPORT_SYMBOL_GPL(acpi_cppc_processor_probe); void acpi_cppc_processor_exit(struct acpi_processor *pr) { struct cpc_desc *cpc_ptr; - unsigned int i; - void __iomem *addr; int pcc_ss_id =3D per_cpu(cpu_pcc_subspace_idx, pr->id); =20 + cpc_ptr =3D per_cpu(cpc_desc_ptr, pr->id); + if (cpc_ptr) { + per_cpu(cpc_desc_ptr, pr->id) =3D NULL; + kobject_del(&cpc_ptr->kobj); + } + if (pcc_ss_id >=3D 0 && pcc_data[pcc_ss_id]) { if (pcc_data[pcc_ss_id]->pcc_channel_acquired) { pcc_data[pcc_ss_id]->refcount--; @@ -1180,20 +1196,12 @@ void acpi_cppc_processor_exit(struct acpi_processor= *pr) } } } + per_cpu(cpu_pcc_subspace_idx, pr->id) =3D -1; =20 - cpc_ptr =3D per_cpu(cpc_desc_ptr, pr->id); if (!cpc_ptr) return; =20 - /* Free all the mapped sys mem areas for this CPU */ - for (i =3D 2; i < cpc_ptr->num_entries; i++) { - addr =3D cpc_ptr->cpc_regs[i-2].sys_mem_vaddr; - if (addr) - iounmap(addr); - } - kobject_put(&cpc_ptr->kobj); - kfree(cpc_ptr); } EXPORT_SYMBOL_GPL(acpi_cppc_processor_exit); =20 --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id D1B1F31280D; Thu, 27 Aug 2026 06:31:30 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812293; cv=none; b=fN6d2h3fd6Yq6T7vh7eucUyhs2fa1+daIUjNqnGtoDlE6vhVPwljLulYzrUVlNiGyg8CJfwKD/aW2ncG/hz2mInbHQIEJXM4nYT5/xSGsG/XAKK1s8uSfxj3+dsjjXMljW0fqMK/v/9gt7Bmc0RC/cdTLyU42Lf9jYwT7E1NFWA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812293; c=relaxed/simple; bh=qrD+HDx7Kose91g5YGgkV5DPoyJT8XGYdnyGmqLKoFU=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=QK+6U3O/NJ39GCR1A3TN+H/iufwClP71lmKxQ5/2Y4wvTojSDrWBErYo9UgaG49fIE6dZf3X6NOkhrRG275jQZd9TcNx1MZ2wxJx2PrKy7xvL6Lm0Q733lU8MRY6uKXPoqZlyQ65hznMk2ilj55e7SjLlw9Lt4tiCZ905kfLM1g= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=pEkRGqVC; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="pEkRGqVC" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 8B7DA1CDD; Wed, 26 Aug 2026 23:31:26 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 4084C3F85F; Wed, 26 Aug 2026 23:31:28 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812290; bh=qrD+HDx7Kose91g5YGgkV5DPoyJT8XGYdnyGmqLKoFU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=pEkRGqVCnao/a9S1W/2YqcQHfUkTLGn+Y0YEoQAtIEmtqrwn1i9VBQDlmbYbszGxf 0A78uOpPZ1zdiS02iDoOFWfej0AkYQvi4H0sMwLMVKKIjYRkraGLHyhGfKqopeLdFR +DHePue06RESNLlrNFJTenAldoqQ88aUr3Rh5OPw= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle Subject: [PATCH v5 08/15] ACPI: CPPC: Release PCC data after probe failures Date: Thu, 27 Aug 2026 07:30:53 +0100 Message-Id: <20260827063100.2741066-9-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" pcc_data_alloc() takes a per-processor reference while parsing the first PCC register. Every later probe failure currently frees the CPC descriptor without dropping that reference. Depending on where probe fails, this leaks an unacquired PCC object, an acquired mailbox channel, or an extra reference to a shared channel. Factor the teardown into pcc_data_put() so it handles both acquired and unacquired state, and invoke it on every failure after allocation. Allocation, reference updates, channel setup, and teardown of a shared subspace are also unsynchronized. Concurrent processor probe and removal can lose a reference update, free a live object, or request and initialize the same mailbox channel twice. Protect that lifecycle with a mutex and publish pcc_channel_acquired only after its lock and wait queue have been initialized. Do not store the temporary pcc_data_alloc() result in the eventual probe return value. A successful allocation must not make a later parsing failure run cleanup and then return success. A later processor-start step can fail after CPPC probe has published its descriptor. Reuse that descriptor when processor start is retried instead of resetting its PCC index and reparsing _CPC. This prevents the live descriptor from becoming detached from its PCC reference. The per-CPU PCC subspace index is zero-initialized. If probe returns before assigning it, a later processor exit can consequently drop the reference for subspace 0 even though this CPU never acquired one. Initialize the index to -1 before any probe return and only release it from exit after a CPC descriptor has been published. Fixes: 85b1407bf6d2 ("ACPI / CPPC: Make CPPC ACPI driver aware of PCC subsp= ace IDs") Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 159 ++++++++++++++++++++++++++------------- 1 file changed, 106 insertions(+), 53 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 4bf17d954d87..118001e1687a 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -81,6 +82,7 @@ struct cppc_pcc_data { =20 /* Array to represent the PCC channel per subspace ID */ static struct cppc_pcc_data *pcc_data[MAX_PCC_SUBSPACES]; +static DEFINE_MUTEX(pcc_data_lock); /* The cpu_pcc_subspace_idx contains per CPU subspace ID */ static DEFINE_PER_CPU(int, cpu_pcc_subspace_idx); =20 @@ -735,35 +737,51 @@ EXPORT_SYMBOL_GPL(acpi_get_psd_map); =20 static int register_pcc_channel(int pcc_ss_idx) { + struct cppc_pcc_data *data; struct pcc_mbox_chan *pcc_chan; u64 usecs_lat; + int ret =3D 0; =20 - if (pcc_ss_idx >=3D 0) { - pcc_chan =3D pcc_mbox_request_channel(&cppc_mbox_cl, pcc_ss_idx); - - if (IS_ERR(pcc_chan)) { - pr_err("Failed to find PCC channel for subspace %d\n", - pcc_ss_idx); - return -ENODEV; - } + if (pcc_ss_idx < 0 || pcc_ss_idx >=3D MAX_PCC_SUBSPACES) + return -EINVAL; =20 - pcc_data[pcc_ss_idx]->pcc_channel =3D pcc_chan; - /* - * cppc_ss->latency is just a Nominal value. In reality - * the remote processor could be much slower to reply. - * So add an arbitrary amount of wait on top of Nominal. - */ - usecs_lat =3D NUM_RETRIES * pcc_chan->latency; - pcc_data[pcc_ss_idx]->deadline_us =3D usecs_lat; - pcc_data[pcc_ss_idx]->pcc_mrtt =3D pcc_chan->min_turnaround_time; - pcc_data[pcc_ss_idx]->pcc_mpar =3D pcc_chan->max_access_rate; - pcc_data[pcc_ss_idx]->pcc_nominal =3D pcc_chan->latency; + mutex_lock(&pcc_data_lock); + data =3D pcc_data[pcc_ss_idx]; + if (!data) { + ret =3D -ENODEV; + goto out_unlock; + } + if (data->pcc_channel_acquired) + goto out_unlock; =20 - /* Set flag so that we don't come here for each CPU. */ - pcc_data[pcc_ss_idx]->pcc_channel_acquired =3D true; + pcc_chan =3D pcc_mbox_request_channel(&cppc_mbox_cl, pcc_ss_idx); + if (IS_ERR(pcc_chan)) { + pr_err("Failed to find PCC channel for subspace %d\n", + pcc_ss_idx); + ret =3D -ENODEV; + goto out_unlock; } =20 - return 0; + data->pcc_channel =3D pcc_chan; + /* + * cppc_ss->latency is just a Nominal value. In reality + * the remote processor could be much slower to reply. + * So add an arbitrary amount of wait on top of Nominal. + */ + usecs_lat =3D NUM_RETRIES * pcc_chan->latency; + data->deadline_us =3D usecs_lat; + data->pcc_mrtt =3D pcc_chan->min_turnaround_time; + data->pcc_mpar =3D pcc_chan->max_access_rate; + data->pcc_nominal =3D pcc_chan->latency; + init_rwsem(&data->pcc_lock); + init_waitqueue_head(&data->pcc_write_wait_q); + + /* Set flag so that we don't come here for each CPU. */ + data->pcc_channel_acquired =3D true; + +out_unlock: + mutex_unlock(&pcc_data_lock); + return ret; } =20 /** @@ -805,19 +823,49 @@ bool __weak cpc_supported_by_cpu(void) */ static int pcc_data_alloc(int pcc_ss_id) { + struct cppc_pcc_data *data; + int ret =3D 0; + if (pcc_ss_id < 0 || pcc_ss_id >=3D MAX_PCC_SUBSPACES) return -EINVAL; =20 - if (pcc_data[pcc_ss_id]) { - pcc_data[pcc_ss_id]->refcount++; - } else { - pcc_data[pcc_ss_id] =3D kzalloc_obj(struct cppc_pcc_data); - if (!pcc_data[pcc_ss_id]) - return -ENOMEM; - pcc_data[pcc_ss_id]->refcount++; + mutex_lock(&pcc_data_lock); + data =3D pcc_data[pcc_ss_id]; + if (!data) { + data =3D kzalloc_obj(struct cppc_pcc_data); + if (!data) { + ret =3D -ENOMEM; + goto out_unlock; + } + pcc_data[pcc_ss_id] =3D data; } + data->refcount++; =20 - return 0; +out_unlock: + mutex_unlock(&pcc_data_lock); + return ret; +} + +static void pcc_data_put(int pcc_ss_id) +{ + struct cppc_pcc_data *data; + + if (pcc_ss_id < 0 || pcc_ss_id >=3D MAX_PCC_SUBSPACES) + return; + + mutex_lock(&pcc_data_lock); + data =3D pcc_data[pcc_ss_id]; + if (!data || --data->refcount) + goto out_unlock; + + pcc_data[pcc_ss_id] =3D NULL; + if (data->pcc_channel_acquired) + pcc_mbox_free_channel(data->pcc_channel); + + kfree(data); + +out_unlock: + mutex_unlock(&pcc_data_lock); } =20 /* @@ -865,8 +913,15 @@ int acpi_cppc_processor_probe(struct acpi_processor *p= r) acpi_handle handle =3D pr->handle; unsigned int num_ent, i, cpc_rev; int pcc_subspace_id =3D -1; + bool pcc_data_ref =3D false; acpi_status status; int ret =3D -ENODATA; + int err; + + if (per_cpu(cpc_desc_ptr, pr->id)) + return 0; + + per_cpu(cpu_pcc_subspace_idx, pr->id) =3D -1; =20 if (!osc_sb_cppc2_support_acked) { pr_debug("CPPC v2 _OSC not acked\n"); @@ -999,8 +1054,12 @@ int acpi_cppc_processor_probe(struct acpi_processor *= pr) if (gas_t->space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM) { if (pcc_subspace_id < 0) { pcc_subspace_id =3D gas_t->access_width; - if (pcc_data_alloc(pcc_subspace_id)) + err =3D pcc_data_alloc(pcc_subspace_id); + if (err) { + ret =3D err; goto out_free; + } + pcc_data_ref =3D true; } else if (pcc_subspace_id !=3D gas_t->access_width) { pr_debug("Mismatched PCC ids in _CPC for CPU:%d\n", pr->id); @@ -1126,13 +1185,10 @@ int acpi_cppc_processor_probe(struct acpi_processor= *pr) goto out_free; =20 /* Register PCC channel once for all PCC subspace ID. */ - if (pcc_subspace_id >=3D 0 && !pcc_data[pcc_subspace_id]->pcc_channel_acq= uired) { + if (pcc_subspace_id >=3D 0) { ret =3D register_pcc_channel(pcc_subspace_id); if (ret) goto out_free; - - init_rwsem(&pcc_data[pcc_subspace_id]->pcc_lock); - init_waitqueue_head(&pcc_data[pcc_subspace_id]->pcc_write_wait_q); } =20 /* Everything looks okay */ @@ -1153,7 +1209,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *= pr) if (ret) { per_cpu(cpc_desc_ptr, pr->id) =3D NULL; kobject_put(&cpc_ptr->kobj); - goto out_buf_free; + goto out_pcc_put; } =20 kfree(output.pointer); @@ -1163,6 +1219,11 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) pr_err("CPU%d: failed to initialize _CPC: %d\n", pr->id, ret); cppc_free_desc(cpc_ptr); =20 +out_pcc_put: + if (pcc_data_ref) + pcc_data_put(pcc_subspace_id); + per_cpu(cpu_pcc_subspace_idx, pr->id) =3D -1; + out_buf_free: kfree(output.pointer); return ret; @@ -1178,28 +1239,20 @@ EXPORT_SYMBOL_GPL(acpi_cppc_processor_probe); void acpi_cppc_processor_exit(struct acpi_processor *pr) { struct cpc_desc *cpc_ptr; - int pcc_ss_id =3D per_cpu(cpu_pcc_subspace_idx, pr->id); + int pcc_ss_id; =20 cpc_ptr =3D per_cpu(cpc_desc_ptr, pr->id); - if (cpc_ptr) { - per_cpu(cpc_desc_ptr, pr->id) =3D NULL; - kobject_del(&cpc_ptr->kobj); + if (!cpc_ptr) { + per_cpu(cpu_pcc_subspace_idx, pr->id) =3D -1; + return; } =20 - if (pcc_ss_id >=3D 0 && pcc_data[pcc_ss_id]) { - if (pcc_data[pcc_ss_id]->pcc_channel_acquired) { - pcc_data[pcc_ss_id]->refcount--; - if (!pcc_data[pcc_ss_id]->refcount) { - pcc_mbox_free_channel(pcc_data[pcc_ss_id]->pcc_channel); - kfree(pcc_data[pcc_ss_id]); - pcc_data[pcc_ss_id] =3D NULL; - } - } - } - per_cpu(cpu_pcc_subspace_idx, pr->id) =3D -1; + pcc_ss_id =3D per_cpu(cpu_pcc_subspace_idx, pr->id); + per_cpu(cpc_desc_ptr, pr->id) =3D NULL; + kobject_del(&cpc_ptr->kobj); =20 - if (!cpc_ptr) - return; + pcc_data_put(pcc_ss_id); + per_cpu(cpu_pcc_subspace_idx, pr->id) =3D -1; =20 kobject_put(&cpc_ptr->kobj); } --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id B04B936493A; Thu, 27 Aug 2026 06:31:33 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812300; cv=none; b=SYQfHGUdevQ4pZu1qVRNK8NSPpvX4i0NfJHEZdjmyOsTwNUUeppBceaiGTPKfGnPStBxdhZlCPMEW2SJHEERTNlw/9jRPpxJNuOucFUfQkmNhE+lG5H3WPVsIW/O8DtGBGBj+ELJXpe5ezpD+tWg7Cthta+9zw2bDNPLMEBuBBA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812300; c=relaxed/simple; bh=bNcr9gYJuVrrlmVAVwhT7hkluiCx6nen9UPJufZJKPs=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=hMeAztNNM0qfA1ehqe1iDIA2v/zhEG0tCAOyBqhJOQSLqxNRvco8VKNhLsw1cWPioScntPsB5BDlqonPfbHJyegaIUumUi8lqnnSf5gU/rZEzhAq2agt49QPSXYa0hqky+d+VfEfURdgRx2+fGrKqcAsHExDrGkEs5A2bcsf8lI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=bPlXHUQt; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="bPlXHUQt" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 5B1A31D13; Wed, 26 Aug 2026 23:31:29 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id B7FE43F85F; Wed, 26 Aug 2026 23:31:30 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812293; bh=bNcr9gYJuVrrlmVAVwhT7hkluiCx6nen9UPJufZJKPs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=bPlXHUQt3DVbC2DZH4+6rru9XJU7MfHd/kXgN2ULPKDS4q3VhCwDEvGQE4qKPlkSJ ochIJxDtF+NcWyZWoKoapD/KP+TTwAczT5Z5E+D8A1KQqU0oVojlrERM61ZBToOBAt RRmZ7YmcRNRSQqvYHMDxtdpbYcC27p/AffD+2vak= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle , Sashiko Subject: [PATCH v5 09/15] ACPI: CPPC: Reject unsafe cross-CPU SystemMemory RMW Date: Thu, 27 Aug 2026 07:30:54 +0100 Message-Id: <20260827063100.2741066-10-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" A SystemMemory CPPC field narrower than its access unit needs a read-modify-write operation. The existing per-CPC-descriptor lock serializes such fields within one _CPC package, but not access units described by different processors. Concurrent writers can therefore lose updates. The ACPI specification does not make _PSD a physical register-ownership boundary. It can legally describe shared or packed registers across processors and performance domains. Supporting every such layout would require locks keyed by physical access unit. Retain the deliberately cheaper per-descriptor locking model and enforce its assumptions at probe instead. Reject overlapping access units from different descriptors when both contain writable partial fields, including exact partial aliases. Such layouts were never safely writable by Linux, so this turns possible corruption into a probe error rather than removing working support. Within one descriptor, disjoint partial writable fields remain supported and share its raw spinlock. Across descriptors, exact full-width writable aliases remain supported and lockless when the architecture provides a native access of the declared width. Reject 64-bit writable aliases on 32-bit kernels because generic writeq() may use two 32-bit stores and is not a portable atomicity guarantee. Read-only 64-bit aliases remain supported on every architecture. Read-only overlaps, and a partial writable field sharing an access unit with a disjoint read-only field, also remain supported. Reject overlapping logical fields when either is writable and reject any other field sharing a full-width writable access unit. Validate the GAS geometry and require naturally aligned 8-, 16-, 32-, or 64-bit SystemMemory access units. The driver uses direct MMIO accessors and cannot portably execute unaligned accesses. Keep an interval tree solely for scalable probe-time overlap validation; the full-width scheduler path does no lookup and takes no lock. Keep validation errors separate from the probe return value. A successful SystemMemory geometry check must not overwrite the pending negative error, because a later mapping or address-space failure still needs to make probe fail. Fixes: 60949b7b8054 ("ACPI: CPPC: Fix MASK_VAL() usage") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260724134251.1632824-1-christian.loe= hle%40arm.com Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loe= hle%40arm.com Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 347 ++++++++++++++++++++++++++++++++++----- include/acpi/cppc_acpi.h | 5 +- 2 files changed, 314 insertions(+), 38 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 118001e1687a..e1fbbf46128e 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -34,9 +34,11 @@ #define pr_fmt(fmt) "ACPI CPPC: " fmt =20 #include +#include #include #include #include +#include #include #include #include @@ -95,6 +97,37 @@ static DEFINE_PER_CPU(int, cpu_pcc_subspace_idx); */ static DEFINE_PER_CPU(struct cpc_desc *, cpc_desc_ptr); =20 +struct cpc_sysmem_node { + struct rb_node rb; + u64 subtree_last; + u64 start; + u64 last; + struct cpc_desc *desc; + unsigned int reg_idx; + bool registered; +}; + +#define CPC_SYSMEM_START(node) ((node)->start) +#define CPC_SYSMEM_LAST(node) ((node)->last) + +INTERVAL_TREE_DEFINE(struct cpc_sysmem_node, rb, u64, subtree_last, + CPC_SYSMEM_START, CPC_SYSMEM_LAST, static inline, + cpc_sysmem_itree) + +static struct rb_root_cached cpc_sysmem_tree =3D RB_ROOT_CACHED; +static DEFINE_MUTEX(cpc_sysmem_lock); + +static struct cpc_sysmem_node *cpc_sysmem_first(u64 start, u64 last) +{ + return cpc_sysmem_itree_iter_first(&cpc_sysmem_tree, start, last); +} + +static struct cpc_sysmem_node *cpc_sysmem_next(struct cpc_sysmem_node *nod= e, + u64 start, u64 last) +{ + return cpc_sysmem_itree_iter_next(node, start, last); +} + /* pcc mapped address + header size + offset within PCC subspace */ #define GET_PCC_VADDR(offs, pcc_ss_id) (pcc_data[pcc_ss_id]->pcc_channel->= shmem + \ 0x8 + (offs)) @@ -255,15 +288,20 @@ static bool cpc_pcc_write_supported(const struct cpc_= register_resource *reg) ((((val) & GENMASK_ULL(((reg)->bit_width) - 1, 0)) << (reg)->bit_offset) = | \ ((prev_val) & ~(GENMASK_ULL(((reg)->bit_width) - 1, 0) << (reg)->bit_offs= et))) \ =20 -static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg) +static unsigned int cpc_reg_access_width(const struct cpc_reg *reg) { - const struct cpc_reg *gas =3D ®->cpc_entry.reg; - unsigned int width; - - if (gas->access_width > 4) + if (reg->access_width > 4) return 0; =20 - width =3D GET_BIT_WIDTH(gas); + if (reg->access_width) + return 8U << (reg->access_width - 1); + + return reg->bit_width; +} + +static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg) +{ + unsigned int width =3D cpc_reg_access_width(®->cpc_entry.reg); =20 if (width !=3D 8 && width !=3D 16 && width !=3D 32 && width !=3D 64) return 0; @@ -289,36 +327,260 @@ static bool cpc_sysmem_access_units_overlap(const st= ruct cpc_register_resource * return a_gas->address - b_gas->address < b_size; } =20 +static bool cpc_reg_is_writable(unsigned int reg_idx) +{ + switch (reg_idx) { + case DESIRED_PERF: + case MIN_PERF: + case MAX_PERF: + case PERF_REDUC_TOLERANCE: + case TIME_WINDOW: + case PERF_LIMITED: + case ENABLE: + case AUTO_SEL_ENABLE: + case AUTO_ACT_WINDOW: + case ENERGY_PERF: + case OSPM_NOMINAL_PERF: + return true; + default: + return false; + } +} + +static bool cpc_sysmem_reg_needs_rmw(const struct cpc_register_resource *r= eg) +{ + const struct cpc_reg *gas =3D ®->cpc_entry.reg; + u64 access_size =3D cpc_sysmem_access_size(reg); + + return gas->bit_offset || gas->bit_width !=3D access_size * 8; +} + +static int cpc_validate_sysmem_reg(const struct cpc_desc *cpc_desc, + const struct cpc_reg *gas, + unsigned int reg_idx) +{ + unsigned int access_width =3D cpc_reg_access_width(gas); + u64 access_size; + + if (access_width !=3D 8 && access_width !=3D 16 && + access_width !=3D 32 && access_width !=3D 64) + goto invalid; + + if (!gas->bit_width || gas->bit_width > access_width || + gas->bit_offset >=3D access_width || + gas->bit_width > access_width - gas->bit_offset) + goto invalid; + + access_size =3D access_width / 8; + if (!gas->address || gas->address > U64_MAX - (access_size - 1)) + goto invalid; + if (gas->address & (access_size - 1)) + goto invalid; + + return 0; + +invalid: + pr_debug("CPU:%d invalid SystemMemory GAS for _CPC register %u\n", + cpc_desc->cpu_id, reg_idx); + return -EINVAL; +} + static void cpc_mark_rmw_lock_users(struct cpc_desc *cpc_desc) { - int i, j; + int i; =20 for (i =3D 0; i < cpc_desc->num_entries - 2; i++) { - struct cpc_register_resource *a =3D &cpc_desc->cpc_regs[i]; - struct cpc_reg *gas; - u64 access_size; + struct cpc_register_resource *reg =3D &cpc_desc->cpc_regs[i]; =20 - if (!CPC_SUPPORTED(a) || !CPC_IN_SYSTEM_MEMORY(a)) - continue; + if (CPC_SUPPORTED(reg) && CPC_IN_SYSTEM_MEMORY(reg)) + reg->cpc_entry.use_rmw_lock =3D + cpc_sysmem_reg_needs_rmw(reg); + } +} =20 - gas =3D &a->cpc_entry.reg; - access_size =3D cpc_sysmem_access_size(a); - if (gas->bit_offset || !access_size || - gas->bit_width !=3D access_size * 8) - a->cpc_entry.use_rmw_lock =3D true; +struct cpc_bit_position { + u64 byte; + u8 bit; +}; =20 - for (j =3D i + 1; j < cpc_desc->num_entries - 2; j++) { - struct cpc_register_resource *b =3D &cpc_desc->cpc_regs[j]; +static bool cpc_bit_position_before(const struct cpc_bit_position *a, + const struct cpc_bit_position *b) +{ + return a->byte < b->byte || (a->byte =3D=3D b->byte && a->bit < b->bit); +} =20 - if (!CPC_SUPPORTED(b) || !CPC_IN_SYSTEM_MEMORY(b)) - continue; - if (!cpc_sysmem_access_units_overlap(a, b)) - continue; +static bool cpc_sysmem_fields_overlap(const struct cpc_register_resource *= a, + const struct cpc_register_resource *b) +{ + const struct cpc_reg *a_gas =3D &a->cpc_entry.reg; + const struct cpc_reg *b_gas =3D &b->cpc_entry.reg; + unsigned int a_last_bit =3D a_gas->bit_offset + a_gas->bit_width - 1; + unsigned int b_last_bit =3D b_gas->bit_offset + b_gas->bit_width - 1; + struct cpc_bit_position a_start =3D { + .byte =3D a_gas->address + a_gas->bit_offset / 8, + .bit =3D a_gas->bit_offset % 8, + }; + struct cpc_bit_position a_end =3D { + .byte =3D a_gas->address + a_last_bit / 8, + .bit =3D a_last_bit % 8, + }; + struct cpc_bit_position b_start =3D { + .byte =3D b_gas->address + b_gas->bit_offset / 8, + .bit =3D b_gas->bit_offset % 8, + }; + struct cpc_bit_position b_end =3D { + .byte =3D b_gas->address + b_last_bit / 8, + .bit =3D b_last_bit % 8, + }; + + return !cpc_bit_position_before(&a_end, &b_start) && + !cpc_bit_position_before(&b_end, &a_start); +} + +static bool cpc_same_sysmem_register(unsigned int a_idx, + const struct cpc_register_resource *a, + unsigned int b_idx, + const struct cpc_register_resource *b) +{ + const struct cpc_reg *a_gas =3D &a->cpc_entry.reg; + const struct cpc_reg *b_gas =3D &b->cpc_entry.reg; + + return a_idx =3D=3D b_idx && + a_gas->address =3D=3D b_gas->address && + a_gas->bit_width =3D=3D b_gas->bit_width && + a_gas->bit_offset =3D=3D b_gas->bit_offset && + cpc_reg_access_width(a_gas) =3D=3D cpc_reg_access_width(b_gas); +} =20 - a->cpc_entry.use_rmw_lock =3D true; - b->cpc_entry.use_rmw_lock =3D true; +static int cpc_validate_sysmem_pair(const struct cpc_desc *a_desc, + unsigned int a_idx, + const struct cpc_desc *b_desc, + unsigned int b_idx) +{ + const struct cpc_register_resource *a =3D &a_desc->cpc_regs[a_idx]; + const struct cpc_register_resource *b =3D &b_desc->cpc_regs[b_idx]; + bool a_writable, b_writable; + + if (!CPC_SUPPORTED(a) || !CPC_IN_SYSTEM_MEMORY(a) || + !CPC_SUPPORTED(b) || !CPC_IN_SYSTEM_MEMORY(b) || + !cpc_sysmem_access_units_overlap(a, b)) + return 0; + + a_writable =3D cpc_reg_is_writable(a_idx); + b_writable =3D cpc_reg_is_writable(b_idx); + if (!a_writable && !b_writable) + return 0; + + if (cpc_same_sysmem_register(a_idx, a, b_idx, b)) { + /* + * Cross-CPU partial writes were never safely serialized, and a + * 64-bit MMIO write may be split on 32-bit kernels. + */ + if (!a_writable || + (!cpc_sysmem_reg_needs_rmw(a) && + (cpc_sysmem_access_size(a) < sizeof(u64) || + IS_ENABLED(CONFIG_64BIT)))) + return 0; + goto conflict; + } + + /* + * A full-width writable register owns its access unit. It cannot + * coexist with another logical field because it has no bits to + * preserve, even when that other field is read-only. + */ + if ((a_writable && !cpc_sysmem_reg_needs_rmw(a)) || + (b_writable && !cpc_sysmem_reg_needs_rmw(b)) || + cpc_sysmem_fields_overlap(a, b) || + (a_desc !=3D b_desc && a_writable && b_writable)) + goto conflict; + + return 0; + +conflict: + pr_err("CPU%d: SystemMemory _CPC register %u conflicts with CPU%d registe= r %u\n", + a_desc->cpu_id, a_idx, b_desc->cpu_id, b_idx); + return -EINVAL; +} + +static void cpc_unregister_sysmem_desc_locked(struct cpc_desc *cpc_desc) +{ + unsigned int i; + + if (!cpc_desc->sysmem_nodes) + return; + + for (i =3D 0; i < cpc_desc->num_entries - 2; i++) { + struct cpc_sysmem_node *node =3D &cpc_desc->sysmem_nodes[i]; + + if (node->registered) + cpc_sysmem_itree_remove(node, &cpc_sysmem_tree); + } + + kfree(cpc_desc->sysmem_nodes); + cpc_desc->sysmem_nodes =3D NULL; +} + +static int cpc_register_sysmem_desc(struct cpc_desc *cpc_desc) +{ + unsigned int nr_regs =3D cpc_desc->num_entries - 2; + unsigned int i; + int ret =3D 0; + + cpc_desc->sysmem_nodes =3D kcalloc(nr_regs, + sizeof(*cpc_desc->sysmem_nodes), + GFP_KERNEL); + if (!cpc_desc->sysmem_nodes) + return -ENOMEM; + + mutex_lock(&cpc_sysmem_lock); + + for (i =3D 0; i < nr_regs; i++) { + struct cpc_register_resource *reg =3D &cpc_desc->cpc_regs[i]; + struct cpc_sysmem_node *match, *node; + u64 size; + + if (!CPC_SUPPORTED(reg) || !CPC_IN_SYSTEM_MEMORY(reg)) + continue; + + node =3D &cpc_desc->sysmem_nodes[i]; + size =3D cpc_sysmem_access_size(reg); + node->start =3D reg->cpc_entry.reg.address; + node->last =3D node->start + size - 1; + node->desc =3D cpc_desc; + node->reg_idx =3D i; + + match =3D cpc_sysmem_first(node->start, node->last); + while (match) { + ret =3D cpc_validate_sysmem_pair(cpc_desc, i, match->desc, + match->reg_idx); + if (ret) + goto out_unregister; + + match =3D cpc_sysmem_next(match, node->start, node->last); } + + cpc_sysmem_itree_insert(node, &cpc_sysmem_tree); + node->registered =3D true; } + + mutex_unlock(&cpc_sysmem_lock); + return 0; + +out_unregister: + cpc_unregister_sysmem_desc_locked(cpc_desc); + mutex_unlock(&cpc_sysmem_lock); + return ret; +} + +static void cpc_unregister_sysmem_desc(struct cpc_desc *cpc_desc) +{ + if (!cpc_desc->sysmem_nodes) + return; + + mutex_lock(&cpc_sysmem_lock); + cpc_unregister_sysmem_desc_locked(cpc_desc); + mutex_unlock(&cpc_sysmem_lock); } =20 static ssize_t show_feedback_ctrs(struct kobject *kobj, @@ -356,6 +618,8 @@ static void cppc_free_desc(struct cpc_desc *cpc_ptr) { unsigned int i; =20 + cpc_unregister_sysmem_desc(cpc_ptr); + for (i =3D 2; i < cpc_ptr->num_entries; i++) { void __iomem *addr =3D cpc_ptr->cpc_regs[i - 2].sys_mem_vaddr; =20 @@ -951,6 +1215,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *p= r) ret =3D -ENOMEM; goto out_buf_free; } + cpc_ptr->cpu_id =3D pr->id; =20 /* First entry is NumEntries. */ cpc_obj =3D &out_obj->package.elements[0]; @@ -1066,21 +1331,28 @@ int acpi_cppc_processor_probe(struct acpi_processor= *pr) goto out_free; } } else if (gas_t->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_MEMORY) { - if (gas_t->address) { + if (!IS_NULL_REG(gas_t)) { void __iomem *addr; size_t access_width; =20 + err =3D cpc_validate_sysmem_reg(cpc_ptr, gas_t, i - 2); + if (err) { + ret =3D err; + goto out_free; + } + if (!osc_cpc_flexible_adr_space_confirmed) { pr_debug("Flexible address space capability not supported\n"); if (!cpc_supported_by_cpu()) goto out_free; } =20 - access_width =3D GET_BIT_WIDTH(gas_t) / 8; + access_width =3D cpc_reg_access_width(gas_t); + access_width /=3D 8; addr =3D ioremap(gas_t->address, access_width); if (!addr) goto out_free; - cpc_ptr->cpc_regs[i-2].sys_mem_vaddr =3D addr; + cpc_ptr->cpc_regs[i - 2].sys_mem_vaddr =3D addr; } } else if (gas_t->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_IO) { if (gas_t->access_width < 1 || gas_t->access_width > 3) { @@ -1113,8 +1385,9 @@ int acpi_cppc_processor_probe(struct acpi_processor *= pr) } } =20 - cpc_ptr->cpc_regs[i-2].type =3D ACPI_TYPE_BUFFER; - memcpy(&cpc_ptr->cpc_regs[i-2].cpc_entry.reg, gas_t, sizeof(*gas_t)); + cpc_ptr->cpc_regs[i - 2].type =3D ACPI_TYPE_BUFFER; + memcpy(&cpc_ptr->cpc_regs[i - 2].cpc_entry.reg, gas_t, + sizeof(*gas_t)); } else if (cpc_obj->type =3D=3D ACPI_TYPE_PACKAGE && (i - 2) =3D=3D RESO= URCE_PRIORITY) { /* * ACPI 6.6, s8.4.6.1.2.7 defines Resource Priority as a @@ -1174,8 +1447,6 @@ int acpi_cppc_processor_probe(struct acpi_processor *= pr) } =20 =20 - /* Store CPU Logical ID */ - cpc_ptr->cpu_id =3D pr->id; cpc_mark_rmw_lock_users(cpc_ptr); raw_spin_lock_init(&cpc_ptr->rmw_lock); =20 @@ -1184,6 +1455,10 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) if (ret) goto out_free; =20 + ret =3D cpc_register_sysmem_desc(cpc_ptr); + if (ret) + goto out_free; + /* Register PCC channel once for all PCC subspace ID. */ if (pcc_subspace_id >=3D 0) { ret =3D register_pcc_channel(pcc_subspace_id); @@ -1208,6 +1483,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *= pr) "acpi_cppc"); if (ret) { per_cpu(cpc_desc_ptr, pr->id) =3D NULL; + cpc_unregister_sysmem_desc(cpc_ptr); kobject_put(&cpc_ptr->kobj); goto out_pcc_put; } @@ -1250,6 +1526,7 @@ void acpi_cppc_processor_exit(struct acpi_processor *= pr) pcc_ss_id =3D per_cpu(cpu_pcc_subspace_idx, pr->id); per_cpu(cpc_desc_ptr, pr->id) =3D NULL; kobject_del(&cpc_ptr->kobj); + cpc_unregister_sysmem_desc(cpc_ptr); =20 pcc_data_put(pcc_ss_id); per_cpu(cpu_pcc_subspace_idx, pr->id) =3D -1; @@ -1437,11 +1714,7 @@ static int cpc_write(int cpu, struct cpc_register_re= source *reg_res, u64 val) val, size); =20 if (reg->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_MEMORY) { - /* - * The _CPC layout is immutable after probe. The precomputed flag - * retains serialization for partial fields or overlapping access - * units; standalone full-width registers avoid the lock. - */ + /* Partial fields use their per-CPU descriptor lock. */ locked =3D reg_res->cpc_entry.use_rmw_lock; if (locked) { cpc_desc =3D per_cpu(cpc_desc_ptr, cpu); diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 94a6277edab2..79702c7c9efd 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -77,6 +77,8 @@ struct cpc_register_resource { } cpc_entry; }; =20 +struct cpc_sysmem_node; + /* Container to hold the CPC details for each CPU */ struct cpc_desc { int num_entries; @@ -84,10 +86,11 @@ struct cpc_desc { int cpu_id; int write_cmd_status; int write_cmd_id; - /* Lock used for RMW operations in cpc_write() */ + /* Serialize partial SystemMemory writes within this descriptor. */ raw_spinlock_t rmw_lock; struct cpc_register_resource cpc_regs[MAX_CPC_REG_ENT]; struct acpi_psd_package domain_info; + struct cpc_sysmem_node *sysmem_nodes; struct kobject kobj; }; =20 --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id F078C364EAA; Thu, 27 Aug 2026 06:31:38 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812302; cv=none; b=CnD1B2UKXqJcNNLstwnwjQuhOvdQRCfU2viQ51dHKltqsY3iwqW/PXn6n7r2gzNvvwio2+USk05VVhTVEU8C9Z/+ro+szMUr6OIoxmY1Y1joMhy1zOhCFbLDfJPynFvML/pD1xahmTY15JdCj1I6yU4ApyAKK1oJHjB0aZ9vxEU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812302; c=relaxed/simple; bh=pKoTNaV5uTjmczuk05FrTbeYCimwTUMRNY2S3fZnUMQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=IjFNLHsUdESyU6s0GMMxjjBLebpVyGwWZlRPDPJlUiXPw38ytKGqtBkScHzdp59LcX+7YaKkNaHnCr27P5yb9lrSwP/TRNVL7qiORty5OdbEJUuWiUk/3efysnxNwICSm0k6tY05U6gt4ep3VGIJgHe1gUtMmDYw/BMqVsymSAA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=vGWUzbX9; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="vGWUzbX9" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 01FEA1691; Wed, 26 Aug 2026 23:31:32 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 8B3A93F85F; Wed, 26 Aug 2026 23:31:33 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812295; bh=pKoTNaV5uTjmczuk05FrTbeYCimwTUMRNY2S3fZnUMQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=vGWUzbX9V6TzR4voyzcmGL4Vr4T39hZB1ZQqMQuha7gr8W0Y60rWHoO4A/XIbshlK EJVaVU0doo9ZpzPCgM1mfPlP/k7XkZsF+gEmo9BzTv106jN6MIxzDNjGFYuEykODnr C8n+8GOf1M3s68SxbxvtQ/vBJhlTKV+mQ/9amzuk= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle , Sashiko Subject: [PATCH v5 10/15] ACPI: CPPC: Reject direct reads of write-only controls Date: Thu, 27 Aug 2026 07:30:55 +0100 Message-Id: <20260827063100.2741066-11-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 consume undefined values. A partial SystemMemory field still makes cpc_write() read its complete access unit to preserve bits outside the field. MASK_VAL_WRITE() replaces every bit of the field being written, so that field's undefined readback is not propagated. However, another writer sharing the access unit would preserve and replay the write-only field when performing its own RMW. Reject such pairs; a partial write-only field may share its access unit only with disjoint read-only fields. The descriptor lock serializes supported RMW, and the preceding validation rejects unsafe cross-descriptor partial writers. Mark an inaccessible OSPM Nominal Performance control unsupported because it is optional. Do the same for inaccessible Desired Performance while parsing, then let the post-parse control check accept it only for immutable autonomous selection. This preserves the autonomous-only exception without accepting an unusable Desired control in non-autonomous mode. Fixes: 71e1815113f7 ("ACPI: CPPC: Add support for CPPC v4") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loe= hle%40arm.com Link: https://sashiko.dev/#/patchset/20260808082644.1251332-1-christian.loe= hle%40arm.com Signed-off-by: Christian Loehle --- 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 e1fbbf46128e..aa5bf02b85a4 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -347,6 +347,21 @@ static bool cpc_reg_is_writable(unsigned int reg_idx) } } =20 +static bool cpc_reg_is_write_only(const struct cpc_desc *cpc_desc, + unsigned int reg_idx) +{ + return cpc_desc->version >=3D CPPC_V4_REV && + (reg_idx =3D=3D DESIRED_PERF || reg_idx =3D=3D OSPM_NOMINAL_PERF); +} + +static void cpc_disable_reg(struct cpc_desc *cpc_desc, unsigned int reg_id= x) +{ + struct cpc_register_resource *reg =3D &cpc_desc->cpc_regs[reg_idx]; + + reg->type =3D ACPI_TYPE_INTEGER; + reg->cpc_entry.int_value =3D 0; +} + static bool cpc_sysmem_reg_needs_rmw(const struct cpc_register_resource *r= eg) { const struct cpc_reg *gas =3D ®->cpc_entry.reg; @@ -495,6 +510,14 @@ static int cpc_validate_sysmem_pair(const struct cpc_d= esc *a_desc, (a_desc !=3D b_desc && a_writable && b_writable)) goto conflict; =20 + /* + * RMW of either writer preserves the other field. If that field is + * write-only, its readback is undefined and cannot safely be replayed. + */ + if ((cpc_reg_is_write_only(a_desc, a_idx) && b_writable) || + (cpc_reg_is_write_only(b_desc, b_idx) && a_writable)) + goto conflict; + return 0; =20 conflict: @@ -1336,6 +1359,17 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) size_t access_width; =20 err =3D cpc_validate_sysmem_reg(cpc_ptr, gas_t, i - 2); + if (err && (i - 2 =3D=3D DESIRED_PERF || + i - 2 =3D=3D OSPM_NOMINAL_PERF)) { + const char *name =3D i - 2 =3D=3D DESIRED_PERF ? + "Desired Performance" : + "OSPM Nominal Performance"; + + pr_warn("CPU%d: disabling inaccessible %s register\n", + pr->id, name); + cpc_disable_reg(cpc_ptr, i - 2); + continue; + } if (err) { ret =3D err; goto out_free; @@ -1726,6 +1760,10 @@ static int cpc_write(int cpu, struct cpc_register_re= source *reg_res, u64 val) } =20 if (reg->bit_offset || reg->bit_width !=3D size) { + /* + * MASK_VAL_WRITE() discards the field's old bits, so undefined + * readback from a write-only field is not propagated. + */ switch (size) { case 8: prev_val =3D readb_relaxed(vaddr); @@ -1817,6 +1855,8 @@ static int cppc_get_reg_val(int cpu, enum cppc_regs r= eg_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; =20 reg =3D &cpc_desc->cpc_regs[reg_idx]; =20 --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 1A05B364EAF; Thu, 27 Aug 2026 06:31:39 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812306; cv=none; b=tDvUfBJjSpQPMQDFfGIiOglrt3e5ha85MQcLpNAdMnPTSPXFjKG5CagfFvxpZVlNdegJ/6gA2dYYtAfbGJrCKDqT5mujnMEwcgc60cMwbFtPtwsI1G9/YAK/MqsN0Jr70jRZA2EfeQVlkIpC0t2dkWIVZVDoCryGkSm+djsPxDg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812306; c=relaxed/simple; bh=U+7495XcYihG/fg//TmDJ3rVpXplt1kTruA8m6xhOMo=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=I5HXspW1xX3260FcFAmgtR60Fo/m8mgHFZzyWEILWCsM6y+xxT4kpw6Gy9LHNnlGwNoEKohBP039UcAQ5QGwKKWJgTSJBf9SyIi7Oa9szQZRx6sArmR8JQzr5lfU1aKKUcI4eD1i5Jtr/ETswD4jtLmmKj2bRaQIvdZCdY/U5Pw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=QLCILi00; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="QLCILi00" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id A188A169E; Wed, 26 Aug 2026 23:31:34 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 2FF9A3F85F; Wed, 26 Aug 2026 23:31:36 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812298; bh=U+7495XcYihG/fg//TmDJ3rVpXplt1kTruA8m6xhOMo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QLCILi00yvzAeFJObAtJCMcGKc0BT89nfXoinvBzipfDWY37zMkTlhGG8bN2d3T97 wwSPAPuWkgbHuLA+fUj6f0F6Mm/hJ+q+8euUamtg5zPePmYxihRkGSeMmlSFlsO1sT J1eAWgcMkIn8mRvs3g+x04OLbKcQHs4Mdk5UWAws= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle Subject: [PATCH v5 11/15] ACPI: CPPC: Validate and access PCC register layouts Date: Thu, 27 Aug 2026 07:30:56 +0100 Message-Id: <20260827063100.2741066-12-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" PCC GAS Access Size identifies the subspace, so the accessors use Bit Width as the field size. Their typed readw/readl/readq operations require natural alignment and support only 8, 16, 32, or 64 bits. This rejects valid firmware: ACPI 6.6 shows a mandatory 32-bit Delivered Performance Counter at unaligned offset 0x116, and performance controls may use byte-multiple widths such as 24 bits. Support zero-offset, byte-multiple fields up to 64 bits with byte-oriented I/O and explicit little-endian encoding. A short per-subspace raw spinlock serializes aliased payload copies performed concurrently under the shared side of pcc_lock. Fields with a nonzero bit offset or non-byte width still need unsupported RMW semantics and are rejected or disabled. Defer that decision until the entire package is known. A provided but inaccessible CPPC Enable control fails probe because OSPM must write it before using CPPC. An inaccessible Desired control can be ignored only for immutable autonomous operation. Inaccessible Minimum or Maximum controls fail probe, and the post-sanitization check enforces the specification requirement that both controls exist or neither does. Other inaccessible optional controls are marked absent; mandatory controls fail. Keep the eventual probe result negative while temporary validation helpers succeed. This prevents a later malformed entry, unsupported address space, or mapping failure from freeing the descriptor and nevertheless returning success. Apply the same control-specific policy to unsupported SystemMemory geometry. Once the PCC channel is available, verify every retained field fits after the eight-byte header and within shared memory, and reject overlapping logical ranges when either is writable. This deliberately does not implement bit-level PCC RMW. In particular, the one-bit CPPC Enable field in the specification example now produces an explicit probe failure instead of being silently treated as absent. Fixes: 85b1407bf6d2 ("ACPI / CPPC: Make CPPC ACPI driver aware of PCC subsp= ace IDs") Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 317 +++++++++++++++++++++++++++++++-------- 1 file changed, 258 insertions(+), 59 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index aa5bf02b85a4..d67815574993 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -73,6 +73,8 @@ struct cppc_pcc_data { * Take write_lock for all purposes which gives exclusive access */ struct rw_semaphore pcc_lock; + /* Serialize byte-oriented accesses to aliased PCC payload fields. */ + raw_spinlock_t payload_lock; =20 /* Wait queue for CPUs whose requests were batched */ wait_queue_head_t pcc_write_wait_q; @@ -128,9 +130,11 @@ static struct cpc_sysmem_node *cpc_sysmem_next(struct = cpc_sysmem_node *node, return cpc_sysmem_itree_iter_next(node, start, last); } =20 +#define CPC_PCC_HEADER_SIZE 0x8 + /* pcc mapped address + header size + offset within PCC subspace */ #define GET_PCC_VADDR(offs, pcc_ss_id) (pcc_data[pcc_ss_id]->pcc_channel->= shmem + \ - 0x8 + (offs)) + CPC_PCC_HEADER_SIZE + (offs)) =20 /* Check if a CPC register is in PCC */ #define CPC_IN_PCC(cpc) ((cpc)->type =3D=3D ACPI_TYPE_BUFFER && \ @@ -270,15 +274,9 @@ show_cppc_data(cppc_get_perf_ctrs, cppc_perf_fb_ctrs, = wraparound_time); =20 static bool cpc_pcc_write_supported(const struct cpc_register_resource *re= g) { - switch (GET_BIT_WIDTH(®->cpc_entry.reg)) { - case 8: - case 16: - case 32: - case 64: - return true; - default: - return false; - } + unsigned int width =3D reg->cpc_entry.reg.bit_width; + + return width && width <=3D 64 && !(width % 8); } =20 /* Shift and apply the mask for CPC reads/writes */ @@ -400,6 +398,170 @@ static int cpc_validate_sysmem_reg(const struct cpc_d= esc *cpc_desc, return -EINVAL; } =20 +static bool cpc_immutable_autonomous(const struct cpc_desc *cpc_desc) +{ + const struct cpc_register_resource *reg; + + reg =3D &cpc_desc->cpc_regs[AUTO_SEL_ENABLE]; + return osc_sb_cppc2_support_acked && reg->type =3D=3D ACPI_TYPE_INTEGER && + reg->cpc_entry.int_value =3D=3D 1; +} + +static int cpc_resolve_unsupported(struct cpc_desc *cpc_desc, + u32 unsupported) +{ + unsigned int i; + + for (i =3D 0; i < cpc_desc->num_entries - 2; i++) { + if (!(unsupported & BIT(i))) + continue; + + if (i =3D=3D DESIRED_PERF && cpc_immutable_autonomous(cpc_desc)) { + pr_warn("CPU%d: ignoring inaccessible Desired Performance register in a= utonomous mode\n", + cpc_desc->cpu_id); + cpc_disable_reg(cpc_desc, i); + continue; + } + + /* A present Enable control must be usable to enter CPPC mode. */ + if (i =3D=3D ENABLE || i =3D=3D MIN_PERF || i =3D=3D MAX_PERF || + !IS_OPTIONAL_CPC_REG(i)) { + pr_err("CPU%d: cannot access _CPC register %u\n", + cpc_desc->cpu_id, i); + return -EINVAL; + } + + pr_warn("CPU%d: ignoring inaccessible optional _CPC register %u\n", + cpc_desc->cpu_id, i); + cpc_disable_reg(cpc_desc, i); + } + + return 0; +} + +static int cpc_validate_required_controls(struct cpc_desc *cpc_desc) +{ + bool have_min, have_max; + unsigned int i; + + /* + * Performance Limited is required by the specification, but tolerate a + * NULL descriptor used by firmware which cannot report limiting events. + * CPPC control does not depend on this status. + */ + for (i =3D 0; i < cpc_desc->num_entries - 2; i++) { + if (i !=3D DESIRED_PERF && i !=3D PERF_LIMITED && + !IS_OPTIONAL_CPC_REG(i) && + !cpc_entry_present(&cpc_desc->cpc_regs[i])) { + pr_debug("CPU:%d lacks mandatory _CPC register %u\n", + cpc_desc->cpu_id, i); + return -EINVAL; + } + } + + /* Desired may be absent only for immutable autonomous operation. */ + if (!cpc_is_writable(&cpc_desc->cpc_regs[DESIRED_PERF]) && + !cpc_immutable_autonomous(cpc_desc)) { + pr_debug("CPU:%d lacks a writable Desired Performance register\n", + cpc_desc->cpu_id); + return -EINVAL; + } + + have_min =3D cpc_is_writable(&cpc_desc->cpc_regs[MIN_PERF]); + have_max =3D cpc_is_writable(&cpc_desc->cpc_regs[MAX_PERF]); + if (have_min !=3D have_max) { + pr_err("CPU%d: _CPC must provide both Minimum and Maximum Performance or= neither\n", + cpc_desc->cpu_id); + return -EINVAL; + } + + return 0; +} + +static int cpc_validate_pcc_bounds(struct cpc_desc *cpc_desc, + struct cppc_pcc_data *data, + u32 *unsupported) +{ + u64 shmem_size =3D data->pcc_channel->shmem_size; + unsigned int i; + + for (i =3D 0; i < cpc_desc->num_entries - 2; i++) { + struct cpc_register_resource *reg =3D &cpc_desc->cpc_regs[i]; + struct cpc_reg *gas; + u64 access_size; + + if ((*unsupported & BIT(i)) || !CPC_SUPPORTED(reg) || + !CPC_IN_PCC(reg)) + continue; + + gas =3D ®->cpc_entry.reg; + access_size =3D gas->bit_width / 8; + if (shmem_size >=3D CPC_PCC_HEADER_SIZE && + gas->address <=3D shmem_size - CPC_PCC_HEADER_SIZE && + access_size <=3D shmem_size - CPC_PCC_HEADER_SIZE - gas->address) + continue; + + pr_debug("CPU%d: _CPC register %u exceeds the PCC shared region\n", + cpc_desc->cpu_id, i); + *unsupported |=3D BIT(i); + } + + return 0; +} + +static u64 cpc_non_mmio_access_size(const struct cpc_register_resource *re= g) +{ + const struct cpc_reg *gas =3D ®->cpc_entry.reg; + + if (gas->space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM) + return gas->bit_width / 8; + + return cpc_reg_access_width(gas) / 8; +} + +static int cpc_validate_non_mmio_overlaps(struct cpc_desc *cpc_desc, + u8 space_id, const char *name) +{ + unsigned int i, j; + + for (i =3D 0; i < cpc_desc->num_entries - 2; i++) { + struct cpc_register_resource *a =3D &cpc_desc->cpc_regs[i]; + const struct cpc_reg *a_gas; + u64 a_size; + + if (!CPC_SUPPORTED(a) || a->type !=3D ACPI_TYPE_BUFFER || + a->cpc_entry.reg.space_id !=3D space_id) + continue; + + a_gas =3D &a->cpc_entry.reg; + a_size =3D cpc_non_mmio_access_size(a); + for (j =3D i + 1; j < cpc_desc->num_entries - 2; j++) { + struct cpc_register_resource *b =3D &cpc_desc->cpc_regs[j]; + const struct cpc_reg *b_gas; + u64 b_size; + + if (!CPC_SUPPORTED(b) || b->type !=3D ACPI_TYPE_BUFFER || + b->cpc_entry.reg.space_id !=3D space_id) + continue; + + b_gas =3D &b->cpc_entry.reg; + b_size =3D cpc_non_mmio_access_size(b); + if (!cpc_reg_is_writable(i) && !cpc_reg_is_writable(j)) + continue; + if (a_gas->address < b_gas->address ? + b_gas->address - a_gas->address >=3D a_size : + a_gas->address - b_gas->address >=3D b_size) + continue; + + pr_err("CPU%d: overlapping writable %s _CPC registers %u and %u\n", + cpc_desc->cpu_id, name, i, j); + return -EINVAL; + } + } + + return 0; +} + static void cpc_mark_rmw_lock_users(struct cpc_desc *cpc_desc) { int i; @@ -1124,6 +1286,7 @@ static int pcc_data_alloc(int pcc_ss_id) ret =3D -ENOMEM; goto out_unlock; } + raw_spin_lock_init(&data->payload_lock); pcc_data[pcc_ss_id] =3D data; } data->refcount++; @@ -1199,10 +1362,11 @@ int acpi_cppc_processor_probe(struct acpi_processor= *pr) struct device *cpu_dev; acpi_handle handle =3D pr->handle; unsigned int num_ent, i, cpc_rev; + u32 unsupported_regs =3D 0; int pcc_subspace_id =3D -1; bool pcc_data_ref =3D false; acpi_status status; - int ret =3D -ENODATA; + int ret =3D -EINVAL; int err; =20 if (per_cpu(cpc_desc_ptr, pr->id)) @@ -1333,6 +1497,10 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) goto out_free; } =20 + cpc_ptr->cpc_regs[i - 2].type =3D ACPI_TYPE_BUFFER; + memcpy(&cpc_ptr->cpc_regs[i - 2].cpc_entry.reg, gas_t, + sizeof(*gas_t)); + /* * The PCC Subspace index is encoded inside * the CPC table entries. The same PCC index @@ -1340,6 +1508,12 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) * so extract it only once. */ if (gas_t->space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM) { + if (!gas_t->bit_width || gas_t->bit_width > 64 || + gas_t->bit_offset || gas_t->bit_width % 8) { + unsupported_regs |=3D BIT(i - 2); + continue; + } + if (pcc_subspace_id < 0) { pcc_subspace_id =3D gas_t->access_width; err =3D pcc_data_alloc(pcc_subspace_id); @@ -1351,6 +1525,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *= pr) } else if (pcc_subspace_id !=3D gas_t->access_width) { pr_debug("Mismatched PCC ids in _CPC for CPU:%d\n", pr->id); + ret =3D -EINVAL; goto out_free; } } else if (gas_t->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_MEMORY) { @@ -1358,21 +1533,11 @@ int acpi_cppc_processor_probe(struct acpi_processor= *pr) void __iomem *addr; size_t access_width; =20 - err =3D cpc_validate_sysmem_reg(cpc_ptr, gas_t, i - 2); - if (err && (i - 2 =3D=3D DESIRED_PERF || - i - 2 =3D=3D OSPM_NOMINAL_PERF)) { - const char *name =3D i - 2 =3D=3D DESIRED_PERF ? - "Desired Performance" : - "OSPM Nominal Performance"; - - pr_warn("CPU%d: disabling inaccessible %s register\n", - pr->id, name); - cpc_disable_reg(cpc_ptr, i - 2); - continue; - } + err =3D cpc_validate_sysmem_reg(cpc_ptr, gas_t, + i - 2); if (err) { - ret =3D err; - goto out_free; + unsupported_regs |=3D BIT(i - 2); + continue; } =20 if (!osc_cpc_flexible_adr_space_confirmed) { @@ -1384,8 +1549,10 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) access_width =3D cpc_reg_access_width(gas_t); access_width /=3D 8; addr =3D ioremap(gas_t->address, access_width); - if (!addr) + if (!addr) { + ret =3D -ENOMEM; goto out_free; + } cpc_ptr->cpc_regs[i - 2].sys_mem_vaddr =3D addr; } } else if (gas_t->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_IO) { @@ -1418,10 +1585,6 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) goto out_free; } } - - cpc_ptr->cpc_regs[i - 2].type =3D ACPI_TYPE_BUFFER; - memcpy(&cpc_ptr->cpc_regs[i - 2].cpc_entry.reg, gas_t, - sizeof(*gas_t)); } else if (cpc_obj->type =3D=3D ACPI_TYPE_PACKAGE && (i - 2) =3D=3D RESO= URCE_PRIORITY) { /* * ACPI 6.6, s8.4.6.1.2.7 defines Resource Priority as a @@ -1440,35 +1603,14 @@ int acpi_cppc_processor_probe(struct acpi_processor= *pr) } per_cpu(cpu_pcc_subspace_idx, pr->id) =3D pcc_subspace_id; =20 - /* - * Performance Limited is required by the specification, but tolerate a - * NULL descriptor used by firmware which cannot report limiting events. - * CPPC control does not depend on this status. - */ - for (i =3D 0; i < num_ent - 2; i++) { - if (i !=3D DESIRED_PERF && i !=3D PERF_LIMITED && - !IS_OPTIONAL_CPC_REG(i) && - !cpc_entry_present(&cpc_ptr->cpc_regs[i])) { - pr_debug("CPU:%d lacks mandatory _CPC register %u\n", - pr->id, i); - ret =3D -EINVAL; - goto out_free; - } - } + ret =3D cpc_resolve_unsupported(cpc_ptr, unsupported_regs); + if (ret) + goto out_free; + unsupported_regs =3D 0; =20 - /* - * In CPPC v1, DESIRED_PERF is mandatory. In CPPC v2, it is optional - * only when AUTO_SEL_ENABLE is supported. - */ - if (!cpc_is_writable(&cpc_ptr->cpc_regs[DESIRED_PERF]) && - (!osc_sb_cppc2_support_acked || - cpc_ptr->cpc_regs[AUTO_SEL_ENABLE].type !=3D ACPI_TYPE_INTEGER || - cpc_ptr->cpc_regs[AUTO_SEL_ENABLE].cpc_entry.int_value !=3D 1)) { - pr_debug("CPU:%d lacks a writable Desired Performance register\n", - pr->id); - ret =3D -EINVAL; + ret =3D cpc_validate_required_controls(cpc_ptr); + if (ret) goto out_free; - } =20 /* * Initialize the remaining cpc_regs as unsupported. @@ -1499,6 +1641,27 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) if (ret) goto out_free; } + if (pcc_subspace_id >=3D 0) { + ret =3D cpc_validate_pcc_bounds(cpc_ptr, + pcc_data[pcc_subspace_id], + &unsupported_regs); + if (ret) + goto out_free; + + ret =3D cpc_resolve_unsupported(cpc_ptr, unsupported_regs); + if (ret) + goto out_free; + } + + ret =3D cpc_validate_non_mmio_overlaps(cpc_ptr, + ACPI_ADR_SPACE_PLATFORM_COMM, + "PCC"); + if (ret) + goto out_free; + + ret =3D cpc_validate_required_controls(cpc_ptr); + if (ret) + goto out_free; =20 /* Everything looks okay */ pr_debug("Parsed CPC struct for CPU: %d\n", pr->id); @@ -1624,6 +1787,9 @@ int __weak cpc_write_ffh(int cpunum, struct cpc_reg *= reg, u64 val) static int cpc_read(int cpu, struct cpc_register_resource *reg_res, u64 *v= al) { void __iomem *vaddr =3D NULL; + unsigned long flags; + u8 buf[sizeof(*val)]; + unsigned int i; int size; int pcc_ss_id =3D per_cpu(cpu_pcc_subspace_idx, cpu); struct cpc_reg *reg =3D ®_res->cpc_entry.reg; @@ -1651,13 +1817,29 @@ static int cpc_read(int cpu, struct cpc_register_re= source *reg_res, u64 *val) =20 *val =3D val_u32; return 0; - } else if (reg->space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id= >=3D 0) { + } else if (reg->space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM) { + if (pcc_ss_id < 0 || !pcc_data[pcc_ss_id]) + return -ENODEV; + /* * For registers in PCC space, the register size is determined * by the bit width field; the access size is used to indicate * the PCC subspace id. */ vaddr =3D GET_PCC_VADDR(reg->address, pcc_ss_id); + size =3D reg->bit_width / 8; + if (!size || size > sizeof(buf) || reg->bit_width % 8) + return -EFAULT; + + raw_spin_lock_irqsave(&pcc_data[pcc_ss_id]->payload_lock, flags); + memcpy_fromio(buf, vaddr, size); + raw_spin_unlock_irqrestore(&pcc_data[pcc_ss_id]->payload_lock, + flags); + + *val =3D 0; + for (i =3D 0; i < size; i++) + *val |=3D (u64)buf[i] << (i * 8); + return 0; } else if (reg->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_MEMORY) vaddr =3D reg_res->sys_mem_vaddr; @@ -1707,6 +1889,8 @@ static int cpc_write(int cpu, struct cpc_register_res= ource *reg_res, u64 val) struct cpc_reg *reg; struct cpc_desc *cpc_desc; unsigned long flags; + u8 buf[sizeof(val)]; + unsigned int i; bool locked =3D false; =20 if (reg_res->type !=3D ACPI_TYPE_BUFFER) @@ -1731,13 +1915,28 @@ static int cpc_write(int cpu, struct cpc_register_r= esource *reg_res, u64 val) } =20 return 0; - } else if (reg->space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id= >=3D 0) { + } else if (reg->space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM) { + if (pcc_ss_id < 0 || !pcc_data[pcc_ss_id]) + return -ENODEV; + /* * For registers in PCC space, the register size is determined * by the bit width field; the access size is used to indicate * the PCC subspace id. */ vaddr =3D GET_PCC_VADDR(reg->address, pcc_ss_id); + size =3D reg->bit_width / 8; + if (!size || size > sizeof(buf) || reg->bit_width % 8) + return -EFAULT; + + for (i =3D 0; i < size; i++) + buf[i] =3D val >> (i * 8); + + raw_spin_lock_irqsave(&pcc_data[pcc_ss_id]->payload_lock, flags); + memcpy_toio(vaddr, buf, size); + raw_spin_unlock_irqrestore(&pcc_data[pcc_ss_id]->payload_lock, + flags); + return 0; } else if (reg->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_MEMORY) vaddr =3D reg_res->sys_mem_vaddr; --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 7FCB236AB6B; Thu, 27 Aug 2026 06:31:48 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812311; cv=none; b=KTTKwhkL+YJQ2ITRzZeYYOO35eJ9lhvW/JwH4Cvouk5hBaE9eqZ7l+kL3LcUTjkoiGaPbEyH7lQ1obzA1UnG1W81ZgtClADJ2WDJDrIWfjvxOhzH+Nf5dhwTVAIh2P/DmqoPdWKBYUQqCS9pRkC+CSgkyPkTv5dmZ0dECyNoA4A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812311; c=relaxed/simple; bh=KoDFLuTEy8ju7L0fvrqmjoLJCSMbqT+ymEhv18rK2B0=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=LUocixwVX//NRzN5BvY2JNXSES7VhKkwqGZMSnDRjKnD8uIEF1nLRhTfTrTqp+yPFPofhB/F5PpcaxAnSSFhyGmFE4XuiJ8U591/cue+xBlWx2XNfUfqnmUP6kkAOj9mt1ICF9GgXm4dsUd5WTpIjk/1VuC73mb2rQyXz+/lMJ0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=e+8caZU5; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="e+8caZU5" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id BDB441691; Wed, 26 Aug 2026 23:31:43 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id CD9E63F85F; Wed, 26 Aug 2026 23:31:38 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812307; bh=KoDFLuTEy8ju7L0fvrqmjoLJCSMbqT+ymEhv18rK2B0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=e+8caZU5ClQHz7Ya5YHEyA0nm64HgnqX6ZwICvO/zge2wWhLASw7lBj84rv17CZDG kgnNo5H9gCzo4IXFI5TXCkXCgcWS4cUH31e4xhdiuBD3tkbi8eC6AEtyU4OGoTz95j dGVT6Qrrn7FJJI7QWPoT9UaOlo5b1oLLdjbSdjHY= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle Subject: [PATCH v5 12/15] ACPI: CPPC: Validate SystemIO register layouts Date: Thu, 27 Aug 2026 07:30:57 +0100 Message-Id: <20260827063100.2741066-13-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" cpc_read() and cpc_write() access SystemIO registers using the complete GAS access width. They do not apply Bit Offset or preserve bits outside the described field. Accepting a partial register therefore reads the wrong value and can clobber adjacent fields on write. Only retain naturally aligned, full-width 8-, 16-, and 32-bit entries with Bit Offset zero whose complete access lies in the 16-bit I/O port space. Keep accepting Access Size zero when Bit Width supplies one of those sizes. Natural alignment is required because architectures which implement port I/O through MMIO may fault on unaligned Word or DWord Device-memory accesses. When CONFIG_HAS_IOPORT is disabled, mark SystemIO layouts inaccessible during probe. Also return -EOPNOTSUPP explicitly in cpc_read() and cpc_write() so a SystemIO entry can never fall through and treat its port number as a physical-memory address. Resolve inaccessible entries using the control-specific policy established for PCC: optional fields can be disabled, while mandatory or semantically required controls fail probe. Reject overlapping logical port ranges when either entry is writable; read-only overlaps remain allowed. These rejected partial forms are permitted by ACPI, but never worked with the existing whole-width Linux accessor. Implementing them would require field-aware I/O and appropriate RMW serialization. Fixes: a2c8f92bea5f ("ACPI: CPPC: Implement support for SystemIO registers") Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 54 ++++++++++++++++++++++++++-------------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index d67815574993..59a797b4250c 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -226,7 +226,6 @@ static bool cpc_integer_entry_valid(unsigned int reg_id= x, u64 value) */ #define NUM_RETRIES 500ULL =20 -#define OVER_16BTS_MASK ~0xFFFFULL #define CPC_GENERIC_REGISTER_DESCRIPTOR 0x82 #define CPC_GENERIC_REGISTER_LENGTH (sizeof(struct cpc_reg) - 3) =20 @@ -1556,21 +1555,29 @@ int acpi_cppc_processor_probe(struct acpi_processor= *pr) cpc_ptr->cpc_regs[i - 2].sys_mem_vaddr =3D addr; } } else if (gas_t->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_IO) { - if (gas_t->access_width < 1 || gas_t->access_width > 3) { - /* - * 1 =3D 8-bit, 2 =3D 16-bit, and 3 =3D 32-bit. - * SystemIO doesn't implement 64-bit - * registers. - */ - pr_debug("Invalid access width %d for SystemIO register in _CPC\n", - gas_t->access_width); - goto out_free; + u64 access_size; + const char *reason =3D "uses unsupported SystemIO geometry"; + unsigned int access_width; + bool unsupported; + + access_width =3D cpc_reg_access_width(gas_t); + unsupported =3D !IS_ENABLED(CONFIG_HAS_IOPORT) || + (access_width !=3D 8 && + access_width !=3D 16 && + access_width !=3D 32); + if (!unsupported) { + access_size =3D access_width / 8; + unsupported =3D gas_t->bit_offset || + gas_t->bit_width !=3D access_width || + (gas_t->address & (access_size - 1)) || + gas_t->address > + U16_MAX - (access_size - 1); } - if (gas_t->address & OVER_16BTS_MASK) { - /* SystemIO registers use 16-bit integer addresses */ - pr_debug("Invalid IO port %llu for SystemIO register in _CPC\n", - gas_t->address); - goto out_free; + if (unsupported) { + pr_debug("CPU%d: _CPC register %u %s\n", + pr->id, i - 2, reason); + unsupported_regs |=3D BIT(i - 2); + continue; } if (!osc_cpc_flexible_adr_space_confirmed) { pr_debug("Flexible address space capability not supported\n"); @@ -1658,6 +1665,11 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) "PCC"); if (ret) goto out_free; + ret =3D cpc_validate_non_mmio_overlaps(cpc_ptr, + ACPI_ADR_SPACE_SYSTEM_IO, + "SystemIO"); + if (ret) + goto out_free; =20 ret =3D cpc_validate_required_controls(cpc_ptr); if (ret) @@ -1802,11 +1814,13 @@ static int cpc_read(int cpu, struct cpc_register_re= source *reg_res, u64 *val) *val =3D 0; size =3D GET_BIT_WIDTH(reg); =20 - if (IS_ENABLED(CONFIG_HAS_IOPORT) && - reg->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_IO) { + if (reg->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_IO) { u32 val_u32; acpi_status status; =20 + if (!IS_ENABLED(CONFIG_HAS_IOPORT)) + return -EOPNOTSUPP; + status =3D acpi_os_read_port((acpi_io_address)reg->address, &val_u32, size); if (ACPI_FAILURE(status)) { @@ -1902,10 +1916,12 @@ static int cpc_write(int cpu, struct cpc_register_r= esource *reg_res, u64 val) =20 size =3D GET_BIT_WIDTH(reg); =20 - if (IS_ENABLED(CONFIG_HAS_IOPORT) && - reg->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_IO) { + if (reg->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_IO) { acpi_status status; =20 + if (!IS_ENABLED(CONFIG_HAS_IOPORT)) + return -EOPNOTSUPP; + status =3D acpi_os_write_port((acpi_io_address)reg->address, (u32)val, size); if (ACPI_FAILURE(status)) { --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 0EE8E369D7C; Thu, 27 Aug 2026 06:31:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812313; cv=none; b=FExKn/G0yHVm2KGhekH4c6lDCmtgxwudfwBy+4wUUOTBiSF0xvQqsBJormyQZsBm6ADJNnM61IvPEGk5HAr+LV9gpY3yM6Qa8C55Hn4OQW47cvAF4Y/mAXEzIFU6wCTju6ttkXPGcSrvtYrVGnD3k1K4qVcmEVJwEiEQ07hR7Yw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812313; c=relaxed/simple; bh=GzsPEyTxFxqzoh0T04eItXptd5ngkaoj38MBnDSPpZA=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=K4XHZ8XPp94q6GBNr3Dqq1ZBM2kBM8fW9xA3mC2JSRqEcYHvJsMb0467bPKmgkKrGRmO70nC/+5j+ZWYcIcwKx5lpuvQIgpmgOUKPmzLp4S8uCLDgP0ERlwwkfRREM1hlYsVwbbIrCDSf0c6aNmhFXtv8gTpx8hVffgXiIxS+BY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=j5jAboNL; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="j5jAboNL" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 72DF7169E; Wed, 26 Aug 2026 23:31:46 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 0FCBD3F85F; Wed, 26 Aug 2026 23:31:47 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812310; bh=GzsPEyTxFxqzoh0T04eItXptd5ngkaoj38MBnDSPpZA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=j5jAboNLXZVngQzSCraaTSwWmvI0PVvkDYyojwhnSJUHQr59lhCebo3Oy+1/KHtKF aBRgzhgWML1Jg7RiH+ZmsDrIUCXP+Pg8Qzwi1xKk3TcVJ1OhJZfd1UYpPqfOQX2KBm i/vfKgQV/aRQrPOT3QzbvZTy90O11Kce2OvjD1kM= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle Subject: [PATCH v5 13/15] ACPI: CPPC: Validate PCC overlaps across processors Date: Thu, 27 Aug 2026 07:30:58 +0100 Message-Id: <20260827063100.2741066-14-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" PCC shared-memory offsets are physical within a subspace, but the existing overlap check stops at one _CPC package. Two processors can therefore pass probe with writable ranges which overlap in the same PCC subspace. This is unsafe in the performance path, where CPUs may stage requests concurrently while holding the shared side of pcc_lock. Partially overlapping stores can construct a payload which belongs to neither request before a doorbell submits it. Index retained PCC byte ranges by subspace and physical interval. Permit read-only overlap and exact aliases of the same logical CPPC entry. The per-subspace payload lock serializes exact writable aliases, including multi-byte fields copied with byte-oriented I/O. Reject every other overlap involving a writable entry. The interval tree keeps registration proportional to real overlaps rather than to the number of processors. Remove entries when processor teardown unpublishes its CPC descriptor, including every probe-failure path after registration. Fixes: 85b1407bf6d2 ("ACPI / CPPC: Make CPPC ACPI driver aware of PCC subsp= ace IDs") Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 157 +++++++++++++++++++++++++++++++++++++++ include/acpi/cppc_acpi.h | 2 + 2 files changed, 159 insertions(+) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 59a797b4250c..955067925cf0 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -109,6 +109,18 @@ struct cpc_sysmem_node { bool registered; }; =20 +struct cpc_non_mmio_node { + struct rb_node rb; + u64 subtree_last; + u64 start; + u64 last; + struct cpc_desc *desc; + unsigned int reg_idx; + u8 space_id; + u8 pcc_ss_id; + bool registered; +}; + #define CPC_SYSMEM_START(node) ((node)->start) #define CPC_SYSMEM_LAST(node) ((node)->last) =20 @@ -119,6 +131,16 @@ INTERVAL_TREE_DEFINE(struct cpc_sysmem_node, rb, u64, = subtree_last, static struct rb_root_cached cpc_sysmem_tree =3D RB_ROOT_CACHED; static DEFINE_MUTEX(cpc_sysmem_lock); =20 +#define CPC_NON_MMIO_START(node) ((node)->start) +#define CPC_NON_MMIO_LAST(node) ((node)->last) + +INTERVAL_TREE_DEFINE(struct cpc_non_mmio_node, rb, u64, subtree_last, + CPC_NON_MMIO_START, CPC_NON_MMIO_LAST, static inline, + cpc_non_mmio_itree) + +static struct rb_root_cached cpc_pcc_trees[MAX_PCC_SUBSPACES]; +static DEFINE_MUTEX(cpc_non_mmio_lock); + static struct cpc_sysmem_node *cpc_sysmem_first(u64 start, u64 last) { return cpc_sysmem_itree_iter_first(&cpc_sysmem_tree, start, last); @@ -561,6 +583,134 @@ static int cpc_validate_non_mmio_overlaps(struct cpc_= desc *cpc_desc, return 0; } =20 +static struct rb_root_cached *cpc_non_mmio_tree(u8 space_id, u8 pcc_ss_id) +{ + if (space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM) + return &cpc_pcc_trees[pcc_ss_id]; + return NULL; +} + +static int cpc_validate_non_mmio_pair(const struct cpc_non_mmio_node *a, + const struct cpc_non_mmio_node *b) +{ + bool a_writable =3D cpc_reg_is_writable(a->reg_idx); + bool b_writable =3D cpc_reg_is_writable(b->reg_idx); + const char *name; + + if (!a_writable && !b_writable) + return 0; + + if (a->reg_idx =3D=3D b->reg_idx && a->start =3D=3D b->start && + a->last =3D=3D b->last) + return 0; + + name =3D "PCC"; + pr_err("CPU%d: %s _CPC register %u conflicts with CPU%d register %u\n", + a->desc->cpu_id, name, a->reg_idx, b->desc->cpu_id, + b->reg_idx); + return -EINVAL; +} + +static void cpc_unregister_non_mmio_desc_locked(struct cpc_desc *cpc_desc) +{ + unsigned int i; + + if (!cpc_desc->non_mmio_nodes) + return; + + for (i =3D 0; i < cpc_desc->num_entries - 2; i++) { + struct cpc_non_mmio_node *node =3D &cpc_desc->non_mmio_nodes[i]; + struct rb_root_cached *tree; + + if (!node->registered) + continue; + + tree =3D cpc_non_mmio_tree(node->space_id, node->pcc_ss_id); + cpc_non_mmio_itree_remove(node, tree); + } + + kfree(cpc_desc->non_mmio_nodes); + cpc_desc->non_mmio_nodes =3D NULL; +} + +static int cpc_register_non_mmio_desc(struct cpc_desc *cpc_desc, + int pcc_ss_id) +{ + unsigned int nr_regs =3D cpc_desc->num_entries - 2; + unsigned int i; + int ret =3D 0; + + cpc_desc->non_mmio_nodes =3D kcalloc(nr_regs, + sizeof(*cpc_desc->non_mmio_nodes), + GFP_KERNEL); + if (!cpc_desc->non_mmio_nodes) + return -ENOMEM; + + mutex_lock(&cpc_non_mmio_lock); + + for (i =3D 0; i < nr_regs; i++) { + struct cpc_register_resource *reg =3D &cpc_desc->cpc_regs[i]; + struct cpc_non_mmio_node *match, *node; + struct rb_root_cached *tree; + u8 space_id; + u64 size; + + if (!CPC_SUPPORTED(reg) || reg->type !=3D ACPI_TYPE_BUFFER) + continue; + + space_id =3D reg->cpc_entry.reg.space_id; + if (space_id !=3D ACPI_ADR_SPACE_PLATFORM_COMM) + continue; + + if (pcc_ss_id < 0) { + ret =3D -EINVAL; + goto out_unregister; + } + + node =3D &cpc_desc->non_mmio_nodes[i]; + size =3D cpc_non_mmio_access_size(reg); + node->start =3D reg->cpc_entry.reg.address; + node->last =3D node->start + size - 1; + node->desc =3D cpc_desc; + node->reg_idx =3D i; + node->space_id =3D space_id; + node->pcc_ss_id =3D pcc_ss_id; + tree =3D cpc_non_mmio_tree(space_id, node->pcc_ss_id); + + match =3D cpc_non_mmio_itree_iter_first(tree, node->start, + node->last); + while (match) { + ret =3D cpc_validate_non_mmio_pair(node, match); + if (ret) + goto out_unregister; + + match =3D cpc_non_mmio_itree_iter_next(match, node->start, + node->last); + } + + cpc_non_mmio_itree_insert(node, tree); + node->registered =3D true; + } + + mutex_unlock(&cpc_non_mmio_lock); + return 0; + +out_unregister: + cpc_unregister_non_mmio_desc_locked(cpc_desc); + mutex_unlock(&cpc_non_mmio_lock); + return ret; +} + +static void cpc_unregister_non_mmio_desc(struct cpc_desc *cpc_desc) +{ + if (!cpc_desc->non_mmio_nodes) + return; + + mutex_lock(&cpc_non_mmio_lock); + cpc_unregister_non_mmio_desc_locked(cpc_desc); + mutex_unlock(&cpc_non_mmio_lock); +} + static void cpc_mark_rmw_lock_users(struct cpc_desc *cpc_desc) { int i; @@ -802,6 +952,7 @@ static void cppc_free_desc(struct cpc_desc *cpc_ptr) { unsigned int i; =20 + cpc_unregister_non_mmio_desc(cpc_ptr); cpc_unregister_sysmem_desc(cpc_ptr); =20 for (i =3D 2; i < cpc_ptr->num_entries; i++) { @@ -1675,6 +1826,10 @@ int acpi_cppc_processor_probe(struct acpi_processor = *pr) if (ret) goto out_free; =20 + ret =3D cpc_register_non_mmio_desc(cpc_ptr, pcc_subspace_id); + if (ret) + goto out_free; + /* Everything looks okay */ pr_debug("Parsed CPC struct for CPU: %d\n", pr->id); =20 @@ -1692,6 +1847,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *= pr) "acpi_cppc"); if (ret) { per_cpu(cpc_desc_ptr, pr->id) =3D NULL; + cpc_unregister_non_mmio_desc(cpc_ptr); cpc_unregister_sysmem_desc(cpc_ptr); kobject_put(&cpc_ptr->kobj); goto out_pcc_put; @@ -1735,6 +1891,7 @@ void acpi_cppc_processor_exit(struct acpi_processor *= pr) pcc_ss_id =3D per_cpu(cpu_pcc_subspace_idx, pr->id); per_cpu(cpc_desc_ptr, pr->id) =3D NULL; kobject_del(&cpc_ptr->kobj); + cpc_unregister_non_mmio_desc(cpc_ptr); cpc_unregister_sysmem_desc(cpc_ptr); =20 pcc_data_put(pcc_ss_id); diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 79702c7c9efd..3275ecb51ca5 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -78,6 +78,7 @@ struct cpc_register_resource { }; =20 struct cpc_sysmem_node; +struct cpc_non_mmio_node; =20 /* Container to hold the CPC details for each CPU */ struct cpc_desc { @@ -91,6 +92,7 @@ struct cpc_desc { struct cpc_register_resource cpc_regs[MAX_CPC_REG_ENT]; struct acpi_psd_package domain_info; struct cpc_sysmem_node *sysmem_nodes; + struct cpc_non_mmio_node *non_mmio_nodes; struct kobject kobj; }; =20 --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id E3FF433E34B; Thu, 27 Aug 2026 06:31:53 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812316; cv=none; b=fdOkSHlhB18OMD9x0WspJnSeWyigoadBrK5177ekA3VaxOEy7flsGzh7VYfTGDqbUb5CZKOqFx7yfdcAueZTfLD+BolD3B4yYcFHqXYNOoUeYRZNM6uDA6Pnp17CHgT27bSxAt6Ozb91FQipBqA7ddM/scCGt5O9pnHDYu7SIqE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812316; c=relaxed/simple; bh=DvYYXglYfzb84lW1z5CfchsPP9ooqr7Nnovmy37rTKs=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=Ne8Dt4SYDOXTSV71VVeW9BSxxOACRWpZKrbo2dc+aGtM2qHdaAFakJIGbFFRJeKsL8a0NN5rubci7xmUfcijv44nOUtt35hV+BPkKe9vVRSNTndhPYd74/QkO6pMKG4aZcF65eyYEk/sLJuSAjytgQi7MCY/OYYWwjsK/znQOOo= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=XYa3wXMb; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="XYa3wXMb" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 062EA1691; Wed, 26 Aug 2026 23:31:49 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 94F443F85F; Wed, 26 Aug 2026 23:31:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812312; bh=DvYYXglYfzb84lW1z5CfchsPP9ooqr7Nnovmy37rTKs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=XYa3wXMbZ1IpVqdzwIStus90kXRYyOID9Y7XGzKqxvlBiR3QIyGfoQSkP26WwG+wh jaLVm0WcO0RU2sJGijNWInHIut9y4C30h/5zeN8EkKbta9AVV4Me5tQCVGxLEyqMfS CkGadRt0YCvfFmAr7pP9P+PAt6jhxa0hRNlde/XI= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle Subject: [PATCH v5 14/15] ACPI: CPPC: Validate SystemIO overlaps across processors Date: Thu, 27 Aug 2026 07:30:59 +0100 Message-Id: <20260827063100.2741066-15-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" System I/O port space is global, but the overlap check only compares registers within one _CPC package. Whole-width accesses from different processors can therefore target partially overlapping port ranges and clobber each other. Extend the physical interval registry to SystemIO. Permit read-only overlap and exact full-width aliases of the same logical CPPC entry. Reject every other cross-processor overlap involving a writable entry. Fixes: a2c8f92bea5f ("ACPI: CPPC: Implement support for SystemIO registers") Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 955067925cf0..62d50ec71ca7 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -139,6 +139,7 @@ INTERVAL_TREE_DEFINE(struct cpc_non_mmio_node, rb, u64,= subtree_last, cpc_non_mmio_itree) =20 static struct rb_root_cached cpc_pcc_trees[MAX_PCC_SUBSPACES]; +static struct rb_root_cached cpc_sysio_tree =3D RB_ROOT_CACHED; static DEFINE_MUTEX(cpc_non_mmio_lock); =20 static struct cpc_sysmem_node *cpc_sysmem_first(u64 start, u64 last) @@ -587,6 +588,8 @@ static struct rb_root_cached *cpc_non_mmio_tree(u8 spac= e_id, u8 pcc_ss_id) { if (space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM) return &cpc_pcc_trees[pcc_ss_id]; + if (space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_IO) + return &cpc_sysio_tree; return NULL; } =20 @@ -604,7 +607,8 @@ static int cpc_validate_non_mmio_pair(const struct cpc_= non_mmio_node *a, a->last =3D=3D b->last) return 0; =20 - name =3D "PCC"; + name =3D a->space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM ? + "PCC" : "SystemIO"; pr_err("CPU%d: %s _CPC register %u conflicts with CPU%d register %u\n", a->desc->cpu_id, name, a->reg_idx, b->desc->cpu_id, b->reg_idx); @@ -659,10 +663,11 @@ static int cpc_register_non_mmio_desc(struct cpc_desc= *cpc_desc, continue; =20 space_id =3D reg->cpc_entry.reg.space_id; - if (space_id !=3D ACPI_ADR_SPACE_PLATFORM_COMM) + if (space_id !=3D ACPI_ADR_SPACE_PLATFORM_COMM && + space_id !=3D ACPI_ADR_SPACE_SYSTEM_IO) continue; =20 - if (pcc_ss_id < 0) { + if (space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM && pcc_ss_id < 0) { ret =3D -EINVAL; goto out_unregister; } @@ -674,7 +679,8 @@ static int cpc_register_non_mmio_desc(struct cpc_desc *= cpc_desc, node->desc =3D cpc_desc; node->reg_idx =3D i; node->space_id =3D space_id; - node->pcc_ss_id =3D pcc_ss_id; + node->pcc_ss_id =3D space_id =3D=3D ACPI_ADR_SPACE_PLATFORM_COMM ? + pcc_ss_id : 0; tree =3D cpc_non_mmio_tree(space_id, node->pcc_ss_id); =20 match =3D cpc_non_mmio_itree_iter_first(tree, node->start, --=20 2.34.1 From nobody Sun Sep 27 02:19:53 2026 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id E176A37E5F2; Thu, 27 Aug 2026 06:31:55 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812318; cv=none; b=elfBHfwtz1ZSH7sQj/WhroP3rEj8s6ooJ8vgPGpUNudNiEAeDs+bTzVyBPFux0OslC3ZIEdO5NzNvEOs06V0yyGbV5zkLFecDb/vSTa0mufXxdfbd9xBwbWUNDINb1rM9wTFkI5oOxWmRkKF8/ZaMN4xhBt9oZ/olUWA6vAZRhE= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787812318; c=relaxed/simple; bh=wYDipPpsn3hVxVkCp+T3hJZXl4lxoxD6MBvRZ1E8Xq8=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=FeRB0RKOyAtx6UhAE+jtOsaRncgN1At0Vt2PHHNW66dOHdDYCfuaBpYjRSJJAW+JE/yAY9NhzYATkIzgR7x5SDbov8VTHFicKXdPt+UZrReJTT5qbM3DCj7+SzsQllKwckm4OuQAI7i9NYvs9YpUX5T1pjdmZJhV/GNZbDE+opM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b=uWUNTv4k; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=arm.com header.i=@arm.com header.b="uWUNTv4k" Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id B8FA0169E; Wed, 26 Aug 2026 23:31:51 -0700 (PDT) Received: from e127648.arm.com (unknown [10.57.73.34]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 2A22D3F85F; Wed, 26 Aug 2026 23:31:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=arm.com; s=foss; t=1787812315; bh=wYDipPpsn3hVxVkCp+T3hJZXl4lxoxD6MBvRZ1E8Xq8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uWUNTv4kZFK3QBh7m7kJzymtCVPUQXCVrnBXyYGpOB7Gdpa/mZkk8GHN7qfv2Tf8p jrYfXx/lv2E2gYCQpQpbhRB4CzgHMlbvVeYXaYQ6pu+fCMagYFs2JGTqDQsJVMjtzD I7/bw4zGHmSgHXtEqG6Vl6fl//m1zaxTOEpId70k= From: Christian Loehle To: "Rafael J . Wysocki" , Viresh Kumar Cc: linux-pm@vger.kernel.org, linux-acpi@vger.kernel.org, linux-kernel@vger.kernel.org, Len Brown , Jie Zhan , Lifeng Zheng , Pierre Gondois , Sumit Gupta , Sudeep Holla , Ionela Voinescu , zhongqiu.han@oss.qualcomm.com, Christian Loehle , Sashiko Subject: [PATCH v5 15/15] ACPI: CPPC: Clear Performance Limited without a stale read Date: Thu, 27 Aug 2026 07:31:00 +0100 Message-Id: <20260827063100.2741066-16-christian.loehle@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260827063100.2741066-1-christian.loehle@arm.com> References: <20260827063100.2741066-1-christian.loehle@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The Performance Limited status bits are sticky and write-zero-to-clear. ACPI 6.6 Section 8.4.6.1.3.2 also requires both entities to use interlocked accesses. cppc_set_perf_limited() currently reads the register, computes a new value, and writes it in a separate transaction. If the platform reports another excursion between those transactions, the stale write can clear that new event. Write zero to the requested bits and one to the other defined status bits directly. Keep reserved bits zero as required for hardware status registers by ACPI 6.6 Section 4.6.1. This removes the stale read window. A partial SystemMemory field would still make the generic writer perform a read-modify-write to preserve the containing access unit. The per-descriptor spinlock cannot interlock that RMW with platform updates, so reject clears of such a field. Keep the descriptor mapped and readable, because reading the containing access unit once and extracting the field does not require RMW. Also reject another writable SystemMemory field sharing Performance Limited's access unit. Its RMW could similarly replay stale status bits, and an OSPM lock cannot serialize against the platform. Also reject 64-bit SystemMemory descriptions on 32-bit kernels, where generic readq()/writeq() may be split into two 32-bit operations and cannot provide the required portable interlocked access. A naturally aligned full-width QWord remains supported on 64-bit kernels, where the architecture provides a native 64-bit MMIO accessor. Performance Limited status is not required for CPPC control. If firmware describes it using an access which Linux cannot read safely, disable that status register instead of rejecting the processor's otherwise usable _CPC package. Report reads as unsupported rather than returning a synthetic zero, and emit a single warning for each nonfatal fallback. Fixes: 13c45a26635f ("ACPI: CPPC: add APIs and sysfs interface for perf_lim= ited") Reported-by: Sashiko Link: https://sashiko.dev/#/patchset/20260807111303.1062391-1-christian.loe= hle%40arm.com Signed-off-by: Christian Loehle --- drivers/acpi/cppc_acpi.c | 74 +++++++++++++++++++++++++++------------- include/acpi/cppc_acpi.h | 1 + 2 files changed, 52 insertions(+), 23 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 62d50ec71ca7..5f0c5e1394d2 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -194,7 +194,8 @@ static struct cpc_sysmem_node *cpc_sysmem_next(struct c= pc_sysmem_node *node, static bool cpc_is_writable(const struct cpc_register_resource *cpc) { return cpc->type =3D=3D ACPI_TYPE_BUFFER && - !IS_NULL_REG(&cpc->cpc_entry.reg); + !IS_NULL_REG(&cpc->cpc_entry.reg) && + !cpc->cpc_entry.write_unsupported; } =20 static bool cpc_entry_present(const struct cpc_register_resource *cpc) @@ -390,7 +391,7 @@ static bool cpc_sysmem_reg_needs_rmw(const struct cpc_r= egister_resource *reg) return gas->bit_offset || gas->bit_width !=3D access_size * 8; } =20 -static int cpc_validate_sysmem_reg(const struct cpc_desc *cpc_desc, +static int cpc_validate_sysmem_reg(struct cpc_desc *cpc_desc, const struct cpc_reg *gas, unsigned int reg_idx) { @@ -412,6 +413,17 @@ static int cpc_validate_sysmem_reg(const struct cpc_de= sc *cpc_desc, if (gas->address & (access_size - 1)) goto invalid; =20 + if (reg_idx =3D=3D PERF_LIMITED) { + if (access_width =3D=3D 64 && !IS_ENABLED(CONFIG_64BIT)) + return -EINVAL; + + if (gas->bit_offset || gas->bit_width !=3D access_width) { + pr_warn("CPU%d: Performance Limited register cannot be cleared safely; = keeping it readable\n", + cpc_desc->cpu_id); + cpc_desc->cpc_regs[reg_idx].cpc_entry.write_unsupported =3D true; + } + } + return 0; =20 invalid: @@ -438,6 +450,14 @@ static int cpc_resolve_unsupported(struct cpc_desc *cp= c_desc, if (!(unsupported & BIT(i))) continue; =20 + /* CPPC control does not depend on Performance Limited status. */ + if (i =3D=3D PERF_LIMITED) { + pr_warn("CPU%d: ignoring inaccessible Performance Limited register\n", + cpc_desc->cpu_id); + cpc_disable_reg(cpc_desc, i); + continue; + } + if (i =3D=3D DESIRED_PERF && cpc_immutable_autonomous(cpc_desc)) { pr_warn("CPU%d: ignoring inaccessible Desired Performance register in a= utonomous mode\n", cpc_desc->cpu_id); @@ -724,7 +744,8 @@ static void cpc_mark_rmw_lock_users(struct cpc_desc *cp= c_desc) for (i =3D 0; i < cpc_desc->num_entries - 2; i++) { struct cpc_register_resource *reg =3D &cpc_desc->cpc_regs[i]; =20 - if (CPC_SUPPORTED(reg) && CPC_IN_SYSTEM_MEMORY(reg)) + if (CPC_SUPPORTED(reg) && CPC_IN_SYSTEM_MEMORY(reg) && + cpc_is_writable(reg)) reg->cpc_entry.use_rmw_lock =3D cpc_sysmem_reg_needs_rmw(reg); } @@ -805,10 +826,11 @@ static int cpc_validate_sysmem_pair(const struct cpc_= desc *a_desc, =20 if (cpc_same_sysmem_register(a_idx, a, b_idx, b)) { /* - * Cross-CPU partial writes were never safely serialized, and a - * 64-bit MMIO write may be split on 32-bit kernels. + * Identical read-only registers may be shared. Cross-CPU partial + * writes were never safely serialized, and a 64-bit MMIO write may + * be split on 32-bit kernels. */ - if (!a_writable || + if ((!cpc_is_writable(a) && !cpc_is_writable(b)) || (!cpc_sysmem_reg_needs_rmw(a) && (cpc_sysmem_access_size(a) < sizeof(u64) || IS_ENABLED(CONFIG_64BIT)))) @@ -816,6 +838,15 @@ static int cpc_validate_sysmem_pair(const struct cpc_d= esc *a_desc, goto conflict; } =20 + /* + * The platform may set Performance Limited asynchronously. A write to + * another field in the same access unit could write back stale status + * bits, which an OSPM lock cannot prevent. + */ + if ((a_idx =3D=3D PERF_LIMITED && b_writable && cpc_is_writable(b)) || + (b_idx =3D=3D PERF_LIMITED && a_writable && cpc_is_writable(a))) + goto conflict; + /* * A full-width writable register owns its access unit. It cannot * coexist with another logical field because it has no bits to @@ -2070,13 +2101,10 @@ static int cpc_write(int cpu, struct cpc_register_r= esource *reg_res, u64 val) unsigned int i; bool locked =3D false; =20 - if (reg_res->type !=3D ACPI_TYPE_BUFFER) + if (!cpc_is_writable(reg_res)) return -EOPNOTSUPP; =20 reg =3D ®_res->cpc_entry.reg; - if (IS_NULL_REG(reg)) - return -EOPNOTSUPP; - size =3D GET_BIT_WIDTH(reg); =20 if (reg->space_id =3D=3D ACPI_ADR_SPACE_SYSTEM_IO) { @@ -2238,9 +2266,13 @@ static int cppc_get_reg_val(int cpu, enum cppc_regs = reg_idx, u64 *val) =20 reg =3D &cpc_desc->cpc_regs[reg_idx]; =20 - /* Desired may be absent for immutable autonomous selection. */ + /* + * Desired and Performance Limited may be disabled despite not being + * generally optional. + */ if ((reg->type =3D=3D ACPI_TYPE_INTEGER && - (IS_OPTIONAL_CPC_REG(reg_idx) || reg_idx =3D=3D DESIRED_PERF) && + (IS_OPTIONAL_CPC_REG(reg_idx) || reg_idx =3D=3D DESIRED_PERF || + reg_idx =3D=3D PERF_LIMITED) && !reg->cpc_entry.int_value) || (reg->type !=3D ACPI_TYPE_INTEGER && IS_NULL_REG(®->cpc_entry.reg))) { pr_debug("CPC register is not supported\n"); @@ -3164,9 +3196,6 @@ EXPORT_SYMBOL_GPL(cppc_get_perf_limited); */ int cppc_set_perf_limited(int cpu, u64 bits_to_clear) { - u64 current_val, new_val; - int ret; - /* Only bits 0 and 1 are valid */ if (bits_to_clear & ~CPPC_PERF_LIMITED_MASK) return -EINVAL; @@ -3174,14 +3203,13 @@ int cppc_set_perf_limited(int cpu, u64 bits_to_clea= r) if (!bits_to_clear) return 0; =20 - ret =3D cppc_get_perf_limited(cpu, ¤t_val); - if (ret) - return ret; - - /* Clear the specified bits */ - new_val =3D current_val & ~bits_to_clear; - - return cppc_set_reg_val(cpu, PERF_LIMITED, new_val); + /* + * Performance Limited is write-zero-to-clear. Write one to the other + * defined sticky bits so a concurrently reported event is not cleared + * using a value obtained by an earlier, separate read transaction. + */ + return cppc_set_reg_val(cpu, PERF_LIMITED, + CPPC_PERF_LIMITED_MASK & ~bits_to_clear); } EXPORT_SYMBOL_GPL(cppc_set_perf_limited); =20 diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 3275ecb51ca5..91635fcac3c5 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -72,6 +72,7 @@ struct cpc_register_resource { struct { struct cpc_reg reg; bool use_rmw_lock; + bool write_unsupported; }; u64 int_value; } cpc_entry; --=20 2.34.1