Blame view
drivers/watchdog/sc520_wdt.c
11.1 KB
1da177e4c Linux-2.6.12-rc2 |
1 2 3 |
/* * AMD Elan SC520 processor Watchdog Timer driver * |
143a2e54b [WATCHDOG] More c... |
4 5 |
* Based on acquirewdt.c by Alan Cox, * and sbc60xxwdt.c by Jakob Oestergaard <jakob@unthought.net> |
1da177e4c Linux-2.6.12-rc2 |
6 7 8 9 10 11 12 13 |
* * 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 |
143a2e54b [WATCHDOG] More c... |
14 |
* the hope that it may be useful for others. |
1da177e4c Linux-2.6.12-rc2 |
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 |
* * (c) Copyright 2001 Scott Jennings <linuxdrivers@oro.net> * 9/27 - 2001 [Initial release] * * Additional fixes Alan Cox * - Fixed formatting * - Removed debug printks * - Fixed SMP built kernel deadlock * - Switched to private locks not lock_kernel * - Used ioremap/writew/readw * - Added NOWAYOUT support * 4/12 - 2002 Changes by Rob Radez <rob@osinvestor.com> * - Change comments * - Eliminate fop_llseek * - Change CONFIG_WATCHDOG_NOWAYOUT semantics * - Add KERN_* tags to printks * - fix possible wdt_is_open race * - Report proper capabilities in watchdog_info * - Add WDIOC_{GETSTATUS, GETBOOTSTATUS, SETTIMEOUT, * GETTIMEOUT, SETOPTIONS} ioctls * 09/8 - 2003 Changes by Wim Van Sebroeck <wim@iguana.be> * - cleanup of trailing spaces * - added extra printk's for startup problems * - use module_param * - made timeout (the emulated heartbeat) a module_param * - made the keepalive ping an internal subroutine * 3/27 - 2004 Changes by Sean Young <sean@mess.org> * - set MMCR_BASE to 0xfffef000 * - CBAR does not need to be read * - removed debugging printks * * This WDT driver is different from most other Linux WDT * drivers in that the driver will ping the watchdog by itself, * because this particular WDT has a very short timeout (1.6 * seconds) and it would be insane to count on any userspace * daemon always getting scheduled within that time frame. * * This driver uses memory mapped IO, and spinlock. */ #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/types.h> #include <linux/timer.h> #include <linux/miscdevice.h> #include <linux/watchdog.h> #include <linux/fs.h> #include <linux/ioport.h> #include <linux/notifier.h> #include <linux/reboot.h> #include <linux/init.h> |
4e57b6817 [PATCH] fix missi... |
66 |
#include <linux/jiffies.h> |
ff9480605 [WATCHDOG 43/57] ... |
67 68 |
#include <linux/io.h> #include <linux/uaccess.h> |
1da177e4c Linux-2.6.12-rc2 |
69 |
|
1da177e4c Linux-2.6.12-rc2 |
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
#include <asm/system.h> #define OUR_NAME "sc520_wdt" #define PFX OUR_NAME ": " /* * The AMD Elan SC520 timeout value is 492us times a power of 2 (0-7) * * 0: 492us 2: 1.01s 4: 4.03s 6: 16.22s * 1: 503ms 3: 2.01s 5: 8.05s 7: 32.21s * * We will program the SC520 watchdog for a timeout of 2.01s. * If we reset the watchdog every ~250ms we should be safe. */ #define WDT_INTERVAL (HZ/4+1) /* * We must not require too good response from the userspace daemon. * Here we require the userspace daemon to send us a heartbeat * char to /dev/watchdog every 30 seconds. */ #define WATCHDOG_TIMEOUT 30 /* 30 sec default timeout */ |
ff9480605 [WATCHDOG 43/57] ... |
94 95 |
/* 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 |
96 |
module_param(timeout, int, 0); |
ff9480605 [WATCHDOG 43/57] ... |
97 98 99 |
MODULE_PARM_DESC(timeout, "Watchdog timeout in seconds. (1 <= timeout <= 3600, default=" __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); |
1da177e4c Linux-2.6.12-rc2 |
100 |
|
4bfdf3783 [PATCH] consolida... |
101 |
static int nowayout = WATCHDOG_NOWAYOUT; |
1da177e4c Linux-2.6.12-rc2 |
102 |
module_param(nowayout, int, 0); |
ff9480605 [WATCHDOG 43/57] ... |
103 104 105 |
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); |
1da177e4c Linux-2.6.12-rc2 |
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
/* * AMD Elan SC520 - Watchdog Timer Registers */ #define MMCR_BASE 0xfffef000 /* The default base address */ #define OFFS_WDTMRCTL 0xCB0 /* Watchdog Timer Control Register */ /* WDT Control Register bit definitions */ #define WDT_EXP_SEL_01 0x0001 /* [01] Time-out = 496 us (with 33 Mhz clk). */ #define WDT_EXP_SEL_02 0x0002 /* [02] Time-out = 508 ms (with 33 Mhz clk). */ #define WDT_EXP_SEL_03 0x0004 /* [03] Time-out = 1.02 s (with 33 Mhz clk). */ #define WDT_EXP_SEL_04 0x0008 /* [04] Time-out = 2.03 s (with 33 Mhz clk). */ #define WDT_EXP_SEL_05 0x0010 /* [05] Time-out = 4.07 s (with 33 Mhz clk). */ #define WDT_EXP_SEL_06 0x0020 /* [06] Time-out = 8.13 s (with 33 Mhz clk). */ #define WDT_EXP_SEL_07 0x0040 /* [07] Time-out = 16.27s (with 33 Mhz clk). */ #define WDT_EXP_SEL_08 0x0080 /* [08] Time-out = 32.54s (with 33 Mhz clk). */ #define WDT_IRQ_FLG 0x1000 /* [12] Interrupt Request Flag */ #define WDT_WRST_ENB 0x4000 /* [14] Watchdog Timer Reset Enable */ #define WDT_ENB 0x8000 /* [15] Watchdog Timer Enable */ static __u16 __iomem *wdtmrctl; static void wdt_timer_ping(unsigned long); |
82eb7c505 [WATCHDOG] timers... |
129 |
static DEFINE_TIMER(timer, wdt_timer_ping, 0, 0); |
1da177e4c Linux-2.6.12-rc2 |
130 131 132 |
static unsigned long next_heartbeat; static unsigned long wdt_is_open; static char wdt_expect_close; |
c7dfd0cca [WATCHDOG] spin_l... |
133 |
static DEFINE_SPINLOCK(wdt_spinlock); |
1da177e4c Linux-2.6.12-rc2 |
134 135 136 137 138 139 140 141 142 143 |
/* * 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 */ |
ff9480605 [WATCHDOG 43/57] ... |
144 |
if (time_before(jiffies, next_heartbeat)) { |
1da177e4c Linux-2.6.12-rc2 |
145 146 147 148 149 150 151 |
/* Ping the WDT */ spin_lock(&wdt_spinlock); writew(0xAAAA, wdtmrctl); writew(0x5555, wdtmrctl); spin_unlock(&wdt_spinlock); /* Re-set the timer interval */ |
82eb7c505 [WATCHDOG] timers... |
152 |
mod_timer(&timer, jiffies + WDT_INTERVAL); |
ff9480605 [WATCHDOG 43/57] ... |
153 154 155 156 |
} else printk(KERN_WARNING PFX "Heartbeat lost! Will not ping the watchdog "); |
1da177e4c Linux-2.6.12-rc2 |
157 158 159 160 161 162 163 164 165 166 167 168 169 |
} /* * Utility routines */ static void wdt_config(int writeval) { __u16 dummy; unsigned long flags; /* buy some time (ping) */ spin_lock_irqsave(&wdt_spinlock, flags); |
ff9480605 [WATCHDOG 43/57] ... |
170 |
dummy = readw(wdtmrctl); /* ensure write synchronization */ |
1da177e4c Linux-2.6.12-rc2 |
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 |
writew(0xAAAA, wdtmrctl); writew(0x5555, wdtmrctl); /* unlock WDT = make WDT configuration register writable one time */ writew(0x3333, wdtmrctl); writew(0xCCCC, wdtmrctl); /* write WDT configuration register */ writew(writeval, wdtmrctl); spin_unlock_irqrestore(&wdt_spinlock, flags); } static int wdt_startup(void) { next_heartbeat = jiffies + (timeout * HZ); /* Start the timer */ |
82eb7c505 [WATCHDOG] timers... |
186 |
mod_timer(&timer, jiffies + WDT_INTERVAL); |
1da177e4c Linux-2.6.12-rc2 |
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
/* Start the watchdog */ wdt_config(WDT_ENB | WDT_WRST_ENB | WDT_EXP_SEL_04); printk(KERN_INFO PFX "Watchdog timer is now enabled. "); return 0; } static int wdt_turnoff(void) { /* Stop the timer */ del_timer(&timer); /* Stop the watchdog */ wdt_config(0); printk(KERN_INFO PFX "Watchdog timer is now disabled... "); return 0; } static int wdt_keepalive(void) { /* user land ping */ next_heartbeat = jiffies + (timeout * HZ); return 0; } static int wdt_set_heartbeat(int t) { if ((t < 1) || (t > 3600)) /* arbitrary upper limit */ return -EINVAL; timeout = t; return 0; } /* * /dev/watchdog handling */ |
ff9480605 [WATCHDOG 43/57] ... |
228 229 |
static ssize_t fop_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) |
1da177e4c Linux-2.6.12-rc2 |
230 231 |
{ /* See if we got the magic character 'V' and reload the timer */ |
ff9480605 [WATCHDOG 43/57] ... |
232 |
if (count) { |
1da177e4c Linux-2.6.12-rc2 |
233 234 235 236 237 238 239 240 |
if (!nowayout) { size_t ofs; /* note: just in case someone wrote the magic character * five months ago... */ wdt_expect_close = 0; /* now scan */ |
ff9480605 [WATCHDOG 43/57] ... |
241 |
for (ofs = 0; ofs != count; ofs++) { |
1da177e4c Linux-2.6.12-rc2 |
242 243 244 |
char c; if (get_user(c, buf + ofs)) return -EFAULT; |
ff9480605 [WATCHDOG 43/57] ... |
245 |
if (c == 'V') |
1da177e4c Linux-2.6.12-rc2 |
246 247 248 |
wdt_expect_close = 42; } } |
ff9480605 [WATCHDOG 43/57] ... |
249 250 |
/* Well, anyhow someone wrote to us, we should return that favour */ |
1da177e4c Linux-2.6.12-rc2 |
251 252 253 254 |
wdt_keepalive(); } return count; } |
ff9480605 [WATCHDOG 43/57] ... |
255 |
static int fop_open(struct inode *inode, struct file *file) |
1da177e4c Linux-2.6.12-rc2 |
256 |
{ |
1da177e4c Linux-2.6.12-rc2 |
257 |
/* Just in case we're already talking to someone... */ |
ff9480605 [WATCHDOG 43/57] ... |
258 |
if (test_and_set_bit(0, &wdt_is_open)) |
1da177e4c Linux-2.6.12-rc2 |
259 260 261 262 263 264 |
return -EBUSY; if (nowayout) __module_get(THIS_MODULE); /* Good, fire up the show */ wdt_startup(); |
6abe78bf1 [WATCHDOG] Return... |
265 |
return nonseekable_open(inode, file); |
1da177e4c Linux-2.6.12-rc2 |
266 |
} |
ff9480605 [WATCHDOG 43/57] ... |
267 |
static int fop_close(struct inode *inode, struct file *file) |
1da177e4c Linux-2.6.12-rc2 |
268 |
{ |
ff9480605 [WATCHDOG 43/57] ... |
269 |
if (wdt_expect_close == 42) |
1da177e4c Linux-2.6.12-rc2 |
270 |
wdt_turnoff(); |
ff9480605 [WATCHDOG 43/57] ... |
271 272 273 274 |
else { printk(KERN_CRIT PFX "Unexpected close, not stopping watchdog! "); |
1da177e4c Linux-2.6.12-rc2 |
275 276 277 278 279 280 |
wdt_keepalive(); } clear_bit(0, &wdt_is_open); wdt_expect_close = 0; return 0; } |
9b748ed03 [WATCHDOG 44/57] ... |
281 |
static long fop_ioctl(struct file *file, unsigned int cmd, unsigned long arg) |
1da177e4c Linux-2.6.12-rc2 |
282 283 284 |
{ void __user *argp = (void __user *)arg; int __user *p = argp; |
ff9480605 [WATCHDOG 43/57] ... |
285 286 287 |
static const struct watchdog_info ident = { .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | WDIOF_MAGICCLOSE, |
1da177e4c Linux-2.6.12-rc2 |
288 289 290 |
.firmware_version = 1, .identity = "SC520", }; |
5eb82498e [WATCHDOG] Coding... |
291 |
switch (cmd) { |
ff9480605 [WATCHDOG 43/57] ... |
292 293 294 295 296 |
case WDIOC_GETSUPPORT: return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; case WDIOC_GETSTATUS: case WDIOC_GETBOOTSTATUS: return put_user(0, p); |
ff9480605 [WATCHDOG 43/57] ... |
297 298 299 |
case WDIOC_SETOPTIONS: { int new_options, retval = -EINVAL; |
1da177e4c Linux-2.6.12-rc2 |
300 |
|
ff9480605 [WATCHDOG 43/57] ... |
301 302 303 304 305 306 307 |
if (get_user(new_options, p)) return -EFAULT; if (new_options & WDIOS_DISABLECARD) { wdt_turnoff(); retval = 0; } |
1da177e4c Linux-2.6.12-rc2 |
308 |
|
ff9480605 [WATCHDOG 43/57] ... |
309 310 311 |
if (new_options & WDIOS_ENABLECARD) { wdt_startup(); retval = 0; |
1da177e4c Linux-2.6.12-rc2 |
312 |
} |
1da177e4c Linux-2.6.12-rc2 |
313 |
|
ff9480605 [WATCHDOG 43/57] ... |
314 315 |
return retval; } |
0c06090c9 [WATCHDOG] Coding... |
316 317 318 |
case WDIOC_KEEPALIVE: wdt_keepalive(); return 0; |
ff9480605 [WATCHDOG 43/57] ... |
319 320 321 |
case WDIOC_SETTIMEOUT: { int new_timeout; |
1da177e4c Linux-2.6.12-rc2 |
322 |
|
ff9480605 [WATCHDOG 43/57] ... |
323 324 |
if (get_user(new_timeout, p)) return -EFAULT; |
1da177e4c Linux-2.6.12-rc2 |
325 |
|
ff9480605 [WATCHDOG 43/57] ... |
326 327 328 329 330 331 332 333 |
if (wdt_set_heartbeat(new_timeout)) return -EINVAL; wdt_keepalive(); /* Fall through */ } case WDIOC_GETTIMEOUT: return put_user(timeout, p); |
0c06090c9 [WATCHDOG] Coding... |
334 335 |
default: return -ENOTTY; |
1da177e4c Linux-2.6.12-rc2 |
336 337 |
} } |
62322d255 [PATCH] make more... |
338 |
static const struct file_operations wdt_fops = { |
1da177e4c Linux-2.6.12-rc2 |
339 340 341 342 343 |
.owner = THIS_MODULE, .llseek = no_llseek, .write = fop_write, .open = fop_open, .release = fop_close, |
9b748ed03 [WATCHDOG 44/57] ... |
344 |
.unlocked_ioctl = fop_ioctl, |
1da177e4c Linux-2.6.12-rc2 |
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
}; 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) { |
ff9480605 [WATCHDOG 43/57] ... |
360 |
if (code == SYS_DOWN || code == SYS_HALT) |
1da177e4c Linux-2.6.12-rc2 |
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
wdt_turnoff(); return NOTIFY_DONE; } /* * The WDT needs to learn about soft shutdowns in order to * turn the timebomb registers off. */ static struct notifier_block wdt_notifier = { .notifier_call = wdt_notify_sys, }; static void __exit sc520_wdt_unload(void) { if (!nowayout) wdt_turnoff(); /* Deregister */ misc_deregister(&wdt_miscdev); unregister_reboot_notifier(&wdt_notifier); iounmap(wdtmrctl); } static int __init sc520_wdt_init(void) { int rc = -EBUSY; |
ff9480605 [WATCHDOG 43/57] ... |
388 389 |
/* Check that the timeout value is within it's range ; if not reset to the default */ |
1da177e4c Linux-2.6.12-rc2 |
390 391 |
if (wdt_set_heartbeat(timeout)) { wdt_set_heartbeat(WATCHDOG_TIMEOUT); |
ff9480605 [WATCHDOG 43/57] ... |
392 393 394 395 |
printk(KERN_INFO PFX "timeout value must be 1 <= timeout <= 3600, using %d ", WATCHDOG_TIMEOUT); |
1da177e4c Linux-2.6.12-rc2 |
396 |
} |
cef153a8d watchdog: sc520_w... |
397 |
wdtmrctl = ioremap(MMCR_BASE + OFFS_WDTMRCTL, 2); |
1da177e4c Linux-2.6.12-rc2 |
398 399 400 401 402 403 404 405 406 |
if (!wdtmrctl) { printk(KERN_ERR PFX "Unable to remap memory "); rc = -ENOMEM; goto err_out_region2; } rc = register_reboot_notifier(&wdt_notifier); if (rc) { |
ff9480605 [WATCHDOG 43/57] ... |
407 408 409 |
printk(KERN_ERR PFX "cannot register reboot notifier (err=%d) ", rc); |
1da177e4c Linux-2.6.12-rc2 |
410 411 412 413 414 |
goto err_out_ioremap; } rc = misc_register(&wdt_miscdev); if (rc) { |
ff9480605 [WATCHDOG 43/57] ... |
415 416 417 418 |
printk(KERN_ERR PFX "cannot register miscdev on minor=%d (err=%d) ", WATCHDOG_MINOR, rc); |
1da177e4c Linux-2.6.12-rc2 |
419 420 |
goto err_out_notifier; } |
ff9480605 [WATCHDOG 43/57] ... |
421 422 423 424 |
printk(KERN_INFO PFX "WDT driver for SC520 initialised. timeout=%d sec (nowayout=%d) ", timeout, nowayout); |
1da177e4c Linux-2.6.12-rc2 |
425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 |
return 0; err_out_notifier: unregister_reboot_notifier(&wdt_notifier); err_out_ioremap: iounmap(wdtmrctl); err_out_region2: return rc; } module_init(sc520_wdt_init); module_exit(sc520_wdt_unload); MODULE_AUTHOR("Scott and Bill Jennings"); |
143a2e54b [WATCHDOG] More c... |
440 441 |
MODULE_DESCRIPTION( "Driver for watchdog timer in AMD \"Elan\" SC520 uProcessor"); |
1da177e4c Linux-2.6.12-rc2 |
442 443 |
MODULE_LICENSE("GPL"); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); |