| // 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(THRESHOLD_APIC); | |
| 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); | |
| } | |
| } |