diff --git a/kernel/arch/i386/arch_watchdog.c b/kernel/arch/i386/arch_watchdog.c index 3c454d6ce..f05dae872 100644 --- a/kernel/arch/i386/arch_watchdog.c +++ b/kernel/arch/i386/arch_watchdog.c @@ -18,7 +18,9 @@ * Intel architecture performance counters watchdog */ -PRIVATE void intel_arch_watchdog_init(int cpu) +PRIVATE struct arch_watchdog intel_arch_watchdog; + +PRIVATE void intel_arch_watchdog_init(const unsigned cpu) { u64_t cpuf; u32_t val; @@ -36,7 +38,7 @@ PRIVATE void intel_arch_watchdog_init(int cpu) cpuf = cpu_get_freq(cpu); while (cpuf.hi || cpuf.lo > 0x7fffffffU) cpuf = div64u64(cpuf, 2); - watchdog->resetval = cpuf.lo; + watchdog->resetval = watchdog->watchdog_resetval = cpuf.lo; ia32_msr_write(MSR_PERFMON_CRT0, 0, -cpuf.lo); @@ -46,21 +48,21 @@ PRIVATE void intel_arch_watchdog_init(int cpu) lapic_write(LAPIC_LVTPCR, APIC_ICR_DM_NMI); } -PRIVATE void intel_arch_watchdog_reinit(const int cpu) +PRIVATE void intel_arch_watchdog_reinit(const unsigned 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) +PUBLIC int arch_watchdog_init(void) { u32_t eax, ebx, ecx, edx; + if (!lapic_addr) { + printf("ERROR : Cannot use NMI watchdog if APIC is not enabled\n"); + return -1; + } + eax = 0xA; _cpuid(&eax, &ebx, &ecx, &edx); @@ -81,14 +83,18 @@ int arch_watchdog_init(void) (void) lapic_read(LAPIC_LVTPCR); /* double check if LAPIC is enabled */ - if (lapic_addr && watchdog_enabled && watchdog->init) { + if (lapic_addr && watchdog->init) { watchdog->init(cpuid); } return 0; } -void arch_watchdog_lockup(const struct nmi_frame * frame) +PUBLIC void arch_watchdog_stop(void) +{ +} + +PUBLIC void arch_watchdog_lockup(const struct nmi_frame * frame) { printf("KERNEL LOCK UP\n" "eax 0x%08x\n" @@ -123,15 +129,44 @@ void arch_watchdog_lockup(const struct nmi_frame * frame) panic("Kernel lockup"); } -void i386_watchdog_start(void) +PUBLIC int i386_watchdog_start(void) { - if (watchdog_enabled) { - if (arch_watchdog_init()) { - printf("WARNING watchdog initialization " - "failed! Disabled\n"); - watchdog_enabled = 0; - } - else - BOOT_VERBOSE(printf("Watchdog enabled\n");); + if (arch_watchdog_init()) { + printf("WARNING watchdog initialization " + "failed! Disabled\n"); + watchdog_enabled = 0; + return -1; } + else + BOOT_VERBOSE(printf("Watchdog enabled\n");); + + return 0; } + +PRIVATE int intel_arch_watchdog_profile_init(const unsigned freq) +{ + u64_t cpuf; + + /* FIXME works only if all CPUs have the same freq */ + cpuf = cpu_get_freq(cpuid); + cpuf = div64u64(cpuf, freq); + + /* + * if freq is too low and the cpu freq too high we may get in a range of + * insane value which cannot be handled by the 31bit CPU perf counter + */ + if (cpuf.hi != 0 || cpuf.lo > 0x7fffffffU) { + printf("ERROR : nmi watchdog ticks exceed 31bits, use higher frequency\n"); + return EINVAL; + } + + watchdog->profile_resetval = cpuf.lo; + + return OK; +} + +PRIVATE struct arch_watchdog intel_arch_watchdog = { + /*.init = */ intel_arch_watchdog_init, + /*.reinit = */ intel_arch_watchdog_reinit, + /*.profile_init = */ intel_arch_watchdog_profile_init +}; diff --git a/kernel/arch/i386/include/arch_watchdog.h b/kernel/arch/i386/include/arch_watchdog.h index c205094e1..24c429804 100644 --- a/kernel/arch/i386/include/arch_watchdog.h +++ b/kernel/arch/i386/include/arch_watchdog.h @@ -21,6 +21,8 @@ struct nmi_frame { reg_t eflags; }; -void i386_watchdog_start(void); +_PROTOTYPE(int i386_watchdog_start, (void)); + +#define nmi_in_kernel(f) ((f)->cs == CS_SELECTOR) #endif /* __I386_WATCHDOG_H__ */ diff --git a/kernel/arch/i386/memory.c b/kernel/arch/i386/memory.c index 3650e900d..4d7417ae8 100644 --- a/kernel/arch/i386/memory.c +++ b/kernel/arch/i386/memory.c @@ -1103,11 +1103,12 @@ PUBLIC int arch_enable_paging(struct proc * caller, const message * m_ptr) #ifdef CONFIG_WATCHDOG /* * We make sure that we don't enable the watchdog until paging is turned - * on as we might get a NMI while switching and we might still use wrong + * on as we might get an NMI while switching and we might still use wrong * lapic address. Bad things would happen. It is unfortunate but such is * life */ - i386_watchdog_start(); + if (watchdog_enabled) + i386_watchdog_start(); #endif return OK; diff --git a/kernel/profile.c b/kernel/profile.c index c1c3c653a..626202338 100644 --- a/kernel/profile.c +++ b/kernel/profile.c @@ -26,6 +26,7 @@ #if SPROFILE #include +#include "watchdog.h" /* Function prototype for the profiling clock handler. */ FORWARD _PROTOTYPE( int profile_clock_handler, (irq_hook_t *hook) ); @@ -90,25 +91,20 @@ PRIVATE sprof_save_proc(struct proc * p) sprof_info.mem_used += sizeof(s); } -/*===========================================================================* - * profile_clock_handler * - *===========================================================================*/ -PRIVATE int profile_clock_handler(irq_hook_t *hook) +PRIVATE void profile_sample(struct proc * p) { - struct proc * p; /* This executes on every tick of the CMOS timer. */ /* Are we profiling, and profiling memory not full? */ - if (!sprofiling || sprof_info.mem_used == -1) return (1); + if (!sprofiling || sprof_info.mem_used == -1) + return; /* Check if enough memory available before writing sample. */ if (sprof_info.mem_used + sizeof(sprof_info) > sprof_mem_size) { sprof_info.mem_used = -1; - return(1); + return; } - p = get_cpulocal_var(proc_ptr); - if (!(p->p_misc_flags & MF_SPROF_SEEN)) { p->p_misc_flags |= MF_SPROF_SEEN; sprof_save_proc(p); @@ -126,6 +122,17 @@ PRIVATE int profile_clock_handler(irq_hook_t *hook) } sprof_info.total_samples++; +} + +/*===========================================================================* + * profile_clock_handler * + *===========================================================================*/ +PRIVATE int profile_clock_handler(irq_hook_t *hook) +{ + struct proc * p; + p = get_cpulocal_var(proc_ptr); + + profile_sample(p); /* Acknowledge interrupt if necessary. */ arch_ack_profile_clock(); @@ -133,6 +140,25 @@ PRIVATE int profile_clock_handler(irq_hook_t *hook) return(1); /* reenable interrupts */ } +PUBLIC void nmi_sprofile_handler(struct nmi_frame * frame) +{ + /* + * test if the kernel was interrupted. If so, save first a sample fo + * kernel and than for the current process, otherwise save just the + * process + */ + if (nmi_in_kernel(frame)) { + struct proc *kern; + + kern = proc_addr(KERNEL); + kern->p_reg.pc = frame->pc; + + profile_sample(kern); + } + + profile_sample(get_cpulocal_var(proc_ptr)); +} + #endif /* SPROFILE */ #if CPROFILE diff --git a/kernel/profile.h b/kernel/profile.h index e09847f35..275ec8711 100644 --- a/kernel/profile.h +++ b/kernel/profile.h @@ -5,12 +5,17 @@ #if SPROFILE /* statistical profiling */ +#include "arch_watchdog.h" + EXTERN int sprofiling; /* whether profiling is running */ +EXTERN int sprofiling_type; /* whether profiling is running */ EXTERN int sprof_mem_size; /* available user memory for data */ EXTERN struct sprof_info_s sprof_info; /* profiling info for user program */ EXTERN vir_bytes sprof_data_addr_vir; /* user address to write data */ EXTERN endpoint_t sprof_ep; /* user process */ +_PROTOTYPE(void nmi_sprofile_handler, (struct nmi_frame * frame)); + #endif /* SPROFILE */ diff --git a/kernel/system/do_sprofile.c b/kernel/system/do_sprofile.c index 92a8b1518..d4f4b404d 100644 --- a/kernel/system/do_sprofile.c +++ b/kernel/system/do_sprofile.c @@ -34,6 +34,7 @@ PRIVATE clean_seen_flag(void) PUBLIC int do_sprofile(struct proc * caller, message * m_ptr) { int proc_nr; + int err; switch(m_ptr->PROF_ACTION) { @@ -66,7 +67,19 @@ PUBLIC int do_sprofile(struct proc * caller, message * m_ptr) sprof_mem_size = m_ptr->PROF_MEM_SIZE; - init_profile_clock(m_ptr->PROF_FREQ); + switch (sprofiling_type = m_ptr->PROF_INTR_TYPE) { + case PROF_RTC: + init_profile_clock(m_ptr->PROF_FREQ); + break; + case PROF_NMI: + err = nmi_watchdog_start_profiling(m_ptr->PROF_FREQ); + if (err) + return err; + break; + default: + printf("ERROR : unknown profiling interrupt type\n"); + return EINVAL; + } sprofiling = 1; @@ -87,7 +100,14 @@ PUBLIC int do_sprofile(struct proc * caller, message * m_ptr) sprofiling = 0; - stop_profile_clock(); + switch (sprofiling_type) { + case PROF_RTC: + stop_profile_clock(); + break; + case PROF_NMI: + nmi_watchdog_stop_profiling(); + break; + } data_copy(KERNEL, (vir_bytes) &sprof_info, sprof_ep, sprof_info_addr_vir, sizeof(sprof_info)); diff --git a/kernel/watchdog.c b/kernel/watchdog.c index b4c19c8b6..739aa3e5d 100644 --- a/kernel/watchdog.c +++ b/kernel/watchdog.c @@ -6,12 +6,13 @@ #include "watchdog.h" #include "arch/i386/glo.h" +#include "profile.h" unsigned watchdog_local_timer_ticks = 0U; struct arch_watchdog *watchdog; int watchdog_enabled; -void nmi_watchdog_handler(struct nmi_frame * frame) +PRIVATE void lockup_check(struct nmi_frame * frame) { /* FIXME this should be CPU local */ static unsigned no_ticks; @@ -23,7 +24,7 @@ void nmi_watchdog_handler(struct nmi_frame * frame) * report a lockup in such situation */ if (serial_debug_active) - goto reset_and_continue; + return; if (last_tick_count != watchdog_local_timer_ticks) { if (no_ticks == 1) { @@ -32,7 +33,7 @@ void nmi_watchdog_handler(struct nmi_frame * frame) } /* we are still ticking, everything seems good */ last_tick_count = watchdog_local_timer_ticks; - goto reset_and_continue; + return; } /* @@ -42,12 +43,63 @@ void nmi_watchdog_handler(struct nmi_frame * frame) if (++no_ticks < 10) { if (no_ticks == 1) printf("WARNING watchdog : possible kernel lockup\n"); - goto reset_and_continue; + return; } + /* if we get this far, the kernel is locked up */ arch_watchdog_lockup(frame); +} -reset_and_continue: - if (watchdog->reinit) +PUBLIC void nmi_watchdog_handler(struct nmi_frame * frame) +{ + /* + * Do not check for lockups while profiling, it is extremely likely that + * a false positive is detected if the frequency is high + */ + if (watchdog_enabled && !sprofiling) + lockup_check(frame); + if (sprofiling) + nmi_sprofile_handler(frame); + + if ((watchdog_enabled || sprofiling) && watchdog->reinit) watchdog->reinit(cpuid); } + +int nmi_watchdog_start_profiling(const unsigned freq) +{ + int err; + + /* if watchdog hasn't been enabled, we must enable it now */ + if (!watchdog_enabled) { + if (arch_watchdog_init()) + return ENODEV; + } + + if (!watchdog->profile_init) { + printf("WARNING NMI watchdog profiling not supported\n"); + nmi_watchdog_stop_profiling(); + return ENODEV; + } + + err = watchdog->profile_init(freq); + if (err != OK) + return err; + + watchdog->resetval = watchdog->profile_resetval; + + return OK; +} + +void nmi_watchdog_stop_profiling(void) +{ + /* + * if we do not rearm the NMI source, we are done, if we want to keep + * the watchdog running, we reset is to its normal value + */ + + if (watchdog) + watchdog->resetval = watchdog->watchdog_resetval; + + if (!watchdog_enabled) + arch_watchdog_stop(); +} diff --git a/kernel/watchdog.h b/kernel/watchdog.h index 04cc0987b..521fd2fa6 100644 --- a/kernel/watchdog.h +++ b/kernel/watchdog.h @@ -13,18 +13,23 @@ extern unsigned watchdog_local_timer_ticks; /* is timer still ticking? */ * implement it in runtime after the correct arch/model was detected */ -typedef void (* arch_watchdog_method_t)(int); +typedef void (* arch_watchdog_method_t)(const unsigned); +typedef int (* arch_watchdog_profile_init_t)(const unsigned); struct arch_watchdog { - arch_watchdog_method_t init; /* initial setup */ - arch_watchdog_method_t reinit; /* reinitialization after a tick */ - unsigned resetval; + arch_watchdog_method_t init; /* initial setup */ + arch_watchdog_method_t reinit; /* reinit after a tick */ + arch_watchdog_profile_init_t profile_init; + unsigned resetval; + unsigned watchdog_resetval; + unsigned profile_resetval; }; extern struct arch_watchdog *watchdog; -/* let the arch code do whatever it needs to setup the watchdog */ +/* let the arch code do whatever it needs to setup or quit the watchdog */ int arch_watchdog_init(void); +void arch_watchdog_stop(void); /* if the watchdog detects lockup, let the arch code to handle it */ void arch_watchdog_lockup(const struct nmi_frame * frame); @@ -33,4 +38,10 @@ void arch_watchdog_lockup(const struct nmi_frame * frame); * arch specific code of the watchdog implementaion */ void nmi_watchdog_handler(struct nmi_frame * frame); +/* + * start and stop profiling using the NMI watchdog + */ +int nmi_watchdog_start_profiling(const unsigned freq); +void nmi_watchdog_stop_profiling(void); + #endif /* __WATCHDOG_H__ */