2007-12-04 20:41:54 +03:00
/*
2015-12-14 23:22:09 +03:00
* HPE WatchDog Driver
2007-12-04 20:41:54 +03:00
* based on
*
* SoftDog 0.05 : A Software Watchdog Device
*
2018-02-26 06:22:19 +03:00
* ( c ) Copyright 2018 Hewlett Packard Enterprise Development LP
2015-12-14 23:22:09 +03:00
* Thomas Mingarelli < thomas . mingarelli @ hpe . com >
2007-12-04 20:41:54 +03: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-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2007-12-04 20:41:54 +03:00
# include <linux/device.h>
# include <linux/io.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/pci.h>
# include <linux/pci_ids.h>
# include <linux/types.h>
# include <linux/watchdog.h>
2011-10-06 16:20:27 +04:00
# include <asm/nmi.h>
2007-12-04 20:41:54 +03:00
2018-02-26 06:22:27 +03:00
# define HPWDT_VERSION "2.0.0"
2010-06-03 02:23:39 +04:00
# define SECS_TO_TICKS(secs) ((secs) * 1000 / 128)
2010-06-03 02:23:40 +04:00
# define TICKS_TO_SECS(ticks) ((ticks) * 128 / 1000)
# define HPWDT_MAX_TIMER TICKS_TO_SECS(65535)
2010-07-28 03:50:54 +04:00
# define DEFAULT_MARGIN 30
2018-02-26 06:22:25 +03:00
# define PRETIMEOUT_SEC 9
2010-07-28 03:50:54 +04:00
2018-02-26 06:22:23 +03:00
static bool ilo5 ;
2010-07-28 03:50:54 +04:00
static unsigned int soft_margin = DEFAULT_MARGIN ; /* in seconds */
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
2018-02-26 06:22:25 +03:00
static bool pretimeout = IS_ENABLED ( CONFIG_HPWDT_NMI_DECODING ) ;
2010-07-28 03:50:54 +04:00
static void __iomem * pci_mem_addr ; /* the PCI-memory address */
2017-10-24 01:46:17 +03:00
static unsigned long __iomem * hpwdt_nmistat ;
2010-07-28 03:50:54 +04:00
static unsigned long __iomem * hpwdt_timer_reg ;
static unsigned long __iomem * hpwdt_timer_con ;
2013-12-03 03:30:22 +04:00
static const struct pci_device_id hpwdt_devices [ ] = {
2010-07-28 03:50:57 +04:00
{ PCI_DEVICE ( PCI_VENDOR_ID_COMPAQ , 0xB203 ) } , /* iLO2 */
{ PCI_DEVICE ( PCI_VENDOR_ID_HP , 0x3306 ) } , /* iLO3 */
2010-07-28 03:50:54 +04:00
{ 0 } , /* terminate list */
} ;
MODULE_DEVICE_TABLE ( pci , hpwdt_devices ) ;
2007-12-04 20:41:54 +03:00
/*
* Watchdog operations
*/
2018-02-26 06:22:22 +03:00
static int hpwdt_start ( struct watchdog_device * wdd )
2007-12-04 20:41:54 +03:00
{
2018-02-26 06:22:25 +03:00
int control = 0x81 | ( pretimeout ? 0x4 : 0 ) ;
int reload = SECS_TO_TICKS ( wdd - > timeout ) ;
2018-02-26 06:22:22 +03:00
2018-02-26 06:22:26 +03:00
dev_dbg ( wdd - > parent , " start watchdog 0x%08x:0x%02x \n " , reload , control ) ;
2007-12-04 20:41:54 +03:00
iowrite16 ( reload , hpwdt_timer_reg ) ;
2018-02-26 06:22:25 +03:00
iowrite8 ( control , hpwdt_timer_con ) ;
2018-02-26 06:22:22 +03:00
return 0 ;
2007-12-04 20:41:54 +03:00
}
static void hpwdt_stop ( void )
{
unsigned long data ;
2018-02-26 06:22:26 +03:00
pr_debug ( " stop watchdog \n " ) ;
2012-04-03 09:37:01 +04:00
data = ioread8 ( hpwdt_timer_con ) ;
2007-12-04 20:41:54 +03:00
data & = 0xFE ;
2012-04-03 09:37:01 +04:00
iowrite8 ( data , hpwdt_timer_con ) ;
2007-12-04 20:41:54 +03:00
}
2018-02-26 06:22:22 +03:00
static int hpwdt_stop_core ( struct watchdog_device * wdd )
2007-12-04 20:41:54 +03:00
{
2018-02-26 06:22:22 +03:00
hpwdt_stop ( ) ;
return 0 ;
2007-12-04 20:41:54 +03:00
}
2018-02-26 06:22:22 +03:00
static int hpwdt_ping ( struct watchdog_device * wdd )
2007-12-04 20:41:54 +03:00
{
2018-02-26 06:22:25 +03:00
int reload = SECS_TO_TICKS ( wdd - > timeout ) ;
2018-02-26 06:22:26 +03:00
dev_dbg ( wdd - > parent , " ping watchdog 0x%08x \n " , reload ) ;
2018-02-26 06:22:22 +03:00
iowrite16 ( reload , hpwdt_timer_reg ) ;
2018-02-26 06:22:25 +03:00
2007-12-04 20:41:54 +03:00
return 0 ;
}
2018-02-26 06:22:22 +03:00
static unsigned int hpwdt_gettimeleft ( struct watchdog_device * wdd )
2010-06-03 02:23:41 +04:00
{
return TICKS_TO_SECS ( ioread16 ( hpwdt_timer_reg ) ) ;
}
2018-02-26 06:22:22 +03:00
static int hpwdt_settimeout ( struct watchdog_device * wdd , unsigned int val )
{
2018-02-26 06:22:26 +03:00
dev_dbg ( wdd - > parent , " set_timeout = %d \n " , val ) ;
2018-02-26 06:22:22 +03:00
wdd - > timeout = val ;
2018-02-26 06:22:25 +03:00
if ( val < = wdd - > pretimeout ) {
2018-02-26 06:22:26 +03:00
dev_dbg ( wdd - > parent , " pretimeout < timeout. Setting to zero \n " ) ;
2018-02-26 06:22:25 +03:00
wdd - > pretimeout = 0 ;
pretimeout = 0 ;
if ( watchdog_active ( wdd ) )
hpwdt_start ( wdd ) ;
}
2018-02-26 06:22:22 +03:00
hpwdt_ping ( wdd ) ;
return 0 ;
}
2017-12-07 00:02:37 +03:00
# ifdef CONFIG_HPWDT_NMI_DECODING
2018-02-26 06:22:25 +03:00
static int hpwdt_set_pretimeout ( struct watchdog_device * wdd , unsigned int req )
{
unsigned int val = 0 ;
2018-02-26 06:22:26 +03:00
dev_dbg ( wdd - > parent , " set_pretimeout = %d \n " , req ) ;
2018-02-26 06:22:25 +03:00
if ( req ) {
val = PRETIMEOUT_SEC ;
if ( val > = wdd - > timeout )
return - EINVAL ;
}
2018-02-26 06:22:26 +03:00
if ( val ! = req )
dev_dbg ( wdd - > parent , " Rounding pretimeout to: %d \n " , val ) ;
2018-02-26 06:22:25 +03:00
wdd - > pretimeout = val ;
pretimeout = ! ! val ;
if ( watchdog_active ( wdd ) )
hpwdt_start ( wdd ) ;
return 0 ;
}
2017-10-24 01:46:17 +03:00
static int hpwdt_my_nmi ( void )
{
return ioread8 ( hpwdt_nmistat ) & 0x6 ;
}
2008-07-15 23:40:41 +04:00
/*
* NMI Handler
*/
2011-09-30 23:06:21 +04:00
static int hpwdt_pretimeout ( unsigned int ulReason , struct pt_regs * regs )
2008-07-15 23:40:41 +04:00
{
2018-02-26 06:22:21 +03:00
unsigned int mynmi = hpwdt_my_nmi ( ) ;
static char panic_msg [ ] =
" 00: An NMI occurred. Depending on your system the reason "
" 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 " ;
2018-05-04 00:00:55 +03:00
if ( ilo5 & & ulReason = = NMI_UNKNOWN & & ! mynmi )
2017-10-24 01:46:17 +03:00
return NMI_DONE ;
2018-02-26 06:22:25 +03:00
if ( ilo5 & & ! pretimeout )
return NMI_DONE ;
2018-02-26 06:22:24 +03:00
hpwdt_stop ( ) ;
2011-08-10 02:27:26 +04:00
2018-02-26 06:22:21 +03:00
hex_byte_pack ( panic_msg , mynmi ) ;
nmi_panic ( regs , panic_msg ) ;
2011-07-26 17:05:53 +04:00
2016-03-23 00:27:24 +03:00
return NMI_HANDLED ;
2008-07-15 23:40:41 +04:00
}
2010-07-28 03:51:02 +04:00
# endif /* CONFIG_HPWDT_NMI_DECODING */
2008-07-15 23:40:41 +04:00
2007-12-04 20:41:54 +03:00
2009-12-26 21:55:22 +03:00
static const struct watchdog_info ident = {
2018-02-26 06:22:25 +03:00
. options = WDIOF_PRETIMEOUT |
WDIOF_SETTIMEOUT |
2007-12-04 20:41:54 +03:00
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE ,
2015-12-14 23:22:09 +03:00
. identity = " HPE iLO2+ HW Watchdog Timer " ,
2007-12-04 20:41:54 +03:00
} ;
/*
* Kernel interfaces
*/
2018-02-26 06:22:22 +03:00
static const struct watchdog_ops hpwdt_ops = {
. owner = THIS_MODULE ,
. start = hpwdt_start ,
. stop = hpwdt_stop_core ,
. ping = hpwdt_ping ,
. set_timeout = hpwdt_settimeout ,
. get_timeleft = hpwdt_gettimeleft ,
2018-02-26 06:22:25 +03:00
# ifdef CONFIG_HPWDT_NMI_DECODING
. set_pretimeout = hpwdt_set_pretimeout ,
# endif
2007-12-04 20:41:54 +03:00
} ;
2018-02-26 06:22:22 +03:00
static struct watchdog_device hpwdt_dev = {
. info = & ident ,
. ops = & hpwdt_ops ,
. min_timeout = 1 ,
. max_timeout = HPWDT_MAX_TIMER ,
. timeout = DEFAULT_MARGIN ,
2018-02-26 06:22:25 +03:00
. pretimeout = PRETIMEOUT_SEC ,
2007-12-04 20:41:54 +03:00
} ;
2018-02-26 06:22:22 +03:00
2007-12-04 20:41:54 +03:00
/*
* Init & Exit
*/
2012-11-19 22:21:41 +04:00
static int hpwdt_init_nmi_decoding ( struct pci_dev * dev )
2010-07-28 22:38:43 +04:00
{
2018-02-26 06:22:20 +03:00
# ifdef CONFIG_HPWDT_NMI_DECODING
2010-07-28 22:38:43 +04:00
int retval ;
/*
2012-03-30 00:11:15 +04:00
* Only one function can register for NMI_UNKNOWN
2010-07-28 22:38:43 +04:00
*/
2012-03-30 00:11:15 +04:00
retval = register_nmi_handler ( NMI_UNKNOWN , hpwdt_pretimeout , 0 , " hpwdt " ) ;
2012-03-30 00: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 22:38:43 +04:00
dev_info ( & dev - > dev ,
2018-02-26 06:22:24 +03:00
" HPE Watchdog Timer Driver: NMI decoding initialized \n " ) ;
2010-07-28 22:38:43 +04:00
return 0 ;
2012-03-30 00: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-26 06:22:20 +03:00
# endif /* CONFIG_HPWDT_NMI_DECODING */
return 0 ;
2010-07-28 22:38:43 +04:00
}
2011-03-02 06:49:44 +03:00
static void hpwdt_exit_nmi_decoding ( void )
2010-07-28 22:38:43 +04:00
{
2018-02-26 06:22:20 +03:00
# ifdef CONFIG_HPWDT_NMI_DECODING
2011-09-30 23:06:21 +04:00
unregister_nmi_handler ( NMI_UNKNOWN , " hpwdt " ) ;
2012-06-26 12:27:00 +04:00
unregister_nmi_handler ( NMI_SERR , " hpwdt " ) ;
unregister_nmi_handler ( NMI_IO_CHECK , " hpwdt " ) ;
2018-02-26 06:22:20 +03:00
# endif
2010-07-28 03:51:02 +04:00
}
2012-11-19 22:21:41 +04:00
static int hpwdt_init_one ( struct pci_dev * dev ,
2008-07-15 23:40:41 +04:00
const struct pci_device_id * ent )
2007-12-04 20:41:54 +03:00
{
int retval ;
/*
2010-07-28 03:50:57 +04:00
* First let ' s find out if we are on an iLO2 + server . We will
2007-12-04 20:41:54 +03:00
* not run on a legacy ASM box .
2008-07-15 23:40:41 +04:00
* So we only support the G5 ProLiant servers and higher .
2007-12-04 20:41:54 +03:00
*/
2016-09-26 21:57:14 +03:00
if ( dev - > subsystem_vendor ! = PCI_VENDOR_ID_HP & &
dev - > subsystem_vendor ! = PCI_VENDOR_ID_HP_3PAR ) {
2007-12-04 20:41:54 +03:00
dev_warn ( & dev - > dev ,
2010-07-28 03:50:57 +04:00
" This server does not have an iLO2+ ASIC. \n " ) ;
2007-12-04 20:41:54 +03:00
return - ENODEV ;
}
2013-08-09 20:31:09 +04:00
/*
* Ignore all auxilary iLO devices with the following PCI ID
*/
2016-09-26 21:57:14 +03:00
if ( dev - > subsystem_vendor = = PCI_VENDOR_ID_HP & &
dev - > subsystem_device = = 0x1979 )
2013-08-09 20:31:09 +04:00
return - ENODEV ;
2007-12-04 20:41:54 +03: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-28 03:50:57 +04:00
" Unable to detect the iLO2+ server memory. \n " ) ;
2007-12-04 20:41:54 +03:00
retval = - ENOMEM ;
goto error_pci_iomap ;
}
2017-10-24 01:46:17 +03:00
hpwdt_nmistat = pci_mem_addr + 0x6e ;
2007-12-04 20:41:54 +03:00
hpwdt_timer_reg = pci_mem_addr + 0x70 ;
hpwdt_timer_con = pci_mem_addr + 0x72 ;
2012-08-27 22:52:24 +04:00
/* Make sure that timer is disabled until /dev/watchdog is opened */
hpwdt_stop ( ) ;
2010-07-28 22:38:43 +04:00
/* Initialize NMI Decoding functionality */
retval = hpwdt_init_nmi_decoding ( dev ) ;
if ( retval ! = 0 )
goto error_init_nmi_decoding ;
2007-12-04 20:41:54 +03:00
2018-02-26 06:22:22 +03:00
watchdog_set_nowayout ( & hpwdt_dev , nowayout ) ;
if ( watchdog_init_timeout ( & hpwdt_dev , soft_margin , NULL ) )
dev_warn ( & dev - > dev , " Invalid soft_margin: %d. \n " , soft_margin ) ;
2018-08-08 22:13:23 +03:00
hpwdt_dev . pretimeout = pretimeout ? PRETIMEOUT_SEC : 0 ;
2018-02-26 06:22:22 +03:00
hpwdt_dev . parent = & dev - > dev ;
retval = watchdog_register_device ( & hpwdt_dev ) ;
2007-12-04 20:41:54 +03:00
if ( retval < 0 ) {
2018-02-26 06:22:22 +03:00
dev_err ( & dev - > dev , " watchdog register failed: %d. \n " , retval ) ;
goto error_wd_register ;
2007-12-04 20:41:54 +03:00
}
2015-12-14 23:22:09 +03:00
dev_info ( & dev - > dev , " HPE Watchdog Timer Driver: %s "
2010-07-28 22:38:43 +04:00
" , timer margin: %d seconds (nowayout=%d). \n " ,
2018-02-26 06:22:22 +03:00
HPWDT_VERSION , hpwdt_dev . timeout , nowayout ) ;
2018-02-26 06:22:23 +03:00
if ( dev - > subsystem_vendor = = PCI_VENDOR_ID_HP_3PAR )
ilo5 = true ;
2007-12-04 20:41:54 +03:00
return 0 ;
2018-02-26 06:22:22 +03:00
error_wd_register :
2010-07-28 22:38:43 +04:00
hpwdt_exit_nmi_decoding ( ) ;
error_init_nmi_decoding :
2007-12-04 20:41:54 +03:00
pci_iounmap ( dev , pci_mem_addr ) ;
error_pci_iomap :
pci_disable_device ( dev ) ;
return retval ;
}
2012-11-19 22:26:24 +04:00
static void hpwdt_exit ( struct pci_dev * dev )
2007-12-04 20:41:54 +03:00
{
if ( ! nowayout )
hpwdt_stop ( ) ;
2018-02-26 06:22:22 +03:00
watchdog_unregister_device ( & hpwdt_dev ) ;
2010-07-28 22:38:43 +04:00
hpwdt_exit_nmi_decoding ( ) ;
2007-12-04 20:41:54 +03: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 22:21:12 +04:00
. remove = hpwdt_exit ,
2007-12-04 20:41:54 +03:00
} ;
MODULE_AUTHOR ( " Tom Mingarelli " ) ;
2018-02-26 06:22:19 +03:00
MODULE_DESCRIPTION ( " hpe watchdog driver " ) ;
2007-12-04 20:41:54 +03:00
MODULE_LICENSE ( " GPL " ) ;
2009-03-03 03:17:16 +03:00
MODULE_VERSION ( HPWDT_VERSION ) ;
2007-12-04 20:41:54 +03:00
module_param ( soft_margin , int , 0 ) ;
MODULE_PARM_DESC ( soft_margin , " Watchdog timeout in seconds " ) ;
2012-03-05 19:51:11 +04:00
module_param ( nowayout , bool , 0 ) ;
2007-12-04 20:41:54 +03:00
MODULE_PARM_DESC ( nowayout , " Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2018-02-26 06:22:25 +03:00
# ifdef CONFIG_HPWDT_NMI_DECODING
module_param ( pretimeout , bool , 0 ) ;
MODULE_PARM_DESC ( pretimeout , " Watchdog pretimeout enabled " ) ;
# endif
2012-05-04 16:43:25 +04:00
module_pci_driver ( hpwdt_driver ) ;