shithub: riscv

ref: a5db044fd3ccb8f021d4deb07b3dc732ed41d699
dir: /sys/src/9/teg2/clock.c/

View raw version
/*
 * 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; )
			;
}