code: 9ferno

Download patch

ref: 8411fca67137ba5d0e22d8184633a83fd25fe0a5
parent: 5c30140aa4d842dbb700cd6c8b9d7296fd91da84
author: joe9 <joe9mail@gmail.com>
date: Fri Jul 16 19:34:58 EDT 2021

copied 9front boot infrastructure over

diff: cannot open b/os/boot/bcm//null: file does not exist: 'b/os/boot/bcm//null' diff: cannot open b/os/boot/bitsy//null: file does not exist: 'b/os/boot/bitsy//null' diff: cannot open b/os/boot/efi//null: file does not exist: 'b/os/boot/efi//null' diff: cannot open b/os/boot/pc//null: file does not exist: 'b/os/boot/pc//null' diff: cannot open b/os/boot/zynq//null: file does not exist: 'b/os/boot/zynq//null' diff: cannot open b/os/boot//null: file does not exist: 'b/os/boot//null'
--- /dev/null
+++ b/os/boot/bcm/mkfile
@@ -1,0 +1,16 @@
+URL=https://github.com/raspberrypi/firmware/raw/master/boot
+FILES=bootcode.bin fixup_cd.dat start_cd.elf fixup4cd.dat start4cd.elf \
+	bcm2711-rpi-4-b.dtb \
+	bcm2711-rpi-400.dtb \
+	bcm2711-rpi-cm4.dtb \
+	LICENCE.broadcom
+
+all:V:	$FILES
+
+clean:V:
+	rm -f $FILES
+
+$FILES:
+	for(i in $target){
+		hget -o $i $URL/$i
+	}
--- /dev/null
+++ b/os/boot/bitsy/donprint.c
@@ -1,0 +1,332 @@
+#include	"u.h"
+#include	"lib.h"
+
+#define	PTR	sizeof(char*)
+#define	SHORT	sizeof(int)
+#define	INT	sizeof(int)
+#define	LONG	sizeof(long)
+#define	IDIGIT	30
+#define	MAXCON	30
+
+#define	FLONG	(1<<0)
+#define	FSHORT	(1<<1)
+#define	FUNSIGN	(1<<2)
+
+typedef struct Op	Op;
+struct Op
+{
+	char	*p;
+	char	*ep;
+	void	*argp;
+	int	f1;
+	int	f2;
+	int	f3;
+};
+
+static	int	noconv(Op*);
+static	int	cconv(Op*);
+static	int	dconv(Op*);
+static	int	hconv(Op*);
+static	int	lconv(Op*);
+static	int	oconv(Op*);
+static	int	sconv(Op*);
+static	int	uconv(Op*);
+static	int	xconv(Op*);
+static	int	Xconv(Op*);
+static	int	percent(Op*);
+
+static
+int	(*fmtconv[MAXCON])(Op*) =
+{
+	noconv,
+	cconv, dconv, hconv, lconv,
+	oconv, sconv, uconv, xconv,
+	Xconv, percent,
+};
+static
+char	fmtindex[128] =
+{
+	['c'] 1,
+	['d'] 2,
+	['h'] 3,
+	['l'] 4,
+	['o'] 5,
+	['s'] 6,
+	['u'] 7,
+	['x'] 8,
+	['X'] 9,
+	['%'] 10,
+};
+
+static	int	convcount  = { 11 };
+static	int	ucase;
+
+static void
+PUT(Op *o, int c)
+{
+	static int pos;
+	int opos;
+
+	if(c == '\t'){
+		opos = pos;
+		pos = (opos+8) & ~7;
+		while(opos++ < pos && o->p < o->ep)
+			*o->p++ = ' ';
+		return;
+	}
+	if(o->p < o->ep){
+		*o->p++ = c;
+		pos++;
+	}
+	if(c == '\n')
+		pos = 0;
+}
+
+int
+fmtinstall(char c, int (*f)(Op*))
+{
+
+	c &= 0177;
+	if(fmtindex[c] == 0) {
+		if(convcount >= MAXCON)
+			return 1;
+		fmtindex[c] = convcount++;
+	}
+	fmtconv[fmtindex[c]] = f;
+	return 0;
+}
+
+char*
+donprint(char *p, char *ep, char *fmt, void *argp)
+{
+	int sf1, c;
+	Op o;
+
+	o.p = p;
+	o.ep = ep;
+	o.argp = argp;
+
+loop:
+	c = *fmt++;
+	if(c != '%') {
+		if(c == 0) {
+			if(o.p < o.ep)
+				*o.p = 0;
+			return o.p;
+		}
+		PUT(&o, c);
+		goto loop;
+	}
+	o.f1 = 0;
+	o.f2 = -1;
+	o.f3 = 0;
+	c = *fmt++;
+	sf1 = 0;
+	if(c == '-') {
+		sf1 = 1;
+		c = *fmt++;
+	}
+	while(c >= '0' && c <= '9') {
+		o.f1 = o.f1*10 + c-'0';
+		c = *fmt++;
+	}
+	if(sf1)
+		o.f1 = -o.f1;
+	if(c != '.')
+		goto l1;
+	c = *fmt++;
+	while(c >= '0' && c <= '9') {
+		if(o.f2 < 0)
+			o.f2 = 0;
+		o.f2 = o.f2*10 + c-'0';
+		c = *fmt++;
+	}
+l1:
+	if(c == 0)
+		fmt--;
+	c = (*fmtconv[fmtindex[c&0177]])(&o);
+	if(c < 0) {
+		o.f3 |= -c;
+		c = *fmt++;
+		goto l1;
+	}
+	o.argp = (char*)o.argp + c;
+	goto loop;
+}
+
+void
+strconv(char *o, Op *op, int f1, int f2)
+{
+	int n, c;
+	char *p;
+
+	n = strlen(o);
+	if(f1 >= 0)
+		while(n < f1) {
+			PUT(op, ' ');
+			n++;
+		}
+	for(p=o; c = *p++;)
+		if(f2 != 0) {
+			PUT(op, c);
+			f2--;
+		}
+	if(f1 < 0) {
+		f1 = -f1;
+		while(n < f1) {
+			PUT(op, ' ');
+			n++;
+		}
+	}
+}
+
+int
+numbconv(Op *op, int base)
+{
+	char b[IDIGIT];
+	int i, f, n, r;
+	long v;
+	short h;
+
+	f = 0;
+	switch(op->f3 & (FLONG|FSHORT|FUNSIGN)) {
+	case FLONG:
+		v = *(long*)op->argp;
+		r = LONG;
+		break;
+
+	case FUNSIGN|FLONG:
+		v = *(ulong*)op->argp;
+		r = LONG;
+		break;
+
+	case FSHORT:
+		h = *(int*)op->argp;
+		v = h;
+		r = SHORT;
+		break;
+
+	case FUNSIGN|FSHORT:
+		h = *(int*)op->argp;
+		v = (ushort)h;
+		r = SHORT;
+		break;
+
+	default:
+		v = *(int*)op->argp;
+		r = INT;
+		break;
+
+	case FUNSIGN:
+		v = *(unsigned*)op->argp;
+		r = INT;
+		break;
+	}
+	if(!(op->f3 & FUNSIGN) && v < 0) {
+		v = -v;
+		f = 1;
+	}
+	b[IDIGIT-1] = 0;
+	for(i = IDIGIT-2;; i--) {
+		n = (ulong)v % base;
+		n += '0';
+		if(n > '9'){
+			n += 'a' - ('9'+1);
+			if(ucase)
+				n += 'A'-'a';
+		}
+		b[i] = n;
+		if(i < 2)
+			break;
+		v = (ulong)v / base;
+		if(op->f2 >= 0 && i >= IDIGIT-op->f2)
+			continue;
+		if(v <= 0)
+			break;
+	}
+	if(f)
+		b[--i] = '-';
+	strconv(b+i, op, op->f1, -1);
+	return r;
+}
+
+static	int
+noconv(Op *op)
+{
+
+	strconv("***", op, 0, -1);
+	return 0;
+}
+
+static	int
+cconv(Op *op)
+{
+	char b[2];
+
+	b[0] = *(int*)op->argp;
+	b[1] = 0;
+	strconv(b, op, op->f1, -1);
+	return INT;
+}
+
+static	int
+dconv(Op *op)
+{
+	return numbconv(op, 10);
+}
+
+static	int
+hconv(Op*)
+{
+	return -FSHORT;
+}
+
+static	int
+lconv(Op*)
+{
+	return -FLONG;
+}
+
+static	int
+oconv(Op *op)
+{
+	return numbconv(op, 8);
+}
+
+static	int
+sconv(Op *op)
+{
+	strconv(*(char**)op->argp, op, op->f1, op->f2);
+	return PTR;
+}
+
+static	int
+uconv(Op*)
+{
+	return -FUNSIGN;
+}
+
+static	int
+xconv(Op *op)
+{
+	return numbconv(op, 16);
+}
+
+static	int
+Xconv(Op *op)
+{
+	int r;
+
+	ucase = 1;
+	r = numbconv(op, 16);
+	ucase = 0;
+	return r;
+}
+
+static	int
+percent(Op *op)
+{
+
+	PUT(op, '%');
+	return 0;
+}
--- /dev/null
+++ b/os/boot/bitsy/fns.h
@@ -1,0 +1,7 @@
+/*
+ *  functions defined locally
+ */
+extern int	gunzip(uchar *out, int outn, uchar *in, int inn);
+extern void	delay(int ms);
+extern void	serialputs(char *str, int n);
+extern void	draincache(void);
--- /dev/null
+++ b/os/boot/bitsy/il.s
@@ -1,0 +1,99 @@
+#include "mem.h"
+/*
+ *  Entered here from Compaq's bootldr.  First relocate to
+ *  the location we're linked for and then copy back the
+ *  decompressed kernel.
+ *
+ *  All 
+ */
+TEXT _start(SB), $-4
+	MOVW	$setR12(SB), R12		/* load the SB */
+	MOVW	$1, R0		/* dance to make 5l think that the magic */
+	MOVW	$1, R1		/* numbers in WORDs below are being used */
+	CMP.S	R0, R1		/* and to align them to where bootldr wants */
+	BEQ	_start2
+	WORD	$0x016f2818	/* magic number to say we are a kernel */
+	WORD	$0xc0008000	/* entry point address */
+	WORD	$0		/* size?, or end of data? */
+
+_start2:
+
+	/* SVC mode, interrupts disabled */
+	MOVW	$(PsrDirq|PsrDfiq|PsrMsvc), R1
+	MOVW	R1, CPSR
+
+	/* disable the MMU */
+	MOVW	$0x130, R1
+	MCR     CpMMU, 0, R1, C(CpControl), C(0x0)
+
+	/* enable caches */
+	MRC	CpMMU, 0, R0, C(CpControl), C(0x0)
+	ORR	$(CpCdcache|CpCicache|CpCwb), R0
+	MCR     CpMMU, 0, R0, C(CpControl), C(0x0)
+
+	/* flush caches */
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0x7), 0
+	/* drain prefetch */
+	MOVW	R0,R0						
+	MOVW	R0,R0
+	MOVW	R0,R0
+	MOVW	R0,R0
+
+	/* drain write buffer */
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0xa), 4
+
+	/* relocate to where we expect to be */
+	MOVW	$(512*1024),R3
+	MOVW	$0xC0008000,R1
+	MOVW	$0xC0200000,R2
+	ADD	R1,R3
+_relloop:
+	MOVW	(R1),R0
+	MOVW	R0,(R2)
+	ADD	$4,R1
+	ADD	$4,R2
+	CMP.S	R1,R3
+	BNE	_relloop
+
+	MOVW	$(MACHADDR+BY2PG), R13		/* stack */
+	SUB	$4, R13				/* link */
+
+	/* jump to where we've been relocated */
+	MOVW	$_relocated(SB),R15
+
+TEXT _relocated(SB),$-4
+	BL	main(SB)
+	BL	exit(SB)
+	/* we shouldn't get here */
+_mainloop:
+	B	_mainloop
+	BL	_div(SB)			/* hack to get _div etc loaded */
+
+TEXT mypc(SB),$-4
+	MOVW	R14,R0
+	RET
+
+TEXT draincache(SB),$-4
+	/* write back any dirty data */
+	MOVW	$0xe0000000,R0
+	ADD	$(8*1024),R0,R1
+_cfloop:
+	MOVW.P	32(R0),R2
+	CMP.S	R0,R1
+	BNE	_cfloop
+	
+	/* drain write buffer and invalidate i&d cache contents */
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0xa), 4
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0x7), 0
+
+	/* drain prefetch */
+	MOVW	R0,R0						
+	MOVW	R0,R0
+	MOVW	R0,R0
+	MOVW	R0,R0
+
+	/* disable caches */
+	MRC	CpMMU, 0, R0, C(CpControl), C(0x0)
+	BIC	$(CpCdcache|CpCicache|CpCwb), R0
+	MCR     CpMMU, 0, R0, C(CpControl), C(0x0)
+	RET
--- /dev/null
+++ b/os/boot/bitsy/imain.c
@@ -1,0 +1,48 @@
+#include "u.h"
+#include "lib.h"
+#include "fns.h"
+#include "dat.h"
+#include "mem.h"
+
+void
+main(void)
+{
+	void (*f)(void);
+	ulong *kernel;
+
+	print("inflating kernel\n");
+
+	kernel = (ulong*)(0xc0200000+20*1024);
+	if(gunzip((uchar*)0xc0008000, 2*1024*1024, (uchar*)kernel, 512*1024) > 0){
+		f = (void (*)(void))0xc0008010;
+		draincache();
+	} else {
+		print("inflation failed\n");
+		f = nil;
+	}
+	(*f)();
+}
+
+void
+exit(void)
+{
+
+	void (*f)(void);
+
+	delay(1000);
+
+	print("it's a wonderful day to die\n");
+	f = nil;
+	(*f)();
+}
+
+void
+delay(int ms)
+{
+	int i;
+
+	while(ms-- > 0){
+		for(i = 0; i < 1000; i++)
+			;
+	}
+}
--- /dev/null
+++ b/os/boot/bitsy/inflate.c
@@ -1,0 +1,208 @@
+#include "u.h"
+#include "lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include <flate.h>
+
+typedef struct Biobuf	Biobuf;
+
+struct Biobuf
+{
+	uchar *bp;
+	uchar *p;
+	uchar *ep;
+};
+
+static int	header(Biobuf*);
+static int	trailer(Biobuf*, Biobuf*);
+static int	getc(void*);
+static ulong	offset(Biobuf*);
+static int	crcwrite(void *out, void *buf, int n);
+static ulong	get4(Biobuf *b);
+static ulong	Boffset(Biobuf *bp);
+
+/* GZIP flags */
+enum {
+	Ftext=		(1<<0),
+	Fhcrc=		(1<<1),
+	Fextra=		(1<<2),
+	Fname=		(1<<3),
+	Fcomment=	(1<<4),
+
+	GZCRCPOLY	= 0xedb88320UL,
+};
+
+static ulong	*crctab;
+static ulong	crc;
+
+int
+gunzip(uchar *out, int outn, uchar *in, int inn)
+{
+	Biobuf bin, bout;
+	int err;
+
+	crc = 0;
+	crctab = mkcrctab(GZCRCPOLY);
+	err = inflateinit();
+	if(err != FlateOk)
+		print("inflateinit failed: %s\n", flateerr(err));
+
+	bin.bp = bin.p = in;
+	bin.ep = in+inn;
+	bout.bp = bout.p = out;
+	bout.ep = out+outn;
+
+	err = header(&bin);
+	if(err != FlateOk)
+		return err;
+
+	err = inflate(&bout, crcwrite, &bin, getc);
+	if(err != FlateOk)
+		print("inflate failed: %s\n", flateerr(err));
+
+	err = trailer(&bout, &bin);
+	if(err != FlateOk)
+		return err;
+
+	return Boffset(&bout);
+}
+
+static int
+header(Biobuf *bin)
+{
+	int i, flag;
+
+	if(getc(bin) != 0x1f || getc(bin) != 0x8b){
+		print("bad magic\n");
+		return FlateCorrupted;
+	}
+	if(getc(bin) != 8){
+		print("unknown compression type\n");
+		return FlateCorrupted;
+	}
+	
+	flag = getc(bin);
+	
+	/* mod time */
+	get4(bin);
+	
+	/* extra flags */
+	getc(bin);
+	
+	/* OS type */
+	getc(bin);
+
+	if(flag & Fextra)
+		for(i=getc(bin); i>0; i--)
+			getc(bin);
+	
+	/* name */
+	if(flag&Fname)
+		while(getc(bin) != 0)
+			;
+
+	/* comment */
+	if(flag&Fcomment)
+		while(getc(bin) != 0)
+			;
+
+	/* crc16 */
+	if(flag&Fhcrc) {
+		getc(bin);
+		getc(bin);
+	}
+		
+	return FlateOk;
+}
+
+static int
+trailer(Biobuf *bout, Biobuf *bin)
+{
+	/* crc32 */
+	ulong x;
+
+	x = get4(bin);
+	if(crc != x){
+		print("crc mismatch %lux %lux\n", crc, x);
+		return FlateCorrupted;
+	}
+
+	/* length */
+	if(get4(bin) != Boffset(bout)){
+		print("bad output len\n");
+		return FlateCorrupted;
+	}
+	return FlateOk;
+}
+
+static ulong
+get4(Biobuf *b)
+{
+	ulong v;
+	int i, c;
+
+	v = 0;
+	for(i = 0; i < 4; i++){
+		c = getc(b);
+		v |= c << (i * 8);
+	}
+	return v;
+}
+
+static int
+getc(void *in)
+{
+	Biobuf *bp = in;
+
+	if((bp->p - bp->bp) % 10000 == 0)
+		print(".");
+	if(bp->p >= bp->ep)
+		return -1;
+	return *bp->p++;
+}
+
+static ulong
+Boffset(Biobuf *bp)
+{
+	return bp->p - bp->bp;
+}
+
+static int
+crcwrite(void *out, void *buf, int n)
+{
+	Biobuf *bp;
+
+	crc = blockcrc(crctab, crc, buf, n);
+	bp = out;
+	if(n > bp->ep-bp->p)
+		n = bp->ep-bp->p;
+	memmove(bp->p, buf, n);
+	bp->p += n;
+	return n;
+}
+
+#undef malloc
+#undef free
+
+static ulong ibrkp = ~0;
+
+void *
+malloc(ulong n)
+{
+	ulong rv;
+
+	if(ibrkp == ~0)
+		ibrkp = ((ulong)end)+1024*1024;
+	n = (n+3)>>2;
+	n <<= 2;
+	rv = ibrkp;
+	ibrkp += n;
+	return (void*)rv;
+}
+
+void
+free(void *)
+{
+}
--- /dev/null
+++ b/os/boot/bitsy/io.h
@@ -1,0 +1,261 @@
+/*
+ *  Definitions for IO devices.  Used only in C.
+ */
+
+enum
+{
+	/* hardware counter frequency */
+	ClockFreq=	3686400,
+};
+
+/*
+ *  IRQ's defined by SA1100
+ */
+enum
+{
+	IRQgpio0=	0,
+	IRQgpio1=	1,
+	IRQgpio2=	2,
+	IRQgpio3=	3,
+	IRQgpio4=	4,
+	IRQgpio5=	5,
+	IRQgpio6=	6,
+	IRQgpio7=	7,
+	IRQgpio8=	8,
+	IRQgpio9=	9,
+	IRQgpio10=	10,
+	IRQgpiohi=	11,
+	IRQlcd=		12,
+	IRQudc=		13,
+	IRQuart1b=	15,
+	IRQuart2=	16,
+	IRQuart3=	17,
+	IRQmcp=		18,
+	IRQssp=		19,
+	IRQdma0=	20,
+	IRQdma1=	21,
+	IRQdma2=	22,
+	IRQdma3=	23,
+	IRQdma4=	24,
+	IRQdma5=	25,
+	IRQtimer0=	26,
+	IRQtimer1=	27,
+	IRQtimer2=	28,
+	IRQtimer3=	29,
+	IRQsecond=	30,
+	IRQrtc=		31,
+};
+
+/*
+ *  GPIO lines (signal names from compaq document).  _i indicates input
+ *  and _o output.
+ */
+enum
+{
+	GPIO_PWR_ON_i=		1<<0,	/* power button */
+	GPIO_UP_IRQ_i=		1<<1,	/* microcontroller interrupts */
+	GPIO_LDD8_o=		1<<2,	/* LCD data 8-15 */
+	GPIO_LDD9_o=		1<<3,
+	GPIO_LDD10_o=		1<<4,
+	GPIO_LDD11_o=		1<<5,
+	GPIO_LDD12_o=		1<<6,
+	GPIO_LDD13_o=		1<<7,
+	GPIO_LDD14_o=		1<<8,
+	GPIO_LDD15_o=		1<<9,
+	GPIO_CARD_IND1_i=	1<<10,	/* card inserted in PCMCIA socket 1 */
+	GPIO_CARD_IRQ1_i=	1<<11,	/* PCMCIA socket 1 interrupt */
+	GPIO_CLK_SET0_o=	1<<12,	/* clock selects for audio codec */
+	GPIO_CLK_SET1_o=	1<<13,
+	GPIO_L3_SDA_io=		1<<14,	/* UDA1341 interface */
+	GPIO_L3_MODE_o=		1<<15,
+	GPIO_L3_SCLK_o=		1<<16,
+	GPIO_CARD_IND0_i=	1<<17,	/* card inserted in PCMCIA socket 0 */
+	GPIO_KEY_ACT_i=		1<<18,	/* hot key from cradle */
+	GPIO_SYS_CLK_i=		1<<19,	/* clock from codec */
+	GPIO_BAT_FAULT_i=	1<<20,	/* battery fault */
+	GPIO_CARD_IRQ0_i=	1<<21,	/* PCMCIA socket 0 interrupt */
+	GPIO_LOCK_i=		1<<22,	/* expansion pack lock/unlock */
+	GPIO_COM_DCD_i=		1<<23,	/* DCD from UART3 */
+	GPIO_OPT_IRQ_i=		1<<24,	/* expansion pack IRQ */
+	GPIO_COM_CTS_i=		1<<25,	/* CTS from UART3 */
+	GPIO_COM_RTS_o=		1<<26,	/* RTS to UART3 */
+	GPIO_OPT_IND_i=		1<<27,	/* expansion pack inserted */
+
+/* Peripheral Unit GPIO pin assignments: alternate functions */
+	GPIO_SSP_TXD_o=		1<<10,	/* SSP Transmit Data */
+	GPIO_SSP_RXD_i=		1<<11,	/* SSP Receive Data */
+	GPIO_SSP_SCLK_o=	1<<12,	/* SSP Sample CLocK */
+	GPIO_SSP_SFRM_o=	1<<13,	/* SSP Sample FRaMe */
+	/* ser. port 1: */
+	GPIO_UART_TXD_o=	1<<14,	/* UART Transmit Data */
+	GPIO_UART_RXD_i=	1<<15,	/* UART Receive Data */
+	GPIO_SDLC_SCLK_io=	1<<16,	/* SDLC Sample CLocK (I/O) */
+	GPIO_SDLC_AAF_o=	1<<17,	/* SDLC Abort After Frame */
+	GPIO_UART_SCLK1_i=	1<<18,	/* UART Sample CLocK 1 */
+	/* ser. port 4: */
+	GPIO_SSP_CLK_i=		1<<19,	/* SSP external CLocK */
+	/* ser. port 3: */
+	GPIO_UART_SCLK3_i=	1<<20,	/* UART Sample CLocK 3 */
+	/* ser. port 4: */
+	GPIO_MCP_CLK_i=		1<<21,	/* MCP CLocK */
+	/* test controller: */
+	GPIO_TIC_ACK_o=		1<<21,	/* TIC ACKnowledge */
+	GPIO_MBGNT_o=		1<<21,	/* Memory Bus GraNT */
+	GPIO_TREQA_i=		1<<22,	/* TIC REQuest A */
+	GPIO_MBREQ_i=		1<<22,	/* Memory Bus REQuest */
+	GPIO_TREQB_i=		1<<23,	/* TIC REQuest B */
+	GPIO_1Hz_o=			1<<25,	/* 1 Hz clock */
+	GPIO_RCLK_o=		1<<26,	/* internal (R) CLocK (O, fcpu/2) */
+	GPIO_32_768kHz_o=	1<<27,	/* 32.768 kHz clock (O, RTC) */
+};
+
+/*
+ *  types of interrupts
+ */
+enum
+{
+	GPIOrising,
+	GPIOfalling,
+	GPIOboth,
+	IRQ,
+};
+
+/* hardware registers */
+typedef struct Uartregs Uartregs;
+struct Uartregs
+{
+	ulong	ctl[4];
+	ulong	dummya;
+	ulong	data;
+	ulong	dummyb;
+	ulong	status[2];
+};
+Uartregs *uart3regs;
+
+/* general purpose I/O lines control registers */
+typedef struct GPIOregs GPIOregs;
+struct GPIOregs
+{
+	ulong	level;		/* 1 == high */
+	ulong	direction;	/* 1 == output */
+	ulong	set;		/* a 1 sets the bit, 0 leaves it alone */
+	ulong	clear;		/* a 1 clears the bit, 0 leaves it alone */
+	ulong	rising;		/* rising edge detect enable */
+	ulong	falling;	/* falling edge detect enable */
+	ulong	edgestatus;	/* writing a 1 bit clears */
+	ulong	altfunc;	/* turn on alternate function for any set bits */
+};
+
+extern GPIOregs *gpioregs;
+
+/* extra general purpose I/O bits, output only */
+enum
+{
+	EGPIO_prog_flash=	1<<0,
+	EGPIO_pcmcia_reset=	1<<1,
+	EGPIO_exppack_reset=	1<<2,
+	EGPIO_codec_reset=	1<<3,
+	EGPIO_exp_nvram_power=	1<<4,
+	EGPIO_exp_full_power=	1<<5,
+	EGPIO_lcd_3v=		1<<6,
+	EGPIO_rs232_power=	1<<7,
+	EGPIO_lcd_ic_power=	1<<8,
+	EGPIO_ir_power=		1<<9,
+	EGPIO_audio_power=	1<<10,
+	EGPIO_audio_ic_power=	1<<11,
+	EGPIO_audio_mute=	1<<12,
+	EGPIO_fir=		1<<13,	/* not set is sir */
+	EGPIO_lcd_5v=		1<<14,
+	EGPIO_lcd_9v=		1<<15,
+};
+extern ulong *egpioreg;
+
+/* Peripheral pin controller registers */
+typedef struct PPCregs PPCregs;
+struct PPCregs {
+	ulong	direction;
+	ulong	state;
+	ulong	assignment;
+	ulong	sleepdir;
+	ulong	flags;
+};
+extern PPCregs *ppcregs;
+
+/* Synchronous Serial Port controller registers */
+typedef struct SSPregs SSPregs;
+struct SSPregs {
+	ulong	control0;
+	ulong	control1;
+	ulong	dummy0;
+	ulong	data;
+	ulong	dummy1;
+	ulong	status;
+};
+extern SSPregs *sspregs;
+
+/* Multimedia Communications Port controller registers */
+typedef struct MCPregs MCPregs;
+struct MCPregs {
+	ulong	control0;
+	ulong	reserved0;
+	ulong	data0;
+	ulong	data1;
+	ulong	data2;
+	ulong	reserved1;
+	ulong	status;
+	ulong	reserved[11];
+	ulong	control1;
+};
+extern MCPregs *mcpregs;
+
+/*
+ *  memory configuration
+ */
+enum
+{
+	/* bit shifts for pcmcia access time counters */
+	MECR_io0=	0,
+	MECR_attr0=	5,
+	MECR_mem0=	10,
+	MECR_fast0=	11,
+	MECR_io1=	MECR_io0+16,
+	MECR_attr1=	MECR_attr0+16,
+	MECR_mem1=	MECR_mem0+16,
+	MECR_fast1=	MECR_fast0+16,
+};
+
+typedef struct MemConfRegs MemConfRegs;
+struct MemConfRegs
+{
+	ulong	mdcnfg;		/* dram */
+	ulong	mdcas00;	/* dram banks 0/1 */
+	ulong	mdcas01;
+	ulong	mdcas02;
+	ulong	msc0;		/* static */
+	ulong	msc1;
+	ulong	mecr;		/* pcmcia */
+	ulong	mdrefr;		/* dram refresh */
+	ulong	mdcas20;	/* dram banks 2/3 */
+	ulong	mdcas21;
+	ulong	mdcas22;
+	ulong	msc2;		/* static */
+	ulong	smcnfg;		/* SMROM config */
+};
+extern MemConfRegs *memconfregs;
+
+/*
+ *  power management
+ */
+typedef struct PowerRegs PowerRegs;
+struct PowerRegs
+{
+	ulong	pmcr;	/* Power manager control register */
+	ulong	pssr;	/* Power manager sleep status register */
+	ulong	pspr;	/* Power manager scratch pad register */
+	ulong	pwer;	/* Power manager wakeup enable register */
+	ulong	pcfr;	/* Power manager general configuration register */
+	ulong	ppcr;	/* Power manager PPL configuration register */
+	ulong	pgsr;	/* Power manager GPIO sleep state register */
+	ulong	posr;	/* Power manager oscillator status register */
+};
+extern PowerRegs *powerregs;
--- /dev/null
+++ b/os/boot/bitsy/l.s
@@ -1,0 +1,454 @@
+#include "mem.h"
+
+/*
+ * Entered here from Compaq's bootldr with MMU disabled.
+ */
+TEXT _start(SB), $-4
+	MOVW	$setR12(SB), R12		/* load the SB */
+_main:
+	/* SVC mode, interrupts disabled */
+	MOVW	$(PsrDirq|PsrDfiq|PsrMsvc), R1
+	MOVW	R1, CPSR
+
+	/* disable the MMU */
+	MOVW	$0x130, R1
+	MCR     CpMMU, 0, R1, C(CpControl), C(0x0)
+
+	/* flush caches */
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0x7), 0
+	/* drain prefetch */
+	MOVW	R0,R0						
+	MOVW	R0,R0
+	MOVW	R0,R0
+	MOVW	R0,R0
+
+	/* drain write buffer */
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0xa), 4
+
+	MOVW	$(MACHADDR+BY2PG), R13		/* stack */
+	SUB	$4, R13				/* link */
+	BL	main(SB)
+	BL	exit(SB)
+	/* we shouldn't get here */
+_mainloop:
+	B	_mainloop
+	BL	_div(SB)			/* hack to get _div etc loaded */
+
+/* flush tlb's */
+TEXT mmuinvalidate(SB), $-4
+	MCR	CpMMU, 0, R0, C(CpTLBFlush), C(0x7)
+	RET
+
+/* flush tlb's */
+TEXT mmuinvalidateaddr(SB), $-4
+	MCR	CpMMU, 0, R0, C(CpTLBFlush), C(0x6), 1
+	RET
+
+/* write back and invalidate i and d caches */
+TEXT cacheflush(SB), $-4
+	/* write back any dirty data */
+	MOVW	$0xe0000000,R0
+	ADD	$(8*1024),R0,R1
+_cfloop:
+	MOVW.P	32(R0),R2
+	CMP.S	R0,R1
+	BNE	_cfloop
+	
+	/* drain write buffer and invalidate i&d cache contents */
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0xa), 4
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0x7), 0
+
+	/* drain prefetch */
+	MOVW	R0,R0						
+	MOVW	R0,R0
+	MOVW	R0,R0
+	MOVW	R0,R0
+	RET
+
+/* write back d cache */
+TEXT cachewb(SB), $-4
+	/* write back any dirty data */
+_cachewb:
+	MOVW	$0xe0000000,R0
+	ADD	$(8*1024),R0,R1
+_cwbloop:
+	MOVW.P	32(R0),R2
+	CMP.S	R0,R1
+	BNE	_cwbloop
+	
+	/* drain write buffer */
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0xa), 4
+	RET
+
+/* write back a single cache line */
+TEXT cachewbaddr(SB), $-4
+	BIC	$31,R0
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0xa), 1
+	B	_wbflush
+
+/* write back a region of cache lines */
+TEXT cachewbregion(SB), $-4
+	MOVW	4(FP),R1
+	CMP.S	$(4*1024),R1
+	BGT	_cachewb
+	ADD	R0,R1
+	BIC	$31,R0
+_cwbrloop:
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0xa), 1
+	ADD	$32,R0
+	CMP.S	R0,R1
+	BGT	_cwbrloop
+	B	_wbflush
+
+/* invalidate the dcache */
+TEXT dcacheinvalidate(SB), $-4
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0x6)
+	RET
+
+/* invalidate the icache */
+TEXT icacheinvalidate(SB), $-4
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0x9)
+	RET
+
+/* drain write buffer */
+TEXT wbflush(SB), $-4
+_wbflush:
+	MCR	CpMMU, 0, R0, C(CpCacheFlush), C(0xa), 4
+	RET
+
+/* return cpu id */
+TEXT getcpuid(SB), $-4
+	MRC	CpMMU, 0, R0, C(CpCPUID), C(0x0)
+	RET
+
+/* return fault status */
+TEXT getfsr(SB), $-4
+	MRC	CpMMU, 0, R0, C(CpFSR), C(0x0)
+	RET
+
+/* return fault address */
+TEXT getfar(SB), $-4
+	MRC	CpMMU, 0, R0, C(CpFAR), C(0x0)
+	RET
+
+/* return fault address */
+TEXT putfar(SB), $-4
+	MRC	CpMMU, 0, R0, C(CpFAR), C(0x0)
+	RET
+
+/* set the translation table base */
+TEXT putttb(SB), $-4
+	MCR	CpMMU, 0, R0, C(CpTTB), C(0x0)
+	RET
+
+/*
+ *  enable mmu, i and d caches
+ */
+TEXT mmuenable(SB), $-4
+	MRC	CpMMU, 0, R0, C(CpControl), C(0x0)
+	ORR	$(CpCmmuena|CpCdcache|CpCicache|CpCwb), R0
+	MCR     CpMMU, 0, R0, C(CpControl), C(0x0)
+	RET
+
+TEXT mmudisable(SB), $-4
+	MRC	CpMMU, 0, R0, C(CpControl), C(0x0)
+	BIC	$(CpCmmuena|CpCdcache|CpCicache|CpCwb|CpCvivec), R0
+	MCR     CpMMU, 0, R0, C(CpControl), C(0x0)
+	RET
+
+/*
+ *  use exception vectors at 0xffff0000
+ */
+TEXT mappedIvecEnable(SB), $-4
+	MRC	CpMMU, 0, R0, C(CpControl), C(0x0)
+	ORR	$(CpCvivec), R0
+	MCR     CpMMU, 0, R0, C(CpControl), C(0x0)
+	RET
+TEXT mappedIvecDisable(SB), $-4
+	MRC	CpMMU, 0, R0, C(CpControl), C(0x0)
+	BIC	$(CpCvivec), R0
+	MCR     CpMMU, 0, R0, C(CpControl), C(0x0)
+	RET
+
+/* set the translation table base */
+TEXT putdac(SB), $-4
+	MCR	CpMMU, 0, R0, C(CpDAC), C(0x0)
+	RET
+
+/* set address translation pid */
+TEXT putpid(SB), $-4
+	MCR	CpMMU, 0, R0, C(CpPID), C(0x0)
+	RET
+
+/*
+ *  set the stack value for the mode passed in R0
+ */
+TEXT setr13(SB), $-4
+	MOVW	4(FP), R1
+
+	MOVW	CPSR, R2
+	BIC	$PsrMask, R2, R3
+	ORR	R0, R3
+	MOVW	R3, CPSR
+
+	MOVW	R13, R0
+	MOVW	R1, R13
+
+	MOVW	R2, CPSR
+	RET
+
+/*
+ *  exception vectors, copied by trapinit() to somewhere useful
+ */
+
+TEXT vectors(SB), $-4
+	MOVW	0x18(R15), R15			/* reset */
+	MOVW	0x18(R15), R15			/* undefined */
+	MOVW	0x18(R15), R15			/* SWI */
+	MOVW	0x18(R15), R15			/* prefetch abort */
+	MOVW	0x18(R15), R15			/* data abort */
+	MOVW	0x18(R15), R15			/* reserved */
+	MOVW	0x18(R15), R15			/* IRQ */
+	MOVW	0x18(R15), R15			/* FIQ */
+
+TEXT vtable(SB), $-4
+	WORD	$_vsvc(SB)			/* reset, in svc mode already */
+	WORD	$_vund(SB)			/* undefined, switch to svc mode */
+	WORD	$_vsvc(SB)			/* swi, in svc mode already */
+	WORD	$_vpabt(SB)			/* prefetch abort, switch to svc mode */
+	WORD	$_vdabt(SB)			/* data abort, switch to svc mode */
+	WORD	$_vsvc(SB)			/* reserved */
+	WORD	$_virq(SB)			/* IRQ, switch to svc mode */
+	WORD	$_vfiq(SB)			/* FIQ, switch to svc mode */
+
+TEXT _vrst(SB), $-4
+	BL	resettrap(SB)
+
+TEXT _vsvc(SB), $-4			/* SWI */
+	MOVW.W	R14, -4(R13)		/* ureg->pc = interupted PC */
+	MOVW	SPSR, R14		/* ureg->psr = SPSR */
+	MOVW.W	R14, -4(R13)		/* ... */
+	MOVW	$PsrMsvc, R14		/* ureg->type = PsrMsvc */
+	MOVW.W	R14, -4(R13)		/* ... */
+	MOVM.DB.W.S [R0-R14], (R13)	/* save user level registers, at end r13 points to ureg */
+	MOVW	$setR12(SB), R12	/* Make sure we've got the kernel's SB loaded */
+	MOVW	R13, R0			/* first arg is pointer to ureg */
+	SUB	$8, R13			/* space for argument+link */
+
+	BL	syscall(SB)
+
+	ADD	$(8+4*15), R13		/* make r13 point to ureg->type */
+	MOVW	8(R13), R14		/* restore link */
+	MOVW	4(R13), R0		/* restore SPSR */
+	MOVW	R0, SPSR		/* ... */
+	MOVM.DB.S (R13), [R0-R14]	/* restore registers */
+	ADD	$8, R13			/* pop past ureg->{type+psr} */
+	RFE				/* MOVM.IA.S.W (R13), [R15] */
+
+TEXT _vund(SB), $-4			/* undefined */
+	MOVM.IA	[R0-R4], (R13)		/* free some working space */
+	MOVW	$PsrMund, R0
+	B	_vswitch
+
+TEXT _vpabt(SB), $-4			/* prefetch abort */
+	MOVM.IA	[R0-R4], (R13)		/* free some working space */
+	MOVW	$PsrMabt, R0		/* r0 = type */
+	B	_vswitch
+
+TEXT _vdabt(SB), $-4			/* prefetch abort */
+	MOVM.IA	[R0-R4], (R13)		/* free some working space */
+	MOVW	$(PsrMabt+1), R0		/* r0 = type */
+	B	_vswitch
+
+TEXT _virq(SB), $-4			/* IRQ */
+	MOVM.IA	[R0-R4], (R13)		/* free some working space */
+	MOVW	$PsrMirq, R0		/* r0 = type */
+	B	_vswitch
+
+	/*
+	 *  come here with type in R0 and R13 pointing above saved [r0-r4]
+	 *  and type in r0.  we'll switch to SVC mode and then call trap.
+	 */
+_vswitch:
+	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-R3] are */
+
+	/* switch to svc mode */
+	MOVW	CPSR, R14
+	BIC	$PsrMask, R14
+	ORR	$(PsrDirq|PsrDfiq|PsrMsvc), R14
+	MOVW	R14, CPSR
+
+	/* interupted code kernel or user? */
+	AND.S	$0xf, R1, R4
+	BEQ	_userexcep
+
+	/* here for trap from SVC mode */
+	MOVM.DB.W [R0-R2], (R13)	/* set ureg->{type, psr, pc}; r13 points to ureg->type  */
+	MOVM.IA	  (R3), [R0-R4]		/* restore [R0-R4] from previous mode's stack */
+	MOVM.DB.W [R0-R14], (R13)	/* save kernel level registers, at end r13 points to ureg */
+	MOVW	$setR12(SB), R12	/* Make sure we've got the kernel's SB loaded */
+	MOVW	R13, R0			/* first arg is pointer to ureg */
+	SUB	$8, R13			/* space for argument+link (for debugger) */
+	MOVW	$0xdeaddead,R11		/* marker */
+
+	BL	trap(SB)
+
+	ADD	$(8+4*15), R13		/* make r13 point to ureg->type */
+	MOVW	8(R13), R14		/* restore link */
+	MOVW	4(R13), R0		/* restore SPSR */
+	MOVW	R0, SPSR		/* ... */
+	MOVM.DB (R13), [R0-R14]	/* restore registers */
+	ADD	$8, R13			/* pop past ureg->{type+psr} */
+	RFE				/* MOVM.IA.S.W (R13), [R15] */
+
+	/* here for trap from USER mode */
+_userexcep:
+	MOVM.DB.W [R0-R2], (R13)	/* set ureg->{type, psr, pc}; r13 points to ureg->type  */
+	MOVM.IA	  (R3), [R0-R4]		/* restore [R0-R4] from previous mode's stack */
+	MOVM.DB.W.S [R0-R14], (R13)	/* save kernel level registers, at end r13 points to ureg */
+	MOVW	$setR12(SB), R12	/* Make sure we've got the kernel's SB loaded */
+	MOVW	R13, R0			/* first arg is pointer to ureg */
+	SUB	$8, R13			/* space for argument+link (for debugger) */
+
+	BL	trap(SB)
+
+	ADD	$(8+4*15), R13		/* make r13 point to ureg->type */
+	MOVW	8(R13), R14		/* restore link */
+	MOVW	4(R13), R0		/* restore SPSR */
+	MOVW	R0, SPSR		/* ... */
+	MOVM.DB.S (R13), [R0-R14]	/* restore registers */
+	ADD	$8, R13			/* pop past ureg->{type+psr} */
+	RFE				/* MOVM.IA.S.W (R13), [R15] */
+
+TEXT _vfiq(SB), $-4			/* FIQ */
+	RFE				/* FIQ is special, ignore it for now */
+
+/*
+ *  This is the first jump from kernel to user mode.
+ *  Fake a return from interrupt.
+ *
+ *  Enter with R0 containing the user stack pointer.
+ *  UTZERO + 0x20 is always the entry point.
+ *  
+ */
+TEXT touser(SB),$-4
+	/* store the user stack pointer into the USR_r13 */
+	MOVM.DB.W [R0], (R13)
+	MOVM.S.IA.W (R13),[R13]
+
+	/* set up a PSR for user level */
+	MOVW	$(PsrMusr), R0
+	MOVW	R0,SPSR
+
+	/* save the PC on the stack */
+	MOVW	$(UTZERO+0x20), R0
+	MOVM.DB.W [R0],(R13)
+
+	/* return from interrupt */
+	RFE				/* MOVM.IA.S.W (R13), [R15] */
+	
+/*
+ *  here to jump to a newly forked process
+ */
+TEXT forkret(SB),$-4
+	ADD	$(4*15), R13		/* make r13 point to ureg->type */
+	MOVW	8(R13), R14		/* restore link */
+	MOVW	4(R13), R0		/* restore SPSR */
+	MOVW	R0, SPSR		/* ... */
+	MOVM.DB.S (R13), [R0-R14]	/* restore registers */
+	ADD	$8, R13			/* pop past ureg->{type+psr} */
+	RFE				/* MOVM.IA.S.W (R13), [R15] */
+
+TEXT splhi(SB), $-4
+	/* save caller pc in Mach */
+	MOVW	$(MACHADDR+0x04),R2
+	MOVW	R14,0(R2)
+	/* turn off interrupts */
+	MOVW	CPSR, R0
+	ORR	$(PsrDfiq|PsrDirq), R0, R1
+	MOVW	R1, CPSR
+	RET
+
+TEXT spllo(SB), $-4
+	MOVW	CPSR, R0
+	BIC	$(PsrDfiq|PsrDirq), R0, R1
+	MOVW	R1, CPSR
+	RET
+
+TEXT splx(SB), $-4
+	/* save caller pc in Mach */
+	MOVW	$(MACHADDR+0x04),R2
+	MOVW	R14,0(R2)
+	/* reset interrupt level */
+	MOVW	R0, R1
+	MOVW	CPSR, R0
+	MOVW	R1, CPSR
+	RET
+
+TEXT splxpc(SB), $-4				/* for iunlock */
+	MOVW	R0, R1
+	MOVW	CPSR, R0
+	MOVW	R1, CPSR
+	RET
+
+TEXT spldone(SB), $0
+	RET
+
+TEXT islo(SB), $-4
+	MOVW	CPSR, R0
+	AND	$(PsrDfiq|PsrDirq), R0
+	EOR	$(PsrDfiq|PsrDirq), R0
+	RET
+
+TEXT cpsrr(SB), $-4
+	MOVW	CPSR, R0
+	RET
+
+TEXT spsrr(SB), $-4
+	MOVW	SPSR, R0
+	RET
+
+TEXT getcallerpc(SB), $-4
+	MOVW	0(R13), R0
+	RET
+
+TEXT tas(SB), $-4
+	MOVW	R0, R1
+	MOVW	$0xDEADDEAD, R2
+	SWPW	R2, (R1), R0
+	RET
+
+TEXT setlabel(SB), $-4
+	MOVW	R13, 0(R0)			/* sp */
+	MOVW	R14, 4(R0)			/* pc */
+	MOVW	$0, R0
+	RET
+
+TEXT gotolabel(SB), $-4
+	MOVW	0(R0), R13			/* sp */
+	MOVW	4(R0), R14			/* pc */
+	MOVW	$1, R0
+	RET
+
+
+/* The first MCR instruction of this function needs to be on a cache-line
+ * boundary; to make this happen, it will be copied (in trap.c).
+ *
+ * Doze puts the machine into idle mode.  Any interrupt will get it out
+ * at the next instruction (the RET, to be precise).
+ */
+TEXT _doze(SB), $-4
+	MOVW	$UCDRAMZERO, R1
+	MOVW	R0,R0
+	MOVW	R0,R0
+	MOVW	R0,R0
+	MOVW	R0,R0
+	MOVW	R0,R0
+	MOVW	R0,R0
+	MOVW	R0,R0
+	MCR     CpPWR, 0, R0, C(CpTest), C(0x2), 2
+	MOVW	(R1), R0
+	MCR     CpPWR, 0, R0, C(CpTest), C(0x8), 2
+	RET
--- /dev/null
+++ b/os/boot/bitsy/lib.h
@@ -1,0 +1,143 @@
+/*
+ * functions (possibly) linked in, complete, from libc.
+ */
+
+/*
+ * mem routines
+ */
+extern	void	*memccpy(void*, void*, int, long);
+extern	void	*memset(void*, int, long);
+extern	int	memcmp(void*, void*, long);
+extern	void	*memmove(void*, void*, long);
+extern	void	*memchr(void*, int, long);
+
+/*
+ * string routines
+ */
+extern	char	*strcat(char*, char*);
+extern	char	*strchr(char*, char);
+extern	char	*strrchr(char*, char);
+extern	int	strcmp(char*, char*);
+extern	char	*strcpy(char*, char*);
+extern	char	*strncat(char*, char*, long);
+extern	char	*strncpy(char*, char*, long);
+extern	int	strncmp(char*, char*, long);
+extern	long	strlen(char*);
+extern	char*	strstr(char*, char*);
+extern	int	atoi(char*);
+
+enum
+{
+	UTFmax		= 3,	/* maximum bytes per rune */
+	Runesync	= 0x80,	/* cannot represent part of a UTF sequence */
+	Runeself	= 0x80,	/* rune and UTF sequences are the same (<) */
+	Runeerror	= 0x80,	/* decoding error in UTF */
+};
+
+/*
+ * rune routines
+ */
+extern	int	runetochar(char*, Rune*);
+extern	int	chartorune(Rune*, char*);
+extern	char*	utfrune(char*, long);
+extern	int	utflen(char*);
+extern	int	runelen(long);
+
+extern	int	abs(int);
+
+/*
+ * print routines
+ */
+typedef struct Cconv Fconv;
+extern	char*	donprint(char*, char*, char*, void*);
+extern	int	sprint(char*, char*, ...);
+extern	char*	seprint(char*, char*, char*, ...);
+extern	int	snprint(char*, int, char*, ...);
+extern	int	print(char*, ...);
+
+/*
+ * one-of-a-kind
+ */
+extern	char*	cleanname(char*);
+extern	uintptr	getcallerpc(void*);
+extern	long	strtol(char*, char**, int);
+extern	ulong	strtoul(char*, char**, int);
+extern	vlong	strtoll(char*, char**, int);
+extern	uvlong	strtoull(char*, char**, int);
+extern	char	etext[];
+extern	char	edata[];
+extern	char	end[];
+extern	int	getfields(char*, char**, int, int, char*);
+
+/*
+ * Syscall data structures
+ */
+#define	MORDER	0x0003	/* mask for bits defining order of mounting */
+#define	MREPL	0x0000	/* mount replaces object */
+#define	MBEFORE	0x0001	/* mount goes before others in union directory */
+#define	MAFTER	0x0002	/* mount goes after others in union directory */
+#define	MCREATE	0x0004	/* permit creation in mounted directory */
+#define	MCACHE	0x0010	/* cache some data */
+#define	MMASK	0x001F	/* all bits on */
+
+#define	OREAD	0	/* open for read */
+#define	OWRITE	1	/* write */
+#define	ORDWR	2	/* read and write */
+#define	OEXEC	3	/* execute, == read but check execute permission */
+#define	OTRUNC	16	/* or'ed in (except for exec), truncate file first */
+#define	OCEXEC	32	/* or'ed in, close on exec */
+#define	ORCLOSE	64	/* or'ed in, remove on close */
+
+#define	NCONT	0	/* continue after note */
+#define	NDFLT	1	/* terminate after note */
+#define	NSAVE	2	/* clear note but hold state */
+#define	NRSTR	3	/* restore saved state */
+
+typedef struct Qid	Qid;
+typedef struct Dir	Dir;
+typedef struct Waitmsg	Waitmsg;
+
+#define	ERRLEN		64
+#define	DIRLEN		116
+#define	NAMELEN		28
+
+struct Qid
+{
+	ulong	path;
+	ulong	vers;
+};
+
+struct Dir
+{
+	char	name[NAMELEN];
+	char	uid[NAMELEN];
+	char	gid[NAMELEN];
+	Qid	qid;
+	ulong	mode;
+	long	atime;
+	long	mtime;
+	vlong	length;
+	short	type;
+	short	dev;
+};
+
+struct Waitmsg
+{
+	char	pid[12];	/* of loved one */
+	char	time[3*12];	/* of loved one and descendants */
+	char	msg[ERRLEN];
+};
+
+/*
+ *  locks
+ */
+typedef
+struct Lock {
+	int	val;
+} Lock;
+
+extern int	_tas(int*);
+
+extern	void	lock(Lock*);
+extern	void	unlock(Lock*);
+extern	int	canlock(Lock*);
--- /dev/null
+++ b/os/boot/bitsy/map
@@ -1,0 +1,10 @@
+defn acidmap()
+{
+	local dfoffset;
+
+	dfoffset = map()[1][3];
+	map({"text", _start, etext, 0x20});
+	map({"data", etext+1, edata, dfoffset});
+	print("Set map for plan 9 kernel image\n");
+	print("btext ", _start, " etext ", etext, "\n");
+}
--- /dev/null
+++ b/os/boot/bitsy/mem.h
@@ -1,0 +1,213 @@
+/*
+ * Memory and machine-specific definitions.  Used in C and assembler.
+ */
+
+/*
+ * Sizes
+ */
+#define	BI2BY		8			/* bits per byte */
+#define BI2WD		32			/* bits per word */
+#define	BY2WD		4			/* bytes per word */
+#define	BY2V		8			/* bytes per double word */
+#define	BY2PG		4096			/* bytes per page */
+#define	WD2PG		(BY2PG/BY2WD)		/* words per page */
+#define	PGSHIFT		12			/* log(BY2PG) */
+#define ROUND(s, sz)	(((s)+(sz-1))&~(sz-1))
+#define PGROUND(s)	ROUND(s, BY2PG)
+#define	BLOCKALIGN	8
+
+#define	MAXMACH		1			/* max # cpus system can run */
+
+/*
+ * Time
+ */
+#define	HZ		(20)				/* clock frequency */
+#define	MS2HZ		(1000/HZ)			/* millisec per clock tick */
+#define	TK2SEC(t)	((t)/HZ)			/* ticks to seconds */
+#define	TK2MS(t)	((((ulong)(t))*1000)/HZ)	/* ticks to milliseconds */
+#define	MS2TK(t)	((((ulong)(t))*HZ)/1000)	/* milliseconds to ticks */
+
+/*
+ *  Virtual addresses:
+ *
+ *  We direct map all discovered DRAM and the area twixt 0xe0000000 and
+ *  0xe8000000 used to provide zeros for cache flushing.
+ *
+ *  Flash is mapped to 0xb0000000 and special registers are mapped
+ *  on demand to areas starting at 0xa0000000.
+ *
+ *  The direct mapping is convenient but not necessary.  It means
+ *  that we don't have to turn on the MMU till well into the
+ *  kernel.  This can be changed by providing a mapping in l.s
+ *  before calling main.
+ */
+#define	UZERO		0			/* base of user address space */
+#define	UTZERO		(UZERO+BY2PG)		/* first address in user text */
+#define	KZERO		0xC0000000		/* base of kernel address space */
+#define	KTZERO		0xC0008000		/* first address in kernel text */
+#define	EMEMZERO	0x90000000		/* 256 meg for add on memory */
+#define	EMEMTOP		0xA0000000		/* ... */
+#define	REGZERO		0xA0000000		/* 128 meg for mapspecial regs */
+#define	REGTOP		0xA8000000		/* ... */
+#define	FLASHZERO	0xB0000000		/* 128 meg for flash */
+#define	FLASHTOP	0xB8000000		/* ... */
+#define	DRAMZERO	0xC0000000		/* 128 meg for dram */
+#define DRAMTOP		0xC8000000		/* ... */
+#define	UCDRAMZERO	0xC8000000		/* 128 meg for dram (uncached/unbuffered) */
+#define UCDRAMTOP	0xD0000000		/* ... */
+#define	NULLZERO	0xE0000000		/* 128 meg for cache flush zeroes */
+#define NULLTOP		0xE8000000		/* ... */
+#define	USTKTOP		0x2000000		/* byte just beyond user stack */
+#define	USTKSIZE	(8*1024*1024)		/* size of user stack */
+#define	TSTKTOP		(USTKTOP-USTKSIZE)	/* end of new stack in sysexec */
+#define TSTKSIZ 	100
+#define MACHADDR	(KZERO+0x00001000)
+#define	EVECTORS	0xFFFF0000		/* virt base of exception vectors */
+
+#define KSTACK		(16*1024)		/* Size of kernel stack */
+
+/*
+ *  Offsets into flash
+ */
+#define Flash_bootldr	(FLASHZERO+0x0)		/* boot loader */
+#define Flash_kernel	(FLASHZERO+0x10000)	/* boot kernel */
+#define	Flash_tar	(FLASHZERO+0x100000)	/* tar file containing fs.sac */
+
+/*
+ *  virtual MMU
+ */
+#define PTEMAPMEM	(1024*1024)	
+#define	PTEPERTAB	(PTEMAPMEM/BY2PG)
+#define SEGMAPSIZE	1984
+#define SSEGMAPSIZE	16
+#define PPN(x)		((x)&~(BY2PG-1))
+
+/*
+ *  SA1110 definitions
+ */
+
+/*
+ *  memory physical addresses
+ */
+#define PHYSFLASH0	0x00000000
+#define PHYSDRAM0	0xC0000000
+#define	PHYSNULL0	0xE0000000
+
+/*
+ *  peripheral control module physical addresses
+ */
+#define USBREGS		0x80000000	/* serial port 0 - USB */
+#define UART1REGS	0x80010000	/* serial port 1 - UART */
+#define GPCLKREGS	0x80020060	/* serial port 1 - general purpose clock */
+#define UART2REGS	0x80030000	/* serial port 2 - low speed IR */
+#define HSSPREGS	0x80040060	/* serial port 2 - high speed IR */
+#define UART3REGS	0x80050000	/* serial port 3 - RS232 UART */
+#define MCPREGS		0x80060000	/* serial port 4 - multimedia comm port */
+#define SSPREGS		0x80070060	/* serial port 4 - synchronous serial port */
+#define OSTIMERREGS	0x90000000	/* operating system timer registers */
+#define POWERREGS	0x90020000	/* power management */
+#define GPIOREGS	0x90040000	/* 28 general purpose IO pins */
+#define INTRREGS	0x90050000	/* interrupt registers */
+#define PPCREGS		0x90060000	/* peripheral pin controller */
+#define MEMCONFREGS	0xA0000000	/* memory configuration */
+#define LCDREGS		0xB0100000	/* display */
+
+/*
+ *  PCMCIA addresses
+ */
+#define PHYSPCM0REGS	0x20000000
+#define PYHSPCM0ATTR	0x28000000
+#define PYHSPCM0MEM	0x2C000000
+#define PHYSPCM1REGS	0x30000000
+#define PYHSPCM1ATTR	0x38000000
+#define PYHSPCM1MEM	0x3C000000
+
+/*
+ *  Program Status Registers
+ */
+#define PsrMusr		0x00000010	/* mode */
+#define PsrMfiq		0x00000011
+#define PsrMirq		0x00000012
+#define PsrMsvc		0x00000013
+#define PsrMabt		0x00000017
+#define PsrMund		0x0000001B
+#define PsrMask		0x0000001F
+
+#define PsrDfiq		0x00000040	/* disable FIQ interrupts */
+#define PsrDirq		0x00000080	/* disable IRQ interrupts */
+
+#define PsrV		0x10000000	/* overflow */
+#define PsrC		0x20000000	/* carry/borrow/extend */
+#define PsrZ		0x40000000	/* zero */
+#define PsrN		0x80000000	/* negative/less than */
+
+/*
+ *  Coprocessors
+ */
+#define CpMMU		15
+#define CpPWR		15
+
+/*
+ *  Internal MMU coprocessor registers
+ */
+#define CpCPUID		0		/* R: */
+#define CpControl	1		/* R: */
+#define CpTTB		2		/* RW: translation table base */
+#define CpDAC		3		/* RW: domain access control */
+#define CpFSR		5		/* RW: fault status */
+#define CpFAR		6		/* RW: fault address */
+#define CpCacheFlush	7		/* W: cache flushing, wb draining*/
+#define CpTLBFlush	8		/* W: TLB flushing */
+#define CpRBFlush	9		/* W: Read Buffer ops */
+#define CpPID		13		/* RW: PID for virtual mapping */
+#define	CpBpt		14		/* W: Breakpoint register */
+#define CpTest		15		/* W: Test, Clock and Idle Control */
+
+/*
+ *  CpControl
+ */
+#define CpCmmuena	0x00000001	/* M: MMU enable */
+#define CpCalign	0x00000002	/* A: alignment fault enable */
+#define CpCdcache	0x00000004	/* C: data cache on */
+#define CpCwb		0x00000008	/* W: write buffer turned on */
+#define CpCi32		0x00000010	/* P: 32-bit program space */
+#define CpCd32		0x00000020	/* D: 32-bit data space */
+#define CpCbe		0x00000080	/* B: big-endian operation */
+#define CpCsystem	0x00000100	/* S: system permission */
+#define CpCrom		0x00000200	/* R: ROM permission */
+#define CpCicache	0x00001000	/* I: instruction cache on */
+#define CpCvivec	0x00002000	/* X: virtual interrupt vector adjust */
+
+/*
+ *  fault codes
+ */
+#define	FCterm		0x2	/* terminal */
+#define	FCvec		0x0	/* vector */
+#define	FCalignf	0x1	/* unaligned full word data access */
+#define	FCalignh	0x3	/* unaligned half word data access */
+#define	FCl1abort	0xc	/* level 1 external abort on translation */
+#define	FCl2abort	0xe	/* level 2 external abort on translation */
+#define	FCtransSec	0x5	/* section translation */
+#define	FCtransPage	0x7	/* page translation */
+#define	FCdomainSec	0x9	/* section domain  */
+#define	FCdomainPage	0x11	/* page domain */
+#define	FCpermSec	0x9	/* section permissions  */
+#define	FCpermPage	0x11	/* page permissions */
+#define	FCabortLFSec	0x4	/* external abort on linefetch for section */
+#define	FCabortLFPage	0x6	/* external abort on linefetch for page */
+#define	FCabortNLFSec	0x8	/* external abort on non-linefetch for section */
+#define	FCabortNLFPage	0xa	/* external abort on non-linefetch for page */
+
+/*
+ *  PTE bits used by fault.h.  mmu.c translates them to real values.
+ */
+#define	PTEVALID	(1<<0)
+#define	PTERONLY	0	/* this is implied by the absence of PTEWRITE */
+#define	PTEWRITE	(1<<1)
+#define	PTEUNCACHED	(1<<2)
+#define PTEKERNEL	(1<<3)	/* no user access */
+
+/*
+ *  H3650 specific definitions
+ */
+#define EGPIOREGS	0x49000000	/* Additional GPIO register */
--- /dev/null
+++ b/os/boot/bitsy/mkfile
@@ -1,0 +1,45 @@
+objtype=arm
+</$objtype/mkfile
+BIN=/arm
+
+TARG=\
+	inflate\
+
+INFLATE=\
+	il.$O\
+	imain.$O\
+
+CORE=\
+	uart.$O\
+	inflate.$O\
+	donprint.$O\
+	print.$O\
+
+HFILES=\
+	mem.h\
+
+CFLAGS=-w -I.
+
+all:V:	$TARG
+
+install:V:	$BIN/$TARG
+
+$BIN/%:	%
+	cp $stem $BIN/$stem
+
+inflate: $INFLATE $CORE
+	$LD -o s$target -R4 -T0xC0200010 $prereq -lflate -lc
+	$LD -o _^$target -H5 -R4 -T0xC0200010 $prereq -lflate -lc
+	dd -conv sync -ibs 20k -if _^$target -of $target
+
+%.$O:	%.s
+	$AS $stem.s
+
+%.$O:	%.c
+	$CC $CFLAGS $stem.c
+
+%.$O:	$HFILES
+
+clean:
+	rm -f *.[$OS] [$OS].out y.tab.? y.debug y.output $TARG _$TARG  
+
--- /dev/null
+++ b/os/boot/bitsy/print.c
@@ -1,0 +1,56 @@
+#include "u.h"
+#include "lib.h"
+#include "fns.h"
+#include "dat.h"
+
+
+#define	SIZE	1024
+
+int
+print(char *fmt, ...)
+{
+	char buf[SIZE], *out;
+	va_list arg;
+
+	va_start(arg, fmt);
+	out = donprint(buf, buf+SIZE, fmt, arg);
+	va_end(arg);
+	serialputs(buf, out-buf);
+	return out-buf;
+}
+
+int
+sprint(char *buf, char *fmt, ...)
+{
+	char *out;
+	va_list arg;
+
+	va_start(arg, fmt);
+	out = donprint(buf, buf+SIZE, fmt, arg);
+	va_end(arg);
+	return out-buf;
+}
+
+int
+snprint(char *buf, int len, char *fmt, ...)
+{
+	char *out;
+	va_list arg;
+
+	va_start(arg, fmt);
+	out = donprint(buf, buf+len, fmt, arg);
+	va_end(arg);
+	return out-buf;
+}
+
+char*
+seprint(char *buf, char *e, char *fmt, ...)
+{
+	char *out;
+	va_list arg;
+
+	va_start(arg, fmt);
+	out = donprint(buf, e, fmt, arg);
+	va_end(arg);
+	return out;
+}
binary files /dev/null b/os/boot/bitsy/sinflate differ
--- /dev/null
+++ b/os/boot/bitsy/uart.c
@@ -1,0 +1,69 @@
+#include	"u.h"
+#include	"lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"io.h"
+
+enum
+{
+	/* ctl[0] bits */
+	Parity=		1<<0,
+	Even=		1<<1,
+	Stop2=		1<<2,
+	Bits8=		1<<3,
+	SCE=		1<<4,	/* synchronous clock enable */
+	RCE=		1<<5,	/* rx on falling edge of clock */
+	TCE=		1<<6,	/* tx on falling edge of clock */
+
+	/* ctl[3] bits */
+	Rena=		1<<0,	/* receiver enable */
+	Tena=		1<<1,	/* transmitter enable */
+	Break=		1<<2,	/* force TXD3 low */
+	Rintena=	1<<3,	/* enable receive interrupt */
+	Tintena=	1<<4,	/* enable transmitter interrupt */
+	Loopback=	1<<5,	/* loop back data */
+
+	/* data bits */
+	DEparity=	1<<8,	/* parity error */
+	DEframe=	1<<9,	/* framing error */
+	DEoverrun=	1<<10,	/* overrun error */
+
+	/* status[0] bits */
+	Tint=		1<<0,	/* transmit fifo half full interrupt */
+	Rint0=		1<<1,	/* receiver fifo 1/3-2/3 full */
+	Rint1=		1<<2,	/* receiver fifo not empty and receiver idle */
+	Breakstart=	1<<3,
+	Breakend=	1<<4,
+	Fifoerror=	1<<5,	/* fifo error */
+
+	/* status[1] bits */
+	Tbusy=		1<<0,	/* transmitting */
+	Rnotempty=	1<<1,	/* receive fifo not empty */
+	Tnotfull=	1<<2,	/* transmit fifo not full */
+	ParityError=	1<<3,
+	FrameError=	1<<4,
+	Overrun=	1<<5,
+};
+
+Uartregs *uart3regs = (Uartregs*)UART3REGS;
+
+
+/*
+ *  for iprint, just write it
+ */
+void
+serialputs(char *str, int n)
+{
+	Uartregs *ur;
+
+	ur = uart3regs;
+	while(n-- > 0){
+		/* wait for output ready */
+		while((ur->status[1] & Tnotfull) == 0)
+			;
+		ur->data = *str++;
+	}
+	while((ur->status[1] & Tbusy))
+		;
+}
--- /dev/null
+++ b/os/boot/efi/efi.c
@@ -1,0 +1,308 @@
+#include <u.h>
+#include "fns.h"
+#include "efi.h"
+
+UINTN MK;
+EFI_HANDLE IH;
+EFI_SYSTEM_TABLE *ST;
+
+void* (*open)(char *name);
+int (*read)(void *f, void *data, int len);
+void (*close)(void *f);
+
+void
+putc(int c)
+{
+	CHAR16 w[2];
+
+	w[0] = c;
+	w[1] = 0;
+	eficall(ST->ConOut->OutputString, ST->ConOut, w);
+}
+
+int
+getc(void)
+{
+	EFI_INPUT_KEY k;
+
+	if(eficall(ST->ConIn->ReadKeyStroke, ST->ConIn, &k))
+		return 0;
+	return k.UnicodeChar;
+}
+
+void
+usleep(int us)
+{
+	eficall(ST->BootServices->Stall, (UINTN)us);
+}
+
+void
+unload(void)
+{
+	eficall(ST->BootServices->ExitBootServices, IH, MK);
+}
+
+static void
+memconf(char **cfg)
+{
+	static uchar memtype[EfiMaxMemoryType] = {
+		[EfiReservedMemoryType]		2,
+		[EfiLoaderCode]			1,
+		[EfiLoaderData]			1,
+		[EfiBootServicesCode]		2,
+		[EfiBootServicesData]		2,
+		[EfiRuntimeServicesCode]	2,
+		[EfiRuntimeServicesData]	2,
+		[EfiConventionalMemory]		1,
+		[EfiUnusableMemory]		2,
+		[EfiACPIReclaimMemory]		3,
+		[EfiACPIMemoryNVS]		4,
+		[EfiMemoryMappedIO]		2,
+		[EfiMemoryMappedIOPortSpace]	2,
+		[EfiPalCode]			2,
+	};
+	UINTN mapsize, entsize;
+	EFI_MEMORY_DESCRIPTOR *t;
+	uchar mapbuf[96*1024], *p, m;
+	UINT32 entvers;
+	char *s;
+
+	mapsize = sizeof(mapbuf);
+	entsize = sizeof(EFI_MEMORY_DESCRIPTOR);
+	entvers = 1;
+	if(eficall(ST->BootServices->GetMemoryMap, &mapsize, mapbuf, &MK, &entsize, &entvers))
+		return;
+
+	s = *cfg;
+	for(p = mapbuf; mapsize >= entsize; p += entsize, mapsize -= entsize){
+		t = (EFI_MEMORY_DESCRIPTOR*)p;
+
+		m = 0;
+		if(t->Type < EfiMaxMemoryType)
+			m = memtype[t->Type];
+
+		if(m == 0)
+			continue;
+
+		if(s == *cfg)
+			memmove(s, "*e820=", 6), s += 6;
+		s = hexfmt(s, 1, m), *s++ = ' ';
+		s = hexfmt(s, 16, t->PhysicalStart), *s++ = ' ';
+		s = hexfmt(s, 16, t->PhysicalStart + t->NumberOfPages * 4096ULL), *s++ = ' ';
+	}
+	*s = '\0';
+	if(s > *cfg){
+		s[-1] = '\n';
+		print(*cfg);
+		*cfg = s;
+	}
+}
+
+static void
+acpiconf(char **cfg)
+{
+	static EFI_GUID ACPI_20_TABLE_GUID = {
+		0x8868e871, 0xe4f1, 0x11d3,
+		0xbc, 0x22, 0x00, 0x80,
+		0xc7, 0x3c, 0x88, 0x81,
+	};
+	static EFI_GUID ACPI_10_TABLE_GUID = {
+		0xeb9d2d30, 0x2d88, 0x11d3,
+		0x9a, 0x16, 0x00, 0x90,
+		0x27, 0x3f, 0xc1, 0x4d,
+	};
+	EFI_CONFIGURATION_TABLE *t;
+	uintptr pa;
+	char *s;
+	int n;
+
+	pa = 0;
+	t = ST->ConfigurationTable;
+	n = ST->NumberOfTableEntries;
+	while(--n >= 0){
+		if(memcmp(&t->VendorGuid, &ACPI_10_TABLE_GUID, sizeof(EFI_GUID)) == 0){
+			if(pa == 0)
+				pa = (uintptr)t->VendorTable;
+		} else if(memcmp(&t->VendorGuid, &ACPI_20_TABLE_GUID, sizeof(EFI_GUID)) == 0)
+			pa = (uintptr)t->VendorTable;
+		t++;
+	}
+
+	if(pa){
+		s = *cfg;
+		memmove(s, "*acpi=0x", 8), s += 8;
+		s = hexfmt(s, 0, pa), *s++ = '\n';
+		*s = '\0';
+		print(*cfg);
+		*cfg = s;
+	}
+}
+
+
+static int
+topbit(ulong mask)
+{
+	int bit = 0;
+
+	while(mask != 0){
+		mask >>= 1;
+		bit++;
+	}
+	return bit;
+}
+
+static int
+lowbit(ulong mask)
+{
+	int bit = 0;
+
+	while((mask & 1) == 0){
+		mask >>= 1;
+		bit++;
+	}
+	return bit;
+}
+
+static void
+screenconf(char **cfg)
+{
+	static EFI_GUID EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID = {
+		0x9042a9de, 0x23dc, 0x4a38,
+		0x96, 0xfb, 0x7a, 0xde,
+		0xd0, 0x80, 0x51, 0x6a,
+	};
+	EFI_GRAPHICS_OUTPUT_PROTOCOL *gop;
+	EFI_HANDLE *Handles;
+	UINTN Count;
+
+	EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *info;
+	ulong mr, mg, mb, mx, mc;
+	int i, bits, depth;
+	char *s;
+
+	Count = 0;
+	Handles = nil;
+	if(eficall(ST->BootServices->LocateHandleBuffer,
+		ByProtocol, &EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID, nil, &Count, &Handles))
+		return;
+
+	for(i=0; i<Count; i++){
+		gop = nil;
+		if(eficall(ST->BootServices->HandleProtocol,
+			Handles[i], &EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID, &gop))
+			continue;
+
+		if(gop == nil)
+			continue;
+		if((info = gop->Mode->Info) == nil)
+			continue;
+
+		switch(info->PixelFormat){
+		default:
+			continue;	/* unsupported */
+
+		case PixelRedGreenBlueReserved8BitPerColor:
+			mr = 0x000000ff;
+			mg = 0x0000ff00;
+			mb = 0x00ff0000;
+			mx = 0xff000000;
+			break;
+
+		case PixelBlueGreenRedReserved8BitPerColor:
+			mb = 0x000000ff;
+			mg = 0x0000ff00;
+			mr = 0x00ff0000;
+			mx = 0xff000000;
+			break;
+
+		case PixelBitMask:
+			mr = info->PixelInformation.RedMask;
+			mg = info->PixelInformation.GreenMask;
+			mb = info->PixelInformation.BlueMask;
+			mx = info->PixelInformation.ReservedMask;
+			break;
+		}
+
+		if((depth = topbit(mr | mg | mb | mx)) == 0)
+			continue;
+
+		/* make sure we have linear framebuffer */
+		if(gop->Mode->FrameBufferBase == 0)
+			continue;
+		if(gop->Mode->FrameBufferSize == 0)
+			continue;
+
+		goto Found;
+	}
+	return;
+
+Found:
+	s = *cfg;
+	memmove(s, "*bootscreen=", 12), s += 12;
+	s = decfmt(s, 0, info->PixelsPerScanLine), *s++ = 'x';
+	s = decfmt(s, 0, info->VerticalResolution), *s++ = 'x';
+	s = decfmt(s, 0, depth), *s++ = ' ';
+
+	while(depth > 0){
+		if(depth == topbit(mr)){
+			mc = mr;
+			*s++ = 'r';
+		} else if(depth == topbit(mg)){
+			mc = mg;
+			*s++ = 'g';
+		} else if(depth == topbit(mb)){
+			mc = mb;
+			*s++ = 'b';
+		} else if(depth == topbit(mx)){
+			mc = mx;
+			*s++ = 'x';
+		} else {
+			break;
+		}
+		bits = depth - lowbit(mc);
+		s = decfmt(s, 0, bits);
+		depth -= bits;
+	}
+	*s++ = ' ';
+
+	*s++ = '0', *s++ = 'x';
+	s = hexfmt(s, 0, gop->Mode->FrameBufferBase), *s++ = '\n';
+	*s = '\0';
+
+	print(*cfg);
+	*cfg = s;
+}
+
+void
+eficonfig(char **cfg)
+{
+	memconf(cfg);
+	acpiconf(cfg);
+	screenconf(cfg);
+}
+
+EFI_STATUS
+efimain(EFI_HANDLE ih, EFI_SYSTEM_TABLE *st)
+{
+	char path[MAXPATH], *kern;
+	void *f;
+
+	IH = ih;
+	ST = st;
+
+	f = nil;
+	if(pxeinit(&f) && isoinit(&f) && fsinit(&f))
+		print("no boot devices\n");
+
+	for(;;){
+		kern = configure(f, path);
+		f = open(kern);
+		if(f == nil){
+			print("not found\n");
+			continue;
+		}
+		print(bootkern(f));
+		print("\n");
+		f = nil;
+	}
+}
--- /dev/null
+++ b/os/boot/efi/efi.h
@@ -1,0 +1,256 @@
+typedef ushort	CHAR16;
+
+typedef uchar	UINT8;
+typedef ushort	UINT16;
+typedef ulong	UINT32;
+typedef uvlong	UINT64;
+typedef UINT8	BOOLEAN;
+
+typedef uintptr	UINTN;
+
+typedef void*	EFI_HANDLE;
+typedef UINT32	EFI_STATUS;
+
+enum {
+	AllHandles,
+	ByRegisterNotify,
+	ByProtocol,
+};
+
+typedef struct {
+	UINT32		Data1;
+	UINT16		Data2;
+	UINT16		Data3;
+	UINT8		Data4[8];
+} EFI_GUID;
+
+typedef struct {
+	UINT16		ScanCode;
+	CHAR16		UnicodeChar;
+} EFI_INPUT_KEY;
+
+typedef struct {
+	void		*Reset;
+	void		*ReadKeyStroke;
+	void		*WaitForKey;
+} EFI_SIMPLE_TEXT_INPUT_PROTOCOL;
+
+typedef struct {
+	void		*Reset;
+	void		*OutputString;
+	void		*TestString;
+	void		*QueryMode;
+	void		*SetMode;
+	void		*SetAttribute;
+	void		*ClearScreen;
+	void		*SetCursorPosition;
+	void		*EnableCursor;
+	void		*Mode;
+} EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;
+
+typedef struct {
+	UINT32		Revision;
+	EFI_HANDLE	ParentHandle;
+	void		*SystemTable;
+	EFI_HANDLE	DeviceHandle;
+	void		*FilePath;
+	void		*Reserved;
+	UINT32		LoadOptionsSize;
+	void		*LoadOptions;
+	void		*ImageBase;
+	UINT64		ImageSize;
+	UINT32		ImageCodeType;
+	UINT32		ImageDataType;
+	void		*Unload;
+} EFI_LOADED_IMAGE_PROTOCOL;
+
+typedef struct {
+	UINT32		RedMask;
+	UINT32		GreenMask;
+	UINT32		BlueMask;
+	UINT32		ReservedMask;
+} EFI_PIXEL_BITMASK;
+
+enum {
+	PixelRedGreenBlueReserved8BitPerColor,
+	PixelBlueGreenRedReserved8BitPerColor,
+	PixelBitMask,
+	PixelBltOnly,
+	PixelFormatMax,
+};
+
+typedef struct {
+	UINT32		Version;
+	UINT32		HorizontalResolution;
+	UINT32		VerticalResolution;
+	UINT32		PixelFormat;
+	EFI_PIXEL_BITMASK	PixelInformation;
+	UINT32		PixelsPerScanLine;
+} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;
+
+typedef struct {
+	UINT32		MaxMode;
+	UINT32		Mode;
+	EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
+	UINTN		SizeOfInfo;
+	UINT64		FrameBufferBase;
+	UINTN		FrameBufferSize;
+} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
+
+typedef struct {
+	void		*QueryMode;
+	void		*SetMode;
+	void		*Blt;
+	EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
+} EFI_GRAPHICS_OUTPUT_PROTOCOL;
+
+enum {
+	EfiReservedMemoryType,
+	EfiLoaderCode,
+	EfiLoaderData,
+	EfiBootServicesCode,
+	EfiBootServicesData,
+	EfiRuntimeServicesCode,
+	EfiRuntimeServicesData,
+	EfiConventionalMemory,
+	EfiUnusableMemory,
+	EfiACPIReclaimMemory,
+	EfiACPIMemoryNVS,
+	EfiMemoryMappedIO,
+	EfiMemoryMappedIOPortSpace,
+	EfiPalCode,
+	EfiMaxMemoryType,
+};
+
+typedef struct {
+	UINT32		Type;
+	UINT32		Reserved;
+	UINT64		PhysicalStart;
+	UINT64		VirtualStart;
+	UINT64		NumberOfPages;
+	UINT64		Attribute;
+} EFI_MEMORY_DESCRIPTOR;
+
+
+typedef struct {
+	UINT64	Signature;
+	UINT32	Revision;
+	UINT32	HeaderSize;
+	UINT32	CRC32;
+	UINT32	Reserved;
+} EFI_TABLE_HEADER;
+
+typedef struct {
+	EFI_TABLE_HEADER;
+
+	void		*RaiseTPL;
+	void		*RestoreTPL;
+	void		*AllocatePages;
+	void		*FreePages;
+	void		*GetMemoryMap;
+	void		*AllocatePool;
+	void		*FreePool;
+
+	void		*CreateEvent;
+	void		*SetTimer;
+	void		*WaitForEvent;
+	void		*SignalEvent;
+	void		*CloseEvent;
+	void		*CheckEvent;
+
+	void		**InstallProtocolInterface;
+	void		**ReinstallProtocolInterface;
+	void		**UninstallProtocolInterface;
+
+	void		*HandleProtocol;
+	void		*Reserved;
+	void		*RegisterProtocolNotify;
+
+	void		*LocateHandle;
+	void		*LocateDevicePath;
+	void		*InstallConfigurationTable;
+
+	void		*LoadImage;
+	void		*StartImage;
+	void		*Exit;
+	void		*UnloadImage;
+	void		*ExitBootServices;
+
+	void		*GetNextMonotonicCount;
+	void		*Stall;
+	void		*SetWatchdogTimer;
+
+	void		*ConnectController;
+	void		*DisconnectController;
+
+	void		*OpenProtocol;
+	void		*CloseProtocol;
+
+	void		*OpenProtocolInformation;
+	void		*ProtocolsPerHandle;
+	void		*LocateHandleBuffer;
+	void		*LocateProtocol;
+
+	void		*InstallMultipleProtocolInterfaces;
+	void		*UninstallMultipleProtocolInterfaces;
+
+	void		*CalculateCrc32;
+
+	void		*CopyMem;
+	void		*SetMem;
+	void		*CreateEventEx;
+} EFI_BOOT_SERVICES;
+
+typedef struct {
+	EFI_TABLE_HEADER;
+
+	void		*GetTime;
+	void		*SetTime;
+	void		*GetWakeupTime;
+	void		*SetWakeupTime;
+
+	void		*SetVirtualAddressMap;
+	void		*ConvertPointer;
+
+	void		*GetVariable;
+	void		*GetNextVariableName;
+	void		*SetVariable;
+
+	void		*GetNextHighMonotonicCount;
+	void		*ResetSystem;
+
+	void		*UpdateCapsule;
+	void		*QueryCapsuleCapabilities;
+
+	void		*QueryVariableInfo;
+} EFI_RUNTIME_SERVICES;
+
+typedef struct {
+	EFI_GUID	VendorGuid;
+	void		*VendorTable;
+} EFI_CONFIGURATION_TABLE;
+
+typedef struct {
+	EFI_TABLE_HEADER;
+
+	CHAR16		*FirmwareVendor;
+	UINT32		FirmwareRevision;
+
+	EFI_HANDLE	ConsoleInHandle;
+	EFI_SIMPLE_TEXT_INPUT_PROTOCOL	*ConIn;
+
+	EFI_HANDLE	ConsoleOutHandle;
+	EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL	*ConOut;
+
+	EFI_HANDLE	StandardErrorHandle;
+	EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL	*StdErr;
+
+	EFI_RUNTIME_SERVICES	*RuntimeServices;
+	EFI_BOOT_SERVICES	*BootServices;
+
+	UINTN			NumberOfTableEntries;
+	EFI_CONFIGURATION_TABLE	*ConfigurationTable;
+} EFI_SYSTEM_TABLE;
+
+extern EFI_SYSTEM_TABLE *ST;
+extern EFI_HANDLE IH;
--- /dev/null
+++ b/os/boot/efi/fns.h
@@ -1,0 +1,39 @@
+enum {
+	MAXPATH = 128,
+};
+
+extern char hex[];
+
+void usleep(int t);
+void jump(void *pc);
+
+int pxeinit(void **pf);
+int isoinit(void **pf);
+int fsinit(void **pf);
+
+void* (*open)(char *name);
+int (*read)(void *f, void *data, int len);
+void (*close)(void *f);
+
+int readn(void *f, void *data, int len);
+void unload(void);
+
+int getc(void);
+void putc(int c);
+
+void memset(void *p, int v, int n);
+void memmove(void *dst, void *src, int n);
+int memcmp(void *src, void *dst, int n);
+int strlen(char *s);
+char *strchr(char *s, int c);
+char *strrchr(char *s, int c);
+void print(char *s);
+
+char *configure(void *f, char *path);
+char *bootkern(void *f);
+
+char *hexfmt(char *s, int i, uvlong a);
+char *decfmt(char *s, int i, ulong a);
+
+uintptr eficall(void *proc, ...);
+void eficonfig(char **cfg);
--- /dev/null
+++ b/os/boot/efi/fs.c
@@ -1,0 +1,166 @@
+#include <u.h>
+#include "fns.h"
+#include "efi.h"
+
+typedef struct {
+	UINT64		Revision;
+	void		*Open;
+	void		*Close;
+	void		*Delete;
+	void		*Read;
+	void		*Write;
+	void		*GetPosition;
+	void		*SetPosition;
+	void		*GetInfo;
+	void		*SetInfo;
+	void		*Flush;
+	void		*OpenEx;
+	void		*ReadEx;
+	void		*WriteEx;
+	void		*FlushEx;
+} EFI_FILE_PROTOCOL;
+
+typedef struct {
+	UINT64		Revision;
+	void		*OpenVolume;
+} EFI_SIMPLE_FILE_SYSTEM_PROTOCOL;
+
+static
+EFI_GUID EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID = {
+	0x0964e5b22, 0x6459, 0x11d2,
+	0x8e, 0x39, 0x00, 0xa0,
+	0xc9, 0x69, 0x72, 0x3b,
+};
+
+static EFI_GUID EFI_LOADED_IMAGE_PROTOCOL_GUID = {
+	0x5b1b31a1, 0x9562, 0x11d2,
+	0x8e, 0x3f, 0x00, 0xa0,
+	0xc9, 0x69, 0x72, 0x3b,
+};
+
+static
+EFI_FILE_PROTOCOL *fsroot;
+
+static void
+towpath(CHAR16 *w, int nw, char *s)
+{
+	int i;
+
+	for(i=0; *s && i<nw-1; i++){
+		*w = *s++;
+		if(*w == '/')
+			*w = '\\';
+		w++;
+	}
+	*w = 0;
+}
+
+static void*
+fsopen(char *name)
+{
+	CHAR16 wname[MAXPATH];
+	EFI_FILE_PROTOCOL *fp;
+
+	if(fsroot == nil)
+		return nil;
+
+	towpath(wname, MAXPATH, name);
+
+	fp = nil;
+	if(eficall(fsroot->Open, fsroot, &fp, wname, (UINT64)1, (UINT64)1))
+		return nil;
+	return fp;
+}
+
+static int
+fsread(void *f, void *data, int len)
+{
+	UINTN size;
+
+	size = len > 4096 ? 4096 : len;
+	if(eficall(((EFI_FILE_PROTOCOL*)f)->Read, f, &size, data))
+		return 0;
+	return (int)size;
+}
+
+static void
+fsclose(void *f)
+{
+	eficall(((EFI_FILE_PROTOCOL*)f)->Close, f);
+}
+
+int
+fsinit(void **pf)
+{
+	EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *fs;
+	EFI_LOADED_IMAGE_PROTOCOL *image;
+	EFI_FILE_PROTOCOL *root;
+	EFI_HANDLE *Handles;
+	void *f;
+	UINTN Count;
+	int i;
+
+	image = nil;
+
+	/* locate kernel and plan9.ini by deriving a fs protocol
+	 * from the device the loader was read from.
+	 * if that fails, fall back to old method.
+	 */
+	if(eficall(ST->BootServices->HandleProtocol, IH,
+		&EFI_LOADED_IMAGE_PROTOCOL_GUID, &image) == 0 &&
+		eficall(ST->BootServices->HandleProtocol, image->DeviceHandle,
+		&EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID, &fs) == 0 &&
+		eficall(fs->OpenVolume, fs, &root) == 0){
+
+		fsroot = root;
+		f = fsopen("/plan9.ini");
+		if(f != nil){
+			if(pf != nil)
+				*pf = f;
+			else
+				fsclose(f);
+
+			goto gotit;
+		}
+	}
+
+	Count = 0;
+	Handles = nil;
+	if(eficall(ST->BootServices->LocateHandleBuffer,
+		ByProtocol, &EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID, nil, &Count, &Handles))
+		return -1;
+
+	/*
+	 * assuming the ESP is the first entry in the handle buffer, so go backwards
+	 * to scan for plan9.ini in other (9fat) filesystems first. if nothing is found
+	 * we'll be defaulting to the ESP.
+	 */
+	fsroot = nil;
+	for(i=Count-1; i>=0; i--){
+		root = nil;
+		fs = nil;
+		if(eficall(ST->BootServices->HandleProtocol,
+			Handles[i], &EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID, &fs))
+			continue;
+		if(eficall(fs->OpenVolume, fs, &root))
+			continue;
+		fsroot = root;
+		f = fsopen("/plan9.ini");
+		if(f != nil){
+			if(pf != nil)
+				*pf = f;
+			else
+				fsclose(f);
+			break;
+		}
+	}
+	if(fsroot == nil)
+		return -1;
+
+gotit:
+	read = fsread;
+	close = fsclose;
+	open = fsopen;
+
+	return 0;
+}
--- /dev/null
+++ b/os/boot/efi/iso.c
@@ -1,0 +1,222 @@
+#include <u.h>
+#include "fns.h"
+#include "efi.h"
+
+enum {
+	Sectsz = 0x800,
+	Dirsz = 33,
+};
+
+typedef struct Extend Extend;
+typedef struct Dir Dir;
+
+struct Extend
+{
+	ulong lba;
+	ulong len;
+	uchar *rp;
+	uchar *ep;
+	uchar buf[Sectsz];
+};
+
+struct Dir
+{
+	uchar dirlen;
+	uchar extlen;
+
+	uchar lba[8];
+	uchar len[8];
+
+	uchar date[7];
+
+	uchar flags[3];
+
+	uchar seq[4];
+
+	uchar namelen;
+};
+
+typedef struct {
+	UINT32		MediaId;
+
+	BOOLEAN		RemovableMedia;
+	BOOLEAN		MediaPresent;
+	BOOLEAN		LogicalPartition;
+	BOOLEAN		ReadOnly;
+
+	BOOLEAN		WriteCaching;
+	BOOLEAN		Pad[3];
+
+	UINT32		BlockSize;
+	UINT32		IoAlign;
+	UINT64		LastBlock;
+} EFI_BLOCK_IO_MEDIA;
+
+typedef struct {
+	UINT64		Revision;
+	EFI_BLOCK_IO_MEDIA	*Media;
+	void		*Reset;
+	void		*ReadBlocks;
+	void		*WriteBlocks;
+	void		*FlushBlocks;
+} EFI_BLOCK_IO_PROTOCOL;
+
+static EFI_GUID
+EFI_BLOCK_IO_PROTOCOL_GUID = {
+	0x964e5b21, 0x6459, 0x11d2,
+	0x8e, 0x39, 0x00, 0xa0,
+	0xc9, 0x69, 0x72, 0x3b,
+};
+
+static EFI_BLOCK_IO_PROTOCOL *bio;
+
+static int
+readsect(ulong lba, void *buf)
+{
+	lba *= Sectsz/bio->Media->BlockSize;
+	return eficall(bio->ReadBlocks, bio, (UINTN)bio->Media->MediaId, (UINT64)lba, (UINTN)Sectsz, buf);
+}
+
+static int
+isoread(void *f, void *data, int len)
+{
+	Extend *ex = f;
+
+	if(ex->len > 0 && ex->rp >= ex->ep)
+		if(readsect(ex->lba++, ex->rp = ex->buf))
+			return -1;
+	if(ex->len < len)
+		len = ex->len;
+	if(len > (ex->ep - ex->rp))
+		len = ex->ep - ex->rp;
+	memmove(data, ex->rp, len);
+	ex->rp += len;
+	ex->len -= len;
+	return len;
+}
+
+void
+isoclose(void *f)
+{
+	Extend *ex = f;
+
+	ex->lba = 0;
+	ex->len = 0;
+	ex->rp = ex->ep = ex->buf + Sectsz;
+}
+
+static int
+isowalk(Extend *ex, char *path)
+{
+	char name[MAXPATH], c, *end;
+	int i;
+	Dir d;
+
+	isoclose(ex);
+
+	/* find pvd */
+	for(i=0x10; i<0x1000; i++){
+		if(readsect(i, ex->buf))
+			return -1;
+		if(memcmp(ex->buf, "\001CD001\001", 7) == 0)
+			goto Foundpvd;
+	}
+	return -1;
+Foundpvd:
+	ex->lba = *((ulong*)(ex->buf + 156 + 2));
+	ex->len = *((ulong*)(ex->buf + 156 + 10));
+	if(*path == 0)
+		return 0;
+
+	for(;;){
+		if(readn(ex, &d, Dirsz) != Dirsz)
+			break;
+		if(d.dirlen == 0)
+			break;
+		if(readn(ex, name, d.namelen) != d.namelen)
+			break;
+		i = d.dirlen - (Dirsz + d.namelen);
+		while(i-- > 0)
+			read(ex, &c, 1);
+		for(i=0; i<d.namelen; i++){
+			c = name[i];
+			if(c >= 'A' && c <= 'Z'){
+				c -= 'A';
+				c += 'a';
+			}
+			name[i] = c;
+		}
+		name[i] = 0;
+		while(*path == '/')
+			path++;
+		if((end = strchr(path, '/')) == 0)
+			end = path + strlen(path);
+		i = end - path;
+		if(d.namelen == i && memcmp(name, path, i) == 0){
+			ex->rp = ex->ep;
+			ex->lba = *((ulong*)d.lba);
+			ex->len = *((ulong*)d.len);
+			if(*end == 0)
+				return 0;
+			else if(d.flags[0] & 2){
+				path = end;
+				continue;
+			}
+			break;
+		}
+	}
+	return -1;
+}
+
+static void*
+isoopen(char *path)
+{
+	static uchar buf[sizeof(Extend)+8];
+	Extend *ex = (Extend*)((uintptr)(buf+7)&~7);
+
+	if(isowalk(ex, path))
+		return nil;
+	return ex;
+}
+
+int
+isoinit(void **fp)
+{
+	EFI_BLOCK_IO_MEDIA *media;
+	EFI_HANDLE *Handles;
+	UINTN Count;
+	int i;
+
+	bio = nil;
+	Count = 0;
+	Handles = nil;
+	if(eficall(ST->BootServices->LocateHandleBuffer,
+		ByProtocol, &EFI_BLOCK_IO_PROTOCOL_GUID, nil, &Count, &Handles))
+		return -1;
+
+	for(i=0; i<Count; i++){
+		bio = nil;
+		if(eficall(ST->BootServices->HandleProtocol,
+			Handles[i], &EFI_BLOCK_IO_PROTOCOL_GUID, &bio))
+			continue;
+	
+		media = bio->Media;
+		if(media != nil
+		&& media->MediaPresent
+		&& media->LogicalPartition == 0
+		&& media->BlockSize != 0
+		&& isoopen("") != nil)
+			goto Found;
+	}
+	return -1;
+
+Found:
+	open = isoopen;
+	read = isoread;
+	close = isoclose;
+
+	if(fp != nil)
+		*fp = isoopen("/cfg/plan9.ini");
+
+	return 0;
+}
--- /dev/null
+++ b/os/boot/efi/mem.h
@@ -1,0 +1,47 @@
+/*
+ * Memory and machine-specific definitions.  Used in C and assembler.
+ */
+
+/*
+ * Sizes
+ */
+#define	BI2BY		8			/* bits per byte */
+#define	BI2WD		32			/* bits per word */
+#define	BY2WD		4			/* bytes per word */
+#define	BY2PG		4096			/* bytes per page */
+#define	WD2PG		(BY2PG/BY2WD)		/* words per page */
+#define	PGSHIFT		12			/* log(BY2PG) */
+#define	PGROUND(s)	(((s)+(BY2PG-1))&~(BY2PG-1))
+
+/*
+ * Fundamental addresses
+ */
+#define CONFADDR	0x1200		/* info passed from boot loader */
+#define BIOSXCHG	0x6000		/* To exchange data with the BIOS */
+
+#define SELGDT	(0<<3)	/* selector is in gdt */
+#define	SELLDT	(1<<3)	/* selector is in ldt */
+
+#define SELECTOR(i, t, p)	(((i)<<3) | (t) | (p))
+
+/*
+ *  fields in segment descriptors
+ */
+#define	SEGDATA	(0x10<<8)	/* data/stack segment */
+#define	SEGEXEC	(0x18<<8)	/* executable segment */
+#define	SEGTSS	(0x9<<8)	/* TSS segment */
+#define	SEGCG	(0x0C<<8)	/* call gate */
+#define	SEGIG	(0x0E<<8)	/* interrupt gate */
+#define	SEGTG	(0x0F<<8)	/* trap gate */
+#define	SEGLDT	(0x02<<8)	/* local descriptor table */
+#define	SEGTYPE	(0x1F<<8)
+
+#define	SEGP	(1<<15)		/* segment present */
+#define	SEGPL(x) ((x)<<13)	/* priority level */
+#define	SEGB	(1<<22)		/* granularity 1==4k (for expand-down) */
+#define	SEGD	(1<<22)		/* default 1==32bit (for code) */
+#define	SEGE	(1<<10)		/* expand down */
+#define	SEGW	(1<<9)		/* writable (for data/stack) */
+#define	SEGR	(1<<9)		/* readable (for code) */
+#define SEGL	(1<<21)		/* 64 bit */
+#define	SEGG	(1<<23)		/* granularity 1==4k (for other) */
--- /dev/null
+++ b/os/boot/efi/mkfile
@@ -1,0 +1,105 @@
+TARG=bootia32.efi bootx64.efi efiboot.fat
+HFILES=fns.h mem.h
+IMAGEBASE=0x8000
+CFLAGS=-FTVw
+PEFLAGS=$CFLAGS '-DIMAGEBASE='$IMAGEBASE
+
+all:V: $TARG
+
+install:V: $TARG
+	cp $prereq /386
+
+bootia32.efi:	pe32.8 efi.8 fs.8 pxe.8 iso.8 sub.8
+	8l -l -H3 -T$IMAGEBASE -o $target $prereq
+
+pe32.8:	pe32.s
+	8a $PEFLAGS pe32.s
+
+efi.8:	efi.c efi.h
+	8c $CFLAGS efi.c
+
+fs.8:	fs.c efi.h
+	8c $CFLAGS fs.c
+
+pxe.8:	pxe.c efi.h
+	8c $CFLAGS pxe.c
+
+iso.8:	iso.c efi.h
+	8c $CFLAGS iso.c
+
+sub.8:	sub.c
+	8c $CFLAGS sub.c
+
+%.8:	$HFILES
+
+
+bootx64.efi:	pe64.6 efi.6 fs.6 pxe.6 iso.6 sub.6
+	6l -l -s -R1 -T$IMAGEBASE -o bootx64.out $prereq
+	dd -if bootx64.out -bs 1 -iseek 40 >$target
+
+pe64.6:	pe64.s
+	6a $PEFLAGS pe64.s
+
+efi.6:	efi.c efi.h
+	6c $CFLAGS efi.c
+
+fs.6:	fs.c efi.h
+	6c $CFLAGS fs.c
+
+pxe.6:	pxe.c efi.h
+	6c $CFLAGS pxe.c
+
+iso.6:	iso.c efi.h
+	6c $CFLAGS iso.c
+
+sub.6:	sub.c
+	6c $CFLAGS sub.c
+
+%.6:	$HFILES
+
+efiboot.fat:D:	bootia32.efi bootx64.efi
+	s = $target.$pid
+	rm -f $target
+	dd -if /dev/zero -of $target -bs 1024 -count 1024
+	disk/format -xd -t hard $target
+	dossrv -f $target $s
+	mount -c /srv/$s /n/esp
+	mkdir /n/esp/efi
+	mkdir /n/esp/efi/boot
+	cp bootia32.efi /n/esp/efi/boot
+	cp bootx64.efi /n/esp/efi/boot
+	unmount /n/esp
+	rm /srv/$s
+
+
+test.iso:D:	efiboot.fat
+	rm -fr tmp
+	mkdir tmp
+	mkdir tmp/cfg
+	mkdir tmp/386
+	cp efiboot.fat tmp/386
+	cp /386/9bootiso tmp/386
+	cp /386/9pc tmp/386
+	echo 'bootfile=/386/9pc' >tmp/cfg/plan9.ini
+	disk/mk9660 -B 386/9bootiso -E 386/efiboot.fat -p <{echo +} -s tmp $target
+	rm -r tmp
+
+test.fat:D:	bootia32.efi bootx64.efi
+	s = $target.$pid
+	rm -f $target
+	dd -if /dev/zero -of $target -bs 65536 -count 128
+	disk/format -xd -t hard $target
+	dossrv -f $target $s
+	mount -c /srv/$s /n/esp
+	mkdir /n/esp/efi
+	mkdir /n/esp/efi/boot
+	cp bootia32.efi /n/esp/efi/boot
+	cp bootx64.efi /n/esp/efi/boot
+	cp /386/9pc /n/esp
+	echo 'bootfile=9pc' >/n/esp/plan9.ini
+	unmount /n/esp
+	rm /srv/$s
+
+
+clean:V:
+	rm -f *.[68] *.out $TARG test.* 
--- /dev/null
+++ b/os/boot/efi/pe32.s
@@ -1,0 +1,159 @@
+TEXT mzhdr(SB), 1, $0
+	BYTE $'M'; BYTE $'Z'
+
+	WORD $0		/* e_cblp UNUSED */
+	WORD $0		/* e_cp UNUSED */
+	WORD $0		/* e_crlc UNUSED */
+	WORD $0		/* e_cparhdr UNUSED */
+	WORD $0		/* e_minalloc UNUSED */
+	WORD $0		/* e_maxalloc UNUSED */
+	WORD $0		/* e_ss UNUSED */
+	WORD $0		/* e_sp UNUSED */
+	WORD $0		/* e_csum UNUSED */
+	WORD $0		/* e_ip UNUSED */
+	WORD $0		/* e_cs UNUSED */
+	WORD $0		/* e_lsarlc UNUSED */
+	WORD $0		/* e_ovno UNUSED */
+
+	WORD $0		/* e_res UNUSED */
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+
+	WORD $0		/* e_oemid UNUSED */
+
+	WORD $0		/* e_res2 UNUSED */
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+
+	LONG $pehdr-IMAGEBASE(SB)	/* offset to pe header */
+
+TEXT pehdr(SB), 1, $0
+	BYTE $'P'; BYTE $'E'; BYTE $0; BYTE $0
+
+	WORD $0x014C		/* Machine (Intel 386) */
+	WORD $1			/* NumberOfSections */
+	LONG $0			/* TimeDateStamp UNUSED */
+	LONG $0			/* PointerToSymbolTable UNUSED */
+	LONG $0			/* NumberOfSymbols UNUSED */
+	WORD $0xE0		/* SizeOfOptionalHeader */
+	WORD $2103		/* Characteristics (no relocations, executable, 32 bit) */
+
+	WORD $0x10B		/* Magic (PE32) */
+    	BYTE $9			/* MajorLinkerVersion UNUSED */
+	BYTE $0			/* MinorLinkerVersion UNUSED */
+	LONG $0			/* SizeOfCode UNUSED */
+	LONG $0			/* SizeOfInitializedData UNUSED */
+	LONG $0			/* SizeOfUninitializedData UNUSED */
+	LONG $start-IMAGEBASE(SB)/* AddressOfEntryPoint */
+	LONG $0			/* BaseOfCode UNUSED */
+	LONG $0			/* BaseOfData UNUSED */
+	LONG $IMAGEBASE		/* ImageBase */
+	LONG $0x200		/* SectionAlignment */
+	LONG $0x200		/* FileAlignment */
+	WORD $4			/* MajorOperatingSystemVersion UNUSED */
+	WORD $0			/* MinorOperatingSystemVersion UNUSED */
+	WORD $0			/* MajorImageVersion UNUSED */
+	WORD $0			/* MinorImageVersion UNUSED */
+	WORD $4			/* MajorSubsystemVersion */
+	WORD $0			/* MinorSubsystemVersion UNUSED */
+	LONG $0			/* Win32VersionValue UNUSED */
+	LONG $end-IMAGEBASE(SB)	/* SizeOfImage */
+ 	LONG $start-IMAGEBASE(SB)/* SizeOfHeaders */
+ 	LONG $0			/* CheckSum UNUSED */
+	WORD $10		/* Subsystem (10 = efi application) */
+	WORD $0			/* DllCharacteristics UNUSED */
+	LONG $0			/* SizeOfStackReserve UNUSED */
+	LONG $0			/* SizeOfStackCommit UNUSED */
+	LONG $0			/* SizeOfHeapReserve UNUSED */
+	LONG $0			/* SizeOfHeapCommit UNUSED */
+	LONG $0			/* LoaderFlags UNUSED */
+	LONG $16		/* NumberOfRvaAndSizes UNUSED */
+
+	LONG $0; LONG $0
+	LONG $0; LONG $0
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+
+	BYTE $'.'; BYTE $'t'; BYTE $'e'; BYTE $'x'
+	BYTE $'t'; BYTE $0;   BYTE $0;   BYTE $0
+	LONG $edata-(IMAGEBASE+0x200)(SB)		/* VirtualSize */
+	LONG $start-IMAGEBASE(SB)			/* VirtualAddress */
+	LONG $edata-(IMAGEBASE+0x200)(SB)		/* SizeOfData */
+	LONG $start-IMAGEBASE(SB)			/* PointerToRawData */
+	LONG $0			/* PointerToRelocations UNUSED */
+	LONG $0			/* PointerToLinenumbers UNUSED */
+	WORD $0			/* NumberOfRelocations UNUSED */
+	WORD $0			/* NumberOfLinenumbers UNUSED */
+	LONG $0x86000020	/* Characteristics (code, execute, read, write) */
+
+	/* padding to get start(SB) at IMAGEBASE+0x200 */
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+
+TEXT start(SB), 1, $0
+	CALL reloc(SP)
+
+TEXT reloc(SB), 1, $0
+	MOVL 0(SP), SI
+	SUBL $reloc-IMAGEBASE(SB), SI
+	MOVL $IMAGEBASE, DI
+	MOVL $edata-IMAGEBASE(SB), CX
+	CLD
+	REP; MOVSB
+	MOVL $efimain(SB), DI
+	MOVL DI, (SP)
+	RET
+
+TEXT jump(SB), $0
+	CLI
+	MOVL 4(SP), AX
+	JMP *AX
+
+TEXT eficall(SB), 1, $0
+	MOVL SP, SI
+	MOVL SP, DI
+	MOVL $(4*16), CX
+	SUBL CX, DI
+	ANDL $~15ULL, DI
+	SUBL $8, DI
+
+	MOVL 4(SI), AX
+	LEAL 8(DI), SP
+
+	CLD
+	REP; MOVSB
+	SUBL $(4*16), SI
+
+	CALL AX
+
+	MOVL SI, SP
+	RET
--- /dev/null
+++ b/os/boot/efi/pe64.s
@@ -1,0 +1,237 @@
+TEXT mzhdr(SB), 1, $0
+	BYTE $'M'; BYTE $'Z'
+
+	WORD $0		/* e_cblp UNUSED */
+	WORD $0		/* e_cp UNUSED */
+	WORD $0		/* e_crlc UNUSED */
+	WORD $0		/* e_cparhdr UNUSED */
+	WORD $0		/* e_minalloc UNUSED */
+	WORD $0		/* e_maxalloc UNUSED */
+	WORD $0		/* e_ss UNUSED */
+	WORD $0		/* e_sp UNUSED */
+	WORD $0		/* e_csum UNUSED */
+	WORD $0		/* e_ip UNUSED */
+	WORD $0		/* e_cs UNUSED */
+	WORD $0		/* e_lsarlc UNUSED */
+	WORD $0		/* e_ovno UNUSED */
+
+	WORD $0		/* e_res UNUSED */
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+
+	WORD $0		/* e_oemid UNUSED */
+
+	WORD $0		/* e_res2 UNUSED */
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+	WORD $0
+
+	LONG $pehdr-IMAGEBASE(SB)	/* offset to pe header */
+
+TEXT pehdr(SB), 1, $0
+	BYTE $'P'; BYTE $'E'; BYTE $0; BYTE $0
+
+	WORD $0x8664		/* Machine (AMD64) */
+	WORD $1			/* NumberOfSections */
+	LONG $0			/* TimeDateStamp UNUSED */
+	LONG $0			/* PointerToSymbolTable UNUSED */
+	LONG $0			/* NumberOfSymbols UNUSED */
+	WORD $0xF0		/* SizeOfOptionalHeader */
+	WORD $2223		/* Characteristics */
+
+	WORD $0x20B		/* Magic (PE32+) */
+    	BYTE $9			/* MajorLinkerVersion UNUSED */
+	BYTE $0			/* MinorLinkerVersion UNUSED */
+	LONG $0			/* SizeOfCode UNUSED */
+	LONG $0			/* SizeOfInitializedData UNUSED */
+	LONG $0			/* SizeOfUninitializedData UNUSED */
+	LONG $start-IMAGEBASE(SB)/* AddressOfEntryPoint */
+	LONG $0			/* BaseOfCode UNUSED */
+
+	QUAD $IMAGEBASE		/* ImageBase */
+	LONG $0x200		/* SectionAlignment */
+	LONG $0x200		/* FileAlignment */
+	WORD $4			/* MajorOperatingSystemVersion UNUSED */
+	WORD $0			/* MinorOperatingSystemVersion UNUSED */
+	WORD $0			/* MajorImageVersion UNUSED */
+	WORD $0			/* MinorImageVersion UNUSED */
+	WORD $4			/* MajorSubsystemVersion */
+	WORD $0			/* MinorSubsystemVersion UNUSED */
+	LONG $0			/* Win32VersionValue UNUSED */
+	LONG $end-IMAGEBASE(SB)	/* SizeOfImage */
+ 	LONG $start-IMAGEBASE(SB)/* SizeOfHeaders */
+ 	LONG $0			/* CheckSum UNUSED */
+	WORD $10		/* Subsystem (10 = efi application) */
+	WORD $0			/* DllCharacteristics UNUSED */
+	QUAD $0			/* SizeOfStackReserve UNUSED */
+	QUAD $0			/* SizeOfStackCommit UNUSED */
+	QUAD $0			/* SizeOfHeapReserve UNUSED */
+	QUAD $0			/* SizeOfHeapCommit UNUSED */
+	LONG $0			/* LoaderFlags UNUSED */
+	LONG $16		/* NumberOfRvaAndSizes UNUSED */
+
+	LONG $0; LONG $0
+	LONG $0; LONG $0
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+	LONG $0; LONG $0		/* RVA */
+
+	BYTE $'.'; BYTE $'t'; BYTE $'e'; BYTE $'x'
+	BYTE $'t'; BYTE $0;   BYTE $0;   BYTE $0
+	LONG $edata-(IMAGEBASE+0x200)(SB)		/* VirtualSize */
+	LONG $start-IMAGEBASE(SB)			/* VirtualAddress */
+	LONG $edata-(IMAGEBASE+0x200)(SB)		/* SizeOfData */
+	LONG $start-IMAGEBASE(SB)			/* PointerToRawData */
+	LONG $0			/* PointerToRelocations UNUSED */
+	LONG $0			/* PointerToLinenumbers UNUSED */
+	WORD $0			/* NumberOfRelocations UNUSED */
+	WORD $0			/* NumberOfLinenumbers UNUSED */
+	LONG $0x86000020	/* Characteristics (code, execute, read, write) */
+
+	/* padding to get start(SB) at IMAGEBASE+0x200 */
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0;
+	LONG $0; LONG $0; LONG $0; LONG $0
+
+MODE $64
+
+TEXT start(SB), 1, $-4
+	/* spill arguments */
+	MOVQ CX, 8(SP)
+	MOVQ DX, 16(SP)
+
+	CALL reloc(SP)
+
+TEXT reloc(SB), 1, $-4
+	MOVQ 0(SP), SI
+	SUBQ $reloc-IMAGEBASE(SB), SI
+	MOVQ $IMAGEBASE, DI
+	MOVQ $edata-IMAGEBASE(SB), CX
+	CLD
+	REP; MOVSB
+
+	MOVQ 16(SP), BP
+	MOVQ $efimain(SB), DI
+	MOVQ DI, (SP)
+	RET
+
+TEXT eficall(SB), 1, $-4
+	MOVQ SP, SI
+	MOVQ SP, DI
+	MOVL $(8*16), CX
+	SUBQ CX, DI
+	ANDQ $~15ULL, DI
+	LEAQ 16(DI), SP
+	CLD
+	REP; MOVSB
+	SUBQ $(8*16), SI
+
+	MOVQ 0(SP), CX
+	MOVQ 8(SP), DX
+	MOVQ 16(SP), R8
+	MOVQ 24(SP), R9
+	CALL BP
+
+	MOVQ SI, SP
+	RET
+
+#include "mem.h"
+
+TEXT jump(SB), 1, $-4
+	CLI
+
+	/* load zero length idt */
+	MOVL	$_idtptr64p<>(SB), AX
+	MOVL	(AX), IDTR
+
+	/* load temporary gdt */
+	MOVL	$_gdtptr64p<>(SB), AX
+	MOVL	(AX), GDTR
+
+	/* load CS with 32bit code segment */
+	PUSHQ	$SELECTOR(3, SELGDT, 0)
+	PUSHQ	$_warp32<>(SB)
+	RETFQ
+
+MODE $32
+
+TEXT	_warp32<>(SB), 1, $-4
+
+	/* load 32bit data segments */
+	MOVL	$SELECTOR(2, SELGDT, 0), AX
+	MOVW	AX, DS
+	MOVW	AX, ES
+	MOVW	AX, FS
+	MOVW	AX, GS
+	MOVW	AX, SS
+
+	/* turn off paging */
+	MOVL	CR0, AX
+	ANDL	$0x7fffffff, AX		/* ~(PG) */
+	MOVL	AX, CR0
+
+	MOVL	$0, AX
+	MOVL	AX, CR3
+
+	/* disable long mode */
+	MOVL	$0xc0000080, CX		/* Extended Feature Enable */
+	RDMSR
+	ANDL	$0xfffffeff, AX		/* Long Mode Disable */
+	WRMSR
+
+	/* diable pae */
+	MOVL	CR4, AX
+	ANDL	$0xffffff5f, AX		/* ~(PAE|PGE) */
+	MOVL	AX, CR4
+
+	JMP	*BP
+
+TEXT _gdt<>(SB), 1, $-4
+	/* null descriptor */
+	LONG	$0
+	LONG	$0
+
+	/* (KESEG) 64 bit long mode exec segment */
+	LONG	$(0xFFFF)
+	LONG	$(SEGL|SEGG|SEGP|(0xF<<16)|SEGPL(0)|SEGEXEC|SEGR)
+
+	/* 32 bit data segment descriptor for 4 gigabytes (PL 0) */
+	LONG	$(0xFFFF)
+	LONG	$(SEGG|SEGB|(0xF<<16)|SEGP|SEGPL(0)|SEGDATA|SEGW)
+
+	/* 32 bit exec segment descriptor for 4 gigabytes (PL 0) */
+	LONG	$(0xFFFF)
+	LONG	$(SEGG|SEGD|(0xF<<16)|SEGP|SEGPL(0)|SEGEXEC|SEGR)
+
+TEXT _gdtptr64p<>(SB), 1, $-4
+	WORD	$(4*8-1)
+	QUAD	$_gdt<>(SB)
+
+TEXT _idtptr64p<>(SB), 1, $-4
+	WORD	$0
+	QUAD	$0
--- /dev/null
+++ b/os/boot/efi/pxe.c
@@ -1,0 +1,487 @@
+#include <u.h>
+#include "fns.h"
+#include "efi.h"
+
+typedef UINT16	EFI_PXE_BASE_CODE_UDP_PORT;
+
+typedef struct {
+	UINT8		Addr[4];
+} EFI_IPv4_ADDRESS;
+
+typedef struct {
+	UINT8		Addr[16];
+} EFI_IPv6_ADDRESS;
+
+typedef union {
+	UINT32			Addr[4];
+	EFI_IPv4_ADDRESS	v4;
+	EFI_IPv6_ADDRESS	v6;
+} EFI_IP_ADDRESS;
+
+typedef struct {
+	UINT8		Addr[32];
+} EFI_MAC_ADDRESS;
+
+typedef struct {
+	UINT8		BootpOpcode;
+	UINT8		BootpHwType;
+	UINT8		BootpHwAddrLen;
+	UINT8		BootpGateHops;
+	UINT32		BootpIdent;
+	UINT16		BootpSeconds;
+	UINT16		BootpFlags;
+	UINT8		BootpCiAddr[4];
+	UINT8		BootpYiAddr[4];
+	UINT8		BootpSiAddr[4];
+	UINT8		BootpGiAddr[4];
+	UINT8		BootpHwAddr[16];
+	UINT8		BootpSrvName[64];
+	UINT8		BootpBootFile[128];
+	UINT32		DhcpMagik;
+	UINT8		DhcpOptions[56];		
+} EFI_PXE_BASE_CODE_DHCPV4_PACKET;
+
+typedef struct {
+	BOOLEAN		Started;
+	BOOLEAN		Ipv6Available;
+	BOOLEAN		Ipv6Supported;
+	BOOLEAN		UsingIpv6;
+	BOOLEAN		BisSupported;
+	BOOLEAN		BisDetected;
+	BOOLEAN		AutoArp;
+	BOOLEAN		SendGUID;
+	BOOLEAN		DhcpDiscoverValid;
+	BOOLEAN		DhcpAckReceived;
+	BOOLEAN		ProxyOfferReceived;
+	BOOLEAN		PxeDiscoverValid;
+	BOOLEAN		PxeReplyReceived;
+	BOOLEAN		PxeBisReplyReceived;
+	BOOLEAN		IcmpErrorReceived;
+	BOOLEAN		TftpErrorReceived;
+	BOOLEAN		MakeCallbacks;
+
+	UINT8		TTL;
+	UINT8		ToS;
+
+	UINT8		Reserved;
+
+	UINT8		StationIp[16];
+	UINT8		SubnetMask[16];
+
+	UINT8		DhcpDiscover[1472];
+	UINT8		DhcpAck[1472];
+	UINT8		ProxyOffer[1472];
+	UINT8		PxeDiscover[1472];
+	UINT8		PxeReply[1472];
+	UINT8		PxeBisReply[1472];
+
+} EFI_PXE_BASE_CODE_MODE;
+
+typedef struct {
+	UINT64		Revision;
+	void		*Start;
+	void		*Stop;
+	void		*Dhcp;
+	void		*Discover;
+	void		*Mtftp;
+	void		*UdpWrite;
+	void		*UdpRead;
+	void		*SetIpFilter;
+	void		*Arp;
+	void		*SetParameters;
+	void		*SetStationIp;
+	void		*SetPackets;
+	EFI_PXE_BASE_CODE_MODE	*Mode;
+} EFI_PXE_BASE_CODE_PROTOCOL;
+
+
+enum {
+	Tftp_READ	= 1,
+	Tftp_WRITE	= 2,
+	Tftp_DATA	= 3,
+	Tftp_ACK	= 4,
+	Tftp_ERROR	= 5,
+	Tftp_OACK	= 6,
+
+	TftpPort	= 69,
+
+	Segsize		= 512,
+};
+
+static
+EFI_GUID EFI_PXE_BASE_CODE_PROTOCOL_GUID = {
+	0x03C4E603, 0xAC28, 0x11D3,
+	0x9A, 0x2D, 0x00, 0x90,
+	0x27, 0x3F, 0xC1, 0x4D,
+};
+
+static
+EFI_PXE_BASE_CODE_PROTOCOL *pxe;
+
+static uchar mymac[6];
+static uchar myip[16];
+static uchar serverip[16];
+
+typedef struct Tftp Tftp;
+struct Tftp
+{
+	EFI_IP_ADDRESS sip;
+	EFI_IP_ADDRESS dip;
+
+	EFI_PXE_BASE_CODE_UDP_PORT sport;
+	EFI_PXE_BASE_CODE_UDP_PORT dport;
+
+	char *rp;
+	char *ep;
+
+	int seq;
+	int eof;
+	
+	char pkt[2+2+Segsize];
+	char nul;
+};
+
+static void
+puts(void *x, ushort v)
+{
+	uchar *p = x;
+
+	p[1] = (v>>8) & 0xFF;
+	p[0] = v & 0xFF;
+}
+
+static ushort
+gets(void *x)
+{
+	uchar *p = x;
+
+	return p[1]<<8 | p[0];
+}
+
+static void
+hnputs(void *x, ushort v)
+{
+	uchar *p = x;
+
+	p[0] = (v>>8) & 0xFF;
+	p[1] = v & 0xFF;
+}
+
+static ushort
+nhgets(void *x)
+{
+	uchar *p = x;
+
+	return p[0]<<8 | p[1];
+}
+
+enum {
+	ANY_SRC_IP	= 0x0001,
+	ANY_SRC_PORT	= 0x0002,
+	ANY_DEST_IP	= 0x0004,
+	ANY_DEST_PORT	= 0x0008,
+	USE_FILTER	= 0x0010,
+	MAY_FRAGMENT	= 0x0020,
+};
+
+static int
+udpread(EFI_IP_ADDRESS *sip, EFI_IP_ADDRESS *dip, 
+	EFI_PXE_BASE_CODE_UDP_PORT *sport, 
+	EFI_PXE_BASE_CODE_UDP_PORT dport, 
+	int *len, void *data)
+{
+	UINTN size;
+
+	size = *len;
+	if(eficall(pxe->UdpRead, pxe, (UINTN)ANY_SRC_PORT, dip, &dport, sip, sport, nil, nil, &size, data))
+		return -1;
+
+	*len = size;
+	return 0;
+}
+
+static int
+udpwrite(EFI_IP_ADDRESS *dip,
+	EFI_PXE_BASE_CODE_UDP_PORT sport, 
+	EFI_PXE_BASE_CODE_UDP_PORT dport,
+	int len, void *data)
+{
+	UINTN size;
+
+	size = len;
+	if(eficall(pxe->UdpWrite, pxe, (UINTN)MAY_FRAGMENT, dip, &dport, nil, nil, &sport, nil, nil, &size, data))
+		return -1;
+
+	return 0;
+}
+
+static int
+pxeread(void *f, void *data, int len)
+{
+	Tftp *t = f;
+	int seq, n;
+
+	while(!t->eof && t->rp >= t->ep){
+		for(;;){
+			n = sizeof(t->pkt);
+			if(udpread(&t->dip, &t->sip, &t->dport, t->sport, &n, t->pkt))
+				continue;
+			if(n >= 4)
+				break;
+		}
+		switch(nhgets(t->pkt)){
+		case Tftp_DATA:
+			seq = nhgets(t->pkt+2);
+			if(seq > t->seq){
+				putc('?');
+				continue;
+			}
+			hnputs(t->pkt, Tftp_ACK);
+			while(udpwrite(&t->dip, t->sport, t->dport, 4, t->pkt))
+				putc('!');
+			if(seq < t->seq){
+				putc('@');
+				continue;
+			}
+			t->seq = seq+1;
+			n -= 4;
+			t->rp = t->pkt + 4;
+			t->ep = t->rp + n;
+			t->eof = n < Segsize;
+			break;
+		case Tftp_ERROR:
+			print(t->pkt+4);
+			print("\n");
+		default:
+			t->eof = 1;
+			return -1;
+		}
+		break;
+	}
+	n = t->ep - t->rp;
+	if(len > n)
+		len = n;
+	memmove(data, t->rp, len);
+	t->rp += len;
+	return len;
+}
+
+static void
+pxeclose(void *f)
+{
+	Tftp *t = f;
+	t->eof = 1;
+}
+
+
+static int
+tftpopen(Tftp *t, char *path)
+{
+	static EFI_PXE_BASE_CODE_UDP_PORT xport = 6666;
+	int r, n;
+	char *p;
+
+	t->sport = xport++;
+	t->dport = 0;
+	t->rp = t->ep = 0;
+	t->seq = 1;
+	t->eof = 0;
+	t->nul = 0;
+	p = t->pkt;
+	hnputs(p, Tftp_READ); p += 2;
+	n = strlen(path)+1;
+	memmove(p, path, n); p += n;
+	memmove(p, "octet", 6); p += 6;
+	n = p - t->pkt;
+	for(;;){
+		if(r = udpwrite(&t->dip, t->sport, TftpPort, n, t->pkt))
+			break;
+		if(r = pxeread(t, 0, 0))
+			break;
+		return 0;
+	}
+	pxeclose(t);
+	return r;
+}
+
+static void*
+pxeopen(char *name)
+{
+	static uchar buf[sizeof(Tftp)+8];
+	Tftp *t = (Tftp*)((uintptr)(buf+7)&~7);
+
+	memset(t, 0, sizeof(Tftp));
+	memmove(&t->sip, myip, sizeof(myip));
+	memmove(&t->dip, serverip, sizeof(serverip));
+	if(tftpopen(t, name))
+		return nil;
+	return t;
+}
+
+static int
+parseipv6(uchar to[16], char *from)
+{
+	int i, dig, elipsis;
+	char *p;
+
+	elipsis = 0;
+	memset(to, 0, 16);
+	for(i = 0; i < 16; i += 2){
+		dig = 0;
+		for(p = from;; p++){
+			if(*p >= '0' && *p <= '9')
+				dig = (dig << 4) | (*p - '0');
+			else if(*p >= 'a' && *p <= 'f')
+				dig = (dig << 4) | (*p - 'a'+10);
+			else if(*p >= 'A' && *p <= 'F')
+				dig = (dig << 4) | (*p - 'A'+10);
+			else
+				break;
+			if(dig > 0xFFFF)
+				return -1;
+		}
+		to[i]   = dig>>8;
+		to[i+1] = dig;
+		if(*p == ':'){
+			if(*++p == ':'){	/* :: is elided zero short(s) */
+				if (elipsis)
+					return -1;	/* second :: */
+				elipsis = i+2;
+				p++;
+			}
+		} else if (p == from)
+			break;
+		from = p;		
+	}
+	if(i < 16){
+		memmove(&to[elipsis+16-i], &to[elipsis], i-elipsis);
+		memset(&to[elipsis], 0, 16-i);
+	}
+	return 0;
+}
+
+static void
+parsedhcp(EFI_PXE_BASE_CODE_DHCPV4_PACKET *dhcp)
+{
+	uchar *p, *e;
+	char *x;
+	int opt;
+	int len;
+	uint type;
+
+	memset(mymac, 0, sizeof(mymac));
+	memset(serverip, 0, sizeof(serverip));
+
+	/* DHCPv4 */
+	if(pxe->Mode->UsingIpv6 == 0){
+		memmove(mymac, dhcp->BootpHwAddr, 6);
+		memmove(serverip, dhcp->BootpSiAddr, 4);
+		return;
+	}
+
+	/* DHCPv6 */
+
+	/*
+	 * some UEFI implementations use random UUID based DUID instead of
+	 * ethernet address, but use ethernet derived link-local addresses.
+	 * so extract the MAC from our IPv6 address as a fallback.
+	 */
+	p = pxe->Mode->StationIp;
+	mymac[0] = p[8] ^ 2;
+	mymac[1] = p[9];
+	mymac[2] = p[10];
+	mymac[3] = p[13];
+	mymac[4] = p[14];
+	mymac[5] = p[15];
+
+	e = (uchar*)dhcp + sizeof(*dhcp);
+	p = (uchar*)dhcp + 4;
+	while(p+4 <= e){
+		opt = p[0]<<8 | p[1];
+		len = p[2]<<8 | p[3];
+		p += 4;
+		if(p + len > e)
+			break;
+		switch(opt){
+		case 1:	/* Client DUID */
+			if(len < 4+6)
+				break;
+			type = p[0]<<24 | p[1]<<16 | p[2]<<8 | p[3];
+			switch(type){
+			case 0x00010001:
+			case 0x00030001:
+				memmove(mymac, p+len-6, 6);
+				break;
+			}
+			break;
+		case 59: /* Boot File URL */
+			for(x = (char*)p; x < (char*)p+len; x++){
+				if(*x == '['){
+					parseipv6(serverip, x+1);
+					break;
+				}
+			}
+			break;
+		}
+		p += len;
+	}
+}
+
+int
+pxeinit(void **pf)
+{
+	EFI_PXE_BASE_CODE_DHCPV4_PACKET	*dhcp;
+	EFI_PXE_BASE_CODE_MODE *mode;
+	EFI_HANDLE *Handles;
+	UINTN Count;
+	int i;
+
+	pxe = nil;
+	Count = 0;
+	Handles = nil;
+	if(eficall(ST->BootServices->LocateHandleBuffer,
+		ByProtocol, &EFI_PXE_BASE_CODE_PROTOCOL_GUID, nil, &Count, &Handles))
+		return -1;
+
+	for(i=0; i<Count; i++){
+		pxe = nil;
+		if(eficall(ST->BootServices->HandleProtocol,
+			Handles[i], &EFI_PXE_BASE_CODE_PROTOCOL_GUID, &pxe))
+			continue;
+		mode = pxe->Mode;
+		if(mode == nil || mode->Started == 0)
+			continue;
+		if(mode->DhcpAckReceived){
+			dhcp = (EFI_PXE_BASE_CODE_DHCPV4_PACKET*)mode->DhcpAck;
+			goto Found;
+		}
+		if(mode->PxeReplyReceived){
+			dhcp = (EFI_PXE_BASE_CODE_DHCPV4_PACKET*)mode->PxeReply;
+			goto Found;
+		}
+	}
+	return -1;
+
+Found:
+	parsedhcp(dhcp);
+	memmove(myip, mode->StationIp, 16);
+
+	open = pxeopen;
+	read = pxeread;
+	close = pxeclose;
+
+	if(pf != nil){
+		char ini[24];
+
+		memmove(ini, "/cfg/pxe/", 9);
+		for(i=0; i<6; i++){
+			ini[9+i*2+0] = hex[mymac[i] >> 4];
+			ini[9+i*2+1] = hex[mymac[i] & 0xF];
+		}
+		ini[9+12] = '\0';
+		if((*pf = pxeopen(ini)) == nil)
+			*pf = pxeopen("/cfg/pxe/default");
+	}
+
+	return 0;
+}
--- /dev/null
+++ b/os/boot/efi/sub.c
@@ -1,0 +1,373 @@
+#include <u.h>
+#include <a.out.h>
+#include "fns.h"
+#include "mem.h"
+
+char hex[] = "0123456789abcdef";
+
+void
+print(char *s)
+{
+	while(*s != 0){
+		if(*s == '\n')
+			putc('\r');
+		putc(*s++);
+	}
+}
+
+int
+readn(void *f, void *data, int len)
+{
+	uchar *p, *e;
+
+	p = data;
+	e = p + len;
+	while(p < e){
+		if((len = read(f, p, e - p)) <= 0)
+			break;
+		p += len;
+	}
+
+	return p - (uchar*)data;
+}
+
+void
+memmove(void *dst, void *src, int n)
+{
+	uchar *d = dst;
+	uchar *s = src;
+
+	if(d < s){
+		while(n-- > 0)
+			*d++ = *s++;
+	} else if(d > s){
+		s += n;
+		d += n;
+		while(n-- > 0)
+			*--d = *--s;
+	}
+}
+
+int
+memcmp(void *src, void *dst, int n)
+{
+	uchar *d = dst;
+	uchar *s = src;
+	int r = 0;
+
+	while(n-- > 0){
+		r = *d++ - *s++;
+		if(r != 0)
+			break;
+	}
+
+	return r;
+}
+
+int
+strlen(char *s)
+{
+	char *p = s;
+
+	while(*p != '\0')
+		p++;
+
+	return p - s;
+}
+
+char*
+strchr(char *s, int c)
+{
+	for(; *s != 0; s++)
+		if(*s == c)
+			return s;
+
+	return nil;
+}
+
+void
+memset(void *dst, int v, int n)
+{
+	uchar *d = dst;
+
+	while(n > 0){
+		*d++ = v;
+		n--;
+	}
+}
+
+static int
+readline(void *f, char *buf)
+{
+	static char white[] = "\t ";
+	char *p;
+
+	p = buf;
+	do{
+		if(f == nil)
+			putc('>');
+		for(;;){
+			if(f == nil){
+				while((*p = getc()) == 0)
+					;
+				if(p == buf && (*p == '\b' || strchr(white, *p) != nil))
+					continue;
+				putc(*p);
+				if(*p == '\r')
+					putc('\n');
+				else if(*p == '\b'){
+					p--;
+					putc(' ');
+					putc('\b');
+					continue;
+				}
+			}else if(read(f, p, 1) <= 0)
+				return 0;
+			if(strchr("\r\n", *p) != nil)
+				break;
+			if(p == buf && strchr(white, *p) != nil)
+				continue;	/* whitespace on start of line */
+			p++;
+		}
+		while(p > buf && strchr(white, p[-1]))
+			p--;
+	}while(p == buf);
+	*p = 0;
+
+	return p - buf;
+}
+
+static int
+timeout(int ms)
+{
+	while(ms > 0){
+		if(getc() != 0)
+			return 1;
+		usleep(100000);
+		ms -= 100;
+	}
+	return 0;
+}
+
+#define BOOTLINE	((char*)CONFADDR)
+#define BOOTLINELEN	64
+#define BOOTARGS	((char*)(CONFADDR+BOOTLINELEN))
+#define	BOOTARGSLEN	(4096-0x200-BOOTLINELEN)
+
+char *confend;
+
+static char*
+getconf(char *s, char *buf)
+{
+	char *p, *e;
+	int n;
+
+	n = strlen(s);
+	for(p = BOOTARGS; p < confend; p = e+1){
+		for(e = p+1; e < confend; e++)
+			if(*e == '\n')
+				break;
+		if(memcmp(p, s, n) == 0){
+			p += n;
+			n = e - p;
+			buf[n] = 0;
+			memmove(buf, p, n);
+			return buf;
+		}
+	}
+	return nil;
+}
+
+static int
+delconf(char *s)
+{
+	char *p, *e;
+
+	for(p = BOOTARGS; p < confend; p = e){
+		for(e = p+1; e < confend; e++){
+			if(*e == '\n'){
+				e++;
+				break;
+			}
+		}
+		if(memcmp(p, s, strlen(s)) == 0){
+			memmove(p, e, confend - e);
+			confend -= e - p;
+			*confend = 0;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+char*
+configure(void *f, char *path)
+{
+	char *line, *kern, *s, *p;
+	int inblock, nowait, n;
+	static int once = 1;
+
+	if(once){
+		once = 0;
+Clear:
+		memset(BOOTLINE, 0, BOOTLINELEN);
+
+		confend = BOOTARGS;
+		memset(confend, 0, BOOTARGSLEN);
+		eficonfig(&confend);
+	}
+	nowait = 1;
+	inblock = 0;
+Loop:
+	while(readline(f, line = confend+1) > 0){
+		if(*line == 0 || strchr("#;=", *line) != nil)
+			continue;
+		if(*line == '['){
+			inblock = memcmp("[common]", line, 8) != 0;
+			continue;
+		}
+		if(memcmp("boot", line, 5) == 0){
+			nowait=1;
+			break;
+		}
+		if(memcmp("wait", line, 5) == 0){
+			nowait=0;
+			continue;
+		}
+		if(memcmp("show", line, 5) == 0){
+			print(BOOTARGS);
+			continue;
+		}
+		if(memcmp("clear", line, 5) == 0){
+			if(line[5] == '\0'){
+				print("ok\n");
+				goto Clear;
+			} else if(line[5] == ' ' && delconf(line+6))
+				print("ok\n");
+			continue;
+		}
+		if(inblock != 0 || (p = strchr(line, '=')) == nil)
+			continue;
+		*p++ = 0;
+		delconf(line);
+		s = confend;
+		memmove(confend, line, n = strlen(line)); confend += n;
+		*confend++ = '=';
+		memmove(confend, p, n = strlen(p)); confend += n;
+		*confend++ = '\n';
+		*confend = 0;
+		print(s);
+	}
+	kern = getconf("bootfile=", path);
+
+	if(f != nil){
+		close(f);
+		f = nil;
+
+		if(kern != nil && (nowait==0 || timeout(1000)))
+			goto Loop;
+	}
+
+	if(kern == nil){
+		print("no bootfile\n");
+		goto Loop;
+	}
+	while((p = strchr(kern, '!')) != nil)
+		kern = p+1;
+
+	return kern;
+}
+
+static char*
+numfmt(char *s, ulong b, ulong i, ulong a)
+{
+	char *r;
+
+	if(i == 0){
+		ulong v = a;
+		while(v != 0){
+			v /= b;
+			i++;
+		}
+		if(i == 0)
+			i = 1;
+	}
+
+	s += i;
+	r = s;
+	while(i > 0){
+		*--s = hex[a % b];
+		a /= b;
+		i--;
+	}
+	return r;
+}
+
+char*
+hexfmt(char *s, int i, uvlong a)
+{
+	if(i > 8 || i == 0 && (a>>32) != 0){
+		s = numfmt(s, 16, i ? i-8 : 0, a>>32);
+		i = 8;
+	}
+	return numfmt(s, 16, i, a);
+}
+
+char*
+decfmt(char *s, int i, ulong a)
+{
+	return numfmt(s, 10, i, a);
+}
+
+static ulong
+beswal(ulong l)
+{
+	uchar *p = (uchar*)&l;
+	return (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3];
+}
+
+char*
+bootkern(void *f)
+{
+	uchar *e, *d, *t;
+	ulong n;
+	Exec ex;
+
+	if(readn(f, &ex, sizeof(ex)) != sizeof(ex))
+		return "bad header";
+
+	e = (uchar*)(beswal(ex.entry) & ~0xF0000000UL);
+	switch(beswal(ex.magic)){
+	case S_MAGIC:
+		if(readn(f, e, 8) != 8)
+			goto Error;
+	case I_MAGIC:
+		break;
+	default:
+		return "bad magic";
+	}
+
+	t = e;
+	n = beswal(ex.text);
+	if(readn(f, t, n) != n)
+		goto Error;
+	t += n;
+	d = (uchar*)PGROUND((uintptr)t);
+	memset(t, 0, d - t);
+	n = beswal(ex.data);
+	if(readn(f, d, n) != n)
+		goto Error;
+	d += n;
+	t = (uchar*)PGROUND((uintptr)d);
+	t += PGROUND(beswal(ex.bss));
+	memset(d, 0, t - d);
+
+	close(f);
+	print("boot\n");
+	unload();
+
+	jump(e);
+
+Error:		
+	return "i/o error";
+}
--- /dev/null
+++ b/os/boot/mkfile
@@ -1,0 +1,23 @@
+ARCH=\
+	bitsy\
+	efi\
+	pc\
+	zynq\
+
+all:V:
+	for(i in $ARCH)@{
+		cd $i
+		mk
+	}
+
+installall install:V:
+	for(i in $ARCH) @{
+		cd $i
+		mk install
+	}
+
+clean:V:
+	for(i in $ARCH) @{
+		cd $i
+		mk clean
+	}
--- /dev/null
+++ b/os/boot/pc/a20.s
@@ -1,0 +1,84 @@
+#include "x16.h"
+
+#undef ORB
+
+TEXT a20test(SB), $0
+	LONG $1234567
+
+TEXT a20check(SB), $0
+	MOVL $10000, CX
+_loop:
+	LEAL a20test(SB), AX
+	MOVL (AX), BX
+	ADDL $12345, BX
+	MOVL BX, (AX)
+	ORL $(1<<20), AX
+	MOVL (AX), AX
+	CMPL AX, BX
+	JNZ _done
+	LOOP _loop
+	RET
+_done:
+	/* return directly to caller of a20() */
+	ADDL $4, SP
+	XORL AX, AX
+	RET
+
+TEXT a20(SB), $0
+	CALL a20check(SB)
+
+	/* try bios */
+	CALL rmode16(SB)
+	STI
+	LWI(0x2401, rAX)
+	BIOSCALL(0x15)
+	CALL16(pmode32(SB))
+
+	CALL a20check(SB)
+
+	/* try fast a20 */
+	MOVL $0x92, DX
+	INB
+	TESTB $2, AL
+	JNZ _no92
+	ORB $2, AL
+	ANDB $0xfe, AL
+	OUTB
+_no92:
+	CALL a20check(SB)
+
+	/* try keyboard */
+	CALL kbdempty(SB)
+	MOVL $0x64, DX
+	MOVB $0xd1, AL	/* command write */
+	OUTB
+	CALL kbdempty(SB)
+	MOVL $0x60, DX
+	MOVB $0xdf, AL	/* a20 on */
+	OUTB
+	CALL kbdempty(SB)
+	MOVL $0x64, DX
+	MOVB $0xff, AL	/* magic */
+	OUTB
+	CALL kbdempty(SB)
+
+	CALL a20check(SB)
+
+	/* fail */
+	XORL AX, AX
+	DECL AX
+	RET
+
+TEXT kbdempty(SB), $0
+_kbdwait:
+	MOVL $0x64, DX
+	INB
+	TESTB $1, AL
+	JZ _kbdempty
+	MOVL $0x60, DX
+	INB
+	JMP _kbdwait
+_kbdempty:
+	TESTB $2, AL
+	JNZ _kbdwait
+	RET
--- /dev/null
+++ b/os/boot/pc/apm.s
@@ -1,0 +1,51 @@
+#include "x16.h"
+#include "mem.h"
+
+TEXT apm(SB), $0
+	MOVL id+4(SP), BX
+	CALL rmode16(SB)
+
+	PUSHR(rBX)
+	LWI(0x5300, rAX)
+	BIOSCALL(0x15)
+	POPR(rBX)
+	JC noapm
+
+	PUSHR(rBX)
+	LWI(0x5304, rAX)
+	BIOSCALL(0x15)
+	POPR(rBX)
+	CLC
+
+	/* connect */
+	LWI(0x5303, rAX)
+	BIOSCALL(0x15)
+	JC noapm
+
+	OPSIZE; PUSHR(rSI)
+	OPSIZE; PUSHR(rBX)
+	PUSHR(rDI)
+	PUSHR(rDX)
+	PUSHR(rCX)
+	PUSHR(rAX)
+
+	LWI(CONFADDR, rDI)
+
+	/*
+	 * write APM data.  first four bytes are APM\0.
+	 */
+	LWI(0x5041, rAX)
+	STOSW
+
+	LWI(0x004d, rAX)
+	STOSW
+
+	LWI(8, rCX)
+apmmove:
+	POPR(rAX)
+	STOSW
+	LOOP apmmove
+
+noapm:
+	CALL16(pmode32(SB))
+	RET
--- /dev/null
+++ b/os/boot/pc/e820.s
@@ -1,0 +1,32 @@
+#include "x16.h"
+#include "mem.h"
+
+TEXT e820(SB), $0
+	MOVL bx+4(SP), BX
+	MOVL p+8(SP), DI
+	MOVL $0xE820, AX
+	MOVL $0x534D4150, DX
+	CALL rmode16(SB)
+	LWI(24, rCX)
+	BIOSCALL(0x15)
+	JC _bad
+	CALL16(pmode32(SB))
+	CMPB CL, $24
+	JZ _ret
+	MOVL $1, AX
+	MOVL p+8(SP), DI
+	MOVL AX, 20(DI)
+_ret:
+	MOVL BX, AX
+	RET
+_bad:
+	CALL16(pmode32(SB))
+	XORL AX, AX
+	MOVL p+8(SP), DI
+	MOVL AX, 0(DI)
+	MOVL AX, 4(DI)
+	MOVL AX, 8(DI)
+	MOVL AX, 12(DI)
+	MOVL AX, 16(DI)
+	MOVL AX, 20(DI)
+	RET
--- /dev/null
+++ b/os/boot/pc/fat.c
@@ -1,0 +1,405 @@
+#include <u.h>
+#include "fns.h"
+
+#define GETSHORT(p) (*(ushort *)(p))
+#define GETLONG(p) (*(uint *)(p))
+
+enum {
+	Sectsz = 0x200,
+	Dirsz = 0x20,
+	Maxpath = 64,
+	Fat12 = 1,
+	Fat16 = 2,
+	Fat32 = 4,
+};
+
+typedef struct File File;
+typedef struct Dir Dir;
+typedef struct Pbs Pbs;
+typedef struct Pbs32 Pbs32;
+typedef struct Fat Fat;
+
+struct Fat
+{
+	ulong ver;
+	int drive;
+	ulong clustsize;
+	ulong eofmark;
+	ulong partlba;
+	ulong fatlba;
+	ulong dirstart; /* LBA for FAT16, cluster for FAT32 */
+	ulong dirents;
+	ulong datalba;
+};
+
+struct File
+{
+	Fat *fat;
+	ulong lba;
+	ulong clust;
+	ulong lbaoff;
+	ulong len;
+	uchar *rp;
+	uchar *ep;
+	uchar buf[Sectsz];
+};
+
+struct Dir
+{
+	char name[11];
+	uchar attr;
+	uchar reserved;
+	uchar ctime;
+	uchar ctime[2];
+	uchar cdate[2];
+	uchar adate[2];
+	uchar starthi[2];
+	uchar mtime[2];
+	uchar mdate[2];
+	uchar startlo[2];
+	uchar len[4];
+};
+
+struct Pbs
+{
+	uchar magic[3];
+	uchar version[8];
+	uchar sectsize[2];
+	uchar clustsize;
+	uchar nreserv[2];
+	uchar nfats;
+	uchar rootsize[2];
+	uchar volsize[2];
+	uchar mediadesc;
+	uchar fatsize[2];
+	uchar trksize[2];
+	uchar nheads[2];
+	uchar nhidden[4];
+	uchar bigvolsize[4];
+	uchar driveno;
+	uchar reserved0;
+	uchar bootsig;
+	uchar volid[4];
+	uchar label[11];
+	uchar type[8];
+};
+
+struct Pbs32
+{
+	uchar common[36];
+	uchar fatsize[4];
+	uchar flags[2];
+	uchar ver[2];
+	uchar rootclust[4];
+	uchar fsinfo[2];
+	uchar bootbak[2];
+	uchar reserved0[12];
+	uchar driveno;
+	uchar reserved1;
+	uchar bootsig;
+	uchar volid[4];
+	uchar label[11];
+	uchar type[8];
+};
+
+int readsect(ulong drive, ulong lba, void *buf);
+
+void
+unload(void)
+{
+}
+
+static ulong
+readnext(File *fp, ulong clust)
+{
+	Fat *fat = fp->fat;
+	uchar tmp[2], *p;
+	ulong idx, lba;
+
+	if(fat->ver == Fat12)
+		idx = (3*clust)/2;
+	else
+		idx = clust*fat->ver;
+	lba = fat->fatlba + (idx / Sectsz);
+	if(readsect(fat->drive, lba, fp->buf))
+		memset(fp->buf, 0xff, Sectsz);
+	p = &fp->buf[idx % Sectsz];
+	if(p == &fp->buf[Sectsz-1]){
+		tmp[0] = *p;
+		if(readsect(fat->drive, ++lba, fp->buf))
+			memset(fp->buf, 0xff, Sectsz);
+		tmp[1] = fp->buf[0];
+		p = tmp;
+	}
+	if(fat->ver == Fat32)
+		return GETLONG(p) & 0xfffffff;
+	idx = GETSHORT(p);
+	if(fat->ver == Fat12){
+		if(clust & 1)
+			idx >>= 4;
+		idx &= 0xfff;
+	}
+	return idx;
+}
+
+int
+read(void *f, void *data, int len)
+{
+	File *fp = f;
+	Fat *fat = fp->fat;
+
+	if(fp->len > 0 && fp->rp >= fp->ep){
+		if(fp->clust != ~0U){
+			if(fp->lbaoff % fat->clustsize == 0){
+				if(fp->clust < 2 || fp->clust >= fat->eofmark)
+					return -1;
+				fp->lbaoff = (fp->clust - 2) * fat->clustsize;
+				fp->clust = readnext(fp, fp->clust);
+				fp->lba = fp->lbaoff + fat->datalba;
+			}
+			fp->lbaoff++;
+		}
+		if(readsect(fat->drive, fp->lba++, fp->rp = fp->buf))
+			return -1;
+	}
+	if(fp->len < len)
+		len = fp->len;
+	if(len > (fp->ep - fp->rp))
+		len = fp->ep - fp->rp;
+	memmove(data, fp->rp, len);
+	fp->rp += len;
+	fp->len -= len;
+	return len;
+}
+
+void
+close(void *)
+{
+}
+
+static int
+dirname(Dir *d, char buf[Maxpath])
+{
+	char c, *x;
+
+	if(d->attr == 0x0F || *d->name <= 0)
+		return -1;
+	memmove(buf, d->name, 8);
+	x = buf+8;
+	while(x > buf && x[-1] == ' ')
+		x--;
+	if(d->name[8] != ' '){
+		*x++ = '.';
+		memmove(x, d->name+8, 3);
+		x += 3;
+	}
+	while(x > buf && x[-1] == ' ')
+		x--;
+	*x = 0;
+	x = buf;
+	while(c = *x){
+		if(c >= 'A' && c <= 'Z'){
+			c -= 'A';
+			c += 'a';
+		}
+		*x++ = c;
+	}
+	return x - buf;
+}
+
+static ulong
+dirclust(Dir *d)
+{
+	return *((ushort*)d->starthi)<<16 | *((ushort*)d->startlo);
+}
+
+static void
+fileinit(File *fp, Fat *fat, ulong lba)
+{
+	fp->fat = fat;
+	fp->lba = lba;
+	fp->len = 0;
+	fp->lbaoff = 0;
+	fp->clust = ~0U;
+	fp->rp = fp->ep = fp->buf + Sectsz;
+}
+
+static int
+fatwalk(File *fp, Fat *fat, char *path)
+{
+	char name[Maxpath], *end;
+	int i, j;
+	Dir d;
+
+	if(fat->ver == Fat32){
+		fileinit(fp, fat, 0);
+		fp->clust = fat->dirstart;
+		fp->len = ~0U;
+	}else{
+		fileinit(fp, fat, fat->dirstart);
+		fp->len = fat->dirents * Dirsz;
+	}
+	for(;;){
+		if(readn(fp, &d, Dirsz) != Dirsz)
+			break;
+		if((i = dirname(&d, name)) <= 0)
+			continue;
+		while(*path == '/')
+			path++;
+		if((end = strchr(path, '/')) == 0)
+			end = path + strlen(path);
+		j = end - path;
+		if(i == j && memcmp(name, path, j) == 0){
+			fileinit(fp, fat, 0);
+			fp->clust = dirclust(&d);
+			fp->len = GETLONG(d.len);
+			if(*end == 0)
+				return 0;
+			else if(d.attr & 0x10){
+				fp->len = fat->clustsize * Sectsz;
+				path = end;
+				continue;
+			}
+			break;
+		}
+	}
+	return -1;
+}
+
+static int
+conffat(Fat *fat, void *buf)
+{
+	Pbs *p = buf;
+	uint fatsize, volsize, datasize, reserved;
+	uint ver, dirsize, dirents, clusters;
+
+	if(GETSHORT(p->sectsize) != Sectsz)
+		return -1;
+	if(memcmp(p->type, "FAT", 3) && memcmp(((Pbs32*)buf)->type, "FAT", 3))
+		return -1;
+	
+	/* load values from fat */
+	ver = 0;
+	fatsize = GETSHORT(p->fatsize);
+	if(fatsize == 0){
+		fatsize = GETLONG(((Pbs32*)buf)->fatsize);
+		ver = Fat32;
+	}
+	volsize = GETSHORT(p->volsize);
+	if(volsize == 0)
+		volsize = GETLONG(p->bigvolsize);
+	reserved = GETSHORT(p->nreserv);
+	dirents = GETSHORT(p->rootsize);
+	dirsize = (dirents * Dirsz + Sectsz - 1) / Sectsz;
+	datasize = volsize - (reserved + fatsize * p->nfats + dirsize);
+	clusters = datasize / p->clustsize;
+	if(ver != Fat32)
+		if(clusters < 0xff7)
+			ver = Fat12;
+		else
+			ver = Fat16;
+	
+	/* fill FAT descriptor */
+	fat->ver = ver;
+	fat->dirents = dirents;
+	fat->clustsize = p->clustsize;
+	fat->fatlba = fat->partlba + reserved;
+	fat->dirstart  = fat->fatlba + fatsize * p->nfats;
+	if(ver == Fat32){
+		fat->datalba = fat->dirstart;
+		fat->dirstart  = GETLONG(((Pbs32*)buf)->rootclust);
+		fat->eofmark = 0xffffff7;
+	}else{
+		fat->datalba = fat->dirstart + dirsize;
+		if(ver == Fat16)
+			fat->eofmark = 0xfff7;
+		else
+			fat->eofmark = 0xff7;
+	}
+	return 0;
+}
+
+static int
+findfat(Fat *fat, int drive, ulong xbase, ulong lba)
+{
+	struct {
+		uchar status;
+		uchar bchs[3];
+		uchar typ;
+		uchar echs[3];
+		uchar lba[4];
+		uchar len[4];
+	} p[4];
+	uchar buf[Sectsz];
+	int i;
+
+	if(xbase == 0)
+		xbase = lba;
+	if(readsect(drive, lba, buf))
+		return -1;
+	if(buf[0x1fe] != 0x55 || buf[0x1ff] != 0xAA)
+		return -1;
+	if(lba == 0 && (drive & 0x80) == 0){	/* floppy */
+		fat->drive = drive;
+		fat->partlba = 0;
+		if(!conffat(fat, buf))
+			return 0;
+	}
+	memmove(p, &buf[0x1be], sizeof(p));
+	for(i=0; i<4; i++){
+		switch(p[i].typ){
+		case 0x05:
+		case 0x0f:
+		case 0x85:
+			/* extended partitions */
+			if(!findfat(fat, drive, xbase, xbase + GETLONG(p[i].lba)))
+				return 0;
+			/* no break */
+		case 0x00:
+			continue;
+		default:
+			if(p[i].status != 0x80)
+				continue;
+		case 0x39:	/* always try plan9 partition */
+			fat->drive = drive;
+			fat->partlba = lba + GETLONG(p[i].lba);
+			if(readsect(drive, fat->partlba, buf))
+				continue;
+			if(!conffat(fat, buf))
+				return 0;
+		}
+	}
+	return -1;
+}
+
+void
+start(void *sp)
+{
+	char path[Maxpath], *kern;
+	int drive;
+	File fi;
+	Fat fat;
+	void *f;
+
+	/* drive passed in DL */
+	drive = ((ushort*)sp)[5] & 0xFF;
+
+	if(findfat(&fat, drive, 0, 0)){
+		print("no fat\n");
+		halt();
+	}
+	if(fatwalk(f = &fi, &fat, "plan9.ini")){
+		print("no config\n");
+		f = 0;
+	}
+	for(;;){
+		kern = configure(f, path); f = 0;
+		if(fatwalk(&fi, &fat, kern)){
+			print("not found\n");
+			continue;
+		}
+		print(bootkern(&fi));
+		print("\n");
+	}
+}
+
--- /dev/null
+++ b/os/boot/pc/fns.h
@@ -1,0 +1,46 @@
+/* handy strings in l.s */
+extern char origin[];
+extern char uart;
+extern char hex[];
+extern char bootname[];
+
+/* l.s */
+void start(void *sp);
+void cgaputc(int c);
+int kbdgetc(void);
+void usleep(int t);
+void halt(void);
+void jump(void *pc);
+
+int read(void *f, void *data, int len);
+int readn(void *f, void *data, int len);
+void close(void *f);
+void unload(void);
+
+int getc(void);
+void putc(int c);
+
+void memset(void *p, int v, int n);
+void memmove(void *dst, void *src, int n);
+int memcmp(void *src, void *dst, int n);
+int strlen(char *s);
+char *strchr(char *s, int c);
+char *strrchr(char *s, int c);
+void print(char *s);
+
+char *configure(void *f, char *path);
+char *bootkern(void *f);
+
+/* a20.s */
+int a20(void);
+
+/* e820.s */
+ulong e820(ulong bx, void *p);
+
+/* apm.s */
+void apm(int id);
+
+/* uart.s */
+void uartinit(int p, int c);
+void uartputc(int p, int c);
+int uartgetc(int p);
--- /dev/null
+++ b/os/boot/pc/iso.c
@@ -1,0 +1,196 @@
+#include <u.h>
+#include "fns.h"
+
+enum {
+	Sectsz = 0x800,
+	Maxpath = 256,
+	Dirsz = 33,
+};
+
+typedef struct Extend Extend;
+typedef struct Dir Dir;
+
+struct Extend
+{
+	int drive;
+	ulong lba;
+	ulong len;
+	uchar *rp;
+	uchar *ep;
+	uchar buf[Sectsz];
+};
+
+struct Dir
+{
+	uchar dirlen;
+	uchar extlen;
+
+	uchar lba[8];
+	uchar len[8];
+
+	uchar date[7];
+
+	uchar flags[3];
+
+	uchar seq[4];
+
+	uchar namelen;
+};
+
+int readsect(ulong drive, ulong lba, void *buf);
+
+#ifdef FAT
+int
+readsect4(ulong drive, ulong lba, void *buf)
+{
+	int i;
+
+	lba *= Sectsz/512;
+	for(i = 0; i<Sectsz/512; i++){
+		if(readsect(drive, lba++, buf))
+			return -1;
+		buf = (uchar*)buf + 512;
+	}
+	return 0;
+}
+#define readsect readsect4
+#endif
+
+void
+unload(void)
+{
+}
+
+int
+read(void *f, void *data, int len)
+{
+	Extend *ex = f;
+
+	if(ex->len > 0 && ex->rp >= ex->ep)
+		if(readsect(ex->drive, ex->lba++, ex->rp = ex->buf))
+			return -1;
+	if(ex->len < len)
+		len = ex->len;
+	if(len > (ex->ep - ex->rp))
+		len = ex->ep - ex->rp;
+	memmove(data, ex->rp, len);
+	ex->rp += len;
+	ex->len -= len;
+	return len;
+}
+
+void
+close(void *f)
+{
+	Extend *ex = f;
+
+	ex->drive = 0;
+	ex->lba = 0;
+	ex->len = 0;
+	ex->rp = ex->ep = ex->buf + Sectsz;
+}
+
+static int
+isowalk(Extend *ex, int drive, char *path)
+{
+	char name[Maxpath], c, *end;
+	int i;
+	Dir d;
+
+	close(ex);
+	ex->drive = drive;
+
+	/* find pvd */
+	for(i=0x10; i<0x1000; i++){
+		if(readsect(drive, i, ex->buf))
+			return -1;
+		if(*ex->buf == 1)
+			break;
+	}
+	ex->lba = *((ulong*)(ex->buf + 156 + 2));
+	ex->len = *((ulong*)(ex->buf + 156 + 10));
+
+	for(;;){
+		if(readn(ex, &d, Dirsz) != Dirsz)
+			break;
+		if(d.dirlen == 0)
+			break;
+		if(readn(ex, name, d.namelen) != d.namelen)
+			break;
+		i = d.dirlen - (Dirsz + d.namelen);
+		while(i-- > 0)
+			read(ex, &c, 1);
+		for(i=0; i<d.namelen; i++){
+			c = name[i];
+			if(c >= 'A' && c <= 'Z'){
+				c -= 'A';
+				c += 'a';
+			}
+			name[i] = c;
+		}
+		name[i] = 0;
+		while(*path == '/')
+			path++;
+		if((end = strchr(path, '/')) == 0)
+			end = path + strlen(path);
+		i = end - path;
+		if(d.namelen == i && memcmp(name, path, i) == 0){
+			ex->rp = ex->ep;
+			ex->lba = *((ulong*)d.lba);
+			ex->len = *((ulong*)d.len);
+			if(*end == 0)
+				return 0;
+			else if(d.flags[0] & 2){
+				path = end;
+				continue;
+			}
+			break;
+		}
+	}
+	close(ex);
+	return -1;
+}
+
+void
+start(void *sp)
+{
+	char path[Maxpath], *kern;
+	int drive;
+	Extend ex;
+	void *f;
+
+	/* drive passed in DL */
+	drive = ((ushort*)sp)[5] & 0xFF;
+
+#ifndef FAT
+	/*
+	 * load full bootblock as only the frist 2K get
+	 * loaded from bios. the code is specially arranged
+	 * to have all the important routines in the first
+	 * 2K of the 9bootiso image. (strings have been
+	 * placed in l.s to make sure they will be < 2K)
+	 */
+	if(isowalk(&ex, drive, bootname)){
+		print(bootname);
+		putc('?');
+		halt();
+	}
+	readn(&ex, origin, ex.len);
+	close(&ex);
+#endif
+
+	if(isowalk(f = &ex, drive, "/cfg/plan9.ini")){
+		print("no config\n");
+		f = 0;
+	}
+	for(;;){
+		kern = configure(f, path); f = 0;
+		if(isowalk(&ex, drive, kern)){
+			print("not found\n");
+			continue;
+		}
+		print(bootkern(&ex));
+		print("\n");
+	}
+}
+
--- /dev/null
+++ b/os/boot/pc/l.s
@@ -1,0 +1,299 @@
+#include "x16.h"
+#include "mem.h"
+
+#undef ORB
+
+#define DATA32SEL SELECTOR(1, SELGDT, 0)
+#define EXEC32SEL SELECTOR(2, SELGDT, 0)
+#define DATA16SEL SELECTOR(3, SELGDT, 0)
+#define EXEC16SEL SELECTOR(4, SELGDT, 0)
+
+#define SEGSS BYTE $0x36
+#define SEGES BYTE $0x26
+#define FARRET BYTE $0xCB
+
+TEXT origin(SB), $0
+	CLI
+	CLR(rCX)
+	MTSR(rCX, rSS)
+	OPSIZE; MOVL $origin(SB), SP
+	PUSHA
+	OPSIZE; ADSIZE; PUSHL SP
+	OPSIZE; ADSIZE; PUSHL CX
+	PUSHI(start(SB))
+
+TEXT pmode32(SB), $0
+	CLI
+
+	/* get return pc */
+	POPR(rDI)
+
+	/* make sure stack is at 0000: */
+	CLR(rCX)
+	MTSR(rCX, rSS)
+	OPSIZE; ANDL $0xFFFF, SP
+
+	/* convert 16-bit return pc to far pointer */
+	PUSHI(EXEC32SEL)
+	PUSHR(rDI)
+
+	/* load gdt */
+	SEGSS; LGDT(tgdtptr(SB))
+
+	/* enable protected mode */
+	MFCR(rCR0, rCX)
+	ORB $1, CL
+	MTCR(rCX, rCR0)
+
+	/* flush */
+	FARJUMP16(EXEC16SEL, pmode32flush(SB));
+TEXT pmode32flush(SB), $0
+
+	/* load 32-bit protected mode data selector */
+	LWI(DATA32SEL, rCX)
+
+_segret:
+	/* load all data segments */
+	MTSR(rCX, rDS)
+	MTSR(rCX, rES)
+	MTSR(rCX, rFS)
+	MTSR(rCX, rGS)
+	MTSR(rCX, rSS)
+	FARRET
+
+TEXT rmode16(SB), $0
+	/* setup farret to rmode16x */
+	PUSHL $EXEC16SEL
+	PUSHL $rmode16x(SB)
+
+	/* load 16-bit protected mode data selector */
+	MOVL $DATA16SEL, CX
+	JMP _segret
+
+TEXT rmode16x(SB), $0
+	/* disable protected mode */
+	MFCR(rCR0, rCX)
+	ANDB $0xfe, CL
+	MTCR(rCX, rCR0)
+
+	/* flush */
+	FARJUMP16(0, rmode16flush(SB));
+TEXT rmode16flush(SB), $0
+
+	/*
+	 * load 16-bit realmode data segment 0000: and
+	 * return to 32 bit return pc interpreted
+	 * as 16 bit far pointer.
+	 */
+	CLR(rCX)
+	JMP _segret
+
+TEXT tgdt(SB), $0 
+	/* null descriptor */
+	LONG $0
+	LONG $0
+
+	/* data segment descriptor for 4 gigabytes (PL 0) */
+	LONG $(0xFFFF)
+	LONG $(SEGG|SEGB|(0xF<<16)|SEGP|SEGPL(0)|SEGDATA|SEGW)
+
+	/* exec segment descriptor for 4 gigabytes (PL 0) */
+	LONG $(0xFFFF)
+	LONG $(SEGG|SEGD|(0xF<<16)|SEGP|SEGPL(0)|SEGEXEC|SEGR)
+
+	/* data segment descriptor for (PL 0) 16-bit */
+	LONG $(0xFFFF)
+	LONG $((0xF<<16)|SEGP|SEGPL(0)|SEGDATA|SEGW)
+
+	/* exec segment descriptor for (PL 0) 16-bit */
+	LONG $(0xFFFF)
+	LONG $((0xF<<16)|SEGP|SEGPL(0)|SEGEXEC|SEGR)
+
+TEXT tgdtptr(SB), $0 
+         WORD $(5*8) 
+         LONG $tgdt(SB)
+
+TEXT jump(SB), $0
+	MOVL 4(SP), AX
+	JMP *AX
+
+TEXT halt(SB), $0
+_halt:
+	JMP _halt
+
+TEXT kbdgetc(SB), $0
+	CALL rmode16(SB)
+	STI
+	MOVB $0x01, AH
+	BIOSCALL(0x16)
+	JNZ _gotkey
+	CLR(rAX)
+	JMP _pret32
+_gotkey:
+	CLR(rAX)
+	BIOSCALL(0x16)
+	JMP _pret32
+	
+TEXT cgaputc(SB), $0
+	MOVL 4(SP),AX
+	CALL rmode16(SB)
+	STI
+	MOVB $0x0E, AH
+	BIOSCALL(0x10)
+_pret32:
+	CALL16(pmode32(SB))
+	ANDL $0xFFFF, AX
+	RET
+
+TEXT usleep(SB), $0
+	MOVL t+4(SP), AX
+	PUSHL AX
+	CALL rmode16(SB)
+	STI
+	POPR(rDX)
+	POPR(rCX)
+	MOVB $0x86, AH
+	BIOSCALL(0x15)
+	JMP _pret32
+
+#ifdef PXE
+
+TEXT pxeinit(SB), $0
+	CALL rmode16(SB)
+
+	/* get pxe env structure in ES:BX */
+	LWI(0x5650, rAX)
+	BIOSCALL(0x1A)
+	JC _pret32
+
+	/* !PXE or PXEENV+ signature */
+	SEGES; LXW(0, xBX, rAX)
+	CMPI((('!'<<0)|('P'<<8)), rAX)
+	JEQ _getentry
+	CMPI((('P'<<0)|('X'<<8)), rAX)
+	JNE _pret32
+
+	SEGES; LXW(0x2A, xBX, rAX)
+	SEGES; LXW(0x28, xBX, rBX)
+	MTSR(rAX, rES)
+
+_getentry:
+	SEGES; LXW(0x12, xBX, rAX)
+	SW(rAX, pxepseg(SB))
+	SEGES; LXW(0x10, xBX, rAX)
+	SW(rAX, pxepoff(SB))
+	CLR(rAX)
+	JMP _pret32
+
+TEXT pxecallret(SB), $0
+	ADDI(6, rSP)
+	JMP _pret32
+
+TEXT pxecall(SB), $0
+	MOVL op+4(SP),AX
+	MOVL buf+8(SP),SI
+	CALL rmode16(SB)
+
+	CLR(rCX)
+	PUSHR(rCX)
+	PUSHR(rSI)
+
+	/* opcode */
+	PUSHR(rAX)
+
+	/* farcall */
+	PUSHR(rCX)
+	PUSHI(pxecallret(SB))
+
+	LW(pxepseg(SB), rAX)
+	PUSHR(rAX)
+	LW(pxepoff(SB), rAX)
+	PUSHR(rAX)
+
+	STI
+
+	CLR(rAX)
+	CLR(rBX)
+	CLR(rCX)
+	CLR(rDX)
+	CLR(rDI)
+	CLR(rSI)
+	FARRET
+
+TEXT pxepseg(SB), $0
+	WORD $0
+TEXT pxepoff(SB), $0
+	WORD $0
+
+#else /* PXE */
+
+/*
+ * in:
+ *	DL drive
+ *	AX:BX lba32,
+ *	0000:SI buffer
+ */
+TEXT readsect16(SB), $0
+	PUSHA
+	CLR(rCX)
+
+	PUSHR(rCX)		/* qword lba */
+	PUSHR(rCX)
+	PUSHR(rBX)
+	PUSHR(rAX)
+
+	PUSHR(rCX)		/* dword buffer */
+	PUSHR(rSI)
+
+	INC(rCX)
+	PUSHR(rCX)		/* word # of sectors */
+
+	PUSHI(0x0010)		/* byte reserved, byte packet size */
+
+	MW(rSP, rSI)
+	LWI(0x4200, rAX)
+	BIOSCALL(0x13)
+	JCC _readok
+	ADDI(0x10, rSP)
+	POPA
+	CLR(rAX)
+	DEC(rAX)
+	RET
+_readok:
+	ADDI(0x10, rSP)
+	POPA
+	CLR(rAX)
+	RET
+
+TEXT readsect(SB), $0
+	MOVL 4(SP), DX
+	MOVW 8(SP), AX
+	MOVW 10(SP), BX
+	MOVL 12(SP), SI 
+	CALL rmode16(SB)
+	STI
+	CALL16(readsect16(SB))
+	CALL16(pmode32(SB))
+	ANDL $0xFFFF, AX
+	RET
+
+#endif
+
+#ifdef ISO
+
+TEXT bootname(SB), $0
+	BYTE $'3'; BYTE $'8'; BYTE $'6'; BYTE $'/';
+	BYTE $'9'; BYTE $'b'; BYTE $'o'; BYTE $'o';
+	BYTE $'t'; BYTE $'i'; BYTE $'s'; BYTE $'o';
+	BYTE $0
+
+#endif
+
+TEXT uart(SB), $0
+	BYTE $0xff
+
+TEXT hex(SB), $0
+	BYTE $'0'; BYTE $'1'; BYTE $'2'; BYTE $'3';
+	BYTE $'4'; BYTE $'5'; BYTE $'6'; BYTE $'7';
+	BYTE $'8'; BYTE $'9'; BYTE $'a'; BYTE $'b';
+	BYTE $'c'; BYTE $'d'; BYTE $'e'; BYTE $'f'
--- /dev/null
+++ b/os/boot/pc/mbr.s
@@ -1,0 +1,259 @@
+/*
+ * Hard disc boot block. Loaded at 0x7C00, relocates to 0x0600:
+ *	8a mbr.s; 8l -o mbr -l -H3 -T0x0600 mbr.8
+ */
+#include "x16.h"
+#include "mem.h"
+
+/*#define FLOPPY	1		/* test on a floppy */
+#define TRACE(C)	PUSHA;\
+			CLR(rBX);\
+			MOVB $C, AL;\
+			LBI(0x0E, rAH);\
+			BIOSCALL(0x10);\
+			POPA
+
+/*
+ * We keep data on the stack, indexed by BP.
+ */
+#define Xdap		0x00		/* disc address packet */
+#define Xtable		0x10		/* partition table entry */
+#define Xdrive		0x12		/* starting disc */
+#define Xtotal		0x14		/* sum of allocated data above */
+
+/*
+ * Start: loaded at 0000:7C00, relocate to 0000:0600.
+ * Boot drive is in rDL.
+ */
+TEXT _start(SB), $0
+	CLI
+	CLR(rAX)
+	MTSR(rAX, rSS)			/* 0000 -> rSS */
+	LWI((0x7C00-Xtotal), rSP)	/* 7Bxx -> rSP */
+	MW(rSP, rBP)			/* set the indexed-data pointer */
+
+	MTSR(rAX, rDS)			/* 0000 -> rDS, source segment */
+	LWI(0x7C00, rSI)		/* 7C00 -> rSI, source offset */
+	MTSR(rAX, rES)			/* 0000 -> rES, destination segment */
+	LWI(0x600, rDI)			/* 0600 -> rDI, destination offset */
+	LWI(0x100, rCX)			/* 0100 -> rCX, loop count (words) */
+
+	CLD
+	REP; MOVSL			/* MOV DS:[(E)SI] -> ES:[(E)DI] */
+
+	FARJUMP16(0x0000, _start0600(SB))
+
+TEXT _start0600(SB), $0
+#ifdef FLOPPY
+	LBI(0x80, rDL)
+#else
+	CLRB(rAL)			/* some systems pass 0 */
+	CMPBR(rAL, rDL)
+	JNE _save
+	LBI(0x80, rDL)
+#endif /* FLOPPY */
+_save:
+	SXB(rDL, Xdrive, xBP)		/* save disc */
+
+	LWI(confidence(SB), rSI)	/* for that warm, fuzzy feeling */
+	CALL16(BIOSputs(SB))
+
+	LWI(_start+0x01BE(SB), rSI)	/* address of partition table */
+	LWI(0x04, rCX)			/* 4 entries in table */
+	LBI(0x80, rAH)			/* active entry value */
+	CLRB(rAL)			/* inactive entry value */
+
+_activeloop0:
+	LXB(0x00, xSI, rBL)		/* get active entry from table */
+	CMPBR(rBL, rAH)			/* is this an active entry? */
+	JEQ _active
+
+	CMPBR(rBL, rAL)			/* if not active it should be 0 */
+	JNE _invalidMBR
+
+	ADDI(0x10, rSI)			/* next table entry */
+	DEC(rCX)
+	JNE _activeloop0
+
+	LWI(noentry(SB), rSI)
+	CALL16(buggery(SB))
+
+_active:
+	MW(rSI, rDI)			/* save table address */
+
+_activeloop1:
+	ADDI(0x10, rSI)			/* next table entry */
+	DEC(rCX)
+	JEQ _readsector
+
+	LXB(0x00, xSI, rBL)		/* get active entry from table */
+	CMPBR(rBL, rAH)			/* is this an active entry? */
+	JNE _activeloop1		/* should only be one active */
+
+_invalidMBR:
+	LWI(invalidMBR(SB), rSI)
+	CALL16(buggery(SB))
+
+_readsector:
+	LBI(0x41, rAH)			/* check extensions present */
+	LWI(0x55AA, rBX)
+	LXB(Xdrive, xBP, rDL)		/* drive */
+	BIOSCALL(0x13)			/* CF set on failure */
+	JCS _readsector2
+	CMPI(0xAA55, rBX)
+	JNE _readsector2
+	ANDI(0x0001, rCX)
+	JEQ _readsector2
+
+_readsector42:
+	SBPBI(0x10, Xdap+0)		/* packet size */
+	SBPBI(0x00, Xdap+1)		/* reserved */
+	SBPBI(0x01, Xdap+2)		/* number of blocks to transfer */
+	SBPBI(0x00, Xdap+3)		/* reserved */
+	SBPWI(0x7C00, Xdap+4)		/* transfer buffer :offset */
+	SBPWI(0x0000, Xdap+6)		/* transfer buffer seg: */
+	LXW(0x08, xDI, rAX)		/* LBA (64-bits) */
+	SBPW(rAX, Xdap+8)
+	LXW(0x0A, xDI, rAX)
+	SBPW(rAX, Xdap+10)
+	SBPWI(0x0000, Xdap+12)
+	SBPWI(0x0000, Xdap+14)
+
+	MW(rBP, rSI)			/* disk address packet */
+	LBI(0x42, rAH)			/* extended read */
+	BIOSCALL(0x13)			/* CF set on failure */
+	JCC _readsectorok
+
+	LWI(ioerror(SB), rSI)
+	CALL16(buggery(SB))
+
+/*
+ * Read a sector from a disc using the traditional BIOS call.
+ * For BIOSCALL(0x13/AH=0x02):
+ *   rAH	0x02
+ *   rAL	number of sectors to read (1)
+ *   rCH	low 8 bits of cylinder
+ *   rCL	high 2 bits of cylinder (7-6), sector (5-0)
+ *   rDH	head
+ *   rDL	drive
+ *   rES:rBX	buffer address
+ */
+_readsector2:
+	LXB(0x01, xDI, rDH)		/* head */
+	LXW(0x02, xDI, rCX)		/* save active cylinder/sector */
+
+	LWI(0x0201, rAX)		/* read one sector */
+	LXB(Xdrive, xBP, rDL)		/* drive */
+	LWI(0x7C00, rBX)		/* buffer address (rES already OK) */
+	BIOSCALL(0x13)			/* CF set on failure */
+	JCC _readsectorok
+
+	LWI(ioerror(SB), rSI)
+	CALL16(buggery(SB))
+
+_readsectorok:
+	LWI(0x7C00, rBX)		/* buffer address (rES already OK) */
+	LXW(0x1FE, xBX, rAX)
+	CMPI(0xAA55, rAX)
+	JNE _bbnotok
+
+	/*
+	 * Jump to the loaded PBS.
+	 * rDL and rSI should still contain the drive
+	 * and partition table pointer respectively.
+	 */
+	MW(rDI, rSI)
+	FARJUMP16(0x0000, 0x7C00)
+
+_bbnotok:
+	LWI(invalidPBS(SB), rSI)
+
+TEXT buggery(SB), $0
+	CALL16(BIOSputs(SB))
+	LWI(reboot(SB), rSI)
+	CALL16(BIOSputs(SB))
+
+_wait:
+	CLR(rAX)			/* wait for any key */
+	BIOSCALL(0x16)
+
+_reset:
+	CLR(rBX)			/* set ES segment for BIOS area */
+	MTSR(rBX, rES)
+
+	LWI(0x0472, rBX)		/* warm-start code address */
+	LWI(0x1234, rAX)		/* warm-start code */
+	POKEW				/* MOVW	AX, ES:[BX] */
+
+	FARJUMP16(0xFFFF, 0x0000)	/* reset */
+
+/*
+ * Output a string to the display.
+ * String argument is in rSI.
+ */
+TEXT BIOSputs(SB), $0
+	PUSHA
+	CLR(rBX)
+_BIOSputs:
+	LODSB
+	ORB(rAL, rAL)
+	JEQ _BIOSputsret
+
+	LBI(0x0E, rAH)
+	BIOSCALL(0x10)
+	JMP _BIOSputs
+
+_BIOSputsret:
+	POPA
+	RET
+
+/* "No active entry in MBR" */
+TEXT noentry(SB), $0
+	BYTE $'N'; BYTE $'o'; BYTE $' '; BYTE $'a';
+	BYTE $'c'; BYTE $'t'; BYTE $'i'; BYTE $'v';
+	BYTE $'e'; BYTE $' '; BYTE $'e'; BYTE $'n';
+	BYTE $'t'; BYTE $'r'; BYTE $'y'; BYTE $' ';
+	BYTE $'i'; BYTE $'n'; BYTE $' '; BYTE $'M';
+	BYTE $'B'; BYTE $'R';
+	BYTE $'\z';
+
+/* "Invalid MBR" */
+TEXT invalidMBR(SB), $0
+	BYTE $'I'; BYTE $'n'; BYTE $'v'; BYTE $'a';
+	BYTE $'l'; BYTE $'i'; BYTE $'d'; BYTE $' ';
+	BYTE $'M'; BYTE $'B'; BYTE $'R';
+	BYTE $'\z';
+
+/* "I/O error" */
+TEXT ioerror(SB), $0
+	BYTE $'I'; BYTE $'/'; BYTE $'O'; BYTE $' ';
+	BYTE $'e'; BYTE $'r'; BYTE $'r'; BYTE $'o';
+	BYTE $'r';
+	BYTE $'\z';
+
+/* "Invalid PBS" */
+TEXT invalidPBS(SB), $0
+	BYTE $'I'; BYTE $'n'; BYTE $'v'; BYTE $'a';
+	BYTE $'l'; BYTE $'i'; BYTE $'d'; BYTE $' ';
+	BYTE $'P'; BYTE $'B'; BYTE $'S';
+	BYTE $'\z';
+
+/* "\r\nPress almost any key to reboot..." */
+TEXT reboot(SB), $0
+	BYTE $'\r';BYTE $'\n';
+	BYTE $'P'; BYTE $'r'; BYTE $'e'; BYTE $'s';
+	BYTE $'s'; BYTE $' '; BYTE $'a'; BYTE $'l'; 
+	BYTE $'m'; BYTE $'o'; BYTE $'s'; BYTE $'t';
+	BYTE $' '; BYTE $'a'; BYTE $'n'; BYTE $'y';
+	BYTE $' '; BYTE $'k'; BYTE $'e'; BYTE $'y';
+	BYTE $' '; BYTE $'t'; BYTE $'o'; BYTE $' ';
+	BYTE $'r'; BYTE $'e'; BYTE $'b'; BYTE $'o';
+	BYTE $'o'; BYTE $'t'; BYTE $'.'; BYTE $'.';
+	BYTE $'.';
+	BYTE $'\z';
+
+/* "MBR..." */
+TEXT confidence(SB), $0
+	BYTE $'M'; BYTE $'B'; BYTE $'R'; BYTE $'.';
+	BYTE $'.'; BYTE $'.';
+	BYTE $'\z';
--- /dev/null
+++ b/os/boot/pc/mem.h
@@ -1,0 +1,46 @@
+/*
+ * Memory and machine-specific definitions.  Used in C and assembler.
+ */
+
+/*
+ * Sizes
+ */
+#define	BI2BY		8			/* bits per byte */
+#define	BI2WD		32			/* bits per word */
+#define	BY2WD		4			/* bytes per word */
+#define	BY2PG		4096			/* bytes per page */
+#define	WD2PG		(BY2PG/BY2WD)		/* words per page */
+#define	PGSHIFT		12			/* log(BY2PG) */
+#define	PGROUND(s)	(((s)+(BY2PG-1))&~(BY2PG-1))
+
+/*
+ * Fundamental addresses
+ */
+#define CONFADDR	0x1200		/* info passed from boot loader */
+#define BIOSXCHG	0x6000		/* To exchange data with the BIOS */
+
+#define SELGDT	(0<<3)	/* selector is in gdt */
+#define	SELLDT	(1<<3)	/* selector is in ldt */
+
+#define SELECTOR(i, t, p)	(((i)<<3) | (t) | (p))
+
+/*
+ *  fields in segment descriptors
+ */
+#define SEGDATA	(0x10<<8)	/* data/stack segment */
+#define SEGEXEC	(0x18<<8)	/* executable segment */
+#define	SEGTSS	(0x9<<8)	/* TSS segment */
+#define SEGCG	(0x0C<<8)	/* call gate */
+#define	SEGIG	(0x0E<<8)	/* interrupt gate */
+#define SEGTG	(0x0F<<8)	/* task gate */
+#define SEGTYPE	(0x1F<<8)
+
+#define SEGP	(1<<15)		/* segment present */
+#define SEGPL(x) ((x)<<13)	/* priority level */
+#define SEGB	(1<<22)		/* granularity 1==4k (for expand-down) */
+#define SEGG	(1<<23)		/* granularity 1==4k (for other) */
+#define SEGE	(1<<10)		/* expand down */
+#define SEGW	(1<<9)		/* writable (for data/stack) */
+#define	SEGR	(1<<9)		/* readable (for code) */
+#define SEGD	(1<<22)		/* default 1==32bit (for code) */
+
--- /dev/null
+++ b/os/boot/pc/mkfile
@@ -1,0 +1,107 @@
+objtype=386
+</$objtype/mkfile
+BIN=/386
+
+TARG=9bootiso 9bootpxe 9bootfat 9boothyb mbr pbs
+
+HFILES=mem.h fns.h x16.h
+
+all: 9bootiso 9bootpxe 9bootfat 9boothyb mbr pbs
+	
+clean:V:
+	rm -rf $TARG *.$O test.* tmp
+
+liso.$O: l.s
+	$AS -DISO -o $target l.s
+
+lpxe.$O: l.s
+	$AS -DPXE -o $target l.s
+
+lfat.$O: l.s
+	$AS -DFAT -o $target l.s
+
+lhyb.$O: l.s
+	$AS -DFAT -o $target l.s
+
+hyb.$O: iso.c
+	$CC $CFLAGS -DFAT -o $target iso.c
+
+%.$O:	%.s
+	$AS $stem.s
+
+%.$O:	%.c
+	$CC $CFLAGS $stem.c
+
+%.$O:	$HFILES
+
+mbr:	mbr.$O
+	$LD -o $target -H3 -T0x0600 -l $prereq
+	ls -l $target
+
+pbs:	pbs.$O
+	$LD -o $target -H3 -T0x0800 -l $prereq
+	ls -l $target
+
+9boot&:	l%.$O %.$O sub.$O apm.$O e820.$O a20.$O uart.$O
+	$LD -o $target -H3 -T0x7c00 -l $prereq
+	ls -l $target
+
+install:V:
+	for (i in $TARG)
+		mk $MKFLAGS $i.install
+
+%.install:V:	$BIN/%
+	
+
+$BIN/%:	%
+	cp $stem $BIN/$stem
+
+test.iso: 9bootiso 9boothyb 9bootfat mbr pbs
+	rm -fr tmp $target
+	mkdir tmp
+	mkdir tmp/386
+	mkdir tmp/cfg
+	cp 9bootiso tmp/386
+	cp /386/9pc tmp/386
+	cp /sys/lib/dist/cfg/plan9.ini tmp/cfg/plan9.ini
+	echo wait >>tmp/cfg/plan9.ini
+	disk/mk9660 -B 386/9bootiso -p <{echo +} -s tmp $target
+	@{rfork n
+		bind 9boothyb 9bootfat
+		dd -if /dev/zero -bs 512 -count 4096 >> $target
+		disk/partfs -m /n/partfs $target
+		disk=/n/partfs/sdXX
+		disk/mbr -m mbr $disk/data
+		@{echo a p1 '$-1' '$'
+			echo t p1 FAT16
+			echo A p1
+			echo w
+			echo q} | disk/fdisk -b $disk/data
+		disk/format -b pbs -d -r 1 $disk/dos 9bootfat
+	}
+	rm -fr tmp
+
+test.dsk: 9bootfat mbr pbs test.iso
+	rm -fr tmp $target
+	mkdir tmp
+	cp test.iso 9bootfat tmp
+	mkdir tmp/386
+	cp /386/9pc tmp/386
+	echo 'bootfile=/386/9pc' >tmp/plan9.ini
+	dd -if /dev/zero -of $target -bs 512 -count 32768
+	disk/partfs -m /n/$target $target
+	disk=/n/$target/sdXX
+	disk/mbr -m mbr $disk/data
+	disk/fdisk -baw $disk/data
+	disk/prep -bw -a 9fat $disk/plan9
+	disk/format -b pbs -d -r 2 $disk/9fat
+	s=$target.dos
+	m=/n/$target.9fat
+	rm -f /srv/$s
+	dossrv -f $disk/9fat $s
+	mount -c /srv/$s $m
+	@{cd tmp; tar c .} | @{cd $m; tar xv}
+	unmount $m
+	rm -f /srv/$s
+	unmount /n/$target
+	rm -fr tmp
--- /dev/null
+++ b/os/boot/pc/pbs.s
@@ -1,0 +1,292 @@
+#include "x16.h"
+#include "mem.h"
+
+#define RELOC 0x7c00
+
+TEXT _magic(SB), $0
+	BYTE $0xEB; BYTE $0x58;		/* jmp .+ 0x58  (_start0x5A) */
+	BYTE $0x90			/* nop */
+TEXT _version(SB), $0
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00
+TEXT _sectsize(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _clustsize(SB), $0
+	BYTE $0x00
+TEXT _nresrv(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _nfats(SB), $0
+	BYTE $0x00
+TEXT _rootsize(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _volsize(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _mediadesc(SB), $0
+	BYTE $0x00
+TEXT _fatsize(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _trksize(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _nheads(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _nhiddenlo(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _nhiddenhi(SB), $0
+	BYTE $0x00; BYTE $0x00;
+TEXT _bigvolsize(SB), $0
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
+/* FAT32 structure, starting @0x24 */
+TEXT _fatsz32lo(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _fatsz32hi(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _extflags(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _fsver(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _rootclust(SB), $0
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00
+TEXT _fsinfo(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _bkboot(SB), $0
+	BYTE $0x00; BYTE $0x00
+TEXT _reserved0(SB), $0
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00
+TEXT _driveno(SB), $0
+	BYTE $0x00
+TEXT _reserved1(SB), $0
+	BYTE $0x00
+TEXT _bootsig(SB), $0
+	BYTE $0x00
+TEXT _volid(SB), $0
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
+TEXT _label(SB), $0
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00
+	BYTE $0x00; BYTE $0x00; BYTE $0x00
+TEXT _type(SB), $0
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00;
+	BYTE $0x00; BYTE $0x00; BYTE $0x00; BYTE $0x00
+
+_start0x5A:
+	CLI
+	CLR(rAX)
+	MTSR(rAX, rSS)			/* 0000 -> rSS */
+	MTSR(rAX, rDS)			/* 0000 -> rDS, source segment */
+	MTSR(rAX, rES)
+
+	LWI(0x100, rCX)
+	LWI(RELOC, rSI)
+	MW(rSI, rSP)
+	LWI(_magic(SB), rDI)
+	CLD
+	REP; MOVSL			/* MOV DS:[(E)SI] -> ES:[(E)DI] */
+
+	MW(rSP, rBP)
+
+	PUSHR(rCX)
+	PUSHI(start16(SB))
+	BYTE $0xCB			/* FAR RET */
+
+TEXT start16(SB), $0
+	STI
+
+	LWI(hello(SB), rSI)
+	CALL16(print16(SB))
+
+	STB(rDL, _driveno(SB))
+
+	CLR(rDX)
+	LW(_fatsize(SB), rAX)
+	CLR(rCX)
+	LB(_nfats(SB), rCL)
+	MUL(rCX)
+	OR(rAX, rAX)
+	JNE _fatszok	/* zero? it's FAT32 */
+
+	LW(_fatsz32hi(SB), rBX)
+	IMUL(rCX, rBX)
+	LW(_fatsz32lo(SB), rAX)
+	MUL(rCX)
+	ADD(rBX, rDX)
+
+_fatszok:
+	LW(_nhiddenlo(SB), rCX)
+	ADD(rCX, rAX)
+	LW(_nhiddenhi(SB), rCX)
+	ADC(rCX, rDX)
+
+	CLR(rBX)
+	LW(_nresrv(SB), rCX)
+	ADD(rCX, rAX)
+	ADC(rDX, rBX)
+
+	SW(rAX, _volid(SB))	/* save for later use */
+	SW(rBX, _volid+2(SB))
+
+	PUSHR(rBP)
+	LW(_sectsize(SB), rCX)
+	SUB(rCX, rSP)
+	MW(rSP, rBP)
+	MW(rSP, rSI)
+
+_nextsect:
+	CALL16(readsect16(SB))
+	LW(_sectsize(SB), rCX)
+	SHRI(5, rCX)
+
+_nextdir:
+	PUSHR(rCX)
+	PUSHR(rSI)			/* save for later if it matches */
+	LWI(bootname(SB), rDI)
+	LW(bootnamelen(SB), rCX)
+	CLD
+	REP
+	CMPSB
+	POPR(rSI)
+	POPR(rCX)
+	JEQ _found
+	ADDI(0x20, rSI)	
+	LOOP _nextdir
+	ADDI(1, rAX)
+	ADC(rCX, rBX)
+	JMP _nextsect
+
+_found:
+	CLR(rBX)
+	LW(_rootsize(SB), rAX)		/* calculate and save Xrootsz */
+	LWI(0x20, rCX)
+	MUL(rCX)
+	LW(_sectsize(SB), rCX)
+	DEC(rCX)
+	ADD(rCX, rAX)
+	ADC(rBX, rDX)
+	INC(rCX)
+	DIV(rCX)
+	PUSHR(rAX)			/* Xrootsz */
+
+	CLR(rCX)
+	LXW(0x1a, xSI, rAX)		/* start cluster low */
+	LXW(0x14, xSI, rBX)		/* start cluster high */
+	SUBI(2, rAX)			/* cluster -= 2 */
+	SBB(rCX, rBX)
+
+	LB(_clustsize(SB), rCL)		/* convert to sectors (AX:DX) */
+	IMUL(rCX, rBX)
+	MUL(rCX)
+	ADD(rBX, rDX)
+
+	LW(_volid(SB), rCX)		/* Xrootlo */
+	ADD(rCX, rAX)
+	LW(_volid+2(SB), rCX)		/* Xroothi */
+	ADC(rCX, rDX)
+
+	CLR(rBX)
+	POPR(rCX)			/* Xrootsz */
+	ADD(rCX, rAX)
+	ADC(rBX, rDX)
+
+	PUSHR(rAX)			/* calculate how many sectors to read (CX) */
+	PUSHR(rDX)
+	LXW(0x1c, xSI, rAX)
+	LXW(0x1e, xSI, rDX)
+	LW(_sectsize(SB), rCX)
+	DEC(rCX)
+	ADD(rCX, rAX)
+	ADC(rBX, rDX)
+	INC(rCX)
+	DIV(rCX)
+	MW(rAX, rCX)
+	POPR(rBX)
+	POPR(rAX)
+
+	LWI(RELOC, rSI)
+	PUSHR(rSI)	/* entry */
+
+_loadnext:
+	CALL16(readsect16(SB))
+
+	LW(_sectsize(SB), rDX)
+	ADD(rDX, rSI)
+
+	CLR(rDX)
+	ADDI(1, rAX)
+	ADC(rDX, rBX)
+
+	LOOP _loadnext
+
+	LWI(ok(SB), rSI)
+	CALL16(print16(SB))
+
+	LB(_driveno(SB), rDL)
+	CLI
+	RET
+
+TEXT print16(SB), $0
+	PUSHA
+	CLR(rBX)
+_printnext:
+	LODSB
+	ORB(rAL, rAL)
+	JEQ _printret
+	LBI(0x0E, rAH)
+	BIOSCALL(0x10)
+	JMP _printnext
+_printret:
+	POPA
+	RET
+
+/*
+ * in:
+ *	AX:BX lba32,
+ *	0000:SI buffer
+ */
+TEXT readsect16(SB), $0
+_retry:
+	PUSHA
+	CLR(rDX)
+
+	PUSHR(rDX)		/* qword lba */
+	PUSHR(rDX)
+	PUSHR(rBX)
+	PUSHR(rAX)
+
+	PUSHR(rDX)		/* dword buffer */
+	PUSHR(rSI)
+
+	INC(rDX)
+	PUSHR(rDX)		/* word # of sectors */
+
+	PUSHI(0x0010)		/* byte reserved, byte packet size */
+
+	MW(rSP, rSI)
+	LB(_driveno(SB), rDL)
+	LWI(0x4200, rAX)
+	BIOSCALL(0x13)
+	JCC _readok
+	LWI((0x0E00|'!'), rAX)
+	BIOSCALL(0x10)
+	ADDI(0x10, rSP)
+	POPA
+	JMP _retry
+_readok:
+	LWI((0x0E00|'.'), rAX)
+	BIOSCALL(0x10)
+	ADDI(0x10, rSP)
+	POPA
+	RET
+
+TEXT bootnamelen(SB), $0
+	WORD $8
+TEXT bootname(SB), $0
+	BYTE $'9'; BYTE $'B'; BYTE $'O'; BYTE $'O';
+	BYTE $'T'; BYTE $'F'; BYTE $'A'; BYTE $'T';
+	BYTE $0
+
+TEXT hello(SB), $0
+	BYTE $'p'; BYTE $'b'; BYTE $'s'; BYTE $0
+TEXT ok(SB), $0
+	BYTE $'o'; BYTE $'k'; BYTE $'\r'; BYTE $'\n';
+	BYTE $0
--- /dev/null
+++ b/os/boot/pc/pxe.c
@@ -1,0 +1,362 @@
+#include <u.h>
+#include "fns.h"
+
+enum {
+	Tftp_READ	= 1,
+	Tftp_WRITE	= 2,
+	Tftp_DATA	= 3,
+	Tftp_ACK	= 4,
+	Tftp_ERROR	= 5,
+	Tftp_OACK	= 6,
+
+	TftpPort	= 69,
+
+	Segsize		= 512,
+	Maxpath		= 64,
+};
+
+typedef uchar IP4[4];
+
+typedef struct Tftp Tftp;
+typedef struct Dhcp Dhcp;
+
+struct Tftp
+{
+	IP4 sip;
+	IP4 dip;
+	IP4 gip;
+
+	int sport;
+	int dport;
+
+	char *rp;
+	char *ep;
+
+	int seq;
+	int eof;
+	
+	char pkt[2+2+Segsize];
+	char nul;
+};
+
+struct Dhcp
+{
+	uchar opcode;
+	uchar hardware;
+	uchar hardlen;
+	uchar gatehops;
+	uchar ident[4];
+	uchar seconds[2];
+	uchar flags[2];
+	uchar cip[4];
+	uchar yip[4];
+	uchar sip[4];
+	uchar gip[4];
+	uchar mac[16];
+	char sname[64];
+	char bootfile[128];
+};
+
+int pxeinit(void);
+int pxecall(int op, void *buf);
+
+static void*
+unfar(ulong seg, ulong off)
+{
+	return (void*)((off & 0xFFFF) + (seg & 0xFFFF)*16);
+}
+
+static void
+puts(void *x, ushort v)
+{
+	uchar *p = x;
+
+	p[1] = (v>>8) & 0xFF;
+	p[0] = v & 0xFF;
+}
+
+static ushort
+gets(void *x)
+{
+	uchar *p = x;
+
+	return p[1]<<8 | p[0];
+}
+
+static void
+hnputs(void *x, ushort v)
+{
+	uchar *p = x;
+
+	p[0] = (v>>8) & 0xFF;
+	p[1] = v & 0xFF;
+}
+
+static ushort
+nhgets(void *x)
+{
+	uchar *p = x;
+
+	return p[0]<<8 | p[1];
+}
+
+static void
+moveip(IP4 d, IP4 s)
+{
+	memmove(d, s, sizeof(d));
+}
+
+void
+unload(void)
+{
+	struct {
+		uchar status[2];
+		uchar junk[10];
+	} buf;
+	static uchar shutdown[] = { 0x05, 0x070, 0x02, 0 };
+	uchar *o;
+
+	for(o = shutdown; *o; o++){ 
+		memset(&buf, 0, sizeof(buf));
+		if(pxecall(*o, &buf))
+			break;
+	}
+}
+
+static int
+getip(IP4 yip, IP4 sip, IP4 gip, char mac[16])
+{
+	struct {
+		uchar status[2];
+		uchar pkttype[2];
+		uchar bufsize[2];
+		uchar off[2];
+		uchar seg[2];
+		uchar lim[2];
+	} buf;
+	int i, r;
+	Dhcp *p;
+
+	memset(&buf, 0, sizeof(buf));
+	puts(buf.pkttype, 3);
+
+	if(r = pxecall(0x71, &buf))
+		return -r;
+	if((p = unfar(gets(buf.seg), gets(buf.off))) == 0)
+		return -1;
+	moveip(yip, p->yip);
+	moveip(sip, p->sip);
+	moveip(gip, p->gip);
+	mac[12] = 0;
+	for(i=0; i<6; i++){
+		mac[i*2] = hex[p->mac[i]>>4];
+		mac[i*2+1] = hex[p->mac[i]&15];
+	}
+	return 0;
+}
+
+static int
+udpopen(IP4 sip)
+{
+	struct {
+		uchar status[2];
+		uchar sip[4];
+	} buf;
+
+	puts(buf.status, 0);
+	moveip(buf.sip, sip);
+	return pxecall(0x30, &buf);
+}
+
+static int
+udpclose(void)
+{
+	uchar status[2];
+	puts(status, 0);
+	return pxecall(0x31, status);
+}
+
+static int
+udpread(IP4 sip, IP4 dip, int *sport, int dport, int *len, void *data)
+{
+	struct {
+		uchar status[2];
+		uchar sip[4];
+		uchar dip[4];
+		uchar sport[2];
+		uchar dport[2];
+		uchar len[2];
+		uchar off[2];
+		uchar seg[2];
+	} buf;
+	int r;
+
+	puts(buf.status, 0);
+	moveip(buf.sip, sip);
+	moveip(buf.dip, dip);
+	hnputs(buf.sport, *sport);
+	hnputs(buf.dport, dport);
+	puts(buf.len, *len);
+	puts(buf.off, (long)data);
+	puts(buf.seg, 0);
+	if(r = pxecall(0x32, &buf))
+		return r;
+	moveip(sip, buf.sip);
+	*sport = nhgets(buf.sport);
+	*len = gets(buf.len);
+	return 0;
+}
+
+static int
+udpwrite(IP4 ip, IP4 gw, int sport, int dport, int len, void *data)
+{
+	struct {
+		uchar status[2];
+		uchar ip[4];
+		uchar gw[4];
+		uchar sport[2];
+		uchar dport[2];
+		uchar len[2];
+		uchar off[2];
+		uchar seg[2];
+	} buf;
+
+	puts(buf.status, 0);
+	moveip(buf.ip, ip);
+	moveip(buf.gw, gw);
+	hnputs(buf.sport, sport);
+	hnputs(buf.dport, dport);
+	puts(buf.len, len);
+	puts(buf.off, (long)data);
+	puts(buf.seg, 0);
+	return pxecall(0x33, &buf);
+}
+
+int
+read(void *f, void *data, int len)
+{
+	Tftp *t = f;
+	int seq, n;
+
+	while(!t->eof && t->rp >= t->ep){
+		for(;;){
+			n = sizeof(t->pkt);
+			if(udpread(t->dip, t->sip, &t->dport, t->sport, &n, t->pkt))
+				continue;
+			if(n >= 4)
+				break;
+		}
+		switch(nhgets(t->pkt)){
+		case Tftp_DATA:
+			seq = nhgets(t->pkt+2);
+			if(seq > t->seq){
+				putc('?');
+				continue;
+			}
+			hnputs(t->pkt, Tftp_ACK);
+			while(udpwrite(t->dip, t->gip, t->sport, t->dport, 4, t->pkt))
+				putc('!');
+			if(seq < t->seq){
+				putc('@');
+				continue;
+			}
+			t->seq = seq+1;
+			n -= 4;
+			t->rp = t->pkt + 4;
+			t->ep = t->rp + n;
+			t->eof = n < Segsize;
+			break;
+		case Tftp_ERROR:
+			print(t->pkt+4);
+			print("\n");
+		default:
+			t->eof = 1;
+			return -1;
+		}
+		break;
+	}
+	n = t->ep - t->rp;
+	if(len > n)
+		len = n;
+	memmove(data, t->rp, len);
+	t->rp += len;
+	return len;
+}
+
+void
+close(void *f)
+{
+	Tftp *t = f;
+	t->eof = 1;
+	udpclose();
+}
+
+
+static int
+tftpopen(Tftp *t, char *path, IP4 sip, IP4 dip, IP4 gip)
+{
+	static ushort xport = 6666;
+	int r, n;
+	char *p;
+
+	moveip(t->sip, sip);
+	moveip(t->gip, gip);
+	memset(t->dip, 0, sizeof(t->dip));
+	t->sport = xport++;
+	t->dport = 0;
+	t->rp = t->ep = 0;
+	t->seq = 1;
+	t->eof = 0;
+	t->nul = 0;
+	if(r = udpopen(t->sip))
+		return r;
+	p = t->pkt;
+	hnputs(p, Tftp_READ); p += 2;
+	n = strlen(path)+1;
+	memmove(p, path, n); p += n;
+	memmove(p, "octet", 6); p += 6;
+	n = p - t->pkt;
+	for(;;){
+		if(r = udpwrite(dip, t->gip, t->sport, TftpPort, n, t->pkt))
+			break;
+		if(r = read(t, 0, 0))
+			break;
+		return 0;
+	}
+	close(t);
+	return r;
+}
+
+void
+start(void *)
+{
+	char mac[16], path[Maxpath], *kern;
+	IP4 yip, sip, gip;
+	void *f;
+	Tftp t;
+
+	if(pxeinit()){
+		print("pxe init\n");
+		halt();
+	}
+	if(getip(yip, sip, gip, mac)){
+		print("bad dhcp\n");
+		halt();
+	}
+	memmove(path, "/cfg/pxe/", 9);
+	memmove(path+9, mac, 13);
+	if(tftpopen(f = &t, path, yip, sip, gip))
+		if(tftpopen(f, "/cfg/pxe/default", yip, sip, gip)){
+			print("no config\n");
+			f = 0;
+		}
+	for(;;){
+		kern = configure(f, path); f = 0;
+		if(tftpopen(&t, kern, yip, sip, gip)){
+			print("not found\n");
+			continue;
+		}
+		print(bootkern(&t));
+		print("\n");
+	}
+}
--- /dev/null
+++ b/os/boot/pc/sub.c
@@ -1,0 +1,554 @@
+#include <u.h>
+#include <a.out.h>
+#include "fns.h"
+#include "mem.h"
+
+void
+putc(int c)
+{
+	cgaputc(c);
+	if(uart != -1)
+		uartputc(uart, c);
+}
+
+void
+print(char *s)
+{
+	while(*s != 0){
+		if(*s == '\n')
+			putc('\r');
+		putc(*s++);
+	}
+}
+
+int
+readn(void *f, void *data, int len)
+{
+	uchar *p, *e;
+
+	putc(' ');
+	p = data;
+	e = p + len;
+	while(p < e){
+		if(((ulong)p & 0xF000) == 0){
+			putc('\b');
+			putc(hex[((ulong)p>>16)&0xF]);
+		}
+		if((len = read(f, p, e - p)) <= 0)
+			break;
+		p += len;
+	}
+	putc('\b');
+
+	return p - (uchar*)data;
+}
+
+void
+memmove(void *dst, void *src, int n)
+{
+	uchar *d = dst;
+	uchar *s = src;
+
+	if(d < s){
+		while(n-- > 0)
+			*d++ = *s++;
+	} else if(d > s){
+		s += n;
+		d += n;
+		while(n-- > 0)
+			*--d = *--s;
+	}
+}
+
+int
+memcmp(void *src, void *dst, int n)
+{
+	uchar *d = dst;
+	uchar *s = src;
+	int r = 0;
+
+	while(n-- > 0){
+		r = *d++ - *s++;
+		if(r != 0)
+			break;
+	}
+
+	return r;
+}
+
+int
+strlen(char *s)
+{
+	char *p = s;
+
+	while(*p != '\0')
+		p++;
+
+	return p - s;
+}
+
+char*
+strchr(char *s, int c)
+{
+	for(; *s != 0; s++)
+		if(*s == c)
+			return s;
+
+	return nil;
+}
+
+void
+memset(void *dst, int v, int n)
+{
+	uchar *d = dst;
+
+	while(n > 0){
+		*d++ = v;
+		n--;
+	}
+}
+
+int
+getc(void)
+{
+	int c;
+
+	c = kbdgetc();
+	if(c == 0 && uart != -1)
+		c = uartgetc(uart);
+	return c;
+}
+
+static int
+readline(void *f, char *buf)
+{
+	static char white[] = "\t ";
+	char *p;
+
+	p = buf;
+	do{
+		if(f == nil)
+			putc('>');
+		for(;;){
+			if(f == nil){
+				while((*p = getc()) == 0)
+					;
+				if(p == buf && (*p == '\b' || strchr(white, *p) != nil))
+					continue;
+				putc(*p);
+				if(*p == '\r')
+					putc('\n');
+				else if(*p == '\b'){
+					p--;
+					putc(' ');
+					putc('\b');
+					continue;
+				}
+			}else if(read(f, p, 1) <= 0)
+				return 0;
+			if(strchr("\r\n", *p) != nil)
+				break;
+			if(p == buf && strchr(white, *p) != nil)
+				continue;	/* whitespace on start of line */
+			p++;
+		}
+		while(p > buf && strchr(white, p[-1]))
+			p--;
+	}while(p == buf);
+	*p = 0;
+
+	return p - buf;
+}
+
+static int
+timeout(int ms)
+{
+	while(ms > 0){
+		if(getc() != 0)
+			return 1;
+		usleep(100000);
+		ms -= 100;
+	}
+	return 0;
+}
+
+#define BOOTLINE	((char*)CONFADDR)
+#define BOOTLINELEN	64
+#define BOOTARGS	((char*)(CONFADDR+BOOTLINELEN))
+#define	BOOTARGSLEN	(4096-0x200-BOOTLINELEN)
+
+char *confend;
+
+static void apmconf(int);
+static void e820conf(void);
+static void ramdiskconf(int);
+static void uartconf(char*);
+
+static char*
+getconf(char *s, char *buf)
+{
+	char *p, *e;
+	int n;
+
+	n = strlen(s);
+	for(p = BOOTARGS; p < confend; p = e+1){
+		for(e = p+1; e < confend; e++)
+			if(*e == '\n')
+				break;
+		if(memcmp(p, s, n) == 0){
+			p += n;
+			n = e - p;
+			buf[n] = 0;
+			memmove(buf, p, n);
+			return buf;
+		}
+	}
+	return nil;
+}
+
+static int
+delconf(char *s)
+{
+	char *p, *e;
+
+	for(p = BOOTARGS; p < confend; p = e){
+		for(e = p+1; e < confend; e++){
+			if(*e == '\n'){
+				e++;
+				break;
+			}
+		}
+		if(memcmp(p, s, strlen(s)) == 0){
+			memmove(p, e, confend - e);
+			confend -= e - p;
+			*confend = 0;
+			return 1;
+		}
+	}
+	return 0;
+}
+
+char*
+configure(void *f, char *path)
+{
+	char *line, *kern, *s, *p;
+	int inblock, nowait, n;
+	static int once = 1;
+
+	if(once){
+		once = 0;
+Clear:
+		memset(BOOTLINE, 0, BOOTLINELEN);
+
+		confend = BOOTARGS;
+		memset(confend, 0, BOOTARGSLEN);
+
+		e820conf();
+		ramdiskconf(0);
+	}
+	nowait = 1;
+	inblock = 0;
+Loop:
+	while(readline(f, line = confend+1) > 0){
+		if(*line == 0 || strchr("#;=", *line) != nil)
+			continue;
+		if(*line == '['){
+			inblock = memcmp("[common]", line, 8) != 0;
+			continue;
+		}
+		if(memcmp("boot", line, 5) == 0){
+			nowait=1;
+			break;
+		}
+		if(memcmp("wait", line, 5) == 0){
+			nowait=0;
+			continue;
+		}
+		if(memcmp("show", line, 5) == 0){
+			print(BOOTARGS);
+			continue;
+		}
+		if(memcmp("clear", line, 5) == 0){
+			if(line[5] == '\0'){
+				print("ok\n");
+				goto Clear;
+			} else if(line[5] == ' ' && delconf(line+6))
+				print("ok\n");
+			continue;
+		}
+		if(inblock != 0 || (p = strchr(line, '=')) == nil)
+			continue;
+		*p++ = 0;
+		delconf(line);
+		if(memcmp("apm", line, 3) == 0){
+			apmconf(line[3] - '0');
+			continue;
+		}
+		if(memcmp("console", line, 8) == 0)
+			uartconf(p);
+
+		s = confend;
+		memmove(confend, line, n = strlen(line)); confend += n;
+		*confend++ = '=';
+		memmove(confend, p, n = strlen(p)); confend += n;
+		*confend++ = '\n';
+		*confend = 0;
+		print(s);
+	}
+	kern = getconf("bootfile=", path);
+
+	if(f != nil){
+		close(f);
+		f = nil;
+
+		if(kern != nil && (nowait==0 || timeout(1000)))
+			goto Loop;
+	}
+
+	if(kern == nil){
+		print("no bootfile\n");
+		goto Loop;
+	}
+	while((p = strchr(kern, '!')) != nil)
+		kern = p+1;
+
+	return kern;
+}
+
+
+static void
+hexfmt(char *s, int i, ulong a)
+{
+	s += i;
+	while(i > 0){
+		*--s = hex[a&15];
+		a >>= 4;
+		i--;
+	}
+}
+
+static void
+addconfx(char *s, int w, ulong v)
+{
+	int n;
+
+	n = strlen(s);
+	memmove(confend, s, n);
+	hexfmt(confend+n, w, v);
+	confend += n+w;
+	*confend = 0;
+}
+
+static void
+apmconf(int id)
+{
+	uchar *a;
+	char *s;
+
+	a = (uchar*)CONFADDR;
+	memset(a, 0, 20);
+
+	apm(id);
+	if(memcmp(a, "APM", 4) != 0)
+		return;
+
+	s = confend;
+
+	addconfx("apm", 1, id);
+	addconfx("=ax=", 4, *((ushort*)(a+4)));
+	addconfx(" ebx=", 8, *((ulong*)(a+12)));
+	addconfx(" cx=", 4, *((ushort*)(a+6)));
+	addconfx(" dx=", 4, *((ushort*)(a+8)));
+	addconfx(" di=", 4, *((ushort*)(a+10)));
+	addconfx(" esi=", 8, *((ulong*)(a+16)));
+
+	*confend++ = '\n';
+	*confend = 0;
+
+	print(s);
+}
+
+static void
+e820conf(void)
+{
+	struct {
+		uvlong	base;
+		uvlong	len;
+		ulong	typ;
+		ulong	ext;
+	} e;
+	uvlong v;
+	ulong bx;
+	char *s;
+
+	bx=0;
+	s = confend;
+
+	do{
+		bx = e820(bx, &e);
+		if(e.len != 0 && (e.ext & 3) == 1){
+			if(confend == s){
+				/* single entry <= 1MB is useless */
+				if(bx == 0 && e.typ == 1 && e.len <= 0x100000)
+					break;
+				memmove(confend, "*e820=", 6);
+				confend += 6;
+			}
+			addconfx("", 1, e.typ);
+			v = e.base;
+			addconfx(" 0x", 8, v>>32);
+			addconfx("", 8, v&0xffffffff);
+			v += e.len;
+			addconfx(" 0x", 8, v>>32);
+			addconfx("", 8, v&0xffffffff);
+			*confend++ = ' ';
+		}
+	} while(bx);
+
+	if(confend == s)
+		return;
+
+	*confend++ = '\n';
+	*confend = 0;
+
+	print(s);
+}
+
+static int
+checksum(void *v, int n)
+{
+	uchar *p, s;
+
+	s = 0;
+	p = v;
+	while(n-- > 0)
+		s += *p++;
+	return s;
+}
+
+static void
+ramdiskconf(int id)
+{
+	struct {
+		/* ACPI header */
+		char	sig[4];
+		u32int	len;
+		uchar	revision;
+		uchar	csum;
+		char	oem_id[6];
+		char	oem_table_id[8];
+		u32int	oem_revision;
+		char	asl_compiler_id[4];
+		u32int	asl_compiler_revision;
+
+		u32int	safe_hook;
+
+		/* MDI structure */
+		u16int	bytes;
+		uchar	version_minor;
+		uchar	version_major;
+		u32int	diskbuf;
+		u32int	disksize;
+		u32int	cmdline;
+		u32int	oldint13;
+		u32int	oldint15;
+		u16int	olddosmem;
+		uchar	bootloaderid;
+		uchar	sector_shift;
+		u16int	dpt_ptr;
+	} *mbft;
+	int shift;
+	char *s;
+
+#define BDA	((uchar*)0x400)
+	mbft = (void*)((((BDA[0x14]<<8) | BDA[0x13])<<10) - 1024);
+	for(; (ulong)&mbft->sector_shift < 0xA0000; mbft = (void*)((ulong)mbft + 16)){
+		if(memcmp("mBFT", mbft, 4) == 0
+		&& mbft->len < 1024 && (uchar*)mbft + mbft->len > &mbft->sector_shift
+		&& checksum(mbft, mbft->len) == 0)
+			goto Found;
+	}
+	return;
+Found:
+	shift = mbft->sector_shift;
+	if(shift == 0)
+		shift = 9;
+
+	s = confend;
+	addconfx("ramdisk", 1, id);
+	addconfx("=0x", 8, mbft->diskbuf);
+	addconfx(" 0x", 8, mbft->disksize<<shift);
+	addconfx(" 0x", 8, 1UL<<shift);
+
+	*confend++ = '\n';
+	*confend = 0;
+
+	print(s);
+}
+
+static void
+uartconf(char *s)
+{
+	if(*s >= '0' && *s <= '3'){
+		uart = *s - '0';
+		uartinit(uart, (7<<5) | 3);	/* b9660 l8 s1 */
+	} else
+		uart = -1;
+}
+
+static ulong
+beswal(ulong l)
+{
+	uchar *p = (uchar*)&l;
+	return (p[0]<<24) | (p[1]<<16) | (p[2]<<8) | p[3];
+}
+
+char*
+bootkern(void *f)
+{
+	uchar *e, *d, *t;
+	ulong n;
+	Exec ex;
+
+	while(a20() < 0)
+		print("a20 enable failed\n");
+
+	if(readn(f, &ex, sizeof(ex)) != sizeof(ex))
+		return "bad header";
+
+	e = (uchar*)(beswal(ex.entry) & ~0xF0000000UL);
+	switch(beswal(ex.magic)){
+	case S_MAGIC:
+		if(readn(f, e, 8) != 8)
+			goto Error;
+	case I_MAGIC:
+		break;
+	default:
+		return "bad magic";
+	}
+
+	t = e;
+	n = beswal(ex.text);
+	if(readn(f, t, n) != n)
+		goto Error;
+	t += n;
+	d = (uchar*)PGROUND((ulong)t);
+	memset(t, 0, d - t);
+	n = beswal(ex.data);
+	if(readn(f, d, n) != n)
+		goto Error;
+	d += n;
+	t = (uchar*)PGROUND((ulong)d);
+	t += PGROUND(beswal(ex.bss));
+	memset(d, 0, t - d);
+
+	close(f);
+	unload();
+
+	print("boot\n");
+
+	jump(e);
+
+Error:		
+	return "i/o error";
+}
--- /dev/null
+++ b/os/boot/pc/uart.s
@@ -1,0 +1,35 @@
+#include "x16.h"
+
+TEXT uartinit(SB), $0
+	MOVL c+8(SP), AX
+	MOVB $0x00, AH
+	JMP _uartbios
+	
+TEXT uartputc(SB), $0
+	MOVL c+8(SP), AX
+	MOVB $0x01, AH
+	JMP _uartbios
+
+TEXT uartgetc(SB), $0
+	MOVL p+4(SP), DX
+	CALL rmode16(SB)
+	STI
+	MOVB $0x03, AH
+	BIOSCALL(0x14)
+	CALL16(pmode32(SB))
+	ANDL $0x8100, AX
+	MOVL $0x0100, BX
+	CMPL BX, AX
+	JE _uartread
+	XORL AX, AX
+	RET
+_uartread:
+	MOVB $0x02, AH
+_uartbios:
+	MOVL p+4(SP), DX
+	CALL rmode16(SB)
+	STI
+	BIOSCALL(0x14)
+	CALL16(pmode32(SB))
+	ANDL $0xFF, AX
+	RET
--- /dev/null
+++ b/os/boot/pc/x16.h
@@ -1,0 +1,163 @@
+/*
+ * Can't write 16-bit code for 8a without getting into
+ * lots of bother, so define some simple commands and
+ * output the code directly.
+ * 
+ * N.B. CALL16(x) kills DI, so don't expect it to be
+ * saved across calls.
+ */
+#define rAX		0		/* rX  */
+#define rCX		1
+#define rDX		2
+#define rBX		3
+#define rSP		4		/* SP */
+#define rBP		5		/* BP */
+#define rSI		6		/* SI */
+#define rDI		7		/* DI */
+
+#define rAL		0		/* rL  */
+#define rCL		1
+#define rDL		2
+#define rBL		3
+#define rAH		4		/* rH */
+#define rCH		5
+#define rDH		6
+#define rBH		7
+
+#define rES		0		/* rS */
+#define rCS		1
+#define rSS		2
+#define rDS		3
+#define rFS		4
+#define rGS		5
+
+#define xSI		4		/* rI (index) */
+#define xDI		5
+#define xBP		6
+#define xBX		7
+
+#define rCR0		0		/* rC */
+#define rCR2		2
+#define rCR3		3
+#define rCR4		4
+
+#define OP(o, m, ro, rm)	BYTE $o;	/* op + modr/m byte */	\
+			BYTE $(((m)<<6)|((ro)<<3)|(rm))
+#define OPrm(o, r, m)	OP(o, 0x00, r, 0x06);	/* general r <-> m */	\
+			WORD $m;
+#define OPrr(o, r0, r1)	OP(o, 0x03, r0, r1);	/* general r -> r */
+
+#define LW(m, rX)	OPrm(0x8B, rX, m)	/* m -> rX */
+#define LXW(x, rI, r)	OP(0x8B, 0x02, r, rI);	/* x(rI) -> r */	\
+			WORD $x
+#define LBPW(x, r)	OP(0x8B, 0x02, r, xBP);	/* x(rBP) -> r */	\
+			WORD $x
+#define LB(m, rB)	OPrm(0x8A, rB, m)	/* m -> r[HL] */
+#define LXB(x, rI, r)	OP(0x8A, 0x01, r, rI);	/* x(rI) -> r */	\
+			BYTE $x
+#define LBPB(x, r)	OP(0x8A, 0x01, r, xBP);	/* x(rBP) -> r */	\
+			BYTE $x
+#define SW(rX, m)	OPrm(0x89, rX, m)	/* rX -> m */
+#define SXW(r, x, rI)	OP(0x89, 0x02, r, rI);	/* r -> x(rI) */	\
+			WORD $x
+#define SBPW(r, x)	OP(0x89, 0x02, r, xBP);	/* r -> x(rBP) */	\
+			WORD $(x)
+#define SBPWI(i, x)	OP(0xC7, 0x01, 0, xBP);	/* i -> x(rBP) */	\
+			BYTE $(x); WORD $(i)
+#define STB(rB, m)	OPrm(0x88, rB, m)	/* rB -> m */
+#define SXB(r, x, rI)	OP(0x88, 0x01, r, rI);	/* rB -> x(rI) */	\
+			BYTE $x
+#define SBPB(r, x)	OP(0x88, 0x01, r, xBP);	/* r -> x(rBP) */	\
+			BYTE $x
+#define SBPBI(i, x)	OP(0xC6, 0x01, 0, xBP);	/* i -> x(rBP) */	\
+			BYTE $(x); BYTE $(i)
+#define LWI(i, rX)	BYTE $(0xB8+rX);	/* i -> rX */		\
+			WORD $i;
+#define LBI(i, rB)	BYTE $(0xB0+rB);	/* i -> r[HL] */	\
+			BYTE $i
+
+#define MW(r0, r1)	OPrr(0x89, r0, r1)	/* r0 -> r1 */
+#define MFSR(rS, rX)	OPrr(0x8C, rS, rX)	/* rS -> rX */
+#define MTSR(rX, rS)	OPrr(0x8E, rS, rX)	/* rX -> rS */
+#define MFCR(rC, rX)	BYTE $0x0F;		/* rC -> rX */		\
+			OP(0x20, 0x03, rC, rX)
+#define MTCR(rX, rC)	BYTE $0x0F;		/* rX -> rC */		\
+			OP(0x22, 0x03, rC, rX)
+
+#define ADC(r0, r1)	OPrr(0x11, r0, r1)	/* r0 + r1 -> r1 */
+#define ADD(r0, r1)	OPrr(0x01, r0, r1)	/* r0 + r1 -> r1 */
+#define ADDI(i, r)	OP(0x81, 0x03, 0x00, r);/* i+r -> r */		\
+			WORD $i;
+#define AND(r0, r1)	OPrr(0x21, r0, r1)	/* r0&r1 -> r1 */
+#define ANDI(i, r)	OP(0x81, 0x03, 0x04, r);/* i&r -> r */		\
+			WORD $i;
+#define CLR(r)		OPrr(0x31, r, r)	/* r^r -> r */
+#define CLRB(r)		OPrr(0x30, r, r)	/* r^r -> r */
+#define CMP(r0, r1)	OPrr(0x39, r0, r1)	/* r1-r0 -> flags */
+#define CMPI(i, r)	OP(0x81, 0x03, 0x07, r);/* r-i -> flags */	\
+			WORD $i;
+#define CMPBR(r0, r1)	OPrr(0x38, r0, r1)	/* r1-r0 -> flags */
+#define DEC(r)		BYTE $(0x48|r)		/* r-1 -> r */
+#define DIV(r)		OPrr(0xF7, 0x06, r)	/* rDX:rAX/r -> rAX, rDX:rAX%r -> rDX */
+#define INC(r)		BYTE $(0x40|r)		/* r+1 -> r */
+#define MUL(r)		OPrr(0xF7, 0x04, r)	/* r*rAX -> rDX:rAX */
+#define IMUL(r0, r1)	BYTE $0x0F;		/* r0*r1 -> r1 */	\
+			OPrr(0xAF, r1, r0)	/* (signed) */
+#define OR(r0, r1)	OPrr(0x09, r0, r1)	/* r0|r1 -> r1 */
+#define ORB(r0, r1)	OPrr(0x08, r0, r1)	/* r0|r1 -> r1 */
+#define ORI(i, r)	OP(0x81, 0x03, 0x01, r);/* i|r -> r */		\
+			WORD $i;
+#define ROLI(i, r)	OPrr(0xC1, 0x00, r);	/* r<<>>i -> r */	\
+			BYTE $i;
+#define SHLI(i, r)	OPrr(0xC1, 0x04, r);	/* r<<i -> r */		\
+			BYTE $i;
+#define SHLBI(i, r)	OPrr(0xC0, 0x04, r);	/* r<<i -> r */		\
+			BYTE $i;
+#define SHRI(i, r)	OPrr(0xC1, 0x05, r);	/* r>>i -> r */		\
+			BYTE $i;
+#define SHRBI(i, r)	OPrr(0xC0, 0x05, r);	/* r>>i -> r */		\
+			BYTE $i;
+#define SBB(r0, r1)	OPrr(0x19, r0, r1)	/* r1-r0 -> r1 */
+#define SUB(r0, r1)	OPrr(0x29, r0, r1)	/* r1-r0 -> r1 */
+#define SUBI(i, r)	OP(0x81, 0x03, 0x05, r);/* r-i -> r */		\
+			WORD $i;
+
+#define STOSW		STOSL
+
+#define CALL16(f)	LWI(f, rDI);		/* &f -> rDI */		\
+			BYTE $0xFF;		/* (*rDI) */		\
+			BYTE $0xD7;
+#define FARJUMP16(s, o)	BYTE $0xEA;		/* jump to ptr16:16 */	\
+			WORD $o; WORD $s
+#define FARJUMP32(s, o)	BYTE $0x66;		/* jump to ptr32:16 */	\
+			BYTE $0xEA; LONG $o; WORD $s
+#define	DELAY		BYTE $0xEB;		/* jmp .+2 */		\
+			BYTE $0x00
+#define BIOSCALL(b)	INT $b			/* INT $b */
+
+#define PEEKW		BYTE $0x26;		/* MOVW	rES:[rBX], rAX  */	\
+			BYTE $0x8B; BYTE $0x07
+#define POKEW		BYTE $0x26;		/* MOVW	rAX, rES:[rBX] */	\
+			BYTE $0x89; BYTE $0x07
+#define OUTPORTB(p, d)	LBI(d, rAL);		/* d -> I/O port p */	\
+			BYTE $0xE6;					\
+			BYTE $p; DELAY
+#define PUSHA		BYTE $0x60
+#define PUSHR(r)	BYTE $(0x50|r)		/* r  -> (--rSP) */
+#define PUSHS(rS)	BYTE $(0x06|((rS)<<3))	/* rS  -> (--rSP) */
+#define PUSHI(i)	BYTE $0x68; WORD $i;	/* i -> --(rSP) */
+#define POPA		BYTE $0x61
+#define POPR(r)		BYTE $(0x58|r)		/* (rSP++) -> r */
+#define POPS(rS)	BYTE $(0x07|((rS)<<3))	/* (rSP++) -> r */
+#define NOP		BYTE $0x90		/* nop */
+
+#define LGDT(gdtptr)	BYTE $0x0F;		/* LGDT */			\
+			BYTE $0x01; BYTE $0x16;					\
+			WORD $gdtptr
+#define LIDT(idtptr)	BYTE $0x0F;		/* LIDT */			\
+			BYTE $0x01; BYTE $0x1e;					\
+			WORD $idtptr
+
+/* operand size switch. */
+#define OPSIZE		BYTE $0x66
+#define ADSIZE		BYTE $0x67
--- /dev/null
+++ b/os/boot/zynq/boothead.c
@@ -1,0 +1,71 @@
+#include <u.h>
+#include <libc.h>
+
+char *data;
+uchar head[0x8c0];
+
+void
+usage(void)
+{
+	fprint(2, "usage: %s file\n", argv0);
+	exits("usage");
+}
+
+void
+u32(int n, u32int p)
+{
+	head[n] = p;
+	head[n+1] = p >> 8;
+	head[n+2] = p >> 16;
+	head[n+3] = p >> 24;
+}
+
+u32int
+gu32(int n)
+{
+	return head[n] | head[n+1] << 8 | head[n+2] << 16 | head[n+3] << 24;
+}
+
+void
+main(int argc, char **argv)
+{
+	int fd, sz, i;
+	u32int ck;
+
+	ARGBEGIN {
+	default:
+		usage();
+	} ARGEND;
+
+	if(argc != 1)
+		usage();
+	fd = open(argv[0], OREAD);
+	if(fd < 0)
+		sysfatal("open: %r");
+	sz = seek(fd, 0, 2);
+	if(sz < 0)
+		sysfatal("seek: %r");
+	data = malloc(sz);
+	if(data == nil)
+		sysfatal("malloc: %r");
+	seek(fd, 0, 0);
+	if(readn(fd, data, sz) < sz)
+		sysfatal("read: %r");
+	close(fd);
+	memset(head, 0, sizeof(head));
+	
+	u32(0x20, 0xaa995566);
+	u32(0x24, 0x584C4E58);
+	u32(0x30, sizeof(head));
+	u32(0x34, sz);
+	u32(0x40, sz);
+	ck = 0;
+	for(i = 0x20; i < 0x48; i += 4)
+		ck += gu32(i);
+	u32(0x48, ~ck);
+	u32(0xa0, -1);
+	
+	write(1, head, sizeof(head));
+	write(1, data, sz);
+	exits(nil);
+}
--- /dev/null
+++ b/os/boot/zynq/dat.h
@@ -1,0 +1,11 @@
+enum {
+	DHCPTIMEOUT = 2000,
+	ARPTIMEOUT = 1000,
+	TFTPTIMEOUT = 10000,
+	
+	TZERO = 0x80000,
+	CONFSIZE = 65536,
+	CONF = TZERO - CONFSIZE,
+};
+
+#define nelem(x) (sizeof(x)/sizeof(*(x)))
--- /dev/null
+++ b/os/boot/zynq/ddr.s
@@ -1,0 +1,258 @@
+#define OUTPUT_EN (3<<9)
+#define DCI_EN (7<<4)
+#define INP_VREF (1<<1)
+#define INP_DIFF (2<<1)
+
+TEXT ddriob(SB), $-4
+	WORD $(OUTPUT_EN) // DDRIOB_ADDR0
+	WORD $(OUTPUT_EN) // DDRIOB_ADDR1
+	WORD $(OUTPUT_EN | DCI_EN | INP_VREF) // DDRIOB_DATA0
+	WORD $(OUTPUT_EN | DCI_EN | INP_VREF) // DDRIOB_DATA1
+	WORD $(OUTPUT_EN | DCI_EN | INP_DIFF) // DDRIOB_DIFF0
+	WORD $(OUTPUT_EN | DCI_EN | INP_DIFF) // DDRIOB_DIFF1
+	WORD $(OUTPUT_EN) // DDRIOB_CLOCK
+	WORD $0x0018C61C // DDRIOB_DRIVE_SLEW_ADDR
+	WORD $0x00F9861C // DDRIOB_DRIVE_SLEW_DATA
+	WORD $0x00F9861C // DDRIOB_DRIVE_SLEW_DIFF
+	WORD $0x00F9861C // DDRIOB_DRIVE_SLEW_CLOCK
+	WORD $0xE60 // DDRIOB_DDR_CTRL
+
+TEXT ddrdata(SB), $-4
+	WORD $0XF8006000
+	WORD $0x0001FFFF
+	WORD $0x00000080
+	WORD $0XF8006004
+	WORD $0x1FFFFFFF
+	WORD $0x00081081
+	WORD $0XF8006008
+	WORD $0x03FFFFFF
+	WORD $0x03C0780F
+	WORD $0XF800600C
+	WORD $0x03FFFFFF
+	WORD $0x02001001
+	WORD $0XF8006010
+	WORD $0x03FFFFFF
+	WORD $0x00014001
+	WORD $0XF8006014
+	WORD $0x001FFFFF
+	WORD $0x0004281A
+	WORD $0XF8006018
+	WORD $0xF7FFFFFF
+	WORD $0x44E458D2
+	WORD $0XF800601C
+	WORD $0xFFFFFFFF
+	WORD $0x82023965
+	WORD $0XF8006020
+	WORD $0xFFFFFFFC
+	WORD $0x2B288290
+	WORD $0XF8006024
+	WORD $0x0FFFFFFF
+	WORD $0x0000003C
+	WORD $0XF8006028
+	WORD $0x00003FFF
+	WORD $0x00002007
+	WORD $0XF800602C
+	WORD $0xFFFFFFFF
+	WORD $0x00000008
+	WORD $0XF8006030
+	WORD $0xFFFFFFFF
+	WORD $0x00040970
+	WORD $0XF8006034
+	WORD $0x13FF3FFF
+	WORD $0x00011054
+	WORD $0XF8006038
+	WORD $0x00001FC3
+	WORD $0x00000000
+	WORD $0XF800603C
+	WORD $0x000FFFFF
+	WORD $0x00000777
+	WORD $0XF8006040
+	WORD $0xFFFFFFFF
+	WORD $0xFFF00000
+	WORD $0XF8006044
+	WORD $0x0FFFFFFF
+	WORD $0x0F666666
+	WORD $0XF8006048
+	WORD $0x3FFFFFFF
+	WORD $0x0003C248
+	WORD $0XF8006050
+	WORD $0xFF0F8FFF
+	WORD $0x77010800
+	WORD $0XF8006058
+	WORD $0x0001FFFF
+	WORD $0x00000101
+	WORD $0XF800605C
+	WORD $0x0000FFFF
+	WORD $0x00005003
+	WORD $0XF8006060
+	WORD $0x000017FF
+	WORD $0x0000003E
+	WORD $0XF8006064
+	WORD $0x00021FE0
+	WORD $0x00020000
+	WORD $0XF8006068
+	WORD $0x03FFFFFF
+	WORD $0x00284545
+	WORD $0XF800606C
+	WORD $0x0000FFFF
+	WORD $0x00001610
+	WORD $0XF80060A0
+	WORD $0x00FFFFFF
+	WORD $0x00008000
+	WORD $0XF80060A4
+	WORD $0xFFFFFFFF
+	WORD $0x10200802
+	WORD $0XF80060A8
+	WORD $0x0FFFFFFF
+	WORD $0x0690CB73
+	WORD $0XF80060AC
+	WORD $0x000001FF
+	WORD $0x000001FE
+	WORD $0XF80060B0
+	WORD $0x1FFFFFFF
+	WORD $0x04FFFFFF
+	WORD $0XF80060B4
+	WORD $0x000007FF
+	WORD $0x00000200
+	WORD $0XF80060B8
+	WORD $0x01FFFFFF
+	WORD $0x0020006A
+	WORD $0XF80060C4
+	WORD $0x00000003
+	WORD $0x00000003
+	WORD $0XF80060C4
+	WORD $0x00000003
+	WORD $0x00000000
+	WORD $0XF80060C8
+	WORD $0x000000FF
+	WORD $0x00000000
+	WORD $0XF80060DC
+	WORD $0x00000001
+	WORD $0x00000000
+	WORD $0XF80060F0
+	WORD $0x0000FFFF
+	WORD $0x00000000
+	WORD $0XF80060F4
+	WORD $0x0000000F
+	WORD $0x00000008
+	WORD $0XF8006114
+	WORD $0x000000FF
+	WORD $0x00000000
+	WORD $0XF8006118
+	WORD $0x7FFFFFFF
+	WORD $0x40000001
+	WORD $0XF800611C
+	WORD $0x7FFFFFFF
+	WORD $0x40000001
+	WORD $0XF8006120
+	WORD $0x7FFFFFFF
+	WORD $0x40000001
+	WORD $0XF8006124
+	WORD $0x7FFFFFFF
+	WORD $0x40000001
+	WORD $0XF800612C
+	WORD $0x000FFFFF
+	WORD $0x00000000
+	WORD $0XF8006130
+	WORD $0x000FFFFF
+	WORD $0x00000000
+	WORD $0XF8006134
+	WORD $0x000FFFFF
+	WORD $0x00000000
+	WORD $0XF8006138
+	WORD $0x000FFFFF
+	WORD $0x00000000
+	WORD $0XF8006140
+	WORD $0x000FFFFF
+	WORD $0x00000035
+	WORD $0XF8006144
+	WORD $0x000FFFFF
+	WORD $0x00000035
+	WORD $0XF8006148
+	WORD $0x000FFFFF
+	WORD $0x00000035
+	WORD $0XF800614C
+	WORD $0x000FFFFF
+	WORD $0x00000035
+	WORD $0XF8006154
+	WORD $0x000FFFFF
+	WORD $0x00000080
+	WORD $0XF8006158
+	WORD $0x000FFFFF
+	WORD $0x00000080
+	WORD $0XF800615C
+	WORD $0x000FFFFF
+	WORD $0x00000080
+	WORD $0XF8006160
+	WORD $0x000FFFFF
+	WORD $0x00000075
+	WORD $0XF8006168
+	WORD $0x001FFFFF
+	WORD $0x000000EE
+	WORD $0XF800616C
+	WORD $0x001FFFFF
+	WORD $0x000000E4
+	WORD $0XF8006170
+	WORD $0x001FFFFF
+	WORD $0x000000FC
+	WORD $0XF8006174
+	WORD $0x001FFFFF
+	WORD $0x000000F4
+	WORD $0XF800617C
+	WORD $0x000FFFFF
+	WORD $0x000000C0
+	WORD $0XF8006180
+	WORD $0x000FFFFF
+	WORD $0x000000C0
+	WORD $0XF8006184
+	WORD $0x000FFFFF
+	WORD $0x000000C0
+	WORD $0XF8006188
+	WORD $0x000FFFFF
+	WORD $0x000000B5
+	WORD $0XF8006190
+	WORD $0xFFFFFFFF
+	WORD $0x10040080
+	WORD $0XF8006194
+	WORD $0x000FFFFF
+	WORD $0x00007D02
+	WORD $0XF8006204
+	WORD $0xFFFFFFFF
+	WORD $0x00000000
+	WORD $0XF8006208
+	WORD $0x000F03FF
+	WORD $0x000803FF
+	WORD $0XF800620C
+	WORD $0x000F03FF
+	WORD $0x000803FF
+	WORD $0XF8006210
+	WORD $0x000F03FF
+	WORD $0x000803FF
+	WORD $0XF8006214
+	WORD $0x000F03FF
+	WORD $0x000803FF
+	WORD $0XF8006218
+	WORD $0x000F03FF
+	WORD $0x000003FF
+	WORD $0XF800621C
+	WORD $0x000F03FF
+	WORD $0x000003FF
+	WORD $0XF8006220
+	WORD $0x000F03FF
+	WORD $0x000003FF
+	WORD $0XF8006224
+	WORD $0x000F03FF
+	WORD $0x000003FF
+	WORD $0XF80062A8
+	WORD $0x00000FF7
+	WORD $0x00000000
+	WORD $0XF80062AC
+	WORD $0xFFFFFFFF
+	WORD $0x00000000
+	WORD $0XF80062B0
+	WORD $0x003FFFFF
+	WORD $0x00005125
+	WORD $0xF80062B4
+	WORD $0x003FFFFF
+	WORD $0x000012A8
+	WORD $0
--- /dev/null
+++ b/os/boot/zynq/fns.h
@@ -1,0 +1,15 @@
+void	putc(int);
+void	puts(char *);
+int	netboot(void);
+int	mmcboot(void);
+void	puthex(u32int);
+void	memset(void *, char, int);
+void	memcpy(void *, void *, int);
+void	print(char *, ...);
+u32int	u32get(void *);
+uchar*	u32put(uchar *, u32int);
+void	jump(void *);
+void	sleep(int);
+void	timeren(int);
+int	timertrig(void);
+void	flash(void);
--- /dev/null
+++ b/os/boot/zynq/fsbl.s
@@ -1,0 +1,310 @@
+#include "mem.h"
+
+#define Rb R10
+#define SET(R, V) MOVW $(V), R0 ; MOVW R0, (R)(Rb)
+#define RMW(r, m, v) MOVW (r)(Rb), R0; BIC $(m), R0; ORR $(v), R0; MOVW R0, (r)(Rb)
+
+TEXT _start(SB), $-4
+	WORD $0xea000006
+	MOVW $abort(SB), R15
+	MOVW $abort(SB), R15
+	MOVW $abort(SB), R15
+	MOVW $abort(SB), R15
+	MOVW $abort(SB), R15
+	MOVW $abort(SB), R15
+	MOVW $abort(SB), R15
+
+TEXT reloc(SB), $-4
+	MOVW $(1<<7|1<<6|0x13), R0
+	MOVW R0, CPSR
+	MOVW $STACKTOP, R13
+	MOVW $_start(SB), R0
+	MCR CpMMU, 0, R0, C(12), C(0)
+	MOVW $SLCR_BASE, Rb
+	SET(SLCR_UNLOCK, UNLOCK_KEY)
+	MOVW $0, R0
+	MCR 15, 0, R0, C(8), C(7), 0
+	MCR 15, 0, R0, C(7), C(5), 0
+	MCR 15, 0, R0, C(7), C(5), 6
+	MOVW $0xc5047a, R1
+	MCR 15, 0, R1, C(1), C(0), 0
+	DSB
+	ISB
+	CMP.S $0, R15
+	BL.LT reset(SB)
+	
+	MOVW $0xf, R1
+	MOVW $0xffff0000, R3
+	MOVW $0xe58a1910, R0
+	MOVW R0, (R3)
+	MOVW $0xf57ff04f, R0
+	MOVW R0, 4(R3)
+	MOVW $0xf57ff06f, R0
+	MOVW R0, 8(R3)
+	MOVW $0xe28ef000, R0
+	MOVW R0, 12(R3)
+	MOVW $reset(SB), R14
+	DSB
+	ISB
+	MOVW R3, R15
+
+TEXT reset(SB), $-4
+	BL pllsetup(SB)
+	BL miosetup(SB)
+	BL ddrsetup(SB)
+	BL uartsetup(SB)
+	MOVW $SLCR_BASE, Rb
+	SET(SLCR_LOCK, LOCK_KEY)
+//	BL memtest(SB)
+	MOVW $setR12(SB), R12
+	BL main(SB)
+	B abort(SB)
+
+TEXT pllsetup(SB), $0
+	MOVW $SLCR_BASE, Rb
+	
+	SET(ARM_PLL_CFG, ARM_PLL_CFG_VAL)
+	SET(DDR_PLL_CFG, DDR_PLL_CFG_VAL)
+	SET(IO_PLL_CFG, IO_PLL_CFG_VAL)
+
+	MOVW $(ARM_FDIV | PLL_BYPASS_FORCE), R0
+	MOVW R0, ARM_PLL_CTRL(Rb)
+	ORR $(PLL_RESET), R4
+	MOVW R4, ARM_PLL_CTRL(Rb)
+	MOVW R0, ARM_PLL_CTRL(Rb)
+
+	MOVW $(DDR_FDIV | PLL_BYPASS_FORCE), R0
+	MOVW R0, DDR_PLL_CTRL(Rb)
+	ORR $(PLL_RESET), R4
+	MOVW R4, DDR_PLL_CTRL(Rb)
+	MOVW R0, DDR_PLL_CTRL(Rb)
+
+	MOVW $(IO_FDIV | PLL_BYPASS_FORCE), R0
+	MOVW R0, IO_PLL_CTRL(Rb)
+	ORR $(PLL_RESET), R4
+	MOVW R4, IO_PLL_CTRL(Rb)
+	MOVW R0, IO_PLL_CTRL(Rb)
+
+_pllsetupl:
+	MOVW PLL_STATUS(Rb), R0
+	AND $7, R0
+	CMP.S $7, R0
+	BNE _pllsetupl
+	
+	SET(ARM_PLL_CTRL, ARM_FDIV)
+	SET(DDR_PLL_CTRL, DDR_FDIV)
+	SET(IO_PLL_CTRL, IO_FDIV)
+	
+	SET(ARM_CLK_CTRL, 0x1f << 24 | CPU_DIV << 8)
+	SET(UART_CLK_CTRL, UART_DIV << 8 | 3)
+	SET(DDR_CLK_CTRL, DDR_DIV3 << 20 | DDR_DIV2 << 26 | 3)
+	SET(DCI_CLK_CTRL, DCI_DIV0 << 8 | DCI_DIV1 << 20 | 1)
+	SET(GEM0_RCLK_CTRL, 1)
+	SET(GEM1_RCLK_CTRL, 0)
+	SET(GEM0_CLK_CTRL, ETH_DIV0 << 8 | ETH_DIV1 << 20 | 1)
+	SET(GEM1_CLK_CTRL, 0)
+	SET(GPIOB_CTRL, VREF_SW_EN)
+	SET(APER_CLK_CTRL, LQSPI_CLK_EN | GPIO_CLK_EN | UART0_CLK_EN | UART1_CLK_EN | I2C0_CLK_EN | SDIO1_CLK_EN | GEM0_CLK_EN | USB0_CLK_EN | USB1_CLK_EN | DMA_CLK_EN)
+	SET(SMC_CLK_CTRL, 0x3C20)
+	SET(LQSPI_CLK_CTRL, QSPI_DIV << 8 | 1)
+	SET(SDIO_CLK_CTRL, SDIO_DIV << 8 | 2)
+	SET(SPI_CLK_CTRL, 0x3F00)
+	SET(CAN_CLK_CTRL, 0x501900)
+	SET(PCAP_CLK_CTRL, PCAP_DIV << 8 | 1)
+	RET
+
+TEXT miosetup(SB), $0
+	MOVW $SLCR_BASE, Rb
+	SET(UART_RST_CTRL, 0xf)
+	SET(UART_RST_CTRL, 0)
+
+	MOVW $miodata(SB), R1
+	ADD $MIO_PIN_0, Rb, R2
+	MOVW $54, R3
+	BL copy(SB)
+
+	MOVW $0, R0
+	MOVW R0, MIO_MST_TRI0(Rb)
+	MOVW R0, MIO_MST_TRI1(Rb)
+	RET
+
+TEXT copy(SB), $0
+_copyl:
+	MOVW.P 4(R1), R0
+	MOVW.P R0, 4(R2)
+	SUB.S $1, R3
+	BNE _copyl
+	RET
+
+TEXT ddrsetup(SB), $0
+	MOVW $SLCR_BASE, Rb
+	RMW(DDRIOB_DCI_CTRL, DCI_RESET, DCI_RESET)
+	RMW(DDRIOB_DCI_CTRL, DCI_RESET, 0)
+	RMW(DDRIOB_DCI_CTRL, DDRIOB_DCI_CTRL_MASK, DCI_NREF | DCI_ENABLE | DCI_RESET)
+
+	MOVW $ddriob(SB), R1
+	ADD $DDRIOB_ADDR0, Rb, R2
+	MOVW $12, R3
+	BL copy(SB)
+
+	MOVW $ddrdata(SB), R1
+_ddrl1:
+	MOVW.P 4(R1), R2
+	ORR.S $0, R2
+	BEQ _ddrl2
+	MOVW.P 4(R1), R3
+	MOVW.P 4(R1), R4
+	AND R3, R4
+	MOVW (R2), R0
+	BIC R3, R0
+	ORR R4, R0
+	MOVW R0, (R2)
+	B _ddrl1
+_ddrl2:
+	MOVW DDRIOB_DCI_STATUS(Rb), R0
+	AND.S $(1<<13), R0
+	BEQ _ddrl2
+	MOVW $DDR_BASE, Rb
+	RMW(DDRC_CTRL, 0x1ffff, 0x81)
+_ddrl4:
+	MOVW DDR_MODE_STS(Rb), R0
+	AND.S $7, R0
+	BEQ _ddrl4
+	
+	MOVW $MP_BASE, Rb
+	SET(FILTER_START, 0)
+	RET
+
+TEXT memtest(SB), $0
+	MOVW $0, R0
+	ADD $(1024 * 1024 * 10), R0, R1
+_testl:
+	MOVW R0, (R0)
+	ADD $4, R0
+	CMP.S R0, R1
+	BNE _testl
+	MOVW $0, R0
+_testl2:
+	MOVW (R0), R2
+	CMP.S R0, R2
+	BNE _no
+	ADD $4, R0
+	CMP.S R0, R1
+	BNE _testl2
+	MOVW $'.', R0
+	BL putc(SB)
+	RET
+_no:
+	MOVW $'!', R0
+	BL putc(SB)
+	RET
+
+TEXT uartsetup(SB), $0
+	MOVW $UART1_BASE, Rb
+	SET(UART_CTRL, 0x17)
+	SET(UART_MODE, 0x20)
+	SET(UART_SAMP, 15)
+	SET(UART_BAUD, 14)
+	RET
+
+TEXT putc(SB), $0
+	MOVW $UART1_BASE, Rb
+	CMP.S $10, R0
+	BNE _putcl
+	MOVW R0, R2
+	MOVW $13, R0
+	BL putc(SB)
+	MOVW R2, R0
+_putcl:
+	MOVW UART_STAT(Rb), R1
+	AND.S $0x10, R1
+	BNE _putcl
+	AND $0xFF, R0
+	MOVW R0, UART_DATA(Rb)
+	RET
+	
+TEXT jump(SB), $-4
+	MOVW R0, R15
+
+TEXT abort(SB), $0
+	MOVW $'?', R0
+	BL putc(SB)
+_loop:
+	WFE
+	B _loop
+
+#define TRI 1
+#define LVCMOS18 (1<<9)
+#define LVCMOS25 (2<<9)
+#define LVCMOS33 (3<<9)
+#define HSTL (4<<9)
+#define PULLUP (1<<12)
+#define NORECV (1<<13)
+#define FAST (1<<8)
+#define MUX(a, b, c, d) ((a)<<1 | (b)<<2 | (c)<<3 | (d)<<5)
+
+#define NO (TRI | LVCMOS33)
+#define SPI (MUX(1, 0, 0, 0) | LVCMOS33)
+#define UART (MUX(0, 0, 0, 7) | LVCMOS33)
+#define SD (MUX(0, 0, 0, 4) | LVCMOS33)
+#define ETX (MUX(1, 0, 0, 0) | HSTL | NORECV | PULLUP)
+#define ERX (MUX(1, 0, 0, 0) | HSTL | TRI | PULLUP)
+#define USB (MUX(0, 1, 0, 0) | LVCMOS18)
+#define MDCLK (MUX(0, 0, 0, 4) | HSTL)
+#define MDDATA (MUX(0, 0, 0, 4) | HSTL)
+
+TEXT miodata(SB), $-4
+	WORD $NO // 0
+	WORD $SPI // 1 
+	WORD $SPI // 2 
+	WORD $SPI // 3
+	WORD $SPI // 4
+	WORD $SPI // 5
+	WORD $SPI // 6
+	WORD $NO // 7
+	WORD $UART // 8
+	WORD $(UART|TRI) // 9
+	WORD $SD // 10
+	WORD $SD // 11
+	WORD $SD // 12
+	WORD $SD // 13
+	WORD $SD // 14
+	WORD $SD // 15
+	WORD $ETX // 16
+	WORD $ETX // 17
+	WORD $ETX // 18
+	WORD $ETX // 19
+	WORD $ETX // 20
+	WORD $ETX // 21
+	WORD $ERX // 22
+	WORD $ERX // 23
+	WORD $ERX // 24
+	WORD $ERX // 25
+	WORD $ERX // 26
+	WORD $ERX // 27
+	WORD $USB // 28
+	WORD $USB // 29
+	WORD $USB // 30
+	WORD $USB // 31
+	WORD $USB // 32
+	WORD $USB // 33
+	WORD $USB // 34
+	WORD $USB // 35
+	WORD $USB // 36
+	WORD $USB // 37
+	WORD $USB // 38
+	WORD $USB // 39
+	WORD $USB // 40
+	WORD $USB // 41
+	WORD $USB // 42
+	WORD $USB // 43
+	WORD $USB // 44
+	WORD $USB // 45
+	WORD $USB // 46
+	WORD $USB // 47
+	WORD $USB // 48
+	WORD $USB // 49
+	WORD $USB // 50
+	WORD $USB // 51
+	WORD $MDCLK // 52
+	WORD $MDDATA // 53
--- /dev/null
+++ b/os/boot/zynq/jtagload.c
@@ -1,0 +1,647 @@
+#include <u.h>
+#include <libc.h>
+
+typedef struct Tap Tap;
+typedef struct Dap Dap;
+
+struct Tap
+{
+	int	off;
+	int	len;
+	int	delay;
+
+	u32int	id;
+	u32int	dapsel;
+};
+
+struct Dap
+{
+	Tap	*tap;
+
+	uint	port;
+	u32int	id;
+};
+
+int	dfd = -1;
+int	lastbit = -1;
+
+int	irlen;
+
+int	ntaps;
+Tap*	taps;
+
+int	ndaps;
+Dap*	daps;
+
+Dap*	ahbap;
+Dap*	apbap;
+
+/* MPSSE command bits */
+enum {
+	FEW		=	1<<0,	/* -ve CLK on write */
+	BITS		=	1<<1,	/* bits or bytes */
+	FER		=	1<<2,	/* -ve CLK on read */
+	LSB		=	1<<3,	/* LSB first = 1 else MSB first */
+	TDI		=	1<<4,	/* do write TDI */
+	TDO		=	1<<5,	/* do read TDO */
+	TMS		=	1<<6,	/* do write TMS */
+};
+
+void
+ioinit(char *dev)
+{
+	uchar b[3];
+
+	dfd = open(dev, ORDWR);
+	if(dfd < 0)
+		sysfatal("open: %r");
+
+	b[0] = 0x80;
+	b[1] = 0x08;
+	b[2] = 0x0B;
+	write(dfd, b, 3);
+}
+
+void
+io(int cmd, int len, uchar *dat)
+{
+	uchar buf[64];
+	uchar *p = buf;
+
+	*p++ = cmd;
+	*p++ = len-1;
+	if((cmd & BITS) != 0)
+		len = 1;
+	else
+		*p++ = (len-1)>>8;
+	if((cmd & (TDI|TMS)) != 0){
+		memmove(p, dat, len);
+		p += len;
+	}
+	if(write(dfd, buf, p - buf) != (p - buf))
+		sysfatal("io write: %r");
+	if((cmd & TDO) != 0)
+		if(readn(dfd, dat, len) != len)
+			sysfatal("io read: %r");
+}
+
+void
+dstate(u32int s, int len)
+{
+	uchar b[1];
+
+	assert(len < 8);
+	b[0] = s;
+	if(lastbit != -1){
+		b[0] |= lastbit << 7;
+		lastbit = -1;
+	}
+	io(TMS|LSB|BITS|FEW, len, b);
+}
+uvlong
+dshift(uvlong w, int len)
+{
+	uchar b[8];
+	int c, s, n;
+
+	c = TDI|LSB|FEW;
+	if(len < 0){
+		len = -len;
+		c |= TDO;
+	}
+	s = 0;
+	n = len/8;
+	if(n > 0) {
+		switch(n){
+		case 8:	b[7] = w >> 56;
+		case 7:	b[6] = w >> 48;
+		case 6:	b[5] = w >> 40;
+		case 5:	b[4] = w >> 32;
+		case 4:	b[3] = w >> 24;
+		case 3:	b[2] = w >> 16;
+		case 2:	b[1] = w >> 8;
+		case 1:	b[0] = w >> 0;
+		}
+		io(c, n, b);
+		s = n*8;
+		if((c & TDO) != 0){
+			w &= ~((1ULL<<s)-1);
+			switch(n){
+			case 8:	w |= (uvlong)b[7] << 56;
+			case 7:	w |= (uvlong)b[6] << 48;
+			case 6:	w |= (uvlong)b[5] << 40;
+			case 5:	w |= (uvlong)b[4] << 32;
+			case 4:	w |= (uvlong)b[3] << 24;
+			case 3:	w |= (uvlong)b[2] << 16;
+			case 2:	w |= (uvlong)b[1] << 8;
+			case 1:	w |= (uvlong)b[0] << 0;
+			}
+		}
+		len -= s;
+	}
+	if(len > 0){
+		b[0] = w >> s;
+		c |= BITS;
+		io(c, len, b);
+		if((c & TDO) != 0){
+			w &= ~((uvlong)((1<<len)-1) << s);
+			w |= (uvlong)(b[0] >> 8-len) << s;
+		}
+		s += len;
+	}
+	return w & (1ULL<<s)-1;
+}
+void
+dshiftones(int len)
+{
+	while(len >= 64){
+		dshift(~0ULL, 64);
+		len -= 64;
+	}
+	dshift(~0ULL, len);
+}
+int
+dshiftdelay(void)
+{
+	int i;
+
+	/* send ones */
+	dshiftones(512);
+	for(i=0; i<512; i++){
+		if(dshift(i != 0, -1) == 0)
+			return i;
+	}
+	return 0;
+}
+
+void
+irw(Tap *tap, uvlong w)
+{
+	/* 0011 -> Shift-IR */
+	dstate(0x3, 4);
+
+	dshiftones(tap->off);
+	if((tap->off + tap->len) == irlen){
+		dshift(w, tap->len-1);
+		lastbit = w >> (tap->len-1);
+	} else {
+		dshift(w, tap->len);
+		dshiftones(irlen - (tap->off + tap->len-1));
+		lastbit = 1;
+	}
+
+	/* 011 -> Idle */
+	dstate(0x3, 3);
+}
+uvlong
+drr(Tap *tap, int len)
+{
+	uvlong w, d;
+
+	/* 001 -> Shift-DR */
+	dstate(0x1, 3);
+
+	d = dshift(0, -tap->delay);
+	w = dshift(0, -len);
+	dshift(d, tap->delay);
+	dshift(w, len-1);
+	lastbit = (w >> len-1) & 1;
+
+	/* 011 -> Idle */
+	dstate(0x3, 3);
+
+	return w;
+}
+void
+drw(Tap *tap, uvlong w, int len)
+{
+	/* 001 -> Shift-DR */
+	dstate(0x1, 3);
+
+	dshift(0, tap->delay);
+	dshift(w, len-1);
+	lastbit = (w >> len-1) & 1;
+
+	/* 011 -> Idle */
+	dstate(0x3, 3);
+}
+
+enum {
+	ABORT	= 0x8,
+	DPACC	= 0xA,
+	APACC	= 0xB,
+		CTRLSTAT	= 0x4,
+		SELECT		= 0x8,
+		RDBUF		= 0xC,
+};
+
+u32int
+dapr(Dap *dap, uchar r, uchar a)
+{
+	uvlong w;
+
+	irw(dap->tap, r);
+	w = 1 | (a >> 1) & 0x6;
+	drw(dap->tap, w, 35);
+	do {
+		w = drr(dap->tap, 35);
+	} while((w & 7) == 1);
+	return w >> 3;
+}
+void
+dapw(Dap *dap, uchar r, uchar a, u32int v)
+{
+	uvlong w;
+
+	irw(dap->tap, r);
+	w = (a >> 1) & 0x6;
+	w |= (uvlong)v << 3;
+	drw(dap->tap, w, 35);
+}
+
+void
+app(Dap *dap)
+{
+	enum {
+	CSYSPWRUPACK	= 1<<31,
+	CSYSPWRUPREQ	= 1<<30,
+	CDBGPWRUPACK	= 1<<29,
+	CDBGPWRUPREQ	= 1<<28,
+	CDBGRSTACK	= 1<<27,
+	CDBGRSTREQ	= 1<<26,
+	};
+	u32int s;
+
+	for(;;){
+		s = dapr(dap, DPACC, CTRLSTAT);
+		if((s & (CDBGPWRUPACK|CSYSPWRUPACK)) == (CDBGPWRUPACK|CSYSPWRUPACK))
+			break;
+		s |= CSYSPWRUPREQ|CDBGPWRUPREQ;
+		dapw(dap, DPACC, CTRLSTAT, s);
+	}
+}
+void
+apa(Dap *dap, uchar a)
+{
+	u32int s;
+
+	s = dap->port<<24 | a&0xf0;
+	if(s != dap->tap->dapsel){
+		dap->tap->dapsel = s;
+		dapw(dap, DPACC, SELECT, s);
+		app(dap);
+	}
+}
+u32int
+apr(Dap *dap, uchar a)
+{
+	apa(dap, a);
+	return dapr(dap, APACC, a&0xC);
+}
+void
+apw(Dap *dap, uchar a, u32int v)
+{
+	apa(dap, a);
+	dapw(dap, APACC, a&0xC, v);
+}
+u32int
+mmr(Dap *ap, u32int addr)
+{
+	apw(ap, 0x4, addr);
+	return apr(ap, 0xC);
+}
+void
+mmw(Dap *ap, u32int addr, u32int val)
+{
+	apw(ap, 0x4, addr);
+	apw(ap, 0xC, val);
+}
+
+void
+tapreset(void)
+{
+	int i, j, o;
+
+	dstate(0x1F, 6);	/* 011111 -> Reset->Idle */
+	dstate(0x3, 4);		/*   0011 -> Shift-IR */
+
+	irlen = dshiftdelay();
+	lastbit = 1;
+	
+	dstate(0x7, 5);		/*  00111 -> Shift-IR->Shift-DR */
+
+	ntaps = dshiftdelay();
+
+	dstate(0x1F, 6);	/* 011111 -> Reset->Idle */
+	dstate(0x1, 3);		/*    001 -> Shift-DR */
+
+	taps = realloc(taps, sizeof(taps[0]) * ntaps);
+
+	o = 0;
+	for(i=ntaps-1; i>=0; i--){
+		taps[i].delay = ntaps - i - 1;
+		taps[i].off = o;
+		taps[i].id = dshift(0, -32);
+		switch(taps[i].id){
+		default:
+			sysfatal("unknown tapid %.8ux\n", taps[i].id);
+		case 0x03727093:
+		case 0x0373b093:
+		case 0x23727093:
+			taps[i].len = 6;
+			break;
+		case 0x4ba00477:
+			taps[i].len = 4;
+			break;
+		}
+		o += taps[i].len;
+	}
+
+	dstate(0x1F, 6);	/* 011111 -> Reset->Idle */
+
+	if(o != irlen)
+		sysfatal("wrong tapchain irlen %d %d\n", o, irlen);
+
+	ndaps = 0;
+	for(i=0; i<ntaps; i++){
+		fprint(2, "tap%d: id=%.8ux off=%d len=%d delay=%d\n",
+			i, taps[i].id, taps[i].off, taps[i].len, taps[i].delay);
+
+		switch(taps[i].id){
+		case 0x4ba00477:
+			o = 3;
+			daps = realloc(daps, sizeof(daps[0]) * (ndaps+o));
+			for(j=0; j<o; j++){
+				daps[ndaps].tap = taps+i;
+				daps[ndaps].port = j;
+				daps[ndaps].id = apr(daps+ndaps, 0xFC);
+				fprint(2, "\tdap%d: id=%.8ux\n", j, daps[ndaps].id);
+
+				ndaps++;
+			}
+			break;
+		}
+	}
+
+	for(i=0; i<ndaps; i++){
+		switch(daps[i].id){
+		case 0x44770001:
+			ahbap = daps+i;
+			break;
+		case 0x24770002:
+			apbap = daps+i;
+			break;
+		}
+	}
+}
+
+enum {
+	DBGDIDR		= 0x000,
+	DBGDEVID	= 0xFC8,
+	DBGDSCR		= 0x088,
+		RXfull		= 1<<30,
+		TXfull		= 1<<29,
+		RXfull_1	= 1<<27,
+		TXfull_1	= 1<<26,
+		PipeAdv		= 1<<25,
+		InstrCompl_1	= 1<<24,
+		ExtDCCmodeShift	= 20,
+		ExtDCCmodeMask	= 3<<ExtDCCmodeShift,
+		ADAdiscard	= 1<<19,
+		NS		= 1<<18,
+		SPNIDdis	= 1<<17,
+		SPIDdis		= 1<<16,
+		MDBGen		= 1<<15,
+		HDBGen		= 1<<14,
+		ITRen		= 1<<13,
+		UDCCdis		= 1<<12,
+		INTdis		= 1<<11,
+		DBGack		= 1<<10,
+		UND_1		= 1<<8,
+		ADABORT_1	= 1<<7,
+		SDABORT_1	= 1<<6,
+		MOEShift	= 2,
+		MOEMask		= 15<<MOEShift,
+		RESTARTED	= 1<<1,
+		HALTED		= 1<<0,
+
+	DBGDRCR	= 0x90,
+		RestartReq	= 1<<1,
+		HaltReq		= 1<<0,
+
+	DBGPRCR	= 0x310,
+
+	DBGITR		= 0x084,	/* Instruction Transfer Register */
+	DBGDTRRX	= 0x080,	/* Host to Target Data Transfer Register */
+	DBGDTRTX	= 0x08C,	/* Target to Host Data Transfer Register */
+};
+
+typedef struct Arm Arm;
+struct Arm
+{
+	u32int	dbgbase;
+
+	Dap	*dbgap;
+	Dap	*memap;
+
+	char	*id;
+};
+Arm arm[2];
+u32int
+dbgr(Arm *arm, u32int reg)
+{
+	return mmr(arm->dbgap, arm->dbgbase+reg);
+}
+void
+dbgw(Arm *arm, u32int reg, u32int val)
+{
+	mmw(arm->dbgap, arm->dbgbase+reg, val);
+}
+u32int
+dbgrpoll(Arm *arm, u32int reg, u32int mask, u32int val)
+{
+	u32int w;
+
+	for(;;){
+		w = dbgr(arm, reg);
+		if((w & mask) == val)
+			break;
+	}
+	return w;
+}
+
+void
+startstop(Arm *arm, int stop)
+{
+	u32int s;
+
+	s = dbgr(arm, DBGDSCR);
+	if((s & HALTED) != stop){
+		if(!stop){
+			s &= ~ITRen;
+			dbgw(arm, DBGDSCR, s);
+		}
+		dbgw(arm, DBGDRCR, stop ? HaltReq : RestartReq);
+		s = dbgrpoll(arm, DBGDSCR, HALTED, stop);
+		if(stop){
+			s |= ITRen;
+			dbgw(arm, DBGDSCR, s);
+		}
+		fprint(2, "%s: startstop: %.8ux\n", arm->id, s);
+	}
+}
+
+void
+armxec(Arm *arm, u32int instr)
+{
+	dbgw(arm, DBGITR, instr);
+	dbgrpoll(arm, DBGDSCR, InstrCompl_1, InstrCompl_1);
+}
+
+#define ARMV4_5_MRC(CP, op1, Rd, CRn, CRm, op2) \
+	(0xee100010 | (CRm) | ((op2) << 5) | ((CP) << 8) \
+	| ((Rd) << 12) | ((CRn) << 16) | ((op1) << 21))
+#define ARMV4_5_MCR(CP, op1, Rd, CRn, CRm, op2) \
+	(0xee000010 | (CRm) | ((op2) << 5) | ((CP) << 8) \
+	| ((Rd) << 12) | ((CRn) << 16) | ((op1) << 21))
+
+void
+trrxw(Arm *arm, u32int val)
+{
+	dbgrpoll(arm, DBGDSCR, RXfull_1, 0);
+	dbgw(arm, DBGDTRRX, val);
+}
+u32int
+trtxr(Arm *arm)
+{
+	dbgrpoll(arm, DBGDSCR, TXfull_1, TXfull_1);
+	return dbgr(arm, DBGDTRTX);
+}
+
+void
+armrw(Arm *arm, int reg, u32int val);
+
+u32int
+armrr(Arm *arm, int rn)
+{
+	if(rn == 15){
+		u32int r0;
+
+		r0 = armrr(arm, 0);
+		armxec(arm, 0xE1A0000F);
+		armxec(arm, ARMV4_5_MCR(14, 0, 0, 0, 5, 0));
+		armrw(arm, 0, r0);
+	} else {
+		armxec(arm, ARMV4_5_MCR(14, 0, rn, 0, 5, 0));
+	}
+	return trtxr(arm);
+}
+void
+armrw(Arm *arm, int rn, u32int val)
+{
+	if(rn == 15){
+		u32int r0;
+
+		r0 = armrr(arm, 0);
+		armrw(arm, 0, val);
+		armxec(arm, 0xE1A0F000);
+		armrw(arm, 0, r0);
+	} else {
+		trrxw(arm, val);
+		armxec(arm, ARMV4_5_MRC(14, 0, rn, 0, 5, 0));
+	}
+}
+
+/*
+ * mww phys 0xf8000008 0xdf0d
+ * mww phys 0xf8000910 0xf
+ * load_image "/sys/src/boot/zynq/fsbl" 0xfffc0000 bin
+ * reg pc 0xfffc0000
+ */
+void
+boot(char *file, u32int entry)
+{
+	u32int *buf, *src;
+	int fd, size;
+	u32int dst;
+
+	fprint(2, "load %s", file);
+	if((fd = open(file, OREAD)) < 0)
+		sysfatal("open: %r");
+
+	size = seek(fd, 0, 2);
+	fprint(2, " [%ud]", size);
+	seek(fd, 0, 0);
+	buf = malloc((size+3) & ~3);
+	if(readn(fd, buf, size) != size)
+		sysfatal("read: %r");
+	close(fd);
+
+	/* map ocm */
+	mmw(arm->memap, 0xf8000008, 0xdf0d);
+	mmw(arm->memap, 0xf8000910, 0xf);
+
+	src = buf;
+	for(dst = entry; size > 0; dst += 4, size -= 4){
+		if((dst & 0xF) == 0)
+			fprint(2, ".");
+		mmw(arm->memap, dst, *src++);
+	}
+	free(buf);
+	fprint(2, ".\nentry %.8ux\n", entry);
+
+	armrw(arm, 15, entry);
+}
+
+void
+usage(void)
+{
+	fprint(2, "%s [ -j jtagdev ] entry image\n", argv0);
+	exits("usage");
+}
+
+void
+main(int argc, char *argv[])
+{
+	char *jtag = "/dev/jtagddd94.0";
+	char *image;
+	u32int entry;
+
+	fmtinstall('H', encodefmt);
+
+	ARGBEGIN {
+	case 'j':
+		jtag = EARGF(usage());
+		break;
+	default:
+		usage();
+	} ARGEND;
+
+	if(argc != 2)
+		usage();
+	entry = strtoul(argv[0], nil, 0);
+	image = argv[1];
+
+	ioinit(jtag);
+	tapreset();
+
+	arm[0].dbgbase = 0x80090000;
+	arm[0].dbgap = apbap;
+	arm[0].memap = ahbap;
+	arm[0].id = "arm0";
+
+	arm[1].dbgbase = 0x80092000;
+	arm[1].dbgap = apbap;
+	arm[1].memap = ahbap;
+	arm[1].id = "arm1";
+
+	startstop(arm+0, 1);
+	startstop(arm+1, 1);
+
+	boot(image, entry);
+
+	startstop(arm+0, 0);
+	startstop(arm+1, 0);
+
+	exits(nil);
+}
--- /dev/null
+++ b/os/boot/zynq/main.c
@@ -1,0 +1,203 @@
+#include <u.h>
+#include <a.out.h>
+#include "dat.h"
+#include "fns.h"
+
+void
+puts(char *s)
+{
+	while(*s)
+		putc(*s++);
+}
+
+void
+puthex(u32int u)
+{
+	static char *dig = "0123456789abcdef";
+	int i;
+	
+	for(i = 0; i < 8; i++){
+		putc(dig[u >> 28]);
+		u <<= 4;
+	}
+}
+
+void
+putdec(int n)
+{
+	if(n / 10 != 0)
+		putdec(n / 10);
+	putc(n % 10 + '0');
+}
+
+void
+print(char *s, ...)
+{
+	va_list va;
+	int n;
+	u32int u;
+	
+	va_start(va, s);
+	while(*s)
+		if(*s == '%'){
+			switch(*++s){
+			case 's':
+				puts(va_arg(va, char *));
+				break;
+			case 'x':
+				puthex(va_arg(va, u32int));
+				break;
+			case 'd':
+				n = va_arg(va, int);
+				if(n < 0){
+					putc('-');
+					putdec(-n);
+				}else
+					putdec(n);
+				break;
+			case 'I':
+				u = va_arg(va, u32int);
+				putdec(u >> 24);
+				putc('.');
+				putdec((uchar)(u >> 16));
+				putc('.');
+				putdec((uchar)(u >> 8));
+				putc('.');
+				putdec((uchar)u);
+				break;
+			case 0:
+				va_end(va);
+				return;
+			}
+			s++;
+		}else
+			putc(*s++);			
+	va_end(va);
+}
+
+void
+memset(void *v, char c, int n)
+{
+	char *vc;
+	
+	vc = v;
+	while(n--)
+		*vc++ = c;
+}
+
+void
+memcpy(void *d, void *s, int n)
+{
+	char *cd, *cs;
+	
+	cd = d;
+	cs = s;
+	while(n--)
+		*cd++ = *cs++;
+}
+
+u32int
+u32get(void *pp)
+{
+	uchar *p;
+	
+	p = pp;
+	return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
+}
+
+uchar *
+u32put(uchar *p, u32int v)
+{
+	p[0] = v >> 24;
+	p[1] = v >> 16;
+	p[2] = v >> 8;
+	p[3] = v;
+	return p + 4;
+}
+
+void
+run(void)
+{
+	ulong t, tr;
+	char *p, *d;
+	int n;
+	ulong *h;
+
+	h = (ulong *) TZERO;
+	if(u32get(&h[0]) != E_MAGIC){
+		print("invalid magic: %x != %x\n", u32get(&h[0]), E_MAGIC);
+		return;
+	}
+	t = u32get(&h[1]) + 0x20;
+	tr = t + 0xfff & ~0xfff;
+	if(t != tr){
+		n = u32get(&h[2]);
+		p = (char *) (TZERO + t + n);
+		d = (char *) (TZERO + tr + n);
+		while(n--)
+			*--d = *--p;
+	}
+	p = (char *) (TZERO + tr + u32get(&h[2]));
+	memset(p, 0, u32get(&h[3]));
+	jump((void *) (u32get(&h[5]) & 0xfffffff));
+}
+
+enum {
+	TIMERVALL,
+	TIMERVALH,
+	TIMERCTL,
+	TIMERSTAT,
+	TIMERCOMPL,
+	TIMERCOMPH,
+};
+
+void
+timeren(int n)
+{
+	ulong *r;
+	
+	r = (ulong *) 0xf8f00200;
+	if(n < 0){
+		r[TIMERSTAT] |= 1;
+		r[TIMERCTL] = 0;
+		return;
+	}
+	r[TIMERCTL] = 0;
+	r[TIMERVALL] = 0;
+	r[TIMERVALH] = 0;
+	r[TIMERCOMPL] = 1000 * n;
+	r[TIMERCOMPH] = 0;
+	r[TIMERSTAT] |= 1;
+	r[TIMERCTL] = 100 << 8 | 3;
+}
+
+int
+timertrig(void)
+{
+	ulong *r;
+	
+	r = (ulong *) 0xf8f00200;
+	if((r[TIMERSTAT] & 1) != 0){
+		r[TIMERCTL] = 0;
+		r[TIMERSTAT] |= 1;
+		return 1;
+	}
+	return 0;
+}
+
+void
+sleep(int n)
+{
+	timeren(n);
+	while(!timertrig())
+		;
+}
+
+void
+main(void)
+{
+	puts("Booting ...\n");
+	if(mmcboot() > 0 || netboot() > 0)
+		run();
+	print("hjboot: ending\n");
+}
--- /dev/null
+++ b/os/boot/zynq/mem.h
@@ -1,0 +1,107 @@
+#define STACKTOP 0xFFFFFE00
+
+#define ARM_FDIV (40 << PLL_FDIV_SH)
+#define DDR_FDIV (32 << PLL_FDIV_SH)
+#define IO_FDIV (30 << PLL_FDIV_SH)
+#define PLL_CFG_VAL(CP, RES, CNT) ((CP)<<8 | (RES)<<4 | (CNT)<<12)
+#define ARM_PLL_CFG_VAL PLL_CFG_VAL(2, 2, 250)
+#define DDR_PLL_CFG_VAL PLL_CFG_VAL(2, 2, 300)
+#define IO_PLL_CFG_VAL PLL_CFG_VAL(2, 12, 325)
+#define PLL_FDIV_SH 12
+#define PLL_BYPASS_FORCE 0x10
+#define PLL_RESET 0x01
+
+#define CPU_DIV 2
+#define DDR_DIV3 2
+#define DDR_DIV2 3
+#define UART_DIV 40
+#define DCI_DIV0 20
+#define DCI_DIV1 5
+#define ETH_DIV0 8
+#define ETH_DIV1 1
+#define QSPI_DIV 5
+#define SDIO_DIV 10
+#define PCAP_DIV 5
+#define MDC_DIV 6 /* this value depends on CPU_1xCLK, see TRM GEM.net_cfg description */
+
+#define SLCR_BASE 0xF8000000
+#define SLCR_LOCK 0x004
+#define LOCK_KEY 0x767B
+#define SLCR_UNLOCK 0x008
+#define UNLOCK_KEY 0xDF0D
+
+#define ARM_PLL_CTRL 0x100
+#define DDR_PLL_CTRL 0x104
+#define IO_PLL_CTRL 0x108
+#define PLL_STATUS 0x10C
+#define ARM_PLL_CFG 0x110
+#define DDR_PLL_CFG 0x114
+#define IO_PLL_CFG 0x118
+#define ARM_CLK_CTRL 0x120
+#define DDR_CLK_CTRL 0x124
+#define DCI_CLK_CTRL 0x128
+#define APER_CLK_CTRL 0x12C
+#define GEM0_RCLK_CTRL 0x138
+#define GEM1_RCLK_CTRL 0x13C
+#define GEM0_CLK_CTRL 0x140
+#define GEM1_CLK_CTRL 0x144
+#define SMC_CLK_CTRL 0x148
+#define LQSPI_CLK_CTRL 0x14C
+#define SDIO_CLK_CTRL 0x150
+#define UART_CLK_CTRL 0x154
+#define SPI_CLK_CTRL 0x158
+#define CAN_CLK_CTRL 0x15C
+#define PCAP_CLK_CTRL 0x168
+#define UART_RST_CTRL 0x228
+#define A9_CPU_RST_CTRL 0x244
+
+#define LQSPI_CLK_EN (1<<23)
+#define GPIO_CLK_EN (1<<22)
+#define UART0_CLK_EN (1<<20)
+#define UART1_CLK_EN (1<<21)
+#define I2C0_CLK_EN (1<<18)
+#define SDIO1_CLK_EN (1<<11)
+#define GEM0_CLK_EN (1<<6)
+#define USB1_CLK_EN (1<<3)
+#define USB0_CLK_EN (1<<2)
+#define DMA_CLK_EN (1<<0)
+
+#define MIO_PIN_0 0x00000700
+#define MIO_MST_TRI0 0x80C
+#define MIO_MST_TRI1 0x810
+#define OCM_CFG 0x910
+#define GPIOB_CTRL 0xB00
+#define VREF_SW_EN (1<<11)
+#define DDRIOB_ADDR0 0xB40
+#define DDRIOB_DCI_CTRL 0xB70
+#define DDRIOB_DCI_CTRL_MASK 0x1ffc3
+#define DDRIOB_DCI_STATUS 0xB74
+#define DCI_RESET 1
+#define DCI_NREF (1<<11)
+#define DCI_ENABLE 2
+
+#define DDR_BASE 0xF8006000
+#define DDRC_CTRL 0x0
+#define DDR_MODE_STS 0x54
+
+#define UART1_BASE 0xE0001000
+#define UART_CTRL 0x0
+#define UART_MODE 0x4
+#define UART_BAUD 0x18
+#define UART_STAT 0x2C
+#define UART_DATA 0x30
+#define UART_SAMP 0x34
+
+#define QSPI_BASE 0xE000D000
+#define QSPI_CFG 0x0
+#define SPI_EN 0x4
+#define QSPI_TX 0x1c
+
+#define MP_BASE 0xF8F00000
+#define FILTER_START 0x40
+
+#define CpMMU 15
+
+#define DSB WORD $0xf57ff04f
+#define ISB WORD $0xf57ff06f
+#define WFE WORD $0xe320f002
--- /dev/null
+++ b/os/boot/zynq/mkfile
@@ -1,0 +1,38 @@
+objtype=arm
+</$objtype/mkfile
+BIN=/arm
+TARG=fsbl fsbl.img
+FSBLFILES=fsbl.$O ddr.$O main.$O mmc.$O net.$O div.$O qspi.$O
+TEXTBASE=0xfffc0000
+
+all:V: $TARG
+
+clean:V:
+	rm -rf $TARG *.$O
+	@{objtype=$cputype mk -f mkfile.port clean}
+
+fsbl: $FSBLFILES
+	$LD -o $target -T$TEXTBASE -H6 -R4096 -l -s $prereq
+
+9fsbl: $FSBLFILES
+	$LD -o $target -T$TEXTBASE -l $prereq
+
+fsbl.img:D: fsbl boothead.$cputype
+	./boothead.$cputype fsbl >fsbl.img
+
+%.$cputype:V: mkfile.port
+	@{objtype=$cputype mk -f $prereq $target}
+
+jtagload:V: fsbl jtagload.$cputype
+	./jtagload.$cputype -j /dev/jtag*.0 $TEXTBASE fsbl
+
+div.$O: /sys/src/libc/arm/div.s
+	$AS /sys/src/libc/arm/div.s
+
+%.$O: dat.h fns.h mem.h
+
+%.$O: %.s
+	$AS $stem.s
+
+%.$O: %.c
+	$CC $CFLAGS $stem.c
--- /dev/null
+++ b/os/boot/zynq/mkfile.port
@@ -1,0 +1,14 @@
+</$objtype/mkfile
+
+TARG=boothead.$objtype jtagload.$objtype
+
+all:V:	$TARG
+
+clean:V:
+	rm -f $TARG *.$O
+
+%.$objtype: %.$O
+	$LD $LDFLAGS -o $target $prereq
+
+%.$O:	%.c
+	$CC $CFLAGS $stem.c
--- /dev/null
+++ b/os/boot/zynq/mmc.c
@@ -1,0 +1,832 @@
+#include <u.h>
+#include "dat.h"
+#include "fns.h"
+#include "mem.h"
+
+enum {
+	Sectsz = 0x200,
+	Dirsz = 0x20,
+	Maxpath = 64,
+	Fat12 = 1,
+	Fat16 = 2,
+	Fat32 = 4,
+};
+
+typedef struct File File;
+typedef struct Dir Dir;
+typedef struct Pbs Pbs;
+typedef struct Pbs32 Pbs32;
+typedef struct Fat Fat;
+
+struct Fat
+{
+	ulong ver;
+	ulong clustsize;
+	ulong eofmark;
+	ulong partlba;
+	ulong fatlba;
+	ulong dirstart; /* LBA for FAT16, cluster for FAT32 */
+	ulong dirents;
+	ulong datalba;
+};
+
+struct File
+{
+	Fat *fat;
+	ulong lba;
+	ulong clust;
+	ulong lbaoff;
+	ulong len;
+	uchar *rp;
+	uchar *ep;
+	uchar buf[Sectsz];
+};
+
+struct Dir
+{
+	char name[11];
+	uchar attr;
+	uchar reserved;
+	uchar ctime;
+	uchar ctime[2];
+	uchar cdate[2];
+	uchar adate[2];
+	uchar starthi[2];
+	uchar mtime[2];
+	uchar mdate[2];
+	uchar startlo[2];
+	uchar len[4];
+};
+
+struct Pbs
+{
+	uchar magic[3];
+	uchar version[8];
+	uchar sectsize[2];
+	uchar clustsize;
+	uchar nreserv[2];
+	uchar nfats;
+	uchar rootsize[2];
+	uchar volsize[2];
+	uchar mediadesc;
+	uchar fatsize[2];
+	uchar trksize[2];
+	uchar nheads[2];
+	uchar nhidden[4];
+	uchar bigvolsize[4];
+	uchar driveno;
+	uchar reserved0;
+	uchar bootsig;
+	uchar volid[4];
+	uchar label[11];
+	uchar type[8];
+};
+
+struct Pbs32
+{
+	uchar common[36];
+	uchar fatsize[4];
+	uchar flags[2];
+	uchar ver[2];
+	uchar rootclust[4];
+	uchar fsinfo[2];
+	uchar bootbak[2];
+	uchar reserved0[12];
+	uchar driveno;
+	uchar reserved1;
+	uchar bootsig;
+	uchar volid[4];
+	uchar label[11];
+	uchar type[8];
+};
+
+enum {
+	Initfreq	= 400000,	/* initialisation frequency for MMC */
+	SDfreq		= 25000000,	/* standard SD frequency */
+	DTO		= 14,		/* data timeout exponent (guesswork) */
+};
+
+enum {
+	/* Controller registers */
+	Sysaddr			= 0x00>>2,
+	Blksizecnt		= 0x04>>2,
+	Arg1			= 0x08>>2,
+	Cmdtm			= 0x0c>>2,
+	Resp0			= 0x10>>2,
+	Resp1			= 0x14>>2,
+	Resp2			= 0x18>>2,
+	Resp3			= 0x1c>>2,
+	Data			= 0x20>>2,
+	Status			= 0x24>>2,
+	Control0		= 0x28>>2,
+	Control1		= 0x2c>>2,
+	Interrupt		= 0x30>>2,
+	Irptmask		= 0x34>>2,
+	Irpten			= 0x38>>2,
+	Capabilites		= 0x40>>2,
+	Forceirpt		= 0x50>>2,
+	Boottimeout		= 0x60>>2,
+	Dbgsel			= 0x64>>2,
+	Spiintspt		= 0xf0>>2,
+	Slotisrver		= 0xfc>>2,
+
+	/* Control0 */
+	Dwidth4			= 1<<1,
+	Dwidth1			= 0<<1,
+
+	/* Control1 */
+	Srstdata		= 1<<26,	/* reset data circuit */
+	Srstcmd			= 1<<25,	/* reset command circuit */
+	Srsthc			= 1<<24,	/* reset complete host controller */
+	Datatoshift		= 16,		/* data timeout unit exponent */
+	Datatomask		= 0xF0000,
+	Clkfreq8shift		= 8,		/* SD clock base divider LSBs */
+	Clkfreq8mask		= 0xFF00,
+	Clkfreqms2shift		= 6,		/* SD clock base divider MSBs */
+	Clkfreqms2mask		= 0xC0,
+	Clkgendiv		= 0<<5,		/* SD clock divided */
+	Clkgenprog		= 1<<5,		/* SD clock programmable */
+	Clken			= 1<<2,		/* SD clock enable */
+	Clkstable		= 1<<1,	
+	Clkintlen		= 1<<0,		/* enable internal EMMC clocks */
+
+	/* Cmdtm */
+	Indexshift		= 24,
+	Suspend			= 1<<22,
+	Resume			= 2<<22,
+	Abort			= 3<<22,
+	Isdata			= 1<<21,
+	Ixchken			= 1<<20,
+	Crcchken		= 1<<19,
+	Respmask		= 3<<16,
+	Respnone		= 0<<16,
+	Resp136			= 1<<16,
+	Resp48			= 2<<16,
+	Resp48busy		= 3<<16,
+	Multiblock		= 1<<5,
+	Host2card		= 0<<4,
+	Card2host		= 1<<4,
+	Autocmd12		= 1<<2,
+	Autocmd23		= 2<<2,
+	Blkcnten		= 1<<1,
+	Dmaen			= 1<<0,
+
+	/* Interrupt */
+	Acmderr		= 1<<24,
+	Denderr		= 1<<22,
+	Dcrcerr		= 1<<21,
+	Dtoerr		= 1<<20,
+	Cbaderr		= 1<<19,
+	Cenderr		= 1<<18,
+	Ccrcerr		= 1<<17,
+	Ctoerr		= 1<<16,
+	Err		= 1<<15,
+	Cardintr	= 1<<8,
+	Cardinsert	= 1<<6,
+	Readrdy		= 1<<5,
+	Writerdy	= 1<<4,
+	Dmaintr		= 1<<3,
+	Datadone	= 1<<1,
+	Cmddone		= 1<<0,
+
+	/* Status */
+	Present		= 1<<18,
+	Bufread		= 1<<11,
+	Bufwrite	= 1<<10,
+	Readtrans	= 1<<9,
+	Writetrans	= 1<<8,
+	Datactive	= 1<<2,
+	Datinhibit	= 1<<1,
+	Cmdinhibit	= 1<<0,
+
+	Inittimeout	= 15,
+//	Multiblock	= 1,
+
+	/* Commands */
+	GO_IDLE_STATE	= 0,
+	ALL_SEND_CID	= 2,
+	SEND_RELATIVE_ADDR= 3,
+	SELECT_CARD	= 7,
+	SD_SEND_IF_COND	= 8,
+	SEND_CSD	= 9,
+	STOP_TRANSMISSION= 12,
+	SEND_STATUS	= 13,
+	SET_BLOCKLEN	= 16,
+	READ_SINGLE_BLOCK= 17,
+	READ_MULTIPLE_BLOCK= 18,
+	WRITE_BLOCK	= 24,
+	WRITE_MULTIPLE_BLOCK= 25,
+	APP_CMD		= 55,	/* prefix for following app-specific commands */
+	SET_BUS_WIDTH	= 6,
+	SD_SEND_OP_COND	= 41,
+
+	/* Command arguments */
+	/* SD_SEND_IF_COND */
+	Voltage		= 1<<8,
+	Checkpattern	= 0x42,
+
+	/* SELECT_CARD */
+	Rcashift	= 16,
+
+	/* SD_SEND_OP_COND */
+	Hcs	= 1<<30,	/* host supports SDHC & SDXC */
+	Ccs	= 1<<30,	/* card is SDHC or SDXC */
+	V3_3	= 3<<20,	/* 3.2-3.4 volts */
+
+	/* SET_BUS_WIDTH */
+	Width1	= 0<<0,
+	Width4	= 2<<0,
+
+	/* OCR (operating conditions register) */
+	Powerup	= 1<<31,
+};
+
+static int cmdinfo[64] = {
+[0]  Ixchken,
+[2]  Resp136,
+[3]  Resp48 | Ixchken | Crcchken,
+[6]  Resp48 | Ixchken | Crcchken,
+[7]  Resp48busy | Ixchken | Crcchken,
+[8]  Resp48 | Ixchken | Crcchken,
+[9]  Resp136,
+[12] Resp48busy | Ixchken | Crcchken,
+[13] Resp48 | Ixchken | Crcchken,
+[16] Resp48,
+[17] Resp48 | Isdata | Card2host | Ixchken | Crcchken | Dmaen,
+[18] Resp48 | Isdata | Card2host | Multiblock | Blkcnten | Ixchken | Crcchken | Dmaen,
+[24] Resp48 | Isdata | Host2card | Ixchken | Crcchken | Dmaen,
+[25] Resp48 | Isdata | Host2card | Multiblock | Blkcnten | Ixchken | Crcchken | Dmaen,
+[41] Resp48,
+[55] Resp48 | Ixchken | Crcchken,
+};
+
+typedef struct Ctlr Ctlr;
+struct Ctlr {
+	u32int	*regs;
+	ulong	extclk;
+
+	/* SD card registers */
+	u16int	rca;
+	u32int	ocr;
+	u32int	cid[4];
+	u32int	csd[4];
+};
+static Ctlr ctlr = {
+	.regs	= (u32int*)0xE0101000,
+	.extclk	= 100000000,
+};
+
+
+static ushort
+GETSHORT(void *v)
+{
+	uchar *p = v;
+	return p[0] | p[1]<<8;
+}
+static ulong
+GETLONG(void *v)
+{
+	uchar *p = v;
+	return p[0] | p[1]<<8 | p[2]<<16 | p[3]<<24;
+}
+
+static int
+memcmp(void *src, void *dst, int n)
+{
+	uchar *d = dst;
+	uchar *s = src;
+	int r = 0;
+
+	while(n-- > 0){
+		r = *d++ - *s++;
+		if(r != 0)
+			break;
+	}
+
+	return r;
+}
+
+static uint
+clkdiv(uint d)
+{
+	uint v;
+
+	v = (d << Clkfreq8shift) & Clkfreq8mask;
+	v |= ((d >> 8) << Clkfreqms2shift) & Clkfreqms2mask;
+	return v;
+}
+
+static int
+mmcwait(int mask)
+{
+	int i, t;
+
+	t = 0;
+	while(((i=ctlr.regs[Interrupt])&mask) == 0)
+		if(t++ > 10000000)
+			break;
+
+	return i;
+}
+
+static int
+mmccmd(u32int cmd, u32int arg, u32int *resp)
+{
+	u32int *r;
+	u32int c;
+	int i;
+
+	c = (cmd << Indexshift) | cmdinfo[cmd];
+
+	r = ctlr.regs;
+	if(r[Status] & Cmdinhibit){
+		print("mmc: need to reset Cmdinhibit intr %x stat %x\n",
+			r[Interrupt], r[Status]);
+		r[Control1] |= Srstcmd;
+		while(r[Control1] & Srstcmd)
+			;
+		while(r[Status] & Cmdinhibit)
+			;
+	}
+	if((c & Isdata || (c & Respmask) == Resp48busy) && r[Status] & Datinhibit){
+		print("mmc: need to reset Datinhibit intr %x stat %x\n",
+			r[Interrupt], r[Status]);
+		r[Control1] |= Srstdata;
+		while(r[Control1] & Srstdata)
+			;
+		while(r[Status] & Datinhibit)
+			;
+	}
+	r[Arg1] = arg;
+	if((i = r[Interrupt]) != 0){
+		if(i != Cardinsert)
+			print("mmc: before command, intr was %x\n", i);
+		r[Interrupt] = i;
+	}
+	r[Cmdtm] = c;
+
+	i = mmcwait(Cmddone|Err);
+	if((i&(Cmddone|Err)) != Cmddone){
+		if((i&~Err) != Ctoerr)
+			print("mmc: CMD%d error intr %x stat %x\n", cmd, i, r[Status]);
+		r[Interrupt] = i;
+		if(r[Status]&Cmdinhibit){
+			r[Control1] |= Srstcmd;
+			while(r[Control1]&Srstcmd)
+				;
+		}
+		return -1;
+	}
+	r[Interrupt] = i & ~(Datadone|Readrdy|Writerdy);
+	switch(c & Respmask){
+	case Resp136:
+		resp[0] = r[Resp0]<<8;
+		resp[1] = r[Resp0]>>24 | r[Resp1]<<8;
+		resp[2] = r[Resp1]>>24 | r[Resp2]<<8;
+		resp[3] = r[Resp2]>>24 | r[Resp3]<<8;
+		break;
+	case Resp48:
+	case Resp48busy:
+		resp[0] = r[Resp0];
+		break;
+	case Respnone:
+		resp[0] = 0;
+		break;
+	}
+	if((c & Respmask) == Resp48busy){
+		r[Irpten] = Datadone|Err;
+		i = mmcwait(Cmddone|Err);
+		if(i)
+			r[Interrupt] = i;
+		r[Irpten] = 0;
+		if((i & Datadone) == 0)
+			print("mmc: no Datadone after CMD%d\n", cmd);
+		if(i & Err)
+			print("mmc: CMD%d error interrupt %x\n", cmd, i);
+	}
+
+	/*
+	 * Once card is selected, use faster clock
+	 */
+	if(cmd == SELECT_CARD){
+		sleep(10);
+		r[Control1] = clkdiv(ctlr.extclk / SDfreq - 1) |
+			DTO << Datatoshift | Clkgendiv | Clken | Clkintlen;
+		for(i = 0; i < 1000; i++){
+			sleep(1);
+			if(r[Control1] & Clkstable)
+				break;
+		}
+		sleep(10);
+	}
+
+	/*
+	 * If card bus width changes, change host bus width
+	 */
+	if(cmd == SET_BUS_WIDTH)
+		switch(arg){
+		case 0:
+			r[Control0] &= ~Dwidth4;
+			break;
+		case 2:
+			r[Control0] |= Dwidth4;
+			break;
+		}
+	return 0;
+}
+
+static int
+mmconline(void)
+{
+	u32int r[4];
+	int hcs, i;
+
+	mmccmd(GO_IDLE_STATE, 0, r);
+
+	hcs = 0;
+	if(mmccmd(SD_SEND_IF_COND, Voltage|Checkpattern, r) == 0){
+		if(r[0] == (Voltage|Checkpattern))	/* SD 2.0 or above */
+			hcs = Hcs;
+	}
+	for(i = 0; i < Inittimeout; i++){
+		sleep(100);
+		mmccmd(APP_CMD, 0, r);
+		mmccmd(SD_SEND_OP_COND, hcs|V3_3, r);
+		if(r[0] & Powerup)
+			break;
+	}
+	if(i == Inittimeout){
+		print("mmc: card won't power up\n");
+		return -1;
+	}
+	ctlr.ocr = r[0];
+	mmccmd(ALL_SEND_CID, 0, r);
+	memcpy(ctlr.cid, r, sizeof ctlr.cid);
+	mmccmd(SEND_RELATIVE_ADDR, 0, r);
+	ctlr.rca = r[0]>>16;
+	mmccmd(SEND_CSD, ctlr.rca<<Rcashift, r);
+	memcpy(ctlr.csd, r, sizeof ctlr.csd);
+	mmccmd(SELECT_CARD, ctlr.rca<<Rcashift, r);
+	mmccmd(SET_BLOCKLEN, Sectsz, r);
+	mmccmd(APP_CMD, ctlr.rca<<Rcashift, r);
+	mmccmd(SET_BUS_WIDTH, Width4, r);
+	return 0;
+}
+
+static int
+mmcinit(void)
+{
+	u32int *r;
+	int i;
+
+	r = ctlr.regs;
+	r[Control1] = Srsthc;
+	for(i = 0; i < 100; i++){
+		sleep(10);
+		if((r[Control1] & Srsthc) == 0)
+			break;
+	}
+	if(i == 100){
+		print("mmc: reset timeout!\n");
+		return -1;
+	}
+	r[Control1] = clkdiv(ctlr.extclk / Initfreq - 1) | DTO << Datatoshift |
+		Clkgendiv | Clken | Clkintlen;
+	for(i = 0; i < 1000; i++){
+		sleep(1);
+		if(r[Control1] & Clkstable)
+			break;
+	}
+	if(i == 1000){
+		print("mmc: SD clock won't initialise!\n");
+		return -1;
+	}
+	r[Irptmask] = ~(Dtoerr|Cardintr|Dmaintr);
+	return mmconline();
+}
+
+static int
+mmcread(ulong bno, uchar buf[Sectsz])
+{
+	u32int *r, rr[4];
+	int i, t;
+
+	r = ctlr.regs;
+	for(t=0; t<3; t++){
+		r[Sysaddr] = (u32int)buf;
+		r[Blksizecnt] = 7<<12 | 1<<16 | Sectsz;
+		r[Irpten] = Datadone|Err;
+		mmccmd(READ_SINGLE_BLOCK, ctlr.ocr & Ccs? bno : bno*Sectsz, rr);
+		i = mmcwait(Datadone|Err);
+		if(i)
+			r[Interrupt] = i;
+		r[Irpten] = 0;
+		if((i & Err) != 0)
+			print("mmcread: error intr %x stat %x\n", i, r[Status]);
+		else if((i & Datadone) == 0)
+			print("mmcread: timeout intr %x stat %x\n", i, r[Status]);
+		else
+			return 0;
+	}
+	return -1;
+}
+
+static int
+dirname(Dir *d, char buf[Maxpath])
+{
+	char c, *x;
+
+	if(d->attr == 0x0F || *d->name <= 0)
+		return -1;
+	memcpy(buf, d->name, 8);
+	x = buf+8;
+	while(x > buf && x[-1] == ' ')
+		x--;
+	if(d->name[8] != ' '){
+		*x++ = '.';
+		memcpy(x, d->name+8, 3);
+		x += 3;
+	}
+	while(x > buf && x[-1] == ' ')
+		x--;
+	*x = 0;
+	x = buf;
+	while(c = *x){
+		if(c >= 'A' && c <= 'Z'){
+			c -= 'A';
+			c += 'a';
+		}
+		*x++ = c;
+	}
+	return x - buf;
+}
+
+static ulong
+dirclust(Dir *d)
+{
+	return GETSHORT(d->starthi)<<16 | GETSHORT(d->startlo);
+}
+
+static void
+fileinit(File *fp, Fat *fat, ulong lba)
+{
+	fp->fat = fat;
+	fp->lba = lba;
+	fp->len = 0;
+	fp->lbaoff = 0;
+	fp->clust = ~0U;
+	fp->rp = fp->ep = fp->buf + Sectsz;
+}
+
+static ulong
+readnext(File *fp, ulong clust)
+{
+	Fat *fat = fp->fat;
+	uchar tmp[2], *p;
+	ulong idx, lba;
+
+	if(fat->ver == Fat12)
+		idx = (3*clust)/2;
+	else
+		idx = clust*fat->ver;
+	lba = fat->fatlba + (idx / Sectsz);
+	if(mmcread(lba, fp->buf))
+		memset(fp->buf, 0xff, Sectsz);
+	p = &fp->buf[idx % Sectsz];
+	if(p == &fp->buf[Sectsz-1]){
+		tmp[0] = *p;
+		if(mmcread(++lba, fp->buf))
+			memset(fp->buf, 0xff, Sectsz);
+		tmp[1] = fp->buf[0];
+		p = tmp;
+	}
+	if(fat->ver == Fat32)
+		return GETLONG(p) & 0xfffffff;
+	idx = GETSHORT(p);
+	if(fat->ver == Fat12){
+		if(clust & 1)
+			idx >>= 4;
+		idx &= 0xfff;
+	}
+	return idx;
+}
+
+static int
+fileread(File *fp, void *data, int len)
+{
+	Fat *fat = fp->fat;
+
+	if(fp->len > 0 && fp->rp >= fp->ep){
+		if(fp->clust != ~0U){
+			if(fp->lbaoff % fat->clustsize == 0){
+				if(fp->clust < 2 || fp->clust >= fat->eofmark)
+					return -1;
+				fp->lbaoff = (fp->clust - 2) * fat->clustsize;
+				fp->clust = readnext(fp, fp->clust);
+				fp->lba = fp->lbaoff + fat->datalba;
+			}
+			fp->lbaoff++;
+		}
+		if(mmcread(fp->lba++, fp->rp = fp->buf))
+			return -1;
+	}
+	if(fp->len < len)
+		len = fp->len;
+	if(len > (fp->ep - fp->rp))
+		len = fp->ep - fp->rp;
+	memcpy(data, fp->rp, len);
+	fp->rp += len;
+	fp->len -= len;
+	return len;
+}
+
+static int
+fatwalk(File *fp, Fat *fat, char *path)
+{
+	char name[Maxpath], *end;
+	int i, j;
+	Dir d;
+
+	if(fat->ver == Fat32){
+		fileinit(fp, fat, 0);
+		fp->clust = fat->dirstart;
+		fp->len = ~0U;
+	}else{
+		fileinit(fp, fat, fat->dirstart);
+		fp->len = fat->dirents * Dirsz;
+	}
+	for(;;){
+		if(fileread(fp, &d, Dirsz) != Dirsz)
+			break;
+		if((i = dirname(&d, name)) <= 0)
+			continue;
+		while(*path == '/')
+			path++;
+		for(end = path; *end != '\0'; end++)
+			if(*end == '/')
+				break;
+		j = end - path;
+		if(i == j && memcmp(name, path, j) == 0){
+			fileinit(fp, fat, 0);
+			fp->clust = dirclust(&d);
+			fp->len = GETLONG(d.len);
+			if(*end == 0)
+				return 0;
+			else if(d.attr & 0x10){
+				fp->len = fat->clustsize * Sectsz;
+				path = end;
+				continue;
+			}
+			break;
+		}
+	}
+	return -1;
+}
+
+static int
+conffat(Fat *fat, void *buf)
+{
+	Pbs *p = buf;
+	uint fatsize, volsize, datasize, reserved;
+	uint ver, dirsize, dirents, clusters;
+
+	if(GETSHORT(p->sectsize) != Sectsz)
+		return -1;
+	if(memcmp(p->type, "FAT", 3) && memcmp(((Pbs32*)buf)->type, "FAT", 3))
+		return -1;
+	
+	/* load values from fat */
+	ver = 0;
+	fatsize = GETSHORT(p->fatsize);
+	if(fatsize == 0){
+		fatsize = GETLONG(((Pbs32*)buf)->fatsize);
+		ver = Fat32;
+	}
+	volsize = GETSHORT(p->volsize);
+	if(volsize == 0)
+		volsize = GETLONG(p->bigvolsize);
+	reserved = GETSHORT(p->nreserv);
+	dirents = GETSHORT(p->rootsize);
+	dirsize = (dirents * Dirsz + Sectsz - 1) / Sectsz;
+	datasize = volsize - (reserved + fatsize * p->nfats + dirsize);
+	clusters = datasize / p->clustsize;
+	if(ver != Fat32)
+		if(clusters < 0xff7)
+			ver = Fat12;
+		else
+			ver = Fat16;
+	
+	/* fill FAT descriptor */
+	fat->ver = ver;
+	fat->dirents = dirents;
+	fat->clustsize = p->clustsize;
+	fat->fatlba = fat->partlba + reserved;
+	fat->dirstart  = fat->fatlba + fatsize * p->nfats;
+	if(ver == Fat32){
+		fat->datalba = fat->dirstart;
+		fat->dirstart  = GETLONG(((Pbs32*)buf)->rootclust);
+		fat->eofmark = 0xffffff7;
+	}else{
+		fat->datalba = fat->dirstart + dirsize;
+		if(ver == Fat16)
+			fat->eofmark = 0xfff7;
+		else
+			fat->eofmark = 0xff7;
+	}
+	return 0;
+}
+
+static int
+findfat(Fat *fat, ulong xbase, ulong lba)
+{
+	struct {
+		uchar status;
+		uchar bchs[3];
+		uchar typ;
+		uchar echs[3];
+		uchar lba[4];
+		uchar len[4];
+	} p[4];
+	uchar buf[Sectsz];
+	int i;
+
+	if(xbase == 0)
+		xbase = lba;
+	if(mmcread(lba, buf))
+		return -1;
+	if(buf[0x1fe] != 0x55 || buf[0x1ff] != 0xAA)
+		return -1;
+	memcpy(p, &buf[0x1be], sizeof(p));
+	for(i=0; i<4; i++){
+		switch(p[i].typ){
+		case 0x05:
+		case 0x0f:
+		case 0x85:
+			/* extended partitions */
+			if(!findfat(fat, xbase, xbase + GETLONG(p[i].lba)))
+				return 0;
+			/* no break */
+		case 0x00:
+			continue;
+		default:
+			fat->partlba = lba + GETLONG(p[i].lba);
+			if(mmcread(fat->partlba, buf))
+				continue;
+			if(!conffat(fat, buf))
+				return 0;
+		}
+	}
+	return -1;
+}
+
+static int
+load(Fat *fat, char *path, void *data)
+{
+	uchar *p;
+	File fi;
+	int n;
+
+	print("%s", path);
+	if(fatwalk(&fi, fat, path)){
+		print(": not found\n", path);
+		return -1;
+	}
+	print("...");
+	p = data;
+	while((n = fileread(&fi, p, Sectsz)) > 0)
+		p += n;
+	print("\n");
+	return p - (uchar*)data;
+}
+
+int
+mmcboot(void)
+{
+	char file[Maxpath], *p;
+	Fat fat;
+
+	if(mmcinit() < 0)
+		return 0;
+	if(findfat(&fat, 0, 0)){
+		print("no fat\n");
+		return 0;
+	}
+	memcpy(file, "9zynq", 6);
+	memset(p = (char*)CONF, 0, CONFSIZE);
+	p += load(&fat, "plan9.ini", p);
+	p -= 9; /* "bootfile=" */
+	while(--p >= (char*)CONF){
+		while(p > (char*)CONF && p[-1] != '\n')
+			p--;
+		if(memcmp("bootfile=", p, 9) == 0){
+			p += 9;
+			memcpy(file, p, sizeof(file)-1);
+			for(p=file; p < &file[sizeof(file)-1]; p++)
+				if(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n')
+					break;
+			*p = '\0';
+			break;
+		}
+	}
+	return load(&fat, file, (void*)TZERO) > 0;
+}
--- /dev/null
+++ b/os/boot/zynq/net.c
@@ -1,0 +1,577 @@
+#include <u.h>
+#include "dat.h"
+#include "fns.h"
+#include "mem.h"
+
+enum {
+	ETHLEN = 1600,
+	UDPLEN = 576,
+	NRX = 64,
+	RXBASE = 128 * 1024 * 1024,
+	
+	ETHHEAD = 14,
+	IPHEAD = 20,
+	UDPHEAD = 8,
+	
+	BOOTREQ = 1,
+	DHCPDISCOVER = 1,
+	DHCPOFFER,
+	DHCPREQUEST,
+	DHCPDECLINE,
+};
+
+enum {
+	NET_CTRL,
+	NET_CFG,
+	NET_STATUS,
+	DMA_CFG = 4,
+	TX_STATUS,
+	RX_QBAR,
+	TX_QBAR,
+	RX_STATUS,
+	INTR_STATUS,
+	INTR_EN,
+	INTR_DIS,
+	INTR_MASK,
+	PHY_MAINT,
+	RX_PAUSEQ,
+	TX_PAUSEQ,
+	HASH_BOT = 32,
+	HASH_TOP,
+	SPEC_ADDR1_BOT,
+	SPEC_ADDR1_TOP,
+};
+
+enum {
+	MDCTRL,
+	MDSTATUS,
+	MDID1,
+	MDID2,
+	MDAUTOADV,
+	MDAUTOPART,
+	MDAUTOEX,
+	MDAUTONEXT,
+	MDAUTOLINK,
+	MDGCTRL,
+	MDGSTATUS,
+	MDPHYCTRL = 0x1f,
+};
+
+enum {
+	/* NET_CTRL */
+	RXEN = 1<<2,
+	TXEN = 1<<3,
+	MDEN = 1<<4,
+	STARTTX = 1<<9,
+	/* NET_CFG */
+	SPEED = 1<<0,
+	FDEN = 1<<1,
+	RX1536EN = 1<<8,
+	GIGE_EN = 1<<10,
+	RXCHKSUMEN = 1<<24,
+	/* NET_STATUS */
+	PHY_IDLE = 1<<2,
+	/* DMA_CFG */
+	TXCHKSUMEN  = 1<<11,
+	/* TX_STATUS */
+	TXCOMPL = 1<<5,
+	/* MDCTRL */
+	MDRESET = 1<<15,
+	AUTONEG = 1<<12,
+	FULLDUP = 1<<8,
+	/* MDSTATUS */
+	LINK = 1<<2,
+	/* MDGSTATUS */
+	RECVOK = 3<<12,
+};
+
+typedef struct {
+	uchar edest[6];
+	uchar esrc[6];
+	ulong idest;
+	ulong isrc;
+	ushort dport, sport;
+	ushort len;
+	uchar data[UDPLEN];
+} udp;
+
+static ulong *eth0 = (ulong *) 0xe000b000;
+static int phyaddr = 7;
+
+static u32int myip, dhcpip, tftpip, xid;
+static uchar mac[6] = {0x0E, 0xA7, 0xDE, 0xAD, 0xBE, 0xEF};
+static uchar tmac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+static char file[128];
+
+static udp ubuf, urbuf;
+static uchar txbuf[ETHLEN];
+static ulong txdesc[4], *txact, *rxact;
+static ulong rxdesc[NRX*2];
+
+void
+mdwrite(ulong *r, int reg, u16int val)
+{
+	while((r[NET_STATUS] & PHY_IDLE) == 0)
+		;
+	r[PHY_MAINT] = 1<<30 | 1<<28 | 1<<17 | phyaddr << 23 | reg << 18 | val;
+	while((r[NET_STATUS] & PHY_IDLE) == 0)
+		;
+}
+
+u16int
+mdread(ulong *r, int reg)
+{
+	while((r[NET_STATUS] & PHY_IDLE) == 0)
+		;
+	r[PHY_MAINT] = 1<<30 | 1<<29 | 1<<17 | phyaddr << 23 | reg << 18;
+	while((r[NET_STATUS] & PHY_IDLE) == 0)
+		;
+	return r[PHY_MAINT];
+}
+
+void
+ethinit(ulong *r)
+{
+	int v;
+	ulong *p;
+	ulong d;
+
+	r[NET_CTRL] = 0;
+	r[RX_STATUS] = 0xf;
+	r[TX_STATUS] = 0xff;
+	r[INTR_DIS] = 0x7FFFEFF;
+	r[RX_QBAR] = r[TX_QBAR] = 0;
+	r[NET_CFG] = MDC_DIV << 18 | FDEN | SPEED | RX1536EN | GIGE_EN | RXCHKSUMEN;
+	r[SPEC_ADDR1_BOT] = mac[0] | mac[1] << 8 | mac[2] << 16 | mac[3] << 24;
+	r[SPEC_ADDR1_TOP] = mac[4] | mac[5] << 8;
+	r[DMA_CFG] = TXCHKSUMEN | 0x18 << 16 | 1 << 10 | 3 << 8 | 0x10;
+
+	txdesc[0] = 0;
+	txdesc[1] = 1<<31;
+	txdesc[2] = 0;
+	txdesc[3] = 1<<31 | 1<<30;
+	txact = txdesc;
+	r[TX_QBAR] = (ulong) txdesc;
+	for(p = rxdesc, d = RXBASE; p < rxdesc + nelem(rxdesc); d += ETHLEN){
+		*p++ = d;
+		*p++ = 0;
+	}
+	p[-2] |= 2;
+	rxact = rxdesc;
+	r[RX_QBAR] = (ulong) rxdesc;
+	
+	r[NET_CTRL] = MDEN;
+//	mdwrite(r, MDCTRL, MDRESET);
+	mdwrite(r, MDCTRL, AUTONEG);
+	if((mdread(r, MDSTATUS) & LINK) == 0){
+		puts("Waiting for Link ...\n");
+		while((mdread(r, MDSTATUS) & LINK) == 0)
+			;
+	}
+	*(u32int*)(SLCR_BASE + SLCR_UNLOCK) = UNLOCK_KEY;
+	v = mdread(r, MDPHYCTRL);
+	if((v & 0x40) != 0){
+		puts("1000BASE-T");
+		while((mdread(r, MDGSTATUS) & RECVOK) != RECVOK)
+			;
+		r[NET_CFG] |= GIGE_EN;
+		*(u32int*)(SLCR_BASE + GEM0_CLK_CTRL) = 1 << 20 | 8 << 8 | 1;
+	}else if((v & 0x20) != 0){
+		puts("100BASE-TX");
+		r[NET_CFG] = r[NET_CFG] & ~GIGE_EN | SPEED;
+		*(u32int*)(SLCR_BASE + GEM0_CLK_CTRL) = 5 << 20 | 8 << 8 | 1;
+	}else if((v & 0x10) != 0){
+		puts("10BASE-T");
+		r[NET_CFG] = r[NET_CFG] & ~(GIGE_EN | SPEED);
+		*(u32int*)(SLCR_BASE + GEM0_CLK_CTRL) = 20 << 20 | 20 << 8 | 1;
+	}else
+		puts("???");
+	*(u32int*)(SLCR_BASE + SLCR_UNLOCK) = LOCK_KEY;
+	if((v & 0x08) != 0)
+		puts(" Full Duplex\n");
+	else{
+		puts(" Half Duplex\n");
+		r[NET_CFG] &= ~FDEN;
+	}
+	r[NET_CTRL] |= TXEN | RXEN;
+}
+
+void
+ethtx(ulong *r, uchar *buf, int len)
+{
+	txact[0] = (ulong) buf;
+	txact[1] = 1<<15 | len;
+	if(txact == txdesc + nelem(txdesc) - 2){
+		txact[1] |= 1<<30;
+		txact = txdesc;
+	}else
+		txact += 2;
+	r[TX_STATUS] = -1;
+	r[NET_CTRL] |= STARTTX;
+	while((r[TX_STATUS] & TXCOMPL) == 0)
+		;
+}
+
+void
+udptx(ulong *r, udp *u)
+{
+	uchar *p, *q;
+	int n;
+	
+	p = q = txbuf;
+	memcpy(p, u->edest, 6);
+	memcpy(p + 6, u->esrc, 6);
+	q += 12;
+	*q++ = 8;
+	*q++ = 0;
+
+	*q++ = 5 | 4 << 4;
+	*q++ = 0;
+	n = IPHEAD + UDPHEAD + u->len;
+	*q++ = n >> 8;
+	*q++ = n;
+
+	*q++ = 0x13;
+	*q++ = 0x37;
+	*q++ = 1<<6;
+	*q++ = 0;
+
+	*q++ = 1;
+	*q++ = 0x11;
+	*q++ = 0;
+	*q++ = 0;
+	q = u32put(q, u->isrc);
+	q = u32put(q, u->idest);
+	
+	*q++ = u->sport >> 8;
+	*q++ = u->sport;
+	*q++ = u->dport >> 8;
+	*q++ = u->dport;
+	n = UDPHEAD + u->len;
+	*q++ = n >> 8;
+	*q++ = n;
+	*q++ = 0;
+	*q++ = 0;
+	
+	memcpy(q, u->data, u->len);
+	ethtx(r, p, ETHHEAD + IPHEAD + UDPHEAD + u->len);
+}
+
+void
+dhcppkg(ulong *r, int t)
+{
+	uchar *p;
+	udp *u;
+	
+	u = &ubuf;
+	p = u->data;
+	*p++ = BOOTREQ;
+	*p++ = 1;
+	*p++ = 6;
+	*p++ = 0;
+	p = u32put(p, xid);
+	p = u32put(p, 0x8000);
+	memset(p, 0, 16);
+	u32put(p + 8, dhcpip);
+	p += 16;
+	memcpy(p, mac, 6);
+	p += 6;
+	memset(p, 0, 202);
+	p += 202;
+	*p++ = 99;
+	*p++ = 130;
+	*p++ = 83;
+	*p++ = 99;
+
+	*p++ = 53;
+	*p++ = 1;
+	*p++ = t;
+	if(t == DHCPREQUEST){
+		*p++ = 50;
+		*p++ = 4;
+		p = u32put(p, myip);
+		*p++ = 54;
+		*p++ = 4;
+		p = u32put(p, dhcpip);
+	}
+	
+	*p++ = 0xff;
+
+	memset(u->edest, 0xff, 6);
+	memcpy(u->esrc, mac, 6);
+	u->sport = 68;
+	u->dport = 67;
+	u->idest = -1;
+	u->isrc = 0;
+	u->len = p - u->data;
+	udptx(r, u);
+}
+
+uchar *
+ethrx(void)
+{
+	while((*rxact & 1) == 0)
+		if(timertrig())
+			return nil;
+	return (uchar *) (*rxact & ~3);
+}
+
+void
+ethnext(void)
+{
+	*rxact &= ~1;
+	if((*rxact & 2) != 0)
+		rxact = rxdesc;
+	else
+		rxact += 2;
+}
+
+void
+arp(int op, uchar *edest, ulong idest)
+{
+	uchar *p;
+	static uchar broad[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+	p = txbuf;
+	if(edest == nil)
+		edest = broad;
+	memcpy(p, edest, 6);
+	memcpy(p + 6, mac, 6);
+	p[12] = 8;
+	p[13] = 6;
+	p += 14;
+	p = u32put(p, 0x00010800);
+	p = u32put(p, 0x06040000 | op);
+	memcpy(p, mac, 6);
+	p = u32put(p + 6, myip);
+	memcpy(p, edest, 6);
+	p = u32put(p + 6, idest);
+	ethtx(eth0, txbuf, p - txbuf);
+}
+
+void
+arpc(uchar *p)
+{
+	p += 14;
+	if(u32get(p) != 0x00010800 || p[4] != 6 || p[5] != 4 || p[6] != 0)
+		return;
+	switch(p[7]){
+	case 1:
+		if(myip != 0 && u32get(p + 24) == myip)
+			arp(2, p + 8, u32get(p + 14));
+		break;
+	case 2:
+		if(tftpip != 0 && u32get(p + 14) == tftpip)
+			memcpy(tmac, p + 8, 6);
+		break;
+	}
+}
+
+udp *
+udprx(void)
+{
+	uchar *p;
+	ulong v;
+	udp *u;
+
+	u = &urbuf;
+	for(;; ethnext()){
+		p = ethrx();
+		if(p == nil)
+			return nil;
+		if(p[12] != 8)
+			continue;
+		if(p[13] == 6){
+			arpc(p);
+			continue;
+		}
+		if(p[13] != 0)
+			continue;
+		p += ETHHEAD;
+		if((p[0] >> 4) != 4 || p[9] != 0x11)
+			continue;
+		v = u32get(p + 16);
+		if(v != (ulong) -1 && v != myip)
+			continue;
+		u->idest = v;
+		u->isrc = u32get(p + 12);
+		p += (p[0] & 0xf) << 2;
+		u->sport = p[0] << 8 | p[1];
+		u->dport = p[2] << 8 | p[3];
+		u->len = p[4] << 8 | p[5];
+		if(u->len < 8)
+			continue;
+		u->len -= 8;
+		if(u->len >= sizeof(u->data))
+			u->len = sizeof(u->data);
+		memcpy(u->data, p + 8, u->len);
+		ethnext();
+		return u;
+	}
+}
+
+void
+arpreq(void)
+{
+	uchar *p;
+
+	arp(1, nil, tftpip);
+	timeren(ARPTIMEOUT);
+	for(;; ethnext()){
+		p = ethrx();
+		if(p == nil){
+			print("ARP timeout\n");
+			timeren(ARPTIMEOUT);
+			arp(1, nil, tftpip);
+		}
+		if(p[12] != 8 || p[13] != 6)
+			continue;
+		arpc(p);
+		if(tmac[0] != 0xff)
+			break;
+	}
+	timeren(-1);
+}
+
+void
+dhcp(ulong *r)
+{
+	udp *u;
+	uchar *p;
+	uchar type;
+
+	xid = 0xdeadbeef;
+	tftpip = 0;
+	dhcppkg(r, DHCPDISCOVER);
+	timeren(DHCPTIMEOUT);
+	for(;;){
+		u = udprx();
+		if(u == nil){
+			timeren(DHCPTIMEOUT);
+			dhcppkg(r, DHCPDISCOVER);
+			print("DHCP timeout\n");
+		}
+		p = u->data;
+		if(u->dport != 68 || p[0] != 2 || u32get(p + 4) != xid || u32get(p + 236) != 0x63825363)
+			continue;
+		p += 240;
+		type = 0;
+		dhcpip = 0;
+		for(; p < u->data + u->len && *p != 0xff; p += 2 + p[1])
+			switch(*p){
+			case 53:
+				type = p[2];
+				break;
+			case 54:
+				dhcpip = u32get(p + 2);
+				break;
+			}
+		if(type != DHCPOFFER)
+			continue;
+		p = u->data;
+		if(p[108] == 0){
+			print("Offer from %I for %I with no boot file\n", dhcpip, u32get(p + 16));
+			continue;
+		}
+		myip = u32get(p + 16);
+		tftpip = u32get(p + 20);
+		memcpy(file, p + 108, 128);
+		print("Offer from %I for %I with boot file '%s' at %I\n", dhcpip, myip, file, tftpip);
+		break;
+	}
+	timeren(-1);
+	dhcppkg(r, DHCPREQUEST);
+}
+
+udp *
+tftppkg(void)
+{
+	udp *u;
+	
+	u = &ubuf;
+	memcpy(u->edest, tmac, 6);
+	memcpy(u->esrc, mac, 6);
+	u->idest = tftpip;
+	u->isrc = myip;
+	u->sport = 69;
+	u->dport = 69;
+	return u;
+}
+
+void
+tftp(ulong *r, char *q, uintptr base)
+{
+	udp *u, *v;
+	uchar *p;
+	int bn, len;
+
+restart:
+	u = tftppkg();
+	p = u->data;
+	*p++ = 0;
+	*p++ = 1;
+	do
+		*p++ = *q;
+	while(*q++ != 0);
+	memcpy(p, "octet", 6);
+	p += 6;
+	u->len = p - u->data;
+	udptx(r, u);
+	timeren(TFTPTIMEOUT);
+	
+	for(;;){
+		v = udprx();
+		if(v == nil){
+			print("TFTP timeout");
+			goto restart;
+		}
+		if(v->dport != 69 || v->isrc != tftpip || v->idest != myip)
+			continue;
+		if(v->data[0] != 0)
+			continue;
+		switch(v->data[1]){
+		case 3:
+			bn = v->data[2] << 8 | v->data[3];
+			len = v->len - 4;
+			if(len < 0)
+				continue;
+			if(len > 512)
+				len = 512;
+			memcpy((char*)base + ((bn - 1) << 9), v->data + 4, len);
+			if((bn & 127) == 0)
+				putc('.');
+			p = u->data;
+			*p++ = 0;
+			*p++ = 4;
+			*p++ = bn >> 8;
+			*p = bn;
+			u->len = 4;
+			udptx(r, u);
+			if(len < 512){
+				putc(10);
+				timeren(-1);
+				return;
+			}
+			timeren(TFTPTIMEOUT);
+			break;
+		case 5:
+			v->data[v->len - 1] = 0;
+			print("TFTP error: %s\n", v->data + 4);
+			timeren(-1);
+			return;
+		}
+	}
+}
+
+int
+netboot(void)
+{
+	ethinit(eth0);
+	myip = 0;
+	dhcp(eth0);
+	arpreq();
+	tftp(eth0, file, TZERO);
+	memset((void *) CONF, 0, CONFSIZE);
+	tftp(eth0, "/cfg/pxe/0ea7deadbeef", CONF);
+	return 1;
+}
--- /dev/null
+++ b/os/boot/zynq/qspi.c
@@ -1,0 +1,45 @@
+#include <u.h>
+#include "dat.h"
+#include "fns.h"
+
+enum {
+	QSPI_CFG,
+	QSPI_STATUS,
+	QSPI_EN = 5,
+	QSPI_TXD4 = 7,
+	QSPI_RXD,
+	QSPI_TXD1 = 32,
+	QSPI_TXD2,
+	QSPI_TXD3
+};
+
+#define QSPI0 ((void *) 0xE000D000)
+
+static u32int
+cmd(ulong *r, int sz, u32int c)
+{
+	if(sz == 4)
+		r[QSPI_TXD4] = c;
+	else
+		r[QSPI_TXD1 + sz - 1] = c;
+	r[QSPI_CFG] |= 1<<16;
+	while((r[QSPI_STATUS] & (1<<2|1<<4)) != (1<<2|1<<4))
+		;
+	return r[QSPI_RXD];
+}
+
+void
+flash(void)
+{
+	ulong *r;
+	
+	r = QSPI0;
+	r[QSPI_CFG] = 1<<31 | 1<<19 | 3<<6 | 1<<15 | 1<<14 | 1<<10 | 1<<3 | 1;
+	r[QSPI_CFG] &= ~(1<<10);
+	r[QSPI_EN] = 1;
+	cmd(r, 1, 0x06);
+//	cmd(r, 3, 0xD8);
+	for(;;)
+		print("%x\n", cmd(r, 2, 0x05));
+	
+}