From 07bfd4abdc3500427fa6ff938f1884ef483e4460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uwe=20Kleine-K=C3=B6nig=20=28The=20Capable=20Hub=29?= Date: Wed, 10 Jun 2026 16:43:33 +0200 Subject: [PATCH 01/61] PNP: Drop unused assignment of pnp_device_id driver data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver explicitly sets the .driver_data member of struct pnp_device_id to zero without relying on that value. Drop these unused assignments. While touching this array simplify the list terminator and align the the array's coding style to what is used most for these. This patch doesn't modify the compiled array, only its representation in source form benefits. The former was confirmed with builds on x86 and arm64. Signed-off-by: Uwe Kleine-König (The Capable Hub) Link: https://patch.msgid.link/0484b30b6935994f5a2d97c55a47a95e1557dd15.1781102535.git.u.kleine-koenig@baylibre.com Signed-off-by: Rafael J. Wysocki --- drivers/pnp/system.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/pnp/system.c b/drivers/pnp/system.c index 835113b2cb04..bd939b564379 100644 --- a/drivers/pnp/system.c +++ b/drivers/pnp/system.c @@ -17,10 +17,10 @@ static const struct pnp_device_id pnp_dev_table[] = { /* General ID for reserving resources */ - {"PNP0c02", 0}, + { .id = "PNP0c02" }, /* memory controller */ - {"PNP0c01", 0}, - {"", 0} + { .id = "PNP0c01" }, + { } }; static void reserve_range(struct pnp_dev *dev, struct resource *r, int port) From 541b293ab186195855a3a8c5e015b6b3cf1a68b8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 1 Jul 2026 21:16:39 +0200 Subject: [PATCH 02/61] ACPI: bus: Eliminate struct acpi_driver Now that struct acpi_driver has no more users, eliminate it along with all of the code related to it. Also remove the file added by commit b8c8a8ea18ad ("ACPI: Documentation: driver-api: Disapprove of using ACPI drivers") because it will not be necessary any more after eliminating struct acpi_driver from the code. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Reviewed-by: Danilo Krummrich Link: https://patch.msgid.link/5132944.31r3eYUQgx@rafael.j.wysocki --- .../driver-api/acpi/acpi-drivers.rst | 80 ---------- Documentation/driver-api/acpi/index.rst | 1 - drivers/acpi/bus.c | 138 +----------------- drivers/acpi/power.c | 1 - drivers/acpi/scan.c | 16 +- include/acpi/acpi_bus.h | 50 +------ 6 files changed, 5 insertions(+), 281 deletions(-) delete mode 100644 Documentation/driver-api/acpi/acpi-drivers.rst diff --git a/Documentation/driver-api/acpi/acpi-drivers.rst b/Documentation/driver-api/acpi/acpi-drivers.rst deleted file mode 100644 index 376b6d8a678c..000000000000 --- a/Documentation/driver-api/acpi/acpi-drivers.rst +++ /dev/null @@ -1,80 +0,0 @@ -.. SPDX-License-Identifier: GPL-2.0 -.. include:: - -========================================= -Why using ACPI drivers is not a good idea -========================================= - -:Copyright: |copy| 2026, Intel Corporation - -:Author: Rafael J. Wysocki - -Even though binding drivers directly to struct acpi_device objects, also -referred to as "ACPI device nodes", allows basic functionality to be provided -at least in some cases, there are problems with it, related to general -consistency, sysfs layout, power management operation ordering, and code -cleanliness. - -First of all, ACPI device nodes represent firmware entities rather than -hardware and in many cases they provide auxiliary information on devices -enumerated independently (like PCI devices or CPUs). It is therefore generally -questionable to assign resources to them because the entities represented by -them do not decode addresses in the memory or I/O address spaces and do not -generate interrupts or similar (all of that is done by hardware). - -Second, as a general rule, a struct acpi_device can only be a parent of another -struct acpi_device. If that is not the case, the location of the child device -in the device hierarchy is at least confusing and it may not be straightforward -to identify the piece of hardware providing functionality represented by it. -However, binding a driver directly to an ACPI device node may cause that to -happen if the given driver registers input devices or wakeup sources under it, -for example. - -Next, using system suspend and resume callbacks directly on ACPI device nodes -is also questionable because it may cause ordering problems to appear. Namely, -ACPI device nodes are registered before enumerating hardware corresponding to -them and they land on the PM list in front of the majority of other device -objects. Consequently, the execution ordering of their PM callbacks may be -different from what is generally expected. Also, in general, dependencies -returned by _DEP objects do not affect ACPI device nodes themselves, but the -"physical" devices associated with them, which potentially is one more source -of inconsistency related to treating ACPI device nodes as "real" device -representation. - -All of the above means that binding drivers to ACPI device nodes should -generally be avoided and so struct acpi_driver objects should not be used. - -Moreover, a device ID is necessary to bind a driver directly to an ACPI device -node, but device IDs are not generally associated with all of them. Some of -them contain alternative information allowing the corresponding pieces of -hardware to be identified, for example represented by an _ADR object return -value, and device IDs are not used in those cases. In consequence, confusingly -enough, binding an ACPI driver to an ACPI device node may even be impossible. - -When that happens, the piece of hardware corresponding to the given ACPI device -node is represented by another device object, like a struct pci_dev, and the -ACPI device node is the "ACPI companion" of that device, accessible through its -fwnode pointer used by the ACPI_COMPANION() macro. The ACPI companion holds -additional information on the device configuration and possibly some "recipes" -on device manipulation in the form of AML (ACPI Machine Language) bytecode -provided by the platform firmware. Thus the role of the ACPI device node is -similar to the role of a struct device_node on a system where Device Tree is -used for platform description. - -For consistency, this approach has been extended to the cases in which ACPI -device IDs are used. Namely, in those cases, an additional device object is -created to represent the piece of hardware corresponding to a given ACPI device -node. By default, it is a platform device, but it may also be a PNP device, a -CPU device, or another type of device, depending on what the given piece of -hardware actually is. There are even cases in which multiple devices are -"backed" or "accompanied" by one ACPI device node (e.g. ACPI device nodes -corresponding to GPUs that may provide firmware interfaces for backlight -brightness control in addition to GPU configuration information). - -This means that it really should never be necessary to bind a driver directly to -an ACPI device node because there is a "proper" device object representing the -corresponding piece of hardware that can be bound to by a "proper" driver using -the given ACPI device node as the device's ACPI companion. Thus, in principle, -there is no reason to use ACPI drivers and if they all were replaced with other -driver types (for example, platform drivers), some code could be dropped and -some complexity would go away. diff --git a/Documentation/driver-api/acpi/index.rst b/Documentation/driver-api/acpi/index.rst index 2b10d83f9994..ace0008e54c2 100644 --- a/Documentation/driver-api/acpi/index.rst +++ b/Documentation/driver-api/acpi/index.rst @@ -7,4 +7,3 @@ ACPI Support linuxized-acpica scan_handlers - acpi-drivers diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index a30a904f6535..fae79cdd3610 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -620,41 +620,6 @@ static void acpi_bus_notify(acpi_handle handle, u32 type, void *data) acpi_evaluate_ost(handle, type, ACPI_OST_SC_NON_SPECIFIC_FAILURE, NULL); } -static void acpi_notify_device(acpi_handle handle, u32 event, void *data) -{ - struct acpi_device *device = data; - struct acpi_driver *acpi_drv = to_acpi_driver(device->dev.driver); - - acpi_drv->ops.notify(device, event); -} - -static int acpi_device_install_notify_handler(struct acpi_device *device, - struct acpi_driver *acpi_drv) -{ - u32 type = acpi_drv->flags & ACPI_DRIVER_ALL_NOTIFY_EVENTS ? - ACPI_ALL_NOTIFY : ACPI_DEVICE_NOTIFY; - acpi_status status; - - status = acpi_install_notify_handler(device->handle, type, - acpi_notify_device, device); - if (ACPI_FAILURE(status)) - return -EINVAL; - - return 0; -} - -static void acpi_device_remove_notify_handler(struct acpi_device *device, - struct acpi_driver *acpi_drv) -{ - u32 type = acpi_drv->flags & ACPI_DRIVER_ALL_NOTIFY_EVENTS ? - ACPI_ALL_NOTIFY : ACPI_DEVICE_NOTIFY; - - acpi_remove_notify_handler(device->handle, type, - acpi_notify_device); - - acpi_os_wait_events_complete(); -} - int acpi_dev_install_notify_handler(struct acpi_device *adev, u32 handler_type, acpi_notify_handler handler, void *context) @@ -1121,57 +1086,13 @@ bool acpi_driver_match_device(struct device *dev, } EXPORT_SYMBOL_GPL(acpi_driver_match_device); -/* -------------------------------------------------------------------------- - ACPI Driver Management - -------------------------------------------------------------------------- */ - -/** - * __acpi_bus_register_driver - register a driver with the ACPI bus - * @driver: driver being registered - * @owner: owning module/driver - * - * Registers a driver with the ACPI bus. Searches the namespace for all - * devices that match the driver's criteria and binds. Returns zero for - * success or a negative error status for failure. - */ -int __acpi_bus_register_driver(struct acpi_driver *driver, struct module *owner) -{ - if (acpi_disabled) - return -ENODEV; - driver->drv.name = driver->name; - driver->drv.bus = &acpi_bus_type; - driver->drv.owner = owner; - - return driver_register(&driver->drv); -} - -EXPORT_SYMBOL(__acpi_bus_register_driver); - -/** - * acpi_bus_unregister_driver - unregisters a driver with the ACPI bus - * @driver: driver to unregister - * - * Unregisters a driver with the ACPI bus. Searches the namespace for all - * devices that match the driver's criteria and unbinds. - */ -void acpi_bus_unregister_driver(struct acpi_driver *driver) -{ - driver_unregister(&driver->drv); -} - -EXPORT_SYMBOL(acpi_bus_unregister_driver); - /* -------------------------------------------------------------------------- ACPI Bus operations -------------------------------------------------------------------------- */ static int acpi_bus_match(struct device *dev, const struct device_driver *drv) { - struct acpi_device *acpi_dev = to_acpi_device(dev); - const struct acpi_driver *acpi_drv = to_acpi_driver(drv); - - return acpi_dev->flags.match_driver - && !acpi_match_device_ids(acpi_dev, acpi_drv->ids); + return 0; } static int acpi_device_uevent(const struct device *dev, struct kobj_uevent_env *env) @@ -1179,66 +1100,9 @@ static int acpi_device_uevent(const struct device *dev, struct kobj_uevent_env * return __acpi_device_uevent_modalias(to_acpi_device(dev), env); } -static int acpi_device_probe(struct device *dev) -{ - struct acpi_device *acpi_dev = to_acpi_device(dev); - struct acpi_driver *acpi_drv = to_acpi_driver(dev->driver); - int ret; - - if (acpi_dev->handler && !acpi_is_pnp_device(acpi_dev)) - return -EINVAL; - - if (!acpi_drv->ops.add) - return -ENOSYS; - - ret = acpi_drv->ops.add(acpi_dev); - if (ret) { - acpi_dev->driver_data = NULL; - return ret; - } - - pr_debug("Driver [%s] successfully bound to device [%s]\n", - acpi_drv->name, acpi_dev->pnp.bus_id); - - if (acpi_drv->ops.notify) { - ret = acpi_device_install_notify_handler(acpi_dev, acpi_drv); - if (ret) { - if (acpi_drv->ops.remove) - acpi_drv->ops.remove(acpi_dev); - - acpi_dev->driver_data = NULL; - return ret; - } - } - - pr_debug("Found driver [%s] for device [%s]\n", acpi_drv->name, - acpi_dev->pnp.bus_id); - - get_device(dev); - return 0; -} - -static void acpi_device_remove(struct device *dev) -{ - struct acpi_device *acpi_dev = to_acpi_device(dev); - struct acpi_driver *acpi_drv = to_acpi_driver(dev->driver); - - if (acpi_drv->ops.notify) - acpi_device_remove_notify_handler(acpi_dev, acpi_drv); - - if (acpi_drv->ops.remove) - acpi_drv->ops.remove(acpi_dev); - - acpi_dev->driver_data = NULL; - - put_device(dev); -} - const struct bus_type acpi_bus_type = { .name = "acpi", .match = acpi_bus_match, - .probe = acpi_device_probe, - .remove = acpi_device_remove, .uevent = acpi_device_uevent, }; diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index d4131c184be8..23a4e207a01e 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -954,7 +954,6 @@ struct acpi_device *acpi_add_power_resource(acpi_handle handle) INIT_LIST_HEAD(&resource->list_node); INIT_LIST_HEAD(&resource->dependents); device->power.state = ACPI_STATE_UNKNOWN; - device->flags.match_driver = true; /* Evaluate the object to get the system level and resource order. */ status = acpi_evaluate_object(handle, NULL, NULL, &buffer); diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 9a7ac2eb9ce0..ee24c65d43ed 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -273,13 +273,9 @@ static int acpi_scan_check_and_detach(struct acpi_device *adev, void *p) } } - adev->flags.match_driver = false; - if (handler) { - if (handler->detach) - handler->detach(adev); - } else { - device_release_driver(&adev->dev); - } + if (handler && handler->detach) + handler->detach(adev); + /* * Most likely, the device is going away, so put it into D3cold before * that. @@ -1821,7 +1817,6 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, acpi_set_pnp_ids(handle, &device->pnp, type); acpi_init_properties(device); acpi_bus_get_flags(device); - device->flags.match_driver = false; device->flags.initialized = true; device->flags.enumeration_by_parent = acpi_device_enumeration_by_parent(device); @@ -2375,16 +2370,11 @@ static int acpi_bus_attach(struct acpi_device *device, void *first_pass) if (ret < 0) return 0; - device->flags.match_driver = true; if (ret > 0 && !device->flags.enumeration_by_parent) { acpi_device_set_enumerated(device); goto ok; } - ret = device_attach(&device->dev); - if (ret < 0) - return 0; - if (device->pnp.type.platform_id || device->pnp.type.backlight || device->flags.enumeration_by_parent) acpi_default_enumeration(device); diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 714d111d8053..7b8051baed75 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -108,7 +108,6 @@ enum acpi_bus_device_type { ACPI_BUS_DEVICE_TYPE_COUNT }; -struct acpi_driver; struct acpi_device; /* @@ -158,32 +157,6 @@ struct acpi_hotplug_context { acpi_hp_fixup fixup; }; -/* - * ACPI Driver - * ----------- - */ - -typedef int (*acpi_op_add) (struct acpi_device * device); -typedef void (*acpi_op_remove) (struct acpi_device *device); -typedef void (*acpi_op_notify) (struct acpi_device * device, u32 event); - -struct acpi_device_ops { - acpi_op_add add; - acpi_op_remove remove; - acpi_op_notify notify; -}; - -#define ACPI_DRIVER_ALL_NOTIFY_EVENTS 0x1 /* system AND device events */ - -struct acpi_driver { - char name[80]; - char class[80]; - const struct acpi_device_id *ids; /* Supported Hardware IDs */ - unsigned int flags; - struct acpi_device_ops ops; - struct device_driver drv; -}; - /* * ACPI Device * ----------- @@ -211,7 +184,6 @@ struct acpi_device_flags { u32 removable:1; u32 ejectable:1; u32 power_manageable:1; - u32 match_driver:1; u32 initialized:1; u32 visited:1; u32 hotplug_notify:1; @@ -221,7 +193,7 @@ struct acpi_device_flags { u32 cca_seen:1; u32 enumeration_by_parent:1; u32 honor_deps:1; - u32 reserved:18; + u32 reserved:19; }; /* File System */ @@ -570,7 +542,6 @@ static inline void *acpi_driver_data(struct acpi_device *d) } #define to_acpi_device(d) container_of(d, struct acpi_device, dev) -#define to_acpi_driver(d) container_of_const(d, struct acpi_driver, drv) static inline struct acpi_device *acpi_dev_parent(struct acpi_device *adev) { @@ -676,13 +647,6 @@ void acpi_scan_lock_release(void); void acpi_lock_hp_context(void); void acpi_unlock_hp_context(void); int acpi_scan_add_handler(struct acpi_scan_handler *handler); -/* - * use a macro to avoid include chaining to get THIS_MODULE - */ -#define acpi_bus_register_driver(drv) \ - __acpi_bus_register_driver(drv, THIS_MODULE) -int __acpi_bus_register_driver(struct acpi_driver *driver, struct module *owner); -void acpi_bus_unregister_driver(struct acpi_driver *driver); int acpi_bus_scan(acpi_handle handle); void acpi_bus_trim(struct acpi_device *start); acpi_status acpi_bus_get_ejd(acpi_handle handle, acpi_handle * ejd); @@ -696,18 +660,6 @@ static inline bool acpi_device_enumerated(struct acpi_device *adev) return adev && adev->flags.initialized && adev->flags.visited; } -/** - * module_acpi_driver(acpi_driver) - Helper macro for registering an ACPI driver - * @__acpi_driver: acpi_driver struct - * - * Helper macro for ACPI drivers which do not do anything special in module - * init/exit. This eliminates a lot of boilerplate. Each module may only - * use this macro once, and calling it replaces module_init() and module_exit() - */ -#define module_acpi_driver(__acpi_driver) \ - module_driver(__acpi_driver, acpi_bus_register_driver, \ - acpi_bus_unregister_driver) - /* * Bind physical devices with ACPI devices */ From beaff265a40e9438447dfb2de6407557d796c9ab Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 1 Jul 2026 21:17:28 +0200 Subject: [PATCH 03/61] ACPI: scan: Set power.no_pm for all struct acpi_device objects Now that drivers do not bind to ACPI device objects, there is no reason for them to be included directly in power management in any way, so set power.no_pm for all of them. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Reviewed-by: Danilo Krummrich Link: https://patch.msgid.link/2436882.ElGaqSPkdT@rafael.j.wysocki --- drivers/acpi/scan.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index ee24c65d43ed..8515e1892643 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1811,6 +1811,7 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, device->dev.release = release; device->dev.bus = &acpi_bus_type; device->dev.groups = acpi_groups; + device_set_pm_not_required(&device->dev); fwnode_init(&device->fwnode, &acpi_device_fwnode_ops); acpi_set_device_status(device, ACPI_STA_DEFAULT); acpi_device_get_busid(device); From 4d77cd711d7bd4b50736460e3725b06fa7c050cd Mon Sep 17 00:00:00 2001 From: Sang-Heon Jeon Date: Fri, 3 Jul 2026 13:13:22 +0900 Subject: [PATCH 04/61] ACPI: NUMA: remove redundant node_set() call numa_add_memblk() now sets the node in numa_nodes_parsed itself, so the caller's own node_set() is redundant. Remove it. No functional change. Signed-off-by: Sang-Heon Jeon [ rjw: Subject adjustment ] Link: https://patch.msgid.link/20260703041329.2797584-3-ekffu200098@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/numa/srat.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/acpi/numa/srat.c b/drivers/acpi/numa/srat.c index 62d4a8df0b8c..5c407dc6401e 100644 --- a/drivers/acpi/numa/srat.c +++ b/drivers/acpi/numa/srat.c @@ -399,8 +399,6 @@ acpi_parse_memory_affinity(union acpi_subtable_headers *header, goto out_err_bad_srat; } - node_set(node, numa_nodes_parsed); - pr_info("SRAT: Node %u PXM %u [mem %#010Lx-%#010Lx]%s%s\n", node, pxm, (unsigned long long) start, (unsigned long long) end - 1, From fcdae14b64e9ac27387a17a96f834009a2c1a8e1 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Thu, 2 Jul 2026 23:36:03 -0400 Subject: [PATCH 05/61] PNP: Fix card device cleanup on registration failure pnp_add_card() ignores __pnp_add_device() failures. If device_register() fails there, the device is removed from the global and protocol lists, but remains on the card list and its device reference is not dropped. Remove the failed device from the card list and call put_device() before continuing with the remaining card devices. Signed-off-by: Yuho Choi Link: https://patch.msgid.link/20260703033603.114931-1-dbgh9129@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/pnp/card.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/drivers/pnp/card.c b/drivers/pnp/card.c index 87f5af454751..df5e3d3cbf6c 100644 --- a/drivers/pnp/card.c +++ b/drivers/pnp/card.c @@ -254,9 +254,16 @@ int pnp_add_card(struct pnp_card *card) /* we wait until now to add devices in order to ensure the drivers * will be able to use all of the related devices on the card * without waiting an unreasonable length of time */ - list_for_each(pos, &card->devices) { + list_for_each_safe(pos, temp, &card->devices) { struct pnp_dev *dev = card_to_pnp_dev(pos); - __pnp_add_device(dev); + error = __pnp_add_device(dev); + if (error) { + mutex_lock(&pnp_lock); + list_del(&dev->card_list); + dev->card = NULL; + mutex_unlock(&pnp_lock); + put_device(&dev->dev); + } } /* match with card drivers */ From 7bfd21e828504e3b7dd923bea5474ffdf204f264 Mon Sep 17 00:00:00 2001 From: Rui Qi Date: Tue, 30 Jun 2026 14:14:45 +0800 Subject: [PATCH 06/61] ACPI: APEI: GHES: Mark ghes_in_nmi_spool_from_list() as maybe unused When CONFIG_ACPI_APEI_SEA and CONFIG_HAVE_ACPI_APEI_NMI are both disabled, ghes_in_nmi_spool_from_list() becomes an unused static function and triggers -Werror=unused-function in some configs, for example riscv defconfig with APEI disabled. Mark it as __maybe_unused to silence the warning while keeping the code available for configurations that use SEA or APEI NMI. Reviewed-by: Breno Leitao Reviewed-by: Hanjun Guo Reviewed-by: Shuai Xue Signed-off-by: Rui Qi Link: https://patch.msgid.link/20260630061445.2191731-1-qirui.001@bytedance.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 3236a3ce79d6..48f915ee5a0d 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -1397,8 +1397,8 @@ static int ghes_in_nmi_queue_one_entry(struct ghes *ghes, return rc; } -static int ghes_in_nmi_spool_from_list(struct list_head *rcu_list, - enum fixed_addresses fixmap_idx) +static int __maybe_unused ghes_in_nmi_spool_from_list(struct list_head *rcu_list, + enum fixed_addresses fixmap_idx) { int ret = -ENOENT; struct ghes *ghes; From deef78d3543976dc4a0d03658e0b55545d7564ad Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 9 Jul 2026 10:40:11 +0200 Subject: [PATCH 07/61] ACPI: Add acpi_device_clear_deps() helper function Code clearing device dependencies in ACPI in drivers through acpi_dev_clear_dependencies() requires annoying ifdeffery to make sure it is compiled out on !CONFIG_ACPI configurations. Implement a wrapper function to clear device dependencies that can be used in device drivers without conditional compilation. Signed-off-by: Lorenzo Pieralisi Link: https://patch.msgid.link/20260709-gic-v5-acpi-iwb-probe-deferral-v4-1-48dae790f871@kernel.org Signed-off-by: Rafael J. Wysocki --- include/linux/acpi.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 60ab50cb8930..becd1f098554 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -94,6 +94,12 @@ static inline void acpi_preset_companion(struct device *dev, ACPI_COMPANION_SET(dev, acpi_find_child_device(parent, addr, false)); } +static inline void acpi_device_clear_deps(struct device *dev) +{ + if (has_acpi_companion(dev)) + acpi_dev_clear_dependencies(ACPI_COMPANION(dev)); +} + static inline const char *acpi_dev_name(struct acpi_device *adev) { return dev_name(&adev->dev); @@ -908,6 +914,8 @@ static inline void acpi_preset_companion(struct device *dev, { } +static inline void acpi_device_clear_deps(struct device *dev) {} + static inline const char *acpi_dev_name(struct acpi_device *adev) { return NULL; From 64ae310bffa477cd11029c818bec489f4b8a845e Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 9 Jul 2026 10:40:12 +0200 Subject: [PATCH 08/61] ACPI: RISC-V: Fix riscv_acpi_irq_get_dep() loop termination In riscv_acpi_add_irq_dep() the main loop condition would currently stop the loop if an interrupt descriptor contains an interrupt for which the respective GSI handle is NULL, which is not correct because subsequent interrupts in the interrupt descriptor might still have a GSI dependency that must not be skipped. Rework riscv_acpi_add_irq_dep() and the riscv_acpi_irq_get_dep() call chain to fix it - by not forcing the loop to stop in order to guarantee dependency detection for all the interrupt entries in the CRS descriptor. Fixes: 1b173cc4bfcd ("ACPI: RISC-V: Implement function to add implicit dependencies") Signed-off-by: Lorenzo Pieralisi Tested-by: Sunil V L Reviewed-by: Sunil V L Link: https://patch.msgid.link/20260709-gic-v5-acpi-iwb-probe-deferral-v4-2-48dae790f871@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/riscv/irq.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/riscv/irq.c b/drivers/acpi/riscv/irq.c index 9b88d0993e88..cd83c3035cf6 100644 --- a/drivers/acpi/riscv/irq.c +++ b/drivers/acpi/riscv/irq.c @@ -299,6 +299,7 @@ static acpi_status riscv_acpi_irq_get_parent(struct acpi_resource *ares, void *c return AE_OK; ctx->handle = riscv_acpi_get_gsi_handle(eirq->interrupts[ctx->index]); + ctx->rc = 0; return AE_CTRL_TERMINATE; } @@ -314,10 +315,8 @@ static int riscv_acpi_irq_get_dep(acpi_handle handle, unsigned int index, acpi_h acpi_walk_resources(handle, METHOD_NAME__CRS, riscv_acpi_irq_get_parent, &ctx); *gsi_handle = ctx.handle; - if (*gsi_handle) - return 1; - return 0; + return ctx.rc; } static u32 riscv_acpi_add_prt_dep(acpi_handle handle) @@ -381,8 +380,11 @@ static u32 riscv_acpi_add_irq_dep(acpi_handle handle) int i; for (i = 0; - riscv_acpi_irq_get_dep(handle, i, &gsi_handle); + !riscv_acpi_irq_get_dep(handle, i, &gsi_handle); i++) { + if (!gsi_handle) + continue; + dep_devices.count = 1; dep_devices.handles = kzalloc_objs(*dep_devices.handles, 1); if (!dep_devices.handles) { From 20435bda13f1219891ed0ce41207e320a916ff9c Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 9 Jul 2026 10:40:13 +0200 Subject: [PATCH 09/61] ACPI: RISC-V: Check acpi_get_handle() status in riscv_acpi_add_prt_dep() In riscv_acpi_add_prt_dep(), the acpi_get_handle() call can fail which would leave link_handle uninitialized. Fix it by checking the acpi_get_handle() return status and skip the entry if it fails. Fixes: 1b173cc4bfcd ("ACPI: RISC-V: Implement function to add implicit dependencies") Signed-off-by: Lorenzo Pieralisi Tested-by: Sunil V L Reviewed-by: Sunil V L Link: https://patch.msgid.link/20260709-gic-v5-acpi-iwb-probe-deferral-v4-3-48dae790f871@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/riscv/irq.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/riscv/irq.c b/drivers/acpi/riscv/irq.c index cd83c3035cf6..75170151c614 100644 --- a/drivers/acpi/riscv/irq.c +++ b/drivers/acpi/riscv/irq.c @@ -339,7 +339,9 @@ static u32 riscv_acpi_add_prt_dep(acpi_handle handle) entry = buffer.pointer; while (entry && (entry->length > 0)) { if (entry->source[0]) { - acpi_get_handle(handle, entry->source, &link_handle); + status = acpi_get_handle(handle, entry->source, &link_handle); + if (ACPI_FAILURE(status)) + continue; dep_devices.count = 1; dep_devices.handles = kzalloc_objs(*dep_devices.handles, 1); From 3a56321d0aceee2a0bd80d23366401c131ff8350 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 9 Jul 2026 10:40:14 +0200 Subject: [PATCH 10/61] ACPI: RISC-V: Fix riscv_acpi_add_prt_dep() loop handling The loop in riscv_acpi_add_prt_dep() includes error conditions that are handled in a dubious - if not outright wrong - way, by continuining the loop (which skips and misses the entry pointer update to point to the next entry). Rewrite the loop as a for loop (that handles the continuation correctly) and wrap the condition and update statements using helper functions to make it cleaner. Fixes: 1b173cc4bfcd ("ACPI: RISC-V: Implement function to add implicit dependencies") Signed-off-by: Lorenzo Pieralisi Tested-by: Sunil V L Reviewed-by: Sunil V L Link: https://patch.msgid.link/20260709-gic-v5-acpi-iwb-probe-deferral-v4-4-48dae790f871@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/riscv/irq.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/riscv/irq.c b/drivers/acpi/riscv/irq.c index 75170151c614..0cdec5dd575e 100644 --- a/drivers/acpi/riscv/irq.c +++ b/drivers/acpi/riscv/irq.c @@ -319,6 +319,20 @@ static int riscv_acpi_irq_get_dep(acpi_handle handle, unsigned int index, acpi_h return ctx.rc; } +static bool acpi_prt_entry_valid(void *prt_entry) +{ + struct acpi_pci_routing_table *entry = prt_entry; + + return entry && entry->length > 0; +} + +static void *acpi_prt_next_entry(void *prt_entry) +{ + struct acpi_pci_routing_table *entry = prt_entry; + + return prt_entry + entry->length; +} + static u32 riscv_acpi_add_prt_dep(acpi_handle handle) { struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; @@ -337,7 +351,7 @@ static u32 riscv_acpi_add_prt_dep(acpi_handle handle) } entry = buffer.pointer; - while (entry && (entry->length > 0)) { + for (; acpi_prt_entry_valid(entry); entry = acpi_prt_next_entry(entry)) { if (entry->source[0]) { status = acpi_get_handle(handle, entry->source, &link_handle); if (ACPI_FAILURE(status)) @@ -365,9 +379,6 @@ static u32 riscv_acpi_add_prt_dep(acpi_handle handle) dep_devices.handles[0] = gsi_handle; count += acpi_scan_add_dep(handle, &dep_devices); } - - entry = (struct acpi_pci_routing_table *) - ((unsigned long)entry + entry->length); } kfree(buffer.pointer); From 704ce0039a629739753551a69132f783eb9912f5 Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 9 Jul 2026 10:40:15 +0200 Subject: [PATCH 11/61] ACPI: irq: Move RISC-V interrupt controllers autodep to ACPI IRQ code RISC-V implements arch code to detect probe dependencies for devices and the interrupt controller the devices GSIs are routed to. The code itself is arch agnostic apart from an arch specific helper function required to retrieve the acpi_handle of the interrupt controller that manages the device GSI interrupt. In order to enable IRQ probe dependencies detection on other architectures, move RISC-V IRQ probe dependency detection code to generic ACPI IRQ code. Allow interrupt controller drivers to register an arch specific function to determine the acpi_handle for a specific GSI number to use the mechanism if needed by the respective interrupt controller drivers. Signed-off-by: Lorenzo Pieralisi Reviewed-by: Sunil V L Tested-by: Sunil V L Link: https://patch.msgid.link/20260709-gic-v5-acpi-iwb-probe-deferral-v4-5-48dae790f871@kernel.org Signed-off-by: Rafael J. Wysocki --- arch/riscv/include/asm/acpi.h | 1 + drivers/acpi/irq.c | 172 +++++++++++++++++++++++++++- drivers/acpi/riscv/irq.c | 156 +------------------------ drivers/irqchip/irq-gic-v3.c | 2 +- drivers/irqchip/irq-gic-v5.c | 2 +- drivers/irqchip/irq-gic.c | 2 +- drivers/irqchip/irq-loongarch-cpu.c | 2 +- drivers/irqchip/irq-riscv-intc.c | 3 +- include/linux/acpi.h | 5 +- 9 files changed, 181 insertions(+), 164 deletions(-) diff --git a/arch/riscv/include/asm/acpi.h b/arch/riscv/include/asm/acpi.h index 26ab37c171bc..f598520ac903 100644 --- a/arch/riscv/include/asm/acpi.h +++ b/arch/riscv/include/asm/acpi.h @@ -67,6 +67,7 @@ int acpi_get_riscv_isa(struct acpi_table_header *table, void acpi_get_cbo_block_size(struct acpi_table_header *table, u32 *cbom_size, u32 *cboz_size, u32 *cbop_size); +acpi_handle acpi_get_riscv_gsi_handle(u32 gsi); #else static inline void acpi_init_rintc_map(void) { } static inline struct acpi_madt_rintc *acpi_cpu_get_madt_rintc(int cpu) diff --git a/drivers/acpi/irq.c b/drivers/acpi/irq.c index d1595156c86a..e4293458bf61 100644 --- a/drivers/acpi/irq.c +++ b/drivers/acpi/irq.c @@ -13,6 +13,7 @@ enum acpi_irq_model_id acpi_irq_model; static acpi_gsi_domain_disp_fn acpi_get_gsi_domain_id; +static acpi_gsi_handle_disp_fn acpi_get_gsi_handle; static u32 (*acpi_gsi_to_irq_fallback)(u32 gsi); /** @@ -321,15 +322,19 @@ const struct cpumask *acpi_irq_get_affinity(acpi_handle handle, /** * acpi_set_irq_model - Setup the GSI irqdomain information - * @model: the value assigned to acpi_irq_model - * @fn: a dispatcher function that will return the domain fwnode - * for a given GSI + * @model: the value assigned to acpi_irq_model + * @fn: a dispatcher function that will return the domain fwnode + * for a given GSI + * @gsi_dep_fn: a function to retrieve the acpi_handle a GSI interrupt is + * dependent on + * */ void __init acpi_set_irq_model(enum acpi_irq_model_id model, - acpi_gsi_domain_disp_fn fn) + acpi_gsi_domain_disp_fn fn, acpi_gsi_handle_disp_fn gsi_dep_fn) { acpi_irq_model = model; acpi_get_gsi_domain_id = fn; + acpi_get_gsi_handle = gsi_dep_fn; } /* @@ -385,3 +390,162 @@ struct irq_domain *acpi_irq_create_hierarchy(unsigned int flags, host_data); } EXPORT_SYMBOL_GPL(acpi_irq_create_hierarchy); + +struct acpi_irq_dep_ctx { + int rc; + unsigned int index; + acpi_handle handle; +}; + +static acpi_status acpi_irq_get_parent(struct acpi_resource *ares, void *context) +{ + struct acpi_irq_dep_ctx *ctx = context; + struct acpi_resource_irq *irq; + struct acpi_resource_extended_irq *eirq; + + switch (ares->type) { + case ACPI_RESOURCE_TYPE_IRQ: + irq = &ares->data.irq; + if (ctx->index >= irq->interrupt_count) { + ctx->index -= irq->interrupt_count; + return AE_OK; + } + ctx->handle = acpi_get_gsi_handle(irq->interrupts[ctx->index]); + ctx->rc = 0; + return AE_CTRL_TERMINATE; + case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: + eirq = &ares->data.extended_irq; + if (eirq->producer_consumer == ACPI_PRODUCER) + return AE_OK; + + if (ctx->index >= eirq->interrupt_count) { + ctx->index -= eirq->interrupt_count; + return AE_OK; + } + + /* Support GSIs only */ + if (eirq->resource_source.string_length) + return AE_OK; + + ctx->handle = acpi_get_gsi_handle(eirq->interrupts[ctx->index]); + ctx->rc = 0; + return AE_CTRL_TERMINATE; + } + + return AE_OK; +} + +static int acpi_irq_get_dep(acpi_handle handle, unsigned int index, acpi_handle *gsi_handle) +{ + struct acpi_irq_dep_ctx ctx = {-EINVAL, index, NULL}; + + if (!gsi_handle) + return -EINVAL; + + acpi_walk_resources(handle, METHOD_NAME__CRS, acpi_irq_get_parent, &ctx); + *gsi_handle = ctx.handle; + + return ctx.rc; +} + +static bool acpi_prt_entry_valid(void *prt_entry) +{ + struct acpi_pci_routing_table *entry = prt_entry; + + return entry && entry->length > 0; +} + +static void *acpi_prt_next_entry(void *prt_entry) +{ + struct acpi_pci_routing_table *entry = prt_entry; + + return prt_entry + entry->length; +} + +static u32 acpi_add_prt_dep(acpi_handle handle) +{ + struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; + struct acpi_pci_routing_table *entry; + struct acpi_handle_list dep_devices; + acpi_handle gsi_handle; + acpi_handle link_handle; + acpi_status status; + u32 count = 0; + + status = acpi_get_irq_routing_table(handle, &buffer); + if (ACPI_FAILURE(status)) { + acpi_handle_err(handle, "failed to get IRQ routing table\n"); + kfree(buffer.pointer); + return 0; + } + + entry = buffer.pointer; + for (; acpi_prt_entry_valid(entry); entry = acpi_prt_next_entry(entry)) { + if (entry->source[0]) { + status = acpi_get_handle(handle, entry->source, &link_handle); + if (ACPI_FAILURE(status)) + continue; + dep_devices.count = 1; + dep_devices.handles = kcalloc(1, sizeof(*dep_devices.handles), GFP_KERNEL); + if (!dep_devices.handles) { + acpi_handle_err(handle, "failed to allocate memory\n"); + continue; + } + + dep_devices.handles[0] = link_handle; + count += acpi_scan_add_dep(handle, &dep_devices); + } else { + gsi_handle = acpi_get_gsi_handle(entry->source_index); + if (!gsi_handle) + continue; + dep_devices.count = 1; + dep_devices.handles = kcalloc(1, sizeof(*dep_devices.handles), GFP_KERNEL); + if (!dep_devices.handles) { + acpi_handle_err(handle, "failed to allocate memory\n"); + continue; + } + + dep_devices.handles[0] = gsi_handle; + count += acpi_scan_add_dep(handle, &dep_devices); + } + } + + kfree(buffer.pointer); + return count; +} + +static u32 acpi_add_irq_dep(acpi_handle handle) +{ + struct acpi_handle_list dep_devices; + acpi_handle gsi_handle; + u32 count = 0; + int i; + + for (i = 0; !acpi_irq_get_dep(handle, i, &gsi_handle); i++) { + if (!gsi_handle) + continue; + + dep_devices.count = 1; + dep_devices.handles = kcalloc(1, sizeof(*dep_devices.handles), GFP_KERNEL); + if (!dep_devices.handles) { + acpi_handle_err(handle, "failed to allocate memory\n"); + continue; + } + + dep_devices.handles[0] = gsi_handle; + count += acpi_scan_add_dep(handle, &dep_devices); + } + + return count; +} + +u32 acpi_irq_add_auto_dep(acpi_handle handle) +{ + if (!acpi_get_gsi_handle) + return 0; + + if (acpi_has_method(handle, "_PRT")) + return acpi_add_prt_dep(handle); + + return acpi_add_irq_dep(handle); +} diff --git a/drivers/acpi/riscv/irq.c b/drivers/acpi/riscv/irq.c index 0cdec5dd575e..da2c42e0ebfd 100644 --- a/drivers/acpi/riscv/irq.c +++ b/drivers/acpi/riscv/irq.c @@ -23,12 +23,6 @@ struct riscv_ext_intc_list { struct list_head list; }; -struct acpi_irq_dep_ctx { - int rc; - unsigned int index; - acpi_handle handle; -}; - LIST_HEAD(ext_intc_list); static int irqchip_cmp_func(const void *in0, const void *in1) @@ -254,7 +248,7 @@ void __init riscv_acpi_init_gsi_mapping(void) acpi_get_devices("RSCV0006", riscv_acpi_create_gsi_map_smsi, NULL, NULL); } -static acpi_handle riscv_acpi_get_gsi_handle(u32 gsi) +acpi_handle acpi_get_riscv_gsi_handle(u32 gsi) { struct riscv_ext_intc_list *ext_intc_element; struct list_head *i; @@ -269,153 +263,7 @@ static acpi_handle riscv_acpi_get_gsi_handle(u32 gsi) return NULL; } -static acpi_status riscv_acpi_irq_get_parent(struct acpi_resource *ares, void *context) -{ - struct acpi_irq_dep_ctx *ctx = context; - struct acpi_resource_irq *irq; - struct acpi_resource_extended_irq *eirq; - - switch (ares->type) { - case ACPI_RESOURCE_TYPE_IRQ: - irq = &ares->data.irq; - if (ctx->index >= irq->interrupt_count) { - ctx->index -= irq->interrupt_count; - return AE_OK; - } - ctx->handle = riscv_acpi_get_gsi_handle(irq->interrupts[ctx->index]); - return AE_CTRL_TERMINATE; - case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: - eirq = &ares->data.extended_irq; - if (eirq->producer_consumer == ACPI_PRODUCER) - return AE_OK; - - if (ctx->index >= eirq->interrupt_count) { - ctx->index -= eirq->interrupt_count; - return AE_OK; - } - - /* Support GSIs only */ - if (eirq->resource_source.string_length) - return AE_OK; - - ctx->handle = riscv_acpi_get_gsi_handle(eirq->interrupts[ctx->index]); - ctx->rc = 0; - return AE_CTRL_TERMINATE; - } - - return AE_OK; -} - -static int riscv_acpi_irq_get_dep(acpi_handle handle, unsigned int index, acpi_handle *gsi_handle) -{ - struct acpi_irq_dep_ctx ctx = {-EINVAL, index, NULL}; - - if (!gsi_handle) - return 0; - - acpi_walk_resources(handle, METHOD_NAME__CRS, riscv_acpi_irq_get_parent, &ctx); - *gsi_handle = ctx.handle; - - return ctx.rc; -} - -static bool acpi_prt_entry_valid(void *prt_entry) -{ - struct acpi_pci_routing_table *entry = prt_entry; - - return entry && entry->length > 0; -} - -static void *acpi_prt_next_entry(void *prt_entry) -{ - struct acpi_pci_routing_table *entry = prt_entry; - - return prt_entry + entry->length; -} - -static u32 riscv_acpi_add_prt_dep(acpi_handle handle) -{ - struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; - struct acpi_pci_routing_table *entry; - struct acpi_handle_list dep_devices; - acpi_handle gsi_handle; - acpi_handle link_handle; - acpi_status status; - u32 count = 0; - - status = acpi_get_irq_routing_table(handle, &buffer); - if (ACPI_FAILURE(status)) { - acpi_handle_err(handle, "failed to get IRQ routing table\n"); - kfree(buffer.pointer); - return 0; - } - - entry = buffer.pointer; - for (; acpi_prt_entry_valid(entry); entry = acpi_prt_next_entry(entry)) { - if (entry->source[0]) { - status = acpi_get_handle(handle, entry->source, &link_handle); - if (ACPI_FAILURE(status)) - continue; - dep_devices.count = 1; - dep_devices.handles = kzalloc_objs(*dep_devices.handles, - 1); - if (!dep_devices.handles) { - acpi_handle_err(handle, "failed to allocate memory\n"); - continue; - } - - dep_devices.handles[0] = link_handle; - count += acpi_scan_add_dep(handle, &dep_devices); - } else { - gsi_handle = riscv_acpi_get_gsi_handle(entry->source_index); - dep_devices.count = 1; - dep_devices.handles = kzalloc_objs(*dep_devices.handles, - 1); - if (!dep_devices.handles) { - acpi_handle_err(handle, "failed to allocate memory\n"); - continue; - } - - dep_devices.handles[0] = gsi_handle; - count += acpi_scan_add_dep(handle, &dep_devices); - } - } - - kfree(buffer.pointer); - return count; -} - -static u32 riscv_acpi_add_irq_dep(acpi_handle handle) -{ - struct acpi_handle_list dep_devices; - acpi_handle gsi_handle; - u32 count = 0; - int i; - - for (i = 0; - !riscv_acpi_irq_get_dep(handle, i, &gsi_handle); - i++) { - if (!gsi_handle) - continue; - - dep_devices.count = 1; - dep_devices.handles = kzalloc_objs(*dep_devices.handles, 1); - if (!dep_devices.handles) { - acpi_handle_err(handle, "failed to allocate memory\n"); - continue; - } - - dep_devices.handles[0] = gsi_handle; - count += acpi_scan_add_dep(handle, &dep_devices); - } - - return count; -} - u32 arch_acpi_add_auto_dep(acpi_handle handle) { - if (acpi_has_method(handle, "_PRT")) - return riscv_acpi_add_prt_dep(handle); - - return riscv_acpi_add_irq_dep(handle); + return acpi_irq_add_auto_dep(handle); } diff --git a/drivers/irqchip/irq-gic-v3.c b/drivers/irqchip/irq-gic-v3.c index 99444a1b2ffa..2673954d4577 100644 --- a/drivers/irqchip/irq-gic-v3.c +++ b/drivers/irqchip/irq-gic-v3.c @@ -2588,7 +2588,7 @@ gic_acpi_init(union acpi_subtable_headers *header, const unsigned long end) if (err) goto out_fwhandle_free; - acpi_set_irq_model(ACPI_IRQ_MODEL_GIC, gic_v3_get_gsi_domain_id); + acpi_set_irq_model(ACPI_IRQ_MODEL_GIC, gic_v3_get_gsi_domain_id, NULL); if (static_branch_likely(&supports_deactivate_key)) gic_acpi_setup_kvm_info(); diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c index e9d1795235a6..b9f648d8e0f0 100644 --- a/drivers/irqchip/irq-gic-v5.c +++ b/drivers/irqchip/irq-gic-v5.c @@ -1251,7 +1251,7 @@ static int __init gic_acpi_init(union acpi_subtable_headers *header, const unsig if (ret) goto out_irs; - acpi_set_irq_model(ACPI_IRQ_MODEL_GIC_V5, gic_v5_get_gsi_domain_id); + acpi_set_irq_model(ACPI_IRQ_MODEL_GIC_V5, gic_v5_get_gsi_domain_id, NULL); return 0; diff --git a/drivers/irqchip/irq-gic.c b/drivers/irqchip/irq-gic.c index ec70c84e9f91..f6bc29f515fb 100644 --- a/drivers/irqchip/irq-gic.c +++ b/drivers/irqchip/irq-gic.c @@ -1690,7 +1690,7 @@ static int __init gic_v2_acpi_init(union acpi_subtable_headers *header, return ret; } - acpi_set_irq_model(ACPI_IRQ_MODEL_GIC, gic_v2_get_gsi_domain_id); + acpi_set_irq_model(ACPI_IRQ_MODEL_GIC, gic_v2_get_gsi_domain_id, NULL); if (IS_ENABLED(CONFIG_ARM_GIC_V2M)) gicv2m_init(NULL, gic_data[0].domain); diff --git a/drivers/irqchip/irq-loongarch-cpu.c b/drivers/irqchip/irq-loongarch-cpu.c index 950bc087e388..84ce24889488 100644 --- a/drivers/irqchip/irq-loongarch-cpu.c +++ b/drivers/irqchip/irq-loongarch-cpu.c @@ -168,7 +168,7 @@ static int __init cpuintc_acpi_init(union acpi_subtable_headers *header, panic("Failed to add irqdomain for LoongArch CPU"); set_handle_irq(&handle_cpu_irq); - acpi_set_irq_model(ACPI_IRQ_MODEL_LPIC, lpic_get_gsi_domain_id); + acpi_set_irq_model(ACPI_IRQ_MODEL_LPIC, lpic_get_gsi_domain_id, NULL); acpi_set_gsi_to_irq_fallback(lpic_gsi_to_irq); ret = acpi_cascade_irqdomain_init(); diff --git a/drivers/irqchip/irq-riscv-intc.c b/drivers/irqchip/irq-riscv-intc.c index 84418dbd5a27..0595144116e2 100644 --- a/drivers/irqchip/irq-riscv-intc.c +++ b/drivers/irqchip/irq-riscv-intc.c @@ -384,7 +384,8 @@ static int __init riscv_intc_acpi_init(union acpi_subtable_headers *header, if (rc) irq_domain_free_fwnode(fn); else - acpi_set_irq_model(ACPI_IRQ_MODEL_RINTC, riscv_acpi_get_gsi_domain_id); + acpi_set_irq_model(ACPI_IRQ_MODEL_RINTC, riscv_acpi_get_gsi_domain_id, + acpi_get_riscv_gsi_handle); return rc; } diff --git a/include/linux/acpi.h b/include/linux/acpi.h index becd1f098554..899dc00b4b01 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -366,9 +366,10 @@ int acpi_gsi_to_irq (u32 gsi, unsigned int *irq); int acpi_isa_irq_to_gsi (unsigned isa_irq, u32 *gsi); typedef struct fwnode_handle *(*acpi_gsi_domain_disp_fn)(u32); +typedef acpi_handle (*acpi_gsi_handle_disp_fn)(u32); void acpi_set_irq_model(enum acpi_irq_model_id model, - acpi_gsi_domain_disp_fn fn); + acpi_gsi_domain_disp_fn fn, acpi_gsi_handle_disp_fn gsi_dep_fn); acpi_gsi_domain_disp_fn acpi_get_gsi_dispatcher(void); void acpi_set_gsi_to_irq_fallback(u32 (*)(u32)); @@ -378,6 +379,8 @@ struct irq_domain *acpi_irq_create_hierarchy(unsigned int flags, const struct irq_domain_ops *ops, void *host_data); +u32 acpi_irq_add_auto_dep(acpi_handle handle); + #ifdef CONFIG_X86_IO_APIC extern int acpi_get_override_irq(u32 gsi, int *trigger, int *polarity); #else From 5a64611747687189415a5298a16e1ad4c08a829b Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 9 Jul 2026 10:40:16 +0200 Subject: [PATCH 12/61] ACPI/IORT: Implement ACPI infrastructure to enable GICv5 IWB probe deferral Implement an IORT ACPI hook to retrieve the acpi_handle of the interrupt controller handling a specific GSI (if any, on GICv5 systems only the IWB is represented in firmware with an ACPI device object) and add the IWB to the list of devices whose dependencies can be detected (and cleared) in ACPI core to guarantee that probe dependencies for the IWB can be satisfied. Enable autodep detection for arm64 by adding the arch_acpi_add_auto_dep() callback in the ACPI IORT driver. Signed-off-by: Lorenzo Pieralisi Link: https://patch.msgid.link/20260709-gic-v5-acpi-iwb-probe-deferral-v4-6-48dae790f871@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/arm64/iort.c | 22 +++++++++++++++++++--- drivers/acpi/scan.c | 1 + drivers/irqchip/irq-gic-v5.c | 2 +- include/linux/acpi_iort.h | 3 ++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/arm64/iort.c b/drivers/acpi/arm64/iort.c index af7a9b2fd5bc..34412cd697d8 100644 --- a/drivers/acpi/arm64/iort.c +++ b/drivers/acpi/arm64/iort.c @@ -789,11 +789,9 @@ struct irq_domain *iort_get_device_domain(struct device *dev, u32 id, return irq_find_matching_fwnode(handle, bus_token); } -struct fwnode_handle *iort_iwb_handle(u32 iwb_id) +acpi_handle iort_iwb_handle(u32 iwb_id) { - struct fwnode_handle *fwnode; struct acpi_iort_node *node; - struct acpi_device *device; struct acpi_iort_iwb *iwb; acpi_status status; acpi_handle handle; @@ -808,6 +806,19 @@ struct fwnode_handle *iort_iwb_handle(u32 iwb_id) if (ACPI_FAILURE(status)) return NULL; + return handle; +} + +struct fwnode_handle *iort_iwb_handle_fwnode(u32 iwb_id) +{ + struct fwnode_handle *fwnode; + struct acpi_device *device; + acpi_handle handle; + + handle = iort_iwb_handle(iwb_id); + if (!handle) + return NULL; + device = acpi_get_acpi_dev(handle); if (!device) return NULL; @@ -2090,6 +2101,11 @@ static void __init iort_init_platform_devices(void) } } +u32 arch_acpi_add_auto_dep(acpi_handle handle) +{ + return acpi_irq_add_auto_dep(handle); +} + void __init acpi_iort_init(void) { acpi_status status; diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 9a7ac2eb9ce0..25d09474f889 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -854,6 +854,7 @@ static const char * const acpi_ignore_dep_ids[] = { /* List of HIDs for which we honor deps of matching ACPI devs, when checking _DEP lists. */ static const char * const acpi_honor_dep_ids[] = { + "ARMH0003", /* ARM GICv5 IWB */ "INT3472", /* Camera sensor PMIC / clk and regulator info */ "INTC1059", /* IVSC (TGL) driver must be loaded to allow i2c access to camera sensors */ "INTC1095", /* IVSC (ADL) driver must be loaded to allow i2c access to camera sensors */ diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c index b9f648d8e0f0..7ea39cb19f75 100644 --- a/drivers/irqchip/irq-gic-v5.c +++ b/drivers/irqchip/irq-gic-v5.c @@ -1226,7 +1226,7 @@ static struct fwnode_handle *gsi_domain_handle; static struct fwnode_handle *gic_v5_get_gsi_domain_id(u32 gsi) { if (FIELD_GET(GICV5_GSI_IC_TYPE, gsi) == GICV5_GSI_IWB_TYPE) - return iort_iwb_handle(FIELD_GET(GICV5_GSI_IWB_FRAME_ID, gsi)); + return iort_iwb_handle_fwnode(FIELD_GET(GICV5_GSI_IWB_FRAME_ID, gsi)); return gsi_domain_handle; } diff --git a/include/linux/acpi_iort.h b/include/linux/acpi_iort.h index 17bb3374f4ca..931eaa7bbf6a 100644 --- a/include/linux/acpi_iort.h +++ b/include/linux/acpi_iort.h @@ -27,7 +27,8 @@ int iort_register_domain_token(int trans_id, phys_addr_t base, struct fwnode_handle *fw_node); void iort_deregister_domain_token(int trans_id); struct fwnode_handle *iort_find_domain_token(int trans_id); -struct fwnode_handle *iort_iwb_handle(u32 iwb_id); +acpi_handle iort_iwb_handle(u32 iwb_id); +struct fwnode_handle *iort_iwb_handle_fwnode(u32 iwb_id); #ifdef CONFIG_ACPI_IORT u32 iort_msi_map_id(struct device *dev, u32 id); From d2aa7b179a711d045cdeb4dfb33428207ae32b4b Mon Sep 17 00:00:00 2001 From: Lorenzo Pieralisi Date: Thu, 9 Jul 2026 10:40:17 +0200 Subject: [PATCH 13/61] irqchip/gic-v5: Enable GICv5 IWB ACPI probe ordering detection Register an ACPI hook in the ACPI interrupt management code for GICv5 to retrieve the ACPI interrupt controller handle (if any) of the controller handling a specific GSI, by updating the acpi_set_irq_model() call with the gic_v5_get_gsi_handle() function pointer parameter. gicv5_get_gsi_handle() allows ACPI core to detect the ACPI handle of the controller that manages a specific GSI interrupt. Update the IWB driver to clear device dependencies in ACPI core once the IWB driver has probed. Signed-off-by: Lorenzo Pieralisi Acked-by: Thomas Gleixner Acked-by: Marc Zyngier Link: https://patch.msgid.link/20260709-gic-v5-acpi-iwb-probe-deferral-v4-7-48dae790f871@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/irqchip/irq-gic-v5-iwb.c | 2 ++ drivers/irqchip/irq-gic-v5.c | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/drivers/irqchip/irq-gic-v5-iwb.c b/drivers/irqchip/irq-gic-v5-iwb.c index 9103feb70ce8..6b02b90d9cce 100644 --- a/drivers/irqchip/irq-gic-v5-iwb.c +++ b/drivers/irqchip/irq-gic-v5-iwb.c @@ -269,6 +269,8 @@ static int gicv5_iwb_device_probe(struct platform_device *pdev) if (IS_ERR(iwb_node)) return PTR_ERR(iwb_node); + acpi_device_clear_deps(&pdev->dev); + return 0; } diff --git a/drivers/irqchip/irq-gic-v5.c b/drivers/irqchip/irq-gic-v5.c index 7ea39cb19f75..5dcf404ab4c8 100644 --- a/drivers/irqchip/irq-gic-v5.c +++ b/drivers/irqchip/irq-gic-v5.c @@ -1231,6 +1231,14 @@ static struct fwnode_handle *gic_v5_get_gsi_domain_id(u32 gsi) return gsi_domain_handle; } +static acpi_handle gic_v5_get_gsi_handle(u32 gsi) +{ + if (FIELD_GET(GICV5_GSI_IC_TYPE, gsi) == GICV5_GSI_IWB_TYPE) + return iort_iwb_handle(FIELD_GET(GICV5_GSI_IWB_FRAME_ID, gsi)); + + return NULL; +} + static int __init gic_acpi_init(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_madt_gicv5_irs *irs = (struct acpi_madt_gicv5_irs *)header; @@ -1251,7 +1259,8 @@ static int __init gic_acpi_init(union acpi_subtable_headers *header, const unsig if (ret) goto out_irs; - acpi_set_irq_model(ACPI_IRQ_MODEL_GIC_V5, gic_v5_get_gsi_domain_id, NULL); + acpi_set_irq_model(ACPI_IRQ_MODEL_GIC_V5, gic_v5_get_gsi_domain_id, + gic_v5_get_gsi_handle); return 0; From 253ed6e24c9a92899dd798a8aaba8c6dc0a41920 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 8 Jul 2026 20:35:47 +0200 Subject: [PATCH 14/61] ACPI: fan: Use devm_acpi_install_notify_handler() Replace the custom open-coded devres-based management of an ACPI notify handler with devm_acpi_install_notify_handler(). No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Armin Wolf Link: https://patch.msgid.link/2866967.mvXUDI8C0e@rafael.j.wysocki --- drivers/acpi/fan_core.c | 25 ++++--------------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/drivers/acpi/fan_core.c b/drivers/acpi/fan_core.c index fb08b8549ed7..624d0736b581 100644 --- a/drivers/acpi/fan_core.c +++ b/drivers/acpi/fan_core.c @@ -489,26 +489,6 @@ static void acpi_fan_notify_handler(acpi_handle handle, u32 event, void *context } } -static void acpi_fan_notify_remove(void *data) -{ - struct acpi_fan *fan = data; - - acpi_remove_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY, acpi_fan_notify_handler); -} - -static int devm_acpi_fan_notify_init(struct device *dev) -{ - struct acpi_fan *fan = dev_get_drvdata(dev); - acpi_status status; - - status = acpi_install_notify_handler(fan->handle, ACPI_DEVICE_NOTIFY, - acpi_fan_notify_handler, dev); - if (ACPI_FAILURE(status)) - return -EIO; - - return devm_add_action_or_reset(dev, acpi_fan_notify_remove, fan); -} - static int acpi_fan_probe(struct platform_device *pdev) { int result = 0; @@ -556,7 +536,10 @@ static int acpi_fan_probe(struct platform_device *pdev) if (result) return result; - result = devm_acpi_fan_notify_init(&pdev->dev); + result = devm_acpi_install_notify_handler(&pdev->dev, + ACPI_DEVICE_NOTIFY, + acpi_fan_notify_handler, + &pdev->dev); if (result) return result; From ad5a796b9e0099754b8ed0f492fbbc000f4668a0 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 6 Jul 2026 14:33:23 +0200 Subject: [PATCH 15/61] platform/loongarch: laptop: Stop setting acpi_device_class() The driver populates acpi_device_class() which is never read afterward, so make it stop doing that and drop the symbol defined specifically for this purpose. No intentional functional impact. This will facilitate the removal of device_class from struct acpi_device_pnp in the future. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2444678.ElGaqSPkdT@rafael.j.wysocki --- drivers/platform/loongarch/loongson-laptop.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/platform/loongarch/loongson-laptop.c b/drivers/platform/loongarch/loongson-laptop.c index 61b18ac206c9..f2dcc6d78051 100644 --- a/drivers/platform/loongarch/loongson-laptop.c +++ b/drivers/platform/loongarch/loongson-laptop.c @@ -30,7 +30,6 @@ #define LOONGSON_ACPI_HKEY_HID "LOON0000" #define ACPI_LAPTOP_NAME "loongson-laptop" -#define ACPI_LAPTOP_ACPI_EVENT_PREFIX "loongson" #define MAX_ACPI_ARGS 3 #define GENERIC_HOTKEY_MAP_MAX 64 @@ -167,8 +166,6 @@ static int __init setup_acpi_notify(struct generic_sub_driver *sub_driver) } sub_driver->device->driver_data = sub_driver; - sprintf(acpi_device_class(sub_driver->device), "%s/%s", - ACPI_LAPTOP_ACPI_EVENT_PREFIX, sub_driver->name); status = acpi_install_notify_handler(*sub_driver->handle, sub_driver->type, dispatch_acpi_notify, sub_driver); From d215a3b2fe63bac012f76cfb5aabc919d77a2bf9 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 6 Jul 2026 14:42:20 +0200 Subject: [PATCH 16/61] ACPI: PAD: xen: Stop setting acpi_device_name/class() The driver sets acpi_device_name() and acpi_device_class() which are never read afterward, so make it stop doing that and drop the symbols defined specifically for this purpose. No intentional functional impact. This will facilitate the removal of device_name and device_class from struct acpi_device_pnp in the future. Signed-off-by: Rafael J. Wysocki Acked-by: Juergen Gross Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2384190.iZASKD2KPV@rafael.j.wysocki --- drivers/xen/xen-acpi-pad.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/xen/xen-acpi-pad.c b/drivers/xen/xen-acpi-pad.c index 5b98e0e93807..2fa9c7d3581f 100644 --- a/drivers/xen/xen-acpi-pad.c +++ b/drivers/xen/xen-acpi-pad.c @@ -17,8 +17,6 @@ #include #include -#define ACPI_PROCESSOR_AGGREGATOR_CLASS "acpi_pad" -#define ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME "Processor Aggregator" #define ACPI_PROCESSOR_AGGREGATOR_NOTIFY 0x80 static DEFINE_MUTEX(xen_cpu_lock); @@ -117,9 +115,6 @@ static int acpi_pad_probe(struct platform_device *pdev) if (!device) return -ENODEV; - strcpy(acpi_device_name(device), ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME); - strcpy(acpi_device_class(device), ACPI_PROCESSOR_AGGREGATOR_CLASS); - status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, acpi_pad_notify, device); if (ACPI_FAILURE(status)) From d4232e1c796c0b3de0dc3f427d14849c1b8c4e03 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 6 Jul 2026 14:43:32 +0200 Subject: [PATCH 17/61] PNP: ACPI: Stop using acpi_device_name() Since acpi_device_name() checked by pnpacpi_add_device() is never populated, its length is always zero and the codition checking it is always false. Accordingly, drop that condition and use acpi_device_bid(device) for setting dev->name in pnpacpi_add_device() unconditionally. No intentional functional impact. This will facilitate the removal of device_name from struct acpi_device_pnp in the future. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/23191780.EfDdHjke4D@rafael.j.wysocki --- drivers/pnp/pnpacpi/core.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/pnp/pnpacpi/core.c b/drivers/pnp/pnpacpi/core.c index fbf03ff007eb..d8ec0ad2ab4e 100644 --- a/drivers/pnp/pnpacpi/core.c +++ b/drivers/pnp/pnpacpi/core.c @@ -248,10 +248,7 @@ static int __init pnpacpi_add_device(struct acpi_device *device) if (acpi_has_method(device->handle, "_DIS")) dev->capabilities |= PNP_DISABLE; - if (strlen(acpi_device_name(device))) - strscpy(dev->name, acpi_device_name(device), sizeof(dev->name)); - else - strscpy(dev->name, acpi_device_bid(device), sizeof(dev->name)); + strscpy(dev->name, acpi_device_bid(device), sizeof(dev->name)); if (dev->active) pnpacpi_parse_allocated_resource(dev); From dbd3f825128d8c74572c2e66f5d3828ccbfe816c Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Tue, 30 Jun 2026 13:55:58 +0300 Subject: [PATCH 18/61] ACPI: NVS: replace __get_free_page() with kmalloc() suspend_nvs_alloc() allocates shadow pages for saving and restoring ACPI Non-Volatile Storage regions across suspend/resume. These buffers can be allocated with kmalloc() as there's nothing special about them to go directly to the page allocator. kmalloc() provides a better API that does not require ugly casts and kfree() does not need to know the size of the freed object. Performance difference between kmalloc() and __get_free_pages() is not measurable as both allocators take an object/page from a per-CPU list for fast path allocations. For the slow path the performance is anyway determined by the amount of reclaim involved rather than by what allocator is used. Replace use of __get_free_page() with kmalloc() and free_page() with kfree(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Signed-off-by: Mike Rapoport (Microsoft) Link: https://patch.msgid.link/20260630-b4-acpi-v1-1-a9f59c04d221@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/nvs.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/nvs.c b/drivers/acpi/nvs.c index 6eaad7dd0241..12aee4102696 100644 --- a/drivers/acpi/nvs.c +++ b/drivers/acpi/nvs.c @@ -133,7 +133,7 @@ void suspend_nvs_free(void) list_for_each_entry(entry, &nvs_list, node) if (entry->data) { - free_page((unsigned long)entry->data); + kfree(entry->data); entry->data = NULL; if (entry->kaddr) { if (entry->unmap) { @@ -156,7 +156,7 @@ int suspend_nvs_alloc(void) struct nvs_page *entry; list_for_each_entry(entry, &nvs_list, node) { - entry->data = (void *)__get_free_page(GFP_KERNEL); + entry->data = kmalloc(PAGE_SIZE, GFP_KERNEL); if (!entry->data) { suspend_nvs_free(); return -ENOMEM; From 458b40fcdd04a9a0334830de5a536d8425454e4b Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Mon, 6 Jul 2026 17:41:41 +0800 Subject: [PATCH 19/61] ACPI: FPDT: validate table and record lengths FPDT records are supplied by firmware and are walked through length fields stored in the table itself. Check the main-table entry length before reading an entry, check the mapped subtable length before remapping it, and check each record length before saving a typed record pointer for sysfs. This keeps malformed firmware records from being interpreted as complete resume, suspend, or boot records when the current item is shorter than the structure consumed by the driver. Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260706094141.82438-1-pengpeng@iscas.ac.cn Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_fpdt.c | 55 +++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/acpi_fpdt.c b/drivers/acpi/acpi_fpdt.c index e75dd28d31a9..79191ed20f76 100644 --- a/drivers/acpi/acpi_fpdt.c +++ b/drivers/acpi/acpi_fpdt.c @@ -168,7 +168,7 @@ static int fpdt_process_subtable(u64 address, u32 subtable_type) struct fpdt_subtable_header *subtable_header; struct fpdt_record_header *record_header; char *signature = (subtable_type == SUBTABLE_FBPT ? "FBPT" : "S3PT"); - u32 length, offset; + u32 length, offset, remaining; int result; if (!fpdt_address_valid(address)) { @@ -182,10 +182,17 @@ static int fpdt_process_subtable(u64 address, u32 subtable_type) if (strncmp((char *)&subtable_header->signature, signature, 4)) { pr_info(FW_BUG "subtable signature and type mismatch!\n"); + acpi_os_unmap_memory(subtable_header, sizeof(*subtable_header)); return -EINVAL; } length = subtable_header->length; + if (length < sizeof(*subtable_header)) { + pr_err(FW_BUG "Invalid FPDT subtable length %u.\n", length); + acpi_os_unmap_memory(subtable_header, sizeof(*subtable_header)); + return -EINVAL; + } + acpi_os_unmap_memory(subtable_header, sizeof(*subtable_header)); subtable_header = acpi_os_map_memory(address, length); @@ -194,17 +201,29 @@ static int fpdt_process_subtable(u64 address, u32 subtable_type) offset = sizeof(*subtable_header); while (offset < length) { - record_header = (void *)subtable_header + offset; - offset += record_header->length; - - if (!record_header->length) { - pr_err(FW_BUG "Zero-length record found in FPTD.\n"); + remaining = length - offset; + if (remaining < sizeof(*record_header)) { + pr_err(FW_BUG "Truncated FPDT record header.\n"); result = -EINVAL; goto err; } + record_header = (void *)subtable_header + offset; + if (record_header->length < sizeof(*record_header) || + record_header->length > remaining) { + pr_err(FW_BUG "Invalid FPDT record length %u.\n", + record_header->length); + result = -EINVAL; + goto err; + } + offset += record_header->length; + switch (record_header->type) { case RECORD_S3_RESUME: + if (record_header->length < sizeof(*record_resume)) { + result = -EINVAL; + goto err; + } if (subtable_type != SUBTABLE_S3PT) { pr_err(FW_BUG "Invalid record %d for subtable %s\n", record_header->type, signature); @@ -221,6 +240,10 @@ static int fpdt_process_subtable(u64 address, u32 subtable_type) goto err; break; case RECORD_S3_SUSPEND: + if (record_header->length < sizeof(*record_suspend)) { + result = -EINVAL; + goto err; + } if (subtable_type != SUBTABLE_S3PT) { pr_err(FW_BUG "Invalid %d for subtable %s\n", record_header->type, signature); @@ -236,6 +259,10 @@ static int fpdt_process_subtable(u64 address, u32 subtable_type) goto err; break; case RECORD_BOOT: + if (record_header->length < sizeof(*record_boot)) { + result = -EINVAL; + goto err; + } if (subtable_type != SUBTABLE_FBPT) { pr_err(FW_BUG "Invalid %d for subtable %s\n", record_header->type, signature); @@ -317,7 +344,21 @@ static int __init acpi_init_fpdt(void) } while (offset < header->length) { + if (header->length - offset < sizeof(*subtable)) { + pr_err(FW_BUG "Truncated FPDT subtable entry.\n"); + result = -EINVAL; + goto err_subtable; + } + subtable = (void *)header + offset; + if (subtable->length < sizeof(*subtable) || + subtable->length > header->length - offset) { + pr_err(FW_BUG "Invalid FPDT subtable entry length %u.\n", + subtable->length); + result = -EINVAL; + goto err_subtable; + } + switch (subtable->type) { case SUBTABLE_FBPT: case SUBTABLE_S3PT: @@ -330,7 +371,7 @@ static int __init acpi_init_fpdt(void) /* Other types are reserved in ACPI 6.4 spec. */ break; } - offset += sizeof(*subtable); + offset += subtable->length; } return 0; err_subtable: From 257f2153762fa80912f5311a0fe88d25a57b6f29 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Thu, 9 Jul 2026 22:31:06 -0700 Subject: [PATCH 20/61] docs: ACPI: DSD: motorcomm: fix docs build error Separate the title with commas (since they are all run together in the produced output) and add a blank line before the descriptive text to avoid a docs build error: Documentation/firmware-guide/acpi/dsd/motorcomm-yt8xxx-phy.rst:70: ERROR: Unexpected indentation. [docutils] Fixes: 28f431eac1e5 ("docs: acpi: dsd: add Motorcomm yt8xxx PHY properties") Signed-off-by: Randy Dunlap Reviewed-by: Andrew Lunn Link: https://patch.msgid.link/20260710053106.730399-1-rdunlap@infradead.org Signed-off-by: Rafael J. Wysocki --- .../firmware-guide/acpi/dsd/motorcomm-yt8xxx-phy.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Documentation/firmware-guide/acpi/dsd/motorcomm-yt8xxx-phy.rst b/Documentation/firmware-guide/acpi/dsd/motorcomm-yt8xxx-phy.rst index d64a396fac81..78d9b712721c 100644 --- a/Documentation/firmware-guide/acpi/dsd/motorcomm-yt8xxx-phy.rst +++ b/Documentation/firmware-guide/acpi/dsd/motorcomm-yt8xxx-phy.rst @@ -64,9 +64,10 @@ defaults as [motorcomm-yt8xxx]_. Enables adjustments related to ``motorcomm,tx-clk-*-inverted`` usage; see [motorcomm-yt8xxx]_. -``motorcomm,tx-clk-10-inverted`` (boolean, optional) -``motorcomm,tx-clk-100-inverted`` (boolean, optional) +``motorcomm,tx-clk-10-inverted`` (boolean, optional), +``motorcomm,tx-clk-100-inverted`` (boolean, optional), ``motorcomm,tx-clk-1000-inverted`` (boolean, optional) + Per-speed TX clock inversion options; see [motorcomm-yt8xxx]_. ASL example (illustrative) From 6587dd87595cfae6bf1304cd0cd1df2274424e42 Mon Sep 17 00:00:00 2001 From: Kate Hsuan Date: Fri, 10 Jul 2026 20:51:22 +0800 Subject: [PATCH 21/61] ACPI: battery: Sanitise model_number by dropping unprintable characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The battery Embedded Controller (EC) may return the model name with trailing unprintable or non-ASCII characters. For example, on some systems: $ cat /sys/class/power_supply/BAT0/model_name LNV-5B10W51864�� If a non-ASCII or an unprintable character is found, it will be replaced with '\0' to ensure the model_number is a valid string. If left intact, the malformed string prevents udev rules and hwdb working correctly. Link: https://gitlab.freedesktop.org/upower/upower/-/work_items/345 Signed-off-by: Kate Hsuan Reviewed-by: Mark Pearson [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260710125122.1621877-1-hpa@redhat.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/battery.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index f5e0eb299610..4409fb95b988 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -10,6 +10,7 @@ #define pr_fmt(fmt) "ACPI: battery: " fmt +#include #include #include #include @@ -483,6 +484,15 @@ static int acpi_battery_get_status(struct acpi_battery *battery) return 0; } +static void acpi_battery_clean_unprintable_chars(char *str, size_t length) +{ + for (unsigned int i = 0; i < length; i++) { + if (!isascii(str[i]) || !isprint(str[i])) { + str[i] = '\0'; + break; + } + } +} static int extract_battery_info(const int use_bix, struct acpi_battery *battery, @@ -524,6 +534,10 @@ static int extract_battery_info(const int use_bix, battery->capacity_now > battery->full_charge_capacity) battery->capacity_now = battery->full_charge_capacity; + if (!result) + acpi_battery_clean_unprintable_chars(battery->model_number, + ARRAY_SIZE(battery->model_number)); + return result; } From c75c99aafef5f36520547f9f319f774035cfea9c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 14 Jul 2026 20:53:40 +0200 Subject: [PATCH 22/61] ACPI: utils: Introduce acpi_dev_is_video_device() helper There are a couple of users that open code functionality of matching a given handle against ACPI video device IDs. The current approach duplicates ID table along with the matching code. Consolidate it under the acpi_dev_is_video_device() helper's hood. Signed-off-by: Andy Shevchenko [ rjw: Moved the static var declaration into the function body ] Link: https://patch.msgid.link/20260714185915.865396-2-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/utils.c | 17 +++++++++++++++++ include/linux/acpi.h | 1 + 2 files changed, 18 insertions(+) diff --git a/drivers/acpi/utils.c b/drivers/acpi/utils.c index 6ab27e4826d1..d499b72574ab 100644 --- a/drivers/acpi/utils.c +++ b/drivers/acpi/utils.c @@ -1047,6 +1047,23 @@ static int __init acpi_backlight(char *str) } __setup("acpi_backlight=", acpi_backlight); +/** + * acpi_dev_is_video_device - test if device matches against ACPI video device IDs + * @adev: ACPI device to test + * + * Return: true when matches, otherwise false. + */ +bool acpi_dev_is_video_device(struct acpi_device *adev) +{ + static const struct acpi_device_id video_device_ids[] = { + { .id = ACPI_VIDEO_HID }, + { } + }; + + return adev && !acpi_match_device_ids(adev, video_device_ids); +} +EXPORT_SYMBOL(acpi_dev_is_video_device); + /** * acpi_match_platform_list - Check if the system matches with a given list * @plat: pointer to acpi_platform_list table terminated by a NULL entry diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 67effb91fa98..c6bd5ee1688d 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -448,6 +448,7 @@ extern char *wmi_get_acpi_device_uid(const char *guid); #define ACPI_VIDEO_OUTPUT_SWITCHING_DMI_VIDEO 0x0800 extern char acpi_video_backlight_string[]; +extern bool acpi_dev_is_video_device(struct acpi_device *adev); extern long acpi_is_video_device(acpi_handle handle); extern void acpi_osi_setup(char *str); From 998a168134be6d7fc12d581467066171abc19448 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 14 Jul 2026 20:53:41 +0200 Subject: [PATCH 23/61] ACPI: scan: Convert to use acpi_dev_is_video_device() helper Replace open coded variant of acpi_dev_is_video_device() helper. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260714185915.865396-3-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/scan.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 530547cda8b2..25d371cef3cb 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1766,7 +1766,6 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) * Some ACPI devs contain SerialBus resources even though they are not * attached to a serial bus at all. */ - {ACPI_VIDEO_HID, }, {"MSHW0028", }, /* * HIDs of device with an UartSerialBusV2 resource for which userspace @@ -1789,6 +1788,9 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) fwnode_property_present(&device->fwnode, "baud"))) return true; + if (acpi_dev_is_video_device(device)) + return false; + if (!acpi_match_device_ids(device, ignore_serial_bus_ids)) return false; From ad921e94ffe1e66df8b2deed01affdd5d8716dc6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 14 Jul 2026 20:53:42 +0200 Subject: [PATCH 24/61] ACPI: video: Convert to use acpi_dev_is_video_device() helper Replace open coded variant of acpi_dev_is_video_device() helper. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260714185915.865396-4-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/video_detect.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index 458efa4fe9d4..cf267b518d2b 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -67,12 +67,7 @@ find_video(acpi_handle handle, u32 lvl, void *context, void **rv) long *cap = context; struct pci_dev *dev; - static const struct acpi_device_id video_ids[] = { - {ACPI_VIDEO_HID, 0}, - {"", 0}, - }; - - if (acpi_dev && !acpi_match_device_ids(acpi_dev, video_ids)) { + if (acpi_dev_is_video_device(acpi_dev)) { dev = acpi_get_pci_dev(handle); if (!dev) return AE_OK; From 4bff98d343e7450384352637d5b5d20f7ddc749a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 14 Jul 2026 20:53:43 +0200 Subject: [PATCH 25/61] i2c: acpi: Convert to use acpi_dev_is_video_device() helper Replace open coded variant of acpi_dev_is_video_device() helper. Acked-by: Wolfram Sang Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260714185915.865396-5-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/i2c/i2c-core-acpi.c | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c index 28c0e4884a7f..4aec8aeea826 100644 --- a/drivers/i2c/i2c-core-acpi.c +++ b/drivers/i2c/i2c-core-acpi.c @@ -128,15 +128,6 @@ static int i2c_acpi_fill_info(struct acpi_resource *ares, void *data) return 1; } -static const struct acpi_device_id i2c_acpi_ignored_device_ids[] = { - /* - * ACPI video acpi_devices, which are handled by the acpi-video driver - * sometimes contain a SERIAL_TYPE_I2C ACPI resource, ignore these. - */ - { ACPI_VIDEO_HID, 0 }, - {} -}; - struct i2c_acpi_irq_context { int irq; bool wake_capable; @@ -155,7 +146,11 @@ static int i2c_acpi_do_lookup(struct acpi_device *adev, if (!acpi_dev_ready_for_enumeration(adev)) return -ENODEV; - if (acpi_match_device_ids(adev, i2c_acpi_ignored_device_ids) == 0) + /* + * ACPI video devices, which are handled by the acpi-video driver, + * sometimes contain a SERIAL_TYPE_I2C ACPI resource, ignore these. + */ + if (acpi_dev_is_video_device(adev)) return -ENODEV; memset(info, 0, sizeof(*info)); From 6fc4e1da76ad692a5e24ee68b7b41bc180c8ab75 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 14 Jul 2026 20:53:44 +0200 Subject: [PATCH 26/61] PCI/VGA: Convert to use acpi_dev_is_video_device() helper Replace open coded variant of acpi_dev_is_video_device() helper. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260714185915.865396-6-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/pci/vgaarb.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/pci/vgaarb.c b/drivers/pci/vgaarb.c index c360eee11dd9..3de05aee7859 100644 --- a/drivers/pci/vgaarb.c +++ b/drivers/pci/vgaarb.c @@ -575,9 +575,7 @@ static bool vga_is_firmware_default(struct pci_dev *pdev) static bool vga_arb_integrated_gpu(struct device *dev) { #if defined(CONFIG_ACPI) - struct acpi_device *adev = ACPI_COMPANION(dev); - - return adev && !strcmp(acpi_device_hid(adev), ACPI_VIDEO_HID); + return acpi_dev_is_video_device(ACPI_COMPANION(dev)); #else return false; #endif From 8e3e2870c03efa276a3d447aa8f2c338cf368067 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 14 Jul 2026 20:53:45 +0200 Subject: [PATCH 27/61] platform/x86: thinkpad_acpi: Convert to use acpi_dev_is_video_device() helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace open coded variant of acpi_dev_is_video_device() helper. Signed-off-by: Andy Shevchenko Acked-by: Ilpo Järvinen Link: https://patch.msgid.link/20260714185915.865396-7-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/platform/x86/lenovo/thinkpad_acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/lenovo/thinkpad_acpi.c b/drivers/platform/x86/lenovo/thinkpad_acpi.c index e1cee42a1683..75770b4bc757 100644 --- a/drivers/platform/x86/lenovo/thinkpad_acpi.c +++ b/drivers/platform/x86/lenovo/thinkpad_acpi.c @@ -767,7 +767,7 @@ static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle, if (!strcmp(context, "video")) { struct acpi_device *dev = acpi_fetch_acpi_dev(handle); - if (!dev || strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev))) + if (!acpi_dev_is_video_device(dev)) return AE_OK; } From e71bdbce27dcaa7f467a3a198cbe723924f05569 Mon Sep 17 00:00:00 2001 From: Zhu Ling Date: Wed, 15 Jul 2026 09:25:19 +0800 Subject: [PATCH 28/61] ACPI: EC: Avoid _REG disconnect on GPIO IRQ defer EC event delivery uses either a GPE or, on ACPI reduced hardware platforms, a GpioInt resource. The GPE path does not have a provider lookup that can defer, but acpi_dev_gpio_irq_get() can return -EPROBE_DEFER for the GpioInt path. ec_install_handlers() currently installs the EC address space handler and executes _REG before looking up the GPIO IRQ. If the GPIO lookup then defers, acpi_ec_setup() tears the handlers down again. Removing the EC address space handler causes ACPICA to execute _REG for disconnect, so firmware may observe an EC OpRegion connected -> disconnected transition during one failed probe attempt. This is observable when the namespace EC reuses a boot EC that has already installed the EC address space handler. A deferred namespace EC probe can disconnect the already usable boot EC OpRegion until a later reprobe connects it again. AML that gates EC field accesses on _REG state can then return fallback values to other drivers during that window. Prepare the GPIOInt IRQ before publishing EC OpRegion availability to AML. This leaves the GPE path unchanged, keeps non-deferred GPIO lookup errors non-fatal as before, and still lets the existing acpi_ec_setup() error path clean up real handler installation failures. Fixes: f6484cadbcaf ("ACPI: EC: clean up handlers on probe failure in acpi_ec_setup()") Signed-off-by: Zhu Ling [ rjw: Added an empty code line after a conditional ] Link: https://patch.msgid.link/20260715012556.12043-1-zhuling2709@phytium.com.cn Signed-off-by: Rafael J. Wysocki --- drivers/acpi/ec.c | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 64ad4cfa6208..a89f10256dbb 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -1510,6 +1510,24 @@ static bool install_gpio_irq_event_handler(struct acpi_ec *ec) IRQF_SHARED | IRQF_ONESHOT, "ACPI EC", ec) >= 0; } +static int ec_prepare_gpio_irq(struct acpi_ec *ec, struct acpi_device *device) +{ + int irq; + + if (!device || ec->gpe >= 0 || ec->irq >= 0) + return 0; + + /* ACPI reduced hardware platforms use a GpioInt from _CRS. */ + irq = acpi_dev_gpio_irq_get(device, 0); + if (irq == -EPROBE_DEFER) + return irq; + + if (irq >= 0) + ec->irq = irq; + + return 0; +} + /** * ec_install_handlers - Install service callbacks and register query methods. * @ec: Target EC. @@ -1524,7 +1542,6 @@ static bool install_gpio_irq_event_handler(struct acpi_ec *ec) * Return: * -ENODEV if the address space handler cannot be installed, which means * "unable to handle transactions", - * -EPROBE_DEFER if GPIO IRQ acquisition needs to be deferred, * or 0 (success) otherwise. */ static int ec_install_handlers(struct acpi_ec *ec, struct acpi_device *device, @@ -1557,19 +1574,6 @@ static int ec_install_handlers(struct acpi_ec *ec, struct acpi_device *device, if (!device) return 0; - if (ec->gpe < 0) { - /* ACPI reduced hardware platforms use a GpioInt from _CRS. */ - int irq = acpi_dev_gpio_irq_get(device, 0); - /* - * Bail out right away for deferred probing or complete the - * initialization regardless of any other errors. - */ - if (irq == -EPROBE_DEFER) - return -EPROBE_DEFER; - else if (irq >= 0) - ec->irq = irq; - } - if (!test_bit(EC_FLAGS_QUERY_METHODS_INSTALLED, &ec->flags)) { /* Find and register all query methods */ acpi_walk_namespace(ACPI_TYPE_METHOD, ec->handle, 1, @@ -1647,6 +1651,14 @@ static int acpi_ec_setup(struct acpi_ec *ec, struct acpi_device *device, bool ca { int ret; + /* + * GPIO IRQ lookup can defer. Do it before publishing the EC + * OpRegion to AML to avoid a spurious _REG(disconnect). + */ + ret = ec_prepare_gpio_irq(ec, device); + if (ret) + return ret; + /* First EC capable of handling transactions */ if (!first_ec) first_ec = ec; From 2c50ffdc73f3a70d745d249f509fc290754121e6 Mon Sep 17 00:00:00 2001 From: Pengpeng Hou Date: Wed, 15 Jul 2026 16:32:53 +0800 Subject: [PATCH 29/61] ACPI: processor: validate MADT IOAPIC entry bounds The IOAPIC hotplug lookup parses both MADT and _MAT records directly. The MADT walk previously used a subtable's declared length to advance the cursor after only locating a generic header. The _MAT path likewise passed a generic header to the IOAPIC helper. Validate that a current record has a complete generic header, that its declared length is contained in the available record range, and that a typed IOAPIC record contains the full fixed IOAPIC body before reading its fields. Use the same relation for both MADT and _MAT provider paths. Fixes: ecf5636dcd59 ("ACPI: Add interfaces to parse IOAPIC ID for IOAPIC hotplug") Signed-off-by: Pengpeng Hou Link: https://patch.msgid.link/20260715083253.22831-1-pengpeng@iscas.ac.cn Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_core.c | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/processor_core.c b/drivers/acpi/processor_core.c index a4498357bd16..3bf076c150fa 100644 --- a/drivers/acpi/processor_core.c +++ b/drivers/acpi/processor_core.c @@ -336,11 +336,26 @@ int acpi_get_cpuid(acpi_handle handle, int type, u32 acpi_id) EXPORT_SYMBOL_GPL(acpi_get_cpuid); #ifdef CONFIG_ACPI_HOTPLUG_IOAPIC -static int get_ioapic_id(struct acpi_subtable_header *entry, u32 gsi_base, +static bool madt_entry_is_valid(struct acpi_subtable_header *entry, + unsigned long end) +{ + unsigned long start = (unsigned long)entry; + + if (start >= end || end - start < sizeof(*entry)) + return false; + + return entry->length >= sizeof(*entry) && entry->length <= end - start; +} + +static int get_ioapic_id(struct acpi_subtable_header *entry, + const unsigned long end, u32 gsi_base, u64 *phys_addr, int *ioapic_id) { struct acpi_madt_io_apic *ioapic = (struct acpi_madt_io_apic *)entry; + if (!madt_entry_is_valid(entry, end) || BAD_MADT_ENTRY(ioapic, end)) + return 0; + if (ioapic->global_irq_base != gsi_base) return 0; @@ -361,17 +376,19 @@ static int parse_madt_ioapic_entry(u32 gsi_base, u64 *phys_addr) return apic_id; entry = (unsigned long)madt; + if (madt->header.length < sizeof(*madt)) + return apic_id; madt_end = entry + madt->header.length; /* Parse all entries looking for a match. */ entry += sizeof(struct acpi_table_madt); - while (entry + sizeof(struct acpi_subtable_header) < madt_end) { + while (madt_entry_is_valid((struct acpi_subtable_header *)entry, + madt_end)) { hdr = (struct acpi_subtable_header *)entry; if (hdr->type == ACPI_MADT_TYPE_IO_APIC && - get_ioapic_id(hdr, gsi_base, phys_addr, &apic_id)) + get_ioapic_id(hdr, madt_end, gsi_base, phys_addr, &apic_id)) break; - else - entry += hdr->length; + entry += hdr->length; } return apic_id; @@ -398,7 +415,9 @@ static int parse_mat_ioapic_entry(acpi_handle handle, u32 gsi_base, header = (struct acpi_subtable_header *)obj->buffer.pointer; if (header->type == ACPI_MADT_TYPE_IO_APIC) - get_ioapic_id(header, gsi_base, phys_addr, &apic_id); + get_ioapic_id(header, + (unsigned long)header + obj->buffer.length, + gsi_base, phys_addr, &apic_id); exit: kfree(buffer.pointer); From 8a742141f7ab84975aa758b775567ef4740ef0cf Mon Sep 17 00:00:00 2001 From: Chen Pei Date: Wed, 15 Jul 2026 21:50:48 +0800 Subject: [PATCH 30/61] ACPI: PCI: Clear driver_data on all paths that free the acpi_pci_root acpi_pci_root_add() assigns the freshly allocated root to device->driver_data before dmar_device_add() and pci_acpi_scan_root(). Both failure paths reach the end: label where root is kfree()'d, but only the pci_acpi_scan_root() path clears driver_data first. When dmar_device_add() fails during a hot-add, root is freed while device->driver_data still points at it. The ACPI core does not clear driver_data on attach failure, so a later acpi_pci_find_root() call may dereference this dangling pointer. acpi_pci_root_remove() has the same problem: it frees root without clearing device->driver_data, leaving a dangling pointer behind after the root bridge is removed. Move the NULL assignment to the shared end: label so every error path in acpi_pci_root_add() clears driver_data before freeing root, and clear it in acpi_pci_root_remove() as well, so the object is never left reachable through driver_data after being freed. Fixes: db89b4f0dbab ("ACPI: catch calls of acpi_driver_data on pointer of wrong type") Reported-by: Sashiko AI review Link: https://sashiko.dev/#/patchset/20260526025118.38935-1-cp0613@linux.alibaba.com Link: https://sashiko.dev/#/patchset/20260707121258.11640-1-cp0613@linux.alibaba.com Signed-off-by: Chen Pei Link: https://patch.msgid.link/20260715135048.3278-1-cp0613@linux.alibaba.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/pci_root.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 4c06c3ffd0cb..408ba12362a7 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -725,7 +725,6 @@ static int acpi_pci_root_add(struct acpi_device *device, dev_err(&device->dev, "Bus %04x:%02x not present in PCI namespace\n", root->segment, (unsigned int)root->secondary.start); - device->driver_data = NULL; result = -ENODEV; goto remove_dmar; } @@ -765,6 +764,7 @@ static int acpi_pci_root_add(struct acpi_device *device, if (hotadd) dmar_device_remove(handle); end: + device->driver_data = NULL; kfree(root); return result; } @@ -788,6 +788,7 @@ static void acpi_pci_root_remove(struct acpi_device *device) pci_unlock_rescan_remove(); + device->driver_data = NULL; kfree(root); } From 5d42a19dac27dc23b827b1a0ba88e76aa89be011 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 16 Jul 2026 16:04:43 +0200 Subject: [PATCH 31/61] ACPI: PCI: Use a mutex guard to simplify acpi_get_pci_dev() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use a mutex guard in acpi_get_pci_dev() for the physical_node_lock locking and drop local variable pci_dev that becomes redundant after that change. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/3056272.e9J7NaK4W3@rafael.j.wysocki --- drivers/acpi/pci_root.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 408ba12362a7..85202f2f5394 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -8,6 +8,7 @@ #define pr_fmt(fmt) "ACPI: " fmt +#include #include #include #include @@ -307,24 +308,20 @@ struct pci_dev *acpi_get_pci_dev(acpi_handle handle) { struct acpi_device *adev = acpi_fetch_acpi_dev(handle); struct acpi_device_physical_node *pn; - struct pci_dev *pci_dev = NULL; if (!adev) return NULL; - mutex_lock(&adev->physical_node_lock); + guard(mutex)(&adev->physical_node_lock); list_for_each_entry(pn, &adev->physical_node_list, node) { if (dev_is_pci(pn->dev)) { get_device(pn->dev); - pci_dev = to_pci_dev(pn->dev); - break; + return to_pci_dev(pn->dev); } } - mutex_unlock(&adev->physical_node_lock); - - return pci_dev; + return NULL; } EXPORT_SYMBOL_GPL(acpi_get_pci_dev); From 9d4f0ecb87fd939207682ec30dcaf128d43f07fb Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 16 Jul 2026 16:04:53 +0200 Subject: [PATCH 32/61] ACPI: PCI: Introduce acpi_dev_get_pci_dev() Some acpi_get_pci_dev() callers already have a struct ACPI device for which they want to get the struct pci_dev pointer of the associated PCI device, so they don't need to look for one. For this reason, add acpi_dev_get_pci_dev() that will get a PCI device for a given ACPI one (if possible) and turn acpi_get_pci_dev() into a static inline helper passing the acpi_fetch_acpi_dev() return value directly to acpi_dev_get_pci_dev(). No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/3430928.44csPzL39Z@rafael.j.wysocki --- drivers/acpi/pci_root.c | 22 +++++++++++----------- include/acpi/acpi_drivers.h | 9 +++++++-- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 85202f2f5394..6f78f96332ea 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -293,20 +293,20 @@ struct acpi_pci_root *acpi_pci_find_root(acpi_handle handle) EXPORT_SYMBOL_GPL(acpi_pci_find_root); /** - * acpi_get_pci_dev - convert ACPI CA handle to struct pci_dev - * @handle: the handle in question + * acpi_dev_get_pci_dev - Get a struct pci_dev for a given ACPI device + * @adev: Target ACPI device. * - * Given an ACPI CA handle, the desired PCI device is located in the - * list of PCI devices. + * Find the PCI device associated with @adev, if any, and bump up its reference + * counter. * - * If the device is found, its reference count is increased and this - * function returns a pointer to its data structure. The caller must - * decrement the reference count by calling pci_dev_put(). - * If no device is found, %NULL is returned. + * Callers are responsible for dropping the PCI device reference obtained by + * this function. + * + * Return: The struct pci_dev pointer of a reference-counted PCI device on + * success or NULL on failure. */ -struct pci_dev *acpi_get_pci_dev(acpi_handle handle) +struct pci_dev *acpi_dev_get_pci_dev(struct acpi_device *adev) { - struct acpi_device *adev = acpi_fetch_acpi_dev(handle); struct acpi_device_physical_node *pn; if (!adev) @@ -323,7 +323,7 @@ struct pci_dev *acpi_get_pci_dev(acpi_handle handle) return NULL; } -EXPORT_SYMBOL_GPL(acpi_get_pci_dev); +EXPORT_SYMBOL_GPL(acpi_dev_get_pci_dev); /** * acpi_pci_osc_control_set - Request control of PCI root _OSC features. diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index 402b97d12138..bff2ca035fcf 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -59,14 +59,19 @@ int acpi_pci_link_free_irq(acpi_handle handle); struct pci_bus; #ifdef CONFIG_PCI -struct pci_dev *acpi_get_pci_dev(acpi_handle); +struct pci_dev *acpi_dev_get_pci_dev(struct acpi_device *adev); #else -static inline struct pci_dev *acpi_get_pci_dev(acpi_handle handle) +static inline struct pci_dev *acpi_dev_get_pci_dev(struct acpi_device *adev) { return NULL; } #endif +static inline struct pci_dev *acpi_get_pci_dev(acpi_handle handle) +{ + return acpi_dev_get_pci_dev(acpi_fetch_acpi_dev(handle)); +} + /* Arch-defined function to add a bus to the system */ struct pci_bus *pci_acpi_scan_root(struct acpi_pci_root *root); From 2e8c155fe8bf552a4c55dc42f19da78ed2213e39 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 16 Jul 2026 16:05:04 +0200 Subject: [PATCH 33/61] ACPI: video: Drop backlight parent device reference later Update acpi_video_dev_register_backlight() to put the parent device after registering the backlight class device under it instead of attempting to register the backlight class device under a parent that (theoretically) may be gone at that point. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/10885673.nUPlyArG6x@rafael.j.wysocki --- drivers/acpi/acpi_video.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index f93e877f87f6..a738265279cd 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -1719,10 +1719,8 @@ static void acpi_video_dev_register_backlight(struct acpi_video_device *device) if (ACPI_SUCCESS(acpi_get_parent(device->dev->handle, &acpi_parent))) { pdev = acpi_get_pci_dev(acpi_parent); - if (pdev) { + if (pdev) parent = &pdev->dev; - pci_dev_put(pdev); - } } memset(&props, 0, sizeof(struct backlight_properties)); @@ -1734,6 +1732,7 @@ 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; From 1c24aa1bb2006d56d4783a9870bba260392edcf7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 16 Jul 2026 16:05:13 +0200 Subject: [PATCH 34/61] ACPI: video: Use acpi_dev_get_pci_dev() instead of acpi_get_pci_dev() In acpi_video_bus_check() and find_video(), simply replace acpi_get_pci_dev() with acpi_dev_get_pci_dev() that can be used in both places because the ACPI device needed to do the lookup is available. In acpi_video_dev_register_backlight(), instead of doing a parent ACPI handle lookup based on the handle of an ACPI device that is already available, pass that ACPI device to acpi_dev_parent() which is much more straightforward and pass the return value of the latter directly to acpi_dev_get_pci_dev() to get the PCI device associated with it. That allows local variable acpi_parent to be eliminated. Signed-off-by: Rafael J. Wysocki Reviewed-by: Andy Shevchenko Link: https://patch.msgid.link/2047771.PYKUYFuaPT@rafael.j.wysocki --- drivers/acpi/acpi_video.c | 11 ++++------- drivers/acpi/video_detect.c | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index a738265279cd..4d6fd9f6e9ad 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -1048,7 +1048,7 @@ static int acpi_video_bus_check(struct acpi_video_bus *video) if (!video) return -EINVAL; - dev = acpi_get_pci_dev(video->device->handle); + dev = acpi_dev_get_pci_dev(video->device); if (!dev) return -ENODEV; pci_dev_put(dev); @@ -1702,7 +1702,6 @@ static void acpi_video_dev_register_backlight(struct acpi_video_device *device) { struct backlight_properties props; struct pci_dev *pdev; - acpi_handle acpi_parent; struct device *parent = NULL; int result; static int count; @@ -1717,11 +1716,9 @@ static void acpi_video_dev_register_backlight(struct acpi_video_device *device) return; count++; - if (ACPI_SUCCESS(acpi_get_parent(device->dev->handle, &acpi_parent))) { - pdev = acpi_get_pci_dev(acpi_parent); - if (pdev) - parent = &pdev->dev; - } + pdev = acpi_dev_get_pci_dev(acpi_dev_parent(device->dev)); + if (pdev) + parent = &pdev->dev; memset(&props, 0, sizeof(struct backlight_properties)); props.type = BACKLIGHT_FIRMWARE; diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index 458efa4fe9d4..c5b08c19e847 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -73,7 +73,7 @@ find_video(acpi_handle handle, u32 lvl, void *context, void **rv) }; if (acpi_dev && !acpi_match_device_ids(acpi_dev, video_ids)) { - dev = acpi_get_pci_dev(handle); + dev = acpi_dev_get_pci_dev(acpi_dev); if (!dev) return AE_OK; pci_dev_put(dev); From f45c999e67120cbfa472e68877aebd4dd3c3f69e Mon Sep 17 00:00:00 2001 From: Thomas Renninger Date: Fri, 17 Jul 2026 08:51:21 +0200 Subject: [PATCH 35/61] ACPI: Use correct region struct for BERT region size check The structure representing BERT data is struct acpi_bert_region, so its size should be used in the BERT region size check. Update the code in question accordingly. Signed-off-by: Thomas Renninger [ rjw: Changelog edits ] Link: https://patch.msgid.link/20260717065129.72924-2-trenn@suse.de Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sysfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c index 908cc5c7e643..8395efbe248b 100644 --- a/drivers/acpi/sysfs.c +++ b/drivers/acpi/sysfs.c @@ -447,7 +447,7 @@ static int acpi_bert_data_init(void *th, struct acpi_data_attr *data_attr) struct acpi_table_bert *bert = th; if (bert->header.length < sizeof(struct acpi_table_bert) || - bert->region_length < sizeof(struct acpi_hest_generic_status)) { + bert->region_length < sizeof(struct acpi_bert_region)) { kfree(data_attr); return -EINVAL; } From e45ee607ca6f9f97a2f97be356d844a912971a16 Mon Sep 17 00:00:00 2001 From: Thomas Renninger Date: Fri, 17 Jul 2026 08:51:22 +0200 Subject: [PATCH 36/61] ACPI: sysfs: Properly map BERT and CCEL data to their ACPI tables In the case of multiple BERT ACPI tables, one gets: sysfs: cannot create duplicate filename '/firmware/acpi/tables/data/BERT' This is because both: /firmware/acpi/tables/BERT1 /firmware/acpi/tables/BERT2 are attempted to be mapped to the same data table: /firmware/acpi/tables/data/BERT Address this problem by passing and using the same filename for data tables. Signed-off-by: Thomas Renninger Reported-by: Michal Suchanek Closes: https://bugzilla.suse.com/show_bug.cgi?id=1270211 [ rjw: Subject and changelog edits ] Link: https://patch.msgid.link/20260717065129.72924-3-trenn@suse.de Signed-off-by: Rafael J. Wysocki --- drivers/acpi/sysfs.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c index 8395efbe248b..dd4a99f09efe 100644 --- a/drivers/acpi/sysfs.c +++ b/drivers/acpi/sysfs.c @@ -316,6 +316,7 @@ struct acpi_table_attr { struct acpi_data_attr { struct bin_attribute attr; u64 addr; + char filename[ACPI_NAMESEG_SIZE+ACPI_INST_SIZE]; }; static ssize_t acpi_table_show(struct file *filp, struct kobject *kobj, @@ -453,7 +454,6 @@ static int acpi_bert_data_init(void *th, struct acpi_data_attr *data_attr) } data_attr->addr = bert->address; data_attr->attr.size = bert->region_length; - data_attr->attr.attr.name = "BERT"; return sysfs_create_bin_file(tables_data_kobj, &data_attr->attr); } @@ -469,7 +469,6 @@ static int acpi_ccel_data_init(void *th, struct acpi_data_attr *data_attr) } data_attr->addr = ccel->log_area_start_address; data_attr->attr.size = ccel->log_area_minimum_length; - data_attr->attr.attr.name = "CCEL"; return sysfs_create_bin_file(tables_data_kobj, &data_attr->attr); } @@ -484,7 +483,7 @@ static struct acpi_data_obj { #define NUM_ACPI_DATA_OBJS ARRAY_SIZE(acpi_data_objs) -static int acpi_table_data_init(struct acpi_table_header *th) +static int acpi_table_data_init(struct acpi_table_header *th, struct acpi_table_attr *table_attr) { struct acpi_data_attr *data_attr; int i; @@ -497,6 +496,8 @@ static int acpi_table_data_init(struct acpi_table_header *th) sysfs_attr_init(&data_attr->attr.attr); data_attr->attr.read = acpi_data_show; data_attr->attr.attr.mode = 0400; + strscpy(data_attr->filename, table_attr->filename); + data_attr->attr.attr.name = data_attr->filename; return acpi_data_objs[i].fn(th, data_attr); } } @@ -543,7 +544,7 @@ static int acpi_tables_sysfs_init(void) return ret; } list_add_tail(&table_attr->node, &acpi_table_attr_list); - acpi_table_data_init(table_header); + acpi_table_data_init(table_header, table_attr); } kobject_uevent(tables_kobj, KOBJ_ADD); From 96a3a2cd26d88450da169d03a6c9260127dfa90d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 17 Jul 2026 19:08:29 +0200 Subject: [PATCH 37/61] ACPI: fan: Don't use "proxy" headers Update header inclusions to follow IWYU (Include What You Use) principle. Signed-off-by: Andy Shevchenko Reviewed-by: Armin Wolf Link: https://patch.msgid.link/20260717170951.1782863-2-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/fan.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/fan.h b/drivers/acpi/fan.h index 97ce3212edf3..e06c055a8bb6 100644 --- a/drivers/acpi/fan.h +++ b/drivers/acpi/fan.h @@ -10,8 +10,9 @@ #ifndef _ACPI_FAN_H_ #define _ACPI_FAN_H_ -#include +#include #include +#include #define ACPI_FAN_DEVICE_IDS \ {"INT3404", }, /* Fan */ \ From 2eb0ea366427f2c654fcb72d810feff69d86a631 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 17 Jul 2026 19:08:30 +0200 Subject: [PATCH 38/61] ACPI: fan: Update ACPI fan IDs to follow modern style Follow modern style of defining ACPI IDs by using C99 initialisers. This is a missing part to bigger rework that's ongoing in the kernel. Signed-off-by: Andy Shevchenko Reviewed-by: Armin Wolf Link: https://patch.msgid.link/20260717170951.1782863-3-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/fan.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/acpi/fan.h b/drivers/acpi/fan.h index e06c055a8bb6..822c5455e43b 100644 --- a/drivers/acpi/fan.h +++ b/drivers/acpi/fan.h @@ -14,17 +14,17 @@ #include #include -#define ACPI_FAN_DEVICE_IDS \ - {"INT3404", }, /* Fan */ \ - {"INTC1044", }, /* Fan for Tiger Lake generation */ \ - {"INTC1048", }, /* Fan for Alder Lake generation */ \ - {"INTC1063", }, /* Fan for Meteor Lake generation */ \ - {"INTC106A", }, /* Fan for Lunar Lake generation */ \ - {"INTC10A2", }, /* Fan for Raptor Lake generation */ \ - {"INTC10D6", }, /* Fan for Panther Lake generation */ \ - {"INTC10FE", }, /* Fan for Wildcat Lake generation */ \ - {"INTC10F5", }, /* Fan for Nova Lake generation */ \ - {"PNP0C0B", } /* Generic ACPI fan */ +#define ACPI_FAN_DEVICE_IDS \ + { .id = "INT3404" }, /* Fan */ \ + { .id = "INTC1044" }, /* Fan for Tiger Lake generation */ \ + { .id = "INTC1048" }, /* Fan for Alder Lake generation */ \ + { .id = "INTC1063" }, /* Fan for Meteor Lake generation */ \ + { .id = "INTC106A" }, /* Fan for Lunar Lake generation */ \ + { .id = "INTC10A2" }, /* Fan for Raptor Lake generation */ \ + { .id = "INTC10D6" }, /* Fan for Panther Lake generation */ \ + { .id = "INTC10FE" }, /* Fan for Wildcat Lake generation */ \ + { .id = "INTC10F5" }, /* Fan for Nova Lake generation */ \ + { .id = "PNP0C0B" } /* Generic ACPI fan */ #define ACPI_FPS_NAME_LEN 20 From 4eff1be9268e57d0f0c519e9eb3bce64316ff377 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Fri, 17 Jul 2026 19:16:35 +0200 Subject: [PATCH 39/61] ACPI: NHLT: Remove always included kconfig.h The inclusion of is unneeded as it's guaranteed by the build starting from the commit 2a11c8ea20bf ("kconfig: Introduce IS_ENABLED(), IS_BUILTIN() and IS_MODULE()"). Remove it here. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260717171635.1783543-1-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- include/acpi/nhlt.h | 1 - 1 file changed, 1 deletion(-) diff --git a/include/acpi/nhlt.h b/include/acpi/nhlt.h index 2108aa6d0207..370958d93706 100644 --- a/include/acpi/nhlt.h +++ b/include/acpi/nhlt.h @@ -10,7 +10,6 @@ #define __ACPI_NHLT_H__ #include -#include #include #include From 69e81ddfee7603124b7f4e2312dacd988bd82e15 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sat, 18 Jul 2026 07:10:19 +0800 Subject: [PATCH 40/61] ACPI: battery: Merge consecutive battery notifications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's a very common pattern to emit consecutive battery notifications, for example: Method (_Qxx, 0, NotSerialized) { Notify (BAT0, 0x80) // Status Change Notify (BAT0, 0x81) // Information Change } In this case, the current code path will update battery state twice within a short period, which is not optimal, as the same data are fetched twice. Moreover, both notifications are likely to call power_supply_changed(), causing power_supply_uevent() to read all battery properties in order to assemble uevents. Even worse, after the first uevent reaches userspace, some userspace processes start to read all battery properties in order to refresh their internal states, which competes with the second notification's handling and uevent assembling. This generates significant pressure on _STA, _BST and _BIX/_BIF methods. Not only that, power_supply_ext properties may also rely on some other ACPI methods, so both uevent assembling and userspace processes call them. It becomes a nightmare when all these methods share the same ACPI mutex protecting EC accesses and hence vulnerable to lock starvation. This is exactly the case of some Lenovo devices, where the mentioned EC query pattern eventually leads to a catastrophic situation that a bunch of ACPI methods (including but not limited to the mentioned ones) fail to acquire the same mutex due to timeout. These devices don't handle mutex acquisition failure gracefully and return garbage data, causing even more chaos. Improve battery notification handling by merging at most 16 consecutive battery notifications within 10ms using a delayed work, so that they only refresh and/or update battery state once. ACPI netlink event and notifier call chain are still triggered multiple times in order not to break other components. Finally, call power_supply_changed() once and lead to a single uevent instead of a bunch, preventing userspace programs from causing too much pressure on power supply properties and underlying ACPI methods. If more than 16 battery notifications are queued within 10ms, the firmware/hardware is anyway buggy, and extra notifications will be dropped. Tested-by: Jeffrey Wälti Tested-by: Avraham Hollander Reported-by: Rick Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221065 Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260718-b4-acpi-battery-notification-v4-1-599c8ed1072f@rong.moe Signed-off-by: Rafael J. Wysocki --- drivers/acpi/battery.c | 89 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 80 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index 4409fb95b988..f9ba601f671e 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ #include #include #include +#include #include @@ -44,6 +46,9 @@ #define MAX_STRING_LENGTH 64 +#define MAX_QUEUED_EVENTS 16 +#define NOTIF_MERGING_MS 10 + MODULE_AUTHOR("Paul Diefenbaugh"); MODULE_AUTHOR("Alexey Starikovskiy "); MODULE_DESCRIPTION("ACPI Battery Driver"); @@ -96,6 +101,8 @@ struct acpi_battery { struct power_supply_desc bat_desc; struct acpi_device *device; struct device *phys_dev; + struct kfifo acpi_notif_fifo; + struct delayed_work acpi_notif_dwork; struct notifier_block pm_nb; struct list_head list; unsigned long update_time; @@ -1073,14 +1080,24 @@ static void acpi_battery_refresh(struct acpi_battery *battery) } /* Driver Interface */ -static void acpi_battery_notify(acpi_handle handle, u32 event, void *data) +static void acpi_battery_notification_worker(struct work_struct *work) { - struct acpi_battery *battery = data; + struct acpi_battery *battery = container_of(work, struct acpi_battery, + acpi_notif_dwork.work); struct acpi_device *device = battery->device; + u32 events[MAX_QUEUED_EVENTS]; struct power_supply *old; + unsigned int count, i; guard(mutex)(&battery->update_lock); + count = kfifo_out(&battery->acpi_notif_fifo, events, sizeof(events)); + count /= sizeof(events[0]); + if (!count) + return; + + pr_debug("merged %u battery notifications within %dms\n", count, NOTIF_MERGING_MS); + old = battery->bat; /* * On Acer Aspire V5-573G notifications are sometimes triggered too @@ -1090,19 +1107,46 @@ static void acpi_battery_notify(acpi_handle handle, u32 event, void *data) */ if (battery_notification_delay_ms > 0) msleep(battery_notification_delay_ms); - if (event == ACPI_BATTERY_NOTIFY_INFO) - acpi_battery_refresh(battery); + + for (i = 0; i < count; i++) { + if (events[i] == ACPI_BATTERY_NOTIFY_INFO) { + acpi_battery_refresh(battery); + break; + } + } + acpi_battery_update(battery, false); - acpi_bus_generate_netlink_event(ACPI_BATTERY_CLASS, - dev_name(&device->dev), event, - acpi_battery_present(battery)); - acpi_notifier_call_chain(ACPI_BATTERY_CLASS, acpi_device_bid(device), - event, acpi_battery_present(battery)); + + for (i = 0; i < count; i++) { + acpi_bus_generate_netlink_event(ACPI_BATTERY_CLASS, + dev_name(&device->dev), events[i], + acpi_battery_present(battery)); + acpi_notifier_call_chain(ACPI_BATTERY_CLASS, acpi_device_bid(device), + events[i], acpi_battery_present(battery)); + } + /* acpi_battery_update could remove power_supply object */ if (old && battery->bat) power_supply_changed(battery->bat); } +static void acpi_battery_notify(acpi_handle handle, u32 event, void *data) +{ + struct acpi_battery *battery = data; + + guard(mutex)(&battery->update_lock); + + if (kfifo_avail(&battery->acpi_notif_fifo) >= sizeof(event)) { + kfifo_in(&battery->acpi_notif_fifo, &event, sizeof(event)); + schedule_delayed_work(&battery->acpi_notif_dwork, + msecs_to_jiffies(NOTIF_MERGING_MS)); + + return; + } + + pr_err_ratelimited("too many battery notifications within %dms\n", NOTIF_MERGING_MS); +} + static int battery_notify(struct notifier_block *nb, unsigned long mode, void *_unused) { @@ -1245,6 +1289,29 @@ static int devm_acpi_battery_update_retry(struct device *dev, return ret; } +static void acpi_battery_notify_dwork_cleanup(void *data) +{ + struct acpi_battery *battery = data; + + cancel_delayed_work_sync(&battery->acpi_notif_dwork); + kfifo_free(&battery->acpi_notif_fifo); +} + +static int devm_acpi_battery_init_notify_dwork(struct device *dev, + struct acpi_battery *battery) +{ + int ret; + + INIT_DELAYED_WORK(&battery->acpi_notif_dwork, acpi_battery_notification_worker); + + ret = kfifo_alloc(&battery->acpi_notif_fifo, + MAX_QUEUED_EVENTS * sizeof(u32), GFP_KERNEL); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, acpi_battery_notify_dwork_cleanup, battery); +} + static int acpi_battery_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -1286,6 +1353,10 @@ static int acpi_battery_probe(struct platform_device *pdev) if (result) return result; + result = devm_acpi_battery_init_notify_dwork(dev, battery); + if (result) + return result; + result = devm_acpi_install_notify_handler(dev, ACPI_ALL_NOTIFY, acpi_battery_notify, battery); if (result) From 57346c4d78d38b357dbe9ef16d3f63bf4610c039 Mon Sep 17 00:00:00 2001 From: Rong Zhang Date: Sat, 18 Jul 2026 07:10:20 +0800 Subject: [PATCH 41/61] ACPI: battery: Use kstrtoul() over sscanf("%lu\n") It is more preferred to use kstrto*() to parse a single number. The function family properly returns an errno on error and is the correct mechanism to parse data from sysfs. The number base is set to 10 in order not to break the ABI. Tested-by: Avraham Hollander Signed-off-by: Rong Zhang Link: https://patch.msgid.link/20260718-b4-acpi-battery-notification-v4-2-599c8ed1072f@rong.moe Signed-off-by: Rafael J. Wysocki --- drivers/acpi/battery.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index f9ba601f671e..f7e7041c39c9 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -689,9 +689,13 @@ static ssize_t acpi_battery_alarm_store(struct device *dev, { unsigned long x; struct acpi_battery *battery = to_acpi_battery(dev_get_drvdata(dev)); + int err; - if (sscanf(buf, "%lu\n", &x) == 1) - battery->alarm = x/1000; + err = kstrtoul(buf, 10, &x); + if (err) + return err; + + battery->alarm = x / 1000; if (acpi_battery_present(battery)) acpi_battery_set_alarm(battery); return count; From 7609bf715c9622df912826627e31eb2c54c3f590 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 19 Jul 2026 20:23:39 -0700 Subject: [PATCH 42/61] ACPI: fan: Use correct function parameter name in kernel-doc Fix kernel-doc warnings by using the correct function parameter name: Warning: ./drivers/acpi/fan.h:83 function parameter 'speed' not described in 'acpi_fan_speed_valid' Warning: ./drivers/acpi/fan.h:83 Excess function parameter 'speeed' description in 'acpi_fan_speed_valid' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260720032341.3087008-1-rdunlap@infradead.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/fan.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/fan.h b/drivers/acpi/fan.h index 822c5455e43b..e20d6ad9df80 100644 --- a/drivers/acpi/fan.h +++ b/drivers/acpi/fan.h @@ -70,7 +70,7 @@ struct acpi_fan { /** * acpi_fan_speed_valid - Check if fan speed value is valid - * @speeed: Speed value returned by the ACPI firmware + * @speed: Speed value returned by the ACPI firmware * * Check if the fan speed value returned by the ACPI firmware is valid. This function is * necessary as ACPI firmware implementations can return 0xFFFFFFFF to signal that the From 3b0eb670f346732d1b545904ab8eb9c8d7f31b91 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 19 Jul 2026 20:23:40 -0700 Subject: [PATCH 43/61] ACPI: bus: Use correct struct member names Avoid kernel-doc warnings by using the correct struct member names: Warning: ./include/acpi/acpi_bus.h:429 struct member 'crs_csi2_local' not described in 'acpi_device_software_node_port' Warning: ./include/acpi/acpi_bus.h:429 Excess struct member 'crs_crs2_local' description in 'acpi_device_software_node_port' Warning: ./include/acpi/acpi_bus.h:445 struct member 'nodeptrs' not described in 'acpi_device_software_nodes' Warning: ./include/acpi/acpi_bus.h:445 Excess struct member 'nodeprts' description in 'acpi_device_software_nodes' Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260720032341.3087008-2-rdunlap@infradead.org Signed-off-by: Rafael J. Wysocki --- include/acpi/acpi_bus.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index 7b8051baed75..32cac3a6f362 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -410,7 +410,7 @@ enum acpi_device_swnode_ep_props { * @lane_polarities: "lane-polarities" property values. * @link_frequencies: "link_frequencies" property values. * @port_nr: Port number. - * @crs_crs2_local: _CRS CSI2 record present (i.e. this is a transmitter one). + * @crs_csi2_local: _CRS CSI2 record present (i.e. this is a transmitter one). * @port_props: Port properties. * @ep_props: Endpoint properties. * @remote_ep: Reference to the remote endpoint. @@ -433,7 +433,7 @@ struct acpi_device_software_node_port { * struct acpi_device_software_nodes - Software nodes for an ACPI device * @dev_props: Device properties. * @nodes: Software nodes for root as well as ports and endpoints. - * @nodeprts: Array of software node pointers, for (un)registering them. + * @nodeptrs: Array of software node pointers, for (un)registering them. * @ports: Information related to each port and endpoint within a port. * @num_ports: The number of ports. */ From 1d697682182957ad6bf38c1ca3fa46cbe3b94e30 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Sun, 19 Jul 2026 20:23:41 -0700 Subject: [PATCH 44/61] ACPI: pmtmr: Convert to kernel-doc format Prevent kernel-doc warnings by converting 2 functions to kernel-doc format: Warning: ./include/linux/acpi_pmtmr.h:29 This comment starts with '/**', but isn't a kernel-doc comment. * Register callback for suspend and resume event Warning: ./include/linux/acpi_pmtmr.h:37 This comment starts with '/**', but isn't a kernel-doc comment. * Remove registered callback for suspend and resume event Signed-off-by: Randy Dunlap Link: https://patch.msgid.link/20260720032341.3087008-3-rdunlap@infradead.org Signed-off-by: Rafael J. Wysocki --- include/linux/acpi_pmtmr.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/include/linux/acpi_pmtmr.h b/include/linux/acpi_pmtmr.h index 0ded9220d379..cceed294d0b5 100644 --- a/include/linux/acpi_pmtmr.h +++ b/include/linux/acpi_pmtmr.h @@ -27,15 +27,17 @@ static inline u32 acpi_pm_read_early(void) } /** - * Register callback for suspend and resume event + * acpi_pmtmr_register_suspend_resume_callback - Register callback for + * suspend and resume event * - * @cb Callback triggered on suspend and resume - * @data Data passed with the callback + * @cb: Callback triggered on suspend and resume + * @data: Data passed with the callback */ void acpi_pmtmr_register_suspend_resume_callback(void (*cb)(void *data, bool suspend), void *data); /** - * Remove registered callback for suspend and resume event + * acpi_pmtmr_unregister_suspend_resume_callback - Remove registered callback + * for suspend and resume event */ void acpi_pmtmr_unregister_suspend_resume_callback(void); From 77ce4be0d8d53c528d1663ab62a14d93d5853f11 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 21 Jul 2026 15:16:31 +0200 Subject: [PATCH 45/61] ACPI: battery: Adjust charging status validation check Commit bb1256e0ddc7 ("ACPI: battery: fix incorrect charging status when current is zero") added a charge rate check to validate the "charging" status of the battery, but that check is reported to cause some systems to misbehave [1]. Namely, it causes the "not charging" status to be reported on them while the battery is in fact charging (and they were correctly reporting the "charging" status in that case previously). To address that, check if the battery is full in addition to checking the charge rate when the "charging" status is reported by the platform firmware and only change it to "not charging" if the battery is full and its charge rate is zero or it is unknown. Fixes: bb1256e0ddc7 ("ACPI: battery: fix incorrect charging status when current is zero") Reported-by: golne tree Tested-by: golne tree Closes: https://lore.kernel.org/linux-acpi/AM9P193MB158895CFE0DDFA62FCD1DA5ED0F22@AM9P193MB1588.EURP193.PROD.OUTLOOK.COM/ [1] Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/6286911.lOV4Wx5bFT@rafael.j.wysocki --- drivers/acpi/battery.c | 44 ++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index f7e7041c39c9..0084f308b790 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -158,27 +158,28 @@ static int acpi_battery_technology(struct acpi_battery *battery) static int acpi_battery_get_state(struct acpi_battery *battery); +static bool acpi_battery_is_full(struct acpi_battery *battery) +{ + /* battery not reporting charge */ + if (battery->capacity_now == ACPI_BATTERY_VALUE_UNKNOWN || + battery->capacity_now == 0) + return false; + + /* good batteries update full_charge as the batteries degrade */ + if (battery->full_charge_capacity == battery->capacity_now) + return true; + + /* fallback to using design values for broken batteries */ + return battery->design_capacity <= battery->capacity_now; +} + static int acpi_battery_is_charged(struct acpi_battery *battery) { /* charging, discharging, critical low or charge limited */ if (battery->state != 0) return 0; - /* battery not reporting charge */ - if (battery->capacity_now == ACPI_BATTERY_VALUE_UNKNOWN || - battery->capacity_now == 0) - return 0; - - /* good batteries update full_charge as the batteries degrade */ - if (battery->full_charge_capacity == battery->capacity_now) - return 1; - - /* fallback to using design values for broken batteries */ - if (battery->design_capacity <= battery->capacity_now) - return 1; - - /* we don't do any sort of metric based on percentages */ - return 0; + return acpi_battery_is_full(battery); } static bool acpi_battery_is_degraded(struct acpi_battery *battery) @@ -219,13 +220,14 @@ static int acpi_battery_get_property(struct power_supply *psy, if (battery->state & ACPI_BATTERY_STATE_DISCHARGING) val->intval = acpi_battery_handle_discharging(battery); else if (battery->state & ACPI_BATTERY_STATE_CHARGING) - /* Validate the status by checking the current. */ - if (battery->rate_now != ACPI_BATTERY_VALUE_UNKNOWN && - battery->rate_now == 0) { - /* On charge but no current (0W/0mA). */ - val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else { + /* Check the rate and capacity to validate the status. */ + if (!acpi_battery_is_full(battery) || + (battery->rate_now != ACPI_BATTERY_VALUE_UNKNOWN && + battery->rate_now > 0)) { val->intval = POWER_SUPPLY_STATUS_CHARGING; + } else { + /* Full and zero rate. */ + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; } else if (battery->state & ACPI_BATTERY_STATE_CHARGE_LIMITING) val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; From 9bfb23e0661be3d6a72aedde08a9f0fec2e8041a Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 23 Jul 2026 13:16:03 +0200 Subject: [PATCH 46/61] ACPI: bus: Avoid confusing complaints regarding missing _OSC features The platform firmware on some platforms sets OSC_CAPABILITIES_MASK_ERROR in _OSC error bits even though it actually acknowledges all of the requested features which after commit e5322888e6bf ("ACPI: bus: Rework the handling of \_SB._OSC platform features") causes the kernel to complain unnecessarily. Avoid the confusing complaints by explicitly checking for that case in acpi_osc_handshake(). Fixes: e5322888e6bf ("ACPI: bus: Rework the handling of \_SB._OSC platform features") Signed-off-by: Rafael J. Wysocki Tested-by: Saverio Miroddi [ rjw: Fixed a typo in the new comment ] Link: https://patch.msgid.link/6315683.lOV4Wx5bFT@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/bus.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index fae79cdd3610..c1876f145ae4 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -335,7 +335,7 @@ static int acpi_osc_handshake(acpi_handle handle, const char *uuid_str, .length = bufsize * sizeof(u32), }; struct acpi_buffer output; - u32 *retbuf, test; + u32 *retbuf, test, errors; guid_t guid; int ret, i; @@ -395,10 +395,18 @@ static int acpi_osc_handshake(acpi_handle handle, const char *uuid_str, * Clear the feature bits in capbuf[] that have not been acknowledged. * After that, capbuf[] contains the resultant feature mask. */ - for (i = OSC_QUERY_DWORD + 1; i < bufsize; i++) + for (i = OSC_QUERY_DWORD + 1, test = 0; i < bufsize; i++) { + test |= capbuf[i] & ~retbuf[i]; capbuf[i] &= retbuf[i]; + } - if (retbuf[OSC_QUERY_DWORD] & OSC_ERROR_MASK) { + errors = retbuf[OSC_QUERY_DWORD] & OSC_ERROR_MASK; + /* + * Some platforms set OSC_CAPABILITIES_MASK_ERROR even though they + * acknowledge all of the requested features, so avoid complaining in + * those cases unless any other error bits are also set. + */ + if (errors && (test || errors != OSC_CAPABILITIES_MASK_ERROR)) { /* * Complain about the unexpected errors and print diagnostic * information related to them. From 06f32dd67e6b23a05bef0d8183c5335af91c0c3b Mon Sep 17 00:00:00 2001 From: Can Peng Date: Wed, 29 Jul 2026 10:36:05 +0800 Subject: [PATCH 47/61] ACPI: processor: Unregister cpufreq notifier on init failure acpi_processor_driver_init() registers the cpufreq policy notifier before registering the ACPI processor driver and setting up CPU hotplug state. If driver_register() or cpuhp_setup_state() fails, the error path only unregisters the ACPI processor driver and the idle driver. The cpufreq notifier remains registered even though initialization failed. Mirror the module exit path on the init failure path and unregister the cpufreq notifier when it has been registered. Fixes: c0e0421a60bf ("ACPI: processor: Reorder acpi_processor_driver_init()") Signed-off-by: Can Peng Link: https://patch.msgid.link/20260729023605.197367-1-pengcan@kylinos.cn Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_driver.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/acpi/processor_driver.c b/drivers/acpi/processor_driver.c index cda8fd720000..cdc2ae1632b2 100644 --- a/drivers/acpi/processor_driver.c +++ b/drivers/acpi/processor_driver.c @@ -285,6 +285,12 @@ static int __init acpi_processor_driver_init(void) unregister_idle_drv: acpi_processor_unregister_idle_driver(); + if (acpi_processor_cpufreq_init) { + cpufreq_unregister_notifier(&acpi_processor_notifier_block, + CPUFREQ_POLICY_NOTIFIER); + acpi_processor_cpufreq_init = false; + } + return result; } From df5a1d4a8cdfda20eb2581a85e81c7d436866534 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Mon, 3 Aug 2026 21:35:29 +0100 Subject: [PATCH 48/61] ACPI: CPPC: Reject desired_perf reads on _CPC revision 4+ When CPPC feedback counters cannot provide a usable sample, cppc-cpufreq calls cppc_get_desired_perf() because some platforms repurpose Desired Performance to report actual delivered performance. ACPI 6.5 defines _CPC revision 3 and lists Read/Write as the Optional Attribute of Desired Performance. ACPI 6.6 advances _CPC to revision 4 and lists only Write, so invoking that workaround for revision 4 or later would require a register read that the interface no longer specifies. Make cppc_get_desired_perf() return -EOPNOTSUPP for _CPC revision 4 or later. Use the revision retained in the per-CPU CPC descriptor rather than the platform-wide FADT revision. The _CPC revision may still not accurately describe the implemented register semantics. If a nominally revision 3 platform implements a non-readable Desired Performance register, a read may return zero and make cppc_cpufreq_get_rate() report 0 kHz. Treat a zero read as unusable and fall back to the cached OSPM request, just as for a failed read. Fixes: c47195631960 ("cppc_cpufreq: Use desired perf if feedback ctrs are 0 or unchanged") Cc: stable@vger.kernel.org Suggested-by: Sumit Gupta Signed-off-by: Christian Loehle Link: https://patch.msgid.link/20260803203531.1268651-2-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 17 ++++++++++++++++- drivers/cpufreq/cppc_cpufreq.c | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 53d09ca98f06..42aeb749ebe0 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1316,15 +1316,30 @@ static int cppc_set_reg_val(int cpu, enum cppc_regs reg_idx, u64 val) return cpc_write(cpu, reg, val); } +static bool cppc_desired_perf_readable(const struct cpc_desc *cpc_desc) +{ + return cpc_desc->version < CPPC_V4_REV; +} + /** * cppc_get_desired_perf - Get the desired performance register value. * @cpunum: CPU from which to get desired performance. * @desired_perf: Return address. * - * Return: 0 for success, -EIO otherwise. + * Return: 0 for success, -EOPNOTSUPP for _CPC revision 4 or later, and a + * negative errno otherwise. */ int cppc_get_desired_perf(int cpunum, u64 *desired_perf) { + struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpunum); + + if (!cpc_desc) + return -ENODEV; + + /* _CPC revision 4 no longer specifies Desired Performance as readable. */ + if (!cppc_desired_perf_readable(cpc_desc)) + return -EOPNOTSUPP; + return cppc_get_reg_val(cpunum, DESIRED_PERF, desired_perf); } EXPORT_SYMBOL_GPL(cppc_get_desired_perf); diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 6fe0e972952a..80893844353c 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -836,7 +836,7 @@ static unsigned int cppc_cpufreq_get_rate(unsigned int cpu) * value first as some platforms may update the actual delivered perf * there; if failed, resort to the cached desired perf. */ - if (cppc_get_desired_perf(cpu, &delivered_perf)) + if (cppc_get_desired_perf(cpu, &delivered_perf) || !delivered_perf) delivered_perf = cpu_data->perf_ctrls.desired_perf; return cppc_perf_to_khz(&cpu_data->perf_caps, delivered_perf); From d3576424e8fa702a15a1963718a07eb672db9be7 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Mon, 3 Aug 2026 21:35:30 +0100 Subject: [PATCH 49/61] ACPI: CPPC: Skip desired_perf read in cppc_get_perf() ACPI 6.5 defines _CPC revision 3 and lists Read/Write as the Optional Attribute of Desired Performance. ACPI 6.6 advances _CPC to revision 4 and lists only Write. cppc_get_perf() nevertheless reads the register when initializing performance controls, even though cppc-cpufreq overwrites the value before using it. Use the _CPC revision check from cppc_get_desired_perf() and leave desired_perf zero instead of reading it for _CPC revision 4 or later. Also exclude the register from PCC read-command detection so it cannot trigger an otherwise unnecessary read command. Fixes: 658fa7b1c47a ("ACPI: CPPC: Add cppc_get_perf() API to read performance controls") Cc: stable@vger.kernel.org Suggested-by: Zhongqiu Han Reviewed-by: Zhongqiu Han Signed-off-by: Christian Loehle Link: https://patch.msgid.link/20260803203531.1268651-3-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 42aeb749ebe0..becb7e442b30 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1845,12 +1845,14 @@ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) u64 desired_perf = 0, min = 0, max = 0, energy_perf = 0, auto_sel = 0; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); struct cppc_pcc_data *pcc_ss_data = NULL; + bool read_desired_perf; int ret = 0, regs_in_pcc = 0; if (!cpc_desc) { pr_debug("No CPC descriptor for CPU:%d\n", cpu); return -ENODEV; } + read_desired_perf = cppc_desired_perf_readable(cpc_desc); if (!perf_ctrls) { pr_debug("Invalid perf_ctrls pointer\n"); @@ -1864,7 +1866,8 @@ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) auto_sel_reg = &cpc_desc->cpc_regs[AUTO_SEL_ENABLE]; /* Are any of the regs PCC ?*/ - if (CPC_IN_PCC(desired_perf_reg) || CPC_IN_PCC(min_perf_reg) || + if ((read_desired_perf && CPC_IN_PCC(desired_perf_reg)) || + CPC_IN_PCC(min_perf_reg) || CPC_IN_PCC(max_perf_reg) || CPC_IN_PCC(energy_perf_reg) || CPC_IN_PCC(auto_sel_reg)) { if (pcc_ss_id < 0) { @@ -1896,7 +1899,7 @@ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) } perf_ctrls->min_perf = min; - if (CPC_SUPPORTED(desired_perf_reg)) { + if (read_desired_perf && CPC_SUPPORTED(desired_perf_reg)) { ret = cpc_read(cpu, desired_perf_reg, &desired_perf); if (ret) goto out_err; From 334dd0ed87e4f7b8e7753a8d6083c150f9520329 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Mon, 3 Aug 2026 21:35:31 +0100 Subject: [PATCH 50/61] ACPI: CPPC: Stop reading desired_perf in cppc_get_perf() cppc_get_perf() has one in-tree caller, cppc_cpufreq_get_cpu_data(). It uses the function to preserve existing controls before writing them, but overwrites desired_perf with highest_perf before the first cppc_set_perf(). Consequently, the current Desired Performance value is not consumed. Remove the Desired Performance read from this aggregate getter and document that the field is returned as zero. Reviewed-by: Zhongqiu Han Signed-off-by: Christian Loehle Link: https://patch.msgid.link/20260803203531.1268651-4-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index becb7e442b30..15a1c5f00e51 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1834,41 +1834,38 @@ EXPORT_SYMBOL_GPL(cppc_set_enable); * @cpu: CPU for which to get performance controls. * @perf_ctrls: ptr to cppc_perf_ctrls. See cppc_acpi.h * + * Desired Performance is not read and is returned as 0. + * * Return: 0 for success with perf_ctrls, -ERRNO otherwise. */ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); - struct cpc_register_resource *desired_perf_reg, - *min_perf_reg, *max_perf_reg, + struct cpc_register_resource *min_perf_reg, *max_perf_reg, *energy_perf_reg, *auto_sel_reg; - u64 desired_perf = 0, min = 0, max = 0, energy_perf = 0, auto_sel = 0; + u64 min = 0, max = 0, energy_perf = 0, auto_sel = 0; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); struct cppc_pcc_data *pcc_ss_data = NULL; - bool read_desired_perf; int ret = 0, regs_in_pcc = 0; if (!cpc_desc) { pr_debug("No CPC descriptor for CPU:%d\n", cpu); return -ENODEV; } - read_desired_perf = cppc_desired_perf_readable(cpc_desc); if (!perf_ctrls) { pr_debug("Invalid perf_ctrls pointer\n"); return -EINVAL; } - desired_perf_reg = &cpc_desc->cpc_regs[DESIRED_PERF]; min_perf_reg = &cpc_desc->cpc_regs[MIN_PERF]; max_perf_reg = &cpc_desc->cpc_regs[MAX_PERF]; energy_perf_reg = &cpc_desc->cpc_regs[ENERGY_PERF]; auto_sel_reg = &cpc_desc->cpc_regs[AUTO_SEL_ENABLE]; /* Are any of the regs PCC ?*/ - if ((read_desired_perf && CPC_IN_PCC(desired_perf_reg)) || - CPC_IN_PCC(min_perf_reg) || - CPC_IN_PCC(max_perf_reg) || CPC_IN_PCC(energy_perf_reg) || + if (CPC_IN_PCC(min_perf_reg) || CPC_IN_PCC(max_perf_reg) || + CPC_IN_PCC(energy_perf_reg) || CPC_IN_PCC(auto_sel_reg)) { if (pcc_ss_id < 0) { pr_debug("Invalid pcc_ss_id for CPU:%d\n", cpu); @@ -1899,12 +1896,7 @@ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) } perf_ctrls->min_perf = min; - if (read_desired_perf && CPC_SUPPORTED(desired_perf_reg)) { - ret = cpc_read(cpu, desired_perf_reg, &desired_perf); - if (ret) - goto out_err; - } - perf_ctrls->desired_perf = desired_perf; + perf_ctrls->desired_perf = 0; if (CPC_SUPPORTED(energy_perf_reg)) { ret = cpc_read(cpu, energy_perf_reg, &energy_perf); From 9d9157ee90e3eed393e92b4032b83ddb8691de64 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Mon, 3 Aug 2026 22:05:25 +0100 Subject: [PATCH 51/61] ACPI: CPPC: Avoid unnecessary reads for full-width writes SystemMemory GAS entries may describe a field within a wider access unit, so cpc_write() reads the access unit before updating the field to preserve the surrounding bits. It also does this when the field covers the complete access unit. When the bit offset is zero and the register bit width equals the resolved access width, the previous value cannot affect the result. Skip the MMIO read and mask operation in that case. Retain rmw_lock because another entry in the same _CPC package may share the access unit. Signed-off-by: Christian Loehle Reviewed-by: Zhongqiu Han Link: https://patch.msgid.link/20260803210527.1285229-2-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 43 ++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 15a1c5f00e51..30a6ae82034c 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1162,25 +1162,34 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) return -ENODEV; } + /* + * Only partial fields need the previous contents to preserve bits + * outside the field. Keep serializing full-width writes because + * another _CPC entry may share the access unit and require RMW. + */ raw_spin_lock_irqsave(&cpc_desc->rmw_lock, flags); - switch (size) { - case 8: - prev_val = readb_relaxed(vaddr); - break; - case 16: - prev_val = readw_relaxed(vaddr); - break; - case 32: - prev_val = readl_relaxed(vaddr); - break; - case 64: - prev_val = readq_relaxed(vaddr); - break; - default: - raw_spin_unlock_irqrestore(&cpc_desc->rmw_lock, flags); - return -EFAULT; + + if (reg->bit_offset || reg->bit_width != size) { + switch (size) { + case 8: + prev_val = readb_relaxed(vaddr); + break; + case 16: + prev_val = readw_relaxed(vaddr); + break; + case 32: + prev_val = readl_relaxed(vaddr); + break; + case 64: + prev_val = readq_relaxed(vaddr); + break; + default: + raw_spin_unlock_irqrestore(&cpc_desc->rmw_lock, + flags); + return -EFAULT; + } + val = MASK_VAL_WRITE(reg, prev_val, val); } - val = MASK_VAL_WRITE(reg, prev_val, val); } switch (size) { From 9c5ab33a6d2cfa34a6d64c819503f7bfd837bb78 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Mon, 3 Aug 2026 22:05:26 +0100 Subject: [PATCH 52/61] ACPI: CPPC: Avoid locking standalone full-width registers cpc_write() serializes every SystemMemory write with the per-CPU rmw_lock. The lock is required for read-modify-write fields and for registers whose access units overlap, but not for a full-width register in a standalone access unit. The _CPC layout is immutable after it has been parsed. Classify each SystemMemory register at probe time and retain locking for partial fields, invalid access widths, and overlapping access units. Allow standalone full-width registers to bypass the descriptor lookup and spinlock. Store the classification in existing structure padding so that struct cpc_register_resource does not grow. Signed-off-by: Christian Loehle Link: https://patch.msgid.link/20260803210527.1285229-3-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 97 ++++++++++++++++++++++++++++++++++------ include/acpi/cppc_acpi.h | 5 ++- 2 files changed, 88 insertions(+), 14 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 30a6ae82034c..6826aad4613c 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -200,6 +200,72 @@ show_cppc_data(cppc_get_perf_ctrs, cppc_perf_fb_ctrs, wraparound_time); ((((val) & GENMASK(((reg)->bit_width) - 1, 0)) << (reg)->bit_offset) | \ ((prev_val) & ~(GENMASK(((reg)->bit_width) - 1, 0) << (reg)->bit_offset))) \ +static u64 cpc_sysmem_access_size(const struct cpc_register_resource *reg) +{ + const struct cpc_reg *gas = ®->cpc_entry.reg; + unsigned int width; + + if (gas->access_width > 4) + return 0; + + width = GET_BIT_WIDTH(gas); + + if (width != 8 && width != 16 && width != 32 && width != 64) + return 0; + + return width / 8; +} + +static bool cpc_sysmem_access_units_overlap(const struct cpc_register_resource *a, + const struct cpc_register_resource *b) +{ + const struct cpc_reg *a_gas = &a->cpc_entry.reg; + const struct cpc_reg *b_gas = &b->cpc_entry.reg; + u64 a_size = cpc_sysmem_access_size(a); + u64 b_size = cpc_sysmem_access_size(b); + + /* Keep the conservative locking path for malformed access widths. */ + if (!a_size || !b_size) + return true; + + if (a_gas->address < b_gas->address) + return b_gas->address - a_gas->address < a_size; + + return a_gas->address - b_gas->address < b_size; +} + +static void cpc_mark_rmw_lock_users(struct cpc_desc *cpc_desc) +{ + int i, j; + + for (i = 0; i < cpc_desc->num_entries - 2; i++) { + struct cpc_register_resource *a = &cpc_desc->cpc_regs[i]; + struct cpc_reg *gas; + u64 access_size; + + if (!CPC_SUPPORTED(a) || !CPC_IN_SYSTEM_MEMORY(a)) + continue; + + gas = &a->cpc_entry.reg; + access_size = cpc_sysmem_access_size(a); + if (gas->bit_offset || !access_size || + gas->bit_width != access_size * 8) + a->cpc_entry.use_rmw_lock = true; + + for (j = i + 1; j < cpc_desc->num_entries - 2; j++) { + struct cpc_register_resource *b = &cpc_desc->cpc_regs[j]; + + if (!CPC_SUPPORTED(b) || !CPC_IN_SYSTEM_MEMORY(b)) + continue; + if (!cpc_sysmem_access_units_overlap(a, b)) + continue; + + a->cpc_entry.use_rmw_lock = true; + b->cpc_entry.use_rmw_lock = true; + } + } +} + static ssize_t show_feedback_ctrs(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { @@ -904,6 +970,7 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) /* Store CPU Logical ID */ cpc_ptr->cpu_id = pr->id; + cpc_mark_rmw_lock_users(cpc_ptr); raw_spin_lock_init(&cpc_ptr->rmw_lock); /* Parse PSD data for this CPU */ @@ -1123,6 +1190,7 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) struct cpc_reg *reg = ®_res->cpc_entry.reg; struct cpc_desc *cpc_desc; unsigned long flags; + bool locked = false; size = GET_BIT_WIDTH(reg); @@ -1156,18 +1224,20 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) val, size); if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) { - cpc_desc = per_cpu(cpc_desc_ptr, cpu); - if (!cpc_desc) { - pr_debug("No CPC descriptor for CPU:%d\n", cpu); - return -ENODEV; - } - /* - * Only partial fields need the previous contents to preserve bits - * outside the field. Keep serializing full-width writes because - * another _CPC entry may share the access unit and require RMW. + * The _CPC layout is immutable after probe. The precomputed flag + * retains serialization for partial fields or overlapping access + * units; standalone full-width registers avoid the lock. */ - raw_spin_lock_irqsave(&cpc_desc->rmw_lock, flags); + locked = reg_res->cpc_entry.use_rmw_lock; + if (locked) { + cpc_desc = per_cpu(cpc_desc_ptr, cpu); + if (!cpc_desc) { + pr_debug("No CPC descriptor for CPU:%d\n", cpu); + return -ENODEV; + } + raw_spin_lock_irqsave(&cpc_desc->rmw_lock, flags); + } if (reg->bit_offset || reg->bit_width != size) { switch (size) { @@ -1184,8 +1254,9 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) prev_val = readq_relaxed(vaddr); break; default: - raw_spin_unlock_irqrestore(&cpc_desc->rmw_lock, - flags); + if (locked) + raw_spin_unlock_irqrestore(&cpc_desc->rmw_lock, + flags); return -EFAULT; } val = MASK_VAL_WRITE(reg, prev_val, val); @@ -1217,7 +1288,7 @@ static int cpc_write(int cpu, struct cpc_register_resource *reg_res, u64 val) break; } - if (reg->space_id == ACPI_ADR_SPACE_SYSTEM_MEMORY) + if (locked) raw_spin_unlock_irqrestore(&cpc_desc->rmw_lock, flags); return ret_val; diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 8c191b9ac18f..19830146c644 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -69,7 +69,10 @@ struct cpc_register_resource { acpi_object_type type; u64 __iomem *sys_mem_vaddr; union { - struct cpc_reg reg; + struct { + struct cpc_reg reg; + bool use_rmw_lock; + }; u64 int_value; } cpc_entry; }; From b0f51f98fbc903414a8294eb6e3da0e40da6bfd0 Mon Sep 17 00:00:00 2001 From: Christian Loehle Date: Mon, 3 Aug 2026 22:05:27 +0100 Subject: [PATCH 53/61] ACPI: CPPC: Evaluate performance-control PCC use once cppc_set_perf() evaluates the same immutable address-space predicates before and after each phase of a performance-control update. This repeats the three-control PCC test three times for every target request. Evaluate the predicate once after resolving the control descriptors and reuse the result throughout the transaction. Signed-off-by: Christian Loehle Link: https://patch.msgid.link/20260803210527.1285229-4-christian.loehle@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 6826aad4613c..2f7c09552566 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -2012,6 +2012,7 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) struct cpc_register_resource *desired_reg, *min_perf_reg, *max_perf_reg; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpu); struct cppc_pcc_data *pcc_ss_data = NULL; + bool regs_in_pcc; int ret = 0; if (!cpc_desc) { @@ -2022,6 +2023,8 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) desired_reg = &cpc_desc->cpc_regs[DESIRED_PERF]; min_perf_reg = &cpc_desc->cpc_regs[MIN_PERF]; max_perf_reg = &cpc_desc->cpc_regs[MAX_PERF]; + regs_in_pcc = CPC_IN_PCC(desired_reg) || CPC_IN_PCC(min_perf_reg) || + CPC_IN_PCC(max_perf_reg); /* * This is Phase-I where we want to write to CPC registers @@ -2030,7 +2033,7 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) * Since read_lock can be acquired by multiple CPUs simultaneously we * achieve that goal here */ - if (CPC_IN_PCC(desired_reg) || CPC_IN_PCC(min_perf_reg) || CPC_IN_PCC(max_perf_reg)) { + if (regs_in_pcc) { if (pcc_ss_id < 0) { pr_debug("Invalid pcc_ss_id\n"); return -ENODEV; @@ -2066,7 +2069,7 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) if (perf_ctrls->max_perf && CPC_SUPPORTED(max_perf_reg)) cpc_write(cpu, max_perf_reg, perf_ctrls->max_perf); - if (CPC_IN_PCC(desired_reg) || CPC_IN_PCC(min_perf_reg) || CPC_IN_PCC(max_perf_reg)) + if (regs_in_pcc) up_read(&pcc_ss_data->pcc_lock); /* END Phase-I */ /* * This is Phase-II where we transfer the ownership of PCC to Platform @@ -2114,7 +2117,7 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) * case during a CMD_READ and if there are pending writes it delivers * the write command before servicing the read command */ - if (CPC_IN_PCC(desired_reg) || CPC_IN_PCC(min_perf_reg) || CPC_IN_PCC(max_perf_reg)) { + if (regs_in_pcc) { if (down_write_trylock(&pcc_ss_data->pcc_lock)) {/* BEGIN Phase-II */ /* Update only if there are pending write commands */ if (pcc_ss_data->pending_pcc_write_cmd) From 525ba51157ac6e1772b5412eae26b20dd7ef8bda Mon Sep 17 00:00:00 2001 From: Marcos Paulo Medeiros Date: Mon, 27 Jul 2026 10:21:51 -0300 Subject: [PATCH 54/61] ACPI: video: force native backlight on Acer Nitro AN515-46 The Acer Nitro AN515-46 is a hybrid graphics laptop whose internal panel is driven by the AMD GPU (Radeon 680M); the discrete NVIDIA GPU only drives external outputs. The firmware nevertheless advertises the NVIDIA WMI EC backlight GUID, so the backlight type resolves to nvidia_wmi_ec. The nvidia-wmi-ec-backlight driver however fails to probe ("EC backlight control failed: AE_NOT_FOUND") and, since the backlight type is still nvidia_wmi_ec, amdgpu skips registering its own backlight device. The result is no backlight device at all, leaving the brightness keys and the desktop brightness slider non-functional. Booting with acpi_backlight=native makes amdgpu register its backlight interface and brightness control works. Add a DMI quirk to use the native backlight on this model by default. Signed-off-by: Marcos Paulo Medeiros Link: https://patch.msgid.link/20260727132151.12792-1-maarcospm1996@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 5b0dc679e322..07755b856973 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -919,6 +919,14 @@ static const struct dmi_system_id video_detect_dmi_table[] = { DMI_MATCH(DMI_PRODUCT_NAME, "OMEN by HP Gaming Laptop 16-n0xxx"), }, }, + { + .callback = video_detect_force_native, + /* Acer Nitro AN515-46 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Acer"), + DMI_MATCH(DMI_PRODUCT_NAME, "Nitro AN515-46"), + }, + }, /* * x86 android tablets which directly control the backlight through From a3df8bbe0a704fa5c1609b9666b594f350558fe0 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 5 Aug 2026 20:53:32 +0200 Subject: [PATCH 55/61] ACPI: TAD: Add locking around AML evaluations In the ACPI TAD driver, there are hidden assumptions that the ACPI control methods used by it will not be evaluated concurrently due to ACPICA namespace and interpreter locking. However, that may not be the case since ACPICA may drop and re-acquire the namespace and interpreter locks during the evaluation of a given object in a few cases, including the one in which the AML in question sleeps causing acpi_ex_system_do_sleep() to be called. In that case, the evaluation of one control method may be started while the evaluation of another one is still in progress. For this reason, add a global lock to the ACPI TAD driver and acquire it every time before evaluating an ACPI control method, except for the initial evaluation of _GCP in acpi_tad_probe(). Fixes: 95c513ec84f7 ("ACPI: Add Time and Alarm Device (TAD) driver") Cc: All applicable Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/12951141.O9o76ZdvQC@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 46 +++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index fc43df083738..45459a4cafae 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -86,6 +87,8 @@ static bool acpi_tad_rt_is_invalid(struct acpi_tad_rt *rt) rt->daylight > 3; } +static DEFINE_MUTEX(acpi_tad_aml_lock); + static int acpi_tad_set_real_time(struct device *dev, struct acpi_tad_rt *rt) { acpi_handle handle = ACPI_HANDLE(dev); @@ -113,6 +116,8 @@ static int acpi_tad_set_real_time(struct device *dev, struct acpi_tad_rt *rt) if (PM_RUNTIME_ACQUIRE_ERR(&pm)) return -ENXIO; + guard(mutex)(&acpi_tad_aml_lock); + status = acpi_evaluate_integer(handle, "_SRT", &arg_list, &retval); if (ACPI_FAILURE(status) || retval) return -EIO; @@ -124,30 +129,27 @@ static int acpi_tad_evaluate_grt(struct device *dev, struct acpi_tad_rt *rt) { acpi_handle handle = ACPI_HANDLE(dev); struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER }; - union acpi_object *out_obj; - struct acpi_tad_rt *data; acpi_status status; int ret = -EIO; + guard(mutex)(&acpi_tad_aml_lock); + status = acpi_evaluate_object(handle, "_GRT", NULL, &output); - if (ACPI_FAILURE(status)) - goto out_free; + if (ACPI_SUCCESS(status)) { + union acpi_object *out_obj; - out_obj = output.pointer; - if (out_obj->type != ACPI_TYPE_BUFFER) - goto out_free; + out_obj = output.pointer; + if (out_obj->type == ACPI_TYPE_BUFFER && + out_obj->buffer.length == sizeof(*rt)) { + struct acpi_tad_rt *data; - if (out_obj->buffer.length != sizeof(*rt)) - goto out_free; - - data = (struct acpi_tad_rt *)(out_obj->buffer.pointer); - if (!data->valid) - goto out_free; - - memcpy(rt, data, sizeof(*rt)); - ret = 0; - -out_free: + data = (struct acpi_tad_rt *)(out_obj->buffer.pointer); + if (data->valid) { + memcpy(rt, data, sizeof(*rt)); + ret = 0; + } + } + } ACPI_FREE(output.pointer); return ret; } @@ -193,6 +195,8 @@ static int __acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, args[0].integer.value = timer_id; args[1].integer.value = value; + guard(mutex)(&acpi_tad_aml_lock); + status = acpi_evaluate_integer(handle, method, &arg_list, &retval); if (ACPI_FAILURE(status) || retval) return -EIO; @@ -215,6 +219,8 @@ static int __acpi_tad_wake_read(struct device *dev, char *method, u32 timer_id, args[0].integer.value = timer_id; + guard(mutex)(&acpi_tad_aml_lock); + status = acpi_evaluate_integer(handle, method, &arg_list, retval); if (ACPI_FAILURE(status)) return -EIO; @@ -416,6 +422,8 @@ static int acpi_tad_clear_status(struct device *dev, u32 timer_id) if (PM_RUNTIME_ACQUIRE_ERR(&pm)) return -ENXIO; + guard(mutex)(&acpi_tad_aml_lock); + status = acpi_evaluate_integer(handle, "_CWS", &arg_list, &retval); if (ACPI_FAILURE(status) || retval) return -EIO; @@ -456,6 +464,8 @@ static ssize_t acpi_tad_status_read(struct device *dev, char *buf, u32 timer_id) if (PM_RUNTIME_ACQUIRE_ERR(&pm)) return -ENXIO; + guard(mutex)(&acpi_tad_aml_lock); + status = acpi_evaluate_integer(handle, "_GWS", &arg_list, &retval); if (ACPI_FAILURE(status)) return -EIO; From fa608d2875b57fac792a9e1c11aa1da91737f04a Mon Sep 17 00:00:00 2001 From: Kazuma Kondo Date: Thu, 6 Aug 2026 11:21:11 +0000 Subject: [PATCH 56/61] ACPI: PCI: Avoid misleading _OSC messages for non-PCIe host bridges without _OSC After commit 7d703df7f4f5 ("ACPI: bus: Split _OSC evaluation out of acpi_run_osc()"), the _OSC evaluation path now returns AE_ERROR to negotiate_os_control() instead of propagating AE_NOT_FOUND from acpi_evaluate_object(). This has not caused any functional issues so far, but it produces additional misleading messages for non-PCIe host bridges without _OSC on some Intel servers: kernel: ACPI: Enabled 2 GPEs in block 00 to 7F kernel: ACPI: PCI Root Bridge [UNC0] (domain 0000 [bus fe]) kernel: acpi PNP0A03:00: _OSC: OS supports [ExtendedConfig ASPM ClockPM Segments MSI EDR HPX-Type3] + kernel: acpi PNP0A03:00: _OSC: OS requested [PCIeHotplug SHPCHotplug PME AER PCIeCapability LTR DPC] + kernel: acpi PNP0A03:00: _OSC: platform willing to grant [PCIeHotplug SHPCHotplug PME AER PCIeCapability LTR DPC] + kernel: acpi PNP0A03:00: _OSC: platform retains control of PCIe features (AE_ERROR) kernel: PCI host bridge to bus 0000:fe Previously, negotiate_os_control() silently ignored AE_NOT_FOUND for non-PCIe host bridges without _OSC, but after the above change, it no longer does so. As a result, negotiate_os_control() logs messages as if the OS had negotiated with the platform via _OSC, even though the non-PCIe host bridge has no _OSC method and no such negotiation actually occurs. Skip _OSC negotiation for non-PCIe host bridges that do not define an _OSC method before attempting to evaluate it. Fixes: 7d703df7f4f5 ("ACPI: bus: Split _OSC evaluation out of acpi_run_osc()") Link: https://lore.kernel.org/linux-acpi/d0be949d-0e21-472e-a44b-cedb1dd8695d@nec.com/ Suggested-by: Rafael J. Wysocki Signed-off-by: Kazuma Kondo [ rjw: Adjust white space, drop comment and tweak the new message ] Link: https://patch.msgid.link/20260806112052.857684-1-kazuma-kondo@nec.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/pci_root.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 6f78f96332ea..88c65f34e305 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -571,6 +571,13 @@ static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm) return; } + if (!is_pcie(root) && !is_cxl(root) && !acpi_has_method(handle, "_OSC")) { + dev_dbg(&device->dev, "Non-PCIe host bridge without _OSC, skipping\n"); + + *no_aspm = 1; + return; + } + support = calculate_support(); decode_osc_support(root, "OS supports", support); @@ -612,10 +619,6 @@ static void negotiate_os_control(struct acpi_pci_root *root, int *no_aspm) */ *no_aspm = 1; - /* _OSC is optional for PCI host bridges */ - if (status == AE_NOT_FOUND && !is_pcie(root)) - return; - if (control) { decode_osc_control(root, "OS requested", requested); decode_osc_control(root, "platform willing to grant", control); From 3d7ed9b8ef47b8bcae1fc3e12f7590a217862a89 Mon Sep 17 00:00:00 2001 From: Yuho Choi Date: Thu, 6 Aug 2026 21:57:34 -0400 Subject: [PATCH 57/61] ACPI: video: Release PCI device reference after lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit video_detect_portege_r100() uses pci_get_device() only as a boolean check for the Trident CyberBlade XP4m32 device. pci_get_device() takes a reference on a matching PCI device, but the callback returns without releasing it. Drop the reference after selecting the vendor backlight quirk so the PCI device can be released normally. Fixes: 35a341c9b25d ("ACPI: video: Add acpi_backlight=vendor quirk for Toshiba Portégé R100") Signed-off-by: Yuho Choi Link: https://patch.msgid.link/20260807015734.913361-1-dbgh9129@gmail.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/video_detect.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index 07755b856973..e3b69f876d24 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -132,8 +132,10 @@ static int video_detect_portege_r100(const struct dmi_system_id *d) struct pci_dev *dev; /* Search for Trident CyberBlade XP4m32 to confirm Portégé R100 */ dev = pci_get_device(PCI_VENDOR_ID_TRIDENT, 0x2100, NULL); - if (dev) + if (dev) { acpi_backlight_dmi = acpi_backlight_vendor; + pci_dev_put(dev); + } return 0; } From 903308ea40adf0577d82eab69882faf8836326ce Mon Sep 17 00:00:00 2001 From: TanZheng Date: Thu, 6 Aug 2026 09:09:44 +0800 Subject: [PATCH 58/61] ACPI: APEI: GHES: fix ARM section length accounting after header In ghes_handle_arm_hw_error(), after skipping the cper_sec_proc_arm header with (err + 1), the remaining length was reduced by sizeof(err) (pointer size) instead of sizeof(*err) (structure size). That overestimates the bytes left for cper_arm_err_info records and can let the parser read past the CPER section when err_info_num is large enough relative to error_data_length. Use sizeof(*err) so the length accounting matches the pointer advance and the earlier sizeof(*err) size check. Fixes: 87880af2d24e ("APEI/GHES: ARM processor Error: don't go past allocated memory") Cc: stable@vger.kernel.org Signed-off-by: TanZheng Reviewed-by: Shuai Xue Link: https://patch.msgid.link/20260806010944.32384-1-kensanya@163.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 48f915ee5a0d..f245169c3026 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -576,7 +576,7 @@ static bool ghes_handle_arm_hw_error(struct acpi_hest_generic_data *gdata, return false; p = (char *)(err + 1); - length -= sizeof(err); + length -= sizeof(*err); for (i = 0; i < err->err_info_num; i++) { struct cper_arm_err_info *err_info; From a685d8eea4a6899dc887e393927c16fa18ff5e9a Mon Sep 17 00:00:00 2001 From: Nirmoy Das Date: Tue, 21 Jul 2026 11:25:51 -0700 Subject: [PATCH 59/61] ACPI: APEI: Fix ERST timeout unit conversion The ACPI specification defines bits 63:32 returned by GET_EXECUTE_OPERATION_TIMINGS as the maximum execution time in microseconds. erst_get_timeout() instead multiplies the value by NSEC_PER_MSEC. Use NSEC_PER_USEC to express the firmware-provided microsecond timeout in the nanosecond units expected by erst_timedout(). Fixes: fac475aab70b ("ACPI: APEI: Use ERST timeout for slow devices") Cc: stable@vger.kernel.org Signed-off-by: Nirmoy Das Reviewed-by: Hanjun Guo Link: https://patch.msgid.link/20260721182551.2434933-1-nirmoyd@nvidia.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/erst.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/apei/erst.c b/drivers/acpi/apei/erst.c index bf65e3461531..15ab797641cf 100644 --- a/drivers/acpi/apei/erst.c +++ b/drivers/acpi/apei/erst.c @@ -108,7 +108,7 @@ static inline u64 erst_get_timeout(void) if (erst_erange.attr & ERST_RANGE_SLOW) { timeout = ((erst_erange.timings & ERST_EXEC_TIMING_MAX_MASK) >> - ERST_EXEC_TIMING_MAX_SHIFT) * NSEC_PER_MSEC; + ERST_EXEC_TIMING_MAX_SHIFT) * NSEC_PER_USEC; if (timeout < FIRMWARE_TIMEOUT) timeout = FIRMWARE_TIMEOUT; } From e61487226b7df39e5f4683b036ee6aa7a5039d7b Mon Sep 17 00:00:00 2001 From: Junhao He Date: Wed, 27 May 2026 16:27:07 +0800 Subject: [PATCH 60/61] ACPI: APEI: Handle repeated SEA error storms When hardware memory corruption occurs and a user process accesses the corrupted page, the CPU triggers a Synchronous External Abort (SEA). The kernel invokes do_sea() to handle the exception, which calls memory_failure() to handle the faulty page. Scenario 1: Memory Error Interrupt First, then SEA The page is already poisoned by the memory error interrupt path. The subsequent SEA handler sends a SIGBUS to the task, which accesses the poisoned page. This flow is correct. Scenario 2: SEA first, then memory error interrupt (problematic scenario) If a user task directly accesses corrupted memory through a PFNMAP-style mapping (e.g., devmem), the page may still be in the free-buddy state when SEA is handled. In this case, memory_failure() will poison the page without invoking kill_accessing_process(), and then takes the free-buddy recovery path. After the CPU returns to the task context, the task re-enters the SEA handler due to the same access. However, ghes_estatus_cached() suppresses all subsequent entries during the 10-second window, preventing ghes_do_proc() from being called. This suppression blocks the MF_ACTION_REQUIRED-based SIGBUS delivery, causing the kernel to fail to kill the task immediately. Consequently, the process keeps re-entering the SEA handler, leading to an SEA storm. Later, the memory error interrupt path also cannot kill the task, leaving the system stuck in this repeated loop. The following error logs are explained using the devmem process: NOTICE: SEA Handle [Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 9 [Hardware Error]: event severity: recoverable [Hardware Error]: section_type: ARM processor error [Hardware Error]: physical fault address: 0x0000001000093c00 [T54990] Memory failure: 0x1000093: recovery action for free buddy page: Recovered [ T9955] EDAC MC0: 1 UE Multi-bit ECC on unknown memory (page:0x1000093 offset:0xc00 grain:1 - APEI location: ...) NOTICE: SEA Handle NOTICE: SEA Handle ... ... ---> SEA storm ... NOTICE: SEA Handle [ T9955] Memory failure: 0x1000093: already hardware poisoned ghes_print_estatus: 1 callbacks suppressed [Hardware Error]: Hardware error from APEI Generic Hardware Error Source: 9 [Hardware Error]: event severity: recoverable [Hardware Error]: section_type: ARM processor error [Hardware Error]: physical fault address: 0x0000001000093c00 [T54990] Memory failure: 0x1000093: already hardware poisoned [T54990] 0x1000093: Sending SIGBUS to devmem:54990 due to hardware memory corruption To resolve this, return an error when encountering the same SEA again. The subsequent SEA handler invocation uses arm64_notify_die() to send a SIGBUS signal to the task, which terminates the process and prevents it from re-entering the handler loop. Signed-off-by: Junhao He Reviewed-by: Wupeng Ma Reviewed-by: Shuai Xue Reviewed-by: Tony Luck Link: https://patch.msgid.link/20260527082707.2013499-1-hejunhao3@h-partners.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index f245169c3026..bc1dfa3398b4 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -1383,8 +1383,16 @@ static int ghes_in_nmi_queue_one_entry(struct ghes *ghes, ghes_clear_estatus(ghes, &tmp_header, buf_paddr, fixmap_idx); /* This error has been reported before, don't process it again. */ - if (ghes_estatus_cached(estatus)) + if (ghes_estatus_cached(estatus)) { + /* + * Return failure on duplicate SEA entries so that the + * subsequent SEA handler invocation sends a SIGBUS signal to + * the task to prevent it from re-entering the handler loop. + */ + if (is_hest_sync_notify(ghes)) + rc = -ECANCELED; goto no_work; + } llist_add(&estatus_node->llnode, &ghes_estatus_llist); From f234fdaae1cad8c39265e7ca0a14633076ec7154 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 7 Aug 2026 12:22:37 +0200 Subject: [PATCH 61/61] ACPI: scan: Avoid registering platform devices with resource overlaps If acpi_dev_get_resources() returns overlapping I/O or memory resources, the subsequent registration of a platform device will fail with -EBUSY due to a resource conflict. This is reported to happen on Acer Aspire ES1-572 [1]. Avoid that by adjusting resources returned by acpi_dev_get_resources() to eliminate partial overlaps between them. This has not been regarded as necessary before because putting overlapping resources into the _CRS of one device is really pointless, but now that the issue has been reported to actually happen in the field, it needs to be done. Fixes: ab06eb920401 ("ACPI: scan: Register platform devices for fixed event buttons") Fixes: 48fe2cddc85c ("tpm_crb: Convert ACPI driver to a platform one") Reported-by: Julien Tested-by: Julien Reviewed-by: Paul Menzel Reviewed-by: Andy Shevchenko Closes: https://lore.kernel.org/linux-integrity/CAJOGg3z6LJPDsdPNBxajgy8_wQxfhYBRxe4EiurZf3kPU5A5Bw@mail.gmail.com/ [1] Cc: All applicable Signed-off-by: Rafael J. Wysocki [ rjw: Tweaked the new message ] Link: https://patch.msgid.link/12955541.O9o76ZdvQC@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_platform.c | 39 ++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/acpi_platform.c b/drivers/acpi/acpi_platform.c index a09636a4168e..373c94de7590 100644 --- a/drivers/acpi/acpi_platform.c +++ b/drivers/acpi/acpi_platform.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -71,6 +72,36 @@ static struct notifier_block acpi_platform_notifier = { .notifier_call = acpi_platform_device_remove_notify, }; +static unsigned int acpi_platform_adjust_resources(struct acpi_device *adev, + struct resource *new_res, + struct resource *resources, + unsigned int count) +{ + unsigned int i; + + if (!(new_res->flags & (IORESOURCE_IO | IORESOURCE_MEM))) + return count; + + for (i = 0; i < count; ) { + struct resource *res = &resources[i]; + + if (resource_type(new_res) != resource_type(res) || + !resource_union(new_res, res, new_res)) { + i++; + continue; + } + + dev_info(&adev->dev, "%pR expanded due to overlap\n", new_res); + /* + * Eliminate the previously processed resource that overlapped + * with the new one because it is not necessary any more. + */ + memmove(res, res + 1, (--count - i) * sizeof(*res)); + } + + return count; +} + static void acpi_platform_fill_resource(struct acpi_device *adev, const struct resource *src, struct resource *dest) { @@ -151,10 +182,14 @@ struct platform_device *acpi_create_platform_device(struct acpi_device *adev, return ERR_PTR(-ENOMEM); } count = 0; - list_for_each_entry(rentry, &resource_list, node) + list_for_each_entry(rentry, &resource_list, node) { + count = acpi_platform_adjust_resources(adev, + rentry->res, + resources, + count); acpi_platform_fill_resource(adev, rentry->res, &resources[count++]); - + } acpi_dev_free_resource_list(&resource_list); } }