| 
									
										
										
										
											2010-01-20 10:56:24 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Blackfin nmi_watchdog Driver | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Originally based on bfin_wdt.c | 
					
						
							|  |  |  |  * Copyright 2010-2010 Analog Devices Inc. | 
					
						
							|  |  |  |  *		Graff Yang <graf.yang@analog.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Enter bugs at http://blackfin.uclinux.org/
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the GPL-2 or later. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include <linux/bitops.h>
 | 
					
						
							|  |  |  | #include <linux/hardirq.h>
 | 
					
						
							|  |  |  | #include <linux/sysdev.h>
 | 
					
						
							|  |  |  | #include <linux/pm.h>
 | 
					
						
							|  |  |  | #include <linux/nmi.h>
 | 
					
						
							|  |  |  | #include <linux/smp.h>
 | 
					
						
							|  |  |  | #include <linux/timer.h>
 | 
					
						
							|  |  |  | #include <asm/blackfin.h>
 | 
					
						
							|  |  |  | #include <asm/atomic.h>
 | 
					
						
							|  |  |  | #include <asm/cacheflush.h>
 | 
					
						
							| 
									
										
										
										
											2010-01-25 03:47:43 +00:00
										 |  |  | #include <asm/bfin_watchdog.h>
 | 
					
						
							| 
									
										
										
										
											2010-01-20 10:56:24 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | #define DRV_NAME "nmi-wdt"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define NMI_WDT_TIMEOUT 5          /* 5 seconds */
 | 
					
						
							|  |  |  | #define NMI_CHECK_TIMEOUT (4 * HZ) /* 4 seconds in jiffies */
 | 
					
						
							|  |  |  | static int nmi_wdt_cpu = 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static unsigned int timeout = NMI_WDT_TIMEOUT; | 
					
						
							|  |  |  | static int nmi_active; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static unsigned short wdoga_ctl; | 
					
						
							|  |  |  | static unsigned int wdoga_cnt; | 
					
						
							|  |  |  | static struct corelock_slot saved_corelock; | 
					
						
							|  |  |  | static atomic_t nmi_touched[NR_CPUS]; | 
					
						
							|  |  |  | static struct timer_list ntimer; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum { | 
					
						
							|  |  |  | 	COREA_ENTER_NMI = 0, | 
					
						
							|  |  |  | 	COREA_EXIT_NMI, | 
					
						
							|  |  |  | 	COREB_EXIT_NMI, | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	NMI_EVENT_NR, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | static unsigned long nmi_event __attribute__ ((__section__(".l2.bss"))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* we are in nmi, non-atomic bit ops is safe */ | 
					
						
							|  |  |  | static inline void set_nmi_event(int event) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	__set_bit(event, &nmi_event); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void wait_nmi_event(int event) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	while (!test_bit(event, &nmi_event)) | 
					
						
							|  |  |  | 		barrier(); | 
					
						
							|  |  |  | 	__clear_bit(event, &nmi_event); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void send_corea_nmi(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	wdoga_ctl = bfin_read_WDOGA_CTL(); | 
					
						
							|  |  |  | 	wdoga_cnt = bfin_read_WDOGA_CNT(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bfin_write_WDOGA_CTL(WDEN_DISABLE); | 
					
						
							|  |  |  | 	bfin_write_WDOGA_CNT(0); | 
					
						
							|  |  |  | 	bfin_write_WDOGA_CTL(WDEN_ENABLE | ICTL_NMI); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void restore_corea_nmi(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	bfin_write_WDOGA_CTL(WDEN_DISABLE); | 
					
						
							|  |  |  | 	bfin_write_WDOGA_CTL(WDOG_EXPIRED | WDEN_DISABLE | ICTL_NONE); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	bfin_write_WDOGA_CNT(wdoga_cnt); | 
					
						
							|  |  |  | 	bfin_write_WDOGA_CTL(wdoga_ctl); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void save_corelock(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	saved_corelock = corelock; | 
					
						
							|  |  |  | 	corelock.lock = 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void restore_corelock(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	corelock = saved_corelock; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void nmi_wdt_keepalive(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	bfin_write_WDOGB_STAT(0); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void nmi_wdt_stop(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	bfin_write_WDOGB_CTL(WDEN_DISABLE); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* before calling this function, you must stop the WDT */ | 
					
						
							|  |  |  | static inline void nmi_wdt_clear(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	/* clear TRO bit, disable event generation */ | 
					
						
							|  |  |  | 	bfin_write_WDOGB_CTL(WDOG_EXPIRED | WDEN_DISABLE | ICTL_NONE); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline void nmi_wdt_start(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	bfin_write_WDOGB_CTL(WDEN_ENABLE | ICTL_NMI); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline int nmi_wdt_running(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return ((bfin_read_WDOGB_CTL() & WDEN_MASK) != WDEN_DISABLE); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static inline int nmi_wdt_set_timeout(unsigned long t) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u32 cnt, max_t, sclk; | 
					
						
							|  |  |  | 	int run; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	sclk = get_sclk(); | 
					
						
							|  |  |  | 	max_t = -1 / sclk; | 
					
						
							|  |  |  | 	cnt = t * sclk; | 
					
						
							|  |  |  | 	if (t > max_t) { | 
					
						
							|  |  |  | 		pr_warning("NMI: timeout value is too large\n"); | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	run = nmi_wdt_running(); | 
					
						
							|  |  |  | 	nmi_wdt_stop(); | 
					
						
							|  |  |  | 	bfin_write_WDOGB_CNT(cnt); | 
					
						
							|  |  |  | 	if (run) | 
					
						
							|  |  |  | 		nmi_wdt_start(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	timeout = t; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int check_nmi_wdt_touched(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned int this_cpu = smp_processor_id(); | 
					
						
							|  |  |  | 	unsigned int cpu; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cpumask_t mask = cpu_online_map; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!atomic_read(&nmi_touched[this_cpu])) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	atomic_set(&nmi_touched[this_cpu], 0); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cpu_clear(this_cpu, mask); | 
					
						
							|  |  |  | 	for_each_cpu_mask(cpu, mask) { | 
					
						
							|  |  |  | 		invalidate_dcache_range((unsigned long)(&nmi_touched[cpu]), | 
					
						
							|  |  |  | 				(unsigned long)(&nmi_touched[cpu])); | 
					
						
							|  |  |  | 		if (!atomic_read(&nmi_touched[cpu])) | 
					
						
							|  |  |  | 			return 0; | 
					
						
							|  |  |  | 		atomic_set(&nmi_touched[cpu], 0); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 1; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void nmi_wdt_timer(unsigned long data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (check_nmi_wdt_touched()) | 
					
						
							|  |  |  | 		nmi_wdt_keepalive(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	mod_timer(&ntimer, jiffies + NMI_CHECK_TIMEOUT); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __init init_nmi_wdt(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	nmi_wdt_set_timeout(timeout); | 
					
						
							|  |  |  | 	nmi_wdt_start(); | 
					
						
							|  |  |  | 	nmi_active = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	init_timer(&ntimer); | 
					
						
							|  |  |  | 	ntimer.function = nmi_wdt_timer; | 
					
						
							|  |  |  | 	ntimer.expires = jiffies + NMI_CHECK_TIMEOUT; | 
					
						
							|  |  |  | 	add_timer(&ntimer); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_info("nmi_wdt: initialized: timeout=%d sec\n", timeout); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | device_initcall(init_nmi_wdt); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void touch_nmi_watchdog(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	atomic_set(&nmi_touched[smp_processor_id()], 1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Suspend/resume support */ | 
					
						
							|  |  |  | #ifdef CONFIG_PM
 | 
					
						
							|  |  |  | static int nmi_wdt_suspend(struct sys_device *dev, pm_message_t state) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	nmi_wdt_stop(); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int nmi_wdt_resume(struct sys_device *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (nmi_active) | 
					
						
							|  |  |  | 		nmi_wdt_start(); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct sysdev_class nmi_sysclass = { | 
					
						
							|  |  |  | 	.name		= DRV_NAME, | 
					
						
							|  |  |  | 	.resume		= nmi_wdt_resume, | 
					
						
							|  |  |  | 	.suspend	= nmi_wdt_suspend, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static struct sys_device device_nmi_wdt = { | 
					
						
							|  |  |  | 	.id	= 0, | 
					
						
							|  |  |  | 	.cls	= &nmi_sysclass, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int __init init_nmi_wdt_sysfs(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	int error; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!nmi_active) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	error = sysdev_class_register(&nmi_sysclass); | 
					
						
							|  |  |  | 	if (!error) | 
					
						
							|  |  |  | 		error = sysdev_register(&device_nmi_wdt); | 
					
						
							|  |  |  | 	return error; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | late_initcall(init_nmi_wdt_sysfs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #endif	/* CONFIG_PM */
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | asmlinkage notrace void do_nmi(struct pt_regs *fp) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	unsigned int cpu = smp_processor_id(); | 
					
						
							|  |  |  | 	nmi_enter(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	cpu_pda[cpu].__nmi_count += 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (cpu == nmi_wdt_cpu) { | 
					
						
							|  |  |  | 		/* CoreB goes here first */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* reload the WDOG_STAT */ | 
					
						
							|  |  |  | 		nmi_wdt_keepalive(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* clear nmi interrupt for CoreB */ | 
					
						
							|  |  |  | 		nmi_wdt_stop(); | 
					
						
							|  |  |  | 		nmi_wdt_clear(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* trigger NMI interrupt of CoreA */ | 
					
						
							|  |  |  | 		send_corea_nmi(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* waiting CoreB to enter NMI */ | 
					
						
							|  |  |  | 		wait_nmi_event(COREA_ENTER_NMI); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* recover WDOGA's settings */ | 
					
						
							|  |  |  | 		restore_corea_nmi(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		save_corelock(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* corelock is save/cleared, CoreA is dummping messages */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		wait_nmi_event(COREA_EXIT_NMI); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		/* OK, CoreA entered NMI */ | 
					
						
							|  |  |  | 		set_nmi_event(COREA_ENTER_NMI); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	pr_emerg("\nNMI Watchdog detected LOCKUP, dump for CPU %d\n", cpu); | 
					
						
							|  |  |  | 	dump_bfin_process(fp); | 
					
						
							|  |  |  | 	dump_bfin_mem(fp); | 
					
						
							|  |  |  | 	show_regs(fp); | 
					
						
							|  |  |  | 	dump_bfin_trace_buffer(); | 
					
						
							|  |  |  | 	show_stack(current, (unsigned long *)fp); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (cpu == nmi_wdt_cpu) { | 
					
						
							|  |  |  | 		pr_emerg("This fault is not recoverable, sorry!\n"); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* CoreA dump finished, restore the corelock */ | 
					
						
							|  |  |  | 		restore_corelock(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		set_nmi_event(COREB_EXIT_NMI); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		/* CoreB dump finished, notice the CoreA we are done */ | 
					
						
							|  |  |  | 		set_nmi_event(COREA_EXIT_NMI); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		/* synchronize with CoreA */ | 
					
						
							|  |  |  | 		wait_nmi_event(COREB_EXIT_NMI); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	nmi_exit(); | 
					
						
							|  |  |  | } |