xref: /plan9-contrib/sys/src/9/pc/x86watchdog.c (revision 86abb9fb23a9f11dbfd9e6dc2fe0c20d62417d94)
1606e0002SDavid du Colombier /*
2606e0002SDavid du Colombier  * simulate independent hardware watch-dog timer
3606e0002SDavid du Colombier  * using local cpu timers and NMIs, one watch-dog per system.
4606e0002SDavid du Colombier  */
5606e0002SDavid du Colombier #include "u.h"
6606e0002SDavid du Colombier #include "../port/lib.h"
7606e0002SDavid du Colombier #include "mem.h"
8606e0002SDavid du Colombier #include "dat.h"
9606e0002SDavid du Colombier #include "fns.h"
10606e0002SDavid du Colombier #include "io.h"
11606e0002SDavid du Colombier #include "../port/error.h"
12606e0002SDavid du Colombier #include "../port/netif.h"
13606e0002SDavid du Colombier 
14606e0002SDavid du Colombier #include "mp.h"
15606e0002SDavid du Colombier 
16606e0002SDavid du Colombier typedef struct Wd Wd;
17606e0002SDavid du Colombier struct Wd {
18606e0002SDavid du Colombier 	Lock;
19606e0002SDavid du Colombier 	int	model;
20606e0002SDavid du Colombier 	int	inuse;
21606e0002SDavid du Colombier 	uint	ticks;
22606e0002SDavid du Colombier };
23606e0002SDavid du Colombier 
24606e0002SDavid du Colombier static Wd x86wd;
25606e0002SDavid du Colombier 
26606e0002SDavid du Colombier enum {
27606e0002SDavid du Colombier 	P6		= 0,			/* Pentium Pro/II/III */
28606e0002SDavid du Colombier 	P4		= 1,			/* P4 */
29606e0002SDavid du Colombier 	K6		= 2,			/* Athlon */
30606e0002SDavid du Colombier 	K8		= 3,			/* AMD64 */
31606e0002SDavid du Colombier 
32606e0002SDavid du Colombier 	Twogigs		= 1ul << 31,
33606e0002SDavid du Colombier };
34606e0002SDavid du Colombier 
35606e0002SDavid du Colombier /*
36606e0002SDavid du Colombier  * return an interval in cycles of about a second, or as long as
37606e0002SDavid du Colombier  * will fit in 31 bits.
38606e0002SDavid du Colombier  */
39606e0002SDavid du Colombier static long
interval(void)40606e0002SDavid du Colombier interval(void)
41606e0002SDavid du Colombier {
42606e0002SDavid du Colombier 	if (m->cpuhz > Twogigs - 1)
43606e0002SDavid du Colombier 		return Twogigs - 1;
44606e0002SDavid du Colombier 	else
45606e0002SDavid du Colombier 		return m->cpuhz;
46606e0002SDavid du Colombier }
47606e0002SDavid du Colombier 
48606e0002SDavid du Colombier static void
runoncpu(int cpu)49606e0002SDavid du Colombier runoncpu(int cpu)
50606e0002SDavid du Colombier {
51f54a2a50SDavid du Colombier 	if (m->machno != cpu) {
52f54a2a50SDavid du Colombier 		if (up == nil)
53f54a2a50SDavid du Colombier 			panic("x86watchdog: nil up");
54606e0002SDavid du Colombier 		procwired(up, cpu);
55606e0002SDavid du Colombier 		sched();
56f54a2a50SDavid du Colombier 		if (m->machno != cpu)
57f54a2a50SDavid du Colombier 			panic("x86watchdog: runoncpu: can't switch to cpu%d",
58f54a2a50SDavid du Colombier 				cpu);
59606e0002SDavid du Colombier 	}
60606e0002SDavid du Colombier }
61606e0002SDavid du Colombier 
62606e0002SDavid du Colombier static void
x86wdenable(void)63606e0002SDavid du Colombier x86wdenable(void)
64606e0002SDavid du Colombier {
65606e0002SDavid du Colombier 	Wd *wd;
66606e0002SDavid du Colombier 	vlong r, t;
67606e0002SDavid du Colombier 	int i, model;
68606e0002SDavid du Colombier 	u32int evntsel;
69606e0002SDavid du Colombier 
70606e0002SDavid du Colombier 	wd = &x86wd;
71*86abb9fbSDavid du Colombier 	ilock(wd);
72606e0002SDavid du Colombier 	if(wd->inuse){
73*86abb9fbSDavid du Colombier 		iunlock(wd);
74606e0002SDavid du Colombier 		error(Einuse);
75606e0002SDavid du Colombier 	}
76*86abb9fbSDavid du Colombier 	iunlock(wd);
77606e0002SDavid du Colombier 
78606e0002SDavid du Colombier 	/*
79606e0002SDavid du Colombier 	 * keep this process on cpu 0 so we always see the same timers
80606e0002SDavid du Colombier 	 * and so that this will work even if all other cpus are shut down.
81606e0002SDavid du Colombier 	 */
82606e0002SDavid du Colombier 	runoncpu(0);
83606e0002SDavid du Colombier 
84606e0002SDavid du Colombier 	/*
85606e0002SDavid du Colombier 	 * Check the processor is capable of doing performance
86606e0002SDavid du Colombier 	 * monitoring and that it has TSC, RDMSR/WRMSR and a local APIC.
87606e0002SDavid du Colombier 	 */
88606e0002SDavid du Colombier 	model = -1;
89606e0002SDavid du Colombier 	if(strncmp(m->cpuidid, "AuthenticAMD", 12) == 0){
90606e0002SDavid du Colombier 		if(X86FAMILY(m->cpuidax) == 0x06)
91606e0002SDavid du Colombier 			model = K6;
92606e0002SDavid du Colombier 		else if(X86FAMILY(m->cpuidax) == 0x0F)
93606e0002SDavid du Colombier 			model = K8;
94606e0002SDavid du Colombier 	}
95606e0002SDavid du Colombier 	else if(strncmp(m->cpuidid, "GenuineIntel", 12) == 0){
96606e0002SDavid du Colombier 		if(X86FAMILY(m->cpuidax) == 0x06)
97606e0002SDavid du Colombier 			model = P6;
98606e0002SDavid du Colombier 		else if(X86FAMILY(m->cpuidax) == 0x0F)
99606e0002SDavid du Colombier 			model = P4;
100606e0002SDavid du Colombier 	}
101606e0002SDavid du Colombier 	if(model == -1 ||
102606e0002SDavid du Colombier 	    (m->cpuiddx & (Cpuapic|Cpumsr|Tsc)) != (Cpuapic|Cpumsr|Tsc))
103606e0002SDavid du Colombier 		error(Enodev);
104606e0002SDavid du Colombier 
105*86abb9fbSDavid du Colombier 	ilock(wd);
106606e0002SDavid du Colombier 	if(wd->inuse){
107*86abb9fbSDavid du Colombier 		iunlock(wd);
108606e0002SDavid du Colombier 		error(Einuse);
109606e0002SDavid du Colombier 	}
110606e0002SDavid du Colombier 	wd->model = model;
111606e0002SDavid du Colombier 	wd->inuse = 1;
112606e0002SDavid du Colombier 	wd->ticks = 0;
113606e0002SDavid du Colombier 
114606e0002SDavid du Colombier 	/*
115606e0002SDavid du Colombier 	 * See the IA-32 Intel Architecture Software
116606e0002SDavid du Colombier 	 * Developer's Manual Volume 3: System Programming Guide,
117606e0002SDavid du Colombier 	 * Chapter 15 and the AMD equivalent for what all this
118606e0002SDavid du Colombier 	 * bit-whacking means.
119606e0002SDavid du Colombier 	 */
120606e0002SDavid du Colombier 	t = interval();
121606e0002SDavid du Colombier 	switch(model){
122606e0002SDavid du Colombier 	case P6:
123606e0002SDavid du Colombier 		wrmsr(0x186, 0);			/* evntsel */
124606e0002SDavid du Colombier 		wrmsr(0x187, 0);
125606e0002SDavid du Colombier 		wrmsr(0xC1, 0);				/* perfctr */
126606e0002SDavid du Colombier 		wrmsr(0xC2, 0);
127606e0002SDavid du Colombier 
128606e0002SDavid du Colombier 		lapicnmienable();
129606e0002SDavid du Colombier 
130606e0002SDavid du Colombier 		evntsel = 0x00130000|0x79;
131606e0002SDavid du Colombier 		wrmsr(0xC1, -t);
132606e0002SDavid du Colombier 		wrmsr(0x186, 0x00400000|evntsel);
133606e0002SDavid du Colombier 		break;
134606e0002SDavid du Colombier 	case P4:
135606e0002SDavid du Colombier 		rdmsr(0x1A0, &r);
136606e0002SDavid du Colombier 		if(!(r & 0x0000000000000080LL))
137606e0002SDavid du Colombier 			return;
138606e0002SDavid du Colombier 
139606e0002SDavid du Colombier 		for(i = 0; i < 18; i++)
140606e0002SDavid du Colombier 			wrmsr(0x300+i, 0);		/* perfctr */
141606e0002SDavid du Colombier 		for(i = 0; i < 18; i++)
142606e0002SDavid du Colombier 			wrmsr(0x360+i, 0);		/* ccr */
143606e0002SDavid du Colombier 
144606e0002SDavid du Colombier 		for(i = 0; i < 31; i++)
145606e0002SDavid du Colombier 			wrmsr(0x3A0+i, 0);		/* escr */
146606e0002SDavid du Colombier 		for(i = 0; i < 6; i++)
147606e0002SDavid du Colombier 			wrmsr(0x3C0+i, 0);		/* escr */
148606e0002SDavid du Colombier 		for(i = 0; i < 6; i++)
149606e0002SDavid du Colombier 			wrmsr(0x3C8+i, 0);		/* escr */
150606e0002SDavid du Colombier 		for(i = 0; i < 2; i++)
151606e0002SDavid du Colombier 			wrmsr(0x3E0+i, 0);		/* escr */
152606e0002SDavid du Colombier 
153606e0002SDavid du Colombier 		if(!(r & 0x0000000000001000LL)){
154606e0002SDavid du Colombier 			for(i = 0; i < 2; i++)
155606e0002SDavid du Colombier 				wrmsr(0x3F1+i, 0);	/* pebs */
156606e0002SDavid du Colombier 		}
157606e0002SDavid du Colombier 
158606e0002SDavid du Colombier 		lapicnmienable();
159606e0002SDavid du Colombier 
160606e0002SDavid du Colombier 		wrmsr(0x3B8, 0x000000007E00000CLL);	/* escr0 */
161606e0002SDavid du Colombier 		r = 0x0000000004FF8000ULL;
162606e0002SDavid du Colombier 		wrmsr(0x36C, r);			/* cccr0 */
163606e0002SDavid du Colombier 		wrmsr(0x30C, -t);
164606e0002SDavid du Colombier 		wrmsr(0x36C, 0x0000000000001000LL|r);
165606e0002SDavid du Colombier 		break;
166606e0002SDavid du Colombier 	case K6:
167606e0002SDavid du Colombier 	case K8:
168606e0002SDavid du Colombier 		/*
169606e0002SDavid du Colombier 		 * PerfEvtSel 0-3, PerfCtr 0-4.
170606e0002SDavid du Colombier 		 */
171606e0002SDavid du Colombier 		for(i = 0; i < 8; i++)
172606e0002SDavid du Colombier 			wrmsr(0xC0010000+i, 0);
173606e0002SDavid du Colombier 
174606e0002SDavid du Colombier 		lapicnmienable();
175606e0002SDavid du Colombier 
176606e0002SDavid du Colombier 		evntsel = 0x00130000|0x76;
177606e0002SDavid du Colombier 		wrmsr(0xC0010004, -t);
178606e0002SDavid du Colombier 		wrmsr(0xC0010000, 0x00400000|evntsel);
179606e0002SDavid du Colombier 		break;
180606e0002SDavid du Colombier 	}
181*86abb9fbSDavid du Colombier 	iunlock(wd);
182606e0002SDavid du Colombier }
183606e0002SDavid du Colombier 
184606e0002SDavid du Colombier static void
x86wddisable(void)185606e0002SDavid du Colombier x86wddisable(void)
186606e0002SDavid du Colombier {
187606e0002SDavid du Colombier 	Wd *wd;
188606e0002SDavid du Colombier 
189606e0002SDavid du Colombier 	wd = &x86wd;
190*86abb9fbSDavid du Colombier 	ilock(wd);
191606e0002SDavid du Colombier 	if(!wd->inuse){
192606e0002SDavid du Colombier 		/*
193606e0002SDavid du Colombier 		 * Can't error, called at boot by addwatchdog().
194606e0002SDavid du Colombier 		 */
195*86abb9fbSDavid du Colombier 		iunlock(wd);
196606e0002SDavid du Colombier 		return;
197606e0002SDavid du Colombier 	}
198*86abb9fbSDavid du Colombier 	iunlock(wd);
199606e0002SDavid du Colombier 
200606e0002SDavid du Colombier 	runoncpu(0);
201606e0002SDavid du Colombier 
202*86abb9fbSDavid du Colombier 	ilock(wd);
203606e0002SDavid du Colombier 	lapicnmidisable();
204606e0002SDavid du Colombier 	switch(wd->model){
205606e0002SDavid du Colombier 	case P6:
206606e0002SDavid du Colombier 		wrmsr(0x186, 0);
207606e0002SDavid du Colombier 		break;
208606e0002SDavid du Colombier 	case P4:
209606e0002SDavid du Colombier 		wrmsr(0x36C, 0);			/* cccr0 */
210606e0002SDavid du Colombier 		wrmsr(0x3B8, 0);			/* escr0 */
211606e0002SDavid du Colombier 		break;
212606e0002SDavid du Colombier 	case K6:
213606e0002SDavid du Colombier 	case K8:
214606e0002SDavid du Colombier 		wrmsr(0xC0010000, 0);
215606e0002SDavid du Colombier 		break;
216606e0002SDavid du Colombier 	}
217606e0002SDavid du Colombier 	wd->inuse = 0;
218*86abb9fbSDavid du Colombier 	iunlock(wd);
219606e0002SDavid du Colombier }
220606e0002SDavid du Colombier 
221606e0002SDavid du Colombier static void
x86wdrestart(void)222606e0002SDavid du Colombier x86wdrestart(void)
223606e0002SDavid du Colombier {
224606e0002SDavid du Colombier 	Wd *wd;
225606e0002SDavid du Colombier 	vlong r, t;
226606e0002SDavid du Colombier 
227606e0002SDavid du Colombier 	runoncpu(0);
228606e0002SDavid du Colombier 	t = interval();
229606e0002SDavid du Colombier 
230606e0002SDavid du Colombier 	wd = &x86wd;
231*86abb9fbSDavid du Colombier 	ilock(wd);
232606e0002SDavid du Colombier 	switch(wd->model){
233606e0002SDavid du Colombier 	case P6:
234606e0002SDavid du Colombier 		wrmsr(0xC1, -t);
235606e0002SDavid du Colombier 		break;
236606e0002SDavid du Colombier 	case P4:
237606e0002SDavid du Colombier 		r = 0x0000000004FF8000LL;
238606e0002SDavid du Colombier 		wrmsr(0x36C, r);
239606e0002SDavid du Colombier 		lapicnmienable();
240606e0002SDavid du Colombier 		wrmsr(0x30C, -t);
241606e0002SDavid du Colombier 		wrmsr(0x36C, 0x0000000000001000LL|r);
242606e0002SDavid du Colombier 		break;
243606e0002SDavid du Colombier 	case K6:
244606e0002SDavid du Colombier 	case K8:
245606e0002SDavid du Colombier 		wrmsr(0xC0010004, -t);
246606e0002SDavid du Colombier 		break;
247606e0002SDavid du Colombier 	}
248606e0002SDavid du Colombier 	wd->ticks++;
249*86abb9fbSDavid du Colombier 	iunlock(wd);
250606e0002SDavid du Colombier }
251606e0002SDavid du Colombier 
252606e0002SDavid du Colombier void
x86wdstat(char * p,char * ep)253606e0002SDavid du Colombier x86wdstat(char* p, char* ep)
254606e0002SDavid du Colombier {
255606e0002SDavid du Colombier 	Wd *wd;
256606e0002SDavid du Colombier 	int inuse;
257606e0002SDavid du Colombier 	uint ticks;
258606e0002SDavid du Colombier 
259606e0002SDavid du Colombier 	wd = &x86wd;
260*86abb9fbSDavid du Colombier 	ilock(wd);
261606e0002SDavid du Colombier 	inuse = wd->inuse;
262606e0002SDavid du Colombier 	ticks = wd->ticks;
263*86abb9fbSDavid du Colombier 	iunlock(wd);
264606e0002SDavid du Colombier 
265606e0002SDavid du Colombier 	if(inuse)
266606e0002SDavid du Colombier 		seprint(p, ep, "enabled %ud restarts\n", ticks);
267606e0002SDavid du Colombier 	else
268606e0002SDavid du Colombier 		seprint(p, ep, "disabled %ud restarts\n", ticks);
269606e0002SDavid du Colombier }
270606e0002SDavid du Colombier 
271606e0002SDavid du Colombier Watchdog x86watchdog = {
272606e0002SDavid du Colombier 	x86wdenable,
273606e0002SDavid du Colombier 	x86wddisable,
274606e0002SDavid du Colombier 	x86wdrestart,
275606e0002SDavid du Colombier 	x86wdstat,
276606e0002SDavid du Colombier };
277606e0002SDavid du Colombier 
278606e0002SDavid du Colombier void
x86watchdoglink(void)279606e0002SDavid du Colombier x86watchdoglink(void)
280606e0002SDavid du Colombier {
281606e0002SDavid du Colombier 	addwatchdog(&x86watchdog);
282606e0002SDavid du Colombier }
283