linux/arch/blackfin/kernel/nmi.c
<<
>>
Prefs
   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/syscore_ops.h>
  16#include <linux/pm.h>
  17#include <linux/nmi.h>
  18#include <linux/smp.h>
  19#include <linux/timer.h>
  20#include <linux/sched/debug.h>
  21#include <asm/blackfin.h>
  22#include <linux/atomic.h>
  23#include <asm/cacheflush.h>
  24#include <asm/bfin_watchdog.h>
  25
  26#define DRV_NAME "nmi-wdt"
  27
  28#define NMI_WDT_TIMEOUT 5          /* 5 seconds */
  29#define NMI_CHECK_TIMEOUT (4 * HZ) /* 4 seconds in jiffies */
  30static int nmi_wdt_cpu = 1;
  31
  32static unsigned int timeout = NMI_WDT_TIMEOUT;
  33static int nmi_active;
  34
  35static unsigned short wdoga_ctl;
  36static unsigned int wdoga_cnt;
  37static struct corelock_slot saved_corelock;
  38static atomic_t nmi_touched[NR_CPUS];
  39static struct timer_list ntimer;
  40
  41enum {
  42        COREA_ENTER_NMI = 0,
  43        COREA_EXIT_NMI,
  44        COREB_EXIT_NMI,
  45
  46        NMI_EVENT_NR,
  47};
  48static unsigned long nmi_event __attribute__ ((__section__(".l2.bss")));
  49
  50/* we are in nmi, non-atomic bit ops is safe */
  51static inline void set_nmi_event(int event)
  52{
  53        __set_bit(event, &nmi_event);
  54}
  55
  56static inline void wait_nmi_event(int event)
  57{
  58        while (!test_bit(event, &nmi_event))
  59                barrier();
  60        __clear_bit(event, &nmi_event);
  61}
  62
  63static inline void send_corea_nmi(void)
  64{
  65        wdoga_ctl = bfin_read_WDOGA_CTL();
  66        wdoga_cnt = bfin_read_WDOGA_CNT();
  67
  68        bfin_write_WDOGA_CTL(WDEN_DISABLE);
  69        bfin_write_WDOGA_CNT(0);
  70        bfin_write_WDOGA_CTL(WDEN_ENABLE | ICTL_NMI);
  71}
  72
  73static inline void restore_corea_nmi(void)
  74{
  75        bfin_write_WDOGA_CTL(WDEN_DISABLE);
  76        bfin_write_WDOGA_CTL(WDOG_EXPIRED | WDEN_DISABLE | ICTL_NONE);
  77
  78        bfin_write_WDOGA_CNT(wdoga_cnt);
  79        bfin_write_WDOGA_CTL(wdoga_ctl);
  80}
  81
  82static inline void save_corelock(void)
  83{
  84        saved_corelock = corelock;
  85        corelock.lock = 0;
  86}
  87
  88static inline void restore_corelock(void)
  89{
  90        corelock = saved_corelock;
  91}
  92
  93
  94static inline void nmi_wdt_keepalive(void)
  95{
  96        bfin_write_WDOGB_STAT(0);
  97}
  98
  99static inline void nmi_wdt_stop(void)
 100{
 101        bfin_write_WDOGB_CTL(WDEN_DISABLE);
 102}
 103
 104/* before calling this function, you must stop the WDT */
 105static inline void nmi_wdt_clear(void)
 106{
 107        /* clear TRO bit, disable event generation */
 108        bfin_write_WDOGB_CTL(WDOG_EXPIRED | WDEN_DISABLE | ICTL_NONE);
 109}
 110
 111static inline void nmi_wdt_start(void)
 112{
 113        bfin_write_WDOGB_CTL(WDEN_ENABLE | ICTL_NMI);
 114}
 115
 116static inline int nmi_wdt_running(void)
 117{
 118        return ((bfin_read_WDOGB_CTL() & WDEN_MASK) != WDEN_DISABLE);
 119}
 120
 121static inline int nmi_wdt_set_timeout(unsigned long t)
 122{
 123        u32 cnt, max_t, sclk;
 124        int run;
 125
 126        sclk = get_sclk();
 127        max_t = -1 / sclk;
 128        cnt = t * sclk;
 129        if (t > max_t) {
 130                pr_warning("NMI: timeout value is too large\n");
 131                return -EINVAL;
 132        }
 133
 134        run = nmi_wdt_running();
 135        nmi_wdt_stop();
 136        bfin_write_WDOGB_CNT(cnt);
 137        if (run)
 138                nmi_wdt_start();
 139
 140        timeout = t;
 141
 142        return 0;
 143}
 144
 145int check_nmi_wdt_touched(void)
 146{
 147        unsigned int this_cpu = smp_processor_id();
 148        unsigned int cpu;
 149        cpumask_t mask;
 150
 151        cpumask_copy(&mask, cpu_online_mask);
 152        if (!atomic_read(&nmi_touched[this_cpu]))
 153                return 0;
 154
 155        atomic_set(&nmi_touched[this_cpu], 0);
 156
 157        cpumask_clear_cpu(this_cpu, &mask);
 158        for_each_cpu(cpu, &mask) {
 159                invalidate_dcache_range((unsigned long)(&nmi_touched[cpu]),
 160                                (unsigned long)(&nmi_touched[cpu]));
 161                if (!atomic_read(&nmi_touched[cpu]))
 162                        return 0;
 163                atomic_set(&nmi_touched[cpu], 0);
 164        }
 165
 166        return 1;
 167}
 168
 169static void nmi_wdt_timer(unsigned long data)
 170{
 171        if (check_nmi_wdt_touched())
 172                nmi_wdt_keepalive();
 173
 174        mod_timer(&ntimer, jiffies + NMI_CHECK_TIMEOUT);
 175}
 176
 177static int __init init_nmi_wdt(void)
 178{
 179        nmi_wdt_set_timeout(timeout);
 180        nmi_wdt_start();
 181        nmi_active = true;
 182
 183        init_timer(&ntimer);
 184        ntimer.function = nmi_wdt_timer;
 185        ntimer.expires = jiffies + NMI_CHECK_TIMEOUT;
 186        add_timer(&ntimer);
 187
 188        pr_info("nmi_wdt: initialized: timeout=%d sec\n", timeout);
 189        return 0;
 190}
 191device_initcall(init_nmi_wdt);
 192
 193void arch_touch_nmi_watchdog(void)
 194{
 195        atomic_set(&nmi_touched[smp_processor_id()], 1);
 196}
 197
 198/* Suspend/resume support */
 199#ifdef CONFIG_PM
 200static int nmi_wdt_suspend(void)
 201{
 202        nmi_wdt_stop();
 203        return 0;
 204}
 205
 206static void nmi_wdt_resume(void)
 207{
 208        if (nmi_active)
 209                nmi_wdt_start();
 210}
 211
 212static struct syscore_ops nmi_syscore_ops = {
 213        .resume         = nmi_wdt_resume,
 214        .suspend        = nmi_wdt_suspend,
 215};
 216
 217static int __init init_nmi_wdt_syscore(void)
 218{
 219        if (nmi_active)
 220                register_syscore_ops(&nmi_syscore_ops);
 221
 222        return 0;
 223}
 224late_initcall(init_nmi_wdt_syscore);
 225
 226#endif  /* CONFIG_PM */
 227
 228
 229asmlinkage notrace void do_nmi(struct pt_regs *fp)
 230{
 231        unsigned int cpu = smp_processor_id();
 232        nmi_enter();
 233
 234        cpu_pda[cpu].__nmi_count += 1;
 235
 236        if (cpu == nmi_wdt_cpu) {
 237                /* CoreB goes here first */
 238
 239                /* reload the WDOG_STAT */
 240                nmi_wdt_keepalive();
 241
 242                /* clear nmi interrupt for CoreB */
 243                nmi_wdt_stop();
 244                nmi_wdt_clear();
 245
 246                /* trigger NMI interrupt of CoreA */
 247                send_corea_nmi();
 248
 249                /* waiting CoreB to enter NMI */
 250                wait_nmi_event(COREA_ENTER_NMI);
 251
 252                /* recover WDOGA's settings */
 253                restore_corea_nmi();
 254
 255                save_corelock();
 256
 257                /* corelock is save/cleared, CoreA is dummping messages */
 258
 259                wait_nmi_event(COREA_EXIT_NMI);
 260        } else {
 261                /* OK, CoreA entered NMI */
 262                set_nmi_event(COREA_ENTER_NMI);
 263        }
 264
 265        pr_emerg("\nNMI Watchdog detected LOCKUP, dump for CPU %d\n", cpu);
 266        dump_bfin_process(fp);
 267        dump_bfin_mem(fp);
 268        show_regs(fp);
 269        dump_bfin_trace_buffer();
 270        show_stack(current, (unsigned long *)fp);
 271
 272        if (cpu == nmi_wdt_cpu) {
 273                pr_emerg("This fault is not recoverable, sorry!\n");
 274
 275                /* CoreA dump finished, restore the corelock */
 276                restore_corelock();
 277
 278                set_nmi_event(COREB_EXIT_NMI);
 279        } else {
 280                /* CoreB dump finished, notice the CoreA we are done */
 281                set_nmi_event(COREA_EXIT_NMI);
 282
 283                /* synchronize with CoreA */
 284                wait_nmi_event(COREB_EXIT_NMI);
 285        }
 286
 287        nmi_exit();
 288}
 289