xref: /minix/minix/kernel/arch/i386/arch_watchdog.c (revision 35b65c5a)
1 #include "kernel/watchdog.h"
2 #include "glo.h"
3 #include <minix/minlib.h>
4 #include <minix/u64.h>
5 
6 #include "apic.h"
7 
8 #define CPUID_UNHALTED_CORE_CYCLES_AVAILABLE	0
9 
10 /*
11  * Intel architecture performance counters watchdog
12  */
13 
14 static struct arch_watchdog intel_arch_watchdog;
15 static struct arch_watchdog amd_watchdog;
16 
17 static void intel_arch_watchdog_init(const unsigned cpu)
18 {
19 	u64_t cpuf;
20 	u32_t val;
21 
22 	ia32_msr_write(INTEL_MSR_PERFMON_CRT0, 0, 0);
23 
24 	/* Int, OS, USR, Core ccyles */
25 	val = 1 << 20 | 1 << 17 | 1 << 16 | 0x3c;
26 	ia32_msr_write(INTEL_MSR_PERFMON_SEL0, 0, val);
27 
28 	/*
29 	 * should give as a tick approx. every 0.5-1s, the perf counter has only
30 	 * lowest 31 bits writable :(
31 	 */
32 	cpuf = cpu_get_freq(cpu);
33 	while (ex64hi(cpuf) || ex64lo(cpuf) > 0x7fffffffU)
34 		cpuf /= 2;
35 	cpuf = make64(-ex64lo(cpuf), ex64hi(cpuf));
36 	watchdog->resetval = watchdog->watchdog_resetval = cpuf;
37 
38 	ia32_msr_write(INTEL_MSR_PERFMON_CRT0, 0, ex64lo(cpuf));
39 
40 	ia32_msr_write(INTEL_MSR_PERFMON_SEL0, 0,
41 			val | INTEL_MSR_PERFMON_SEL0_ENABLE);
42 
43 	/* unmask the performance counter interrupt */
44 	lapic_write(LAPIC_LVTPCR, APIC_ICR_DM_NMI);
45 }
46 
47 static void intel_arch_watchdog_reinit(const unsigned cpu)
48 {
49 	lapic_write(LAPIC_LVTPCR, APIC_ICR_DM_NMI);
50 	ia32_msr_write(INTEL_MSR_PERFMON_CRT0, 0, ex64lo(watchdog->resetval));
51 }
52 
53 int arch_watchdog_init(void)
54 {
55 	u32_t eax, ebx, ecx, edx;
56 	unsigned cpu = cpuid;
57 
58 	if (!lapic_addr) {
59 		printf("ERROR : Cannot use NMI watchdog if APIC is not enabled\n");
60 		return -1;
61 	}
62 
63 	if (cpu_info[cpu].vendor == CPU_VENDOR_INTEL) {
64 		eax = 0xA;
65 
66 		_cpuid(&eax, &ebx, &ecx, &edx);
67 
68 		/* FIXME currently we support only watchdog based on the intel
69 		 * architectural performance counters. Some Intel CPUs don't have this
70 		 * feature
71 		 */
72 		if (ebx & (1 << CPUID_UNHALTED_CORE_CYCLES_AVAILABLE))
73 			return -1;
74 		if (!((((eax >> 8)) & 0xff) > 0))
75 			return -1;
76 
77 		watchdog = &intel_arch_watchdog;
78 	} else if (cpu_info[cpu].vendor == CPU_VENDOR_AMD) {
79 		if (cpu_info[cpu].family != 6 &&
80 				cpu_info[cpu].family != 15 &&
81 				cpu_info[cpu].family != 16 &&
82 				cpu_info[cpu].family != 17)
83 			return -1;
84 		else
85 			watchdog = &amd_watchdog;
86 	} else
87 		return -1;
88 
89 	/* Setup PC overflow as NMI for watchdog, it is masked for now */
90 	lapic_write(LAPIC_LVTPCR, APIC_ICR_INT_MASK | APIC_ICR_DM_NMI);
91 	(void) lapic_read(LAPIC_LVTPCR);
92 
93 	/* double check if LAPIC is enabled */
94 	if (lapic_addr && watchdog->init) {
95 		watchdog->init(cpuid);
96 	}
97 
98 	return 0;
99 }
100 
101 void arch_watchdog_stop(void)
102 {
103 }
104 
105 void arch_watchdog_lockup(const struct nmi_frame * frame)
106 {
107 	printf("KERNEL LOCK UP\n"
108 			"eax    0x%08x\n"
109 			"ecx    0x%08x\n"
110 			"edx    0x%08x\n"
111 			"ebx    0x%08x\n"
112 			"ebp    0x%08x\n"
113 			"esi    0x%08x\n"
114 			"edi    0x%08x\n"
115 			"gs     0x%08x\n"
116 			"fs     0x%08x\n"
117 			"es     0x%08x\n"
118 			"ds     0x%08x\n"
119 			"pc     0x%08x\n"
120 			"cs     0x%08x\n"
121 			"eflags 0x%08x\n",
122 			frame->eax,
123 			frame->ecx,
124 			frame->edx,
125 			frame->ebx,
126 			frame->ebp,
127 			frame->esi,
128 			frame->edi,
129 			frame->gs,
130 			frame->fs,
131 			frame->es,
132 			frame->ds,
133 			frame->pc,
134 			frame->cs,
135 			frame->eflags
136 			);
137 	panic("Kernel lockup");
138 }
139 
140 int i386_watchdog_start(void)
141 {
142 	if (arch_watchdog_init()) {
143 		printf("WARNING watchdog initialization "
144 				"failed! Disabled\n");
145 		watchdog_enabled = 0;
146 		return -1;
147 	}
148 	else
149 		BOOT_VERBOSE(printf("Watchdog enabled\n"););
150 
151 	return 0;
152 }
153 
154 static int intel_arch_watchdog_profile_init(const unsigned freq)
155 {
156 	u64_t cpuf;
157 
158 	/* FIXME works only if all CPUs have the same freq */
159 	cpuf = cpu_get_freq(cpuid);
160 	cpuf /= freq;
161 
162 	/*
163 	 * if freq is too low and the cpu freq too high we may get in a range of
164 	 * insane value which cannot be handled by the 31bit CPU perf counter
165 	 */
166 	if (ex64hi(cpuf) != 0 || ex64lo(cpuf) > 0x7fffffffU) {
167 		printf("ERROR : nmi watchdog ticks exceed 31bits, use higher frequency\n");
168 		return EINVAL;
169 	}
170 
171 	cpuf = make64(-ex64lo(cpuf), ex64hi(cpuf));
172 	watchdog->profile_resetval = cpuf;
173 
174 	return OK;
175 }
176 
177 static struct arch_watchdog intel_arch_watchdog = {
178 	/*.init = */		intel_arch_watchdog_init,
179 	/*.reinit = */		intel_arch_watchdog_reinit,
180 	/*.profile_init = */	intel_arch_watchdog_profile_init
181 };
182 
183 #define AMD_MSR_EVENT_SEL0		0xc0010000
184 #define AMD_MSR_EVENT_CTR0		0xc0010004
185 #define AMD_MSR_EVENT_SEL0_ENABLE	(1 << 22)
186 
187 static void amd_watchdog_init(const unsigned cpu)
188 {
189 	u64_t cpuf;
190 	u32_t val;
191 
192 	ia32_msr_write(AMD_MSR_EVENT_CTR0, 0, 0);
193 
194 	/* Int, OS, USR, Cycles cpu is running */
195 	val = 1 << 20 | 1 << 17 | 1 << 16 | 0x76;
196 	ia32_msr_write(AMD_MSR_EVENT_SEL0, 0, val);
197 
198 	cpuf = -cpu_get_freq(cpu);
199 	watchdog->resetval = watchdog->watchdog_resetval = cpuf;
200 
201 	ia32_msr_write(AMD_MSR_EVENT_CTR0,
202 		       ex64hi(watchdog->resetval), ex64lo(watchdog->resetval));
203 
204 	ia32_msr_write(AMD_MSR_EVENT_SEL0, 0,
205 			val | AMD_MSR_EVENT_SEL0_ENABLE);
206 
207 	/* unmask the performance counter interrupt */
208 	lapic_write(LAPIC_LVTPCR, APIC_ICR_DM_NMI);
209 }
210 
211 static void amd_watchdog_reinit(const unsigned cpu)
212 {
213 	lapic_write(LAPIC_LVTPCR, APIC_ICR_DM_NMI);
214 	ia32_msr_write(AMD_MSR_EVENT_CTR0,
215 		       ex64hi(watchdog->resetval), ex64lo(watchdog->resetval));
216 }
217 
218 static int amd_watchdog_profile_init(const unsigned freq)
219 {
220 	u64_t cpuf;
221 
222 	/* FIXME works only if all CPUs have the same freq */
223 	cpuf = cpu_get_freq(cpuid);
224 	cpuf = -cpuf / freq;
225 
226 	watchdog->profile_resetval = cpuf;
227 
228 	return OK;
229 }
230 
231 static struct arch_watchdog amd_watchdog = {
232 	/*.init = */		amd_watchdog_init,
233 	/*.reinit = */		amd_watchdog_reinit,
234 	/*.profile_init = */	amd_watchdog_profile_init
235 };
236