shithub: riscv

Download patch

ref: dd79854239a8c434b1d50e29b381cb21c62f713f
parent: 0d9119da0f6e85e4263fd790989b1851dd3d104d
author: Keegan Saunders <keegan@undefinedbehaviour.org>
date: Sat Oct 28 20:41:46 EDT 2023

add arm64 qemu kernel

This kernel is designed for running on the QEMU "virt"
machine. It implements the QEMU ECAM-based PCIe and
utilizes VirtIO drivers for networking and storage.
USB, screen and so on are umimplemented, because this
kernel is meant to be installed via UART, and then
administered as a CPU server via rcpu. It is booted
using U-Boot, so the QEMU "virt" machine "firmware"
is required, otherwise installation is straightforward.

This is the QEMU command used to run this kernel on
an M1 Mac under Hypervisor.framework:

	qemu-system-aarch64 -M virt-2.12,accel=hvf,gic-version=3 \
		-cpu host -m 4G -smp 4 \
		-bios u-boot.bin \
		-drive file=9front.arm64.qcow2,if=none,id=disk \
		-device virtio-blk-pci-non-transitional,drive=disk \
		-serial stdio

This code is based off of the i.MX 8 kernel.

--- a/sys/lib/dist/mkfile
+++ b/sys/lib/dist/mkfile
@@ -32,6 +32,19 @@
 	mv $target.$pid.disk $target
 	}
 
+%.arm64.qcow2: /n/src9/sys/src/boot/qemu/boot.scr
+	@{
+	objtype=arm64
+	kernel=/n/src9/$objtype/9qemu.u
+	> /env/plan9.ini {
+		echo 'console=0'
+	}
+	fatfiles=(/n/src9/sys/src/boot/qemu/boot.scr /env/plan9.ini $kernel)
+	mb=3770
+	mk $target.$pid.disk
+	mv $target.$pid.disk $target
+	}
+
 %.pi.img:
 	@{
 	objtype=arm
@@ -148,7 +161,7 @@
 	mk binds
 	rm -f $target
 	s=`{basename $target}
-	if(~ $target *.amd64.qcow2.*){
+	if(~ $target *.qcow2.*){
 		disk/qcowfs -n `{echo $mb '*1048576' | pc} $target
 		disk/partfs -m /n/$s /mnt/qcow/data
 	}
@@ -172,7 +185,7 @@
 		disk/prep -bw -a^(nvram fs) $d/plan9
 		disk/format -d $d/dos $fatfiles
 	}
-	if not if(~ $target *.reform.img.*){
+	if not if(~ $target *.reform.img.* *.arm64.qcow2.*){
 		{
 			echo 'a p1 4M 100M'
 			echo 't p1 FAT32'
--- /dev/null
+++ b/sys/src/9/arm64/cache.v8.s
@@ -1,0 +1,212 @@
+#include "sysreg.h"
+
+#undef	SYSREG
+#define	SYSREG(op0,op1,Cn,Cm,op2)	SPR(((op0)<<19|(op1)<<16|(Cn)<<12|(Cm)<<8|(op2)<<5))
+
+/*
+ * instruction cache operations
+ */
+TEXT cacheiinvse(SB), 1, $-4
+	MOVWU	len+8(FP), R2
+	ADD	R0, R2
+
+	MRS	DAIF, R11
+	MSR	$0x2, DAIFSet
+	MOVWU	$1, R10
+	MSR	R10, CSSELR_EL1
+	ISB	$SY
+	MRS	CCSIDR_EL1, R4
+
+	ANDW	$7, R4
+	ADDW	$4, R4		// log2(linelen)
+	LSL	R4, R10
+	LSR	R4, R0
+	LSL	R4, R0
+
+_iinvse:
+	IC	R0, 3,7,5,1	// IVAU
+	ADD	R10, R0
+	CMP	R0, R2
+	BGT	_iinvse
+	DSB	$NSH
+	ISB	$SY
+	MSR	R11, DAIF
+	RETURN
+
+TEXT cacheiinv(SB), 1, $-4
+	IC	R0, 0,7,5,0	// IALLU
+	DSB	$NSH
+	ISB	$SY
+	RETURN
+
+TEXT cacheuwbinv(SB), 1, $0
+	BL	cachedwbinv(SB)
+	BL	cacheiinv(SB)
+	RETURN
+
+/*
+ * data cache operations
+ */
+TEXT cachedwbse(SB), 1, $-4
+	MOV	LR, R29
+	BL	cachedva<>(SB)
+TEXT dccvac(SB), 1, $-4
+	DC	R0, 3,7,10,1	// CVAC
+	RETURN
+
+TEXT cacheduwbse(SB), 1, $-4
+	MOV	LR, R29
+	BL	cachedva<>(SB)
+TEXT dccvau(SB), 1, $-4
+	DC	R0, 3,7,11,1	// CVAU
+	RETURN
+
+TEXT cachedinvse(SB), 1, $-4
+	MOV	LR, R29
+	BL	cachedva<>(SB)
+TEXT dcivac(SB), 1, $-4
+	DC	R0, 0,7,6,1	// IVAC
+	RETURN
+
+TEXT cachedwbinvse(SB), 1, $-4
+	MOV	LR, R29
+	BL	cachedva<>(SB)
+TEXT dccivac(SB), 1, $-4
+	DC	R0, 3,7,14,1	// CIVAC
+	RETURN
+
+TEXT cachedva<>(SB), 1, $-4
+	MOV	LR, R1
+	MOVWU	len+8(FP), R2
+	ADD	R0, R2
+
+	MRS	DAIF, R11
+	MSR	$0x2, DAIFSet
+	MOVWU	$0, R10
+	MSR	R10, CSSELR_EL1
+	ISB	$SY
+	MRS	CCSIDR_EL1, R4
+
+	ANDW	$7, R4
+	ADDW	$4, R4		// log2(linelen)
+	MOVWU	$1, R10
+	LSL	R4, R10
+	LSR	R4, R0
+	LSL	R4, R0
+
+	DSB	$SY
+	ISB	$SY
+_cachedva:
+	BL	(R1)
+	ADD	R10, R0
+	CMP	R0, R2
+	BGT	_cachedva
+	DSB	$SY
+	ISB	$SY
+	MSR	R11, DAIF
+	RET	R29
+
+/*
+ * l1 cache operations
+ */
+TEXT cachedwb(SB), 1, $-4
+	MOVWU	$0, R0
+_cachedwb:
+	MOV	LR, R29
+	BL	cachedsw<>(SB)
+TEXT dccsw(SB), 1, $-4
+	DC	R0, 0,7,10,2	// CSW
+	RETURN
+
+TEXT cachedinv(SB), 1, $-4
+	MOVWU	$0, R0
+_cachedinv:
+	MOV	LR, R29
+	BL	cachedsw<>(SB)
+TEXT dcisw(SB), 1, $-4
+	DC	R0, 0,7,6,2	// ISW
+	RETURN
+
+TEXT cachedwbinv(SB), 1, $-4
+	MOVWU	$0, R0
+_cachedwbinv:
+	MOV	LR, R29
+	BL	cachedsw<>(SB)
+TEXT dccisw(SB), 1, $-4
+	DC	R0, 0,7,14,2	// CISW
+	RETURN
+
+/*
+ * l2 cache operations
+ */
+TEXT l2cacheuwb(SB), 1, $-4
+	MOVWU	$1, R0
+	B	_cachedwb
+TEXT l2cacheuinv(SB), 1, $-4
+	MOVWU	$1, R0
+	B	_cachedinv
+TEXT l2cacheuwbinv(SB), 1, $-4
+	MOVWU	$1, R0
+	B	_cachedwbinv
+
+TEXT cachesize(SB), 1, $-4
+	MRS	DAIF, R11
+	MSR	$0x2, DAIFSet
+	MSR	R0, CSSELR_EL1
+	ISB	$SY
+	MRS	CCSIDR_EL1, R0
+	MSR	R11, DAIF
+	RETURN
+
+TEXT cachedsw<>(SB), 1, $-4
+	MOV	LR, R1
+
+	MRS	DAIF, R11
+	MSR	$0x2, DAIFSet
+	ADDW	R0, R0, R8
+	MSR	R8, CSSELR_EL1
+	ISB	$SY
+	MRS	CCSIDR_EL1, R4
+
+	LSR	$3, R4, R7
+	ANDW	$1023, R7	// lastway
+	ADDW	$1, R7, R5	// #ways
+
+	LSR	$13, R4, R2
+	ANDW	$32767, R2	// lastset
+	ADDW	$1, R2		// #sets
+
+	ANDW	$7, R4
+	ADDW	$4, R4		// log2(linelen)
+
+	MOVWU	$32, R3		// wayshift = 32 - log2(#ways)
+_countlog2ways:
+	CBZ	R7, _loop	// lastway == 0?
+	LSR	$1, R7		// lastway >>= 1
+	SUB	$1, R3		// wayshift--
+	B _countlog2ways
+_loop:
+	DSB	$SY
+	ISB	$SY
+_nextway:
+	MOVWU	$0, R6		// set
+_nextset:
+	LSL	R3, R7, R0	// way<<wayshift
+	LSL	R4, R6, R9	// set<<log2(linelen)
+	ORRW	R8, R0		// level
+	ORRW	R9, R0		// setway
+
+	BL	(R1)		// op(setway)
+
+	ADDW	$1, R6		// set++
+	CMPW	R2, R6
+	BLT	_nextset
+
+	ADDW	$1, R7		// way++
+	CMPW	R5, R7
+	BLT	_nextway
+
+	DSB	$SY
+	ISB	$SY
+	MSR	R11, DAIF
+	RET	R29
--- /dev/null
+++ b/sys/src/9/arm64/clock.c
@@ -1,0 +1,120 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "ureg.h"
+#include "sysreg.h"
+
+static uvlong freq;
+
+enum {
+	Enable	= 1<<0,
+	Imask	= 1<<1,
+	Istatus = 1<<2,
+};
+
+void
+clockshutdown(void)
+{
+}
+
+static void
+localclockintr(Ureg *ureg, void *)
+{
+	timerintr(ureg, 0);
+}
+
+void
+clockinit(void)
+{
+	syswr(PMCR_EL0, 1<<6 | 7);
+	syswr(PMCNTENSET, 1<<31);
+	syswr(PMUSERENR_EL0, 1<<2);
+	syswr(CNTKCTL_EL1, 1<<1);
+
+	syswr(CNTV_TVAL_EL0, ~0UL);
+	syswr(CNTV_CTL_EL0, Enable);
+
+	if(m->machno == 0){
+		freq = sysrd(CNTFRQ_EL0);
+		print("timer frequency %lld Hz\n", freq);
+	}
+
+	/*
+	 * we are using virtual counter register CNTVCT_EL0
+	 * instead of the performance counter in userspace.
+	 */
+	m->cyclefreq = freq;
+
+	intrenable(IRQcntvns, localclockintr, nil, BUSUNKNOWN, "clock");
+}
+
+void
+timerset(uvlong next)
+{
+	uvlong now;
+	long period;
+
+	now = fastticks(nil);
+	period = next - now;
+	syswr(CNTV_TVAL_EL0, period);
+}
+
+uvlong
+fastticks(uvlong *hz)
+{
+	if(hz)
+		*hz = freq;
+	return sysrd(CNTVCT_EL0);
+}
+
+ulong
+perfticks(void)
+{
+	return fastticks(nil);
+}
+
+ulong
+µs(void)
+{
+	uvlong hz;
+	uvlong t = fastticks(&hz);
+	return (t * 1000000ULL) / hz;
+}
+
+void
+microdelay(int n)
+{
+	ulong now;
+
+	now = µs();
+	while(µs() - now < n);
+}
+
+void
+delay(int n)
+{
+	while(--n >= 0)
+		microdelay(1000);
+}
+
+void
+synccycles(void)
+{
+	static Ref r1, r2;
+	int s;
+
+	s = splhi();
+	r2.ref = 0;
+	incref(&r1);
+	while(r1.ref != conf.nmach)
+		;
+//	syswr(PMCR_EL0, 1<<6 | 7);
+	incref(&r2);
+	while(r2.ref != conf.nmach)
+		;
+	r1.ref = 0;
+	splx(s);
+}
--- /dev/null
+++ b/sys/src/9/arm64/dat.h
@@ -1,0 +1,229 @@
+/*
+ * Time.
+ *
+ * HZ should divide 1000 evenly, ideally.
+ * 100, 125, 200, 250 and 333 are okay.
+ */
+#define	HZ		100			/* clock frequency */
+#define	MS2HZ		(1000/HZ)		/* millisec per clock tick */
+#define	TK2SEC(t)	((t)/HZ)		/* ticks to seconds */
+
+enum {
+	Mhz	= 1000 * 1000,
+
+	GpioLow = 0,
+	GpioHigh,
+	GpioRising,
+	GpioFalling,
+	GpioEdge,
+};
+
+typedef struct Conf	Conf;
+typedef struct Confmem	Confmem;
+typedef struct FPsave	FPsave;
+typedef struct PFPU	PFPU;
+typedef struct ISAConf	ISAConf;
+typedef struct Label	Label;
+typedef struct Lock	Lock;
+typedef struct Memcache	Memcache;
+typedef struct MMMU	MMMU;
+typedef struct Mach	Mach;
+typedef struct Page	Page;
+typedef struct PhysUart	PhysUart;
+typedef struct Pcidev	Pcidev;
+typedef struct PMMU	PMMU;
+typedef struct Proc	Proc;
+typedef u64int		PTE;
+typedef struct Soc	Soc;
+typedef struct Uart	Uart;
+typedef struct Ureg	Ureg;
+typedef uvlong		Tval;
+typedef void		KMap;
+
+#pragma incomplete Pcidev
+#pragma incomplete Ureg
+
+#define MAXSYSARG	5	/* for mount(fd, mpt, flag, arg, srv) */
+
+/*
+ *  parameters for sysproc.c
+ */
+#define AOUT_MAGIC	(R_MAGIC)
+
+struct Lock
+{
+	ulong	key;
+	u32int	sr;
+	uintptr	pc;
+	Proc*	p;
+	Mach*	m;
+	int	isilock;
+};
+
+struct Label
+{
+	uintptr	sp;
+	uintptr	pc;
+};
+
+struct FPsave
+{
+	uvlong	regs[32][2];
+
+	ulong	control;
+	ulong	status;
+};
+
+#define KFPSTATE
+
+struct PFPU
+{
+	int	fpstate;
+	int	kfpstate;
+	FPsave	*fpsave;
+	FPsave	*kfpsave;
+};
+
+enum
+{
+	FPinit,
+	FPactive,
+	FPinactive,
+	FPprotected,
+
+	/* bits or'd with the state */
+	FPillegal= 0x100,
+};
+
+struct Confmem
+{
+	uintptr	base;
+	ulong	npage;
+	uintptr	limit;
+	uintptr	kbase;
+	uintptr	klimit;
+};
+
+struct Conf
+{
+	ulong	nmach;		/* processors */
+	ulong	nproc;		/* processes */
+	Confmem	mem[3];		/* physical memory */
+	ulong	npage;		/* total physical pages of memory */
+	ulong	upages;		/* user page pool */
+	ulong	copymode;	/* 0 is copy on write, 1 is copy on reference */
+	ulong	ialloc;		/* max interrupt time allocation in bytes */
+	ulong	pipeqsize;	/* size in bytes of pipe queues */
+	ulong	nimage;		/* number of page cache image headers */
+	ulong	nswap;		/* number of swap pages */
+	int	nswppo;		/* max # of pageouts per segment pass */
+	ulong	hz;		/* processor cycle freq */
+	ulong	mhz;
+	int	monitor;	/* flag */
+};
+
+/*
+ *  MMU stuff in Mach.
+ */
+struct MMMU
+{
+	PTE*	mmutop;		/* first level user page table */
+};
+
+/*
+ *  MMU stuff in proc
+ */
+#define NCOLOR	1		/* 1 level cache, don't worry about VCE's */
+
+struct PMMU
+{
+	union {
+	Page	*mmufree;	/* mmuhead[0] is freelist head */
+	Page	*mmuhead[PTLEVELS];
+	};
+	Page	*mmutail[PTLEVELS];
+	int	asid;
+	uintptr	tpidr;
+};
+
+#include "../port/portdat.h"
+
+struct Mach
+{
+	int	machno;			/* physical id of processor */
+	uintptr	splpc;			/* pc of last caller to splhi */
+	Proc*	proc;			/* current process on this processor */
+	/* end of offsets known to asm */
+
+	MMMU;
+
+	PMach;
+
+	int	fpstate;
+	FPsave	*fpsave;
+
+	int	cputype;
+	ulong	delayloop;
+
+	int	stack[1];
+};
+
+struct
+{
+	char	machs[MAXMACH];		/* active CPUs */
+	int	exiting;		/* shutdown */
+}active;
+
+#define MACHP(n)	((Mach*)MACHADDR(n))
+
+extern register Mach* m;			/* R27 */
+extern register Proc* up;			/* R26 */
+extern int normalprint;
+
+/*
+ *  a parsed plan9.ini line
+ */
+#define NISAOPT		8
+
+struct ISAConf {
+	char	*type;
+	uvlong	port;
+	int	irq;
+	ulong	dma;
+	ulong	mem;
+	ulong	size;
+	ulong	freq;
+
+	int	nopt;
+	char	*opt[NISAOPT];
+};
+
+/*
+ * Horrid. But the alternative is 'defined'.
+ */
+#ifdef _DBGC_
+#define DBGFLG		(dbgflg[_DBGC_])
+#else
+#define DBGFLG		(0)
+#endif /* _DBGC_ */
+
+int vflag;
+extern char dbgflg[256];
+
+#define dbgprint	print		/* for now */
+
+/*
+ *  hardware info about a device
+ */
+typedef struct {
+	ulong	port;
+	int	size;
+} Devport;
+
+struct DevConf
+{
+	ulong	intnum;			/* interrupt number */
+	char	*type;			/* card type, malloced */
+	int	nports;			/* Number of ports */
+	Devport	*ports;			/* The ports themselves */
+};
--- /dev/null
+++ b/sys/src/9/arm64/devrtc.c
@@ -1,0 +1,108 @@
+/*
+ *  PL031 RTC driver
+ */
+
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"../port/error.h"
+
+enum{
+	Qdir = 0,
+	Qrtc,
+};
+
+static u32int *regs = (u32int *)(VIRTIO + 0x01010000);
+
+Dirtab rtcdir[]={
+	".",	{Qdir, 0, QTDIR},	0,	0555,
+	"rtc",	{Qrtc, 0},		0,	0664,
+};
+
+static Chan*
+rtcattach(char* spec)
+{
+	return devattach('r', spec);
+}
+
+static Walkqid*	 
+rtcwalk(Chan* c, Chan *nc, char** name, int nname)
+{
+	return devwalk(c, nc, name, nname, rtcdir, nelem(rtcdir), devgen);
+}
+
+static int	 
+rtcstat(Chan* c, uchar* dp, int n)
+{
+	return devstat(c, dp, n, rtcdir, nelem(rtcdir), devgen);
+}
+
+static Chan*
+rtcopen(Chan* c, int omode)
+{
+	omode = openmode(omode);
+	switch((ulong)c->qid.path){
+	case Qrtc:
+		if(strcmp(up->user, eve)!=0 && omode!=OREAD)
+			error(Eperm);
+		break;
+	}
+	return devopen(c, omode, rtcdir, nelem(rtcdir), devgen);
+}
+
+static void	 
+rtcclose(Chan*)
+{
+}
+
+long	 
+rtctime(void)
+{
+	return *regs;
+}
+
+static long	 
+rtcread(Chan* c, void* buf, long n, vlong off)
+{
+	ulong offset = off;
+
+	if(c->qid.type & QTDIR)
+		return devdirread(c, buf, n, rtcdir, nelem(rtcdir), devgen);
+
+	switch((ulong)c->qid.path){
+	case Qrtc:
+		return readnum(offset, buf, n, rtctime(), 12);
+	}
+	error(Ebadarg);
+	return 0;
+}
+
+static long	 
+rtcwrite(Chan*, void*, long, vlong)
+{
+	error(Eperm);
+	return 0;
+}
+
+Dev rtcdevtab = {
+	'r',
+	"rtc",
+
+	devreset,
+	devinit,
+	devshutdown,
+	rtcattach,
+	rtcwalk,
+	rtcstat,
+	rtcopen,
+	devcreate,
+	rtcclose,
+	rtcread,
+	devbread,
+	rtcwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
--- /dev/null
+++ b/sys/src/9/arm64/fns.h
@@ -1,0 +1,171 @@
+#include "../port/portfns.h"
+
+/* l.s */
+extern void sev(void);
+extern int tas(void *);
+extern int cmpswap(long*, long, long);
+extern void coherence(void);
+extern void idlehands(void);
+extern uvlong vcycles(void);
+#define cycles(ip) *(ip) = vcycles()
+extern int splfhi(void);
+extern void splflo(void);
+extern void touser(uintptr sp);
+extern void forkret(void);
+extern void noteret(void);
+extern void returnto(void*);
+extern void fpon(void);
+extern void fpoff(void);
+extern void fpsaveregs(void*);
+extern void fploadregs(void*);
+extern void hvccall(Ureg*);
+
+extern void setttbr(uintptr pa);
+extern uintptr getfar(void);
+
+extern void flushasidva(uintptr asidva);
+extern void tlbivae1is(uintptr asidva);
+
+extern void flushasidvall(uintptr asidva);
+extern void tlbivale1is(uintptr asidva);
+
+extern void flushasid(uintptr asid);
+extern void tlbiaside1is(uintptr asid);
+
+extern void flushtlb(void);
+extern void tlbivmalle1(void);
+
+extern void flushlocaltlb(void);
+extern void tlbivmalle1(void);
+
+/* cache */
+extern ulong cachesize(int level);
+
+extern void cacheiinvse(void*, int);
+extern void cacheuwbinv(void);
+extern void cacheiinv(void);
+
+extern void cachedwbse(void*, int);
+extern void cacheduwbse(void*, int);
+extern void cachedinvse(void*, int);
+extern void cachedwbinvse(void*, int);
+
+extern void cachedwb(void);
+extern void cachedinv(void);
+extern void cachedwbinv(void);
+
+extern void l2cacheuwb(void);
+extern void l2cacheuinv(void);
+extern void l2cacheuwbinv(void);
+
+/* mmu */
+#define	getpgcolor(a)	0
+extern uintptr paddr(void*);
+#define PADDR(a) paddr((void*)(a))
+extern uintptr cankaddr(uintptr);
+extern void* kaddr(uintptr);
+#define KADDR(a) kaddr(a)
+extern void kmapinval(void);
+#define	VA(k)	((uintptr)(k))
+extern KMap *kmap(Page*);
+extern void kunmap(KMap*);
+extern uintptr mmukmap(uintptr, uintptr, usize);
+extern void* vmap(uvlong, vlong);
+extern void vunmap(void*, vlong);
+
+extern void mmu0init(uintptr*);
+extern void mmuidmap(uintptr*);
+extern void mmu1init(void);
+extern void meminit(void);
+
+extern void putasid(Proc*);
+
+extern void* ucalloc(usize);
+
+/* clock */
+extern void clockinit(void);
+extern void synccycles(void);
+extern void armtimerset(int);
+extern void clockshutdown(void);
+
+/* fpu */
+extern void fpuinit(void);
+extern void fpuprocsetup(Proc*);
+extern void fpuprocfork(Proc*);
+extern void fpuprocsave(Proc*);
+extern void fpuprocrestore(Proc*);
+extern FPsave* fpukenter(Ureg*);
+extern void fpukexit(Ureg*, FPsave*);
+extern void mathtrap(Ureg*);
+
+/* trap */
+extern void trapinit(void);
+extern int userureg(Ureg*);
+extern void evenaddr(uintptr);
+extern void setkernur(Ureg*, Proc*);
+extern void procfork(Proc*);
+extern void procsetup(Proc*);
+extern void procsave(Proc*);
+extern void procrestore(Proc *);
+extern void trap(Ureg*);
+extern void syscall(Ureg*);
+extern void noted(Ureg*, ulong);
+extern void faultarm64(Ureg*);
+extern void dumpstack(void);
+extern void dumpregs(Ureg*);
+
+/* irq */
+extern void intrinit(void);
+extern void intrcpushutdown(void);
+extern void intrsoff(void);
+extern void intrenable(int, void (*)(Ureg*, void*), void*, int, char*);
+extern void intrdisable(int, void (*)(Ureg*, void*), void*, int, char*);
+extern int irq(Ureg*);
+extern void fiq(Ureg*);
+
+/* sysreg */
+extern uvlong	sysrd(ulong);
+extern void	syswr(ulong, uvlong);
+
+/* uartimx */
+extern void uartconsinit(void);
+
+/* dma */
+extern void dmaflush(int, void*, ulong);
+
+/* main */
+extern char *getconf(char *name);
+extern void setconfenv(void);
+extern void writeconf(void);
+
+extern int isaconfig(char*, int, ISAConf*);
+extern void links(void);
+
+/* ccm */
+extern void setclkgate(char *name, int on);
+extern void setclkrate(char *name, char *source, int freq);
+extern int getclkrate(char *name);
+
+/* gpc */
+extern void powerup(char *dom);
+
+/* lcd */
+extern void lcdinit(void);
+
+/* iomux */
+extern void iomuxpad(char *pads, char *sel, char *cfg);
+extern uint iomuxgpr(int gpr, uint set, uint mask);
+
+/* gpio */
+#define GPIO_PIN(n, m)	((n)<<5 | (m))
+extern void gpioout(uint pin, int set);
+extern int gpioin(uint pin);
+void gpiointrenable(uint pin, int mode, void (*f)(uint pin, void *a), void *a);
+void gpiointrdisable(uint pin);
+
+/* pciimx */
+extern int pcicfgrw8(int tbdf, int rno, int data, int read);
+extern int pcicfgrw16(int tbdf, int rno, int data, int read);
+extern int pcicfgrw32(int tbdf, int rno, int data, int read);
+extern void pciintrenable(int tbdf, void (*f)(Ureg*, void*), void *a);
+extern void pciintrdisable(int tbdf, void (*f)(Ureg*, void*), void *a);
--- /dev/null
+++ b/sys/src/9/arm64/fpu.c
@@ -1,0 +1,289 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+#include "ureg.h"
+#include "sysreg.h"
+
+/* libc */
+extern ulong getfcr(void);
+extern void setfcr(ulong fcr);
+extern ulong getfsr(void);
+extern void setfsr(ulong fsr);
+
+static FPsave fpsave0;
+
+static void
+fpsave(FPsave *p)
+{
+	p->control = getfcr();
+	p->status = getfsr();
+	fpsaveregs(p->regs);
+	fpoff();
+}
+
+static void
+fprestore(FPsave *p)
+{
+	fpon();
+	setfcr(p->control);
+	setfsr(p->status);
+	fploadregs(p->regs);
+}
+
+static void
+fpinit(void)
+{
+	fprestore(&fpsave0);
+}
+
+void
+fpuinit(void)
+{
+	m->fpstate = FPinit;
+	m->fpsave = nil;
+	fpoff();
+}
+
+static FPsave*
+fpalloc(void)
+{
+	FPsave *save;
+
+	while((save = mallocalign(sizeof(FPsave), 16, 0, 0)) == nil){
+		spllo();
+		resrcwait("no memory for FPsave");
+		splhi();
+	}
+	return save;
+}
+
+static void
+fpfree(FPsave *save)
+{
+	free(save);
+}
+
+
+/*
+ *  Protect or save FPU state and setup new state
+ *  (lazily in the case of user process) for the kernel.
+ *  All syscalls, traps and interrupts (except mathtrap()!)
+ *  are handled between fpukenter() and fpukexit(),
+ *  so they can use floating point and vector instructions.
+ */
+FPsave*
+fpukenter(Ureg*)
+{
+	if(up == nil){
+		switch(m->fpstate){
+		case FPactive:
+			fpsave(m->fpsave);
+			/* wet floor */
+		case FPinactive:
+			m->fpstate = FPinit;
+			return m->fpsave;
+		}
+		return nil;
+	}
+
+	switch(up->fpstate){
+	case FPactive:
+		up->fpstate = FPprotected;
+		fpoff();
+		/* wet floor */
+	case FPprotected:
+		return nil;
+	}
+
+	switch(up->kfpstate){
+	case FPactive:
+		fpsave(up->kfpsave);
+		/* wet floor */
+	case FPinactive:
+		up->kfpstate = FPinit;
+		return up->kfpsave;
+	}
+	return nil;
+}
+
+void
+fpukexit(Ureg *ureg, FPsave *save)
+{
+	if(up == nil){
+		switch(m->fpstate){
+		case FPactive:
+			fpoff();
+			/* wet floor */
+		case FPinactive:
+			fpfree(m->fpsave);
+			m->fpstate = FPinit;
+		}
+		m->fpsave = save;
+		if(save != nil)
+			m->fpstate = FPinactive;
+		return;
+	}
+
+	if(up->fpstate == FPprotected){
+		if(userureg(ureg)){
+			up->fpstate = FPactive;
+			fpon();
+		}
+		return;
+	}
+
+	switch(up->kfpstate){
+	case FPactive:
+		fpoff();
+		/* wet floor */
+	case FPinactive:
+		fpfree(up->kfpsave);
+		up->kfpstate = FPinit;
+	}
+	up->kfpsave = save;
+	if(save != nil)
+		up->kfpstate = FPinactive;
+}
+
+void
+fpuprocsetup(Proc *p)
+{
+	p->fpstate = FPinit;
+}
+
+void
+fpuprocfork(Proc *p)
+{
+	int s;
+
+	s = splhi();
+	switch(up->fpstate & ~FPillegal){
+	case FPprotected:
+		fpon();
+		/* wet floor */
+	case FPactive:
+		fpsave(up->fpsave);
+		up->fpstate = FPinactive;
+		/* wet floor */
+	case FPinactive:
+		if(p->fpsave == nil)
+			p->fpsave = fpalloc();
+		memmove(p->fpsave, up->fpsave, sizeof(FPsave));
+		p->fpstate = FPinactive;
+	}
+	splx(s);
+}
+
+void
+fpuprocsave(Proc *p)
+{
+	if(p->state == Moribund){
+		if(p->fpstate == FPactive || p->kfpstate == FPactive)
+			fpoff();
+		fpfree(p->fpsave);
+		fpfree(p->kfpsave);
+		p->fpsave = p->kfpsave = nil;
+		p->fpstate = p->kfpstate = FPinit;
+		return;
+	}
+	if(p->kfpstate == FPactive){
+		fpsave(p->kfpsave);
+		p->kfpstate = FPinactive;
+		return;
+	}
+	if(p->fpstate == FPprotected)
+		fpon();
+	else if(p->fpstate != FPactive)
+		return;
+	fpsave(p->fpsave);
+	p->fpstate = FPinactive;
+}
+
+void
+fpuprocrestore(Proc*)
+{
+	/*
+	 * when the scheduler switches,
+	 * we can discard its fp state.
+	 */
+	switch(m->fpstate){
+	case FPactive:
+		fpoff();
+		/* wet floor */
+	case FPinactive:
+		fpfree(m->fpsave);
+		m->fpsave = nil;
+		m->fpstate = FPinit;
+	}
+}
+
+void
+mathtrap(Ureg *ureg)
+{
+	if(!userureg(ureg)){
+		if(up == nil){
+			switch(m->fpstate){
+			case FPinit:
+				m->fpsave = fpalloc();
+				m->fpstate = FPactive;
+				fpinit();
+				break;
+			case FPinactive:
+				fprestore(m->fpsave);
+				m->fpstate = FPactive;
+				break;
+			default:
+				panic("floating point error in irq");
+			}
+			return;
+		}
+
+		if(up->fpstate == FPprotected){
+			fpon();
+			fpsave(up->fpsave);
+			up->fpstate = FPinactive;
+		}
+
+		switch(up->kfpstate){
+		case FPinit:
+			up->kfpsave = fpalloc();
+			up->kfpstate = FPactive;
+			fpinit();
+			break;
+		case FPinactive:
+			fprestore(up->kfpsave);
+			up->kfpstate = FPactive;
+			break;
+		default:
+			panic("floating point error in trap");
+		}
+		return;
+	}
+
+	if(up->fpstate & FPillegal){
+		postnote(up, 1, "sys: floating point in note handler", NDebug);
+		return;
+	}
+	switch(up->fpstate){
+	case FPinit:
+		if(up->fpsave == nil)
+			up->fpsave = fpalloc();
+		up->fpstate = FPactive;
+		fpinit();
+		break;
+	case FPinactive:
+		fprestore(up->fpsave);
+		up->fpstate = FPactive;
+		break;
+	case FPprotected:
+		up->fpstate = FPactive;
+		fpon();
+		break;
+	case FPactive:
+		postnote(up, 1, "sys: floating point error", NDebug);
+		break;
+	}
+}
--- /dev/null
+++ b/sys/src/9/arm64/gic.c
@@ -1,0 +1,320 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/pci.h"
+#include "ureg.h"
+#include "sysreg.h"
+#include "../port/error.h"
+
+enum {
+	GICD_CTLR	= 0x000/4,	/* RW, Distributor Control Register */
+	GICD_TYPER	= 0x004/4,	/* RO, Interrupt Controller Type */
+	GICD_IIDR	= 0x008/4,	/* RO, Distributor Implementer Identification Register */
+
+	GICD_IGROUPR0	= 0x080/4,	/* RW, Interrupt Group Registers (0x80-0xBC) */
+
+	GICD_ISENABLER0	= 0x100/4,	/* RW, Interrupt Set-Enable Registers (0x100-0x13C) */
+	GICD_ICENABLER0	= 0x180/4,	/* RW, Interrupt Clear-Enable Registers (0x180-0x1BC) */
+
+	GICD_ISPENDR0	= 0x200/4,	/* RW, Interrupt Set-Pending Registers (0x200-0x23C) */
+	GICD_ICPENDR0	= 0x280/4,	/* RW, Interrupt Clear-Pending Registers (0x280-0x2BC) */
+
+	GICD_ISACTIVER0	= 0x300/4,	/* RW, Interrupt Set-Active Registers (0x300-0x33C) */
+	GICD_ICACTIVER0 = 0x380/4,	/* RW, Interrupt Clear-Active Registers (0x380-0x3BC) */
+
+	GICD_IPRIORITYR0= 0x400/4,	/* RW, Interrupt Priority Registers (0x400-0x5FC) */
+	GICD_TARGETSR0	= 0x800/4,	/* RW, Interrupt Target Registers (0x800-0x9FC) */
+	GICD_ICFGR0	= 0xC00/4,	/* RW, Interrupt Configuration Registers (0xC00-0xC7C) */
+
+	GICD_ISR0	= 0xD00/4,
+	GICD_PPISR	= GICD_ISR0,	/* RO, Private Peripheral Interrupt Status Register */
+	GICD_SPISR0	= GICD_ISR0+1,	/* RO, Shared Peripheral Interrupt Status Register */
+	GICD_SGIR	= 0xF00/4,	/* WO, Software Generated Interrupt Register */
+
+	GICD_CPENDSGIR0	= 0xF10/4,	/* RW, SGI Clear-Pending Registers (0xF10-0xF1C) */
+	GICD_SPENDSGIR0	= 0xF20/4,	/* RW, SGI Set-Pending Registers (0xF20-0xF2C) */
+
+	GICD_PIDR4	= 0xFD0/4,	/* RO, Perpheral ID Registers */
+	GICD_PIDR5	= 0xFD4/4,
+	GICD_PIDR6	= 0xFD8/4,
+	GICD_PIDR7	= 0xFDC/4,
+	GICD_PIDR0	= 0xFE0/4,
+	GICD_PIDR1	= 0xFE4/4,
+	GICD_PIDR2	= 0xFE8/4,
+	GICD_PIDR3	= 0xFEC/4,
+
+	GICD_CIDR0	= 0xFF0/4,	/* RO, Component ID Registers */
+	GICD_CIDR1	= 0xFF4/4,
+	GICD_CIDR2	= 0xFF8/4,
+	GICD_CIDR3	= 0xFFC/4,
+
+	RD_base		= 0x00000,
+	GICR_CTLR	= (RD_base+0x000)/4,
+	GICR_IIDR	= (RD_base+0x004)/4,
+	GICR_TYPER	= (RD_base+0x008)/4,
+	GICR_STATUSR	= (RD_base+0x010)/4,
+	GICR_WAKER	= (RD_base+0x014)/4,
+	GICR_SETLPIR	= (RD_base+0x040)/4,
+	GICR_CLRLPIR	= (RD_base+0x048)/4,
+	GICR_PROPBASER	= (RD_base+0x070)/4,
+	GICR_PENDBASER	= (RD_base+0x078)/4,
+	GICR_INVLPIR	= (RD_base+0x0A0)/4,
+	GICR_INVALLR	= (RD_base+0x0B0)/4,
+	GICR_SYNCR	= (RD_base+0x0C0)/4,
+
+	SGI_base	= 0x10000,
+	GICR_IGROUPR0	= (SGI_base+0x080)/4,
+	GICR_ISENABLER0	= (SGI_base+0x100)/4,
+	GICR_ICENABLER0	= (SGI_base+0x180)/4,
+	GICR_ISPENDR0	= (SGI_base+0x200)/4,
+	GICR_ICPENDR0	= (SGI_base+0x280)/4,
+	GICR_ISACTIVER0	= (SGI_base+0x300)/4,
+	GICR_ICACTIVER0	= (SGI_base+0x380)/4,
+	GICR_IPRIORITYR0= (SGI_base+0x400)/4,
+	GICR_ICFGR0	= (SGI_base+0xC00)/4,
+	GICR_ICFGR1	= (SGI_base+0xC04)/4,
+	GICR_IGRPMODR0	= (SGI_base+0xD00)/4,
+	GICR_NSACR	= (SGI_base+0xE00)/4,
+};
+
+typedef struct Vctl Vctl;
+struct Vctl {
+	Vctl	*next;
+	void	(*f)(Ureg*, void*);
+	void	*a;
+	int	irq;
+	u32int	intid;
+};
+
+static Lock vctllock;
+static Vctl *vctl[MAXMACH][32], *vfiq;
+static u32int *dregs = (u32int*)VIRTIO;
+
+static u32int*
+getrregs(int machno)
+{
+	u32int *rregs = (u32int*)(VIRTIO + 0xa0000);
+
+	for(;;){
+		if((rregs[GICR_TYPER] & 0xFFFF00) == (machno << 8))
+			return rregs;
+		if(rregs[GICR_TYPER] & (1<<4))
+			break;
+		rregs += (0x20000/4);
+	}
+	panic("getrregs: no re-distributor for cpu %d\n", machno);
+	return nil;
+}
+
+void
+intrcpushutdown(void)
+{
+	/* disable cpu interface */
+	syswr(ICC_IGRPEN0_EL1, 0);
+	syswr(ICC_IGRPEN1_EL1, 0);
+	coherence();
+}
+
+void
+intrsoff(void)
+{
+	/* disable distributor */
+	dregs[GICD_CTLR] = 0;
+	coherence();
+	while(dregs[GICD_CTLR]&(1<<31))
+		;
+}
+
+void
+intrinit(void)
+{
+	u32int *rregs;
+	int i, n;
+
+	if(m->machno == 0){
+		intrsoff();
+
+		/* clear all interrupts */
+		n = ((dregs[GICD_TYPER] & 0x1F)+1) << 5;
+		for(i = 32; i < n; i += 32){
+			dregs[GICD_IGROUPR0 + (i/32)] = -1;
+
+			dregs[GICD_ISENABLER0 + (i/32)] = -1;
+			while(dregs[GICD_CTLR]&(1<<31))
+				;
+			dregs[GICD_ICENABLER0 + (i/32)] = -1;
+			while(dregs[GICD_CTLR]&(1<<31))
+				;
+			dregs[GICD_ICACTIVER0 + (i/32)] = -1;
+		}
+		for(i = 0; i < n; i += 4){
+			dregs[GICD_IPRIORITYR0 + (i/4)] = 0;
+			dregs[GICD_TARGETSR0 + (i/4)] = 0;
+		}
+		for(i = 32; i < n; i += 16){
+			dregs[GICD_ICFGR0 + (i/16)] = 0;
+		}
+		coherence();
+		while(dregs[GICD_CTLR]&(1<<31))
+			;
+		dregs[GICD_CTLR] = (1<<0) | (1<<1) | (1<<4);
+	}
+
+	rregs = getrregs(m->machno);
+	n = 32;
+	for(i = 0; i < n; i += 32){
+		rregs[GICR_IGROUPR0 + (i/32)] = -1;
+
+		rregs[GICR_ISENABLER0 + (i/32)] = -1;
+		while(rregs[GICR_CTLR]&(1<<3))
+			;
+		rregs[GICR_ICENABLER0 + (i/32)] = -1;
+		while(dregs[GICD_CTLR]&(1<<31))
+			;
+		rregs[GICR_ICACTIVER0 + (i/32)] = -1;
+	}
+	for(i = 0; i < n; i += 4){
+		rregs[GICR_IPRIORITYR0 + (i/4)] = 0;
+	}
+	coherence();
+	while(rregs[GICR_CTLR]&(1<<3))
+		;
+
+	coherence();
+
+	/* enable cpu interface */
+	syswr(ICC_CTLR_EL1, 0);
+	syswr(ICC_BPR1_EL1, 7);
+	syswr(ICC_PMR_EL1, 0xFF);
+
+	coherence();
+}
+
+
+/*
+ *  called by trap to handle irq interrupts.
+ *  returns true iff a clock interrupt, thus maybe reschedule.
+ */
+int
+irq(Ureg* ureg)
+{
+	Vctl *v;
+	int clockintr;
+	u32int intid;
+
+	m->intr++;
+	intid = sysrd(ICC_IAR1_EL1) & 0xFFFFFF;
+// iprint("i<%d>", intid);
+	if((intid & ~3) == 1020)
+		return 0; // spurious
+	clockintr = 0;
+	for(v = vctl[m->machno][intid%32]; v != nil; v = v->next)
+		if(v->intid == intid){
+			coherence();
+			v->f(ureg, v->a);
+			coherence();
+			if(v->irq == IRQcntvns)
+				clockintr = 1;
+		}
+	coherence();
+	syswr(ICC_EOIR1_EL1, intid);
+	return clockintr;
+}
+
+/*
+ * called direct from lexception.s to handle fiq interrupt.
+ */
+void
+fiq(Ureg *ureg)
+{
+	Vctl *v;
+	u32int intid;
+
+	m->intr++;
+	intid = sysrd(ICC_IAR1_EL1) & 0xFFFFFF;
+// iprint("f<%d>", intid);
+	if((intid & ~3) == 1020)
+		return;	// spurious
+	v = vfiq;
+	if(v != nil && v->intid == intid && m->machno == 0){
+		coherence();
+		v->f(ureg, v->a);
+		coherence();
+	}
+	syswr(ICC_EOIR1_EL1, intid);
+}
+
+void
+intrenable(int irq, void (*f)(Ureg*, void*), void *a, int tbdf, char *)
+{
+	Vctl *v;
+	u32int intid;
+	int cpu, prio;
+
+	if(BUSTYPE(tbdf) == BusPCI){
+		pciintrenable(tbdf, f, a);
+		return;
+	}
+
+	if(tbdf != BUSUNKNOWN)
+		return;
+
+	prio = 0x80;
+	intid = irq;
+	if((v = xalloc(sizeof(Vctl))) == nil)
+		panic("intrenable: no mem");
+	v->irq = irq;
+	v->intid = intid;
+	v->f = f;
+	v->a = a;
+
+	lock(&vctllock);
+	if(intid < SPI)
+		cpu = m->machno;
+	else
+		cpu = 0;
+	if(irq == IRQfiq){
+		vfiq = v;
+		prio = 0;
+	}else{
+		v->next = vctl[cpu][intid%32];
+		vctl[cpu][intid%32] = v;
+	}
+	syswr(ICC_IGRPEN1_EL1, sysrd(ICC_IGRPEN1_EL1)|1);
+	coherence();
+
+	syswr(ICC_EOIR1_EL1, intid);
+	coherence();
+
+	/* setup */
+	if(intid < 32){
+		u32int *rregs = getrregs(cpu);
+		rregs[GICR_IPRIORITYR0 + (intid/4)] |= prio << ((intid%4) << 3);
+		coherence();
+		rregs[GICR_ISENABLER0] = 1 << (intid%32);
+		coherence();
+		while(rregs[GICR_CTLR]&(1<<3))
+			;
+	} else {
+		dregs[GICD_IPRIORITYR0 + (intid/4)] |= prio << ((intid%4) << 3);
+		dregs[GICD_TARGETSR0 + (intid/4)] |= (1<<cpu) << ((intid%4) << 3);
+		coherence();
+		dregs[GICD_ISENABLER0 + (intid/32)] = 1 << (intid%32);
+		coherence();
+		while(dregs[GICD_CTLR]&(1<<31))
+			;
+	}
+	unlock(&vctllock);
+}
+
+void
+intrdisable(int tbdf, void (*f)(Ureg*, void*), void *a, int, char*)
+{
+	if(BUSTYPE(tbdf) == BusPCI){
+		pciintrdisable(tbdf, f, a);
+		return;
+	}
+}
--- /dev/null
+++ b/sys/src/9/arm64/init9.s
@@ -1,0 +1,4 @@
+TEXT main(SB), 1, $8
+	MOV	$setSB(SB), R28		/* load the SB */
+	MOV	$boot(SB), R0
+	B	startboot(SB)
--- /dev/null
+++ b/sys/src/9/arm64/io.h
@@ -1,0 +1,17 @@
+enum {
+	IRQfiq		= -1,
+
+	PPI		= 16,
+	SPI		= 32,
+
+	IRQcntvns	= PPI+11,
+
+	IRQuart		= SPI+1,
+
+	IRQpci1		= SPI+3,
+	IRQpci2		= SPI+4,
+	IRQpci3		= SPI+5,
+	IRQpci4		= SPI+6,
+};
+
+#define BUSUNKNOWN (-1)
--- /dev/null
+++ b/sys/src/9/arm64/l.s
@@ -1,0 +1,732 @@
+#include "mem.h"
+#include "sysreg.h"
+
+#undef	SYSREG
+#define	SYSREG(op0,op1,Cn,Cm,op2)	SPR(((op0)<<19|(op1)<<16|(Cn)<<12|(Cm)<<8|(op2)<<5))
+
+TEXT _start(SB), 1, $-4
+	MOV	R0, R26		/* save */
+
+	MOV	$setSB-KZERO(SB), R28
+	BL	svcmode<>(SB)
+
+	/* use dedicated stack pointer per exception level */
+	MOVWU	$1, R1
+	MSR	R1, SPSel
+
+	BL	mmudisable<>(SB)
+
+	/* invalidate local caches */
+	BL	cachedwbinv(SB)
+	BL	l2cacheuwbinv(SB)
+	BL	cacheiinv(SB)
+
+	MOV	$(MACHADDR(0)-KZERO), R27
+	MRS	MPIDR_EL1, R1
+	ANDW	$(MAXMACH-1), R1
+	MOVWU	$MACHSIZE, R2
+	MULW	R1, R2, R2
+	SUB	R2, R27
+
+	ADD	$(MACHSIZE-16), R27, R2
+	MOV	R2, SP
+
+	CBNZ	R1, _startup
+
+	/* clear page table and machs */
+	MOV	$(L1BOT-KZERO), R1
+	MOV	$(MACHADDR(-1)-KZERO), R2
+_zerol1:
+	MOV	ZR, (R1)8!
+	CMP	R1, R2
+	BNE	_zerol1
+
+	/* clear BSS */
+	MOV	$edata-KZERO(SB), R1
+	MOV	$end-KZERO(SB), R2
+_zerobss:
+	MOV	ZR, (R1)8!
+	CMP	R1, R2
+	BNE	_zerobss
+
+	/* setup page tables */
+	MOV	$(L1BOT-KZERO), R0
+	BL	mmuidmap(SB)
+
+	MOV	$(L1-KZERO), R0
+	BL	mmu0init(SB)
+
+	SEVL
+_startup:
+	WFE
+	BL	mmuenable<>(SB)
+
+	MOV	R26, R0
+	MOV	$0, R26
+	ORR	$KZERO, R27
+	MSR	R27, TPIDR_EL1
+	MOV	$setSB(SB), R28
+
+	BL	main(SB)
+
+TEXT	stop<>(SB), 1, $-4
+_stop:
+	WFE
+	B	_stop
+
+TEXT	aaa<>(SB), 1, $-4
+xxx:
+	MOV $(0x860040+VIRTIO), R1
+	MOVW $'A', R2
+	MOVW R2, (R1)
+	B xxx
+
+TEXT sev(SB), 1, $-4
+	SEV
+	WFE
+	RETURN
+
+TEXT svcmode<>(SB), 1, $-4
+	MSR	$0xF, DAIFSet
+	MRS	CurrentEL, R0
+	ANDW	$(3<<2), R0
+	CMPW	$(1<<2), R0
+	BEQ	el1
+	CMPW	$(2<<2), R0
+	BEQ	el2
+	B	stop<>(SB)
+el2:
+	MOV	$0, R0
+	MSR	R0, MDCR_EL2
+	ISB	$SY
+
+	/* set virtual timer offset to zero */
+	MOV	$0, R0
+	MSR	R0, CNTVOFF_EL2
+
+	/* HCR = RW, HCD, SWIO, BSU, FB */
+	MOVWU	$(1<<31 | 1<<29 | 1<<2 | 0<<10 | 0<<9), R0
+	MSR	R0, HCR_EL2
+	ISB	$SY
+
+	/* SCTLR = RES1 */
+	MOVWU	$(3<<4 | 1<<11 | 1<<16 | 1<<18 | 3<<22 | 3<<28), R0
+	ISB	$SY
+	MSR	R0, SCTLR_EL2
+	ISB	$SY
+
+	/* set VMID to zero */
+	MOV	$0, R0
+	MSR	R0, VTTBR_EL2
+	ISB	$SY
+
+	MOVWU	$(0xF<<6 | 4), R0
+	MSR	R0, SPSR_EL2
+	MSR	LR, ELR_EL2
+	ERET
+el1:
+	RETURN
+
+TEXT mmudisable<>(SB), 1, $-4
+#define SCTLRCLR \
+	/* RES0 */	( 3<<30 \
+	/* RES0 */	| 1<<27 \
+	/* UCI */	| 1<<26 \
+	/* EE */	| 1<<25 \
+	/* RES0 */	| 1<<21 \
+	/* E0E */	| 1<<24 \
+	/* WXN */	| 1<<19 \
+	/* nTWE */	| 1<<18 \
+	/* RES0 */	| 1<<17 \
+	/* nTWI */	| 1<<16 \
+	/* UCT */	| 1<<15 \
+	/* DZE */	| 1<<14 \
+	/* RES0 */	| 1<<13 \
+	/* RES0 */	| 1<<10 \
+	/* UMA */	| 1<<9 \
+	/* SA0 */	| 1<<4 \
+	/* SA */	| 1<<3 \
+	/* A */		| 1<<1 )
+#define SCTLRSET \
+	/* RES1 */	( 3<<28 \
+	/* RES1 */	| 3<<22 \
+	/* RES1 */	| 1<<20 \
+	/* RES1 */	| 1<<11 )
+#define SCTLRMMU \
+	/* I */		( 1<<12 \
+	/* C */		| 1<<2 \
+	/* M */		| 1<<0 )
+
+	/* initialise SCTLR, MMU and caches off */
+	ISB	$SY
+	MRS	SCTLR_EL1, R0
+	BIC	$(SCTLRCLR | SCTLRMMU), R0
+	ORR	$SCTLRSET, R0
+	ISB	$SY
+	MSR	R0, SCTLR_EL1
+	ISB	$SY
+
+	B	flushlocaltlb(SB)
+
+TEXT mmuenable<>(SB), 1, $-4
+	/* return to virtual */
+	ORR	$KZERO, LR
+	MOV	LR, -16(RSP)!
+
+	BL	flushlocaltlb(SB)
+
+	/* memory attributes */
+#define MAIRINIT \
+	( 0xFF << MA_MEM_WB*8 \
+	| 0x33 << MA_MEM_WT*8 \
+	| 0x44 << MA_MEM_UC*8 \
+	| 0x00 << MA_DEV_nGnRnE*8 \
+	| 0x04 << MA_DEV_nGnRE*8 \
+	| 0x08 << MA_DEV_nGRE*8 \
+	| 0x0C << MA_DEV_GRE*8 )
+	MOV	$MAIRINIT, R1
+	MSR	R1, MAIR_EL1
+	ISB	$SY
+
+	/* translation control */
+#define TCRINIT \
+	/* TBI1 */	( 0<<38 \
+	/* TBI0 */	| 0<<37 \
+	/* AS */	| 0<<36 \
+	/* TG1 */	| (((3<<16|1<<14|2<<12)>>PGSHIFT)&3)<<30 \
+	/* SH1 */	| SHARE_INNER<<28 \
+	/* ORGN1 */	| CACHE_WB<<26 \
+	/* IRGN1 */	| CACHE_WB<<24 \
+	/* EPD1 */	| 0<<23 \
+	/* A1 */	| 0<<22 \
+	/* T1SZ */	| (64-EVASHIFT)<<16 \
+	/* TG0 */	| (((1<<16|2<<14|0<<12)>>PGSHIFT)&3)<<14 \
+	/* SH0 */	| SHARE_INNER<<12 \
+	/* ORGN0 */	| CACHE_WB<<10 \
+	/* IRGN0 */	| CACHE_WB<<8 \
+	/* EPD0 */	| 0<<7 \
+	/* T0SZ */	| (64-EVASHIFT)<<0 )
+	MOV	$TCRINIT, R1
+	MRS	ID_AA64MMFR0_EL1, R2
+	ANDW	$0x7, R2	// PARange
+	ADD	R2<<32, R1	// IPS
+	MSR	R1, TCR_EL1
+	ISB	$SY
+
+	/* load the page tables */
+	MOV	$(L1BOT-KZERO), R0
+	MOV	$(L1TOP-KZERO), R1
+	ISB	$SY
+	MSR	R0, TTBR0_EL1
+	MSR	R1, TTBR1_EL1
+	ISB	$SY
+
+	/* enable MMU and caches */
+	MRS	SCTLR_EL1, R1
+	ORR	$SCTLRMMU, R1
+	ISB	$SY
+	MSR	R1, SCTLR_EL1
+	ISB	$SY
+
+	MOV	RSP, R1
+	ORR	$KZERO, R1
+	MOV	R1, RSP
+	MOV	(RSP)16!, LR
+	B	cacheiinv(SB)
+
+TEXT touser(SB), 1, $-4
+	MOVWU	$0x10028, R1	// entry
+	MOVWU	$0, R2		// psr
+	MSR	R0, SP_EL0	// sp
+	MSR	R1, ELR_EL1
+	MSR	R2, SPSR_EL1
+	ERET
+
+TEXT cas(SB), 1, $-4
+TEXT cmpswap(SB), 1, $-4
+	MOVWU	ov+8(FP), R1
+	MOVWU	nv+16(FP), R2
+_cas1:
+	LDXRW	(R0), R3
+	CMP	R3, R1
+	BNE	_cas0
+	STXRW	R2, (R0), R4
+	CBNZ	R4, _cas1
+	MOVW	$1, R0
+	DMB	$ISH
+	RETURN
+_cas0:
+	CLREX
+	MOVW	$0, R0
+	RETURN
+
+TEXT tas(SB), 1, $-4
+TEXT _tas(SB), 1, $-4
+	MOVW	$0xdeaddead, R2
+_tas1:
+	LDXRW	(R0), R1
+	STXRW	R2, (R0), R3
+	CBNZ	R3, _tas1
+	MOVW	R1, R0
+
+TEXT coherence(SB), 1, $-4
+	DMB	$ISH
+	RETURN
+
+TEXT islo(SB), 1, $-4
+	MRS	DAIF, R0
+	AND	$(0x2<<6), R0
+	EOR	$(0x2<<6), R0
+	RETURN
+
+TEXT splhi(SB), 1, $-4
+	MRS	DAIF, R0
+	MSR	$0x2, DAIFSet
+	RETURN
+
+TEXT splfhi(SB), 1, $-4
+	MRS	DAIF, R0
+	MSR	$0x3, DAIFSet
+	RETURN
+
+TEXT spllo(SB), 1, $-4
+	MSR	$0x3, DAIFClr
+	RETURN
+
+TEXT splflo(SB), 1, $-4
+	MSR	$0x1, DAIFClr
+	RETURN
+
+TEXT splx(SB), 1, $-4
+	MSR	R0, DAIF
+	RETURN
+
+TEXT idlehands(SB), 1, $-4
+	DMB	$ISH
+	MOVW	nrdy(SB), R0
+	CBNZ	R0, _ready
+	WFI
+_ready:
+	RETURN
+
+TEXT vcycles(SB), 1, $-4
+	MRS	CNTVCT_EL0, R0
+	RETURN
+
+TEXT lcycles(SB), 1, $-4
+	MRS	PMCCNTR_EL0, R0
+	RETURN
+
+TEXT setlabel(SB), 1, $-4
+	MOV	LR, 8(R0)
+	MOV	SP, R1
+	MOV	R1, 0(R0)
+	MOVW	$0, R0
+	RETURN
+
+TEXT gotolabel(SB), 1, $-4
+	MOV	8(R0), LR	/* link */
+	MOV	0(R0), R1	/* sp */
+	MOV	R1, SP
+	MOVW	$1, R0
+	RETURN
+
+TEXT returnto(SB), 1, $-4
+	MOV	R0, 0(SP)
+	RETURN
+
+TEXT getfar(SB), 1, $-4
+	MRS	FAR_EL1, R0
+	RETURN
+
+TEXT setttbr(SB), 1, $-4
+	DSB	$ISHST
+	MSR	R0, TTBR0_EL1
+	DSB	$ISH
+	ISB	$SY
+	RETURN
+
+/*
+ * TLB maintenance operations.
+ * these broadcast to all cpu's in the cluser
+ * (inner sharable domain).
+ */
+TEXT flushasidva(SB), 1, $-4
+TEXT tlbivae1is(SB), 1, $-4
+	DSB	$ISHST
+	TLBI	R0, 0,8,3,1	/* VAE1IS */
+	DSB	$ISH
+	ISB	$SY
+	RETURN
+
+TEXT flushasidvall(SB), 1, $-4
+TEXT tlbivale1is(SB), 1, $-4
+	DSB	$ISHST
+	TLBI	R0, 0,8,3,5	/* VALE1IS */
+	DSB	$ISH
+	ISB	$SY
+	RETURN
+
+TEXT flushasid(SB), 1, $-4
+TEXT tlbiaside1is(SB), 1, $-4
+	DSB	$ISHST
+	TLBI	R0, 0,8,3,2	/* ASIDE1IS */
+	DSB	$ISH
+	ISB	$SY
+	RETURN
+
+TEXT flushtlb(SB), 1, $-4
+TEXT tlbivmalle1is(SB), 1, $-4
+	DSB	$ISHST
+	TLBI	R0, 0,8,3,0	/* VMALLE1IS */
+	DSB	$ISH
+	ISB	$SY
+	RETURN
+
+/*
+ * flush the tlb of this cpu. no broadcast.
+ */
+TEXT flushlocaltlb(SB), 1, $-4
+TEXT tlbivmalle1(SB), 1, $-4
+	DSB	$NSHST
+	TLBI	R0, 0,8,7,0	/* VMALLE1 */
+	DSB	$NSH
+	ISB	$SY
+	RETURN
+
+/*
+ * floating-point support.
+ */
+TEXT fpon(SB), 1, $-4
+	MOVW $(3<<20), R0
+	MSR R0, CPACR_EL1
+	ISB $SY
+	RETURN
+
+TEXT fpoff(SB), 1, $-4
+	MOVW $(0<<20), R0
+	MSR R0, CPACR_EL1
+	ISB $SY
+	RETURN
+
+TEXT fpsaveregs(SB), 1, $-4
+	WORD	$(1<<30 | 3 << 26 | 2<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 0)  /* MOV { V0, V1, V2, V3  }, (R0)64! */
+	WORD	$(1<<30 | 3 << 26 | 2<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 4)  /* MOV { V4, V5, V6, V7  }, (R0)64! */
+	WORD	$(1<<30 | 3 << 26 | 2<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 8)  /* MOV { V8, V9, V10,V11 }, (R0)64! */
+	WORD	$(1<<30 | 3 << 26 | 2<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 12) /* MOV { V12,V13,V14,V15 }, (R0)64! */
+	WORD	$(1<<30 | 3 << 26 | 2<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 16) /* MOV { V16,V17,V18,V19 }, (R0)64! */
+	WORD	$(1<<30 | 3 << 26 | 2<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 20) /* MOV { V20,V21,V22,V23 }, (R0)64! */
+	WORD	$(1<<30 | 3 << 26 | 2<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 24) /* MOV { V24,V25,V26,V27 }, (R0)64! */
+	WORD	$(1<<30 | 3 << 26 | 2<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 28) /* MOV { V28,V29,V30,V31 }, (R0)64! */
+	RETURN
+
+TEXT fploadregs(SB), 1, $-4
+	WORD	$(1<<30 | 3 << 26 | 3<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 0)  /* MOV (R0)64!, { V0, V1, V2, V3  } */
+	WORD	$(1<<30 | 3 << 26 | 3<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 4)  /* MOV (R0)64!, { V4, V5, V6, V7  } */
+	WORD	$(1<<30 | 3 << 26 | 3<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 8)  /* MOV (R0)64!, { V8, V9, V10,V11 } */
+	WORD	$(1<<30 | 3 << 26 | 3<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 12) /* MOV (R0)64!, { V12,V13,V14,V15 } */
+	WORD	$(1<<30 | 3 << 26 | 3<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 16) /* MOV (R0)64!, { V16,V17,V18,V19 } */
+	WORD	$(1<<30 | 3 << 26 | 3<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 20) /* MOV (R0)64!, { V20,V21,V22,V23 } */
+	WORD	$(1<<30 | 3 << 26 | 3<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 24) /* MOV (R0)64!, { V24,V25,V26,V27 } */
+	WORD	$(1<<30 | 3 << 26 | 3<<22 | 0x1F<<16 | 3<<10 | 0<<5 | 28) /* MOV (R0)64!, { V28,V29,V30,V31 } */
+	RETURN
+
+// syscall or trap from EL0
+TEXT vsys0(SB), 1, $-4
+	LSRW	$26, R0, R17	// ec
+	CMPW	$0x15, R17	// SVC trap?
+	BNE	_itsatrap	// nope.
+
+	MOVP	R26, R27, 224(RSP)
+	MOVP	R28, R29, 240(RSP)
+
+	MRS	SP_EL0, R1
+	MRS	ELR_EL1, R2
+	MRS	SPSR_EL1, R3
+
+	MOV	R0, 288(RSP)	// type
+	MOV	R1, 264(RSP)	// sp
+	MOV	R2, 272(RSP)	// pc
+	MOV	R3, 280(RSP)	// psr
+
+	MOV	$setSB(SB), R28
+	MRS	TPIDR_EL1, R27
+	MOV	16(R27), R26
+
+	ADD	$16, RSP, R0	// ureg
+	BL	syscall(SB)
+
+TEXT forkret(SB), 1, $-4
+	MSR	$0x3, DAIFSet	// interrupts off
+
+	ADD	$16, RSP, R0	// ureg
+
+	MOV	16(RSP), R0	// ret
+	MOV	264(RSP), R1	// sp
+	MOV	272(RSP), R2	// pc
+	MOV	280(RSP), R3	// psr
+
+	MSR	R1, SP_EL0
+	MSR	R2, ELR_EL1
+	MSR	R3, SPSR_EL1
+
+	MOVP	224(RSP), R26, R27
+	MOVP	240(RSP), R28, R29
+
+	MOV	256(RSP), R30	// link
+
+	ADD	$TRAPFRAMESIZE, RSP
+	ERET
+
+TEXT itsatrap<>(SB), 1, $-4
+_itsatrap:
+	MOVP	R1, R2, 24(RSP)
+	MOVP	R3, R4, 40(RSP)
+	MOVP	R5, R6, 56(RSP)
+	MOVP	R7, R8, 72(RSP)
+	MOVP	R9, R10, 88(RSP)
+	MOVP	R11, R12, 104(RSP)
+	MOVP	R13, R14, 120(RSP)
+	MOVP	R15, R16, 136(RSP)
+
+	MOVP	R18, R19, 160(RSP)
+	MOVP	R20, R21, 176(RSP)
+	MOVP	R22, R23, 192(RSP)
+	MOVP	R24, R25, 208(RSP)
+
+// trap/irq/fiq/serr from EL0
+TEXT vtrap0(SB), 1, $-4
+	MOVP	R26, R27, 224(RSP)
+	MOVP	R28, R29, 240(RSP)
+
+	MRS	SP_EL0, R1
+	MRS	ELR_EL1, R2
+	MRS	SPSR_EL1, R3
+
+	MOV	R0, 288(RSP)	// type
+	MOV	R1, 264(RSP)	// sp
+	MOV	R2, 272(RSP)	// pc
+	MOV	R3, 280(RSP)	// psr
+
+	MOV	$setSB(SB), R28
+	MRS	TPIDR_EL1, R27
+	MOV	16(R27), R26
+
+	ADD	$16, RSP, R0	// ureg
+	BL	trap(SB)
+
+TEXT noteret(SB), 1, $-4
+	MSR	$0x3, DAIFSet	// interrupts off
+
+	ADD	$16, RSP, R0	// ureg
+
+	MOV	264(RSP), R1	// sp
+	MOV	272(RSP), R2	// pc
+	MOV	280(RSP), R3	// psr
+
+	MSR	R1, SP_EL0
+	MSR	R2, ELR_EL1
+	MSR	R3, SPSR_EL1
+
+	MOVP	224(RSP), R26, R27
+	MOVP	240(RSP), R28, R29
+
+_intrreturn:
+	MOVP	16(RSP), R0, R1
+	MOVP	32(RSP), R2, R3
+	MOVP	48(RSP), R4, R5
+	MOVP	64(RSP), R6, R7
+	MOVP	80(RSP), R8, R9
+	MOVP	96(RSP), R10, R11
+	MOVP	112(RSP), R12, R13
+	MOVP	128(RSP), R14, R15
+	MOVP	144(RSP), R16, R17
+	MOVP	160(RSP), R18, R19
+	MOVP	176(RSP), R20, R21
+	MOVP	192(RSP), R22, R23
+	MOVP	208(RSP), R24, R25
+
+	MOV	256(RSP), R30	// link
+
+	ADD	$TRAPFRAMESIZE, RSP
+	ERET
+
+// irq/fiq/trap/serr from EL1
+TEXT vtrap1(SB), 1, $-4
+	MOV	R29, 248(RSP)	// special
+
+	ADD	$TRAPFRAMESIZE, RSP, R1
+	MRS	ELR_EL1, R2
+	MRS	SPSR_EL1, R3
+
+	MOV	R0, 288(RSP)	// type
+	MOV	R1, 264(RSP)	// sp
+	MOV	R2, 272(RSP)	// pc
+	MOV	R3, 280(RSP)	// psr
+
+	ADD	$16, RSP, R0	// ureg
+	BL	trap(SB)
+
+	MSR	$0x3, DAIFSet	// interrupts off
+
+	MOV	272(RSP), R2	// pc
+	MOV	280(RSP), R3	// psr
+
+	MSR	R2, ELR_EL1
+	MSR	R3, SPSR_EL1
+
+	MOV	248(RSP), R29	// special
+	B	_intrreturn	
+
+// vector tables
+TEXT vsys(SB), 1, $-4
+	SUB	$TRAPFRAMESIZE, RSP
+
+	MOV	R0, 16(RSP)
+	MOV	R30, 256(RSP)	// link
+
+	MOV	R17, 152(RSP)	// temp
+
+	MRS	ESR_EL1, R0	// type
+
+_vsyspatch:
+	B	_vsyspatch	// branch to vsys0() patched in
+
+TEXT vtrap(SB), 1, $-4
+	SUB	$TRAPFRAMESIZE, RSP
+
+	MOVP	R0, R1, 16(RSP)
+	MOVP	R2, R3, 32(RSP)
+	MOVP	R4, R5, 48(RSP)
+	MOVP	R6, R7, 64(RSP)
+	MOVP	R8, R9, 80(RSP)
+	MOVP	R10, R11, 96(RSP)
+	MOVP	R12, R13, 112(RSP)
+	MOVP	R14, R15, 128(RSP)
+	MOVP	R16, R17, 144(RSP)
+	MOVP	R18, R19, 160(RSP)
+	MOVP	R20, R21, 176(RSP)
+	MOVP	R22, R23, 192(RSP)
+	MOVP	R24, R25, 208(RSP)
+
+	MOV	R30, 256(RSP)	// link
+
+	MRS	ESR_EL1, R0	// type
+
+_vtrappatch:
+	B	_vtrappatch	// branch to vtrapX() patched in
+
+TEXT virq(SB), 1, $-4
+	SUB	$TRAPFRAMESIZE, RSP
+
+	MOVP	R0, R1, 16(RSP)
+	MOVP	R2, R3, 32(RSP)
+	MOVP	R4, R5, 48(RSP)
+	MOVP	R6, R7, 64(RSP)
+	MOVP	R8, R9, 80(RSP)
+	MOVP	R10, R11, 96(RSP)
+	MOVP	R12, R13, 112(RSP)
+	MOVP	R14, R15, 128(RSP)
+	MOVP	R16, R17, 144(RSP)
+	MOVP	R18, R19, 160(RSP)
+	MOVP	R20, R21, 176(RSP)
+	MOVP	R22, R23, 192(RSP)
+	MOVP	R24, R25, 208(RSP)
+
+	MOV	R30, 256(RSP)	// link
+
+	MOV	$(1<<32), R0	// type irq
+
+_virqpatch:
+	B	_virqpatch	// branch to vtrapX() patched in
+
+TEXT vfiq(SB), 1, $-4
+	SUB	$TRAPFRAMESIZE, RSP
+
+	MOVP	R0, R1, 16(RSP)
+	MOVP	R2, R3, 32(RSP)
+	MOVP	R4, R5, 48(RSP)
+	MOVP	R6, R7, 64(RSP)
+	MOVP	R8, R9, 80(RSP)
+	MOVP	R10, R11, 96(RSP)
+	MOVP	R12, R13, 112(RSP)
+	MOVP	R14, R15, 128(RSP)
+	MOVP	R16, R17, 144(RSP)
+	MOVP	R18, R19, 160(RSP)
+	MOVP	R20, R21, 176(RSP)
+	MOVP	R22, R23, 192(RSP)
+	MOVP	R24, R25, 208(RSP)
+
+	MOV	R30, 256(RSP)	// link
+	MOV	$(2<<32), R0	// type fiq
+
+_vfiqpatch:
+	B	_vfiqpatch	// branch to vtrapX() patched in
+
+TEXT vserr(SB), 1, $-4
+	SUB	$TRAPFRAMESIZE, RSP
+
+	MOVP	R0, R1, 16(RSP)
+	MOVP	R2, R3, 32(RSP)
+	MOVP	R4, R5, 48(RSP)
+	MOVP	R6, R7, 64(RSP)
+	MOVP	R8, R9, 80(RSP)
+	MOVP	R10, R11, 96(RSP)
+	MOVP	R12, R13, 112(RSP)
+	MOVP	R14, R15, 128(RSP)
+	MOVP	R16, R17, 144(RSP)
+	MOVP	R18, R19, 160(RSP)
+	MOVP	R20, R21, 176(RSP)
+	MOVP	R22, R23, 192(RSP)
+	MOVP	R24, R25, 208(RSP)
+
+	MOV	R30, 256(RSP)	// link
+
+	MRS	ESR_EL1, R0
+	ORR	$(3<<32), R0	// type
+_vserrpatch:
+	B	_vserrpatch	// branch to vtrapX() patched in
+
+/* fault-proof memcpy */
+TEXT peek(SB), 1, $-4
+	MOV	R0, R1
+	MOV	dst+8(FP), R2
+	MOVWU	len+16(FP), R0
+TEXT _peekinst(SB), 1, $-4
+_peekloop:
+	MOVBU	(R1)1!, R3
+	MOVBU	R3, (R2)1!
+	SUBS	$1, R0
+	BNE	_peekloop
+	RETURN
+
+TEXT hvccall(SB), 1, $32
+	/* save extern registers */
+	MOVP	R26, R27, (RSP)
+
+	/* R0 = Ureg */
+	MOV	R0, R8
+
+	/* save ureg pointer */
+	MOV	R8, 16(RSP)
+
+	MOVP	0(R8), R0, R1
+	MOVP	16(R8), R2, R3
+	MOVP	32(R8), R4, R5
+	MOVP	48(R8), R6, R7
+
+	HVC
+
+	/* restore ureg pointer */
+	MOV	16(RSP), R8
+
+	MOVP	R0, R1, 0(R8)
+	MOVP	R2, R3, 16(R8)
+
+	/* restore extern registers */
+	MOVP	(RSP), R26, R27
+
+	RETURN
+
+
+	
--- /dev/null
+++ b/sys/src/9/arm64/main.c
@@ -1,0 +1,412 @@
+#include "u.h"
+#include "tos.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "pool.h"
+#include "io.h"
+#include "sysreg.h"
+#include "ureg.h"
+
+#include "rebootcode.i"
+
+Conf conf;
+
+#define	MAXCONF 64
+static char *confname[MAXCONF];
+static char *confval[MAXCONF];
+static int nconf = -1;
+
+void
+bootargsinit(void)
+{
+	int i, j, n;
+	char *cp, *line[MAXCONF], *p, *q;
+
+	/*
+	 *  parse configuration args from dos file plan9.ini
+	 */
+	cp = BOOTARGS;
+	cp[BOOTARGSLEN-1] = 0;
+
+	/*
+	 * Strip out '\r', change '\t' -> ' '.
+	 */
+	p = cp;
+	for(q = cp; *q; q++){
+		if(*q == -1)
+			break;
+		if(*q == '\r')
+			continue;
+		if(*q == '\t')
+			*q = ' ';
+		*p++ = *q;
+	}
+	*p = 0;
+
+	n = getfields(cp, line, MAXCONF, 1, "\n");
+	if(n <= 0){
+		/* empty plan9.ini, no configuration passed */
+		return;
+	}
+
+	nconf = 0;
+	for(i = 0; i < n; i++){
+		if(*line[i] == '#')
+			continue;
+		cp = strchr(line[i], '=');
+		if(cp == nil)
+			continue;
+		*cp++ = '\0';
+		for(j = 0; j < nconf; j++){
+			if(cistrcmp(confname[j], line[i]) == 0)
+				break;
+		}
+		confname[j] = line[i];
+		confval[j] = cp;
+		if(j == nconf)
+			nconf++;
+	}
+}
+
+char*
+getconf(char *name)
+{
+	int i;
+
+	for(i = 0; i < nconf; i++)
+		if(cistrcmp(confname[i], name) == 0)
+			return confval[i];
+	return nil;
+}
+
+void
+setconfenv(void)
+{
+	int i;
+
+	if(nconf < 0){
+		/* use defaults when there was no configuration */
+		ksetenv("console", "0", 1);
+		return;
+	}
+
+	for(i = 0; i < nconf; i++){
+		if(confname[i][0] != '*')
+			ksetenv(confname[i], confval[i], 0);
+		ksetenv(confname[i], confval[i], 1);
+	}
+}
+
+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;
+	if(n >= BOOTARGSLEN)
+		error("kernel configuration too large");
+	memmove(BOOTARGS, p, n);
+	memset(BOOTARGS+n, 0, BOOTARGSLEN-n);
+	poperror();
+	free(p);
+}
+
+int
+isaconfig(char *, int, ISAConf *)
+{
+	return 0;
+}
+
+/*
+ *  starting place for first process
+ */
+void
+init0(void)
+{
+	char buf[2*KNAMELEN], **sp;
+
+	chandevinit();
+
+	if(!waserror()){
+		snprint(buf, sizeof(buf), "%s %s", "ARM64", conffile);
+		ksetenv("terminal", buf, 0);
+		ksetenv("cputype", "arm64", 0);
+		if(cpuserver)
+			ksetenv("service", "cpu", 0);
+		else
+			ksetenv("service", "terminal", 0);
+		setconfenv();
+		poperror();
+	}
+	kproc("alarm", alarmkproc, 0);
+
+	sp = (char**)(USTKTOP-sizeof(Tos) - 8 - sizeof(sp[0])*4);
+	sp[3] = sp[2] = sp[1] = nil;
+	strcpy(sp[1] = (char*)&sp[4], "boot");
+	sp[0] = (void*)&sp[1];
+
+	splhi();
+	fpukexit(nil, nil);
+	touser((uintptr)sp);
+}
+
+void
+confinit(void)
+{
+	int userpcnt;
+	ulong kpages;
+	char *p;
+	int i;
+
+	conf.nmach = 1;
+	if(p = getconf("*ncpu"))
+		conf.nmach = strtol(p, 0, 0);
+	if(conf.nmach > MAXMACH)
+		conf.nmach = MAXMACH;
+
+	if(p = getconf("service")){
+		if(strcmp(p, "cpu") == 0)
+			cpuserver = 1;
+		else if(strcmp(p,"terminal") == 0)
+			cpuserver = 0;
+	}
+
+	if(p = getconf("*kernelpercent"))
+		userpcnt = 100 - strtol(p, 0, 0);
+	else
+		userpcnt = 0;
+
+	if(userpcnt < 10)
+		userpcnt = 60 + cpuserver*10;
+
+	conf.npage = 0;
+	for(i = 0; i < nelem(conf.mem); i++)
+		conf.npage += conf.mem[i].npage;
+
+	kpages = conf.npage - (conf.npage*userpcnt)/100;
+	if(kpages > ((uintptr)-VDRAM)/BY2PG)
+		kpages = ((uintptr)-VDRAM)/BY2PG;
+
+	conf.upages = conf.npage - kpages;
+	conf.ialloc = (kpages/2)*BY2PG;
+
+	/* set up other configuration parameters */
+	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 = conf.nmach > 1;
+
+	/*
+	 * Guess how much is taken by the large permanent
+	 * datastructures. Mntcache and Mntrpc are not accounted for.
+	 */
+	kpages = conf.npage - conf.upages;
+	kpages *= BY2PG;
+	kpages -= conf.upages*sizeof(Page)
+		+ conf.nproc*sizeof(Proc*)
+		+ conf.nimage*sizeof(Image)
+		+ conf.nswap
+		+ conf.nswppo*sizeof(Page*);
+	mainmem->maxsize = kpages;
+	imagmem->maxsize = kpages;
+}
+
+void
+machinit(void)
+{
+	m->ticks = 1;
+	m->perf.period = 1;
+	active.machs[m->machno] = 1;
+}
+
+void
+mpinit(void)
+{
+	extern void _start(void);
+	int i;
+
+	for(i = 1; i < conf.nmach; i++){
+		Ureg u = {0};
+
+		MACHP(i)->machno = i;
+		cachedwbinvse(MACHP(i), MACHSIZE);
+
+		u.r0 = 0x84000003;	/* CPU_ON */
+		u.r1 = (sysrd(MPIDR_EL1) & ~(0xFF0000FFULL)) | i;
+		u.r2 = PADDR(_start);
+		u.r3 = i;
+		hvccall(&u);
+	}
+	synccycles();
+}
+
+void
+cpuidprint(void)
+{
+	iprint("cpu%d: 1000MHz QEMU\n", m->machno);
+}
+
+void
+main(void)
+{
+	machinit();
+	if(m->machno){
+		trapinit();
+		fpuinit();
+		intrinit();
+		clockinit();
+		cpuidprint();
+		synccycles();
+		timersinit();
+		mmu1init();
+		m->ticks = MACHP(0)->ticks;
+		schedinit();
+		return;
+	}
+	quotefmtinstall();
+	bootargsinit();
+	meminit();
+	confinit();
+	xinit();
+	uartconsinit();
+	printinit();
+	print("\nPlan 9\n");
+	trapinit();
+	fpuinit();
+	intrinit();
+	clockinit();
+	cpuidprint();
+	timersinit();
+	pageinit();
+	procinit0();
+	initseg();
+	links();
+	chandevreset();
+	userinit();
+	mpinit();
+	mmu1init();
+	schedinit();
+}
+
+void
+exit(int)
+{
+	Ureg u = { .r0 = 0x84000002 };	/* CPU_OFF */
+
+	cpushutdown();
+	splfhi();
+
+	if(m->machno == 0){
+		/* clear secrets */
+		zeroprivatepages();
+		poolreset(secrmem);
+
+		u.r0 = 0x84000009;	/* SYSTEM RESET */
+	}
+	hvccall(&u);
+}
+
+static void
+rebootjump(void *entry, void *code, ulong size)
+{
+	void (*f)(void*, void*, ulong);
+
+	intrcpushutdown();
+
+	/* redo identity map */
+	setttbr(PADDR(L1BOT));
+
+	/* setup reboot trampoline function */
+	f = (void*)REBOOTADDR;
+	memmove(f, rebootcode, sizeof(rebootcode));
+
+	cachedwbinvse(f, sizeof(rebootcode));
+	cacheiinvse(f, sizeof(rebootcode));
+
+	(*f)(entry, code, size);
+
+	for(;;);
+}
+
+void
+reboot(void*, void *code, ulong size)
+{
+	writeconf();
+	while(m->machno != 0){
+		procwired(up, 0);
+		sched();
+	}
+
+	cpushutdown();
+	delay(2000);
+
+	splfhi();
+
+	/* turn off buffered serial console */
+	serialoq = nil;
+
+	/* shutdown devices */
+	chandevshutdown();
+
+	/* stop the clock */
+	clockshutdown();
+	intrsoff();
+
+	/* clear secrets */
+	zeroprivatepages();
+	poolreset(secrmem);
+
+	/* off we go - never to return */
+	rebootjump((void*)(KTZERO-KZERO), code, size);
+}
+
+void
+dmaflush(int clean, void *p, ulong len)
+{
+	uintptr s = (uintptr)p;
+	uintptr e = (uintptr)p + len;
+
+	if(clean){
+		s &= ~(BLOCKALIGN-1);
+		e += BLOCKALIGN-1;
+		e &= ~(BLOCKALIGN-1);
+		cachedwbse((void*)s, e - s);
+		return;
+	}
+	if(s & BLOCKALIGN-1){
+		s &= ~(BLOCKALIGN-1);
+		cachedwbinvse((void*)s, BLOCKALIGN);
+		s += BLOCKALIGN;
+	}
+	if(e & BLOCKALIGN-1){
+		e &= ~(BLOCKALIGN-1);
+		if(e < s)
+			return;
+		cachedwbinvse((void*)e, BLOCKALIGN);
+	}
+	if(s < e)
+		cachedinvse((void*)s, e - s);
+}
--- /dev/null
+++ b/sys/src/9/arm64/mem.h
@@ -1,0 +1,147 @@
+/*
+ * Memory and machine-specific definitions.  Used in C and assembler.
+ */
+#define KiB		1024u			/* Kibi 0x0000000000000400 */
+#define MiB		1048576u		/* Mebi 0x0000000000100000 */
+#define GiB		1073741824u		/* Gibi 000000000040000000 */
+
+/*
+ * Sizes:
+ * 	L0	L1	L2	L3
+ *	4K	2M	1G	512G
+ *	16K	32M	64G	128T
+ *	64K	512M	4T	-
+ */
+#define	PGSHIFT		12		/* log(BY2PG) */
+#define	BY2PG		(1ULL<<PGSHIFT)	/* bytes per page */
+#define	ROUND(s, sz)	(((s)+(sz-1))&~(sz-1))
+#define	PGROUND(s)	ROUND(s, BY2PG)
+
+/* effective virtual address space */
+#define EVASHIFT	34
+#define EVAMASK		((1ULL<<EVASHIFT)-1)
+
+#define PTSHIFT		(PGSHIFT-3)
+#define PTLEVELS	(((EVASHIFT-PGSHIFT)+PTSHIFT-1)/PTSHIFT)	
+#define PTLX(v, l)	((((v) & EVAMASK) >> (PGSHIFT + (l)*PTSHIFT)) & ((1 << PTSHIFT)-1))
+#define PGLSZ(l)	(1ULL << (PGSHIFT + (l)*PTSHIFT))
+
+#define PTL1X(v, l)	(L1TABLEX(v, l) | PTLX(v, l))
+#define L1TABLEX(v, l)	(L1TABLE(v, l) << PTSHIFT)
+#define L1TABLES	((-KSEG0+PGLSZ(2)-1)/PGLSZ(2))
+#define L1TABLE(v, l)	(L1TABLES - ((PTLX(v, 2) % L1TABLES) >> (((l)-1)*PTSHIFT)) + (l)-1)
+#define L1TOPSIZE	(1ULL << (EVASHIFT - PTLEVELS*PTSHIFT))
+
+#define	MAXMACH		24			/* max # cpus system can run */
+#define	MACHSIZE	(8*KiB)
+
+#define KSTACK		(8*KiB)
+#define STACKALIGN(sp)	((sp) & ~7)		/* bug: assure with alloc */
+#define TRAPFRAMESIZE	(38*8)
+
+#define VDRAM		(0xFFFFFFFFC0000000ULL)	/* 0x40000000 - 0x80000000 */
+#define	KTZERO		(VDRAM + 0x100000)	/* 0x40100000 - kernel text start */
+
+#define PHYSIO		0x8000000
+#define PHYSIOEND	0x10000000
+
+#define	VIRTIO		(0xFFFFFFFFB0000000ULL)
+
+#define	KZERO		(0xFFFFFFFF80000000ULL)	/* 0x00000000 - kernel address space */
+
+#define VMAP		(0xFFFFFFFF00000000ULL)	/* 0x00000000 - 0x40000000 */
+
+#define KMAPEND		(0xFFFFFFFF00000000ULL)	/* 0x140000000 */
+#define KMAP		(0xFFFFFFFE00000000ULL)	/*  0x40000000 */
+
+#define KSEG0		(0xFFFFFFFE00000000ULL)
+
+/* temporary identity map for TTBR0 (using only top-level) */
+#define L1BOT		((L1-L1TOPSIZE)&-BY2PG)
+
+/* shared kernel page table for TTBR1 */
+#define L1		(L1TOP-L1SIZE)
+#define L1SIZE		((L1TABLES+PTLEVELS-2)*BY2PG)
+#define L1TOP		((MACHADDR(MAXMACH-1)-L1TOPSIZE)&-BY2PG)
+
+#define MACHADDR(n)	(KTZERO-((n)+1)*MACHSIZE)
+
+#define CONFADDR	(VDRAM + 0x10000)	/* 0x40010000 */
+
+#define BOOTARGS	((char*)CONFADDR)
+#define BOOTARGSLEN	0x10000
+
+#define	REBOOTADDR	(VDRAM-KZERO + 0x20000)	/* 0x40020000 */
+
+#define	UZERO		0ULL			/* user segment */
+#define	UTZERO		(UZERO+0x10000)		/* user text start */
+#define	USTKTOP		((EVAMASK>>1)-0xFFFF)	/* user segment end +1 */
+#define	USTKSIZE	(16*1024*1024)		/* user stack size */
+
+#define BLOCKALIGN	64			/* only used in allocb.c */
+
+/*
+ * Sizes
+ */
+#define BI2BY		8			/* bits per byte */
+#define BY2SE		4
+#define BY2WD		8
+#define BY2V		8			/* only used in xalloc.c */
+
+#define	PTEMAPMEM	(1024*1024)
+#define	PTEPERTAB	(PTEMAPMEM/BY2PG)
+#define	SEGMAPSIZE	8192
+#define	SSEGMAPSIZE	16
+#define	PPN(x)		((x)&~(BY2PG-1))
+
+#define SHARE_NONE	0
+#define SHARE_OUTER	2
+#define SHARE_INNER	3
+
+#define CACHE_UC	0
+#define CACHE_WB	1
+#define CACHE_WT	2
+#define CACHE_WB_NA	3
+
+#define MA_MEM_WB	0
+#define MA_MEM_WT	1
+#define MA_MEM_UC	2
+#define MA_DEV_nGnRnE	3
+#define MA_DEV_nGnRE	4
+#define MA_DEV_nGRE	5
+#define MA_DEV_GRE	6
+
+#define	PTEVALID	1
+#define PTEBLOCK	0
+#define PTETABLE	2
+#define PTEPAGE		2
+
+#define PTEMA(x)	((x)<<2)
+#define PTEAP(x)	((x)<<6)
+#define PTESH(x)	((x)<<8)
+
+#define PTEAF		(1<<10)
+#define PTENG		(1<<11)
+#define PTEPXN		(1ULL<<53)
+#define PTEUXN		(1ULL<<54)
+
+#define PTEKERNEL	PTEAP(0)
+#define PTEUSER		PTEAP(1)
+#define PTEWRITE	PTEAP(0)
+#define PTERONLY	PTEAP(2)
+#define PTENOEXEC	(PTEPXN|PTEUXN)
+
+#define PTECACHED	PTEMA(MA_MEM_WB)
+#define PTEWT		PTEMA(MA_MEM_WT)
+#define PTEUNCACHED	PTEMA(MA_MEM_UC)
+#define PTEDEVICE	PTEMA(MA_DEV_nGnRE)
+
+/*
+ * Physical machine information from here on.
+ *	PHYS addresses as seen from the arm cpu.
+ *	BUS  addresses as seen from peripherals
+ */
+#define	PHYSDRAM	0
+
+#define MIN(a, b)	((a) < (b)? (a): (b))
+#define MAX(a, b)	((a) > (b)? (a): (b))
--- /dev/null
+++ b/sys/src/9/arm64/mkfile
@@ -1,0 +1,101 @@
+CONF=qemu
+CONFLIST=qemu
+
+kzero=0xffffffff80000000
+loadaddr=0xffffffffc0100000
+
+objtype=arm64
+</$objtype/mkfile
+p=9
+
+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\
+	page.$O\
+	parse.$O\
+	pgrp.$O\
+	portclock.$O\
+	print.$O\
+	proc.$O\
+	qio.$O\
+	qlock.$O\
+	rdb.$O\
+	rebootcmd.$O\
+	segment.$O\
+	syscallfmt.$O\
+	sysfile.$O\
+	sysproc.$O\
+	taslock.$O\
+	tod.$O\
+	xalloc.$O\
+	userinit.$O\
+
+OBJ=\
+	l.$O\
+	cache.v8.$O\
+	clock.$O\
+	fpu.$O\
+	main.$O\
+	mmu.$O\
+	sysreg.$O\
+	random.$O\
+	trap.$O\
+	$CONF.root.$O\
+	$CONF.rootc.$O\
+	$DEVS\
+	$PORT\
+
+# HFILES=
+
+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\
+
+9:V:	$p$CONF $p$CONF.u
+
+$p$CONF.u:D:	$p$CONF
+	aux/aout2uimage -Z$kzero $p$CONF
+
+$p$CONF:D:	$OBJ $CONF.$O $LIB
+	$LD -o $target -T$loadaddr -l $prereq
+	size $target
+
+$OBJ: $HFILES
+
+install:V: /$objtype/$p$CONF
+
+/$objtype/$p$CONF:D: $p$CONF $p$CONF.u
+	cp -x $p$CONF $p$CONF.u /$objtype/
+
+<../boot/bootmkfile
+<../port/portmkfile
+<|../port/mkbootrules $CONF
+
+main.$O: rebootcode.i
+
+pciqemu.$O: ../port/pci.h
+
+initcode.out:		init9.$O initcode.$O /$objtype/lib/libc.a
+	$LD -l -R1 -s -o $target $prereq
+
+rebootcode.out:		rebootcode.$O cache.v8.$O
+	$LD -l -H6 -R1 -T0x40020000 -s -o $target $prereq
+
+$CONF.clean:
+	rm -rf $p$CONF $p$CONF.u errstr.h $CONF.c boot$CONF.c
--- /dev/null
+++ b/sys/src/9/arm64/mmu.c
@@ -1,0 +1,450 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "sysreg.h"
+
+#define INITMAP	(ROUND((uintptr)end + BY2PG, PGLSZ(1))-KZERO)
+
+/*
+ * Create initial identity map in top-level page table
+ * (L1BOT) for TTBR0. This page table is only used until
+ * mmu1init() loads m->mmutop.
+ */
+void
+mmuidmap(uintptr *l1bot)
+{
+	uintptr pa, pe, attr;
+
+	/* VDRAM */
+	attr = PTEWRITE | PTEAF | PTEKERNEL | PTEUXN | PTESH(SHARE_INNER);
+	pe = -KZERO;
+	for(pa = VDRAM - KZERO; pa < pe; pa += PGLSZ(PTLEVELS-1))
+		l1bot[PTLX(pa, PTLEVELS-1)] = pa | PTEVALID | PTEBLOCK | attr;
+}
+
+/*
+ * Create initial shared kernel page table (L1) for TTBR1.
+ * This page table coveres the INITMAP and VIRTIO,
+ * and later we fill the ram mappings in meminit().
+ */
+void
+mmu0init(uintptr *l1)
+{
+	uintptr va, pa, pe, attr;
+
+	/* DRAM - INITMAP */
+	attr = PTEWRITE | PTEAF | PTEKERNEL | PTEUXN | PTESH(SHARE_INNER);
+	pe = INITMAP;
+	for(pa = VDRAM - KZERO, va = VDRAM; pa < pe; pa += PGLSZ(1), va += PGLSZ(1))
+		l1[PTL1X(va, 1)] = pa | PTEVALID | PTEBLOCK | attr;
+
+	/* VIRTIO */
+	attr = PTEWRITE | PTEAF | PTEKERNEL | PTEUXN | PTEPXN | PTESH(SHARE_OUTER) | PTEDEVICE;
+	pe = PHYSIOEND;
+	for(pa = PHYSIO, va = VIRTIO; pa < pe; pa += PGLSZ(1), va += PGLSZ(1)){
+		if(((pa|va) & PGLSZ(1)-1) != 0){
+			l1[PTL1X(va, 1)] = (uintptr)l1 | PTEVALID | PTETABLE;
+			for(; pa < pe && ((va|pa) & PGLSZ(1)-1) != 0; pa += PGLSZ(0), va += PGLSZ(0)){
+				assert(l1[PTLX(va, 0)] == 0);
+				l1[PTLX(va, 0)] = pa | PTEVALID | PTEPAGE | attr;
+			}
+			break;
+		}
+		l1[PTL1X(va, 1)] = pa | PTEVALID | PTEBLOCK | attr;
+	}
+
+	if(PTLEVELS > 2)
+	for(va = KSEG0; va != 0; va += PGLSZ(2))
+		l1[PTL1X(va, 2)] = (uintptr)&l1[L1TABLEX(va, 1)] | PTEVALID | PTETABLE;
+
+	if(PTLEVELS > 3)
+	for(va = KSEG0; va != 0; va += PGLSZ(3))
+		l1[PTL1X(va, 3)] = (uintptr)&l1[L1TABLEX(va, 2)] | PTEVALID | PTETABLE;
+}
+
+void
+mmu1init(void)
+{
+	m->mmutop = mallocalign(L1TOPSIZE, BY2PG, 0, 0);
+	if(m->mmutop == nil)
+		panic("mmu1init: no memory for mmutop");
+	memset(m->mmutop, 0, L1TOPSIZE);
+	mmuswitch(nil);
+}
+
+/* KZERO maps the first 1GB of ram */
+uintptr
+paddr(void *va)
+{
+	if((uintptr)va >= KZERO)
+		return (uintptr)va-KZERO;
+	panic("paddr: va=%#p pc=%#p", va, getcallerpc(&va));
+	return 0;
+}
+
+uintptr
+cankaddr(uintptr pa)
+{
+	if(pa < (uintptr)-KZERO)
+		return -KZERO - pa;
+	return 0;
+}
+
+void*
+kaddr(uintptr pa)
+{
+	if(pa < (uintptr)-KZERO)
+		return (void*)(pa + KZERO);
+	panic("kaddr: pa=%#p pc=%#p", pa, getcallerpc(&pa));
+	return nil;
+}
+
+static void*
+kmapaddr(uintptr pa)
+{
+	if(pa < (uintptr)-KZERO)
+		return (void*)(pa + KZERO);
+	if(pa < (VDRAM - KZERO) || pa >= (VDRAM - KZERO) + (KMAPEND - KMAP))
+		panic("kmapaddr: pa=%#p pc=%#p", pa, getcallerpc(&pa));
+	return (void*)(pa + KMAP - (VDRAM - KZERO));
+}
+
+KMap*
+kmap(Page *p)
+{
+	return kmapaddr(p->pa);
+}
+
+void
+kunmap(KMap*)
+{
+}
+
+void
+kmapinval(void)
+{
+}
+
+static void*
+rampage(void)
+{
+	uintptr pa;
+
+	if(conf.npage)
+		return mallocalign(BY2PG, BY2PG, 0, 0);
+
+	pa = conf.mem[0].base;
+	assert((pa % BY2PG) == 0);
+	assert(pa < INITMAP);
+	conf.mem[0].base += BY2PG;
+	return KADDR(pa);
+}
+
+static void
+l1map(uintptr va, uintptr pa, uintptr pe, uintptr attr)
+{
+	uintptr *l1, *l0;
+
+	assert(pa < pe);
+
+	va &= -BY2PG;
+	pa &= -BY2PG;
+	pe = PGROUND(pe);
+
+	attr |= PTEKERNEL | PTEAF;
+
+	l1 = (uintptr*)L1;
+
+	while(pa < pe){
+		if(l1[PTL1X(va, 1)] == 0 && (pe-pa) >= PGLSZ(1) && ((va|pa) & PGLSZ(1)-1) == 0){
+			l1[PTL1X(va, 1)] = PTEVALID | PTEBLOCK | pa | attr;
+			va += PGLSZ(1);
+			pa += PGLSZ(1);
+			continue;
+		}
+		if(l1[PTL1X(va, 1)] & PTEVALID) {
+			assert((l1[PTL1X(va, 1)] & PTETABLE) == PTETABLE);
+			l0 = KADDR(l1[PTL1X(va, 1)] & -PGLSZ(0));
+		} else {
+			l0 = rampage();
+			memset(l0, 0, BY2PG);
+			l1[PTL1X(va, 1)] = PTEVALID | PTETABLE | PADDR(l0);
+		}
+		assert(l0[PTLX(va, 0)] == 0);
+		l0[PTLX(va, 0)] = PTEVALID | PTEPAGE | pa | attr;
+		va += BY2PG;
+		pa += BY2PG;
+	}
+}
+
+static void
+kmapram(uintptr base, uintptr limit)
+{
+	if(base < (uintptr)-KZERO && limit > (uintptr)-KZERO){
+		kmapram(base, (uintptr)-KZERO);
+		kmapram((uintptr)-KZERO, limit);
+		return;
+	}
+	if(base < INITMAP)
+		base = INITMAP;
+	if(base >= limit || limit <= INITMAP)
+		return;
+
+	l1map((uintptr)kmapaddr(base), base, limit,
+		PTEWRITE | PTEPXN | PTEUXN | PTESH(SHARE_INNER));
+}
+
+void
+meminit(void)
+{
+	char *p;
+
+	conf.mem[0].base = PGROUND((uintptr)end - KZERO);
+	conf.mem[0].limit = GiB + 128 * MiB;
+	if(p = getconf("*maxmem"))
+		conf.mem[0].limit = strtoull(p, 0, 0);
+
+	kmapram(conf.mem[0].base, conf.mem[0].limit);
+
+	conf.mem[0].npage = (conf.mem[0].limit - conf.mem[0].base)/BY2PG;
+}
+
+uintptr
+mmukmap(uintptr va, uintptr pa, usize size)
+{
+	uintptr attr, off;
+
+	if(va == 0)
+		return 0;
+
+	off = pa & BY2PG-1;
+
+	attr = va & PTEMA(7);
+	attr |= PTEWRITE | PTEUXN | PTEPXN | PTESH(SHARE_OUTER);
+
+	va &= -BY2PG;
+	pa &= -BY2PG;
+
+	l1map(va, pa, pa + off + size, attr);
+	flushtlb();
+
+	return va + off;
+}
+
+void*
+vmap(uvlong pa, vlong size)
+{
+	static uintptr base = VMAP;
+	uvlong pe = pa + size;
+	uintptr va;
+
+	va = base;
+	base += PGROUND(pe) - (pa & -BY2PG);
+	
+	return (void*)mmukmap(va | PTEDEVICE, pa, size);
+}
+
+void
+vunmap(void *, vlong)
+{
+}
+
+static uintptr*
+mmuwalk(uintptr va, int level)
+{
+	uintptr *table, pte;
+	Page *pg;
+	int i, x;
+
+	x = PTLX(va, PTLEVELS-1);
+	table = m->mmutop;
+	for(i = PTLEVELS-2; i >= level; i--){
+		pte = table[x];
+		if(pte & PTEVALID) {
+			if(pte & (0xFFFFULL<<48))
+				iprint("strange pte %#p va %#p\n", pte, va);
+			pte &= ~(0xFFFFULL<<48 | BY2PG-1);
+		} else {
+			pg = up->mmufree;
+			if(pg == nil)
+				return nil;
+			up->mmufree = pg->next;
+			pg->va = va & -PGLSZ(i+1);
+			if((pg->next = up->mmuhead[i+1]) == nil)
+				up->mmutail[i+1] = pg;
+			up->mmuhead[i+1] = pg;
+			pte = pg->pa;
+			memset(kmapaddr(pte), 0, BY2PG);
+			coherence();
+			table[x] = pte | PTEVALID | PTETABLE;
+		}
+		table = kmapaddr(pte);
+		x = PTLX(va, (uintptr)i);
+	}
+	return &table[x];
+}
+
+static Proc *asidlist[256];
+
+static int
+allocasid(Proc *p)
+{
+	static Lock lk;
+	Proc *x;
+	int a;
+
+	lock(&lk);
+	a = p->asid;
+	if(a < 0)
+		a = -a;
+	if(a == 0)
+		a = p->pid;
+	for(;; a++){
+		a %= nelem(asidlist);
+		if(a == 0)
+			continue;	// reserved
+		x = asidlist[a];
+		if(x == p || x == nil || (x->asid < 0 && x->mach == nil))
+			break;
+	}
+	p->asid = a;
+	asidlist[a] = p;
+	unlock(&lk);
+
+	return x != p;
+}
+
+static void
+freeasid(Proc *p)
+{
+	int a;
+
+	a = p->asid;
+	if(a < 0)
+		a = -a;
+	if(a > 0 && asidlist[a] == p)
+		asidlist[a] = nil;
+	p->asid = 0;
+}
+
+void
+putasid(Proc *p)
+{
+	/*
+	 * Prevent the following scenario:
+	 *	pX sleeps on cpuA, leaving its page tables in mmutop
+	 *	pX wakes up on cpuB, and exits, freeing its page tables
+	 *  pY on cpuB allocates a freed page table page and overwrites with data
+	 *  cpuA takes an interrupt, and is now running with bad page tables
+	 * In theory this shouldn't hurt because only user address space tables
+	 * are affected, and mmuswitch will clear mmutop before a user process is
+	 * dispatched.  But empirically it correlates with weird problems, eg
+	 * resetting of the core clock at 0x4000001C which confuses local timers.
+	 */
+	if(conf.nmach > 1)
+		mmuswitch(nil);
+
+	if(p->asid > 0)
+		p->asid = -p->asid;
+}
+
+void
+putmmu(uintptr va, uintptr pa, Page *pg)
+{
+	uintptr *pte, old;
+	int s;
+
+	s = splhi();
+	while((pte = mmuwalk(va, 0)) == nil){
+		spllo();
+		up->mmufree = newpage(0, nil, 0);
+		splhi();
+	}
+	old = *pte;
+	*pte = 0;
+	if((old & PTEVALID) != 0)
+		flushasidvall((uvlong)up->asid<<48 | va>>12);
+	else
+		flushasidva((uvlong)up->asid<<48 | va>>12);
+	*pte = pa | PTEPAGE | PTEUSER | PTEPXN | PTENG | PTEAF |
+		(((pa & PTEMA(7)) == PTECACHED)? PTESH(SHARE_INNER): PTESH(SHARE_OUTER));
+	if(needtxtflush(pg)){
+		cachedwbinvse(kmap(pg), BY2PG);
+		cacheiinvse((void*)va, BY2PG);
+		donetxtflush(pg);
+	}
+	splx(s);
+}
+
+static void
+mmufree(Proc *p)
+{
+	int i;
+
+	freeasid(p);
+
+	for(i=1; i<PTLEVELS; i++){
+		if(p->mmuhead[i] == nil)
+			break;
+		p->mmutail[i]->next = p->mmufree;
+		p->mmufree = p->mmuhead[i];
+		p->mmuhead[i] = p->mmutail[i] = nil;
+	}
+}
+
+void
+mmuswitch(Proc *p)
+{
+	uintptr va;
+	Page *t;
+
+	for(va = UZERO; va < USTKTOP; va += PGLSZ(PTLEVELS-1))
+		m->mmutop[PTLX(va, PTLEVELS-1)] = 0;
+
+	if(p == nil){
+		setttbr(PADDR(m->mmutop));
+		return;
+	}
+
+	if(p->newtlb){
+		mmufree(p);
+		p->newtlb = 0;
+	}
+
+	if(allocasid(p))
+		flushasid((uvlong)p->asid<<48);
+
+	setttbr((uvlong)p->asid<<48 | PADDR(m->mmutop));
+
+	for(t = p->mmuhead[PTLEVELS-1]; t != nil; t = t->next){
+		va = t->va;
+		m->mmutop[PTLX(va, PTLEVELS-1)] = t->pa | PTEVALID | PTETABLE;
+	}
+}
+
+void
+mmurelease(Proc *p)
+{
+	mmuswitch(nil);
+	mmufree(p);
+	freepages(p->mmufree, nil, 0);
+	p->mmufree = nil;
+}
+
+void
+flushmmu(void)
+{
+	int x;
+
+	x = splhi();
+	up->newtlb = 1;
+	mmuswitch(up);
+	splx(x);
+}
+
+void
+checkmmu(uintptr, uintptr)
+{
+}
--- /dev/null
+++ b/sys/src/9/arm64/pciqemu.c
@@ -1,0 +1,175 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/pci.h"
+
+#define MMIOBASE 0x10000000
+#define ECAMBASE 0x3F000000
+#define ECAMSIZE 0x01000000
+
+typedef struct Intvec Intvec;
+
+struct Intvec {
+	Pcidev *p;
+	void (*f)(Ureg*, void*);
+	void *a;
+};
+
+static uchar *ecam;
+static Intvec vec[32];
+
+static void*
+cfgaddr(int tbdf, int rno)
+{
+	return ecam + (BUSBNO(tbdf)<<20 | BUSDNO(tbdf)<<15 | BUSFNO(tbdf)<<12) + rno;
+}
+
+int
+pcicfgrw32(int tbdf, int rno, int data, int read)
+{
+	u32int *p;
+
+	if((p = cfgaddr(tbdf, rno & ~3)) != nil){
+		if(read)
+			data = *p;
+		else
+			*p = data;
+	} else {
+		data = -1;
+	}
+	return data;
+}
+
+int
+pcicfgrw16(int tbdf, int rno, int data, int read)
+{
+	u16int *p;
+
+	if((p = cfgaddr(tbdf, rno & ~1)) != nil){
+		if(read)
+			data = *p;
+		else
+			*p = data;
+	} else {
+		data = -1;
+	}
+	return data;
+}
+
+int
+pcicfgrw8(int tbdf, int rno, int data, int read)
+{
+	u8int *p;
+
+	if((p = cfgaddr(tbdf, rno)) != nil){
+		if(read)
+			data = *p;
+		else
+			*p = data;
+	} else {
+		data = -1;
+	}
+	return data;
+}
+
+static void
+pciinterrupt(Ureg *ureg, void *)
+{
+	int i;
+	Intvec *v;
+
+	for(i = 0; i < nelem(vec); i++){
+		v = &vec[i];
+		if(v->f)
+			v->f(ureg, v->a);
+	}
+}
+
+static void
+pciintrinit(void)
+{
+	intrenable(IRQpci1, pciinterrupt, nil, BUSUNKNOWN, "pci");
+	intrenable(IRQpci2, pciinterrupt, nil, BUSUNKNOWN, "pci");
+	intrenable(IRQpci3, pciinterrupt, nil, BUSUNKNOWN, "pci");
+	intrenable(IRQpci4, pciinterrupt, nil, BUSUNKNOWN, "pci");
+}
+
+void
+pciintrenable(int tbdf, void (*f)(Ureg*, void*), void *a)
+{
+	Pcidev *p;
+	int i;
+	Intvec *v;
+
+	if((p = pcimatchtbdf(tbdf)) == nil){
+		print("pciintrenable: %T: unknown device\n", tbdf);
+		return;
+	}
+
+	for(i = 0; i < nelem(vec); i++){
+		v = &vec[i];
+		if(v->f == nil){
+			v->p = p;
+			v->f = f;
+			v->a = a;
+			return;
+		}
+	}
+}
+
+void
+pciintrdisable(int tbdf, void (*f)(Ureg*, void*), void *a)
+{
+	Pcidev *p;
+	int i;
+	Intvec *v;
+
+	if((p = pcimatchtbdf(tbdf)) == nil){
+		print("pciintrdisable: %T: unknown device\n", tbdf);
+		return;
+	}
+
+	for(i = 0; i < nelem(vec); i++){
+		v = &vec[i];
+		if(v->p == p && v->f == f && v->a == a)
+			v->f = nil;
+	}
+}
+
+static void
+pcicfginit(void)
+{
+	char *p;
+	Pcidev *pciroot;
+	ulong ioa;
+	uvlong base;
+
+	fmtinstall('T', tbdffmt);
+
+	pcimaxdno = 32;
+	if(p = getconf("*pcimaxdno"))
+		pcimaxdno = strtoul(p, 0, 0);
+
+	pciscan(0, &pciroot, nil);
+	if(pciroot == nil)
+		return;
+
+	pciintrinit();
+
+	ioa = 0;
+	base = MMIOBASE;
+	pcibusmap(pciroot, &base, &ioa, 1);
+
+	if(getconf("*pcihinv"))
+		pcihinv(pciroot);
+}
+
+void
+pciqemulink(void)
+{
+	ecam = vmap(ECAMBASE, ECAMSIZE);
+	pcicfginit();
+}
--- /dev/null
+++ b/sys/src/9/arm64/qemu
@@ -1,0 +1,50 @@
+dev
+	root
+	cons
+	swap
+	env
+	pipe
+	proc
+	mnt
+	srv
+	shr
+	dup
+	tls
+	cap
+	fs
+	ether	netif
+	bridge	log
+	ip	arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium inferno
+	uart
+	rtc
+	pci	pci
+	sd
+
+link
+	ethervirtio10	pci
+	ethersink
+	ethermedium
+	loopbackmedium
+	netdevmedium
+	pciqemu		pci
+
+ip
+	tcp
+	udp
+	il
+	ipifc
+	icmp
+	icmp6
+	igmp
+	ipmux
+misc
+	gic
+	uartqemu
+	sdvirtio10	pci sdscsi
+port
+	int cpuserver = 0;
+bootdir
+	/$objtype/bin/paqfs
+	/$objtype/bin/auth/factotum
+	bootfs.paq
+	boot
--- /dev/null
+++ b/sys/src/9/arm64/rebootcode.s
@@ -1,0 +1,48 @@
+#include "mem.h"
+#include "sysreg.h"
+
+#undef	SYSREG
+#define	SYSREG(op0,op1,Cn,Cm,op2)	SPR(((op0)<<19|(op1)<<16|(Cn)<<12|(Cm)<<8|(op2)<<5))
+
+TEXT _start(SB), 1, $-4
+	MOV	$setSB(SB), R28
+
+	MOV	R0, R27
+
+	MOV	code+8(FP), R1
+	MOVWU	size+16(FP), R2
+	BIC	$3, R2
+	ADD	R1, R2, R3
+
+_copy:
+	MOVW	(R1)4!, R4
+	MOVW	R4, (R0)4!
+	CMP	R1, R3
+	BNE	_copy
+
+	BL	cachedwbinv(SB)
+	BL	l2cacheuwbinv(SB)
+
+	ISB	$SY
+	MRS	SCTLR_EL1, R0
+	BIC	$(1<<0 | 1<<2 | 1<<12), R0
+	ISB	$SY
+	MSR	R0, SCTLR_EL1
+	ISB	$SY
+
+	DSB	$NSHST
+	TLBI	R0, 0,8,7,0	/* VMALLE1 */
+	DSB	$NSH
+	ISB	$SY
+
+	BL	cachedwbinv(SB)
+	BL	cacheiinv(SB)
+
+	MOVWU	$0, R0
+	MOVWU	$0, R1
+	MOVWU	$0, R2
+	MOVWU	$0, R3
+
+	MOV	R27, LR
+
+	RETURN
--- /dev/null
+++ b/sys/src/9/arm64/sysreg.c
@@ -1,0 +1,58 @@
+/*
+ * ARMv8 system registers
+ * mainly to cope with arm hard-wiring register numbers into instructions.
+ *
+ * these routines must be callable from KZERO.
+ *
+ * on a multiprocessor, process switching to another cpu is assumed
+ * to be inhibited by the caller as these registers are local to the cpu.
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+static void*
+mkinstr(ulong wd)
+{
+	static ulong ib[256], *ep[MAXMACH+1];
+	static Lock lk;
+	ulong *ip, *ie;
+
+	ie = ep[m->machno];
+	for(ip = ib; ip < ie; ip += 2)
+		if(*ip == wd)
+			return ip;
+
+	ilock(&lk);
+	ie = ep[MAXMACH];
+	for(; ip < ie; ip += 2)
+		if(*ip == wd)
+			goto Found;
+	if(ip >= &ib[nelem(ib)])
+		panic("mkinstr: out of instrucuction buffer");
+	ip[0] = wd;
+	ip[1] = 0xd65f03c0;	// RETURN
+	ep[MAXMACH] = ie = ip + 2;
+	cachedwbinvse(ip, 2*sizeof(*ip));
+Found:
+	iunlock(&lk);
+	cacheiinv();
+	ep[m->machno] = ie;
+	return ip;
+}
+
+uvlong
+sysrd(ulong spr)
+{
+	uvlong (*fp)(void) = mkinstr(0xd5380000UL | spr);
+	return fp();
+}
+
+void
+syswr(ulong spr, uvlong val)
+{
+	void (*fp)(uvlong) = mkinstr(0xd5180000UL | spr);
+	fp(val);
+}
--- /dev/null
+++ b/sys/src/9/arm64/sysreg.h
@@ -1,0 +1,90 @@
+#define MIDR_EL1			SYSREG(3,0,0,0,0)
+#define MPIDR_EL1			SYSREG(3,0,0,0,5)
+#define ID_AA64AFR0_EL1			SYSREG(3,0,0,5,4)
+#define ID_AA64AFR1_EL1			SYSREG(3,0,0,5,5)
+#define ID_AA64DFR0_EL1			SYSREG(3,0,0,5,0)
+#define ID_AA64DFR1_EL1			SYSREG(3,0,0,5,1)
+#define ID_AA64ISAR0_EL1		SYSREG(3,0,0,6,0)
+#define ID_AA64ISAR1_EL1		SYSREG(3,0,0,6,1)
+#define ID_AA64MMFR0_EL1		SYSREG(3,0,0,7,0)
+#define ID_AA64MMFR1_EL1		SYSREG(3,0,0,7,1)
+#define ID_AA64PFR0_EL1			SYSREG(3,0,0,4,0)
+#define ID_AA64PFR1_EL1			SYSREG(3,0,0,4,1)
+#define SCTLR_EL1			SYSREG(3,0,1,0,0)
+#define CPACR_EL1			SYSREG(3,0,1,0,2)
+#define MAIR_EL1			SYSREG(3,0,10,2,0)
+#define TCR_EL1				SYSREG(3,0,2,0,2)
+#define TTBR0_EL1			SYSREG(3,0,2,0,0)
+#define TTBR1_EL1			SYSREG(3,0,2,0,1)
+#define ESR_EL1				SYSREG(3,0,5,2,0)
+#define FAR_EL1				SYSREG(3,0,6,0,0)
+#define VBAR_EL1			SYSREG(3,0,12,0,0)
+#define VTTBR_EL2			SYSREG(3,4,2,1,0)
+#define SP_EL0				SYSREG(3,0,4,1,0)
+#define SP_EL1				SYSREG(3,4,4,1,0)
+#define SP_EL2				SYSREG(3,6,4,1,0)
+#define SCTLR_EL2			SYSREG(3,4,1,0,0)
+#define HCR_EL2				SYSREG(3,4,1,1,0)
+#define MDCR_EL2			SYSREG(3,4,1,1,1)
+#define PMCR_EL0			SYSREG(3,3,9,12,0)
+#define PMCNTENSET			SYSREG(3,3,9,12,1)
+#define PMCCNTR_EL0			SYSREG(3,3,9,13,0)
+#define PMUSERENR_EL0			SYSREG(3,3,9,14,0)
+
+#define CNTPCT_EL0			SYSREG(3,3,14,0,1)
+#define CNTVCT_EL0			SYSREG(3,3,14,0,2)
+#define CNTKCTL_EL1			SYSREG(3,0,14,1,0)
+#define	CNTFRQ_EL0			SYSREG(3,3,14,0,0)
+#define CNTV_TVAL_EL0			SYSREG(3,3,14,3,0)
+#define CNTV_CTL_EL0			SYSREG(3,3,14,3,1)
+#define CNTV_CVAL_EL0			SYSREG(3,3,14,3,2)
+#define CNTVOFF_EL2			SYSREG(3,4,14,0,3)
+
+#define TPIDR_EL0			SYSREG(3,3,13,0,2)
+#define TPIDR_EL1			SYSREG(3,0,13,0,4)
+
+#define CCSIDR_EL1			SYSREG(3,1,0,0,0)
+#define CSSELR_EL1			SYSREG(3,2,0,0,0)
+
+#define ACTLR_EL2			SYSREG(3,4,1,0,1)
+#define CPUACTLR_EL1			SYSREG(3,1,15,2,0)
+#define CPUECTLR_EL1			SYSREG(3,1,15,2,1)
+#define CBAR_EL1			SYSREG(3,1,15,3,0)
+
+#define	ICC_AP0R_EL1(m)			SYSREG(3,0,12,8,4|(m))
+#define	ICC_AP1R_EL1(m)			SYSREG(3,0,12,9,0|(m))
+#define	ICC_ASGI1R_EL1			SYSREG(3,0,12,11,6)
+#define	ICC_BPR0_EL1			SYSREG(3,0,12,8,3)
+#define	ICC_BPR1_EL1			SYSREG(3,0,12,12,3)
+#define	ICC_CTLR_EL1			SYSREG(3,0,12,12,4)
+#define	ICC_DIR_EL1			SYSREG(3,0,12,11,1)
+#define	ICC_EOIR0_EL1			SYSREG(3,0,12,8,1)
+#define	ICC_EOIR1_EL1			SYSREG(3,0,12,12,1)
+#define	ICC_HPPIR0_EL1			SYSREG(3,0,12,8,2)
+#define	ICC_HPPIR1_EL1			SYSREG(3,0,12,12,2)
+#define ICC_IAR0_EL1			SYSREG(3,0,12,8,0)
+#define	ICC_IAR1_EL1			SYSREG(3,0,12,12,0)
+#define	ICC_IGRPEN0_EL1			SYSREG(3,0,12,12,6)
+#define	ICC_IGRPEN1_EL1			SYSREG(3,0,12,12,7)
+#define	ICC_NMIAR1_EL1			SYSREG(3,0,12,9,5)
+#define	ICC_PMR_EL1			SYSREG(3,0,4,6,0)
+#define	ICC_RPR_EL1			SYSREG(3,0,12,11,3)
+#define	ICC_SGI0R_EL1			SYSREG(3,0,12,11,7)
+#define	ICC_SGI1R_EL1			SYSREG(3,0,12,11,5)
+#define ICC_SRE_EL1			SYSREG(3,0,12,12,5)
+
+/* l.s redefines this for the assembler */
+#define SYSREG(op0,op1,Cn,Cm,op2)	((op0)<<19|(op1)<<16|(Cn)<<12|(Cm)<<8|(op2)<<5)
+
+#define	OSHLD	(0<<2 | 1)
+#define OSHST	(0<<2 | 2)
+#define	OSH	(0<<2 | 3)
+#define NSHLD	(1<<2 | 1)
+#define NSHST	(1<<2 | 2)
+#define NSH	(1<<2 | 3)
+#define ISHLD	(2<<2 | 1)
+#define ISHST	(2<<2 | 2)
+#define ISH	(2<<2 | 3)
+#define LD	(3<<2 | 1)
+#define ST	(3<<2 | 2)
+#define SY	(3<<2 | 3)
--- /dev/null
+++ b/sys/src/9/arm64/trap.c
@@ -1,0 +1,688 @@
+#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"
+#include "sysreg.h"
+
+int	(*buserror)(Ureg*);
+
+/* SPSR bits user can modify */
+#define USPSRMASK	(0xFULL<<28)
+
+static void
+setupvector(u32int *v, void (*t)(void), void (*f)(void))
+{
+	int i;
+
+	for(i = 0; i < 0x80/4; i++){
+		v[i] = ((u32int*)t)[i];
+		if(v[i] == 0x14000000){
+			v[i] |= ((u32int*)f - &v[i]) & 0x3ffffff;
+			return;
+		}
+	}
+	panic("bug in vector code");
+}
+
+void
+trapinit(void)
+{
+	extern void vsys(void);
+	extern void vtrap(void);
+	extern void virq(void);
+	extern void vfiq(void);
+	extern void vserr(void);
+
+	extern void vsys0(void);
+	extern void vtrap0(void);
+	extern void vtrap1(void);
+
+	static u32int *v;
+
+	intrcpushutdown();
+	if(v == nil){
+		/* disable everything */
+		intrsoff();
+
+		v = mallocalign(0x80*4*4, 1<<11, 0, 0);
+		if(v == nil)
+			panic("no memory for vector table");
+
+		setupvector(&v[0x000/4], vtrap,	vtrap0);
+		setupvector(&v[0x080/4], virq,	vtrap0);
+		setupvector(&v[0x100/4], vfiq,	vtrap0);
+		setupvector(&v[0x180/4], vserr,	vtrap0);
+
+		setupvector(&v[0x200/4], vtrap,	vtrap1);
+		setupvector(&v[0x280/4], virq,	vtrap1);
+		setupvector(&v[0x300/4], vfiq,	vtrap1);
+		setupvector(&v[0x380/4], vserr,	vtrap1);
+
+		setupvector(&v[0x400/4], vsys,	vsys0);
+		setupvector(&v[0x480/4], virq,	vtrap0);
+		setupvector(&v[0x500/4], vfiq,	vtrap0);
+		setupvector(&v[0x580/4], vserr, vtrap0);
+
+		setupvector(&v[0x600/4], vtrap,	vtrap0);
+		setupvector(&v[0x680/4], virq,	vtrap0);
+		setupvector(&v[0x700/4], vfiq,	vtrap0);
+		setupvector(&v[0x780/4], vserr,	vtrap0);
+
+		cacheduwbse(v, 0x80*4*4);
+	}
+	cacheiinvse(v, 0x80*4*4);
+	syswr(VBAR_EL1, (uintptr)v);
+	splx(0x3<<6);	// unmask serr and debug
+}
+
+static char *traps[64] = {
+	[0x00]	"sys: trap: unknown",
+	[0x01]	"sys: trap: WFI or WFE instruction execution",
+	[0x0E]	"sys: trap: illegal execution state",
+	[0x18]	"sys: trap: illegal MSR/MRS access",
+	[0x22]	"sys: trap: misaligned pc",
+	[0x26]	"sys: trap: stack pointer misaligned",
+	[0x30]	"sys: breakpoint",
+	[0x32]	"sys: software step",
+	[0x34]	"sys: watchpoint",
+	[0x3C]	"sys: breakpoint",
+};
+
+void
+trap(Ureg *ureg)
+{
+	FPsave *f = nil;
+	u32int type, intr;
+	int user;
+
+	intr = ureg->type >> 32;
+	if(intr == 2){
+		fiq(ureg);
+		return;
+	}
+	splflo();
+	user = kenter(ureg);
+	type = (u32int)ureg->type >> 26;
+	switch(type){
+	case 0x20:	// instruction abort from lower level
+	case 0x21:	// instruction abort from same level
+	case 0x24:	// data abort from lower level
+	case 0x25:	// data abort from same level
+		f = fpukenter(ureg);
+		faultarm64(ureg);
+		break;
+	case 0x07:	// SIMD/FP
+	case 0x2C:	// FPU exception (A64 only)
+		mathtrap(ureg);
+		break;
+	case 0x00:	// unknown
+		if(intr == 1){
+			f = fpukenter(ureg);
+			if(!irq(ureg))
+				preempted();
+			else if(up != nil && up->delaysched)
+				sched();
+			break;
+		}
+		if(intr == 3){
+	case 0x2F:	// SError interrupt
+			f = fpukenter(ureg);
+			if(buserror != nil && (*buserror)(ureg))
+				break;
+			dumpregs(ureg);
+			panic("SError interrupt");
+			break;
+		}
+		/* wet floor */
+	case 0x01:	// WFI or WFE instruction execution
+	case 0x03:	// MCR or MRC access to CP15 (A32 only)
+	case 0x04:	// MCRR or MRC access to CP15 (A32 only)
+	case 0x05:	// MCR or MRC access to CP14 (A32 only)
+	case 0x06:	// LDC or STD access to CP14 (A32 only)
+	case 0x08:	// MCR or MRC to CP10 (A32 only)
+	case 0x0C:	// MRC access to CP14 (A32 only)
+	case 0x0E:	// Illegal Execution State
+	case 0x11:	// SVC instruction execution (A32 only)
+	case 0x12:	// HVC instruction execution (A32 only)
+	case 0x13:	// SMC instruction execution (A32 only)
+	case 0x15:	// SVC instruction execution (A64 only)
+	case 0x16:	// HVC instruction execution (A64 only)
+	case 0x17:	// SMC instruction execution (A64 only)
+	case 0x18:	// MSR/MRS (A64)
+	case 0x22:	// misaligned pc
+	case 0x26:	// stack pointer misaligned
+	case 0x28:	// FPU exception (A32 only)
+	case 0x30:	// breakpoint from lower level
+	case 0x31:	// breakpoint from same level
+	case 0x32:	// software step from lower level
+	case 0x33:	// software step from same level
+	case 0x34:	// watchpoint execution from lower level
+	case 0x35:	// watchpoint exception from same level
+	case 0x38:	// breapoint (A32 only)
+	case 0x3A:	// vector catch exception (A32 only)
+	case 0x3C:	// BRK instruction (A64 only)
+	default:
+		f = fpukenter(ureg);
+		if(!userureg(ureg)){
+			dumpregs(ureg);
+			panic("unhandled trap");
+		}
+		if(traps[type] == nil) type = 0;	// unknown
+		postnote(up, 1, traps[type], NDebug);
+		break;
+	}
+
+	splhi();
+	if(user){
+		if(up->procctl || up->nnote)
+			notify(ureg);
+		kexit(ureg);
+	}
+	if(type != 0x07 && type != 0x2C)
+		fpukexit(ureg, f);
+}
+
+void
+syscall(Ureg *ureg)
+{
+	vlong startns, stopns;
+	uintptr sp, ret;
+	ulong scallnr;
+	int i, s;
+	char *e;
+
+	if(!kenter(ureg))
+		panic("syscall from  kernel");
+	fpukenter(ureg);
+	
+	m->syscall++;
+	up->insyscall = 1;
+	up->pc = ureg->pc;
+	
+	sp = ureg->sp;
+	up->scallnr = scallnr = ureg->r0;
+	spllo();
+	
+	up->nerrlab = 0;
+	startns = 0;
+	ret = -1;
+	if(!waserror()){
+		if(sp < USTKTOP - BY2PG || sp > USTKTOP - sizeof(Sargs) - BY2WD){
+			validaddr(sp, sizeof(Sargs)+BY2WD, 0);
+			evenaddr(sp);
+		}
+		up->s = *((Sargs*) (sp + BY2WD));
+
+		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);
+		}
+		
+		if(scallnr >= nsyscall || systab[scallnr] == nil){
+			pprint("bad sys call number %lud pc %#p", scallnr, ureg->pc);
+			postnote(up, 1, "sys: bad sys call", NDebug);
+			error(Ebadarg);
+		}
+		up->psstate = sysctab[scallnr];
+		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=%#p pc=%#p\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));
+		/*
+		 * normally, syscall() returns to forkret()
+		 * not restoring general registers when going
+		 * to userspace. to completely restore the
+		 * interrupted context, we have to return thru
+		 * noteret(). we override return pc to jump to
+		 * to it when returning form syscall()
+		 */
+		returnto(noteret);
+
+		splhi();
+		up->fpstate &= ~FPillegal;
+	}
+	else
+		splhi();
+
+	if(scallnr != RFORK && (up->procctl || up->nnote))
+		notify(ureg);
+
+	if(up->delaysched){
+		sched();
+		splhi();
+	}
+
+	kexit(ureg);
+	fpukexit(ureg, nil);
+}
+
+int
+notify(Ureg *ureg)
+{
+	uintptr sp;
+	char *msg;
+
+	if(up->procctl)
+		procctl();
+	if(up->nnote == 0)
+		return 0;
+
+	spllo();
+	qlock(&up->debug);
+	msg = popnote(ureg);
+	if(msg == nil){
+		qunlock(&up->debug);
+		splhi();
+		return 0;
+	}
+
+	sp = ureg->sp;
+	sp -= 256;	/* debugging: preserve context causing problem */
+	sp -= sizeof(Ureg);
+	sp = STACKALIGN(sp);
+
+	if(!okaddr((uintptr)up->notify, 1, 0)
+	|| !okaddr(sp-ERRMAX-4*BY2WD, sizeof(Ureg)+ERRMAX+4*BY2WD, 1)
+	|| ((uintptr) up->notify & 3) != 0
+	|| (sp & 7) != 0){
+		qunlock(&up->debug);
+		pprint("suicide: bad address in notify: handler=%#p sp=%#p\n",
+			up->notify, sp);
+		pexit("Suicide", 0);
+	}
+
+	memmove((Ureg*)sp, ureg, sizeof(Ureg));
+	*(Ureg**)(sp-BY2WD) = up->ureg;	/* word under Ureg is old 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->link = 0;
+	qunlock(&up->debug);
+
+	splhi();
+	fpuprocsave(up);
+	up->fpstate |= FPillegal;
+	return 1;
+}
+
+void
+noted(Ureg *ureg, ulong arg0)
+{
+	Ureg *nureg;
+	uintptr 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;
+	
+	nureg = up->ureg;
+	
+	oureg = (uintptr) nureg;
+	if(!okaddr(oureg - BY2WD, BY2WD + sizeof(Ureg), 0) || (oureg & 7) != 0){
+		qunlock(&up->debug);
+		pprint("bad ureg in noted or call to noted when not notified\n");
+		pexit("Suicide", 0);
+	}
+
+	nureg->psr = (nureg->psr & USPSRMASK) | (ureg->psr & ~USPSRMASK);
+	memmove(ureg, nureg, sizeof(Ureg));
+	
+	switch(arg0){
+	case NCONT: case NRSTR:
+		if(!okaddr(nureg->pc, BY2WD, 0) || !okaddr(nureg->sp, BY2WD, 0) ||
+				(nureg->pc & 3) != 0 || (nureg->sp & 7) != 0){
+			qunlock(&up->debug);
+			pprint("suicide: trap in noted\n");
+			pexit("Suicide", 0);
+		}
+		up->ureg = (Ureg *) (*(uintptr*) (oureg - BY2WD));
+		qunlock(&up->debug);
+		break;
+	
+	case NSAVE:
+		if(!okaddr(nureg->pc, BY2WD, 0) || !okaddr(nureg->sp, BY2WD, 0) ||
+				(nureg->pc & 3) != 0 || (nureg->sp & 7) != 0){
+			qunlock(&up->debug);
+			pprint("suicide: trap in noted\n");
+			pexit("Suicide", 0);
+		}
+		qunlock(&up->debug);
+		sp = oureg - 4 * BY2WD - ERRMAX;
+		ureg->sp = sp;
+		ureg->r0 = (uintptr) oureg;
+		((uintptr *) sp)[1] = oureg;
+		((uintptr *) sp)[0] = 0;
+		break;
+	
+	default:
+		up->lastnote->flag = NDebug;
+	
+	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);
+	}
+}
+
+static void
+faultnote(Ureg *ureg, char *access, uintptr addr)
+{
+	extern void checkpages(void);
+	char buf[ERRMAX];
+
+	if(!userureg(ureg)){
+		dumpregs(ureg);
+		panic("fault: %s addr=%#p", access, addr);
+	}
+	checkpages();
+	snprint(buf, sizeof(buf), "sys: trap: fault %s addr=%#p", access, addr);
+	postnote(up, 1, buf, NDebug);
+}
+
+void
+faultarm64(Ureg *ureg)
+{
+	int user, read;
+	uintptr addr;
+
+	user = userureg(ureg);
+	if(user)
+		up->insyscall = 1;
+	else {
+		extern void _peekinst(void);
+
+		if(ureg->pc == (uintptr)_peekinst){
+			ureg->pc = ureg->link;
+			return;
+		}
+		if(waserror()){
+			if(up->nerrlab == 0){
+				pprint("suicide: sys: %s\n", up->errstr);
+				pexit(up->errstr, 1);
+			}
+			nexterror();
+		}
+	}
+
+	addr = getfar();
+	read = (ureg->type & (1<<6)) == 0;
+
+	switch((u32int)ureg->type & 0x3F){
+	case  4: case  5: case  6: case  7:	// Tanslation fault.
+	case  8: case  9: case 10: case 11:	// Access flag fault.
+	case 12: case 13: case 14: case 15:	// Permission fault.
+	case 48:				// tlb conflict fault.
+		if(fault(addr, ureg->pc, read) == 0)
+			break;
+
+		/* wet floor */
+	case  0: case  1: case  2: case  3:	// Address size fault.
+	case 16: 				// synchronous external abort.
+	case 24: 				// synchronous parity error on a memory access.
+	case 20: case 21: case 22: case 23:	// synchronous external abort on a table walk.
+	case 28: case 29: case 30: case 31:	// synchronous parity error on table walk.
+	case 33:				// alignment fault.
+	case 52:				// implementation defined, lockdown abort.
+	case 53:				// implementation defined, unsuppoted exclusive.
+	case 61:				// first level domain fault
+	case 62:				// second level domain fault
+	default:
+		faultnote(ureg, read? "read": "write", addr);
+	}
+
+	if(user)
+		up->insyscall = 0;
+	else
+		poperror();
+}
+
+int
+userureg(Ureg* ureg)
+{
+	return (ureg->psr & 15) == 0;
+}
+
+uintptr
+userpc(void)
+{
+	Ureg *ur = up->dbgreg;
+	return ur->pc;
+}
+
+uintptr
+dbgpc(Proc *)
+{
+	Ureg *ur = up->dbgreg;
+	if(ur == nil)
+		return 0;
+	return ur->pc;
+}
+
+void
+procfork(Proc *p)
+{
+	fpuprocfork(p);
+	p->tpidr = up->tpidr;
+}
+
+void
+procsetup(Proc *p)
+{
+	fpuprocsetup(p);
+	p->tpidr = 0;
+	syswr(TPIDR_EL0, p->tpidr);
+}
+
+void
+procsave(Proc *p)
+{
+	fpuprocsave(p);
+	if(p->kp == 0)
+		p->tpidr = sysrd(TPIDR_EL0);
+	putasid(p);	// release asid
+}
+
+void
+procrestore(Proc *p)
+{
+	fpuprocrestore(p);
+	if(p->kp == 0)
+		syswr(TPIDR_EL0, p->tpidr);
+}
+
+void
+kprocchild(Proc *p, void (*entry)(void))
+{
+	p->sched.pc = (uintptr) entry;
+	p->sched.sp = (uintptr) p - 16;
+	*(void**)p->sched.sp = kprocchild;	/* fake */
+}
+
+void
+forkchild(Proc *p, Ureg *ureg)
+{
+	Ureg *cureg;
+
+	p->sched.pc = (uintptr) forkret;
+	p->sched.sp = (uintptr) p - TRAPFRAMESIZE;
+
+	cureg = (Ureg*) (p->sched.sp + 16);
+	memmove(cureg, ureg, sizeof(Ureg));
+	cureg->r0 = 0;
+}
+
+uintptr
+execregs(uintptr entry, ulong ssize, ulong nargs)
+{
+	uintptr *sp;
+	Ureg *ureg;
+
+	sp = (uintptr*)(USTKTOP - ssize);
+	*--sp = nargs;
+
+	ureg = up->dbgreg;
+	ureg->sp = (uintptr)sp;
+	ureg->pc = entry;
+	ureg->link = 0;
+	return USTKTOP-sizeof(Tos);
+}
+
+void
+evenaddr(uintptr addr)
+{
+	if(addr & 3){
+		postnote(up, 1, "sys: odd address", NDebug);
+		error(Ebadarg);
+	}
+}
+
+void
+callwithureg(void (*f) (Ureg *))
+{
+	Ureg u;
+	
+	u.pc = getcallerpc(&f);
+	u.sp = (uintptr) &f;
+	f(&u);
+}
+
+void
+setkernur(Ureg *ureg, Proc *p)
+{
+	ureg->pc = p->sched.pc;
+	ureg->sp = p->sched.sp;
+	ureg->link = (uintptr)sched;
+}
+
+void
+setupwatchpts(Proc*, Watchpt*, int)
+{
+}
+
+void
+setregisters(Ureg* ureg, char* pureg, char* uva, int n)
+{
+	ulong v;
+
+	v = ureg->psr;
+	memmove(pureg, uva, n);
+	ureg->psr = (ureg->psr & USPSRMASK) | (v & ~USPSRMASK);
+}
+
+static void
+dumpstackwithureg(Ureg *ureg)
+{
+	uintptr v, estack, sp;
+	char *s;
+	int i;
+
+	if((s = getconf("*nodumpstack")) != nil && strcmp(s, "0") != 0){
+		iprint("dumpstack disabled\n");
+		return;
+	}
+	iprint("ktrace /kernel/path %#p %#p %#p # pc, sp, link\n",
+		ureg->pc, ureg->sp, ureg->link);
+	delay(2000);
+
+	sp = ureg->sp;
+	if(sp < KZERO || (sp & 7) != 0)
+		sp = (uintptr)&ureg;
+
+	estack = (uintptr)m+MACHSIZE;
+	if(up != nil && sp <= (uintptr)up)
+		estack = (uintptr)up;
+
+	if(sp > estack){
+		if(up != nil)
+			iprint("&up %#p sp %#p\n", up, sp);
+		else
+			iprint("&m %#p sp %#p\n", m, sp);
+		return;
+	}
+
+	i = 0;
+	for(; sp < estack; sp += sizeof(uintptr)){
+		v = *(uintptr*)sp;
+		if(KTZERO < v && v < (uintptr)etext && (v & 3) == 0){
+			iprint("%#8.8lux=%#8.8lux ", (ulong)sp, (ulong)v);
+			i++;
+		}
+		if(i == 4){
+			i = 0;
+			iprint("\n");
+		}
+	}
+	if(i)
+		iprint("\n");
+}
+
+void
+dumpstack(void)
+{
+	callwithureg(dumpstackwithureg);
+}
+
+void
+dumpregs(Ureg *ureg)
+{
+	u64int *r;
+	int i, x;
+
+	x = splhi();
+	if(up != nil)
+		iprint("cpu%d: dumpregs ureg %#p process %lud: %s\n", m->machno, ureg,
+			up->pid, up->text);
+	else
+		iprint("cpu%d: dumpregs ureg %#p\n", m->machno, ureg);
+	r = &ureg->r0;
+	for(i = 0; i < 30; i += 3)
+		iprint("R%d %.16llux  R%d %.16llux  R%d %.16llux\n", i, r[i], i+1, r[i+1], i+2, r[i+2]);
+	iprint("PC %#p  SP %#p  LR %#p  PSR %llux  TYPE %llux\n",
+		ureg->pc, ureg->sp, ureg->link,
+		ureg->psr, ureg->type);
+	splx(x);
+}
--- /dev/null
+++ b/sys/src/9/arm64/uartqemu.c
@@ -1,0 +1,366 @@
+/*
+ * PL011 UART from Miller's BCM2835 driver
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+enum {
+	DR	=	0x00>>2,
+	RSRECR	=	0x04>>2,
+	FR	=	0x18>>2,
+		TXFE	= 1<<7,
+		RXFF	= 1<<6,
+		TXFF	= 1<<5,
+		RXFE	= 1<<4,
+		BUSY	= 1<<3,
+
+	ILPR	=	0x20>>2,
+	IBRD	=	0x24>>2,
+	FBRD	=	0x28>>2,
+	LCRH	=	0x2c>>2,
+		WLENM	= 3<<5,
+		WLEN8	= 3<<5,
+		WLEN7	= 2<<5,
+		WLEN6	= 1<<5,
+		WLEN5	= 0<<5,
+		FEN	= 1<<4,	/* fifo enable */
+		STP2	= 1<<3,	/* 2 stop bits */
+		EPS	= 1<<2,	/* even parity select */
+		PEN	= 1<<1,	/* parity enabled */
+		BRK	= 1<<0,	/* send break */
+
+	CR	=	0x30>>2,
+		CTSEN	= 1<<15,
+		RTSEN	= 1<<14,
+		RTS	= 1<<11,
+		RXE	= 1<<9,
+		TXE	= 1<<8,
+		LBE	= 1<<7,
+		UARTEN	= 1<<0,
+		
+	IFLS	=	0x34>>2,
+	IMSC	=	0x38>>2,
+		TXIM	= 1<<5,
+		RXIM	= 1<<4,
+
+	RIS	=	0x3c>>2,
+	MIS	=	0x40>>2,
+	ICR	=	0x44>>2,
+	DMACR	=	0x48>>2,
+	ITCR	=	0x80>>2,
+	ITIP	=	0x84>>2,
+	ITOP	=	0x88>>2,
+	TDR	=	0x8c>>2,
+};
+
+extern PhysUart qemuphysuart;
+
+static Uart qemuuart = {
+	.regs	= (u32int*)(VIRTIO+0x1000000ULL),
+	.name	= "uart0",
+	.freq	= 24*Mhz,
+	.baud	= 115200,
+	.phys	= &qemuphysuart,
+};
+
+static Uart*
+pnp(void)
+{
+	return &qemuuart;
+}
+
+static void
+interrupt(Ureg*, void *arg)
+{
+	Uart *uart = arg;
+	u32int *reg = (u32int*)uart->regs;
+
+	coherence();
+	while((reg[FR] & RXFE) == 0)
+		uartrecv(uart, reg[DR] & 0xFF);
+	if((reg[FR] & TXFF) == 0)
+		uartkick(uart);
+	reg[ICR] = 1<<5 | 1<<6 | 1<<7 | 1<<8 | 1<<9 | 1<<10;
+	coherence();
+}
+
+static void
+disable(Uart *uart)
+{
+	u32int *reg = (u32int*)uart->regs;
+
+	/* disable interrupt */
+	reg[IMSC] = 0;
+	coherence();
+
+	/* clear interrupt */
+	reg[ICR] = 1<<5 | 1<<6 | 1<<7 | 1<<8 | 1<<9 | 1<<10;
+	coherence();
+
+	/* wait for last transmission to complete */
+	while((reg[FR] & BUSY) != 0)
+		delay(1);
+
+	/* disable uart */
+	reg[CR] = 0;
+	coherence();
+
+	/* flush rx fifo */
+	reg[LCRH] &= ~FEN;
+	coherence();
+}
+
+static void
+uartoff(Uart *uart)
+{
+	u32int *reg = (u32int*)uart->regs;
+	u32int im;
+
+	im = reg[IMSC];
+	disable(uart);
+	reg[IMSC] = im;
+}
+
+static void
+uarton(Uart *uart)
+{
+	u32int *reg = (u32int*)uart->regs;
+
+	/* enable fifo */
+	reg[LCRH] |= FEN;
+	coherence();
+
+	/* enable uart */
+	reg[CR] = UARTEN | RXE | TXE;
+	coherence();
+}
+
+static void
+enable(Uart *uart, int ie)
+{
+	u32int *reg = (u32int*)uart->regs;
+
+	disable(uart);
+	if(ie){
+		intrenable(IRQuart, interrupt, uart, BUSUNKNOWN, uart->name);
+		reg[IMSC] = TXIM|RXIM;
+	}
+	uarton(uart);
+}
+
+static void
+linectl(Uart *uart, u32int set, u32int clr)
+{
+	u32int *reg = (u32int*)uart->regs;
+
+	if(uart->enabled)
+		uartoff(uart);
+
+	reg[LCRH] = set | (reg[LCRH] & ~clr);
+
+	if(uart->enabled)
+		uarton(uart);
+}
+
+static void
+kick(Uart *uart)
+{
+	u32int *reg = (u32int*)uart->regs;
+
+	if(uart->blocked)
+		return;
+	coherence();
+	while((reg[FR] & TXFF) == 0){
+		if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
+			break;
+		reg[DR] = *(uart->op++);
+	}
+	coherence();
+}
+
+static void
+dobreak(Uart *uart, int ms)
+{
+	linectl(uart, BRK, 0);
+	delay(ms);
+	linectl(uart, 0, BRK);
+}
+
+static int
+baud(Uart *uart, int n)
+{
+	u32int *reg = (u32int*)uart->regs;
+
+	if(uart->freq <= 0 || n <= 0)
+		return -1;
+
+	if(uart->enabled)
+		uartoff(uart);
+
+	reg[IBRD] = (uart->freq >> 4) / n;
+	reg[FBRD] = (uart->freq >> 4) % n;
+
+	if(uart->enabled)
+		uarton(uart);
+
+	uart->baud = n;
+	return 0;
+}
+
+static int
+bits(Uart *uart, int n)
+{
+	switch(n){
+	case 8:
+		linectl(uart, WLEN8, WLENM);
+		break;
+	case 7:
+		linectl(uart, WLEN7, WLENM);
+		break;
+	case 6:
+		linectl(uart, WLEN6, WLENM);
+		break;
+	case 5:
+		linectl(uart, WLEN5, WLENM);
+		break;
+	default:
+		return -1;
+	}
+	uart->bits = n;
+	return 0;
+}
+
+static int
+stop(Uart *uart, int n)
+{
+	switch(n){
+	case 1:
+		linectl(uart, 0, STP2);
+		break;
+	case 2:
+		linectl(uart, STP2, 0);
+		break;
+	default:
+		return -1;
+	}
+	uart->stop = n;
+	return 0;
+}
+
+static int
+parity(Uart *uart, int n)
+{
+	switch(n){
+	case 'n':
+		linectl(uart, 0, PEN);
+		break;
+	case 'e':
+		linectl(uart, EPS|PEN, 0);
+		break;
+	case 'o':
+		linectl(uart, PEN, EPS);
+		break;
+	default:
+		return -1;
+	}
+	uart->parity = n;
+	return 0;
+}
+
+static void
+modemctl(Uart *uart, int on)
+{
+	uart->modem = on;
+}
+
+static void
+rts(Uart*, int)
+{
+}
+
+static long
+status(Uart *uart, void *buf, long n, long offset)
+{
+	char *p;
+
+	p = malloc(READSTR);
+	if(p == nil)
+		error(Enomem);
+	snprint(p, READSTR,
+		"b%d\n"
+		"dev(%d) type(%d) framing(%d) overruns(%d) "
+		"berr(%d) serr(%d)\n",
+
+		uart->baud,
+		uart->dev,
+		uart->type,
+		uart->ferr,
+		uart->oerr,
+		uart->berr,
+		uart->serr
+	);
+	n = readstr(offset, buf, n, p);
+	free(p);
+
+	return n;
+}
+
+static void
+donothing(Uart*, int)
+{
+}
+
+static void
+putc(Uart *uart, int c)
+{
+	u32int *reg = (u32int*)uart->regs;
+
+	while((reg[FR] & TXFF) != 0)
+		;
+	reg[DR] = c & 0xFF;
+}
+
+static int
+getc(Uart *uart)
+{
+	u32int *reg = (u32int*)uart->regs;
+
+	while((reg[FR] & RXFE) != 0)
+		;
+	return reg[DR] & 0xFF;
+}
+
+void
+uartconsinit(void)
+{
+	consuart = &qemuuart;
+	consuart->console = 1;
+	uartctl(consuart, "l8 pn s1");
+	uartputs(kmesg.buf, kmesg.n);
+}
+
+PhysUart qemuphysuart = {
+	.name		= "qemu",
+	.pnp		= pnp,
+	.enable		= enable,
+	.disable	= disable,
+	.kick		= kick,
+	.dobreak	= dobreak,
+	.baud		= baud,
+	.bits		= bits,
+	.stop		= stop,
+	.parity		= parity,
+	.modemctl	= donothing,
+	.rts		= rts,
+	.dtr		= donothing,
+	.status		= status,
+	.fifo		= donothing,
+	.getc		= getc,
+	.putc		= putc,
+};
--- a/sys/src/9/mkfile
+++ b/sys/src/9/mkfile
@@ -1,4 +1,5 @@
 ARCH=\
+	arm64\
 	bcm\
 	bcm64\
 	cycv\
--- a/sys/src/boot/mkfile
+++ b/sys/src/boot/mkfile
@@ -3,6 +3,7 @@
 	bitsy\
 	efi\
 	pc\
+	qemu\
 	reform\
 	zynq\
 
--- /dev/null
+++ b/sys/src/boot/qemu/boot.txt
@@ -1,0 +1,3 @@
+mw.b 0x40010000 0x0 0x10000
+load ${devtype} ${devnum}:${bootpart} 0x40010000 ${prefix}plan9.ini
+load ${devtype} ${devnum}:${bootpart} 0x40100000 ${prefix}9qemu.u && bootm 0x40100000
--- /dev/null
+++ b/sys/src/boot/qemu/mkfile
@@ -1,0 +1,9 @@
+FILES=boot.scr
+
+all:V:	$FILES
+
+clean:V:
+	rm -f $FILES
+
+boot.scr:	boot.txt
+	aux/txt2uimage -o $target $prereq