2005-08-20 01:15:11 +04:00
/*
* IBM Automatic Server Restart driver .
*
* Copyright ( c ) 2005 Andrey Panin < pazke @ donpac . ru >
*
* Based on driver written by Pete Reynolds .
* Copyright ( c ) IBM Corporation , 1998 - 2004.
*
* This software may be used and distributed according to the terms
* of the GNU Public License , incorporated herein by reference .
*/
2012-02-16 03:06:19 +04:00
# define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
2005-08-20 01:15:11 +04:00
# include <linux/fs.h>
# include <linux/kernel.h>
# include <linux/module.h>
# include <linux/pci.h>
# include <linux/timer.h>
# include <linux/miscdevice.h>
# include <linux/watchdog.h>
# include <linux/dmi.h>
2008-05-19 17:06:03 +04:00
# include <linux/io.h>
# include <linux/uaccess.h>
2005-08-20 01:15:11 +04:00
enum {
ASMTYPE_UNKNOWN ,
ASMTYPE_TOPAZ ,
ASMTYPE_JASPER ,
ASMTYPE_PEARL ,
ASMTYPE_JUNIPER ,
ASMTYPE_SPRUCE ,
} ;
# define TOPAZ_ASR_REG_OFFSET 4
# define TOPAZ_ASR_TOGGLE 0x40
# define TOPAZ_ASR_DISABLE 0x80
/* PEARL ASR S/W REGISTER SUPERIO PORT ADDRESSES */
# define PEARL_BASE 0xe04
# define PEARL_WRITE 0xe06
# define PEARL_READ 0xe07
# define PEARL_ASR_DISABLE_MASK 0x80 /* bit 7: disable = 1, enable = 0 */
# define PEARL_ASR_TOGGLE_MASK 0x40 /* bit 6: 0, then 1, then 0 */
/* JASPER OFFSET FROM SIO BASE ADDR TO ASR S/W REGISTERS. */
# define JASPER_ASR_REG_OFFSET 0x38
# define JASPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1, enable = 0 */
# define JASPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */
# define JUNIPER_BASE_ADDRESS 0x54b /* Base address of Juniper ASR */
# define JUNIPER_ASR_DISABLE_MASK 0x01 /* bit 0: disable = 1 enable = 0 */
# define JUNIPER_ASR_TOGGLE_MASK 0x02 /* bit 1: 0, then 1, then 0 */
# define SPRUCE_BASE_ADDRESS 0x118e /* Base address of Spruce ASR */
# define SPRUCE_ASR_DISABLE_MASK 0x01 /* bit 1: disable = 1 enable = 0 */
# define SPRUCE_ASR_TOGGLE_MASK 0x02 /* bit 0: 0, then 1, then 0 */
2012-03-05 19:51:11 +04:00
static bool nowayout = WATCHDOG_NOWAYOUT ;
2005-08-20 01:15:11 +04:00
static unsigned long asr_is_open ;
static char asr_expect_close ;
static unsigned int asr_type , asr_base , asr_length ;
static unsigned int asr_read_addr , asr_write_addr ;
static unsigned char asr_toggle_mask , asr_disable_mask ;
2011-11-29 09:54:01 +04:00
static DEFINE_SPINLOCK ( asr_lock ) ;
2005-08-20 01:15:11 +04:00
2008-05-19 17:06:03 +04:00
static void __asr_toggle ( void )
2005-08-20 01:15:11 +04:00
{
2008-05-19 17:06:03 +04:00
unsigned char reg ;
reg = inb ( asr_read_addr ) ;
2005-08-20 01:15:11 +04:00
outb ( reg & ~ asr_toggle_mask , asr_write_addr ) ;
reg = inb ( asr_read_addr ) ;
outb ( reg | asr_toggle_mask , asr_write_addr ) ;
reg = inb ( asr_read_addr ) ;
outb ( reg & ~ asr_toggle_mask , asr_write_addr ) ;
reg = inb ( asr_read_addr ) ;
2008-05-19 17:06:03 +04:00
}
static void asr_toggle ( void )
{
spin_lock ( & asr_lock ) ;
__asr_toggle ( ) ;
spin_unlock ( & asr_lock ) ;
2005-08-20 01:15:11 +04:00
}
static void asr_enable ( void )
{
unsigned char reg ;
2008-05-19 17:06:03 +04:00
spin_lock ( & asr_lock ) ;
2005-08-20 01:15:11 +04:00
if ( asr_type = = ASMTYPE_TOPAZ ) {
/* asr_write_addr == asr_read_addr */
reg = inb ( asr_read_addr ) ;
outb ( reg & ~ ( TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE ) ,
asr_read_addr ) ;
} else {
/*
* First make sure the hardware timer is reset by toggling
* ASR hardware timer line .
*/
2008-05-19 17:06:03 +04:00
__asr_toggle ( ) ;
2005-08-20 01:15:11 +04:00
reg = inb ( asr_read_addr ) ;
outb ( reg & ~ asr_disable_mask , asr_write_addr ) ;
}
reg = inb ( asr_read_addr ) ;
2008-05-19 17:06:03 +04:00
spin_unlock ( & asr_lock ) ;
2005-08-20 01:15:11 +04:00
}
static void asr_disable ( void )
{
2008-05-19 17:06:03 +04:00
unsigned char reg ;
spin_lock ( & asr_lock ) ;
reg = inb ( asr_read_addr ) ;
2005-08-20 01:15:11 +04:00
if ( asr_type = = ASMTYPE_TOPAZ )
/* asr_write_addr == asr_read_addr */
outb ( reg | TOPAZ_ASR_TOGGLE | TOPAZ_ASR_DISABLE ,
asr_read_addr ) ;
else {
outb ( reg | asr_toggle_mask , asr_write_addr ) ;
reg = inb ( asr_read_addr ) ;
outb ( reg | asr_disable_mask , asr_write_addr ) ;
}
reg = inb ( asr_read_addr ) ;
2008-05-19 17:06:03 +04:00
spin_unlock ( & asr_lock ) ;
2005-08-20 01:15:11 +04:00
}
static int __init asr_get_base_address ( void )
{
unsigned char low , high ;
const char * type = " " ;
asr_length = 1 ;
switch ( asr_type ) {
case ASMTYPE_TOPAZ :
2008-05-19 17:06:03 +04:00
/* SELECT SuperIO CHIP FOR QUERYING
( WRITE 0x07 TO BOTH 0x2E and 0x2F ) */
2005-08-20 01:15:11 +04:00
outb ( 0x07 , 0x2e ) ;
outb ( 0x07 , 0x2f ) ;
/* SELECT AND READ THE HIGH-NIBBLE OF THE GPIO BASE ADDRESS */
outb ( 0x60 , 0x2e ) ;
high = inb ( 0x2f ) ;
/* SELECT AND READ THE LOW-NIBBLE OF THE GPIO BASE ADDRESS */
outb ( 0x61 , 0x2e ) ;
low = inb ( 0x2f ) ;
asr_base = ( high < < 16 ) | low ;
asr_read_addr = asr_write_addr =
asr_base + TOPAZ_ASR_REG_OFFSET ;
asr_length = 5 ;
break ;
case ASMTYPE_JASPER :
type = " Jaspers " ;
2008-05-19 17:06:03 +04:00
#if 0
u32 r ;
/* Suggested fix */
pdev = pci_get_bus_and_slot ( 0 , DEVFN ( 0x1f , 0 ) ) ;
if ( pdev = = NULL )
return - ENODEV ;
pci_read_config_dword ( pdev , 0x58 , & r ) ;
asr_base = r & 0xFFFE ;
pci_dev_put ( pdev ) ;
# else
/* FIXME: need to use pci_config_lock here,
but it ' s not exported */
2005-08-20 01:15:11 +04:00
/* spin_lock_irqsave(&pci_config_lock, flags);*/
/* Select the SuperIO chip in the PCI I/O port register */
outl ( 0x8000f858 , 0xcf8 ) ;
2008-05-19 17:06:03 +04:00
/* BUS 0, Slot 1F, fnc 0, offset 58 */
2005-08-20 01:15:11 +04:00
/*
* Read the base address for the SuperIO chip .
* Only the lower 16 bits are valid , but the address is word
* aligned so the last bit must be masked off .
*/
asr_base = inl ( 0xcfc ) & 0xfffe ;
/* spin_unlock_irqrestore(&pci_config_lock, flags);*/
2008-05-19 17:06:03 +04:00
# endif
2005-08-20 01:15:11 +04:00
asr_read_addr = asr_write_addr =
asr_base + JASPER_ASR_REG_OFFSET ;
asr_toggle_mask = JASPER_ASR_TOGGLE_MASK ;
asr_disable_mask = JASPER_ASR_DISABLE_MASK ;
asr_length = JASPER_ASR_REG_OFFSET + 1 ;
break ;
case ASMTYPE_PEARL :
type = " Pearls " ;
asr_base = PEARL_BASE ;
asr_read_addr = PEARL_READ ;
asr_write_addr = PEARL_WRITE ;
asr_toggle_mask = PEARL_ASR_TOGGLE_MASK ;
asr_disable_mask = PEARL_ASR_DISABLE_MASK ;
asr_length = 4 ;
break ;
case ASMTYPE_JUNIPER :
type = " Junipers " ;
asr_base = JUNIPER_BASE_ADDRESS ;
asr_read_addr = asr_write_addr = asr_base ;
asr_toggle_mask = JUNIPER_ASR_TOGGLE_MASK ;
asr_disable_mask = JUNIPER_ASR_DISABLE_MASK ;
break ;
case ASMTYPE_SPRUCE :
type = " Spruce's " ;
asr_base = SPRUCE_BASE_ADDRESS ;
asr_read_addr = asr_write_addr = asr_base ;
asr_toggle_mask = SPRUCE_ASR_TOGGLE_MASK ;
asr_disable_mask = SPRUCE_ASR_DISABLE_MASK ;
break ;
}
if ( ! request_region ( asr_base , asr_length , " ibmasr " ) ) {
2012-02-16 03:06:19 +04:00
pr_err ( " address %#x already in use \n " , asr_base ) ;
2005-08-20 01:15:11 +04:00
return - EBUSY ;
}
2012-02-16 03:06:19 +04:00
pr_info ( " found %sASR @ addr %#x \n " , type , asr_base ) ;
2005-08-20 01:15:11 +04:00
return 0 ;
}
static ssize_t asr_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 */
asr_expect_close = 0 ;
for ( i = 0 ; i ! = count ; i + + ) {
char c ;
if ( get_user ( c , buf + i ) )
return - EFAULT ;
if ( c = = ' V ' )
asr_expect_close = 42 ;
}
}
asr_toggle ( ) ;
}
return count ;
}
2008-05-19 17:06:03 +04:00
static long asr_ioctl ( struct file * file , unsigned int cmd , unsigned long arg )
2005-08-20 01:15:11 +04:00
{
static const struct watchdog_info ident = {
2008-05-19 17:06:03 +04:00
. options = WDIOF_KEEPALIVEPING |
2005-08-20 01:15:11 +04:00
WDIOF_MAGICCLOSE ,
2008-08-07 00:19:41 +04:00
. identity = " IBM ASR " ,
2005-08-20 01:15:11 +04:00
} ;
void __user * argp = ( void __user * ) arg ;
int __user * p = argp ;
int heartbeat ;
switch ( cmd ) {
2008-05-19 17:06:03 +04:00
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_SETOPTIONS :
{
int new_options , retval = - EINVAL ;
if ( get_user ( new_options , p ) )
return - EFAULT ;
if ( new_options & WDIOS_DISABLECARD ) {
asr_disable ( ) ;
retval = 0 ;
}
if ( new_options & WDIOS_ENABLECARD ) {
asr_enable ( ) ;
2005-08-20 01:15:11 +04:00
asr_toggle ( ) ;
2008-05-19 17:06:03 +04:00
retval = 0 ;
2005-08-20 01:15:11 +04:00
}
2008-05-19 17:06:03 +04:00
return retval ;
}
2008-07-18 15:41:17 +04:00
case WDIOC_KEEPALIVE :
asr_toggle ( ) ;
return 0 ;
/*
* The hardware has a fixed timeout value , so no WDIOC_SETTIMEOUT
* and WDIOC_GETTIMEOUT always returns 256.
*/
case WDIOC_GETTIMEOUT :
heartbeat = 256 ;
return put_user ( heartbeat , p ) ;
2008-05-19 17:06:03 +04:00
default :
return - ENOTTY ;
2005-08-20 01:15:11 +04:00
}
}
static int asr_open ( struct inode * inode , struct file * file )
{
2008-05-19 17:06:03 +04:00
if ( test_and_set_bit ( 0 , & asr_is_open ) )
2005-08-20 01:15:11 +04:00
return - EBUSY ;
asr_toggle ( ) ;
asr_enable ( ) ;
return nonseekable_open ( inode , file ) ;
}
static int asr_release ( struct inode * inode , struct file * file )
{
if ( asr_expect_close = = 42 )
asr_disable ( ) ;
else {
2012-02-16 03:06:19 +04:00
pr_crit ( " unexpected close, not stopping watchdog! \n " ) ;
2005-08-20 01:15:11 +04:00
asr_toggle ( ) ;
}
clear_bit ( 0 , & asr_is_open ) ;
asr_expect_close = 0 ;
return 0 ;
}
2006-07-03 11:24:21 +04:00
static const struct file_operations asr_fops = {
2008-05-19 17:06:03 +04:00
. owner = THIS_MODULE ,
2008-08-07 00:19:41 +04:00
. llseek = no_llseek ,
2008-05-19 17:06:03 +04:00
. write = asr_write ,
. unlocked_ioctl = asr_ioctl ,
. open = asr_open ,
. release = asr_release ,
2005-08-20 01:15:11 +04:00
} ;
static struct miscdevice asr_miscdev = {
. minor = WATCHDOG_MINOR ,
. name = " watchdog " ,
. fops = & asr_fops ,
} ;
struct ibmasr_id {
const char * desc ;
int type ;
} ;
static struct ibmasr_id __initdata ibmasr_id_table [ ] = {
{ " IBM Automatic Server Restart - eserver xSeries 220 " , ASMTYPE_TOPAZ } ,
{ " IBM Automatic Server Restart - Machine Type 8673 " , ASMTYPE_PEARL } ,
{ " IBM Automatic Server Restart - Machine Type 8480 " , ASMTYPE_JASPER } ,
{ " IBM Automatic Server Restart - Machine Type 8482 " , ASMTYPE_JUNIPER } ,
{ " IBM Automatic Server Restart - Machine Type 8648 " , ASMTYPE_SPRUCE } ,
{ NULL }
} ;
static int __init ibmasr_init ( void )
{
struct ibmasr_id * id ;
int rc ;
for ( id = ibmasr_id_table ; id - > desc ; id + + ) {
if ( dmi_find_device ( DMI_DEV_TYPE_OTHER , id - > desc , NULL ) ) {
asr_type = id - > type ;
break ;
}
}
if ( ! asr_type )
return - ENODEV ;
2007-03-24 15:58:12 +03:00
rc = asr_get_base_address ( ) ;
if ( rc )
return rc ;
2005-08-20 01:15:11 +04:00
rc = misc_register ( & asr_miscdev ) ;
if ( rc < 0 ) {
2007-03-24 15:58:12 +03:00
release_region ( asr_base , asr_length ) ;
2012-02-16 03:06:19 +04:00
pr_err ( " failed to register misc device \n " ) ;
2005-08-20 01:15:11 +04:00
return rc ;
}
return 0 ;
}
static void __exit ibmasr_exit ( void )
{
if ( ! nowayout )
asr_disable ( ) ;
misc_deregister ( & asr_miscdev ) ;
release_region ( asr_base , asr_length ) ;
}
module_init ( ibmasr_init ) ;
module_exit ( ibmasr_exit ) ;
2012-03-05 19:51:11 +04:00
module_param ( nowayout , bool , 0 ) ;
2008-05-19 17:06:03 +04:00
MODULE_PARM_DESC ( nowayout ,
" Watchdog cannot be stopped once started (default= "
__MODULE_STRING ( WATCHDOG_NOWAYOUT ) " ) " ) ;
2005-08-20 01:15:11 +04:00
MODULE_DESCRIPTION ( " IBM Automatic Server Restart driver " ) ;
MODULE_AUTHOR ( " Andrey Panin " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS_MISCDEV ( WATCHDOG_MINOR ) ;