/* * 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 = <->loc; tn->isr = Xisrclk; coherence(); timerintr(ureg, 0); #ifdef watchdog_not_bloody_useless /* appease the dogs */ wd = <->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(<->loc); watchdogoff(<->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 = <->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 = <->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; ) ; }