diff options
Diffstat (limited to 'drivers/char/watchdog')
36 files changed, 14255 insertions, 0 deletions
diff --git a/drivers/char/watchdog/Kconfig b/drivers/char/watchdog/Kconfig new file mode 100644 index 000000000000..06a31da2381c --- /dev/null +++ b/drivers/char/watchdog/Kconfig @@ -0,0 +1,549 @@ +# +# Watchdog device configuration +# + +menu "Watchdog Cards" + +config WATCHDOG + bool "Watchdog Timer Support" + ---help--- + If you say Y here (and to one of the following options) and create a + character special file /dev/watchdog with major number 10 and minor + number 130 using mknod ("man mknod"), you will get a watchdog, i.e.: + subsequently opening the file and then failing to write to it for + longer than 1 minute will result in rebooting the machine. This + could be useful for a networked machine that needs to come back + online as fast as possible after a lock-up. There's both a watchdog + implementation entirely in software (which can sometimes fail to + reboot the machine) and a driver for hardware watchdog boards, which + are more robust and can also keep track of the temperature inside + your computer. For details, read <file:Documentation/watchdog/watchdog.txt> + in the kernel source. + + The watchdog is usually used together with the watchdog daemon + which is available from + <ftp://ibiblio.org/pub/Linux/system/daemons/watchdog/>. This daemon can + also monitor NFS connections and can reboot the machine when the process + table is full. + + If unsure, say N. + +config WATCHDOG_NOWAYOUT + bool "Disable watchdog shutdown on close" + depends on WATCHDOG + help + The default watchdog behaviour (which you get if you say N here) is + to stop the timer if the process managing it closes the file + /dev/watchdog. It's always remotely possible that this process might + get killed. If you say Y here, the watchdog cannot be stopped once + it has been started. + +# +# General Watchdog drivers +# + +comment "Watchdog Device Drivers" + depends on WATCHDOG + +# Architecture Independant + +config SOFT_WATCHDOG + tristate "Software watchdog" + depends on WATCHDOG + help + A software monitoring watchdog. This will fail to reboot your system + from some situations that the hardware watchdog will recover + from. Equally it's a lot cheaper to install. + + To compile this driver as a module, choose M here: the + module will be called softdog. + +# ARM Architecture + +config 21285_WATCHDOG + tristate "DC21285 watchdog" + depends on WATCHDOG && FOOTBRIDGE + help + The Intel Footbridge chip contains a builtin watchdog circuit. Say Y + here if you wish to use this. Alternatively say M to compile the + driver as a module, which will be called wdt285. + + This driver does not work on all machines. In particular, early CATS + boards have hardware problems that will cause the machine to simply + lock up if the watchdog fires. + + "If in doubt, leave it out" - say N. + +config 977_WATCHDOG + tristate "NetWinder WB83C977 watchdog" + depends on WATCHDOG && FOOTBRIDGE && ARCH_NETWINDER + help + Say Y here to include support for the WB977 watchdog included in + NetWinder machines. Alternatively say M to compile the driver as + a module, which will be called wdt977. + + Not sure? It's safe to say N. + +config IXP4XX_WATCHDOG + tristate "IXP4xx Watchdog" + depends on WATCHDOG && ARCH_IXP4XX + help + Say Y here if to include support for the watchdog timer + in the Intel IXP4xx network processors. This driver can + be built as a module by choosing M. The module will + be called ixp4xx_wdt. + + Note: The internal IXP4xx watchdog does a soft CPU reset + which doesn't reset any peripherals. There are circumstances + where the watchdog will fail to reset the board correctly + (e.g., if the boot ROM is in an unreadable state). + + Say N if you are unsure. + +config IXP2000_WATCHDOG + tristate "IXP2000 Watchdog" + depends on WATCHDOG && ARCH_IXP2000 + help + Say Y here if to include support for the watchdog timer + in the Intel IXP2000(2400, 2800, 2850) network processors. + This driver can be built as a module by choosing M. The module + will be called ixp2000_wdt. + + Say N if you are unsure. + +config S3C2410_WATCHDOG + tristate "S3C2410 Watchdog" + depends on WATCHDOG && ARCH_S3C2410 + help + Watchdog timer block in the Samsung S3C2410 chips. This will + reboot the system when the timer expires with the watchdog + enabled. + + The driver is limited by the speed of the system's PCLK + signal, so with reasonbaly fast systems (PCLK around 50-66MHz) + then watchdog intervals of over approximately 20seconds are + unavailable. + + The driver can be built as a module by choosing M, and will + be called s3c2410_wdt + +config SA1100_WATCHDOG + tristate "SA1100/PXA2xx watchdog" + depends on WATCHDOG && ( ARCH_SA1100 || ARCH_PXA ) + help + Watchdog timer embedded into SA11x0 and PXA2xx chips. This will + reboot your system when timeout is reached. + + NOTE: once enabled, this timer cannot be disabled. + + To compile this driver as a module, choose M here: the + module will be called sa1100_wdt. + +# X86 (i386 + ia64 + x86_64) Architecture + +config ACQUIRE_WDT + tristate "Acquire SBC Watchdog Timer" + depends on WATCHDOG && X86 + ---help--- + This is the driver for the hardware watchdog on Single Board + Computers produced by Acquire Inc (and others). This watchdog + simply watches your kernel to make sure it doesn't freeze, and if + it does, it reboots your computer after a certain amount of time. + + To compile this driver as a module, choose M here: the + module will be called acquirewdt. + + Most people will say N. + +config ADVANTECH_WDT + tristate "Advantech SBC Watchdog Timer" + depends on WATCHDOG && X86 + help + If you are configuring a Linux kernel for the Advantech single-board + computer, say `Y' here to support its built-in watchdog timer + feature. More information can be found at + <http://www.advantech.com.tw/products/> + +config ALIM1535_WDT + tristate "ALi M1535 PMU Watchdog Timer" + depends on WATCHDOG && X86 && PCI + ---help--- + This is the driver for the hardware watchdog on the ALi M1535 PMU. + + To compile this driver as a module, choose M here: the + module will be called alim1535_wdt. + + Most people will say N. + +config ALIM7101_WDT + tristate "ALi M7101 PMU Computer Watchdog" + depends on WATCHDOG && X86 && PCI + help + This is the driver for the hardware watchdog on the ALi M7101 PMU + as used in the x86 Cobalt servers. + + To compile this driver as a module, choose M here: the + module will be called alim7101_wdt. + + Most people will say N. + +config SC520_WDT + tristate "AMD Elan SC520 processor Watchdog" + depends on WATCHDOG && X86 + help + This is the driver for the hardware watchdog built in to the + AMD "Elan" SC520 microcomputer commonly used in embedded systems. + This watchdog simply watches your kernel to make sure it doesn't + freeze, and if it does, it reboots your computer after a certain + amount of time. + + You can compile this driver directly into the kernel, or use + it as a module. The module will be called sc520_wdt. + +config EUROTECH_WDT + tristate "Eurotech CPU-1220/1410 Watchdog Timer" + depends on WATCHDOG && X86 + help + Enable support for the watchdog timer on the Eurotech CPU-1220 and + CPU-1410 cards. These are PC/104 SBCs. Spec sheets and product + information are at <http://www.eurotech.it/>. + +config IB700_WDT + tristate "IB700 SBC Watchdog Timer" + depends on WATCHDOG && X86 + ---help--- + This is the driver for the hardware watchdog on the IB700 Single + Board Computer produced by TMC Technology (www.tmc-uk.com). This watchdog + simply watches your kernel to make sure it doesn't freeze, and if + it does, it reboots your computer after a certain amount of time. + + This driver is like the WDT501 driver but for slightly different hardware. + + To compile this driver as a module, choose M here: the + module will be called ib700wdt. + + Most people will say N. + +config WAFER_WDT + tristate "ICP Wafer 5823 Single Board Computer Watchdog" + depends on WATCHDOG && X86 + help + This is a driver for the hardware watchdog on the ICP Wafer 5823 + Single Board Computer (and probably other similar models). + + To compile this driver as a module, choose M here: the + module will be called wafer5823wdt. + +config I8XX_TCO + tristate "Intel i8xx TCO Timer/Watchdog" + depends on WATCHDOG && (X86 || IA64) && PCI + ---help--- + 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 + "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 i8xx_tco. + +config SC1200_WDT + tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog" + depends on WATCHDOG && X86 + help + This is a driver for National Semiconductor PC87307/PC97307 hardware + watchdog cards as found on the SC1200. This watchdog is mainly used + for power management purposes and can be used to power down the device + during inactivity periods (includes interrupt activity monitoring). + + To compile this driver as a module, choose M here: the + module will be called sc1200wdt. + + Most people will say N. + +config SCx200_WDT + tristate "National Semiconductor SCx200 Watchdog" + depends on WATCHDOG && SCx200 && PCI + help + Enable the built-in watchdog timer support on the National + Semiconductor SCx200 processors. + + If compiled as a module, it will be called scx200_wdt. + +config 60XX_WDT + tristate "SBC-60XX Watchdog Timer" + depends on WATCHDOG && X86 + help + This driver can be used with the watchdog timer found on some + single board computers, namely the 6010 PII based computer. + It may well work with other cards. It reads port 0x443 to enable + and re-set the watchdog timer, and reads port 0x45 to disable + the watchdog. If you have a card that behave in similar ways, + you can probably make this driver work with your card as well. + + You can compile this driver directly into the kernel, or use + it as a module. The module will be called sbc60xxwdt. + +config CPU5_WDT + tristate "SMA CPU5 Watchdog" + depends on WATCHDOG && X86 + ---help--- + TBD. + To compile this driver as a module, choose M here: the + module will be called cpu5wdt. + +config W83627HF_WDT + tristate "W83627HF Watchdog Timer" + depends on WATCHDOG && X86 + ---help--- + This is the driver for the hardware watchdog on the W83627HF chipset + as used in Advantech PC-9578 and Tyan S2721-533 motherboards + (and likely others). This watchdog simply watches your kernel to + make sure it doesn't freeze, and if it does, it reboots your computer + after a certain amount of time. + + To compile this driver as a module, choose M here: the + module will be called w83627hf_wdt. + + Most people will say N. + +config W83877F_WDT + tristate "W83877F (EMACS) Watchdog Timer" + depends on WATCHDOG && X86 + ---help--- + This is the driver for the hardware watchdog on the W83877F chipset + as used in EMACS PC-104 motherboards (and likely others). This + watchdog simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. + + To compile this driver as a module, choose M here: the + module will be called w83877f_wdt. + + Most people will say N. + +config MACHZ_WDT + tristate "ZF MachZ Watchdog" + depends on WATCHDOG && X86 + ---help--- + If you are using a ZF Micro MachZ processor, say Y here, otherwise + N. This is the driver for the watchdog timer builtin on that + processor using ZF-Logic interface. This watchdog simply watches + your kernel to make sure it doesn't freeze, and if it does, it + reboots your computer after a certain amount of time. + + To compile this driver as a module, choose M here: the + module will be called machzwd. + +# PowerPC Architecture + +config 8xx_WDT + tristate "MPC8xx Watchdog Timer" + depends on WATCHDOG && 8xx + +# MIPS Architecture + +config INDYDOG + tristate "Indy/I2 Hardware Watchdog" + depends on WATCHDOG && SGI_IP22 + help + Hardwaredriver for the Indy's/I2's watchdog. This is a + watchdog timer that will reboot the machine after a 60 second + timer expired and no process has written to /dev/watchdog during + that time. + +# S390 Architecture + +config ZVM_WATCHDOG + tristate "z/VM Watchdog Timer" + depends on WATCHDOG && ARCH_S390 + help + IBM s/390 and zSeries machines running under z/VM 5.1 or later + provide a virtual watchdog timer to their guest that cause a + user define Control Program command to be executed after a + timeout. + + To compile this driver as a module, choose M here. The module + will be called vmwatchdog. + +# SUPERH Architecture + +config SH_WDT + tristate "SuperH Watchdog" + depends on WATCHDOG && SUPERH + help + This driver adds watchdog support for the integrated watchdog in the + SuperH processors. If you have one of these processors and wish + to have watchdog support enabled, say Y, otherwise say N. + + As a side note, saying Y here will automatically boost HZ to 1000 + so that the timer has a chance to clear the overflow counter. On + slower systems (such as the SH-2 and SH-3) this will likely yield + some performance issues. As such, the WDT should be avoided here + unless it is absolutely necessary. + + To compile this driver as a module, choose M here: the + module will be called shwdt. + +# SPARC64 Architecture + +config WATCHDOG_CP1XXX + tristate "CP1XXX Hardware Watchdog support" + depends on WATCHDOG && SPARC64 && PCI + ---help--- + This is the driver for the hardware watchdog timers present on + Sun Microsystems CompactPCI models CP1400 and CP1500. + + To compile this driver as a module, choose M here: the + module will be called cpwatchdog. + + If you do not have a CompactPCI model CP1400 or CP1500, or + another UltraSPARC-IIi-cEngine boardset with hardware watchdog, + you should say N to this option. + +config WATCHDOG_RIO + tristate "RIO Hardware Watchdog support" + depends on WATCHDOG && SPARC64 && PCI + help + Say Y here to support the hardware watchdog capability on Sun RIO + machines. The watchdog timeout period is normally one minute but + can be changed with a boot-time parameter. + +# +# ISA-based Watchdog Cards +# + +comment "ISA-based Watchdog Cards" + depends on WATCHDOG && ISA + +config PCWATCHDOG + tristate "Berkshire Products ISA-PC Watchdog" + depends on WATCHDOG && ISA + ---help--- + This is the driver for the Berkshire Products ISA-PC Watchdog card. + This card simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. This driver is like the WDT501 driver but for different + hardware. Please read <file:Documentation/watchdog/pcwd-watchdog.txt>. The PC + watchdog cards can be ordered from <http://www.berkprod.com/>. + + To compile this driver as a module, choose M here: the + module will be called pcwd. + + Most people will say N. + +config MIXCOMWD + tristate "Mixcom Watchdog" + depends on WATCHDOG && ISA + ---help--- + This is a driver for the Mixcom hardware watchdog cards. This + watchdog simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. + + To compile this driver as a module, choose M here: the + module will be called mixcomwd. + + Most people will say N. + +config WDT + tristate "WDT Watchdog timer" + depends on WATCHDOG && ISA + ---help--- + If you have a WDT500P or WDT501P watchdog board, say Y here, + otherwise N. It is not possible to probe for this board, which means + that you have to inform the kernel about the IO port and IRQ that + is needed (you can do this via the io and irq parameters) + + To compile this driver as a module, choose M here: the + module will be called wdt. + +config WDT_501 + bool "WDT501 features" + depends on WDT + help + Saying Y here and creating a character special file /dev/temperature + with major number 10 and minor number 131 ("man mknod") will give + you a thermometer inside your computer: reading from + /dev/temperature yields one byte, the temperature in degrees + Fahrenheit. This works only if you have a WDT501P watchdog board + installed. + + If you want to enable the Fan Tachometer on the WDT501P, then you + can do this via the tachometer parameter. Only do this if you have a + fan tachometer actually set up. + +# +# PCI-based Watchdog Cards +# + +comment "PCI-based Watchdog Cards" + depends on WATCHDOG && PCI + +config PCIPCWATCHDOG + tristate "Berkshire Products PCI-PC Watchdog" + depends on WATCHDOG && PCI + ---help--- + This is the driver for the Berkshire Products PCI-PC Watchdog card. + This card simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. The card can also monitor the internal temperature of the PC. + More info is available at <http://www.berkprod.com/pci_pc_watchdog.htm>. + + To compile this driver as a module, choose M here: the + module will be called pcwd_pci. + + Most people will say N. + +config WDTPCI + tristate "PCI-WDT500/501 Watchdog timer" + depends on WATCHDOG && PCI + ---help--- + If you have a PCI-WDT500/501 watchdog board, say Y here, otherwise N. + + To compile this driver as a module, choose M here: the + module will be called wdt_pci. + +config WDT_501_PCI + bool "PCI-WDT501 features" + depends on WDTPCI + help + Saying Y here and creating a character special file /dev/temperature + with major number 10 and minor number 131 ("man mknod") will give + you a thermometer inside your computer: reading from + /dev/temperature yields one byte, the temperature in degrees + Fahrenheit. This works only if you have a PCI-WDT501 watchdog board + installed. + + If you want to enable the Fan Tachometer on the PCI-WDT501, then you + can do this via the tachometer parameter. Only do this if you have a + fan tachometer actually set up. + +# +# USB-based Watchdog Cards +# + +comment "USB-based Watchdog Cards" + depends on WATCHDOG && USB + +config USBPCWATCHDOG + tristate "Berkshire Products USB-PC Watchdog" + depends on WATCHDOG && USB + ---help--- + This is the driver for the Berkshire Products USB-PC Watchdog card. + This card simply watches your kernel to make sure it doesn't freeze, + and if it does, it reboots your computer after a certain amount of + time. The card can also monitor the internal temperature of the PC. + More info is available at <http://www.berkprod.com/usb_pc_watchdog.htm>. + + To compile this driver as a module, choose M here: the + module will be called pcwd_usb. + + Most people will say N. + +endmenu diff --git a/drivers/char/watchdog/Makefile b/drivers/char/watchdog/Makefile new file mode 100644 index 000000000000..1cd27efa35c1 --- /dev/null +++ b/drivers/char/watchdog/Makefile @@ -0,0 +1,42 @@ +# +# Makefile for the WatchDog device drivers. +# + +obj-$(CONFIG_PCWATCHDOG) += pcwd.o +obj-$(CONFIG_ACQUIRE_WDT) += acquirewdt.o +obj-$(CONFIG_ADVANTECH_WDT) += advantechwdt.o +obj-$(CONFIG_IB700_WDT) += ib700wdt.o +obj-$(CONFIG_MIXCOMWD) += mixcomwd.o +obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o +obj-$(CONFIG_60XX_WDT) += sbc60xxwdt.o +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_I8XX_TCO) += i8xx_tco.o +obj-$(CONFIG_MACHZ_WDT) += machzwd.o +obj-$(CONFIG_SH_WDT) += shwdt.o +obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o +obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o +obj-$(CONFIG_EUROTECH_WDT) += eurotechwdt.o +obj-$(CONFIG_W83877F_WDT) += w83877f_wdt.o +obj-$(CONFIG_W83627HF_WDT) += w83627hf_wdt.o +obj-$(CONFIG_SC520_WDT) += sc520_wdt.o +obj-$(CONFIG_ALIM7101_WDT) += alim7101_wdt.o +obj-$(CONFIG_ALIM1535_WDT) += alim1535_wdt.o +obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o +obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o +obj-$(CONFIG_CPU5_WDT) += cpu5wdt.o +obj-$(CONFIG_INDYDOG) += indydog.o +obj-$(CONFIG_PCIPCWATCHDOG) += pcwd_pci.o +obj-$(CONFIG_USBPCWATCHDOG) += pcwd_usb.o +obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o +obj-$(CONFIG_IXP2000_WATCHDOG) += ixp2000_wdt.o +obj-$(CONFIG_8xx_WDT) += mpc8xx_wdt.o + +# Only one watchdog can succeed. We probe the hardware watchdog +# drivers first, then the softdog driver. This means if your hardware +# watchdog dies or is 'borrowed' for some reason the software watchdog +# still gives you some cover. + +obj-$(CONFIG_SOFT_WATCHDOG) += softdog.o diff --git a/drivers/char/watchdog/acquirewdt.c b/drivers/char/watchdog/acquirewdt.c new file mode 100644 index 000000000000..8f302121741b --- /dev/null +++ b/drivers/char/watchdog/acquirewdt.c @@ -0,0 +1,332 @@ +/* + * Acquire Single Board Computer Watchdog Timer driver + * + * Based on wdt.c. Original copyright messages: + * + * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. + * http://www.redhat.com + * + * 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@redhat.com> + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * Can't add timeout - driver doesn't allow changing value + */ + +/* + * Theory of Operation: + * The Watch-Dog Timer is provided to ensure that standalone + * Systems can always recover from catastrophic conditions that + * caused the CPU to crash. This condition may have occured by + * external EMI or a software bug. When the CPU stops working + * correctly, hardware on the board will either perform a hardware + * reset (cold boot) or a non-maskable interrupt (NMI) to bring the + * system back to a known state. + * + * The Watch-Dog Timer is controlled by two I/O Ports. + * 443 hex - Read - Enable or refresh the Watch-Dog Timer + * 043 hex - Read - Disable the Watch-Dog Timer + * + * To enable the Watch-Dog Timer, a read from I/O port 443h must + * be performed. This will enable and activate the countdown timer + * which will eventually time out and either reset the CPU or cause + * an NMI depending on the setting of a jumper. To ensure that this + * reset condition does not occur, the Watch-Dog Timer must be + * periodically refreshed by reading the same I/O port 443h. + * The Watch-Dog Timer is disabled by reading I/O port 043h. + * + * The Watch-Dog Timer Time-Out Period is set via jumpers. + * It can be 1, 2, 10, 20, 110 or 220 seconds. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#define WATCHDOG_NAME "Acquire WDT" +#define PFX WATCHDOG_NAME ": " +#define WATCHDOG_HEARTBEAT 0 /* There is no way to see what the correct time-out period is */ + +static unsigned long acq_is_open; +static char expect_close; + +/* + * You must set these - there is no sane way to probe for this board. + */ + +static int wdt_stop = 0x43; +module_param(wdt_stop, int, 0); +MODULE_PARM_DESC(wdt_stop, "Acquire WDT 'stop' io port (default 0x43)"); + +static int wdt_start = 0x443; +module_param(wdt_start, int, 0); +MODULE_PARM_DESC(wdt_start, "Acquire WDT 'start' io port (default 0x443)"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * Kernel methods. + */ + +static void acq_keepalive(void) +{ + /* Write a watchdog value */ + inb_p(wdt_start); +} + +static void acq_stop(void) +{ + /* Turn the card off */ + inb_p(wdt_stop); +} + +/* + * /dev/watchdog handling. + */ + +static ssize_t acq_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if(count) { + if (!nowayout) { + size_t i; + + /* note: just in case someone wrote the magic character + * five months ago... */ + expect_close = 0; + + /* scan to see whether or not we got the magic character */ + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + + /* Well, anyhow someone wrote to us, we should return that favour */ + acq_keepalive(); + } + return count; +} + +static int acq_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + int options, retval = -EINVAL; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = + { + .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "Acquire WDT", + }; + + 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: + acq_keepalive(); + return 0; + + case WDIOC_GETTIMEOUT: + return put_user(WATCHDOG_HEARTBEAT, p); + + case WDIOC_SETOPTIONS: + { + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) + { + acq_stop(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) + { + acq_keepalive(); + retval = 0; + } + + return retval; + } + + default: + return -ENOIOCTLCMD; + } +} + +static int acq_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &acq_is_open)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + /* Activate */ + acq_keepalive(); + return nonseekable_open(inode, file); +} + +static int acq_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + acq_stop(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + acq_keepalive(); + } + clear_bit(0, &acq_is_open); + expect_close = 0; + return 0; +} + +/* + * Notifier for system down + */ + +static int acq_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if(code==SYS_DOWN || code==SYS_HALT) { + /* Turn the WDT off */ + acq_stop(); + } + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations acq_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = acq_write, + .ioctl = acq_ioctl, + .open = acq_open, + .release = acq_close, +}; + +static struct miscdevice acq_miscdev= +{ + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &acq_fops, +}; + +/* + * The WDT card needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block acq_notifier = +{ + .notifier_call = acq_notify_sys, +}; + +static int __init acq_init(void) +{ + int ret; + + printk(KERN_INFO "WDT driver for Acquire single board computer initialising.\n"); + + if (wdt_stop != wdt_start) { + if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) { + printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", + wdt_stop); + ret = -EIO; + goto out; + } + } + + if (!request_region(wdt_start, 1, WATCHDOG_NAME)) { + printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", + wdt_start); + ret = -EIO; + goto unreg_stop; + } + + ret = register_reboot_notifier(&acq_notifier); + if (ret != 0) { + printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + ret); + goto unreg_regions; + } + + ret = misc_register(&acq_miscdev); + if (ret != 0) { + printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto unreg_reboot; + } + + printk (KERN_INFO PFX "initialized. (nowayout=%d)\n", + nowayout); + + return 0; + +unreg_reboot: + unregister_reboot_notifier(&acq_notifier); +unreg_regions: + release_region(wdt_start, 1); +unreg_stop: + if (wdt_stop != wdt_start) + release_region(wdt_stop, 1); +out: + return ret; +} + +static void __exit acq_exit(void) +{ + misc_deregister(&acq_miscdev); + unregister_reboot_notifier(&acq_notifier); + if(wdt_stop != wdt_start) + release_region(wdt_stop,1); + release_region(wdt_start,1); +} + +module_init(acq_init); +module_exit(acq_exit); + +MODULE_AUTHOR("David Woodhouse"); +MODULE_DESCRIPTION("Acquire Inc. Single Board Computer Watchdog Timer driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/advantechwdt.c b/drivers/char/watchdog/advantechwdt.c new file mode 100644 index 000000000000..ea73c8379bdd --- /dev/null +++ b/drivers/char/watchdog/advantechwdt.c @@ -0,0 +1,333 @@ +/* + * Advantech Single Board Computer WDT driver + * + * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> + * + * Based on acquirewdt.c which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. + * http://www.redhat.com + * + * 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@redhat.com> + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * + * 16-Oct-2002 Rob Radez <rob@osinvestor.com> + * Clean up ioctls, clean up init + exit, add expect close support, + * add wdt_start and wdt_stop as parameters. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#define WATCHDOG_NAME "Advantech WDT" +#define PFX WATCHDOG_NAME ": " +#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ + +static unsigned long advwdt_is_open; +static char adv_expect_close; + +/* + * You must set these - there is no sane way to probe for this board. + * + * To enable or restart, write the timeout value in seconds (1 to 63) + * to I/O port wdt_start. To disable, read I/O port wdt_stop. + * Both are 0x443 for most boards (tested on a PCA-6276VE-00B1), but + * check your manual (at least the PCA-6159 seems to be different - + * the manual says wdt_stop is 0x43, not 0x443). + * (0x43 is also a write-only control register for the 8254 timer!) + */ + +static int wdt_stop = 0x443; +module_param(wdt_stop, int, 0); +MODULE_PARM_DESC(wdt_stop, "Advantech WDT 'stop' io port (default 0x443)"); + +static int wdt_start = 0x443; +module_param(wdt_start, int, 0); +MODULE_PARM_DESC(wdt_start, "Advantech WDT 'start' io port (default 0x443)"); + +static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=63, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) "."); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * Kernel methods. + */ + +static void +advwdt_ping(void) +{ + /* Write a watchdog value */ + outb_p(timeout, wdt_start); +} + +static void +advwdt_disable(void) +{ + inb_p(wdt_stop); +} + +static ssize_t +advwdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + adv_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf+i)) + return -EFAULT; + if (c == 'V') + adv_expect_close = 42; + } + } + advwdt_ping(); + } + return count; +} + +static int +advwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + int new_timeout; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "Advantech WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + advwdt_ping(); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, p)) + return -EFAULT; + if ((new_timeout < 1) || (new_timeout > 63)) + return -EINVAL; + timeout = new_timeout; + advwdt_ping(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + advwdt_disable(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + advwdt_ping(); + retval = 0; + } + + return retval; + } + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int +advwdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &advwdt_is_open)) + return -EBUSY; + /* + * Activate + */ + + advwdt_ping(); + return nonseekable_open(inode, file); +} + +static int +advwdt_close(struct inode *inode, struct file *file) +{ + if (adv_expect_close == 42) { + advwdt_disable(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + advwdt_ping(); + } + clear_bit(0, &advwdt_is_open); + adv_expect_close = 0; + return 0; +} + +/* + * Notifier for system down + */ + +static int +advwdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) { + /* Turn the WDT off */ + advwdt_disable(); + } + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations advwdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = advwdt_write, + .ioctl = advwdt_ioctl, + .open = advwdt_open, + .release = advwdt_close, +}; + +static struct miscdevice advwdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &advwdt_fops, +}; + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block advwdt_notifier = { + .notifier_call = advwdt_notify_sys, +}; + +static int __init +advwdt_init(void) +{ + int ret; + + printk(KERN_INFO "WDT driver for Advantech single board computer initialising.\n"); + + if (timeout < 1 || timeout > 63) { + timeout = WATCHDOG_TIMEOUT; + printk (KERN_INFO PFX "timeout value must be 1<=x<=63, using %d\n", + timeout); + } + + if (wdt_stop != wdt_start) { + if (!request_region(wdt_stop, 1, WATCHDOG_NAME)) { + printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", + wdt_stop); + ret = -EIO; + goto out; + } + } + + if (!request_region(wdt_start, 1, WATCHDOG_NAME)) { + printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", + wdt_start); + ret = -EIO; + goto unreg_stop; + } + + ret = register_reboot_notifier(&advwdt_notifier); + if (ret != 0) { + printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + ret); + goto unreg_regions; + } + + ret = misc_register(&advwdt_miscdev); + if (ret != 0) { + printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto unreg_reboot; + } + + printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + +out: + return ret; +unreg_reboot: + unregister_reboot_notifier(&advwdt_notifier); +unreg_regions: + release_region(wdt_start, 1); +unreg_stop: + if (wdt_stop != wdt_start) + release_region(wdt_stop, 1); + goto out; +} + +static void __exit +advwdt_exit(void) +{ + misc_deregister(&advwdt_miscdev); + unregister_reboot_notifier(&advwdt_notifier); + if(wdt_stop != wdt_start) + release_region(wdt_stop,1); + release_region(wdt_start,1); +} + +module_init(advwdt_init); +module_exit(advwdt_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Marek Michalkiewicz <marekm@linux.org.pl>"); +MODULE_DESCRIPTION("Advantech Single Board Computer WDT driver"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/alim1535_wdt.c b/drivers/char/watchdog/alim1535_wdt.c new file mode 100644 index 000000000000..35dcbf8be7d1 --- /dev/null +++ b/drivers/char/watchdog/alim1535_wdt.c @@ -0,0 +1,463 @@ +/* + * Watchdog for the 7101 PMU version found in the ALi M1535 chipsets + * + * 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. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/pci.h> + +#include <asm/uaccess.h> +#include <asm/io.h> + +#define WATCHDOG_NAME "ALi_M1535" +#define PFX WATCHDOG_NAME ": " +#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ + +/* internal variables */ +static unsigned long ali_is_open; +static char ali_expect_release; +static struct pci_dev *ali_pci; +static u32 ali_timeout_bits; /* stores the computed timeout */ +static spinlock_t ali_lock; /* Guards the hardware */ + +/* module parameters */ +static int timeout = WATCHDOG_TIMEOUT; +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (0<timeout<18000, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * ali_start - start watchdog countdown + * + * Starts the timer running providing the timer has a counter + * configuration set. + */ + +static void ali_start(void) +{ + u32 val; + + spin_lock(&ali_lock); + + pci_read_config_dword(ali_pci, 0xCC, &val); + val &= ~0x3F; /* Mask count */ + val |= (1<<25) | ali_timeout_bits; + pci_write_config_dword(ali_pci, 0xCC, val); + + spin_unlock(&ali_lock); +} + +/* + * ali_stop - stop the timer countdown + * + * Stop the ALi watchdog countdown + */ + +static void ali_stop(void) +{ + u32 val; + + spin_lock(&ali_lock); + + pci_read_config_dword(ali_pci, 0xCC, &val); + val &= ~0x3F; /* Mask count to zero (disabled) */ + val &= ~(1<<25);/* and for safety mask the reset enable */ + pci_write_config_dword(ali_pci, 0xCC, val); + + spin_unlock(&ali_lock); +} + +/* + * ali_keepalive - send a keepalive to the watchdog + * + * Send a keepalive to the timer (actually we restart the timer). + */ + +static void ali_keepalive(void) +{ + ali_start(); +} + +/* + * ali_settimer - compute the timer reload value + * @t: time in seconds + * + * Computes the timeout values needed + */ + +static int ali_settimer(int t) +{ + if(t < 0) + return -EINVAL; + else if(t < 60) + ali_timeout_bits = t|(1<<6); + else if(t < 3600) + ali_timeout_bits = (t/60)|(1<<7); + else if(t < 18000) + ali_timeout_bits = (t/300)|(1<<6)|(1<<7); + else return -EINVAL; + + timeout = t; + return 0; +} + +/* + * /dev/watchdog handling + */ + +/* + * ali_write - writes to ALi watchdog + * @file: file from VFS + * @data: user address of data + * @len: length of data + * @ppos: pointer to the file offset + * + * Handle a write to the ALi watchdog. Writing to the file pings + * the watchdog and resets it. Writing the magic 'V' sequence allows + * the next close to turn off the watchdog. + */ + +static ssize_t ali_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... */ + ali_expect_release = 0; + + /* scan to see whether or not we got the magic character */ + for (i = 0; i != len; i++) { + char c; + if(get_user(c, data+i)) + return -EFAULT; + if (c == 'V') + ali_expect_release = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + ali_start(); + } + return len; +} + +/* + * ali_ioctl - handle watchdog ioctls + * @inode: VFS inode + * @file: VFS file pointer + * @cmd: ioctl number + * @arg: arguments to the ioctl + * + * Handle the watchdog ioctls supported by the ALi driver. Really + * we want an extension to enable irq ack monitoring and the like + */ + +static int ali_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = "ALi M1535 WatchDog Timer", + }; + + 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: + ali_keepalive(); + return 0; + + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if (get_user (new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + ali_stop(); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + ali_start(); + retval = 0; + } + + return retval; + } + + case WDIOC_SETTIMEOUT: + { + int new_timeout; + + if (get_user(new_timeout, p)) + return -EFAULT; + + if (ali_settimer(new_timeout)) + return -EINVAL; + + ali_keepalive(); + /* Fall */ + } + + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + + default: + return -ENOIOCTLCMD; + } +} + +/* + * ali_open - handle open of ali watchdog + * @inode: inode from VFS + * @file: file from VFS + * + * Open the ALi watchdog device. Ensure only one person opens it + * at a time. Also start the watchdog running. + */ + +static int ali_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &ali_is_open)) + return -EBUSY; + + /* Activate */ + ali_start(); + return nonseekable_open(inode, file); +} + +/* + * ali_release - close an ALi watchdog + * @inode: inode from VFS + * @file: file from VFS + * + * Close the ALi watchdog device. Actual shutdown of the timer + * only occurs if the magic sequence has been set. + */ + +static int ali_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + */ + if (ali_expect_release == 42) { + ali_stop(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + ali_keepalive(); + } + clear_bit(0, &ali_is_open); + ali_expect_release = 0; + return 0; +} + +/* + * ali_notify_sys - System down notifier + * + * Notifier for system down + */ + + +static int ali_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +{ + if (code==SYS_DOWN || code==SYS_HALT) { + /* Turn the WDT off */ + ali_stop(); + } + + return NOTIFY_DONE; +} + +/* + * Data for PCI driver interface + * + * This data only exists for exporting the supported + * PCI ids via MODULE_DEVICE_TABLE. We do not actually + * 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 ali_pci_tbl[] = { + { PCI_VENDOR_ID_AL, 1535, PCI_ANY_ID, PCI_ANY_ID,}, + { 0, }, +}; +MODULE_DEVICE_TABLE(pci, ali_pci_tbl); + +/* + * ali_find_watchdog - find a 1535 and 7101 + * + * Scans the PCI hardware for a 1535 series bridge and matching 7101 + * watchdog device. This may be overtight but it is better to be safe + */ + +static int __init ali_find_watchdog(void) +{ + struct pci_dev *pdev; + u32 wdog; + + /* Check for a 1535 series bridge */ + pdev = pci_find_device(PCI_VENDOR_ID_AL, 0x1535, NULL); + if(pdev == NULL) + return -ENODEV; + + /* Check for the a 7101 PMU */ + pdev = pci_find_device(PCI_VENDOR_ID_AL, 0x7101, NULL); + if(pdev == NULL) + return -ENODEV; + + if(pci_enable_device(pdev)) + return -EIO; + + ali_pci = pdev; + + /* + * Initialize the timer bits + */ + pci_read_config_dword(pdev, 0xCC, &wdog); + + wdog &= ~0x3F; /* Timer bits */ + wdog &= ~((1<<27)|(1<<26)|(1<<25)|(1<<24)); /* Issued events */ + wdog &= ~((1<<16)|(1<<13)|(1<<12)|(1<<11)|(1<<10)|(1<<9)); /* No monitor bits */ + + pci_write_config_dword(pdev, 0xCC, wdog); + + return 0; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations ali_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ali_write, + .ioctl = ali_ioctl, + .open = ali_open, + .release = ali_release, +}; + +static struct miscdevice ali_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &ali_fops, +}; + +static struct notifier_block ali_notifier = { + .notifier_call = ali_notify_sys, +}; + +/* + * watchdog_init - module initialiser + * + * Scan for a suitable watchdog and if so initialize it. Return an error + * if we cannot, the error causes the module to unload + */ + +static int __init watchdog_init(void) +{ + int ret; + + spin_lock_init(&ali_lock); + + /* Check whether or not the hardware watchdog is there */ + if (ali_find_watchdog() != 0) { + return -ENODEV; + } + + /* Check that the timeout value is within it's range ; if not reset to the default */ + if (timeout < 1 || timeout >= 18000) { + timeout = WATCHDOG_TIMEOUT; + printk(KERN_INFO PFX "timeout value must be 0<timeout<18000, using %d\n", + timeout); + } + + /* Calculate the watchdog's timeout */ + ali_settimer(timeout); + + ret = misc_register(&ali_miscdev); + if (ret != 0) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto out; + } + + ret = register_reboot_notifier(&ali_notifier); + if (ret != 0) { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + ret); + goto unreg_miscdev; + } + + printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + +out: + return ret; +unreg_miscdev: + misc_deregister(&ali_miscdev); + goto out; +} + +/* + * watchdog_exit - module de-initialiser + * + * Called while unloading a successfully installed watchdog module. + */ + +static void __exit watchdog_exit(void) +{ + /* Stop the timer before we leave */ + ali_stop(); + + /* Deregister */ + unregister_reboot_notifier(&ali_notifier); + misc_deregister(&ali_miscdev); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_AUTHOR("Alan Cox"); +MODULE_DESCRIPTION("ALi M1535 PMU Watchdog Timer driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/alim7101_wdt.c b/drivers/char/watchdog/alim7101_wdt.c new file mode 100644 index 000000000000..90c091d9e0f5 --- /dev/null +++ b/drivers/char/watchdog/alim7101_wdt.c @@ -0,0 +1,421 @@ +/* + * ALi M7101 PMU Computer Watchdog Timer driver + * + * Based on w83877f_wdt.c by Scott Jennings <linuxdrivers@oro.net> + * and the Cobalt kernel WDT timer driver by Tim Hockin + * <thockin@cobaltnet.com> + * + * (c)2002 Steve Hill <steve@navaho.co.uk> + * + * This WDT driver is different from most other Linux WDT + * drivers in that the driver will ping the watchdog by itself, + * because this particular WDT has a very short timeout (1.6 + * seconds) and it would be insane to count on any userspace + * daemon always getting scheduled within that time frame. + * + * Additions: + * Aug 23, 2004 - Added use_gpio module parameter for use on revision a1d PMUs + * found on very old cobalt hardware. + * -- Mike Waychison <michael.waychison@sun.com> + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/pci.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#define OUR_NAME "alim7101_wdt" +#define PFX OUR_NAME ": " + +#define WDT_ENABLE 0x9C +#define WDT_DISABLE 0x8C + +#define ALI_7101_WDT 0x92 +#define ALI_7101_GPIO 0x7D +#define ALI_7101_GPIO_O 0x7E +#define ALI_WDT_ARM 0x01 + +/* + * We're going to use a 1 second timeout. + * If we reset the watchdog every ~250ms we should be safe. */ + +#define WDT_INTERVAL (HZ/4+1) + +/* + * We must not require too good response from the userspace daemon. + * Here we require the userspace daemon to send us a heartbeat + * char to /dev/watchdog every 30 seconds. + */ + +#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ +static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +static int use_gpio = 0; /* Use the pic (for a1d revision alim7101) */ +module_param(use_gpio, int, 0); +MODULE_PARM_DESC(use_gpio, "Use the gpio watchdog. (required by old cobalt boards)"); + +static void wdt_timer_ping(unsigned long); +static struct timer_list timer; +static unsigned long next_heartbeat; +static unsigned long wdt_is_open; +static char wdt_expect_close; +static struct pci_dev *alim7101_pmu; + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * Whack the dog + */ + +static void wdt_timer_ping(unsigned long data) +{ + /* If we got a heartbeat pulse within the WDT_US_INTERVAL + * we agree to ping the WDT + */ + char tmp; + + if(time_before(jiffies, next_heartbeat)) + { + /* Ping the WDT (this is actually a disarm/arm sequence) */ + pci_read_config_byte(alim7101_pmu, 0x92, &tmp); + pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM)); + pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM)); + if (use_gpio) { + pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp); + pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp + | 0x20); + pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp + & ~0x20); + } + } else { + printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); + } + /* Re-set the timer interval */ + timer.expires = jiffies + WDT_INTERVAL; + add_timer(&timer); +} + +/* + * Utility routines + */ + +static void wdt_change(int writeval) +{ + char tmp; + + pci_read_config_byte(alim7101_pmu, ALI_7101_WDT, &tmp); + if (writeval == WDT_ENABLE) { + pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp | ALI_WDT_ARM)); + if (use_gpio) { + pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp); + pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp & ~0x20); + } + + } else { + pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, (tmp & ~ALI_WDT_ARM)); + if (use_gpio) { + pci_read_config_byte(alim7101_pmu, ALI_7101_GPIO_O, &tmp); + pci_write_config_byte(alim7101_pmu, ALI_7101_GPIO_O, tmp | 0x20); + } + } +} + +static void wdt_startup(void) +{ + next_heartbeat = jiffies + (timeout * HZ); + + /* We must enable before we kick off the timer in case the timer + occurs as we ping it */ + + wdt_change(WDT_ENABLE); + + /* Start the timer */ + timer.expires = jiffies + WDT_INTERVAL; + add_timer(&timer); + + + printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); +} + +static void wdt_turnoff(void) +{ + /* Stop the timer */ + del_timer_sync(&timer); + wdt_change(WDT_DISABLE); + printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); +} + +static void wdt_keepalive(void) +{ + /* user land ping */ + next_heartbeat = jiffies + (timeout * HZ); +} + +/* + * /dev/watchdog handling + */ + +static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if(count) { + if (!nowayout) { + size_t ofs; + + /* note: just in case someone wrote the magic character + * five months ago... */ + wdt_expect_close = 0; + + /* now scan */ + for (ofs = 0; ofs != count; ofs++) { + char c; + if (get_user(c, buf+ofs)) + return -EFAULT; + if (c == 'V') + wdt_expect_close = 42; + } + } + /* someone wrote to us, we should restart timer */ + wdt_keepalive(); + } + return count; +} + +static int fop_open(struct inode * inode, struct file * file) +{ + /* Just in case we're already talking to someone... */ + if(test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + /* Good, fire up the show */ + wdt_startup(); + return nonseekable_open(inode, file); +} + +static int fop_close(struct inode * inode, struct file * file) +{ + if(wdt_expect_close == 42) + wdt_turnoff(); + else { + /* wim: shouldn't there be a: del_timer(&timer); */ + printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); + } + clear_bit(0, &wdt_is_open); + wdt_expect_close = 0; + return 0; +} + +static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = + { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "ALiM7101", + }; + + 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: + wdt_keepalive(); + return 0; + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if(get_user(new_options, p)) + return -EFAULT; + + if(new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; + } + + if(new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; + } + + return retval; + } + case WDIOC_SETTIMEOUT: + { + int new_timeout; + + if(get_user(new_timeout, p)) + return -EFAULT; + + if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ + return -EINVAL; + + timeout = new_timeout; + wdt_keepalive(); + /* Fall through */ + } + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + default: + return -ENOIOCTLCMD; + } +} + +static struct file_operations wdt_fops = { + .owner= THIS_MODULE, + .llseek= no_llseek, + .write= fop_write, + .open= fop_open, + .release= fop_close, + .ioctl= fop_ioctl, +}; + +static struct miscdevice wdt_miscdev = { + .minor=WATCHDOG_MINOR, + .name="watchdog", + .fops=&wdt_fops, +}; + +/* + * Notifier for system down + */ + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +{ + if (code==SYS_DOWN || code==SYS_HALT) + wdt_turnoff(); + + if (code==SYS_RESTART) { + /* + * Cobalt devices have no way of rebooting themselves other than + * getting the watchdog to pull reset, so we restart the watchdog on + * reboot with no heartbeat + */ + wdt_change(WDT_ENABLE); + printk(KERN_INFO PFX "Watchdog timer is now enabled with no heartbeat - should reboot in ~1 second.\n"); + } + return NOTIFY_DONE; +} + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier= +{ + .notifier_call = wdt_notify_sys, +}; + +static void __exit alim7101_wdt_unload(void) +{ + wdt_turnoff(); + /* Deregister */ + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); +} + +static int __init alim7101_wdt_init(void) +{ + int rc = -EBUSY; + struct pci_dev *ali1543_south; + char tmp; + + printk(KERN_INFO PFX "Steve Hill <steve@navaho.co.uk>.\n"); + alim7101_pmu = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M7101,NULL); + if (!alim7101_pmu) { + printk(KERN_INFO PFX "ALi M7101 PMU not present - WDT not set\n"); + return -EBUSY; + } + + /* Set the WDT in the PMU to 1 second */ + pci_write_config_byte(alim7101_pmu, ALI_7101_WDT, 0x02); + + ali1543_south = pci_find_device(PCI_VENDOR_ID_AL, PCI_DEVICE_ID_AL_M1533, NULL); + if (!ali1543_south) { + printk(KERN_INFO PFX "ALi 1543 South-Bridge not present - WDT not set\n"); + return -EBUSY; + } + pci_read_config_byte(ali1543_south, 0x5e, &tmp); + if ((tmp & 0x1e) == 0x00) { + if (!use_gpio) { + printk(KERN_INFO PFX "Detected old alim7101 revision 'a1d'. If this is a cobalt board, set the 'use_gpio' module parameter.\n"); + return -EBUSY; + } + nowayout = 1; + } else if ((tmp & 0x1e) != 0x12 && (tmp & 0x1e) != 0x00) { + printk(KERN_INFO PFX "ALi 1543 South-Bridge does not have the correct revision number (???1001?) - WDT not set\n"); + return -EBUSY; + } + + if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */ + { + timeout = WATCHDOG_TIMEOUT; + printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", + timeout); + } + + init_timer(&timer); + timer.function = wdt_timer_ping; + timer.data = 1; + + rc = misc_register(&wdt_miscdev); + if (rc) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); + goto err_out; + } + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + rc); + goto err_out_miscdev; + } + + if (nowayout) { + __module_get(THIS_MODULE); + } + + printk(KERN_INFO PFX "WDT driver for ALi M7101 initialised. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + return 0; + +err_out_miscdev: + misc_deregister(&wdt_miscdev); +err_out: + return rc; +} + +module_init(alim7101_wdt_init); +module_exit(alim7101_wdt_unload); + +MODULE_AUTHOR("Steve Hill"); +MODULE_DESCRIPTION("ALi M7101 PMU Computer Watchdog Timer driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/cpu5wdt.c b/drivers/char/watchdog/cpu5wdt.c new file mode 100644 index 000000000000..2865dac0a813 --- /dev/null +++ b/drivers/char/watchdog/cpu5wdt.c @@ -0,0 +1,303 @@ +/* + * sma cpu5 watchdog driver + * + * Copyright (C) 2003 Heiko Ronsdorf <hero@ihg.uni-duisburg.de> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/errno.h> +#include <linux/miscdevice.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/ioport.h> +#include <linux/timer.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +#include <linux/watchdog.h> + +/* adjustable parameters */ + +static int verbose = 0; +static int port = 0x91; +static int ticks = 10000; + +#define PFX "cpu5wdt: " + +#define CPU5WDT_EXTENT 0x0A + +#define CPU5WDT_STATUS_REG 0x00 +#define CPU5WDT_TIME_A_REG 0x02 +#define CPU5WDT_TIME_B_REG 0x03 +#define CPU5WDT_MODE_REG 0x04 +#define CPU5WDT_TRIGGER_REG 0x07 +#define CPU5WDT_ENABLE_REG 0x08 +#define CPU5WDT_RESET_REG 0x09 + +#define CPU5WDT_INTERVAL (HZ/10+1) + +/* some device data */ + +static struct { + struct semaphore stop; + volatile int running; + struct timer_list timer; + volatile int queue; + int default_ticks; + unsigned long inuse; +} cpu5wdt_device; + +/* generic helper functions */ + +static void cpu5wdt_trigger(unsigned long unused) +{ + if ( verbose > 2 ) + printk(KERN_DEBUG PFX "trigger at %i ticks\n", ticks); + + if( cpu5wdt_device.running ) + ticks--; + + /* keep watchdog alive */ + outb(1, port + CPU5WDT_TRIGGER_REG); + + /* requeue?? */ + if( cpu5wdt_device.queue && ticks ) { + cpu5wdt_device.timer.expires = jiffies + CPU5WDT_INTERVAL; + add_timer(&cpu5wdt_device.timer); + } + else { + /* ticks doesn't matter anyway */ + up(&cpu5wdt_device.stop); + } + +} + +static void cpu5wdt_reset(void) +{ + ticks = cpu5wdt_device.default_ticks; + + if ( verbose ) + printk(KERN_DEBUG PFX "reset (%i ticks)\n", (int) ticks); + +} + +static void cpu5wdt_start(void) +{ + if ( !cpu5wdt_device.queue ) { + cpu5wdt_device.queue = 1; + outb(0, port + CPU5WDT_TIME_A_REG); + outb(0, port + CPU5WDT_TIME_B_REG); + outb(1, port + CPU5WDT_MODE_REG); + outb(0, port + CPU5WDT_RESET_REG); + outb(0, port + CPU5WDT_ENABLE_REG); + cpu5wdt_device.timer.expires = jiffies + CPU5WDT_INTERVAL; + add_timer(&cpu5wdt_device.timer); + } + /* if process dies, counter is not decremented */ + cpu5wdt_device.running++; +} + +static int cpu5wdt_stop(void) +{ + if ( cpu5wdt_device.running ) + cpu5wdt_device.running = 0; + + ticks = cpu5wdt_device.default_ticks; + + if ( verbose ) + printk(KERN_CRIT PFX "stop not possible\n"); + + return -EIO; +} + +/* filesystem operations */ + +static int cpu5wdt_open(struct inode *inode, struct file *file) +{ + if ( test_and_set_bit(0, &cpu5wdt_device.inuse) ) + return -EBUSY; + + return nonseekable_open(inode, file); +} + +static int cpu5wdt_release(struct inode *inode, struct file *file) +{ + clear_bit(0, &cpu5wdt_device.inuse); + return 0; +} + +static int cpu5wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + unsigned int value; + static struct watchdog_info ident = + { + .options = WDIOF_CARDRESET, + .identity = "CPU5 WDT", + }; + + switch(cmd) { + case WDIOC_KEEPALIVE: + cpu5wdt_reset(); + break; + case WDIOC_GETSTATUS: + value = inb(port + CPU5WDT_STATUS_REG); + value = (value >> 2) & 1; + if ( copy_to_user(argp, &value, sizeof(int)) ) + return -EFAULT; + break; + case WDIOC_GETSUPPORT: + if ( copy_to_user(argp, &ident, sizeof(ident)) ) + return -EFAULT; + break; + case WDIOC_SETOPTIONS: + if ( copy_from_user(&value, argp, sizeof(int)) ) + return -EFAULT; + switch(value) { + case WDIOS_ENABLECARD: + cpu5wdt_start(); + break; + case WDIOS_DISABLECARD: + return cpu5wdt_stop(); + default: + return -EINVAL; + } + break; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static ssize_t cpu5wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + if ( !count ) + return -EIO; + + cpu5wdt_reset(); + + return count; +} + +static struct file_operations cpu5wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .ioctl = cpu5wdt_ioctl, + .open = cpu5wdt_open, + .write = cpu5wdt_write, + .release = cpu5wdt_release, +}; + +static struct miscdevice cpu5wdt_misc = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &cpu5wdt_fops, +}; + +/* init/exit function */ + +static int __devinit cpu5wdt_init(void) +{ + unsigned int val; + int err; + + if ( verbose ) + printk(KERN_DEBUG PFX "port=0x%x, verbose=%i\n", port, verbose); + + if ( (err = misc_register(&cpu5wdt_misc)) < 0 ) { + printk(KERN_ERR PFX "misc_register failed\n"); + goto no_misc; + } + + if ( !request_region(port, CPU5WDT_EXTENT, PFX) ) { + printk(KERN_ERR PFX "request_region failed\n"); + err = -EBUSY; + goto no_port; + } + + /* watchdog reboot? */ + val = inb(port + CPU5WDT_STATUS_REG); + val = (val >> 2) & 1; + if ( !val ) + printk(KERN_INFO PFX "sorry, was my fault\n"); + + init_MUTEX_LOCKED(&cpu5wdt_device.stop); + cpu5wdt_device.queue = 0; + + clear_bit(0, &cpu5wdt_device.inuse); + + init_timer(&cpu5wdt_device.timer); + cpu5wdt_device.timer.function = cpu5wdt_trigger; + cpu5wdt_device.timer.data = 0; + + cpu5wdt_device.default_ticks = ticks; + + printk(KERN_INFO PFX "init success\n"); + + return 0; + +no_port: + misc_deregister(&cpu5wdt_misc); +no_misc: + return err; +} + +static int __devinit cpu5wdt_init_module(void) +{ + return cpu5wdt_init(); +} + +static void __devexit cpu5wdt_exit(void) +{ + if ( cpu5wdt_device.queue ) { + cpu5wdt_device.queue = 0; + down(&cpu5wdt_device.stop); + } + + misc_deregister(&cpu5wdt_misc); + + release_region(port, CPU5WDT_EXTENT); + +} + +static void __devexit cpu5wdt_exit_module(void) +{ + cpu5wdt_exit(); +} + +/* module entry points */ + +module_init(cpu5wdt_init_module); +module_exit(cpu5wdt_exit_module); + +MODULE_AUTHOR("Heiko Ronsdorf <hero@ihg.uni-duisburg.de>"); +MODULE_DESCRIPTION("sma cpu5 watchdog driver"); +MODULE_SUPPORTED_DEVICE("sma cpu5 watchdog"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +module_param(port, int, 0); +MODULE_PARM_DESC(port, "base address of watchdog card, default is 0x91"); + +module_param(verbose, int, 0); +MODULE_PARM_DESC(verbose, "be verbose, default is 0 (no)"); + +module_param(ticks, int, 0); +MODULE_PARM_DESC(ticks, "count down ticks, default is 10000"); diff --git a/drivers/char/watchdog/eurotechwdt.c b/drivers/char/watchdog/eurotechwdt.c new file mode 100644 index 000000000000..d10e554a14d6 --- /dev/null +++ b/drivers/char/watchdog/eurotechwdt.c @@ -0,0 +1,474 @@ +/* + * Eurotech CPU-1220/1410 on board WDT driver + * + * (c) Copyright 2001 Ascensit <support@ascensit.com> + * (c) Copyright 2001 Rodolfo Giometti <giometti@ascensit.com> + * (c) Copyright 2002 Rob Radez <rob@osinvestor.com> + * + * Based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. + * http://www.redhat.com + * + * 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>* + */ + +/* Changelog: + * + * 2002/04/25 - Rob Radez + * clean up #includes + * clean up locking + * make __setup param unique + * proper options in watchdog_info + * add WDIOC_GETSTATUS and WDIOC_SETOPTIONS ioctls + * add expect_close support + * + * 2001 - Rodolfo Giometti + * Initial release + * + * 2002.05.30 - Joel Becker <joel.becker@oracle.com> + * Added Matt Domsch's nowayout module option. + */ + +#include <linux/config.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +static unsigned long eurwdt_is_open; +static int eurwdt_timeout; +static char eur_expect_close; + +/* + * You must set these - there is no sane way to probe for this board. + * You can use eurwdt=x,y to set these now. + */ + +static int io = 0x3f0; +static int irq = 10; +static char *ev = "int"; + +#define WDT_TIMEOUT 60 /* 1 minute */ + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * Some symbolic names + */ + +#define WDT_CTRL_REG 0x30 +#define WDT_OUTPIN_CFG 0xe2 +#define WDT_EVENT_INT 0x00 +#define WDT_EVENT_REBOOT 0x08 +#define WDT_UNIT_SEL 0xf1 +#define WDT_UNIT_SECS 0x80 +#define WDT_TIMEOUT_VAL 0xf2 +#define WDT_TIMER_CFG 0xf3 + + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "Eurotech WDT io port (default=0x3f0)"); +module_param(irq, int, 0); +MODULE_PARM_DESC(irq, "Eurotech WDT irq (default=10)"); +module_param(ev, charp, 0); +MODULE_PARM_DESC(ev, "Eurotech WDT event type (default is `int')"); + + +/* + * Programming support + */ + +static inline void eurwdt_write_reg(u8 index, u8 data) +{ + outb(index, io); + outb(data, io+1); +} + +static inline void eurwdt_lock_chip(void) +{ + outb(0xaa, io); +} + +static inline void eurwdt_unlock_chip(void) +{ + outb(0x55, io); + eurwdt_write_reg(0x07, 0x08); /* set the logical device */ +} + +static inline void eurwdt_set_timeout(int timeout) +{ + eurwdt_write_reg(WDT_TIMEOUT_VAL, (u8) timeout); +} + +static inline void eurwdt_disable_timer(void) +{ + eurwdt_set_timeout(0); +} + +static void eurwdt_activate_timer(void) +{ + eurwdt_disable_timer(); + eurwdt_write_reg(WDT_CTRL_REG, 0x01); /* activate the WDT */ + eurwdt_write_reg(WDT_OUTPIN_CFG, !strcmp("int", ev) ? WDT_EVENT_INT : WDT_EVENT_REBOOT); + + /* Setting interrupt line */ + if (irq == 2 || irq > 15 || irq < 0) { + printk(KERN_ERR ": invalid irq number\n"); + irq = 0; /* if invalid we disable interrupt */ + } + if (irq == 0) + printk(KERN_INFO ": interrupt disabled\n"); + + eurwdt_write_reg(WDT_TIMER_CFG, irq<<4); + + eurwdt_write_reg(WDT_UNIT_SEL, WDT_UNIT_SECS); /* we use seconds */ + eurwdt_set_timeout(0); /* the default timeout */ +} + + +/* + * Kernel methods. + */ + +static irqreturn_t eurwdt_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + printk(KERN_CRIT "timeout WDT timeout\n"); + +#ifdef ONLY_TESTING + printk(KERN_CRIT "Would Reboot.\n"); +#else + printk(KERN_CRIT "Initiating system reboot.\n"); + machine_restart(NULL); +#endif + return IRQ_HANDLED; +} + + +/** + * eurwdt_ping: + * + * Reload counter one with the watchdog timeout. + */ + +static void eurwdt_ping(void) +{ + /* Write the watchdog default value */ + eurwdt_set_timeout(eurwdt_timeout); +} + +/** + * eurwdt_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 eurwdt_write(struct file *file, const char __user *buf, +size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + eur_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if(get_user(c, buf+i)) + return -EFAULT; + if (c == 'V') + eur_expect_close = 42; + } + } + eurwdt_ping(); /* the default timeout */ + } + + return count; +} + +/** + * eurwdt_ioctl: + * @inode: inode of the device + * @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. + */ + +static int eurwdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "WDT Eurotech CPU-1220/1410", + }; + + int time; + int options, retval = -EINVAL; + + switch(cmd) { + default: + return -ENOIOCTLCMD; + + 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: + eurwdt_ping(); + return 0; + + case WDIOC_SETTIMEOUT: + if (copy_from_user(&time, p, sizeof(int))) + return -EFAULT; + + /* Sanity check */ + if (time < 0 || time > 255) + return -EINVAL; + + eurwdt_timeout = time; + eurwdt_set_timeout(time); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(eurwdt_timeout, p); + + case WDIOC_SETOPTIONS: + if (get_user(options, p)) + return -EFAULT; + if (options & WDIOS_DISABLECARD) { + eurwdt_disable_timer(); + retval = 0; + } + if (options & WDIOS_ENABLECARD) { + eurwdt_activate_timer(); + eurwdt_ping(); + retval = 0; + } + return retval; + } +} + +/** + * eurwdt_open: + * @inode: inode of device + * @file: file handle to device + * + * The misc device has been opened. The watchdog device is single + * open and on opening we load the counter. + */ + +static int eurwdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &eurwdt_is_open)) + return -EBUSY; + eurwdt_timeout = WDT_TIMEOUT; /* initial timeout */ + /* Activate the WDT */ + eurwdt_activate_timer(); + return nonseekable_open(inode, file); +} + +/** + * eurwdt_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 eurwdt_release(struct inode *inode, struct file *file) +{ + if (eur_expect_close == 42) { + eurwdt_disable_timer(); + } else { + printk(KERN_CRIT "eurwdt: Unexpected close, not stopping watchdog!\n"); + eurwdt_ping(); + } + clear_bit(0, &eurwdt_is_open); + eur_expect_close = 0; + return 0; +} + +/** + * eurwdt_notify_sys: + * @this: our notifier block + * @code: the event being reported + * @unused: unused + * + * Our notifier is called on system shutdowns. We want to turn the card + * off at reboot otherwise the machine will reboot again during memory + * test or worse yet during the following fsck. This would suck, in fact + * trust me - if it happens it does suck. + */ + +static int eurwdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) { + /* Turn the card off */ + eurwdt_disable_timer(); + } + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + + +static struct file_operations eurwdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = eurwdt_write, + .ioctl = eurwdt_ioctl, + .open = eurwdt_open, + .release = eurwdt_release, +}; + +static struct miscdevice eurwdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &eurwdt_fops, +}; + +/* + * The WDT card needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block eurwdt_notifier = { + .notifier_call = eurwdt_notify_sys, +}; + +/** + * cleanup_module: + * + * Unload the watchdog. You cannot do this with any file handles open. + * If your watchdog is set to continue ticking on close and you unload + * it, well it keeps ticking. We won't get the interrupt but the board + * will not touch PC memory so all is fine. You just have to load a new + * module in 60 seconds or reboot. + */ + +static void __exit eurwdt_exit(void) +{ + eurwdt_lock_chip(); + + misc_deregister(&eurwdt_miscdev); + + unregister_reboot_notifier(&eurwdt_notifier); + release_region(io, 2); + free_irq(irq, NULL); +} + +/** + * eurwdt_init: + * + * Set up the WDT watchdog board. After grabbing the resources + * we require we need also to unlock the device. + * The open() function will actually kick the board off. + */ + +static int __init eurwdt_init(void) +{ + int ret; + + ret = misc_register(&eurwdt_miscdev); + if (ret) { + printk(KERN_ERR "eurwdt: can't misc_register on minor=%d\n", + WATCHDOG_MINOR); + goto out; + } + + ret = request_irq(irq, eurwdt_interrupt, SA_INTERRUPT, "eurwdt", NULL); + if(ret) { + printk(KERN_ERR "eurwdt: IRQ %d is not free.\n", irq); + goto outmisc; + } + + if (!request_region(io, 2, "eurwdt")) { + printk(KERN_ERR "eurwdt: IO %X is not free.\n", io); + ret = -EBUSY; + goto outirq; + } + + ret = register_reboot_notifier(&eurwdt_notifier); + if (ret) { + printk(KERN_ERR "eurwdt: can't register reboot notifier (err=%d)\n", ret); + goto outreg; + } + + eurwdt_unlock_chip(); + + ret = 0; + printk(KERN_INFO "Eurotech WDT driver 0.01 at %X (Interrupt %d)" + " - timeout event: %s\n", + io, irq, (!strcmp("int", ev) ? "int" : "reboot")); + +out: + return ret; + +outreg: + release_region(io, 2); + +outirq: + free_irq(irq, NULL); + +outmisc: + misc_deregister(&eurwdt_miscdev); + goto out; +} + +module_init(eurwdt_init); +module_exit(eurwdt_exit); + +MODULE_AUTHOR("Rodolfo Giometti"); +MODULE_DESCRIPTION("Driver for Eurotech CPU-1220/1410 on board watchdog"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/i8xx_tco.c b/drivers/char/watchdog/i8xx_tco.c new file mode 100644 index 000000000000..c337978dc966 --- /dev/null +++ b/drivers/char/watchdog/i8xx_tco.c @@ -0,0 +1,535 @@ +/* + * i8xx_tco 0.07: TCO timer driver for i8xx chipsets + * + * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved. + * http://www.kernelconcepts.de + * + * 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 kernel concepts nor Nils Faerber admit liability nor provide + * warranty for any of this software. This material is provided + * "AS-IS" and at no charge. + * + * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de> + * developed for + * Jentro AG, Haar/Munich (Germany) + * + * TCO timer driver for i8xx chipsets + * based on softdog.c by Alan Cox <alan@redhat.com> + * + * The TCO timer is implemented in the following I/O controller hubs: + * (See the intel documentation on http://developer.intel.com.) + * 82801AA (ICH) : document number 290655-003, 290677-014, + * 82801AB (ICHO) : document number 290655-003, 290677-014, + * 82801BA (ICH2) : document number 290687-002, 298242-027, + * 82801BAM (ICH2-M) : document number 290687-002, 298242-027, + * 82801CA (ICH3-S) : document number 290733-003, 290739-013, + * 82801CAM (ICH3-M) : document number 290716-001, 290718-007, + * 82801DB (ICH4) : document number 290744-001, 290745-020, + * 82801DBM (ICH4-M) : document number 252337-001, 252663-005, + * 82801E (C-ICH) : document number 273599-001, 273645-002, + * 82801EB (ICH5) : document number 252516-001, 252517-003, + * 82801ER (ICH5R) : document number 252516-001, 252517-003, + * 82801FB (ICH6) : document number 301473-002, 301474-007, + * 82801FR (ICH6R) : document number 301473-002, 301474-007, + * 82801FBM (ICH6-M) : document number 301473-002, 301474-007, + * 82801FW (ICH6W) : document number 301473-001, 301474-007, + * 82801FRW (ICH6RW) : document number 301473-001, 301474-007 + * + * 20000710 Nils Faerber + * Initial Version 0.01 + * 20000728 Nils Faerber + * 0.02 Fix for SMI_EN->TCO_EN bit, some cleanups + * 20011214 Matt Domsch <Matt_Domsch@dell.com> + * 0.03 Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * Didn't add timeout option as i810_margin already exists. + * 20020224 Joel Becker, Wim Van Sebroeck + * 0.04 Support for 82801CA(M) chipset, timer margin needs to be > 3, + * add support for WDIOC_SETTIMEOUT and WDIOC_GETTIMEOUT. + * 20020412 Rob Radez <rob@osinvestor.com>, Wim Van Sebroeck + * 0.05 Fix possible timer_alive race, add expect close support, + * clean up ioctls (WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS and + * WDIOC_SETOPTIONS), made i810tco_getdevice __init, + * removed boot_status, removed tco_timer_read, + * 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. + * 20050128 Wim Van Sebroeck <wim@iguana.be> + * 0.07 Added support for the ICH4-M, ICH6, ICH6R, ICH6-M, ICH6W and ICH6RW + * chipsets. Also added support for the "undocumented" ICH7 chipset. + */ + +/* + * 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 "i8xx_tco.h" + +/* Module and version information */ +#define TCO_VERSION "0.07" +#define TCO_MODULE_NAME "i8xx TCO timer" +#define TCO_DRIVER_NAME TCO_MODULE_NAME ", v" TCO_VERSION +#define PFX TCO_MODULE_NAME ": " + +/* 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; + +/* 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; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * 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; +} + +static int tco_timer_start (void) +{ + unsigned char val; + + spin_lock(&tco_lock); + val = inb (TCO1_CNT + 1); + val &= 0xf7; + outb (val, TCO1_CNT + 1); + val = inb (TCO1_CNT + 1); + spin_unlock(&tco_lock); + + if (val & 0x08) + return -1; + return 0; +} + +static int tco_timer_stop (void) +{ + unsigned char val; + + spin_lock(&tco_lock); + val = inb (TCO1_CNT + 1); + val |= 0x08; + outb (val, TCO1_CNT + 1); + val = inb (TCO1_CNT + 1); + spin_unlock(&tco_lock); + + if ((val & 0x08) == 0) + return -1; + return 0; +} + +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 -EINVAL; + + /* Write new heartbeat to watchdog */ + spin_lock(&tco_lock); + val = inb (TCO1_TMR); + val &= 0xc0; + val |= tmrval; + outb (val, TCO1_TMR); + val = inb (TCO1_TMR); + spin_unlock(&tco_lock); + + if ((val & 0x3f) != tmrval) + return -EINVAL; + + heartbeat = t; + return 0; +} + +/* + * /dev/watchdog handling + */ + +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_keepalive (); + tco_timer_start (); + return nonseekable_open(inode, file); +} + +static int i8xx_tco_release (struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + */ + if (tco_expect_close == 42) { + tco_timer_stop (); + } else { + 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 i8xx_tco_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... */ + tco_expect_close = 0; + + /* scan to see whether or not we got the magic character */ + for (i = 0; i != len; i++) { + char c; + if(get_user(c, data+i)) + return -EFAULT; + if (c == 'V') + tco_expect_close = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + tco_timer_keepalive (); + } + return len; +} + +static int i8xx_tco_ioctl (struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int new_options, retval = -EINVAL; + int new_heartbeat; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = TCO_MODULE_NAME, + }; + + 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: + tco_timer_keepalive (); + return 0; + + case WDIOC_SETOPTIONS: + { + if (get_user (new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + tco_timer_stop (); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + tco_timer_keepalive (); + tco_timer_start (); + retval = 0; + } + + return retval; + } + + case WDIOC_SETTIMEOUT: + { + if (get_user(new_heartbeat, p)) + return -EFAULT; + + if (tco_timer_set_heartbeat(new_heartbeat)) + return -EINVAL; + + tco_timer_keepalive (); + /* Fall */ + } + + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + + 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 + * + * This data only exists for exporting the supported + * PCI ids via MODULE_DEVICE_TABLE. We do not actually + * 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 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, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801BA_10, PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_0, PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801CA_12, PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_0, PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82801DB_12, 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, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_0, PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_1, PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH6_2, PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0, PCI_ANY_ID, PCI_ANY_ID, }, + { PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1, PCI_ANY_ID, PCI_ANY_ID, }, + { 0, }, /* End of list */ +}; +MODULE_DEVICE_TABLE (pci, i8xx_tco_pci_tbl); + +/* + * Init & exit routines + */ + +static unsigned char __init i8xx_tco_getdevice (void) +{ + struct pci_dev *dev = NULL; + u8 val1, val2; + u16 badr; + /* + * Find the PCI device + */ + + while ((dev = pci_find_device(PCI_ANY_ID, PCI_ANY_ID, dev)) != NULL) { + if (pci_match_device(i8xx_tco_pci_tbl, dev)) { + i8xx_tco_pci = dev; + break; + } + } + + 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 (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 PFX "failed to get TCOBASE address\n"); + return 0; + } + /* + * Check chipset's NO_REBOOT bit + */ + pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1); + if (val1 & 0x02) { + val1 &= 0xfd; + pci_write_config_byte (i8xx_tco_pci, 0xd4, val1); + pci_read_config_byte (i8xx_tco_pci, 0xd4, &val1); + if (val1 & 0x02) { + printk (KERN_ERR PFX "failed to reset NO_REBOOT flag, reboot disabled by hardware\n"); + return 0; /* Cannot reset NO_REBOOT bit */ + } + } + /* Set the TCO_EN bit in SMI_EN register */ + if (!request_region (SMI_EN + 1, 1, "i8xx TCO")) { + printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", + SMI_EN + 1); + return 0; + } + val1 = inb (SMI_EN + 1); + val1 &= 0xdf; + outb (val1, SMI_EN + 1); + release_region (SMI_EN + 1, 1); + return 1; + } + return 0; +} + +static int __init watchdog_init (void) +{ + int ret; + + spin_lock_init(&tco_lock); + + /* Check whether or not the hardware watchdog is there */ + if (!i8xx_tco_getdevice () || i8xx_tco_pci == NULL) + return -ENODEV; + + if (!request_region (TCOBASE, 0x10, "i8xx TCO")) { + printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", + TCOBASE); + ret = -EIO; + goto out; + } + + /* Clear out the (probably old) status */ + outb (0, TCO1_STS); + outb (3, TCO2_STS); + + /* 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); + } + + 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; + } + + 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_stop (); + + 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; + + /* 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 (i8xx_tco_pci, 0xd4, &val); + val |= 0x02; + 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); +} + +module_init(watchdog_init); +module_exit(watchdog_cleanup); + +MODULE_AUTHOR("Nils Faerber"); +MODULE_DESCRIPTION("TCO timer driver for i8xx chipsets"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/i8xx_tco.h b/drivers/char/watchdog/i8xx_tco.h new file mode 100644 index 000000000000..cc14eb8ac3d6 --- /dev/null +++ b/drivers/char/watchdog/i8xx_tco.h @@ -0,0 +1,42 @@ +/* + * i8xx_tco: TCO timer driver for i8xx chipsets + * + * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de>, All Rights Reserved. + * http://www.kernelconcepts.de + * + * 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 kernel concepts nor Nils Faerber admit liability nor provide + * warranty for any of this software. This material is provided + * "AS-IS" and at no charge. + * + * (c) Copyright 2000 kernel concepts <nils@kernelconcepts.de> + * developed for + * Jentro AG, Haar/Munich (Germany) + * + * TCO timer driver for i8xx chipsets + * based on softdog.c by Alan Cox <alan@redhat.com> + * + * For history and the complete list of supported I/O Controller Hub's + * see i8xx_tco.c + */ + + +/* + * Some address definitions for the TCO + */ + +#define TCOBASE ACPIBASE + 0x60 /* TCO base address */ +#define TCO1_RLD TCOBASE + 0x00 /* TCO Timer Reload and Current Value */ +#define TCO1_TMR TCOBASE + 0x01 /* TCO Timer Initial Value */ +#define TCO1_DAT_IN TCOBASE + 0x02 /* TCO Data In Register */ +#define TCO1_DAT_OUT TCOBASE + 0x03 /* TCO Data Out Register */ +#define TCO1_STS TCOBASE + 0x04 /* TCO1 Status Register */ +#define TCO2_STS TCOBASE + 0x06 /* TCO2 Status Register */ +#define TCO1_CNT TCOBASE + 0x08 /* TCO1 Control Register */ +#define TCO2_CNT TCOBASE + 0x0a /* TCO2 Control Register */ + +#define SMI_EN ACPIBASE + 0x30 /* SMI Control and Enable Register */ diff --git a/drivers/char/watchdog/ib700wdt.c b/drivers/char/watchdog/ib700wdt.c new file mode 100644 index 000000000000..d974f16e84d2 --- /dev/null +++ b/drivers/char/watchdog/ib700wdt.c @@ -0,0 +1,352 @@ +/* + * IB700 Single Board Computer WDT driver + * + * (c) Copyright 2001 Charles Howes <chowes@vsol.net> + * + * Based on advantechwdt.c which is based on acquirewdt.c which + * is based on wdt.c. + * + * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> + * + * Based on acquirewdt.c which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. + * http://www.redhat.com + * + * 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@redhat.com> + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * Added timeout module option to override default + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/fs.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/moduleparam.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +static unsigned long ibwdt_is_open; +static spinlock_t ibwdt_lock; +static char expect_close; + +#define PFX "ib700wdt: " + +/* + * + * Watchdog Timer Configuration + * + * The function of the watchdog timer is to reset the system + * automatically and is defined at I/O port 0443H. To enable the + * watchdog timer and allow the system to reset, write I/O port 0443H. + * To disable the timer, write I/O port 0441H for the system to stop the + * watchdog function. The timer has a tolerance of 20% for its + * intervals. + * + * The following describes how the timer should be programmed. + * + * Enabling Watchdog: + * MOV AX,000FH (Choose the values from 0 to F) + * MOV DX,0443H + * OUT DX,AX + * + * Disabling Watchdog: + * MOV AX,000FH (Any value is fine.) + * MOV DX,0441H + * OUT DX,AX + * + * Watchdog timer control table: + * Level Value Time/sec | Level Value Time/sec + * 1 F 0 | 9 7 16 + * 2 E 2 | 10 6 18 + * 3 D 4 | 11 5 20 + * 4 C 6 | 12 4 22 + * 5 B 8 | 13 3 24 + * 6 A 10 | 14 2 26 + * 7 9 12 | 15 1 28 + * 8 8 14 | 16 0 30 + * + */ + +static int wd_times[] = { + 30, /* 0x0 */ + 28, /* 0x1 */ + 26, /* 0x2 */ + 24, /* 0x3 */ + 22, /* 0x4 */ + 20, /* 0x5 */ + 18, /* 0x6 */ + 16, /* 0x7 */ + 14, /* 0x8 */ + 12, /* 0x9 */ + 10, /* 0xA */ + 8, /* 0xB */ + 6, /* 0xC */ + 4, /* 0xD */ + 2, /* 0xE */ + 0, /* 0xF */ +}; + +#define WDT_STOP 0x441 +#define WDT_START 0x443 + +/* Default timeout */ +#define WD_TIMO 0 /* 30 seconds +/- 20%, from table */ + +static int wd_margin = WD_TIMO; + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + + +/* + * Kernel methods. + */ + +static void +ibwdt_ping(void) +{ + /* Write a watchdog value */ + outb_p(wd_margin, WDT_START); +} + +static ssize_t +ibwdt_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 = 42; + } + } + ibwdt_ping(); + } + return count; +} + +static int +ibwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + int i, new_margin; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "IB700 WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + ibwdt_ping(); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, p)) + return -EFAULT; + if ((new_margin < 0) || (new_margin > 30)) + return -EINVAL; + for (i = 0x0F; i > -1; i--) + if (wd_times[i] > new_margin) + break; + wd_margin = i; + ibwdt_ping(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(wd_times[wd_margin], p); + break; + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int +ibwdt_open(struct inode *inode, struct file *file) +{ + spin_lock(&ibwdt_lock); + if (test_and_set_bit(0, &ibwdt_is_open)) { + spin_unlock(&ibwdt_lock); + return -EBUSY; + } + if (nowayout) + __module_get(THIS_MODULE); + + /* Activate */ + ibwdt_ping(); + spin_unlock(&ibwdt_lock); + return nonseekable_open(inode, file); +} + +static int +ibwdt_close(struct inode *inode, struct file *file) +{ + spin_lock(&ibwdt_lock); + if (expect_close == 42) + outb_p(0, WDT_STOP); + else + printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n"); + + clear_bit(0, &ibwdt_is_open); + expect_close = 0; + spin_unlock(&ibwdt_lock); + return 0; +} + +/* + * Notifier for system down + */ + +static int +ibwdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) { + /* Turn the WDT off */ + outb_p(0, WDT_STOP); + } + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations ibwdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ibwdt_write, + .ioctl = ibwdt_ioctl, + .open = ibwdt_open, + .release = ibwdt_close, +}; + +static struct miscdevice ibwdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &ibwdt_fops, +}; + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block ibwdt_notifier = { + .notifier_call = ibwdt_notify_sys, +}; + +static int __init ibwdt_init(void) +{ + int res; + + printk(KERN_INFO PFX "WDT driver for IB700 single board computer initialising.\n"); + + spin_lock_init(&ibwdt_lock); + res = misc_register(&ibwdt_miscdev); + if (res) { + printk (KERN_ERR PFX "failed to register misc device\n"); + goto out_nomisc; + } + +#if WDT_START != WDT_STOP + if (!request_region(WDT_STOP, 1, "IB700 WDT")) { + printk (KERN_ERR PFX "STOP method I/O %X is not available.\n", WDT_STOP); + res = -EIO; + goto out_nostopreg; + } +#endif + + if (!request_region(WDT_START, 1, "IB700 WDT")) { + printk (KERN_ERR PFX "START method I/O %X is not available.\n", WDT_START); + res = -EIO; + goto out_nostartreg; + } + res = register_reboot_notifier(&ibwdt_notifier); + if (res) { + printk (KERN_ERR PFX "Failed to register reboot notifier.\n"); + goto out_noreboot; + } + return 0; + +out_noreboot: + release_region(WDT_START, 1); +out_nostartreg: +#if WDT_START != WDT_STOP + release_region(WDT_STOP, 1); +#endif +out_nostopreg: + misc_deregister(&ibwdt_miscdev); +out_nomisc: + return res; +} + +static void __exit +ibwdt_exit(void) +{ + misc_deregister(&ibwdt_miscdev); + unregister_reboot_notifier(&ibwdt_notifier); +#if WDT_START != WDT_STOP + release_region(WDT_STOP,1); +#endif + release_region(WDT_START,1); +} + +module_init(ibwdt_init); +module_exit(ibwdt_exit); + +MODULE_AUTHOR("Charles Howes <chowes@vsol.net>"); +MODULE_DESCRIPTION("IB700 SBC watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +/* end of ib700wdt.c */ diff --git a/drivers/char/watchdog/indydog.c b/drivers/char/watchdog/indydog.c new file mode 100644 index 000000000000..6af2c799b57e --- /dev/null +++ b/drivers/char/watchdog/indydog.c @@ -0,0 +1,221 @@ +/* + * IndyDog 0.3 A Hardware Watchdog Device for SGI IP22 + * + * (c) Copyright 2002 Guido Guenther <agx@sigxcpu.org>, 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. + * + * based on softdog.c by Alan Cox <alan@redhat.com> + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#include <asm/sgi/mc.h> + +#define PFX "indydog: " +static int indydog_alive; + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +static void indydog_start(void) +{ + u32 mc_ctrl0 = sgimc->cpuctrl0; + + mc_ctrl0 = sgimc->cpuctrl0 | SGIMC_CCTRL0_WDOG; + sgimc->cpuctrl0 = mc_ctrl0; +} + +static void indydog_stop(void) +{ + u32 mc_ctrl0 = sgimc->cpuctrl0; + + mc_ctrl0 &= ~SGIMC_CCTRL0_WDOG; + sgimc->cpuctrl0 = mc_ctrl0; + + printk(KERN_INFO PFX "Stopped watchdog timer.\n"); +} + +static void indydog_ping(void) +{ + sgimc->watchdogt = 0; +} + +/* + * Allow only one person to hold it open + */ +static int indydog_open(struct inode *inode, struct file *file) +{ + if (indydog_alive) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + /* Activate timer */ + indydog_start(); + indydog_ping(); + + indydog_alive = 1; + printk(KERN_INFO "Started watchdog timer.\n"); + + return nonseekable_open(inode, file); +} + +static int indydog_release(struct inode *inode, struct file *file) +{ + /* Shut off the timer. + * Lock it in if it's a module and we defined ...NOWAYOUT */ + if (!nowayout) + indydog_stop(); /* Turn the WDT off */ + + indydog_alive = 0; + + return 0; +} + +static ssize_t indydog_write(struct file *file, const char *data, size_t len, loff_t *ppos) +{ + /* Refresh the timer. */ + if (len) { + indydog_ping(); + } + return len; +} + +static int indydog_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int options, retval = -EINVAL; + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = "Hardware Watchdog for SGI IP22", + }; + + switch (cmd) { + default: + return -ENOIOCTLCMD; + case WDIOC_GETSUPPORT: + if (copy_to_user((struct watchdog_info *)arg, + &ident, sizeof(ident))) + return -EFAULT; + return 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0,(int *)arg); + case WDIOC_KEEPALIVE: + indydog_ping(); + return 0; + case WDIOC_GETTIMEOUT: + return put_user(WATCHDOG_TIMEOUT,(int *)arg); + case WDIOC_SETOPTIONS: + { + if (get_user(options, (int *)arg)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + indydog_stop(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + indydog_start(); + retval = 0; + } + + return retval; + } + } +} + +static int indydog_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + indydog_stop(); /* Turn the WDT off */ + + return NOTIFY_DONE; +} + +static struct file_operations indydog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = indydog_write, + .ioctl = indydog_ioctl, + .open = indydog_open, + .release = indydog_release, +}; + +static struct miscdevice indydog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &indydog_fops, +}; + +static struct notifier_block indydog_notifier = { + .notifier_call = indydog_notify_sys, +}; + +static char banner[] __initdata = + KERN_INFO PFX "Hardware Watchdog Timer for SGI IP22: 0.3\n"; + +static int __init watchdog_init(void) +{ + int ret; + + ret = register_reboot_notifier(&indydog_notifier); + if (ret) { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + ret); + return ret; + } + + ret = misc_register(&indydog_miscdev); + if (ret) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + unregister_reboot_notifier(&indydog_notifier); + return ret; + } + + printk(banner); + + return 0; +} + +static void __exit watchdog_exit(void) +{ + misc_deregister(&indydog_miscdev); + unregister_reboot_notifier(&indydog_notifier); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_AUTHOR("Guido Guenther <agx@sigxcpu.org>"); +MODULE_DESCRIPTION("Hardware Watchdog Device for SGI IP22"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/ixp2000_wdt.c b/drivers/char/watchdog/ixp2000_wdt.c new file mode 100644 index 000000000000..ab659d37b4d2 --- /dev/null +++ b/drivers/char/watchdog/ixp2000_wdt.c @@ -0,0 +1,219 @@ +/* + * drivers/watchdog/ixp2000_wdt.c + * + * Watchdog driver for Intel IXP2000 network processors + * + * Adapted from the IXP4xx watchdog driver by Lennert Buytenhek. + * The original version carries these notices: + * + * Author: Deepak Saxena <dsaxena@plexity.net> + * + * Copyright 2004 (c) MontaVista, Software, Inc. + * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/init.h> +#include <linux/bitops.h> + +#include <asm/hardware.h> +#include <asm/uaccess.h> + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif +static unsigned int heartbeat = 60; /* (secs) Default is 1 minute */ +static unsigned long wdt_status; + +#define WDT_IN_USE 0 +#define WDT_OK_TO_CLOSE 1 + +static unsigned long wdt_tick_rate; + +static void +wdt_enable(void) +{ + ixp2000_reg_write(IXP2000_RESET0, *(IXP2000_RESET0) | WDT_RESET_ENABLE); + ixp2000_reg_write(IXP2000_TWDE, WDT_ENABLE); + ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); + ixp2000_reg_write(IXP2000_T4_CTL, TIMER_DIVIDER_256 | TIMER_ENABLE); +} + +static void +wdt_disable(void) +{ + ixp2000_reg_write(IXP2000_T4_CTL, 0); +} + +static void +wdt_keepalive(void) +{ + ixp2000_reg_write(IXP2000_T4_CLD, heartbeat * wdt_tick_rate); +} + +static int +ixp2000_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(WDT_IN_USE, &wdt_status)) + return -EBUSY; + + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + wdt_enable(); + + return nonseekable_open(inode, file); +} + +static ssize_t +ixp2000_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + set_bit(WDT_OK_TO_CLOSE, &wdt_status); + } + } + wdt_keepalive(); + } + + return len; +} + + +static struct watchdog_info ident = { + .options = WDIOF_MAGICCLOSE | WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING, + .identity = "IXP2000 Watchdog", +}; + +static int +ixp2000_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOIOCTLCMD; + int time; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user((struct watchdog_info *)arg, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, (int *)arg); + if (ret) + break; + + if (time <= 0 || time > 60) { + ret = -EINVAL; + break; + } + + heartbeat = time; + wdt_keepalive(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(heartbeat, (int *)arg); + break; + + case WDIOC_KEEPALIVE: + wdt_enable(); + ret = 0; + break; + } + + return ret; +} + +static int +ixp2000_wdt_release(struct inode *inode, struct file *file) +{ + if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { + wdt_disable(); + } else { + printk(KERN_CRIT "WATCHDOG: Device closed unexpectdly - " + "timer will not stop\n"); + } + + clear_bit(WDT_IN_USE, &wdt_status); + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + return 0; +} + + +static struct file_operations ixp2000_wdt_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ixp2000_wdt_write, + .ioctl = ixp2000_wdt_ioctl, + .open = ixp2000_wdt_open, + .release = ixp2000_wdt_release, +}; + +static struct miscdevice ixp2000_wdt_miscdev = +{ + .minor = WATCHDOG_MINOR, + .name = "IXP2000 Watchdog", + .fops = &ixp2000_wdt_fops, +}; + +static int __init ixp2000_wdt_init(void) +{ + wdt_tick_rate = (*IXP2000_T1_CLD * HZ)/ 256;; + + return misc_register(&ixp2000_wdt_miscdev); +} + +static void __exit ixp2000_wdt_exit(void) +{ + misc_deregister(&ixp2000_wdt_miscdev); +} + +module_init(ixp2000_wdt_init); +module_exit(ixp2000_wdt_exit); + +MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net">); +MODULE_DESCRIPTION("IXP2000 Network Processor Watchdog"); + +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + diff --git a/drivers/char/watchdog/ixp4xx_wdt.c b/drivers/char/watchdog/ixp4xx_wdt.c new file mode 100644 index 000000000000..82396e06c8a8 --- /dev/null +++ b/drivers/char/watchdog/ixp4xx_wdt.c @@ -0,0 +1,230 @@ +/* + * drivers/watchdog/ixp4xx_wdt.c + * + * Watchdog driver for Intel IXP4xx network processors + * + * Author: Deepak Saxena <dsaxena@plexity.net> + * + * Copyright 2004 (c) MontaVista, Software, Inc. + * Based on sa1100 driver, Copyright (C) 2000 Oleg Drokin <green@crimea.edu> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/init.h> +#include <linux/bitops.h> + +#include <asm/hardware.h> +#include <asm/uaccess.h> + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif +static int heartbeat = 60; /* (secs) Default is 1 minute */ +static unsigned long wdt_status; +static unsigned long boot_status; + +#define WDT_TICK_RATE (IXP4XX_PERIPHERAL_BUS_CLOCK * 1000000UL) + +#define WDT_IN_USE 0 +#define WDT_OK_TO_CLOSE 1 + +static void +wdt_enable(void) +{ + *IXP4XX_OSWK = IXP4XX_WDT_KEY; + *IXP4XX_OSWE = 0; + *IXP4XX_OSWT = WDT_TICK_RATE * heartbeat; + *IXP4XX_OSWE = IXP4XX_WDT_COUNT_ENABLE | IXP4XX_WDT_RESET_ENABLE; + *IXP4XX_OSWK = 0; +} + +static void +wdt_disable(void) +{ + *IXP4XX_OSWK = IXP4XX_WDT_KEY; + *IXP4XX_OSWE = 0; + *IXP4XX_OSWK = 0; +} + +static int +ixp4xx_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(WDT_IN_USE, &wdt_status)) + return -EBUSY; + + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + wdt_enable(); + + return nonseekable_open(inode, file); +} + +static ssize_t +ixp4xx_wdt_write(struct file *file, const char *data, size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + set_bit(WDT_OK_TO_CLOSE, &wdt_status); + } + } + wdt_enable(); + } + + return len; +} + +static struct watchdog_info ident = { + .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | + WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "IXP4xx Watchdog", +}; + + +static int +ixp4xx_wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + int ret = -ENOIOCTLCMD; + int time; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user((struct watchdog_info *)arg, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(boot_status, (int *)arg); + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, (int *)arg); + if (ret) + break; + + if (time <= 0 || time > 60) { + ret = -EINVAL; + break; + } + + heartbeat = time; + wdt_enable(); + /* Fall through */ + + case WDIOC_GETTIMEOUT: + ret = put_user(heartbeat, (int *)arg); + break; + + case WDIOC_KEEPALIVE: + wdt_enable(); + ret = 0; + break; + } + return ret; +} + +static int +ixp4xx_wdt_release(struct inode *inode, struct file *file) +{ + if (test_bit(WDT_OK_TO_CLOSE, &wdt_status)) { + wdt_disable(); + } else { + printk(KERN_CRIT "WATCHDOG: Device closed unexpectdly - " + "timer will not stop\n"); + } + + clear_bit(WDT_IN_USE, &wdt_status); + clear_bit(WDT_OK_TO_CLOSE, &wdt_status); + + return 0; +} + + +static struct file_operations ixp4xx_wdt_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = ixp4xx_wdt_write, + .ioctl = ixp4xx_wdt_ioctl, + .open = ixp4xx_wdt_open, + .release = ixp4xx_wdt_release, +}; + +static struct miscdevice ixp4xx_wdt_miscdev = +{ + .minor = WATCHDOG_MINOR, + .name = "IXP4xx Watchdog", + .fops = &ixp4xx_wdt_fops, +}; + +static int __init ixp4xx_wdt_init(void) +{ + int ret; + unsigned long processor_id; + + asm("mrc p15, 0, %0, cr0, cr0, 0;" : "=r"(processor_id) :); + if (!(processor_id & 0xf)) { + printk("IXP4XXX Watchdog: Rev. A0 CPU detected - " + "watchdog disabled\n"); + + return -ENODEV; + } + + ret = misc_register(&ixp4xx_wdt_miscdev); + if (ret == 0) + printk("IXP4xx Watchdog Timer: heartbeat %d sec\n", heartbeat); + + boot_status = (*IXP4XX_OSST & IXP4XX_OSST_TIMER_WARM_RESET) ? + WDIOF_CARDRESET : 0; + + return ret; +} + +static void __exit ixp4xx_wdt_exit(void) +{ + misc_deregister(&ixp4xx_wdt_miscdev); +} + + +module_init(ixp4xx_wdt_init); +module_exit(ixp4xx_wdt_exit); + +MODULE_AUTHOR("Deepak Saxena <dsaxena@plexity.net>"); +MODULE_DESCRIPTION("IXP4xx Network Processor Watchdog"); + +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds (default 60s)"); + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + diff --git a/drivers/char/watchdog/machzwd.c b/drivers/char/watchdog/machzwd.c new file mode 100644 index 000000000000..9da395fa7794 --- /dev/null +++ b/drivers/char/watchdog/machzwd.c @@ -0,0 +1,501 @@ +/* + * MachZ ZF-Logic Watchdog Timer driver for Linux + * + * + * 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. + * + * The author does NOT admit liability nor provide warranty for + * any of this software. This material is provided "AS-IS" in + * the hope that it may be useful for others. + * + * Author: Fernando Fuganti <fuganti@conectiva.com.br> + * + * Based on sbc60xxwdt.c by Jakob Oestergaard + * + * + * We have two timers (wd#1, wd#2) driven by a 32 KHz clock with the + * following periods: + * wd#1 - 2 seconds; + * wd#2 - 7.2 ms; + * After the expiration of wd#1, it can generate a NMI, SCI, SMI, or + * a system RESET and it starts wd#2 that unconditionaly will RESET + * the system when the counter reaches zero. + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +/* ports */ +#define ZF_IOBASE 0x218 +#define INDEX 0x218 +#define DATA_B 0x219 +#define DATA_W 0x21A +#define DATA_D 0x21A + +/* indexes */ /* size */ +#define ZFL_VERSION 0x02 /* 16 */ +#define CONTROL 0x10 /* 16 */ +#define STATUS 0x12 /* 8 */ +#define COUNTER_1 0x0C /* 16 */ +#define COUNTER_2 0x0E /* 8 */ +#define PULSE_LEN 0x0F /* 8 */ + +/* controls */ +#define ENABLE_WD1 0x0001 +#define ENABLE_WD2 0x0002 +#define RESET_WD1 0x0010 +#define RESET_WD2 0x0020 +#define GEN_SCI 0x0100 +#define GEN_NMI 0x0200 +#define GEN_SMI 0x0400 +#define GEN_RESET 0x0800 + + +/* utilities */ + +#define WD1 0 +#define WD2 1 + +#define zf_writew(port, data) { outb(port, INDEX); outw(data, DATA_W); } +#define zf_writeb(port, data) { outb(port, INDEX); outb(data, DATA_B); } +#define zf_get_ZFL_version() zf_readw(ZFL_VERSION) + + +static unsigned short zf_readw(unsigned char port) +{ + outb(port, INDEX); + return inw(DATA_W); +} + + +MODULE_AUTHOR("Fernando Fuganti <fuganti@conectiva.com.br>"); +MODULE_DESCRIPTION("MachZ ZF-Logic Watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +#define PFX "machzwd" + +static struct watchdog_info zf_info = { + .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "ZF-Logic watchdog", +}; + + +/* + * action refers to action taken when watchdog resets + * 0 = GEN_RESET + * 1 = GEN_SMI + * 2 = GEN_NMI + * 3 = GEN_SCI + * defaults to GEN_RESET (0) + */ +static int action = 0; +module_param(action, int, 0); +MODULE_PARM_DESC(action, "after watchdog resets, generate: 0 = RESET(*) 1 = SMI 2 = NMI 3 = SCI"); + +static int zf_action = GEN_RESET; +static unsigned long zf_is_open; +static char zf_expect_close; +static spinlock_t zf_lock; +static spinlock_t zf_port_lock; +static struct timer_list zf_timer; +static unsigned long next_heartbeat = 0; + + +/* timeout for user land heart beat (10 seconds) */ +#define ZF_USER_TIMEO (HZ*10) + +/* timeout for hardware watchdog (~500ms) */ +#define ZF_HW_TIMEO (HZ/2) + +/* number of ticks on WD#1 (driven by a 32KHz clock, 2s) */ +#define ZF_CTIMEOUT 0xffff + +#ifndef ZF_DEBUG +# define dprintk(format, args...) +#else +# define dprintk(format, args...) printk(KERN_DEBUG PFX ":%s:%d: " format, __FUNCTION__, __LINE__ , ## args) +#endif + + +static inline void zf_set_status(unsigned char new) +{ + zf_writeb(STATUS, new); +} + + +/* CONTROL register functions */ + +static inline unsigned short zf_get_control(void) +{ + return zf_readw(CONTROL); +} + +static inline void zf_set_control(unsigned short new) +{ + zf_writew(CONTROL, new); +} + + +/* WD#? counter functions */ +/* + * Just set counter value + */ + +static inline void zf_set_timer(unsigned short new, unsigned char n) +{ + switch(n){ + case WD1: + zf_writew(COUNTER_1, new); + case WD2: + zf_writeb(COUNTER_2, new > 0xff ? 0xff : new); + default: + return; + } +} + +/* + * stop hardware timer + */ +static void zf_timer_off(void) +{ + unsigned int ctrl_reg = 0; + unsigned long flags; + + /* stop internal ping */ + del_timer_sync(&zf_timer); + + spin_lock_irqsave(&zf_port_lock, flags); + /* stop watchdog timer */ + ctrl_reg = zf_get_control(); + ctrl_reg |= (ENABLE_WD1|ENABLE_WD2); /* disable wd1 and wd2 */ + ctrl_reg &= ~(ENABLE_WD1|ENABLE_WD2); + zf_set_control(ctrl_reg); + spin_unlock_irqrestore(&zf_port_lock, flags); + + printk(KERN_INFO PFX ": Watchdog timer is now disabled\n"); +} + + +/* + * start hardware timer + */ +static void zf_timer_on(void) +{ + unsigned int ctrl_reg = 0; + unsigned long flags; + + spin_lock_irqsave(&zf_port_lock, flags); + + zf_writeb(PULSE_LEN, 0xff); + + zf_set_timer(ZF_CTIMEOUT, WD1); + + /* user land ping */ + next_heartbeat = jiffies + ZF_USER_TIMEO; + + /* start the timer for internal ping */ + zf_timer.expires = jiffies + ZF_HW_TIMEO; + + add_timer(&zf_timer); + + /* start watchdog timer */ + ctrl_reg = zf_get_control(); + ctrl_reg |= (ENABLE_WD1|zf_action); + zf_set_control(ctrl_reg); + spin_unlock_irqrestore(&zf_port_lock, flags); + + printk(KERN_INFO PFX ": Watchdog timer is now enabled\n"); +} + + +static void zf_ping(unsigned long data) +{ + unsigned int ctrl_reg = 0; + unsigned long flags; + + zf_writeb(COUNTER_2, 0xff); + + if(time_before(jiffies, next_heartbeat)){ + + dprintk("time_before: %ld\n", next_heartbeat - jiffies); + + /* + * reset event is activated by transition from 0 to 1 on + * RESET_WD1 bit and we assume that it is already zero... + */ + + spin_lock_irqsave(&zf_port_lock, flags); + ctrl_reg = zf_get_control(); + ctrl_reg |= RESET_WD1; + zf_set_control(ctrl_reg); + + /* ...and nothing changes until here */ + ctrl_reg &= ~(RESET_WD1); + zf_set_control(ctrl_reg); + spin_unlock_irqrestore(&zf_port_lock, flags); + + zf_timer.expires = jiffies + ZF_HW_TIMEO; + add_timer(&zf_timer); + }else{ + printk(KERN_CRIT PFX ": I will reset your machine\n"); + } +} + +static ssize_t zf_write(struct file *file, const char __user *buf, size_t count, + loff_t *ppos) +{ + /* See if we got the magic character */ + if(count){ + + /* + * no need to check for close confirmation + * no way to disable watchdog ;) + */ + if (!nowayout) { + size_t ofs; + + /* + * note: just in case someone wrote the magic character + * five months ago... + */ + zf_expect_close = 0; + + /* now scan */ + for (ofs = 0; ofs != count; ofs++){ + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if (c == 'V'){ + zf_expect_close = 42; + dprintk("zf_expect_close = 42\n"); + } + } + } + + /* + * Well, anyhow someone wrote to us, + * we should return that favour + */ + next_heartbeat = jiffies + ZF_USER_TIMEO; + dprintk("user ping at %ld\n", jiffies); + + } + + return count; +} + +static int zf_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + switch(cmd){ + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &zf_info, sizeof(zf_info))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + zf_ping(0); + break; + + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +static int zf_open(struct inode *inode, struct file *file) +{ + spin_lock(&zf_lock); + if(test_and_set_bit(0, &zf_is_open)) { + spin_unlock(&zf_lock); + return -EBUSY; + } + + if (nowayout) + __module_get(THIS_MODULE); + + spin_unlock(&zf_lock); + + zf_timer_on(); + + return nonseekable_open(inode, file); +} + +static int zf_close(struct inode *inode, struct file *file) +{ + if(zf_expect_close == 42){ + zf_timer_off(); + } else { + del_timer(&zf_timer); + printk(KERN_ERR PFX ": device file closed unexpectedly. Will not stop the WDT!\n"); + } + + spin_lock(&zf_lock); + clear_bit(0, &zf_is_open); + spin_unlock(&zf_lock); + + zf_expect_close = 0; + + return 0; +} + +/* + * Notifier for system down + */ + +static int zf_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if(code == SYS_DOWN || code == SYS_HALT){ + zf_timer_off(); + } + + return NOTIFY_DONE; +} + + + + +static struct file_operations zf_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = zf_write, + .ioctl = zf_ioctl, + .open = zf_open, + .release = zf_close, +}; + +static struct miscdevice zf_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &zf_fops, +}; + + +/* + * The device needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ +static struct notifier_block zf_notifier = { + .notifier_call = zf_notify_sys, +}; + +static void __init zf_show_action(int act) +{ + char *str[] = { "RESET", "SMI", "NMI", "SCI" }; + + printk(KERN_INFO PFX ": Watchdog using action = %s\n", str[act]); +} + +static int __init zf_init(void) +{ + int ret; + + printk(KERN_INFO PFX ": MachZ ZF-Logic Watchdog driver initializing.\n"); + + ret = zf_get_ZFL_version(); + printk("%#x\n", ret); + if((!ret) || (ret != 0xffff)){ + printk(KERN_WARNING PFX ": no ZF-Logic found\n"); + return -ENODEV; + } + + if((action <= 3) && (action >= 0)){ + zf_action = zf_action>>action; + } else + action = 0; + + zf_show_action(action); + + spin_lock_init(&zf_lock); + spin_lock_init(&zf_port_lock); + + ret = misc_register(&zf_miscdev); + if (ret){ + printk(KERN_ERR "can't misc_register on minor=%d\n", + WATCHDOG_MINOR); + goto out; + } + + if(!request_region(ZF_IOBASE, 3, "MachZ ZFL WDT")){ + printk(KERN_ERR "cannot reserve I/O ports at %d\n", + ZF_IOBASE); + ret = -EBUSY; + goto no_region; + } + + ret = register_reboot_notifier(&zf_notifier); + if(ret){ + printk(KERN_ERR "can't register reboot notifier (err=%d)\n", + ret); + goto no_reboot; + } + + zf_set_status(0); + zf_set_control(0); + + /* this is the timer that will do the hard work */ + init_timer(&zf_timer); + zf_timer.function = zf_ping; + zf_timer.data = 0; + + return 0; + +no_reboot: + release_region(ZF_IOBASE, 3); +no_region: + misc_deregister(&zf_miscdev); +out: + return ret; +} + + +static void __exit zf_exit(void) +{ + zf_timer_off(); + + misc_deregister(&zf_miscdev); + unregister_reboot_notifier(&zf_notifier); + release_region(ZF_IOBASE, 3); +} + +module_init(zf_init); +module_exit(zf_exit); diff --git a/drivers/char/watchdog/mixcomwd.c b/drivers/char/watchdog/mixcomwd.c new file mode 100644 index 000000000000..3143e4a07535 --- /dev/null +++ b/drivers/char/watchdog/mixcomwd.c @@ -0,0 +1,306 @@ +/* + * MixCom Watchdog: A Simple Hardware Watchdog Device + * Based on Softdog driver by Alan Cox and PC Watchdog driver by Ken Hollis + * + * Author: Gergely Madarasz <gorgo@itc.hu> + * + * Copyright (c) 1999 ITConsult-Pro Co. <info@itc.hu> + * + * 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. + * + * Version 0.1 (99/04/15): + * - first version + * + * Version 0.2 (99/06/16): + * - added kernel timer watchdog ping after close + * since the hardware does not support watchdog shutdown + * + * Version 0.3 (99/06/21): + * - added WDIOC_GETSTATUS and WDIOC_GETSUPPORT ioctl calls + * + * Version 0.3.1 (99/06/22): + * - allow module removal while internal timer is active, + * print warning about probable reset + * + * Version 0.4 (99/11/15): + * - support for one more type board + * + * Version 0.5 (2001/12/14) Matt Domsch <Matt_Domsch@dell.com> + * - added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * + */ + +#define VERSION "0.5" + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/ioport.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <asm/uaccess.h> +#include <asm/io.h> + +static int mixcomwd_ioports[] = { 0x180, 0x280, 0x380, 0x000 }; + +#define MIXCOM_WATCHDOG_OFFSET 0xc10 +#define MIXCOM_ID 0x11 +#define FLASHCOM_WATCHDOG_OFFSET 0x4 +#define FLASHCOM_ID 0x18 + +static unsigned long mixcomwd_opened; /* long req'd for setbit --RR */ + +static int watchdog_port; +static int mixcomwd_timer_alive; +static struct timer_list mixcomwd_timer = TIMER_INITIALIZER(NULL, 0, 0); +static char expect_close; + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +static void mixcomwd_ping(void) +{ + outb_p(55,watchdog_port); + return; +} + +static void mixcomwd_timerfun(unsigned long d) +{ + mixcomwd_ping(); + + mod_timer(&mixcomwd_timer,jiffies+ 5*HZ); +} + +/* + * Allow only one person to hold it open + */ + +static int mixcomwd_open(struct inode *inode, struct file *file) +{ + if(test_and_set_bit(0,&mixcomwd_opened)) { + return -EBUSY; + } + mixcomwd_ping(); + + if (nowayout) { + /* + * fops_get() code via open() has already done + * a try_module_get() so it is safe to do the + * __module_get(). + */ + __module_get(THIS_MODULE); + } else { + if(mixcomwd_timer_alive) { + del_timer(&mixcomwd_timer); + mixcomwd_timer_alive=0; + } + } + return nonseekable_open(inode, file); +} + +static int mixcomwd_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + if(mixcomwd_timer_alive) { + printk(KERN_ERR "mixcomwd: release called while internal timer alive"); + return -EBUSY; + } + init_timer(&mixcomwd_timer); + mixcomwd_timer.expires=jiffies + 5 * HZ; + mixcomwd_timer.function=mixcomwd_timerfun; + mixcomwd_timer.data=0; + mixcomwd_timer_alive=1; + add_timer(&mixcomwd_timer); + } else { + printk(KERN_CRIT "mixcomwd: WDT device closed unexpectedly. WDT will not stop!\n"); + } + + clear_bit(0,&mixcomwd_opened); + expect_close=0; + return 0; +} + + +static ssize_t mixcomwd_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) +{ + if(len) + { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + mixcomwd_ping(); + } + return len; +} + +static int mixcomwd_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int status; + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "MixCOM watchdog", + }; + + switch(cmd) + { + case WDIOC_GETSTATUS: + status=mixcomwd_opened; + if (!nowayout) { + status|=mixcomwd_timer_alive; + } + if (copy_to_user(p, &status, sizeof(int))) { + return -EFAULT; + } + break; + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) { + return -EFAULT; + } + break; + case WDIOC_KEEPALIVE: + mixcomwd_ping(); + break; + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static struct file_operations mixcomwd_fops= +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = mixcomwd_write, + .ioctl = mixcomwd_ioctl, + .open = mixcomwd_open, + .release = mixcomwd_release, +}; + +static struct miscdevice mixcomwd_miscdev= +{ + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &mixcomwd_fops, +}; + +static int __init mixcomwd_checkcard(int port) +{ + int id; + + port += MIXCOM_WATCHDOG_OFFSET; + if (!request_region(port, 1, "MixCOM watchdog")) { + return 0; + } + + id=inb_p(port) & 0x3f; + if(id!=MIXCOM_ID) { + release_region(port, 1); + return 0; + } + return port; +} + +static int __init flashcom_checkcard(int port) +{ + int id; + + port += FLASHCOM_WATCHDOG_OFFSET; + if (!request_region(port, 1, "MixCOM watchdog")) { + return 0; + } + + id=inb_p(port); + if(id!=FLASHCOM_ID) { + release_region(port, 1); + return 0; + } + return port; + } + +static int __init mixcomwd_init(void) +{ + int i; + int ret; + int found=0; + + for (i = 0; !found && mixcomwd_ioports[i] != 0; i++) { + watchdog_port = mixcomwd_checkcard(mixcomwd_ioports[i]); + if (watchdog_port) { + found = 1; + } + } + + /* The FlashCOM card can be set up at 0x300 -> 0x378, in 0x8 jumps */ + for (i = 0x300; !found && i < 0x380; i+=0x8) { + watchdog_port = flashcom_checkcard(i); + if (watchdog_port) { + found = 1; + } + } + + if (!found) { + printk("mixcomwd: No card detected, or port not available.\n"); + return -ENODEV; + } + + ret = misc_register(&mixcomwd_miscdev); + if (ret) + { + release_region(watchdog_port, 1); + return ret; + } + + printk(KERN_INFO "MixCOM watchdog driver v%s, watchdog port at 0x%3x\n",VERSION,watchdog_port); + + return 0; +} + +static void __exit mixcomwd_exit(void) +{ + if (!nowayout) { + if(mixcomwd_timer_alive) { + printk(KERN_WARNING "mixcomwd: I quit now, hardware will" + " probably reboot!\n"); + del_timer(&mixcomwd_timer); + mixcomwd_timer_alive=0; + } + } + release_region(watchdog_port,1); + misc_deregister(&mixcomwd_miscdev); +} + +module_init(mixcomwd_init); +module_exit(mixcomwd_exit); + +MODULE_AUTHOR("Gergely Madarasz <gorgo@itc.hu>"); +MODULE_DESCRIPTION("MixCom Watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/mpc8xx_wdt.c b/drivers/char/watchdog/mpc8xx_wdt.c new file mode 100644 index 000000000000..56d62ba7c6ce --- /dev/null +++ b/drivers/char/watchdog/mpc8xx_wdt.c @@ -0,0 +1,164 @@ +/* + * mpc8xx_wdt.c - MPC8xx watchdog userspace interface + * + * Author: Florian Schirmer <jolt@tuxbox.org> + * + * 2002 (c) Florian Schirmer <jolt@tuxbox.org> This file is licensed under + * the terms of the GNU General Public License version 2. This program + * is licensed "as is" without any warranty of any kind, whether express + * or implied. + */ + +#include <linux/config.h> +#include <linux/fs.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/miscdevice.h> +#include <linux/module.h> +#include <linux/watchdog.h> +#include <asm/8xx_immap.h> +#include <asm/uaccess.h> +#include <syslib/m8xx_wdt.h> + +static unsigned long wdt_opened; +static int wdt_status; + +static void mpc8xx_wdt_handler_disable(void) +{ + volatile immap_t *imap = (volatile immap_t *)IMAP_ADDR; + + imap->im_sit.sit_piscr &= ~(PISCR_PIE | PISCR_PTE); + + printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler deactivated\n"); +} + +static void mpc8xx_wdt_handler_enable(void) +{ + volatile immap_t *imap = (volatile immap_t *)IMAP_ADDR; + + imap->im_sit.sit_piscr |= PISCR_PIE | PISCR_PTE; + + printk(KERN_NOTICE "mpc8xx_wdt: keep-alive handler activated\n"); +} + +static int mpc8xx_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &wdt_opened)) + return -EBUSY; + + m8xx_wdt_reset(); + mpc8xx_wdt_handler_disable(); + + return 0; +} + +static int mpc8xx_wdt_release(struct inode *inode, struct file *file) +{ + m8xx_wdt_reset(); + +#if !defined(CONFIG_WATCHDOG_NOWAYOUT) + mpc8xx_wdt_handler_enable(); +#endif + + clear_bit(0, &wdt_opened); + + return 0; +} + +static ssize_t mpc8xx_wdt_write(struct file *file, const char *data, size_t len, + loff_t * ppos) +{ + if (ppos != &file->f_pos) + return -ESPIPE; + + if (len) + m8xx_wdt_reset(); + + return len; +} + +static int mpc8xx_wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int timeout; + static struct watchdog_info info = { + .options = WDIOF_KEEPALIVEPING, + .firmware_version = 0, + .identity = "MPC8xx watchdog", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user((void *)arg, &info, sizeof(info))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + if (put_user(wdt_status, (int *)arg)) + return -EFAULT; + wdt_status &= ~WDIOF_KEEPALIVEPING; + break; + + case WDIOC_GETTEMP: + return -EOPNOTSUPP; + + case WDIOC_SETOPTIONS: + return -EOPNOTSUPP; + + case WDIOC_KEEPALIVE: + m8xx_wdt_reset(); + wdt_status |= WDIOF_KEEPALIVEPING; + break; + + case WDIOC_SETTIMEOUT: + return -EOPNOTSUPP; + + case WDIOC_GETTIMEOUT: + timeout = m8xx_wdt_get_timeout(); + if (put_user(timeout, (int *)arg)) + return -EFAULT; + break; + + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +static struct file_operations mpc8xx_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = mpc8xx_wdt_write, + .ioctl = mpc8xx_wdt_ioctl, + .open = mpc8xx_wdt_open, + .release = mpc8xx_wdt_release, +}; + +static struct miscdevice mpc8xx_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &mpc8xx_wdt_fops, +}; + +static int __init mpc8xx_wdt_init(void) +{ + return misc_register(&mpc8xx_wdt_miscdev); +} + +static void __exit mpc8xx_wdt_exit(void) +{ + misc_deregister(&mpc8xx_wdt_miscdev); + + m8xx_wdt_reset(); + mpc8xx_wdt_handler_enable(); +} + +module_init(mpc8xx_wdt_init); +module_exit(mpc8xx_wdt_exit); + +MODULE_AUTHOR("Florian Schirmer <jolt@tuxbox.org>"); +MODULE_DESCRIPTION("MPC8xx watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/pcwd.c b/drivers/char/watchdog/pcwd.c new file mode 100644 index 000000000000..592dca108866 --- /dev/null +++ b/drivers/char/watchdog/pcwd.c @@ -0,0 +1,926 @@ +/* + * PC Watchdog Driver + * by Ken Hollis (khollis@bitgate.com) + * + * Permission granted from Simon Machell (73244.1270@compuserve.com) + * Written for the Linux Kernel, and GPLed by Ken Hollis + * + * 960107 Added request_region routines, modulized the whole thing. + * 960108 Fixed end-of-file pointer (Thanks to Dan Hollis), added + * WD_TIMEOUT define. + * 960216 Added eof marker on the file, and changed verbose messages. + * 960716 Made functional and cosmetic changes to the source for + * inclusion in Linux 2.0.x kernels, thanks to Alan Cox. + * 960717 Removed read/seek routines, replaced with ioctl. Also, added + * check_region command due to Alan's suggestion. + * 960821 Made changes to compile in newer 2.0.x kernels. Added + * "cold reboot sense" entry. + * 960825 Made a few changes to code, deleted some defines and made + * typedefs to replace them. Made heartbeat reset only available + * via ioctl, and removed the write routine. + * 960828 Added new items for PC Watchdog Rev.C card. + * 960829 Changed around all of the IOCTLs, added new features, + * added watchdog disable/re-enable routines. Added firmware + * version reporting. Added read routine for temperature. + * Removed some extra defines, added an autodetect Revision + * routine. + * 961006 Revised some documentation, fixed some cosmetic bugs. Made + * drivers to panic the system if it's overheating at bootup. + * 961118 Changed some verbiage on some of the output, tidied up + * code bits, and added compatibility to 2.1.x. + * 970912 Enabled board on open and disable on close. + * 971107 Took account of recent VFS changes (broke read). + * 971210 Disable board on initialisation in case board already ticking. + * 971222 Changed open/close for temperature handling + * Michael Meskes <meskes@debian.org>. + * 980112 Used minor numbers from include/linux/miscdevice.h + * 990403 Clear reset status after reading control status register in + * pcwd_showprevstate(). [Marc Boucher <marc@mbsi.ca>] + * 990605 Made changes to code to support Firmware 1.22a, added + * fairly useless proc entry. + * 990610 removed said useless proc code for the merge <alan> + * 000403 Removed last traces of proc code. <davej> + * 011214 Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT <Matt_Domsch@dell.com> + * Added timeout module option to override default + */ + +/* + * A bells and whistles driver is available from http://www.pcwd.de/ + * More info available at http://www.berkprod.com/ or http://www.pcwatchdog.com/ + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/config.h> +#include <linux/wait.h> +#include <linux/slab.h> +#include <linux/ioport.h> +#include <linux/delay.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/reboot.h> + +#include <asm/uaccess.h> +#include <asm/io.h> + +#define WD_VER "1.16 (06/12/2004)" +#define PFX "pcwd: " + +/* + * It should be noted that PCWD_REVISION_B was removed because A and B + * are essentially the same types of card, with the exception that B + * has temperature reporting. Since I didn't receive a Rev.B card, + * the Rev.B card is not supported. (It's a good thing too, as they + * are no longer in production.) + */ +#define PCWD_REVISION_A 1 +#define PCWD_REVISION_C 2 + +/* + * These are the defines that describe the control status bits for the + * PC Watchdog card, revision A. + */ +#define WD_WDRST 0x01 /* Previously reset state */ +#define WD_T110 0x02 /* Temperature overheat sense */ +#define WD_HRTBT 0x04 /* Heartbeat sense */ +#define WD_RLY2 0x08 /* External relay triggered */ +#define WD_SRLY2 0x80 /* Software external relay triggered */ + +/* + * These are the defines that describe the control status bits for the + * PC Watchdog card, revision C. + */ +#define WD_REVC_WTRP 0x01 /* Watchdog Trip status */ +#define WD_REVC_HRBT 0x02 /* Watchdog Heartbeat */ +#define WD_REVC_TTRP 0x04 /* Temperature Trip status */ + +/* max. time we give an ISA watchdog card to process a command */ +/* 500ms for each 4 bit response (according to spec.) */ +#define ISA_COMMAND_TIMEOUT 1000 + +/* Watchdog's internal commands */ +#define CMD_ISA_IDLE 0x00 +#define CMD_ISA_VERSION_INTEGER 0x01 +#define CMD_ISA_VERSION_TENTH 0x02 +#define CMD_ISA_VERSION_HUNDRETH 0x03 +#define CMD_ISA_VERSION_MINOR 0x04 +#define CMD_ISA_SWITCH_SETTINGS 0x05 +#define CMD_ISA_DELAY_TIME_2SECS 0x0A +#define CMD_ISA_DELAY_TIME_4SECS 0x0B +#define CMD_ISA_DELAY_TIME_8SECS 0x0C + +/* + * We are using an kernel timer to do the pinging of the watchdog + * every ~500ms. We try to set the internal heartbeat of the + * watchdog to 2 ms. + */ + +#define WDT_INTERVAL (HZ/2+1) + +/* We can only use 1 card due to the /dev/watchdog restriction */ +static int cards_found; + +/* internal variables */ +static atomic_t open_allowed = ATOMIC_INIT(1); +static char expect_close; +static struct timer_list timer; +static unsigned long next_heartbeat; +static int temp_panic; +static int revision; /* The card's revision */ +static int supports_temp; /* Wether or not the card has a temperature device */ +static int command_mode; /* Wether or not the card is in command mode */ +static int initial_status; /* The card's boot status */ +static int current_readport; /* The cards I/O address */ +static spinlock_t io_lock; + +/* module parameters */ +#define WATCHDOG_HEARTBEAT 60 /* 60 sec default heartbeat */ +static int heartbeat = WATCHDOG_HEARTBEAT; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (2<=heartbeat<=7200, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * Internal functions + */ + +static int send_isa_command(int cmd) +{ + int i; + int control_status; + int port0, last_port0; /* Double read for stabilising */ + + /* The WCMD bit must be 1 and the command is only 4 bits in size */ + control_status = (cmd & 0x0F) | 0x80; + outb_p(control_status, current_readport + 2); + udelay(ISA_COMMAND_TIMEOUT); + + port0 = inb_p(current_readport); + for (i = 0; i < 25; ++i) { + last_port0 = port0; + port0 = inb_p(current_readport); + + if (port0 == last_port0) + break; /* Data is stable */ + + udelay (250); + } + + return port0; +} + +static int set_command_mode(void) +{ + int i, found=0, count=0; + + /* Set the card into command mode */ + spin_lock(&io_lock); + while ((!found) && (count < 3)) { + i = send_isa_command(CMD_ISA_IDLE); + + if (i == 0x00) + found = 1; + else if (i == 0xF3) { + /* Card does not like what we've done to it */ + outb_p(0x00, current_readport + 2); + udelay(1200); /* Spec says wait 1ms */ + outb_p(0x00, current_readport + 2); + udelay(ISA_COMMAND_TIMEOUT); + } + count++; + } + spin_unlock(&io_lock); + command_mode = found; + + return(found); +} + +static void unset_command_mode(void) +{ + /* Set the card into normal mode */ + spin_lock(&io_lock); + outb_p(0x00, current_readport + 2); + udelay(ISA_COMMAND_TIMEOUT); + spin_unlock(&io_lock); + + command_mode = 0; +} + +static void pcwd_timer_ping(unsigned long data) +{ + int wdrst_stat; + + /* If we got a heartbeat pulse within the WDT_INTERVAL + * we agree to ping the WDT */ + if(time_before(jiffies, next_heartbeat)) { + /* Ping the watchdog */ + spin_lock(&io_lock); + if (revision == PCWD_REVISION_A) { + /* Rev A cards are reset by setting the WD_WDRST bit in register 1 */ + wdrst_stat = inb_p(current_readport); + wdrst_stat &= 0x0F; + wdrst_stat |= WD_WDRST; + + outb_p(wdrst_stat, current_readport + 1); + } else { + /* Re-trigger watchdog by writing to port 0 */ + outb_p(0x00, current_readport); + } + + /* Re-set the timer interval */ + mod_timer(&timer, jiffies + WDT_INTERVAL); + + spin_unlock(&io_lock); + } else { + printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); + } +} + +static int pcwd_start(void) +{ + int stat_reg; + + next_heartbeat = jiffies + (heartbeat * HZ); + + /* Start the timer */ + mod_timer(&timer, jiffies + WDT_INTERVAL); + + /* Enable the port */ + if (revision == PCWD_REVISION_C) { + spin_lock(&io_lock); + outb_p(0x00, current_readport + 3); + udelay(ISA_COMMAND_TIMEOUT); + stat_reg = inb_p(current_readport + 2); + spin_unlock(&io_lock); + if (stat_reg & 0x10) { + printk(KERN_INFO PFX "Could not start watchdog\n"); + return -EIO; + } + } + return 0; +} + +static int pcwd_stop(void) +{ + int stat_reg; + + /* Stop the timer */ + del_timer(&timer); + + /* Disable the board */ + if (revision == PCWD_REVISION_C) { + spin_lock(&io_lock); + outb_p(0xA5, current_readport + 3); + udelay(ISA_COMMAND_TIMEOUT); + outb_p(0xA5, current_readport + 3); + udelay(ISA_COMMAND_TIMEOUT); + stat_reg = inb_p(current_readport + 2); + spin_unlock(&io_lock); + if ((stat_reg & 0x10) == 0) { + printk(KERN_INFO PFX "Could not stop watchdog\n"); + return -EIO; + } + } + return 0; +} + +static int pcwd_keepalive(void) +{ + /* user land ping */ + next_heartbeat = jiffies + (heartbeat * HZ); + return 0; +} + +static int pcwd_set_heartbeat(int t) +{ + if ((t < 2) || (t > 7200)) /* arbitrary upper limit */ + return -EINVAL; + + heartbeat = t; + return 0; +} + +static int pcwd_get_status(int *status) +{ + int card_status; + + *status=0; + spin_lock(&io_lock); + if (revision == PCWD_REVISION_A) + /* Rev A cards return status information from + * the base register, which is used for the + * temperature in other cards. */ + card_status = inb(current_readport); + else { + /* Rev C cards return card status in the base + * address + 1 register. And use different bits + * to indicate a card initiated reset, and an + * over-temperature condition. And the reboot + * status can be reset. */ + card_status = inb(current_readport + 1); + } + spin_unlock(&io_lock); + + if (revision == PCWD_REVISION_A) { + if (card_status & WD_WDRST) + *status |= WDIOF_CARDRESET; + + if (card_status & WD_T110) { + *status |= WDIOF_OVERHEAT; + if (temp_panic) { + printk (KERN_INFO PFX "Temperature overheat trip!\n"); + machine_power_off(); + } + } + } else { + if (card_status & WD_REVC_WTRP) + *status |= WDIOF_CARDRESET; + + if (card_status & WD_REVC_TTRP) { + *status |= WDIOF_OVERHEAT; + if (temp_panic) { + printk (KERN_INFO PFX "Temperature overheat trip!\n"); + machine_power_off(); + } + } + } + + return 0; +} + +static int pcwd_clear_status(void) +{ + if (revision == PCWD_REVISION_C) { + spin_lock(&io_lock); + outb_p(0x00, current_readport + 1); /* clear reset status */ + spin_unlock(&io_lock); + } + return 0; +} + +static int pcwd_get_temperature(int *temperature) +{ + /* check that port 0 gives temperature info and no command results */ + if (command_mode) + return -1; + + *temperature = 0; + if (!supports_temp) + return -ENODEV; + + /* + * Convert celsius to fahrenheit, since this was + * the decided 'standard' for this return value. + */ + spin_lock(&io_lock); + *temperature = ((inb(current_readport)) * 9 / 5) + 32; + spin_unlock(&io_lock); + + return 0; +} + +/* + * /dev/watchdog handling + */ + +static int pcwd_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int rv; + int status; + int temperature; + int new_heartbeat; + int __user *argp = (int __user *)arg; + static struct watchdog_info ident = { + .options = WDIOF_OVERHEAT | + WDIOF_CARDRESET | + WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "PCWD", + }; + + switch(cmd) { + default: + return -ENOIOCTLCMD; + + case WDIOC_GETSUPPORT: + if(copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + return 0; + + case WDIOC_GETSTATUS: + pcwd_get_status(&status); + return put_user(status, argp); + + case WDIOC_GETBOOTSTATUS: + return put_user(initial_status, argp); + + case WDIOC_GETTEMP: + if (pcwd_get_temperature(&temperature)) + return -EFAULT; + + return put_user(temperature, argp); + + case WDIOC_SETOPTIONS: + if (revision == PCWD_REVISION_C) + { + if(copy_from_user(&rv, argp, sizeof(int))) + return -EFAULT; + + if (rv & WDIOS_DISABLECARD) + { + return pcwd_stop(); + } + + if (rv & WDIOS_ENABLECARD) + { + return pcwd_start(); + } + + if (rv & WDIOS_TEMPPANIC) + { + temp_panic = 1; + } + } + return -EINVAL; + + case WDIOC_KEEPALIVE: + pcwd_keepalive(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, argp)) + return -EFAULT; + + if (pcwd_set_heartbeat(new_heartbeat)) + return -EINVAL; + + pcwd_keepalive(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, argp); + } + + return 0; +} + +static ssize_t pcwd_write(struct file *file, const char __user *buf, size_t len, + loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + pcwd_keepalive(); + } + return len; +} + +static int pcwd_open(struct inode *inode, struct file *file) +{ + if (!atomic_dec_and_test(&open_allowed) ) { + atomic_inc( &open_allowed ); + return -EBUSY; + } + + if (nowayout) + __module_get(THIS_MODULE); + + /* Activate */ + pcwd_start(); + pcwd_keepalive(); + return nonseekable_open(inode, file); +} + +static int pcwd_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + pcwd_stop(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + pcwd_keepalive(); + } + expect_close = 0; + atomic_inc( &open_allowed ); + return 0; +} + +/* + * /dev/temperature handling + */ + +static ssize_t pcwd_temp_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int temperature; + + if (pcwd_get_temperature(&temperature)) + return -EFAULT; + + if (copy_to_user(buf, &temperature, 1)) + return -EFAULT; + + return 1; +} + +static int pcwd_temp_open(struct inode *inode, struct file *file) +{ + if (!supports_temp) + return -ENODEV; + + return nonseekable_open(inode, file); +} + +static int pcwd_temp_close(struct inode *inode, struct file *file) +{ + return 0; +} + +/* + * Notify system + */ + +static int pcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +{ + if (code==SYS_DOWN || code==SYS_HALT) { + /* Turn the WDT off */ + pcwd_stop(); + } + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations pcwd_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = pcwd_write, + .ioctl = pcwd_ioctl, + .open = pcwd_open, + .release = pcwd_close, +}; + +static struct miscdevice pcwd_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &pcwd_fops, +}; + +static struct file_operations pcwd_temp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = pcwd_temp_read, + .open = pcwd_temp_open, + .release = pcwd_temp_close, +}; + +static struct miscdevice temp_miscdev = { + .minor = TEMP_MINOR, + .name = "temperature", + .fops = &pcwd_temp_fops, +}; + +static struct notifier_block pcwd_notifier = { + .notifier_call = pcwd_notify_sys, +}; + +/* + * Init & exit routines + */ + +static inline void get_support(void) +{ + if (inb(current_readport) != 0xF0) + supports_temp = 1; +} + +static inline int get_revision(void) +{ + int r = PCWD_REVISION_C; + + spin_lock(&io_lock); + /* REV A cards use only 2 io ports; test + * presumes a floating bus reads as 0xff. */ + if ((inb(current_readport + 2) == 0xFF) || + (inb(current_readport + 3) == 0xFF)) + r=PCWD_REVISION_A; + spin_unlock(&io_lock); + + return r; +} + +static inline char *get_firmware(void) +{ + int one, ten, hund, minor; + char *ret; + + ret = kmalloc(6, GFP_KERNEL); + if(ret == NULL) + return NULL; + + if (set_command_mode()) { + one = send_isa_command(CMD_ISA_VERSION_INTEGER); + ten = send_isa_command(CMD_ISA_VERSION_TENTH); + hund = send_isa_command(CMD_ISA_VERSION_HUNDRETH); + minor = send_isa_command(CMD_ISA_VERSION_MINOR); + sprintf(ret, "%c.%c%c%c", one, ten, hund, minor); + } + else + sprintf(ret, "ERROR"); + + unset_command_mode(); + return(ret); +} + +static inline int get_option_switches(void) +{ + int rv=0; + + if (set_command_mode()) { + /* Get switch settings */ + rv = send_isa_command(CMD_ISA_SWITCH_SETTINGS); + } + + unset_command_mode(); + return(rv); +} + +static int __devinit pcwatchdog_init(int base_addr) +{ + int ret; + char *firmware; + int option_switches; + + cards_found++; + if (cards_found == 1) + printk(KERN_INFO PFX "v%s Ken Hollis (kenji@bitgate.com)\n", WD_VER); + + if (cards_found > 1) { + printk(KERN_ERR PFX "This driver only supports 1 device\n"); + return -ENODEV; + } + + if (base_addr == 0x0000) { + printk(KERN_ERR PFX "No I/O-Address for card detected\n"); + return -ENODEV; + } + current_readport = base_addr; + + /* Check card's revision */ + revision = get_revision(); + + if (!request_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4, "PCWD")) { + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", + current_readport); + current_readport = 0x0000; + return -EIO; + } + + /* Initial variables */ + supports_temp = 0; + temp_panic = 0; + initial_status = 0x0000; + + /* get the boot_status */ + pcwd_get_status(&initial_status); + + /* clear the "card caused reboot" flag */ + pcwd_clear_status(); + + init_timer(&timer); + timer.function = pcwd_timer_ping; + timer.data = 0; + + /* Disable the board */ + pcwd_stop(); + + /* Check whether or not the card supports the temperature device */ + get_support(); + + /* Get some extra info from the hardware (in command/debug/diag mode) */ + if (revision == PCWD_REVISION_A) + printk(KERN_INFO PFX "ISA-PC Watchdog (REV.A) detected at port 0x%04x\n", current_readport); + else if (revision == PCWD_REVISION_C) { + firmware = get_firmware(); + printk(KERN_INFO PFX "ISA-PC Watchdog (REV.C) detected at port 0x%04x (Firmware version: %s)\n", + current_readport, firmware); + kfree(firmware); + option_switches = get_option_switches(); + printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", + option_switches, + ((option_switches & 0x10) ? "ON" : "OFF"), + ((option_switches & 0x08) ? "ON" : "OFF")); + + /* Reprogram internal heartbeat to 2 seconds */ + if (set_command_mode()) { + send_isa_command(CMD_ISA_DELAY_TIME_2SECS); + unset_command_mode(); + } + } else { + /* Should NEVER happen, unless get_revision() fails. */ + printk(KERN_INFO PFX "Unable to get revision\n"); + release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4); + current_readport = 0x0000; + return -1; + } + + if (supports_temp) + printk(KERN_INFO PFX "Temperature Option Detected\n"); + + if (initial_status & WDIOF_CARDRESET) + printk(KERN_INFO PFX "Previous reboot was caused by the card\n"); + + if (initial_status & WDIOF_OVERHEAT) { + printk(KERN_EMERG PFX "Card senses a CPU Overheat. Panicking!\n"); + printk(KERN_EMERG PFX "CPU Overheat\n"); + } + + if (initial_status == 0) + printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n"); + + /* Check that the heartbeat value is within it's range ; if not reset to the default */ + if (pcwd_set_heartbeat(heartbeat)) { + pcwd_set_heartbeat(WATCHDOG_HEARTBEAT); + printk(KERN_INFO PFX "heartbeat value must be 2<=heartbeat<=7200, using %d\n", + WATCHDOG_HEARTBEAT); + } + + ret = register_reboot_notifier(&pcwd_notifier); + if (ret) { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + ret); + release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4); + current_readport = 0x0000; + return ret; + } + + if (supports_temp) { + ret = misc_register(&temp_miscdev); + if (ret) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + TEMP_MINOR, ret); + unregister_reboot_notifier(&pcwd_notifier); + release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4); + current_readport = 0x0000; + return ret; + } + } + + ret = misc_register(&pcwd_miscdev); + if (ret) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + if (supports_temp) + misc_deregister(&temp_miscdev); + unregister_reboot_notifier(&pcwd_notifier); + release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4); + current_readport = 0x0000; + return ret; + } + + printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + + return 0; +} + +static void __devexit pcwatchdog_exit(void) +{ + /* Disable the board */ + if (!nowayout) + pcwd_stop(); + + /* Deregister */ + misc_deregister(&pcwd_miscdev); + if (supports_temp) + misc_deregister(&temp_miscdev); + unregister_reboot_notifier(&pcwd_notifier); + release_region(current_readport, (revision == PCWD_REVISION_A) ? 2 : 4); + current_readport = 0x0000; +} + +/* + * The ISA cards have a heartbeat bit in one of the registers, which + * register is card dependent. The heartbeat bit is monitored, and if + * found, is considered proof that a Berkshire card has been found. + * The initial rate is once per second at board start up, then twice + * per second for normal operation. + */ +static int __init pcwd_checkcard(int base_addr) +{ + int port0, last_port0; /* Reg 0, in case it's REV A */ + int port1, last_port1; /* Register 1 for REV C cards */ + int i; + int retval; + + if (!request_region (base_addr, 4, "PCWD")) { + printk (KERN_INFO PFX "Port 0x%04x unavailable\n", base_addr); + return 0; + } + + retval = 0; + + port0 = inb_p(base_addr); /* For REV A boards */ + port1 = inb_p(base_addr + 1); /* For REV C boards */ + if (port0 != 0xff || port1 != 0xff) { + /* Not an 'ff' from a floating bus, so must be a card! */ + for (i = 0; i < 4; ++i) { + + msleep(500); + + last_port0 = port0; + last_port1 = port1; + + port0 = inb_p(base_addr); + port1 = inb_p(base_addr + 1); + + /* Has either hearbeat bit changed? */ + if ((port0 ^ last_port0) & WD_HRTBT || + (port1 ^ last_port1) & WD_REVC_HRBT) { + retval = 1; + break; + } + } + } + release_region (base_addr, 4); + + return retval; +} + +/* + * These are the auto-probe addresses available. + * + * Revision A only uses ports 0x270 and 0x370. Revision C introduced 0x350. + * Revision A has an address range of 2 addresses, while Revision C has 4. + */ +static int pcwd_ioports[] = { 0x270, 0x350, 0x370, 0x000 }; + +static int __init pcwd_init_module(void) +{ + int i, found = 0; + + spin_lock_init(&io_lock); + + for (i = 0; pcwd_ioports[i] != 0; i++) { + if (pcwd_checkcard(pcwd_ioports[i])) { + if (!(pcwatchdog_init(pcwd_ioports[i]))) + found++; + } + } + + if (!found) { + printk (KERN_INFO PFX "No card detected, or port not available\n"); + return -ENODEV; + } + + return 0; +} + +static void __exit pcwd_cleanup_module(void) +{ + if (current_readport) + pcwatchdog_exit(); + return; +} + +module_init(pcwd_init_module); +module_exit(pcwd_cleanup_module); + +MODULE_AUTHOR("Ken Hollis <kenji@bitgate.com>"); +MODULE_DESCRIPTION("Berkshire ISA-PC Watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS_MISCDEV(TEMP_MINOR); diff --git a/drivers/char/watchdog/pcwd_pci.c b/drivers/char/watchdog/pcwd_pci.c new file mode 100644 index 000000000000..8ce066627326 --- /dev/null +++ b/drivers/char/watchdog/pcwd_pci.c @@ -0,0 +1,677 @@ +/* + * Berkshire PCI-PC Watchdog Card Driver + * + * (c) Copyright 2003 Wim Van Sebroeck <wim@iguana.be>. + * + * Based on source code of the following authors: + * Ken Hollis <kenji@bitgate.com>, + * Lindsay Harris <lindsay@bluegum.com>, + * Alan Cox <alan@redhat.com>, + * Matt Domsch <Matt_Domsch@dell.com>, + * Rob Radez <rob@osinvestor.com> + * + * 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 Wim Van Sebroeck nor Iguana vzw. admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + */ + +/* + * A bells and whistles driver is available from http://www.pcwd.de/ + * More info available at http://www.berkprod.com/ or http://www.pcwatchdog.com/ + */ + +/* + * Includes, defines, variables, module parameters, ... + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/delay.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 <linux/spinlock.h> + +#include <asm/uaccess.h> +#include <asm/io.h> + +/* Module and version information */ +#define WATCHDOG_VERSION "1.01" +#define WATCHDOG_DATE "15 Mar 2005" +#define WATCHDOG_DRIVER_NAME "PCI-PC Watchdog" +#define WATCHDOG_NAME "pcwd_pci" +#define PFX WATCHDOG_NAME ": " +#define DRIVER_VERSION WATCHDOG_DRIVER_NAME " driver, v" WATCHDOG_VERSION " (" WATCHDOG_DATE ")\n" + +/* Stuff for the PCI ID's */ +#ifndef PCI_VENDOR_ID_QUICKLOGIC +#define PCI_VENDOR_ID_QUICKLOGIC 0x11e3 +#endif + +#ifndef PCI_DEVICE_ID_WATCHDOG_PCIPCWD +#define PCI_DEVICE_ID_WATCHDOG_PCIPCWD 0x5030 +#endif + +/* + * These are the defines that describe the control status bits for the + * PCI-PC Watchdog card. + */ +#define WD_PCI_WTRP 0x01 /* Watchdog Trip status */ +#define WD_PCI_HRBT 0x02 /* Watchdog Heartbeat */ +#define WD_PCI_TTRP 0x04 /* Temperature Trip status */ + +/* according to documentation max. time to process a command for the pci + * watchdog card is 100 ms, so we give it 150 ms to do it's job */ +#define PCI_COMMAND_TIMEOUT 150 + +/* Watchdog's internal commands */ +#define CMD_GET_STATUS 0x04 +#define CMD_GET_FIRMWARE_VERSION 0x08 +#define CMD_READ_WATCHDOG_TIMEOUT 0x18 +#define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 + +/* We can only use 1 card due to the /dev/watchdog restriction */ +static int cards_found; + +/* internal variables */ +static int temp_panic; +static unsigned long is_active; +static char expect_release; +static struct { + int supports_temp; /* Wether or not the card has a temperature device */ + int boot_status; /* The card's boot status */ + unsigned long io_addr; /* The cards I/O address */ + spinlock_t io_lock; + struct pci_dev *pdev; +} pcipcwd_private; + +/* module parameters */ +#define WATCHDOG_HEARTBEAT 2 /* 2 sec default heartbeat */ +static int heartbeat = WATCHDOG_HEARTBEAT; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * Internal functions + */ + +static int send_command(int cmd, int *msb, int *lsb) +{ + int got_response, count; + + spin_lock(&pcipcwd_private.io_lock); + /* If a command requires data it should be written first. + * Data for commands with 8 bits of data should be written to port 4. + * Commands with 16 bits of data, should be written as LSB to port 4 + * and MSB to port 5. + * After the required data has been written then write the command to + * port 6. */ + outb_p(*lsb, pcipcwd_private.io_addr + 4); + outb_p(*msb, pcipcwd_private.io_addr + 5); + outb_p(cmd, pcipcwd_private.io_addr + 6); + + /* wait till the pci card processed the command, signaled by + * the WRSP bit in port 2 and give it a max. timeout of + * PCI_COMMAND_TIMEOUT to process */ + got_response = inb_p(pcipcwd_private.io_addr + 2) & 0x40; + for (count = 0; (count < PCI_COMMAND_TIMEOUT) && (!got_response); count++) { + mdelay(1); + got_response = inb_p(pcipcwd_private.io_addr + 2) & 0x40; + } + + if (got_response) { + /* read back response */ + *lsb = inb_p(pcipcwd_private.io_addr + 4); + *msb = inb_p(pcipcwd_private.io_addr + 5); + + /* clear WRSP bit */ + inb_p(pcipcwd_private.io_addr + 6); + } + spin_unlock(&pcipcwd_private.io_lock); + + return got_response; +} + +static int pcipcwd_start(void) +{ + int stat_reg; + + spin_lock(&pcipcwd_private.io_lock); + outb_p(0x00, pcipcwd_private.io_addr + 3); + udelay(1000); + + stat_reg = inb_p(pcipcwd_private.io_addr + 2); + spin_unlock(&pcipcwd_private.io_lock); + + if (stat_reg & 0x10) { + printk(KERN_ERR PFX "Card timer not enabled\n"); + return -1; + } + + return 0; +} + +static int pcipcwd_stop(void) +{ + int stat_reg; + + spin_lock(&pcipcwd_private.io_lock); + outb_p(0xA5, pcipcwd_private.io_addr + 3); + udelay(1000); + + outb_p(0xA5, pcipcwd_private.io_addr + 3); + udelay(1000); + + stat_reg = inb_p(pcipcwd_private.io_addr + 2); + spin_unlock(&pcipcwd_private.io_lock); + + if (!(stat_reg & 0x10)) { + printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n"); + return -1; + } + + return 0; +} + +static int pcipcwd_keepalive(void) +{ + /* Re-trigger watchdog by writing to port 0 */ + outb_p(0x42, pcipcwd_private.io_addr); + return 0; +} + +static int pcipcwd_set_heartbeat(int t) +{ + int t_msb = t / 256; + int t_lsb = t % 256; + + if ((t < 0x0001) || (t > 0xFFFF)) + return -EINVAL; + + /* Write new heartbeat to watchdog */ + send_command(CMD_WRITE_WATCHDOG_TIMEOUT, &t_msb, &t_lsb); + + heartbeat = t; + return 0; +} + +static int pcipcwd_get_status(int *status) +{ + int new_status; + + *status=0; + new_status = inb_p(pcipcwd_private.io_addr + 1); + if (new_status & WD_PCI_WTRP) + *status |= WDIOF_CARDRESET; + if (new_status & WD_PCI_TTRP) { + *status |= WDIOF_OVERHEAT; + if (temp_panic) + panic(PFX "Temperature overheat trip!\n"); + } + + return 0; +} + +static int pcipcwd_clear_status(void) +{ + outb_p(0x01, pcipcwd_private.io_addr + 1); + return 0; +} + +static int pcipcwd_get_temperature(int *temperature) +{ + *temperature = 0; + if (!pcipcwd_private.supports_temp) + return -ENODEV; + + /* + * Convert celsius to fahrenheit, since this was + * the decided 'standard' for this return value. + */ + *temperature = ((inb_p(pcipcwd_private.io_addr)) * 9 / 5) + 32; + + return 0; +} + +/* + * /dev/watchdog handling + */ + +static ssize_t pcipcwd_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 character */ + for (i = 0; i != len; i++) { + char c; + if(get_user(c, data+i)) + return -EFAULT; + if (c == 'V') + expect_release = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + pcipcwd_keepalive(); + } + return len; +} + +static int pcipcwd_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .options = WDIOF_OVERHEAT | + WDIOF_CARDRESET | + WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = WATCHDOG_DRIVER_NAME, + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, + sizeof (ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + { + int status; + + pcipcwd_get_status(&status); + + return put_user(status, p); + } + + case WDIOC_GETBOOTSTATUS: + return put_user(pcipcwd_private.boot_status, p); + + case WDIOC_GETTEMP: + { + int temperature; + + if (pcipcwd_get_temperature(&temperature)) + return -EFAULT; + + return put_user(temperature, p); + } + + case WDIOC_KEEPALIVE: + pcipcwd_keepalive(); + return 0; + + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if (get_user (new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + pcipcwd_stop(); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + pcipcwd_start(); + retval = 0; + } + + if (new_options & WDIOS_TEMPPANIC) { + temp_panic = 1; + retval = 0; + } + + return retval; + } + + case WDIOC_SETTIMEOUT: + { + int new_heartbeat; + + if (get_user(new_heartbeat, p)) + return -EFAULT; + + if (pcipcwd_set_heartbeat(new_heartbeat)) + return -EINVAL; + + pcipcwd_keepalive(); + /* Fall */ + } + + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + + default: + return -ENOIOCTLCMD; + } +} + +static int pcipcwd_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &is_active)) + return -EBUSY; + + /* Activate */ + pcipcwd_start(); + pcipcwd_keepalive(); + return nonseekable_open(inode, file); +} + +static int pcipcwd_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + */ + if (expect_release == 42) { + pcipcwd_stop(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + pcipcwd_keepalive(); + } + expect_release = 0; + clear_bit(0, &is_active); + return 0; +} + +/* + * /dev/temperature handling + */ + +static ssize_t pcipcwd_temp_read(struct file *file, char __user *data, + size_t len, loff_t *ppos) +{ + int temperature; + + if (pcipcwd_get_temperature(&temperature)) + return -EFAULT; + + if (copy_to_user (data, &temperature, 1)) + return -EFAULT; + + return 1; +} + +static int pcipcwd_temp_open(struct inode *inode, struct file *file) +{ + if (!pcipcwd_private.supports_temp) + return -ENODEV; + + return nonseekable_open(inode, file); +} + +static int pcipcwd_temp_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/* + * Notify system + */ + +static int pcipcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +{ + if (code==SYS_DOWN || code==SYS_HALT) { + /* Turn the WDT off */ + pcipcwd_stop(); + } + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations pcipcwd_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = pcipcwd_write, + .ioctl = pcipcwd_ioctl, + .open = pcipcwd_open, + .release = pcipcwd_release, +}; + +static struct miscdevice pcipcwd_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &pcipcwd_fops, +}; + +static struct file_operations pcipcwd_temp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = pcipcwd_temp_read, + .open = pcipcwd_temp_open, + .release = pcipcwd_temp_release, +}; + +static struct miscdevice pcipcwd_temp_miscdev = { + .minor = TEMP_MINOR, + .name = "temperature", + .fops = &pcipcwd_temp_fops, +}; + +static struct notifier_block pcipcwd_notifier = { + .notifier_call = pcipcwd_notify_sys, +}; + +/* + * Init & exit routines + */ + +static inline void check_temperature_support(void) +{ + if (inb_p(pcipcwd_private.io_addr) != 0xF0) + pcipcwd_private.supports_temp = 1; +} + +static int __devinit pcipcwd_card_init(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + int ret = -EIO; + int got_fw_rev, fw_rev_major, fw_rev_minor; + char fw_ver_str[20]; + char option_switches; + + cards_found++; + if (cards_found == 1) + printk(KERN_INFO PFX DRIVER_VERSION); + + if (cards_found > 1) { + printk(KERN_ERR PFX "This driver only supports 1 device\n"); + return -ENODEV; + } + + if (pci_enable_device(pdev)) { + printk(KERN_ERR PFX "Not possible to enable PCI Device\n"); + return -ENODEV; + } + + if (pci_resource_start(pdev, 0) == 0x0000) { + printk(KERN_ERR PFX "No I/O-Address for card detected\n"); + ret = -ENODEV; + goto err_out_disable_device; + } + + pcipcwd_private.pdev = pdev; + pcipcwd_private.io_addr = pci_resource_start(pdev, 0); + + if (pci_request_regions(pdev, WATCHDOG_NAME)) { + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", + (int) pcipcwd_private.io_addr); + ret = -EIO; + goto err_out_disable_device; + } + + /* get the boot_status */ + pcipcwd_get_status(&pcipcwd_private.boot_status); + + /* clear the "card caused reboot" flag */ + pcipcwd_clear_status(); + + /* disable card */ + pcipcwd_stop(); + + /* Check whether or not the card supports the temperature device */ + check_temperature_support(); + + /* Get the Firmware Version */ + got_fw_rev = send_command(CMD_GET_FIRMWARE_VERSION, &fw_rev_major, &fw_rev_minor); + if (got_fw_rev) { + sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor); + } else { + sprintf(fw_ver_str, "<card no answer>"); + } + + /* Get switch settings */ + option_switches = inb_p(pcipcwd_private.io_addr + 3); + + printk(KERN_INFO PFX "Found card at port 0x%04x (Firmware: %s) %s temp option\n", + (int) pcipcwd_private.io_addr, fw_ver_str, + (pcipcwd_private.supports_temp ? "with" : "without")); + + printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", + option_switches, + ((option_switches & 0x10) ? "ON" : "OFF"), + ((option_switches & 0x08) ? "ON" : "OFF")); + + if (pcipcwd_private.boot_status & WDIOF_CARDRESET) + printk(KERN_INFO PFX "Previous reset was caused by the Watchdog card\n"); + + if (pcipcwd_private.boot_status & WDIOF_OVERHEAT) + printk(KERN_INFO PFX "Card sensed a CPU Overheat\n"); + + if (pcipcwd_private.boot_status == 0) + printk(KERN_INFO PFX "No previous trip detected - Cold boot or reset\n"); + + /* Check that the heartbeat value is within it's range ; if not reset to the default */ + if (pcipcwd_set_heartbeat(heartbeat)) { + pcipcwd_set_heartbeat(WATCHDOG_HEARTBEAT); + printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", + WATCHDOG_HEARTBEAT); + } + + ret = register_reboot_notifier(&pcipcwd_notifier); + if (ret != 0) { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + ret); + goto err_out_release_region; + } + + if (pcipcwd_private.supports_temp) { + ret = misc_register(&pcipcwd_temp_miscdev); + if (ret != 0) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + TEMP_MINOR, ret); + goto err_out_unregister_reboot; + } + } + + ret = misc_register(&pcipcwd_miscdev); + if (ret != 0) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto err_out_misc_deregister; + } + + printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + + return 0; + +err_out_misc_deregister: + if (pcipcwd_private.supports_temp) + misc_deregister(&pcipcwd_temp_miscdev); +err_out_unregister_reboot: + unregister_reboot_notifier(&pcipcwd_notifier); +err_out_release_region: + pci_release_regions(pdev); +err_out_disable_device: + pci_disable_device(pdev); + return ret; +} + +static void __devexit pcipcwd_card_exit(struct pci_dev *pdev) +{ + /* Stop the timer before we leave */ + if (!nowayout) + pcipcwd_stop(); + + /* Deregister */ + misc_deregister(&pcipcwd_miscdev); + if (pcipcwd_private.supports_temp) + misc_deregister(&pcipcwd_temp_miscdev); + unregister_reboot_notifier(&pcipcwd_notifier); + pci_release_regions(pdev); + pci_disable_device(pdev); + cards_found--; +} + +static struct pci_device_id pcipcwd_pci_tbl[] = { + { PCI_VENDOR_ID_QUICKLOGIC, PCI_DEVICE_ID_WATCHDOG_PCIPCWD, + PCI_ANY_ID, PCI_ANY_ID, }, + { 0 }, /* End of list */ +}; +MODULE_DEVICE_TABLE(pci, pcipcwd_pci_tbl); + +static struct pci_driver pcipcwd_driver = { + .name = WATCHDOG_NAME, + .id_table = pcipcwd_pci_tbl, + .probe = pcipcwd_card_init, + .remove = __devexit_p(pcipcwd_card_exit), +}; + +static int __init pcipcwd_init_module(void) +{ + spin_lock_init (&pcipcwd_private.io_lock); + + return pci_register_driver(&pcipcwd_driver); +} + +static void __exit pcipcwd_cleanup_module(void) +{ + pci_unregister_driver(&pcipcwd_driver); +} + +module_init(pcipcwd_init_module); +module_exit(pcipcwd_cleanup_module); + +MODULE_AUTHOR("Wim Van Sebroeck <wim@iguana.be>"); +MODULE_DESCRIPTION("Berkshire PCI-PC Watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS_MISCDEV(TEMP_MINOR); diff --git a/drivers/char/watchdog/pcwd_usb.c b/drivers/char/watchdog/pcwd_usb.c new file mode 100644 index 000000000000..1127201d73b8 --- /dev/null +++ b/drivers/char/watchdog/pcwd_usb.c @@ -0,0 +1,796 @@ +/* + * Berkshire USB-PC Watchdog Card Driver + * + * (c) Copyright 2004 Wim Van Sebroeck <wim@iguana.be>. + * + * Based on source code of the following authors: + * Ken Hollis <kenji@bitgate.com>, + * Alan Cox <alan@redhat.com>, + * Matt Domsch <Matt_Domsch@dell.com>, + * Rob Radez <rob@osinvestor.com>, + * Greg Kroah-Hartman <greg@kroah.com> + * + * 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 Wim Van Sebroeck nor Iguana vzw. admit liability nor + * provide warranty for any of this software. This material is + * provided "AS-IS" and at no charge. + * + * Thanks also to Simon Machell at Berkshire Products Inc. for + * providing the test hardware. More info is available at + * http://www.berkprod.com/ or http://www.pcwatchdog.com/ + */ + +#include <linux/config.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/fs.h> +#include <linux/smp_lock.h> +#include <linux/completion.h> +#include <asm/uaccess.h> +#include <linux/usb.h> + + +#ifdef CONFIG_USB_DEBUG + static int debug = 1; +#else + static int debug; +#endif + +/* Use our own dbg macro */ +#undef dbg +#define dbg(format, arg...) do { if (debug) printk(KERN_DEBUG PFX format "\n" , ## arg); } while (0) + + +/* Module and Version Information */ +#define DRIVER_VERSION "1.01" +#define DRIVER_DATE "15 Mar 2005" +#define DRIVER_AUTHOR "Wim Van Sebroeck <wim@iguana.be>" +#define DRIVER_DESC "Berkshire USB-PC Watchdog driver" +#define DRIVER_LICENSE "GPL" +#define DRIVER_NAME "pcwd_usb" +#define PFX DRIVER_NAME ": " + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE(DRIVER_LICENSE); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS_MISCDEV(TEMP_MINOR); + +/* Module Parameters */ +module_param(debug, int, 0); +MODULE_PARM_DESC(debug, "Debug enabled or not"); + +#define WATCHDOG_HEARTBEAT 2 /* 2 sec default heartbeat */ +static int heartbeat = WATCHDOG_HEARTBEAT; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* The vendor and product id's for the USB-PC Watchdog card */ +#define USB_PCWD_VENDOR_ID 0x0c98 +#define USB_PCWD_PRODUCT_ID 0x1140 + +/* table of devices that work with this driver */ +static struct usb_device_id usb_pcwd_table [] = { + { USB_DEVICE(USB_PCWD_VENDOR_ID, USB_PCWD_PRODUCT_ID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE (usb, usb_pcwd_table); + +/* according to documentation max. time to process a command for the USB + * watchdog card is 100 or 200 ms, so we give it 250 ms to do it's job */ +#define USB_COMMAND_TIMEOUT 250 + +/* Watchdog's internal commands */ +#define CMD_READ_TEMP 0x02 /* Read Temperature; Re-trigger Watchdog */ +#define CMD_TRIGGER CMD_READ_TEMP +#define CMD_GET_STATUS 0x04 /* Get Status Information */ +#define CMD_GET_FIRMWARE_VERSION 0x08 /* Get Firmware Version */ +#define CMD_GET_DIP_SWITCH_SETTINGS 0x0c /* Get Dip Switch Settings */ +#define CMD_READ_WATCHDOG_TIMEOUT 0x18 /* Read Current Watchdog Time */ +#define CMD_WRITE_WATCHDOG_TIMEOUT 0x19 /* Write Current Watchdog Time */ +#define CMD_ENABLE_WATCHDOG 0x30 /* Enable / Disable Watchdog */ +#define CMD_DISABLE_WATCHDOG CMD_ENABLE_WATCHDOG + +/* Some defines that I like to be somewhere else like include/linux/usb_hid.h */ +#define HID_REQ_SET_REPORT 0x09 +#define HID_DT_REPORT (USB_TYPE_CLASS | 0x02) + +/* We can only use 1 card due to the /dev/watchdog restriction */ +static int cards_found; + +/* some internal variables */ +static unsigned long is_active; +static char expect_release; + +/* Structure to hold all of our device specific stuff */ +struct usb_pcwd_private { + struct usb_device * udev; /* save off the usb device pointer */ + struct usb_interface * interface; /* the interface for this device */ + + unsigned int interface_number; /* the interface number used for cmd's */ + + unsigned char * intr_buffer; /* the buffer to intr data */ + dma_addr_t intr_dma; /* the dma address for the intr buffer */ + size_t intr_size; /* the size of the intr buffer */ + struct urb * intr_urb; /* the urb used for the intr pipe */ + + unsigned char cmd_command; /* The command that is reported back */ + unsigned char cmd_data_msb; /* The data MSB that is reported back */ + unsigned char cmd_data_lsb; /* The data LSB that is reported back */ + atomic_t cmd_received; /* true if we received a report after a command */ + + int exists; /* Wether or not the device exists */ + struct semaphore sem; /* locks this structure */ +}; +static struct usb_pcwd_private *usb_pcwd_device; + +/* prevent races between open() and disconnect() */ +static DECLARE_MUTEX (disconnect_sem); + +/* local function prototypes */ +static int usb_pcwd_probe (struct usb_interface *interface, const struct usb_device_id *id); +static void usb_pcwd_disconnect (struct usb_interface *interface); + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver usb_pcwd_driver = { + .owner = THIS_MODULE, + .name = DRIVER_NAME, + .probe = usb_pcwd_probe, + .disconnect = usb_pcwd_disconnect, + .id_table = usb_pcwd_table, +}; + + +static void usb_pcwd_intr_done(struct urb *urb, struct pt_regs *regs) +{ + struct usb_pcwd_private *usb_pcwd = (struct usb_pcwd_private *)urb->context; + unsigned char *data = usb_pcwd->intr_buffer; + int retval; + + switch (urb->status) { + case 0: /* success */ + break; + case -ECONNRESET: /* unlink */ + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); + return; + /* -EPIPE: should clear the halt */ + default: /* error */ + dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); + goto resubmit; + } + + dbg("received following data cmd=0x%02x msb=0x%02x lsb=0x%02x", + data[0], data[1], data[2]); + + usb_pcwd->cmd_command = data[0]; + usb_pcwd->cmd_data_msb = data[1]; + usb_pcwd->cmd_data_lsb = data[2]; + + /* notify anyone waiting that the cmd has finished */ + atomic_set (&usb_pcwd->cmd_received, 1); + +resubmit: + retval = usb_submit_urb (urb, GFP_ATOMIC); + if (retval) + printk(KERN_ERR PFX "can't resubmit intr, usb_submit_urb failed with result %d\n", + retval); +} + +static int usb_pcwd_send_command(struct usb_pcwd_private *usb_pcwd, unsigned char cmd, + unsigned char *msb, unsigned char *lsb) +{ + int got_response, count; + unsigned char buf[6]; + + /* We will not send any commands if the USB PCWD device does not exist */ + if ((!usb_pcwd) || (!usb_pcwd->exists)) + return -1; + + /* The USB PC Watchdog uses a 6 byte report format. The board currently uses + * only 3 of the six bytes of the report. */ + buf[0] = cmd; /* Byte 0 = CMD */ + buf[1] = *msb; /* Byte 1 = Data MSB */ + buf[2] = *lsb; /* Byte 2 = Data LSB */ + buf[3] = buf[4] = buf[5] = 0; /* All other bytes not used */ + + dbg("sending following data cmd=0x%02x msb=0x%02x lsb=0x%02x", + buf[0], buf[1], buf[2]); + + atomic_set (&usb_pcwd->cmd_received, 0); + + if (usb_control_msg(usb_pcwd->udev, usb_sndctrlpipe(usb_pcwd->udev, 0), + HID_REQ_SET_REPORT, HID_DT_REPORT, + 0x0200, usb_pcwd->interface_number, buf, sizeof(buf), + USB_COMMAND_TIMEOUT) != sizeof(buf)) { + dbg("usb_pcwd_send_command: error in usb_control_msg for cmd 0x%x 0x%x 0x%x\n", cmd, *msb, *lsb); + } + /* wait till the usb card processed the command, + * with a max. timeout of USB_COMMAND_TIMEOUT */ + got_response = 0; + for (count = 0; (count < USB_COMMAND_TIMEOUT) && (!got_response); count++) { + mdelay(1); + if (atomic_read (&usb_pcwd->cmd_received)) + got_response = 1; + } + + if ((got_response) && (cmd == usb_pcwd->cmd_command)) { + /* read back response */ + *msb = usb_pcwd->cmd_data_msb; + *lsb = usb_pcwd->cmd_data_lsb; + } + + return got_response; +} + +static int usb_pcwd_start(struct usb_pcwd_private *usb_pcwd) +{ + unsigned char msb = 0x00; + unsigned char lsb = 0x00; + int retval; + + /* Enable Watchdog */ + retval = usb_pcwd_send_command(usb_pcwd, CMD_ENABLE_WATCHDOG, &msb, &lsb); + + if ((retval == 0) || (lsb == 0)) { + printk(KERN_ERR PFX "Card did not acknowledge enable attempt\n"); + return -1; + } + + return 0; +} + +static int usb_pcwd_stop(struct usb_pcwd_private *usb_pcwd) +{ + unsigned char msb = 0xA5; + unsigned char lsb = 0xC3; + int retval; + + /* Disable Watchdog */ + retval = usb_pcwd_send_command(usb_pcwd, CMD_DISABLE_WATCHDOG, &msb, &lsb); + + if ((retval == 0) || (lsb != 0)) { + printk(KERN_ERR PFX "Card did not acknowledge disable attempt\n"); + return -1; + } + + return 0; +} + +static int usb_pcwd_keepalive(struct usb_pcwd_private *usb_pcwd) +{ + unsigned char dummy; + + /* Re-trigger Watchdog */ + usb_pcwd_send_command(usb_pcwd, CMD_TRIGGER, &dummy, &dummy); + + return 0; +} + +static int usb_pcwd_set_heartbeat(struct usb_pcwd_private *usb_pcwd, int t) +{ + unsigned char msb = t / 256; + unsigned char lsb = t % 256; + + if ((t < 0x0001) || (t > 0xFFFF)) + return -EINVAL; + + /* Write new heartbeat to watchdog */ + usb_pcwd_send_command(usb_pcwd, CMD_WRITE_WATCHDOG_TIMEOUT, &msb, &lsb); + + heartbeat = t; + return 0; +} + +static int usb_pcwd_get_temperature(struct usb_pcwd_private *usb_pcwd, int *temperature) +{ + unsigned char msb, lsb; + + usb_pcwd_send_command(usb_pcwd, CMD_READ_TEMP, &msb, &lsb); + + /* + * Convert celsius to fahrenheit, since this was + * the decided 'standard' for this return value. + */ + *temperature = (lsb * 9 / 5) + 32; + + return 0; +} + +/* + * /dev/watchdog handling + */ + +static ssize_t usb_pcwd_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 character */ + for (i = 0; i != len; i++) { + char c; + if(get_user(c, data+i)) + return -EFAULT; + if (c == 'V') + expect_release = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + usb_pcwd_keepalive(usb_pcwd_device); + } + return len; +} + +static int usb_pcwd_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | + WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = DRIVER_NAME, + }; + + 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_GETTEMP: + { + int temperature; + + if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature)) + return -EFAULT; + + return put_user(temperature, p); + } + + case WDIOC_KEEPALIVE: + usb_pcwd_keepalive(usb_pcwd_device); + return 0; + + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if (get_user (new_options, p)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + usb_pcwd_stop(usb_pcwd_device); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + usb_pcwd_start(usb_pcwd_device); + retval = 0; + } + + return retval; + } + + case WDIOC_SETTIMEOUT: + { + int new_heartbeat; + + if (get_user(new_heartbeat, p)) + return -EFAULT; + + if (usb_pcwd_set_heartbeat(usb_pcwd_device, new_heartbeat)) + return -EINVAL; + + usb_pcwd_keepalive(usb_pcwd_device); + /* Fall */ + } + + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + + default: + return -ENOIOCTLCMD; + } +} + +static int usb_pcwd_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &is_active)) + return -EBUSY; + + /* Activate */ + usb_pcwd_start(usb_pcwd_device); + usb_pcwd_keepalive(usb_pcwd_device); + return nonseekable_open(inode, file); +} + +static int usb_pcwd_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + */ + if (expect_release == 42) { + usb_pcwd_stop(usb_pcwd_device); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + usb_pcwd_keepalive(usb_pcwd_device); + } + expect_release = 0; + clear_bit(0, &is_active); + return 0; +} + +/* + * /dev/temperature handling + */ + +static ssize_t usb_pcwd_temperature_read(struct file *file, char __user *data, + size_t len, loff_t *ppos) +{ + int temperature; + + if (usb_pcwd_get_temperature(usb_pcwd_device, &temperature)) + return -EFAULT; + + if (copy_to_user(data, &temperature, 1)) + return -EFAULT; + + return 1; +} + +static int usb_pcwd_temperature_open(struct inode *inode, struct file *file) +{ + return nonseekable_open(inode, file); +} + +static int usb_pcwd_temperature_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/* + * Notify system + */ + +static int usb_pcwd_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +{ + if (code==SYS_DOWN || code==SYS_HALT) { + /* Turn the WDT off */ + usb_pcwd_stop(usb_pcwd_device); + } + + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations usb_pcwd_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = usb_pcwd_write, + .ioctl = usb_pcwd_ioctl, + .open = usb_pcwd_open, + .release = usb_pcwd_release, +}; + +static struct miscdevice usb_pcwd_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &usb_pcwd_fops, +}; + +static struct file_operations usb_pcwd_temperature_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = usb_pcwd_temperature_read, + .open = usb_pcwd_temperature_open, + .release = usb_pcwd_temperature_release, +}; + +static struct miscdevice usb_pcwd_temperature_miscdev = { + .minor = TEMP_MINOR, + .name = "temperature", + .fops = &usb_pcwd_temperature_fops, +}; + +static struct notifier_block usb_pcwd_notifier = { + .notifier_call = usb_pcwd_notify_sys, +}; + +/** + * usb_pcwd_delete + */ +static inline void usb_pcwd_delete (struct usb_pcwd_private *usb_pcwd) +{ + if (usb_pcwd->intr_urb != NULL) + usb_free_urb (usb_pcwd->intr_urb); + if (usb_pcwd->intr_buffer != NULL) + usb_buffer_free(usb_pcwd->udev, usb_pcwd->intr_size, + usb_pcwd->intr_buffer, usb_pcwd->intr_dma); + kfree (usb_pcwd); +} + +/** + * usb_pcwd_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int usb_pcwd_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + struct usb_pcwd_private *usb_pcwd = NULL; + int pipe, maxp; + int retval = -ENOMEM; + int got_fw_rev; + unsigned char fw_rev_major, fw_rev_minor; + char fw_ver_str[20]; + unsigned char option_switches, dummy; + + cards_found++; + if (cards_found > 1) { + printk(KERN_ERR PFX "This driver only supports 1 device\n"); + return -ENODEV; + } + + /* get the active interface descriptor */ + iface_desc = interface->cur_altsetting; + + /* check out that we have a HID device */ + if (!(iface_desc->desc.bInterfaceClass == USB_CLASS_HID)) { + printk(KERN_ERR PFX "The device isn't a Human Interface Device\n"); + return -ENODEV; + } + + /* check out the endpoint: it has to be Interrupt & IN */ + endpoint = &iface_desc->endpoint[0].desc; + + if (!((endpoint->bEndpointAddress & USB_DIR_IN) && + ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) + == USB_ENDPOINT_XFER_INT))) { + /* we didn't find a Interrupt endpoint with direction IN */ + printk(KERN_ERR PFX "Couldn't find an INTR & IN endpoint\n"); + return -ENODEV; + } + + /* get a handle to the interrupt data pipe */ + pipe = usb_rcvintpipe(udev, endpoint->bEndpointAddress); + maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); + + /* allocate memory for our device and initialize it */ + usb_pcwd = kmalloc (sizeof(struct usb_pcwd_private), GFP_KERNEL); + if (usb_pcwd == NULL) { + printk(KERN_ERR PFX "Out of memory\n"); + goto error; + } + memset (usb_pcwd, 0x00, sizeof (*usb_pcwd)); + + usb_pcwd_device = usb_pcwd; + + init_MUTEX (&usb_pcwd->sem); + usb_pcwd->udev = udev; + usb_pcwd->interface = interface; + usb_pcwd->interface_number = iface_desc->desc.bInterfaceNumber; + usb_pcwd->intr_size = (le16_to_cpu(endpoint->wMaxPacketSize) > 8 ? le16_to_cpu(endpoint->wMaxPacketSize) : 8); + + /* set up the memory buffer's */ + if (!(usb_pcwd->intr_buffer = usb_buffer_alloc(udev, usb_pcwd->intr_size, SLAB_ATOMIC, &usb_pcwd->intr_dma))) { + printk(KERN_ERR PFX "Out of memory\n"); + goto error; + } + + /* allocate the urb's */ + usb_pcwd->intr_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!usb_pcwd->intr_urb) { + printk(KERN_ERR PFX "Out of memory\n"); + goto error; + } + + /* initialise the intr urb's */ + usb_fill_int_urb(usb_pcwd->intr_urb, udev, pipe, + usb_pcwd->intr_buffer, usb_pcwd->intr_size, + usb_pcwd_intr_done, usb_pcwd, endpoint->bInterval); + usb_pcwd->intr_urb->transfer_dma = usb_pcwd->intr_dma; + usb_pcwd->intr_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + /* register our interrupt URB with the USB system */ + if (usb_submit_urb(usb_pcwd->intr_urb, GFP_KERNEL)) { + printk(KERN_ERR PFX "Problem registering interrupt URB\n"); + retval = -EIO; /* failure */ + goto error; + } + + /* The device exists and can be communicated with */ + usb_pcwd->exists = 1; + + /* disable card */ + usb_pcwd_stop(usb_pcwd); + + /* Get the Firmware Version */ + got_fw_rev = usb_pcwd_send_command(usb_pcwd, CMD_GET_FIRMWARE_VERSION, &fw_rev_major, &fw_rev_minor); + if (got_fw_rev) { + sprintf(fw_ver_str, "%u.%02u", fw_rev_major, fw_rev_minor); + } else { + sprintf(fw_ver_str, "<card no answer>"); + } + + printk(KERN_INFO PFX "Found card (Firmware: %s) with temp option\n", + fw_ver_str); + + /* Get switch settings */ + usb_pcwd_send_command(usb_pcwd, CMD_GET_DIP_SWITCH_SETTINGS, &dummy, &option_switches); + + printk(KERN_INFO PFX "Option switches (0x%02x): Temperature Reset Enable=%s, Power On Delay=%s\n", + option_switches, + ((option_switches & 0x10) ? "ON" : "OFF"), + ((option_switches & 0x08) ? "ON" : "OFF")); + + /* Check that the heartbeat value is within it's range ; if not reset to the default */ + if (usb_pcwd_set_heartbeat(usb_pcwd, heartbeat)) { + usb_pcwd_set_heartbeat(usb_pcwd, WATCHDOG_HEARTBEAT); + printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", + WATCHDOG_HEARTBEAT); + } + + retval = register_reboot_notifier(&usb_pcwd_notifier); + if (retval != 0) { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + retval); + goto error; + } + + retval = misc_register(&usb_pcwd_temperature_miscdev); + if (retval != 0) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + TEMP_MINOR, retval); + goto err_out_unregister_reboot; + } + + retval = misc_register(&usb_pcwd_miscdev); + if (retval != 0) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, retval); + goto err_out_misc_deregister; + } + + /* we can register the device now, as it is ready */ + usb_set_intfdata (interface, usb_pcwd); + + printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + + return 0; + +err_out_misc_deregister: + misc_deregister(&usb_pcwd_temperature_miscdev); +err_out_unregister_reboot: + unregister_reboot_notifier(&usb_pcwd_notifier); +error: + usb_pcwd_delete (usb_pcwd); + usb_pcwd_device = NULL; + return retval; +} + + +/** + * usb_pcwd_disconnect + * + * Called by the usb core when the device is removed from the system. + * + * This routine guarantees that the driver will not submit any more urbs + * by clearing dev->udev. + */ +static void usb_pcwd_disconnect(struct usb_interface *interface) +{ + struct usb_pcwd_private *usb_pcwd; + + /* prevent races with open() */ + down (&disconnect_sem); + + usb_pcwd = usb_get_intfdata (interface); + usb_set_intfdata (interface, NULL); + + down (&usb_pcwd->sem); + + /* Stop the timer before we leave */ + if (!nowayout) + usb_pcwd_stop(usb_pcwd); + + /* We should now stop communicating with the USB PCWD device */ + usb_pcwd->exists = 0; + + /* Deregister */ + misc_deregister(&usb_pcwd_miscdev); + misc_deregister(&usb_pcwd_temperature_miscdev); + unregister_reboot_notifier(&usb_pcwd_notifier); + + up (&usb_pcwd->sem); + + /* Delete the USB PCWD device */ + usb_pcwd_delete(usb_pcwd); + + cards_found--; + + up (&disconnect_sem); + + printk(KERN_INFO PFX "USB PC Watchdog disconnected\n"); +} + + + +/** + * usb_pcwd_init + */ +static int __init usb_pcwd_init(void) +{ + int result; + + /* register this driver with the USB subsystem */ + result = usb_register(&usb_pcwd_driver); + if (result) { + printk(KERN_ERR PFX "usb_register failed. Error number %d\n", + result); + return result; + } + + printk(KERN_INFO PFX DRIVER_DESC " v" DRIVER_VERSION " (" DRIVER_DATE ")\n"); + return 0; +} + + +/** + * usb_pcwd_exit + */ +static void __exit usb_pcwd_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&usb_pcwd_driver); +} + + +module_init (usb_pcwd_init); +module_exit (usb_pcwd_exit); diff --git a/drivers/char/watchdog/s3c2410_wdt.c b/drivers/char/watchdog/s3c2410_wdt.c new file mode 100644 index 000000000000..1699d2c28ce5 --- /dev/null +++ b/drivers/char/watchdog/s3c2410_wdt.c @@ -0,0 +1,516 @@ +/* linux/drivers/char/watchdog/s3c2410_wdt.c + * + * Copyright (c) 2004 Simtec Electronics + * Ben Dooks <ben@simtec.co.uk> + * + * S3C2410 Watchdog Timer Support + * + * Based on, softdog.c by Alan Cox, + * (c) Copyright 1996 Alan Cox <alan@redhat.com> + * + * 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. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Changelog: + * 05-Oct-2004 BJD Added semaphore init to stop crashes on open + * Fixed tmr_count / wdt_count confusion + * Added configurable debug + * + * 11-Jan-2004 BJD Fixed divide-by-2 in timeout code + * + * 10-Mar-2005 LCVR Changed S3C2410_VA to S3C24XX_VA +*/ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/interrupt.h> + +#include <asm/uaccess.h> +#include <asm/io.h> + +#include <asm/arch/map.h> +#include <asm/hardware/clock.h> + +#undef S3C24XX_VA_WATCHDOG +#define S3C24XX_VA_WATCHDOG (0) + +#include <asm/arch/regs-watchdog.h> + +#define PFX "s3c2410-wdt: " + +#define CONFIG_S3C2410_WATCHDOG_ATBOOT (0) +#define CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME (15) + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +static int tmr_margin = CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME; +static int tmr_atboot = CONFIG_S3C2410_WATCHDOG_ATBOOT; +static int soft_noboot = 0; +static int debug = 0; + +module_param(tmr_margin, int, 0); +module_param(tmr_atboot, int, 0); +module_param(nowayout, int, 0); +module_param(soft_noboot, int, 0); +module_param(debug, int, 0); + +MODULE_PARM_DESC(tmr_margin, "Watchdog tmr_margin in seconds. default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME) ")"); + +MODULE_PARM_DESC(tmr_atboot, "Watchdog is started at boot time if set to 1, default=" __MODULE_STRING(CONFIG_S3C2410_WATCHDOG_ATBOOT)); + +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +MODULE_PARM_DESC(soft_noboot, "Watchdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); + +MODULE_PARM_DESC(debug, "Watchdog debug, set to >1 for debug, (default 0)"); + + +typedef enum close_state { + CLOSE_STATE_NOT, + CLOSE_STATE_ALLOW=0x4021 +} close_state_t; + +static DECLARE_MUTEX(open_lock); + +static struct resource *wdt_mem; +static struct resource *wdt_irq; +static struct clk *wdt_clock; +static void __iomem *wdt_base; +static unsigned int wdt_count; +static close_state_t allow_close; + +/* watchdog control routines */ + +#define DBG(msg...) do { \ + if (debug) \ + printk(KERN_INFO msg); \ + } while(0) + +/* functions */ + +static int s3c2410wdt_keepalive(void) +{ + writel(wdt_count, wdt_base + S3C2410_WTCNT); + return 0; +} + +static int s3c2410wdt_stop(void) +{ + unsigned long wtcon; + + wtcon = readl(wdt_base + S3C2410_WTCON); + wtcon &= ~(S3C2410_WTCON_ENABLE | S3C2410_WTCON_RSTEN); + writel(wtcon, wdt_base + S3C2410_WTCON); + + return 0; +} + +static int s3c2410wdt_start(void) +{ + unsigned long wtcon; + + s3c2410wdt_stop(); + + wtcon = readl(wdt_base + S3C2410_WTCON); + wtcon |= S3C2410_WTCON_ENABLE | S3C2410_WTCON_DIV128; + + if (soft_noboot) { + wtcon |= S3C2410_WTCON_INTEN; + wtcon &= ~S3C2410_WTCON_RSTEN; + } else { + wtcon &= ~S3C2410_WTCON_INTEN; + wtcon |= S3C2410_WTCON_RSTEN; + } + + DBG("%s: wdt_count=0x%08x, wtcon=%08lx\n", + __FUNCTION__, wdt_count, wtcon); + + writel(wdt_count, wdt_base + S3C2410_WTDAT); + writel(wdt_count, wdt_base + S3C2410_WTCNT); + writel(wtcon, wdt_base + S3C2410_WTCON); + + return 0; +} + +static int s3c2410wdt_set_heartbeat(int timeout) +{ + unsigned int freq = clk_get_rate(wdt_clock); + unsigned int count; + unsigned int divisor = 1; + unsigned long wtcon; + + if (timeout < 1) + return -EINVAL; + + freq /= 128; + count = timeout * freq; + + DBG("%s: count=%d, timeout=%d, freq=%d\n", + __FUNCTION__, count, timeout, freq); + + /* if the count is bigger than the watchdog register, + then work out what we need to do (and if) we can + actually make this value + */ + + if (count >= 0x10000) { + for (divisor = 1; divisor <= 0x100; divisor++) { + if ((count / divisor) < 0x10000) + break; + } + + if ((count / divisor) >= 0x10000) { + printk(KERN_ERR PFX "timeout %d too big\n", timeout); + return -EINVAL; + } + } + + tmr_margin = timeout; + + DBG("%s: timeout=%d, divisor=%d, count=%d (%08x)\n", + __FUNCTION__, timeout, divisor, count, count/divisor); + + count /= divisor; + wdt_count = count; + + /* update the pre-scaler */ + wtcon = readl(wdt_base + S3C2410_WTCON); + wtcon &= ~S3C2410_WTCON_PRESCALE_MASK; + wtcon |= S3C2410_WTCON_PRESCALE(divisor-1); + + writel(count, wdt_base + S3C2410_WTDAT); + writel(wtcon, wdt_base + S3C2410_WTCON); + + return 0; +} + +/* + * /dev/watchdog handling + */ + +static int s3c2410wdt_open(struct inode *inode, struct file *file) +{ + if(down_trylock(&open_lock)) + return -EBUSY; + + if (nowayout) { + __module_get(THIS_MODULE); + } else { + allow_close = CLOSE_STATE_ALLOW; + } + + /* start the timer */ + s3c2410wdt_start(); + return nonseekable_open(inode, file); +} + +static int s3c2410wdt_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + * Lock it in if it's a module and we set nowayout + */ + if (allow_close == CLOSE_STATE_ALLOW) { + s3c2410wdt_stop(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + s3c2410wdt_keepalive(); + } + + allow_close = CLOSE_STATE_NOT; + up(&open_lock); + return 0; +} + +static ssize_t s3c2410wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* + * Refresh the timer. + */ + if(len) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + allow_close = CLOSE_STATE_NOT; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + allow_close = CLOSE_STATE_ALLOW; + } + } + + s3c2410wdt_keepalive(); + } + return len; +} + +#define OPTIONS WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE + +static struct watchdog_info s3c2410_wdt_ident = { + .options = OPTIONS, + .firmware_version = 0, + .identity = "S3C2410 Watchdog", +}; + + +static int s3c2410wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_margin; + + switch (cmd) { + default: + return -ENOIOCTLCMD; + + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &s3c2410_wdt_ident, + sizeof(s3c2410_wdt_ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + s3c2410wdt_keepalive(); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, p)) + return -EFAULT; + + if (s3c2410wdt_set_heartbeat(new_margin)) + return -EINVAL; + + s3c2410wdt_keepalive(); + return put_user(tmr_margin, p); + + case WDIOC_GETTIMEOUT: + return put_user(tmr_margin, p); + } +} + +/* + * Notifier for system down + */ + +static int s3c2410wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if(code==SYS_DOWN || code==SYS_HALT) { + /* Turn the WDT off */ + s3c2410wdt_stop(); + } + return NOTIFY_DONE; +} + +/* kernel interface */ + +static struct file_operations s3c2410wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = s3c2410wdt_write, + .ioctl = s3c2410wdt_ioctl, + .open = s3c2410wdt_open, + .release = s3c2410wdt_release, +}; + +static struct miscdevice s3c2410wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &s3c2410wdt_fops, +}; + +static struct notifier_block s3c2410wdt_notifier = { + .notifier_call = s3c2410wdt_notify_sys, +}; + +/* interrupt handler code */ + +static irqreturn_t s3c2410wdt_irq(int irqno, void *param, + struct pt_regs *regs) +{ + printk(KERN_INFO PFX "Watchdog timer expired!\n"); + + s3c2410wdt_keepalive(); + return IRQ_HANDLED; +} +/* device interface */ + +static int s3c2410wdt_probe(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct resource *res; + int started = 0; + int ret; + int size; + + DBG("%s: probe=%p, device=%p\n", __FUNCTION__, pdev, dev); + + /* get the memory region for the watchdog timer */ + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (res == NULL) { + printk(KERN_INFO PFX "failed to get memory region resouce\n"); + return -ENOENT; + } + + size = (res->end-res->start)+1; + wdt_mem = request_mem_region(res->start, size, pdev->name); + if (wdt_mem == NULL) { + printk(KERN_INFO PFX "failed to get memory region\n"); + return -ENOENT; + } + + wdt_base = ioremap(res->start, size); + if (wdt_base == 0) { + printk(KERN_INFO PFX "failed to ioremap() region\n"); + return -EINVAL; + } + + DBG("probe: mapped wdt_base=%p\n", wdt_base); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res == NULL) { + printk(KERN_INFO PFX "failed to get irq resource\n"); + return -ENOENT; + } + + ret = request_irq(res->start, s3c2410wdt_irq, 0, pdev->name, dev); + if (ret != 0) { + printk(KERN_INFO PFX "failed to install irq (%d)\n", ret); + return ret; + } + + wdt_clock = clk_get(dev, "watchdog"); + if (wdt_clock == NULL) { + printk(KERN_INFO PFX "failed to find watchdog clock source\n"); + return -ENOENT; + } + + clk_use(wdt_clock); + clk_enable(wdt_clock); + + /* see if we can actually set the requested timer margin, and if + * not, try the default value */ + + if (s3c2410wdt_set_heartbeat(tmr_margin)) { + started = s3c2410wdt_set_heartbeat(CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); + + if (started == 0) { + printk(KERN_INFO PFX "tmr_margin value out of range, default %d used\n", + CONFIG_S3C2410_WATCHDOG_DEFAULT_TIME); + } else { + printk(KERN_INFO PFX "default timer value is out of range, cannot start\n"); + } + } + + ret = register_reboot_notifier(&s3c2410wdt_notifier); + if (ret) { + printk (KERN_ERR PFX "cannot register reboot notifier (%d)\n", + ret); + return ret; + } + + ret = misc_register(&s3c2410wdt_miscdev); + if (ret) { + printk (KERN_ERR PFX "cannot register miscdev on minor=%d (%d)\n", + WATCHDOG_MINOR, ret); + unregister_reboot_notifier(&s3c2410wdt_notifier); + return ret; + } + + if (tmr_atboot && started == 0) { + printk(KERN_INFO PFX "Starting Watchdog Timer\n"); + s3c2410wdt_start(); + } + + return 0; +} + +static int s3c2410wdt_remove(struct device *dev) +{ + if (wdt_mem != NULL) { + release_resource(wdt_mem); + kfree(wdt_mem); + wdt_mem = NULL; + } + + if (wdt_irq != NULL) { + free_irq(wdt_irq->start, dev); + wdt_irq = NULL; + } + + if (wdt_clock != NULL) { + clk_disable(wdt_clock); + clk_unuse(wdt_clock); + clk_put(wdt_clock); + wdt_clock = NULL; + } + + misc_deregister(&s3c2410wdt_miscdev); + return 0; +} + +static struct device_driver s3c2410wdt_driver = { + .name = "s3c2410-wdt", + .bus = &platform_bus_type, + .probe = s3c2410wdt_probe, + .remove = s3c2410wdt_remove, +}; + + + +static char banner[] __initdata = KERN_INFO "S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics\n"; + +static int __init watchdog_init(void) +{ + printk(banner); + return driver_register(&s3c2410wdt_driver); +} + +static void __exit watchdog_exit(void) +{ + driver_unregister(&s3c2410wdt_driver); + unregister_reboot_notifier(&s3c2410wdt_notifier); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("S3C2410 Watchdog Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/sa1100_wdt.c b/drivers/char/watchdog/sa1100_wdt.c new file mode 100644 index 000000000000..34e8f7b15e30 --- /dev/null +++ b/drivers/char/watchdog/sa1100_wdt.c @@ -0,0 +1,223 @@ +/* + * Watchdog driver for the SA11x0/PXA2xx + * + * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> + * Based on SoftDog driver by Alan Cox <alan@redhat.com> + * + * 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 Oleg Drokin nor iXcelerator.com admit liability nor provide + * warranty for any of this software. This material is provided + * "AS-IS" and at no charge. + * + * (c) Copyright 2000 Oleg Drokin <green@crimea.edu> + * + * 27/11/2000 Initial release + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/init.h> + +#ifdef CONFIG_ARCH_PXA +#include <asm/arch/pxa-regs.h> +#endif + +#include <asm/hardware.h> +#include <asm/bitops.h> +#include <asm/uaccess.h> + +#define OSCR_FREQ CLOCK_TICK_RATE +#define SA1100_CLOSE_MAGIC (0x5afc4453) + +static unsigned long sa1100wdt_users; +static int expect_close; +static int pre_margin; +static int boot_status; +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +/* + * Allow only one person to hold it open + */ +static int sa1100dog_open(struct inode *inode, struct file *file) +{ + nonseekable_open(inode, file); + if (test_and_set_bit(1,&sa1100wdt_users)) + return -EBUSY; + + /* Activate SA1100 Watchdog timer */ + OSMR3 = OSCR + pre_margin; + OSSR = OSSR_M3; + OWER = OWER_WME; + OIER |= OIER_E3; + return 0; +} + +/* + * Shut off the timer. + * Lock it in if it's a module and we defined ...NOWAYOUT + * Oddly, the watchdog can only be enabled, but we can turn off + * the interrupt, which appears to prevent the watchdog timing out. + */ +static int sa1100dog_release(struct inode *inode, struct file *file) +{ + OSMR3 = OSCR + pre_margin; + + if (expect_close == SA1100_CLOSE_MAGIC) { + OIER &= ~OIER_E3; + } else { + printk(KERN_CRIT "WATCHDOG: WDT device closed unexpectedly. WDT will not stop!\n"); + } + + clear_bit(1, &sa1100wdt_users); + expect_close = 0; + + return 0; +} + +static ssize_t sa1100dog_write(struct file *file, const char *data, size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = SA1100_CLOSE_MAGIC; + } + } + /* Refresh OSMR3 timer. */ + OSMR3 = OSCR + pre_margin; + } + + return len; +} + +static struct watchdog_info ident = { + .options = WDIOF_CARDRESET | WDIOF_MAGICCLOSE | + WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "SA1100 Watchdog", +}; + +static int sa1100dog_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = -ENOIOCTLCMD; + int time; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user((struct watchdog_info *)arg, &ident, + sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, (int *)arg); + break; + + case WDIOC_GETBOOTSTATUS: + ret = put_user(boot_status, (int *)arg); + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, (int *)arg); + if (ret) + break; + + if (time <= 0 || time > 255) { + ret = -EINVAL; + break; + } + + pre_margin = OSCR_FREQ * time; + OSMR3 = OSCR + pre_margin; + /*fall through*/ + + case WDIOC_GETTIMEOUT: + ret = put_user(pre_margin / OSCR_FREQ, (int *)arg); + break; + + case WDIOC_KEEPALIVE: + OSMR3 = OSCR + pre_margin; + ret = 0; + break; + } + return ret; +} + +static struct file_operations sa1100dog_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sa1100dog_write, + .ioctl = sa1100dog_ioctl, + .open = sa1100dog_open, + .release = sa1100dog_release, +}; + +static struct miscdevice sa1100dog_miscdev = +{ + .minor = WATCHDOG_MINOR, + .name = "SA1100/PXA2xx watchdog", + .fops = &sa1100dog_fops, +}; + +static int margin __initdata = 60; /* (secs) Default is 1 minute */ + +static int __init sa1100dog_init(void) +{ + int ret; + + /* + * Read the reset status, and save it for later. If + * we suspend, RCSR will be cleared, and the watchdog + * reset reason will be lost. + */ + boot_status = (RCSR & RCSR_WDR) ? WDIOF_CARDRESET : 0; + pre_margin = OSCR_FREQ * margin; + + ret = misc_register(&sa1100dog_miscdev); + if (ret == 0) + printk("SA1100/PXA2xx Watchdog Timer: timer margin %d sec\n", + margin); + + return ret; +} + +static void __exit sa1100dog_exit(void) +{ + misc_deregister(&sa1100dog_miscdev); +} + +module_init(sa1100dog_init); +module_exit(sa1100dog_exit); + +MODULE_AUTHOR("Oleg Drokin <green@crimea.edu>"); +MODULE_DESCRIPTION("SA1100/PXA2xx Watchdog"); + +module_param(margin, int, 0); +MODULE_PARM_DESC(margin, "Watchdog margin in seconds (default 60s)"); + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/sbc60xxwdt.c b/drivers/char/watchdog/sbc60xxwdt.c new file mode 100644 index 000000000000..d7de9880605a --- /dev/null +++ b/drivers/char/watchdog/sbc60xxwdt.c @@ -0,0 +1,413 @@ +/* + * 60xx Single Board Computer Watchdog Timer driver for Linux 2.2.x + * + * Based on acquirewdt.c by Alan Cox. + * + * 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. + * + * The author does NOT admit liability nor provide warranty for + * any of this software. This material is provided "AS-IS" in + * the hope that it may be useful for others. + * + * (c) Copyright 2000 Jakob Oestergaard <jakob@unthought.net> + * + * 12/4 - 2000 [Initial revision] + * 25/4 - 2000 Added /dev/watchdog support + * 09/5 - 2001 [smj@oro.net] fixed fop_write to "return 1" on success + * 12/4 - 2002 [rob@osinvestor.com] eliminate fop_read + * fix possible wdt_is_open race + * add CONFIG_WATCHDOG_NOWAYOUT support + * remove lock_kernel/unlock_kernel pairs + * added KERN_* to printk's + * got rid of extraneous comments + * changed watchdog_info to correctly reflect what the driver offers + * added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, WDIOC_SETTIMEOUT, + * WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls + * 09/8 - 2003 [wim@iguana.be] cleanup of trailing spaces + * use module_param + * made timeout (the emulated heartbeat) a module_param + * made the keepalive ping an internal subroutine + * made wdt_stop and wdt_start module params + * added extra printk's for startup problems + * added MODULE_AUTHOR and MODULE_DESCRIPTION info + * + * + * This WDT driver is different from the other Linux WDT + * drivers in the following ways: + * *) The driver will ping the watchdog by itself, because this + * particular WDT has a very short timeout (one second) and it + * would be insane to count on any userspace daemon always + * getting scheduled within that time frame. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#define OUR_NAME "sbc60xxwdt" +#define PFX OUR_NAME ": " + +/* + * You must set these - The driver cannot probe for the settings + */ + +static int wdt_stop = 0x45; +module_param(wdt_stop, int, 0); +MODULE_PARM_DESC(wdt_stop, "SBC60xx WDT 'stop' io port (default 0x45)"); + +static int wdt_start = 0x443; +module_param(wdt_start, int, 0); +MODULE_PARM_DESC(wdt_start, "SBC60xx WDT 'start' io port (default 0x443)"); + +/* + * The 60xx board can use watchdog timeout values from one second + * to several minutes. The default is one second, so if we reset + * the watchdog every ~250ms we should be safe. + */ + +#define WDT_INTERVAL (HZ/4+1) + +/* + * We must not require too good response from the userspace daemon. + * Here we require the userspace daemon to send us a heartbeat + * char to /dev/watchdog every 30 seconds. + * If the daemon pulses us every 25 seconds, we can still afford + * a 5 second scheduling delay on the (high priority) daemon. That + * should be sufficient for a box under any load. + */ + +#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ +static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +static void wdt_timer_ping(unsigned long); +static struct timer_list timer; +static unsigned long next_heartbeat; +static unsigned long wdt_is_open; +static char wdt_expect_close; + +/* + * Whack the dog + */ + +static void wdt_timer_ping(unsigned long data) +{ + /* If we got a heartbeat pulse within the WDT_US_INTERVAL + * we agree to ping the WDT + */ + if(time_before(jiffies, next_heartbeat)) + { + /* Ping the WDT by reading from wdt_start */ + inb_p(wdt_start); + /* Re-set the timer interval */ + timer.expires = jiffies + WDT_INTERVAL; + add_timer(&timer); + } else { + printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); + } +} + +/* + * Utility routines + */ + +static void wdt_startup(void) +{ + next_heartbeat = jiffies + (timeout * HZ); + + /* Start the timer */ + timer.expires = jiffies + WDT_INTERVAL; + add_timer(&timer); + printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); +} + +static void wdt_turnoff(void) +{ + /* Stop the timer */ + del_timer(&timer); + inb_p(wdt_stop); + printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); +} + +static void wdt_keepalive(void) +{ + /* user land ping */ + next_heartbeat = jiffies + (timeout * HZ); +} + +/* + * /dev/watchdog handling + */ + +static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if(count) + { + if (!nowayout) + { + size_t ofs; + + /* note: just in case someone wrote the magic character + * five months ago... */ + wdt_expect_close = 0; + + /* scan to see whether or not we got the magic character */ + for(ofs = 0; ofs != count; ofs++) + { + char c; + if(get_user(c, buf+ofs)) + return -EFAULT; + if(c == 'V') + wdt_expect_close = 42; + } + } + + /* Well, anyhow someone wrote to us, we should return that favour */ + wdt_keepalive(); + } + return count; +} + +static int fop_open(struct inode * inode, struct file * file) +{ + nonseekable_open(inode, file); + + /* Just in case we're already talking to someone... */ + if(test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + /* Good, fire up the show */ + wdt_startup(); + return 0; +} + +static int fop_close(struct inode * inode, struct file * file) +{ + if(wdt_expect_close == 42) + wdt_turnoff(); + else { + del_timer(&timer); + printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); + } + clear_bit(0, &wdt_is_open); + wdt_expect_close = 0; + return 0; +} + +static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident= + { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "SBC60xx", + }; + + switch(cmd) + { + default: + return -ENOIOCTLCMD; + 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: + wdt_keepalive(); + return 0; + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if(get_user(new_options, p)) + return -EFAULT; + + if(new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; + } + + if(new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; + } + + return retval; + } + case WDIOC_SETTIMEOUT: + { + int new_timeout; + + if(get_user(new_timeout, p)) + return -EFAULT; + + if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ + return -EINVAL; + + timeout = new_timeout; + wdt_keepalive(); + /* Fall through */ + } + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + } +} + +static struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = fop_write, + .open = fop_open, + .release = fop_close, + .ioctl = fop_ioctl, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +/* + * Notifier for system down + */ + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if(code==SYS_DOWN || code==SYS_HALT) + wdt_turnoff(); + return NOTIFY_DONE; +} + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier= +{ + .notifier_call = wdt_notify_sys, +}; + +static void __exit sbc60xxwdt_unload(void) +{ + wdt_turnoff(); + + /* Deregister */ + misc_deregister(&wdt_miscdev); + + unregister_reboot_notifier(&wdt_notifier); + if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) + release_region(wdt_stop,1); + release_region(wdt_start,1); +} + +static int __init sbc60xxwdt_init(void) +{ + int rc = -EBUSY; + + if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */ + { + timeout = WATCHDOG_TIMEOUT; + printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", + timeout); + } + + if (!request_region(wdt_start, 1, "SBC 60XX WDT")) + { + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", + wdt_start); + rc = -EIO; + goto err_out; + } + + /* We cannot reserve 0x45 - the kernel already has! */ + if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) + { + if (!request_region(wdt_stop, 1, "SBC 60XX WDT")) + { + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", + wdt_stop); + rc = -EIO; + goto err_out_region1; + } + } + + init_timer(&timer); + timer.function = wdt_timer_ping; + timer.data = 0; + + rc = misc_register(&wdt_miscdev); + if (rc) + { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); + goto err_out_region2; + } + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) + { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + rc); + goto err_out_miscdev; + } + + printk(KERN_INFO PFX "WDT driver for 60XX single board computer initialised. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + + return 0; + +err_out_miscdev: + misc_deregister(&wdt_miscdev); +err_out_region2: + if ((wdt_stop != 0x45) && (wdt_stop != wdt_start)) + release_region(wdt_stop,1); +err_out_region1: + release_region(wdt_start,1); +err_out: + return rc; +} + +module_init(sbc60xxwdt_init); +module_exit(sbc60xxwdt_unload); + +MODULE_AUTHOR("Jakob Oestergaard <jakob@unthought.net>"); +MODULE_DESCRIPTION("60xx Single Board Computer Watchdog Timer driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/sc1200wdt.c b/drivers/char/watchdog/sc1200wdt.c new file mode 100644 index 000000000000..24401e84729e --- /dev/null +++ b/drivers/char/watchdog/sc1200wdt.c @@ -0,0 +1,467 @@ +/* + * National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver + * (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com>, + * All Rights Reserved. + * Based on wdt.c and wdt977.c by Alan Cox and Woody Suwalski respectively. + * + * 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. + * + * The author(s) of this software shall not be held liable for damages + * of any nature resulting due to the use of this software. This + * software is provided AS-IS with no warranties. + * + * Changelog: + * 20020220 Zwane Mwaikambo Code based on datasheet, no hardware. + * 20020221 Zwane Mwaikambo Cleanups as suggested by Jeff Garzik and Alan Cox. + * 20020222 Zwane Mwaikambo Added probing. + * 20020225 Zwane Mwaikambo Added ISAPNP support. + * 20020412 Rob Radez Broke out start/stop functions + * <rob@osinvestor.com> Return proper status instead of temperature warning + * Add WDIOC_GETBOOTSTATUS and WDIOC_SETOPTIONS ioctls + * Fix CONFIG_WATCHDOG_NOWAYOUT + * 20020530 Joel Becker Add Matt Domsch's nowayout module option + * 20030116 Adam Belay Updated to the latest pnp code + * + */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/spinlock.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/pnp.h> +#include <linux/fs.h> +#include <linux/pci.h> + +#include <asm/semaphore.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +#define SC1200_MODULE_VER "build 20020303" +#define SC1200_MODULE_NAME "sc1200wdt" +#define PFX SC1200_MODULE_NAME ": " + +#define MAX_TIMEOUT 255 /* 255 minutes */ +#define PMIR (io) /* Power Management Index Register */ +#define PMDR (io+1) /* Power Management Data Register */ + +/* Data Register indexes */ +#define FER1 0x00 /* Function enable register 1 */ +#define FER2 0x01 /* Function enable register 2 */ +#define PMC1 0x02 /* Power Management Ctrl 1 */ +#define PMC2 0x03 /* Power Management Ctrl 2 */ +#define PMC3 0x04 /* Power Management Ctrl 3 */ +#define WDTO 0x05 /* Watchdog timeout register */ +#define WDCF 0x06 /* Watchdog config register */ +#define WDST 0x07 /* Watchdog status register */ + +/* WDCF bitfields - which devices assert WDO */ +#define KBC_IRQ 0x01 /* Keyboard Controller */ +#define MSE_IRQ 0x02 /* Mouse */ +#define UART1_IRQ 0x03 /* Serial0 */ +#define UART2_IRQ 0x04 /* Serial1 */ +/* 5 -7 are reserved */ + +static char banner[] __initdata = KERN_INFO PFX SC1200_MODULE_VER; +static int timeout = 1; +static int io = -1; +static int io_len = 2; /* for non plug and play */ +static struct semaphore open_sem; +static char expect_close; +static spinlock_t sc1200wdt_lock; /* io port access serialisation */ + +#if defined CONFIG_PNP +static int isapnp = 1; +static struct pnp_dev *wdt_dev; + +module_param(isapnp, int, 0); +MODULE_PARM_DESC(isapnp, "When set to 0 driver ISA PnP support will be disabled"); +#endif + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "io port"); +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "range is 0-255 minutes, default is 1"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + + + +/* Read from Data Register */ +static inline void sc1200wdt_read_data(unsigned char index, unsigned char *data) +{ + spin_lock(&sc1200wdt_lock); + outb_p(index, PMIR); + *data = inb(PMDR); + spin_unlock(&sc1200wdt_lock); +} + + +/* Write to Data Register */ +static inline void sc1200wdt_write_data(unsigned char index, unsigned char data) +{ + spin_lock(&sc1200wdt_lock); + outb_p(index, PMIR); + outb(data, PMDR); + spin_unlock(&sc1200wdt_lock); +} + + +static void sc1200wdt_start(void) +{ + unsigned char reg; + + sc1200wdt_read_data(WDCF, ®); + /* assert WDO when any of the following interrupts are triggered too */ + reg |= (KBC_IRQ | MSE_IRQ | UART1_IRQ | UART2_IRQ); + sc1200wdt_write_data(WDCF, reg); + /* set the timeout and get the ball rolling */ + sc1200wdt_write_data(WDTO, timeout); +} + + +static void sc1200wdt_stop(void) +{ + sc1200wdt_write_data(WDTO, 0); +} + + +/* This returns the status of the WDO signal, inactive high. */ +static inline int sc1200wdt_status(void) +{ + unsigned char ret; + + sc1200wdt_read_data(WDST, &ret); + /* If the bit is inactive, the watchdog is enabled, so return + * KEEPALIVEPING which is a bit of a kludge because there's nothing + * else for enabled/disabled status + */ + return (ret & 0x01) ? 0 : WDIOF_KEEPALIVEPING; /* bits 1 - 7 are undefined */ +} + + +static int sc1200wdt_open(struct inode *inode, struct file *file) +{ + nonseekable_open(inode, file); + + /* allow one at a time */ + if (down_trylock(&open_sem)) + return -EBUSY; + + if (timeout > MAX_TIMEOUT) + timeout = MAX_TIMEOUT; + + sc1200wdt_start(); + printk(KERN_INFO PFX "Watchdog enabled, timeout = %d min(s)", timeout); + + return 0; +} + + +static int sc1200wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +{ + int new_timeout; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = "PC87307/PC97307", + }; + + switch (cmd) { + default: + return -ENOIOCTLCMD; /* Keep Pavel Machek amused ;) */ + + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof ident)) + return -EFAULT; + return 0; + + case WDIOC_GETSTATUS: + return put_user(sc1200wdt_status(), p); + + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + sc1200wdt_write_data(WDTO, timeout); + return 0; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, p)) + return -EFAULT; + + /* the API states this is given in secs */ + new_timeout /= 60; + if (new_timeout < 0 || new_timeout > MAX_TIMEOUT) + return -EINVAL; + + timeout = new_timeout; + sc1200wdt_write_data(WDTO, timeout); + /* fall through and return the new timeout */ + + case WDIOC_GETTIMEOUT: + return put_user(timeout * 60, p); + + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + sc1200wdt_stop(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + sc1200wdt_start(); + retval = 0; + } + + return retval; + } + } +} + + +static int sc1200wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + sc1200wdt_stop(); + printk(KERN_INFO PFX "Watchdog disabled\n"); + } else { + sc1200wdt_write_data(WDTO, timeout); + printk(KERN_CRIT PFX "Unexpected close!, timeout = %d min(s)\n", timeout); + } + up(&open_sem); + expect_close = 0; + + return 0; +} + + +static ssize_t sc1200wdt_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) +{ + if (len) { + if (!nowayout) { + size_t i; + + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data+i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + + sc1200wdt_write_data(WDTO, timeout); + return len; + } + + return 0; +} + + +static int sc1200wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) + sc1200wdt_stop(); + + return NOTIFY_DONE; +} + + +static struct notifier_block sc1200wdt_notifier = +{ + .notifier_call = sc1200wdt_notify_sys, +}; + +static struct file_operations sc1200wdt_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sc1200wdt_write, + .ioctl = sc1200wdt_ioctl, + .open = sc1200wdt_open, + .release = sc1200wdt_release, +}; + +static struct miscdevice sc1200wdt_miscdev = +{ + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sc1200wdt_fops, +}; + + +static int __init sc1200wdt_probe(void) +{ + /* The probe works by reading the PMC3 register's default value of 0x0e + * there is one caveat, if the device disables the parallel port or any + * of the UARTs we won't be able to detect it. + * Nb. This could be done with accuracy by reading the SID registers, but + * we don't have access to those io regions. + */ + + unsigned char reg; + + sc1200wdt_read_data(PMC3, ®); + reg &= 0x0f; /* we don't want the UART busy bits */ + return (reg == 0x0e) ? 0 : -ENODEV; +} + + +#if defined CONFIG_PNP + +static struct pnp_device_id scl200wdt_pnp_devices[] = { + /* National Semiconductor PC87307/PC97307 watchdog component */ + {.id = "NSC0800", .driver_data = 0}, + {.id = ""}, +}; + +static int scl200wdt_pnp_probe(struct pnp_dev * dev, const struct pnp_device_id *dev_id) +{ + /* this driver only supports one card at a time */ + if (wdt_dev || !isapnp) + return -EBUSY; + + wdt_dev = dev; + io = pnp_port_start(wdt_dev, 0); + io_len = pnp_port_len(wdt_dev, 0); + + if (!request_region(io, io_len, SC1200_MODULE_NAME)) { + printk(KERN_ERR PFX "Unable to register IO port %#x\n", io); + return -EBUSY; + } + + printk(KERN_INFO "scl200wdt: PnP device found at io port %#x/%d\n", io, io_len); + return 0; +} + +static void scl200wdt_pnp_remove(struct pnp_dev * dev) +{ + if (wdt_dev){ + release_region(io, io_len); + wdt_dev = NULL; + } +} + +static struct pnp_driver scl200wdt_pnp_driver = { + .name = "scl200wdt", + .id_table = scl200wdt_pnp_devices, + .probe = scl200wdt_pnp_probe, + .remove = scl200wdt_pnp_remove, +}; + +#endif /* CONFIG_PNP */ + + +static int __init sc1200wdt_init(void) +{ + int ret; + + printk(banner); + + spin_lock_init(&sc1200wdt_lock); + sema_init(&open_sem, 1); + +#if defined CONFIG_PNP + if (isapnp) { + ret = pnp_register_driver(&scl200wdt_pnp_driver); + if (ret) + goto out_clean; + } +#endif + + if (io == -1) { + printk(KERN_ERR PFX "io parameter must be specified\n"); + ret = -EINVAL; + goto out_clean; + } + +#if defined CONFIG_PNP + /* now that the user has specified an IO port and we haven't detected + * any devices, disable pnp support */ + isapnp = 0; + pnp_unregister_driver(&scl200wdt_pnp_driver); +#endif + + if (!request_region(io, io_len, SC1200_MODULE_NAME)) { + printk(KERN_ERR PFX "Unable to register IO port %#x\n", io); + ret = -EBUSY; + goto out_clean; + } + + ret = sc1200wdt_probe(); + if (ret) + goto out_io; + + ret = register_reboot_notifier(&sc1200wdt_notifier); + if (ret) { + printk(KERN_ERR PFX "Unable to register reboot notifier err = %d\n", ret); + goto out_io; + } + + ret = misc_register(&sc1200wdt_miscdev); + if (ret) { + printk(KERN_ERR PFX "Unable to register miscdev on minor %d\n", WATCHDOG_MINOR); + goto out_rbt; + } + + /* ret = 0 */ + +out_clean: + return ret; + +out_rbt: + unregister_reboot_notifier(&sc1200wdt_notifier); + +out_io: + release_region(io, io_len); + + goto out_clean; +} + + +static void __exit sc1200wdt_exit(void) +{ + misc_deregister(&sc1200wdt_miscdev); + unregister_reboot_notifier(&sc1200wdt_notifier); + +#if defined CONFIG_PNP + if(isapnp) + pnp_unregister_driver(&scl200wdt_pnp_driver); + else +#endif + release_region(io, io_len); +} + +module_init(sc1200wdt_init); +module_exit(sc1200wdt_exit); + +MODULE_AUTHOR("Zwane Mwaikambo <zwane@commfireservices.com>"); +MODULE_DESCRIPTION("Driver for National Semiconductor PC87307/PC97307 watchdog component"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/sc520_wdt.c b/drivers/char/watchdog/sc520_wdt.c new file mode 100644 index 000000000000..f6d143e1900d --- /dev/null +++ b/drivers/char/watchdog/sc520_wdt.c @@ -0,0 +1,447 @@ +/* + * AMD Elan SC520 processor Watchdog Timer driver + * + * Based on acquirewdt.c by Alan Cox, + * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net> + * + * 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. + * + * The authors do NOT admit liability nor provide warranty for + * any of this software. This material is provided "AS-IS" in + * the hope that it may be useful for others. + * + * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net> + * 9/27 - 2001 [Initial release] + * + * Additional fixes Alan Cox + * - Fixed formatting + * - Removed debug printks + * - Fixed SMP built kernel deadlock + * - Switched to private locks not lock_kernel + * - Used ioremap/writew/readw + * - Added NOWAYOUT support + * 4/12 - 2002 Changes by Rob Radez <rob@osinvestor.com> + * - Change comments + * - Eliminate fop_llseek + * - Change CONFIG_WATCHDOG_NOWAYOUT semantics + * - Add KERN_* tags to printks + * - fix possible wdt_is_open race + * - Report proper capabilities in watchdog_info + * - Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT, + * GETTIMEOUT, SETOPTIONS} ioctls + * 09/8 - 2003 Changes by Wim Van Sebroeck <wim@iguana.be> + * - cleanup of trailing spaces + * - added extra printk's for startup problems + * - use module_param + * - made timeout (the emulated heartbeat) a module_param + * - made the keepalive ping an internal subroutine + * 3/27 - 2004 Changes by Sean Young <sean@mess.org> + * - set MMCR_BASE to 0xfffef000 + * - CBAR does not need to be read + * - removed debugging printks + * + * This WDT driver is different from most other Linux WDT + * drivers in that the driver will ping the watchdog by itself, + * because this particular WDT has a very short timeout (1.6 + * seconds) and it would be insane to count on any userspace + * daemon always getting scheduled within that time frame. + * + * This driver uses memory mapped IO, and spinlock. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#define OUR_NAME "sc520_wdt" +#define PFX OUR_NAME ": " + +/* + * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7) + * + * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s + * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s + * + * We will program the SC520 watchdog for a timeout of 2.01s. + * If we reset the watchdog every ~250ms we should be safe. + */ + +#define WDT_INTERVAL (HZ/4+1) + +/* + * We must not require too good response from the userspace daemon. + * Here we require the userspace daemon to send us a heartbeat + * char to /dev/watchdog every 30 seconds. + */ + +#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ +static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * AMD Elan SC520 - Watchdog Timer Registers + */ +#define MMCR_BASE 0xfffef000 /* The default base address */ +#define OFFS_WDTMRCTL 0xCB0 /* Watchdog Timer Control Register */ + +/* WDT Control Register bit definitions */ +#define WDT_EXP_SEL_01 0x0001 /* [01] Time-out = 496 us (with 33 Mhz clk). */ +#define WDT_EXP_SEL_02 0x0002 /* [02] Time-out = 508 ms (with 33 Mhz clk). */ +#define WDT_EXP_SEL_03 0x0004 /* [03] Time-out = 1.02 s (with 33 Mhz clk). */ +#define WDT_EXP_SEL_04 0x0008 /* [04] Time-out = 2.03 s (with 33 Mhz clk). */ +#define WDT_EXP_SEL_05 0x0010 /* [05] Time-out = 4.07 s (with 33 Mhz clk). */ +#define WDT_EXP_SEL_06 0x0020 /* [06] Time-out = 8.13 s (with 33 Mhz clk). */ +#define WDT_EXP_SEL_07 0x0040 /* [07] Time-out = 16.27s (with 33 Mhz clk). */ +#define WDT_EXP_SEL_08 0x0080 /* [08] Time-out = 32.54s (with 33 Mhz clk). */ +#define WDT_IRQ_FLG 0x1000 /* [12] Interrupt Request Flag */ +#define WDT_WRST_ENB 0x4000 /* [14] Watchdog Timer Reset Enable */ +#define WDT_ENB 0x8000 /* [15] Watchdog Timer Enable */ + +static __u16 __iomem *wdtmrctl; + +static void wdt_timer_ping(unsigned long); +static struct timer_list timer; +static unsigned long next_heartbeat; +static unsigned long wdt_is_open; +static char wdt_expect_close; +static spinlock_t wdt_spinlock; + +/* + * Whack the dog + */ + +static void wdt_timer_ping(unsigned long data) +{ + /* If we got a heartbeat pulse within the WDT_US_INTERVAL + * we agree to ping the WDT + */ + if(time_before(jiffies, next_heartbeat)) + { + /* Ping the WDT */ + spin_lock(&wdt_spinlock); + writew(0xAAAA, wdtmrctl); + writew(0x5555, wdtmrctl); + spin_unlock(&wdt_spinlock); + + /* Re-set the timer interval */ + timer.expires = jiffies + WDT_INTERVAL; + add_timer(&timer); + } else { + printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); + } +} + +/* + * Utility routines + */ + +static void wdt_config(int writeval) +{ + __u16 dummy; + unsigned long flags; + + /* buy some time (ping) */ + spin_lock_irqsave(&wdt_spinlock, flags); + dummy=readw(wdtmrctl); /* ensure write synchronization */ + writew(0xAAAA, wdtmrctl); + writew(0x5555, wdtmrctl); + /* unlock WDT = make WDT configuration register writable one time */ + writew(0x3333, wdtmrctl); + writew(0xCCCC, wdtmrctl); + /* write WDT configuration register */ + writew(writeval, wdtmrctl); + spin_unlock_irqrestore(&wdt_spinlock, flags); +} + +static int wdt_startup(void) +{ + next_heartbeat = jiffies + (timeout * HZ); + + /* Start the timer */ + timer.expires = jiffies + WDT_INTERVAL; + add_timer(&timer); + + /* Start the watchdog */ + wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04); + + printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); + return 0; +} + +static int wdt_turnoff(void) +{ + /* Stop the timer */ + del_timer(&timer); + + /* Stop the watchdog */ + wdt_config(0); + + printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); + return 0; +} + +static int wdt_keepalive(void) +{ + /* user land ping */ + next_heartbeat = jiffies + (timeout * HZ); + return 0; +} + +static int wdt_set_heartbeat(int t) +{ + if ((t < 1) || (t > 3600)) /* arbitrary upper limit */ + return -EINVAL; + + timeout = t; + return 0; +} + +/* + * /dev/watchdog handling + */ + +static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if(count) { + if (!nowayout) { + size_t ofs; + + /* note: just in case someone wrote the magic character + * five months ago... */ + wdt_expect_close = 0; + + /* now scan */ + for(ofs = 0; ofs != count; ofs++) { + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if(c == 'V') + wdt_expect_close = 42; + } + } + + /* Well, anyhow someone wrote to us, we should return that favour */ + wdt_keepalive(); + } + return count; +} + +static int fop_open(struct inode * inode, struct file * file) +{ + nonseekable_open(inode, file); + + /* Just in case we're already talking to someone... */ + if(test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + if (nowayout) + __module_get(THIS_MODULE); + + /* Good, fire up the show */ + wdt_startup(); + return 0; +} + +static int fop_close(struct inode * inode, struct file * file) +{ + if(wdt_expect_close == 42) { + wdt_turnoff(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + wdt_keepalive(); + } + clear_bit(0, &wdt_is_open); + wdt_expect_close = 0; + return 0; +} + +static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "SC520", + }; + + switch(cmd) + { + default: + return -ENOIOCTLCMD; + 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: + wdt_keepalive(); + return 0; + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if(get_user(new_options, p)) + return -EFAULT; + + if(new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; + } + + if(new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; + } + + return retval; + } + case WDIOC_SETTIMEOUT: + { + int new_timeout; + + if(get_user(new_timeout, p)) + return -EFAULT; + + if(wdt_set_heartbeat(new_timeout)) + return -EINVAL; + + wdt_keepalive(); + /* Fall through */ + } + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + } +} + +static struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = fop_write, + .open = fop_open, + .release = fop_close, + .ioctl = fop_ioctl, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +/* + * Notifier for system down + */ + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if(code==SYS_DOWN || code==SYS_HALT) + wdt_turnoff(); + return NOTIFY_DONE; +} + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +static void __exit sc520_wdt_unload(void) +{ + if (!nowayout) + wdt_turnoff(); + + /* Deregister */ + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); + iounmap(wdtmrctl); +} + +static int __init sc520_wdt_init(void) +{ + int rc = -EBUSY; + + spin_lock_init(&wdt_spinlock); + + init_timer(&timer); + timer.function = wdt_timer_ping; + timer.data = 0; + + /* Check that the timeout value is within it's range ; if not reset to the default */ + if (wdt_set_heartbeat(timeout)) { + wdt_set_heartbeat(WATCHDOG_TIMEOUT); + printk(KERN_INFO PFX "timeout value must be 1<=timeout<=3600, using %d\n", + WATCHDOG_TIMEOUT); + } + + wdtmrctl = ioremap((unsigned long)(MMCR_BASE + OFFS_WDTMRCTL), 2); + if (!wdtmrctl) { + printk(KERN_ERR PFX "Unable to remap memory\n"); + rc = -ENOMEM; + goto err_out_region2; + } + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + rc); + goto err_out_ioremap; + } + + rc = misc_register(&wdt_miscdev); + if (rc) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, rc); + goto err_out_notifier; + } + + printk(KERN_INFO PFX "WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d)\n", + timeout,nowayout); + + return 0; + +err_out_notifier: + unregister_reboot_notifier(&wdt_notifier); +err_out_ioremap: + iounmap(wdtmrctl); +err_out_region2: + return rc; +} + +module_init(sc520_wdt_init); +module_exit(sc520_wdt_unload); + +MODULE_AUTHOR("Scott and Bill Jennings"); +MODULE_DESCRIPTION("Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/scx200_wdt.c b/drivers/char/watchdog/scx200_wdt.c new file mode 100644 index 000000000000..b569670e4ed5 --- /dev/null +++ b/drivers/char/watchdog/scx200_wdt.c @@ -0,0 +1,274 @@ +/* drivers/char/watchdog/scx200_wdt.c + + National Semiconductor SCx200 Watchdog support + + Copyright (c) 2001,2002 Christer Weinigel <wingel@nano-system.com> + + Some code taken from: + National Semiconductor PC87307/PC97307 (ala SC1200) WDT driver + (c) Copyright 2002 Zwane Mwaikambo <zwane@commfireservices.com> + + 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. + + The author(s) of this software shall not be held liable for damages + of any nature resulting due to the use of this software. This + software is provided AS-IS with no warranties. */ + +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/fs.h> +#include <linux/pci.h> +#include <linux/scx200.h> + +#include <asm/uaccess.h> +#include <asm/io.h> + +#define NAME "scx200_wdt" + +MODULE_AUTHOR("Christer Weinigel <wingel@nano-system.com>"); +MODULE_DESCRIPTION("NatSemi SCx200 Watchdog Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +#ifndef CONFIG_WATCHDOG_NOWAYOUT +#define CONFIG_WATCHDOG_NOWAYOUT 0 +#endif + +static int margin = 60; /* in seconds */ +module_param(margin, int, 0); +MODULE_PARM_DESC(margin, "Watchdog margin in seconds"); + +static int nowayout = CONFIG_WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Disable watchdog shutdown on close"); + +static u16 wdto_restart; +static struct semaphore open_semaphore; +static char expect_close; + +/* Bits of the WDCNFG register */ +#define W_ENABLE 0x00fa /* Enable watchdog */ +#define W_DISABLE 0x0000 /* Disable watchdog */ + +/* The scaling factor for the timer, this depends on the value of W_ENABLE */ +#define W_SCALE (32768/1024) + +static void scx200_wdt_ping(void) +{ + outw(wdto_restart, scx200_cb_base + SCx200_WDT_WDTO); +} + +static void scx200_wdt_update_margin(void) +{ + printk(KERN_INFO NAME ": timer margin %d seconds\n", margin); + wdto_restart = margin * W_SCALE; +} + +static void scx200_wdt_enable(void) +{ + printk(KERN_DEBUG NAME ": enabling watchdog timer, wdto_restart = %d\n", + wdto_restart); + + outw(0, scx200_cb_base + SCx200_WDT_WDTO); + outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS); + outw(W_ENABLE, scx200_cb_base + SCx200_WDT_WDCNFG); + + scx200_wdt_ping(); +} + +static void scx200_wdt_disable(void) +{ + printk(KERN_DEBUG NAME ": disabling watchdog timer\n"); + + outw(0, scx200_cb_base + SCx200_WDT_WDTO); + outb(SCx200_WDT_WDSTS_WDOVF, scx200_cb_base + SCx200_WDT_WDSTS); + outw(W_DISABLE, scx200_cb_base + SCx200_WDT_WDCNFG); +} + +static int scx200_wdt_open(struct inode *inode, struct file *file) +{ + /* only allow one at a time */ + if (down_trylock(&open_semaphore)) + return -EBUSY; + scx200_wdt_enable(); + + return nonseekable_open(inode, file); +} + +static int scx200_wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close != 42) { + printk(KERN_WARNING NAME ": watchdog device closed unexpectedly, will not disable the watchdog timer\n"); + } else if (!nowayout) { + scx200_wdt_disable(); + } + expect_close = 0; + up(&open_semaphore); + + return 0; +} + +static int scx200_wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_HALT || code == SYS_POWER_OFF) + if (!nowayout) + scx200_wdt_disable(); + + return NOTIFY_DONE; +} + +static struct notifier_block scx200_wdt_notifier = +{ + .notifier_call = scx200_wdt_notify_sys, +}; + +static ssize_t scx200_wdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* check for a magic close character */ + if (len) + { + size_t i; + + scx200_wdt_ping(); + + expect_close = 0; + for (i = 0; i < len; ++i) { + char c; + if (get_user(c, data+i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + + return len; + } + + return 0; +} + +static int scx200_wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .identity = "NatSemi SCx200 Watchdog", + .firmware_version = 1, + .options = (WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING), + }; + int new_margin; + + switch (cmd) { + default: + return -ENOIOCTLCMD; + case WDIOC_GETSUPPORT: + if(copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + return 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + if (put_user(0, p)) + return -EFAULT; + return 0; + case WDIOC_KEEPALIVE: + scx200_wdt_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, p)) + return -EFAULT; + if (new_margin < 1) + return -EINVAL; + margin = new_margin; + scx200_wdt_update_margin(); + scx200_wdt_ping(); + case WDIOC_GETTIMEOUT: + if (put_user(margin, p)) + return -EFAULT; + return 0; + } +} + +static struct file_operations scx200_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = scx200_wdt_write, + .ioctl = scx200_wdt_ioctl, + .open = scx200_wdt_open, + .release = scx200_wdt_release, +}; + +static struct miscdevice scx200_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = NAME, + .fops = &scx200_wdt_fops, +}; + +static int __init scx200_wdt_init(void) +{ + int r; + + printk(KERN_DEBUG NAME ": NatSemi SCx200 Watchdog Driver\n"); + + /* check that we have found the configuration block */ + if (!scx200_cb_present()) + return -ENODEV; + + if (!request_region(scx200_cb_base + SCx200_WDT_OFFSET, + SCx200_WDT_SIZE, + "NatSemi SCx200 Watchdog")) { + printk(KERN_WARNING NAME ": watchdog I/O region busy\n"); + return -EBUSY; + } + + scx200_wdt_update_margin(); + scx200_wdt_disable(); + + sema_init(&open_semaphore, 1); + + r = misc_register(&scx200_wdt_miscdev); + if (r) { + release_region(scx200_cb_base + SCx200_WDT_OFFSET, + SCx200_WDT_SIZE); + return r; + } + + r = register_reboot_notifier(&scx200_wdt_notifier); + if (r) { + printk(KERN_ERR NAME ": unable to register reboot notifier"); + misc_deregister(&scx200_wdt_miscdev); + release_region(scx200_cb_base + SCx200_WDT_OFFSET, + SCx200_WDT_SIZE); + return r; + } + + return 0; +} + +static void __exit scx200_wdt_cleanup(void) +{ + unregister_reboot_notifier(&scx200_wdt_notifier); + misc_deregister(&scx200_wdt_miscdev); + release_region(scx200_cb_base + SCx200_WDT_OFFSET, + SCx200_WDT_SIZE); +} + +module_init(scx200_wdt_init); +module_exit(scx200_wdt_cleanup); + +/* + Local variables: + compile-command: "make -k -C ../.. SUBDIRS=drivers/char modules" + c-basic-offset: 8 + End: +*/ diff --git a/drivers/char/watchdog/shwdt.c b/drivers/char/watchdog/shwdt.c new file mode 100644 index 000000000000..3bc9272a474c --- /dev/null +++ b/drivers/char/watchdog/shwdt.c @@ -0,0 +1,452 @@ +/* + * drivers/char/watchdog/shwdt.c + * + * Watchdog driver for integrated watchdog in the SuperH processors. + * + * Copyright (C) 2001, 2002, 2003 Paul Mundt <lethal@linux-sh.org> + * + * 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. + * + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * + * 19-Apr-2002 Rob Radez <rob@osinvestor.com> + * Added expect close support, made emulated timeout runtime changeable + * general cleanups, add some ioctls + */ +#include <linux/config.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/reboot.h> +#include <linux/notifier.h> +#include <linux/ioport.h> +#include <linux/fs.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/watchdog.h> + +#define PFX "shwdt: " + +/* + * Default clock division ratio is 5.25 msecs. For an additional table of + * values, consult the asm-sh/watchdog.h. Overload this at module load + * time. + * + * In order for this to work reliably we need to have HZ set to 1000 or + * something quite higher than 100 (or we need a proper high-res timer + * implementation that will deal with this properly), otherwise the 10ms + * resolution of a jiffy is enough to trigger the overflow. For things like + * the SH-4 and SH-5, this isn't necessarily that big of a problem, though + * for the SH-2 and SH-3, this isn't recommended unless the WDT is absolutely + * necssary. + * + * As a result of this timing problem, the only modes that are particularly + * feasible are the 4096 and the 2048 divisors, which yeild 5.25 and 2.62ms + * overflow periods respectively. + * + * Also, since we can't really expect userspace to be responsive enough + * before the overflow happens, we maintain two seperate timers .. One in + * the kernel for clearing out WOVF every 2ms or so (again, this depends on + * HZ == 1000), and another for monitoring userspace writes to the WDT device. + * + * As such, we currently use a configurable heartbeat interval which defaults + * to 30s. In this case, the userspace daemon is only responsible for periodic + * writes to the device before the next heartbeat is scheduled. If the daemon + * misses its deadline, the kernel timer will allow the WDT to overflow. + */ +static int clock_division_ratio = WTCSR_CKS_4096; + +#define next_ping_period(cks) msecs_to_jiffies(cks - 4) + +static unsigned long shwdt_is_open; +static struct watchdog_info sh_wdt_info; +static char shwdt_expect_close; +static struct timer_list timer; +static unsigned long next_heartbeat; + +#define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ +static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +/** + * sh_wdt_start - Start the Watchdog + * + * Starts the watchdog. + */ +static void sh_wdt_start(void) +{ + __u8 csr; + + next_heartbeat = jiffies + (heartbeat * HZ); + mod_timer(&timer, next_ping_period(clock_division_ratio)); + + csr = sh_wdt_read_csr(); + csr |= WTCSR_WT | clock_division_ratio; + sh_wdt_write_csr(csr); + + sh_wdt_write_cnt(0); + + /* + * These processors have a bit of an inconsistent initialization + * process.. starting with SH-3, RSTS was moved to WTCSR, and the + * RSTCSR register was removed. + * + * On the SH-2 however, in addition with bits being in different + * locations, we must deal with RSTCSR outright.. + */ + csr = sh_wdt_read_csr(); + csr |= WTCSR_TME; + csr &= ~WTCSR_RSTS; + sh_wdt_write_csr(csr); + +#ifdef CONFIG_CPU_SH2 + /* + * Whoever came up with the RSTCSR semantics must've been smoking + * some of the good stuff, since in addition to the WTCSR/WTCNT write + * brain-damage, it's managed to fuck things up one step further.. + * + * If we need to clear the WOVF bit, the upper byte has to be 0xa5.. + * but if we want to touch RSTE or RSTS, the upper byte has to be + * 0x5a.. + */ + csr = sh_wdt_read_rstcsr(); + csr &= ~RSTCSR_RSTS; + sh_wdt_write_rstcsr(csr); +#endif +} + +/** + * sh_wdt_stop - Stop the Watchdog + * + * Stops the watchdog. + */ +static void sh_wdt_stop(void) +{ + __u8 csr; + + del_timer(&timer); + + csr = sh_wdt_read_csr(); + csr &= ~WTCSR_TME; + sh_wdt_write_csr(csr); +} + +/** + * sh_wdt_keepalive - Keep the Userspace Watchdog Alive + * + * The Userspace watchdog got a KeepAlive: schedule the next heartbeat. + */ +static void sh_wdt_keepalive(void) +{ + next_heartbeat = jiffies + (heartbeat * HZ); +} + +/** + * sh_wdt_set_heartbeat - Set the Userspace Watchdog heartbeat + * + * Set the Userspace Watchdog heartbeat + */ +static int sh_wdt_set_heartbeat(int t) +{ + if ((t < 1) || (t > 3600)) /* arbitrary upper limit */ + return -EINVAL; + + heartbeat = t; + return 0; +} + +/** + * sh_wdt_ping - Ping the Watchdog + * + * @data: Unused + * + * Clears overflow bit, resets timer counter. + */ +static void sh_wdt_ping(unsigned long data) +{ + if (time_before(jiffies, next_heartbeat)) { + __u8 csr; + + csr = sh_wdt_read_csr(); + csr &= ~WTCSR_IOVF; + sh_wdt_write_csr(csr); + + sh_wdt_write_cnt(0); + + mod_timer(&timer, next_ping_period(clock_division_ratio)); + } else { + printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); + } +} + +/** + * sh_wdt_open - Open the Device + * + * @inode: inode of device + * @file: file handle of device + * + * Watchdog device is opened and started. + */ +static int sh_wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &shwdt_is_open)) + return -EBUSY; + if (nowayout) + __module_get(THIS_MODULE); + + sh_wdt_start(); + + return nonseekable_open(inode, file); +} + +/** + * sh_wdt_close - Close the Device + * + * @inode: inode of device + * @file: file handle of device + * + * Watchdog device is closed and stopped. + */ +static int sh_wdt_close(struct inode *inode, struct file *file) +{ + if (shwdt_expect_close == 42) { + sh_wdt_stop(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + sh_wdt_keepalive(); + } + + clear_bit(0, &shwdt_is_open); + shwdt_expect_close = 0; + + return 0; +} + +/** + * sh_wdt_write - Write to Device + * + * @file: file handle of device + * @buf: buffer to write + * @count: length of buffer + * @ppos: offset + * + * Pings the watchdog on write. + */ +static ssize_t sh_wdt_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + shwdt_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + shwdt_expect_close = 42; + } + } + sh_wdt_keepalive(); + } + + return count; +} + +/** + * sh_wdt_ioctl - Query Device + * + * @inode: inode of device + * @file: file handle of device + * @cmd: watchdog command + * @arg: argument + * + * Query basic information from the device or ping it, as outlined by the + * watchdog API. + */ +static int sh_wdt_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int new_heartbeat; + int options, retval = -EINVAL; + + switch (cmd) { + case WDIOC_GETSUPPORT: + return copy_to_user((struct watchdog_info *)arg, + &sh_wdt_info, + sizeof(sh_wdt_info)) ? -EFAULT : 0; + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, (int *)arg); + case WDIOC_KEEPALIVE: + sh_wdt_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, (int *)arg)) + return -EFAULT; + + if (sh_wdt_set_heartbeat(new_heartbeat)) + return -EINVAL; + + sh_wdt_keepalive(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, (int *)arg); + case WDIOC_SETOPTIONS: + if (get_user(options, (int *)arg)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + sh_wdt_stop(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + sh_wdt_start(); + retval = 0; + } + + return retval; + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +/** + * sh_wdt_notify_sys - Notifier Handler + * + * @this: notifier block + * @code: notifier event + * @unused: unused + * + * Handles specific events, such as turning off the watchdog during a + * shutdown event. + */ +static int sh_wdt_notify_sys(struct notifier_block *this, + unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) { + sh_wdt_stop(); + } + + return NOTIFY_DONE; +} + +static struct file_operations sh_wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sh_wdt_write, + .ioctl = sh_wdt_ioctl, + .open = sh_wdt_open, + .release = sh_wdt_close, +}; + +static struct watchdog_info sh_wdt_info = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "SH WDT", +}; + +static struct notifier_block sh_wdt_notifier = { + .notifier_call = sh_wdt_notify_sys, +}; + +static struct miscdevice sh_wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sh_wdt_fops, +}; + +/** + * sh_wdt_init - Initialize module + * + * Registers the device and notifier handler. Actual device + * initialization is handled by sh_wdt_open(). + */ +static int __init sh_wdt_init(void) +{ + int rc; + + if ((clock_division_ratio < 0x5) || (clock_division_ratio > 0x7)) { + clock_division_ratio = WTCSR_CKS_4096; + printk(KERN_INFO PFX "clock_division_ratio value must be 0x5<=x<=0x7, using %d\n", + clock_division_ratio); + } + + if (sh_wdt_set_heartbeat(heartbeat)) + { + heartbeat = WATCHDOG_HEARTBEAT; + printk(KERN_INFO PFX "heartbeat value must be 1<=x<=3600, using %d\n", + heartbeat); + } + + init_timer(&timer); + timer.function = sh_wdt_ping; + timer.data = 0; + + rc = register_reboot_notifier(&sh_wdt_notifier); + if (rc) { + printk(KERN_ERR PFX "Can't register reboot notifier (err=%d)\n", rc); + return rc; + } + + rc = misc_register(&sh_wdt_miscdev); + if (rc) { + printk(KERN_ERR PFX "Can't register miscdev on minor=%d (err=%d)\n", + sh_wdt_miscdev.minor, rc); + unregister_reboot_notifier(&sh_wdt_notifier); + return rc; + } + + printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); + + return 0; +} + +/** + * sh_wdt_exit - Deinitialize module + * + * Unregisters the device and notifier handler. Actual device + * deinitialization is handled by sh_wdt_close(). + */ +static void __exit sh_wdt_exit(void) +{ + misc_deregister(&sh_wdt_miscdev); + unregister_reboot_notifier(&sh_wdt_notifier); +} + +MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); +MODULE_DESCRIPTION("SuperH watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +module_param(clock_division_ratio, int, 0); +MODULE_PARM_DESC(clock_division_ratio, "Clock division ratio. Valid ranges are from 0x5 (1.31ms) to 0x7 (5.25ms). Defaults to 0x7."); + +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (1<=heartbeat<=3600, default=" __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +module_init(sh_wdt_init); +module_exit(sh_wdt_exit); + diff --git a/drivers/char/watchdog/softdog.c b/drivers/char/watchdog/softdog.c new file mode 100644 index 000000000000..117903498a01 --- /dev/null +++ b/drivers/char/watchdog/softdog.c @@ -0,0 +1,309 @@ +/* + * SoftDog 0.07: A Software Watchdog Device + * + * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. + * http://www.redhat.com + * + * 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> + * + * Software only watchdog driver. Unlike its big brother the WDT501P + * driver this won't always recover a failed machine. + * + * 03/96: Angelo Haritsis <ah@doc.ic.ac.uk> : + * Modularised. + * Added soft_margin; use upon insmod to change the timer delay. + * NB: uses same minor as wdt (WATCHDOG_MINOR); we could use separate + * minors. + * + * 19980911 Alan Cox + * Made SMP safe for 2.3.x + * + * 20011127 Joel Becker (jlbec@evilplan.org> + * Added soft_noboot; Allows testing the softdog trigger without + * requiring a recompile. + * Added WDIOC_GETTIMEOUT and WDIOC_SETTIMOUT. + * + * 20020530 Joel Becker <joel.becker@oracle.com> + * Added Matt Domsch's nowayout module option. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <asm/uaccess.h> + +#define PFX "SoftDog: " + +#define TIMER_MARGIN 60 /* Default is 60 seconds */ +static int soft_margin = TIMER_MARGIN; /* in seconds */ +module_param(soft_margin, int, 0); +MODULE_PARM_DESC(soft_margin, "Watchdog soft_margin in seconds. (0<soft_margin<65536, default=" __MODULE_STRING(TIMER_MARGIN) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +#ifdef ONLY_TESTING +static int soft_noboot = 1; +#else +static int soft_noboot = 0; +#endif /* ONLY_TESTING */ + +module_param(soft_noboot, int, 0); +MODULE_PARM_DESC(soft_noboot, "Softdog action, set to 1 to ignore reboots, 0 to reboot (default depends on ONLY_TESTING)"); + +/* + * Our timer + */ + +static void watchdog_fire(unsigned long); + +static struct timer_list watchdog_ticktock = + TIMER_INITIALIZER(watchdog_fire, 0, 0); +static unsigned long timer_alive; +static char expect_close; + + +/* + * If the timer expires.. + */ + +static void watchdog_fire(unsigned long data) +{ + if (soft_noboot) + printk(KERN_CRIT PFX "Triggered - Reboot ignored.\n"); + else + { + printk(KERN_CRIT PFX "Initiating system reboot.\n"); + machine_restart(NULL); + printk(KERN_CRIT PFX "Reboot didn't ?????\n"); + } +} + +/* + * Softdog operations + */ + +static int softdog_keepalive(void) +{ + mod_timer(&watchdog_ticktock, jiffies+(soft_margin*HZ)); + return 0; +} + +static int softdog_stop(void) +{ + del_timer(&watchdog_ticktock); + return 0; +} + +static int softdog_set_heartbeat(int t) +{ + if ((t < 0x0001) || (t > 0xFFFF)) + return -EINVAL; + + soft_margin = t; + return 0; +} + +/* + * /dev/watchdog handling + */ + +static int softdog_open(struct inode *inode, struct file *file) +{ + if(test_and_set_bit(0, &timer_alive)) + return -EBUSY; + if (nowayout) + __module_get(THIS_MODULE); + /* + * Activate timer + */ + softdog_keepalive(); + return nonseekable_open(inode, file); +} + +static int softdog_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + * Lock it in if it's a module and we set nowayout + */ + if (expect_close == 42) { + softdog_stop(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + softdog_keepalive(); + } + clear_bit(0, &timer_alive); + expect_close = 0; + return 0; +} + +static ssize_t softdog_write(struct file *file, const char __user *data, size_t len, loff_t *ppos) +{ + /* + * Refresh the timer. + */ + if(len) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + for (i = 0; i != len; i++) { + char c; + + if (get_user(c, data + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + softdog_keepalive(); + } + return len; +} + +static int softdog_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_margin; + static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .firmware_version = 0, + .identity = "Software Watchdog", + }; + switch (cmd) { + default: + return -ENOIOCTLCMD; + 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: + softdog_keepalive(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_margin, p)) + return -EFAULT; + if (softdog_set_heartbeat(new_margin)) + return -EINVAL; + softdog_keepalive(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(soft_margin, p); + } +} + +/* + * Notifier for system down + */ + +static int softdog_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if(code==SYS_DOWN || code==SYS_HALT) { + /* Turn the WDT off */ + softdog_stop(); + } + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations softdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = softdog_write, + .ioctl = softdog_ioctl, + .open = softdog_open, + .release = softdog_release, +}; + +static struct miscdevice softdog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &softdog_fops, +}; + +static struct notifier_block softdog_notifier = { + .notifier_call = softdog_notify_sys, +}; + +static char banner[] __initdata = KERN_INFO "Software Watchdog Timer: 0.07 initialized. soft_noboot=%d soft_margin=%d sec (nowayout= %d)\n"; + +static int __init watchdog_init(void) +{ + int ret; + + /* Check that the soft_margin value is within it's range ; if not reset to the default */ + if (softdog_set_heartbeat(soft_margin)) { + softdog_set_heartbeat(TIMER_MARGIN); + printk(KERN_INFO PFX "soft_margin value must be 0<soft_margin<65536, using %d\n", + TIMER_MARGIN); + } + + ret = register_reboot_notifier(&softdog_notifier); + if (ret) { + printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + ret); + return ret; + } + + ret = misc_register(&softdog_miscdev); + if (ret) { + printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + unregister_reboot_notifier(&softdog_notifier); + return ret; + } + + printk(banner, soft_noboot, soft_margin, nowayout); + + return 0; +} + +static void __exit watchdog_exit(void) +{ + misc_deregister(&softdog_miscdev); + unregister_reboot_notifier(&softdog_notifier); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_AUTHOR("Alan Cox"); +MODULE_DESCRIPTION("Software Watchdog Device Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/w83627hf_wdt.c b/drivers/char/watchdog/w83627hf_wdt.c new file mode 100644 index 000000000000..813c97038f84 --- /dev/null +++ b/drivers/char/watchdog/w83627hf_wdt.c @@ -0,0 +1,362 @@ +/* + * w83627hf WDT driver + * + * (c) Copyright 2003 Pádraig Brady <P@draigBrady.com> + * + * Based on advantechwdt.c which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 2000-2001 Marek Michalkiewicz <marekm@linux.org.pl> + * + * (c) Copyright 1996 Alan Cox <alan@redhat.com>, All Rights Reserved. + * http://www.redhat.com + * + * 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@redhat.com> + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#define WATCHDOG_NAME "w83627hf WDT" +#define PFX WATCHDOG_NAME ": " +#define WATCHDOG_TIMEOUT 60 /* 60 sec default timeout */ + +static unsigned long wdt_is_open; +static char expect_close; + +/* You must set this - there is no sane way to probe for this board. */ +static int wdt_io = 0x2E; +module_param(wdt_io, int, 0); +MODULE_PARM_DESC(wdt_io, "w83627hf WDT io port (default 0x2E)"); + +static int timeout = WATCHDOG_TIMEOUT; /* in seconds */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=63, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) "."); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * Kernel methods. + */ + +#define WDT_EFER (wdt_io+0) /* Extended Function Enable Registers */ +#define WDT_EFIR (wdt_io+0) /* Extended Function Index Register (same as EFER) */ +#define WDT_EFDR (WDT_EFIR+1) /* Extended Function Data Register */ + +static void +w83627hf_select_wd_register(void) +{ + outb_p(0x87, WDT_EFER); /* Enter extended function mode */ + outb_p(0x87, WDT_EFER); /* Again according to manual */ + + outb_p(0x07, WDT_EFER); /* point to logical device number reg */ + outb_p(0x08, WDT_EFDR); /* select logical device 8 (GPIO2) */ + outb_p(0x30, WDT_EFER); /* select CR30 */ + outb_p(0x01, WDT_EFDR); /* set bit 0 to activate GPIO2 */ +} + +static void +w83627hf_unselect_wd_register(void) +{ + outb_p(0xAA, WDT_EFER); /* Leave extended function mode */ +} + +/* tyan motherboards seem to set F5 to 0x4C ? + * So explicitly init to appropriate value. */ +static void +w83627hf_init(void) +{ + unsigned char t; + + w83627hf_select_wd_register(); + + outb_p(0xF5, WDT_EFER); /* Select CRF5 */ + t=inb_p(WDT_EFDR); /* read CRF5 */ + t&=~0x0C; /* set second mode & disable keyboard turning off watchdog */ + outb_p(t, WDT_EFDR); /* Write back to CRF5 */ + + w83627hf_unselect_wd_register(); +} + +static void +wdt_ctrl(int timeout) +{ + w83627hf_select_wd_register(); + + outb_p(0xF6, WDT_EFER); /* Select CRF6 */ + outb_p(timeout, WDT_EFDR); /* Write Timeout counter to CRF6 */ + + w83627hf_unselect_wd_register(); +} + +static int +wdt_ping(void) +{ + wdt_ctrl(timeout); + return 0; +} + +static int +wdt_disable(void) +{ + wdt_ctrl(0); + return 0; +} + +static int +wdt_set_heartbeat(int t) +{ + if ((t < 1) || (t > 63)) + return -EINVAL; + + timeout = t; + return 0; +} + +static ssize_t +wdt_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf+i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + wdt_ping(); + } + return count; +} + +static int +wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_timeout; + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "W83627HF WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof(ident))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + wdt_ping(); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, p)) + return -EFAULT; + if (wdt_set_heartbeat(new_timeout)) + return -EINVAL; + wdt_ping(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + wdt_disable(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + wdt_ping(); + retval = 0; + } + + return retval; + } + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int +wdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + /* + * Activate + */ + + wdt_ping(); + return nonseekable_open(inode, file); +} + +static int +wdt_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + wdt_disable(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + wdt_ping(); + } + expect_close = 0; + clear_bit(0, &wdt_is_open); + return 0; +} + +/* + * Notifier for system down + */ + +static int +wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) { + /* Turn the WDT off */ + wdt_disable(); + } + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt_write, + .ioctl = wdt_ioctl, + .open = wdt_open, + .release = wdt_close, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +static int __init +wdt_init(void) +{ + int ret; + + printk(KERN_INFO "WDT driver for the Winbond(TM) W83627HF Super I/O chip initialising.\n"); + + if (wdt_set_heartbeat(timeout)) { + wdt_set_heartbeat(WATCHDOG_TIMEOUT); + printk (KERN_INFO PFX "timeout value must be 1<=timeout<=63, using %d\n", + WATCHDOG_TIMEOUT); + } + + if (!request_region(wdt_io, 1, WATCHDOG_NAME)) { + printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", + wdt_io); + ret = -EIO; + goto out; + } + + w83627hf_init(); + + ret = register_reboot_notifier(&wdt_notifier); + if (ret != 0) { + printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + ret); + goto unreg_regions; + } + + ret = misc_register(&wdt_miscdev); + if (ret != 0) { + printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto unreg_reboot; + } + + printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + +out: + return ret; +unreg_reboot: + unregister_reboot_notifier(&wdt_notifier); +unreg_regions: + release_region(wdt_io, 1); + goto out; +} + +static void __exit +wdt_exit(void) +{ + misc_deregister(&wdt_miscdev); + unregister_reboot_notifier(&wdt_notifier); + release_region(wdt_io,1); +} + +module_init(wdt_init); +module_exit(wdt_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Pádraig Brady <P@draigBrady.com>"); +MODULE_DESCRIPTION("w38627hf WDT driver"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/w83877f_wdt.c b/drivers/char/watchdog/w83877f_wdt.c new file mode 100644 index 000000000000..bccbd4d6ac2d --- /dev/null +++ b/drivers/char/watchdog/w83877f_wdt.c @@ -0,0 +1,426 @@ +/* + * W83877F Computer Watchdog Timer driver + * + * Based on acquirewdt.c by Alan Cox, + * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net> + * + * 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. + * + * The authors do NOT admit liability nor provide warranty for + * any of this software. This material is provided "AS-IS" in + * the hope that it may be useful for others. + * + * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net> + * + * 4/19 - 2001 [Initial revision] + * 9/27 - 2001 Added spinlocking + * 4/12 - 2002 [rob@osinvestor.com] Eliminate extra comments + * Eliminate fop_read + * Eliminate extra spin_unlock + * Added KERN_* tags to printks + * add CONFIG_WATCHDOG_NOWAYOUT support + * fix possible wdt_is_open race + * changed watchdog_info to correctly reflect what the driver offers + * added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, WDIOC_SETTIMEOUT, + * WDIOC_GETTIMEOUT, and WDIOC_SETOPTIONS ioctls + * 09/8 - 2003 [wim@iguana.be] cleanup of trailing spaces + * added extra printk's for startup problems + * use module_param + * made timeout (the emulated heartbeat) a module_param + * made the keepalive ping an internal subroutine + * + * This WDT driver is different from most other Linux WDT + * drivers in that the driver will ping the watchdog by itself, + * because this particular WDT has a very short timeout (1.6 + * seconds) and it would be insane to count on any userspace + * daemon always getting scheduled within that time frame. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#define OUR_NAME "w83877f_wdt" +#define PFX OUR_NAME ": " + +#define ENABLE_W83877F_PORT 0x3F0 +#define ENABLE_W83877F 0x87 +#define DISABLE_W83877F 0xAA +#define WDT_PING 0x443 +#define WDT_REGISTER 0x14 +#define WDT_ENABLE 0x9C +#define WDT_DISABLE 0x8C + +/* + * The W83877F seems to be fixed at 1.6s timeout (at least on the + * EMACS PC-104 board I'm using). If we reset the watchdog every + * ~250ms we should be safe. */ + +#define WDT_INTERVAL (HZ/4+1) + +/* + * We must not require too good response from the userspace daemon. + * Here we require the userspace daemon to send us a heartbeat + * char to /dev/watchdog every 30 seconds. + */ + +#define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ +static int timeout = WATCHDOG_TIMEOUT; /* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); + + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +static void wdt_timer_ping(unsigned long); +static struct timer_list timer; +static unsigned long next_heartbeat; +static unsigned long wdt_is_open; +static char wdt_expect_close; +static spinlock_t wdt_spinlock; + +/* + * Whack the dog + */ + +static void wdt_timer_ping(unsigned long data) +{ + /* If we got a heartbeat pulse within the WDT_US_INTERVAL + * we agree to ping the WDT + */ + if(time_before(jiffies, next_heartbeat)) + { + /* Ping the WDT */ + spin_lock(&wdt_spinlock); + + /* Ping the WDT by reading from WDT_PING */ + inb_p(WDT_PING); + + /* Re-set the timer interval */ + timer.expires = jiffies + WDT_INTERVAL; + add_timer(&timer); + + spin_unlock(&wdt_spinlock); + + } else { + printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog\n"); + } +} + +/* + * Utility routines + */ + +static void wdt_change(int writeval) +{ + unsigned long flags; + spin_lock_irqsave(&wdt_spinlock, flags); + + /* buy some time */ + inb_p(WDT_PING); + + /* make W83877F available */ + outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT); + outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT); + + /* enable watchdog */ + outb_p(WDT_REGISTER, ENABLE_W83877F_PORT); + outb_p(writeval, ENABLE_W83877F_PORT+1); + + /* lock the W8387FF away */ + outb_p(DISABLE_W83877F, ENABLE_W83877F_PORT); + + spin_unlock_irqrestore(&wdt_spinlock, flags); +} + +static void wdt_startup(void) +{ + next_heartbeat = jiffies + (timeout * HZ); + + /* Start the timer */ + timer.expires = jiffies + WDT_INTERVAL; + add_timer(&timer); + + wdt_change(WDT_ENABLE); + + printk(KERN_INFO PFX "Watchdog timer is now enabled.\n"); +} + +static void wdt_turnoff(void) +{ + /* Stop the timer */ + del_timer(&timer); + + wdt_change(WDT_DISABLE); + + printk(KERN_INFO PFX "Watchdog timer is now disabled...\n"); +} + +static void wdt_keepalive(void) +{ + /* user land ping */ + next_heartbeat = jiffies + (timeout * HZ); +} + +/* + * /dev/watchdog handling + */ + +static ssize_t fop_write(struct file * file, const char __user * buf, size_t count, loff_t * ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if(count) + { + if (!nowayout) + { + size_t ofs; + + /* note: just in case someone wrote the magic character + * five months ago... */ + wdt_expect_close = 0; + + /* scan to see whether or not we got the magic character */ + for(ofs = 0; ofs != count; ofs++) + { + char c; + if (get_user(c, buf + ofs)) + return -EFAULT; + if (c == 'V') + wdt_expect_close = 42; + } + } + + /* someone wrote to us, we should restart timer */ + wdt_keepalive(); + } + return count; +} + +static int fop_open(struct inode * inode, struct file * file) +{ + /* Just in case we're already talking to someone... */ + if(test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + + /* Good, fire up the show */ + wdt_startup(); + return nonseekable_open(inode, file); +} + +static int fop_close(struct inode * inode, struct file * file) +{ + if(wdt_expect_close == 42) + wdt_turnoff(); + else { + del_timer(&timer); + printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT!\n"); + } + clear_bit(0, &wdt_is_open); + wdt_expect_close = 0; + return 0; +} + +static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident= + { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "W83877F", + }; + + switch(cmd) + { + default: + return -ENOIOCTLCMD; + 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: + wdt_keepalive(); + return 0; + case WDIOC_SETOPTIONS: + { + int new_options, retval = -EINVAL; + + if(get_user(new_options, p)) + return -EFAULT; + + if(new_options & WDIOS_DISABLECARD) { + wdt_turnoff(); + retval = 0; + } + + if(new_options & WDIOS_ENABLECARD) { + wdt_startup(); + retval = 0; + } + + return retval; + } + case WDIOC_SETTIMEOUT: + { + int new_timeout; + + if(get_user(new_timeout, p)) + return -EFAULT; + + if(new_timeout < 1 || new_timeout > 3600) /* arbitrary upper limit */ + return -EINVAL; + + timeout = new_timeout; + wdt_keepalive(); + /* Fall through */ + } + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + } +} + +static struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = fop_write, + .open = fop_open, + .release = fop_close, + .ioctl = fop_ioctl, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +/* + * Notifier for system down + */ + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if(code==SYS_DOWN || code==SYS_HALT) + wdt_turnoff(); + return NOTIFY_DONE; +} + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier= +{ + .notifier_call = wdt_notify_sys, +}; + +static void __exit w83877f_wdt_unload(void) +{ + wdt_turnoff(); + + /* Deregister */ + misc_deregister(&wdt_miscdev); + + unregister_reboot_notifier(&wdt_notifier); + release_region(WDT_PING,1); + release_region(ENABLE_W83877F_PORT,2); +} + +static int __init w83877f_wdt_init(void) +{ + int rc = -EBUSY; + + spin_lock_init(&wdt_spinlock); + + if(timeout < 1 || timeout > 3600) /* arbitrary upper limit */ + { + timeout = WATCHDOG_TIMEOUT; + printk(KERN_INFO PFX "timeout value must be 1<=x<=3600, using %d\n", + timeout); + } + + if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) + { + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", + ENABLE_W83877F_PORT); + rc = -EIO; + goto err_out; + } + + if (!request_region(WDT_PING, 1, "W8387FF WDT")) + { + printk(KERN_ERR PFX "I/O address 0x%04x already in use\n", + WDT_PING); + rc = -EIO; + goto err_out_region1; + } + + init_timer(&timer); + timer.function = wdt_timer_ping; + timer.data = 0; + + rc = misc_register(&wdt_miscdev); + if (rc) + { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + wdt_miscdev.minor, rc); + goto err_out_region2; + } + + rc = register_reboot_notifier(&wdt_notifier); + if (rc) + { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + rc); + goto err_out_miscdev; + } + + printk(KERN_INFO PFX "WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + + return 0; + +err_out_miscdev: + misc_deregister(&wdt_miscdev); +err_out_region2: + release_region(WDT_PING,1); +err_out_region1: + release_region(ENABLE_W83877F_PORT,2); +err_out: + return rc; +} + +module_init(w83877f_wdt_init); +module_exit(w83877f_wdt_unload); + +MODULE_AUTHOR("Scott and Bill Jennings"); +MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/wafer5823wdt.c b/drivers/char/watchdog/wafer5823wdt.c new file mode 100644 index 000000000000..abb0bea45c02 --- /dev/null +++ b/drivers/char/watchdog/wafer5823wdt.c @@ -0,0 +1,330 @@ +/* + * ICP Wafer 5823 Single Board Computer WDT driver + * http://www.icpamerica.com/wafer_5823.php + * May also work on other similar models + * + * (c) Copyright 2002 Justin Cormack <justin@street-vision.com> + * + * Release 0.02 + * + * Based on advantechwdt.c which is based on wdt.c. + * Original copyright messages: + * + * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. + * http://www.redhat.com + * + * 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> + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <asm/io.h> +#include <asm/uaccess.h> + +#define WATCHDOG_NAME "Wafer 5823 WDT" +#define PFX WATCHDOG_NAME ": " +#define WD_TIMO 60 /* 60 sec default timeout */ + +static unsigned long wafwdt_is_open; +static char expect_close; +static spinlock_t wafwdt_lock; + +/* + * You must set these - there is no sane way to probe for this board. + * + * To enable, write the timeout value in seconds (1 to 255) to I/O + * port WDT_START, then read the port to start the watchdog. To pat + * the dog, read port WDT_STOP to stop the timer, then read WDT_START + * to restart it again. + */ + +static int wdt_stop = 0x843; +static int wdt_start = 0x443; + +static int timeout = WD_TIMO; /* in seconds */ +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. 1<= timeout <=255, default=" __MODULE_STRING(WD_TIMO) "."); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +static void wafwdt_ping(void) +{ + /* pat watchdog */ + spin_lock(&wafwdt_lock); + inb_p(wdt_stop); + inb_p(wdt_start); + spin_unlock(&wafwdt_lock); +} + +static void wafwdt_start(void) +{ + /* start up watchdog */ + outb_p(timeout, wdt_start); + inb_p(wdt_start); +} + +static void +wafwdt_stop(void) +{ + /* stop watchdog */ + inb_p(wdt_stop); +} + +static ssize_t wafwdt_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (count) { + if (!nowayout) { + size_t i; + + /* In case it was set long ago */ + expect_close = 0; + + /* scan to see whether or not we got the magic character */ + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + /* Well, anyhow someone wrote to us, we should return that favour */ + wafwdt_ping(); + } + return count; +} + +static int wafwdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + int new_timeout; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, + .firmware_version = 1, + .identity = "Wafer 5823 WDT", + }; + + switch (cmd) { + case WDIOC_GETSUPPORT: + if (copy_to_user(argp, &ident, sizeof (ident))) + return -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + + case WDIOC_KEEPALIVE: + wafwdt_ping(); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, p)) + return -EFAULT; + if ((new_timeout < 1) || (new_timeout > 255)) + return -EINVAL; + timeout = new_timeout; + wafwdt_stop(); + wafwdt_start(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(timeout, p); + + case WDIOC_SETOPTIONS: + { + int options, retval = -EINVAL; + + if (get_user(options, p)) + return -EFAULT; + + if (options & WDIOS_DISABLECARD) { + wafwdt_start(); + retval = 0; + } + + if (options & WDIOS_ENABLECARD) { + wafwdt_stop(); + retval = 0; + } + + return retval; + } + + default: + return -ENOIOCTLCMD; + } + return 0; +} + +static int wafwdt_open(struct inode *inode, struct file *file) +{ + if (test_and_set_bit(0, &wafwdt_is_open)) + return -EBUSY; + + /* + * Activate + */ + wafwdt_start(); + return nonseekable_open(inode, file); +} + +static int +wafwdt_close(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + wafwdt_stop(); + } else { + printk(KERN_CRIT PFX "WDT device closed unexpectedly. WDT will not stop!\n"); + wafwdt_ping(); + } + clear_bit(0, &wafwdt_is_open); + expect_close = 0; + return 0; +} + +/* + * Notifier for system down + */ + +static int wafwdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) +{ + if (code == SYS_DOWN || code == SYS_HALT) { + /* Turn the WDT off */ + wafwdt_stop(); + } + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + +static struct file_operations wafwdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wafwdt_write, + .ioctl = wafwdt_ioctl, + .open = wafwdt_open, + .release = wafwdt_close, +}; + +static struct miscdevice wafwdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wafwdt_fops, +}; + +/* + * The WDT needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wafwdt_notifier = { + .notifier_call = wafwdt_notify_sys, +}; + +static int __init wafwdt_init(void) +{ + int ret; + + printk(KERN_INFO "WDT driver for Wafer 5823 single board computer initialising.\n"); + + spin_lock_init(&wafwdt_lock); + + if (timeout < 1 || timeout > 255) { + timeout = WD_TIMO; + printk (KERN_INFO PFX "timeout value must be 1<=x<=255, using %d\n", + timeout); + } + + if (wdt_stop != wdt_start) { + if(!request_region(wdt_stop, 1, "Wafer 5823 WDT")) { + printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", + wdt_stop); + ret = -EIO; + goto error; + } + } + + if(!request_region(wdt_start, 1, "Wafer 5823 WDT")) { + printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", + wdt_start); + ret = -EIO; + goto error2; + } + + ret = register_reboot_notifier(&wafwdt_notifier); + if (ret != 0) { + printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + ret); + goto error3; + } + + ret = misc_register(&wafwdt_miscdev); + if (ret != 0) { + printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto error4; + } + + printk (KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d)\n", + timeout, nowayout); + + return ret; +error4: + unregister_reboot_notifier(&wafwdt_notifier); +error3: + release_region(wdt_start, 1); +error2: + if (wdt_stop != wdt_start) + release_region(wdt_stop, 1); +error: + return ret; +} + +static void __exit wafwdt_exit(void) +{ + misc_deregister(&wafwdt_miscdev); + unregister_reboot_notifier(&wafwdt_notifier); + if(wdt_stop != wdt_start) + release_region(wdt_stop, 1); + release_region(wdt_start, 1); +} + +module_init(wafwdt_init); +module_exit(wafwdt_exit); + +MODULE_AUTHOR("Justin Cormack"); +MODULE_DESCRIPTION("ICP Wafer 5823 Single Board Computer WDT driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +/* end of wafer5823wdt.c */ diff --git a/drivers/char/watchdog/wd501p.h b/drivers/char/watchdog/wd501p.h new file mode 100644 index 000000000000..84e60eb74337 --- /dev/null +++ b/drivers/char/watchdog/wd501p.h @@ -0,0 +1,52 @@ +/* + * Industrial Computer Source WDT500/501 driver + * + * (c) Copyright 1995 CymruNET Ltd + * Innovation Centre + * Singleton Park + * Swansea + * Wales + * UK + * SA2 8PP + * + * http://www.cymru.net + * + * This driver is provided under the GNU General Public License, incorporated + * herein by reference. The driver is provided without warranty or + * support. + * + * Release 0.04. + * + */ + +#include <linux/config.h> + +#define WDT_COUNT0 (io+0) +#define WDT_COUNT1 (io+1) +#define WDT_COUNT2 (io+2) +#define WDT_CR (io+3) +#define WDT_SR (io+4) /* Start buzzer on PCI write */ +#define WDT_RT (io+5) /* Stop buzzer on PCI write */ +#define WDT_BUZZER (io+6) /* PCI only: rd=disable, wr=enable */ +#define WDT_DC (io+7) + +/* The following are only on the PCI card, they're outside of I/O space on + * the ISA card: */ +#define WDT_CLOCK (io+12) /* COUNT2: rd=16.67MHz, wr=2.0833MHz */ +/* inverted opto isolated reset output: */ +#define WDT_OPTONOTRST (io+13) /* wr=enable, rd=disable */ +/* opto isolated reset output: */ +#define WDT_OPTORST (io+14) /* wr=enable, rd=disable */ +/* programmable outputs: */ +#define WDT_PROGOUT (io+15) /* wr=enable, rd=disable */ + + /* FAN 501 500 */ +#define WDC_SR_WCCR 1 /* Active low */ /* X X X */ +#define WDC_SR_TGOOD 2 /* X X - */ +#define WDC_SR_ISOI0 4 /* X X X */ +#define WDC_SR_ISII1 8 /* X X X */ +#define WDC_SR_FANGOOD 16 /* X - - */ +#define WDC_SR_PSUOVER 32 /* Active low */ /* X X - */ +#define WDC_SR_PSUUNDR 64 /* Active low */ /* X X - */ +#define WDC_SR_IRQ 128 /* Active low */ /* X X X */ + diff --git a/drivers/char/watchdog/wdt.c b/drivers/char/watchdog/wdt.c new file mode 100644 index 000000000000..5684aa379886 --- /dev/null +++ b/drivers/char/watchdog/wdt.c @@ -0,0 +1,647 @@ +/* + * Industrial Computer Source WDT500/501 driver + * + * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. + * http://www.redhat.com + * + * 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> + * + * Release 0.10. + * + * Fixes + * Dave Gregorich : Modularisation and minor bugs + * Alan Cox : Added the watchdog ioctl() stuff + * Alan Cox : Fixed the reboot problem (as noted by + * Matt Crocker). + * Alan Cox : Added wdt= boot option + * Alan Cox : Cleaned up copy/user stuff + * Tim Hockin : Added insmod parameters, comment cleanup + * Parameterized timeout + * Tigran Aivazian : Restructured wdt_init() to handle failures + * Joel Becker : Added WDIOC_GET/SETTIMEOUT + * Matt Domsch : Added nowayout module option + */ + +#include <linux/config.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/fs.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> +#include "wd501p.h" + +static unsigned long wdt_is_open; +static char expect_close; + +/* + * Module parameters + */ + +#define WD_TIMO 60 /* Default heartbeat = 60 seconds */ + +static int heartbeat = WD_TIMO; +static int wd_heartbeat; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* You must set these - there is no sane way to probe for this board. */ +static int io=0x240; +static int irq=11; + +module_param(io, int, 0); +MODULE_PARM_DESC(io, "WDT io port (default=0x240)"); +module_param(irq, int, 0); +MODULE_PARM_DESC(irq, "WDT irq (default=11)"); + +#ifdef CONFIG_WDT_501 +/* Support for the Fan Tachometer on the WDT501-P */ +static int tachometer; + +module_param(tachometer, int, 0); +MODULE_PARM_DESC(tachometer, "WDT501-P Fan Tachometer support (0=disable, default=0)"); +#endif /* CONFIG_WDT_501 */ + +/* + * Programming support + */ + +static void wdt_ctr_mode(int ctr, int mode) +{ + ctr<<=6; + ctr|=0x30; + ctr|=(mode<<1); + outb_p(ctr, WDT_CR); +} + +static void wdt_ctr_load(int ctr, int val) +{ + outb_p(val&0xFF, WDT_COUNT0+ctr); + outb_p(val>>8, WDT_COUNT0+ctr); +} + +/** + * wdt_start: + * + * Start the watchdog driver. + */ + +static int wdt_start(void) +{ + inb_p(WDT_DC); /* Disable watchdog */ + wdt_ctr_mode(0,3); /* Program CTR0 for Mode 3: Square Wave Generator */ + wdt_ctr_mode(1,2); /* Program CTR1 for Mode 2: Rate Generator */ + wdt_ctr_mode(2,0); /* Program CTR2 for Mode 0: Pulse on Terminal Count */ + wdt_ctr_load(0, 8948); /* Count at 100Hz */ + wdt_ctr_load(1,wd_heartbeat); /* Heartbeat */ + wdt_ctr_load(2,65535); /* Length of reset pulse */ + outb_p(0, WDT_DC); /* Enable watchdog */ + return 0; +} + +/** + * wdt_stop: + * + * Stop the watchdog driver. + */ + +static int wdt_stop (void) +{ + /* Turn the card off */ + inb_p(WDT_DC); /* Disable watchdog */ + wdt_ctr_load(2,0); /* 0 length reset pulses now */ + return 0; +} + +/** + * wdt_ping: + * + * Reload counter one with the watchdog heartbeat. We don't bother reloading + * the cascade counter. + */ + +static int wdt_ping(void) +{ + /* Write a watchdog value */ + inb_p(WDT_DC); /* Disable watchdog */ + wdt_ctr_mode(1,2); /* Re-Program CTR1 for Mode 2: Rate Generator */ + wdt_ctr_load(1,wd_heartbeat); /* Heartbeat */ + outb_p(0, WDT_DC); /* Enable watchdog */ + return 0; +} + +/** + * wdt_set_heartbeat: + * @t: the new heartbeat value that needs to be set. + * + * Set a new heartbeat value for the watchdog device. If the heartbeat value is + * incorrect we keep the old value and return -EINVAL. If successfull we + * return 0. + */ +static int wdt_set_heartbeat(int t) +{ + if ((t < 1) || (t > 65535)) + return -EINVAL; + + heartbeat = t; + wd_heartbeat = t * 100; + return 0; +} + +/** + * wdt_get_status: + * @status: the new status. + * + * Extract the status information from a WDT watchdog device. There are + * several board variants so we have to know which bits are valid. Some + * bits default to one and some to zero in order to be maximally painful. + * + * we then map the bits onto the status ioctl flags. + */ + +static int wdt_get_status(int *status) +{ + unsigned char new_status=inb_p(WDT_SR); + + *status=0; + if (new_status & WDC_SR_ISOI0) + *status |= WDIOF_EXTERN1; + if (new_status & WDC_SR_ISII1) + *status |= WDIOF_EXTERN2; +#ifdef CONFIG_WDT_501 + if (!(new_status & WDC_SR_TGOOD)) + *status |= WDIOF_OVERHEAT; + if (!(new_status & WDC_SR_PSUOVER)) + *status |= WDIOF_POWEROVER; + if (!(new_status & WDC_SR_PSUUNDR)) + *status |= WDIOF_POWERUNDER; + if (tachometer) { + if (!(new_status & WDC_SR_FANGOOD)) + *status |= WDIOF_FANFAULT; + } +#endif /* CONFIG_WDT_501 */ + return 0; +} + +#ifdef CONFIG_WDT_501 +/** + * wdt_get_temperature: + * + * Reports the temperature in degrees Fahrenheit. The API is in + * farenheit. It was designed by an imperial measurement luddite. + */ + +static int wdt_get_temperature(int *temperature) +{ + unsigned short c=inb_p(WDT_RT); + + *temperature = (c * 11 / 15) + 7; + return 0; +} +#endif /* CONFIG_WDT_501 */ + +/** + * wdt_interrupt: + * @irq: Interrupt number + * @dev_id: Unused as we don't allow multiple devices. + * @regs: Unused. + * + * Handle an interrupt from the board. These are raised when the status + * map changes in what the board considers an interesting way. That means + * a failure condition occurring. + */ + +static irqreturn_t wdt_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + /* + * Read the status register see what is up and + * then printk it. + */ + unsigned char status=inb_p(WDT_SR); + + printk(KERN_CRIT "WDT status %d\n", status); + +#ifdef CONFIG_WDT_501 + if (!(status & WDC_SR_TGOOD)) + printk(KERN_CRIT "Overheat alarm.(%d)\n",inb_p(WDT_RT)); + if (!(status & WDC_SR_PSUOVER)) + printk(KERN_CRIT "PSU over voltage.\n"); + if (!(status & WDC_SR_PSUUNDR)) + printk(KERN_CRIT "PSU under voltage.\n"); + if (tachometer) { + if (!(status & WDC_SR_FANGOOD)) + printk(KERN_CRIT "Possible fan fault.\n"); + } +#endif /* CONFIG_WDT_501 */ + if (!(status & WDC_SR_WCCR)) +#ifdef SOFTWARE_REBOOT +#ifdef ONLY_TESTING + printk(KERN_CRIT "Would Reboot.\n"); +#else + printk(KERN_CRIT "Initiating system reboot.\n"); + machine_restart(NULL); +#endif +#else + printk(KERN_CRIT "Reset in 5ms.\n"); +#endif + return IRQ_HANDLED; +} + + +/** + * 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 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 = 42; + } + } + wdt_ping(); + } + return count; +} + +/** + * wdt_ioctl: + * @inode: inode of the device + * @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 current status. + */ + +static int wdt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_heartbeat; + int status; + + static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT| + WDIOF_MAGICCLOSE| + WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "WDT500/501", + }; + + /* Add options according to the card we have */ + ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2); +#ifdef CONFIG_WDT_501 + ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER); + if (tachometer) + ident.options |= WDIOF_FANFAULT; +#endif /* CONFIG_WDT_501 */ + + switch(cmd) + { + default: + return -ENOIOCTLCMD; + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; + + case WDIOC_GETSTATUS: + wdt_get_status(&status); + return put_user(status, p); + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_KEEPALIVE: + wdt_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, p)) + return -EFAULT; + + if (wdt_set_heartbeat(new_heartbeat)) + return -EINVAL; + + wdt_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + } +} + +/** + * 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 load the counters. Counter zero is a 100Hz + * cascade, into counter 1 which downcounts to reboot. When the counter + * triggers counter 2 downcounts the length of the reset pulse which + * set set to be as long as possible. + */ + +static int wdt_open(struct inode *inode, struct file *file) +{ + if(test_and_set_bit(0, &wdt_is_open)) + return -EBUSY; + /* + * Activate + */ + wdt_start(); + return nonseekable_open(inode, file); +} + +/** + * 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 wdt_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + wdt_stop(); + clear_bit(0, &wdt_is_open); + } else { + printk(KERN_CRIT "wdt: WDT device closed unexpectedly. WDT will not stop!\n"); + wdt_ping(); + } + expect_close = 0; + return 0; +} + +#ifdef CONFIG_WDT_501 +/** + * wdt_temp_read: + * @file: file handle to the watchdog board + * @buf: buffer to write 1 byte into + * @count: length of buffer + * @ptr: offset (no seek allowed) + * + * Temp_read reports the temperature in degrees Fahrenheit. The API is in + * farenheit. It was designed by an imperial measurement luddite. + */ + +static ssize_t wdt_temp_read(struct file *file, char __user *buf, size_t count, loff_t *ptr) +{ + int temperature; + + if (wdt_get_temperature(&temperature)) + return -EFAULT; + + if (copy_to_user (buf, &temperature, 1)) + return -EFAULT; + + return 1; +} + +/** + * wdt_temp_open: + * @inode: inode of device + * @file: file handle to device + * + * The temperature device has been opened. + */ + +static int wdt_temp_open(struct inode *inode, struct file *file) +{ + return nonseekable_open(inode, file); +} + +/** + * wdt_temp_release: + * @inode: inode to board + * @file: file handle to board + * + * The temperature device has been closed. + */ + +static int wdt_temp_release(struct inode *inode, struct file *file) +{ + return 0; +} +#endif /* CONFIG_WDT_501 */ + +/** + * notify_sys: + * @this: our notifier block + * @code: the event being reported + * @unused: unused + * + * Our notifier is called on system shutdowns. We want to turn the card + * off at reboot otherwise the machine will reboot again during memory + * test or worse yet during the following fsck. This would suck, in fact + * trust me - if it happens it does suck. + */ + +static int wdt_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if(code==SYS_DOWN || code==SYS_HALT) { + /* Turn the card off */ + wdt_stop(); + } + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + + +static struct file_operations wdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt_write, + .ioctl = wdt_ioctl, + .open = wdt_open, + .release = wdt_release, +}; + +static struct miscdevice wdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt_fops, +}; + +#ifdef CONFIG_WDT_501 +static struct file_operations wdt_temp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = wdt_temp_read, + .open = wdt_temp_open, + .release = wdt_temp_release, +}; + +static struct miscdevice temp_miscdev = { + .minor = TEMP_MINOR, + .name = "temperature", + .fops = &wdt_temp_fops, +}; +#endif /* CONFIG_WDT_501 */ + +/* + * The WDT card needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdt_notifier = { + .notifier_call = wdt_notify_sys, +}; + +/** + * cleanup_module: + * + * Unload the watchdog. You cannot do this with any file handles open. + * If your watchdog is set to continue ticking on close and you unload + * it, well it keeps ticking. We won't get the interrupt but the board + * will not touch PC memory so all is fine. You just have to load a new + * module in 60 seconds or reboot. + */ + +static void __exit wdt_exit(void) +{ + misc_deregister(&wdt_miscdev); +#ifdef CONFIG_WDT_501 + misc_deregister(&temp_miscdev); +#endif /* CONFIG_WDT_501 */ + unregister_reboot_notifier(&wdt_notifier); + free_irq(irq, NULL); + release_region(io,8); +} + +/** + * wdt_init: + * + * Set up the WDT watchdog board. All we have to do is grab the + * resources we require and bitch if anyone beat us to them. + * The open() function will actually kick the board off. + */ + +static int __init wdt_init(void) +{ + int ret; + + /* Check that the heartbeat value is within it's range ; if not reset to the default */ + if (wdt_set_heartbeat(heartbeat)) { + wdt_set_heartbeat(WD_TIMO); + printk(KERN_INFO "wdt: heartbeat value must be 0<heartbeat<65536, using %d\n", + WD_TIMO); + } + + if (!request_region(io, 8, "wdt501p")) { + printk(KERN_ERR "wdt: I/O address 0x%04x already in use\n", io); + ret = -EBUSY; + goto out; + } + + ret = request_irq(irq, wdt_interrupt, SA_INTERRUPT, "wdt501p", NULL); + if(ret) { + printk(KERN_ERR "wdt: IRQ %d is not free.\n", irq); + goto outreg; + } + + ret = register_reboot_notifier(&wdt_notifier); + if(ret) { + printk(KERN_ERR "wdt: cannot register reboot notifier (err=%d)\n", ret); + goto outirq; + } + +#ifdef CONFIG_WDT_501 + ret = misc_register(&temp_miscdev); + if (ret) { + printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n", + TEMP_MINOR, ret); + goto outrbt; + } +#endif /* CONFIG_WDT_501 */ + + ret = misc_register(&wdt_miscdev); + if (ret) { + printk(KERN_ERR "wdt: cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto outmisc; + } + + ret = 0; + printk(KERN_INFO "WDT500/501-P driver 0.10 at 0x%04x (Interrupt %d). heartbeat=%d sec (nowayout=%d)\n", + io, irq, heartbeat, nowayout); +#ifdef CONFIG_WDT_501 + printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled")); +#endif /* CONFIG_WDT_501 */ + +out: + return ret; + +outmisc: +#ifdef CONFIG_WDT_501 + misc_deregister(&temp_miscdev); +outrbt: +#endif /* CONFIG_WDT_501 */ + unregister_reboot_notifier(&wdt_notifier); +outirq: + free_irq(irq, NULL); +outreg: + release_region(io,8); + goto out; +} + +module_init(wdt_init); +module_exit(wdt_exit); + +MODULE_AUTHOR("Alan Cox"); +MODULE_DESCRIPTION("Driver for ISA ICS watchdog cards (WDT500/501)"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS_MISCDEV(TEMP_MINOR); +MODULE_LICENSE("GPL"); diff --git a/drivers/char/watchdog/wdt285.c b/drivers/char/watchdog/wdt285.c new file mode 100644 index 000000000000..52825a1f1779 --- /dev/null +++ b/drivers/char/watchdog/wdt285.c @@ -0,0 +1,229 @@ +/* + * Intel 21285 watchdog driver + * Copyright (c) Phil Blundell <pb@nexus.co.uk>, 1998 + * + * based on + * + * SoftDog 0.05: A Software Watchdog Device + * + * (c) Copyright 1996 Alan Cox <alan@redhat.com>, 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. + * + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/mm.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/interrupt.h> + +#include <asm/irq.h> +#include <asm/uaccess.h> +#include <asm/hardware.h> +#include <asm/mach-types.h> +#include <asm/hardware/dec21285.h> + +/* + * Define this to stop the watchdog actually rebooting the machine. + */ +#undef ONLY_TESTING + +static unsigned int soft_margin = 60; /* in seconds */ +static unsigned int reload; +static unsigned long timer_alive; + +#ifdef ONLY_TESTING +/* + * If the timer expires.. + */ +static void watchdog_fire(int irq, void *dev_id, struct pt_regs *regs) +{ + printk(KERN_CRIT "Watchdog: Would Reboot.\n"); + *CSR_TIMER4_CNTL = 0; + *CSR_TIMER4_CLR = 0; +} +#endif + +/* + * Refresh the timer. + */ +static void watchdog_ping(void) +{ + *CSR_TIMER4_LOAD = reload; +} + +/* + * Allow only one person to hold it open + */ +static int watchdog_open(struct inode *inode, struct file *file) +{ + int ret; + + if (*CSR_SA110_CNTL & (1 << 13)) + return -EBUSY; + + if (test_and_set_bit(1, &timer_alive)) + return -EBUSY; + + reload = soft_margin * (mem_fclk_21285 / 256); + + *CSR_TIMER4_CLR = 0; + watchdog_ping(); + *CSR_TIMER4_CNTL = TIMER_CNTL_ENABLE | TIMER_CNTL_AUTORELOAD + | TIMER_CNTL_DIV256; + +#ifdef ONLY_TESTING + ret = request_irq(IRQ_TIMER4, watchdog_fire, 0, "watchdog", NULL); + if (ret) { + *CSR_TIMER4_CNTL = 0; + clear_bit(1, &timer_alive); + } +#else + /* + * Setting this bit is irreversible; once enabled, there is + * no way to disable the watchdog. + */ + *CSR_SA110_CNTL |= 1 << 13; + + ret = 0; +#endif + nonseekable_open(inode, file); + return ret; +} + +/* + * Shut off the timer. + * Note: if we really have enabled the watchdog, there + * is no way to turn off. + */ +static int watchdog_release(struct inode *inode, struct file *file) +{ +#ifdef ONLY_TESTING + free_irq(IRQ_TIMER4, NULL); + clear_bit(1, &timer_alive); +#endif + return 0; +} + +static ssize_t +watchdog_write(struct file *file, const char *data, size_t len, loff_t *ppos) +{ + /* + * Refresh the timer. + */ + if (len) + watchdog_ping(); + + return len; +} + +static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT, + .identity = "Footbridge Watchdog", +}; + +static int +watchdog_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + unsigned int new_margin; + int ret = -ENOIOCTLCMD; + + switch(cmd) { + case WDIOC_GETSUPPORT: + ret = 0; + if (copy_to_user((void *)arg, &ident, sizeof(ident))) + ret = -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + ret = put_user(0,(int *)arg); + break; + + case WDIOC_KEEPALIVE: + watchdog_ping(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(new_margin, (int *)arg); + if (ret) + break; + + /* Arbitrary, can't find the card's limits */ + if (new_margin < 0 || new_margin > 60) { + ret = -EINVAL; + break; + } + + soft_margin = new_margin; + reload = soft_margin * (mem_fclk_21285 / 256); + watchdog_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + ret = put_user(soft_margin, (int *)arg); + break; + } + return ret; +} + +static struct file_operations watchdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = watchdog_write, + .ioctl = watchdog_ioctl, + .open = watchdog_open, + .release = watchdog_release, +}; + +static struct miscdevice watchdog_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &watchdog_fops, +}; + +static int __init footbridge_watchdog_init(void) +{ + int retval; + + if (machine_is_netwinder()) + return -ENODEV; + + retval = misc_register(&watchdog_miscdev); + if (retval < 0) + return retval; + + printk("Footbridge Watchdog Timer: 0.01, timer margin: %d sec\n", + soft_margin); + + if (machine_is_cats()) + printk("Warning: Watchdog reset may not work on this machine.\n"); + return 0; +} + +static void __exit footbridge_watchdog_exit(void) +{ + misc_deregister(&watchdog_miscdev); +} + +MODULE_AUTHOR("Phil Blundell <pb@nexus.co.uk>"); +MODULE_DESCRIPTION("Footbridge watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +module_param(soft_margin, int, 0); +MODULE_PARM_DESC(soft_margin,"Watchdog timeout in seconds"); + +module_init(footbridge_watchdog_init); +module_exit(footbridge_watchdog_exit); diff --git a/drivers/char/watchdog/wdt977.c b/drivers/char/watchdog/wdt977.c new file mode 100644 index 000000000000..072e9b214759 --- /dev/null +++ b/drivers/char/watchdog/wdt977.c @@ -0,0 +1,459 @@ +/* + * Wdt977 0.03: A Watchdog Device for Netwinder W83977AF chip + * + * (c) Copyright 1998 Rebel.com (Woody Suwalski <woody@netwinder.org>) + * + * ----------------------- + * + * 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. + * + * ----------------------- + * 14-Dec-2001 Matt Domsch <Matt_Domsch@dell.com> + * Added nowayout module option to override CONFIG_WATCHDOG_NOWAYOUT + * 19-Dec-2001 Woody Suwalski: Netwinder fixes, ioctl interface + * 06-Jan-2002 Woody Suwalski: For compatibility, convert all timeouts + * from minutes to seconds. + * 07-Jul-2003 Daniele Bellucci: Audit return code of misc_register in + * nwwatchdog_init. + */ + +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/config.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/fs.h> +#include <linux/miscdevice.h> +#include <linux/init.h> +#include <linux/watchdog.h> +#include <linux/notifier.h> +#include <linux/reboot.h> + +#include <asm/io.h> +#include <asm/system.h> +#include <asm/mach-types.h> +#include <asm/uaccess.h> + +#define PFX "Wdt977: " +#define WATCHDOG_MINOR 130 + +#define DEFAULT_TIMEOUT 60 /* default timeout in seconds */ + +static int timeout = DEFAULT_TIMEOUT; +static int timeoutM; /* timeout in minutes */ +static unsigned long timer_alive; +static int testmode; +static char expect_close; + +module_param(timeout, int, 0); +MODULE_PARM_DESC(timeout,"Watchdog timeout in seconds (60..15300), default=" __MODULE_STRING(DEFAULT_TIMEOUT) ")"); +module_param(testmode, int, 0); +MODULE_PARM_DESC(testmode,"Watchdog testmode (1 = no reboot), default=0"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +/* + * Start the watchdog + */ + +static int wdt977_start(void) +{ + /* unlock the SuperIO chip */ + outb(0x87,0x370); + outb(0x87,0x370); + + /* select device Aux2 (device=8) and set watchdog regs F2, F3 and F4 + * F2 has the timeout in minutes + * F3 could be set to the POWER LED blink (with GP17 set to PowerLed) + * at timeout, and to reset timer on kbd/mouse activity (not impl.) + * F4 is used to just clear the TIMEOUT'ed state (bit 0) + */ + outb(0x07,0x370); + outb(0x08,0x371); + outb(0xF2,0x370); + outb(timeoutM,0x371); + outb(0xF3,0x370); + outb(0x00,0x371); /* another setting is 0E for kbd/mouse/LED */ + outb(0xF4,0x370); + outb(0x00,0x371); + + /* at last select device Aux1 (dev=7) and set GP16 as a watchdog output */ + /* in test mode watch the bit 1 on F4 to indicate "triggered" */ + if (!testmode) + { + outb(0x07,0x370); + outb(0x07,0x371); + outb(0xE6,0x370); + outb(0x08,0x371); + } + + /* lock the SuperIO chip */ + outb(0xAA,0x370); + + printk(KERN_INFO PFX "activated.\n"); + + return 0; +} + +/* + * Stop the watchdog + */ + +static int wdt977_stop(void) +{ + /* unlock the SuperIO chip */ + outb(0x87,0x370); + outb(0x87,0x370); + + /* select device Aux2 (device=8) and set watchdog regs F2,F3 and F4 + * F3 is reset to its default state + * F4 can clear the TIMEOUT'ed state (bit 0) - back to default + * We can not use GP17 as a PowerLed, as we use its usage as a RedLed + */ + outb(0x07,0x370); + outb(0x08,0x371); + outb(0xF2,0x370); + outb(0xFF,0x371); + outb(0xF3,0x370); + outb(0x00,0x371); + outb(0xF4,0x370); + outb(0x00,0x371); + outb(0xF2,0x370); + outb(0x00,0x371); + + /* at last select device Aux1 (dev=7) and set GP16 as a watchdog output */ + outb(0x07,0x370); + outb(0x07,0x371); + outb(0xE6,0x370); + outb(0x08,0x371); + + /* lock the SuperIO chip */ + outb(0xAA,0x370); + + printk(KERN_INFO PFX "shutdown.\n"); + + return 0; +} + +/* + * Send a keepalive ping to the watchdog + * This is done by simply re-writing the timeout to reg. 0xF2 + */ + +static int wdt977_keepalive(void) +{ + /* unlock the SuperIO chip */ + outb(0x87,0x370); + outb(0x87,0x370); + + /* select device Aux2 (device=8) and kicks watchdog reg F2 */ + /* F2 has the timeout in minutes */ + outb(0x07,0x370); + outb(0x08,0x371); + outb(0xF2,0x370); + outb(timeoutM,0x371); + + /* lock the SuperIO chip */ + outb(0xAA,0x370); + + return 0; +} + +/* + * Set the watchdog timeout value + */ + +static int wdt977_set_timeout(int t) +{ + int tmrval; + + /* convert seconds to minutes, rounding up */ + tmrval = (t + 59) / 60; + + if (machine_is_netwinder()) { + /* we have a hw bug somewhere, so each 977 minute is actually only 30sec + * this limits the max timeout to half of device max of 255 minutes... + */ + tmrval += tmrval; + } + + if ((tmrval < 1) || (tmrval > 255)) + return -EINVAL; + + /* timeout is the timeout in seconds, timeoutM is the timeout in minutes) */ + timeout = t; + timeoutM = tmrval; + return 0; +} + +/* + * Get the watchdog status + */ + +static int wdt977_get_status(int *status) +{ + int new_status; + + *status=0; + + /* unlock the SuperIO chip */ + outb(0x87,0x370); + outb(0x87,0x370); + + /* select device Aux2 (device=8) and read watchdog reg F4 */ + outb(0x07,0x370); + outb(0x08,0x371); + outb(0xF4,0x370); + new_status = inb(0x371); + + /* lock the SuperIO chip */ + outb(0xAA,0x370); + + if (new_status & 1) + *status |= WDIOF_CARDRESET; + + return 0; +} + + +/* + * /dev/watchdog handling + */ + +static int wdt977_open(struct inode *inode, struct file *file) +{ + /* If the watchdog is alive we don't need to start it again */ + if( test_and_set_bit(0,&timer_alive) ) + return -EBUSY; + + if (nowayout) + __module_get(THIS_MODULE); + + wdt977_start(); + return nonseekable_open(inode, file); +} + +static int wdt977_release(struct inode *inode, struct file *file) +{ + /* + * Shut off the timer. + * Lock it in if it's a module and we set nowayout + */ + if (expect_close == 42) + { + wdt977_stop(); + clear_bit(0,&timer_alive); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); + wdt977_keepalive(); + } + expect_close = 0; + return 0; +} + + +/* + * wdt977_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 wdt977_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 = 42; + } + } + + wdt977_keepalive(); + } + return count; +} + +/* + * wdt977_ioctl: + * @inode: inode of the device + * @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. + */ + +static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_MAGICCLOSE | + WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "Winbond 83977", +}; + +static int wdt977_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int status; + int new_options, retval = -EINVAL; + int new_timeout; + union { + struct watchdog_info __user *ident; + int __user *i; + } uarg; + + uarg.i = (int __user *)arg; + + switch(cmd) + { + default: + return -ENOIOCTLCMD; + + case WDIOC_GETSUPPORT: + return copy_to_user(uarg.ident, &ident, + sizeof(ident)) ? -EFAULT : 0; + + case WDIOC_GETSTATUS: + wdt977_get_status(&status); + return put_user(status, uarg.i); + + case WDIOC_GETBOOTSTATUS: + return put_user(0, uarg.i); + + case WDIOC_KEEPALIVE: + wdt977_keepalive(); + return 0; + + case WDIOC_SETOPTIONS: + if (get_user (new_options, uarg.i)) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + wdt977_stop(); + retval = 0; + } + + if (new_options & WDIOS_ENABLECARD) { + wdt977_start(); + retval = 0; + } + + return retval; + + case WDIOC_SETTIMEOUT: + if (get_user(new_timeout, uarg.i)) + return -EFAULT; + + if (wdt977_set_timeout(new_timeout)) + return -EINVAL; + + wdt977_keepalive(); + /* Fall */ + + case WDIOC_GETTIMEOUT: + return put_user(timeout, uarg.i); + + } +} + +static int wdt977_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if(code==SYS_DOWN || code==SYS_HALT) + wdt977_stop(); + return NOTIFY_DONE; +} + +static struct file_operations wdt977_fops= +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdt977_write, + .ioctl = wdt977_ioctl, + .open = wdt977_open, + .release = wdt977_release, +}; + +static struct miscdevice wdt977_miscdev= +{ + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdt977_fops, +}; + +static struct notifier_block wdt977_notifier = { + .notifier_call = wdt977_notify_sys, +}; + +static int __init nwwatchdog_init(void) +{ + int retval; + if (!machine_is_netwinder()) + return -ENODEV; + + /* Check that the timeout value is within it's range ; if not reset to the default */ + if (wdt977_set_timeout(timeout)) { + wdt977_set_timeout(DEFAULT_TIMEOUT); + printk(KERN_INFO PFX "timeout value must be 60<timeout<15300, using %d\n", + DEFAULT_TIMEOUT); + } + + retval = register_reboot_notifier(&wdt977_notifier); + if (retval) { + printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", + retval); + return retval; + } + + retval = misc_register(&wdt977_miscdev); + if (retval) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, retval); + unregister_reboot_notifier(&wdt977_notifier); + return retval; + } + + printk(KERN_INFO PFX "initialized. timeout=%d sec (nowayout=%d, testmode = %i)\n", + timeout, nowayout, testmode); + + return 0; +} + +static void __exit nwwatchdog_exit(void) +{ + misc_deregister(&wdt977_miscdev); + unregister_reboot_notifier(&wdt977_notifier); +} + +module_init(nwwatchdog_init); +module_exit(nwwatchdog_exit); + +MODULE_AUTHOR("Woody Suwalski <woody@netwinder.org>"); +MODULE_DESCRIPTION("W83977AF Watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/char/watchdog/wdt_pci.c b/drivers/char/watchdog/wdt_pci.c new file mode 100644 index 000000000000..7651deda928c --- /dev/null +++ b/drivers/char/watchdog/wdt_pci.c @@ -0,0 +1,763 @@ +/* + * Industrial Computer Source PCI-WDT500/501 driver + * + * (c) Copyright 1996-1997 Alan Cox <alan@redhat.com>, All Rights Reserved. + * http://www.redhat.com + * + * 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> + * + * Release 0.10. + * + * Fixes + * Dave Gregorich : Modularisation and minor bugs + * Alan Cox : Added the watchdog ioctl() stuff + * Alan Cox : Fixed the reboot problem (as noted by + * Matt Crocker). + * Alan Cox : Added wdt= boot option + * Alan Cox : Cleaned up copy/user stuff + * Tim Hockin : Added insmod parameters, comment cleanup + * Parameterized timeout + * JP Nollmann : Added support for PCI wdt501p + * Alan Cox : Split ISA and PCI cards into two drivers + * Jeff Garzik : PCI cleanups + * Tigran Aivazian : Restructured wdtpci_init_one() to handle failures + * Joel Becker : Added WDIOC_GET/SETTIMEOUT + * Zwane Mwaikambo : Magic char closing, locking changes, cleanups + * Matt Domsch : nowayout module option + */ + +#include <linux/config.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/types.h> +#include <linux/miscdevice.h> +#include <linux/watchdog.h> +#include <linux/ioport.h> +#include <linux/notifier.h> +#include <linux/reboot.h> +#include <linux/init.h> +#include <linux/fs.h> +#include <linux/pci.h> + +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/system.h> + +#define WDT_IS_PCI +#include "wd501p.h" + +#define PFX "wdt_pci: " + +/* + * Until Access I/O gets their application for a PCI vendor ID approved, + * I don't think that it's appropriate to move these constants into the + * regular pci_ids.h file. -- JPN 2000/01/18 + */ + +#ifndef PCI_VENDOR_ID_ACCESSIO +#define PCI_VENDOR_ID_ACCESSIO 0x494f +#endif +#ifndef PCI_DEVICE_ID_WDG_CSM +#define PCI_DEVICE_ID_WDG_CSM 0x22c0 +#endif + +/* We can only use 1 card due to the /dev/watchdog restriction */ +static int dev_count; + +static struct semaphore open_sem; +static spinlock_t wdtpci_lock; +static char expect_close; + +static int io; +static int irq; + +/* Default timeout */ +#define WD_TIMO 60 /* Default heartbeat = 60 seconds */ + +static int heartbeat = WD_TIMO; +static int wd_heartbeat; +module_param(heartbeat, int, 0); +MODULE_PARM_DESC(heartbeat, "Watchdog heartbeat in seconds. (0<heartbeat<65536, default=" __MODULE_STRING(WD_TIMO) ")"); + +#ifdef CONFIG_WATCHDOG_NOWAYOUT +static int nowayout = 1; +#else +static int nowayout = 0; +#endif + +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=CONFIG_WATCHDOG_NOWAYOUT)"); + +#ifdef CONFIG_WDT_501_PCI +/* Support for the Fan Tachometer on the PCI-WDT501 */ +static int tachometer; + +module_param(tachometer, int, 0); +MODULE_PARM_DESC(tachometer, "PCI-WDT501 Fan Tachometer support (0=disable, default=0)"); +#endif /* CONFIG_WDT_501_PCI */ + +/* + * Programming support + */ + +static void wdtpci_ctr_mode(int ctr, int mode) +{ + ctr<<=6; + ctr|=0x30; + ctr|=(mode<<1); + outb_p(ctr, WDT_CR); +} + +static void wdtpci_ctr_load(int ctr, int val) +{ + outb_p(val&0xFF, WDT_COUNT0+ctr); + outb_p(val>>8, WDT_COUNT0+ctr); +} + +/** + * wdtpci_start: + * + * Start the watchdog driver. + */ + +static int wdtpci_start(void) +{ + unsigned long flags; + + spin_lock_irqsave(&wdtpci_lock, flags); + + /* + * "pet" the watchdog, as Access says. + * This resets the clock outputs. + */ + inb_p(WDT_DC); /* Disable watchdog */ + wdtpci_ctr_mode(2,0); /* Program CTR2 for Mode 0: Pulse on Terminal Count */ + outb_p(0, WDT_DC); /* Enable watchdog */ + + inb_p(WDT_DC); /* Disable watchdog */ + outb_p(0, WDT_CLOCK); /* 2.0833MHz clock */ + inb_p(WDT_BUZZER); /* disable */ + inb_p(WDT_OPTONOTRST); /* disable */ + inb_p(WDT_OPTORST); /* disable */ + inb_p(WDT_PROGOUT); /* disable */ + wdtpci_ctr_mode(0,3); /* Program CTR0 for Mode 3: Square Wave Generator */ + wdtpci_ctr_mode(1,2); /* Program CTR1 for Mode 2: Rate Generator */ + wdtpci_ctr_mode(2,1); /* Program CTR2 for Mode 1: Retriggerable One-Shot */ + wdtpci_ctr_load(0,20833); /* count at 100Hz */ + wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */ + /* DO NOT LOAD CTR2 on PCI card! -- JPN */ + outb_p(0, WDT_DC); /* Enable watchdog */ + + spin_unlock_irqrestore(&wdtpci_lock, flags); + return 0; +} + +/** + * wdtpci_stop: + * + * Stop the watchdog driver. + */ + +static int wdtpci_stop (void) +{ + unsigned long flags; + + /* Turn the card off */ + spin_lock_irqsave(&wdtpci_lock, flags); + inb_p(WDT_DC); /* Disable watchdog */ + wdtpci_ctr_load(2,0); /* 0 length reset pulses now */ + spin_unlock_irqrestore(&wdtpci_lock, flags); + return 0; +} + +/** + * wdtpci_ping: + * + * Reload counter one with the watchdog heartbeat. We don't bother reloading + * the cascade counter. + */ + +static int wdtpci_ping(void) +{ + unsigned long flags; + + /* Write a watchdog value */ + spin_lock_irqsave(&wdtpci_lock, flags); + inb_p(WDT_DC); /* Disable watchdog */ + wdtpci_ctr_mode(1,2); /* Re-Program CTR1 for Mode 2: Rate Generator */ + wdtpci_ctr_load(1,wd_heartbeat);/* Heartbeat */ + outb_p(0, WDT_DC); /* Enable watchdog */ + spin_unlock_irqrestore(&wdtpci_lock, flags); + return 0; +} + +/** + * wdtpci_set_heartbeat: + * @t: the new heartbeat value that needs to be set. + * + * Set a new heartbeat value for the watchdog device. If the heartbeat value is + * incorrect we keep the old value and return -EINVAL. If successfull we + * return 0. + */ +static int wdtpci_set_heartbeat(int t) +{ + /* Arbitrary, can't find the card's limits */ + if ((t < 1) || (t > 65535)) + return -EINVAL; + + heartbeat = t; + wd_heartbeat = t * 100; + return 0; +} + +/** + * wdtpci_get_status: + * @status: the new status. + * + * Extract the status information from a WDT watchdog device. There are + * several board variants so we have to know which bits are valid. Some + * bits default to one and some to zero in order to be maximally painful. + * + * we then map the bits onto the status ioctl flags. + */ + +static int wdtpci_get_status(int *status) +{ + unsigned char new_status=inb_p(WDT_SR); + + *status=0; + if (new_status & WDC_SR_ISOI0) + *status |= WDIOF_EXTERN1; + if (new_status & WDC_SR_ISII1) + *status |= WDIOF_EXTERN2; +#ifdef CONFIG_WDT_501_PCI + if (!(new_status & WDC_SR_TGOOD)) + *status |= WDIOF_OVERHEAT; + if (!(new_status & WDC_SR_PSUOVER)) + *status |= WDIOF_POWEROVER; + if (!(new_status & WDC_SR_PSUUNDR)) + *status |= WDIOF_POWERUNDER; + if (tachometer) { + if (!(new_status & WDC_SR_FANGOOD)) + *status |= WDIOF_FANFAULT; + } +#endif /* CONFIG_WDT_501_PCI */ + return 0; +} + +#ifdef CONFIG_WDT_501_PCI +/** + * wdtpci_get_temperature: + * + * Reports the temperature in degrees Fahrenheit. The API is in + * farenheit. It was designed by an imperial measurement luddite. + */ + +static int wdtpci_get_temperature(int *temperature) +{ + unsigned short c=inb_p(WDT_RT); + + *temperature = (c * 11 / 15) + 7; + return 0; +} +#endif /* CONFIG_WDT_501_PCI */ + +/** + * wdtpci_interrupt: + * @irq: Interrupt number + * @dev_id: Unused as we don't allow multiple devices. + * @regs: Unused. + * + * Handle an interrupt from the board. These are raised when the status + * map changes in what the board considers an interesting way. That means + * a failure condition occurring. + */ + +static irqreturn_t wdtpci_interrupt(int irq, void *dev_id, struct pt_regs *regs) +{ + /* + * Read the status register see what is up and + * then printk it. + */ + unsigned char status=inb_p(WDT_SR); + + printk(KERN_CRIT PFX "status %d\n", status); + +#ifdef CONFIG_WDT_501_PCI + if (!(status & WDC_SR_TGOOD)) + printk(KERN_CRIT PFX "Overheat alarm.(%d)\n",inb_p(WDT_RT)); + if (!(status & WDC_SR_PSUOVER)) + printk(KERN_CRIT PFX "PSU over voltage.\n"); + if (!(status & WDC_SR_PSUUNDR)) + printk(KERN_CRIT PFX "PSU under voltage.\n"); + if (tachometer) { + if (!(status & WDC_SR_FANGOOD)) + printk(KERN_CRIT PFX "Possible fan fault.\n"); + } +#endif /* CONFIG_WDT_501_PCI */ + if (!(status&WDC_SR_WCCR)) +#ifdef SOFTWARE_REBOOT +#ifdef ONLY_TESTING + printk(KERN_CRIT PFX "Would Reboot.\n"); +#else + printk(KERN_CRIT PFX "Initiating system reboot.\n"); + machine_restart(NULL); +#endif +#else + printk(KERN_CRIT PFX "Reset in 5ms.\n"); +#endif + return IRQ_HANDLED; +} + + +/** + * wdtpci_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 wdtpci_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) +{ + if (count) { + if (!nowayout) { + size_t i; + + expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if(get_user(c, buf+i)) + return -EFAULT; + if (c == 'V') + expect_close = 42; + } + } + wdtpci_ping(); + } + + return count; +} + +/** + * wdtpci_ioctl: + * @inode: inode of the device + * @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 current status. + */ + +static int wdtpci_ioctl(struct inode *inode, struct file *file, unsigned int cmd, + unsigned long arg) +{ + int new_heartbeat; + int status; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT| + WDIOF_MAGICCLOSE| + WDIOF_KEEPALIVEPING, + .firmware_version = 1, + .identity = "PCI-WDT500/501", + }; + + /* Add options according to the card we have */ + ident.options |= (WDIOF_EXTERN1|WDIOF_EXTERN2); +#ifdef CONFIG_WDT_501_PCI + ident.options |= (WDIOF_OVERHEAT|WDIOF_POWERUNDER|WDIOF_POWEROVER); + if (tachometer) + ident.options |= WDIOF_FANFAULT; +#endif /* CONFIG_WDT_501_PCI */ + + switch(cmd) + { + default: + return -ENOIOCTLCMD; + case WDIOC_GETSUPPORT: + return copy_to_user(argp, &ident, sizeof(ident))?-EFAULT:0; + + case WDIOC_GETSTATUS: + wdtpci_get_status(&status); + return put_user(status, p); + case WDIOC_GETBOOTSTATUS: + return put_user(0, p); + case WDIOC_KEEPALIVE: + wdtpci_ping(); + return 0; + case WDIOC_SETTIMEOUT: + if (get_user(new_heartbeat, p)) + return -EFAULT; + + if (wdtpci_set_heartbeat(new_heartbeat)) + return -EINVAL; + + wdtpci_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + return put_user(heartbeat, p); + } +} + +/** + * wdtpci_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 load the counters. Counter zero is a 100Hz + * cascade, into counter 1 which downcounts to reboot. When the counter + * triggers counter 2 downcounts the length of the reset pulse which + * set set to be as long as possible. + */ + +static int wdtpci_open(struct inode *inode, struct file *file) +{ + if (down_trylock(&open_sem)) + return -EBUSY; + + if (nowayout) { + __module_get(THIS_MODULE); + } + /* + * Activate + */ + wdtpci_start(); + return nonseekable_open(inode, file); +} + +/** + * wdtpci_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 wdtpci_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + wdtpci_stop(); + } else { + printk(KERN_CRIT PFX "Unexpected close, not stopping timer!"); + wdtpci_ping(); + } + expect_close = 0; + up(&open_sem); + return 0; +} + +#ifdef CONFIG_WDT_501_PCI +/** + * wdtpci_temp_read: + * @file: file handle to the watchdog board + * @buf: buffer to write 1 byte into + * @count: length of buffer + * @ptr: offset (no seek allowed) + * + * Read reports the temperature in degrees Fahrenheit. The API is in + * fahrenheit. It was designed by an imperial measurement luddite. + */ + +static ssize_t wdtpci_temp_read(struct file *file, char __user *buf, size_t count, loff_t *ptr) +{ + int temperature; + + if (wdtpci_get_temperature(&temperature)) + return -EFAULT; + + if (copy_to_user (buf, &temperature, 1)) + return -EFAULT; + + return 1; +} + +/** + * wdtpci_temp_open: + * @inode: inode of device + * @file: file handle to device + * + * The temperature device has been opened. + */ + +static int wdtpci_temp_open(struct inode *inode, struct file *file) +{ + return nonseekable_open(inode, file); +} + +/** + * wdtpci_temp_release: + * @inode: inode to board + * @file: file handle to board + * + * The temperature device has been closed. + */ + +static int wdtpci_temp_release(struct inode *inode, struct file *file) +{ + return 0; +} +#endif /* CONFIG_WDT_501_PCI */ + +/** + * notify_sys: + * @this: our notifier block + * @code: the event being reported + * @unused: unused + * + * Our notifier is called on system shutdowns. We want to turn the card + * off at reboot otherwise the machine will reboot again during memory + * test or worse yet during the following fsck. This would suck, in fact + * trust me - if it happens it does suck. + */ + +static int wdtpci_notify_sys(struct notifier_block *this, unsigned long code, + void *unused) +{ + if (code==SYS_DOWN || code==SYS_HALT) { + /* Turn the card off */ + wdtpci_stop(); + } + return NOTIFY_DONE; +} + +/* + * Kernel Interfaces + */ + + +static struct file_operations wdtpci_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = wdtpci_write, + .ioctl = wdtpci_ioctl, + .open = wdtpci_open, + .release = wdtpci_release, +}; + +static struct miscdevice wdtpci_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &wdtpci_fops, +}; + +#ifdef CONFIG_WDT_501_PCI +static struct file_operations wdtpci_temp_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = wdtpci_temp_read, + .open = wdtpci_temp_open, + .release = wdtpci_temp_release, +}; + +static struct miscdevice temp_miscdev = { + .minor = TEMP_MINOR, + .name = "temperature", + .fops = &wdtpci_temp_fops, +}; +#endif /* CONFIG_WDT_501_PCI */ + +/* + * The WDT card needs to learn about soft shutdowns in order to + * turn the timebomb registers off. + */ + +static struct notifier_block wdtpci_notifier = { + .notifier_call = wdtpci_notify_sys, +}; + + +static int __devinit wdtpci_init_one (struct pci_dev *dev, + const struct pci_device_id *ent) +{ + int ret = -EIO; + + dev_count++; + if (dev_count > 1) { + printk (KERN_ERR PFX "this driver only supports 1 device\n"); + return -ENODEV; + } + + if (pci_enable_device (dev)) { + printk (KERN_ERR PFX "Not possible to enable PCI Device\n"); + return -ENODEV; + } + + if (pci_resource_start (dev, 2) == 0x0000) { + printk (KERN_ERR PFX "No I/O-Address for card detected\n"); + ret = -ENODEV; + goto out_pci; + } + + sema_init(&open_sem, 1); + spin_lock_init(&wdtpci_lock); + + irq = dev->irq; + io = pci_resource_start (dev, 2); + + if (request_region (io, 16, "wdt_pci") == NULL) { + printk (KERN_ERR PFX "I/O address 0x%04x already in use\n", io); + goto out_pci; + } + + if (request_irq (irq, wdtpci_interrupt, SA_INTERRUPT | SA_SHIRQ, + "wdt_pci", &wdtpci_miscdev)) { + printk (KERN_ERR PFX "IRQ %d is not free\n", irq); + goto out_reg; + } + + printk ("PCI-WDT500/501 (PCI-WDG-CSM) driver 0.10 at 0x%04x (Interrupt %d)\n", + io, irq); + + /* Check that the heartbeat value is within it's range ; if not reset to the default */ + if (wdtpci_set_heartbeat(heartbeat)) { + wdtpci_set_heartbeat(WD_TIMO); + printk(KERN_INFO PFX "heartbeat value must be 0<heartbeat<65536, using %d\n", + WD_TIMO); + } + + ret = register_reboot_notifier (&wdtpci_notifier); + if (ret) { + printk (KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret); + goto out_irq; + } + +#ifdef CONFIG_WDT_501_PCI + ret = misc_register (&temp_miscdev); + if (ret) { + printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + TEMP_MINOR, ret); + goto out_rbt; + } +#endif /* CONFIG_WDT_501_PCI */ + + ret = misc_register (&wdtpci_miscdev); + if (ret) { + printk (KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + WATCHDOG_MINOR, ret); + goto out_misc; + } + + printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", + heartbeat, nowayout); +#ifdef CONFIG_WDT_501_PCI + printk(KERN_INFO "wdt: Fan Tachometer is %s\n", (tachometer ? "Enabled" : "Disabled")); +#endif /* CONFIG_WDT_501_PCI */ + + ret = 0; +out: + return ret; + +out_misc: +#ifdef CONFIG_WDT_501_PCI + misc_deregister(&temp_miscdev); +out_rbt: +#endif /* CONFIG_WDT_501_PCI */ + unregister_reboot_notifier(&wdtpci_notifier); +out_irq: + free_irq(irq, &wdtpci_miscdev); +out_reg: + release_region (io, 16); +out_pci: + pci_disable_device(dev); + goto out; +} + + +static void __devexit wdtpci_remove_one (struct pci_dev *pdev) +{ + /* here we assume only one device will ever have + * been picked up and registered by probe function */ + misc_deregister(&wdtpci_miscdev); +#ifdef CONFIG_WDT_501_PCI + misc_deregister(&temp_miscdev); +#endif /* CONFIG_WDT_501_PCI */ + unregister_reboot_notifier(&wdtpci_notifier); + free_irq(irq, &wdtpci_miscdev); + release_region(io, 16); + pci_disable_device(pdev); + dev_count--; +} + + +static struct pci_device_id wdtpci_pci_tbl[] = { + { + .vendor = PCI_VENDOR_ID_ACCESSIO, + .device = PCI_DEVICE_ID_WDG_CSM, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + { 0, }, /* terminate list */ +}; +MODULE_DEVICE_TABLE(pci, wdtpci_pci_tbl); + + +static struct pci_driver wdtpci_driver = { + .name = "wdt_pci", + .id_table = wdtpci_pci_tbl, + .probe = wdtpci_init_one, + .remove = __devexit_p(wdtpci_remove_one), +}; + + +/** + * wdtpci_cleanup: + * + * Unload the watchdog. You cannot do this with any file handles open. + * If your watchdog is set to continue ticking on close and you unload + * it, well it keeps ticking. We won't get the interrupt but the board + * will not touch PC memory so all is fine. You just have to load a new + * module in xx seconds or reboot. + */ + +static void __exit wdtpci_cleanup(void) +{ + pci_unregister_driver (&wdtpci_driver); +} + + +/** + * wdtpci_init: + * + * Set up the WDT watchdog board. All we have to do is grab the + * resources we require and bitch if anyone beat us to them. + * The open() function will actually kick the board off. + */ + +static int __init wdtpci_init(void) +{ + return pci_register_driver (&wdtpci_driver); +} + + +module_init(wdtpci_init); +module_exit(wdtpci_cleanup); + +MODULE_AUTHOR("JP Nollmann, Alan Cox"); +MODULE_DESCRIPTION("Driver for the ICS PCI-WDT500/501 watchdog cards"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); +MODULE_ALIAS_MISCDEV(TEMP_MINOR); |