From 738e97eefad8bf9137104b19caf23cda9865f7df Mon Sep 17 00:00:00 2001 From: Shiraz Hashim Date: Fri, 31 Jul 2026 00:37:50 +0530 Subject: [PATCH] ras: aest: fix duplicate cpu_pm notifier registration aest_device_probe() unconditionally calls aest_cpu_pm_init(), which registers the driver's single static cpu_pm notifier_block. On targets that expose more than one AEST platform device (e.g. QCS8300/Monaco, which has aest-processor-0, aest-l3-cluster0 and aest-l3-cluster1), the "AEST" platform driver probes once per device, so the same notifier is registered multiple times. cpu_pm's notifier_chain_register() detects the re-add of an already-linked notifier_block and fires WARN(1, "notifier callback %ps already registered", ...), producing a call trace on every boot for the 2nd and later probes. Refcount aest_cpu_pm_init()/aest_cpu_pm_exit() so the notifier is registered exactly once regardless of how many AEST devices this target exposes, and unregistered only once every probed device has been torn down. aest_device_remove() now balances aest_cpu_pm_init() with aest_cpu_pm_exit() for every device type, and the now-redundant direct aest_cpu_pm_exit() call in aest_exit() is removed since platform_driver_unregister() already drives aest_device_remove() for each bound device. Fixes: 0132f7bc9b90 ("WORKAROUND: Move CPU PM notifier registration to Probe") Signed-off-by: Shiraz Hashim --- drivers/ras/aest/aest-core.c | 57 ++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 6 deletions(-) diff --git a/drivers/ras/aest/aest-core.c b/drivers/ras/aest/aest-core.c index 3e1344c937f23..14ee6d178675f 100644 --- a/drivers/ras/aest/aest-core.c +++ b/drivers/ras/aest/aest-core.c @@ -110,17 +110,52 @@ static struct notifier_block aest_cpu_pm_nb = { .notifier_call = aest_cpu_pm_notify, }; -static void aest_cpu_pm_init(void) +/* + * aest_device_probe() runs once per AEST platform device, but all + * instances share this single, global CPU PM notifier. Refcount + * registration/teardown so it is added to the cpu_pm notifier chain + * exactly once no matter how many AEST devices this target exposes, + * and removed only once every probed device has been torn down. + * aest_cpu_pm_registered tracks whether the chain actually holds the + * notifier, so a failed registration is never mistakenly unregistered. + * Only the probe/remove call that crosses the 0->1 or 1->0 refcount + * boundary ever touches aest_cpu_pm_registered, so no separate lock + * is needed for it. + */ +static atomic_t aest_cpu_pm_refcount = ATOMIC_INIT(0); +static bool aest_cpu_pm_registered; + +/* + * Failure is logged, not propagated to probe(): this notifier call has + * no real failure path today, and failing the whole AEST probe over + * this one errata-workaround registration would be a worse outcome + * than proceeding without it. + */ +static int aest_cpu_pm_init(void) { - cpu_pm_register_notifier(&aest_cpu_pm_nb); + int ret = 0; + + if (atomic_inc_return(&aest_cpu_pm_refcount) == 1) { + ret = cpu_pm_register_notifier(&aest_cpu_pm_nb); + if (!ret) + aest_cpu_pm_registered = true; + else + pr_err("Failed to register cpu_pm notifier: %d\n", ret); + } + + return ret; } static void aest_cpu_pm_exit(void) { - cpu_pm_unregister_notifier(&aest_cpu_pm_nb); + if (atomic_dec_return(&aest_cpu_pm_refcount) == 0 && + aest_cpu_pm_registered) { + cpu_pm_unregister_notifier(&aest_cpu_pm_nb); + aest_cpu_pm_registered = false; + } } #else -static inline void aest_cpu_pm_init(void) { } +static inline int aest_cpu_pm_init(void) { return 0; } static inline void aest_cpu_pm_exit(void) { } #endif /* CONFIG_CPU_PM */ @@ -831,8 +866,10 @@ static void aest_device_remove(struct platform_device *pdev) platform_set_drvdata(pdev, NULL); - if (adev->type != ACPI_AEST_PROCESSOR_ERROR_NODE) + if (adev->type != ACPI_AEST_PROCESSOR_ERROR_NODE) { + aest_cpu_pm_exit(); return; + } on_each_cpu(aest_offline_oncore_dev, adev->adev_oncore, 1); @@ -840,6 +877,8 @@ static void aest_device_remove(struct platform_device *pdev) if (adev->irq[i]) free_percpu_irq(adev->irq[i], adev->adev_oncore); } + + aest_cpu_pm_exit(); } static char *alloc_aest_node_name(struct aest_node *node) @@ -1197,7 +1236,13 @@ static void __exit aest_exit(void) #ifdef CONFIG_DEBUG_FS debugfs_remove(aest_debugfs); #endif - aest_cpu_pm_exit(); + /* + * platform_driver_unregister() tears down every bound AEST + * device, and each device's aest_device_remove() already + * balances its aest_cpu_pm_init() with aest_cpu_pm_exit(). + * Do not call aest_cpu_pm_exit() here too - it would double + * count against removes that have not happened yet. + */ platform_driver_unregister(&aest_driver); } module_exit(aest_exit);