2007-12-04 17:41:54 +00:00
/*
2015-12-14 20:22:09 +00:00
* HPE WatchDog Driver
2007-12-04 17:41:54 +00:00
* based on
*
* SoftDog 0.05 : A Software Watchdog Device
*
2018-02-25 20:22:19 -07:00
* ( c ) Copyright 2018 Hewlett Packard Enterprise Development LP
2015-12-14 20:22:09 +00:00
* Thomas Mingarelli < thomas . mingarelli @ hpe . com >
2007-12-04 17:41:54 +00:00
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation
*
*/
2012-02-15 15:06:19 -08:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2007-12-04 17:41:54 +00:00
# include <linux/device.h>
# include <linux/fs.h>
# include <linux/io.h>
2010-07-27 17:50:50 -06:00
# include <linux/bitops.h>
2007-12-04 17:41:54 +00:00
# include <linux/kernel.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/pci.h>
# include <linux/pci_ids.h>
# include <linux/types.h>
# include <linux/uaccess.h>
# include <linux/watchdog.h>
2011-10-06 14:20:27 +02:00
# include <asm/nmi.h>
2007-12-04 17:41:54 +00:00
2016-09-26 13:57:14 -05:00
# define HPWDT_VERSION "1.4.0"
2010-06-02 16:23:39 -06:00
# define SECS_TO_TICKS(secs) ((secs) * 1000 / 128)
2010-06-02 16:23:40 -06:00
# define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000)
# define HPWDT_MAX_TIMER TICKS_TO_SECS(65535)
2010-07-27 17:50:54 -06:00
# define DEFAULT_MARGIN 30
static unsigned int soft_margin = DEFAULT_MARGIN ; /* in seconds */
static unsigned int reload ; /* the computed soft_margin */
2012-03-05 16:51:11 +01:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
2018-02-25 20:22:20 -07:00
# ifdef CONFIG_HPWDT_NMI_DECODING
static unsigned int allow_kdump = 1 ;
# endif
2010-07-27 17:50:54 -06:00
static char expect_release ;
static unsigned long hpwdt_is_open ;
static void __iomem * pci_mem_addr ; /* the PCI-memory address */
2017-10-23 16:46:17 -06:00
static unsigned long __iomem * hpwdt_nmistat ;
2010-07-27 17:50:54 -06:00
static unsigned long __iomem * hpwdt_timer_reg ;
static unsigned long __iomem * hpwdt_timer_con ;
2013-12-03 08:30:22 +09:00
static const struct pci_device_id hpwdt_devices [ ] = {
2010-07-27 17:50:57 -06:00
{ PCI_DEVICE ( PCI_VENDOR_ID_COMPAQ , 0xB203 ) } , /* iLO2 */
{ PCI_DEVICE ( PCI_VENDOR_ID_HP , 0x3306 ) } , /* iLO3 */
2010-07-27 17:50:54 -06:00
{ 0 } , /* terminate list */
} ;
MODULE_DEVICE_TABLE ( pci , hpwdt_devices ) ;
2007-12-04 17:41:54 +00:00
/*
* Watchdog operations
*/
static void hpwdt_start ( void )
{
2010-06-02 16:23:39 -06:00
reload = SECS_TO_TICKS ( soft_margin ) ;
2007-12-04 17:41:54 +00:00
iowrite16 ( reload , hpwdt_timer_reg ) ;
2012-04-03 05:37:01 +00:00
iowrite8 ( 0x85 , hpwdt_timer_con ) ;
2007-12-04 17:41:54 +00:00
}
static void hpwdt_stop ( void )
{
unsigned long data ;
2012-04-03 05:37:01 +00:00
data = ioread8 ( hpwdt_timer_con ) ;
2007-12-04 17:41:54 +00:00
data & = 0xFE ;
2012-04-03 05:37:01 +00:00
iowrite8 ( data , hpwdt_timer_con ) ;
2007-12-04 17:41:54 +00:00
}
static void hpwdt_ping ( void )
{
iowrite16 ( reload , hpwdt_timer_reg ) ;
}
static int hpwdt_change_timer ( int new_margin )
{
2010-06-02 16:23:40 -06:00
if ( new_margin < 1 | | new_margin > HPWDT_MAX_TIMER ) {
2012-02-15 15:06:19 -08:00
pr_warn ( " New value passed in is invalid: %d seconds \n " ,
2007-12-04 17:41:54 +00:00
new_margin ) ;
return - EINVAL ;
}
soft_margin = new_margin ;
2012-02-15 15:06:19 -08:00
pr_debug ( " New timer passed in is %d seconds \n " , new_margin ) ;
2010-06-02 16:23:39 -06:00
reload = SECS_TO_TICKS ( soft_margin ) ;
2007-12-04 17:41:54 +00:00
return 0 ;
}
2010-06-02 16:23:41 -06:00
static int hpwdt_time_left ( void )
{
return TICKS_TO_SECS ( ioread16 ( hpwdt_timer_reg ) ) ;
}
2017-12-06 22:02:37 +01:00
# ifdef CONFIG_HPWDT_NMI_DECODING
2017-10-23 16:46:17 -06:00
static int hpwdt_my_nmi ( void )
{
return ioread8 ( hpwdt_nmistat ) & 0x6 ;
}
2008-07-15 19:40:41 +00:00
/*
* NMI Handler
*/
2011-09-30 15:06:21 -04:00
static int hpwdt_pretimeout ( unsigned int ulReason , struct pt_regs * regs )
2008-07-15 19:40:41 +00:00
{
2017-10-23 16:46:17 -06:00
if ( ( ulReason = = NMI_UNKNOWN ) & & ! hpwdt_my_nmi ( ) )
return NMI_DONE ;
2011-08-09 22:27:26 +00:00
if ( allow_kdump )
hpwdt_stop ( ) ;
2016-03-22 14:27:24 -07:00
nmi_panic ( regs , " An NMI occurred. Depending on your system the reason "
2014-01-28 21:26:10 +01:00
" for the NMI is logged in any one of the following "
" resources: \n "
" 1. Integrated Management Log (IML) \n "
" 2. OA Syslog \n "
" 3. OA Forward Progress Log \n "
" 4. iLO Event Log " ) ;
2011-07-26 14:05:53 +01:00
2016-03-22 14:27:24 -07:00
return NMI_HANDLED ;
2008-07-15 19:40:41 +00:00
}
2010-07-27 17:51:02 -06:00
# endif /* CONFIG_HPWDT_NMI_DECODING */
2008-07-15 19:40:41 +00:00
2007-12-04 17:41:54 +00:00
/*
* / dev / watchdog handling
*/
static int hpwdt_open ( struct inode * inode , struct file * file )
{
/* /dev/watchdog can only be opened once */
if ( test_and_set_bit ( 0 , & hpwdt_is_open ) )
return - EBUSY ;
/* Start the watchdog */
hpwdt_start ( ) ;
hpwdt_ping ( ) ;
return nonseekable_open ( inode , file ) ;
}
static int hpwdt_release ( struct inode * inode , struct file * file )
{
/* Stop the watchdog */
if ( expect_release = = 42 ) {
hpwdt_stop ( ) ;
} else {
2012-02-15 15:06:19 -08:00
pr_crit ( " Unexpected close, not stopping watchdog! \n " ) ;
2007-12-04 17:41:54 +00:00
hpwdt_ping ( ) ;
}
expect_release = 0 ;
/* /dev/watchdog is being closed, make sure it can be re-opened */
clear_bit ( 0 , & hpwdt_is_open ) ;
return 0 ;
}
static ssize_t hpwdt_write ( struct file * file , const char __user * data ,
size_t len , loff_t * ppos )
{
/* See if we got the magic character 'V' and reload the timer */
if ( len ) {
if ( ! nowayout ) {
size_t i ;
/* note: just in case someone wrote the magic character
* five months ago . . . */
expect_release = 0 ;
/* scan to see whether or not we got the magic char. */
for ( i = 0 ; i ! = len ; i + + ) {
char c ;
2008-08-06 20:19:41 +00:00
if ( get_user ( c , data + i ) )
2007-12-04 17:41:54 +00:00
return - EFAULT ;
if ( c = = ' V ' )
expect_release = 42 ;
}
}
/* someone wrote to us, we should reload the timer */
hpwdt_ping ( ) ;
}
return len ;
}
2009-12-26 18:55:22 +00:00
static const struct watchdog_info ident = {
2007-12-04 17:41:54 +00:00
. options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
2015-12-14 20:22:09 +00:00
. identity = " HPE iLO2+ HW Watchdog Timer " ,
2007-12-04 17:41:54 +00:00
} ;
static long hpwdt_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
{
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
2015-06-21 09:32:33 +02:00
int new_margin , options ;
2007-12-04 17:41:54 +00:00
int ret = - ENOTTY ;
switch ( cmd ) {
case WDIOC_GETSUPPORT :
ret = 0 ;
if ( copy_to_user ( argp , & ident , sizeof ( ident ) ) )
ret = - EFAULT ;
break ;
case WDIOC_GETSTATUS :
case WDIOC_GETBOOTSTATUS :
ret = put_user ( 0 , p ) ;
break ;
case WDIOC_KEEPALIVE :
hpwdt_ping ( ) ;
ret = 0 ;
break ;
2015-06-21 09:32:33 +02:00
case WDIOC_SETOPTIONS :
ret = get_user ( options , p ) ;
if ( ret )
break ;
if ( options & WDIOS_DISABLECARD )
hpwdt_stop ( ) ;
if ( options & WDIOS_ENABLECARD ) {
hpwdt_start ( ) ;
hpwdt_ping ( ) ;
}
break ;
2007-12-04 17:41:54 +00:00
case WDIOC_SETTIMEOUT :
ret = get_user ( new_margin , p ) ;
if ( ret )
break ;
ret = hpwdt_change_timer ( new_margin ) ;
if ( ret )
break ;
hpwdt_ping ( ) ;
/* Fall */
case WDIOC_GETTIMEOUT :
ret = put_user ( soft_margin , p ) ;
break ;
2010-06-02 16:23:41 -06:00
case WDIOC_GETTIMELEFT :
ret = put_user ( hpwdt_time_left ( ) , p ) ;
break ;
2007-12-04 17:41:54 +00:00
}
return ret ;
}
/*
* Kernel interfaces
*/
2009-03-18 08:18:43 +00:00
static const struct file_operations hpwdt_fops = {
2007-12-04 17:41:54 +00:00
. owner = THIS_MODULE ,
. llseek = no_llseek ,
. write = hpwdt_write ,
. unlocked_ioctl = hpwdt_ioctl ,
. open = hpwdt_open ,
. release = hpwdt_release ,
} ;
static struct miscdevice hpwdt_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & hpwdt_fops ,
} ;
/*
* Init & Exit
*/
2011-07-26 14:05:53 +01:00
2012-11-19 13:21:41 -05:00
static int hpwdt_init_nmi_decoding ( struct pci_dev * dev )
2010-07-28 12:38:43 -06:00
{
2018-02-25 20:22:20 -07:00
# ifdef CONFIG_HPWDT_NMI_DECODING
2010-07-28 12:38:43 -06:00
int retval ;
/*
2012-03-29 16:11:15 -04:00
* Only one function can register for NMI_UNKNOWN
2010-07-28 12:38:43 -06:00
*/
2012-03-29 16:11:15 -04:00
retval = register_nmi_handler ( NMI_UNKNOWN , hpwdt_pretimeout , 0 , " hpwdt " ) ;
2012-03-29 16:11:16 -04:00
if ( retval )
goto error ;
retval = register_nmi_handler ( NMI_SERR , hpwdt_pretimeout , 0 , " hpwdt " ) ;
if ( retval )
goto error1 ;
retval = register_nmi_handler ( NMI_IO_CHECK , hpwdt_pretimeout , 0 , " hpwdt " ) ;
if ( retval )
goto error2 ;
2010-07-28 12:38:43 -06:00
dev_info ( & dev - > dev ,
2015-12-14 20:22:09 +00:00
" HPE Watchdog Timer Driver: NMI decoding initialized "
2014-10-22 20:24:35 +09:00
" , allow kernel dump: %s (default = 1/ON) \n " ,
2012-03-29 16:11:15 -04:00
( allow_kdump = = 0 ) ? " OFF " : " ON " ) ;
2010-07-28 12:38:43 -06:00
return 0 ;
2012-03-29 16:11:16 -04:00
error2 :
unregister_nmi_handler ( NMI_SERR , " hpwdt " ) ;
error1 :
unregister_nmi_handler ( NMI_UNKNOWN , " hpwdt " ) ;
error :
dev_warn ( & dev - > dev ,
" Unable to register a die notifier (err=%d). \n " ,
retval ) ;
return retval ;
2018-02-25 20:22:20 -07:00
# endif /* CONFIG_HPWDT_NMI_DECODING */
return 0 ;
2010-07-28 12:38:43 -06:00
}
2011-03-02 11:49:44 +08:00
static void hpwdt_exit_nmi_decoding ( void )
2010-07-28 12:38:43 -06:00
{
2018-02-25 20:22:20 -07:00
# ifdef CONFIG_HPWDT_NMI_DECODING
2011-09-30 15:06:21 -04:00
unregister_nmi_handler ( NMI_UNKNOWN , " hpwdt " ) ;
2012-06-26 10:27:00 +02:00
unregister_nmi_handler ( NMI_SERR , " hpwdt " ) ;
unregister_nmi_handler ( NMI_IO_CHECK , " hpwdt " ) ;
2018-02-25 20:22:20 -07:00
# endif
2010-07-27 17:51:02 -06:00
}
2012-11-19 13:21:41 -05:00
static int hpwdt_init_one ( struct pci_dev * dev ,
2008-07-15 19:40:41 +00:00
const struct pci_device_id * ent )
2007-12-04 17:41:54 +00:00
{
int retval ;
/*
2010-07-27 17:50:57 -06:00
* First let ' s find out if we are on an iLO2 + server . We will
2007-12-04 17:41:54 +00:00
* not run on a legacy ASM box .
2008-07-15 19:40:41 +00:00
* So we only support the G5 ProLiant servers and higher .
2007-12-04 17:41:54 +00:00
*/
2016-09-26 13:57:14 -05:00
if ( dev - > subsystem_vendor ! = PCI_VENDOR_ID_HP & &
dev - > subsystem_vendor ! = PCI_VENDOR_ID_HP_3PAR ) {
2007-12-04 17:41:54 +00:00
dev_warn ( & dev - > dev ,
2010-07-27 17:50:57 -06:00
" This server does not have an iLO2+ ASIC. \n " ) ;
2007-12-04 17:41:54 +00:00
return - ENODEV ;
}
2013-08-09 16:31:09 +00:00
/*
* Ignore all auxilary iLO devices with the following PCI ID
*/
2016-09-26 13:57:14 -05:00
if ( dev - > subsystem_vendor = = PCI_VENDOR_ID_HP & &
dev - > subsystem_device = = 0x1979 )
2013-08-09 16:31:09 +00:00
return - ENODEV ;
2007-12-04 17:41:54 +00:00
if ( pci_enable_device ( dev ) ) {
dev_warn ( & dev - > dev ,
" Not possible to enable PCI Device: 0x%x:0x%x. \n " ,
ent - > vendor , ent - > device ) ;
return - ENODEV ;
}
pci_mem_addr = pci_iomap ( dev , 1 , 0x80 ) ;
if ( ! pci_mem_addr ) {
dev_warn ( & dev - > dev ,
2010-07-27 17:50:57 -06:00
" Unable to detect the iLO2+ server memory. \n " ) ;
2007-12-04 17:41:54 +00:00
retval = - ENOMEM ;
goto error_pci_iomap ;
}
2017-10-23 16:46:17 -06:00
hpwdt_nmistat = pci_mem_addr + 0x6e ;
2007-12-04 17:41:54 +00:00
hpwdt_timer_reg = pci_mem_addr + 0x70 ;
hpwdt_timer_con = pci_mem_addr + 0x72 ;
2012-08-27 12:52:24 -06:00
/* Make sure that timer is disabled until /dev/watchdog is opened */
hpwdt_stop ( ) ;
2007-12-04 17:41:54 +00:00
/* Make sure that we have a valid soft_margin */
if ( hpwdt_change_timer ( soft_margin ) )
hpwdt_change_timer ( DEFAULT_MARGIN ) ;
2010-07-28 12:38:43 -06:00
/* Initialize NMI Decoding functionality */
retval = hpwdt_init_nmi_decoding ( dev ) ;
if ( retval ! = 0 )
goto error_init_nmi_decoding ;
2007-12-04 17:41:54 +00:00
retval = misc_register ( & hpwdt_miscdev ) ;
if ( retval < 0 ) {
dev_warn ( & dev - > dev ,
" Unable to register miscdev on minor=%d (err=%d). \n " ,
WATCHDOG_MINOR , retval ) ;
goto error_misc_register ;
}
2015-12-14 20:22:09 +00:00
dev_info ( & dev - > dev , " HPE Watchdog Timer Driver: %s "
2010-07-28 12:38:43 -06:00
" , timer margin: %d seconds (nowayout=%d). \n " ,
HPWDT_VERSION , soft_margin , nowayout ) ;
2007-12-04 17:41:54 +00:00
return 0 ;
error_misc_register :
2010-07-28 12:38:43 -06:00
hpwdt_exit_nmi_decoding ( ) ;
error_init_nmi_decoding :
2007-12-04 17:41:54 +00:00
pci_iounmap ( dev , pci_mem_addr ) ;
error_pci_iomap :
pci_disable_device ( dev ) ;
return retval ;
}
2012-11-19 13:26:24 -05:00
static void hpwdt_exit ( struct pci_dev * dev )
2007-12-04 17:41:54 +00:00
{
if ( ! nowayout )
hpwdt_stop ( ) ;
misc_deregister ( & hpwdt_miscdev ) ;
2010-07-28 12:38:43 -06:00
hpwdt_exit_nmi_decoding ( ) ;
2007-12-04 17:41:54 +00:00
pci_iounmap ( dev , pci_mem_addr ) ;
pci_disable_device ( dev ) ;
}
static struct pci_driver hpwdt_driver = {
. name = " hpwdt " ,
. id_table = hpwdt_devices ,
. probe = hpwdt_init_one ,
2012-11-19 13:21:12 -05:00
. remove = hpwdt_exit ,
2007-12-04 17:41:54 +00:00
} ;
MODULE_AUTHOR ( " Tom Mingarelli " ) ;
2018-02-25 20:22:19 -07:00
MODULE_DESCRIPTION ( " hpe watchdog driver " ) ;
2007-12-04 17:41:54 +00:00
MODULE_LICENSE ( " GPL " ) ;
2009-03-03 00:17:16 +00:00
MODULE_VERSION ( HPWDT_VERSION ) ;
2007-12-04 17:41:54 +00:00
module_param ( soft_margin , int , 0 ) ;
MODULE_PARM_DESC ( soft_margin , " Watchdog timeout in seconds " ) ;
2012-03-05 16:51:11 +01:00
module_param ( nowayout , bool , 0 ) ;
2007-12-04 17:41:54 +00:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2010-07-27 17:51:02 -06:00
# ifdef CONFIG_HPWDT_NMI_DECODING
2010-07-27 17:50:54 -06:00
module_param ( allow_kdump , int , 0 ) ;
MODULE_PARM_DESC ( allow_kdump , " Start a kernel dump after NMI occurs " ) ;
2018-02-25 20:22:20 -07:00
# endif /* CONFIG_HPWDT_NMI_DECODING */
2009-06-18 23:28:57 +00:00
2012-05-04 14:43:25 +02:00
module_pci_driver ( hpwdt_driver ) ;