From a5a612cd3eecbadf135df1e9b2b40bc9f4c28f1c Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:25 -0700 Subject: [PATCH 01/90] efi/cper: Reject CPER records with an out-of-range error_data_length cper_estatus_check() sizes each section with acpi_hest_get_record_size(), which adds the firmware-controlled u32 error_data_length to the header size as a signed int (see ). A value in the top sizeof(*gdata) bytes of the u32 range wraps the sum small rather than large, so it slips past the "record_size > data_len" check: against the 72-byte v300 header, 0xffffffb9 sizes the record at 1 and acpi_hest_get_next() walks it a byte at a time, off the end. 0xffffffb8 sizes it at 0 and loops forever. Use check_add_overflow() to reject a sum that will not fit the int those helpers return, since the walk advances by that value. That subsumes the "acpi_hest_get_size(gdata) > data_len" test above it, because record_size is never smaller than the header. The later "len < sizeof(*foo)" guards rely on this per-section upper bound; they are lower bounds only. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=1 Fixes: 45b14a4ffcc1 ("efi: cper: Fix possible out-of-bounds access") Reviewed-by: Hanjun Guo Reviewed-by: Shuai Xue Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-2-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/firmware/efi/cper.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c index 06b4fdb59917..332c78f2d561 100644 --- a/drivers/firmware/efi/cper.c +++ b/drivers/firmware/efi/cper.c @@ -752,7 +752,7 @@ EXPORT_SYMBOL_GPL(cper_estatus_check_header); int cper_estatus_check(const struct acpi_hest_generic_status *estatus) { struct acpi_hest_generic_data *gdata; - unsigned int data_len, record_size; + unsigned int data_len; int rc; rc = cper_estatus_check_header(estatus); @@ -762,10 +762,18 @@ int cper_estatus_check(const struct acpi_hest_generic_status *estatus) data_len = estatus->data_length; apei_estatus_for_each_section(estatus, gdata) { - if (acpi_hest_get_size(gdata) > data_len) + int record_size; + + /* + * The helpers sum these as a signed int, so a + * huge error_data_length wraps small rather than large and the + * walk then advances by that wrapped value. Reject a size an + * int cannot carry. + */ + if (check_add_overflow(acpi_hest_get_size(gdata), + gdata->error_data_length, &record_size)) return -EINVAL; - record_size = acpi_hest_get_record_size(gdata); if (record_size > data_len) return -EINVAL; From 41e25ef5e417c62eb7bf39b187242b283b3ef9f1 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:26 -0700 Subject: [PATCH 02/90] efi/cper: Reject an error status block length that wraps a u32 cper_estatus_len() sums the firmware-controlled data_length (or raw_data_offset plus raw_data_length) into a u32. A data_length of 0xffffffec wraps that sum to 0, and a length that reads back short defeats every bound built on it: bert_print_all() passes its "remain < estatus_len" check, then advances "estatus += estatus_len" by zero and loops forever. GHES survives only because __ghes_check_estatus() rejects a length below sizeof(*estatus) first. Reject a length that cannot be expressed in a u32 in cper_estatus_check_header(), which both of today's callers reach: GHES via __ghes_check_estatus() and BERT via cper_estatus_check(). extlog reaches neither yet; a later patch routes it through cper_estatus_check(), whose ELOG_ENTRY_LEN bound needs this to hold. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=1 Fixes: 06d65deade9a ("ACPI, APEI, UEFI Common Platform Error Record (CPER) header") Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Reviewed-by: Shuai Xue Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-3-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/firmware/efi/cper.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c index 332c78f2d561..3dcef618ad39 100644 --- a/drivers/firmware/efi/cper.c +++ b/drivers/firmware/efi/cper.c @@ -745,6 +745,17 @@ int cper_estatus_check_header(const struct acpi_hest_generic_status *estatus) estatus->raw_data_offset < sizeof(*estatus) + estatus->data_length) return -EINVAL; + /* + * cper_estatus_len() sums these into a u32, and a wrapped sum reads + * back smaller than the record. Reject a length that cannot be + * expressed so no caller is handed the short value. + */ + if ((u64)sizeof(*estatus) + estatus->data_length > U32_MAX) + return -EINVAL; + if (estatus->raw_data_length && + (u64)estatus->raw_data_offset + estatus->raw_data_length > U32_MAX) + return -EINVAL; + return 0; } EXPORT_SYMBOL_GPL(cper_estatus_check_header); From e1fd582a8134150aa10c1f0286dbce00cf63b086 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:27 -0700 Subject: [PATCH 03/90] ACPI: extlog: Validate elog record length before walking sections extlog_print() copies a fixed ELOG_ENTRY_LEN (4096) bytes from the elog record into elog_buf, then walks the sections using the firmware-controlled data_length. Nothing keeps data_length inside the buffer, so a malformed record walks the section pointer past elog_buf and reads adjacent memory. Unlike the GHES paths, extlog never calls cper_estatus_check(). Reject a record longer than ELOG_ENTRY_LEN and run cper_estatus_check() before walking the sections. The length test alone is not enough: a wrapped length reads back short and passes it, which cper_estatus_check() catches via the header check added earlier. Drop a malformed record with NOTIFY_DONE and without MCE_HANDLED_EXTLOG, since extlog did not consume it. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=6 Fixes: f6ec01da40e4 ("ACPI: extlog: Handle multiple records") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-4-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 7ad3b36013cc..046e92ee195f 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -208,6 +208,15 @@ static int extlog_print(struct notifier_block *nb, unsigned long val, tmp = (struct acpi_hest_generic_status *)elog_buf; + /* + * Bound the length before cper_estatus_check() walks the sections: it + * iterates over data_length, which is not yet known to fit elog_buf. + * cper_estatus_check_header() then rejects a length that wrapped, which + * the bound cannot see. + */ + if (cper_estatus_len(tmp) > ELOG_ENTRY_LEN || cper_estatus_check(tmp)) + return NOTIFY_DONE; + if (!ras_userspace_consumers()) { print_extlog_rcd(NULL, tmp, cpu); goto out; From 179ef6d7f971d72a0f54b7c499daed1b244ee1a5 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:28 -0700 Subject: [PATCH 04/90] ACPI: extlog: Defer CXL protocol error handling to avoid lock inversion extlog_print() calls cxl_cper_handle_prot_err() synchronously while the MCE notifier chain rwsem is held, and that path takes the PCI device_lock via guard(device)(). The probe path takes the two in the opposite order, holding device_lock while mce_register_decode_chain() takes the rwsem, so they can deadlock AB-BA. ghes.c already avoids this by posting protocol errors to a kfifo and handling them from a workqueue via cxl_cper_post_prot_err(). Export that function and call it instead. Declare it with the other CONFIG_ACPI_APEI_GHES exports rather than at the end of the header. No #else stub: ACPI_EXTLOG selects ACPI_APEI_GHES, so the only caller cannot exist without it. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-cxl/20260709165457.8BA181F000E9@smtp.kernel.org/ Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-5-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 21 ++------------------- drivers/acpi/apei/ghes.c | 5 +++-- include/acpi/ghes.h | 4 ++++ 3 files changed, 9 insertions(+), 21 deletions(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 046e92ee195f..2451362e696d 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -163,23 +163,6 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, #endif } -static void -extlog_cxl_cper_handle_prot_err(struct cxl_cper_sec_prot_err *prot_err, - int severity) -{ -#ifdef ACPI_APEI_PCIEAER - struct cxl_cper_prot_err_work_data wd; - - if (cxl_cper_sec_prot_err_valid(prot_err)) - return; - - if (cxl_cper_setup_prot_err_work_data(&wd, prot_err, severity)) - return; - - cxl_cper_handle_prot_err(&wd); -#endif -} - static int extlog_print(struct notifier_block *nb, unsigned long val, void *data) { @@ -244,8 +227,8 @@ static int extlog_print(struct notifier_block *nb, unsigned long val, struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); - extlog_cxl_cper_handle_prot_err(prot_err, - gdata->error_severity); + cxl_cper_post_prot_err(prot_err, + gdata->error_severity); } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index fe10ab0e02f6..83558bb10be1 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -752,8 +752,8 @@ static DEFINE_KFIFO(cxl_cper_prot_err_fifo, struct cxl_cper_prot_err_work_data, static DEFINE_RAW_SPINLOCK(cxl_cper_prot_err_work_lock); struct work_struct *cxl_cper_prot_err_work; -static void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, - int severity) +void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, + int severity) { #ifdef CONFIG_ACPI_APEI_PCIEAER struct cxl_cper_prot_err_work_data wd; @@ -777,6 +777,7 @@ static void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, schedule_work(cxl_cper_prot_err_work); #endif } +EXPORT_SYMBOL_FOR_MODULES(cxl_cper_post_prot_err, "acpi_extlog"); void cxl_cper_register_prot_err_work(struct work_struct *work) { diff --git a/include/acpi/ghes.h b/include/acpi/ghes.h index 8d7e5caef3f1..be496bc0386f 100644 --- a/include/acpi/ghes.h +++ b/include/acpi/ghes.h @@ -85,6 +85,10 @@ int devm_ghes_register_vendor_record_notifier(struct device *dev, struct list_head *ghes_get_devices(void); void ghes_estatus_pool_region_free(unsigned long addr, u32 size); + +struct cxl_cper_sec_prot_err; +void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, + int severity); #else static inline struct list_head *ghes_get_devices(void) { return NULL; } From 31333d134afb13e3942867d48efbcf8d6b88c849 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:29 -0700 Subject: [PATCH 05/90] ACPI: extlog: Avoid populating software AER metadata from raw hardware buffer extlog_print_pcie() casts pcie_err->aer_info straight to struct aer_capability_regs *. That struct embeds struct pcie_tlp_log, whose software-only header_len and flit fields sit at offset 84 - inside the 96-byte aer_info buffer - so the cast fills them with raw firmware bytes. pcie_print_tlp_log() uses both to bound a loop over dw[], and a large header_len walks past the end of the array. Copy into a zeroed local struct, and only as far as aer_info maps onto it: the leading registers and the four Header Log DWORDs. Place the TLP Prefix Log separately, from the offset the hardware keeps it at. The rest stays zero, which covers header_len and flit and keeps the Root Error registers out of the prefix log, where pcie_print_tlp_log() would print them as end-to-end prefixes. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-cxl/20260709165457.8BA181F000E9@smtp.kernel.org/ Fixes: e778ffefa34d ("ACPI: extlog: Trace CPER PCI Express Error Section") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-6-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 2451362e696d..1b61788abd5a 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -137,7 +137,7 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, int severity) { #ifdef ACPI_APEI_PCIEAER - struct aer_capability_regs *aer; + struct aer_capability_regs aer_regs = {}; struct pci_dev *pdev; unsigned int devfn; unsigned int bus; @@ -149,7 +149,21 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, return; aer_severity = cper_severity_to_aer(severity); - aer = (struct aer_capability_regs *)pcie_err->aer_info; + + /* + * struct pcie_tlp_log is larger than the hardware layout, so aer_info + * only maps onto the struct up to the four Header Log DWORDs. Copy that + * much, then place the TLP Prefix Log from where the hardware keeps it. + * Everything else stays zero: nothing reads root_command, root_status or + * the error source IDs, and header_len and flit are software-only. + */ + memcpy(&aer_regs, pcie_err->aer_info, + offsetof(struct aer_capability_regs, header_log) + + PCIE_STD_NUM_TLP_HEADERLOG * sizeof(u32)); + memcpy(aer_regs.header_log.prefix, + pcie_err->aer_info + PCI_ERR_PREFIX_LOG, + sizeof(aer_regs.header_log.prefix)); + domain = pcie_err->device_id.segment; bus = pcie_err->device_id.bus; devfn = PCI_DEVFN(pcie_err->device_id.device, @@ -158,7 +172,7 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, if (!pdev) return; - pci_print_aer(pdev, aer_severity, aer); + pci_print_aer(pdev, aer_severity, &aer_regs); pci_dev_put(pdev); #endif } From 5eefd849e26506170552e614496d094230650e78 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:30 -0700 Subject: [PATCH 06/90] ACPI: extlog: Validate PCIe error section length before payload access extlog_print_pcie() reads pcie_err->validation_bits and device_id and copies the 96-byte aer_info buffer without checking that gdata->error_data_length is big enough for a struct cper_sec_pcie. The cper_estatus_check() call added earlier keeps the read inside the estatus block, but a short section still gets stale adjacent bytes treated as PCIe error data. Reject a section too small to hold the record before touching any field, and warn: a truncated section means firmware is emitting malformed records. Reported-by: sashiko-bot@kernel.org Closes: https://lore.kernel.org/linux-cxl/20260709165457.8BA181F000E9@smtp.kernel.org/ Fixes: e778ffefa34d ("ACPI: extlog: Trace CPER PCI Express Error Section") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-7-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 1b61788abd5a..0ab7885e5471 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -134,7 +134,7 @@ static int print_extlog_rcd(const char *pfx, } static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, - int severity) + int severity, u32 len) { #ifdef ACPI_APEI_PCIEAER struct aer_capability_regs aer_regs = {}; @@ -144,6 +144,12 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, int aer_severity; int domain; + if (len < sizeof(*pcie_err)) { + pr_warn_ratelimited(FW_WARN + "PCIe error section too small (%u)\n", len); + return; + } + if (!(pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID && pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO)) return; @@ -246,7 +252,8 @@ static int extlog_print(struct notifier_block *nb, unsigned long val, } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); - extlog_print_pcie(pcie_err, gdata->error_severity); + extlog_print_pcie(pcie_err, gdata->error_severity, + gdata->error_data_length); } else { void *err = acpi_hest_get_payload(gdata); From 44a8d464d79b60883f7bd386049a1ffff32ca690 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:31 -0700 Subject: [PATCH 07/90] ACPI: extlog: Fix CONFIG_ACPI_APEI_PCIEAER guard typo The guard reads "#ifdef ACPI_APEI_PCIEAER" rather than "#ifdef CONFIG_ACPI_APEI_PCIEAER". That symbol is never defined, so the extlog PCIe AER handling is always compiled out. Use the CONFIG_ prefixed symbol. Fixes: e778ffefa34d ("ACPI: extlog: Trace CPER PCI Express Error Section") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Ben Cheatham Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-8-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 0ab7885e5471..8c17c2719526 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -136,7 +136,7 @@ static int print_extlog_rcd(const char *pfx, static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, int severity, u32 len) { -#ifdef ACPI_APEI_PCIEAER +#ifdef CONFIG_ACPI_APEI_PCIEAER struct aer_capability_regs aer_regs = {}; struct pci_dev *pdev; unsigned int devfn; From b7a4915a2b43d47f0bbb0b2a4c30e722fa0d3191 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:32 -0700 Subject: [PATCH 08/90] ACPI: APEI: GHES: Bound CXL event record copy to the firmware section length cxl_cper_post_event() copies a fixed sizeof(struct cxl_cper_event_rec) out of the firmware CPER section without checking how long the section actually is, so a short one reads past the record. Pass gdata->error_data_length in and reject a section too small to hold the record before the copy. Reported-by: sashiko-bot@kernel.org Link: https://sashiko.dev/#/patchset/20260617-topics-ahmtib01-ras_ffh_arm_internal_review-v6-0-91f725174aa0@arm.com?part=6 Fixes: 5e4a264bf8b5 ("acpi/ghes: Process CXL Component Events") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-9-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 83558bb10be1..8f66dfb8edba 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -824,10 +824,15 @@ static DEFINE_RAW_SPINLOCK(cxl_cper_work_lock); struct work_struct *cxl_cper_work; static void cxl_cper_post_event(enum cxl_event_type event_type, - struct cxl_cper_event_rec *rec) + struct cxl_cper_event_rec *rec, u32 len) { struct cxl_cper_work_data wd; + if (len < sizeof(*rec)) { + pr_err(FW_WARN "CXL CPER section too small (%u)\n", len); + return; + } + if (rec->hdr.length <= sizeof(rec->hdr) || rec->hdr.length > sizeof(*rec)) { pr_err(FW_WARN "CXL CPER Invalid section length (%u)\n", @@ -964,15 +969,18 @@ static void ghes_do_proc(struct ghes *ghes, } else if (guid_equal(sec_type, &CPER_SEC_CXL_GEN_MEDIA_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); - cxl_cper_post_event(CXL_CPER_EVENT_GEN_MEDIA, rec); + cxl_cper_post_event(CXL_CPER_EVENT_GEN_MEDIA, rec, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_CXL_DRAM_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); - cxl_cper_post_event(CXL_CPER_EVENT_DRAM, rec); + cxl_cper_post_event(CXL_CPER_EVENT_DRAM, rec, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_CXL_MEM_MODULE_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); - cxl_cper_post_event(CXL_CPER_EVENT_MEM_MODULE, rec); + cxl_cper_post_event(CXL_CPER_EVENT_MEM_MODULE, rec, + gdata->error_data_length); } else { void *err = acpi_hest_get_payload(gdata); From 8bf58815315aeb60125d26404d6fa7d576d5d518 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:33 -0700 Subject: [PATCH 09/90] ACPI: APEI: GHES: Validate CXL protocol error section length before RAS cap copy cxl_cper_setup_prot_err_work_data() locates the RAS Capability block at prot_err + sizeof(*prot_err) + dvsec_len and copies it, but dvsec_len is firmware controlled and never validated, so it can point the copy outside the section. Extend cxl_cper_sec_prot_err_valid() to check that the section can hold the header, and that the header, DVSEC and RAS Capability block together fit the reported section length. Reported-by: sashiko-bot@kernel.org Link: https://sashiko.dev/#/patchset/20260617-topics-ahmtib01-ras_ffh_arm_internal_review-v6-0-91f725174aa0@arm.com?part=6 Link: https://lore.kernel.org/linux-cxl/20260709165457.8BA181F000E9@smtp.kernel.org/ Fixes: 315c2f0b53ba ("acpi/ghes, cper: Recognize and cache CXL Protocol errors") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Ben Cheatham Reviewed-by: Jonathan Cameron Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-10-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 3 ++- drivers/acpi/apei/ghes.c | 7 ++++--- drivers/acpi/apei/ghes_helpers.c | 18 +++++++++++++++++- include/acpi/ghes.h | 2 +- include/cxl/event.h | 4 ++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 8c17c2719526..9e61354a807b 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -248,7 +248,8 @@ static int extlog_print(struct notifier_block *nb, unsigned long val, acpi_hest_get_payload(gdata); cxl_cper_post_prot_err(prot_err, - gdata->error_severity); + gdata->error_severity, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 8f66dfb8edba..111895c57c2f 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -753,12 +753,12 @@ static DEFINE_RAW_SPINLOCK(cxl_cper_prot_err_work_lock); struct work_struct *cxl_cper_prot_err_work; void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, - int severity) + int severity, u32 len) { #ifdef CONFIG_ACPI_APEI_PCIEAER struct cxl_cper_prot_err_work_data wd; - if (cxl_cper_sec_prot_err_valid(prot_err)) + if (cxl_cper_sec_prot_err_valid(prot_err, len)) return; guard(raw_spinlock_irqsave)(&cxl_cper_prot_err_work_lock); @@ -965,7 +965,8 @@ static void ghes_do_proc(struct ghes *ghes, } else if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR)) { struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); - cxl_cper_post_prot_err(prot_err, gdata->error_severity); + cxl_cper_post_prot_err(prot_err, gdata->error_severity, + gdata->error_data_length); } else if (guid_equal(sec_type, &CPER_SEC_CXL_GEN_MEDIA_GUID)) { struct cxl_cper_event_rec *rec = acpi_hest_get_payload(gdata); diff --git a/drivers/acpi/apei/ghes_helpers.c b/drivers/acpi/apei/ghes_helpers.c index bc7111b740af..df41b993f413 100644 --- a/drivers/acpi/apei/ghes_helpers.c +++ b/drivers/acpi/apei/ghes_helpers.c @@ -5,8 +5,15 @@ #include #include -int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err) +int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err, u32 len) { + if (len < sizeof(*prot_err)) { + pr_err_ratelimited(FW_WARN + "CXL CPER prot err section too small (%u)\n", + len); + return -EINVAL; + } + if (!(prot_err->valid_bits & PROT_ERR_VALID_AGENT_ADDRESS)) { pr_err_ratelimited("CXL CPER invalid agent type\n"); return -EINVAL; @@ -23,6 +30,15 @@ int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err) return -EINVAL; } + /* The RAS Capability block sits after a firmware-sized DVSEC. */ + if (sizeof(*prot_err) + prot_err->dvsec_len + + sizeof(struct cxl_ras_capability_regs) > len) { + pr_err_ratelimited(FW_WARN + "CXL CPER prot err DVSEC (%u) overruns section (%u)\n", + prot_err->dvsec_len, len); + return -EINVAL; + } + if ((prot_err->agent_type == RCD || prot_err->agent_type == DEVICE || prot_err->agent_type == LD || prot_err->agent_type == FMLD) && !(prot_err->valid_bits & PROT_ERR_VALID_SERIAL_NUMBER)) diff --git a/include/acpi/ghes.h b/include/acpi/ghes.h index be496bc0386f..7acf209061ea 100644 --- a/include/acpi/ghes.h +++ b/include/acpi/ghes.h @@ -88,7 +88,7 @@ void ghes_estatus_pool_region_free(unsigned long addr, u32 size); struct cxl_cper_sec_prot_err; void cxl_cper_post_prot_err(struct cxl_cper_sec_prot_err *prot_err, - int severity); + int severity, u32 len); #else static inline struct list_head *ghes_get_devices(void) { return NULL; } diff --git a/include/cxl/event.h b/include/cxl/event.h index b5673384d930..5779e35310c7 100644 --- a/include/cxl/event.h +++ b/include/cxl/event.h @@ -312,13 +312,13 @@ static inline int cxl_cper_prot_err_kfifo_get(struct cxl_cper_prot_err_work_data #endif #ifdef CONFIG_ACPI_APEI_PCIEAER -int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err); +int cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err, u32 len); int cxl_cper_setup_prot_err_work_data(struct cxl_cper_prot_err_work_data *wd, struct cxl_cper_sec_prot_err *prot_err, int severity); #else static inline int -cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err) +cxl_cper_sec_prot_err_valid(struct cxl_cper_sec_prot_err *prot_err, u32 len) { return -EOPNOTSUPP; } From 92480a2b00a15b0add512b6fc474151ec5340a8e Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:34 -0700 Subject: [PATCH 10/90] efi/cper: Read only validated fields in cper_mem_err_pack() cper_mem_err_pack() copies extended, rank, mem_array_handle and mem_dev_handle unconditionally. Those live at offsets 73 to 79, past the end of struct cper_sec_mem_err_old, the 73-byte UEFI 2.1/2.2 layout that older firmware still emits and that cper_estatus_print_section() admits. On such a record the copy reads up to seven bytes past the payload, and off the end of the error status block when that section is the last one. Copy each of the four only when its validation bit is set, and zero it otherwise. Nothing is lost: a 2.1/2.2 record leaves those bits clear, and every consumer of struct cper_mem_err_compact already gates the fields on the same bits. Zeroing also stops callers reading them back out of the uninitialised on-stack struct they pass in. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=7 Fixes: 2dfb7d51a61d ("trace, RAS: Add eMCA trace event interface") Reviewed-by: Hanjun Guo Reviewed-by: Shuai Xue Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-11-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/firmware/efi/cper.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/drivers/firmware/efi/cper.c b/drivers/firmware/efi/cper.c index 3dcef618ad39..13b3e72c3b62 100644 --- a/drivers/firmware/efi/cper.c +++ b/drivers/firmware/efi/cper.c @@ -389,10 +389,28 @@ void cper_mem_err_pack(const struct cper_sec_mem_err *mem, cmem->requestor_id = mem->requestor_id; cmem->responder_id = mem->responder_id; cmem->target_id = mem->target_id; - cmem->extended = mem->extended; - cmem->rank = mem->rank; - cmem->mem_array_handle = mem->mem_array_handle; - cmem->mem_dev_handle = mem->mem_dev_handle; + + /* + * These four sit past the end of the UEFI 2.1/2.2 layout, which older + * firmware still emits, so reading them unconditionally runs off a + * short record. Every consumer of the compact record gates them on the + * same validation bits, so leave them zero when firmware does not + * claim them. + */ + cmem->extended = 0; + cmem->rank = 0; + cmem->mem_array_handle = 0; + cmem->mem_dev_handle = 0; + + if (mem->validation_bits & + (CPER_MEM_VALID_ROW_EXT | CPER_MEM_VALID_CHIP_ID)) + cmem->extended = mem->extended; + if (mem->validation_bits & CPER_MEM_VALID_RANK_NUMBER) + cmem->rank = mem->rank; + if (mem->validation_bits & CPER_MEM_VALID_CARD_HANDLE) + cmem->mem_array_handle = mem->mem_array_handle; + if (mem->validation_bits & CPER_MEM_VALID_MODULE_HANDLE) + cmem->mem_dev_handle = mem->mem_dev_handle; } EXPORT_SYMBOL_GPL(cper_mem_err_pack); From 10338c9085b4939a428a8dc78d2bbf62c161278b Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:35 -0700 Subject: [PATCH 11/90] ACPI: APEI: GHES: Validate memory error section length before payload access ghes_do_proc() hands the CPER_SEC_PLATFORM_MEM payload to the report chain, arch_apei_report_mem_error() and ghes_handle_memory_failure() without checking gdata->error_data_length. All three read validation_bits and physical_addr, at offsets 0 and 16, so a shorter section reads past the record. Check the length once in ghes_do_proc(), before any consumer runs. Take the 73-byte struct cper_sec_mem_err_old as the floor: older firmware legitimately emits that UEFI 2.1/2.2 layout, and it makes validation_bits safe to read. The fields from "extended" on are absent from that layout, so also require whatever length the validation bits claim. Derive it per field rather than demanding the full 80 bytes: rank needs only 76, and the BANK_GROUP and BANK_ADDRESS bits describe "bank" at offset 38, which every record carries. Testing the whole mask against one size, as cper_print_mem() does, gets it wrong both ways. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=7 Fixes: ca104edc1784 ("ACPI, APEI, GHES: Cleanup ghes memory error handling") Reviewed-by: Hanjun Guo Reviewed-by: Shuai Xue Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-12-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 111895c57c2f..2077cc90569a 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -929,6 +929,28 @@ static void ghes_log_hwerr(int sev, guid_t *sec_type) hwerr_log_error_type(HWERR_RECOV_OTHERS); } +/* + * The fields from "extended" on are absent from the 73-byte UEFI 2.1/2.2 + * layout that older firmware still emits. Return the length needed for the + * fields the validation bits claim, so over-claiming is rejected without + * rejecting an honest short record. + */ +static u32 ghes_mem_err_min_len(u64 validation_bits) +{ + u32 len = sizeof(struct cper_sec_mem_err_old); + + if (validation_bits & (CPER_MEM_VALID_ROW_EXT | CPER_MEM_VALID_CHIP_ID)) + len = offsetof(struct cper_sec_mem_err, rank); + if (validation_bits & CPER_MEM_VALID_RANK_NUMBER) + len = offsetof(struct cper_sec_mem_err, mem_array_handle); + if (validation_bits & CPER_MEM_VALID_CARD_HANDLE) + len = offsetof(struct cper_sec_mem_err, mem_dev_handle); + if (validation_bits & CPER_MEM_VALID_MODULE_HANDLE) + len = sizeof(struct cper_sec_mem_err); + + return len; +} + static void ghes_do_proc(struct ghes *ghes, const struct acpi_hest_generic_status *estatus) { @@ -954,6 +976,25 @@ static void ghes_do_proc(struct ghes *ghes, if (guid_equal(sec_type, &CPER_SEC_PLATFORM_MEM)) { struct cper_sec_mem_err *mem_err = acpi_hest_get_payload(gdata); + /* + * Check once for all three consumers below. The 73-byte + * UEFI 2.1/2.2 layout is the floor, matching + * cper_estatus_print_section() and making + * validation_bits safe to read. + */ + if (gdata->error_data_length < + sizeof(struct cper_sec_mem_err_old)) + continue; + + /* Then require what the claimed fields actually need. */ + if (gdata->error_data_length < + ghes_mem_err_min_len(mem_err->validation_bits)) { + pr_warn_ratelimited(FW_WARN GHES_PFX + "memory error section too small (%u) for the fields it claims\n", + gdata->error_data_length); + continue; + } + atomic_notifier_call_chain(&ghes_report_chain, sev, mem_err); arch_apei_report_mem_error(sev, mem_err); From 9c3ad3bfff20d2d7a0c9614db2a58e78123bf0ce Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:36 -0700 Subject: [PATCH 12/90] ACPI: APEI: GHES: Bound AER info copy and sanitize software metadata ghes_handle_aer() copies sizeof(struct aer_capability_regs) out of the fixed 96-byte pcie_err->aer_info. The struct is larger, so the copy reads past the section, and it fills the software-only header_len and flit fields of the embedded struct pcie_tlp_log from firmware bytes. pcie_print_tlp_log() uses both to bound a loop over dw[], so a large value walks past the array. Nothing checks the section can hold a struct cper_sec_pcie either. Validate error_data_length, zero the destination, and map aer_info onto the struct as extlog_print_pcie() does: copy up to the four Header Log DWORDs, then place the TLP Prefix Log from its own offset. The rest stays zero, covering header_len and flit. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260714231835.303081-1-dave.jiang@intel.com?part=3 Fixes: 7e077e6707b3 ("PCI/ERR: Handle TLP Log in Flit mode") Reviewed-by: Alison Schofield Reviewed-by: Shuai Xue Reviewed-by: Hanjun Guo Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-13-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 2077cc90569a..08c985e729d6 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -642,11 +642,14 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata) #ifdef CONFIG_ACPI_APEI_PCIEAER struct cper_sec_pcie *pcie_err = acpi_hest_get_payload(gdata); + if (gdata->error_data_length < sizeof(*pcie_err)) + return; + if (pcie_err->validation_bits & CPER_PCIE_VALID_DEVICE_ID && pcie_err->validation_bits & CPER_PCIE_VALID_AER_INFO) { + struct aer_capability_regs *aer_info; unsigned int devfn; int aer_severity; - u8 *aer_info; devfn = PCI_DEVFN(pcie_err->device_id.device, pcie_err->device_id.function); @@ -664,13 +667,25 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata) sizeof(struct aer_capability_regs)); if (!aer_info) return; - memcpy(aer_info, pcie_err->aer_info, sizeof(struct aer_capability_regs)); + + /* + * Map aer_info onto the struct as extlog_print_pcie() does: + * copy up to the four Header Log DWORDs, then place the TLP + * Prefix Log from where the hardware keeps it. The rest stays + * zero, so firmware cannot drive the pcie_print_tlp_log() loop + * over dw[] out of bounds. + */ + memset(aer_info, 0, sizeof(struct aer_capability_regs)); + memcpy(aer_info, pcie_err->aer_info, + offsetof(struct aer_capability_regs, header_log) + + PCIE_STD_NUM_TLP_HEADERLOG * sizeof(u32)); + memcpy(aer_info->header_log.prefix, + pcie_err->aer_info + PCI_ERR_PREFIX_LOG, + sizeof(aer_info->header_log.prefix)); aer_recover_queue(pcie_err->device_id.segment, pcie_err->device_id.bus, - devfn, aer_severity, - (struct aer_capability_regs *) - aer_info); + devfn, aer_severity, aer_info); } #endif } From a412a81560b3b0ec992872e8ce8858d78c3abd3f Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 4 Sep 2026 10:23:37 -0700 Subject: [PATCH 13/90] cxl/ras: Make cxl_cper_handle_prot_err() static The extlog caller went away when extlog switched to cxl_cper_post_prot_err(), leaving cxl_cper_prot_err_work_fn() in the same file as the only caller. Drop the export and the declaration in , and make it static. Reviewed-by: Hanjun Guo Reviewed-by: Shuai Xue Signed-off-by: Dave Jiang Link: https://patch.msgid.link/20260904172337.1409775-14-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/cxl/core/ras.c | 3 +-- include/cxl/event.h | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/cxl/core/ras.c b/drivers/cxl/core/ras.c index e307361bb39e..c91db125a971 100644 --- a/drivers/cxl/core/ras.c +++ b/drivers/cxl/core/ras.c @@ -77,7 +77,7 @@ static int match_memdev_by_parent(struct device *dev, const void *uport) return 0; } -void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *data) +static void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *data) { unsigned int devfn = PCI_DEVFN(data->prot_err.agent_addr.device, data->prot_err.agent_addr.function); @@ -118,7 +118,6 @@ void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *data) else cxl_cper_trace_uncorr_prot_err(cxlmd, data->ras_cap); } -EXPORT_SYMBOL_GPL(cxl_cper_handle_prot_err); static void cxl_cper_prot_err_work_fn(struct work_struct *work) { diff --git a/include/cxl/event.h b/include/cxl/event.h index 5779e35310c7..a9f5c2381cc9 100644 --- a/include/cxl/event.h +++ b/include/cxl/event.h @@ -331,6 +331,4 @@ cxl_cper_setup_prot_err_work_data(struct cxl_cper_prot_err_work_data *wd, } #endif -void cxl_cper_handle_prot_err(struct cxl_cper_prot_err_work_data *wd); - #endif /* _LINUX_CXL_EVENT_H */ From 21af603b6509e1e2297de5c159317639477dcbf3 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Fri, 7 Aug 2026 01:38:17 +0200 Subject: [PATCH 14/90] ACPI: battery: Fix bogus cycle count values on systems without _BIX Devices supporting only the legacy _BIF ACPI control method cannot know the cycle count of the associated battery, so cycle_count always remains 0. Claiming support for POWER_SUPPLY_PROP_CYCLE_COUNT on such devices can thus mislead userspace applications and prevents power supply extensions from supplying correct cycle count values. Fix this by not claiming support for POWER_SUPPLY_PROP_CYCLE_COUNT on devices without _BIX. Signed-off-by: Armin Wolf Link: https://patch.msgid.link/20260806233817.622975-1-W_Armin@gmx.de Signed-off-by: Rafael J. Wysocki --- drivers/acpi/battery.c | 131 +++++++++++++++++++++++++++++++++++------ 1 file changed, 113 insertions(+), 18 deletions(-) diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index 670853ec3a4d..8599949f8786 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -341,7 +341,9 @@ static int acpi_battery_get_property(struct power_supply *psy, return ret; } -static const enum power_supply_property charge_battery_props[] = { +/* For devices supporting the _BIX ACPI control method */ + +static const enum power_supply_property charge_battery_extended_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, @@ -359,7 +361,7 @@ static const enum power_supply_property charge_battery_props[] = { POWER_SUPPLY_PROP_SERIAL_NUMBER, }; -static const enum power_supply_property charge_battery_full_cap_broken_props[] = { +static const enum power_supply_property charge_battery_full_cap_broken_extended_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, @@ -373,7 +375,7 @@ static const enum power_supply_property charge_battery_full_cap_broken_props[] = POWER_SUPPLY_PROP_SERIAL_NUMBER, }; -static const enum power_supply_property energy_battery_props[] = { +static const enum power_supply_property energy_battery_extended_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, @@ -391,7 +393,7 @@ static const enum power_supply_property energy_battery_props[] = { POWER_SUPPLY_PROP_SERIAL_NUMBER, }; -static const enum power_supply_property energy_battery_full_cap_broken_props[] = { +static const enum power_supply_property energy_battery_full_cap_broken_extended_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_TECHNOLOGY, @@ -405,6 +407,68 @@ static const enum power_supply_property energy_battery_full_cap_broken_props[] = POWER_SUPPLY_PROP_SERIAL_NUMBER, }; +/* For devices supporting only the _BIF ACPI control method */ + +static const enum power_supply_property charge_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static const enum power_supply_property charge_battery_full_cap_broken_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static const enum power_supply_property energy_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, + POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + +static const enum power_supply_property energy_battery_full_cap_broken_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_SERIAL_NUMBER, +}; + /* Battery Management */ struct acpi_offsets { size_t offset; /* offset inside struct acpi_sbs_battery */ @@ -904,6 +968,7 @@ static void __exit battery_hook_exit(void) static int sysfs_add_battery(struct acpi_battery *battery) { + bool extended_info_available = test_bit(ACPI_BATTERY_XINFO_PRESENT, &battery->flags); struct power_supply_config psy_cfg = { .drv_data = battery, .attr_grp = acpi_battery_groups, @@ -922,25 +987,55 @@ static int sysfs_add_battery(struct acpi_battery *battery) if (power_unit == ACPI_BATTERY_POWER_UNIT_MA) { if (full_cap_broken) { - battery->bat_desc.properties = - charge_battery_full_cap_broken_props; - battery->bat_desc.num_properties = - ARRAY_SIZE(charge_battery_full_cap_broken_props); + if (extended_info_available) { + battery->bat_desc.properties = + charge_battery_full_cap_broken_extended_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(charge_battery_full_cap_broken_extended_props); + } else { + battery->bat_desc.properties = + charge_battery_full_cap_broken_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(charge_battery_full_cap_broken_props); + } } else { - battery->bat_desc.properties = charge_battery_props; - battery->bat_desc.num_properties = - ARRAY_SIZE(charge_battery_props); + if (extended_info_available) { + battery->bat_desc.properties = + charge_battery_extended_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(charge_battery_extended_props); + } else { + battery->bat_desc.properties = + charge_battery_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(charge_battery_props); + } } } else { if (full_cap_broken) { - battery->bat_desc.properties = - energy_battery_full_cap_broken_props; - battery->bat_desc.num_properties = - ARRAY_SIZE(energy_battery_full_cap_broken_props); + if (extended_info_available) { + battery->bat_desc.properties = + energy_battery_full_cap_broken_extended_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(energy_battery_full_cap_broken_extended_props); + } else { + battery->bat_desc.properties = + energy_battery_full_cap_broken_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(energy_battery_full_cap_broken_props); + } } else { - battery->bat_desc.properties = energy_battery_props; - battery->bat_desc.num_properties = - ARRAY_SIZE(energy_battery_props); + if (extended_info_available) { + battery->bat_desc.properties = + energy_battery_extended_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(energy_battery_extended_props); + } else { + battery->bat_desc.properties = + energy_battery_props; + battery->bat_desc.num_properties = + ARRAY_SIZE(energy_battery_props); + } } } From 72a35790da31acc12f05d35466e46254439d5dcd Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 21 Aug 2026 22:15:08 +0900 Subject: [PATCH 15/90] ACPI: MRRM: Use for_each_populated_zone() in get_node_num() get_node_num() iterates over the populated zones of every online node with a nested loop. for_each_populated_zone() iterates over the same zones in the same order. So use for_each_populated_zone() instead and remove the unused variable. No functional change. Signed-off-by: Sang-Heon Jeon Link: https://patch.msgid.link/20260821131530.3375820-1-ekffu200098@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_mrrm.c | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/drivers/acpi/acpi_mrrm.c b/drivers/acpi/acpi_mrrm.c index e99cbda83452..83934e2aa0d4 100644 --- a/drivers/acpi/acpi_mrrm.c +++ b/drivers/acpi/acpi_mrrm.c @@ -36,17 +36,11 @@ static u32 mrrm_mem_entry_num; static int get_node_num(struct mrrm_mem_range_entry *e) { - unsigned int nid; + struct zone *zone; - for_each_online_node(nid) { - for (int z = 0; z < MAX_NR_ZONES; z++) { - struct zone *zone = NODE_DATA(nid)->node_zones + z; - - if (!populated_zone(zone)) - continue; - if (zone_intersects(zone, PHYS_PFN(e->base), PHYS_PFN(e->length))) - return zone_to_nid(zone); - } + for_each_populated_zone(zone) { + if (zone_intersects(zone, PHYS_PFN(e->base), PHYS_PFN(e->length))) + return zone_to_nid(zone); } return -ENOENT; From d776369a9eded071df83e4738e1572c4c5b912d7 Mon Sep 17 00:00:00 2001 From: Karl Mehltretter Date: Thu, 27 Aug 2026 07:37:41 +0200 Subject: [PATCH 16/90] ACPI: pfr_update: zero-initialize capability query result query_capability() copies four ACPI buffers into fixed-size fields using the firmware-supplied lengths. The existing overflow guard rejects oversized buffers, but shorter buffers are accepted and leave trailing bytes of the stack-allocated cap_hdr uninitialized. PFRU_IOC_QUERY_CAP then copies the entire structure to userspace. Zero-initialize cap_hdr before querying the firmware. This preserves the existing handling of short firmware responses while preventing disclosure of kernel stack data. Oversized buffers remain rejected. A QEMU _DSM returning one-byte buffers reproduced nonzero stack suffix bytes in all four fields. With this change, the ioctl still succeeds and each unwritten suffix is zero-filled, while 64-byte buffers continue to return -EINVAL. Fixes: 0db89fa243e5 ("ACPI: Introduce Platform Firmware Runtime Update device driver") Reported-by: Sashiko Closes: https://sashiko.dev/#/patchset/1a001e1fee9.637da6dc3533246.238498880682901704%40a0rg.com Cc: All applicable Signed-off-by: Karl Mehltretter Link: https://patch.msgid.link/20260827053741.79425-1-kmehltretter@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/pfr_update.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/pfr_update.c b/drivers/acpi/pfr_update.c index 9afd2c52fdbd..98ace679601b 100644 --- a/drivers/acpi/pfr_update.c +++ b/drivers/acpi/pfr_update.c @@ -422,7 +422,7 @@ static int start_update(int action, struct pfru_device *pfru_dev) static long pfru_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { - struct pfru_update_cap_info cap_hdr; + struct pfru_update_cap_info cap_hdr = {}; struct pfru_device *pfru_dev = to_pfru_dev(file); void __user *p = (void __user *)arg; u32 rev; From f16c512a07b954aa3b0b3c580e7abb5dd4f5e703 Mon Sep 17 00:00:00 2001 From: Ian Gallagher Date: Sun, 30 Aug 2026 13:19:24 +1200 Subject: [PATCH 17/90] ACPI: button: Add lid_init_state quirk for Razer Blade Stealth 13 (Early 2020) RZ09-0310 This patch adds a quirk mapping for the "Blade Stealth 13 (Early 2020) - RZ09-0310" laptop, which exhibits the same issue as the following two related products already accounted for in this file. - Razer Blade Stealth 13 Late 2019 - Blade Pro 17 (Early 2020) - RZ09-0329 Summary of the issue this fixes: The firmware's _LID method still returns "closed" after resume when the lid was opened during S3, causing systemd-logind to suspend the system again within several seconds (15-40) after waking. Signed-off-by: Ian Gallagher Link: https://patch.msgid.link/90ab9b58-da8b-4ffc-bd4b-adf7590545a9@app.fastmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/button.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index cdbb1023a8ee..34c7173b5a6c 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -144,6 +144,18 @@ static const struct dmi_system_id dmi_lid_quirks[] = { }, .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_OPEN, }, + { + /* + * Razer Blade Stealth 13 early 2020, when the lid is opened + * while suspended the open notification is lost and _LID keeps + * returning closed after resume, causing spurious re-suspends. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Razer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Blade Stealth 13 (Early 2020) - RZ09-0310"), + }, + .driver_data = (void *)(long)ACPI_BUTTON_LID_INIT_OPEN, + }, { /* * Samsung galaxybook2 ,initial _LID device notification returns From 412746bb6652ddc6fa435bc11a9b1253d63bf8a6 Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Mon, 31 Aug 2026 06:51:04 +0200 Subject: [PATCH 18/90] ACPI: OSL: Use vsnprintf() in acpi_os_vprintf() acpi_os_vprintf() formats every ACPICA diagnostic into a 512-byte static buffer with an unbounded vsprintf(): static char buffer[512]; vsprintf(buffer, fmt, args); ACPICA interpolates table-derived namespace paths into its messages, so the length is driven by table content. A malformed ACPI table can therefore produce a diagnostic longer than the buffer and overwrite whatever follows it. From the System.map of an x86_64_defconfig build, acpi_ioremap_lock sits 624 bytes into that buffer - 112 bytes past its end - so bytes 624..631 of an over-long message land on the mutex's owner field, which __mutex_lock() then dereferences. The overwritten bytes are attacker-influenced but not attacker-controlled: they are printable ASCII from a NUL-terminated string, so the resulting pointer is always non-canonical and always faults. This is a reliable denial of service, not a demonstrated pointer-control primitive. Because acpi_tb_load_namespace() parses the DSDT and every SSDT during acpi_init(), a table supplied by firmware triggers this in PID 1 inside do_one_initcall(), panicking the kernel before userspace exists. Bound the write with vsnprintf(). Truncating a diagnostic is strictly better than corrupting adjacent kernel objects. Tested on pristine Linux 7.2.2 - tag: v7.2.2., Stock x86_64_defconfig (no KASAN, no debug options), table supplied by firmware via "qemu -acpitable": [ 0.022259] ACPI: SSDT 0x000000003FFE1C52 000640 (v01 _ASUS_ Notebook 00000005 MSFT 0100000D) [ 0.353684] Oops: general protection fault, probably for non-canonical address 0x41435f2e345f2e64: 0000 [#1] SMP NOPTI [ 0.353727] RIP: 0010:__mutex_lock.constprop.0+0xbd/0xa40 [ 0.353727] RAX: 41435f2e345f2e30 RBX: 00000000fed00000 [ 0.353727] R15: ffffffff8c248530 [ 0.353727] Call Trace: [ 0.353727] acpi_os_map_iomem [ 0.353727] acpi_ex_system_memory_space_handler [ 0.353727] acpi_ev_address_space_dispatch [ 0.353727] acpi_ex_access_region [ 0.353727] acpi_ex_read_data_from_field [ 0.353727] acpi_ex_resolve_node_to_value [ 0.353727] acpi_ds_evaluate_name_path [ 0.353727] acpi_ps_parse_loop [ 0.353727] acpi_ps_execute_method [ 0.354729] Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000009 RAX is the corrupted owner; the faulting address is RAX + 0x34. R15 matches acpi_ioremap_lock. On a KASAN build the overflow is reported directly as: BUG: KASAN: global-out-of-bounds in string. Signed-off-by: Vlatko Kosturjak [ rjw: Subject tweak ] Link: https://patch.msgid.link/CAPAw8HEMdsNN6WhizHkv2Orp2pkQ2-g009XCGFnKnhXoRhcKZQ@mail.gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/osl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index ed2162a97bd2..5b7aefb48eeb 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -159,7 +159,7 @@ void __printf(1, 0) acpi_os_vprintf(const char *fmt, va_list args) { static char buffer[512]; - vsprintf(buffer, fmt, args); + vsnprintf(buffer, sizeof(buffer), fmt, args); #ifdef ENABLE_DEBUGGER if (acpi_in_debugger) { From 36bca207462d491e5d61cefeb041acf0fce506a7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 31 Aug 2026 22:04:32 +0200 Subject: [PATCH 19/90] ACPI: scan: Drop useless and noisy debug statement The debug message printing statement in handle_to_device() is super-noisy because it triggers whenever the presence of an attached struct acpi_device is checked for the given object in the ACPI namespace and it turns out that there is none. That is a perfectly valid situation and the lack of an attached struct acpi_device is not particularly interesting, so drop that message. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2861358.mvXUDI8C0e@rafael.j.wysocki --- drivers/acpi/scan.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 163a3cccf197..ec3a802e9238 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -637,10 +637,9 @@ static struct acpi_device *handle_to_device(acpi_handle handle, status = acpi_get_data_full(handle, acpi_scan_drop_device, (void **)&adev, callback); - if (ACPI_FAILURE(status) || !adev) { - acpi_handle_debug(handle, "No context!\n"); + if (ACPI_FAILURE(status) || !adev) return NULL; - } + return adev; } From 13e5c292e0f41856ba41bab065a826da31bc1ec4 Mon Sep 17 00:00:00 2001 From: Prabhakar Pujeri Date: Wed, 2 Sep 2026 11:49:12 +0000 Subject: [PATCH 20/90] Documentation: ACPI: EINJ: Update stale driver source reference The EINJ implementation was split when CXL protocol error injection support landed: einj-core.c holds the core logic, einj-cxl.c the CXL-specific part, and apei-internal.h the shared plumbing. The old drivers/acpi/apei/einj.c does not exist anymore, so the documentation points readers at a nonexistent file. Point them at the actual files instead. Signed-off-by: Prabhakar Pujeri Link: https://patch.msgid.link/20260902114912.6690-1-prabhakar.pujeri@dell.com Signed-off-by: Rafael J. Wysocki --- Documentation/firmware-guide/acpi/apei/einj.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Documentation/firmware-guide/acpi/apei/einj.rst b/Documentation/firmware-guide/acpi/apei/einj.rst index 7d8435d35a18..70fa59bd97d8 100644 --- a/Documentation/firmware-guide/acpi/apei/einj.rst +++ b/Documentation/firmware-guide/acpi/apei/einj.rst @@ -180,8 +180,10 @@ function are specified using param1:: | segment | bus | device | function | reserved | +-------------------------------------------------+ -Anyway, you get the idea, if there's doubt just take a look at the code -in drivers/acpi/apei/einj.c. +Anyway, you get the idea, if there's doubt just take a look at the code: +drivers/acpi/apei/einj-core.c holds the core logic, while CXL-specific +handling lives in drivers/acpi/apei/einj-cxl.c and the shared plumbing +in drivers/acpi/apei/apei-internal.h. An ACPI 5.0 BIOS may also allow vendor-specific errors to be injected. In this case a file named vendor will contain identifying information From 7c0940e431c33ca13e9fde57ea0ad56da6529c56 Mon Sep 17 00:00:00 2001 From: Itay Shem-tov Date: Thu, 3 Sep 2026 13:42:44 +0300 Subject: [PATCH 21/90] ACPI: SBS: report relative state of charge as CAPACITY acpi_sbs reads SBS command 0x0e (AbsoluteStateOfCharge) into battery->state_of_charge, which is exported unmodified as POWER_SUPPLY_PROP_CAPACITY. Per the Smart Battery Data Specification 1.1, 0x0e is expressed as a percentage of DesignCapacity and is explicitly permitted to exceed 100%. Documentation/ABI/testing/sysfs-class-power specifies the capacity attribute as "Valid values: 0 - 100 (percent)", so any pack whose FullChargeCapacity exceeds its DesignCapacity - the normal state of a new or recently replaced battery - makes the driver report out of range. The correct source is 0x0d (RelativeStateOfCharge), a percentage of FullChargeCapacity, which the specification bounds to 0..100. This is the same defect that was fixed in the i2c SBS driver by commit b1f092f6480e ("sbs-battery.c: Capacity attr = remaining relative capacity"), whose reasoning applies verbatim here; drivers/acpi/sbs.c was not updated at the time. drivers/power/supply/sbs-battery.c has used 0x0d since, so the two SBS drivers currently disagree about what CAPACITY means. Observed on a MacBookPro11,1 with an SMP/bq20z451 pack (FullChargeCapacity 6775 mAh, DesignCapacity 6400 mAh). Both registers read back-to-back from the pack at a full charge: 0x0d RelativeStateOfCharge = 100 % 0x0e AbsoluteStateOfCharge = 106 % /sys/class/power_supply/BAT0/capacity reported 106 while upower, which computes charge_now/charge_full itself rather than trusting the driver, reported 100. battery->state_of_charge has no other consumer, so no other property changes behaviour. Signed-off-by: Itay Shem-tov Reviewed-by: Sebastian Reichel Link: https://patch.msgid.link/20260903104244.25556-1-itayst@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sbs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index 86b7c7975852..862cb94a8b43 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c @@ -318,7 +318,7 @@ static struct acpi_battery_reader state_readers[] = { {0x0a, SMBUS_READ_WORD, offsetof(struct acpi_battery, rate_now)}, {0x0b, SMBUS_READ_WORD, offsetof(struct acpi_battery, rate_avg)}, {0x0f, SMBUS_READ_WORD, offsetof(struct acpi_battery, capacity_now)}, - {0x0e, SMBUS_READ_WORD, offsetof(struct acpi_battery, state_of_charge)}, + {0x0d, SMBUS_READ_WORD, offsetof(struct acpi_battery, state_of_charge)}, {0x16, SMBUS_READ_WORD, offsetof(struct acpi_battery, state)}, }; From 5636eebb7bb1c6f06f0058d48353686304d4bb47 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 3 Sep 2026 18:58:34 +0200 Subject: [PATCH 22/90] ACPI: PM: Drop parent state update from acpi_device_get_power() The parent state update in acpi_device_get_power(), that occurs when the child device turns out to be in D0 and the parent's power state is still unknown, is highly questionable because it may cause the reference counters of the power resources used by the parent in D0 (if any) to underflow when the parent goes into a low-power state later. Moreover, there is no reason to do it on reads from the real_power_state sysfs attribute of the child. That check had been added by commit 8f7412a792bc ("ACPI / PM: Infer parent power state from child if unknown, v2") before starting to handle the "missing _PSC and no power resources" case directly in acpi_bus_init_power(), as of commit b3785492268f ("ACPI / PM: Do not power manage devices in unknown initial states"). It is not necessary any more and commit b3785492268f should have removed it. Drop it now. Fixes: b3785492268f ("ACPI / PM: Do not power manage devices in unknown initial states") Signed-off-by: Rafael J. Wysocki Tested-by: Peixin Xie Tested-by: Sakari Ailus Link: https://patch.msgid.link/5147279.31r3eYUQgx@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/device_pm.c | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index aa55ecfc2923..4269735aadde 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -75,15 +75,14 @@ static int acpi_dev_pm_explicit_get(struct acpi_device *device, int *state) int acpi_device_get_power(struct acpi_device *device, int *state) { int result = ACPI_STATE_UNKNOWN; - struct acpi_device *parent; int error; if (!device || !state) return -EINVAL; - parent = acpi_dev_parent(device); - if (!device->flags.power_manageable) { + struct acpi_device *parent = acpi_dev_parent(device); + /* TBD: Non-recursive algorithm for walking up hierarchy. */ *state = parent ? parent->power.state : ACPI_STATE_D0; goto out; @@ -119,16 +118,6 @@ int acpi_device_get_power(struct acpi_device *device, int *state) result = psc > ACPI_STATE_D2 ? ACPI_STATE_D3_HOT : psc; } - /* - * If we were unsure about the device parent's power state up to this - * point, the fact that the device is in D0 implies that the parent has - * to be in D0 too, except if ignore_parent is set. - */ - if (!device->power.flags.ignore_parent && parent && - parent->power.state == ACPI_STATE_UNKNOWN && - result == ACPI_STATE_D0) - parent->power.state = ACPI_STATE_D0; - *state = result; out: From 2f4ddf2bd1baf0ee3c8542ec6f382a3585cae607 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 3 Sep 2026 19:05:37 +0200 Subject: [PATCH 23/90] ACPI: scan: Stop calling acpi_bus_init_power() early There is a problem, introduced by commit 9d9bcae47fd5 ("ACPI: delay enumeration of devices with a _DEP pointing to an INT3472 device") inadvertently, that devices with missing dependencies may be put into power state D0 prematurely [1]. Namely, acpi_bus_init_power() called by acpi_bus_get_power_flags() during the early initialization of ACPI device objects, may discover that all of the power resources needed by the given device to be in power state D0 are initially on, so it will reference count those power resources and transition the device into D0. Later, if acpi_bus_attach() running for that device notices that it has missing dependencies, the enumeration of it will be deferred and its power_manageable flag will be cleared, even though it is still in D0 at that point. After the dependencies in question have been met, acpi_bus_attach() runs again for the device and now it calls acpi_bus_init_power() that takes additional references to the power resources used by the device in D0. These additional references prevent the power resources from being turned off when the device goes into D3hot/D3cold. Another problem, related to the previous one, is that ACPI power state initialization may be carried out for devices whose parents are not ready for enumeration which may lead to initialization ordering issues. To address both, stop calling acpi_bus_init_power() from acpi_bus_get_power_flags(), but also take the initialization of PCI devices into account, which needs to be done because they are initialized and bound to their ACPI companions before acpi_bus_attach() is called for the latter. To that end, notice that acpi_power_up_if_adr_present() is used for powering-up PCI devices in D3cold before walking the bus in order to discover them and the initial ACPI power state of those devices needs to be known for this purpose, so add an acpi_bus_init_power() invocation to that function. [The debug statement printed by it duplicates the debug statements printed during the acpi_bus_init_power() execution, so drop it.] Moreover, since the ACPI companions of PCI devices are associated with the corresponding PCI devices found on the bus before acpi_bus_attach() is called for them, it is not necessary or even useful to skip them in acpi_bus_attach() due to an ACPI status mismatch, so avoid doing that and complain if the ACPI status does not match the observed situation. Also use the ACPI power state tracking to decide whether or not the device's power state needs to be initialized in acpi_bus_attach() instead of using the "initialized" flag of the ACPI device object for this purpose, which is fragile and inconvenient, and clear the power_manageable flag on failure in acpi_bus_init_power() (additionally, poison the device ACPI power state as "invalid" if the initialization of it fails). That allows the clearing of the power_manageable flag for devices with unmet dependencies to be dropped. While at it, add a debug message pringing statement to acpi_bus_init_power() to facilitate diagnostics. Fixes: 9d9bcae47fd5 ("ACPI: delay enumeration of devices with a _DEP pointing to an INT3472 device") Link: https://lore.kernel.org/linux-acpi/20260820-acpi-power-resource-ref-fix-v2-1-29818173ea13@linux.spacemit.com/ [1] Signed-off-by: Rafael J. Wysocki Tested-by: Peixin Xie Tested-by: Sakari Ailus Link: https://patch.msgid.link/2295263.irdbgypaU6@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/device_pm.c | 62 ++++++++++++++++++++++++++++++++-------- drivers/acpi/scan.c | 40 +++++++++++++++++--------- 2 files changed, 76 insertions(+), 26 deletions(-) diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index 4269735aadde..76104f715efa 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -23,6 +23,8 @@ #include "fan.h" #include "internal.h" +#define ACPI_D_STATE_INVALID ACPI_D_STATE_COUNT + /** * acpi_power_state_string - String representation of ACPI device power state. * @state: ACPI device power state to return the string representation of. @@ -293,24 +295,28 @@ int acpi_bus_set_power(acpi_handle handle, int state) } EXPORT_SYMBOL(acpi_bus_set_power); -int acpi_bus_init_power(struct acpi_device *device) +static int acpi_device_init_power(struct acpi_device *device) { int state; int result; - if (!device) - return -EINVAL; - - device->power.state = ACPI_STATE_UNKNOWN; - if (!acpi_device_is_present(device)) { - device->flags.initialized = false; - return -ENXIO; - } - result = acpi_device_get_power(device, &state); if (result) return result; + /* + * If the current power state of the device is D0 and it has a parent + * whose power state is not ignored, and the parent's power state + * initialization has failed, the parent's power state can be updated to + * D0 for consistency. + */ + if (!device->power.flags.ignore_parent && state == ACPI_STATE_D0) { + struct acpi_device *parent = acpi_dev_parent(device); + + if (parent && parent->power.state == ACPI_D_STATE_INVALID) + parent->power.state = ACPI_STATE_D0; + } + if (state < ACPI_STATE_D3_COLD && device->power.flags.power_resources) { /* Reference count the power resources. */ result = acpi_power_on_resources(device, state); @@ -340,9 +346,36 @@ int acpi_bus_init_power(struct acpi_device *device) state = ACPI_STATE_D0; } device->power.state = state; + + acpi_handle_debug(device->handle, "Initial power state: %s\n", + acpi_power_state_string(state)); + return 0; } +int acpi_bus_init_power(struct acpi_device *device) +{ + int result; + + if (device->power.state != ACPI_STATE_UNKNOWN) + return 0; + + /* + * The ACPI device power state can be only initialized once. If this + * fails, ACPI power management will not be used for the device going + * forward. + */ + result = acpi_device_init_power(device); + if (result) { + device->flags.power_manageable = 0; + device->power.state = ACPI_D_STATE_INVALID; + acpi_handle_info(device->handle, + "Initial power state undetermined, ACPI PM disabled\n"); + } + + return result; +} + /** * acpi_device_fix_up_power - Force device with missing _PSC into D0. * @device: Device object whose power state is to be fixed up. @@ -464,8 +497,13 @@ static int acpi_power_up_if_adr_present(struct acpi_device *adev, void *not_used if (!(adev->flags.power_manageable && adev->pnp.type.bus_address)) return 0; - acpi_handle_debug(adev->handle, "Power state: %s\n", - acpi_power_state_string(adev->power.state)); + /* + * This is done during the PCI root initialization which occurs before + * acpi_bus_attach() is called for the device, so the ACPI power state + * of the device needs to be initialized here. + */ + if (acpi_bus_init_power(adev)) + return 0; if (adev->power.state == ACPI_STATE_D3_COLD) return acpi_device_set_power(adev, ACPI_STATE_D0); diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index ec3a802e9238..13d1e77c9973 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -1142,8 +1143,7 @@ static void acpi_bus_get_power_flags(struct acpi_device *device) device->power.states[ACPI_STATE_D3_COLD].flags.valid = 1; } - if (acpi_bus_init_power(device)) - device->flags.power_manageable = 0; + device->power.state = ACPI_STATE_UNKNOWN; } static void acpi_bus_get_flags(struct acpi_device *device) @@ -2337,38 +2337,50 @@ static int acpi_scan_attach_handler(struct acpi_device *device) static int acpi_bus_attach(struct acpi_device *device, void *first_pass) { bool skip = !first_pass && device->flags.visited; + struct pci_dev *pci; acpi_handle ejd; int ret; if (skip) goto ok; + device->flags.initialized = true; + if (ACPI_SUCCESS(acpi_bus_get_ejd(device->handle, &ejd))) register_dock_dependent_device(device, ejd); acpi_bus_get_status(device); - /* Skip devices that are not ready for enumeration (e.g. not present) */ - if (!acpi_dev_ready_for_enumeration(device)) { - device->flags.initialized = false; + /* + * If the given ACPI device object has been already associated with a + * PCI device found on the bus, its status is effectively "present and + * enabled", and dependencies are not tracked for PCI devices, so it is + * not necessary or even useful to check the device's readiness in that + * case. + */ + pci = acpi_dev_get_pci_dev(device); + if (pci) { + acpi_handle_debug(device->handle, "PCI companion %s found\n", + pci_name(pci)); + + if (!acpi_device_is_present(device)) + pci_info(pci, FW_BUG "ACPI status differs from reality\n"); + + pci_dev_put(pci); + } else if (!acpi_dev_ready_for_enumeration(device)) { + /* The device is not ready (e.g. not present), so skip it. */ acpi_device_clear_enumerated(device); - device->flags.power_manageable = 0; return 0; } + if (device->handler) goto ok; acpi_ec_register_opregions(device); - if (!device->flags.initialized) { - device->flags.power_manageable = - device->power.states[ACPI_STATE_D0].flags.valid; - if (acpi_bus_init_power(device)) - device->flags.power_manageable = 0; + acpi_bus_init_power(device); - device->flags.initialized = true; - } else if (device->flags.visited) { + if (device->flags.visited) goto ok; - } ret = acpi_scan_attach_handler(device); if (ret < 0) From 811268084ef2f9ead128c3d31b5b646510b7c180 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 3 Sep 2026 19:06:32 +0200 Subject: [PATCH 24/90] ACPI: PM: Move acpi_bus_init_power() declaration to internal header file Since acpi_bus_init_power() is only used internally in the core ACPI device enumeration and power management code, it need not be visible outside, so move its declaration to an internal header file. No functional impact. Signed-off-by: Rafael J. Wysocki Tested-by: Peixin Xie Tested-by: Sakari Ailus Link: https://patch.msgid.link/3079955.e9J7NaK4W3@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/internal.h | 1 + include/acpi/acpi_bus.h | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 40f875b265a9..011da4ed6a80 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -157,6 +157,7 @@ void acpi_turn_off_unused_power_resources(void); Device Power Management -------------------------------------------------------------------------- */ int acpi_device_get_power(struct acpi_device *device, int *state); +int acpi_bus_init_power(struct acpi_device *device); int acpi_wakeup_device_init(void); /* -------------------------------------------------------------------------- diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a10a591c18b2..9da74b3d4774 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -613,7 +613,6 @@ int acpi_bus_get_status(struct acpi_device *device); int acpi_bus_set_power(acpi_handle handle, int state); const char *acpi_power_state_string(int state); int acpi_device_set_power(struct acpi_device *device, int state); -int acpi_bus_init_power(struct acpi_device *device); int acpi_device_fix_up_power(struct acpi_device *device); void acpi_device_fix_up_power_extended(struct acpi_device *adev); void acpi_device_fix_up_power_children(struct acpi_device *adev); From dc2447880b728993a40881bf4ad482dbb93c14f2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 3 Sep 2026 19:09:22 +0200 Subject: [PATCH 25/90] ACPI: scan: Combine two conditionals in acpi_bus_attach() There are two conditionals in acpi_bus_attach() that can be combined, which slightly reduces the overhead and makes the code a bit easier to follow, so do that. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Tested-by: Peixin Xie Tested-by: Sakari Ailus Link: https://patch.msgid.link/8746939.T7Z3S40VBb@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/scan.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 13d1e77c9973..30bfef67cbaa 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -2386,13 +2386,8 @@ static int acpi_bus_attach(struct acpi_device *device, void *first_pass) if (ret < 0) return 0; - if (ret > 0 && !device->flags.enumeration_by_parent) { - acpi_device_set_enumerated(device); - goto ok; - } - - if (device->pnp.type.platform_id || device->pnp.type.backlight || - device->flags.enumeration_by_parent) + if (device->flags.enumeration_by_parent || + (!ret && (device->pnp.type.platform_id || device->pnp.type.backlight))) acpi_default_enumeration(device); else acpi_device_set_enumerated(device); From 5bc1e72f80da4c20ce8b57851be4fad67e855ebd Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 6 Sep 2026 17:51:23 +0200 Subject: [PATCH 26/90] ACPI: glue: Reduce debug noise from acpi_device_notify() When debug is enabled in the ACPI glue code that handles binding devices to ACPI companions, acpi_device_notify() produces a lot of message noise related to devices that have no ACPI companions. Reduce that noise by checking the most obvious case, ACPI device objects, directly and returning from acpi_device_notify() in that case without printing any debug messages. Also avoid printing a debug message when there is no matching ACPI bus type definition for the given device, which is the case for the vast majority of devices. Additionally, make the debug messages that get printed more informative. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/6325978.lOV4Wx5bFT@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/glue.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index b1776809279d..a2a394c226b3 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -354,22 +354,27 @@ void acpi_device_notify(struct device *dev) struct acpi_device *adev; int ret; + /* ACPI devices have no ACPI companions. */ + if (dev->bus == &acpi_bus_type) + return; + ret = acpi_bind_one(dev, NULL); if (ret) { struct acpi_bus_type *type = acpi_get_bus_type(dev); if (!type) - goto err; + return; adev = type->find_companion(dev); if (!adev) { dev_dbg(dev, "ACPI companion not found\n"); - goto err; + return; } ret = acpi_bind_one(dev, adev); - if (ret) - goto err; - + if (ret) { + dev_dbg(dev, "Binding to ACPI companion failed\n"); + return; + } if (type->setup) { type->setup(dev); goto done; @@ -391,11 +396,6 @@ void acpi_device_notify(struct device *dev) done: acpi_handle_debug(ACPI_HANDLE(dev), "Bound to device %s\n", dev_name(dev)); - - return; - -err: - dev_dbg(dev, "No ACPI support\n"); } void acpi_device_notify_remove(struct device *dev) From aef7792cddcac7ab400912c2947a595401d4a7e8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 6 Sep 2026 17:54:10 +0200 Subject: [PATCH 27/90] ACPI: glue: Rework the success message in acpi_device_notify() Using an ACPI handle in the acpi_device_notify() success message is somewhat misleading because the "physical" device is not bound to an ACPI namespace object in that function, but to an ACPI device object attached to it. Moreover, the message is outright confusing for the "physical" devices bound to ACPI devices without ACPI namespace objects (for example, fixed event buttons). Address this by turning that message into a dev_dbg() one printed for the "physical" device and mentioning the name of the ACPI companion. Fixes: ab06eb920401 ("ACPI: scan: Register platform devices for fixed event buttons") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2448150.ElGaqSPkdT@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/glue.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index a2a394c226b3..a47cccc4efd3 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -394,8 +394,7 @@ void acpi_device_notify(struct device *dev) adev->handler->bind(dev); done: - acpi_handle_debug(ACPI_HANDLE(dev), "Bound to device %s\n", - dev_name(dev)); + dev_dbg(dev, "Bound to ACPI device %s\n", acpi_dev_name(adev)); } void acpi_device_notify_remove(struct device *dev) From 78ddbfaa72d68acc98cd5ba12eca071d452702c4 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sun, 6 Sep 2026 17:56:56 +0200 Subject: [PATCH 28/90] ACPI: utils: Adjust message printing macros for ACPI objects The majority of acpi_handle_() macros use neither pr_fmt() nor dev_fmt() and acpi_handle_debug() uses pr_fmt() like dev_fmt(), but only if dynamic debug is compiled in. For more consistency, redefine all of the macros in question to use dev_fmt() because they are more similar to the analogous dev_*() family of macros than to the pr_*() ones and make all of them use it in the same way. While at it, rephrase the comment documenting those macros for more clarity. Fixes: 45fef5b88d1f ("ACPI: add dynamic_debug support") Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2300525.irdbgypaU6@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- include/linux/acpi.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index ddacac812094..f7e3efd328f7 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -1305,34 +1305,34 @@ void __acpi_handle_debug(struct _ddebug *descriptor, acpi_handle handle, const c #endif /* - * acpi_handle_: Print message with ACPI prefix and object path + * acpi_handle_ - Print a message with ACPI prefix and object path * - * These interfaces acquire the global namespace mutex to obtain an object - * path. In interrupt context, it shows the object path as . + * In thread context, the global namespace mutex is acquired to obtain the + * object path. In interrupt context, the object path is shown as . */ #define acpi_handle_emerg(handle, fmt, ...) \ - acpi_handle_printk(KERN_EMERG, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_EMERG, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_alert(handle, fmt, ...) \ - acpi_handle_printk(KERN_ALERT, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_ALERT, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_crit(handle, fmt, ...) \ - acpi_handle_printk(KERN_CRIT, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_CRIT, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_err(handle, fmt, ...) \ - acpi_handle_printk(KERN_ERR, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_ERR, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_warn(handle, fmt, ...) \ - acpi_handle_printk(KERN_WARNING, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_WARNING, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_notice(handle, fmt, ...) \ - acpi_handle_printk(KERN_NOTICE, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_NOTICE, handle, dev_fmt(fmt), ##__VA_ARGS__) #define acpi_handle_info(handle, fmt, ...) \ - acpi_handle_printk(KERN_INFO, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_INFO, handle, dev_fmt(fmt), ##__VA_ARGS__) #if defined(DEBUG) #define acpi_handle_debug(handle, fmt, ...) \ - acpi_handle_printk(KERN_DEBUG, handle, fmt, ##__VA_ARGS__) + acpi_handle_printk(KERN_DEBUG, handle, dev_fmt(fmt), ##__VA_ARGS__) #else #if defined(CONFIG_DYNAMIC_DEBUG) #define acpi_handle_debug(handle, fmt, ...) \ _dynamic_func_call(fmt, __acpi_handle_debug, \ - handle, pr_fmt(fmt), ##__VA_ARGS__) + handle, dev_fmt(fmt), ##__VA_ARGS__) #else #define acpi_handle_debug(handle, fmt, ...) \ ({ \ From 3107a9a3241d6b585cbc08c7035b03fa44e9fb94 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Tue, 8 Sep 2026 14:34:58 -0500 Subject: [PATCH 29/90] ACPI: utils: Ignore leading root scope prefix in string _UID match Firmware may express an ACPI namespace path used as a _UID with or without the leading root scope character ('\'). For example, an AMD IVRS IVHD ACPI HID device entry may carry a character UID of "\_SB.MHSP" while the corresponding device's _UID evaluates to "_SB.MHSP" (or vice versa). This semantic difference is due to how Windows PnP enumerates and uses devices. acpi_str_uid_match() compared the two strings verbatim, so such entries failed to match on the UID even though they refer to the same object. In the AMD IOMMU case (get_acpihid_device_id()) this caused the exact HID+UID match to be missed and the code to fall through to the HID-only path, spuriously raising a FW_BUG. Skip a single leading '\' on either string before comparing so that paths that differ only by the root scope prefix are treated as a match. The integer _UID path is unaffected. [ rjw: It is sad that making this change needed to be considered at all. That being said, it is also rather unlikely that someone will use a leading backslash as the only difference to distinguish one UID string from antother, so this change is unlikely to break anything. ] Signed-off-by: Mario Limonciello [ rjw: Added separator line to a kerneldoc comment ] [ rjw: Droppend Link: tag with a limited access target ] Signed-off-by: Rafael J. Wysocki --- include/acpi/acpi_bus.h | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a10a591c18b2..22be27e02682 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -830,7 +830,15 @@ static inline bool acpi_str_uid_match(struct acpi_device *adev, const char *uid2 { const char *uid1 = acpi_device_uid(adev); - return uid1 && uid2 && !strcmp(uid1, uid2); + if (!uid1 || !uid2) + return false; + + if (*uid1 == '\\' && uid1[1]) + uid1++; + if (*uid2 == '\\' && uid2[1]) + uid2++; + + return !strcmp(uid1, uid2); } static inline bool acpi_int_uid_match(struct acpi_device *adev, u64 uid2) @@ -857,6 +865,10 @@ static inline bool acpi_int_uid_match(struct acpi_device *adev, u64 uid2) * * Matches UID in @adev with given @uid2. * + * If both the UID in @adev and @uid2 are strings, they are compared + * after optionally skipping a leading backslash ('\') if the given + * string contains additional characters. + * * Returns: %true if matches, %false otherwise. */ #define acpi_dev_uid_match(adev, uid2) \ From f8113f311cf8be566d809598e967bf5ceb35b567 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 9 Sep 2026 18:39:18 +0200 Subject: [PATCH 30/90] ACPI: bus: Reduce runtime memory footprint of struct acpi_device The ACPI handle is heavily used by the ACPI code. Making it as the first member in the struct acpi_device reduces code size. For instance, the drivers/acpi/scan.o (with `make allyesconfig`) gives this statistics: add/remove: 0/0 grow/shrink: 3/8 up/down: 50/-169 (-119) Function old new delta acpi_device_add 2294 2319 +25 acpi_add_single_object 6523 6544 +21 acpi_dev_get_next_consumer_dev 602 606 +4 acpi_tie_acpi_dev 169 165 -4 acpi_scan_drop_device 319 315 -4 acpi_dev_clear_dependencies 1151 1147 -4 acpi_bus_attach 2898 2894 -4 acpi_dma_get_range 1420 1415 -5 acpi_init_device_object 5497 5485 -12 acpi_bus_check_add 1940 1923 -17 acpi_device_hotplug 3407 3288 -119 Total: Before=77509, After=77390, chg -0.15% Signed-off-by: Andy Shevchenko [ rjw: Changelog tweaks ] Link: https://patch.msgid.link/20260909163918.368017-1-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- include/acpi/acpi_bus.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a10a591c18b2..64ae66ca456e 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -435,9 +435,9 @@ struct acpi_device_software_nodes { /* Device */ struct acpi_device { + acpi_handle handle; /* no handle for fixed hardware */ u32 pld_crc; int device_type; - acpi_handle handle; /* no handle for fixed hardware */ struct fwnode_handle fwnode; struct list_head wakeup_list; struct list_head del_list; From c94619c8a5b5d3b980433d247ac87a2d1c827cb6 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 10 Sep 2026 19:56:04 +0200 Subject: [PATCH 31/90] ACPI: glue: Carry out companion lookup under bus_type_sem When acpi_device_notify() looks up an ACPI companion for the given device, it invokes callbacks from struct acpi_bus_type() without synchronization which may lead to a use-after-free if the driver module containing those callbacks is unloaded at the same time. Address this by holding bus_type_sem throughout the entire ACPI companion lookup and the execution of the .setup() callback in struct acpi_bus_type (if present) instead of dropping the semaphore (prematurely) after finding a matching struct acpi_bus_type. For this purpose, rename acpi_get_bus_type() to acpi_companion_lookup(), make it return a struct acpi_device pointer, and move the relevant code from acpi_device_notify() to it. Also notice that the only case in which the .bind() callback from an ACPI scan handler may need to be invoked is when the given device is a platform one, so adjust acpi_device_notify() accordingly and drop the "done" label that is not used any more from it. Fixes: 2ef5236660b6 ("ACPI: glue: Look for ACPI bus type only if ACPI companion is not known") Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3089807.e9J7NaK4W3@rafael.j.wysocki --- drivers/acpi/glue.c | 61 +++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index a47cccc4efd3..40e6513a9942 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -59,19 +59,34 @@ int unregister_acpi_bus_type(struct acpi_bus_type *type) } EXPORT_SYMBOL_GPL(unregister_acpi_bus_type); -static struct acpi_bus_type *acpi_get_bus_type(struct device *dev) +static struct acpi_device *acpi_companion_lookup(struct device *dev) { - struct acpi_bus_type *tmp, *ret = NULL; + struct acpi_bus_type *type; - down_read(&bus_type_sem); - list_for_each_entry(tmp, &bus_type_list, list) { - if (tmp->match(dev)) { - ret = tmp; - break; + guard(rwsem_read)(&bus_type_sem); + + list_for_each_entry(type, &bus_type_list, list) { + struct acpi_device *adev; + + if (!type->match(dev)) + continue; + + adev = type->find_companion(dev); + if (!adev) { + dev_dbg(dev, "ACPI companion not found\n"); + return NULL; } + if (acpi_bind_one(dev, adev)) { + dev_dbg(dev, "Binding to ACPI companion failed\n"); + return NULL; + } + if (type->setup) + type->setup(dev); + + return adev; } - up_read(&bus_type_sem); - return ret; + + return NULL; } #define FIND_CHILD_MIN_SCORE 1 @@ -360,40 +375,22 @@ void acpi_device_notify(struct device *dev) ret = acpi_bind_one(dev, NULL); if (ret) { - struct acpi_bus_type *type = acpi_get_bus_type(dev); - - if (!type) + adev = acpi_companion_lookup(dev); + if (!adev) return; - - adev = type->find_companion(dev); - if (!adev) { - dev_dbg(dev, "ACPI companion not found\n"); - return; - } - ret = acpi_bind_one(dev, adev); - if (ret) { - dev_dbg(dev, "Binding to ACPI companion failed\n"); - return; - } - if (type->setup) { - type->setup(dev); - goto done; - } } else { adev = ACPI_COMPANION(dev); if (dev_is_pci(dev)) { pci_acpi_setup(dev, adev); - goto done; } else if (dev_is_platform(dev)) { acpi_configure_pmsi_domain(dev); + + if (adev->handler && adev->handler->bind) + adev->handler->bind(dev); } } - if (adev->handler && adev->handler->bind) - adev->handler->bind(dev); - -done: dev_dbg(dev, "Bound to ACPI device %s\n", acpi_dev_name(adev)); } From fcfb2b562115632e0a60a0928dca495f98eb335c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 10 Sep 2026 19:56:10 +0200 Subject: [PATCH 32/90] ACPI: glue: Rearrange acpi_bind_one() to avoid breakage Rearange the code in acpi_bind_one() to avoid situations in which the existing ACPI companion of the given device would be replaced with NULL due to a memory allocation error or because somebody tries to bind a physical device with an ACPI companion to a different ACPI device erroneously. Fixes: 7b1998116bbb ("ACPI / driver core: Store an ACPI device pointer in struct acpi_dev_node") Signed-off-by: Rafael J. Wysocki [ rjw: Merged two conditionals to avoid redundant check ] Link: https://patch.msgid.link/3465482.44csPzL39Z@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/glue.c | 58 +++++++++++++++++++-------------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index 40e6513a9942..1e9e7150b95a 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -243,31 +243,25 @@ static void acpi_physnode_link_name(char *buf, unsigned int node_id) int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) { struct acpi_device_physical_node *physical_node, *pn; + struct acpi_device *comp_dev = ACPI_COMPANION(dev); char physical_node_name[PHYSICAL_NODE_NAME_SIZE]; struct list_head *physnode_list; unsigned int node_id; int retval = -EINVAL; - if (has_acpi_companion(dev)) { - if (acpi_dev) { - dev_warn(dev, "ACPI companion already set\n"); + if (!acpi_dev) { + if (!comp_dev) return -EINVAL; - } else { - acpi_dev = ACPI_COMPANION(dev); - } - } - if (!acpi_dev) - return -EINVAL; - acpi_dev_get(acpi_dev); - get_device(dev); - physical_node = kzalloc_obj(*physical_node); - if (!physical_node) { - retval = -ENOMEM; - goto err; + /* If the companion has been set upfront, pick it up. */ + acpi_dev = comp_dev; + } else if (comp_dev && acpi_dev != comp_dev) { + dev_warn(dev, "ACPI companion already set to %s which is not %s\n", + acpi_dev_name(comp_dev), acpi_dev_name(acpi_dev)); + return -EEXIST; } - mutex_lock(&acpi_dev->physical_node_lock); + guard(mutex)(&acpi_dev->physical_node_lock); /* * Keep the list sorted by node_id so that the IDs of removed nodes can @@ -278,15 +272,12 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) list_for_each_entry(pn, &acpi_dev->physical_node_list, node) { /* Sanity check. */ if (pn->dev == dev) { - mutex_unlock(&acpi_dev->physical_node_lock); - - dev_warn(dev, "Already associated with ACPI node\n"); - kfree(physical_node); - if (ACPI_COMPANION(dev) != acpi_dev) - goto err; - - put_device(dev); - acpi_dev_put(acpi_dev); + if (!comp_dev) { + /* Really unexpected. */ + ACPI_COMPANION_SET(dev, acpi_dev); + dev_warn(&acpi_dev->dev, + "Physical device list corruption fixed up\n"); + } return 0; } if (pn->node_id == node_id) { @@ -295,12 +286,19 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) } } + physical_node = kzalloc_obj(*physical_node); + if (!physical_node) + return -ENOMEM; + + acpi_dev_get(acpi_dev); + get_device(dev); + physical_node->node_id = node_id; physical_node->dev = dev; list_add(&physical_node->node, physnode_list); acpi_dev->physical_node_count++; - if (!has_acpi_companion(dev)) + if (!comp_dev) ACPI_COMPANION_SET(dev, acpi_dev); acpi_physnode_link_name(physical_node_name, node_id); @@ -316,18 +314,10 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) dev_err(dev, "Failed to create link firmware_node (%d)\n", retval); - mutex_unlock(&acpi_dev->physical_node_lock); - if (acpi_dev->wakeup.flags.valid) device_set_wakeup_capable(dev, true); return 0; - - err: - ACPI_COMPANION_SET(dev, NULL); - put_device(dev); - acpi_dev_put(acpi_dev); - return retval; } EXPORT_SYMBOL_GPL(acpi_bind_one); From 693546852656075e0247ac637b05625ead134eff Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 10 Sep 2026 19:56:16 +0200 Subject: [PATCH 33/90] ACPI: glue: Fix up and adjust acpi_unbind_one() Since none of the acpi_unbind_one() callers check its return value and it always returns 0 anyway, make it void. Also notice that unlocking physical_node_lock for the given ACPI device should be carried out before dropping the reference to it in case that reference is the last one (highly unlikely), so rearrange the code to make that happen. Fixes: 3e3327837c18 ("ACPI: Use list_for_each_entry() in acpi_unbind_one()") Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/10940500.nUPlyArG6x@rafael.j.wysocki --- drivers/acpi/glue.c | 10 ++++++---- include/acpi/acpi_bus.h | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index 1e9e7150b95a..e55daa4221da 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -321,13 +321,13 @@ int acpi_bind_one(struct device *dev, struct acpi_device *acpi_dev) } EXPORT_SYMBOL_GPL(acpi_bind_one); -int acpi_unbind_one(struct device *dev) +void acpi_unbind_one(struct device *dev) { struct acpi_device *acpi_dev = ACPI_COMPANION(dev); struct acpi_device_physical_node *entry; if (!acpi_dev) - return 0; + return; mutex_lock(&acpi_dev->physical_node_lock); @@ -342,15 +342,17 @@ int acpi_unbind_one(struct device *dev) sysfs_remove_link(&acpi_dev->dev.kobj, physnode_name); sysfs_remove_link(&dev->kobj, "firmware_node"); ACPI_COMPANION_SET(dev, NULL); + + mutex_unlock(&acpi_dev->physical_node_lock); + /* Drop references taken by acpi_bind_one(). */ put_device(dev); acpi_dev_put(acpi_dev); kfree(entry); - break; + return; } mutex_unlock(&acpi_dev->physical_node_lock); - return 0; } EXPORT_SYMBOL_GPL(acpi_unbind_one); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a10a591c18b2..93b00635ba3d 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -665,7 +665,7 @@ struct acpi_bus_type { int register_acpi_bus_type(struct acpi_bus_type *); int unregister_acpi_bus_type(struct acpi_bus_type *); int acpi_bind_one(struct device *dev, struct acpi_device *adev); -int acpi_unbind_one(struct device *dev); +void acpi_unbind_one(struct device *dev); enum acpi_bridge_type { ACPI_BRIDGE_TYPE_PCIE = 1, From ea9102cae4a634a752749dda4914cb235910134e Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 10 Sep 2026 19:56:22 +0200 Subject: [PATCH 34/90] ACPI: glue: Skip devices with no type in acpi_device_notify() Since all of the users of struct acpi_bus_type use the device type for matching, devices without a type will not be matched by any of them, so they can be skipped early in acpi_device_notify(). Update the code accordingly. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2078562.PYKUYFuaPT@rafael.j.wysocki --- drivers/acpi/glue.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/acpi/glue.c b/drivers/acpi/glue.c index e55daa4221da..0959f58cb77c 100644 --- a/drivers/acpi/glue.c +++ b/drivers/acpi/glue.c @@ -63,6 +63,9 @@ static struct acpi_device *acpi_companion_lookup(struct device *dev) { struct acpi_bus_type *type; + if (!dev->type) + return NULL; + guard(rwsem_read)(&bus_type_sem); list_for_each_entry(type, &bus_type_list, list) { From 2cf65d00347005eccf9a24801783377d7f920021 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:00:51 +0200 Subject: [PATCH 35/90] ACPI: fan: Fix memory leak due to leftover devm_kcalloc() argument An ACPI device object's dev field in passed as the first argument to devm_kcalloc() in acpi_fan_get_fps() which is incorrect and leads to a memory leak on driver probe errors and removal because the driver is not bound to that ACPI device. Address this by replacing that pointer with a pointer to the device the driver is actually bound to. While at it, drop a redundant error message after a memory allocation failure (that also gets printed relative to the ACPI device). Fixes: d91a1d129b63 ("ACPI: fan: Use platform device for devres-related actions") Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/14089663.uLZWGnKmhe@rafael.j.wysocki --- drivers/acpi/fan_core.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index 624d0736b581..a31d7beca5d0 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -284,7 +284,7 @@ static int acpi_fan_speed_cmp(const void *a, const void *b) return fps1->speed - fps2->speed; } -static int acpi_fan_get_fps(struct acpi_device *device) +static int acpi_fan_get_fps(struct device *dev, struct acpi_device *device) { struct acpi_fan *fan = acpi_driver_data(device); struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -304,11 +304,8 @@ static int acpi_fan_get_fps(struct acpi_device *device) } fan->fps_count = obj->package.count - 1; /* minus revision field */ - fan->fps = devm_kcalloc(&device->dev, - fan->fps_count, sizeof(struct acpi_fan_fps), - GFP_KERNEL); + fan->fps = devm_kcalloc(dev, fan->fps_count, sizeof(*fan->fps), GFP_KERNEL); if (!fan->fps) { - dev_err(&device->dev, "Not enough memory\n"); status = -ENOMEM; goto err; } @@ -522,7 +519,7 @@ static int acpi_fan_probe(struct platform_device *pdev) if (result) return result; - result = acpi_fan_get_fps(device); + result = acpi_fan_get_fps(&pdev->dev, device); if (result) return result; } From 46577d36611953d1b99e86493289cfc824116ad2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:01:30 +0200 Subject: [PATCH 36/90] ACPI: video: Fix backlight unregistration ordering In acpi_video_dev_unregister_backlight(), the sysfs interface of the cooling class device may access the brightness object under the backlight device's ACPI companion, so that object cannot be freed before unregistering the cooling class device. Adjust the code to take that into account. Fixes: 67b662e189f4 ("ACPI / video: seperate backlight control and event interface") Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf [ rjw: Fixed the Fixes: tag ] Link: https://patch.msgid.link/3429468.aeNJFYEL58@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_video.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index 4d6fd9f6e9ad..6cfe390411c1 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -1825,6 +1825,12 @@ static int acpi_video_bus_register_backlight(struct acpi_video_bus *video) static void acpi_video_dev_unregister_backlight(struct acpi_video_device *device) { + if (device->cooling_dev) { + sysfs_remove_link(&device->dev->dev.kobj, "thermal_cooling"); + sysfs_remove_link(&device->cooling_dev->device.kobj, "device"); + thermal_cooling_device_unregister(device->cooling_dev); + device->cooling_dev = NULL; + } if (device->backlight) { backlight_device_unregister(device->backlight); device->backlight = NULL; @@ -1834,12 +1840,6 @@ static void acpi_video_dev_unregister_backlight(struct acpi_video_device *device kfree(device->brightness); device->brightness = NULL; } - if (device->cooling_dev) { - sysfs_remove_link(&device->dev->dev.kobj, "thermal_cooling"); - sysfs_remove_link(&device->cooling_dev->device.kobj, "device"); - thermal_cooling_device_unregister(device->cooling_dev); - device->cooling_dev = NULL; - } } static int acpi_video_bus_unregister_backlight(struct acpi_video_bus *video) From 7e2c70f8a104e777df799130b4a39c35f51ba19c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:02:09 +0200 Subject: [PATCH 37/90] thermal: core: Introduce thermal_cooling_device_create() Currently, thermal cooling devices have no parents, but it would be generally useful to be able to create them under specific parents in the device hierarchy (for instance, it may help to identify the device representing the actual cooling hardware). To make that possible, add thermal_cooling_device_create() that will work like thermal_cooling_device_register() except that it will take an additional parent argument (which may be NULL). Redefine thermal_cooling_device_register() as a static inline wrapper around thermal_cooling_device_create(). Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/8729484.T7Z3S40VBb@rafael.j.wysocki --- drivers/thermal/thermal_core.c | 28 ++++++++++++++++------------ drivers/thermal/thermal_core.h | 3 ++- drivers/thermal/thermal_of.c | 2 +- include/linux/thermal.h | 19 ++++++++++++++----- 4 files changed, 33 insertions(+), 19 deletions(-) diff --git a/drivers/thermal/thermal_core.c b/drivers/thermal/thermal_core.c index 82e2f0d8a26d..ac928c199829 100644 --- a/drivers/thermal/thermal_core.c +++ b/drivers/thermal/thermal_core.c @@ -1005,7 +1005,8 @@ thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_devi return ERR_PTR(ret); } -int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata) +int thermal_cooling_device_add(struct thermal_cooling_device *cdev, + struct device *parent, void *devdata) { unsigned long current_state; int ret; @@ -1013,6 +1014,7 @@ int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdat mutex_init(&cdev->lock); INIT_LIST_HEAD(&cdev->thermal_instances); cdev->updated = false; + cdev->device.parent = parent; cdev->device.class = &thermal_class; cdev->device.release = thermal_cdev_release; device_initialize(&cdev->device); @@ -1062,21 +1064,23 @@ int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdat } /** - * thermal_cooling_device_register() - register a new thermal cooling device + * thermal_cooling_device_create() - register a new thermal cooling device + * @parent: parent device (optional). * @type: the thermal cooling device type. * @devdata: device private data. * @ops: standard thermal cooling devices callbacks. * - * This interface function adds a new thermal cooling device (fan/processor/...) - * to /sys/class/thermal/ folder as cooling_device[0-*]. It tries to bind itself - * to all the thermal zone devices registered at the same time. + * Allocate and register a new thermal cooling device under the given parent (if + * not NULL) and with the given type, device data, and operations. During the + * registration, it will be matched against all of the registered thermal zones + * and it will be bound to the matching ones. * - * Return: a pointer to the created struct thermal_cooling_device or an - * ERR_PTR. Caller must check return value with IS_ERR*() helpers. + * Return: A pointer to the created struct thermal_cooling_device or an ERR_PTR. + * Callers must use IS_ERR*() helpers to check the return value. */ -struct thermal_cooling_device * -thermal_cooling_device_register(const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +struct thermal_cooling_device *thermal_cooling_device_create( + struct device *parent, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) { struct thermal_cooling_device *cdev; int ret; @@ -1085,13 +1089,13 @@ thermal_cooling_device_register(const char *type, void *devdata, if (IS_ERR(cdev)) return cdev; - ret = thermal_cooling_device_add(cdev, devdata); + ret = thermal_cooling_device_add(cdev, parent, devdata); if (ret) return ERR_PTR(ret); return cdev; } -EXPORT_SYMBOL_GPL(thermal_cooling_device_register); +EXPORT_SYMBOL_GPL(thermal_cooling_device_create); static void thermal_cooling_device_release(void *data) { diff --git a/drivers/thermal/thermal_core.h b/drivers/thermal/thermal_core.h index e98b0aa5aacc..7ef7c6cab437 100644 --- a/drivers/thermal/thermal_core.h +++ b/drivers/thermal/thermal_core.h @@ -270,7 +270,8 @@ void thermal_governor_update_tz(struct thermal_zone_device *tz, struct thermal_cooling_device * thermal_cooling_device_alloc(const char *type, const struct thermal_cooling_device_ops *ops); -int thermal_cooling_device_add(struct thermal_cooling_device *cdev, void *devdata); +int thermal_cooling_device_add(struct thermal_cooling_device *cdev, + struct device *parent, void *devdata); /* Helpers */ #define for_each_trip_desc(__tz, __td) \ diff --git a/drivers/thermal/thermal_of.c b/drivers/thermal/thermal_of.c index 0217a49b08ae..14dfac9359b8 100644 --- a/drivers/thermal/thermal_of.c +++ b/drivers/thermal/thermal_of.c @@ -561,7 +561,7 @@ thermal_of_cooling_device_register(struct device_node *np, u32 cdev_id, cdev->np = np; cdev->cdev_id = cdev_id; - ret = thermal_cooling_device_add(cdev, devdata); + ret = thermal_cooling_device_add(cdev, NULL, devdata); if (ret) return ERR_PTR(ret); diff --git a/include/linux/thermal.h b/include/linux/thermal.h index 083b4f533933..306ad17aed89 100644 --- a/include/linux/thermal.h +++ b/include/linux/thermal.h @@ -293,8 +293,9 @@ struct device *thermal_zone_device(struct thermal_zone_device *tzd); void thermal_zone_device_update(struct thermal_zone_device *, enum thermal_notify_event); -struct thermal_cooling_device *thermal_cooling_device_register(const char *, - void *, const struct thermal_cooling_device_ops *); +struct thermal_cooling_device *thermal_cooling_device_create( + struct device *parent, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops); struct thermal_cooling_device * devm_thermal_cooling_device_register(struct device *dev, const char *type, void *devdata, @@ -340,9 +341,9 @@ static inline void thermal_zone_device_update(struct thermal_zone_device *tz, enum thermal_notify_event event) { } -static inline struct thermal_cooling_device * -thermal_cooling_device_register(const char *type, void *devdata, - const struct thermal_cooling_device_ops *ops) +static inline struct thermal_cooling_device *thermal_cooling_device_create( + struct device *parent, const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) { return ERR_PTR(-ENODEV); } static inline struct thermal_cooling_device * @@ -391,4 +392,12 @@ static inline void thermal_pm_prepare(void) {} static inline void thermal_pm_complete(void) {} #endif /* CONFIG_THERMAL */ +static inline struct thermal_cooling_device *thermal_cooling_device_register( + const char *type, void *devdata, + const struct thermal_cooling_device_ops *ops) +{ + return thermal_cooling_device_create(NULL, type, devdata, ops); +} + + #endif /* __THERMAL_H__ */ From bb03cfceb1f11047a0155fa7871b729a345c8de2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:02:53 +0200 Subject: [PATCH 38/90] ACPI: processor: thermal: Use thermal_cooling_device_create() Instead of using thermal_cooling_device_register() for registering a cooling device in the ACPI processor driver, make it use thermal_cooling_device_create() and pass a pointer to the processor device representing the given CPU to that function as the cooling device's parent. That will cause the cooling device's sysfs directory to be created under the parent's sysfs directory (among other things). Since creating a class device under a parent causes a "device" symbolic link from the sysfs directory of the class device to the sysfs directory of the parent to appear automatically, remove the code creating the "device" symbolic link from the sysfs directory of the cooling device in question to the sysfs directory of the parent's companion ACPI device. That ACPI device is reachable through the "firmware_node" symbolic link in the parent's sysfs directory regardless. Moreover, since the cooling device is now located in sysfs under its parent and it can be easily identified as a cooling device, there is no need to create a "thermal_cooling" symbolic link from its parent's ACPI companion to it. Accordingly, also remove the code creating that symbolic link. While at it, check for error pointer values in addition to checking for NULL in acpi_processor_thermal_exit() to avoid dereferencing them mistakenly. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf [ rjw: Corrected typo in the changelog ] Link: https://patch.msgid.link/2362345.iZASKD2KPV@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_driver.c | 4 +-- drivers/acpi/processor_thermal.c | 47 +++++--------------------------- include/acpi/processor.h | 3 +- 3 files changed, 10 insertions(+), 44 deletions(-) diff --git a/drivers/acpi/processor_driver.c b/drivers/acpi/processor_driver.c index cdc2ae1632b2..90c14480393d 100644 --- a/drivers/acpi/processor_driver.c +++ b/drivers/acpi/processor_driver.c @@ -179,7 +179,7 @@ static int __acpi_processor_start(struct acpi_device *device) return 0; err_thermal_exit: - acpi_processor_thermal_exit(pr, device); + acpi_processor_thermal_exit(pr); err_power_exit: acpi_processor_power_exit(pr); return result; @@ -203,7 +203,7 @@ static int acpi_processor_stop(struct device *dev) acpi_cppc_processor_exit(pr); - acpi_processor_thermal_exit(pr, device); + acpi_processor_thermal_exit(pr); return 0; } diff --git a/drivers/acpi/processor_thermal.c b/drivers/acpi/processor_thermal.c index c7b1dc5687ec..6cd47551844a 100644 --- a/drivers/acpi/processor_thermal.c +++ b/drivers/acpi/processor_thermal.c @@ -311,52 +311,19 @@ const struct thermal_cooling_device_ops processor_cooling_ops = { int acpi_processor_thermal_init(struct acpi_processor *pr, struct acpi_device *device) { - int result = 0; + pr->cdev = thermal_cooling_device_create(pr->dev, "Processor", device, + &processor_cooling_ops); + if (IS_ERR(pr->cdev)) + return PTR_ERR(pr->cdev); - pr->cdev = thermal_cooling_device_register("Processor", device, - &processor_cooling_ops); - if (IS_ERR(pr->cdev)) { - result = PTR_ERR(pr->cdev); - return result; - } - - dev_dbg(&device->dev, "registered as cooling_device%d\n", - pr->cdev->id); - - result = sysfs_create_link(&device->dev.kobj, - &pr->cdev->device.kobj, - "thermal_cooling"); - if (result) { - dev_err(&device->dev, - "Failed to create sysfs link 'thermal_cooling'\n"); - goto err_thermal_unregister; - } - - result = sysfs_create_link(&pr->cdev->device.kobj, - &device->dev.kobj, - "device"); - if (result) { - dev_err(&pr->cdev->device, - "Failed to create sysfs link 'device'\n"); - goto err_remove_sysfs_thermal; - } + dev_dbg(pr->dev, "registered as cooling_device%d\n", pr->cdev->id); return 0; - -err_remove_sysfs_thermal: - sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); -err_thermal_unregister: - thermal_cooling_device_unregister(pr->cdev); - - return result; } -void acpi_processor_thermal_exit(struct acpi_processor *pr, - struct acpi_device *device) +void acpi_processor_thermal_exit(struct acpi_processor *pr) { - if (pr->cdev) { - sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); - sysfs_remove_link(&pr->cdev->device.kobj, "device"); + if (!IS_ERR_OR_NULL(pr->cdev)) { thermal_cooling_device_unregister(pr->cdev); pr->cdev = NULL; } diff --git a/include/acpi/processor.h b/include/acpi/processor.h index 554be224ce76..656aaf74fb18 100644 --- a/include/acpi/processor.h +++ b/include/acpi/processor.h @@ -429,8 +429,7 @@ int acpi_processor_ffh_lpi_enter(struct acpi_lpi_state *lpi); /* in processor_thermal.c */ int acpi_processor_thermal_init(struct acpi_processor *pr, struct acpi_device *device); -void acpi_processor_thermal_exit(struct acpi_processor *pr, - struct acpi_device *device); +void acpi_processor_thermal_exit(struct acpi_processor *pr); extern const struct thermal_cooling_device_ops processor_cooling_ops; #ifdef CONFIG_CPU_FREQ void acpi_thermal_cpufreq_init(struct cpufreq_policy *policy); From d7ab07938c46f877448fa61d38ec87ba8643dff1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:03:34 +0200 Subject: [PATCH 39/90] ACPI: video: Use thermal_cooling_device_create() Instead of using thermal_cooling_device_register() for registering a backlight cooling device in the ACPI video bus driver, make it use thermal_cooling_device_create() and pass a pointer to the LCD device to that function as the cooling class device's parent. That will cause the cooling device's sysfs directory to be created under the parent's sysfs directory (among other things). Since creating a class device under a parent causes a "device" symbolic link from the sysfs directory of the class device to the sysfs directory of the parent to appear automatically, remove the code creating the "device" symbolic link from the sysfs directory of the cooling device in question to the sysfs directory of the parent's ACPI companion device. That companion device is reachable through the "firmware_node" symbolic link in the parent's sysfs directory regardless. Moreover, since the cooling class device is now located in sysfs under its parent and it can be easily identified as a cooling device, there is no need to create a "thermal_cooling" symbolic link from its parent's ACPI companion to it. Accordingly, also remove the code creating that symbolic link. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/23237491.EfDdHjke4D@rafael.j.wysocki --- drivers/acpi/acpi_video.c | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index 6cfe390411c1..2a2822516665 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -1700,9 +1700,9 @@ static int acpi_video_resume(struct notifier_block *nb, static void acpi_video_dev_register_backlight(struct acpi_video_device *device) { + struct device *phys_dev, *parent = NULL; struct backlight_properties props; struct pci_dev *pdev; - struct device *parent = NULL; int result; static int count; char *name; @@ -1729,11 +1729,10 @@ static void acpi_video_dev_register_backlight(struct acpi_video_device *device) device, &acpi_backlight_ops, &props); - put_device(parent); kfree(name); if (IS_ERR(device->backlight)) { device->backlight = NULL; - return; + goto put_parent; } /* @@ -1743,8 +1742,12 @@ static void acpi_video_dev_register_backlight(struct acpi_video_device *device) device->backlight->props.brightness = acpi_video_get_brightness(device->backlight); - device->cooling_dev = thermal_cooling_device_register("LCD", device, - &video_cooling_ops); + phys_dev = acpi_bus_get_primary_device(device->dev); + if (!phys_dev) + phys_dev = get_device(parent); + + device->cooling_dev = thermal_cooling_device_create(phys_dev, "LCD", device, + &video_cooling_ops); if (IS_ERR(device->cooling_dev)) { /* * Set cooling_dev to NULL so we don't crash trying to free it. @@ -1753,21 +1756,17 @@ static void acpi_video_dev_register_backlight(struct acpi_video_device *device) * -- dtor */ device->cooling_dev = NULL; - return; + goto put_phys_dev; } - dev_info(&device->dev->dev, "registered as cooling_device%d\n", - device->cooling_dev->id); - result = sysfs_create_link(&device->dev->dev.kobj, - &device->cooling_dev->device.kobj, - "thermal_cooling"); - if (result) - pr_info("sysfs link creation failed\n"); + dev_info(&device->cooling_dev->device, "Using ACPI device %s\n", + acpi_dev_name(device->dev)); - result = sysfs_create_link(&device->cooling_dev->device.kobj, - &device->dev->dev.kobj, "device"); - if (result) - pr_info("Reverse sysfs link creation failed\n"); +put_phys_dev: + put_device(phys_dev); + +put_parent: + put_device(parent); } static void acpi_video_run_bcl_for_osi(struct acpi_video_bus *video) @@ -1826,8 +1825,6 @@ static int acpi_video_bus_register_backlight(struct acpi_video_bus *video) static void acpi_video_dev_unregister_backlight(struct acpi_video_device *device) { if (device->cooling_dev) { - sysfs_remove_link(&device->dev->dev.kobj, "thermal_cooling"); - sysfs_remove_link(&device->cooling_dev->device.kobj, "device"); thermal_cooling_device_unregister(device->cooling_dev); device->cooling_dev = NULL; } From 8a2622ae68984913ba0a778e64722f192721109b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:04:11 +0200 Subject: [PATCH 40/90] ACPI: fan: Use thermal_cooling_device_create() Instead of using thermal_cooling_device_register() for registering a cooling device in the ACPI fan driver, make it use thermal_cooling_device_create() and pass a pointer to the platform device representing the given fan to that function as the cooling device's parent. That will cause the cooling device's sysfs directory to be created under the parent's sysfs directory (among other things). Since creating a class device under a parent causes a "device" symbolic link from the sysfs directory of the class device to the sysfs directory of the parent to appear automatically, remove the code creating that symbolic link manually. Moreover, since the cooling device is now located in sysfs under its parent and it can be easily identified as a cooling device, there is no need to create a "thermal_cooling" symbolic link from its parent to it. Accordingly, also remove the code creating that symbolic link. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/3969045.kQq0lBPeGt@rafael.j.wysocki --- drivers/acpi/fan_core.c | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index a31d7beca5d0..29ca97503deb 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -564,8 +564,7 @@ static int acpi_fan_probe(struct platform_device *pdev) else name = acpi_device_bid(device); - cdev = thermal_cooling_device_register(name, device, - &fan_cooling_ops); + cdev = thermal_cooling_device_create(&pdev->dev, name, device, &fan_cooling_ops); if (IS_ERR(cdev)) { result = PTR_ERR(cdev); goto err_end; @@ -574,28 +573,9 @@ static int acpi_fan_probe(struct platform_device *pdev) dev_dbg(&pdev->dev, "registered as cooling_device%d\n", cdev->id); fan->cdev = cdev; - result = sysfs_create_link(&pdev->dev.kobj, - &cdev->device.kobj, - "thermal_cooling"); - if (result) { - dev_err(&pdev->dev, "Failed to create sysfs link 'thermal_cooling'\n"); - goto err_unregister; - } - - result = sysfs_create_link(&cdev->device.kobj, - &pdev->dev.kobj, - "device"); - if (result) { - dev_err(&pdev->dev, "Failed to create sysfs link 'device'\n"); - goto err_remove_link; - } return 0; -err_remove_link: - sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling"); -err_unregister: - thermal_cooling_device_unregister(cdev); err_end: if (fan->has_fst) acpi_fan_delete_attributes(device); @@ -612,8 +592,6 @@ static void acpi_fan_remove(struct platform_device *pdev) acpi_fan_delete_attributes(device); } - sysfs_remove_link(&pdev->dev.kobj, "thermal_cooling"); - sysfs_remove_link(&fan->cdev->device.kobj, "device"); thermal_cooling_device_unregister(fan->cdev); } From cdb9bf0faa50592d976eb956fcfc68382dae567b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:05:07 +0200 Subject: [PATCH 41/90] ACPI: thermal: Use cooling device parent for thermal zone binding The ACPI thermal zone .should_bind() callback function, acpi_thermal_should_bind_cdev(), expects the given cooling device's devdata to point to an ACPI device object whose ACPI handle should be compared with ACPI handles in a list associated with the given trip point. That is not particularly straightforward and it effectively requires the drivers of ACPI cooling devices to populate the devdata with addresses of the ACPI companions of the devices they bind to. Consequently, the devdata cannot be used by the driver for its own needs which is its intended purpose. That can be overcome with the help of the observation that the ACPI device objects to be matched against the lists of ACPI handles associated with trip points are in fact the ACPI companions of the parents of cooling devices. Thus instead of using the given cooling device's devdata, it is sufficient to obtain the ACPI handle of its parent and compare that ACPI handle with the ones in the list associated with the given trip point. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf [ rjw: Added parent_handle check against NULL ] Link: https://patch.msgid.link/9672996.CDJkKcVGEf@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/thermal.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index dd7666c176a0..08c5a4ba16a6 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -564,17 +564,20 @@ static bool acpi_thermal_should_bind_cdev(struct thermal_zone_device *thermal, struct cooling_spec *c) { struct acpi_thermal_trip *acpi_trip = trip->priv; - struct acpi_device *cdev_adev = cdev->devdata; + struct device *parent = cdev->device.parent; + acpi_handle parent_handle; int i; - /* Skip critical and hot trips. */ - if (!acpi_trip) + /* Skip critical and hot trips and parentless cooling devices. */ + if (!acpi_trip || !parent) + return false; + + parent_handle = ACPI_HANDLE(parent); + if (!parent_handle) return false; for (i = 0; i < acpi_trip->devices.count; i++) { - acpi_handle handle = acpi_trip->devices.handles[i]; - - if (acpi_fetch_acpi_dev(handle) == cdev_adev) + if (acpi_trip->devices.handles[i] == parent_handle) return true; } From d1b25eb2d87738b5ad07070e0859017f6561760c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:05:50 +0200 Subject: [PATCH 42/90] ACPI: processor: thermal: Use more suitable cooling device data Instead of passing an ACPI device object pointer as devdata to thermal_cooling_device_create(), make acpi_processor_thermal_init() pass a pointer to the struct acpi_processor representing the given CPU to it, which allows the callback functions in processor_cooling_ops to be simplified and the second argument of acpi_processor_thermal_init() and acpi_processor_thermal_exit() to be dropped. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/6192780.MhkbZ0Pkbq@rafael.j.wysocki --- drivers/acpi/processor_driver.c | 2 +- drivers/acpi/processor_thermal.c | 35 +++++--------------------------- include/acpi/processor.h | 3 +-- 3 files changed, 7 insertions(+), 33 deletions(-) diff --git a/drivers/acpi/processor_driver.c b/drivers/acpi/processor_driver.c index 90c14480393d..bdd1529d79f0 100644 --- a/drivers/acpi/processor_driver.c +++ b/drivers/acpi/processor_driver.c @@ -164,7 +164,7 @@ static int __acpi_processor_start(struct acpi_device *device) acpi_pss_perf_init(pr); - result = acpi_processor_thermal_init(pr, device); + result = acpi_processor_thermal_init(pr); if (result) goto err_power_exit; diff --git a/drivers/acpi/processor_thermal.c b/drivers/acpi/processor_thermal.c index 6cd47551844a..11036f0d2b94 100644 --- a/drivers/acpi/processor_thermal.c +++ b/drivers/acpi/processor_thermal.c @@ -235,15 +235,7 @@ static int processor_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { - struct acpi_device *device = cdev->devdata; - struct acpi_processor *pr; - - if (!device) - return -EINVAL; - - pr = acpi_driver_data(device); - if (!pr) - return -EINVAL; + struct acpi_processor *pr = cdev->devdata; *state = acpi_processor_max_state(pr); return 0; @@ -253,15 +245,7 @@ static int processor_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *cur_state) { - struct acpi_device *device = cdev->devdata; - struct acpi_processor *pr; - - if (!device) - return -EINVAL; - - pr = acpi_driver_data(device); - if (!pr) - return -EINVAL; + struct acpi_processor *pr = cdev->devdata; *cur_state = cpufreq_get_cur_state(pr->id); if (pr->flags.throttling) @@ -273,18 +257,10 @@ static int processor_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { - struct acpi_device *device = cdev->devdata; - struct acpi_processor *pr; + struct acpi_processor *pr = cdev->devdata; int result = 0; int max_pstate; - if (!device) - return -EINVAL; - - pr = acpi_driver_data(device); - if (!pr) - return -EINVAL; - max_pstate = cpufreq_get_max_state(pr->id); if (state > acpi_processor_max_state(pr)) @@ -308,10 +284,9 @@ const struct thermal_cooling_device_ops processor_cooling_ops = { .set_cur_state = processor_set_cur_state, }; -int acpi_processor_thermal_init(struct acpi_processor *pr, - struct acpi_device *device) +int acpi_processor_thermal_init(struct acpi_processor *pr) { - pr->cdev = thermal_cooling_device_create(pr->dev, "Processor", device, + pr->cdev = thermal_cooling_device_create(pr->dev, "Processor", pr, &processor_cooling_ops); if (IS_ERR(pr->cdev)) return PTR_ERR(pr->cdev); diff --git a/include/acpi/processor.h b/include/acpi/processor.h index 656aaf74fb18..b5447af4d40b 100644 --- a/include/acpi/processor.h +++ b/include/acpi/processor.h @@ -427,8 +427,7 @@ int acpi_processor_ffh_lpi_enter(struct acpi_lpi_state *lpi); #endif /* CONFIG_ACPI_PROCESSOR_IDLE */ /* in processor_thermal.c */ -int acpi_processor_thermal_init(struct acpi_processor *pr, - struct acpi_device *device); +int acpi_processor_thermal_init(struct acpi_processor *pr); void acpi_processor_thermal_exit(struct acpi_processor *pr); extern const struct thermal_cooling_device_ops processor_cooling_ops; #ifdef CONFIG_CPU_FREQ From 520aac33ff5617e925de31cf3bf069a403763532 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:06:27 +0200 Subject: [PATCH 43/90] ACPI: fan: Store ACPI device pointer in struct acpi_fan In preparation for subsequent changes, instead of storing a fan ACPI handle in struct acpi_fan, store a pointer to the corresponding struct acpi_device in it. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/8772267.NyiUUSuA9g@rafael.j.wysocki --- drivers/acpi/fan.h | 2 +- drivers/acpi/fan_core.c | 11 ++++++----- drivers/acpi/fan_hwmon.c | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/fan.h b/drivers/acpi/fan.h index e20d6ad9df80..3faa247af514 100644 --- a/drivers/acpi/fan.h +++ b/drivers/acpi/fan.h @@ -52,7 +52,7 @@ struct acpi_fan_fst { }; struct acpi_fan { - acpi_handle handle; + struct acpi_device *adev; bool acpi4; bool has_fst; struct acpi_fan_fif fif; diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index 29ca97503deb..8b20b8a55c13 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -340,17 +340,18 @@ static int acpi_fan_dsm_init(struct device *dev) }, }; struct acpi_fan *fan = dev_get_drvdata(dev); + acpi_handle fan_handle = fan->adev->handle; union acpi_object *obj; int ret = 0; - if (!acpi_check_dsm(fan->handle, &acpi_fan_microsoft_guid, 0, + if (!acpi_check_dsm(fan_handle, &acpi_fan_microsoft_guid, 0, BIT(ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY) | BIT(ACPI_FAN_DSM_SET_TRIP_POINTS))) return 0; dev_info(dev, "Using Microsoft fan extensions\n"); - obj = acpi_evaluate_dsm_typed(fan->handle, &acpi_fan_microsoft_guid, 0, + obj = acpi_evaluate_dsm_typed(fan_handle, &acpi_fan_microsoft_guid, 0, ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY, &dummy, ACPI_TYPE_INTEGER); if (!obj) @@ -392,8 +393,8 @@ static int acpi_fan_dsm_set_trip_points(struct device *dev, u64 upper, u64 lower }; union acpi_object *obj; - obj = acpi_evaluate_dsm(fan->handle, &acpi_fan_microsoft_guid, 0, - ACPI_FAN_DSM_SET_TRIP_POINTS, &in); + obj = acpi_evaluate_dsm(fan->adev->handle, &acpi_fan_microsoft_guid, + 0, ACPI_FAN_DSM_SET_TRIP_POINTS, &in); kfree(obj); return 0; @@ -503,7 +504,7 @@ static int acpi_fan_probe(struct platform_device *pdev) return -ENOMEM; } - fan->handle = device->handle; + fan->adev = device; device->driver_data = fan; platform_set_drvdata(pdev, fan); diff --git a/drivers/acpi/fan_hwmon.c b/drivers/acpi/fan_hwmon.c index d3374f8f524b..c5d8419f41c5 100644 --- a/drivers/acpi/fan_hwmon.c +++ b/drivers/acpi/fan_hwmon.c @@ -94,7 +94,7 @@ static int acpi_fan_hwmon_read(struct device *dev, enum hwmon_sensor_types type, struct acpi_fan_fst fst; int ret; - ret = acpi_fan_get_fst(fan->handle, &fst); + ret = acpi_fan_get_fst(fan->adev->handle, &fst); if (ret < 0) return ret; From cc695a67458758bfbc0a6d40630d8ecc9ec2533d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 11 Sep 2026 15:07:07 +0200 Subject: [PATCH 44/90] ACPI: fan: Use more suitable cooling device data Instead of passing an ACPI device object pointer as devdata to thermal_cooling_device_create(), make acpi_fan_probe() pass a pointer to struct acpi_fan to it, which allows the callback functions in fan_cooling_ops to be simplified. Also avoid using acpi_driver_data() in two functions invoked by the cooling device callbacks by passing struct acpi_fan pointers instead of struct acpi_device pointers to them. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/3631084.QJadu78ljV@rafael.j.wysocki --- drivers/acpi/fan_core.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index 8b20b8a55c13..5ad65975fbb4 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -54,8 +54,7 @@ MODULE_DEVICE_TABLE(acpi, fan_device_ids); static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { - struct acpi_device *device = cdev->devdata; - struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_fan *fan = cdev->devdata; if (fan->acpi4) { if (fan->fif.fine_grain_ctrl) @@ -105,9 +104,9 @@ int acpi_fan_get_fst(acpi_handle handle, struct acpi_fan_fst *fst) return ret; } -static int fan_get_state_acpi4(struct acpi_device *device, unsigned long *state) +static int fan_get_state_acpi4(struct acpi_fan *fan, unsigned long *state) { - struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_device *device = fan->adev; struct acpi_fan_fst fst; int status, i; @@ -159,13 +158,12 @@ static int fan_get_state(struct acpi_device *device, unsigned long *state) static int fan_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *state) { - struct acpi_device *device = cdev->devdata; - struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_fan *fan = cdev->devdata; if (fan->acpi4) - return fan_get_state_acpi4(device, state); + return fan_get_state_acpi4(fan, state); else - return fan_get_state(device, state); + return fan_get_state(fan->adev, state); } static int fan_set_state(struct acpi_device *device, unsigned long state) @@ -177,9 +175,9 @@ static int fan_set_state(struct acpi_device *device, unsigned long state) state ? ACPI_STATE_D0 : ACPI_STATE_D3_COLD); } -static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state) +static int fan_set_state_acpi4(struct acpi_fan *fan, unsigned long state) { - struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_device *device = fan->adev; acpi_status status; u64 value = state; int max_state; @@ -213,13 +211,12 @@ static int fan_set_state_acpi4(struct acpi_device *device, unsigned long state) static int fan_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { - struct acpi_device *device = cdev->devdata; - struct acpi_fan *fan = acpi_driver_data(device); + struct acpi_fan *fan = cdev->devdata; if (fan->acpi4) - return fan_set_state_acpi4(device, state); + return fan_set_state_acpi4(fan, state); else - return fan_set_state(device, state); + return fan_set_state(fan->adev, state); } static const struct thermal_cooling_device_ops fan_cooling_ops = { @@ -565,7 +562,7 @@ static int acpi_fan_probe(struct platform_device *pdev) else name = acpi_device_bid(device); - cdev = thermal_cooling_device_create(&pdev->dev, name, device, &fan_cooling_ops); + cdev = thermal_cooling_device_create(&pdev->dev, name, fan, &fan_cooling_ops); if (IS_ERR(cdev)) { result = PTR_ERR(cdev); goto err_end; From d54229a6b6d68296a4d9c5067c3bc061da639a66 Mon Sep 17 00:00:00 2001 From: Myeonghun Pak Date: Thu, 10 Sep 2026 16:37:27 -0400 Subject: [PATCH 45/90] PNP: Detach device after resource transition failure pnp_device_probe() attaches the PNP device before it activates or disables its resources. The attach changes the status from PNP_READY to PNP_ATTACHED, but errors from either resource transition return directly and leave that status behind. A later bind or manual PNP configuration attempt then sees a device that is still marked in use. Route both errors through the existing failure path so it restores the status to PNP_READY. Do not disable the device or clean its resources: pnp_activate_dev() only marks it active after a successful start, while pnp_disable_dev() preserves the active state and resource table when stopping fails. This issue was identified during our ongoing static-analysis research while reviewing kernel code. Co-developed-by: Ijae Kim Signed-off-by: Ijae Kim Signed-off-by: Myeonghun Pak Link: https://patch.msgid.link/20260910203727.63489-1-mhun512@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/pnp/driver.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/pnp/driver.c b/drivers/pnp/driver.c index 6d58b1465081..80c2a0f15b80 100644 --- a/drivers/pnp/driver.c +++ b/drivers/pnp/driver.c @@ -96,13 +96,13 @@ static int pnp_device_probe(struct device *dev) if (!(pnp_drv->flags & PNP_DRIVER_RES_DO_NOT_CHANGE)) { error = pnp_activate_dev(pnp_dev); if (error < 0) - return error; + goto fail; } } else if ((pnp_drv->flags & PNP_DRIVER_RES_DISABLE) == PNP_DRIVER_RES_DISABLE) { error = pnp_disable_dev(pnp_dev); if (error < 0) - return error; + goto fail; } error = 0; if (pnp_drv->probe) { From 2b30842e52d9a364428b598f5edd4b12fa772f5a Mon Sep 17 00:00:00 2001 From: Guangshuo Li Date: Sat, 12 Sep 2026 19:33:55 +0800 Subject: [PATCH 46/90] ACPI: SBS: Fix NULL pointer dereference on allocation failure acpi_sbs_probe() jumps to the common error path when kzalloc_obj() fails, but platform_set_drvdata() is only called after the allocation succeeds. The error path calls acpi_sbs_remove(), which retrieves the unset driver data and dereferences the resulting NULL pointer when locking sbs->lock, causing a NULL pointer dereference. Return -ENOMEM directly when the allocation fails, since no resources have been initialized at that point and there is nothing to clean up. This issue was found by manual code inspection. Fixes: 9460eaae2ee42 ("ACPI: SBS: Convert the driver to a platform one") Cc: All applicable Signed-off-by: Guangshuo Li Link: https://patch.msgid.link/20260912113355.644238-1-lgs201920130244@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sbs.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index 862cb94a8b43..f10bbf13c242 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c @@ -639,10 +639,8 @@ static int acpi_sbs_probe(struct platform_device *pdev) return -ENODEV; sbs = kzalloc_obj(struct acpi_sbs); - if (!sbs) { - result = -ENOMEM; - goto end; - } + if (!sbs) + return -ENOMEM; mutex_init(&sbs->lock); From efed63bc78ede446f8bdc3b7b1730882016d332d Mon Sep 17 00:00:00 2001 From: Dirga Yuza Date: Fri, 11 Sep 2026 21:33:06 +0700 Subject: [PATCH 47/90] ACPI: video: add backlight=native DMI quirk on Acer Nitro AN515-58 The Acer Nitro AN515-58 requires the same quirk entry as AN515-46, since its firmware also advertises the NVIDIA WMI EC backlight GUID. However, on this hybrid graphics setup, the internal display is wired to the Integrated GPU (Intel Iris Xe Graphics). As a result, the nvidia_wmi_ec_backlight driver fails to probe, leaving no backlight device registered and rendering the brightness keys and desktop brightness slider non-functional. Setting acpi_backlight=native allows intel_backlight to register its backlight interface and restoring brightness control. Add a DMI quirk entry so this model uses the native backlight by default. Signed-off-by: Dirga Yuza Link: https://patch.msgid.link/20260911143306.49015-1-dirgayuza123@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/video_detect.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index e3b69f876d24..3037fd18d420 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -929,6 +929,14 @@ static const struct dmi_system_id video_detect_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-46"), }, }, + { + .callback = video_detect_force_native, + /* Acer Nitro AN515-58 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-58"), + }, + }, /* * x86 android tablets which directly control the backlight through From 68a385dbe05fd8da69ec8a7753591fd653f97d89 Mon Sep 17 00:00:00 2001 From: Ruslan Vagner Date: Tue, 15 Sep 2026 05:56:13 -0700 Subject: [PATCH 48/90] ACPI: sysfs: use strscpy() instead of strcpy() strcpy() is deprecated in the kernel, so use strscpy() with the compile-time known size of the trace_method_name buffer instead of it. The existing length check already rejects overlong input, so the behavior is unchanged. Signed-off-by: Ruslan Vagner [ rjw: Changelog edits ] Link: https://patch.msgid.link/CAF9RqJ4ip_egVwRKnwn3=X-Qxuwz0u_9pNrgfujBw8RSf2DTPQ@mail.gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sysfs.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c index dd4a99f09efe..a99373547126 100644 --- a/drivers/acpi/sysfs.c +++ b/drivers/acpi/sysfs.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "internal.h" @@ -181,10 +182,10 @@ static int param_set_trace_method_name(const char *val, /* This is a hack. We can't kmalloc in early boot. */ if (is_abs_path) - strcpy(trace_method_name, val); + strscpy(trace_method_name, val, sizeof(trace_method_name)); else { trace_method_name[0] = '\\'; - strcpy(trace_method_name+1, val); + strscpy(trace_method_name + 1, val, sizeof(trace_method_name) - 1); } /* Restore the original tracer state */ From 047c834d939df75386cfc0603c7ddeaa23a349a4 Mon Sep 17 00:00:00 2001 From: Slavin Liu Date: Sun, 13 Sep 2026 20:51:23 +0800 Subject: [PATCH 49/90] ACPI: tables: handle failed initrd override mappings The runtime AML table-loading path can invoke initrd override after permanent mappings are enabled. Mapping bookkeeping can then fail. Return AE_NO_MEMORY in that case to prevent a NULL pointer dereference when attempting to read the table length. Detected by static analysis and reviewed with AI-assisted source auditing. Fixes: b2a35003dfbc ("ACPI: Implement physical address table override") Assisted-by: LLM Signed-off-by: Slavin Liu [ rjw: Added empty code line after conditional, changelog edits ] Link: https://patch.msgid.link/20260913125123.109803-1-bolin.liu@seu.edu.cn Signed-off-by: Rafael J. Wysocki --- drivers/acpi/tables.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/acpi/tables.c b/drivers/acpi/tables.c index eb93c060426f..aaa1de79bccf 100644 --- a/drivers/acpi/tables.c +++ b/drivers/acpi/tables.c @@ -560,6 +560,9 @@ acpi_table_initrd_override(struct acpi_table_header *existing_table, while (table_offset + ACPI_HEADER_SIZE <= all_tables_size) { table = acpi_os_map_memory(acpi_tables_addr + table_offset, ACPI_HEADER_SIZE); + if (!table) + return AE_NO_MEMORY; + if (table_offset + table->length > all_tables_size) { acpi_os_unmap_memory(table, ACPI_HEADER_SIZE); WARN_ON(1); From 46767b17fda3fba1ed91dd73d7d432b0bb6851c1 Mon Sep 17 00:00:00 2001 From: Saranya Gopal Date: Fri, 4 Sep 2026 13:08:44 +0530 Subject: [PATCH 50/90] ACPI: PM: s2idle: Add _Ixx GPE wake indicator support ACPI 6.6 defines _Ixx indicator objects under \_GPE to mark GPEs that should remain wake-enabled in s2idle without a _PRW association. To support them, add new function acpi_setup_ixx_gpes() that looks for _Ixx objects for all GPEs in the FADT 0/1 blocks and if it finds any, it marks the corresponding GPE as wake-capable, in which case the GPE is also enabled so long as it has a handler method because marking it as wake-capable causes acpi_update_all_gpes() to skip it. Since the above only needs to be done if suspend-to-idle is enabled, make acpi_s2idle_setup() call that new function. For now, this is limited to x86 systems and it assumes that the GPEs with _Ixx will not cause spurious wakeups to occur while the system is suspended. Signed-off-by: Saranya Gopal [ rjw: Added three empty code lines, subject tweak, changelog edits ] [ rjw: New debug message adjustment ] Link: https://patch.msgid.link/20260904073844.2740931-1-saranya.gopal@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/x86/s2idle.c | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index b6b1dd76a06b..c278ea14c0b3 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -24,6 +24,34 @@ #ifdef CONFIG_SUSPEND +static void acpi_setup_ixx_gpes(void) +{ + acpi_handle gpe_root; + unsigned int i; + char gpe_nr_str[5]; + + if (ACPI_FAILURE(acpi_get_handle(NULL, "\\_GPE", &gpe_root))) + return; + + for (i = 0; i <= 0xff; i++) { + scnprintf(gpe_nr_str, sizeof(gpe_nr_str), "_I%02X", i); + if (!acpi_has_method(gpe_root, gpe_nr_str)) + continue; + + /* + * Enable the GPE if it has a handler method because marking it + * as wake-capable causes acpi_update_all_gpes() to skip it. + */ + if (ACPI_FAILURE(acpi_enable_gpe_cond(NULL, i, ACPI_GPE_DISPATCH_METHOD))) + continue; + + acpi_mark_gpe_for_wake(NULL, i); + acpi_set_gpe_wake_mask(NULL, i, ACPI_GPE_ENABLE); + + pm_pr_dbg("ACPI: GPE0x%02x armed for wake via %s\n", i, gpe_nr_str); + } +} + static bool sleep_no_lps0 __read_mostly; module_param(sleep_no_lps0, bool, 0644); MODULE_PARM_DESC(sleep_no_lps0, "Do not use the special LPS0 device interface"); @@ -649,6 +677,7 @@ void __init acpi_s2idle_setup(void) { acpi_scan_add_handler(&lps0_handler); s2idle_set_ops(&acpi_s2idle_ops_lps0); + acpi_setup_ixx_gpes(); } int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg) From b1c75a77d494298b8dedf46062f2072239bbc33b Mon Sep 17 00:00:00 2001 From: Alejandro Lucero Date: Thu, 16 Jul 2026 19:11:14 +0100 Subject: [PATCH 51/90] ACPI: HMAT: preserve hmat_adist_nb notifier block Remove __meminitdata from hmat_adist_nb declaration preserving it even when CONFIG_MEMORY_HOTPLUG is not set. Otherwise further notifier registrations using register_mt_adistance_algorithm() will find a notifier chain likely corrupted. This is the case when cxl_region_probe() calls register_mt_adistance_algorithm() what can happen when CXL regions are dynamically created at any time from Type3 device(s) or when a Type2 device is initialized at its driver probe. Fixes: 3718c02dbd4c ("acpi, hmat: calculate abstract distance with HMAT") Signed-off-by: Alejandro Lucero Reviewed-by: Dave Jiang Reviewed-by: Jonathan Cameron [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260716181114.35962-2-alejandro.lucero-palau@amd.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/numa/hmat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/numa/hmat.c b/drivers/acpi/numa/hmat.c index 9792dc394756..3006789ae31f 100644 --- a/drivers/acpi/numa/hmat.c +++ b/drivers/acpi/numa/hmat.c @@ -995,7 +995,7 @@ static int hmat_calculate_adistance(struct notifier_block *self, return NOTIFY_STOP; } -static struct notifier_block hmat_adist_nb __meminitdata = { +static struct notifier_block hmat_adist_nb = { .notifier_call = hmat_calculate_adistance, .priority = 100, }; From ca38d128b66275286da4230cb07b401c8f973fbd Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 19 Sep 2026 14:31:31 +0200 Subject: [PATCH 52/90] =?UTF-8?q?ACPI:=20thermal:=20Fix=20acpi=5Fthermal?= =?UTF-8?q?=5Fguess=5Foffset()=20for=20device=20with=20a=20273.0=C2=B0K=20?= =?UTF-8?q?offset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Some devices round the 273.15° offset between Kelvin and Celsius to 273.0 instead of 273.1 or 273.2. Make acpi_thermal_guess_offset() check for this value too, fixing e.g. all zones reporting x.8°C to them reporting y.0°C instead. Signed-off-by: Hans de Goede Link: https://patch.msgid.link/20260919123131.11443-1-johannes.goede@oss.qualcomm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/thermal.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index 08c5a4ba16a6..13e9a3a79d92 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -741,7 +741,9 @@ static void acpi_thermal_aml_dependency_fix(struct acpi_thermal *tz) */ static void acpi_thermal_guess_offset(struct acpi_thermal *tz, long crit_temp) { - if (crit_temp != THERMAL_TEMP_INVALID && crit_temp % 5 == 1) + if (crit_temp != THERMAL_TEMP_INVALID && crit_temp % 5 == 0) + tz->kelvin_offset = 273000; + else if (crit_temp != THERMAL_TEMP_INVALID && crit_temp % 5 == 1) tz->kelvin_offset = 273100; else tz->kelvin_offset = 273200; From e7280ab196ba4bcef6d3f6ee860bf0d2c8e96e51 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:45:59 +0200 Subject: [PATCH 53/90] ACPI: bus: Use acpi/acpi_bus.h without CONFIG_ACPI Corrently acpi/acpi_bus.h is included in linux/acpi.h only if CONFIG_ACPI is set, but it contains declarations that are useful otherwise too. For consistency, modify linux/acpi.h to always include acpi/acpi_bus.h and update the latter to avoid exposing too much when CONFIG_ACPI is not set. Since acpi_has_method() is called from some code build when CONFIG_ACPI is unset, add a stub for it to the !CONFIG_ACPI block in acpi/acpi_bus.h. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/3717753.iIbC2pHGDl@rafael.j.wysocki --- include/acpi/acpi_bus.h | 12 ++++++++---- include/linux/acpi.h | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index a10a591c18b2..32837f507f40 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -9,6 +9,8 @@ #ifndef __ACPI_BUS_H__ #define __ACPI_BUS_H__ +#ifdef CONFIG_ACPI + #include #include #include @@ -58,7 +60,6 @@ bool acpi_dock_match(acpi_handle handle); bool acpi_check_dsm(acpi_handle handle, const guid_t *guid, u64 rev, u64 funcs); union acpi_object *acpi_evaluate_dsm(acpi_handle handle, const guid_t *guid, u64 rev, u64 func, union acpi_object *argv4); -#ifdef CONFIG_ACPI bool acpi_get_physical_device_location(acpi_handle handle, struct acpi_pld_info **pld); @@ -77,7 +78,6 @@ acpi_evaluate_dsm_typed(acpi_handle handle, const guid_t *guid, u64 rev, return obj; } -#endif #define ACPI_INIT_DSM_ARGV4(cnt, eles) \ { \ @@ -90,8 +90,6 @@ bool acpi_dev_found(const char *hid); bool acpi_dev_present(const char *hid, const char *uid, s64 hrv); bool acpi_reduced_hardware(void); -#ifdef CONFIG_ACPI - struct proc_dir_entry; #define ACPI_BUS_FILE_ROOT "acpi" @@ -942,8 +940,14 @@ int acpi_wait_for_acpi_ipmi(void); int acpi_scan_add_dep(acpi_handle handle, struct acpi_handle_list *dep_devices); u32 arch_acpi_add_auto_dep(acpi_handle handle); + #else /* CONFIG_ACPI */ +static inline bool acpi_has_method(acpi_handle handle, char *name) +{ + return false; +} + static inline struct device *acpi_bus_get_primary_device(struct acpi_device *adev) { return NULL; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index ddacac812094..ba5234bd93a2 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -25,6 +25,7 @@ struct irq_domain_ops; #define _LINUX #endif #include +#include #include #ifdef CONFIG_ACPI @@ -35,7 +36,6 @@ struct irq_domain_ops; #include #include -#include #include #include #include From 28bfd74601a4ac16310b0797c24e6220108c58d1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:46:37 +0200 Subject: [PATCH 54/90] ACPI: amba: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Hanjun Guo Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/3362405.5fSG56mABF@rafael.j.wysocki --- drivers/acpi/arm64/amba.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/arm64/amba.c b/drivers/acpi/arm64/amba.c index 1350083bce5f..7ee431d9464a 100644 --- a/drivers/acpi/arm64/amba.c +++ b/drivers/acpi/arm64/amba.c @@ -87,11 +87,12 @@ static int amba_handler_attach(struct acpi_device *adev, * the amba device we are about to create. */ if (parent) - dev->dev.parent = acpi_get_first_physical_node(parent); + dev->dev.parent = acpi_bus_get_primary_device(parent); device_set_node(&dev->dev, acpi_fwnode_handle(adev)); ret = amba_device_add(dev, &iomem_resource); + put_device(dev->dev.parent); if (ret) { dev_err(&adev->dev, "%s(): amba_device_add() failed (%d)\n", __func__, ret); From 574799253d1598f7dec5f57a261a8773ac9179f9 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:48:30 +0200 Subject: [PATCH 55/90] platform/x86: yogabook: Switch to use acpi_bus_get_primary_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Ilpo Järvinen Reviewed-by: Hans de Goede Reviewed-by: Mark Pearson Link: https://patch.msgid.link/1973238.CQOukoFCf9@rafael.j.wysocki --- drivers/platform/x86/lenovo/yogabook.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/lenovo/yogabook.c b/drivers/platform/x86/lenovo/yogabook.c index 1a4b2ab1f35d..8a91d440724b 100644 --- a/drivers/platform/x86/lenovo/yogabook.c +++ b/drivers/platform/x86/lenovo/yogabook.c @@ -353,13 +353,13 @@ static int yogabook_wmi_probe(struct wmi_device *wdev, const void *context) goto error_put_devs; } - data->kbd_dev = get_device(acpi_get_first_physical_node(data->kbd_adev)); + data->kbd_dev = acpi_bus_get_primary_device(data->kbd_adev); if (!data->kbd_dev || !data->kbd_dev->driver) { r = -EPROBE_DEFER; goto error_put_devs; } - data->dig_dev = get_device(acpi_get_first_physical_node(data->dig_adev)); + data->dig_dev = acpi_bus_get_primary_device(data->dig_adev); if (!data->dig_dev || !data->dig_dev->driver) { r = -EPROBE_DEFER; goto error_put_devs; From 790b502a4b019a71f1c3e1d7904c7426ac2f2e11 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:49:07 +0200 Subject: [PATCH 56/90] platform/x86: serdev_helpers: Switch to use acpi_bus_get_primary_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Ilpo Järvinen Reviewed-by: Hans de Goede Link: https://patch.msgid.link/2720286.Lt9SDvczpP@rafael.j.wysocki --- drivers/platform/x86/serdev_helpers.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/platform/x86/serdev_helpers.h b/drivers/platform/x86/serdev_helpers.h index 57eac75805e2..20267e1941f1 100644 --- a/drivers/platform/x86/serdev_helpers.h +++ b/drivers/platform/x86/serdev_helpers.h @@ -74,8 +74,7 @@ get_serdev_controller(const char *serial_ctrl_hid, return ERR_PTR(-ENODEV); } - /* get_first_physical_node() returns a weak ref */ - parent = get_device(acpi_get_first_physical_node(adev)); + parent = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!parent) { pr_err("error could not get %s/%s serial-ctrl physical node\n", From 2058cd0f59caf475c8efad843b900a3119eb3a5c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:50:28 +0200 Subject: [PATCH 57/90] platform/x86: x86-android-tablets: Use acpi_bus_get_primary_device() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Ilpo Järvinen Reviewed-by: Hans de Goede Link: https://patch.msgid.link/3912360.MHq7AAxBmi@rafael.j.wysocki --- drivers/platform/x86/x86-android-tablets/core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets/core.c b/drivers/platform/x86/x86-android-tablets/core.c index cfff7f5eac5d..2c262f117235 100644 --- a/drivers/platform/x86/x86-android-tablets/core.c +++ b/drivers/platform/x86/x86-android-tablets/core.c @@ -390,7 +390,6 @@ static int gpio_secondary_fwnode_init(struct device *parent, { const struct software_node *const *swnode; struct fwnode_handle *fwnode; - struct device *phys_dev; int ret; if (!node_group) @@ -418,7 +417,8 @@ static int gpio_secondary_fwnode_init(struct device *parent, if (WARN_ON(!fwnode)) return -ENOENT; - phys_dev = acpi_get_first_physical_node(to_acpi_device(dev)); + struct device *phys_dev __free(put_device) = + acpi_bus_get_primary_device(to_acpi_device(dev)); if (!phys_dev) return dev_err_probe(parent, -ENODEV, "No physical device for ACPI GPIO dev: %pfwP\n", From 7a4c8ddba1dcf7c63b329d81de1d494188e5d2b0 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:49:49 +0200 Subject: [PATCH 58/90] platform/x86: apple-gmux: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Hans de Goede Link: https://patch.msgid.link/2287208.Mh6RI2rZIc@rafael.j.wysocki --- include/linux/apple-gmux.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/apple-gmux.h b/include/linux/apple-gmux.h index 206d97ffda79..d7939c3a08fd 100644 --- a/include/linux/apple-gmux.h +++ b/include/linux/apple-gmux.h @@ -109,7 +109,7 @@ static inline bool apple_gmux_detect(struct pnp_dev *pnp_dev, enum apple_gmux_ty if (!adev) return false; - dev = get_device(acpi_get_first_physical_node(adev)); + dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!dev) return false; From 37a78048d6161e13f61ab773dcbd6e7ba965e978 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:51:06 +0200 Subject: [PATCH 59/90] thunderbolt: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Mika Westerberg Link: https://patch.msgid.link/1941270.atdPhlSkOF@rafael.j.wysocki --- drivers/thunderbolt/acpi.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/thunderbolt/acpi.c b/drivers/thunderbolt/acpi.c index 53546bc477a5..be91bc604900 100644 --- a/drivers/thunderbolt/acpi.c +++ b/drivers/thunderbolt/acpi.c @@ -17,8 +17,8 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, struct acpi_device *adev = acpi_fetch_acpi_dev(handle); struct fwnode_handle *fwnode; struct tb_nhi *nhi = data; + struct device *dev = NULL; struct pci_dev *pdev; - struct device *dev; if (!adev) return AE_OK; @@ -37,7 +37,7 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, * USB3 ports might not even have a physical device yet if xHCI driver * isn't bound yet. */ - dev = acpi_get_first_physical_node(adev); + dev = acpi_bus_get_primary_device(adev); if (!dev || !dev_is_pci(dev)) goto out_put; @@ -74,6 +74,7 @@ static acpi_status tb_acpi_add_link(acpi_handle handle, u32 level, void *data, } out_put: + put_device(dev); fwnode_handle_put(fwnode); return AE_OK; } From 6f50ab7ab1f93e874fa75d18bf98bbca4ce0204f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:53:51 +0200 Subject: [PATCH 60/90] ASoC: amd: acp-es8336: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. It also fixes a device reference counting issue that may lead to a premature device release if gpiod_get_optional() fails in st_es8336_late_probe(). Fixes: 02527c3f2300 ("ASoC: amd: add Machine driver for Jadeite platform") Signed-off-by: Rafael J. Wysocki Acked-by: Mark Brown Link: https://patch.msgid.link/2063881.yKVeVyVuyW@rafael.j.wysocki --- sound/soc/amd/acp-es8336.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp-es8336.c b/sound/soc/amd/acp-es8336.c index 9f3f11256788..34e953d1a3a7 100644 --- a/sound/soc/amd/acp-es8336.c +++ b/sound/soc/amd/acp-es8336.c @@ -198,7 +198,7 @@ static int st_es8336_late_probe(struct snd_soc_card *card) if (!adev) return -ENODEV; - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) { dev_err(card->dev, "can not find codec dev\n"); From aae5bd00ced5b7e42ef3b3aa46923ad72388c60b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:54:29 +0200 Subject: [PATCH 61/90] ASoC: amd: acp3x-es83xx: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. It also fixes a device reference counting issue that may lead to a premature device release if devm_kzalloc() fails in acp3x_es83xx_probe(). Fixes: 54fcd9dd44b2 ("ASoC: amd: acp: Add machine driver that enables sound for systems with a ES8336 codec") Signed-off-by: Rafael J. Wysocki Acked-by: Mark Brown Link: https://patch.msgid.link/2310746.NgBsaNRSFp@rafael.j.wysocki --- sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c index 3a640e652314..c522b421e1ec 100644 --- a/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c +++ b/sound/soc/amd/acp/acp3x-es83xx/acp3x-es83xx.c @@ -428,7 +428,7 @@ static int acp3x_es83xx_probe(struct snd_soc_card *card) return -ENXIO; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) { dev_warn(dev, "Error cannot find codec device, will defer probe\n"); From e1aee48abfaeed1fe1a88018066d2f06c98c065f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:55:15 +0200 Subject: [PATCH 62/90] ASoC: intel: boards: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Cezary Rojewski Acked-by: Mark Brown Link: https://patch.msgid.link/15844908.tv2OnDr8pf@rafael.j.wysocki --- sound/soc/intel/boards/bytcht_es8316.c | 4 ++-- sound/soc/intel/boards/bytcr_rt5640.c | 4 ++-- sound/soc/intel/boards/bytcr_rt5651.c | 4 ++-- sound/soc/intel/boards/cht_bsw_rt5645.c | 5 ++--- sound/soc/intel/boards/sof_cirrus_common.c | 2 +- sound/soc/intel/boards/sof_es8336.c | 4 ++-- 6 files changed, 11 insertions(+), 12 deletions(-) diff --git a/sound/soc/intel/boards/bytcht_es8316.c b/sound/soc/intel/boards/bytcht_es8316.c index ea387dc74273..4d8d1d176a49 100644 --- a/sound/soc/intel/boards/bytcht_es8316.c +++ b/sound/soc/intel/boards/bytcht_es8316.c @@ -601,11 +601,11 @@ static int snd_byt_cht_es8316_mc_probe(struct platform_device *pdev) return -ENOENT; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) return -EPROBE_DEFER; - priv->codec_dev = get_device(codec_dev); + priv->codec_dev = codec_dev; /* override platform name, if required */ byt_cht_es8316_card.dev = dev; diff --git a/sound/soc/intel/boards/bytcr_rt5640.c b/sound/soc/intel/boards/bytcr_rt5640.c index 40da3eea5fa7..c84b9a0fe65a 100644 --- a/sound/soc/intel/boards/bytcr_rt5640.c +++ b/sound/soc/intel/boards/bytcr_rt5640.c @@ -1744,11 +1744,11 @@ static int snd_byt_rt5640_mc_probe(struct platform_device *pdev) return -ENOENT; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (codec_dev) { - priv->codec_dev = get_device(codec_dev); + priv->codec_dev = codec_dev; } else { /* * Special case for Android tablets where the codec i2c_client diff --git a/sound/soc/intel/boards/bytcr_rt5651.c b/sound/soc/intel/boards/bytcr_rt5651.c index 62cb4856c797..a0ed56d285bc 100644 --- a/sound/soc/intel/boards/bytcr_rt5651.c +++ b/sound/soc/intel/boards/bytcr_rt5651.c @@ -943,11 +943,11 @@ static int snd_byt_rt5651_mc_probe(struct platform_device *pdev) return -ENOENT; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) return -EPROBE_DEFER; - priv->codec_dev = get_device(codec_dev); + priv->codec_dev = codec_dev; /* * swap SSP0 if bytcr is detected diff --git a/sound/soc/intel/boards/cht_bsw_rt5645.c b/sound/soc/intel/boards/cht_bsw_rt5645.c index 249be121be15..4f20928a3604 100644 --- a/sound/soc/intel/boards/cht_bsw_rt5645.c +++ b/sound/soc/intel/boards/cht_bsw_rt5645.c @@ -529,7 +529,6 @@ static int snd_cht_mc_probe(struct platform_device *pdev) const char *platform_name; struct cht_mc_private *drv; struct acpi_device *adev; - struct device *codec_dev; bool sof_parent; bool found = false; bool is_bytcr = false; @@ -583,8 +582,8 @@ static int snd_cht_mc_probe(struct platform_device *pdev) return -ENOENT; } - /* acpi_get_first_physical_node() returns a borrowed ref, no need to deref */ - codec_dev = acpi_get_first_physical_node(adev); + struct device *codec_dev __free(put_device) = acpi_bus_get_primary_device(adev); + acpi_dev_put(adev); if (!codec_dev) return -EPROBE_DEFER; diff --git a/sound/soc/intel/boards/sof_cirrus_common.c b/sound/soc/intel/boards/sof_cirrus_common.c index 88fc6cb2bfd4..a4ecb1f9a615 100644 --- a/sound/soc/intel/boards/sof_cirrus_common.c +++ b/sound/soc/intel/boards/sof_cirrus_common.c @@ -168,7 +168,7 @@ static int cs35l41_compute_codec_conf(void) cs35l41_name_prefixes[uid]); continue; } - physdev = get_device(acpi_get_first_physical_node(adev)); + physdev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!physdev) { pr_devel("Cannot find physical node for HID %s UID %u (%s)\n", CS35L41_HID, diff --git a/sound/soc/intel/boards/sof_es8336.c b/sound/soc/intel/boards/sof_es8336.c index f1e62c2e79fe..d40f10c6ae54 100644 --- a/sound/soc/intel/boards/sof_es8336.c +++ b/sound/soc/intel/boards/sof_es8336.c @@ -702,11 +702,11 @@ static int sof_es8336_probe(struct platform_device *pdev) return -ENOENT; } - codec_dev = acpi_get_first_physical_node(adev); + codec_dev = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!codec_dev) return -EPROBE_DEFER; - priv->codec_dev = get_device(codec_dev); + priv->codec_dev = codec_dev; ret = snd_soc_fixup_dai_links_platform_name(&sof_es8336_card, mach->mach_params.platform); From ec6bb46cf5f70b7df2b2d87bc3a7fbe55f60abc2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:55:56 +0200 Subject: [PATCH 63/90] ASoC: loongson: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Mark Brown Link: https://patch.msgid.link/2796562.BddDVKsqQX@rafael.j.wysocki --- sound/soc/loongson/loongson_card.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/soc/loongson/loongson_card.c b/sound/soc/loongson/loongson_card.c index 6422cc1703b6..9f3907278462 100644 --- a/sound/soc/loongson/loongson_card.c +++ b/sound/soc/loongson/loongson_card.c @@ -196,7 +196,6 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) struct snd_soc_card *card = &data->snd_card; const char *codec_dai_name; struct acpi_device *adev; - struct device *phy_dev; int i, ret; /* fixup platform name based on reference node */ @@ -204,7 +203,7 @@ static int loongson_card_parse_acpi(struct loongson_card_data *data) if (!adev) return -ENOENT; - phy_dev = acpi_get_first_physical_node(adev); + struct device *phy_dev __free(put_device) = acpi_bus_get_primary_device(adev); if (!phy_dev) return -EPROBE_DEFER; From 7df0f9ee9c5a1aa70b9b34ec719324d2961cb7a7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 21 Sep 2026 21:52:32 +0200 Subject: [PATCH 64/90] ALSA: hda: aw88399: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Marco Giunta Link: https://patch.msgid.link/2568518.jE0xQCEvom@rafael.j.wysocki --- sound/hda/codecs/side-codecs/aw88399_hda.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/hda/codecs/side-codecs/aw88399_hda.c b/sound/hda/codecs/side-codecs/aw88399_hda.c index 11cef4923024..439ad45557a4 100644 --- a/sound/hda/codecs/side-codecs/aw88399_hda.c +++ b/sound/hda/codecs/side-codecs/aw88399_hda.c @@ -209,8 +209,7 @@ static int aw88399_hda_acpi_probe(struct aw88399_hda *aw88399) return -ENODEV; } - struct device *physdev __free(put_device) = - get_device(acpi_get_first_physical_node(adev)); + struct device *physdev __free(put_device) = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!physdev) return -ENODEV; From 0fa8ad52b6e3e0838a6c865d1159b6409621d91b Mon Sep 17 00:00:00 2001 From: Robin Murphy Date: Tue, 22 Sep 2026 16:26:34 +0100 Subject: [PATCH 65/90] ACPI: bus: Provide !ACPI stub for acpi_device_uid() While the likes of acpi_dev_uid_to_integer() and acpi_dev_uid_match() already have stubs for !ACPI, the basic acpi_device_uid() getter does not. Although calls in portable drivers should already be conditional on a valid acpi_device in a manner which would usually lead to compile- time elimination anyway, add a stub for the sake of completeness. Signed-off-by: Robin Murphy [ rjw: Subject tweak ] Link: https://patch.msgid.link/8325277ff0484ab869fecf662a7ea356cd00bcde.1790090794.git.robin.murphy@arm.com Signed-off-by: Rafael J. Wysocki --- include/acpi/acpi_bus.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 32837f507f40..dca4df750c54 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -963,6 +963,11 @@ static inline const char *acpi_device_hid(struct acpi_device *device) return ""; } +static inline char *acpi_device_uid(struct acpi_device *device) +{ + return NULL; +} + static inline bool acpi_get_physical_device_location(acpi_handle handle, struct acpi_pld_info **pld) { From b9026ef6189ef44b32b4163431c8666be3cbd881 Mon Sep 17 00:00:00 2001 From: Lalit Shankar Chowdhury Date: Mon, 21 Sep 2026 08:14:11 +0000 Subject: [PATCH 66/90] ACPI: WMI: use IS_ENABLED() to simplify built-in or module check Replace the remaining built-in/module check for CONFIG_ACPI_WMI with IS_ENABLED() macro. Signed-off-by: Lalit Shankar Chowdhury Reviewed-by: Armin Wolf [ rjw: Subject tweaks ] Link: https://patch.msgid.link/20260921081411.4819-1-lalitshankarch@gmail.com Signed-off-by: Rafael J. Wysocki --- include/linux/acpi.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index ddacac812094..b590f520dc57 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -434,7 +434,7 @@ extern acpi_handle ec_get_handle(void); extern bool acpi_is_pnp_device(struct acpi_device *); -#if defined(CONFIG_ACPI_WMI) || defined(CONFIG_ACPI_WMI_MODULE) +#if IS_ENABLED(CONFIG_ACPI_WMI) typedef void (*wmi_notify_handler) (union acpi_object *data, void *context); From a0039113053d4d3f629ea1b9cd74130ca597f14a Mon Sep 17 00:00:00 2001 From: Matthias Dittrich Date: Mon, 7 Sep 2026 09:04:49 +0200 Subject: [PATCH 67/90] ACPI: PM: Add quirk for Acer Swift 3 SF314-56G (MX250) The Acer Swift 3 SF314-56G (with NVIDIA GeForce MX250) suffers from the same ACPI power resource regression as the Thunderobot ZERO. The power resource controlling the dGPU is incorrectly turned off during initialization, causing the GPU to fall off the bus (D3cold). This commit adds a DMI quirk to keep the power resource on for this model, similar to the existing Thunderobot quirk. Tested on Acer Swift 3 SF314-56G with NVIDIA MX250. Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221087 Signed-off-by: Matthias Dittrich [ rjw: Changelog edits ] Signed-off-by: Rafael J. Wysocki --- drivers/acpi/power.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 23a4e207a01e..c6955d1ad29d 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -1120,6 +1120,19 @@ static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { DMI_MATCH(DMI_PRODUCT_NAME, "ZERO"), } + }, + { + /* + * Acer Swift 3 SF314-56G with NVIDIA MX250 suffers from the + * same ACPI power resource regression as the Thunderobot ZERO. + * The power resource controlling the dGPU is incorrectly turned off + * during initialization, causing the GPU to fall off the bus (D3cold). + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Swift SF314-56G"), + } + }, {} }; From b2ddc080570c9012359a786bf8ae5480c2911a6a Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 16 Sep 2026 21:42:54 +0200 Subject: [PATCH 68/90] ACPI: fan: Use __free() to simplify AML error handling Introduce acpi_object_free for freeing union acpi_object objects allocated by AML and use it for simplifying AML error handling in the ACPI fan driver. While at it, update the driver to use consistent error values across all function using the union acpi_object data type. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Reviewed-by: Armin Wolf [ rjw: Put the new free definition under CONFIG_ACPI ] Link: https://patch.msgid.link/6029013.DvuYhMxLoT@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/fan_core.c | 80 ++++++++++++++++------------------------- include/linux/acpi.h | 2 ++ 2 files changed, 32 insertions(+), 50 deletions(-) diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index 5ad65975fbb4..3674f9613d40 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -71,37 +71,29 @@ static int fan_get_max_state(struct thermal_cooling_device *cdev, unsigned long int acpi_fan_get_fst(acpi_handle handle, struct acpi_fan_fst *fst) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; acpi_status status; - int ret = 0; status = acpi_evaluate_object(handle, "_FST", NULL, &buffer); if (ACPI_FAILURE(status)) - return -EIO; + return -ENXIO; - obj = buffer.pointer; + union acpi_object *obj __free(acpi_object_free) = buffer.pointer; if (!obj) return -ENODATA; - if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3) { - ret = -EPROTO; - goto err; - } + if (obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 3) + return -EPROTO; if (obj->package.elements[0].type != ACPI_TYPE_INTEGER || obj->package.elements[1].type != ACPI_TYPE_INTEGER || - obj->package.elements[2].type != ACPI_TYPE_INTEGER) { - ret = -EPROTO; - goto err; - } + obj->package.elements[2].type != ACPI_TYPE_INTEGER) + return -EPROTO; fst->revision = obj->package.elements[0].integer.value; fst->control = obj->package.elements[1].integer.value; fst->speed = obj->package.elements[2].integer.value; -err: - kfree(obj); - return ret; + return 0; } static int fan_get_state_acpi4(struct acpi_fan *fan, unsigned long *state) @@ -237,25 +229,22 @@ static int acpi_fan_get_fif(struct acpi_device *device) struct acpi_buffer format = { sizeof("NNNN"), "NNNN" }; u64 fields[4]; struct acpi_buffer fif = { sizeof(fields), fields }; - union acpi_object *obj; acpi_status status; status = acpi_evaluate_object(device->handle, "_FIF", NULL, &buffer); if (ACPI_FAILURE(status)) - return status; + return -ENXIO; - obj = buffer.pointer; + union acpi_object *obj __free(acpi_object_free) = buffer.pointer; if (!obj || obj->type != ACPI_TYPE_PACKAGE) { dev_err(&device->dev, "Invalid _FIF data\n"); - status = -EINVAL; - goto err; + return -ENODATA; } status = acpi_extract_package(obj, &format, &fif); if (ACPI_FAILURE(status)) { dev_err(&device->dev, "Invalid _FIF element\n"); - status = -EINVAL; - goto err; + return -ENODATA; } fan->fif.revision = fields[0]; @@ -269,9 +258,8 @@ static int acpi_fan_get_fif(struct acpi_device *device) /* If step size > 9, change to 9 (by spec valid values 1-9) */ else if (fan->fif.step_size > 9) fan->fif.step_size = 9; -err: - kfree(obj); - return status; + + return 0; } static int acpi_fan_speed_cmp(const void *a, const void *b) @@ -285,27 +273,24 @@ static int acpi_fan_get_fps(struct device *dev, struct acpi_device *device) { struct acpi_fan *fan = acpi_driver_data(device); struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - union acpi_object *obj; acpi_status status; int i; status = acpi_evaluate_object(device->handle, "_FPS", NULL, &buffer); if (ACPI_FAILURE(status)) - return status; + return -ENXIO; - obj = buffer.pointer; + union acpi_object *obj __free(acpi_object_free) = buffer.pointer; if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count < 2) { dev_err(&device->dev, "Invalid _FPS data\n"); - status = -EINVAL; - goto err; + return -ENODATA; } fan->fps_count = obj->package.count - 1; /* minus revision field */ fan->fps = devm_kcalloc(dev, fan->fps_count, sizeof(*fan->fps), GFP_KERNEL); - if (!fan->fps) { - status = -ENOMEM; - goto err; - } + if (!fan->fps) + return -ENOMEM; + for (i = 0; i < fan->fps_count; i++) { struct acpi_buffer format = { sizeof("NNNNN"), "NNNNN" }; struct acpi_buffer fps = { offsetof(struct acpi_fan_fps, name), @@ -314,7 +299,7 @@ static int acpi_fan_get_fps(struct device *dev, struct acpi_device *device) &format, &fps); if (ACPI_FAILURE(status)) { dev_err(&device->dev, "Invalid _FPS element\n"); - goto err; + return -ENODATA; } } @@ -322,9 +307,7 @@ static int acpi_fan_get_fps(struct device *dev, struct acpi_device *device) sort(fan->fps, fan->fps_count, sizeof(*fan->fps), acpi_fan_speed_cmp, NULL); -err: - kfree(obj); - return status; + return 0; } static int acpi_fan_dsm_init(struct device *dev) @@ -338,8 +321,6 @@ static int acpi_fan_dsm_init(struct device *dev) }; struct acpi_fan *fan = dev_get_drvdata(dev); acpi_handle fan_handle = fan->adev->handle; - union acpi_object *obj; - int ret = 0; if (!acpi_check_dsm(fan_handle, &acpi_fan_microsoft_guid, 0, BIT(ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY) | @@ -348,20 +329,19 @@ static int acpi_fan_dsm_init(struct device *dev) dev_info(dev, "Using Microsoft fan extensions\n"); - obj = acpi_evaluate_dsm_typed(fan_handle, &acpi_fan_microsoft_guid, 0, - ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY, &dummy, - ACPI_TYPE_INTEGER); + union acpi_object *obj __free(acpi_object_free) = + acpi_evaluate_dsm_typed(fan_handle, &acpi_fan_microsoft_guid, 0, + ACPI_FAN_DSM_GET_TRIP_POINT_GRANULARITY, + &dummy, ACPI_TYPE_INTEGER); if (!obj) - return -EIO; + return -ENXIO; if (obj->integer.value > U32_MAX) - ret = -EOVERFLOW; - else - fan->fan_trip_granularity = obj->integer.value; + return -EOVERFLOW; - kfree(obj); + fan->fan_trip_granularity = obj->integer.value; - return ret; + return 0; } static int acpi_fan_dsm_set_trip_points(struct device *dev, u64 upper, u64 lower) @@ -392,7 +372,7 @@ static int acpi_fan_dsm_set_trip_points(struct device *dev, u64 upper, u64 lower obj = acpi_evaluate_dsm(fan->adev->handle, &acpi_fan_microsoft_guid, 0, ACPI_FAN_DSM_SET_TRIP_POINTS, &in); - kfree(obj); + ACPI_FREE(obj); return 0; } diff --git a/include/linux/acpi.h b/include/linux/acpi.h index ddacac812094..99d7aa7afaa1 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -29,6 +29,8 @@ struct irq_domain_ops; #ifdef CONFIG_ACPI +DEFINE_FREE(acpi_object_free, union acpi_object *, if (_T) ACPI_FREE(_T)); + #include #include #include From dbb3d99ee95c32711950850ddd33eb22a6042eb2 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 25 Sep 2026 07:35:10 -0700 Subject: [PATCH 69/90] ACPICA: Fix memory leak in acpi_ds_create_field() acpi_ds_create_field() allocates internal_pcc_buffer for operation regions in the PCC address space. The function runs once per field, so a second field on the same region overwrites the pointer and orphans the previous buffer. The matching ACPI_FREE() in acpi_ut_delete_internal_obj() only ever sees the last one. An arm64 platform whose SSDT declares regions with multiple fields each leaks two buffers at boot, as detected by kmemleak: unreferenced object 0xffff000051e65400 (size 192): comm "swapper/0", pid 1, jiffies 4294668436 backtrace (crc 0): __kmalloc_noprof acpi_ds_create_field acpi_ds_load2_end_op acpi_ds_exec_end_op acpi_ps_parse_loop acpi_ps_parse_aml acpi_ns_load_table acpi_load_tables acpi_init Do not allocate a region if it was previously allocated before, preserving the current design, but, not leaking memory. Fixes: aa6ec56b574d ("ACPICA: ACPI 6.3: add PCC operation region support for AML interpreter") Link: https://github.com/open-acpica/acpica/commit/c71849d476bb5f97ca1867849e893368e12a69e6 Signed-off-by: Breno Leitao [ rjw: Subject tweak, fixed the Fixes: tag ] Link: https://patch.msgid.link/20260925-acpica-pcc-field-leak-v2-1-e37008e715af@debian.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpica/dsfield.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/acpica/dsfield.c b/drivers/acpi/acpica/dsfield.c index d7d56cc600d3..f7e503bf57c8 100644 --- a/drivers/acpi/acpica/dsfield.c +++ b/drivers/acpi/acpica/dsfield.c @@ -522,7 +522,8 @@ acpi_ds_create_field(union acpi_parse_object *op, } if (info.region_node->object->region.space_id == - ACPI_ADR_SPACE_PLATFORM_COMM) { + ACPI_ADR_SPACE_PLATFORM_COMM && + !region_node->object->field.internal_pcc_buffer) { region_node->object->field.internal_pcc_buffer = ACPI_ALLOCATE_ZEROED(info.region_node->object->region. length); From 888dab4b04ea8725168c6d50d0bfb8057fccca18 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 22 Sep 2026 15:42:59 +0100 Subject: [PATCH 70/90] ACPICA: Fix PCC OperationRegion command offsets ACPI 6.3, section 5.5.2.4.7.3, states that the PCC Operation Region is associated with the region of shared memory that follows the PCC signature. The generic and extended PCC shared memory layouts include the 4-byte signature at offset 0, so their raw shared memory COMMAND fields are at offsets 4 and 12 respectively. Since AML field offsets for the PCC OperationRegion are relative to the region after that signature, ACPICA must look for those COMMAND fields at OperationRegion offsets 0 and 8. Adjust the generic and master subspace command checks to use those OperationRegion-relative offsets. Otherwise writes to the COMMAND field can fail to invoke the PCC address space handler at the offset described by the PCC OperationRegion definition. Link: https://github.com/open-acpica/acpica/pull/1205 Fixes: aa6ec56b574d ("ACPICA: ACPI 6.3: add PCC operation region support for AML interpreter") Signed-off-by: Sudeep Holla [ rjw: Fixed the Fixes: tag ] Link: https://patch.msgid.link/20260922144302.3847593-2-sudeep.holla@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpica/exfield.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/acpica/exfield.c b/drivers/acpi/acpica/exfield.c index 9a55524ed8f4..50317fe0cbd5 100644 --- a/drivers/acpi/acpica/exfield.c +++ b/drivers/acpi/acpica/exfield.c @@ -45,12 +45,13 @@ static const u8 acpi_protocol_lengths[] = { /* * The following macros determine a given offset is a COMD field. - * According to the specification, generic subspaces (types 0-2) contains a - * 2-byte COMD field at offset 4 and master subspaces (type 3) contains a 4-byte - * COMD field starting at offset 12. + * According to the specification, the PCC OperationRegion begins after + * the PCC signature. The raw shared memory COMD offsets of 4 for generic + * subspaces (types 0-2) and 12 for master subspaces (type 3) therefore + * appear at OperationRegion offsets 0 and 8. */ -#define GENERIC_SUBSPACE_COMMAND(a) (4 == a || a == 5) -#define MASTER_SUBSPACE_COMMAND(a) (12 <= a && a <= 15) +#define GENERIC_SUBSPACE_COMMAND(a) (0 == a || a == 1) +#define MASTER_SUBSPACE_COMMAND(a) (8 <= a && a <= 11) /******************************************************************************* * From 46c263c213876bdc2297ef61aa90f0b210ebac73 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 22 Sep 2026 15:43:00 +0100 Subject: [PATCH 71/90] ACPI: PCC: Preserve shared memory signature in OpRegion handler ACPI 6.3 introduced PCC OperationRegions. Section 5.5.2.4.7.3, "Declaring message fields within a PCC OperationRegion", states that, for all PCC subspace types, the PCC Operation Region pertains to the region of PCC subspace that succeeds the PCC signature. Its length therefore excludes the 4-byte signature. The PCC address space handler currently copies the OperationRegion buffer to and from the start of the shared memory. This overwrites or exposes the platform-populated signature and omits the final four bytes of the OperationRegion. Offset the copies by the size of the signature and reject an OperationRegion that does not fit in the remaining shared memory. The example added by commit 77e2a04745ff ("ACPI: PCC: Implement OperationRegion handler for the PCC Type 3 subtype") incorrectly included the signature as an OperationRegion field. A Type 3 OperationRegion containing a 100-byte communication area is declared as follows: OperationRegion (PFRM, PCC, 2, 0x70) Field (PFRM, ByteAcc, NoLock, Preserve) { FLGS, 32, LEN, 32, CMD, 32, DATA, 800 } This contains three 32-bit fields followed by a 100-byte DATA field. The fields correspond to the Type 3 shared memory layout in ACPI 6.3, Table 14-372, after excluding its 4-byte signature. Fixes: 77e2a04745ff ("ACPI: PCC: Implement OperationRegion handler for the PCC Type 3 subtype") Reviewed-by: Huisong Li Signed-off-by: Sudeep Holla Link: https://patch.msgid.link/20260922144302.3847593-3-sudeep.holla@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_pcc.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/acpi_pcc.c b/drivers/acpi/acpi_pcc.c index 438c67189511..9881c9ee293d 100644 --- a/drivers/acpi/acpi_pcc.c +++ b/drivers/acpi/acpi_pcc.c @@ -28,6 +28,7 @@ * to PCC commands */ #define PCC_CMD_WAIT_RETRIES_NUM 500ULL +#define PCC_SIGNATURE_SIZE sizeof(u32) struct pcc_data { struct pcc_mbox_chan *pcc_chan; @@ -74,6 +75,14 @@ acpi_pcc_address_space_setup(acpi_handle region_handle, u32 function, } pcc_chan = data->pcc_chan; + if (pcc_chan->shmem_size < PCC_SIGNATURE_SIZE || + ctx->length > pcc_chan->shmem_size - PCC_SIGNATURE_SIZE) { + pr_err("PCC channel-%d shared memory is too small.\n", + ctx->subspace_id); + ret = AE_AML_REGION_LIMIT; + goto err_free_channel; + } + if (!pcc_chan->mchan->mbox->txdone_irq) { pr_err("This channel-%d does not support interrupt.\n", ctx->subspace_id); @@ -97,14 +106,17 @@ acpi_pcc_address_space_handler(u32 function, acpi_physical_address addr, u32 bits, acpi_integer *value, void *handler_context, void *region_context) { - int ret; struct pcc_data *data = region_context; + void __iomem *pcc_opregion; u64 usecs_lat; + int ret; + + pcc_opregion = data->pcc_chan->shmem + PCC_SIGNATURE_SIZE; reinit_completion(&data->done); - /* Write to Shared Memory */ - memcpy_toio(data->pcc_chan->shmem, (void *)value, data->ctx.length); + /* Write to the PCC OperationRegion after the shared memory signature. */ + memcpy_toio(pcc_opregion, (void *)value, data->ctx.length); ret = mbox_send_message(data->pcc_chan->mchan, NULL); if (ret < 0) @@ -125,7 +137,7 @@ acpi_pcc_address_space_handler(u32 function, acpi_physical_address addr, mbox_chan_txdone(data->pcc_chan->mchan, ret); - memcpy_fromio(value, data->pcc_chan->shmem, data->ctx.length); + memcpy_fromio(value, pcc_opregion, data->ctx.length); return AE_OK; } From fa9c7ee692988edddc1da74bc5257d53dd3c7130 Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 22 Sep 2026 15:43:01 +0100 Subject: [PATCH 72/90] ACPI: PCC: Free channel on OpRegion deactivation ACPICA calls the address space setup callback with ACPI_REGION_DEACTIVATE when a PCC OperationRegion is torn down. The PCC setup callback currently allocates a fresh pcc_data and requests the mailbox channel before looking at the function argument. If ACPICA deactivates a region, this can leave the existing region context and mailbox channel unreleased, and may also request a channel during teardown. Handle ACPI_REGION_DEACTIVATE before allocation. Free the PCC mailbox channel, release the region context and clear the context pointer. Fixes: 77e2a04745ff ("ACPI: PCC: Implement OperationRegion handler for the PCC Type 3 subtype") Reviewed-by: Huisong Li Signed-off-by: Sudeep Holla Link: https://patch.msgid.link/20260922144302.3847593-4-sudeep.holla@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_pcc.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/acpi/acpi_pcc.c b/drivers/acpi/acpi_pcc.c index 9881c9ee293d..57d13b25c1d6 100644 --- a/drivers/acpi/acpi_pcc.c +++ b/drivers/acpi/acpi_pcc.c @@ -55,6 +55,19 @@ acpi_pcc_address_space_setup(acpi_handle region_handle, u32 function, struct pcc_mbox_chan *pcc_chan; acpi_status ret; + if (function == ACPI_REGION_DEACTIVATE) { + data = *region_context; + if (data) { + pcc_mbox_free_channel(data->pcc_chan); + kfree(data); + *region_context = NULL; + } + return AE_OK; + } + + if (function != ACPI_REGION_ACTIVATE) + return AE_BAD_PARAMETER; + data = kzalloc_obj(*data); if (!data) return AE_NO_MEMORY; From c0232f00f703a409bb6e6080d94f6dc7fdebe5ca Mon Sep 17 00:00:00 2001 From: Sudeep Holla Date: Tue, 22 Sep 2026 15:43:02 +0100 Subject: [PATCH 73/90] ACPI: PCC: Cache OpRegion command timeout The PCC OperationRegion handler computes the same command completion wait timeout each time it sends a command. The timeout is derived from static channel properties, so compute it once when the PCC channel is set up and store the millisecond value in the mailbox client timeout field. Use the cached timeout when waiting for the OperationRegion command to complete. This keeps the timeout calculation in one place and avoids recomputing it for every command. Reviewed-by: Huisong Li Signed-off-by: Sudeep Holla Link: https://patch.msgid.link/20260922144302.3847593-5-sudeep.holla@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_pcc.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/acpi_pcc.c b/drivers/acpi/acpi_pcc.c index 57d13b25c1d6..345f233d77cd 100644 --- a/drivers/acpi/acpi_pcc.c +++ b/drivers/acpi/acpi_pcc.c @@ -50,10 +50,11 @@ static acpi_status acpi_pcc_address_space_setup(acpi_handle region_handle, u32 function, void *handler_context, void **region_context) { - struct pcc_data *data; struct acpi_pcc_info *ctx = handler_context; struct pcc_mbox_chan *pcc_chan; + struct pcc_data *data; acpi_status ret; + u64 usecs_lat; if (function == ACPI_REGION_DEACTIVATE) { data = *region_context; @@ -103,6 +104,16 @@ acpi_pcc_address_space_setup(acpi_handle region_handle, u32 function, goto err_free_channel; } + /* + * pcc_chan->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 = PCC_CMD_WAIT_RETRIES_NUM * pcc_chan->latency; + data->cl.tx_tout = DIV_ROUND_UP_ULL(usecs_lat, 1000); + if (!data->cl.tx_tout) + data->cl.tx_tout = 1; + *region_context = data; return AE_OK; @@ -121,7 +132,6 @@ acpi_pcc_address_space_handler(u32 function, acpi_physical_address addr, { struct pcc_data *data = region_context; void __iomem *pcc_opregion; - u64 usecs_lat; int ret; pcc_opregion = data->pcc_chan->shmem + PCC_SIGNATURE_SIZE; @@ -135,14 +145,8 @@ acpi_pcc_address_space_handler(u32 function, acpi_physical_address addr, if (ret < 0) return AE_ERROR; - /* - * pcc_chan->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 = PCC_CMD_WAIT_RETRIES_NUM * data->pcc_chan->latency; ret = wait_for_completion_timeout(&data->done, - usecs_to_jiffies(usecs_lat)); + msecs_to_jiffies(data->cl.tx_tout)); if (ret == 0) { pr_err("PCC command executed timeout!\n"); return AE_TIME; From 57c59c0bda8b19d8cbb877f61dbc8372e5f97749 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 25 Sep 2026 11:23:35 +0200 Subject: [PATCH 74/90] driver core/ACPI: Introduce companion_bus_register() The ACPI bus type does not allow drivers to be matched to devices, so the sysfs attributes related to drivers created for it are useless and their existence is confusing. Moreover, it is better to prevent drivers from being registered and looked up for a bus like that. To allow skipping the creation of those sysfs attributes and preventing driver registration and lookup for the ACPI bus type, introduce a "companion" bus type concept and add a special registration function for registering "companion" bus types, companion_bus_register(). The "drivers" directory under the ACPI bus type is still needed because there are versions of systemd that depend on it [1]. Link: https://lore.kernel.org/linux-acpi/SN6PR02MB41575266A4580339E186E5D9D4812@SN6PR02MB4157.namprd02.prod.outlook.com/ [1] Signed-off-by: Rafael J. Wysocki Acked-by: Greg Kroah-Hartman Reviewed-by: Danilo Krummrich Tested-by: Michael Kelley Reviewed-by: Michael Kelley [ rjw: Add comment regarding drivers_kset creation in bus_register_internal() ] Link: https://patch.msgid.link/12985239.O9o76ZdvQC@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/bus.c | 8 +---- drivers/base/base.h | 2 ++ drivers/base/bus.c | 67 ++++++++++++++++++++++++++++++-------- include/linux/device/bus.h | 1 + 4 files changed, 58 insertions(+), 20 deletions(-) diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index 808c6746be14..48f0b4809f94 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -1120,11 +1120,6 @@ EXPORT_SYMBOL_GPL(acpi_driver_match_device); ACPI Bus operations -------------------------------------------------------------------------- */ -static int acpi_bus_match(struct device *dev, const struct device_driver *drv) -{ - return 0; -} - static int acpi_device_uevent(const struct device *dev, struct kobj_uevent_env *env) { return __acpi_device_uevent_modalias(to_acpi_device(dev), env); @@ -1132,7 +1127,6 @@ static int acpi_device_uevent(const struct device *dev, struct kobj_uevent_env * const struct bus_type acpi_bus_type = { .name = "acpi", - .match = acpi_bus_match, .uevent = acpi_device_uevent, }; @@ -1451,7 +1445,7 @@ static int __init acpi_bus_init(void) */ acpi_root_dir = proc_mkdir(ACPI_BUS_FILE_ROOT, NULL); - result = bus_register(&acpi_bus_type); + result = companion_bus_register(&acpi_bus_type); if (!result) return 0; diff --git a/drivers/base/base.h b/drivers/base/base.h index a5b7abc10ff0..21c7d244f8aa 100644 --- a/drivers/base/base.h +++ b/drivers/base/base.h @@ -27,6 +27,7 @@ * @drivers_autoprobe: gate whether new devices are automatically attached to * registered drivers, or new drivers automatically attach * to existing devices. + * @no_drivers: gate whether drivers can be registered. * @bus: pointer back to the struct bus_type that this structure is associated * with. * @dev_root: Default device to use as the parent. @@ -51,6 +52,7 @@ struct subsys_private { struct klist klist_drivers; struct blocking_notifier_head bus_notifier; unsigned int drivers_autoprobe:1; + unsigned int no_drivers:1; const struct bus_type *bus; struct device *dev_root; diff --git a/drivers/base/bus.c b/drivers/base/bus.c index d17bd91490ee..d89333806a4e 100644 --- a/drivers/base/bus.c +++ b/drivers/base/bus.c @@ -738,6 +738,11 @@ int bus_add_driver(struct device_driver *drv) if (!sp) return -EINVAL; + if (sp->no_drivers) { + error = -ENXIO; + goto out_put_bus; + } + /* * Reference in sp is now incremented and will be dropped when * the driver is removed from the bus @@ -930,15 +935,7 @@ static ssize_t bus_uevent_store(const struct bus_type *bus, static struct bus_attribute bus_attr_uevent = __ATTR(uevent, 0200, NULL, bus_uevent_store); -/** - * bus_register - register a driver-core subsystem - * @bus: bus to register - * - * Once we have that, we register the bus with the kobject - * infrastructure, then register the children subsystems it has: - * the devices and drivers that belong to the subsystem. - */ -int bus_register(const struct bus_type *bus) +static int bus_register_internal(const struct bus_type *bus, bool use_drivers) { int retval; struct subsys_private *priv; @@ -960,7 +957,8 @@ int bus_register(const struct bus_type *bus) bus_kobj->kset = bus_kset; bus_kobj->ktype = &bus_ktype; - priv->drivers_autoprobe = 1; + priv->drivers_autoprobe = use_drivers; + priv->no_drivers = !use_drivers; retval = kset_register(&priv->subsys); if (retval) @@ -976,6 +974,10 @@ int bus_register(const struct bus_type *bus) goto bus_devices_fail; } + /* + * Create the drivers kset even if use_drivers is unset to prevent some + * older versions of systemd from failing. + */ priv->drivers_kset = kset_create_and_add("drivers", NULL, bus_kobj); if (!priv->drivers_kset) { retval = -ENOMEM; @@ -989,9 +991,11 @@ int bus_register(const struct bus_type *bus) klist_init(&priv->klist_devices, klist_devices_get, klist_devices_put); klist_init(&priv->klist_drivers, NULL, NULL); - retval = add_probe_files(bus); - if (retval) - goto bus_probe_files_fail; + if (use_drivers) { + retval = add_probe_files(bus); + if (retval) + goto bus_probe_files_fail; + } retval = sysfs_create_groups(bus_kobj, bus->bus_groups); if (retval) @@ -1016,8 +1020,40 @@ int bus_register(const struct bus_type *bus) kfree(priv); return retval; } + +/** + * bus_register - register a driver-core subsystem + * @bus: bus to register + * + * Once we have that, we register the bus with the kobject + * infrastructure, then register the children subsystems it has: + * the devices and drivers that belong to the subsystem. + */ +int bus_register(const struct bus_type *bus) +{ + return bus_register_internal(bus, true); +} EXPORT_SYMBOL_GPL(bus_register); +/** + * companion_bus_register - register a companion bus type + * @bus: companion bus to register + * + * A companion bus is a bus without drivers. Devices that belong to it can be + * bound to other devices as their "companions" and represent interfaces that + * can be used by the drivers of those other devices. They may also be used for + * the enumeration of those other devices. + * + * The ACPI bus is a specific example of a companion bus. + * + * Registering a companion bus is like registering a regular bus except that it + * skips the creation of sysfs interfaces related to drivers for @bus. + */ +int companion_bus_register(const struct bus_type *bus) +{ + return bus_register_internal(bus, false); +} + /** * bus_unregister - remove a bus from the system * @bus: bus. @@ -1415,6 +1451,11 @@ struct device_driver *driver_find(const char *name, const struct bus_type *bus) if (!sp) return NULL; + if (sp->no_drivers) { + subsys_put(sp); + return NULL; + } + k = kset_find_obj(sp->drivers_kset, name); subsys_put(sp); if (!k) diff --git a/include/linux/device/bus.h b/include/linux/device/bus.h index a38f7229b8f4..89e156b9acb5 100644 --- a/include/linux/device/bus.h +++ b/include/linux/device/bus.h @@ -113,6 +113,7 @@ struct bus_type { bool need_parent_lock; }; +int __must_check companion_bus_register(const struct bus_type *bus); int __must_check bus_register(const struct bus_type *bus); void bus_unregister(const struct bus_type *bus); From 62d6cb1f7de0f88c6f13415e0cdb413b09cdf348 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Fri, 25 Sep 2026 11:26:24 -0500 Subject: [PATCH 75/90] ACPI: Add stub for acpi_find_child_device() Provide a NULL-returning stub when CONFIG_ACPI is disabled so callers can be compiled without ACPI support. Reported-by: Arnd Bergmann Closes: https://lore.kernel.org/linux-sound/20260925131104.118993-1-arnd@kernel.org/ Suggested-by: Arnd Bergmann Signed-off-by: Mario Limonciello Link: https://patch.msgid.link/20260925162628.4142365-1-mario.limonciello@amd.com Signed-off-by: Rafael J. Wysocki --- include/acpi/acpi_bus.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index dca4df750c54..c060eb8f4556 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -943,6 +943,13 @@ u32 arch_acpi_add_auto_dep(acpi_handle handle); #else /* CONFIG_ACPI */ +static inline struct acpi_device * +acpi_find_child_device(struct acpi_device *parent, u64 address, + bool check_children) +{ + return NULL; +} + static inline bool acpi_has_method(acpi_handle handle, char *name) { return false; From c237f6b429b914905bb379de4089d661fb370446 Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Mon, 7 Sep 2026 12:17:25 +0530 Subject: [PATCH 76/90] ACPI: NUMA: fix typo in log message Correct the misspelling in a message the user sees, reported by scripts/checkpatch.pl using the list in scripts/spelling.txt. Only the message text changes, no code changes. Signed-off-by: Hemanth Selam Reviewed-by: Randy Dunlap Link: https://patch.msgid.link/20260907064725.10686-6-hemanth.selam@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/numa/srat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/numa/srat.c b/drivers/acpi/numa/srat.c index 5c407dc6401e..f5616830f5a9 100644 --- a/drivers/acpi/numa/srat.c +++ b/drivers/acpi/numa/srat.c @@ -111,7 +111,7 @@ int __init fix_pxm_node_maps(int max_nid) for (j = 0; j <= max_nid; j++) { if ((emu_nid_to_phys[j] == i) && WARN(node_to_pxm_map_copy[j] != PXM_INVAL, - "Node %d is already binded to PXM %d\n", + "Node %d is already bound to PXM %d\n", j, node_to_pxm_map_copy[j])) return -1; if (emu_nid_to_phys[j] == i) { From 1a58c47fae2a3982b38ec6348c739a7c60833189 Mon Sep 17 00:00:00 2001 From: Dave Jiang Date: Fri, 11 Sep 2026 09:47:05 -0700 Subject: [PATCH 77/90] PCI/AER: Map a raw AER Capability image field by field struct aer_capability_regs is not the hardware layout: the embedded struct pcie_tlp_log spans 60 bytes where the Header Log it stands in for is 16, so a flat copy misplaces everything behind it. extlog_print_pcie(), ghes_handle_aer() and cxl_rch_get_aer_info() work around that by stopping at the Header Log, dropping everything past offset 44. Flit mode is never decoded either: the Flit bit and Logged TLP Size sit at offset 0x18, unread, so the Flit DWORDs at 0x38 are lost and the log prints as non-Flit. Add aer_cap_regs_unpack() to map the registers individually, taking the TLP Log layout from the Flit bit as pcie_read_tlp_log() does. Clamp the logged length: Logged TLP Size is 5 bits wide, so an untrusted value reaches 31 where dw[] holds 14 and pcie_print_tlp_log() walks header_len unbounded. Take the image little-endian, as both sources are: CPER by definition, PCIe registers on the wire. Read it with get_unaligned_le32(), since a CPER section sits wherever firmware put it, and take the two source IDs out of the Error Source Identification register by bit position rather than byte offset. cxl_rch_get_aer_info() uses readl(), so it hands back __le32. Convert all three callers. cxl_rch_get_aer_info() now reads the whole MMIO block, so size its ioremap by the hardware registers (96 bytes) rather than by sizeof(struct aer_capability_regs) (100). Shrinking the mapping is safe: the read it replaces stopped at 44 bytes. Reported-by: sashiko-bot@kernel.org Closes: https://sashiko.dev/#/patchset/20260904172337.1409775-1-dave.jiang@intel.com?part=5 Link: https://lore.kernel.org/linux-cxl/CAJZ5v0hDv11cPuztPZsaDd7uwD_49KznJy=tzuRO+dZc=CnAEQ@mail.gmail.com/ Assisted-by: LLM Signed-off-by: Dave Jiang Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20260911164705.841407-1-dave.jiang@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_extlog.c | 14 +----- drivers/acpi/apei/ghes.c | 15 +------ drivers/cxl/core/ras_rch.c | 27 ++++-------- drivers/pci/pcie/tlp.c | 88 ++++++++++++++++++++++++++++++++++++++ include/linux/aer.h | 9 ++++ 5 files changed, 107 insertions(+), 46 deletions(-) diff --git a/drivers/acpi/acpi_extlog.c b/drivers/acpi/acpi_extlog.c index 9e61354a807b..bf5f7b1a9aea 100644 --- a/drivers/acpi/acpi_extlog.c +++ b/drivers/acpi/acpi_extlog.c @@ -156,19 +156,7 @@ static void extlog_print_pcie(struct cper_sec_pcie *pcie_err, aer_severity = cper_severity_to_aer(severity); - /* - * struct pcie_tlp_log is larger than the hardware layout, so aer_info - * only maps onto the struct up to the four Header Log DWORDs. Copy that - * much, then place the TLP Prefix Log from where the hardware keeps it. - * Everything else stays zero: nothing reads root_command, root_status or - * the error source IDs, and header_len and flit are software-only. - */ - memcpy(&aer_regs, pcie_err->aer_info, - offsetof(struct aer_capability_regs, header_log) + - PCIE_STD_NUM_TLP_HEADERLOG * sizeof(u32)); - memcpy(aer_regs.header_log.prefix, - pcie_err->aer_info + PCI_ERR_PREFIX_LOG, - sizeof(aer_regs.header_log.prefix)); + aer_cap_regs_unpack(&aer_regs, pcie_err->aer_info, sizeof(pcie_err->aer_info)); domain = pcie_err->device_id.segment; bus = pcie_err->device_id.bus; diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 08c985e729d6..ade1cc8c9f8d 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -668,20 +668,7 @@ static void ghes_handle_aer(struct acpi_hest_generic_data *gdata) if (!aer_info) return; - /* - * Map aer_info onto the struct as extlog_print_pcie() does: - * copy up to the four Header Log DWORDs, then place the TLP - * Prefix Log from where the hardware keeps it. The rest stays - * zero, so firmware cannot drive the pcie_print_tlp_log() loop - * over dw[] out of bounds. - */ - memset(aer_info, 0, sizeof(struct aer_capability_regs)); - memcpy(aer_info, pcie_err->aer_info, - offsetof(struct aer_capability_regs, header_log) + - PCIE_STD_NUM_TLP_HEADERLOG * sizeof(u32)); - memcpy(aer_info->header_log.prefix, - pcie_err->aer_info + PCI_ERR_PREFIX_LOG, - sizeof(aer_info->header_log.prefix)); + aer_cap_regs_unpack(aer_info, pcie_err->aer_info, sizeof(pcie_err->aer_info)); aer_recover_queue(pcie_err->device_id.segment, pcie_err->device_id.bus, diff --git a/drivers/cxl/core/ras_rch.c b/drivers/cxl/core/ras_rch.c index e0e01aa5eba6..50abbdc80722 100644 --- a/drivers/cxl/core/ras_rch.c +++ b/drivers/cxl/core/ras_rch.c @@ -19,7 +19,7 @@ void cxl_dport_map_rch_aer(struct cxl_dport *dport) aer_phys = aer_cap + dport->rcrb.base; dport->regs.dport_aer = devm_cxl_iomap_block(host, aer_phys, - sizeof(struct aer_capability_regs)); + PCIE_AER_CAP_HW_SIZE); } } @@ -58,31 +58,20 @@ void cxl_disable_rch_root_ints(struct cxl_dport *dport) static bool cxl_rch_get_aer_info(void __iomem *aer_base, struct aer_capability_regs *aer_regs) { - /* - * Bound the copy to the physically-defined AER registers (header - * through the 16-byte Header Log). struct aer_capability_regs is a - * software layout whose embedded struct pcie_tlp_log is larger than - * the on-wire AER capability; copying sizeof(*aer_regs) would - * over-read the RCRB-mapped MMIO block. - */ - int read_cnt = (PCI_ERR_HEADER_LOG + 16) / sizeof(u32); - u32 *aer_regs_buf = (u32 *)aer_regs; - int n; + /* A flat copy cannot fill the struct; aer_cap_regs_unpack() places it. */ + __le32 raw[PCIE_AER_CAP_HW_SIZE / sizeof(__le32)]; if (!aer_base) return false; /* - * Zero the destination so the software-only tail fields - * (e.g. header_log.header_len) are deterministic rather than - * left as uninitialized stack, which could drive a bogus loop - * length in pcie_print_tlp_log(). + * Use readl() to guarantee 32-bit accesses; it returns host order, so + * put the registers back little-endian for aer_cap_regs_unpack(). */ - memset(aer_regs, 0, sizeof(*aer_regs)); + for (int n = 0; n < ARRAY_SIZE(raw); n++) + raw[n] = cpu_to_le32(readl(aer_base + n * sizeof(u32))); - /* Use readl() to guarantee 32-bit accesses */ - for (n = 0; n < read_cnt; n++) - aer_regs_buf[n] = readl(aer_base + n * sizeof(u32)); + aer_cap_regs_unpack(aer_regs, raw, sizeof(raw)); writel(aer_regs->uncor_status, aer_base + PCI_ERR_UNCOR_STATUS); writel(aer_regs->cor_status, aer_base + PCI_ERR_COR_STATUS); diff --git a/drivers/pci/pcie/tlp.c b/drivers/pci/pcie/tlp.c index 71f8fc9ea2ed..7f94a09ac7c7 100644 --- a/drivers/pci/pcie/tlp.c +++ b/drivers/pci/pcie/tlp.c @@ -8,8 +8,11 @@ #include #include #include +#include +#include #include #include +#include #include "../pci.h" @@ -92,6 +95,91 @@ int pcie_read_tlp_log(struct pci_dev *dev, int where, int where2, return 0; } +/* The Prefix Log registers hold dw[4..13] in Flit mode. */ +static_assert((PCI_ERR_PREFIX_LOG + + (PCIE_STD_MAX_TLP_HEADERLOG - PCIE_STD_NUM_TLP_HEADERLOG) * + sizeof(u32)) == PCIE_AER_CAP_HW_SIZE); + +/** + * aer_cap_regs_unpack - Convert a raw AER Capability image to the kernel layout + * @regs: Destination, fully initialised + * @raw: AER Capability register block in hardware order, little-endian + * @raw_len: Bytes readable at @raw + * + * struct aer_capability_regs is not the hardware layout: struct pcie_tlp_log + * spans 60 bytes where the Header Log it stands in for is 16, so a flat copy + * misplaces everything behind it. Map the registers one by one instead, + * taking the TLP Log layout from the Flit bit as pcie_read_tlp_log() does. + * + * @raw is not necessarily aligned. Registers past @raw_len are left zero, so + * a short image cannot be read past. + */ +void aer_cap_regs_unpack(struct aer_capability_regs *regs, const void *raw, + size_t raw_len) +{ + unsigned int i, tlp_len; + bool flit; + + memset(regs, 0, sizeof(*regs)); + + if (raw_len < PCI_ERR_HEADER_LOG) + return; + + /* The Extended Capability Header, at offset 0, has no define */ + regs->header = get_unaligned_le32(raw); + regs->uncor_status = get_unaligned_le32(raw + PCI_ERR_UNCOR_STATUS); + regs->uncor_mask = get_unaligned_le32(raw + PCI_ERR_UNCOR_MASK); + regs->uncor_severity = get_unaligned_le32(raw + PCI_ERR_UNCOR_SEVER); + regs->cor_status = get_unaligned_le32(raw + PCI_ERR_COR_STATUS); + regs->cor_mask = get_unaligned_le32(raw + PCI_ERR_COR_MASK); + regs->cap_control = get_unaligned_le32(raw + PCI_ERR_CAP); + + flit = FIELD_GET(PCI_ERR_CAP_TLP_LOG_FLIT, regs->cap_control); + if (flit) { + tlp_len = FIELD_GET(PCI_ERR_CAP_TLP_LOG_SIZE, regs->cap_control); + } else { + /* + * dw[4..7] alias the Prefix Log. Take all four whatever + * eetlp_prefix_max says; pcie_print_tlp_log() stops at the + * first zero one. + */ + tlp_len = PCIE_STD_NUM_TLP_HEADERLOG + PCIE_STD_MAX_TLP_PREFIXLOG; + } + + tlp_len = min(tlp_len, ARRAY_SIZE(regs->header_log.dw)); + + for (i = 0; i < tlp_len; i++) { + unsigned int off; + + if (i < PCIE_STD_NUM_TLP_HEADERLOG) + off = PCI_ERR_HEADER_LOG + i * sizeof(u32); + else + off = PCI_ERR_PREFIX_LOG + + (i - PCIE_STD_NUM_TLP_HEADERLOG) * sizeof(u32); + + if (off + sizeof(u32) > raw_len) + break; + regs->header_log.dw[i] = get_unaligned_le32(raw + off); + } + + /* @i may be short of tlp_len; non-Flit needs the TLP parsed, so cap at 4 */ + regs->header_log.header_len = flit ? i : min(i, PCIE_STD_NUM_TLP_HEADERLOG); + regs->header_log.flit = flit; + + if (raw_len >= PCI_ERR_ROOT_COMMAND + sizeof(u32)) + regs->root_command = get_unaligned_le32(raw + PCI_ERR_ROOT_COMMAND); + if (raw_len >= PCI_ERR_ROOT_STATUS + sizeof(u32)) + regs->root_status = get_unaligned_le32(raw + PCI_ERR_ROOT_STATUS); + if (raw_len >= PCI_ERR_ROOT_ERR_SRC + sizeof(u32)) { + u32 src = get_unaligned_le32(raw + PCI_ERR_ROOT_ERR_SRC); + + /* One register: ERR_COR in 15:0, ERR_FATAL/NONFATAL in 31:16 */ + regs->cor_err_source = src; + regs->uncor_err_source = src >> 16; + } +} +EXPORT_SYMBOL_GPL(aer_cap_regs_unpack); + #define EE_PREFIX_STR " E-E Prefixes:" /** diff --git a/include/linux/aer.h b/include/linux/aer.h index df0f5c382286..043ee76b6f53 100644 --- a/include/linux/aer.h +++ b/include/linux/aer.h @@ -24,6 +24,13 @@ #define PCIE_STD_MAX_TLP_PREFIXLOG 4 #define PCIE_STD_MAX_TLP_HEADERLOG (PCIE_STD_NUM_TLP_HEADERLOG + 10) +/* + * Size of the AER Capability register block in hardware, long enough for the + * longest Flit mode TLP Log. struct aer_capability_regs below is larger and + * laid out differently; use aer_cap_regs_unpack() to convert. + */ +#define PCIE_AER_CAP_HW_SIZE 96 + struct pci_dev; struct pcie_tlp_log { @@ -68,6 +75,8 @@ static inline void pci_aer_unmask_internal_errors(struct pci_dev *dev) { } void pci_print_aer(struct pci_dev *dev, int aer_severity, struct aer_capability_regs *aer); +void aer_cap_regs_unpack(struct aer_capability_regs *regs, const void *raw, + size_t raw_len); int cper_severity_to_aer(int cper_severity); void aer_recover_queue(int domain, unsigned int bus, unsigned int devfn, int severity, struct aer_capability_regs *aer_regs); From 0d20d54f089eca8cf43ecc9a2bca552d322619da Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 30 Sep 2026 20:43:03 +0200 Subject: [PATCH 78/90] iommu/arm-smmu-v3: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Nicolin Chen Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3636226.QJadu78ljV@rafael.j.wysocki --- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c index 5732f3ba0122..98f105798b89 100644 --- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c @@ -5307,7 +5307,7 @@ static void acpi_smmu_dsdt_probe_tegra241_cmdqv(struct acpi_iort_node *node, adev = acpi_dev_get_first_match_dev("NVDA200C", uid, -1); if (adev) { /* Tegra241 CMDQV driver is responsible for put_device() */ - smmu->impl_dev = get_device(acpi_get_first_physical_node(adev)); + smmu->impl_dev = acpi_bus_get_primary_device(adev); smmu->options |= ARM_SMMU_OPT_TEGRA241_CMDQV; dev_info(smmu->dev, "found companion CMDQV device: %s\n", dev_name(smmu->impl_dev)); From 2f47382b3ea31346b46444389e0520ca3cad2c81 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 30 Sep 2026 20:42:15 +0200 Subject: [PATCH 79/90] ALSA: hda: cs35l41: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Takashi Iwai Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/6200303.MhkbZ0Pkbq@rafael.j.wysocki --- sound/hda/codecs/side-codecs/cs35l41_hda.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sound/hda/codecs/side-codecs/cs35l41_hda.c b/sound/hda/codecs/side-codecs/cs35l41_hda.c index 62296be45c55..32abb71dd401 100644 --- a/sound/hda/codecs/side-codecs/cs35l41_hda.c +++ b/sound/hda/codecs/side-codecs/cs35l41_hda.c @@ -1926,8 +1926,7 @@ static int cs35l41_hda_read_acpi(struct cs35l41_hda *cs35l41, const char *hid, i } cs35l41->dacpi = adev; - struct device *physdev __free(put_device) = - get_device(acpi_get_first_physical_node(adev)); + struct device *physdev __free(put_device) = acpi_bus_get_primary_device(adev); if (!physdev) { acpi_dev_put(adev); return -ENODEV; From b85fdc68f3d9ac545aa523ffdddd46c470a652b8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 30 Sep 2026 20:42:36 +0200 Subject: [PATCH 80/90] ALSA: hda: tas2781: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Acked-by: Takashi Iwai Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/8782185.NyiUUSuA9g@rafael.j.wysocki --- sound/hda/codecs/side-codecs/tas2781_hda_i2c.c | 3 +-- sound/hda/codecs/side-codecs/tas2781_hda_spi.c | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c index 5e5f46c9fb6b..161ca965c63e 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_i2c.c @@ -100,8 +100,7 @@ static int tas2781_read_acpi(struct tasdevice_priv *p, const char *hid) return -ENODEV; } - struct device *physdev __free(put_device) = - get_device(acpi_get_first_physical_node(adev)); + struct device *physdev __free(put_device) = acpi_bus_get_primary_device(adev); ret = acpi_dev_get_resources(adev, &resources, tas2781_get_i2c_res, p); if (ret < 0) { dev_err(p->dev, "Failed to get ACPI resource.\n"); diff --git a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c index da71b57a26ee..f3ba7132d2b8 100644 --- a/sound/hda/codecs/side-codecs/tas2781_hda_spi.c +++ b/sound/hda/codecs/side-codecs/tas2781_hda_spi.c @@ -341,8 +341,7 @@ static int tas2781_read_acpi(struct tas2781_hda *tas_hda, strscpy(p->dev_name, hid, sizeof(p->dev_name)); - struct device *physdev __free(put_device) = - get_device(acpi_get_first_physical_node(adev)); + struct device *physdev __free(put_device) = acpi_bus_get_primary_device(adev); acpi_dev_put(adev); if (!physdev) return -ENODEV; From 4ed5b18e4ebd3c8bfc353ec68c1bfc818f1b9aac Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 30 Sep 2026 20:41:40 +0200 Subject: [PATCH 81/90] efi/dev-path-parser: Switch to use acpi_bus_get_primary_device() Replace acpi_get_first_physical_node() that is slated for removal with acpi_bus_get_primary_device() that takes a reference to the device it is about to return. This addresses a potential use-after-free that may occur if the device returned by acpi_get_first_physical_node() is removed right after dropping its ACPI companion's physical_node_lock in that function. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3974502.kQq0lBPeGt@rafael.j.wysocki --- drivers/firmware/efi/dev-path-parser.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/firmware/efi/dev-path-parser.c b/drivers/firmware/efi/dev-path-parser.c index 13ea141c0def..e0f864cbaa8c 100644 --- a/drivers/firmware/efi/dev-path-parser.c +++ b/drivers/firmware/efi/dev-path-parser.c @@ -37,9 +37,9 @@ static long __init parse_acpi_path(const struct efi_dev_path *node, if (!adev) return -ENODEV; - phys_dev = acpi_get_first_physical_node(adev); + phys_dev = acpi_bus_get_primary_device(adev); if (phys_dev) { - *child = get_device(phys_dev); + *child = phys_dev; acpi_dev_put(adev); } else *child = &adev->dev; From 538076b45429652424801cb127581ed3dc56a0b7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 30 Sep 2026 20:45:01 +0200 Subject: [PATCH 82/90] ACPI: bus: Drop acpi_get_first_physical_node() Fold acpi_get_first_physical_node() into acpi_primary_dev_companion() that is its only remaining caller and drop its signature from the header file. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Sudeep Holla Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/4537440.ejJDZkT8p0@rafael.j.wysocki --- drivers/acpi/bus.c | 20 +++++--------------- include/linux/acpi.h | 7 ------- 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index 808c6746be14..cc6989d88645 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -809,24 +809,14 @@ struct device *acpi_bus_get_primary_device(struct acpi_device *adev) } EXPORT_SYMBOL_GPL(acpi_bus_get_primary_device); -/** - * acpi_get_first_physical_node - Find first physical node of an ACPI device - * @adev: ACPI device in question - * - * Return: First physical node of ACPI device @adev - */ -struct device *acpi_get_first_physical_node(struct acpi_device *adev) -{ - guard(mutex)(&adev->physical_node_lock); - - return primary_physical_device(adev); -} -EXPORT_SYMBOL_GPL(acpi_get_first_physical_node); - static struct acpi_device *acpi_primary_dev_companion(struct acpi_device *adev, const struct device *dev) { - const struct device *phys_dev = acpi_get_first_physical_node(adev); + const struct device *phys_dev; + + guard(mutex)(&adev->physical_node_lock); + + phys_dev = primary_physical_device(adev); return phys_dev && phys_dev == dev ? adev : NULL; } diff --git a/include/linux/acpi.h b/include/linux/acpi.h index ba5234bd93a2..6f69819f9c9a 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -105,8 +105,6 @@ static inline const char *acpi_dev_name(struct acpi_device *adev) return dev_name(&adev->dev); } -struct device *acpi_get_first_physical_node(struct acpi_device *adev); - enum acpi_irq_model_id { ACPI_IRQ_MODEL_PIC = 0, ACPI_IRQ_MODEL_IOAPIC, @@ -938,11 +936,6 @@ static inline const char *acpi_dev_name(struct acpi_device *adev) return NULL; } -static inline struct device *acpi_get_first_physical_node(struct acpi_device *adev) -{ - return NULL; -} - static inline void acpi_early_init(void) { } static inline void acpi_subsystem_init(void) { } From 3bd2779b6cfdb67a05bc52b86a1d9cd2e31cdcc2 Mon Sep 17 00:00:00 2001 From: Bibo Mao Date: Tue, 22 Sep 2026 20:10:55 +0800 Subject: [PATCH 83/90] ACPICA: actbl2.h: Add bit constants of the Flags field in the IOVT IOMMU structure According to the IOVT specification, define macros for each bit of the Flags field in the IOVT IOMMU structure. Link: https://github.com/open-acpica/acpica/commit/352f750e4cc0 Signed-off-by: Bibo Mao Link: https://patch.msgid.link/20260922121058.972734-2-maobibo@loongson.cn Signed-off-by: Rafael J. Wysocki --- include/acpi/actbl2.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/acpi/actbl2.h b/include/acpi/actbl2.h index baef525367b5..76e4a5bc79b4 100644 --- a/include/acpi/actbl2.h +++ b/include/acpi/actbl2.h @@ -919,6 +919,13 @@ struct acpi_iovt_iommu { u32 device_entry_offset; }; +/* Masks for Flags field above */ +#define ACPI_IOVT_IOMMU_PCI_DEVICE (1<<0) +#define ACPI_IOVT_IOMMU_PXM_VALID (1<<1) +#define ACPI_IOVT_IOMMU_MAGAGE_BY_SEGMENT (1<<2) +#define ACPI_IOVT_IOMMU_HW_CAP_SUPPORT (1<<3) +#define ACPI_IOVT_IOMMU_MSI_INT_BYPASS (1<<4) + struct acpi_iovt_device_entry { u8 type; u8 length; From f341307a14895e5da056fb419a31ef7fb534ab18 Mon Sep 17 00:00:00 2001 From: Breno Leitao Date: Fri, 25 Sep 2026 08:09:11 -0700 Subject: [PATCH 84/90] ACPI: APEI: accumulate the queued flag across error sections ghes_do_proc() walks every error section of a CPER record, and ghes_handle_arm_hw_error() walks every error-info entry of an ARM processor section. Both assign to "queued" instead of OR-ing into it, so only the last iteration counts: a later section or entry that queues no memory-failure work clears the flag an earlier one set. Instead of reassigning the variable at every time, just do an `OR`, so, if there was a single match, it will return "queued" as set. The comment above the check says "If no memory failure work is queued", which is the semantics this patch implements. I.e, only SIGBUS if there are queue was not handled at all. If we want to SIGBUS if any event was not handled/queued at all, we will need something more sophisticated. Fixes: 7f17b4a121d0 ("ACPI: APEI: Kick the memory_failure() queue for synchronous errors") Fixes: ccb5ecdc2dde ("ACPI: APEI: fix synchronous external aborts in user-mode") Cc: All applicable Signed-off-by: Breno Leitao Reviewed-by: Shuai Xue Link: https://patch.msgid.link/20260925-ghes-queued-accumulate-v1-1-df50553957d4@debian.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index ade1cc8c9f8d..663dc2e601cf 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -603,7 +603,7 @@ static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, * and don't filter out 'corrected' error here. */ if (is_cache && has_pa) { - queued = ghes_do_memory_failure(err_info->physical_fault_addr, flags); + queued |= ghes_do_memory_failure(err_info->physical_fault_addr, flags); p += err_info->length; continue; } @@ -1000,11 +1000,11 @@ static void ghes_do_proc(struct ghes *ghes, atomic_notifier_call_chain(&ghes_report_chain, sev, mem_err); arch_apei_report_mem_error(sev, mem_err); - queued = ghes_handle_memory_failure(gdata, sev, sync); + queued |= ghes_handle_memory_failure(gdata, sev, sync); } else if (guid_equal(sec_type, &CPER_SEC_PCIE)) { ghes_handle_aer(gdata); } else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { - queued = ghes_handle_arm_hw_error(gdata, sev, sync); + queued |= ghes_handle_arm_hw_error(gdata, sev, sync); } else if (guid_equal(sec_type, &CPER_SEC_CXL_PROT_ERR)) { struct cxl_cper_sec_prot_err *prot_err = acpi_hest_get_payload(gdata); From 691c33289b4d7a50b6fe1e03918bfbc21d4641c5 Mon Sep 17 00:00:00 2001 From: Jiale Yao Date: Fri, 25 Sep 2026 23:58:46 +0800 Subject: [PATCH 85/90] ACPI: PCI: pci_link: clear driver_data before free acpi_pci_link_remove() frees the link object without clearing the ACPI device's driver_data pointer. The ACPI device survives hot-removal and can later be retrieved by acpi_fetch_acpi_dev(), leaving callers with a dangling pointer. Clear driver_data before freeing the link. Fixes: db89b4f0dbab ("ACPI: catch calls of acpi_driver_data on pointer of wrong type") Signed-off-by: Jiale Yao [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260925155846.2381956-1-yaojiale02@163.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/pci_link.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/acpi/pci_link.c b/drivers/acpi/pci_link.c index e6ed13aee48d..84bfcdf242dd 100644 --- a/drivers/acpi/pci_link.c +++ b/drivers/acpi/pci_link.c @@ -787,6 +787,7 @@ static void acpi_pci_link_remove(struct acpi_device *device) list_del(&link->list); mutex_unlock(&acpi_link_lock); + device->driver_data = NULL; kfree(link); } From 029d11df81278d99d8a0441ff45a5dfdfce0168b Mon Sep 17 00:00:00 2001 From: Vibhor Goel Date: Sat, 3 Oct 2026 22:10:19 +0530 Subject: [PATCH 86/90] ACPI: dock: Retry probing battery docks after hotplug On a ThinkPad T480 booted with the removable battery absent, reinserting that battery can leave BAT1 missing from /sys/class/power_supply even after firmware sends an ACPI add notification. The battery dock platform device already exists, but its driver did not bind while the battery was absent. During hotplug, acpi_bus_scan() updates the ACPI device and attempts to attach it on the ACPI bus. acpi_create_platform_device() does not create another platform device because the existing physical node is still associated with the ACPI device. Consequently, the existing platform device remains unbound. Retry probing the battery dock platform device after hotplug_dock_devices() has refreshed the ACPI state. Restrict this to battery docks. If the platform device is already bound, device_initial_probe() leaves that binding in place. Signed-off-by: Vibhor Goel [ rjw: Add empty code line after a conditional ] Link: https://patch.msgid.link/20261003164019.11195-1-vibhor1102@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/dock.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/acpi/dock.c b/drivers/acpi/dock.c index d7f3a8912e6a..24c5813ca291 100644 --- a/drivers/acpi/dock.c +++ b/drivers/acpi/dock.c @@ -445,6 +445,15 @@ int dock_notify(struct acpi_device *adev, u32 event) break; } hotplug_dock_devices(ds, event); + /* + * The dock platform device already exists when an empty battery + * bay becomes occupied, but its driver may not have bound while + * the battery was absent. Retry after the ACPI scan updates its + * status, since the scan only attaches the ACPI device. + */ + if (ds->flags & DOCK_IS_BAT) + device_initial_probe(&ds->dock_device->dev); + complete_dock(ds); dock_event(ds, event, DOCK_EVENT); acpi_evaluate_lck(ds->handle, 1); From 23ec17231cc6a83bb11d8660db5bfc5336f53a0e Mon Sep 17 00:00:00 2001 From: Hakan Abay Date: Mon, 5 Oct 2026 20:49:39 +0300 Subject: [PATCH 87/90] ACPI: PM: Add unused power resource quirk for Casper Excalibur G900 On the Casper Excalibur G900, the NVIDIA discrete GPU and the second NVMe device become inaccessible when unused ACPI power resources are turned off during initialization. The failure is similar to the THUNDEROBOT ZERO case addressed by commit cd7ef20ba8c6 ("ACPI: PM: Add unused power resource quirk for THUNDEROBOT ZERO"). Without the quirk, the NVIDIA GPU and the second NVMe device are left inaccessible in D3cold and attempts to transition them back to D0 fail. Add a DMI quirk for the affected Casper Excalibur G900 with NLCC 001 mainboard to leave unused ACPI power resources on. With the quirk applied, the second NVMe device remains accessible. The NVIDIA GPU can enter D3cold while idle and subsequently return to D0 for accelerated rendering. Link: https://bugs.kali.org/view.php?id=9891 Signed-off-by: Hakan Abay Link: https://patch.msgid.link/20261005174939.53873-1-hakan.abay84@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/power.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index c6955d1ad29d..34b73f320b18 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -1134,6 +1134,18 @@ static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { } }, + { + /* + * CASPER EXCALIBUR G900 laptop: Keep unused ACPI power + * resources on to prevent the NVIDIA dGPU and NVMe #2 + * from becoming inaccessible. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "CASPER BILGISAYAR SISTEMLERI"), + DMI_MATCH(DMI_PRODUCT_NAME, "EXCALIBUR G900"), + DMI_MATCH(DMI_BOARD_NAME, "NLCC 001"), + }, + }, {} }; From f43cdd75b38a29c96896ed1f7bf9b3ec04a6c3b7 Mon Sep 17 00:00:00 2001 From: Riwen Lu Date: Thu, 8 Oct 2026 10:51:52 +0800 Subject: [PATCH 88/90] ACPI: PM: sleep: Drop unused sleep_no_lps0 define The sleep_no_lps0 stub define in the !CONFIG_ACPI_SLEEP branch of sleep.c has no users left in that file: the LPS0 code consuming it was moved to drivers/acpi/x86/s2idle.c by commit fef98671194b ("ACPI: PM: s2idle: Move x86-specific code to the x86 directory"), which has its own static module parameter of the same name, so the define is no longer used. Drop it. Signed-off-by: Riwen Lu Link: https://patch.msgid.link/20261008025152.153073-1-luriwen@kylinos.cn Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sleep.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index 132a9df98471..a94bac4422d4 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -548,7 +548,6 @@ static void acpi_pm_end(void) acpi_sleep_tts_switch(acpi_target_sleep_state); } #else /* !CONFIG_ACPI_SLEEP */ -#define sleep_no_lps0 (1) #define acpi_target_sleep_state ACPI_STATE_S0 #define acpi_sleep_default_s3 (1) static inline void acpi_sleep_dmi_check(void) {} From 5d76269ee75391812bfdf18df4b2db690d7e264d Mon Sep 17 00:00:00 2001 From: Chitransh Mandhaniya Date: Sat, 10 Oct 2026 18:18:30 +0530 Subject: [PATCH 89/90] ACPI: video: force native backlight on Dell OptiPlex AIO 7410 35W The Dell OptiPlex AIO 7410 35W (board 01KP8D) declares DELL0501 in its ACPI DSDT (status = 0x0F) even though no UART scalar board is physically present. This causes acpi_video_get_backlight_type() to return acpi_backlight_dell_uart, which makes i915 skip registering intel_backlight. The dell-uart-backlight driver then times out talking to the absent board and the probe fails, leaving the machine with zero backlight control. A BIOS update fixes this on some models (e.g. OptiPlex 7420, board 0914RT) by setting DELL0501 _STA to 0, but Dell has not shipped a corrected BIOS for the 7410 even in the latest release (1.37.0, 2026-05-28). Add a DMI quirk to force native backlight on this model so that i915 registers intel_backlight and brightness control works out of the box. Tested on: - OptiPlex AIO 7410 35W (01KP8D, BIOS 1.37.0) -- confirmed acpi_backlight=native produces working intel_backlight (raw, max=96000), DMI quirk has the same effect. - OptiPlex AIO 7420 35W (0914RT, BIOS 1.27.0) -- DELL0501 absent in updated BIOS, intel_backlight works natively (no regression). Signed-off-by: Chitransh Mandhaniya Reviewed-by: Hans de Goede Link: https://patch.msgid.link/20261010124830.116750-1-chitranshmandhaniya@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/video_detect.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index 3037fd18d420..326d4c6d132a 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -883,6 +883,14 @@ static const struct dmi_system_id video_detect_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex 7770 AIO"), }, }, + { + .callback = video_detect_force_native, + /* Dell OptiPlex AIO 7410 35W */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex AIO 7410 35W"), + }, + }, /* * Models which have nvidia-ec-wmi support, but should not use it. From 8eb2754ef96d77d870d6b1ef1e0e8470c892b6b4 Mon Sep 17 00:00:00 2001 From: Krittikun Wongnin Date: Tue, 13 Oct 2026 14:42:45 +0700 Subject: [PATCH 90/90] ACPI: PM: Add Acer Aspire A515-53G to dmi_leave_unused_power_resources_on[] On the Acer Aspire A515-53G (Whiskey Lake + NVIDIA GeForce MX230), the dGPU behind root port RP05 (00:1c.4) falls off the bus during boot and is unusable until the next power cycle: nvidia 0000:02:00.0: Unable to change power state from D3cold to D0, device inaccessible NVRM: The NVIDIA GPU 0000:02:00.0 ... has fallen off the bus The firmware ships two SSDTs which both provide power management for the dGPU: - One SSDT defines \_SB.PCI0.RP05.PC01 together with RP05._PR0/_PR2/_PR3 = { PC01 }. - A second SSDT defines \_SB.PCI0.RP05.PCRP (_STA initially One, _OFF calls \_SB.PCI0.HGOF, which puts the link into L2/L3 and drops the dGPU power enable GPIO) and its own RP05._PR0/_PR2/_PR3/_S0W. The latter fail to load: ACPI BIOS Error (bug): Failure creating named object [\_SB.PCI0.RP05._PR0], AE_ALREADY_EXISTS As a result PCRP is not referenced by any device and acpi_turn_off_unused_power_resources() turns it off right after the ACPI namespace scan. This cuts power to the dGPU, but PC01 still believes it is on, so its _ON method is a no-op when the PCI core later tries to bring the GPU back to D0. Windows does not turn off unused power resources, so the dGPU works there. Add the machine to dmi_leave_unused_power_resources_on[], like the THUNDEROBOT ZERO which has a similar SSDT bug. With this change the dGPU stays on the bus and the nvidia driver binds. Runtime PM of the dGPU is unaffected, as it is handled through PC01: with no driver bound and power/control set to auto, RP05 reaches D3cold and the GPU comes back to D0 on resume. Link: https://lore.kernel.org/r/CADS-TgDQ1G8vrTQLJeQ3RTBVFTyMe0C+5p2=UBKS=EFCDFuaxw@mail.gmail.com/ Signed-off-by: Krittikun Wongnin [ rjw: Subject and changelog tweaks ] Link: https://patch.msgid.link/20261013074245.4327-1-krittikunwongnin@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/power.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 34b73f320b18..c77342a584f0 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -1146,6 +1146,19 @@ static const struct dmi_system_id dmi_leave_unused_power_resources_on[] = { DMI_MATCH(DMI_BOARD_NAME, "NLCC 001"), }, }, + { + /* + * Acer Aspire A515-53G: Two SSDTs both define power resources + * for the dGPU root port RP05 ('PC01' and 'PCRP'). Only PC01 ends + * up in RP05's _PR0/_PR3, so 'PCRP' is shut down on + * initialization, cutting power to the NVIDIA dGPU behind the + * back of 'PC01' and making the dGPU unavailable. + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Aspire A515-53G"), + }, + }, {} };