#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 */ /* counter 2 controls */ C2gate= 0x1, C2speak=0x2, C2out= 0x10, Freq= 1193182, /* Real clock frequency */ Minusec=20, /* minimum cycles for a clock interrupt */ FreqMul=16, /* extra accuracy in fastticks/Freq calculation; ok up to ~8ghz */ }; static struct { Lock; int enabled; int mode; vlong when; /* next fastticks a clock interrupt should occur */ vlong timerwhen; /* next fastticks a cycintr happens; 0 == infinity */ long fastperiod; /* fastticks/hz */ long fast2freq; /* fastticks*FreqMul/Freq */ Lock lock1; /* mutex for the following */ ushort last1; /* last value of clock 1 */ uvlong ticks1; /* cumulative ticks of counter 1 */ }i8253; void i8253init(void) { int loops, x; ioalloc(T0cntr, 4, 0, "i8253"); ioalloc(T2ctl, 1, 0, "i8253.cntr2ctl"); /* * set interrupting clock for 1/HZ seconds */ outb(Tmode, Load0|Square); i8253.mode = Square; outb(T0cntr, (Freq/HZ)); /* low byte */ outb(T0cntr, (Freq/HZ)>>8); /* high byte */ /* * set time clock */ outb(Tmode, Load1|Square); i8253.mode = Square; outb(T1cntr, 0xfe); /* low byte */ outb(T1cntr, 0xff); /* high byte */ /* * 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; } } void guesscpuhz(int aalcycles) { int cpufreq, loops, incr, x, y; vlong a, b; /* find biggest loop that doesn't wrap */ incr = 16000000/(aalcycles*HZ*2); x = 2000; for(loops = incr; loops < 64*1024; loops += incr) { /* * 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, Latch0); if(m->havetsc) rdtsc(&a); x = inb(T0cntr); x |= inb(T0cntr)<<8; aamloop(loops); outb(Tmode, Latch0); if(m->havetsc) rdtsc(&b); y = inb(T0cntr); y |= inb(T0cntr)<<8; x -= y; if(x < 0) x += Freq/HZ; if(x > Freq/(3*HZ)) break; } /* * figure out clock frequency and a loop multiplier for delay(). * n.b. counter goes up by 2*Freq */ cpufreq = loops*((aalcycles*2*Freq)/x); m->loopconst = (cpufreq/1000)/aalcycles; /* AAM+LOOP's for 1 ms */ if(m->havetsc){ /* counter goes up by 2*Freq */ b = (b-a)<<1; b *= Freq; b /= x; /* * round to the nearest megahz */ m->cpumhz = (b+500000)/1000000L; m->cpuhz = b; } else { /* * add in possible 0.5% error and convert to MHz */ m->cpumhz = (cpufreq + cpufreq/200)/1000000; m->cpuhz = cpufreq; } } /* * schedule the next interrupt * timerwhen is the time of the next interrupt */ static void clockintrsched0(vlong next) { vlong now; long set; i8253.timerwhen = next; if(i8253.mode == Square && next == 0) return; now = fastticks(nil); if(next || now < i8253.when - i8253.fastperiod || now > i8253.when - ((3*i8253.fastperiod)>>2)){ if(next > i8253.when) next = i8253.when; set = (long)(next - now) * FreqMul / i8253.fast2freq; set -= 3; /* three cycles for the count to take effect: outb, outb, wait */ if(i8253.mode != Trigger){ outb(Tmode, Load0|Trigger); i8253.mode = Trigger; set--; } if(set < Minusec) set = Minusec; outb(T0cntr, set); /* low byte */ outb(T0cntr, set>>8); /* high byte */ }else if(i8253.mode != Square){ i8253.mode = Square; outb(Tmode, Load0|Square); outb(T0cntr, (Freq/HZ)); /* low byte */ outb(T0cntr, (Freq/HZ)>>8); /* high byte */ } } int havetimer(void) { return i8253.enabled; } void clockintrsched(void) { vlong next; ilock(&i8253); if(i8253.enabled){ next = timernext(); if(next != i8253.timerwhen) clockintrsched0(next); } iunlock(&i8253); } int nclockintr0; static void clockintr0(Ureg* ureg, void *v) { vlong now, when; nclockintr0++; /* goes with syncing in squidboy */ if(m->havetsc) rdtsc(&m->lasttsc); now = fastticks(nil); when = i8253.when; if(now >= i8253.when){ clockintr(ureg, v); do i8253.when += i8253.fastperiod; while(i8253.when < now); } if(i8253.timerwhen || i8253.mode != Square) clockintrsched0(checktimer(ureg, v)); } void i8253enable(void) { uvlong hz; i8253.when = fastticks(&hz); i8253.fastperiod = (hz + HZ/2) / HZ; i8253.fast2freq = (hz * FreqMul) / Freq; i8253.enabled = 1; intrenable(IrqCLOCK, clockintr0, 0, BUSUNKNOWN, "clock"); } /* * return the total ticks of counter 1. 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<<8; ilock(&i8253.lock1); outb(Tmode, Latch1); y = inb(T1cntr); y |= inb(T1cntr)<<8; if(y < i8253.last1) x = i8253.last1 - y; else x = i8253.last1 + (0xfffe - y); i8253.last1 = y; i8253.ticks1 += x>>1; ticks = i8253.ticks1; iunlock(&i8253.lock1); return ticks<<8; } /* * return value and speed of timer set in arch->clockenable */ vlong fastticks(uvlong *hz) { return (*arch->fastclock)(hz); }