ref: fcb6d6ec65fb82c3b6b936dd148df72b9955994b
dir: /sys/src/9/pc/i8253.c/
#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" /* * 8253 timer */ enum { T0cntr= 0x40, /* counter ports */ T1cntr= 0x41, /* ... */ T2cntr= 0x42, /* ... */ Tmode= 0x43, /* mode port (control word register) */ T2ctl= 0x61, /* counter 2 control port */ /* commands */ Latch0= 0x00, /* latch counter 0's value */ Load0l= 0x10, /* load counter 0's lsb */ Load0m= 0x20, /* load counter 0's msb */ Load0= 0x30, /* load counter 0 with 2 bytes */ Latch1= 0x40, /* latch counter 1's value */ Load1l= 0x50, /* load counter 1's lsb */ Load1m= 0x60, /* load counter 1's msb */ Load1= 0x70, /* load counter 1 with 2 bytes */ Latch2= 0x80, /* latch counter 2's value */ Load2l= 0x90, /* load counter 2's lsb */ Load2m= 0xa0, /* load counter 2's msb */ Load2= 0xb0, /* load counter 2 with 2 bytes */ /* 8254 read-back command: everything > pc-at has an 8254 */ Rdback= 0xc0, /* readback counters & status */ Rdnstat=0x10, /* don't read status */ Rdncnt= 0x20, /* don't read counter value */ Rd0cntr=0x02, /* read back for which counter */ Rd1cntr=0x04, Rd2cntr=0x08, /* modes */ ModeMsk=0xe, Square= 0x6, /* periodic square wave */ Trigger=0x0, /* interrupt on terminal count */ Sstrobe=0x8, /* software triggered strobe */ /* T2ctl bits */ T2gate= (1<<0), /* enable T2 counting */ T2spkr= (1<<1), /* connect T2 out to speaker */ T2out= (1<<5), /* output of T2 */ Freq= 1193182, /* Real clock frequency */ Tickshift=8, /* extra accuracy */ MaxPeriod=Freq/HZ, MinPeriod=Freq/(100*HZ), }; typedef struct I8253 I8253; struct I8253 { Lock; ulong period; /* current clock period */ ushort last; /* last value of clock 1 */ uvlong ticks; /* cumulative ticks of counter 1 */ ulong periodset; }; static I8253 i8253; void i8253reset(void) { int loops, x; ilock(&i8253); i8253.last = 0; i8253.period = Freq/HZ; /* * enable a 1/HZ interrupt for providing scheduling interrupts */ outb(Tmode, Load0|Square); outb(T0cntr, (Freq/HZ)); /* low byte */ outb(T0cntr, (Freq/HZ)>>8); /* high byte */ /* * enable a longer period counter to use as a clock */ outb(Tmode, Load2|Square); outb(T2cntr, 0); /* low byte */ outb(T2cntr, 0); /* high byte */ x = inb(T2ctl); x |= T2gate; outb(T2ctl, x); /* * Introduce a little delay to make sure the count is * latched and the timer is counting down; with a fast * enough processor this may not be the case. * The i8254 (which this probably is) has a read-back * command which can be used to make sure the counting * register has been written into the counting element. */ x = (Freq/HZ); for(loops = 0; loops < 100000 && x >= (Freq/HZ); loops++){ outb(Tmode, Latch0); x = inb(T0cntr); x |= inb(T0cntr)<<8; } iunlock(&i8253); } static uvlong i8253cpufreq(void) { int loops, x, y; uvlong a, b; ilock(&i8253); for(loops = 1000;;loops += 1000) { /* * measure time for the loop * * MOVL loops,CX * aaml1: AAM * LOOP aaml1 * * the time for the loop should be independent of external * cache and memory system since it fits in the execution * prefetch buffer. * */ outb(Tmode, Latch2); cycles(&a); x = inb(T2cntr); x |= inb(T2cntr)<<8; aamloop(loops); outb(Tmode, Latch2); cycles(&b); y = inb(T2cntr); y |= inb(T2cntr)<<8; x -= y; if(x < 0) x += 0x10000; if(x >= MaxPeriod || loops >= 1000000) break; } iunlock(&i8253); /* avoid division by zero on vmware 7 */ if(x == 0) x = 1; if(m->havetsc && b > a){ b -= a; m->cyclefreq = b * 2*Freq / x; m->aalcycles = (b + loops-1) / loops; return m->cyclefreq; } return (vlong)loops*m->aalcycles * 2*Freq / x; } void i8253init(void) { uvlong cpufreq; if(m->machno != 0){ m->cpuhz = MACHP(0)->cpuhz; m->cpumhz = MACHP(0)->cpumhz; m->cyclefreq = MACHP(0)->cyclefreq; m->loopconst = MACHP(0)->loopconst; return; } ioalloc(T0cntr, 4, 0, "i8253"); ioalloc(T2ctl, 1, 0, "i8253.cntr2ctl"); i8253reset(); cpufreq = i8253cpufreq(); m->loopconst = (cpufreq/1000)/m->aalcycles; /* AAM+LOOP's for 1 ms */ m->cpuhz = cpufreq; /* * round to the nearest megahz */ m->cpumhz = (cpufreq+500000)/1000000L; if(m->cpumhz == 0) m->cpumhz = 1; } void i8253timerset(uvlong next) { long period; ulong want; ulong now; want = next>>Tickshift; now = i8253.ticks; /* assuming whomever called us just did fastticks() */ period = want - now; if(period < MinPeriod) period = MinPeriod; else if(period > MaxPeriod) period = MaxPeriod; /* hysteresis */ if(i8253.period != period){ ilock(&i8253); /* load new value */ outb(Tmode, Load0|Square); outb(T0cntr, period); /* low byte */ outb(T0cntr, period >> 8); /* high byte */ /* remember period */ i8253.period = period; i8253.periodset++; iunlock(&i8253); } } static void i8253clock(Ureg* ureg, void*) { timerintr(ureg, 0); } void i8253enable(void) { intrenable(IrqCLOCK, i8253clock, 0, BUSUNKNOWN, "clock"); } /* * return the total ticks of counter 2. We shift by * 8 to give timesync more wriggle room for interpretation * of the frequency */ uvlong i8253read(uvlong *hz) { ushort y, x; uvlong ticks; if(hz) *hz = Freq<<Tickshift; ilock(&i8253); outb(Tmode, Latch2); y = inb(T2cntr); y |= inb(T2cntr)<<8; if(y < i8253.last) x = i8253.last - y; else { x = i8253.last + (0x10000 - y); if (x > 3*MaxPeriod) { outb(Tmode, Load2|Square); outb(T2cntr, 0); /* low byte */ outb(T2cntr, 0); /* high byte */ y = 0xFFFF; x = i8253.period; } } i8253.last = y; i8253.ticks += x>>1; ticks = i8253.ticks; iunlock(&i8253); return ticks<<Tickshift; }