]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
ACPI: APEI: GHES: Improve ghes_notify_nmi() status check
authorTony Luck <tony.luck@intel.com>
Mon, 12 Jan 2026 03:22:37 +0000 (11:22 +0800)
committerRafael J. Wysocki <rafael.j.wysocki@intel.com>
Wed, 14 Jan 2026 16:05:05 +0000 (17:05 +0100)
ghes_notify_nmi() is called for every NMI and must check whether the NMI was
generated because an error was signalled by platform firmware.

This check is very expensive as for each registered GHES NMI source it reads
from the acpi generic address attached to this error source to get the physical
address of the acpi_hest_generic_status block.  It then checks the "block_status"
to see if an error was logged.

The ACPI/APEI code must create virtual mappings for each of those physical
addresses, and tear them down afterwards. On an Icelake system this takes around
15,000 TSC cycles. Enough to disturb efforts to profile system performance.

If that were not bad enough, there are some atomic accesses in the code path
that will cause cache line bounces between CPUs. A problem that gets worse as
the core count increases.

But BIOS changes neither the acpi generic address nor the physical address of
the acpi_hest_generic_status block. So this walk can be done once when the NMI is
registered to save the virtual address (unmapping if the NMI is ever unregistered).
The "block_status" can be checked directly in the NMI handler. This can be done
without any atomic accesses.

Resulting time to check that there is not an error record is around 900 cycles.

Reported-by: Andi Kleen <andi.kleen@intel.com>
Signed-off-by: Tony Luck <tony.luck@intel.com>
Tested-by: Tony Luck <tony.luck@intel.com>
Signed-off-by: Shuai Xue <xueshuai@linux.alibaba.com>
Reviewed-by: Hanjun Guo <guohanjun@huawei.com>
Link: https://patch.msgid.link/20260112032239.30023-2-xueshuai@linux.alibaba.com
Signed-off-by: Rafael J. Wysocki <rafael.j.wysocki@intel.com>
drivers/acpi/apei/ghes.c
include/acpi/ghes.h

index 77ea7a5b761f12daf2869da997a299efde503bbf..8796013b516679d9044fac277a9f1de31e8990f7 100644 (file)
@@ -1484,7 +1484,21 @@ static LIST_HEAD(ghes_nmi);
 static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
 {
        static DEFINE_RAW_SPINLOCK(ghes_notify_lock_nmi);
+       bool active_error = false;
        int ret = NMI_DONE;
+       struct ghes *ghes;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(ghes, &ghes_nmi, list) {
+               if (ghes->error_status_vaddr && readl(ghes->error_status_vaddr)) {
+                       active_error = true;
+                       break;
+               }
+       }
+       rcu_read_unlock();
+
+       if (!active_error)
+               return ret;
 
        if (!atomic_add_unless(&ghes_in_nmi, 1, 1))
                return ret;
@@ -1498,13 +1512,27 @@ static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
        return ret;
 }
 
-static void ghes_nmi_add(struct ghes *ghes)
+static int ghes_nmi_add(struct ghes *ghes)
 {
+       struct acpi_hest_generic *g = ghes->generic;
+       u64 paddr;
+       int rc;
+
+       rc = apei_read(&paddr, &g->error_status_address);
+       if (rc)
+               return rc;
+
+       ghes->error_status_vaddr = acpi_os_ioremap(paddr, sizeof(ghes->estatus->block_status));
+       if (!ghes->error_status_vaddr)
+               return -EINVAL;
+
        mutex_lock(&ghes_list_mutex);
        if (list_empty(&ghes_nmi))
                register_nmi_handler(NMI_LOCAL, ghes_notify_nmi, 0, "ghes");
        list_add_rcu(&ghes->list, &ghes_nmi);
        mutex_unlock(&ghes_list_mutex);
+
+       return 0;
 }
 
 static void ghes_nmi_remove(struct ghes *ghes)
@@ -1514,6 +1542,10 @@ static void ghes_nmi_remove(struct ghes *ghes)
        if (list_empty(&ghes_nmi))
                unregister_nmi_handler(NMI_LOCAL, "ghes");
        mutex_unlock(&ghes_list_mutex);
+
+       if (ghes->error_status_vaddr)
+               iounmap(ghes->error_status_vaddr);
+
        /*
         * To synchronize with NMI handler, ghes can only be
         * freed after NMI handler finishes.
@@ -1521,7 +1553,7 @@ static void ghes_nmi_remove(struct ghes *ghes)
        synchronize_rcu();
 }
 #else /* CONFIG_HAVE_ACPI_APEI_NMI */
-static inline void ghes_nmi_add(struct ghes *ghes) { }
+static inline int ghes_nmi_add(struct ghes *ghes) { return -EINVAL; }
 static inline void ghes_nmi_remove(struct ghes *ghes) { }
 #endif /* CONFIG_HAVE_ACPI_APEI_NMI */
 
@@ -1689,7 +1721,9 @@ static int ghes_probe(struct platform_device *ghes_dev)
                ghes_sea_add(ghes);
                break;
        case ACPI_HEST_NOTIFY_NMI:
-               ghes_nmi_add(ghes);
+               rc = ghes_nmi_add(ghes);
+               if (rc)
+                       goto err;
                break;
        case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED:
                rc = apei_sdei_register_ghes(ghes);
index 93db60da5934e7ce12a0f0fb366d0d9d051424e7..7bea522c065753d2233c93487242b6743b3de0e7 100644 (file)
@@ -30,6 +30,7 @@ struct ghes {
        };
        struct device *dev;
        struct list_head elist;
+       void __iomem *error_status_vaddr;
 };
 
 struct ghes_estatus_node {