Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

ACPI: APEI: GHES: Improve ghes_notify_nmi() status check

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>

authored by

Tony Luck and committed by
Rafael J. Wysocki
f2edc1fb 55cc6fe5

+38 -3
+37 -3
drivers/acpi/apei/ghes.c
··· 1484 1484 static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs) 1485 1485 { 1486 1486 static DEFINE_RAW_SPINLOCK(ghes_notify_lock_nmi); 1487 + bool active_error = false; 1487 1488 int ret = NMI_DONE; 1489 + struct ghes *ghes; 1490 + 1491 + rcu_read_lock(); 1492 + list_for_each_entry_rcu(ghes, &ghes_nmi, list) { 1493 + if (ghes->error_status_vaddr && readl(ghes->error_status_vaddr)) { 1494 + active_error = true; 1495 + break; 1496 + } 1497 + } 1498 + rcu_read_unlock(); 1499 + 1500 + if (!active_error) 1501 + return ret; 1488 1502 1489 1503 if (!atomic_add_unless(&ghes_in_nmi, 1, 1)) 1490 1504 return ret; ··· 1512 1498 return ret; 1513 1499 } 1514 1500 1515 - static void ghes_nmi_add(struct ghes *ghes) 1501 + static int ghes_nmi_add(struct ghes *ghes) 1516 1502 { 1503 + struct acpi_hest_generic *g = ghes->generic; 1504 + u64 paddr; 1505 + int rc; 1506 + 1507 + rc = apei_read(&paddr, &g->error_status_address); 1508 + if (rc) 1509 + return rc; 1510 + 1511 + ghes->error_status_vaddr = acpi_os_ioremap(paddr, sizeof(ghes->estatus->block_status)); 1512 + if (!ghes->error_status_vaddr) 1513 + return -EINVAL; 1514 + 1517 1515 mutex_lock(&ghes_list_mutex); 1518 1516 if (list_empty(&ghes_nmi)) 1519 1517 register_nmi_handler(NMI_LOCAL, ghes_notify_nmi, 0, "ghes"); 1520 1518 list_add_rcu(&ghes->list, &ghes_nmi); 1521 1519 mutex_unlock(&ghes_list_mutex); 1520 + 1521 + return 0; 1522 1522 } 1523 1523 1524 1524 static void ghes_nmi_remove(struct ghes *ghes) ··· 1542 1514 if (list_empty(&ghes_nmi)) 1543 1515 unregister_nmi_handler(NMI_LOCAL, "ghes"); 1544 1516 mutex_unlock(&ghes_list_mutex); 1517 + 1518 + if (ghes->error_status_vaddr) 1519 + iounmap(ghes->error_status_vaddr); 1520 + 1545 1521 /* 1546 1522 * To synchronize with NMI handler, ghes can only be 1547 1523 * freed after NMI handler finishes. ··· 1553 1521 synchronize_rcu(); 1554 1522 } 1555 1523 #else /* CONFIG_HAVE_ACPI_APEI_NMI */ 1556 - static inline void ghes_nmi_add(struct ghes *ghes) { } 1524 + static inline int ghes_nmi_add(struct ghes *ghes) { return -EINVAL; } 1557 1525 static inline void ghes_nmi_remove(struct ghes *ghes) { } 1558 1526 #endif /* CONFIG_HAVE_ACPI_APEI_NMI */ 1559 1527 ··· 1721 1689 ghes_sea_add(ghes); 1722 1690 break; 1723 1691 case ACPI_HEST_NOTIFY_NMI: 1724 - ghes_nmi_add(ghes); 1692 + rc = ghes_nmi_add(ghes); 1693 + if (rc) 1694 + goto err; 1725 1695 break; 1726 1696 case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED: 1727 1697 rc = apei_sdei_register_ghes(ghes);
+1
include/acpi/ghes.h
··· 30 30 }; 31 31 struct device *dev; 32 32 struct list_head elist; 33 + void __iomem *error_status_vaddr; 33 34 }; 34 35 35 36 struct ghes_estatus_node {