From ba0b236736dde4059bdcb8e99beaa50d6e5b6e7e Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 23 Feb 2026 16:28:15 +0100 Subject: [PATCH 01/73] ACPI: x86: cmos_rtc: Clean up address space handler driver Make multiple changes that do not alter functionality to the CMOS RTC ACPI address space handler driver, including the following: - Drop the unused .detach() callback from cmos_rtc_handler. - Rename acpi_cmos_rtc_attach_handler() to acpi_cmos_rtc_attach(). - Rearrange acpi_cmos_rtc_space_handler() to reduce the number of redundant checks and make white space follow the coding style. - Adjust an error message in acpi_install_cmos_rtc_space_handler() and make the white space follow the coding style. - Rearrange acpi_remove_cmos_rtc_space_handler() and adjust an error message in it. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/5094429.31r3eYUQgx@rafael.j.wysocki --- drivers/acpi/x86/cmos_rtc.c | 63 +++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/drivers/acpi/x86/cmos_rtc.c b/drivers/acpi/x86/cmos_rtc.c index 51643ff6fe5f..977234da9fc1 100644 --- a/drivers/acpi/x86/cmos_rtc.c +++ b/drivers/acpi/x86/cmos_rtc.c @@ -24,31 +24,35 @@ static const struct acpi_device_id acpi_cmos_rtc_ids[] = { {} }; -static acpi_status -acpi_cmos_rtc_space_handler(u32 function, acpi_physical_address address, - u32 bits, u64 *value64, - void *handler_context, void *region_context) +static acpi_status acpi_cmos_rtc_space_handler(u32 function, + acpi_physical_address address, + u32 bits, u64 *value64, + void *handler_context, + void *region_context) { - int i; + unsigned int i, bytes = DIV_ROUND_UP(bits, 8); u8 *value = (u8 *)value64; if (address > 0xff || !value64) return AE_BAD_PARAMETER; - if (function != ACPI_WRITE && function != ACPI_READ) - return AE_BAD_PARAMETER; + guard(spinlock_irq)(&rtc_lock); - spin_lock_irq(&rtc_lock); - - for (i = 0; i < DIV_ROUND_UP(bits, 8); ++i, ++address, ++value) - if (function == ACPI_READ) - *value = CMOS_READ(address); - else + if (function == ACPI_WRITE) { + for (i = 0; i < bytes; i++, address++, value++) CMOS_WRITE(*value, address); - spin_unlock_irq(&rtc_lock); + return AE_OK; + } - return AE_OK; + if (function == ACPI_READ) { + for (i = 0; i < bytes; i++, address++, value++) + *value = CMOS_READ(address); + + return AE_OK; + } + + return AE_BAD_PARAMETER; } int acpi_install_cmos_rtc_space_handler(acpi_handle handle) @@ -56,11 +60,11 @@ int acpi_install_cmos_rtc_space_handler(acpi_handle handle) acpi_status status; status = acpi_install_address_space_handler(handle, - ACPI_ADR_SPACE_CMOS, - &acpi_cmos_rtc_space_handler, - NULL, NULL); + ACPI_ADR_SPACE_CMOS, + acpi_cmos_rtc_space_handler, + NULL, NULL); if (ACPI_FAILURE(status)) { - pr_err("Error installing CMOS-RTC region handler\n"); + pr_err("Failed to install CMOS-RTC address space handler\n"); return -ENODEV; } @@ -70,26 +74,25 @@ EXPORT_SYMBOL_GPL(acpi_install_cmos_rtc_space_handler); void acpi_remove_cmos_rtc_space_handler(acpi_handle handle) { - if (ACPI_FAILURE(acpi_remove_address_space_handler(handle, - ACPI_ADR_SPACE_CMOS, &acpi_cmos_rtc_space_handler))) - pr_err("Error removing CMOS-RTC region handler\n"); + acpi_status status; + + status = acpi_remove_address_space_handler(handle, + ACPI_ADR_SPACE_CMOS, + acpi_cmos_rtc_space_handler); + if (ACPI_FAILURE(status)) + pr_err("Failed to remove CMOS-RTC address space handler\n"); } EXPORT_SYMBOL_GPL(acpi_remove_cmos_rtc_space_handler); -static int acpi_cmos_rtc_attach_handler(struct acpi_device *adev, const struct acpi_device_id *id) +static int acpi_cmos_rtc_attach(struct acpi_device *adev, + const struct acpi_device_id *id) { return acpi_install_cmos_rtc_space_handler(adev->handle); } -static void acpi_cmos_rtc_detach_handler(struct acpi_device *adev) -{ - acpi_remove_cmos_rtc_space_handler(adev->handle); -} - static struct acpi_scan_handler cmos_rtc_handler = { .ids = acpi_cmos_rtc_ids, - .attach = acpi_cmos_rtc_attach_handler, - .detach = acpi_cmos_rtc_detach_handler, + .attach = acpi_cmos_rtc_attach, }; void __init acpi_cmos_rtc_init(void) From 6cee29ad9d7e400d39ae0b1a54447fedcb62eecd Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 23 Feb 2026 16:28:57 +0100 Subject: [PATCH 02/73] ACPI: x86: cmos_rtc: Improve coordination with ACPI TAD driver If a CMOS RTC (PNP0B00/PNP0B01/PNP0B02) device coexists with an ACPI TAD (timer and event alarm device, ACPI000E), the ACPI TAD driver will attempt to install the CMOS RTC address space hanlder that has been installed already and the TAD probing will fail. Avoid that by changing acpi_install_cmos_rtc_space_handler() to return zero and acpi_remove_cmos_rtc_space_handler() to do nothing if the CMOS RTC address space handler has been installed already. Fixes: 596ca52a56da ("ACPI: TAD: Install SystemCMOS address space handler for ACPI000E") Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2415111.ElGaqSPkdT@rafael.j.wysocki --- drivers/acpi/x86/cmos_rtc.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/x86/cmos_rtc.c b/drivers/acpi/x86/cmos_rtc.c index 977234da9fc1..45db7e51cbe6 100644 --- a/drivers/acpi/x86/cmos_rtc.c +++ b/drivers/acpi/x86/cmos_rtc.c @@ -24,6 +24,8 @@ static const struct acpi_device_id acpi_cmos_rtc_ids[] = { {} }; +static bool cmos_rtc_space_handler_present __read_mostly; + static acpi_status acpi_cmos_rtc_space_handler(u32 function, acpi_physical_address address, u32 bits, u64 *value64, @@ -59,6 +61,9 @@ int acpi_install_cmos_rtc_space_handler(acpi_handle handle) { acpi_status status; + if (cmos_rtc_space_handler_present) + return 0; + status = acpi_install_address_space_handler(handle, ACPI_ADR_SPACE_CMOS, acpi_cmos_rtc_space_handler, @@ -68,6 +73,8 @@ int acpi_install_cmos_rtc_space_handler(acpi_handle handle) return -ENODEV; } + cmos_rtc_space_handler_present = true; + return 1; } EXPORT_SYMBOL_GPL(acpi_install_cmos_rtc_space_handler); @@ -76,6 +83,9 @@ void acpi_remove_cmos_rtc_space_handler(acpi_handle handle) { acpi_status status; + if (cmos_rtc_space_handler_present) + return; + status = acpi_remove_address_space_handler(handle, ACPI_ADR_SPACE_CMOS, acpi_cmos_rtc_space_handler); @@ -87,7 +97,13 @@ EXPORT_SYMBOL_GPL(acpi_remove_cmos_rtc_space_handler); static int acpi_cmos_rtc_attach(struct acpi_device *adev, const struct acpi_device_id *id) { - return acpi_install_cmos_rtc_space_handler(adev->handle); + int ret; + + ret = acpi_install_cmos_rtc_space_handler(adev->handle); + if (ret < 0) + return ret; + + return 1; } static struct acpi_scan_handler cmos_rtc_handler = { From 1ae2f435350ec05224a39995c3a680aa6fdae5a5 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 23 Feb 2026 16:29:37 +0100 Subject: [PATCH 03/73] ACPI: x86: cmos_rtc: Create a CMOS RTC platform device Make the CMOS RTC ACPI scan handler create a platform device that will be used subsequently by rtc-cmos for driver binding on x86 systems with ACPI and update add_rtc_cmos() to skip registering a fallback platform device for the CMOS RTC when the above one has been registered. Signed-off-by: Rafael J. Wysocki Acked-by: Dave Hansen # x86 Link: https://patch.msgid.link/1962427.tdWV9SEqCh@rafael.j.wysocki --- arch/x86/kernel/rtc.c | 4 ++++ drivers/acpi/x86/cmos_rtc.c | 8 ++++++++ include/linux/acpi.h | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/arch/x86/kernel/rtc.c b/arch/x86/kernel/rtc.c index 51a849a79c98..b112178e8185 100644 --- a/arch/x86/kernel/rtc.c +++ b/arch/x86/kernel/rtc.c @@ -2,6 +2,7 @@ /* * RTC related functions */ +#include #include #include #include @@ -146,6 +147,9 @@ static __init int add_rtc_cmos(void) } } #endif + if (cmos_rtc_platform_device_present) + return 0; + if (!x86_platform.legacy.rtc) return -ENODEV; diff --git a/drivers/acpi/x86/cmos_rtc.c b/drivers/acpi/x86/cmos_rtc.c index 45db7e51cbe6..bdd66dfd4a44 100644 --- a/drivers/acpi/x86/cmos_rtc.c +++ b/drivers/acpi/x86/cmos_rtc.c @@ -24,6 +24,8 @@ static const struct acpi_device_id acpi_cmos_rtc_ids[] = { {} }; +bool cmos_rtc_platform_device_present; + static bool cmos_rtc_space_handler_present __read_mostly; static acpi_status acpi_cmos_rtc_space_handler(u32 function, @@ -103,6 +105,12 @@ static int acpi_cmos_rtc_attach(struct acpi_device *adev, if (ret < 0) return ret; + if (IS_ERR_OR_NULL(acpi_create_platform_device(adev, NULL))) { + pr_err("Failed to create CMOS-RTC platform device\n"); + return 0; + } else { + cmos_rtc_platform_device_present = true; + } return 1; } diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 4d2f0bed7a06..2bdb801cee01 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -791,6 +791,8 @@ const char *acpi_get_subsystem_id(acpi_handle handle); int acpi_mrrm_max_mem_region(void); #endif +extern bool cmos_rtc_platform_device_present; + #else /* !CONFIG_ACPI */ #define acpi_disabled 1 @@ -1116,6 +1118,8 @@ static inline int acpi_mrrm_max_mem_region(void) return 1; } +#define cmos_rtc_platform_device_present false + #endif /* !CONFIG_ACPI */ #ifdef CONFIG_ACPI_HMAT From 2a78e42104444f948698f1225deaf515e9b7224d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 23 Feb 2026 16:30:21 +0100 Subject: [PATCH 04/73] ACPI: x86/rtc-cmos: Use platform device for driver binding Modify the rtc-cmos driver to bind to a platform device on systems with ACPI via acpi_match_table and advertise the CMOST RTC ACPI device IDs for driver auto-loading. Note that adding the requisite device IDs to it and exposing them via MODULE_DEVICE_TABLE() is sufficient for this purpose. Since the ACPI device IDs in question are the same as for the CMOS RTC ACPI scan handler, put them into a common header file and use the definition from there in both places. Additionally, to prevent a PNP device from being created for the CMOS RTC if a platform one is present already, make is_cmos_rtc_device() check cmos_rtc_platform_device_present introduced previously. Signed-off-by: Rafael J. Wysocki Acked-by: Alexandre Belloni Link: https://patch.msgid.link/13969123.uLZWGnKmhe@rafael.j.wysocki --- drivers/acpi/acpi_pnp.c | 2 +- drivers/acpi/x86/cmos_rtc.c | 5 +---- drivers/rtc/rtc-cmos.c | 10 ++++++++++ include/linux/acpi.h | 6 ++++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/acpi_pnp.c b/drivers/acpi/acpi_pnp.c index 85d9f78619a2..4ad8f56d1a5d 100644 --- a/drivers/acpi/acpi_pnp.c +++ b/drivers/acpi/acpi_pnp.c @@ -368,7 +368,7 @@ static int is_cmos_rtc_device(struct acpi_device *adev) { "PNP0B02" }, {""}, }; - return !acpi_match_device_ids(adev, ids); + return !cmos_rtc_platform_device_present && !acpi_match_device_ids(adev, ids); } bool acpi_is_pnp_device(struct acpi_device *adev) diff --git a/drivers/acpi/x86/cmos_rtc.c b/drivers/acpi/x86/cmos_rtc.c index bdd66dfd4a44..a6df5b991c96 100644 --- a/drivers/acpi/x86/cmos_rtc.c +++ b/drivers/acpi/x86/cmos_rtc.c @@ -18,10 +18,7 @@ #include "../internal.h" static const struct acpi_device_id acpi_cmos_rtc_ids[] = { - { "PNP0B00" }, - { "PNP0B01" }, - { "PNP0B02" }, - {} + ACPI_CMOS_RTC_IDS }; bool cmos_rtc_platform_device_present; diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index 0743c6acd6e2..7457f42fd6f0 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -27,6 +27,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include #include #include #include @@ -1476,6 +1477,14 @@ static __init void cmos_of_init(struct platform_device *pdev) #else static inline void cmos_of_init(struct platform_device *pdev) {} #endif + +#ifdef CONFIG_ACPI +static const struct acpi_device_id acpi_cmos_rtc_ids[] = { + ACPI_CMOS_RTC_IDS +}; +MODULE_DEVICE_TABLE(acpi, acpi_cmos_rtc_ids); +#endif + /*----------------------------------------------------------------*/ /* Platform setup should have set up an RTC device, when PNP is @@ -1530,6 +1539,7 @@ static struct platform_driver cmos_platform_driver = { .name = driver_name, .pm = &cmos_pm_ops, .of_match_table = of_match_ptr(of_cmos_match), + .acpi_match_table = ACPI_PTR(acpi_cmos_rtc_ids), } }; diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 2bdb801cee01..5ecdcdaf31aa 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -791,6 +791,12 @@ const char *acpi_get_subsystem_id(acpi_handle handle); int acpi_mrrm_max_mem_region(void); #endif +#define ACPI_CMOS_RTC_IDS \ + { "PNP0B00", }, \ + { "PNP0B01", }, \ + { "PNP0B02", }, \ + { "", } + extern bool cmos_rtc_platform_device_present; #else /* !CONFIG_ACPI */ From d15f1c2e413e861270ca6aa5dc5d9da1bcd678ca Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 23 Feb 2026 16:31:03 +0100 Subject: [PATCH 05/73] ACPI: PNP: Drop CMOS RTC PNP device support A previous change updated is_cmos_rtc_device() to effectively never allow PNP devices to be created for the CMOS RTC on x86 with ACPI and the PNP bus type is only used on x86, so the CMOS RTC device IDs can be dropped from acpi_pnp_device_ids[] and is_cmos_rtc_device() can go away completely. Update the code accordingly. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3411834.aeNJFYEL58@rafael.j.wysocki --- drivers/acpi/acpi_pnp.c | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/drivers/acpi/acpi_pnp.c b/drivers/acpi/acpi_pnp.c index 4ad8f56d1a5d..da886923b008 100644 --- a/drivers/acpi/acpi_pnp.c +++ b/drivers/acpi/acpi_pnp.c @@ -125,10 +125,6 @@ static const struct acpi_device_id acpi_pnp_device_ids[] = { {"PNP0401"}, /* ECP Printer Port */ /* apple-gmux */ {"APP000B"}, - /* rtc_cmos */ - {"PNP0b00"}, - {"PNP0b01"}, - {"PNP0b02"}, /* c6xdigio */ {"PNP0400"}, /* Standard LPT Printer Port */ {"PNP0401"}, /* ECP Printer Port */ @@ -355,25 +351,9 @@ static struct acpi_scan_handler acpi_pnp_handler = { .attach = acpi_pnp_attach, }; -/* - * For CMOS RTC devices, the PNP ACPI scan handler does not work, because - * there is a CMOS RTC ACPI scan handler installed already, so we need to - * check those devices and enumerate them to the PNP bus directly. - */ -static int is_cmos_rtc_device(struct acpi_device *adev) -{ - static const struct acpi_device_id ids[] = { - { "PNP0B00" }, - { "PNP0B01" }, - { "PNP0B02" }, - {""}, - }; - return !cmos_rtc_platform_device_present && !acpi_match_device_ids(adev, ids); -} - bool acpi_is_pnp_device(struct acpi_device *adev) { - return adev->handler == &acpi_pnp_handler || is_cmos_rtc_device(adev); + return adev->handler == &acpi_pnp_handler; } EXPORT_SYMBOL_GPL(acpi_is_pnp_device); From 0139085310c40853cc429d5c38fd66e540c97d34 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 23 Feb 2026 16:31:49 +0100 Subject: [PATCH 06/73] x86: rtc: Drop PNP device check Previous changes effectively prevented PNP devices from being created for the CMOS RTC on x86 with ACPI. Although in principle a CMOS RTC PNP device may exist on an x86 system without ACPI (that is, an x86 system where there is no ACPI at all, not one booted with ACPI disabled), such systems were there in the field ~30 years ago and most likely they would not be able to run a contemporary Linux kernel. For the above reasons, drop the PNP device check from add_rtc_cmos(). Signed-off-by: Rafael J. Wysocki Acked-by: Dave Hansen # x86 Link: https://patch.msgid.link/8660687.T7Z3S40VBb@rafael.j.wysocki --- arch/x86/kernel/rtc.c | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/arch/x86/kernel/rtc.c b/arch/x86/kernel/rtc.c index b112178e8185..314b062a15de 100644 --- a/arch/x86/kernel/rtc.c +++ b/arch/x86/kernel/rtc.c @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -134,19 +133,6 @@ static struct platform_device rtc_device = { static __init int add_rtc_cmos(void) { -#ifdef CONFIG_PNP - static const char * const ids[] __initconst = - { "PNP0b00", "PNP0b01", "PNP0b02", }; - struct pnp_dev *dev; - int i; - - pnp_for_each_dev(dev) { - for (i = 0; i < ARRAY_SIZE(ids); i++) { - if (compare_pnp_id(dev->id, ids[i]) != 0) - return 0; - } - } -#endif if (cmos_rtc_platform_device_present) return 0; @@ -154,8 +140,7 @@ static __init int add_rtc_cmos(void) return -ENODEV; platform_device_register(&rtc_device); - dev_info(&rtc_device.dev, - "registered platform RTC device (no PNP device found)\n"); + dev_info(&rtc_device.dev, "registered fallback platform RTC device\n"); return 0; } From 62bf102c9d47a4eeb7a4f82f5ba032187942dc7d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 23 Feb 2026 16:32:29 +0100 Subject: [PATCH 07/73] rtc: cmos: Drop PNP device support Previous changes effectively prevented PNP devices from being created for the CMOS RTC on x86 with ACPI. Although in principle a CMOS RTC PNP device may exist on an x86 system without ACPI (that is, an x86 system where there is no ACPI at all, not one booted with ACPI disabled), such systems were there in the field ~30 years ago and most likely they would not be able to run a contemporary Linux kernel. For the above reasons, drop the PNP device support from the rtc-cmos driver. Signed-off-by: Rafael J. Wysocki Acked-by: Alexandre Belloni Link: https://patch.msgid.link/2355012.iZASKD2KPV@rafael.j.wysocki --- drivers/rtc/rtc-cmos.c | 115 ++++------------------------------------- 1 file changed, 9 insertions(+), 106 deletions(-) diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index 7457f42fd6f0..9ac5bab846c1 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -1370,85 +1370,6 @@ static int __maybe_unused cmos_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(cmos_pm_ops, cmos_suspend, cmos_resume); -/*----------------------------------------------------------------*/ - -/* On non-x86 systems, a "CMOS" RTC lives most naturally on platform_bus. - * ACPI systems always list these as PNPACPI devices, and pre-ACPI PCs - * probably list them in similar PNPBIOS tables; so PNP is more common. - * - * We don't use legacy "poke at the hardware" probing. Ancient PCs that - * predate even PNPBIOS should set up platform_bus devices. - */ - -#ifdef CONFIG_PNP - -#include - -static int cmos_pnp_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) -{ - int irq; - - if (pnp_port_start(pnp, 0) == 0x70 && !pnp_irq_valid(pnp, 0)) { - irq = 0; -#ifdef CONFIG_X86 - /* Some machines contain a PNP entry for the RTC, but - * don't define the IRQ. It should always be safe to - * hardcode it on systems with a legacy PIC. - */ - if (nr_legacy_irqs()) - irq = RTC_IRQ; -#endif - } else { - irq = pnp_irq(pnp, 0); - } - - return cmos_do_probe(&pnp->dev, pnp_get_resource(pnp, IORESOURCE_IO, 0), irq); -} - -static void cmos_pnp_remove(struct pnp_dev *pnp) -{ - cmos_do_remove(&pnp->dev); -} - -static void cmos_pnp_shutdown(struct pnp_dev *pnp) -{ - struct device *dev = &pnp->dev; - struct cmos_rtc *cmos = dev_get_drvdata(dev); - - if (system_state == SYSTEM_POWER_OFF) { - int retval = cmos_poweroff(dev); - - if (cmos_aie_poweroff(dev) < 0 && !retval) - return; - } - - cmos_do_shutdown(cmos->irq); -} - -static const struct pnp_device_id rtc_ids[] = { - { .id = "PNP0b00", }, - { .id = "PNP0b01", }, - { .id = "PNP0b02", }, - { }, -}; -MODULE_DEVICE_TABLE(pnp, rtc_ids); - -static struct pnp_driver cmos_pnp_driver = { - .name = driver_name, - .id_table = rtc_ids, - .probe = cmos_pnp_probe, - .remove = cmos_pnp_remove, - .shutdown = cmos_pnp_shutdown, - - /* flag ensures resume() gets called, and stops syslog spam */ - .flags = PNP_DRIVER_RES_DO_NOT_CHANGE, - .driver = { - .pm = &cmos_pm_ops, - }, -}; - -#endif /* CONFIG_PNP */ - #ifdef CONFIG_OF static const struct of_device_id of_cmos_match[] = { { @@ -1543,45 +1464,27 @@ static struct platform_driver cmos_platform_driver = { } }; -#ifdef CONFIG_PNP -static bool pnp_driver_registered; -#endif static bool platform_driver_registered; static int __init cmos_init(void) { - int retval = 0; + int retval; -#ifdef CONFIG_PNP - retval = pnp_register_driver(&cmos_pnp_driver); - if (retval == 0) - pnp_driver_registered = true; -#endif - - if (!cmos_rtc.dev) { - retval = platform_driver_probe(&cmos_platform_driver, - cmos_platform_probe); - if (retval == 0) - platform_driver_registered = true; - } - - if (retval == 0) + if (cmos_rtc.dev) return 0; -#ifdef CONFIG_PNP - if (pnp_driver_registered) - pnp_unregister_driver(&cmos_pnp_driver); -#endif - return retval; + retval = platform_driver_probe(&cmos_platform_driver, cmos_platform_probe); + if (retval) + return retval; + + platform_driver_registered = true; + + return 0; } module_init(cmos_init); static void __exit cmos_exit(void) { -#ifdef CONFIG_PNP - if (pnp_driver_registered) - pnp_unregister_driver(&cmos_pnp_driver); -#endif if (platform_driver_registered) platform_driver_unregister(&cmos_platform_driver); } From e8d1eb65193ce93283f8f56a069eee5d548a6b70 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 23 Feb 2026 16:33:27 +0100 Subject: [PATCH 08/73] ACPI: TAD/x86: cmos_rtc: Consolidate address space handler setup On x86, as a rule the CMOS RTC address space handler is set up by the CMOS RTC ACPI scan handler attach callback, acpi_cmos_rtc_attach(), but if the ACPI namespace does not contain a CMOS RTC device object, the CMOS RTC address space handler installation is taken care of the ACPI TAD (Timer and Alarm Device) driver. This is not particularly straightforward and can be avoided by adding the ACPI TAD device ID to the CMOS RTC ACPI scan handler which will cause it to create a platform device for ACPI TAD after installing the CMOS RTC address space handler. One related detail that needs to be taken care of, though, is that the creation of an ACPI TAD platform device should not cause cmos_rtc_platform_device_present to be set, since this may cause add_rtc_cmos() to suppress the creation of a fallback CMOS RTC platform device which may not be the right thing to do (for instance, due to the fact that the ACPI TAD driver is missing an RTC class device interface). After doing the above, the CMOS RTC address space handler installation and removal can be dropped from the ACPI TAD driver (which allows it to be simplified quite a bit), acpi_remove_cmos_rtc_space_handler() can be dropped and acpi_install_cmos_rtc_space_handler() can be made static. Update the code as per the above. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/23028644.EfDdHjke4D@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 27 +++++---------------------- drivers/acpi/x86/cmos_rtc.c | 26 +++++--------------------- include/acpi/acpi_bus.h | 9 --------- 3 files changed, 10 insertions(+), 52 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 6d870d97ada6..4f5089fc023d 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -563,7 +563,6 @@ static int acpi_tad_disable_timer(struct device *dev, u32 timer_id) static void acpi_tad_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; - acpi_handle handle = ACPI_HANDLE(dev); struct acpi_tad_driver_data *dd = dev_get_drvdata(dev); device_init_wakeup(dev, false); @@ -587,7 +586,6 @@ static void acpi_tad_remove(struct platform_device *pdev) pm_runtime_suspend(dev); pm_runtime_disable(dev); - acpi_remove_cmos_rtc_space_handler(handle); } static int acpi_tad_probe(struct platform_device *pdev) @@ -599,11 +597,6 @@ static int acpi_tad_probe(struct platform_device *pdev) unsigned long long caps; int ret; - ret = acpi_install_cmos_rtc_space_handler(handle); - if (ret < 0) { - dev_info(dev, "Unable to install space handler\n"); - return -ENODEV; - } /* * Initialization failure messages are mostly about firmware issues, so * print them at the "info" level. @@ -611,27 +604,22 @@ static int acpi_tad_probe(struct platform_device *pdev) status = acpi_evaluate_integer(handle, "_GCP", NULL, &caps); if (ACPI_FAILURE(status)) { dev_info(dev, "Unable to get capabilities\n"); - ret = -ENODEV; - goto remove_handler; + return -ENODEV; } if (!(caps & ACPI_TAD_AC_WAKE)) { dev_info(dev, "Unsupported capabilities\n"); - ret = -ENODEV; - goto remove_handler; + return -ENODEV; } if (!acpi_has_method(handle, "_PRW")) { dev_info(dev, "Missing _PRW\n"); - ret = -ENODEV; - goto remove_handler; + return -ENODEV; } dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); - if (!dd) { - ret = -ENOMEM; - goto remove_handler; - } + if (!dd) + return -ENOMEM; dd->capabilities = caps; dev_set_drvdata(dev, dd); @@ -673,11 +661,6 @@ static int acpi_tad_probe(struct platform_device *pdev) fail: acpi_tad_remove(pdev); - /* Don't fallthrough because cmos rtc space handler is removed in acpi_tad_remove() */ - return ret; - -remove_handler: - acpi_remove_cmos_rtc_space_handler(handle); return ret; } diff --git a/drivers/acpi/x86/cmos_rtc.c b/drivers/acpi/x86/cmos_rtc.c index a6df5b991c96..ced334e19896 100644 --- a/drivers/acpi/x86/cmos_rtc.c +++ b/drivers/acpi/x86/cmos_rtc.c @@ -18,13 +18,12 @@ #include "../internal.h" static const struct acpi_device_id acpi_cmos_rtc_ids[] = { + { "ACPI000E", 1 }, /* ACPI Time and Alarm Device (TAD) */ ACPI_CMOS_RTC_IDS }; bool cmos_rtc_platform_device_present; -static bool cmos_rtc_space_handler_present __read_mostly; - static acpi_status acpi_cmos_rtc_space_handler(u32 function, acpi_physical_address address, u32 bits, u64 *value64, @@ -56,8 +55,9 @@ static acpi_status acpi_cmos_rtc_space_handler(u32 function, return AE_BAD_PARAMETER; } -int acpi_install_cmos_rtc_space_handler(acpi_handle handle) +static int acpi_install_cmos_rtc_space_handler(acpi_handle handle) { + static bool cmos_rtc_space_handler_present __read_mostly; acpi_status status; if (cmos_rtc_space_handler_present) @@ -76,22 +76,6 @@ int acpi_install_cmos_rtc_space_handler(acpi_handle handle) return 1; } -EXPORT_SYMBOL_GPL(acpi_install_cmos_rtc_space_handler); - -void acpi_remove_cmos_rtc_space_handler(acpi_handle handle) -{ - acpi_status status; - - if (cmos_rtc_space_handler_present) - return; - - status = acpi_remove_address_space_handler(handle, - ACPI_ADR_SPACE_CMOS, - acpi_cmos_rtc_space_handler); - if (ACPI_FAILURE(status)) - pr_err("Failed to remove CMOS-RTC address space handler\n"); -} -EXPORT_SYMBOL_GPL(acpi_remove_cmos_rtc_space_handler); static int acpi_cmos_rtc_attach(struct acpi_device *adev, const struct acpi_device_id *id) @@ -103,9 +87,9 @@ static int acpi_cmos_rtc_attach(struct acpi_device *adev, return ret; if (IS_ERR_OR_NULL(acpi_create_platform_device(adev, NULL))) { - pr_err("Failed to create CMOS-RTC platform device\n"); + pr_err("Failed to create a platform device for %s\n", (char *)id->id); return 0; - } else { + } else if (!id->driver_data) { cmos_rtc_platform_device_present = true; } return 1; diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index aad1a95e6863..be6d9032a161 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -760,8 +760,6 @@ int acpi_disable_wakeup_device_power(struct acpi_device *dev); #ifdef CONFIG_X86 bool acpi_device_override_status(struct acpi_device *adev, unsigned long long *status); bool acpi_quirk_skip_acpi_ac_and_battery(void); -int acpi_install_cmos_rtc_space_handler(acpi_handle handle); -void acpi_remove_cmos_rtc_space_handler(acpi_handle handle); int acpi_quirk_skip_serdev_enumeration(struct device *controller_parent, bool *skip); #else static inline bool acpi_device_override_status(struct acpi_device *adev, @@ -773,13 +771,6 @@ static inline bool acpi_quirk_skip_acpi_ac_and_battery(void) { return false; } -static inline int acpi_install_cmos_rtc_space_handler(acpi_handle handle) -{ - return 1; -} -static inline void acpi_remove_cmos_rtc_space_handler(acpi_handle handle) -{ -} static inline int acpi_quirk_skip_serdev_enumeration(struct device *controller_parent, bool *skip) { From 658fa7b1c47a857af484c5c5dff8d0164b7c7bfb Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Fri, 6 Feb 2026 19:56:52 +0530 Subject: [PATCH 09/73] ACPI: CPPC: Add cppc_get_perf() API to read performance controls Add cppc_get_perf() function to read values of performance control registers including desired_perf, min_perf, max_perf, energy_perf, and auto_sel. This provides a read interface to complement the existing cppc_set_perf() write interface for performance control registers. Note that auto_sel is read by cppc_get_perf() but not written by cppc_set_perf() to avoid unintended mode changes during performance updates. It can be updated with existing dedicated cppc_set_auto_sel() API. Use cppc_get_perf() in cppc_cpufreq_get_cpu_data() to initialize perf_ctrls with current hardware register values during cpufreq policy initialization. Signed-off-by: Sumit Gupta Reviewed-by: Pierre Gondois Reviewed-by: Lifeng Zheng Link: https://patch.msgid.link/20260206142658.72583-2-sumitg@nvidia.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 80 ++++++++++++++++++++++++++++++++++ drivers/cpufreq/cppc_cpufreq.c | 6 +++ include/acpi/cppc_acpi.h | 5 +++ 3 files changed, 91 insertions(+) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index f0e513e9ed5d..5122e99bddb0 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1738,6 +1738,86 @@ int cppc_set_enable(int cpu, bool enable) } EXPORT_SYMBOL_GPL(cppc_set_enable); +/** + * cppc_get_perf - Get a CPU's performance controls. + * @cpu: CPU for which to get performance controls. + * @perf_ctrls: ptr to cppc_perf_ctrls. See cppc_acpi.h + * + * 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, + *energy_perf_reg, *auto_sel_reg; + 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; + int ret = 0, regs_in_pcc = 0; + + if (!cpc_desc) { + pr_debug("No CPC descriptor for CPU:%d\n", cpu); + return -ENODEV; + } + + 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 (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) { + pr_debug("Invalid pcc_ss_id for CPU:%d\n", cpu); + return -ENODEV; + } + pcc_ss_data = pcc_data[pcc_ss_id]; + regs_in_pcc = 1; + down_write(&pcc_ss_data->pcc_lock); + /* Ring doorbell once to update PCC subspace */ + if (send_pcc_cmd(pcc_ss_id, CMD_READ) < 0) { + ret = -EIO; + goto out_err; + } + } + + /* Read optional elements if present */ + if (CPC_SUPPORTED(max_perf_reg)) + cpc_read(cpu, max_perf_reg, &max); + perf_ctrls->max_perf = max; + + if (CPC_SUPPORTED(min_perf_reg)) + cpc_read(cpu, min_perf_reg, &min); + perf_ctrls->min_perf = min; + + if (CPC_SUPPORTED(desired_perf_reg)) + cpc_read(cpu, desired_perf_reg, &desired_perf); + perf_ctrls->desired_perf = desired_perf; + + if (CPC_SUPPORTED(energy_perf_reg)) + cpc_read(cpu, energy_perf_reg, &energy_perf); + perf_ctrls->energy_perf = energy_perf; + + if (CPC_SUPPORTED(auto_sel_reg)) + cpc_read(cpu, auto_sel_reg, &auto_sel); + perf_ctrls->auto_sel = (bool)auto_sel; + +out_err: + if (regs_in_pcc) + up_write(&pcc_ss_data->pcc_lock); + return ret; +} +EXPORT_SYMBOL_GPL(cppc_get_perf); + /** * cppc_set_perf - Set a CPU's performance controls. * @cpu: CPU for which to set performance controls. diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 011f35cb47b9..a61a24e0dcae 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -594,6 +594,12 @@ static struct cppc_cpudata *cppc_cpufreq_get_cpu_data(unsigned int cpu) goto free_mask; } + ret = cppc_get_perf(cpu, &cpu_data->perf_ctrls); + if (ret) { + pr_debug("Err reading CPU%d perf ctrls: ret:%d\n", cpu, ret); + goto free_mask; + } + return cpu_data; free_mask: diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 4d644f03098e..3fc796c0d902 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -151,6 +151,7 @@ extern int cppc_get_desired_perf(int cpunum, u64 *desired_perf); extern int cppc_get_nominal_perf(int cpunum, u64 *nominal_perf); extern int cppc_get_highest_perf(int cpunum, u64 *highest_perf); extern int cppc_get_perf_ctrs(int cpu, struct cppc_perf_fb_ctrs *perf_fb_ctrs); +extern int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls); extern int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls); extern int cppc_set_enable(int cpu, bool enable); extern int cppc_get_perf_caps(int cpu, struct cppc_perf_caps *caps); @@ -193,6 +194,10 @@ static inline int cppc_get_perf_ctrs(int cpu, struct cppc_perf_fb_ctrs *perf_fb_ { return -EOPNOTSUPP; } +static inline int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) +{ + return -EOPNOTSUPP; +} static inline int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) { return -EOPNOTSUPP; From b3e45fb2db9d8a733e94b315f1272e2c4468ed4b Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Fri, 6 Feb 2026 19:56:53 +0530 Subject: [PATCH 10/73] ACPI: CPPC: Warn on missing mandatory DESIRED_PERF register Add a warning during CPPC processor probe if the Desired Performance register is not supported when it should be. As per 8.4.6.1.2.3 section of ACPI 6.6 specification, "The Desired Performance Register is optional only when OSPM indicates support for CPPC2 in the platform-wide _OSC capabilities and the Autonomous Selection Enable field is encoded as an Integer with a value of 1." In other words: - In CPPC v1, DESIRED_PERF is mandatory - In CPPC v2, it becomes optional only when AUTO_SEL_ENABLE is supported This helps detect firmware configuration issues early during boot. Link: https://lore.kernel.org/lkml/9fa21599-004a-4af8-acc2-190fd0404e35@nvidia.com/ Suggested-by: Pierre Gondois Signed-off-by: Sumit Gupta Reviewed-by: Pierre Gondois Reviewed-by: Lifeng Zheng Link: https://patch.msgid.link/20260206142658.72583-3-sumitg@nvidia.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 5122e99bddb0..3f758d2944e2 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -853,6 +853,16 @@ int acpi_cppc_processor_probe(struct acpi_processor *pr) } per_cpu(cpu_pcc_subspace_idx, pr->id) = pcc_subspace_id; + /* + * In CPPC v1, DESIRED_PERF is mandatory. In CPPC v2, it is optional + * only when AUTO_SEL_ENABLE is supported. + */ + if (!CPC_SUPPORTED(&cpc_ptr->cpc_regs[DESIRED_PERF]) && + (!osc_sb_cppc2_support_acked || + !CPC_SUPPORTED(&cpc_ptr->cpc_regs[AUTO_SEL_ENABLE]))) + pr_warn("Desired perf. register is mandatory if CPPC v2 is not supported " + "or autonomous selection is disabled\n"); + /* * Initialize the remaining cpc_regs as unsupported. * Example: In case FW exposes CPPC v2, the below loop will initialize From 38428a680026c52a1fc64212325d161974c3e4cf Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Fri, 6 Feb 2026 19:56:54 +0530 Subject: [PATCH 11/73] ACPI: CPPC: Extend cppc_set_epp_perf() for FFH/SystemMemory Extend cppc_set_epp_perf() to write both auto_sel and energy_perf registers when they are in FFH or SystemMemory address space. This keeps the behavior consistent with PCC case where both registers are already updated together, but was missing for FFH/SystemMemory. Signed-off-by: Sumit Gupta Reviewed-by: Pierre Gondois Reviewed-by: Lifeng Zheng Link: https://patch.msgid.link/20260206142658.72583-4-sumitg@nvidia.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 3f758d2944e2..94a7ffa8be3c 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1571,6 +1571,8 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable) struct cpc_register_resource *auto_sel_reg; struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); struct cppc_pcc_data *pcc_ss_data = NULL; + bool autosel_ffh_sysmem; + bool epp_ffh_sysmem; int ret; if (!cpc_desc) { @@ -1581,6 +1583,11 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable) auto_sel_reg = &cpc_desc->cpc_regs[AUTO_SEL_ENABLE]; epp_set_reg = &cpc_desc->cpc_regs[ENERGY_PERF]; + epp_ffh_sysmem = CPC_SUPPORTED(epp_set_reg) && + (CPC_IN_FFH(epp_set_reg) || CPC_IN_SYSTEM_MEMORY(epp_set_reg)); + autosel_ffh_sysmem = CPC_SUPPORTED(auto_sel_reg) && + (CPC_IN_FFH(auto_sel_reg) || CPC_IN_SYSTEM_MEMORY(auto_sel_reg)); + if (CPC_IN_PCC(epp_set_reg) || CPC_IN_PCC(auto_sel_reg)) { if (pcc_ss_id < 0) { pr_debug("Invalid pcc_ss_id for CPU:%d\n", cpu); @@ -1606,11 +1613,22 @@ int cppc_set_epp_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls, bool enable) ret = send_pcc_cmd(pcc_ss_id, CMD_WRITE); up_write(&pcc_ss_data->pcc_lock); } else if (osc_cpc_flexible_adr_space_confirmed && - CPC_SUPPORTED(epp_set_reg) && CPC_IN_FFH(epp_set_reg)) { - ret = cpc_write(cpu, epp_set_reg, perf_ctrls->energy_perf); + (epp_ffh_sysmem || autosel_ffh_sysmem)) { + if (autosel_ffh_sysmem) { + ret = cpc_write(cpu, auto_sel_reg, enable); + if (ret) + return ret; + } + + if (epp_ffh_sysmem) { + ret = cpc_write(cpu, epp_set_reg, + perf_ctrls->energy_perf); + if (ret) + return ret; + } } else { ret = -ENOTSUPP; - pr_debug("_CPC in PCC and _CPC in FFH are not supported\n"); + pr_debug("_CPC in PCC/FFH/SystemMemory are not supported\n"); } return ret; From 24ad4c6c136bdaa4c92c5c5948856752ce3e9f76 Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Fri, 6 Feb 2026 19:56:55 +0530 Subject: [PATCH 12/73] cpufreq: CPPC: Update cached perf_ctrls on sysfs write Update the cached perf_ctrls values when writing via sysfs to keep them in sync with hardware registers: - store_auto_select(): update perf_ctrls.auto_sel - store_energy_performance_preference_val(): update perf_ctrls.energy_perf This ensures consistent cached values after sysfs writes, which complements the cppc_get_perf() initialization during policy setup. Signed-off-by: Sumit Gupta Reviewed-by: Pierre Gondois Reviewed-by: Lifeng Zheng Link: https://patch.msgid.link/20260206142658.72583-5-sumitg@nvidia.com Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/cppc_cpufreq.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index a61a24e0dcae..ebb5746df220 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -855,6 +855,7 @@ static ssize_t show_auto_select(struct cpufreq_policy *policy, char *buf) static ssize_t store_auto_select(struct cpufreq_policy *policy, const char *buf, size_t count) { + struct cppc_cpudata *cpu_data = policy->driver_data; bool val; int ret; @@ -866,6 +867,8 @@ static ssize_t store_auto_select(struct cpufreq_policy *policy, if (ret) return ret; + cpu_data->perf_ctrls.auto_sel = val; + return count; } @@ -916,8 +919,32 @@ static ssize_t store_##_name(struct cpufreq_policy *policy, \ CPPC_CPUFREQ_ATTR_RW_U64(auto_act_window, cppc_get_auto_act_window, cppc_set_auto_act_window) -CPPC_CPUFREQ_ATTR_RW_U64(energy_performance_preference_val, - cppc_get_epp_perf, cppc_set_epp) +static ssize_t +show_energy_performance_preference_val(struct cpufreq_policy *policy, char *buf) +{ + return cppc_cpufreq_sysfs_show_u64(policy->cpu, cppc_get_epp_perf, buf); +} + +static ssize_t +store_energy_performance_preference_val(struct cpufreq_policy *policy, + const char *buf, size_t count) +{ + struct cppc_cpudata *cpu_data = policy->driver_data; + u64 val; + int ret; + + ret = kstrtou64(buf, 0, &val); + if (ret) + return ret; + + ret = cppc_set_epp(policy->cpu, val); + if (ret) + return ret; + + cpu_data->perf_ctrls.energy_perf = val; + + return count; +} cpufreq_freq_attr_ro(freqdomain_cpus); cpufreq_freq_attr_rw(auto_select); From ea3db45ae476889a1ba0ab3617e6afdeeefbda3d Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Fri, 6 Feb 2026 19:56:56 +0530 Subject: [PATCH 13/73] cpufreq: cppc: Update MIN_PERF/MAX_PERF in target callbacks Update MIN_PERF and MAX_PERF registers from policy->min and policy->max in the .target() and .fast_switch() callbacks. This allows controlling performance bounds via standard scaling_min_freq and scaling_max_freq sysfs interfaces. Similar to intel_cpufreq which updates HWP min/max limits in .target(), cppc_cpufreq now programs MIN_PERF/MAX_PERF along with DESIRED_PERF. Since MIN_PERF/MAX_PERF can be updated even when auto_sel is disabled, they are updated unconditionally. Also program MIN_PERF/MAX_PERF in store_auto_select() when enabling autonomous selection so the platform uses correct bounds immediately. Suggested-by: Rafael J. Wysocki Signed-off-by: Sumit Gupta Link: https://patch.msgid.link/20260206142658.72583-6-sumitg@nvidia.com Signed-off-by: Rafael J. Wysocki --- drivers/cpufreq/cppc_cpufreq.c | 41 +++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index ebb5746df220..8a8cf76828ee 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -287,6 +287,21 @@ static inline void cppc_freq_invariance_exit(void) } #endif /* CONFIG_ACPI_CPPC_CPUFREQ_FIE */ +static void cppc_cpufreq_update_perf_limits(struct cppc_cpudata *cpu_data, + struct cpufreq_policy *policy) +{ + struct cppc_perf_caps *caps = &cpu_data->perf_caps; + u32 min_perf, max_perf; + + min_perf = cppc_khz_to_perf(caps, policy->min); + max_perf = cppc_khz_to_perf(caps, policy->max); + + cpu_data->perf_ctrls.min_perf = + clamp_t(u32, min_perf, caps->lowest_perf, caps->highest_perf); + cpu_data->perf_ctrls.max_perf = + clamp_t(u32, max_perf, caps->lowest_perf, caps->highest_perf); +} + static int cppc_cpufreq_set_target(struct cpufreq_policy *policy, unsigned int target_freq, unsigned int relation) @@ -298,6 +313,8 @@ static int cppc_cpufreq_set_target(struct cpufreq_policy *policy, cpu_data->perf_ctrls.desired_perf = cppc_khz_to_perf(&cpu_data->perf_caps, target_freq); + cppc_cpufreq_update_perf_limits(cpu_data, policy); + freqs.old = policy->cur; freqs.new = target_freq; @@ -322,8 +339,9 @@ static unsigned int cppc_cpufreq_fast_switch(struct cpufreq_policy *policy, desired_perf = cppc_khz_to_perf(&cpu_data->perf_caps, target_freq); cpu_data->perf_ctrls.desired_perf = desired_perf; - ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls); + cppc_cpufreq_update_perf_limits(cpu_data, policy); + ret = cppc_set_perf(cpu, &cpu_data->perf_ctrls); if (ret) { pr_debug("Failed to set target on CPU:%d. ret:%d\n", cpu, ret); @@ -869,6 +887,27 @@ static ssize_t store_auto_select(struct cpufreq_policy *policy, cpu_data->perf_ctrls.auto_sel = val; + if (val) { + u32 old_min_perf = cpu_data->perf_ctrls.min_perf; + u32 old_max_perf = cpu_data->perf_ctrls.max_perf; + + /* + * When enabling autonomous selection, program MIN_PERF and + * MAX_PERF from current policy limits so that the platform + * uses the correct performance bounds immediately. + */ + cppc_cpufreq_update_perf_limits(cpu_data, policy); + + ret = cppc_set_perf(policy->cpu, &cpu_data->perf_ctrls); + if (ret) { + cpu_data->perf_ctrls.min_perf = old_min_perf; + cpu_data->perf_ctrls.max_perf = old_max_perf; + cppc_set_auto_sel(policy->cpu, false); + cpu_data->perf_ctrls.auto_sel = false; + return ret; + } + } + return count; } From 13c45a26635fa51a68911aa57e6778bdad18b103 Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Fri, 6 Feb 2026 19:56:57 +0530 Subject: [PATCH 14/73] ACPI: CPPC: add APIs and sysfs interface for perf_limited Add sysfs interface to read/write the Performance Limited register. The Performance Limited register indicates to the OS that an unpredictable event (like thermal throttling) has limited processor performance. It contains two sticky bits set by the platform: - Bit 0 (Desired_Excursion): Set when delivered performance is constrained below desired performance. Not used when Autonomous Selection is enabled. - Bit 1 (Minimum_Excursion): Set when delivered performance is constrained below minimum performance. These bits remain set until OSPM explicitly clears them. The write operation accepts a bitmask of bits to clear: - Write 0x1 to clear bit 0 - Write 0x2 to clear bit 1 - Write 0x3 to clear both bits This enables users to detect if platform throttling impacted a workload. Users clear the register before execution, run the workload, then check afterward - if set, hardware throttling occurred during that time window. The interface is exposed as: /sys/devices/system/cpu/cpuX/cpufreq/perf_limited Signed-off-by: Sumit Gupta Reviewed-by: Pierre Gondois Reviewed-by: Lifeng Zheng Link: https://patch.msgid.link/20260206142658.72583-7-sumitg@nvidia.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 56 ++++++++++++++++++++++++++++++++++ drivers/cpufreq/cppc_cpufreq.c | 5 +++ include/acpi/cppc_acpi.h | 15 +++++++++ 3 files changed, 76 insertions(+) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 94a7ffa8be3c..53a6ffd995a1 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1978,6 +1978,62 @@ int cppc_set_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) } EXPORT_SYMBOL_GPL(cppc_set_perf); +/** + * cppc_get_perf_limited - Get the Performance Limited register value. + * @cpu: CPU from which to get Performance Limited register. + * @perf_limited: Pointer to store the Performance Limited value. + * + * The returned value contains sticky status bits indicating platform-imposed + * performance limitations. + * + * Return: 0 for success, -EIO on failure, -EOPNOTSUPP if not supported. + */ +int cppc_get_perf_limited(int cpu, u64 *perf_limited) +{ + return cppc_get_reg_val(cpu, PERF_LIMITED, perf_limited); +} +EXPORT_SYMBOL_GPL(cppc_get_perf_limited); + +/** + * cppc_set_perf_limited() - Clear bits in the Performance Limited register. + * @cpu: CPU on which to write register. + * @bits_to_clear: Bitmask of bits to clear in the perf_limited register. + * + * The Performance Limited register contains two sticky bits set by platform: + * - Bit 0 (Desired_Excursion): Set when delivered performance is constrained + * below desired performance. Not used when Autonomous Selection is enabled. + * - Bit 1 (Minimum_Excursion): Set when delivered performance is constrained + * below minimum performance. + * + * These bits are sticky and remain set until OSPM explicitly clears them. + * This function only allows clearing bits (the platform sets them). + * + * Return: 0 for success, -EINVAL for invalid bits, -EIO on register + * access failure, -EOPNOTSUPP if not supported. + */ +int cppc_set_perf_limited(int cpu, u64 bits_to_clear) +{ + u64 current_val, new_val; + int ret; + + /* Only bits 0 and 1 are valid */ + if (bits_to_clear & ~CPPC_PERF_LIMITED_MASK) + return -EINVAL; + + if (!bits_to_clear) + return 0; + + ret = cppc_get_perf_limited(cpu, ¤t_val); + if (ret) + return ret; + + /* Clear the specified bits */ + new_val = current_val & ~bits_to_clear; + + return cppc_set_reg_val(cpu, PERF_LIMITED, new_val); +} +EXPORT_SYMBOL_GPL(cppc_set_perf_limited); + /** * cppc_get_transition_latency - returns frequency transition latency in ns * @cpu_num: CPU number for per_cpu(). diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 8a8cf76828ee..94d489a4c90d 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -985,16 +985,21 @@ store_energy_performance_preference_val(struct cpufreq_policy *policy, return count; } +CPPC_CPUFREQ_ATTR_RW_U64(perf_limited, cppc_get_perf_limited, + cppc_set_perf_limited) + cpufreq_freq_attr_ro(freqdomain_cpus); cpufreq_freq_attr_rw(auto_select); cpufreq_freq_attr_rw(auto_act_window); cpufreq_freq_attr_rw(energy_performance_preference_val); +cpufreq_freq_attr_rw(perf_limited); static struct freq_attr *cppc_cpufreq_attr[] = { &freqdomain_cpus, &auto_select, &auto_act_window, &energy_performance_preference_val, + &perf_limited, NULL, }; diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index 3fc796c0d902..f7afa20b8ad9 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -42,6 +42,11 @@ #define CPPC_EPP_PERFORMANCE_PREF 0x00 #define CPPC_EPP_ENERGY_EFFICIENCY_PREF 0xFF +#define CPPC_PERF_LIMITED_DESIRED_EXCURSION BIT(0) +#define CPPC_PERF_LIMITED_MINIMUM_EXCURSION BIT(1) +#define CPPC_PERF_LIMITED_MASK (CPPC_PERF_LIMITED_DESIRED_EXCURSION | \ + CPPC_PERF_LIMITED_MINIMUM_EXCURSION) + /* Each register has the folowing format. */ struct cpc_reg { u8 descriptor; @@ -174,6 +179,8 @@ extern int cppc_get_auto_act_window(int cpu, u64 *auto_act_window); extern int cppc_set_auto_act_window(int cpu, u64 auto_act_window); extern int cppc_get_auto_sel(int cpu, bool *enable); extern int cppc_set_auto_sel(int cpu, bool enable); +extern int cppc_get_perf_limited(int cpu, u64 *perf_limited); +extern int cppc_set_perf_limited(int cpu, u64 bits_to_clear); extern int amd_get_highest_perf(unsigned int cpu, u32 *highest_perf); extern int amd_get_boost_ratio_numerator(unsigned int cpu, u64 *numerator); extern int amd_detect_prefcore(bool *detected); @@ -270,6 +277,14 @@ static inline int cppc_set_auto_sel(int cpu, bool enable) { return -EOPNOTSUPP; } +static inline int cppc_get_perf_limited(int cpu, u64 *perf_limited) +{ + return -EOPNOTSUPP; +} +static inline int cppc_set_perf_limited(int cpu, u64 bits_to_clear) +{ + return -EOPNOTSUPP; +} static inline int amd_get_highest_perf(unsigned int cpu, u32 *highest_perf) { return -ENODEV; From 856250ba2e810e772dc95b3234ebf0d6393a51d9 Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Fri, 6 Feb 2026 19:56:58 +0530 Subject: [PATCH 15/73] cpufreq: CPPC: Add sysfs documentation for perf_limited Add ABI documentation for the Performance Limited Register sysfs interface in the cppc_cpufreq driver. Signed-off-by: Sumit Gupta Reviewed-by: Randy Dunlap Reviewed-by: Pierre Gondois Reviewed-by: Lifeng Zheng Link: https://patch.msgid.link/20260206142658.72583-8-sumitg@nvidia.com Signed-off-by: Rafael J. Wysocki --- .../ABI/testing/sysfs-devices-system-cpu | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-devices-system-cpu b/Documentation/ABI/testing/sysfs-devices-system-cpu index 3a05604c21bf..82d10d556cc8 100644 --- a/Documentation/ABI/testing/sysfs-devices-system-cpu +++ b/Documentation/ABI/testing/sysfs-devices-system-cpu @@ -327,6 +327,24 @@ Description: Energy performance preference This file is only present if the cppc-cpufreq driver is in use. +What: /sys/devices/system/cpu/cpuX/cpufreq/perf_limited +Date: February 2026 +Contact: linux-pm@vger.kernel.org +Description: Performance Limited + + Read to check if platform throttling (thermal/power/current + limits) caused delivered performance to fall below the + requested level. A non-zero value indicates throttling occurred. + + Write the bitmask of bits to clear: + + - 0x1 = clear bit 0 (desired performance excursion) + - 0x2 = clear bit 1 (minimum performance excursion) + - 0x3 = clear both bits + + The platform sets these bits; OSPM can only clear them. + + This file is only present if the cppc-cpufreq driver is in use. What: /sys/devices/system/cpu/cpu*/cache/index3/cache_disable_{0,1} Date: August 2008 From 591230c6f268306a673112fc3c3b74ab06fa9ee3 Mon Sep 17 00:00:00 2001 From: Armin Wolf Date: Wed, 4 Feb 2026 22:29:31 +0100 Subject: [PATCH 16/73] ACPI: OSL: Poweroff when encountering a fatal ACPI error The ACPI spec states that the operating system should respond to a fatal ACPI error by "performing a controlled OS shutdown in a timely fashion". Comply with the ACPI specification by powering off the system when ACPICA signals a fatal ACPI error. Users can still disable this behavior by using the acpi.poweroff_on_fatal kernel option to work around firmware bugs. Link: https://uefi.org/specs/ACPI/6.6/19_ASL_Reference.html#fatal-fatal-error-check Signed-off-by: Armin Wolf [ rjw: Dropped the new Kconfig option, adjusted header file inclusions ] Link: https://patch.msgid.link/20260204212931.3860-1-W_Armin@gmx.de Signed-off-by: Rafael J. Wysocki --- .../admin-guide/kernel-parameters.txt | 8 ++++++++ drivers/acpi/osl.c | 19 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt index cb850e5290c2..4b723526a5a9 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -189,6 +189,14 @@ Kernel parameters unusable. The "log_buf_len" parameter may be useful if you need to capture more output. + acpi.poweroff_on_fatal= [ACPI] + {0 | 1} + Causes the system to poweroff when the ACPI bytecode signals + a fatal error. The default value of this setting is 1. + Overriding this value should only be done for diagnosing + ACPI firmware problems, as the system might behave erratically + after having encountered a fatal ACPI error. + acpi_enforce_resources= [ACPI] { strict | lax | no } Check for resource conflicts between native drivers diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 5b777316b9ac..f63e38bbf129 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -13,6 +13,8 @@ #include #include +#include +#include #include #include #include @@ -70,6 +72,10 @@ static bool acpi_os_initialized; unsigned int acpi_sci_irq = INVALID_ACPI_IRQ; bool acpi_permanent_mmap = false; +static bool poweroff_on_fatal = true; +module_param(poweroff_on_fatal, bool, 0); +MODULE_PARM_DESC(poweroff_on_fatal, "Poweroff when encountering a fatal ACPI error"); + /* * This list of permanent mappings is for memory that may be accessed from * interrupt context, where we can't do the ioremap(). @@ -1381,9 +1387,20 @@ acpi_status acpi_os_notify_command_complete(void) acpi_status acpi_os_signal(u32 function, void *info) { + struct acpi_signal_fatal_info *fatal_info; + switch (function) { case ACPI_SIGNAL_FATAL: - pr_err("Fatal opcode executed\n"); + fatal_info = info; + pr_emerg("Fatal error while evaluating ACPI control method\n"); + pr_emerg("Type 0x%X Code 0x%X Argument 0x%X\n", + fatal_info->type, fatal_info->code, fatal_info->argument); + + if (poweroff_on_fatal) + orderly_poweroff(true); + else + add_taint(TAINT_FIRMWARE_WORKAROUND, LOCKDEP_STILL_OK); + break; case ACPI_SIGNAL_BREAKPOINT: /* From 8505bfb4e4eca28ef1b20d3369435ec2d6a125c6 Mon Sep 17 00:00:00 2001 From: Pengjie Zhang Date: Fri, 13 Feb 2026 18:09:35 +0800 Subject: [PATCH 17/73] ACPI: CPPC: Move reference performance to capabilities Currently, the `Reference Performance` register is read every time the CPU frequency is sampled in `cppc_get_perf_ctrs()`. This function is on the hot path of the cppc_cpufreq driver. Reference Performance indicates the performance level that corresponds to the Reference Counter incrementing and is not expected to change dynamically during runtime (unlike the Delivered and Reference counters). Reading this register in the hot path incurs unnecessary overhead, particularly on platforms where CPC registers are located in the PCC (Platform Communication Channel) subspace. This patch moves `reference_perf` from the dynamic feedback counters structure (`cppc_perf_fb_ctrs`) to the static capabilities structure (`cppc_perf_caps`). Signed-off-by: Pengjie Zhang [ rjw: Changelog adjustment ] Link: https://patch.msgid.link/20260213100935.19111-1-zhangpengjie2@huawei.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 55 +++++++++++++++------------------- drivers/cpufreq/cppc_cpufreq.c | 21 +++++++------ include/acpi/cppc_acpi.h | 2 +- 3 files changed, 37 insertions(+), 41 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 53a6ffd995a1..07bbf5b366a4 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -177,12 +177,12 @@ __ATTR(_name, 0444, show_##_name, NULL) show_cppc_data(cppc_get_perf_caps, cppc_perf_caps, highest_perf); show_cppc_data(cppc_get_perf_caps, cppc_perf_caps, lowest_perf); show_cppc_data(cppc_get_perf_caps, cppc_perf_caps, nominal_perf); +show_cppc_data(cppc_get_perf_caps, cppc_perf_caps, reference_perf); show_cppc_data(cppc_get_perf_caps, cppc_perf_caps, lowest_nonlinear_perf); show_cppc_data(cppc_get_perf_caps, cppc_perf_caps, guaranteed_perf); show_cppc_data(cppc_get_perf_caps, cppc_perf_caps, lowest_freq); show_cppc_data(cppc_get_perf_caps, cppc_perf_caps, nominal_freq); -show_cppc_data(cppc_get_perf_ctrs, cppc_perf_fb_ctrs, reference_perf); show_cppc_data(cppc_get_perf_ctrs, cppc_perf_fb_ctrs, wraparound_time); /* Check for valid access_width, otherwise, fallback to using bit_width */ @@ -1352,9 +1352,10 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpunum); struct cpc_register_resource *highest_reg, *lowest_reg, - *lowest_non_linear_reg, *nominal_reg, *guaranteed_reg, - *low_freq_reg = NULL, *nom_freq_reg = NULL; - u64 high, low, guaranteed, nom, min_nonlinear, low_f = 0, nom_f = 0; + *lowest_non_linear_reg, *nominal_reg, *reference_reg, + *guaranteed_reg, *low_freq_reg = NULL, *nom_freq_reg = NULL; + u64 high, low, guaranteed, nom, ref, min_nonlinear, + low_f = 0, nom_f = 0; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpunum); struct cppc_pcc_data *pcc_ss_data = NULL; int ret = 0, regs_in_pcc = 0; @@ -1368,6 +1369,7 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) lowest_reg = &cpc_desc->cpc_regs[LOWEST_PERF]; lowest_non_linear_reg = &cpc_desc->cpc_regs[LOW_NON_LINEAR_PERF]; nominal_reg = &cpc_desc->cpc_regs[NOMINAL_PERF]; + reference_reg = &cpc_desc->cpc_regs[REFERENCE_PERF]; low_freq_reg = &cpc_desc->cpc_regs[LOWEST_FREQ]; nom_freq_reg = &cpc_desc->cpc_regs[NOMINAL_FREQ]; guaranteed_reg = &cpc_desc->cpc_regs[GUARANTEED_PERF]; @@ -1375,6 +1377,7 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) /* Are any of the regs PCC ?*/ if (CPC_IN_PCC(highest_reg) || CPC_IN_PCC(lowest_reg) || CPC_IN_PCC(lowest_non_linear_reg) || CPC_IN_PCC(nominal_reg) || + (CPC_SUPPORTED(reference_reg) && CPC_IN_PCC(reference_reg)) || CPC_IN_PCC(low_freq_reg) || CPC_IN_PCC(nom_freq_reg) || CPC_IN_PCC(guaranteed_reg)) { if (pcc_ss_id < 0) { @@ -1400,6 +1403,17 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) cpc_read(cpunum, nominal_reg, &nom); perf_caps->nominal_perf = nom; + /* + * If reference perf register is not supported then we should + * use the nominal perf value + */ + if (CPC_SUPPORTED(reference_reg)) { + cpc_read(cpunum, reference_reg, &ref); + perf_caps->reference_perf = ref; + } else { + perf_caps->reference_perf = nom; + } + if (guaranteed_reg->type != ACPI_TYPE_BUFFER || IS_NULL_REG(&guaranteed_reg->cpc_entry.reg)) { perf_caps->guaranteed_perf = 0; @@ -1411,7 +1425,7 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) cpc_read(cpunum, lowest_non_linear_reg, &min_nonlinear); perf_caps->lowest_nonlinear_perf = min_nonlinear; - if (!high || !low || !nom || !min_nonlinear) + if (!high || !low || !nom || !ref || !min_nonlinear) ret = -EFAULT; /* Read optional lowest and nominal frequencies if present */ @@ -1441,20 +1455,10 @@ EXPORT_SYMBOL_GPL(cppc_get_perf_caps); bool cppc_perf_ctrs_in_pcc_cpu(unsigned int cpu) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpu); - struct cpc_register_resource *ref_perf_reg; - - /* - * If reference perf register is not supported then we should use the - * nominal perf value - */ - ref_perf_reg = &cpc_desc->cpc_regs[REFERENCE_PERF]; - if (!CPC_SUPPORTED(ref_perf_reg)) - ref_perf_reg = &cpc_desc->cpc_regs[NOMINAL_PERF]; return CPC_IN_PCC(&cpc_desc->cpc_regs[DELIVERED_CTR]) || CPC_IN_PCC(&cpc_desc->cpc_regs[REFERENCE_CTR]) || - CPC_IN_PCC(&cpc_desc->cpc_regs[CTR_WRAP_TIME]) || - CPC_IN_PCC(ref_perf_reg); + CPC_IN_PCC(&cpc_desc->cpc_regs[CTR_WRAP_TIME]); } EXPORT_SYMBOL_GPL(cppc_perf_ctrs_in_pcc_cpu); @@ -1491,10 +1495,10 @@ int cppc_get_perf_ctrs(int cpunum, struct cppc_perf_fb_ctrs *perf_fb_ctrs) { struct cpc_desc *cpc_desc = per_cpu(cpc_desc_ptr, cpunum); struct cpc_register_resource *delivered_reg, *reference_reg, - *ref_perf_reg, *ctr_wrap_reg; + *ctr_wrap_reg; int pcc_ss_id = per_cpu(cpu_pcc_subspace_idx, cpunum); struct cppc_pcc_data *pcc_ss_data = NULL; - u64 delivered, reference, ref_perf, ctr_wrap_time; + u64 delivered, reference, ctr_wrap_time; int ret = 0, regs_in_pcc = 0; if (!cpc_desc) { @@ -1504,19 +1508,11 @@ int cppc_get_perf_ctrs(int cpunum, struct cppc_perf_fb_ctrs *perf_fb_ctrs) delivered_reg = &cpc_desc->cpc_regs[DELIVERED_CTR]; reference_reg = &cpc_desc->cpc_regs[REFERENCE_CTR]; - ref_perf_reg = &cpc_desc->cpc_regs[REFERENCE_PERF]; ctr_wrap_reg = &cpc_desc->cpc_regs[CTR_WRAP_TIME]; - /* - * If reference perf register is not supported then we should - * use the nominal perf value - */ - if (!CPC_SUPPORTED(ref_perf_reg)) - ref_perf_reg = &cpc_desc->cpc_regs[NOMINAL_PERF]; - /* Are any of the regs PCC ?*/ if (CPC_IN_PCC(delivered_reg) || CPC_IN_PCC(reference_reg) || - CPC_IN_PCC(ctr_wrap_reg) || CPC_IN_PCC(ref_perf_reg)) { + CPC_IN_PCC(ctr_wrap_reg)) { if (pcc_ss_id < 0) { pr_debug("Invalid pcc_ss_id\n"); return -ENODEV; @@ -1533,8 +1529,6 @@ int cppc_get_perf_ctrs(int cpunum, struct cppc_perf_fb_ctrs *perf_fb_ctrs) cpc_read(cpunum, delivered_reg, &delivered); cpc_read(cpunum, reference_reg, &reference); - cpc_read(cpunum, ref_perf_reg, &ref_perf); - /* * Per spec, if ctr_wrap_time optional register is unsupported, then the * performance counters are assumed to never wrap during the lifetime of @@ -1544,14 +1538,13 @@ int cppc_get_perf_ctrs(int cpunum, struct cppc_perf_fb_ctrs *perf_fb_ctrs) if (CPC_SUPPORTED(ctr_wrap_reg)) cpc_read(cpunum, ctr_wrap_reg, &ctr_wrap_time); - if (!delivered || !reference || !ref_perf) { + if (!delivered || !reference) { ret = -EFAULT; goto out_err; } perf_fb_ctrs->delivered = delivered; perf_fb_ctrs->reference = reference; - perf_fb_ctrs->reference_perf = ref_perf; perf_fb_ctrs->wraparound_time = ctr_wrap_time; out_err: if (regs_in_pcc) diff --git a/drivers/cpufreq/cppc_cpufreq.c b/drivers/cpufreq/cppc_cpufreq.c index 94d489a4c90d..5dfb109cf1f4 100644 --- a/drivers/cpufreq/cppc_cpufreq.c +++ b/drivers/cpufreq/cppc_cpufreq.c @@ -50,7 +50,8 @@ struct cppc_freq_invariance { static DEFINE_PER_CPU(struct cppc_freq_invariance, cppc_freq_inv); static struct kthread_worker *kworker_fie; -static int cppc_perf_from_fbctrs(struct cppc_perf_fb_ctrs *fb_ctrs_t0, +static int cppc_perf_from_fbctrs(u64 reference_perf, + struct cppc_perf_fb_ctrs *fb_ctrs_t0, struct cppc_perf_fb_ctrs *fb_ctrs_t1); /** @@ -70,7 +71,7 @@ static void __cppc_scale_freq_tick(struct cppc_freq_invariance *cppc_fi) struct cppc_perf_fb_ctrs fb_ctrs = {0}; struct cppc_cpudata *cpu_data; unsigned long local_freq_scale; - u64 perf; + u64 perf, ref_perf; cpu_data = cppc_fi->cpu_data; @@ -79,7 +80,9 @@ static void __cppc_scale_freq_tick(struct cppc_freq_invariance *cppc_fi) return; } - perf = cppc_perf_from_fbctrs(&cppc_fi->prev_perf_fb_ctrs, &fb_ctrs); + ref_perf = cpu_data->perf_caps.reference_perf; + perf = cppc_perf_from_fbctrs(ref_perf, + &cppc_fi->prev_perf_fb_ctrs, &fb_ctrs); if (!perf) return; @@ -747,13 +750,11 @@ static inline u64 get_delta(u64 t1, u64 t0) return (u32)t1 - (u32)t0; } -static int cppc_perf_from_fbctrs(struct cppc_perf_fb_ctrs *fb_ctrs_t0, +static int cppc_perf_from_fbctrs(u64 reference_perf, + struct cppc_perf_fb_ctrs *fb_ctrs_t0, struct cppc_perf_fb_ctrs *fb_ctrs_t1) { u64 delta_reference, delta_delivered; - u64 reference_perf; - - reference_perf = fb_ctrs_t0->reference_perf; delta_reference = get_delta(fb_ctrs_t1->reference, fb_ctrs_t0->reference); @@ -790,7 +791,7 @@ static unsigned int cppc_cpufreq_get_rate(unsigned int cpu) struct cpufreq_policy *policy __free(put_cpufreq_policy) = cpufreq_cpu_get(cpu); struct cppc_perf_fb_ctrs fb_ctrs_t0 = {0}, fb_ctrs_t1 = {0}; struct cppc_cpudata *cpu_data; - u64 delivered_perf; + u64 delivered_perf, reference_perf; int ret; if (!policy) @@ -807,7 +808,9 @@ static unsigned int cppc_cpufreq_get_rate(unsigned int cpu) return 0; } - delivered_perf = cppc_perf_from_fbctrs(&fb_ctrs_t0, &fb_ctrs_t1); + reference_perf = cpu_data->perf_caps.reference_perf; + delivered_perf = cppc_perf_from_fbctrs(reference_perf, + &fb_ctrs_t0, &fb_ctrs_t1); if (!delivered_perf) goto out_invalid_counters; diff --git a/include/acpi/cppc_acpi.h b/include/acpi/cppc_acpi.h index f7afa20b8ad9..d8e405becdc3 100644 --- a/include/acpi/cppc_acpi.h +++ b/include/acpi/cppc_acpi.h @@ -121,6 +121,7 @@ struct cppc_perf_caps { u32 guaranteed_perf; u32 highest_perf; u32 nominal_perf; + u32 reference_perf; u32 lowest_perf; u32 lowest_nonlinear_perf; u32 lowest_freq; @@ -138,7 +139,6 @@ struct cppc_perf_ctrls { struct cppc_perf_fb_ctrs { u64 reference; u64 delivered; - u64 reference_perf; u64 wraparound_time; }; From 638a95168fd53a911201681cd5e55c7965b20733 Mon Sep 17 00:00:00 2001 From: Jingkai Tan Date: Thu, 5 Mar 2026 21:38:31 +0000 Subject: [PATCH 18/73] ACPI: processor: idle: Add missing bounds check in flatten_lpi_states() The inner loop in flatten_lpi_states() that combines composite LPI states can increment flat_state_cnt multiple times within the loop. The condition that guards this (checks bounds against ACPI_PROCESSOR _MAX_POWER) occurs at the top of the outer loop. flat_state_cnt might exceed ACPI_PROCESSOR_MAX_POWER if it is incremented multiple times within the inner loop between outer loop iterations. Add a bounds check after the increment inside the inner loop so that it breaks out when flat_state_cnt reaches ACPI_PROCESSOR_MAX_POWER. The existing check in the outer loop will then handle the warning. Signed-off-by: Jingkai Tan Reviewed-by: Sudeep Holla Link: https://patch.msgid.link/20260305213831.53985-1-contact@jingk.ai Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_idle.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index f6c72e3a2be1..d4753420ae0b 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1068,6 +1068,8 @@ static unsigned int flatten_lpi_states(struct acpi_processor *pr, stash_composite_state(curr_level, flpi); flat_state_cnt++; flpi++; + if (flat_state_cnt >= ACPI_PROCESSOR_MAX_POWER) + break; } } } From 6a0c7d388b6a19899ab0db60325699980706fb11 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 5 Mar 2026 21:10:38 +0100 Subject: [PATCH 19/73] ACPI: AC: Get rid of unnecessary declarations Drop unnecessary forward declarations of 4 functions and move the SIMPLE_DEV_PM_OPS() definition after the definition of the resume callback function. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3757869.R56niFO833@rafael.j.wysocki --- drivers/acpi/ac.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/drivers/acpi/ac.c b/drivers/acpi/ac.c index c5d77c3cb4bc..e0560a2c71a0 100644 --- a/drivers/acpi/ac.c +++ b/drivers/acpi/ac.c @@ -33,22 +33,12 @@ MODULE_AUTHOR("Paul Diefenbaugh"); MODULE_DESCRIPTION("ACPI AC Adapter Driver"); MODULE_LICENSE("GPL"); -static int acpi_ac_probe(struct platform_device *pdev); -static void acpi_ac_remove(struct platform_device *pdev); - -static void acpi_ac_notify(acpi_handle handle, u32 event, void *data); - static const struct acpi_device_id ac_device_ids[] = { {"ACPI0003", 0}, {"", 0}, }; MODULE_DEVICE_TABLE(acpi, ac_device_ids); -#ifdef CONFIG_PM_SLEEP -static int acpi_ac_resume(struct device *dev); -#endif -static SIMPLE_DEV_PM_OPS(acpi_ac_pm, NULL, acpi_ac_resume); - static int ac_sleep_before_get_state_ms; static int ac_only; @@ -272,10 +262,10 @@ static int acpi_ac_resume(struct device *dev) return 0; } -#else -#define acpi_ac_resume NULL #endif +static SIMPLE_DEV_PM_OPS(acpi_ac_pm, NULL, acpi_ac_resume); + static void acpi_ac_remove(struct platform_device *pdev) { struct acpi_ac *ac = platform_get_drvdata(pdev); From 6e5cbf46c27ae46f13c7ac830e618c6e0b1ed07c Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 5 Mar 2026 21:11:22 +0100 Subject: [PATCH 20/73] ACPI: PAD: Rearrange notify handler installation and removal Use acpi_dev_install_notify_handler() and acpi_dev_remove_notify_handler() for installing and removing the ACPI notify handler, respectively, which allows acpi_pad_notify() and acpi_pad_remove() to be simplified quite a bit. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2011993.taCxCBeP46@rafael.j.wysocki --- drivers/acpi/acpi_pad.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/drivers/acpi/acpi_pad.c b/drivers/acpi/acpi_pad.c index c9a0bcaba2e4..407a0d68525c 100644 --- a/drivers/acpi/acpi_pad.c +++ b/drivers/acpi/acpi_pad.c @@ -407,8 +407,7 @@ static void acpi_pad_handle_notify(acpi_handle handle) mutex_unlock(&isolated_cpus_lock); } -static void acpi_pad_notify(acpi_handle handle, u32 event, - void *data) +static void acpi_pad_notify(acpi_handle handle, u32 event, void *data) { struct acpi_device *adev = data; @@ -427,30 +426,22 @@ static void acpi_pad_notify(acpi_handle handle, u32 event, static int acpi_pad_probe(struct platform_device *pdev) { struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); - acpi_status status; strscpy(acpi_device_name(adev), ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME); strscpy(acpi_device_class(adev), ACPI_PROCESSOR_AGGREGATOR_CLASS); - status = acpi_install_notify_handler(adev->handle, - ACPI_DEVICE_NOTIFY, acpi_pad_notify, adev); - - if (ACPI_FAILURE(status)) - return -ENODEV; - - return 0; + return acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, + acpi_pad_notify, adev); } static void acpi_pad_remove(struct platform_device *pdev) { - struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); - mutex_lock(&isolated_cpus_lock); acpi_pad_idle_cpus(0); mutex_unlock(&isolated_cpus_lock); - acpi_remove_notify_handler(adev->handle, - ACPI_DEVICE_NOTIFY, acpi_pad_notify); + acpi_dev_remove_notify_handler(ACPI_COMPANION(&pdev->dev), + ACPI_DEVICE_NOTIFY, acpi_pad_notify); } static const struct acpi_device_id pad_device_ids[] = { From be473f0591f183990a998edee02161b319047eaa Mon Sep 17 00:00:00 2001 From: Pengjie Zhang Date: Wed, 11 Mar 2026 15:13:34 +0800 Subject: [PATCH 21/73] ACPI: CPPC: Fix uninitialized ref variable in cppc_get_perf_caps() Commit 8505bfb4e4ec ("ACPI: CPPC: Move reference performance to capabilities") introduced a logical error when retrieving the reference performance. On platforms lacking the reference performance register, the fallback logic leaves the local 'ref' variable uninitialized (0). This causes the subsequent sanity check to incorrectly return -EFAULT, breaking amd_pstate initialization. Fix this by assigning 'ref = nom' in the fallback path. Fixes: 8505bfb4e4ec ("ACPI: CPPC: Move reference performance to capabilities") Reported-by: Nathan Chancellor Closes: https://lore.kernel.org/all/20260310003026.GA2639793@ax162/ Tested-by: Nathan Chancellor Signed-off-by: Pengjie Zhang [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260311071334.1494960-1-zhangpengjie2@huawei.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 07bbf5b366a4..5ad922eb937a 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1407,12 +1407,11 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) * If reference perf register is not supported then we should * use the nominal perf value */ - if (CPC_SUPPORTED(reference_reg)) { + if (CPC_SUPPORTED(reference_reg)) cpc_read(cpunum, reference_reg, &ref); - perf_caps->reference_perf = ref; - } else { - perf_caps->reference_perf = nom; - } + else + ref = nom; + perf_caps->reference_perf = ref; if (guaranteed_reg->type != ACPI_TYPE_BUFFER || IS_NULL_REG(&guaranteed_reg->cpc_entry.reg)) { From db19103ea847ed139da59a2fb71773081c12cd40 Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Wed, 11 Mar 2026 14:50:36 +0800 Subject: [PATCH 22/73] ACPI: processor: idle: Remove redundant cstate check in acpi_processor_power_init The function acpi_processor_cstate_first_run_checks() is responsible for updating max_cstate and performing initial hardware validation. Currently, this function is invoked within acpi_processor_power_init(). However, the initialization flow already ensures this is called during acpi_processor_register_idle_driver(). Therefore, the call in acpi_processor_power_init() is redundant and effectively performs no work, so remove it. Signed-off-by: Huisong Li Link: https://patch.msgid.link/20260311065038.4151558-2-lihuisong@huawei.com [ rjw: Changelog edits ] Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_idle.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index d4753420ae0b..758dbe79cdaf 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1411,8 +1411,6 @@ void acpi_processor_power_init(struct acpi_processor *pr) if (disabled_by_idle_boot_param()) return; - acpi_processor_cstate_first_run_checks(); - if (!acpi_processor_get_power_info(pr)) pr->flags.power_setup_done = 1; From 1f23194c8b8208bf3a43beb6c97d4c843197b6f6 Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Wed, 11 Mar 2026 14:50:37 +0800 Subject: [PATCH 23/73] ACPI: processor: idle: Move max_cstate update out of the loop The acpi_processor_cstate_first_run_checks() function, which updates max_cstate on certain platforms, only needs to be executed once. Move this call outside of the loop to avoid redundant executions. Signed-off-by: Huisong Li Link: https://patch.msgid.link/20260311065038.4151558-3-lihuisong@huawei.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_idle.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 758dbe79cdaf..625f80fe0b98 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1359,6 +1359,8 @@ void acpi_processor_register_idle_driver(void) int ret = -ENODEV; int cpu; + acpi_processor_cstate_first_run_checks(); + /* * ACPI idle driver is used by all possible CPUs. * Use the processor power info of one in them to set up idle states. @@ -1370,7 +1372,6 @@ void acpi_processor_register_idle_driver(void) if (!pr) continue; - acpi_processor_cstate_first_run_checks(); ret = acpi_processor_get_power_info(pr); if (!ret) { pr->flags.power_setup_done = 1; From 4d613fb1ea0516e1f69d3a4ebfbf2572d5da5368 Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Wed, 11 Mar 2026 14:50:38 +0800 Subject: [PATCH 24/73] ACPI: processor: idle: Remove redundant static variable and rename cstate check function The function acpi_processor_cstate_first_run_checks() is currently called only once during initialization in acpi_processor_register_idle_driver(). Since its execution is already limited by the caller's lifecycle, the internal static 'first_run' variable is redundant and can be safely removed. Additionally, the current function name is no longer descriptive of its behavior, so rename the function to acpi_processor_update_max_cstate() to better reflect its actual purpose. Signed-off-by: Huisong Li Link: https://patch.msgid.link/20260311065038.4151558-4-lihuisong@huawei.com [ rjw: Changelog edits ] Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_idle.c | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 625f80fe0b98..45b5d17443cf 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -819,19 +819,13 @@ static void acpi_processor_setup_cstates(struct acpi_processor *pr) drv->state_count = count; } -static inline void acpi_processor_cstate_first_run_checks(void) +static inline void acpi_processor_update_max_cstate(void) { - static int first_run; - - if (first_run) - return; dmi_check_system(processor_power_dmi_table); max_cstate = acpi_processor_cstate_check(max_cstate); if (max_cstate < ACPI_C_STATES_MAX) pr_notice("processor limited to max C-state %d\n", max_cstate); - first_run++; - if (nocst) return; @@ -840,7 +834,7 @@ static inline void acpi_processor_cstate_first_run_checks(void) #else static inline int disabled_by_idle_boot_param(void) { return 0; } -static inline void acpi_processor_cstate_first_run_checks(void) { } +static inline void acpi_processor_update_max_cstate(void) { } static int acpi_processor_get_cstate_info(struct acpi_processor *pr) { return -ENODEV; @@ -1359,7 +1353,7 @@ void acpi_processor_register_idle_driver(void) int ret = -ENODEV; int cpu; - acpi_processor_cstate_first_run_checks(); + acpi_processor_update_max_cstate(); /* * ACPI idle driver is used by all possible CPUs. From 97e6fabee5dcb2d86d4ff45f20606b8a73181f74 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 13 Mar 2026 13:53:03 +0100 Subject: [PATCH 25/73] driver core: auxiliary bus: Introduce dev_is_auxiliary() Introduce dev_is_auxiliary() in analogy with dev_is_platform() to facilitate subsequent changes. Signed-off-by: Rafael J. Wysocki Reviewed-by: Danilo Krummrich Reviewed-by: Greg Kroah-Hartman Link: https://patch.msgid.link/5079467.GXAFRqVoOG@rafael.j.wysocki --- drivers/base/auxiliary.c | 10 ++++++++++ include/linux/auxiliary_bus.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/drivers/base/auxiliary.c b/drivers/base/auxiliary.c index 9fd3820d1f8a..11949d6bcda4 100644 --- a/drivers/base/auxiliary.c +++ b/drivers/base/auxiliary.c @@ -502,6 +502,16 @@ struct auxiliary_device *__devm_auxiliary_device_create(struct device *dev, } EXPORT_SYMBOL_GPL(__devm_auxiliary_device_create); +/** + * dev_is_auxiliary - check if the device is an auxiliary one + * @dev: device to check + */ +bool dev_is_auxiliary(struct device *dev) +{ + return dev->bus == &auxiliary_bus_type; +} +EXPORT_SYMBOL_GPL(dev_is_auxiliary); + void __init auxiliary_bus_init(void) { WARN_ON(bus_register(&auxiliary_bus_type)); diff --git a/include/linux/auxiliary_bus.h b/include/linux/auxiliary_bus.h index 4086afd0cc6b..bc09b55e3682 100644 --- a/include/linux/auxiliary_bus.h +++ b/include/linux/auxiliary_bus.h @@ -271,6 +271,8 @@ struct auxiliary_device *__devm_auxiliary_device_create(struct device *dev, __devm_auxiliary_device_create(dev, KBUILD_MODNAME, devname, \ platform_data, 0) +bool dev_is_auxiliary(struct device *dev); + /** * module_auxiliary_driver() - Helper macro for registering an auxiliary driver * @__auxiliary_driver: auxiliary driver struct From 9dc11faca2456384c8f8c5d83b7fc80dafeb9745 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 13 Mar 2026 13:56:19 +0100 Subject: [PATCH 26/73] ACPI: video: Rework checking for duplicate video bus devices The current way of checking for duplicate video bus devices in acpi_video_bus_probe() is based on walking the ACPI namespace which is not necessary after recent driver conversions. It is also susceptible to race conditions (for example, if two video bus devices are probed at the same time) and ordering issues. Instead of doing it the old way, inspect the children of the parent of the device being probed, excluding the latter and the children that are not auxiliary devices. For each of the remaining children, check if any of the entries in the video_bus_head list is equal to its driver data which can only happen if the given child has been processed by acpi_video_bus_probe() successfully and so it is a duplicate of the one being probed. Moreover, to prevent acpi_video_bus_probe() from processing two devices concurrently, which might defeat the above check, use a new internal mutex in it. Also, print the FW_BUG message only if allow_duplicates is unset which allows the entire duplicates check to be skipped in that case (doing it just to print the message about the case that is going to be ignored anyway is kind of pointless). Signed-off-by: Rafael J. Wysocki Reviewed-by: Danilo Krummrich Link: https://patch.msgid.link/3058492.e9J7NaK4W3@rafael.j.wysocki --- drivers/acpi/acpi_video.c | 56 +++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index adbaf0226c90..037582807453 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -1681,26 +1681,6 @@ static int acpi_video_resume(struct notifier_block *nb, return NOTIFY_DONE; } -static acpi_status -acpi_video_bus_match(acpi_handle handle, u32 level, void *context, - void **return_value) -{ - struct acpi_device *device = context; - struct acpi_device *sibling; - - if (handle == device->handle) - return AE_CTRL_TERMINATE; - - sibling = acpi_fetch_acpi_dev(handle); - if (!sibling) - return AE_OK; - - if (!strcmp(acpi_device_name(sibling), ACPI_VIDEO_BUS_NAME)) - return AE_ALREADY_EXISTS; - - return AE_OK; -} - static void acpi_video_dev_register_backlight(struct acpi_video_device *device) { struct backlight_properties props; @@ -1976,29 +1956,49 @@ static int acpi_video_bus_put_devices(struct acpi_video_bus *video) return 0; } +static int duplicate_dev_check(struct device *sibling, void *data) +{ + struct acpi_video_bus *video; + + if (sibling == data || !dev_is_auxiliary(sibling)) + return 0; + + guard(mutex)(&video_list_lock); + + list_for_each_entry(video, &video_bus_head, entry) { + if (video == dev_get_drvdata(sibling)) + return -EEXIST; + } + + return 0; +} + +static bool acpi_video_bus_dev_is_duplicate(struct device *dev) +{ + return device_for_each_child(dev->parent, dev, duplicate_dev_check); +} + static int instance; static int acpi_video_bus_probe(struct auxiliary_device *aux_dev, const struct auxiliary_device_id *id_unused) { struct acpi_device *device = ACPI_COMPANION(&aux_dev->dev); + static DEFINE_MUTEX(probe_lock); struct acpi_video_bus *video; bool auto_detect; int error; - acpi_status status; - status = acpi_walk_namespace(ACPI_TYPE_DEVICE, - acpi_dev_parent(device)->handle, 1, - acpi_video_bus_match, NULL, - device, NULL); - if (status == AE_ALREADY_EXISTS) { + /* Probe one video bus device at a time in case there are duplicates. */ + guard(mutex)(&probe_lock); + + if (!allow_duplicates && acpi_video_bus_dev_is_duplicate(&aux_dev->dev)) { pr_info(FW_BUG "Duplicate ACPI video bus devices for the" " same VGA controller, please try module " "parameter \"video.allow_duplicates=1\"" "if the current driver doesn't work.\n"); - if (!allow_duplicates) - return -ENODEV; + return -ENODEV; } video = kzalloc_obj(struct acpi_video_bus); From 6a8e793ca8db1546d05071ba0807cbf8c4f44a8b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 13 Mar 2026 13:57:43 +0100 Subject: [PATCH 27/73] ACPI: video: Consolidate pnp.bus_id workarounds handling Reduce code duplication related to pnp.bus_id workarounds by combining the two existing cases. Also move the definition of static variable "instance" into acpi_video_bus_probe() because it is only used there. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3430879.44csPzL39Z@rafael.j.wysocki --- drivers/acpi/acpi_video.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index 037582807453..f48bc7817417 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -1978,14 +1978,13 @@ static bool acpi_video_bus_dev_is_duplicate(struct device *dev) return device_for_each_child(dev->parent, dev, duplicate_dev_check); } -static int instance; - static int acpi_video_bus_probe(struct auxiliary_device *aux_dev, const struct auxiliary_device_id *id_unused) { struct acpi_device *device = ACPI_COMPANION(&aux_dev->dev); static DEFINE_MUTEX(probe_lock); struct acpi_video_bus *video; + static int instance; bool auto_detect; int error; @@ -2005,16 +2004,15 @@ static int acpi_video_bus_probe(struct auxiliary_device *aux_dev, if (!video) return -ENOMEM; - /* a hack to fix the duplicate name "VID" problem on T61 */ - if (!strcmp(device->pnp.bus_id, "VID")) { - if (instance) - device->pnp.bus_id[3] = '0' + instance; - instance++; - } - /* a hack to fix the duplicate name "VGA" problem on Pa 3553 */ - if (!strcmp(device->pnp.bus_id, "VGA")) { + /* + * A hack to fix the duplicate name "VID" problem on T61 and the + * duplicate name "VGA" problem on Pa 3553. + */ + if (!strcmp(device->pnp.bus_id, "VID") || + !strcmp(device->pnp.bus_id, "VGA")) { if (instance) device->pnp.bus_id[3] = '0' + instance; + instance++; } From 97892d5f0690f588bbcf755efe922c72cd248639 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 13 Mar 2026 13:58:30 +0100 Subject: [PATCH 28/73] ACPI: driver: Do not set acpi_device_name() unnecessarily ACPI drivers usually set acpi_device_name() for the given struct acpi_device to whatever they like, but that value is never used unless the driver itself uses it and, quite unfortunately, drivers neglect to clear it on remove. Some drivers use it for printing messages or initializing the names of subordinate devices, but it is better to use string literals for that, especially if the given one is used just once. To eliminate unnecessary overhead related to acpi_device_name() handling, rework multiple core ACPI device drivers to stop setting acpi_device_name() for struct acpi_device objects manipulated by them and use a string literal instead of it where applicable. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/10840483.nUPlyArG6x@rafael.j.wysocki --- drivers/acpi/ac.c | 6 ++---- drivers/acpi/acpi_memhotplug.c | 2 -- drivers/acpi/acpi_pad.c | 2 -- drivers/acpi/acpi_processor.c | 1 - drivers/acpi/acpi_video.c | 16 +++++----------- drivers/acpi/battery.c | 3 --- drivers/acpi/button.c | 7 +++---- drivers/acpi/ec.c | 2 -- drivers/acpi/pci_link.c | 2 -- drivers/acpi/pci_root.c | 7 ++----- drivers/acpi/power.c | 2 -- drivers/acpi/sbs.c | 1 - drivers/acpi/sbshc.c | 2 -- drivers/acpi/thermal.c | 6 ++---- include/acpi/processor.h | 1 - 15 files changed, 14 insertions(+), 46 deletions(-) diff --git a/drivers/acpi/ac.c b/drivers/acpi/ac.c index e0560a2c71a0..4985c8890609 100644 --- a/drivers/acpi/ac.c +++ b/drivers/acpi/ac.c @@ -22,7 +22,6 @@ #include #define ACPI_AC_CLASS "ac_adapter" -#define ACPI_AC_DEVICE_NAME "AC Adapter" #define ACPI_AC_FILE_STATE "state" #define ACPI_AC_NOTIFY_STATUS 0x80 #define ACPI_AC_STATUS_OFFLINE 0x00 @@ -203,7 +202,6 @@ static int acpi_ac_probe(struct platform_device *pdev) return -ENOMEM; ac->device = adev; - strscpy(acpi_device_name(adev), ACPI_AC_DEVICE_NAME); strscpy(acpi_device_class(adev), ACPI_AC_CLASS); platform_set_drvdata(pdev, ac); @@ -226,8 +224,8 @@ static int acpi_ac_probe(struct platform_device *pdev) goto err_release_ac; } - pr_info("%s [%s] (%s-line)\n", acpi_device_name(adev), - acpi_device_bid(adev), str_on_off(ac->state)); + pr_info("AC Adapter [%s] (%s-line)\n", acpi_device_bid(adev), + str_on_off(ac->state)); ac->battery_nb.notifier_call = acpi_ac_battery_notify; register_acpi_notifier(&ac->battery_nb); diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c index 02a472fa85fc..7f021e6d8b0e 100644 --- a/drivers/acpi/acpi_memhotplug.c +++ b/drivers/acpi/acpi_memhotplug.c @@ -20,7 +20,6 @@ #define ACPI_MEMORY_DEVICE_CLASS "memory" #define ACPI_MEMORY_DEVICE_HID "PNP0C80" -#define ACPI_MEMORY_DEVICE_NAME "Hotplug Mem Device" static const struct acpi_device_id memory_device_ids[] = { {ACPI_MEMORY_DEVICE_HID, 0}, @@ -297,7 +296,6 @@ static int acpi_memory_device_add(struct acpi_device *device, INIT_LIST_HEAD(&mem_device->res_list); mem_device->device = device; mem_device->mgid = -1; - sprintf(acpi_device_name(device), "%s", ACPI_MEMORY_DEVICE_NAME); sprintf(acpi_device_class(device), "%s", ACPI_MEMORY_DEVICE_CLASS); device->driver_data = mem_device; diff --git a/drivers/acpi/acpi_pad.c b/drivers/acpi/acpi_pad.c index 407a0d68525c..1f735f77fd1a 100644 --- a/drivers/acpi/acpi_pad.c +++ b/drivers/acpi/acpi_pad.c @@ -24,7 +24,6 @@ #include #define ACPI_PROCESSOR_AGGREGATOR_CLASS "acpi_pad" -#define ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME "Processor Aggregator" #define ACPI_PROCESSOR_AGGREGATOR_NOTIFY 0x80 #define ACPI_PROCESSOR_AGGREGATOR_STATUS_SUCCESS 0 @@ -427,7 +426,6 @@ static int acpi_pad_probe(struct platform_device *pdev) { struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); - strscpy(acpi_device_name(adev), ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME); strscpy(acpi_device_class(adev), ACPI_PROCESSOR_AGGREGATOR_CLASS); return acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, diff --git a/drivers/acpi/acpi_processor.c b/drivers/acpi/acpi_processor.c index b34a48068a8d..46020a49a7ed 100644 --- a/drivers/acpi/acpi_processor.c +++ b/drivers/acpi/acpi_processor.c @@ -438,7 +438,6 @@ static int acpi_processor_add(struct acpi_device *device, } pr->handle = device->handle; - strscpy(acpi_device_name(device), ACPI_PROCESSOR_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_PROCESSOR_CLASS); device->driver_data = pr; diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index f48bc7817417..30822d46a71e 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -30,9 +30,6 @@ #include #include -#define ACPI_VIDEO_BUS_NAME "Video Bus" -#define ACPI_VIDEO_DEVICE_NAME "Video Device" - #define MAX_NAME_LEN 20 MODULE_AUTHOR("Bruno Ducrot"); @@ -1144,7 +1141,6 @@ static int acpi_video_bus_get_one_device(struct acpi_device *device, void *arg) return -ENOMEM; } - strscpy(acpi_device_name(device), ACPI_VIDEO_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_VIDEO_CLASS); data->device_id = device_id; @@ -1882,7 +1878,7 @@ static int acpi_video_bus_add_notify_handler(struct acpi_video_bus *video, snprintf(video->phys, sizeof(video->phys), "%s/video/input0", acpi_device_hid(video->device)); - input->name = acpi_device_name(video->device); + input->name = "Video Bus"; input->phys = video->phys; input->id.bustype = BUS_HOST; input->id.product = 0x06; @@ -2019,7 +2015,6 @@ static int acpi_video_bus_probe(struct auxiliary_device *aux_dev, auxiliary_set_drvdata(aux_dev, video); video->device = device; - strscpy(acpi_device_name(device), ACPI_VIDEO_BUS_NAME); strscpy(acpi_device_class(device), ACPI_VIDEO_CLASS); device->driver_data = video; @@ -2041,11 +2036,10 @@ static int acpi_video_bus_probe(struct auxiliary_device *aux_dev, */ acpi_device_fix_up_power_children(device); - pr_info("%s [%s] (multi-head: %s rom: %s post: %s)\n", - ACPI_VIDEO_DEVICE_NAME, acpi_device_bid(device), - str_yes_no(video->flags.multihead), - str_yes_no(video->flags.rom), - str_yes_no(video->flags.post)); + pr_info("Video Device [%s] (multi-head: %s rom: %s post: %s)\n", + acpi_device_bid(device), str_yes_no(video->flags.multihead), + str_yes_no(video->flags.rom), str_yes_no(video->flags.post)); + mutex_lock(&video_list_lock); list_add_tail(&video->entry, &video_bus_head); mutex_unlock(&video_list_lock); diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index 8fbad8bc4650..acf5dd2177a1 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -33,8 +33,6 @@ #define ACPI_BATTERY_CAPACITY_VALID(capacity) \ ((capacity) != 0 && (capacity) != ACPI_BATTERY_VALUE_UNKNOWN) -#define ACPI_BATTERY_DEVICE_NAME "Battery" - /* Battery power unit: 0 means mW, 1 means mA */ #define ACPI_BATTERY_POWER_UNIT_MA 1 @@ -1229,7 +1227,6 @@ static int acpi_battery_probe(struct platform_device *pdev) platform_set_drvdata(pdev, battery); battery->device = device; - strscpy(acpi_device_name(device), ACPI_BATTERY_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_BATTERY_CLASS); result = devm_mutex_init(&pdev->dev, &battery->update_lock); diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index 97b05246efab..c57bd9c63057 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -558,27 +558,26 @@ static int acpi_button_probe(struct platform_device *pdev) goto err_free_button; } - name = acpi_device_name(device); class = acpi_device_class(device); if (!strcmp(hid, ACPI_BUTTON_HID_POWER) || !strcmp(hid, ACPI_BUTTON_HID_POWERF)) { button->type = ACPI_BUTTON_TYPE_POWER; handler = acpi_button_notify; - strscpy(name, ACPI_BUTTON_DEVICE_NAME_POWER, MAX_ACPI_DEVICE_NAME_LEN); + name = ACPI_BUTTON_DEVICE_NAME_POWER; sprintf(class, "%s/%s", ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_POWER); } else if (!strcmp(hid, ACPI_BUTTON_HID_SLEEP) || !strcmp(hid, ACPI_BUTTON_HID_SLEEPF)) { button->type = ACPI_BUTTON_TYPE_SLEEP; handler = acpi_button_notify; - strscpy(name, ACPI_BUTTON_DEVICE_NAME_SLEEP, MAX_ACPI_DEVICE_NAME_LEN); + name = ACPI_BUTTON_DEVICE_NAME_SLEEP; sprintf(class, "%s/%s", ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_SLEEP); } else if (!strcmp(hid, ACPI_BUTTON_HID_LID)) { button->type = ACPI_BUTTON_TYPE_LID; handler = acpi_lid_notify; - strscpy(name, ACPI_BUTTON_DEVICE_NAME_LID, MAX_ACPI_DEVICE_NAME_LEN); + name = ACPI_BUTTON_DEVICE_NAME_LID; sprintf(class, "%s/%s", ACPI_BUTTON_CLASS, ACPI_BUTTON_SUBCLASS_LID); input->open = acpi_lid_input_open; diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 5f63ed120a2c..4b21279012a7 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -36,7 +36,6 @@ #include "internal.h" #define ACPI_EC_CLASS "embedded_controller" -#define ACPI_EC_DEVICE_NAME "Embedded Controller" /* EC status register */ #define ACPI_EC_FLAG_OBF 0x01 /* Output buffer full */ @@ -1681,7 +1680,6 @@ static int acpi_ec_probe(struct platform_device *pdev) struct acpi_ec *ec; int ret; - strscpy(acpi_device_name(device), ACPI_EC_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_EC_CLASS); if (boot_ec && (boot_ec->handle == device->handle || diff --git a/drivers/acpi/pci_link.c b/drivers/acpi/pci_link.c index 45bdfd06bd21..5745de24024c 100644 --- a/drivers/acpi/pci_link.c +++ b/drivers/acpi/pci_link.c @@ -30,7 +30,6 @@ #include "internal.h" #define ACPI_PCI_LINK_CLASS "pci_irq_routing" -#define ACPI_PCI_LINK_DEVICE_NAME "PCI Interrupt Link" #define ACPI_PCI_LINK_MAX_POSSIBLE 16 static int acpi_pci_link_add(struct acpi_device *device, @@ -725,7 +724,6 @@ static int acpi_pci_link_add(struct acpi_device *device, return -ENOMEM; link->device = device; - strscpy(acpi_device_name(device), ACPI_PCI_LINK_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_PCI_LINK_CLASS); device->driver_data = link; diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 4a882e939525..f4aa5b624d9b 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -25,7 +25,6 @@ #include "internal.h" #define ACPI_PCI_ROOT_CLASS "pci_bridge" -#define ACPI_PCI_ROOT_DEVICE_NAME "PCI Root Bridge" static int acpi_pci_root_add(struct acpi_device *device, const struct acpi_device_id *not_used); static void acpi_pci_root_remove(struct acpi_device *device); @@ -689,7 +688,6 @@ static int acpi_pci_root_add(struct acpi_device *device, root->device = device; root->segment = segment & 0xFFFF; - strscpy(acpi_device_name(device), ACPI_PCI_ROOT_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_PCI_ROOT_CLASS); device->driver_data = root; @@ -698,9 +696,8 @@ static int acpi_pci_root_add(struct acpi_device *device, goto end; } - pr_info("%s [%s] (domain %04x %pR)\n", - acpi_device_name(device), acpi_device_bid(device), - root->segment, &root->secondary); + pr_info("PCI Root Bridge [%s] (domain %04x %pR)\n", + acpi_device_bid(device), root->segment, &root->secondary); root->mcfg_addr = acpi_pci_root_get_mcfg_addr(handle); diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 4611159ee734..dcc9ad7790f0 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -38,7 +38,6 @@ #include "internal.h" #define ACPI_POWER_CLASS "power_resource" -#define ACPI_POWER_DEVICE_NAME "Power Resource" #define ACPI_POWER_RESOURCE_STATE_OFF 0x00 #define ACPI_POWER_RESOURCE_STATE_ON 0x01 #define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF @@ -955,7 +954,6 @@ struct acpi_device *acpi_add_power_resource(acpi_handle handle) mutex_init(&resource->resource_lock); INIT_LIST_HEAD(&resource->list_node); INIT_LIST_HEAD(&resource->dependents); - strscpy(acpi_device_name(device), ACPI_POWER_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_POWER_CLASS); device->power.state = ACPI_STATE_UNKNOWN; device->flags.match_driver = true; diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index bbd3938f7b52..7e789290c5ad 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c @@ -648,7 +648,6 @@ static int acpi_sbs_probe(struct platform_device *pdev) sbs->hc = dev_get_drvdata(pdev->dev.parent); sbs->device = device; - strscpy(acpi_device_name(device), ACPI_SBS_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_SBS_CLASS); result = acpi_charger_add(sbs); diff --git a/drivers/acpi/sbshc.c b/drivers/acpi/sbshc.c index 36850831910b..97eaa2fc31f2 100644 --- a/drivers/acpi/sbshc.c +++ b/drivers/acpi/sbshc.c @@ -19,7 +19,6 @@ #include "internal.h" #define ACPI_SMB_HC_CLASS "smbus_host_ctl" -#define ACPI_SMB_HC_DEVICE_NAME "ACPI SMBus HC" struct acpi_smb_hc { struct acpi_ec *ec; @@ -251,7 +250,6 @@ static int acpi_smbus_hc_probe(struct platform_device *pdev) return -EIO; } - strscpy(acpi_device_name(device), ACPI_SMB_HC_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_SMB_HC_CLASS); hc = kzalloc_obj(struct acpi_smb_hc); diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index 64356b004a57..6ccb364665d1 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -35,7 +35,6 @@ #include "internal.h" #define ACPI_THERMAL_CLASS "thermal_zone" -#define ACPI_THERMAL_DEVICE_NAME "Thermal Zone" #define ACPI_THERMAL_NOTIFY_TEMPERATURE 0x80 #define ACPI_THERMAL_NOTIFY_THRESHOLDS 0x81 #define ACPI_THERMAL_NOTIFY_DEVICES 0x82 @@ -800,7 +799,6 @@ static int acpi_thermal_probe(struct platform_device *pdev) tz->device = device; strscpy(tz->name, device->pnp.bus_id); - strscpy(acpi_device_name(device), ACPI_THERMAL_DEVICE_NAME); strscpy(acpi_device_class(device), ACPI_THERMAL_CLASS); acpi_thermal_aml_dependency_fix(tz); @@ -879,8 +877,8 @@ static int acpi_thermal_probe(struct platform_device *pdev) mutex_init(&tz->thermal_check_lock); INIT_WORK(&tz->thermal_check_work, acpi_thermal_check_fn); - pr_info("%s [%s] (%ld C)\n", acpi_device_name(device), - acpi_device_bid(device), deci_kelvin_to_celsius(tz->temp_dk)); + pr_info("Thermal Zone [%s] (%ld C)\n", acpi_device_bid(device), + deci_kelvin_to_celsius(tz->temp_dk)); result = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY, acpi_thermal_notify, tz); diff --git a/include/acpi/processor.h b/include/acpi/processor.h index 7146a8e9e9c2..43fe4a85fc0f 100644 --- a/include/acpi/processor.h +++ b/include/acpi/processor.h @@ -15,7 +15,6 @@ #include #define ACPI_PROCESSOR_CLASS "processor" -#define ACPI_PROCESSOR_DEVICE_NAME "Processor" #define ACPI_PROCESSOR_DEVICE_HID "ACPI0007" #define ACPI_PROCESSOR_CONTAINER_HID "ACPI0010" From 69652f32c9ac71e2b0c8ed407e13ad905e00e947 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 13 Mar 2026 13:59:17 +0100 Subject: [PATCH 29/73] ACPI: event: Redefine acpi_notifier_call_chain() Notice that acpi_notifier_call_chain() only uses its device argument to retrieve the pnp.device_class and pnp.bus_id values from there, so it can be redefined to take pointers to those two strings as parameters istead of a struct acpi_device pointer. That allows all of its callers to pass a string literal as its first argument, so they won't need to initialize pnp.device_class in struct acpi_device objects operated by them any more, and its signature becomes more similar to acpi_bus_generate_netlink_event() then. Update the code as per the above. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2056097.PYKUYFuaPT@rafael.j.wysocki --- drivers/acpi/ac.c | 3 ++- drivers/acpi/acpi_video.c | 9 ++++++--- drivers/acpi/battery.c | 3 ++- drivers/acpi/event.c | 7 ++++--- include/acpi/acpi_bus.h | 3 ++- 5 files changed, 16 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/ac.c b/drivers/acpi/ac.c index 4985c8890609..2b500e89169f 100644 --- a/drivers/acpi/ac.c +++ b/drivers/acpi/ac.c @@ -133,7 +133,8 @@ static void acpi_ac_notify(acpi_handle handle, u32 event, void *data) acpi_bus_generate_netlink_event(adev->pnp.device_class, dev_name(&adev->dev), event, (u32) ac->state); - acpi_notifier_call_chain(adev, event, (u32) ac->state); + acpi_notifier_call_chain(ACPI_AC_CLASS, acpi_device_bid(adev), + event, ac->state); power_supply_changed(ac->charger); } } diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index 30822d46a71e..c747827653d9 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -1566,7 +1566,8 @@ static void acpi_video_bus_notify(acpi_handle handle, u32 event, void *data) break; } - if (acpi_notifier_call_chain(device, event, 0)) + if (acpi_notifier_call_chain(ACPI_VIDEO_CLASS, acpi_device_bid(device), + event, 0)) /* Something vetoed the keypress. */ keycode = 0; @@ -1607,7 +1608,8 @@ static void acpi_video_device_notify(acpi_handle handle, u32 event, void *data) if (video_device->backlight) backlight_force_update(video_device->backlight, BACKLIGHT_UPDATE_HOTKEY); - acpi_notifier_call_chain(device, event, 0); + acpi_notifier_call_chain(ACPI_VIDEO_CLASS, acpi_device_bid(device), + event, 0); return; } @@ -1640,7 +1642,8 @@ static void acpi_video_device_notify(acpi_handle handle, u32 event, void *data) if (keycode) may_report_brightness_keys = true; - acpi_notifier_call_chain(device, event, 0); + acpi_notifier_call_chain(ACPI_VIDEO_CLASS, acpi_device_bid(device), + event, 0); if (keycode && (report_key_events & REPORT_BRIGHTNESS_KEY_EVENTS)) { input_report_key(input, keycode, 1); diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index acf5dd2177a1..1bfc4179e885 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -1081,7 +1081,8 @@ static void acpi_battery_notify(acpi_handle handle, u32 event, void *data) acpi_bus_generate_netlink_event(device->pnp.device_class, dev_name(&device->dev), event, acpi_battery_present(battery)); - acpi_notifier_call_chain(device, event, acpi_battery_present(battery)); + acpi_notifier_call_chain(ACPI_BATTERY_CLASS, acpi_device_bid(device), + event, acpi_battery_present(battery)); /* acpi_battery_update could remove power_supply object */ if (old && battery->bat) power_supply_changed(battery->bat); diff --git a/drivers/acpi/event.c b/drivers/acpi/event.c index 96a9aaaaf9f7..4d840d2e7b98 100644 --- a/drivers/acpi/event.c +++ b/drivers/acpi/event.c @@ -24,12 +24,13 @@ /* ACPI notifier chain */ static BLOCKING_NOTIFIER_HEAD(acpi_chain_head); -int acpi_notifier_call_chain(struct acpi_device *dev, u32 type, u32 data) +int acpi_notifier_call_chain(const char *device_class, + const char *bus_id, u32 type, u32 data) { struct acpi_bus_event event; - strscpy(event.device_class, dev->pnp.device_class); - strscpy(event.bus_id, dev->pnp.bus_id); + strscpy(event.device_class, device_class); + strscpy(event.bus_id, bus_id); event.type = type; event.data = data; return (blocking_notifier_call_chain(&acpi_chain_head, 0, (void *)&event) diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index aad1a95e6863..ff14c9362122 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -625,7 +625,8 @@ int acpi_dev_install_notify_handler(struct acpi_device *adev, void acpi_dev_remove_notify_handler(struct acpi_device *adev, u32 handler_type, acpi_notify_handler handler); -extern int acpi_notifier_call_chain(struct acpi_device *, u32, u32); +extern int acpi_notifier_call_chain(const char *device_class, + const char *bus_id, u32 type, u32 data); extern int register_acpi_notifier(struct notifier_block *); extern int unregister_acpi_notifier(struct notifier_block *); From 76866c912ac8639965861c648fa234ab61c0e72d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 13 Mar 2026 13:59:58 +0100 Subject: [PATCH 30/73] ACPI: driver: Avoid using pnp.device_class for netlink handling Update several core ACPI device drivers that use acpi_bus_generate_netlink_event() for generating netlink messages to pass a string literal as its first argument instead of a pointer to pnp.device_class in a given struct acpi_device, which will allow them to avoid initializing the pnp.device_class field in the future. The ACPI button driver that uses different acpi_device_class() values for different button types will still pass it to acpi_bus_generate_netlink_event(), but update it to use the acpi_device_class() macro instead of open coding the pointer access path. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/7944022.EvYhyI6sBW@rafael.j.wysocki --- drivers/acpi/ac.c | 6 +++--- drivers/acpi/acpi_pad.c | 4 ++-- drivers/acpi/battery.c | 2 +- drivers/acpi/button.c | 2 +- drivers/acpi/processor_driver.c | 22 ++++++++-------------- drivers/acpi/thermal.c | 6 +++--- 6 files changed, 18 insertions(+), 24 deletions(-) diff --git a/drivers/acpi/ac.c b/drivers/acpi/ac.c index 2b500e89169f..41a085562c63 100644 --- a/drivers/acpi/ac.c +++ b/drivers/acpi/ac.c @@ -130,9 +130,9 @@ static void acpi_ac_notify(acpi_handle handle, u32 event, void *data) msleep(ac_sleep_before_get_state_ms); acpi_ac_get_state(ac); - acpi_bus_generate_netlink_event(adev->pnp.device_class, - dev_name(&adev->dev), event, - (u32) ac->state); + acpi_bus_generate_netlink_event(ACPI_AC_CLASS, + dev_name(&adev->dev), event, + ac->state); acpi_notifier_call_chain(ACPI_AC_CLASS, acpi_device_bid(adev), event, ac->state); power_supply_changed(ac->charger); diff --git a/drivers/acpi/acpi_pad.c b/drivers/acpi/acpi_pad.c index 1f735f77fd1a..b46c4dd65fbe 100644 --- a/drivers/acpi/acpi_pad.c +++ b/drivers/acpi/acpi_pad.c @@ -413,8 +413,8 @@ static void acpi_pad_notify(acpi_handle handle, u32 event, void *data) switch (event) { case ACPI_PROCESSOR_AGGREGATOR_NOTIFY: acpi_pad_handle_notify(handle); - acpi_bus_generate_netlink_event(adev->pnp.device_class, - dev_name(&adev->dev), event, 0); + acpi_bus_generate_netlink_event(ACPI_PROCESSOR_AGGREGATOR_CLASS, + dev_name(&adev->dev), event, 0); break; default: pr_warn("Unsupported event [0x%x]\n", event); diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index 1bfc4179e885..54048438b5da 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -1078,7 +1078,7 @@ static void acpi_battery_notify(acpi_handle handle, u32 event, void *data) if (event == ACPI_BATTERY_NOTIFY_INFO) acpi_battery_refresh(battery); acpi_battery_update(battery, false); - acpi_bus_generate_netlink_event(device->pnp.device_class, + 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), diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index c57bd9c63057..cc17d9d843ec 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -468,7 +468,7 @@ static void acpi_button_notify(acpi_handle handle, u32 event, void *data) input_report_key(input, keycode, 0); input_sync(input); - acpi_bus_generate_netlink_event(device->pnp.device_class, + acpi_bus_generate_netlink_event(acpi_device_class(device), dev_name(&device->dev), event, ++button->pushed); } diff --git a/drivers/acpi/processor_driver.c b/drivers/acpi/processor_driver.c index 882709796b4f..bbe2c5afb8ba 100644 --- a/drivers/acpi/processor_driver.c +++ b/drivers/acpi/processor_driver.c @@ -53,7 +53,7 @@ static void acpi_processor_notify(acpi_handle handle, u32 event, void *data) { struct acpi_device *device = data; struct acpi_processor *pr; - int saved; + int saved, ev_data = 0; if (device->handle != handle) return; @@ -66,33 +66,27 @@ static void acpi_processor_notify(acpi_handle handle, u32 event, void *data) case ACPI_PROCESSOR_NOTIFY_PERFORMANCE: saved = pr->performance_platform_limit; acpi_processor_ppc_has_changed(pr, 1); - if (saved == pr->performance_platform_limit) - break; - acpi_bus_generate_netlink_event(device->pnp.device_class, - dev_name(&device->dev), event, - pr->performance_platform_limit); + ev_data = pr->performance_platform_limit; + if (saved == ev_data) + return; + break; case ACPI_PROCESSOR_NOTIFY_POWER: acpi_processor_power_state_has_changed(pr); - acpi_bus_generate_netlink_event(device->pnp.device_class, - dev_name(&device->dev), event, 0); break; case ACPI_PROCESSOR_NOTIFY_THROTTLING: acpi_processor_tstate_has_changed(pr); - acpi_bus_generate_netlink_event(device->pnp.device_class, - dev_name(&device->dev), event, 0); break; case ACPI_PROCESSOR_NOTIFY_HIGEST_PERF_CHANGED: cpufreq_update_limits(pr->id); - acpi_bus_generate_netlink_event(device->pnp.device_class, - dev_name(&device->dev), event, 0); break; default: acpi_handle_debug(handle, "Unsupported event [0x%x]\n", event); - break; + return; } - return; + acpi_bus_generate_netlink_event(ACPI_PROCESSOR_CLASS, + dev_name(&device->dev), event, ev_data); } static int __acpi_processor_start(struct acpi_device *device); diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index 6ccb364665d1..e764641a43c1 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -340,7 +340,7 @@ static void acpi_thermal_trips_update(struct acpi_thermal *tz, u32 event) thermal_zone_for_each_trip(tz->thermal_zone, acpi_thermal_adjust_trip, &atd); acpi_queue_thermal_check(tz); - acpi_bus_generate_netlink_event(adev->pnp.device_class, + acpi_bus_generate_netlink_event(ACPI_THERMAL_CLASS, dev_name(&adev->dev), event, 0); } @@ -542,7 +542,7 @@ static void acpi_thermal_zone_device_hot(struct thermal_zone_device *thermal) { struct acpi_thermal *tz = thermal_zone_device_priv(thermal); - acpi_bus_generate_netlink_event(tz->device->pnp.device_class, + acpi_bus_generate_netlink_event(ACPI_THERMAL_CLASS, dev_name(&tz->device->dev), ACPI_THERMAL_NOTIFY_HOT, 1); } @@ -551,7 +551,7 @@ static void acpi_thermal_zone_device_critical(struct thermal_zone_device *therma { struct acpi_thermal *tz = thermal_zone_device_priv(thermal); - acpi_bus_generate_netlink_event(tz->device->pnp.device_class, + acpi_bus_generate_netlink_event(ACPI_THERMAL_CLASS, dev_name(&tz->device->dev), ACPI_THERMAL_NOTIFY_CRITICAL, 1); From e18947038bf4f39d47cdba511f85a9af668d56e1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 13 Mar 2026 14:00:41 +0100 Subject: [PATCH 31/73] ACPI: driver: Do not set acpi_device_class() unnecessarily Several core ACPI device drivers set acpi_device_class() for the given struct acpi_device to whatever they like, but that value is never used unless the driver itself uses it and, sadly, they neglect to clear it on remove. Since the only one of them still using acpi_device_class() after previous changes is the button driver, update the others to stop setting it in vain. Also drop the related device class sybmols that become redundant. Since the ACPI button driver continues to use acpi_device_class(), make it clear the struct field represented by acpi_device_class() in its remove callback. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3706295.iIbC2pHGDl@rafael.j.wysocki --- drivers/acpi/ac.c | 1 - drivers/acpi/acpi_memhotplug.c | 2 -- drivers/acpi/acpi_pad.c | 5 +---- drivers/acpi/acpi_processor.c | 1 - drivers/acpi/acpi_video.c | 3 --- drivers/acpi/battery.c | 1 - drivers/acpi/button.c | 2 ++ drivers/acpi/ec.c | 4 ---- drivers/acpi/pci_link.c | 2 -- drivers/acpi/pci_root.c | 2 -- drivers/acpi/power.c | 2 -- drivers/acpi/processor_driver.c | 4 ++-- drivers/acpi/sbs.c | 2 -- drivers/acpi/sbshc.c | 4 ---- drivers/acpi/thermal.c | 1 - include/acpi/processor.h | 1 - 16 files changed, 5 insertions(+), 32 deletions(-) diff --git a/drivers/acpi/ac.c b/drivers/acpi/ac.c index 41a085562c63..2825db974bd8 100644 --- a/drivers/acpi/ac.c +++ b/drivers/acpi/ac.c @@ -203,7 +203,6 @@ static int acpi_ac_probe(struct platform_device *pdev) return -ENOMEM; ac->device = adev; - strscpy(acpi_device_class(adev), ACPI_AC_CLASS); platform_set_drvdata(pdev, ac); diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c index 7f021e6d8b0e..1d7dfe4ee9a6 100644 --- a/drivers/acpi/acpi_memhotplug.c +++ b/drivers/acpi/acpi_memhotplug.c @@ -18,7 +18,6 @@ #include "internal.h" -#define ACPI_MEMORY_DEVICE_CLASS "memory" #define ACPI_MEMORY_DEVICE_HID "PNP0C80" static const struct acpi_device_id memory_device_ids[] = { @@ -296,7 +295,6 @@ static int acpi_memory_device_add(struct acpi_device *device, INIT_LIST_HEAD(&mem_device->res_list); mem_device->device = device; mem_device->mgid = -1; - sprintf(acpi_device_class(device), "%s", ACPI_MEMORY_DEVICE_CLASS); device->driver_data = mem_device; /* Get the range from the _CRS */ diff --git a/drivers/acpi/acpi_pad.c b/drivers/acpi/acpi_pad.c index b46c4dd65fbe..0a8e02bc8c8b 100644 --- a/drivers/acpi/acpi_pad.c +++ b/drivers/acpi/acpi_pad.c @@ -23,7 +23,6 @@ #include #include -#define ACPI_PROCESSOR_AGGREGATOR_CLASS "acpi_pad" #define ACPI_PROCESSOR_AGGREGATOR_NOTIFY 0x80 #define ACPI_PROCESSOR_AGGREGATOR_STATUS_SUCCESS 0 @@ -413,7 +412,7 @@ static void acpi_pad_notify(acpi_handle handle, u32 event, void *data) switch (event) { case ACPI_PROCESSOR_AGGREGATOR_NOTIFY: acpi_pad_handle_notify(handle); - acpi_bus_generate_netlink_event(ACPI_PROCESSOR_AGGREGATOR_CLASS, + acpi_bus_generate_netlink_event("acpi_pad", dev_name(&adev->dev), event, 0); break; default: @@ -426,8 +425,6 @@ static int acpi_pad_probe(struct platform_device *pdev) { struct acpi_device *adev = ACPI_COMPANION(&pdev->dev); - strscpy(acpi_device_class(adev), ACPI_PROCESSOR_AGGREGATOR_CLASS); - return acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY, acpi_pad_notify, adev); } diff --git a/drivers/acpi/acpi_processor.c b/drivers/acpi/acpi_processor.c index 46020a49a7ed..2ac76f3b1cfd 100644 --- a/drivers/acpi/acpi_processor.c +++ b/drivers/acpi/acpi_processor.c @@ -438,7 +438,6 @@ static int acpi_processor_add(struct acpi_device *device, } pr->handle = device->handle; - strscpy(acpi_device_class(device), ACPI_PROCESSOR_CLASS); device->driver_data = pr; result = acpi_processor_get_info(device); diff --git a/drivers/acpi/acpi_video.c b/drivers/acpi/acpi_video.c index c747827653d9..05793ddef787 100644 --- a/drivers/acpi/acpi_video.c +++ b/drivers/acpi/acpi_video.c @@ -1141,8 +1141,6 @@ static int acpi_video_bus_get_one_device(struct acpi_device *device, void *arg) return -ENOMEM; } - strscpy(acpi_device_class(device), ACPI_VIDEO_CLASS); - data->device_id = device_id; data->video = video; data->dev = device; @@ -2018,7 +2016,6 @@ static int acpi_video_bus_probe(struct auxiliary_device *aux_dev, auxiliary_set_drvdata(aux_dev, video); video->device = device; - strscpy(acpi_device_class(device), ACPI_VIDEO_CLASS); device->driver_data = video; acpi_video_bus_find_cap(video); diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index 54048438b5da..b4c25474f42f 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -1228,7 +1228,6 @@ static int acpi_battery_probe(struct platform_device *pdev) platform_set_drvdata(pdev, battery); battery->device = device; - strscpy(acpi_device_class(device), ACPI_BATTERY_CLASS); result = devm_mutex_init(&pdev->dev, &battery->update_lock); if (result) diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index cc17d9d843ec..dc064a388c23 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -697,6 +697,8 @@ static void acpi_button_remove(struct platform_device *pdev) acpi_button_remove_fs(button); input_unregister_device(button->input); kfree(button); + + memset(acpi_device_class(adev), 0, sizeof(acpi_device_class)); } static int param_set_lid_init_state(const char *val, diff --git a/drivers/acpi/ec.c b/drivers/acpi/ec.c index 4b21279012a7..0624d8673679 100644 --- a/drivers/acpi/ec.c +++ b/drivers/acpi/ec.c @@ -35,8 +35,6 @@ #include "internal.h" -#define ACPI_EC_CLASS "embedded_controller" - /* EC status register */ #define ACPI_EC_FLAG_OBF 0x01 /* Output buffer full */ #define ACPI_EC_FLAG_IBF 0x02 /* Input buffer full */ @@ -1680,8 +1678,6 @@ static int acpi_ec_probe(struct platform_device *pdev) struct acpi_ec *ec; int ret; - strscpy(acpi_device_class(device), ACPI_EC_CLASS); - if (boot_ec && (boot_ec->handle == device->handle || !strcmp(acpi_device_hid(device), ACPI_ECDT_HID))) { /* Fast path: this device corresponds to the boot EC. */ diff --git a/drivers/acpi/pci_link.c b/drivers/acpi/pci_link.c index 5745de24024c..e6ed13aee48d 100644 --- a/drivers/acpi/pci_link.c +++ b/drivers/acpi/pci_link.c @@ -29,7 +29,6 @@ #include "internal.h" -#define ACPI_PCI_LINK_CLASS "pci_irq_routing" #define ACPI_PCI_LINK_MAX_POSSIBLE 16 static int acpi_pci_link_add(struct acpi_device *device, @@ -724,7 +723,6 @@ static int acpi_pci_link_add(struct acpi_device *device, return -ENOMEM; link->device = device; - strscpy(acpi_device_class(device), ACPI_PCI_LINK_CLASS); device->driver_data = link; mutex_lock(&acpi_link_lock); diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index f4aa5b624d9b..a0ba64e45e8a 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -24,7 +24,6 @@ #include #include "internal.h" -#define ACPI_PCI_ROOT_CLASS "pci_bridge" static int acpi_pci_root_add(struct acpi_device *device, const struct acpi_device_id *not_used); static void acpi_pci_root_remove(struct acpi_device *device); @@ -688,7 +687,6 @@ static int acpi_pci_root_add(struct acpi_device *device, root->device = device; root->segment = segment & 0xFFFF; - strscpy(acpi_device_class(device), ACPI_PCI_ROOT_CLASS); device->driver_data = root; if (hotadd && dmar_device_add(handle)) { diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index dcc9ad7790f0..6b1680ec3694 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -37,7 +37,6 @@ #include "sleep.h" #include "internal.h" -#define ACPI_POWER_CLASS "power_resource" #define ACPI_POWER_RESOURCE_STATE_OFF 0x00 #define ACPI_POWER_RESOURCE_STATE_ON 0x01 #define ACPI_POWER_RESOURCE_STATE_UNKNOWN 0xFF @@ -954,7 +953,6 @@ struct acpi_device *acpi_add_power_resource(acpi_handle handle) mutex_init(&resource->resource_lock); INIT_LIST_HEAD(&resource->list_node); INIT_LIST_HEAD(&resource->dependents); - strscpy(acpi_device_class(device), ACPI_POWER_CLASS); device->power.state = ACPI_STATE_UNKNOWN; device->flags.match_driver = true; diff --git a/drivers/acpi/processor_driver.c b/drivers/acpi/processor_driver.c index bbe2c5afb8ba..cda8fd720000 100644 --- a/drivers/acpi/processor_driver.c +++ b/drivers/acpi/processor_driver.c @@ -85,8 +85,8 @@ static void acpi_processor_notify(acpi_handle handle, u32 event, void *data) return; } - acpi_bus_generate_netlink_event(ACPI_PROCESSOR_CLASS, - dev_name(&device->dev), event, ev_data); + acpi_bus_generate_netlink_event("processor", dev_name(&device->dev), + event, ev_data); } static int __acpi_processor_start(struct acpi_device *device); diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index 7e789290c5ad..e301d73ac420 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c @@ -26,7 +26,6 @@ #include "sbshc.h" -#define ACPI_SBS_CLASS "sbs" #define ACPI_AC_CLASS "ac_adapter" #define ACPI_SBS_DEVICE_NAME "Smart Battery System" #define ACPI_BATTERY_DIR_NAME "BAT%i" @@ -648,7 +647,6 @@ static int acpi_sbs_probe(struct platform_device *pdev) sbs->hc = dev_get_drvdata(pdev->dev.parent); sbs->device = device; - strscpy(acpi_device_class(device), ACPI_SBS_CLASS); result = acpi_charger_add(sbs); if (result && result != -ENODEV) diff --git a/drivers/acpi/sbshc.c b/drivers/acpi/sbshc.c index 97eaa2fc31f2..f413270415b6 100644 --- a/drivers/acpi/sbshc.c +++ b/drivers/acpi/sbshc.c @@ -18,8 +18,6 @@ #include "sbshc.h" #include "internal.h" -#define ACPI_SMB_HC_CLASS "smbus_host_ctl" - struct acpi_smb_hc { struct acpi_ec *ec; struct mutex lock; @@ -250,8 +248,6 @@ static int acpi_smbus_hc_probe(struct platform_device *pdev) return -EIO; } - strscpy(acpi_device_class(device), ACPI_SMB_HC_CLASS); - hc = kzalloc_obj(struct acpi_smb_hc); if (!hc) return -ENOMEM; diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index e764641a43c1..b8b487d89d25 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -799,7 +799,6 @@ static int acpi_thermal_probe(struct platform_device *pdev) tz->device = device; strscpy(tz->name, device->pnp.bus_id); - strscpy(acpi_device_class(device), ACPI_THERMAL_CLASS); acpi_thermal_aml_dependency_fix(tz); diff --git a/include/acpi/processor.h b/include/acpi/processor.h index 43fe4a85fc0f..554be224ce76 100644 --- a/include/acpi/processor.h +++ b/include/acpi/processor.h @@ -14,7 +14,6 @@ #include -#define ACPI_PROCESSOR_CLASS "processor" #define ACPI_PROCESSOR_DEVICE_HID "ACPI0007" #define ACPI_PROCESSOR_CONTAINER_HID "ACPI0010" From 236ad358166cca167e6ed33639bb7948e7a2f6fd Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Fri, 13 Mar 2026 14:03:46 +0100 Subject: [PATCH 32/73] ACPI: AC: Define ACPI_AC_CLASS in one place MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ACPI_AC_CLASS symbol is defined in several places in the same way which is rather unfortunate. Instead, define it in one common header file (acpi_bus.h) so that it is accessible to all of its users. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/6163384.MhkbZ0Pkbq@rafael.j.wysocki --- drivers/acpi/ac.c | 1 - drivers/acpi/sbs.c | 1 - drivers/gpu/drm/amd/include/amd_acpi.h | 2 -- drivers/gpu/drm/radeon/radeon_acpi.c | 2 -- drivers/platform/x86/hp/hp-wmi.c | 2 -- drivers/platform/x86/lenovo/wmi-capdata.c | 1 - include/acpi/acpi_bus.h | 2 ++ 7 files changed, 2 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/ac.c b/drivers/acpi/ac.c index 2825db974bd8..e9e970fd8f33 100644 --- a/drivers/acpi/ac.c +++ b/drivers/acpi/ac.c @@ -21,7 +21,6 @@ #include #include -#define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_FILE_STATE "state" #define ACPI_AC_NOTIFY_STATUS 0x80 #define ACPI_AC_STATUS_OFFLINE 0x00 diff --git a/drivers/acpi/sbs.c b/drivers/acpi/sbs.c index e301d73ac420..440f1d69aca8 100644 --- a/drivers/acpi/sbs.c +++ b/drivers/acpi/sbs.c @@ -26,7 +26,6 @@ #include "sbshc.h" -#define ACPI_AC_CLASS "ac_adapter" #define ACPI_SBS_DEVICE_NAME "Smart Battery System" #define ACPI_BATTERY_DIR_NAME "BAT%i" #define ACPI_AC_DIR_NAME "AC0" diff --git a/drivers/gpu/drm/amd/include/amd_acpi.h b/drivers/gpu/drm/amd/include/amd_acpi.h index 84933c07f720..4225640131f2 100644 --- a/drivers/gpu/drm/amd/include/amd_acpi.h +++ b/drivers/gpu/drm/amd/include/amd_acpi.h @@ -26,8 +26,6 @@ #include -#define ACPI_AC_CLASS "ac_adapter" - struct atif_verify_interface { u16 size; /* structure size in bytes (includes size field) */ u16 version; /* version */ diff --git a/drivers/gpu/drm/radeon/radeon_acpi.c b/drivers/gpu/drm/radeon/radeon_acpi.c index 08f8ba4fd148..9f511ff08822 100644 --- a/drivers/gpu/drm/radeon/radeon_acpi.c +++ b/drivers/gpu/drm/radeon/radeon_acpi.c @@ -44,8 +44,6 @@ bool radeon_atpx_dgpu_req_power_for_displays(void); static inline bool radeon_atpx_dgpu_req_power_for_displays(void) { return false; } #endif -#define ACPI_AC_CLASS "ac_adapter" - struct atif_verify_interface { u16 size; /* structure size in bytes (includes size field) */ u16 version; /* version */ diff --git a/drivers/platform/x86/hp/hp-wmi.c b/drivers/platform/x86/hp/hp-wmi.c index 68ede7e5757a..1ee8e2a5c738 100644 --- a/drivers/platform/x86/hp/hp-wmi.c +++ b/drivers/platform/x86/hp/hp-wmi.c @@ -58,8 +58,6 @@ enum hp_ec_offsets { #define HP_POWER_LIMIT_DEFAULT 0x00 #define HP_POWER_LIMIT_NO_CHANGE 0xFF -#define ACPI_AC_CLASS "ac_adapter" - #define zero_if_sup(tmp) (zero_insize_support?0:sizeof(tmp)) // use when zero insize is required enum hp_thermal_profile_omen_v0 { diff --git a/drivers/platform/x86/lenovo/wmi-capdata.c b/drivers/platform/x86/lenovo/wmi-capdata.c index ee1fb02d8e31..b73d378f0e8b 100644 --- a/drivers/platform/x86/lenovo/wmi-capdata.c +++ b/drivers/platform/x86/lenovo/wmi-capdata.c @@ -53,7 +53,6 @@ #define LENOVO_CAPABILITY_DATA_01_GUID "7A8F5407-CB67-4D6E-B547-39B3BE018154" #define LENOVO_FAN_TEST_DATA_GUID "B642801B-3D21-45DE-90AE-6E86F164FB21" -#define ACPI_AC_CLASS "ac_adapter" #define ACPI_AC_NOTIFY_STATUS 0x80 #define LWMI_FEATURE_ID_FAN_TEST 0x05 diff --git a/include/acpi/acpi_bus.h b/include/acpi/acpi_bus.h index ff14c9362122..f7c2d3daed44 100644 --- a/include/acpi/acpi_bus.h +++ b/include/acpi/acpi_bus.h @@ -613,6 +613,8 @@ struct acpi_bus_event { u32 data; }; +#define ACPI_AC_CLASS "ac_adapter" + extern struct kobject *acpi_kobj; extern int acpi_bus_generate_netlink_event(const char*, const char*, u8, int); void acpi_bus_private_data_handler(acpi_handle, void *); From 36cb728754ea4583f145ecacb6e4fb9a6d8e62d6 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 17 Mar 2026 09:01:06 +0100 Subject: [PATCH 33/73] ACPI: processor: idle: Replace strlcat() with better alternative strlcpy() and strlcat() are confusing APIs and the former one already gone from the kernel. In preparation to kill strlcat() replace it with the better alternative. Signed-off-by: Andy Shevchenko Link: https://patch.msgid.link/20260317080218.1814693-1-andriy.shevchenko@linux.intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_idle.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 45b5d17443cf..479995a4c48a 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1010,9 +1010,7 @@ static bool combine_lpi_states(struct acpi_lpi_state *local, result->arch_flags = parent->arch_flags; result->index = parent->index; - strscpy(result->desc, local->desc, ACPI_CX_DESC_LEN); - strlcat(result->desc, "+", ACPI_CX_DESC_LEN); - strlcat(result->desc, parent->desc, ACPI_CX_DESC_LEN); + scnprintf(result->desc, ACPI_CX_DESC_LEN, "%s+%s", local->desc, parent->desc); return true; } From 0cc24977224a6c7d470860265a4990109f0a32ee Mon Sep 17 00:00:00 2001 From: Sumit Gupta Date: Wed, 18 Mar 2026 15:20:05 +0530 Subject: [PATCH 34/73] ACPI: CPPC: Check cpc_read() return values consistently Callers of cpc_read() ignore its return value, which can lead to using uninitialized or stale values when the read fails. Fix this by consistently checking cpc_read() return values in cppc_get_perf_caps(), cppc_get_perf_ctrs(), and cppc_get_perf(). Link: https://lore.kernel.org/lkml/48bdf87e-39f1-402f-a7dc-1a0e1e7a819d@nvidia.com/ Suggested-by: Rafael J. Wysocki Signed-off-by: Sumit Gupta Link: https://patch.msgid.link/20260318095005.2437960-1-sumitg@nvidia.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/cppc_acpi.c | 99 +++++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 27 deletions(-) diff --git a/drivers/acpi/cppc_acpi.c b/drivers/acpi/cppc_acpi.c index 5ad922eb937a..053fc6765a59 100644 --- a/drivers/acpi/cppc_acpi.c +++ b/drivers/acpi/cppc_acpi.c @@ -1394,45 +1394,66 @@ int cppc_get_perf_caps(int cpunum, struct cppc_perf_caps *perf_caps) } } - cpc_read(cpunum, highest_reg, &high); + ret = cpc_read(cpunum, highest_reg, &high); + if (ret) + goto out_err; perf_caps->highest_perf = high; - cpc_read(cpunum, lowest_reg, &low); + ret = cpc_read(cpunum, lowest_reg, &low); + if (ret) + goto out_err; perf_caps->lowest_perf = low; - cpc_read(cpunum, nominal_reg, &nom); + ret = cpc_read(cpunum, nominal_reg, &nom); + if (ret) + goto out_err; perf_caps->nominal_perf = nom; /* * If reference perf register is not supported then we should * use the nominal perf value */ - if (CPC_SUPPORTED(reference_reg)) - cpc_read(cpunum, reference_reg, &ref); - else + if (CPC_SUPPORTED(reference_reg)) { + ret = cpc_read(cpunum, reference_reg, &ref); + if (ret) + goto out_err; + } else { ref = nom; + } perf_caps->reference_perf = ref; if (guaranteed_reg->type != ACPI_TYPE_BUFFER || IS_NULL_REG(&guaranteed_reg->cpc_entry.reg)) { perf_caps->guaranteed_perf = 0; } else { - cpc_read(cpunum, guaranteed_reg, &guaranteed); + ret = cpc_read(cpunum, guaranteed_reg, &guaranteed); + if (ret) + goto out_err; perf_caps->guaranteed_perf = guaranteed; } - cpc_read(cpunum, lowest_non_linear_reg, &min_nonlinear); + ret = cpc_read(cpunum, lowest_non_linear_reg, &min_nonlinear); + if (ret) + goto out_err; perf_caps->lowest_nonlinear_perf = min_nonlinear; - if (!high || !low || !nom || !ref || !min_nonlinear) + if (!high || !low || !nom || !ref || !min_nonlinear) { ret = -EFAULT; + goto out_err; + } /* Read optional lowest and nominal frequencies if present */ - if (CPC_SUPPORTED(low_freq_reg)) - cpc_read(cpunum, low_freq_reg, &low_f); + if (CPC_SUPPORTED(low_freq_reg)) { + ret = cpc_read(cpunum, low_freq_reg, &low_f); + if (ret) + goto out_err; + } - if (CPC_SUPPORTED(nom_freq_reg)) - cpc_read(cpunum, nom_freq_reg, &nom_f); + if (CPC_SUPPORTED(nom_freq_reg)) { + ret = cpc_read(cpunum, nom_freq_reg, &nom_f); + if (ret) + goto out_err; + } perf_caps->lowest_freq = low_f; perf_caps->nominal_freq = nom_f; @@ -1526,16 +1547,25 @@ int cppc_get_perf_ctrs(int cpunum, struct cppc_perf_fb_ctrs *perf_fb_ctrs) } } - cpc_read(cpunum, delivered_reg, &delivered); - cpc_read(cpunum, reference_reg, &reference); + ret = cpc_read(cpunum, delivered_reg, &delivered); + if (ret) + goto out_err; + + ret = cpc_read(cpunum, reference_reg, &reference); + if (ret) + goto out_err; + /* * Per spec, if ctr_wrap_time optional register is unsupported, then the * performance counters are assumed to never wrap during the lifetime of * platform */ ctr_wrap_time = (u64)(~((u64)0)); - if (CPC_SUPPORTED(ctr_wrap_reg)) - cpc_read(cpunum, ctr_wrap_reg, &ctr_wrap_time); + if (CPC_SUPPORTED(ctr_wrap_reg)) { + ret = cpc_read(cpunum, ctr_wrap_reg, &ctr_wrap_time); + if (ret) + goto out_err; + } if (!delivered || !reference) { ret = -EFAULT; @@ -1811,24 +1841,39 @@ int cppc_get_perf(int cpu, struct cppc_perf_ctrls *perf_ctrls) } /* Read optional elements if present */ - if (CPC_SUPPORTED(max_perf_reg)) - cpc_read(cpu, max_perf_reg, &max); + if (CPC_SUPPORTED(max_perf_reg)) { + ret = cpc_read(cpu, max_perf_reg, &max); + if (ret) + goto out_err; + } perf_ctrls->max_perf = max; - if (CPC_SUPPORTED(min_perf_reg)) - cpc_read(cpu, min_perf_reg, &min); + if (CPC_SUPPORTED(min_perf_reg)) { + ret = cpc_read(cpu, min_perf_reg, &min); + if (ret) + goto out_err; + } perf_ctrls->min_perf = min; - if (CPC_SUPPORTED(desired_perf_reg)) - cpc_read(cpu, desired_perf_reg, &desired_perf); + if (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; - if (CPC_SUPPORTED(energy_perf_reg)) - cpc_read(cpu, energy_perf_reg, &energy_perf); + if (CPC_SUPPORTED(energy_perf_reg)) { + ret = cpc_read(cpu, energy_perf_reg, &energy_perf); + if (ret) + goto out_err; + } perf_ctrls->energy_perf = energy_perf; - if (CPC_SUPPORTED(auto_sel_reg)) - cpc_read(cpu, auto_sel_reg, &auto_sel); + if (CPC_SUPPORTED(auto_sel_reg)) { + ret = cpc_read(cpu, auto_sel_reg, &auto_sel); + if (ret) + goto out_err; + } perf_ctrls->auto_sel = (bool)auto_sel; out_err: From 97f7d3f9c9acb70d4eef7bcdef3218823d142733 Mon Sep 17 00:00:00 2001 From: Kees Cook Date: Mon, 23 Mar 2026 10:24:52 -0700 Subject: [PATCH 35/73] ACPICA: Replace strncpy() with strscpy_pad() in acpi_ut_safe_strncpy() Replace the deprecated[1] strncpy() with strscpy_pad() in acpi_ut_safe_strncpy(). The function is a "safe strncpy" wrapper that does strncpy(dest, source, dest_size) followed by manual NUL-termination at dest[dest_size - 1]. strscpy_pad() is a direct replacement: it NUL-terminates, zero-pads the remainder, and the manual termination is no longer needed. All callers pass NUL-terminated source strings (C string literals, __FILE__ via ACPI_MODULE_NAME, or user-provided filenames that have already been validated). The destinations are fixed-size char arrays in ACPICA internal structures (allocation->module, aml_op_name, acpi_gbl_db_debug_filename), all consumed as C strings. No behavioral change: strscpy_pad() produces identical output to strncpy() + manual NUL-termination for NUL-terminated sources that are shorter than dest_size. For sources longer than dest_size, strncpy() wrote dest_size non-NUL bytes then the manual termination overwrote the last byte with NUL; strscpy_pad() writes dest_size-1 bytes plus NUL: same result. Link: https://github.com/KSPP/linux/issues/90 [1] Signed-off-by: Kees Cook Link: https://patch.msgid.link/20260323172451.work.079-kees@kernel.org Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpica/utnonansi.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/acpi/acpica/utnonansi.c b/drivers/acpi/acpica/utnonansi.c index ff0802ace19b..3a7952be6545 100644 --- a/drivers/acpi/acpica/utnonansi.c +++ b/drivers/acpi/acpica/utnonansi.c @@ -168,8 +168,7 @@ void acpi_ut_safe_strncpy(char *dest, char *source, acpi_size dest_size) { /* Always terminate destination string */ - strncpy(dest, source, dest_size); - dest[dest_size - 1] = 0; + strscpy_pad(dest, source, dest_size); } #endif From 9b776ddcf3236f860f81f12ba10864ce6e237ff9 Mon Sep 17 00:00:00 2001 From: Ben Horgan Date: Tue, 24 Mar 2026 11:33:00 +0000 Subject: [PATCH 36/73] ACPI: PPTT: Remove duplicate structure, acpi_pptt_cache_v1_full acpi_pptt_cache_v1_full was initially added as a stop gap until the equivalent structure imported from ACPICA, acpi_pptt_v1 in actbl2.h, contained all the fields of the Cache Type Structure. Since commit 091c4af3562d ("ACPICA: ACPI 6.4: PPTT: include all fields in subtable type1"), acpi_pptt_v1 contains all these fields making acpi_pptt_cache_v1_full redundant. Remove acpi_pptt_cache_v1_full. No functional change intended. Signed-off-by: Ben Horgan Reviewed-by: Jeremy Linton Reviewed-by: Sudeep Holla [ rjw: Subject and changelog edits ] Link: https://patch.msgid.link/20260324113300.1002569-1-ben.horgan@arm.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/pptt.c | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/drivers/acpi/pptt.c b/drivers/acpi/pptt.c index de5f8c018333..ea28ba61ddd9 100644 --- a/drivers/acpi/pptt.c +++ b/drivers/acpi/pptt.c @@ -21,25 +21,6 @@ #include #include -/* - * The acpi_pptt_cache_v1 in actbl2.h, which is imported from acpica, - * only contains the cache_id field rather than all the fields of the - * Cache Type Structure. Use this alternative structure until it is - * resolved in acpica. - */ -struct acpi_pptt_cache_v1_full { - struct acpi_subtable_header header; - u16 reserved; - u32 flags; - u32 next_level_of_cache; - u32 size; - u32 number_of_sets; - u8 associativity; - u8 attributes; - u16 line_size; - u32 cache_id; -} __packed; - static struct acpi_subtable_header *fetch_pptt_subtable(struct acpi_table_header *table_hdr, u32 pptt_ref) { @@ -75,16 +56,16 @@ static struct acpi_pptt_cache *fetch_pptt_cache(struct acpi_table_header *table_ return (struct acpi_pptt_cache *)fetch_pptt_subtable(table_hdr, pptt_ref); } -static struct acpi_pptt_cache_v1_full *upgrade_pptt_cache(struct acpi_pptt_cache *cache) +static struct acpi_pptt_cache_v1 *upgrade_pptt_cache(struct acpi_pptt_cache *cache) { - if (cache->header.length < sizeof(struct acpi_pptt_cache_v1_full)) + if (cache->header.length < sizeof(struct acpi_pptt_cache_v1)) return NULL; /* No use for v1 if the only additional field is invalid */ if (!(cache->flags & ACPI_PPTT_CACHE_ID_VALID)) return NULL; - return (struct acpi_pptt_cache_v1_full *)cache; + return (struct acpi_pptt_cache_v1 *)cache; } static struct acpi_subtable_header *acpi_get_pptt_resource(struct acpi_table_header *table_hdr, @@ -397,7 +378,7 @@ static void update_cache_properties(struct cacheinfo *this_leaf, struct acpi_pptt_cache *found_cache, struct acpi_pptt_processor *cpu_node) { - struct acpi_pptt_cache_v1_full *found_cache_v1; + struct acpi_pptt_cache_v1 *found_cache_v1; this_leaf->fw_token = cpu_node; if (found_cache->flags & ACPI_PPTT_SIZE_PROPERTY_VALID) @@ -979,7 +960,7 @@ int find_acpi_cache_level_from_id(u32 cache_id) empty = true; for (int i = 0; i < ARRAY_SIZE(cache_type); i++) { - struct acpi_pptt_cache_v1_full *cache_v1; + struct acpi_pptt_cache_v1 *cache_v1; cache = acpi_find_cache_node(table, acpi_cpu_id, cache_type[i], level, &cpu_node); @@ -1043,7 +1024,7 @@ int acpi_pptt_get_cpumask_from_cache_id(u32 cache_id, cpumask_t *cpus) empty = true; for (int i = 0; i < ARRAY_SIZE(cache_type); i++) { - struct acpi_pptt_cache_v1_full *cache_v1; + struct acpi_pptt_cache_v1 *cache_v1; cache = acpi_find_cache_node(table, acpi_cpu_id, cache_type[i], level, &cpu_node); From 8dcb4485e7672d3abe9fac03b843dff7bb8a1582 Mon Sep 17 00:00:00 2001 From: Nate DeSimone Date: Tue, 24 Mar 2026 16:14:54 -0700 Subject: [PATCH 37/73] ACPI: FPDT: expose FBPT and S3PT subtables via sysfs Add sysfs files at /sys/firmware/acpi/fpdt/FBPT and /sys/firmware/acpi/fpdt/S3PT that expose the raw contents of the FPDT subtables. Note that /sys/firmware/acpi/tables/FPDT only provides the top level table, not the subtables. Adding access to the subtables enables a usage model similar to /sys/firmware/dmi/tables/DMI, allowing userspace tools to interpret newer record types (e.g. String Event Records, Microcontroller Boot Performance Data Records, etc.) defined in recent ACPI specifications [1] without requiring kernel changes. Link: https://uefi.org/specs/ACPI/6.6/05_ACPI_Software_Programming_Model.html#performance-event-record-types [1] Signed-off-by: Nate DeSimone [ rjw: Changelog edits ] Link: https://patch.msgid.link/20260324231456.701-2-nathaniel.l.desimone@intel.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_fpdt.c | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/drivers/acpi/acpi_fpdt.c b/drivers/acpi/acpi_fpdt.c index 271092f2700a..e75dd28d31a9 100644 --- a/drivers/acpi/acpi_fpdt.c +++ b/drivers/acpi/acpi_fpdt.c @@ -141,6 +141,9 @@ static const struct attribute_group boot_attr_group = { .name = "boot", }; +static BIN_ATTR(FBPT, 0400, sysfs_bin_attr_simple_read, NULL, 0); +static BIN_ATTR(S3PT, 0400, sysfs_bin_attr_simple_read, NULL, 0); + static struct kobject *fpdt_kobj; #if defined CONFIG_X86 && defined CONFIG_PHYS_ADDR_T_64BIT @@ -254,9 +257,34 @@ static int fpdt_process_subtable(u64 address, u32 subtable_type) break; } } + + if (subtable_type == SUBTABLE_FBPT) { + bin_attr_FBPT.private = subtable_header; + bin_attr_FBPT.size = length; + result = sysfs_create_bin_file(fpdt_kobj, &bin_attr_FBPT); + if (result) + pr_warn("Failed to create FBPT sysfs attribute.\n"); + } else if (subtable_type == SUBTABLE_S3PT) { + bin_attr_S3PT.private = subtable_header; + bin_attr_S3PT.size = length; + result = sysfs_create_bin_file(fpdt_kobj, &bin_attr_S3PT); + if (result) + pr_warn("Failed to create S3PT sysfs attribute.\n"); + } + return 0; err: + if (bin_attr_FBPT.private) { + sysfs_remove_bin_file(fpdt_kobj, &bin_attr_FBPT); + bin_attr_FBPT.private = NULL; + } + + if (bin_attr_S3PT.private) { + sysfs_remove_bin_file(fpdt_kobj, &bin_attr_S3PT); + bin_attr_S3PT.private = NULL; + } + if (record_boot) sysfs_remove_group(fpdt_kobj, &boot_attr_group); From fad2964471e98c126ac688004b721a5a03064417 Mon Sep 17 00:00:00 2001 From: Nate DeSimone Date: Tue, 24 Mar 2026 16:14:55 -0700 Subject: [PATCH 38/73] Documentation: ABI: add FBPT and S3PT entries to sysfs-firmware-acpi Document the FBPT and S3PT sysfs attributes located under /sys/firmware/acpi/fpdt/. Signed-off-by: Nate DeSimone [ rjw: Changelog tweak ] Link: https://patch.msgid.link/20260324231456.701-3-nathaniel.l.desimone@intel.com Signed-off-by: Rafael J. Wysocki --- Documentation/ABI/testing/sysfs-firmware-acpi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-firmware-acpi b/Documentation/ABI/testing/sysfs-firmware-acpi index 72e7c9161ce7..fa33dda331f2 100644 --- a/Documentation/ABI/testing/sysfs-firmware-acpi +++ b/Documentation/ABI/testing/sysfs-firmware-acpi @@ -41,6 +41,12 @@ Description: platform runtime firmware S3 resume, just prior to handoff to the OS waking vector. In nanoseconds. + FBPT: The raw binary contents of the Firmware Basic Boot + Performance Table (FBPT) subtable. + + S3PT: The raw binary contents of the S3 Performance Table + (S3PT) subtable. + What: /sys/firmware/acpi/bgrt/ Date: January 2012 Contact: Matthew Garrett From db5dab0784a8e8477598b52f3de7bdfbed733b3f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 14 Mar 2026 13:11:20 +0100 Subject: [PATCH 39/73] rtc: cmos: Enable ACPI alarm if advertised in ACPI FADT If the ACPI_FADT_FIXED_RTC flag is unset, the platform is declaring that it supports the ACPI RTC fixed event which should be used instead of a dedicated CMOS RTC IRQ. However, the driver only enables it when is_hpet_enabled() returns true, which is questionable because there is no clear connection between enabled HPET and signaling wakeup via the ACPI RTC fixed event (for instance, the latter can be expected to work on systems that don't include a functional HPET). Moreover, since use_hpet_alarm() returns false if use_acpi_alarm is set, the ACPI RTC fixed event is effectively used instead of the HPET alarm if the latter is functional, but there is no particular reason why it could not be used otherwise. Accordingly, on x86 systems with ACPI, set use_acpi_alarm if ACPI_FADT_FIXED_RTC is unset without looking at whether or not HPET is enabled. Also, do the ACPI FADT check in use_acpi_alarm_quirks() before the DMI BIOS year checks which are more expensive and it's better to skip them if ACPI_FADT_FIXED_RTC is set. Signed-off-by: Rafael J. Wysocki Acked-by: Alexandre Belloni Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/9618535.CDJkKcVGEf@rafael.j.wysocki --- drivers/rtc/rtc-cmos.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index 9ac5bab846c1..221e7525cd3f 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -817,6 +817,9 @@ static void rtc_wake_off(struct device *dev) #ifdef CONFIG_X86 static void use_acpi_alarm_quirks(void) { + if (acpi_gbl_FADT.flags & ACPI_FADT_FIXED_RTC) + return; + switch (boot_cpu_data.x86_vendor) { case X86_VENDOR_INTEL: if (dmi_get_bios_year() < 2015) @@ -830,8 +833,6 @@ static void use_acpi_alarm_quirks(void) default: return; } - if (!is_hpet_enabled()) - return; use_acpi_alarm = true; } From b48ee1f7404a17138785cb3ceb6a95e96b489c0d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 14 Mar 2026 13:12:44 +0100 Subject: [PATCH 40/73] rtc: cmos: Do not require IRQ if ACPI alarm is used If the ACPI RTC fixed event is used, a dedicated IRQ is not required for the CMOS RTC alarm to work, so allow the driver to use the alarm without a valid IRQ in that case. Signed-off-by: Rafael J. Wysocki Acked-by: Alexandre Belloni Reviewed-by: Mario Limonciello (AMD) Link: https://patch.msgid.link/6168746.MhkbZ0Pkbq@rafael.j.wysocki --- drivers/rtc/rtc-cmos.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/drivers/rtc/rtc-cmos.c b/drivers/rtc/rtc-cmos.c index 221e7525cd3f..acc064baefcd 100644 --- a/drivers/rtc/rtc-cmos.c +++ b/drivers/rtc/rtc-cmos.c @@ -216,6 +216,11 @@ static inline void cmos_write_bank2(unsigned char val, unsigned char addr) /*----------------------------------------------------------------*/ +static bool cmos_no_alarm(struct cmos_rtc *cmos) +{ + return !is_valid_irq(cmos->irq) && !cmos_use_acpi_alarm(); +} + static int cmos_read_time(struct device *dev, struct rtc_time *t) { int ret; @@ -287,7 +292,7 @@ static int cmos_read_alarm(struct device *dev, struct rtc_wkalrm *t) }; /* This not only a rtc_op, but also called directly */ - if (!is_valid_irq(cmos->irq)) + if (cmos_no_alarm(cmos)) return -ETIMEDOUT; /* Basic alarms only support hour, minute, and seconds fields. @@ -520,7 +525,7 @@ static int cmos_set_alarm(struct device *dev, struct rtc_wkalrm *t) int ret; /* This not only a rtc_op, but also called directly */ - if (!is_valid_irq(cmos->irq)) + if (cmos_no_alarm(cmos)) return -EIO; ret = cmos_validate_alarm(dev, t); @@ -1096,7 +1101,7 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) dev_dbg(dev, "IRQ %d is already in use\n", rtc_irq); goto cleanup1; } - } else { + } else if (!cmos_use_acpi_alarm()) { clear_bit(RTC_FEATURE_ALARM, cmos_rtc.rtc->features); } @@ -1121,7 +1126,7 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) acpi_rtc_event_setup(dev); dev_info(dev, "%s%s, %d bytes nvram%s\n", - !is_valid_irq(rtc_irq) ? "no alarms" : + cmos_no_alarm(&cmos_rtc) ? "no alarms" : cmos_rtc.mon_alrm ? "alarms up to one year" : cmos_rtc.day_alrm ? "alarms up to one month" : "alarms up to one day", @@ -1147,7 +1152,7 @@ cmos_do_probe(struct device *dev, struct resource *ports, int rtc_irq) static void cmos_do_shutdown(int rtc_irq) { spin_lock_irq(&rtc_lock); - if (is_valid_irq(rtc_irq)) + if (!cmos_no_alarm(&cmos_rtc)) cmos_irq_disable(&cmos_rtc, RTC_IRQMASK); spin_unlock_irq(&rtc_lock); } From 8be4a29b495ec8b1ed1e38a7db2e9160317b1ce8 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:12:22 +0100 Subject: [PATCH 41/73] ACPI: TAD: Create one attribute group Instead of creating three attribute groups, one for each supported subset of capabilities, create just one and use an .is_visible() callback in it to decide which attributes to use. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2412153.ElGaqSPkdT@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 80 ++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 4f5089fc023d..78d0bee4b28a 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -249,14 +249,6 @@ static ssize_t time_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(time); -static struct attribute *acpi_tad_time_attrs[] = { - &dev_attr_time.attr, - NULL, -}; -static const struct attribute_group acpi_tad_time_attr_group = { - .attrs = acpi_tad_time_attrs, -}; - static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, u32 value) { @@ -486,17 +478,6 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(ac_status); -static struct attribute *acpi_tad_attrs[] = { - &dev_attr_caps.attr, - &dev_attr_ac_alarm.attr, - &dev_attr_ac_policy.attr, - &dev_attr_ac_status.attr, - NULL, -}; -static const struct attribute_group acpi_tad_attr_group = { - .attrs = acpi_tad_attrs, -}; - static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { @@ -545,14 +526,44 @@ static ssize_t dc_status_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(dc_status); -static struct attribute *acpi_tad_dc_attrs[] = { +static struct attribute *acpi_tad_attrs[] = { + &dev_attr_caps.attr, + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, &dev_attr_dc_alarm.attr, &dev_attr_dc_policy.attr, &dev_attr_dc_status.attr, + &dev_attr_time.attr, NULL, }; -static const struct attribute_group acpi_tad_dc_attr_group = { - .attrs = acpi_tad_dc_attrs, + +static umode_t acpi_tad_attr_is_visible(struct kobject *kobj, + struct attribute *a, int n) +{ + struct acpi_tad_driver_data *dd = dev_get_drvdata(kobj_to_dev(kobj)); + + if (a == &dev_attr_caps.attr) + return a->mode; + + if (a == &dev_attr_ac_alarm.attr || a == &dev_attr_ac_policy.attr || + a == &dev_attr_ac_status.attr) + return a->mode; + + if ((dd->capabilities & ACPI_TAD_DC_WAKE) && + (a == &dev_attr_dc_alarm.attr || a == &dev_attr_dc_policy.attr || + a == &dev_attr_dc_status.attr)) + return a->mode; + + if ((dd->capabilities & ACPI_TAD_RT) && a == &dev_attr_time.attr) + return a->mode; + + return 0; +} + +static const struct attribute_group acpi_tad_attr_group = { + .attrs = acpi_tad_attrs, + .is_visible = acpi_tad_attr_is_visible, }; static int acpi_tad_disable_timer(struct device *dev, u32 timer_id) @@ -567,12 +578,6 @@ static void acpi_tad_remove(struct platform_device *pdev) device_init_wakeup(dev, false); - if (dd->capabilities & ACPI_TAD_RT) - sysfs_remove_group(&dev->kobj, &acpi_tad_time_attr_group); - - if (dd->capabilities & ACPI_TAD_DC_WAKE) - sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); - sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); scoped_guard(pm_runtime_noresume, dev) { @@ -633,6 +638,7 @@ static int acpi_tad_probe(struct platform_device *pdev) device_init_wakeup(dev, true); dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND | DPM_FLAG_MAY_SKIP_RESUME); + /* * The platform bus type layer tells the ACPI PM domain powers up the * device, so set the runtime PM status of it to "active". @@ -643,24 +649,8 @@ static int acpi_tad_probe(struct platform_device *pdev) ret = sysfs_create_group(&dev->kobj, &acpi_tad_attr_group); if (ret) - goto fail; + acpi_tad_remove(pdev); - if (caps & ACPI_TAD_DC_WAKE) { - ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); - if (ret) - goto fail; - } - - if (caps & ACPI_TAD_RT) { - ret = sysfs_create_group(&dev->kobj, &acpi_tad_time_attr_group); - if (ret) - goto fail; - } - - return 0; - -fail: - acpi_tad_remove(pdev); return ret; } From 6c711fde3a1ce00165e3423eb8b7fa7888f0f313 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:13:15 +0100 Subject: [PATCH 42/73] ACPI: TAD: Support RTC without wakeup The ACPI TAD can provide a functional RTC without wakeup capabilities, so stop failing probe if AC wakeup is not supported. Also, if _PRW is missing, do not fail probe, but clear the wakeup bits in capabilities. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/1959268.tdWV9SEqCh@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 78d0bee4b28a..560ed6495daf 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -546,8 +546,9 @@ static umode_t acpi_tad_attr_is_visible(struct kobject *kobj, if (a == &dev_attr_caps.attr) return a->mode; - if (a == &dev_attr_ac_alarm.attr || a == &dev_attr_ac_policy.attr || - a == &dev_attr_ac_status.attr) + if ((dd->capabilities & ACPI_TAD_AC_WAKE) && + (a == &dev_attr_ac_alarm.attr || a == &dev_attr_ac_policy.attr || + a == &dev_attr_ac_status.attr)) return a->mode; if ((dd->capabilities & ACPI_TAD_DC_WAKE) && @@ -581,8 +582,10 @@ static void acpi_tad_remove(struct platform_device *pdev) sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); scoped_guard(pm_runtime_noresume, dev) { - acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); - acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); + if (dd->capabilities & ACPI_TAD_AC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); + } if (dd->capabilities & ACPI_TAD_DC_WAKE) { acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); @@ -612,14 +615,9 @@ static int acpi_tad_probe(struct platform_device *pdev) return -ENODEV; } - if (!(caps & ACPI_TAD_AC_WAKE)) { - dev_info(dev, "Unsupported capabilities\n"); - return -ENODEV; - } - if (!acpi_has_method(handle, "_PRW")) { dev_info(dev, "Missing _PRW\n"); - return -ENODEV; + caps &= ~(ACPI_TAD_AC_WAKE | ACPI_TAD_DC_WAKE); } dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); @@ -635,9 +633,11 @@ static int acpi_tad_probe(struct platform_device *pdev) * runtime suspend. Everything else should be taken care of by the ACPI * PM domain callbacks. */ - device_init_wakeup(dev, true); - dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND | - DPM_FLAG_MAY_SKIP_RESUME); + if (ACPI_TAD_AC_WAKE | ACPI_TAD_DC_WAKE) { + device_init_wakeup(dev, true); + dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND | + DPM_FLAG_MAY_SKIP_RESUME); + } /* * The platform bus type layer tells the ACPI PM domain powers up the From e64ab3e217cf5e16301a73d5a5230265454b15c1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:14:01 +0100 Subject: [PATCH 43/73] ACPI: TAD: Use __free() for cleanup in time_store() Use __free() for the automatic freeing of memory pointed to by local variable str in time_store() which allows the code to become somewhat easier to follow. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/13971300.uLZWGnKmhe@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 560ed6495daf..eae8ee42e23f 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -167,57 +167,57 @@ static ssize_t time_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct acpi_tad_rt rt; - char *str, *s; - int val, ret = -ENODATA; + int val, ret; + char *s; - str = kmemdup_nul(buf, count, GFP_KERNEL); + char *str __free(kfree) = kmemdup_nul(buf, count, GFP_KERNEL); if (!str) return -ENOMEM; s = acpi_tad_rt_next_field(str, &val); if (!s) - goto out_free; + return -ENODATA; rt.year = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.month = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.day = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.hour = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.minute = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.second = val; s = acpi_tad_rt_next_field(s, &val); if (!s) - goto out_free; + return -ENODATA; rt.tz = val; if (kstrtoint(s, 10, &val)) - goto out_free; + return -ENODATA; rt.daylight = val; @@ -226,10 +226,10 @@ static ssize_t time_store(struct device *dev, struct device_attribute *attr, memset(rt.padding, 0, 3); ret = acpi_tad_set_real_time(dev, &rt); + if (ret) + return ret; -out_free: - kfree(str); - return ret ? ret : count; + return count; } static ssize_t time_show(struct device *dev, struct device_attribute *attr, From 3329a1416c3350449081ca5daaa94802a65b2992 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:14:44 +0100 Subject: [PATCH 44/73] ACPI: TAD: Rearrange RT data validation checking Move RT data validation checks from acpi_tad_set_real_time() to a separate function called acpi_tad_rt_is_invalid() and use it also in acpi_tad_get_real_time() to validate data coming from the platform firmware. Also make acpi_tad_set_real_time() return -EINVAL when the RT data passed to it is invalid (instead of -ERANGE which is somewhat confusing) and introduce ACPI_TAD_TZ_UNSPEC to represent the "unspecified timezone" value. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/3409319.aeNJFYEL58@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index eae8ee42e23f..240e5d53c4a5 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -49,6 +49,9 @@ MODULE_AUTHOR("Rafael J. Wysocki"); /* Special value for disabled timer or expired timer wake policy. */ #define ACPI_TAD_WAKE_DISABLED (~(u32)0) +/* ACPI TAD RTC */ +#define ACPI_TAD_TZ_UNSPEC 2047 + struct acpi_tad_driver_data { u32 capabilities; }; @@ -67,6 +70,16 @@ struct acpi_tad_rt { u8 padding[3]; /* must be 0 */ } __packed; +static bool acpi_tad_rt_is_invalid(struct acpi_tad_rt *rt) +{ + return rt->year < 1900 || rt->year > 9999 || + rt->month < 1 || rt->month > 12 || + rt->hour > 23 || rt->minute > 59 || rt->second > 59 || + rt->tz < -1440 || + (rt->tz > 1440 && rt->tz != ACPI_TAD_TZ_UNSPEC) || + rt->daylight > 3; +} + static int acpi_tad_set_real_time(struct device *dev, struct acpi_tad_rt *rt) { acpi_handle handle = ACPI_HANDLE(dev); @@ -80,12 +93,8 @@ static int acpi_tad_set_real_time(struct device *dev, struct acpi_tad_rt *rt) unsigned long long retval; acpi_status status; - if (rt->year < 1900 || rt->year > 9999 || - rt->month < 1 || rt->month > 12 || - rt->hour > 23 || rt->minute > 59 || rt->second > 59 || - rt->tz < -1440 || (rt->tz > 1440 && rt->tz != 2047) || - rt->daylight > 3) - return -ERANGE; + if (acpi_tad_rt_is_invalid(rt)) + return -EINVAL; args[0].buffer.pointer = (u8 *)rt; args[0].buffer.length = sizeof(*rt); @@ -145,6 +154,9 @@ static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) if (ret) return ret; + if (acpi_tad_rt_is_invalid(rt)) + return -ENODATA; + return 0; } From fca4f233a247986db24b451682c3c6d52be4ff45 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:15:21 +0100 Subject: [PATCH 45/73] ACPI: TAD: Clear unused RT data in acpi_tad_set_real_time() Move the clearing of the fields in struct acpi_tad_rt that are not used on the real time setting side to acpi_tad_set_real_time(). No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/8660506.T7Z3S40VBb@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 240e5d53c4a5..b570e1bda236 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -96,6 +96,10 @@ static int acpi_tad_set_real_time(struct device *dev, struct acpi_tad_rt *rt) if (acpi_tad_rt_is_invalid(rt)) return -EINVAL; + rt->valid = 0; + rt->msec = 0; + memset(rt->padding, 0, 3); + args[0].buffer.pointer = (u8 *)rt; args[0].buffer.length = sizeof(*rt); @@ -233,10 +237,6 @@ static ssize_t time_store(struct device *dev, struct device_attribute *attr, rt.daylight = val; - rt.valid = 0; - rt.msec = 0; - memset(rt.padding, 0, 3); - ret = acpi_tad_set_real_time(dev, &rt); if (ret) return ret; From 8a1e7f4b176401ae015516a3cf4a71af3615f7c2 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:16:01 +0100 Subject: [PATCH 46/73] ACPI: TAD: Add RTC class device interface Add an RTC class device interface allowing to read and set the real time value to the ACPI TAD driver. Signed-off-by: Rafael J. Wysocki Reviewed-by: Alexandre Belloni Link: https://patch.msgid.link/2352027.iZASKD2KPV@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 78 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index b570e1bda236..395983b270ba 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -25,6 +25,7 @@ #include #include #include +#include #include MODULE_DESCRIPTION("ACPI Time and Alarm (TAD) Device Driver"); @@ -51,6 +52,7 @@ MODULE_AUTHOR("Rafael J. Wysocki"); /* ACPI TAD RTC */ #define ACPI_TAD_TZ_UNSPEC 2047 +#define ACPI_TAD_TIME_ISDST 3 struct acpi_tad_driver_data { u32 capabilities; @@ -164,6 +166,8 @@ static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) return 0; } +/* sysfs interface */ + static char *acpi_tad_rt_next_field(char *s, int *val) { char *p; @@ -579,6 +583,71 @@ static const struct attribute_group acpi_tad_attr_group = { .is_visible = acpi_tad_attr_is_visible, }; +#ifdef CONFIG_RTC_CLASS +/* RTC class device interface */ + +static int acpi_tad_rtc_set_time(struct device *dev, struct rtc_time *tm) +{ + struct acpi_tad_rt rt; + + rt.year = tm->tm_year + 1900; + rt.month = tm->tm_mon + 1; + rt.day = tm->tm_mday; + rt.hour = tm->tm_hour; + rt.minute = tm->tm_min; + rt.second = tm->tm_sec; + rt.tz = ACPI_TAD_TZ_UNSPEC; + rt.daylight = ACPI_TAD_TIME_ISDST * !!tm->tm_isdst; + + return acpi_tad_set_real_time(dev, &rt); +} + +static int acpi_tad_rtc_read_time(struct device *dev, struct rtc_time *tm) +{ + struct acpi_tad_rt rt; + int ret; + + ret = acpi_tad_get_real_time(dev, &rt); + if (ret) + return ret; + + tm->tm_year = rt.year - 1900; + tm->tm_mon = rt.month - 1; + tm->tm_mday = rt.day; + tm->tm_hour = rt.hour; + tm->tm_min = rt.minute; + tm->tm_sec = rt.second; + tm->tm_isdst = rt.daylight == ACPI_TAD_TIME_ISDST; + + return 0; +} + +static const struct rtc_class_ops acpi_tad_rtc_ops = { + .read_time = acpi_tad_rtc_read_time, + .set_time = acpi_tad_rtc_set_time, +}; + +static void acpi_tad_register_rtc(struct device *dev) +{ + struct rtc_device *rtc; + + rtc = devm_rtc_allocate_device(dev); + if (IS_ERR(rtc)) + return; + + rtc->range_min = mktime64(1900, 1, 1, 0, 0, 0); + rtc->range_max = mktime64(9999, 12, 31, 23, 59, 59); + + rtc->ops = &acpi_tad_rtc_ops; + + devm_rtc_register_device(rtc); +} +#else /* !CONFIG_RTC_CLASS */ +static inline void acpi_tad_register_rtc(struct device *dev) {} +#endif /* !CONFIG_RTC_CLASS */ + +/* Platform driver interface */ + static int acpi_tad_disable_timer(struct device *dev, u32 timer_id) { return acpi_tad_wake_set(dev, "_STV", timer_id, ACPI_TAD_WAKE_DISABLED); @@ -660,10 +729,15 @@ static int acpi_tad_probe(struct platform_device *pdev) pm_runtime_suspend(dev); ret = sysfs_create_group(&dev->kobj, &acpi_tad_attr_group); - if (ret) + if (ret) { acpi_tad_remove(pdev); + return ret; + } - return ret; + if (caps & ACPI_TAD_RT) + acpi_tad_register_rtc(dev); + + return 0; } static const struct acpi_device_id acpi_tad_ids[] = { From 6db176eb41ad51e48a97431c9fbc0680aa338e5f Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 4 Mar 2026 19:16:48 +0100 Subject: [PATCH 47/73] ACPI: TAD: Update the driver description comment Update the preamble comment describing the driver to match the code after previous changes along with the copyright information. No functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/23034847.EfDdHjke4D@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 395983b270ba..f1db2807f59c 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -2,12 +2,10 @@ /* * ACPI Time and Alarm (TAD) Device Driver * - * Copyright (C) 2018 Intel Corporation + * Copyright (C) 2018 - 2026 Intel Corporation * Author: Rafael J. Wysocki * - * This driver is based on Section 9.18 of the ACPI 6.2 specification revision. - * - * It only supports the system wakeup capabilities of the TAD. + * This driver is based on ACPI 6.6, Section 9.17. * * Provided are sysfs attributes, available under the TAD platform device, * allowing user space to manage the AC and DC wakeup timers of the TAD: @@ -18,6 +16,11 @@ * * The wakeup events handling and power management of the TAD is expected to * be taken care of by the ACPI PM domain attached to its platform device. + * + * If the TAD supports the get/set real time features, as indicated by the + * capability mask returned by _GCP under the TAD object, additional sysfs + * attributes are created allowing the real time to be set and read and an RTC + * class device is registered under the TAD platform device. */ #include @@ -32,7 +35,7 @@ MODULE_DESCRIPTION("ACPI Time and Alarm (TAD) Device Driver"); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Rafael J. Wysocki"); -/* ACPI TAD capability flags (ACPI 6.2, Section 9.18.2) */ +/* ACPI TAD capability flags (ACPI 6.6, Section 9.17.2) */ #define ACPI_TAD_AC_WAKE BIT(0) #define ACPI_TAD_DC_WAKE BIT(1) #define ACPI_TAD_RT BIT(2) From 93afe8ba9b0179b8e42777dad530db3ad9c9e835 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 10 Mar 2026 21:29:29 +0100 Subject: [PATCH 48/73] ACPI: TAD: Use dev_groups in struct device_driver Instead of creating and removing the device sysfs attributes directly during probe and remove of the driver, respectively, use dev_groups in struct device_driver to point to the attribute definitions and let the core take care of creating and removing them. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2836803.mvXUDI8C0e@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index f1db2807f59c..d67c43edf855 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -586,6 +586,11 @@ static const struct attribute_group acpi_tad_attr_group = { .is_visible = acpi_tad_attr_is_visible, }; +static const struct attribute_group *acpi_tad_attr_groups[] = { + &acpi_tad_attr_group, + NULL, +}; + #ifdef CONFIG_RTC_CLASS /* RTC class device interface */ @@ -663,8 +668,6 @@ static void acpi_tad_remove(struct platform_device *pdev) device_init_wakeup(dev, false); - sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); - scoped_guard(pm_runtime_noresume, dev) { if (dd->capabilities & ACPI_TAD_AC_WAKE) { acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); @@ -687,7 +690,6 @@ static int acpi_tad_probe(struct platform_device *pdev) struct acpi_tad_driver_data *dd; acpi_status status; unsigned long long caps; - int ret; /* * Initialization failure messages are mostly about firmware issues, so @@ -731,12 +733,6 @@ static int acpi_tad_probe(struct platform_device *pdev) pm_runtime_enable(dev); pm_runtime_suspend(dev); - ret = sysfs_create_group(&dev->kobj, &acpi_tad_attr_group); - if (ret) { - acpi_tad_remove(pdev); - return ret; - } - if (caps & ACPI_TAD_RT) acpi_tad_register_rtc(dev); @@ -752,6 +748,7 @@ static struct platform_driver acpi_tad_driver = { .driver = { .name = "acpi-tad", .acpi_match_table = acpi_tad_ids, + .dev_groups = acpi_tad_attr_groups, }, .probe = acpi_tad_probe, .remove = acpi_tad_remove, From f706a63fd82242a21cb5248b76c21d3c302a4b54 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Mon, 23 Mar 2026 16:26:37 +0100 Subject: [PATCH 49/73] ACPI: TAD: Use DC wakeup only if AC wakeup is supported According to Section 9.17.2 of ACPI 6.6 [1], setting ACPI_TAD_DC_WAKE in the capabilities without setting ACPI_TAD_AC_WAKE is invalid, so don't support wakeup if that's the case. Moreover, it is sufficient to check ACPI_TAD_AC_WAKE alone to determine if wakeup is supported at all, so use this observation to simplify one check. Link: https://uefi.org/specs/ACPI/6.6/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#gcp-get-capability [1] Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2833494.mvXUDI8C0e@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index d67c43edf855..8b43d883125e 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -706,6 +706,9 @@ static int acpi_tad_probe(struct platform_device *pdev) caps &= ~(ACPI_TAD_AC_WAKE | ACPI_TAD_DC_WAKE); } + if (!(caps & ACPI_TAD_AC_WAKE)) + caps &= ~ACPI_TAD_DC_WAKE; + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); if (!dd) return -ENOMEM; @@ -719,7 +722,7 @@ static int acpi_tad_probe(struct platform_device *pdev) * runtime suspend. Everything else should be taken care of by the ACPI * PM domain callbacks. */ - if (ACPI_TAD_AC_WAKE | ACPI_TAD_DC_WAKE) { + if (ACPI_TAD_AC_WAKE) { device_init_wakeup(dev, true); dev_pm_set_driver_flags(dev, DPM_FLAG_SMART_SUSPEND | DPM_FLAG_MAY_SKIP_RESUME); From 00154eede77616d5c95206728ee36a1e5bbe52e1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 31 Mar 2026 21:09:42 +0200 Subject: [PATCH 50/73] ACPI: processor: Rearrange and clean up acpi_processor_errata_piix4() In acpi_processor_errata_piix4() it is not necessary to use three struct pci_dev pointers. One is sufficient, so use it everywhere and drop the other two. Additionally, define the auxiliary local variables value1 and value2 in the code block in which they are used. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/2846888.mvXUDI8C0e@rafael.j.wysocki --- drivers/acpi/acpi_processor.c | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/drivers/acpi/acpi_processor.c b/drivers/acpi/acpi_processor.c index b1652cab631a..2afe798d1586 100644 --- a/drivers/acpi/acpi_processor.c +++ b/drivers/acpi/acpi_processor.c @@ -48,11 +48,6 @@ acpi_handle acpi_get_processor_handle(int cpu) static int acpi_processor_errata_piix4(struct pci_dev *dev) { - u8 value1 = 0; - u8 value2 = 0; - struct pci_dev *ide_dev = NULL, *isa_dev = NULL; - - if (!dev) return -EINVAL; @@ -108,16 +103,16 @@ static int acpi_processor_errata_piix4(struct pci_dev *dev) * each IDE controller's DMA status to make sure we catch all * DMA activity. */ - ide_dev = pci_get_subsys(PCI_VENDOR_ID_INTEL, + dev = pci_get_subsys(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB, PCI_ANY_ID, PCI_ANY_ID, NULL); - if (ide_dev) { - errata.piix4.bmisx = pci_resource_start(ide_dev, 4); + if (dev) { + errata.piix4.bmisx = pci_resource_start(dev, 4); if (errata.piix4.bmisx) - dev_dbg(&ide_dev->dev, + dev_dbg(&dev->dev, "Bus master activity detection (BM-IDE) erratum enabled\n"); - pci_dev_put(ide_dev); + pci_dev_put(dev); } /* @@ -129,18 +124,20 @@ static int acpi_processor_errata_piix4(struct pci_dev *dev) * disable C3 support if this is enabled, as some legacy * devices won't operate well if fast DMA is disabled. */ - isa_dev = pci_get_subsys(PCI_VENDOR_ID_INTEL, + dev = pci_get_subsys(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_0, PCI_ANY_ID, PCI_ANY_ID, NULL); - if (isa_dev) { - pci_read_config_byte(isa_dev, 0x76, &value1); - pci_read_config_byte(isa_dev, 0x77, &value2); + if (dev) { + u8 value1 = 0, value2 = 0; + + pci_read_config_byte(dev, 0x76, &value1); + pci_read_config_byte(dev, 0x77, &value2); if ((value1 & 0x80) || (value2 & 0x80)) { errata.piix4.fdma = 1; - dev_dbg(&isa_dev->dev, + dev_dbg(&dev->dev, "Type-F DMA livelock erratum (C3 disabled)\n"); } - pci_dev_put(isa_dev); + pci_dev_put(dev); } break; From b14b77bbea0a82cb66f1538824783e63d4128510 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 31 Mar 2026 21:24:44 +0200 Subject: [PATCH 51/73] ACPI: TAD: Split three functions to untangle runtime PM handling Move the core functionality of acpi_tad_get_real_time(), acpi_tad_wake_set(), and acpi_tad_wake_read() into separate functions called __acpi_tad_get_real_time(), __acpi_tad_wake_set(), and __acpi_tad_wake_read(), respectively, which can be called from code blocks following a single runtime resume of the device. This will facilitate adding alarm support to the RTC class device interface of the driver going forward. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Alexandre Belloni Link: https://patch.msgid.link/23076728.EfDdHjke4D@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 57 +++++++++++++++++++++++++++++------------ 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 8b43d883125e..7b422904e45b 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -151,14 +151,10 @@ static int acpi_tad_evaluate_grt(struct device *dev, struct acpi_tad_rt *rt) return ret; } -static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) +static int __acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) { int ret; - PM_RUNTIME_ACQUIRE(dev, pm); - if (PM_RUNTIME_ACQUIRE_ERR(&pm)) - return -ENXIO; - ret = acpi_tad_evaluate_grt(dev, rt); if (ret) return ret; @@ -169,6 +165,15 @@ static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) return 0; } +static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) +{ + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + return __acpi_tad_get_real_time(dev, rt); +} + /* sysfs interface */ static char *acpi_tad_rt_next_field(char *s, int *val) @@ -268,8 +273,8 @@ static ssize_t time_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(time); -static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, - u32 value) +static int __acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, + u32 value) { acpi_handle handle = ACPI_HANDLE(dev); union acpi_object args[] = { @@ -286,10 +291,6 @@ 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; - PM_RUNTIME_ACQUIRE(dev, pm); - if (PM_RUNTIME_ACQUIRE_ERR(&pm)) - return -ENXIO; - status = acpi_evaluate_integer(handle, method, &arg_list, &retval); if (ACPI_FAILURE(status) || retval) return -EIO; @@ -297,6 +298,16 @@ static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, return 0; } +static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, + u32 value) +{ + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + return __acpi_tad_wake_set(dev, method, timer_id, value); +} + static int acpi_tad_wake_write(struct device *dev, const char *buf, char *method, u32 timer_id, const char *specval) { @@ -317,8 +328,8 @@ static int acpi_tad_wake_write(struct device *dev, const char *buf, char *method return acpi_tad_wake_set(dev, method, timer_id, value); } -static ssize_t acpi_tad_wake_read(struct device *dev, char *buf, char *method, - u32 timer_id, const char *specval) +static int __acpi_tad_wake_read(struct device *dev, char *method, u32 timer_id, + unsigned long long *retval) { acpi_handle handle = ACPI_HANDLE(dev); union acpi_object args[] = { @@ -328,18 +339,30 @@ static ssize_t acpi_tad_wake_read(struct device *dev, char *buf, char *method, .pointer = args, .count = ARRAY_SIZE(args), }; - unsigned long long retval; acpi_status status; args[0].integer.value = timer_id; + status = acpi_evaluate_integer(handle, method, &arg_list, retval); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + +static ssize_t acpi_tad_wake_read(struct device *dev, char *buf, char *method, + u32 timer_id, const char *specval) +{ + unsigned long long retval; + int ret; + PM_RUNTIME_ACQUIRE(dev, pm); if (PM_RUNTIME_ACQUIRE_ERR(&pm)) return -ENXIO; - status = acpi_evaluate_integer(handle, method, &arg_list, &retval); - if (ACPI_FAILURE(status)) - return -EIO; + ret = __acpi_tad_wake_read(dev, method, timer_id, &retval); + if (ret) + return ret; if ((u32)retval == ACPI_TAD_WAKE_DISABLED) return sprintf(buf, "%s\n", specval); From 9bcdd4ef4cd24ff360c6d0df3b8efad35d82bd94 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 31 Mar 2026 21:25:40 +0200 Subject: [PATCH 52/73] ACPI: TAD: Relocate two functions Move two functions introduced previously, __acpi_tad_wake_set() and __acpi_tad_wake_read(), to the part of the code preceding the sysfs interface implementation, since subsequently they will be used by the RTC device interface too. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Alexandre Belloni Link: https://patch.msgid.link/3960639.kQq0lBPeGt@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 94 ++++++++++++++++++++--------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 7b422904e45b..a0857a7904df 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -174,6 +174,53 @@ static int acpi_tad_get_real_time(struct device *dev, struct acpi_tad_rt *rt) return __acpi_tad_get_real_time(dev, rt); } +static int __acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, + u32 value) +{ + acpi_handle handle = ACPI_HANDLE(dev); + union acpi_object args[] = { + { .type = ACPI_TYPE_INTEGER, }, + { .type = ACPI_TYPE_INTEGER, }, + }; + struct acpi_object_list arg_list = { + .pointer = args, + .count = ARRAY_SIZE(args), + }; + unsigned long long retval; + acpi_status status; + + args[0].integer.value = timer_id; + args[1].integer.value = value; + + status = acpi_evaluate_integer(handle, method, &arg_list, &retval); + if (ACPI_FAILURE(status) || retval) + return -EIO; + + return 0; +} + +static int __acpi_tad_wake_read(struct device *dev, char *method, u32 timer_id, + unsigned long long *retval) +{ + acpi_handle handle = ACPI_HANDLE(dev); + union acpi_object args[] = { + { .type = ACPI_TYPE_INTEGER, }, + }; + struct acpi_object_list arg_list = { + .pointer = args, + .count = ARRAY_SIZE(args), + }; + acpi_status status; + + args[0].integer.value = timer_id; + + status = acpi_evaluate_integer(handle, method, &arg_list, retval); + if (ACPI_FAILURE(status)) + return -EIO; + + return 0; +} + /* sysfs interface */ static char *acpi_tad_rt_next_field(char *s, int *val) @@ -273,31 +320,6 @@ static ssize_t time_show(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(time); -static int __acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, - u32 value) -{ - acpi_handle handle = ACPI_HANDLE(dev); - union acpi_object args[] = { - { .type = ACPI_TYPE_INTEGER, }, - { .type = ACPI_TYPE_INTEGER, }, - }; - struct acpi_object_list arg_list = { - .pointer = args, - .count = ARRAY_SIZE(args), - }; - unsigned long long retval; - acpi_status status; - - args[0].integer.value = timer_id; - args[1].integer.value = value; - - status = acpi_evaluate_integer(handle, method, &arg_list, &retval); - if (ACPI_FAILURE(status) || retval) - return -EIO; - - return 0; -} - static int acpi_tad_wake_set(struct device *dev, char *method, u32 timer_id, u32 value) { @@ -328,28 +350,6 @@ static int acpi_tad_wake_write(struct device *dev, const char *buf, char *method return acpi_tad_wake_set(dev, method, timer_id, value); } -static int __acpi_tad_wake_read(struct device *dev, char *method, u32 timer_id, - unsigned long long *retval) -{ - acpi_handle handle = ACPI_HANDLE(dev); - union acpi_object args[] = { - { .type = ACPI_TYPE_INTEGER, }, - }; - struct acpi_object_list arg_list = { - .pointer = args, - .count = ARRAY_SIZE(args), - }; - acpi_status status; - - args[0].integer.value = timer_id; - - status = acpi_evaluate_integer(handle, method, &arg_list, retval); - if (ACPI_FAILURE(status)) - return -EIO; - - return 0; -} - static ssize_t acpi_tad_wake_read(struct device *dev, char *buf, char *method, u32 timer_id, const char *specval) { From 2ffc8bf29e4d7dff0e6c94f245d5a757be6c013d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 31 Mar 2026 21:26:23 +0200 Subject: [PATCH 53/73] ACPI: TAD: Split acpi_tad_rtc_read_time() Move the code converting a struct acpi_tad_rt into a struct rtc_time from acpi_tad_rtc_read_time() into a new function, acpi_tad_rt_to_tm(), to facilitate adding alarm support to the driver's RTC class device interface going forward. No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Alexandre Belloni [ rjw: Subject and changelog edits ] Link: https://patch.msgid.link/9619488.CDJkKcVGEf@rafael.j.wysocki Signed-off-by: Rafael J. Wysocki --- drivers/acpi/acpi_tad.c | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index a0857a7904df..8bb71396f99c 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -617,6 +617,17 @@ static const struct attribute_group *acpi_tad_attr_groups[] = { #ifdef CONFIG_RTC_CLASS /* RTC class device interface */ +static void acpi_tad_rt_to_tm(struct acpi_tad_rt *rt, struct rtc_time *tm) +{ + tm->tm_year = rt->year - 1900; + tm->tm_mon = rt->month - 1; + tm->tm_mday = rt->day; + tm->tm_hour = rt->hour; + tm->tm_min = rt->minute; + tm->tm_sec = rt->second; + tm->tm_isdst = rt->daylight == ACPI_TAD_TIME_ISDST; +} + static int acpi_tad_rtc_set_time(struct device *dev, struct rtc_time *tm) { struct acpi_tad_rt rt; @@ -642,13 +653,7 @@ static int acpi_tad_rtc_read_time(struct device *dev, struct rtc_time *tm) if (ret) return ret; - tm->tm_year = rt.year - 1900; - tm->tm_mon = rt.month - 1; - tm->tm_mday = rt.day; - tm->tm_hour = rt.hour; - tm->tm_min = rt.minute; - tm->tm_sec = rt.second; - tm->tm_isdst = rt.daylight == ACPI_TAD_TIME_ISDST; + acpi_tad_rt_to_tm(&rt, tm); return 0; } From 7572dcabe38d904dd501e652b504a9ad364ba1cc Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Tue, 31 Mar 2026 21:38:52 +0200 Subject: [PATCH 54/73] ACPI: TAD: Add alarm support to the RTC class device interface Add alarm support, based on Section 9.17 of ACPI 6.6 [1], to the RTC class device interface of the driver. The ACPI time and alarm device (TAD) can support two separate alarm timers, one for waking up the system when it is on AC power, and one for waking it up when it is on DC power. In principle, each of them can be set to a different value representing the number of seconds till the given alarm timer expires. However, the RTC class device can only set one alarm, so it will set both the alarm timers of the ACPI TAD (if the DC one is supported) to the same value. That is somewhat cumbersome because there is no way in the ACPI TAD firmware interface to set both timers in one go, so they need to be set sequentially, but that's how it goes. On the alarm read side, the driver assumes that both timers have been set to the same value, so it is sufficient to access one of them (the AC one specifically). Link: https://uefi.org/specs/ACPI/6.6/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device [1] Signed-off-by: Rafael J. Wysocki Reviewed-by: Alexandre Belloni Link: https://patch.msgid.link/2076980.usQuhbGJ8B@rafael.j.wysocki --- drivers/acpi/acpi_tad.c | 112 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c index 8bb71396f99c..b406d7a98996 100644 --- a/drivers/acpi/acpi_tad.c +++ b/drivers/acpi/acpi_tad.c @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -658,12 +659,113 @@ static int acpi_tad_rtc_read_time(struct device *dev, struct rtc_time *tm) return 0; } +static int acpi_tad_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *t) +{ + struct acpi_tad_driver_data *dd = dev_get_drvdata(dev); + s64 value = ACPI_TAD_WAKE_DISABLED; + struct rtc_time tm_now; + struct acpi_tad_rt rt; + int ret; + + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + if (t->enabled) { + /* + * The value to pass to _STV is expected to be the number of + * seconds between the time when the timer is programmed and the + * time when it expires represented as a 32-bit integer. + */ + ret = __acpi_tad_get_real_time(dev, &rt); + if (ret) + return ret; + + acpi_tad_rt_to_tm(&rt, &tm_now); + + value = ktime_divns(ktime_sub(rtc_tm_to_ktime(t->time), + rtc_tm_to_ktime(tm_now)), NSEC_PER_SEC); + if (value <= 0 || value > U32_MAX) + return -EINVAL; + } + + ret = __acpi_tad_wake_set(dev, "_STV", ACPI_TAD_AC_TIMER, value); + if (ret && t->enabled) + return ret; + + /* + * If a separate DC alarm timer is supported, set it to the same value + * as the AC alarm timer. + */ + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + ret = __acpi_tad_wake_set(dev, "_STV", ACPI_TAD_DC_TIMER, value); + if (ret && t->enabled) { + __acpi_tad_wake_set(dev, "_STV", ACPI_TAD_AC_TIMER, + ACPI_TAD_WAKE_DISABLED); + return ret; + } + } + + /* Assume success if the alarm is being disabled. */ + return 0; +} + +static int acpi_tad_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *t) +{ + unsigned long long retval; + struct rtc_time tm_now; + struct acpi_tad_rt rt; + int ret; + + PM_RUNTIME_ACQUIRE(dev, pm); + if (PM_RUNTIME_ACQUIRE_ERR(&pm)) + return -ENXIO; + + ret = __acpi_tad_get_real_time(dev, &rt); + if (ret) + return ret; + + acpi_tad_rt_to_tm(&rt, &tm_now); + + /* + * Assume that the alarm was set by acpi_tad_rtc_set_alarm(), so the AC + * and DC alarm timer settings are the same and it is sufficient to read + * the former. + * + * The value returned by _TIV should be the number of seconds till the + * expiration of the timer, represented as a 32-bit integer, or the + * special ACPI_TAD_WAKE_DISABLED value meaning that the timer has + * been disabled. + */ + ret = __acpi_tad_wake_read(dev, "_TIV", ACPI_TAD_AC_TIMER, &retval); + if (ret) + return ret; + + if (retval > U32_MAX) + return -ENODATA; + + t->pending = 0; + + if (retval != ACPI_TAD_WAKE_DISABLED) { + t->enabled = 1; + t->time = rtc_ktime_to_tm(ktime_add_ns(rtc_tm_to_ktime(tm_now), + (u64)retval * NSEC_PER_SEC)); + } else { + t->enabled = 0; + t->time = tm_now; + } + + return 0; +} + static const struct rtc_class_ops acpi_tad_rtc_ops = { .read_time = acpi_tad_rtc_read_time, .set_time = acpi_tad_rtc_set_time, + .set_alarm = acpi_tad_rtc_set_alarm, + .read_alarm = acpi_tad_rtc_read_alarm, }; -static void acpi_tad_register_rtc(struct device *dev) +static void acpi_tad_register_rtc(struct device *dev, unsigned long long caps) { struct rtc_device *rtc; @@ -676,10 +778,14 @@ static void acpi_tad_register_rtc(struct device *dev) rtc->ops = &acpi_tad_rtc_ops; + if (!(caps & ACPI_TAD_AC_WAKE)) + clear_bit(RTC_FEATURE_ALARM, rtc->features); + devm_rtc_register_device(rtc); } #else /* !CONFIG_RTC_CLASS */ -static inline void acpi_tad_register_rtc(struct device *dev) {} +static inline void acpi_tad_register_rtc(struct device *dev, + unsigned long long caps) {} #endif /* !CONFIG_RTC_CLASS */ /* Platform driver interface */ @@ -765,7 +871,7 @@ static int acpi_tad_probe(struct platform_device *pdev) pm_runtime_suspend(dev); if (caps & ACPI_TAD_RT) - acpi_tad_register_rtc(dev); + acpi_tad_register_rtc(dev, caps); return 0; } From 02c68ed11ceed569ad2d029a4138aead8ff13229 Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Fri, 3 Apr 2026 16:53:43 +0800 Subject: [PATCH 55/73] ACPI: processor: idle: Reset power_setup_done flag on initialization failure The 'power_setup_done' flag is a key indicator used across the ACPI processor driver to determine if cpuidle are properly configured and available for a given CPU. Currently, this flag is set during the early stages of initialization. However, if the subsequent registration of the cpuidle driver in acpi_processor_register_idle_driver() or the per-CPU device registration in acpi_processor_power_init() fails, this flag remains set. This may lead to some issues where other functions in ACPI idle driver use these flags. Fix this by explicitly resetting this flag to 0 in these error paths. Signed-off-by: Huisong Li Link: https://patch.msgid.link/20260403085343.866440-1-lihuisong@huawei.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_idle.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 479995a4c48a..6172462c7b4e 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1379,6 +1379,7 @@ void acpi_processor_register_idle_driver(void) ret = cpuidle_register_driver(&acpi_idle_driver); if (ret) { + pr->flags.power_setup_done = 0; pr_debug("register %s failed.\n", acpi_idle_driver.name); return; } @@ -1387,7 +1388,16 @@ void acpi_processor_register_idle_driver(void) void acpi_processor_unregister_idle_driver(void) { + struct acpi_processor *pr; + int cpu; + cpuidle_unregister_driver(&acpi_idle_driver); + for_each_possible_cpu(cpu) { + pr = per_cpu(processors, cpu); + if (!pr) + continue; + pr->flags.power_setup_done = 0; + } } void acpi_processor_power_init(struct acpi_processor *pr) @@ -1424,6 +1434,7 @@ void acpi_processor_power_init(struct acpi_processor *pr) */ if (cpuidle_register_device(dev)) { per_cpu(acpi_cpuidle_device, pr->id) = NULL; + pr->flags.power_setup_done = 0; kfree(dev); } } From 47e6a863a88034be102bde11197f2ca1bc18cbaf Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Fri, 3 Apr 2026 17:02:53 +0800 Subject: [PATCH 56/73] ACPI: processor: idle: Fix NULL pointer dereference in hotplug path A cpuidle_device might fail to register during boot, but the system can continue to run. In such cases, acpi_processor_hotplug() can trigger a NULL pointer dereference when accessing the per-cpu acpi_cpuidle_device. So add NULL pointer check for the per-cpu acpi_cpuidle_device in acpi_processor_hotplug. Signed-off-by: Huisong Li Link: https://patch.msgid.link/20260403090253.998322-1-lihuisong@huawei.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_idle.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index 6172462c7b4e..c1cddb4a5887 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1267,16 +1267,15 @@ static int acpi_processor_get_power_info(struct acpi_processor *pr) int acpi_processor_hotplug(struct acpi_processor *pr) { + struct cpuidle_device *dev = per_cpu(acpi_cpuidle_device, pr->id); int ret = 0; - struct cpuidle_device *dev; if (disabled_by_idle_boot_param()) return 0; - if (!pr->flags.power_setup_done) + if (!pr->flags.power_setup_done || !dev) return -ENODEV; - dev = per_cpu(acpi_cpuidle_device, pr->id); cpuidle_pause_and_lock(); cpuidle_disable_device(dev); ret = acpi_processor_get_power_info(pr); From 1b7cbe343349ec5aec6f3140820180c5bc00b14f Mon Sep 17 00:00:00 2001 From: Xi Ruoyao Date: Wed, 1 Apr 2026 21:53:12 +0800 Subject: [PATCH 57/73] ACPI: tables: Enable FPDT on LoongArch FPDT provides system- and application-readable performance statistics, useful for profiling and analyzing boot-time performance. FPDT table support is now available as a pending patch at the EDK II upstream [1] and has been tested on real hardware such as Loongson XA61200_V1.1 and XB612B0_V1.2 with patched firmware. We have also cross checked systemd-analyze(1) against a stop watch and the `dp' command in EFI Shell to see that the timing information are correct. Now that the functionality of FPDT is verified on LoongArch hardware, list LOONGARCH as a possible dependency, allowing it to be enabled. Link: https://github.com/tianocore/edk2/pull/12378 [1] Signed-off-by: Xi Ruoyao [ rjw: Subject tweak ] Link: https://patch.msgid.link/20260401135311.1737958-2-xry111@xry111.site Signed-off-by: Rafael J. Wysocki --- drivers/acpi/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 6f4b545f7377..f165d14cf61a 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -96,7 +96,7 @@ config ACPI_SPCR_TABLE config ACPI_FPDT bool "ACPI Firmware Performance Data Table (FPDT) support" - depends on X86_64 || ARM64 + depends on X86_64 || ARM64 || LOONGARCH help Enable support for the Firmware Performance Data Table (FPDT). This table provides information on the timing of the system From 441fa10a5a1978e7a2f751f2d6f6a9194056262e Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Mon, 30 Mar 2026 17:41:55 +0800 Subject: [PATCH 58/73] ACPI: APEI: GHES: Add devm_ghes_register_vendor_record_notifier() Add a device-managed wrapper around ghes_register_vendor_record_notifier() so drivers can avoid manual cleanup on device removal or probe failure. Signed-off-by: Kai-Heng Feng Reviewed-by: Breno Leitao Reviewed-by: Shiju Jose Reviewed-by: Shuai Xue Link: https://patch.msgid.link/20260330094203.38022-2-kaihengf@nvidia.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/apei/ghes.c | 18 ++++++++++++++++++ include/acpi/ghes.h | 11 +++++++++++ 2 files changed, 29 insertions(+) diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c index 8acd2742bb27..3236a3ce79d6 100644 --- a/drivers/acpi/apei/ghes.c +++ b/drivers/acpi/apei/ghes.c @@ -689,6 +689,24 @@ void ghes_unregister_vendor_record_notifier(struct notifier_block *nb) } EXPORT_SYMBOL_GPL(ghes_unregister_vendor_record_notifier); +static void ghes_vendor_record_notifier_destroy(void *nb) +{ + ghes_unregister_vendor_record_notifier(nb); +} + +int devm_ghes_register_vendor_record_notifier(struct device *dev, + struct notifier_block *nb) +{ + int ret; + + ret = ghes_register_vendor_record_notifier(nb); + if (ret) + return ret; + + return devm_add_action_or_reset(dev, ghes_vendor_record_notifier_destroy, nb); +} +EXPORT_SYMBOL_GPL(devm_ghes_register_vendor_record_notifier); + static void ghes_vendor_record_work_func(struct work_struct *work) { struct ghes_vendor_record_entry *entry; diff --git a/include/acpi/ghes.h b/include/acpi/ghes.h index 7bea522c0657..8d7e5caef3f1 100644 --- a/include/acpi/ghes.h +++ b/include/acpi/ghes.h @@ -71,6 +71,17 @@ int ghes_register_vendor_record_notifier(struct notifier_block *nb); */ void ghes_unregister_vendor_record_notifier(struct notifier_block *nb); +/** + * devm_ghes_register_vendor_record_notifier - device-managed vendor + * record notifier registration. + * @dev: device that owns the notifier lifetime + * @nb: pointer to the notifier_block structure of the vendor record handler + * + * Return: 0 on success, negative errno on failure. + */ +int devm_ghes_register_vendor_record_notifier(struct device *dev, + struct notifier_block *nb); + struct list_head *ghes_get_devices(void); void ghes_estatus_pool_region_free(unsigned long addr, u32 size); From 35bdb5dbacf3ab4e4ee970ec2df2aa972ebbc21f Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Mon, 30 Mar 2026 17:41:56 +0800 Subject: [PATCH 59/73] PCI: hisi: Use devm_ghes_register_vendor_record_notifier() Switch to the device-managed variant so the notifier is automatically unregistered on device removal, allowing the open-coded remove callback to be dropped entirely. Signed-off-by: Kai-Heng Feng Acked-by: Manivannan Sadhasivam Reviewed-by: Shiju Jose Link: https://patch.msgid.link/20260330094203.38022-3-kaihengf@nvidia.com Signed-off-by: Rafael J. Wysocki --- drivers/pci/controller/pcie-hisi-error.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/drivers/pci/controller/pcie-hisi-error.c b/drivers/pci/controller/pcie-hisi-error.c index aaf1ed2b6e59..36be86d827a8 100644 --- a/drivers/pci/controller/pcie-hisi-error.c +++ b/drivers/pci/controller/pcie-hisi-error.c @@ -287,25 +287,16 @@ static int hisi_pcie_error_handler_probe(struct platform_device *pdev) priv->nb.notifier_call = hisi_pcie_notify_error; priv->dev = &pdev->dev; - ret = ghes_register_vendor_record_notifier(&priv->nb); + ret = devm_ghes_register_vendor_record_notifier(&pdev->dev, &priv->nb); if (ret) { dev_err(&pdev->dev, "Failed to register hisi pcie controller error handler with apei\n"); return ret; } - platform_set_drvdata(pdev, priv); - return 0; } -static void hisi_pcie_error_handler_remove(struct platform_device *pdev) -{ - struct hisi_pcie_error_private *priv = platform_get_drvdata(pdev); - - ghes_unregister_vendor_record_notifier(&priv->nb); -} - static const struct acpi_device_id hisi_pcie_acpi_match[] = { { "HISI0361", 0 }, { } @@ -317,7 +308,6 @@ static struct platform_driver hisi_pcie_error_handler_driver = { .acpi_match_table = hisi_pcie_acpi_match, }, .probe = hisi_pcie_error_handler_probe, - .remove = hisi_pcie_error_handler_remove, }; module_platform_driver(hisi_pcie_error_handler_driver); From d7610855b0b5e934a35dedb02047a2419bf00770 Mon Sep 17 00:00:00 2001 From: Kai-Heng Feng Date: Mon, 30 Mar 2026 17:41:57 +0800 Subject: [PATCH 60/73] ACPI: APEI: GHES: Add NVIDIA vendor CPER record handler Add support for decoding NVIDIA-specific CPER sections delivered via the APEI GHES vendor record notifier chain. NVIDIA hardware generates vendor-specific CPER sections containing error signatures and diagnostic register dumps. This implementation registers a notifier_block with the GHES vendor record notifier and decodes these sections, printing error details via dev_info(). The driver binds to ACPI device NVDA2012, present on NVIDIA server platforms. The NVIDIA CPER section contains a fixed header with error metadata (signature, error type, severity, socket) followed by variable-length register address-value pairs for hardware diagnostics. This work is based on libcper [1]. Example output: nvidia-ghes NVDA2012:00: NVIDIA CPER section, error_data_length: 544 nvidia-ghes NVDA2012:00: signature: CMET-INFO nvidia-ghes NVDA2012:00: error_type: 0 nvidia-ghes NVDA2012:00: error_instance: 0 nvidia-ghes NVDA2012:00: severity: 3 nvidia-ghes NVDA2012:00: socket: 0 nvidia-ghes NVDA2012:00: number_regs: 32 nvidia-ghes NVDA2012:00: instance_base: 0x0000000000000000 nvidia-ghes NVDA2012:00: register[0]: address=0x8000000100000000 value=0x0000000100000000 https://github.com/openbmc/libcper/commit/683e055061ce [1] Reviewed-by: Jonathan Cameron Signed-off-by: Kai-Heng Feng [ rjw: Changelog edits ] Link: https://patch.msgid.link/20260330094203.38022-4-kaihengf@nvidia.com Signed-off-by: Rafael J. Wysocki --- MAINTAINERS | 6 ++ drivers/acpi/apei/Kconfig | 14 +++ drivers/acpi/apei/Makefile | 1 + drivers/acpi/apei/ghes-nvidia.c | 149 ++++++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+) create mode 100644 drivers/acpi/apei/ghes-nvidia.c diff --git a/MAINTAINERS b/MAINTAINERS index c3fe46d7c4bc..94608f8e247e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -18919,6 +18919,12 @@ S: Maintained F: drivers/video/fbdev/nvidia/ F: drivers/video/fbdev/riva/ +NVIDIA GHES VENDOR CPER RECORD HANDLER +M: Kai-Heng Feng +L: linux-acpi@vger.kernel.org +S: Maintained +F: drivers/acpi/apei/nvidia-ghes.c + NVIDIA VRS RTC DRIVER M: Shubhi Garg L: linux-tegra@vger.kernel.org diff --git a/drivers/acpi/apei/Kconfig b/drivers/acpi/apei/Kconfig index 070c07d68dfb..428458c623f0 100644 --- a/drivers/acpi/apei/Kconfig +++ b/drivers/acpi/apei/Kconfig @@ -74,6 +74,20 @@ config ACPI_APEI_EINJ_CXL If unsure say 'n' +config ACPI_APEI_GHES_NVIDIA + tristate "NVIDIA GHES vendor record handler" + depends on ACPI_APEI_GHES + help + Support for decoding NVIDIA-specific CPER sections delivered via + the APEI GHES vendor record notifier chain. Registers a handler + for the NVIDIA section GUID and logs error signatures, severity, + socket, and diagnostic register address-value pairs. + + Enable on NVIDIA server platforms (e.g. DGX, HGX) that expose + ACPI device NVDA2012 in their firmware tables. + + If unsure, say N. + config ACPI_APEI_ERST_DEBUG tristate "APEI Error Record Serialization Table (ERST) Debug Support" depends on ACPI_APEI diff --git a/drivers/acpi/apei/Makefile b/drivers/acpi/apei/Makefile index 1a0b85923cd4..66588d6be56f 100644 --- a/drivers/acpi/apei/Makefile +++ b/drivers/acpi/apei/Makefile @@ -10,5 +10,6 @@ obj-$(CONFIG_ACPI_APEI_EINJ) += einj.o einj-y := einj-core.o einj-$(CONFIG_ACPI_APEI_EINJ_CXL) += einj-cxl.o obj-$(CONFIG_ACPI_APEI_ERST_DEBUG) += erst-dbg.o +obj-$(CONFIG_ACPI_APEI_GHES_NVIDIA) += ghes-nvidia.o apei-y := apei-base.o hest.o erst.o bert.o diff --git a/drivers/acpi/apei/ghes-nvidia.c b/drivers/acpi/apei/ghes-nvidia.c new file mode 100644 index 000000000000..597275d81de8 --- /dev/null +++ b/drivers/acpi/apei/ghes-nvidia.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NVIDIA GHES vendor record handler + * + * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include + +static const guid_t nvidia_sec_guid = + GUID_INIT(0x6d5244f2, 0x2712, 0x11ec, + 0xbe, 0xa7, 0xcb, 0x3f, 0xdb, 0x95, 0xc7, 0x86); + +struct cper_sec_nvidia { + char signature[16]; + __le16 error_type; + __le16 error_instance; + u8 severity; + u8 socket; + u8 number_regs; + u8 reserved; + __le64 instance_base; + struct { + __le64 addr; + __le64 val; + } regs[] __counted_by(number_regs); +}; + +struct nvidia_ghes_private { + struct notifier_block nb; + struct device *dev; +}; + +static void nvidia_ghes_print_error(struct device *dev, + const struct cper_sec_nvidia *nvidia_err, + size_t error_data_length, bool fatal) +{ + const char *level = fatal ? KERN_ERR : KERN_INFO; + size_t min_size; + + dev_printk(level, dev, "signature: %.16s\n", nvidia_err->signature); + dev_printk(level, dev, "error_type: %u\n", le16_to_cpu(nvidia_err->error_type)); + dev_printk(level, dev, "error_instance: %u\n", le16_to_cpu(nvidia_err->error_instance)); + dev_printk(level, dev, "severity: %u\n", nvidia_err->severity); + dev_printk(level, dev, "socket: %u\n", nvidia_err->socket); + dev_printk(level, dev, "number_regs: %u\n", nvidia_err->number_regs); + dev_printk(level, dev, "instance_base: 0x%016llx\n", + le64_to_cpu(nvidia_err->instance_base)); + + if (nvidia_err->number_regs == 0) + return; + + /* + * Validate that all registers fit within error_data_length. + * Each register pair is two little-endian u64s. + */ + min_size = struct_size(nvidia_err, regs, nvidia_err->number_regs); + if (error_data_length < min_size) { + dev_err(dev, "Invalid number_regs %u (section size %zu, need %zu)\n", + nvidia_err->number_regs, error_data_length, min_size); + return; + } + + for (int i = 0; i < nvidia_err->number_regs; i++) + dev_printk(level, dev, "register[%d]: address=0x%016llx value=0x%016llx\n", + i, le64_to_cpu(nvidia_err->regs[i].addr), + le64_to_cpu(nvidia_err->regs[i].val)); +} + +static int nvidia_ghes_notify(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct acpi_hest_generic_data *gdata = data; + struct nvidia_ghes_private *priv; + const struct cper_sec_nvidia *nvidia_err; + guid_t sec_guid; + + import_guid(&sec_guid, gdata->section_type); + if (!guid_equal(&sec_guid, &nvidia_sec_guid)) + return NOTIFY_DONE; + + priv = container_of(nb, struct nvidia_ghes_private, nb); + + if (acpi_hest_get_error_length(gdata) < sizeof(*nvidia_err)) { + dev_err(priv->dev, "Section too small (%d < %zu)\n", + acpi_hest_get_error_length(gdata), sizeof(*nvidia_err)); + return NOTIFY_OK; + } + + nvidia_err = acpi_hest_get_payload(gdata); + + if (event >= GHES_SEV_RECOVERABLE) + dev_err(priv->dev, "NVIDIA CPER section, error_data_length: %u\n", + acpi_hest_get_error_length(gdata)); + else + dev_info(priv->dev, "NVIDIA CPER section, error_data_length: %u\n", + acpi_hest_get_error_length(gdata)); + + nvidia_ghes_print_error(priv->dev, nvidia_err, acpi_hest_get_error_length(gdata), + event >= GHES_SEV_RECOVERABLE); + + return NOTIFY_OK; +} + +static int nvidia_ghes_probe(struct platform_device *pdev) +{ + struct nvidia_ghes_private *priv; + int ret; + + priv = devm_kmalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + *priv = (struct nvidia_ghes_private) { + .nb.notifier_call = nvidia_ghes_notify, + .dev = &pdev->dev, + }; + + ret = devm_ghes_register_vendor_record_notifier(&pdev->dev, &priv->nb); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "Failed to register NVIDIA GHES vendor record notifier\n"); + + return 0; +} + +static const struct acpi_device_id nvidia_ghes_acpi_match[] = { + { "NVDA2012" }, + { } +}; +MODULE_DEVICE_TABLE(acpi, nvidia_ghes_acpi_match); + +static struct platform_driver nvidia_ghes_driver = { + .driver = { + .name = "nvidia-ghes", + .acpi_match_table = nvidia_ghes_acpi_match, + }, + .probe = nvidia_ghes_probe, +}; +module_platform_driver(nvidia_ghes_driver); + +MODULE_AUTHOR("Kai-Heng Feng "); +MODULE_DESCRIPTION("NVIDIA GHES vendor CPER record handler"); +MODULE_LICENSE("GPL"); From 7cd5f5659ac8e49811ef03cbfe8b4a2069abaa27 Mon Sep 17 00:00:00 2001 From: Chengwen Feng Date: Wed, 1 Apr 2026 16:16:33 +0800 Subject: [PATCH 61/73] arm64: acpi: Add acpi_get_cpu_uid() for unified ACPI CPU UID retrieval As a step towards unifying the interface for retrieving ACPI CPU UID across architectures, introduce a new function acpi_get_cpu_uid() for arm64. While at it, add input validation to make the code more robust. Reimplement get_cpu_for_acpi_id() based on acpi_get_cpu_uid() for consistency, and move its implementation next to the new function for code coherence. Signed-off-by: Chengwen Feng Reviewed-by: Jonathan Cameron Acked-by: Catalin Marinas Link: https://patch.msgid.link/20260401081640.26875-2-fengchengwen@huawei.com Signed-off-by: Rafael J. Wysocki --- arch/arm64/include/asm/acpi.h | 14 ++------------ arch/arm64/kernel/acpi.c | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h index c07a58b96329..2219a3301e72 100644 --- a/arch/arm64/include/asm/acpi.h +++ b/arch/arm64/include/asm/acpi.h @@ -118,18 +118,8 @@ static inline u32 get_acpi_id_for_cpu(unsigned int cpu) { return acpi_cpu_get_madt_gicc(cpu)->uid; } - -static inline int get_cpu_for_acpi_id(u32 uid) -{ - int cpu; - - for (cpu = 0; cpu < nr_cpu_ids; cpu++) - if (acpi_cpu_get_madt_gicc(cpu) && - uid == get_acpi_id_for_cpu(cpu)) - return cpu; - - return -EINVAL; -} +int acpi_get_cpu_uid(unsigned int cpu, u32 *uid); +int get_cpu_for_acpi_id(u32 uid); static inline void arch_fix_phys_package_id(int num, u32 slot) { } void __init acpi_init_cpus(void); diff --git a/arch/arm64/kernel/acpi.c b/arch/arm64/kernel/acpi.c index a9d884fd1d00..5891f92c2035 100644 --- a/arch/arm64/kernel/acpi.c +++ b/arch/arm64/kernel/acpi.c @@ -458,3 +458,33 @@ int acpi_unmap_cpu(int cpu) } EXPORT_SYMBOL(acpi_unmap_cpu); #endif /* CONFIG_ACPI_HOTPLUG_CPU */ + +int acpi_get_cpu_uid(unsigned int cpu, u32 *uid) +{ + struct acpi_madt_generic_interrupt *gicc; + + if (cpu >= nr_cpu_ids) + return -EINVAL; + + gicc = acpi_cpu_get_madt_gicc(cpu); + if (!gicc) + return -ENODEV; + + *uid = gicc->uid; + return 0; +} +EXPORT_SYMBOL_GPL(acpi_get_cpu_uid); + +int get_cpu_for_acpi_id(u32 uid) +{ + u32 cpu_uid; + int ret; + + for (int cpu = 0; cpu < nr_cpu_ids; cpu++) { + ret = acpi_get_cpu_uid(cpu, &cpu_uid); + if (ret == 0 && uid == cpu_uid) + return cpu; + } + + return -EINVAL; +} From d78ef9d2e1f2c7e0b69c102fc2867e8daa5612ed Mon Sep 17 00:00:00 2001 From: Chengwen Feng Date: Wed, 1 Apr 2026 16:16:34 +0800 Subject: [PATCH 62/73] LoongArch: Add acpi_get_cpu_uid() for unified ACPI CPU UID retrieval As a step towards unifying the interface for retrieving ACPI CPU UID across architectures, introduce a new function acpi_get_cpu_uid() for loongarch. While at it, add input validation to make the code more robust. Signed-off-by: Chengwen Feng Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20260401081640.26875-3-fengchengwen@huawei.com Signed-off-by: Rafael J. Wysocki --- arch/loongarch/include/asm/acpi.h | 1 + arch/loongarch/kernel/acpi.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/arch/loongarch/include/asm/acpi.h b/arch/loongarch/include/asm/acpi.h index 7376840fa9f7..8bb101b4557e 100644 --- a/arch/loongarch/include/asm/acpi.h +++ b/arch/loongarch/include/asm/acpi.h @@ -44,6 +44,7 @@ static inline u32 get_acpi_id_for_cpu(unsigned int cpu) { return acpi_core_pic[cpu_logical_map(cpu)].processor_id; } +int acpi_get_cpu_uid(unsigned int cpu, u32 *uid); #endif /* !CONFIG_ACPI */ diff --git a/arch/loongarch/kernel/acpi.c b/arch/loongarch/kernel/acpi.c index 1367ca759468..058f0dbe8e8f 100644 --- a/arch/loongarch/kernel/acpi.c +++ b/arch/loongarch/kernel/acpi.c @@ -385,3 +385,12 @@ int acpi_unmap_cpu(int cpu) EXPORT_SYMBOL(acpi_unmap_cpu); #endif /* CONFIG_ACPI_HOTPLUG_CPU */ + +int acpi_get_cpu_uid(unsigned int cpu, u32 *uid) +{ + if (cpu >= nr_cpu_ids) + return -EINVAL; + *uid = acpi_core_pic[cpu_logical_map(cpu)].processor_id; + return 0; +} +EXPORT_SYMBOL_GPL(acpi_get_cpu_uid); From 0c8231994e43f445597166e8b342459079244d25 Mon Sep 17 00:00:00 2001 From: Chengwen Feng Date: Wed, 1 Apr 2026 16:16:35 +0800 Subject: [PATCH 63/73] RISC-V: ACPI: Add acpi_get_cpu_uid() for unified ACPI CPU UID retrieval As a step towards unifying the interface for retrieving ACPI CPU UID across architectures, introduce a new function acpi_get_cpu_uid() for riscv. While at it, add input validation to make the code more robust. And also update acpi_numa.c and rhct.c to use the new interface instead of the legacy get_acpi_id_for_cpu(). Signed-off-by: Chengwen Feng Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20260401081640.26875-4-fengchengwen@huawei.com Signed-off-by: Rafael J. Wysocki --- arch/riscv/include/asm/acpi.h | 1 + arch/riscv/kernel/acpi.c | 16 ++++++++++++++++ arch/riscv/kernel/acpi_numa.c | 9 ++++++--- drivers/acpi/riscv/rhct.c | 7 ++++++- 4 files changed, 29 insertions(+), 4 deletions(-) diff --git a/arch/riscv/include/asm/acpi.h b/arch/riscv/include/asm/acpi.h index 6e13695120bc..f3520cc85af3 100644 --- a/arch/riscv/include/asm/acpi.h +++ b/arch/riscv/include/asm/acpi.h @@ -65,6 +65,7 @@ static inline u32 get_acpi_id_for_cpu(int cpu) { return acpi_cpu_get_madt_rintc(cpu)->uid; } +int acpi_get_cpu_uid(unsigned int cpu, u32 *uid); int acpi_get_riscv_isa(struct acpi_table_header *table, unsigned int cpu, const char **isa); diff --git a/arch/riscv/kernel/acpi.c b/arch/riscv/kernel/acpi.c index 71698ee11621..322ea92aa39f 100644 --- a/arch/riscv/kernel/acpi.c +++ b/arch/riscv/kernel/acpi.c @@ -337,3 +337,19 @@ int raw_pci_write(unsigned int domain, unsigned int bus, } #endif /* CONFIG_PCI */ + +int acpi_get_cpu_uid(unsigned int cpu, u32 *uid) +{ + struct acpi_madt_rintc *rintc; + + if (cpu >= nr_cpu_ids) + return -EINVAL; + + rintc = acpi_cpu_get_madt_rintc(cpu); + if (!rintc) + return -ENODEV; + + *uid = rintc->uid; + return 0; +} +EXPORT_SYMBOL_GPL(acpi_get_cpu_uid); diff --git a/arch/riscv/kernel/acpi_numa.c b/arch/riscv/kernel/acpi_numa.c index 130769e3a99c..6a2d4289f806 100644 --- a/arch/riscv/kernel/acpi_numa.c +++ b/arch/riscv/kernel/acpi_numa.c @@ -37,11 +37,14 @@ static int __init acpi_numa_get_nid(unsigned int cpu) static inline int get_cpu_for_acpi_id(u32 uid) { - int cpu; + u32 cpu_uid; + int ret; - for (cpu = 0; cpu < nr_cpu_ids; cpu++) - if (uid == get_acpi_id_for_cpu(cpu)) + for (int cpu = 0; cpu < nr_cpu_ids; cpu++) { + ret = acpi_get_cpu_uid(cpu, &cpu_uid); + if (ret == 0 && uid == cpu_uid) return cpu; + } return -EINVAL; } diff --git a/drivers/acpi/riscv/rhct.c b/drivers/acpi/riscv/rhct.c index caa2c16e1697..8f3f38c64a88 100644 --- a/drivers/acpi/riscv/rhct.c +++ b/drivers/acpi/riscv/rhct.c @@ -44,10 +44,15 @@ int acpi_get_riscv_isa(struct acpi_table_header *table, unsigned int cpu, const struct acpi_rhct_isa_string *isa_node; struct acpi_table_rhct *rhct; u32 *hart_info_node_offset; - u32 acpi_cpu_id = get_acpi_id_for_cpu(cpu); + u32 acpi_cpu_id; + int ret; BUG_ON(acpi_disabled); + ret = acpi_get_cpu_uid(cpu, &acpi_cpu_id); + if (ret != 0) + return ret; + if (!table) { rhct = acpi_get_rhct(); if (!rhct) From 3cfe889f8965ded727f3de38ee941b44978e1d9b Mon Sep 17 00:00:00 2001 From: Chengwen Feng Date: Wed, 1 Apr 2026 16:16:36 +0800 Subject: [PATCH 64/73] x86/acpi: Add acpi_get_cpu_uid() for unified ACPI CPU UID retrieval As a step towards unifying the interface for retrieving ACPI CPU UID across architectures, introduce a new function acpi_get_cpu_uid() for x86. While at it, add input validation to make the code more robust. Update Xen-related code to use acpi_get_cpu_uid() instead of the legacy cpu_acpi_id() function, and remove the now-unused cpu_acpi_id() to clean up redundant code. Signed-off-by: Chengwen Feng Reviewed-by: Jonathan Cameron Reviewed-by: Juergen Gross Link: https://patch.msgid.link/20260401081640.26875-5-fengchengwen@huawei.com Signed-off-by: Rafael J. Wysocki --- arch/x86/include/asm/acpi.h | 2 ++ arch/x86/include/asm/cpu.h | 1 - arch/x86/include/asm/smp.h | 1 - arch/x86/kernel/acpi/boot.c | 20 ++++++++++++++++++++ arch/x86/xen/enlighten_hvm.c | 5 +++-- 5 files changed, 25 insertions(+), 4 deletions(-) diff --git a/arch/x86/include/asm/acpi.h b/arch/x86/include/asm/acpi.h index a03aa6f999d1..92b5c27c4fea 100644 --- a/arch/x86/include/asm/acpi.h +++ b/arch/x86/include/asm/acpi.h @@ -157,6 +157,8 @@ static inline bool acpi_has_cpu_in_madt(void) return !!acpi_lapic; } +int acpi_get_cpu_uid(unsigned int cpu, u32 *uid); + #define ACPI_HAVE_ARCH_SET_ROOT_POINTER static __always_inline void acpi_arch_set_root_pointer(u64 addr) { diff --git a/arch/x86/include/asm/cpu.h b/arch/x86/include/asm/cpu.h index ad235dda1ded..57a0786dfd75 100644 --- a/arch/x86/include/asm/cpu.h +++ b/arch/x86/include/asm/cpu.h @@ -11,7 +11,6 @@ #ifndef CONFIG_SMP #define cpu_physical_id(cpu) boot_cpu_physical_apicid -#define cpu_acpi_id(cpu) 0 #endif /* CONFIG_SMP */ #ifdef CONFIG_HOTPLUG_CPU diff --git a/arch/x86/include/asm/smp.h b/arch/x86/include/asm/smp.h index 84951572ab81..05d1d479b4cf 100644 --- a/arch/x86/include/asm/smp.h +++ b/arch/x86/include/asm/smp.h @@ -130,7 +130,6 @@ __visible void smp_call_function_interrupt(struct pt_regs *regs); __visible void smp_call_function_single_interrupt(struct pt_regs *r); #define cpu_physical_id(cpu) per_cpu(x86_cpu_to_apicid, cpu) -#define cpu_acpi_id(cpu) per_cpu(x86_cpu_to_acpiid, cpu) /* * This function is needed by all SMP systems. It must _always_ be valid diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c index a3f2fb1fea1b..ceba24f65ae3 100644 --- a/arch/x86/kernel/acpi/boot.c +++ b/arch/x86/kernel/acpi/boot.c @@ -1848,3 +1848,23 @@ void __iomem * (*acpi_os_ioremap)(acpi_physical_address phys, acpi_size size) = x86_acpi_os_ioremap; EXPORT_SYMBOL_GPL(acpi_os_ioremap); #endif + +int acpi_get_cpu_uid(unsigned int cpu, u32 *uid) +{ + u32 acpi_id; + + if (cpu >= nr_cpu_ids) + return -EINVAL; + +#ifdef CONFIG_SMP + acpi_id = per_cpu(x86_cpu_to_acpiid, cpu); + if (acpi_id == CPU_ACPIID_INVALID) + return -ENODEV; +#else + acpi_id = 0; +#endif + + *uid = acpi_id; + return 0; +} +EXPORT_SYMBOL_GPL(acpi_get_cpu_uid); diff --git a/arch/x86/xen/enlighten_hvm.c b/arch/x86/xen/enlighten_hvm.c index fe57ff85d004..2f9fa27e5a3c 100644 --- a/arch/x86/xen/enlighten_hvm.c +++ b/arch/x86/xen/enlighten_hvm.c @@ -151,6 +151,7 @@ static void xen_hvm_crash_shutdown(struct pt_regs *regs) static int xen_cpu_up_prepare_hvm(unsigned int cpu) { + u32 cpu_uid; int rc = 0; /* @@ -161,8 +162,8 @@ static int xen_cpu_up_prepare_hvm(unsigned int cpu) */ xen_uninit_lock_cpu(cpu); - if (cpu_acpi_id(cpu) != CPU_ACPIID_INVALID) - per_cpu(xen_vcpu_id, cpu) = cpu_acpi_id(cpu); + if (acpi_get_cpu_uid(cpu, &cpu_uid) == 0) + per_cpu(xen_vcpu_id, cpu) = cpu_uid; else per_cpu(xen_vcpu_id, cpu) = cpu; xen_vcpu_setup(cpu); From f652d0a4e13c5f5416da15ba791b99c5d1ac9b18 Mon Sep 17 00:00:00 2001 From: Chengwen Feng Date: Wed, 1 Apr 2026 16:16:37 +0800 Subject: [PATCH 65/73] ACPI: Centralize acpi_get_cpu_uid() declaration in include/linux/acpi.h Centralize acpi_get_cpu_uid() in include/linux/acpi.h (global scope) and remove arch-specific declarations from arm64/loongarch/riscv/x86 asm/acpi.h. This unifies the interface across architectures and simplifies maintenance by eliminating duplicate prototypes. Signed-off-by: Chengwen Feng Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20260401081640.26875-6-fengchengwen@huawei.com Signed-off-by: Rafael J. Wysocki --- arch/arm64/include/asm/acpi.h | 1 - arch/loongarch/include/asm/acpi.h | 1 - arch/riscv/include/asm/acpi.h | 1 - arch/x86/include/asm/acpi.h | 2 -- include/linux/acpi.h | 11 +++++++++++ 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h index 2219a3301e72..bdb0ecf95b5c 100644 --- a/arch/arm64/include/asm/acpi.h +++ b/arch/arm64/include/asm/acpi.h @@ -118,7 +118,6 @@ static inline u32 get_acpi_id_for_cpu(unsigned int cpu) { return acpi_cpu_get_madt_gicc(cpu)->uid; } -int acpi_get_cpu_uid(unsigned int cpu, u32 *uid); int get_cpu_for_acpi_id(u32 uid); static inline void arch_fix_phys_package_id(int num, u32 slot) { } diff --git a/arch/loongarch/include/asm/acpi.h b/arch/loongarch/include/asm/acpi.h index 8bb101b4557e..7376840fa9f7 100644 --- a/arch/loongarch/include/asm/acpi.h +++ b/arch/loongarch/include/asm/acpi.h @@ -44,7 +44,6 @@ static inline u32 get_acpi_id_for_cpu(unsigned int cpu) { return acpi_core_pic[cpu_logical_map(cpu)].processor_id; } -int acpi_get_cpu_uid(unsigned int cpu, u32 *uid); #endif /* !CONFIG_ACPI */ diff --git a/arch/riscv/include/asm/acpi.h b/arch/riscv/include/asm/acpi.h index f3520cc85af3..6e13695120bc 100644 --- a/arch/riscv/include/asm/acpi.h +++ b/arch/riscv/include/asm/acpi.h @@ -65,7 +65,6 @@ static inline u32 get_acpi_id_for_cpu(int cpu) { return acpi_cpu_get_madt_rintc(cpu)->uid; } -int acpi_get_cpu_uid(unsigned int cpu, u32 *uid); int acpi_get_riscv_isa(struct acpi_table_header *table, unsigned int cpu, const char **isa); diff --git a/arch/x86/include/asm/acpi.h b/arch/x86/include/asm/acpi.h index 92b5c27c4fea..a03aa6f999d1 100644 --- a/arch/x86/include/asm/acpi.h +++ b/arch/x86/include/asm/acpi.h @@ -157,8 +157,6 @@ static inline bool acpi_has_cpu_in_madt(void) return !!acpi_lapic; } -int acpi_get_cpu_uid(unsigned int cpu, u32 *uid); - #define ACPI_HAVE_ARCH_SET_ROOT_POINTER static __always_inline void acpi_arch_set_root_pointer(u64 addr) { diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 4d2f0bed7a06..74a73f0e5944 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -324,6 +324,17 @@ int acpi_unmap_cpu(int cpu); acpi_handle acpi_get_processor_handle(int cpu); +/** + * acpi_get_cpu_uid() - Get ACPI Processor UID of from MADT table + * @cpu: Logical CPU number (0-based) + * @uid: Pointer to store ACPI Processor UID + * + * Return: 0 on success (ACPI Processor ID stored in *uid); + * -EINVAL if CPU number is invalid or out of range; + * -ENODEV if ACPI Processor UID for the CPU is not found. + */ +int acpi_get_cpu_uid(unsigned int cpu, u32 *uid); + #ifdef CONFIG_ACPI_HOTPLUG_IOAPIC int acpi_get_ioapic_id(acpi_handle handle, u32 gsi_base, u64 *phys_addr); #endif From 1ab03189793ffe60f184ce58ea7d0a4f0dcb7e06 Mon Sep 17 00:00:00 2001 From: Chengwen Feng Date: Wed, 1 Apr 2026 16:16:38 +0800 Subject: [PATCH 66/73] perf: arm_cspmu: Switch to acpi_get_cpu_uid() from get_acpi_id_for_cpu() Update arm_cspmu to use acpi_get_cpu_uid() instead of get_acpi_id_for_cpu(), aligning with unified ACPI CPU UID interface. No functional changes are introduced by this switch (valid inputs retain original behavior). Signed-off-by: Chengwen Feng Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20260401081640.26875-7-fengchengwen@huawei.com Signed-off-by: Rafael J. Wysocki --- drivers/perf/arm_cspmu/arm_cspmu.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/perf/arm_cspmu/arm_cspmu.c b/drivers/perf/arm_cspmu/arm_cspmu.c index 34430b68f602..ed72c3d1f796 100644 --- a/drivers/perf/arm_cspmu/arm_cspmu.c +++ b/drivers/perf/arm_cspmu/arm_cspmu.c @@ -1107,15 +1107,17 @@ static int arm_cspmu_acpi_get_cpus(struct arm_cspmu *cspmu) { struct acpi_apmt_node *apmt_node; int affinity_flag; + u32 cpu_uid; int cpu; + int ret; apmt_node = arm_cspmu_apmt_node(cspmu->dev); affinity_flag = apmt_node->flags & ACPI_APMT_FLAGS_AFFINITY; if (affinity_flag == ACPI_APMT_FLAGS_AFFINITY_PROC) { for_each_possible_cpu(cpu) { - if (apmt_node->proc_affinity == - get_acpi_id_for_cpu(cpu)) { + ret = acpi_get_cpu_uid(cpu, &cpu_uid); + if (ret == 0 && apmt_node->proc_affinity == cpu_uid) { cpumask_set_cpu(cpu, &cspmu->associated_cpus); break; } From a7034e9e4491573d268126a5e6991b83e95db560 Mon Sep 17 00:00:00 2001 From: Chengwen Feng Date: Wed, 1 Apr 2026 16:16:39 +0800 Subject: [PATCH 67/73] ACPI: PPTT: Use acpi_get_cpu_uid() and remove get_acpi_id_for_cpu() Update acpi/pptt.c to use acpi_get_cpu_uid() and remove unused get_acpi_id_for_cpu() from arm64/loongarch/riscv, completing PPTT's migration to the unified ACPI CPU UID interface Signed-off-by: Chengwen Feng Reviewed-by: Jonathan Cameron Link: https://patch.msgid.link/20260401081640.26875-8-fengchengwen@huawei.com Signed-off-by: Rafael J. Wysocki --- arch/arm64/include/asm/acpi.h | 4 --- arch/loongarch/include/asm/acpi.h | 5 ---- arch/riscv/include/asm/acpi.h | 4 --- drivers/acpi/pptt.c | 50 +++++++++++++++++++++++-------- 4 files changed, 37 insertions(+), 26 deletions(-) diff --git a/arch/arm64/include/asm/acpi.h b/arch/arm64/include/asm/acpi.h index bdb0ecf95b5c..8a54ca6ba602 100644 --- a/arch/arm64/include/asm/acpi.h +++ b/arch/arm64/include/asm/acpi.h @@ -114,10 +114,6 @@ static inline bool acpi_has_cpu_in_madt(void) } struct acpi_madt_generic_interrupt *acpi_cpu_get_madt_gicc(int cpu); -static inline u32 get_acpi_id_for_cpu(unsigned int cpu) -{ - return acpi_cpu_get_madt_gicc(cpu)->uid; -} int get_cpu_for_acpi_id(u32 uid); static inline void arch_fix_phys_package_id(int num, u32 slot) { } diff --git a/arch/loongarch/include/asm/acpi.h b/arch/loongarch/include/asm/acpi.h index 7376840fa9f7..eda9d4d0a493 100644 --- a/arch/loongarch/include/asm/acpi.h +++ b/arch/loongarch/include/asm/acpi.h @@ -40,11 +40,6 @@ extern struct acpi_madt_core_pic acpi_core_pic[MAX_CORE_PIC]; extern int __init parse_acpi_topology(void); -static inline u32 get_acpi_id_for_cpu(unsigned int cpu) -{ - return acpi_core_pic[cpu_logical_map(cpu)].processor_id; -} - #endif /* !CONFIG_ACPI */ #define ACPI_TABLE_UPGRADE_MAX_PHYS ARCH_LOW_ADDRESS_LIMIT diff --git a/arch/riscv/include/asm/acpi.h b/arch/riscv/include/asm/acpi.h index 6e13695120bc..26ab37c171bc 100644 --- a/arch/riscv/include/asm/acpi.h +++ b/arch/riscv/include/asm/acpi.h @@ -61,10 +61,6 @@ static inline void arch_fix_phys_package_id(int num, u32 slot) { } void acpi_init_rintc_map(void); struct acpi_madt_rintc *acpi_cpu_get_madt_rintc(int cpu); -static inline u32 get_acpi_id_for_cpu(int cpu) -{ - return acpi_cpu_get_madt_rintc(cpu)->uid; -} int acpi_get_riscv_isa(struct acpi_table_header *table, unsigned int cpu, const char **isa); diff --git a/drivers/acpi/pptt.c b/drivers/acpi/pptt.c index de5f8c018333..7bd5bc1f225a 100644 --- a/drivers/acpi/pptt.c +++ b/drivers/acpi/pptt.c @@ -459,11 +459,14 @@ static void cache_setup_acpi_cpu(struct acpi_table_header *table, { struct acpi_pptt_cache *found_cache; struct cpu_cacheinfo *this_cpu_ci = get_cpu_cacheinfo(cpu); - u32 acpi_cpu_id = get_acpi_id_for_cpu(cpu); + u32 acpi_cpu_id; struct cacheinfo *this_leaf; unsigned int index = 0; struct acpi_pptt_processor *cpu_node = NULL; + if (acpi_get_cpu_uid(cpu, &acpi_cpu_id) != 0) + return; + while (index < get_cpu_cacheinfo(cpu)->num_leaves) { this_leaf = this_cpu_ci->info_list + index; found_cache = acpi_find_cache_node(table, acpi_cpu_id, @@ -546,7 +549,10 @@ static int topology_get_acpi_cpu_tag(struct acpi_table_header *table, unsigned int cpu, int level, int flag) { struct acpi_pptt_processor *cpu_node; - u32 acpi_cpu_id = get_acpi_id_for_cpu(cpu); + u32 acpi_cpu_id; + + if (acpi_get_cpu_uid(cpu, &acpi_cpu_id) != 0) + return -ENOENT; cpu_node = acpi_find_processor_node(table, acpi_cpu_id); if (cpu_node) { @@ -614,18 +620,22 @@ static int find_acpi_cpu_topology_tag(unsigned int cpu, int level, int flag) * * Check the node representing a CPU for a given flag. * - * Return: -ENOENT if the PPTT doesn't exist, the CPU cannot be found or - * the table revision isn't new enough. + * Return: -ENOENT if can't get CPU's ACPI Processor UID, the PPTT doesn't + * exist, the CPU cannot be found or the table revision isn't new + * enough. * 1, any passed flag set * 0, flag unset */ static int check_acpi_cpu_flag(unsigned int cpu, int rev, u32 flag) { struct acpi_table_header *table; - u32 acpi_cpu_id = get_acpi_id_for_cpu(cpu); + u32 acpi_cpu_id; struct acpi_pptt_processor *cpu_node = NULL; int ret = -ENOENT; + if (acpi_get_cpu_uid(cpu, &acpi_cpu_id) != 0) + return -ENOENT; + table = acpi_get_pptt(); if (!table) return -ENOENT; @@ -651,7 +661,8 @@ static int check_acpi_cpu_flag(unsigned int cpu, int rev, u32 flag) * in the PPTT. Errors caused by lack of a PPTT table, or otherwise, return 0 * indicating we didn't find any cache levels. * - * Return: -ENOENT if no PPTT table or no PPTT processor struct found. + * Return: -ENOENT if no PPTT table, can't get CPU's ACPI Process UID or no PPTT + * processor struct found. * 0 on success. */ int acpi_get_cache_info(unsigned int cpu, unsigned int *levels, @@ -671,7 +682,9 @@ int acpi_get_cache_info(unsigned int cpu, unsigned int *levels, pr_debug("Cache Setup: find cache levels for CPU=%d\n", cpu); - acpi_cpu_id = get_acpi_id_for_cpu(cpu); + if (acpi_get_cpu_uid(cpu, &acpi_cpu_id)) + return -ENOENT; + cpu_node = acpi_find_processor_node(table, acpi_cpu_id); if (!cpu_node) return -ENOENT; @@ -780,8 +793,9 @@ int find_acpi_cpu_topology_package(unsigned int cpu) * It may not exist in single CPU systems. In simple multi-CPU systems, * it may be equal to the package topology level. * - * Return: -ENOENT if the PPTT doesn't exist, the CPU cannot be found - * or there is no toplogy level above the CPU.. + * Return: -ENOENT if the PPTT doesn't exist, can't get CPU's ACPI + * Processor UID, the CPU cannot be found or there is no toplogy level + * above the CPU. * Otherwise returns a value which represents the package for this CPU. */ @@ -797,7 +811,9 @@ int find_acpi_cpu_topology_cluster(unsigned int cpu) if (!table) return -ENOENT; - acpi_cpu_id = get_acpi_id_for_cpu(cpu); + if (acpi_get_cpu_uid(cpu, &acpi_cpu_id) != 0) + return -ENOENT; + cpu_node = acpi_find_processor_node(table, acpi_cpu_id); if (!cpu_node || !cpu_node->parent) return -ENOENT; @@ -872,7 +888,9 @@ static void acpi_pptt_get_child_cpus(struct acpi_table_header *table_hdr, cpumask_clear(cpus); for_each_possible_cpu(cpu) { - acpi_id = get_acpi_id_for_cpu(cpu); + if (acpi_get_cpu_uid(cpu, &acpi_id) != 0) + continue; + cpu_node = acpi_find_processor_node(table_hdr, acpi_id); while (cpu_node) { @@ -966,10 +984,13 @@ int find_acpi_cache_level_from_id(u32 cache_id) for_each_possible_cpu(cpu) { bool empty; int level = 1; - u32 acpi_cpu_id = get_acpi_id_for_cpu(cpu); + u32 acpi_cpu_id; struct acpi_pptt_cache *cache; struct acpi_pptt_processor *cpu_node; + if (acpi_get_cpu_uid(cpu, &acpi_cpu_id) != 0) + continue; + cpu_node = acpi_find_processor_node(table, acpi_cpu_id); if (!cpu_node) continue; @@ -1030,10 +1051,13 @@ int acpi_pptt_get_cpumask_from_cache_id(u32 cache_id, cpumask_t *cpus) for_each_possible_cpu(cpu) { bool empty; int level = 1; - u32 acpi_cpu_id = get_acpi_id_for_cpu(cpu); + u32 acpi_cpu_id; struct acpi_pptt_cache *cache; struct acpi_pptt_processor *cpu_node; + if (acpi_get_cpu_uid(cpu, &acpi_cpu_id) != 0) + continue; + cpu_node = acpi_find_processor_node(table, acpi_cpu_id); if (!cpu_node) continue; From abdd2a86535b59c76d14da2547160bc83e059c03 Mon Sep 17 00:00:00 2001 From: Chengwen Feng Date: Wed, 1 Apr 2026 16:16:40 +0800 Subject: [PATCH 68/73] PCI/TPH: Pass ACPI Processor UID to Cache Locality _DSM pcie_tph_get_cpu_st() uses the Query Cache Locality Features _DSM [1] to retrieve the TPH Steering Tag for memory associated with the CPU identified by its "cpu_uid" parameter, a Linux logical CPU ID. The _DSM requires an ACPI Processor UID, which pcie_tph_get_cpu_st() previously assumed was the same as the Linux logical CPU ID. This is true on x86 but not on arm64, so pcie_tph_get_cpu_st() returned the wrong Steering Tag, resulting in incorrect TPH functionality on arm64. Convert the Linux logical CPU ID to the ACPI Processor UID with acpi_get_cpu_uid() before passing it to the _DSM. Additionally, rename the pcie_tph_get_cpu_st() parameter from "cpu_uid" to "cpu" to reflect that it represents a logical CPU ID (not an ACPI Processor UID). [1] According to ECN_TPH-ST_Revision_20200924 (https://members.pcisig.com/wg/PCI-SIG/document/15470), the input is defined as: "If the target is a processor, then this field represents the ACPI Processor UID of the processor as specified in the MADT. If the target is a processor container, then this field represents the ACPI Processor UID of the processor container as specified in the PPTT." Fixes: d2e8a34876ce ("PCI/TPH: Add Steering Tag support") Signed-off-by: Chengwen Feng Reviewed-by: Jonathan Cameron Reviewed-by: Bjorn Helgaas Link: https://patch.msgid.link/20260401081640.26875-9-fengchengwen@huawei.com Signed-off-by: Rafael J. Wysocki --- Documentation/PCI/tph.rst | 4 ++-- drivers/pci/tph.c | 16 +++++++++++----- include/linux/pci-tph.h | 4 ++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Documentation/PCI/tph.rst b/Documentation/PCI/tph.rst index e8993be64fd6..b6cf22b9bd90 100644 --- a/Documentation/PCI/tph.rst +++ b/Documentation/PCI/tph.rst @@ -79,10 +79,10 @@ To retrieve a Steering Tag for a target memory associated with a specific CPU, use the following function:: int pcie_tph_get_cpu_st(struct pci_dev *pdev, enum tph_mem_type type, - unsigned int cpu_uid, u16 *tag); + unsigned int cpu, u16 *tag); The `type` argument is used to specify the memory type, either volatile -or persistent, of the target memory. The `cpu_uid` argument specifies the +or persistent, of the target memory. The `cpu` argument specifies the CPU where the memory is associated to. After the ST value is retrieved, the device driver can use the following diff --git a/drivers/pci/tph.c b/drivers/pci/tph.c index ca4f97be7538..b67c9ad14bda 100644 --- a/drivers/pci/tph.c +++ b/drivers/pci/tph.c @@ -236,21 +236,27 @@ static int write_tag_to_st_table(struct pci_dev *pdev, int index, u16 tag) * with a specific CPU * @pdev: PCI device * @mem_type: target memory type (volatile or persistent RAM) - * @cpu_uid: associated CPU id + * @cpu: associated CPU id * @tag: Steering Tag to be returned * * Return the Steering Tag for a target memory that is associated with a - * specific CPU as indicated by cpu_uid. + * specific CPU as indicated by cpu. * * Return: 0 if success, otherwise negative value (-errno) */ int pcie_tph_get_cpu_st(struct pci_dev *pdev, enum tph_mem_type mem_type, - unsigned int cpu_uid, u16 *tag) + unsigned int cpu, u16 *tag) { #ifdef CONFIG_ACPI struct pci_dev *rp; acpi_handle rp_acpi_handle; union st_info info; + u32 cpu_uid; + int ret; + + ret = acpi_get_cpu_uid(cpu, &cpu_uid); + if (ret != 0) + return ret; rp = pcie_find_root_port(pdev); if (!rp || !rp->bus || !rp->bus->bridge) @@ -265,9 +271,9 @@ int pcie_tph_get_cpu_st(struct pci_dev *pdev, enum tph_mem_type mem_type, *tag = tph_extract_tag(mem_type, pdev->tph_req_type, &info); - pci_dbg(pdev, "get steering tag: mem_type=%s, cpu_uid=%d, tag=%#04x\n", + pci_dbg(pdev, "get steering tag: mem_type=%s, cpu=%d, tag=%#04x\n", (mem_type == TPH_MEM_TYPE_VM) ? "volatile" : "persistent", - cpu_uid, *tag); + cpu, *tag); return 0; #else diff --git a/include/linux/pci-tph.h b/include/linux/pci-tph.h index ba28140ce670..be68cd17f2f8 100644 --- a/include/linux/pci-tph.h +++ b/include/linux/pci-tph.h @@ -25,7 +25,7 @@ int pcie_tph_set_st_entry(struct pci_dev *pdev, unsigned int index, u16 tag); int pcie_tph_get_cpu_st(struct pci_dev *dev, enum tph_mem_type mem_type, - unsigned int cpu_uid, u16 *tag); + unsigned int cpu, u16 *tag); void pcie_disable_tph(struct pci_dev *pdev); int pcie_enable_tph(struct pci_dev *pdev, int mode); u16 pcie_tph_get_st_table_size(struct pci_dev *pdev); @@ -36,7 +36,7 @@ static inline int pcie_tph_set_st_entry(struct pci_dev *pdev, { return -EINVAL; } static inline int pcie_tph_get_cpu_st(struct pci_dev *dev, enum tph_mem_type mem_type, - unsigned int cpu_uid, u16 *tag) + unsigned int cpu, u16 *tag) { return -EINVAL; } static inline void pcie_disable_tph(struct pci_dev *pdev) { } static inline int pcie_enable_tph(struct pci_dev *pdev, int mode) From a4c6c18e93a1d24df9ab95794cef471c89daefe4 Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Tue, 7 Apr 2026 16:11:40 +0800 Subject: [PATCH 69/73] cpuidle: Extract and export no-lock variants of cpuidle_unregister_device() The cpuidle_unregister_device() function always acquires the internal cpuidle_lock (or pause/resume idle) during their execution. However, in some power notification scenarios (e.g., when old idle states may become unavailable), it is necessary to efficiently disable cpuidle first, then remove and re-create all cpuidle devices for all CPUs. To avoid frequent lock overhead and ensure atomicity across the entire batch operation, the caller needs to hold the cpuidle_lock once outside the loop. To address this, extract the core logic into the new function cpuidle_unregister_device_no_lock() and export it. Signed-off-by: Huisong Li [ rjw: Added missing "inline", subject and changelog tweaks ] Link: https://patch.msgid.link/20260407081141.2493581-2-lihuisong@huawei.com Signed-off-by: Rafael J. Wysocki --- drivers/cpuidle/cpuidle.c | 28 ++++++++++++++++++---------- include/linux/cpuidle.h | 2 ++ 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index c7876e9e024f..1a55542efead 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -714,6 +714,23 @@ int cpuidle_register_device(struct cpuidle_device *dev) EXPORT_SYMBOL_GPL(cpuidle_register_device); +void cpuidle_unregister_device_no_lock(struct cpuidle_device *dev) +{ + if (!dev || dev->registered == 0) + return; + + lockdep_assert_held(&cpuidle_lock); + + cpuidle_disable_device(dev); + + cpuidle_remove_sysfs(dev); + + __cpuidle_unregister_device(dev); + + cpuidle_coupled_unregister_device(dev); +} +EXPORT_SYMBOL_GPL(cpuidle_unregister_device_no_lock); + /** * cpuidle_unregister_device - unregisters a CPU's idle PM feature * @dev: the cpu @@ -724,18 +741,9 @@ void cpuidle_unregister_device(struct cpuidle_device *dev) return; cpuidle_pause_and_lock(); - - cpuidle_disable_device(dev); - - cpuidle_remove_sysfs(dev); - - __cpuidle_unregister_device(dev); - - cpuidle_coupled_unregister_device(dev); - + cpuidle_unregister_device_no_lock(dev); cpuidle_resume_and_unlock(); } - EXPORT_SYMBOL_GPL(cpuidle_unregister_device); /** diff --git a/include/linux/cpuidle.h b/include/linux/cpuidle.h index 4073690504a7..a2485348def3 100644 --- a/include/linux/cpuidle.h +++ b/include/linux/cpuidle.h @@ -188,6 +188,7 @@ extern void cpuidle_driver_state_disabled(struct cpuidle_driver *drv, int idx, extern void cpuidle_unregister_driver(struct cpuidle_driver *drv); extern int cpuidle_register_device(struct cpuidle_device *dev); extern void cpuidle_unregister_device(struct cpuidle_device *dev); +extern void cpuidle_unregister_device_no_lock(struct cpuidle_device *dev); extern int cpuidle_register(struct cpuidle_driver *drv, const struct cpumask *const coupled_cpus); extern void cpuidle_unregister(struct cpuidle_driver *drv); @@ -226,6 +227,7 @@ static inline void cpuidle_unregister_driver(struct cpuidle_driver *drv) { } static inline int cpuidle_register_device(struct cpuidle_device *dev) {return -ENODEV; } static inline void cpuidle_unregister_device(struct cpuidle_device *dev) { } +static inline void cpuidle_unregister_device_no_lock(struct cpuidle_device *dev) {} static inline int cpuidle_register(struct cpuidle_driver *drv, const struct cpumask *const coupled_cpus) {return -ENODEV; } From 07cba0de5598a427cc8a7161202ebf4e79e692bd Mon Sep 17 00:00:00 2001 From: Huisong Li Date: Tue, 7 Apr 2026 16:11:41 +0800 Subject: [PATCH 70/73] ACPI: processor: idle: Reset cpuidle on C-state list changes When a power notification event occurs, existing ACPI idle states may become obsolete. The current implementation only performs a partial update, leaving critical cpuidle parameters, like target_residency_ns and exit_latency_ns, stale. Furthermore, per-CPU cpuidle_device data, including last_residency_ns, states_usage, and the disable flag, are not properly synchronized. Using these stale values leads to incorrect power management decisions. To ensure all parameters are correctly synchronized, modify the notification handling logic: 1. Unregister all cpuidle_device instances to ensure a clean slate. 2. Unregister and re-register the ACPI idle driver. This forces the framework to re-evaluate global state parameters and ensures the driver state matches the new hardware power profile. 3. Re-initialize power information and re-register cpuidle_device for all possible CPUs to restore functional idle management. This complete reset ensures that the cpuidle framework and the underlying ACPI states are perfectly synchronized after a power state change. Signed-off-by: Huisong Li [ rjw: Subject rewrite ] Link: https://patch.msgid.link/20260407081141.2493581-3-lihuisong@huawei.com Signed-off-by: Rafael J. Wysocki --- drivers/acpi/processor_idle.c | 51 +++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index c1cddb4a5887..ee5facccbe10 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -1307,37 +1307,42 @@ int acpi_processor_power_state_has_changed(struct acpi_processor *pr) */ if (pr->id == 0 && cpuidle_get_driver() == &acpi_idle_driver) { - /* Protect against cpu-hotplug */ cpus_read_lock(); + + /* Unregister cpuidle device of all CPUs */ cpuidle_pause_and_lock(); - - /* Disable all cpuidle devices */ - for_each_online_cpu(cpu) { - _pr = per_cpu(processors, cpu); - if (!_pr || !_pr->flags.power_setup_done) - continue; + for_each_possible_cpu(cpu) { dev = per_cpu(acpi_cpuidle_device, cpu); - cpuidle_disable_device(dev); - } - - /* Populate Updated C-state information */ - acpi_processor_get_power_info(pr); - acpi_processor_setup_cpuidle_states(pr); - - /* Enable all cpuidle devices */ - for_each_online_cpu(cpu) { _pr = per_cpu(processors, cpu); - if (!_pr || !_pr->flags.power_setup_done) + if (!_pr || !_pr->flags.power || !dev) continue; - acpi_processor_get_power_info(_pr); - if (_pr->flags.power) { - dev = per_cpu(acpi_cpuidle_device, cpu); - acpi_processor_setup_cpuidle_dev(_pr, dev); - cpuidle_enable_device(dev); - } + + cpuidle_unregister_device_no_lock(dev); + kfree(dev); + _pr->flags.power = 0; } cpuidle_resume_and_unlock(); + + /* + * Unregister ACPI idle driver, reinitialize ACPI idle states + * and register ACPI idle driver again. + */ + acpi_processor_unregister_idle_driver(); + acpi_processor_register_idle_driver(); + + /* + * Reinitialize power information of all CPUs and re-register + * all cpuidle devices. Now idle states is ok to use, can enable + * cpuidle of each CPU safely one by one. + */ + for_each_possible_cpu(cpu) { + _pr = per_cpu(processors, cpu); + if (!_pr) + continue; + acpi_processor_power_init(_pr); + } + cpus_read_unlock(); } From 112b2f978afee7df725cda74a94802f919c61564 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 14 Mar 2026 12:57:23 +0100 Subject: [PATCH 71/73] ACPI: PAD: xen: Convert to a platform driver In all cases in which a struct acpi_driver is used for binding a driver to an ACPI device object, a corresponding platform device is created by the ACPI core and that device is regarded as a proper representation of underlying hardware. Accordingly, a struct platform_driver should be used by driver code to bind to that device. There are multiple reasons why drivers should not bind directly to ACPI device objects [1]. Overall, it is better to bind drivers to platform devices than to their ACPI companions, so convert the Xen ACPI processor aggregator device (PAD) driver to a platform one. While this is not expected to alter functionality, it changes sysfs layout and so it will be visible to user space. Link: https://lore.kernel.org/all/2396510.ElGaqSPkdT@rafael.j.wysocki/ [1] Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/8683270.T7Z3S40VBb@rafael.j.wysocki --- drivers/xen/xen-acpi-pad.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/drivers/xen/xen-acpi-pad.c b/drivers/xen/xen-acpi-pad.c index ede69a5278d3..75a39862c1df 100644 --- a/drivers/xen/xen-acpi-pad.c +++ b/drivers/xen/xen-acpi-pad.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -107,8 +108,9 @@ static void acpi_pad_notify(acpi_handle handle, u32 event, } } -static int acpi_pad_add(struct acpi_device *device) +static int acpi_pad_probe(struct platform_device *pdev) { + struct acpi_device *device = ACPI_COMPANION(&pdev->dev); acpi_status status; strcpy(acpi_device_name(device), ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME); @@ -122,13 +124,13 @@ static int acpi_pad_add(struct acpi_device *device) return 0; } -static void acpi_pad_remove(struct acpi_device *device) +static void acpi_pad_remove(struct platform_device *pdev) { mutex_lock(&xen_cpu_lock); xen_acpi_pad_idle_cpus(0); mutex_unlock(&xen_cpu_lock); - acpi_remove_notify_handler(device->handle, + acpi_remove_notify_handler(ACPI_HANDLE(&pdev->dev), ACPI_DEVICE_NOTIFY, acpi_pad_notify); } @@ -137,13 +139,12 @@ static const struct acpi_device_id pad_device_ids[] = { {"", 0}, }; -static struct acpi_driver acpi_pad_driver = { - .name = "processor_aggregator", - .class = ACPI_PROCESSOR_AGGREGATOR_CLASS, - .ids = pad_device_ids, - .ops = { - .add = acpi_pad_add, - .remove = acpi_pad_remove, +static struct platform_driver acpi_pad_driver = { + .probe = acpi_pad_probe, + .remove = acpi_pad_remove, + .driver = { + .name = "acpi_processor_aggregator", + .acpi_match_table = pad_device_ids, }, }; @@ -157,6 +158,6 @@ static int __init xen_acpi_pad_init(void) if (!xen_running_on_version_or_later(4, 2)) return -ENODEV; - return acpi_bus_register_driver(&acpi_pad_driver); + return platform_driver_register(&acpi_pad_driver); } subsys_initcall(xen_acpi_pad_init); From d37ec2fbab55d732aae48ef2c877fb2d5ab08cd7 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 14 Mar 2026 12:53:01 +0100 Subject: [PATCH 72/73] watchdog: ni903x_wdt: Convert to a platform driver In all cases in which a struct acpi_driver is used for binding a driver to an ACPI device object, a corresponding platform device is created by the ACPI core and that device is regarded as a proper representation of underlying hardware. Accordingly, a struct platform_driver should be used by driver code to bind to that device. There are multiple reasons why drivers should not bind directly to ACPI device objects [1]. In particular, registering a watchdog device under a struct acpi_device is questionable because it causes the watchdog to be hidden in the ACPI bus sysfs hierarchy and it goes against the general rule that a struct acpi_device can only be a parent of another struct acpi_device. Overall, it is better to bind drivers to platform devices than to their ACPI companions, so convert the ni903x_wdt watchdog ACPI driver to a platform one. While this is not expected to alter functionality, it changes sysfs layout and so it will be visible to user space. Note that after this change it actually makes sense to look for the "timeout-sec" property via device_property_read_u32() under the device passed to watchdog_init_timeout() because it has an fwnode handle (unlike a struct acpi_device which is an fwnode itself). Link: https://lore.kernel.org/all/2396510.ElGaqSPkdT@rafael.j.wysocki/ [1] Signed-off-by: Rafael J. Wysocki Reviewed-by: Guenter Roeck Link: https://patch.msgid.link/13996583.uLZWGnKmhe@rafael.j.wysocki --- drivers/watchdog/ni903x_wdt.c | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/drivers/watchdog/ni903x_wdt.c b/drivers/watchdog/ni903x_wdt.c index 045bb72d9a43..8b1b9baa914e 100644 --- a/drivers/watchdog/ni903x_wdt.c +++ b/drivers/watchdog/ni903x_wdt.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #define NIWD_CONTROL 0x01 @@ -177,9 +178,9 @@ static const struct watchdog_ops ni903x_wdd_ops = { .get_timeleft = ni903x_wdd_get_timeleft, }; -static int ni903x_acpi_add(struct acpi_device *device) +static int ni903x_acpi_probe(struct platform_device *pdev) { - struct device *dev = &device->dev; + struct device *dev = &pdev->dev; struct watchdog_device *wdd; struct ni903x_wdt *wdt; acpi_status status; @@ -189,10 +190,10 @@ static int ni903x_acpi_add(struct acpi_device *device) if (!wdt) return -ENOMEM; - device->driver_data = wdt; + platform_set_drvdata(pdev, wdt); wdt->dev = dev; - status = acpi_walk_resources(device->handle, METHOD_NAME__CRS, + status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS, ni903x_resources, wdt); if (ACPI_FAILURE(status) || wdt->io_base == 0) { dev_err(dev, "failed to get resources\n"); @@ -224,9 +225,9 @@ static int ni903x_acpi_add(struct acpi_device *device) return 0; } -static void ni903x_acpi_remove(struct acpi_device *device) +static void ni903x_acpi_remove(struct platform_device *pdev) { - struct ni903x_wdt *wdt = acpi_driver_data(device); + struct ni903x_wdt *wdt = platform_get_drvdata(pdev); ni903x_wdd_stop(&wdt->wdd); watchdog_unregister_device(&wdt->wdd); @@ -238,16 +239,16 @@ static const struct acpi_device_id ni903x_device_ids[] = { }; MODULE_DEVICE_TABLE(acpi, ni903x_device_ids); -static struct acpi_driver ni903x_acpi_driver = { - .name = NIWD_NAME, - .ids = ni903x_device_ids, - .ops = { - .add = ni903x_acpi_add, - .remove = ni903x_acpi_remove, +static struct platform_driver ni903x_acpi_driver = { + .probe = ni903x_acpi_probe, + .remove = ni903x_acpi_remove, + .driver = { + .name = NIWD_NAME, + .acpi_match_table = ni903x_device_ids, }, }; -module_acpi_driver(ni903x_acpi_driver); +module_platform_driver(ni903x_acpi_driver); MODULE_DESCRIPTION("NI 903x Watchdog"); MODULE_AUTHOR("Jeff Westfahl "); From 22dd51bb0a48abe74b32cd4425fca878030d16ca Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Thu, 9 Apr 2026 13:24:28 +0200 Subject: [PATCH 73/73] ACPICA: Update maintainers information Update MAINTAINERS to reflect ACPICA maintainership changes. Signed-off-by: Rafael J. Wysocki Link: https://patch.msgid.link/12876647.O9o76ZdvQC@rafael.j.wysocki --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 7d10988cbc62..cff7e08206d5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -318,7 +318,7 @@ F: drivers/firmware/efi/cper* ACPI COMPONENT ARCHITECTURE (ACPICA) M: "Rafael J. Wysocki" -M: Robert Moore +M: Saket Dumbre L: linux-acpi@vger.kernel.org L: acpica-devel@lists.linux.dev S: Supported