2010-07-24 21:16:05 +04:00
/*
* Octeon Watchdog driver
*
* Copyright ( C ) 2007 , 2008 , 2009 , 2010 Cavium Networks
*
* Some parts derived from wdt . c
*
* ( c ) Copyright 1996 - 1997 Alan Cox < alan @ lxorguk . ukuu . org . uk > ,
* All Rights Reserved .
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation ; either version
* 2 of the License , or ( at your option ) any later version .
*
* Neither Alan Cox nor CymruNet Ltd . admit liability nor provide
* warranty for any of this software . This material is provided
* " AS-IS " and at no charge .
*
* ( c ) Copyright 1995 Alan Cox < alan @ lxorguk . ukuu . org . uk >
*
* This file is subject to the terms and conditions of the GNU General Public
* License . See the file " COPYING " in the main directory of this archive
* for more details .
*
*
* The OCTEON watchdog has a maximum timeout of 2 ^ 32 * io_clock .
* For most systems this is less than 10 seconds , so to allow for
* software to request longer watchdog heartbeats , we maintain software
* counters to count multiples of the base rate . If the system locks
* up in such a manner that we can not run the software counters , the
* only result is a watchdog reset sooner than was requested . But
* that is OK , because in this case userspace would likely not be able
* to do anything anyhow .
*
* The hardware watchdog interval we call the period . The OCTEON
* watchdog goes through several stages , after the first period an
* irq is asserted , then if it is not reset , after the next period NMI
* is asserted , then after an additional period a chip wide soft reset .
* So for the software counters , we reset watchdog after each period
* and decrement the counter . But for the last two periods we need to
* let the watchdog progress to the NMI stage so we disable the irq
* and let it proceed . Once in the NMI , we print the register state
* to the serial port and then wait for the reset .
*
* A watchdog is maintained for each CPU in the system , that way if
* one CPU suffers a lockup , we also get a register dump and reset .
* The userspace ping resets the watchdog on all CPUs .
*
* Before userspace opens the watchdog device , we still run the
* watchdogs to catch any lockups that may be kernel related .
*
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2010-07-24 21:16:05 +04:00
# include <linux/miscdevice.h>
# include <linux/interrupt.h>
# include <linux/watchdog.h>
# include <linux/cpumask.h>
# include <linux/bitops.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/string.h>
# include <linux/delay.h>
# include <linux/cpu.h>
# include <linux/smp.h>
# include <linux/fs.h>
2010-10-07 17:08:54 +04:00
# include <linux/irq.h>
2010-07-24 21:16:05 +04:00
# include <asm/mipsregs.h>
# include <asm/uasm.h>
# include <asm/octeon/octeon.h>
/* The count needed to achieve timeout_sec. */
static unsigned int timeout_cnt ;
/* The maximum period supported. */
static unsigned int max_timeout_sec ;
/* The current period. */
static unsigned int timeout_sec ;
/* Set to non-zero when userspace countdown mode active */
static int do_coundown ;
static unsigned int countdown_reset ;
static unsigned int per_cpu_countdown [ NR_CPUS ] ;
static cpumask_t irq_enabled_cpus ;
# define WD_TIMO 60 /* Default heartbeat = 60 seconds */
static int heartbeat = WD_TIMO ;
module_param ( heartbeat , int , S_IRUGO ) ;
MODULE_PARM_DESC ( heartbeat ,
" Watchdog heartbeat in seconds. (0 < heartbeat, default= "
__MODULE_STRING ( WD_TIMO ) " ) " ) ;
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
module_param ( nowayout , bool , S_IRUGO ) ;
2010-07-24 21:16:05 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
static unsigned long octeon_wdt_is_open ;
static char expect_close ;
static u32 __initdata nmi_stage1_insns [ 64 ] ;
/* We need one branch and therefore one relocation per target label. */
static struct uasm_label __initdata labels [ 5 ] ;
static struct uasm_reloc __initdata relocs [ 5 ] ;
enum lable_id {
label_enter_bootloader = 1
} ;
/* Some CP0 registers */
# define K0 26
# define C0_CVMMEMCTL 11, 7
# define C0_STATUS 12, 0
# define C0_EBASE 15, 1
# define C0_DESAVE 31, 0
void octeon_wdt_nmi_stage2 ( void ) ;
static void __init octeon_wdt_build_stage1 ( void )
{
int i ;
int len ;
u32 * p = nmi_stage1_insns ;
# ifdef CONFIG_HOTPLUG_CPU
struct uasm_label * l = labels ;
struct uasm_reloc * r = relocs ;
# endif
/*
* For the next few instructions running the debugger may
* cause corruption of k0 in the saved registers . Since we ' re
* about to crash , nobody probably cares .
*
* Save K0 into the debug scratch register
*/
uasm_i_dmtc0 ( & p , K0 , C0_DESAVE ) ;
uasm_i_mfc0 ( & p , K0 , C0_STATUS ) ;
# ifdef CONFIG_HOTPLUG_CPU
uasm_il_bbit0 ( & p , & r , K0 , ilog2 ( ST0_NMI ) , label_enter_bootloader ) ;
# endif
/* Force 64-bit addressing enabled */
uasm_i_ori ( & p , K0 , K0 , ST0_UX | ST0_SX | ST0_KX ) ;
uasm_i_mtc0 ( & p , K0 , C0_STATUS ) ;
# ifdef CONFIG_HOTPLUG_CPU
uasm_i_mfc0 ( & p , K0 , C0_EBASE ) ;
/* Coreid number in K0 */
uasm_i_andi ( & p , K0 , K0 , 0xf ) ;
/* 8 * coreid in bits 16-31 */
uasm_i_dsll_safe ( & p , K0 , K0 , 3 + 16 ) ;
uasm_i_ori ( & p , K0 , K0 , 0x8001 ) ;
uasm_i_dsll_safe ( & p , K0 , K0 , 16 ) ;
uasm_i_ori ( & p , K0 , K0 , 0x0700 ) ;
uasm_i_drotr_safe ( & p , K0 , K0 , 32 ) ;
/*
* Should result in : 0x8001 , 0700 , 0000 , 8 * coreid which is
* CVMX_CIU_WDOGX ( coreid ) - 0x0500
*
* Now ld K0 , CVMX_CIU_WDOGX ( coreid )
*/
uasm_i_ld ( & p , K0 , 0x500 , K0 ) ;
/*
* If bit one set handle the NMI as a watchdog event .
* otherwise transfer control to bootloader .
*/
uasm_il_bbit0 ( & p , & r , K0 , 1 , label_enter_bootloader ) ;
uasm_i_nop ( & p ) ;
# endif
/* Clear Dcache so cvmseg works right. */
uasm_i_cache ( & p , 1 , 0 , 0 ) ;
/* Use K0 to do a read/modify/write of CVMMEMCTL */
uasm_i_dmfc0 ( & p , K0 , C0_CVMMEMCTL ) ;
/* Clear out the size of CVMSEG */
uasm_i_dins ( & p , K0 , 0 , 0 , 6 ) ;
/* Set CVMSEG to its largest value */
uasm_i_ori ( & p , K0 , K0 , 0x1c0 | 54 ) ;
/* Store the CVMMEMCTL value */
uasm_i_dmtc0 ( & p , K0 , C0_CVMMEMCTL ) ;
/* Load the address of the second stage handler */
UASM_i_LA ( & p , K0 , ( long ) octeon_wdt_nmi_stage2 ) ;
uasm_i_jr ( & p , K0 ) ;
uasm_i_dmfc0 ( & p , K0 , C0_DESAVE ) ;
# ifdef CONFIG_HOTPLUG_CPU
uasm_build_label ( & l , p , label_enter_bootloader ) ;
/* Jump to the bootloader and restore K0 */
UASM_i_LA ( & p , K0 , ( long ) octeon_bootloader_entry_addr ) ;
uasm_i_jr ( & p , K0 ) ;
uasm_i_dmfc0 ( & p , K0 , C0_DESAVE ) ;
# endif
uasm_resolve_relocs ( relocs , labels ) ;
len = ( int ) ( p - nmi_stage1_insns ) ;
2012-02-16 03:06:19 +04:00
pr_debug ( " Synthesized NMI stage 1 handler (%d instructions) \n " , len ) ;
2010-07-24 21:16:05 +04:00
pr_debug ( " \t .set push \n " ) ;
pr_debug ( " \t .set noreorder \n " ) ;
for ( i = 0 ; i < len ; i + + )
pr_debug ( " \t .word 0x%08x \n " , nmi_stage1_insns [ i ] ) ;
pr_debug ( " \t .set pop \n " ) ;
if ( len > 32 )
panic ( " NMI stage 1 handler exceeds 32 instructions, was %d \n " , len ) ;
}
static int cpu2core ( int cpu )
{
# ifdef CONFIG_SMP
return cpu_logical_map ( cpu ) ;
# else
return cvmx_get_core_num ( ) ;
# endif
}
static int core2cpu ( int coreid )
{
# ifdef CONFIG_SMP
return cpu_number_map ( coreid ) ;
# else
return 0 ;
# endif
}
/**
* Poke the watchdog when an interrupt is received
*
* @ cpl :
* @ dev_id :
*
* Returns
*/
static irqreturn_t octeon_wdt_poke_irq ( int cpl , void * dev_id )
{
unsigned int core = cvmx_get_core_num ( ) ;
int cpu = core2cpu ( core ) ;
if ( do_coundown ) {
if ( per_cpu_countdown [ cpu ] > 0 ) {
/* We're alive, poke the watchdog */
cvmx_write_csr ( CVMX_CIU_PP_POKEX ( core ) , 1 ) ;
per_cpu_countdown [ cpu ] - - ;
} else {
/* Bad news, you are about to reboot. */
disable_irq_nosync ( cpl ) ;
cpumask_clear_cpu ( cpu , & irq_enabled_cpus ) ;
}
} else {
/* Not open, just ping away... */
cvmx_write_csr ( CVMX_CIU_PP_POKEX ( core ) , 1 ) ;
}
return IRQ_HANDLED ;
}
/* From setup.c */
extern int prom_putchar ( char c ) ;
/**
* Write a string to the uart
*
* @ str : String to write
*/
static void octeon_wdt_write_string ( const char * str )
{
/* Just loop writing one byte at a time */
while ( * str )
prom_putchar ( * str + + ) ;
}
/**
* Write a hex number out of the uart
*
* @ value : Number to display
* @ digits : Number of digits to print ( 1 to 16 )
*/
static void octeon_wdt_write_hex ( u64 value , int digits )
{
int d ;
int v ;
for ( d = 0 ; d < digits ; d + + ) {
v = ( value > > ( ( digits - d - 1 ) * 4 ) ) & 0xf ;
if ( v > = 10 )
prom_putchar ( ' a ' + v - 10 ) ;
else
prom_putchar ( ' 0 ' + v ) ;
}
}
const char * reg_name [ ] = {
" $0 " , " at " , " v0 " , " v1 " , " a0 " , " a1 " , " a2 " , " a3 " ,
" a4 " , " a5 " , " a6 " , " a7 " , " t0 " , " t1 " , " t2 " , " t3 " ,
" s0 " , " s1 " , " s2 " , " s3 " , " s4 " , " s5 " , " s6 " , " s7 " ,
" t8 " , " t9 " , " k0 " , " k1 " , " gp " , " sp " , " s8 " , " ra "
} ;
/**
* NMI stage 3 handler . NMIs are handled in the following manner :
* 1 ) The first NMI handler enables CVMSEG and transfers from
* the bootbus region into normal memory . It is careful to not
* destroy any registers .
* 2 ) The second stage handler uses CVMSEG to save the registers
* and create a stack for C code . It then calls the third level
* handler with one argument , a pointer to the register values .
* 3 ) The third , and final , level handler is the following C
* function that prints out some useful infomration .
*
* @ reg : Pointer to register state before the NMI
*/
void octeon_wdt_nmi_stage3 ( u64 reg [ 32 ] )
{
u64 i ;
unsigned int coreid = cvmx_get_core_num ( ) ;
/*
* Save status and cause early to get them before any changes
* might happen .
*/
u64 cp0_cause = read_c0_cause ( ) ;
u64 cp0_status = read_c0_status ( ) ;
u64 cp0_error_epc = read_c0_errorepc ( ) ;
u64 cp0_epc = read_c0_epc ( ) ;
/* Delay so output from all cores output is not jumbled together. */
__delay ( 100000000ull * coreid ) ;
octeon_wdt_write_string ( " \r \n *** NMI Watchdog interrupt on Core 0x " ) ;
octeon_wdt_write_hex ( coreid , 1 ) ;
octeon_wdt_write_string ( " *** \r \n " ) ;
for ( i = 0 ; i < 32 ; i + + ) {
octeon_wdt_write_string ( " \t " ) ;
octeon_wdt_write_string ( reg_name [ i ] ) ;
octeon_wdt_write_string ( " \t 0x " ) ;
octeon_wdt_write_hex ( reg [ i ] , 16 ) ;
if ( i & 1 )
octeon_wdt_write_string ( " \r \n " ) ;
}
octeon_wdt_write_string ( " \t err_epc \t 0x " ) ;
octeon_wdt_write_hex ( cp0_error_epc , 16 ) ;
octeon_wdt_write_string ( " \t epc \t 0x " ) ;
octeon_wdt_write_hex ( cp0_epc , 16 ) ;
octeon_wdt_write_string ( " \r \n " ) ;
octeon_wdt_write_string ( " \t status \t 0x " ) ;
octeon_wdt_write_hex ( cp0_status , 16 ) ;
octeon_wdt_write_string ( " \t cause \t 0x " ) ;
octeon_wdt_write_hex ( cp0_cause , 16 ) ;
octeon_wdt_write_string ( " \r \n " ) ;
octeon_wdt_write_string ( " \t sum0 \t 0x " ) ;
octeon_wdt_write_hex ( cvmx_read_csr ( CVMX_CIU_INTX_SUM0 ( coreid * 2 ) ) , 16 ) ;
octeon_wdt_write_string ( " \t en0 \t 0x " ) ;
octeon_wdt_write_hex ( cvmx_read_csr ( CVMX_CIU_INTX_EN0 ( coreid * 2 ) ) , 16 ) ;
octeon_wdt_write_string ( " \r \n " ) ;
octeon_wdt_write_string ( " *** Chip soft reset soon *** \r \n " ) ;
}
static void octeon_wdt_disable_interrupt ( int cpu )
{
unsigned int core ;
unsigned int irq ;
union cvmx_ciu_wdogx ciu_wdog ;
core = cpu2core ( cpu ) ;
irq = OCTEON_IRQ_WDOG0 + core ;
/* Poke the watchdog to clear out its state */
cvmx_write_csr ( CVMX_CIU_PP_POKEX ( core ) , 1 ) ;
/* Disable the hardware. */
ciu_wdog . u64 = 0 ;
cvmx_write_csr ( CVMX_CIU_WDOGX ( core ) , ciu_wdog . u64 ) ;
free_irq ( irq , octeon_wdt_poke_irq ) ;
}
static void octeon_wdt_setup_interrupt ( int cpu )
{
unsigned int core ;
unsigned int irq ;
union cvmx_ciu_wdogx ciu_wdog ;
core = cpu2core ( cpu ) ;
/* Disable it before doing anything with the interrupts. */
ciu_wdog . u64 = 0 ;
cvmx_write_csr ( CVMX_CIU_WDOGX ( core ) , ciu_wdog . u64 ) ;
per_cpu_countdown [ cpu ] = countdown_reset ;
irq = OCTEON_IRQ_WDOG0 + core ;
if ( request_irq ( irq , octeon_wdt_poke_irq ,
2011-10-04 04:22:04 +04:00
IRQF_NO_THREAD , " octeon_wdt " , octeon_wdt_poke_irq ) )
2010-07-24 21:16:05 +04:00
panic ( " octeon_wdt: Couldn't obtain irq %d " , irq ) ;
cpumask_set_cpu ( cpu , & irq_enabled_cpus ) ;
/* Poke the watchdog to clear out its state */
cvmx_write_csr ( CVMX_CIU_PP_POKEX ( core ) , 1 ) ;
/* Finally enable the watchdog now that all handlers are installed */
ciu_wdog . u64 = 0 ;
ciu_wdog . s . len = timeout_cnt ;
ciu_wdog . s . mode = 3 ; /* 3 = Interrupt + NMI + Soft-Reset */
cvmx_write_csr ( CVMX_CIU_WDOGX ( core ) , ciu_wdog . u64 ) ;
}
static int octeon_wdt_cpu_callback ( struct notifier_block * nfb ,
unsigned long action , void * hcpu )
{
unsigned int cpu = ( unsigned long ) hcpu ;
switch ( action ) {
case CPU_DOWN_PREPARE :
octeon_wdt_disable_interrupt ( cpu ) ;
break ;
case CPU_ONLINE :
case CPU_DOWN_FAILED :
octeon_wdt_setup_interrupt ( cpu ) ;
break ;
default :
break ;
}
return NOTIFY_OK ;
}
static void octeon_wdt_ping ( void )
{
int cpu ;
int coreid ;
for_each_online_cpu ( cpu ) {
coreid = cpu2core ( cpu ) ;
cvmx_write_csr ( CVMX_CIU_PP_POKEX ( coreid ) , 1 ) ;
per_cpu_countdown [ cpu ] = countdown_reset ;
if ( ( countdown_reset | | ! do_coundown ) & &
! cpumask_test_cpu ( cpu , & irq_enabled_cpus ) ) {
/* We have to enable the irq */
int irq = OCTEON_IRQ_WDOG0 + coreid ;
enable_irq ( irq ) ;
cpumask_set_cpu ( cpu , & irq_enabled_cpus ) ;
}
}
}
static void octeon_wdt_calc_parameters ( int t )
{
unsigned int periods ;
timeout_sec = max_timeout_sec ;
/*
* Find the largest interrupt period , that can evenly divide
* the requested heartbeat time .
*/
while ( ( t % timeout_sec ) ! = 0 )
timeout_sec - - ;
periods = t / timeout_sec ;
/*
* The last two periods are after the irq is disabled , and
* then to the nmi , so we subtract them off .
*/
countdown_reset = periods > 2 ? periods - 2 : 0 ;
heartbeat = t ;
2010-10-08 03:03:52 +04:00
timeout_cnt = ( ( octeon_get_io_clock_rate ( ) > > 8 ) * timeout_sec ) > > 8 ;
2010-07-24 21:16:05 +04:00
}
static int octeon_wdt_set_heartbeat ( int t )
{
int cpu ;
int coreid ;
union cvmx_ciu_wdogx ciu_wdog ;
if ( t < = 0 )
return - 1 ;
octeon_wdt_calc_parameters ( t ) ;
for_each_online_cpu ( cpu ) {
coreid = cpu2core ( cpu ) ;
cvmx_write_csr ( CVMX_CIU_PP_POKEX ( coreid ) , 1 ) ;
ciu_wdog . u64 = 0 ;
ciu_wdog . s . len = timeout_cnt ;
ciu_wdog . s . mode = 3 ; /* 3 = Interrupt + NMI + Soft-Reset */
cvmx_write_csr ( CVMX_CIU_WDOGX ( coreid ) , ciu_wdog . u64 ) ;
cvmx_write_csr ( CVMX_CIU_PP_POKEX ( coreid ) , 1 ) ;
}
octeon_wdt_ping ( ) ; /* Get the irqs back on. */
return 0 ;
}
/**
* octeon_wdt_write :
* @ file : file handle to the watchdog
* @ buf : buffer to write ( unused as data does not matter here
* @ count : count of bytes
* @ ppos : pointer to the position to write . No seeks allowed
*
* A write to a watchdog device is defined as a keepalive signal . Any
* write of data will do , as we we don ' t define content meaning .
*/
static ssize_t octeon_wdt_write ( struct file * file , const char __user * buf ,
size_t count , loff_t * ppos )
{
if ( count ) {
if ( ! nowayout ) {
size_t i ;
/* In case it was set long ago */
expect_close = 0 ;
for ( i = 0 ; i ! = count ; i + + ) {
char c ;
if ( get_user ( c , buf + i ) )
return - EFAULT ;
if ( c = = ' V ' )
expect_close = 1 ;
}
}
octeon_wdt_ping ( ) ;
}
return count ;
}
/**
* octeon_wdt_ioctl :
* @ file : file handle to the device
* @ cmd : watchdog command
* @ arg : argument pointer
*
* The watchdog API defines a common set of functions for all
* watchdogs according to their available features . We only
* actually usefully support querying capabilities and setting
* the timeout .
*/
static long octeon_wdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
int new_heartbeat ;
static struct watchdog_info ident = {
. options = WDIOF_SETTIMEOUT |
WDIOF_MAGICCLOSE |
WDIOF_KEEPALIVEPING ,
. firmware_version = 1 ,
. identity = " OCTEON " ,
} ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
return copy_to_user ( argp , & ident , sizeof ( ident ) ) ? - EFAULT : 0 ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
return put_user ( 0 , p ) ;
case WDIOC_KEEPALIVE :
octeon_wdt_ping ( ) ;
return 0 ;
case WDIOC_SETTIMEOUT :
if ( get_user ( new_heartbeat , p ) )
return - EFAULT ;
if ( octeon_wdt_set_heartbeat ( new_heartbeat ) )
return - EINVAL ;
/* Fall through. */
case WDIOC_GETTIMEOUT :
return put_user ( heartbeat , p ) ;
default :
return - ENOTTY ;
}
}
/**
* octeon_wdt_open :
* @ inode : inode of device
* @ file : file handle to device
*
* The watchdog device has been opened . The watchdog device is single
* open and on opening we do a ping to reset the counters .
*/
static int octeon_wdt_open ( struct inode * inode , struct file * file )
{
if ( test_and_set_bit ( 0 , & octeon_wdt_is_open ) )
return - EBUSY ;
/*
* Activate
*/
octeon_wdt_ping ( ) ;
do_coundown = 1 ;
return nonseekable_open ( inode , file ) ;
}
/**
* octeon_wdt_release :
* @ inode : inode to board
* @ file : file handle to board
*
* The watchdog has a configurable API . There is a religious dispute
* between people who want their watchdog to be able to shut down and
* those who want to be sure if the watchdog manager dies the machine
* reboots . In the former case we disable the counters , in the latter
* case you have to open it again very soon .
*/
static int octeon_wdt_release ( struct inode * inode , struct file * file )
{
if ( expect_close ) {
do_coundown = 0 ;
octeon_wdt_ping ( ) ;
} else {
2012-02-16 03:06:19 +04:00
pr_crit ( " WDT device closed unexpectedly. WDT will not stop! \n " ) ;
2010-07-24 21:16:05 +04:00
}
clear_bit ( 0 , & octeon_wdt_is_open ) ;
expect_close = 0 ;
return 0 ;
}
static const struct file_operations octeon_wdt_fops = {
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = octeon_wdt_write ,
. unlocked_ioctl = octeon_wdt_ioctl ,
. open = octeon_wdt_open ,
. release = octeon_wdt_release ,
} ;
static struct miscdevice octeon_wdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & octeon_wdt_fops ,
} ;
static struct notifier_block octeon_wdt_cpu_notifier = {
. notifier_call = octeon_wdt_cpu_callback ,
} ;
/**
* Module / driver initialization .
*
* Returns Zero on success
*/
static int __init octeon_wdt_init ( void )
{
int i ;
int ret ;
int cpu ;
u64 * ptr ;
/*
* Watchdog time expiration length = The 16 bits of LEN
* represent the most significant bits of a 24 bit decrementer
* that decrements every 256 cycles .
*
* Try for a timeout of 5 sec , if that fails a smaller number
* of even seconds ,
*/
max_timeout_sec = 6 ;
do {
max_timeout_sec - - ;
2010-10-08 03:03:52 +04:00
timeout_cnt = ( ( octeon_get_io_clock_rate ( ) > > 8 ) * max_timeout_sec ) > > 8 ;
2010-07-24 21:16:05 +04:00
} while ( timeout_cnt > 65535 ) ;
BUG_ON ( timeout_cnt = = 0 ) ;
octeon_wdt_calc_parameters ( heartbeat ) ;
2012-02-16 03:06:19 +04:00
pr_info ( " Initial granularity %d Sec \n " , timeout_sec ) ;
2010-07-24 21:16:05 +04:00
ret = misc_register ( & octeon_wdt_miscdev ) ;
if ( ret ) {
2012-02-16 03:06:19 +04:00
pr_err ( " cannot register miscdev on minor=%d (err=%d) \n " ,
WATCHDOG_MINOR , ret ) ;
2010-07-24 21:16:05 +04:00
goto out ;
}
/* Build the NMI handler ... */
octeon_wdt_build_stage1 ( ) ;
/* ... and install it. */
ptr = ( u64 * ) nmi_stage1_insns ;
for ( i = 0 ; i < 16 ; i + + ) {
cvmx_write_csr ( CVMX_MIO_BOOT_LOC_ADR , i * 8 ) ;
cvmx_write_csr ( CVMX_MIO_BOOT_LOC_DAT , ptr [ i ] ) ;
}
cvmx_write_csr ( CVMX_MIO_BOOT_LOC_CFGX ( 0 ) , 0x81fc0000 ) ;
cpumask_clear ( & irq_enabled_cpus ) ;
for_each_online_cpu ( cpu )
octeon_wdt_setup_interrupt ( cpu ) ;
register_hotcpu_notifier ( & octeon_wdt_cpu_notifier ) ;
out :
return ret ;
}
/**
* Module / driver shutdown
*/
static void __exit octeon_wdt_cleanup ( void )
{
int cpu ;
misc_deregister ( & octeon_wdt_miscdev ) ;
unregister_hotcpu_notifier ( & octeon_wdt_cpu_notifier ) ;
for_each_online_cpu ( cpu ) {
int core = cpu2core ( cpu ) ;
/* Disable the watchdog */
cvmx_write_csr ( CVMX_CIU_WDOGX ( core ) , 0 ) ;
/* Free the interrupt handler */
free_irq ( OCTEON_IRQ_WDOG0 + core , octeon_wdt_poke_irq ) ;
}
/*
* Disable the boot - bus memory , the code it points to is soon
* to go missing .
*/
cvmx_write_csr ( CVMX_MIO_BOOT_LOC_CFGX ( 0 ) , 0 ) ;
}
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " Cavium Networks <support@caviumnetworks.com> " ) ;
MODULE_DESCRIPTION ( " Cavium Networks Octeon Watchdog driver. " ) ;
module_init ( octeon_wdt_init ) ;
module_exit ( octeon_wdt_cleanup ) ;