diff options
Diffstat (limited to 'drivers/watchdog/iTCO_wdt.c')
-rw-r--r-- | drivers/watchdog/iTCO_wdt.c | 213 |
1 files changed, 48 insertions, 165 deletions
diff --git a/drivers/watchdog/iTCO_wdt.c b/drivers/watchdog/iTCO_wdt.c index 9c2c27c3b424..ceed39f26011 100644 --- a/drivers/watchdog/iTCO_wdt.c +++ b/drivers/watchdog/iTCO_wdt.c | |||
@@ -47,7 +47,7 @@ | |||
47 | 47 | ||
48 | /* Module and version information */ | 48 | /* Module and version information */ |
49 | #define DRV_NAME "iTCO_wdt" | 49 | #define DRV_NAME "iTCO_wdt" |
50 | #define DRV_VERSION "1.07" | 50 | #define DRV_VERSION "1.10" |
51 | 51 | ||
52 | /* Includes */ | 52 | /* Includes */ |
53 | #include <linux/module.h> /* For module specific items */ | 53 | #include <linux/module.h> /* For module specific items */ |
@@ -88,8 +88,6 @@ | |||
88 | #define TCOv2_TMR (TCOBASE + 0x12) /* TCOv2 Timer Initial Value */ | 88 | #define TCOv2_TMR (TCOBASE + 0x12) /* TCOv2 Timer Initial Value */ |
89 | 89 | ||
90 | /* internal variables */ | 90 | /* internal variables */ |
91 | static unsigned long is_active; | ||
92 | static char expect_release; | ||
93 | static struct { /* this is private data for the iTCO_wdt device */ | 91 | static struct { /* this is private data for the iTCO_wdt device */ |
94 | /* TCO version/generation */ | 92 | /* TCO version/generation */ |
95 | unsigned int iTCO_version; | 93 | unsigned int iTCO_version; |
@@ -106,12 +104,12 @@ static struct { /* this is private data for the iTCO_wdt device */ | |||
106 | } iTCO_wdt_private; | 104 | } iTCO_wdt_private; |
107 | 105 | ||
108 | /* module parameters */ | 106 | /* module parameters */ |
109 | #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ | 107 | #define WATCHDOG_TIMEOUT 30 /* 30 sec default heartbeat */ |
110 | static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ | 108 | static int heartbeat = WATCHDOG_TIMEOUT; /* in seconds */ |
111 | module_param(heartbeat, int, 0); | 109 | module_param(heartbeat, int, 0); |
112 | MODULE_PARM_DESC(heartbeat, "Watchdog timeout in seconds. " | 110 | MODULE_PARM_DESC(heartbeat, "Watchdog timeout in seconds. " |
113 | "5..76 (TCO v1) or 3..614 (TCO v2), default=" | 111 | "5..76 (TCO v1) or 3..614 (TCO v2), default=" |
114 | __MODULE_STRING(WATCHDOG_HEARTBEAT) ")"); | 112 | __MODULE_STRING(WATCHDOG_TIMEOUT) ")"); |
115 | 113 | ||
116 | static bool nowayout = WATCHDOG_NOWAYOUT; | 114 | static bool nowayout = WATCHDOG_NOWAYOUT; |
117 | module_param(nowayout, bool, 0); | 115 | module_param(nowayout, bool, 0); |
@@ -178,13 +176,13 @@ static int iTCO_wdt_unset_NO_REBOOT_bit(void) | |||
178 | return ret; /* returns: 0 = OK, -EIO = Error */ | 176 | return ret; /* returns: 0 = OK, -EIO = Error */ |
179 | } | 177 | } |
180 | 178 | ||
181 | static int iTCO_wdt_start(void) | 179 | static int iTCO_wdt_start(struct watchdog_device *wd_dev) |
182 | { | 180 | { |
183 | unsigned int val; | 181 | unsigned int val; |
184 | 182 | ||
185 | spin_lock(&iTCO_wdt_private.io_lock); | 183 | spin_lock(&iTCO_wdt_private.io_lock); |
186 | 184 | ||
187 | iTCO_vendor_pre_start(iTCO_wdt_private.smi_res, heartbeat); | 185 | iTCO_vendor_pre_start(iTCO_wdt_private.smi_res, wd_dev->timeout); |
188 | 186 | ||
189 | /* disable chipset's NO_REBOOT bit */ | 187 | /* disable chipset's NO_REBOOT bit */ |
190 | if (iTCO_wdt_unset_NO_REBOOT_bit()) { | 188 | if (iTCO_wdt_unset_NO_REBOOT_bit()) { |
@@ -212,7 +210,7 @@ static int iTCO_wdt_start(void) | |||
212 | return 0; | 210 | return 0; |
213 | } | 211 | } |
214 | 212 | ||
215 | static int iTCO_wdt_stop(void) | 213 | static int iTCO_wdt_stop(struct watchdog_device *wd_dev) |
216 | { | 214 | { |
217 | unsigned int val; | 215 | unsigned int val; |
218 | 216 | ||
@@ -236,11 +234,11 @@ static int iTCO_wdt_stop(void) | |||
236 | return 0; | 234 | return 0; |
237 | } | 235 | } |
238 | 236 | ||
239 | static int iTCO_wdt_keepalive(void) | 237 | static int iTCO_wdt_ping(struct watchdog_device *wd_dev) |
240 | { | 238 | { |
241 | spin_lock(&iTCO_wdt_private.io_lock); | 239 | spin_lock(&iTCO_wdt_private.io_lock); |
242 | 240 | ||
243 | iTCO_vendor_pre_keepalive(iTCO_wdt_private.smi_res, heartbeat); | 241 | iTCO_vendor_pre_keepalive(iTCO_wdt_private.smi_res, wd_dev->timeout); |
244 | 242 | ||
245 | /* Reload the timer by writing to the TCO Timer Counter register */ | 243 | /* Reload the timer by writing to the TCO Timer Counter register */ |
246 | if (iTCO_wdt_private.iTCO_version == 2) | 244 | if (iTCO_wdt_private.iTCO_version == 2) |
@@ -257,7 +255,7 @@ static int iTCO_wdt_keepalive(void) | |||
257 | return 0; | 255 | return 0; |
258 | } | 256 | } |
259 | 257 | ||
260 | static int iTCO_wdt_set_heartbeat(int t) | 258 | static int iTCO_wdt_set_timeout(struct watchdog_device *wd_dev, unsigned int t) |
261 | { | 259 | { |
262 | unsigned int val16; | 260 | unsigned int val16; |
263 | unsigned char val8; | 261 | unsigned char val8; |
@@ -304,14 +302,15 @@ static int iTCO_wdt_set_heartbeat(int t) | |||
304 | return -EINVAL; | 302 | return -EINVAL; |
305 | } | 303 | } |
306 | 304 | ||
307 | heartbeat = t; | 305 | wd_dev->timeout = t; |
308 | return 0; | 306 | return 0; |
309 | } | 307 | } |
310 | 308 | ||
311 | static int iTCO_wdt_get_timeleft(int *time_left) | 309 | static unsigned int iTCO_wdt_get_timeleft(struct watchdog_device *wd_dev) |
312 | { | 310 | { |
313 | unsigned int val16; | 311 | unsigned int val16; |
314 | unsigned char val8; | 312 | unsigned char val8; |
313 | unsigned int time_left = 0; | ||
315 | 314 | ||
316 | /* read the TCO Timer */ | 315 | /* read the TCO Timer */ |
317 | if (iTCO_wdt_private.iTCO_version == 2) { | 316 | if (iTCO_wdt_private.iTCO_version == 2) { |
@@ -320,7 +319,7 @@ static int iTCO_wdt_get_timeleft(int *time_left) | |||
320 | val16 &= 0x3ff; | 319 | val16 &= 0x3ff; |
321 | spin_unlock(&iTCO_wdt_private.io_lock); | 320 | spin_unlock(&iTCO_wdt_private.io_lock); |
322 | 321 | ||
323 | *time_left = (val16 * 6) / 10; | 322 | time_left = (val16 * 6) / 10; |
324 | } else if (iTCO_wdt_private.iTCO_version == 1) { | 323 | } else if (iTCO_wdt_private.iTCO_version == 1) { |
325 | spin_lock(&iTCO_wdt_private.io_lock); | 324 | spin_lock(&iTCO_wdt_private.io_lock); |
326 | val8 = inb(TCO_RLD); | 325 | val8 = inb(TCO_RLD); |
@@ -329,156 +328,35 @@ static int iTCO_wdt_get_timeleft(int *time_left) | |||
329 | val8 += (inb(TCOv1_TMR) & 0x3f); | 328 | val8 += (inb(TCOv1_TMR) & 0x3f); |
330 | spin_unlock(&iTCO_wdt_private.io_lock); | 329 | spin_unlock(&iTCO_wdt_private.io_lock); |
331 | 330 | ||
332 | *time_left = (val8 * 6) / 10; | 331 | time_left = (val8 * 6) / 10; |
333 | } else | ||
334 | return -EINVAL; | ||
335 | return 0; | ||
336 | } | ||
337 | |||
338 | /* | ||
339 | * /dev/watchdog handling | ||
340 | */ | ||
341 | |||
342 | static int iTCO_wdt_open(struct inode *inode, struct file *file) | ||
343 | { | ||
344 | /* /dev/watchdog can only be opened once */ | ||
345 | if (test_and_set_bit(0, &is_active)) | ||
346 | return -EBUSY; | ||
347 | |||
348 | /* | ||
349 | * Reload and activate timer | ||
350 | */ | ||
351 | iTCO_wdt_start(); | ||
352 | return nonseekable_open(inode, file); | ||
353 | } | ||
354 | |||
355 | static int iTCO_wdt_release(struct inode *inode, struct file *file) | ||
356 | { | ||
357 | /* | ||
358 | * Shut off the timer. | ||
359 | */ | ||
360 | if (expect_release == 42) { | ||
361 | iTCO_wdt_stop(); | ||
362 | } else { | ||
363 | pr_crit("Unexpected close, not stopping watchdog!\n"); | ||
364 | iTCO_wdt_keepalive(); | ||
365 | } | ||
366 | clear_bit(0, &is_active); | ||
367 | expect_release = 0; | ||
368 | return 0; | ||
369 | } | ||
370 | |||
371 | static ssize_t iTCO_wdt_write(struct file *file, const char __user *data, | ||
372 | size_t len, loff_t *ppos) | ||
373 | { | ||
374 | /* See if we got the magic character 'V' and reload the timer */ | ||
375 | if (len) { | ||
376 | if (!nowayout) { | ||
377 | size_t i; | ||
378 | |||
379 | /* note: just in case someone wrote the magic | ||
380 | character five months ago... */ | ||
381 | expect_release = 0; | ||
382 | |||
383 | /* scan to see whether or not we got the | ||
384 | magic character */ | ||
385 | for (i = 0; i != len; i++) { | ||
386 | char c; | ||
387 | if (get_user(c, data + i)) | ||
388 | return -EFAULT; | ||
389 | if (c == 'V') | ||
390 | expect_release = 42; | ||
391 | } | ||
392 | } | ||
393 | |||
394 | /* someone wrote to us, we should reload the timer */ | ||
395 | iTCO_wdt_keepalive(); | ||
396 | } | ||
397 | return len; | ||
398 | } | ||
399 | |||
400 | static long iTCO_wdt_ioctl(struct file *file, unsigned int cmd, | ||
401 | unsigned long arg) | ||
402 | { | ||
403 | int new_options, retval = -EINVAL; | ||
404 | int new_heartbeat; | ||
405 | void __user *argp = (void __user *)arg; | ||
406 | int __user *p = argp; | ||
407 | static const struct watchdog_info ident = { | ||
408 | .options = WDIOF_SETTIMEOUT | | ||
409 | WDIOF_KEEPALIVEPING | | ||
410 | WDIOF_MAGICCLOSE, | ||
411 | .firmware_version = 0, | ||
412 | .identity = DRV_NAME, | ||
413 | }; | ||
414 | |||
415 | switch (cmd) { | ||
416 | case WDIOC_GETSUPPORT: | ||
417 | return copy_to_user(argp, &ident, sizeof(ident)) ? -EFAULT : 0; | ||
418 | case WDIOC_GETSTATUS: | ||
419 | case WDIOC_GETBOOTSTATUS: | ||
420 | return put_user(0, p); | ||
421 | |||
422 | case WDIOC_SETOPTIONS: | ||
423 | { | ||
424 | if (get_user(new_options, p)) | ||
425 | return -EFAULT; | ||
426 | |||
427 | if (new_options & WDIOS_DISABLECARD) { | ||
428 | iTCO_wdt_stop(); | ||
429 | retval = 0; | ||
430 | } | ||
431 | if (new_options & WDIOS_ENABLECARD) { | ||
432 | iTCO_wdt_keepalive(); | ||
433 | iTCO_wdt_start(); | ||
434 | retval = 0; | ||
435 | } | ||
436 | return retval; | ||
437 | } | ||
438 | case WDIOC_KEEPALIVE: | ||
439 | iTCO_wdt_keepalive(); | ||
440 | return 0; | ||
441 | |||
442 | case WDIOC_SETTIMEOUT: | ||
443 | { | ||
444 | if (get_user(new_heartbeat, p)) | ||
445 | return -EFAULT; | ||
446 | if (iTCO_wdt_set_heartbeat(new_heartbeat)) | ||
447 | return -EINVAL; | ||
448 | iTCO_wdt_keepalive(); | ||
449 | /* Fall */ | ||
450 | } | ||
451 | case WDIOC_GETTIMEOUT: | ||
452 | return put_user(heartbeat, p); | ||
453 | case WDIOC_GETTIMELEFT: | ||
454 | { | ||
455 | int time_left; | ||
456 | if (iTCO_wdt_get_timeleft(&time_left)) | ||
457 | return -EINVAL; | ||
458 | return put_user(time_left, p); | ||
459 | } | ||
460 | default: | ||
461 | return -ENOTTY; | ||
462 | } | 332 | } |
333 | return time_left; | ||
463 | } | 334 | } |
464 | 335 | ||
465 | /* | 336 | /* |
466 | * Kernel Interfaces | 337 | * Kernel Interfaces |
467 | */ | 338 | */ |
468 | 339 | ||
469 | static const struct file_operations iTCO_wdt_fops = { | 340 | static const struct watchdog_info ident = { |
341 | .options = WDIOF_SETTIMEOUT | | ||
342 | WDIOF_KEEPALIVEPING | | ||
343 | WDIOF_MAGICCLOSE, | ||
344 | .firmware_version = 0, | ||
345 | .identity = DRV_NAME, | ||
346 | }; | ||
347 | |||
348 | static const struct watchdog_ops iTCO_wdt_ops = { | ||
470 | .owner = THIS_MODULE, | 349 | .owner = THIS_MODULE, |
471 | .llseek = no_llseek, | 350 | .start = iTCO_wdt_start, |
472 | .write = iTCO_wdt_write, | 351 | .stop = iTCO_wdt_stop, |
473 | .unlocked_ioctl = iTCO_wdt_ioctl, | 352 | .ping = iTCO_wdt_ping, |
474 | .open = iTCO_wdt_open, | 353 | .set_timeout = iTCO_wdt_set_timeout, |
475 | .release = iTCO_wdt_release, | 354 | .get_timeleft = iTCO_wdt_get_timeleft, |
476 | }; | 355 | }; |
477 | 356 | ||
478 | static struct miscdevice iTCO_wdt_miscdev = { | 357 | static struct watchdog_device iTCO_wdt_watchdog_dev = { |
479 | .minor = WATCHDOG_MINOR, | 358 | .info = &ident, |
480 | .name = "watchdog", | 359 | .ops = &iTCO_wdt_ops, |
481 | .fops = &iTCO_wdt_fops, | ||
482 | }; | 360 | }; |
483 | 361 | ||
484 | /* | 362 | /* |
@@ -489,10 +367,10 @@ static void __devexit iTCO_wdt_cleanup(void) | |||
489 | { | 367 | { |
490 | /* Stop the timer before we leave */ | 368 | /* Stop the timer before we leave */ |
491 | if (!nowayout) | 369 | if (!nowayout) |
492 | iTCO_wdt_stop(); | 370 | iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); |
493 | 371 | ||
494 | /* Deregister */ | 372 | /* Deregister */ |
495 | misc_deregister(&iTCO_wdt_miscdev); | 373 | watchdog_unregister_device(&iTCO_wdt_watchdog_dev); |
496 | 374 | ||
497 | /* release resources */ | 375 | /* release resources */ |
498 | release_region(iTCO_wdt_private.tco_res->start, | 376 | release_region(iTCO_wdt_private.tco_res->start, |
@@ -605,20 +483,25 @@ static int __devinit iTCO_wdt_probe(struct platform_device *dev) | |||
605 | outw(0x0002, TCO2_STS); /* Clear SECOND_TO_STS bit */ | 483 | outw(0x0002, TCO2_STS); /* Clear SECOND_TO_STS bit */ |
606 | outw(0x0004, TCO2_STS); /* Clear BOOT_STS bit */ | 484 | outw(0x0004, TCO2_STS); /* Clear BOOT_STS bit */ |
607 | 485 | ||
486 | iTCO_wdt_watchdog_dev.bootstatus = 0; | ||
487 | iTCO_wdt_watchdog_dev.timeout = WATCHDOG_TIMEOUT; | ||
488 | watchdog_set_nowayout(&iTCO_wdt_watchdog_dev, nowayout); | ||
489 | iTCO_wdt_watchdog_dev.parent = dev->dev.parent; | ||
490 | |||
608 | /* Make sure the watchdog is not running */ | 491 | /* Make sure the watchdog is not running */ |
609 | iTCO_wdt_stop(); | 492 | iTCO_wdt_stop(&iTCO_wdt_watchdog_dev); |
610 | 493 | ||
611 | /* Check that the heartbeat value is within it's range; | 494 | /* Check that the heartbeat value is within it's range; |
612 | if not reset to the default */ | 495 | if not reset to the default */ |
613 | if (iTCO_wdt_set_heartbeat(heartbeat)) { | 496 | if (iTCO_wdt_set_timeout(&iTCO_wdt_watchdog_dev, heartbeat)) { |
614 | iTCO_wdt_set_heartbeat(WATCHDOG_HEARTBEAT); | 497 | iTCO_wdt_set_timeout(&iTCO_wdt_watchdog_dev, WATCHDOG_TIMEOUT); |
615 | pr_info("timeout value out of range, using %d\n", heartbeat); | 498 | pr_info("timeout value out of range, using %d\n", |
499 | WATCHDOG_TIMEOUT); | ||
616 | } | 500 | } |
617 | 501 | ||
618 | ret = misc_register(&iTCO_wdt_miscdev); | 502 | ret = watchdog_register_device(&iTCO_wdt_watchdog_dev); |
619 | if (ret != 0) { | 503 | if (ret != 0) { |
620 | pr_err("cannot register miscdev on minor=%d (err=%d)\n", | 504 | pr_err("cannot register watchdog device (err=%d)\n", ret); |
621 | WATCHDOG_MINOR, ret); | ||
622 | goto unreg_tco; | 505 | goto unreg_tco; |
623 | } | 506 | } |
624 | 507 | ||
@@ -659,7 +542,7 @@ static int __devexit iTCO_wdt_remove(struct platform_device *dev) | |||
659 | 542 | ||
660 | static void iTCO_wdt_shutdown(struct platform_device *dev) | 543 | static void iTCO_wdt_shutdown(struct platform_device *dev) |
661 | { | 544 | { |
662 | iTCO_wdt_stop(); | 545 | iTCO_wdt_stop(NULL); |
663 | } | 546 | } |
664 | 547 | ||
665 | static struct platform_driver iTCO_wdt_driver = { | 548 | static struct platform_driver iTCO_wdt_driver = { |