[PATCH v6 3/4] x86/mce/intel: Clear mce_poll_banks for firmware-first banks on hotplugged CPUs
From: Aaron Tomlin
Date: Fri Sep 11 2026 - 16:17:08 EST
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@xxxxxxxxxx>
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Aaron Tomlin <atomlin@xxxxxxxxxxx>
---
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