aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/char/watchdog/wdrtas.c
diff options
context:
space:
mode:
authorUtz Bacher <utz.bacher@de.ibm.com>2005-06-22 19:43:34 -0400
committerPaul Mackerras <paulus@samba.org>2005-06-22 19:43:34 -0400
commit031f7edecf46d731673a5dd19ecb0de38f1a2219 (patch)
treebfb58498e6c6ea8c69c7ea0abb3713fb27f86afa /drivers/char/watchdog/wdrtas.c
parent5f5b4e669a59be1cf8fc9d6d04ff1ccad8ab6de0 (diff)
[PATCH] ppc64: add a watchdog driver for rtas
Add a watchdog using the RTAS OS surveillance service. This is provided as a simpler alternative to rtasd. The added value is that it works with standard watchdog client programs and can therefore also do user space monitoring. On BPA, rtasd is not really useful because the hardware does not have much to report with event-scan. The driver should also work on other platforms that support the OS surveillance rtas calls. Signed-off-by: Arnd Bergmann <arndb@de.ibm.com> Signed-off-by: Paul Mackerras <paulus@samba.org>
Diffstat (limited to 'drivers/char/watchdog/wdrtas.c')
-rw-r--r--drivers/char/watchdog/wdrtas.c696
1 files changed, 696 insertions, 0 deletions
diff --git a/drivers/char/watchdog/wdrtas.c b/drivers/char/watchdog/wdrtas.c
new file mode 100644
index 000000000000..619e2ffca33f
--- /dev/null
+++ b/drivers/char/watchdog/wdrtas.c
@@ -0,0 +1,696 @@
1/*
2 * FIXME: add wdrtas_get_status and wdrtas_get_boot_status as soon as
3 * RTAS calls are available
4 */
5
6/*
7 * RTAS watchdog driver
8 *
9 * (C) Copyright IBM Corp. 2005
10 * device driver to exploit watchdog RTAS functions
11 *
12 * Authors : Utz Bacher <utz.bacher@de.ibm.com>
13 *
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2, or (at your option)
17 * any later version.
18 *
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
27 */
28
29#include <linux/config.h>
30#include <linux/fs.h>
31#include <linux/init.h>
32#include <linux/kernel.h>
33#include <linux/miscdevice.h>
34#include <linux/module.h>
35#include <linux/notifier.h>
36#include <linux/reboot.h>
37#include <linux/types.h>
38#include <linux/watchdog.h>
39
40#include <asm/rtas.h>
41#include <asm/uaccess.h>
42
43#define WDRTAS_MAGIC_CHAR 42
44#define WDRTAS_SUPPORTED_MASK (WDIOF_SETTIMEOUT | \
45 WDIOF_MAGICCLOSE)
46
47MODULE_AUTHOR("Utz Bacher <utz.bacher@de.ibm.com>");
48MODULE_DESCRIPTION("RTAS watchdog driver");
49MODULE_LICENSE("GPL");
50MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
51MODULE_ALIAS_MISCDEV(TEMP_MINOR);
52
53#ifdef CONFIG_WATCHDOG_NOWAYOUT
54static int wdrtas_nowayout = 1;
55#else
56static int wdrtas_nowayout = 0;
57#endif
58
59static atomic_t wdrtas_miscdev_open = ATOMIC_INIT(0);
60static char wdrtas_expect_close = 0;
61
62static int wdrtas_interval;
63
64#define WDRTAS_THERMAL_SENSOR 3
65static int wdrtas_token_get_sensor_state;
66#define WDRTAS_SURVEILLANCE_IND 9000
67static int wdrtas_token_set_indicator;
68#define WDRTAS_SP_SPI 28
69static int wdrtas_token_get_sp;
70static int wdrtas_token_event_scan;
71
72#define WDRTAS_DEFAULT_INTERVAL 300
73
74#define WDRTAS_LOGBUFFER_LEN 128
75static char wdrtas_logbuffer[WDRTAS_LOGBUFFER_LEN];
76
77
78/*** watchdog access functions */
79
80/**
81 * wdrtas_set_interval - sets the watchdog interval
82 * @interval: new interval
83 *
84 * returns 0 on success, <0 on failures
85 *
86 * wdrtas_set_interval sets the watchdog keepalive interval by calling the
87 * RTAS function set-indicator (surveillance). The unit of interval is
88 * seconds.
89 */
90static int
91wdrtas_set_interval(int interval)
92{
93 long result;
94 static int print_msg = 10;
95
96 /* rtas uses minutes */
97 interval = (interval + 59) / 60;
98
99 result = rtas_call(wdrtas_token_set_indicator, 3, 1, NULL,
100 WDRTAS_SURVEILLANCE_IND, 0, interval);
101 if ( (result < 0) && (print_msg) ) {
102 printk(KERN_ERR "wdrtas: setting the watchdog to %i "
103 "timeout failed: %li\n", interval, result);
104 print_msg--;
105 }
106
107 return result;
108}
109
110/**
111 * wdrtas_get_interval - returns the current watchdog interval
112 * @fallback_value: value (in seconds) to use, if the RTAS call fails
113 *
114 * returns the interval
115 *
116 * wdrtas_get_interval returns the current watchdog keepalive interval
117 * as reported by the RTAS function ibm,get-system-parameter. The unit
118 * of the return value is seconds.
119 */
120static int
121wdrtas_get_interval(int fallback_value)
122{
123 long result;
124 char value[4];
125
126 result = rtas_call(wdrtas_token_get_sp, 3, 1, NULL,
127 WDRTAS_SP_SPI, (void *)__pa(&value), 4);
128 if ( (value[0] != 0) || (value[1] != 2) || (value[3] != 0) ||
129 (result < 0) ) {
130 printk(KERN_WARNING "wdrtas: could not get sp_spi watchdog "
131 "timeout (%li). Continuing\n", result);
132 return fallback_value;
133 }
134
135 /* rtas uses minutes */
136 return ((int)value[2]) * 60;
137}
138
139/**
140 * wdrtas_timer_start - starts watchdog
141 *
142 * wdrtas_timer_start starts the watchdog by calling the RTAS function
143 * set-interval (surveillance)
144 */
145static void
146wdrtas_timer_start(void)
147{
148 wdrtas_set_interval(wdrtas_interval);
149}
150
151/**
152 * wdrtas_timer_stop - stops watchdog
153 *
154 * wdrtas_timer_stop stops the watchdog timer by calling the RTAS function
155 * set-interval (surveillance)
156 */
157static void
158wdrtas_timer_stop(void)
159{
160 wdrtas_set_interval(0);
161}
162
163/**
164 * wdrtas_log_scanned_event - logs an event we received during keepalive
165 *
166 * wdrtas_log_scanned_event prints a message to the log buffer dumping
167 * the results of the last event-scan call
168 */
169static void
170wdrtas_log_scanned_event(void)
171{
172 int i;
173
174 for (i = 0; i < WDRTAS_LOGBUFFER_LEN; i += 16)
175 printk(KERN_INFO "wdrtas: dumping event (line %i/%i), data = "
176 "%02x %02x %02x %02x %02x %02x %02x %02x "
177 "%02x %02x %02x %02x %02x %02x %02x %02x\n",
178 (i / 16) + 1, (WDRTAS_LOGBUFFER_LEN / 16),
179 wdrtas_logbuffer[i + 0], wdrtas_logbuffer[i + 1],
180 wdrtas_logbuffer[i + 2], wdrtas_logbuffer[i + 3],
181 wdrtas_logbuffer[i + 4], wdrtas_logbuffer[i + 5],
182 wdrtas_logbuffer[i + 6], wdrtas_logbuffer[i + 7],
183 wdrtas_logbuffer[i + 8], wdrtas_logbuffer[i + 9],
184 wdrtas_logbuffer[i + 10], wdrtas_logbuffer[i + 11],
185 wdrtas_logbuffer[i + 12], wdrtas_logbuffer[i + 13],
186 wdrtas_logbuffer[i + 14], wdrtas_logbuffer[i + 15]);
187}
188
189/**
190 * wdrtas_timer_keepalive - resets watchdog timer to keep system alive
191 *
192 * wdrtas_timer_keepalive restarts the watchdog timer by calling the
193 * RTAS function event-scan and repeats these calls as long as there are
194 * events available. All events will be dumped.
195 */
196static void
197wdrtas_timer_keepalive(void)
198{
199 long result;
200
201 do {
202 result = rtas_call(wdrtas_token_event_scan, 4, 1, NULL,
203 RTAS_EVENT_SCAN_ALL_EVENTS, 0,
204 (void *)__pa(wdrtas_logbuffer),
205 WDRTAS_LOGBUFFER_LEN);
206 if (result < 0)
207 printk(KERN_ERR "wdrtas: event-scan failed: %li\n",
208 result);
209 if (result == 0)
210 wdrtas_log_scanned_event();
211 } while (result == 0);
212}
213
214/**
215 * wdrtas_get_temperature - returns current temperature
216 *
217 * returns temperature or <0 on failures
218 *
219 * wdrtas_get_temperature returns the current temperature in Fahrenheit. It
220 * uses the RTAS call get-sensor-state, token 3 to do so
221 */
222static int
223wdrtas_get_temperature(void)
224{
225 long result;
226 int temperature = 0;
227
228 result = rtas_call(wdrtas_token_get_sensor_state, 2, 2,
229 (void *)__pa(&temperature),
230 WDRTAS_THERMAL_SENSOR, 0);
231
232 if (result < 0)
233 printk(KERN_WARNING "wdrtas: reading the thermal sensor "
234 "faild: %li\n", result);
235 else
236 temperature = ((temperature * 9) / 5) + 32; /* fahrenheit */
237
238 return temperature;
239}
240
241/**
242 * wdrtas_get_status - returns the status of the watchdog
243 *
244 * returns a bitmask of defines WDIOF_... as defined in
245 * include/linux/watchdog.h
246 */
247static int
248wdrtas_get_status(void)
249{
250 return 0; /* TODO */
251}
252
253/**
254 * wdrtas_get_boot_status - returns the reason for the last boot
255 *
256 * returns a bitmask of defines WDIOF_... as defined in
257 * include/linux/watchdog.h, indicating why the watchdog rebooted the system
258 */
259static int
260wdrtas_get_boot_status(void)
261{
262 return 0; /* TODO */
263}
264
265/*** watchdog API and operations stuff */
266
267/* wdrtas_write - called when watchdog device is written to
268 * @file: file structure
269 * @buf: user buffer with data
270 * @len: amount to data written
271 * @ppos: position in file
272 *
273 * returns the number of successfully processed characters, which is always
274 * the number of bytes passed to this function
275 *
276 * wdrtas_write processes all the data given to it and looks for the magic
277 * character 'V'. This character allows the watchdog device to be closed
278 * properly.
279 */
280static ssize_t
281wdrtas_write(struct file *file, const char __user *buf,
282 size_t len, loff_t *ppos)
283{
284 int i;
285 char c;
286
287 if (!len)
288 goto out;
289
290 if (!wdrtas_nowayout) {
291 wdrtas_expect_close = 0;
292 /* look for 'V' */
293 for (i = 0; i < len; i++) {
294 if (get_user(c, buf + i))
295 return -EFAULT;
296 /* allow to close device */
297 if (c == 'V')
298 wdrtas_expect_close = WDRTAS_MAGIC_CHAR;
299 }
300 }
301
302 wdrtas_timer_keepalive();
303
304out:
305 return len;
306}
307
308/**
309 * wdrtas_ioctl - ioctl function for the watchdog device
310 * @inode: inode structure
311 * @file: file structure
312 * @cmd: command for ioctl
313 * @arg: argument pointer
314 *
315 * returns 0 on success, <0 on failure
316 *
317 * wdrtas_ioctl implements the watchdog API ioctls
318 */
319static int
320wdrtas_ioctl(struct inode *inode, struct file *file,
321 unsigned int cmd, unsigned long arg)
322{
323 int __user *argp = (void *)arg;
324 int i;
325 static struct watchdog_info wdinfo = {
326 .options = WDRTAS_SUPPORTED_MASK,
327 .firmware_version = 0,
328 .identity = "wdrtas"
329 };
330
331 switch (cmd) {
332 case WDIOC_GETSUPPORT:
333 if (copy_to_user(argp, &wdinfo, sizeof(wdinfo)))
334 return -EFAULT;
335 return 0;
336
337 case WDIOC_GETSTATUS:
338 i = wdrtas_get_status();
339 return put_user(i, argp);
340
341 case WDIOC_GETBOOTSTATUS:
342 i = wdrtas_get_boot_status();
343 return put_user(i, argp);
344
345 case WDIOC_GETTEMP:
346 if (wdrtas_token_get_sensor_state == RTAS_UNKNOWN_SERVICE)
347 return -EOPNOTSUPP;
348
349 i = wdrtas_get_temperature();
350 return put_user(i, argp);
351
352 case WDIOC_SETOPTIONS:
353 if (get_user(i, argp))
354 return -EFAULT;
355 if (i & WDIOS_DISABLECARD)
356 wdrtas_timer_stop();
357 if (i & WDIOS_ENABLECARD) {
358 wdrtas_timer_keepalive();
359 wdrtas_timer_start();
360 }
361 if (i & WDIOS_TEMPPANIC) {
362 /* not implemented. Done by H8 */
363 }
364 return 0;
365
366 case WDIOC_KEEPALIVE:
367 wdrtas_timer_keepalive();
368 return 0;
369
370 case WDIOC_SETTIMEOUT:
371 if (get_user(i, argp))
372 return -EFAULT;
373
374 if (wdrtas_set_interval(i))
375 return -EINVAL;
376
377 wdrtas_timer_keepalive();
378
379 if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE)
380 wdrtas_interval = i;
381 else
382 wdrtas_interval = wdrtas_get_interval(i);
383 /* fallthrough */
384
385 case WDIOC_GETTIMEOUT:
386 return put_user(wdrtas_interval, argp);
387
388 default:
389 return -ENOIOCTLCMD;
390 }
391}
392
393/**
394 * wdrtas_open - open function of watchdog device
395 * @inode: inode structure
396 * @file: file structure
397 *
398 * returns 0 on success, -EBUSY if the file has been opened already, <0 on
399 * other failures
400 *
401 * function called when watchdog device is opened
402 */
403static int
404wdrtas_open(struct inode *inode, struct file *file)
405{
406 /* only open once */
407 if (atomic_inc_return(&wdrtas_miscdev_open) > 1) {
408 atomic_dec(&wdrtas_miscdev_open);
409 return -EBUSY;
410 }
411
412 wdrtas_timer_start();
413 wdrtas_timer_keepalive();
414
415 return nonseekable_open(inode, file);
416}
417
418/**
419 * wdrtas_close - close function of watchdog device
420 * @inode: inode structure
421 * @file: file structure
422 *
423 * returns 0 on success
424 *
425 * close function. Always succeeds
426 */
427static int
428wdrtas_close(struct inode *inode, struct file *file)
429{
430 /* only stop watchdog, if this was announced using 'V' before */
431 if (wdrtas_expect_close == WDRTAS_MAGIC_CHAR)
432 wdrtas_timer_stop();
433 else {
434 printk(KERN_WARNING "wdrtas: got unexpected close. Watchdog "
435 "not stopped.\n");
436 wdrtas_timer_keepalive();
437 }
438
439 wdrtas_expect_close = 0;
440 atomic_dec(&wdrtas_miscdev_open);
441 return 0;
442}
443
444/**
445 * wdrtas_temp_read - gives back the temperature in fahrenheit
446 * @file: file structure
447 * @buf: user buffer
448 * @count: number of bytes to be read
449 * @ppos: position in file
450 *
451 * returns always 1 or -EFAULT in case of user space copy failures, <0 on
452 * other failures
453 *
454 * wdrtas_temp_read gives the temperature to the users by copying this
455 * value as one byte into the user space buffer. The unit is Fahrenheit...
456 */
457static ssize_t
458wdrtas_temp_read(struct file *file, char __user *buf,
459 size_t count, loff_t *ppos)
460{
461 int temperature = 0;
462
463 temperature = wdrtas_get_temperature();
464 if (temperature < 0)
465 return temperature;
466
467 if (copy_to_user(buf, &temperature, 1))
468 return -EFAULT;
469
470 return 1;
471}
472
473/**
474 * wdrtas_temp_open - open function of temperature device
475 * @inode: inode structure
476 * @file: file structure
477 *
478 * returns 0 on success, <0 on failure
479 *
480 * function called when temperature device is opened
481 */
482static int
483wdrtas_temp_open(struct inode *inode, struct file *file)
484{
485 return nonseekable_open(inode, file);
486}
487
488/**
489 * wdrtas_temp_close - close function of temperature device
490 * @inode: inode structure
491 * @file: file structure
492 *
493 * returns 0 on success
494 *
495 * close function. Always succeeds
496 */
497static int
498wdrtas_temp_close(struct inode *inode, struct file *file)
499{
500 return 0;
501}
502
503/**
504 * wdrtas_reboot - reboot notifier function
505 * @nb: notifier block structure
506 * @code: reboot code
507 * @ptr: unused
508 *
509 * returns NOTIFY_DONE
510 *
511 * wdrtas_reboot stops the watchdog in case of a reboot
512 */
513static int
514wdrtas_reboot(struct notifier_block *this, unsigned long code, void *ptr)
515{
516 if ( (code==SYS_DOWN) || (code==SYS_HALT) )
517 wdrtas_timer_stop();
518
519 return NOTIFY_DONE;
520}
521
522/*** initialization stuff */
523
524static struct file_operations wdrtas_fops = {
525 .owner = THIS_MODULE,
526 .llseek = no_llseek,
527 .write = wdrtas_write,
528 .ioctl = wdrtas_ioctl,
529 .open = wdrtas_open,
530 .release = wdrtas_close,
531};
532
533static struct miscdevice wdrtas_miscdev = {
534 .minor = WATCHDOG_MINOR,
535 .name = "watchdog",
536 .fops = &wdrtas_fops,
537};
538
539static struct file_operations wdrtas_temp_fops = {
540 .owner = THIS_MODULE,
541 .llseek = no_llseek,
542 .read = wdrtas_temp_read,
543 .open = wdrtas_temp_open,
544 .release = wdrtas_temp_close,
545};
546
547static struct miscdevice wdrtas_tempdev = {
548 .minor = TEMP_MINOR,
549 .name = "temperature",
550 .fops = &wdrtas_temp_fops,
551};
552
553static struct notifier_block wdrtas_notifier = {
554 .notifier_call = wdrtas_reboot,
555};
556
557/**
558 * wdrtas_get_tokens - reads in RTAS tokens
559 *
560 * returns 0 on succes, <0 on failure
561 *
562 * wdrtas_get_tokens reads in the tokens for the RTAS calls used in
563 * this watchdog driver. It tolerates, if "get-sensor-state" and
564 * "ibm,get-system-parameter" are not available.
565 */
566static int
567wdrtas_get_tokens(void)
568{
569 wdrtas_token_get_sensor_state = rtas_token("get-sensor-state");
570 if (wdrtas_token_get_sensor_state == RTAS_UNKNOWN_SERVICE) {
571 printk(KERN_WARNING "wdrtas: couldn't get token for "
572 "get-sensor-state. Trying to continue without "
573 "temperature support.\n");
574 }
575
576 wdrtas_token_get_sp = rtas_token("ibm,get-system-parameter");
577 if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE) {
578 printk(KERN_WARNING "wdrtas: couldn't get token for "
579 "ibm,get-system-parameter. Trying to continue with "
580 "a default timeout value of %i seconds.\n",
581 WDRTAS_DEFAULT_INTERVAL);
582 }
583
584 wdrtas_token_set_indicator = rtas_token("set-indicator");
585 if (wdrtas_token_set_indicator == RTAS_UNKNOWN_SERVICE) {
586 printk(KERN_ERR "wdrtas: couldn't get token for "
587 "set-indicator. Terminating watchdog code.\n");
588 return -EIO;
589 }
590
591 wdrtas_token_event_scan = rtas_token("event-scan");
592 if (wdrtas_token_event_scan == RTAS_UNKNOWN_SERVICE) {
593 printk(KERN_ERR "wdrtas: couldn't get token for event-scan. "
594 "Terminating watchdog code.\n");
595 return -EIO;
596 }
597
598 return 0;
599}
600
601/**
602 * wdrtas_unregister_devs - unregisters the misc dev handlers
603 *
604 * wdrtas_register_devs unregisters the watchdog and temperature watchdog
605 * misc devs
606 */
607static void
608wdrtas_unregister_devs(void)
609{
610 misc_deregister(&wdrtas_miscdev);
611 if (wdrtas_token_get_sensor_state != RTAS_UNKNOWN_SERVICE)
612 misc_deregister(&wdrtas_tempdev);
613}
614
615/**
616 * wdrtas_register_devs - registers the misc dev handlers
617 *
618 * returns 0 on succes, <0 on failure
619 *
620 * wdrtas_register_devs registers the watchdog and temperature watchdog
621 * misc devs
622 */
623static int
624wdrtas_register_devs(void)
625{
626 int result;
627
628 result = misc_register(&wdrtas_miscdev);
629 if (result) {
630 printk(KERN_ERR "wdrtas: couldn't register watchdog misc "
631 "device. Terminating watchdog code.\n");
632 return result;
633 }
634
635 if (wdrtas_token_get_sensor_state != RTAS_UNKNOWN_SERVICE) {
636 result = misc_register(&wdrtas_tempdev);
637 if (result) {
638 printk(KERN_WARNING "wdrtas: couldn't register "
639 "watchdog temperature misc device. Continuing "
640 "without temperature support.\n");
641 wdrtas_token_get_sensor_state = RTAS_UNKNOWN_SERVICE;
642 }
643 }
644
645 return 0;
646}
647
648/**
649 * wdrtas_init - init function of the watchdog driver
650 *
651 * returns 0 on succes, <0 on failure
652 *
653 * registers the file handlers and the reboot notifier
654 */
655static int __init
656wdrtas_init(void)
657{
658 if (wdrtas_get_tokens())
659 return -ENODEV;
660
661 if (wdrtas_register_devs())
662 return -ENODEV;
663
664 if (register_reboot_notifier(&wdrtas_notifier)) {
665 printk(KERN_ERR "wdrtas: could not register reboot notifier. "
666 "Terminating watchdog code.\n");
667 wdrtas_unregister_devs();
668 return -ENODEV;
669 }
670
671 if (wdrtas_token_get_sp == RTAS_UNKNOWN_SERVICE)
672 wdrtas_interval = WDRTAS_DEFAULT_INTERVAL;
673 else
674 wdrtas_interval = wdrtas_get_interval(WDRTAS_DEFAULT_INTERVAL);
675
676 return 0;
677}
678
679/**
680 * wdrtas_exit - exit function of the watchdog driver
681 *
682 * unregisters the file handlers and the reboot notifier
683 */
684static void __exit
685wdrtas_exit(void)
686{
687 if (!wdrtas_nowayout)
688 wdrtas_timer_stop();
689
690 wdrtas_unregister_devs();
691
692 unregister_reboot_notifier(&wdrtas_notifier);
693}
694
695module_init(wdrtas_init);
696module_exit(wdrtas_exit);