Commit 2523e5b6 authored by Wim Van Sebroeck's avatar Wim Van Sebroeck Committed by Linus Torvalds

[WATCHDOG] v2.6.2 i8xx_tco-v0.06_update

Version 0.06 of the intel i8xx TCO driver:
* change i810_margin to heartbeat (in seconds)
* use module_param
* added notify system support
* renamed module to i8xx_tco
parent a546bacd
......@@ -204,25 +204,23 @@ config IB700_WDT
Most people will say N.
config I810_TCO
config I8XX_TCO
tristate "Intel i8xx TCO timer / Watchdog"
depends on WATCHDOG && X86
depends on WATCHDOG && (X86 || IA64) && PCI
---help---
Hardware driver for the TCO timer built into the Intel i8xx
chipset family. The TCO (Total Cost of Ownership) timer is a
watchdog timer that will reboot the machine after its second
expiration. The expiration time can be configured by commandline
argument "i810_margin=<n>" where <n> is the counter initial value.
It is decremented every 0.6 secs, the default is 50 which gives a
timeout of 30 seconds and one minute until reset.
Hardware driver for the TCO timer built into the Intel 82801
I/O Controller Hub family. The TCO (Total Cost of Ownership)
timer is a watchdog timer that will reboot the machine after
its second expiration. The expiration time can be configured
with the "heartbeat" parameter.
On some motherboards the driver may fail to reset the chipset's
NO_REBOOT flag which prevents the watchdog from rebooting the
machine. If this is the case you will get a kernel message like
"i810tco init: failed to reset NO_REBOOT flag".
"failed to reset NO_REBOOT flag, reboot disabled by hardware".
To compile this driver as a module, choose M here: the
module will be called i810-tco.
module will be called i8xx_tco.
config MIXCOMWD
tristate "Mixcom Watchdog"
......
......@@ -18,7 +18,7 @@ obj-$(CONFIG_WDT) += wdt.o
obj-$(CONFIG_WDTPCI) += wdt_pci.o
obj-$(CONFIG_21285_WATCHDOG) += wdt285.o
obj-$(CONFIG_977_WATCHDOG) += wdt977.o
obj-$(CONFIG_I810_TCO) += i810-tco.o
obj-$(CONFIG_I8XX_TCO) += i8xx_tco.o
obj-$(CONFIG_MACHZ_WDT) += machzwd.o
obj-$(CONFIG_SH_WDT) += shwdt.o
obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o
......
/*
* i810-tco 0.05: TCO timer driver for i8xx chipsets
* i8xx_tco 0.06: TCO timer driver for i8xx chipsets
*
* (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved.
* http://www.kernelconcepts.de
......@@ -46,36 +46,50 @@
* added support for 82801DB and 82801E chipset,
* added support for 82801EB and 8280ER chipset,
* general cleanup.
* 20030921 Wim Van Sebroeck <wim@iguana.be>
* 0.06 change i810_margin to heartbeat, use module_param,
* added notify system support, renamed module to i8xx_tco.
*/
/*
* Includes, defines, variables, module parameters, ...
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/types.h>
#include <linux/miscdevice.h>
#include <linux/watchdog.h>
#include <linux/notifier.h>
#include <linux/reboot.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/pci.h>
#include <linux/ioport.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include "i810-tco.h"
#include "i8xx_tco.h"
/* Module and version information */
#define TCO_VERSION "0.05"
#define TCO_MODULE_NAME "i810 TCO timer"
#define TCO_VERSION "0.06"
#define TCO_MODULE_NAME "i8xx TCO timer"
#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION
#define PFX TCO_MODULE_NAME ": "
/* Default expire timeout */
#define TIMER_MARGIN 50 /* steps of 0.6sec, 3<n<64. Default is 30 seconds */
/* internal variables */
static unsigned int ACPIBASE;
static spinlock_t tco_lock; /* Guards the hardware */
static unsigned long timer_alive;
static char tco_expect_close;
static struct pci_dev *i8xx_tco_pci;
static int i810_margin = TIMER_MARGIN; /* steps of 0.6sec */
MODULE_PARM(i810_margin, "i");
MODULE_PARM_DESC(i810_margin, "i810-tco timeout in steps of 0.6sec, 3<n<64. Default = 50 (30 seconds)");
/* module parameters */
#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat (2<heartbeat<39) */
static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */
module_param(heartbeat, int, 0);
MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<heartbeat<39, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")");
#ifdef CONFIG_WATCHDOG_NOWAYOUT
static int nowayout = 1;
......@@ -83,25 +97,20 @@ static int nowayout = 1;
static int nowayout = 0;
#endif
MODULE_PARM(nowayout,"i");
module_param(nowayout, int, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)");
/*
* Timer active flag
*/
static unsigned long timer_alive;
static char tco_expect_close;
/*
* Some TCO specific functions
*/
static inline unsigned char seconds_to_ticks(int seconds)
{
/* the internal timer is stored as ticks which decrement
* every 0.6 seconds */
return (seconds * 10) / 6;
}
/*
* Start the timer countdown
*/
static int tco_timer_start (void)
{
unsigned char val;
......@@ -118,9 +127,6 @@ static int tco_timer_start (void)
return 0;
}
/*
* Stop the timer countdown
*/
static int tco_timer_stop (void)
{
unsigned char val;
......@@ -137,18 +143,26 @@ static int tco_timer_stop (void)
return 0;
}
/*
* Set the timer reload value
*/
static int tco_timer_settimer (unsigned char tmrval)
static int tco_timer_keepalive (void)
{
spin_lock(&tco_lock);
outb (0x01, TCO1_RLD);
spin_unlock(&tco_lock);
return 0;
}
static int tco_timer_set_heartbeat (int t)
{
unsigned char val;
unsigned char tmrval;
tmrval = seconds_to_ticks(t);
/* from the specs: */
/* "Values of 0h-3h are ignored and should not be attempted" */
if (tmrval > 0x3f || tmrval < 0x04)
return -1;
return -EINVAL;
/* Write new heartbeat to watchdog */
spin_lock(&tco_lock);
val = inb (TCO1_TMR);
val &= 0xc0;
......@@ -158,41 +172,31 @@ static int tco_timer_settimer (unsigned char tmrval)
spin_unlock(&tco_lock);
if ((val & 0x3f) != tmrval)
return -1;
return -EINVAL;
heartbeat = t;
return 0;
}
/*
* Reload (trigger) the timer. Lock is needed so we don't reload it during
* a reprogramming event
*/
static void tco_timer_reload (void)
{
spin_lock(&tco_lock);
outb (0x01, TCO1_RLD);
spin_unlock(&tco_lock);
}
/*
* Allow only one person to hold it open
* /dev/watchdog handling
*/
static int i810tco_open (struct inode *inode, struct file *file)
static int i8xx_tco_open (struct inode *inode, struct file *file)
{
/* /dev/watchdog can only be opened once */
if (test_and_set_bit(0, &timer_alive))
return -EBUSY;
/*
* Reload and activate timer
*/
tco_timer_reload ();
tco_timer_keepalive ();
tco_timer_start ();
return 0;
}
static int i810tco_release (struct inode *inode, struct file *file)
static int i8xx_tco_release (struct inode *inode, struct file *file)
{
/*
* Shut off the timer.
......@@ -200,15 +204,15 @@ static int i810tco_release (struct inode *inode, struct file *file)
if (tco_expect_close == 42) {
tco_timer_stop ();
} else {
tco_timer_reload ();
printk(KERN_CRIT TCO_MODULE_NAME ": Unexpected close, not stopping watchdog!\n");
printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n");
tco_timer_keepalive ();
}
clear_bit(0, &timer_alive);
tco_expect_close = 0;
return 0;
}
static ssize_t i810tco_write (struct file *file, const char *data,
static ssize_t i8xx_tco_write (struct file *file, const char *data,
size_t len, loff_t * ppos)
{
/* Can't seek (pwrite) on this device */
......@@ -226,7 +230,7 @@ static ssize_t i810tco_write (struct file *file, const char *data,
/* scan to see whether or not we got the magic character */
for (i = 0; i != len; i++) {
u8 c;
char c;
if(get_user(c, data+i))
return -EFAULT;
if (c == 'V')
......@@ -235,67 +239,113 @@ static ssize_t i810tco_write (struct file *file, const char *data,
}
/* someone wrote to us, we should reload the timer */
tco_timer_reload ();
tco_timer_keepalive ();
}
return len;
}
static int i810tco_ioctl (struct inode *inode, struct file *file,
static int i8xx_tco_ioctl (struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int new_margin, u_margin;
int options, retval = -EINVAL;
int new_options, retval = -EINVAL;
int new_heartbeat;
static struct watchdog_info ident = {
.options = WDIOF_SETTIMEOUT |
WDIOF_KEEPALIVEPING |
WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "i810 TCO timer",
.identity = TCO_MODULE_NAME,
};
switch (cmd) {
default:
return -ENOIOCTLCMD;
case WDIOC_GETSUPPORT:
if (copy_to_user
((struct watchdog_info *) arg, &ident, sizeof (ident)))
return -EFAULT;
return 0;
return copy_to_user((struct watchdog_info *) arg, &ident,
sizeof (ident)) ? -EFAULT : 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user (0, (int *) arg);
case WDIOC_KEEPALIVE:
tco_timer_keepalive ();
return 0;
case WDIOC_SETOPTIONS:
if (get_user (options, (int *) arg))
{
if (get_user (new_options, (int *) arg))
return -EFAULT;
if (options & WDIOS_DISABLECARD) {
if (new_options & WDIOS_DISABLECARD) {
tco_timer_stop ();
retval = 0;
}
if (options & WDIOS_ENABLECARD) {
tco_timer_reload ();
if (new_options & WDIOS_ENABLECARD) {
tco_timer_keepalive ();
tco_timer_start ();
retval = 0;
}
return retval;
case WDIOC_KEEPALIVE:
tco_timer_reload ();
return 0;
}
case WDIOC_SETTIMEOUT:
if (get_user (u_margin, (int *) arg))
{
if (get_user(new_heartbeat, (int *) arg))
return -EFAULT;
new_margin = (u_margin * 10 + 5) / 6;
if ((new_margin < 4) || (new_margin > 63))
return -EINVAL;
if (tco_timer_settimer ((unsigned char) new_margin))
if (tco_timer_set_heartbeat(new_heartbeat))
return -EINVAL;
i810_margin = new_margin;
tco_timer_reload ();
tco_timer_keepalive ();
/* Fall */
}
case WDIOC_GETTIMEOUT:
return put_user ((int)(i810_margin * 6 / 10), (int *) arg);
return put_user(heartbeat, (int *)arg);
default:
return -ENOIOCTLCMD;
}
}
/*
* Notify system
*/
static int i8xx_tco_notify_sys (struct notifier_block *this, unsigned long code, void *unused)
{
if (code==SYS_DOWN || code==SYS_HALT) {
/* Turn the WDT off */
tco_timer_stop ();
}
return NOTIFY_DONE;
}
/*
* Kernel Interfaces
*/
static struct file_operations i8xx_tco_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.write = i8xx_tco_write,
.ioctl = i8xx_tco_ioctl,
.open = i8xx_tco_open,
.release = i8xx_tco_release,
};
static struct miscdevice i8xx_tco_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &i8xx_tco_fops,
};
static struct notifier_block i8xx_tco_notifier = {
.notifier_call = i8xx_tco_notify_sys,
};
/*
* Data for PCI driver interface
*
......@@ -304,7 +354,7 @@ static int i810tco_ioctl (struct inode *inode, struct file *file,
* register a pci_driver, because someone else might one day
* want to register another driver on the same PCI id.
*/
static struct pci_device_id i810tco_pci_tbl[] = {
static struct pci_device_id i8xx_tco_pci_tbl[] = {
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AA_0, PCI_ANY_ID, PCI_ANY_ID, },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801AB_0, PCI_ANY_ID, PCI_ANY_ID, },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_0, PCI_ANY_ID, PCI_ANY_ID, },
......@@ -314,13 +364,15 @@ static struct pci_device_id i810tco_pci_tbl[] = {
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, PCI_ANY_ID, PCI_ANY_ID, },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801E_0, PCI_ANY_ID, PCI_ANY_ID, },
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801EB_0, PCI_ANY_ID, PCI_ANY_ID, },
{ 0, },
{ 0, }, /* End of list */
};
MODULE_DEVICE_TABLE (pci, i810tco_pci_tbl);
MODULE_DEVICE_TABLE (pci, i8xx_tco_pci_tbl);
static struct pci_dev *i810tco_pci;
/*
* Init & exit routines
*/
static unsigned char __init i810tco_getdevice (void)
static unsigned char __init i8xx_tco_getdevice (void)
{
struct pci_dev *dev = NULL;
u8 val1, val2;
......@@ -330,37 +382,37 @@ static unsigned char __init i810tco_getdevice (void)
*/
while ((dev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) {
if (pci_match_device(i810tco_pci_tbl, dev)) {
i810tco_pci = dev;
if (pci_match_device(i8xx_tco_pci_tbl, dev)) {
i8xx_tco_pci = dev;
break;
}
}
if (i810tco_pci) {
if (i8xx_tco_pci) {
/*
* Find the ACPI base I/O address which is the base
* for the TCO registers (TCOBASE=ACPIBASE + 0x60)
* ACPIBASE is bits [15:7] from 0x40-0x43
*/
pci_read_config_byte (i810tco_pci, 0x40, &val1);
pci_read_config_byte (i810tco_pci, 0x41, &val2);
pci_read_config_byte (i8xx_tco_pci, 0x40, &val1);
pci_read_config_byte (i8xx_tco_pci, 0x41, &val2);
badr = ((val2 << 1) | (val1 >> 7)) << 7;
ACPIBASE = badr;
/* Something's wrong here, ACPIBASE has to be set */
if (badr == 0x0001 || badr == 0x0000) {
printk (KERN_ERR TCO_MODULE_NAME " init: failed to get TCOBASE address\n");
printk (KERN_ERR PFX "failed to get TCOBASE address\n");
return 0;
}
/*
* Check chipset's NO_REBOOT bit
*/
pci_read_config_byte (i810tco_pci, 0xd4, &val1);
pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1);
if (val1 & 0x02) {
val1 &= 0xfd;
pci_write_config_byte (i810tco_pci, 0xd4, val1);
pci_read_config_byte (i810tco_pci, 0xd4, &val1);
pci_write_config_byte (i8xx_tco_pci, 0xd4, val1);
pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1);
if (val1 & 0x02) {
printk (KERN_ERR TCO_MODULE_NAME " init: failed to reset NO_REBOOT flag, reboot disabled by hardware\n");
printk (KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n");
return 0; /* Cannot reset NO_REBOOT bit */
}
}
......@@ -376,57 +428,77 @@ static unsigned char __init i810tco_getdevice (void)
return 0;
}
static struct file_operations i810tco_fops = {
.owner = THIS_MODULE,
.write = i810tco_write,
.ioctl = i810tco_ioctl,
.open = i810tco_open,
.release = i810tco_release,
};
static struct miscdevice i810tco_miscdev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &i810tco_fops,
};
static int __init watchdog_init (void)
{
int ret;
spin_lock_init(&tco_lock);
if (!i810tco_getdevice () || i810tco_pci == NULL)
/* Check whether or not the hardware watchdog is there */
if (!i8xx_tco_getdevice () || i8xx_tco_pci == NULL)
return -ENODEV;
if (!request_region (TCOBASE, 0x10, "i810 TCO")) {
printk (KERN_ERR TCO_MODULE_NAME
": I/O address 0x%04x already in use\n",
if (!request_region (TCOBASE, 0x10, "i8xx TCO")) {
printk (KERN_ERR PFX "I/O address 0x%04x already in use\n",
TCOBASE);
return -EIO;
ret = -EIO;
goto out;
}
/* Check that the heartbeat value is within it's range ; if not reset to the default */
if (tco_timer_set_heartbeat (heartbeat)) {
heartbeat = WATCHDOG_HEARTBEAT;
tco_timer_set_heartbeat (heartbeat);
printk(KERN_INFO PFX "heartbeat value must be 2<heartbeat<39, using %d\n",
heartbeat);
}
if (misc_register (&i810tco_miscdev) != 0) {
release_region (TCOBASE, 0x10);
printk (KERN_ERR TCO_MODULE_NAME ": cannot register miscdev\n");
return -EIO;
ret = register_reboot_notifier(&i8xx_tco_notifier);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n",
ret);
goto unreg_region;
}
tco_timer_settimer ((unsigned char) i810_margin);
tco_timer_reload ();
printk (KERN_INFO TCO_DRIVER_NAME
": timer margin: %d sec (0x%04x) (nowayout=%d)\n",
(int) (i810_margin * 6 / 10), TCOBASE, nowayout);
ret = misc_register(&i8xx_tco_miscdev);
if (ret != 0) {
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n",
WATCHDOG_MINOR, ret);
goto unreg_notifier;
}
tco_timer_keepalive ();
printk (KERN_INFO PFX "initialized (0x%04x). heartbeat=%d sec (nowayout=%d)\n",
TCOBASE, heartbeat, nowayout);
return 0;
unreg_notifier:
unregister_reboot_notifier(&i8xx_tco_notifier);
unreg_region:
release_region (TCOBASE, 0x10);
out:
return ret;
}
static void __exit watchdog_cleanup (void)
{
u8 val;
/* Reset the timer before we leave */
tco_timer_reload ();
/* Stop the timer before we leave */
if (!nowayout)
tco_timer_stop ();
/* Set the NO_REBOOT bit to prevent later reboots, just for sure */
pci_read_config_byte (i810tco_pci, 0xd4, &val);
pci_read_config_byte (i8xx_tco_pci, 0xd4, &val);
val |= 0x02;
pci_write_config_byte (i810tco_pci, 0xd4, val);
pci_write_config_byte (i8xx_tco_pci, 0xd4, val);
/* Deregister */
misc_deregister (&i8xx_tco_miscdev);
unregister_reboot_notifier(&i8xx_tco_notifier);
release_region (TCOBASE, 0x10);
misc_deregister (&i810tco_miscdev);
}
module_init(watchdog_init);
......@@ -435,3 +507,4 @@ module_exit(watchdog_cleanup);
MODULE_AUTHOR("Nils Faerber");
MODULE_DESCRIPTION("TCO timer driver for i8xx chipsets");
MODULE_LICENSE("GPL");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
/*
* i810-tco: TCO timer driver for i8xx chipsets
* i8xx_tco: TCO timer driver for i8xx chipsets
*
* (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved.
* http://www.kernelconcepts.de
......@@ -21,12 +21,12 @@
* based on softdog.c by Alan Cox <alan@redhat.com>
*
* For history and the complete list of supported I/O Controller Hub's
* see i810-tco.c
* see i8xx_tco.c
*/
/*
* Some address definitions for the i810 TCO
* Some address definitions for the TCO
*/
#define TCOBASE ACPIBASE + 0x60 /* TCO base address */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment