#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 */
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 havecyc;
int enabled;
int mode;
vlong when; /* next fastticks a clock interrupt should occur */
vlong cycwhen; /* next fastticks a cycintr happens; 0 == infinity */
long fastperiod; /* fastticks/hz */
long fast2freq; /* fastticks*FreqMul/Freq */
}i8253;
void
i8253init(int aalcycles, int havecycleclock)
{
int cpufreq, loops, incr, x, y;
vlong a, b;
static int initialised;
if(initialised == 0){
initialised = 1;
i8253.havecyc = havecycleclock;
ioalloc(T0cntr, 4, 0, "i8253");
ioalloc(T2ctl, 1, 0, "i8253.cntr2ctl");
/*
* set 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 */
/*
* 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;
}
}
/* 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(havecycleclock)
rdtsc(&a);
x = inb(T0cntr);
x |= inb(T0cntr)<<8;
aamloop(loops);
outb(Tmode, Latch0);
if(havecycleclock)
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(havecycleclock){
/* 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
* cycwhen is the next time for the cycle counter routines
*/
static void
clockintrsched0(vlong next)
{
vlong now;
long set;
i8253.cycwhen = 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
havecycintr(void)
{
return i8253.havecyc && i8253.enabled;
}
void
clockintrsched(void)
{
vlong next;
ilock(&i8253);
if(i8253.enabled && i8253.havecyc){
next = cycintrnext();
if(next != i8253.cycwhen)
clockintrsched0(next);
}
iunlock(&i8253);
}
static void
clockintr0(Ureg* ureg, void *v)
{
vlong now, when;
if(!i8253.havecyc){
clockintr(ureg, v);
return;
}
now = fastticks(nil);
when = i8253.when;
/*
* If we suspend the machine and resume, the
* processor cycle counter gets reset. Detect this
* and deal with it.
*/
if(now < i8253.when && i8253.when-now > 3*i8253.fastperiod)
i8253.when = now;
if(now >= i8253.when){
clockintr(ureg, v);
do
i8253.when += i8253.fastperiod;
while(i8253.when < now);
}
if(i8253.cycwhen || i8253.mode != Square)
clockintrsched0(checkcycintr(ureg, v));
}
void
i8253enable(void)
{
i8253.when = fastticks(nil);
i8253.fastperiod = (m->cpuhz + HZ/2) / HZ;
i8253.fast2freq = m->cpuhz * FreqMul / Freq;
i8253.enabled = 1;
intrenable(IrqCLOCK, clockintr0, 0, BUSUNKNOWN, "clock");
}
/*
* return time elapsed since clock start in
* 100 times hz
*/
uvlong
i8253read(uvlong *hz)
{
if(hz)
*hz = HZ*100;
return m->ticks*100;
}