Re: [PATCH v3 2/2] x86/mce: Avoid arming periodic polling timer when not required

From: Aaron Tomlin

Date: Wed Sep 02 2026 - 23:39:59 EST


On Wed, Sep 02, 2026 at 09:39:33PM -0400, Aaron Tomlin wrote:
> 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) so that mce_timer is never
> armed when no banks on that CPU require software 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 legacy platforms lacking CMCI
> or when booted with mce=no_cmci, the 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@xxxxxxxxx>
> Signed-off-by: Aaron Tomlin <atomlin@xxxxxxxxxxx>
> ---
> arch/x86/kernel/cpu/mce/core.c | 7 +++++++
> arch/x86/kernel/cpu/mce/intel.c | 11 ++++++-----
> 2 files changed, 13 insertions(+), 5 deletions(-)
>
> diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c
> index 765e8103b0d2..b01ad1a9b455 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 4655223ba560..cc7d0dfcf9c7 100644
> --- a/arch/x86/kernel/cpu/mce/intel.c
> +++ b/arch/x86/kernel/cpu/mce/intel.c
> @@ -229,16 +229,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;
> }
>
> --
> 2.55.0
>

Hi Tony,

As per Sashiko [1], since poll_only field of struct storm_bank is no longer
used, it should be removed. I will send a v4 shortly.

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 6c370d5af5bd..2a82c9f6b5e4 100644
--- a/arch/x86/kernel/cpu/mce/threshold.c
+++ b/arch/x86/kernel/cpu/mce/threshold.c
@@ -118,10 +118,6 @@ void mce_track_storm(struct mce *mce)
unsigned int shift = 1;
u64 history = 0;

- /* No tracking needed for banks that do not support CMCI */
- if (storm->banks[mce->bank].poll_only)
- return;
-
/*
* 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.

[1]: https://sashiko.dev/#/patchset/20260903013933.172063-1-atomlin%40atomlin.com

Kind regards,
--
Aaron Tomlin