* [PATCH v6 0/4] x86/mce: Fix timer and storm tracking races, and avoid redundant polling
@ 2026-09-11 20:12 Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 1/4] x86/mce: Do not reinitialise mce_timer structure on CPU restart Aaron Tomlin
` (3 more replies)
0 siblings, 4 replies; 6+ messages in thread
From: Aaron Tomlin @ 2026-09-11 20:12 UTC (permalink / raw)
To: tony.luck, bp, tglx, mingo, dave.hansen
Cc: x86, hpa, frederic, marco.crivellari, neelx, sean, chjohnst,
mproche, nick.lange, linux-edac, linux-kernel
This series addresses several distinct issues within the x86 Machine Check
Architecture (MCA) timer and storm mitigation subsystem: a race condition
during runtime CPU reconfiguration that can corrupt the kernel timer wheel,
concurrency races and re-entrancy in storm tracking, stale polling state on
hotplugged CPUs with Firmware First banks, and redundant periodic software
polling of banks that never log corrected errors.
Patch 1 fixes a concurrency race between sysfs configuration updates
(mce_restart()) and asynchronous CMCI interrupts. When mce_restart() runs,
a concurrent CMCI interrupt can arm mce_timer on a remote CPU before the
restart IPI arrives. By removing the redundant timer_setup() call from
__mcheck_cpu_init_timer(), Patch 1 ensures that mce_timer descriptors are
not re-initialised whilst actively linked in the timer wheel, avoiding
potential linked-list corruption and kernel crashes.
Patch 2 resolves concurrency races and re-entrancy within the storm
tracking subsystem. By enclosing mce_track_storm(), cmci_storm_begin(), and
cmci_storm_end() within local_irq_save() and local_irq_restore(), it
prevents incoming CMCI hardirqs from re-entrantly corrupting the bank's
storm state machine or overriding timer kicks when a storm subsides in
softirq context. Additionally, it converts all remaining non-atomic bit
operations on mce_poll_banks (in threshold.c and __mce_disable_bank()) to
atomic set_bit() and clear_bit() variants, eliminating lost-update
read-modify-write hazards.
Patch 3 addresses a bug on CPUs brought online late or physically
hotplugged after boot. Because acpi_hest_init() broadcasts via
on_each_cpu() to clear Firmware First banks only on currently online CPUs,
hotplugged CPUs retain the static ~0UL initialisation value in
mce_poll_banks. When these CPUs come online, cmci_skip_bank() skips CMCI
setup but fails to clear mce_poll_banks, causing machine_check_poll() to
periodically poll and clear Firmware First registers (stealing telemetry
from firmware) and defeating bitmap_empty() checks. Patch 3 ensures
cmci_skip_bank() clears the bank from mce_poll_banks.
Patch 4 implements Tony Luck's suggested approach by recognising that banks
without CMCI support on modern Intel platforms (such as the PCU bank) never
report corrected or UCNA errors (per Intel SDM Vol 3B 18.5). It clears
these non-CMCI banks from mce_poll_banks and ensures mce_timer is never
armed when mce_poll_banks is empty. Additionally, it integrates a
housekeeping check (HK_TYPE_TIMER) so that on legacy platforms or
polling-only configurations where mce_poll_banks is non-empty, routine
polling is restricted to housekeeping CPUs, sparing isolated nohz_full
cores from timer interrupts. This eliminates polling timer jitter across
all CPUs in steady state on modern hardware whilst preserving full polling
capabilities and isolation guarantees.
Thank you.
Changes since v5:
- Split the series into a 4-patch series
- Enclosed mce_track_storm(), cmci_storm_begin(), and cmci_storm_end()
with local_irq_save() and local_irq_restore() to eliminate re-entrancy
races from CMCI hardirqs against the storm state machine and counter
transitions (Tony Luck)
- Converted __mce_disable_bank() to atomic clear_bit() for uniform bit
operations across mce_poll_banks (Tony Luck)
- Added Patch 3 as a dedicated bugfix to clear mce_poll_banks for Firmware
First banks in cmci_skip_bank(), preventing hotplugged CPUs from polling
firmware-controlled registers (Tony Luck)
- Link to v5: https://lore.kernel.org/lkml/20260903194130.186096-1-atomlin@atomlin.com/
Changes since v4:
- Added a patch to switch cmci_storm_begin() and cmci_storm_end() to use
set_bit() and clear_bit(), preventing lost updates on mce_poll_banks
when a timer softirq is interrupted by a CMCI hardirq (Marco Crivellari)
- Switched to clear_bit() in cmci_claim_bank()
- Link to v4: https://lore.kernel.org/lkml/20260903041320.179965-1-atomlin@atomlin.com/
Changes since v3:
- Removed redundant code since field poll_only of struct storm_bank is no
longer set
- Link to v3: https://lore.kernel.org/lkml/20260903013933.172063-1-atomlin@atomlin.com/
Changes since v2:
- Bounded bitmap_empty() in should_enable_timer() to
this_cpu_read(mce_num_banks) to prevent initialised upper bits from
keeping the timer active (Tony Luck)
- Clarified that on polling fallback systems, restricting mce_timer to
housekeeping CPUs leaves core-private banks on isolated cores exempt
from polling, while preserving shared platform telemetry
(e.g., Memory Controller ECC)
- Link to v2: https://lore.kernel.org/lkml/20260902020234.149814-1-atomlin@atomlin.com/
Changes since v1:
- Fixed a pre-existing race condition in mce_restart() by removing the
redundant timer_setup() call in __mcheck_cpu_init_timer(), preventing
active timer wheel linked-list corruption
- Non-CMCI banks are cleared from mce_poll_banks in cmci_claim_bank(),
and should_enable_timer() verifies bitmap_empty(mce_poll_banks) before
checking HK_TYPE_TIMER
- Link to v1: https://lore.kernel.org/lkml/20260901151138.132950-1-atomlin@atomlin.com/
Aaron Tomlin (4):
x86/mce: Do not reinitialise mce_timer structure on CPU restart
x86/mce/threshold: Fix concurrency races in storm tracking
x86/mce/intel: Clear mce_poll_banks for firmware-first banks on
hotplugged CPUs
x86/mce: Avoid arming periodic polling timer when not required
arch/x86/kernel/cpu/mce/core.c | 10 ++++++++--
arch/x86/kernel/cpu/mce/intel.c | 19 +++++++++++--------
arch/x86/kernel/cpu/mce/internal.h | 2 --
arch/x86/kernel/cpu/mce/threshold.c | 23 +++++++++++++++--------
4 files changed, 34 insertions(+), 20 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v6 1/4] x86/mce: Do not reinitialise mce_timer structure on CPU restart
2026-09-11 20:12 [PATCH v6 0/4] x86/mce: Fix timer and storm tracking races, and avoid redundant polling Aaron Tomlin
@ 2026-09-11 20:12 ` Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 2/4] x86/mce/threshold: Fix concurrency races in storm tracking Aaron Tomlin
` (2 subsequent siblings)
3 siblings, 0 replies; 6+ messages in thread
From: Aaron Tomlin @ 2026-09-11 20:12 UTC (permalink / raw)
To: tony.luck, bp, tglx, mingo, dave.hansen
Cc: x86, hpa, frederic, marco.crivellari, neelx, sean, chjohnst,
mproche, nick.lange, linux-edac, linux-kernel
During early CPU initialisation, mcheck_cpu_init() invokes
__mcheck_cpu_setup_timer(), which initialises the per-CPU mce_timer
descriptor via timer_setup(t, mce_timer_fn, TIMER_PINNED).
However, __mcheck_cpu_init_timer() is also called at runtime during
sysfs reconfiguration (via mce_cpu_restart() and mce_enable_ce()),
redundantly re-initialising mce_timer with timer_setup().
This introduces a race condition against asynchronous CMCI interrupts.
When mce_restart() runs, it invokes mce_timer_delete_all() before
broadcasting an IPI to execute mce_cpu_restart() on all CPUs. If a
hardware CMCI interrupt fires on another CPU before the IPI is serviced,
the CMCI handler triggers storm tracking and arms mce_timer via
mce_timer_kick(true), enqueuing it in the active timer wheel.
When mce_cpu_restart() runs subsequently in IPI context,
__mcheck_cpu_init_timer() calls timer_setup(), which invokes
__init_timer() on the already-queued timer. This clears the timer's
internal list pointers (entry.next and entry.pprev) whilst it remains
linked in the active timer wheel bucket, causing linked list corruption,
softirq lockups, or kernel panics during timer expiration.
Fix this by removing the redundant timer_setup() call from
__mcheck_cpu_init_timer(). The timer is already initialised once during
CPU setup; runtime restart paths only need to arm it via
mce_start_timer().
Fixes: 26c3c283c5b0 ("x86: mce: Split timer init")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
arch/x86/kernel/cpu/mce/core.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c
index ab469605fc89..765e8103b0d2 100644
--- a/arch/x86/kernel/cpu/mce/core.c
+++ b/arch/x86/kernel/cpu/mce/core.c
@@ -2091,7 +2091,6 @@ static void __mcheck_cpu_init_timer(void)
{
struct timer_list *t = this_cpu_ptr(&mce_timer);
- timer_setup(t, mce_timer_fn, TIMER_PINNED);
mce_start_timer(t);
}
--
2.55.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v6 2/4] x86/mce/threshold: Fix concurrency races in storm tracking
2026-09-11 20:12 [PATCH v6 0/4] x86/mce: Fix timer and storm tracking races, and avoid redundant polling Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 1/4] x86/mce: Do not reinitialise mce_timer structure on CPU restart Aaron Tomlin
@ 2026-09-11 20:12 ` Aaron Tomlin
2026-09-12 2:21 ` Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 3/4] x86/mce/intel: Clear mce_poll_banks for firmware-first banks on hotplugged CPUs Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 4/4] x86/mce: Avoid arming periodic polling timer when not required Aaron Tomlin
3 siblings, 1 reply; 6+ messages in thread
From: Aaron Tomlin @ 2026-09-11 20:12 UTC (permalink / raw)
To: tony.luck, bp, tglx, mingo, dave.hansen
Cc: x86, hpa, frederic, marco.crivellari, neelx, sean, chjohnst,
mproche, nick.lange, linux-edac, linux-kernel
Functions cmci_storm_begin(), cmci_storm_end(), and __mce_disable_bank()
modify the per-CPU bitmap mce_poll_banks using non-atomic __set_bit()
and __clear_bit(). In addition, mce_track_storm(), cmci_storm_begin(),
and cmci_storm_end() manipulate per-CPU storm descriptors and invoke
mce_timer_kick() without synchronisation against local interrupts.
While mce_poll_banks and storm_desc are per-CPU, mce_track_storm() and
cmci_storm_end() execute in timer softirq context (via mce_timer_fn())
with local hardirqs enabled, whereas cmci_storm_begin() can be invoked
from CMCI hardirq context (via intel_threshold_interrupt()).
This introduces multiple concurrency races between softirq and hardirq
contexts on the same CPU:
1. A hardirq interrupting a softirq's non-atomic read-modify-write
on mce_poll_banks will have its bit update clobbered when the
softirq resumes, dropping a stormy bank from polling.
2. An incoming CMCI hardirq during mce_track_storm() re-entrantly
executes mce_track_storm() on the same CPU, racing on
in_storm_mode, history, and timestamps, which can corrupt the
bank's storm state machine.
3. If a hardirq fires after cmci_storm_end() decrements
stormy_bank_count to zero, but before mce_timer_kick(false) is
called, the hardirq's cmci_storm_begin() will increment
stormy_bank_count to 1 and call mce_timer_kick(true). When the
softirq resumes, its delayed mce_timer_kick(false) will
erroneously override storm mode, leaving the CPU with
stormy_bank_count == 1 whilst the timer reverts to the default
polling interval.
Resolve these issues by switching to atomic set_bit() and clear_bit()
operations on mce_poll_banks and __mce_disable_bank(). Finally, enclose
mce_track_storm(), cmci_storm_begin(), and cmci_storm_end() with
local_irq_save() and local_irq_restore().
Fixes: 7eae17c4add5 ("x86/mce: Add per-bank CMCI storm mitigation")
Cc: stable@vger.kernel.org
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
arch/x86/kernel/cpu/mce/core.c | 2 +-
arch/x86/kernel/cpu/mce/threshold.c | 23 +++++++++++++++++------
2 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c
index 765e8103b0d2..aa604d981358 100644
--- a/arch/x86/kernel/cpu/mce/core.c
+++ b/arch/x86/kernel/cpu/mce/core.c
@@ -2294,7 +2294,7 @@ void mcheck_cpu_clear(struct cpuinfo_x86 *c)
static void __mce_disable_bank(void *arg)
{
int bank = *((int *)arg);
- __clear_bit(bank, this_cpu_ptr(mce_poll_banks));
+ clear_bit(bank, this_cpu_ptr(mce_poll_banks));
cmci_disable_bank(bank);
}
diff --git a/arch/x86/kernel/cpu/mce/threshold.c b/arch/x86/kernel/cpu/mce/threshold.c
index 6c370d5af5bd..83f3e2250ae2 100644
--- a/arch/x86/kernel/cpu/mce/threshold.c
+++ b/arch/x86/kernel/cpu/mce/threshold.c
@@ -85,8 +85,10 @@ static void mce_handle_storm(unsigned int bank, bool on)
void cmci_storm_begin(unsigned int bank)
{
struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc);
+ unsigned long flags;
- __set_bit(bank, this_cpu_ptr(mce_poll_banks));
+ local_irq_save(flags);
+ set_bit(bank, this_cpu_ptr(mce_poll_banks));
storm->banks[bank].in_storm_mode = true;
/*
@@ -95,32 +97,38 @@ void cmci_storm_begin(unsigned int bank)
*/
if (++storm->stormy_bank_count == 1)
mce_timer_kick(true);
+ local_irq_restore(flags);
}
void cmci_storm_end(unsigned int bank)
{
struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc);
+ unsigned long flags;
+ local_irq_save(flags);
if (!mce_flags.amd_threshold)
- __clear_bit(bank, this_cpu_ptr(mce_poll_banks));
+ clear_bit(bank, this_cpu_ptr(mce_poll_banks));
storm->banks[bank].history = 0;
storm->banks[bank].in_storm_mode = false;
/* If no banks left in storm mode, stop polling. */
if (!--storm->stormy_bank_count)
mce_timer_kick(false);
+ local_irq_restore(flags);
}
void mce_track_storm(struct mce *mce)
{
struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc);
- unsigned long now = jiffies, delta;
+ unsigned long flags, now = jiffies, delta;
unsigned int shift = 1;
u64 history = 0;
+ local_irq_save(flags);
+
/* No tracking needed for banks that do not support CMCI */
if (storm->banks[mce->bank].poll_only)
- return;
+ goto out;
/*
* When a bank is in storm mode it is polled once per second and
@@ -149,15 +157,18 @@ void mce_track_storm(struct mce *mce)
if (storm->banks[mce->bank].in_storm_mode) {
if (history & GENMASK_ULL(STORM_END_POLL_THRESHOLD, 0))
- return;
+ goto out;
printk_deferred(KERN_NOTICE "CPU%d BANK%d CMCI storm subsided\n", smp_processor_id(), mce->bank);
mce_handle_storm(mce->bank, false);
cmci_storm_end(mce->bank);
} else {
if (hweight64(history) < STORM_BEGIN_THRESHOLD)
- return;
+ goto out;
printk_deferred(KERN_NOTICE "CPU%d BANK%d CMCI storm detected\n", smp_processor_id(), mce->bank);
mce_handle_storm(mce->bank, true);
cmci_storm_begin(mce->bank);
}
+
+out:
+ local_irq_restore(flags);
}
--
2.55.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v6 3/4] x86/mce/intel: Clear mce_poll_banks for firmware-first banks on hotplugged CPUs
2026-09-11 20:12 [PATCH v6 0/4] x86/mce: Fix timer and storm tracking races, and avoid redundant polling Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 1/4] x86/mce: Do not reinitialise mce_timer structure on CPU restart Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 2/4] x86/mce/threshold: Fix concurrency races in storm tracking Aaron Tomlin
@ 2026-09-11 20:12 ` Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 4/4] x86/mce: Avoid arming periodic polling timer when not required Aaron Tomlin
3 siblings, 0 replies; 6+ messages in thread
From: Aaron Tomlin @ 2026-09-11 20:12 UTC (permalink / raw)
To: tony.luck, bp, tglx, mingo, dave.hansen
Cc: x86, hpa, frederic, marco.crivellari, neelx, sean, chjohnst,
mproche, nick.lange, linux-edac, linux-kernel
During early boot, acpi_hest_init() parses APEI HEST CMC structures and
calls mce_disable_bank() for banks designated as Firmware First.
mce_disable_bank() broadcasts via on_each_cpu() to clear the bank from
the per-CPU mce_poll_banks bitmap on all currently online CPUs.
However, CPUs that are brought online late or physically hotplugged
after boot miss this broadcast. Because per-CPU mce_poll_banks is
statically initialised to ~0UL, hotplugged CPUs retain the set bit for
the Firmware First bank.
When such a CPU comes online, cmci_discover() invokes cmci_skip_bank(),
which checks mce_banks_ce_disabled and skips CMCI setup for the bank.
However, cmci_skip_bank() returns early without clearing the bank's bit
from mce_poll_banks.
Consequently, machine_check_poll() on hotplugged CPUs periodically polls
the Firmware First bank, reading and clearing IA32_MCi_STATUS. This
steals hardware error telemetry from the firmware, breaking Firmware
First error handling. In addition, leaving the bit set defeats software
optimisations that check whether mce_poll_banks is empty.
Fix this by clearing the bank's bit from mce_poll_banks in
cmci_skip_bank() when the bank is configured for Firmware First mode.
Fixes: c3d1fb567a63 ("mce: acpi/apei: Honour Firmware First for MCA banks listed in APEI HEST CMC")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Cc: stable@vger.kernel.org
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
arch/x86/kernel/cpu/mce/intel.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kernel/cpu/mce/intel.c b/arch/x86/kernel/cpu/mce/intel.c
index 4655223ba560..7be5713aa190 100644
--- a/arch/x86/kernel/cpu/mce/intel.c
+++ b/arch/x86/kernel/cpu/mce/intel.c
@@ -181,15 +181,17 @@ static bool cmci_skip_bank(int bank, u64 *val)
return true;
/* Skip banks in firmware first mode */
- if (test_bit(bank, mce_banks_ce_disabled))
+ if (test_bit(bank, mce_banks_ce_disabled)) {
+ clear_bit(bank, this_cpu_ptr(mce_poll_banks));
return true;
+ }
rdmsrq(MSR_IA32_MCx_CTL2(bank), *val);
/* Already owned by someone else? */
if (*val & MCI_CTL2_CMCI_EN) {
clear_bit(bank, owned);
- __clear_bit(bank, this_cpu_ptr(mce_poll_banks));
+ clear_bit(bank, this_cpu_ptr(mce_poll_banks));
return true;
}
--
2.55.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH v6 4/4] x86/mce: Avoid arming periodic polling timer when not required
2026-09-11 20:12 [PATCH v6 0/4] x86/mce: Fix timer and storm tracking races, and avoid redundant polling Aaron Tomlin
` (2 preceding siblings ...)
2026-09-11 20:12 ` [PATCH v6 3/4] x86/mce/intel: Clear mce_poll_banks for firmware-first banks on hotplugged CPUs Aaron Tomlin
@ 2026-09-11 20:12 ` Aaron Tomlin
3 siblings, 0 replies; 6+ messages in thread
From: Aaron Tomlin @ 2026-09-11 20:12 UTC (permalink / raw)
To: tony.luck, bp, tglx, mingo, dave.hansen
Cc: x86, hpa, frederic, marco.crivellari, neelx, sean, chjohnst,
mproche, nick.lange, linux-edac, linux-kernel
On x86 platforms, the Machine Check Architecture (MCA) subsystem arms a
per-CPU, pinned standard timer (mce_timer) to periodically poll hardware
banks for "silent" corrected machine check errors. Because mce_timer is
pinned to the local CPU via TIMER_PINNED, the timer core cannot migrate
its expiration to a housekeeping CPU, causing periodic latency jitter on
isolated cores.
On Intel systems supporting Corrected Machine Check Interrupt (CMCI),
hardware generates an interrupt for banks with CMCI enabled, clearing
their respective bits in mce_poll_banks. However, for banks where
MCI_CTL2_CMCI_EN does not stick (such as the Power Control Unit bank),
Linux historically assumed software polling was required and left the
bank flagged in mce_poll_banks.
Per the Intel SDM (Vol 3B, Section 18.5 "Corrected Machine Check Error
Interrupt"), if bit 30 of IA32_MCi_CTL2 is zero, no CMCI is available
for that bank and no corrected or Uncorrected No Action Required (UCNA)
errors will be reported on that bank. Therefore, polling such banks is
redundant and wasteful.
Clear non-CMCI banks from mce_poll_banks in cmci_claim_bank() on
CMCI-capable CPUs, and amend should_enable_timer() to:
1. Check bitmap_empty(mce_poll_banks) bounded to the number of
implemented banks (this_cpu_read(mce_num_banks)) so that
mce_timer is never armed when no banks require polling.
2. Pin the MCE polling timer to a HK_TYPE_TIMER housekeeping CPU,
sparing isolated nohz_full cores from periodic wakeups on
systems requiring mce_poll_banks polling.
On systems requiring software polling such as legacy platforms lacking CMCI
or when booted with mce=no_cmci, mce_timer is restricted to
housekeeping CPUs. While this intentionally leaves core-private banks
(e.g. L1/L2 caches) on isolated cores exempt from periodic polling to
guarantee zero timer jitter, housekeeping CPUs continue to harvest
telemetry from their own banks as well as shared package-level
resources. Synchronous (#MC) exceptions on isolated cores remain
entirely unaffected.
Suggested-by: Tony Luck <tony.luck@intel.com>
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
arch/x86/kernel/cpu/mce/core.c | 7 +++++++
arch/x86/kernel/cpu/mce/intel.c | 13 +++++++------
arch/x86/kernel/cpu/mce/internal.h | 2 --
arch/x86/kernel/cpu/mce/threshold.c | 4 ----
4 files changed, 14 insertions(+), 12 deletions(-)
diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c
index aa604d981358..66783205a958 100644
--- a/arch/x86/kernel/cpu/mce/core.c
+++ b/arch/x86/kernel/cpu/mce/core.c
@@ -25,6 +25,7 @@
#include <linux/delay.h>
#include <linux/ctype.h>
#include <linux/sched.h>
+#include <linux/sched/isolation.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#include <linux/slab.h>
@@ -1759,6 +1760,12 @@ void (*mc_poll_banks)(void) = mc_poll_banks_default;
static bool should_enable_timer(unsigned long iv)
{
+ if (bitmap_empty(this_cpu_ptr(mce_poll_banks), this_cpu_read(mce_num_banks)))
+ return false;
+
+ if (!housekeeping_cpu(smp_processor_id(), HK_TYPE_TIMER))
+ return false;
+
return !mca_cfg.ignore_ce && iv;
}
diff --git a/arch/x86/kernel/cpu/mce/intel.c b/arch/x86/kernel/cpu/mce/intel.c
index 7be5713aa190..f5f27d3798a8 100644
--- a/arch/x86/kernel/cpu/mce/intel.c
+++ b/arch/x86/kernel/cpu/mce/intel.c
@@ -231,16 +231,17 @@ static u64 cmci_pick_threshold(u64 val, int *bios_zero_thresh)
*/
static void cmci_claim_bank(int bank, u64 val, int bios_zero_thresh, int *bios_wrong_thresh)
{
- struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc);
-
val |= MCI_CTL2_CMCI_EN;
wrmsrq(MSR_IA32_MCx_CTL2(bank), val);
rdmsrq(MSR_IA32_MCx_CTL2(bank), val);
- /* If the enable bit did not stick, this bank should be polled. */
+ /*
+ * If the enable bit did not stick, this bank does not support CMCI
+ * and no corrected or UCNA errors will be reported on this bank
+ * (SDM Vol 3B 18.5). No polling is needed.
+ */
if (!(val & MCI_CTL2_CMCI_EN)) {
- WARN_ON(!test_bit(bank, this_cpu_ptr(mce_poll_banks)));
- storm->banks[bank].poll_only = true;
+ clear_bit(bank, this_cpu_ptr(mce_poll_banks));
return;
}
@@ -252,7 +253,7 @@ static void cmci_claim_bank(int bank, u64 val, int bios_zero_thresh, int *bios_w
mce_inherit_storm(bank);
cmci_storm_begin(bank);
} else {
- __clear_bit(bank, this_cpu_ptr(mce_poll_banks));
+ clear_bit(bank, this_cpu_ptr(mce_poll_banks));
}
/*
diff --git a/arch/x86/kernel/cpu/mce/internal.h b/arch/x86/kernel/cpu/mce/internal.h
index a31cf984619c..32ae574d2ad5 100644
--- a/arch/x86/kernel/cpu/mce/internal.h
+++ b/arch/x86/kernel/cpu/mce/internal.h
@@ -84,13 +84,11 @@ static inline u32 mce_get_apei_thr_limit(void) { return 0; }
*
* timestamp: Last time (in jiffies) that the bank was polled.
* in_storm_mode: Is this bank in storm mode?
- * poll_only: Bank does not support CMCI, skip storm tracking.
*/
struct storm_bank {
u64 history;
u64 timestamp;
bool in_storm_mode;
- bool poll_only;
};
#define NUM_HISTORY_BITS (sizeof(u64) * BITS_PER_BYTE)
diff --git a/arch/x86/kernel/cpu/mce/threshold.c b/arch/x86/kernel/cpu/mce/threshold.c
index 83f3e2250ae2..07f326edae83 100644
--- a/arch/x86/kernel/cpu/mce/threshold.c
+++ b/arch/x86/kernel/cpu/mce/threshold.c
@@ -126,10 +126,6 @@ void mce_track_storm(struct mce *mce)
local_irq_save(flags);
- /* No tracking needed for banks that do not support CMCI */
- if (storm->banks[mce->bank].poll_only)
- goto out;
-
/*
* When a bank is in storm mode it is polled once per second and
* the history mask will record about the last minute of poll results.
--
2.55.0
^ permalink raw reply [flat|nested] 6+ messages in thread
* Re: [PATCH v6 2/4] x86/mce/threshold: Fix concurrency races in storm tracking
2026-09-11 20:12 ` [PATCH v6 2/4] x86/mce/threshold: Fix concurrency races in storm tracking Aaron Tomlin
@ 2026-09-12 2:21 ` Aaron Tomlin
0 siblings, 0 replies; 6+ messages in thread
From: Aaron Tomlin @ 2026-09-12 2:21 UTC (permalink / raw)
To: tony.luck, bp, tglx, mingo, dave.hansen
Cc: x86, hpa, frederic, marco.crivellari, neelx, sean, chjohnst,
mproche, nick.lange, linux-edac, linux-kernel
On Fri, Sep 11, 2026 at 04:12:04PM -0400, Aaron Tomlin wrote:
> Functions cmci_storm_begin(), cmci_storm_end(), and __mce_disable_bank()
> modify the per-CPU bitmap mce_poll_banks using non-atomic __set_bit()
> and __clear_bit(). In addition, mce_track_storm(), cmci_storm_begin(),
> and cmci_storm_end() manipulate per-CPU storm descriptors and invoke
> mce_timer_kick() without synchronisation against local interrupts.
>
> While mce_poll_banks and storm_desc are per-CPU, mce_track_storm() and
> cmci_storm_end() execute in timer softirq context (via mce_timer_fn())
> with local hardirqs enabled, whereas cmci_storm_begin() can be invoked
> from CMCI hardirq context (via intel_threshold_interrupt()).
>
> This introduces multiple concurrency races between softirq and hardirq
> contexts on the same CPU:
> 1. A hardirq interrupting a softirq's non-atomic read-modify-write
> on mce_poll_banks will have its bit update clobbered when the
> softirq resumes, dropping a stormy bank from polling.
>
> 2. An incoming CMCI hardirq during mce_track_storm() re-entrantly
> executes mce_track_storm() on the same CPU, racing on
> in_storm_mode, history, and timestamps, which can corrupt the
> bank's storm state machine.
>
> 3. If a hardirq fires after cmci_storm_end() decrements
> stormy_bank_count to zero, but before mce_timer_kick(false) is
> called, the hardirq's cmci_storm_begin() will increment
> stormy_bank_count to 1 and call mce_timer_kick(true). When the
> softirq resumes, its delayed mce_timer_kick(false) will
> erroneously override storm mode, leaving the CPU with
> stormy_bank_count == 1 whilst the timer reverts to the default
> polling interval.
>
> Resolve these issues by switching to atomic set_bit() and clear_bit()
> operations on mce_poll_banks and __mce_disable_bank(). Finally, enclose
> mce_track_storm(), cmci_storm_begin(), and cmci_storm_end() with
> local_irq_save() and local_irq_restore().
>
> Fixes: 7eae17c4add5 ("x86/mce: Add per-bank CMCI storm mitigation")
> Cc: stable@vger.kernel.org
> Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
> ---
> arch/x86/kernel/cpu/mce/core.c | 2 +-
> arch/x86/kernel/cpu/mce/threshold.c | 23 +++++++++++++++++------
> 2 files changed, 18 insertions(+), 7 deletions(-)
>
> diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c
> index 765e8103b0d2..aa604d981358 100644
> --- a/arch/x86/kernel/cpu/mce/core.c
> +++ b/arch/x86/kernel/cpu/mce/core.c
> @@ -2294,7 +2294,7 @@ void mcheck_cpu_clear(struct cpuinfo_x86 *c)
> static void __mce_disable_bank(void *arg)
> {
> int bank = *((int *)arg);
> - __clear_bit(bank, this_cpu_ptr(mce_poll_banks));
> + clear_bit(bank, this_cpu_ptr(mce_poll_banks));
> cmci_disable_bank(bank);
> }
>
Hi Tony,
This was an artifact. Sorry about that.
Indeed, the atomic bit operations became completely redundant the moment
local_irq_save() was introduced to enclose mce_track_storm(); the use of
non-atomic __clear_bit(bank, this_cpu_ptr(mce_poll_banks)) is fine.
mce_track_storm
{
local_irq_save(flags)
if (storm->banks[mce->bank].in_storm_mode)
cmci_storm_end(mce->bank)
{
local_irq_save(flags)
if (!mce_flags.amd_threshold)
clear_bit(bank, this_cpu_ptr(mce_poll_banks))
> diff --git a/arch/x86/kernel/cpu/mce/threshold.c b/arch/x86/kernel/cpu/mce/threshold.c
> index 6c370d5af5bd..83f3e2250ae2 100644
> --- a/arch/x86/kernel/cpu/mce/threshold.c
> +++ b/arch/x86/kernel/cpu/mce/threshold.c
> @@ -85,8 +85,10 @@ static void mce_handle_storm(unsigned int bank, bool on)
> void cmci_storm_begin(unsigned int bank)
> {
> struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc);
> + unsigned long flags;
>
> - __set_bit(bank, this_cpu_ptr(mce_poll_banks));
> + local_irq_save(flags);
> + set_bit(bank, this_cpu_ptr(mce_poll_banks));
> storm->banks[bank].in_storm_mode = true;
>
> /*
> @@ -95,32 +97,38 @@ void cmci_storm_begin(unsigned int bank)
> */
> if (++storm->stormy_bank_count == 1)
> mce_timer_kick(true);
> + local_irq_restore(flags);
> }
>
> void cmci_storm_end(unsigned int bank)
> {
> struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc);
> + unsigned long flags;
>
> + local_irq_save(flags);
> if (!mce_flags.amd_threshold)
> - __clear_bit(bank, this_cpu_ptr(mce_poll_banks));
> + clear_bit(bank, this_cpu_ptr(mce_poll_banks));
> storm->banks[bank].history = 0;
> storm->banks[bank].in_storm_mode = false;
>
> /* If no banks left in storm mode, stop polling. */
> if (!--storm->stormy_bank_count)
> mce_timer_kick(false);
> + local_irq_restore(flags);
> }
>
> void mce_track_storm(struct mce *mce)
> {
> struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc);
> - unsigned long now = jiffies, delta;
> + unsigned long flags, now = jiffies, delta;
> unsigned int shift = 1;
> u64 history = 0;
>
> + local_irq_save(flags);
> +
> /* No tracking needed for banks that do not support CMCI */
> if (storm->banks[mce->bank].poll_only)
> - return;
> + goto out;
>
> /*
> * When a bank is in storm mode it is polled once per second and
> @@ -149,15 +157,18 @@ void mce_track_storm(struct mce *mce)
>
> if (storm->banks[mce->bank].in_storm_mode) {
> if (history & GENMASK_ULL(STORM_END_POLL_THRESHOLD, 0))
> - return;
> + goto out;
> printk_deferred(KERN_NOTICE "CPU%d BANK%d CMCI storm subsided\n", smp_processor_id(), mce->bank);
> mce_handle_storm(mce->bank, false);
> cmci_storm_end(mce->bank);
> } else {
> if (hweight64(history) < STORM_BEGIN_THRESHOLD)
> - return;
> + goto out;
> printk_deferred(KERN_NOTICE "CPU%d BANK%d CMCI storm detected\n", smp_processor_id(), mce->bank);
> mce_handle_storm(mce->bank, true);
> cmci_storm_begin(mce->bank);
> }
> +
> +out:
> + local_irq_restore(flags);
> }
> --
> 2.55.0
>
--
Aaron Tomlin
^ permalink raw reply [flat|nested] 6+ messages in thread
end of thread, other threads:[~2026-09-12 2:21 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-11 20:12 [PATCH v6 0/4] x86/mce: Fix timer and storm tracking races, and avoid redundant polling Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 1/4] x86/mce: Do not reinitialise mce_timer structure on CPU restart Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 2/4] x86/mce/threshold: Fix concurrency races in storm tracking Aaron Tomlin
2026-09-12 2:21 ` Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 3/4] x86/mce/intel: Clear mce_poll_banks for firmware-first banks on hotplugged CPUs Aaron Tomlin
2026-09-11 20:12 ` [PATCH v6 4/4] x86/mce: Avoid arming periodic polling timer when not required Aaron Tomlin
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®