minix/kernel/arch/i386/watchdog.c

136 lines
2.8 KiB
C
Raw Normal View History

NMI watchdog is an awesome feature for debugging locked up kernels. There is not that much use for it on a single CPU, however, deadlock between kernel and system task can be delected. Or a runaway loop. If a kernel gets locked up the timer interrupts don't occure (as all interrupts are disabled in kernel mode). The only chance is to interrupt the kernel by a non-maskable interrupt. This patch generates NMIs using performance counters. It uses the most widely available performace counters. As the performance counters are highly model-specific this patch is not guaranteed to work on every machine. Unfortunately this is also true for KVM :-/ On the other hand adding this feature for other models is not extremely difficult and the framework makes it hopefully easy enough. Depending on the frequency of the CPU an NMI is generated at most about every 0.5s If the cpu's speed is less then 2Ghz it is generated at most every 1s. In general an NMI is generated much less often as the performance counter counts down only if the cpu is not idle. Therefore the overhead of this feature is fairly minimal even if the load is high. Uppon detecting that the kernel is locked up the kernel dumps the state of the kernel registers and panics. Local APIC must be enabled for the watchdog to work. The code is _always_ compiled in, however, it is only enabled if watchdog=<non-zero> is set in the boot monitor. One corner case is serial console debugging. As dumping a lot of stuff to the serial link may take a lot of time, the watchdog does not detect lockups during this time!!! as it would result in too many false positives. 10 nmi have to be handled before the lockup is detected. This means something between ~5s to 10s. Another corner case is that the watchdog is enabled only after the paging is enabled as it would be pure madness to try to get it right.
2010-01-16 21:53:55 +01:00
#include "../../kernel.h"
#include "../../watchdog.h"
2010-01-19 15:47:25 +01:00
#include "proto.h"
#include <minix/minlib.h>
NMI watchdog is an awesome feature for debugging locked up kernels. There is not that much use for it on a single CPU, however, deadlock between kernel and system task can be delected. Or a runaway loop. If a kernel gets locked up the timer interrupts don't occure (as all interrupts are disabled in kernel mode). The only chance is to interrupt the kernel by a non-maskable interrupt. This patch generates NMIs using performance counters. It uses the most widely available performace counters. As the performance counters are highly model-specific this patch is not guaranteed to work on every machine. Unfortunately this is also true for KVM :-/ On the other hand adding this feature for other models is not extremely difficult and the framework makes it hopefully easy enough. Depending on the frequency of the CPU an NMI is generated at most about every 0.5s If the cpu's speed is less then 2Ghz it is generated at most every 1s. In general an NMI is generated much less often as the performance counter counts down only if the cpu is not idle. Therefore the overhead of this feature is fairly minimal even if the load is high. Uppon detecting that the kernel is locked up the kernel dumps the state of the kernel registers and panics. Local APIC must be enabled for the watchdog to work. The code is _always_ compiled in, however, it is only enabled if watchdog=<non-zero> is set in the boot monitor. One corner case is serial console debugging. As dumping a lot of stuff to the serial link may take a lot of time, the watchdog does not detect lockups during this time!!! as it would result in too many false positives. 10 nmi have to be handled before the lockup is detected. This means something between ~5s to 10s. Another corner case is that the watchdog is enabled only after the paging is enabled as it would be pure madness to try to get it right.
2010-01-16 21:53:55 +01:00
#include "apic.h"
#define CPUID_UNHALTED_CORE_CYCLES_AVAILABLE 0
#define MSR_PERFMON_CRT0 0xc1
#define MSR_PERFMON_SEL0 0x186
#define MSR_PERFMON_SEL0_ENABLE (1 << 22)
/*
* Intel architecture performance counters watchdog
*/
PRIVATE void intel_arch_watchdog_init(int cpu)
{
u32_t cpuf;
u32_t val;
ia32_msr_write(MSR_PERFMON_CRT0, 0, 0);
/* Int, OS, USR, Core ccyles */
val = 1 << 20 | 1 << 17 | 1 << 16 | 0x3c;
ia32_msr_write(MSR_PERFMON_SEL0, 0, val);
/*
* should give as a tick approx. every 0.5-1s, the perf counter has only
* lowest 31 bits writable :(
*/
cpuf = cpu_get_freq(cpu);
if (cpuf > 0x7fffffffU)
cpuf >>= 2;
watchdog->resetval = cpuf;
ia32_msr_write(MSR_PERFMON_CRT0, 0, -cpuf);
ia32_msr_write(MSR_PERFMON_SEL0, 0, val | MSR_PERFMON_SEL0_ENABLE);
/* unmask the performance counter interrupt */
lapic_write(LAPIC_LVTPCR, APIC_ICR_DM_NMI);
}
PRIVATE void intel_arch_watchdog_reinit(int cpu)
{
lapic_write(LAPIC_LVTPCR, APIC_ICR_DM_NMI);
ia32_msr_write(MSR_PERFMON_CRT0, 0, -watchdog->resetval);
}
PRIVATE struct arch_watchdog intel_arch_watchdog = {
/*.init = */ intel_arch_watchdog_init,
/*.reinit = */ intel_arch_watchdog_reinit
};
int arch_watchdog_init(void)
{
2010-01-19 15:47:25 +01:00
u32_t eax, ebx, ecx, edx;
NMI watchdog is an awesome feature for debugging locked up kernels. There is not that much use for it on a single CPU, however, deadlock between kernel and system task can be delected. Or a runaway loop. If a kernel gets locked up the timer interrupts don't occure (as all interrupts are disabled in kernel mode). The only chance is to interrupt the kernel by a non-maskable interrupt. This patch generates NMIs using performance counters. It uses the most widely available performace counters. As the performance counters are highly model-specific this patch is not guaranteed to work on every machine. Unfortunately this is also true for KVM :-/ On the other hand adding this feature for other models is not extremely difficult and the framework makes it hopefully easy enough. Depending on the frequency of the CPU an NMI is generated at most about every 0.5s If the cpu's speed is less then 2Ghz it is generated at most every 1s. In general an NMI is generated much less often as the performance counter counts down only if the cpu is not idle. Therefore the overhead of this feature is fairly minimal even if the load is high. Uppon detecting that the kernel is locked up the kernel dumps the state of the kernel registers and panics. Local APIC must be enabled for the watchdog to work. The code is _always_ compiled in, however, it is only enabled if watchdog=<non-zero> is set in the boot monitor. One corner case is serial console debugging. As dumping a lot of stuff to the serial link may take a lot of time, the watchdog does not detect lockups during this time!!! as it would result in too many false positives. 10 nmi have to be handled before the lockup is detected. This means something between ~5s to 10s. Another corner case is that the watchdog is enabled only after the paging is enabled as it would be pure madness to try to get it right.
2010-01-16 21:53:55 +01:00
eax = 0xA;
_cpuid(&eax, &ebx, &ecx, &edx);
/* FIXME currently we support only watchdog base on the intel
* architectural performance counters. Some Intel CPUs don't have this
* feature
*/
if (ebx & (1 << CPUID_UNHALTED_CORE_CYCLES_AVAILABLE))
return -1;
if (!((((eax >> 8)) & 0xff) > 0))
return -1;
watchdog = &intel_arch_watchdog;
/* Setup PC tas NMI for watchdog, is is masked for now */
lapic_write(LAPIC_LVTPCR, APIC_ICR_INT_MASK | APIC_ICR_DM_NMI);
lapic_read(LAPIC_LVTPCR);
/* double check if LAPIC is enabled */
if (lapic_addr && watchdog_enabled && watchdog->init) {
watchdog->init(cpuid);
}
return 0;
}
void arch_watchdog_lockup(struct nmi_frame * frame)
{
kprintf("KERNEL LOCK UP\n"
"eax 0x%08x\n"
"ecx 0x%08x\n"
"edx 0x%08x\n"
"ebx 0x%08x\n"
"ebp 0x%08x\n"
"esi 0x%08x\n"
"edi 0x%08x\n"
"gs 0x%08x\n"
"fs 0x%08x\n"
"es 0x%08x\n"
"ds 0x%08x\n"
"pc 0x%08x\n"
"cs 0x%08x\n"
"eflags 0x%08x\n",
frame->eax,
frame->ecx,
frame->edx,
frame->ebx,
frame->ebp,
frame->esi,
frame->edi,
frame->gs,
frame->fs,
frame->es,
frame->ds,
frame->pc,
frame->cs,
frame->eflags
);
minix_panic("Kernel lockup\n", NO_NUM);
}
void i386_watchdog_start(void)
{
if (watchdog_enabled) {
if (arch_watchdog_init()) {
kprintf("WARNING watchdog initialization "
"failed! Disabled\n");
watchdog_enabled = 0;
}
else
BOOT_VERBOSE(kprintf("Watchdog enabled\n"););
}
}