diff options
Diffstat (limited to 'arch/mn10300/kernel/mn10300-watchdog.c')
-rw-r--r-- | arch/mn10300/kernel/mn10300-watchdog.c | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/arch/mn10300/kernel/mn10300-watchdog.c b/arch/mn10300/kernel/mn10300-watchdog.c new file mode 100644 index 000000000000..10811e981d20 --- /dev/null +++ b/arch/mn10300/kernel/mn10300-watchdog.c | |||
@@ -0,0 +1,183 @@ | |||
1 | /* MN10300 Watchdog timer | ||
2 | * | ||
3 | * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved. | ||
4 | * Written by David Howells (dhowells@redhat.com) | ||
5 | * - Derived from arch/i386/kernel/nmi.c | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or | ||
8 | * modify it under the terms of the GNU General Public Licence | ||
9 | * as published by the Free Software Foundation; either version | ||
10 | * 2 of the Licence, or (at your option) any later version. | ||
11 | */ | ||
12 | #include <linux/module.h> | ||
13 | #include <linux/sched.h> | ||
14 | #include <linux/kernel.h> | ||
15 | #include <linux/init.h> | ||
16 | #include <linux/delay.h> | ||
17 | #include <linux/interrupt.h> | ||
18 | #include <linux/kernel_stat.h> | ||
19 | #include <linux/nmi.h> | ||
20 | #include <asm/processor.h> | ||
21 | #include <asm/system.h> | ||
22 | #include <asm/atomic.h> | ||
23 | #include <asm/intctl-regs.h> | ||
24 | #include <asm/rtc-regs.h> | ||
25 | #include <asm/div64.h> | ||
26 | #include <asm/smp.h> | ||
27 | #include <asm/gdb-stub.h> | ||
28 | #include <asm/proc/clock.h> | ||
29 | |||
30 | static DEFINE_SPINLOCK(watchdog_print_lock); | ||
31 | static unsigned int watchdog; | ||
32 | static unsigned int watchdog_hz = 1; | ||
33 | unsigned int watchdog_alert_counter; | ||
34 | |||
35 | EXPORT_SYMBOL(touch_nmi_watchdog); | ||
36 | |||
37 | /* | ||
38 | * the best way to detect whether a CPU has a 'hard lockup' problem | ||
39 | * is to check its timer makes IRQ counts. If they are not | ||
40 | * changing then that CPU has some problem. | ||
41 | * | ||
42 | * as these watchdog NMI IRQs are generated on every CPU, we only | ||
43 | * have to check the current processor. | ||
44 | * | ||
45 | * since NMIs dont listen to _any_ locks, we have to be extremely | ||
46 | * careful not to rely on unsafe variables. The printk might lock | ||
47 | * up though, so we have to break up any console locks first ... | ||
48 | * [when there will be more tty-related locks, break them up | ||
49 | * here too!] | ||
50 | */ | ||
51 | static unsigned int last_irq_sums[NR_CPUS]; | ||
52 | |||
53 | int __init check_watchdog(void) | ||
54 | { | ||
55 | irq_cpustat_t tmp[1]; | ||
56 | |||
57 | printk(KERN_INFO "Testing Watchdog... "); | ||
58 | |||
59 | memcpy(tmp, irq_stat, sizeof(tmp)); | ||
60 | local_irq_enable(); | ||
61 | mdelay((10 * 1000) / watchdog_hz); /* wait 10 ticks */ | ||
62 | local_irq_disable(); | ||
63 | |||
64 | if (nmi_count(0) - tmp[0].__nmi_count <= 5) { | ||
65 | printk(KERN_WARNING "CPU#%d: Watchdog appears to be stuck!\n", | ||
66 | 0); | ||
67 | return -1; | ||
68 | } | ||
69 | |||
70 | printk(KERN_INFO "OK.\n"); | ||
71 | |||
72 | /* now that we know it works we can reduce NMI frequency to | ||
73 | * something more reasonable; makes a difference in some configs | ||
74 | */ | ||
75 | watchdog_hz = 1; | ||
76 | |||
77 | return 0; | ||
78 | } | ||
79 | |||
80 | static int __init setup_watchdog(char *str) | ||
81 | { | ||
82 | unsigned tmp; | ||
83 | int opt; | ||
84 | u8 ctr; | ||
85 | |||
86 | get_option(&str, &opt); | ||
87 | if (opt != 1) | ||
88 | return 0; | ||
89 | |||
90 | watchdog = opt; | ||
91 | if (watchdog) { | ||
92 | set_intr_stub(EXCEP_WDT, watchdog_handler); | ||
93 | ctr = WDCTR_WDCK_65536th; | ||
94 | WDCTR = WDCTR_WDRST | ctr; | ||
95 | WDCTR = ctr; | ||
96 | tmp = WDCTR; | ||
97 | |||
98 | tmp = __muldiv64u(1 << (16 + ctr * 2), 1000000, MN10300_WDCLK); | ||
99 | tmp = 1000000000 / tmp; | ||
100 | watchdog_hz = (tmp + 500) / 1000; | ||
101 | } | ||
102 | |||
103 | return 1; | ||
104 | } | ||
105 | |||
106 | __setup("watchdog=", setup_watchdog); | ||
107 | |||
108 | void __init watchdog_go(void) | ||
109 | { | ||
110 | u8 wdt; | ||
111 | |||
112 | if (watchdog) { | ||
113 | printk(KERN_INFO "Watchdog: running at %uHz\n", watchdog_hz); | ||
114 | wdt = WDCTR & ~WDCTR_WDCNE; | ||
115 | WDCTR = wdt | WDCTR_WDRST; | ||
116 | wdt = WDCTR; | ||
117 | WDCTR = wdt | WDCTR_WDCNE; | ||
118 | wdt = WDCTR; | ||
119 | |||
120 | check_watchdog(); | ||
121 | } | ||
122 | } | ||
123 | |||
124 | asmlinkage | ||
125 | void watchdog_interrupt(struct pt_regs *regs, enum exception_code excep) | ||
126 | { | ||
127 | |||
128 | /* | ||
129 | * Since current-> is always on the stack, and we always switch | ||
130 | * the stack NMI-atomically, it's safe to use smp_processor_id(). | ||
131 | */ | ||
132 | int sum, cpu = smp_processor_id(); | ||
133 | u8 wdt, tmp; | ||
134 | |||
135 | wdt = WDCTR & ~WDCTR_WDCNE; | ||
136 | WDCTR = wdt; | ||
137 | tmp = WDCTR; | ||
138 | NMICR = NMICR_WDIF; | ||
139 | |||
140 | nmi_count(cpu)++; | ||
141 | kstat_this_cpu.irqs[NMIIRQ]++; | ||
142 | sum = irq_stat[cpu].__irq_count; | ||
143 | |||
144 | if (last_irq_sums[cpu] == sum) { | ||
145 | /* | ||
146 | * Ayiee, looks like this CPU is stuck ... | ||
147 | * wait a few IRQs (5 seconds) before doing the oops ... | ||
148 | */ | ||
149 | watchdog_alert_counter++; | ||
150 | if (watchdog_alert_counter == 5 * watchdog_hz) { | ||
151 | spin_lock(&watchdog_print_lock); | ||
152 | /* | ||
153 | * We are in trouble anyway, lets at least try | ||
154 | * to get a message out. | ||
155 | */ | ||
156 | bust_spinlocks(1); | ||
157 | printk(KERN_ERR | ||
158 | "NMI Watchdog detected LOCKUP on CPU%d," | ||
159 | " pc %08lx, registers:\n", | ||
160 | cpu, regs->pc); | ||
161 | show_registers(regs); | ||
162 | printk("console shuts up ...\n"); | ||
163 | console_silent(); | ||
164 | spin_unlock(&watchdog_print_lock); | ||
165 | bust_spinlocks(0); | ||
166 | #ifdef CONFIG_GDBSTUB | ||
167 | if (gdbstub_busy) | ||
168 | gdbstub_exception(regs, excep); | ||
169 | else | ||
170 | gdbstub_intercept(regs, excep); | ||
171 | #endif | ||
172 | do_exit(SIGSEGV); | ||
173 | } | ||
174 | } else { | ||
175 | last_irq_sums[cpu] = sum; | ||
176 | watchdog_alert_counter = 0; | ||
177 | } | ||
178 | |||
179 | WDCTR = wdt | WDCTR_WDRST; | ||
180 | tmp = WDCTR; | ||
181 | WDCTR = wdt | WDCTR_WDCNE; | ||
182 | tmp = WDCTR; | ||
183 | } | ||