xref: /plan9-contrib/sys/src/9/pc/i8253.c (revision a650be7d984b0294fadd12cd786574e09003d20c)
1 #include "u.h"
2 #include "../port/lib.h"
3 #include "mem.h"
4 #include "dat.h"
5 #include "fns.h"
6 #include "io.h"
7 
8 /*
9  *  8253 timer
10  */
11 enum
12 {
13 	T0cntr=	0x40,		/* counter ports */
14 	T1cntr=	0x41,		/* ... */
15 	T2cntr=	0x42,		/* ... */
16 	Tmode=	0x43,		/* mode port (control word register) */
17 	T2ctl=	0x61,		/* counter 2 control port */
18 
19 	/* commands */
20 	Latch0=	0x00,		/* latch counter 0's value */
21 	Load0l=	0x10,		/* load counter 0's lsb */
22 	Load0m=	0x20,		/* load counter 0's msb */
23 	Load0=	0x30,		/* load counter 0 with 2 bytes */
24 
25 	Latch1=	0x40,		/* latch counter 1's value */
26 	Load1l=	0x50,		/* load counter 1's lsb */
27 	Load1m=	0x60,		/* load counter 1's msb */
28 	Load1=	0x70,		/* load counter 1 with 2 bytes */
29 
30 	Latch2=	0x80,		/* latch counter 2's value */
31 	Load2l=	0x90,		/* load counter 2's lsb */
32 	Load2m=	0xa0,		/* load counter 2's msb */
33 	Load2=	0xb0,		/* load counter 2 with 2 bytes */
34 
35 	/* 8254 read-back command: everything > pc-at has an 8254 */
36 	Rdback=	0xc0,		/* readback counters & status */
37 	Rdnstat=0x10,		/* don't read status */
38 	Rdncnt=	0x20,		/* don't read counter value */
39 	Rd0cntr=0x02,		/* read back for which counter */
40 	Rd1cntr=0x04,
41 	Rd2cntr=0x08,
42 
43 	/* modes */
44 	ModeMsk=0xe,
45 	Square=	0x6,		/* periodic square wave */
46 	Trigger=0x0,		/* interrupt on terminal count */
47 	Sstrobe=0x8,		/* software triggered strobe */
48 
49 	/* T2ctl bits */
50 	T2gate=	(1<<0),		/* enable T2 counting */
51 	T2spkr=	(1<<1),		/* connect T2 out to speaker */
52 	T2out=	(1<<5),		/* output of T2 */
53 
54 	Freq=	1193182,	/* Real clock frequency */
55 	Tickshift=8,		/* extra accuracy */
56 	MaxPeriod=Freq/HZ,
57 	MinPeriod=Freq/(100*HZ),
58 
59 	Wdogms	= 200,		/* ms between strokes */
60 };
61 
62 typedef struct I8253 I8253;
63 struct I8253
64 {
65 	Lock;
66 	ulong	period;		/* current clock period */
67 	int	enabled;
68 	uvlong	hz;
69 
70 	ushort	last;		/* last value of clock 1 */
71 	uvlong	ticks;		/* cumulative ticks of counter 1 */
72 
73 	ulong	periodset;
74 };
75 I8253 i8253;
76 
77 void
i8253init(void)78 i8253init(void)
79 {
80 	int loops, x;
81 
82 	ioalloc(T0cntr, 4, 0, "i8253");
83 	ioalloc(T2ctl, 1, 0, "i8253.cntr2ctl");
84 
85 	i8253.period = Freq/HZ;
86 
87 	/*
88 	 *  enable a 1/HZ interrupt for providing scheduling interrupts
89 	 */
90 	outb(Tmode, Load0|Square);
91 	outb(T0cntr, (Freq/HZ));	/* low byte */
92 	outb(T0cntr, (Freq/HZ)>>8);	/* high byte */
93 
94 	/*
95 	 *  enable a longer period counter to use as a clock
96 	 */
97 	outb(Tmode, Load2|Square);
98 	outb(T2cntr, 0);		/* low byte */
99 	outb(T2cntr, 0);		/* high byte */
100 	x = inb(T2ctl);
101 	x |= T2gate;
102 	outb(T2ctl, x);
103 
104 	/*
105 	 * Introduce a little delay to make sure the count is
106 	 * latched and the timer is counting down; with a fast
107 	 * enough processor this may not be the case.
108 	 * The i8254 (which this probably is) has a read-back
109 	 * command which can be used to make sure the counting
110 	 * register has been written into the counting element.
111 	 */
112 	x = (Freq/HZ);
113 	for(loops = 0; loops < 100000 && x >= (Freq/HZ); loops++){
114 		outb(Tmode, Latch0);
115 		x = inb(T0cntr);
116 		x |= inb(T0cntr)<<8;
117 	}
118 }
119 
120 /*
121  * if the watchdog is running and we're on cpu 0 and ignoring (clock)
122  * interrupts, disable the watchdog temporarily so that the (presumed)
123  * long-running loop to follow will not trigger an NMI.
124  * wdogresume restarts the watchdog if wdogpause stopped it.
125  */
126 static int
wdogpause(void)127 wdogpause(void)
128 {
129 	int turndogoff;
130 
131 	turndogoff = watchdogon && m->machno == 0 && !islo();
132 	if (turndogoff) {
133 		watchdog->disable();
134 		watchdogon = 0;
135 	}
136 	return turndogoff;
137 }
138 
139 static void
wdogresume(int resume)140 wdogresume(int resume)
141 {
142 	if (resume) {
143 		watchdog->enable();
144 		watchdogon = 1;
145 	}
146 }
147 
148 void
guesscpuhz(int aalcycles)149 guesscpuhz(int aalcycles)
150 {
151 	int loops, incr, x, y, dogwason;
152 	uvlong a, b, cpufreq;
153 
154 	dogwason = wdogpause();		/* don't get NMI while busy looping */
155 
156 	/* find biggest loop that doesn't wrap */
157 	incr = 16000000/(aalcycles*HZ*2);
158 	x = 2000;
159 	for(loops = incr; loops < 64*1024; loops += incr) {
160 
161 		/*
162 		 *  measure time for the loop
163 		 *
164 		 *			MOVL	loops,CX
165 		 *	aaml1:	 	AAM
166 		 *			LOOP	aaml1
167 		 *
168 		 *  the time for the loop should be independent of external
169 		 *  cache and memory system since it fits in the execution
170 		 *  prefetch buffer.
171 		 *
172 		 */
173 		outb(Tmode, Latch0);
174 		cycles(&a);
175 		x = inb(T0cntr);
176 		x |= inb(T0cntr)<<8;
177 		aamloop(loops);
178 		outb(Tmode, Latch0);
179 		cycles(&b);
180 		y = inb(T0cntr);
181 		y |= inb(T0cntr)<<8;
182 		x -= y;
183 
184 		if(x < 0)
185 			x += Freq/HZ;
186 
187 		if(x > Freq/(3*HZ))
188 			break;
189 	}
190 	wdogresume(dogwason);
191 
192 	/*
193  	 *  figure out clock frequency and a loop multiplier for delay().
194 	 *  n.b. counter goes up by 2*Freq
195 	 */
196 	if(x == 0)
197 		x = 1;			/* avoid division by zero on vmware 7 */
198 	cpufreq = (vlong)loops*((aalcycles*2*Freq)/x);
199 	m->loopconst = (cpufreq/1000)/aalcycles;	/* AAM+LOOP's for 1 ms */
200 
201 	if(m->havetsc && a != b){  /* a == b means virtualbox has confused us */
202 		/* counter goes up by 2*Freq */
203 		b = (b-a)<<1;
204 		b *= Freq;
205 		b /= x;
206 
207 		/*
208 		 *  round to the nearest megahz
209 		 */
210 		m->cpumhz = (b+500000)/1000000L;
211 		m->cpuhz = b;
212 		m->cyclefreq = b;
213 	} else {
214 		/*
215 		 *  add in possible 0.5% error and convert to MHz
216 		 */
217 		m->cpumhz = (cpufreq + cpufreq/200)/1000000;
218 		m->cpuhz = cpufreq;
219 	}
220 
221 	/* don't divide by zero in trap.c */
222 	if (m->cpumhz == 0)
223 		panic("guesscpuhz: zero m->cpumhz");
224 	i8253.hz = Freq<<Tickshift;
225 }
226 
227 void
i8253timerset(uvlong next)228 i8253timerset(uvlong next)
229 {
230 	long period;
231 	ulong want;
232 	ulong now;
233 
234 	period = MaxPeriod;
235 	if(next != 0){
236 		want = next>>Tickshift;
237 		now = i8253.ticks;	/* assuming whomever called us just did fastticks() */
238 
239 		period = want - now;
240 		if(period < MinPeriod)
241 			period = MinPeriod;
242 		else if(period > MaxPeriod)
243 			period = MaxPeriod;
244 	}
245 
246 	/* hysteresis */
247 	if(i8253.period != period){
248 		ilock(&i8253);
249 		/* load new value */
250 		outb(Tmode, Load0|Square);
251 		outb(T0cntr, period);		/* low byte */
252 		outb(T0cntr, period >> 8);		/* high byte */
253 
254 		/* remember period */
255 		i8253.period = period;
256 		i8253.periodset++;
257 		iunlock(&i8253);
258 	}
259 }
260 
261 static void
i8253clock(Ureg * ureg,void *)262 i8253clock(Ureg* ureg, void*)
263 {
264 	timerintr(ureg, 0);
265 }
266 
267 void
i8253enable(void)268 i8253enable(void)
269 {
270 	i8253.enabled = 1;
271 	i8253.period = Freq/HZ;
272 	intrenable(IrqCLOCK, i8253clock, 0, BUSUNKNOWN, "clock");
273 }
274 
275 void
i8253link(void)276 i8253link(void)
277 {
278 }
279 
280 /*
281  *  return the total ticks of counter 2.  We shift by
282  *  8 to give timesync more wriggle room for interpretation
283  *  of the frequency
284  */
285 uvlong
i8253read(uvlong * hz)286 i8253read(uvlong *hz)
287 {
288 	ushort y, x;
289 	uvlong ticks;
290 
291 	if(hz)
292 		*hz = i8253.hz;
293 
294 	ilock(&i8253);
295 	outb(Tmode, Latch2);
296 	y = inb(T2cntr);
297 	y |= inb(T2cntr)<<8;
298 
299 	if(y < i8253.last)
300 		x = i8253.last - y;
301 	else {
302 		x = i8253.last + (0x10000 - y);
303 		if (x > 3*MaxPeriod) {
304 			outb(Tmode, Load2|Square);
305 			outb(T2cntr, 0);		/* low byte */
306 			outb(T2cntr, 0);		/* high byte */
307 			y = 0xFFFF;
308 			x = i8253.period;
309 		}
310 	}
311 	i8253.last = y;
312 	i8253.ticks += x>>1;
313 	ticks = i8253.ticks;
314 	iunlock(&i8253);
315 
316 	return ticks<<Tickshift;
317 }
318 
319 void
delay(int millisecs)320 delay(int millisecs)
321 {
322 	if (millisecs > 10*1000)
323 		iprint("delay(%d) from %#p\n", millisecs,
324 			getcallerpc(&millisecs));
325 	if (watchdogon && m->machno == 0 && !islo())
326 		for (; millisecs > Wdogms; millisecs -= Wdogms) {
327 			delay(Wdogms);
328 			watchdog->restart();
329 		}
330 	millisecs *= m->loopconst;
331 	if(millisecs <= 0)
332 		millisecs = 1;
333 	aamloop(millisecs);
334 }
335 
336 void
microdelay(int microsecs)337 microdelay(int microsecs)
338 {
339 	if (watchdogon && m->machno == 0 && !islo())
340 		for (; microsecs > Wdogms*1000; microsecs -= Wdogms*1000) {
341 			delay(Wdogms);
342 			watchdog->restart();
343 		}
344 	microsecs *= m->loopconst;
345 	microsecs /= 1000;
346 	if(microsecs <= 0)
347 		microsecs = 1;
348 	aamloop(microsecs);
349 }
350 
351 /*
352  *  performance measurement ticks.  must be low overhead.
353  *  doesn't have to count over a second.
354  */
355 ulong
perfticks(void)356 perfticks(void)
357 {
358 	uvlong x;
359 
360 	if(m->havetsc)
361 		cycles(&x);
362 	else
363 		x = 0;
364 	return x;
365 }
366