Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

authored by

Graf Yang and committed by
Mike Frysinger
60ffdb36 726e9656

+361 -1
+9
arch/blackfin/Kconfig.debug
··· 238 238 all of this lives in the init section and is thrown away after the 239 239 kernel boots completely. 240 240 241 + config NMI_WATCHDOG 242 + bool "Enable NMI watchdog to help debugging lockup on SMP" 243 + default n 244 + depends on (SMP && !BFIN_SCRATCH_REG_RETN) 245 + help 246 + If any CPU in the system does not execute the period local timer 247 + interrupt for more than 5 seconds, then the NMI handler dumps debug 248 + information. This information can be used to debug the lockup. 249 + 241 250 config CPLB_INFO 242 251 bool "Display the CPLB information" 243 252 help
+4
arch/blackfin/include/asm/irq.h
··· 38 38 39 39 #include <asm-generic/irq.h> 40 40 41 + #ifdef CONFIG_NMI_WATCHDOG 42 + # define ARCH_HAS_NMI_WATCHDOG 43 + #endif 44 + 41 45 #endif /* _BFIN_IRQ_H_ */
+12
arch/blackfin/include/asm/nmi.h
··· 1 + /* 2 + * Copyright 2010 Analog Devices Inc. 3 + * 4 + * Licensed under the GPL-2 5 + */ 6 + 7 + #ifndef _BFIN_NMI_H_ 8 + #define _BFIN_NMI_H_ 9 + 10 + #include <linux/nmi.h> 11 + 12 + #endif
+1
arch/blackfin/include/asm/smp.h
··· 22 22 struct corelock_slot { 23 23 int lock; 24 24 }; 25 + extern struct corelock_slot corelock; 25 26 26 27 void smp_icache_flush_range_others(unsigned long start, 27 28 unsigned long end);
+1
arch/blackfin/kernel/Makefile
··· 25 25 obj-$(CONFIG_MODULES) += module.o 26 26 obj-$(CONFIG_KGDB) += kgdb.o 27 27 obj-$(CONFIG_KGDB_TESTS) += kgdb_test.o 28 + obj-$(CONFIG_NMI_WATCHDOG) += nmi.o 28 29 obj-$(CONFIG_EARLY_PRINTK) += early_printk.o 29 30 obj-$(CONFIG_EARLY_PRINTK) += shadow_console.o 30 31 obj-$(CONFIG_STACKTRACE) += stacktrace.o
+313
arch/blackfin/kernel/nmi.c
··· 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 */ 43 + static int nmi_wdt_cpu = 1; 44 + 45 + static unsigned int timeout = NMI_WDT_TIMEOUT; 46 + static int nmi_active; 47 + 48 + static unsigned short wdoga_ctl; 49 + static unsigned int wdoga_cnt; 50 + static struct corelock_slot saved_corelock; 51 + static atomic_t nmi_touched[NR_CPUS]; 52 + static struct timer_list ntimer; 53 + 54 + enum { 55 + COREA_ENTER_NMI = 0, 56 + COREA_EXIT_NMI, 57 + COREB_EXIT_NMI, 58 + 59 + NMI_EVENT_NR, 60 + }; 61 + static unsigned long nmi_event __attribute__ ((__section__(".l2.bss"))); 62 + 63 + /* we are in nmi, non-atomic bit ops is safe */ 64 + static inline void set_nmi_event(int event) 65 + { 66 + __set_bit(event, &nmi_event); 67 + } 68 + 69 + static 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 + 76 + static 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 + 86 + static 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 + 95 + static inline void save_corelock(void) 96 + { 97 + saved_corelock = corelock; 98 + corelock.lock = 0; 99 + } 100 + 101 + static inline void restore_corelock(void) 102 + { 103 + corelock = saved_corelock; 104 + } 105 + 106 + 107 + static inline void nmi_wdt_keepalive(void) 108 + { 109 + bfin_write_WDOGB_STAT(0); 110 + } 111 + 112 + static 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 */ 118 + static 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 + 124 + static inline void nmi_wdt_start(void) 125 + { 126 + bfin_write_WDOGB_CTL(WDEN_ENABLE | ICTL_NMI); 127 + } 128 + 129 + static inline int nmi_wdt_running(void) 130 + { 131 + return ((bfin_read_WDOGB_CTL() & WDEN_MASK) != WDEN_DISABLE); 132 + } 133 + 134 + static 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 + 158 + int 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 + 182 + static 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 + 190 + static 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 + } 204 + device_initcall(init_nmi_wdt); 205 + 206 + void touch_nmi_watchdog(void) 207 + { 208 + atomic_set(&nmi_touched[smp_processor_id()], 1); 209 + } 210 + 211 + /* Suspend/resume support */ 212 + #ifdef CONFIG_PM 213 + static int nmi_wdt_suspend(struct sys_device *dev, pm_message_t state) 214 + { 215 + nmi_wdt_stop(); 216 + return 0; 217 + } 218 + 219 + static int nmi_wdt_resume(struct sys_device *dev) 220 + { 221 + if (nmi_active) 222 + nmi_wdt_start(); 223 + return 0; 224 + } 225 + 226 + static struct sysdev_class nmi_sysclass = { 227 + .name = DRV_NAME, 228 + .resume = nmi_wdt_resume, 229 + .suspend = nmi_wdt_suspend, 230 + }; 231 + 232 + static struct sys_device device_nmi_wdt = { 233 + .id = 0, 234 + .cls = &nmi_sysclass, 235 + }; 236 + 237 + static 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 + } 249 + late_initcall(init_nmi_wdt_sysfs); 250 + 251 + #endif /* CONFIG_PM */ 252 + 253 + 254 + asmlinkage 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 + }
+4
arch/blackfin/kernel/time-ts.c
··· 21 21 #include <asm/blackfin.h> 22 22 #include <asm/time.h> 23 23 #include <asm/gptimers.h> 24 + #include <asm/nmi.h> 24 25 25 26 /* Accelerators for sched_clock() 26 27 * convert from cycles(64bits) => nanoseconds (64bits) ··· 310 309 311 310 smp_mb(); 312 311 evt->event_handler(evt); 312 + 313 + touch_nmi_watchdog(); 314 + 313 315 return IRQ_HANDLED; 314 316 } 315 317
+17 -1
arch/blackfin/mach-common/interrupt.S
··· 194 194 ENDPROC(_evt_ivhw) 195 195 196 196 /* Interrupt routine for evt2 (NMI). 197 - * We don't actually use this, so just return. 198 197 * For inner circle type details, please see: 199 198 * http://docs.blackfin.uclinux.org/doku.php?id=linux-kernel:nmi 200 199 */ 201 200 ENTRY(_evt_nmi) 201 + #ifndef CONFIG_NMI_WATCHDOG 202 202 .weak _evt_nmi 203 + #else 204 + /* Not take account of CPLBs, this handler will not return */ 205 + SAVE_ALL_SYS 206 + r0 = sp; 207 + r1 = retn; 208 + [sp + PT_PC] = r1; 209 + trace_buffer_save(p4,r5); 210 + 211 + ANOMALY_283_315_WORKAROUND(p4, r5) 212 + 213 + SP += -12; 214 + call _do_nmi; 215 + SP += 12; 216 + 1: 217 + jump 1b; 218 + #endif 203 219 rtn; 204 220 ENDPROC(_evt_nmi) 205 221