aboutsummaryrefslogtreecommitdiffstats
path: root/arch/blackfin/kernel/nmi.c
diff options
context:
space:
mode:
authorGraf Yang <graf.yang@analog.com>2010-01-20 05:56:24 -0500
committerMike Frysinger <vapier@gentoo.org>2010-03-09 00:30:49 -0500
commit60ffdb36547da2397d6cfefe9c752ebad16524f6 (patch)
treece188bc0ccd1d8d0e6ff0f49937dca95e1f571c0 /arch/blackfin/kernel/nmi.c
parent726e96561e4704278bc5197238f6459e1a63aa77 (diff)
Blackfin: implement nmi_watchdog for SMP on BF561
Signed-off-by: Graf Yang <graf.yang@analog.com> Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Diffstat (limited to 'arch/blackfin/kernel/nmi.c')
-rw-r--r--arch/blackfin/kernel/nmi.c313
1 files changed, 313 insertions, 0 deletions
diff --git a/arch/blackfin/kernel/nmi.c b/arch/blackfin/kernel/nmi.c
new file mode 100644
index 000000000000..19093c17632b
--- /dev/null
+++ b/arch/blackfin/kernel/nmi.c
@@ -0,0 +1,313 @@
1/*
2 * Blackfin nmi_watchdog Driver
3 *
4 * Originally based on bfin_wdt.c
5 * Copyright 2010-2010 Analog Devices Inc.
6 * Graff Yang <graf.yang@analog.com>
7 *
8 * Enter bugs at http://blackfin.uclinux.org/
9 *
10 * Licensed under the GPL-2 or later.
11 */
12
13#include <linux/bitops.h>
14#include <linux/hardirq.h>
15#include <linux/sysdev.h>
16#include <linux/pm.h>
17#include <linux/nmi.h>
18#include <linux/smp.h>
19#include <linux/timer.h>
20#include <asm/blackfin.h>
21#include <asm/atomic.h>
22#include <asm/cacheflush.h>
23
24/* Bit in WDOG_CTL that indicates watchdog has expired (WDR0) */
25#define WDOG_EXPIRED 0x8000
26
27/* Masks for WDEV field in WDOG_CTL register */
28#define ICTL_RESET 0x0
29#define ICTL_NMI 0x2
30#define ICTL_GPI 0x4
31#define ICTL_NONE 0x6
32#define ICTL_MASK 0x6
33
34/* Masks for WDEN field in WDOG_CTL register */
35#define WDEN_MASK 0x0FF0
36#define WDEN_ENABLE 0x0000
37#define WDEN_DISABLE 0x0AD0
38
39#define DRV_NAME "nmi-wdt"
40
41#define NMI_WDT_TIMEOUT 5 /* 5 seconds */
42#define NMI_CHECK_TIMEOUT (4 * HZ) /* 4 seconds in jiffies */
43static int nmi_wdt_cpu = 1;
44
45static unsigned int timeout = NMI_WDT_TIMEOUT;
46static int nmi_active;
47
48static unsigned short wdoga_ctl;
49static unsigned int wdoga_cnt;
50static struct corelock_slot saved_corelock;
51static atomic_t nmi_touched[NR_CPUS];
52static struct timer_list ntimer;
53
54enum {
55 COREA_ENTER_NMI = 0,
56 COREA_EXIT_NMI,
57 COREB_EXIT_NMI,
58
59 NMI_EVENT_NR,
60};
61static unsigned long nmi_event __attribute__ ((__section__(".l2.bss")));
62
63/* we are in nmi, non-atomic bit ops is safe */
64static inline void set_nmi_event(int event)
65{
66 __set_bit(event, &nmi_event);
67}
68
69static inline void wait_nmi_event(int event)
70{
71 while (!test_bit(event, &nmi_event))
72 barrier();
73 __clear_bit(event, &nmi_event);
74}
75
76static inline void send_corea_nmi(void)
77{
78 wdoga_ctl = bfin_read_WDOGA_CTL();
79 wdoga_cnt = bfin_read_WDOGA_CNT();
80
81 bfin_write_WDOGA_CTL(WDEN_DISABLE);
82 bfin_write_WDOGA_CNT(0);
83 bfin_write_WDOGA_CTL(WDEN_ENABLE | ICTL_NMI);
84}
85
86static inline void restore_corea_nmi(void)
87{
88 bfin_write_WDOGA_CTL(WDEN_DISABLE);
89 bfin_write_WDOGA_CTL(WDOG_EXPIRED | WDEN_DISABLE | ICTL_NONE);
90
91 bfin_write_WDOGA_CNT(wdoga_cnt);
92 bfin_write_WDOGA_CTL(wdoga_ctl);
93}
94
95static inline void save_corelock(void)
96{
97 saved_corelock = corelock;
98 corelock.lock = 0;
99}
100
101static inline void restore_corelock(void)
102{
103 corelock = saved_corelock;
104}
105
106
107static inline void nmi_wdt_keepalive(void)
108{
109 bfin_write_WDOGB_STAT(0);
110}
111
112static inline void nmi_wdt_stop(void)
113{
114 bfin_write_WDOGB_CTL(WDEN_DISABLE);
115}
116
117/* before calling this function, you must stop the WDT */
118static inline void nmi_wdt_clear(void)
119{
120 /* clear TRO bit, disable event generation */
121 bfin_write_WDOGB_CTL(WDOG_EXPIRED | WDEN_DISABLE | ICTL_NONE);
122}
123
124static inline void nmi_wdt_start(void)
125{
126 bfin_write_WDOGB_CTL(WDEN_ENABLE | ICTL_NMI);
127}
128
129static inline int nmi_wdt_running(void)
130{
131 return ((bfin_read_WDOGB_CTL() & WDEN_MASK) != WDEN_DISABLE);
132}
133
134static inline int nmi_wdt_set_timeout(unsigned long t)
135{
136 u32 cnt, max_t, sclk;
137 int run;
138
139 sclk = get_sclk();
140 max_t = -1 / sclk;
141 cnt = t * sclk;
142 if (t > max_t) {
143 pr_warning("NMI: timeout value is too large\n");
144 return -EINVAL;
145 }
146
147 run = nmi_wdt_running();
148 nmi_wdt_stop();
149 bfin_write_WDOGB_CNT(cnt);
150 if (run)
151 nmi_wdt_start();
152
153 timeout = t;
154
155 return 0;
156}
157
158int check_nmi_wdt_touched(void)
159{
160 unsigned int this_cpu = smp_processor_id();
161 unsigned int cpu;
162
163 cpumask_t mask = cpu_online_map;
164
165 if (!atomic_read(&nmi_touched[this_cpu]))
166 return 0;
167
168 atomic_set(&nmi_touched[this_cpu], 0);
169
170 cpu_clear(this_cpu, mask);
171 for_each_cpu_mask(cpu, mask) {
172 invalidate_dcache_range((unsigned long)(&nmi_touched[cpu]),
173 (unsigned long)(&nmi_touched[cpu]));
174 if (!atomic_read(&nmi_touched[cpu]))
175 return 0;
176 atomic_set(&nmi_touched[cpu], 0);
177 }
178
179 return 1;
180}
181
182static void nmi_wdt_timer(unsigned long data)
183{
184 if (check_nmi_wdt_touched())
185 nmi_wdt_keepalive();
186
187 mod_timer(&ntimer, jiffies + NMI_CHECK_TIMEOUT);
188}
189
190static int __init init_nmi_wdt(void)
191{
192 nmi_wdt_set_timeout(timeout);
193 nmi_wdt_start();
194 nmi_active = true;
195
196 init_timer(&ntimer);
197 ntimer.function = nmi_wdt_timer;
198 ntimer.expires = jiffies + NMI_CHECK_TIMEOUT;
199 add_timer(&ntimer);
200
201 pr_info("nmi_wdt: initialized: timeout=%d sec\n", timeout);
202 return 0;
203}
204device_initcall(init_nmi_wdt);
205
206void touch_nmi_watchdog(void)
207{
208 atomic_set(&nmi_touched[smp_processor_id()], 1);
209}
210
211/* Suspend/resume support */
212#ifdef CONFIG_PM
213static int nmi_wdt_suspend(struct sys_device *dev, pm_message_t state)
214{
215 nmi_wdt_stop();
216 return 0;
217}
218
219static int nmi_wdt_resume(struct sys_device *dev)
220{
221 if (nmi_active)
222 nmi_wdt_start();
223 return 0;
224}
225
226static struct sysdev_class nmi_sysclass = {
227 .name = DRV_NAME,
228 .resume = nmi_wdt_resume,
229 .suspend = nmi_wdt_suspend,
230};
231
232static struct sys_device device_nmi_wdt = {
233 .id = 0,
234 .cls = &nmi_sysclass,
235};
236
237static int __init init_nmi_wdt_sysfs(void)
238{
239 int error;
240
241 if (!nmi_active)
242 return 0;
243
244 error = sysdev_class_register(&nmi_sysclass);
245 if (!error)
246 error = sysdev_register(&device_nmi_wdt);
247 return error;
248}
249late_initcall(init_nmi_wdt_sysfs);
250
251#endif /* CONFIG_PM */
252
253
254asmlinkage notrace void do_nmi(struct pt_regs *fp)
255{
256 unsigned int cpu = smp_processor_id();
257 nmi_enter();
258
259 cpu_pda[cpu].__nmi_count += 1;
260
261 if (cpu == nmi_wdt_cpu) {
262 /* CoreB goes here first */
263
264 /* reload the WDOG_STAT */
265 nmi_wdt_keepalive();
266
267 /* clear nmi interrupt for CoreB */
268 nmi_wdt_stop();
269 nmi_wdt_clear();
270
271 /* trigger NMI interrupt of CoreA */
272 send_corea_nmi();
273
274 /* waiting CoreB to enter NMI */
275 wait_nmi_event(COREA_ENTER_NMI);
276
277 /* recover WDOGA's settings */
278 restore_corea_nmi();
279
280 save_corelock();
281
282 /* corelock is save/cleared, CoreA is dummping messages */
283
284 wait_nmi_event(COREA_EXIT_NMI);
285 } else {
286 /* OK, CoreA entered NMI */
287 set_nmi_event(COREA_ENTER_NMI);
288 }
289
290 pr_emerg("\nNMI Watchdog detected LOCKUP, dump for CPU %d\n", cpu);
291 dump_bfin_process(fp);
292 dump_bfin_mem(fp);
293 show_regs(fp);
294 dump_bfin_trace_buffer();
295 show_stack(current, (unsigned long *)fp);
296
297 if (cpu == nmi_wdt_cpu) {
298 pr_emerg("This fault is not recoverable, sorry!\n");
299
300 /* CoreA dump finished, restore the corelock */
301 restore_corelock();
302
303 set_nmi_event(COREB_EXIT_NMI);
304 } else {
305 /* CoreB dump finished, notice the CoreA we are done */
306 set_nmi_event(COREA_EXIT_NMI);
307
308 /* synchronize with CoreA */
309 wait_nmi_event(COREB_EXIT_NMI);
310 }
311
312 nmi_exit();
313}