Linux ACPI
 help / color / mirror / Atom feed
* [PATCH 0/3] ACPI: APEI: GHES: Performance improvements for error notification handlers
@ 2025-12-03 13:02 Shuai Xue
  2025-12-03 13:02 ` [PATCH 1/3] ACPI: APEI: GHES: Improve ghes_notify_nmi() status check Shuai Xue
                   ` (3 more replies)
  0 siblings, 4 replies; 9+ messages in thread
From: Shuai Xue @ 2025-12-03 13:02 UTC (permalink / raw)
  To: tony.luck, guohanjun, mchehab, yazen.ghannam
  Cc: dave.jiang, Smita.KoralahalliChannabasappa, leitao, pengdonglin,
	xueshuai, baolin.wang, benjamin.cheatham, bp, dan.j.williams,
	james.morse, lenb, linux-acpi, linux-kernel, rafael, zhuo.song

This patch series improves the performance of GHES error notification handlers
(NMI and SEA) by optimizing how they check for active error conditions.

Currently, both ghes_notify_nmi() and ghes_notify_sea() perform expensive
operations on each invocation to determine if there are actual error records
to process. This includes mapping/unmapping physical addresses and accessing
hardware registers, which causes significant overhead especially on systems
with many cores.

The optimizations introduced in this series:
1. Pre-map error status registers during initialization
2. Directly check for active errors using mapped virtual addresses
3. Extract common functionality into reusable helper functions
4. Apply the same optimization to both NMI and SEA handlers

These changes significantly reduce the overhead of error checking:
- NMI handler: From ~15,000 TSC cycles to ~900 cycles
- SEA handler: From 8,138.3 ns to a much faster check

The initial idea for this optimization came from Tony Luck [1], who identified
and implemented the approach for the NMI handler. This series extends the
same concept to the SEA handler and refactors common code into shared helpers.

Patch 1 (Tony Luck): Improves ghes_notify_nmi() status check by pre-mapping
                     error status registers and avoiding repeated mappings.

Patch 2 (Shuai Xue): Extracts common helper functions for error status handling
                     to eliminate code duplication.

Patch 3 (Shuai Xue): Applies the same optimization to ghes_notify_sea() to improve
                     ARMv8 system performance.

https://lore.kernel.org/lkml/20251103230547.8715-1-tony.luck@intel.com/

Shuai Xue (2):
  ACPI: APEI: GHES: Extract helper functions for error status handling
  ACPI: APEI: GHES: Improve ghes_notify_sea() status check

Tony Luck (1):
  ACPI: APEI: GHES: Improve ghes_notify_nmi() status check

 drivers/acpi/apei/ghes.c | 111 ++++++++++++++++++++++++++++++++++++---
 include/acpi/ghes.h      |   1 +
 2 files changed, 106 insertions(+), 6 deletions(-)

-- 
2.39.3


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH 1/3] ACPI: APEI: GHES: Improve ghes_notify_nmi() status check
  2025-12-03 13:02 [PATCH 0/3] ACPI: APEI: GHES: Performance improvements for error notification handlers Shuai Xue
@ 2025-12-03 13:02 ` Shuai Xue
  2025-12-17  1:13   ` Hanjun Guo
  2025-12-03 13:02 ` [PATCH 2/3] ACPI: APEI: GHES: Extract helper functions for error status handling Shuai Xue
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 9+ messages in thread
From: Shuai Xue @ 2025-12-03 13:02 UTC (permalink / raw)
  To: tony.luck, guohanjun, mchehab, yazen.ghannam
  Cc: dave.jiang, Smita.KoralahalliChannabasappa, leitao, pengdonglin,
	xueshuai, baolin.wang, benjamin.cheatham, bp, dan.j.williams,
	james.morse, lenb, linux-acpi, linux-kernel, rafael, zhuo.song

From: Tony Luck <tony.luck@intel.com>

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>
---
 drivers/acpi/apei/ghes.c | 39 ++++++++++++++++++++++++++++++++++++---
 include/acpi/ghes.h      |  1 +
 2 files changed, 37 insertions(+), 3 deletions(-)

diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index 97ee19f2cae0..62713b612865 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -1425,7 +1425,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;
@@ -1439,13 +1453,26 @@ 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 AE_BAD_ADDRESS;
+
 	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)
@@ -1455,6 +1482,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.
@@ -1462,7 +1493,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 */
 
@@ -1630,7 +1661,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);
diff --git a/include/acpi/ghes.h b/include/acpi/ghes.h
index ebd21b05fe6e..58655d313a1f 100644
--- a/include/acpi/ghes.h
+++ b/include/acpi/ghes.h
@@ -29,6 +29,7 @@ struct ghes {
 	};
 	struct device *dev;
 	struct list_head elist;
+	void __iomem *error_status_vaddr;
 };
 
 struct ghes_estatus_node {
-- 
2.39.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 2/3] ACPI: APEI: GHES: Extract helper functions for error status handling
  2025-12-03 13:02 [PATCH 0/3] ACPI: APEI: GHES: Performance improvements for error notification handlers Shuai Xue
  2025-12-03 13:02 ` [PATCH 1/3] ACPI: APEI: GHES: Improve ghes_notify_nmi() status check Shuai Xue
@ 2025-12-03 13:02 ` Shuai Xue
  2025-12-17  1:25   ` Hanjun Guo
  2025-12-03 13:02 ` [PATCH 3/3] ACPI: APEI: GHES: Improve ghes_notify_sea() status check Shuai Xue
  2025-12-11 18:44 ` [PATCH 0/3] ACPI: APEI: GHES: Performance improvements for error notification handlers Luck, Tony
  3 siblings, 1 reply; 9+ messages in thread
From: Shuai Xue @ 2025-12-03 13:02 UTC (permalink / raw)
  To: tony.luck, guohanjun, mchehab, yazen.ghannam
  Cc: dave.jiang, Smita.KoralahalliChannabasappa, leitao, pengdonglin,
	xueshuai, baolin.wang, benjamin.cheatham, bp, dan.j.williams,
	james.morse, lenb, linux-acpi, linux-kernel, rafael, zhuo.song

Refactors the GHES driver by extracting common functionality into
reusable helper functions:

1. ghes_has_active_errors() - Checks if any error sources in a given list
   have active errors
2. ghes_map_error_status() - Maps error status address to virtual address
3. ghes_unmap_error_status() - Unmaps error status virtual address

These helpers eliminate code duplication in the NMI path and prepare for
similar usage in the SEA path in a subsequent patch.

No functional change intended.

Signed-off-by: Shuai Xue <xueshuai@linux.alibaba.com>
---
 drivers/acpi/apei/ghes.c | 92 +++++++++++++++++++++++++++++++---------
 1 file changed, 72 insertions(+), 20 deletions(-)

diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index 62713b612865..2c7f3ca6ce50 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -1375,6 +1375,74 @@ static int ghes_in_nmi_spool_from_list(struct list_head *rcu_list,
 	return ret;
 }
 
+/**
+ * ghes_has_active_errors - Check if there are active errors in error sources
+ * @ghes_list: List of GHES entries to check for active errors
+ *
+ * This function iterates through all GHES entries in the given list and
+ * checks if any of them has active error status by reading the error
+ * status register.
+ *
+ * Return: true if at least one source has active error, false otherwise.
+ */
+static bool __maybe_unused ghes_has_active_errors(struct list_head *ghes_list)
+{
+	bool active_error = false;
+	struct ghes *ghes;
+
+	rcu_read_lock();
+	list_for_each_entry_rcu(ghes, ghes_list, list) {
+		if (ghes->error_status_vaddr &&
+		    readl(ghes->error_status_vaddr)) {
+			active_error = true;
+			break;
+		}
+	}
+	rcu_read_unlock();
+
+	return active_error;
+}
+
+/**
+ * ghes_map_error_status - Map error status address to virtual address
+ * @ghes: pointer to GHES structure
+ *
+ * Reads the error status address from ACPI HEST table and maps it to a virtual
+ * address that can be accessed by the kernel.
+ *
+ * Return: 0 on success, error code on failure.
+ */
+static int __maybe_unused ghes_map_error_status(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 AE_BAD_ADDRESS;
+
+	return 0;
+}
+
+/**
+ * ghes_unmap_error_status - Unmap error status virtual address
+ * @ghes: pointer to GHES structure
+ *
+ * Unmaps the error status address if it was previously mapped.
+ */
+static void __maybe_unused ghes_unmap_error_status(struct ghes *ghes)
+{
+	if (ghes->error_status_vaddr) {
+		iounmap(ghes->error_status_vaddr);
+		ghes->error_status_vaddr = NULL;
+	}
+}
+
 #ifdef CONFIG_ACPI_APEI_SEA
 static LIST_HEAD(ghes_sea);
 
@@ -1425,20 +1493,9 @@ 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)
+	if (!ghes_has_active_errors(&ghes_nmi))
 		return ret;
 
 	if (!atomic_add_unless(&ghes_in_nmi, 1, 1))
@@ -1455,16 +1512,11 @@ static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
 
 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);
+	rc = ghes_map_error_status(ghes);
 	if (rc)
 		return rc;
-	ghes->error_status_vaddr = acpi_os_ioremap(paddr, sizeof(ghes->estatus->block_status));
-	if (!ghes->error_status_vaddr)
-		return AE_BAD_ADDRESS;
 
 	mutex_lock(&ghes_list_mutex);
 	if (list_empty(&ghes_nmi))
@@ -1483,8 +1535,7 @@ static void ghes_nmi_remove(struct ghes *ghes)
 		unregister_nmi_handler(NMI_LOCAL, "ghes");
 	mutex_unlock(&ghes_list_mutex);
 
-	if (ghes->error_status_vaddr)
-		iounmap(ghes->error_status_vaddr);
+	ghes_unmap_error_status(ghes);
 
 	/*
 	 * To synchronize with NMI handler, ghes can only be
@@ -1492,6 +1543,7 @@ static void ghes_nmi_remove(struct ghes *ghes)
 	 */
 	synchronize_rcu();
 }
+
 #else /* CONFIG_HAVE_ACPI_APEI_NMI */
 static inline int ghes_nmi_add(struct ghes *ghes) { return -EINVAL; }
 static inline void ghes_nmi_remove(struct ghes *ghes) { }
-- 
2.39.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* [PATCH 3/3] ACPI: APEI: GHES: Improve ghes_notify_sea() status check
  2025-12-03 13:02 [PATCH 0/3] ACPI: APEI: GHES: Performance improvements for error notification handlers Shuai Xue
  2025-12-03 13:02 ` [PATCH 1/3] ACPI: APEI: GHES: Improve ghes_notify_nmi() status check Shuai Xue
  2025-12-03 13:02 ` [PATCH 2/3] ACPI: APEI: GHES: Extract helper functions for error status handling Shuai Xue
@ 2025-12-03 13:02 ` Shuai Xue
  2025-12-11 18:44 ` [PATCH 0/3] ACPI: APEI: GHES: Performance improvements for error notification handlers Luck, Tony
  3 siblings, 0 replies; 9+ messages in thread
From: Shuai Xue @ 2025-12-03 13:02 UTC (permalink / raw)
  To: tony.luck, guohanjun, mchehab, yazen.ghannam
  Cc: dave.jiang, Smita.KoralahalliChannabasappa, leitao, pengdonglin,
	xueshuai, baolin.wang, benjamin.cheatham, bp, dan.j.williams,
	james.morse, lenb, linux-acpi, linux-kernel, rafael, zhuo.song

Performance testing on ARMv8 systems shows significant overhead in error
status handling in SEA error handling.

- ghes_peek_estatus(): 8,138.3 ns (21,160 cycles).
- ghes_clear_estatus(): 2,038.3 ns (5,300 cycles).

Apply the same optimization used in ghes_notify_nmi() to
ghes_notify_sea() by checking for active errors before processing,

Signed-off-by: Shuai Xue <xueshuai@linux.alibaba.com>
---
 drivers/acpi/apei/ghes.c | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
index 2c7f3ca6ce50..0be46d63db53 100644
--- a/drivers/acpi/apei/ghes.c
+++ b/drivers/acpi/apei/ghes.c
@@ -1455,6 +1455,9 @@ int ghes_notify_sea(void)
 	static DEFINE_RAW_SPINLOCK(ghes_notify_lock_sea);
 	int rv;
 
+	if (!ghes_has_active_errors(&ghes_sea))
+		return -ENOENT;
+
 	raw_spin_lock(&ghes_notify_lock_sea);
 	rv = ghes_in_nmi_spool_from_list(&ghes_sea, FIX_APEI_GHES_SEA);
 	raw_spin_unlock(&ghes_notify_lock_sea);
@@ -1462,11 +1465,19 @@ int ghes_notify_sea(void)
 	return rv;
 }
 
-static void ghes_sea_add(struct ghes *ghes)
+static int ghes_sea_add(struct ghes *ghes)
 {
+	int rc;
+
+	rc = ghes_map_error_status(ghes);
+	if (rc)
+		return rc;
+
 	mutex_lock(&ghes_list_mutex);
 	list_add_rcu(&ghes->list, &ghes_sea);
 	mutex_unlock(&ghes_list_mutex);
+
+	return 0;
 }
 
 static void ghes_sea_remove(struct ghes *ghes)
@@ -1474,10 +1485,11 @@ static void ghes_sea_remove(struct ghes *ghes)
 	mutex_lock(&ghes_list_mutex);
 	list_del_rcu(&ghes->list);
 	mutex_unlock(&ghes_list_mutex);
+	ghes_unmap_error_status(ghes);
 	synchronize_rcu();
 }
 #else /* CONFIG_ACPI_APEI_SEA */
-static inline void ghes_sea_add(struct ghes *ghes) { }
+static inline int ghes_sea_add(struct ghes *ghes) { return -EINVAL; }
 static inline void ghes_sea_remove(struct ghes *ghes) { }
 #endif /* CONFIG_ACPI_APEI_SEA */
 
@@ -1710,7 +1722,9 @@ static int ghes_probe(struct platform_device *ghes_dev)
 		break;
 
 	case ACPI_HEST_NOTIFY_SEA:
-		ghes_sea_add(ghes);
+		rc = ghes_sea_add(ghes);
+		if (rc)
+			goto err;
 		break;
 	case ACPI_HEST_NOTIFY_NMI:
 		rc = ghes_nmi_add(ghes);
-- 
2.39.3


^ permalink raw reply related	[flat|nested] 9+ messages in thread

* Re: [PATCH 0/3] ACPI: APEI: GHES: Performance improvements for error notification handlers
  2025-12-03 13:02 [PATCH 0/3] ACPI: APEI: GHES: Performance improvements for error notification handlers Shuai Xue
                   ` (2 preceding siblings ...)
  2025-12-03 13:02 ` [PATCH 3/3] ACPI: APEI: GHES: Improve ghes_notify_sea() status check Shuai Xue
@ 2025-12-11 18:44 ` Luck, Tony
  3 siblings, 0 replies; 9+ messages in thread
From: Luck, Tony @ 2025-12-11 18:44 UTC (permalink / raw)
  To: Shuai Xue
  Cc: guohanjun, mchehab, yazen.ghannam, dave.jiang,
	Smita.KoralahalliChannabasappa, leitao, pengdonglin, baolin.wang,
	benjamin.cheatham, bp, dan.j.williams, james.morse, lenb,
	linux-acpi, linux-kernel, rafael, zhuo.song

On Wed, Dec 03, 2025 at 09:02:50PM +0800, Shuai Xue wrote:
> This patch series improves the performance of GHES error notification handlers
> (NMI and SEA) by optimizing how they check for active error conditions.
> 
> Currently, both ghes_notify_nmi() and ghes_notify_sea() perform expensive
> operations on each invocation to determine if there are actual error records
> to process. This includes mapping/unmapping physical addresses and accessing
> hardware registers, which causes significant overhead especially on systems
> with many cores.
> 
> The optimizations introduced in this series:
> 1. Pre-map error status registers during initialization
> 2. Directly check for active errors using mapped virtual addresses
> 3. Extract common functionality into reusable helper functions
> 4. Apply the same optimization to both NMI and SEA handlers
> 
> These changes significantly reduce the overhead of error checking:
> - NMI handler: From ~15,000 TSC cycles to ~900 cycles
> - SEA handler: From 8,138.3 ns to a much faster check
> 
> The initial idea for this optimization came from Tony Luck [1], who identified
> and implemented the approach for the NMI handler. This series extends the
> same concept to the SEA handler and refactors common code into shared helpers.
> 
> Patch 1 (Tony Luck): Improves ghes_notify_nmi() status check by pre-mapping
>                      error status registers and avoiding repeated mappings.
> 
> Patch 2 (Shuai Xue): Extracts common helper functions for error status handling
>                      to eliminate code duplication.
> 
> Patch 3 (Shuai Xue): Applies the same optimization to ghes_notify_sea() to improve
>                      ARMv8 system performance.
> 
> https://lore.kernel.org/lkml/20251103230547.8715-1-tony.luck@intel.com/

Thanks for the improvements to extend coverage to SEA.

Tested-by: Tony Luck <tony.luck@intel.com>
Reviewed-by: Tony Luck <tony.luck@intel.com>

-Tony

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/3] ACPI: APEI: GHES: Improve ghes_notify_nmi() status check
  2025-12-03 13:02 ` [PATCH 1/3] ACPI: APEI: GHES: Improve ghes_notify_nmi() status check Shuai Xue
@ 2025-12-17  1:13   ` Hanjun Guo
  2025-12-18  6:01     ` Shuai Xue
  0 siblings, 1 reply; 9+ messages in thread
From: Hanjun Guo @ 2025-12-17  1:13 UTC (permalink / raw)
  To: Shuai Xue, tony.luck, mchehab, yazen.ghannam
  Cc: dave.jiang, Smita.KoralahalliChannabasappa, leitao, pengdonglin,
	baolin.wang, benjamin.cheatham, bp, dan.j.williams, james.morse,
	lenb, linux-acpi, linux-kernel, rafael, zhuo.song

Hi Shuai,

Some minor comments inline.

On 2025/12/3 21:02, Shuai Xue wrote:
> From: Tony Luck <tony.luck@intel.com>
> 
> 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>
> ---
>   drivers/acpi/apei/ghes.c | 39 ++++++++++++++++++++++++++++++++++++---
>   include/acpi/ghes.h      |  1 +
>   2 files changed, 37 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
> index 97ee19f2cae0..62713b612865 100644
> --- a/drivers/acpi/apei/ghes.c
> +++ b/drivers/acpi/apei/ghes.c
> @@ -1425,7 +1425,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;
> @@ -1439,13 +1453,26 @@ 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;

It will be good to add a empty line here.

> +	ghes->error_status_vaddr = acpi_os_ioremap(paddr, sizeof(ghes->estatus->block_status));
> +	if (!ghes->error_status_vaddr)
> +		return AE_BAD_ADDRESS;

It's static int for ghes_nmi_add(), and AE_BAD_ADDRESS is the type of
acpi_status, it's better to return -EINVAL here.

Thanks
Hanjun

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/3] ACPI: APEI: GHES: Extract helper functions for error status handling
  2025-12-03 13:02 ` [PATCH 2/3] ACPI: APEI: GHES: Extract helper functions for error status handling Shuai Xue
@ 2025-12-17  1:25   ` Hanjun Guo
  2025-12-18  6:03     ` Shuai Xue
  0 siblings, 1 reply; 9+ messages in thread
From: Hanjun Guo @ 2025-12-17  1:25 UTC (permalink / raw)
  To: Shuai Xue, tony.luck, mchehab, yazen.ghannam
  Cc: dave.jiang, Smita.KoralahalliChannabasappa, leitao, pengdonglin,
	baolin.wang, benjamin.cheatham, bp, dan.j.williams, james.morse,
	lenb, linux-acpi, linux-kernel, rafael, zhuo.song

On 2025/12/3 21:02, Shuai Xue wrote:
> Refactors the GHES driver by extracting common functionality into
> reusable helper functions:
> 
> 1. ghes_has_active_errors() - Checks if any error sources in a given list
>     have active errors
> 2. ghes_map_error_status() - Maps error status address to virtual address
> 3. ghes_unmap_error_status() - Unmaps error status virtual address
> 
> These helpers eliminate code duplication in the NMI path and prepare for
> similar usage in the SEA path in a subsequent patch.
> 
> No functional change intended.
> 
> Signed-off-by: Shuai Xue <xueshuai@linux.alibaba.com>
> ---
>   drivers/acpi/apei/ghes.c | 92 +++++++++++++++++++++++++++++++---------
>   1 file changed, 72 insertions(+), 20 deletions(-)
> 
> diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
> index 62713b612865..2c7f3ca6ce50 100644
> --- a/drivers/acpi/apei/ghes.c
> +++ b/drivers/acpi/apei/ghes.c
> @@ -1375,6 +1375,74 @@ static int ghes_in_nmi_spool_from_list(struct list_head *rcu_list,
>   	return ret;
>   }
>   
> +/**
> + * ghes_has_active_errors - Check if there are active errors in error sources
> + * @ghes_list: List of GHES entries to check for active errors
> + *
> + * This function iterates through all GHES entries in the given list and
> + * checks if any of them has active error status by reading the error
> + * status register.
> + *
> + * Return: true if at least one source has active error, false otherwise.
> + */
> +static bool __maybe_unused ghes_has_active_errors(struct list_head *ghes_list)
> +{
> +	bool active_error = false;
> +	struct ghes *ghes;
> +
> +	rcu_read_lock();
> +	list_for_each_entry_rcu(ghes, ghes_list, list) {
> +		if (ghes->error_status_vaddr &&
> +		    readl(ghes->error_status_vaddr)) {
> +			active_error = true;
> +			break;
> +		}
> +	}
> +	rcu_read_unlock();
> +
> +	return active_error;
> +}
> +
> +/**
> + * ghes_map_error_status - Map error status address to virtual address
> + * @ghes: pointer to GHES structure
> + *
> + * Reads the error status address from ACPI HEST table and maps it to a virtual
> + * address that can be accessed by the kernel.
> + *
> + * Return: 0 on success, error code on failure.
> + */
> +static int __maybe_unused ghes_map_error_status(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 AE_BAD_ADDRESS;

Same update here.

> +
> +	return 0;
> +}
> +
> +/**
> + * ghes_unmap_error_status - Unmap error status virtual address
> + * @ghes: pointer to GHES structure
> + *
> + * Unmaps the error status address if it was previously mapped.
> + */
> +static void __maybe_unused ghes_unmap_error_status(struct ghes *ghes)
> +{
> +	if (ghes->error_status_vaddr) {
> +		iounmap(ghes->error_status_vaddr);
> +		ghes->error_status_vaddr = NULL;
> +	}
> +}
> +
>   #ifdef CONFIG_ACPI_APEI_SEA
>   static LIST_HEAD(ghes_sea);
>   
> @@ -1425,20 +1493,9 @@ 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)
> +	if (!ghes_has_active_errors(&ghes_nmi))
>   		return ret;
>   
>   	if (!atomic_add_unless(&ghes_in_nmi, 1, 1))
> @@ -1455,16 +1512,11 @@ static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
>   
>   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);
> +	rc = ghes_map_error_status(ghes);
>   	if (rc)
>   		return rc;
> -	ghes->error_status_vaddr = acpi_os_ioremap(paddr, sizeof(ghes->estatus->block_status));
> -	if (!ghes->error_status_vaddr)
> -		return AE_BAD_ADDRESS;
>   
>   	mutex_lock(&ghes_list_mutex);
>   	if (list_empty(&ghes_nmi))
> @@ -1483,8 +1535,7 @@ static void ghes_nmi_remove(struct ghes *ghes)
>   		unregister_nmi_handler(NMI_LOCAL, "ghes");
>   	mutex_unlock(&ghes_list_mutex);
>   
> -	if (ghes->error_status_vaddr)
> -		iounmap(ghes->error_status_vaddr);
> +	ghes_unmap_error_status(ghes);
>   
>   	/*
>   	 * To synchronize with NMI handler, ghes can only be
> @@ -1492,6 +1543,7 @@ static void ghes_nmi_remove(struct ghes *ghes)
>   	 */
>   	synchronize_rcu();
>   }
> +

I think it's not belong to this patch.

Thanks
Hanjun

>   #else /* CONFIG_HAVE_ACPI_APEI_NMI */
>   static inline int ghes_nmi_add(struct ghes *ghes) { return -EINVAL; }
>   static inline void ghes_nmi_remove(struct ghes *ghes) { }
> 

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 1/3] ACPI: APEI: GHES: Improve ghes_notify_nmi() status check
  2025-12-17  1:13   ` Hanjun Guo
@ 2025-12-18  6:01     ` Shuai Xue
  0 siblings, 0 replies; 9+ messages in thread
From: Shuai Xue @ 2025-12-18  6:01 UTC (permalink / raw)
  To: Hanjun Guo, tony.luck, mchehab, yazen.ghannam
  Cc: dave.jiang, Smita.KoralahalliChannabasappa, leitao, pengdonglin,
	baolin.wang, benjamin.cheatham, bp, dan.j.williams, james.morse,
	lenb, linux-acpi, linux-kernel, rafael, zhuo.song

Hi, Hanjun

On 12/17/25 9:13 AM, Hanjun Guo wrote:
> Hi Shuai,
> 
> Some minor comments inline.
> 
> On 2025/12/3 21:02, Shuai Xue wrote:
>> From: Tony Luck <tony.luck@intel.com>
>>
>> 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>
>> ---
>>   drivers/acpi/apei/ghes.c | 39 ++++++++++++++++++++++++++++++++++++---
>>   include/acpi/ghes.h      |  1 +
>>   2 files changed, 37 insertions(+), 3 deletions(-)
>>
>> diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
>> index 97ee19f2cae0..62713b612865 100644
>> --- a/drivers/acpi/apei/ghes.c
>> +++ b/drivers/acpi/apei/ghes.c
>> @@ -1425,7 +1425,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;
>> @@ -1439,13 +1453,26 @@ 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;
> 
> It will be good to add a empty line here.

Sure, will fix it.

> 
>> +    ghes->error_status_vaddr = acpi_os_ioremap(paddr, sizeof(ghes->estatus->block_status));
>> +    if (!ghes->error_status_vaddr)
>> +        return AE_BAD_ADDRESS;
> 
> It's static int for ghes_nmi_add(), and AE_BAD_ADDRESS is the type of
> acpi_status, it's better to return -EINVAL here.

Thanks for pointing it out, will fix it.

> 
> Thanks
> Hanjun

Thanks.
Shuai


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH 2/3] ACPI: APEI: GHES: Extract helper functions for error status handling
  2025-12-17  1:25   ` Hanjun Guo
@ 2025-12-18  6:03     ` Shuai Xue
  0 siblings, 0 replies; 9+ messages in thread
From: Shuai Xue @ 2025-12-18  6:03 UTC (permalink / raw)
  To: Hanjun Guo, tony.luck, mchehab, yazen.ghannam
  Cc: dave.jiang, Smita.KoralahalliChannabasappa, leitao, pengdonglin,
	baolin.wang, benjamin.cheatham, bp, dan.j.williams, james.morse,
	lenb, linux-acpi, linux-kernel, rafael, zhuo.song



On 12/17/25 9:25 AM, Hanjun Guo wrote:
> On 2025/12/3 21:02, Shuai Xue wrote:
>> Refactors the GHES driver by extracting common functionality into
>> reusable helper functions:
>>
>> 1. ghes_has_active_errors() - Checks if any error sources in a given list
>>     have active errors
>> 2. ghes_map_error_status() - Maps error status address to virtual address
>> 3. ghes_unmap_error_status() - Unmaps error status virtual address
>>
>> These helpers eliminate code duplication in the NMI path and prepare for
>> similar usage in the SEA path in a subsequent patch.
>>
>> No functional change intended.
>>
>> Signed-off-by: Shuai Xue <xueshuai@linux.alibaba.com>
>> ---
>>   drivers/acpi/apei/ghes.c | 92 +++++++++++++++++++++++++++++++---------
>>   1 file changed, 72 insertions(+), 20 deletions(-)
>>
>> diff --git a/drivers/acpi/apei/ghes.c b/drivers/acpi/apei/ghes.c
>> index 62713b612865..2c7f3ca6ce50 100644
>> --- a/drivers/acpi/apei/ghes.c
>> +++ b/drivers/acpi/apei/ghes.c
>> @@ -1375,6 +1375,74 @@ static int ghes_in_nmi_spool_from_list(struct list_head *rcu_list,
>>       return ret;
>>   }
>> +/**
>> + * ghes_has_active_errors - Check if there are active errors in error sources
>> + * @ghes_list: List of GHES entries to check for active errors
>> + *
>> + * This function iterates through all GHES entries in the given list and
>> + * checks if any of them has active error status by reading the error
>> + * status register.
>> + *
>> + * Return: true if at least one source has active error, false otherwise.
>> + */
>> +static bool __maybe_unused ghes_has_active_errors(struct list_head *ghes_list)
>> +{
>> +    bool active_error = false;
>> +    struct ghes *ghes;
>> +
>> +    rcu_read_lock();
>> +    list_for_each_entry_rcu(ghes, ghes_list, list) {
>> +        if (ghes->error_status_vaddr &&
>> +            readl(ghes->error_status_vaddr)) {
>> +            active_error = true;
>> +            break;
>> +        }
>> +    }
>> +    rcu_read_unlock();
>> +
>> +    return active_error;
>> +}
>> +
>> +/**
>> + * ghes_map_error_status - Map error status address to virtual address
>> + * @ghes: pointer to GHES structure
>> + *
>> + * Reads the error status address from ACPI HEST table and maps it to a virtual
>> + * address that can be accessed by the kernel.
>> + *
>> + * Return: 0 on success, error code on failure.
>> + */
>> +static int __maybe_unused ghes_map_error_status(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 AE_BAD_ADDRESS;
> 
> Same update here.

Got it. Will fix it.

> 
>> +
>> +    return 0;
>> +}
>> +
>> +/**
>> + * ghes_unmap_error_status - Unmap error status virtual address
>> + * @ghes: pointer to GHES structure
>> + *
>> + * Unmaps the error status address if it was previously mapped.
>> + */
>> +static void __maybe_unused ghes_unmap_error_status(struct ghes *ghes)
>> +{
>> +    if (ghes->error_status_vaddr) {
>> +        iounmap(ghes->error_status_vaddr);
>> +        ghes->error_status_vaddr = NULL;
>> +    }
>> +}
>> +
>>   #ifdef CONFIG_ACPI_APEI_SEA
>>   static LIST_HEAD(ghes_sea);
>> @@ -1425,20 +1493,9 @@ 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)
>> +    if (!ghes_has_active_errors(&ghes_nmi))
>>           return ret;
>>       if (!atomic_add_unless(&ghes_in_nmi, 1, 1))
>> @@ -1455,16 +1512,11 @@ static int ghes_notify_nmi(unsigned int cmd, struct pt_regs *regs)
>>   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);
>> +    rc = ghes_map_error_status(ghes);
>>       if (rc)
>>           return rc;
>> -    ghes->error_status_vaddr = acpi_os_ioremap(paddr, sizeof(ghes->estatus->block_status));
>> -    if (!ghes->error_status_vaddr)
>> -        return AE_BAD_ADDRESS;
>>       mutex_lock(&ghes_list_mutex);
>>       if (list_empty(&ghes_nmi))
>> @@ -1483,8 +1535,7 @@ static void ghes_nmi_remove(struct ghes *ghes)
>>           unregister_nmi_handler(NMI_LOCAL, "ghes");
>>       mutex_unlock(&ghes_list_mutex);
>> -    if (ghes->error_status_vaddr)
>> -        iounmap(ghes->error_status_vaddr);
>> +    ghes_unmap_error_status(ghes);
>>       /*
>>        * To synchronize with NMI handler, ghes can only be
>> @@ -1492,6 +1543,7 @@ static void ghes_nmi_remove(struct ghes *ghes)
>>        */
>>       synchronize_rcu();
>>   }
>> +
> 
> I think it's not belong to this patch.

Will drop it.

> 
> Thanks
> Hanjun
>
Thanks.
Shuai

^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2025-12-18  6:03 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-12-03 13:02 [PATCH 0/3] ACPI: APEI: GHES: Performance improvements for error notification handlers Shuai Xue
2025-12-03 13:02 ` [PATCH 1/3] ACPI: APEI: GHES: Improve ghes_notify_nmi() status check Shuai Xue
2025-12-17  1:13   ` Hanjun Guo
2025-12-18  6:01     ` Shuai Xue
2025-12-03 13:02 ` [PATCH 2/3] ACPI: APEI: GHES: Extract helper functions for error status handling Shuai Xue
2025-12-17  1:25   ` Hanjun Guo
2025-12-18  6:03     ` Shuai Xue
2025-12-03 13:02 ` [PATCH 3/3] ACPI: APEI: GHES: Improve ghes_notify_sea() status check Shuai Xue
2025-12-11 18:44 ` [PATCH 0/3] ACPI: APEI: GHES: Performance improvements for error notification handlers Luck, Tony

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox