xref: /plan9-contrib/sys/src/9/pc/x86watchdog.c (revision 86abb9fb23a9f11dbfd9e6dc2fe0c20d62417d94)
1 /*
2  * simulate independent hardware watch-dog timer
3  * using local cpu timers and NMIs, one watch-dog per system.
4  */
5 #include "u.h"
6 #include "../port/lib.h"
7 #include "mem.h"
8 #include "dat.h"
9 #include "fns.h"
10 #include "io.h"
11 #include "../port/error.h"
12 #include "../port/netif.h"
13 
14 #include "mp.h"
15 
16 typedef struct Wd Wd;
17 struct Wd {
18 	Lock;
19 	int	model;
20 	int	inuse;
21 	uint	ticks;
22 };
23 
24 static Wd x86wd;
25 
26 enum {
27 	P6		= 0,			/* Pentium Pro/II/III */
28 	P4		= 1,			/* P4 */
29 	K6		= 2,			/* Athlon */
30 	K8		= 3,			/* AMD64 */
31 
32 	Twogigs		= 1ul << 31,
33 };
34 
35 /*
36  * return an interval in cycles of about a second, or as long as
37  * will fit in 31 bits.
38  */
39 static long
interval(void)40 interval(void)
41 {
42 	if (m->cpuhz > Twogigs - 1)
43 		return Twogigs - 1;
44 	else
45 		return m->cpuhz;
46 }
47 
48 static void
runoncpu(int cpu)49 runoncpu(int cpu)
50 {
51 	if (m->machno != cpu) {
52 		if (up == nil)
53 			panic("x86watchdog: nil up");
54 		procwired(up, cpu);
55 		sched();
56 		if (m->machno != cpu)
57 			panic("x86watchdog: runoncpu: can't switch to cpu%d",
58 				cpu);
59 	}
60 }
61 
62 static void
x86wdenable(void)63 x86wdenable(void)
64 {
65 	Wd *wd;
66 	vlong r, t;
67 	int i, model;
68 	u32int evntsel;
69 
70 	wd = &x86wd;
71 	ilock(wd);
72 	if(wd->inuse){
73 		iunlock(wd);
74 		error(Einuse);
75 	}
76 	iunlock(wd);
77 
78 	/*
79 	 * keep this process on cpu 0 so we always see the same timers
80 	 * and so that this will work even if all other cpus are shut down.
81 	 */
82 	runoncpu(0);
83 
84 	/*
85 	 * Check the processor is capable of doing performance
86 	 * monitoring and that it has TSC, RDMSR/WRMSR and a local APIC.
87 	 */
88 	model = -1;
89 	if(strncmp(m->cpuidid, "AuthenticAMD", 12) == 0){
90 		if(X86FAMILY(m->cpuidax) == 0x06)
91 			model = K6;
92 		else if(X86FAMILY(m->cpuidax) == 0x0F)
93 			model = K8;
94 	}
95 	else if(strncmp(m->cpuidid, "GenuineIntel", 12) == 0){
96 		if(X86FAMILY(m->cpuidax) == 0x06)
97 			model = P6;
98 		else if(X86FAMILY(m->cpuidax) == 0x0F)
99 			model = P4;
100 	}
101 	if(model == -1 ||
102 	    (m->cpuiddx & (Cpuapic|Cpumsr|Tsc)) != (Cpuapic|Cpumsr|Tsc))
103 		error(Enodev);
104 
105 	ilock(wd);
106 	if(wd->inuse){
107 		iunlock(wd);
108 		error(Einuse);
109 	}
110 	wd->model = model;
111 	wd->inuse = 1;
112 	wd->ticks = 0;
113 
114 	/*
115 	 * See the IA-32 Intel Architecture Software
116 	 * Developer's Manual Volume 3: System Programming Guide,
117 	 * Chapter 15 and the AMD equivalent for what all this
118 	 * bit-whacking means.
119 	 */
120 	t = interval();
121 	switch(model){
122 	case P6:
123 		wrmsr(0x186, 0);			/* evntsel */
124 		wrmsr(0x187, 0);
125 		wrmsr(0xC1, 0);				/* perfctr */
126 		wrmsr(0xC2, 0);
127 
128 		lapicnmienable();
129 
130 		evntsel = 0x00130000|0x79;
131 		wrmsr(0xC1, -t);
132 		wrmsr(0x186, 0x00400000|evntsel);
133 		break;
134 	case P4:
135 		rdmsr(0x1A0, &r);
136 		if(!(r & 0x0000000000000080LL))
137 			return;
138 
139 		for(i = 0; i < 18; i++)
140 			wrmsr(0x300+i, 0);		/* perfctr */
141 		for(i = 0; i < 18; i++)
142 			wrmsr(0x360+i, 0);		/* ccr */
143 
144 		for(i = 0; i < 31; i++)
145 			wrmsr(0x3A0+i, 0);		/* escr */
146 		for(i = 0; i < 6; i++)
147 			wrmsr(0x3C0+i, 0);		/* escr */
148 		for(i = 0; i < 6; i++)
149 			wrmsr(0x3C8+i, 0);		/* escr */
150 		for(i = 0; i < 2; i++)
151 			wrmsr(0x3E0+i, 0);		/* escr */
152 
153 		if(!(r & 0x0000000000001000LL)){
154 			for(i = 0; i < 2; i++)
155 				wrmsr(0x3F1+i, 0);	/* pebs */
156 		}
157 
158 		lapicnmienable();
159 
160 		wrmsr(0x3B8, 0x000000007E00000CLL);	/* escr0 */
161 		r = 0x0000000004FF8000ULL;
162 		wrmsr(0x36C, r);			/* cccr0 */
163 		wrmsr(0x30C, -t);
164 		wrmsr(0x36C, 0x0000000000001000LL|r);
165 		break;
166 	case K6:
167 	case K8:
168 		/*
169 		 * PerfEvtSel 0-3, PerfCtr 0-4.
170 		 */
171 		for(i = 0; i < 8; i++)
172 			wrmsr(0xC0010000+i, 0);
173 
174 		lapicnmienable();
175 
176 		evntsel = 0x00130000|0x76;
177 		wrmsr(0xC0010004, -t);
178 		wrmsr(0xC0010000, 0x00400000|evntsel);
179 		break;
180 	}
181 	iunlock(wd);
182 }
183 
184 static void
x86wddisable(void)185 x86wddisable(void)
186 {
187 	Wd *wd;
188 
189 	wd = &x86wd;
190 	ilock(wd);
191 	if(!wd->inuse){
192 		/*
193 		 * Can't error, called at boot by addwatchdog().
194 		 */
195 		iunlock(wd);
196 		return;
197 	}
198 	iunlock(wd);
199 
200 	runoncpu(0);
201 
202 	ilock(wd);
203 	lapicnmidisable();
204 	switch(wd->model){
205 	case P6:
206 		wrmsr(0x186, 0);
207 		break;
208 	case P4:
209 		wrmsr(0x36C, 0);			/* cccr0 */
210 		wrmsr(0x3B8, 0);			/* escr0 */
211 		break;
212 	case K6:
213 	case K8:
214 		wrmsr(0xC0010000, 0);
215 		break;
216 	}
217 	wd->inuse = 0;
218 	iunlock(wd);
219 }
220 
221 static void
x86wdrestart(void)222 x86wdrestart(void)
223 {
224 	Wd *wd;
225 	vlong r, t;
226 
227 	runoncpu(0);
228 	t = interval();
229 
230 	wd = &x86wd;
231 	ilock(wd);
232 	switch(wd->model){
233 	case P6:
234 		wrmsr(0xC1, -t);
235 		break;
236 	case P4:
237 		r = 0x0000000004FF8000LL;
238 		wrmsr(0x36C, r);
239 		lapicnmienable();
240 		wrmsr(0x30C, -t);
241 		wrmsr(0x36C, 0x0000000000001000LL|r);
242 		break;
243 	case K6:
244 	case K8:
245 		wrmsr(0xC0010004, -t);
246 		break;
247 	}
248 	wd->ticks++;
249 	iunlock(wd);
250 }
251 
252 void
x86wdstat(char * p,char * ep)253 x86wdstat(char* p, char* ep)
254 {
255 	Wd *wd;
256 	int inuse;
257 	uint ticks;
258 
259 	wd = &x86wd;
260 	ilock(wd);
261 	inuse = wd->inuse;
262 	ticks = wd->ticks;
263 	iunlock(wd);
264 
265 	if(inuse)
266 		seprint(p, ep, "enabled %ud restarts\n", ticks);
267 	else
268 		seprint(p, ep, "disabled %ud restarts\n", ticks);
269 }
270 
271 Watchdog x86watchdog = {
272 	x86wdenable,
273 	x86wddisable,
274 	x86wdrestart,
275 	x86wdstat,
276 };
277 
278 void
x86watchdoglink(void)279 x86watchdoglink(void)
280 {
281 	addwatchdog(&x86watchdog);
282 }
283