Blame view
drivers/watchdog/w83877f_wdt.c
10.1 KB
1da177e4c Linux-2.6.12-rc2 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/* * 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 |
c1cfd1a2f [WATCHDOG 51/57] ... |
26 27 28 29 |
* changed watchdog_info to correctly reflect what * the driver offers * added WDIOC_GETSTATUS, WDIOC_GETBOOTSTATUS, * WDIOC_SETTIMEOUT, |
1da177e4c Linux-2.6.12-rc2 |
30 31 32 33 |
* 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 |
c1cfd1a2f [WATCHDOG 51/57] ... |
34 35 |
* made timeout (the emulated heartbeat) a * module_param |
1da177e4c Linux-2.6.12-rc2 |
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
* 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> |
c1cfd1a2f [WATCHDOG 51/57] ... |
57 58 |
#include <linux/io.h> #include <linux/uaccess.h> |
1da177e4c Linux-2.6.12-rc2 |
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
#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 */ |
c1cfd1a2f [WATCHDOG 51/57] ... |
86 87 |
/* in seconds, will be multiplied by HZ to get seconds to wait for a ping */ static int timeout = WATCHDOG_TIMEOUT; |
1da177e4c Linux-2.6.12-rc2 |
88 |
module_param(timeout, int, 0); |
c1cfd1a2f [WATCHDOG 51/57] ... |
89 90 91 |
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1<=timeout<=3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); |
1da177e4c Linux-2.6.12-rc2 |
92 |
|
4bfdf3783 [PATCH] consolida... |
93 |
static int nowayout = WATCHDOG_NOWAYOUT; |
1da177e4c Linux-2.6.12-rc2 |
94 |
module_param(nowayout, int, 0); |
c1cfd1a2f [WATCHDOG 51/57] ... |
95 96 97 |
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); |
1da177e4c Linux-2.6.12-rc2 |
98 99 |
static void wdt_timer_ping(unsigned long); |
82eb7c505 [WATCHDOG] timers... |
100 |
static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0); |
1da177e4c Linux-2.6.12-rc2 |
101 102 103 |
static unsigned long next_heartbeat; static unsigned long wdt_is_open; static char wdt_expect_close; |
c7dfd0cca [WATCHDOG] spin_l... |
104 |
static DEFINE_SPINLOCK(wdt_spinlock); |
1da177e4c Linux-2.6.12-rc2 |
105 106 107 108 109 110 111 112 113 114 |
/* * 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 */ |
c1cfd1a2f [WATCHDOG 51/57] ... |
115 |
if (time_before(jiffies, next_heartbeat)) { |
1da177e4c Linux-2.6.12-rc2 |
116 117 118 119 120 121 122 |
/* Ping the WDT */ spin_lock(&wdt_spinlock); /* Ping the WDT by reading from WDT_PING */ inb_p(WDT_PING); /* Re-set the timer interval */ |
82eb7c505 [WATCHDOG] timers... |
123 |
mod_timer(&timer, jiffies + WDT_INTERVAL); |
1da177e4c Linux-2.6.12-rc2 |
124 125 |
spin_unlock(&wdt_spinlock); |
c1cfd1a2f [WATCHDOG 51/57] ... |
126 127 128 129 |
} else printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog "); |
1da177e4c Linux-2.6.12-rc2 |
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
} /* * 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 */ |
82eb7c505 [WATCHDOG] timers... |
163 |
mod_timer(&timer, jiffies + WDT_INTERVAL); |
1da177e4c Linux-2.6.12-rc2 |
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
wdt_change(WDT_ENABLE); printk(KERN_INFO PFX "Watchdog timer is now enabled. "); } static void wdt_turnoff(void) { /* Stop the timer */ del_timer(&timer); wdt_change(WDT_DISABLE); printk(KERN_INFO PFX "Watchdog timer is now disabled... "); } static void wdt_keepalive(void) { /* user land ping */ next_heartbeat = jiffies + (timeout * HZ); } /* * /dev/watchdog handling */ |
c1cfd1a2f [WATCHDOG 51/57] ... |
191 192 |
static ssize_t fop_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) |
1da177e4c Linux-2.6.12-rc2 |
193 194 |
{ /* See if we got the magic character 'V' and reload the timer */ |
c1cfd1a2f [WATCHDOG 51/57] ... |
195 196 |
if (count) { if (!nowayout) { |
1da177e4c Linux-2.6.12-rc2 |
197 |
size_t ofs; |
c1cfd1a2f [WATCHDOG 51/57] ... |
198 199 |
/* note: just in case someone wrote the magic character five months ago... */ |
1da177e4c Linux-2.6.12-rc2 |
200 |
wdt_expect_close = 0; |
c1cfd1a2f [WATCHDOG 51/57] ... |
201 202 203 |
/* scan to see whether or not we got the magic character */ for (ofs = 0; ofs != count; ofs++) { |
1da177e4c Linux-2.6.12-rc2 |
204 205 206 207 208 209 210 211 212 213 214 215 216 |
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; } |
c1cfd1a2f [WATCHDOG 51/57] ... |
217 |
static int fop_open(struct inode *inode, struct file *file) |
1da177e4c Linux-2.6.12-rc2 |
218 219 |
{ /* Just in case we're already talking to someone... */ |
c1cfd1a2f [WATCHDOG 51/57] ... |
220 |
if (test_and_set_bit(0, &wdt_is_open)) |
1da177e4c Linux-2.6.12-rc2 |
221 222 223 224 225 226 |
return -EBUSY; /* Good, fire up the show */ wdt_startup(); return nonseekable_open(inode, file); } |
c1cfd1a2f [WATCHDOG 51/57] ... |
227 |
static int fop_close(struct inode *inode, struct file *file) |
1da177e4c Linux-2.6.12-rc2 |
228 |
{ |
c1cfd1a2f [WATCHDOG 51/57] ... |
229 |
if (wdt_expect_close == 42) |
1da177e4c Linux-2.6.12-rc2 |
230 231 232 |
wdt_turnoff(); else { del_timer(&timer); |
c1cfd1a2f [WATCHDOG 51/57] ... |
233 234 235 |
printk(KERN_CRIT PFX "device file closed unexpectedly. Will not stop the WDT! "); |
1da177e4c Linux-2.6.12-rc2 |
236 237 238 239 240 |
} clear_bit(0, &wdt_is_open); wdt_expect_close = 0; return 0; } |
c1cfd1a2f [WATCHDOG 51/57] ... |
241 |
static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
1da177e4c Linux-2.6.12-rc2 |
242 243 244 |
{ void __user *argp = (void __user *)arg; int __user *p = argp; |
c1cfd1a2f [WATCHDOG 51/57] ... |
245 246 247 |
static const struct watchdog_info ident = { .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, |
1da177e4c Linux-2.6.12-rc2 |
248 249 250 |
.firmware_version = 1, .identity = "W83877F", }; |
c1cfd1a2f [WATCHDOG 51/57] ... |
251 |
switch (cmd) { |
c1cfd1a2f [WATCHDOG 51/57] ... |
252 253 254 255 256 |
case WDIOC_GETSUPPORT: return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: return put_user(0, p); |
c1cfd1a2f [WATCHDOG 51/57] ... |
257 |
case WDIOC_SETOPTIONS: |
1da177e4c Linux-2.6.12-rc2 |
258 |
{ |
c1cfd1a2f [WATCHDOG 51/57] ... |
259 |
int new_options, retval = -EINVAL; |
1da177e4c Linux-2.6.12-rc2 |
260 |
|
c1cfd1a2f [WATCHDOG 51/57] ... |
261 262 |
if (get_user(new_options, p)) return -EFAULT; |
1da177e4c Linux-2.6.12-rc2 |
263 |
|
c1cfd1a2f [WATCHDOG 51/57] ... |
264 265 266 |
if (new_options & WDIOS_DISABLECARD) { wdt_turnoff(); retval = 0; |
1da177e4c Linux-2.6.12-rc2 |
267 |
} |
1da177e4c Linux-2.6.12-rc2 |
268 |
|
c1cfd1a2f [WATCHDOG 51/57] ... |
269 270 271 272 |
if (new_options & WDIOS_ENABLECARD) { wdt_startup(); retval = 0; } |
1da177e4c Linux-2.6.12-rc2 |
273 |
|
c1cfd1a2f [WATCHDOG 51/57] ... |
274 275 |
return retval; } |
0c06090c9 [WATCHDOG] Coding... |
276 277 278 |
case WDIOC_KEEPALIVE: wdt_keepalive(); return 0; |
c1cfd1a2f [WATCHDOG 51/57] ... |
279 280 281 |
case WDIOC_SETTIMEOUT: { int new_timeout; |
1da177e4c Linux-2.6.12-rc2 |
282 |
|
c1cfd1a2f [WATCHDOG 51/57] ... |
283 284 285 286 287 288 289 290 291 292 293 294 295 |
if (get_user(new_timeout, p)) return -EFAULT; /* arbitrary upper limit */ if (new_timeout < 1 || new_timeout > 3600) return -EINVAL; timeout = new_timeout; wdt_keepalive(); /* Fall through */ } case WDIOC_GETTIMEOUT: return put_user(timeout, p); |
0c06090c9 [WATCHDOG] Coding... |
296 297 |
default: return -ENOTTY; |
1da177e4c Linux-2.6.12-rc2 |
298 299 |
} } |
62322d255 [PATCH] make more... |
300 |
static const struct file_operations wdt_fops = { |
1da177e4c Linux-2.6.12-rc2 |
301 302 303 304 305 |
.owner = THIS_MODULE, .llseek = no_llseek, .write = fop_write, .open = fop_open, .release = fop_close, |
c1cfd1a2f [WATCHDOG 51/57] ... |
306 |
.unlocked_ioctl = fop_ioctl, |
1da177e4c Linux-2.6.12-rc2 |
307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
}; 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) { |
c1cfd1a2f [WATCHDOG 51/57] ... |
322 |
if (code == SYS_DOWN || code == SYS_HALT) |
1da177e4c Linux-2.6.12-rc2 |
323 324 325 326 327 328 329 330 |
wdt_turnoff(); return NOTIFY_DONE; } /* * The WDT needs to learn about soft shutdowns in order to * turn the timebomb registers off. */ |
c1cfd1a2f [WATCHDOG 51/57] ... |
331 |
static struct notifier_block wdt_notifier = { |
1da177e4c Linux-2.6.12-rc2 |
332 333 334 335 336 337 338 339 340 341 342 |
.notifier_call = wdt_notify_sys, }; static void __exit w83877f_wdt_unload(void) { wdt_turnoff(); /* Deregister */ misc_deregister(&wdt_miscdev); unregister_reboot_notifier(&wdt_notifier); |
c1cfd1a2f [WATCHDOG 51/57] ... |
343 344 |
release_region(WDT_PING, 1); release_region(ENABLE_W83877F_PORT, 2); |
1da177e4c Linux-2.6.12-rc2 |
345 346 347 348 349 |
} static int __init w83877f_wdt_init(void) { int rc = -EBUSY; |
c1cfd1a2f [WATCHDOG 51/57] ... |
350 |
if (timeout < 1 || timeout > 3600) { /* arbitrary upper limit */ |
1da177e4c Linux-2.6.12-rc2 |
351 |
timeout = WATCHDOG_TIMEOUT; |
c1cfd1a2f [WATCHDOG 51/57] ... |
352 353 354 355 |
printk(KERN_INFO PFX "timeout value must be 1 <= x <= 3600, using %d ", timeout); |
1da177e4c Linux-2.6.12-rc2 |
356 |
} |
c1cfd1a2f [WATCHDOG 51/57] ... |
357 |
if (!request_region(ENABLE_W83877F_PORT, 2, "W83877F WDT")) { |
1da177e4c Linux-2.6.12-rc2 |
358 359 360 361 362 363 |
printk(KERN_ERR PFX "I/O address 0x%04x already in use ", ENABLE_W83877F_PORT); rc = -EIO; goto err_out; } |
c1cfd1a2f [WATCHDOG 51/57] ... |
364 |
if (!request_region(WDT_PING, 1, "W8387FF WDT")) { |
1da177e4c Linux-2.6.12-rc2 |
365 366 367 368 369 370 |
printk(KERN_ERR PFX "I/O address 0x%04x already in use ", WDT_PING); rc = -EIO; goto err_out_region1; } |
c6cb13aea [WATCHDOG] misc_r... |
371 |
rc = register_reboot_notifier(&wdt_notifier); |
c1cfd1a2f [WATCHDOG 51/57] ... |
372 373 374 375 |
if (rc) { printk(KERN_ERR PFX "cannot register reboot notifier (err=%d) ", rc); |
1da177e4c Linux-2.6.12-rc2 |
376 377 |
goto err_out_region2; } |
c6cb13aea [WATCHDOG] misc_r... |
378 |
rc = misc_register(&wdt_miscdev); |
c1cfd1a2f [WATCHDOG 51/57] ... |
379 380 381 382 383 |
if (rc) { printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d) ", wdt_miscdev.minor, rc); |
c6cb13aea [WATCHDOG] misc_r... |
384 |
goto err_out_reboot; |
1da177e4c Linux-2.6.12-rc2 |
385 |
} |
c1cfd1a2f [WATCHDOG 51/57] ... |
386 387 388 |
printk(KERN_INFO PFX "WDT driver for W83877F initialised. timeout=%d sec (nowayout=%d) ", |
1da177e4c Linux-2.6.12-rc2 |
389 390 391 |
timeout, nowayout); return 0; |
c6cb13aea [WATCHDOG] misc_r... |
392 393 |
err_out_reboot: unregister_reboot_notifier(&wdt_notifier); |
1da177e4c Linux-2.6.12-rc2 |
394 |
err_out_region2: |
c1cfd1a2f [WATCHDOG 51/57] ... |
395 |
release_region(WDT_PING, 1); |
1da177e4c Linux-2.6.12-rc2 |
396 |
err_out_region1: |
c1cfd1a2f [WATCHDOG 51/57] ... |
397 |
release_region(ENABLE_W83877F_PORT, 2); |
1da177e4c Linux-2.6.12-rc2 |
398 399 400 401 402 403 404 405 406 407 408 |
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); |