summaryrefslogtreecommitdiffstats
path: root/arch/mn10300/kernel/mn10300-watchdog.c
blob: 0d5641beadf5cecfe37addd4aa569acdf2be8fc5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/* MN10300 Watchdog timer
 *
 * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 * - Derived from arch/i386/kernel/nmi.c
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public Licence
 * as published by the Free Software Foundation; either version
 * 2 of the Licence, or (at your option) any later version.
 */
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/kernel_stat.h>
#include <linux/nmi.h>
#include <asm/processor.h>
#include <linux/atomic.h>
#include <asm/intctl-regs.h>
#include <asm/rtc-regs.h>
#include <asm/div64.h>
#include <asm/smp.h>
#include <asm/gdb-stub.h>
#include <proc/clock.h>

static DEFINE_SPINLOCK(watchdog_print_lock);
static unsigned int watchdog;
static unsigned int watchdog_hz = 1;
unsigned int watchdog_alert_counter[NR_CPUS];

EXPORT_SYMBOL(arch_touch_nmi_watchdog);

/*
 * the best way to detect whether a CPU has a 'hard lockup' problem
 * is to check its timer makes IRQ counts. If they are not
 * changing then that CPU has some problem.
 *
 * since NMIs dont listen to _any_ locks, we have to be extremely
 * careful not to rely on unsafe variables. The printk might lock
 * up though, so we have to break up any console locks first ...
 * [when there will be more tty-related locks, break them up
 *  here too!]
 */
static unsigned int last_irq_sums[NR_CPUS];

int __init check_watchdog(void)
{
	irq_cpustat_t tmp[1];

	printk(KERN_INFO "Testing Watchdog... ");

	memcpy(tmp, irq_stat, sizeof(tmp));
	local_irq_enable();
	mdelay((10 * 1000) / watchdog_hz); /* wait 10 ticks */
	local_irq_disable();

	if (nmi_count(0) - tmp[0].__nmi_count <= 5) {
		printk(KERN_WARNING "CPU#%d: Watchdog appears to be stuck!\n",
		       0);
		return -1;
	}

	printk(KERN_INFO "OK.\n");

	/* now that we know it works we can reduce NMI frequency to something
	 * more reasonable; makes a difference in some configs
	 */
	watchdog_hz = 1;

	return 0;
}

static int __init setup_watchdog(char *str)
{
	unsigned tmp;
	int opt;
	u8 ctr;

	get_option(&str, &opt);
	if (opt != 1)
		return 0;

	watchdog = opt;
	if (watchdog) {
		set_intr_stub(EXCEP_WDT, watchdog_handler);
		ctr = WDCTR_WDCK_65536th;
		WDCTR = WDCTR_WDRST | ctr;
		WDCTR = ctr;
		tmp = WDCTR;

		tmp = __muldiv64u(1 << (16 + ctr * 2), 1000000, MN10300_WDCLK);
		tmp = 1000000000 / tmp;
		watchdog_hz = (tmp + 500) / 1000;
	}

	return 1;
}

__setup("watchdog=", setup_watchdog);

void __init watchdog_go(void)
{
	u8 wdt;

	if (watchdog) {
		printk(KERN_INFO "Watchdog: running at %uHz\n", watchdog_hz);
		wdt = WDCTR & ~WDCTR_WDCNE;
		WDCTR = wdt | WDCTR_WDRST;
		wdt = WDCTR;
		WDCTR = wdt | WDCTR_WDCNE;
		wdt = WDCTR;

		check_watchdog();
	}
}

#ifdef CONFIG_SMP
static void watchdog_dump_register(void *dummy)
{
	printk(KERN_ERR "--- Register Dump (CPU%d) ---\n", CPUID);
	show_registers(current_frame());
}
#endif

asmlinkage
void watchdog_interrupt(struct pt_regs *regs, enum exception_code excep)
{
	/*
	 * Since current-> is always on the stack, and we always switch
	 * the stack NMI-atomically, it's safe to use smp_processor_id().
	 */
	int sum, cpu;
	int irq = NMIIRQ;
	u8 wdt, tmp;

	wdt = WDCTR & ~WDCTR_WDCNE;
	WDCTR = wdt;
	tmp = WDCTR;
	NMICR = NMICR_WDIF;

	nmi_count(smp_processor_id())++;
	kstat_incr_irq_this_cpu(irq);

	for_each_online_cpu(cpu) {

		sum = irq_stat[cpu].__irq_count;

		if ((last_irq_sums[cpu] == sum)
#if defined(CONFIG_GDBSTUB) && defined(CONFIG_SMP)
			&& !(CHK_GDBSTUB_BUSY()
			     || atomic_read(&cpu_doing_single_step))
#endif
			) {
			/*
			 * Ayiee, looks like this CPU is stuck ...
			 * wait a few IRQs (5 seconds) before doing the oops ...
			 */
			watchdog_alert_counter[cpu]++;
			if (watchdog_alert_counter[cpu] == 5 * watchdog_hz) {
				spin_lock(&watchdog_print_lock);
				/*
				 * We are in trouble anyway, lets at least try
				 * to get a message out.
				 */
				bust_spinlocks(1);
				printk(KERN_ERR
				       "NMI Watchdog detected LOCKUP on CPU%d,"
				       " pc %08lx, registers:\n",
				       cpu, regs->pc);
#ifdef CONFIG_SMP
				printk(KERN_ERR
				       "--- Register Dump (CPU%d) ---\n",
				       CPUID);
#endif
				show_registers(regs);
#ifdef CONFIG_SMP
				smp_nmi_call_function(watchdog_dump_register,
					NULL, 1);
#endif
				printk(KERN_NOTICE "console shuts up ...\n");
				console_silent();
				spin_unlock(&watchdog_print_lock);
				bust_spinlocks(0);
#ifdef CONFIG_GDBSTUB
				if (CHK_GDBSTUB_BUSY_AND_ACTIVE())
					gdbstub_exception(regs, excep);
				else
					gdbstub_intercept(regs, excep);
#endif
				do_exit(SIGSEGV);
			}
		} else {
			last_irq_sums[cpu] = sum;
			watchdog_alert_counter[cpu] = 0;
		}
	}

	WDCTR = wdt | WDCTR_WDRST;
	tmp = WDCTR;
	WDCTR = wdt | WDCTR_WDCNE;
	tmp = WDCTR;
}