Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * cortex-a clocks; excludes tegra 2 SoC clocks
 *
 * cortex-a processors include private `global' and local timers
 * at soc.scu + 0x200 (global) and + 0x600 (local).
 * the global timer is a single count-up timer shared by all cores
 * but with per-cpu comparator and auto-increment registers.
 * a local count-down timer can be used as a watchdog.
 *
 * v7 arch provides a 32-bit count-up cycle counter (at about 1GHz in our case)
 * but it's unsuitable as our source of fastticks, because it stops advancing
 * when the cpu is suspended by WFI.
 */
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "arm.h"

enum {
        Debug           = 0,

        Basetickfreq    = Mhz,                  /* soc.µs rate in Hz */
        /* the local timers seem to run at half the expected rate */
        Clockfreqbase   = 250*Mhz / 2,  /* private timer rate (PERIPHCLK/2) */
        Tcycles         = Clockfreqbase / HZ,   /* cycles per clock tick */

        MinPeriod       = Tcycles / 100,
        MaxPeriod       = Tcycles,

        Dogtimeout      = Dogsectimeout * Clockfreqbase,
};

typedef struct Ltimer Ltimer;
typedef struct Pglbtmr Pglbtmr;
typedef struct Ploctmr Ploctmr;

/*
 * cortex-a private-intr local timer registers.  all cpus see their
 * own local timers at the same base address.
 */
struct Ltimer {
        ulong   load;           /* new value + 1 */
        ulong   cnt;            /* counts down */
        ulong   ctl;
        ulong   isr;

        /* watchdog only */
        ulong   wdrst;
        ulong   wddis;          /* wo */

        ulong   _pad0[2];
};
struct Ploctmr {
        Ltimer  loc;
        Ltimer  wd;
};

enum {
        /* ctl bits */
        Tmrena  = 1<<0,         /* timer enabled */
        Wdogena = Tmrena,       /* watchdog enabled */
        Xreload = 1<<1,         /* reload on intr; periodic interrupts */
        Tintena = 1<<2,         /* enable irq 29 at cnt==0 (30 for watchdog) */
        Wdog    = 1<<3,         /* watchdog, not timer, mode */
        Xsclrshift = 8,
        Xsclrmask = MASK(8),

        /* isr bits */
        Xisrclk = 1<<0,         /* write to clear */

        /* wdrst bits */
        Wdrst   = 1<<0,

        /* wddis values */
        Wdon    = 1,
        Wdoff1  = 0x12345678,   /* send these two to switch to timer mode */
        Wdoff2  = 0x87654321,
};

/* cortex-a private-intr globl timer registers */
struct Pglbtmr {
        ulong   cnt[2];         /* counts up; little-endian uvlong */
        ulong   ctl;
        ulong   isr;
        ulong   cmp[2];         /* little-endian uvlong */
        ulong   inc;
};

enum {
        /* unique ctl bits (otherwise see X* above) */
        Gcmp    = 1<<1,
//      Gtintena= 1<<2,         /* enable irq 27 */
        Gincr   = 1<<3,
};

/*
 * until 5[cl] inline vlong ops, avoid them where possible,
 * they are currently slow function calls.
 */
typedef union Vlong Vlong;
union Vlong {
        uvlong  uvl;
        struct {                        /* little-endian */
                ulong   low;
                ulong   high;
        };
};

static int fired;
static int ticking[MAXMACH];

/* no lock is needed to update our local timer.  splhi keeps it tight. */
static void
setltimer(Ltimer *tn, ulong ticks)
{
        int s;

        assert(ticks <= Clockfreqbase);
        s = splhi();
        tn->load = ticks - 1;
        coherence();
        tn->ctl = Tmrena | Tintena | Xreload;
        coherence();
        splx(s);
}

static void
ckstuck(int cpu, long myticks, long histicks)
{
        if (labs(histicks - myticks) > HZ) {
//              iprint("cpu%d: clock ticks %ld (vs myticks %ld cpu0 %ld); "
//                      "apparently stopped\n",
//                      cpu, histicks, myticks, MACHP(0)->ticks);
                if (!ticking[cpu])
                        panic("cpu%d: clock not interrupting", cpu);
        }
}

static void
mpclocksanity(void)
{
        int cpu, mycpu;
        long myticks, histicks;

        if (conf.nmach <= 1 || active.exiting || navailcpus == 0)
                return;

        mycpu = m->machno;
        myticks = m->ticks;
        if (myticks == HZ)
                ticking[mycpu] = 1;

        if (myticks < 5*HZ)
                return;

        for (cpu = 0; cpu < navailcpus; cpu++) {
                if (cpu == mycpu)
                        continue;
                histicks = MACHP(cpu)->ticks;
                if (myticks == 5*HZ || histicks > 1)
                        ckstuck(cpu, myticks, histicks);
        }
}

static void
clockintr(Ureg* ureg, void *arg)
{
        Ltimer *wd, *tn;
        Ploctmr *lt;

        lt = (Ploctmr *)arg;
        tn = &lt->loc;
        tn->isr = Xisrclk;
        coherence();

        timerintr(ureg, 0);

#ifdef watchdog_not_bloody_useless
        /* appease the dogs */
        wd = &lt->wd;
        if (wd->cnt == 0 &&
            (wd->ctl & (Wdog | Wdogena | Tintena)) == (Wdog | Wdogena))
                panic("cpu%d: zero watchdog count but no system reset",
                        m->machno);
        wd->load = Dogtimeout - 1;
        coherence();
#endif
        SET(wd); USED(wd);
        tegclockintr();

        mpclocksanity();
}

void
clockprod(Ureg *ureg)
{
        Ltimer *tn;

        timerintr(ureg, 0);
        tegclockintr();
        if (m->machno != 0) {           /* cpu1 gets stuck */
                tn = &((Ploctmr *)soc.loctmr)->loc;
                setltimer(tn, Tcycles);
        }
}

static void
clockreset(Ltimer *tn)
{
        if (probeaddr((uintptr)tn) < 0)
                panic("no clock at %#p", tn);
        tn->ctl = 0;
        coherence();
}

void
watchdogoff(Ltimer *wd)
{
        wd->ctl &= ~Wdogena;
        coherence();
        wd->wddis = Wdoff1;
        coherence();
        wd->wddis = Wdoff2;
        coherence();
}

/* clear any pending watchdog intrs or causes */
void
wdogclrintr(Ltimer *wd)
{
#ifdef watchdog_not_bloody_useless
        wd->isr = Xisrclk;
        coherence();
        wd->wdrst = Wdrst;
        coherence();
#endif
        USED(wd);
}

/*
 * stop clock interrupts on this cpu and disable the local watchdog timer,
 * and, if on cpu0, shutdown the shared tegra2 watchdog timer.
 */
void
clockshutdown(void)
{
        Ploctmr *lt;

        lt = (Ploctmr *)soc.loctmr;
        clockreset(&lt->loc);
        watchdogoff(&lt->wd);

        tegclockshutdown();
}

enum {
        Instrs          = 10*Mhz,
};

/* we assume that perfticks are microseconds */
static long
issue1loop(void)
{
        register int i;
        long st;

        i = Instrs;
        st = perfticks();
        do {
                --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
                --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
                --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
                --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
                --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
                --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
                --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
                --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
                --i; --i; --i; --i; --i; --i; --i; --i; --i; --i;
                --i; --i; --i; --i; --i; --i; --i; --i; --i;
        } while(--i >= 0);
        return perfticks() - st;
}

static long
issue2loop(void)
{
        register int i, j;
        long st;

        i = Instrs / 2;                 /* j gets half the decrements */
        j = 0;
        st = perfticks();
        do {
                     --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;

                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
                --i; --j; --i; --j; --i; --j; --i; --j; --i; --j;
        } while(--i >= 0);
        return perfticks() - st;
}

/* estimate instructions/s. */
static void
guessmips(long (*loop)(void), char *lab)
{
        int s;
        long tcks;

        do {
                s = splhi();
                tcks = loop();
                splx(s);
                if (tcks < 0)
                        iprint("again...");
        } while (tcks < 0);
        /*
         * Instrs instructions took tcks ticks @ Basetickfreq Hz.
         * round the result.
         */
        s = (((vlong)Basetickfreq * Instrs) / tcks + 500000) / 1000000;
        if (Debug)
                iprint("%ud mips (%s-issue)", s, lab);
        USED(s);
}

void
wdogintr(Ureg *, void *ltmr)
{
#ifdef watchdog_not_bloody_useless
        Ltimer *wd;

        wd = ltmr;
        fired++;
        wdogclrintr(wd);
#endif
        USED(ltmr);
}

static void
ckcounting(Ltimer *lt)
{
        ulong old;

        old = lt->cnt;
        if (old == lt->cnt)
                delay(1);
        if (old == lt->cnt)
                panic("cpu%d: watchdog timer not counting down", m->machno);
}

/* test fire with interrupt to see that it's working */
static void
ckwatchdog(Ltimer *wd)
{
#ifdef watchdog_not_bloody_useless
        int s;

        fired = 0;
        wd->load = Tcycles - 1;
        coherence();
        /* Tintena is supposed to be ignored in watchdog mode */
        wd->ctl |= Wdogena | Tintena;
        coherence();

        ckcounting(wd);

        s = spllo();
        delay(2 * 1000/HZ);
        splx(s);
        if (!fired)
                /* useless local watchdog */
                iprint("cpu%d: local watchdog failed to interrupt\n", m->machno);
        /* clean up */
        wd->ctl &= ~Wdogena;
        coherence();
#endif
        USED(wd);
}

static void
startwatchdog(void)
{
#ifdef watchdog_not_bloody_useless
        Ltimer *wd;
        Ploctmr *lt;

        lt = (Ploctmr *)soc.loctmr;
        wd = &lt->wd;
        watchdogoff(wd);
        wdogclrintr(wd);
        irqenable(Wdtmrirq, wdogintr, wd, "watchdog");

        ckwatchdog(wd);

        /* set up for normal use, causing reset */
        wd->ctl &= ~Tintena;                    /* reset, don't interrupt */
        coherence();
        wd->ctl |= Wdog;
        coherence();
        wd->load = Dogtimeout - 1;
        coherence();
        wd->ctl |= Wdogena;
        coherence();

        ckcounting(wd);
#endif
}

static void
clock0init(Ltimer *tn)
{
        int s;
        ulong old, fticks;

        /*
         * calibrate fastclock
         */
        s = splhi();
        tn->load = ~0ul >> 1;
        coherence();
        tn->ctl = Tmrena;
        coherence();

        old = perfticks();
        fticks = tn->cnt;
        delay(1);
        fticks = abs(tn->cnt - fticks);
        old = perfticks() - old;
        splx(s);
        if (Debug)
                iprint("cpu%d: fastclock %ld/%ldµs = %ld fastticks/µs (MHz)\n",
                        m->machno, fticks, old, (fticks + old/2 - 1) / old);
        USED(fticks, old);

        if (Debug)
                iprint("cpu%d: ", m->machno);
        guessmips(issue1loop, "single");
        if (Debug)
                iprint(", ");
        guessmips(issue2loop, "dual");
        if (Debug)
                iprint("\n");

        /*
         * m->delayloop should be the number of delay loop iterations
         * needed to consume 1 ms.  2 is instr'ns in the delay loop.
         */
        m->delayloop = m->cpuhz / (1000 * 2);
//      iprint("cpu%d: m->delayloop = %lud\n", m->machno, m->delayloop);

        tegclock0init();
}

/*
 * the local timer is the interrupting timer and does not
 * participate in measuring time.  It is initially set to HZ.
 */
void
clockinit(void)
{
        ulong old;
        Ltimer *tn;
        Ploctmr *lt;

        clockshutdown();

        /* turn my cycle counter on */
        cpwrsc(0, CpCLD, CpCLDena, CpCLDenacyc, 1<<31);

        /* turn all my counters on and clear my cycle counter */
        cpwrsc(0, CpCLD, CpCLDena, CpCLDenapmnc, 1<<2 | 1);

        /* let users read my cycle counter directly */
        cpwrsc(0, CpCLD, CpCLDuser, CpCLDenapmnc, 1);

        /* verify µs counter sanity */
        tegclockinit();

        lt = (Ploctmr *)soc.loctmr;
        tn = &lt->loc;
        if (m->machno == 0)
                irqenable(Loctmrirq, clockintr, lt, "clock");
        else
                intcunmask(Loctmrirq);

        /*
         * verify sanity of local timer
         */
        tn->load = Clockfreqbase / 1000;
        tn->isr = Xisrclk;
        coherence();
        tn->ctl = Tmrena;
        coherence();

        old = tn->cnt;
        delay(5);
        /* m->ticks won't be incremented here because timersinit hasn't run. */
        if (tn->cnt == old)
                panic("cpu%d: clock not ticking at all", m->machno);
        else if ((long)tn->cnt > 0)
                panic("cpu%d: clock ticking slowly", m->machno);

        if (m->machno == 0)
                clock0init(tn);

        /* if pci gets stuck, maybe one of the many watchdogs will nuke us. */
        startwatchdog();

        /*
         *  desynchronize the processor clocks so that they all don't
         *  try to resched at the same time.
         */
        delay(m->machno*2);
        setltimer(tn, Tcycles);
}

/* our fastticks are at 1MHz (Basetickfreq), so the conversion is trivial. */
ulong
µs(void)
{
        return fastticks2us(fastticks(nil));
}

/* Tval is supposed to be in fastticks units. */
void
timerset(Tval next)
{
        int s;
        long offset;
        Ltimer *tn;

        tn = &((Ploctmr *)soc.loctmr)->loc;
        s = splhi();
        offset = fastticks2us(next - fastticks(nil));
        /* offset is now in µs (MHz); convert to Clockfreqbase Hz. */
        offset *= Clockfreqbase / Mhz;
        if(offset < MinPeriod)
                offset = MinPeriod;
        else if(offset > MaxPeriod)
                offset = MaxPeriod;

        setltimer(tn, offset);
        splx(s);
}

static ulong
cpucycles(void) /* cpu clock rate, except when waiting for intr (unused) */
{
        ulong v;

        /* reads 32-bit cycle counter (counting up) */
//      v = cprdsc(0, CpCLD, CpCLDcyc, 0);
        v = getcyc();                           /* fast asm */
        /* keep it non-negative; prevent m->fastclock ever going to 0 */
        return v == 0? 1: v;
}

long
lcycles(void)
{
        return perfticks();
}

uvlong
fastticks(uvlong *hz)
{
        int s;
        ulong newticks;
        Vlong *fcp;

        if(hz)
                *hz = Basetickfreq;

        fcp = (Vlong *)&m->fastclock;
        /* avoid reentry on interrupt or trap, to prevent recursion */
        s = splhi();
        newticks = perfticks();
        if(newticks < fcp->low)         /* low word must have wrapped */
                fcp->high++;
        fcp->low = newticks;
        splx(s);

        if (fcp->low == 0 && fcp->high == 0 && m->ticks > HZ/10)
                panic("fastticks: zero m->fastclock; ticks %lud fastclock %#llux",
                        m->ticks, m->fastclock);
        return m->fastclock;
}

void
microdelay(int l)
{
        for (l = l * (vlong)m->delayloop / 1000; --l >= 0; )
                ;
}

void
delay(int l)
{
        int i, d;

        d = m->delayloop;
        while(--l >= 0)
                for (i = d; --i >= 0; )
                        ;
}