mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] perf/x86/amd: Move NMI latency window to last-resort suppression
@ 2026-09-16  6:57 Guanghui Feng
  2026-09-16  9:32 ` Peter Zijlstra
  0 siblings, 1 reply; 5+ messages in thread
From: Guanghui Feng @ 2026-09-16  6:57 UTC (permalink / raw)
  To: peterz, mingo, acme, namhyung, mark.rutland, alexander.shishkin,
	jolsa, irogers, adrian.hunter, james.clark, tglx, bp,
	dave.hansen, hpa, seanjc, kai.huang, radu
  Cc: x86, linux-perf-users, linux-kernel

The upstream amd_pmu_adjust_nmi_window() mitigation claims every NMI
that arrives within a 100ms window opened after a PMC overflow. When no
counter overflowed, the handler still returns NMI_HANDLED, which makes
the NMI dispatch path take its "handled" exit. Everything that comes
after the perf handler is then skipped: the NMI reason port, which may
hold a latched SERR#/IOCHK# error, and all NMI_UNKNOWN handlers, such
as hpwdt. As a result unrelated NMIs are silently dropped for the whole
duration of the window.

Stop claiming NMIs from within the perf handler. When no counter
overflowed there is nothing to claim, so always report NMI_DONE and let
the remaining NMI sources be probed first. Defer the window check to the
very end of the dispatch path, where it is used only as a last resort.

Introduce perf_nmi_window_active(), which reports whether the latency
window opened by the last PMC overflow on this CPU is still open. It is
called from unknown_nmi_error() only after all NMI_LOCAL handlers, the
reason port and all NMI_UNKNOWN handlers failed to identify the NMI.
Only then is the bogus "unknown NMI" report suppressed. A __weak
fallback in arch/x86/kernel/nmi.c always returns false, so non-AMD
platforms are unaffected.

nmi_stats.unknown is still incremented before the suppression, so the
dropped NMIs remain observable through debugfs.

Fixes: df4d29732fda ("perf/x86/amd: Change/fix NMI latency mitigation to use a timestamp")
Signed-off-by: Guanghui Feng <guanghuifeng@linux.alibaba.com>
---
 arch/x86/events/amd/core.c | 56 ++++++++++++++++++++++++++++++--------
 arch/x86/include/asm/nmi.h |  8 ++++++
 arch/x86/kernel/nmi.c      | 23 ++++++++++++++++
 3 files changed, 76 insertions(+), 11 deletions(-)

diff --git a/arch/x86/events/amd/core.c b/arch/x86/events/amd/core.c
index 49b6b8fce566..36721042c79c 100644
--- a/arch/x86/events/amd/core.c
+++ b/arch/x86/events/amd/core.c
@@ -868,18 +868,28 @@ static void amd_pmu_del_event(struct perf_event *event)
  * handler when multiple PMCs are active or PMC overflow while handling some
  * other source of an NMI.
  *
- * Attempt to mitigate this by creating an NMI window in which un-handled NMIs
- * received during this window will be claimed. This prevents extending the
- * window past when it is possible that latent NMIs should be received. The
- * per-CPU perf_nmi_tstamp will be set to the window end time whenever perf has
- * handled a counter. When an un-handled NMI is received, it will be claimed
- * only if arriving within that window.
+ * Attempt to mitigate this by creating an NMI window in which a latent NMI
+ * generated by an already handled overflow may still show up. This prevents
+ * extending the window past when it is possible that latent NMIs should be
+ * received. The per-CPU perf_nmi_tstamp will be set to the window end time
+ * whenever perf has handled a counter.
+ *
+ * The window is deliberately *not* used to claim NMIs from within this
+ * handler. Claiming an NMI although no counter overflowed makes the NMI
+ * dispatch path take the "handled" exit and skip everything that comes after
+ * it: the NMI reason port, which may hold a latched SERR#/IOCHK# error, and
+ * all NMI_UNKNOWN handlers, such as hpwdt. Those unrelated NMIs would then be
+ * silently dropped for the whole duration of the window. Instead this handler
+ * reports NMI_DONE and the window is consulted by the dispatch path as the
+ * very last resort, once no handler was able to identify the NMI. See
+ * perf_nmi_window_active() below and unknown_nmi_error().
  */
 static inline int amd_pmu_adjust_nmi_window(int handled)
 {
 	/*
-	 * If a counter was handled, record a timestamp such that un-handled
-	 * NMIs will be claimed if arriving within that window.
+	 * If a counter was handled, record a timestamp such that a latent NMI
+	 * generated by that overflow can still be recognized later on by the
+	 * NMI dispatch path.
 	 */
 	if (handled) {
 		this_cpu_write(perf_nmi_tstamp, jiffies + perf_nmi_window);
@@ -887,11 +897,35 @@ static inline int amd_pmu_adjust_nmi_window(int handled)
 		return handled;
 	}
 
-	if (time_after(jiffies, this_cpu_read(perf_nmi_tstamp)))
-		return NMI_DONE;
+	/*
+	 * No counter overflowed here, so there is nothing to claim. Report
+	 * NMI_DONE even when the window is open and let the remaining NMI
+	 * sources be probed first.
+	 */
+	return NMI_DONE;
+}
 
-	return NMI_HANDLED;
+/**
+ * perf_nmi_window_active - Check if the PMC NMI latency window is still open
+ *
+ * Returns true if the current time is within the NMI latency mitigation window
+ * that was opened by the last PMC counter overflow handled on this CPU. An
+ * open window means that a recently processed overflow may have generated an
+ * NMI which arrived too late to be paired with that overflow, and which
+ * therefore cannot be identified by any NMI handler.
+ *
+ * This is the last resort of the x86 NMI dispatch path: it is only called
+ * after all NMI_LOCAL handlers, the NMI reason port and all NMI_UNKNOWN
+ * handlers failed to claim the NMI. It must never be used to claim an NMI on
+ * behalf of perf, otherwise unrelated NMI sources would be dropped.
+ *
+ * Context: NMI context. Must not sleep and must not take locks.
+ */
+bool perf_nmi_window_active(void)
+{
+	return !time_after(jiffies, this_cpu_read(perf_nmi_tstamp));
 }
+EXPORT_SYMBOL_GPL(perf_nmi_window_active);
 
 static int amd_pmu_handle_irq(struct pt_regs *regs)
 {
diff --git a/arch/x86/include/asm/nmi.h b/arch/x86/include/asm/nmi.h
index 79d88d12c8fb..703d717a21a3 100644
--- a/arch/x86/include/asm/nmi.h
+++ b/arch/x86/include/asm/nmi.h
@@ -105,4 +105,12 @@ void stop_nmi(void);
 void restart_nmi(void);
 void local_touch_nmi(void);
 
+/*
+ * Last resort check of the NMI dispatch path, used to suppress "unknown NMI"
+ * reports caused by PMC overflow NMI latency. The strong implementation lives
+ * in the AMD perf core, arch/x86/kernel/nmi.c provides a __weak fallback which
+ * always returns false.
+ */
+bool perf_nmi_window_active(void);
+
 #endif /* _ASM_X86_NMI_H */
diff --git a/arch/x86/kernel/nmi.c b/arch/x86/kernel/nmi.c
index 3c9f60d6ca5a..1aa161b56be0 100644
--- a/arch/x86/kernel/nmi.c
+++ b/arch/x86/kernel/nmi.c
@@ -322,6 +322,16 @@ io_check_error(unsigned char reason, struct pt_regs *regs)
 }
 NOKPROBE_SYMBOL(io_check_error);
 
+/*
+ * Fallback used when the AMD perf core, which provides the strong
+ * implementation, is not built in. Without it no PMC overflow NMI latency
+ * window is tracked at all, so no "unknown NMI" report is ever suppressed.
+ */
+bool __weak perf_nmi_window_active(void)
+{
+	return false;
+}
+
 static void
 unknown_nmi_error(unsigned char reason, struct pt_regs *regs)
 {
@@ -340,6 +350,19 @@ unknown_nmi_error(unsigned char reason, struct pt_regs *regs)
 
 	__this_cpu_add(nmi_stats.unknown, 1);
 
+	/*
+	 * No handler was able to identify this NMI, so its source is unknown.
+	 * The one exception is a latent PMC overflow NMI: the overflow NMI can
+	 * arrive long after the counter was already processed by an earlier
+	 * NMI, which leaves nothing here that could identify it. If the perf
+	 * NMI latency window is still open, this NMI is very likely that late
+	 * arrival, so keep quiet about it instead of reporting a bogus unknown
+	 * NMI (and instead of panicking on unknown_nmi_panic). It is still
+	 * accounted in nmi_stats.unknown and thus stays visible in debugfs.
+	 */
+	if (perf_nmi_window_active())
+		return;
+
 	pr_emerg_ratelimited("Uhhuh. NMI received for unknown reason %02x on CPU %d.\n",
 			     reason, smp_processor_id());
 
-- 
2.43.7


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

end of thread, other threads:[~2026-09-16 13:18 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-16  6:57 [PATCH] perf/x86/amd: Move NMI latency window to last-resort suppression Guanghui Feng
2026-09-16  9:32 ` Peter Zijlstra
2026-09-16 12:50   ` [PATCH v2] " Guanghui Feng
2026-09-16 13:04     ` Peter Zijlstra
2026-09-16 13:18   ` [PATCH] " guanghuifeng

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

all inboxes | Powered by JetHome®