shithub: riscv

Download patch

ref: 83e20b4df18d539db59c8e1090f77a6565df250e
parent: 796e5e6000677a39577d545e4603ce251e7cbfe9
author: cinap_lenrek <cinap_lenrek@felloff.net>
date: Sat Oct 20 15:56:31 EDT 2018

bcm: import changes for raspi2/3 from richard miller

--- a/sys/src/9/bcm/archbcm.c
+++ b/sys/src/9/bcm/archbcm.c
@@ -1,5 +1,5 @@
 /*
- * bcm2835 (e.g. raspberry pi) architecture-specific stuff
+ * bcm2835 (e.g. original raspberry pi) architecture-specific stuff
  */
 
 #include "u.h"
@@ -13,8 +13,19 @@
 
 #define	POWERREGS	(VIRTIO+0x100000)
 
+Soc soc = {
+	.dramsize	= 512*MiB,
+	.physio		= 0x20000000,
+	.busdram	= 0x40000000,
+	.busio		= 0x7E000000,
+	.armlocal	= 0,
+	.l1ptedramattrs = Cached | Buffered,
+	.l2ptedramattrs = Cached | Buffered,
+};
+
 enum {
 	Wdogfreq	= 65536,
+	Wdogtime	= 10,	/* seconds, ≤ 15 */
 };
 
 /*
@@ -25,6 +36,7 @@
 		Password	= 0x5A<<24,
 		CfgMask		= 0x03<<4,
 		CfgReset	= 0x02<<4,
+	Rsts		= 0x20>>2,
 	Wdog		= 0x24>>2,
 };
 
@@ -48,13 +60,68 @@
 }
 
 void
+wdogfeed(void)
+{
+	u32int *r;
+
+	r = (u32int*)POWERREGS;
+	r[Wdog] = Password | (Wdogtime * Wdogfreq);
+	r[Rstc] = Password | (r[Rstc] & ~CfgMask) | CfgReset;
+}
+
+void
+wdogoff(void)
+{
+	u32int *r;
+
+	r = (u32int*)POWERREGS;
+	r[Rstc] = Password | (r[Rstc] & ~CfgMask);
+}
+	
+char *
+cputype2name(char *buf, int size)
+{
+	seprint(buf, buf + size, "1176JZF-S");
+	return buf;
+}
+
+void
 cpuidprint(void)
 {
-	print("cpu%d: %dMHz ARM1176JZF-S\n", m->machno, m->cpumhz);
+	char name[64];
+
+	cputype2name(name, sizeof name);
+	delay(50);				/* let uart catch up */
+	print("cpu%d: %dMHz ARM %s\n", m->machno, m->cpumhz, name);
 }
 
+int
+getncpus(void)
+{
+	return 1;
+}
+
+int
+startcpus(uint)
+{
+	return 1;
+}
+
 void
 archbcmlink(void)
 {
+	addclock0link(wdogfeed, HZ);
+}
+
+int
+l2ap(int ap)
+{
+	return (AP(3, (ap))|AP(2, (ap))|AP(1, (ap))|AP(0, (ap)));
+}
+
+int
+cmpswap(long *addr, long old, long new)
+{
+	return cas32(addr, old, new);
 }
 
--- /dev/null
+++ b/sys/src/9/bcm/archbcm2.c
@@ -1,0 +1,248 @@
+/*
+ * bcm2836 (e.g.raspberry pi 2) architecture-specific stuff
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "../port/error.h"
+#include "io.h"
+#include "arm.h"
+
+#include "../port/netif.h"
+
+typedef struct Mbox Mbox;
+typedef struct Mboxes Mboxes;
+
+#define	POWERREGS	(VIRTIO+0x100000)
+
+Soc soc = {
+	.dramsize	= 0x3F000000, 	/* was 1024*MiB, but overlaps with physio */
+	.physio		= 0x3F000000,
+	.busdram	= 0xC0000000,
+	.busio		= 0x7E000000,
+	.armlocal	= 0x40000000,
+	.l1ptedramattrs = Cached | Buffered | L1wralloc | L1sharable,
+	.l2ptedramattrs = Cached | Buffered | L2wralloc | L2sharable,
+};
+
+enum {
+	Wdogfreq	= 65536,
+	Wdogtime	= 10,	/* seconds, ≤ 15 */
+};
+
+/*
+ * Power management / watchdog registers
+ */
+enum {
+	Rstc		= 0x1c>>2,
+		Password	= 0x5A<<24,
+		CfgMask		= 0x03<<4,
+		CfgReset	= 0x02<<4,
+	Rsts		= 0x20>>2,
+	Wdog		= 0x24>>2,
+};
+
+/*
+ * Arm local regs for smp
+ */
+struct Mbox {
+	u32int	doorbell;
+	u32int	mbox1;
+	u32int	mbox2;
+	u32int	startcpu;
+};
+struct Mboxes {
+	Mbox	set[4];
+	Mbox	clr[4];
+};
+
+enum {
+	Mboxregs	= 0x80
+};
+
+static Lock startlock[MAXMACH + 1];
+
+void
+archreset(void)
+{
+	fpon();
+}
+
+void
+archreboot(void)
+{
+	u32int *r;
+
+	r = (u32int*)POWERREGS;
+	r[Wdog] = Password | 1;
+	r[Rstc] = Password | (r[Rstc] & ~CfgMask) | CfgReset;
+	coherence();
+	for(;;)
+		;
+}
+
+void
+wdogfeed(void)
+{
+	u32int *r;
+
+	r = (u32int*)POWERREGS;
+	r[Wdog] = Password | (Wdogtime * Wdogfreq);
+	r[Rstc] = Password | (r[Rstc] & ~CfgMask) | CfgReset;
+}
+
+void
+wdogoff(void)
+{
+	u32int *r;
+
+	r = (u32int*)POWERREGS;
+	r[Rstc] = Password | (r[Rstc] & ~CfgMask);
+}
+
+
+char *
+cputype2name(char *buf, int size)
+{
+	u32int r;
+	uint part;
+	char *p;
+
+	r = cpidget();			/* main id register */
+	assert((r >> 24) == 'A');
+	part = (r >> 4) & MASK(12);
+	switch(part){
+	case 0xc07:
+		p = seprint(buf, buf + size, "Cortex-A7");
+		break;
+	case 0xd03:
+		p = seprint(buf, buf + size, "Cortex-A53");
+		break;
+	default:
+		p = seprint(buf, buf + size, "Unknown-%#x", part);
+		break;
+	}
+	seprint(p, buf + size, " r%ldp%ld",
+		(r >> 20) & MASK(4), r & MASK(4));
+	return buf;
+}
+
+void
+cpuidprint(void)
+{
+	char name[64];
+
+	cputype2name(name, sizeof name);
+	delay(50);				/* let uart catch up */
+	print("cpu%d: %dMHz ARM %s\n", m->machno, m->cpumhz, name);
+}
+
+int
+getncpus(void)
+{
+	int n, max;
+	char *p;
+
+	n = 4;
+	if(n > MAXMACH)
+		n = MAXMACH;
+	p = getconf("*ncpu");
+	if(p && (max = atoi(p)) > 0 && n > max)
+		n = max;
+	return n;
+}
+
+static int
+startcpu(uint cpu)
+{
+	Mboxes *mb;
+	int i;
+	void cpureset();
+
+	mb = (Mboxes*)(ARMLOCAL + Mboxregs);
+	if(mb->clr[cpu].startcpu)
+		return -1;
+	mb->set[cpu].startcpu = PADDR(cpureset);
+	coherence();
+	sev();
+	for(i = 0; i < 1000; i++)
+		if(mb->clr[cpu].startcpu == 0)
+			return 0;
+	mb->clr[cpu].startcpu = PADDR(cpureset);
+	mb->set[cpu].doorbell = 1;
+	return 0;
+}
+
+void
+mboxclear(uint cpu)
+{
+	Mboxes *mb;
+
+	mb = (Mboxes*)(ARMLOCAL + Mboxregs);
+	mb->clr[cpu].mbox1 = 1;
+}
+
+void
+wakecpu(uint cpu)
+{
+	Mboxes *mb;
+
+	mb = (Mboxes*)(ARMLOCAL + Mboxregs);
+	mb->set[cpu].mbox1 = 1;
+}
+
+int
+startcpus(uint ncpu)
+{
+	int i, timeout;
+
+	for(i = 0; i < ncpu; i++)
+		lock(&startlock[i]);
+	cachedwbse(startlock, sizeof startlock);
+	for(i = 1; i < ncpu; i++){
+		if(startcpu(i) < 0)
+			return i;
+		timeout = 10000000;
+		while(!canlock(&startlock[i]))
+			if(--timeout == 0)
+				return i;
+		unlock(&startlock[i]);
+	}
+	return ncpu;
+}
+
+void
+archbcm2link(void)
+{
+	addclock0link(wdogfeed, HZ);
+}
+
+int
+l2ap(int ap)
+{
+	return (AP(0, (ap)));
+}
+
+void
+cpustart(int cpu)
+{
+	Mboxes *mb;
+
+	up = nil;
+	machinit();
+	mb = (Mboxes*)(ARMLOCAL + Mboxregs);
+	mb->clr[cpu].doorbell = 1;
+	trapinit();
+	clockinit();
+	mmuinit1();
+	timersinit();
+	cpuidprint();
+	archreset();
+	active.machs[m->machno] = 1;
+	unlock(&startlock[cpu]);
+	schedinit();
+	panic("schedinit returned");
+}
--- a/sys/src/9/bcm/arm.h
+++ b/sys/src/9/bcm/arm.h
@@ -1,5 +1,5 @@
 /*
- * arm-specific definitions for armv6
+ * arm-specific definitions for armv6 (arm11), armv7 (cortex-a8 and -a7)
  * these are used in C and assembler
  */
 
@@ -12,6 +12,7 @@
 #define PsrMsvc		0x00000013	/* `protected mode for OS' */
 #define PsrMmon		0x00000016	/* `secure monitor' (trustzone hyper) */
 #define PsrMabt		0x00000017
+#define PsrMhyp		0x0000001A
 #define PsrMund		0x0000001B
 #define PsrMsys		0x0000001F	/* `privileged user mode for OS' (trustzone) */
 #define PsrMask		0x0000001F
@@ -52,9 +53,19 @@
 #define CpTLD		10			/* TLB Lockdown, with op2 */
 #define CpVECS		12			/* vector bases, op1==0, Crm==0, op2s (cortex) */
 #define	CpPID		13			/* Process ID */
+#define	CpTIMER		14			/* Generic timer (cortex-a7) */
 #define CpSPM		15			/* system performance monitor (arm1176) */
 
 /*
+ * CpTIMER op1==0 Crm and opcode2 registers (cortex-a7)
+ */
+#define	CpTIMERcntfrq	0
+#define CpTIMERphys		2
+
+#define CpTIMERphysval	0
+#define CpTIMERphysctl	1
+
+/*
  * CpTTB op1==0, Crm==0 opcode2 values.
  */
 #define CpTTB0		0
@@ -71,6 +82,7 @@
  * CpID Secondary (CRm) registers.
  */
 #define CpIDidct	0
+#define	CpIDfeat	1
 
 /*
  * CpID op1==0 opcode2 fields.
@@ -80,6 +92,7 @@
 #define CpIDct		1			/* cache type */
 #define CpIDtlb		3			/* tlb type (cortex) */
 #define CpIDmpid	5			/* multiprocessor id (cortex) */
+#define	CpIDrevid	6			/* extra revision ID */
 
 /* CpIDid op1 values */
 #define CpIDcsize	1			/* cache size (cortex) */
@@ -133,6 +146,10 @@
 #define CpACasa			(1<<4)	/* enable speculative accesses */
 #define CpACl1pe		(1<<3)	/* l1 cache parity enable */
 #define CpACl2en		(1<<1)	/* l2 cache enable; default 1 */
+
+/* cortex-a7 and cortex-a9 */
+#define CpACsmp			(1<<6)	/* SMP l1 caches coherence; needed for ldrex/strex */
+#define CpACl1pctl		(3<<13)	/* l1 prefetch control */
 /*
  * CpCONTROL Secondary (CRm) registers and opcode2 fields.
  */
@@ -151,9 +168,9 @@
 #define CpCACHEinvd	6			/* data or unified */
 #define CpCACHEinvu	7			/* unified (not on cortex) */
 #define CpCACHEva2pa	8			/* va -> pa translation (cortex) */
-#define CpCACHEwb	10			/* writeback */
-#define CpCACHEinvdse	11			/* data or unified by mva */
-#define CpCACHEwbi	14			/* writeback+invalidate */
+#define CpCACHEwb	10			/* writeback to PoC */
+#define CpCACHEwbu	11			/* writeback to PoU */
+#define CpCACHEwbi	14			/* writeback+invalidate (to PoC) */
 
 #define CpCACHEall	0			/* entire (not for invd nor wb(i) on cortex) */
 #define CpCACHEse	1			/* single entry */
@@ -223,7 +240,7 @@
 #define CpVECSmon	1			/* secure monitor base addr */
 
 /*
- * CpSPM Secondary (CRm) registers and opcode2 fields.
+ * CpSPM Secondary (CRm) registers and opcode2 fields (armv6)
  */
 #define CpSPMperf	12			/* various counters */
 
@@ -239,6 +256,21 @@
 #define CpCACHERANGEdwbi	14		/* writeback+invalidate */
 
 /*
+ * CpTTB cache control bits
+ */
+#define CpTTBnos	(1<<5)	/* only Inner cache shareable */
+#define CpTTBinc	(0<<0|0<<6)	/* inner non-cacheable */
+#define CpTTBiwba	(0<<0|1<<6)	/* inner write-back write-allocate */
+#define CpTTBiwt	(1<<0|0<<6)	/* inner write-through */
+#define CpTTBiwb	(1<<0|1<<6)	/* inner write-back no write-allocate */
+#define CpTTBonc	(0<<3)	/* outer non-cacheable */
+#define CpTTBowba	(1<<3)	/* outer write-back write-allocate */
+#define CpTTBowt	(2<<3)	/* outer write-through */
+#define CpTTBowb	(3<<3)	/* outer write-back no write-allocate */
+#define CpTTBs	(1<<1)	/* page table in shareable memory */
+#define CpTTBbase	~0x7F		/* mask off control bits */
+
+/*
  * MMU page table entries.
  * Mbz (0x10) bit is implementation-defined and must be 0 on the cortex.
  */
@@ -256,6 +288,15 @@
 #define Cached		0x00000008		/* L[12] */
 #define Dom0		0
 
+#define L1wralloc	(1<<12)			/* L1 TEX */
+#define L1sharable	(1<<16)
+#define L2wralloc	(1<<6)			/* L2 TEX (small pages) */
+#define L2sharable	(1<<10)
+
+/* attributes for memory containing locks -- differs between armv6 and armv7 */
+//#define L1ptedramattrs	(Cached | Buffered | L1wralloc | L1sharable)
+//#define L2ptedramattrs	(Cached | Buffered | L2wralloc | L2sharable)
+
 #define Noaccess	0			/* AP, DAC */
 #define Krw		1			/* AP */
 /* armv7 deprecates AP[2] == 1 & AP[1:0] == 2 (Uro), prefers 3 (new in v7) */
@@ -267,7 +308,7 @@
 #define F(v, o, w)	(((v) & ((1<<(w))-1))<<(o))
 #define AP(n, v)	F((v), ((n)*2)+4, 2)
 #define L1AP(ap)	(AP(3, (ap)))
-#define L2AP(ap) (AP(3, (ap))|AP(2, (ap))|AP(1, (ap))|AP(0, (ap))) /* pre-armv7 */
+/* L2AP differs between armv6 and armv7 -- see l2ap in arch*.c */
 #define DAC(n, v)	F((v), (n)*2, 2)
 
 #define HVECTORS	0xffff0000
--- a/sys/src/9/bcm/arm.s
+++ b/sys/src/9/bcm/arm.s
@@ -1,5 +1,5 @@
 /*
- * armv6 machine assist, definitions
+ * armv6/v7 machine assist, definitions
  *
  * loader uses R11 as scratch.
  */
@@ -11,8 +11,6 @@
 
 #define L1X(va)		(((((va))>>20) & 0x0fff)<<2)
 
-#define PTEDRAM		(Dom0|L1AP(Krw)|Section|Cached|Buffered)
-
 /*
  * new instructions
  */
@@ -25,12 +23,32 @@
 	MOVW	$0, R0; \
 	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEwait
 
-#define	BARRIERS	ISB; DSB
+#define	BARRIERS	DSB; ISB
 
 #define MCRR(coproc, op, rd, rn, crm) \
 	WORD $(0xec400000|(rn)<<16|(rd)<<12|(coproc)<<8|(op)<<4|(crm))
+#define MRRC(coproc, op, rd, rn, crm) \
+	WORD $(0xec500000|(rn)<<16|(rd)<<12|(coproc)<<8|(op)<<4|(crm))
+#define MSR(R, rn, m, m1) \
+	WORD $(0xe120f200|(R)<<22|(m1)<<16|(m)<<8|(rn))
 
+#define CPSIE	WORD	$0xf1080080	/* intr enable: zeroes I bit */
+#define CPSID	WORD	$0xf10c0080	/* intr disable: sets I bit */
+
 #define OKAY \
 	MOVW	$0x7E200028,R2; \
 	MOVW	$0x10000,R3; \
 	MOVW	R3,(R2)
+
+#define PUTC(s)
+
+/*
+ * get cpu id, or zero if armv6
+ */
+#define CPUID(r) \
+	MRC	CpSC, 0, r, C(CpID), C(CpIDfeat), 7; \
+	CMP	$0, r; \
+	B.EQ	2(PC); \
+	MRC	CpSC, 0, r, C(CpID), C(CpIDidct), CpIDmpid; \
+	AND.S	$(MAXMACH-1), r
+
--- /dev/null
+++ b/sys/src/9/bcm/armv6.s
@@ -1,0 +1,324 @@
+/*
+ * Broadcom bcm2835 SoC, as used in Raspberry Pi
+ * arm1176jzf-s processor (armv6)
+ */
+
+#include "arm.s"
+
+#define CACHELINESZ 32
+
+TEXT armstart(SB), 1, $-4
+
+	/*
+	 * SVC mode, interrupts disabled
+	 */
+	MOVW	$(PsrDirq|PsrDfiq|PsrMsvc), R1
+	MOVW	R1, CPSR
+
+	/*
+	 * disable the mmu and L1 caches
+	 * invalidate caches and tlb
+	 */
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	BIC	$(CpCdcache|CpCicache|CpCpredict|CpCmmu), R1
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvu), CpCACHEall
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
+	ISB
+
+	/*
+	 * clear mach and page tables
+	 */
+	MOVW	$PADDR(MACHADDR), R1
+	MOVW	$PADDR(KTZERO), R2
+_ramZ:
+	MOVW	R0, (R1)
+	ADD	$4, R1
+	CMP	R1, R2
+	BNE	_ramZ
+
+	/*
+	 * start stack at top of mach (physical addr)
+	 * set up page tables for kernel
+	 */
+	MOVW	$PADDR(MACHADDR+MACHSIZE-4), R13
+	MOVW	$PADDR(L1), R0
+	BL	,mmuinit(SB)
+
+	/*
+	 * set up domain access control and page table base
+	 */
+	MOVW	$Client, R1
+	MCR	CpSC, 0, R1, C(CpDAC), C(0)
+	MOVW	$PADDR(L1), R1
+	MCR	CpSC, 0, R1, C(CpTTB), C(0)
+
+	/*
+	 * enable caches, mmu, and high vectors
+	 */
+	MRC	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	ORR	$(CpChv|CpCdcache|CpCicache|CpCpredict|CpCmmu), R0
+	MCR	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	ISB
+
+	/*
+	 * switch SB, SP, and PC into KZERO space
+	 */
+	MOVW	$setR12(SB), R12
+	MOVW	$(MACHADDR+MACHSIZE-4), R13
+	MOVW	$_startpg(SB), R15
+
+TEXT _startpg(SB), 1, $-4
+
+	/*
+	 * enable cycle counter
+	 */
+	MOVW	$1, R1
+	MCR	CpSC, 0, R1, C(CpSPM), C(CpSPMperf), CpSPMctl
+
+	/*
+	 * call main and loop forever if it returns
+	 */
+	BL	,main(SB)
+	B	,0(PC)
+
+	BL	_div(SB)		/* hack to load _div, etc. */
+
+TEXT cpidget(SB), 1, $-4			/* main ID */
+	MRC	CpSC, 0, R0, C(CpID), C(0), CpIDid
+	RET
+
+TEXT fsrget(SB), 1, $-4				/* data fault status */
+	MRC	CpSC, 0, R0, C(CpFSR), C(0), CpFSRdata
+	RET
+
+TEXT ifsrget(SB), 1, $-4			/* instruction fault status */
+	MRC	CpSC, 0, R0, C(CpFSR), C(0), CpFSRinst
+	RET
+
+TEXT farget(SB), 1, $-4				/* fault address */
+	MRC	CpSC, 0, R0, C(CpFAR), C(0x0)
+	RET
+
+TEXT lcycles(SB), 1, $-4
+	MRC	CpSC, 0, R0, C(CpSPM), C(CpSPMperf), CpSPMcyc
+	RET
+
+TEXT splhi(SB), 1, $-4
+	MOVW	$(MACHADDR+4), R2		/* save caller pc in Mach */
+	MOVW	R14, 0(R2)
+
+	MOVW	CPSR, R0			/* turn off irqs (but not fiqs) */
+	ORR	$(PsrDirq), R0, R1
+	MOVW	R1, CPSR
+	RET
+
+TEXT splfhi(SB), 1, $-4
+	MOVW	$(MACHADDR+4), R2		/* save caller pc in Mach */
+	MOVW	R14, 0(R2)
+
+	MOVW	CPSR, R0			/* turn off irqs and fiqs */
+	ORR	$(PsrDirq|PsrDfiq), R0, R1
+	MOVW	R1, CPSR
+	RET
+
+TEXT splflo(SB), 1, $-4
+	MOVW	CPSR, R0			/* turn on fiqs */
+	BIC	$(PsrDfiq), R0, R1
+	MOVW	R1, CPSR
+	RET
+
+TEXT spllo(SB), 1, $-4
+	MOVW	CPSR, R0			/* turn on irqs and fiqs */
+	BIC	$(PsrDirq|PsrDfiq), R0, R1
+	MOVW	R1, CPSR
+	RET
+
+TEXT splx(SB), 1, $-4
+	MOVW	$(MACHADDR+0x04), R2		/* save caller pc in Mach */
+	MOVW	R14, 0(R2)
+
+	MOVW	R0, R1				/* reset interrupt level */
+	MOVW	CPSR, R0
+	MOVW	R1, CPSR
+	RET
+
+TEXT spldone(SB), 1, $0				/* end marker for devkprof.c */
+	RET
+
+TEXT islo(SB), 1, $-4
+	MOVW	CPSR, R0
+	AND	$(PsrDirq), R0
+	EOR	$(PsrDirq), R0
+	RET
+
+TEXT	tas(SB), $-4
+TEXT	_tas(SB), $-4
+	MOVW	R0,R1
+	MOVW	$1,R0
+	SWPW	R0,(R1)			/* fix: deprecated in armv6 */
+	RET
+
+TEXT setlabel(SB), 1, $-4
+	MOVW	R13, 0(R0)		/* sp */
+	MOVW	R14, 4(R0)		/* pc */
+	MOVW	$0, R0
+	RET
+
+TEXT gotolabel(SB), 1, $-4
+	MOVW	0(R0), R13		/* sp */
+	MOVW	4(R0), R14		/* pc */
+	MOVW	$1, R0
+	RET
+
+TEXT getcallerpc(SB), 1, $-4
+	MOVW	0(R13), R0
+	RET
+
+TEXT idlehands(SB), $-4
+	MOVW	CPSR, R3
+	ORR	$(PsrDirq|PsrDfiq), R3, R1		/* splfhi */
+	MOVW	R1, CPSR
+
+	DSB
+	MOVW	nrdy(SB), R0
+	CMP	$0, R0
+	MCR.EQ	CpSC, 0, R0, C(CpCACHE), C(CpCACHEintr), CpCACHEwait
+	DSB
+
+	MOVW	R3, CPSR			/* splx */
+	RET
+
+
+TEXT coherence(SB), $-4
+	BARRIERS
+	RET
+
+/*
+ * invalidate tlb
+ */
+TEXT mmuinvalidate(SB), 1, $-4
+	MOVW	$0, R0
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
+	BARRIERS
+	MCR CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEflushbtc
+	RET
+
+/*
+ * mmuinvalidateaddr(va)
+ *   invalidate tlb entry for virtual page address va, ASID 0
+ */
+TEXT mmuinvalidateaddr(SB), 1, $-4
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinvse
+	BARRIERS
+	RET
+
+/*
+ * drain write buffer
+ * writeback data cache
+ */
+TEXT cachedwb(SB), 1, $-4
+	DSB
+	MOVW	$0, R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEall
+	RET
+
+/*
+ * drain write buffer
+ * writeback and invalidate data cache
+ */
+TEXT cachedwbinv(SB), 1, $-4
+	DSB
+	MOVW	$0, R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwbi), CpCACHEall
+	RET
+
+/*
+ * cachedwbinvse(va, n)
+ *   drain write buffer
+ *   writeback and invalidate data cache range [va, va+n)
+ */
+TEXT cachedwbinvse(SB), 1, $-4
+	MOVW	R0, R1		/* DSB clears R0 */
+	DSB
+	MOVW	n+4(FP), R2
+	ADD	R1, R2
+	SUB	$1, R2
+	BIC	$(CACHELINESZ-1), R1
+	BIC	$(CACHELINESZ-1), R2
+	MCRR(CpSC, 0, 2, 1, CpCACHERANGEdwbi)
+	RET
+
+/*
+ * cachedwbse(va, n)
+ *   drain write buffer
+ *   writeback data cache range [va, va+n)
+ */
+TEXT cachedwbtlb(SB), 1, $-4
+TEXT cachedwbse(SB), 1, $-4
+
+	MOVW	R0, R1		/* DSB clears R0 */
+	DSB
+	MOVW	n+4(FP), R2
+	ADD	R1, R2
+	BIC	$(CACHELINESZ-1), R1
+	BIC	$(CACHELINESZ-1), R2
+	MCRR(CpSC, 0, 2, 1, CpCACHERANGEdwb)
+	RET
+
+/*
+ * cachedinvse(va, n)
+ *   drain write buffer
+ *   invalidate data cache range [va, va+n)
+ */
+TEXT cachedinvse(SB), 1, $-4
+	MOVW	R0, R1		/* DSB clears R0 */
+	DSB
+	MOVW	n+4(FP), R2
+	ADD	R1, R2
+	SUB	$1, R2
+	BIC	$(CACHELINESZ-1), R1
+	BIC	$(CACHELINESZ-1), R2
+	MCRR(CpSC, 0, 2, 1, CpCACHERANGEinvd)
+	RET
+
+/*
+ * drain write buffer and prefetch buffer
+ * writeback and invalidate data cache
+ * invalidate instruction cache
+ */
+TEXT cacheuwbinv(SB), 1, $-4
+	BARRIERS
+	MOVW	$0, R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwbi), CpCACHEall
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall
+	RET
+
+/*
+ * L2 cache is not enabled
+ */
+TEXT l2cacheuwbinv(SB), 1, $-4
+	RET
+
+/*
+ * invalidate instruction cache
+ */
+TEXT cacheiinv(SB), 1, $-4
+	MOVW	$0, R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall
+	RET
+
+/*
+ * invalidate range of instruction cache
+ */
+TEXT cacheiinvse(SB), 1, $-4
+	MOVW	R0, R1		/* DSB clears R0 */
+	DSB
+	MOVW n+4(FP), R2
+	ADD	R1, R2
+	SUB	$1, R2
+	MCRR(CpSC, 0, 2, 1, CpCACHERANGEinvi)
+	MCR CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEflushbtc
+	DSB
+	ISB
+	RET
--- /dev/null
+++ b/sys/src/9/bcm/armv7.s
@@ -1,0 +1,510 @@
+/*
+ * Broadcom bcm2836 SoC, as used in Raspberry Pi 2
+ * 4 x Cortex-A7 processor (armv7)
+ */
+
+#include "arm.s"
+
+#define CACHELINESZ 	64
+#define ICACHELINESZ	32
+
+#undef DSB
+#undef DMB
+#undef ISB
+#define DSB	WORD	$0xf57ff04f	/* data synch. barrier; last f = SY */
+#define DMB	WORD	$0xf57ff05f	/* data mem. barrier; last f = SY */
+#define ISB	WORD	$0xf57ff06f	/* instr. sync. barrier; last f = SY */
+#define WFI	WORD	$0xe320f003	/* wait for interrupt */
+#define WFI_EQ	WORD	$0x0320f003	/* wait for interrupt if eq */
+#define ERET	WORD	$0xe160006e	/* exception return from HYP */
+#define SEV	WORD	$0xe320f004	/* send event */
+
+/* tas/cas strex debugging limits; started at 10000 */
+#define MAXSC 1000000
+
+TEXT armstart(SB), 1, $-4
+
+	/*
+	 * if not cpu0, go to secondary startup
+	 */
+	CPUID(R1)
+	BNE	reset
+
+	/*
+	 * go to SVC mode, interrupts disabled
+	 */
+	BL	svcmode(SB)
+
+	/*
+	 * disable the mmu and caches
+	 */
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	BIC	$(CpCdcache|CpCicache|CpCmmu), R1
+	ORR	$(CpCsbo|CpCsw), R1
+	BIC	$CpCsbz, R1
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	BARRIERS
+
+	/*
+	 * clear mach and page tables
+	 */
+	MOVW	$PADDR(MACHADDR), R1
+	MOVW	$PADDR(KTZERO), R2
+_ramZ:
+	MOVW	R0, (R1)
+	ADD	$4, R1
+	CMP	R1, R2
+	BNE	_ramZ
+
+	/*
+	 * turn SMP on
+	 * invalidate tlb
+	 */
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	ORR	$CpACsmp, R1		/* turn SMP on */
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	BARRIERS
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
+	BARRIERS
+
+	/*
+	 * start stack at top of mach (physical addr)
+	 * set up page tables for kernel
+	 */
+	MOVW	$PADDR(MACHADDR+MACHSIZE-4), R13
+	MOVW	$PADDR(L1), R0
+	BL	mmuinit(SB)
+
+	/*
+	 * set up domain access control and page table base
+	 */
+	MOVW	$Client, R1
+	MCR	CpSC, 0, R1, C(CpDAC), C(0)
+	MOVW	$PADDR(L1), R1
+	ORR	$(CpTTBs|CpTTBowba|CpTTBiwba), R1
+	MCR	CpSC, 0, R1, C(CpTTB), C(0)
+	MCR	CpSC, 0, R1, C(CpTTB), C(0), CpTTB1	/* cortex has two */
+
+	/*
+	 * invalidate my caches before enabling
+	 */
+	BL	cachedinv(SB)
+	BL	cacheiinv(SB)
+	BL	l2cacheuinv(SB)
+	BARRIERS
+
+	/*
+	 * enable caches, mmu, and high vectors
+	 */
+
+	MRC	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	ORR	$(CpChv|CpCdcache|CpCicache|CpCmmu), R0
+	MCR	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	BARRIERS
+
+	/*
+	 * switch SB, SP, and PC into KZERO space
+	 */
+	MOVW	$setR12(SB), R12
+	MOVW	$(MACHADDR+MACHSIZE-4), R13
+	MOVW	$_startpg(SB), R15
+
+TEXT _startpg(SB), 1, $-4
+
+	/*
+	 * enable cycle counter
+	 */
+	MOVW	$(1<<31), R1
+	MCR	CpSC, 0, R1, C(CpCLD), C(CpCLDena), CpCLDenacyc
+	MOVW	$1, R1
+	MCR	CpSC, 0, R1, C(CpCLD), C(CpCLDena), CpCLDenapmnc
+
+	/*
+	 * call main and loop forever if it returns
+	 */
+	BL	,main(SB)
+	B	,0(PC)
+
+	BL	_div(SB)		/* hack to load _div, etc. */
+
+/*
+ * startup entry for cpu(s) other than 0
+ */
+TEXT cpureset(SB), 1, $-4
+reset:
+	/*
+	 * load physical base for SB addressing while mmu is off
+	 * keep a handy zero in R0 until first function call
+	 */
+	MOVW	$setR12(SB), R12
+	SUB	$KZERO, R12
+	ADD	$PHYSDRAM, R12
+	MOVW	$0, R0
+
+	/*
+	 * SVC mode, interrupts disabled
+	 */
+	BL	svcmode(SB)
+
+	/*
+	 * disable the mmu and caches
+	 */
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	BIC	$(CpCdcache|CpCicache|CpCmmu), R1
+	ORR	$(CpCsbo|CpCsw), R1
+	BIC	$CpCsbz, R1
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	BARRIERS
+
+	/*
+	 * turn SMP on
+	 * invalidate tlb
+	 */
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	ORR	$CpACsmp, R1		/* turn SMP on */
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpAuxctl
+	BARRIERS
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
+	BARRIERS
+
+	/*
+	 * find Mach for this cpu
+	 */
+	MRC	CpSC, 0, R2, C(CpID), C(CpIDidct), CpIDmpid
+	AND	$(MAXMACH-1), R2	/* mask out non-cpu-id bits */
+	SLL	$2, R2			/* convert to word index */
+	MOVW	$machaddr(SB), R0
+	ADD	R2, R0			/* R0 = &machaddr[cpuid] */
+	MOVW	(R0), R0		/* R0 = machaddr[cpuid] */
+	CMP	$0, R0
+	BEQ	0(PC)			/* must not be zero */
+	SUB	$KZERO, R0, R(MACH)	/* m = PADDR(machaddr[cpuid]) */
+
+	/*
+	 * start stack at top of local Mach
+	 */
+	ADD	$(MACHSIZE-4), R(MACH), R13
+
+	/*
+	 * set up domain access control and page table base
+	 */
+	MOVW	$Client, R1
+	MCR	CpSC, 0, R1, C(CpDAC), C(0)
+	MOVW	12(R(MACH)), R1	/* m->mmul1 */
+	SUB	$KZERO, R1		/* phys addr */
+	ORR	$(CpTTBs|CpTTBowba|CpTTBiwba), R1
+	MCR	CpSC, 0, R1, C(CpTTB), C(0)
+	MCR	CpSC, 0, R1, C(CpTTB), C(0), CpTTB1	/* cortex has two */
+
+	/*
+	 * invalidate my caches before enabling
+	 */
+	BL	cachedinv(SB)
+	BL	cacheiinv(SB)
+	BARRIERS
+
+	/*
+	 * enable caches, mmu, and high vectors
+	 */
+	MRC	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	ORR	$(CpChv|CpCdcache|CpCicache|CpCmmu), R0
+	MCR	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
+	BARRIERS
+
+	/*
+	 * switch MACH, SB, SP, and PC into KZERO space
+	 */
+	ADD	$KZERO, R(MACH)
+	MOVW	$setR12(SB), R12
+	ADD	$KZERO, R13
+	MOVW	$_startpg2(SB), R15
+
+TEXT _startpg2(SB), 1, $-4
+
+	/*
+	 * enable cycle counter
+	 */
+	MOVW	$(1<<31), R1
+	MCR	CpSC, 0, R1, C(CpCLD), C(CpCLDena), CpCLDenacyc
+	MOVW	$1, R1
+	MCR	CpSC, 0, R1, C(CpCLD), C(CpCLDena), CpCLDenapmnc
+
+	/*
+	 * call cpustart and loop forever if it returns
+	 */
+	MRC	CpSC, 0, R0, C(CpID), C(CpIDidct), CpIDmpid
+	AND	$(MAXMACH-1), R0		/* mask out non-cpu-id bits */
+	BL	,cpustart(SB)
+	B	,0(PC)
+
+/*
+ * get into SVC mode with interrupts disabled
+ * raspberry pi firmware since 29 Sept 2015 starts in HYP mode
+ */
+TEXT svcmode(SB), 1, $-4
+	MOVW	CPSR, R1
+	AND	$PsrMask, R1
+	MOVW	$PsrMhyp, R2
+	CMP	R2, R1
+	MOVW	$(PsrDirq|PsrDfiq|PsrMsvc), R1
+	BNE	nothyp
+	MSR(1, 1, 1, 0xe)	/* MOVW	R1, SPSR_HYP */
+	MSR(0, 14, 1, 0xe)	/* MOVW	R14, ELR_HYP */
+	ERET
+nothyp:
+	MOVW	R1, CPSR
+	RET
+
+TEXT cpidget(SB), 1, $-4			/* main ID */
+	MRC	CpSC, 0, R0, C(CpID), C(0), CpIDid
+	RET
+
+TEXT fsrget(SB), 1, $-4				/* data fault status */
+	MRC	CpSC, 0, R0, C(CpFSR), C(0), CpFSRdata
+	RET
+
+TEXT ifsrget(SB), 1, $-4			/* instruction fault status */
+	MRC	CpSC, 0, R0, C(CpFSR), C(0), CpFSRinst
+	RET
+
+TEXT farget(SB), 1, $-4				/* fault address */
+	MRC	CpSC, 0, R0, C(CpFAR), C(0x0)
+	RET
+
+TEXT cpctget(SB), 1, $-4			/* cache type */
+	MRC	CpSC, 0, R0, C(CpID), C(CpIDidct), CpIDct
+	RET
+
+TEXT lcycles(SB), 1, $-4
+	MRC	CpSC, 0, R0, C(CpCLD), C(CpCLDcyc), 0
+	RET
+
+TEXT splhi(SB), 1, $-4
+	MOVW	R14, 4(R(MACH))		/* save caller pc in m->splpc */
+
+	MOVW	CPSR, R0			/* turn off irqs (but not fiqs) */
+	ORR	$(PsrDirq), R0, R1
+	MOVW	R1, CPSR
+	RET
+
+TEXT splfhi(SB), 1, $-4
+	MOVW	R14, 4(R(MACH))		/* save caller pc in m->splpc */
+
+	MOVW	CPSR, R0			/* turn off irqs and fiqs */
+	ORR	$(PsrDirq|PsrDfiq), R0, R1
+	MOVW	R1, CPSR
+	RET
+
+TEXT splflo(SB), 1, $-4
+	MOVW	CPSR, R0			/* turn on fiqs */
+	BIC	$(PsrDfiq), R0, R1
+	MOVW	R1, CPSR
+	RET
+
+TEXT spllo(SB), 1, $-4
+	MOVW	CPSR, R0			/* turn on irqs and fiqs */
+	MOVW	$0, R1
+	CMP.S	R1, R(MACH)
+	MOVW.NE	R1, 4(R(MACH))			/* clear m->splpc */
+	BIC	$(PsrDirq|PsrDfiq), R0, R1
+	MOVW	R1, CPSR
+	RET
+
+TEXT splx(SB), 1, $-4
+	MOVW	R14, 4(R(MACH))		/* save caller pc in m->splpc */
+
+	MOVW	R0, R1				/* reset interrupt level */
+	MOVW	CPSR, R0
+	MOVW	R1, CPSR
+	RET
+
+TEXT spldone(SB), 1, $0				/* end marker for devkprof.c */
+	RET
+
+TEXT islo(SB), 1, $-4
+	MOVW	CPSR, R0
+	AND	$(PsrDirq), R0
+	EOR	$(PsrDirq), R0
+	RET
+
+TEXT cas(SB), $0
+TEXT cmpswap(SB), $0
+	MOVW	ov+4(FP), R1
+	MOVW	nv+8(FP), R2
+spincas:
+	LDREX	(R0), R3
+	CMP.S	R3, R1
+	BNE	fail
+	STREX	R2, (R0), R4
+	CMP.S	$0, R4
+	BNE	spincas
+	MOVW	$1, R0
+	DMB
+	RET
+fail:
+	CLREX
+	MOVW	$0, R0
+	RET
+
+TEXT	tas(SB), $-4
+TEXT	_tas(SB), $-4			/* _tas(ulong *) */
+	/* returns old (R0) after modifying (R0) */
+	MOVW	R0,R5
+	DMB
+
+	MOVW	$1,R2		/* new value of (R0) */
+	MOVW	$MAXSC, R8
+tas1:
+	LDREX (R5), R7
+	CMP.S	$0, R7		/* old value non-zero (lock taken)? */
+	BNE	lockbusy	/* we lose */
+	SUB.S	$1, R8
+	BEQ	lockloop2
+	STREX R2,(R5),R4
+	CMP.S	$0, R4
+	BNE	tas1		/* strex failed? try again */
+	DMB
+	B	tas0
+lockloop2:
+	BL	abort(SB)
+lockbusy:
+	CLREX
+tas0:
+	MOVW	R7, R0		/* return old value */
+	RET
+
+TEXT setlabel(SB), 1, $-4
+	MOVW	R13, 0(R0)		/* sp */
+	MOVW	R14, 4(R0)		/* pc */
+	MOVW	$0, R0
+	RET
+
+TEXT gotolabel(SB), 1, $-4
+	MOVW	0(R0), R13		/* sp */
+	MOVW	4(R0), R14		/* pc */
+	MOVW	$1, R0
+	RET
+
+TEXT getcallerpc(SB), 1, $-4
+	MOVW	0(R13), R0
+	RET
+
+TEXT idlehands(SB), $-4
+	MOVW	CPSR, R3
+	ORR	$(PsrDirq|PsrDfiq), R3, R1		/* splfhi */
+	MOVW	R1, CPSR
+
+	DSB
+	MOVW	nrdy(SB), R0
+	CMP	$0, R0
+	WFI_EQ
+	DSB
+
+	MOVW	R3, CPSR			/* splx */
+	RET
+
+
+TEXT coherence(SB), $-4
+	BARRIERS
+	RET
+
+TEXT sev(SB), $-4
+	SEV
+	RET
+
+/*
+ * invalidate tlb
+ */
+TEXT mmuinvalidate(SB), 1, $-4
+	DSB
+	MOVW	$0, R0
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
+	BARRIERS
+	RET
+
+/*
+ * mmuinvalidateaddr(va)
+ *   invalidate tlb entry for virtual page address va, ASID 0
+ */
+TEXT mmuinvalidateaddr(SB), 1, $-4
+	DSB
+	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinvse
+	BARRIERS
+	RET
+
+/*
+ * `single-element' cache operations.
+ * in arm arch v7, if effective to PoC, they operate on all cache levels, so separate
+ * l2 functions are unnecessary.
+ */
+
+TEXT cachedwbse(SB), $-4			/* D writeback SE */
+	MOVW	R0, R2
+
+	MOVW	CPSR, R3
+	CPSID					/* splhi */
+
+	BARRIERS			/* force outstanding stores to cache */
+	MOVW	R2, R0
+	MOVW	4(FP), R1
+	ADD	R0, R1				/* R1 is end address */
+	BIC	$(CACHELINESZ-1), R0		/* cache line start */
+_dwbse:
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEse
+	/* can't have a BARRIER here since it zeroes R0 */
+	ADD	$CACHELINESZ, R0
+	CMP.S	R0, R1
+	BGT	_dwbse
+	B	_wait
+
+/*
+ * TLB on armv7 loads from cache, so no need for writeback
+ */
+TEXT cachedwbtlb(SB), $-4
+	DSB
+	ISB
+	RET
+
+TEXT cachedwbinvse(SB), $-4			/* D writeback+invalidate SE */
+	MOVW	R0, R2
+
+	MOVW	CPSR, R3
+	CPSID					/* splhi */
+
+	BARRIERS			/* force outstanding stores to cache */
+	MOVW	R2, R0
+	MOVW	4(FP), R1
+	ADD	R0, R1				/* R1 is end address */
+	BIC	$(CACHELINESZ-1), R0		/* cache line start */
+_dwbinvse:
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwbi), CpCACHEse
+	/* can't have a BARRIER here since it zeroes R0 */
+	ADD	$CACHELINESZ, R0
+	CMP.S	R0, R1
+	BGT	_dwbinvse
+_wait:						/* drain write buffer */
+	BARRIERS
+
+	MOVW	R3, CPSR			/* splx */
+	RET
+
+TEXT cachedinvse(SB), $-4			/* D invalidate SE */
+	MOVW	R0, R2
+
+	MOVW	CPSR, R3
+	CPSID					/* splhi */
+
+	BARRIERS			/* force outstanding stores to cache */
+	MOVW	R2, R0
+	MOVW	4(FP), R1
+	ADD	R0, R1				/* R1 is end address */
+	BIC	$(CACHELINESZ-1), R0		/* cache line start */
+_dinvse:
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvd), CpCACHEse
+	/* can't have a BARRIER here since it zeroes R0 */
+	ADD	$CACHELINESZ, R0
+	CMP.S	R0, R1
+	BGT	_dinvse
+	B	_wait
+
+#include "cache.v7.s"
--- /dev/null
+++ b/sys/src/9/bcm/cache.v7.s
@@ -1,0 +1,220 @@
+/*
+ * cortex arm arch v7 cache flushing and invalidation
+ * shared by l.s and rebootcode.s
+ */
+
+#define	BPIALL	MCR CpSC, 0, R0, C(CpCACHE), C(5), 6	/* branch predictor invalidate all */
+
+TEXT cacheiinv(SB), $-4				/* I invalidate */
+	DSB
+	MOVW	$0, R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall /* ok on cortex */
+	BPIALL	/* redundant? */
+	DSB
+	ISB
+	RET
+
+TEXT cacheiinvse(SB), $0			/* I invalidate SE */
+	MOVW 4(FP), R1
+	ADD	R0, R1
+	BIC $(ICACHELINESZ - 1), R0
+	DSB
+_iinvse:
+	MCR CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEse
+	ADD $ICACHELINESZ, R0
+	CMP.S R0, R1
+	BGT _iinvse
+	BPIALL
+	DSB
+	ISB
+	RET
+
+/*
+ * set/way operators, passed a suitable set/way value in R0.
+ */
+TEXT cachedwb_sw(SB), $-4
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEsi
+	RET
+
+TEXT cachedwbinv_sw(SB), $-4
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwbi), CpCACHEsi
+	RET
+
+TEXT cachedinv_sw(SB), $-4
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvd), CpCACHEsi
+	RET
+
+	/* set cache size select */
+TEXT setcachelvl(SB), $-4
+	MCR	CpSC, CpIDcssel, R0, C(CpID), C(CpIDidct), 0
+	ISB
+	RET
+
+	/* return cache sizes */
+TEXT getwayssets(SB), $-4
+	MRC	CpSC, CpIDcsize, R0, C(CpID), C(CpIDidct), 0
+	RET
+
+/*
+ * l1 cache operations.
+ * l1 and l2 ops are intended to be called from C, thus need save no
+ * caller's regs, only those we need to preserve across calls.
+ */
+
+TEXT cachedwb(SB), $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	$cachedwb_sw(SB), R0
+	MOVW	$1, R8
+	BL	wholecache(SB)
+	MOVW.P	8(R13), R15
+
+TEXT cachedwbinv(SB), $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	$cachedwbinv_sw(SB), R0
+	MOVW	$1, R8
+	BL	wholecache(SB)
+	MOVW.P	8(R13), R15
+
+TEXT cachedinv(SB), $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	$cachedinv_sw(SB), R0
+	MOVW	$1, R8
+	BL	wholecache(SB)
+	MOVW.P	8(R13), R15
+
+TEXT cacheuwbinv(SB), $-4
+	MOVM.DB.W [R14], (R13)	/* save lr on stack */
+	MOVW	CPSR, R1
+	CPSID			/* splhi */
+
+	MOVM.DB.W [R1], (R13)	/* save R1 on stack */
+
+	BL	cachedwbinv(SB)
+	BL	cacheiinv(SB)
+
+	MOVM.IA.W (R13), [R1]	/* restore R1 (saved CPSR) */
+	MOVW	R1, CPSR
+	MOVM.IA.W (R13), [R14]	/* restore lr */
+	RET
+
+/*
+ * l2 cache operations
+ */
+
+TEXT l2cacheuwb(SB), $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	$cachedwb_sw(SB), R0
+	MOVW	$2, R8
+	BL	wholecache(SB)
+	MOVW.P	8(R13), R15
+
+TEXT l2cacheuwbinv(SB), $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	CPSR, R1
+	CPSID			/* splhi */
+
+	MOVM.DB.W [R1], (R13)	/* save R1 on stack */
+
+	MOVW	$cachedwbinv_sw(SB), R0
+	MOVW	$2, R8
+	BL	wholecache(SB)
+	BL	l2cacheuinv(SB)
+
+	MOVM.IA.W (R13), [R1]	/* restore R1 (saved CPSR) */
+	MOVW	R1, CPSR
+	MOVW.P	8(R13), R15
+
+TEXT l2cacheuinv(SB), $-4
+	MOVW.W	R14, -8(R13)
+	MOVW	$cachedinv_sw(SB), R0
+	MOVW	$2, R8
+	BL	wholecache(SB)
+	MOVW.P	8(R13), R15
+
+/*
+ * these shift values are for the Cortex-A8 L1 cache (A=2, L=6) and
+ * the Cortex-A8 L2 cache (A=3, L=6).
+ * A = log2(# of ways), L = log2(bytes per cache line).
+ * see armv7 arch ref p. 1403.
+ */
+#define L1WAYSH 30
+#define L1SETSH 6
+#define L2WAYSH 29
+#define L2SETSH 6
+
+/*
+ * callers are assumed to be the above l1 and l2 ops.
+ * R0 is the function to call in the innermost loop.
+ * R8 is the cache level (one-origin: 1 or 2).
+ *
+ * initial translation by 5c, then massaged by hand.
+ */
+TEXT wholecache+0(SB), $-4
+	MOVW	R0, R1		/* save argument for inner loop in R1 */
+	SUB	$1, R8		/* convert cache level to zero origin */
+
+	/* we may not have the MMU on yet, so map R1 to PC's space */
+	BIC	$KSEGM,	R1	/* strip segment from address */
+	MOVW	PC, R2		/* get PC's segment ... */
+	AND	$KSEGM, R2
+	ORR	R2, R1		/* combine them */
+
+	/* drain write buffers */
+	BARRIERS
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEwait
+	ISB
+
+	MOVW	CPSR, R2
+	MOVM.DB.W [R2,R14], (SP) /* save regs on stack */
+	CPSID			/* splhi to make entire op atomic */
+
+	/* get cache sizes */
+	SLL	$1, R8, R0	/* R0 = (cache - 1) << 1 */
+	MCR	CpSC, CpIDcssel, R0, C(CpID), C(CpIDidct), 0 /* set cache size select */
+	ISB
+	MRC	CpSC, CpIDcsize, R0, C(CpID), C(CpIDidct), 0 /* get cache sizes */
+
+	/* compute # of ways and sets for this cache level */
+	SRA	$3, R0, R5	/* R5 (ways) = R0 >> 3 */
+	AND	$1023, R5	/* R5 = (R0 >> 3) & MASK(10) */
+	ADD	$1, R5		/* R5 (ways) = ((R0 >> 3) & MASK(10)) + 1 */
+
+	SRA	$13, R0, R2	/* R2 = R0 >> 13 */
+	AND	$32767, R2	/* R2 = (R0 >> 13) & MASK(15) */
+	ADD	$1, R2		/* R2 (sets) = ((R0 >> 13) & MASK(15)) + 1 */
+
+	/* precompute set/way shifts for inner loop */
+	CMP	$0, R8		/* cache == 1? */
+	MOVW.EQ	$L1WAYSH, R3 	/* yes */
+	MOVW.EQ	$L1SETSH, R4
+	MOVW.NE	$L2WAYSH, R3	/* no */
+	MOVW.NE	$L2SETSH, R4
+
+	/* iterate over ways */
+	MOVW	$0, R7		/* R7: way */
+outer:
+	/* iterate over sets */
+	MOVW	$0, R6		/* R6: set */
+inner:
+	/* compute set/way register contents */
+	SLL	R3, R7, R0 	/* R0 = way << R3 (L?WAYSH) */
+	ORR	R8<<1, R0	/* R0 = way << L?WAYSH | (cache - 1) << 1 */
+	ORR	R6<<R4, R0 	/* R0 = way<<L?WAYSH | (cache-1)<<1 |set<<R4 */
+
+	BL	(R1)		/* call set/way operation with R0 */
+
+	ADD	$1, R6		/* set++ */
+	CMP	R2, R6		/* set >= sets? */
+	BLT	inner		/* no, do next set */
+
+	ADD	$1, R7		/* way++ */
+	CMP	R5, R7		/* way >= ways? */
+	BLT	outer		/* no, do next way */
+
+	MOVM.IA.W (SP), [R2,R14] /* restore regs */
+	MOVW	R2, CPSR	/* splx */
+
+	/* drain write buffers */
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwb), CpCACHEwait
+	ISB
+	RET
--- a/sys/src/9/bcm/clock.c
+++ b/sys/src/9/bcm/clock.c
@@ -1,11 +1,13 @@
 /*
- * bcm2835 timers
+ * bcm283[56] timers
  *	System timers run at 1MHz (timers 1 and 2 are used by GPU)
  *	ARM timer usually runs at 250MHz (may be slower in low power modes)
  *	Cycle counter runs at 700MHz (unless overclocked)
  *    All are free-running up-counters
+ *  Cortex-a7 has local generic timers per cpu (which we run at 1MHz)
  *
  * Use system timer 3 (64 bits) for hzclock interrupts and fastticks
+ *   For smp on bcm2836, use local generic timer for interrupts on cpu1-3
  * Use ARM timer (32 bits) for perfticks
  * Use ARM timer to force immediate interrupt
  * Use cycle counter for cycles()
@@ -17,14 +19,21 @@
 #include "dat.h"
 #include "fns.h"
 #include "io.h"
+#include "ureg.h"
+#include "arm.h"
 
 enum {
 	SYSTIMERS	= VIRTIO+0x3000,
 	ARMTIMER	= VIRTIO+0xB400,
 
+	Localctl	= 0x00,
+	Prescaler	= 0x08,
+	Localintpending	= 0x60,
+
 	SystimerFreq	= 1*Mhz,
 	MaxPeriod	= SystimerFreq / HZ,
-	MinPeriod	= SystimerFreq / (100*HZ),
+	MinPeriod	= 10,
+
 };
 
 typedef struct Systimers Systimers;
@@ -64,6 +73,11 @@
 	TmrPrescale256	= 0x02<<2,
 	CntWidth16	= 0<<1,
 	CntWidth32	= 1<<1,
+
+	/* generic timer (cortex-a7) */
+	Enable	= 1<<0,
+	Imask	= 1<<1,
+	Istatus = 1<<2,
 };
 
 static void
@@ -71,6 +85,8 @@
 {
 	Systimers *tn;
 
+	if(m->machno != 0)
+		panic("cpu%d: unexpected system timer interrupt", m->machno);
 	tn = (Systimers*)SYSTIMERS;
 	/* dismiss interrupt */
 	tn->cs = 1<<3;
@@ -77,6 +93,15 @@
 	timerintr(ureg, 0);
 }
 
+static void
+localclockintr(Ureg *ureg, void *)
+{
+	if(m->machno == 0)
+		panic("cpu0: Unexpected local generic timer interrupt");
+	cpwrsc(0, CpTIMER, CpTIMERphys, CpTIMERphysctl, Imask|Enable);
+	timerintr(ureg, 0);
+}
+
 void
 clockshutdown(void)
 {
@@ -84,6 +109,10 @@
 
 	tm = (Armtimer*)ARMTIMER;
 	tm->ctl = 0;
+	if(cpuserver)
+		wdogfeed();
+	else
+		wdogoff();
 }
 
 void
@@ -93,12 +122,16 @@
 	Armtimer *tm;
 	u32int t0, t1, tstart, tend;
 
-	tn = (Systimers*)SYSTIMERS;
-	tm = (Armtimer*)ARMTIMER;
-	tm->load = 0;
-	tm->ctl = TmrPrescale1|CntEnable|CntWidth32;
-	coherence();
+	if(((cprdsc(0, CpID, CpIDfeat, 1) >> 16) & 0xF) != 0) {
+		/* generic timer supported */
+		if(m->machno == 0){
+			*(ulong*)(ARMLOCAL + Localctl) = 0;				/* input clock is 19.2Mhz crystal */
+			*(ulong*)(ARMLOCAL + Prescaler) = 0x06aaaaab;	/* divide by (2^31/Prescaler) for 1Mhz */
+		}
+		cpwrsc(0, CpTIMER, CpTIMERphys, CpTIMERphysctl, Imask);
+	}
 
+	tn = (Systimers*)SYSTIMERS;
 	tstart = tn->clo;
 	do{
 		t0 = lcycles();
@@ -111,9 +144,14 @@
 	m->cpuhz = 100 * t1;
 	m->cpumhz = (m->cpuhz + Mhz/2 - 1) / Mhz;
 	m->cyclefreq = m->cpuhz;
-
-	tn->c3 = tn->clo - 1;
-	intrenable(IRQtimer3, clockintr, nil, 0, "clock");
+	if(m->machno == 0){
+		tn->c3 = tn->clo - 1;
+		tm = (Armtimer*)ARMTIMER;
+		tm->load = 0;
+		tm->ctl = TmrPrescale1|CntEnable|CntWidth32;
+		intrenable(IRQtimer3, clockintr, nil, 0, "clock");
+	}else
+		intrenable(IRQcntpns, localclockintr, nil, 0, "clock");
 }
 
 void
@@ -120,16 +158,22 @@
 timerset(uvlong next)
 {
 	Systimers *tn;
-	vlong now, period;
+	uvlong now;
+	long period;
 
-	tn = (Systimers*)SYSTIMERS;
 	now = fastticks(nil);
-	period = next - fastticks(nil);
+	period = next - now;
 	if(period < MinPeriod)
-		next = now + MinPeriod;
+		period = MinPeriod;
 	else if(period > MaxPeriod)
-		next = now + MaxPeriod;
-	tn->c3 = (ulong)next;
+		period = MaxPeriod;
+	if(m->machno > 0){
+		cpwrsc(0, CpTIMER, CpTIMERphys, CpTIMERphysval, period);
+		cpwrsc(0, CpTIMER, CpTIMERphys, CpTIMERphysctl, Enable);
+	}else{
+		tn = (Systimers*)SYSTIMERS;
+		tn->c3 = tn->clo + period;
+	}
 }
 
 uvlong
@@ -137,16 +181,17 @@
 {
 	Systimers *tn;
 	ulong lo, hi;
+	uvlong now;
 
-	tn = (Systimers*)SYSTIMERS;
 	if(hz)
 		*hz = SystimerFreq;
+	tn = (Systimers*)SYSTIMERS;
 	do{
 		hi = tn->chi;
 		lo = tn->clo;
 	}while(tn->chi != hi);
-	m->fastclock = (uvlong)hi<<32 | lo;
-	return m->fastclock;
+	now = (uvlong)hi<<32 | lo;
+	return now;
 }
 
 ulong
@@ -172,7 +217,6 @@
 		tm->ctl &= ~(TmrEnable|TmrIntEnable);
 		tm->irq = 1;
 	}
-	coherence();
 }
 
 ulong
@@ -180,7 +224,7 @@
 {
 	if(SystimerFreq != 1*Mhz)
 		return fastticks2us(fastticks(nil));
-	return fastticks(nil);
+	return ((Systimers*)SYSTIMERS)->clo;
 }
 
 void
@@ -189,8 +233,8 @@
 	Systimers *tn;
 	u32int now, diff;
 
-	tn = (Systimers*)SYSTIMERS;
 	diff = n + 1;
+	tn = (Systimers*)SYSTIMERS;
 	now = tn->clo;
 	while(tn->clo - now < diff)
 		;
--- a/sys/src/9/bcm/dat.h
+++ b/sys/src/9/bcm/dat.h
@@ -27,6 +27,7 @@
 typedef struct PMMU	PMMU;
 typedef struct Proc	Proc;
 typedef u32int		PTE;
+typedef struct Soc	Soc;
 typedef struct Uart	Uart;
 typedef struct Ureg	Ureg;
 typedef uvlong		Tval;
@@ -214,7 +215,7 @@
 typedef void		KMap;
 #define	VA(k)		((uintptr)(k))
 #define	kmap(p)		(KMap*)((p)->pa|kseg0)
-#define	kunmap(k)
+extern void kunmap(KMap*);
 
 struct
 {
@@ -279,3 +280,29 @@
 	Devport	*ports;			/* The ports themselves */
 };
 
+struct Soc {			/* SoC dependent configuration */
+	ulong	dramsize;
+	uintptr	physio;
+	uintptr	busdram;
+	uintptr	busio;
+	uintptr	armlocal;
+	u32int	l1ptedramattrs;
+	u32int	l2ptedramattrs;
+};
+extern Soc soc;
+
+#define BUSUNKNOWN -1
+
+/*
+ * GPIO
+ */
+enum {
+	Input	= 0x0,
+	Output	= 0x1,
+	Alt0	= 0x4,
+	Alt1	= 0x5,
+	Alt2	= 0x6,
+	Alt3	= 0x7,
+	Alt4	= 0x3,
+	Alt5	= 0x2,
+};
--- a/sys/src/9/bcm/devarch.c
+++ b/sys/src/9/bcm/devarch.c
@@ -150,9 +150,10 @@
 static long
 cputyperead(Chan*, void *a, long n, vlong offset)
 {
-	char str[128];
+	char name[64], str[128];
 
-	snprint(str, sizeof str, "ARM11 %d\n", m->cpumhz);
+	cputype2name(name, sizeof name);
+	snprint(str, sizeof str, "ARM %s %d\n", name, m->cpumhz);
 	return readstr(offset, a, n, str);
 }
 
@@ -159,8 +160,9 @@
 static long
 cputempread(Chan*, void *a, long n, vlong offset)
 {
- 	char str[128];
- 	snprint(str, sizeof str, "%d±%d\n", gettemp(0) / 1000, 1);
+	char str[16];
+
+	snprint(str, sizeof str, "%ud\n", (getcputemp()+500)/1000);
 	return readstr(offset, a, n, str);
 }
 
--- a/sys/src/9/bcm/devgpio.c
+++ b/sys/src/9/bcm/devgpio.c
@@ -232,110 +232,7 @@
 	}
 }
 
-// stolen from uartmini.c
-#define GPIOREGS	(VIRTIO+0x200000)
-/* GPIO regs */
-enum {
-	Fsel0	= 0x00>>2,
-		FuncMask= 0x7,
-	Set0	= 0x1c>>2,
-	Clr0	= 0x28>>2,
-	Lev0	= 0x34>>2,
-	Evds0	= 0x40>>2,
-	Redge0	= 0x4C>>2,
-	Fedge0	= 0x58>>2,
-	Hpin0	= 0x64>>2,
-	Lpin0	= 0x70>>2,
-	ARedge0	= 0x7C>>2,
-	AFedge0	= 0x88>2,
-	PUD	= 0x94>>2,
-	PUDclk0	= 0x98>>2,
-	PUDclk1	= 0x9c>>2,
-};
-
 static void
-gpiofuncset(uint pin, int func)
-{	
-	u32int *gp, *fsel;
-	int off;
-
-	gp = (u32int*)GPIOREGS;
-	fsel = &gp[Fsel0 + pin/10];
-	off = (pin % 10) * 3;
-	*fsel = (*fsel & ~(FuncMask<<off)) | func<<off;
-}
-
-static int
-gpiofuncget(uint pin)
-{	
-	u32int *gp, *fsel;
-	int off;
-
-	gp = (u32int*)GPIOREGS;
-	fsel = &gp[Fsel0 + pin/10];
-	off = (pin % 10) * 3;
-	return ((*fsel >> off) & FuncMask);
-}
-
-static void
-gpiopullset(uint pin, int state)
-{
-	u32int *gp, *reg;
-	u32int mask;
-
-	gp = (u32int*)GPIOREGS;
-	reg = &gp[PUDclk0 + pin/32];
-	mask = 1 << (pin % 32);
-	gp[PUD] = state;
-	microdelay(1);
-	*reg = mask;
-	microdelay(1);
-	*reg = 0;
-}
-
-static void
-gpioout(uint pin, int set)
-{
-	u32int *gp;
-	int v;
-
-	gp = (u32int*)GPIOREGS;
-	v = set? Set0 : Clr0;
-	gp[v + pin/32] = 1 << (pin % 32);
-}
-
-static int
-gpioin(uint pin)
-{
-	u32int *gp;
-
-	gp = (u32int*)GPIOREGS;
-	return (gp[Lev0 + pin/32] & (1 << (pin % 32))) != 0;
-}
-
-static void
-gpioevent(uint pin, int event, int enable)
-{
-	u32int *gp, *field;
-	int reg = 0;
-	
-	switch(event)
-	{
-		case Erising:
-			reg = Redge0;
-			break;
-		case Efalling:
-			reg = Fedge0;
-			break;
-		default:
-			panic("gpio: unknown event type");
-	}
-	gp = (u32int*)GPIOREGS;
-	field = &gp[reg + pin/32];
-	SET_BIT(field, pin, enable);
-}
-
-static void
 mkdeventry(Chan *c, Qid qid, Dirtab *tab, Dir *db)
 {
 	mkqid(&qid, tab->qid.path, tab->qid.vers, tab->qid.type);
@@ -417,13 +314,8 @@
 interrupt(Ureg*, void *)
 {
 	
-	u32int *gp, *field;
-	char pin;
+	uint pin;
 	
-	gp = (u32int*)GPIOREGS;
-
-	int set;
-
 	coherence();
 	
 	eventvalue = 0;
@@ -430,14 +322,8 @@
 	
 	for(pin = 0; pin < PIN_TABLE_SIZE; pin++)
 	{
-		set = (gp[Evds0 + pin/32] & (1 << (pin % 32))) != 0;
-
-		if(set)
-		{
-			field = &gp[Evds0 + pin/32];
-			SET_BIT(field, pin, 1);
+		if(gpiogetevent(pin))
 			SET_BIT(&eventvalue, pin, 1);
-		}
 	}
 	coherence();
 
@@ -447,7 +333,8 @@
 static void
 gpioinit(void)
 {
-	boardrev = getrevision() & 0xff;
+	gpiomeminit();
+	boardrev = getboardrev() & 0xff;
 	pinscheme = Qboard;
 	intrenable(49, interrupt, nil, 0, "gpio1");
 }
@@ -676,7 +563,7 @@
 			{
 				if(strncmp(funcname[i], arg, strlen(funcname[i])) == 0)
 				{
-					gpiofuncset(pin, i);
+					gpiosel(pin, i);
 					break;
 				}
 			}
@@ -691,7 +578,7 @@
 			{
 				if(strncmp(pudname[i], arg, strlen(pudname[i])) == 0)
 				{
-					gpiopullset(pin, i);
+					gpiopull(pin, i);
 					break;
 				}
 			}
@@ -707,7 +594,7 @@
 			{
 				if(strncmp(evtypename[i], arg, strlen(evtypename[i])) == 0)
 				{
-					gpioevent(pin, i, (cb->f[2][0] == 'e'));
+					gpioselevent(pin, i, (cb->f[2][0] == 'e'));
 					break;
 				}
 			}
--- a/sys/src/9/bcm/dma.c
+++ b/sys/src/9/bcm/dma.c
@@ -25,7 +25,7 @@
 enum {
 	Nchan		= 7,		/* number of dma channels */
 	Regsize		= 0x100,	/* size of regs for each chan */
-	Cbalign		= 32,		/* control block byte alignment */
+	Cbalign		= 64,		/* control block byte alignment (allow for 64-byte cache on bcm2836) */
 	Dbg		= 0,
 	
 	/* registers for each dma controller */
@@ -97,6 +97,18 @@
 static Ctlr dma[Nchan];
 static u32int *dmaregs = (u32int*)DMAREGS;
 
+uintptr
+dmaaddr(void *va)
+{
+	return soc.busdram | (PTR2UINT(va) & ~KSEGM);
+}
+
+static uintptr
+dmaioaddr(void *va)
+{
+	return soc.busio | (PTR2UINT(va) & ~VIRTIO);
+}
+
 static void
 dump(char *msg, uchar *p, int n)
 {
@@ -146,7 +158,7 @@
 		ctlr->regs = (u32int*)(DMAREGS + chan*Regsize);
 		ctlr->cb = xspanalloc(sizeof(Cb), Cbalign, 0);
 		assert(ctlr->cb != nil);
-		dmaregs[Enable] |= 1 << chan;
+		dmaregs[Enable] |= 1<<chan;
 		ctlr->regs[Cs] = Reset;
 		while(ctlr->regs[Cs] & Reset)
 			;
@@ -156,26 +168,26 @@
 	ti = 0;
 	switch(dir){
 	case DmaD2M:
-		cachedwbinvse(dst, len);
+		cachedinvse(dst, len);
 		ti = Srcdreq | Destinc;
-		cb->sourcead = DMAIO(src);
-		cb->destad = DMAADDR(dst);
+		cb->sourcead = dmaioaddr(src);
+		cb->destad = dmaaddr(dst);
 		break;
 	case DmaM2D:
 		cachedwbse(src, len);
 		ti = Destdreq | Srcinc;
-		cb->sourcead = DMAADDR(src);
-		cb->destad = DMAIO(dst);
+		cb->sourcead = dmaaddr(src);
+		cb->destad = dmaioaddr(dst);
 		break;
 	case DmaM2M:
 		cachedwbse(src, len);
-		cachedwbinvse(dst, len);
+		cachedinvse(dst, len);
 		ti = Srcinc | Destinc;
-		cb->sourcead = DMAADDR(src);
-		cb->destad = DMAADDR(dst);
+		cb->sourcead = dmaaddr(src);
+		cb->destad = dmaaddr(dst);
 		break;
 	}
-	cb->ti = ti | dev << Permapshift | Inten;
+	cb->ti = ti | dev<<Permapshift | Inten;
 	cb->txfrlen = len;
 	cb->stride = 0;
 	cb->nextconbk = 0;
@@ -182,7 +194,7 @@
 	cachedwbse(cb, sizeof(Cb));
 	ctlr->regs[Cs] = 0;
 	microdelay(1);
-	ctlr->regs[Conblkad] = DMAADDR(cb);
+	ctlr->regs[Conblkad] = dmaaddr(cb);
 	DBG print("dma start: %ux %ux %ux %ux %ux %ux\n",
 		cb->ti, cb->sourcead, cb->destad, cb->txfrlen,
 		cb->stride, cb->nextconbk);
--- a/sys/src/9/bcm/dwcotg.h
+++ b/sys/src/9/bcm/dwcotg.h
@@ -434,8 +434,8 @@
 		Episo		= 1<<18,
 		Epbulk		= 2<<18,
 		Epintr		= 3<<18,
-	Multicnt	= 0x3<<20,	/* transactions per μframe or retries */
-					/* per periodic split */
+	Multicnt	= 0x3<<20,	/* transactions per μframe */
+					/* or retries per periodic split */
 		OMulticnt	= 20,
 	Devaddr		= 0x7f<<22,	/* device address */
 		ODevaddr	= 22,
--- a/sys/src/9/bcm/fns.h
+++ b/sys/src/9/bcm/fns.h
@@ -10,6 +10,9 @@
 extern void cachedwbinvse(void*, int);
 extern void cacheiinv(void);
 extern void cacheuwbinv(void);
+extern void cachedwbtlb(void*, int);
+extern void cacheiinvse(void*, int);
+extern void cachedinvse(void*, int);
 extern uintptr cankaddr(uintptr pa);
 extern int cas32(void*, u32int, u32int);
 extern void checkmmu(uintptr, uintptr);
@@ -20,11 +23,13 @@
 extern ulong cprd(int cp, int op1, int crn, int crm, int op2);
 extern ulong cprdsc(int op1, int crn, int crm, int op2);
 extern void cpuidprint(void);
+extern u32int cpidget(void);
 extern void cpwr(int cp, int op1, int crn, int crm, int op2, ulong val);
 extern void cpwrsc(int op1, int crn, int crm, int op2, ulong val);
 #define cycles(ip) *(ip) = lcycles()
 extern void dmastart(int, int, int, void*, void*, int);
 extern int dmawait(int);
+extern uintptr dmaaddr(void *va);
 extern int fbblank(int);
 extern void* fbinit(int, int*, int*, int*);
 extern u32int farget(void);
@@ -41,13 +46,26 @@
 extern uint getfirmware(void);
 extern int getpower(int);
 extern void getramsize(Confmem*);
+extern int getncpus(void);
+extern void gpiosel(uint, int);
+extern void gpiopull(uint, int);
+extern void gpiopullup(uint);
+extern void gpiopulloff(uint);
+extern void gpiopulldown(uint);
+extern void gpioout(uint, int);
+extern int gpioin(uint);
+extern void gpioselevent(uint, int, int);
+extern int gpiogetevent(uint);
+extern void gpiomeminit(void);
 extern u32int ifsrget(void);
+extern void intrcpushutdown(void);
 extern void irqenable(int, void (*)(Ureg*, void*), void*);
 #define intrenable(i, f, a, b, n) irqenable((i), (f), (a))
 extern void intrsoff(void);
 extern int isaconfig(char*, int, ISAConf*);
+extern void l2cacheuwbinv(void);
 extern void links(void);
-extern void mmuinit(void);
+extern void mmuinit(void*);
 extern void mmuinit1(void);
 extern void mmuinvalidate(void);
 extern void mmuinvalidateaddr(u32int);
@@ -58,8 +76,10 @@
 extern void procfork(Proc*);
 extern void procsetup(Proc*);
 extern void screeninit(void);
+extern void setclkrate(int, ulong);
 extern void setpower(int, int);
 extern void setr13(int, u32int*);
+extern int startcpus(uint);
 extern int splfhi(void);
 extern int splflo(void);
 extern int tas(void *);
@@ -68,9 +88,17 @@
 extern void uartconsinit(void);
 extern int userureg(Ureg*);
 extern void vectors(void);
+extern void vgpinit(void);
+extern void vgpset(uint, int);
 extern void vtable(void);
-extern uint gettemp(int);
-extern uint getrevision(void);
+extern void wdogoff(void);
+extern void wdogfeed(void);
+extern void vtable(void);
+extern int l2ap(int);
+extern uint getcputemp(void);
+extern char *cputype2name(char *buf, int size);
+extern uint getboardrev(void);
+extern void sev(void);
 
 /*
  * floating point emulation
--- /dev/null
+++ b/sys/src/9/bcm/gpio.c
@@ -1,0 +1,142 @@
+/*
+ * Raspberry Pi GPIO support
+ */
+
+#include "u.h"
+#include "../port/lib.h"
+#include "../port/error.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+#define GPIOREGS	(VIRTIO+0x200000)
+
+/* GPIO regs */
+enum {
+	Fsel0	= 0x00>>2,
+		FuncMask= 0x7,
+	Set0	= 0x1c>>2,
+	Clr0	= 0x28>>2,
+	Lev0	= 0x34>>2,
+	Evds0	= 0x40>>2,
+	Redge0	= 0x4C>>2,
+	Fedge0	= 0x58>>2,
+	Hpin0	= 0x64>>2,
+	Lpin0	= 0x70>>2,
+	ARedge0	= 0x7C>>2,
+	AFedge0	= 0x88>2,
+	PUD	= 0x94>>2,
+		Off	= 0x0,
+		Pulldown= 0x1,
+		Pullup	= 0x2,
+	PUDclk0	= 0x98>>2,
+	PUDclk1	= 0x9c>>2,
+};
+
+void
+gpiosel(uint pin, int func)
+{	
+	u32int *gp, *fsel;
+	int off;
+
+	gp = (u32int*)GPIOREGS;
+	fsel = &gp[Fsel0 + pin/10];
+	off = (pin % 10) * 3;
+	*fsel = (*fsel & ~(FuncMask<<off)) | func<<off;
+}
+
+void
+gpiopull(uint pin, int func)
+{
+	u32int *gp, *reg;
+	u32int mask;
+
+	gp = (u32int*)GPIOREGS;
+	reg = &gp[PUDclk0 + pin/32];
+	mask = 1 << (pin % 32);
+	gp[PUD] = func;
+	microdelay(1);
+	*reg = mask;
+	microdelay(1);
+	*reg = 0;
+}
+
+void
+gpiopulloff(uint pin)
+{
+	gpiopull(pin, Off);
+}
+
+void
+gpiopullup(uint pin)
+{
+	gpiopull(pin, Pullup);
+}
+
+void
+gpiopulldown(uint pin)
+{
+	gpiopull(pin, Pulldown);
+}
+
+void
+gpioout(uint pin, int set)
+{
+	u32int *gp;
+	int v;
+
+	gp = (u32int*)GPIOREGS;
+	v = set? Set0 : Clr0;
+	gp[v + pin/32] = 1 << (pin % 32);
+}
+
+int
+gpioin(uint pin)
+{
+	u32int *gp;
+
+	gp = (u32int*)GPIOREGS;
+	return (gp[Lev0 + pin/32] & (1 << (pin % 32))) != 0;
+}
+
+void
+gpioselevent(uint pin, int falling, int enable)
+{
+	u32int *gp, *field;
+	int reg;
+
+	enable = enable != 0;
+	if(falling)
+		reg = Fedge0;
+	else
+		reg = Redge0;
+	gp = (u32int*)GPIOREGS;
+	field = &gp[reg + pin/32];
+	*field = (*field & ~(enable<<pin)) | (enable<<pin);
+}
+
+int
+gpiogetevent(uint pin)
+{
+	u32int *gp, *reg, val;
+
+	gp = (u32int*)GPIOREGS;
+	reg = &gp[Evds0 + pin/32];
+	val = *reg & (1 << (pin % 32));
+	*reg |= val;
+	return val != 0;
+}
+
+void
+gpiomeminit(void)
+{
+	Physseg seg;
+
+	memset(&seg, 0, sizeof seg);
+	seg.attr = SG_PHYSICAL;
+	seg.name = "gpio";
+	seg.pa = GPIOREGS;
+	seg.size = BY2PG;
+	addphysseg(&seg);
+}
--- a/sys/src/9/bcm/io.h
+++ b/sys/src/9/bcm/io.h
@@ -8,11 +8,23 @@
 	IRQdma0		= 16,
 #define IRQDMA(chan)	(IRQdma0+(chan))
 	IRQaux		= 29,
+	IRQi2c		= 53,
+	IRQspi		= 54,
+	IRQsdhost	= 56,
 	IRQmmc		= 62,
 
 	IRQbasic	= 64,
 	IRQtimerArm	= IRQbasic + 0,
 
+	IRQlocal	= 96,
+	IRQcntps	= IRQlocal + 0,
+	IRQcntpns	= IRQlocal + 1,
+	IRQmbox0	= IRQlocal + 4,
+	IRQmbox1	= IRQlocal + 5,
+	IRQmbox2	= IRQlocal + 6,
+	IRQmbox3	= IRQlocal + 7,
+	IRQlocaltmr	= IRQlocal + 11,
+
 	IRQfiq		= IRQusb,	/* only one source can be FIQ */
 
 	DmaD2M		= 0,		/* device to memory */
@@ -20,7 +32,14 @@
 	DmaM2M		= 2,		/* memory to memory */
 
 	DmaChanEmmc	= 4,		/* can only use 2-5, maybe 0 */
+	DmaChanSdhost	= 5,
+	DmaChanSpiTx= 2,
+	DmaChanSpiRx= 0,
+
+	DmaDevSpiTx	= 6,
+	DmaDevSpiRx	= 7,
 	DmaDevEmmc	= 11,
+	DmaDevSdhost	= 13,
 
 	PowerSd		= 0,
 	PowerUart0,
@@ -43,4 +62,3 @@
 	ClkPixel,
 	ClkPwm,
 };
-#define BUSUNKNOWN	(-1)
--- a/sys/src/9/bcm/l.s
+++ b/sys/src/9/bcm/l.s
@@ -1,10 +1,14 @@
 /*
- * Broadcom bcm2835 SoC, as used in Raspberry Pi
- * arm1176jzf-s processor (armv6)
+ * Common startup for armv6 and armv7
+ * The rest of l.s has been moved to armv[67].s
  */
 
 #include "arm.s"
 
+/*
+ * on bcm2836, only cpu0 starts here
+ * other cpus enter at cpureset in armv7.s
+ */
 TEXT _start(SB), 1, $-4
 	/*
 	 * load physical base for SB addressing while mmu is off
@@ -16,259 +20,14 @@
 	MOVW	$0, R0
 
 	/*
-	 * SVC mode, interrupts disabled
-	 */
-	MOVW	$(PsrDirq|PsrDfiq|PsrMsvc), R1
-	MOVW	R1, CPSR
-
-	/*
-	 * disable the mmu and L1 caches
-	 * invalidate caches and tlb
-	 */
-	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
-	BIC	$(CpCdcache|CpCicache|CpCpredict|CpCmmu), R1
-	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
-	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvu), CpCACHEall
-	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
-	ISB
-
-	/*
-	 * clear mach and page tables
-	 */
-	MOVW	$PADDR(MACHADDR), R1
-	MOVW	$PADDR(KTZERO), R2
-_ramZ:
-	MOVW	R0, (R1)
-	ADD	$4, R1
-	CMP	R1, R2
-	BNE	_ramZ
-
-	/*
 	 * start stack at top of mach (physical addr)
-	 * set up page tables for kernel
 	 */
 	MOVW	$PADDR(MACHADDR+MACHSIZE-4), R13
-	BL	,mmuinit(SB)
 
 	/*
-	 * set up domain access control and page table base
+	 * do arch-dependent startup (no return)
 	 */
-	MOVW	$Client, R1
-	MCR	CpSC, 0, R1, C(CpDAC), C(0)
-	MOVW	$PADDR(L1), R1
-	MCR	CpSC, 0, R1, C(CpTTB), C(0)
-
-	/*
-	 * enable caches, mmu, and high vectors
-	 */
-	MRC	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
-	ORR	$(CpChv|CpCdcache|CpCicache|CpCmmu), R0
-	MCR	CpSC, 0, R0, C(CpCONTROL), C(0), CpMainctl
-	ISB
-
-	/*
-	 * switch SB, SP, and PC into KZERO space
-	 */
-	MOVW	$setR12(SB), R12
-	MOVW	$(MACHADDR+MACHSIZE-4), R13
-	MOVW	$_startpg(SB), R15
-
-TEXT _startpg(SB), 1, $-4
-
-	/*
-	 * enable cycle counter
-	 */
-	MOVW	$1, R1
-	MCR	CpSC, 0, R1, C(CpSPM), C(CpSPMperf), CpSPMctl
-
-	/*
-	 * call main and loop forever if it returns
-	 */
-	BL	,main(SB)
+	BL	,armstart(SB)
 	B	,0(PC)
 
-	BL	_div(SB)		/* hack to load _div, etc. */
-
-TEXT fsrget(SB), 1, $-4				/* data fault status */
-	MRC	CpSC, 0, R0, C(CpFSR), C(0), CpFSRdata
-	RET
-
-TEXT ifsrget(SB), 1, $-4			/* instruction fault status */
-	MRC	CpSC, 0, R0, C(CpFSR), C(0), CpFSRinst
-	RET
-
-TEXT farget(SB), 1, $-4				/* fault address */
-	MRC	CpSC, 0, R0, C(CpFAR), C(0x0)
-	RET
-
-TEXT lcycles(SB), 1, $-4
-	MRC	CpSC, 0, R0, C(CpSPM), C(CpSPMperf), CpSPMcyc
-	RET
-
-TEXT splhi(SB), 1, $-4
-	MOVW	$(MACHADDR+4), R2		/* save caller pc in Mach */
-	MOVW	R14, 0(R2)
-
-	MOVW	CPSR, R0			/* turn off irqs (but not fiqs) */
-	ORR	$(PsrDirq), R0, R1
-	MOVW	R1, CPSR
-	RET
-
-TEXT splfhi(SB), 1, $-4
-	MOVW	$(MACHADDR+4), R2		/* save caller pc in Mach */
-	MOVW	R14, 0(R2)
-
-	MOVW	CPSR, R0			/* turn off irqs and fiqs */
-	ORR	$(PsrDirq|PsrDfiq), R0, R1
-	MOVW	R1, CPSR
-	RET
-
-TEXT splflo(SB), 1, $-4
-	MOVW	CPSR, R0			/* turn on fiqs */
-	BIC	$(PsrDfiq), R0, R1
-	MOVW	R1, CPSR
-	RET
-
-TEXT spllo(SB), 1, $-4
-	MOVW	CPSR, R0			/* turn on irqs and fiqs */
-	BIC	$(PsrDirq|PsrDfiq), R0, R1
-	MOVW	R1, CPSR
-	RET
-
-TEXT splx(SB), 1, $-4
-	MOVW	$(MACHADDR+0x04), R2		/* save caller pc in Mach */
-	MOVW	R14, 0(R2)
-
-	MOVW	R0, R1				/* reset interrupt level */
-	MOVW	CPSR, R0
-	MOVW	R1, CPSR
-	RET
-
-TEXT spldone(SB), 1, $0				/* end marker for devkprof.c */
-	RET
-
-TEXT islo(SB), 1, $-4
-	MOVW	CPSR, R0
-	AND	$(PsrDirq), R0
-	EOR	$(PsrDirq), R0
-	RET
-
-TEXT	tas(SB), $-4
-TEXT	_tas(SB), $-4
-	MOVW	R0,R1
-	MOVW	$1,R0
-	SWPW	R0,(R1)			/* fix: deprecated in armv6 */
-	RET
-
-TEXT setlabel(SB), 1, $-4
-	MOVW	R13, 0(R0)		/* sp */
-	MOVW	R14, 4(R0)		/* pc */
-	MOVW	$0, R0
-	RET
-
-TEXT gotolabel(SB), 1, $-4
-	MOVW	0(R0), R13		/* sp */
-	MOVW	4(R0), R14		/* pc */
-	MOVW	$1, R0
-	RET
-
-TEXT getcallerpc(SB), 1, $-4
-	MOVW	0(R13), R0
-	RET
-
-TEXT idlehands(SB), $-4
-	BARRIERS
-	MOVW	CPSR, R3
-	BIC	$(PsrDirq|PsrDfiq), R3, R1		/* spllo */
-	MOVW	R1, CPSR
-
-	MOVW	$0, R0				/* wait for interrupt */
-	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEintr), CpCACHEwait
-	ISB
-
-	MOVW	R3, CPSR			/* splx */
-	RET
-
-
-TEXT coherence(SB), $-4
-	BARRIERS
-	RET
-
-/*
- * invalidate tlb
- */
-TEXT mmuinvalidate(SB), 1, $-4
-	MOVW	$0, R0
-	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
-	BARRIERS
-	RET
-
-/*
- * mmuinvalidateaddr(va)
- *   invalidate tlb entry for virtual page address va, ASID 0
- */
-TEXT mmuinvalidateaddr(SB), 1, $-4
-	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinvse
-	BARRIERS
-	RET
-
-/*
- * drain write buffer
- * writeback and invalidate data cache
- */
-TEXT cachedwbinv(SB), 1, $-4
-	DSB
-	MOVW	$0, R0
-	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwbi), CpCACHEall
-	RET
-
-/*
- * cachedwbinvse(va, n)
- *   drain write buffer
- *   writeback and invalidate data cache range [va, va+n)
- */
-TEXT cachedwbinvse(SB), 1, $-4
-	MOVW	R0, R1		/* DSB clears R0 */
-	DSB
-	MOVW	n+4(FP), R2
-	ADD	R1, R2
-	SUB	$1, R2
-	BIC	$(CACHELINESZ-1), R1
-	BIC	$(CACHELINESZ-1), R2
-	MCRR(CpSC, 0, 2, 1, CpCACHERANGEdwbi)
-	RET
-
-/*
- * cachedwbse(va, n)
- *   drain write buffer
- *   writeback data cache range [va, va+n)
- */
-TEXT cachedwbse(SB), 1, $-4
-	MOVW	R0, R1		/* DSB clears R0 */
-	DSB
-	MOVW	n+4(FP), R2
-	ADD	R1, R2
-	BIC	$(CACHELINESZ-1), R1
-	BIC	$(CACHELINESZ-1), R2
-	MCRR(CpSC, 0, 2, 1, CpCACHERANGEdwb)
-	RET
-
-/*
- * drain write buffer and prefetch buffer
- * writeback and invalidate data cache
- * invalidate instruction cache
- */
-TEXT cacheuwbinv(SB), 1, $-4
-	BARRIERS
-	MOVW	$0, R0
-	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwbi), CpCACHEall
-	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall
-	RET
-
-/*
- * invalidate instruction cache
- */
-TEXT cacheiinv(SB), 1, $-4
-	MOVW	$0, R0
-	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall
 	RET
--- a/sys/src/9/bcm/lexception.s
+++ b/sys/src/9/bcm/lexception.s
@@ -27,6 +27,7 @@
 	WORD	$_vfiq(SB)		/* FIQ, switch to svc mode */
 
 TEXT _vsvc(SB), 1, $-4			/* SWI */
+	CLREX
 	MOVW.W	R14, -4(R13)		/* ureg->pc = interrupted PC */
 	MOVW	SPSR, R14		/* ureg->psr = SPSR */
 	MOVW.W	R14, -4(R13)		/* ... */
@@ -39,10 +40,17 @@
 
 	MOVW	$setR12(SB), R12	/* Make sure we've got the kernel's SB loaded */
 
-//	MOVW	$(KSEG0+16*KiB-MACHSIZE), R10	/* m */
-	MOVW	$(MACHADDR), R10	/* m */
-	MOVW	8(R10), R9		/* up */
+	/* get R(MACH) for this cpu */
+	CPUID(R1)
+	SLL	$2, R1			/* convert to word index */
+	MOVW	$machaddr(SB), R2
+	ADD	R1, R2
+	MOVW	(R2), R(MACH)		/* m = machaddr[cpuid] */
+	CMP	$0, R(MACH)
+	MOVW.EQ	$MACHADDR, R0		/* paranoia: use MACHADDR if 0 */
 
+	MOVW	8(R(MACH)), R(USER)		/* up */
+
 	MOVW	R13, R0			/* first arg is pointer to ureg */
 	SUB	$8, R13			/* space for argument+link */
 
@@ -81,6 +89,7 @@
 	 *  we'll switch to SVC mode and then call trap.
 	 */
 _vswitch:
+	CLREX
 	MOVW	SPSR, R1		/* save SPSR for ureg */
 	MOVW	R14, R2			/* save interrupted pc for ureg */
 	MOVW	R13, R3			/* save pointer to where the original [R0-R4] are */
@@ -119,7 +128,16 @@
 
 	BL	trap(SB)
 
+	MOVW	$setR12(SB), R12	/* reload kernel's SB (ORLY?) */
 	ADD	$(4*2+4*15), R13	/* make r13 point to ureg->type */
+	/*
+	 * if we interrupted a previous trap's handler and are now
+	 * returning to it, we need to propagate the current R(MACH) (R10)
+	 * by overriding the saved one on the stack, since we may have
+	 * been rescheduled and be on a different processor now than
+	 * at entry.
+	 */
+	MOVW	R(MACH), (-(15-MACH)*4)(R13) /* restore current cpu's MACH */
 	MOVW	8(R13), R14		/* restore link */
 	MOVW	4(R13), R0		/* restore SPSR */
 	MOVW	R0, SPSR		/* ... */
@@ -140,10 +158,17 @@
 
 	MOVW	$setR12(SB), R12	/* Make sure we've got the kernel's SB loaded */
 
-//	MOVW	$(KSEG0+16*KiB-MACHSIZE), R10	/* m */
-	MOVW	$(MACHADDR), R10	/* m */
-	MOVW	8(R10), R9		/* up */
+	/* get R(MACH) for this cpu */
+	CPUID(R1)
+	SLL	$2, R1			/* convert to word index */
+	MOVW	$machaddr(SB), R2
+	ADD	R1, R2
+	MOVW	(R2), R(MACH)		/* m = machaddr[cpuid] */
+	CMP	$0, R(MACH)
+	MOVW.EQ	$MACHADDR, R(MACH)		/* paranoia: use MACHADDR if 0 */
 
+	MOVW	8(R(MACH)), R(USER)		/* up */
+
 	MOVW	R13, R0			/* first arg is pointer to ureg */
 	SUB	$(4*2), R13		/* space for argument+link (for debugger) */
 
@@ -158,14 +183,24 @@
 	RFE				/* MOVM.IA.S.W (R13), [R15] */
 
 TEXT _vfiq(SB), 1, $-4			/* FIQ */
+	CLREX
 	MOVW	$PsrMfiq, R8		/* trap type */
 	MOVW	SPSR, R9		/* interrupted psr */
 	MOVW	R14, R10		/* interrupted pc */
 	MOVM.DB.W [R8-R10], (R13)	/* save in ureg */
-	MOVM.DB.W.S [R0-R14], (R13)	/* save interrupted regs */
+	MOVM.DB.S [R0-R14], (R13)	/* save interrupted regs */
+	SUB	$(15*4), R13
 	MOVW	$setR12(SB), R12	/* Make sure we've got the kernel's SB loaded */
-	MOVW	$(MACHADDR), R10	/* m */
-	MOVW	8(R10), R9		/* up */
+	/* get R(MACH) for this cpu */
+	CPUID(R1)
+	SLL	$2, R1			/* convert to word index */
+	MOVW	$machaddr(SB), R2
+	ADD	R1, R2
+	MOVW	(R2), R(MACH)		/* m = machaddr[cpuid] */
+	CMP	$0, R(MACH)
+	MOVW.EQ	$MACHADDR, R(MACH)		/* paranoia: use MACHADDR if 0 */
+
+	MOVW	8(R(MACH)), R(USER)		/* up */
 	MOVW	R13, R0			/* first arg is pointer to ureg */
 	SUB	$(4*2), R13		/* space for argument+link (for debugger) */
 
@@ -187,6 +222,7 @@
 
 	MOVW	CPSR, R2
 	BIC	$PsrMask, R2, R3
+	ORR	$(PsrDirq|PsrDfiq), R3
 	ORR	R0, R3
 	MOVW	R3, CPSR		/* switch to new mode */
 
--- a/sys/src/9/bcm/main.c
+++ b/sys/src/9/bcm/main.c
@@ -4,6 +4,7 @@
 #include "mem.h"
 #include "dat.h"
 #include "fns.h"
+#include "io.h"
 
 #include "init.h"
 #include <pool.h>
@@ -191,6 +192,21 @@
 void
 machinit(void)
 {
+	Mach *m0;
+
+	m->ticks = 1;
+	m->perf.period = 1;
+	m0 = MACHP(0);
+	if (m->machno != 0) {
+		/* synchronise with cpu 0 */
+		m->ticks = m0->ticks;
+	}
+}
+
+void
+mach0init(void)
+{
+	m->mmul1 = (PTE*)L1;
 	m->machno = 0;
 	machaddr[m->machno] = m;
 
@@ -197,8 +213,6 @@
 	m->ticks = 1;
 	m->perf.period = 1;
 
-	conf.nmach = 1;
-
 	active.machs[0] = 1;
 	active.exiting = 0;
 
@@ -206,6 +220,32 @@
 }
 
 static void
+launchinit(void)
+{
+	int mach;
+	Mach *mm;
+	PTE *l1;
+
+	for(mach = 1; mach < conf.nmach; mach++){
+		machaddr[mach] = mm = mallocalign(MACHSIZE, MACHSIZE, 0, 0);
+		l1 = mallocalign(L1SIZE, L1SIZE, 0, 0);
+		if(mm == nil || l1 == nil)
+			panic("launchinit");
+		memset(mm, 0, MACHSIZE);
+		mm->machno = mach;
+
+		memmove(l1, m->mmul1, L1SIZE);  /* clone cpu0's l1 table */
+		cachedwbse(l1, L1SIZE);
+		mm->mmul1 = l1;
+		cachedwbse(mm, MACHSIZE);
+
+	}
+	cachedwbse(machaddr, sizeof machaddr);
+	if((mach = startcpus(conf.nmach)) < conf.nmach)
+			print("only %d cpu%s started\n", mach, mach == 1? "" : "s");
+}
+
+static void
 optionsinit(char* s)
 {
 	strecpy(oargb, oargb+sizeof(oargb), s);
@@ -216,29 +256,14 @@
 }
 
 void
-gpiomeminit(void)
-{
-	Physseg seg;
-	memset(&seg, 0, sizeof seg);
-	seg.attr = SG_PHYSICAL;
-	seg.name = "gpio";
-	seg.pa = (VIRTIO+0x200000);
-	seg.size = BY2PG;
-	addphysseg(&seg);
-}
-
-
-void
 main(void)
 {
 	extern char edata[], end[];
-	uint rev;
+	uint fw, board;
 
-	okay(1);
 	m = (Mach*)MACHADDR;
 	memset(edata, 0, end - edata);	/* clear bss */
-	machinit();
-	mmuinit1();
+	mach0init();
 
 	optionsinit("/boot/boot boot");
 	quotefmtinstall();
@@ -250,14 +275,17 @@
 	screeninit();
 
 	print("\nPlan 9 from Bell Labs\n");
-	rev = getfirmware();
-	print("firmware: rev %d\n", rev);
-	if(rev < Minfirmrev){
-		print("Sorry, firmware (start.elf) must be at least rev %d (%s)\n",
-			Minfirmrev, Minfirmdate);
+	board = getboardrev();
+	fw = getfirmware();
+	print("board rev: %#ux firmware rev: %d\n", board, fw);
+	if(fw < Minfirmrev){
+		print("Sorry, firmware (start*.elf) must be at least rev %d"
+		      " or newer than %s\n", Minfirmrev, Minfirmdate);
 		for(;;)
 			;
 	}
+	/* set clock rate to arm_freq from config.txt (default pi1:700Mhz pi2:900MHz) */
+	setclkrate(ClkArm, 0);
 	trapinit();
 	clockinit();
 	printinit();
@@ -264,6 +292,7 @@
 	timersinit();
 	cpuidprint();
 	archreset();
+	vgpinit();
 
 	procinit0();
 	initseg();
@@ -271,7 +300,8 @@
 	chandevreset();			/* most devices are discovered here */
 	pageinit();
 	userinit();
-	gpiomeminit();
+	launchinit();
+	mmuinit1();
 	schedinit();
 	assert(0);			/* shouldn't have returned */
 }
@@ -484,8 +514,7 @@
 	conf.upages = conf.npage - kpages;
 	conf.ialloc = (kpages/2)*BY2PG;
 
-	/* only one processor */
-	conf.nmach = 1;
+	conf.nmach = getncpus();
 
 	/* set up other configuration parameters */
 	conf.nproc = 100 + ((conf.npage*BY2PG)/MB)*5;
@@ -497,7 +526,7 @@
 	conf.nswppo = 4096;
 	conf.nimage = 200;
 
-	conf.copymode = 0;		/* copy on write */
+	conf.copymode = conf.nmach > 1;
 
 	/*
 	 * Guess how much is taken by the large permanent
@@ -529,6 +558,14 @@
 {
 	cpushutdown();
 	splfhi();
+	if(m->machno != 0){
+		void (*f)(ulong, ulong, ulong) = (void*)REBOOTADDR;
+		intrsoff();
+		intrcpushutdown();
+		cacheuwbinv();
+		(*f)(0, 0, 0);
+		for(;;);
+	}
 	archreboot();
 }
 
@@ -536,11 +573,9 @@
  * stub for ../omap/devether.c
  */
 int
-isaconfig(char *class, int ctlrno, ISAConf *isa)
+isaconfig(char *, int, ISAConf *)
 {
-	USED(ctlrno);
-	USED(isa);
-	return strcmp(class, "ether") == 0;
+	return 0;
 }
 
 /*
@@ -553,12 +588,23 @@
 	void (*f)(ulong, ulong, ulong);
 
 	writeconf();
+	if (m->machno != 0) {
+		procwired(up, 0);
+		sched();
+	}
+
+	/* setup reboot trampoline function */
+	f = (void*)REBOOTADDR;
+	memmove(f, rebootcode, sizeof(rebootcode));
+	cachedwbse(f, sizeof(rebootcode));
+
 	cpushutdown();
+	delay(500);
 
+	splfhi();
+
 	/* turn off buffered serial console */
 	serialoq = nil;
-	kprintoq = nil;
-	screenputs = nil;
 
 	/* shutdown devices */
 	chandevshutdown();
@@ -565,23 +611,14 @@
 
 	/* stop the clock (and watchdog if any) */
 	clockshutdown();
-
-	splfhi();
 	intrsoff();
+	intrcpushutdown();
 
-	/* setup reboot trampoline function */
-	f = (void*)REBOOTADDR;
-	memmove(f, rebootcode, sizeof(rebootcode));
 	cacheuwbinv();
+	l2cacheuwbinv();
 
 	/* off we go - never to return */
 	(*f)(PADDR(entry), PADDR(code), size);
-}
-
-int
-cmpswap(long *addr, long old, long new)
-{
-	return cas32(addr, old, new);
 }
 
 void
--- a/sys/src/9/bcm/mem.h
+++ b/sys/src/9/bcm/mem.h
@@ -5,27 +5,31 @@
 #define MiB		1048576u		/* Mebi 0x0000000000100000 */
 #define GiB		1073741824u		/* Gibi 000000000040000000 */
 
-#define HOWMANY(x, y)	(((x)+((y)-1))/(y))
-#define ROUNDUP(x, y)	(HOWMANY((x), (y))*(y))	/* ceiling */
-#define ROUNDDN(x, y)	(((x)/(y))*(y))		/* floor */
-#define MIN(a, b)	((a) < (b)? (a): (b))
-#define MAX(a, b)	((a) > (b)? (a): (b))
-
 /*
  * Sizes
  */
 #define	BY2PG		(4*KiB)			/* bytes per page */
 #define	PGSHIFT		12			/* log(BY2PG) */
+#define	HOWMANY(x,y)	(((x)+((y)-1))/(y))
+#define	ROUNDUP(x,y)	(HOWMANY((x),(y))*(y))
 #define	PGROUND(s)	ROUNDUP(s, BY2PG)
 #define	ROUND(s, sz)	(((s)+(sz-1))&~(sz-1))
 
-#define	MAXMACH		1			/* max # cpus system can run */
+#define	MAXMACH		4			/* max # cpus system can run */
 #define	MACHSIZE	BY2PG
+#define L1SIZE		(4 * BY2PG)
 
 #define KSTKSIZE	(8*KiB)
 #define STACKALIGN(sp)	((sp) & ~3)		/* bug: assure with alloc */
 
 /*
+ * Magic registers
+ */
+
+#define	USER		9		/* R9 is up-> */
+#define	MACH		10		/* R10 is m-> */
+
+/*
  * Address spaces.
  * KTZERO is used by kprof and dumpstack (if any).
  *
@@ -36,8 +40,8 @@
  */
 
 #define	KSEG0		0x80000000		/* kernel segment */
-/* mask to check segment; good for 512MB dram */
-#define	KSEGM		0xE0000000
+/* mask to check segment; good for 1GB dram */
+#define	KSEGM		0xC0000000
 #define	KZERO		KSEG0			/* kernel address space */
 #define CONFADDR	(KZERO+0x100)		/* unparsed plan9.ini */
 #define	MACHADDR	(KZERO+0x2000)		/* Mach structure */
@@ -47,20 +51,24 @@
 #define	L1		(KZERO+0x4000)		/* tt ptes: 16KiB aligned */
 #define	KTZERO		(KZERO+0x8000)		/* kernel text start */
 #define VIRTIO		0x7E000000		/* i/o registers */
-#define	FRAMEBUFFER	0xA0000000		/* video framebuffer */
+#define	ARMLOCAL	(VIRTIO+IOSIZE)		/* armv7 only */
+#define	VGPIO		(ARMLOCAL+MiB)		/* virtual gpio for pi3 ACT LED */
+#define	FRAMEBUFFER	0xC0000000		/* video framebuffer */
 
 #define	UZERO		0			/* user segment */
 #define	UTZERO		(UZERO+BY2PG)		/* user text start */
-#define	USTKTOP		0x20000000		/* user segment end +1 */
+#define	USTKTOP		0x40000000		/* user segment end +1 */
 #define	USTKSIZE	(8*1024*1024)		/* user stack size */
+#define	TSTKTOP		(USTKTOP-USTKSIZE)	/* sysexec temporary stack */
+#define	TSTKSIZ	 	256
 
 /* address at which to copy and execute rebootcode */
-#define	REBOOTADDR	(KZERO+0x3400)
+#define	REBOOTADDR	(KZERO+0x1800)
 
 /*
  * Legacy...
  */
-#define BLOCKALIGN	32			/* only used in allocb.c */
+#define BLOCKALIGN	64			/* only used in allocb.c */
 #define KSTACK		KSTKSIZE
 
 /*
@@ -71,7 +79,6 @@
 #define BY2WD		4
 #define BY2V		8			/* only used in xalloc.c */
 
-#define CACHELINESZ	32
 #define	PTEMAPMEM	(1024*1024)
 #define	PTEPERTAB	(PTEMAPMEM/BY2PG)
 #define	SEGMAPSIZE	1984
@@ -93,8 +100,7 @@
  *	BUS  addresses as seen from the videocore gpu.
  */
 #define	PHYSDRAM	0
-#define BUSDRAM		0x40000000
-#define	DRAMSIZE	(512*MiB)
-#define	PHYSIO		0x20000000
-#define	BUSIO		0x7E000000
 #define	IOSIZE		(16*MiB)
+
+#define MIN(a, b)	((a) < (b)? (a): (b))
+#define MAX(a, b)	((a) > (b)? (a): (b))
--- a/sys/src/9/bcm/mkfile
+++ b/sys/src/9/bcm/mkfile
@@ -1,7 +1,7 @@
-CONF=pif
-CONFLIST=pif picpuf
+CONF=pi2
+CONFLIST=pi pi2
+CRAPLIST=pif picpuf
 EXTRACOPIES=
-#EXTRACOPIES=''piestand lookout boundary # bovril
 
 loadaddr=0x80008000
 
@@ -104,12 +104,13 @@
 arch.$O clock.$O fpiarm.$O main.$O mmu.$O screen.$O syscall.$O trap.$O: \
 	/$objtype/include/ureg.h
 
-archbcm.$O: ../port/flashif.h
 fpi.$O fpiarm.$O fpimem.$O: fpi.h
-l.$O lexception.$O lproc.$O mmu.$O: arm.s mem.h
+l.$O lexception.$O lproc.$O mmu.$O: mem.h
+l.$O lexception.$O lproc.$O armv6.$O armv7.$O: arm.s
+armv7.$O: cache.v7.s
 main.$O: errstr.h init.h reboot.h
 devmouse.$O mouse.$O screen.$O: screen.h
-devusb.$O: ../port/usb.h
+usbdwc.$O: dwcotg.h ../port/usb.h
 
 init.h:D:	../port/initcode.c init9.s
 	$CC ../port/initcode.c
@@ -123,11 +124,12 @@
 reboot.h:D:	rebootcode.s arm.s arm.h mem.h
 	$AS rebootcode.s
 	# -lc is only for memmove.  -T arg is PADDR(REBOOTADDR)
-	$LD -l -s -T0x3400 -R4 -o reboot.out rebootcode.$O -lc
+	$LD -l -s -T0x1800 -R4 -o reboot.out rebootcode.$O -lc
 	{echo 'uchar rebootcode[]={'
 	 xd -1x reboot.out |
 		sed -e '1,2d' -e 's/^[0-9a-f]+ //' -e 's/ ([0-9a-f][0-9a-f])/0x\1,/g'
 	 echo '};'} > reboot.h
+
 errstr.h:D:	../port/mkerrstr ../port/error.h
 	rc ../port/mkerrstr > errstr.h
 
--- a/sys/src/9/bcm/mmu.c
+++ b/sys/src/9/bcm/mmu.c
@@ -9,19 +9,26 @@
 #define FEXT(d, o, w)	(((d)>>(o)) & ((1<<(w))-1))
 #define L1X(va)		FEXT((va), 20, 12)
 #define L2X(va)		FEXT((va), 12, 8)
+#define L2AP(ap)	l2ap(ap)
+#define L1ptedramattrs	soc.l1ptedramattrs
+#define L2ptedramattrs	soc.l2ptedramattrs
 
 enum {
 	L1lo		= UZERO/MiB,		/* L1X(UZERO)? */
 	L1hi		= (USTKTOP+MiB-1)/MiB,	/* L1X(USTKTOP+MiB-1)? */
+	L2size		= 256*sizeof(PTE),
 };
 
+/*
+ * Set up initial PTEs for cpu0 (called with mmu off)
+ */
 void
-mmuinit(void)
+mmuinit(void *a)
 {
 	PTE *l1, *l2;
 	uintptr pa, va;
 
-	l1 = (PTE*)PADDR(L1);
+	l1 = (PTE*)a;
 	l2 = (PTE*)PADDR(L2);
 
 	/*
@@ -28,8 +35,8 @@
 	 * map all of ram at KZERO
 	 */
 	va = KZERO;
-	for(pa = PHYSDRAM; pa < PHYSDRAM+DRAMSIZE; pa += MiB){
-		l1[L1X(va)] = pa|Dom0|L1AP(Krw)|Section|Cached|Buffered;
+	for(pa = PHYSDRAM; pa < PHYSDRAM+soc.dramsize; pa += MiB){
+		l1[L1X(va)] = pa|Dom0|L1AP(Krw)|Section|L1ptedramattrs;
 		va += MiB;
 	}
 
@@ -36,39 +43,41 @@
 	/*
 	 * identity map first MB of ram so mmu can be enabled
 	 */
-	l1[L1X(PHYSDRAM)] = PHYSDRAM|Dom0|L1AP(Krw)|Section|Cached|Buffered;
+	l1[L1X(PHYSDRAM)] = PHYSDRAM|Dom0|L1AP(Krw)|Section|L1ptedramattrs;
 
 	/*
 	 * map i/o registers 
 	 */
 	va = VIRTIO;
-	for(pa = PHYSIO; pa < PHYSIO+IOSIZE; pa += MiB){
+	for(pa = soc.physio; pa < soc.physio+IOSIZE; pa += MiB){
 		l1[L1X(va)] = pa|Dom0|L1AP(Krw)|Section;
 		va += MiB;
 	}
-
+	pa = soc.armlocal;
+	if(pa)
+		l1[L1X(va)] = pa|Dom0|L1AP(Krw)|Section;
+	
 	/*
-	 * double map exception vectors at top of virtual memory
+	 * double map exception vectors near top of virtual memory
 	 */
 	va = HVECTORS;
 	l1[L1X(va)] = (uintptr)l2|Dom0|Coarse;
-	l2[L2X(va)] = PHYSDRAM|L2AP(Krw)|Small;
+	l2[L2X(va)] = PHYSDRAM|L2AP(Krw)|Small|L2ptedramattrs;
 }
 
 void
-mmuinit1(void)
+mmuinit1()
 {
 	PTE *l1;
 
-	l1 = (PTE*)L1;
-	m->mmul1 = l1;
+	l1 = m->mmul1;
 
 	/*
 	 * undo identity map of first MB of ram
 	 */
 	l1[L1X(PHYSDRAM)] = 0;
-	cachedwbse(&l1[L1X(PHYSDRAM)], sizeof(PTE));
-	mmuinvalidate();
+	cachedwbtlb(&l1[L1X(PHYSDRAM)], sizeof(PTE));
+	mmuinvalidateaddr(PHYSDRAM);
 }
 
 static void
@@ -81,10 +90,11 @@
 	l2 = &proc->mmul2;
 	for(page = *l2; page != nil; page = page->next){
 		if(clear)
-			memset(UINT2PTR(page->va), 0, BY2PG);
+			memset(UINT2PTR(page->va), 0, L2size);
 		l1[page->daddr] = Fault;
 		l2 = &page->next;
 	}
+	coherence();
 	*l2 = proc->mmul2cache;
 	proc->mmul2cache = proc->mmul2;
 	proc->mmul2 = nil;
@@ -93,29 +103,24 @@
 static void
 mmul1empty(void)
 {
-#ifdef notdef
-/* there's a bug in here */
 	PTE *l1;
 
 	/* clean out any user mappings still in l1 */
-	if(m->mmul1lo > L1lo){
+	if(m->mmul1lo > 0){
 		if(m->mmul1lo == 1)
 			m->mmul1[L1lo] = Fault;
 		else
 			memset(&m->mmul1[L1lo], 0, m->mmul1lo*sizeof(PTE));
-		m->mmul1lo = L1lo;
+		m->mmul1lo = 0;
 	}
-	if(m->mmul1hi < L1hi){
-		l1 = &m->mmul1[m->mmul1hi];
-		if((L1hi - m->mmul1hi) == 1)
+	if(m->mmul1hi > 0){
+		l1 = &m->mmul1[L1hi - m->mmul1hi];
+		if(m->mmul1hi == 1)
 			*l1 = Fault;
 		else
-			memset(l1, 0, (L1hi - m->mmul1hi)*sizeof(PTE));
-		m->mmul1hi = L1hi;
+			memset(l1, 0, m->mmul1hi*sizeof(PTE));
+		m->mmul1hi = 0;
 	}
-#else
-	memset(&m->mmul1[L1lo], 0, (L1hi - L1lo)*sizeof(PTE));
-#endif /* notdef */
 }
 
 void
@@ -125,15 +130,7 @@
 	PTE *l1;
 	Page *page;
 
-	/* do kprocs get here and if so, do they need to? */
-	if(m->mmupid == proc->pid && !proc->newtlb)
-		return;
-	m->mmupid = proc->pid;
-
-	/* write back dirty and invalidate l1 caches */
-	cacheuwbinv();
-
-	if(proc->newtlb){
+	if(proc != nil && proc->newtlb){
 		mmul2empty(proc, 1);
 		proc->newtlb = 0;
 	}
@@ -142,19 +139,21 @@
 
 	/* move in new map */
 	l1 = m->mmul1;
+	if(proc != nil)
 	for(page = proc->mmul2; page != nil; page = page->next){
 		x = page->daddr;
 		l1[x] = PPN(page->pa)|Dom0|Coarse;
-		/* know here that L1lo < x < L1hi */
-		if(x+1 - m->mmul1lo < m->mmul1hi - x)
-			m->mmul1lo = x+1;
-		else
-			m->mmul1hi = x;
+		if(x >= L1lo + m->mmul1lo && x < L1hi - m->mmul1hi){
+			if(x+1 - L1lo < L1hi - x)
+				m->mmul1lo = x+1 - L1lo;
+			else
+				m->mmul1hi = L1hi - x;
+		}
 	}
 
 	/* make sure map is in memory */
 	/* could be smarter about how much? */
-	cachedwbse(&l1[L1X(UZERO)], (L1hi - L1lo)*sizeof(PTE));
+	cachedwbtlb(&l1[L1X(UZERO)], (L1hi - L1lo)*sizeof(PTE));
 
 	/* lose any possible stale tlb entries */
 	mmuinvalidate();
@@ -176,9 +175,6 @@
 {
 	Page *page, *next;
 
-	/* write back dirty and invalidate l1 caches */
-	cacheuwbinv();
-
 	mmul2empty(proc, 0);
 	for(page = proc->mmul2cache; page != nil; page = next){
 		next = page->next;
@@ -194,7 +190,7 @@
 
 	/* make sure map is in memory */
 	/* could be smarter about how much? */
-	cachedwbse(&m->mmul1[L1X(UZERO)], (L1hi - L1lo)*sizeof(PTE));
+	cachedwbtlb(&m->mmul1[L1X(UZERO)], (L1hi - L1lo)*sizeof(PTE));
 
 	/* lose any possible stale tlb entries */
 	mmuinvalidate();
@@ -203,39 +199,45 @@
 void
 putmmu(uintptr va, uintptr pa, Page* page)
 {
-	int x;
+	int x, s;
 	Page *pg;
 	PTE *l1, *pte;
 
+	/*
+	 * disable interrupts to prevent flushmmu (called from hzclock)
+	 * from clearing page tables while we are setting them
+	 */
+	s = splhi();
 	x = L1X(va);
 	l1 = &m->mmul1[x];
 	if(*l1 == Fault){
-		/* wasteful - l2 pages only have 256 entries - fix */
+		/* l2 pages only have 256 entries - wastes 3K per 1M of address space */
 		if(up->mmul2cache == nil){
-			/* auxpg since we don't need much? memset if so */
+			spllo();
 			pg = newpage(1, 0, 0);
+			splhi();
+			/* if newpage slept, we might be on a different cpu */
+			l1 = &m->mmul1[x];
 			pg->va = VA(kmap(pg));
-		}
-		else{
+		}else{
 			pg = up->mmul2cache;
 			up->mmul2cache = pg->next;
-			memset(UINT2PTR(pg->va), 0, BY2PG);
 		}
 		pg->daddr = x;
 		pg->next = up->mmul2;
 		up->mmul2 = pg;
 
-		/* force l2 page to memory */
-		cachedwbse((void *)pg->va, BY2PG);
+		/* force l2 page to memory (armv6) */
+		cachedwbtlb((void *)pg->va, L2size);
 
 		*l1 = PPN(pg->pa)|Dom0|Coarse;
-		cachedwbse(l1, sizeof *l1);
+		cachedwbtlb(l1, sizeof *l1);
 
-		if(x >= m->mmul1lo && x < m->mmul1hi){
-			if(x+1 - m->mmul1lo < m->mmul1hi - x)
-				m->mmul1lo = x+1;
+		if(x >= L1lo + m->mmul1lo && x < L1hi - m->mmul1hi){
+			if(x+1 - L1lo < L1hi - x)
+				m->mmul1lo = x+1 - L1lo;
 			else
-				m->mmul1hi = x;
+				m->mmul1hi = L1hi - x;
 		}
 	}
 	pte = UINT2PTR(KADDR(PPN(*l1)));
@@ -247,31 +249,53 @@
 	 */
 	x = Small;
 	if(!(pa & PTEUNCACHED))
-		x |= Cached|Buffered;
+		x |= L2ptedramattrs;
 	if(pa & PTEWRITE)
 		x |= L2AP(Urw);
 	else
 		x |= L2AP(Uro);
 	pte[L2X(va)] = PPN(pa)|x;
-	cachedwbse(&pte[L2X(va)], sizeof pte[0]);
+	cachedwbtlb(&pte[L2X(va)], sizeof(PTE));
 
 	/* clear out the current entry */
 	mmuinvalidateaddr(PPN(va));
 
-	/*  write back dirty entries - we need this because the pio() in
-	 *  fault.c is writing via a different virt addr and won't clean
-	 *  its changes out of the dcache.  Page coloring doesn't work
-	 *  on this mmu because the virtual cache is set associative
-	 *  rather than direct mapped.
-	 */
-	cachedwbinv();
-	if(page->txtflush){
-		cacheiinv();
-		page->txtflush = 0;
+	if((page->txtflush & (1<<m->machno)) != 0){
+		/* pio() sets PG_TXTFLUSH whenever a text pg has been written */
+		cachedwbse((void*)(page->pa|KZERO), BY2PG);
+		cacheiinvse((void*)page->va, BY2PG);
+		page->txtflush &= ~(1<<m->machno);
 	}
-	checkmmu(va, PPN(pa));
+	//checkmmu(va, PPN(pa));
+	splx(s);
 }
 
+void*
+mmuuncache(void* v, usize size)
+{
+	int x;
+	PTE *pte;
+	uintptr va;
+
+	/*
+	 * Simple helper for ucalloc().
+	 * Uncache a Section, must already be
+	 * valid in the MMU.
+	 */
+	va = PTR2UINT(v);
+	assert(!(va & (1*MiB-1)) && size == 1*MiB);
+
+	x = L1X(va);
+	pte = &m->mmul1[x];
+	if((*pte & (Fine|Section|Coarse)) != Section)
+		return nil;
+	*pte &= ~L1ptedramattrs;
+	mmuinvalidateaddr(va);
+	cachedwbinvse(pte, 4);
+
+	return v;
+}
+
 /*
  * Return the number of bytes that can be accessed via KADDR(pa).
  * If pa is not a valid argument to KADDR, return 0.
@@ -304,15 +328,31 @@
 		*pte++ = (pa+n)|Dom0|L1AP(Krw)|Section;
 		mmuinvalidateaddr(va+n);
 	}
-	cachedwbse(pte0, (uintptr)pte - (uintptr)pte0);
+	cachedwbtlb(pte0, (uintptr)pte - (uintptr)pte0);
 	return va + o;
 }
 
-
 void
 checkmmu(uintptr va, uintptr pa)
 {
-	USED(va);
-	USED(pa);
+	int x;
+	PTE *l1, *pte;
+
+	x = L1X(va);
+	l1 = &m->mmul1[x];
+	if(*l1 == Fault){
+		iprint("checkmmu cpu%d va=%lux l1 %p=%ux\n", m->machno, va, l1, *l1);
+		return;
+	}
+	pte = KADDR(PPN(*l1));
+	pte += L2X(va);
+	if(pa == ~0 || (pa != 0 && PPN(*pte) != pa))
+		iprint("checkmmu va=%lux pa=%lux l1 %p=%ux pte %p=%ux\n", va, pa, l1, *l1, pte, *pte);
 }
 
+void
+kunmap(KMap *k)
+{
+	USED(k);
+	coherence();
+}
--- /dev/null
+++ b/sys/src/9/bcm/pi
@@ -1,0 +1,55 @@
+dev
+	root
+	cons
+	swap
+	env
+	pipe
+	proc
+	mnt
+	srv
+	shr
+	swap
+	dup
+	arch
+	ssl
+	tls
+	cap
+	fs
+	ip		arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum inferno
+	draw	screen swcursor
+	mouse	mouse
+	uart	gpio
+	gpio	gpio
+	sd
+	usb
+
+link
+	loopbackmedium
+	ethermedium
+	archbcm
+	usbdwc
+
+ip
+	tcp
+	udp
+	ipifc
+	icmp
+	icmp6
+	ipmux
+
+misc
+	armv6
+	uartmini
+#	sdmmc	emmc
+	dma
+	vcore
+	vfp3	coproc
+
+port
+	int cpuserver = 0;
+
+bootdir
+	/$objtype/bin/paqfs
+	/$objtype/bin/auth/factotum
+	bootfs.paq
+	boot
--- /dev/null
+++ b/sys/src/9/bcm/pi2
@@ -1,0 +1,55 @@
+dev
+	root
+	cons
+	swap
+	env
+	pipe
+	proc
+	mnt
+	srv
+	shr
+	swap
+	dup
+	arch
+	ssl
+	tls
+	cap
+	fs
+	ip		arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum inferno
+	draw	screen swcursor
+	mouse	mouse
+	uart	gpio
+	gpio	gpio
+	sd
+	usb
+
+link
+	loopbackmedium
+	ethermedium
+	archbcm2
+	usbdwc
+
+ip
+	tcp
+	udp
+	ipifc
+	icmp
+	icmp6
+	ipmux
+
+misc
+	armv7
+	uartmini
+	sdmmc	emmc
+	dma
+	vcore
+	vfp3	coproc
+
+port
+	int cpuserver = 0;
+
+bootdir
+	/$objtype/bin/paqfs
+	/$objtype/bin/auth/factotum
+	bootfs.paq
+	boot
--- a/sys/src/9/bcm/pif
+++ /dev/null
@@ -1,56 +1,0 @@
-dev
-	root
-	cons
-	swap
-	env
-	pipe
-	proc
-	mnt
-	srv
-	shr
-	dup
-	arch
-	ssl
-	tls
-	cap
-	fs
-	ip		arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum inferno
-	draw	screen swcursor
-	mouse	mouse
-	uart
-	gpio
-	sd
-	usb
-
-link
-	ethermedium
-	loopbackmedium
-	netdevmedium
-	archbcm
-	usbdwc
-
-ip
-	tcp
-	udp
-	ipifc
-	icmp
-	icmp6
-	ipmux
-	il
-
-misc
-	uartmini
-	sdmmc	emmc
-	dma
-	vcore
-	vfp3	coproc
-
-port
-	int cpuserver = 0;
-
-bootdir
-	/$objtype/bin/paqfs
-	/$objtype/bin/auth/factotum
-	bootfs.paq
-	boot
-
--- a/sys/src/9/bcm/rebootcode.s
+++ b/sys/src/9/bcm/rebootcode.s
@@ -1,8 +1,13 @@
 /*
- * armv6 reboot code
+ * armv6/armv7 reboot code
  */
 #include "arm.s"
 
+#define PTEDRAM		(Dom0|L1AP(Krw)|Section)
+
+#define WFI	WORD	$0xe320f003	/* wait for interrupt */
+#define WFE	WORD	$0xe320f002	/* wait for event */
+
 /*
  * Turn off MMU, then copy the new kernel to its correct location
  * in physical memory.  Then jump to the start of the kernel.
@@ -15,7 +20,7 @@
 	/* copy in arguments before stack gets unmapped */
 	MOVW	R0, R8			/* entry point */
 	MOVW	p2+4(FP), R9		/* source */
-	MOVW	n+8(FP), R10		/* byte count */
+	MOVW	n+8(FP), R6		/* byte count */
 
 	/* SVC mode, interrupts disabled */
 	MOVW	$(PsrDirq|PsrDfiq|PsrMsvc), R1
@@ -29,6 +34,28 @@
 	BIC	$CpCmmu, R1
 	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
 
+	/* continue with reboot only on cpu0 */
+	CPUID(R2)
+	BEQ	bootcpu
+
+	/* other cpus wait for inter processor interrupt from cpu0 */
+	/* turn icache back on */
+	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	ORR	$(CpCicache), R1
+	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	BARRIERS
+dowfi:
+	WFI
+	MOVW	$0x40000060, R1
+	ADD		R2<<2, R1
+	MOVW	0(R1), R0
+	AND		$0x10, R0
+	BEQ		dowfi
+	MOVW	$0x8000, R1
+	BL		(R1)
+	B		dowfi
+
+bootcpu:
 	/* set up a tiny stack for local vars and memmove args */
 	MOVW	R8, SP			/* stack top just before kernel dest */
 	SUB	$20, SP			/* allocate stack frame */
@@ -37,11 +64,12 @@
 	MOVW	R8, 16(SP)		/* save dest (entry point) */
 	MOVW	R8, R0			/* first arg is dest */
 	MOVW	R9, 8(SP)		/* push src */
-	MOVW	R10, 12(SP)		/* push size */
+	MOVW	R6, 12(SP)		/* push size */
 	BL	memmove(SB)
 	MOVW	16(SP), R8		/* restore entry point */
 
 	/* jump to kernel physical entry point */
+	ORR	R8,R8
 	B	(R8)
 	B	0(PC)
 
@@ -51,35 +79,31 @@
  * clobbers R0-R2, and returns with SP invalid.
  */
 TEXT cachesoff(SB), 1, $-4
+	MOVM.DB.W [R14,R1-R10], (R13)		/* save regs on stack */
 
-	/* write back and invalidate caches */
-	BARRIERS
-	MOVW	$0, R0
-	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEwbi), CpCACHEall
-	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall
-
-	/* turn caches off */
+	/* turn caches off, invalidate icache */
 	MRC	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
 	BIC	$(CpCdcache|CpCicache|CpCpredict), R1
 	MCR	CpSC, 0, R1, C(CpCONTROL), C(0), CpMainctl
+	MOVW	$0, R0
+	MCR	CpSC, 0, R0, C(CpCACHE), C(CpCACHEinvi), CpCACHEall
 
 	/* invalidate stale TLBs before changing them */
 	BARRIERS
-	MOVW	$KZERO, R0			/* some valid virtual address */
+	MOVW	$0, R0
 	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
 	BARRIERS
 
-	/* from here on, R0 is base of physical memory */
-	MOVW	$PHYSDRAM, R0
-
 	/* redo double map of first MiB PHYSDRAM = KZERO */
-	MOVW	$(L1+L1X(PHYSDRAM)), R2		/* address of PHYSDRAM's PTE */
+	MOVW	12(R(MACH)), R2		/* m->mmul1 (virtual addr) */
 	MOVW	$PTEDRAM, R1			/* PTE bits */
-	ORR	R0, R1				/* dram base */
 	MOVW	R1, (R2)
+	DSB
+	MCR	CpSC, 0, R2, C(CpCACHE), C(CpCACHEwb), CpCACHEse
 
 	/* invalidate stale TLBs again */
 	BARRIERS
+	MOVW	$0, R0
 	MCR	CpSC, 0, R0, C(CpTLB), C(CpTLBinvu), CpTLBinv
 	BARRIERS
 
@@ -86,8 +110,9 @@
 	/* relocate SB and return address to PHYSDRAM addressing */
 	MOVW	$KSEGM, R1		/* clear segment bits */
 	BIC	R1, R12			/* adjust SB */
-	ORR	R0, R12
+	MOVM.IA.W (R13), [R14,R1-R10]		/* restore regs from stack */
+
+	MOVW	$KSEGM, R1		/* clear segment bits */
 	BIC	R1, R14			/* adjust return address */
-	ORR	R0, R14
 
 	RET
--- a/sys/src/9/bcm/trap.c
+++ b/sys/src/9/bcm/trap.c
@@ -13,6 +13,7 @@
 #include "arm.h"
 
 #define INTREGS		(VIRTIO+0xB200)
+#define	LOCALREGS	(VIRTIO+IOSIZE)
 
 typedef struct Intregs Intregs;
 typedef struct Vctl Vctl;
@@ -20,6 +21,10 @@
 enum {
 	Nvec = 8,		/* # of vectors at start of lexception.s */
 	Fiqenable = 1<<7,
+
+	Localtimerint	= 0x40,
+	Localmboxint	= 0x50,
+	Localintpending	= 0x60,
 };
 
 /*
@@ -46,6 +51,7 @@
 struct Vctl {
 	Vctl	*next;
 	int	irq;
+	int	cpu;
 	u32int	*reg;
 	u32int	mask;
 	void	(*f)(Ureg*, void*);
@@ -52,6 +58,7 @@
 	void	*a;
 };
 
+static Lock vctllock;
 static Vctl *vctl, *vfiq;
 
 static char *trapnames[PsrMask+1] = {
@@ -75,15 +82,17 @@
 {
 	Vpage0 *vpage0;
 
-	/* disable everything */
-	intrsoff();
+	if (m->machno == 0) {
+		/* disable everything */
+		intrsoff();
+		/* set up the exception vectors */
+		vpage0 = (Vpage0*)HVECTORS;
+		memmove(vpage0->vectors, vectors, sizeof(vpage0->vectors));
+		memmove(vpage0->vtable, vtable, sizeof(vpage0->vtable));
+		cacheuwbinv();
+		l2cacheuwbinv();
+	}
 
-	/* set up the exception vectors */
-	vpage0 = (Vpage0*)HVECTORS;
-	memmove(vpage0->vectors, vectors, sizeof(vpage0->vectors));
-	memmove(vpage0->vtable, vtable, sizeof(vpage0->vtable));
-	cacheuwbinv();
-
 	/* set up the stacks for the interrupt modes */
 	setr13(PsrMfiq, (u32int*)(FIQSTKTOP));
 	setr13(PsrMirq, m->sirq);
@@ -95,6 +104,21 @@
 }
 
 void
+intrcpushutdown(void)
+{
+	u32int *enable;
+
+	if(soc.armlocal == 0)
+		return;
+	enable = (u32int*)(LOCALREGS + Localtimerint) + m->machno;
+	*enable = 0;
+	if(m->machno){
+		enable = (u32int*)(LOCALREGS + Localmboxint) + m->machno;
+		*enable = 1;
+	}
+}
+
+void
 intrsoff(void)
 {
 	Intregs *ip;
@@ -120,11 +144,11 @@
 
 	clockintr = 0;
 	for(v = vctl; v; v = v->next)
-		if(*v->reg & v->mask){
+		if(v->cpu == m->machno && (*v->reg & v->mask) != 0){
 			coherence();
 			v->f(ureg, v->a);
 			coherence();
-			if(v->irq == IRQclock)
+			if(v->irq == IRQclock || v->irq == IRQcntps || v->irq == IRQcntpns)
 				clockintr = 1;
 		}
 	return clockintr;
@@ -140,7 +164,7 @@
 
 	v = vfiq;
 	if(v == nil)
-		panic("unexpected item in bagging area");
+		panic("cpu%d: unexpected item in bagging area", m->machno);
 	m->intr++;
 	ureg->pc -= 4;
 	coherence();
@@ -160,7 +184,16 @@
 	if(v == nil)
 		panic("irqenable: no mem");
 	v->irq = irq;
-	if(irq >= IRQbasic){
+	v->cpu = 0;
+	if(irq >= IRQlocal){
+		v->reg = (u32int*)(LOCALREGS + Localintpending) + m->machno;
+		if(irq >= IRQmbox0)
+			enable = (u32int*)(LOCALREGS + Localmboxint) + m->machno;
+		else
+			enable = (u32int*)(LOCALREGS + Localtimerint) + m->machno;
+		v->mask = 1 << (irq - IRQlocal);
+		v->cpu = m->machno;
+	}else if(irq >= IRQbasic){
 		enable = &ip->ARMenable;
 		v->reg = &ip->ARMpending;
 		v->mask = 1 << (irq - IRQbasic);
@@ -171,6 +204,7 @@
 	}
 	v->f = f;
 	v->a = a;
+	lock(&vctllock);
 	if(irq == IRQfiq){
 		assert((ip->FIQctl & Fiqenable) == 0);
 		assert((*enable & v->mask) == 0);
@@ -179,8 +213,15 @@
 	}else{
 		v->next = vctl;
 		vctl = v;
-		*enable = v->mask;
+		if(irq >= IRQmbox0){
+			if(irq <= IRQmbox3)
+				*enable |= 1 << (irq - IRQmbox0);
+		}else if(irq >= IRQlocal)
+			*enable |= 1 << (irq - IRQlocal);
+		else
+			*enable = v->mask;
 	}
+	unlock(&vctllock);
 }
 
 static char *
--- a/sys/src/9/bcm/uartmini.c
+++ b/sys/src/9/bcm/uartmini.c
@@ -10,35 +10,11 @@
 #include "fns.h"
 #include "io.h"
 
-#define GPIOREGS	(VIRTIO+0x200000)
 #define AUXREGS		(VIRTIO+0x215000)
 #define	OkLed		16
 #define	TxPin		14
 #define	RxPin		15
 
-/* GPIO regs */
-enum {
-	Fsel0	= 0x00>>2,
-		FuncMask= 0x7,
-		Input	= 0x0,
-		Output	= 0x1,
-		Alt0	= 0x4,
-		Alt1	= 0x5,
-		Alt2	= 0x6,
-		Alt3	= 0x7,
-		Alt4	= 0x3,
-		Alt5	= 0x2,
-	Set0	= 0x1c>>2,
-	Clr0	= 0x28>>2,
-	Lev0	= 0x34>>2,
-	PUD	= 0x94>>2,
-		Off	= 0x0,
-		Pulldown= 0x1,
-		Pullup	= 0x2,
-	PUDclk0	= 0x98>>2,
-	PUDclk1	= 0x9c>>2,
-};
-
 /* AUX regs */
 enum {
 	Irq	= 0x00>>2,
@@ -73,57 +49,12 @@
 	.regs	= (u32int*)AUXREGS,
 	.name	= "uart0",
 	.freq	= 250000000,
+	.baud	= 115200,
 	.phys	= &miniphysuart,
 };
 
-void
-gpiosel(uint pin, int func)
-{	
-	u32int *gp, *fsel;
-	int off;
+static int baud(Uart*, int);
 
-	gp = (u32int*)GPIOREGS;
-	fsel = &gp[Fsel0 + pin/10];
-	off = (pin % 10) * 3;
-	*fsel = (*fsel & ~(FuncMask << off)) | func << off;
-}
-
-void
-gpiopulloff(uint pin)
-{
-	u32int *gp, *reg;
-	u32int mask;
-
-	gp = (u32int*)GPIOREGS;
-	reg = &gp[PUDclk0 + pin/32];
-	mask = 1 << (pin % 32);
-	gp[PUD] = Off;
-	microdelay(1);
-	*reg = mask;
-	microdelay(1);
-	*reg = 0;
-}
-
-void
-gpioout(uint pin, int set)
-{
-	u32int *gp;
-	int v;
-
-	gp = (u32int*)GPIOREGS;
-	v = set? Set0: Clr0;
-	gp[v + pin/32] = 1 << (pin % 32);
-}
-
-int
-gpioin(uint pin)
-{
-	u32int *gp;
-
-	gp = (u32int*)GPIOREGS;
-	return (gp[Lev0 + pin/32] & (1 << (pin % 32))) != 0;
-}
-
 static void
 interrupt(Ureg*, void *arg)
 {
@@ -162,10 +93,12 @@
 	gpiosel(TxPin, Alt5);
 	gpiosel(RxPin, Alt5);
 	gpiopulloff(TxPin);
-	gpiopulloff(RxPin);
+	gpiopullup(RxPin);
 	ap[Enables] |= UartEn;
 	ap[MuIir] = 6;
+	ap[MuLcr] = Bits8;
 	ap[MuCntl] = TxEn|RxEn;
+	baud(uart, uart->baud);
 	if(ie){
 		intrenable(IRQaux, interrupt, uart, 0, "uart");
 		ap[MuIer] = RxIen|TxIen;
@@ -370,13 +303,12 @@
 		break;
 	}
 
-	uartctl(uart, "b9600 l8 pn s1");
+	if(!uart->enabled)
+		(*uart->phys->enable)(uart, 0);
+	uartctl(uart, "l8 pn s1");
 	if(*cmd != '\0')
 		uartctl(uart, cmd);
 
-	if(!uart->enabled)
-		(*uart->phys->enable)(uart, 0);
-
 	consuart = uart;
 	uart->console = 1;
 }
@@ -405,8 +337,26 @@
 okay(int on)
 {
 	static int first;
+	static int okled, polarity;
+	char *p;
 
-	if(!first++)
-		gpiosel(OkLed, Output);
-	gpioout(OkLed, !on);
+	if(!first++){
+		p = getconf("bcm2709.disk_led_gpio");
+		if(p == nil)
+			p = getconf("bcm2708.disk_led_gpio");
+		if(p != nil)
+			okled = strtol(p, 0, 0);
+		else
+			okled = 'v';
+		p = getconf("bcm2709.disk_led_active_low");
+		if(p == nil)
+			p = getconf("bcm2708.disk_led_active_low");
+		polarity = (p == nil || *p == '1');
+		if(okled != 'v')
+			gpiosel(okled, Output);
+	}
+	if(okled == 'v')
+		vgpset(0, on);
+	else if(okled != 0)
+		gpioout(okled, on^polarity);
 }
--- a/sys/src/9/bcm/usbdwc.c
+++ b/sys/src/9/bcm/usbdwc.c
@@ -33,6 +33,18 @@
 
 	Read		= 0,
 	Write		= 1,
+
+	/*
+	 * Workaround for an unexplained glitch where an Ack interrupt
+	 * is received without Chhltd, whereupon all channels remain
+	 * permanently busy and can't be halted.  This was only seen
+	 * when the controller is reading a sequence of bulk input
+	 * packets in DMA mode.  Setting Slowbulkin=1 will avoid the
+	 * lockup by reading packets individually with an interrupt
+	 * after each.  More recent chips don't seem to exhibit the
+	 * problem, so it's probably safe to leave this off now.
+	 */
+	Slowbulkin	= 0,
 };
 
 typedef struct Ctlr Ctlr;
@@ -39,10 +51,11 @@
 typedef struct Epio Epio;
 
 struct Ctlr {
+	Lock;
 	Dwcregs	*regs;		/* controller registers */
 	int	nchan;		/* number of host channels */
 	ulong	chanbusy;	/* bitmap of in-use channels */
-	QLock	chanlock;	/* serialise access to chanbusy */
+	Lock	chanlock;	/* serialise access to chanbusy */
 	QLock	split;		/* serialise split transactions */
 	int	splitretry;	/* count retries of Nyet */
 	int	sofchan;	/* bitmap of channels waiting for sof */
@@ -52,7 +65,11 @@
 };
 
 struct Epio {
-	QLock;
+	union {
+		QLock	rlock;
+		QLock	ctllock;
+	};
+	QLock	wlock;
 	Block	*cb;
 	ulong	lastpoll;
 };
@@ -61,29 +78,48 @@
 static int debug;
 
 static char Ebadlen[] = "bad usb request length";
-static char Enotconfig[] = "usb endpoint not configured";
 
 static void clog(Ep *ep, Hostchan *hc);
 static void logdump(Ep *ep);
 
+static void
+filock(Lock *l)
+{
+	int x;
+
+	x = splfhi();
+	ilock(l);
+	l->sr = x;
+}
+
+static void
+fiunlock(Lock *l)
+{
+	iunlock(l);
+}
+
 static Hostchan*
 chanalloc(Ep *ep)
 {
 	Ctlr *ctlr;
 	int bitmap, i;
+	static int first;
 
 	ctlr = ep->hp->aux;
-	qlock(&ctlr->chanlock);
+retry:
+	lock(&ctlr->chanlock);
 	bitmap = ctlr->chanbusy;
 	for(i = 0; i < ctlr->nchan; i++)
 		if((bitmap & (1<<i)) == 0){
 			ctlr->chanbusy = bitmap | 1<<i;
-			qunlock(&ctlr->chanlock);
+			unlock(&ctlr->chanlock);
 			return &ctlr->regs->hchan[i];
 		}
-	qunlock(&ctlr->chanlock);
-	panic("miller is a lazy git");
-	return nil;
+	unlock(&ctlr->chanlock);
+	if(!first++)
+		print("usbdwc: all host channels busy - retrying\n");
+	tsleep(&up->sleep, return0, 0, 1);
+	goto retry;
 }
 
 static void
@@ -94,9 +130,9 @@
 
 	ctlr = ep->hp->aux;
 	i = chan - ctlr->regs->hchan;
-	qlock(&ctlr->chanlock);
+	lock(&ctlr->chanlock);
 	ctlr->chanbusy &= ~(1<<i);
-	qunlock(&ctlr->chanlock);
+	unlock(&ctlr->chanlock);
 }
 
 static void
@@ -158,7 +194,7 @@
 	Dwcregs *r;
 
 	r = a;
-	return r->gintsts & Sofintr;
+	return (r->gintmsk & Sofintr) == 0;
 }
 
 static void
@@ -165,16 +201,15 @@
 sofwait(Ctlr *ctlr, int n)
 {
 	Dwcregs *r;
-	int x;
 
 	r = ctlr->regs;
 	do{
+		filock(ctlr);
 		r->gintsts = Sofintr;
-		x = splfhi();
 		ctlr->sofchan |= 1<<n;
 		r->gintmsk |= Sofintr;
+		fiunlock(ctlr);
 		sleep(&ctlr->chanintr[n], sofdone, r);
-		splx(x);
 	}while((r->hfnum & 7) == 6);
 }
 
@@ -192,7 +227,7 @@
 static int
 chanwait(Ep *ep, Ctlr *ctlr, Hostchan *hc, int mask)
 {
-	int intr, n, x, ointr;
+	int intr, n, ointr;
 	ulong start, now;
 	Dwcregs *r;
 
@@ -200,13 +235,14 @@
 	n = hc - r->hchan;
 	for(;;){
 restart:
-		x = splfhi();
+		filock(ctlr);
 		r->haintmsk |= 1<<n;
 		hc->hcintmsk = mask;
-		sleep(&ctlr->chanintr[n], chandone, hc);
+		fiunlock(ctlr);
+		tsleep(&ctlr->chanintr[n], chandone, hc, 1000);
+		if((intr = hc->hcint) == 0)
+			goto restart;
 		hc->hcintmsk = 0;
-		splx(x);
-		intr = hc->hcint;
 		if(intr & Chhltd)
 			return intr;
 		start = fastticks(0);
@@ -218,13 +254,14 @@
 				if((ointr != Ack && ointr != (Ack|Xfercomp)) ||
 				   intr != (Ack|Chhltd|Xfercomp) ||
 				   (now - start) > 60)
-					dprint("await %x after %ld %x -> %x\n",
+					dprint("await %x after %ldµs %x -> %x\n",
 						mask, now - start, ointr, intr);
 				return intr;
 			}
 			if((intr & mask) == 0){
-				dprint("ep%d.%d await %x intr %x -> %x\n",
-					ep->dev->nb, ep->nb, mask, ointr, intr);
+				if(intr != Nak)
+					dprint("ep%d.%d await %x after %ldµs intr %x -> %x\n",
+						ep->dev->nb, ep->nb, mask, now - start, ointr, intr);
 				goto restart;
 			}
 			now = fastticks(0);
@@ -254,6 +291,8 @@
 	int i;
 
 	hc = &ctlr->regs->hchan[n];
+	if((hc->hcint & hc->hcintmsk) == 0)
+		return 1;
 	if(ctlr->debugchan & (1<<n))
 		clog(nil, hc);
 	if((hc->hcsplt & Spltena) == 0)
@@ -347,7 +386,7 @@
 	else
 		n = len;
 	hc->hctsiz = n | npkt<<OPktcnt | pid;
-	hc->hcdma  = PADDR(a);
+	hc->hcdma  = dmaaddr(a);
 
 	nleft = len;
 	logstart(ep);
@@ -378,13 +417,19 @@
 		}
 		hc->hcchar = (hc->hcchar &~ Chdis) | Chen;
 		clog(ep, hc);
+wait:
 		if(ep->ttype == Tbulk && dir == Epin)
-			i = chanwait(ep, ctlr, hc, /* Ack| */ Chhltd);
+			i = chanwait(ep, ctlr, hc, Chhltd);
 		else if(ep->ttype == Tintr && (hc->hcsplt & Spltena))
 			i = chanwait(ep, ctlr, hc, Chhltd);
 		else
 			i = chanwait(ep, ctlr, hc, Chhltd|Nak);
 		clog(ep, hc);
+		if(hc->hcint != i){
+			dprint("chanwait intr %ux->%ux\n", i, hc->hcint);
+			if((i = hc->hcint) == 0)
+				goto wait;
+		}
 		hc->hcint = i;
 
 		if(hc->hcsplt & Spltena){
@@ -405,12 +450,12 @@
 				continue;
 			}
 			logdump(ep);
-			print("usbotg: ep%d.%d error intr %8.8ux\n",
+			print("usbdwc: ep%d.%d error intr %8.8ux\n",
 				ep->dev->nb, ep->nb, i);
 			if(i & ~(Chhltd|Ack))
 				error(Eio);
 			if(hc->hcdma != hcdma)
-				print("usbotg: weird hcdma %x->%x intr %x->%x\n",
+				print("usbdwc: weird hcdma %ux->%ux intr %ux->%ux\n",
 					hcdma, hc->hcdma, i, hc->hcint);
 		}
 		n = hc->hcdma - hcdma;
@@ -420,13 +465,13 @@
 			else
 				continue;
 		}
-		if(dir == Epin && ep->ttype == Tbulk && n == nleft){
+		if(dir == Epin && ep->ttype == Tbulk){
 			nt = (hctsiz & Xfersize) - (hc->hctsiz & Xfersize);
 			if(nt != n){
 				if(n == ROUND(nt, 4))
 					n = nt;
 				else
-					print("usbotg: intr %8.8ux "
+					print("usbdwc: intr %8.8ux "
 						"dma %8.8ux-%8.8ux "
 						"hctsiz %8.8ux-%8.ux\n",
 						i, hcdma, hc->hcdma, hctsiz,
@@ -491,7 +536,7 @@
 		nexterror();
 	}
 	chansetup(hc, ep);
-	if(rw == Read && ep->ttype == Tbulk)
+	if(Slowbulkin && rw == Read && ep->ttype == Tbulk)
 		n = multitrans(ep, hc, rw, a, n);
 	else{
 		n = chanio(ep, hc, rw == Read? Epin : Epout, ep->toggle[rw],
@@ -524,8 +569,8 @@
 		if(datalen <= 0 || datalen > Maxctllen)
 			error(Ebadlen);
 		/* XXX cache madness */
-		epio->cb = b = allocb(ROUND(datalen, ep->maxpkt) + CACHELINESZ);
-		b->wp = (uchar*)ROUND((uintptr)b->wp, CACHELINESZ);
+		epio->cb = b = allocb(ROUND(datalen, ep->maxpkt));
+		assert(((uintptr)b->wp & (BLOCKALIGN-1)) == 0);
 		memset(b->wp, 0x55, b->lim - b->wp);
 		cachedwbinvse(b->wp, b->lim - b->wp);
 		data = b->wp;
@@ -550,6 +595,7 @@
 		}else
 			b->wp += chanio(ep, hc, Epin, DATA1, data, datalen);
 		chanio(ep, hc, Epout, DATA1, nil, 0);
+		cachedinvse(b->rp, BLEN(b));
 		n = Rsetuplen;
 	}else{
 		if(datalen > 0)
@@ -627,7 +673,7 @@
 	greset(r, Rxfflsh);
 	r->grstctl = TXF_ALL;
 	greset(r, Txfflsh);
-	dprint("usbotg: FIFO depth %d sizes rx/nptx/ptx %8.8ux %8.8ux %8.8ux\n",
+	dprint("usbdwc: FIFO depth %d sizes rx/nptx/ptx %8.8ux %8.8ux %8.8ux\n",
 		n, r->grxfsiz, r->gnptxfsiz, r->hptxfsiz);
 
 	r->hport0 = Prtpwr|Prtconndet|Prtenchng|Prtovrcurrchng;
@@ -654,6 +700,7 @@
 	ctlr = hp->aux;
 	r = ctlr->regs;
 	wakechan = 0;
+	filock(ctlr);
 	intr = r->gintsts;
 	if(intr & Hcintr){
 		haint = r->haint & r->haintmsk;
@@ -679,6 +726,7 @@
 		ctlr->wakechan |= wakechan;
 		armtimerset(1);
 	}
+	fiunlock(ctlr);
 }
 
 static void
@@ -686,14 +734,14 @@
 {
 	Ctlr *ctlr;
 	uint wakechan;
-	int i, x;
+	int i;
 
 	ctlr = a;
-	x = splfhi();
+	filock(ctlr);
 	armtimerset(0);
 	wakechan = ctlr->wakechan;
 	ctlr->wakechan = 0;
-	splx(x);
+	fiunlock(ctlr);
 	for(i = 0; wakechan; i++){
 		if(wakechan & 1)
 			wakeup(&ctlr->chanintr[i]);
@@ -704,11 +752,12 @@
 static void
 epopen(Ep *ep)
 {
-	ddprint("usbotg: epopen ep%d.%d ttype %d\n",
+	ddprint("usbdwc: epopen ep%d.%d ttype %d\n",
 		ep->dev->nb, ep->nb, ep->ttype);
 	switch(ep->ttype){
-	case Tnone:
-		error(Enotconfig);
+	default:
+		error("endpoint type not supported");
+		return;
 	case Tintr:
 		assert(ep->pollival > 0);
 		/* fall through */
@@ -717,6 +766,8 @@
 			ep->toggle[Read] = DATA0;
 		if(ep->toggle[Write] == 0)
 			ep->toggle[Write] = DATA0;
+		/* fall through */
+	case Tctl:
 		break;
 	}
 	ep->aux = malloc(sizeof(Epio));
@@ -727,7 +778,7 @@
 static void
 epclose(Ep *ep)
 {
-	ddprint("usbotg: epclose ep%d.%d ttype %d\n",
+	ddprint("usbdwc: epclose ep%d.%d ttype %d\n",
 		ep->dev->nb, ep->nb, ep->ttype);
 	switch(ep->ttype){
 	case Tctl:
@@ -743,6 +794,7 @@
 epread(Ep *ep, void *a, long n)
 {
 	Epio *epio;
+	QLock *q;
 	Block *b;
 	uchar *p;
 	ulong elapsed;
@@ -750,10 +802,11 @@
 
 	ddprint("epread ep%d.%d %ld\n", ep->dev->nb, ep->nb, n);
 	epio = ep->aux;
+	q = ep->ttype == Tctl? &epio->ctllock : &epio->rlock;
 	b = nil;
-	qlock(epio);
+	qlock(q);
 	if(waserror()){
-		qunlock(epio);
+		qunlock(q);
 		if(b)
 			freeb(b);
 		nexterror();
@@ -763,7 +816,7 @@
 		error(Egreg);
 	case Tctl:
 		nr = ctldata(ep, a, n);
-		qunlock(epio);
+		qunlock(q);
 		poperror();
 		return nr;
 	case Tintr:
@@ -773,13 +826,15 @@
 		/* fall through */
 	case Tbulk:
 		/* XXX cache madness */
-		b = allocb(ROUND(n, ep->maxpkt) + CACHELINESZ);
-		p = (uchar*)ROUND((uintptr)b->base, CACHELINESZ);
-		cachedwbinvse(p, n);
+		b = allocb(ROUND(n, ep->maxpkt));
+		p = b->rp;
+		assert(((uintptr)p & (BLOCKALIGN-1)) == 0);
+		cachedinvse(p, n);
 		nr = eptrans(ep, Read, p, n);
+		cachedinvse(p, nr);
 		epio->lastpoll = TK2MS(m->ticks);
 		memmove(a, p, nr);
-		qunlock(epio);
+		qunlock(q);
 		freeb(b);
 		poperror();
 		return nr;
@@ -790,6 +845,7 @@
 epwrite(Ep *ep, void *a, long n)
 {
 	Epio *epio;
+	QLock *q;
 	Block *b;
 	uchar *p;
 	ulong elapsed;
@@ -796,10 +852,11 @@
 
 	ddprint("epwrite ep%d.%d %ld\n", ep->dev->nb, ep->nb, n);
 	epio = ep->aux;
+	q = ep->ttype == Tctl? &epio->ctllock : &epio->wlock;
 	b = nil;
-	qlock(epio);
+	qlock(q);
 	if(waserror()){
-		qunlock(epio);
+		qunlock(q);
 		if(b)
 			freeb(b);
 		nexterror();
@@ -815,8 +872,9 @@
 	case Tctl:
 	case Tbulk:
 		/* XXX cache madness */
-		b = allocb(n + CACHELINESZ);
-		p = (uchar*)ROUND((uintptr)b->base, CACHELINESZ);
+		b = allocb(n);
+		p = b->wp;
+		assert(((uintptr)p & (BLOCKALIGN-1)) == 0);
 		memmove(p, a, n);
 		cachedwbse(p, n);
 		if(ep->ttype == Tctl)
@@ -825,7 +883,7 @@
 			n = eptrans(ep, Write, p, n);
 			epio->lastpoll = TK2MS(m->ticks);
 		}
-		qunlock(epio);
+		qunlock(q);
 		freeb(b);
 		poperror();
 		return n;
@@ -847,11 +905,11 @@
 	assert(port == 1);
 	ctlr = hp->aux;
 	r = ctlr->regs;
-	dprint("usbotg enable=%d; sts %#x\n", on, r->hport0);
+	dprint("usbdwc enable=%d; sts %#x\n", on, r->hport0);
 	if(!on)
 		r->hport0 = Prtpwr | Prtena;
 	tsleep(&up->sleep, return0, 0, Enabledelay);
-	dprint("usbotg enable=%d; sts %#x\n", on, r->hport0);
+	dprint("usbdwc enable=%d; sts %#x\n", on, r->hport0);
 	return 0;
 }
 
@@ -865,7 +923,7 @@
 	assert(port == 1);
 	ctlr = hp->aux;
 	r = ctlr->regs;
-	dprint("usbotg reset=%d; sts %#x\n", on, r->hport0);
+	dprint("usbdwc reset=%d; sts %#x\n", on, r->hport0);
 	if(!on)
 		return 0;
 	r->hport0 = Prtpwr | Prtrst;
@@ -876,9 +934,9 @@
 	b = s & (Prtconndet|Prtenchng|Prtovrcurrchng);
 	if(b != 0)
 		r->hport0 = Prtpwr | b;
-	dprint("usbotg reset=%d; sts %#x\n", on, s);
+	dprint("usbdwc reset=%d; sts %#x\n", on, s);
 	if((s & Prtena) == 0)
-		print("usbotg: host port not enabled after reset");
+		print("usbdwc: host port not enabled after reset");
 	return 0;
 }
 
@@ -948,7 +1006,7 @@
 	id = ctlr->regs->gsnpsid;
 	if((id>>16) != ('O'<<8 | 'T'))
 		return -1;
-	dprint("usbotg: rev %d.%3.3x\n", (id>>12)&0xF, id&0xFFF);
+	dprint("usbdwc: rev %d.%3.3x\n", (id>>12)&0xF, id&0xFFF);
 
 	intrenable(IRQtimerArm, irqintr, ctlr, 0, "dwc");
 
--- a/sys/src/9/bcm/vcore.c
+++ b/sys/src/9/bcm/vcore.c
@@ -12,6 +12,7 @@
 
 typedef struct Prophdr Prophdr;
 typedef struct Fbinfo Fbinfo;
+typedef struct Vgpio Vgpio;
 
 enum {
 	Read		= 0x00>>2,
@@ -33,7 +34,7 @@
 	TagResp		= 1<<31,
 
 	TagGetfwrev	= 0x00000001,
-	TagGetbrdrev	= 0x00010002,
+	TagGetrev	= 0x00010002,
 	TagGetmac	= 0x00010003,
 	TagGetram	= 0x00010005,
 	TagGetpower	= 0x00020001,
@@ -40,6 +41,9 @@
 	TagSetpower	= 0x00028001,
 		Powerwait	= 1<<1,
 	TagGetclkspd= 0x00030002,
+	TagGetclkmax= 0x00030004,
+	TagSetclkspd= 0x00038002,
+	TagGettemp	= 0x00030006,
 	TagFballoc	= 0x00040001,
 	TagFbfree	= 0x00048001,
 	TagFbblank	= 0x00040002,
@@ -49,8 +53,11 @@
 	TagSetvres	= 0x00048004,
 	TagGetdepth	= 0x00040005,
 	TagSetdepth	= 0x00048005,
-	TagGetrgb	= 0x00044006,
+	TagGetrgb	= 0x00040006,
 	TagSetrgb	= 0x00048006,
+	TagGetGpio	= 0x00040010,
+
+	Nvgpio		= 2,
 };
 
 struct Fbinfo {
@@ -76,6 +83,15 @@
 	u32int	data[1];
 };
 
+struct Vgpio {
+	u32int	*counts;
+	u16int	incs;
+	u16int	decs;
+	int	ison;
+};
+
+static Vgpio vgpio;
+
 static void
 vcwrite(uint chan, int val)
 {
@@ -115,7 +131,8 @@
 	uintptr r;
 	int n;
 	Prophdr *prop;
-	static uintptr base = BUSDRAM;
+	uintptr aprop;
+	static int busaddr = 1;
 
 	if(rsplen < vallen)
 		rsplen = vallen;
@@ -132,15 +149,18 @@
 		memmove(prop->data, buf, vallen);
 	cachedwbinvse(prop, prop->len);
 	for(;;){
-		vcwrite(ChanProps, PADDR(prop) + base);
+		aprop = busaddr? dmaaddr(prop) : PTR2UINT(prop);
+		vcwrite(ChanProps, aprop);
 		r = vcread(ChanProps);
-		if(r == PADDR(prop) + base)
+		if(r == aprop)
 			break;
-		if(base == 0)
+		if(!busaddr)
 			return -1;
-		base = 0;
+		busaddr = 0;
 	}
-	if(prop->req == RspOk && prop->tag == tag && prop->taglen & TagResp) {
+	if(prop->req == RspOk &&
+	   prop->tag == tag &&
+	   (prop->taglen&TagResp)) {
 		if((n = prop->taglen & ~TagResp) < rsplen)
 			rsplen = n;
 		memmove(buf, prop->data, rsplen);
@@ -158,6 +178,7 @@
 fbdefault(int *width, int *height, int *depth)
 {
 	u32int buf[3];
+	char *p;
 
 	if(vcreq(TagGetres, &buf[0], 0, 2*4) != 2*4 ||
 	   vcreq(TagGetdepth, &buf[2], 0, 4) != 4)
@@ -164,7 +185,10 @@
 		return -1;
 	*width = buf[0];
 	*height = buf[1];
-	*depth = buf[2];
+	if((p = getconf("bcm2708_fb.fbdepth")) != nil)
+		*depth = atoi(p);
+	else
+		*depth = buf[2];
 	return 0;
 }
 
@@ -184,7 +208,7 @@
 	fi->yres = fi->yresvirtual = *height;
 	fi->bpp = *depth;
 	cachedwbinvse(fi, sizeof(*fi));
-	vcwrite(ChanFb, DMAADDR(fi));
+	vcwrite(ChanFb, dmaaddr(fi));
 	if(vcread(ChanFb) != 0)
 		return 0;
 	va = mmukmap(FRAMEBUFFER, PADDR(fi->base), fi->screensize);
@@ -213,7 +237,7 @@
 	u32int buf[2];
 
 	buf[0] = dev;
-	buf[1] = Powerwait | (on? 1: 0);
+	buf[1] = Powerwait | (on? 1 : 0);
 	vcreq(TagSetpower, buf, sizeof buf, sizeof buf);
 }
 
@@ -250,23 +274,27 @@
 }
 
 /*
- * Get firmware revision
+ * Get board revision
  */
 uint
-getfirmware(void)
+getboardrev(void)
 {
 	u32int buf[1];
 
-	if(vcreq(TagGetfwrev, buf, 0, sizeof buf) != sizeof buf)
+	if(vcreq(TagGetrev, buf, 0, sizeof buf) != sizeof buf)
 		return 0;
 	return buf[0];
 }
 
+/*
+ * Get firmware revision
+ */
 uint
-getrevision(void)
+getfirmware(void)
 {
 	u32int buf[1];
-	if(vcreq(TagGetbrdrev, buf, 0, sizeof buf) != sizeof buf)
+
+	if(vcreq(TagGetfwrev, buf, 0, sizeof buf) != sizeof buf)
 		return 0;
 	return buf[0];
 }
@@ -299,13 +327,63 @@
 	return buf[1];
 }
 
+/*
+ * Set clock rate to hz (or max speed if hz == 0)
+ */
+void
+setclkrate(int clkid, ulong hz)
+{
+	u32int buf[2];
+
+	buf[0] = clkid;
+	if(hz != 0)
+		buf[1] = hz;
+	else if(vcreq(TagGetclkmax, buf, sizeof(buf[0]), sizeof(buf)) != sizeof buf)
+		return;
+	vcreq(TagSetclkspd, buf, sizeof(buf), sizeof(buf));
+}
+
+/*
+ * Get cpu temperature
+ */
 uint
-gettemp(int tempid)
+getcputemp(void)
 {
 	u32int buf[2];
-	buf[0] = tempid;
-	if(vcreq(0x00030006, buf, sizeof(buf[0]), sizeof(buf)) != sizeof buf)
-		return 0;
 
+	buf[0] = 0;
+	if(vcreq(TagGettemp, buf, sizeof(buf[0]), sizeof buf) != sizeof buf)
+		return 0;
 	return buf[1];
+}
+
+/*
+ * Virtual GPIO - used for ACT LED on pi3
+ */
+void
+vgpinit(void)
+{
+	u32int buf[1];
+	uintptr va;
+
+	buf[0] = 0;
+	if(vcreq(TagGetGpio, buf, 0, sizeof(buf)) != sizeof buf || buf[0] == 0)
+		return;
+	va = mmukmap(VGPIO, buf[0] & ~0xC0000000, BY2PG);
+	if(va == 0)
+		return;
+	vgpio.counts = (u32int*)va;
+}
+
+void
+vgpset(uint port, int on)
+{
+	if(vgpio.counts == nil || port >= Nvgpio || on == vgpio.ison)
+		return;
+	if(on)
+		vgpio.incs++;
+	else
+		vgpio.decs++;
+	vgpio.counts[port] = (vgpio.incs << 16) | vgpio.decs;
+	vgpio.ison = on;
 }
--- a/sys/src/9/bcm/vfp3.c
+++ b/sys/src/9/bcm/vfp3.c
@@ -163,7 +163,10 @@
 	static int printed;
 
 	/* clear pending exceptions; no traps in vfp3; all v7 ops are scalar */
-	m->fpscr = Dn | Fz | FPRNR | (FPINVAL | FPZDIV | FPOVFL) & ~Alltraps;
+	m->fpscr = Dn | FPRNR | (FPINVAL | FPZDIV | FPOVFL) & ~Alltraps;
+	/* VFPv2 needs software support for underflows, so force them to zero */
+	if(m->havefp == VFPv2)
+		m->fpscr |= Fz;
 	fpwr(Fpscr, m->fpscr);
 	m->fpconfiged = 1;
 
@@ -278,7 +281,7 @@
 {
 	if(p->fpstate == FPactive){
 		if(p->state == Moribund)
-			fpclear();
+			fpoff();
 		else{
 			/*
 			 * Fpsave() stores without handling pending
@@ -371,8 +374,6 @@
 static void
 mathemu(Ureg *)
 {
-	if(m->havefp == VFPv3 && !(fprd(Fpexc) & (Fpex|Fpdex)))
-		iprint("mathemu: not an FP exception but an unknown FP opcode\n");
 	switch(up->fpstate){
 	case FPemu:
 		error("illegal instruction: VFP opcode in emulated mode");
@@ -472,6 +473,7 @@
 {
 	int s, nfp, cop, op;
 	uintptr pc;
+	static int already;
 
 	if(waserror()){
 		postnote(up, 1, up->errstr, NDebug);
@@ -484,16 +486,14 @@
 	nfp = 0;
 	pc = ureg->pc;
 	validaddr(pc, 4, 0);
-	if(!condok(ureg->psr, *(ulong*)pc >> 28))
-		iprint("fpuemu: conditional instr shouldn't have got here\n");
 	op  = (*(ulong *)pc >> 24) & MASK(4);
 	cop = (*(ulong *)pc >>  8) & MASK(4);
 	if(m->fpon)
 		fpstuck(pc);		/* debugging; could move down 1 line */
 	if (ISFPAOP(cop, op)) {		/* old arm 7500 fpa opcode? */
-//		iprint("fpuemu: fpa instr %#8.8lux at %#p\n", *(ulong *)pc, pc);
-//		error("illegal instruction: old arm 7500 fpa opcode");
 		s = spllo();
+		if(!already++)
+			pprint("warning: emulated arm7500 fpa instr %#8.8lux at %#p\n", *(ulong *)pc, pc);
 		if(waserror()){
 			splx(s);
 			nexterror();
@@ -503,7 +503,7 @@
 			m->fppc = m->fpcnt = 0;
 		splx(s);
 		poperror();
-	} else if (ISVFPOP(cop, op)) {	/* if vfp, fpu must be off */
+	} else if (ISVFPOP(cop, op)) {	/* if vfp, fpu off or unsupported instruction */
 		mathemu(ureg);		/* enable fpu & retry */
 		nfp = 1;
 	}