linux/arch/x86/kernel/cpu/mce/threshold.c
Smita Koralahalli 5c4663ed1e x86/mce: Handle AMD threshold interrupt storms
Extend the logic of handling CMCI storms to AMD threshold interrupts.

Rely on the similar approach as of Intel's CMCI to mitigate storms per CPU and
per bank. But, unlike CMCI, do not set thresholds and reduce interrupt rate on
a storm. Rather, disable the interrupt on the corresponding CPU and bank.
Re-enable back the interrupts if enough consecutive polls of the bank show no
corrected errors (30, as programmed by Intel).

Turning off the threshold interrupts would be a better solution on AMD systems
as other error severities will still be handled even if the threshold
interrupts are disabled.

  [ Tony: Small tweak because mce_handle_storm() isn't a pointer now ]
  [ Yazen: Rebase and simplify ]
  [ Avadhut: Remove check to not clear bank's bit in mce_poll_banks and fix
    checkpatch warnings. ]

Signed-off-by: Smita Koralahalli <Smita.KoralahalliChannabasappa@amd.com>
Signed-off-by: Tony Luck <tony.luck@intel.com>
Signed-off-by: Yazen Ghannam <yazen.ghannam@amd.com>
Signed-off-by: Avadhut Naik <avadhut.naik@amd.com>
Signed-off-by: Borislav Petkov (AMD) <bp@alien8.de>
Link: https://patch.msgid.link/20251121190542.2447913-3-avadhut.naik@amd.com
2025-11-21 20:41:10 +01:00

164 lines
4.2 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Common corrected MCE threshold handler code:
*/
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <asm/irq_vectors.h>
#include <asm/traps.h>
#include <asm/apic.h>
#include <asm/mce.h>
#include <asm/trace/irq_vectors.h>
#include "internal.h"
static u32 mce_apei_thr_limit;
void mce_save_apei_thr_limit(u32 thr_limit)
{
mce_apei_thr_limit = thr_limit;
pr_info("HEST corrected error threshold limit: %u\n", thr_limit);
}
u32 mce_get_apei_thr_limit(void)
{
return mce_apei_thr_limit;
}
static void default_threshold_interrupt(void)
{
pr_err("Unexpected threshold interrupt at vector %x\n",
THRESHOLD_APIC_VECTOR);
}
void (*mce_threshold_vector)(void) = default_threshold_interrupt;
DEFINE_IDTENTRY_SYSVEC(sysvec_threshold)
{
trace_threshold_apic_entry(THRESHOLD_APIC_VECTOR);
inc_irq_stat(irq_threshold_count);
mce_threshold_vector();
trace_threshold_apic_exit(THRESHOLD_APIC_VECTOR);
apic_eoi();
}
DEFINE_PER_CPU(struct mca_storm_desc, storm_desc);
void mce_inherit_storm(unsigned int bank)
{
struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc);
/*
* Previous CPU owning this bank had put it into storm mode,
* but the precise history of that storm is unknown. Assume
* the worst (all recent polls of the bank found a valid error
* logged). This will avoid the new owner prematurely declaring
* the storm has ended.
*/
storm->banks[bank].history = ~0ull;
storm->banks[bank].timestamp = jiffies;
}
bool mce_get_storm_mode(void)
{
return __this_cpu_read(storm_desc.poll_mode);
}
void mce_set_storm_mode(bool storm)
{
__this_cpu_write(storm_desc.poll_mode, storm);
}
static void mce_handle_storm(unsigned int bank, bool on)
{
switch (boot_cpu_data.x86_vendor) {
case X86_VENDOR_INTEL:
mce_intel_handle_storm(bank, on);
break;
case X86_VENDOR_AMD:
mce_amd_handle_storm(bank, on);
break;
}
}
void cmci_storm_begin(unsigned int bank)
{
struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc);
__set_bit(bank, this_cpu_ptr(mce_poll_banks));
storm->banks[bank].in_storm_mode = true;
/*
* If this is the first bank on this CPU to enter storm mode
* start polling.
*/
if (++storm->stormy_bank_count == 1)
mce_timer_kick(true);
}
void cmci_storm_end(unsigned int bank)
{
struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc);
if (!mce_flags.amd_threshold)
__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);
}
void mce_track_storm(struct mce *mce)
{
struct mca_storm_desc *storm = this_cpu_ptr(&storm_desc);
unsigned long now = jiffies, delta;
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.
* If it is not in storm mode, then the bank is only checked when
* there is a CMCI interrupt. Check how long it has been since
* this bank was last checked, and adjust the amount of "shift"
* to apply to history.
*/
if (!storm->banks[mce->bank].in_storm_mode) {
delta = now - storm->banks[mce->bank].timestamp;
shift = (delta + HZ) / HZ;
}
/* If it has been a long time since the last poll, clear history. */
if (shift < NUM_HISTORY_BITS)
history = storm->banks[mce->bank].history << shift;
storm->banks[mce->bank].timestamp = now;
/* History keeps track of corrected errors. VAL=1 && UC=0 */
if ((mce->status & MCI_STATUS_VAL) && mce_is_correctable(mce))
history |= 1;
storm->banks[mce->bank].history = history;
if (storm->banks[mce->bank].in_storm_mode) {
if (history & GENMASK_ULL(STORM_END_POLL_THRESHOLD, 0))
return;
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;
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);
}
}