2005-04-16 15:20:36 -07:00
/*
* linux / arch / i386 / nmi . c
*
* NMI watchdog support on APIC systems
*
* Started by Ingo Molnar < mingo @ redhat . com >
*
* Fixes :
* Mikael Pettersson : AMD K7 support for local APIC NMI watchdog .
* Mikael Pettersson : Power Management for local APIC NMI watchdog .
* Mikael Pettersson : Pentium 4 support for local APIC NMI watchdog .
* Pavel Machek and
* Mikael Pettersson : PM converted to driver model . Disable / enable API .
*/
# include <linux/delay.h>
# include <linux/interrupt.h>
# include <linux/module.h>
# include <linux/nmi.h>
# include <linux/sysdev.h>
# include <linux/sysctl.h>
2006-06-26 13:57:01 +02:00
# include <linux/percpu.h>
2006-09-26 10:52:27 +02:00
# include <linux/dmi.h>
2006-09-26 10:52:36 +02:00
# include <linux/kprobes.h>
2006-12-07 02:14:01 +01:00
# include <linux/cpumask.h>
2007-02-16 01:28:09 -08:00
# include <linux/kernel_stat.h>
2005-04-16 15:20:36 -07:00
# include <asm/smp.h>
# include <asm/nmi.h>
2006-09-26 10:52:26 +02:00
# include <asm/kdebug.h>
2006-09-26 10:52:27 +02:00
# include <asm/intel_arch_perfmon.h>
2005-04-16 15:20:36 -07:00
# include "mach_traps.h"
2006-09-30 01:47:55 +02:00
int unknown_nmi_panic ;
int nmi_watchdog_enabled ;
2006-09-26 10:52:26 +02:00
/* perfctr_nmi_owner tracks the ownership of the perfctr registers:
* evtsel_nmi_owner tracks the ownership of the event selection
* - different performance counters / event selection may be reserved for
* different subsystems this reservation system just tries to coordinate
* things a little
*/
static DEFINE_PER_CPU ( unsigned long , perfctr_nmi_owner ) ;
static DEFINE_PER_CPU ( unsigned long , evntsel_nmi_owner [ 3 ] ) ;
2006-12-07 02:14:01 +01:00
static cpumask_t backtrace_mask = CPU_MASK_NONE ;
2006-09-26 10:52:26 +02:00
/* this number is calculated from Intel's MSR_P4_CRU_ESCR5 register and it's
* offset from MSR_P4_BSU_ESCR0 . It will be the max for all platforms ( for now )
*/
# define NMI_MAX_COUNTER_BITS 66
2005-04-16 15:20:36 -07:00
/* nmi_active:
2006-09-26 10:52:26 +02:00
* > 0 : the lapic NMI watchdog is active , but can be disabled
* < 0 : the lapic NMI watchdog has not been set up , and cannot
2005-04-16 15:20:36 -07:00
* be enabled
2006-09-26 10:52:26 +02:00
* 0 : the lapic NMI watchdog is disabled , but can be enabled
2005-04-16 15:20:36 -07:00
*/
2006-09-26 10:52:26 +02:00
atomic_t nmi_active = ATOMIC_INIT ( 0 ) ; /* oprofile uses this */
2005-04-16 15:20:36 -07:00
2006-09-26 10:52:26 +02:00
unsigned int nmi_watchdog = NMI_DEFAULT ;
static unsigned int nmi_hz = HZ ;
2005-04-16 15:20:36 -07:00
2006-09-26 10:52:26 +02:00
struct nmi_watchdog_ctlblk {
int enabled ;
u64 check_bit ;
unsigned int cccr_msr ;
unsigned int perfctr_msr ; /* the MSR to reset in NMI handler */
unsigned int evntsel_msr ; /* the MSR to select the events to handle */
} ;
static DEFINE_PER_CPU ( struct nmi_watchdog_ctlblk , nmi_watchdog_ctlblk ) ;
2005-04-16 15:20:36 -07:00
2006-09-26 10:52:26 +02:00
/* local prototypes */
static int unknown_nmi_panic_callback ( struct pt_regs * regs , int cpu ) ;
extern void show_registers ( struct pt_regs * regs ) ;
extern int unknown_nmi_panic ;
2005-04-16 15:20:36 -07:00
2006-09-26 10:52:26 +02:00
/* converts an msr to an appropriate reservation bit */
static inline unsigned int nmi_perfctr_msr_to_bit ( unsigned int msr )
{
/* returns the bit offset of the performance counter register */
switch ( boot_cpu_data . x86_vendor ) {
case X86_VENDOR_AMD :
return ( msr - MSR_K7_PERFCTR0 ) ;
case X86_VENDOR_INTEL :
2006-09-26 10:52:27 +02:00
if ( cpu_has ( & boot_cpu_data , X86_FEATURE_ARCH_PERFMON ) )
return ( msr - MSR_ARCH_PERFMON_PERFCTR0 ) ;
2006-09-26 10:52:26 +02:00
switch ( boot_cpu_data . x86 ) {
case 6 :
return ( msr - MSR_P6_PERFCTR0 ) ;
case 15 :
return ( msr - MSR_P4_BPU_PERFCTR0 ) ;
}
}
return 0 ;
}
/* converts an msr to an appropriate reservation bit */
static inline unsigned int nmi_evntsel_msr_to_bit ( unsigned int msr )
{
/* returns the bit offset of the event selection register */
switch ( boot_cpu_data . x86_vendor ) {
case X86_VENDOR_AMD :
return ( msr - MSR_K7_EVNTSEL0 ) ;
case X86_VENDOR_INTEL :
2006-09-26 10:52:27 +02:00
if ( cpu_has ( & boot_cpu_data , X86_FEATURE_ARCH_PERFMON ) )
return ( msr - MSR_ARCH_PERFMON_EVENTSEL0 ) ;
2006-09-26 10:52:26 +02:00
switch ( boot_cpu_data . x86 ) {
case 6 :
return ( msr - MSR_P6_EVNTSEL0 ) ;
case 15 :
return ( msr - MSR_P4_BSU_ESCR0 ) ;
}
}
return 0 ;
}
/* checks for a bit availability (hack for oprofile) */
int avail_to_resrv_perfctr_nmi_bit ( unsigned int counter )
{
2007-04-02 12:14:12 +02:00
int cpu ;
2006-09-26 10:52:26 +02:00
BUG_ON ( counter > NMI_MAX_COUNTER_BITS ) ;
2007-04-02 12:14:12 +02:00
for_each_possible_cpu ( cpu ) {
if ( test_bit ( counter , & per_cpu ( perfctr_nmi_owner , cpu ) ) )
return 0 ;
}
return 1 ;
2006-09-26 10:52:26 +02:00
}
/* checks the an msr for availability */
int avail_to_resrv_perfctr_nmi ( unsigned int msr )
{
unsigned int counter ;
2007-04-02 12:14:12 +02:00
int cpu ;
2006-09-26 10:52:26 +02:00
counter = nmi_perfctr_msr_to_bit ( msr ) ;
BUG_ON ( counter > NMI_MAX_COUNTER_BITS ) ;
2007-04-02 12:14:12 +02:00
for_each_possible_cpu ( cpu ) {
if ( test_bit ( counter , & per_cpu ( perfctr_nmi_owner , cpu ) ) )
return 0 ;
}
return 1 ;
2006-09-26 10:52:26 +02:00
}
2007-04-02 12:14:12 +02:00
static int __reserve_perfctr_nmi ( int cpu , unsigned int msr )
2006-09-26 10:52:26 +02:00
{
unsigned int counter ;
2007-04-02 12:14:12 +02:00
if ( cpu < 0 )
cpu = smp_processor_id ( ) ;
2006-09-26 10:52:26 +02:00
counter = nmi_perfctr_msr_to_bit ( msr ) ;
BUG_ON ( counter > NMI_MAX_COUNTER_BITS ) ;
2007-04-02 12:14:12 +02:00
if ( ! test_and_set_bit ( counter , & per_cpu ( perfctr_nmi_owner , cpu ) ) )
2006-09-26 10:52:26 +02:00
return 1 ;
return 0 ;
}
2007-04-02 12:14:12 +02:00
static void __release_perfctr_nmi ( int cpu , unsigned int msr )
2006-09-26 10:52:26 +02:00
{
unsigned int counter ;
2007-04-02 12:14:12 +02:00
if ( cpu < 0 )
cpu = smp_processor_id ( ) ;
2006-09-26 10:52:26 +02:00
counter = nmi_perfctr_msr_to_bit ( msr ) ;
BUG_ON ( counter > NMI_MAX_COUNTER_BITS ) ;
2007-04-02 12:14:12 +02:00
clear_bit ( counter , & per_cpu ( perfctr_nmi_owner , cpu ) ) ;
2006-09-26 10:52:26 +02:00
}
2007-04-02 12:14:12 +02:00
int reserve_perfctr_nmi ( unsigned int msr )
{
int cpu , i ;
for_each_possible_cpu ( cpu ) {
if ( ! __reserve_perfctr_nmi ( cpu , msr ) ) {
for_each_possible_cpu ( i ) {
if ( i > = cpu )
break ;
__release_perfctr_nmi ( i , msr ) ;
}
return 0 ;
}
}
return 1 ;
}
void release_perfctr_nmi ( unsigned int msr )
{
int cpu ;
for_each_possible_cpu ( cpu ) {
__release_perfctr_nmi ( cpu , msr ) ;
}
}
int __reserve_evntsel_nmi ( int cpu , unsigned int msr )
2006-09-26 10:52:26 +02:00
{
unsigned int counter ;
2007-04-02 12:14:12 +02:00
if ( cpu < 0 )
cpu = smp_processor_id ( ) ;
2006-09-26 10:52:26 +02:00
counter = nmi_evntsel_msr_to_bit ( msr ) ;
BUG_ON ( counter > NMI_MAX_COUNTER_BITS ) ;
2007-04-02 12:14:12 +02:00
if ( ! test_and_set_bit ( counter , & per_cpu ( evntsel_nmi_owner , cpu ) [ 0 ] ) )
2006-09-26 10:52:26 +02:00
return 1 ;
return 0 ;
}
2007-04-02 12:14:12 +02:00
static void __release_evntsel_nmi ( int cpu , unsigned int msr )
2006-09-26 10:52:26 +02:00
{
unsigned int counter ;
2007-04-02 12:14:12 +02:00
if ( cpu < 0 )
cpu = smp_processor_id ( ) ;
2006-09-26 10:52:26 +02:00
counter = nmi_evntsel_msr_to_bit ( msr ) ;
BUG_ON ( counter > NMI_MAX_COUNTER_BITS ) ;
2007-04-02 12:14:12 +02:00
clear_bit ( counter , & per_cpu ( evntsel_nmi_owner , cpu ) [ 0 ] ) ;
}
int reserve_evntsel_nmi ( unsigned int msr )
{
int cpu , i ;
for_each_possible_cpu ( cpu ) {
if ( ! __reserve_evntsel_nmi ( cpu , msr ) ) {
for_each_possible_cpu ( i ) {
if ( i > = cpu )
break ;
__release_evntsel_nmi ( i , msr ) ;
}
return 0 ;
}
}
return 1 ;
}
void release_evntsel_nmi ( unsigned int msr )
{
int cpu ;
for_each_possible_cpu ( cpu ) {
__release_evntsel_nmi ( cpu , msr ) ;
}
2006-09-26 10:52:26 +02:00
}
2006-09-26 10:52:26 +02:00
static __cpuinit inline int nmi_known_cpu ( void )
{
switch ( boot_cpu_data . x86_vendor ) {
case X86_VENDOR_AMD :
2007-02-13 13:26:25 +01:00
return ( ( boot_cpu_data . x86 = = 15 ) | | ( boot_cpu_data . x86 = = 6 )
| | ( boot_cpu_data . x86 = = 16 ) ) ;
2006-09-26 10:52:26 +02:00
case X86_VENDOR_INTEL :
2006-09-26 10:52:27 +02:00
if ( cpu_has ( & boot_cpu_data , X86_FEATURE_ARCH_PERFMON ) )
return 1 ;
else
return ( ( boot_cpu_data . x86 = = 15 ) | | ( boot_cpu_data . x86 = = 6 ) ) ;
2006-09-26 10:52:26 +02:00
}
return 0 ;
}
2006-12-09 21:33:35 +01:00
static int endflag __initdata = 0 ;
2005-10-30 14:59:40 -08:00
# ifdef CONFIG_SMP
/* The performance counters used by NMI_LOCAL_APIC don't trigger when
* the CPU is idle . To make sure the NMI watchdog really ticks on all
* CPUs during the test make them busy .
*/
static __init void nmi_cpu_busy ( void * data )
{
2006-07-03 00:25:25 -07:00
local_irq_enable_in_hardirq ( ) ;
2005-10-30 14:59:40 -08:00
/* Intentionally don't use cpu_relax here. This is
to make sure that the performance counter really ticks ,
even if there is a simulator or similar that catches the
pause instruction . On a real HT machine this is fine because
all other CPUs are busy with " useless " delay loops and don ' t
care if they get somewhat less cycles . */
2006-12-09 21:33:35 +01:00
while ( endflag = = 0 )
mb ( ) ;
2005-10-30 14:59:40 -08:00
}
# endif
2007-02-13 13:26:22 +01:00
static unsigned int adjust_for_32bit_ctr ( unsigned int hz )
{
u64 counter_val ;
unsigned int retval = hz ;
/*
* On Intel CPUs with P6 / ARCH_PERFMON only 32 bits in the counter
* are writable , with higher bits sign extending from bit 31.
* So , we can only program the counter with 31 bit values and
* 32 nd bit should be 1 , for 33. . to be 1.
* Find the appropriate nmi_hz
*/
counter_val = ( u64 ) cpu_khz * 1000 ;
do_div ( counter_val , retval ) ;
if ( counter_val > 0x7fffffffULL ) {
u64 count = ( u64 ) cpu_khz * 1000 ;
do_div ( count , 0x7fffffffUL ) ;
retval = count + 1 ;
}
return retval ;
}
[PATCH] check nmi watchdog is broken
A bug against an xSeries system showed up recently noting that the
check_nmi_watchdog() test was failing.
I have been investigating it and discovered in both i386 and x86_64 the
recent change to the routine to use the cpu_callin_map has uncovered a
problem. Prior to that change, on an SMP box, the test was trivally
passing because all cpu's were found to not yet be online, but now with the
callin_map they are discovered, it goes on to test the counter and they
have not yet begun to increment, so it announces a CPU is stuck and bails
out.
On all the systems I have access to test, the announcement of failure is
also bougs... by the time you can login and check /proc/interrupts, the
NMI count is happily incrementing on all CPUs. Its just that the test is
being done too early.
I have tried moving the call to the test around a bit, and it was always
too early. I finally hit on this proposed solution, it delays the routine
via a late_initcall(), seems like the right solution to me.
Signed-off-by: Adrian Bunk <bunk@stusta.de>
Cc: Andi Kleen <ak@muc.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-05-01 08:58:48 -07:00
static int __init check_nmi_watchdog ( void )
2005-04-16 15:20:36 -07:00
{
2005-10-30 14:59:40 -08:00
unsigned int * prev_nmi_count ;
2005-04-16 15:20:36 -07:00
int cpu ;
2006-09-26 10:52:26 +02:00
if ( ( nmi_watchdog = = NMI_NONE ) | | ( nmi_watchdog = = NMI_DEFAULT ) )
return 0 ;
if ( ! atomic_read ( & nmi_active ) )
[PATCH] check nmi watchdog is broken
A bug against an xSeries system showed up recently noting that the
check_nmi_watchdog() test was failing.
I have been investigating it and discovered in both i386 and x86_64 the
recent change to the routine to use the cpu_callin_map has uncovered a
problem. Prior to that change, on an SMP box, the test was trivally
passing because all cpu's were found to not yet be online, but now with the
callin_map they are discovered, it goes on to test the counter and they
have not yet begun to increment, so it announces a CPU is stuck and bails
out.
On all the systems I have access to test, the announcement of failure is
also bougs... by the time you can login and check /proc/interrupts, the
NMI count is happily incrementing on all CPUs. Its just that the test is
being done too early.
I have tried moving the call to the test around a bit, and it was always
too early. I finally hit on this proposed solution, it delays the routine
via a late_initcall(), seems like the right solution to me.
Signed-off-by: Adrian Bunk <bunk@stusta.de>
Cc: Andi Kleen <ak@muc.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-05-01 08:58:48 -07:00
return 0 ;
2005-10-30 14:59:40 -08:00
prev_nmi_count = kmalloc ( NR_CPUS * sizeof ( int ) , GFP_KERNEL ) ;
if ( ! prev_nmi_count )
return - 1 ;
[PATCH] check nmi watchdog is broken
A bug against an xSeries system showed up recently noting that the
check_nmi_watchdog() test was failing.
I have been investigating it and discovered in both i386 and x86_64 the
recent change to the routine to use the cpu_callin_map has uncovered a
problem. Prior to that change, on an SMP box, the test was trivally
passing because all cpu's were found to not yet be online, but now with the
callin_map they are discovered, it goes on to test the counter and they
have not yet begun to increment, so it announces a CPU is stuck and bails
out.
On all the systems I have access to test, the announcement of failure is
also bougs... by the time you can login and check /proc/interrupts, the
NMI count is happily incrementing on all CPUs. Its just that the test is
being done too early.
I have tried moving the call to the test around a bit, and it was always
too early. I finally hit on this proposed solution, it delays the routine
via a late_initcall(), seems like the right solution to me.
Signed-off-by: Adrian Bunk <bunk@stusta.de>
Cc: Andi Kleen <ak@muc.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-05-01 08:58:48 -07:00
printk ( KERN_INFO " Testing NMI watchdog ... " ) ;
2005-04-16 15:20:36 -07:00
2005-10-30 14:59:40 -08:00
if ( nmi_watchdog = = NMI_LOCAL_APIC )
smp_call_function ( nmi_cpu_busy , ( void * ) & endflag , 0 , 0 ) ;
2006-03-28 01:56:39 -08:00
for_each_possible_cpu ( cpu )
2005-04-16 15:20:36 -07:00
prev_nmi_count [ cpu ] = per_cpu ( irq_stat , cpu ) . __nmi_count ;
local_irq_enable ( ) ;
mdelay ( ( 10 * 1000 ) / nmi_hz ) ; // wait 10 ticks
2006-03-28 01:56:39 -08:00
for_each_possible_cpu ( cpu ) {
2005-04-16 15:20:36 -07:00
# ifdef CONFIG_SMP
/* Check cpu_callin_map here because that is set
after the timer is started . */
if ( ! cpu_isset ( cpu , cpu_callin_map ) )
continue ;
# endif
2006-09-26 10:52:26 +02:00
if ( ! per_cpu ( nmi_watchdog_ctlblk , cpu ) . enabled )
continue ;
2005-04-16 15:20:36 -07:00
if ( nmi_count ( cpu ) - prev_nmi_count [ cpu ] < = 5 ) {
2005-10-30 14:59:40 -08:00
printk ( " CPU#%d: NMI appears to be stuck (%d->%d)! \n " ,
cpu ,
prev_nmi_count [ cpu ] ,
nmi_count ( cpu ) ) ;
2006-09-26 10:52:26 +02:00
per_cpu ( nmi_watchdog_ctlblk , cpu ) . enabled = 0 ;
atomic_dec ( & nmi_active ) ;
2005-04-16 15:20:36 -07:00
}
}
2006-09-26 10:52:26 +02:00
if ( ! atomic_read ( & nmi_active ) ) {
kfree ( prev_nmi_count ) ;
atomic_set ( & nmi_active , - 1 ) ;
return - 1 ;
}
2005-10-30 14:59:40 -08:00
endflag = 1 ;
2005-04-16 15:20:36 -07:00
printk ( " OK. \n " ) ;
/* now that we know it works we can reduce NMI frequency to
something more reasonable ; makes a difference in some configs */
2006-09-26 10:52:27 +02:00
if ( nmi_watchdog = = NMI_LOCAL_APIC ) {
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
2005-04-16 15:20:36 -07:00
nmi_hz = 1 ;
2007-02-13 13:26:22 +01:00
if ( wd - > perfctr_msr = = MSR_P6_PERFCTR0 | |
wd - > perfctr_msr = = MSR_ARCH_PERFMON_PERFCTR0 ) {
nmi_hz = adjust_for_32bit_ctr ( nmi_hz ) ;
2006-09-26 10:52:27 +02:00
}
}
2005-04-16 15:20:36 -07:00
2005-10-30 14:59:40 -08:00
kfree ( prev_nmi_count ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
[PATCH] check nmi watchdog is broken
A bug against an xSeries system showed up recently noting that the
check_nmi_watchdog() test was failing.
I have been investigating it and discovered in both i386 and x86_64 the
recent change to the routine to use the cpu_callin_map has uncovered a
problem. Prior to that change, on an SMP box, the test was trivally
passing because all cpu's were found to not yet be online, but now with the
callin_map they are discovered, it goes on to test the counter and they
have not yet begun to increment, so it announces a CPU is stuck and bails
out.
On all the systems I have access to test, the announcement of failure is
also bougs... by the time you can login and check /proc/interrupts, the
NMI count is happily incrementing on all CPUs. Its just that the test is
being done too early.
I have tried moving the call to the test around a bit, and it was always
too early. I finally hit on this proposed solution, it delays the routine
via a late_initcall(), seems like the right solution to me.
Signed-off-by: Adrian Bunk <bunk@stusta.de>
Cc: Andi Kleen <ak@muc.de>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
2005-05-01 08:58:48 -07:00
/* This needs to happen later in boot so counters are working */
late_initcall ( check_nmi_watchdog ) ;
2005-04-16 15:20:36 -07:00
static int __init setup_nmi_watchdog ( char * str )
{
int nmi ;
get_option ( & str , & nmi ) ;
2006-09-26 10:52:26 +02:00
if ( ( nmi > = NMI_INVALID ) | | ( nmi < NMI_NONE ) )
2005-04-16 15:20:36 -07:00
return 0 ;
2007-01-22 20:40:34 -08:00
2006-09-26 10:52:26 +02:00
nmi_watchdog = nmi ;
2005-04-16 15:20:36 -07:00
return 1 ;
}
__setup ( " nmi_watchdog= " , setup_nmi_watchdog ) ;
static void disable_lapic_nmi_watchdog ( void )
{
2006-09-26 10:52:26 +02:00
BUG_ON ( nmi_watchdog ! = NMI_LOCAL_APIC ) ;
if ( atomic_read ( & nmi_active ) < = 0 )
2005-04-16 15:20:36 -07:00
return ;
2006-09-26 10:52:26 +02:00
on_each_cpu ( stop_apic_nmi_watchdog , NULL , 0 , 1 ) ;
2005-04-16 15:20:36 -07:00
2006-09-26 10:52:26 +02:00
BUG_ON ( atomic_read ( & nmi_active ) ! = 0 ) ;
2005-04-16 15:20:36 -07:00
}
static void enable_lapic_nmi_watchdog ( void )
{
2006-09-26 10:52:26 +02:00
BUG_ON ( nmi_watchdog ! = NMI_LOCAL_APIC ) ;
/* are we already enabled */
if ( atomic_read ( & nmi_active ) ! = 0 )
return ;
/* are we lapic aware */
if ( nmi_known_cpu ( ) < = 0 )
return ;
on_each_cpu ( setup_apic_nmi_watchdog , NULL , 0 , 1 ) ;
touch_nmi_watchdog ( ) ;
2005-04-16 15:20:36 -07:00
}
void disable_timer_nmi_watchdog ( void )
{
2006-09-26 10:52:26 +02:00
BUG_ON ( nmi_watchdog ! = NMI_IO_APIC ) ;
if ( atomic_read ( & nmi_active ) < = 0 )
2005-04-16 15:20:36 -07:00
return ;
2006-09-26 10:52:26 +02:00
disable_irq ( 0 ) ;
on_each_cpu ( stop_apic_nmi_watchdog , NULL , 0 , 1 ) ;
BUG_ON ( atomic_read ( & nmi_active ) ! = 0 ) ;
2005-04-16 15:20:36 -07:00
}
void enable_timer_nmi_watchdog ( void )
{
2006-09-26 10:52:26 +02:00
BUG_ON ( nmi_watchdog ! = NMI_IO_APIC ) ;
if ( atomic_read ( & nmi_active ) = = 0 ) {
2005-04-16 15:20:36 -07:00
touch_nmi_watchdog ( ) ;
2006-09-26 10:52:26 +02:00
on_each_cpu ( setup_apic_nmi_watchdog , NULL , 0 , 1 ) ;
enable_irq ( 0 ) ;
2005-04-16 15:20:36 -07:00
}
}
2007-02-13 13:26:24 +01:00
static void __acpi_nmi_disable ( void * __unused )
{
apic_write_around ( APIC_LVT0 , APIC_DM_NMI | APIC_LVT_MASKED ) ;
}
/*
* Disable timer based NMIs on all CPUs :
*/
void acpi_nmi_disable ( void )
{
if ( atomic_read ( & nmi_active ) & & nmi_watchdog = = NMI_IO_APIC )
on_each_cpu ( __acpi_nmi_disable , NULL , 0 , 1 ) ;
}
static void __acpi_nmi_enable ( void * __unused )
{
apic_write_around ( APIC_LVT0 , APIC_DM_NMI ) ;
}
/*
* Enable timer based NMIs on all CPUs :
*/
void acpi_nmi_enable ( void )
{
if ( atomic_read ( & nmi_active ) & & nmi_watchdog = = NMI_IO_APIC )
on_each_cpu ( __acpi_nmi_enable , NULL , 0 , 1 ) ;
}
2005-04-16 15:20:36 -07:00
# ifdef CONFIG_PM
static int nmi_pm_active ; /* nmi_active before suspend */
2005-04-16 15:25:24 -07:00
static int lapic_nmi_suspend ( struct sys_device * dev , pm_message_t state )
2005-04-16 15:20:36 -07:00
{
2006-09-26 10:52:27 +02:00
/* only CPU0 goes here, other CPUs should be offline */
2006-09-26 10:52:26 +02:00
nmi_pm_active = atomic_read ( & nmi_active ) ;
2006-09-26 10:52:27 +02:00
stop_apic_nmi_watchdog ( NULL ) ;
BUG_ON ( atomic_read ( & nmi_active ) ! = 0 ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
static int lapic_nmi_resume ( struct sys_device * dev )
{
2006-09-26 10:52:27 +02:00
/* only CPU0 goes here, other CPUs should be offline */
if ( nmi_pm_active > 0 ) {
setup_apic_nmi_watchdog ( NULL ) ;
touch_nmi_watchdog ( ) ;
}
2005-04-16 15:20:36 -07:00
return 0 ;
}
static struct sysdev_class nmi_sysclass = {
set_kset_name ( " lapic_nmi " ) ,
. resume = lapic_nmi_resume ,
. suspend = lapic_nmi_suspend ,
} ;
static struct sys_device device_lapic_nmi = {
. id = 0 ,
. cls = & nmi_sysclass ,
} ;
static int __init init_lapic_nmi_sysfs ( void )
{
int error ;
2006-09-26 10:52:26 +02:00
/* should really be a BUG_ON but b/c this is an
* init call , it just doesn ' t work . - dcz
*/
if ( nmi_watchdog ! = NMI_LOCAL_APIC )
return 0 ;
if ( atomic_read ( & nmi_active ) < 0 )
2005-04-16 15:20:36 -07:00
return 0 ;
error = sysdev_class_register ( & nmi_sysclass ) ;
if ( ! error )
error = sysdev_register ( & device_lapic_nmi ) ;
return error ;
}
/* must come after the local APIC's device_initcall() */
late_initcall ( init_lapic_nmi_sysfs ) ;
# endif /* CONFIG_PM */
/*
* Activate the NMI watchdog via the local APIC .
* Original code written by Keith Owens .
*/
2006-09-26 10:52:26 +02:00
static void write_watchdog_counter ( unsigned int perfctr_msr , const char * descr )
2005-06-23 00:08:23 -07:00
{
u64 count = ( u64 ) cpu_khz * 1000 ;
do_div ( count , nmi_hz ) ;
if ( descr )
Dprintk ( " setting %s to -0x%08Lx \n " , descr , count ) ;
2006-09-26 10:52:26 +02:00
wrmsrl ( perfctr_msr , 0 - count ) ;
2005-06-23 00:08:23 -07:00
}
2007-02-13 13:26:22 +01:00
static void write_watchdog_counter32 ( unsigned int perfctr_msr ,
const char * descr )
{
u64 count = ( u64 ) cpu_khz * 1000 ;
do_div ( count , nmi_hz ) ;
if ( descr )
Dprintk ( " setting %s to -0x%08Lx \n " , descr , count ) ;
wrmsr ( perfctr_msr , ( u32 ) ( - count ) , 0 ) ;
}
2006-09-26 10:52:26 +02:00
/* Note that these events don't tick when the CPU idles. This means
the frequency varies with CPU load . */
# define K7_EVNTSEL_ENABLE (1 << 22)
# define K7_EVNTSEL_INT (1 << 20)
# define K7_EVNTSEL_OS (1 << 17)
# define K7_EVNTSEL_USR (1 << 16)
# define K7_EVENT_CYCLES_PROCESSOR_IS_RUNNING 0x76
# define K7_NMI_EVENT K7_EVENT_CYCLES_PROCESSOR_IS_RUNNING
2006-09-26 10:52:26 +02:00
static int setup_k7_watchdog ( void )
2005-04-16 15:20:36 -07:00
{
2006-09-26 10:52:26 +02:00
unsigned int perfctr_msr , evntsel_msr ;
2005-04-16 15:20:36 -07:00
unsigned int evntsel ;
2006-09-26 10:52:26 +02:00
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
2005-04-16 15:20:36 -07:00
2006-09-26 10:52:26 +02:00
perfctr_msr = MSR_K7_PERFCTR0 ;
evntsel_msr = MSR_K7_EVNTSEL0 ;
2007-04-02 12:14:12 +02:00
if ( ! __reserve_perfctr_nmi ( - 1 , perfctr_msr ) )
2006-09-26 10:52:26 +02:00
goto fail ;
2007-04-02 12:14:12 +02:00
if ( ! __reserve_evntsel_nmi ( - 1 , evntsel_msr ) )
2006-09-26 10:52:26 +02:00
goto fail1 ;
2006-09-26 10:52:26 +02:00
wrmsrl ( perfctr_msr , 0UL ) ;
2005-04-16 15:20:36 -07:00
evntsel = K7_EVNTSEL_INT
| K7_EVNTSEL_OS
| K7_EVNTSEL_USR
| K7_NMI_EVENT ;
2006-09-26 10:52:26 +02:00
/* setup the timer */
wrmsr ( evntsel_msr , evntsel , 0 ) ;
write_watchdog_counter ( perfctr_msr , " K7_PERFCTR0 " ) ;
2005-04-16 15:20:36 -07:00
apic_write ( APIC_LVTPC , APIC_DM_NMI ) ;
evntsel | = K7_EVNTSEL_ENABLE ;
2006-09-26 10:52:26 +02:00
wrmsr ( evntsel_msr , evntsel , 0 ) ;
wd - > perfctr_msr = perfctr_msr ;
wd - > evntsel_msr = evntsel_msr ;
wd - > cccr_msr = 0 ; //unused
wd - > check_bit = 1ULL < < 63 ;
2006-09-26 10:52:26 +02:00
return 1 ;
fail1 :
2007-04-02 12:14:12 +02:00
__release_perfctr_nmi ( - 1 , perfctr_msr ) ;
2006-09-26 10:52:26 +02:00
fail :
return 0 ;
2005-04-16 15:20:36 -07:00
}
2006-09-26 10:52:26 +02:00
static void stop_k7_watchdog ( void )
{
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
wrmsr ( wd - > evntsel_msr , 0 , 0 ) ;
2007-04-02 12:14:12 +02:00
__release_evntsel_nmi ( - 1 , wd - > evntsel_msr ) ;
__release_perfctr_nmi ( - 1 , wd - > perfctr_msr ) ;
2006-09-26 10:52:26 +02:00
}
# define P6_EVNTSEL0_ENABLE (1 << 22)
# define P6_EVNTSEL_INT (1 << 20)
# define P6_EVNTSEL_OS (1 << 17)
# define P6_EVNTSEL_USR (1 << 16)
# define P6_EVENT_CPU_CLOCKS_NOT_HALTED 0x79
# define P6_NMI_EVENT P6_EVENT_CPU_CLOCKS_NOT_HALTED
2006-09-26 10:52:26 +02:00
static int setup_p6_watchdog ( void )
2005-04-16 15:20:36 -07:00
{
2006-09-26 10:52:26 +02:00
unsigned int perfctr_msr , evntsel_msr ;
2005-04-16 15:20:36 -07:00
unsigned int evntsel ;
2006-09-26 10:52:26 +02:00
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
2005-04-16 15:20:36 -07:00
2006-09-26 10:52:26 +02:00
perfctr_msr = MSR_P6_PERFCTR0 ;
evntsel_msr = MSR_P6_EVNTSEL0 ;
2007-04-02 12:14:12 +02:00
if ( ! __reserve_perfctr_nmi ( - 1 , perfctr_msr ) )
2006-09-26 10:52:26 +02:00
goto fail ;
2007-04-02 12:14:12 +02:00
if ( ! __reserve_evntsel_nmi ( - 1 , evntsel_msr ) )
2006-09-26 10:52:26 +02:00
goto fail1 ;
2005-04-16 15:20:36 -07:00
2006-09-26 10:52:26 +02:00
wrmsrl ( perfctr_msr , 0UL ) ;
2005-04-16 15:20:36 -07:00
evntsel = P6_EVNTSEL_INT
| P6_EVNTSEL_OS
| P6_EVNTSEL_USR
| P6_NMI_EVENT ;
2006-09-26 10:52:26 +02:00
/* setup the timer */
wrmsr ( evntsel_msr , evntsel , 0 ) ;
2007-02-13 13:26:22 +01:00
nmi_hz = adjust_for_32bit_ctr ( nmi_hz ) ;
write_watchdog_counter32 ( perfctr_msr , " P6_PERFCTR0 " ) ;
2005-04-16 15:20:36 -07:00
apic_write ( APIC_LVTPC , APIC_DM_NMI ) ;
evntsel | = P6_EVNTSEL0_ENABLE ;
2006-09-26 10:52:26 +02:00
wrmsr ( evntsel_msr , evntsel , 0 ) ;
wd - > perfctr_msr = perfctr_msr ;
wd - > evntsel_msr = evntsel_msr ;
wd - > cccr_msr = 0 ; //unused
wd - > check_bit = 1ULL < < 39 ;
2006-09-26 10:52:26 +02:00
return 1 ;
fail1 :
2007-04-02 12:14:12 +02:00
__release_perfctr_nmi ( - 1 , perfctr_msr ) ;
2006-09-26 10:52:26 +02:00
fail :
return 0 ;
2005-04-16 15:20:36 -07:00
}
2006-09-26 10:52:26 +02:00
static void stop_p6_watchdog ( void )
{
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
wrmsr ( wd - > evntsel_msr , 0 , 0 ) ;
2007-04-02 12:14:12 +02:00
__release_evntsel_nmi ( - 1 , wd - > evntsel_msr ) ;
__release_perfctr_nmi ( - 1 , wd - > perfctr_msr ) ;
2006-09-26 10:52:26 +02:00
}
/* Note that these events don't tick when the CPU idles. This means
the frequency varies with CPU load . */
# define MSR_P4_MISC_ENABLE_PERF_AVAIL (1<<7)
# define P4_ESCR_EVENT_SELECT(N) ((N)<<25)
# define P4_ESCR_OS (1<<3)
# define P4_ESCR_USR (1<<2)
# define P4_CCCR_OVF_PMI0 (1<<26)
# define P4_CCCR_OVF_PMI1 (1<<27)
# define P4_CCCR_THRESHOLD(N) ((N)<<20)
# define P4_CCCR_COMPLEMENT (1<<19)
# define P4_CCCR_COMPARE (1<<18)
# define P4_CCCR_REQUIRED (3<<16)
# define P4_CCCR_ESCR_SELECT(N) ((N)<<13)
# define P4_CCCR_ENABLE (1<<12)
# define P4_CCCR_OVF (1<<31)
/* Set up IQ_COUNTER0 to behave like a clock, by having IQ_CCCR0 filter
CRU_ESCR0 ( with any non - null event selector ) through a complemented
max threshold . [ IA32 - Vol3 , Section 14.9 .9 ] */
2005-04-16 15:20:36 -07:00
static int setup_p4_watchdog ( void )
{
2006-09-26 10:52:26 +02:00
unsigned int perfctr_msr , evntsel_msr , cccr_msr ;
unsigned int evntsel , cccr_val ;
2005-04-16 15:20:36 -07:00
unsigned int misc_enable , dummy ;
2006-09-26 10:52:26 +02:00
unsigned int ht_num ;
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
2005-04-16 15:20:36 -07:00
2006-09-26 10:52:26 +02:00
rdmsr ( MSR_IA32_MISC_ENABLE , misc_enable , dummy ) ;
2005-04-16 15:20:36 -07:00
if ( ! ( misc_enable & MSR_P4_MISC_ENABLE_PERF_AVAIL ) )
return 0 ;
# ifdef CONFIG_SMP
2006-09-26 10:52:26 +02:00
/* detect which hyperthread we are on */
if ( smp_num_siblings = = 2 ) {
unsigned int ebx , apicid ;
ebx = cpuid_ebx ( 1 ) ;
apicid = ( ebx > > 24 ) & 0xff ;
ht_num = apicid & 1 ;
} else
2005-04-16 15:20:36 -07:00
# endif
2006-09-26 10:52:26 +02:00
ht_num = 0 ;
/* performance counters are shared resources
* assign each hyperthread its own set
* ( re - use the ESCR0 register , seems safe
* and keeps the cccr_val the same )
*/
if ( ! ht_num ) {
/* logical cpu 0 */
perfctr_msr = MSR_P4_IQ_PERFCTR0 ;
evntsel_msr = MSR_P4_CRU_ESCR0 ;
cccr_msr = MSR_P4_IQ_CCCR0 ;
cccr_val = P4_CCCR_OVF_PMI0 | P4_CCCR_ESCR_SELECT ( 4 ) ;
} else {
/* logical cpu 1 */
perfctr_msr = MSR_P4_IQ_PERFCTR1 ;
evntsel_msr = MSR_P4_CRU_ESCR0 ;
cccr_msr = MSR_P4_IQ_CCCR1 ;
cccr_val = P4_CCCR_OVF_PMI1 | P4_CCCR_ESCR_SELECT ( 4 ) ;
}
2005-04-16 15:20:36 -07:00
2007-04-02 12:14:12 +02:00
if ( ! __reserve_perfctr_nmi ( - 1 , perfctr_msr ) )
2006-09-26 10:52:26 +02:00
goto fail ;
2007-04-02 12:14:12 +02:00
if ( ! __reserve_evntsel_nmi ( - 1 , evntsel_msr ) )
2006-09-26 10:52:26 +02:00
goto fail1 ;
2005-04-16 15:20:36 -07:00
2006-09-26 10:52:26 +02:00
evntsel = P4_ESCR_EVENT_SELECT ( 0x3F )
| P4_ESCR_OS
| P4_ESCR_USR ;
cccr_val | = P4_CCCR_THRESHOLD ( 15 )
| P4_CCCR_COMPLEMENT
| P4_CCCR_COMPARE
| P4_CCCR_REQUIRED ;
wrmsr ( evntsel_msr , evntsel , 0 ) ;
wrmsr ( cccr_msr , cccr_val , 0 ) ;
write_watchdog_counter ( perfctr_msr , " P4_IQ_COUNTER0 " ) ;
2005-04-16 15:20:36 -07:00
apic_write ( APIC_LVTPC , APIC_DM_NMI ) ;
2006-09-26 10:52:26 +02:00
cccr_val | = P4_CCCR_ENABLE ;
wrmsr ( cccr_msr , cccr_val , 0 ) ;
wd - > perfctr_msr = perfctr_msr ;
wd - > evntsel_msr = evntsel_msr ;
wd - > cccr_msr = cccr_msr ;
wd - > check_bit = 1ULL < < 39 ;
2005-04-16 15:20:36 -07:00
return 1 ;
2006-09-26 10:52:26 +02:00
fail1 :
2007-04-02 12:14:12 +02:00
__release_perfctr_nmi ( - 1 , perfctr_msr ) ;
2006-09-26 10:52:26 +02:00
fail :
return 0 ;
2005-04-16 15:20:36 -07:00
}
2006-09-26 10:52:26 +02:00
static void stop_p4_watchdog ( void )
2005-04-16 15:20:36 -07:00
{
2006-09-26 10:52:26 +02:00
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
wrmsr ( wd - > cccr_msr , 0 , 0 ) ;
wrmsr ( wd - > evntsel_msr , 0 , 0 ) ;
2005-04-16 15:20:36 -07:00
2007-04-02 12:14:12 +02:00
__release_evntsel_nmi ( - 1 , wd - > evntsel_msr ) ;
__release_perfctr_nmi ( - 1 , wd - > perfctr_msr ) ;
2006-09-26 10:52:26 +02:00
}
2006-09-26 10:52:27 +02:00
# define ARCH_PERFMON_NMI_EVENT_SEL ARCH_PERFMON_UNHALTED_CORE_CYCLES_SEL
# define ARCH_PERFMON_NMI_EVENT_UMASK ARCH_PERFMON_UNHALTED_CORE_CYCLES_UMASK
static int setup_intel_arch_watchdog ( void )
{
unsigned int ebx ;
union cpuid10_eax eax ;
unsigned int unused ;
unsigned int perfctr_msr , evntsel_msr ;
unsigned int evntsel ;
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
/*
* Check whether the Architectural PerfMon supports
* Unhalted Core Cycles Event or not .
* NOTE : Corresponding bit = 0 in ebx indicates event present .
*/
cpuid ( 10 , & ( eax . full ) , & ebx , & unused , & unused ) ;
if ( ( eax . split . mask_length < ( ARCH_PERFMON_UNHALTED_CORE_CYCLES_INDEX + 1 ) ) | |
( ebx & ARCH_PERFMON_UNHALTED_CORE_CYCLES_PRESENT ) )
goto fail ;
perfctr_msr = MSR_ARCH_PERFMON_PERFCTR0 ;
evntsel_msr = MSR_ARCH_PERFMON_EVENTSEL0 ;
2007-04-02 12:14:12 +02:00
if ( ! __reserve_perfctr_nmi ( - 1 , perfctr_msr ) )
2006-09-26 10:52:27 +02:00
goto fail ;
2007-04-02 12:14:12 +02:00
if ( ! __reserve_evntsel_nmi ( - 1 , evntsel_msr ) )
2006-09-26 10:52:27 +02:00
goto fail1 ;
wrmsrl ( perfctr_msr , 0UL ) ;
evntsel = ARCH_PERFMON_EVENTSEL_INT
| ARCH_PERFMON_EVENTSEL_OS
| ARCH_PERFMON_EVENTSEL_USR
| ARCH_PERFMON_NMI_EVENT_SEL
| ARCH_PERFMON_NMI_EVENT_UMASK ;
/* setup the timer */
wrmsr ( evntsel_msr , evntsel , 0 ) ;
2007-02-13 13:26:22 +01:00
nmi_hz = adjust_for_32bit_ctr ( nmi_hz ) ;
write_watchdog_counter32 ( perfctr_msr , " INTEL_ARCH_PERFCTR0 " ) ;
2006-09-26 10:52:27 +02:00
apic_write ( APIC_LVTPC , APIC_DM_NMI ) ;
evntsel | = ARCH_PERFMON_EVENTSEL0_ENABLE ;
wrmsr ( evntsel_msr , evntsel , 0 ) ;
wd - > perfctr_msr = perfctr_msr ;
wd - > evntsel_msr = evntsel_msr ;
wd - > cccr_msr = 0 ; //unused
wd - > check_bit = 1ULL < < ( eax . split . bit_width - 1 ) ;
return 1 ;
fail1 :
2007-04-02 12:14:12 +02:00
__release_perfctr_nmi ( - 1 , perfctr_msr ) ;
2006-09-26 10:52:27 +02:00
fail :
return 0 ;
}
static void stop_intel_arch_watchdog ( void )
{
unsigned int ebx ;
union cpuid10_eax eax ;
unsigned int unused ;
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
/*
* Check whether the Architectural PerfMon supports
* Unhalted Core Cycles Event or not .
* NOTE : Corresponding bit = 0 in ebx indicates event present .
*/
cpuid ( 10 , & ( eax . full ) , & ebx , & unused , & unused ) ;
if ( ( eax . split . mask_length < ( ARCH_PERFMON_UNHALTED_CORE_CYCLES_INDEX + 1 ) ) | |
( ebx & ARCH_PERFMON_UNHALTED_CORE_CYCLES_PRESENT ) )
return ;
wrmsr ( wd - > evntsel_msr , 0 , 0 ) ;
2007-04-02 12:14:12 +02:00
__release_evntsel_nmi ( - 1 , wd - > evntsel_msr ) ;
__release_perfctr_nmi ( - 1 , wd - > perfctr_msr ) ;
2006-09-26 10:52:27 +02:00
}
2006-09-26 10:52:26 +02:00
void setup_apic_nmi_watchdog ( void * unused )
{
2006-09-26 10:52:27 +02:00
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
2006-09-26 10:52:26 +02:00
/* only support LOCAL and IO APICs for now */
if ( ( nmi_watchdog ! = NMI_LOCAL_APIC ) & &
( nmi_watchdog ! = NMI_IO_APIC ) )
return ;
2006-09-26 10:52:27 +02:00
if ( wd - > enabled = = 1 )
return ;
/* cheap hack to support suspend/resume */
/* if cpu0 is not active neither should the other cpus */
if ( ( smp_processor_id ( ) ! = 0 ) & & ( atomic_read ( & nmi_active ) < = 0 ) )
return ;
2006-09-26 10:52:26 +02:00
if ( nmi_watchdog = = NMI_LOCAL_APIC ) {
switch ( boot_cpu_data . x86_vendor ) {
case X86_VENDOR_AMD :
2007-02-13 13:26:25 +01:00
if ( boot_cpu_data . x86 ! = 6 & & boot_cpu_data . x86 ! = 15 & &
boot_cpu_data . x86 ! = 16 )
2006-09-26 10:52:26 +02:00
return ;
2006-09-26 10:52:26 +02:00
if ( ! setup_k7_watchdog ( ) )
2005-04-16 15:20:36 -07:00
return ;
2006-09-26 10:52:26 +02:00
break ;
case X86_VENDOR_INTEL :
2006-09-26 10:52:27 +02:00
if ( cpu_has ( & boot_cpu_data , X86_FEATURE_ARCH_PERFMON ) ) {
if ( ! setup_intel_arch_watchdog ( ) )
return ;
break ;
}
2006-09-26 10:52:26 +02:00
switch ( boot_cpu_data . x86 ) {
case 6 :
if ( boot_cpu_data . x86_model > 0xd )
return ;
if ( ! setup_p6_watchdog ( ) )
return ;
break ;
case 15 :
if ( boot_cpu_data . x86_model > 0x4 )
return ;
2005-04-16 15:20:36 -07:00
2006-09-26 10:52:26 +02:00
if ( ! setup_p4_watchdog ( ) )
return ;
break ;
default :
2005-04-16 15:20:36 -07:00
return ;
2006-09-26 10:52:26 +02:00
}
break ;
default :
return ;
}
}
2006-09-26 10:52:27 +02:00
wd - > enabled = 1 ;
2006-09-26 10:52:26 +02:00
atomic_inc ( & nmi_active ) ;
}
2006-09-26 10:52:27 +02:00
void stop_apic_nmi_watchdog ( void * unused )
2006-09-26 10:52:26 +02:00
{
2006-09-26 10:52:27 +02:00
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
2006-09-26 10:52:26 +02:00
/* only support LOCAL and IO APICs for now */
if ( ( nmi_watchdog ! = NMI_LOCAL_APIC ) & &
( nmi_watchdog ! = NMI_IO_APIC ) )
return ;
2006-09-26 10:52:27 +02:00
if ( wd - > enabled = = 0 )
return ;
2006-09-26 10:52:26 +02:00
if ( nmi_watchdog = = NMI_LOCAL_APIC ) {
switch ( boot_cpu_data . x86_vendor ) {
case X86_VENDOR_AMD :
stop_k7_watchdog ( ) ;
break ;
case X86_VENDOR_INTEL :
2006-09-26 10:52:27 +02:00
if ( cpu_has ( & boot_cpu_data , X86_FEATURE_ARCH_PERFMON ) ) {
stop_intel_arch_watchdog ( ) ;
break ;
}
2006-09-26 10:52:26 +02:00
switch ( boot_cpu_data . x86 ) {
case 6 :
if ( boot_cpu_data . x86_model > 0xd )
break ;
stop_p6_watchdog ( ) ;
break ;
case 15 :
if ( boot_cpu_data . x86_model > 0x4 )
break ;
stop_p4_watchdog ( ) ;
break ;
}
2005-04-16 15:20:36 -07:00
break ;
default :
return ;
}
}
2006-09-26 10:52:27 +02:00
wd - > enabled = 0 ;
2006-09-26 10:52:26 +02:00
atomic_dec ( & nmi_active ) ;
2005-04-16 15:20:36 -07:00
}
/*
* the best way to detect whether a CPU has a ' hard lockup ' problem
* is to check it ' s local APIC timer IRQ counts . If they are not
* changing then that CPU has some problem .
*
* as these watchdog NMI IRQs are generated on every CPU , we only
* have to check the current processor .
*
* since NMIs don ' t listen to _any_ locks , we have to be extremely
* careful not to rely on unsafe variables . The printk might lock
* up though , so we have to break up any console locks first . . .
* [ when there will be more tty - related locks , break them up
* here too ! ]
*/
static unsigned int
last_irq_sums [ NR_CPUS ] ,
alert_counter [ NR_CPUS ] ;
void touch_nmi_watchdog ( void )
{
2006-12-07 02:14:09 +01:00
if ( nmi_watchdog > 0 ) {
unsigned cpu ;
2005-04-16 15:20:36 -07:00
2006-12-07 02:14:09 +01:00
/*
* Just reset the alert counters , ( other CPUs might be
* spinning on locks we hold ) :
*/
for_each_present_cpu ( cpu )
alert_counter [ cpu ] = 0 ;
}
2005-09-06 15:16:27 -07:00
/*
* Tickle the softlockup detector too :
*/
touch_softlockup_watchdog ( ) ;
2005-04-16 15:20:36 -07:00
}
2006-07-30 03:03:29 -07:00
EXPORT_SYMBOL ( touch_nmi_watchdog ) ;
2005-04-16 15:20:36 -07:00
extern void die_nmi ( struct pt_regs * , const char * msg ) ;
2006-09-26 10:52:36 +02:00
__kprobes int nmi_watchdog_tick ( struct pt_regs * regs , unsigned reason )
2005-04-16 15:20:36 -07:00
{
/*
* Since current_thread_info ( ) - > is always on the stack , and we
* always switch the stack NMI - atomically , it ' s safe to use
* smp_processor_id ( ) .
*/
2006-03-28 01:56:52 -08:00
unsigned int sum ;
2006-09-26 10:52:26 +02:00
int touched = 0 ;
2006-03-28 01:56:52 -08:00
int cpu = smp_processor_id ( ) ;
2006-09-26 10:52:26 +02:00
struct nmi_watchdog_ctlblk * wd = & __get_cpu_var ( nmi_watchdog_ctlblk ) ;
u64 dummy ;
2006-09-26 10:52:26 +02:00
int rc = 0 ;
2006-09-26 10:52:26 +02:00
/* check for other users first */
if ( notify_die ( DIE_NMI , " nmi " , regs , reason , 2 , SIGINT )
= = NOTIFY_STOP ) {
2006-09-26 10:52:26 +02:00
rc = 1 ;
2006-09-26 10:52:26 +02:00
touched = 1 ;
}
2005-04-16 15:20:36 -07:00
2006-12-07 02:14:01 +01:00
if ( cpu_isset ( cpu , backtrace_mask ) ) {
static DEFINE_SPINLOCK ( lock ) ; /* Serialise the printks */
spin_lock ( & lock ) ;
printk ( " NMI backtrace for cpu %d \n " , cpu ) ;
dump_stack ( ) ;
spin_unlock ( & lock ) ;
cpu_clear ( cpu , backtrace_mask ) ;
}
2007-02-16 01:28:09 -08:00
/*
* Take the local apic timer and PIT / HPET into account . We don ' t
* know which one is active , when we have highres / dyntick on
*/
sum = per_cpu ( irq_stat , cpu ) . apic_timer_irqs + kstat_irqs ( 0 ) ;
2005-04-16 15:20:36 -07:00
2007-02-16 01:28:09 -08:00
/* if the none of the timers isn't firing, this cpu isn't doing much */
2006-09-26 10:52:26 +02:00
if ( ! touched & & last_irq_sums [ cpu ] = = sum ) {
2005-04-16 15:20:36 -07:00
/*
* Ayiee , looks like this CPU is stuck . . .
* wait a few IRQs ( 5 seconds ) before doing the oops . . .
*/
alert_counter [ cpu ] + + ;
if ( alert_counter [ cpu ] = = 5 * nmi_hz )
2005-09-03 15:56:48 -07:00
/*
* die_nmi will return ONLY if NOTIFY_STOP happens . .
*/
2006-03-23 03:00:54 -08:00
die_nmi ( regs , " BUG: NMI Watchdog detected LOCKUP " ) ;
2006-03-07 21:55:29 -08:00
} else {
2005-04-16 15:20:36 -07:00
last_irq_sums [ cpu ] = sum ;
alert_counter [ cpu ] = 0 ;
}
2006-09-26 10:52:26 +02:00
/* see if the nmi watchdog went off */
if ( wd - > enabled ) {
if ( nmi_watchdog = = NMI_LOCAL_APIC ) {
rdmsrl ( wd - > perfctr_msr , dummy ) ;
if ( dummy & wd - > check_bit ) {
/* this wasn't a watchdog timer interrupt */
goto done ;
}
/* only Intel P4 uses the cccr msr */
if ( wd - > cccr_msr ! = 0 ) {
/*
* P4 quirks :
* - An overflown perfctr will assert its interrupt
* until the OVF flag in its CCCR is cleared .
* - LVTPC is masked on interrupt and must be
* unmasked by the LVTPC handler .
*/
rdmsrl ( wd - > cccr_msr , dummy ) ;
dummy & = ~ P4_CCCR_OVF ;
wrmsrl ( wd - > cccr_msr , dummy ) ;
apic_write ( APIC_LVTPC , APIC_DM_NMI ) ;
2007-02-13 13:26:22 +01:00
/* start the cycle over again */
write_watchdog_counter ( wd - > perfctr_msr , NULL ) ;
2006-09-26 10:52:26 +02:00
}
2006-09-26 10:52:27 +02:00
else if ( wd - > perfctr_msr = = MSR_P6_PERFCTR0 | |
wd - > perfctr_msr = = MSR_ARCH_PERFMON_PERFCTR0 ) {
/* P6 based Pentium M need to re-unmask
2006-09-26 10:52:26 +02:00
* the apic vector but it doesn ' t hurt
2006-09-26 10:52:27 +02:00
* other P6 variant .
* ArchPerfom / Core Duo also needs this */
2006-09-26 10:52:26 +02:00
apic_write ( APIC_LVTPC , APIC_DM_NMI ) ;
2007-02-13 13:26:22 +01:00
/* P6/ARCH_PERFMON has 32 bit counter write */
write_watchdog_counter32 ( wd - > perfctr_msr , NULL ) ;
} else {
/* start the cycle over again */
write_watchdog_counter ( wd - > perfctr_msr , NULL ) ;
2006-09-26 10:52:26 +02:00
}
2006-09-26 10:52:26 +02:00
rc = 1 ;
} else if ( nmi_watchdog = = NMI_IO_APIC ) {
/* don't know how to accurately check for this.
* just assume it was a watchdog timer interrupt
* This matches the old behaviour .
*/
rc = 1 ;
2006-09-26 10:52:36 +02:00
}
2005-04-16 15:20:36 -07:00
}
2006-09-26 10:52:26 +02:00
done :
2006-09-26 10:52:26 +02:00
return rc ;
2005-04-16 15:20:36 -07:00
}
2006-09-26 10:52:27 +02:00
int do_nmi_callback ( struct pt_regs * regs , int cpu )
{
# ifdef CONFIG_SYSCTL
if ( unknown_nmi_panic )
return unknown_nmi_panic_callback ( regs , cpu ) ;
# endif
return 0 ;
}
2005-04-16 15:20:36 -07:00
# ifdef CONFIG_SYSCTL
static int unknown_nmi_panic_callback ( struct pt_regs * regs , int cpu )
{
unsigned char reason = get_nmi_reason ( ) ;
char buf [ 64 ] ;
2006-09-26 10:52:27 +02:00
sprintf ( buf , " NMI received for unknown reason %02x \n " , reason ) ;
die_nmi ( regs , buf ) ;
2005-04-16 15:20:36 -07:00
return 0 ;
}
2006-09-26 10:52:27 +02:00
/*
2006-09-26 10:52:27 +02:00
* proc handler for / proc / sys / kernel / nmi
2006-09-26 10:52:27 +02:00
*/
int proc_nmi_enabled ( struct ctl_table * table , int write , struct file * file ,
void __user * buffer , size_t * length , loff_t * ppos )
{
int old_state ;
nmi_watchdog_enabled = ( atomic_read ( & nmi_active ) > 0 ) ? 1 : 0 ;
old_state = nmi_watchdog_enabled ;
proc_dointvec ( table , write , file , buffer , length , ppos ) ;
if ( ! ! old_state = = ! ! nmi_watchdog_enabled )
return 0 ;
if ( atomic_read ( & nmi_active ) < 0 ) {
2006-09-26 10:52:27 +02:00
printk ( KERN_WARNING " NMI watchdog is permanently disabled \n " ) ;
return - EIO ;
2006-09-26 10:52:27 +02:00
}
if ( nmi_watchdog = = NMI_DEFAULT ) {
if ( nmi_known_cpu ( ) > 0 )
nmi_watchdog = NMI_LOCAL_APIC ;
else
nmi_watchdog = NMI_IO_APIC ;
}
2006-09-26 10:52:27 +02:00
if ( nmi_watchdog = = NMI_LOCAL_APIC ) {
2006-09-26 10:52:27 +02:00
if ( nmi_watchdog_enabled )
enable_lapic_nmi_watchdog ( ) ;
else
disable_lapic_nmi_watchdog ( ) ;
} else {
printk ( KERN_WARNING
" NMI watchdog doesn't know what hardware to touch \n " ) ;
return - EIO ;
}
return 0 ;
}
2005-04-16 15:20:36 -07:00
# endif
2006-12-07 02:14:01 +01:00
void __trigger_all_cpu_backtrace ( void )
{
int i ;
backtrace_mask = cpu_online_map ;
/* Wait for up to 10 seconds for all CPUs to do the backtrace */
for ( i = 0 ; i < 10 * 1000 ; i + + ) {
if ( cpus_empty ( backtrace_mask ) )
break ;
mdelay ( 1 ) ;
}
}
2005-04-16 15:20:36 -07:00
EXPORT_SYMBOL ( nmi_active ) ;
EXPORT_SYMBOL ( nmi_watchdog ) ;
2006-09-26 10:52:26 +02:00
EXPORT_SYMBOL ( avail_to_resrv_perfctr_nmi ) ;
EXPORT_SYMBOL ( avail_to_resrv_perfctr_nmi_bit ) ;
EXPORT_SYMBOL ( reserve_perfctr_nmi ) ;
EXPORT_SYMBOL ( release_perfctr_nmi ) ;
EXPORT_SYMBOL ( reserve_evntsel_nmi ) ;
EXPORT_SYMBOL ( release_evntsel_nmi ) ;
2005-04-16 15:20:36 -07:00
EXPORT_SYMBOL ( disable_timer_nmi_watchdog ) ;
EXPORT_SYMBOL ( enable_timer_nmi_watchdog ) ;