Skip to content

Commit f2edc1f

Browse files
aeglrafaeljw
authored andcommitted
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>
1 parent 55cc6fe commit f2edc1f

2 files changed

Lines changed: 38 additions & 3 deletions

File tree

drivers/acpi/apei/ghes.c

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1484,7 +1484,21 @@ static LIST_HEAD(ghes_nmi);
14841484
static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
14851485
{
14861486
static DEFINE_RAW_SPINLOCK(ghes_notify_lock_nmi);
1487+
bool active_error = false;
14871488
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;
14881502

14891503
if (!atomic_add_unless(&ghes_in_nmi, 1, 1))
14901504
return ret;
@@ -1498,13 +1512,27 @@ static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
14981512
return ret;
14991513
}
15001514

1501-
static void ghes_nmi_add(struct ghes *ghes)
1515+
static int ghes_nmi_add(struct ghes *ghes)
15021516
{
1517+
struct acpi_hest_generic *g = ghes->generic;
1518+
u64 paddr;
1519+
int rc;
1520+
1521+
rc = apei_read(&paddr, &g->error_status_address);
1522+
if (rc)
1523+
return rc;
1524+
1525+
ghes->error_status_vaddr = acpi_os_ioremap(paddr, sizeof(ghes->estatus->block_status));
1526+
if (!ghes->error_status_vaddr)
1527+
return -EINVAL;
1528+
15031529
mutex_lock(&ghes_list_mutex);
15041530
if (list_empty(&ghes_nmi))
15051531
register_nmi_handler(NMI_LOCAL, ghes_notify_nmi, 0, "ghes");
15061532
list_add_rcu(&ghes->list, &ghes_nmi);
15071533
mutex_unlock(&ghes_list_mutex);
1534+
1535+
return 0;
15081536
}
15091537

15101538
static void ghes_nmi_remove(struct ghes *ghes)
@@ -1514,14 +1542,18 @@ static void ghes_nmi_remove(struct ghes *ghes)
15141542
if (list_empty(&ghes_nmi))
15151543
unregister_nmi_handler(NMI_LOCAL, "ghes");
15161544
mutex_unlock(&ghes_list_mutex);
1545+
1546+
if (ghes->error_status_vaddr)
1547+
iounmap(ghes->error_status_vaddr);
1548+
15171549
/*
15181550
* To synchronize with NMI handler, ghes can only be
15191551
* freed after NMI handler finishes.
15201552
*/
15211553
synchronize_rcu();
15221554
}
15231555
#else /* CONFIG_HAVE_ACPI_APEI_NMI */
1524-
static inline void ghes_nmi_add(struct ghes *ghes) { }
1556+
static inline int ghes_nmi_add(struct ghes *ghes) { return -EINVAL; }
15251557
static inline void ghes_nmi_remove(struct ghes *ghes) { }
15261558
#endif /* CONFIG_HAVE_ACPI_APEI_NMI */
15271559

@@ -1689,7 +1721,9 @@ static int ghes_probe(struct platform_device *ghes_dev)
16891721
ghes_sea_add(ghes);
16901722
break;
16911723
case ACPI_HEST_NOTIFY_NMI:
1692-
ghes_nmi_add(ghes);
1724+
rc = ghes_nmi_add(ghes);
1725+
if (rc)
1726+
goto err;
16931727
break;
16941728
case ACPI_HEST_NOTIFY_SOFTWARE_DELEGATED:
16951729
rc = apei_sdei_register_ghes(ghes);

include/acpi/ghes.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ struct ghes {
3030
};
3131
struct device *dev;
3232
struct list_head elist;
33+
void __iomem *error_status_vaddr;
3334
};
3435

3536
struct ghes_estatus_node {

0 commit comments

Comments
 (0)