Browse Source

Plan 9 from Bell Labs 2013-07-24

David du Colombier 10 years ago
parent
commit
7137e45891

+ 10 - 15
sys/lib/lp/bin/lpsend.rc

@@ -1,18 +1,13 @@
 #!/bin/rc
 #!/bin/rc
-if (! ~ $DEBUG '') { flag x + }
-if (test -e /net/tcp/clone) {
-	dialstring=`{ndb/query sys $1 dom}
-	network=tcp
-	if (~ $#dialstring 0 || ! ~ $dialstring '') {
-		dialstring=$1
-	}
-	if(lpsend $network^!^$dialstring^!printer) exit ''
-	rv='tcp failed'
-}
-if not rv='no tcp'
+# lpsend.rc dialstring - run lpsend network!dialstring!printer
+if (! ~ $DEBUG '')
+	flag x +
+if (! test -e /net/tcp/clone)
+	exit 'no tcp'
 
 
+dialstring=`{ndb/query sys $1 dom}
+if (~ $#dialstring 0 || ~ $dialstring '')	# no dom for sys in ndb?
+	dialstring=$1				# use arg unchanged
 
 
-if (! ~ $dialstring '')
-	exit 'lpsend: no dialstring'
-if not
-	exit 'lpsend: '^$rv
+lpsend tcp!^$dialstring^!printer
+exit $status

+ 1 - 0
sys/src/9/mkfile

@@ -5,6 +5,7 @@ ARCH=\
 	omap\
 	omap\
 	pc\
 	pc\
 	ppc\
 	ppc\
+	rb\
 	teg2\
 	teg2\
 	
 	
 all:V:
 all:V:

+ 3 - 0
sys/src/9/rb/c_fcr0.s

@@ -0,0 +1,3 @@
+TEXT	C_fcr0(SB), $-4
+	MOVW	$0x500, R1	/* claim to be an r4k, thus have ll/sc */
+	RET

+ 299 - 0
sys/src/9/rb/clock.c

@@ -0,0 +1,299 @@
+/*
+ * ar7161 clocks and timers
+ */
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"io.h"
+
+#include	"ureg.h"
+
+enum {
+	Cyccntres	= 2, /* counter advances at ½ clock rate (mips 24k) */
+	Basetickfreq	= 680*Mhz / Cyccntres,	/* rb450g */
+};
+
+void (*kproftimer)(ulong);
+
+void
+silencewdog(void)
+{
+	*Rstwdogtimer = Basetickfreq * 2 * 5;	/* pet the dog */
+}
+
+void
+sicwdog(void)
+{
+	*Rstwdogtimer = Basetickfreq * 2;
+	*Rstwdogctl = Wdogreset;		/* wake the dog */
+}
+
+void
+wdogreset(void)
+{
+	*Rstwdogtimer = Basetickfreq / 100;
+	*Rstwdogctl = Wdogreset;		/* wake the dog */
+	coherence();
+	*Rstwdogtimer = Basetickfreq / 10000;
+	coherence();
+}
+
+void
+stopwdog(void)
+{
+	*Rstwdogtimer = ~0;
+	*Rstwdogctl = Wdognoaction;		/* put the dog to sleep */
+}
+
+void
+clockshutdown(void)
+{
+	stopwdog();
+}
+
+/*
+ *  delay for l milliseconds more or less.
+ */
+void
+delay(int l)
+{
+	while(l-- > 0)
+		microdelay(1000);
+}
+
+/*
+ *  microseconds delay
+ */
+void
+microdelay(int l)
+{
+	int s;
+	ulong x, cyc, cnt, speed;
+
+	speed = m->speed;
+	if (speed == 0)
+		speed = Basetickfreq / Mhz * Cyccntres;
+	cyc = (ulong)l * (speed / Cyccntres);
+	s = splhi();
+	cnt = rdcount();
+	x = cnt + cyc;
+	if (x < cnt || x >= ~0ul - Basetickfreq) {
+		/* counter will wrap between now and x, or x is too near ~0 */
+		wrcount(0);			/* somewhat drastic */
+		wrcompare(rdcompare() - cnt);	/* match new count */
+		x = cyc;
+	}
+	while(rdcount() < x)
+		;
+	splx(s);
+	silencewdog();
+}
+
+void
+clock(Ureg *ureg)
+{
+	wrcompare(rdcount()+m->maxperiod);	/* side-effect: dismiss intr */
+	silencewdog();
+	timerintr(ureg, 0);
+}
+
+enum {
+	Instrs		= 10*Mhz,
+};
+
+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;
+		/* omit 3 (--i) to account for conditional branch, nop & jump */
+		i -= 1+3;	 /* --i plus 3 omitted (--i) instructions */
+	} while(--i >= 0);
+	return perfticks() - st;
+}
+
+/* estimate instructions/s. */
+static int
+guessmips(long (*loop)(void), char *)
+{
+	int s;
+	long cyc;
+
+	do {
+		s = splhi();
+		cyc = loop();
+		splx(s);
+		if (cyc < 0)
+			iprint("again...");
+	} while (cyc < 0);
+	/*
+	 * Instrs instructions took cyc cycles @ Basetickfreq Hz.
+	 * round the result.
+	 */
+	return (((vlong)Basetickfreq * Instrs) / cyc + Mhz/2) / Mhz;
+}
+
+void
+clockinit(void)
+{
+	int mips;
+
+	silencewdog();
+
+	/*
+	 * calibrate fastclock
+	 */
+	mips = guessmips(issue1loop, "single");
+
+	/*
+	 * m->delayloop should be the number of delay loop iterations
+	 * needed to consume 1 ms, assuming 2 instr'ns in the delay loop.
+	 */
+	m->delayloop = mips*Mhz / (1000 * 2);
+	if(m->delayloop == 0)
+		m->delayloop = 1;
+
+	m->speed = mips;
+	m->hz = m->speed*Mhz;
+
+	m->maxperiod = Basetickfreq / HZ;
+	m->minperiod = Basetickfreq / (100*HZ);
+	wrcompare(rdcount()+m->maxperiod);
+
+	/*
+	 *  desynchronize the processor clocks so that they all don't
+	 *  try to resched at the same time.
+	 */
+	delay(m->machno*2);
+
+	syncclock();
+	intron(INTR7);
+}
+
+/*
+ * Tval is supposed to be in fastticks units.
+ * One fasttick unit is 1/Basetickfreq seconds.
+ */
+void
+timerset(Tval next)
+{
+	int x;
+	long period;
+
+	if(next == 0)
+		return;
+	x = splhi();			/* don't let us get scheduled */
+	period = next - fastticks(nil);
+	if(period > m->maxperiod - m->minperiod)
+		period = m->maxperiod;
+	else if(period < m->minperiod)
+		period = m->minperiod;
+	wrcompare(rdcount()+period);
+	silencewdog();
+	splx(x);
+}
+
+/*
+ *  The rewriting of compare in this routine shouldn't be necessary.
+ *  However, we lose clock interrupts if I don't, either a chip bug
+ *  or one of ours -- presotto
+ */
+uvlong
+fastticks(uvlong *hz)
+{
+	int x;
+	ulong delta, count;
+
+	if(hz)
+		*hz = Basetickfreq;
+
+	/* avoid reentry on interrupt or trap, to prevent recursion */
+	x = splhi();
+	count = rdcount();
+	if(rdcompare() - count > m->maxperiod)
+		wrcompare(count+m->maxperiod);
+	silencewdog();
+
+	if (count < m->lastcount)		/* wrapped around? */
+		delta = count + ((1ull<<32) - m->lastcount);
+	else
+		delta = count - m->lastcount;
+	m->lastcount = count;
+	m->fastticks += delta;
+	splx(x);
+	return m->fastticks;
+}
+
+ulong
+µs(void)
+{
+	return fastticks2us(fastticks(nil));
+}
+
+/*
+ *  performance measurement ticks.  must be low overhead.
+ *  doesn't have to count over a second.
+ */
+ulong
+perfticks(void)
+{
+	return rdcount();
+}
+
+long
+lcycles(void)
+{
+	return perfticks();
+}
+
+/* should use vlong hw counters ideally; lcycles is inadequate */
+void
+cycles(uvlong *cycp)
+{
+	*cycp = fastticks(nil);
+}
+
+Lock mpsynclock;
+
+/*
+ *  synchronize all clocks with processor 0
+ */
+void
+syncclock(void)
+{
+	uvlong x;
+
+	if(m->machno == 0){
+		m->lastcount = rdcount();
+		m->fastticks = 0;
+		m->ticks = 0;
+		wrcompare(rdcount()+m->maxperiod);
+	} else {
+		/* wait for processor 0's soft clock to change and then sync ours */
+		lock(&mpsynclock);
+		x = MACHP(0)->fastticks;
+		while(MACHP(0)->fastticks == x)
+			;
+		m->lastcount = rdcount();
+		m->fastticks = MACHP(0)->fastticks;
+		m->ticks = MACHP(0)->ticks;
+		wrcompare(rdcount()+m->maxperiod);
+		unlock(&mpsynclock);
+	}
+}

+ 225 - 0
sys/src/9/rb/dat.h

@@ -0,0 +1,225 @@
+typedef struct Conf	Conf;
+typedef struct Confmem	Confmem;
+typedef struct FPsave	FPsave;
+typedef struct KMap	KMap;
+typedef struct Lance	Lance;
+typedef struct Lancemem	Lancemem;
+typedef struct Label	Label;
+typedef struct Lock	Lock;
+typedef struct Mach	Mach;
+typedef struct MMU	MMU;
+typedef struct Notsave	Notsave;
+typedef struct Pcidev	Pcidev;
+typedef struct PMMU	PMMU;
+typedef struct Softtlb	Softtlb;
+typedef struct Ureg	Ureg;
+typedef struct Proc	Proc;
+typedef uvlong		Tval;
+
+#pragma incomplete Pcidev
+
+#define MAXSYSARG	5	/* for mount(fd, afd, mpt, flag, arg) */
+
+/*
+ *  parameters for sysproc.c and rebootcmd.c
+ */
+#define AOUT_MAGIC	V_MAGIC || magic==M_MAGIC
+/* r3k or r4k boot images */
+#define BOOT_MAGIC	(0x160<<16) || magic == ((0x160<<16)|3)
+
+/*
+ *  machine dependent definitions used by ../port/dat.h
+ */
+
+struct Lock
+{
+	ulong	key;			/* semaphore (non-zero = locked) */
+	ulong	sr;
+	ulong	pc;
+	Proc	*p;
+	Mach	*m;
+	ushort	isilock;
+};
+
+struct Label
+{
+	ulong	sp;
+	ulong	pc;
+};
+
+struct Confmem
+{
+	ulong	base;
+	ulong	npage;
+	ulong	kbase;
+	ulong	klimit;
+};
+
+struct Conf
+{
+	ulong	nmach;		/* processors */
+	ulong	nproc;		/* processes */
+	Confmem	mem[1];
+	ulong	npage;		/* total physical pages of memory */
+	ulong	upages;		/* user page pool */
+	ulong	nimage;		/* number of page cache image headers */
+	ulong	nswap;		/* number of swap pages */
+	int	nswppo;		/* max # of pageouts per segment pass */
+	ulong	copymode;	/* 0 is copy on write, 1 is copy on reference */
+	ulong	ialloc;		/* bytes available for interrupt-time allocation */
+	ulong	pipeqsize;	/* size in bytes of pipe queues */
+	int	nuart;		/* number of uart devices */
+};
+
+/*
+ * floating point registers
+ */
+enum
+{
+	/* floating point state */
+	FPinit,
+	FPactive,
+	FPinactive,
+	FPemu,
+
+	/* bit meaning floating point illegal */
+	FPillegal= 0x100,
+};
+
+enum {
+	Nfpregs		= 32,		/* floats; half as many doubles */
+};
+
+/*
+ * emulated floating point (mips32r2 with ieee fp regs)
+ * fpstate is separate, kept in Proc
+ */
+struct FPsave
+{
+	/* /dev/proc expects the registers to be first in FPsave */
+	ulong	reg[Nfpregs];		/* the canonical bits */
+	union {
+		ulong	fpstatus;	/* both are fcr31 */
+		ulong	fpcontrol;
+	};
+
+	int	fpdelayexec;		/* executing delay slot of branch */
+	uintptr	fpdelaypc;		/* pc to resume at after */
+	ulong	fpdelaysts;	/* save across user-mode delay-slot execution */
+
+	/* stuck-fault detection */
+	uintptr	fppc;			/* addr of last fault */
+	int	fpcnt;			/* how many consecutive at that addr */
+};
+
+int fpemudebug;
+
+/*
+ *  mmu goo in the Proc structure
+ */
+struct PMMU
+{
+	int	pidonmach[MAXMACH];
+};
+
+/*
+ *  things saved in the Proc structure during a notify
+ */
+struct Notsave
+{
+	ulong	nonempty;
+};
+
+#include "../port/portdat.h"
+
+/* First FIVE members' offsets known by l.s */
+struct Mach
+{
+	/* the following are all known by l.s and cannot be moved */
+	int	machno;			/* physical id of processor FIRST */
+	Softtlb*stb;			/* Software tlb simulation SECOND */
+	Proc*	proc;			/* process on this processor THIRD */
+	ulong	splpc;			/* pc that called splhi() FOURTH */
+	ulong	tlbfault;		/* # of tlb faults FIFTH */
+	ulong	ktlbfault;
+	ulong	utlbfault;
+
+	/* the following is safe to move */
+	ulong	tlbpurge;
+	ulong	ticks;			/* of the clock since boot time */
+	Label	sched;			/* scheduler wakeup */
+	void*	alarm;			/* alarms bound to this clock */
+	int	lastpid;		/* last pid allocated on this machine */
+	Proc*	pidproc[NTLBPID];	/* proc that owns tlbpid on this mach */
+	KMap*	kactive;		/* active on this machine */
+	int	knext;
+	uchar	ktlbx[NTLB];		/* tlb index used for kmap */
+	uchar	ktlbnext;
+	int	speed;			/* cpu speed */
+	ulong	delayloop;		/* for the delay() routine */
+	ulong	fairness;		/* for runproc */
+	int	flushmmu;
+	int	inclockintr;
+	int	ilockdepth;
+	Perf	perf;			/* performance counters */
+	uvlong	cyclefreq;		/* Frequency of user readable cycle counter */
+
+	/* for per-processor timers */
+	ulong	lastcount;
+	uvlong	fastticks;
+	ulong	hz;
+	ulong	maxperiod;
+	ulong	minperiod;
+
+	Proc*	readied;		/* for runproc */
+	ulong	schedticks;		/* next forced context switch */
+
+	int	pfault;
+	int	cs;
+	int	syscall;
+	int	load;
+	int	intr;
+	int	hashcoll;		/* soft-tlb hash collisions */
+	int	paststartup;		/* for putktlb */
+
+	int	stack[1];
+};
+
+struct KMap
+{
+	Ref;
+	ulong	virt;
+	ulong	phys0;
+	ulong	phys1;
+	KMap*	next;
+	KMap*	konmach[MAXMACH];
+	Page*	pg;
+	ulong	pc;			/* of caller to kmap() */
+};
+
+#define	VA(k)		((k)->virt)
+#define PPN(x)		((ulong)(x)>>6)
+
+/* offsets known by l.s */
+struct Softtlb
+{
+	ulong	virt;
+	ulong	phys0;
+	ulong	phys1;
+};
+
+struct
+{
+	Lock;
+	long	machs;		/* bitmap of processors */
+	short	exiting;
+	int	ispanic;
+}active;
+
+extern KMap kpte[];
+extern register Mach	*m;
+extern register Proc	*up;
+
+extern FPsave initfp;
+
+extern	int normalprint;

+ 258 - 0
sys/src/9/rb/devarch.c

@@ -0,0 +1,258 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "io.h"
+
+#include "../ip/ip.h"
+
+enum {
+	Qdir = 0,
+	Qbase,
+
+	Qmax = 16,
+};
+
+typedef long Rdwrfn(Chan*, void*, long, vlong);
+
+static Rdwrfn *readfn[Qmax];
+static Rdwrfn *writefn[Qmax];
+
+static Dirtab archdir[Qmax] = {
+	".",		{ Qdir, 0, QTDIR },	0,	0555,
+};
+
+Lock archwlock;	/* the lock is only for changing archdir */
+int narchdir = Qbase;
+
+/*
+ * Add a file to the #P listing.  Once added, you can't delete it.
+ * You can't add a file with the same name as one already there,
+ * and you get a pointer to the Dirtab entry so you can do things
+ * like change the Qid version.  Changing the Qid path is disallowed.
+ */
+Dirtab*
+addarchfile(char *name, int perm, Rdwrfn *rdfn, Rdwrfn *wrfn)
+{
+	int i;
+	Dirtab d;
+	Dirtab *dp;
+
+	memset(&d, 0, sizeof d);
+	strcpy(d.name, name);
+	d.perm = perm;
+
+	lock(&archwlock);
+	if(narchdir >= Qmax){
+		unlock(&archwlock);
+		return nil;
+	}
+
+	for(i=0; i<narchdir; i++)
+		if(strcmp(archdir[i].name, name) == 0){
+			unlock(&archwlock);
+			return nil;
+		}
+
+	d.qid.path = narchdir;
+	archdir[narchdir] = d;
+	readfn[narchdir] = rdfn;
+	writefn[narchdir] = wrfn;
+	dp = &archdir[narchdir++];
+	unlock(&archwlock);
+
+	return dp;
+}
+
+static Chan*
+archattach(char* spec)
+{
+	return devattach('P', spec);
+}
+
+Walkqid*
+archwalk(Chan* c, Chan *nc, char** name, int nname)
+{
+	return devwalk(c, nc, name, nname, archdir, narchdir, devgen);
+}
+
+static int
+archstat(Chan* c, uchar* dp, int n)
+{
+	return devstat(c, dp, n, archdir, narchdir, devgen);
+}
+
+static Chan*
+archopen(Chan* c, int omode)
+{
+	return devopen(c, omode, archdir, narchdir, devgen);
+}
+
+static void
+archclose(Chan*)
+{
+}
+
+static long
+archread(Chan *c, void *a, long n, vlong offset)
+{
+	Rdwrfn *fn;
+
+	switch((ulong)c->qid.path){
+	case Qdir:
+		return devdirread(c, a, n, archdir, narchdir, devgen);
+
+	default:
+		if(c->qid.path < narchdir && (fn = readfn[c->qid.path]))
+			return fn(c, a, n, offset);
+		error(Eperm);
+		break;
+	}
+
+	return 0;
+}
+
+static long
+archwrite(Chan *c, void *a, long n, vlong offset)
+{
+	Rdwrfn *fn;
+
+	if(c->qid.path < narchdir && (fn = writefn[c->qid.path]))
+		return fn(c, a, n, offset);
+	error(Eperm);
+
+	return 0;
+}
+
+void archinit(void);
+
+Dev archdevtab = {
+	'P',
+	"arch",
+
+	devreset,
+	archinit,
+	devshutdown,
+	archattach,
+	archwalk,
+	archstat,
+	archopen,
+	devcreate,
+	archclose,
+	archread,
+	devbread,
+	archwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
+static long
+cputyperead(Chan*, void *a, long n, vlong offset)
+{
+	char str[128];
+
+	snprint(str, sizeof str, "MIPS 24k %lud\n", m->hz / Mhz);
+	return readstr(offset, a, n, str);
+}
+
+static long
+tbread(Chan*, void *a, long n, vlong offset)
+{
+	char str[16];
+	uvlong tb;
+
+	cycles(&tb);
+
+	snprint(str, sizeof(str), "%16.16llux", tb);
+	return readstr(offset, a, n, str);
+}
+
+static long
+nsread(Chan*, void *a, long n, vlong offset)
+{
+	char str[16];
+	uvlong tb;
+
+	cycles(&tb);
+
+	snprint(str, sizeof(str), "%16.16llux", (tb/700)* 1000);
+	return readstr(offset, a, n, str);
+}
+
+char *cputype = "mips";
+
+char	*faultsprint(char *, char *);
+char	*fpemuprint(char *, char *);
+
+static long
+archctlread(Chan*, void *a, long nn, vlong offset)
+{
+	int n;
+	char *buf, *p, *ep;
+
+	p = buf = malloc(READSTR);
+	if(p == nil)
+		error(Enomem);
+	ep = p + READSTR;
+	p = seprint(p, ep, "cpu %s %lud\n", cputype,
+		(ulong)(m->hz+999999)/1000000);
+	p = seprint(p, ep, "stlb hash collisions");
+	for (n = 0; n < conf.nmach; n++)
+		p = seprint(p, ep, " %d", MACHP(n)->hashcoll);
+	p = seprint(p, ep, "\n");
+	p = seprint(p, ep, "NKTLB %d ktlb misses %ld utlb misses %ld\n",
+		NKTLB, m->ktlbfault, m->utlbfault);
+	p = fpemuprint(p, ep);
+	faultsprint(p, ep);
+	n = readstr(offset, a, nn, buf);
+	free(buf);
+	return n;
+}
+
+enum
+{
+	CMfpemudebug,
+};
+
+static Cmdtab archctlmsg[] =
+{
+#ifdef FPEMUDEBUG
+	CMfpemudebug,	"fpemudebug",	2,
+#else
+	CMfpemudebug,	"dummy",	1,
+#endif
+};
+
+static long
+archctlwrite(Chan*, void *a, long n, vlong)
+{
+	Cmdbuf *cb;
+	Cmdtab *ct;
+
+	cb = parsecmd(a, n);
+	if(waserror()){
+		free(cb);
+		nexterror();
+	}
+	ct = lookupcmd(cb, archctlmsg, nelem(archctlmsg));
+	switch(ct->index){
+	case CMfpemudebug:
+		fpemudebug = atoi(cb->f[1]);
+		break;
+	}
+	free(cb);
+	poperror();
+	return n;
+}
+
+void
+archinit(void)
+{
+	addarchfile("cputype", 0444, cputyperead, nil);
+	addarchfile("timebase",0444, tbread, nil);
+	addarchfile("archctl", 0664, archctlread, archctlwrite);
+//	addarchfile("nsec", 0444, nsread, nil);
+}

+ 1475 - 0
sys/src/9/rb/devether.c

@@ -0,0 +1,1475 @@
+/*
+ * Atheros 71xx ethernets for rb450g.
+ *
+ * all 5 PHYs are accessible only through first ether's register space.
+ *
+ * TODO:
+ *	promiscuous mode.
+ *	make ether1 work: probably needs mii/phy initialisation,
+ *	maybe needs 8316 switch code too (which requires mdio, phy, etc. glop).
+ * to maybe do some day:
+ *	dig mac addresses out & config phy/mii via spi or other grot and swill
+ *	(instead of editing rb config file).
+ */
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"io.h"
+#include	"../port/error.h"
+#include	"../port/netif.h"
+#include	"etherif.h"
+#include	"ethermii.h"
+#include	<pool.h>
+
+enum {
+	Ntd	= 64,
+	Nrd	= 256,
+	Nrb	= 1024,
+
+	Bufalign= 4,
+	Rbsz	= ETHERMAXTU + 4,	/* 4 for CRC */
+};
+
+extern uchar arge0mac[Eaddrlen];	/* see rb config file */
+extern uchar arge1mac[Eaddrlen];
+
+typedef struct Arge Arge;
+typedef struct Ctlr Ctlr;
+typedef struct Desc Desc;
+typedef struct Etherif Etherif;
+
+/*
+ *  device registers
+ */
+struct Arge {
+	ulong	cfg1;
+	ulong	cfg2;
+	ulong	ifg;
+	ulong	hduplex;
+	ulong	maxframelen;
+	uchar	_pad0[0x20 - 0x14];
+
+	ulong	miicfg;
+	ulong	miicmd;
+	ulong	miiaddr;
+	ulong	miictl;
+	ulong	miists;
+	ulong	miiindic;
+
+	ulong	ifctl;
+	ulong	_pad1;
+	ulong	staaddr1;
+	ulong	staaddr2;
+
+	ulong	fifocfg[3];
+	ulong	fifotxthresh;
+	ulong	fiforxfiltmatch;
+	ulong	fiforxfiltmask;
+	ulong	fiforam[7];
+	uchar	_pad2[0x180 - 0x7c];
+
+	/* dma */
+	ulong	txctl;
+	ulong	txdesc;
+	ulong	txsts;
+	ulong	rxctl;
+	ulong	rxdesc;
+	ulong	rxsts;
+	ulong	dmaintr;
+	ulong	dmaintrsts;
+};
+
+enum {
+	Cfg1softrst		= 1 << 31,
+	Cfg1simulrst		= 1 << 30,
+	Cfg1macrxblkrst		= 1 << 19,
+	Cfg1mactxblkrst		= 1 << 18,
+	Cfg1rxfuncrst		= 1 << 17,
+	Cfg1txfuncrst		= 1 << 16,
+	Cfg1loopback		= 1 <<  8,
+	Cfg1rxflowctl		= 1 <<  5,
+	Cfg1txflowctl		= 1 <<  4,
+	Cfg1syncrx		= 1 <<  3,
+	Cfg1rxen		= 1 <<  2,
+	Cfg1synctx		= 1 <<  1,
+	Cfg1txen		= 1 <<  0,
+
+	Cfg2preamblelenmask	= 0xf,
+	Cfg2preamblelenshift	= 12,
+	Cfg2ifmode1000		= 2 << 8,
+	Cfg2ifmode10_100	= 1 << 8,
+	Cfg2ifmodeshift		= 8,
+	Cfg2ifmodemask		= 3,
+	Cfg2hugeframe		= 1 << 5,
+	Cfg2lenfield		= 1 << 4,
+	Cfg2enpadcrc		= 1 << 2,
+	Cfg2encrc		= 1 << 1,
+	Cfg2fdx			= 1 << 0,
+
+	Miicfgrst		= 1 << 31,
+	Miicfgscanautoinc	= 1 <<  5,
+	Miicfgpreamblesup	= 1 <<  4,
+	Miicfgclkselmask	= 0x7,
+	Miicfgclkdiv4		= 0,
+	Miicfgclkdiv6		= 2,
+	Miicfgclkdiv8		= 3,
+	Miicfgclkdiv10		= 4,
+	Miicfgclkdiv14		= 5,
+	Miicfgclkdiv20		= 6,
+	Miicfgclkdiv28		= 7,
+
+	Miicmdscancycle		= 1 << 1,
+	Miicmdread		= 1,
+	Miicmdwrite		= 0,
+
+	Miiphyaddrshift		= 8,
+	Miiphyaddrmask		= 0xff,
+	Miiregmask		= 0x1f,
+
+	Miictlmask		= 0xffff,
+
+	Miistsmask		= 0xffff,
+
+	Miiindicinvalid		= 1 << 2,
+	Miiindicscanning	= 1 << 1,
+	Miiindicbusy		= 1 << 0,
+
+	Ifctlspeed		= 1 << 16,
+
+	Fifocfg0txfabric	= 1 << 4,
+	Fifocfg0txsys		= 1 << 3,
+	Fifocfg0rxfabric	= 1 << 2,
+	Fifocfg0rxsys		= 1 << 1,
+	Fifocfg0watermark	= 1 << 0,
+	Fifocfg0all		= MASK(5),
+	Fifocfg0enshift		= 8,
+
+	/*
+	 * these flags applicable both to filter mask and to filter match.
+	 * `Ff' is for `fifo filter'.
+	 */
+	Ffunicast		= 1 << 17,
+	Fftruncframe		= 1 << 16,
+	Ffvlantag		= 1 << 15,
+	Ffunsupopcode		= 1 << 14,
+	Ffpauseframe		= 1 << 13,
+	Ffctlframe		= 1 << 12,
+	Fflongevent		= 1 << 11,
+	Ffdribblenibble		= 1 << 10,
+	Ffbcast			= 1 <<  9,
+	Ffmcast			= 1 <<  8,
+	Ffok			= 1 <<  7,
+	Ffoorange		= 1 <<  6,
+	Fflenmsmtch		= 1 <<  5,
+	Ffcrcerr		= 1 <<  4,
+	Ffcodeerr		= 1 <<  3,
+	Fffalsecarrier		= 1 <<  2,
+	Ffrxdvevent		= 1 <<  1,
+	Ffdropevent		= 1 <<  0,
+	/*
+	 * exclude unicast and truncated frames from matching.
+	 */
+	Ffmatchdflt = Ffvlantag | Ffunsupopcode | Ffpauseframe | Ffctlframe |
+		Fflongevent | Ffdribblenibble | Ffbcast | Ffmcast | Ffok |
+		Ffoorange | Fflenmsmtch | Ffcrcerr | Ffcodeerr |
+		Fffalsecarrier | Ffrxdvevent | Ffdropevent,
+
+	/* `Frm' is for `fifo receive mask'. */
+	Frmbytemode		= 1 << 19,
+	Frmnoshortframe		= 1 << 18,
+	Frmbit17		= 1 << 17,
+	Frmbit16		= 1 << 16,
+	Frmtruncframe		= 1 << 15,
+	Frmlongevent		= 1 << 14,
+	Frmvlantag		= 1 << 13,
+	Frmunsupopcode		= 1 << 12,
+	Frmpauseframe		= 1 << 11,
+	Frmctlframe		= 1 << 10,
+	Frmdribblenibble	= 1 <<  9,
+	Frmbcast		= 1 <<  8,
+	Frmmcast		= 1 <<  7,
+	Frmok			= 1 <<  6,
+	Frmoorange		= 1 <<  5,
+	Frmlenmsmtch		= 1 <<  4,
+	Frmcodeerr		= 1 <<  3,
+	Frmfalsecarrier		= 1 <<  2,
+	Frmrxdvevent		= 1 <<  1,
+	Frmdropevent		= 1 <<  0,
+	/*
+	 *  len. mismatch, unsupp. opcode and short frame bits excluded
+	 */
+	Ffmaskdflt = Frmnoshortframe | Frmbit17 | Frmbit16 | Frmtruncframe |
+		Frmlongevent | Frmvlantag | Frmpauseframe | Frmctlframe |
+		Frmdribblenibble | Frmbcast | Frmmcast | Frmok | Frmoorange |
+		Frmcodeerr | Frmfalsecarrier | Frmrxdvevent | Frmdropevent,
+
+	Dmatxctlen	= 1 << 0,
+
+	/* dma tx status */
+	Txpcountmask	= 0xff,
+	Txpcountshift	= 16,
+	Txbuserr	= 1 << 3,
+	Txunderrun	= 1 << 1,
+	Txpktsent	= 1 << 0,
+
+	Dmarxctlen	= 1 << 0,
+
+	/* dma rx status */
+	Rxpcountmask	= 0xff,
+	Rxpcountshift	= 16,
+	Rxbuserr	= 1 << 3,
+	Rxovflo		= 1 << 2,
+	Rxpktrcvd	= 1 << 0,
+
+	/* dmaintr & dmaintrsts bits */
+	Dmarxbuserr	= 1 << 7,
+	Dmarxovflo	= 1 << 6,
+	Dmarxpktrcvd	= 1 << 4,
+	Dmatxbuserr	= 1 << 3,
+	Dmatxunderrun	= 1 << 1,
+	Dmatxpktsent	= 1 << 0,
+	/* we don't really need most tx interrupts */
+	Dmaall		= Dmarxbuserr | Dmarxovflo | Dmarxpktrcvd | Dmatxbuserr,
+
+	Spictlremapdisable	= 1 << 6,
+	Spictlclkdividermask	= MASK(6),
+
+	Spiioctlcs2		= 1 << 18,
+	Spiioctlcs1		= 1 << 17,
+	Spiioctlcs0		= 1 << 16,
+	Spiioctlcsmask		= 7 << 16,
+	Spiioctlclk		= 1 << 8,
+	Spiioctldo		= 1,
+};
+
+struct Spi {			/* at 0x1f000000 */
+	ulong	fs;
+	ulong	ctl;
+	ulong	ioctl;
+	ulong	rds;
+};
+
+/* hw descriptors of buffer rings (rx and tx), need to be uncached */
+struct Desc {
+	ulong	addr;		/* of packet buffer */
+	ulong	ctl;
+	Desc	*next;
+	ulong	_pad;
+};
+
+enum {
+	Descempty	= 1 << 31,
+	Descmore	= 1 << 24,
+	Descszmask	= MASK(12),
+};
+#define DMASIZE(len)	((len) & Descszmask)
+
+struct Ctlr {
+	Arge	*regs;
+	Ether*	edev;			/* backward pointer */
+
+	Lock;				/* attach */
+	int	init;
+	int	attached;
+
+	Mii*	mii;
+	Rendez	lrendez;
+	int	lim;
+	int	link;
+	int	phymask;
+
+	/* receiver */
+	Rendez	rrendez;
+	uint	rintr;			/* count */
+	int	pktstoread;		/* flag */
+	int	discard;
+	/* rx descriptors */
+	Desc*	rdba;			/* base address */
+	Block**	rd;
+	uint	rdh;			/* head */
+	uint	rdt;			/* tail */
+	uint	nrdfree;		/* rd's awaiting pkts (sort of) */
+
+	/* transmitter */
+	Rendez	trendez;
+	uint	tintr;			/* count */
+	int	pktstosend;		/* flag */
+	int	ntq;
+	/* tx descriptors */
+	Desc*	tdba;			/* base address */
+	Block**	td;
+	uint	tdh;			/* head */
+	uint	tdt;			/* tail */
+};
+
+struct Etherif {
+	uintptr	regs;
+	int	irq;
+	uchar	*mac;
+	int	phymask;
+};
+
+static Etherif etherifs[] = {
+	{ 0x1a000000, ILenet0, arge0mac, 1<<4 },
+	{ 0x19000000, ILenet1, arge1mac, MASK(4) },
+};
+
+static Ether *etherxx[MaxEther];
+static Lock athrblock;		/* free receive Blocks */
+static Block* athrbpool;	/* receive Blocks for all ath controllers */
+
+static void	athrbfree(Block* bp);
+
+/*
+ * ar8316 ether switch
+ */
+
+enum {
+	Swrgmii	= 0,
+	Swgmii	= 1,
+	Swphy4cpu = 0, /* flag: port 4 connected to CPU (not internal switch) */
+};
+
+typedef struct Switch Switch;
+struct Switch {
+	int	page;
+	int	scdev;
+};
+
+enum {
+	/* atheros-specific mii registers */
+	Miiathdbgaddr	= 0x1d,
+	Miiathdbgdata	= 0x1e,
+
+	Swregmask	= 0,
+		Swmaskrevmask	= 0x00ff,
+		Swmaskvermask	= 0xff00,
+		Swmaskvershift	= 8,
+		Swmasksoftreset	= 1 << 31,
+
+	Swregmode	= 8,
+		Swdir615uboot	= 0x8d1003e0,
+		/* from ubiquiti rspro */
+		Swrgmiiport4iso	= 0x81461bea,
+		Swrgmiiport4sw	= 0x01261be2,
+		/* avm fritz!box 7390 */
+		Swgmiiavm	= 0x010e5b71,
+
+		Swmac0gmiien	= 1 <<  0,
+		Swmac0rgmiien	= 1 <<  1,
+		Swphy4gmiien	= 1 <<  2,
+		Swphy4rgmiien	= 1 <<  3,
+		Swmac0macmode	= 1 <<  4,
+		Swrgmiirxclkdelayen= 1 <<  6,
+		Swrgmiitxclkdelayen= 1 <<  7,
+		Swmac5macmode	= 1 << 14,
+		Swmac5phymode	= 1 << 15,
+		Swtxdelays0	= 1 << 21,
+		Swtxdelays1	= 1 << 22,
+		Swrxdelays0	= 1 << 23,
+		Swledopenen	= 1 << 24,
+		Swspien		= 1 << 25,
+		Swrxdelays1	= 1 << 26,
+		Swpoweronsel	= 1 << 31,
+
+	Swregfloodmask	= 0x2c,
+		Swfloodmaskbcast2cpu= 1 << 26,
+
+	Swregglobal	= 0x30,
+		Swglobalmtumask	= 0x7fff,
+};
+
+#ifdef NOTYET
+void *
+devicegetparent(int)
+{
+	static int glop;
+
+	return &glop;
+}
+
+static void
+arswsplitsetpage(int dev, ulong addr, ushort *phy, ushort *reg)
+{
+	static Switch ar8316;
+	Switch *sc = &ar8316;
+	ushort page;
+
+	page = ((addr) >> 9) & 0xffff;
+	*phy = (((addr) >> 6) & 0x7) | 0x10;
+	*reg = ((addr) >> 1) & 0x1f;
+	MDIOWRREG(devicegetparent(dev), 0x18, 0, page);
+	sc->page = page;
+}
+
+/*
+ * Read half a register.  Some of the registers define control bits, and
+ * the sequence of half-word accesses matters.  The register addresses
+ * are word-even (mod 4).
+ */
+static int
+arswrdreg16(int dev, int addr)
+{
+	ushort phy, reg;
+
+	arswsplitsetpage(dev, addr, &phy, &reg);
+	return MDIORDREG(devicegetparent(dev), phy, reg);
+}
+
+void
+arswwritedbg(int dev, int phy, ushort dbgaddr, ushort dbgdata)
+{
+	MDIOWRREG(devicegetparent(dev), phy, Miiathdbgaddr, dbgaddr);
+	MDIOWRREG(devicegetparent(dev), phy, Miiathdbgdata, dbgdata);
+}
+
+/*
+ * Write half a register
+ */
+static inline int
+arswwrreg16(int dev, int addr, int data)
+{
+	ushort phy, reg;
+
+	arswsplitsetpage(dev, addr, &phy, &reg);
+	return MDIOWRREG(devicegetparent(dev), phy, reg, data);
+}
+
+/* arsw??reglsb routines operate on lower 16 bits; *msb on upper ones */
+
+int
+arswrdreg(int dev, int addr)
+{
+	return arswrdreglsb(dev, addr) | arswrdregmsb(dev, addr);
+}
+
+int
+arswwrreg(int dev, int addr, int value)
+{
+	arswwrreglsb(dev, addr, value);		/* XXX check this write too? */
+	return arswwrregmsb(dev, addr, value);
+}
+
+int
+arswmodifyreg(int dev, int addr, int mask, int set)
+{
+	return arswwrreg(dev, addr, (arswrdreg(dev, addr) & ~mask) | set);
+}
+
+/*
+ * initialise the switch
+ */
+static int
+ar8316init(Switch *sc)
+{
+	if (Swrgmii && Swphy4cpu) {
+		arswwrreg(sc->scdev, Swregmode, Swrgmiiport4iso);
+		iprint("ar8316: MAC port == RGMII, port 4 = dedicated PHY\n");
+	} else if (Swrgmii) {
+		arswwrreg(sc->scdev, Swregmode, Swrgmiiport4sw);
+		iprint("ar8316: MAC port == RGMII, port 4 = switch port\n");
+	} else if (Swgmii) {
+		arswwrreg(sc->scdev, Swregmode, Swgmiiavm);
+		iprint("ar8316: MAC port == GMII\n");
+	} else {
+		iprint("ar8316: unknown switch PHY config\n");
+		return -1;
+	}
+
+	delay(1);			/* wait for things to settle */
+
+	if (Swrgmii && Swphy4cpu) {
+		iprint("ar8316: port 4 RGMII hack\n");
+
+		/* work around for phy4 rgmii mode */
+		arswwritedbg(sc->scdev, 4, 0x12, 0x480c);
+		arswwritedbg(sc->scdev, 4, 0x0, 0x824e);	/* rx delay */
+		arswwritedbg(sc->scdev, 4, 0x5, 0x3d47);	/* tx delay */
+		delay(1);		/* again to let things settle */
+	}
+	arswwrreg(sc->scdev, 0x38, 0xc000050e);	/* mystery */
+
+	/*
+	 * Flood address table misses to all ports, and enable forwarding of
+	 * broadcasts to the cpu port.
+	 */
+	arswwrreg(sc->scdev, Swregfloodmask, Swfloodmaskbcast2cpu | 0x003f003f);
+	arswmodifyreg(sc->scdev, Swregglobal, Swglobalmtumask, ETHERMAXTU+8+2);
+	return 0;
+}
+#endif			/* NOTYET */
+
+static long
+ifstat(Ether* edev, void* a, long n, ulong offset)
+{
+	int l, i, r;
+	char *p;
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	p = malloc(READSTR);
+	if(p == nil)
+		error(Enomem);
+	l = 0;
+	l += snprint(p+l, READSTR-l, "tintr: %ud\n", ctlr->tintr);
+	l += snprint(p+l, READSTR-l, "rintr: %ud\n", ctlr->rintr);
+	l += snprint(p+l, READSTR-l, "discarded: %ud\n", ctlr->discard);
+
+	if(ctlr->mii != nil && ctlr->mii->curphy != nil){
+		l += snprint(p+l, READSTR-l, "phy:   ");
+		for(i = 0; i < NMiiPhyr; i++){
+			if(i && ((i & 0x07) == 0))
+				l += snprint(p+l, READSTR-l, "\n       ");
+			r = miimir(ctlr->mii, i);
+			l += snprint(p+l, READSTR-l, " %4.4uX", r);
+		}
+		snprint(p+l, READSTR-l, "\n");
+	}
+	n = readstr(offset, a, n, p);
+	free(p);
+
+	return n;
+}
+
+static void
+etherrtrace(Netfile* f, Etherpkt* pkt, int len)
+{
+	int i, n;
+	Block *bp;
+
+	if(qwindow(f->in) <= 0)
+		return;
+	if(len > 58)
+		n = 58;
+	else
+		n = len;
+	bp = iallocb(64);
+	if(bp == nil)
+		return;
+	memmove(bp->wp, pkt->d, n);
+	i = TK2MS(MACHP(0)->ticks);
+	bp->wp[58] = len>>8;
+	bp->wp[59] = len;
+	bp->wp[60] = i>>24;
+	bp->wp[61] = i>>16;
+	bp->wp[62] = i>>8;
+	bp->wp[63] = i;
+	bp->wp += 64;
+	qpass(f->in, bp);
+}
+
+Block*
+etheriq(Ether* ether, Block* bp, int fromwire)
+{
+	Etherpkt *pkt;
+	ushort type;
+	int len, multi, tome, fromme;
+	Netfile **ep, *f, **fp, *fx;
+	Block *xbp;
+	Ctlr *ctlr;
+
+	ether->inpackets++;
+	ctlr = ether->ctlr;
+
+	pkt = (Etherpkt*)bp->rp;
+	len = BLEN(bp);
+	type = (pkt->type[0]<<8)|pkt->type[1];
+	fx = 0;
+	ep = &ether->f[Ntypes];
+
+	multi = pkt->d[0] & 1;
+	/* check for valid multicast addresses */
+	if(multi && memcmp(pkt->d, ether->bcast, sizeof(pkt->d)) != 0 &&
+	    ether->prom == 0)
+		if(!activemulti(ether, pkt->d, sizeof(pkt->d))){
+			if(fromwire){
+				ctlr->discard++;
+				freeb(bp);
+				bp = 0;
+			}
+			return bp;
+		}
+
+	/* is it for me? */
+	tome   = memcmp(pkt->d, ether->ea, sizeof(pkt->d)) == 0;
+	fromme = memcmp(pkt->s, ether->ea, sizeof(pkt->s)) == 0;
+
+	/*
+	 * Multiplex the packet to all the connections which want it.
+	 * If the packet is not to be used subsequently (fromwire != 0),
+	 * attempt to simply pass it into one of the connections, thereby
+	 * saving a copy of the data (usual case hopefully).
+	 */
+	for(fp = ether->f; fp < ep; fp++)
+		if((f = *fp) != nil && (f->type == type || f->type < 0))
+		if(tome || multi || f->prom)
+			/* Don't want to hear bridged packets */
+			if(f->bridge && !fromwire && !fromme)
+				continue;
+			else if(f->headersonly)
+				etherrtrace(f, pkt, len);
+			else if(fromwire && fx == 0)
+				fx = f;
+			else if(xbp = iallocb(len)){
+				memmove(xbp->wp, pkt, len);
+				xbp->wp += len;
+				if(qpass(f->in, xbp) < 0){
+					iprint("soverflow for f->in\n");
+					ether->soverflows++;
+				}
+			}else{
+				iprint("soverflow iallocb\n");
+				ether->soverflows++;
+			}
+	if(fx){
+		if(qpass(fx->in, bp) < 0){
+			iprint("soverflow for fx->in\n");
+			ether->soverflows++;
+		}
+		return 0;
+	}
+	if(fromwire){
+		ctlr->discard++;
+		freeb(bp);
+		return 0;
+	}
+	return bp;
+}
+
+static void
+athhwreset(Ether *ether)
+{
+	Ctlr *ctlr;
+	Arge *arge;
+
+	ctlr = ether->ctlr;
+	if (ctlr == nil)
+		return;
+	arge = ctlr->regs;
+	if (arge == nil)
+		return;
+
+	arge->dmaintr = 0;
+
+	arge->rxctl = 0;
+	arge->txctl = 0;
+	coherence();
+
+	/*
+	 * give tx & rx time to stop, otherwise clearing desc registers
+	 * too early will cause random memory corruption.
+	 */
+	delay(1);
+
+	arge->rxdesc = 0;
+	arge->txdesc = 0;
+	coherence();
+
+	/* clear all interrupts */
+	while (arge->rxsts & Rxpktrcvd)
+		arge->rxsts = Rxpktrcvd;
+	while (arge->txsts & Txpktsent)
+		arge->txsts = Txpktsent;
+
+	/* and errors */
+	arge->rxsts = Rxbuserr | Rxovflo;
+	arge->txsts = Txbuserr | Txunderrun;
+}
+
+static void
+txreclaim(Ctlr *ctlr)
+{
+	uint tdh;
+	Arge *arge;
+	Block *bp;
+
+	arge = ctlr->regs;
+	tdh = ctlr->tdh;
+	while (tdh != ctlr->tdt && ctlr->tdba[tdh].ctl & Descempty){
+		arge->txsts = Txpktsent;
+
+		bp = ctlr->td[tdh];
+		ctlr->td[tdh] = nil;
+		if (bp)
+			freeb(bp);
+
+		ctlr->tdba[tdh].addr = 0;
+		ctlr->ntq--;
+		tdh = NEXT(tdh, Ntd);
+	}
+	ctlr->tdh = tdh;
+}
+
+static Block*
+athrballoc(void)
+{
+	Block *bp;
+
+	ilock(&athrblock);
+	if((bp = athrbpool) != nil){
+		athrbpool = bp->next;
+		bp->next = nil;
+		_xinc(&bp->ref);	/* prevent bp from being freed */
+	}
+	iunlock(&athrblock);
+	return bp;
+}
+
+static void
+athrbfree(Block* bp)
+{
+	bp->wp = bp->rp = bp->lim - ROUND(Rbsz, BLOCKALIGN);
+	bp->flag &= ~(Bipck | Budpck | Btcpck | Bpktck);
+
+	ilock(&athrblock);
+	bp->next = athrbpool;
+	athrbpool = bp;
+	iunlock(&athrblock);
+}
+
+static void
+rxnewbuf(Ctlr *ctlr, int i)
+{
+	Block *bp;
+	Desc *rd;
+
+	if (ctlr->rd[i] != nil)
+		return;
+	ctlr->rd[i] = bp = athrballoc();
+	if(bp == nil)
+		panic("#l%d: can't allocate receive buffer",
+			ctlr->edev->ctlrno);
+	dcflush(bp->rp, Rbsz);		/* writeback & invalidate */
+
+	rd = &ctlr->rdba[i];
+	rd->addr = PADDR(bp->rp);
+	rd->ctl = Descempty | DMASIZE(Rbsz);
+	ctlr->nrdfree++;
+}
+
+static void
+rxreclaim(Ctlr *ctlr)
+{
+	uint rdt;
+
+	rdt = ctlr->rdt;
+	while (rdt != ctlr->rdh && !(ctlr->rdba[rdt].ctl & Descempty)){
+		rxnewbuf(ctlr, rdt);
+		rdt = NEXT(rdt, Nrd);
+	}
+	ctlr->rdt = rdt;
+}
+
+static void
+etherintr(void *arg)
+{
+	int sts;
+	Arge *arge;
+	Ctlr *ctlr;
+	Ether *ether;
+
+	ether = arg;
+	ctlr = ether->ctlr;
+	arge = ctlr->regs;
+	ilock(ctlr);
+	sts = arge->dmaintrsts;
+	if (sts & Dmarxpktrcvd) {
+		arge->dmaintr &= ~Dmarxpktrcvd;
+		ctlr->pktstoread = 1;
+		wakeup(&ctlr->rrendez);
+		ctlr->rintr++;
+		sts &= ~Dmarxpktrcvd;
+	}
+	if (sts & (Dmatxpktsent | Dmatxunderrun)) {
+		arge->dmaintr &= ~(Dmatxpktsent | Dmatxunderrun);
+		ctlr->pktstosend = 1;
+		wakeup(&ctlr->trendez);
+		ctlr->tintr++;
+		sts &= ~(Dmatxpktsent | Dmatxunderrun);
+	}
+	iunlock(ctlr);
+	if (sts)
+		iprint("#l%d: sts %#ux\n", ether->ctlrno, sts);
+}
+
+static int
+pktstoread(void* v)
+{
+	Ctlr *ctlr = v;
+
+	return ctlr->pktstoread || !(ctlr->rdba[ctlr->rdh].ctl & Descempty);
+}
+
+static void
+rproc(void* arg)
+{
+	uint rdh, sz;
+	Arge *arge;
+	Block *bp;
+	Ctlr *ctlr;
+	Desc *rd;
+	Ether *edev;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+	arge = ctlr->regs;
+	for(;;){
+		/* wait for next interrupt */
+		ilock(ctlr);
+		arge->dmaintr |= Dmarxpktrcvd;
+		iunlock(ctlr);
+
+		sleep(&ctlr->rrendez, pktstoread, ctlr);
+		ctlr->pktstoread = 0;
+
+		rxreclaim(ctlr);
+		rdh = ctlr->rdh;
+		for (rd = &ctlr->rdba[rdh]; !(rd->ctl & Descempty);
+		     rd = &ctlr->rdba[rdh]){
+			bp = ctlr->rd[rdh];
+			assert(bp != nil);
+			ctlr->rd[rdh] = nil;
+
+			/* omit final 4 bytes (crc), pass pkt upstream */
+			sz = DMASIZE(rd->ctl) - 4;
+			assert(sz > 0 && sz <= Rbsz);
+			bp->wp = bp->rp + sz;
+			bp = etheriq(edev, bp, 1);
+			assert(bp == nil);		/* Block was consumed */
+
+			arge->rxsts = Rxpktrcvd;
+
+			ctlr->nrdfree--;
+			rdh = NEXT(rdh, Nrd);
+			if(ctlr->nrdfree < Nrd/2) {
+				/* rxreclaim reads ctlr->rdh */
+				ctlr->rdh = rdh;
+				rxreclaim(edev->ctlr);
+			}
+		}
+		ctlr->rdh = rdh;
+	}
+}
+
+static int
+pktstosend(void* v)
+{
+	Ether *edev = v;
+	Ctlr *ctlr = edev->ctlr;
+
+	return ctlr->pktstosend || ctlr->ntq > 0 || qlen(edev->oq) > 0;
+}
+
+static void
+tproc(void* arg)
+{
+	uint tdt, added;
+	Arge *arge;
+	Block *bp;
+	Ctlr *ctlr;
+	Desc *td;
+	Ether *edev;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+	arge = ctlr->regs;
+	for(;;){
+		/* wait for next free buffer and output queue block */
+		sleep(&ctlr->trendez, pktstosend, edev);
+		ctlr->pktstosend = 0;
+
+		txreclaim(ctlr);
+
+		/* copy as much of my output q as possible into output ring */
+		added = 0;
+		tdt = ctlr->tdt;
+		while(ctlr->ntq < Ntd - 1){
+			td = &ctlr->tdba[tdt];
+			if (!(td->ctl & Descempty))
+				break;
+			bp = qget(edev->oq);
+			if(bp == nil)
+				break;
+
+			/* make sure the whole packet is in ram */
+			dcflush(bp->rp, BLEN(bp));
+
+			/*
+			 * Give ownership of the descriptor to the chip,
+			 * increment the software ring descriptor pointer.
+			 */
+			ctlr->td[tdt] = bp;
+			td->addr = PADDR(bp->rp);
+			td->ctl = DMASIZE(BLEN(bp));
+			coherence();
+
+			added++;
+			ctlr->ntq++;
+			tdt = NEXT(tdt, Ntd);
+		}
+		ctlr->tdt = tdt;
+		/*
+		 * Underrun turns off TX.  Clear underrun indication.
+		 * If there's anything left in the ring, reactivate the tx.
+		 */
+		if (arge->dmaintrsts & Dmatxunderrun)
+			arge->txsts = Txunderrun;
+		if(1 || added)
+			arge->txctl = Dmatxctlen;	/* kick xmiter */
+		ilock(ctlr);
+		if(ctlr->ntq >= Ntd/2)			/* tx ring half-full? */
+			arge->dmaintr |= Dmatxpktsent;
+		else if (ctlr->ntq > 0)
+			arge->dmaintr |= Dmatxunderrun;
+		iunlock(ctlr);
+		txreclaim(ctlr);
+	}
+}
+
+/*
+ *  turn promiscuous mode on/off
+ */
+static void
+promiscuous(void *ve, int on)
+{
+	USED(ve, on);
+}
+
+static void
+multicast(void *ve, uchar*, int on)
+{
+	USED(ve, on);
+}
+
+static void
+linkdescs(Desc *base, int ndesc)
+{
+	int i;
+
+	for(i = 0; i < ndesc - 1; i++)
+		base[i].next = (Desc *)PADDR(&base[i+1]);
+	base[ndesc - 1].next = (Desc *)PADDR(&base[0]);
+}
+
+/*
+ * Initialise the receive and transmit buffer rings.
+ *
+ * This routine is protected by ctlr->init.
+ */
+static void
+ringinit(Ctlr* ctlr)
+{
+	int i;
+	void *v;
+
+	if(ctlr->rdba == 0){
+		v = xspanalloc(Nrd * sizeof(Desc), CACHELINESZ, 0);
+		assert(v);
+		ctlr->rdba = (Desc *)KSEG1ADDR(v);
+		ctlr->rd = xspanalloc(Nrd * sizeof(Block *), 0, 0);
+		assert(ctlr->rd != nil);
+		linkdescs(ctlr->rdba, Nrd);
+		for(i = 0; i < Nrd; i++)
+			rxnewbuf(ctlr, i);
+	}
+	ctlr->rdt = ctlr->rdh = 0;
+
+	if(ctlr->tdba == 0) {
+		v = xspanalloc(Ntd * sizeof(Desc), CACHELINESZ, 0);
+		assert(v);
+		ctlr->tdba = (Desc *)KSEG1ADDR(v);
+		ctlr->td = xspanalloc(Ntd * sizeof(Block *), 0, 0);
+		assert(ctlr->td != nil);
+	}
+	memset(ctlr->td, 0, Ntd * sizeof(Block *));
+
+	linkdescs(ctlr->tdba, Ntd);
+	for(i = 0; i < Ntd; i++)
+		ctlr->tdba[i].ctl = Descempty;
+
+	ctlr->tdh = ctlr->tdt = 0;
+}
+
+static void
+cfgmediaduplex(Ether *ether)
+{
+	Arge *arge, *arge0;
+	Ctlr *ctlr;
+
+	ctlr = ether->ctlr;
+	arge = ctlr->regs;
+	arge->cfg2 = (arge->cfg2 & ~Cfg2ifmode10_100) | Cfg2ifmode1000 | Cfg2fdx;
+	arge->ifctl &= ~Ifctlspeed;
+	arge->fiforxfiltmask |= Frmbytemode;
+	arge->fifotxthresh = 0x008001ff;	/* undocumented magic */
+
+	if (ether->ctlrno > 0) {
+		/* set PLL registers: copy from arge0 */
+		arge0 = (Arge *)(KSEG1 | etherifs[0].regs);
+		USED(arge0);
+	}
+}
+
+static void
+athmii(Ether *ether, int phymask)
+{
+	USED(ether, phymask);
+}
+
+static void
+athcfg(Ether *ether, int phymask)
+{
+	uchar *eaddr;
+	Arge *arge;
+	Ctlr *ctlr;
+
+	ctlr = ether->ctlr;
+	arge = ctlr->regs;
+	if(ether->ctlrno > 0){
+		if(0){
+			/* doing this seems to disable both ethers */
+			arge->cfg1 |= Cfg1softrst;		/* stop */
+			delay(20);
+			*Reset |= Rstge1mac;
+			delay(100);
+		}
+		*Reset &= ~Rstge1mac;
+		delay(200);
+	}
+
+	/* configure */
+	arge->cfg1 = Cfg1syncrx | Cfg1rxen | Cfg1synctx | Cfg1txen;
+	arge->cfg2 |= Cfg2enpadcrc | Cfg2lenfield | Cfg2encrc;
+	arge->maxframelen = Rbsz;
+
+	if(ether->ctlrno > 0){
+		arge->miicfg = Miicfgrst;
+		delay(100);
+		arge->miicfg = Miicfgclkdiv28;
+		delay(100);
+	}
+
+	/*
+	 * Set all Ethernet address registers to the same initial values
+	 * set all four addresses to 66-88-aa-cc-dd-ee
+	 */
+	eaddr = ether->ea;
+	arge->staaddr1 = eaddr[2]<<24 | eaddr[3]<<16 | eaddr[4]<<8  | eaddr[5];
+	arge->staaddr2 = eaddr[0]<< 8 | eaddr[1];
+
+	arge->fifocfg[0] = Fifocfg0all << Fifocfg0enshift; /* undocumented magic */
+	arge->fifocfg[1] = 0x0fff0000;	/* undocumented magic */
+	arge->fifocfg[2] = 0x00001fff;	/* undocumented magic */
+
+	arge->fiforxfiltmatch = Ffmatchdflt;
+	arge->fiforxfiltmask  = Ffmaskdflt;
+
+	/* phy goo */
+	athmii(ether, phymask);
+	if (ether->ctlrno > 0)
+		cfgmediaduplex(ether);
+}
+
+static int
+athattach(Ether *ether)
+{
+	int i;
+	char name[32];
+	Arge *arge;
+	Block *bp;
+	Ctlr *ctlr;
+
+	ctlr = ether->ctlr;
+	if (ctlr->attached)
+		return -1;
+	ilock(ctlr);
+	ctlr->init = 1;
+	for(i = 0; i < Nrb; i++){
+		if((bp = allocb(Rbsz + Bufalign)) == nil)
+			error(Enomem);
+		bp->free = athrbfree;
+		freeb(bp);
+	}
+	ringinit(ctlr);
+	ctlr->init = 0;
+	iunlock(ctlr);
+
+	athcfg(ether, ctlr->phymask);
+
+	/* start */
+	arge = ctlr->regs;
+	arge->txdesc = PADDR(ctlr->tdba);
+	arge->rxdesc = PADDR(ctlr->rdba);
+	coherence();
+	arge->rxctl = Dmarxctlen;
+
+	snprint(name, KNAMELEN, "#l%drproc", ether->ctlrno);
+	kproc(name, rproc, ether);
+
+	snprint(name, KNAMELEN, "#l%dtproc", ether->ctlrno);
+	kproc(name, tproc, ether);
+
+	ilock(ctlr);
+	arge->dmaintr |= Dmaall;
+	iunlock(ctlr);
+
+	ctlr->attached = 1;
+	return 0;
+}
+
+/*
+ * strategy: RouterBOOT has initialised arge0, try to leave it alone.
+ * copy arge0 registers to arge1, with a few exceptions.
+ */
+static int
+athreset(Ether *ether)
+{
+	Arge *arge;
+	Ctlr *ctlr;
+	Etherif *ep;
+
+	if (ether->ctlrno < 0 || ether->ctlrno >= MaxEther)
+		return -1;
+	if (ether->ctlr == nil) {
+		/*
+		 * Allocate a controller structure and start to initialise it.
+		 */
+		ether->ctlr = ctlr = malloc(sizeof(Ctlr));
+		if (ctlr == nil)
+			return -1;
+		ctlr->edev = ether;
+		ep = etherifs + ether->ctlrno;
+		ctlr->regs = arge = (Arge *)(KSEG1 | ep->regs);
+		ctlr->phymask = ep->phymask;
+
+		ether->port = (uint)arge;
+		ether->irq = ep->irq;
+		memmove(ether->ea, ep->mac, Eaddrlen);
+		ether->ifstat = ifstat;
+		ether->promiscuous = promiscuous;
+		ether->multicast = multicast;
+		ether->arg = ether;
+	}
+	athhwreset(ether);
+	return 0;
+}
+
+static Ether*
+etherprobe(int ctlrno)
+{
+	int i, lg;
+	ulong mb, bsz;
+	Ether *ether;
+	char buf[128], name[32];
+
+	ether = malloc(sizeof(Ether));
+	if(ether == nil)
+		error(Enomem);
+	memset(ether, 0, sizeof(Ether));
+	ether->ctlrno = ctlrno;
+	ether->tbdf = BUSUNKNOWN;
+	ether->mbps = 1000;
+	ether->minmtu = ETHERMINTU;
+	ether->maxmtu = ETHERMAXTU;
+	ether->mtu = ETHERMAXTU;
+
+	if(ctlrno >= MaxEther || athreset(ether) < 0){
+		free(ether);
+		return nil;
+	}
+
+	snprint(name, sizeof(name), "ether%d", ctlrno);
+
+	/*
+	 * If ether->irq is <0, it is a hack to indicate no interrupt
+	 * used by ethersink.
+	 * apparently has to be done here and cannot be deferred until attach.
+	 */
+	if(ether->irq >= 0)
+		intrenable(ether->irq, etherintr, ether);
+
+	i = sprint(buf, "#l%d: atheros71xx: ", ctlrno);
+	if(ether->mbps >= 1000)
+		i += sprint(buf+i, "%dGbps", ether->mbps/1000);
+	else
+		i += sprint(buf+i, "%dMbps", ether->mbps);
+	i += sprint(buf+i, " port %#luX irq %d", PADDR(ether->port), ether->irq);
+	i += sprint(buf+i, ": %2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux",
+		ether->ea[0], ether->ea[1], ether->ea[2],
+		ether->ea[3], ether->ea[4], ether->ea[5]);
+	sprint(buf+i, "\n");
+	print(buf);
+
+	/*
+	 * input queues are allocated by ../port/netif.c:/^openfile.
+	 * the size will be the last argument to netifinit() below.
+	 *
+	 * output queues should be small, to minimise `bufferbloat',
+	 * which confuses tcp's feedback loop.  at 1Gb/s, it only takes
+	 * ~15µs to transmit a full-sized non-jumbo packet.
+	 */
+
+	/* compute log10(ether->mbps) into lg */
+	for(lg = 0, mb = ether->mbps; mb >= 10; lg++)
+		mb /= 10;
+	if (lg > 13)			/* sanity cap; 2**(13+16) = 2²⁹ */
+		lg = 13;
+
+	/* allocate larger input queues for higher-speed interfaces */
+	bsz = 1UL << (lg + 16);		/* 2ⁱ⁶ = 64K, bsz = 2ⁿ × 64K */
+	while (bsz > mainmem->maxsize / 8 && bsz > 128*1024)	/* sanity */
+		bsz /= 2;
+	netifinit(ether, name, Ntypes, bsz);
+
+	if(ether->oq == nil)
+		ether->oq = qopen(1 << (lg + 13), Qmsg, 0, 0);
+	if(ether->oq == nil)
+		panic("etherreset %s: can't allocate output queue", name);
+
+	ether->alen = Eaddrlen;
+	memmove(ether->addr, ether->ea, Eaddrlen);
+	memset(ether->bcast, 0xFF, Eaddrlen);
+	return ether;
+}
+
+static void
+etherreset(void)
+{
+	int ctlrno;
+
+	for(ctlrno = 0; ctlrno < MaxEther; ctlrno++)
+		etherxx[ctlrno] = etherprobe(ctlrno);
+}
+
+static void
+ethershutdown(void)
+{
+	Ether *ether;
+	int i;
+
+	for(i = 0; i < MaxEther; i++){
+		ether = etherxx[i];
+		if(ether)
+			athhwreset(ether);
+	}
+}
+
+static Chan *
+etherattach(char* spec)
+{
+	ulong ctlrno;
+	char *p;
+	Chan *chan;
+
+	ctlrno = 0;
+	if(spec && *spec){
+		ctlrno = strtoul(spec, &p, 0);
+		if((ctlrno == 0 && p == spec) || *p || (ctlrno >= MaxEther))
+			error(Ebadarg);
+	}
+	if(etherxx[ctlrno] == 0)
+		error(Enodev);
+
+	chan = devattach('l', spec);
+	if(waserror()){
+		chanfree(chan);
+		nexterror();
+	}
+	chan->dev = ctlrno;
+	athattach(etherxx[ctlrno]);
+	poperror();
+	return chan;
+}
+
+static Walkqid*
+etherwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return netifwalk(etherxx[c->dev], c, nc, name, nname);
+}
+
+static Chan*
+etheropen(Chan *c, int omode)
+{
+	return netifopen(etherxx[c->dev], c, omode);
+}
+
+static void
+ethercreate(Chan*, char*, int, ulong)
+{
+}
+
+static void
+etherclose(Chan *c)
+{
+	netifclose(etherxx[c->dev], c);
+}
+
+static long
+etherread(Chan *chan, void *buf, long n, vlong off)
+{
+	Ether *ether;
+	ulong offset = off;
+
+	ether = etherxx[chan->dev];
+	if((chan->qid.type & QTDIR) == 0 && ether->ifstat){
+		/*
+		 * With some controllers it is necessary to reach
+		 * into the chip to extract statistics.
+		 */
+		if(NETTYPE(chan->qid.path) == Nifstatqid)
+			return ether->ifstat(ether, buf, n, offset);
+		else if(NETTYPE(chan->qid.path) == Nstatqid)
+			ether->ifstat(ether, buf, 0, offset);
+	}
+
+	return netifread(ether, chan, buf, n, offset);
+}
+
+static Block*
+etherbread(Chan *c, long n, ulong offset)
+{
+	return netifbread(etherxx[c->dev], c, n, offset);
+}
+
+/* kick the transmitter to drain the output ring */
+static void
+athtransmit(Ether* ether)
+{
+	Ctlr *ctlr;
+
+	ctlr = ether->ctlr;
+	ilock(ctlr);
+	ctlr->pktstosend = 1;
+	wakeup(&ctlr->trendez);
+	iunlock(ctlr);
+}
+
+static long (*athctl)(Ether *, char *, int) = nil;
+
+static int
+etheroq(Ether* ether, Block* bp)
+{
+	int len, loopback, s;
+	Etherpkt *pkt;
+
+	ether->outpackets++;
+
+	/*
+	 * Check if the packet has to be placed back onto the input queue,
+	 * i.e. if it's a loopback or broadcast packet or the interface is
+	 * in promiscuous mode.
+	 * If it's a loopback packet indicate to etheriq that the data isn't
+	 * needed and return, etheriq will pass-on or free the block.
+	 * To enable bridging to work, only packets that were originated
+	 * by this interface are fed back.
+	 */
+	pkt = (Etherpkt*)bp->rp;
+	len = BLEN(bp);
+	loopback = memcmp(pkt->d, ether->ea, sizeof(pkt->d)) == 0;
+	if(loopback || memcmp(pkt->d, ether->bcast, sizeof(pkt->d)) == 0 || ether->prom){
+		s = splhi();
+		etheriq(ether, bp, 0);
+		splx(s);
+	}
+
+	if(!loopback){
+		if(qfull(ether->oq))
+			print("etheroq: WARNING: ether->oq full!\n");
+		qbwrite(ether->oq, bp);
+		athtransmit(ether);
+	} else
+		freeb(bp);
+
+	return len;
+}
+
+static long
+etherwrite(Chan* chan, void* buf, long n, vlong)
+{
+	Ether *ether;
+	Block *bp;
+	int nn, onoff;
+	Cmdbuf *cb;
+
+	ether = etherxx[chan->dev];
+	if(NETTYPE(chan->qid.path) != Ndataqid) {
+		nn = netifwrite(ether, chan, buf, n);
+		if(nn >= 0)
+			return nn;
+		cb = parsecmd(buf, n);
+		if(cb->f[0] && strcmp(cb->f[0], "nonblocking") == 0){
+			if(cb->nf <= 1)
+				onoff = 1;
+			else
+				onoff = atoi(cb->f[1]);
+			qnoblock(ether->oq, onoff);
+			free(cb);
+			return n;
+		}
+		free(cb);
+		if(athctl != nil)
+			return athctl(ether, buf, n);
+		error(Ebadctl);
+	}
+
+	assert(ether->ctlr != nil);
+	if(n > ether->mtu)
+		error(Etoobig);
+	if(n < ether->minmtu)
+		error(Etoosmall);
+
+	bp = allocb(n);
+	if(waserror()){
+		freeb(bp);
+		nexterror();
+	}
+	memmove(bp->rp, buf, n);
+	memmove(bp->rp+Eaddrlen, ether->ea, Eaddrlen);
+	poperror();
+	bp->wp += n;
+
+	return etheroq(ether, bp);
+}
+
+static long
+etherbwrite(Chan *c, Block *bp, ulong offset)
+{
+	return devbwrite(c, bp, offset);
+}
+
+static int
+etherstat(Chan *c, uchar *dp, int n)
+{
+	return netifstat(etherxx[c->dev], c, dp, n);
+}
+
+static int
+etherwstat(Chan *c, uchar *dp, int n)
+{
+	return netifwstat(etherxx[c->dev], c, dp, n);
+}
+
+Dev etherdevtab = {
+	'l',
+	"ether",
+
+	etherreset,
+	devinit,
+	ethershutdown,
+	etherattach,
+	etherwalk,
+	etherstat,
+	etheropen,
+	ethercreate,
+	etherclose,
+	etherread,
+	etherbread,
+	etherwrite,
+	etherbwrite,
+	devremove,
+	etherwstat,
+	devpower,
+	devconfig,
+};

+ 36 - 0
sys/src/9/rb/etherif.h

@@ -0,0 +1,36 @@
+enum {
+	MaxEther	= 2,
+	Ntypes		= 8,
+};
+
+typedef struct Ether Ether;
+struct Ether {
+	int	ctlrno;
+	int	port;
+	int	irq;
+	int	tbdf;			/* type+busno+devno+funcno */
+
+	void	*ctlr;
+	Queue*	oq;
+	uchar	ea[Eaddrlen];
+
+	long	(*ifstat)(Ether*, void*, long, ulong);
+#ifdef MULTIETHERTYPES
+	void	(*attach)(Ether*);	/* filled in by reset routine */
+	void	(*detach)(Ether*);
+	void	(*transmit)(Ether*);
+	void	(*interrupt)(Ureg*, void*);
+	long 	(*ctl)(Ether*, void*, long); /* custom ctl messages */
+	void	(*power)(Ether*, int);	/* power on/off */
+	void	(*shutdown)(Ether*);	/* shutdown hardware before reboot */
+#endif
+	Netif;
+};
+
+extern Block* etheriq(Ether*, Block*, int);
+extern void addethercard(char*, int(*)(Ether*));
+extern ulong ethercrc(uchar*, int);
+extern int parseether(uchar*, char*);
+
+#define NEXT(x, l)	(((x)+1)%(l))
+#define PREV(x, l)	(((x) == 0) ? (l)-1: (x)-1)

+ 235 - 0
sys/src/9/rb/ethermii.c

@@ -0,0 +1,235 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+
+#include "etherif.h"
+#include "ethermii.h"
+
+int
+mii(Mii* mii, int mask)
+{
+	MiiPhy *miiphy;
+	int bit, oui, phyno, r, rmask;
+
+	/*
+	 * Probe through mii for PHYs in mask;
+	 * return the mask of those found in the current probe.
+	 * If the PHY has not already been probed, update
+	 * the Mii information.
+	 */
+	rmask = 0;
+	for(phyno = 0; phyno < NMiiPhy; phyno++){
+		bit = 1<<phyno;
+		if(!(mask & bit))
+			continue;
+		if(mii->mask & bit){
+			rmask |= bit;
+			continue;
+		}
+		if(mii->mir(mii, phyno, Bmsr) == -1)
+			continue;
+		r = mii->mir(mii, phyno, Phyidr1);
+		oui = (r & 0x3FFF)<<6;
+		r = mii->mir(mii, phyno, Phyidr2);
+		oui |= r>>10;
+		if(oui == 0xFFFFF || oui == 0)
+			continue;
+
+		if((miiphy = malloc(sizeof(MiiPhy))) == nil)
+			continue;
+
+		miiphy->mii = mii;
+		miiphy->oui = oui;
+		miiphy->phyno = phyno;
+
+		miiphy->anar = ~0;
+		miiphy->fc = ~0;
+		miiphy->mscr = ~0;
+
+		mii->phy[phyno] = miiphy;
+		if(mii->curphy == nil)
+			mii->curphy = miiphy;
+		mii->mask |= bit;
+		mii->nphy++;
+
+		rmask |= bit;
+	}
+	return rmask;
+}
+
+int
+miimir(Mii* mii, int r)
+{
+	if(mii == nil || mii->ctlr == nil || mii->curphy == nil)
+		return -1;
+	return mii->mir(mii, mii->curphy->phyno, r);
+}
+
+int
+miimiw(Mii* mii, int r, int data)
+{
+	if(mii == nil || mii->ctlr == nil || mii->curphy == nil)
+		return -1;
+	return mii->miw(mii, mii->curphy->phyno, r, data);
+}
+
+int
+miireset(Mii* mii)
+{
+	int bmcr;
+
+	if(mii == nil || mii->ctlr == nil || mii->curphy == nil)
+		return -1;
+	bmcr = mii->mir(mii, mii->curphy->phyno, Bmcr);
+	bmcr |= BmcrR;
+	mii->miw(mii, mii->curphy->phyno, Bmcr, bmcr);
+	microdelay(1);
+
+	return 0;
+}
+
+int
+miiane(Mii* mii, int a, int p, int e)
+{
+	int anar, bmsr, mscr, r, phyno;
+
+	if(mii == nil || mii->ctlr == nil || mii->curphy == nil)
+		return -1;
+	phyno = mii->curphy->phyno;
+
+	bmsr = mii->mir(mii, phyno, Bmsr);
+	if(!(bmsr & BmsrAna))
+		return -1;
+
+	if(a != ~0)
+		anar = (AnaTXFD|AnaTXHD|Ana10FD|Ana10HD) & a;
+	else if(mii->curphy->anar != ~0)
+		anar = mii->curphy->anar;
+	else{
+		anar = mii->mir(mii, phyno, Anar);
+		anar &= ~(AnaAP|AnaP|AnaT4|AnaTXFD|AnaTXHD|Ana10FD|Ana10HD);
+		if(bmsr & Bmsr10THD)
+			anar |= Ana10HD;
+		if(bmsr & Bmsr10TFD)
+			anar |= Ana10FD;
+		if(bmsr & Bmsr100TXHD)
+			anar |= AnaTXHD;
+		if(bmsr & Bmsr100TXFD)
+			anar |= AnaTXFD;
+	}
+	mii->curphy->anar = anar;
+
+	if(p != ~0)
+		anar |= (AnaAP|AnaP) & p;
+	else if(mii->curphy->fc != ~0)
+		anar |= mii->curphy->fc;
+	mii->curphy->fc = (AnaAP|AnaP) & anar;
+
+	if(bmsr & BmsrEs){
+		mscr = mii->mir(mii, phyno, Mscr);
+		mscr &= ~(Mscr1000TFD|Mscr1000THD);
+		if(e != ~0)
+			mscr |= (Mscr1000TFD|Mscr1000THD) & e;
+		else if(mii->curphy->mscr != ~0)
+			mscr = mii->curphy->mscr;
+		else{
+			r = mii->mir(mii, phyno, Esr);
+			if(r & Esr1000THD)
+				mscr |= Mscr1000THD;
+			if(r & Esr1000TFD)
+				mscr |= Mscr1000TFD;
+		}
+		mii->curphy->mscr = mscr;
+		mii->miw(mii, phyno, Mscr, mscr);
+	}
+	mii->miw(mii, phyno, Anar, anar);
+
+	r = mii->mir(mii, phyno, Bmcr);
+	if(!(r & BmcrR)){
+		r |= BmcrAne|BmcrRan;
+		mii->miw(mii, phyno, Bmcr, r);
+	}
+
+	return 0;
+}
+
+int
+miistatus(Mii* mii)
+{
+	MiiPhy *phy;
+	int anlpar, bmsr, p, r, phyno;
+
+	if(mii == nil || mii->ctlr == nil || mii->curphy == nil)
+		return -1;
+	phy = mii->curphy;
+	phyno = phy->phyno;
+
+	/*
+	 * Check Auto-Negotiation is complete and link is up.
+	 * (Read status twice as the Ls bit is sticky).
+	 */
+	bmsr = mii->mir(mii, phyno, Bmsr);
+	if(!(bmsr & (BmsrAnc|BmsrAna))) {
+		// print("miistatus: auto-neg incomplete\n");
+		return -1;
+	}
+
+	bmsr = mii->mir(mii, phyno, Bmsr);
+	if(!(bmsr & BmsrLs)){
+		// print("miistatus: link down\n");
+		phy->link = 0;
+		return -1;
+	}
+
+	phy->speed = phy->fd = phy->rfc = phy->tfc = 0;
+	if(phy->mscr){
+		r = mii->mir(mii, phyno, Mssr);
+		if((phy->mscr & Mscr1000TFD) && (r & Mssr1000TFD)){
+			phy->speed = 1000;
+			phy->fd = 1;
+		}
+		else if((phy->mscr & Mscr1000THD) && (r & Mssr1000THD))
+			phy->speed = 1000;
+	}
+
+	anlpar = mii->mir(mii, phyno, Anlpar);
+	if(phy->speed == 0){
+		r = phy->anar & anlpar;
+		if(r & AnaTXFD){
+			phy->speed = 100;
+			phy->fd = 1;
+		}
+		else if(r & AnaTXHD)
+			phy->speed = 100;
+		else if(r & Ana10FD){
+			phy->speed = 10;
+			phy->fd = 1;
+		}
+		else if(r & Ana10HD)
+			phy->speed = 10;
+	}
+	if(phy->speed == 0) {
+		// print("miistatus: phy speed 0\n");
+		return -1;
+	}
+
+	if(phy->fd){
+		p = phy->fc;
+		r = anlpar & (AnaAP|AnaP);
+		if(p == AnaAP && r == (AnaAP|AnaP))
+			phy->tfc = 1;
+		else if(p == (AnaAP|AnaP) && r == AnaAP)
+			phy->rfc = 1;
+		else if((p & AnaP) && (r & AnaP))
+			phy->rfc = phy->tfc = 1;
+	}
+
+	phy->link = 1;
+
+	return 0;
+}

+ 116 - 0
sys/src/9/rb/ethermii.h

@@ -0,0 +1,116 @@
+typedef struct Mii Mii;
+typedef struct MiiPhy MiiPhy;
+
+enum {					/* registers */
+	Bmcr		= 0x00,		/* Basic Mode Control */
+	Bmsr		= 0x01,		/* Basic Mode Status */
+	Phyidr1		= 0x02,		/* PHY Identifier #1 */
+	Phyidr2		= 0x03,		/* PHY Identifier #2 */
+	Anar		= 0x04,		/* Auto-Negotiation Advertisement */
+	Anlpar		= 0x05,		/* AN Link Partner Ability */
+	Aner		= 0x06,		/* AN Expansion */
+	Annptr		= 0x07,		/* AN Next Page TX */
+	Annprr		= 0x08,		/* AN Next Page RX */
+	Mscr		= 0x09,		/* MASTER-SLAVE Control */
+	Mssr		= 0x0A,		/* MASTER-SLAVE Status */
+	Esr		= 0x0F,		/* Extended Status */
+
+	NMiiPhyr	= 32,
+	NMiiPhy		= 32,
+};
+
+enum {					/* Bmcr */
+	BmcrSs1		= 0x0040,	/* Speed Select[1] */
+	BmcrCte		= 0x0080,	/* Collision Test Enable */
+	BmcrDm		= 0x0100,	/* Duplex Mode */
+	BmcrRan		= 0x0200,	/* Restart Auto-Negotiation */
+	BmcrI		= 0x0400,	/* Isolate */
+	BmcrPd		= 0x0800,	/* Power Down */
+	BmcrAne		= 0x1000,	/* Auto-Negotiation Enable */
+	BmcrSs0		= 0x2000,	/* Speed Select[0] */
+	BmcrLe		= 0x4000,	/* Loopback Enable */
+	BmcrR		= 0x8000,	/* Reset */
+};
+
+enum {					/* Bmsr */
+	BmsrEc		= 0x0001,	/* Extended Capability */
+	BmsrJd		= 0x0002,	/* Jabber Detect */
+	BmsrLs		= 0x0004,	/* Link Status */
+	BmsrAna		= 0x0008,	/* Auto-Negotiation Ability */
+	BmsrRf		= 0x0010,	/* Remote Fault */
+	BmsrAnc		= 0x0020,	/* Auto-Negotiation Complete */
+	BmsrPs		= 0x0040,	/* Preamble Suppression Capable */
+	BmsrEs		= 0x0100,	/* Extended Status */
+	Bmsr100T2HD	= 0x0200,	/* 100BASE-T2 HD Capable */
+	Bmsr100T2FD	= 0x0400,	/* 100BASE-T2 FD Capable */
+	Bmsr10THD	= 0x0800,	/* 10BASE-T HD Capable */
+	Bmsr10TFD	= 0x1000,	/* 10BASE-T FD Capable */
+	Bmsr100TXHD	= 0x2000,	/* 100BASE-TX HD Capable */
+	Bmsr100TXFD	= 0x4000,	/* 100BASE-TX FD Capable */
+	Bmsr100T4	= 0x8000,	/* 100BASE-T4 Capable */
+};
+
+enum {					/* Anar/Anlpar */
+	Ana10HD		= 0x0020,	/* Advertise 10BASE-T */
+	Ana10FD		= 0x0040,	/* Advertise 10BASE-T FD */
+	AnaTXHD		= 0x0080,	/* Advertise 100BASE-TX */
+	AnaTXFD		= 0x0100,	/* Advertise 100BASE-TX FD */
+	AnaT4		= 0x0200,	/* Advertise 100BASE-T4 */
+	AnaP		= 0x0400,	/* Pause */
+	AnaAP		= 0x0800,	/* Asymmetrical Pause */
+	AnaRf		= 0x2000,	/* Remote Fault */
+	AnaAck		= 0x4000,	/* Acknowledge */
+	AnaNp		= 0x8000,	/* Next Page Indication */
+};
+
+enum {					/* Mscr */
+	Mscr1000THD	= 0x0100,	/* Advertise 1000BASE-T HD */
+	Mscr1000TFD	= 0x0200,	/* Advertise 1000BASE-T FD */
+};
+
+enum {					/* Mssr */
+	Mssr1000THD	= 0x0400,	/* Link Partner 1000BASE-T HD able */
+	Mssr1000TFD	= 0x0800,	/* Link Partner 1000BASE-T FD able */
+};
+
+enum {					/* Esr */
+	Esr1000THD	= 0x1000,	/* 1000BASE-T HD Capable */
+	Esr1000TFD	= 0x2000,	/* 1000BASE-T FD Capable */
+	Esr1000XHD	= 0x4000,	/* 1000BASE-X HD Capable */
+	Esr1000XFD	= 0x8000,	/* 1000BASE-X FD Capable */
+};
+
+typedef struct Mii {
+	Lock;
+	int	nphy;
+	int	mask;
+	MiiPhy*	phy[NMiiPhy];
+	MiiPhy*	curphy;
+
+	void*	ctlr;
+	int	(*mir)(Mii*, int, int);
+	int	(*miw)(Mii*, int, int, int);
+} Mii;
+
+typedef struct MiiPhy {
+	Mii*	mii;
+	int	oui;
+	int	phyno;
+
+	int	anar;
+	int	fc;
+	int	mscr;
+
+	int	link;
+	int	speed;
+	int	fd;
+	int	rfc;
+	int	tfc;
+};
+
+extern int mii(Mii*, int);
+extern int miiane(Mii*, int, int, int);
+extern int miimir(Mii*, int);
+extern int miimiw(Mii*, int, int);
+extern int miireset(Mii*);
+extern int miistatus(Mii*);

+ 238 - 0
sys/src/9/rb/faultmips.c

@@ -0,0 +1,238 @@
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"ureg.h"
+#include	"../port/error.h"
+#include	"io.h"
+
+enum {
+	Debug = 0,
+};
+
+typedef struct Fault Fault;
+struct Fault {
+	uintptr	va;
+	ulong	pid;
+	uintptr	pc;
+	int	cnt;
+	char	*prog;
+	int	code;
+};
+
+extern char *excname[];
+
+static Fault lflt, maxflt;
+
+/*
+ * Ask if the instruction at EPC could have cause this badvaddr
+ */
+int
+tstbadvaddr(Ureg *ur)
+{
+	int rn;
+	ulong iw, off, ea;
+
+	iw = ur->pc;
+	if(ur->cause & BD)
+		iw += 4;
+
+	if(seg(up, iw, 0) == 0)
+		return 0;
+
+	iw = *(ulong*)iw;
+
+/*	print("iw: %#lux\n", iw);	/**/
+
+	switch((iw>>26) & 0x3f) {
+	default:
+		return 1;
+	case 0x20:	/* LB */
+	case 0x24:	/* LBU */
+			/* LD */
+	case 0x35:
+	case 0x36:
+	case 0x37:	/* LDCz */
+	case 0x1A:	/* LDL */
+	case 0x1B:	/* LDR */
+	case 0x21:	/* LH */
+	case 0x25:	/* LHU */
+	case 0x30:	/* LL */
+	case 0x34:	/* LLD */
+	case 0x23:	/* LW */
+	case 0x31:
+	case 0x32:	/* LWCz possible 0x33 */
+	case 0x27:	/* LWU */
+	case 0x22:	/* LWL */
+	case 0x26:	/* LWR */
+		break;
+
+	case 0x28:	/* SB */
+	case 0x38:	/* SC */
+	case 0x3C:	/* SCD */
+	case 0x3D:
+	case 0x3E:
+	case 0x3F:	/* SDCz */
+	case 0x2C:	/* SDL */
+	case 0x2D:	/* SDR */
+	case 0x29:	/* SH */
+	case 0x2B:	/* SW */
+	case 0x39:
+	case 0x3A:	/* SWCz */
+	case 0x2A:	/* SWL */
+	case 0x2E:	/* SWR */
+		break;
+	}
+
+	off = iw & 0xffff;
+	if(off & 0x8000)
+		off |= ~0xffff;
+
+	rn = (iw>>21) & 0x1f;
+	ea = *reg(ur, rn);
+	if(rn == 0)
+		ea = 0;
+	ea += off;
+
+	/* print("ea %#lux %#lux(R%d) bv %#lux pc %#lux\n", ea, off, rn, ur->badvaddr, ur->pc); /**/
+
+	if(ur->badvaddr == ea)
+		return 0;
+
+	return 1;
+}
+
+/*
+ * we think we get consecutive page faults from unlucky combinations of
+ * scheduling and stlb hashes, and they only happen with 16K pages.
+ * however, we also get page faults while servicing the exact same fault.
+ * more than 5 consecutive faults is unusual, now that we have a better
+ * hash function.
+ *
+ * this can be helpful during mmu and cache debugging.
+ */
+static int
+ckfaultstuck(Ureg *ur, int read, int code)
+{
+	uintptr pc, va;
+
+	va = ur->badvaddr;
+	pc = ur->pc;
+	if (va != lflt.va || up->pid != lflt.pid || pc != lflt.pc ||
+	    code != lflt.code) {
+		/* at least one address or cause is different from last time */
+		lflt.cnt = 1;
+		lflt.va = va;
+		lflt.pid = up->pid;
+		lflt.pc = pc;
+		lflt.code = code;
+		return 0;
+	}
+	++lflt.cnt;
+	if (lflt.cnt >= 1000)	/* fixfault() isn't fixing underlying cause? */
+		panic("fault: %d consecutive faults for va %#p", lflt.cnt, va);
+	if (lflt.cnt > maxflt.cnt) {
+		maxflt.cnt = lflt.cnt;
+		maxflt.va = va;
+		maxflt.pid = up->pid;
+		maxflt.pc = pc;
+		kstrdup(&maxflt.prog, up->text);
+	}
+
+	/* we're servicing that fault now! */
+	/* adjust the threshold and program name to suit */
+	if (lflt.cnt < 5 || strncmp(up->text, "8l", 2) != 0)
+		return 0;
+	iprint("%d consecutive faults for va %#p at pc %#p in %s "
+		"pid %ld\n", lflt.cnt, lflt.va, pc, up->text, lflt.pid);
+	iprint("\t%s: %s%s r31 %#lux tlbvirt %#lux\n",
+		excname[code], va == pc? "[instruction] ": "",
+		(read? "read": "write"), ur->r31, tlbvirt());
+	return 0;
+}
+
+char *
+faultsprint(char *p, char *ep)
+{
+	if (Debug)
+		p = seprint(p, ep,
+			"max consecutive faults %d for va %#p in %s\n",
+			maxflt.cnt, maxflt.va, maxflt.prog);
+	return p;
+}
+
+/*
+ *  find out fault address and type of access.
+ *  Call common fault handler.
+ */
+void
+faultmips(Ureg *ur, int user, int code)
+{
+	int read;
+	ulong addr;
+	char *p, buf[ERRMAX];
+	static int infault, printed;
+
+	if (0 && infault && !printed) {
+		printed = 1;
+		print("fault: recursive fault (%d deep) pc %#p va %#p\n",
+			infault+1, ur->pc, ur->badvaddr);
+	}
+	infault++;
+	if(waserror()){
+		infault--;
+		nexterror();
+	}
+
+	addr = ur->badvaddr;
+	addr &= ~(BY2PG-1);
+
+	read = !(code==CTLBM || code==CTLBS);
+
+/*	print("fault: %s code %d va %#p pc %#p r31 %#lux tlbvirt %#lux\n",
+		up->text, code, ur->badvaddr, ur->pc, ur->r31, tlbvirt());/**/
+
+	if (Debug && ckfaultstuck(ur, read, code) || fault(addr, read) == 0){
+		infault--;
+		poperror();
+		return;
+	}
+
+	infault--;
+	poperror();
+
+	if(tstbadvaddr(ur)) {
+		print("fault: spurious badvaddr %#lux in %s at pc %#lux\n",
+			ur->badvaddr, up->text, ur->pc);/**/
+		return;
+	}
+
+	if(user) {
+		p = "store";
+		if(read)
+			p = "load";
+		snprint(buf, sizeof buf, "sys: trap: fault %s addr=%#lux r31=%#lux",
+			p, ur->badvaddr, ur->r31);
+		postnote(up, 1, buf, NDebug);
+		return;
+	}
+
+	print("kernel %s vaddr=%#lux\n", excname[code], ur->badvaddr);
+	print("st=%#lux pc=%#lux r31=%#lux sp=%#lux\n",
+		ur->status, ur->pc, ur->r31, ur->sp);
+	dumpregs(ur);
+	panic("fault");
+}
+
+/*
+ * called in sysfile.c
+ */
+void
+evenaddr(ulong addr)
+{
+	if(addr & 3){
+		postnote(up, 1, "sys: odd address", NDebug);
+		error(Ebadarg);
+	}
+}

+ 146 - 0
sys/src/9/rb/fns.h

@@ -0,0 +1,146 @@
+#include "../port/portfns.h"
+
+void	arginit(void);
+int	busprobe(ulong);
+ulong	cankaddr(ulong);
+void	cleancache(void);
+void	clearmmucache(void);
+void	clock(Ureg*);
+void	clockinit(void);
+void	clockshutdown(void);
+int	cmpswap(long*, long, long);
+void	coherence(void);
+void	cycles(uvlong *);
+void	dcflush(void*, ulong);
+void	evenaddr(ulong);
+void	faultmips(Ureg*, int, int);
+ulong	fcr31(void);
+void	firmware(int);
+void	fpclear(void);
+void	fpsave(FPsave *);
+void	fptrap(Ureg*);
+int	fpuemu(Ureg *);
+void	fpwatch(Ureg *);
+ulong	getcause(void);
+char*	getconf(char*);
+ulong	getconfig(void);
+ulong	getconfig1(void);
+ulong	getconfig2(void);
+ulong	getconfig3(void);
+ulong	getconfig7(void);
+ulong	gethwreg3(void);
+ulong	getpagemask(void);
+ulong	getstatus(void);
+int	gettlbp(ulong, ulong*);
+ulong	gettlbvirt(int);
+void	gotopc(ulong);
+void	hinv(void);
+int	i8250console(void);
+void	icflush(void *, ulong);
+void	idle(void);
+void	idlehands(void);
+int	inb(int);
+void	insb(int, void*, int);
+ushort	ins(int);
+void	inss(int, void*, int);
+ulong	inl(int);
+void	insl(int, void*, int);
+void	ioinit(void);
+void	introff(int);
+void	intron(int);
+void	intrshutdown(void);
+void	kfault(Ureg*);
+KMap*	kmap(Page*);
+void	kmapinit(void);
+void	kmapinval(void);
+void	kunmap(KMap*);
+void	launchinit(void);
+void	launch(int);
+void	links(void);
+ulong	machstatus(void);
+void	newstart(void);
+int	newtlbpid(Proc*);
+void	online(void);
+void	outb(int, int);
+void	outsb(int, void*, int);
+void	outs(int, ushort);
+void	outss(int, void*, int);
+void	outl(int, ulong);
+void	outsl(int, void*, int);
+ulong	pcibarsize(Pcidev*, int);
+void	pcibussize(Pcidev*, ulong*, ulong*);
+int	pcicfgr8(Pcidev*, int);
+int	pcicfgr16(Pcidev*, int);
+int	pcicfgr32(Pcidev*, int);
+void	pcicfgw8(Pcidev*, int, int);
+void	pcicfgw16(Pcidev*, int, int);
+void	pcicfgw32(Pcidev*, int, int);
+void	pciclrbme(Pcidev*);
+void	pciclrioe(Pcidev*);
+void	pciclrmwi(Pcidev*);
+int	pcigetpms(Pcidev*);
+void	pcihinv(Pcidev*);
+uchar	pciipin(Pcidev*, uchar);
+Pcidev* pcimatch(Pcidev*, int, int);
+Pcidev* pcimatchtbdf(int);
+void	pcireset(void);
+int	pciscan(int, Pcidev**);
+void	pcisetbme(Pcidev*);
+void	pcisetioe(Pcidev*);
+void	pcisetmwi(Pcidev*);
+int	pcisetpms(Pcidev*, int);
+ulong	prid(void);
+void	procrestore(Proc *);
+void	procsave(Proc *);
+#define	procsetup(p)	((p)->fpstate = FPinit)
+void	purgetlb(int);
+Softtlb*	putstlb(ulong, ulong);
+int	puttlb(ulong, ulong, ulong);
+void	puttlbx(int, ulong, ulong, ulong, int);
+ulong	rdcompare(void);
+ulong	rdcount(void);
+ulong*	reg(Ureg*, int);
+void	restfpregs(FPsave*, ulong);
+void	intrenable(int, void(*)(void *), void *);
+void	setleveldest(int, int, uvlong*);
+void	setpagemask(ulong);
+void	setsp(ulong);
+void	setstatus(ulong);
+void	setwatchhi0(ulong);
+void	setwatchlo0(ulong);
+void	setwired(ulong);
+void	sicwdog(void);
+void	silencewdog(void);
+ulong	stlbhash(ulong);
+void	stopwdog(void);
+void	syncclock(void);
+long	syscall(Ureg*);
+void	syscallfmt(int syscallno, ulong pc, va_list list);
+void	sysretfmt(int syscallno, va_list list, long ret, uvlong start, uvlong stop);
+int	tas(ulong*);
+void	tlbinit(void);
+ulong	tlbvirt(void);
+void	touser(uintptr);
+void	unleash(void);
+#define	userureg(ur) ((ur)->status & KUSER)
+void	vecinit(void);
+void	vector0(void);
+void	vector100(void);
+void	vector180(void);
+void	wdogreset(void);
+void	wrcompare(ulong);
+void	wrcount(ulong);
+ulong	wiredpte(vlong);
+void	machwire(void);
+void	_uartputs(char*, int);
+int	_uartprint(char*, ...);
+
+#define PTR2UINT(p)	((uintptr)(p))
+#define UINT2PTR(i)	((void*)(i))
+
+#define	waserror()	(up->nerrlab++, setlabel(&up->errlab[up->nerrlab-1]))
+
+#define KADDR(a)	((void*)((ulong)(a)|KSEG0))
+#define PADDR(a)	((ulong)(a)&~KSEGM)
+
+#define KSEG1ADDR(a)	((void*)((ulong)(a)|KSEG1))

+ 1485 - 0
sys/src/9/rb/fpimips.c

@@ -0,0 +1,1485 @@
+/*
+ * this doesn't attempt to implement MIPS floating-point properties
+ * that aren't visible in the Inferno environment.
+ * all arithmetic is done in double precision.
+ * the FP trap status isn't updated.
+ *
+ * we emulate the original MIPS FP register model: 32-bits each,
+ * F(2n) and F(2n+1) are a double, with lower-order word first;
+ * note that this is little-endian order, unlike the rest of the
+ * machine, so double-word operations will need to swap the words
+ * when transferring between FP registers and memory.
+ *
+ * on some machines, we can convert to an FP internal representation when
+ * moving to FPU registers and back (to integer, for example) when moving
+ * from them.  the MIPS is different: its conversion instructions operate
+ * on FP registers only, and there's no way to tell if data being moved
+ * into an FP register is integer or FP, so it must be possible to store
+ * integers in FP registers without conversion.  Furthermore, pairs of FP
+ * registers can be combined into a double.  So we keep the raw bits
+ * around as the canonical representation and convert only to and from
+ * Internal FP format when we must (i.e., before calling the common fpi
+ * code).
+ */
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"ureg.h"
+#include	"../port/fpi.h"
+#include	<tos.h>
+
+#ifdef FPEMUDEBUG
+#define DBG(bits) (fpemudebug & (bits))
+#define intpr _intpr
+#define internsane _internsane
+#define dbgstuck _dbgstuck
+#else
+#define DBG(bits) (0)
+#define internsane(i, ur)	do { USED(ur); } while(0)
+#define intpr(i, reg, fmt, ufp)	do {} while(0)
+#define dbgstuck(pc, ur, ufp)	do {} while(0)
+#endif
+
+#define	OFR(memb) (uintptr)&((Ureg*)0)->memb	/* offset into Ureg of memb */
+#define	REG(ur, r) *acpureg(ur, r)			/* cpu reg in Ureg */
+#define	FREG(ufp, fr) (ufp)->reg[(fr) & REGMASK]	/* fp reg raw bits */
+
+/*
+ * instruction decoding for COP1 instructions; integer instructions
+ * are laid out differently.
+ */
+#define OP(ul)	 ((ul) >> 26)
+#define REGMASK MASK(5)				/* mask for a register number */
+#define FMT(ul)	 (((ul) >> 21) & REGMASK)	/* data type */
+#define REGT(ul) (((ul) >> 16) & REGMASK)	/* source2 register */
+#define REGS(ul) (((ul) >> 11) & REGMASK)	/* source1 register */
+#define REGD(ul) (((ul) >>  6) & REGMASK)	/* destination register */
+#define FUNC(ul) ((ul) & MASK(6))
+
+enum {
+	Dbgbasic = 1<<0,	/* base debugging: ops, except'ns */
+	Dbgmoves = 1<<1,	/* not very exciting usually */
+	Dbgregs	 = 1<<2,	/* print register contents around ops */
+	Dbgdelay = 1<<3,	/* branch-delay-slot-related machinery */
+
+	/* fpimips status codes */
+	Failed = -1,
+	Advpc,				/* advance pc normally */
+	Leavepc,			/* don't change the pc */
+	Leavepcret,			/* ... and return to user mode now */
+	Nomatch,
+
+	/* no-ops */
+	NOP	= 0x27,			/* NOR R0, R0, R0 */
+	MIPSNOP = 0,			/* SLL R0, R0, R0 */
+
+	/* fp op-codes */
+	COP1	= 0x11,			/* fpu op */
+	LWC1	= 0x31,			/* load float/long */
+	LDC1	= 0x35,			/* load double/vlong */
+	SWC1	= 0x39,			/* store float/long */
+	SDC1	= 0x3d,			/* store double/vlong */
+
+	N = 1<<31,			/* condition codes */
+	Z = 1<<30,
+	C = 1<<29,
+	V = 1<<28,
+
+	/* data types (format field values) */
+	MFC1	= 0,			/* and func == 0 ... */
+	DMFC1,				/* vlong move */
+	CFC1,				/* ctl word move */
+	MTC1	= 4,
+	DMTC1,
+	CTC1,				/* ... end `and func == 0' */
+	BRANCH	= 8,
+	Ffloat	= 16,
+	Fdouble,
+	Flong	= 20,
+	Fvlong,
+
+	/* fp control registers */
+	Fpimp	= 0,
+	Fpcsr	= 31,
+};
+
+typedef struct FP1 FP1;
+typedef struct FP2 FP2;
+typedef struct FPcvt FPcvt;
+typedef struct Instr Instr;
+
+struct Instr {	/* a COP1 instruction, broken out and registers converted */
+	int	iw;		/* whole word */
+	uintptr	pc;
+	int	o;		/* opcode or cop1 func code */
+	int	fmt;		/* operand format */
+	int	rm;		/* first operand register */
+	int	rn;		/* second operand register */
+	int	rd;		/* destination register */
+
+	Internal *fm;		/* converted from FREG(ufp, rm) */
+	Internal *fn;
+	char	*dfmt;
+	FPsave	*ufp;		/* fp state, including fp registers */
+	Ureg	*ur;		/* user registers */
+};
+
+struct FP2 {
+	char*	name;
+	void	(*f)(Internal*, Internal*, Internal*);
+};
+
+struct FP1 {
+	char*	name;
+	void	(*f)(Internal*, Internal*);
+};
+
+struct FPcvt {
+	char*	name;
+	void	(*f)(int, int, int, Ureg *, FPsave *);
+};
+
+static	int	roff[32] = {
+	0,       OFR(r1), OFR(r2), OFR(r3),
+	OFR(r4), OFR(r5), OFR(r6), OFR(r7),
+	OFR(r8), OFR(r9), OFR(r10), OFR(r11),
+	OFR(r12), OFR(r13), OFR(r14), OFR(r15),
+	OFR(r16), OFR(r17), OFR(r18), OFR(r19),
+	OFR(r20), OFR(r21), OFR(r22), OFR(r23),
+	OFR(r24), OFR(r25), OFR(r26), OFR(r27),
+	OFR(r28), OFR(sp),  OFR(r30), OFR(r31),
+};
+
+/*
+ * plan 9 assumes F24 initialized to 0.0, F26 to 0.5, F28 to 1.0, F30 to 2.0.
+ */
+enum {
+	FZERO = 24,
+	FHALF = 26,
+};
+static Internal fpconst[Nfpregs] = {		/* indexed by register no. */
+	/* s, e, l, h */
+[FZERO]	{0, 0x1, 0x00000000, 0x00000000},	/* 0.0 */
+[FHALF]	{0, 0x3FE, 0x00000000, 0x08000000},	/* 0.5 */
+[28]	{0, 0x3FF, 0x00000000, 0x08000000},	/* 1.0 */
+[30]	{0, 0x400, 0x00000000, 0x08000000},	/* 2.0 */
+};
+
+static char *fmtnames[] = {
+[MFC1]	"MF",
+[DMFC1]	"DMF",
+[CFC1]	"CF",
+[MTC1]	"MT",
+[DMTC1]	"DMT",
+[CTC1]	"CT",
+[BRANCH]"BR",
+
+[Ffloat]"F",
+[Fdouble]"D",
+[Flong]	"W",
+[Fvlong]"L",
+};
+
+static char *prednames[] = {
+[0]	"F",
+[1]	"UN",
+[2]	"EQ",
+[3]	"UEQ",
+[4]	"OLT",
+[5]	"ULT",
+[6]	"OLE",
+[7]	"ULE",
+[8]	"SF",
+[9]	"NGLE",
+[10]	"SEQ",
+[11]	"NGL",
+[12]	"LT",
+[13]	"NGE",
+[14]	"LE",
+[15]	"NGT",
+};
+
+int fpemudebug = 0;			/* settable via /dev/archctl */
+
+static ulong dummyr0;
+static QLock watchlock;			/* lock for watch-points */
+
+ulong	branch(Ureg*, ulong);
+int	isbranch(ulong *);
+
+static int	fpimips(ulong, ulong, Ureg *, FPsave *);
+
+char *
+fpemuprint(char *p, char *ep)
+{
+#ifdef FPEMUDEBUG
+	return seprint(p, ep, "fpemudebug %d\n", fpemudebug);
+#else
+	USED(ep);
+	return p;
+#endif
+}
+
+static ulong *
+acpureg(Ureg *ur, int r)
+{
+	r &= REGMASK;
+	if (r == 0 || roff[r] == 0) {
+		dummyr0 = 0;
+		return &dummyr0;
+	}
+	return (ulong *)((char*)ur + roff[r]);
+}
+
+ulong *
+reg(Ureg *ur, int r)		/* for faultmips */
+{
+	return &REG(ur, r);
+}
+
+static void
+_internsane(Internal *i, Ureg *ur)
+{
+	static char buf[ERRMAX];
+
+	USED(i);
+	if (!(DBG(Dbgbasic)))
+		return;
+	if ((unsigned)i->s > 1) {
+		snprint(buf, sizeof buf,
+			"fpuemu: bogus Internal sign at pc=%#p", ur->pc);
+		error(buf);
+	}
+	if ((unsigned)i->e > DoubleExpMax) {
+		snprint(buf, sizeof buf,
+			"fpuemu: bogus Internal exponent at pc=%#p", ur->pc);
+		error(buf);
+	}
+}
+
+/*
+ * mips binary operations (d = n operator m)
+ */
+
+static void
+fadd(Internal *m, Internal *n, Internal *d)
+{
+	(m->s == n->s? fpiadd: fpisub)(m, n, d);
+}
+
+static void
+fsub(Internal *m, Internal *n, Internal *d)
+{
+	m->s ^= 1;
+	(m->s == n->s? fpiadd: fpisub)(m, n, d);
+}
+
+/*
+ * mips unary operations
+ */
+
+static void
+frnd(Internal *m, Internal *d)
+{
+	short e;
+	Internal tmp;
+
+	tmp = fpconst[FHALF];
+	(m->s? fsub: fadd)(&tmp, m, d);
+	if(IsWeird(d))
+		return;
+	fpiround(d);
+	e = (d->e - ExpBias) + 1;
+	if(e <= 0)
+		SetZero(d);
+	else if(e > FractBits){
+		if(e < 2*FractBits)
+			d->l &= ~((1<<(2*FractBits - e))-1);
+	}else{
+		d->l = 0;
+		if(e < FractBits)
+			d->h &= ~((1<<(FractBits-e))-1);
+	}
+}
+
+/* debugging: print internal representation of an fp reg */
+static void
+_intpr(Internal *i, int reg, int fmt, FPsave *ufp)
+{
+	USED(i);
+	if (!(DBG(Dbgregs)))
+		return;
+	if (fmt == Fdouble && reg < 31)
+		iprint("\tD%02d: l %08lux h %08lux =\ts %d e %04d h %08lux l %08lux\n",
+			reg, FREG(ufp, reg), FREG(ufp, reg+1),
+			i->s, i->e, i->h, i->l);
+	else
+		iprint("\tF%02d: %08lux =\ts %d e %04d h %08lux l %08lux\n",
+			reg, FREG(ufp, reg),
+			i->s, i->e, i->h, i->l);
+	delay(75);
+}
+
+static void
+dreg2dbl(Double *dp, int reg, FPsave *ufp)
+{
+	reg &= ~1;
+	dp->l = FREG(ufp, reg);
+	dp->h = FREG(ufp, reg+1);
+}
+
+static void
+dbl2dreg(int reg, Double *dp, FPsave *ufp)
+{
+	reg &= ~1;
+	FREG(ufp, reg)   = dp->l;
+	FREG(ufp, reg+1) = dp->h;
+}
+
+static void
+vreg2dbl(Double *dp, int reg, FPsave *ufp)
+{
+	reg &= ~1;
+	dp->l = FREG(ufp, reg+1);
+	dp->h = FREG(ufp, reg);
+}
+
+static void
+dbl2vreg(int reg, Double *dp, FPsave *ufp)
+{
+	reg &= ~1;
+	FREG(ufp, reg+1) = dp->l;
+	FREG(ufp, reg)   = dp->h;
+}
+
+/* convert fmt (rm) to double (rd) */
+static void
+fcvtd(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
+{
+	Double d;
+	Internal intrn;
+
+	switch (fmt) {
+	case Ffloat:
+		fpis2i(&intrn, &FREG(ufp, rm));
+		internsane(&intrn, ur);
+		fpii2d(&d, &intrn);
+		break;
+	case Fdouble:
+		dreg2dbl(&d, rm, ufp);
+		break;
+	case Flong:
+		fpiw2i(&intrn, &FREG(ufp, rm));
+		internsane(&intrn, ur);
+		fpii2d(&d, &intrn);
+		break;
+	case Fvlong:
+		vreg2dbl(&d, rm, ufp);
+		fpiv2i(&intrn, &d);
+		internsane(&intrn, ur);
+		fpii2d(&d, &intrn);
+		break;
+	}
+	dbl2dreg(rd, &d, ufp);
+	if (fmt != Fdouble && DBG(Dbgregs))
+		intpr(&intrn, rm, Fdouble, ufp);
+}
+
+/* convert fmt (rm) to single (rd) */
+static void
+fcvts(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
+{
+	Double d;
+	Internal intrn;
+
+	switch (fmt) {
+	case Ffloat:
+		FREG(ufp, rd) = FREG(ufp, rm);
+		break;
+	case Fdouble:
+		dreg2dbl(&d, rm, ufp);
+		fpid2i(&intrn, &d);
+		break;
+	case Flong:
+		fpiw2i(&intrn, &FREG(ufp, rm));
+		break;
+	case Fvlong:
+		vreg2dbl(&d, rm, ufp);
+		fpiv2i(&intrn, &d);
+		break;
+	}
+	if (fmt != Ffloat) {
+		if(DBG(Dbgregs))
+			intpr(&intrn, rm, Ffloat, ufp);
+		internsane(&intrn, ur);
+		fpii2s(&FREG(ufp, rd), &intrn);
+	}
+}
+
+/* convert fmt (rm) to long (rd) */
+static void
+fcvtw(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
+{
+	Double d;
+	Internal intrn;
+
+	switch (fmt) {
+	case Ffloat:
+		fpis2i(&intrn, &FREG(ufp, rm));
+		break;
+	case Fdouble:
+		dreg2dbl(&d, rm, ufp);
+		fpid2i(&intrn, &d);
+		break;
+	case Flong:
+		FREG(ufp, rd) = FREG(ufp, rm);
+		break;
+	case Fvlong:
+		vreg2dbl(&d, rm, ufp);
+		fpiv2i(&intrn, &d);
+		break;
+	}
+	if (fmt != Flong) {
+		if(DBG(Dbgregs))
+			intpr(&intrn, rm, Flong, ufp);
+		internsane(&intrn, ur);
+		fpii2w((long *)&FREG(ufp, rd), &intrn);
+	}
+}
+
+/* convert fmt (rm) to vlong (rd) */
+static void
+fcvtv(int fmt, int rm, int rd, Ureg *ur, FPsave *ufp)
+{
+	Double d;
+	Internal intrn;
+
+	switch (fmt) {
+	case Ffloat:
+		fpis2i(&intrn, &FREG(ufp, rm));
+		break;
+	case Fdouble:
+		dreg2dbl(&d, rm, ufp);
+		fpid2i(&intrn, &d);
+		break;
+	case Flong:
+		fpiw2i(&intrn, &FREG(ufp, rm));
+		break;
+	case Fvlong:
+		vreg2dbl(&d, rm, ufp);
+		dbl2vreg(rd, &d, ufp);
+		break;
+	}
+	if (fmt != Fvlong) {
+		if(DBG(Dbgregs))
+			intpr(&intrn, rm, Fvlong, ufp);
+		internsane(&intrn, ur);
+		fpii2v((vlong *)&FREG(ufp, rd), &intrn);
+	}
+}
+
+/*
+ * MIPS function codes
+ */
+
+static	FP2	optab2[] = {	/* Fd := Fn OP Fm (binary) */
+[0]	{"ADDF",	fadd},	/* can ignore fmt, just use doubles */
+[1]	{"SUBF",	fsub},
+[2]	{"MULF",	fpimul},
+[3]	{"DIVF",	fpidiv},
+};
+
+static	FP1	optab1[32] = {	/* Fd := OP Fm (unary) */
+[4]	{"SQTF",	/*fsqt*/0},
+[5]	{"ABSF",	/*fabsf*/0},	/* inline in unaryemu... */
+[6]	{"MOVF",	/*fmov*/0},
+[7]	{"NEGF",	/*fmovn*/0},
+[8]	{"ROUND.L",	/*froundl*/0},	/* 64-bit integer results ... */
+[9]	{"TRUNC.L",	/*ftruncl*/0},
+[10]	{"CEIL.L",	/*fceill*/0},
+[11]	{"FLOOR.L",	/*ffloorl*/0},
+[12]	{"ROUND.W",	frnd},		/* 32-bit integer results ... */
+[13]	{"TRUNC.W",	/*ftrunc*/0},
+[14]	{"CEIL.W",	/*fceil*/0},
+[15]	{"FLOOR.W",	/*ffloor*/0},
+/* 17—19 are newish MIPS32/64 conditional moves */
+/* 21, 22, 28—31 are newish reciprocal or sqrt */
+};
+
+static	FPcvt	optabcvt[] = {	/* Fd := OP(fmt, Fm) (unary) */
+[32]	{"CVT.S",	fcvts},		/* must honour fmt as src format */
+[33]	{"CVT.D",	fcvtd},
+[36]	{"CVT.W",	fcvtw},
+[37]	{"CVT.L",	fcvtv},
+};
+
+/*
+ * No type conversion is implied and the type of the cpu register is
+ * unknown, so copy the bits into reg.
+ * Later instructions will have to know the correct type and use the
+ * right format specifier to convert to or from Internal FP.
+ */
+static void
+fld(int d, ulong ea, int n, FPsave *ufp)
+{
+	if(DBG(Dbgmoves))
+		iprint("MOV%c #%lux, F%d\n", n==8? 'D': 'F', ea, d);
+	if (n == 4)
+		memmove(&FREG(ufp, d), (void *)ea, 4);
+	else if (n == 8){
+		d &= ~1;
+		/* NB: we swap order of the words */
+		memmove(&FREG(ufp, d), (void *)(ea+4), 4);
+		memmove(&FREG(ufp, d+1), (void *)ea, 4);
+	} else
+		panic("fld: n (%d) not 4 nor 8", n);
+}
+
+static void
+fst(ulong ea, int s, int n, FPsave *ufp)
+{
+	if(DBG(Dbgmoves))
+		iprint("MOV%c	F%d,#%lux\n", n==8? 'D': 'F', s, ea);
+	if (n == 4)
+		memmove((void *)ea, &FREG(ufp, s), 4);
+	else if (n == 8){
+		s &= ~1;
+		/* NB: we swap order of the words */
+		memmove((void *)(ea+4), &FREG(ufp, s), 4);
+		memmove((void *)ea, &FREG(ufp, s+1), 4);
+	} else
+		panic("fst: n (%d) not 4 nor 8", n);
+}
+
+void
+unimp(ulong pc, ulong op, char *msg)
+{
+	char buf[120];
+
+	snprint(buf, sizeof(buf), "sys: fp: pc=%#lux unimp fp %#.8lux: %s",
+		pc, op, msg);
+	if(DBG(Dbgbasic))
+		iprint("FPE: %s\n", buf);
+	error(buf);
+	/* no return */
+}
+
+static int
+isfpop(ulong iw)
+{
+	switch (OP(iw)) {
+	case COP1:
+	case LWC1:
+	case LDC1:
+	case SWC1:
+	case SDC1:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static int
+ldst(ulong op, Ureg *ur, FPsave *ufp)
+{
+	int rn, rd, o, size, wr;
+	short off;
+	ulong ea;
+
+	/* we're using the COP1 macros, but the fields have diff'nt meanings */
+	o = OP(op);
+	rn = FMT(op);
+	off = op;
+	ea = REG(ur, rn) + off;
+	rd = REGT(op);
+//iprint("fpemu: ld/st (F%d)=%#lux + %d => ea %#lux\n", rn, REG(ur, rn), off, ea);
+
+	size = 4;
+	if (o == LDC1 || o == SDC1)
+		size = 8;
+	wr = (o == SWC1 || o == SDC1);
+	validaddr(ea, size, wr);
+
+	switch (o) {
+	case LWC1:	/* load an fp register, rd, from memory */
+	case LDC1:	/* load an fp register pair, (rd, rd+1), from memory */
+		fld(rd, ea, size, ufp);
+		break;
+	case SWC1:	/* store an fp register, rd, into memory */
+	case SDC1:	/* store an fp register pair, (rd, rd+1), into memory */
+		fst(ea, rd, size, ufp);
+		break;
+	default:
+		unimp(ur->pc, op, "unknown non-COP1 load or store");
+		return Failed;
+	}
+	return Advpc;
+}
+
+static int
+cop1mov(Instr *ip)
+{
+	int fs, rt;
+	uvlong vl;
+	FPsave *ufp;
+	Ureg *ur;
+
+	fs = ip->rm;		/* F(s) aka rm */
+	rt = ip->rn;		/* R(t) aka rn */
+	ur = ip->ur;
+	ufp = ip->ufp;
+//iprint("fpemu: cop1 prob ld/st (R%d)=%#lux FREG%d\n", rn, REG(ip->ur, rn), rm);
+
+	/* MIPS fp register pairs are in little-endian order: low word first */
+	switch (ip->fmt) {
+	case MTC1:
+		/* load an fp register, F(s), from cpu register R(t) */
+		fld(fs, (uintptr)&REG(ur, rt), 4, ufp);
+		return Advpc;
+	case DMTC1:
+		/*
+		 * load an fp register pair, (F(s), F(s+1)),
+		 * from cpu registers (rt, rt+1)
+		 */
+		iprint("fpemu: 64-bit DMTC1 may have words backward\n");
+		rt &= ~1;
+		vl = (uvlong)REG(ur, rt+1) << 32 | REG(ur, rt);
+		fld(fs & ~1, (uintptr)&vl, 8, ufp);
+		return Advpc;
+	case MFC1:
+		/* store an fp register, fs, into a cpu register rt */
+		fst((uintptr)&REG(ur, rt), fs, 4, ufp);
+		return Advpc;
+	case DMFC1:
+		/*
+		 * store an fp register pair, (F(s), F(s+1)),
+		 * into cpu registers (rt, rt+1)
+		 */
+		iprint("fpemu: 64-bit DMFC1 may have words backward\n");
+		fst((uintptr)&vl, fs & ~1, 8, ufp);
+		rt &= ~1;
+		REG(ur, rt) = (ulong)vl;
+		REG(ur, rt+1) = vl>>32;
+		return Advpc;
+	case CFC1:
+		switch (fs) {
+		case Fpimp:			/* MOVW FCR0,Rn */
+			REG(ur, rt) = 0x500;	/* claim to be r4k */
+			break;
+		case Fpcsr:
+			REG(ur, rt) = ufp->fpcontrol;
+			break;
+		}
+		if(DBG(Dbgbasic))
+			iprint("MOVW	FCR%d, R%d\n", fs, rt);
+		return Advpc;
+	case CTC1:
+		switch (fs) {
+		case Fpcsr:
+			ufp->fpcontrol = REG(ur, rt);
+			break;
+		}
+		if(DBG(Dbgbasic))
+			iprint("MOVW	R%d, FCR%d\n", rt, fs);
+		return Advpc;
+	}
+	return Nomatch;			/* not a load or store; keep looking */
+}
+
+static char *
+decodefmt(int fmt)
+{
+	if (fmtnames[fmt])
+		return fmtnames[fmt];
+	else
+		return "GOK";
+}
+
+static char *
+predname(int pred)			/* predicate name */
+{
+	if (prednames[pred])
+		return prednames[pred];
+	else
+		return "GOK";
+}
+
+static int
+fcmpf(Internal m, Internal n, int, int cond)
+{
+	int i;
+
+	if(IsWeird(&m) || IsWeird(&n)){
+		/* BUG: should trap if not masked */
+		return 0;
+	}
+	fpiround(&n);
+	fpiround(&m);
+	i = fpicmp(&m, &n);		/* returns -1, 0, or 1 */
+	switch (cond) {
+	case 0:			/* F - false */
+	case 1:			/* UN - unordered */
+		return 0;
+	case 2:			/* EQ */
+	case 3:			/* UEQ */
+		return i == 0;
+	case 4:			/* OLT */
+	case 5:			/* ULT */
+		return i < 0;
+	case 6:			/* OLE */
+	case 7:			/* ULE */
+		return i <= 0;
+	case 8:			/* SF */
+	case 9:			/* NGLE - not >, < or = */
+		return 0;
+	case 10:		/* SEQ */
+		return i == 0;
+	case 11:		/* NGL */
+		return i != 0;
+	case 12:		/* LT */
+	case 13:		/* NGE */
+		return i < 0;
+	case 14:		/* LE */
+	case 15:		/* NGT */
+		return i <= 0;
+	}
+	return 0;
+}
+
+/*
+ * assuming that ur->pc points to a branch instruction,
+ * change it to point to the branch's target and return it.
+ */
+static uintptr
+followbr(Ureg *ur)
+{
+	uintptr npc;
+
+	npc = branch(ur, up->fpsave.fpstatus);
+	if(npc == 0)
+		panic("fpemu: branch expected but not seen at %#p", ur->pc);
+	ur->pc = npc;
+	return npc;
+}
+
+/* emulate COP1 instruction in branch delay slot */
+static void
+dsemu(Instr *ip, ulong dsinsn, Ureg *ur, FPsave *ufp)
+{
+	uintptr npc;
+
+	npc = ur->pc;		/* save ur->pc since fpemu will change it */
+	if(DBG(Dbgdelay))
+		iprint(">>> emulating br delay slot\n");
+
+	fpimips(ip->pc + 4, dsinsn, ur, ufp);
+
+	if(DBG(Dbgdelay))
+		iprint("<<< done emulating br delay slot\n");
+	ur->pc = npc;
+}
+
+/*
+ * execute non-COP1 instruction in branch delay slot, in user mode with
+ * user registers, then trap so we can finish up and take the branch.
+ */
+static void
+dsexec(Instr *ip, Ureg *ur, FPsave *ufp)
+{
+	ulong dsaddr, wpaddr;
+	Tos *tos;
+
+	/*
+	 * copy delay slot, EHB, EHB, EHB to tos->kscr, flush caches,
+	 * point pc there, set watch point on tos->kscr[2], return.
+	 * this is safe since we've already checked for branches (and FP
+	 * instructions) in the delay slot, so the instruction can be
+	 * executed at any address.
+	 */
+	dsaddr = ip->pc + 4;
+	tos = (Tos*)(USTKTOP-sizeof(Tos));
+	tos->kscr[0] = *(ulong *)dsaddr;
+	tos->kscr[1] = 0xc0;		/* EHB; we could use some trap instead */
+	tos->kscr[2] = 0xc0;			/* EHB */
+	tos->kscr[3] = 0xc0;			/* EHB */
+	dcflush(tos->kscr, sizeof tos->kscr);
+	icflush(tos->kscr, sizeof tos->kscr);
+
+	wpaddr = (ulong)&tos->kscr[2] & ~7;	/* clear I/R/W bits */
+	ufp->fpdelayexec = 1;
+	ufp->fpdelaypc = ip->pc;		/* remember branch ip->pc */
+	ufp->fpdelaysts = ufp->fpstatus;	/* remember state of FPCOND */
+	ur->pc = (ulong)tos->kscr;		/* restart in tos */
+	qlock(&watchlock);			/* wait for first watchpoint */
+	setwatchlo0(wpaddr | 1<<2);	/* doubleword addr(!); i-fetches only */
+	setwatchhi0(TLBPID(tlbvirt())<<16);	/* asid; see mmu.c */
+	if (DBG(Dbgdelay))
+		iprint("fpemu: set %s watch point at %#lux, after br ds %#lux...",
+			up->text, wpaddr, *(ulong *)dsaddr);
+	/* return to user mode, await fpwatch() trap */
+}
+
+void
+fpwatch(Ureg *ur)			/* called on watch-point trap */
+{
+	FPsave *ufp;
+
+	ufp = &up->fpsave;
+	if(ufp->fpdelayexec == 0)
+		panic("fpwatch: unexpected watch trap");
+
+	/* assume we got here after branch-delay-slot execution */
+	ufp->fpdelayexec = 0;
+	setwatchlo0(0);
+	setwatchhi0(0);
+	qunlock(&watchlock);
+
+	ur->pc = ufp->fpdelaypc;	/* pc of fp branch */
+	ur->cause &= BD;		/* take no chances */
+	ufp->fpstatus = ufp->fpdelaysts;
+	followbr(ur);			/* sets ur->pc to fp branch target */
+	if (DBG(Dbgdelay))
+		iprint("delay slot executed; resuming at %#lux\n", ur->pc);
+}
+
+static ulong
+validiw(uintptr pc)
+{
+	validaddr(pc, 4, 0);
+	return *(ulong*)pc;
+}
+
+/*
+ * COP1 (6) | BRANCH (5) | cc (3) | likely | true | offset(16)
+ *	cc = ip->rn >> 2;			// assume cc == 0
+ */
+static int
+bremu(Instr *ip)
+{
+	int off, taken;
+	ulong dsinsn;
+	FPsave *ufp;
+	Ureg *ur;
+
+	if (ip->iw & (1<<17))
+		error("fpuemu: `likely' fp branch (obs)");
+	ufp = ip->ufp;
+	if (ufp->fpstatus & FPCOND)
+		taken = ip->iw & (1<<16);	/* taken iff BCT */
+	else
+		taken = !(ip->iw & (1<<16));	/* taken iff BCF */
+	dsinsn = validiw(ip->pc + 4);		/* delay slot addressible? */
+	if(DBG(Dbgdelay)){
+		off = (short)(ip->iw & MASK(16));
+		iprint("BFP%c\t%d(PC): %staken\n", (ip->iw & (1<<16)? 'T': 'F'),
+			off, taken? "": "not ");
+		iprint("\tdelay slot: %08lux\n", dsinsn);
+		delay(75);
+	}
+	ur = ip->ur;
+	assert(ur->pc == ip->pc);
+	if(!taken)
+		return Advpc;	/* didn't branch, so return to delay slot */
+
+	/*
+	 * fp branch taken; emulate or execute the delay slot, then jump.
+	 */
+	if(dsinsn == NOP || dsinsn == MIPSNOP){
+		;				/* delay slot does nothing */
+	}else if(isbranch((ulong *)(ip->pc + 4)))
+		error("fpuemu: branch in fp branch delay slot");
+	else if (isfpop(dsinsn))
+		dsemu(ip, dsinsn, ur, ufp);	/* emulate delay slot */
+	else{
+		/*
+		 * The hard case: we need to execute the delay slot
+		 * in user mode with user registers.  Set a watch point,
+		 * return to user mode, await fpwatch() trap.
+		 */
+		dsexec(ip, ur, ufp);
+		return Leavepcret;
+	}
+	followbr(ur);
+	return Leavepc;
+}
+
+/* interpret fp reg as fmt (float or double) and convert to Internal */
+static void
+reg2intern(Internal *i, int reg, int fmt, Ureg *ur)
+{
+	Double d;
+	FPsave *ufp;
+
+	/* we may see other fmt types on conversion or unary ops; ignore */
+	ufp = &up->fpsave;
+	switch (fmt) {
+	case Ffloat:
+		fpis2i(i, &FREG(ufp, reg));
+		internsane(i, ur);
+		break;
+	case Fdouble:
+		dreg2dbl(&d, reg, ufp);
+		fpid2i(i, &d);
+		internsane(i, ur);
+		break;
+	default:
+		SetQNaN(i);		/* cause trouble if we try to use i */
+		break;
+	}
+}
+
+/* convert Internal to fp reg as fmt (float or double) */
+static void
+intern2reg(int reg, Internal *i, int fmt, Ureg *ur)
+{
+	Double d;
+	FPsave *ufp;
+	Internal tmp;
+
+	ufp = &up->fpsave;
+	tmp = *i;		/* make a disposable copy */
+	internsane(&tmp, ur);
+	switch (fmt) {
+	case Ffloat:
+		fpii2s(&FREG(ufp, reg), &tmp);
+		break;
+	case Fdouble:
+		fpii2d(&d, &tmp);
+		dbl2dreg(reg, &d, ufp);
+		break;
+	default:
+		panic("intern2reg: bad fmt %d", fmt);
+	}
+}
+
+/*
+ * comparisons - encoded slightly differently than arithmetic:
+ * COP1 (6) | fmt(5) | ft (5) | fs (5) | # same
+ *	cc (3) | 0 | A=0 |		# diff, was REGD
+ *	FC=11 | cond (4)		# FUNC
+ */
+static int
+cmpemu(Instr *ip)
+{
+	int cc, cond;
+
+	cc = ip->rd >> 2;
+	cond = ip->o & MASK(4);
+	reg2intern(ip->fn, ip->rn, ip->fmt, ip->ur);
+	/* fpicmp args are swapped, so this is `n compare m' */
+	if (fcmpf(*ip->fm, *ip->fn, cc, cond))
+		ip->ufp->fpstatus |= FPCOND;
+	else
+		ip->ufp->fpstatus &= ~FPCOND;
+	if(DBG(Dbgbasic))
+		iprint("CMP%s.%s	F%d,F%d =%d\n", predname(cond), ip->dfmt,
+			ip->rm, ip->rn, (ip->ufp->fpstatus & FPCOND? 1: 0));
+	if(DBG(Dbgregs)) {
+		intpr(ip->fm, ip->rm, ip->fmt, ip->ufp);
+		intpr(ip->fn, ip->rn, ip->fmt, ip->ufp);
+		delay(75);
+	}
+	return Advpc;
+}
+
+static int
+binemu(Instr *ip)
+{
+	FP2 *fp;
+	Internal fd, prfd;
+	Internal *fn;
+
+	fp = &optab2[ip->o];
+	if(fp->f == nil)
+		unimp(ip->pc, ip->iw, "missing binary op");
+
+	/* convert the second operand */
+	fn = ip->fn;
+	reg2intern(fn, ip->rn, ip->fmt, ip->ur);
+	if(DBG(Dbgregs))
+		intpr(fn, ip->rn, ip->fmt, ip->ufp);
+
+	if(DBG(Dbgbasic)){
+		iprint("%s.%s\tF%d,F%d,F%d\n", fp->name, ip->dfmt,
+			ip->rm, ip->rn, ip->rd);
+		delay(75);
+	}
+	/*
+	 * fn and fm are scratch Internals just for this instruction,
+	 * so it's okay to let the fpi routines trash them in the course
+	 * of operation.
+	 */
+	/* NB: fpi routines take m and n (s and t) in reverse order */
+	(*fp->f)(fn, ip->fm, &fd);
+
+	/* convert the result */
+	if(DBG(Dbgregs))
+		prfd = fd;			/* intern2reg modifies fd */
+	intern2reg(ip->rd, &fd, ip->fmt, ip->ur);
+	if(DBG(Dbgregs))
+		intpr(&prfd, ip->rd, ip->fmt, ip->ufp);
+	return Advpc;
+}
+
+static int
+unaryemu(Instr *ip)
+{
+	int o;
+	FP1 *fp;
+	FPsave *ufp;
+
+	o = ip->o;
+	fp = &optab1[o];
+	if(DBG(Dbgbasic)){
+		iprint("%s.%s\tF%d,F%d\n", fp->name, ip->dfmt, ip->rm, ip->rd);
+		delay(75);
+	}
+	if(o == 6){			/* MOV */
+		int rm, rd;
+
+		ufp = ip->ufp;
+		rd = ip->rd;
+		rm = ip->rm;
+		if(ip->fmt == Fdouble){
+			rd &= ~1;
+			rm &= ~1;
+			FREG(ufp, rd+1) = FREG(ufp, rm+1);
+		}
+		FREG(ufp, rd) = FREG(ufp, rm);
+	}else{
+		Internal fdint, prfd;
+		Internal *fd;
+
+		switch(o){
+		case 5:			/* ABS */
+			fd = ip->fm;	/* use src Internal as dest */
+			fd->s = 0;
+			break;
+		case 7:			/* NEG */
+			fd = ip->fm;	/* use src Internal as dest */
+			fd->s ^= 1;
+			break;
+		default:
+			if(fp->f == nil)
+				unimp(ip->pc, ip->iw, "missing unary op");
+			fd = &fdint;
+			(*fp->f)(ip->fm, fd);
+			break;
+		}
+		if(DBG(Dbgregs))
+			prfd = *fd;		/* intern2reg modifies fd */
+		intern2reg(ip->rd, fd, ip->fmt, ip->ur);
+		if(DBG(Dbgregs))
+			intpr(&prfd, ip->rd, ip->fmt, ip->ufp);
+	}
+	return Advpc;
+}
+
+static int
+cvtemu(Instr *ip)
+{
+	FPcvt *fp;
+
+	fp = &optabcvt[ip->o];
+	if(fp->f == nil)
+		unimp(ip->pc, ip->iw, "missing conversion op");
+	if(DBG(Dbgbasic)){
+		iprint("%s.%s\tF%d,F%d\n", fp->name, ip->dfmt, ip->rm, ip->rd);
+		delay(75);
+	}
+	(*fp->f)(ip->fmt, ip->rm, ip->rd, ip->ur, ip->ufp);
+	return Advpc;
+}
+
+static void
+cop1decode(Instr *ip, ulong iw, ulong pc, Ureg *ur, FPsave *ufp,
+	Internal *imp, Internal *inp)
+{
+	ip->iw = iw;
+	ip->pc = pc;
+	ip->ur = ur;
+	ip->ufp = ufp;
+	ip->fmt = FMT(iw);
+	ip->rm = REGS(iw);		/* 1st operand */
+	ip->rn = REGT(iw);		/* 2nd operand (ignored by unary ops) */
+	ip->rd = REGD(iw);		/* destination */
+	ip->o = FUNC(iw);
+	ip->fm = imp;
+	ip->fn = inp;
+	if (DBG(Dbgbasic))
+		ip->dfmt = decodefmt(ip->fmt);
+}
+
+void
+fpstuck(uintptr pc, FPsave *fp)
+{
+	USED(pc);
+	if(!(DBG(Dbgbasic)))
+		return;
+	if (fp->fppc == pc) {
+		fp->fpcnt++;
+		if (fp->fpcnt > 4)
+			panic("fpuemu: cpu%d stuck at pid %ld %s pc %#p "
+				"instr %#8.8lux", m->machno, up->pid, up->text,
+				pc, *(ulong *)pc);
+	} else {
+		fp->fppc = pc;
+		fp->fpcnt = 0;
+	}
+}
+
+static void
+_dbgstuck(ulong pc, Ureg *ur, FPsave *ufp)
+{
+	fpstuck(pc, ufp);
+	if (DBG(Dbgdelay) && ur->cause & BD)
+		iprint("fpuemu: FP in a branch delay slot\n");
+}
+
+/* decode the opcode and call common emulation code */
+static int
+fpimips(ulong pc, ulong op, Ureg *ur, FPsave *ufp)
+{
+	int r, o;
+	Instr insn;
+	Instr *ip;
+	Internal im, in;
+
+	/* note: would update fault status here if we noted numeric exceptions */
+	dummyr0 = 0;
+	switch (OP(op)) {
+	case LWC1:
+	case LDC1:
+	case SWC1:
+	case SDC1:
+		dbgstuck(pc, ur, ufp);
+		return ldst(op, ur, ufp);
+	default:
+		unimp(pc, op, "non-FP instruction");
+		return Failed;
+	case COP1:
+		dbgstuck(pc, ur, ufp);
+		break;
+	}
+
+	ip = &insn;
+	cop1decode(ip, op, pc, ur, ufp, &im, &in);
+	if (ip->fmt == BRANCH) {		/* FP conditional branch? */
+		r = bremu(ip);
+		if(DBG(Dbgdelay)){
+			iprint("resuming after br, at %#lux", ur->pc);
+			if (r == Leavepcret)
+				iprint("...");	/* we'll be right back */
+			else
+				iprint("\n");
+		}
+		return r;
+	}
+	o = ip->o;
+	if (o == 0 && ip->rd == 0) {	/* *[TF]C1 load or store? */
+		r = cop1mov(ip);
+		if (r != Nomatch)
+			return r;
+		/* else wasn't a [tf]c1 move */
+	}
+	/* don't decode & print rm yet; it might be an integer */
+	if(o >= 32 && o < 40)		/* conversion? */
+		return cvtemu(ip);
+
+	/* decode the mandatory operand, rm */
+	reg2intern(ip->fm, ip->rm, ip->fmt, ip->ur);
+	if(DBG(Dbgregs))
+		intpr(&im, ip->rm, ip->fmt, ip->ufp);
+
+	/*
+	 * arithmetic
+	 * all operands must be of the same format
+	 */
+	if(o >= 4 && o < 32)		/* monadic */
+		return unaryemu(ip);
+	if(o < 4)			/* the few binary ops */
+		return binemu(ip);
+
+	if(o >= 48 && (ip->rd & MASK(2)) == 0)	/* comparison? */
+		return cmpemu(ip);
+
+	/* don't recognise the opcode */
+	if(DBG(Dbgbasic))
+		iprint("fp at %#lux: %#8.8lux BOGON\n", pc, op);
+	unimp(pc, op, "unknown opcode");
+	return Failed;
+}
+
+static FPsave *
+fpinit(Ureg *ur)
+{
+	int i, n;
+	Double d;
+	FPsave *ufp;
+	Internal tmp;
+
+	/*
+	 * because all the emulated fp state is in the proc structure,
+	 * it need not be saved/restored
+	 */
+	ufp = &up->fpsave;
+	switch(up->fpstate){
+	case FPactive:
+	case FPinactive:
+		error("fpu (in)active but fp is emulated");
+	case FPinit:
+		up->fpstate = FPemu;
+		ufp->fpcontrol = 0;
+		ufp->fpstatus = 0;
+		ufp->fpcnt = 0;
+		ufp->fppc = 0;
+		for(n = 0; n < Nfpregs-1; n += 2) {
+			if (fpconst[n].h == 0)	/* uninitialised consts */
+				i = FZERO;	/* treated as 0.0 */
+			else
+				i = n;
+			tmp = fpconst[i];
+			internsane(&tmp, ur);
+			fpii2d(&d, &tmp);
+			dbl2dreg(n, &d, ufp);
+		}
+		break;
+	}
+	return ufp;
+}
+
+/*
+ * called from trap.c's CCPU case, only to deal with user-mode
+ * instruction faults.  
+ *
+ * libc/mips/lock.c reads FCR0 to determine what kind of system
+ * this is (and thus if it can use LL/SC or must use some
+ * system-dependent method).  So we simulate the move from FCR0.
+ * All modern mips have LL/SC, so just claim to be an r4k.
+ */
+int
+fpuemu(Ureg *ureg)
+{
+	int s;
+	uintptr pc;
+	ulong iw, r;
+
+	if(waserror()){
+		postnote(up, 1, up->errstr, NDebug);
+		return -1;
+	}
+
+	if(up->fpstate & FPillegal)
+		error("floating point in note handler");
+	if(up->fpsave.fpdelayexec)
+		panic("fpuemu: entered with outstanding watch trap");
+
+	pc = ureg->pc;
+	validaddr(pc, 4, 0);
+	/* only the first instruction can be in a branch delay slot */
+	if(ureg->cause & BD) {
+		pc += 4;
+		validaddr(pc, 4, 0);		/* check branch delay slot */
+	}
+	iw = *(ulong*)pc;
+	do {
+		/* recognise & optimise a common case */
+		if (iw == 0x44410000){		/* MOVW FCR0,R1 (CFC1) */
+			ureg->r1 = 0x500;	/* claim an r4k */
+			r = Advpc;
+			if (DBG(Dbgbasic))
+				iprint("faked MOVW FCR0,R1\n");
+		}else{
+			s = spllo();
+			if(waserror()){
+				splx(s);
+				nexterror();
+			}
+			r = fpimips(pc, iw, ureg, fpinit(ureg));
+			splx(s);
+			poperror();
+			if (r == Failed || r == Leavepcret)
+				break;
+		}
+		if (r == Advpc)	/* simulation succeeded, advance the pc? */
+			if(ureg->cause & BD)
+				followbr(ureg);
+			else
+				ureg->pc += 4;
+		ureg->cause &= ~BD;
+
+		pc = ureg->pc;
+		iw = validiw(pc);
+		while (iw == NOP || iw == MIPSNOP) {	/* skip NOPs */
+			pc += 4;
+			ureg->pc = pc;
+			iw = validiw(pc);
+		}
+		/* is next ins'n also FP? */
+	} while (isfpop(iw));
+	if (r == Failed){
+		iprint("fpuemu: fp emulation failed for %#lux"
+			" at pc %#p in %lud %s\n",
+			iw, ureg->pc, up->pid, up->text);
+		unimp(ureg->pc, iw, "no fp instruction");
+		/* no return */
+	}
+	ureg->cause &= ~BD;
+	poperror();
+	return 0;
+}
+
+int
+isbranch(ulong *pc)
+{
+	ulong iw;
+
+	iw = *(ulong*)pc;
+	/*
+	 * Integer unit jumps first
+	 */
+	switch(iw>>26){
+	case 0:			/* SPECIAL: JR or JALR */
+		switch(iw&0x3F){
+		case 0x09:	/* JALR */
+		case 0x08:	/* JR */
+			return 1;
+		default:
+			return 0;
+		}
+	case 1:			/* BCOND */
+		switch((iw>>16) & 0x1F){
+		case 0x10:	/* BLTZAL */
+		case 0x00:	/* BLTZ */
+		case 0x11:	/* BGEZAL */
+		case 0x01:	/* BGEZ */
+			return 1;
+		default:
+			return 0;
+		}
+	case 3:			/* JAL */
+	case 2:			/* JMP */
+	case 4:			/* BEQ */
+	case 5:			/* BNE */
+	case 6:			/* BLEZ */
+	case 7:			/* BGTZ */
+		return 1;
+	}
+	/*
+	 * Floating point unit jumps
+	 */
+	if((iw>>26) == COP1)
+		switch((iw>>16) & 0x3C1){
+		case 0x101:	/* BCT */
+		case 0x181:	/* BCT */
+		case 0x100:	/* BCF */
+		case 0x180:	/* BCF */
+			return 1;
+		}
+	return 0;
+}
+
+/*
+ * if current instruction is a (taken) branch, return new pc and,
+ * for jump-and-links, set r31.
+ */
+ulong
+branch(Ureg *ur, ulong fcr31)
+{
+	ulong iw, npc, rs, rt, rd, offset, targ, next;
+
+	iw = ur->pc;
+	iw = *(ulong*)iw;
+	rs = (iw>>21) & 0x1F;
+	if(rs)
+		rs = REG(ur, rs);
+	rt = (iw>>16) & 0x1F;
+	if(rt)
+		rt = REG(ur, rt);
+	offset = iw & ((1<<16)-1);
+	if(offset & (1<<15))	/* sign extend */
+		offset |= ~((1<<16)-1);
+	offset <<= 2;
+	targ = ur->pc + 4 + offset;	/* branch target */
+	/* ins'n after delay slot (assumes delay slot has already been exec'd) */
+	next = ur->pc + 8;
+	/*
+	 * Integer unit jumps first
+	 */
+	switch(iw>>26){
+	case 0:			/* SPECIAL: JR or JALR */
+		switch(iw&0x3F){
+		case 0x09:	/* JALR */
+			rd = (iw>>11) & 0x1F;
+			if(rd)
+				REG(ur, rd) = next;
+			/* fall through */
+		case 0x08:	/* JR */
+			return rs;
+		default:
+			return 0;
+		}
+	case 1:			/* BCOND */
+		switch((iw>>16) & 0x1F){
+		case 0x10:	/* BLTZAL */
+			ur->r31 = next;
+			/* fall through */
+		case 0x00:	/* BLTZ */
+			if((long)rs < 0)
+				return targ;
+			return next;
+		case 0x11:	/* BGEZAL */
+			ur->r31 = next;
+			/* fall through */
+		case 0x01:	/* BGEZ */
+			if((long)rs >= 0)
+				return targ;
+			return next;
+		default:
+			return 0;
+		}
+	case 3:			/* JAL */
+		ur->r31 = next;
+		/* fall through */
+	case 2:			/* JMP */
+		npc = iw & ((1<<26)-1);
+		npc <<= 2;
+		return npc | (ur->pc&0xF0000000);
+	case 4:			/* BEQ */
+		if(rs == rt)
+			return targ;
+		return next;
+	case 5:			/* BNE */
+		if(rs != rt)
+			return targ;
+		return next;
+	case 6:			/* BLEZ */
+		if((long)rs <= 0)
+			return targ;
+		return next;
+	case 7:			/* BGTZ */
+		if((long)rs > 0)
+			return targ;
+		return next;
+	}
+	/*
+	 * Floating point unit jumps
+	 */
+	if((iw>>26) == COP1)
+		switch((iw>>16) & 0x3C1){
+		case 0x101:	/* BCT */
+		case 0x181:	/* BCT */
+			if(fcr31 & FPCOND)
+				return targ;
+			return next;
+		case 0x100:	/* BCF */
+		case 0x180:	/* BCF */
+			if(!(fcr31 & FPCOND))
+				return targ;
+			return next;
+		}
+	/* shouldn't get here */
+	return 0;
+}

+ 8 - 0
sys/src/9/rb/init9.s

@@ -0,0 +1,8 @@
+TEXT	_main(SB), $8
+	MOVW	$setR30(SB), R30
+	MOVW	$boot(SB), R1
+	ADDU	$12, R29, R2	/* get a pointer to 0(FP) */
+	MOVW	R1, 4(R29)
+	MOVW	R2, 8(R29)
+	JAL	startboot(SB)
+

+ 44 - 0
sys/src/9/rb/initcode

@@ -0,0 +1,44 @@
+#include "/sys/src/libc/9syscall/sys.h"
+
+/*
+ *  we pass in the argument of the exec parameters as 0(FP)
+ */
+
+TEXT	main(SB),$8
+
+	MOVW	$setR30(SB), R30
+
+	MOVW	$boot(SB), R1
+	ADD	$12, R29, R2	/* get a pointer to 0(FP) */
+	MOVW	R1, 4(R29)
+	MOVW	R2, 8(R29)
+	JAL	exec(SB)
+
+	MOVW	$(1<<4), R1
+	MOVW	R1, 4(R29)
+	MOVW	$RFORK, R1
+	SYSCALL
+	MOVW	$RFORK, R1
+	SYSCALL
+	MOVW	$RFORK, R1
+	SYSCALL
+again:
+	ADDU	$1, R2
+	MOVW	$0, R1	/* print r1 */
+	SYSCALL
+	MOVW	$5000000, R3
+foo:
+	SUBU	$1, R3
+	BNE	R3, foo
+	JMP	again
+
+TEXT	exec(SB), $0
+	MOVW	$EXEC, R1
+	SYSCALL
+	RET
+
+DATA	boot+0(SB)/5,$"/boot"
+DATA	boot+5(SB)/5,$"/boot"
+DATA	bootv+0(SB)/4,$boot+6(SB)
+GLOBL	boot+0(SB),$11
+GLOBL	bootv+0(SB),$8

+ 60 - 0
sys/src/9/rb/initreboot.s

@@ -0,0 +1,60 @@
+/*
+ * mips 24k machine assist for routerboard rb450g (minimal for reboot)
+ */
+#include "mem.h"
+#include "mips.s"
+
+	NOSCHED
+
+TEXT	_main(SB), $0
+	MOVW	$setR30(SB), R30
+	JMP	main(SB)
+
+/* target for JALRHB in BARRIERS */
+TEXT ret(SB), $-4
+	JMP	(R22)
+	NOP
+
+TEXT	setsp(SB), $-4
+	MOVW	R1, SP
+	RETURN
+
+TEXT	coherence(SB), $-4
+	BARRIERS(7, R7, cohhb)
+	SYNC
+	EHB
+	RETURN
+
+/*
+ *  cache manipulation
+ */
+
+/* the i and d caches may be different sizes, so clean them separately */
+TEXT	cleancache(SB), $-4
+	DI(10)				/* intrs off, old status -> R10 */
+	SYNC
+	EHB
+
+	MOVW	R0, R1			/* index, not address */
+	MOVW	$ICACHESIZE, R9
+iccache:
+	CACHE	PI+IWBI, (R1)		/* flush & invalidate I by index */
+	SUBU	$CACHELINESZ, R9
+	ADDU	$CACHELINESZ, R1
+	BGTZ	R9, iccache
+	NOP
+
+	MOVW	R0, R1			/* index, not address */
+	MOVW	$DCACHESIZE, R9
+dccache:
+	CACHE	PD+IWBI, (R1)		/* flush & invalidate D by index */
+	SUBU	$CACHELINESZ, R9
+	ADDU	$CACHELINESZ, R1
+	BGTZ	R9, dccache
+	NOP
+
+	SYNC
+	MOVW	R10, M(STATUS)
+	JRHB(31)			/* return and clear all hazards */
+
+	SCHED

+ 333 - 0
sys/src/9/rb/io.h

@@ -0,0 +1,333 @@
+enum {
+	Mhz		= 1000*1000,
+};
+
+/*
+ *  duarts, frequency and registers
+ */
+#define DUARTFREQ	3672000
+
+/*
+ *  interrupt levels on CPU boards.
+ */
+enum
+{
+	ILmin		= 2,
+	ILpci		= 2,
+	ILehci		= 3,
+	ILenet1		= 4,		/* arge1 @ 0x19:: w switch */
+	ILenet0		= 5,		/* arge0 @ 0x1a:: */
+	ILduart0	= 6,		/* actually APB, uart is subintr 3 */
+	ILclock		= 7,
+	ILmax		= 7,
+
+	ILshift		= 8,
+};
+
+#define	Rstblockbase	(ulong *)KSEG1ADDR(0x18060000)
+
+#define Rstwdogctl	(ulong *)KSEG1ADDR(0x18060008)
+#define		Wdoglast	(1 << 31)
+#define		Wdogmask	3
+#define		Wdognoaction	0
+#define		Wdoggpintr	1
+#define		Wdognmi		2
+#define		Wdogreset	3
+#define Rstwdogtimer	(ulong *)KSEG1ADDR(0x1806000c)
+
+/*
+ * APB interrupt status and mask register and interrupt bits
+ */
+#define Apbintrsts	(ulong *)KSEG1ADDR(0x18060010)
+#define Apbintrmask	(ulong *)KSEG1ADDR(0x18060014)
+#define		Apbintrtimer		0
+#define		Apbintrerror		1
+#define		Apbintrgpio		2
+#define		Apbintruart		3
+#define		Apbintrwatchdog		4
+#define		Apbintrperf		5
+#define		Apbintrohci		6
+#define		Apbintrdma		7
+
+#define Pciintrsts	(ulong *)KSEG1ADDR(0x18060018)
+#define Pciintrmask	(ulong *)KSEG1ADDR(0x1806001C)
+#define		PCI_INTR_CORE		(1 << 4)
+
+#define Reset	(ulong *)KSEG1ADDR(0x18060024)
+#define		Rstfullchip	(1 << 24) /* same as pulling the reset pin */
+#define		Rstcpucold	(1 << 20) /* cold reset */
+#define		Rstge1mac	(1 << 13)
+#define		Rstge1phy	(1 << 12)
+#define		Rstge0mac	(1 <<  9)
+#define		Rstge0phy	(1 <<  8)
+#define		Rstusbohcidll	(1 <<  6)
+#define		Rstusbhost	(1 <<  5)
+#define		Rstusbphy	(1 <<  4)
+#define		Rstpcibus	(1 <<  1)
+#define		Rstpcicore	(1 <<  0)
+
+/*
+ * mostly PCI from here on
+ */
+
+typedef struct Pcisiz Pcisiz;
+typedef struct Pcidev Pcidev;
+typedef struct Vctl Vctl;
+
+struct Vctl {
+	Vctl*	next;			/* handlers on this vector */
+
+	char	name[KNAMELEN];		/* of driver */
+	int	isintr;			/* interrupt or fault/trap */
+	int	irq;
+	int	tbdf;
+	int	(*isr)(int);		/* get isr bit for this irq */
+	int	(*eoi)(int);		/* eoi */
+
+	void	(*f)(Ureg*, void*);	/* handler to call */
+	void*	a;			/* argument to call it with */
+};
+
+enum {
+	BusCBUS		= 0,		/* Corollary CBUS */
+	BusCBUSII,			/* Corollary CBUS II */
+	BusEISA,			/* Extended ISA */
+	BusFUTURE,			/* IEEE Futurebus */
+	BusINTERN,			/* Internal bus */
+	BusISA,				/* Industry Standard Architecture */
+	BusMBI,				/* Multibus I */
+	BusMBII,			/* Multibus II */
+	BusMCA,				/* Micro Channel Architecture */
+	BusMPI,				/* MPI */
+	BusMPSA,			/* MPSA */
+	BusNUBUS,			/* Apple Macintosh NuBus */
+	BusPCI,				/* Peripheral Component Interconnect */
+	BusPCMCIA,			/* PC Memory Card International Association */
+	BusTC,				/* DEC TurboChannel */
+	BusVL,				/* VESA Local bus */
+	BusVME,				/* VMEbus */
+	BusXPRESS,			/* Express System Bus */
+};
+
+#define MKBUS(t,b,d,f)	(((t)<<24)|(((b)&0xFF)<<16)|(((d)&0x1F)<<11)|(((f)&0x07)<<8))
+#define BUSFNO(tbdf)	(((tbdf)>>8)&0x07)
+#define BUSDNO(tbdf)	(((tbdf)>>11)&0x1F)
+#define BUSBNO(tbdf)	(((tbdf)>>16)&0xFF)
+#define BUSTYPE(tbdf)	((tbdf)>>24)
+#define BUSBDF(tbdf)	((tbdf)&0x00FFFF00)
+#define BUSUNKNOWN	(-1)
+
+enum {
+	MaxEISA		= 16,
+	CfgEISA		= 0xC80,
+};
+
+/*
+ * PCI support code.
+ */
+enum {					/* type 0 & type 1 pre-defined header */
+	PciVID		= 0x00,		/* vendor ID */
+	PciDID		= 0x02,		/* device ID */
+	PciPCR		= 0x04,		/* command */
+	PciPSR		= 0x06,		/* status */
+	PciRID		= 0x08,		/* revision ID */
+	PciCCRp		= 0x09,		/* programming interface class code */
+	PciCCRu		= 0x0A,		/* sub-class code */
+	PciCCRb		= 0x0B,		/* base class code */
+	PciCLS		= 0x0C,		/* cache line size */
+	PciLTR		= 0x0D,		/* latency timer */
+	PciHDT		= 0x0E,		/* header type */
+	PciBST		= 0x0F,		/* BIST */
+
+	PciBAR0		= 0x10,		/* base address */
+	PciBAR1		= 0x14,
+
+	PciINTL		= 0x3C,		/* interrupt line */
+	PciINTP		= 0x3D,		/* interrupt pin */
+};
+
+/* ccrb (base class code) values; controller types */
+enum {
+	Pcibcpci1	= 0,		/* pci 1.0; no class codes defined */
+	Pcibcstore	= 1,		/* mass storage */
+	Pcibcnet	= 2,		/* network */
+	Pcibcdisp	= 3,		/* display */
+	Pcibcmmedia	= 4,		/* multimedia */
+	Pcibcmem	= 5,		/* memory */
+	Pcibcbridge	= 6,		/* bridge */
+	Pcibccomm	= 7,		/* simple comms (e.g., serial) */
+	Pcibcbasesys	= 8,		/* base system */
+	Pcibcinput	= 9,		/* input */
+	Pcibcdock	= 0xa,		/* docking stations */
+	Pcibcproc	= 0xb,		/* processors */
+	Pcibcserial	= 0xc,		/* serial bus (e.g., USB) */
+	Pcibcwireless	= 0xd,		/* wireless */
+	Pcibcintell	= 0xe,		/* intelligent i/o */
+	Pcibcsatcom	= 0xf,		/* satellite comms */
+	Pcibccrypto	= 0x10,		/* encryption/decryption */
+	Pcibcdacq	= 0x11,		/* data acquisition & signal proc. */
+};
+
+/* ccru (sub-class code) values; common cases only */
+enum {
+	/* mass storage */
+	Pciscscsi	= 0,		/* SCSI */
+	Pciscide	= 1,		/* IDE (ATA) */
+	Pciscsata	= 6,		/* SATA */
+
+	/* network */
+	Pciscether	= 0,		/* Ethernet */
+
+	/* display */
+	Pciscvga	= 0,		/* VGA */
+	Pciscxga	= 1,		/* XGA */
+	Pcisc3d		= 2,		/* 3D */
+
+	/* bridges */
+	Pcischostpci	= 0,		/* host/pci */
+	Pciscpcicpci	= 1,		/* pci/pci */
+
+	/* simple comms */
+	Pciscserial	= 0,		/* 16450, etc. */
+	Pciscmultiser	= 1,		/* multiport serial */
+
+	/* serial bus */
+	Pciscusb	= 3,		/* USB */
+};
+
+enum {					/* type 0 pre-defined header */
+	PciCIS		= 0x28,		/* cardbus CIS pointer */
+	PciSVID		= 0x2C,		/* subsystem vendor ID */
+	PciSID		= 0x2E,		/* cardbus CIS pointer */
+	PciEBAR0	= 0x30,		/* expansion ROM base address */
+	PciMGNT		= 0x3E,		/* burst period length */
+	PciMLT		= 0x3F,		/* maximum latency between bursts */
+};
+
+enum {					/* type 1 pre-defined header */
+	PciPBN		= 0x18,		/* primary bus number */
+	PciSBN		= 0x19,		/* secondary bus number */
+	PciUBN		= 0x1A,		/* subordinate bus number */
+	PciSLTR		= 0x1B,		/* secondary latency timer */
+	PciIBR		= 0x1C,		/* I/O base */
+	PciILR		= 0x1D,		/* I/O limit */
+	PciSPSR		= 0x1E,		/* secondary status */
+	PciMBR		= 0x20,		/* memory base */
+	PciMLR		= 0x22,		/* memory limit */
+	PciPMBR		= 0x24,		/* prefetchable memory base */
+	PciPMLR		= 0x26,		/* prefetchable memory limit */
+	PciPUBR		= 0x28,		/* prefetchable base upper 32 bits */
+	PciPULR		= 0x2C,		/* prefetchable limit upper 32 bits */
+	PciIUBR		= 0x30,		/* I/O base upper 16 bits */
+	PciIULR		= 0x32,		/* I/O limit upper 16 bits */
+	PciEBAR1	= 0x28,		/* expansion ROM base address */
+	PciBCR		= 0x3E,		/* bridge control register */
+};
+
+enum {					/* type 2 pre-defined header */
+	PciCBExCA	= 0x10,
+	PciCBSPSR	= 0x16,
+	PciCBPBN	= 0x18,		/* primary bus number */
+	PciCBSBN	= 0x19,		/* secondary bus number */
+	PciCBUBN	= 0x1A,		/* subordinate bus number */
+	PciCBSLTR	= 0x1B,		/* secondary latency timer */
+	PciCBMBR0	= 0x1C,
+	PciCBMLR0	= 0x20,
+	PciCBMBR1	= 0x24,
+	PciCBMLR1	= 0x28,
+	PciCBIBR0	= 0x2C,		/* I/O base */
+	PciCBILR0	= 0x30,		/* I/O limit */
+	PciCBIBR1	= 0x34,		/* I/O base */
+	PciCBILR1	= 0x38,		/* I/O limit */
+	PciCBSVID	= 0x40,		/* subsystem vendor ID */
+	PciCBSID	= 0x42,		/* subsystem ID */
+	PciCBLMBAR	= 0x44,		/* legacy mode base address */
+};
+
+struct Pcisiz
+{
+	Pcidev*	dev;
+	int	siz;
+	int	bar;
+};
+
+struct Pcidev
+{
+	int	tbdf;			/* type+bus+device+function */
+	ushort	vid;			/* vendor ID */
+	ushort	did;			/* device ID */
+
+	ushort	pcr;
+
+	uchar	rid;
+	uchar	ccrp;
+	uchar	ccru;
+	uchar	ccrb;
+	uchar	cls;
+	uchar	ltr;
+
+	struct {
+		ulong	bar;		/* base address */
+		int	size;
+	} mem[6];
+
+	struct {
+		ulong	bar;	
+		int	size;
+	} rom;
+	uchar	intl;			/* interrupt line */
+
+	Pcidev*	list;
+	Pcidev*	link;			/* next device on this bno */
+
+	Pcidev*	bridge;			/* down a bus */
+	struct {
+		ulong	bar;
+		int	size;
+	} ioa, mema;
+
+	int	pmrb;			/* power management register block */
+};
+
+enum {
+	/* vendor ids */
+	Vatiamd	= 0x1002,
+	Vintel	= 0x8086,
+	Vjmicron= 0x197b,
+	Vmarvell= 0x1b4b,
+	Vmyricom= 0x14c1,
+};
+
+#define PCIWINDOW	0
+#define PCIWADDR(va)	(PADDR(va)+PCIWINDOW)
+#define ISAWINDOW	0
+#define ISAWADDR(va)	(PADDR(va)+ISAWINDOW)
+
+/* SMBus transactions */
+enum
+{
+	SMBquick,		/* sends address only */
+
+	/* write */
+	SMBsend,		/* sends address and cmd */
+	SMBbytewrite,		/* sends address and cmd and 1 byte */
+	SMBwordwrite,		/* sends address and cmd and 2 bytes */
+
+	/* read */
+	SMBrecv,		/* sends address, recvs 1 byte */
+	SMBbyteread,		/* sends address and cmd, recv's byte */
+	SMBwordread,		/* sends address and cmd, recv's 2 bytes */
+};
+
+typedef struct SMBus SMBus;
+struct SMBus {
+	QLock;		/* mutex */
+	Rendez	r;	/* rendezvous point for completion interrupts */
+	void	*arg;	/* implementation dependent */
+	ulong	base;	/* port or memory base of smbus */
+	int	busy;
+	void	(*transact)(SMBus*, int, int, int, uchar*);
+};
+
+#pragma varargck	type	"T"	int
+#pragma varargck	type	"T"	uint

+ 1073 - 0
sys/src/9/rb/l.s

@@ -0,0 +1,1073 @@
+/*
+ * mips 24k machine assist for routerboard rb450g
+ */
+#include "mem.h"
+#include "mips.s"
+
+#define SANITY 0x12345678
+
+	NOSCHED
+
+/*
+ * Boot only processor
+ */
+TEXT	start(SB), $-4
+	MOVW	$setR30(SB), R30
+
+PUTC('9', R1, R2)
+	DI(0)
+
+	MOVW	sanity(SB), R1
+	CONST(SANITY, R2)
+	SUBU	R1, R2, R2
+	BNE	R2, insane
+	NOP
+
+	MOVW	R0, M(COMPARE)
+	EHB
+
+	/* don't enable any interrupts nor FP, but leave BEV on. */
+	MOVW	$BEV,R1
+	MOVW	R1, M(STATUS)
+	UBARRIERS(7, R7, stshb)		/* returns to kseg1 space */
+	MOVW	R0, M(CAUSE)
+	EHB
+
+	/* silence the atheros watchdog */
+	MOVW	$(KSEG1|0x18060008), R1
+	MOVW	R0, (R1)			/* set no action */
+	SYNC
+
+	MOVW	$PE, R1
+	MOVW	R1, M(CACHEECC)		/* aka ErrCtl */
+	EHB
+	JAL	cleancache(SB)
+	NOP
+
+	MOVW	$TLBROFF, R1
+	MOVW	R1, M(WIRED)
+
+	MOVW	R0, M(CONTEXT)
+	EHB
+
+	/* set KSEG0 cachability before trying LL/SC in lock code */
+	MOVW	M(CONFIG), R1
+	AND	$~CFG_K0, R1
+	/* make kseg0 cachable, enable write-through merging */
+	OR	$((PTECACHABILITY>>3)|CFG_MM), R1
+	MOVW	R1, M(CONFIG)
+	BARRIERS(7, R7, cfghb)			/* back to kseg0 space */
+
+	MOVW	$setR30(SB), R30		/* again */
+
+	/* initialize Mach, including stack */
+	MOVW	$MACHADDR, R(MACH)
+	ADDU	$(MACHSIZE-BY2V), R(MACH), SP
+	MOVW	R(MACH), R1
+clrmach:
+	MOVW	R0, (R1)
+	ADDU	$BY2WD, R1
+	BNE	R1, SP, clrmach
+	NOP
+	MOVW	R0, 0(R(MACH))			/* m->machno = 0 */
+	MOVW	R0, R(USER)			/* up = nil */
+
+	/* zero bss, byte-by-byte */
+	MOVW	$edata(SB), R1
+	MOVW	$end(SB), R2
+clrbss:
+	MOVB	R0, (R1)
+	ADDU	$1, R1
+	BNE	R1, R2, clrbss
+	NOP
+
+	MOVW	$0x16, R16
+	MOVW	$0x17, R17
+	MOVW	$0x18, R18
+	MOVW	$0x19, R19
+	MOVW	$0x20, R20
+	MOVW	$0x21, R21
+	MOVW	$0x22, R22
+	MOVW	$0x23, R23
+
+	MOVW	R0, HI
+	MOVW	R0, LO
+
+PUTC('\r', R1, R2)
+PUTC('\n', R1, R2)
+	JAL	main(SB)
+	NOP
+	CONST(ROM, R1)
+	JMP	(R1)			/* back to the rom */
+
+#define PUT(c) PUTC(c, R1, R2)
+#define DELAY(lab) \
+	CONST(34000000, R3); \
+lab:	SUBU	$1, R3; \
+	BNE	R3, lab; \
+	NOP
+
+insane:
+	/*
+	 * data segment is misaligned; kernel needs vl -R4096 or -R16384,
+	 * as appropriate, for reboot.
+	 */
+	PUT('?'); PUT('d'); PUT('a'); PUT('t'); PUT('a'); PUT(' '); DELAY(dl1)
+	PUT('s'); PUT('e'); PUT('g'); PUT('m'); PUT('e'); PUT('n'); DELAY(dl2)
+	PUT('t'); PUT(' '); PUT('m'); PUT('i'); PUT('s'); PUT('a'); DELAY(dl3)
+	PUT('l'); PUT('i'); PUT('g'); PUT('n'); PUT('e'); PUT('d'); DELAY(dl4)
+	PUT('\r'); PUT('\n'); DELAY(dl5)
+	CONST(ROM, R1)
+	JMP	(R1)			/* back to the rom */
+	NOP
+
+/* target for JALRHB in BARRIERS */
+TEXT ret(SB), $-4
+	JMP	(R22)
+	NOP
+
+/* print R1 in hex; clobbers R3—8 */
+TEXT printhex(SB), $-4
+	MOVW	$32, R5
+	MOVW	$9, R7
+prtop:
+	SUB	$4, R5
+	MOVW	R1, R6
+	SRL	R5, R6
+	AND	$0xf, R6
+	SGTU	R6, R7, R8
+	BEQ	R8, prdec		/* branch if R6 <= 9 */
+	NOP
+	ADD	$('a'-10), R6
+	JMP	prchar
+	NOP
+prdec:
+	ADD	$'0', R6
+prchar:
+	PUTC(R6, R3, R4)
+	BNE	R5, prtop
+	NOP
+	RETURN
+
+/*
+ * Take first processor into user mode
+ * 	- argument is stack pointer to user
+ */
+TEXT	touser(SB), $-4
+	MOVW	R1, SP
+	MOVW	$(UTZERO+32), R2	/* header appears in text */
+	MOVW	R2, M(EPC)
+	EHB
+	MOVW	M(STATUS), R4
+	AND	$(~KMODEMASK), R4
+	OR	$(KUSER|IE|EXL), R4	/* switch to user mode, intrs on, exc */
+	MOVW	R4, M(STATUS)		/* " */
+	ERET				/* clears EXL */
+
+/*
+ * manipulate interrupts
+ */
+
+/* enable an interrupt; bit is in R1 */
+TEXT	intron(SB), $0
+	MOVW	M(STATUS), R2
+	OR	R1, R2
+	MOVW	R2, M(STATUS)
+	EHB
+	RETURN
+
+/* disable an interrupt; bit is in R1 */
+TEXT	introff(SB), $0
+	MOVW	M(STATUS), R2
+	XOR	$-1, R1
+	AND	R1, R2
+	MOVW	R2, M(STATUS)
+	EHB
+	RETURN
+
+/* on our 24k, wait instructions are not interruptible, alas. */
+TEXT	idle(SB), $-4
+	EI(1)				/* old M(STATUS) into R1 */
+	EHB
+	/* fall through */
+
+TEXT	wait(SB), $-4
+	WAIT
+	NOP
+
+	MOVW	R1, M(STATUS)		/* interrupts restored */
+	EHB
+	RETURN
+
+TEXT	splhi(SB), $0
+	EHB
+	MOVW	R31, 12(R(MACH))	/* save PC in m->splpc */
+	DI(1)				/* old M(STATUS) into R1 */
+	EHB
+	RETURN
+
+TEXT	splx(SB), $0
+	EHB
+	MOVW	R31, 12(R(MACH))	/* save PC in m->splpc */
+	MOVW	M(STATUS), R2
+	AND	$IE, R1
+	AND	$~IE, R2
+	OR	R2, R1
+	MOVW	R1, M(STATUS)
+	EHB
+	RETURN
+
+TEXT	spllo(SB), $0
+	EHB
+	EI(1)				/* old M(STATUS) into R1 */
+	EHB
+	RETURN
+
+TEXT	spldone(SB), $0
+	RETURN
+
+TEXT	islo(SB), $0
+	MOVW	M(STATUS), R1
+	AND	$IE, R1
+	RETURN
+
+TEXT	coherence(SB), $-4
+	BARRIERS(7, R7, cohhb)
+	SYNC
+	EHB
+	RETURN
+
+/*
+ * process switching
+ */
+
+TEXT	setlabel(SB), $-4
+	MOVW	R29, 0(R1)
+	MOVW	R31, 4(R1)
+	MOVW	R0, R1
+	RETURN
+
+TEXT	gotolabel(SB), $-4
+	MOVW	0(R1), R29
+	MOVW	4(R1), R31
+	MOVW	$1, R1
+	RETURN
+
+/*
+ * the tlb routines need to be called at splhi.
+ */
+
+TEXT	puttlb(SB), $0			/* puttlb(virt, phys0, phys1) */
+	EHB
+	MOVW	R1, M(TLBVIRT)
+	EHB
+	MOVW	4(FP), R2		/* phys0 */
+	MOVW	8(FP), R3		/* phys1 */
+	MOVW	R2, M(TLBPHYS0)
+	EHB
+	MOVW	$PGSZ, R1
+	MOVW	R3, M(TLBPHYS1)
+	EHB
+	MOVW	R1, M(PAGEMASK)
+	OR	R2, R3, R4		/* MTC0 delay slot */
+	AND	$PTEVALID, R4		/* MTC0 delay slot */
+	EHB
+	TLBP				/* tlb probe */
+	EHB
+	MOVW	M(INDEX), R1
+	BGEZ	R1, index		/* if tlb entry found, use it */
+	NOP
+	BEQ	R4, dont		/* not valid? cf. kunmap */
+	NOP
+	MOVW	M(RANDOM), R1		/* write random tlb entry */
+	MOVW	R1, M(INDEX)
+index:
+	EHB
+	TLBWI				/* write indexed tlb entry */
+	JRHB(31)			/* return and clear all hazards */
+dont:
+	RETURN
+
+TEXT	getwired(SB),$0
+	MOVW	M(WIRED), R1
+	RETURN
+
+TEXT	setwired(SB),$0
+	MOVW	R1, M(WIRED)
+	EHB
+	RETURN
+
+TEXT	getrandom(SB),$0
+	MOVW	M(RANDOM), R1
+	RETURN
+
+TEXT	getpagemask(SB),$0
+	MOVW	M(PAGEMASK), R1
+	RETURN
+
+TEXT	setpagemask(SB),$0
+	EHB
+	MOVW	R1, M(PAGEMASK)
+	EHB
+	MOVW	R0, R1			/* prevent accidents */
+	RETURN
+
+TEXT	puttlbx(SB), $0	/* puttlbx(index, virt, phys0, phys1, pagemask) */
+	MOVW	4(FP), R2
+	MOVW	8(FP), R3
+	MOVW	12(FP), R4
+	MOVW	16(FP), R5
+	EHB
+	MOVW	R2, M(TLBVIRT)
+	EHB
+	MOVW	R3, M(TLBPHYS0)
+	MOVW	R4, M(TLBPHYS1)
+	MOVW	R5, M(PAGEMASK)
+	EHB
+	MOVW	R1, M(INDEX)
+	EHB
+	TLBWI				/* write indexed tlb entry */
+	JRHB(31)			/* return and clear all hazards */
+
+TEXT	tlbvirt(SB), $0
+	EHB
+	MOVW	M(TLBVIRT), R1
+	EHB
+	RETURN
+
+TEXT	gettlbx(SB), $0			/* gettlbx(index, &entry) */
+	MOVW	4(FP), R5
+	MOVW	M(TLBVIRT), R10		/* save our asid */
+	EHB
+	MOVW	R1, M(INDEX)
+	EHB
+	TLBR				/* read indexed tlb entry */
+	EHB
+	MOVW	M(TLBVIRT), R2
+	MOVW	M(TLBPHYS0), R3
+	MOVW	M(TLBPHYS1), R4
+	MOVW	R2, 0(R5)
+	MOVW	R3, 4(R5)
+	MIPS24KNOP
+	MOVW	R4, 8(R5)
+	EHB
+	MOVW	R10, M(TLBVIRT)		/* restore our asid */
+	EHB
+	RETURN
+
+TEXT	gettlbp(SB), $0			/* gettlbp(tlbvirt, &entry) */
+	MOVW	4(FP), R5
+	MOVW	M(TLBVIRT), R10		/* save our asid */
+	EHB
+	MOVW	R1, M(TLBVIRT)
+	EHB
+	TLBP				/* probe tlb */
+	EHB
+	MOVW	M(INDEX), R1
+	BLTZ	R1, gettlbp1		/* if no tlb entry found, return */
+	NOP
+	EHB
+	TLBR				/* read indexed tlb entry */
+	EHB
+	MOVW	M(TLBVIRT), R2
+	MOVW	M(TLBPHYS0), R3
+	MOVW	M(TLBPHYS1), R4
+	MOVW	M(PAGEMASK), R6
+	MOVW	R2, 0(R5)
+	MOVW	R3, 4(R5)
+	MIPS24KNOP
+	MOVW	R4, 8(R5)
+	MOVW	R6, 12(R5)
+gettlbp1:
+	EHB
+	MOVW	R10, M(TLBVIRT)		/* restore our asid */
+	EHB
+	RETURN
+
+TEXT	gettlbvirt(SB), $0		/* gettlbvirt(index) */
+	MOVW	M(TLBVIRT), R10		/* save our asid */
+	EHB
+	MOVW	R1, M(INDEX)
+	EHB
+	TLBR				/* read indexed tlb entry */
+	EHB
+	MOVW	M(TLBVIRT), R1
+	EHB
+	MOVW	R10, M(TLBVIRT)		/* restore our asid */
+	EHB
+	RETURN
+
+/*
+ * exceptions.
+ * mips promises that there will be no current hazards upon entry
+ * to exception handlers.
+ */
+
+TEXT	vector0(SB), $-4
+	MOVW	$utlbmiss(SB), R26
+	JMP	(R26)
+	NOP
+
+/*
+ * compute stlb hash index.
+ * must match index calculation in mmu.c/putstlb()
+ *
+ * M(TLBVIRT) [page & asid] in arg, result in arg.
+ * stir in swizzled asid; we get best results with asid in both high & low bits.
+ *
+ * page = tlbvirt >> (PGSHIFT+1);	// ignoring even/odd bit
+ * R27 = ((tlbvirt<<(STLBLOG-8) ^ (uchar)tlbvirt ^ page ^
+ *	((page & (MASK(HIPFNBITS) << STLBLOG)) >> HIPFNBITS)) &
+ *	(STLBSIZE-1)) * 12;
+ */
+#define STLBHASH(arg, tmp, tmp2) \
+	MOVW	arg, tmp2; \
+	SRL	$(PGSHIFT+1), arg;	/* move low page # bits to low bits */ \
+	CONST	((MASK(HIPFNBITS) << STLBLOG), tmp); \
+	AND	arg, tmp;		/* extract high page # bits */ \
+	SRL	$HIPFNBITS, tmp;	/* position them */ \
+	XOR	tmp, arg;		/* include them */ \
+	MOVW	tmp2, tmp;		/* asid in low byte */ \
+	SLL	$(STLBLOG-8), tmp;	/* move asid to high bits */ \
+	XOR	tmp, arg;		/* include asid in high bits too */ \
+	AND	$0xff, tmp2, tmp;	/* asid in low byte */ \
+	XOR	tmp, arg;		/* include asid in low bits */ \
+	CONST	(STLBSIZE-1, tmp); \
+	AND	tmp, arg		/* chop to fit */
+
+TEXT	utlbmiss(SB), $-4
+	/*
+	 * don't use R28 by using constants that span both word halves,
+	 * it's unsaved so far.  avoid R24 (up in kernel) and R25 (m in kernel).
+	 */
+	/* update statistics */
+	CONST	(MACHADDR, R26)		/* R26 = m-> */
+	MOVW	16(R26), R27
+	ADDU	$1, R27
+	MOVW	R27, 16(R26)		/* m->tlbfault++ */
+
+	MOVW	R23, M(DESAVE)		/* save R23 */
+
+#ifdef	KUTLBSTATS
+	MOVW	M(STATUS), R23
+	AND	$KUSER, R23
+	BEQ	R23, kmiss
+
+	MOVW	24(R26), R27
+	ADDU	$1, R27
+	MOVW	R27, 24(R26)		/* m->utlbfault++ */
+	JMP	either
+kmiss:
+	MOVW	20(R26), R27
+	ADDU	$1, R27
+	MOVW	R27, 20(R26)		/* m->ktlbfault++ */
+either:
+#endif
+
+	/* compute stlb index */
+	EHB
+	MOVW	M(TLBVIRT), R27		/* asid in low byte */
+	STLBHASH(R27, R26, R23)
+	MOVW	M(DESAVE), R23		/* restore R23 */
+
+	/* scale to a byte index (multiply by 12) */
+	SLL	$1, R27, R26		/* × 2 */
+	ADDU	R26, R27		/* × 3 */
+	SLL	$2, R27			/* × 12 */
+
+	CONST	(MACHADDR, R26)		/* R26 = m-> */
+	MOVW	4(R26), R26		/* R26 = m->stb */
+	ADDU	R26, R27		/* R27 = &m->stb[hash] */
+
+	MOVW	M(BADVADDR), R26
+	AND	$BY2PG, R26
+	BNE	R26, utlbodd		/* odd page? */
+	NOP
+
+utlbeven:
+	MOVW	4(R27), R26		/* R26 = m->stb[hash].phys0 */
+	BEQ	R26, stlbm		/* nothing cached? do it the hard way */
+	NOP
+	MOVW	R26, M(TLBPHYS0)
+	EHB
+	MOVW	8(R27), R26		/* R26 = m->stb[hash].phys1 */
+	JMP	utlbcom
+	MOVW	R26, M(TLBPHYS1)	/* branch delay slot */
+
+utlbodd:
+	MOVW	8(R27), R26		/* R26 = m->stb[hash].phys1 */
+	BEQ	R26, stlbm		/* nothing cached? do it the hard way */
+	NOP
+	MOVW	R26, M(TLBPHYS1)
+	EHB
+	MOVW	4(R27), R26		/* R26 = m->stb[hash].phys0 */
+	MOVW	R26, M(TLBPHYS0)
+
+utlbcom:
+	EHB				/* MTC0/MFC0 hazard */
+	MOVW	M(TLBVIRT), R26
+	MOVW	(R27), R27		/* R27 = m->stb[hash].virt */
+	BEQ	R27, stlbm		/* nothing cached? do it the hard way */
+	NOP
+	/* is the stlb entry for the right virtual address? */
+	BNE	R26, R27, stlbm		/* M(TLBVIRT) != m->stb[hash].virt? */
+	NOP
+
+	/* if an entry exists, overwrite it, else write a random one */
+	CONST	(PGSZ, R27)
+	MOVW	R27, M(PAGEMASK)	/* select page size */
+	EHB
+	TLBP				/* probe tlb */
+	EHB
+	MOVW	M(INDEX), R26
+	BGEZ	R26, utlindex		/* if tlb entry found, rewrite it */
+	EHB				/* delay slot */
+	TLBWR				/* else write random tlb entry */
+	ERET
+utlindex:
+	TLBWI				/* write indexed tlb entry */
+	ERET
+
+/* not in the stlb either; make trap.c figure it out */
+stlbm:
+	MOVW	$exception(SB), R26
+	JMP	(R26)
+	NOP
+
+TEXT	stlbhash(SB), $-4
+	STLBHASH(R1, R2, R3)
+	RETURN
+
+TEXT	vector100(SB), $-4
+	MOVW	$exception(SB), R26
+	JMP	(R26)
+	NOP
+
+TEXT	vector180(SB), $-4
+	MOVW	$exception(SB), R26
+	JMP	(R26)
+	NOP
+
+TEXT	exception(SB), $-4
+	MOVW	M(STATUS), R26
+	AND	$KUSER, R26, R27
+	BEQ	R27, waskernel
+	MOVW	SP, R27			/* delay slot */
+
+wasuser:
+	CONST	(MACHADDR, SP)		/*  m-> */
+	MOVW	8(SP), SP		/*  m->proc */
+	MOVW	8(SP), SP		/*  m->proc->kstack */
+	MOVW	M(STATUS), R26		/* redundant load */
+	ADDU	$(KSTACK-UREGSIZE), SP
+	MOVW	R31, Ureg_r31(SP)
+
+	JAL	savereg1(SB)
+	NOP
+
+	MOVW	R30, Ureg_r30(SP)
+	MOVW	R(MACH), Ureg_r25(SP)
+	MIPS24KNOP
+	MOVW	R(USER), Ureg_r24(SP)
+
+	MOVW	$setR30(SB), R30
+	CONST	(MACHADDR, R(MACH))		/* R(MACH) = m-> */
+	MOVW	8(R(MACH)), R(USER)		/* up = m->proc */
+
+	AND	$(EXCMASK<<2), R26, R1
+	SUBU	$(CSYS<<2), R1
+	BNE	R1, notsys
+	NOP
+
+	/* the carrera does this: */
+//	ADDU	$8, SP, R1			/* first arg for syscall */
+
+	MOVW	SP, R1				/* first arg for syscall */
+	JAL	syscall(SB)
+	SUBU	$Notuoffset, SP			/* delay slot */
+sysrestore:
+	JAL	restreg1(SB)
+	ADDU	$Notuoffset, SP			/* delay slot */
+
+	MOVW	Ureg_r31(SP), R31
+	MOVW	Ureg_status(SP), R26
+	MOVW	Ureg_r30(SP), R30
+	MOVW	R26, M(STATUS)
+	EHB
+	MOVW	Ureg_pc(SP), R26		/* old pc */
+	MOVW	Ureg_sp(SP), SP
+	MOVW	R26, M(EPC)
+	ERET
+
+notsys:
+	JAL	savereg2(SB)
+	NOP
+
+	/* the carrera does this: */
+//	ADDU	$8, SP, R1			/* first arg for trap */
+
+	MOVW	SP, R1				/* first arg for trap */
+	JAL	trap(SB)
+	SUBU	$Notuoffset, SP			/* delay slot */
+
+	ADDU	$Notuoffset, SP
+
+restore:
+	JAL	restreg1(SB)
+	NOP
+	JAL	restreg2(SB)		/* restores R28, among others */
+	NOP
+
+	MOVW	Ureg_r30(SP), R30
+	MOVW	Ureg_r31(SP), R31
+	MOVW	Ureg_r25(SP), R(MACH)
+	MOVW	Ureg_r24(SP), R(USER)
+	MOVW	Ureg_sp(SP), SP
+	MOVW	R26, M(EPC)
+	ERET
+
+waskernel:
+	SUBU	$UREGSIZE, SP
+	OR	$7, SP				/* conservative rounding */
+	XOR	$7, SP
+	MOVW	R31, Ureg_r31(SP)
+
+	JAL	savereg1(SB)
+	NOP
+	JAL	savereg2(SB)
+	NOP
+
+	/* the carrera does this: */
+//	ADDU	$8, SP, R1			/* first arg for trap */
+
+	MOVW	SP, R1			/* first arg for trap */
+	JAL	trap(SB)
+	SUBU	$Notuoffset, SP			/* delay slot */
+
+	ADDU	$Notuoffset, SP
+
+	JAL	restreg1(SB)
+	NOP
+
+	/*
+	 * if about to return to `wait', interrupt arrived just before
+	 * executing wait, so move saved pc past it.
+	 */
+	MOVW	Ureg_pc(SP), R26
+	MOVW	R26, R31
+	MOVW	$wait(SB), R1
+	SUBU	R1, R31
+	BNE	R31, notwait
+	NOP
+	ADD	$BY2WD, R26		/* advance saved pc */
+	MOVW	R26, Ureg_pc(SP)
+notwait:
+	JAL	restreg2(SB)		/* restores R28, among others */
+	NOP
+
+	MOVW	Ureg_r31(SP), R31
+	MOVW	Ureg_sp(SP), SP
+	MOVW	R26, M(EPC)
+	ERET
+
+TEXT	forkret(SB), $0
+	JMP	sysrestore
+	MOVW	R0, R1			/* delay slot; child returns 0 */
+
+/*
+ * save mandatory registers.
+ * called with old M(STATUS) in R26.
+ * called with old SP in R27
+ * returns with M(CAUSE) in R26
+ */
+TEXT	savereg1(SB), $-4
+	MOVW	R1, Ureg_r1(SP)
+
+	MOVW	$(~KMODEMASK),R1	/* don't use R28, it's unsaved so far */
+	AND	R26, R1
+	MOVW	R1, M(STATUS)
+	EHB
+
+	MOVW	R26, Ureg_status(SP)	/* status */
+	MOVW	R27, Ureg_sp(SP)	/* user SP */
+
+	MOVW	M(EPC), R1
+	MOVW	M(CAUSE), R26
+
+	MOVW	R23, Ureg_r23(SP)
+	MOVW	R22, Ureg_r22(SP)
+	MIPS24KNOP
+	MOVW	R21, Ureg_r21(SP)
+	MOVW	R20, Ureg_r20(SP)
+	MIPS24KNOP
+	MOVW	R19, Ureg_r19(SP)
+	MOVW	R1, Ureg_pc(SP)
+	RETURN
+
+/*
+ * all other registers.
+ * called with M(CAUSE) in R26
+ */
+TEXT	savereg2(SB), $-4
+	MOVW	R2, Ureg_r2(SP)
+
+	MOVW	M(BADVADDR), R2
+	MOVW	R26, Ureg_cause(SP)
+	MOVW	M(TLBVIRT), R1
+	MOVW	R2, Ureg_badvaddr(SP)
+	MOVW	R1, Ureg_tlbvirt(SP)
+	MOVW	HI, R1
+	MOVW	LO, R2
+	MOVW	R1, Ureg_hi(SP)
+	MOVW	R2, Ureg_lo(SP)
+	MIPS24KNOP
+					/* LINK,SB,SP missing */
+	MOVW	R28, Ureg_r28(SP)
+					/* R27, R26 not saved */
+					/* R25, R24 missing */
+					/* R23- R19 saved in save1 */
+	MOVW	R18, Ureg_r18(SP)
+	MIPS24KNOP
+	MOVW	R17, Ureg_r17(SP)
+	MOVW	R16, Ureg_r16(SP)
+	MIPS24KNOP
+	MOVW	R15, Ureg_r15(SP)
+	MOVW	R14, Ureg_r14(SP)
+	MIPS24KNOP
+	MOVW	R13, Ureg_r13(SP)
+	MOVW	R12, Ureg_r12(SP)
+	MIPS24KNOP
+	MOVW	R11, Ureg_r11(SP)
+	MOVW	R10, Ureg_r10(SP)
+	MIPS24KNOP
+	MOVW	R9, Ureg_r9(SP)
+	MOVW	R8, Ureg_r8(SP)
+	MIPS24KNOP
+	MOVW	R7, Ureg_r7(SP)
+	MOVW	R6, Ureg_r6(SP)
+	MIPS24KNOP
+	MOVW	R5, Ureg_r5(SP)
+	MOVW	R4, Ureg_r4(SP)
+	MIPS24KNOP
+	MOVW	R3, Ureg_r3(SP)
+	RETURN
+
+TEXT	restreg1(SB), $-4
+	MOVW	Ureg_r23(SP), R23
+	MOVW	Ureg_r22(SP), R22
+	MOVW	Ureg_r21(SP), R21
+	MOVW	Ureg_r20(SP), R20
+	MOVW	Ureg_r19(SP), R19
+	RETURN
+
+TEXT	restreg2(SB), $-4
+					/* LINK,SB,SP missing */
+	MOVW	Ureg_r28(SP), R28
+					/* R27, R26 not saved */
+					/* R25, R24 missing */
+					/* R19- R23 restored in rest1 */
+	MOVW	Ureg_r18(SP), R18
+	MOVW	Ureg_r17(SP), R17
+	MOVW	Ureg_r16(SP), R16
+	MOVW	Ureg_r15(SP), R15
+	MOVW	Ureg_r14(SP), R14
+	MOVW	Ureg_r13(SP), R13
+	MOVW	Ureg_r12(SP), R12
+	MOVW	Ureg_r11(SP), R11
+	MOVW	Ureg_r10(SP), R10
+	MOVW	Ureg_r9(SP), R9
+	MOVW	Ureg_r8(SP), R8
+	MOVW	Ureg_r7(SP), R7
+	MOVW	Ureg_r6(SP), R6
+	MOVW	Ureg_r5(SP), R5
+	MOVW	Ureg_r4(SP), R4
+	MOVW	Ureg_r3(SP), R3
+	MOVW	Ureg_lo(SP), R2
+	MOVW	Ureg_hi(SP), R1
+	MOVW	R2, LO
+	MOVW	R1, HI
+
+	MOVW	Ureg_status(SP), R1
+	MOVW	Ureg_r2(SP), R2
+	MOVW	R1, M(STATUS)		/* could change interruptibility */
+	EHB
+	MOVW	Ureg_r1(SP), R1	/* BOTCH */
+	MOVW	Ureg_pc(SP), R26
+	RETURN
+
+#ifdef OLD_MIPS_EXAMPLE
+/* this appears to be a dreg from the distant past */
+TEXT	rfnote(SB), $0
+	MOVW	R1, R26			/* 1st arg is &uregpointer */
+	JMP	restore
+	SUBU	$(BY2WD), R26, SP	/* delay slot: pc hole */
+#endif
+
+/*
+ * degenerate floating-point stuff
+ */
+
+TEXT	clrfpintr(SB), $0
+	RETURN
+
+TEXT	savefpregs(SB), $0
+	RETURN
+
+TEXT	restfpregs(SB), $0
+	RETURN
+
+TEXT	fcr31(SB), $0			/* fp csr */
+	MOVW	R0, R1
+	RETURN
+
+/*
+ * Emulate 68020 test and set: load linked / store conditional
+ */
+
+TEXT	tas(SB), $0
+	MOVW	R1, R2		/* address of key */
+tas1:
+	MOVW	$1, R3
+	LL(2, 1)
+	NOP
+	SC(2, 3)
+	NOP
+	BEQ	R3, tas1
+	NOP
+	RETURN
+
+TEXT	_xinc(SB), $0
+	MOVW	R1, R2		/* address of counter */
+loop:
+	MOVW	$1, R3
+	LL(2, 1)
+	NOP
+	ADDU	R1, R3, R3
+	SC(2, 3)
+	NOP
+	BEQ	R3, loop
+	NOP
+	RETURN
+
+TEXT	_xdec(SB), $0
+	SYNC
+	MOVW	R1, R2		/* address of counter */
+loop1:
+	MOVW	$-1, R3
+	LL(2, 1)
+	NOP
+	ADDU	R1, R3, R3
+	MOVW	R3, R1
+	SC(2, 3)
+	NOP
+	BEQ	R3, loop1
+	NOP
+	RETURN
+
+TEXT cmpswap(SB), $0
+	MOVW	R1, R2		/* address of key */
+	MOVW	old+4(FP), R3	/* old value */
+	MOVW	new+8(FP), R4	/* new value */
+	LL(2, 1)		/* R1 = (R2) */
+	NOP
+	BNE	R1, R3, fail
+	NOP
+	MOVW	R4, R1
+	SC(2, 1)	/* (R2) = R1 if (R2) hasn't changed; R1 = success */
+	NOP
+	RETURN
+fail:
+	MOVW	R0, R1
+	RETURN
+
+/*
+ *  cache manipulation
+ */
+
+/*
+ *  we avoided using R4, R5, R6, and R7 so gotopc can call us without saving
+ *  them, but gotopc is now gone.
+ */
+TEXT	icflush(SB), $-4			/* icflush(virtaddr, count) */
+	MOVW	4(FP), R9
+	DI(10)				/* intrs off, old status -> R10 */
+	UBARRIERS(7, R7, ichb);		/* return to kseg1 (uncached) */
+	ADDU	R1, R9			/* R9 = last address */
+	MOVW	$(~(CACHELINESZ-1)), R8
+	AND	R1, R8			/* R8 = first address, rounded down */
+	ADDU	$(CACHELINESZ-1), R9
+	AND	$(~(CACHELINESZ-1)), R9	/* round last address up */
+	SUBU	R8, R9			/* R9 = revised count */
+icflush1:
+//	CACHE	PD+HWB, (R8)		/* flush D to ram */
+	CACHE	PI+HINV, (R8)		/* invalidate in I */
+	SUBU	$CACHELINESZ, R9
+	BGTZ	R9, icflush1
+	ADDU	$CACHELINESZ, R8	/* delay slot */
+
+	BARRIERS(7, R7, ic2hb);		/* return to kseg0 (cached) */
+	MOVW	R10, M(STATUS)
+	JRHB(31)			/* return and clear all hazards */
+
+TEXT	dcflush(SB), $-4			/* dcflush(virtaddr, count) */
+	MOVW	4(FP), R9
+	DI(10)				/* intrs off, old status -> R10 */
+	SYNC
+	EHB
+	ADDU	R1, R9			/* R9 = last address */
+	MOVW	$(~(CACHELINESZ-1)), R8
+	AND	R1, R8			/* R8 = first address, rounded down */
+	ADDU	$(CACHELINESZ-1), R9
+	AND	$(~(CACHELINESZ-1)), R9	/* round last address up */
+	SUBU	R8, R9			/* R9 = revised count */
+dcflush1:
+//	CACHE	PI+HINV, (R8)		/* invalidate in I */
+	CACHE	PD+HWBI, (R8)		/* flush & invalidate in D */
+	SUBU	$CACHELINESZ, R9
+	BGTZ	R9, dcflush1
+	ADDU	$CACHELINESZ, R8	/* delay slot */
+	SYNC
+	EHB
+	MOVW	R10, M(STATUS)
+	JRHB(31)			/* return and clear all hazards */
+
+/* the i and d caches may be different sizes, so clean them separately */
+TEXT	cleancache(SB), $-4
+	DI(10)				/* intrs off, old status -> R10 */
+
+	UBARRIERS(7, R7, cchb);		/* return to kseg1 (uncached) */
+	MOVW	R0, R1			/* index, not address */
+	MOVW	$ICACHESIZE, R9
+iccache:
+	CACHE	PI+IWBI, (R1)		/* flush & invalidate I by index */
+	SUBU	$CACHELINESZ, R9
+	BGTZ	R9, iccache
+	ADDU	$CACHELINESZ, R1	/* delay slot */
+
+	BARRIERS(7, R7, cc2hb);		/* return to kseg0 (cached) */
+
+	MOVW	R0, R1			/* index, not address */
+	MOVW	$DCACHESIZE, R9
+dccache:
+	CACHE	PD+IWBI, (R1)		/* flush & invalidate D by index */
+	SUBU	$CACHELINESZ, R9
+	BGTZ	R9, dccache
+	ADDU	$CACHELINESZ, R1	/* delay slot */
+
+	SYNC
+	MOVW	R10, M(STATUS)
+	JRHB(31)			/* return and clear all hazards */
+
+/*
+ * access to CP0 registers
+ */
+
+TEXT	prid(SB), $0
+	MOVW	M(PRID), R1
+	RETURN
+
+TEXT	rdcount(SB), $0
+	MOVW	M(COUNT), R1
+	RETURN
+
+TEXT	wrcount(SB), $0
+	MOVW	R1, M(COUNT)
+	EHB
+	RETURN
+
+TEXT	wrcompare(SB), $0
+	MOVW	R1, M(COMPARE)
+	EHB
+	RETURN
+
+TEXT	rdcompare(SB), $0
+	MOVW	M(COMPARE), R1
+	RETURN
+
+TEXT	getconfig(SB), $-4
+	MOVW	M(CONFIG), R1
+	RETURN
+
+TEXT	getconfig1(SB), $-4
+	MFC0(CONFIG, 1, 1)
+	RETURN
+
+TEXT	getconfig2(SB), $-4
+	MFC0(CONFIG, 2, 1)
+	RETURN
+
+TEXT	getconfig3(SB), $-4
+	MFC0(CONFIG, 3, 1)
+	RETURN
+
+TEXT	getconfig4(SB), $-4
+	MFC0(CONFIG, 4, 1)
+	RETURN
+
+TEXT	getconfig7(SB), $-4
+	MFC0(CONFIG, 7, 1)
+	RETURN
+
+TEXT	gethwreg3(SB), $-4
+	RDHWR(3, 1)
+	RETURN
+
+TEXT	getcause(SB), $-4
+	MOVW	M(CAUSE), R1
+	RETURN
+
+TEXT	C_fcr0(SB), $-4		/* fp implementation */
+	MOVW	$0x500, R1	/* claim to be an r4k, thus have ll/sc */
+	RETURN
+
+TEXT	getstatus(SB), $0
+	MOVW	M(STATUS), R1
+	RETURN
+
+TEXT	setstatus(SB), $0
+	MOVW	R1, M(STATUS)
+	EHB
+	RETURN
+
+TEXT	setwatchhi0(SB), $0
+	MOVW	R1, M(WATCHHI)
+	EHB
+	RETURN
+
+/*
+ * beware that the register takes a double-word address, so it's not
+ * precise to the individual instruction.
+ */
+TEXT	setwatchlo0(SB), $0
+	MOVW	R1, M(WATCHLO)
+	EHB
+	RETURN
+
+TEXT	setsp(SB), $-4
+	MOVW	R1, SP
+	RETURN
+
+TEXT	getintctl(SB), $-4
+	MFC0(STATUS, 1, 1)
+	RETURN
+
+TEXT	getsrsctl(SB), $-4
+	MFC0(STATUS, 2, 1)
+	RETURN
+
+TEXT	getsrsmap(SB), $-4
+	MFC0(STATUS, 3, 1)
+	RETURN
+
+TEXT	getperfctl0(SB), $-4
+	MFC0(PERFCOUNT, 0, 1)
+	RETURN
+
+TEXT	getperfctl1(SB), $-4
+	MFC0(PERFCOUNT, 2, 1)
+	RETURN
+
+	GLOBL	sanity(SB), $4
+	DATA	sanity(SB)/4, $SANITY
+
+	SCHED

+ 622 - 0
sys/src/9/rb/main.c

@@ -0,0 +1,622 @@
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"io.h"
+#include	"init.h"
+#include	"pool.h"
+#include	"../ip/ip.h"
+#include	<tos.h>
+#include	<bootexec.h>
+#include	"reboot.h"
+
+typedef struct mipsexec Mipsexec;
+
+/*
+ * Option arguments from the command line.
+ * oargv[0] is the boot file.
+ */
+static int oargc;
+static char* oargv[20];
+static char oargb[128];
+static int oargblen;
+
+static uintptr sp;		/* XXX - must go - user stack of init proc */
+
+/*
+ * software tlb simulation
+ */
+static Softtlb stlb[MAXMACH][STLBSIZE];
+
+Conf	conf;
+FPsave	initfp;
+
+int normalprint;
+
+char *
+getconf(char *)
+{
+	return nil;	/* stub */
+}
+
+static void
+optionsinit(char* s)
+{
+	strecpy(oargb, oargb+sizeof(oargb), s);
+
+	oargblen = strlen(oargb);
+	oargc = tokenize(oargb, oargv, nelem(oargv)-1);
+	oargv[oargc] = nil;
+}
+
+static void
+prcpuid(void)
+{
+	ulong cpuid, cfg1;
+	char *cpu;
+
+	cpuid = prid();
+	if (((cpuid>>16) & MASK(8)) == 0)		/* vendor */
+		cpu = "old mips";
+	else if (((cpuid>>16) & MASK(8)) == 1)
+		switch ((cpuid>>8) & MASK(8)) {		/* processor */
+		case 0x93:
+			cpu = "mips 24k";
+			break;
+		case 0x96:
+			cpu = "mips 24ke";
+			break;
+		default:
+			cpu = "mips";
+			break;
+		}
+	else
+		cpu = "other mips";
+	delay(20);
+	print("cpu%d: %ldMHz %s %se v%ld.%ld rev %ld, ",
+		m->machno, m->hz / Mhz, cpu, getconfig() & (1<<15)? "b": "l",
+		(cpuid>>5) & MASK(3), (cpuid>>2) & MASK(3), cpuid & MASK(2));
+	delay(200);
+	cfg1 = getconfig1();
+	print("%s fpu\n", (cfg1 & 1? "has": "no"));
+	print("cpu%d: %ld tlb entries, using %dK pages\n", m->machno,
+		((cfg1>>25) & MASK(6)) + 1, BY2PG/1024);
+	delay(50);
+	print("cpu%d: l1 i cache: %d sets 4 ways 32 bytes/line\n", m->machno,
+		64 << ((cfg1>>22) & MASK(3)));
+	delay(50);
+	print("cpu%d: l1 d cache: %d sets 4 ways 32 bytes/line\n", m->machno,
+		64 << ((cfg1>>13) & MASK(3)));
+	delay(500);
+	if (0)
+		print("cpu%d: cycle counter res = %ld\n",
+			m->machno, gethwreg3());
+}
+
+static void
+fmtinit(void)
+{
+	printinit();
+	quotefmtinstall();
+	/* ipreset installs these when chandevreset runs */
+	fmtinstall('i', eipfmt);
+	fmtinstall('I', eipfmt);
+	fmtinstall('E', eipfmt);
+	fmtinstall('V', eipfmt);
+	fmtinstall('M', eipfmt);
+}
+
+static int
+ckpagemask(ulong mask, ulong size)
+{
+	int s;
+	ulong pm;
+
+	s = splhi();
+	setpagemask(mask);
+	pm = getpagemask();
+	splx(s);
+	if(pm != mask){
+		iprint("page size %ldK not supported on this cpu; "
+			"mask %#lux read back as %#lux\n", size/1024, mask, pm);
+		return -1;
+	}
+	return 0;
+}
+
+/* called from rebootcmd() */
+int
+parsemipsboothdr(Chan *c, ulong magic, Execvals *evp)
+{
+	long extra;
+	Mipsexec me;
+
+	/*
+	 * BOOT_MAGIC is sometimes defined like this:
+	 * #define BOOT_MAGIC	(0x160<<16) || magic == ((0x160<<16)|3)
+	 * so we can only use it in a fairly stylized manner.
+	 */
+	if(magic == BOOT_MAGIC) {
+		c->offset = 0;			/* back up */
+		readn(c, &me, sizeof me);
+		/* if binary is -H1, read an extra long */
+		if (l2be(me.amagic) == 0407 && me.nscns == 0)
+			readn(c, &extra, sizeof extra);
+		evp->entry = l2be(me.mentry);
+		evp->textsize = l2be(me.tsize);
+		evp->datasize = l2be(me.dsize);
+		return 0;
+	} else
+		return -1;
+}
+
+void
+main(void)
+{
+	stopwdog();			/* tranquilise the dog */
+	optionsinit("/boot/boot boot");
+	confinit();
+	savefpregs(&initfp);
+
+	machinit();			/* calls clockinit */
+	active.exiting = 0;
+	active.machs = 1;
+
+	kmapinit();
+	xinit();
+
+	timersinit();
+	fmtinit();
+	vecinit();
+
+	normalprint = 1;
+	print("\nPlan 9\n");
+	prcpuid();
+	if (PTECACHABILITY == PTENONCOHERWT)
+		print("caches configured as write-through\n");
+	if (0)
+		xsummary();
+
+	ckpagemask(PGSZ, BY2PG);
+	tlbinit();
+	machwire();
+	pageinit();
+	procinit0();
+	initseg();
+	links();
+	chandevreset();
+
+	swapinit();
+	userinit();
+	sicwdog();
+	parseboothdr = parsemipsboothdr;
+	schedinit();
+	panic("schedinit returned");
+}
+
+/*
+ *  initialize a processor's mach structure.  each processor does this
+ *  for itself.
+ */
+void
+machinit(void)
+{
+	/* Ensure CU1 is off */
+	clrfpintr();
+
+	m->stb = &stlb[m->machno][0];
+
+	clockinit();
+}
+
+/*
+ *  setup MIPS trap vectors
+ */
+void
+vecinit(void)
+{
+	memmove((ulong*)UTLBMISS, (ulong*)vector0, 0x80);
+	memmove((ulong*)XEXCEPTION, (ulong*)vector0, 0x80);
+	memmove((ulong*)CACHETRAP, (ulong*)vector100, 0x80);
+	memmove((ulong*)EXCEPTION, (ulong*)vector180, 0x80);
+	memmove((ulong*)(KSEG0+0x200), (ulong*)vector180, 0x80);
+	icflush((ulong*)UTLBMISS, 4*1024);
+
+	setstatus(getstatus() & ~BEV);
+}
+
+void
+init0(void)
+{
+	char buf[128];
+
+	up->nerrlab = 0;
+
+	spllo();
+
+	/*
+	 * These are o.k. because rootinit is null.
+	 * Then early kproc's will have a root and dot.
+	 */
+	up->slash = namec("#/", Atodir, 0, 0);
+	pathclose(up->slash->path);
+	up->slash->path = newpath("/");
+	up->dot = cclone(up->slash);
+
+	chandevinit();
+
+	if(!waserror()){
+		ksetenv("cputype", "mips", 0);
+		snprint(buf, sizeof buf, "mips %s rb450g", conffile);
+		ksetenv("terminal", buf, 0);
+		if(cpuserver)
+			ksetenv("service", "cpu", 0);
+		else
+			ksetenv("service", "terminal", 0);
+		/*
+		 * we don't have a good way to read our cfg file in
+		 * RouterBOOT, so set the configuration here.
+		 */
+		ksetenv("nobootprompt", "tcp", 0);
+		ksetenv("nvram", "/boot/nvram", 0);
+		poperror();
+	}
+	kproc("alarm", alarmkproc, 0);
+	i8250console();
+	touser(sp);
+}
+
+FPsave	initfp;
+
+static void
+bootargs(uintptr base)
+{
+	int i;
+	ulong ssize;
+	char **av, *p;
+
+	/*
+	 * Push the boot args onto the stack.
+	 * The initial value of the user stack must be such
+	 * that the total used is larger than the maximum size
+	 * of the argument list checked in syscall.
+	 */
+	i = oargblen+1;
+	p = UINT2PTR(STACKALIGN(base + BY2PG - sizeof(Tos) - i));
+	memmove(p, oargb, i);
+
+	/*
+	 * Now push the argv pointers.
+	 * The code jumped to by touser in lproc.s expects arguments
+	 *	main(char* argv0, ...)
+	 * and calls
+	 * 	startboot("/boot/boot", &argv0)
+	 * not the usual (int argc, char* argv[])
+	 */
+	av = (char**)(p - (oargc+1)*sizeof(char*));
+	ssize = base + BY2PG - PTR2UINT(av);
+	for(i = 0; i < oargc; i++)
+		*av++ = (oargv[i] - oargb) + (p - base) + (USTKTOP - BY2PG);
+	*av = nil;
+	sp = USTKTOP - ssize;
+}
+
+void
+userinit(void)
+{
+	Proc *p;
+	KMap *k;
+	Page *pg;
+	Segment *s;
+
+	p = newproc();
+	p->pgrp = newpgrp();
+	p->egrp = smalloc(sizeof(Egrp));
+	p->egrp->ref = 1;
+	p->fgrp = dupfgrp(nil);
+	p->rgrp = newrgrp();
+	p->procmode = 0640;
+
+	kstrdup(&eve, "");
+	kstrdup(&p->text, "*init*");
+	kstrdup(&p->user, eve);
+
+	p->fpstate = FPinit;
+	p->fpsave.fpstatus = initfp.fpstatus;
+
+	/*
+	 * Kernel Stack
+	 */
+	p->sched.pc = (ulong)init0;
+	p->sched.sp = (ulong)p->kstack+KSTACK-(sizeof(Sargs)+BY2WD);
+	p->sched.sp = STACKALIGN(p->sched.sp);
+
+	/*
+	 * User Stack
+	 *
+	 * Technically, newpage can't be called here because it
+	 * should only be called when in a user context as it may
+	 * try to sleep if there are no pages available, but that
+	 * shouldn't be the case here.
+	 */
+	s = newseg(SG_STACK, USTKTOP-USTKSIZE, USTKSIZE/BY2PG);
+	p->seg[SSEG] = s;
+	pg = newpage(1, 0, USTKTOP-BY2PG);
+	segpage(s, pg);
+	k = kmap(pg);
+	bootargs(VA(k));
+	kunmap(k);
+
+	/*
+	 * Text
+	 */
+	s = newseg(SG_TEXT, UTZERO, 1);
+	s->flushme++;
+	p->seg[TSEG] = s;
+	pg = newpage(1, 0, UTZERO);
+	memset(pg->cachectl, PG_TXTFLUSH, sizeof(pg->cachectl));
+	segpage(s, pg);
+	k = kmap(s->map[0]->pages[0]);
+	memset((void *)VA(k), 0, BY2PG);
+	memmove((ulong*)VA(k), initcode, sizeof initcode);
+	kunmap(k);
+
+	ready(p);
+}
+
+void
+procrestore(Proc *p)
+{
+	uvlong t;
+
+	if(p->kp)
+		return;
+	cycles(&t);
+	p->pcycles -= t;
+}
+
+/*
+ *  Save the mach dependent part of the process state.
+ */
+void
+procsave(Proc *p)
+{
+	uvlong t;
+
+	cycles(&t);
+	p->pcycles += t;
+	/* no fpu, so no fp state to save */
+}
+
+static void
+writeconf(void)
+{
+	char *p, *q;
+	int n;
+
+	p = getconfenv();
+
+	if(waserror()) {
+		free(p);
+		nexterror();
+	}
+
+	/* convert to name=value\n format */
+	for(q=p; *q; q++) {
+		q += strlen(q);
+		*q = '=';
+		q += strlen(q);
+		*q = '\n';
+	}
+	n = q - p + 1;
+#ifdef BOOTARGS_EXIST
+	if(n >= BOOTARGSLEN)
+		error("kernel configuration too large");
+	memmove(BOOTARGS, p, n);
+	memset(BOOTARGS + n, '\n', BOOTARGSLEN - n);
+#endif
+	USED(n);
+	poperror();
+	free(p);
+}
+
+static void
+shutdown(int ispanic)
+{
+	int ms, once;
+
+	ilock(&active);
+	if(ispanic)
+		active.ispanic = ispanic;
+	else if(m->machno == 0 && (active.machs & (1<<m->machno)) == 0)
+		active.ispanic = 0;
+	once = active.machs & (1<<m->machno);
+	/*
+	 * setting exiting will make hzclock() on each processor call exit(0),
+	 * which calls shutdown(0) and idles non-bootstrap cpus and returns
+	 * on bootstrap processors (to permit a reboot).  clearing our bit
+	 * in machs avoids calling exit(0) from hzclock() on this processor.
+	 */
+	active.machs &= ~(1<<m->machno);
+	active.exiting = 1;
+	iunlock(&active);
+
+	if(once) {
+		delay(m->machno*1000);		/* stagger them */
+		iprint("cpu%d: exiting\n", m->machno);
+	}
+	spllo();
+	ms = MAXMACH * 1000;
+	for(; ms > 0; ms -= TK2MS(2)){
+		delay(TK2MS(2));
+		if(active.machs == 0 && consactive() == 0)
+			break;
+	}
+	delay(100);
+}
+
+/*
+ * the new kernel is already loaded at address `code'
+ * of size `size' and entry point `entry'.
+ */
+void
+reboot(void *entry, void *code, ulong size)
+{
+	void (*f)(ulong, ulong, ulong);
+
+	writeconf();
+
+	/*
+	 * the boot processor is cpu0.  execute this function on it
+	 * so that the new kernel has the same cpu0.
+	 */
+	if (m->machno != 0) {
+		procwired(up, 0);
+		sched();
+	}
+	if (m->machno != 0)
+		print("on cpu%d (not 0)!\n", m->machno);
+
+	shutdown(0);
+
+	/*
+	 * should be the only processor running now
+	 */
+//	iprint("reboot: entry %#p code %#p size %ld\n", entry, code, size);
+//	iprint("code[0] = %#lux\n", *(ulong *)code);
+
+	/* turn off buffered serial console */
+	serialoq = nil;
+	kprintoq = nil;
+	screenputs = nil;
+
+	/* shutdown devices */
+	chandevshutdown();
+
+	/* call off the dog */
+	clockshutdown();
+
+	splhi();
+	intrshutdown();
+
+	/* is the watchdog tied into the usb machinery? */
+//	*Reset |= Rstusbohcidll | Rstusbhost | Rstusbphy;
+//		Rstge0mac | Rstge0phy |
+//		Rstge1mac | Rstge1phy;
+
+	/* setup reboot trampoline function */
+	f = (void*)REBOOTADDR;
+	memmove(f, rebootcode, sizeof(rebootcode));
+	icflush(f, sizeof(rebootcode));
+
+	setstatus(BEV);		/* also, kernel mode, no interrupts */
+	coherence();
+
+	/* off we go - never to return */
+	(*f)((ulong)entry, (ulong)code, size);
+
+	panic("loaded kernel returned!");
+}
+
+void
+exit(int type)
+{
+	int timer;
+	void (*fnp)(void);
+
+	stopwdog();
+
+	delay(1000);
+	lock(&active);
+	active.machs &= ~(1<<m->machno);
+	active.exiting = 1;
+	unlock(&active);
+	spllo();
+
+	print("cpu %d exiting\n", m->machno);
+	timer = 0;
+	while(active.machs || consactive()) {
+		if(timer++ > 400)
+			break;
+		delay(10);
+	}
+	delay(1000);
+	splhi();
+	USED(type);
+
+	setstatus(BEV);
+	coherence();
+
+	iprint("exit: awaiting reset\n");
+	wdogreset();			/* wake the dog with v. short timeout */
+
+//	*Reset |= Rstfullchip;
+//	*Reset |= Rstcpucold;
+
+	delay(1000);			/* await a reset */
+
+	iprint("exit: jumping to rom\n");
+	fnp = (void (*)(void))ROM;
+	(*fnp)();
+
+	iprint("exit: looping\n");
+	for (;;)
+		;
+}
+
+void
+idlehands(void)
+{
+	stopwdog();
+	idle();
+	sicwdog();			/* wake the dog */
+}
+
+void
+confinit(void)
+{
+	ulong kpages, ktop;
+
+	/*
+	 *  divide memory twixt user pages and kernel.
+	 */
+	conf.mem[0].base = ktop = PADDR(PGROUND((ulong)end));
+	/* fixed memory on routerboard */
+	conf.mem[0].npage = MEMSIZE/BY2PG - ktop/BY2PG;
+	conf.npage = conf.mem[0].npage;
+	conf.nuart = 1;
+
+	kpages = conf.npage - (conf.npage*80)/100;
+	if(kpages > (64*MB + conf.npage*sizeof(Page))/BY2PG){
+		kpages = (64*MB + conf.npage*sizeof(Page))/BY2PG;
+		kpages += (conf.nproc*KSTACK)/BY2PG;
+	}
+	conf.upages = conf.npage - kpages;
+	conf.ialloc = (kpages/2)*BY2PG;
+
+	kpages *= BY2PG;
+	kpages -= conf.upages*sizeof(Page)
+		+ conf.nproc*sizeof(Proc)
+		+ conf.nimage*sizeof(Image)
+		+ conf.nswap
+		+ conf.nswppo*sizeof(Page);
+	mainmem->maxsize = kpages;
+
+	/*
+	 *  set up CPU's mach structure
+	 *  cpu0's was zeroed in l.s and our stack is in Mach, so don't zero it.
+	 */
+	m->machno = 0;
+	m->speed = 680;			/* initial guess at MHz, for rb450g */
+	m->hz = m->speed * Mhz;
+	conf.nmach = 1;
+
+	/* set up other configuration parameters */
+	conf.nproc = 2000;
+	conf.nswap = 262144;
+	conf.nswppo = 4096;
+	conf.nimage = 200;
+
+	conf.copymode = 0;		/* copy on write */
+}

+ 328 - 0
sys/src/9/rb/mem.h

@@ -0,0 +1,328 @@
+/*
+ * Memory and machine-specific definitions.  Used in C and assembler.
+ */
+
+/*
+ * Sizes
+ */
+
+#define	BI2BY		8			/* bits per byte */
+#define	BI2WD		32			/* bits per word */
+#define	BY2WD		4			/* bytes per word */
+#define	BY2V		8			/* bytes per vlong */
+
+#define MAXBY2PG (16*1024) /* rounding for UTZERO in executables; see mkfile */
+#define UTROUND(t)	ROUNDUP((t), MAXBY2PG)
+
+#ifndef BIGPAGES
+#define	BY2PG		4096			/* bytes per page */
+#define	PGSHIFT		12			/* log2(BY2PG) */
+#define	PGSZ		PGSZ4K
+#define MACHSIZE	(2*BY2PG)
+#else
+/* 16K pages work very poorly */
+#define	BY2PG		(16*1024)		/* bytes per page */
+#define	PGSHIFT		14			/* log2(BY2PG) */
+#define PGSZ		PGSZ16K
+#define MACHSIZE	BY2PG
+#endif
+
+#define	KSTACK		8192			/* Size of kernel stack */
+#define	WD2PG		(BY2PG/BY2WD)		/* words per page */
+
+#define	MAXMACH		1   /* max # cpus system can run; see active.machs */
+#define STACKALIGN(sp)	((sp) & ~7)		/* bug: assure with alloc */
+#define	BLOCKALIGN	16
+#define CACHELINESZ	32			/* mips24k */
+#define ICACHESIZE	(64*1024)		/* rb450g */
+#define DCACHESIZE	(32*1024)		/* rb450g */
+
+#define MASK(w)		FMASK(0, w)
+
+/*
+ * Time
+ */
+#define	HZ		100			/* clock frequency */
+#define	MS2HZ		(1000/HZ)		/* millisec per clock tick */
+#define	TK2SEC(t)	((t)/HZ)		/* ticks to seconds */
+
+/*
+ * CP0 registers
+ */
+
+#define INDEX		0
+#define RANDOM		1
+#define TLBPHYS0	2	/* aka ENTRYLO0 */
+#define TLBPHYS1	3	/* aka ENTRYLO1 */
+#define CONTEXT		4
+#define PAGEMASK	5
+#define WIRED		6
+#define BADVADDR	8
+#define COUNT		9
+#define TLBVIRT		10	/* aka ENTRYHI */
+#define COMPARE		11
+#define STATUS		12
+#define CAUSE		13
+#define EPC		14
+#define	PRID		15
+#define	CONFIG		16
+#define	LLADDR		17
+#define	WATCHLO		18
+#define	WATCHHI		19
+#define DEBUG		23
+#define DEPC		24
+#define PERFCOUNT	25
+#define	CACHEECC	26
+#define	CACHEERR	27
+#define	TAGLO		28
+#define	TAGHI		29
+#define	ERROREPC	30
+#define DESAVE		31
+
+/*
+ * M(STATUS) bits
+ */
+#define KMODEMASK	0x0000001f
+#define IE		0x00000001	/* master interrupt enable */
+#define EXL		0x00000002	/* exception level */
+#define ERL		0x00000004	/* error level */
+#define KSUPER		0x00000008
+#define KUSER		0x00000010
+#define KSU		0x00000018
+//#define UX		0x00000020 /* no [USK]X 64-bit extension bits on 24k */
+//#define SX		0x00000040
+//#define KX		0x00000080
+#define INTMASK		0x0000ff00
+#define INTR0		0x00000100	/* interrupt enable bits */
+#define INTR1		0x00000200
+#define INTR2		0x00000400
+#define INTR3		0x00000800
+#define INTR4		0x00001000
+#define INTR5		0x00002000
+#define INTR6		0x00004000
+#define INTR7		0x00008000
+//#define DE		0x00010000	/* not on 24k */
+#define TS		0x00200000	/* tlb shutdown; on 24k at least */
+#define BEV		0x00400000	/* bootstrap exception vectors */
+#define RE		0x02000000	/* reverse-endian in user mode */
+#define FR		0x04000000	/* enable 32 FP regs */
+#define CU0		0x10000000
+#define CU1		0x20000000	/* FPU enable */
+
+/*
+ * M(CONFIG) bits
+ */
+
+#define CFG_K0		7	/* kseg0 cachability */
+#define CFG_MM		(1<<18)	/* write-through merging enabled */
+
+/*
+ * M(CAUSE) bits
+ */
+
+#define BD		(1<<31)	/* last excep'n occurred in branch delay slot */
+
+/*
+ * Exception codes
+ */
+#define	EXCMASK	0x1f		/* mask of all causes */
+#define	CINT	 0		/* external interrupt */
+#define	CTLBM	 1		/* TLB modification: store to unwritable page */
+#define	CTLBL	 2		/* TLB miss (load or fetch) */
+#define	CTLBS	 3		/* TLB miss (store) */
+#define	CADREL	 4		/* address error (load or fetch) */
+#define	CADRES	 5		/* address error (store) */
+#define	CBUSI	 6		/* bus error (fetch) */
+#define	CBUSD	 7		/* bus error (data load or store) */
+#define	CSYS	 8		/* system call */
+#define	CBRK	 9		/* breakpoint */
+#define	CRES	10		/* reserved instruction */
+#define	CCPU	11		/* coprocessor unusable */
+#define	COVF	12		/* arithmetic overflow */
+#define	CTRAP	13		/* trap */
+#define	CVCEI	14		/* virtual coherence exception (instruction) */
+#define	CFPE	15		/* floating point exception */
+#define CTLBRI	19		/* tlb read-inhibit */
+#define CTLBXI	20		/* tlb execute-inhibit */
+#define	CWATCH	23		/* watch exception */
+#define CMCHK	24		/* machine checkcore */
+#define CCACHERR 30		/* cache error */
+#define	CVCED	31		/* virtual coherence exception (data) */
+
+/*
+ * M(CACHEECC) a.k.a. ErrCtl bits
+ */
+#define PE	(1<<31)
+#define LBE	(1<<25)
+#define WABE	(1<<24)
+
+/*
+ * Trap vectors
+ */
+
+#define	UTLBMISS	(KSEG0+0x000)
+#define	XEXCEPTION	(KSEG0+0x080)
+#define	CACHETRAP	(KSEG0+0x100)
+#define	EXCEPTION	(KSEG0+0x180)
+
+/*
+ * Magic registers
+ */
+
+#define	USER		24		/* R24 is up-> */
+#define	MACH		25		/* R25 is m-> */
+
+/*
+ * offsets in ureg.h for l.s
+ */
+#define	Ureg_status	(Uoffset+0)
+#define	Ureg_pc		(Uoffset+4)
+#define	Ureg_sp		(Uoffset+8)
+#define	Ureg_cause	(Uoffset+12)
+#define	Ureg_badvaddr	(Uoffset+16)
+#define	Ureg_tlbvirt	(Uoffset+20)
+
+#define	Ureg_hi		(Uoffset+24)
+#define	Ureg_lo		(Uoffset+28)
+#define	Ureg_r31	(Uoffset+32)
+#define	Ureg_r30	(Uoffset+36)
+#define	Ureg_r28	(Uoffset+40)
+#define	Ureg_r27	(Uoffset+44)
+#define	Ureg_r26	(Uoffset+48)
+#define	Ureg_r25	(Uoffset+52)
+#define	Ureg_r24	(Uoffset+56)
+#define	Ureg_r23	(Uoffset+60)
+#define	Ureg_r22	(Uoffset+64)
+#define	Ureg_r21	(Uoffset+68)
+#define	Ureg_r20	(Uoffset+72)
+#define	Ureg_r19	(Uoffset+76)
+#define	Ureg_r18	(Uoffset+80)
+#define	Ureg_r17	(Uoffset+84)
+#define	Ureg_r16	(Uoffset+88)
+#define	Ureg_r15	(Uoffset+92)
+#define	Ureg_r14	(Uoffset+96)
+#define	Ureg_r13	(Uoffset+100)
+#define	Ureg_r12	(Uoffset+104)
+#define	Ureg_r11	(Uoffset+108)
+#define	Ureg_r10	(Uoffset+112)
+#define	Ureg_r9		(Uoffset+116)
+#define	Ureg_r8		(Uoffset+120)
+#define	Ureg_r7		(Uoffset+124)
+#define	Ureg_r6		(Uoffset+128)
+#define	Ureg_r5		(Uoffset+132)
+#define	Ureg_r4		(Uoffset+136)
+#define	Ureg_r3		(Uoffset+140)
+#define	Ureg_r2		(Uoffset+144)
+#define	Ureg_r1		(Uoffset+148)
+
+/* ch and carrera used these defs */
+	/* Sizeof(Ureg) + (R5,R6) + 16 bytes slop + retpc + ur */
+// #define UREGSIZE ((Ureg_r1+4-Uoffset) + 2*BY2V + 16 + BY2WD + BY2WD)
+// #define Uoffset	8
+
+// #define UREGSIZE	(Ureg_r1 + 4 - Uoffset)	/* this ought to work */
+#define UREGSIZE ((Ureg_r1+4-Uoffset) + 2*BY2V + 16 + BY2WD + BY2WD)
+#define Uoffset		0
+#define Notuoffset	8
+
+/*
+ * MMU
+ */
+#define	PGSZ4K		(0x00<<13)
+#define PGSZ16K		(0x03<<13)	/* on 24k */
+#define	PGSZ64K		(0x0F<<13)
+#define	PGSZ256K	(0x3F<<13)
+#define	PGSZ1M		(0xFF<<13)
+#define	PGSZ4M		(0x3FF<<13)
+// #define PGSZ8M	(0x7FF<<13)	/* not on 24k */
+#define	PGSZ16M		(0xFFF<<13)
+#define PGSZ64M		(0x3FFF<<13)	/* on 24k */
+#define PGSZ256M	(0xFFFF<<13)	/* on 24k */
+
+/* mips address spaces, tlb-mapped unless marked otherwise */
+#define	KUSEG	0x00000000	/* user process */
+#define KSEG0	0x80000000	/* kernel (direct mapped, cached) */
+#define KSEG1	0xA0000000	/* kernel (direct mapped, uncached: i/o) */
+#define	KSEG2	0xC0000000	/* kernel, used for TSTKTOP */
+#define	KSEG3	0xE0000000	/* kernel, used by kmap */
+#define	KSEGM	0xE0000000	/* mask to check which seg */
+
+/*
+ * Fundamental addresses
+ */
+
+#define	REBOOTADDR	KADDR(0x1000)	/* just above vectors */
+#define	MACHADDR	0x80005000	/* Mach structures */
+#define	MACHP(n)	((Mach *)(MACHADDR+(n)*MACHSIZE))
+#define ROM		0xbfc00000
+#define	KMAPADDR	0xE0000000	/* kmap'd addresses */
+#define	WIREDADDR	0xE2000000	/* address wired kernel space */
+
+#define PHYSCONS	(KSEG1|0x18020000)		/* i8250 uart */
+
+#define PIDXSHFT	12
+#ifndef BIGPAGES
+#define NCOLOR		8
+#define PIDX		((NCOLOR-1)<<PIDXSHFT)
+#define getpgcolor(a)	(((ulong)(a)>>PIDXSHFT) % NCOLOR)
+#else
+/* no cache aliases are possible with pages of 16K or larger */
+#define NCOLOR		1
+#define PIDX		0
+#define getpgcolor(a)	0
+#endif
+#define KMAPSHIFT	15
+
+#define	PTEGLOBL	(1<<0)
+#define	PTEVALID	(1<<1)
+#define	PTEWRITE	(1<<2)
+#define PTERONLY	0
+#define PTEALGMASK	(7<<3)
+#define PTENONCOHERWT	(0<<3)		/* cached, write-through (slower) */
+#define PTEUNCACHED	(2<<3)
+#define PTENONCOHERWB	(3<<3)		/* cached, write-back */
+#define PTEUNCACHEDACC	(7<<3)
+/* rest are reserved on 24k */
+#define PTECOHERXCL	(4<<3)
+#define PTECOHERXCLW	(5<<3)
+#define PTECOHERUPDW	(6<<3)
+
+/* how much faster is it? mflops goes from about .206 (WT) to .37 (WB) */
+#define PTECACHABILITY PTENONCOHERWT	/* 24k erratum 48 disallows WB */
+// #define PTECACHABILITY PTENONCOHERWB
+
+#define	PTEPID(n)	(n)
+#define PTEMAPMEM	(1024*1024)
+#define	PTEPERTAB	(PTEMAPMEM/BY2PG)
+#define SEGMAPSIZE	512
+#define SSEGMAPSIZE	16
+
+#define STLBLOG		15
+#define STLBSIZE	(1<<STLBLOG)	/* entries in the soft TLB */
+/* page # bits that don't fit in STLBLOG bits */
+#define HIPFNBITS	(BI2WD - (PGSHIFT+1) - STLBLOG)
+#define KPTELOG		8
+#define KPTESIZE	(1<<KPTELOG)	/* entries in the kfault soft TLB */
+
+#define TLBPID(n) ((n)&0xFF)
+#define	NTLBPID	256		/* # of pids (affects size of Mach) */
+#define	NTLB	16		/* # of entries (mips 24k) */
+#define TLBOFF	1		/* first tlb entry (0 used within mmuswitch) */
+#define NKTLB	2		/* # of initial kfault tlb entries */
+#define WTLBOFF	(TLBOFF+NKTLB)	/* first large IO window tlb entry */
+#define NWTLB	0		/* # of large IO window tlb entries */
+#define	TLBROFF	(WTLBOFF+NWTLB)	/* offset of first randomly-indexed entry */
+
+/*
+ * Address spaces
+ */
+#define	UZERO	KUSEG			/* base of user address space */
+#define	UTZERO	(UZERO+MAXBY2PG)	/* 1st user text address; see mkfile */
+#define	USTKTOP	(KZERO-BY2PG)		/* byte just beyond user stack */
+#define	USTKSIZE (8*1024*1024)		/* size of user stack */
+#define TSTKTOP (KSEG2+USTKSIZE-BY2PG)	/* top of temporary stack */
+#define TSTKSIZ (1024*1024/BY2PG)	/* can be at most UTSKSIZE/BY2PG */
+#define	KZERO	KSEG0			/* base of kernel address space */
+#define	KTZERO	(KZERO+0x20000)		/* first address in kernel text */
+#define MEMSIZE	(256*MB)		/* fixed memory on routerboard */
+#define PCIMEM	0x10000000		/* on rb450g */

+ 84 - 0
sys/src/9/rb/mips.s

@@ -0,0 +1,84 @@
+/*
+ * mips 24k machine assist
+ */
+#undef	MASK
+#define	MASK(w) ((1<<(w))-1)
+
+#define	SP	R29
+
+#define NOP	NOR R0, R0, R0
+
+#define	CONST(x,r) MOVW $((x)&0xffff0000), r; OR  $((x)&0xffff), r
+
+/* a mips 24k erratum requires a NOP after; experience dictates EHB before */
+#define	ERET	EHB; WORD $0x42000018; NOP
+
+#define RETURN	RET; NOP
+
+/*
+ *  R4000 instructions
+ */
+#define	LL(base, rt)	WORD	$((060<<26)|((base)<<21)|((rt)<<16))
+#define	SC(base, rt)	WORD	$((070<<26)|((base)<<21)|((rt)<<16))
+
+/* new instructions in mips 24k (mips32r2) */
+#define DI(rt)	WORD $(0x41606000|((rt)<<16))	/* interrupts off */
+#define EI(rt)	WORD $(0x41606020|((rt)<<16))	/* interrupts on */
+#define EHB	WORD $0xc0
+/* jalr with hazard barrier, link in R22 */
+#define JALRHB(r) WORD $(((r)<<21)|(22<<11)|(1<<10)|9); NOP
+/* jump register with hazard barrier */
+#define JRHB(r)	WORD $(((r)<<21)|(1<<10)|8); NOP
+#define MFC0(src,sel,dst) WORD $(0x40000000|((src)<<11)|((dst)<<16)|(sel))
+#define MTC0(src,dst,sel) WORD $(0x40800000|((dst)<<11)|((src)<<16)|(sel))
+#define MIPS24KNOP NOP				/* for erratum #48 */
+#define RDHWR(hwr, r)	WORD $(0x7c00003b|((hwr)<<11)|((r)<<16))
+#define SYNC	WORD $0xf			/* all sync barriers */
+#define WAIT	WORD $0x42000020		/* wait for interrupt */
+
+/* all barriers, clears all hazards; clobbers r/Reg and R22 */
+#define BARRIERS(r, Reg, label) \
+	SYNC; EHB; MOVW $ret(SB), Reg; JALRHB(r)
+/* same but return to KSEG1 */
+#define UBARRIERS(r, Reg, label) \
+	SYNC; EHB; MOVW $ret(SB), Reg; OR $KSEG1, Reg; JALRHB(r)
+
+/* alternative definitions using labels */
+#ifdef notdef
+/* all barriers, clears all hazards; clobbers r/Reg */
+#define BARRIERS(r, Reg, label) \
+	SYNC; EHB; \
+	MOVW	$label(SB), Reg; \
+	JRHB(r); \
+TEXT label(SB), $-4; \
+	NOP
+#define UBARRIERS(r, Reg, label) \
+	SYNC; EHB; \
+	MOVW	$label(SB), Reg; \
+	OR	$KSEG1, Reg; \
+	JRHB(r); \
+TEXT label(SB), $-4; \
+	NOP
+#endif
+
+#define PUTC(c, r1, r2)	CONST(PHYSCONS, r1); MOVW $(c), r2; MOVW r2, (r1); NOP
+
+/*
+ *  cache manipulation
+ */
+
+#define	CACHE	BREAK		/* overloaded op-code */
+
+#define	PI	R((0		/* primary I cache */
+#define	PD	R((1		/* primary D cache */
+#define	TD	R((2		/* tertiary I/D cache */
+#define	SD	R((3		/* secondary combined I/D cache */
+
+#define	IWBI	(0<<2)))	/* index write-back invalidate */
+#define	ILT	(1<<2)))	/* index load tag */
+#define	IST	(2<<2)))	/* index store tag */
+/* #define CDE	(3<<2)))	/* create dirty exclusive */
+#define	HINV	(4<<2)))	/* hit invalidate */
+#define	HWBI	(5<<2)))	/* hit write back invalidate */
+#define	HWB	(6<<2)))	/* hit write back */
+/* #define HSV	(7<<2)))	/* hit set virtual */

+ 131 - 0
sys/src/9/rb/mkfile

@@ -0,0 +1,131 @@
+CONF=rb
+CONFLIST=rb
+# no rb with nvram on bovril (outside)
+EXTRACOPIES=piestand lookout boundary 
+
+objtype=mips
+</$objtype/mkfile
+p=9
+# must match mem.h
+KTZERO=0x80020000
+PHYSKTZERO=0x20000
+# must match mem.h
+UTZERO=0x4020
+# must match mem.h
+BY2PG=4096
+# must match mem.h
+MAXBY2PG=16384
+
+# CFLAGS=$CFLAGS -DFPEMUDEBUG
+
+DEVS=`{rc ../port/mkdevlist $CONF}
+
+PORT=\
+	alarm.$O\
+	alloc.$O\
+	allocb.$O\
+	auth.$O\
+	cache.$O\
+	chan.$O\
+	dev.$O\
+	edf.$O\
+	fault.$O\
+	latin1.$O\
+	mul64fract.$O\
+	page.$O\
+	parse.$O\
+	pgrp.$O\
+	portclock.$O\
+	print.$O\
+	proc.$O\
+	qio.$O\
+	qlock.$O\
+	rdb.$O\
+	rebootcmd.$O\
+	segment.$O\
+	swap.$O\
+	sysfile.$O\
+	sysproc.$O\
+	taslock.$O\
+	tod.$O\
+	xalloc.$O\
+
+OBJ=\
+	l.$O\
+	clock.$O\
+	faultmips.$O\
+	main.$O\
+	mmu.$O\
+	random.$O\
+	syscallfmt.$O\
+	trap.$O\
+	$CONF.root.$O\
+	$CONF.rootc.$O\
+	$DEVS\
+	$PORT\
+
+LIB=\
+	/mips/lib/libauth.a\
+	/mips/lib/libsec.a\
+	/mips/lib/libmp.a\
+	/mips/lib/libip.a\
+	/mips/lib/libc.a\
+
+# generate ELF format for pxe booting via RouterBOOT
+$p$CONF:Q:	$OBJ $CONF.c $LIB
+	$CC $CFLAGS '-DKERNDATE='`{date -n} $CONF.c
+	echo linking kernel
+	# -a for debugging
+	$LD -a -o $target -H5 -l -R$BY2PG -T$KTZERO -P$PHYSKTZERO -S $OBJ $CONF.$O $LIB >$target.list
+	# -H1 is plan 9 boot image with rounded segments
+	# $LD -o $target -H1 -l -R$BY2PG -T$KTZERO $OBJ $CONF.$O $LIB
+	size $target
+
+install:V:	$p$CONF
+	cp $p$CONF /$objtype/
+	echo installed.
+	for(i in $EXTRACOPIES)
+		{ 9fs $i && cp $p$CONF /n/$i/$objtype/ && echo -n $i... & }
+	wait
+	echo
+
+<../boot/bootmkfile
+<../port/portmkfile
+<|../port/mkbootrules $CONF
+
+init.h:	init9.s ../port/initcode.c /sys/src/libc/9syscall/sys.h
+	va init9.s
+	vc ../port/initcode.c
+	vl -T$UTZERO -R4 -o init.out init9.$O initcode.$O
+	{echo 'uchar initcode[]={'
+	 xd -r -1x init.out |
+		sed -e 's/^[0-9a-f]+ //' -e 's/ ([0-9a-f][0-9a-f])/0x\1,/g'
+	 echo '};'} > init.h
+
+reboot.h:D:	initreboot.s rebootcode.c mem.h
+	$AS initreboot.s
+	$CC -FTVw rebootcode.c
+	# -lc is only for memmove.  -T arg is REBOOTADDR.
+	$LD -l -a -s -T0x80001000 -R4 -o reboot.out initreboot.$O rebootcode.$O -lc >reboot.list
+	{echo 'uchar rebootcode[]={'
+	 xd -1x reboot.out |
+		sed -e '1,2d' -e 's/^[0-9a-f]+ //' -e 's/ ([0-9a-f][0-9a-f])/0x\1,/g'
+	 echo '};'} > reboot.h
+
+l.$O: mips.s
+arch.$O clock.$O fpimips.$O faultmips.$O mmu.$O syscall.$O \
+	trap.$O: /$objtype/include/ureg.h
+main.$O:	/$objtype/include/ureg.h errstr.h init.h reboot.h
+fpi.$O fpiarm.$O fpimem.$O: ../port/fpi.h
+devether.$O: 		../port/netif.h etherif.h
+
+%.clean:V:
+	rm -f $stem.c [9bz]$stem [9bz]$stem.gz boot$stem.* reboot.h apbootstrap.h init.h *.list
+
+# override ../port/portmkfile
+# create /boot/boot
+boot$CONF.out: $CONF print.$O $BOOTDIR/boot.c $BOOTLIB
+	$BOOTDIR/mkboot $CONF > boot$CONF.c
+	$CC $CFLAGS boot$CONF.c ../boot/printstub.c
+	$AS c_fcr0.s
+	$LD -o boot$CONF.out -T$UTZERO -R$MAXBY2PG boot$CONF.$O $BOOTLIB printstub.$O c_fcr0.$O

+ 540 - 0
sys/src/9/rb/mmu.c

@@ -0,0 +1,540 @@
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"ureg.h"
+
+/*
+ *  tlb entry 0 is used only by mmuswitch() to set the current tlb pid.
+ *
+ *  It is apparently assumed that user tlb entries are not
+ *  overwritten during start-up, so ...
+ *  During system start-up (before up first becomes non-nil),
+ *  Kmap entries start at tlb index 1 and work their way up until
+ *  kmapinval() removes them.  They then restart at 1.  As long as there
+ *  are few kmap entries they will not pass tlbroff (the WIRED tlb entry
+ *  limit) and interfere with user tlb entries.
+ *  Once start-up is over, we combine the kernel and user tlb pools into one,
+ *  in the hope of making better use of the tlb on systems with small ones.
+ *
+ *  All invalidations of the tlb are via indexed entries.  The virtual
+ *  address used is always 'KZERO | (x<<(PGSHIFT+1) | currentpid' where
+ *  'x' is the index into the tlb.  This ensures that the current pid doesn't
+ *  change and that no two invalidated entries have matching virtual
+ *  addresses just in case SGI/MIPS ever makes a chip that cares (as
+ *  they keep threatening).  These entries should never be used in
+ *  lookups since accesses to KZERO addresses don't go through the tlb
+ *  (actually only true of KSEG0 and KSEG1; KSEG2 and KSEG3 do go
+ *  through the tlb).
+ */
+
+#define TLBINVAL(x, pid) puttlbx(x, KZERO|((x)<<(PGSHIFT+1))|(pid), 0, 0, PGSZ)
+
+enum {
+	Debugswitch	= 0,
+	Debughash	= 0,
+};
+
+static ulong ktime[8];		/* only for first 8 cpus */
+
+void
+tlbinit(void)
+{
+	int i;
+
+	for(i=0; i<NTLB; i++)
+		TLBINVAL(i, 0);
+}
+
+Lock	kmaplock;
+KMap	kpte[KPTESIZE];
+KMap*	kmapfree;
+
+static int minfree = KPTESIZE;
+static int lastfree;
+static int tlbroff = TLBROFF;
+
+static void
+nfree(void)
+{
+	int i;
+	KMap *k;
+
+	i = 0;
+	for(k=kmapfree; k; k=k->next)
+		i++;
+	if(i<minfree){
+		iprint("%d free\n", i);
+		minfree = i;
+	}
+	lastfree = i;
+}
+
+void
+kmapinit(void)
+{
+	KMap *k, *klast;
+
+	lock(&kmaplock);
+	kmapfree = kpte;
+	klast = &kpte[KPTESIZE-1];
+	for(k=kpte; k<klast; k++)
+		k->next = k+1;
+	k->next = 0;
+	unlock(&kmaplock);
+
+	m->ktlbnext = TLBOFF;
+}
+
+void
+kmapdump(void)
+{
+	int i;
+
+	for(i=0; i<KPTESIZE; i++)
+		iprint("%d: %lud pc=%#lux\n", i, kpte[i].ref, kpte[i].pc);
+}
+
+static int
+putktlb(KMap *k)
+{
+	int x;
+	ulong virt;
+	ulong tlbent[3];
+
+	virt = k->virt & ~BY2PG | TLBPID(tlbvirt());
+	x = gettlbp(virt, tlbent);
+	if (!m->paststartup)
+		if (up) {			/* startup just ended? */
+			tlbroff = 1;
+			setwired(tlbroff);	/* share all-but-one entries */
+			m->paststartup = 1;
+		} else if (x < 0) {		/* no such entry? use next */
+			x = m->ktlbnext++;
+			if(m->ktlbnext >= tlbroff)
+				m->ktlbnext = TLBOFF;
+		}
+	if (x < 0)		/* no entry for va? overwrite random one */
+		x = puttlb(virt, k->phys0, k->phys1);
+	else
+		puttlbx(x, virt, k->phys0, k->phys1, PGSZ);
+	m->ktlbx[x] = 1;
+	return x;
+}
+
+/*
+ *  Arrange that the KMap'd virtual address will hit the same
+ *  primary cache line as pg->va by making bits 14...12 of the
+ *  tag the same as virtual address.  These bits are the index
+ *  into the primary cache and are checked whenever accessing
+ *  the secondary cache through the primary.  Violation causes
+ *  a VCE trap.
+ */
+KMap *
+kmap(Page *pg)
+{
+	int s, printed = 0;
+	ulong pte, virt;
+	KMap *k;
+
+	s = splhi();
+	lock(&kmaplock);
+
+	if(kmapfree == 0) {
+retry:
+		unlock(&kmaplock);
+		kmapinval();		/* try and free some */
+		lock(&kmaplock);
+		if(kmapfree == 0){
+			unlock(&kmaplock);
+			splx(s);
+			if(printed++ == 0){
+			/* using iprint here we get mixed up with other prints */
+				print("%d KMAP RETRY %#lux ktime %ld %ld %ld %ld %ld %ld %ld %ld\n",
+					m->machno, getcallerpc(&pg),
+					ktime[0], ktime[1], ktime[2], ktime[3],
+					ktime[4], ktime[5], ktime[6], ktime[7]);
+				delay(200);
+			}
+			splhi();
+			lock(&kmaplock);
+			goto retry;
+		}
+	}
+
+	k = kmapfree;
+	kmapfree = k->next;
+
+	k->pg = pg;
+	/*
+	 * One for the allocation,
+	 * One for kactive
+	 */
+	k->pc = getcallerpc(&pg);
+	k->ref = 2;
+	k->konmach[m->machno] = m->kactive;
+	m->kactive = k;
+
+	virt = pg->va;
+	/* bits 14..12 form the secondary-cache virtual index */
+	virt &= PIDX;
+	virt |= KMAPADDR | ((k-kpte)<<KMAPSHIFT);
+
+	k->virt = virt;
+	pte = PPN(pg->pa)|PTECACHABILITY|PTEGLOBL|PTEWRITE|PTEVALID;
+	if(virt & BY2PG) {
+		k->phys0 = PTEGLOBL | PTECACHABILITY;
+		k->phys1 = pte;
+	}
+	else {
+		k->phys0 = pte;
+		k->phys1 = PTEGLOBL | PTECACHABILITY;
+	}
+
+	putktlb(k);
+	unlock(&kmaplock);
+
+	splx(s);
+	return k;
+}
+
+void
+kunmap(KMap *k)
+{
+	int s;
+
+	s = splhi();
+	if(decref(k) == 0) {
+		k->virt = 0;
+		k->phys0 = 0;
+		k->phys1 = 0;
+		k->pg = 0;
+
+		lock(&kmaplock);
+		k->next = kmapfree;
+		kmapfree = k;
+//nfree();
+		unlock(&kmaplock);
+	}
+	splx(s);
+}
+
+void
+kfault(Ureg *ur)			/* called from trap() */
+{
+	ulong index, addr;
+	KMap *k, *f;
+
+	addr = ur->badvaddr;
+	index = (addr & ~KSEGM) >> KMAPSHIFT;
+	if(index >= KPTESIZE)
+		panic("kmapfault: %#lux", addr);
+
+	k = &kpte[index];
+	if(k->virt == 0)
+		panic("kmapfault: unmapped %#lux", addr);
+
+	for(f = m->kactive; f; f = f->konmach[m->machno])
+		if(f == k)
+			break;
+	if(f == 0) {
+		incref(k);
+		k->konmach[m->machno] = m->kactive;
+		m->kactive = k;
+	}
+	putktlb(k);
+}
+
+void
+kmapinval(void)
+{
+	int mno, i, curpid;
+	KMap *k, *next;
+	uchar *ktlbx;
+
+	if(m->machno < nelem(ktime))
+		ktime[m->machno] = MACHP(0)->ticks;
+	if(m->kactive == 0)
+		return;
+
+	curpid = PTEPID(TLBPID(tlbvirt()));
+	ktlbx = m->ktlbx;
+	for(i = 0; i < NTLB; i++, ktlbx++){
+		if(*ktlbx == 0)
+			continue;
+		TLBINVAL(i, curpid);
+		*ktlbx = 0;
+	}
+
+	mno = m->machno;
+	for(k = m->kactive; k; k = next) {
+		next = k->konmach[mno];
+		kunmap(k);
+	}
+
+	m->kactive = 0;
+	m->ktlbnext = TLBOFF;
+}
+
+struct
+{
+	ulong	va;
+	ulong	pl;
+	ulong	ph;
+} wired[NWTLB+1];		/* +1 to avoid zero size if NWTLB==0 */
+// = {
+//	PCIMEM,
+//	 (PCIMEM>>6) | PTEUNCACHED|PTEGLOBL|PTEWRITE|PTEVALID,
+//	((PCIMEM>>6) | PTEUNCACHED|PTEGLOBL|PTEWRITE|PTEVALID)+(1<<(PGSHIFT-6)),
+//};
+
+/*
+ * allocate a virtual address corresponding to physical addr and map them.
+ * run on cpu 0.
+ */
+ulong
+wiredpte(vlong addr)
+{
+	int i;
+	ulong va;
+
+	for(i = 0; i < NWTLB; i++)
+		if(wired[i].va == 0)
+			break;
+	if(i >= NWTLB)
+		panic("wiredpte: not enough wired TLB entries");
+
+	va = WIREDADDR + i*256*MB;
+	wired[i].va = va;
+	wired[i].pl = (addr >> 6) | PTEUNCACHED|PTEGLOBL|PTEWRITE|PTEVALID;
+	wired[i].ph = wired[i].pl + (1<<(PGSHIFT-6));
+
+	puttlbx(i+WTLBOFF, va, wired[i].pl, wired[i].ph, PGSZ256M);
+	return va;
+}
+
+void
+machwire(void)
+{
+	int i;
+
+	if(m->machno == 0)
+		return;
+	for(i = 0; i < NWTLB; i++)
+		if(wired[i].va)
+			puttlbx(i+WTLBOFF, wired[i].va, wired[i].pl,
+				wired[i].ph, PGSZ256M);
+}
+
+void
+mmuswitch(Proc *p)
+{
+	int tp;
+	static char lasttext[32];
+
+	if(Debugswitch && !p->kp){
+		if(strncmp(lasttext, p->text, sizeof lasttext) != 0)
+			iprint("[%s]", p->text);
+		strncpy(lasttext, p->text, sizeof lasttext);
+	}
+
+	if(p->newtlb) {
+		memset(p->pidonmach, 0, sizeof p->pidonmach);
+		p->newtlb = 0;
+	}
+	tp = p->pidonmach[m->machno];
+	if(tp == 0)
+		tp = newtlbpid(p);
+	puttlbx(0, KZERO|PTEPID(tp), 0, 0, PGSZ);
+}
+
+void
+mmurelease(Proc *p)
+{
+	memset(p->pidonmach, 0, sizeof p->pidonmach);
+}
+
+/*
+ * Process must be splhi
+ */
+int
+newtlbpid(Proc *p)
+{
+	int i, s;
+	Proc **h;
+
+	i = m->lastpid;
+	h = m->pidproc;
+	for(s = 0; s < NTLBPID; s++) {
+		i++;
+		if(i >= NTLBPID)
+			i = 1;
+		if(h[i] == 0)
+			break;
+	}
+
+	if(h[i])
+		purgetlb(i);
+	if(h[i] != 0)
+		panic("newtlb");
+
+	m->pidproc[i] = p;
+	p->pidonmach[m->machno] = i;
+	m->lastpid = i;
+
+	return i;
+}
+
+void
+putmmu(ulong tlbvirt, ulong tlbphys, Page *pg)
+{
+	short tp;
+	char *ctl;
+	Softtlb *entry;
+	int s;
+
+	s = splhi();
+	tp = up->pidonmach[m->machno];
+	if(tp == 0)
+		tp = newtlbpid(up);
+
+	tlbvirt |= PTEPID(tp);
+	if((tlbphys & PTEALGMASK) != PTEUNCACHED) {
+		tlbphys &= ~PTEALGMASK;
+		tlbphys |= PTECACHABILITY;
+	}
+
+	entry = putstlb(tlbvirt, tlbphys);
+	puttlb(entry->virt, entry->phys0, entry->phys1);
+
+	ctl = &pg->cachectl[m->machno];
+	switch(*ctl) {
+	case PG_TXTFLUSH:
+		icflush((void*)pg->va, BY2PG);
+		*ctl = PG_NOFLUSH;
+		break;
+	case PG_DATFLUSH:
+		dcflush((void*)pg->va, BY2PG);
+		*ctl = PG_NOFLUSH;
+		break;
+	case PG_NEWCOL:
+		cleancache();		/* Too expensive */
+		*ctl = PG_NOFLUSH;
+		break;
+	}
+	splx(s);
+}
+
+void
+purgetlb(int pid)
+{
+	int i, mno;
+	Proc *sp, **pidproc;
+	Softtlb *entry, *etab;
+
+	m->tlbpurge++;
+
+	/*
+	 * find all pid entries that are no longer used by processes
+	 */
+	mno = m->machno;
+	pidproc = m->pidproc;
+	for(i=1; i<NTLBPID; i++) {
+		sp = pidproc[i];
+		if(sp && sp->pidonmach[mno] != i)
+			pidproc[i] = 0;
+	}
+
+	/*
+	 * shoot down the one we want
+	 */
+	sp = pidproc[pid];
+	if(sp != 0)
+		sp->pidonmach[mno] = 0;
+	pidproc[pid] = 0;
+
+	/*
+	 * clean out all dead pids from the stlb;
+	 */
+	entry = m->stb;
+	for(etab = &entry[STLBSIZE]; entry < etab; entry++)
+		if(pidproc[TLBPID(entry->virt)] == 0)
+			entry->virt = 0;
+
+	/*
+	 * clean up the hardware
+	 */
+	for(i=tlbroff; i<NTLB; i++)
+		if(pidproc[TLBPID(gettlbvirt(i))] == 0)
+			TLBINVAL(i, pid);
+}
+
+void
+flushmmu(void)
+{
+	int s;
+
+	s = splhi();
+	up->newtlb = 1;
+	mmuswitch(up);
+	splx(s);
+}
+
+/* tlbvirt also has TLBPID() in its low byte as the asid */
+Softtlb*
+putstlb(ulong tlbvirt, ulong tlbphys)
+{
+	int odd;
+	Softtlb *entry;
+
+	/* identical calculation in l.s/utlbmiss */
+	entry = &m->stb[stlbhash(tlbvirt)];
+	odd = tlbvirt & BY2PG;		/* even/odd bit */
+	tlbvirt &= ~BY2PG;		/* zero even/odd bit */
+	if(entry->virt != tlbvirt) {	/* not my entry? overwrite it */
+		if(entry->virt != 0) {
+			m->hashcoll++;
+			if (Debughash)
+				iprint("putstlb: hash collision: %#lx old virt "
+					"%#lux new virt %#lux page %#lux\n",
+					entry - m->stb, entry->virt, tlbvirt,
+					tlbvirt >> (PGSHIFT+1));
+		}
+		entry->virt = tlbvirt;
+		entry->phys0 = 0;
+		entry->phys1 = 0;
+	}
+
+	if(odd)
+		entry->phys1 = tlbphys;
+	else
+		entry->phys0 = tlbphys;
+
+	if(entry->phys0 == 0 && entry->phys1 == 0)
+		entry->virt = 0;
+
+	return entry;
+}
+
+void
+checkmmu(ulong, ulong)
+{
+}
+
+void
+countpagerefs(ulong*, int)
+{
+}
+
+/*
+ * Return the number of bytes that can be accessed via KADDR(pa).
+ * If pa is not a valid argument to KADDR, return 0.
+ */
+ulong
+cankaddr(ulong pa)
+{
+	if(pa >= KZERO || pa >= MEMSIZE)
+		return 0;
+	return MEMSIZE - pa;
+}

+ 189 - 0
sys/src/9/rb/notes/9rb.ms

@@ -0,0 +1,189 @@
+.FP palatino
+.TM
+.TL
+Plan 9 on the Mikrotik RB450G Routerboard
+.AU
+Geoff Collyer
+.AI
+.MH
+.NH 1
+Motivation
+.LP
+I ported Plan 9 to the Routerboard mainly to verify
+that Plan 9's MIPS-related code
+(compiler, assembler, loader,
+.CW libmach ,
+etc.) was still in working order and would
+work on newer machines than the 1993-era ones that we last owned
+(MIPS Magnum, SGI Challenge, Carrera and the like).
+The verdict is that,
+with a few surprising exceptions, the code still works on newish machines
+(the MIPS 24K CPU in the Routerboard dates to about 2003 originally;
+this revision is from about 2005).
+So we now have a
+machine on which to test MIPS executables.
+.LP
+The other reason I did the port was
+as an incremental step toward
+running Plan 9 on a MIPS64 machine (e.g., the dual-core, dual-issue
+Cavium CN5020 in the Ubiquiti Edgerouter Lite 3).
+.NH 1
+The new MIPS world
+.LP
+These newer MIPS systems are aimed at embedded applications, so they
+typically lack FPUs and may also lack L2 caches or have small TLBs;
+the MIPS 24K in the Atheros 7161 SoC lacks FPU and L2 cache, and has a
+16-entry TLB.
+It is a MIPS32R2 architecture system and lacks the 64-bit instructions
+of the R4000.
+These new MIPS systems are still big-endian,
+so provide a useful test case to expose byte-ordering bugs.
+.NH 1
+Plan 9 changes and additions
+.NH 2
+CPU Bug Workarounds
+.LP
+The Linux MIPS people cite MIPS 24K erratum 48:
+3 consecutive stores lose data.
+MIPS only distribute their errata lists under NDA and to their
+corporate partners, so we have only the Linux report to go on.
+The fix requires
+.I both
+write-through data cache and
+no more than two consecutive single-word stores in all executables.
+I have made a crude optional change to
+.I vl
+to generate a NOP before every third consecutive store.
+The fix could be better, in particular the technique for
+keeping stores out of branch delay slots.
+.NH 2
+Driver for Undocumented Ethernet Controller
+.LP
+The FreeBSD Atheros
+.I arge
+driver
+(in
+.CW /usr/src/sys/mips/atheros )
+provided inspiration for our Gigabit Ethernet driver, since the
+hardware is otherwise largely undocumented.
+I haven't got the second
+Ethernet controller entirely working yet;
+it's perhaps complicated by having a switch attached to it (the Atheros 8316).
+At minimum, it probably needs MII or PHY initialisation.
+.NH 2
+Floating-point Emulation
+.LP
+Floating-point emulation works but is
+.I very
+slow:
+.I astro
+takes about 8 seconds.
+I added an
+.CW fpemudebug
+command to
+.CW /dev/archctl ;
+it
+takes a number as argument corresponding to the
+.CW Dbg*
+bits in
+.CW fpimips.c ,
+but requires the kernel to be compiled with
+.CW FPEMUDEBUG
+defined.
+.NH 3
+\&... in Locking Code
+.LP
+The big surprises included that
+.CW /sys/src/libc/mips/lock.c
+read
+.CW FCR0
+to
+choose the locking style.
+That's been broken out into
+.CW c_fcr0.s
+so that we can change it, but the kernel also emulates the
+.CW MOVW
+.CW FCR0,R1
+(and via a fast code path), to keep alive the possibility of running
+old binaries from the dump.
+.NH 2
+No 64-bit Instructions
+.LP
+The other big surprise was that
+.CW /sys/src/libmp/mips/mpdigdiv.s
+used 64-bit instructions (SLLV, SRLV, ADDVU, DIVVU).
+For now I've resolved the problem by pushing it into a
+subdirectory (\c
+.CW r4k )
+and editing the
+.CW mkfile s
+to use the
+.CW port
+version
+(and similarly in APE).
+.br
+.ne 8
+.NH 2
+Page Size vs TLB Faults
+.LP
+I started out with a 4K page size and reduced the number of TLB
+entries reserved for the kernel to 2, leaving 14 for user programs,
+but
+.CW /dev/sysstat
+was reporting 6 times as many TLB faults as page
+faults, and the number increased at a furious rate.
+.LP
+So I switched to
+a 16K page size, adjusted
+.CW vl
+.CW -H2
+accordingly and recompiled the
+.CW /mips
+world.
+This reduced the TLB faults to just 10% more than the number of page faults.
+(That number is now around 15% more, due to a better soft-TLB hash function
+that makes the soft TLB more effective.)
+16K pages also produce consecutive (even recursive) page faults
+for the same address at the same PC
+and the system runs at about 10% of its normal speed,
+so 4K pages are currently the only sensible choice;
+we'll just live with the absurdly-high number of TLB faults
+(around 20k–30k per second).
+It probably doesn't help that one 16K page is half of the L1 data cache
+and one quarter of the L1 instruction cache.
+.LP
+Page size is controlled by
+.CW BIGPAGES
+in
+.CW mem.h .
+.NH 3
+Combined TLB Pool
+.LP
+I also changed
+.CW mmu.c
+to collapse the separate kernel and user TLB pools into one,
+once user processes start running,
+but that only helps to reduce TLB faults a little.
+.
+.br
+.ne 8
+.
+.NH 1
+Remaining Problems
+.LP
+Interrupt-driven UART output isn't quite right.
+It can get stuck and then input makes it resume.
+The UART is apparently connected via the APB and requires
+interrupt unmasking in the APB (which we now do).
+There's some kludgey stuff in
+.CW uarti8250.c
+that makes output work most of the time
+(characters do sometimes get dropped).
+.LP
+The Ethernet driver currently does not
+dig out the MAC addresses from the hardware,
+so you'll need to edit the
+.CW rb
+configuration file for each Routerboard; the format should be obvious.
+I don't have the stomach to dig the MAC address out of the hardware
+via SPI or whatever vile interface it requires.

+ 36 - 0
sys/src/9/rb/notes/erratum48

@@ -0,0 +1,36 @@
+/*
+ * 24K Erratum 48: Lost Data on Stores During Refill.
+
+  Problem: The FSB (fetch store buffer) acts as an intermediate buffer
+  for the data cache refills and store data. The following describes
+  the scenario where the store data could be lost.
+
+  * A data cache miss, due to either a load or a store, causing fill
+    data to be supplied by the memory subsystem
+  * The first three doublewords of fill data are returned and written
+    into the cache
+  * A sequence of four stores occurs in consecutive cycles around the
+    final doubleword of the fill:
+  * Store A
+  * Store B
+  * Store C
+  * Zero, One or more instructions
+  * Store D
+
+  The four stores A-D must be to different doublewords of the line that
+  is being filled. The fourth instruction in the sequence above permits
+  the fill of the final doubleword to be transferred from the FSB into
+  the cache. In the sequence above, the stores may be either integer
+  (sb, sh, sw, swr, swl, sc) or coprocessor (swc1/swc2, sdc1/sdc2,
+  swxc1, sdxc1, suxc1) stores, as long as the four stores are to
+  different doublewords on the line. If the floating point unit is
+  running in 1:2 mode, it is not possible to create the sequence above
+  using only floating point store instructions.  In this case, the cache
+  line being filled is incorrectly marked invalid, thereby losing the
+  data from any store to the line that occurs between the original miss
+  and the completion of the five cycle sequence shown above.
+
+  * Run the data cache in write-through mode.
+  * Insert a non-store instruction between
+    Store A and Store B or Store B and Store C.
+ */

+ 12 - 0
sys/src/9/rb/notes/pci.intr

@@ -0,0 +1,12 @@
+	/*
+	 * much of this is inspired by the freebsd driver,
+	 * but is hard to understand due to lack of documentation.
+	 *
+	 * only pci irqs 0-2 are valid.
+	 * 0 seems to yield INTR6.
+	 * pci irqs 1-2 yield spurious ether intrs.
+	 */
+	i = 0;
+	*Pciintrsts = 0;
+	*Pciintrmask = 1 << i;
+	junk = *Pciintrmask;		/* flush the write */

+ 58 - 0
sys/src/9/rb/rb

@@ -0,0 +1,58 @@
+# routerboard rb450g
+dev
+	root
+	cons
+	arch
+	env
+	pipe
+	proc
+	mnt
+	srv
+	dup
+#	rtc
+	ssl
+	tls
+	cap
+	kprof
+	fs
+
+	ether		netif
+	ip		arp chandial inferno ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum
+
+	uart
+link
+	loopbackmedium
+	ethermedium
+
+misc
+	uarti8250
+# emulated fp
+	fpi
+	fpimips
+	fpimem
+	ethermii
+
+ip
+	tcp
+	udp
+	ipifc
+	icmp
+	icmp6
+	gre
+	ipmux
+	esp
+
+port
+	int cpuserver = 1;
+	uchar arge0mac[] = { 0xd4, 0xca, 0x6d, 0x7d, 0xf1, 0xce, };
+	uchar arge1mac[] = { 0xd4, 0xca, 0x6d, 0x7d, 0xf1, 0xcf, };
+};
+
+boot cpu
+	tcp
+
+bootdir
+	boot$CONF.out boot
+	/mips/bin/ip/ipconfig
+	/mips/bin/auth/factotum
+	nvram

+ 81 - 0
sys/src/9/rb/rebootcode.c

@@ -0,0 +1,81 @@
+/*
+ * mips reboot trampoline code
+ */
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"io.h"
+
+#define csr8r(r)	(((ulong *)PHYSCONS)[r])
+#define csr8o(r, v)	(((ulong *)PHYSCONS)[r] = (v))
+
+enum {					/* i8250 registers */
+	Thr		= 0,		/* Transmitter Holding (WO) */
+	Lsr		= 5,		/* Line Status */
+};
+enum {					/* Lsr */
+	Thre		= 0x20,		/* Thr Empty */
+};
+
+void	putc(int);
+
+/*
+ * Copy the new kernel to its correct location in physical memory,
+ * flush caches, ignore TLBs (we're in KSEG0 space), and jump to
+ * the start of the kernel.
+ */
+void
+main(ulong aentry, ulong acode, ulong asize)
+{
+	void (*kernel)(void);
+	static ulong entry, code, size;
+
+	putc('B'); putc('o'); putc('o'); putc('t');
+	/* copy args to heap before moving stack to before a.out header */
+	entry = aentry;
+	code = acode;
+	size = asize;
+	setsp(entry-0x20-4);
+
+	memmove((void *)entry, (void *)code, size);
+
+	cleancache();
+	coherence();
+
+	/*
+	 * jump to kernel entry point.
+	 */
+	putc(' ');
+	kernel = (void*)entry;
+	(*kernel)();			/* off we go - never to return */
+
+	putc('?');
+	putc('!');
+	for(;;)
+		;
+}
+
+void
+putc(int c)
+{
+	int i;
+
+	for(i = 0; !(csr8r(Lsr) & Thre) && i < 1000000; i++)
+		;
+	csr8o(Thr, (uchar)c);
+	for(i = 0; !(csr8r(Lsr) & Thre) && i < 1000000; i++)
+		;
+}
+
+long
+syscall(Ureg*)
+{
+	return -1;
+}
+
+void
+trap(Ureg *)
+{
+}

+ 1075 - 0
sys/src/9/rb/trap.c

@@ -0,0 +1,1075 @@
+/*
+ * traps, exceptions, faults and interrupts on ar7161
+ */
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"ureg.h"
+#include	"io.h"
+#include	<tos.h>
+#include	"../port/error.h"
+
+#define setstatus(v)	/* experiment: delete this to enable recursive traps */
+
+typedef struct Handler Handler;
+
+struct Handler {
+	void	(*handler)(void *);
+	void	*arg;
+	Handler	*next;			/* at this interrupt level */
+	ulong	intrs;
+};
+
+ulong offintrs;
+ulong intrcauses[ILmax+1];
+
+int	intr(Ureg*);
+void	kernfault(Ureg*, int);
+void	noted(Ureg*, Ureg**, ulong);
+void	rfnote(Ureg**);
+
+char *excname[] =
+{
+	"trap: external interrupt",
+	"trap: TLB modification (store to unwritable)",
+	"trap: TLB miss (load or fetch)",
+	"trap: TLB miss (store)",
+	"trap: address error (load or fetch)",
+	"trap: address error (store)",
+	"trap: bus error (fetch)",
+	"trap: bus error (data load or store)",
+	"trap: system call",
+	"breakpoint",
+	"trap: reserved instruction",
+	"trap: coprocessor unusable",
+	"trap: arithmetic overflow",
+	"trap: TRAP exception",
+	"trap: VCE (instruction)",
+	"trap: floating-point exception",
+	"trap: coprocessor 2 implementation-specific", /* used as sys call for debugger */
+	"trap: corextend unusable",
+	"trap: precise coprocessor 2 exception",
+	"trap: TLB read-inhibit",
+	"trap: TLB execute-inhibit",
+	"trap: undefined 21",
+	"trap: undefined 22",
+	"trap: WATCH exception",
+	"trap: machine checkcore",
+	"trap: undefined 25",
+	"trap: undefined 26",
+	"trap: undefined 27",
+	"trap: undefined 28",
+	"trap: undefined 29",
+	"trap: cache error",
+	"trap: VCE (data)",
+};
+
+char *fpcause[] =
+{
+	"inexact operation",
+	"underflow",
+	"overflow",
+	"division by zero",
+	"invalid operation",
+};
+char	*fpexcname(Ureg*, ulong, char*, uint);
+#define FPEXPMASK	(0x3f<<12)	/* Floating exception bits in fcr31 */
+
+struct {
+	char	*name;
+	uint	off;
+} regname[] = {
+	"STATUS", Ureg_status,
+	"PC",	Ureg_pc,
+	"SP",	Ureg_sp,
+	"CAUSE",Ureg_cause,
+	"BADADDR", Ureg_badvaddr,
+	"TLBVIRT", Ureg_tlbvirt,
+	"HI",	Ureg_hi,
+	"LO",	Ureg_lo,
+	"R31",	Ureg_r31,
+	"R30",	Ureg_r30,
+	"R28",	Ureg_r28,
+	"R27",	Ureg_r27,
+	"R26",	Ureg_r26,
+	"R25",	Ureg_r25,
+	"R24",	Ureg_r24,
+	"R23",	Ureg_r23,
+	"R22",	Ureg_r22,
+	"R21",	Ureg_r21,
+	"R20",	Ureg_r20,
+	"R19",	Ureg_r19,
+	"R18",	Ureg_r18,
+	"R17",	Ureg_r17,
+	"R16",	Ureg_r16,
+	"R15",	Ureg_r15,
+	"R14",	Ureg_r14,
+	"R13",	Ureg_r13,
+	"R12",	Ureg_r12,
+	"R11",	Ureg_r11,
+	"R10",	Ureg_r10,
+	"R9",	Ureg_r9,
+	"R8",	Ureg_r8,
+	"R7",	Ureg_r7,
+	"R6",	Ureg_r6,
+	"R5",	Ureg_r5,
+	"R4",	Ureg_r4,
+	"R3",	Ureg_r3,
+	"R2",	Ureg_r2,
+	"R1",	Ureg_r1,
+};
+
+static Lock intrlock;
+static Handler handlers[ILmax+1];
+
+static char *
+ptlb(ulong phys)
+{
+	static char buf[4][32];
+	static int k;
+	char *p;
+
+	k = (k+1)&3;
+	p = buf[k];
+	p += snprint(p, sizeof buf[k] - (p - buf[k]), "(%#lux %lud ",
+		(phys<<6) & ~(BY2PG-1), (phys>>3)&7);
+	if(phys & 4)
+		*p++ = 'd';
+	if(phys & 2)
+		*p++ = 'v';
+	if(phys & 1)
+		*p++ = 'g';
+	*p++ = ')';
+	*p = 0;
+	return buf[k];
+}
+
+static void
+kpteprint(Ureg *ur)
+{
+	ulong i, tlbstuff[3];
+	KMap *k;
+
+	i = (ur->badvaddr & ~(2*BY2PG-1)) | TLBPID(tlbvirt());
+	print("tlbvirt=%#lux\n", i);
+	i = gettlbp(i, tlbstuff);
+	print("i=%lud v=%#lux p0=%s p1=%s\n",
+		i, tlbstuff[0], ptlb(tlbstuff[1]), ptlb(tlbstuff[2]));
+
+	i = (ur->badvaddr & ~KMAPADDR)>>15;
+	if(i > KPTESIZE){
+		print("kpte index = %lud ?\n", i);
+		return;
+	}
+	k = &kpte[i];
+	print("i=%lud, &k=%#p, k={v=%#lux, p0=%s, p1=%s, pg=%#p}\n",
+		i, k, k->virt, ptlb(k->phys0), ptlb(k->phys1), k->pg);
+	print("pg={pa=%#lux, va=%#lux}\n", k->pg->pa, k->pg->va);
+}
+
+void
+kvce(Ureg *ur, int ecode)
+{
+	char c;
+	Pte **p;
+	Page **pg;
+	Segment *s;
+	ulong addr, soff;
+
+	c = 'D';
+	if(ecode == CVCEI)
+		c = 'I';
+	print("Trap: VCE%c: addr=%#lux\n", c, ur->badvaddr);
+	if((ur->badvaddr & KSEGM) == KSEG3) {
+		kpteprint(ur);
+		return;
+	}
+	if(up && !(ur->badvaddr & KSEGM)) {
+		addr = ur->badvaddr;
+		s = seg(up, addr, 0);
+		if(s == 0){
+			print("kvce: no seg for %#lux\n", addr);
+			for(;;)
+				;
+		}
+		addr &= ~(BY2PG-1);
+		soff = addr - s->base;
+		p = &s->map[soff/PTEMAPMEM];
+		if(*p){
+			pg = &(*p)->pages[(soff&(PTEMAPMEM-1))/BY2PG];
+			if(*pg)
+				print("kvce: pa=%#lux, va=%#lux\n",
+					(*pg)->pa, (*pg)->va);
+			else
+				print("kvce: no *pg\n");
+		}else
+			print("kvce: no *p\n");
+	}
+}
+
+/* prepare to go to user space */
+void
+kexit(Ureg*)
+{
+	Tos *tos;
+
+	/* precise time accounting, kernel exit */
+	tos = (Tos*)(USTKTOP-sizeof(Tos));
+	tos->kcycles += fastticks(&tos->cyclefreq) - up->kentry;
+	tos->pcycles = up->pcycles;
+	tos->pid = up->pid;
+}
+
+void
+trap(Ureg *ur)
+{
+	int ecode, clockintr, user, cop, x, fpchk;
+	ulong fpfcr31;
+	char buf[2*ERRMAX], buf1[ERRMAX], *fpexcep;
+	static int dumps;
+
+	ecode = (ur->cause>>2)&EXCMASK;
+	user = ur->status&KUSER;
+	if (ur->cause & TS)
+		panic("trap: tlb shutdown");
+
+	fpchk = 0;
+	if(user){
+		up->dbgreg = ur;
+		cycles(&up->kentry);
+		/* no fpu, so no fp state to save */
+	}
+
+	if (up && (char *)(ur) - up->kstack < 1024 && dumps++ == 0) {
+		iprint("trap: proc %ld kernel stack getting full\n", up->pid);
+		dumpregs(ur);
+		dumpstack();
+	}
+	if (up == nil &&
+	    (char *)(ur) - (char *)m->stack < 1024 && dumps++ == 0) {
+		iprint("trap: cpu%d kernel stack getting full\n", m->machno);
+		dumpregs(ur);
+		dumpstack();
+	}
+
+//	splhi();		/* for the experiment: make it explicit */
+	/* clear EXL in status */
+	setstatus(getstatus() & ~EXL);
+
+	clockintr = 0;
+	switch(ecode){
+	case CINT:
+		clockintr = intr(ur);
+		break;
+
+	case CFPE:
+		panic("FP exception but no FPU");
+#ifdef OLD_MIPS_EXAMPLE
+		fptrap(ur);
+		clrfpintr();
+		fpchk = 1;
+#endif
+		break;
+
+	case CTLBM:
+	case CTLBL:
+	case CTLBS:
+		/* user tlb entries assumed not overwritten during startup */
+		if(up == 0)
+			kernfault(ur, ecode);
+
+		if(!user && (ur->badvaddr & KSEGM) == KSEG3) {
+			kfault(ur);
+			break;
+		}
+		x = up->insyscall;
+		up->insyscall = 1;
+		spllo();
+		faultmips(ur, user, ecode);
+		up->insyscall = x;
+		break;
+
+	case CVCEI:
+	case CVCED:
+		kvce(ur, ecode);
+		goto Default;
+
+	case CWATCH:
+		if(!user)
+			panic("watchpoint trap from kernel mode pc=%#p",
+				ur->pc);
+		fpwatch(ur);
+		break;
+
+	case CCPU:
+		cop = (ur->cause>>28)&3;
+		if(user && up && cop == 1) {
+			if(up->fpstate & FPillegal) {
+				/* someone used floating point in a note handler */
+				postnote(up, 1,
+					"sys: floating point in note handler",
+					NDebug);
+				break;
+			}
+			/* no fpu, so we can only emulate fp ins'ns */
+			if (fpuemu(ur) < 0)
+				postnote(up, 1,
+					"sys: fp instruction not emulated",
+					NDebug);
+			else
+				fpchk = 1;
+			break;
+		}
+		/* Fallthrough */
+
+	Default:
+	default:
+		if(user) {
+			spllo();
+			snprint(buf, sizeof buf, "sys: %s", excname[ecode]);
+			postnote(up, 1, buf, NDebug);
+			break;
+		}
+		if (ecode == CADREL || ecode == CADRES)
+			iprint("kernel addr exception for va %#p pid %#ld %s\n",
+				ur->badvaddr, (up? up->pid: 0),
+				(up? up->text: ""));
+		print("cpu%d: kernel %s pc=%#lux\n",
+			m->machno, excname[ecode], ur->pc);
+		dumpregs(ur);
+		dumpstack();
+		if(m->machno == 0)
+			spllo();
+		exit(1);
+	}
+
+	if(fpchk) {
+		fpfcr31 = up->fpsave.fpstatus;
+		if((fpfcr31>>12) & ((fpfcr31>>7)|0x20) & 0x3f) {
+			spllo();
+			fpexcep	= fpexcname(ur, fpfcr31, buf1, sizeof buf1);
+			snprint(buf, sizeof buf, "sys: fp: %s", fpexcep);
+			postnote(up, 1, buf, NDebug);
+		}
+	}
+
+	splhi();
+
+	/* delaysched set because we held a lock or because our quantum ended */
+	if(up && up->delaysched && clockintr){
+		sched();
+		splhi();
+	}
+
+	if(user){
+		notify(ur);
+		/* no fpu, so no fp state to restore */
+		kexit(ur);
+	}
+
+	/* restore EXL in status */
+	setstatus(getstatus() | EXL);
+}
+
+/* periodically zero all the interrupt counts */
+static void
+resetcounts(void)
+{
+	int i;
+	Handler *hp;
+
+	ilock(&intrlock);
+	for (i = 0; i < nelem(handlers); i++)
+		for (hp = &handlers[i]; hp != nil; hp = hp->next)
+			hp->intrs = 0;
+	iunlock(&intrlock);
+}
+
+/*
+ *  set handlers
+ */
+void
+intrenable(int irq, void (*h)(void *), void *arg)
+{
+	Handler *hp;
+	static int resetclock;
+
+	if (h == nil)
+		panic("intrenable: nil handler intr %d", irq);
+	if(irq < ILmin || irq >= nelem(handlers))
+		panic("intrenable: bad handler intr %d %#p", irq, h);
+
+	hp = &handlers[irq];
+	ilock(&intrlock);
+	if (hp->handler != nil) {		/* occupied? */
+		/* add a new one at the end of the chain */
+		for (; hp->next != nil; hp = hp->next)
+			;
+		hp->next = smalloc(sizeof *hp);
+		hp = hp->next;
+		hp->next = nil;
+	}
+	hp->handler = h;
+	hp->arg = arg;
+	iunlock(&intrlock);
+
+	if (irq == ILduart0) {		/* special apb sub-interrupt */
+		*Apbintrsts = 0;
+		*Apbintrmask = 1 << Apbintruart;  /* enable, actually */
+		coherence();
+	}
+	intron(1 << (ILshift + irq));
+	if (!resetclock) {
+		resetclock = 1;
+		addclock0link(resetcounts, 100);
+	}
+}
+
+void
+intrshutdown(void)
+{
+	introff(INTMASK);
+}
+
+static void
+jabberoff(Ureg *ur, int irq, ulong bit)
+{
+	introff(bit);			/* interrupt off now ... */
+	if (ur)
+		ur->status &= ~bit;	/* ... and upon return */
+	offintrs |= bit;
+	iprint("irq %d jabbering; shutting it down\n", irq);
+}
+
+ulong
+pollall(Ureg *ur, ulong cause)			/* must be called splhi */
+{
+	int i, intrs, sts;
+	ulong bit;
+	Handler *hp;
+
+	/* exclude clock and sw intrs */
+	intrs = cause & (INTR6|INTR5|INTR4|INTR3|INTR2) & getstatus();
+	if(intrs == 0)
+		return cause;
+
+	ilock(&intrlock);
+	for (i = ILmax; i >= ILmin; i--) {
+		bit = 1 << (ILshift + i);
+		if (!(intrs & bit))
+			continue;
+		intrcauses[i]++;
+		for (hp = &handlers[i]; hp != nil; hp = hp->next)
+			if (hp->handler) {
+				if (i == ILduart0) {
+					sts = *Apbintrsts;
+					if((sts & (1 << Apbintruart)) == 0)
+						continue;
+					/* don't need to ack apb sub-intr */
+					// *Apbintrsts &= ~(1 << Apbintruart);
+				}
+				(*hp->handler)(hp->arg);
+				splhi();
+				if (++hp->intrs > 25000) {
+					jabberoff(ur, i, bit);
+					intrs &= ~bit;
+					hp->intrs = 0;
+				}
+			} else if (ur)
+				iprint("no handler for interrupt %d\n", i);
+		cause &= ~bit;
+	}
+	iunlock(&intrlock);
+	return cause;
+}
+
+int
+intr(Ureg *ur)
+{
+	int clockintr;
+	ulong cause;
+
+	m->intr++;
+	clockintr = 0;
+	/*
+	 * ignore interrupts that we have disabled, even if their cause bits
+	 * are set.
+	 */
+	cause = ur->cause & ur->status & INTMASK;
+	cause &= ~(INTR1|INTR0);		/* ignore sw interrupts */
+	if (cause == 0)
+		print("spurious interrupt\n");
+	if(cause & INTR7){
+		clock(ur);
+		intrcauses[ILclock]++;
+		cause &= ~(1 << (ILclock + ILshift));
+		clockintr = 1;
+	}
+	cause = pollall(ur, cause);
+	if(cause){
+		print("intr: cause %#lux not handled\n", cause);
+		exit(1);
+	}
+
+	/* preemptive scheduling */
+	if(up && !clockintr)
+		preempted();
+	/* if it was a clockintr, sched will be called at end of trap() */
+	return clockintr;
+}
+
+char*
+fpexcname(Ureg *ur, ulong fcr31, char *buf, uint size)
+{
+	int i;
+	char *s;
+	ulong fppc;
+
+	fppc = ur->pc;
+	if(ur->cause & BD)	/* branch delay */
+		fppc += 4;
+	s = 0;
+	if(fcr31 & (1<<17))
+		s = "unimplemented operation";
+	else{
+		fcr31 >>= 7;		/* trap enable bits */
+		fcr31 &= (fcr31>>5);	/* anded with exceptions */
+		for(i=0; i<5; i++)
+			if(fcr31 & (1<<i))
+				s = fpcause[i];
+	}
+
+	if(s == 0)
+		return "no floating point exception";
+
+	snprint(buf, size, "%s fppc=%#lux", s, fppc);
+	return buf;
+}
+
+#define KERNPC(x) (KTZERO <= (ulong)(x) && (ulong)(x) < (ulong)&etext)
+
+void
+kernfault(Ureg *ur, int code)
+{
+	print("panic: kfault %s badvaddr=%#lux", excname[code], ur->badvaddr);
+	kpteprint(ur);
+	print("u=%#p status=%#lux pc=%#lux sp=%#lux\n",
+		up, ur->status, ur->pc, ur->sp);
+	delay(500);
+	panic("kfault");
+}
+
+static void
+getpcsp(ulong *pc, ulong *sp)
+{
+	*pc = getcallerpc(&pc);
+	*sp = (ulong)&pc-4;
+}
+
+void
+callwithureg(void (*fn)(Ureg*))
+{
+	Ureg ureg;
+
+	memset(&ureg, 0, sizeof ureg);
+	getpcsp((ulong*)&ureg.pc, (ulong*)&ureg.sp);
+	ureg.r31 = getcallerpc(&fn);
+	fn(&ureg);
+}
+
+static void
+_dumpstack(Ureg *ureg)
+{
+	ulong l, v, top, i;
+	extern ulong etext;
+
+	if(up == 0)
+		return;
+
+	print("ktrace /kernel/path %.8lux %.8lux %.8lux\n",
+		ureg->pc, ureg->sp, ureg->r31);
+	top = (ulong)up->kstack + KSTACK;
+	i = 0;
+	for(l=ureg->sp; l < top; l += BY2WD) {
+		v = *(ulong*)l;
+		if(KTZERO < v && v < (ulong)&etext) {
+			print("%.8lux=%.8lux ", l, v);
+			if((++i%4) == 0){
+				print("\n");
+				delay(200);
+			}
+		}
+	}
+	print("\n");
+}
+
+void
+dumpstack(void)
+{
+	callwithureg(_dumpstack);
+}
+
+static ulong
+R(Ureg *ur, int i)
+{
+	uchar *s;
+
+	s = (uchar*)ur;
+	return *(ulong*)(s + regname[i].off - Uoffset);
+}
+
+void
+dumpregs(Ureg *ur)
+{
+	int i;
+
+	if(up)
+		print("registers for %s %lud\n", up->text, up->pid);
+	else
+		print("registers for kernel\n");
+
+	for(i = 0; i < nelem(regname); i += 2)
+		print("%s\t%#.8lux\t%s\t%#.8lux\n",
+			regname[i].name,   R(ur, i),
+			regname[i+1].name, R(ur, i+1));
+}
+
+int
+notify(Ureg *ur)
+{
+	int l, s;
+	ulong sp;
+	Note *n;
+
+	if(up->procctl)
+		procctl(up);
+	if(up->nnote == 0)
+		return 0;
+
+	s = spllo();
+	qlock(&up->debug);
+	up->fpstate |= FPillegal;
+	up->notepending = 0;
+	n = &up->note[0];
+	if(strncmp(n->msg, "sys:", 4) == 0) {
+		l = strlen(n->msg);
+		if(l > ERRMAX-15)	/* " pc=0x12345678\0" */
+			l = ERRMAX-15;
+
+		seprint(n->msg+l, &n->msg[sizeof n->msg], " pc=%#lux", ur->pc);
+	}
+
+	if(n->flag != NUser && (up->notified || up->notify==0)) {
+		if(n->flag == NDebug)
+			pprint("suicide: %s\n", n->msg);
+
+		qunlock(&up->debug);
+		pexit(n->msg, n->flag!=NDebug);
+	}
+
+	if(up->notified) {
+		qunlock(&up->debug);
+		splx(s);
+		return 0;
+	}
+
+	if(!up->notify) {
+		qunlock(&up->debug);
+		pexit(n->msg, n->flag!=NDebug);
+	}
+	sp = ur->usp & ~(BY2V-1);
+	sp -= sizeof(Ureg);
+
+	if(!okaddr((ulong)up->notify, BY2WD, 0) ||
+	   !okaddr(sp-ERRMAX-4*BY2WD, sizeof(Ureg)+ERRMAX+4*BY2WD, 1)) {
+		pprint("suicide: bad address or sp in notify\n");
+		qunlock(&up->debug);
+		pexit("Suicide", 0);
+	}
+
+	memmove((Ureg*)sp, ur, sizeof(Ureg));	/* push user regs */
+	*(Ureg**)(sp-BY2WD) = up->ureg;	/* word under Ureg is old up->ureg */
+	up->ureg = (void*)sp;
+
+	sp -= BY2WD+ERRMAX;
+	memmove((char*)sp, up->note[0].msg, ERRMAX);	/* push err string */
+
+	sp -= 3*BY2WD;
+	*(ulong*)(sp+2*BY2WD) = sp+3*BY2WD;	/* arg 2 is string */
+	ur->r1 = (long)up->ureg;		/* arg 1 is ureg* */
+	((ulong*)sp)[1] = (ulong)up->ureg;	/* arg 1 0(FP) is ureg* */
+	((ulong*)sp)[0] = 0;			/* arg 0 is pc */
+	ur->usp = sp;
+	/*
+	 * arrange to resume at user's handler as if handler(ureg, errstr)
+	 * were being called.
+	 */
+	ur->pc = (ulong)up->notify;
+
+	up->notified = 1;
+	up->nnote--;
+	memmove(&up->lastnote, &up->note[0], sizeof(Note));
+	memmove(&up->note[0], &up->note[1], up->nnote*sizeof(Note));
+
+	qunlock(&up->debug);
+	splx(s);
+	return 1;
+}
+
+/*
+ * Check that status is OK to return from note.
+ */
+int
+validstatus(ulong kstatus, ulong ustatus)
+{
+//	if((kstatus & (INTMASK|KX|SX|UX)) != (ustatus & (INTMASK|KX|SX|UX)))
+	if((kstatus & INTMASK) != (ustatus & INTMASK))
+		return 0;
+	if((ustatus&(KSU|ERL|EXL|IE)) != (KUSER|EXL|IE))
+		return 0;
+	if(ustatus & (0xFFFF0000&~CU1))	/* no CU3, CU2, CU0, RP, FR, RE, DS */
+		return 0;
+	return 1;
+}
+
+/*
+ * Return user to state before notify(); called from user's handler.
+ */
+void
+noted(Ureg *kur, Ureg **urp, ulong arg0)
+{
+	Ureg *nur;
+	ulong oureg, sp;
+
+	qlock(&up->debug);
+	if(arg0!=NRSTR && !up->notified) {
+		qunlock(&up->debug);
+		pprint("call to noted() when not notified\n");
+		pexit("Suicide", 0);
+	}
+	up->notified = 0;
+
+	up->fpstate &= ~FPillegal;
+
+	nur = up->ureg;
+
+	oureg = (ulong)nur;
+	if((oureg & (BY2WD-1))
+	|| !okaddr((ulong)oureg-BY2WD, BY2WD+sizeof(Ureg), 0)){
+		pprint("bad up->ureg in noted or call to noted() when not notified\n");
+		qunlock(&up->debug);
+		pexit("Suicide", 0);
+	}
+
+	if(!validstatus(kur->status, nur->status)) {
+		qunlock(&up->debug);
+		pprint("bad noted ureg status %#lux\n", nur->status);
+		pexit("Suicide", 0);
+	}
+
+	memmove(*urp, up->ureg, sizeof(Ureg));
+	switch(arg0) {
+	case NCONT:
+	case NRSTR:				/* only used by APE */
+		if(!okaddr(nur->pc, BY2WD, 0) || !okaddr(nur->usp, BY2WD, 0)){
+			pprint("suicide: trap in noted\n");
+			qunlock(&up->debug);
+			pexit("Suicide", 0);
+		}
+		up->ureg = (Ureg*)(*(ulong*)(oureg-BY2WD));
+		qunlock(&up->debug);
+		splhi();
+		/*
+		 * the old challenge and carrera ports called rfnote here,
+		 * but newer ports do not, and notes seem to work only
+		 * without this call.
+		 */
+		// rfnote(urp);		/* return from note with SP=urp */
+		break;
+
+	case NSAVE:				/* only used by APE */
+		if(!okaddr(nur->pc, BY2WD, 0) || !okaddr(nur->usp, BY2WD, 0)){
+			pprint("suicide: trap in noted\n");
+			qunlock(&up->debug);
+			pexit("Suicide", 0);
+		}
+		qunlock(&up->debug);
+		sp = oureg-4*BY2WD-ERRMAX;
+
+		splhi();
+		(*urp)->sp = sp;
+		((ulong*)sp)[1] = oureg;	/* arg 1 0(FP) is ureg* */
+		((ulong*)sp)[0] = 0;		/* arg 0 is pc */
+		(*urp)->r1 = oureg;		/* arg 1 is ureg* */
+
+		// rfnote(urp);		/* return from note with SP=urp */
+		break;
+
+	default:
+		pprint("unknown noted arg %#lux\n", arg0);
+		up->lastnote.flag = NDebug;
+		/* fall through */
+
+	case NDFLT:
+		if(up->lastnote.flag == NDebug)
+			pprint("suicide: %s\n", up->lastnote.msg);
+		qunlock(&up->debug);
+		pexit(up->lastnote.msg, up->lastnote.flag!=NDebug);
+	}
+}
+
+#include "../port/systab.h"
+
+static Ref goodsyscall;
+static Ref totalsyscall;
+
+static void
+sctracesetup(ulong scallnr, ulong sp, uintptr pc, vlong *startnsp)
+{
+	if(up->procctl == Proc_tracesyscall){
+		/*
+		 * Redundant validaddr.  Do we care?
+		 * Tracing syscalls is not exactly a fast path...
+		 * Beware, validaddr currently does a pexit rather
+		 * than an error if there's a problem; that might
+		 * change in the future.
+		 */
+		if(sp < (USTKTOP-BY2PG) || sp > (USTKTOP-sizeof(Sargs)-BY2WD))
+			validaddr(sp, sizeof(Sargs)+BY2WD, 0);
+
+		syscallfmt(scallnr, pc, (va_list)(sp+BY2WD));
+		up->procctl = Proc_stopme;
+		procctl(up);
+		if(up->syscalltrace)
+			free(up->syscalltrace);
+		up->syscalltrace = nil;
+		*startnsp = todget(nil);
+	}
+}
+
+static void
+sctracefinish(ulong scallnr, ulong sp, int ret, vlong startns)
+{
+	int s;
+
+	if(up->procctl == Proc_tracesyscall){
+		up->procctl = Proc_stopme;
+		sysretfmt(scallnr, (va_list)(sp+BY2WD), ret,
+			startns, todget(nil));
+		s = splhi();
+		procctl(up);
+		splx(s);
+		if(up->syscalltrace)
+			free(up->syscalltrace);
+		up->syscalltrace = nil;
+	}
+}
+
+/*
+ * called directly from assembler, not via trap()
+ */
+long
+syscall(Ureg *aur)
+{
+	int i;
+	volatile long ret;
+	ulong sp, scallnr;
+	vlong startns;
+	char *e;
+	Ureg *ur;
+
+	cycles(&up->kentry);
+
+	incref(&totalsyscall);
+	m->syscall++;
+	up->insyscall = 1;
+	ur = aur;
+	up->pc = ur->pc;
+	up->dbgreg = aur;
+	ur->cause = 16<<2;	/* for debugging: system call is undef 16 */
+
+	scallnr = ur->r1;
+	up->scallnr = ur->r1;
+	sp = ur->sp;
+	sctracesetup(scallnr, sp, ur->pc, &startns);
+
+	/* clear EXL in status */
+	setstatus(getstatus() & ~EXL);
+
+	/* no fpu, so no fp state to save */
+	spllo();
+
+	up->nerrlab = 0;
+	ret = -1;
+	if(!waserror()) {
+		if(scallnr >= nsyscall || systab[scallnr] == 0){
+			pprint("bad sys call number %ld pc %#lux\n",
+				scallnr, ur->pc);
+			postnote(up, 1, "sys: bad sys call", NDebug);
+			error(Ebadarg);
+		}
+
+		if(sp & (BY2WD-1)){
+			pprint("odd sp in sys call pc %#lux sp %#lux\n",
+				ur->pc, ur->sp);
+			postnote(up, 1, "sys: odd stack", NDebug);
+			error(Ebadarg);
+		}
+
+		if(sp<(USTKTOP-BY2PG) || sp>(USTKTOP-sizeof(Sargs)-BY2WD))
+			validaddr(sp, sizeof(Sargs)+BY2WD, 0);
+
+		up->s = *((Sargs*)(sp+BY2WD));
+		up->psstate = sysctab[scallnr];
+
+		ret = systab[scallnr](up->s.args);
+		poperror();
+	}else{
+		/* failure: save the error buffer for errstr */
+		e = up->syserrstr;
+		up->syserrstr = up->errstr;
+		up->errstr = e;
+		if(0 && up->pid == 1)
+			print("[%lud %s] syscall %lud: %s\n",
+				up->pid, up->text, scallnr, up->errstr);
+	}
+	if(up->nerrlab){
+		print("bad errstack [%lud]: %d extra\n", scallnr, up->nerrlab);
+		for(i = 0; i < NERR; i++)
+			print("sp=%#lux pc=%#lux\n",
+				up->errlab[i].sp, up->errlab[i].pc);
+		panic("error stack");
+	}
+	sctracefinish(scallnr, sp, ret, startns);
+
+	ur->pc += 4;
+	ur->r1 = ret;
+
+	up->psstate = 0;
+	up->insyscall = 0;
+
+	if(scallnr == NOTED) {				/* ugly hack */
+		noted(ur, &aur, *(ulong*)(sp+BY2WD));	/* may return */
+		ret = ur->r1;
+	}
+	incref(&goodsyscall);
+	splhi();
+	if(scallnr!=RFORK && (up->procctl || up->nnote)){
+		ur->r1 = ret;			/* load up for noted() above */
+		notify(ur);
+	}
+	/* if we delayed sched because we held a lock, sched now */
+	if(up->delaysched)
+		sched();
+	kexit(ur);
+
+	/* restore EXL in status */
+	setstatus(getstatus() | EXL);
+
+	return ret;
+}
+
+void
+forkchild(Proc *p, Ureg *ur)
+{
+	Ureg *cur;
+
+	p->sched.sp = (ulong)p->kstack+KSTACK-UREGSIZE;
+	p->sched.pc = (ulong)forkret;
+
+	cur = (Ureg*)(p->sched.sp+2*BY2WD);
+	memmove(cur, ur, sizeof(Ureg));
+
+	cur->pc += 4;
+
+	/* Things from bottom of syscall we never got to execute */
+	p->psstate = 0;
+	p->insyscall = 0;
+}
+
+static
+void
+linkproc(void)
+{
+	spllo();
+	up->kpfun(up->kparg);
+	pexit("kproc exiting", 0);
+}
+
+void
+kprocchild(Proc *p, void (*func)(void*), void *arg)
+{
+	p->sched.pc = (ulong)linkproc;
+	p->sched.sp = (ulong)p->kstack+KSTACK;
+
+	p->kpfun = func;
+	p->kparg = arg;
+}
+
+/* set up user registers before return from exec() */
+long
+execregs(ulong entry, ulong ssize, ulong nargs)
+{
+	Ureg *ur;
+	ulong *sp;
+
+	sp = (ulong*)(USTKTOP - ssize);
+	*--sp = nargs;
+
+	ur = (Ureg*)up->dbgreg;
+	ur->usp = (ulong)sp;
+	ur->pc = entry - 4;		/* syscall advances it */
+	up->fpsave.fpstatus = initfp.fpstatus;
+	return USTKTOP-sizeof(Tos);	/* address of kernel/user shared data */
+}
+
+ulong
+userpc(void)
+{
+	Ureg *ur;
+
+	ur = (Ureg*)up->dbgreg;
+	return ur->pc;
+}
+
+/*
+ * This routine must save the values of registers the user is not
+ * permitted to write from devproc and then restore the saved values
+ * before returning
+ */
+void
+setregisters(Ureg *xp, char *pureg, char *uva, int n)
+{
+	ulong status;
+
+	status = xp->status;
+	memmove(pureg, uva, n);
+	xp->status = status;
+}
+
+/*
+ * Give enough context in the ureg to produce a kernel stack for
+ * a sleeping process
+ */
+void
+setkernur(Ureg *xp, Proc *p)
+{
+	xp->pc = p->sched.pc;
+	xp->sp = p->sched.sp;
+	xp->r24 = (ulong)p;		/* up */
+	xp->r31 = (ulong)sched;
+}
+
+ulong
+dbgpc(Proc *p)
+{
+	Ureg *ur;
+
+	ur = p->dbgreg;
+	if(ur == 0)
+		return 0;
+
+	return ur->pc;
+}

+ 854 - 0
sys/src/9/rb/uarti8250.c

@@ -0,0 +1,854 @@
+/*
+ * 8250-like UART
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+enum {
+	CONSOLE		= 0,		/* first uart */
+	Pollstuckoutput	= 1,
+};
+
+enum {					/* registers */
+	Rbr		= 0,		/* Receiver Buffer (RO) */
+	Thr		= 0,		/* Transmitter Holding (WO) */
+	Ier		= 1,		/* Interrupt Enable */
+	Iir		= 2,		/* Interrupt Identification (RO) */
+	Fcr		= 2,		/* FIFO Control (WO) */
+	Lcr		= 3,		/* Line Control */
+	Mcr		= 4,		/* Modem Control */
+	Lsr		= 5,		/* Line Status */
+	Msr		= 6,		/* Modem Status */
+
+	Scr		= 7,		/* Scratch Pad */
+//	Mdr		= 8,		/* Mode Def'n (omap rw) */
+	Usr		= 31,		/* Uart Status Register */
+	Stickyend,
+
+	Dll		= 0,		/* Divisor Latch LSB */
+	Dlm		= 1,		/* Divisor Latch MSB */
+};
+
+enum {					/* Usr */
+	Busy		= 0x01,
+};
+
+enum {					/* Ier */
+	Erda		= 0x01,		/* Enable Received Data Available */
+	Ethre		= 0x02,		/* Enable Thr Empty */
+	Erls		= 0x04,		/* Enable Receiver Line Status */
+	Ems		= 0x08,		/* Enable Modem Status */
+};
+
+enum {					/* Iir */
+	Ims		= 0x00,		/* Ms interrupt */
+	Ip		= 0x01,		/* Interrupt Pending (not) */
+	Ithre		= 0x02,		/* Thr Empty */
+	Irda		= 0x04,		/* Received Data Available */
+	Irls		= 0x06,		/* Receiver Line Status */
+	Ictoi		= 0x0C,		/* Character Time-out Indication */
+	IirMASK		= 0x3F,
+	Ifena		= 0xC0,		/* FIFOs enabled */
+};
+
+enum {					/* Fcr */
+	FIFOena		= 0x01,		/* FIFO enable */
+	FIFOrclr	= 0x02,		/* clear Rx FIFO */
+	FIFOtclr	= 0x04,		/* clear Tx FIFO */
+//	FIFOdma		= 0x08,
+	FIFO1		= 0x00,		/* Rx FIFO trigger level 1 byte */
+	FIFO4		= 0x40,		/*	4 bytes */
+	FIFO8		= 0x80,		/*	8 bytes */
+	FIFO14		= 0xC0,		/*	14 bytes */
+};
+
+enum {					/* Lcr */
+	Wls5		= 0x00,		/* Word Length Select 5 bits/byte */
+	Wls6		= 0x01,		/*	6 bits/byte */
+	Wls7		= 0x02,		/*	7 bits/byte */
+	Wls8		= 0x03,		/*	8 bits/byte */
+	WlsMASK		= 0x03,
+	Stb		= 0x04,		/* 2 stop bits */
+	Pen		= 0x08,		/* Parity Enable */
+	Eps		= 0x10,		/* Even Parity Select */
+	Stp		= 0x20,		/* Stick Parity */
+	Brk		= 0x40,		/* Break */
+	Dlab		= 0x80,		/* Divisor Latch Access Bit */
+};
+
+enum {					/* Mcr */
+	Dtr		= 0x01,		/* Data Terminal Ready */
+	Rts		= 0x02,		/* Ready To Send */
+	Out1		= 0x04,		/* no longer in use */
+	Ie		= 0x08,		/* IRQ Enable (cd_sts_ch on omap) */
+	Dm		= 0x10,		/* Diagnostic Mode loopback */
+};
+
+enum {					/* Lsr */
+	Dr		= 0x01,		/* Data Ready */
+	Oe		= 0x02,		/* Overrun Error */
+	Pe		= 0x04,		/* Parity Error */
+	Fe		= 0x08,		/* Framing Error */
+	Bi		= 0x10,		/* Break Interrupt */
+	Thre		= 0x20,		/* Thr Empty */
+	Temt		= 0x40,		/* Transmitter Empty */
+	FIFOerr		= 0x80,		/* error in receiver FIFO */
+};
+
+enum {					/* Msr */
+	Dcts		= 0x01,		/* Delta Cts */
+	Ddsr		= 0x02,		/* Delta Dsr */
+	Teri		= 0x04,		/* Trailing Edge of Ri */
+	Ddcd		= 0x08,		/* Delta Dcd */
+	Cts		= 0x10,		/* Clear To Send */
+	Dsr		= 0x20,		/* Data Set Ready */
+	Ri		= 0x40,		/* Ring Indicator */
+	Dcd		= 0x80,		/* Carrier Detect */
+};
+
+enum {					/* Mdr */
+	Modemask	= 7,
+	Modeuart	= 0,
+};
+
+
+typedef struct Ctlr {
+	u32int*	io;
+	int	irq;
+	int	tbdf;
+	int	iena;
+	int	poll;
+
+	uchar	sticky[Stickyend];
+
+	Lock;
+	int	hasfifo;
+	int	checkfifo;
+	int	fena;
+} Ctlr;
+
+extern PhysUart i8250physuart;
+
+static Ctlr i8250ctlr[] = {
+{	.io	= (u32int*)PHYSCONS,
+	.irq	= ILduart0,
+	.tbdf	= -1,
+	.poll	= 0, },
+};
+
+static Uart i8250uart[] = {
+{	.regs	= &i8250ctlr[0],
+	.name	= "cons",
+	.freq	= 3686000,	/* Not used, we use the global i8250freq */
+	.phys	= &i8250physuart,
+	.console= 1,
+	.next	= nil, },
+};
+
+#define csr8r(c, r)	((c)->io[r])
+#define csr8w(c, r, v)	((c)->io[r] = (uchar)((c)->sticky[r] | (v)))
+#define csr8o(c, r, v)	((c)->io[r] = (uchar)(v))
+
+static long
+i8250status(Uart* uart, void* buf, long n, long offset)
+{
+	char *p;
+	Ctlr *ctlr;
+	uchar ier, lcr, mcr, msr;
+
+	ctlr = uart->regs;
+	p = smalloc(READSTR);
+	mcr = ctlr->sticky[Mcr];
+	msr = csr8r(ctlr, Msr);
+	ier = ctlr->sticky[Ier];
+	lcr = ctlr->sticky[Lcr];
+	snprint(p, READSTR,
+		"b%d c%d d%d e%d l%d m%d p%c r%d s%d i%d\n"
+		"dev(%d) type(%d) framing(%d) overruns(%d) "
+		"berr(%d) serr(%d)%s%s%s%s\n",
+
+		uart->baud,
+		uart->hup_dcd,
+		(msr & Dsr) != 0,
+		uart->hup_dsr,
+		(lcr & WlsMASK) + 5,
+		(ier & Ems) != 0,
+		(lcr & Pen) ? ((lcr & Eps) ? 'e': 'o'): 'n',
+		(mcr & Rts) != 0,
+		(lcr & Stb) ? 2: 1,
+		ctlr->fena,
+
+		uart->dev,
+		uart->type,
+		uart->ferr,
+		uart->oerr,
+		uart->berr,
+		uart->serr,
+		(msr & Cts) ? " cts": "",
+		(msr & Dsr) ? " dsr": "",
+		(msr & Dcd) ? " dcd": "",
+		(msr & Ri) ? " ring": ""
+	);
+	n = readstr(offset, buf, n, p);
+	free(p);
+
+	return n;
+}
+
+static void
+i8250fifo(Uart* uart, int level)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	if(ctlr->hasfifo == 0)
+		return;
+
+	/*
+	 * Changing the FIFOena bit in Fcr flushes data
+	 * from both receive and transmit FIFOs; there's
+	 * no easy way to guarantee not losing data on
+	 * the receive side, but it's possible to wait until
+	 * the transmitter is really empty.
+	 */
+	ilock(ctlr);
+	while(!(csr8r(ctlr, Lsr) & Temt))
+		;
+
+	/*
+	 * Set the trigger level, default is the max.
+	 * value.
+	 * Some UARTs require FIFOena to be set before
+	 * other bits can take effect, so set it twice.
+	 */
+	ctlr->fena = level;
+	switch(level){
+	case 0:
+		break;
+	case 1:
+		level = FIFO1|FIFOena;
+		break;
+	case 4:
+		level = FIFO4|FIFOena;
+		break;
+	case 8:
+		level = FIFO8|FIFOena;
+		break;
+	default:
+		level = FIFO14|FIFOena;
+		break;
+	}
+	csr8w(ctlr, Fcr, level);
+	csr8w(ctlr, Fcr, level);
+	iunlock(ctlr);
+}
+
+static void
+i8250dtr(Uart* uart, int on)
+{
+	Ctlr *ctlr;
+
+	/*
+	 * Toggle DTR.
+	 */
+	ctlr = uart->regs;
+	if(on)
+		ctlr->sticky[Mcr] |= Dtr;
+	else
+		ctlr->sticky[Mcr] &= ~Dtr;
+	csr8w(ctlr, Mcr, 0);
+}
+
+static void
+i8250rts(Uart* uart, int on)
+{
+	Ctlr *ctlr;
+
+	/*
+	 * Toggle RTS.
+	 */
+	ctlr = uart->regs;
+	if(on)
+		ctlr->sticky[Mcr] |= Rts;
+	else
+		ctlr->sticky[Mcr] &= ~Rts;
+	csr8w(ctlr, Mcr, 0);
+}
+
+static void
+i8250modemctl(Uart* uart, int on)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	ilock(&uart->tlock);
+	if(on){
+		ctlr->sticky[Ier] |= Ems;
+		csr8w(ctlr, Ier, 0);
+		uart->modem = 1;
+		uart->cts = csr8r(ctlr, Msr) & Cts;
+	}
+	else{
+		ctlr->sticky[Ier] &= ~Ems;
+		csr8w(ctlr, Ier, 0);
+		uart->modem = 0;
+		uart->cts = 1;
+	}
+	iunlock(&uart->tlock);
+
+	/* modem needs fifo */
+	(*uart->phys->fifo)(uart, on);
+}
+
+static int
+i8250parity(Uart* uart, int parity)
+{
+	int lcr;
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	lcr = ctlr->sticky[Lcr] & ~(Eps|Pen);
+
+	switch(parity){
+	case 'e':
+		lcr |= Eps|Pen;
+		break;
+	case 'o':
+		lcr |= Pen;
+		break;
+	case 'n':
+		break;
+	default:
+		return -1;
+	}
+	ctlr->sticky[Lcr] = lcr;
+	csr8w(ctlr, Lcr, 0);
+
+	uart->parity = parity;
+
+	return 0;
+}
+
+static int
+i8250stop(Uart* uart, int stop)
+{
+	int lcr;
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	lcr = ctlr->sticky[Lcr] & ~Stb;
+
+	switch(stop){
+	case 1:
+		break;
+	case 2:
+		lcr |= Stb;
+		break;
+	default:
+		return -1;
+	}
+	ctlr->sticky[Lcr] = lcr;
+	csr8w(ctlr, Lcr, 0);
+
+	uart->stop = stop;
+
+	return 0;
+}
+
+static int
+i8250bits(Uart* uart, int bits)
+{
+	int lcr;
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	lcr = ctlr->sticky[Lcr] & ~WlsMASK;
+
+	switch(bits){
+	case 5:
+		lcr |= Wls5;
+		break;
+	case 6:
+		lcr |= Wls6;
+		break;
+	case 7:
+		lcr |= Wls7;
+		break;
+	case 8:
+		lcr |= Wls8;
+		break;
+	default:
+		return -1;
+	}
+	ctlr->sticky[Lcr] = lcr;
+	csr8w(ctlr, Lcr, 0);
+
+	uart->bits = bits;
+
+	return 0;
+}
+
+static int
+i8250baud(Uart* uart, int baud)
+{
+#ifdef notdef				/* don't change the speed */
+	ulong bgc;
+	Ctlr *ctlr;
+	extern int i8250freq;	/* In the config file */
+
+	/*
+	 * Set the Baud rate by calculating and setting the Baud rate
+	 * Generator Constant. This will work with fairly non-standard
+	 * Baud rates.
+	 */
+	if(i8250freq == 0 || baud <= 0)
+		return -1;
+	bgc = (i8250freq+8*baud-1)/(16*baud);
+
+	ctlr = uart->regs;
+	while(csr8r(ctlr, Usr) & Busy)
+		delay(1);
+	csr8w(ctlr, Lcr, Dlab);		/* begin kludge */
+	csr8o(ctlr, Dlm, bgc>>8);
+	csr8o(ctlr, Dll, bgc);
+	csr8w(ctlr, Lcr, 0);
+#endif
+	uart->baud = baud;
+	return 0;
+}
+
+static void
+i8250break(Uart* uart, int ms)
+{
+	Ctlr *ctlr;
+
+	if (up == nil)
+		panic("i8250break: nil up");
+	/*
+	 * Send a break.
+	 */
+	if(ms <= 0)
+		ms = 200;
+
+	ctlr = uart->regs;
+	csr8w(ctlr, Lcr, Brk);
+	tsleep(&up->sleep, return0, 0, ms);
+	csr8w(ctlr, Lcr, 0);
+}
+
+static void
+emptyoutstage(Uart *uart, int n)
+{
+	_uartputs((char *)uart->op, n);
+	uart->op = uart->oe = uart->ostage;
+}
+
+static void
+i8250kick(Uart* uart)
+{
+	int i;
+	Ctlr *ctlr;
+
+	if(/* uart->cts == 0 || */ uart->blocked)
+		return;
+
+	if(!normalprint) {			/* early */
+		if (uart->op < uart->oe)
+			emptyoutstage(uart, uart->oe - uart->op);
+		while ((i = uartstageoutput(uart)) > 0)
+			emptyoutstage(uart, i);
+		return;
+	}
+
+	/* nothing more to send? then disable xmit intr */
+	ctlr = uart->regs;
+	if (uart->op >= uart->oe && qlen(uart->oq) == 0 &&
+	    (1 || csr8r(ctlr, Lsr) & Temt)) {	/* could try ignoring Temt */
+		ctlr->sticky[Ier] &= ~Ethre;
+		csr8w(ctlr, Ier, 0);
+		return;
+	}
+
+	/*
+	 *  128 here is an arbitrary limit to make sure
+	 *  we don't stay in this loop too long.  If the
+	 *  chip's output queue is longer than 128, too
+	 *  bad -- presotto
+	 */
+	for(i = 0; i < 128; i++){
+		if(!(csr8r(ctlr, Lsr) & Thre))
+			break;
+		if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
+			break;
+		csr8o(ctlr, Thr, *uart->op++);		/* start tx */
+		ctlr->sticky[Ier] |= Ethre;
+		csr8w(ctlr, Ier, 0);			/* intr when done */
+	}
+}
+
+void
+serialkick(void)
+{
+	uartkick(&i8250uart[CONSOLE]);
+}
+
+static Lock i8250intrlock;
+
+static void
+i8250interrupt(void* arg)
+{
+	Ctlr *ctlr;
+	Uart *uart;
+	int iir, lsr, old, r;
+
+	uart = arg;
+	ctlr = uart->regs;
+	ilock(&i8250intrlock);
+
+	/* force Ethre on.  don't know why this is needed, but it is. */
+	ctlr->sticky[Ier] |= Ethre;
+	csr8w(ctlr, Ier, 0);
+	/* this is probably optional.  maybe it helps fast input. */
+	ctlr->sticky[Mcr] |= Ie;
+	csr8w(ctlr, Mcr, 0);
+
+	for(iir = csr8r(ctlr, Iir); !(iir & Ip); iir = csr8r(ctlr, Iir)){
+		switch(iir & IirMASK){
+		case Ims:		/* Ms interrupt */
+			r = csr8r(ctlr, Msr);
+			if(r & Dcts){
+				ilock(&uart->tlock);
+				old = uart->cts;
+				uart->cts = r & Cts;
+				if(old == 0 && uart->cts)
+					uart->ctsbackoff = 2;
+				iunlock(&uart->tlock);
+			}
+		 	if(r & Ddsr){
+				old = r & Dsr;
+				if(uart->hup_dsr && uart->dsr && !old)
+					uart->dohup = 1;
+				uart->dsr = old;
+			}
+		 	if(r & Ddcd){
+				old = r & Dcd;
+				if(uart->hup_dcd && uart->dcd && !old)
+					uart->dohup = 1;
+				uart->dcd = old;
+			}
+			break;
+		case Ithre:		/* Thr Empty */
+			uartkick(uart);
+			break;
+		case Irda:		/* Received Data Available */
+		case Irls:		/* Receiver Line Status */
+		case Ictoi:		/* Character Time-out Indication */
+			/*
+			 * Consume any received data.
+			 * If the received byte came in with a break,
+			 * parity or framing error, throw it away;
+			 * overrun is an indication that something has
+			 * already been tossed.
+			 */
+			while((lsr = csr8r(ctlr, Lsr)) & Dr){
+				if(lsr & (FIFOerr|Oe))
+					uart->oerr++;
+				if(lsr & Pe)
+					uart->perr++;
+				if(lsr & Fe)
+					uart->ferr++;
+				r = csr8r(ctlr, Rbr);
+				if(!(lsr & (Bi|Fe|Pe)))
+					uartrecv(uart, r);
+			}
+			break;
+
+		default:
+			iprint("weird uart interrupt type %#2.2uX\n", iir);
+			break;
+		}
+	}
+	iunlock(&i8250intrlock);
+}
+
+static void
+i8250disable(Uart* uart)
+{
+	Ctlr *ctlr;
+
+	/*
+	 * Turn off DTR and RTS, disable interrupts and fifos.
+	 */
+	(*uart->phys->dtr)(uart, 0);
+	(*uart->phys->rts)(uart, 0);
+	(*uart->phys->fifo)(uart, 0);
+
+	ctlr = uart->regs;
+	ctlr->sticky[Ier] = 0;
+	csr8w(ctlr, Ier, 0);
+
+	if(ctlr->iena != 0){
+		/* bad idea if the IRQ is shared */
+//		introff(1 << (ILshift + ctlr->irq));
+		ctlr->iena = 0;
+	}
+}
+
+static void
+i8250clock(void)
+{
+	i8250interrupt(&i8250uart[CONSOLE]);
+}
+
+static void
+i8250enable(Uart* uart, int ie)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	ctlr->sticky[Lcr] = Wls8;		/* no parity */
+	csr8w(ctlr, Lcr, 0);
+
+	/*
+	 * Check if there is a FIFO.
+	 * Changing the FIFOena bit in Fcr flushes data
+	 * from both receive and transmit FIFOs; there's
+	 * no easy way to guarantee not losing data on
+	 * the receive side, but it's possible to wait until
+	 * the transmitter is really empty.
+	 * Also, reading the Iir outwith i8250interrupt()
+	 * can be dangerous, but this should only happen
+	 * once, before interrupts are enabled.
+	 */
+	ilock(ctlr);
+	if(!ctlr->checkfifo){
+		/*
+		 * Wait until the transmitter is really empty.
+		 */
+		while(!(csr8r(ctlr, Lsr) & Temt))
+			;
+		csr8w(ctlr, Fcr, FIFOena);
+		if(csr8r(ctlr, Iir) & Ifena)
+			ctlr->hasfifo = 1;
+		csr8w(ctlr, Fcr, 0);
+		ctlr->checkfifo = 1;
+	}
+	iunlock(ctlr);
+
+	/*
+	 * Enable interrupts and turn on DTR and RTS.
+	 * Be careful if this is called to set up a polled serial line
+	 * early on not to try to enable interrupts as interrupt-
+	 * -enabling mechanisms might not be set up yet.
+	 */
+	if(ie){
+		if(ctlr->iena == 0 && !ctlr->poll){
+			intrenable(ctlr->irq, i8250interrupt, uart);
+			ctlr->iena = 1;
+		}
+		ctlr->sticky[Ier] = Erda;
+		ctlr->sticky[Mcr] |= Ie;
+	}
+	else{
+		ctlr->sticky[Ier] = 0;
+		ctlr->sticky[Mcr] = 0;
+	}
+	csr8w(ctlr, Ier, 0);
+	csr8w(ctlr, Mcr, 0);
+
+	(*uart->phys->dtr)(uart, 1);
+	(*uart->phys->rts)(uart, 1);
+
+	/*
+	 * During startup, the i8259 interrupt controller is reset.
+	 * This may result in a lost interrupt from the i8250 uart.
+	 * The i8250 thinks the interrupt is still outstanding and does not
+	 * generate any further interrupts. The workaround is to call the
+	 * interrupt handler to clear any pending interrupt events.
+	 * Note: this must be done after setting Ier.
+	 */
+	if(ie) {
+		i8250interrupt(uart);
+		/*
+		 * force output to resume if stuck.  shouldn't be needed.
+		 */
+		if (Pollstuckoutput)
+			addclock0link(i8250clock, 10);
+	}
+}
+
+static Uart*
+i8250pnp(void)
+{
+	return i8250uart;
+}
+
+static int
+i8250getc(Uart* uart)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	while(!(csr8r(ctlr, Lsr) & Dr))
+		delay(1);
+	return csr8r(ctlr, Rbr);
+}
+
+static void
+i8250putc(Uart* uart, int c)
+{
+	int i, s;
+	Ctlr *ctlr;
+
+	if (!normalprint) {		/* too early; use brute force */
+		s = splhi();
+		while (!(((ulong *)PHYSCONS)[Lsr] & Thre))
+			;
+		((ulong *)PHYSCONS)[Thr] = (uchar)c;
+		splx(s);
+		return;
+	}
+
+	ctlr = uart->regs;
+	s = splhi();
+	for(i = 0; !(csr8r(ctlr, Lsr) & Thre) && i < 200; i++)
+		delay(5);
+	csr8o(ctlr, Thr, c);
+	for(i = 0; !(csr8r(ctlr, Lsr) & Thre) && i < 200; i++)
+		delay(5);
+	splx(s);
+}
+
+void
+serialputc(int c)
+{
+	i8250putc(&i8250uart[CONSOLE], c);
+}
+
+void
+serialputs(char* s, int n)
+{
+	_uartputs(s, n);
+}
+
+#ifdef PLAN9K
+static void
+i8250poll(Uart* uart)
+{
+	Ctlr *ctlr;
+
+	/*
+	 * If PhysUart has a non-nil .poll member, this
+	 * routine will be called from the uartclock timer.
+	 * If the Ctlr .poll member is non-zero, when the
+	 * Uart is enabled interrupts will not be enabled
+	 * and the result is polled input and output.
+	 * Not very useful here, but ports to new hardware
+	 * or simulators can use this to get serial I/O
+	 * without setting up the interrupt mechanism.
+	 */
+	ctlr = uart->regs;
+	if(ctlr->iena || !ctlr->poll)
+		return;
+	i8250interrupt(uart);
+}
+#endif
+
+PhysUart i8250physuart = {
+	.name		= "i8250",
+	.pnp		= i8250pnp,
+	.enable		= i8250enable,
+	.disable	= i8250disable,
+	.kick		= i8250kick,
+	.dobreak	= i8250break,
+	.baud		= i8250baud,
+	.bits		= i8250bits,
+	.stop		= i8250stop,
+	.parity		= i8250parity,
+	.modemctl	= i8250modemctl,
+	.rts		= i8250rts,
+	.dtr		= i8250dtr,
+	.status		= i8250status,
+	.fifo		= i8250fifo,
+	.getc		= i8250getc,
+	.putc		= i8250putc,
+#ifdef PLAN9K
+	.poll		= i8250poll,
+#endif
+};
+
+static void
+i8250dumpregs(Ctlr* ctlr)
+{
+	int dlm, dll;
+	int _uartprint(char*, ...);
+
+	csr8w(ctlr, Lcr, Dlab);
+	dlm = csr8r(ctlr, Dlm);
+	dll = csr8r(ctlr, Dll);
+	csr8w(ctlr, Lcr, 0);
+
+	_uartprint("dlm %#ux dll %#ux\n", dlm, dll);
+}
+
+Uart*	uartenable(Uart *p);
+
+/* must call this from a process's context */
+int
+i8250console(void)
+{
+	Uart *uart;
+
+	if (up == nil)
+		return -1;			/* too early */
+
+	uart = &i8250uart[CONSOLE];
+	if(uartenable(uart) != nil && uart->console){
+		kbdq = uart->iq;
+		assert(kbdq);
+		serialoq = uart->oq;
+		assert(serialoq);
+		uart->putc = kbdcr2nl;
+		uart->opens++;
+		consuart = uart;
+		/* up wasn't set when chandevreset ran, so enable it now */
+		i8250disable(uart);
+		i8250enable(uart, 1);
+	}
+	uartctl(uart, "b115200 l8 pn m0 s1 i128 w100");
+	return 0;
+}
+
+void
+_uartputs(char* s, int n)
+{
+	char *e;
+
+	for(e = s+n; s < e; s++){
+		if(*s == '\n')
+			i8250putc(&i8250uart[CONSOLE], '\r');
+		i8250putc(&i8250uart[CONSOLE], *s);
+	}
+}
+
+int
+_uartprint(char* fmt, ...)
+{
+	int n;
+	va_list arg;
+	char buf[PRINTSIZE];
+
+	va_start(arg, fmt);
+	n = vseprint(buf, buf+sizeof(buf), fmt, arg) - buf;
+	va_end(arg);
+	_uartputs(buf, n);
+
+	return n;
+}
+
+void (*lprint)(char *, int) = _uartputs;

+ 73 - 0
sys/src/9/rb/words

@@ -0,0 +1,73 @@
+mikrotik rb450g routerboard
+
+atheros ar7161 cpu: mips 24Kc v3.5 rev 0: 30 dec 2005,
+	big-endian, 32-bit only, 8- or 9-stage pipeline; 8kb cache
+	option, but no l2 support, no relocatable reset exception
+	vector, no UserLocal register, no wait with interrupts
+	disabled, no errata fixes.
+no fpu
+256mb dram
+mmu 16 tlbs
+l1 i-cache 4 ways, 512 sets, 32 bytes per line = 64K
+l1 d-cache 4 ways, 256 sets, 32 bytes per line = 32k
+config1: 0x9ee3519e
+config2: 0x80000000 (no l2 cache)
+config3: 0x20	(VInt: vectored interrupts available)
+config7: 0	(wait instruction not interruptible)
+cause:	0x50008018 (original interrupt behaviour)
+intctl: 0	(original interrupt behaviour)
+srsctl, srsmap are 0
+perf ctl 0: 0x80000000
+perf ctl 1: 0x0
+ll/sc target memory must be cached.
+
+1 uart		8250 (actually 16550ish)
+2 ethers	arge[01] ar71xx, 2nd has 4 ports & a bridge
+pci bus(es)
+no video
+no disk but has flash, alas
+
+addresses
+
+devices; access by KSEG1|addr
+0x10000000	pci memory space
+0x18020000	uart 16550 in apb space
+0x19000000	ether arge1, phy0-3
+0x1a000000	ether arge0, phy4
+0x1b000000	ehci
+0x1c000000	ochi
+0x1f000000	spi
+
+0x80060000	virtual start addr (linux)
+0xbfc00000	prom
+
+irqs
+2	pci
+3	ehci
+4	arge1 ar71xx @ 0x19:: + ar8316 switch
+5	arge0 ar71xx @ 0x1a::
+6	uart
+7	clock
+
+apb intrs
+0	timer
+1	error
+2	gpio
+3	uart
+4	watchdog
+5	hwpmc
+6	ohci
+7	dma
+
+for previous mips assembler code, see
+	/1998/0101/sys/src/boot/carrera
+	/1998/0101/sys/src/brazil/carrera
+	/2000/0615/sys/src/9/carrera
+	/n/fornaxdump/1997/1201/sys/src/brazil/magnum
+
+for ether, see
+	/o/bsd/free/sys/mips/atheros/if_arge.c
+	/o/bsd/free/sys/mips/atheros/if_argevar.h
+	/o/bsd/free/sys/mips/atheros/ar71xxreg.h
+	/o/bsd/free/sys/dev/etherswitch/*
+	/o/openwrt/target/linux/ar71xx/files/drivers/net/ethernet/atheros/ag71xx

+ 24 - 3
sys/src/cmd/spell/local

@@ -51,8 +51,8 @@ ASCII	d,nopref
 ASR	pc,nopref
 ASR	pc,nopref
 Assoc	d,nopref
 Assoc	d,nopref
 Asthana	pc,nopref
 Asthana	pc,nopref
-Atal	n,nopref
 AT&TTJ	pc,nopref
 AT&TTJ	pc,nopref
+Atal	n,nopref
 Audix	pc,nopref
 Audix	pc,nopref
 autodecrement	v,er
 autodecrement	v,er
 autoincrement	v,er
 autoincrement	v,er
@@ -157,6 +157,7 @@ codimension	n,na
 coinbox	n
 coinbox	n
 Coker	n,nopref
 Coker	n,nopref
 Colbry	n,nopref
 Colbry	n,nopref
+Collyer	n,nopref
 Comm	d,nopref
 Comm	d,nopref
 compand	n
 compand	n
 compandor	n
 compandor	n
@@ -229,6 +230,7 @@ download	v,er,va
 DPCM	pc,nopref
 DPCM	pc,nopref
 DSP	n,nopref
 DSP	n,nopref
 Dunellen	pc,nopref
 Dunellen	pc,nopref
+e.g	n,nopref
 Earley	n,nopref
 Earley	n,nopref
 EBCDIC	pc,nopref
 EBCDIC	pc,nopref
 ECC	n,nopref
 ECC	n,nopref
@@ -243,6 +245,8 @@ EISPACK	pc,nopref
 elementwise	d
 elementwise	d
 Elko	pc,nopref
 Elko	pc,nopref
 Emlin	pc,nopref
 Emlin	pc,nopref
+endian	a
+executable	n
 EOF	n,nopref
 EOF	n,nopref
 EOT	n,nopref
 EOT	n,nopref
 Erdos	pc,nopref
 Erdos	pc,nopref
@@ -270,6 +274,7 @@ Flemington	pc,nopref
 Florham	d,nopref
 Florham	d,nopref
 FOCS	d,nopref
 FOCS	d,nopref
 Fostik	n,nopref
 Fostik	n,nopref
+FPU	n
 Fredman	n,nopref
 Fredman	n,nopref
 Freeny	n,nopref
 Freeny	n,nopref
 Frobenius	pc,nopref
 Frobenius	pc,nopref
@@ -284,6 +289,7 @@ GCOS	pc,nopref
 Geffrard	n,nopref
 Geffrard	n,nopref
 Gehani	n,nopref
 Gehani	n,nopref
 GEI	pc,nopref
 GEI	pc,nopref
+Geoff	n,nopref
 Gerardo	pc,nopref
 Gerardo	pc,nopref
 Geri	pc,nopref
 Geri	pc,nopref
 Gersho	n,nopref
 Gersho	n,nopref
@@ -333,6 +339,7 @@ Hunterdon	pc,nopref
 Hwang	pc,nopref
 Hwang	pc,nopref
 hypertext	n
 hypertext	n
 Hz	pc,nopref
 Hz	pc,nopref
+i.e	n,nopref
 IC	n,nopref
 IC	n,nopref
 Ichbiah	pc,nopref
 Ichbiah	pc,nopref
 ICL	pc,nopref
 ICL	pc,nopref
@@ -369,6 +376,7 @@ Juergen	pc,nopref
 Jukl	n,nopref
 Jukl	n,nopref
 Julesz	n,nopref
 Julesz	n,nopref
 junctor	n
 junctor	n
+K&R	pc,nopref
 Kac	pc,nopref
 Kac	pc,nopref
 Kadota	n,nopref
 Kadota	n,nopref
 Kagi	n,nopref
 Kagi	n,nopref
@@ -396,12 +404,12 @@ Kinnelon	pc,nopref
 Kleene	pc,nopref
 Kleene	pc,nopref
 Klinger	n,nopref
 Klinger	n,nopref
 kludge	pc,y
 kludge	pc,y
+kludgey	a
 Knuth	pc,nopref
 Knuth	pc,nopref
 Kolmogorov	pc
 Kolmogorov	pc
 Korn	pc
 Korn	pc
 Kornfeld	n,nopref
 Kornfeld	n,nopref
 Kosaraju	pc,nopref
 Kosaraju	pc,nopref
-K&R	pc,nopref
 Krishan	pc,nopref
 Krishan	pc,nopref
 Krishnakumar	n,nopref
 Krishnakumar	n,nopref
 Kroon	pc,nopref
 Kroon	pc,nopref
@@ -429,6 +437,10 @@ Ledgard	pc,nopref
 Lehmer	pc,nopref
 Lehmer	pc,nopref
 Lempel	pc,nopref
 Lempel	pc,nopref
 Lesk	n,nopref
 Lesk	n,nopref
+libc	n
+libmach	n
+libmp	n
+libsec	n
 Liberman	n,nopref
 Liberman	n,nopref
 lightpen	n
 lightpen	n
 Lincroft	pc,nopref
 Lincroft	pc,nopref
@@ -436,6 +448,7 @@ Linderman	n,nopref
 linewidth	n
 linewidth	n
 Linhart	n,nopref
 Linhart	n,nopref
 Linotron	pc,nopref
 Linotron	pc,nopref
+linux	n,nopref
 Liskov	pc,nopref
 Liskov	pc,nopref
 Litman	n,nopref
 Litman	n,nopref
 livelock	n,ed
 livelock	n,ed
@@ -492,6 +505,7 @@ MH	pc,nopref
 MHCC	pc,nopref
 MHCC	pc,nopref
 MHz	pc,nopref
 MHz	pc,nopref
 Mihalis	pc,nopref
 Mihalis	pc,nopref
+MII	n
 Millburn	pc,nopref
 Millburn	pc,nopref
 Millington	pc,nopref
 Millington	pc,nopref
 Milne	pc,nopref
 Milne	pc,nopref
@@ -500,6 +514,7 @@ Minkowski	pc,nopref
 MIPS	pc,nopref
 MIPS	pc,nopref
 Mitch	pc,nopref
 Mitch	pc,nopref
 Mitra	n,nopref
 Mitra	n,nopref
+mkfile	n
 ML	pc,nopref
 ML	pc,nopref
 mm	d
 mm	d
 MMU	n,nopref
 MMU	n,nopref
@@ -509,6 +524,7 @@ monoid	n
 Montville	pc,nopref
 Montville	pc,nopref
 MOS	pc,nopref
 MOS	pc,nopref
 Mosteller	pc,nopref
 Mosteller	pc,nopref
+MOVW	n
 MTBF	d,nopref
 MTBF	d,nopref
 MTS	n,nopref
 MTS	n,nopref
 Multics	pc,nopref
 Multics	pc,nopref
@@ -533,6 +549,7 @@ Netcong	pc,nopref
 Netravali	n,nopref
 Netravali	n,nopref
 newline	n
 newline	n
 NMOS	pc,nopref
 NMOS	pc,nopref
+NOP	n
 NP	pc,nopref
 NP	pc,nopref
 NPA	pc,nopref
 NPA	pc,nopref
 NPL	pc,nopref
 NPL	pc,nopref
@@ -544,12 +561,12 @@ NTT	pc,nopref
 nullary	d
 nullary	d
 Nutley	pc,nopref
 Nutley	pc,nopref
 NYNEX	pc,nopref
 NYNEX	pc,nopref
+O'Gorman	pc,nopref
 OCLC	pc,nopref
 OCLC	pc,nopref
 OCR	pc,nopref
 OCR	pc,nopref
 Odlyzko	n,nopref
 Odlyzko	n,nopref
 OEM	n,nopref
 OEM	n,nopref
 Ogielski	n,nopref
 Ogielski	n,nopref
-O'Gorman	pc,nopref
 Oldwick	pc,nopref
 Oldwick	pc,nopref
 ONR	pc,nopref
 ONR	pc,nopref
 OOH	pc,nopref
 OOH	pc,nopref
@@ -586,6 +603,7 @@ perceptron	n
 Perlis	pc,nopref
 Perlis	pc,nopref
 Petajan	n,nopref
 Petajan	n,nopref
 phreak	n
 phreak	n
+PHY	n
 Phys	pc,nopref
 Phys	pc,nopref
 Pierrehumbert	n,nopref
 Pierrehumbert	n,nopref
 Pinson	n,nopref
 Pinson	n,nopref
@@ -706,6 +724,7 @@ Skokie	pc,nopref
 Sleator	pc,nopref
 Sleator	pc,nopref
 Slepian	n,nopref
 Slepian	n,nopref
 Snobol	pc,nopref
 Snobol	pc,nopref
+SoC	n,nopref
 Softech	pc,nopref
 Softech	pc,nopref
 Sondhi	n,nopref
 Sondhi	n,nopref
 Speakerphone	pc,nopref
 Speakerphone	pc,nopref
@@ -746,6 +765,7 @@ Tewksbury	pc,nopref
 Thu	pc,nopref
 Thu	pc,nopref
 Tillman	n,nopref
 Tillman	n,nopref
 Tishby	n,nopref
 Tishby	n,nopref
+TLB	n
 TM	s,nopref
 TM	s,nopref
 Toeplitz	pc,nopref
 Toeplitz	pc,nopref
 TOMS	pc,nopref
 TOMS	pc,nopref
@@ -809,6 +829,7 @@ Winterbottom	n
 wirewrap	n,v,ms
 wirewrap	n,v,ms
 Wirth	pc,nopref
 Wirth	pc,nopref
 Witsenhausen	n,nopref
 Witsenhausen	n,nopref
+workaround	n,nopref
 Wulf	pc,nopref
 Wulf	pc,nopref
 Wyk	n,nopref
 Wyk	n,nopref
 Yannakakis	n,nopref
 Yannakakis	n,nopref