shithub: n900

Download patch

ref: d0fb274a9820f51d4fe0eb8cd13c6f0c53c0a3df
author: mia soweli <[email protected]>
date: Sun May 28 09:01:01 EDT 2023

*

--- /dev/null
+++ b/dat.h
@@ -1,0 +1,147 @@
+typedef struct Conf Conf;
+typedef struct Confmem Confmem;
+typedef struct FPsave FPsave;
+typedef struct Label Label;
+typedef struct Lock Lock;
+typedef struct Mach Mach;
+typedef struct MMMU MMMU;
+typedef struct Page Page;
+typedef struct Proc Proc;
+typedef struct PFPU PFPU;
+typedef struct PMMU PMMU;
+typedef struct Ureg Ureg;
+
+typedef u32int PTE;
+typedef uvlong Tval;
+
+#pragma incomplete Ureg
+
+#define MAXSYSARG 5
+#define AOUT_MAGIC (E_MAGIC)
+
+struct Lock {
+	ulong key;
+	u32int sr;
+	uintptr pc;
+	Proc *p;
+	Mach *m;
+	int isilock;
+};
+
+struct Label {
+	uintptr sp;
+	uintptr pc;
+};
+
+struct Confmem {
+	uintptr base;
+	uintptr limit;
+	uintptr kbase;
+	uintptr klimit;
+	ulong npage;
+};
+
+struct Conf {
+	ulong nmach;
+	ulong nproc;
+	Confmem mem[1];
+	ulong npage;
+	ulong upages;
+	ulong copymode;
+	ulong ialloc;
+	ulong pipeqsize;
+	ulong nimage;
+	ulong nswap;
+	int nswppo;
+	int monitor;
+};
+
+struct FPsave {
+	ulong exc;
+	ulong scr;
+	uchar regs[256];
+};
+
+struct PFPU {
+	enum {
+		FPinit,
+		FPactive,
+		FPinactive,
+
+		FPillegal = 0x100,
+	} fpstate;
+	FPsave fpsave[1];
+};
+
+#define NCOLOR 1
+struct PMMU {
+	Page *mmul2;
+	Page *mmul2cache;
+};
+
+struct MMMU {
+	PTE *mmul1;
+	uint mmupid;
+};
+
+#include "../port/portdat.h"
+
+struct Mach {
+	int machno;
+	uintptr splpc;
+	Proc *proc;
+	MMMU;
+
+	PMach;
+
+	u32int save[5];
+	uintptr stack[1];
+};
+
+typedef struct ISAConf ISAConf;
+typedef struct Devport Devport;
+typedef struct DevConf DevConf;
+
+#define BUSUNKNOWN 0
+#define BUSMODEM 1
+
+#define NISAOPT 8
+struct ISAConf {
+	char *type;
+	uintptr port;
+	int irq;
+	ulong dma;
+	ulong mem;
+	ulong size;
+	ulong freq;
+
+	int nopt;
+	char *opt[NISAOPT];
+};
+
+struct Devport {
+	ulong port;
+	int size;
+};
+
+struct DevConf {
+	ulong intnum;
+	char *type;
+	int nports;
+	Devport *ports;
+};
+
+typedef void KMap;
+#define VA(p) ((uintptr)(p))
+#define kmap(p) (KMap*)((p)->pa|KZERO)
+#define kunmap(p)
+#define kmapinval()
+#define getpgcolor(p) 0
+
+struct {
+	char machs[MAXMACH];
+	int exiting;
+} active;
+
+extern register Mach *m;
+extern register Proc *up;
--- /dev/null
+++ b/devkbd.c
@@ -1,0 +1,243 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/i2c.h"
+
+enum {
+	Rctrl		= 0xd2,
+		Csoftreset	= 1<<0,
+		Csoftmode	= 1<<1,
+		Cenable		= 1<<6,
+	Rcode	= 0xdb,
+	Risr		= 0xe3,
+	Rimr		= 0xe4,
+		Ikp			= 1<<0,
+		Ilk			= 1<<1,
+		Ito			= 1<<2,
+	Rsir		= 0xe7,
+	Redr		= 0xe8,
+		Ekpfalling		= 1<<0,
+		Ekprising		= 1<<1,
+		Elkfalling		= 1<<2,
+		Elkrising		= 1<<3,
+		Etofalling		= 1<<4,
+		Etorising		= 1<<5,
+		Emisfalling	= 1<<6,
+		Emisrising	= 1<<7,
+	Rsih		= 0xe9,
+		Scor			= 1<<2,
+};
+
+enum {
+	Qdir,
+	Qscan,
+};
+
+typedef struct Ctlr Ctlr;
+struct Ctlr {
+	Ref;
+	Lock;
+
+	I2Cdev *dev;
+	Queue *q;
+
+	uchar cur[8];
+	uchar prev[8];
+};
+
+static Ctlr ctlr;
+static Dirtab kbdtab[] = {
+	".",			{Qdir, 0, QTDIR},		0, 0555,
+	"scancode",	{Qscan, 0, QTFILE},	0, 0440,
+};
+
+static u8int
+csr8r(Ctlr *ctlr, u8int r)
+{
+	uchar buf;
+
+	i2crecv(ctlr->dev, &buf, sizeof(buf), r);
+	return buf;
+}
+
+static u8int
+csr8w(Ctlr *ctlr, u8int r, u8int w)
+{
+	i2csend(ctlr->dev, &w, sizeof(w), r);
+	return w;
+}
+
+static void
+kbdinterrupt(Ureg *, void*)
+{
+	int i, j, c, k;
+
+	ilock(&ctlr);
+	if(!(csr8r(&ctlr, Risr) & Ikp)) {
+		iunlock(&ctlr);
+		return;
+	}
+
+	/* scan key columns */
+	for(i = 0; i < 8; i++) {
+		ctlr.prev[i] = ctlr.cur[i];
+		ctlr.cur[i] = csr8r(&ctlr, Rcode + i);
+
+		/* changed? */
+		c = ctlr.cur[i] ^ ctlr.prev[i];
+		if(!c)
+			continue;
+
+		/* scan key rows */
+		for(j = 0; j < 8; j++) {
+			if(!(c & (1 << j)))
+				continue;
+
+			/* pressed or released? */
+			k = i << 3 | j;
+			if(ctlr.prev[i] & (1 << j))
+				k |= 0x80;
+
+			qproduce(ctlr.q, &k, 1);
+		}
+	}
+
+	iunlock(&ctlr);
+}
+
+static void
+kbdreset(void)
+{
+	ilock(&ctlr);
+	ctlr.q = qopen(1024, Qcoalesce, 0, 0);
+	if(!ctlr.q) {
+		iunlock(&ctlr);
+		return;
+	}
+
+	ctlr.dev = i2cdev(i2cbus("i2c1"), 0x4a);
+	if(!ctlr.dev) {
+		iunlock(&ctlr);
+		return;
+	}
+
+	ctlr.dev->subaddr = 1;
+	ctlr.dev->size = 0x100;
+
+	csr8w(&ctlr, Rctrl, Csoftreset | Csoftmode | Cenable);
+	csr8w(&ctlr, Rsih, Scor);
+	csr8w(&ctlr, Rimr, ~Ikp);
+
+	intrenable(IRQTWL, kbdinterrupt, nil, BUSUNKNOWN, "kbd");
+	iunlock(&ctlr);
+}
+
+static void
+kbdshutdown(void)
+{
+}
+
+static Chan *
+kbdattach(char *spec)
+{
+	if(!ctlr.dev)
+		error(Enonexist);
+
+	return devattach(L'b', spec);
+}
+
+static Walkqid *
+kbdwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, kbdtab, nelem(kbdtab), devgen);
+}
+
+static int
+kbdstat(Chan *c, uchar *dp, int n)
+{
+	return devstat(c, dp, n, kbdtab, nelem(kbdtab), devgen);
+}
+
+static Chan *
+kbdopen(Chan *c, int mode)
+{
+	if(!iseve)
+		error(Eperm);
+
+	if(c->qid.path == Qscan) {
+		if(waserror()) {
+			decref(&ctlr);
+			nexterror();
+		}
+
+		if(incref(&ctlr) != 1)
+			error(Einuse);
+
+		c = devopen(c, mode, kbdtab, nelem(kbdtab), devgen);
+		poperror();
+		return c;
+	}
+
+	return devopen(c, mode, kbdtab, nelem(kbdtab), devgen);
+}
+
+static void
+kbdclose(Chan *c)
+{
+	if((c->flag & COPEN) && c->qid.path == Qscan)
+		decref(&ctlr);
+}
+
+static Block*
+kbdbread(Chan *c, long n, ulong off)
+{
+	if(c->qid.path == Qscan)
+		return qbread(ctlr.q, n);
+
+	return devbread(c, n, off);
+}
+
+static long
+kbdread(Chan *c, void *a, long n, vlong)
+{
+	if(c->qid.path == Qscan)
+		return qread(ctlr.q, a, n);
+
+	if(c->qid.path == Qdir)
+		return devdirread(c, a, n, kbdtab, nelem(kbdtab), devgen);
+
+	error(Egreg);
+	return 0;
+}
+
+static long
+kbdwrite(Chan *, void *, long, vlong)
+{
+	error(Egreg);
+	return 0;
+}
+
+Dev kbddevtab = {
+	L'b',
+	"kbd",
+
+	kbdreset,
+	devinit,
+	kbdshutdown,
+	kbdattach,
+	kbdwalk,
+	kbdstat,
+	kbdopen,
+	devcreate,
+	kbdclose,
+	kbdread,
+	kbdbread,
+	kbdwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
--- /dev/null
+++ b/devrtc.c
@@ -1,0 +1,202 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/i2c.h"
+
+enum {
+	Rsec		= 0x1c,
+	Rmin	= 0x1d,
+	Rhour	= 0x1e,
+	Rday	= 0x1f,
+	Rmonth	= 0x20,
+	Ryear	= 0x21,
+	Rweeks	= 0x22,
+	Rctrl		= 0x29,
+		Cget		= 1<<6,
+
+	Qdir = 0,
+	Qrtc,
+
+	SecMin	= 60,
+	SecHour	= 60*SecMin,
+	SecDay	= 24*SecHour,
+};
+
+typedef struct Ctlr Ctlr;
+struct Ctlr {
+	I2Cdev *dev;
+
+	int sec;
+	int min;
+	int hour;
+	int day;
+	int month;
+	int year;
+};
+
+static Ctlr ctlr;
+static int dmsize[] = { 365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+static int ldmsize[] = { 366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+static Dirtab rtctab[] = {
+	".",		{Qdir, 0, QTDIR},	0, 0555,
+	"rtc",	{Qrtc, 0, QTFILE},	0, 0440,
+};
+
+#define bcddec(x) (((x) & 0xf) + ((x) >> 4) * 10)
+#define bcdenc(x) (((x / 10) << 4) + (x) % 10)
+#define leap(x) (((x) % 4) == 0 && ((x % 100) != 0 || (x % 400) == 0))
+
+static u8int
+csr8r(Ctlr *ctlr, u8int r)
+{
+	uchar buf;
+
+	i2crecv(ctlr->dev, &buf, sizeof(buf), r);
+	return buf;
+}
+
+static u8int
+csr8w(Ctlr *ctlr, u8int r, u8int w)
+{
+	i2csend(ctlr->dev, &w, sizeof(w), r);
+	return w;
+}
+
+static vlong
+rtcsnarf(void)
+{
+	vlong s;
+	int i;
+
+	/* latch and snarf */
+	csr8w(&ctlr, Rctrl, csr8r(&ctlr, Rctrl) | Cget);
+	ctlr.sec = bcddec(csr8r(&ctlr, Rsec)) % 60;
+	ctlr.min = bcddec(csr8r(&ctlr, Rmin)) % 60;
+	ctlr.hour = bcddec(csr8r(&ctlr, Rhour)) % 24;
+	ctlr.day = bcddec(csr8r(&ctlr, Rday));
+	ctlr.month = bcddec(csr8r(&ctlr, Rmonth));
+	ctlr.year = bcddec(csr8r(&ctlr, Ryear)) % 100;
+	ctlr.year += 2000;
+
+	/* seconds per year */
+	s = 0;
+	for(i = 1970; i < ctlr.year; i++) {
+		if(leap(i))
+			s += ldmsize[0] * SecDay;
+		else
+			s += dmsize[0] * SecDay;
+	}
+
+	/* seconds per month */
+	for(i = 1; i < ctlr.month; i++) {
+		if(leap(ctlr.year))
+			s += ldmsize[i] * SecDay;
+		else
+			s += dmsize[i] * SecDay;
+	}
+
+	/* days, hours, minutes, seconds */
+	s += (ctlr.day - 1) * SecDay;
+	s += ctlr.hour * SecHour;
+	s += ctlr.min * SecMin;
+	s += ctlr.sec;
+	return s;
+}
+
+static void
+rtcreset(void)
+{
+	ctlr.dev = i2cdev(i2cbus("i2c1"), 0x4b);
+	if(!ctlr.dev)
+		return;
+
+	ctlr.dev->subaddr = 1;
+	ctlr.dev->size = 0x100;
+}
+
+static void
+rtcshutdown(void)
+{
+}
+
+static Chan *
+rtcattach(char *spec)
+{
+	if(!ctlr.dev)
+		error(Enonexist);
+
+	return devattach('r', spec);
+}
+
+static Walkqid *
+rtcwalk(Chan *c, Chan *nc, char **name, int nname)
+{
+	return devwalk(c, nc, name, nname, rtctab, nelem(rtctab), devgen);
+}
+
+static int
+rtcstat(Chan *c, uchar *dp, int n)
+{
+	return devstat(c, dp, n, rtctab, nelem(rtctab), devgen);
+}
+
+static Chan *
+rtcopen(Chan *c, int mode)
+{
+	mode = openmode(mode);
+	if(c->qid.path == Qrtc) {
+		if(!iseve() && mode != OREAD)
+			error(Eperm);
+	}
+
+	return devopen(c, mode, rtctab, nelem(rtctab), devgen);
+}
+
+static void
+rtcclose(Chan *)
+{
+}
+
+static long
+rtcread(Chan *c, void *a, long n, vlong off)
+{
+	if(c->qid.path == Qdir)
+		return devdirread(c, a, n, rtctab, nelem(rtctab), devgen);
+	if(c->qid.path == Qrtc)
+		return readnum(off, a, n, rtcsnarf(), 12);
+
+	error(Egreg);
+	return 0;
+}
+
+static long
+rtcwrite(Chan *, void *, long, vlong)
+{
+	error(Egreg);
+	return 0;
+}
+
+Dev rtcdevtab = {
+	'r',
+	"rtc",
+
+	rtcreset,
+	devinit,
+	rtcshutdown,
+	rtcattach,
+	rtcwalk,
+	rtcstat,
+	rtcopen,
+	devcreate,
+	rtcclose,
+	rtcread,
+	devbread,
+	rtcwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
--- /dev/null
+++ b/fns.h
@@ -1,0 +1,77 @@
+#include "../port/portfns.h"
+
+#define KADDR(a) ((void*)(a))
+#define PADDR(a) ((uintptr)(a))
+
+#define userureg(ur) (((ur)->psr & PsrMask) == PsrMusr)
+
+void*	ucallocalign(usize, int, usize);
+void*	ucalloc(usize);
+
+int cmpswap(long *, long, long);
+int cas(long *, long, long);
+int tas(void *);
+
+void evenaddr(uintptr va);
+void procrestore(Proc *);
+void procsave(Proc *);
+void procsetup(Proc *);
+void procfork(Proc *);
+
+void coherence(void);
+void idlehands(void);
+void touser(void*);
+void setR13(uint, u32int*);
+ulong getdfsr(void);
+ulong getifsr(void);
+uintptr getdfar(void);
+uintptr getifar(void);
+void setvectors(uintptr);
+void breakpt(void);
+
+char* getconf(char*);
+int isaconfig(char *, int, ISAConf *);
+
+void mmuinvalidate(void);
+void* mmuuncache(void*, usize);
+uintptr cankaddr(uintptr);
+
+ulong µs(void);
+void delay(int);
+void microdelay(int);
+void cycles(uvlong*);
+
+void dumpureg(Ureg*);
+void dumpstackureg(Ureg*);
+
+void intrenable(int, void (*f)(Ureg *, void*), void *, int, char*);
+void intrdisable(int, void (*f)(Ureg *, void*), void *, int, char*);
+void intr(Ureg *);
+
+void uartinit(void);
+void mmuinit(void);
+void trapinit(void);
+void intrinit(void);
+void timerinit(void);
+void screeninit(void);
+
+void links(void);
+
+void l1icacheinv(void);
+void l1dcachewb(void);
+void l1dcacheinv(void);
+void l1dcachewbinv(void);
+void l1ucachewbinv(void);
+
+void l2idcacheinv(void);
+void l2idcachewb(void);
+void l2idcachewbinv(void);
+void l2ucachewbinv(void);
+
+
+void fpinit(void);
+void fpoff(void);
+void fpclear(void);
+
+void fpsave(FPsave *);
+void fprestore(FPsave *);
--- /dev/null
+++ b/i2cn900.c
@@ -1,0 +1,224 @@
+#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/i2c.h"
+
+enum {
+	Rrev	= 0x00,
+	Rie		= 0x04,
+	Ris		= 0x08,
+		Ial		= 1 << 0, /* arbitration lost */
+		Inack	= 1 << 1, /* no acknowledgement */
+		Iardy	= 1 << 2, /* address ready */
+		Irrdy		= 1 << 3, /* receive ready */
+		Ixrdy	= 1 << 4, /* transmit ready */
+		Ibb		= 1 << 12, /* bus busy */
+		Iall		= 0xffff,
+
+	Rwe		= 0x0c,
+	Rsyss	= 0x10,
+		SSreset	= 1 << 0, /* reset status */
+
+	Rbuf	= 0x14,
+	Rcnt	= 0x18,
+	Rdata	= 0x1c,
+	Rsysc	= 0x20,
+		SCreset	= 1 << 1, /* software reset */
+
+	Rcon	= 0x24,
+		Cstt		= 1 << 0, /* start condition */
+		Cstp		= 1 << 1, /* stop condiction */
+		Cxoa3	= 1 << 4, /* expand address */
+		Cxoa2	= 1 << 5,
+		Cxoa1	= 1 << 6,
+		Cxoa0	= 1 << 7,
+		Cxa		= 1 << 8,
+		Ctrx		= 1 << 9, /* transmit mode */
+		Cmst	= 1 << 10, /* master mode */
+		Cstb		= 1 << 11, /* start byte */
+		Cen		= 1 << 15, /* enable */
+
+	Raddr	= 0x2c,
+};
+
+#define csr32r(c, r) ((c)->io[(r)/4])
+#define csr32w(c, r, w) ((c)->io[(r)/4] = (w))
+
+typedef struct Ctlr Ctlr;
+struct Ctlr {
+	u32int *io;
+	ulong irq;
+
+	Rendez;
+};
+
+static Ctlr ctlr[] = {
+	{ .io = (u32int*) PHYSI2C1, .irq = IRQI2C1 },
+	{ .io = (u32int*) PHYSI2C2, .irq = IRQI2C2 },
+	{ .io = (u32int*) PHYSI2C3, .irq = IRQI2C3 },
+};
+
+static void
+n900i2cwaitbus(Ctlr *ctlr)
+{
+	/* FIXME: timeout here? */
+	while(csr32r(ctlr, Ris) & Ibb)
+		;
+}
+
+static int
+n900i2cwaitirq(void *arg)
+{
+	Ctlr *ctlr = arg; return csr32r(ctlr, Ris);
+}
+
+static uint
+n900i2cwait(Ctlr *ctlr)
+{
+	uint s;
+
+	/* FIXME: timeout here? */
+	while(!(s = csr32r(ctlr, Ris))) {
+		if(!up || !islo())
+			continue;
+
+		tsleep(ctlr, n900i2cwaitirq, ctlr, 5);
+	}
+
+	return s;
+}
+
+static void
+n900i2cflush(Ctlr *ctlr)
+{
+	while(csr32r(ctlr, Ris) & Irrdy) {
+		USED(csr32r(ctlr, Rdata));
+		csr32w(ctlr, Ris, Irrdy);
+	}
+}
+
+static void
+n900i2cintr(Ureg *, void *arg)
+{
+	Ctlr *ctlr;
+
+	ctlr = arg;
+	wakeup(ctlr);
+}
+
+static int
+n900i2cinit(I2Cbus *bus)
+{
+	Ctlr *ctlr;
+
+	/* reset the ctlr */
+	ctlr = bus->ctlr;
+	csr32w(ctlr, Rsysc, SCreset);
+	csr32w(ctlr, Rcon, Cen);
+
+	/* FIXME: timeout here? */
+	while(!(csr32r(ctlr, Rsyss) & SSreset))
+		;
+
+	intrenable(ctlr->irq, n900i2cintr, ctlr, 0, bus->name);
+	return 0;
+}
+
+static int
+n900i2cio(I2Cbus *bus, uchar *pkt, int olen, int ilen)
+{
+	Ctlr *ctlr;
+	uint con, addr, stat;
+	uint o;
+
+	ctlr = bus->ctlr;
+	if(olen <= 0 || pkt == nil)
+		return -1;
+
+	o = 0;
+	con = Cen | Cmst | Ctrx | Cstp | Cstt;
+	if((pkt[o] & 0xf8) == 0xf0) {
+		/* 10-bit address: qemu has bugs, nothing on the n900 needs them.
+		 * con |= Cxa;
+		 * addr = ((pkt[o++] & 6) << 7) | pkt[o++];
+		 */
+		return -1;
+	} else {
+		/* 7-bit address */
+		addr = pkt[o++] >> 1;
+	}
+
+	/* wait for bus */
+	n900i2cwaitbus(ctlr);
+
+	/* first attempt to probe, will get nack here if no dev */
+	csr32w(ctlr, Rcnt, olen);
+	csr32w(ctlr, Raddr, addr);
+	csr32w(ctlr, Rcon, con);
+	stat = n900i2cwait(ctlr);
+	if(stat & Inack || stat & Ial) {
+		o = -1; goto err;
+	}
+
+	/* transmit */
+	while(o < olen) {
+		stat = n900i2cwait(ctlr);
+		if(stat == 0 || stat & Inack || stat & Ial) {
+			o = -1; goto err;
+		}
+
+		if(stat & Iardy) {
+			csr32w(ctlr, Ris, Iardy);
+			break;
+		}
+
+		if(stat & Ixrdy) {
+			csr32w(ctlr, Rdata, pkt[o++]);
+			csr32w(ctlr, Ris, Ixrdy);
+		}
+	}
+
+	/* receive */
+	csr32w(ctlr, Rcnt, ilen);
+	csr32w(ctlr, Raddr, addr);
+	csr32w(ctlr, Rcon, Cen | Cmst | Cstp | Cstt);
+	while(o < olen + ilen) {
+		stat = n900i2cwait(ctlr);
+		if(stat == 0 || stat & Inack || stat & Ial) {
+			o = -1; goto err;
+		}
+
+		if(stat & Iardy) {
+			csr32w(ctlr, Ris, Iardy);
+			break;
+		}
+
+		if(stat & Irrdy) {
+			pkt[o++] = csr32r(ctlr, Rdata);
+			csr32w(ctlr, Ris, Irrdy);
+		}
+	}
+
+err:
+	n900i2cflush(ctlr);
+	csr32w(ctlr, Ris, Iall);
+	return o;
+}
+
+void
+i2cn900link(void)
+{
+	int i;
+	static I2Cbus bus[] = {
+		{ "i2c1", 4000000, &ctlr[0], n900i2cinit, n900i2cio },
+		{ "i2c2", 4000000, &ctlr[1], n900i2cinit, n900i2cio },
+		{ "i2c3", 4000000, &ctlr[2], n900i2cinit, n900i2cio },
+	};
+
+	for(i = 0; i < nelem(bus); i++)
+		addi2cbus(&bus[i]);
+}
--- /dev/null
+++ b/init9.s
@@ -1,0 +1,12 @@
+TEXT main(SB), 1, $8
+	MOVW $setR12(SB), R12
+	MOVW $boot(SB), R0
+
+	ADD $12, R13, R1
+
+	MOVW R0, 4(R13)
+	MOVW R1, 8(R13)
+
+	BL startboot(SB)
+_limbo:
+	B _limbo
--- /dev/null
+++ b/intr.c
@@ -1,0 +1,199 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+enum {
+	Rrev			= 0x00,
+	Rsysconf		= 0x10,
+	Rsysstat		= 0x14,
+	Rirq			= 0x40,
+	Rfiq			= 0x44,
+	Rcontrol		= 0x48,
+		Cnewirqgen		= 1<<0,
+	Rprot			= 0x4c,
+	Ridle			= 0x50,
+	Rirqprio		= 0x60,
+	Rfiqprio		= 0x64,
+	Rthreshold		= 0x68,
+
+	Ritr		= 0x80,
+	Rmir		= 0x84,
+	Rmirclear	= 0x88,
+	Rmirset		= 0x8c,
+	Risrset		= 0x90,
+	Risrclear	= 0x94,
+	Rirqpend	= 0x98,
+	Rfiqpend	= 0x9c,
+
+	Rilr		= 0x100,
+};
+
+enum {
+	Nmir = 3,
+	Nitr = 3,
+	Nintrs = 96,
+};
+
+#define Ritrn(n)		(Ritrn + 32*(n))
+#define Rmirn(n)		(Rmirn + 32*(n))
+#define Rmirclearn(n)	(Rmirclear + 32*(n))
+#define Rmirsetn(n)		(Rmirset + 32*(n))
+#define Rirqpendn(n)	(Rirqpend + 32*(n))
+#define Rfiqpendn(n)	(Rfiqpend + 32*(n))
+
+#define Rilrn(n)	(Rilr + 4*(n))
+
+#define csr32r(c, r) ((c)->io[(r)/4])
+#define csr32w(c, r, w) ((c)->io[(r)/4] = (w))
+
+typedef struct Intr Intr;
+typedef struct Ctlr Ctlr;
+
+struct Intr {
+	void (*f)(Ureg *, void *);
+	void *arg;
+	char *name;
+
+	Intr *next;
+};
+
+struct Ctlr {
+	Lock;
+
+	u32int *io;
+
+	Intr *intrs[Nintrs];
+};
+
+static Ctlr ctlrmpu = { .io = (u32int*) PHYSINTRMPU };
+
+void
+intrinit(void)
+{
+	Ctlr *ctlr = &ctlrmpu;
+	int i;
+
+	/* mask all interrupts */
+	for (i = 0; i < Nmir; i++)
+		csr32w(ctlr, Rmirsetn(i), ~0);
+
+	/* protection off, threshold off, set all intrs priority 0, mapped to irq */
+	csr32w(ctlr, Rcontrol, 0);
+	csr32w(ctlr, Rthreshold, 0xff);
+	for (i = 0; i < Nintrs; i++)
+		csr32w(ctlr, Rilrn(i), 0);
+
+	coherence();
+}
+
+void
+intrenable(int n, void (*f)(Ureg *, void *), void *arg, int, char *name)
+{
+	Ctlr *ctlr = &ctlrmpu;
+	Intr *intr;
+
+	if (n >= nelem(ctlr->intrs) || n < 0)
+		panic("intrenable %d", n);
+
+	intr = malloc(sizeof(*intr));
+	if(!intr)
+		panic("intrenable: no memory for interrupt");
+
+	intr->f = f;
+	intr->arg = arg;
+	intr->name = name;
+
+	lock(ctlr);
+
+	/* chain this interrupt */
+	intr->next = ctlr->intrs[n];
+	ctlr->intrs[n] = intr;
+
+	/* new handler assigned, unmask this interrupt */
+	csr32w(ctlr, Rmirclearn(n >> 5), 1 << (n & 31));
+
+	unlock(ctlr);
+	coherence();
+}
+
+void
+intrdisable(int n, void (*f)(Ureg *, void *), void *arg, int, char *name)
+{
+	Ctlr *ctlr = &ctlrmpu;
+	Intr *intr, **ip;
+
+	if (n >= nelem(ctlr->intrs) || n < 0)
+		panic("intrdisable %d", n);
+
+	lock(ctlr);
+	for(ip = &ctlr->intrs[n]; intr = *ip; ip = &intr->next) {
+		if(intr->f == f && intr->arg == arg && strcmp(intr->name, name) == 0) {
+			*ip = intr->next;
+			free(intr);
+			break;
+		}
+	}
+
+	/* no more handlers assigned, mask this interrupt */
+	if(ctlr->intrs[n] == nil)
+		csr32w(ctlr, Rmirsetn(n >> 5), 1 << (n & 31));
+
+	unlock(ctlr);
+	coherence();
+}
+
+void
+intr(Ureg *ureg)
+{
+	Ctlr *ctlr = &ctlrmpu;
+	Intr *intr;
+	int n, h, s;
+
+	h = 0;
+	n = csr32r(ctlr, Rirq) & 0x7f;
+	s = csr32r(ctlr, Rirq) & ~0x7f;
+	if(s) {
+		/* interrupt controller reports spurious interrupt flag. */
+		iprint("cpu%d: spurious interrupt\n", m->machno);
+		csr32w(ctlr, Rcontrol, Cnewirqgen);
+		return;
+	}
+
+	if(n >= nelem(ctlr->intrs)) {
+		iprint("cpu%d: invalid interrupt %d\n", m->machno, n);
+		csr32w(ctlr, Rcontrol, Cnewirqgen);
+		return;
+	}
+
+	/* call all handlers for this interrupt number */
+	for (intr = ctlr->intrs[n]; intr; intr = intr->next) {
+		if(intr->f) {
+			if(islo())
+				panic("trap: islo() in interrupt handler\n");
+
+			intr->f(ureg, intr->arg);
+			if(islo())
+				panic("trap: islo() after interrupt handler\n");
+		}
+
+		h++;
+	}
+
+	csr32w(ctlr, Rcontrol, Cnewirqgen);
+	coherence();
+
+	if(!h) iprint("cpu%d: spurious interrupt %d\n", m->machno, n);
+	if(up) {
+		if(n >= IRQTIMER1 && n <= IRQTIMER11) {
+			if(up->delaysched) {
+				splhi();
+				sched();
+			}
+		} else {
+			preempted();
+		}
+	}
+}
--- /dev/null
+++ b/io.h
@@ -1,0 +1,63 @@
+#define PHYSL4 0x40000000
+#define PHYSL4END 0x50000000
+
+#define PHYSTIMER1 0x48318000
+#define PHYSTIMER2 0x49032000
+#define PHYSTIMER3 0x49034000
+#define PHYSTIMER4 0x49036000
+#define PHYSTIMER5 0x49038000
+#define PHYSTIMER6 0x4903a000
+#define PHYSTIMER7 0x4903c000
+#define PHYSTIMER8 0x4903e000
+#define PHYSTIMER9 0x49040000
+#define PHYSTIMER10 0x48086000
+#define PHYSTIMER11 0x48088000
+
+#define PHYSUART1 0x4806a000
+#define PHYSUART2 0x4806c000
+#define PHYSUART3 0x49020000
+
+#define PHYSI2C1 0x48070000
+#define PHYSI2C2 0x48072000
+#define PHYSI2C3 0x48060000
+
+#define PHYSMMC1 0x4809c000
+#define PHYSMMC2 0x480b4000
+#define PHYSMMC3 0x480ad000
+
+#define PHYSDSS 0x48050000
+
+#define PHYSINTRMODEM 0x480c7000
+#define PHYSINTRMPU 0x48200000
+
+#define PHYSL3 0x68000000
+#define PHYSL3END 0x70000000
+
+#define PHYSMEM 0x80000000
+#define PHYSMEMEND 0x90000000
+
+#define IRQTIMER1 37
+#define IRQTIMER2 38
+#define IRQTIMER3 39
+#define IRQTIMER4 40
+#define IRQTIMER5 41
+#define IRQTIMER6 42
+#define IRQTIMER7 43
+#define IRQTIMER8 44
+#define IRQTIMER9 45
+#define IRQTIMER10 46
+#define IRQTIMER11 47
+
+#define IRQUART1 72
+#define IRQUART2 73
+#define IRQUART3 74
+
+#define IRQI2C1 56
+#define IRQI2C2 57
+#define IRQI2C3 61
+
+#define IRQMMC1 83
+#define IRQMMC2 86
+#define IRQMMC3 94
+
+#define IRQTWL 7
--- /dev/null
+++ b/l.s
@@ -1,0 +1,354 @@
+#include "mem.h"
+#include "io.h"
+
+TEXT _start(SB), $-4
+	/* load static base */
+	MOVW $setR12(SB), R12
+
+	/* fiqs and irqs off, svc mode (cortex a8 trm figure 2.12) */
+	MOVW $(PsrDfiq|PsrDirq|PsrMsvc), R0
+	MOVW R0, CPSR
+
+	/* mmu and l1 caches off (cortex a8 trm table 3.46) */
+	MRC 15, 0, R0, C1, C0, 0
+	BIC $(1<<12), R0 /* level 1 instruction cache */
+	BIC $(1<<1), R0 /* level 1 data cache */
+	BIC $(1<<0), R0 /* mmu */
+	MCR 15, 0, R0, C1, C0, 0
+	ISB
+
+	/* l2 caches off (cortex a8 trm table 3.49) */
+	MCR 15, 0, R0, C1, C0, 1
+	BIC $(1<<1), R0 /* level 2 cache */
+	MCR 15, 0, R0, C1, C0, 1
+	ISB
+
+	/* fill mach with 0 */
+	MOVW $0, R0
+	MOVW $MACH(0), R1
+	MOVW $MACH(MAXMACH), R2
+zeromach:
+	MOVW R0, (R1)
+	ADD $4, R1
+	CMP R1, R2
+	BNE zeromach
+
+	/* fill page tables with 0 */
+	MOVW R0, R0
+	MOVW $MACHL1(0), R1 /* bottom of l1 tables */
+	MOVW $MACHL1(MAXMACH), R2 /* top of l1 tables */
+zeropte:
+	MOVW R0, (R1)
+	ADD $4, R1
+	CMP R1, R2
+	BNE zeropte
+
+	/* fill bss with 0 */
+	MOVW $0, R0
+	MOVW $edata(SB), R1
+	MOVW $end(SB), R2
+zerobss:
+	MOVW R0, (R1)
+	ADD $4, R1
+	CMP R1, R2
+	BNE zerobss
+
+	/* fill page tables for memory:
+	 * 1mb section, cached, buffered, kernel read-write */
+	MOVW $((1<<1)|(1<<2)|(1<<3)|(1<<10)), R1
+	MOVW $PHYSMEM, R2
+	MOVW $(MACHL1(0)+L1X(PHYSMEM)), R3
+	MOVW $(MACHL1(0)+L1X(PHYSMEMEND)), R4
+ptemem:
+	ORR R2, R1, R0
+	MOVW R0, (R3)
+	ADD $(MiB), R2
+	ADD $4, R3
+	CMP R3, R4
+	BNE ptemem
+
+	/* fill page tables for l4 interconnect:
+	 * 1mb section, kernel read-write */
+	MOVW $((1<<1)|(1<<4)|(1<<10)), R1
+	MOVW $PHYSL4, R2
+	MOVW $(MACHL1(0)+L1X(PHYSL4)), R3
+	MOVW $(MACHL1(0)+L1X(PHYSL4END)), R4
+ptel4:
+	ORR R2, R1, R0
+	MOVW R0, (R3)
+	ADD $(MiB), R2
+	ADD $4, R3
+	CMP R3, R4
+	BNE ptel4
+
+	/* fill page tables for l3 interconnect:
+	 * 1mb section, kernel read-write */
+	MOVW $((1<<1)|(1<<4)|(1<<10)), R1
+	MOVW $PHYSL3, R2
+	MOVW $(MACHL1(0)+L1X(PHYSL3)), R3
+	MOVW $(MACHL1(0)+L1X(PHYSL3END)), R4
+ptel3:
+	ORR R2, R1, R0
+	MOVW R0, (R3)
+	ADD $(MiB), R2
+	ADD $4, R3
+	CMP R3, R4
+	BNE ptel3
+
+	/* fpu on (set bits 20-23 in CPACR) but disabled */
+	MRC 15, 0, R0, C1, C0, 2
+	ORR $(0xf<<20), R0
+	MCR 15, 0, R0, C1, C0, 2
+
+	VMRS(FPEXC, 0)
+	BIC $(FPEXCEX|FPEXCEN), R0
+	VMSR(0, FPEXC)
+
+	/* invalidate caches */
+	BL l1dcacheinv(SB)
+	BL l1icacheinv(SB)
+
+	/* l2 caches back on */
+	MRC 15, 0, R0, C1, C0, 1
+	ORR $(1<<1), R0
+	MCR 15, 0, R0, C1, C0, 1
+
+	/* l1 caches back on */
+	MRC 15, 0, R0, C1, C0, 0
+	ORR $(1<<12), R0
+	ORR $(1<<1), R0
+	MCR 15, 0, R0, C1, C0, 0
+
+	/* set domain access control to client. */
+	MOVW $1, R0
+	BL putdac(SB)
+
+	/* set translation table base */
+	MOVW $MACHL1(0), R0
+	BL putttb(SB)
+
+	/* mmu on, time to get virtual */
+	MOVW $virt(SB), R2
+	BL mmuinvalidate(SB)
+	BL mmuenable(SB)
+	MOVW R2, R15
+
+TEXT virt(SB), $-4
+	/* setup register variables */
+	MOVW $setR12(SB), R12
+	MOVW $(MACH(0)), R(Rmach)
+	MOVW $0, R(Rup)
+
+	/* setup stack in mach */
+	MOVW $(MACH(0)), R13
+	ADD $(MACHSZ), R13
+	SUB $4, R13
+
+	BL main(SB)
+
+_limbo:
+	BL idlehands(SB)
+	B _limbo
+
+	/* hack to load div */
+	BL _div(SB)
+
+
+TEXT mmuenable(SB), 1, $-4
+	MRC 15, 0, R0, C1, C0, 0
+	ORR $(1<<0), R0
+	MCR 15, 0, R0, C1, C0, 0
+	MCR 15, 0, R0, C7, C5, 6
+	DMB; DSB; ISB
+	RET
+
+TEXT mmudisable(SB), 1, $-4
+	MRC 15, 0, R0, C1, C0, 0
+	BIC $(1<<0), R0
+	MCR 15, 0, R0, C1, C0, 0
+	MCR 15, 0, R0, C7, C5, 6
+	DMB; DSB; ISB
+	RET
+
+TEXT mmuinvalidate(SB), 1, $-4
+	MOVW CPSR, R1
+	CPSID
+
+	MOVW R15, R0
+	MCR 15, 0, R0, C8, C7, 0
+	MCR 15, 0, R0, C7, C5, 6
+	DMB; DSB; ISB
+
+	MOVW R1, CPSR
+	RET
+
+/* get and put domain access control */
+TEXT getdac(SB), 1, $-4; MCR 15, 0, R0, C3, C0; RET
+TEXT putdac(SB), 1, $-4
+	MRC 15, 0, R0, C3, C0
+	ISB
+	RET
+
+
+/* get and put translation table base */
+TEXT getttb(SB), 1, $-4; MRC 15, 0, R0, C2, C0, 0; RET
+TEXT putttb(SB), 1, $-4
+	MCR 15, 0, R0, C2, C0, 0
+	MCR 15, 0, R0, C2, C0, 1
+	ISB
+	RET
+
+TEXT getdfsr(SB), $-4; MRC 15, 0, R0, C5, C0, 0; RET
+TEXT getifsr(SB), $-4; MRC 15, 0, R0, C5, C0, 1; RET
+TEXT getdfar(SB), $-4; MRC 15, 0, R0, C6, C0, 0; RET
+TEXT getifar(SB), $-4; MRC 15, 0, R0, C6, C0, 2; RET
+
+TEXT setvectors(SB), $-4;
+	MCR 15, 0, R0, C12, C0, 0
+	RET
+
+TEXT setlabel(SB), $-4
+	MOVW R13, 0(R0)
+	MOVW R14, 4(R0)
+	MOVW $0, R0
+	RET
+
+TEXT gotolabel(SB), $-4
+	MOVW 0(R0), R13
+	MOVW 4(R0), R14
+	MOVW $1, R0
+	RET
+
+TEXT cas(SB), $0
+TEXT cmpswap(SB), $0
+	MOVW ov+4(FP), R1
+	MOVW nv+8(FP), R2
+casspin:
+	LDREX (R0), R3
+	CMP R3, R1
+	BNE casfail
+	STREX R2, (R0), R4
+	CMP $0, R4
+	BNE casspin
+	MOVW $1, R0
+	DMB
+	RET
+casfail:
+	CLREX
+	MOVW $0, R0
+	RET
+
+TEXT tas(SB), $0
+TEXT _tas(SB), $0
+	MOVW $0xdeaddead, R2
+tasspin:
+	LDREX (R0), R1
+	STREX R2, (R0), R3
+	CMP $0, R3
+	BNE tasspin
+	MOVW R1, R0
+	DMB
+	RET
+
+TEXT idlehands(SB), $-4
+	DMB; DSB; ISB
+	WFI
+	RET
+
+TEXT coherence(SB), $-4
+	DMB; DSB; ISB
+	RET
+
+TEXT splhi(SB), $-4
+	MOVW R14, 4(R(Rmach))
+	MOVW CPSR, R0
+	CPSID
+	RET
+
+TEXT spllo(SB), $-4
+	MOVW CPSR, R0
+	CPSIE
+	RET
+
+TEXT splx(SB), $-4
+	MOVW R14, 4(R(Rmach))
+	MOVW R0, R1
+	MOVW CPSR, R0
+	MOVW R1, CPSR
+	RET
+
+TEXT spldone(SB), $-4
+	RET
+
+TEXT islo(SB), $0
+	MOVW CPSR, R0
+	AND $(PsrDirq), R0
+	EOR $(PsrDirq), R0
+	RET
+
+TEXT perfticks(SB), $0
+	MCR 15, 0, R0, C9, C13, 0
+	RET
+
+TEXT touser(SB), $-4
+	MOVM.DB.W [R0], (R13)
+	MOVM.S (R13), [R13]
+	ADD $4, R13
+
+	MOVW CPSR, R0
+	BIC $(PsrMask|PsrDirq|PsrDfiq), R0
+	ORR $PsrMusr, R0
+	MOVW R0, SPSR
+
+	MOVW $(UTZERO+0x20), R0
+	MOVM.DB.W [R0], (R13)
+
+	MOVM.IA.S.W (R13), [R15]
+
+TEXT forkret(SB), $-4
+	ADD $(15*4), R13
+	MOVW 8(R13), R14
+	MOVW 4(R13), R0
+	MOVW R0, SPSR
+	MOVM.DB.S (R13), [R0-R14]
+	ADD $8, R13
+	MOVM.IA.S.W (R13), [R15]
+
+TEXT peek(SB), $0
+	MOVW src+0(FP), R0
+	MOVW dst+4(FP), R1
+	MOVW cnt+8(FP), R2
+TEXT _peekinst(SB), $0
+_peekloop:
+	MOVB (R0), R3
+	MOVB R3, (R1)
+	SUB.S $1, R0
+	BNE _peekloop
+	RET
+
+TEXT fpinit(SB), $0
+	MOVW $FPEXCEN, R0
+	VMSR(0, FPEXC)
+	MOVW $0, R0
+	VMSR(0, FPSCR)
+	RET
+
+TEXT fpoff(SB), $0
+TEXT fpclear(SB), $0
+	MOVW $0, R1
+	VMSR(1, FPEXC)
+	RET
+
+TEXT fpsave(SB), $0
+	VMRS(FPEXC, 1)
+	VMRS(FPSCR, 2)
+	MOVM.IA.W [R1-R2], (R0)
+	VSTMIA
+	RET
+
+TEXT fprestore(SB), $0
+	MOVM.IA.W (R0), [R1-R2]
+	VMSR(1, FPEXC)
+	VMSR(2, FPSCR)
+	VLDMIA
+	RET
--- /dev/null
+++ b/lcache.s
@@ -1,0 +1,143 @@
+#include "mem.h"
+#include "io.h"
+
+/* l1 instruction cache invalidate */
+TEXT l1icacheinv(SB), $-4
+	MOVW $0, R0
+	MCR 15, 0, R0, C7, C5, 0
+	ISB
+	RET
+
+/* l1 data cache writeback */
+TEXT l1dcachewb(SB), $-4
+	MOVW $cacheopwb(SB), R0
+	MOVW $0, R1
+	B cacheop(SB)
+
+/* l1 data cache invalidate */
+TEXT l1dcacheinv(SB), $-4
+	MOVW $cacheopinv(SB), R0
+	MOVW $0, R1
+	B cacheop(SB)
+
+/* l1 data cache writeback + invalidate */
+TEXT l1dcachewbinv(SB), $-4
+	MOVW $cacheopwbinv(SB), R0
+	MOVW $0, R1
+	B cacheop(SB)
+
+/* l1 unified instruction + data cache writeback + invalidate */
+TEXT l1ucachewbinv(SB), $-4
+	/* stash and splhi */
+	MOVW CPSR, R0
+	MOVM.DB.W [R0], (SP)
+	MOVM.DB.W [R14], (SP)
+	CPSID
+
+	/* go */
+	BL l1dcachewbinv(SB)
+	BL l1icacheinv(SB)
+
+	/* restore and splx */
+	MOVM.IA.W (SP), [R14]
+	MOVM.IA.W (SP), [R0]
+	MOVW R0, CPSR
+	RET
+
+/* l2 instruction + data cache writeback */
+TEXT l2idcachewb(SB), $-4
+	MOVW $cacheopwb(SB), R0
+	MOVW $1, R1
+	B cacheop(SB)
+
+/* l2 instruction + data cache invalidate */
+TEXT l2idcacheinv(SB), $-4
+	MOVW $cacheopinv(SB), R0
+	MOVW $1, R1
+	B cacheop(SB)
+
+/* l2 instruction + data cache writeback + invalidate */
+TEXT l2idcachewbinv(SB), $-4
+	MOVW $cacheopwbinv(SB), R0
+	MOVW $1, R1
+	B cacheop(SB)
+
+/* l1 unified instruction + data cache writeback + invalidate */
+TEXT l2ucachewbinv(SB), $-4
+	/* stash and splhi */
+	MOVW CPSR, R0
+	MOVM.DB.W [R0], (SP)
+	MOVM.DB.W [R14], (SP)
+	CPSID
+
+	BL l2idcachewbinv(SB)
+	BL l2idcacheinv(SB)
+
+	/* restore and splx */
+	MOVM.IA.W (SP), [R14]
+	MOVM.IA.W (SP), [R0]
+	MOVW R0, CPSR
+	RET
+
+/* set/way operations for cacheop */
+TEXT cacheopwb(SB), $-4;	MCR 15, 0, R0, C7, C10, 2; RET
+TEXT cacheopinv(SB), $-4;	MCR 15, 0, R0, C7, C6, 2; RET
+TEXT cacheopwbinv(SB), $-4;	MCR 15, 0, R0, C7, C14, 2; RET
+
+#define Rop R2
+#define Rcache R3
+#define Rways R4
+#define Rwayshift R5
+#define Rsets R6
+#define Rsetshift R7
+#define Rset R8
+
+/* apply a cache operation to the whole cache */
+TEXT cacheop(SB), $-4
+	/* stash and splhi */
+	MOVW CPSR, R2
+	MOVM.DB.W [R2,R14], (SP)
+	MOVW R0, Rop
+	MOVW R1, Rcache
+	CPSID
+
+	/* get cache geometry */
+	MCR 15, 2, Rcache, C0, C0, 0; ISB
+	MRC 15, 1, R0, C0, C0, 0
+
+	/* compute ways = ((R0 >> 3) & 0x3ff) + 1) */
+	SRA $3, R0, Rways
+	AND $0x3ff, Rways
+	ADD $1, Rways
+
+	/* compute wayshift = log₂(ways) */
+	CLZ(4, 5) /* Rways, Rwayshift */
+	ADD $1, Rwayshift
+
+	/* compute sets = ((R0 >> 13) & 0x7fff) + 1) */
+	SRA $13, R0, Rsets
+	AND $0x7fff, Rsets
+	ADD $1, Rsets
+
+	/* compute setshift = log₂(cache line size) */
+	AND $0x7, R0, Rsetshift
+	ADD $4, Rsetshift
+
+cacheopways:
+	MOVW Rsets, Rset
+cacheopsets:
+	/* compute set / way register contents */
+	SLL Rwayshift, Rways, R0
+	SLL Rsetshift, Rset, R1
+	ORR R1, R0
+	SLL $1, Rcache, R1
+	ORR R1, R0
+
+	BL (Rop)
+	SUB $1, Rset; CMP $0, Rset; BEQ cacheopsets /* loop sets */
+	SUB $1, Rways; CMP $0, Rways; BEQ cacheopways /* loop ways */
+
+	/* restore regs and splx */
+	MOVM.IA.W (SP), [R2,R14]
+	MOVW R2, CPSR
+	RET
--- /dev/null
+++ b/ltrap.s
@@ -1,0 +1,200 @@
+#include "mem.h"
+#include "io.h"
+
+TEXT vectors(SB), $-4
+	MOVW 24(R15), R15
+	MOVW 24(R15), R15
+	MOVW 24(R15), R15
+	MOVW 24(R15), R15
+	MOVW 24(R15), R15
+	MOVW 24(R15), R15
+	MOVW 24(R15), R15
+	MOVW 24(R15), R15
+
+TEXT vtable(SB), $-4
+	WORD $_vund(SB)
+	WORD $_vund(SB)
+	WORD $_vsvc(SB)
+	WORD $_viabt(SB)
+	WORD $_vdabt(SB)
+	WORD $_vund(SB)
+	WORD $_virq(SB)
+	WORD $_vfiq(SB)
+
+TEXT _vsvc(SB), $-4
+	CLREX
+	DSB
+
+	/* stash in ureg */
+	MOVM.DB.W [R14], (R13) /* ureg->pc */
+	MOVW SPSR, R14
+	MOVM.DB.W [R14], (R13) /* ureg->psr */
+	MOVW $PsrMsvc, R14
+	MOVM.DB.W [R14], (R13) /* ureg->type */
+
+	/* save user regs.
+	 * not MOVM.DB.W.S because the saved value of R13 is undefined.
+	 * (arm v7 manual §A8.8.199) */
+	MOVM.DB.S [R0-R14], (R13)
+	SUB $(15*4), R13
+
+	/* get our sb, mach, up */
+	MOVW $setR12(SB), R12
+	MOVW $(MACH(0)), R(Rmach)
+	MOVW 8(R(Rmach)), R(Rup)
+
+	/* make space for debugger and go to syscall passing ureg */
+	MOVW R13, R0
+	SUB $8, R13
+	BL syscall(SB)
+	ADD $8, R13
+
+	/* restore link, spsr */
+	ADD $(15*4), R13
+	MOVW 8(R13), R14
+	MOVW 4(R13), R0
+	MOVW R0, SPSR
+
+	/* restore user regs */
+	MOVM.DB.S (R13), [R0-R14]
+
+	/* pop past ureg->type, ureg->psr and restore ureg->pc.
+	 * omap and others have RFE here but 5a has no idea about newer instructions
+	 * and simulates it with the MOVM below */
+	ADD $8, R13
+	MOVM.IA.S.W (R13), [R15]
+
+TEXT _viabt(SB), $-4
+	CLREX
+	DSB
+	MOVM.IA [R0-R4], (R13)
+	MOVW $PsrMiabt, R0
+	B _vswitch
+
+TEXT _vdabt(SB), $-4
+	CLREX
+	DSB
+	MOVM.IA [R0-R4], (R13)
+	MOVW $PsrMdabt, R0
+	B _vswitch
+
+TEXT _virq(SB), $-4
+	DSB
+	MOVM.IA [R0-R4], (R13)
+	MOVW $PsrMirq, R0
+	B _vswitch
+
+TEXT _vfiq(SB), $-4
+	CLREX
+	DSB
+	MOVM.IA [R0-R4], (R13)
+	MOVW $PsrMfiq, R0
+	B _vswitch
+
+TEXT _vund(SB), $-4
+	CLREX
+	DSB
+	MOVM.IA [R0-R4], (R13)
+	MOVW $PsrMund, R0
+	B _vswitch
+
+_vswitch:
+	/* stash pointer to previous R0-R4, stash SPSR and R14 for ureg */
+	MOVW SPSR, R1
+	MOVW R14, R2
+	MOVW R13, R3
+
+	/* back to svc mode */
+	MOVW CPSR, R14
+	BIC $PsrMask, R14
+	ORR $(PsrDirq|PsrDfiq|PsrMsvc), R14
+	MOVW R14, CPSR
+
+	/* from user or kernel mode? */
+	AND.S $0xf, R1, R4
+	BEQ _vuser
+
+	/* from kernel mode */
+	/* set ureg->type, ureg->psr, ureg->pc */
+	MOVM.DB.W [R0-R2], (R13)
+	MOVM.IA (R3), [R0-R4]
+
+	/* save kernel regs
+	 * not MOVM.DB.W.S because the saved value of R13 is undefined.
+	 * (arm v7 manual §A8.8.199) */
+	MOVM.DB [R0-R14], (R13)
+	SUB $(15*4), R13
+
+	/* get our sb, mach, up */
+	MOVW $setR12(SB), R12
+
+	/* make space for debugger and go to trap passing ureg */
+	MOVW R13, R0
+	SUB $8, R13
+	BL trap(SB)
+	ADD $8, R13
+
+	/* restore link, spsr */
+	ADD $(15*4), R13
+	MOVW 8(R13), R14
+	MOVW 4(R13), R0
+	MOVW R0, SPSR
+
+	/* restore kernel regs */
+	MOVM.DB (R13), [R0-R14]
+
+	/* pop past ureg->type, ureg->psr, and restore ureg->pc. */
+	ADD $8, R13
+	MOVM.IA.S.W (R13), [R15]
+
+_vuser:
+	/* from user mode */
+	/* set ureg->type, ureg->psr, ureg->pc */
+	MOVM.DB.W [R0-R2], (R13)
+	MOVM.IA (R3), [R0-R4]
+
+	/* save kernel regs
+	 * not MOVM.DB.W.S because the saved value of R13 is undefined.
+	 * (arm v7 manual §A8.8.199) */
+	MOVM.DB.S [R0-R14], (R13)
+	SUB $(15*4), R13
+
+	/* get our sb, mach, up */
+	MOVW $setR12(SB), R12
+	MOVW $(MACH(0)), R(Rmach)
+	MOVW 8(R(Rmach)), R(Rup)
+
+	/* make space for debugger and go to trap passing ureg */
+	MOVW R13, R0
+	SUB $8, R13
+	BL trap(SB)
+	ADD $8, R13
+
+	/* restore link, spsr */
+	ADD $(15*4), R13
+	MOVW 8(R13), R14
+	MOVW 4(R13), R0
+	MOVW R0, SPSR
+
+	/* restore kernel regs */
+	MOVM.DB.S (R13), [R0-R14]
+
+	/* pop past ureg->type, ureg->psr, and restore ureg->pc */
+	ADD $8, R13
+	MOVM.IA.S.W (R13), [R15]
+
+TEXT setR13(SB), $-4
+	MOVW 4(FP), R1
+
+	/* switch to new mode */
+	MOVW CPSR, R2
+	BIC $PsrMask, R2, R3
+	ORR R0, R3
+	MOVW R3, CPSR
+
+	/* set r13 */
+	MOVW R1, R13
+
+	/* back to old mode */
+	MOVW R2, CPSR
+	RET
--- /dev/null
+++ b/main.c
@@ -1,0 +1,222 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+#include "tos.h"
+#include "ureg.h"
+#include "pool.h"
+
+#define MAXCONF 64
+
+static char *confname[MAXCONF];
+static char *confval[MAXCONF];
+static int nconf;
+
+Conf conf;
+
+char*
+getconf(char *name)
+{
+	int i;
+
+	for(i = 0; i < nconf; i++)
+		if(cistrcmp(confname[i], name) == 0)
+			return confval[i];
+
+	return nil;
+}
+
+int
+isaconfig(char *, int, ISAConf *)
+{
+	return 0;
+}
+
+void
+cpuidprint(void)
+{
+	/* FIXME: how fast are we really? */
+	print("cpu%d: %dMHz ARM Cortex-A8\n", m->machno, 600);
+}
+
+void
+plan9iniinit(void)
+{
+	char *c, *p, *q;
+	char *v[MAXCONF];
+	int i, n;
+
+	c = (char*) CONFADDR;
+	for(p = q = c; *q; q++) {
+		if(*q == '\r')
+			continue;
+		if(*q == '\t')
+			*q = ' ';
+		*p++ = *q;
+	}
+
+	*p = 0;
+	n = getfields(c, v, MAXCONF, 1, "\n");
+	for(i = 0; i < n; i++) {
+		if(v[i][0] == '#')
+			continue;
+
+		c = strchr(v[i], '=');
+		if(!c)
+			continue;
+
+		confname[nconf] = v[i];
+		confval[nconf] = c;
+		nconf++;
+	}
+}
+
+void
+confinit(void)
+{
+	int i;
+	uintptr pa;
+	ulong kp;
+
+	conf.nmach = 1;
+	conf.npage = 0;
+	conf.mem[0].base = PHYSMEM;
+	conf.mem[0].limit = PHYSMEMEND;
+
+	pa = PADDR(PGROUND((uintptr)end));
+	for(i = 0; i < nelem(conf.mem); i++) {
+		if(pa > conf.mem[i].base && pa < conf.mem[i].limit)
+			conf.mem[i].base = pa;
+
+		conf.mem[i].npage = (conf.mem[i].limit - conf.mem[i].base)/BY2PG;
+		conf.npage += conf.mem[i].npage;
+	}
+
+	conf.upages = (conf.npage*80)/100;
+	conf.ialloc = ((conf.npage-conf.upages)/2)*BY2PG;
+
+	conf.nproc = 100 + ((conf.npage*BY2PG)/MB)*5;
+	if(cpuserver)
+		conf.nproc *= 3;
+	if(conf.nproc > 4000)
+		conf.nproc = 4000;
+
+	conf.nswap = conf.npage*3;
+	conf.nswppo = 4096;
+	conf.nimage = 200;
+	conf.copymode = 0;
+
+	kp = conf.npage - conf.upages;
+	kp *= BY2PG;
+	kp -= conf.upages * sizeof(Page)
+		+ conf.nproc * sizeof(Proc*)
+		+ conf.nimage * sizeof(Image)
+		+ conf.nswap
+		+ conf.nswppo * sizeof(Page*);
+
+	mainmem->maxsize = kp;
+	if(!cpuserver)
+		imagmem->maxsize = kp;
+}
+
+void
+machinit(void)
+{
+	m->machno = 0;
+
+	active.machs[0] = 1;
+	active.exiting = 0;
+
+	up = nil;
+}
+
+void
+init0(void)
+{
+	char **sp, buf[KNAMELEN];
+	int i;
+
+	chandevinit();
+	if(!waserror()) {
+		ksetenv("cputype", "arm", 0);
+		if(cpuserver)
+			ksetenv("service", "cpu", 0);
+		else
+			ksetenv("service", "terminal", 0);
+
+		snprint(buf, sizeof(buf), "nokia %s", conffile);
+		ksetenv("terminal", buf, 0);
+		ksetenv("console", "2", 0);
+		ksetenv("kbmap", "n900", 0);
+		for(i = 0; i < nconf; i++) {
+			if(*confname[i] != '*')
+				ksetenv(confname[i], confval[i], 0);
+			ksetenv(confname[i], confval[i], 1);
+		}
+
+		poperror();
+	}
+
+	kproc("alarm", alarmkproc, 0);
+
+	/* prepare stack for boot */
+	sp = (char**)(USTKTOP - sizeof(Tos) - 8 - sizeof(sp[0])*4);
+	sp[3] = sp[2] = sp[1] = nil;
+	strcpy(sp[0] = (char*)&sp[4], "boot");
+	touser(sp);
+}
+
+void
+main(void)
+{
+	uartinit();
+	machinit();
+	mmuinit();
+	plan9iniinit();
+	confinit();
+	printinit();
+	quotefmtinstall();
+	fmtinstall(L'H', encodefmt);
+	print("\nPlan 9\n");
+
+	xinit();
+	trapinit();
+	intrinit();
+	timerinit();
+	cpuidprint();
+	procinit0();
+	initseg();
+	links();
+
+	screeninit();
+	chandevreset();
+
+	pageinit();
+	userinit();
+	schedinit();
+
+	panic("schedinit returned");
+}
+
+void
+exit(int)
+{
+	for(;;)
+		idlehands();
+}
+
+void
+reboot(void *, void *, ulong)
+{
+}
+
+void
+setupwatchpts(Proc *, Watchpt *, int n)
+{
+	if(n > 0)
+		error("no watchpoints");
+}
--- /dev/null
+++ b/mem.h
@@ -1,0 +1,105 @@
+#define KiB (1024u)
+#define MiB (1024*1024u)
+#define GiB (1024*1024*1024u)
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+#define Rmach 10
+#define Rup 9
+
+#define HZ (100)
+#define MS2HZ (1000/HZ)
+#define TK2SEC(t) ((t)/HZ)
+
+#define CONFADDR 0x80010000
+
+#define KZERO 0x80000000
+#define KTZERO 0x80020000
+#define KSTACK (8*KiB)
+
+#define UZERO 0
+#define UTZERO BY2PG
+#define USTKTOP 0x20000000
+#define USTKSIZE (16*MiB)
+
+#define MAXMACH 1
+#define MACHSZ (16*KiB)
+#define L1SZ (4*4096)
+#define L2SZ (4*256)
+
+#define L1X(va) ((((va)>>20) & 0xfff) << 2)
+
+#define MACH(n) (KZERO+(n)*MACHSZ)
+#define MACHP(n) ((Mach*)MACH(n))
+#define MACHL1(n) (MACH(MAXMACH)+(n)*L1SZ)
+#define MACHVEC(n) (MACHL1(MAXMACH)+(n)*64*4)
+
+#define BY2PG (4*KiB)
+#define BY2SE 4
+#define BY2WD 4
+#define BY2V 8
+
+#define CACHELINEZ 64
+#define BLOCKALIGN 32
+
+#define ROUND(s, sz) (((s)+((sz)-1))&~((sz)-1))
+#define PGROUND(s) ROUND(s, BY2PG)
+#define PGSHIFT 12
+
+#define PTEMAPMEM MiB
+#define PTEPERTAB (PTEMAPMEM/BY2PG)
+#define SEGMAPSIZE 1984
+#define SSEGMAPSIZE 16
+
+#define PPN(p) ((p)&~(BY2PG-1))
+
+#define PTEVALID (1<<0)
+#define PTERONLY (0<<1)
+#define PTEWRITE (1<<1)
+#define PTECACHED (0<<2)
+#define PTEUNCACHED (1<<2)
+#define PTENOEXEC (1<<3)
+
+#define PsrDfiq 0x40
+#define PsrDirq 0x80
+
+#define PsrMusr 0x10
+#define PsrMfiq 0x11
+#define PsrMirq 0x12
+#define PsrMsvc 0x13
+#define PsrMmon 0x16
+#define PsrMiabt 0x17
+#define PsrMdabt 0x18
+#define PsrMund 0x1b
+#define PsrMsys 0x1f
+
+#define PsrMask 0x1f
+
+#define WFI WORD $0xe320f003
+#define DSB WORD $0xf57ff04f
+#define DMB WORD $0xf57ff05f
+#define ISB WORD $0xf57ff06f
+
+#define CPSIE WORD $0xf1080080
+#define CPSID WORD $0xf10c0080
+
+#define CLZ(s, d) WORD $(0xe16f0f10 | (d) << 12 | (s))
+#define VMSR(cpu, fp) WORD $(0xeee00a10|(fp)<<16|(cpu)<<12)
+#define VMRS(fp, cpu) WORD $(0xeef00a10|(fp)<<16|(cpu)<<12)
+
+#define FPSID 0x0
+#define FPSCR 0x1
+#define MVFR1 0x6
+#define MVFR0 0x7
+#define FPEXC 0x8
+#define FPEXCEX (1<<31)
+#define FPEXCEN (1<<30)
+
+/* vlmdia r0!, {d0-d15}
+ * vldmia r0!, {d16-d31} */
+#define VLDMIA WORD $0xecb00b20; WORD $0xecf00b20;
+
+/* vstmia r0!, {d0-d15}
+ * vstmia r0!, {d16-d31} */
+#define VSTMIA WORD $0xeca00b20; WORD $0xece00b20;
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,82 @@
+CONF=n900
+CONFLIST=n900
+
+p=9
+objtype=arm
+ktzero=0x80020000
+
+</$objtype/mkfile
+
+DEVS=`{rc ../port/mkdevlist $CONF}
+PORT=\
+	alarm.$O\
+	alloc.$O\
+	allocb.$O\
+	auth.$O\
+	cache.$O\
+	chan.$O\
+	dev.$O\
+	edf.$O\
+	fault.$O\
+	mul64fract.$O\
+	rebootcmd.$O\
+	page.$O\
+	parse.$O\
+	pgrp.$O\
+	portclock.$O\
+	print.$O\
+	proc.$O\
+	qio.$O\
+	qlock.$O\
+	segment.$O\
+	sysfile.$O\
+	sysproc.$O\
+	taslock.$O\
+	tod.$O\
+	xalloc.$O\
+	random.$O\
+	rdb.$O\
+	syscallfmt.$O\
+	userinit.$O\
+	ucalloc.$O\
+
+OBJ=\
+	l.$O\
+	lcache.$O\
+	ltrap.$O\
+	main.$O\
+	mmu.$O\
+	timer.$O\
+	trap.$O\
+	intr.$O\
+	$CONF.root.$O\
+	$CONF.rootc.$O\
+	$DEVS\
+	$PORT\
+
+LIB=\
+	/$objtype/lib/libmemlayer.a\
+	/$objtype/lib/libmemdraw.a\
+	/$objtype/lib/libdraw.a\
+	/$objtype/lib/libip.a\
+	/$objtype/lib/libsec.a\
+	/$objtype/lib/libmp.a\
+	/$objtype/lib/libc.a\
+	/$objtype/lib/libdtracy.a\
+
+$p$CONF.u:D: $p$CONF
+	aux/aout2uimage -o $target -Z0 $prereq
+
+$p$CONF:D: $OBJ $CONF.$O $LIB
+	$LD -o $target -T$ktzero -l $prereq
+
+<../boot/bootmkfile
+<../port/portmkfile
+<|../port/mkbootrules $CONF
+
+initcode.out: init9.$O initcode.$O /$objtype/lib/libc.a
+	$LD -l -R1 -s -o $target $prereq
+
+install:V: $p$CONF.u
+	cp $p$CONF /$objtype/
+	cp $p$CONF.u /$objtype/
--- /dev/null
+++ b/mmcn900.c
@@ -1,0 +1,285 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/pci.h"
+#include "../port/sd.h"
+
+enum {
+	Rsysc		= 0x10,
+		SCreset		= 1 << 1,
+	Rsyss		= 0x14,
+		SSreset		= 1 << 0,
+	Rcsre		= 0x24,
+	Rcon		= 0x28,
+	Rpwcnt		= 0x2c,
+	Rblk		= 0x104,
+	Rarg		= 0x108,
+	Rcmd		= 0x10c,
+		CRnone		= 0 << 16,
+		CR136		= 1 << 16,
+		CR48		= 2 << 16,
+		CR48busy	= 3 << 16,
+		CRmask		= 3 << 16,
+
+		CFcheckidx	= 1 << 19,
+		CFcheckcrc	= 1 << 20,
+		CFdata		= 1 << 21,
+		CFdataread	= 1 << 4,
+		CFdatamulti	= 1 << 5,
+
+		CTnone		= 0 << 22,
+		CTbus		= 1 << 22,
+		CTfunc		= 2 << 22,
+		CTio		= 3 << 22,
+	Rrsp10		= 0x110,
+	Rrsp32		= 0x114,
+	Rrsp54		= 0x118,
+	Rrsp76		= 0x11c,
+	Rdata		= 0x120,
+	Rpstate		= 0x124,
+	Rhctl		= 0x128,
+	Rsysctl		= 0x12c,
+	Rstatus		= 0x130,
+		STcmd		= 1 << 0,
+		STtransfer	= 1 << 1,
+		STbufwrite	= 1 << 4,
+		STbufread	= 1 << 5,
+
+		STmaskok	= 0xffff << 0,
+		STmaskerr	= 0xffff << 16,
+	Rie			= 0x134,
+	Rise		= 0x138,
+	Rac12		= 0x13c,
+	Rcapa		= 0x140,
+	Rcapacur	= 0x148,
+	Rrev		= 0x1fc,
+};
+
+#define csr32r(c, r) ((c)->io[(r)/4])
+#define csr32w(c, r, w) ((c)->io[(r)/4] = (w))
+
+typedef struct Ctlr Ctlr;
+struct Ctlr {
+	u32int *io;
+	ulong irq;
+
+	struct {
+		uint bcount;
+		uint bsize;
+	} cmd;
+
+	Lock;
+	Rendez;
+};
+
+static int
+n900mmcinit(SDio *io)
+{
+	Ctlr *ctlr;
+
+	ctlr = io->aux;
+	csr32w(ctlr, Rsysc, SCreset);
+	while(!(csr32r(ctlr, Rsyss) & SSreset))
+		;
+
+	return 0;
+}
+
+static void
+n900mmcenable(SDio *)
+{
+}
+
+static int
+n900mmcinquiry(SDio *, char *inquiry, int len)
+{
+	return snprint(inquiry, len, "MMC Host Controller");
+}
+
+static int
+n900mmccmd(SDio *io, SDiocmd *iocmd, u32int arg, u32int *resp)
+{
+	Ctlr *ctlr;
+	u32int cmd;
+
+	/* prepare flags for this command */
+	ctlr = io->aux;
+	cmd = iocmd->index << 24;
+	switch(iocmd->resp) {
+	case 0: cmd |= CRnone; break;
+	case 2: cmd |= CR136 | CFcheckcrc; break;
+	case 3: cmd |= CR48; break;
+	case 1:
+		if(iocmd->busy) {
+			cmd |= CR48busy | CFcheckidx | CFcheckcrc;
+			break;
+		}
+
+	default:
+		cmd |= CR48 | CFcheckidx | CFcheckcrc;
+		break;
+	}
+
+	/* if there is data, set the data, read, and multi flags */
+	if(iocmd->data) {
+		cmd |= CFdata;
+		if(iocmd->data & 1)
+			cmd |= CFdataread;
+		if(iocmd->data > 2)
+			cmd |= CFdatamulti;
+	}
+
+	/* off it goes, wait for a response */
+	csr32w(ctlr, Rstatus, ~0);
+	csr32w(ctlr, Rarg, arg);
+	csr32w(ctlr, Rcmd, cmd);
+
+	/* FIXME: interrupts */
+	while(!(csr32r(ctlr, Rstatus) & STcmd)) {
+		if(csr32r(ctlr, Rstatus) & STmaskerr)
+			error(Eio);
+
+		delay(10);
+	}
+
+	/* unpack the response */
+	switch(cmd & CRmask) {
+	case CRnone:
+		resp[0] = 0;
+		break;
+
+	case CR136:
+		resp[0] = csr32r(ctlr, Rrsp10);
+		resp[1] = csr32r(ctlr, Rrsp32);
+		resp[2] = csr32r(ctlr, Rrsp54);
+		resp[3] = csr32r(ctlr, Rrsp76);
+		break;
+
+	case CR48:
+	case CR48busy:
+		resp[0] = csr32r(ctlr, Rrsp10);
+		break;
+	}
+
+	return 0;
+}
+
+static void
+n900mmciosetup(SDio *io, int, void *, int bsize, int bcount)
+{
+	Ctlr *ctlr;
+
+	ctlr = io->aux;
+	if(bsize == 0 || (bsize & 3) != 0)
+		error(Egreg);
+
+	ctlr->cmd.bsize = bsize;
+	ctlr->cmd.bcount = bcount;
+	csr32w(ctlr, Rblk, (bsize & 0x3ff) | (bcount << 16));
+}
+
+static void
+n900mmcbufread(Ctlr *ctlr, uchar *buf, int len)
+{
+	for(len >>= 2; len > 0; len--) {
+		*((u32int*)buf) = csr32r(ctlr, Rdata);
+		buf += 4;
+	}
+}
+
+static void
+n900mmcbufwrite(Ctlr *ctlr, uchar *buf, int len)
+{
+	for(len >>= 2; len > 0; len--) {
+		csr32w(ctlr, Rdata, *((u32int*)buf));
+		buf += 4;
+	}
+}
+
+static void
+n900mmcio(SDio *io, int write, uchar *buf, int len)
+{
+	Ctlr *ctlr;
+	u32int stat, n;
+
+	ctlr = io->aux;
+	if(len != ctlr->cmd.bsize * ctlr->cmd.bcount)
+		error(Egreg);
+
+	while(len > 0) {
+		stat = csr32r(ctlr, Rstatus);
+		if(stat & STmaskerr) {
+			csr32w(ctlr, Rstatus, STmaskerr);
+			error(Eio);
+		}
+
+		if(stat & STbufwrite) {
+			csr32w(ctlr, Rstatus, STbufwrite);
+			if(!write)
+				error(Eio);
+
+			n = len;
+			if(n > ctlr->cmd.bsize)
+				n = ctlr->cmd.bsize;
+
+			n900mmcbufwrite(ctlr, buf, n);
+			len -= n;
+			buf += n;
+		}
+
+		if(stat & STbufread) {
+			csr32w(ctlr, Rstatus, STbufread);
+			if(write)
+				error(Eio);
+
+			n = len;
+			if(n > ctlr->cmd.bsize)
+				n = ctlr->cmd.bsize;
+
+			n900mmcbufread(ctlr, buf, n);
+			len -= n;
+			buf += n;
+		}
+
+		if(stat & STtransfer) {
+			csr32w(ctlr, Rstatus, STtransfer);
+			if(len != 0)
+				error(Eio);
+		}
+	}
+}
+
+static void
+n900mmcbus(SDio *, int, int)
+{
+	/* FIXME: change bus width */
+}
+
+void
+mmcn900link(void)
+{
+	int i;
+	static Ctlr ctlr[2] = {
+		{ .io = (u32int*) PHYSMMC1, .irq = IRQMMC1, },
+		{ .io = (u32int*) PHYSMMC2, .irq = IRQMMC2, },
+	};
+
+	static SDio io[nelem(ctlr)];
+	for(i = 0; i < nelem(io); i++) {
+		io[i].name = "mmc",
+		io[i].init = n900mmcinit,
+		io[i].enable = n900mmcenable,
+		io[i].inquiry = n900mmcinquiry,
+		io[i].cmd = n900mmccmd,
+		io[i].iosetup = n900mmciosetup,
+		io[i].io = n900mmcio,
+		io[i].bus = n900mmcbus,
+		io[i].aux = &ctlr[i];
+
+		addmmcio(&io[i]);
+	}
+}
--- /dev/null
+++ b/mmu.c
@@ -1,0 +1,308 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+static int debug = 0;
+
+#define L1(va) (((va)>>20) & 0xfff)
+#define L2(va) (((va)>>12) & 0xff)
+#define L1AP(ap) (((ap) << 10) & 0xff) /* dont care about ap[2] */
+#define L2AP(ap) (((ap) << 4) & 0xff)
+
+enum {
+	/* l1 descriptor type (arm v7 manual fig. 12.4) */
+	L1coarse = 1,
+	L1section = 2,
+	L1fine = 3,
+
+	/* l2 descriptor type (arm v7 manual fig. 12.5) */
+	L2large = 1,
+	L2small = 2,
+	L2tiny = 3,
+
+	Fnoexec = 1<<0,
+	Fbuffered = 1<<2,
+	Fcached = 1<<3,
+
+	APkrw = 1, /* kernel read write */
+	APuro = 2, /* user read only */
+	APurw = 3, /* user read write */
+};
+
+void
+mmudebugl2(PTE *l2, uintptr va)
+{
+	uintptr startpa, pa;
+	uintptr startva, endva;
+	int i, t;
+
+	t = 0;
+	startpa = 0;
+	startva = endva = 0;
+	for(i = 0; i < 256; i++) {
+		pa = l2[i] & ~((4*KiB)-1);
+		if(l2[i] == 0) {
+			if(endva) {
+				iprint("mmudebug: l2 type %#ux %#lux %#lux -> %#lux\n", t, startva, endva, startpa);
+				endva = 0;
+			}
+		} else {
+			if(!endva) {
+				startva = va;
+				startpa = pa;
+				t = l2[i] & (L2large | L2small | L2tiny);
+			}
+
+			endva = va + (4*KiB);
+		}
+
+		va += (4*KiB);
+	}
+
+	if(endva)
+		iprint("mmudebug: l2 type %#ux %#lux %#lux -> %#lux\n", t, startva, endva, startpa);
+}
+
+void
+mmudebug(char *where)
+{
+	PTE *l1;
+	uintptr pa, startpa;
+	uintptr va, startva, endva;
+	int i, t;
+
+	if(!debug)
+		return;
+
+	iprint("mmudebug: %s pid %d\n", where, m->mmupid);
+
+	t = 0;
+	l1 = m->mmul1;
+	startpa = 0;
+	startva = endva = 0;
+	for(va = i = 0; i < 4096; i++) {
+		pa = l1[i] & ~(MiB-1);
+		if(l1[i] == 0) {
+			if(endva) {
+				iprint("mmudebug: l1 type %#ux %#lux %#lux -> %#lux\n", t, startva, endva, startpa);
+				endva = 0;
+			}
+		} else {
+			if(!endva) {
+				startva = va;
+				startpa = pa;
+				t = l1[i] & (L1coarse|L1section|L1fine);
+			}
+
+			if(t == L1coarse) {
+				mmudebugl2((PTE*) (l1[i] & ~(KiB-1)), startva);
+				endva = 0;
+			} else {
+				endva = va + MB;
+			}
+		}
+
+		va += MB;
+	}
+
+	if(endva)
+		iprint("mmudebug: l1 type %#ux %#lux %#lux -> %#lux\n", t, startva, endva, startpa);
+}
+
+void
+mmuinit(void)
+{
+	m->mmul1 = (PTE*)MACHL1(m->machno);
+}
+
+static void
+mmul1empty(void)
+{
+	memset(m->mmul1, 0, (ROUND(USTKTOP, MiB)/MiB)*sizeof(PTE));
+}
+
+static void
+mmul2empty(Proc *proc, int clear)
+{
+	PTE *l1;
+	Page **l2, *page;
+
+	l1 = m->mmul1;
+	l2 = &proc->mmul2;
+	for(page = *l2; page != nil; page = page->next) {
+		if(clear)
+			memset((void*)page->va, 0, BY2PG);
+
+		l1[page->daddr] = 0;
+		l2 = &page->next;
+	}
+
+	*l2 = proc->mmul2cache;
+	proc->mmul2cache = proc->mmul2;
+	proc->mmul2 = nil;
+}
+
+void
+mmuswitch(Proc *proc)
+{
+	PTE *l1;
+	Page *page;
+	int l1x;
+
+	if(m->mmupid == proc->pid && !proc->newtlb)
+		return;
+	m->mmupid = proc->pid;
+
+	/* write back and invalidate caches */
+	l1ucachewbinv();
+	l2ucachewbinv();
+
+	if(proc->newtlb) {
+		mmul2empty(proc, 1);
+		proc->newtlb = 0;
+	}
+
+	mmul1empty();
+
+	/* switch to new map */
+	l1 = m->mmul1;
+	for(page = proc->mmul2; page != nil; page = page->next) {
+		l1x = page->daddr;
+		l1[l1x] = PPN(page->pa)|L1coarse;
+	}
+
+	/* FIXME: excessive invalidation */
+	l1ucachewbinv();
+	l2ucachewbinv();
+	mmuinvalidate();
+	mmudebug("mmuswitch");
+}
+
+void
+mmurelease(Proc *proc)
+{
+	l1ucachewbinv();
+	l2ucachewbinv();
+
+	mmul2empty(proc, 0);
+
+	freepages(proc->mmul2cache, nil, 0);
+	proc->mmul2cache = nil;
+
+	mmul1empty();
+
+	/* FIXME: excessive invalidation */
+	l1ucachewbinv();
+	l2ucachewbinv();
+	mmuinvalidate();
+	mmudebug("mmurelease");
+}
+
+void*
+mmuuncache(void *v, usize s)
+{
+	PTE *l1;
+	uintptr va;
+
+	assert(!((uintptr)v & (MiB-1)) && s == MiB);
+
+	va = (uintptr)v;
+	l1 = &m->mmul1[L1(va)];
+	if((*l1 & (L1fine|L1section|L1coarse)) != L1section)
+		return nil;
+
+	*l1 &= ~(Fbuffered|Fcached);
+
+	/* FIXME: excessive invalidation */
+	l1ucachewbinv();
+	l2ucachewbinv();
+	mmuinvalidate();
+	mmudebug("mmuuncache");
+
+	return v;
+}
+
+void
+putmmu(uintptr va, uintptr pa, Page *page)
+{
+	PTE *l1, *l2;
+	Page *pg;
+	int l1x;
+	int x;
+
+	l1x = L1(va);
+	l1 = &m->mmul1[l1x];
+
+	/* put l1 for l2 table if needed */
+	if(*l1 == 0) {
+		if(up->mmul2cache == nil) {
+			pg = newpage(1, 0, 0);
+			pg->va = VA(kmap(pg));
+		} else {
+			pg = up->mmul2cache;
+			up->mmul2cache = pg->next;
+			memset((void*)pg->va, 0, BY2PG);
+		}
+
+		pg->daddr = l1x;
+		pg->next = up->mmul2;
+		up->mmul2 = pg;
+
+		/* FIXME: excessive invalidation */
+		*l1 = PPN(pg->pa)|L1coarse;
+		l1ucachewbinv();
+		l2ucachewbinv();
+	}
+
+	/* put l2 entry */
+	x = L2small;
+	if(!(pa & PTEUNCACHED))
+		x |= Fbuffered|Fcached;
+	if(pa & PTENOEXEC)
+		x |= Fnoexec;
+	if(pa & PTEWRITE)
+		x |= L2AP(APurw);
+	else
+		x |= L2AP(APuro);
+
+	l2 = KADDR(PPN(*l1)); l2[L2(va)] = PPN(pa)|x;
+
+	/* FIXME: excessive invalidation */
+	l1ucachewbinv();
+	l2ucachewbinv();
+	if(needtxtflush(page)) {
+		l1icacheinv();
+		donetxtflush(page);
+	}
+
+	mmuinvalidate();
+	mmudebug("putmmu");
+}
+
+void
+checkmmu(uintptr, uintptr)
+{
+	/* this page is intentionally left blank */
+}
+
+void
+flushmmu(void)
+{
+	uint s;
+
+	s = splhi();
+	up->newtlb = 1; mmuswitch(up);
+	splx(s);
+}
+
+uintptr
+cankaddr(uintptr pa)
+{
+	if(pa >= PHYSMEM && pa < PHYSMEMEND)
+		return PHYSMEMEND-pa;
+
+	return 0;
+}
--- /dev/null
+++ b/n900
@@ -1,0 +1,49 @@
+dev
+	root
+	cons
+	swap
+	env
+	pipe
+	proc
+	mnt
+	srv
+	shr
+	dup
+	tls
+	cap
+	sd
+	fs
+	dtracy
+
+	draw	screen
+
+	kbd		devi2c
+	rtc		devi2c
+
+	uart
+	usb
+	i2c
+
+misc
+	uartn900
+
+	sdmmc	mmcn900
+	sdloop
+	sdram
+
+	dtracysys
+	dtracytimer
+	dtracydev
+
+link
+	i2cn900		devi2c
+	mmcn900
+
+port
+	int cpuserver = 0;
+
+bootdir
+	/$objtype/bin/paqfs
+	/$objtype/bin/auth/factotum
+	boot
+	bootfs.paq
--- /dev/null
+++ b/screen.c
@@ -1,0 +1,373 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+#define Image IMAGE
+#include <draw.h>
+#include <memdraw.h>
+#include "screen.h"
+
+enum {
+	/* display controller registers */
+	RDrev			= 0x400,
+	RDsysconf		= 0x410,
+		DSCidle			= 1<<0,
+		DSCreset		= 1<<1,
+		DSCidlesmart	= 2<<3,
+	RDsysstat		= 0x414,
+		DSSreset		= 1<<0,
+	RDirqstat		= 0x418,
+	RDirqen			= 0x41c,
+	RDcontrol		= 0x440,
+		DClcdon			= 1<<0,
+		DClcdactive		= 1<<3,
+		DClcdclockrun	= 1<<27,
+		DClcdenable		= 1<<28,
+		DClcdenablehi	= 1<<29,
+		DClcdbits24		= 3<<8,
+	RDconfig		= 0x444,
+		DCNgamma		= 1<<3,
+	RDdefcolor		= 0x44c,
+	RDtranscolor	= 0x454,
+	RDlinestat		= 0x45c,
+	RDlineintr		= 0x460,
+	RDtimeh			= 0x464,
+	RDtimev			= 0x468,
+	RDsize			= 0x47c,
+
+	/* display controller graphics layer registers */
+	RDgfxba				= 0x480,
+	RDgfxpos			= 0x488,
+	RDgfxsize			= 0x48c,
+	RDgfxattr			= 0x4a0,
+		DGAenable			= 1<<0,
+		DGAfmt				= 0x6<<1,
+		DGAburst			= 0x2<<6,
+	RDgfxrowinc			= 0x4ac,
+	RDgfxpixelinc		= 0x4b0,
+};
+
+enum {
+	Tab	= 4,
+	Scroll = 8,
+};
+
+#define RDdefcolorn(n)		(RDdefcolor + (n)*4)
+#define RDtranscolorn(n)	(RDtranscolor + (n)*4)
+
+#define RDgfxban(n)			(RDgfxba + (n)*4)
+
+#define TIME(s, f, b)		((s & 0xff) | (f & 0xfff) << 8 | (b & 0xfff) << 20)
+#define POS(x, y)			((x)<<16 | (y))
+#define SIZE(w, h)			((h-1)<<16 | (w-1))
+
+typedef struct Ctlr Ctlr;
+struct Ctlr {
+	Lock;
+
+	u32int *io;
+
+	Memdata scrdata;
+	Memimage *scrimg;
+	Memsubfont *scrfont;
+
+	Rectangle win;
+	Point font;
+	Point pos;
+};
+
+static Ctlr ctlr = {
+	.io = (u32int*) PHYSDSS,
+};
+
+#define csr32r(c, r) ((c)->io[(r)/4])
+#define csr32w(c, r, w) ((c)->io[(r)/4] = (w))
+
+static void myscreenputs(char *s, int n);
+
+static int
+screendatainit(Ctlr *ctlr)
+{
+	Rectangle r;
+
+	if(memimageinit() < 0)
+		return -1;
+
+	r = Rect(0, 0, 800, 480);
+	ctlr->scrdata.ref = 1;
+	ctlr->scrdata.bdata = ucalloc(r.max.x * r.max.y * 2);
+	if(!ctlr->scrdata.bdata)
+		return -1;
+
+	ctlr->scrimg = allocmemimaged(r, RGB16, &ctlr->scrdata);
+	if(!ctlr->scrimg)
+		return -1;
+
+	ctlr->scrfont = getmemdefont();
+	return 0;
+}
+
+static Memimage*
+screenmkcolor(Memimage *scr, ulong color)
+{
+	Memimage *i;
+
+	i = allocmemimage(Rect(0, 0, 1, 1), scr->chan);
+	if(i) {
+		i->flags |= Frepl;
+		i->clipr = scr->r;
+		memfillcolor(i, color);
+	}
+
+	return i;
+}
+
+static void
+screenwin(Ctlr *ctlr)
+{
+	Rectangle r;
+	Memimage *i;
+	Point p;
+	int h;
+
+	ctlr->font.y = h = ctlr->scrfont->height;
+	ctlr->font.x = ctlr->scrfont->info[' '].width;
+	
+	r = ctlr->scrimg->r;
+	if(i = screenmkcolor(ctlr->scrimg, 0x0D686BFF)) {
+		memimagedraw(ctlr->scrimg, r, i, ZP, memopaque, ZP, S);
+		freememimage(i);
+	}
+
+	r = insetrect(r, 16); memimagedraw(ctlr->scrimg, r, memblack, ZP, memopaque, ZP, S);
+	r = insetrect(r, 4); memimagedraw(ctlr->scrimg, r, memwhite, ZP, memopaque, ZP, S);
+	if(i = screenmkcolor(ctlr->scrimg, 0xaaaaaaff)) {
+		memimagedraw(ctlr->scrimg, Rpt(r.min, Pt(r.max.x, r.min.y+h+12)), i, ZP, memopaque, ZP, S);
+		freememimage(i);
+
+		r = insetrect(r, 6);
+		p = r.min;
+		memimagestring(ctlr->scrimg, p, memblack, ZP, getmemdefont(), " Plan 9 Console ");
+	}
+
+	ctlr->win = Rpt(addpt(r.min, Pt(0, h + 6)), subpt(r.max, Pt(6, 6)));
+	ctlr->pos = ctlr->win.min;
+	screenputs = myscreenputs;
+}
+
+void
+screeninit(void)
+{
+	Ctlr *c;
+
+	c = &ctlr;
+	if(screendatainit(c) < 0)
+		return;
+
+	screenwin(c);
+	screenputs(kmesg.buf, kmesg.n);
+
+	/* reset the display controller */
+	csr32w(c, RDsysconf, DSCreset);
+	while(!(csr32r(c, RDsysstat) & DSSreset))
+		;
+
+	/* configure the display controller */
+	csr32w(c, RDsysconf, DSCidle | DSCidlesmart);
+	csr32w(c, RDcontrol, DClcdbits24);
+	csr32w(c, RDconfig, DCNgamma);
+
+	/* configure display size and timings */
+	csr32w(c, RDtimeh, TIME(3, 15, 11));
+	csr32w(c, RDtimev, TIME(2, 3, 3));
+	csr32w(c, RDsize, SIZE(800, 480));
+
+	/* enable the lcd interface */
+	csr32w(c, RDcontrol, csr32r(c, RDcontrol) | DClcdactive | DClcdclockrun | DClcdenablehi);
+	csr32w(c, RDcontrol, csr32r(c, RDcontrol) | DClcdenable);
+
+	/* configure the graphics layer */
+	csr32w(c, RDgfxban(0), (uintptr) c->scrdata.bdata);
+	csr32w(c, RDgfxpos, POS(c->scrimg->r.min.x, c->scrimg->r.min.y));
+	csr32w(c, RDgfxsize, SIZE(c->scrimg->r.max.x, c->scrimg->r.max.y));
+	csr32w(c, RDgfxattr, DGAfmt | DGAburst);
+	csr32w(c, RDgfxrowinc, 1);
+	csr32w(c, RDgfxpixelinc, 1);
+
+	/* enable gfx pipeline and turn lcd on */
+	csr32w(c, RDgfxattr, csr32r(c, RDgfxattr) | DGAenable);
+	csr32w(c, RDcontrol, csr32r(c, RDcontrol) | DClcdon);
+
+	conf.monitor = 1;
+}
+
+static void
+screenscroll(Ctlr *ctlr)
+{
+	int o, h;
+	Point p;
+	Rectangle r;
+
+	h = ctlr->font.y;
+	o = Scroll * h;
+	r = Rpt(ctlr->win.min, Pt(ctlr->win.max.x, ctlr->win.max.y - o));
+	p = Pt(ctlr->win.min.x, ctlr->win.min.y + o);
+	memimagedraw(ctlr->scrimg, r, ctlr->scrimg, p, nil, p, S);
+	flushmemscreen(r);
+
+	r = Rpt(Pt(ctlr->win.min.x, ctlr->win.max.y - o), ctlr->win.max);
+	memimagedraw(ctlr->scrimg, r, memwhite, ZP, nil, ZP, S);
+	flushmemscreen(r);
+
+	ctlr->pos.y -= o;
+	
+}
+
+static void
+screenputc(Ctlr *ctlr, char *buf)
+{
+	Point p;
+	Rectangle r;
+	uint chr;
+	int w, h;
+	static int *xp;
+	static int xbuf[256];
+
+	w = ctlr->font.x;
+	h = ctlr->font.y;
+	if(xp < xbuf || xp >= &xbuf[nelem(xbuf)])
+		xp = xbuf;
+
+	switch(buf[0]) {
+	case '\n':
+		if(ctlr->pos.y + h >= ctlr->win.max.y)
+			screenscroll(ctlr);
+
+		ctlr->pos.y += h;
+		screenputc(ctlr, "\r");
+		break;
+
+	case '\r':
+		xp = xbuf;
+		ctlr->pos.x = ctlr->win.min.x;
+		break;
+
+	case '\t':
+		if(ctlr->pos.x >= ctlr->win.max.x - 4 * w)
+			screenputc(ctlr, "\n");
+
+		chr = (ctlr->pos.x - ctlr->win.min.x) / w;
+		chr = Tab - chr % Tab;
+		*xp++ = ctlr->pos.x;
+
+		r = Rect(ctlr->pos.x, ctlr->pos.y, ctlr->pos.x + chr * w, ctlr->pos.y + h);
+		memimagedraw(ctlr->scrimg, r, memwhite, ZP, memopaque, ZP, S);
+		flushmemscreen(r);
+		ctlr->pos.x += chr * w;
+		break;
+
+	case '\b':
+		if(xp <= xbuf)
+			break;
+
+		xp--;
+		r = Rect(*xp, ctlr->pos.y, ctlr->pos.x, ctlr->pos.y + h);
+		memimagedraw(ctlr->scrimg, r, memwhite, ZP, memopaque, ZP, S);
+		ctlr->pos.x = *xp;
+		break;
+
+	case '\0':
+		break;
+
+	default:
+		p = memsubfontwidth(getmemdefont(), buf); w = p.x;
+		if(ctlr->pos.x >= ctlr->win.max.x - w)
+			screenputc(ctlr, "\n");
+
+		*xp++ = ctlr->pos.x;
+		r = Rect(ctlr->pos.x, ctlr->pos.y, ctlr->pos.x + w, ctlr->pos.y + h);
+		memimagedraw(ctlr->scrimg, r, memwhite, ZP, memopaque, ZP, S);
+		memimagestring(ctlr->scrimg, ctlr->pos, memblack, ZP, getmemdefont(), buf);
+		ctlr->pos.x += w;
+		break;
+	}
+}
+
+static void
+myscreenputs(char *s, int n)
+{
+	Ctlr *c;
+	Rune r;
+	int i;
+	char buf[UTFmax];
+
+	c = &ctlr;
+	if(!c->scrimg)
+		return;
+
+	if(!islo()) {
+		/* don't deadlock trying to print in an interrupt */
+		if(!canlock(c))
+			return;
+	} else {
+		lock(c);
+	}
+
+	while(n > 0) {
+		i = chartorune(&r, s);
+		if(i == 0) {
+			s++; n--;
+			continue;
+		}
+
+		memmove(buf, s, i);
+		buf[i] = 0;
+		s += i; n -= i;
+		screenputc(c, buf);
+	}
+
+	unlock(c);
+}
+
+Memdata*
+attachscreen(Rectangle *r, ulong *chan, int *d, int *width, int *softscreen)
+{
+	Ctlr *c;
+
+	c = &ctlr;
+	if(!c->scrimg)
+		return nil;
+
+	*r = c->scrimg->r;
+	*d = c->scrimg->depth;
+	*chan = c->scrimg->chan;
+	*width = c->scrimg->width;
+	*softscreen = 1;
+
+	c->scrdata.ref++;
+	return &c->scrdata;
+}
+
+void
+getcolor(ulong, ulong *, ulong *, ulong *)
+{
+}
+
+int
+setcolor(ulong, ulong, ulong, ulong)
+{
+	return 0;
+}
+
+void
+flushmemscreen(Rectangle)
+{
+}
+
+void
+mouseresize(void)
+{
+}
--- /dev/null
+++ b/screen.h
@@ -1,0 +1,9 @@
+void screeninit(void);
+void flushmemscreen(Rectangle);
+Memdata *attachscreen(Rectangle *, ulong *, int *, int *, int *);
+
+#define ishwimage(i) 1
+
+void mousectl(Cmdbuf*);
+void mouseresize(void);
+void mouseredraw(void);
--- /dev/null
+++ b/timer.c
@@ -1,0 +1,172 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+enum {
+	Rrev		= 0x00,
+	Rsyscfg		= 0x10,
+		Cidle		= 1<<0,
+		Creset		= 1<<1,
+	Rsysstat	= 0x14,
+		Sreset		= 1<<0,
+	Risr		= 0x18,
+	Rier		= 0x1c,
+		Imatch		= 1<<0,
+		Ioverflow	= 1<<1,
+		Icapture	= 1<<2,
+	Rclr		= 0x24,
+		CLst		= 1<<0,
+		CLar		= 1<<1,
+		CLtgovf		= 1<<10,
+		CLtgmatch	= 1<<11,
+	Rcrr		= 0x28,
+	Rldr		= 0x2c,
+};
+
+typedef union Counter Counter;
+typedef struct Ctlr Ctlr;
+
+union Counter {
+	uvlong cnt;
+	struct {
+		ulong cntlo;
+		ulong cnthi;
+	};
+};
+
+struct Ctlr {
+	Lock;
+	Counter;
+
+	u32int *io;
+};
+
+static Ctlr timers[] = {
+	{ .io = (u32int*) PHYSTIMER1 }, /* for cycles */
+	{ .io = (u32int*) PHYSTIMER2 }, /* for interrupts */
+};
+
+#define csr32r(c, r) ((c)->io[(r)/4])
+#define csr32w(c, r, w) ((c)->io[(r)/4] = (w))
+
+static void
+timerreset(Ctlr *ctlr)
+{
+	int i;
+	int cfg;
+
+	cfg = csr32r(ctlr, Rsyscfg);
+	cfg |= Creset;
+	cfg &= ~Cidle;
+
+	ilock(ctlr);
+	csr32w(ctlr, Rsyscfg, cfg);
+	for(i = 40000; i > 0; i++) {
+		if(csr32r(ctlr, Rsysstat) & Sreset)
+			break;
+	}
+
+	if(i == 0)
+		panic("clock reset failed");
+
+	iunlock(ctlr);
+}
+
+static void
+timerstartcycles(Ctlr *ctlr)
+{
+	timerreset(ctlr);
+
+	/* configure this timer for measuring cycles */
+	ilock(ctlr);
+	csr32w(ctlr, Rldr, 0);
+	csr32w(ctlr, Rclr, CLst | CLar);
+	iunlock(ctlr);
+}
+
+static void
+timerstartintr(Ctlr *ctlr, ulong t)
+{
+	timerreset(ctlr);
+
+	/* configure this timer for periodic interrupts */
+	ilock(ctlr);
+	csr32w(ctlr, Rier, Ioverflow);
+	csr32w(ctlr, Rldr, -t);
+	csr32w(ctlr, Rcrr, -t);
+	csr32w(ctlr, Rclr, CLst | CLar);
+	iunlock(ctlr);
+}
+
+static void
+timerinterrupt(Ureg *u, void *arg)
+{
+	Ctlr *ctlr = arg;
+	csr32w(ctlr, Risr, Ioverflow);
+
+	timerintr(u, 0);
+}
+
+void
+timerinit(void)
+{
+	intrenable(IRQTIMER2, timerinterrupt, &timers[1], BUSUNKNOWN, "timer");
+
+	timerstartcycles(&timers[0]);
+	timerstartintr(&timers[1], 32);
+}
+
+uvlong
+fastticks(uvlong *hz)
+{
+	Counter c;
+	Ctlr *ctlr;
+
+	/* FIXME: this has poor precision, but qemu has no cycle counter */
+	ctlr = &timers[0];
+	if(hz)
+		*hz = 32*1024;
+
+	ilock(ctlr);
+	c.cnt = ctlr->cnt;
+	c.cntlo = csr32r(ctlr, Rcrr);
+	if(c.cnt < ctlr->cnt)
+		c.cnthi++;
+
+	ctlr->cnt = c.cnt;
+	iunlock(ctlr);
+
+	return ctlr->cnt;
+}
+
+ulong
+µs(void)
+{
+	return fastticks2us(fastticks(nil));
+}
+
+void
+microdelay(int n)
+{
+	ulong now;
+
+	now = µs();
+	while(µs() - now < n)
+		;
+}
+
+void
+delay(int n)
+{
+	while(--n >= 0)
+		microdelay(1000);
+}
+
+void
+timerset(Tval)
+{
+	/* FIXME: ? */
+}
--- /dev/null
+++ b/trap.c
@@ -1,0 +1,570 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "../port/systab.h"
+
+#include "tos.h"
+#include "ureg.h"
+
+void
+callwithureg(void (*f) (Ureg *))
+{
+	Ureg u;
+
+	u.pc = getcallerpc(&f);
+	u.sp = (uintptr) &f - 4;
+	f(&u);
+}
+
+void
+dumpstackureg(Ureg *ureg)
+{
+	uintptr l, v, i, estack;
+	int x;
+
+	x = 0;
+	x += iprint("ktrace /arm/9n900 %#.8lux %#.8lux %#.8lux <<EOF\n",
+		ureg->pc, ureg->sp, ureg->r14);
+
+	i = 0;
+	if(up)
+		estack = (uintptr)up;
+	else
+		estack = (uintptr)m+MACHSZ;
+
+	x += iprint("estackx %p\n", estack);
+	for(l = (uintptr)&l; l < estack; l += sizeof(uintptr)) {
+		v = *(uintptr*)l;
+		if((KTZERO < v && v < (uintptr)etext) || estack-l < 32) {
+			x += iprint("%.8p=%.8p ", l, v);
+			i++;
+		}
+
+		if(i == 4) {
+			i = 0;
+			x += iprint("\n");
+		}
+	}
+
+	if(i)
+		iprint("\n");
+	iprint("EOF\n");
+}
+
+void
+dumpstack(void)
+{
+	callwithureg(dumpstackureg);
+}
+
+void
+dumpureg(Ureg *ureg)
+{
+	if(up)
+		iprint("cpu%d: registers for %s %lud\n", m->machno, up->text, up->pid);
+	else
+		iprint("cpu%d: registers for kernel\n", m->machno);
+
+	iprint("r0 %#.8lux\tr1 %#.8lux\tr2 %#.8lux\tr3 %#.8lux\n", ureg->r0, ureg->r1, ureg->r2, ureg->r3);
+	iprint("r4 %#.8lux\tr5 %#.8lux\tr6 %#.8lux\tr7 %#.8lux\n", ureg->r4, ureg->r5, ureg->r6, ureg->r7);
+	iprint("r8 %#.8lux\tr9 %#.8lux\tr10 %#.8lux\tr11 %#.8lux\n", ureg->r8, ureg->r9, ureg->r10, ureg->r11);
+	iprint("r12 %#.8lux\tr13 %#.8lux\tr14 %#.8lux\tr15 %#.8lux\n", ureg->r12, ureg->r13, ureg->r14, ureg->pc);
+}
+
+
+uintptr
+userpc(void)
+{
+	return ((Ureg*)up->dbgreg)->pc;
+}
+
+uintptr
+dbgpc(Proc *)
+{
+	if(up->dbgreg)
+		return userpc();
+
+	return 0;
+}
+
+void
+procsetup(Proc *p)
+{
+	p->fpstate = FPinit;
+	fpoff();
+}
+
+void
+procsave(Proc *p)
+{
+	if(p->fpstate == FPactive) {
+		if(p->state == Moribund)
+			fpclear();
+		else
+			fpsave(p->fpsave);
+		p->fpstate = FPinactive;
+	}
+}
+
+void
+procrestore(Proc *)
+{
+}
+
+void
+procfork(Proc *p)
+{
+	ulong s;
+
+	s = splhi();
+	switch(up->fpstate & ~FPillegal) {
+	case FPactive:
+		fpsave(up->fpsave);
+		up->fpstate = FPinactive;
+	case FPinactive:
+		memmove(p->fpsave, up->fpsave, sizeof(FPsave));
+		p->fpstate = FPinactive;
+	}
+
+	splx(s);
+}
+
+void
+kprocchild(Proc *p, void (*entry)(void))
+{
+	p->sched.pc = (uintptr) entry;
+	p->sched.sp = (uintptr) p;
+}
+
+void
+forkchild(Proc *p, Ureg *ureg)
+{
+	Ureg *cureg;
+
+	p->sched.sp = (uintptr) p - sizeof(Ureg);
+	p->sched.pc = (uintptr) forkret;
+
+	cureg = (Ureg*) p->sched.sp;
+	memmove(cureg, ureg, sizeof(Ureg));
+	cureg->r0 = 0;
+}
+
+uintptr
+execregs(uintptr entry, ulong ssize, ulong nargs)
+{
+	ulong *sp;
+	Ureg *ureg;
+
+	sp = (ulong*)(USTKTOP - ssize); *--sp = nargs;
+	ureg = up->dbgreg;
+	ureg->sp = (uintptr) sp;
+	ureg->pc = entry;
+	ureg->r14 = 0;
+
+	return USTKTOP-sizeof(Tos);
+}
+
+void
+setkernur(Ureg *ureg, Proc *p)
+{
+	ureg->pc = p->sched.pc;
+	ureg->sp = p->sched.sp+4;
+	ureg->r14 = (uintptr) sched;
+}
+
+void
+setregisters(Ureg *ureg, char *pureg, char *uva, int n)
+{
+	uvlong v;
+
+	v = ureg->psr;
+	memmove(pureg, uva, n);
+	ureg->psr &= ~(PsrMask|PsrDfiq|PsrDirq);
+	ureg->psr |= v & (PsrMask|PsrDfiq|PsrDirq);
+}
+
+void
+evenaddr(uintptr addr)
+{
+	if(addr & 3) {
+		postnote(up, 1, "sys: odd address", NDebug);
+		error(Ebadarg);
+	}
+}
+
+int
+notify(Ureg *ureg)
+{
+	ulong s, sp;
+	char *msg;
+
+	if(up->procctl)
+		procctl();
+	if(up->nnote == 0)
+		return 0;
+
+	s = spllo();
+	qlock(&up->debug);
+	msg = popnote(ureg);
+	if(msg == nil) {
+		qunlock(&up->debug);
+		splhi();
+		return 0;
+	}
+
+	sp = ureg->sp;
+	sp -= 256;
+	sp -= sizeof(Ureg);
+
+	if(!okaddr((uintptr)up->notify, 1, 0)
+	|| !okaddr(sp-ERRMAX-4*BY2WD, sizeof(Ureg)+ERRMAX+4*BY2WD, 1)
+	|| ((uintptr) up->notify & 3) != 0
+	|| (sp & 3) != 0) {
+		qunlock(&up->debug);
+		pprint("suicide: bad address in notify\n");
+		pexit("Suicide", 0);
+	}
+
+	memmove((Ureg*)sp, ureg, sizeof(Ureg));
+	*(Ureg**)(sp-BY2WD) = up->ureg;
+	up->ureg = (void*)sp;
+	sp -= BY2WD+ERRMAX;
+	memmove((char*)sp, msg, ERRMAX);
+	sp -= 3*BY2WD;
+	*(uintptr*)(sp+2*BY2WD) = sp+3*BY2WD;
+	*(uintptr*)(sp+1*BY2WD) = (uintptr)up->ureg;
+	ureg->r0 = (uintptr) up->ureg;
+	ureg->sp = sp;
+	ureg->pc = (uintptr) up->notify;
+	ureg->r14 = 0;
+
+	qunlock(&up->debug);
+	splx(s);
+	return 1;
+}
+
+void
+noted(Ureg *ureg, ulong arg0)
+{
+	Ureg *nureg;
+	ulong oureg, sp;
+
+	qlock(&up->debug);
+	if(arg0 != NRSTR && !up->notified) {
+		qunlock(&up->debug);
+		iprint("called to noted when not notified\n");
+		pexit("Suicide", 0);
+	}
+
+	up->notified = 0;
+	nureg = up->ureg;
+	oureg = (ulong) nureg;
+	if(!okaddr(oureg - BY2WD, BY2WD + sizeof(Ureg), 0) || (oureg & 3) != 0) {
+		qunlock(&up->debug);
+		pprint("bad ureg in noted or call to noted when not notifed\n");
+		pexit("Suicide", 0);
+	}
+
+	nureg->psr &= PsrMask|PsrDfiq|PsrDirq;
+	nureg->psr |= (ureg->psr & ~(PsrMask|PsrDfiq|PsrDirq));
+
+	memmove(ureg, nureg, sizeof(Ureg));
+	switch(arg0) {
+	case NCONT:
+	case NRSTR:
+		if(!okaddr(nureg->pc, BY2WD, 0) || (nureg->pc & 3) != 0
+		|| !okaddr(nureg->sp, BY2WD, 0) || (nureg->sp & 3) != 0) {
+			qunlock(&up->debug);
+			pprint("suicide: trap in noted\n");
+			pexit("Suicide", 0);
+		}
+
+		up->ureg = (Ureg *) (*(ulong*) (oureg - BY2WD));
+		qunlock(&up->debug);
+		break;
+
+	case NSAVE:
+		if(!okaddr(nureg->pc, BY2WD, 0) || (nureg->pc & 3) != 0
+		|| !okaddr(nureg->sp, BY2WD, 0) || (nureg->sp & 3) != 0) {
+			qunlock(&up->debug);
+			pprint("suicide: trap in noted\n");
+			pexit("Suicide", 0);
+		}
+
+		qunlock(&up->debug);
+		sp = oureg - 4 * BY2WD - ERRMAX;
+		splhi();
+		ureg->sp = sp;
+		ureg->r0 = (uintptr) oureg;
+		((ulong*) sp)[1] = oureg;
+		((ulong*) sp)[0] = 0;
+		break;
+
+	default:
+		up->lastnote->flag = NDebug;
+		/* wet floor */
+
+	case NDFLT:
+		qunlock(&up->debug);
+		if(up->lastnote->flag == NDebug)
+			pprint("suicide: %s\n", up->lastnote->msg);
+
+		pexit(up->lastnote->msg, up->lastnote->flag != NDebug);
+		break;
+	}
+}
+
+void
+trapinit(void)
+{
+	extern ulong vectors[];
+
+	/* install stack pointer for other exception modes */
+	setR13(PsrMfiq, m->save);
+	setR13(PsrMirq, m->save);
+	setR13(PsrMiabt, m->save);
+	setR13(PsrMund, m->save);
+	setR13(PsrMsys, m->save);
+
+	/* install vectors and vtable to MACHVEC because vectors must be
+	 * aligned on a 128 byte boundary */
+	memmove((ulong*)MACHVEC(m->machno), vectors, 64 * 4);
+
+	/* set vectors base address */
+	setvectors(MACHVEC(m->machno));
+}
+
+static void
+trapfpu(void)
+{
+	int s;
+
+	if((up->fpstate & FPillegal) != 0) {
+		postnote(up, 1, "sys: floating point in note handler", NDebug);
+		return;
+	}
+
+	switch(up->fpstate) {
+	case FPinit:
+		s = splhi();
+		fpinit(); up->fpstate = FPactive;
+		splx(s);
+		break;
+
+	case FPinactive:
+		s = splhi();
+		fprestore(up->fpsave); up->fpstate = FPactive;
+		splx(s);
+		break;
+
+	case FPactive:
+		postnote(up, 1, "sys: floating point error", NDebug);
+		break;
+	}
+}
+
+static void
+traparm(Ureg *ureg, ulong fsr, uintptr far)
+{
+	int user;
+	int read;
+	int syscall;
+
+	static char buf[ERRMAX];
+
+	read = (fsr & (1<<11)) == 0;
+	user = userureg(ureg);
+	if(!user) {
+		if(far >= USTKTOP)
+			panic("kernel fault: bad address pc=%#.8lux far=%#.8lux fsr=%#.8lux",
+				ureg->pc, far, fsr);
+		if(up == nil)
+			panic("kernel fault: no user process pc=%#.8lux far=%#.8lux fsr=%#.8lux",
+				ureg->pc, far, fsr);
+	}
+
+	if(up == nil) {
+		panic("user fault: up=nil pc=%#.8lux far=%#.8lux fsr=%#.8lux",
+			ureg->pc, far, fsr);
+	}
+
+	syscall = up->insyscall; up->insyscall = 1;
+	switch(fsr & 0x1f) {
+	case 0x03: /* l1 access flag fault */
+	case 0x05: /* l1 translation fault */
+	case 0x06: /* l2 access flag fault */
+	case 0x07: /* l2 translation fault */
+	case 0x09: /* l1 domain fault */
+	case 0x0b: /* l2 domain fault */
+	case 0x0d: /* l1 permission fault */
+	case 0x0f: /* l2 permission fault */
+		if(fault(far, ureg->pc, read) == 0)
+			break;
+
+	default:
+		if(!user)
+			panic("kernel fault: pc=%#.8lux far=%#.8lux fsr=%#.8lux",
+				ureg->pc, far, fsr);
+
+		dumpureg(ureg);
+		dumpstackureg(ureg);
+		snprint(buf, sizeof(buf), "sys: trap: fault %s far=%#.8lux fsr=%#.8lux",
+			read ? "read" : "write", far, fsr);
+		postnote(up, 1, buf, NDebug);
+	}
+
+	up->insyscall = syscall;
+}
+
+void
+trap(Ureg *ureg)
+{
+	int user;
+	u32int op, cp;
+
+	user = kenter(ureg);
+	switch(ureg->type) {
+	case PsrMfiq:
+	case PsrMirq:
+		ureg->pc -= 4;
+		intr(ureg);
+		break;
+
+	case PsrMiabt:
+		ureg->pc -= 4;
+		traparm(ureg, getifsr(), getifar());
+		break;
+
+	case PsrMdabt:
+		ureg->pc -= 8;
+		traparm(ureg, getdfsr(), getdfar());
+		break;
+
+	case PsrMund:
+		ureg->pc -= 4;
+		if(user) {
+			spllo();
+			if(okaddr(ureg->pc, 4, 0)) {
+				op = *(u32int*)ureg->pc;
+				if((op & 0x0f000000) == 0x0e000000 || (op & 0x0e000000) == 0x0c000000) {
+					cp = op >> 8 & 15;
+					if(cp == 10 || cp == 11) {
+						trapfpu();
+						break;
+					}
+				}
+			}
+				
+			postnote(up, 1, "sys: trap: invalid opcode", NDebug);
+			break;
+		}
+
+		panic("invalid opcode at pc=%#.8lux lr=%#.8lux", ureg->pc, ureg->r14);
+		break;
+
+	default:
+		panic("unknown trap at pc=%#.8lux lr=%#.8lux", ureg->pc, ureg->r14);
+		break;
+	}
+
+	splhi();
+	if(user) {
+		if(up->procctl || up->nnote)
+			notify(ureg);
+
+		kexit(ureg);
+	}
+}
+
+void
+syscall(Ureg *ureg)
+{
+	char *e;
+	uintptr sp;
+	long ret;
+	int i, s;
+	ulong scallnr;
+	vlong startns, stopns;
+
+	if(!kenter(ureg))
+		panic("syscall: from kernel: pc=%#.8lux", ureg->pc);
+
+	m->syscall++;
+	up->insyscall = 1;
+	up->pc = ureg->pc;
+
+	scallnr = up->scallnr = ureg->r0;
+	sp = ureg->sp;
+
+	spllo();
+
+	up->nerrlab = 0;
+	ret = -1;
+	if(!waserror()) {
+		if(scallnr >= nsyscall) {
+			pprint("bad sys call number %lux pc %#lux", scallnr, ureg->pc);
+			postnote(up, 1, "sys: bad sys call", NDebug);
+			error(Ebadarg);
+		}
+
+		if(sp < (USTKTOP-BY2PG) || sp > (USTKTOP-sizeof(Sargs)-BY2WD)) {
+			validaddr(sp, sizeof(Sargs)+BY2WD, 0);
+			evenaddr(sp);
+		}
+
+		up->s = *((Sargs*)(sp + BY2WD));
+		up->psstate = sysctab[scallnr];
+		if (up->procctl == Proc_tracesyscall) {
+			syscallfmt(scallnr, ureg->pc, (va_list)up->s.args);
+			s = splhi();
+			up->procctl = Proc_stopme;
+			procctl();
+			splx(s);
+			startns = todget(nil);
+		}
+
+		ret = systab[scallnr]((va_list)up->s.args);
+		poperror();
+	} else {
+		e = up->syserrstr;
+		up->syserrstr = up->errstr;
+		up->errstr = e;
+	}
+
+	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");
+	}
+
+	ureg->r0 = ret;
+	if(up->procctl == Proc_tracesyscall) {
+		stopns = todget(nil);
+		sysretfmt(scallnr, (va_list)up->s.args, ret, startns, stopns);
+		s = splhi();
+		up->procctl = Proc_stopme;
+		procctl();
+		splx(s);
+	}
+
+	up->insyscall = 0;
+	up->psstate = 0;
+	if(scallnr == NOTED)
+		noted(ureg, *((ulong *)up->s.args));
+
+	if(scallnr != RFORK && (up->procctl || up->nnote)) {
+		splhi();
+		notify(ureg);
+	}
+
+	if(up->delaysched)
+		sched();
+
+	kexit(ureg);
+	splhi();
+}
--- /dev/null
+++ b/uartn900.c
@@ -1,0 +1,268 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+enum {
+	Rdll	= 0x00,
+	Rrhr	= 0x00,
+	Rthr	= 0x00,
+	Rdlh	= 0x04,
+	Rier	= 0x04,
+		IErhr	= 1 << 0,
+		IEthr	= 1 << 1,
+		IEls	= 1 << 2,
+		IEms	= 1 << 3,
+	Riir	= 0x08,
+		Ipending = 1 << 0,
+		Imodem	= 0x00,
+		Ithr	= 0x01,
+		Irhr	= 0x02,
+		Ils		= 0x03,
+		Irxt	= 0x06,
+		Ixoff	= 0x08,
+		Icts	= 0x10,
+		Imask 	= 0x1f,
+	Rfcr	= 0x08,
+		FCRen		= 1 << 0,
+	Refr	= 0x08,
+	Rlcr	= 0x0c,
+	Rmcr	= 0x10,
+	Rxon1	= 0x10,
+	Rlsr	= 0x14,
+		LSRrxempty	= 1 << 0,
+		LSRrxover	= 1 << 1,
+		LSRrxparity	= 1 << 2,
+		LSRrxframe	= 1 << 3,
+		LSRrxbreak	= 1 << 4,
+		LSRtxempty	= 1 << 5,
+		LSRtxshift	= 1 << 6,
+		LSRrxstat	= 1 << 7,
+	Rxon2	= 0x14,
+	Rmsr	= 0x18,
+	Rtcr	= 0x18,
+	Rxoff1	= 0x18,
+	Rspr	= 0x1c,
+	Rtlr	= 0x1c,
+	Rxoff2	= 0x1c,
+	Rmdr1	= 0x20,
+	Rmdr2	= 0x24,
+	Rsysc	= 0x54,
+		SCreset		= 1 << 1,
+	Rsyss	= 0x58,
+		SSreset		= 1 << 0,
+};
+
+#define csr32r(c, r) ((c)->io[(r)/4])
+#define csr32w(c, r, w) ((c)->io[(r)/4] = (w))
+
+typedef struct Ctlr Ctlr;
+struct Ctlr {
+	Lock;
+
+	u32int *io;
+	ulong irq;
+
+	int ie;
+};
+
+extern PhysUart n900physuart;
+
+static Ctlr ctlr[] = {
+	{ .io = (u32int*) PHYSUART1, .irq = IRQUART1, },
+	{ .io = (u32int*) PHYSUART2, .irq = IRQUART2, },
+	{ .io = (u32int*) PHYSUART3, .irq = IRQUART3, },
+};
+
+static Uart n900uart[] = {
+	{
+		.regs = &ctlr[0],
+		.name = "uart1",
+		.freq = 48000000,
+		.phys = &n900physuart,
+		.next = &n900uart[1],
+	},
+	{
+		.regs = &ctlr[1],
+		.name = "uart2",
+		.freq = 48000000,
+		.phys = &n900physuart,
+		.next = &n900uart[2],
+	},
+	{
+		.regs = &ctlr[2],
+		.name = "uart3",
+		.freq = 48000000,
+		.phys = &n900physuart,
+		.next = nil,
+	},
+};
+
+static Uart *
+n900uartpnp(void)
+{
+	return n900uart;
+}
+
+static long
+n900uartstatus(Uart *, void *, long, long)
+{
+	return 0;
+}
+
+static void
+n900uartintr(Ureg *, void *arg)
+{
+	Uart *uart;
+	Ctlr *ctlr;
+	int lsr;
+	char c;
+
+	uart = arg;
+	ctlr = uart->regs;
+
+	ilock(ctlr);
+	switch((csr32r(ctlr, Riir) >> 1) & Imask) {
+	case Ithr:
+		uartkick(uart);
+		break;
+
+	case Irhr:
+		while((lsr = csr32r(ctlr, Rlsr)) & LSRrxempty) {
+			c = csr32r(ctlr, Rrhr);
+
+			if(lsr & LSRrxover) { uart->oerr++; break; }
+			if(lsr & LSRrxparity) { uart->perr++; break; }
+			if(lsr & LSRrxframe) { uart->ferr++; break; }
+
+			uartrecv(uart, c);
+		}
+
+		break;
+	}
+
+	iunlock(ctlr);
+}
+
+static void
+n900uartenable(Uart *uart, int ie)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	ilock(ctlr);
+
+	csr32w(ctlr, Rsysc, SCreset);
+	while(!(csr32r(ctlr, Rsyss) & SSreset))
+		;
+
+	csr32w(ctlr, Rfcr, FCRen);
+	if(ie) {
+		if(!ctlr->ie) {
+			intrenable(ctlr->irq, n900uartintr, uart, 0, uart->name);
+			ctlr->ie = 1;
+		}
+
+		csr32w(ctlr, Rier, IErhr);
+	}
+
+	iunlock(ctlr);
+}
+
+static void
+n900uartdisable(Uart *uart)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+
+	ilock(ctlr);
+	csr32w(ctlr, Rier, 0);
+	if(ctlr->ie) {
+		intrdisable(ctlr->irq, n900uartintr, uart, 0, uart->name);
+		ctlr->ie = 0;
+	}
+
+	iunlock(ctlr);
+}
+
+static void
+n900uartkick(Uart *uart)
+{
+	Ctlr *ctlr;
+	int i;
+
+	ctlr = uart->regs;
+	if(uart->blocked)
+		return;
+
+	for(i = 0; i < 128; i++) {
+		if(csr32r(ctlr, Rlsr) & LSRtxempty) {
+			if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
+				break;
+
+			csr32w(ctlr, Rthr, *uart->op++);
+		}
+	}
+}
+
+static int
+n900uartgetc(Uart *uart)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	while(!(csr32r(ctlr, Rlsr) & LSRrxempty))
+		;
+
+	return csr32r(ctlr, Rrhr);
+}
+
+static void
+n900uartputc(Uart *uart, int c)
+{
+	Ctlr *ctlr;
+
+	ctlr = uart->regs;
+	while(!(csr32r(ctlr, Rlsr) & LSRtxempty))
+		;
+
+	csr32w(ctlr, Rthr, c);
+}
+
+static void n900uartnop(Uart *, int) {}
+static int n900uartnope(Uart *, int) { return -1; }
+
+PhysUart n900physuart = {
+	.name = "n900",
+
+	.pnp = n900uartpnp,
+	.enable = n900uartenable,
+	.disable = n900uartdisable,
+	.kick = n900uartkick,
+	.status = n900uartstatus,
+	.getc = n900uartgetc,
+	.putc = n900uartputc,
+
+	.dobreak = n900uartnop,
+	.baud = n900uartnope,
+	.bits = n900uartnope,
+	.stop = n900uartnope,
+	.parity = n900uartnope,
+	.modemctl = n900uartnop,
+	.rts = n900uartnop,
+	.dtr = n900uartnop,
+	.fifo = n900uartnop,
+	.power = n900uartnop,
+};
+
+void
+uartinit(void)
+{
+	consuart = &n900uart[2];
+	consuart->console = 1;
+	uartputs(kmesg.buf, kmesg.n);
+}