mirror of
https://mirrors.bfsu.edu.cn/git/linux.git
synced 2024-11-17 01:04:19 +08:00
4efc0670ba
The 64bit machine check code is in many ways much better than the 32bit machine check code: it is more specification compliant, is cleaner, only has a single code base versus one per CPU, has better infrastructure for recovery, has a cleaner way to communicate with user space etc. etc. Use the 64bit code for 32bit too. This is the second attempt to do this. There was one a couple of years ago to unify this code for 32bit and 64bit. Back then this ran into some trouble with K7s and was reverted. I believe this time the K7 problems (and some others) are addressed. I went over the old handlers and was very careful to retain all quirks. But of course this needs a lot of testing on old systems. On newer 64bit capable systems I don't expect much problems because they have been already tested with the 64bit kernel. I made this a CONFIG for now that still allows to select the old machine check code. This is mostly to make testing easier, if someone runs into a problem we can ask them to try with the CONFIG switched. The new code is default y for more coverage. Once there is confidence the 64bit code works well on older hardware too the CONFIG_X86_OLD_MCE and the associated code can be easily removed. This causes a behaviour change for 32bit installations. They now have to install the mcelog package to be able to log corrected machine checks. The 64bit machine check code only handles CPUs which support the standard Intel machine check architecture described in the IA32 SDM. The 32bit code has special support for some older CPUs which have non standard machine check architectures, in particular WinChip C3 and Intel P5. I made those a separate CONFIG option and kept them for now. The WinChip variant could be probably removed without too much pain, it doesn't really do anything interesting. P5 is also disabled by default (like it was before) because many motherboards have it miswired, but according to Alan Cox a few embedded setups use that one. Forward ported/heavily changed version of old patch, original patch included review/fixes from Thomas Gleixner, Bert Wesarg. Signed-off-by: Andi Kleen <ak@linux.intel.com> Signed-off-by: H. Peter Anvin <hpa@zytor.com> Signed-off-by: Hidetoshi Seto <seto.hidetoshi@jp.fujitsu.com> Signed-off-by: H. Peter Anvin <hpa@zytor.com>
256 lines
6.0 KiB
C
256 lines
6.0 KiB
C
/*
|
|
* Common interrupt code for 32 and 64 bit
|
|
*/
|
|
#include <linux/cpu.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/kernel_stat.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/smp.h>
|
|
#include <linux/ftrace.h>
|
|
|
|
#include <asm/apic.h>
|
|
#include <asm/io_apic.h>
|
|
#include <asm/irq.h>
|
|
#include <asm/idle.h>
|
|
|
|
atomic_t irq_err_count;
|
|
|
|
/* Function pointer for generic interrupt vector handling */
|
|
void (*generic_interrupt_extension)(void) = NULL;
|
|
|
|
/*
|
|
* 'what should we do if we get a hw irq event on an illegal vector'.
|
|
* each architecture has to answer this themselves.
|
|
*/
|
|
void ack_bad_irq(unsigned int irq)
|
|
{
|
|
printk(KERN_ERR "unexpected IRQ trap at vector %02x\n", irq);
|
|
|
|
#ifdef CONFIG_X86_LOCAL_APIC
|
|
/*
|
|
* Currently unexpected vectors happen only on SMP and APIC.
|
|
* We _must_ ack these because every local APIC has only N
|
|
* irq slots per priority level, and a 'hanging, unacked' IRQ
|
|
* holds up an irq slot - in excessive cases (when multiple
|
|
* unexpected vectors occur) that might lock up the APIC
|
|
* completely.
|
|
* But only ack when the APIC is enabled -AK
|
|
*/
|
|
if (cpu_has_apic)
|
|
ack_APIC_irq();
|
|
#endif
|
|
}
|
|
|
|
#define irq_stats(x) (&per_cpu(irq_stat, x))
|
|
/*
|
|
* /proc/interrupts printing:
|
|
*/
|
|
static int show_other_interrupts(struct seq_file *p, int prec)
|
|
{
|
|
int j;
|
|
|
|
seq_printf(p, "%*s: ", prec, "NMI");
|
|
for_each_online_cpu(j)
|
|
seq_printf(p, "%10u ", irq_stats(j)->__nmi_count);
|
|
seq_printf(p, " Non-maskable interrupts\n");
|
|
#ifdef CONFIG_X86_LOCAL_APIC
|
|
seq_printf(p, "%*s: ", prec, "LOC");
|
|
for_each_online_cpu(j)
|
|
seq_printf(p, "%10u ", irq_stats(j)->apic_timer_irqs);
|
|
seq_printf(p, " Local timer interrupts\n");
|
|
|
|
seq_printf(p, "%*s: ", prec, "SPU");
|
|
for_each_online_cpu(j)
|
|
seq_printf(p, "%10u ", irq_stats(j)->irq_spurious_count);
|
|
seq_printf(p, " Spurious interrupts\n");
|
|
#endif
|
|
if (generic_interrupt_extension) {
|
|
seq_printf(p, "%*s: ", prec, "PLT");
|
|
for_each_online_cpu(j)
|
|
seq_printf(p, "%10u ", irq_stats(j)->generic_irqs);
|
|
seq_printf(p, " Platform interrupts\n");
|
|
}
|
|
#ifdef CONFIG_SMP
|
|
seq_printf(p, "%*s: ", prec, "RES");
|
|
for_each_online_cpu(j)
|
|
seq_printf(p, "%10u ", irq_stats(j)->irq_resched_count);
|
|
seq_printf(p, " Rescheduling interrupts\n");
|
|
seq_printf(p, "%*s: ", prec, "CAL");
|
|
for_each_online_cpu(j)
|
|
seq_printf(p, "%10u ", irq_stats(j)->irq_call_count);
|
|
seq_printf(p, " Function call interrupts\n");
|
|
seq_printf(p, "%*s: ", prec, "TLB");
|
|
for_each_online_cpu(j)
|
|
seq_printf(p, "%10u ", irq_stats(j)->irq_tlb_count);
|
|
seq_printf(p, " TLB shootdowns\n");
|
|
#endif
|
|
#ifdef CONFIG_X86_MCE
|
|
seq_printf(p, "%*s: ", prec, "TRM");
|
|
for_each_online_cpu(j)
|
|
seq_printf(p, "%10u ", irq_stats(j)->irq_thermal_count);
|
|
seq_printf(p, " Thermal event interrupts\n");
|
|
# ifdef CONFIG_X86_MCE_THRESHOLD
|
|
seq_printf(p, "%*s: ", prec, "THR");
|
|
for_each_online_cpu(j)
|
|
seq_printf(p, "%10u ", irq_stats(j)->irq_threshold_count);
|
|
seq_printf(p, " Threshold APIC interrupts\n");
|
|
# endif
|
|
#endif
|
|
seq_printf(p, "%*s: %10u\n", prec, "ERR", atomic_read(&irq_err_count));
|
|
#if defined(CONFIG_X86_IO_APIC)
|
|
seq_printf(p, "%*s: %10u\n", prec, "MIS", atomic_read(&irq_mis_count));
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
int show_interrupts(struct seq_file *p, void *v)
|
|
{
|
|
unsigned long flags, any_count = 0;
|
|
int i = *(loff_t *) v, j, prec;
|
|
struct irqaction *action;
|
|
struct irq_desc *desc;
|
|
|
|
if (i > nr_irqs)
|
|
return 0;
|
|
|
|
for (prec = 3, j = 1000; prec < 10 && j <= nr_irqs; ++prec)
|
|
j *= 10;
|
|
|
|
if (i == nr_irqs)
|
|
return show_other_interrupts(p, prec);
|
|
|
|
/* print header */
|
|
if (i == 0) {
|
|
seq_printf(p, "%*s", prec + 8, "");
|
|
for_each_online_cpu(j)
|
|
seq_printf(p, "CPU%-8d", j);
|
|
seq_putc(p, '\n');
|
|
}
|
|
|
|
desc = irq_to_desc(i);
|
|
if (!desc)
|
|
return 0;
|
|
|
|
spin_lock_irqsave(&desc->lock, flags);
|
|
for_each_online_cpu(j)
|
|
any_count |= kstat_irqs_cpu(i, j);
|
|
action = desc->action;
|
|
if (!action && !any_count)
|
|
goto out;
|
|
|
|
seq_printf(p, "%*d: ", prec, i);
|
|
for_each_online_cpu(j)
|
|
seq_printf(p, "%10u ", kstat_irqs_cpu(i, j));
|
|
seq_printf(p, " %8s", desc->chip->name);
|
|
seq_printf(p, "-%-8s", desc->name);
|
|
|
|
if (action) {
|
|
seq_printf(p, " %s", action->name);
|
|
while ((action = action->next) != NULL)
|
|
seq_printf(p, ", %s", action->name);
|
|
}
|
|
|
|
seq_putc(p, '\n');
|
|
out:
|
|
spin_unlock_irqrestore(&desc->lock, flags);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* /proc/stat helpers
|
|
*/
|
|
u64 arch_irq_stat_cpu(unsigned int cpu)
|
|
{
|
|
u64 sum = irq_stats(cpu)->__nmi_count;
|
|
|
|
#ifdef CONFIG_X86_LOCAL_APIC
|
|
sum += irq_stats(cpu)->apic_timer_irqs;
|
|
sum += irq_stats(cpu)->irq_spurious_count;
|
|
#endif
|
|
if (generic_interrupt_extension)
|
|
sum += irq_stats(cpu)->generic_irqs;
|
|
#ifdef CONFIG_SMP
|
|
sum += irq_stats(cpu)->irq_resched_count;
|
|
sum += irq_stats(cpu)->irq_call_count;
|
|
sum += irq_stats(cpu)->irq_tlb_count;
|
|
#endif
|
|
#ifdef CONFIG_X86_MCE
|
|
sum += irq_stats(cpu)->irq_thermal_count;
|
|
# ifdef CONFIG_X86_MCE_THRESHOLD
|
|
sum += irq_stats(cpu)->irq_threshold_count;
|
|
#endif
|
|
#endif
|
|
return sum;
|
|
}
|
|
|
|
u64 arch_irq_stat(void)
|
|
{
|
|
u64 sum = atomic_read(&irq_err_count);
|
|
|
|
#ifdef CONFIG_X86_IO_APIC
|
|
sum += atomic_read(&irq_mis_count);
|
|
#endif
|
|
return sum;
|
|
}
|
|
|
|
|
|
/*
|
|
* do_IRQ handles all normal device IRQ's (the special
|
|
* SMP cross-CPU interrupts have their own specific
|
|
* handlers).
|
|
*/
|
|
unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
|
|
{
|
|
struct pt_regs *old_regs = set_irq_regs(regs);
|
|
|
|
/* high bit used in ret_from_ code */
|
|
unsigned vector = ~regs->orig_ax;
|
|
unsigned irq;
|
|
|
|
exit_idle();
|
|
irq_enter();
|
|
|
|
irq = __get_cpu_var(vector_irq)[vector];
|
|
|
|
if (!handle_irq(irq, regs)) {
|
|
#ifdef CONFIG_X86_64
|
|
if (!disable_apic)
|
|
ack_APIC_irq();
|
|
#endif
|
|
|
|
if (printk_ratelimit())
|
|
printk(KERN_EMERG "%s: %d.%d No irq handler for vector (irq %d)\n",
|
|
__func__, smp_processor_id(), vector, irq);
|
|
}
|
|
|
|
irq_exit();
|
|
|
|
set_irq_regs(old_regs);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* Handler for GENERIC_INTERRUPT_VECTOR.
|
|
*/
|
|
void smp_generic_interrupt(struct pt_regs *regs)
|
|
{
|
|
struct pt_regs *old_regs = set_irq_regs(regs);
|
|
|
|
ack_APIC_irq();
|
|
|
|
exit_idle();
|
|
|
|
irq_enter();
|
|
|
|
inc_irq_stat(generic_irqs);
|
|
|
|
if (generic_interrupt_extension)
|
|
generic_interrupt_extension();
|
|
|
|
irq_exit();
|
|
|
|
set_irq_regs(old_regs);
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(vector_used_by_percpu_irq);
|