GIT bbf61680a07168c1549d1abb2b0aba5ee620ccef git+ssh://master.kernel.org/pub/scm/linux/kernel/git/wim/linux-2.6-watchdog-mm.git commit Author: Mike Frysinger Date: Wed Jan 30 17:38:21 2008 +0800 [WATCHDOG] blackfin Watchdog driver: relocate all strings used in __init functions to __initdata Signed-off-by: Mike Frysinger Signed-off-by: Bryan Wu Signed-off-by: Wim Van Sebroeck commit 2441b76f2dd8be11695202dbda945f3f0f8640c1 Author: Florian Fainelli Date: Mon Jan 7 19:08:49 2008 +0100 [WATCHDOG] Convert mtx1 wdt to be a platform device and use generic GPIO API This patch converts the MTX-1 to be a platform device, use the available generic GPIO API for the MTX-1 board and register the miscdev alias. Signed-off-by: Florian Fainelli Signed-off-by: Wim Van Sebroeck commit 02d102d71920b828426e54bd447de4e91c86ef76 Author: Jan Engelhardt Date: Tue Jan 22 20:48:10 2008 +0100 [WATCHDOG] constify function pointer tables "static struct file_operations" should be "static const struct file_operations". Signed-off-by: Jan Engelhardt Signed-off-by: Wim Van Sebroeck commit 7e7b81a49fcfa45e804b23a9cc7df3bf2ed68534 Author: Andrew Dyer Date: Tue Jan 8 14:40:37 2008 -0600 [WATCHDOG] clarify watchdog operation in documentation It was not clear what the difference is/was between the nowayout feature and the Magic Close feature. Signed-off-by: "Andrew Dyer" Signed-off-by: Wim Van Sebroeck commit 8cb868701552312f2c1c5d576c8708d52ae20892 Author: Wim Van Sebroeck Date: Fri Jan 18 21:47:37 2008 +0000 [WATCHDOG] Revert "Stop looking for device as soon as one is found" This reverts commit 41d26417ddbebcb6e919a273e83166e19080d22a. the !found check in the for loop allready made sure that only one device was found. Signed-Off-By: Pádraig Brady Signed-Off-By: Wim Van Sebroeck commit 5e12c18267b6149eba657efa8564c7a25f9788b4 Author: Alan Cox Date: Wed Jan 9 21:36:01 2008 -0800 [WATDHOG] wdt: fix locking The audit of _p usage shows various drivers assume inb_p is somehow atomic. Of course it isn't and the delay can be split from the I/O cycle causing a timing violation on chips that matter (eg this one) With the proposed use of udelay() for some _p delays this will cease to be a mostly theoretical bug (as the delay stall is unsplittable) and wants fixing. Lots of other drivers need fixing this way too. Signed-off-by: Alan Cox Signed-off-by: Wim Van Sebroeck Signed-off-by: Andrew Morton commit 743b8d130857b8adb2ccd235a7335e7d16131b04 Author: Wim Van Sebroeck Date: Tue Jan 8 19:12:04 2008 +0000 [WATCHDOG] HP ProLiant WatchDog driver - pci_iomap patch The drivers map's io-memory from the pci-device. It's better to use the pci_iomap functions (see lib/iomap.c). Signed-off-by: Thomas Mingarelli Signed-off-by: Wim Van Sebroeck commit 634d32cedb947582690424db1177ee2dc617907e Author: Wim Van Sebroeck Date: Wed Dec 26 20:32:51 2007 +0000 [WATCHDOG] misc_register patch Make sure that we first do a register_reboot_notifier before we do a misc_register. A misc_register opens the interface to userspace and it's best to do this as the last action. Signed-off-by: Wim Van Sebroeck commit 66764c7fe2b5a2d96b918d0716f3494c4857f5ea Author: Thomas Bogendoerfer Date: Sun Dec 2 12:54:42 2007 +0100 [WATCHDOG] use SGI_HAS_INDYDOG for INDYDOG depends Use SGI_HAS_INDYDOG for INDYDOG depends. Signed-off-by: Thomas Bogendoerfer Signed-off-by: Wim Van Sebroeck Cc: Ralf Baechle Signed-off-by: Andrew Morton commit 1541a5b89042231d6a11e9f575e02c7b5eb454fc Author: Andrew Sharp Date: Thu Dec 13 16:16:42 2007 -0800 [WATCHOG] Add support for SB1 hardware watchdog Support watchdog timers built into SiByte MIPS SoCs. Signed-off-by: Andy Sharp Signed-off-by: Wim Van Sebroeck Cc: Ralf Baechle Signed-off-by: Andrew Morton commit 31699b3bf0c3ac3439d5331be08d0a92b0eb1a60 Author: Wim Van Sebroeck Date: Wed Dec 26 18:22:36 2007 +0000 [WATCHDOG] TXx9 watchdog driver - misc-register patch We do the registering of the miscdevice as the last action because this action opens the interface to userspace. Cc: Atsushi Nemoto Cc: Ralf Baechle Signed-off-by: Wim Van Sebroeck Cc: Andrew Morton commit 1d39c58f49f8040172b9b55bb5b7127174068356 Author: Atsushi Nemoto Date: Mon Nov 12 01:32:17 2007 +0900 [WATCHDOG] TXx9 watchdog driver This is a driver for watchdog timer built into TXx9 MIPS SoCs. Signed-off-by: Atsushi Nemoto Cc: Ralf Baechle Signed-off-by: Wim Van Sebroeck Signed-off-by: Andrew Morton commit cdb70683c20c7d2527e8fd6727163d9ee626a23b Author: Wim Van Sebroeck Date: Thu Dec 13 20:43:04 2007 +0000 [WATCHDOG] hpwdt - clean-up patches part 1 First patch to improve the code. These patches are based on Andrew's comments when he reviewed the code. Signed-off-by: Wim Van Sebroeck Signed-off-by: Thomas Mingarelli Signed-off-by: Andrew Morton commit 22569271b4e6b2374111f6be0c86950e47bdeafd Author: Andrew Morton Date: Tue Dec 4 15:57:36 2007 -0800 [WATCHDOG] hpwdt build fix From: Andrew Morton drivers/watchdog/hpwdt.c: In function 'hpwdt_init_one': drivers/watchdog/hpwdt.c:898: error: implicit declaration of function 'register_die_notifier' drivers/watchdog/hpwdt.c:922: error: implicit declaration of function 'unregister_die_notifier' Signed-off-by: Wim Van Sebroeck Signed-off-by: Andrew Morton commit b1ad0c9cab52a6437584929cc755898bd85d6045 Author: Thomas Mingarelli Date: Tue Dec 4 17:41:54 2007 +0000 [WATCHDOG] HP ProLiant WatchDog driver Hp is providing a Hardware WatchDog Timer driver that will only work with the specific HW Timer located in the HP ProLiant iLO 2 ASIC. The iLO 2 HW Timer will generate a Non-maskable Interrupt (NMI) 9 seconds before physically resetting the server, by removing power, so that the event can be logged to the HP Integrated Management Log (IML), a Non-Volatile Random Access Memory (NVRAM). The logging of the event is performed using the HP ProLiant ROM via an Industry Standard access known as a BIOS Service Directory Entry. Signed-off-by: Thomas Mingarelli Signed-off-by: Wim Van Sebroeck commit 94d68213dc0f040ba6bb54e1eb070093cac22081 Author: Gilles Gigan Date: Wed Oct 31 16:31:42 2007 +1000 [WATCHDOG] add Nano 7240 driver Adds support for the built-in watchdog on EPIC Nano 7240 boards from IEI. Tested on Nano-7240RS. Hardware documentation of the platform (including watchdog) can be found on the IEI website: http://www.ieiworld.com Signed-off-by: Gilles Gigan Signed-off-by: Wim Van Sebroeck commit 2f3c13cbd1fa344ec8c1eaced83cbb855a161b20 Author: Corey Minyard Date: Tue Nov 20 12:14:46 2007 -0800 [WATCHDOG] ipmi: add the standard watchdog timeout ioctls Add the standard IOCTLs to the IPMI driver for setting and getting the pretimeout. Tested by Benoit Guillon. Signed off by: Corey Minyard Cc: Benoit Guillon Signed-off-by: Wim Van Sebroeck Signed-off-by: Andrew Morton commit 0d9bcdc1351412a4ab7864a65d3f3bdd953cce02 Author: Jorge Boncompte [DTI2] Date: Mon Nov 19 15:09:21 2007 +0100 [WATCHDOG] IT8212F watchdog driver This patch adds support for the ITE Tech Inc. IT8712F EC-LPC Super I/O chipset found on many Pentium III and AMD motherboards. Developed using code from other watchdog drivers and the datasheet on ITE Tech homepage. Signed-off-by: Jorge Boncompte Signed-off-by: Wim Van Sebroeck commit 07e7d61080217ffc91740d6f3a506c44f3b675d1 Author: Jiri Slaby Date: Sat Nov 10 04:32:45 2007 +0100 [WATCHDOG] Sbus: cpwatchdog, remove SPIN_LOCK_UNLOCKED cpwatchdog, remove SPIN_LOCK_UNLOCKED SPIN_LOCK_UNLOCKED is deprecated, use __SPIN_LOCK_UNLOCKED with an unique name instead Signed-off-by: Jiri Slaby Signed-off-by: Wim Van Sebroeck commit ac34925ab8398e21013341418baa2a08c5b8df48 Author: Jiri Slaby Date: Sat Nov 10 05:58:44 2007 +0100 [WATCHDOG] bfin_wdt, remove SPIN_LOCK_UNLOCKED bfin_wdt, remove SPIN_LOCK_UNLOCKED SPIN_LOCK_UNLOCKED is deprecated, use DEFINE_SPINLOCK instead Signed-off-by: Jiri Slaby Signed-off-by: Mike Frysinger Signed-off-by: Wim Van Sebroeck commit 41d26417ddbebcb6e919a273e83166e19080d22a Author: Samuel Tardieu Date: Sun Nov 4 20:20:23 2007 +0100 [WATCHDOG] Stop looking for device as soon as one is found If no address is given for the W83697HF/HG watchdog IO port, stop looping through possible locations when a watchdog device has been found. Signed-off-by: Samuel Tardieu Signed-off-by: Wim Van Sebroeck commit 6538b10f45452ac016578dc5403fafc44c795970 Author: Wim Van Sebroeck Date: Sun Aug 19 20:08:16 2007 +0000 [WATCHDOG] Convert iTCO_wdt.c to the Uniform Watchdog Device Driver Convert the iTCO_wdt.c driver to the Uniform Watchdog Device Driver framework. Signed-off-by: Wim Van Sebroeck commit 87d2e05b16ad1fa317aef683ab32be4392c9f8b0 Author: Wim Van Sebroeck Date: Sun Aug 19 19:44:24 2007 +0000 [WATCHDOG] Uniform Watchdog Device Driver The Uniform Watchdog Device Driver is a frame-work that contains the common code for all watchdog-driver's. It also introduces a watchdog device structure and the operations that go with it. Signed-off-by: Wim Van Sebroeck commit 061e2f454ad75203924cb70e9e0b9f609ab8505b Author: Hans-Christian Egtvedt Date: Tue Oct 30 14:56:20 2007 +0100 [WATCHDOG] at32ap700x_wdt: add support for boot status and add fix for silicon errata This patch enables the watchdog to read out the reset cause after a boot and provide this to the user. The driver will now also return -EIO if probed when booting from a watchdog reset. This is due to a silicon errata in the AT32AP700x devices. Detailed description and work-arounds can be found in the errata section of the datasheet avilable from http://www.atmel.com/dyn/products/datasheets.asp?family_id=682 Signed-off-by: Hans-Christian Egtvedt Signed-off-by: Wim Van Sebroeck drivers/watchdog/Kconfig | 33 ++- drivers/watchdog/Makefile | 8 + drivers/watchdog/bfin_wdt.c | 7 +- drivers/watchdog/core/Kconfig | 33 ++ drivers/watchdog/core/Makefile | 7 + drivers/watchdog/core/watchdog_core.c | 189 +++++++ drivers/watchdog/core/watchdog_dev.c | 465 ++++++++++++++++ drivers/watchdog/hpwdt.c | 975 +++++++++++++++++++++++++++++++++ drivers/watchdog/iTCO_wdt.c | 247 +++------ drivers/watchdog/mtx-1_wdt.c | 35 +- drivers/watchdog/sb_wdog.c | 353 ++++++++++++ include/linux/watchdog.h | 49 ++ 12 files changed, 2211 insertions(+), 190 deletions(-) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index afcdc69..ba75e5a 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -37,6 +37,8 @@ config WATCHDOG_NOWAYOUT get killed. If you say Y here, the watchdog cannot be stopped once it has been started. +source "drivers/watchdog/core/Kconfig" + # # General Watchdog drivers # @@ -366,10 +368,11 @@ config I6300ESB_WDT config ITCO_WDT tristate "Intel TCO Timer/Watchdog" depends on (X86 || IA64) && PCI + select WATCHDOG_CORE ---help--- Hardware driver for the intel TCO timer based watchdog devices. These drivers are included in the Intel 82801 I/O Controller - Hub family (from ICH0 up to ICH8) and in the Intel 6300ESB + Hub family (from ICH0 up to ICH9) and in the Intel 63xxESB controller hub. The TCO (Total Cost of Ownership) timer is a watchdog timer @@ -402,6 +405,21 @@ config IT8712F_WDT To compile this driver as a module, choose M here: the module will be called it8712f_wdt. +<<<<<<< HEAD:drivers/watchdog/Kconfig +======= +config HP_WATCHDOG + tristate "HP Proliant iLO 2 Hardware Watchdog Timer" + depends on X86 + help + A software monitoring watchdog and NMI sourcing driver. This driver + will detect lockups and provide stack trace. Also, when an NMI + occurs this driver will make the necessary BIOS calls to log + the cause of the NMI. This is a driver that will only load on a + HP ProLiant system with a minimum of iLO2 support. + To compile this driver as a module, choose M here: the + module will be called hpwdt. + +>>>>>>> FETCH_HEAD:drivers/watchdog/Kconfig config SC1200_WDT tristate "National Semiconductor PC87307/PC97307 (ala SC1200) Watchdog" depends on X86 @@ -633,6 +651,19 @@ config WDT_RM9K_GPI To compile this driver as a module, choose M here: the module will be called rm9k_wdt. +config SIBYTE_WDOG + tristate "Sibyte SoC hardware watchdog" + depends on CPU_SB1 + help + Watchdog driver for the built in watchdog hardware in Sibyte + SoC processors. There are apparently two watchdog timers + on such processors; this driver supports only the first one, + because currently Linux only supports exporting one watchdog + to userspace. + + To compile this driver as a loadable module, choose M here. + The module will be called sb_wdog. + config AR7_WDT tristate "TI AR7 Watchdog Timer" depends on AR7 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index ebc2114..e891c4b 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -2,6 +2,8 @@ # Makefile for the WatchDog device drivers. # +obj-$(CONFIG_WATCHDOG) += core/ + # Only one watchdog can succeed. We probe the ISA/PCI/USB based # watchdog-cards first, then the architecture specific watchdog # drivers and then the architecture independant "softdog" driver. @@ -67,6 +69,11 @@ obj-$(CONFIG_WAFER_WDT) += wafer5823wdt.o obj-$(CONFIG_I6300ESB_WDT) += i6300esb.o obj-$(CONFIG_ITCO_WDT) += iTCO_wdt.o iTCO_vendor_support.o obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o +<<<<<<< HEAD:drivers/watchdog/Makefile +======= +CFLAGS_hpwdt.o += -O +obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o +>>>>>>> FETCH_HEAD:drivers/watchdog/Makefile obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o obj-$(CONFIG_SCx200_WDT) += scx200_wdt.o obj-$(CONFIG_PC87413_WDT) += pc87413_wdt.o @@ -92,6 +99,7 @@ obj-$(CONFIG_SBC_EPX_C3_WATCHDOG) += sbc_epx_c3.o obj-$(CONFIG_INDYDOG) += indydog.o obj-$(CONFIG_WDT_MTX1) += mtx-1_wdt.o obj-$(CONFIG_WDT_RM9K_GPI) += rm9k_wdt.o +obj-$(CONFIG_SIBYTE_WDOG) += sb_wdog.o obj-$(CONFIG_AR7_WDT) += ar7_wdt.o obj-$(CONFIG_TXX9_WDT) += txx9wdt.o diff --git a/drivers/watchdog/bfin_wdt.c b/drivers/watchdog/bfin_wdt.c index 472be10..1237113 100644 --- a/drivers/watchdog/bfin_wdt.c +++ b/drivers/watchdog/bfin_wdt.c @@ -29,6 +29,7 @@ #define stamp(fmt, args...) pr_debug("%s:%i: " fmt "\n", __func__, __LINE__, ## args) #define stampit() stamp("here i am") +#define pr_init(fmt, args...) ({ static const __initdata char __fmt[] = fmt; printk(__fmt, ## args); }) #define WATCHDOG_NAME "bfin-wdt" #define PFX WATCHDOG_NAME ": " @@ -445,19 +446,19 @@ static int __init bfin_wdt_init(void) ret = register_reboot_notifier(&bfin_wdt_notifier); if (ret) { - printk(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret); + pr_init(KERN_ERR PFX "cannot register reboot notifier (err=%d)\n", ret); return ret; } ret = misc_register(&bfin_wdt_miscdev); if (ret) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + pr_init(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", WATCHDOG_MINOR, ret); unregister_reboot_notifier(&bfin_wdt_notifier); return ret; } - printk(KERN_INFO PFX "initialized: timeout=%d sec (nowayout=%d)\n", + pr_init(KERN_INFO PFX "initialized: timeout=%d sec (nowayout=%d)\n", timeout, nowayout); return 0; diff --git a/drivers/watchdog/core/Kconfig b/drivers/watchdog/core/Kconfig new file mode 100644 index 0000000..ce8c391 --- /dev/null +++ b/drivers/watchdog/core/Kconfig @@ -0,0 +1,33 @@ +# +# Watchdog device driver core +# + +if WATCHDOG + +config WATCHDOG_CORE + tristate "Uniform Watchdog Device Driver" + depends on EXPERIMENTAL + default m + ---help--- + Say Y here if you want to use the new uniform watchdog device + driver. This driver provides a framework for all watchdog + device drivers and gives them the /dev/watchdog interface (and + later also the sysfs interface). + + At this moment only the iTCO_wdt driver uses this new framework. + + To compile this driver as a module, choose M here: the module will + be called watchdog_core. + +config WATCHDOG_DEBUG_CORE + bool "Uniform Watchdog Device Driver debugging output" + depends on WATCHDOG_CORE + default n + ---help--- + Say Y here if you want the Uniform Watchdog Device Driver to + produce debugging information. Select this if you are having a + problem with the uniform watchdog device driver and want to see + more of what is really happening. + +endif # WATCHDOG + diff --git a/drivers/watchdog/core/Makefile b/drivers/watchdog/core/Makefile new file mode 100644 index 0000000..fc04384 --- /dev/null +++ b/drivers/watchdog/core/Makefile @@ -0,0 +1,7 @@ +# +# Makefile for the Watchdog Device Drivers generic core. +# + +# The Generic Watchdog Driver +obj-$(CONFIG_WATCHDOG_CORE) += watchdog_core.o watchdog_dev.o + diff --git a/drivers/watchdog/core/watchdog_core.c b/drivers/watchdog/core/watchdog_core.c new file mode 100644 index 0000000..fb05f8f --- /dev/null +++ b/drivers/watchdog/core/watchdog_core.c @@ -0,0 +1,189 @@ +/* + * watchdog.c + * + * (c) Copyright 2007 Wim Van Sebroeck . + * + * This code is generic code that can be shared by all the + * watchdog drivers. + * + * Based on source code of the following authors: + * Alan Cox , + * Matt Domsch , + * Rob Radez , + * Rusty Lynch + * Satyam Sharma + * Randy Dunlap + * + * 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. + */ + +#include /* For module related things/EXPORT_SYMBOL/... */ +#include /* For standard types */ +#include /* For the -ENODEV/... values */ +#include /* For printk/panic/... */ +#include /* For memory allocations ... */ +#include /* For watchdog specific items */ +#include /* For __init/__exit/... */ + +/* + * Version information + */ +#define DRV_VERSION "0.01" +#define DRV_NAME "watchdog_core" +#define PFX DRV_NAME ": " + +/* + * External functions/procedures + */ +extern int watchdog_dev_register(struct watchdog_device *, struct device *); +extern int watchdog_dev_unregister(struct watchdog_device *); + +/** + * alloc_watchdogdev - allocates a watchdog device + * + * Creates a new watchdog device structure. + * Returns the new structure, or NULL if an error occured. + */ +struct watchdog_device *alloc_watchdogdev(void) +{ + int alloc_size = sizeof(struct watchdog_device); + void *p; + struct watchdog_device *dev; + + /* allocate memory for our device and initialize it */ + p = kzalloc(alloc_size, GFP_KERNEL); + if (!p) { + printk(KERN_ERR PFX "Unable to allocate watchdog device\n"); + return NULL; + } + dev = (struct watchdog_device *) p; + + return dev; +} +EXPORT_SYMBOL(alloc_watchdogdev); + +/** + * free_watchdogdev - free watchdog device + * @dev: watchdog device + * + * This function does the last stage of destroying an allocated + * watchdog device. + */ +int free_watchdogdev(struct watchdog_device *dev) +{ + if (!((dev->watchdog_state == WATCHDOG_UNINITIALIZED) || + (dev->watchdog_state == WATCHDOG_UNREGISTERED))) { + printk(KERN_ERR PFX "Unable to destroy a watchdog device that is still in use\n"); + return -1; + } + + kfree(dev); + return 0; +} +EXPORT_SYMBOL(free_watchdogdev); + +/** + * register_watchdogdevice - register a watchdog device + * @dev: watchdog device + * @parent: parent device for the watchdog class device + * + * This function registers a watchdog device in the kernel so + * that it can be accessed from userspace. + */ +int register_watchdogdevice(struct watchdog_device *dev, struct device *parent) +{ + int ret; + + if (dev == NULL || + dev->watchdog_ops == NULL) + return -ENODATA; + + if (dev->watchdog_ops->start == NULL || + dev->watchdog_ops->stop == NULL || + dev->watchdog_ops->keepalive == NULL) + return -ENODATA; + + if (!((dev->watchdog_state == WATCHDOG_UNINITIALIZED) || + (dev->watchdog_state == WATCHDOG_UNREGISTERED))) { + printk(KERN_ERR PFX "Unable to register a watchdog device that is allready in use\n"); + return -1; + } + + dev->options |= WDIOF_MAGICCLOSE; + if (dev->watchdog_ops->set_heartbeat) { + dev->options |= WDIOF_SETTIMEOUT; + } else { + dev->options &= ~WDIOF_SETTIMEOUT; + } + + ret = watchdog_dev_register(dev, parent); + if (ret) { + printk(KERN_ERR PFX "error registering /dev/watchdog (err=%d)", + ret); + return ret; + } + + dev->watchdog_state = WATCHDOG_REGISTERED; + return 0; +} +EXPORT_SYMBOL(register_watchdogdevice); + +/** + * unregister_watchdogdevice - unregister a watchdog device + * @dev: watchdog device + * + * This function unregisters a watchdog device from the kernel. + */ +int unregister_watchdogdevice(struct watchdog_device *dev) +{ + int ret; + + if (dev == NULL) + return -ENODATA; + + if ((dev->watchdog_state == WATCHDOG_UNINITIALIZED) || + (dev->watchdog_state == WATCHDOG_UNREGISTERED)) { + printk(KERN_ERR PFX "Unable to unregister a watchdog device that has not been registered\n"); + return -ENODEV; + } + + ret = watchdog_dev_unregister(dev); + if (ret) { + printk(KERN_ERR PFX "error unregistering /dev/watchdog (err=%d)", + ret); + return ret; + } + + dev->watchdog_state = WATCHDOG_UNREGISTERED; + return 0; +} +EXPORT_SYMBOL(unregister_watchdogdevice); + +static int __init watchdog_init(void) +{ + printk(KERN_INFO "Uniform watchdog device driver v%s loaded\n", + DRV_VERSION); + return 0; +} + +static void __exit watchdog_exit(void) +{ + printk(KERN_INFO "Uniform watchdog device driver unloaded\n"); +} + +module_init(watchdog_init); +module_exit(watchdog_exit); + +MODULE_AUTHOR("Wim Van Sebroeck "); +MODULE_DESCRIPTION("Uniform Watchdog Device Driver"); +MODULE_VERSION(DRV_VERSION); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("watchdog"); + diff --git a/drivers/watchdog/core/watchdog_dev.c b/drivers/watchdog/core/watchdog_dev.c new file mode 100644 index 0000000..a0614d6 --- /dev/null +++ b/drivers/watchdog/core/watchdog_dev.c @@ -0,0 +1,465 @@ +/* + * watchdog_dev.c + * + * (c) Copyright 2007 Wim Van Sebroeck . + * + * This source code is part of the generic code that can be used + * by all the watchdog drivers. + * + * This part of the generic code takes care of the following + * misc device: /dev/watchdog. + * + * Based on source code of the following authors: + * Alan Cox , + * Matt Domsch , + * Rob Radez , + * Rusty Lynch + * Satyam Sharma + * Randy Dunlap + * + * 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. + */ + +#include /* For module related things/EXPORT_SYMBOL/... */ +#include /* For standard types (like size_t) */ +#include /* For the -ENODEV/... values */ +#include /* For printk/panic/... */ +#include /* For file operations */ +#include /* For watchdog specific items */ +#include /* For handling misc devices */ +#include /* For mutex locking */ +#include /* For __init/__exit/... */ +#include /* For copy_to_user/put_user/... */ + +#ifdef CONFIG_WATCHDOG_DEBUG_CORE +#define trace(format, args...) \ + printk(KERN_INFO "%s(" format ")\n", __FUNCTION__ , ## args) +#define dbg(format, arg...) \ + printk(KERN_DEBUG "%s: " format "\n", __FUNCTION__, ## arg) +#else +#define trace(format, arg...) do { } while (0) +#define dbg(format, arg...) do { } while (0) +#endif + +/* + * Version information + */ +#define DRV_VERSION "0.01" +#define DRV_NAME "watchdog_dev" +#define PFX DRV_NAME ": " + +/* + * Locally used variables + */ + +static struct watchdog_device *watchdogdev; /* the watchdog device behind /dev/watchdog */ +static unsigned long watchdog_dev_open; /* wether or not /dev/watchdog has been opened */ +static char received_magic_char; /* wether or not we received the magic char */ +static DEFINE_MUTEX(watchdog_register_mtx); /* prevent races between register & unregister */ + +/* + * /dev/watchdog operations + */ + +/* + * watchdog_write: writes to the watchdog. + * @file: file from VFS + * @data: user address of data + * @len: length of data + * @ppos: pointer to the file offset + * + * A write to a watchdog device is defined as a keepalive signal. + * Writing the magic 'V' sequence allows the next close to turn + * off the watchdog (if 'nowayout' is not set). + */ + +static ssize_t watchdog_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + trace("%p, %p, %zu, %p", file, data, len, ppos); + + if (!watchdogdev || + !watchdogdev->watchdog_ops || + !watchdogdev->watchdog_ops->keepalive) + return -ENODEV; + + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!watchdogdev->nowayout) { + size_t i; + + /* note: just in case someone wrote the magic character + * five months ago... */ + received_magic_char = 0; + + /* scan to see wether 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') { + received_magic_char = 42; + dbg("received the magic character\n"); + } + } + } + + /* someone wrote to us, so we sent the watchdog a keepalive signal if + * the watchdog is active */ + if (watchdogdev->watchdog_state == WATCHDOG_STARTED) + watchdogdev->watchdog_ops->keepalive(watchdogdev); + } + return len; +} + +/* + * watchdog_ioctl: handle the different ioctl's for the watchdog device. + * @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 watchdog_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int status; + int err; + int new_options; + int new_heartbeat; + int time_left; + void __user *argp = (void __user *)arg; + int __user *p = argp; + static struct watchdog_info ident = { + .options = 0, + .firmware_version = 0, + .identity = "Watchdog Device", + }; + + trace("%p, %p, %u, %li", inode, file, cmd, arg); + + if (!watchdogdev || !watchdogdev->watchdog_ops) + return -ENODEV; + + switch (cmd) { + case WDIOC_GETSUPPORT: + { + ident.options = watchdogdev->options; + ident.firmware_version = watchdogdev->firmware; + + strncpy(ident.identity, watchdogdev->name, 31); + ident.identity[32] = 0; + + return copy_to_user(argp, &ident, + sizeof(ident)) ? -EFAULT : 0; + } + + case WDIOC_GETSTATUS: + { + status = 0; + + if (watchdogdev->watchdog_ops->get_status && + watchdogdev->watchdog_ops->get_status(watchdogdev, &status)) + return -EFAULT; + + return put_user(status, p); + } + + case WDIOC_GETBOOTSTATUS: + return put_user(watchdogdev->bootstatus, p); + + case WDIOC_KEEPALIVE: + { + if (!watchdogdev->watchdog_ops->keepalive) + return -EFAULT; + + /* We only sent a keepalive when the watchdog is active */ + if (watchdogdev->watchdog_state == WATCHDOG_STARTED) + watchdogdev->watchdog_ops->keepalive(watchdogdev); + + return 0; + } + + case WDIOC_SETOPTIONS: + { + if (get_user(new_options, p)) + return -EFAULT; + + if (!watchdogdev->watchdog_ops->start || + !watchdogdev->watchdog_ops->stop) + return -EFAULT; + + if (new_options & WDIOS_DISABLECARD) { + /* only try to stop the watchdog if it's allready running */ + if (watchdogdev->watchdog_state == WATCHDOG_STARTED) { + err = watchdogdev->watchdog_ops->stop(watchdogdev); + if (err == 0) { + watchdogdev->watchdog_state = WATCHDOG_STOPPED; + } else { + printk(KERN_CRIT PFX "WDIOS_DISABLECARD not successfull! (err=%d)", + err); + return -EFAULT; + } + } + } + + if (new_options & WDIOS_ENABLECARD) { + /* if the watchdog is not allready running, try to start it */ + if (watchdogdev->watchdog_state != WATCHDOG_STARTED) { + err = watchdogdev->watchdog_ops->start(watchdogdev); + if (err == 0) { + watchdogdev->watchdog_state = WATCHDOG_STARTED; + } else { + printk(KERN_CRIT PFX "WDIOS_ENABLECARD not successfull! (err=%d)", + err); + return -EFAULT; + } + } + } + + return 0; + } + + case WDIOC_SETTIMEOUT: + { + if (!watchdogdev->watchdog_ops->set_heartbeat) + return -ENOTTY; + + if (get_user(new_heartbeat, p)) + return -EFAULT; + + if (watchdogdev->watchdog_ops->set_heartbeat(watchdogdev, new_heartbeat)) + return -EFAULT; + + /* If the watchdog is active then we sent a keepalive to make sure + * that the watchdog keep's running (and if possible takes the new + * heartbeat) */ + if (watchdogdev->watchdog_ops->keepalive && + (watchdogdev->watchdog_state == WATCHDOG_STARTED)) + watchdogdev->watchdog_ops->keepalive(watchdogdev); + /* Fall */ + } + + case WDIOC_GETTIMEOUT: + return put_user(watchdogdev->heartbeat, p); + + case WDIOC_GETTIMELEFT: + { + if (!watchdogdev->watchdog_ops->get_timeleft) + return -ENOTTY; + + if (watchdogdev->watchdog_ops->get_timeleft(watchdogdev, &time_left)) + return -EFAULT; + + return put_user(time_left, p); + } + + default: + return -ENOTTY; + } +} + +/* + * watchdog_open: open the /dev/watchdog device. + * @inode: inode of device + * @file: file handle to device + * + * When the /dev/watchdog device get's opened, we start the watchdog + * and feed it with his first keepalive signal. Watch out: the + * /dev/watchdog device is single open, so make sure it can only be + * opened once. + */ + +static int watchdog_open(struct inode *inode, struct file *file) +{ + trace("%p, %p", inode, file); + + /* only open if we have a valid watchdog device */ + if (!watchdogdev || + !watchdogdev->watchdog_ops || + !watchdogdev->watchdog_ops->start || + !watchdogdev->watchdog_ops->stop || + !watchdogdev->watchdog_ops->keepalive) + return -EBUSY; + + /* the watchdog is single open! */ + if (test_and_set_bit(0, &watchdog_dev_open)) + return -EBUSY; + + /* if the watchdog is not allready running, try to start it */ + if (watchdogdev->watchdog_state != WATCHDOG_STARTED) { + if (watchdogdev->watchdog_ops->start(watchdogdev) == 0) + watchdogdev->watchdog_state = WATCHDOG_STARTED; + } + + /* if the watchdog started, then feed the watchdog it's first keepalive signal */ + if (watchdogdev->watchdog_state == WATCHDOG_STARTED) + watchdogdev->watchdog_ops->keepalive(watchdogdev); + + return nonseekable_open(inode, file); +} + +/* + * watchdog_release: release the /dev/watchdog device. + * @inode: inode of device + * @file: file handle to device + * + * This is the code for when /dev/watchdog get's closed. We will only + * stop the watchdog when we have received the magic char, else the + * watchdog will keep running. + */ + +static int watchdog_release(struct inode *inode, struct file *file) +{ + int err; + + trace("%p, %p", inode, file); + dbg("received_magic_char=%d", received_magic_char); + + if (watchdogdev && (watchdogdev->watchdog_state == WATCHDOG_STARTED)) { + /* Only stop a watchdog if it actually started */ + if (received_magic_char == 42) { + /* we received the magic char -> we can stop the watchdog */ + if (watchdogdev->watchdog_ops && watchdogdev->watchdog_ops->stop) { + err = watchdogdev->watchdog_ops->stop(watchdogdev); + if (err == 0) { + watchdogdev->watchdog_state = WATCHDOG_STOPPED; + } else { + printk(KERN_CRIT PFX "Watchdog didn't stop successfull! (err=%d)", + err); + } + } else { + printk(KERN_CRIT PFX "Unable to stop watchdog!"); + } + } else { + /* If we didn't receive the magic char, then we will close + * /dev/watchdog but the watchdog keeps running... */ + printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!"); + if (watchdogdev->watchdog_ops && watchdogdev->watchdog_ops->keepalive) { + watchdogdev->watchdog_ops->keepalive(watchdogdev); + } + } + } + + received_magic_char = 0; + + /* make sure that /dev/watchdog can be re-opened */ + clear_bit(0, &watchdog_dev_open); + + return 0; +} + +/* + * /dev/watchdog kernel interfaces + */ + +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, +}; + +/* + * /dev/watchdog register and unregister functions + */ + +/* + * watchdog_dev_register: + * + * Register a watchdog device as /dev/watchdog. /dev/watchdog + * is actually a miscdevice and thus we set it up like that. + */ + +int watchdog_dev_register(struct watchdog_device *wdd, struct device *parent) +{ + int err = -EBUSY; + + trace("%p %p", wdd, parent); + + mutex_lock(&watchdog_register_mtx); + + if (watchdogdev) { + printk(KERN_ERR PFX "another watchdog device is allready registered as /dev/watchdog\n"); + goto out; + } + + watchdog_miscdev.parent = parent; + + dbg("Register a new /dev/watchdog device\n"); + err = misc_register(&watchdog_miscdev); + if (err != 0) { + printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", + watchdog_miscdev.minor, err); + goto out; + } + + watchdogdev = wdd; + +out: + mutex_unlock(&watchdog_register_mtx); + return err; +} +EXPORT_SYMBOL(watchdog_dev_register); + +/* + * watchdog_dev_unregister: + * + * Deregister the /dev/watchdog device. + */ + +int watchdog_dev_unregister(struct watchdog_device *wdd) +{ + trace("%p", wdd); + + mutex_lock(&watchdog_register_mtx); + + if (!watchdogdev) { + printk(KERN_ERR PFX "there is no watchdog registered\n"); + mutex_unlock(&watchdog_register_mtx); + return -1; + } + + if (!wdd) { + printk(KERN_ERR PFX "cannot unregister non-existing watchdog-driver\n"); + mutex_unlock(&watchdog_register_mtx); + return -2; + } + + if (watchdogdev != wdd) { + printk(KERN_ERR PFX "another watchdog device is running\n"); + mutex_unlock(&watchdog_register_mtx); + return -3; + } + + dbg("Unregister /dev/watchdog device\n"); + misc_deregister(&watchdog_miscdev); + watchdogdev = NULL; + mutex_unlock(&watchdog_register_mtx); + return 0; +} +EXPORT_SYMBOL(watchdog_dev_unregister); + +MODULE_AUTHOR("Wim Van Sebroeck "); +MODULE_DESCRIPTION("Generic /dev/watchdog Code"); +MODULE_VERSION(DRV_VERSION); +MODULE_LICENSE("GPL"); + diff --git a/drivers/watchdog/hpwdt.c b/drivers/watchdog/hpwdt.c new file mode 100644 index 0000000..c0ebb1e --- /dev/null +++ b/drivers/watchdog/hpwdt.c @@ -0,0 +1,975 @@ +/* + * HP WatchDog Driver + * based on + * + * SoftDog 0.05: A Software Watchdog Device + * + * (c) Copyright 2007 Hewlett-Packard Development Company, L.P. + * Thomas Mingarelli + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef CONFIG_X86_64 +#define HPWDT_ARCH 32 +#else +#define HPWDT_ARCH 64 +#endif + +#define PCI_BIOS32_SD_VALUE 0x5F32335F +#define CRU_BIOS_SIGNATURE_VALUE 0x55524324 +#define PCI_BIOS32_PARAGRAPH_LEN 16 +#define PCI_ROM_BASE1 0x000F0000 +#define ROM_SIZE 0x0FFFF + +struct bios32_service_dir { + u32 signature; + u32 entry_point; + u8 revision; + u8 length; + u8 checksum; + u8 reserved[5]; +}; + +/* + * smbios_entry_point - defines SMBIOS entry point structure + * + * anchor_string[4] - anchor string (_SM_) + * check_sum - checksum of the entry point structure + * length - length of the entry point structure + * major_ver - major version (02h for revision 2.1) + * minor_ver - minor version (01h for revision 2.1) + * max_struct_size - size of the largest SMBIOS structure + * revision - entry point structure revision implemented + * reserved[5] - reserved + * intermediate_anchor[5] - intermediate anchor string (_DMI_) + * intermediate_check_sum - intermediate checksum + * table_length - structure table length + * table_address - structure table address + * number_structs - number of SMBIOS structures present + * bcd_revision - BCD revision + */ +struct smbios_entry_point { + u8 anchor_string[4]; + u8 check_sum; + u8 length; + u8 major_ver; + u8 minor_ver; + u16 max_struct_size; + u8 revision; + u8 reserved[5]; + u8 intermediate_anchor[5]; + u8 intermediate_check_sum; + u16 table_length; + u64 table_address; + u16 number_structs; + u8 bcd_revision; +}; + +struct smbios_header { + u8 byte_type; + u8 byte_length; + u16 handle; +}; + +/* type 212 */ +struct smbios_cru64_info { + u8 type; + u8 byte_length; + u16 handle; + u32 signature; + u64 physical_address; + u32 double_length; + u32 double_offset; +}; +#define SMBIOS_CRU64_INFORMATION 212 + +struct cmn_registers { + union { + struct { + u8 ral; + u8 rah; + u16 rea2; + }; + u32 reax; + } u1; + union { + struct { + u8 rbl; + u8 rbh; + u8 reb2l; + u8 reb2h; + }; + u32 rebx; + } u2; + union { + struct { + u8 rcl; + u8 rch; + u16 rec2; + }; + u32 recx; + } u3; + union { + struct { + u8 rdl; + u8 rdh; + u16 red2; + }; + u32 redx; + } u4; + + u32 resi; + u32 redi; + u16 rds; + u16 res; + u32 reflags; +} __attribute__((packed)); + +#define DEFAULT_MARGIN 30 +static unsigned int soft_margin = DEFAULT_MARGIN; /* in seconds */ +static unsigned int reload; /* the computed soft_margin */ +static int nowayout = WATCHDOG_NOWAYOUT; +static char expect_release; +static unsigned long hpwdt_is_open; + +static void __iomem *p_mem_addr; /* the PCI-memory address */ +static unsigned long __iomem *hpwdt_timer_reg; +static unsigned long __iomem *hpwdt_timer_con; +static u16 tbl_len; +static u64 tbl_addr; + +static DEFINE_SPINLOCK(rom_lock); + +static void *gpVirtRomCRUAddr; +static void *gpBios32Map; +static unsigned char *gpBios32EntryPoint; + +static struct cmn_registers cmn_regs; + +static struct pci_device_id hpwdt_devices[] = { + { + .vendor = PCI_VENDOR_ID_COMPAQ, + .device = 0xB203, + .subvendor = PCI_ANY_ID, + .subdevice = PCI_ANY_ID, + }, + {0}, /* terminate list */ +}; +MODULE_DEVICE_TABLE(pci, hpwdt_devices); + +asmlinkage void asminline_call(struct cmn_registers *pi86Regs, + unsigned long *pRomEntry) +{ +#ifdef CONFIG_X86_32 + asm("pushl %ebp \n\t" + "movl %esp, %ebp \n\t" + "pusha \n\t" + "pushf \n\t" + "push %es \n\t" + "push %ds \n\t" + "pop %es \n\t" + "movl 8(%ebp),%eax \n\t" + "movl 4(%eax),%ebx \n\t" + "movl 8(%eax),%ecx \n\t" + "movl 12(%eax),%edx \n\t" + "movl 16(%eax),%esi \n\t" + "movl 20(%eax),%edi \n\t" + "movl (%eax),%eax \n\t" + "push %cs \n\t" + "call *12(%ebp) \n\t" + "pushf \n\t" + "pushl %eax \n\t" + "movl 8(%ebp),%eax \n\t" + "movl %ebx,4(%eax) \n\t" + "movl %ecx,8(%eax) \n\t" + "movl %edx,12(%eax) \n\t" + "movl %esi,16(%eax) \n\t" + "movl %edi,20(%eax) \n\t" + "movw %ds,24(%eax) \n\t" + "movw %es,26(%eax) \n\t" + "popl %ebx \n\t" + "movl %ebx,(%eax) \n\t" + "popl %ebx \n\t" + "movl %ebx,28(%eax) \n\t" + "pop %es \n\t" + "popf \n\t" + "popa \n\t" + "leave \n\t" "ret"); +#endif + +#ifdef CONFIG_X86_64 + asm("pushq %rbp \n\t" + "movq %rsp, %rbp \n\t" + "pushq %rax \n\t" + "pushq %rbx \n\t" + "pushq %rdx \n\t" + "pushq %r12 \n\t" + "pushq %r9 \n\t" + "movq %rsi, %r12 \n\t" + "movq %rdi, %r9 \n\t" + "movl 4(%r9),%ebx \n\t" + "movl 8(%r9),%ecx \n\t" + "movl 12(%r9),%edx \n\t" + "movl 16(%r9),%esi \n\t" + "movl 20(%r9),%edi \n\t" + "movl (%r9),%eax \n\t" + "call *%r12 \n\t" + "pushfq \n\t" + "popq %r12 \n\t" + "popfq \n\t" + "movl %eax, (%r9) \n\t" + "movl %ebx, 4(%r9) \n\t" + "movl %ecx, 8(%r9) \n\t" + "movl %edx, 12(%r9) \n\t" + "movl %esi, 16(%r9) \n\t" + "movl %edi, 20(%r9) \n\t" + "movq %r12, %rax \n\t" + "movl %eax, 28(%r9) \n\t" + "popq %r9 \n\t" + "popq %r12 \n\t" + "popq %rdx \n\t" + "popq %rbx \n\t" + "popq %rax \n\t" + "leave \n\t" "ret"); +#endif +} + +/* --32 Bit Bios------------------------------------------------------------ */ + +/* + * cru_detect + * + * Routine Description: + * This function uses the 32-bit BIOS Service Directory record to + * search for a $CRU record. + * + * Return Value: + * 0 : SUCCESS + * <0 : FAILURE + */ +static int __devinit cru_detect(void) +{ + unsigned long cru_physical_address; + unsigned long cru_length; + unsigned long physical_bios_base = 0; + unsigned long physical_bios_offset = 0; + int retval = -ENODEV; + + cmn_regs.u1.reax = CRU_BIOS_SIGNATURE_VALUE; + + asminline_call(&cmn_regs, (unsigned long *)gpBios32EntryPoint); + + if (cmn_regs.u1.ral != 0) { + printk(KERN_WARNING + "hpwdt: Call succeeded but with an error: 0x%x\n", + cmn_regs.u1.ral); + } else { + physical_bios_base = cmn_regs.u2.rebx; + physical_bios_offset = cmn_regs.u4.redx; + cru_length = cmn_regs.u3.recx; + cru_physical_address = + physical_bios_base + physical_bios_offset; + + /* If the values look OK, then map it in. */ + if ((physical_bios_base + physical_bios_offset)) { + gpVirtRomCRUAddr = + ioremap(cru_physical_address, cru_length); + if (gpVirtRomCRUAddr) + retval = 0; + } + + printk(KERN_DEBUG "hpwdt: CRU Base Address: 0x%lx\n", + physical_bios_base); + printk(KERN_DEBUG "hpwdt: CRU Offset Address: 0x%lx\n", + physical_bios_offset); + printk(KERN_DEBUG "hpwdt: CRU Length: 0x%lx\n", + cru_length); + printk(KERN_DEBUG "hpwdt: CRU Mapped Address: 0x%x\n", + (unsigned int)&gpVirtRomCRUAddr); + } + iounmap(gpBios32Map); + return retval; +} + +/* + * valid_checksum + */ +static int valid_checksum(void *header_ptr, unsigned char size) +{ + unsigned char i; + unsigned char check_sum = 0; + unsigned char *ptr = header_ptr; + + /* + * calculate checksum of size bytes. This should add up + * to zero if we have a valid header. + */ + for (i = 0; i < size; i++, ptr++) + check_sum = (check_sum + (*ptr)); + + return ((check_sum == 0) && (size > 0)); +} + +/* + * check_for_bios32_support + * + * Routine Description: + * This function determine if a pointer is pointing to the + * 32 bit BIOS Directory Services entry in ROM space. + * + * Return Value: + * 1 - if found the correct signature + * 0 - unable to find _32_ + */ +static int check_for_bios32_support(struct bios32_service_dir *bios_32_ptr) +{ + /* + * Search for signature by checking equal to the swizzled value + * instead of calling another routine to perform a strcmp. + */ + if (bios_32_ptr->signature == PCI_BIOS32_SD_VALUE) { + if (valid_checksum(bios_32_ptr, + ((unsigned char)(bios_32_ptr->length * + PCI_BIOS32_PARAGRAPH_LEN)))) + return 1; + } + return 0; +} + +/* + * find_32bit_bios_sd + * + * Routine Description: + * This function find the 32-bit BIOS Service Directory + * + * Return Value: + * 0 : SUCCESS + * <0 : FAILURE + */ +static int __devinit find_32bit_bios_sd(void) +{ + struct bios32_service_dir *bios_32_data_ptr; + unsigned long map_entry, map_offset; + void *pRomVirtAddr; + unsigned char *p; + int retval = -ENOMEM; + + pRomVirtAddr = ioremap(PCI_ROM_BASE1, ROM_SIZE); + if (!pRomVirtAddr) { + printk(KERN_WARNING "hpwdt: Unable to map in BIOS ROM.\n"); + return retval; + } + + /* + * BIOS32 spec indicates that the signature will be + * paragraph-aligned, so we'll skip by 16 bytes each pass + * through the loop. + */ + for (p = pRomVirtAddr; + p < ((unsigned char *)pRomVirtAddr + ROM_SIZE); p += 16) { + + if (!check_for_bios32_support((struct bios32_service_dir *) p)) + continue; + + /* Found a Service Directory. */ + bios_32_data_ptr = (struct bios32_service_dir *) p; + + /* + * According to the spec, we're looking for the + * first 4KB-aligned address below the entrypoint + * listed in the header. The Service Directory code + * is guaranteed to occupy no more than 2 4KB pages. + */ + map_entry = bios_32_data_ptr->entry_point & ~(PAGE_SIZE - 1); + map_offset = bios_32_data_ptr->entry_point - map_entry; + + gpBios32Map = ioremap(map_entry, (2 * PAGE_SIZE)); + + if (gpBios32Map) { + /* This will be unmapped in cru_detect. */ + gpBios32EntryPoint = gpBios32Map + map_offset; + retval = 0; + } + break; + } + iounmap(pRomVirtAddr); + return retval; +} + +/* --64 Bit Bios------------------------------------------------------------ */ + +/* + * smbios_get_record + * + * Routine Description: + * This function will get the next record in the SMB Tables + * + * Return Value: + * 1 : SUCCESS - if record found + * 0 : FAILURE - if record not found + */ +static int smbios_get_record(unsigned char **pp_record, void *smbios_table_ptr) +{ + unsigned char *buffer; + struct smbios_header *header_ptr; + + /* + * if pp_record is NULL, find the first record + */ + if (*pp_record == 0) { + *pp_record = smbios_table_ptr; + buffer = smbios_table_ptr; + } else { + header_ptr = (struct smbios_header *) *pp_record; + buffer = *pp_record; + + /* + * skip over the structure itself + */ + *pp_record += header_ptr->byte_length; + + buffer += header_ptr->byte_length; + + /* + * skip over any strings following the structure + */ + while (*buffer || *(buffer + 1)) + buffer++; + + /* + * skip over the double NULL characters + */ + buffer += 2; + } + if (buffer >= ((unsigned char *)smbios_table_ptr + tbl_len)) + return 0; + + *pp_record = buffer; + return 1; +} + +/* + * smbios_get_record_by_type + * + * Routine Description: + * This function will get a record in the SMB Table by the type specified + * + * Return Value: + * 1 : SUCCESS - if record found + * 0 : FAILURE - if record not found + */ +static int smbios_get_record_by_type(unsigned char type, unsigned short copy, + void **pp_record, void *smbios_table_ptr) +{ + unsigned char *save_state_ptr = NULL; + struct smbios_header *header_ptr; + + while (smbios_get_record(&save_state_ptr, smbios_table_ptr)) { + header_ptr = (struct smbios_header *) save_state_ptr; + if (header_ptr->byte_type == type) { + if (copy == 0) { + *pp_record = (void *)save_state_ptr; + return 1; + } else + copy--; + } + } + return 0; +} + +/* + * smbios_get_64bit_cru_info + * + * Routine Description: + * This routine will collect the 64bit CRU entry point from SMBIOS + * table. + * + * Return Value: + * 0 : SUCCESS + * <0 : FAILURE + */ +static int __devinit smbios_get_64bit_cru_info(void) +{ + unsigned short copy; + unsigned char *record_ptr = NULL; + struct smbios_cru64_info *smbios_cru64_ptr; + unsigned long cru_physical_address; + void *smbios_table_ptr; + int retval = -ENOMEM; + + smbios_table_ptr = ioremap((unsigned long)tbl_addr, tbl_len); + if (!smbios_table_ptr) + return retval; + + copy = 0; + while (smbios_get_record_by_type + (SMBIOS_CRU64_INFORMATION, copy, (void *)&record_ptr, + smbios_table_ptr)) { + /* + * In the event the ROM has a bug, let's print this + * out and quit before we get into trouble. + */ + if (!record_ptr) { + printk(KERN_WARNING + "hpwdt: smbios: Missing SMBIOS_CRU64_INFO!\n"); + retval = -ENODEV; + break; + } + smbios_cru64_ptr = (struct smbios_cru64_info *) record_ptr; + if (smbios_cru64_ptr->signature == CRU_BIOS_SIGNATURE_VALUE) { + + cru_physical_address = + smbios_cru64_ptr->physical_address + + smbios_cru64_ptr->double_offset; + gpVirtRomCRUAddr = + ioremap((unsigned long)cru_physical_address, + smbios_cru64_ptr->double_length); + if (gpVirtRomCRUAddr) + retval = 0; + break; + } + } + + iounmap(smbios_table_ptr); + return retval; +} + +/* + * smbios_detect + * + * Routine Description: + * This function parses SMBIOS to retrieve the 64 bit CRU Service. + * + * Return Value: + * 0 : SUCCESS + * <0 : FAILURE + */ +static int smbios_detect(void) +{ + unsigned char *p_virtual_rom; + unsigned char *p; + struct smbios_entry_point *pEPS; + int retval = -ENOMEM; + + /* + * Search from 0x0f0000 through 0x0fffff, inclusive. + */ + p_virtual_rom = ioremap(PCI_ROM_BASE1, ROM_SIZE); + if (!p_virtual_rom) + return retval; + + for (p = p_virtual_rom; p < p_virtual_rom + ROM_SIZE; p += 16) { + pEPS = (struct smbios_entry_point *) p; + if ((strncmp((char *)pEPS->anchor_string, "_SM_", + sizeof(pEPS->anchor_string))) == 0) { + tbl_addr = pEPS->table_address; + tbl_len = pEPS->table_length; + retval = 0; + break; + } + } + iounmap(p_virtual_rom); + return retval; +} + +/* ------------------------------------------------------------------------- */ + +/* + * NMI Handler + */ +static int hpwdt_pretimeout(struct notifier_block *nb, unsigned long ulReason, + void *dummy) +{ + static unsigned long rom_pl; + static int die_nmi_called; + + if (ulReason == DIE_NMI || ulReason == DIE_NMI_IPI) { + spin_lock_irqsave(&rom_lock, rom_pl); + if (!die_nmi_called) + asminline_call(&cmn_regs, gpVirtRomCRUAddr); + die_nmi_called = 1; + spin_unlock_irqrestore(&rom_lock, rom_pl); + if (cmn_regs.u1.ral == 0) { + printk(KERN_WARNING "hpwdt: An NMI occurred, " + "but unable to determine source.\n"); + } else { + panic("An NMI occurred, please see the Integrated " + "Management Log for details.\n"); + } + } + return NOTIFY_STOP; +} + +/* + * Watchdog operations + */ +static void hpwdt_start(void) +{ + reload = (soft_margin * 1000) / 128; + iowrite16(reload, hpwdt_timer_reg); + iowrite16(0x85, hpwdt_timer_con); +} + +static void hpwdt_stop(void) +{ + unsigned long data; + + data = ioread16(hpwdt_timer_con); + data &= 0xFE; + iowrite16(data, hpwdt_timer_con); +} + +static void hpwdt_ping(void) +{ + iowrite16(reload, hpwdt_timer_reg); +} + +static int hpwdt_change_timer(int new_margin) +{ + /* Arbitrary, can't find the card's limits */ + if (new_margin < 30 || new_margin > 600) { + printk(KERN_WARNING + "hpwdt: New value passed in is invalid: %d seconds.\n", + new_margin); + return -EINVAL; + } + + soft_margin = new_margin; + printk(KERN_DEBUG + "hpwdt: New timer passed in is %d seconds.\n", + new_margin); + reload = (soft_margin * 1000) / 128; + + return 0; +} + +/* + * /dev/watchdog handling + */ +static int hpwdt_open(struct inode *inode, struct file *file) +{ + /* /dev/watchdog can only be opened once */ + if (test_and_set_bit(0, &hpwdt_is_open)) + return -EBUSY; + + /* Start the watchdog */ + hpwdt_start(); + hpwdt_ping(); + + return nonseekable_open(inode, file); +} + +static int hpwdt_release(struct inode *inode, struct file *file) +{ + /* Stop the watchdog */ + if (expect_release == 42) { + hpwdt_stop(); + } else { + printk(KERN_CRIT + "hpwdt: Unexpected close, not stopping watchdog!\n"); + hpwdt_ping(); + } + + expect_release = 0; + + /* /dev/watchdog is being closed, make sure it can be re-opened */ + clear_bit(0, &hpwdt_is_open); + + return 0; +} + +static ssize_t hpwdt_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + /* See if we got the magic character 'V' and reload the timer */ + if (len) { + if (!nowayout) { + size_t i; + + /* note: just in case someone wrote the magic character + * five months ago... */ + expect_release = 0; + + /* scan to see whether or not we got the magic char. */ + for (i = 0; i != len; i++) { + char c; + if (get_user(c, data+i)) + return -EFAULT; + if (c == 'V') + expect_release = 42; + } + } + + /* someone wrote to us, we should reload the timer */ + hpwdt_ping(); + } + + return len; +} + +static struct watchdog_info ident = { + .options = WDIOF_SETTIMEOUT | + WDIOF_KEEPALIVEPING | + WDIOF_MAGICCLOSE, + .identity = "HP iLO2 HW Watchdog Timer", +}; + +static long hpwdt_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + int __user *p = argp; + int new_margin; + int ret = -ENOTTY; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = 0; + if (copy_to_user(argp, &ident, sizeof(ident))) + ret = -EFAULT; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, p); + break; + + case WDIOC_KEEPALIVE: + hpwdt_ping(); + ret = 0; + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(new_margin, p); + if (ret) + break; + + ret = hpwdt_change_timer(new_margin); + if (ret) + break; + + hpwdt_ping(); + /* Fall */ + case WDIOC_GETTIMEOUT: + ret = put_user(soft_margin, p); + break; + } + return ret; +} + +/* + * Kernel interfaces + */ +static struct file_operations hpwdt_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = hpwdt_write, + .unlocked_ioctl = hpwdt_ioctl, + .open = hpwdt_open, + .release = hpwdt_release, +}; + +static struct miscdevice hpwdt_miscdev = { + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &hpwdt_fops, +}; + +static struct notifier_block die_notifier = { + .notifier_call = hpwdt_pretimeout, + .priority = 0x7FFFFFFF, +}; + +/* + * Init & Exit + */ + +static int __devinit detect_bios_directory(void) +{ + if (HPWDT_ARCH == 32) + return find_32bit_bios_sd(); + else + return smbios_detect(); +} + +static int __devinit detect_cru_service(void) +{ + if (HPWDT_ARCH == 32) + return cru_detect(); + else + return smbios_get_64bit_cru_info(); +} + +static int __devinit hpwdt_init_one(struct pci_dev *dev, + const struct pci_device_id *ent) +{ + int retval; + + /* + * First let's find out if we are on an iLO2 server. We will + * not run on a legacy ASM box. + */ + if (dev->subsystem_vendor != PCI_VENDOR_ID_HP) { + dev_warn(&dev->dev, + "This server does not have an iLO2 ASIC.\n"); + return -ENODEV; + } + + if (pci_enable_device(dev)) { + dev_warn(&dev->dev, + "Not possible to enable PCI Device: 0x%x:0x%x.\n", + ent->vendor, ent->device); + return -ENODEV; + } + + p_mem_addr = pci_iomap(dev, 1, 0x80); + if (!p_mem_addr) { + dev_warn(&dev->dev, + "Unable to detect the iLO2 server memory.\n"); + retval = -ENOMEM; + goto error_pci_iomap; + } + hpwdt_timer_reg = p_mem_addr + 0x70; + hpwdt_timer_con = p_mem_addr + 0x72; + + /* Make sure that we have a valid soft_margin */ + if (hpwdt_change_timer(soft_margin)) + hpwdt_change_timer(DEFAULT_MARGIN); + + /* + * We need to map the ROM to get the CRU service. + * For 32 bit Operating Systems we need to go through the 32 Bit + * BIOS Service Directory + * For 64 bit Operating Systems we get that service through SMBIOS. + */ + retval = detect_bios_directory(); + if (retval < 0) { + dev_warn(&dev->dev, "No %s found.\n", + (HPWDT_ARCH == 32) ? + "32 Bit BIOS Service Directory" : "64 Bit SMBIOS"); + goto error_get_cru; + } + + retval = detect_cru_service(); + if (retval < 0) { + dev_warn(&dev->dev, + "Unable to detect the %d Bit CRU Service.\n", + HPWDT_ARCH); + goto error_get_cru; + } + + /* + * We know this is the only CRU call we need to make so lets keep as + * few instructions as possible once the NMI comes in. + */ + cmn_regs.u1.rah = 0x0D; + cmn_regs.u1.ral = 0x02; + + retval = register_die_notifier(&die_notifier); + if (retval != 0) { + dev_warn(&dev->dev, + "Unable to register a die notifier (err=%d).\n", + retval); + goto error_die_notifier; + } + + retval = misc_register(&hpwdt_miscdev); + if (retval < 0) { + dev_warn(&dev->dev, + "Unable to register miscdev on minor=%d (err=%d).\n", + WATCHDOG_MINOR, retval); + goto error_misc_register; + } + + printk(KERN_INFO + "hp Watchdog Timer Driver: 1.00" + ", timer margin: %d seconds( nowayout=%d).\n", + soft_margin, nowayout); + + return 0; + +error_misc_register: + unregister_die_notifier(&die_notifier); +error_die_notifier: + if (gpVirtRomCRUAddr) + iounmap(gpVirtRomCRUAddr); +error_get_cru: + pci_iounmap(dev, p_mem_addr); +error_pci_iomap: + pci_disable_device(dev); + return retval; +} + +static void __devexit hpwdt_exit(struct pci_dev *dev) +{ + if (!nowayout) + hpwdt_stop(); + + misc_deregister(&hpwdt_miscdev); + unregister_die_notifier(&die_notifier); + + if (gpVirtRomCRUAddr) + iounmap(gpVirtRomCRUAddr); + pci_iounmap(dev, p_mem_addr); + pci_disable_device(dev); +} + +static struct pci_driver hpwdt_driver = { + .name = "hpwdt", + .id_table = hpwdt_devices, + .probe = hpwdt_init_one, + .remove = __devexit_p(hpwdt_exit), +}; + +static void __exit hpwdt_cleanup(void) +{ + pci_unregister_driver(&hpwdt_driver); +} + +static int __init hpwdt_init(void) +{ + return pci_register_driver(&hpwdt_driver); +} + +MODULE_AUTHOR("Tom Mingarelli"); +MODULE_DESCRIPTION("hp 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_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); + +module_init(hpwdt_init); +module_exit(hpwdt_cleanup); diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c index a0e6809..ceac0b2 100644 --- a/drivers/watchdog/iTCO_wdt.c +++ b/drivers/watchdog/iTCO_wdt.c @@ -1,5 +1,5 @@ /* - * intel TCO Watchdog Driver (Used in i82801 and i6300ESB chipsets) + * intel TCO Watchdog Driver (Used in i82801 and i63xxESB chipsets) * * (c) Copyright 2006-2007 Wim Van Sebroeck . * @@ -55,8 +55,8 @@ /* Module and version information */ #define DRV_NAME "iTCO_wdt" -#define DRV_VERSION "1.02" -#define DRV_RELDATE "26-Jul-2007" +#define DRV_VERSION "1.10" +#define DRV_RELDATE "17-Aug-2007" #define PFX DRV_NAME ": " /* Includes */ @@ -137,10 +137,10 @@ static struct { {"ICH9R", 2}, {"ICH9DH", 2}, {"631xESB/632xESB", 2}, - {NULL,0} + {NULL, 0} }; -#define ITCO_PCI_DEVICE(dev, data) \ +#define ITCO_PCI_DEVICE(dev, data) \ .vendor = PCI_VENDOR_ID_INTEL, \ .device = dev, \ .subvendor = PCI_ANY_ID, \ @@ -207,18 +207,16 @@ MODULE_DEVICE_TABLE (pci, iTCO_wdt_pci_tbl); #define TCO_RLD TCOBASE + 0x00 /* TCO Timer Reload and Current Value */ #define TCOv1_TMR TCOBASE + 0x01 /* TCOv1 Timer Initial Value */ -#define TCO_DAT_IN TCOBASE + 0x02 /* TCO Data In Register */ -#define TCO_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 TCO_DAT_IN TCOBASE + 0x02 /* TCO Data In Register */ +#define TCO_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 TCOv2_TMR TCOBASE + 0x12 /* TCOv2 Timer Initial Value */ /* internal variables */ -static unsigned long is_active; -static char expect_release; -static struct { /* this is private data for the iTCO_wdt device */ +static struct iTCO_wdt_data { /* this is private data for the iTCO_wdt device */ unsigned int iTCO_version; /* TCO version/generation */ unsigned long ACPIBASE; /* The cards ACPIBASE address (TCOBASE = ACPIBASE+0x60) */ unsigned long __iomem *gcs; /* NO_REBOOT flag is Memory-Mapped GCS register bit 5 (TCO version 2) */ @@ -226,6 +224,7 @@ static struct { /* this is private data for the iTCO_wdt device */ struct pci_dev *pdev; /* the PCI-device */ } iTCO_wdt_private; +static struct watchdog_device *iTCO_wdt_dev; /* the watchdog device */ static struct platform_device *iTCO_wdt_platform_device; /* the watchdog platform device */ /* module parameters */ @@ -248,7 +247,7 @@ extern int iTCO_vendor_check_noreboot_on(void); #else #define iTCO_vendor_pre_start(acpibase, heartbeat) {} #define iTCO_vendor_pre_stop(acpibase) {} -#define iTCO_vendor_pre_keepalive(acpibase,heartbeat) {} +#define iTCO_vendor_pre_keepalive(acpibase, heartbeat) {} #define iTCO_vendor_pre_set_heartbeat(heartbeat) {} #define iTCO_vendor_check_noreboot_on() 1 /* 1=check noreboot; 0=don't check */ #endif @@ -307,10 +306,13 @@ static int iTCO_wdt_unset_NO_REBOOT_bit(void) return ret; /* returns: 0 = OK, -EIO = Error */ } -static int iTCO_wdt_start(void) +static int iTCO_wdt_start(struct watchdog_device *wd_dev) { unsigned int val; + if (!wd_dev) + return -ENODEV; + spin_lock(&iTCO_wdt_private.io_lock); iTCO_vendor_pre_start(iTCO_wdt_private.ACPIBASE, heartbeat); @@ -334,10 +336,13 @@ static int iTCO_wdt_start(void) return 0; } -static int iTCO_wdt_stop(void) +static int iTCO_wdt_stop(struct watchdog_device *wd_dev) { unsigned int val; + if (!wd_dev) + return -ENODEV; + spin_lock(&iTCO_wdt_private.io_lock); iTCO_vendor_pre_stop(iTCO_wdt_private.ACPIBASE); @@ -358,8 +363,11 @@ static int iTCO_wdt_stop(void) return 0; } -static int iTCO_wdt_keepalive(void) +static int iTCO_wdt_keepalive(struct watchdog_device *wd_dev) { + if (!wd_dev) + return -ENODEV; + spin_lock(&iTCO_wdt_private.io_lock); iTCO_vendor_pre_keepalive(iTCO_wdt_private.ACPIBASE, heartbeat); @@ -375,12 +383,15 @@ static int iTCO_wdt_keepalive(void) return 0; } -static int iTCO_wdt_set_heartbeat(int t) +static int iTCO_wdt_set_heartbeat(struct watchdog_device *wd_dev, int t) { unsigned int val16; unsigned char val8; unsigned int tmrval; + if (!wd_dev) + return -ENODEV; + tmrval = seconds_to_ticks(t); /* from the specs: */ /* "Values of 0h-3h are ignored and should not be attempted" */ @@ -417,15 +428,18 @@ static int iTCO_wdt_set_heartbeat(int t) return -EINVAL; } - heartbeat = t; + wd_dev->heartbeat = t; return 0; } -static int iTCO_wdt_get_timeleft (int *time_left) +static int iTCO_wdt_get_timeleft (struct watchdog_device *wd_dev, int *time_left) { unsigned int val16; unsigned char val8; + if (!wd_dev) + return -ENODEV; + /* read the TCO Timer */ if (iTCO_wdt_private.iTCO_version == 2) { spin_lock(&iTCO_wdt_private.io_lock); @@ -447,161 +461,15 @@ static int iTCO_wdt_get_timeleft (int *time_left) } /* - * /dev/watchdog handling - */ - -static int iTCO_wdt_open (struct inode *inode, struct file *file) -{ - /* /dev/watchdog can only be opened once */ - if (test_and_set_bit(0, &is_active)) - return -EBUSY; - - /* - * Reload and activate timer - */ - iTCO_wdt_keepalive(); - iTCO_wdt_start(); - return nonseekable_open(inode, file); -} - -static int iTCO_wdt_release (struct inode *inode, struct file *file) -{ - /* - * Shut off the timer. - */ - if (expect_release == 42) { - iTCO_wdt_stop(); - } else { - printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog!\n"); - iTCO_wdt_keepalive(); - } - clear_bit(0, &is_active); - expect_release = 0; - return 0; -} - -static ssize_t iTCO_wdt_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 */ - iTCO_wdt_keepalive(); - } - return len; -} - -static int iTCO_wdt_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 = DRV_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: - iTCO_wdt_keepalive(); - return 0; - - case WDIOC_SETOPTIONS: - { - if (get_user(new_options, p)) - return -EFAULT; - - if (new_options & WDIOS_DISABLECARD) { - iTCO_wdt_stop(); - retval = 0; - } - - if (new_options & WDIOS_ENABLECARD) { - iTCO_wdt_keepalive(); - iTCO_wdt_start(); - retval = 0; - } - - return retval; - } - - case WDIOC_SETTIMEOUT: - { - if (get_user(new_heartbeat, p)) - return -EFAULT; - - if (iTCO_wdt_set_heartbeat(new_heartbeat)) - return -EINVAL; - - iTCO_wdt_keepalive(); - /* Fall */ - } - - case WDIOC_GETTIMEOUT: - return put_user(heartbeat, p); - - case WDIOC_GETTIMELEFT: - { - int time_left; - - if (iTCO_wdt_get_timeleft(&time_left)) - return -EINVAL; - - return put_user(time_left, p); - } - - default: - return -ENOTTY; - } -} - -/* * Kernel Interfaces */ -static const struct file_operations iTCO_wdt_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .write = iTCO_wdt_write, - .ioctl = iTCO_wdt_ioctl, - .open = iTCO_wdt_open, - .release = iTCO_wdt_release, -}; - -static struct miscdevice iTCO_wdt_miscdev = { - .minor = WATCHDOG_MINOR, - .name = "watchdog", - .fops = &iTCO_wdt_fops, +static struct watchdog_ops iTCO_wdt_ops = { + .start = iTCO_wdt_start, + .stop = iTCO_wdt_stop, + .keepalive = iTCO_wdt_keepalive, + .set_heartbeat = iTCO_wdt_set_heartbeat, + .get_timeleft = iTCO_wdt_get_timeleft, }; /* @@ -615,6 +483,21 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev, const struct pci_device unsigned long RCBA; unsigned long val32; + iTCO_wdt_dev = alloc_watchdogdev(); + if (!iTCO_wdt_dev) { + printk(KERN_ERR PFX "Unable to allocate a watchdogdevice\n"); + pci_dev_put(pdev); + return -ENODEV; + } + + strcpy(iTCO_wdt_dev->name, DRV_NAME); + iTCO_wdt_dev->options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING; + iTCO_wdt_dev->firmware = iTCO_chipset_info[ent->driver_data].iTCO_version; + iTCO_wdt_dev->nowayout = nowayout; + iTCO_wdt_dev->watchdog_ops = &iTCO_wdt_ops; + + iTCO_wdt_dev->private = &iTCO_wdt_private; + /* * Find the ACPI/PM base I/O address which is the base * for the TCO registers (TCOBASE=ACPIBASE + 0x60) @@ -638,7 +521,7 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev, const struct pci_device if (iTCO_wdt_private.iTCO_version == 2) { pci_read_config_dword(pdev, 0xf0, &base_address); RCBA = base_address & 0xffffc000; - iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410),4); + iTCO_wdt_private.gcs = ioremap((RCBA + 0x3410), 4); } /* Check chipset's NO_REBOOT bit */ @@ -681,19 +564,19 @@ static int __devinit iTCO_wdt_init(struct pci_dev *pdev, const struct pci_device outb(3, TCO2_STS); /* Make sure the watchdog is not running */ - iTCO_wdt_stop(); + iTCO_wdt_stop(iTCO_wdt_dev); /* Check that the heartbeat value is within it's range ; if not reset to the default */ - if (iTCO_wdt_set_heartbeat(heartbeat)) { - iTCO_wdt_set_heartbeat(WATCHDOG_HEARTBEAT); + if (iTCO_wdt_set_heartbeat(iTCO_wdt_dev, heartbeat)) { + iTCO_wdt_set_heartbeat(iTCO_wdt_dev, WATCHDOG_HEARTBEAT); printk(KERN_INFO PFX "heartbeat value must be 2dev); if (ret != 0) { - printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d)\n", - WATCHDOG_MINOR, ret); + printk(KERN_ERR PFX "cannot register new watchdog device (err=%d)\n", + ret); goto unreg_region; } @@ -707,6 +590,7 @@ unreg_region: out: if (iTCO_wdt_private.iTCO_version == 2) iounmap(iTCO_wdt_private.gcs); + free_watchdogdev(iTCO_wdt_dev); pci_dev_put(iTCO_wdt_private.pdev); iTCO_wdt_private.ACPIBASE = 0; return ret; @@ -716,13 +600,14 @@ static void __devexit iTCO_wdt_cleanup(void) { /* Stop the timer before we leave */ if (!nowayout) - iTCO_wdt_stop(); + iTCO_wdt_stop(iTCO_wdt_dev); /* Deregister */ - misc_deregister(&iTCO_wdt_miscdev); + unregister_watchdogdevice(iTCO_wdt_dev); release_region(TCOBASE, 0x20); if (iTCO_wdt_private.iTCO_version == 2) iounmap(iTCO_wdt_private.gcs); + free_watchdogdev(iTCO_wdt_dev); pci_dev_put(iTCO_wdt_private.pdev); iTCO_wdt_private.ACPIBASE = 0; } @@ -763,7 +648,7 @@ static int __devexit iTCO_wdt_remove(struct platform_device *dev) static void iTCO_wdt_shutdown(struct platform_device *dev) { - iTCO_wdt_stop(); + iTCO_wdt_stop(iTCO_wdt_dev); } #define iTCO_wdt_suspend NULL diff --git a/drivers/watchdog/mtx-1_wdt.c b/drivers/watchdog/mtx-1_wdt.c index 9845174..789831b 100644 --- a/drivers/watchdog/mtx-1_wdt.c +++ b/drivers/watchdog/mtx-1_wdt.c @@ -45,10 +45,13 @@ #include #include #include +#include + #include #include #include +#include #define MTX1_WDT_INTERVAL (5 * HZ) @@ -61,6 +64,7 @@ static struct { volatile int queue; int default_ticks; unsigned long inuse; + unsigned gpio; } mtx1_wdt_device; static void mtx1_wdt_trigger(unsigned long unused) @@ -73,7 +77,8 @@ static void mtx1_wdt_trigger(unsigned long unused) * toggle GPIO2_15 */ tmp = au_readl(GPIO2_DIR); - tmp = (tmp & ~(1<<15)) | ((~tmp) & (1<<15)); + tmp = (tmp & ~(1 << mtx1_wdt_device.gpio)) | + ((~tmp) & (1 << mtx1_wdt_device.gpio)); au_writel (tmp, GPIO2_DIR); if (mtx1_wdt_device.queue && ticks) @@ -93,7 +98,7 @@ static void mtx1_wdt_start(void) { if (!mtx1_wdt_device.queue) { mtx1_wdt_device.queue = 1; - au_writel (au_readl(GPIO2_DIR) | (u32)(1<<15), GPIO2_DIR); + gpio_set_value(mtx1_wdt_device.gpio, 1); mod_timer(&mtx1_wdt_device.timer, jiffies + MTX1_WDT_INTERVAL); } mtx1_wdt_device.running++; @@ -103,7 +108,7 @@ static int mtx1_wdt_stop(void) { if (mtx1_wdt_device.queue) { mtx1_wdt_device.queue = 0; - au_writel (au_readl(GPIO2_DIR) & ~((u32)(1<<15)), GPIO2_DIR); + gpio_set_value(mtx1_wdt_device.gpio, 0); } ticks = mtx1_wdt_device.default_ticks; @@ -197,10 +202,12 @@ static struct miscdevice mtx1_wdt_misc = { }; -static int __init mtx1_wdt_init(void) +static int mtx1_wdt_probe(struct platform_device *pdev) { int ret; + mtx1_wdt_device.gpio = pdev->resource[0].start; + if ((ret = misc_register(&mtx1_wdt_misc)) < 0) { printk(KERN_ERR " mtx-1_wdt : failed to register\n"); return ret; @@ -222,13 +229,30 @@ static int __init mtx1_wdt_init(void) return 0; } -static void __exit mtx1_wdt_exit(void) +static int mtx1_wdt_remove(struct platform_device *pdev) { if (mtx1_wdt_device.queue) { mtx1_wdt_device.queue = 0; wait_for_completion(&mtx1_wdt_device.stop); } misc_deregister(&mtx1_wdt_misc); + return 0; +} + +static struct platform_driver mtx1_wdt = { + .probe = mtx1_wdt_probe, + .remove = mtx1_wdt_remove, + .driver.name = "mtx1-wdt", +}; + +static int __init mtx1_wdt_init(void) +{ + return platform_driver_register(&mtx1_wdt); +} + +static void __exit mtx1_wdt_exit(void) +{ + platform_driver_unregister(&mtx1_wdt); } module_init(mtx1_wdt_init); @@ -237,3 +261,4 @@ module_exit(mtx1_wdt_exit); MODULE_AUTHOR("Michael Stickel, Florian Fainelli"); MODULE_DESCRIPTION("Driver for the MTX-1 watchdog"); MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); diff --git a/drivers/watchdog/sb_wdog.c b/drivers/watchdog/sb_wdog.c new file mode 100644 index 0000000..b944314 --- /dev/null +++ b/drivers/watchdog/sb_wdog.c @@ -0,0 +1,353 @@ +/* + * Watchdog driver for SiByte SB1 SoCs + * + * Copyright (C) 2007 OnStor, Inc. * Andrew Sharp + * + * This driver is intended to make the second of two hardware watchdogs + * on the Sibyte 12XX and 11XX SoCs available to the user. There are two + * such devices available on the SoC, but it seems that there isn't an + * enumeration class for watchdogs in Linux like there is for RTCs. + * The second is used rather than the first because it uses IRQ 1, + * thereby avoiding all that IRQ 0 problematic nonsense. + * + * I have not tried this driver on a 1480 processor; it might work + * just well enough to really screw things up. + * + * It is a simple timer, and there is an interrupt that is raised the + * first time the timer expires. The second time it expires, the chip + * is reset and there is no way to redirect that NMI. Which could + * be problematic in some cases where this chip is sitting on the HT + * bus and has just taken responsibility for providing a cache block. + * Since the reset can't be redirected to the external reset pin, it is + * possible that other HT connected processors might hang and not reset. + * For Linux, a soft reset would probably be even worse than a hard reset. + * There you have it. + * + * The timer takes 23 bits of a 64 bit register (?) as a count value, + * and decrements the count every microsecond, for a max value of + * 0x7fffff usec or about 8.3ish seconds. + * + * This watchdog borrows some user semantics from the softdog driver, + * in that if you close the fd, it leaves the watchdog running, unless + * you previously wrote a 'V' to the fd, in which case it disables + * the watchdog when you close the fd like some other drivers. + * + * Based on various other watchdog drivers, which are probably all + * loosely based on something Alan Cox wrote years ago. + * + * (c) Copyright 1996 Alan Cox , 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 + * version 1 or 2 as published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +/* + * set the initial count value of a timer + * + * wdog is the iomem address of the cfg register + */ +void sbwdog_set(char __iomem *wdog, unsigned long t) +{ + __raw_writeb(0, wdog - 0x10); + __raw_writeq(t & 0x7fffffUL, wdog); +} + +/* + * cause the timer to [re]load it's initial count and start counting + * all over again + * + * wdog is the iomem address of the cfg register + */ +void sbwdog_pet(char __iomem *wdog) +{ + __raw_writeb(__raw_readb(wdog) | 1, wdog); +} + +static unsigned long sbwdog_gate; /* keeps it to one thread only */ +static char __iomem *kern_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_0)); +static char __iomem *user_dog = (char __iomem *)(IO_BASE + (A_SCD_WDOG_CFG_1)); +static unsigned long timeout = 0x7fffffUL; /* useconds: 8.3ish secs. */ +static int expect_close; + +static struct watchdog_info ident = { + .options = WDIOF_CARDRESET | WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING, + .identity = "SiByte Watchdog", +}; + +/* + * Allow only a single thread to walk the dog + */ +static int sbwdog_open(struct inode *inode, struct file *file) +{ + nonseekable_open(inode, file); + if (test_and_set_bit(0, &sbwdog_gate)) { + return -EBUSY; + } + __module_get(THIS_MODULE); + + /* + * Activate the timer + */ + sbwdog_set(user_dog, timeout); + __raw_writeb(1, user_dog); + + return 0; +} + +/* + * Put the dog back in the kennel. + */ +static int sbwdog_release(struct inode *inode, struct file *file) +{ + if (expect_close == 42) { + __raw_writeb(0, user_dog); + module_put(THIS_MODULE); + } else { + printk(KERN_CRIT "%s: Unexpected close, not stopping watchdog!\n", + ident.identity); + sbwdog_pet(user_dog); + } + clear_bit(0, &sbwdog_gate); + expect_close = 0; + + return 0; +} + +/* + * 42 - the answer + */ +static ssize_t sbwdog_write(struct file *file, const char __user *data, + size_t len, loff_t *ppos) +{ + int i; + + if (len) { + /* + * restart the timer + */ + 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; + } + } + sbwdog_pet(user_dog); + } + + return len; +} + +static int sbwdog_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + int ret = -ENOTTY; + unsigned long time; + void __user *argp = (void __user *)arg; + int __user *p = argp; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ret = copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; + break; + + case WDIOC_GETSTATUS: + case WDIOC_GETBOOTSTATUS: + ret = put_user(0, p); + break; + + case WDIOC_SETTIMEOUT: + ret = get_user(time, p); + if (ret) { + break; + } + + time *= 1000000; + if (time > 0x7fffffUL) { + ret = -EINVAL; + break; + } + timeout = time; + sbwdog_set(user_dog, timeout); + sbwdog_pet(user_dog); + + case WDIOC_GETTIMEOUT: + /* + * get the remaining count from the ... count register + * which is 1*8 before the config register + */ + ret = put_user(__raw_readq(user_dog - 8) / 1000000, p); + break; + + case WDIOC_KEEPALIVE: + sbwdog_pet(user_dog); + ret = 0; + break; + } + return ret; +} + +/* + * Notifier for system down + */ +static int +sbwdog_notify_sys(struct notifier_block *this, unsigned long code, void *erf) +{ + if (code == SYS_DOWN || code == SYS_HALT) { + /* + * sit and sit + */ + __raw_writeb(0, user_dog); + __raw_writeb(0, kern_dog); + } + + return NOTIFY_DONE; +} + +static const struct file_operations sbwdog_fops = +{ + .owner = THIS_MODULE, + .llseek = no_llseek, + .write = sbwdog_write, + .ioctl = sbwdog_ioctl, + .open = sbwdog_open, + .release = sbwdog_release, +}; + +static struct miscdevice sbwdog_miscdev = +{ + .minor = WATCHDOG_MINOR, + .name = "watchdog", + .fops = &sbwdog_fops, +}; + +static struct notifier_block sbwdog_notifier = { + .notifier_call = sbwdog_notify_sys, +}; + +/* + * interrupt handler + * + * doesn't do a whole lot for user, but oh so cleverly written so kernel + * code can use it to re-up the watchdog, thereby saving the kernel from + * having to create and maintain a timer, just to tickle another timer, + * which is just so wrong. + */ +irqreturn_t sbwdog_interrupt(int irq, void *addr) +{ + unsigned long wd_init; + char *wd_cfg_reg = (char *)addr; + u8 cfg; + + cfg = __raw_readb(wd_cfg_reg); + wd_init = __raw_readq(wd_cfg_reg - 8) & 0x7fffff; + + /* + * if it's the second watchdog timer, it's for those users + */ + if (wd_cfg_reg == user_dog) { + printk(KERN_CRIT + "%s in danger of initiating system reset in %ld.%01ld seconds\n", + ident.identity, wd_init / 1000000, (wd_init / 100000) % 10); + } else { + cfg |= 1; + } + + __raw_writeb(cfg, wd_cfg_reg); + + return IRQ_HANDLED; +} + +static int __init sbwdog_init(void) +{ + int ret; + + /* + * register a reboot notifier + */ + ret = register_reboot_notifier(&sbwdog_notifier); + if (ret) { + printk (KERN_ERR "%s: cannot register reboot notifier (err=%d)\n", + ident.identity, ret); + return ret; + } + + /* + * get the resources + */ + ret = misc_register(&sbwdog_miscdev); + if (ret == 0) { + printk(KERN_INFO "%s: timeout is %ld.%ld secs\n", ident.identity, + timeout / 1000000, (timeout / 100000) % 10); + } + + ret = request_irq(1, sbwdog_interrupt, IRQF_DISABLED | IRQF_SHARED, + ident.identity, (void *)user_dog); + if (ret) { + printk(KERN_ERR "%s: failed to request irq 1 - %d\n", ident.identity, + ret); + misc_deregister(&sbwdog_miscdev); + } + + return ret; +} + +static void __exit sbwdog_exit(void) +{ + misc_deregister(&sbwdog_miscdev); +} + +module_init(sbwdog_init); +module_exit(sbwdog_exit); + +MODULE_AUTHOR("Andrew Sharp "); +MODULE_DESCRIPTION("SiByte Watchdog"); + +module_param(timeout, ulong, 0); +MODULE_PARM_DESC(timeout, + "Watchdog timeout in microseconds (max/default 8388607 or 8.3ish secs)"); + +MODULE_LICENSE("GPL"); +MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); + +/* + * example code that can be put in a platform code area to utilize the + * first watchdog timer for the kernels own purpose. + + void +platform_wd_setup(void) +{ + int ret; + + ret = request_irq(0, sbwdog_interrupt, IRQF_DISABLED | IRQF_SHARED, + "Kernel Watchdog", IOADDR(A_SCD_WDOG_CFG_0)); + if (ret) { + printk(KERN_CRIT "Watchdog IRQ zero(0) failed to be requested - %d\n", + ret); + } +} + + + */ diff --git a/include/linux/watchdog.h b/include/linux/watchdog.h index 011bcfe..4f57bf4 100644 --- a/include/linux/watchdog.h +++ b/include/linux/watchdog.h @@ -53,12 +53,61 @@ struct watchdog_info { #ifdef __KERNEL__ +#include + #ifdef CONFIG_WATCHDOG_NOWAYOUT #define WATCHDOG_NOWAYOUT 1 #else #define WATCHDOG_NOWAYOUT 0 #endif +struct watchdog_ops; +struct watchdog_device; + +struct watchdog_ops { + /* mandatory routines */ + /* operation = start watchdog */ + int (*start)(struct watchdog_device *); + /* operation = stop watchdog */ + int (*stop)(struct watchdog_device *); + /* operation = send keepalive ping */ + int (*keepalive)(struct watchdog_device *); + /* optional routines */ + /* operation = set watchdog's heartbeat */ + int (*set_heartbeat)(struct watchdog_device *, int); + /* operation = get the watchdog's status */ + int (*get_status)(struct watchdog_device *, int *); + /* operation = get the time left before reboot */ + int (*get_timeleft)(struct watchdog_device *, int *); +}; + +struct watchdog_device { + unsigned char name[32]; /* The watchdog's 'identity' */ + unsigned long options; /* The supported capabilities/options */ + unsigned long firmware; /* The Watchdog's Firmware version */ + int nowayout; /* The nowayout setting for this watchdog */ + int heartbeat; /* The watchdog's heartbeat */ + int bootstatus; /* The watchdog's bootstatus */ + struct watchdog_ops *watchdog_ops; /* link to watchdog_ops */ + + /* watchdog status (register/unregister) state machine */ + enum { WATCHDOG_UNINITIALIZED = 0, + WATCHDOG_REGISTERED, /* completed register_watchdogdevice */ + WATCHDOG_STARTED, /* watchdog device started */ + WATCHDOG_STOPPED, /* watchdog device stopped */ + WATCHDOG_UNREGISTERED, /* completed unregister_watchdogdevice */ + } watchdog_state; + + /* From here on everything is device dependent */ + void *private; +}; + +/* drivers/watchdog/watchdog_core.c */ +extern struct watchdog_device *alloc_watchdogdev(void); +extern int register_watchdogdevice(struct watchdog_device *, struct device *); +extern int unregister_watchdogdevice(struct watchdog_device *); +extern int free_watchdogdev(struct watchdog_device *); + #endif /* __KERNEL__ */ #endif /* ifndef _LINUX_WATCHDOG_H */