shithub: riscv

Download patch

ref: 7a3f0998a0ce7470d70c1a13bc4646abafdcc236
parent: 6dafa424805d128fcd08c71dca3e3abdc40aa6c6
author: aiju <aiju@phicode.de>
date: Wed Dec 24 05:21:51 EST 2014

added zynq kernel

diff: cannot open b/sys/src/9/zynq//null: file does not exist: 'b/sys/src/9/zynq//null' diff: cannot open b/sys/src/boot/zynq//null: file does not exist: 'b/sys/src/boot/zynq//null'
--- /dev/null
+++ b/sys/src/9/zynq/dat.h
@@ -1,0 +1,196 @@
+typedef struct Conf	Conf;
+typedef struct Confmem	Confmem;
+typedef struct FPsave	FPsave;
+typedef struct L1	L1;
+typedef struct Label	Label;
+typedef struct Lock	Lock;
+typedef struct KMap	KMap;
+typedef struct MMMU	MMMU;
+typedef struct Mach	Mach;
+typedef struct Notsave	Notsave;
+typedef struct Page	Page;
+typedef struct Proc	Proc;
+typedef struct PMMU	PMMU;
+typedef u32int		PTE;
+typedef struct Ureg	Ureg;
+typedef struct ISAConf	ISAConf;
+typedef uvlong		Tval;
+
+#pragma incomplete Ureg
+
+#define MAXSYSARG	5	/* for mount(fd, mpt, flag, arg, srv) */
+
+#define AOUT_MAGIC	(E_MAGIC)
+
+struct Lock
+{
+	ulong	key;
+	u32int	sr;
+	uintptr	pc;
+	Proc*	p;
+	Mach*	m;
+	int	isilock;
+};
+
+struct Label
+{
+	uintptr	sp;
+	uintptr	pc;
+};
+
+struct FPsave
+{
+	ulong	exc, scr;
+	uchar	regs[256];
+};
+
+/*
+ * FPsave.status
+ */
+enum
+{
+	FPinit,
+	FPactive,
+	FPinactive,
+	FPillegal = 0x100
+};
+
+struct Confmem
+{
+	uintptr	base;
+	uintptr	limit;
+	usize	npage;
+	uintptr	kbase;
+	uintptr	klimit;
+};
+
+struct Conf
+{
+	ulong	nmach;		/* processors */
+	ulong	nproc;		/* processes */
+	Confmem	mem[1];		/* physical memory */
+	ulong	npage;		/* total physical pages of memory */
+	usize	upages;		/* user page pool */
+	ulong	copymode;	/* 0 is copy on write, 1 is copy on reference */
+	ulong	ialloc;		/* max interrupt time allocation in bytes */
+	ulong	pipeqsize;	/* size in bytes of pipe queues */
+	ulong	nimage;		/* number of page cache image headers */
+	ulong	nswap;		/* number of swap pages */
+	int	nswppo;		/* max # of pageouts per segment pass */
+	int	monitor;
+};
+
+/*
+ *  things saved in the Proc structure during a notify
+ */
+struct Notsave {
+	int	emptiness;
+};
+
+/*
+ *  MMU stuff in proc
+ */
+#define NCOLOR	1
+
+struct PMMU
+{
+	L1 *l1;
+	Page *mmuused, *mmufree;
+	
+	int nkmap;
+	Page *kmaptable;
+};
+
+#include "../port/portdat.h"
+
+struct L1
+{
+	Ref;
+	uintptr pa;
+	ulong *va;
+	L1 *next;
+};
+
+struct MMMU
+{
+	L1 l1;
+	L1 *l1free;
+	int nfree;
+	uchar asid;
+};
+
+struct Mach
+{
+	/* known to assembly */
+	int	machno;			/* physical id of processor */
+	uintptr	splpc;			/* pc of last caller to splhi */
+	Proc*	proc;			/* current process */
+	ulong	excregs[3];
+	ulong	cycleshi;
+	/* end of known to assembly */
+
+	int	flushmmu;		/* flush current proc mmu state */
+
+	ulong	ticks;			/* of the clock since boot time */
+	Label	sched;			/* scheduler wakeup */
+	Lock	alarmlock;		/* access to alarm list */
+	void*	alarm;			/* alarms bound to this clock */
+	int	inclockintr;
+
+	Proc*	readied;		/* for runproc */
+	ulong	schedticks;		/* next forced context switch */
+
+	int	cputype;
+	ulong	delayloop;
+
+	/* stats */
+	int	tlbfault;
+	int	tlbpurge;
+	int	pfault;
+	int	cs;
+	int	syscall;
+	int	load;
+	int	intr;
+	uvlong	fastclock;		/* last sampled value */
+	uvlong	inidle;			/* time spent in idlehands() */
+//	ulong	spuriousintr;
+	int	lastintr;
+	int	ilockdepth;
+	Perf	perf;			/* performance counters */
+
+
+	int	cpumhz;
+	uvlong	cpuhz;			/* speed of cpu */
+	uvlong	cyclefreq;		/* Frequency of user readable cycle counter */
+	
+	MMMU;
+
+	int	stack[1];
+};
+
+struct ISAConf
+{
+	int dummy;
+	char *type;
+	ulong port;
+	int irq;
+};
+#define BUSUNKNOWN -1
+
+struct
+{
+	Lock;
+	int	machs;			/* bitmap of active CPUs */
+	int	exiting;		/* shutdown */
+	int	ispanic;		/* shutdown in response to a panic */
+}active;
+
+extern register Mach* m;			/* R10 */
+extern register Proc* up;			/* R9 */
+
+extern int normalprint;
+
+extern ulong *mpcore, *slcr;
+
+void nope(void);
+#define NOPE nope();
--- /dev/null
+++ b/sys/src/9/zynq/devarch.c
@@ -1,0 +1,391 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "ureg.h"
+#include "../port/error.h"
+
+enum {
+	Qdir = 0,
+	Qtemp,
+	Qpl,
+	Qbase,
+
+	Qmax = 16,
+};
+
+static Dirtab archdir[Qmax] = {
+	".",		{ Qdir, 0, QTDIR },	0,	0555,
+	"temp",		{ Qtemp, 0},		0,	0440,
+	"pl",		{ Qpl, 0 }, 		0,	0660,
+};
+static int narchdir = Qbase;
+
+int temp = -128;
+ulong *devc;
+int dmadone;
+QLock pllock;
+enum { PLBUFSIZ = 8192 };
+uchar *plbuf;
+Rendez plinitr, pldoner, pldmar;
+
+enum {
+	DEVCTRL = 0,
+	DEVISTS = 0xc/4,
+	DEVMASK,
+	DEVSTS,
+	DMASRC = 0x18/4,
+	DMADST,
+	DMASRCL,
+	DMADSTL,
+	XADCCFG = 0x100/4,
+	XADCSTS,
+	XADCMASK,
+	XADCMSTS,
+	XADCCMD,
+	XADCREAD,
+	XADCMCTL,
+	
+	FPGA0_CLK_CTRL = 0x170/4,
+};
+
+enum {
+	PROG = 1<<30,
+	DONE = 1<<2,
+	INITPE = 1<<1,
+	INIT = 1<<4,
+	DMADONE = 1<<13,
+};
+
+static void
+scram(void)
+{
+	splhi();
+	slcr[0x100/4] |= 1<<4;
+	slcr[0x104/4] |= 1<<4;
+	slcr[0x108/4] |= 1<<4;
+	slcr[DEVCTRL] &= ~PROG;
+	slcr[0x244/4] = 1<<4|1<<5;
+}
+
+static void
+xadcirq(Ureg *, void *)
+{
+	int v;
+	static int al, notfirst;
+	
+	while((devc[XADCMSTS] & 1<<8) == 0){
+		v = ((u16int)devc[XADCREAD]) >> 4;
+		if(v == 0){
+			if(notfirst)
+				print("temperature sensor reads 0, shouldn't happen\n");
+			break;
+		}
+		notfirst = 1;
+		temp = v * 5040 / 4096 - 2732;
+		if(temp >= 800){
+			if(al == 0)
+				print("temperature exceeds 80 deg C\n");
+			al = 1;
+		}
+		if(temp <= 750)
+			al = 0;
+		if(temp >= 900){
+			print("chip temperature exceeds 90 deg C, shutting down");
+			scram();
+		}
+	}
+	devc[XADCSTS] = -1;
+}
+
+static void
+xadctimer(void)
+{
+	devc[XADCCMD] = 1<<26 | 0<<16;
+}
+
+static void
+xadcinit(void)
+{
+	int i;
+	int x;
+
+	devc = vmap(DEVC_BASE, 0x11C);
+	devc[XADCMCTL] |= 1<<4;
+	devc[XADCMCTL] &= ~(1<<4);
+	devc[XADCCMD] = 0x08030000;
+	for(i = 0; i < 15; i++)
+		devc[XADCCMD] = 0;
+	while((devc[XADCMSTS] & 1<<10) == 0)
+		;
+	while((devc[XADCMSTS] & 1<<8) == 0){
+		x = devc[XADCREAD];
+		USED(x);
+	}
+	devc[XADCCFG] = 0x80001114;
+	devc[XADCMASK] = ~(1<<8);
+	devc[XADCSTS] = -1;
+	intrenable(XADCIRQ, xadcirq, nil, LEVEL, "xadc");
+	addclock0link(xadctimer, XADCINTERVAL);
+}
+
+static int
+isplinit(void *)
+{
+	return devc[DEVSTS] & INIT;
+}
+
+static int
+ispldone(void *)
+{
+	return devc[DEVISTS] & DONE;
+}
+
+static int
+isdmadone(void *)
+{
+	return dmadone;
+}
+
+static void
+plirq(Ureg *, void *)
+{
+	ulong fl;
+	
+	fl = devc[DEVISTS];
+	if((fl & INITPE) != 0)
+		wakeup(&plinitr);
+	if((fl & DONE) != 0){
+		slcr[0x900/4] = 0xf;
+		slcr[0x240/4] = 0;
+		print("DONE!\n");
+		devc[DEVMASK] |= DONE;
+		wakeup(&pldoner);
+	}
+	if((fl & DMADONE) != 0){
+		dmadone++;
+		wakeup(&pldmar);
+	}
+	devc[DEVISTS] = fl;
+}
+
+static void
+plinit(void)
+{
+	Physseg seg;
+
+	devc[DEVCTRL] &= ~(PROG|1<<25);
+	devc[DEVCTRL] |= 3<<26|PROG;
+	devc[DEVISTS] = -1;
+	devc[DEVMASK] = ~(DONE|INITPE|DMADONE);
+	intrenable(DEVCIRQ, plirq, nil, LEVEL, "pl");
+	
+	slcr[FPGA0_CLK_CTRL] = 1<<20 | 10<<8;
+
+	memset(&seg, 0, sizeof seg);
+	seg.attr = SG_PHYSICAL;
+	seg.name = "axi";
+	seg.pa = 0x40000000;
+	seg.size = 0x8000000;
+	addphysseg(&seg);
+}
+
+static void
+plconf(void)
+{
+	if(!canqlock(&pllock))
+		error(Einuse);
+	slcr[0x240/4] = 0xf;
+	slcr[0x900/4] = 0xa;
+	dmadone = 1;
+	devc[DEVISTS] = DONE|INITPE|DMADONE;
+	devc[DEVCTRL] |= PROG;
+	devc[DEVCTRL] &= ~PROG;
+	devc[DEVMASK] &= ~DONE;
+	devc[DEVCTRL] |= PROG;
+	sleep(&plinitr, isplinit, nil);
+	plbuf = smalloc(PLBUFSIZ);
+}
+
+static long
+plwrite(uintptr pa, long n)
+{
+	long w;
+	
+	w = n >> 2;
+	sleep(&pldmar, isdmadone, nil);
+	dmadone = 0;
+	coherence();
+	devc[DMASRC] = pa;
+	devc[DMADST] = -1;
+	devc[DMASRCL] = w;
+	devc[DMADSTL] = 0;
+	return n;
+}
+
+static long
+plcopy(uchar *d, long n)
+{
+	long ret;
+	ulong nn;
+	uintptr pa;
+	
+	if((n & 3) != 0 || n <= 0)
+		error(Eshort);
+	ret = n;
+	pa = PADDR(plbuf);
+	while(n > 0){
+		if(n > PLBUFSIZ)
+			nn = PLBUFSIZ;
+		else
+			nn = n;
+		memmove(plbuf, d, nn);
+		cleandse(plbuf, plbuf + nn);
+		clean2pa(pa, pa + nn);
+		n -= plwrite(pa, nn);
+	}
+	return ret;
+}
+
+static long
+userdma(void *a, long n, long (*f)(uintptr, long))
+{
+	ulong s;
+	void *va;
+	uintptr pa;
+	long off, nn, ret;
+
+	evenaddr((uintptr) a);
+	if((n & 3) != 0)
+		error(Eshort);
+	
+	ret = n;
+	while(n > 0){
+		off = (uintptr)a & BY2PG - 1;
+		va = (void *) ((uintptr)a & ~(BY2PG - 1));
+		s = splhi();
+		while(pa = palookur(va), (pa & 1) != 0){
+			splx(s);
+			if(fault(pa, 1) < 0)
+				error(Egreg);
+			s = splhi();
+		}
+		if(off + n >= BY2PG)
+			nn = BY2PG - off;
+		else
+			nn = n;
+		pa = (pa & ~(BY2PG - 1)) + off;
+		cleandse((char *) a + off, (char*) a + off + nn);
+		clean2pa(pa, pa + nn);
+		n -= f(pa, nn);
+		splx(s);
+		a = (char *) va + BY2PG;
+	}
+	return ret;
+}
+
+void
+archinit(void)
+{
+	slcr[2] = 0xDF0D;
+	xadcinit();
+	plinit();
+}
+
+static long
+archread(Chan *c, void *a, long n, vlong offset)
+{
+	char buf[64];
+
+	switch((ulong)c->qid.path){
+	case Qdir:
+		return devdirread(c, a, n, archdir, narchdir, devgen);
+	case Qtemp:
+		snprint(buf, sizeof(buf), "%d.%d\n", temp/10, temp%10);
+		return readstr(offset, a, n, buf);
+	case Qpl:
+		sleep(&pldoner, ispldone, nil);
+		return 0;
+	default:
+		error(Egreg);
+		return -1;
+	}
+}
+
+static long
+archwrite(Chan *c, void *a, long n, vlong offset)
+{
+	switch((ulong)c->qid.path){
+	case Qpl:
+		return plcopy(a, n);
+	default:
+		error(Egreg);
+		return -1;
+	}
+}
+
+Walkqid*
+archwalk(Chan* c, Chan *nc, char** name, int nname)
+{
+	return devwalk(c, nc, name, nname, archdir, narchdir, devgen);
+}
+
+static int
+archstat(Chan* c, uchar* dp, int n)
+{
+	return devstat(c, dp, n, archdir, narchdir, devgen);
+}
+
+static Chan*
+archopen(Chan* c, int omode)
+{
+	devopen(c, omode, archdir, narchdir, devgen);
+	if((ulong)c->qid.path == Qpl && c->mode == OWRITE)
+		plconf();
+	return c;
+}
+
+static void
+archclose(Chan* c)
+{
+	if((ulong)c->qid.path == Qpl && c->mode == OWRITE){
+	/*	cleandse(plbuf, plbuf + plbufn);
+		clean2pa(PADDR(plbuf), PADDR(plbuf) + plbufn);
+		plwrite(PADDR(plbuf), 4096);
+		plwrite(PADDR(plbuf) + 4096, plbufn - 4096);
+		plbufn = 0;*/
+		free(plbuf);
+		plbuf = nil;
+		qunlock(&pllock);
+	}
+}
+
+static Chan*
+archattach(char* spec)
+{
+	return devattach('P', spec);
+}
+
+Dev archdevtab = {
+	'P',
+	"arch",
+	
+	devreset,
+	devinit,
+	devshutdown,
+	archattach,
+	archwalk,
+	archstat,
+	archopen,
+	devcreate,
+	archclose,
+	archread,
+	devbread,
+	archwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
--- /dev/null
+++ b/sys/src/9/zynq/devether.c
@@ -1,0 +1,517 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "pool.h"
+#include "ureg.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+
+#include "etherif.h"
+
+static Ether *etherxx[MaxEther];
+
+Chan*
+etherattach(char* spec)
+{
+	ulong ctlrno;
+	char *p;
+	Chan *chan;
+
+	ctlrno = 0;
+	if(spec && *spec){
+		ctlrno = strtoul(spec, &p, 0);
+		if((ctlrno == 0 && p == spec) || *p || (ctlrno >= MaxEther))
+			error(Ebadarg);
+	}
+	if(etherxx[ctlrno] == 0)
+		error(Enodev);
+
+	chan = devattach('l', spec);
+	if(waserror()){
+		chanfree(chan);
+		nexterror();
+	}
+	chan->dev = ctlrno;
+	if(etherxx[ctlrno]->attach)
+		etherxx[ctlrno]->attach(etherxx[ctlrno]);
+	poperror();
+	return chan;
+}
+
+static Walkqid*
+etherwalk(Chan* chan, Chan* nchan, char** name, int nname)
+{
+	return netifwalk(etherxx[chan->dev], chan, nchan, name, nname);
+}
+
+static int
+etherstat(Chan* chan, uchar* dp, int n)
+{
+	return netifstat(etherxx[chan->dev], chan, dp, n);
+}
+
+static Chan*
+etheropen(Chan* chan, int omode)
+{
+	return netifopen(etherxx[chan->dev], chan, omode);
+}
+
+static Chan*
+ethercreate(Chan*, char*, int, ulong)
+{
+	error(Eperm);
+	return 0;
+}
+
+static void
+etherclose(Chan* chan)
+{
+	netifclose(etherxx[chan->dev], chan);
+}
+
+static long
+etherread(Chan* chan, void* buf, long n, vlong off)
+{
+	Ether *ether;
+	ulong offset = off;
+
+	ether = etherxx[chan->dev];
+	if((chan->qid.type & QTDIR) == 0 && ether->ifstat){
+		/*
+		 * With some controllers it is necessary to reach
+		 * into the chip to extract statistics.
+		 */
+		if(NETTYPE(chan->qid.path) == Nifstatqid)
+			return ether->ifstat(ether, buf, n, offset);
+		else if(NETTYPE(chan->qid.path) == Nstatqid)
+			ether->ifstat(ether, buf, 0, offset);
+	}
+
+	return netifread(ether, chan, buf, n, offset);
+}
+
+static Block*
+etherbread(Chan* chan, long n, ulong offset)
+{
+	return netifbread(etherxx[chan->dev], chan, n, offset);
+}
+
+static int
+etherwstat(Chan* chan, uchar* dp, int n)
+{
+	return netifwstat(etherxx[chan->dev], chan, dp, n);
+}
+
+static void
+etherrtrace(Netfile* f, Etherpkt* pkt, int len)
+{
+	int i, n;
+	Block *bp;
+
+	if(qwindow(f->in) <= 0)
+		return;
+	if(len > 58)
+		n = 58;
+	else
+		n = len;
+	bp = iallocb(64);
+	if(bp == nil)
+		return;
+	memmove(bp->wp, pkt->d, n);
+	i = TK2MS(MACHP(0)->ticks);
+	bp->wp[58] = len>>8;
+	bp->wp[59] = len;
+	bp->wp[60] = i>>24;
+	bp->wp[61] = i>>16;
+	bp->wp[62] = i>>8;
+	bp->wp[63] = i;
+	bp->wp += 64;
+	qpass(f->in, bp);
+}
+
+Block*
+etheriq(Ether* ether, Block* bp, int fromwire)
+{
+	Etherpkt *pkt;
+	ushort type;
+	int len, multi, tome, fromme;
+	Netfile **ep, *f, **fp, *fx;
+	Block *xbp;
+
+	ether->inpackets++;
+
+	pkt = (Etherpkt*)bp->rp;
+	len = BLEN(bp);
+	type = (pkt->type[0]<<8)|pkt->type[1];
+	fx = 0;
+	ep = &ether->f[Ntypes];
+
+	multi = pkt->d[0] & 1;
+	/* check for valid multicast addresses */
+	if(multi && memcmp(pkt->d, ether->bcast, sizeof(pkt->d)) != 0 && ether->prom == 0){
+		if(!activemulti(ether, pkt->d, sizeof(pkt->d))){
+			if(fromwire){
+				freeb(bp);
+				bp = 0;
+			}
+			return bp;
+		}
+	}
+
+	/* is it for me? */
+	tome = memcmp(pkt->d, ether->ea, sizeof(pkt->d)) == 0;
+	fromme = memcmp(pkt->s, ether->ea, sizeof(pkt->s)) == 0;
+
+	/*
+	 * Multiplex the packet to all the connections which want it.
+	 * If the packet is not to be used subsequently (fromwire != 0),
+	 * attempt to simply pass it into one of the connections, thereby
+	 * saving a copy of the data (usual case hopefully).
+	 */
+	for(fp = ether->f; fp < ep; fp++){
+		if(f = *fp)
+		if(f->type == type || f->type < 0)
+		if(tome || multi || f->prom){
+			/* Don't want to hear bridged packets */
+			if(f->bridge && !fromwire && !fromme)
+				continue;
+			if(!f->headersonly){
+				if(fromwire && fx == 0)
+					fx = f;
+				else if(xbp = iallocb(len)){
+					memmove(xbp->wp, pkt, len);
+					xbp->wp += len;
+					if(qpass(f->in, xbp) < 0) {
+						// print("soverflow for f->in\n");
+						ether->soverflows++;
+					}
+				}
+				else {
+					// print("soverflow iallocb\n");
+					ether->soverflows++;
+				}
+			}
+			else
+				etherrtrace(f, pkt, len);
+		}
+	}
+
+	if(fx){
+		if(qpass(fx->in, bp) < 0) {
+			// print("soverflow for fx->in\n");
+			ether->soverflows++;
+		}
+		return 0;
+	}
+	if(fromwire){
+		freeb(bp);
+		return 0;
+	}
+
+	return bp;
+}
+
+static int
+etheroq(Ether* ether, Block* bp)
+{
+	int len, loopback;
+	Etherpkt *pkt;
+
+	ether->outpackets++;
+
+	/*
+	 * Check if the packet has to be placed back onto the input queue,
+	 * i.e. if it's a loopback or broadcast packet or the interface is
+	 * in promiscuous mode.
+	 * If it's a loopback packet indicate to etheriq that the data isn't
+	 * needed and return, etheriq will pass-on or free the block.
+	 * To enable bridging to work, only packets that were originated
+	 * by this interface are fed back.
+	 */
+	pkt = (Etherpkt*)bp->rp;
+	len = BLEN(bp);
+	loopback = memcmp(pkt->d, ether->ea, sizeof(pkt->d)) == 0;
+	if(loopback || memcmp(pkt->d, ether->bcast, sizeof(pkt->d)) == 0 || ether->prom)
+		if(etheriq(ether, bp, loopback) == 0)
+			return len;
+
+	qbwrite(ether->oq, bp);
+	if(ether->transmit != nil)
+		ether->transmit(ether);
+	return len;
+}
+
+static long
+etherwrite(Chan* chan, void* buf, long n, vlong)
+{
+	Ether *ether;
+	Block *bp;
+	int nn, onoff;
+	Cmdbuf *cb;
+
+	ether = etherxx[chan->dev];
+	if(NETTYPE(chan->qid.path) != Ndataqid) {
+		nn = netifwrite(ether, chan, buf, n);
+		if(nn >= 0)
+			return nn;
+		cb = parsecmd(buf, n);
+		if(cb->f[0] && strcmp(cb->f[0], "nonblocking") == 0){
+			if(cb->nf <= 1)
+				onoff = 1;
+			else
+				onoff = atoi(cb->f[1]);
+			qnoblock(ether->oq, onoff);
+			free(cb);
+			return n;
+		}
+		free(cb);
+		if(ether->ctl!=nil)
+			return ether->ctl(ether,buf,n);
+
+		error(Ebadctl);
+	}
+
+	if(n > ether->maxmtu)
+		error(Etoobig);
+	if(n < ether->minmtu)
+		error(Etoosmall);
+
+	bp = allocb(n);
+	if(waserror()){
+		freeb(bp);
+		nexterror();
+	}
+	memmove(bp->rp, buf, n);
+	memmove(bp->rp+Eaddrlen, ether->ea, Eaddrlen);
+	poperror();
+	bp->wp += n;
+
+	return etheroq(ether, bp);
+}
+
+static long
+etherbwrite(Chan* chan, Block* bp, ulong)
+{
+	Ether *ether;
+	long n;
+
+	n = BLEN(bp);
+	if(NETTYPE(chan->qid.path) != Ndataqid){
+		if(waserror()) {
+			freeb(bp);
+			nexterror();
+		}
+		n = etherwrite(chan, bp->rp, n, 0);
+		poperror();
+		freeb(bp);
+		return n;
+	}
+	ether = etherxx[chan->dev];
+
+	if(n > ether->maxmtu){
+		freeb(bp);
+		error(Etoobig);
+	}
+	if(n < ether->minmtu){
+		freeb(bp);
+		error(Etoosmall);
+	}
+
+	return etheroq(ether, bp);
+}
+
+static struct {
+	char*	type;
+	int	(*reset)(Ether*);
+} cards[MaxEther+1];
+
+void
+addethercard(char* t, int (*r)(Ether*))
+{
+	static int ncard;
+
+	if(ncard == MaxEther)
+		panic("too many ether cards");
+	cards[ncard].type = t;
+	cards[ncard].reset = r;
+	ncard++;
+}
+
+int
+parseether(uchar *to, char *from)
+{
+	char nip[4];
+	char *p;
+	int i;
+
+	p = from;
+	for(i = 0; i < Eaddrlen; i++){
+		if(*p == 0)
+			return -1;
+		nip[0] = *p++;
+		if(*p == 0)
+			return -1;
+		nip[1] = *p++;
+		nip[2] = 0;
+		to[i] = strtoul(nip, 0, 16);
+		if(*p == ':')
+			p++;
+	}
+	return 0;
+}
+
+static Ether*
+etherprobe(int cardno, int ctlrno)
+{
+	int i, lg;
+	ulong mb, bsz;
+	Ether *ether;
+	char buf[128], name[32];
+
+	ether = malloc(sizeof(Ether));
+	if(ether == nil){
+		print("etherprobe: no memory for Ether\n");
+		return nil;
+	}
+	memset(ether, 0, sizeof(Ether));
+	ether->ctlrno = ctlrno;
+	ether->mbps = 10;
+	ether->minmtu = ETHERMINTU;
+	ether->maxmtu = ETHERMAXTU;
+
+	if(cardno >= MaxEther || cards[cardno].type == nil){
+		free(ether);
+		return nil;
+	}
+	if(cards[cardno].reset(ether) < 0){
+		free(ether);
+		return nil;
+	}
+
+	snprint(name, sizeof(name), "ether%d", ctlrno);
+
+	intrenable(ether->irq, ether->interrupt, ether, ether->irqlevel, name);
+
+	i = sprint(buf, "#l%d: %s: %dMbps port 0x%luX irq %d",
+		ctlrno, cards[cardno].type, ether->mbps, ether->port, ether->irq);
+	i += sprint(buf+i, ": %2.2ux%2.2ux%2.2ux%2.2ux%2.2ux%2.2ux",
+		ether->ea[0], ether->ea[1], ether->ea[2],
+		ether->ea[3], ether->ea[4], ether->ea[5]);
+	sprint(buf+i, "\n");
+	print(buf);
+
+	/* compute log10(ether->mbps) into lg */
+	for(lg = 0, mb = ether->mbps; mb >= 10; lg++)
+		mb /= 10;
+	if (lg > 0)
+		lg--;
+	if (lg > 14)			/* 2^(14+17) = 2³¹ */
+		lg = 14;
+	/* allocate larger output queues for higher-speed interfaces */
+	bsz = 1UL << (lg + 17);		/* 2¹⁷ = 128K, bsz = 2ⁿ × 128K */
+	while (bsz > mainmem->maxsize / 8 && bsz > 128*1024)
+		bsz /= 2;
+
+	netifinit(ether, name, Ntypes, bsz);
+	if(ether->oq == nil) {
+		ether->oq = qopen(bsz, Qmsg, 0, 0);
+		ether->limit = bsz;
+	}
+	if(ether->oq == nil)
+		panic("etherreset %s: can't allocate output queue of %ld bytes", name, bsz);
+	ether->alen = Eaddrlen;
+	memmove(ether->addr, ether->ea, Eaddrlen);
+	memset(ether->bcast, 0xFF, Eaddrlen);
+
+	return ether;
+}
+
+static void
+etherreset(void)
+{
+	Ether *ether;
+	int cardno, ctlrno;
+
+	for(ctlrno = 0; ctlrno < MaxEther; ctlrno++){
+		if((ether = etherprobe(-1, ctlrno)) == nil)
+			continue;
+		etherxx[ctlrno] = ether;
+	}
+
+	cardno = ctlrno = 0;
+	while(cards[cardno].type != nil && ctlrno < MaxEther){
+		if(etherxx[ctlrno] != nil){
+			ctlrno++;
+			continue;
+		}
+		if((ether = etherprobe(cardno, ctlrno)) == nil){
+			cardno++;
+			continue;
+		}
+		etherxx[ctlrno] = ether;
+		ctlrno++;
+	}
+}
+
+static void
+ethershutdown(void)
+{
+	Ether *ether;
+	int i;
+
+	for(i = 0; i < MaxEther; i++){
+		ether = etherxx[i];
+		if(ether == nil)
+			continue;
+		if(ether->shutdown == nil) {
+			print("#l%d: no shutdown function\n", i);
+			continue;
+		}
+		(*ether->shutdown)(ether);
+	}
+}
+
+
+#define POLY 0xedb88320
+
+/* really slow 32 bit crc for ethers */
+ulong
+ethercrc(uchar *p, int len)
+{
+	int i, j;
+	ulong crc, b;
+
+	crc = 0xffffffff;
+	for(i = 0; i < len; i++){
+		b = *p++;
+		for(j = 0; j < 8; j++){
+			crc = (crc>>1) ^ (((crc^b) & 1) ? POLY : 0);
+			b >>= 1;
+		}
+	}
+	return crc;
+}
+
+Dev etherdevtab = {
+	'l',
+	"ether",
+
+	etherreset,
+	devinit,
+	ethershutdown,
+	etherattach,
+	etherwalk,
+	etherstat,
+	etheropen,
+	ethercreate,
+	etherclose,
+	etherread,
+	etherbread,
+	etherwrite,
+	etherbwrite,
+	devremove,
+	etherwstat,
+};
--- /dev/null
+++ b/sys/src/9/zynq/devqspi.c
@@ -1,0 +1,275 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "ureg.h"
+#include "../port/error.h"
+
+enum {
+	QSPIDIV = 1,
+	QSPISIZ = 1<<24,
+};
+
+enum {
+	CONF,
+	INTSTAT,
+	INTEN,
+	INTDIS,
+	INTMASK,
+	SPIEN,
+	DELAY,
+	TXD0,
+	RXD,
+	IDLE,
+	TXTHRES,
+	RXTHRES,
+	TXD1 = 0x80/4,
+	TXD2,
+	TXD3,
+	LQSPICFG = 0xA0/4,
+};
+
+enum {
+	TXFULL = 1<<3,
+	TXNFULL = 1<<2,
+	RXNEMPTY = 1<<4,
+	STARTCOM = 1<<16,
+};
+
+enum {
+	Qdir = 0,
+	Qboot,
+	Qbase,
+
+	Qmax = 16,
+};
+
+static ulong *qspi;
+static QLock qspil;
+
+static Dirtab qspidir[Qmax] = {
+	".",		{ Qdir, 0, QTDIR },	0,	0555,
+	"boot",		{ Qboot, 0},		65536,	0640,
+};
+static int nqspidir = Qbase;
+
+static ulong
+qspicmd(int n, ulong d)
+{
+	while((qspi[INTSTAT] & TXNFULL) == 0)
+		;
+	if(n == 4)
+		qspi[TXD0] = d;
+	else
+		qspi[TXD1 - 1 + n] = d;
+	qspi[CONF] |= STARTCOM;
+	while((qspi[INTSTAT] & (TXNFULL|RXNEMPTY)) != (TXNFULL|RXNEMPTY))
+		;
+	return qspi[RXD];
+}
+
+static void
+qspiinit(void)
+{
+	static int done;
+	
+	if(done)
+		return;
+	qspi = vmap(QSPI_BASE, 0x100);
+	qspi[LQSPICFG] &= ~(1<<31);
+	qspi[CONF] = 1<<31 | 1<<19 | 1<<15 | 1<<14 | 1<<10 | 3<<6 | QSPIDIV<<3 | 1;
+	qspi[SPIEN] = 1;	
+}
+
+static void
+qspisel(int sel)
+{
+	if(sel)
+		qspi[CONF] &= ~(1<<10);
+	else
+		qspi[CONF] |= 1<<10;
+}
+
+static void
+waitbusy(void)
+{
+	ulong d;
+
+	for(;;){
+		qspisel(1);
+		d = qspicmd(2, 0x05);
+		qspisel(0);
+		if((d & 1<<24) == 0)
+			break;
+		tsleep(&up->sleep, return0, nil, 1);
+	}
+}
+
+static ulong
+doread(uvlong addr, void *a, ulong n)
+{
+	ulong d, *aa, nn, ret;
+
+	if(addr >= QSPISIZ)
+		return 0;
+	if(addr + n > QSPISIZ)
+		n = QSPISIZ - addr;
+	evenaddr((uintptr) a);
+	qspisel(1);
+	qspicmd(4, 0x6B | addr << 8);
+	qspicmd(1, 0);
+	ret = n;
+	aa = a;
+	while(n > 0){
+		d = qspicmd(4, 0);
+		if(n >= 4){
+			*aa++ = d;
+			n -= 4;
+		}else{
+			memmove(aa, (char*) &d + 4 - n, n);
+			break;
+		}
+	}
+	qspisel(0);
+	return ret;
+}
+
+static ulong
+dowrite(uvlong addr, void *a, ulong n)
+{
+	ulong *aa, ret, nn;
+
+	if(addr >= QSPISIZ)
+		return 0;
+	if(addr + n > QSPISIZ)
+		n = QSPISIZ - addr;
+	evenaddr((uintptr) a);
+	ret = n;
+	aa = a;
+	while(n > 0){
+		qspisel(1);
+		qspicmd(1, 6);
+		qspisel(0);
+		qspisel(1);
+		qspicmd(4, 0x32 | addr << 8);
+		if(n > 256)
+			nn = 256;
+		else
+			nn = n;
+		n -= nn;
+		addr += nn;
+		while(nn > 0)
+			if(nn >= 4){
+				qspicmd(4, *aa++);
+				nn -= 4;
+			}else{
+				qspicmd(n, *aa);
+				break;
+			}
+		qspisel(0);
+		waitbusy();
+	}
+	return ret;
+}
+
+static void
+doerase(ulong addr)
+{
+	qspisel(1);
+	qspicmd(1, 6);
+	qspisel(0);
+	qspisel(1);
+	qspicmd(4, 0xD8 | addr << 8);
+	qspisel(0);
+	waitbusy();
+}
+
+static Walkqid*
+qspiwalk(Chan* c, Chan *nc, char** name, int nname)
+{
+	return devwalk(c, nc, name, nname, qspidir, nqspidir, devgen);
+}
+
+static int
+qspistat(Chan* c, uchar* dp, int n)
+{
+	return devstat(c, dp, n, qspidir, nqspidir, devgen);
+}
+
+static Chan*
+qspiopen(Chan* c, int omode)
+{
+	devopen(c, omode, qspidir, nqspidir, devgen);
+	if(c->qid.path == Qboot){
+		qlock(&qspil);
+		if((omode & OTRUNC) != 0)
+			doerase(0);
+	}
+	return c;
+}
+
+static void
+qspiclose(Chan* c)
+{
+	if(c->qid.path == Qboot)
+		qunlock(&qspil);
+}
+
+static long
+qspiread(Chan *c, void *a, long n, vlong offset)
+{
+	char buf[64];
+
+	switch((ulong)c->qid.path){
+	case Qdir:
+		return devdirread(c, a, n, qspidir, nqspidir, devgen);
+	case Qboot:
+		return doread(offset, a, n);
+	default:
+		error(Egreg);
+		return -1;
+	}
+}
+
+static long
+qspiwrite(Chan *c, void *a, long n, vlong offset)
+{
+	switch((ulong)c->qid.path){
+	case Qboot:
+		return dowrite(offset, a, n);
+	default:
+		error(Egreg);
+		return -1;
+	}
+}
+
+static Chan*
+qspiattach(char* spec)
+{
+	qspiinit();
+	return devattach('Q', spec);
+}
+
+Dev qspidevtab = {
+	'Q',
+	"qspi",
+	
+	devreset,
+	devinit,
+	devshutdown,
+	qspiattach,
+	qspiwalk,
+	qspistat,
+	qspiopen,
+	devcreate,
+	qspiclose,
+	qspiread,
+	devbread,
+	qspiwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
--- /dev/null
+++ b/sys/src/9/zynq/etherif.h
@@ -1,0 +1,40 @@
+enum {
+	MaxEther	= 64,
+	Ntypes		= 8,
+};
+
+typedef struct Ether Ether;
+struct Ether {
+
+	int	ctlrno;
+	int	minmtu;
+	int 	maxmtu;
+	uchar	ea[Eaddrlen];
+	
+	int irq, irqlevel;
+	uintptr port;
+
+	void	(*attach)(Ether*);	/* filled in by reset routine */
+	void	(*detach)(Ether*);
+	void	(*transmit)(Ether*);
+	void	(*interrupt)(Ureg*, void*);
+	long	(*ifstat)(Ether*, void*, long, ulong);
+	long 	(*ctl)(Ether*, void*, long); /* custom ctl messages */
+	void	(*power)(Ether*, int);	/* power on/off */
+	void	(*shutdown)(Ether*);	/* shutdown hardware before reboot */
+	void	*ctlr;
+
+	Queue*	oq;
+
+	Netif;
+};
+
+extern Block* etheriq(Ether*, Block*, int);
+extern void addethercard(char*, int(*)(Ether*));
+extern ulong ethercrc(uchar*, int);
+extern int parseether(uchar*, char*);
+
+#define NEXT(x, l)	(((x)+1)%(l))
+#define PREV(x, l)	(((x) == 0) ? (l)-1: (x)-1)
+#define	HOWMANY(x, y)	(((x)+((y)-1))/(y))
+#define ROUNDUP(x, y)	(HOWMANY((x), (y))*(y))
--- /dev/null
+++ b/sys/src/9/zynq/etherzynq.c
@@ -1,0 +1,380 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/netif.h"
+#include "etherif.h"
+
+#define Rbsz		ROUNDUP(sizeof(Etherpkt)+16, 64)
+
+enum {
+	RXRING = 0x200,
+	TXRING = 0x200,
+	Linkdelay = 500,
+	MDC_DIV = 6,
+};
+
+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,
+	/* INTR_{EN,DIS} */
+	MGMTDONE = 1<<0,
+	RXCOMPL = 1<<1,
+	RXUSED = 1<<2,
+	TXUNDER = 1<<4,
+	RXOVER = 1<<10,
+	/* MDCTRL */
+	MDRESET = 1<<15,
+	AUTONEG = 1<<12,
+	FULLDUP = 1<<8,
+	/* MDSTATUS */
+	LINK = 1<<2,
+	/* MDGSTATUS */
+	RECVOK = 3<<12,
+};
+
+enum {
+	RxUsed = 1,
+	TxUsed = 1<<31,
+	FrameEnd = 1<<15,
+};
+
+typedef struct Ctlr Ctlr;
+
+struct Ctlr {
+	ulong *r;
+	Rendez phy;
+	int phyaddr;
+	int rxconsi, rxprodi, txi;
+	ulong *rxr, *txr;
+	Block **rxs, **txs;
+	Lock txlock;
+	int attach;
+};
+
+static int
+phyidle(void *v)
+{
+	return ((Ctlr*)v)->r[NET_STATUS] & PHY_IDLE;
+}
+
+static void
+mdwrite(Ctlr *c, int r, u16int v)
+{
+	sleep(&c->phy, phyidle, c);
+	c->r[PHY_MAINT] = 1<<30 | 1<<28 | 1<<17 | c->phyaddr << 23 | r << 18 | v;
+	sleep(&c->phy, phyidle, c);
+}
+
+static u16int
+mdread(Ctlr *c, int r)
+{
+	sleep(&c->phy, phyidle, c);
+	c->r[PHY_MAINT] = 1<<30 | 1<< 29 | 1<<17 | c->phyaddr << 23 | r << 18;
+	sleep(&c->phy, phyidle, c);
+	return c->r[PHY_MAINT];
+}
+
+static void
+ethproc(void *ved)
+{
+	Ether *edev;
+	Ctlr *c;
+	char *sp, *dpl;
+	u16int v;
+	
+	edev = ved;
+	c = edev->ctlr;
+	mdwrite(c, MDCTRL, AUTONEG);
+	for(;;){
+		if((mdread(c, MDSTATUS) & LINK) == 0){
+			edev->link = 0;
+			print("eth: no link\n");
+			while((mdread(c, MDSTATUS) & LINK) == 0)
+				tsleep(&up->sleep, return0, nil, Linkdelay);
+		}
+		v = mdread(c, MDPHYCTRL);
+		if((v & 0x40) != 0){
+			sp = "1000BASE-T";
+			while((mdread(c, MDGSTATUS) & RECVOK) != RECVOK)
+				;
+			edev->mbps = 1000;
+			c->r[NET_CFG] |= GIGE_EN;
+		}else if((v & 0x20) != 0){
+			sp = "100BASE-TX";
+			edev->mbps = 100;
+			c->r[NET_CFG] = NET_CFG & ~GIGE_EN | SPEED;
+		}else if((v & 0x10) != 0){
+			sp = "10BASE-T";
+			edev->mbps = 10;
+			c->r[NET_CFG] = NET_CFG & ~(GIGE_EN | SPEED);
+		}else
+			sp = "???";
+		if((v & 0x08) != 0){
+			dpl = "full";
+			c->r[NET_CFG] |= FDEN;
+		}else{
+			dpl = "half";
+			c->r[NET_CFG] &= ~FDEN;
+		}
+		edev->link = 1;
+		print("eth: %s %s duplex link\n", sp, dpl);
+		while((mdread(c, MDSTATUS) & LINK) != 0)
+			tsleep(&up->sleep, return0, nil, Linkdelay);
+	}
+}
+
+static int
+replenish(Ctlr *c)
+{
+	Block *bp;
+	int i;
+	ulong *r;
+	
+	while(c->rxprodi != c->rxconsi){
+		i = c->rxprodi;
+		bp = iallocb(Rbsz);
+		if(bp == nil){
+			print("eth: out of memory for receive buffers\n");
+			return -1;
+		}
+		c->rxs[i] = bp;
+		r = &c->rxr[2 * i];
+		r[0] = RxUsed | PADDR(bp->rp);
+		if(i == RXRING - 1)
+			r[0] |= 2;
+		r[1] = 0;
+		cleandse(bp->base, bp->lim);
+		clean2pa(PADDR(bp->base), PADDR(bp->lim));
+		r[0] &= ~RxUsed;
+		c->rxprodi = (c->rxprodi + 1) & (RXRING - 1);
+	}
+	return 0;
+}
+
+static void
+ethrx(Ether *edev)
+{
+	Ctlr *c;
+	ulong *r;
+	Block *bp;
+
+	c = edev->ctlr;
+//	print("rx! %p %p\n", PADDR(&c->rxr[2 * c->rxconsi]), c->r[RX_QBAR]);
+	for(;;){
+		r = &c->rxr[2 * c->rxconsi];
+		if((r[0] & RxUsed) == 0)
+			break;
+		if((r[1] & FrameEnd) == 0)
+			print("eth: partial frame received -- shouldn't happen\n");
+		bp = c->rxs[c->rxconsi];
+		bp->wp = bp->rp + (r[1] & 0x1fff);
+		invaldse(bp->rp, bp->wp);
+		inval2pa(PADDR(bp->rp), PADDR(bp->wp));
+		etheriq(edev, bp, 1);
+		c->rxconsi = (c->rxconsi + 1) & (RXRING - 1);
+		replenish(c);
+	}
+}
+
+static void
+ethtx(Ether *edev)
+{
+	Ctlr *c;
+	ulong *r;
+	Block *bp;
+	
+	c = edev->ctlr;
+	ilock(&c->txlock);
+	for(;;){
+		r = &c->txr[2 * c->txi];
+		if((r[1] & TxUsed) == 0){
+			print("eth: transmit buffer full\n");
+			break;
+		}
+		bp = qget(edev->oq);
+		if(bp == nil)
+			break;
+		if(c->txs[c->txi] != nil)
+			freeb(c->txs[c->txi]);
+		c->txs[c->txi] = bp;
+		cleandse(bp->rp, bp->wp);
+		clean2pa(PADDR(bp->rp), PADDR(bp->wp));
+		r[0] = PADDR(bp->rp);
+		r[1] = BLEN(bp) | FrameEnd | TxUsed;
+		if(r == c->txr + 2 * (TXRING - 1))
+			r[1] |= 1<<30;
+		coherence();
+		r[1] &= ~TxUsed;
+		coherence();
+		c->r[NET_CTRL] |= STARTTX;
+		c->txi = (c->txi + 1) & (TXRING - 1);
+	}
+	iunlock(&c->txlock);
+}
+
+static void
+ethirq(Ureg *, void *arg)
+{
+	Ether *edev;
+	Ctlr *c;
+	ulong fl;
+	
+	edev = arg;
+	c = edev->ctlr;
+	fl = c->r[INTR_STATUS];
+	c->r[INTR_STATUS] = fl;
+	if((fl & MGMTDONE) != 0)
+		wakeup(&c->phy);
+	if((fl & TXUNDER) != 0)
+		ethtx(edev);
+	if((fl & RXCOMPL) != 0)
+		ethrx(edev);
+	if((fl & RXUSED) != 0)
+		print("eth: DMA read RX descriptor with used bit set, shouldn't happen\n");
+	if((fl & RXOVER) != 0)
+		print("eth: RX overrun, shouldn't happen\n");
+}
+
+static int
+ethinit(Ether *edev)
+{
+	Ctlr *c;
+	int i;
+	uintptr rxrpa, txrpa;
+	
+	c = edev->ctlr;
+	c->r[NET_CTRL] = 0;
+	c->r[RX_STATUS] = 0xf;
+	c->r[TX_STATUS] = 0xff;
+	c->r[INTR_DIS] = 0x7FFFEFF;
+	c->r[NET_CFG] = MDC_DIV << 18 | FDEN | SPEED | RX1536EN | GIGE_EN | RXCHKSUMEN;
+	c->r[SPEC_ADDR1_BOT] = edev->ea[0] | edev->ea[1] << 8 | edev->ea[2] << 16 | edev->ea[3] << 24;
+	c->r[SPEC_ADDR1_TOP] = edev->ea[4] | edev->ea[5] << 8;
+	c->r[DMA_CFG] = TXCHKSUMEN | (Rbsz/64) << 16 | 1 << 10 | 3 << 8 | 0x10;
+	
+	rxrpa = ualloc(8 * RXRING, &c->rxr);
+	txrpa = ualloc(8 * TXRING, &c->txr);
+	c->rxs = xspanalloc(4 * RXRING, 4, 0);
+	c->txs = xspanalloc(4 * TXRING, 4, 0);
+	for(i = 0; i < 2 * RXRING; ){
+		c->rxr[i++] = 1;
+		c->rxr[i++] = 0;
+	}
+	c->rxconsi = 1;
+	replenish(c);
+	c->rxconsi = 0;
+	replenish(c);
+	for(i = 0; i < 2 * TXRING; ){
+		c->txr[i++] = 0;
+		c->txr[i++] = 1<<31;
+	}
+	c->txr[2 * (TXRING - 1)] |= 1<<30;
+	c->r[RX_QBAR] = rxrpa;
+	c->r[TX_QBAR] = txrpa;
+	
+	c->r[NET_CTRL] = MDEN | TXEN | RXEN;
+	c->r[INTR_EN] = MGMTDONE | TXUNDER | RXCOMPL | RXUSED | RXOVER;
+	return 0;
+}
+
+static void
+ethattach(Ether *edev)
+{
+	Ctlr *c;
+
+	c = edev->ctlr;
+	if(c->attach)
+		return;
+	c->attach = 1;
+	kproc("ethproc", ethproc, edev);
+}
+
+static int
+etherpnp(Ether *edev)
+{
+	static Ctlr ct;
+	static uchar mac[] = {0x0e, 0xa7, 0xde, 0xad, 0xbe, 0xef};
+	
+	if(ct.r != nil)
+		return -1;
+
+	memmove(edev->ea, mac, 6);
+	edev->ctlr = &ct;
+	edev->port = ETH0_BASE;
+	ct.r = vmap(edev->port, BY2PG);
+	edev->irq = ETH0IRQ;
+	edev->irqlevel = LEVEL;
+	edev->ctlr = &ct;
+	edev->interrupt = ethirq;
+	edev->transmit = ethtx;
+	edev->attach = ethattach;
+	edev->arg = edev;
+	edev->mbps = 1000;
+	
+	if(ethinit(edev) < 0){
+		edev->ctlr = nil;
+		return -1;
+	}
+	return 0;
+}
+
+void
+etherzynqlink(void)
+{
+	addethercard("eth", etherpnp);
+}
--- /dev/null
+++ b/sys/src/9/zynq/fns.h
@@ -1,0 +1,75 @@
+#include "../port/portfns.h"
+
+int tas(void *);
+int cmpswap(long *, long, long);
+void evenaddr(uintptr);
+void* kaddr(uintptr);
+uintptr paddr(void *);
+uintptr cankaddr(uintptr);
+void procsave(Proc *);
+void procrestore(Proc *);
+void idlehands(void);
+void coherence(void);
+void procfork(Proc *);
+void procsetup(Proc *);
+KMap* kmap(Page *);
+void kunmap(KMap *);
+
+#define	waserror()	(up->nerrlab++, setlabel(&up->errlab[up->nerrlab-1]))
+#define getpgcolor(a) 0
+#define kmapinval()
+#define KADDR(a) kaddr(a)
+#define PADDR(a) paddr((void*)(a))
+#define userureg(ur) (((ur)->psr & PsrMask) == PsrMusr)
+#define VA(k) ((void*)(k))
+#define PTR2UINT(p) ((uintptr)(p))
+
+void uartinit(void);
+void mmuinit(void);
+uintptr ttbget(void);
+void ttbput(uintptr);
+void cycles(uvlong *);
+void kexit(Ureg *);
+ulong getifsr(void);
+ulong getdfsr(void);
+uintptr getifar(void);
+uintptr getdfar(void);
+void links(void);
+void* vmap(uintptr, ulong);
+void timerinit(void);
+void setpmcr(ulong);
+void* tmpmap(uintptr);
+void tmpunmap(void*);
+void flushpg(void *);
+void setasid(ulong);
+void flushtlb(void);
+void touser(void *);
+void noted(Ureg *, ulong);
+void l1switch(L1 *, int);
+void intrenable(int, void (*)(Ureg *, void *), void *, int, char *);
+void intrinit(void);
+void intr(Ureg *);
+int uartconsole(void);
+void fpoff(void);
+void fpsave(FPsave *);
+void fprestore(FPsave *);
+void fpinit(void);
+void fpclear(void);
+char* getconf(char *);
+void invalise(void *, void *);
+void cleandse(void *, void *);
+void invaldse(void *, void *);
+void clinvdse(void *, void *);
+void invaldln(void *);
+void cleandln(void *);
+void clinvdln(void *);
+uintptr ualloc(ulong, void **);
+void clean2pa(uintptr, uintptr);
+void inval2pa(uintptr, uintptr);
+void archinit(void);
+uintptr palookur(void *);
+void screeninit(void);
+int isaconfig(char*, int, ISAConf*);
+void *ucalloc(usize);
+void *ucallocalign(usize, int, int);
+void ucfree(void *);
--- /dev/null
+++ b/sys/src/9/zynq/init9.s
@@ -1,0 +1,7 @@
+TEXT _main(SB), $-4
+	MOVW $setR12(SB), R12
+	MOVW 4(R13), R0
+	ADD $4, R13, R1
+	SUB $4, R13
+	MOVW R1, 8(R13)
+	MOVW $startboot(SB), R15
--- /dev/null
+++ b/sys/src/9/zynq/intr.c
@@ -1,0 +1,118 @@
+#include "u.h"
+#include <ureg.h>
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+enum {
+	NINTR = 96,
+	NPRIVATE = 32
+};
+
+static struct irq {
+	void (*f)(Ureg *, void *);
+	void *arg;
+	char *name;
+} irqs[NINTR];
+
+enum {
+	ICCICR = 0x100/4,
+	ICCPMR,
+	ICCBPR,
+	ICCIAR,
+	ICCEOIR,
+	ICDDCR = 0x1000/4,
+	ICDISR = 0x1080/4,
+	ICDISER = 0x1100/4,
+	ICDICER = 0x1180/4,
+	ICDICPR = 0x1280/4,
+	ICDABR  = 0x1300/4,
+	ICDIPRI = 0x1400/4,
+	ICDIPTR = 0x1800/4,
+	ICDICFR = 0x1C00/4,
+};
+
+void
+intrinit(void)
+{
+	int i;
+
+	mpcore[ICDDCR] = 3;
+	mpcore[ICCICR] = 7;
+	mpcore[ICCBPR] = 3;
+	mpcore[ICCPMR] = 255;
+	
+	/* disable all irqs and clear any pending interrupts */
+	for(i = 0; i < NINTR/32; i++){
+		mpcore[ICDISR + i] = -1;
+		mpcore[ICDICER + i] = -1;
+		mpcore[ICDICPR + i] = -1;
+		mpcore[ICDABR + i] = 0;
+	}
+}
+
+void
+intrenable(int irq, void (*f)(Ureg *, void *), void *arg, int type, char *name)
+{
+	ulong *e, s;
+	struct irq *i;
+
+	if(f == nil)
+		panic("intrenable: f == nil");
+	if(irq < 0 || irq >= NINTR)
+		panic("intrenable: invalid irq %d", irq);
+	if(type != LEVEL && type != EDGE)
+		panic("intrenable: invalid type %d", type);
+	if(irqs[irq].f != nil)
+		panic("intrenable: handler already assigned");
+	if(irq >= NPRIVATE){
+		e = &mpcore[ICDIPTR + (irq >> 2)];
+		s = irq << 3 & 24;
+		*e = *e & ~(3 << s) | 1 << s;
+		e = &mpcore[ICDICFR + (irq >> 4)];
+		s = irq << 1 & 30 | 1;
+		*e = *e & ~(1 << s) | type << s;
+	}
+	((uchar*)&mpcore[ICDIPRI])[irq] = 0;
+	i = &irqs[irq];
+	i->f = f;
+	i->arg = arg;
+	i->name = name;
+	mpcore[ICDISER + (irq >> 5)] = 1 << (irq & 31);
+	mpcore[ICDABR + (irq >> 5)] |= 1 << (irq & 31);
+}
+
+void
+intr(Ureg *ureg)
+{
+	ulong v;
+	int irq;
+	struct irq *i;
+
+	v = mpcore[ICCIAR];
+	irq = v & 0x3ff;
+	if(irq == 0x3ff)
+		return;
+		
+	m->intr++;
+	m->lastintr = irq;
+	i = &irqs[irq];
+	if(i->f == nil)
+		print("irq without handler %d\n", irq);
+	else
+		i->f(ureg, i->arg);
+	mpcore[ICCEOIR] = v;
+
+	if(up != nil){
+		if(irq == TIMERIRQ){
+			if(up->delaysched){
+				splhi();
+				sched();
+			}
+		}else
+			preempted();
+	}
+}
--- /dev/null
+++ b/sys/src/9/zynq/io.h
@@ -1,0 +1,25 @@
+#define PS_CLK 33
+
+#define UART_BASE 0xE0001000
+#define USB0_BASE 0xE0002000
+#define USB1_BASE 0xE0003000
+#define ETH0_BASE 0xE000B000
+#define QSPI_BASE 0xE000D000
+#define SLCR_BASE 0xF8000000
+#define DEVC_BASE 0xF8007000
+#define MPCORE_BASE 0xF8F00000
+#define L2_BASE 0xF8F02000
+#define OCM_BASE 0xFFFC0000
+
+#define TIMERIRQ 29
+#define XADCIRQ 39
+#define DEVCIRQ 40
+#define USB0IRQ 53
+#define ETH0IRQ 54
+#define USB1IRQ 76
+#define UART1IRQ 82
+
+#define LEVEL 0
+#define EDGE 1
+
+#define XADCINTERVAL 500
--- /dev/null
+++ b/sys/src/9/zynq/l.s
@@ -1,0 +1,460 @@
+#include "mem.h"
+#include "io.h"
+
+#define PUTC(c) MOVW $(c), R0; MOVW R0, (R8)
+
+TEXT _start(SB), $-4
+	MOVW $(KTZERO-KZERO), R13
+	MOVW $0xE0001030, R8
+
+	PUTC('P')
+	MOVW $0, R0
+	MOVW R0, R1
+	MOVW $(CONFADDR-KZERO), R2
+_start0:
+	MOVW.P R0, 4(R1)
+	CMP.S R1, R2
+	BNE _start0
+
+	PUTC('l')
+	MOVW $SECSZ, R0
+	MOVW $(CPU0L1-KZERO), R4
+	MOVW $KZERO, R1
+	ADD R1>>(SECSH-2), R4, R1
+	MOVW $(L1SEC|L1CACHED|L1KERRW), R2
+	MOVW $(-KZERO), R3
+_start1:
+	MOVW.P R2, 4(R1)
+	ADD R0, R2
+	CMP.S R2, R3
+	BGE _start1
+
+	PUTC('a')
+	MOVW $L2SZ, R0
+	MOVW $VMAP, R1
+	ADD R1>>(SECSH-2), R4, R1
+	MOVW $((VMAPL2-KZERO)|L1PT), R2
+	MOVW $(VMAPL2-KZERO+VMAPL2SZ), R3
+_start2:
+	MOVW.P R2, 4(R1)
+	ADD R0, R2
+	CMP.S R2, R3
+	BGE _start2
+
+	MOVW $(UART_BASE|L2VALID|L2DEVICE|L2KERRW), R0
+	MOVW $(VMAPL2 - KZERO), R1
+	MOVW R0, (R1)
+	
+	PUTC('n')
+	MOVW $0, R0
+	MCR 15, 0, R0, C(8), C(7), 0
+	MOVW $(CPU0L1 - KZERO | TTBATTR), R1
+	MCR 15, 0, R1, C(2), C(0), 0
+	MOVW $0x20c5047b, R1
+	MOVW $_virt(SB), R2
+	PUTC(' ')
+	MCR 15, 0, R1, C(1), C(0), 0
+	MOVW R2, R15
+
+TEXT _virt(SB), $-4
+	DSB
+	ISB
+	
+	MOVW $(MACH(0) + MACHSIZE), R13
+	MOVW $(MACH(0) + 12), R0
+	BL loadsp(SB)
+	MOVW $vectors(SB), R1
+	MCR 15, 0, R1, C(12), C(0)
+	
+	/* enable MMU permission checking */
+	MOVW $0x55555555, R0
+	MCR 15, 0, R0, C(3), C(0), 0
+	
+	/* enable maths coprocessors in CPACR but disable them in FPEXC */
+	MRC 15, 0, R0, C(1), C(0), 2
+	ORR $(15<<20), R0
+	MCR 15, 0, R0, C(1), C(0), 2
+
+	VMRS(0xe, FPEXC, 0)
+	BIC $(3<<30), R0
+	VMSR(0xe, 0, FPEXC)
+	
+	/* enable L1 cache */
+	MOVW $0, R0
+	MCR 15, 0, R0, C(7), C(5), 0
+	MCR 15, 0, R0, C(7), C(5), 6
+	BL l1dclear(SB)
+	MRC 15, 0, R0, C(1), C(0), 1
+	ORR $(1|1<<6), R0
+	MCR 15, 0, R0, C(1), C(0), 1
+	MRC 15, 0, R0, C(1), C(0), 0
+	ORR $(1<<12|1<<2), R0
+	MCR 15, 0, R0, C(1), C(0), 0
+	DSB
+	ISB
+	
+	MOVW $(VMAP+0x30), R8
+	PUTC('9')
+	
+	MOVW $setR12(SB), R12
+	MOVW $MACH(0), R(Rmach)
+	MOVW $0, R(Rup)
+	BL main(SB)
+	B idlehands(SB)
+
+	BL _div(SB) /* hack to load _div */
+
+TEXT touser(SB), $-4
+	CPS(CPSID)
+
+	SUB $12, R13
+	MOVW R0, (R13)
+	MOVW $0, R1
+	MOVW R1, 4(R13)
+	MOVW $(UTZERO+0x20), R1
+	MOVW R1, 8(R13)
+
+	MOVW CPSR, R1
+	BIC $(PsrMask|PsrDirq|PsrDfiq), R1
+	ORR $PsrMusr, R1
+	MOVW R1, SPSR
+
+	MOVW $(KTZERO-(15*4)), R0
+	MOVM.IA (R0), [R0-R12]
+
+	MOVM.IA.S (R13), [R13-R14]
+	ADD $8, R13
+	MOVM.IA.W.S (R13), [R15]
+
+TEXT forkret(SB), $-4
+	MOVW (16*4)(R13), R0
+	MOVW R0, SPSR
+	MOVM.IA.W (R13), [R0-R12]
+	MOVM.IA.S (R13), [R13-R14]
+	ADD $16, R13
+	DSB
+	ISB
+	MOVM.IA.W.S (R13), [R15]
+
+TEXT nope(SB), $-4 // NOPE
+	MOVW $(VMAP+0x30), R8
+	PUTC(13)
+	PUTC(10)
+	MOVW R14, R7
+	BL puthex(SB)
+	PUTC(' ')
+	PUTC('N')
+	PUTC('O')
+	PUTC('P')
+	PUTC('E')
+_nope:	B _nope
+
+TEXT loadsp(SB), $0
+	CPS(CPSMODE | PsrMabt)
+	MOVW R0, R13
+	CPS(CPSMODE | PsrMund)
+	MOVW R0, R13
+	CPS(CPSMODE | PsrMirq)
+	MOVW R0, R13
+	CPS(CPSMODE | PsrMfiq)
+	MOVW R0, R13
+	CPS(CPSMODE | PsrMsvc)
+	RET
+
+TEXT cputhex(SB), $0
+	MOVW R0, R7
+	MOVW $(VMAP+0x30), R8
+TEXT puthex(SB), $0
+_p0:
+	MOVW -4(R8), R6
+	AND.S $(1<<3), R6
+	BEQ _p0
+#define DIG MOVW R7>>28, R6; AND $15, R6; ADD $'0', R6; CMP $'9', R6; ADD.GT $7, R6; MOVW R6, (R8); MOVW R7<<4, R7
+	DIG; DIG; DIG; DIG
+	DIG; DIG; DIG; DIG
+	MOVW $13, R6
+	MOVW R6, (R8)
+	MOVW $10, R6
+	MOVW R6, (R8)
+	RET
+
+TEXT spllo(SB), $-4
+	MOVW CPSR, R0
+	CPS(CPSIE)
+	RET
+
+TEXT splhi(SB), $-4
+	MOVW R14, 4(R(Rmach))
+	MOVW CPSR, R0
+	CPS(CPSID)
+	RET
+
+TEXT splx(SB), $-4
+	MOVW R14, 4(R(Rmach))
+	MOVW R0, R1
+	MOVW CPSR, R0
+	MOVW R1, CPSR
+	RET
+	
+TEXT spldone(SB), $-4
+	RET
+
+TEXT islo(SB), $0
+	MOVW CPSR, R0
+	AND $(PsrDirq), R0
+	EOR $(PsrDirq), R0
+	RET
+
+TEXT setlabel(SB), $-4
+	MOVW R13, 0(R0)
+	MOVW R14, 4(R0)
+	MOVW $0, R0
+	RET
+
+TEXT gotolabel(SB), $-4
+	MOVW 0(R0), R13
+	MOVW 4(R0), R14
+	MOVW $1, R0
+	RET
+
+TEXT tas(SB), $0
+	DMB
+	MOVW $0xDEADDEAD, R2
+_tas1:
+	LDREX (R0), R1
+	STREX R2, (R0), R3
+	CMP.S $0, R3
+	BNE _tas1
+	MOVW R1, R0
+	DMB
+	RET
+
+TEXT coherence(SB), $0
+	DSB
+	RET
+
+TEXT idlehands(SB), $0
+	DSB
+	WFE
+	RET
+
+TEXT ttbget(SB), $0
+	MRC 15, 0, R0, C(2), C(0), 0
+	BIC $0x7f, R0
+	RET
+	
+TEXT ttbput(SB), $0
+	ORR $(TTBATTR), R0
+	MCR 15, 0, R0, C(2), C(0), 0
+	RET
+
+TEXT flushpg(SB), $0
+	MCR 15, 0, R0, C(8), C(7), 3
+	DSB
+	RET
+
+TEXT flushtlb(SB), $0
+	MCR 15, 0, R0, C(8), C(3), 0
+	DSB
+	RET
+
+TEXT setasid(SB), $0
+	MCR 15, 0, R0, C(13), C(0), 1
+	RET
+
+TEXT getifar(SB), $0
+	MRC 15, 0, R0, C(6), C(0), 2
+	RET
+
+TEXT getdfar(SB), $0
+	MRC 15, 0, R0, C(6), C(0), 0
+	RET
+
+TEXT getifsr(SB), $0
+	MRC 15, 0, R0, C(5), C(0), 1
+	RET
+
+TEXT getdfsr(SB), $0
+	MRC 15, 0, R0, C(5), C(0), 0
+	RET
+
+TEXT setpmcr(SB), $0
+	MCR 15, 0, R0, C(9), C(12), 0
+	RET
+
+TEXT perfticks(SB), $0
+	MRC 15, 0, R0, C(9), C(13), 0
+	RET
+
+TEXT cycles(SB), $0
+	MRC 15, 0, R1, C(9), C(13), 0
+	MOVW R1, (R0)
+	MOVW 24(R(Rmach)), R1
+	MRC 15, 0, R2, C(9), C(12), 3
+	AND.S $(1<<31), R2
+	BEQ _cycles0
+	MCR 15, 0, R2, C(9), C(12), 3
+	ADD $1, R1
+	MOVW R1, 24(R(Rmach))
+_cycles0:
+	MOVW R1, 4(R0)
+	RET
+
+TEXT fpinit(SB), $0
+	MOVW $(1<<30), R0
+	VMSR(0xe, 0, FPEXC)
+	MOVW $0, R0
+	VMSR(0xe, 0, FPSCR)
+	RET
+
+TEXT fpsave(SB), $0
+	VMRS(0xe, FPEXC, 1)
+	VMRS(0xe, FPSCR, 2)
+	MOVM.IA.W [R1-R2], (R0)
+	WORD $0xeca00b20
+	WORD $0xece00b20
+	RET
+
+TEXT fprestore(SB), $0
+	MOVM.IA.W (R0), [R1-R2]
+	VMSR(0xe, 1, FPEXC)
+	VMSR(0xe, 2, FPSCR)
+	WORD $0xecb00b20
+	WORD $0xecf00b20
+	RET
+
+TEXT fpoff(SB), $0
+	MOVW $0, R1
+	VMSR(0xe, 1, FPEXC)
+	RET
+
+TEXT fpclear(SB), $0
+	VMRS(0xe, FPEXC, 1)
+	AND $(3<<30), R1
+	VMSR(0xe, 1, FPEXC)
+	RET
+
+#define Rnoway R1
+#define Rwayinc R2
+#define Rmaxway R3
+#define Rsetinc R4
+#define Rmaxset R5
+
+TEXT l1dclear(SB), $0
+	MOVW $0, R0
+	MCR 15, 2, R0, C(0), C(0), 0
+	MRC 15, 1, R9, C(0), C(0), 0
+	AND $7, R9, R8
+	ADD $4, R8
+	MOVW $1, Rsetinc
+	MOVW Rsetinc<<R8, Rsetinc
+
+	MOVW R9>>13, Rmaxset
+	AND $0x7fff, Rmaxset
+	MOVW Rmaxset<<R8, Rmaxset
+	
+	MOVW R9>>3, R0
+	AND $0x3ff, R0
+	MOVW $(1<<31), Rwayinc
+	MOVW $(1<<31), Rnoway
+	MOVW R0, Rmaxway
+	ADD $1, R0
+_l1dclear0:
+	MOVW.S R0>>1, R0
+	BEQ _l1dclear1
+	MOVW Rwayinc>>1, Rwayinc
+	MOVW Rnoway->1, Rnoway
+	MOVW Rmaxway@>1, Rmaxway
+	B _l1dclear0
+_l1dclear1:
+	MOVW Rwayinc<<1, Rwayinc
+	MVN Rnoway<<1, Rnoway
+	BIC Rnoway, Rmaxway
+
+	MOVW $0, R0
+_l1dclear2:
+	MCR 15, 0, R0, C(7), C(14), 2
+	ADD Rwayinc, R0
+	CMP.S Rmaxway, R0
+	BLT _l1dclear2
+	AND Rnoway, R0
+	ADD Rsetinc, R0
+	CMP.S Rmaxset, R0
+	BLT _l1dclear2
+	RET
+
+TEXT invalise(SB), $0
+	MOVW 4(FP), R1
+	ADD $(LINSIZ - 1), R1
+	BIC $(LINSIZ - 1), R0
+	BIC $(LINSIZ - 1), R1
+_invalise0:
+	MCR 15, 0, R0, C(7), C(5), 1
+	ADD $LINSIZ, R0
+	CMP.S R1, R0
+	BLT _invalise0	
+	RET
+
+TEXT cleandse(SB), $0
+	DSB
+	MOVW 4(FP), R1
+	ADD $(LINSIZ - 1), R1
+	BIC $(LINSIZ - 1), R0
+	BIC $(LINSIZ - 1), R1
+_cleandse0:
+	MCR 15, 0, R0, C(7), C(10), 1
+	ADD $LINSIZ, R0
+	CMP.S R1, R0
+	BLT _cleandse0
+	DSB
+	RET
+	
+TEXT invaldse(SB), $0
+	MOVW 4(FP), R1
+	ADD $(LINSIZ - 1), R1
+	BIC $(LINSIZ - 1), R0
+	BIC $(LINSIZ - 1), R1
+_invaldse0:
+	MCR 15, 0, R0, C(7), C(6), 1
+	ADD $LINSIZ, R0
+	CMP.S R1, R0
+	BLT _invaldse0
+	DSB
+	RET
+
+TEXT clinvdse(SB), $0
+	DSB
+	MOVW 4(FP), R1
+	ADD $(LINSIZ - 1), R1
+	BIC $(LINSIZ - 1), R0
+	BIC $(LINSIZ - 1), R1
+_clinvdse0:
+	MCR 15, 0, R0, C(7), C(14), 1
+	ADD $LINSIZ, R0
+	CMP.S R1, R0
+	BLT _clinvdse0
+	DSB
+	RET
+
+TEXT cleandln(SB), $0
+	DSB
+	MCR 15, 0, R0, C(7), C(10), 1
+	DSB
+	RET
+
+TEXT invaldln(SB), $0
+	MCR 15, 0, R0, C(7), C(6), 1
+	DSB
+	RET
+
+TEXT clinvdln(SB), $0
+	DSB
+	MCR 15, 0, R0, C(7), C(14), 1
+	DSB
+	RET
+
+TEXT palookur(SB), $0
+	MCR 15, 0, R0, C(7), C(8), 2
+	DSB
+	MRC 15, 0, R0, C(7), C(4), 0
+	RET
--- /dev/null
+++ b/sys/src/9/zynq/ltrap.s
@@ -1,0 +1,98 @@
+#include "mem.h"
+#include "io.h"
+
+TEXT vectors(SB), $-4
+	MOVW $_start-KZERO(SB), R15
+	MOVW $_vexc(SB), R15
+	MOVW $_vsvc(SB), R15
+	MOVW $_viabt(SB), R15
+	MOVW $_vexc(SB), R15
+	MOVW $vectors(SB), R15
+	MOVW $_vexc(SB), R15
+	MOVW $_vexc(SB), R15
+
+TEXT _viabt(SB), $-4
+	CPS(CPSID)
+	CLREX
+	DSB
+	MOVW R14, 8(R13)
+	MOVW SPSR, R14
+	MOVW R14, 4(R13)
+	MOVW CPSR, R14
+	AND $0x1e, R14
+	B _exc
+	
+
+TEXT _vexc(SB), $-4
+	CPS(CPSID)
+	CLREX
+	DSB
+	MOVW R14, 8(R13)
+	MOVW SPSR, R14
+	MOVW R14, 4(R13)
+	MOVW CPSR, R14
+	AND $0x1f, R14
+_exc:
+	MOVW R14, 0(R13)
+	CPS(CPSMODE | PsrMsvc)
+
+	SUB $(18*4), R13
+	MOVM.IA [R0-R14], (R13)
+
+	MOVW $MACH(0), R(Rmach) /* FIXME */
+	MOVW 8(R(Rmach)), R(Rup)
+	MOVW $setR12(SB), R12
+	
+	ADD $12, R(Rmach), R0
+	MOVM.IA (R0), [R1-R3]
+	ADD $(15*4), R13, R0
+	MOVM.IA [R1-R3], (R0)
+	
+	AND.S $0xf, R2
+	ADD.NE $(18*4), R13, R0
+	MOVW.NE R0, (13*4)(R13)
+	ADD.EQ $(13*4), R13, R0
+	MOVM.IA.S.EQ [R13-R14], (R0)
+	
+	MOVW R13, R0
+	SUB $8, R13
+	BL trap(SB)
+	ADD $8, R13
+	
+	MOVW (16*4)(R13), R0
+	MOVW R0, SPSR
+	AND.S $0xf, R0
+	BEQ _uret
+	MOVW R(Rmach), (Rmach*4)(R13)
+	MOVM.IA (R13), [R0-R14]
+	DSB
+	MOVM.DB.S (R13), [R15]
+
+TEXT _vsvc(SB), $-4
+	CLREX
+	DSB
+	MOVW.W R14, -4(R13)
+	MOVW SPSR, R14
+	MOVW.W R14, -4(R13)
+	MOVW $PsrMsvc, R14
+	MOVW.W R14, -4(R13)
+	MOVM.DB.S [R0-R14], (R13)
+	SUB $(15*4), R13
+	
+	MOVW $MACH(0), R(Rmach) /* FIXME */
+	MOVW 8(R(Rmach)), R(Rup)
+	MOVW $setR12(SB), R12
+	
+	MOVW R13, R0
+	SUB $8, R13
+	BL syscall(SB)
+	ADD $8, R13
+
+	MOVW (16*4)(R13), R0
+	MOVW R0, SPSR
+_uret:
+	MOVM.IA.S (R13), [R0-R14]
+	ADD $(17*4), R13
+	DSB
+	ISB
+	MOVM.IA.S.W (R13), [R15]
--- /dev/null
+++ b/sys/src/9/zynq/main.c
@@ -1,0 +1,372 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "init.h"
+#include "pool.h"
+#include "io.h"
+#include "../port/error.h"
+#include "tos.h"
+
+Conf conf;
+int normalprint, delaylink;
+uchar *sp;
+
+enum { MAXCONF = 64 };
+
+char *confname[MAXCONF], *confval[MAXCONF];
+int nconf;
+
+void
+exit(int)
+{
+	NOPE
+}
+
+void
+reboot(void *, void *, ulong)
+{
+	NOPE
+}
+
+void
+evenaddr(uintptr va)
+{
+	if((va & 3) != 0){
+		dumpstack();
+		postnote(up, 1, "sys: odd address", NDebug);
+		error(Ebadarg);
+	}
+}
+
+void
+procfork(Proc *p)
+{
+	ulong s;
+
+	p->kentry = up->kentry;
+	p->pcycles = -p->kentry;
+	
+	s = splhi();
+	switch(up->fpstate & ~FPillegal){
+	case FPactive:
+		fpsave(&up->fpsave);
+		up->fpstate = FPinactive;
+	case FPinactive:
+		p->fpsave = up->fpsave;
+		p->fpstate = FPinactive;
+	}
+	splx(s);
+}
+
+void
+procsetup(Proc *p)
+{
+	p->fpstate = FPinit;
+	fpoff();
+	
+	cycles(&p->kentry);
+	p->pcycles = -p->kentry;
+}
+
+int
+cmpswap(long *a, long b, long c)
+{
+	extern int cas(int *, int, int);
+
+	return cas((int *) a, b, c);
+}
+
+void
+kexit(Ureg *)
+{
+	Tos *tos;
+	uvlong t;
+
+	tos = (Tos*)(USTKTOP-sizeof(Tos));
+	cycles(&t);
+	tos->kcycles += t - up->kentry;
+	tos->pcycles = t + up->pcycles;
+	tos->pid = up->pid;
+}
+
+ulong *l2;
+
+void
+l2init(void)
+{
+	enum {
+		CTRL = 0x100/4,
+		AUX,
+		TAGRAM,
+		DATARAM,
+		INVPA = 0x770/4,
+		INVWAY = 0x77C/4,
+		PREFETCH = 0xF60/4,
+	};
+
+	mpcore[0] |= 1;
+	slcr[0xa1c/4] = 0x020202;
+	l2 = vmap(L2_BASE, BY2PG);
+	l2[CTRL] &= ~1;
+	l2[TAGRAM] = l2[TAGRAM] & ~0x777 | 0x111;
+	l2[DATARAM] = l2[DATARAM] & ~0x777 | 0x121;
+	l2[PREFETCH] |= 3<<28;
+	l2[AUX] |= 3<<28 | 1<<20;
+	l2[INVWAY] = 0xff;
+	while((l2[INVPA] & 1) != 0)
+		;
+	l2[CTRL] = 1;
+}
+
+void
+clean2pa(uintptr start, uintptr end)
+{
+	uintptr pa;
+	
+	start &= ~31;
+	end = (end + 31) & ~31;
+	for(pa = start; pa < end; pa += 32)
+		l2[0x7b0/4] = pa;
+}
+
+void
+inval2pa(uintptr start, uintptr end)
+{
+	uintptr pa;
+	
+	start &= ~31;
+	end = (end + 31) & ~31;
+	for(pa = start; pa < end; pa += 32)
+		l2[0x770/4] = pa;
+}
+
+static void
+options(void)
+{
+	long i, n;
+	char *cp, *line[MAXCONF], *p, *q;
+
+	cp = (char *) CONFADDR;
+
+	p = cp;
+	for(q = cp; *q; q++){
+		if(*q == '\r')
+			continue;
+		if(*q == '\t')
+			*q = ' ';
+		*p++ = *q;
+	}
+	*p = 0;
+
+	n = getfields(cp, line, MAXCONF, 1, "\n");
+	for(i = 0; i < n; i++){
+		if(*line[i] == '#')
+			continue;
+		cp = strchr(line[i], '=');
+		if(cp == nil)
+			continue;
+		*cp++ = '\0';
+		confname[nconf] = line[i];
+		confval[nconf] = cp;
+		nconf++;
+	}
+
+}
+
+void
+confinit(void)
+{
+	int i;
+
+	conf.nmach = 1;
+	conf.nproc = 100;
+	conf.ialloc = 16*1024*1024;
+	conf.nimage = conf.nproc;
+	conf.mem[0].base = PGROUND((ulong)end - KZERO);
+	conf.mem[0].limit = 1024*1024*1024;
+	conf.npage = 0;
+	for(i = 0; i < nelem(conf.mem); i++)
+		conf.npage += conf.mem[i].npage = (conf.mem[i].limit - conf.mem[i].base) >> PGSHIFT;
+	conf.upages = conf.npage - 100*1024*1024 / BY2PG;
+}
+
+static uchar *
+pusharg(char *p)
+{
+	int n;
+	
+	n = strlen(p) + 1;
+	sp -= n;
+	memmove(sp, p, n);
+	return sp;
+}
+
+static void
+bootargs(void *base)
+{
+	int i, ac;
+	uchar *av[32];
+	uchar **lsp;
+	
+	sp = (uchar *) base + BY2PG - sizeof(Tos);
+	
+	ac = 0;
+	av[ac++] = pusharg("boot");
+	sp = (uchar *) ((ulong) sp & ~3);
+	sp -= (ac + 1) * sizeof(sp);
+	lsp = (uchar **) sp;
+	for(i = 0; i < ac; i++)
+		lsp[i] = av[i] + ((USTKTOP - BY2PG) - (ulong) base);
+	lsp[i] = 0;
+	sp += (USTKTOP - BY2PG) - (ulong) base;
+	sp -= BY2WD;
+}
+
+static void
+init0(void)
+{
+	char buf[ERRMAX];
+	int i;
+
+	up->nerrlab = 0;
+	spllo();
+	
+	up->slash = namec("#/", Atodir, 0, 0);
+	pathclose(up->slash->path);
+	up->slash->path = newpath("/");
+	up->dot = cclone(up->slash);
+	
+	chandevinit();
+	uartconsole();
+	
+	if(!waserror()){
+		ksetenv("cputype", "arm", 0);
+		if(cpuserver)
+			ksetenv("service", "cpu", 0);
+		else
+			ksetenv("service", "terminal", 0);
+		ksetenv("console", "0", 0);
+		snprint(buf, sizeof(buf), "zynq %s", conffile);
+		ksetenv("terminal", buf, 0);
+		for(i = 0; i < nconf; i++){
+			if(*confname[i] != '*')
+				ksetenv(confname[i], confval[i], 0);
+			ksetenv(confname[i], confval[i], 1);
+		}
+		poperror();
+	}
+	kproc("alarm", alarmkproc, 0);
+	touser(sp);
+}
+
+void
+userinit(void)
+{
+	Proc *p;
+	Segment *s;
+	void *v;
+	Page *pg;
+	
+	p = newproc();
+	p->pgrp = newpgrp();
+	p->egrp = smalloc(sizeof(Egrp));
+	p->egrp->ref = 1;
+	p->fgrp = dupfgrp(nil);
+	p->rgrp = newrgrp();
+	p->procmode = 0640;
+	
+	kstrdup(&eve, "");
+	kstrdup(&p->text, "*init*");
+	kstrdup(&p->user, eve);
+	
+	procsetup(p);
+	
+	p->sched.pc = (ulong)init0;
+	p->sched.sp = (ulong)p->kstack + KSTACK - (sizeof(Sargs) + BY2WD);
+	
+	s = newseg(SG_STACK, USTKTOP - USTKSIZE, USTKSIZE / BY2PG);
+	p->seg[SSEG] = s;
+	pg = newpage(0, 0, USTKTOP - BY2PG);
+	v = tmpmap(pg->pa);
+	memset(v, 0, BY2PG);
+	segpage(s, pg);
+	bootargs(v);
+	tmpunmap(v);
+	
+	s = newseg(SG_TEXT, UTZERO, 1);
+	s->flushme++;
+	p->seg[TSEG] = s;
+	pg = newpage(0, 0, UTZERO);
+	memset(pg->cachectl, PG_TXTFLUSH, sizeof(pg->cachectl));
+
+	segpage(s, pg);
+	v = tmpmap(pg->pa);
+	memset(v, 0, BY2PG);
+	memmove(v, initcode, sizeof(initcode));
+	tmpunmap(v);
+	
+	ready(p);
+}
+
+void
+sanity(void)
+{
+	static int dat = 0xdeadbeef;
+	extern ulong vectors[];
+
+	assert(dat == 0xdeadbeef);
+	assert(((uintptr)vectors & 31) == 0);
+	assert(sizeof(Mach) + KSTACK <= MACHSIZE);
+	assert((KZERO & SECSZ - 1) == 0);
+}
+
+char *
+getconf(char *n)
+{
+	int i;
+	
+	for(i = 0; i < nconf; i++)
+		if(cistrcmp(confname[i], n) == 0)
+			return confval[i];
+	return nil;
+}
+
+int
+isaconfig(char *, int, ISAConf*)
+{
+	return 1;
+}
+
+void
+main(void)
+{
+	uartinit();
+	mmuinit();
+	l2init();
+	intrinit();
+	options();
+	confinit();
+	timerinit();
+	uartputs(" from Bell Labs\n", 16);
+	xinit();
+	printinit();
+	quotefmtinstall();
+	sanity();
+	todinit();
+	timersinit();
+	procinit0();
+	initseg(); 
+	if(delaylink)
+		bootlinks();
+	else
+		links();
+	archinit();
+	chandevreset();
+	pageinit();
+	swapinit();
+	screeninit();
+	userinit();
+	schedinit();
+}
--- /dev/null
+++ b/sys/src/9/zynq/mem.h
@@ -1,0 +1,124 @@
+/*
+ * Memory and machine-specific definitions.  Used in C and assembler.
+ */
+
+#define MIN(a, b)	((a) < (b)? (a): (b))
+#define MAX(a, b)	((a) > (b)? (a): (b))
+
+/*
+ * 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 LINSIZ		32
+#define	BLOCKALIGN	LINSIZ
+#define	FPalign		16
+
+#define MAXMACH 2
+#define KSTACK 4096
+
+#define HZ (1000)
+#define MS2HZ (1000/HZ)
+#define TK2SEC(t) ((t)/HZ)
+
+#define KZERO 0xF0000000
+#define KTZERO (KZERO+0x80000)
+#define VMAPSZ (SECSZ * 4)
+#define VMAP (KZERO - VMAPSZ)
+#define TMAPSZ SECSZ
+#define TMAP (VMAP - TMAPSZ)
+#define KMAPSZ SECSZ
+#define KMAP (TMAP - KMAPSZ)
+#define NKMAP (KMAPSZ / BY2PG - 1)
+#define MACHSIZE 8192
+#define MACH(n) (KZERO+(n)*MACHSIZE)
+#define MACHP(n) ((Mach *)MACH(n))
+#define CPU0L1 ROUND(MACH(MAXMACH), L1SZ)
+#define VMAPL2 (CPU0L1 + L1SZ)
+#define VMAPL2SZ (L2SZ * (VMAPSZ / SECSZ))
+#define TMAPL2(n) (VMAPL2 + VMAPL2SZ + (n) * L2SZ)
+#define TMAPL2SZ (MAXMACH * L2SZ)
+#define CONFSIZE 65536
+#define CONFADDR (KTZERO-CONFSIZE)
+
+#define UZERO 0
+#define UTZERO BY2PG
+#define UTROUND(t) ROUNDUP(t, BY2PG)
+#define USTKTOP 0xE0000000
+#define USTKSIZE (16*1024*1024)
+
+#define PTEMAPMEM (1024*1024)
+#define PTEPERTAB (PTEMAPMEM/BY2PG)
+#define SEGMAPSIZE 1984
+#define SSEGMAPSIZE 16
+
+#define PTEVALID L2VALID
+#define PTERONLY L2RONLY
+#define PTEWRITE L2WRITE
+#define PTEUNCACHED L2DEVICE
+#define PPN(x) ((x)&~(BY2PG-1))
+
+#define PsrDirq (1<<7)
+#define PsrDfiq (1<<6)
+#define PsrMask 0x1f
+#define PsrMusr 0x10
+#define PsrMfiq 0x11
+#define PsrMirq 0x12
+#define PsrMsvc 0x13
+#define PsrMabt 0x17
+#define PsrMiabt 0x16 /* not an actual mode; for ureg->type */
+#define PsrMund 0x1b
+
+#define DMB WORD $0xf57ff05f
+#define DSB WORD $0xf57ff04f
+#define ISB WORD $0xf57ff06f
+#define WFE WORD $0xe320f002
+#define CPS(m) WORD $(0xf1000000|(m))
+#define CPSMODE (1<<17)
+#define CPSIE (3<<6|2<<18)
+#define CPSID (3<<6|3<<18)
+#define Rmach 10
+#define Rup 9
+
+#define VMSR(c, r1, r2) WORD $(0x0ee00a10|(c)<<28|(r2)<<16|(r1)<<12)
+#define VMRS(c, r1, r2) WORD $(0x0ef00a10|(c)<<28|(r2)<<12|(r1)<<16)
+#define FPSID 0x0
+#define FPSCR 0x1
+#define MVFR1 0x6
+#define MVFR0 0x7
+#define FPEXC 0x8
+
+#define L1PT 1
+#define L1SEC (2|1<<10)
+#define L1DEVICE (1<<4)
+#define L1CACHED (1<<16|1<<14|1<<12|1<<2)
+#define L1KERRW 0
+#define L1SZ (4096*4)
+#define L2SZ (256*4)
+#define SECSZ 1048576
+#define SECSH 20
+#define NL2 256
+
+#define L1X(va) (((ulong)(va)) >> 20)
+#define L1RX(va) (((ulong)(va)) >> 20 & ~3)
+#define L2X(va) (((ulong)(va)) >> 12 & 0xff)
+#define L2RX(va) (((ulong)(va)) >> 12 & 0x3ff)
+
+#define L2VALID (2|1<<4)
+#define L2CACHED (1<<10|1<<8|1<<6|1<<2)
+#define L2DEVICE (1<<0)
+#define L2KERRW L2KERNEL
+#define L2KERNEL 0
+#define L2USER (1<<5)
+#define L2RONLY (1<<9)
+#define L2WRITE 0
+#define L2LOCAL (1<<11)
+
+#define TTBATTR (1<<6|1<<3)
--- /dev/null
+++ b/sys/src/9/zynq/mkfile
@@ -1,0 +1,92 @@
+CONF=zynq
+CONFLIST=zynq
+
+#must match mem.h
+KTZERO=0xf0080020
+
+objtype=arm
+</$objtype/mkfile
+p=9
+
+DEVS=`{rc ../port/mkdevlist $CONF}
+
+PORT=\
+	alarm.$O\
+	alloc.$O\
+	allocb.$O\
+	auth.$O\
+	cache.$O\
+	chan.$O\
+	dev.$O\
+	edf.$O\
+	fault.$O\
+	mul64fract.$O\
+	rebootcmd.$O\
+	page.$O\
+	parse.$O\
+	pgrp.$O\
+	portclock.$O\
+	print.$O\
+	proc.$O\
+	qio.$O\
+	qlock.$O\
+	segment.$O\
+	swap.$O\
+	sysfile.$O\
+	sysproc.$O\
+	taslock.$O\
+	tod.$O\
+	xalloc.$O\
+	random.$O\
+	rdb.$O\
+	syscallfmt.$O\
+
+OBJ=\
+	ltrap.$O\
+	l.$O\
+	main.$O\
+	mmu.$O\
+	trap.$O\
+	intr.$O\
+	timer.$O\
+	$CONF.root.$O\
+	$CONF.rootc.$O\
+	$DEVS\
+	$PORT\
+
+
+LIB=\
+	/$objtype/lib/libmemlayer.a\
+	/$objtype/lib/libmemdraw.a\
+	/$objtype/lib/libdraw.a\
+	/$objtype/lib/libip.a\
+	/$objtype/lib/libsec.a\
+	/$objtype/lib/libmp.a\
+	/$objtype/lib/libc.a\
+
+9:V: install # $p$CONF
+
+$p$CONF:D: $CONF.c $OBJ $LIB mkfile
+	$CC $CFLAGS '-DKERNDATE='`{date -n} $CONF.c
+	$LD -o $target -T$KTZERO -l $OBJ $CONF.$O $LIB
+
+<../boot/bootmkfile
+<../port/portmkfile
+<|../port/mkbootrules $CONF
+
+init.h:D: ../port/initcode.c init9.s
+	$CC ../port/initcode.c
+	$AS init9.s
+	$LD -l -R1 -s -o init.out init9.$O initcode.$O /arm/lib/libc.a
+	{echo 'uchar initcode[]={'
+	 xd -1x <init.out |
+		sed -e 's/^[0-9a-f]+ //' -e 's/ ([0-9a-f][0-9a-f])/0x\1,/g'
+	 echo '};'} > init.h
+
+install:V:	$p$CONF
+	cp $p$CONF /$objtype/
+	for(i in $EXTRACOPIES)
+		import $i / /n/$i && cp $p$CONF $p$CONF.gz /n/$i/$objtype/
+
+devusb.$O usbehci.$O usbehcizynq.$O: ../port/usb.h uncached.h
+usbehci.$O usbehcizynq.$O: usbehci.h
--- /dev/null
+++ b/sys/src/9/zynq/mmu.c
@@ -1,0 +1,422 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+ulong *mpcore, *slcr;
+
+void
+mmuinit(void)
+{
+	m->l1.pa = ttbget();
+	m->l1.va = KADDR(m->l1.pa);
+	mpcore = vmap(MPCORE_BASE, 0x2000);
+	slcr = vmap(SLCR_BASE, 0x1000);
+	m->l1.va[L1X(TMAP)] = PADDR(TMAPL2(m->machno)) | L1PT;
+	incref(&m->l1);
+}
+
+void
+l1switch(L1 *p, int flush)
+{
+	assert(!islo());
+
+	ttbput(p->pa);
+	if(flush){
+		if(++m->asid == 0)
+			flushtlb();
+		setasid(m->asid);
+	}
+}
+
+static L1 *
+l1alloc(void)
+{
+	L1 *p;
+	int s;
+
+	s = splhi();
+	if(m->l1free != nil){
+		p = m->l1free;
+		p->next = nil;
+		m->l1free = m->l1free->next;
+		m->nfree--;
+		splx(s);
+		return p;
+	}else{
+		p = smalloc(sizeof(L1));
+		for(;;){
+			p->va = mallocalign(L1SZ, L1SZ, 0, 0);
+			if(p->va != nil)
+				break;
+			if(!waserror()){
+				resrcwait("no memory for L1 table");
+				poperror();
+			}
+		}
+		memmove(p->va, m->l1.va, L1SZ);
+		p->pa = PADDR(p->va);
+		splx(s);
+		return p;
+	}
+}
+
+static void
+l1free(L1 *l1)
+{
+	if(islo())
+		panic("l1free: islo");
+	if(m->nfree >= 40){
+		free(l1->va);
+		free(l1);
+	}else{
+		l1->next = m->l1free;
+		m->l1free = l1;
+		m->nfree++;
+	}
+}
+
+static void
+upallocl1(void)
+{
+	L1 *p;
+	int s;
+
+	if(up->l1 != nil)
+		return;
+	p = l1alloc();
+	s = splhi();
+	if(up->l1 != nil)
+		l1free(p);
+	else{
+		up->l1 = p;
+		l1switch(p, 1);
+	}
+	splx(s);
+}
+
+static void
+l2free(Proc *proc)
+{
+	int s;
+	ulong *t;
+	Page *p, **l;
+
+	if(proc->l1 == nil || proc->mmuused == nil)
+		return;
+	s = splhi();
+	l = &proc->mmuused;
+	for(p = *l; p != nil; p = p->next){
+		t = proc->l1->va + p->daddr;
+		*t++ = 0;
+		*t++ = 0;
+		*t++ = 0;
+		*t = 0;
+		l = &p->next;
+	}
+	splx(s);
+	*l = proc->mmufree;
+	proc->mmufree = proc->mmuused;
+	proc->mmuused = 0;
+}
+
+void
+mmuswitch(Proc *p)
+{
+	if(p->newtlb){
+		p->newtlb = 0;
+		l2free(p);
+	}
+	if(p->l1 != nil)
+		l1switch(p->l1, 1);
+	else
+		l1switch(&m->l1, 1);
+}
+
+void
+putmmu(uintptr va, uintptr pa, Page *pg)
+{
+	Page *p;
+	ulong *e;
+	ulong *l2;
+	PTE old;
+	uintptr l2p;
+
+	if(up->l1 == nil)
+		upallocl1();
+	if((pa & PTEUNCACHED) == 0)
+		pa |= L2CACHED;
+	e = &up->l1->va[L1RX(va)];
+	if((*e & 3) == 0){
+		if(up->mmufree != nil){
+			p = up->mmufree;
+			up->mmufree = p->next;
+		}else
+			p = newpage(0, 0, 0);
+		l2p = p->pa;
+		l2 = tmpmap(l2p);
+		memset(l2, 0, BY2PG);
+		coherence();
+		e[0] = p->pa | L1PT;
+		e[1] = e[0] + L2SZ;
+		e[2] = e[1] + L2SZ;
+		e[3] = e[2] + L2SZ;
+		coherence();
+		p->daddr = L1RX(va);
+		p->next = up->mmuused;
+		up->mmuused = p;
+	}else{
+		l2p = *e & ~(BY2PG - 1);
+		l2 = tmpmap(l2p);
+	}
+	e = &l2[L2RX(va)];
+	old = *e;
+	*e = pa | L2VALID | L2USER | L2LOCAL;
+	tmpunmap(l2);
+	coherence();
+	if((old & L2VALID) != 0)
+		flushpg((void *) va);
+	if(pg->cachectl[0] == PG_TXTFLUSH){
+		cleandse((void *) va, (void *) (va + BY2PG));
+		invalise((void *) va, (void *) (va + BY2PG));
+		pg->cachectl[0] = PG_NOFLUSH;
+	}
+}
+
+void
+checkmmu(uintptr, uintptr)
+{
+	print("checkmmu\n");
+}
+
+void
+flushmmu(void)
+{
+	int s;
+
+	s = splhi();
+	up->newtlb = 1;
+	mmuswitch(up);
+	splx(s);
+}
+
+void
+mmurelease(Proc *proc)
+{
+	Page *p, *n;
+
+	if(islo())
+		panic("mmurelease: islo");
+	
+	l1switch(&m->l1, 0);
+	if(proc->kmaptable != nil){
+		if(proc->l1 == nil)
+			panic("mmurelease: no l1");
+		if(decref(proc->kmaptable) != 0)
+			panic("mmurelease: kmap ref %ld", proc->kmaptable->ref);
+		if(proc->nkmap)
+			panic("mmurelease: nkmap %d", proc->nkmap);
+		if(PPN(proc->l1->va[L1X(KMAP)]) != proc->kmaptable->pa)
+			panic("mmurelease: bad kmap l2 %#.8lux kmap %#.8lux", proc->l1->va[L1X(KMAP)], proc->kmaptable->pa);
+		proc->l1->va[L1X(KMAP)] = 0;
+		pagechainhead(proc->kmaptable);
+		proc->kmaptable = nil;
+	}
+	if(proc->l1 != nil){
+		l2free(proc);
+		l1free(proc->l1);
+		proc->l1 = nil;
+	}
+	for(p = proc->mmufree; p != nil; p = n){
+		n = p->next;
+		if(decref(p) != 0)
+			panic("mmurelease: p->ref %ld", p->ref);
+		pagechainhead(p);
+	}
+	if(proc->mmufree != nil && palloc.r.p != nil)
+		wakeup(&palloc.r);
+	proc->mmufree = nil;
+}
+
+void
+countpagerefs(ulong *, int)
+{
+	print("countpagerefs\n");
+}
+
+void *
+kaddr(uintptr u)
+{
+	if(u >= (uintptr)-KZERO)
+		panic("kaddr: pa=%#.8lux", u);
+	return (void *)(u + KZERO);	
+}
+
+uintptr
+paddr(void *v)
+{
+	if((uintptr)v < KZERO)
+		panic("paddr: va=%#.8lux", (uintptr) v);
+	return (uintptr)v - KZERO;
+}
+
+uintptr
+cankaddr(uintptr u)
+{
+	if(u >= (uintptr)-KZERO)
+		return 0;
+	return -KZERO - u;
+}
+
+KMap *
+kmap(Page *page)
+{
+	ulong *e, *v;
+	int i;
+	ulong s;
+
+	if(up == nil)
+		panic("kmap: up=0 pc=%#.8lux", getcallerpc(&page));
+	if(up->l1 == nil)
+		upallocl1();
+	if(up->nkmap < 0)
+		panic("kmap %lud %s: nkmap=%d", up->pid, up->text, up->nkmap);
+	s = splhi();
+	up->nkmap++;
+	e = &up->l1->va[L1X(KMAP)];
+	if((*e & 3) == 0){
+		if(up->kmaptable != nil)
+			panic("kmaptable");
+		spllo();
+		up->kmaptable = newpage(0, 0, 0);
+		splhi();
+		v = tmpmap(up->kmaptable->pa);
+		memset(v, 0, BY2PG);
+		v[0] = page->pa | L2KERRW | L2VALID | L2CACHED | L2LOCAL;
+		v[NKMAP] = up->kmaptable->pa | L2KERRW | L2VALID | L2CACHED | L2LOCAL;
+		tmpunmap(v);
+		coherence();
+		*e = up->kmaptable->pa | L1PT;
+		splx(s);
+		coherence();
+		return (KMap *) KMAP;
+	}
+	if(up->kmaptable == nil)
+		panic("kmaptable");
+	e = (ulong *) (KMAP + NKMAP * BY2PG);
+	for(i = 0; i < NKMAP; i++)
+		if((e[i] & 3) == 0){
+			e[i] = page->pa | L2KERRW | L2VALID | L2CACHED | L2LOCAL;
+			splx(s);
+			coherence();
+			return (KMap *) (KMAP + i * BY2PG);
+		}
+	panic("out of kmap");
+	return nil;
+}
+
+void
+kunmap(KMap *arg)
+{
+	uintptr va;
+	ulong *e;
+	
+	va = (uintptr) arg;
+	if(up->l1 == nil || (up->l1->va[L1X(KMAP)] & 3) == 0)
+		panic("kunmap: no kmaps");
+	if(va < KMAP || va >= KMAP + NKMAP * BY2PG)
+		panic("kunmap: bad address %#.8lux pc=%#p", va, getcallerpc(&arg));
+	e = (ulong *) (KMAP + NKMAP * BY2PG) + L2X(va);
+	if((*e & 3) == 0)
+		panic("kunmap: not mapped %#.8lux pc=%#p", va, getcallerpc(&arg));
+	up->nkmap--;
+	if(up->nkmap < 0)
+		panic("kunmap %lud %s: nkmap=%d", up->pid, up->text, up->nkmap);
+	*e = 0;
+	coherence();
+	flushpg((void *) va);
+}
+
+void *
+tmpmap(ulong pa)
+{
+	ulong *u, *ub, *ue;
+	void *v;
+
+	if(cankaddr(pa))
+		return KADDR(pa);
+	ub = (ulong *) TMAPL2(m->machno);
+	ue = ub + NL2;
+	for(u = ub; u < ue; u++)
+		if((*u & 3) == 0){
+			*u = pa | L2VALID | L2CACHED | L2KERRW;
+			coherence();
+			v = (void *) ((u - ub) * BY2PG + TMAP);
+			return v;
+		}
+	panic("tmpmap: full (pa=%#.8lux)", pa);
+	return nil;
+}
+
+void
+tmpunmap(void *v)
+{
+	ulong *u;
+	
+	if(v >= (void*) KZERO)
+		return;
+	if(v < (void*)TMAP || v >= (void*)(TMAP + TMAPSZ))
+		panic("tmpunmap: invalid address (va=%#.8lux)", (uintptr) v);
+	u = (ulong *) TMAPL2(m->machno) + L2X(v);
+	if((*u & 3) == 0)
+		panic("tmpunmap: double unmap (va=%#.8lux)", (uintptr) v);
+	*u = 0;
+	coherence();
+	flushpg(v);
+}
+
+void *
+vmap(uintptr pa, ulong sz)
+{
+	ulong np;
+	void *vr, *ve;
+	static ulong *vp = (ulong *) VMAPL2 + 1; /* first page is uart */
+	
+	if((pa & BY2PG - 1) != 0)
+		panic("vmap: misaligned pa=%#.8lux", pa);
+	np = (sz + BY2PG - 1) >> PGSHIFT;
+	vr = (char*) VMAP + (vp - (ulong *)VMAPL2 << PGSHIFT);
+	ve = (ulong *) (VMAPL2 + VMAPL2SZ);
+	while(np-- != 0){
+		if(vp == ve)
+			panic("vmap: out of vmap space (pa=%#.8lux)", pa);
+		*vp++ = pa | L2VALID | L2DEVICE | L2KERRW;
+		pa += BY2PG;
+	}
+	coherence();
+	return vr;
+}
+
+/* nasty things happen when there are cache entries for uncached memory
+   so must make sure memory is not mapped ANYWHERE cached */
+uintptr
+ualloc(ulong len, void **va)
+{
+	static uintptr free = OCM_BASE;
+	uintptr pa;
+	
+	if(len == 0)
+		panic("ualloc: len == 0");
+	len = PGROUND(len);
+	if(free + len < OCM_BASE)
+		panic("ualloc: out of uncached memory");
+	pa = free;
+	free += len;
+	if(va != nil){
+		*va = vmap(pa, len);
+		invaldse(*va, (char *) *va + len);
+	}
+	return pa;
+}
--- /dev/null
+++ b/sys/src/9/zynq/screen.c
@@ -1,0 +1,126 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+
+#define	Image	IMAGE
+#include <draw.h>
+#include <memdraw.h>
+#include <cursor.h>
+#include "screen.h"
+
+Cursor	arrow = {
+	{ -1, -1 },
+	{ 0xFF, 0xFF, 0x80, 0x01, 0x80, 0x02, 0x80, 0x0C,
+	  0x80, 0x10, 0x80, 0x10, 0x80, 0x08, 0x80, 0x04,
+	  0x80, 0x02, 0x80, 0x01, 0x80, 0x02, 0x8C, 0x04,
+	  0x92, 0x08, 0x91, 0x10, 0xA0, 0xA0, 0xC0, 0x40,
+	},
+	{ 0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFC, 0x7F, 0xF0,
+	  0x7F, 0xE0, 0x7F, 0xE0, 0x7F, 0xF0, 0x7F, 0xF8,
+	  0x7F, 0xFC, 0x7F, 0xFE, 0x7F, 0xFC, 0x73, 0xF8,
+	  0x61, 0xF0, 0x60, 0xE0, 0x40, 0x40, 0x00, 0x00,
+	},
+};
+
+Memimage *gscreen;
+static Memdata xgdata;
+
+static Memimage xgscreen =
+{
+	{ 0, 0, 800, 600 },	/* r */
+	{ 0, 0, 800, 600 },	/* clipr */
+	24,			/* depth */
+	3,			/* nchan */
+	BGR24,			/* chan */
+	nil,			/* cmap */
+	&xgdata,		/* data */
+	0,			/* zero */
+	0, 			/* width in words of a single scan line */
+	0,			/* layer */
+	0,			/* flags */
+};
+
+void
+cursoron(void)
+{
+}
+
+void
+cursoroff(void)
+{
+}
+
+void
+setcursor(Cursor*)
+{
+}
+
+void
+flushmemscreen(Rectangle)
+{
+}
+
+void
+drawflushreal(void)
+{
+	uchar *fb, *fbe;
+	
+	fb = xgdata.bdata;
+	fbe = fb + Dx(xgscreen.r) * Dy(xgscreen.r) * 3;
+	cleandse(fb, fbe);
+	clean2pa(PADDR(fb), PADDR(fbe));
+}
+
+void
+screeninit(void)
+{
+	uchar *fb;
+
+	fb = xspanalloc(Dx(xgscreen.r) * Dy(xgscreen.r) * 3, 64, 0);
+	print("%x\n", PADDR(fb));
+	memsetchan(&xgscreen, BGR24);
+	conf.monitor = 1;
+	xgdata.bdata = fb;
+	xgdata.ref = 1;
+	gscreen = &xgscreen;
+	gscreen->width = wordsperline(gscreen->r, gscreen->depth);
+
+	memimageinit();
+}
+
+uchar*
+attachscreen(Rectangle *r, ulong *chan, int* d, int *width, int *softscreen)
+{
+	*r = gscreen->r;
+	*d = gscreen->depth;
+	*chan = gscreen->chan;
+	*width = gscreen->width;
+	*softscreen = 0;
+
+	return gscreen->data->bdata;
+}
+
+void
+getcolor(ulong, ulong *, ulong *, ulong *)
+{
+}
+
+int
+setcolor(ulong, ulong, ulong, ulong)
+{
+	return 0;
+}
+
+void
+blankscreen(int)
+{
+}
+
+void
+mousectl(Cmdbuf *)
+{
+}
--- /dev/null
+++ b/sys/src/9/zynq/screen.h
@@ -1,0 +1,44 @@
+typedef struct Cursor Cursor;
+typedef struct Cursorinfo Cursorinfo;
+struct Cursorinfo {
+	Cursor;
+	Lock;
+};
+
+/* devmouse.c */
+extern void mousetrack(int, int, int, int);
+extern void absmousetrack(int, int, int, int);
+extern Point mousexy(void);
+
+extern void mouseaccelerate(int);
+extern int m3mouseputc(Queue*, int);
+extern int m5mouseputc(Queue*, int);
+extern int mouseputc(Queue*, int);
+
+extern Cursorinfo cursor;
+extern Cursor arrow;
+
+/* mouse.c */
+extern void mousectl(Cmdbuf*);
+extern void mouseresize(void);
+extern void mouseredraw(void);
+
+/* screen.c */
+extern void	blankscreen(int);
+extern void	flushmemscreen(Rectangle);
+extern uchar*	attachscreen(Rectangle*, ulong*, int*, int*, int*);
+extern void	cursoron(void);
+extern void	cursoroff(void);
+extern void	setcursor(Cursor*);
+
+/* devdraw.c */
+extern QLock	drawlock;
+
+#define ishwimage(i)	1		/* for ../port/devdraw.c */
+
+/* swcursor.c */
+void		swcursorhide(void);
+void		swcursoravoid(Rectangle);
+void		swcursordraw(Point);
+void		swcursorload(Cursor *);
+void		swcursorinit(void);
--- /dev/null
+++ b/sys/src/9/zynq/timer.c
@@ -1,0 +1,91 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+enum {
+	TIMERDIV = 1,
+	LTIMERDIV = 1,
+	
+	ARM_PLL_CTRL = 0x100/4,
+	ARM_CLK_CTRL = 0x120/4,
+
+	GTIMERVALL = 0x200/4,
+	GTIMERVALH,
+	GTIMERCTL,
+	LTIMERVAL = 0x604/4,
+	LTIMERCTL,
+	LTIMERISR,
+};
+
+uvlong timerhz;
+
+void
+delay(int)
+{
+}
+
+void
+microdelay(int)
+{
+}
+
+uvlong
+fastticks(uvlong *hz)
+{
+	ulong lo, hi;
+
+	if(hz != nil)
+		*hz = timerhz;
+	do{
+		hi = mpcore[GTIMERVALH];
+		lo = mpcore[GTIMERVALL];
+	}while(hi != mpcore[GTIMERVALH]);
+	return lo | (uvlong)hi << 32;
+}
+
+ulong
+µs(void)
+{
+	NOPE
+	return 0;
+}
+
+void
+timerset(Tval v)
+{
+	vlong w;
+
+	w = v - fastticks(nil);
+	if(w < 1)
+		w = 1;
+	if(w > 0xffffffffLL)
+		w = 0xffffffff;
+	mpcore[LTIMERCTL] &= ~1;
+	mpcore[LTIMERVAL] = w;
+	mpcore[LTIMERCTL] |= 1;
+}
+
+void
+timerirq(Ureg *u, void *)
+{
+	if((mpcore[LTIMERISR] & 1) != 0){
+		mpcore[LTIMERISR] |= 1;
+		timerintr(u, 0);
+	}
+}
+
+void
+timerinit(void)
+{
+	int mhz;
+	
+	mhz = PS_CLK * (slcr[ARM_PLL_CTRL] >> 12 & 0x7f) / (slcr[ARM_CLK_CTRL] >> 8 & 0x3f);
+	timerhz = mhz * 500000;
+	mpcore[GTIMERCTL] = TIMERDIV - 1 << 8 | 3;
+	mpcore[LTIMERCTL] = LTIMERDIV - 1 << 8 | 4;
+	intrenable(TIMERIRQ, timerirq, nil, EDGE, "clock");
+	setpmcr(7);
+}
--- /dev/null
+++ b/sys/src/9/zynq/trap.c
@@ -1,0 +1,544 @@
+#include "u.h"
+#include <ureg.h>
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/error.h"
+#include "tos.h"
+
+static void
+_dumpstack(Ureg *ureg)
+{
+	uintptr l, v, i, estack;
+	extern ulong etext;
+	int x;
+	char *s;
+
+	if((s = getconf("*nodumpstack")) != nil && strcmp(s, "0") != 0){
+		iprint("dumpstack disabled\n");
+		return;
+	}
+	iprint("dumpstack\n");
+
+	x = 0;
+	x += iprint("ktrace /arm/9zynq %.8lux %.8lux %.8lux <<EOF\n", ureg->pc, ureg->sp, ureg->r14);
+	i = 0;
+	if(up
+	&& (uintptr)&l >= (uintptr)up->kstack
+	&& (uintptr)&l <= (uintptr)up->kstack+KSTACK)
+		estack = (uintptr)up->kstack+KSTACK;
+	else if((uintptr)&l >= (uintptr)m->stack
+	&& (uintptr)&l <= (uintptr)m+MACHSIZE)
+		estack = (uintptr)m+MACHSIZE;
+	else
+		return;
+	x += iprint("estackx %p\n", estack);
+
+	for(l = (uintptr)&l; l < estack; l += sizeof(uintptr)){
+		v = *(uintptr*)l;
+		if((KTZERO < v && v < (uintptr)&etext) || estack-l < 32){
+			x += iprint("%.8p=%.8p ", l, v);
+			i++;
+		}
+		if(i == 4){
+			i = 0;
+			x += iprint("\n");
+		}
+	}
+	if(i)
+		iprint("\n");
+	iprint("EOF\n");
+}
+
+static void
+faultarm(Ureg *ureg, ulong fsr, uintptr addr)
+{
+	int user, insyscall, read, n;
+	static char buf[ERRMAX];
+	
+	read = (fsr & (1<<11)) == 0;
+	user = userureg(ureg);
+	if(!user){
+		if(addr >= USTKTOP || up == nil)
+			_dumpstack(ureg);
+		if(addr >= USTKTOP)
+			panic("kernel fault: bad address pc=%#.8lux addr=%#.8lux fsr=%#.8lux", ureg->pc, addr, fsr);
+		if(up == nil)
+			panic("kernel fault: no user process pc=%#.8lux addr=%#.8lux fsr=%#.8lux", ureg->pc, addr, fsr);
+	}
+	if(up == nil)
+		panic("user fault: up=nil pc=%#.8lux addr=%#.8lux fsr=%#.8lux", ureg->pc, addr, fsr);
+	insyscall = up->insyscall;
+	up->insyscall = 1;
+	n = fault(addr, read);
+	if(n < 0){
+		if(!user){
+			dumpregs(ureg);
+			_dumpstack(ureg);
+			panic("kernel fault: pc=%#.8lux addr=%#.8lux fsr=%#.8lux", ureg->pc, addr, fsr);
+		}
+		sprint(buf, "sys: trap: fault %s addr=%#.8lux", read ? "read" : "write", addr);
+		postnote(up, 1, buf, NDebug);
+	}
+	up->insyscall = insyscall;
+}
+
+static void
+mathtrap(Ureg *, ulong)
+{
+	if((up->fpstate & FPillegal) != 0){
+		postnote(up, 1, "sys: floating point in note handler", NDebug);
+		return;
+	}
+	switch(up->fpstate){
+	case FPinit:
+		fpinit();
+		up->fpstate = FPactive;
+		break;
+	case FPinactive:
+		fprestore(&up->fpsave);
+		up->fpstate = FPactive;
+		break;
+	case FPactive:
+		postnote(up, 1, "sys: floating point error", NDebug);
+		break;
+	}
+}
+
+void
+trap(Ureg *ureg)
+{
+	int user;
+	ulong opc, cp;
+
+	user = userureg(ureg);
+	if(user){
+		if(up == nil)
+			panic("user trap: up=nil");
+		up->dbgreg = ureg;
+		cycles(&up->kentry);
+	}
+	switch(ureg->type){
+	case PsrMund:
+		ureg->pc -= 4;
+		if(user){
+			spllo();
+			if(okaddr(ureg->pc, 4, 0)){
+				opc = *(ulong*)ureg->pc;
+				if((opc & 0x0f000000) == 0x0e000000 || (opc & 0x0e000000) == 0x0c000000){
+					cp = opc >> 8 & 15;
+					if(cp == 10 || cp == 11){
+						mathtrap(ureg, opc);
+						break;
+					}
+				}
+			}
+			postnote(up, 1, "sys: trap: invalid opcode", NDebug);
+			break;
+		}
+		panic("invalid opcode at pc=%#.8lux lr=%#.8lux", ureg->pc, ureg->r14);
+		break;
+	case PsrMiabt:
+		ureg->pc -= 4;
+		faultarm(ureg, getifsr(), getifar());
+		break;
+	case PsrMabt:
+		ureg->pc -= 8;
+		faultarm(ureg, getdfsr(), getdfar());
+		break;
+	case PsrMirq:
+		ureg->pc -= 4;
+		intr(ureg);
+		break;
+	default:
+		print("unknown trap type %ulx\n", ureg->type);
+	}
+	splhi();
+	if(user){
+		if(up->procctl || up->nnote)
+			notify(ureg);
+		kexit(ureg);
+	}
+}
+
+#include "../port/systab.h"
+
+void
+syscall(Ureg *ureg)
+{
+	char *e;
+	uintptr sp;
+	long ret;
+	int i, s;
+	ulong scallnr;
+	vlong startns, stopns;
+	
+	if(!userureg(ureg))
+		panic("syscall: pc=%#.8lux", ureg->pc);
+	
+	cycles(&up->kentry);
+	
+	m->syscall++;
+	up->insyscall = 1;
+	up->pc = ureg->pc;
+	up->dbgreg = ureg;
+	
+	sp = ureg->sp;
+	up->scallnr = scallnr = ureg->r0;
+
+	spllo();
+	
+	up->nerrlab = 0;
+	ret = -1;
+	if(!waserror()){
+		if(sp < USTKTOP - BY2PG || sp > USTKTOP - sizeof(Sargs) - BY2WD){
+			validaddr(sp, sizeof(Sargs)+BY2WD, 0);
+			evenaddr(sp);
+		}
+		up->s = *((Sargs*) (sp + BY2WD));
+		
+		if(up->procctl == Proc_tracesyscall){
+			syscallfmt(scallnr, ureg->pc, (va_list) up->s.args);
+			s = splhi();
+			up->procctl = Proc_stopme;
+			procctl(up);
+			splx(s);
+			startns = todget(nil);
+		}
+		
+		if(scallnr >= nsyscall || systab[scallnr] == 0){
+			pprint("bad sys call number %lud pc %lux", scallnr, ureg->pc);
+			postnote(up, 1, "sys: bad sys call", NDebug);
+			error(Ebadarg);
+		}
+		up->psstate = sysctab[scallnr];
+		ret = systab[scallnr]((va_list)up->s.args);
+		poperror();
+	}else{
+		e = up->syserrstr;
+		up->syserrstr = up->errstr;
+		up->errstr = e;
+	}
+	if(up->nerrlab){
+		print("bad errstack [%lud]: %d extra\n", scallnr, up->nerrlab);
+		for(i = 0; i < NERR; i++)
+			print("sp=%lux pc=%lux\n", up->errlab[i].sp, up->errlab[i].pc);
+		panic("error stack");
+	}
+	
+	ureg->r0 = ret;
+	if(up->procctl == Proc_tracesyscall){
+		stopns = todget(nil);
+		sysretfmt(scallnr, (va_list) up->s.args, ret, startns, stopns);
+		s = splhi();
+		up->procctl = Proc_stopme;
+		procctl(up);
+		splx(s);
+	}
+	
+	up->insyscall = 0;
+	up->psstate = 0;
+	if(scallnr == NOTED)
+		noted(ureg, *((ulong *) up->s.args));
+
+	if(scallnr != RFORK && (up->procctl || up->nnote)){
+		splhi();
+		notify(ureg);
+	}
+	if(up->delaysched)
+		sched();
+	kexit(ureg);
+	splhi();
+}
+
+int
+notify(Ureg *ureg)
+{
+	int l;
+	ulong s, sp;
+	Note *n;
+
+	if(up->procctl)
+		procctl(up);
+	if(up->nnote == 0)
+		return 0;
+
+	if(up->fpstate == FPactive){
+		fpsave(&up->fpsave);
+		up->fpstate = FPinactive;
+	}
+	up->fpstate |= FPillegal;
+
+	s = spllo();
+	qlock(&up->debug);
+	up->notepending = 0;
+	n = &up->note[0];
+	if(strncmp(n->msg, "sys:", 4) == 0){
+		l = strlen(n->msg);
+		if(l > ERRMAX-15)	/* " pc=0x12345678\0" */
+			l = ERRMAX-15;
+		sprint(n->msg+l, " pc=0x%.8lux", ureg->pc);
+	}
+
+	if(n->flag!=NUser && (up->notified || up->notify==0)){
+		qunlock(&up->debug);
+		if(n->flag == NDebug)
+			pprint("suicide: %s\n", n->msg);
+		pexit(n->msg, n->flag!=NDebug);
+	}
+
+	if(up->notified){
+		qunlock(&up->debug);
+		splhi();
+		return 0;
+	}
+
+	if(!up->notify){
+		qunlock(&up->debug);
+		pexit(n->msg, n->flag!=NDebug);
+	}
+	sp = ureg->sp;
+	sp -= 256;	/* debugging: preserve context causing problem */
+	sp -= sizeof(Ureg);
+
+	if(!okaddr((uintptr)up->notify, 1, 0)
+	|| !okaddr(sp-ERRMAX-4*BY2WD, sizeof(Ureg)+ERRMAX+4*BY2WD, 1)
+	|| ((uintptr) up->notify & 3) != 0
+	|| (sp & 3) != 0){
+		qunlock(&up->debug);
+		pprint("suicide: bad address in notify\n");
+		pexit("Suicide", 0);
+	}
+
+	memmove((Ureg*)sp, ureg, sizeof(Ureg));
+	*(Ureg**)(sp-BY2WD) = up->ureg;	/* word under Ureg is old up->ureg */
+	up->ureg = (void*)sp;
+	sp -= BY2WD+ERRMAX;
+	memmove((char*)sp, up->note[0].msg, ERRMAX);
+	sp -= 3*BY2WD;
+	*(ulong*)(sp+2*BY2WD) = sp+3*BY2WD;
+	*(ulong*)(sp+1*BY2WD) = (ulong)up->ureg;
+	ureg->r0 = (uintptr) up->ureg;
+	ureg->sp = sp;
+	ureg->pc = (uintptr) up->notify;
+	ureg->r14 = 0;
+	up->notified = 1;
+	up->nnote--;
+	memmove(&up->lastnote, &up->note[0], sizeof(Note));
+	memmove(&up->note[0], &up->note[1], up->nnote*sizeof(Note));
+
+	qunlock(&up->debug);
+	splx(s);
+	return 1;
+}
+
+void
+noted(Ureg *ureg, ulong arg0)
+{
+	Ureg *nureg;
+	ulong oureg, sp;
+	
+	qlock(&up->debug);
+	if(arg0 != NRSTR && !up->notified){
+		qunlock(&up->debug);
+		pprint("call to noted() when not notified\n");
+		pexit("Suicide", 0);
+	}
+	up->notified = 0;
+	
+	nureg = up->ureg;
+	up->fpstate &= ~FPillegal;
+	
+	oureg = (ulong) nureg;
+	if(!okaddr(oureg - BY2WD, BY2WD + sizeof(Ureg), 0) || (oureg & 3) != 0){
+		qunlock(&up->debug);
+		pprint("bad ureg in noted or call to noted when not notified\n");
+		pexit("Suicide", 0);
+	}
+	
+	nureg->psr = nureg->psr & 0xf80f0000 | ureg->psr & 0x07f0ffff;
+	
+	memmove(ureg, nureg, sizeof(Ureg));
+	
+	switch(arg0){
+	case NCONT: case NRSTR:
+		if(!okaddr(nureg->pc, BY2WD, 0) || !okaddr(nureg->sp, BY2WD, 0) ||
+				(nureg->pc & 3) != 0 || (nureg->sp & 3) != 0){
+			qunlock(&up->debug);
+			pprint("suicide: trap in noted\n");
+			pexit("Suicide", 0);
+		}
+		up->ureg = (Ureg *) (*(ulong *) (oureg - BY2WD));
+		qunlock(&up->debug);
+		break;
+	
+	case NSAVE:
+		if(!okaddr(nureg->pc, BY2WD, 0) || !okaddr(nureg->sp, BY2WD, 0) ||
+				(nureg->pc & 3) != 0 || (nureg->sp & 3) != 0){
+			qunlock(&up->debug);
+			pprint("suicide: trap in noted\n");
+			pexit("Suicide", 0);
+		}
+		qunlock(&up->debug);
+		sp = oureg - 4 * BY2WD - ERRMAX;
+		splhi();
+		ureg->sp = sp;
+		((ulong *) sp)[1] = oureg;
+		((ulong *) sp)[0] = 0;
+		break;
+	
+	default:
+		up->lastnote.flag = NDebug;
+	
+	case NDFLT:
+		qunlock(&up->debug);
+		if(up->lastnote.flag == NDebug)
+			pprint("suicide: %s\n", up->lastnote.msg);
+		pexit(up->lastnote.msg, up->lastnote.flag != NDebug);
+	}
+}
+
+
+void
+dumpstack(void)
+{
+	callwithureg(_dumpstack);
+}
+
+void
+dumpregs(Ureg *)
+{
+	print("dumpregs\n");
+}
+
+void
+setkernur(Ureg *ureg, Proc *p)
+{
+	ureg->pc = p->sched.pc;
+	ureg->sp = p->sched.sp + 4;
+	ureg->r14 = (uintptr) sched;
+}
+
+void
+setregisters(Ureg* ureg, char* pureg, char* uva, int n)
+{
+	ulong v;
+
+	v = ureg->psr;
+	memmove(pureg, uva, n);
+	ureg->psr = ureg->psr & 0xf80f0000 | v & 0x07f0ffff;
+}
+
+void
+callwithureg(void (*f) (Ureg *))
+{
+	Ureg u;
+	
+	u.pc = getcallerpc(&f);
+	u.sp = (uintptr) &f - 4;
+	f(&u);
+}
+
+uintptr
+userpc(void)
+{
+	Ureg *ur;
+	
+	ur = up->dbgreg;
+	return ur->pc;
+}
+
+uintptr
+dbgpc(Proc *)
+{
+	Ureg *ur;
+	
+	ur = up->dbgreg;
+	if(ur == nil)
+		return 0;
+	return ur->pc;
+}
+
+void
+procsave(Proc *p)
+{
+	uvlong t;
+
+	if(p->fpstate == FPactive){
+		if(p->state == Moribund)
+			fpclear();
+		else
+			fpsave(&p->fpsave);
+		p->fpstate = FPinactive;
+	}
+	cycles(&t);
+	p->kentry -= t;
+	p->pcycles += t;
+	
+	l1switch(&m->l1, 0);
+}
+
+void
+procrestore(Proc *p)
+{
+	uvlong t;
+
+	if(p->kp)
+		return;
+
+	cycles(&t);
+	p->kentry += t;
+	p->pcycles -= t;
+}
+
+static void
+linkproc(void)
+{
+	spllo();
+	up->kpfun(up->kparg);
+	pexit("kproc dying", 0);
+}
+
+void
+kprocchild(Proc* p, void (*func)(void*), void* arg)
+{
+	p->sched.pc = (uintptr) linkproc;
+	p->sched.sp = (uintptr) p->kstack + KSTACK;
+
+	p->kpfun = func;
+	p->kparg = arg;
+}
+
+void
+forkchild(Proc *p, Ureg *ureg)
+{
+	Ureg *cureg;
+
+	p->sched.pc = (uintptr) forkret;
+	p->sched.sp = (uintptr) p->kstack + KSTACK - sizeof(Ureg);
+
+	cureg = (Ureg*) p->sched.sp;
+	memmove(cureg, ureg, sizeof(Ureg));
+	cureg->r0 = 0;
+
+	p->psstate = 0;
+	p->insyscall = 0;
+}
+
+uintptr
+execregs(uintptr entry, ulong ssize, ulong nargs)
+{
+	ulong *sp;
+	Ureg *ureg;
+
+	sp = (ulong*)(USTKTOP - ssize);
+	*--sp = nargs;
+
+	ureg = up->dbgreg;
+	ureg->sp = (uintptr) sp;
+	ureg->pc = entry;
+	ureg->r14 = 0;
+	return USTKTOP-sizeof(Tos);
+}
--- /dev/null
+++ b/sys/src/9/zynq/uartzynq.c
@@ -1,0 +1,244 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+
+enum {
+	CTRL = 0,
+	MODE,
+	IRQEN,
+	IRQDIS,
+	MASK,
+	INTSTAT,
+	RXFIFOLVL = 0x20/4,
+	CHANSTAT = 0x2C/4,
+	FIFO = 0x30/4,
+};
+
+enum {
+	TXFULL = 1<<4,
+	TXEMPTY = 1<<3,
+	RXEMPTY = 1<<1,
+	RXTRIG = 1<<0,
+};
+
+typedef struct Ctlr {
+	Lock;
+	ulong *r;
+	int irq, iena;
+} Ctlr;
+
+Uart* uartenable(Uart *);
+
+extern PhysUart zynqphysuart;
+
+static Ctlr zctlr[1] = {
+	{
+		.r = (void *) VMAP,
+		.irq = UART1IRQ,
+	}
+};
+
+static Uart zuart[1] = {
+	{
+		.regs = &zctlr[0],
+		.name = "UART1",
+		.freq = 25000000,
+		.phys = &zynqphysuart,
+		.console = 1,
+		.baud = 115200,
+	}
+};
+
+void
+uartinit(void)
+{
+	consuart = zuart;
+}
+
+static Uart *
+zuartpnp(void)
+{
+	return zuart;
+}
+
+static void
+zuartkick(Uart *uart)
+{
+	Ctlr *ct;
+	int i;
+
+	if(uart->blocked)
+		return;
+	ct = uart->regs;
+	for(i = 0; i < 128; i++){
+		if((ct->r[CHANSTAT] & TXFULL) != 0)
+			break;
+		if(uart->op >= uart->oe && uartstageoutput(uart) == 0)
+			break;
+		ct->r[FIFO] = *uart->op++;
+	}
+}
+
+static void
+zuartintr(Ureg *, void *arg)
+{
+	Uart *uart;
+	Ctlr *ct;
+	int c;
+	ulong fl;
+	
+	uart = arg;
+	ct = uart->regs;
+	fl = ct->r[INTSTAT] & ct->r[MASK];
+	ct->r[INTSTAT] = fl;
+	if((fl & RXTRIG) != 0)
+		while((ct->r[CHANSTAT] & RXEMPTY) == 0){
+			c = ct->r[FIFO];
+			uartrecv(uart, c);
+		}
+	if((fl & TXEMPTY) != 0)
+		zuartkick(uart);
+}
+
+static void
+zuartenable(Uart *uart, int ie)
+{
+	Ctlr *ctlr;
+	
+	ctlr = uart->regs;
+	ilock(ctlr);
+	while((ctlr->r[CHANSTAT] & TXEMPTY) == 0)
+		;
+	ctlr->r[IRQDIS] = -1;
+	ctlr->r[RXFIFOLVL] = 1;
+	if(ie){
+		if(!ctlr->iena){
+			intrenable(ctlr->irq, zuartintr, uart, LEVEL, uart->name);
+			ctlr->iena = 1;
+		}
+		ctlr->r[IRQEN] = RXTRIG | TXEMPTY;
+	}
+	iunlock(ctlr);
+}
+
+static int
+zuartgetc(Uart *uart)
+{
+	Ctlr *c;
+	
+	c = uart->regs;
+	while((c->r[CHANSTAT] & RXEMPTY) != 0)
+		;
+	return c->r[FIFO];
+}
+
+static void
+zuartputc(Uart *uart, int c)
+{
+	Ctlr *ct;
+	
+	ct = uart->regs;
+	while((ct->r[CHANSTAT] & TXFULL) != 0)
+		;
+	ct->r[FIFO] = c;
+	return;
+}
+
+int
+uartconsole(void)
+{
+	Uart *uart = zuart;
+
+	if(up == nil)
+		return -1;
+
+	if(uartenable(uart) != nil){
+		serialoq = uart->oq;
+		uart->opens++;
+		consuart = uart;
+	}
+	return 0;
+}
+
+int
+zuartbits(Uart *uart, int n)
+{
+	Ctlr *ct;
+	
+	ct = uart->regs;
+	ct->r[MODE] &= ~6;
+	switch(n){
+	case 8:
+		return 0;
+	case 7:
+		ct->r[MODE] |= 4;
+		return 0;
+	case 6:
+		ct->r[MODE] |= 6;
+		return 0;
+	default:
+		return -1;
+	}
+}
+
+int
+zuartbaud(Uart *, int n)
+{
+	print("uart baud %d\n", n);
+	return 0;
+}
+
+int
+zuartparity(Uart *uart, int p)
+{
+	Ctlr *ct;
+	
+	ct = uart->regs;
+	switch(p){
+	case 'o':
+		ct->r[MODE] = ct->r[MODE] & ~0x38 | 0x08;
+		return 0;
+	case 'e':
+		ct->r[MODE] = ct->r[MODE] & ~0x38;
+		return 0;
+	case 'n':
+		ct->r[MODE] = ct->r[MODE] & 0x38 | 0x20;
+		return 0;
+	default:
+		return -1;
+	}
+}
+
+void
+zuartnop(Uart *, int)
+{
+}
+
+int
+zuartnope(Uart *, int)
+{
+	return -1;
+}
+
+
+PhysUart zynqphysuart = {
+	.pnp = zuartpnp,
+	.enable = zuartenable,
+	.kick = zuartkick,
+	.getc = zuartgetc,
+	.putc = zuartputc,
+	.bits = zuartbits,
+	.baud = zuartbaud,
+	.parity = zuartparity,
+	
+	.stop = zuartnope,
+	.rts = zuartnop,
+	.dtr = zuartnop,
+	.dobreak = zuartnop,
+	.fifo = zuartnop,
+	.power = zuartnop,
+	.modemctl = zuartnop,
+};
--- /dev/null
+++ b/sys/src/9/zynq/uncached.h
@@ -1,0 +1,27 @@
+#define free			ucfree
+#define malloc			myucalloc
+#define mallocz			ucallocz
+#define smalloc			myucalloc
+#define xspanalloc		ucallocalign
+
+#define allocb			ucallocb
+#define iallocb			uciallocb
+#define freeb			ucfreeb
+
+static void *
+ucallocz(uint n, int)
+{
+	char *p = ucalloc(n);
+
+	if (p)
+		memset(p, 0, n);
+	else
+		panic("ucalloc: out of memory");
+	return p;
+}
+
+static void *
+myucalloc(uint n)
+{
+	return ucallocz(n, 1);
+}
--- /dev/null
+++ b/sys/src/9/zynq/usbehci.h
@@ -1,0 +1,141 @@
+/* override default macros from ../port/usb.h */
+#undef	dprint
+#undef	ddprint
+#undef	deprint
+#undef	ddeprint
+#define dprint		if(ehcidebug)print
+#define ddprint		if(ehcidebug>1)print
+#define deprint		if(ehcidebug || ep->debug)print
+#define ddeprint	if(ehcidebug>1 || ep->debug>1)print
+
+enum {
+	/* typed links  */
+	Lterm		= 1,
+	Litd		= 0<<1,
+	Lqh		= 1<<1,
+	Lsitd		= 2<<1,
+	Lfstn		= 3<<1,		/* we don't use these */
+
+	/* Cmd reg. */
+	Cstop		= 0x00000,	/* stop running */
+	Crun		= 0x00001,	/* start operation */
+	Chcreset	= 0x00002,	/* host controller reset */
+	Cflsmask	= 0x0000C,	/* frame list size bits */
+	Cfls1024	= 0x00000,	/* frame list size 1024 */
+	Cfls512		= 0x00004,	/* frame list size 512 frames */
+	Cfls256		= 0x00008,	/* frame list size 256 frames */
+	Cpse		= 0x00010,	/* periodic sched. enable */
+	Case		= 0x00020,	/* async sched. enable */
+	Ciasync		= 0x00040,	/* interrupt on async advance doorbell */
+	Citc1		= 0x10000,	/* interrupt threshold ctl. 1 µframe */
+	Citc4		= 0x40000,	/* same. 2 µframes */
+	/* ... */
+	Citc8		= 0x80000,	/* same. 8 µframes (can go up to 64) */
+
+	/* Sts reg. */
+	Sasyncss	= 0x08000,	/* aync schedule status */
+	Speriodss	= 0x04000,	/* periodic schedule status */
+	Srecl		= 0x02000,	/* reclamnation (empty async sched.) */
+	Shalted		= 0x01000,	/* h.c. is halted */
+	Sasync		= 0x00020,	/* interrupt on async advance */
+	Sherr		= 0x00010,	/* host system error */
+	Sfrroll		= 0x00008,	/* frame list roll over */
+	Sportchg	= 0x00004,	/* port change detect */
+	Serrintr	= 0x00002,		/* error interrupt */
+	Sintr		= 0x00001,	/* interrupt */
+	Sintrs		= 0x0003F,	/* interrupts status */
+
+	/* Portsc reg. */
+	Pspresent	= 0x00000001,	/* device present */
+	Psstatuschg	= 0x00000002,	/* Pspresent changed */
+	Psenable	= 0x00000004,	/* device enabled */
+	Pschange	= 0x00000008,	/* Psenable changed */
+	Psresume	= 0x00000040,	/* resume detected */
+	Pssuspend	= 0x00000080,	/* port suspended */
+	Psreset		= 0x00000100,	/* port reset */
+	Pspower		= 0x00001000,	/* port power on */
+
+	/* Intr reg. */
+	Iusb		= 0x01,		/* intr. on usb */
+	Ierr		= 0x02,		/* intr. on usb error */
+	Iportchg	= 0x04,		/* intr. on port change */
+	Ifrroll		= 0x08,		/* intr. on frlist roll over */
+	Ihcerr		= 0x10,		/* intr. on host error */
+	Iasync		= 0x20,		/* intr. on async advance enable */
+	Iall		= 0x3F,		/* all interrupts */
+	
+	Callmine	= 1,
+	
+	/* hack to disable port handoff */
+	Psowner = 0,
+	Pslinemask = 0,
+	Pslow = -1
+};
+
+typedef struct Ctlr Ctlr;
+typedef void Ecapio;
+typedef struct Eopio Eopio;
+typedef struct Isoio Isoio;
+typedef struct Poll Poll;
+typedef struct Qh Qh;
+typedef struct Qtree Qtree;
+
+#pragma incomplete Ctlr
+
+struct Eopio
+{
+	ulong cmd;
+	ulong sts;
+	ulong intr;
+	ulong frno;
+	ulong dummy1[2];
+	ulong frbase;
+	ulong link;
+	ulong dummy2[9];
+	ulong config;
+	ulong portsc[1];
+};
+
+struct Poll
+{
+	Lock;
+	Rendez;
+	int must;
+	int does;
+};
+
+struct Ctlr
+{
+	Rendez;			/* for waiting to async advance doorbell */
+	Lock;			/* for ilock. qh lists and basic ctlr I/O */
+	QLock	portlck;	/* for port resets/enable... (and doorbell) */
+	int	active;		/* in use or not */
+	void*	capio;		/* base address for debug info */
+	Eopio*	opio;		/* Operational i/o regs */
+
+	int	nframes;	/* 1024, 512, or 256 frames in the list */
+	ulong*	frames;		/* periodic frame list (hw) */
+	Qh*	qhs;		/* async Qh circular list for bulk/ctl */
+	Qtree*	tree;		/* tree of Qhs for the periodic list */
+	int	ntree;		/* number of dummy qhs in tree */
+	Qh*	intrqhs;		/* list of (not dummy) qhs in tree  */
+	Isoio*	iso;		/* list of active Iso I/O */
+	ulong	load;
+	ulong	isoload;
+	int	nintr;		/* number of interrupts attended */
+	int	ntdintr;	/* number of intrs. with something to do */
+	int	nqhintr;	/* number of async td intrs. */
+	int	nisointr;	/* number of periodic td intrs. */
+	int	nreqs;
+	Poll	poll;
+	
+	ulong	base;
+	int	irq;
+	ulong*	r;
+};
+
+extern int ehcidebug;
+
+void	ehcilinkage(Hci *hp);
+void	ehcimeminit(Ctlr *ctlr);
+void	ehcirun(Ctlr *ctlr, int on);
--- /dev/null
+++ b/sys/src/9/zynq/usbehcizynq.c
@@ -1,0 +1,103 @@
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"io.h"
+#include	"../port/error.h"
+#include	"../port/usb.h"
+#include	"usbehci.h"
+
+enum {
+	USBMODE = 0x1A8/4,
+	USBHOST = 3,
+	OTGSC = 0x1A4/4,
+	ULPI = 0x170/4,
+};
+
+static Ctlr ctlrs[3] = {
+	{
+		.base = USB0_BASE,
+		.irq = USB0IRQ,
+	},
+	{
+		.base = USB1_BASE,
+		.irq = USB1IRQ,
+	},
+};
+
+static void
+ehcireset(Ctlr *ctlr)
+{
+	int i;
+	Eopio *opio;
+
+	ilock(ctlr);
+	opio = ctlr->opio;
+	ehcirun(ctlr, 0);
+	opio->cmd |= Chcreset;
+	for(i = 0; i < 100; i++){
+		if((opio->cmd & Chcreset) == 0)
+			break;
+		delay(1);
+	}
+	if(i == 100)
+		print("ehci %#p controller reset timed out\n", ctlr->base);
+	opio->cmd |= Citc1;
+	switch(opio->cmd & Cflsmask){
+	case Cfls1024:
+		ctlr->nframes = 1024;
+		break;
+	case Cfls512:
+		ctlr->nframes = 512;
+		break;
+	case Cfls256:
+		ctlr->nframes = 256;
+		break;
+	default:
+		panic("ehci: unknown fls %ld", opio->cmd & Cflsmask);
+	}
+	dprint("ehci: %d frames\n", ctlr->nframes);
+	iunlock(ctlr);
+}
+
+static int
+reset(Hci *hp)
+{
+	static Lock resetlck;
+	Ctlr *ctlr;
+	
+	ilock(&resetlck);
+	for(ctlr = ctlrs; ctlr->base != 0; ctlr++)
+		if(!ctlr->active && (hp->port == 0 || hp->port == ctlr->base)){
+			ctlr->active = 1;
+			break;
+		}
+	iunlock(&resetlck);
+	if(ctlr->base == 0)
+		return -1;
+	hp->port = ctlr->base;
+	hp->irq = ctlr->irq;
+	hp->aux = ctlr;
+	
+	ctlr->r = vmap(ctlr->base, 0x1F0);
+	ctlr->opio = (Eopio *) ((uchar *) ctlr->r + 0x140);
+	ctlr->capio = (void *) ctlr->base;
+	hp->nports = 1;
+	ctlr->r[USBMODE] |= USBHOST;
+	
+	ehcireset(ctlr);
+	ehcimeminit(ctlr);
+	ehcilinkage(hp);
+	ctlr->r[ULPI] = 1<<30 | 1<<29 | 0x0B << 16 | 3<<5;
+	if(hp->interrupt != nil)
+		intrenable(hp->irq, hp->interrupt, hp, LEVEL, hp->type);
+	return 0;
+}
+
+void
+usbehcilink(void)
+{
+	ehcidebug = 2;
+	addhcitype("ehci", reset);
+}
--- /dev/null
+++ b/sys/src/9/zynq/zynq
@@ -1,0 +1,56 @@
+dev
+	root
+	cons
+	arch
+	uart
+	mnt
+	srv
+	shr
+	proc
+	env
+	pipe
+	dup
+	ether netif
+	ip		arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium inferno
+	ssl
+	tls
+	cap
+	kprof
+	fs
+	qspi
+	draw screen
+	mouse
+	usb
+	
+link
+	etherzynq
+	ethermedium
+	loopbackmedium
+	usbehci usbehcizynq
+
+misc
+	uartzynq
+
+ip
+	tcp
+	udp
+	rudp
+	ipifc
+	icmp
+	icmp6
+	gre
+	ipmux
+	esp
+
+port
+	int cpuserver = 0;
+
+boot boot
+	tcp
+	local
+
+bootdir
+	boot$CONF.out boot
+	/$objtype/bin/paqfs
+	/$objtype/bin/auth/factotum
+	bootfs.paq
--- /dev/null
+++ b/sys/src/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/sys/src/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/sys/src/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/sys/src/boot/zynq/fns.h
@@ -1,0 +1,13 @@
+void	putc(int);
+void	puts(char *);
+int	netboot(void);
+void	puthex(u32int);
+void	memset(void *, char, int);
+void	memcpy(void *, void *, int);
+void	print(char *, ...);
+u32int	u32get(void *);
+void	jump(void *);
+void	sleep(int);
+void	timeren(int);
+int	timertrig(void);
+void	flash(void);
--- /dev/null
+++ b/sys/src/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/sys/src/boot/zynq/main.c
@@ -1,0 +1,184 @@
+#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++;
+}
+
+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(netboot() > 0)
+		run();
+	print("hjboot: ending\n");
+}
--- /dev/null
+++ b/sys/src/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/sys/src/boot/zynq/mkfile
@@ -1,0 +1,34 @@
+objtype=arm
+</$objtype/mkfile
+BIN=/arm
+TARG=fsbl fsbl.img
+CLEANFILES=boothead.$cputype
+FSBLFILES=fsbl.$O ddr.$O main.$O net.$O div.$O qspi.$O
+
+all:V: $TARG
+
+clean:V:
+	rm -rf $TARG *.$O
+
+fsbl: $FSBLFILES
+	$LD -o $target -T0xfffc0000 -H6 -R4096 -l -s $prereq
+
+9fsbl: $FSBLFILES
+	$LD -o $target -T0xfffc0000 -l $prereq
+
+fsbl.img:D: fsbl boothead.$cputype
+	boothead.$cputype fsbl >fsbl.img
+
+boothead.$cputype:V: mkfile.boothead
+	@{objtype=$cputype mk -f $prereq all}
+
+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/sys/src/boot/zynq/mkfile.boothead
@@ -1,0 +1,9 @@
+</$objtype/mkfile
+
+all:V: boothead.$objtype
+
+boothead.$objtype: boothead.$O
+	$LD $LDFLAGS -o $target $prereq
+
+%.$O:	%.c
+	$CC $CFLAGS $stem.c
--- /dev/null
+++ b/sys/src/boot/zynq/net.c
@@ -1,0 +1,591 @@
+#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)
+			;
+	}
+	v = mdread(r, MDPHYCTRL);
+	if((v & 0x40) != 0){
+		puts("1000BASE-T");
+		while((mdread(r, MDGSTATUS) & RECVOK) != RECVOK)
+			;
+		r[NET_CFG] |= GIGE_EN;
+	}else if((v & 0x20) != 0){
+		puts("100BASE-TX");
+		r[NET_CFG] = NET_CFG & ~GIGE_EN | SPEED;
+	}else if((v & 0x10) != 0){
+		puts("10BASE-T");
+		r[NET_CFG] = NET_CFG & ~(GIGE_EN | SPEED);
+	}else
+		puts("???");
+	if((v & 0x08) != 0)
+		puts(" Full Duplex\n");
+	else{
+		puts(" Half Duplex\n");
+		r[NET_CFG] &= ~FDEN;
+	}
+	r[NET_CTRL] |= TXEN | RXEN;
+}
+
+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
+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/sys/src/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));
+	
+}