Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

#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),

        Wdogms  = 200,          /* ms between strokes */
};

typedef struct I8253 I8253;
struct I8253
{
        Lock;
        ulong   period;         /* current clock period */
        int     enabled;
        uvlong  hz;

        ushort  last;           /* last value of clock 1 */
        uvlong  ticks;          /* cumulative ticks of counter 1 */

        ulong   periodset;
};
I8253 i8253;

void
i8253init(void)
{
        int loops, x;

        ioalloc(T0cntr, 4, 0, "i8253");
        ioalloc(T2ctl, 1, 0, "i8253.cntr2ctl");

        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;
        }
}

/*
 * if the watchdog is running and we're on cpu 0 and ignoring (clock)
 * interrupts, disable the watchdog temporarily so that the (presumed)
 * long-running loop to follow will not trigger an NMI.
 * wdogresume restarts the watchdog if wdogpause stopped it.
 */
static int
wdogpause(void)
{
        int turndogoff;

        turndogoff = watchdogon && m->machno == 0 && !islo();
        if (turndogoff) {
                watchdog->disable();
                watchdogon = 0;
        }
        return turndogoff;
}

static void
wdogresume(int resume)
{
        if (resume) {
                watchdog->enable();
                watchdogon = 1;
        }
}

void
guesscpuhz(int aalcycles)
{
        int loops, incr, x, y, dogwason;
        uvlong a, b, cpufreq;

        dogwason = wdogpause();         /* don't get NMI while busy looping */

        /* 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);
                cycles(&a);
                x = inb(T0cntr);
                x |= inb(T0cntr)<<8;
                aamloop(loops);
                outb(Tmode, Latch0);
                cycles(&b);
                y = inb(T0cntr);
                y |= inb(T0cntr)<<8;
                x -= y;
        
                if(x < 0)
                        x += Freq/HZ;

                if(x > Freq/(3*HZ))
                        break;
        }
        wdogresume(dogwason);

        /*
         *  figure out clock frequency and a loop multiplier for delay().
         *  n.b. counter goes up by 2*Freq
         */
        if(x == 0)
                x = 1;                  /* avoid division by zero on vmware 7 */
        cpufreq = (vlong)loops*((aalcycles*2*Freq)/x);
        m->loopconst = (cpufreq/1000)/aalcycles;        /* AAM+LOOP's for 1 ms */

        if(m->havetsc && a != b){  /* a == b means virtualbox has confused us */
                /* 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;
                m->cyclefreq = b;
        } else {
                /*
                 *  add in possible 0.5% error and convert to MHz
                 */
                m->cpumhz = (cpufreq + cpufreq/200)/1000000;
                m->cpuhz = cpufreq;
        }

        /* don't divide by zero in trap.c */
        if (m->cpumhz == 0)
                panic("guesscpuhz: zero m->cpumhz");
        i8253.hz = Freq<<Tickshift;
}

void
i8253timerset(uvlong next)
{
        long period;
        ulong want;
        ulong now;

        period = MaxPeriod;
        if(next != 0){
                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)
{
        i8253.enabled = 1;
        i8253.period = Freq/HZ;
        intrenable(IrqCLOCK, i8253clock, 0, BUSUNKNOWN, "clock");
}

void
i8253link(void)
{
}

/*
 *  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 = i8253.hz;

        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;
}

void
delay(int millisecs)
{
        if (millisecs > 10*1000)
                iprint("delay(%d) from %#p\n", millisecs,
                        getcallerpc(&millisecs));
        if (watchdogon && m->machno == 0 && !islo())
                for (; millisecs > Wdogms; millisecs -= Wdogms) {
                        delay(Wdogms);
                        watchdog->restart();
                }
        millisecs *= m->loopconst;
        if(millisecs <= 0)
                millisecs = 1;
        aamloop(millisecs);
}

void
microdelay(int microsecs)
{
        if (watchdogon && m->machno == 0 && !islo())
                for (; microsecs > Wdogms*1000; microsecs -= Wdogms*1000) {
                        delay(Wdogms);
                        watchdog->restart();
                }
        microsecs *= m->loopconst;
        microsecs /= 1000;
        if(microsecs <= 0)
                microsecs = 1;
        aamloop(microsecs);
}

/*  
 *  performance measurement ticks.  must be low overhead.
 *  doesn't have to count over a second.
 */
ulong
perfticks(void)
{
        uvlong x;

        if(m->havetsc)
                cycles(&x);
        else
                x = 0;
        return x;
}