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