xref: /inferno-os/os/boot/pc/clock.c (revision 8a8c2d742b51525f66c2210e3c8a251de10022ff)
1 #include	"u.h"
2 #include	"lib.h"
3 #include	"mem.h"
4 #include	"dat.h"
5 #include	"fns.h"
6 #include	"io.h"
7 #include	"ureg.h"
8 
9 /*
10  *  8253 timer
11  */
12 enum
13 {
14 	T0cntr=	0x40,		/* counter ports */
15 	T1cntr=	0x41,		/* ... */
16 	T2cntr=	0x42,		/* ... */
17 	Tmode=	0x43,		/* mode port */
18 
19 	/* commands */
20 	Latch0=	0x00,		/* latch counter 0's value */
21 	Load0=	0x30,		/* load counter 0 with 2 bytes */
22 
23 	/* modes */
24 	Square=	0x36,		/* perioic square wave */
25 
26 	Freq=	1193182,	/* Real clock frequency */
27 };
28 
29 static uvlong cpuhz = 66000000;
30 static int cpumhz = 66;
31 static int loopconst = 100;
32 int cpuidax, cpuiddx;
33 int havetsc;
34 
35 extern void _cycles(uvlong*);		/* in l.s */
36 extern void wrmsr(int, vlong);
37 
38 static void
clockintr(Ureg *,void *)39 clockintr(Ureg*, void*)
40 {
41 	m->ticks++;
42 	checkalarms();
43 }
44 
45 #define STEPPING(x)	((x)&0xf)
46 #define X86MODEL(x)	(((x)>>4)&0xf)
47 #define X86FAMILY(x)	(((x)>>8)&0xf)
48 
49 enum
50 {
51 	/* flags */
52 	CpuidFPU	= 0x001,	/* on-chip floating point unit */
53 	CpuidMCE	= 0x080,	/* machine check exception */
54 	CpuidCX8	= 0x100,	/* CMPXCHG8B instruction */
55 };
56 
57 typedef struct
58 {
59 	int family;
60 	int model;
61 	int aalcycles;
62 	char *name;
63 } X86type;
64 
65 X86type x86intel[] =
66 {
67 	{ 4,	0,	22,	"486DX", },	/* known chips */
68 	{ 4,	1,	22,	"486DX50", },
69 	{ 4,	2,	22,	"486SX", },
70 	{ 4,	3,	22,	"486DX2", },
71 	{ 4,	4,	22,	"486SL", },
72 	{ 4,	5,	22,	"486SX2", },
73 	{ 4,	7,	22,	"DX2WB", },	/* P24D */
74 	{ 4,	8,	22,	"DX4", },	/* P24C */
75 	{ 4,	9,	22,	"DX4WB", },	/* P24CT */
76 	{ 5,	0,	23,	"P5", },
77 	{ 5,	1,	23,	"P5", },
78 	{ 5,	2,	23,	"P54C", },
79 	{ 5,	3,	23,	"P24T", },
80 	{ 5,	4,	23,	"P55C MMX", },
81 	{ 5,	7,	23,	"P54C VRT", },
82 	{ 6,	1,	16,	"PentiumPro", },/* trial and error */
83 	{ 6,	3,	16,	"PentiumII", },
84 	{ 6,	5,	16,	"PentiumII/Xeon", },
85 	{ 6,	6,	16,	"Celeron", },
86 	{ 6,	7,	16,	"PentiumIII/Xeon", },
87 	{ 6,	8,	16,	"PentiumIII/Xeon", },
88 	{ 6,	0xB,	16,	"PentiumIII/Xeon", },
89 	{ 0xF,	1,	16,	"P4", },	/* P4 */
90 	{ 0xF,	2,	16,	"PentiumIV/Xeon", },
91 
92 	{ 3,	-1,	32,	"386", },	/* family defaults */
93 	{ 4,	-1,	22,	"486", },
94 	{ 5,	-1,	23,	"P5", },
95 	{ 6,	-1,	16,	"P6", },
96 	{ 0xF,	-1,	16,	"P4", },	/* P4 */
97 
98 	{ -1,	-1,	16,	"unknown", },	/* total default */
99 };
100 
101 
102 /*
103  * The AMD processors all implement the CPUID instruction.
104  * The later ones also return the processor name via functions
105  * 0x80000002, 0x80000003 and 0x80000004 in registers AX, BX, CX
106  * and DX:
107  *	K5	"AMD-K5(tm) Processor"
108  *	K6	"AMD-K6tm w/ multimedia extensions"
109  *	K6 3D	"AMD-K6(tm) 3D processor"
110  *	K6 3D+	?
111  */
112 static X86type x86amd[] =
113 {
114 	{ 5,	0,	23,	"AMD-K5", },	/* guesswork */
115 	{ 5,	1,	23,	"AMD-K5", },	/* guesswork */
116 	{ 5,	2,	23,	"AMD-K5", },	/* guesswork */
117 	{ 5,	3,	23,	"AMD-K5", },	/* guesswork */
118 	{ 5,	4,	23,	"AMD Geode GX1", },	/* guesswork */
119 	{ 5,	5,	23,	"AMD Geode GX2", },	/* guesswork */
120 	{ 5,	6,	11,	"AMD-K6", },	/* trial and error */
121 	{ 5,	7,	11,	"AMD-K6", },	/* trial and error */
122 	{ 5,	8,	11,	"AMD-K6-2", },	/* trial and error */
123 	{ 5,	9,	11,	"AMD-K6-III", },/* trial and error */
124 	{ 5,	0xa,	23,	"AMD Geode LX", },	/* guesswork */
125 
126 	{ 6,	1,	11,	"AMD-Athlon", },/* trial and error */
127 	{ 6,	2,	11,	"AMD-Athlon", },/* trial and error */
128 
129 	{ 4,	-1,	22,	"Am486", },	/* guesswork */
130 	{ 5,	-1,	23,	"AMD-K5/K6", },	/* guesswork */
131 	{ 6,	-1,	11,	"AMD-Athlon", },/* guesswork */
132 	{ 0xF,	-1,	11,	"AMD64", },	/* guesswork */
133 
134 	{ -1,	-1,	11,	"unknown", },	/* total default */
135 };
136 
137 static X86type	*cputype;
138 
139 
140 void
delay(int millisecs)141 delay(int millisecs)
142 {
143 	millisecs *= loopconst;
144 	if(millisecs <= 0)
145 		millisecs = 1;
146 	aamloop(millisecs);
147 }
148 
149 void
microdelay(int microsecs)150 microdelay(int microsecs)
151 {
152 	microsecs *= loopconst;
153 	microsecs /= 1000;
154 	if(microsecs <= 0)
155 		microsecs = 1;
156 	aamloop(microsecs);
157 }
158 
159 extern void cpuid(char*, int*, int*);
160 
161 X86type*
cpuidentify(void)162 cpuidentify(void)
163 {
164 	int family, model;
165 	X86type *t;
166 	char cpuidid[16];
167 	int cpuidax, cpuiddx;
168 
169 	cpuid(cpuidid, &cpuidax, &cpuiddx);
170 	if(strncmp(cpuidid, "AuthenticAMD", 12) == 0 ||
171 	   strncmp(cpuidid, "Geode by NSC", 12) == 0)
172 		t = x86amd;
173 	else
174 		t = x86intel;
175 	family = X86FAMILY(cpuidax);
176 	model = X86MODEL(cpuidax);
177 	if (0)
178 		print("cpuidentify: cpuidax 0x%ux cpuiddx 0x%ux\n",
179 			cpuidax, cpuiddx);
180 	while(t->name){
181 		if((t->family == family && t->model == model)
182 		|| (t->family == family && t->model == -1)
183 		|| (t->family == -1))
184 			break;
185 		t++;
186 	}
187 	if(t->name == nil)
188 		panic("cpuidentify");
189 
190 	if(cpuiddx & 0x10){
191 		havetsc = 1;
192 		if(cpuiddx & 0x20)
193 			wrmsr(0x10, 0);
194 	}
195 
196 	return t;
197 }
198 
199 void
clockinit(void)200 clockinit(void)
201 {
202 	uvlong a, b, cpufreq;
203 	int loops, incr, x, y;
204 	X86type *t;
205 
206 	/*
207 	 *  set vector for clock interrupts
208 	 */
209 	setvec(VectorCLOCK, clockintr, 0);
210 
211 	t = cpuidentify();
212 
213 	/*
214 	 *  set clock for 1/HZ seconds
215 	 */
216 	outb(Tmode, Load0|Square);
217 	outb(T0cntr, (Freq/HZ));	/* low byte */
218 	outb(T0cntr, (Freq/HZ)>>8);	/* high byte */
219 
220 	/*
221 	 * Introduce a little delay to make sure the count is
222 	 * latched and the timer is counting down; with a fast
223 	 * enough processor this may not be the case.
224 	 * The i8254 (which this probably is) has a read-back
225 	 * command which can be used to make sure the counting
226 	 * register has been written into the counting element.
227 	 */
228 	x = (Freq/HZ);
229 	for(loops = 0; loops < 100000 && x >= (Freq/HZ); loops++){
230 		outb(Tmode, Latch0);
231 		x = inb(T0cntr);
232 		x |= inb(T0cntr)<<8;
233 	}
234 
235 	/* find biggest loop that doesn't wrap */
236 	incr = 16000000/(t->aalcycles*HZ*2);
237 	x = 2000;
238 	for(loops = incr; loops < 64*1024; loops += incr) {
239 
240 		/*
241 		 *  measure time for the loop
242 		 *
243 		 *			MOVL	loops,CX
244 		 *	aaml1:	 	AAM
245 		 *			LOOP	aaml1
246 		 *
247 		 *  the time for the loop should be independent of external
248 		 *  cache and memory system since it fits in the execution
249 		 *  prefetch buffer.
250 		 *
251 		 */
252 		outb(Tmode, Latch0);
253 		if(havetsc)
254 			_cycles(&a);
255 		x = inb(T0cntr);
256 		x |= inb(T0cntr)<<8;
257 		aamloop(loops);
258 		outb(Tmode, Latch0);
259 		if(havetsc)
260 			_cycles(&b);
261 		y = inb(T0cntr);
262 		y |= inb(T0cntr)<<8;
263 		x -= y;
264 
265 		if(x < 0)
266 			x += Freq/HZ;
267 
268 		if(x > Freq/(3*HZ))
269 			break;
270 	}
271 
272 	/*
273  	 *  figure out clock frequency and a loop multiplier for delay().
274 	 *  counter  goes at twice the frequency, once per transition,
275 	 *  i.e., twice per square wave
276 	 */
277 	cpufreq = (vlong)loops*((t->aalcycles*2*Freq)/x);
278 	loopconst = (cpufreq/1000)/t->aalcycles;	/* AAM+LOOP's for 1 ms */
279 
280 	if(havetsc){
281 		/* counter goes up by 2*Freq */
282 		b = (b-a)<<1;
283 		b *= Freq;
284 		b /= x;
285 
286 		/*
287 		 *  round to the nearest megahz
288 		 */
289 		cpumhz = (b+500000)/1000000L;
290 		cpuhz = b;
291 	}
292 	else{
293 		/*
294 		 *  add in possible .5% error and convert to MHz
295 		 */
296 		cpumhz = (cpufreq + cpufreq/200)/1000000;
297 		cpuhz = cpufreq;
298 	}
299 
300 	if(debug){
301 		int timeo;
302 
303 		print("%dMHz %s loop %d\n", cpumhz, t->name, loopconst);
304 		print("tick...");
305 		for(timeo = 0; timeo < 10; timeo++)
306 			delay(1000);
307 		print("tock...\n");
308 	}
309 }
310