1/* 2 * W83877F Computer Watchdog Timer driver for Linux 2.4.x 3 * 4 * Based on acquirewdt.c by Alan Cox, 5 * and sbc60xxwdt.c by Jakob Oestergaard <jakob@ostenfeld.dk> 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 10 * 2 of the License, or (at your option) any later version. 11 * 12 * The authors do NOT admit liability nor provide warranty for 13 * any of this software. This material is provided "AS-IS" in 14 * the hope that it may be useful for others. 15 * 16 * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net> 17 * 18 * 4/19 - 2001 [Initial revision] 19 * 9/27 - 2001 Added spinlocking 20 * 21 * 22 * Theory of operation: 23 * A Watchdog Timer (WDT) is a hardware circuit that can 24 * reset the computer system in case of a software fault. 25 * You probably knew that already. 26 * 27 * Usually a userspace daemon will notify the kernel WDT driver 28 * via the /proc/watchdog special device file that userspace is 29 * still alive, at regular intervals. When such a notification 30 * occurs, the driver will usually tell the hardware watchdog 31 * that everything is in order, and that the watchdog should wait 32 * for yet another little while to reset the system. 33 * If userspace fails (RAM error, kernel bug, whatever), the 34 * notifications cease to occur, and the hardware watchdog will 35 * reset the system (causing a reboot) after the timeout occurs. 36 * 37 * This WDT driver is different from most other Linux WDT 38 * drivers in that the driver will ping the watchdog by itself, 39 * because this particular WDT has a very short timeout (1.6 40 * seconds) and it would be insane to count on any userspace 41 * daemon always getting scheduled within that time frame. 42 */ 43 44#include <linux/module.h> 45#include <linux/version.h> 46#include <linux/types.h> 47#include <linux/errno.h> 48#include <linux/kernel.h> 49#include <linux/timer.h> 50#include <linux/sched.h> 51#include <linux/miscdevice.h> 52#include <linux/watchdog.h> 53#include <linux/slab.h> 54#include <linux/ioport.h> 55#include <linux/fcntl.h> 56#include <linux/smp_lock.h> 57#include <asm/io.h> 58#include <asm/uaccess.h> 59#include <asm/system.h> 60#include <linux/notifier.h> 61#include <linux/reboot.h> 62#include <linux/init.h> 63 64#define OUR_NAME "w83877f_wdt" 65 66#define ENABLE_W83877F_PORT 0x3F0 67#define ENABLE_W83877F 0x87 68#define DISABLE_W83877F 0xAA 69#define WDT_PING 0x443 70#define WDT_REGISTER 0x14 71#define WDT_ENABLE 0x9C 72#define WDT_DISABLE 0x8C 73 74/* 75 * The W83877F seems to be fixed at 1.6s timeout (at least on the 76 * EMACS PC-104 board I'm using). If we reset the watchdog every 77 * ~250ms we should be safe. */ 78 79#define WDT_INTERVAL (HZ/4+1) 80 81/* 82 * We must not require too good response from the userspace daemon. 83 * Here we require the userspace daemon to send us a heartbeat 84 * char to /dev/watchdog every 30 seconds. 85 */ 86 87#define WDT_HEARTBEAT (HZ * 30) 88 89static void wdt_timer_ping(unsigned long); 90static struct timer_list timer; 91static unsigned long next_heartbeat; 92static unsigned long wdt_is_open; 93static int wdt_expect_close; 94static spinlock_t wdt_spinlock; 95 96/* 97 * Whack the dog 98 */ 99 100static void wdt_timer_ping(unsigned long data) 101{ 102 /* If we got a heartbeat pulse within the WDT_US_INTERVAL 103 * we agree to ping the WDT 104 */ 105 if(time_before(jiffies, next_heartbeat)) 106 { 107 /* Ping the WDT */ 108 spin_lock(&wdt_spinlock); 109 110 /* Ping the WDT by reading from WDT_PING */ 111 inb_p(WDT_PING); 112 113 /* Re-set the timer interval */ 114 timer.expires = jiffies + WDT_INTERVAL; 115 add_timer(&timer); 116 117 spin_unlock(&wdt_spinlock); 118 119 } else { 120 printk(OUR_NAME ": Heartbeat lost! Will not ping the watchdog\n"); 121 } 122} 123 124/* 125 * Utility routines 126 */ 127 128static void wdt_change(int writeval) 129{ 130 unsigned long flags; 131 spin_lock_irqsave(&wdt_spinlock, flags); 132 133 /* buy some time */ 134 inb_p(WDT_PING); 135 136 /* make W83877F available */ 137 outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT); 138 outb_p(ENABLE_W83877F, ENABLE_W83877F_PORT); 139 140 /* enable watchdog */ 141 outb_p(WDT_REGISTER, ENABLE_W83877F_PORT); 142 outb_p(writeval, ENABLE_W83877F_PORT+1); 143 144 /* lock the W8387FF away */ 145 outb_p(DISABLE_W83877F, ENABLE_W83877F_PORT); 146 147 spin_unlock_irqrestore(&wdt_spinlock, flags); 148} 149 150static void wdt_startup(void) 151{ 152 next_heartbeat = jiffies + WDT_HEARTBEAT; 153 154 /* Start the timer */ 155 timer.expires = jiffies + WDT_INTERVAL; 156 add_timer(&timer); 157 158 wdt_change(WDT_ENABLE); 159 160 printk(OUR_NAME ": Watchdog timer is now enabled.\n"); 161} 162 163static void wdt_turnoff(void) 164{ 165 /* Stop the timer */ 166 del_timer(&timer); 167 168 wdt_change(WDT_DISABLE); 169 170 printk(OUR_NAME ": Watchdog timer is now disabled...\n"); 171} 172 173 174/* 175 * /dev/watchdog handling 176 */ 177 178static ssize_t fop_write(struct file * file, const char * buf, size_t count, loff_t * ppos) 179{ 180 /* We can't seek */ 181 if(ppos != &file->f_pos) 182 return -ESPIPE; 183 184 /* See if we got the magic character */ 185 if(count) 186 { 187 size_t ofs; 188 189 /* note: just in case someone wrote the magic character 190 * five months ago... */ 191 wdt_expect_close = 0; 192 193 /* now scan */ 194 for(ofs = 0; ofs != count; ofs++) 195 { 196 char c; 197 if(get_user(c, buf + ofs)) 198 return -EFAULT; 199 if(c == 'V') 200 wdt_expect_close = 1; 201 } 202 203 /* someone wrote to us, we should restart timer */ 204 next_heartbeat = jiffies + WDT_HEARTBEAT; 205 return 1; 206 }; 207 return 0; 208} 209 210static ssize_t fop_read(struct file * file, char * buf, size_t count, loff_t * ppos) 211{ 212 /* No can do */ 213 return -EINVAL; 214} 215 216static int fop_open(struct inode * inode, struct file * file) 217{ 218 switch(MINOR(inode->i_rdev)) 219 { 220 case WATCHDOG_MINOR: 221 /* Just in case we're already talking to someone... */ 222 if(test_and_set_bit(0, &wdt_is_open)) { 223 spin_unlock(&wdt_spinlock); 224 return -EBUSY; 225 } 226 /* Good, fire up the show */ 227 wdt_startup(); 228 return 0; 229 230 default: 231 return -ENODEV; 232 } 233} 234 235static int fop_close(struct inode * inode, struct file * file) 236{ 237 if(MINOR(inode->i_rdev) == WATCHDOG_MINOR) 238 { 239 if(wdt_expect_close) 240 wdt_turnoff(); 241 else { 242 del_timer(&timer); 243 printk(OUR_NAME ": device file closed unexpectedly. Will not stop the WDT!\n"); 244 } 245 } 246 clear_bit(0, &wdt_is_open); 247 return 0; 248} 249 250static int fop_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) 251{ 252 static struct watchdog_info ident= 253 { 254 WDIOF_MAGICCLOSE, 255 1, 256 "W83877F" 257 }; 258 259 switch(cmd) 260 { 261 default: 262 return -ENOIOCTLCMD; 263 case WDIOC_GETSUPPORT: 264 return copy_to_user((struct watchdog_info *)arg, &ident, sizeof(ident))?-EFAULT:0; 265 case WDIOC_KEEPALIVE: 266 next_heartbeat = jiffies + WDT_HEARTBEAT; 267 return 0; 268 } 269} 270 271static struct file_operations wdt_fops = { 272 owner: THIS_MODULE, 273 llseek: no_llseek, 274 read: fop_read, 275 write: fop_write, 276 open: fop_open, 277 release: fop_close, 278 ioctl: fop_ioctl 279}; 280 281static struct miscdevice wdt_miscdev = { 282 WATCHDOG_MINOR, 283 "watchdog", 284 &wdt_fops 285}; 286 287/* 288 * Notifier for system down 289 */ 290 291static int wdt_notify_sys(struct notifier_block *this, unsigned long code, 292 void *unused) 293{ 294 if(code==SYS_DOWN || code==SYS_HALT) 295 wdt_turnoff(); 296 return NOTIFY_DONE; 297} 298 299/* 300 * The WDT needs to learn about soft shutdowns in order to 301 * turn the timebomb registers off. 302 */ 303 304static struct notifier_block wdt_notifier= 305{ 306 wdt_notify_sys, 307 0, 308 0 309}; 310 311static void __exit w83877f_wdt_unload(void) 312{ 313 wdt_turnoff(); 314 315 /* Deregister */ 316 misc_deregister(&wdt_miscdev); 317 318 unregister_reboot_notifier(&wdt_notifier); 319 release_region(WDT_PING,1); 320 release_region(ENABLE_W83877F_PORT,2); 321} 322 323static int __init w83877f_wdt_init(void) 324{ 325 int rc = -EBUSY; 326 327 spin_lock_init(&wdt_spinlock); 328 329 if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) 330 goto err_out; 331 if (!request_region(WDT_PING, 1, "W8387FF WDT")) 332 goto err_out_region1; 333 334 init_timer(&timer); 335 timer.function = wdt_timer_ping; 336 timer.data = 0; 337 338 rc = misc_register(&wdt_miscdev); 339 if (rc) 340 goto err_out_region2; 341 342 rc = register_reboot_notifier(&wdt_notifier); 343 if (rc) 344 goto err_out_miscdev; 345 346 printk(KERN_INFO OUR_NAME ": WDT driver for W83877F initialised.\n"); 347 348 return 0; 349 350err_out_miscdev: 351 misc_deregister(&wdt_miscdev); 352err_out_region2: 353 release_region(WDT_PING,1); 354err_out_region1: 355 release_region(ENABLE_W83877F_PORT,2); 356err_out: 357 return rc; 358} 359 360module_init(w83877f_wdt_init); 361module_exit(w83877f_wdt_unload); 362 363MODULE_AUTHOR("Scott and Bill Jennings"); 364MODULE_DESCRIPTION("Driver for watchdog timer in w83877f chip"); 365MODULE_LICENSE("GPL"); 366EXPORT_NO_SYMBOLS; 367