shithub: riscv

Download patch

ref: d6e0e9c402e215dc5659ad525e3e652501f76810
parent: debb786fea3d6ea8018c3d83cdedfdbff0703441
author: cinap_lenrek <[email protected]>
date: Sun Feb 11 13:08:03 EST 2018

kernel: move devether and wifi to port/

the only architecture dependence of devether was enabling interrupts,
which is now done at the end of the driver's reset() function now.

the wifi stack and dummy ethersink also go to port/.

do the IRQ2->IRQ9 hack for pc kernels in intrenabale(), so not
every caller of intrenable() has to be aware of it.

--- a/sys/src/9/kw/archkw.c
+++ b/sys/src/9/kw/archkw.c
@@ -11,7 +11,7 @@
 #include "io.h"
 
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 #include "../port/flashif.h"
 
 #include "arm.h"
@@ -393,17 +393,6 @@
 void
 archkwlink(void)
 {
-}
-
-int
-archether(unsigned ctlno, Ether *ether)
-{
-	if(ctlno >= 2)
-		return -1;
-	ether->type = "88e1116";
-	ether->port = ctlno;
-//	ether->mbps = 1000;
-	return 1;
 }
 
 /* LED/USB gpios */
--- a/sys/src/9/kw/devether.c
+++ /dev/null
@@ -1,502 +1,0 @@
-#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/netif.h"
-#include "etherif.h"
-
-extern int archether(unsigned ctlno, Ether *ether);
-
-static Ether *etherxx[MaxEther];
-
-Chan*
-etherattach(char* spec)
-{
-	int ctlrno;
-	char *p;
-	Chan *chan;
-
-	ctlrno = 0;
-	if(spec && *spec){
-		ctlrno = strtoul(spec, &p, 0);
-		if((ctlrno == 0 && p == spec) || *p != 0)
-			error(Ebadarg);
-		if(ctlrno < 0 || 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 loopback or bridged packets */
-			if(f->bridge && (tome || !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)
-						ether->soverflows++;
-				}
-				else
-					ether->soverflows++;
-			}
-			else
-				etherrtrace(f, pkt, len);
-		}
-	}
-
-	if(fx){
-		if(qpass(fx->in, bp) < 0)
-			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(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);
-	if(!ether->f[NETID(chan->qid.path)]->bridge)
-		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++;
-}
-
-static void
-etherreset(void)
-{
-	Ether *ether;
-	int i, n, ctlrno;
-	char name[KNAMELEN], buf[128];
-
-	for(ether = 0, ctlrno = 0; ctlrno < MaxEther; ctlrno++){
-		if(ether == 0)
-			ether = malloc(sizeof(Ether));
-		memset(ether, 0, sizeof(Ether));
-		ether->ctlrno = ctlrno;
-		ether->mbps = 10;
-		ether->minmtu = ETHERMINTU;
-		ether->maxmtu = ETHERMAXTU;
-
-		if(archether(ctlrno, ether) <= 0)
-			continue;
-
-		for(n = 0; cards[n].type; n++){
-			if(cistrcmp(cards[n].type, ether->type))
-				continue;
-			for(i = 0; i < ether->nopt; i++){
-				if(cistrncmp(ether->opt[i], "ea=", 3) == 0){
-					if(parseether(ether->ea, &ether->opt[i][3]) == -1)
-						memset(ether->ea, 0, Eaddrlen);
-				}else if(cistrcmp(ether->opt[i], "fullduplex") == 0 ||
-					cistrcmp(ether->opt[i], "10BASE-TFD") == 0)
-					ether->fullduplex = 1;
-				else if(cistrcmp(ether->opt[i], "100BASE-TXFD") == 0)
-					ether->mbps = 100;
-			}
-			if(cards[n].reset(ether))
-				break;
-			snprint(name, sizeof(name), "ether%d", ctlrno);
-
-			if(ether->interrupt != nil)
-				intrenable(Irqlo, ether->irq, ether->interrupt,
-					ether, name);
-
-			i = snprint(buf, sizeof buf,
-				"#l%d: %s: %dMbps port %#lux irq %d",
-				ctlrno, ether->type, ether->mbps, ether->port,
-				ether->irq);
-			if(ether->mem)
-				i += snprint(buf+i, sizeof buf - i,
-					" addr %#lux", PADDR(ether->mem));
-			if(ether->size)
-				i += snprint(buf+i, sizeof buf - i,
-					" size %#luX", ether->size);
-			i += snprint(buf+i, sizeof 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]);
-			snprint(buf+i, sizeof buf - i, "\n");
-			print("%s", buf);
-
-			if(ether->mbps >= 1000)
-				netifinit(ether, name, Ntypes, 4*1024*1024);
-			else if(ether->mbps >= 100)
-				netifinit(ether, name, Ntypes, 1024*1024);
-			else
-				netifinit(ether, name, Ntypes, 65*1024);
-			if(ether->oq == 0)
-				ether->oq = qopen(ether->limit, Qmsg, 0, 0);
-			if(ether->oq == 0)
-				panic("etherreset %s", name);
-			ether->alen = Eaddrlen;
-			memmove(ether->addr, ether->ea, Eaddrlen);
-			memset(ether->bcast, 0xFF, Eaddrlen);
-
-			etherxx[ctlrno] = ether;
-			ether = 0;
-			break;
-		}
-	}
-	if(ether)
-		free(ether);
-}
-
-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;
-}
-
-void
-dumpoq(Queue *oq)
-{
-	if (oq == nil)
-		print("no outq! ");
-	else if (qisclosed(oq))
-		print("outq closed ");
-	else if (qfull(oq))
-		print("outq full ");
-	else
-		print("outq %d ", qlen(oq));
-}
-
-void
-dumpnetif(Netif *netif)
-{
-	print("netif %s ", netif->name);
-	print("limit %d mbps %d link %d ",
-		netif->limit, netif->mbps, netif->link);
-	print("inpkts %lld outpkts %lld errs %d\n",
-		netif->inpackets, netif->outpackets,
-		netif->crcs + netif->oerrs + netif->frames + netif->overflows +
-		netif->buffs + netif->soverflows);
-}
-
-Dev etherdevtab = {
-	'l',
-	"ether",
-
-	etherreset,
-	devinit,
-	ethershutdown,
-	etherattach,
-	etherwalk,
-	etherstat,
-	etheropen,
-	ethercreate,
-	etherclose,
-	etherread,
-	etherbread,
-	etherwrite,
-	etherbwrite,
-	devremove,
-	etherwstat,
-};
--- a/sys/src/9/kw/ether1116.c
+++ b/sys/src/9/kw/ether1116.c
@@ -16,8 +16,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ethermii.h"
 #include "../ip/ip.h"
 
@@ -144,6 +144,9 @@
 	Mii	*mii;
 	int	port;
 
+	int	linkchg;		/* link status changed? */
+	uvlong	starttime;		/* last activity time */
+
 	/* stats */
 	ulong	intrs;
 	ulong	newintrs;
@@ -579,14 +582,16 @@
 static void
 etheractive(Ether *ether)
 {
-	ether->starttime = TK2MS(MACHP(0)->ticks)/1000;
+	Ctlr *ctlr = ether->ctlr;
+	ctlr->starttime = TK2MS(MACHP(0)->ticks)/1000;
 }
 
 static void
 ethercheck(Ether *ether)
 {
-	if (ether->starttime != 0 &&
-	    TK2MS(MACHP(0)->ticks)/1000 - ether->starttime > Etherstuck) {
+	Ctlr *ctlr = ether->ctlr;
+	if (ctlr->starttime != 0 &&
+	    TK2MS(MACHP(0)->ticks)/1000 - ctlr->starttime > Etherstuck) {
 		etheractive(ether);
 		if (ether->ctlrno == 0)	/* only complain about main ether */
 			iprint("#l%d: ethernet stuck\n", ether->ctlrno);
@@ -833,7 +838,7 @@
 		 */
 		if(irqe & IEphystschg) {
 			ether->link = (reg->ps0 & PS0linkup) != 0;
-			ether->linkchg = 1;
+			ctlr->linkchg = 1;
 		}
 		if(irqe & IEtxerrq(Qno))
 			ether->oerrs++;
@@ -858,10 +863,10 @@
 		irq  &= ~(Irxerr | Irxerrq(Qno));
 	}
 
-	if(ether->linkchg && (reg->ps1 & PS1an_done)) {
+	if(ctlr->linkchg && (reg->ps1 & PS1an_done)) {
 		handled++;
 		ether->link = (reg->ps0 & PS0linkup) != 0;
-		ether->linkchg = 0;
+		ctlr->linkchg = 0;
 	}
 	ctlr->newintrs++;
 
@@ -1652,7 +1657,6 @@
 	p = seprint(p, e, "transmitted broadcast frames: %lud\n", ctlr->txbcastpkt);
 	p = seprint(p, e, "transmitted multicast frames: %lud\n", ctlr->txmcastpkt);
 	p = seprint(p, e, "transmit frames dropped by collision: %lud\n", ctlr->txcollpktdrop);
-	p = seprint(p, e, "misaligned buffers: %lud\n", ether->pktsmisaligned);
 
 	p = seprint(p, e, "bad mac control frames: %lud\n", ctlr->badmacctlpkts);
 	p = seprint(p, e, "transmitted flow control messages: %lud\n", ctlr->txflctl);
@@ -1689,7 +1693,7 @@
 		ether->irq = IRQ0gbe1sum;
 		break;
 	default:
-		panic("ether1116: bad ether ctlr #%d", ether->ctlrno);
+		return -1;
 	}
 	ctlr->reg = (Gbereg*)soc.ether[ether->ctlrno];
 
@@ -1728,7 +1732,6 @@
 
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->interrupt = interrupt;
 	ether->ifstat = ifstat;
 	ether->shutdown = shutdown;
 	ether->ctl = ctl;
@@ -1736,6 +1739,9 @@
 	ether->arg = ether;
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
+
+	intrenable(Irqlo, ether->irq, interrupt, ether, ether->name);
+
 	return 0;
 }
 
--- a/sys/src/9/kw/etherif.h
+++ /dev/null
@@ -1,55 +1,0 @@
-enum
-{
-	MaxEther	= 2,
-	Ntypes		= 8,
-};
-
-typedef struct Ether Ether;
-struct Ether {
-	RWlock;				/* TO DO */
-	ISAConf;			/* hardware info */
-	int	ctlrno;
-	int	minmtu;
-	int	maxmtu;
-	uchar	ea[Eaddrlen];
-	void	*address;
-	int	tbusy;
-
-	void	(*attach)(Ether*);	/* filled in by reset routine */
-	void	(*closed)(Ether*);
-	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;
-	int	pcmslot;		/* PCMCIA */
-	int	fullduplex;		/* non-zero if full duplex */
-	int	linkchg;		/* link status changed? */
-	uvlong	starttime;		/* last activity time */
-
-	Queue*	oq;
-
-	/* statistics */
-	ulong	interrupts;
-	ulong	dmarxintr;
-	ulong	dmatxintr;
-	ulong	promisc;
-	ulong	pktsdropped;
-	ulong	pktsmisaligned;
-	ulong	resets;			/* after initialisation */
-	ulong	bcasts;			/* broadcast pkts rcv'd */
-	ulong	mcasts;			/* multicast pkts rcv'd */
-
-	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)
--- a/sys/src/9/kw/ethermii.c
+++ b/sys/src/9/kw/ethermii.c
@@ -6,8 +6,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ethermii.h"
 
 int
--- a/sys/src/9/kw/mem.h
+++ b/sys/src/9/kw/mem.h
@@ -5,9 +5,6 @@
 #define MiB		1048576u		/* Mebi 0x0000000000100000 */
 #define GiB		1073741824u		/* Gibi 000000000040000000 */
 
-#define HOWMANY(x, y)	(((x)+((y)-1))/(y))
-#define ROUNDUP(x, y)	(HOWMANY((x), (y))*(y))	/* ceiling */
-#define ROUNDDN(x, y)	(((x)/(y))*(y))		/* floor */
 #define MIN(a, b)	((a) < (b)? (a): (b))
 #define MAX(a, b)	((a) > (b)? (a): (b))
 
@@ -36,7 +33,7 @@
  */
 #define	BY2PG		(4*KiB)			/* bytes per page */
 #define	PGSHIFT		12			/* log(BY2PG) */
-#define	PGROUND(s)	ROUNDUP(s, BY2PG)
+#define	PGROUND(s)	ROUND(s, BY2PG)
 #define	ROUND(s, sz)	(((s)+(sz-1))&~(sz-1))
 
 #define	MAXMACH		1			/* max # cpus system can run */
--- a/sys/src/9/kw/mkfile
+++ b/sys/src/9/kw/mkfile
@@ -124,7 +124,7 @@
 	trap.$O: /$objtype/include/ureg.h
 
 archkw.$O devether.$O ether1116.$O ethermii.$O: \
-	etherif.h ethermii.h ../port/netif.h
+	ethermii.h ../port/etherif.h ../port/netif.h
 archkw.$O devflash.$O flashkw.$O: ../port/flashif.h
 fpi.$O fpiarm.$O fpimem.$O: fpi.h
 l.$O lexception.$O lproc.$O mmu.$O: arm.s arm.h mem.h
--- a/sys/src/9/kw/mmu.c
+++ b/sys/src/9/kw/mmu.c
@@ -465,7 +465,7 @@
 	o = pa & (BY2PG-1);
 	pa -= o;
 	size += o;
-	size = ROUNDUP(size, BY2PG);
+	size = PGROUND(size);
 
 	va = kseg0|pa;
 	pae = mmukmap(va, pa, size);
--- a/sys/src/9/mtx/devether.c
+++ /dev/null
@@ -1,447 +1,0 @@
-#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"
-#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);
-	chan->dev = ctlrno;
-	if(etherxx[ctlrno]->attach)
-		etherxx[ctlrno]->attach(etherxx[ctlrno]);
-	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)) && 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 loopback or bridged packets */
-			if(f->bridge && (tome || !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;
-					qpass(f->in, xbp);
-				}
-				else
-					ether->soverflows++;
-			}
-			else
-				etherrtrace(f, pkt, len);
-		}
-	}
-
-	if(fx){
-		if(qpass(fx->in, bp) < 0)
-			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;
-
-	ether = etherxx[chan->dev];
-	if(NETTYPE(chan->qid.path) != Ndataqid) {
-		nn = netifwrite(ether, chan, buf, n);
-		if(nn >= 0)
-			return nn;
-
-		if(n == sizeof("nonblocking")-1 && strncmp((char*)buf, "nonblocking", n) == 0){
-			qnoblock(ether->oq, 1);
-			return n;
-		}
-
-		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);
-	if(!ether->f[NETID(chan->qid.path)]->bridge)
-		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++;
-}
-
-static void
-etherreset(void)
-{
-	Ether *ether;
-	int i, n, ctlrno;
-	char name[32], buf[128];
-
-	for(ether = 0, ctlrno = 0; ctlrno < MaxEther; ctlrno++){
-		if(ether == 0)
-			ether = malloc(sizeof(Ether));
-		memset(ether, 0, sizeof(Ether));
-		ether->ctlrno = ctlrno;
-		ether->tbdf = BUSUNKNOWN;
-		ether->mbps = 10;
-		ether->minmtu = ETHERMINTU;
-		ether->maxmtu = ETHERMAXTU;
-		if(isaconfig("ether", ctlrno, ether) == 0)
-			continue;
-		for(n = 0; cards[n].type; n++){
-			if(cistrcmp(cards[n].type, ether->type))
-				continue;
-			for(i = 0; i < ether->nopt; i++){
-				if(strncmp(ether->opt[i], "ea=", 3))
-					continue;
-				if(parseether(ether->ea, &ether->opt[i][3]) == -1)
-					memset(ether->ea, 0, Eaddrlen);
-			}	
-			if(cards[n].reset(ether))
-				break;
-
-			/*
-			 * IRQ2 doesn't really exist, it's used to gang the interrupt
-			 * controllers together. A device set to IRQ2 will appear on
-			 * the second interrupt controller as IRQ9.
-			 */
-			if(ether->irq == 2 && BUSTYPE(ether->tbdf) != BusPCI)
-				ether->irq = 9;
-			snprint(name, sizeof(name), "ether%d", ctlrno);
-
-			/*
-			 * If ether->irq is <0, it is a hack to indicate no interrupt
-			 * used by ethersink.
-			 */
-			if(ether->irq >= 0)
-				intrenable(ether->irq, ether->interrupt, ether, ether->tbdf, name);
-
-			i = sprint(buf, "#l%d: %s: %dMbps port 0x%luX irq %d",
-				ctlrno, ether->type, ether->mbps, ether->port, ether->irq);
-			if(ether->mem)
-				i += sprint(buf+i, " addr 0x%luX", PADDR(ether->mem));
-			if(ether->size)
-				i += sprint(buf+i, " size 0x%luX", ether->size);
-			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);
-
-			if(ether->mbps >= 100){
-				netifinit(ether, name, Ntypes, 256*1024);
-				if(ether->oq == 0)
-					ether->oq = qopen(256*1024, Qmsg, 0, 0);
-			}
-			else{
-				netifinit(ether, name, Ntypes, 65*1024);
-				if(ether->oq == 0)
-					ether->oq = qopen(65*1024, Qmsg, 0, 0);
-			}
-			if(ether->oq == 0)
-				panic("etherreset %s", name);
-			ether->alen = Eaddrlen;
-			memmove(ether->addr, ether->ea, Eaddrlen);
-			memset(ether->bcast, 0xFF, Eaddrlen);
-
-			etherxx[ctlrno] = ether;
-			ether = 0;
-			break;
-		}
-	}
-	if(ether)
-		free(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,
-	devshutdown,
-	etherattach,
-	etherwalk,
-	etherstat,
-	etheropen,
-	ethercreate,
-	etherclose,
-	etherread,
-	etherbread,
-	etherwrite,
-	etherbwrite,
-	devremove,
-	etherwstat,
-};
--- a/sys/src/9/mtx/ether2114x.c
+++ b/sys/src/9/mtx/ether2114x.c
@@ -16,9 +16,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
-
 #define DEBUG		(0)
 #define debug		if(DEBUG)print
 
@@ -1654,11 +1653,12 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->interrupt = interrupt;
 	ether->ifstat = ifstat;
 
 	ether->arg = ether;
 	ether->promiscuous = promiscuous;
+
+	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
 
 	return 0;
 }
--- a/sys/src/9/mtx/etherif.h
+++ /dev/null
@@ -1,35 +1,0 @@
-enum {
-	MaxEther	= 24,
-	Ntypes		= 8,
-};
-
-typedef struct Ether Ether;
-struct Ether {
-	ISAConf;			/* hardware info */
-
-	int	ctlrno;
-	int	tbdf;			/* type+busno+devno+funcno */
-	int	minmtu;
-	int 	maxmtu;
-	uchar	ea[Eaddrlen];
-
-	void	(*attach)(Ether*);	/* filled in by reset routine */
-	void	(*transmit)(Ether*);
-	void	(*interrupt)(Ureg*, void*);
-	long	(*ifstat)(Ether*, void*, long, ulong);
-	long 	(*ctl)(Ether*, void*, long); /* custom ctl messages */
-	void	*ctlr;
-
-	Queue*	oq;
-
-	Netif;
-};
-
-extern Block* etheriq(Ether*, Block*, int);
-extern void addethercard(char*, int(*)(Ether*));
-extern ulong ethercrc(uchar*, int);
-
-#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))
--- a/sys/src/9/mtx/mkfile
+++ b/sys/src/9/mtx/mkfile
@@ -82,7 +82,7 @@
 main.$O:	/$objtype/include/ureg.h errstr.h init.h
 trap.$O:	/$objtype/include/ureg.h
 
-$ETHER: 	etherif.h ../port/netif.h
+$ETHER: 	../port/etherif.h ../port/netif.h
 
 init.h:	initcode /sys/src/libc/9syscall/sys.h
 	$AS initcode
--- a/sys/src/9/omap/archomap.c
+++ b/sys/src/9/omap/archomap.c
@@ -15,7 +15,7 @@
 #include "arm.h"
 
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 #include "../port/flashif.h"
 #include "../port/usb.h"
 #include "../port/portusbehci.h"
--- a/sys/src/9/omap/dat.h
+++ b/sys/src/9/omap/dat.h
@@ -240,6 +240,8 @@
 	char	*opt[NISAOPT];
 };
 
+#define BUSUNKNOWN -1
+
 #define	MACHP(n)	(machaddr[n])
 
 /*
--- a/sys/src/9/omap/devether.c
+++ /dev/null
@@ -1,504 +1,0 @@
-#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/netif.h"
-#include "etherif.h"
-
-extern int archether(unsigned ctlno, Ether *ether);
-
-static Ether *etherxx[MaxEther];
-
-Chan*
-etherattach(char* spec)
-{
-	int ctlrno;
-	char *p;
-	Chan *chan;
-
-	ctlrno = 0;
-	if(spec && *spec){
-		ctlrno = strtoul(spec, &p, 0);
-		if((ctlrno == 0 && p == spec) || *p != 0)
-			error(Ebadarg);
-		if(ctlrno < 0 || 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) != nil && (f->type == type || f->type < 0) &&
-		    (tome || multi || f->prom)){
-			/* Don't want to hear loopback or bridged packets */
-			if(f->bridge && (tome || !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)
-						ether->soverflows++;
-				}
-				else
-					ether->soverflows++;
-			}
-			else
-				etherrtrace(f, pkt, len);
-		}
-	}
-
-	if(fx){
-		if(qpass(fx->in, bp) < 0)
-			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);
-	if(!ether->f[NETID(chan->qid.path)]->bridge)
-		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++;
-}
-
-static void
-etherreset(void)
-{
-	Ether *ether;
-	int i, n, ctlrno;
-	char name[KNAMELEN], buf[128];
-
-	for(ether = 0, ctlrno = 0; ctlrno < MaxEther; ctlrno++){
-		if(ether == 0)
-			ether = malloc(sizeof(Ether));
-		memset(ether, 0, sizeof(Ether));
-		ether->ctlrno = ctlrno;
-		ether->mbps = 10;
-		ether->minmtu = ETHERMINTU;
-		ether->maxmtu = ETHERMAXTU;
-
-		if(archether(ctlrno, ether) <= 0)
-			continue;
-
-		if(isaconfig("ether", ctlrno, ether) == 0){
-//			free(ether);
-//			return nil;
-			continue;
-		}
-		for(n = 0; cards[n].type; n++){
-			if(cistrcmp(cards[n].type, ether->type))
-				continue;
-			for(i = 0; i < ether->nopt; i++)
-				if(cistrncmp(ether->opt[i], "ea=", 3) == 0){
-					if(parseether(ether->ea,
-					    &ether->opt[i][3]) == -1)
-						memset(ether->ea, 0, Eaddrlen);
-				} else if(cistrcmp(ether->opt[i],
-				    "100BASE-TXFD") == 0)
-					ether->mbps = 100;
-			if(cards[n].reset(ether))
-				break;
-			snprint(name, sizeof(name), "ether%d", ctlrno);
-
-			if(ether->interrupt != nil && ether->irq >= 0)
-				intrenable(ether->irq, ether->interrupt,
-					ether, 0, name);
-
-			i = snprint(buf, sizeof buf,
-				"#l%d: %s: %dMbps port %#lux irq %d",
-				ctlrno, ether->type, ether->mbps, ether->port,
-				ether->irq);
-			if(ether->mem)
-				i += snprint(buf+i, sizeof buf - i,
-					" addr %#lux", PADDR(ether->mem));
-			if(ether->size)
-				i += snprint(buf+i, sizeof buf - i,
-					" size %#luX", ether->size);
-			i += snprint(buf+i, sizeof 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]);
-			snprint(buf+i, sizeof buf - i, "\n");
-			iprint("%s", buf);  /* it may be too early for print */
-
-			if(ether->mbps >= 1000)
-				netifinit(ether, name, Ntypes, 4*1024*1024);
-			else if(ether->mbps >= 100)
-				netifinit(ether, name, Ntypes, 1024*1024);
-			else
-				netifinit(ether, name, Ntypes, 65*1024);
-			if(ether->oq == 0)
-				ether->oq = qopen(ether->limit, Qmsg, 0, 0);
-			if(ether->oq == 0)
-				panic("etherreset %s", name);
-			ether->alen = Eaddrlen;
-			memmove(ether->addr, ether->ea, Eaddrlen);
-			memset(ether->bcast, 0xFF, Eaddrlen);
-
-			etherxx[ctlrno] = ether;
-			ether = 0;
-			break;
-		}
-	}
-	if(ether)
-		free(ether);
-}
-
-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;
-}
-
-void
-dumpoq(Queue *oq)
-{
-	if (oq == nil)
-		print("no outq! ");
-	else if (qisclosed(oq))
-		print("outq closed ");
-	else if (qfull(oq))
-		print("outq full ");
-	else
-		print("outq %d ", qlen(oq));
-}
-
-void
-dumpnetif(Netif *netif)
-{
-	print("netif %s ", netif->name);
-	print("limit %d mbps %d link %d ",
-		netif->limit, netif->mbps, netif->link);
-	print("inpkts %lld outpkts %lld errs %d\n",
-		netif->inpackets, netif->outpackets,
-		netif->crcs + netif->oerrs + netif->frames + netif->overflows +
-		netif->buffs + netif->soverflows);
-}
-
-Dev etherdevtab = {
-	'l',
-	"ether",
-
-	etherreset,
-	devinit,
-	ethershutdown,
-	etherattach,
-	etherwalk,
-	etherstat,
-	etheropen,
-	ethercreate,
-	etherclose,
-	etherread,
-	etherbread,
-	etherwrite,
-	etherbwrite,
-	devremove,
-	etherwstat,
-};
--- a/sys/src/9/omap/dma.c
+++ b/sys/src/9/omap/dma.c
@@ -235,7 +235,7 @@
 	*done = 0;
 	iunlock(&alloclck);
 
-	ruplen = ROUNDUP(len, sizeof(ulong));
+	ruplen = ROUND(len, sizeof(ulong));
 	assert(to != from);
 
 	cp = regs->chan + chan;
--- a/sys/src/9/omap/ether9221.c
+++ b/sys/src/9/omap/ether9221.c
@@ -18,9 +18,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
-
 /* currently using kprocs is a lot slower than not (87 s. to boot vs 60) */
 #undef USE_KPROCS
 
@@ -943,7 +942,6 @@
 	 */
 	edev->attach = smcattach;
 	edev->transmit = smctransmitcall;
-	edev->interrupt = smcinterrupt;
 	edev->ifstat = smcifstat;
 /*	edev->ctl = smcctl;			/* no ctl msgs supported */
 
@@ -951,6 +949,9 @@
 	edev->promiscuous = smcpromiscuous;
 	edev->multicast = smcmulticast;
 	edev->shutdown = smcshutdown;
+
+	intrenable(edev->irq, smcinterrupt, edev, 0, edev->name);
+
 	return 0;
 }
 
--- a/sys/src/9/omap/etherif.h
+++ /dev/null
@@ -1,41 +1,0 @@
-enum
-{
-	MaxEther	= 4,
-	Ntypes		= 8,
-};
-
-typedef struct Ether Ether;
-struct Ether {
-	RWlock;
-	ISAConf;			/* hardware info */
-
-	int	ctlrno;
-	int	minmtu;
-	int 	maxmtu;
-
-	Netif;
-
-	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;
-	uchar	ea[Eaddrlen];
-	void*	address;
-	int	irq;
-
-	Queue*	oq;
-};
-
-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)
--- a/sys/src/9/omap/mem.h
+++ b/sys/src/9/omap/mem.h
@@ -5,9 +5,6 @@
 #define MiB		1048576u		/* Mebi 0x0000000000100000 */
 #define GiB		1073741824u		/* Gibi 000000000040000000 */
 
-#define HOWMANY(x, y)	(((x)+((y)-1))/(y))
-#define ROUNDUP(x, y)	(HOWMANY((x), (y))*(y))	/* ceiling */
-#define ROUNDDN(x, y)	(((x)/(y))*(y))		/* floor */
 #define MIN(a, b)	((a) < (b)? (a): (b))
 #define MAX(a, b)	((a) > (b)? (a): (b))
 
@@ -36,7 +33,7 @@
  */
 #define	BY2PG		(4*KiB)			/* bytes per page */
 #define	PGSHIFT		12			/* log(BY2PG) */
-#define	PGROUND(s)	ROUNDUP(s, BY2PG)
+#define	PGROUND(s)	ROUND(s, BY2PG)
 #define	ROUND(s, sz)	(((s)+(sz-1))&~(sz-1))
 
 #define	MAXMACH		1			/* max # cpus system can run */
@@ -71,7 +68,7 @@
 
 #define	UZERO		0			/* user segment */
 #define	UTZERO		(UZERO+BY2PG)		/* user text start */
-#define UTROUND(t)	ROUNDUP((t), BY2PG)
+
 /* moved USTKTOP down to 512MB to keep MMIO space out of user space. */
 #define	USTKTOP		0x20000000		/* user segment end +1 */
 #define	USTKSIZE	(8*1024*1024)		/* user stack size */
--- a/sys/src/9/omap/mkfile
+++ b/sys/src/9/omap/mkfile
@@ -101,7 +101,7 @@
 arch.$O clock.$O fpiarm.$O main.$O mmu.$O screen.$O sdscsi.$O syscall.$O \
 	trap.$O: /$objtype/include/ureg.h
 
-archomap.$O devether.$0 ether9221.$O: etherif.h ../port/netif.h
+archomap.$O devether.$0 ether9221.$O: ../port/etherif.h ../port/netif.h
 archomap.$O devflash.$O flashbeagle.$O flashigep.$O: ../port/flashif.h
 ecc.$O flashbeagle.$O flashigep.$O: ../port/nandecc.h io.h
 fpi.$O fpiarm.$O fpimem.$O: fpi.h
--- a/sys/src/9/omap/mmu.c
+++ b/sys/src/9/omap/mmu.c
@@ -439,7 +439,7 @@
 	o = pa & (BY2PG-1);
 	pa -= o;
 	size += o;
-	size = ROUNDUP(size, BY2PG);
+	size = PGROUND(size);
 
 	va = kseg0|pa;
 	pae = mmukmap(va, pa, size);
--- a/sys/src/9/omap/screen.c
+++ b/sys/src/9/omap/screen.c
@@ -257,7 +257,7 @@
 			(sp->vsw-1);
 
 	dispc->pol_req = Ipc | Ihs | Ivs | Acb;
-	dispc->divisor = 1 << 16 | HOWMANY(432000, sp->pixelclock);
+	dispc->divisor = 1 << 16 | ((432000+sp->pixelclock-1)/sp->pixelclock);
 
 	dispc->lcdsize = (sp->ht - 1) << 16 | (sp->wid - 1);
 	coherence();
--- a/sys/src/9/pc/devether.c
+++ /dev/null
@@ -1,672 +1,0 @@
-#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 loopback or bridged packets */
-			if(f->bridge && (tome || !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;
-					xbp->flag = bp->flag;
-					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);
-	if(!ether->f[NETID(chan->qid.path)]->bridge)
-		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++;
-}
-
-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->tbdf = BUSUNKNOWN;
-	ether->mbps = 10;
-	ether->minmtu = ETHERMINTU;
-	ether->maxmtu = ETHERMAXTU;
-
-	if(cardno < 0){
-		if(isaconfig("ether", ctlrno, ether) == 0){
-			free(ether);
-			return nil;
-		}
-		for(cardno = 0; cards[cardno].type; cardno++){
-			if(cistrcmp(cards[cardno].type, ether->type))
-				continue;
-			for(i = 0; i < ether->nopt; i++){
-				if(strncmp(ether->opt[i], "ea=", 3))
-					continue;
-				if(parseether(ether->ea, &ether->opt[i][3]))
-					memset(ether->ea, 0, Eaddrlen);
-			}
-			break;
-		}
-	}
-
-	if(cardno >= MaxEther || cards[cardno].type == nil){
-		free(ether);
-		return nil;
-	}
-	if(cards[cardno].reset(ether) < 0){
-		free(ether);
-		return nil;
-	}
-
-	/*
-	 * IRQ2 doesn't really exist, it's used to gang the interrupt
-	 * controllers together. A device set to IRQ2 will appear on
-	 * the second interrupt controller as IRQ9.
-	 */
-	if(ether->irq == 2)
-		ether->irq = 9;
-	snprint(name, sizeof(name), "ether%d", ctlrno);
-
-	/*
-	 * If ether->irq is <0, it is a hack to indicate no interrupt
-	 * used by ethersink.
-	 */
-	if(ether->irq >= 0)
-		intrenable(ether->irq, ether->interrupt, ether, ether->tbdf, name);
-
-	i = sprint(buf, "#l%d: %s: %dMbps port 0x%luX irq %d",
-		ctlrno, cards[cardno].type, ether->mbps, ether->port, ether->irq);
-	if(ether->mem)
-		i += sprint(buf+i, " addr 0x%luX", ether->mem);
-	if(ether->size)
-		i += sprint(buf+i, " size 0x%luX", ether->size);
-	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;
-	}
-
-	if(getconf("*noetherprobe"))
-		return;
-
-	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,
-};
-
-enum { PktHdr = 42 };
-typedef struct Netconsole Netconsole;
-struct Netconsole {
-	char buf[512];
-	int n;
-	Lock;
-	Ether *ether;
-};
-static Netconsole *netcons;
-
-extern ushort ipcsum(uchar *);
-
-void
-netconsputc(Uart *, int c)
-{
-	char *p;
-	u16int cs;
-
-	ilock(netcons);
-	netcons->buf[netcons->n++] = c;
-	if(c != '\n' && netcons->n < sizeof(netcons->buf)){
-		iunlock(netcons);
-		return;
-	}
-	p = netcons->buf;
-	p[16] = netcons->n - 14 >> 8;
-	p[17] = netcons->n - 14;
-	p[24] = 0;
-	p[25] = 0;
-	cs = ipcsum((uchar*) p + 14);
-	p[24] = cs >> 8;
-	p[25] = cs;
-	p[38] = netcons->n - 34 >> 8;
-	p[39] = netcons->n - 34;
-	memmove(p+Eaddrlen, netcons->ether->ea, Eaddrlen);
-	qiwrite(netcons->ether->oq, p, netcons->n);
-	netcons->n = PktHdr;
-	iunlock(netcons);
-	if(netcons->ether->transmit != nil)
-		netcons->ether->transmit(netcons->ether);
-}
-
-PhysUart netconsphys = {
-	.putc = netconsputc,
-};
-Uart netconsuart = { .phys = &netconsphys };
-
-void
-netconsole(void)
-{
-	char *p;
-	char *r;
-	int i;
-	int srcport, devno, dstport;
-	u8int srcip[4], dstip[4];
-	u64int dstmac;
-	Netconsole *nc;
-
-	if((p = getconf("console")) == nil || strncmp(p, "net ", 4) != 0)
-		return;
-	p += 4;
-	for(i = 0; i < 4; i++){
-		srcip[i] = strtol(p, &r, 0);
-		p = r + 1;
-		if(i == 3) break;
-		if(*r != '.') goto err;
-	}
-	if(*r == '!'){
-		srcport = strtol(p, &r, 0);
-		p = r + 1;
-	}else
-		srcport = 6665;
-	if(*r == '/'){
-		devno = strtol(p, &r, 0);
-		p = r + 1;
-	}else
-		devno = 0;
-	if(*r != ',') goto err;
-	for(i = 0; i < 4; i++){
-		dstip[i] = strtol(p, &r, 0);
-		p = r + 1;
-		if(i == 3) break;
-		if(*r != '.') goto err;
-	}
-	if(*r == '!'){
-		dstport = strtol(p, &r, 0);
-		p = r + 1;
-	}else
-		dstport = 6666;
-	if(*r == '/'){
-		dstmac = strtoull(p, &r, 16);
-		if(r - p != 12) goto err;
-	}else
-		dstmac = ((uvlong)-1) >> 16;
-	if(*r != 0) goto err;
-	
-	if(devno >= MaxEther || etherxx[devno] == nil){
-		print("netconsole: no device #l%d\n", devno);
-		return;
-	}
-	
-	nc = malloc(sizeof(Netconsole));
-	if(nc == nil){
-		print("netconsole: out of memory");
-		return;
-	}
-	memset(nc, 0, sizeof(Netconsole));
-	nc->ether = etherxx[devno];
-	
-	uchar header[PktHdr] = {
-		/* 0 */ dstmac >> 40, dstmac >> 32, dstmac >> 24, dstmac >> 16, dstmac >> 8, dstmac >> 0,
-		/* 6 */ 0, 0, 0, 0, 0, 0,
-		/* 12 */ 0x08, 0x00,
-		/* 14 */ 0x45, 0x00,
-		/* 16 */ 0x00, 0x00, /* total length */
-		/* 18 */ 0x00, 0x00, 0x00, 0x00,
-		/* 22 */ 64, /* ttl */
-		/* 23 */ 0x11, /* protocol */
-		/* 24 */ 0x00, 0x00, /* checksum */
-		/* 26 */ srcip[0], srcip[1], srcip[2], srcip[3],
-		/* 30 */ dstip[0], dstip[1], dstip[2], dstip[3],
-		/* 34 */ srcport >> 8, srcport, dstport >> 8, dstport,
-		/* 38 */ 0x00, 0x00, /* length */
-		/* 40 */ 0x00, 0x00 /* checksum */
-	};
-	
-	memmove(nc->buf, header, PktHdr);
-	nc->n = PktHdr;
-	
-	netcons = nc;
-	consuart = &netconsuart;
-	return;
-
-err:
-	print("netconsole: invalid string %#q\n", getconf("console"));
-	print("netconsole: usage: srcip[!srcport][/srcdev],dstip[!dstport][/dstmac]\n");
-}
--- a/sys/src/9/pc/ether2000.c
+++ b/sys/src/9/pc/ether2000.c
@@ -6,8 +6,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ether8390.h"
 
 /*
--- a/sys/src/9/pc/ether2114x.c
+++ b/sys/src/9/pc/ether2114x.c
@@ -16,9 +16,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
-
 #define DEBUG		(0)
 #define debug		if(DEBUG)print
 
@@ -1818,7 +1817,6 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->interrupt = interrupt;
 	ether->ifstat = ifstat;
 
 	ether->arg = ether;
@@ -1825,6 +1823,8 @@
 	ether->shutdown = shutdown;
 	ether->multicast = multicast;
 	ether->promiscuous = promiscuous;
+
+	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/ether589.c
+++ b/sys/src/9/pc/ether589.c
@@ -11,8 +11,7 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
-
-#include "etherif.h"
+#include "../port/etherif.h"
 
 enum {						/* all windows */
 	CommandR		= 0x000E,
--- a/sys/src/9/pc/ether79c970.c
+++ b/sys/src/9/pc/ether79c970.c
@@ -12,9 +12,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
-
 enum {
 	Lognrdre	= 6,
 	Nrdre		= (1<<Lognrdre),/* receive descriptor ring entries */
@@ -672,7 +671,6 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->interrupt = interrupt;
 	ether->ifstat = ifstat;
 
 	ether->arg = ether;
@@ -679,6 +677,8 @@
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
 //	ether->shutdown = shutdown;
+
+	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/ether8003.c
+++ b/sys/src/9/pc/ether8003.c
@@ -6,8 +6,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ether8390.h"
 
 /*
--- a/sys/src/9/pc/ether8139.c
+++ b/sys/src/9/pc/ether8139.c
@@ -11,9 +11,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
-
 enum {					/* registers */
 	Idr0		= 0x0000,	/* MAC address */
 	Mar0		= 0x0008,	/* Multicast address */
@@ -817,7 +816,6 @@
 
 	edev->attach = rtl8139attach;
 	edev->transmit = rtl8139transmit;
-	edev->interrupt = rtl8139interrupt;
 	edev->ifstat = rtl8139ifstat;
 
 	edev->arg = edev;
@@ -824,6 +822,8 @@
 	edev->promiscuous = rtl8139promiscuous;
 	edev->multicast = rtl8139multicast;
 //	edev->shutdown = rtl8139shutdown;
+
+	intrenable(edev->irq, rtl8139interrupt, edev, edev->tbdf, edev->name);
 
 	/*
 	 * This should be much more dynamic but will do for now.
--- a/sys/src/9/pc/ether8169.c
+++ b/sys/src/9/pc/ether8169.c
@@ -15,8 +15,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ethermii.h"
 
 enum {					/* registers */
@@ -1207,7 +1207,6 @@
 
 	edev->attach = rtl8169attach;
 	edev->transmit = rtl8169transmit;
-	edev->interrupt = rtl8169interrupt;
 	edev->ifstat = rtl8169ifstat;
 
 	edev->arg = edev;
@@ -1215,6 +1214,8 @@
 	edev->multicast = rtl8169multicast;
 
 	rtl8169link(edev);
+
+	intrenable(edev->irq, rtl8169interrupt, edev, edev->tbdf, edev->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/ether82543gc.c
+++ b/sys/src/9/pc/ether82543gc.c
@@ -18,8 +18,7 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
-
-#include "etherif.h"
+#include "../port/etherif.h"
 
 enum {
 	Ctrl		= 0x00000000,	/* Device Control */
--- a/sys/src/9/pc/ether82557.c
+++ b/sys/src/9/pc/ether82557.c
@@ -16,9 +16,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
-
 enum {
 	Nrfd		= 64,		/* receive frame area */
 	Ncb		= 64,		/* maximum control blocks queued */
@@ -1332,7 +1331,6 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->interrupt = interrupt;
 	ether->ifstat = ifstat;
 	ether->shutdown = shutdown;
 
@@ -1339,6 +1337,8 @@
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
 	ether->arg = ether;
+
+	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/ether82563.c
+++ b/sys/src/9/pc/ether82563.c
@@ -11,9 +11,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
-
 /*
  * note: the 82575, 82576 and 82580 are operated using registers aliased
  * to the 82563-style architecture.  many features seen in the 82598
@@ -2105,7 +2104,6 @@
 	 */
 	edev->attach = i82563attach;
 //	edev->transmit = i82563transmit;
-	edev->interrupt = i82563interrupt;
 	edev->ifstat = i82563ifstat;
 	edev->ctl = i82563ctl;
 
@@ -2113,6 +2111,8 @@
 	edev->promiscuous = i82563promiscuous;
 	edev->shutdown = i82563shutdown;
 	edev->multicast = i82563multicast;
+
+	intrenable(edev->irq, i82563interrupt, edev, edev->tbdf, edev->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/ether82598.c
+++ b/sys/src/9/pc/ether82598.c
@@ -10,7 +10,7 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 
 /*
  * // comments note conflicts with 82563-style drivers,
@@ -953,11 +953,12 @@
 	e->attach = attach;
 	e->ctl = ctl;
 	e->ifstat = ifstat;
-	e->interrupt = interrupt;
 	e->multicast = multicast;
 	e->promiscuous = promiscuous;
 	e->shutdown = shutdown;
 	e->transmit = transmit;
+
+	intrenable(e->irq, interrupt, e, e->tbdf, e->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/ether83815.c
+++ b/sys/src/9/pc/ether83815.c
@@ -24,9 +24,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
-
 #define DEBUG		0
 #define debug		if(DEBUG)print
 
@@ -1219,7 +1218,6 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->interrupt = interrupt;
 	ether->ifstat = ifstat;
 
 	ether->arg = ether;
@@ -1226,6 +1224,9 @@
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
 	ether->shutdown = shutdown;
+
+	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
+
 	return 0;
 }
 
--- a/sys/src/9/pc/ether8390.c
+++ b/sys/src/9/pc/ether8390.c
@@ -10,8 +10,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ether8390.h"
 
 enum {					/* NIC core registers */
@@ -801,12 +801,13 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->interrupt = interrupt;
 	ether->ifstat = 0;
 
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
 	ether->arg = ether;
+
+	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/etherbcm.c
+++ b/sys/src/9/pc/etherbcm.c
@@ -15,9 +15,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
-
 #define Rbsz		ROUNDUP(sizeof(Etherpkt)+4, 4)
 
 typedef struct Ctlr Ctlr;
@@ -873,7 +872,6 @@
 	edev->port = ctlr->port;
 	edev->irq = ctlr->pdev->intl;
 	edev->tbdf = ctlr->pdev->tbdf;
-	edev->interrupt = bcminterrupt;
 	edev->transmit = bcmtransmit;
 	edev->multicast = bcmmulticast;
 	edev->promiscuous = bcmpromiscuous;
@@ -884,6 +882,9 @@
 		edev->ctlr = nil;
 		goto again;
 	}
+
+	intrenable(edev->irq, bcminterrupt, edev, edev->tbdf, edev->name);
+
 	return 0;
 }
 
--- a/sys/src/9/pc/etherdp83820.c
+++ b/sys/src/9/pc/etherdp83820.c
@@ -12,8 +12,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ethermii.h"
 
 enum {					/* Registers */
@@ -1232,7 +1232,6 @@
 
 	edev->attach = dp83820attach;
 	edev->transmit = dp83820transmit;
-	edev->interrupt = dp83820interrupt;
 	edev->ifstat = dp83820ifstat;
 
 	edev->arg = edev;
@@ -1239,6 +1238,8 @@
 	edev->promiscuous = dp83820promiscuous;
 	edev->multicast = dp83820multicast;
 	edev->shutdown = dp83820shutdown;
+
+	intrenable(edev->irq, dp83820interrupt, edev, edev->tbdf, edev->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/etherec2t.c
+++ b/sys/src/9/pc/etherec2t.c
@@ -9,8 +9,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ether8390.h"
 
 enum {
--- a/sys/src/9/pc/etherelnk3.c
+++ b/sys/src/9/pc/etherelnk3.c
@@ -16,9 +16,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
-
 #define XCVRDEBUG		if(0)print
 
 enum {
@@ -2124,7 +2123,6 @@
 	 */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->interrupt = interrupt;
 	ether->ifstat = ifstat;
 
 	ether->promiscuous = promiscuous;
@@ -2131,6 +2129,8 @@
 	ether->multicast = multicast;
 	ether->shutdown = shutdown;
 	ether->arg = ether;
+
+	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/etherga620.c
+++ b/sys/src/9/pc/etherga620.c
@@ -18,10 +18,9 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
 #define malign(n)	xspanalloc((n), 32, 0)
-
-#include "etherif.h"
 #include "etherga620fw.h"
 
 enum {
@@ -1260,7 +1259,6 @@
 	 */
 	edev->attach = ga620attach;
 	edev->transmit = ga620transmit;
-	edev->interrupt = ga620interrupt;
 	edev->ifstat = ga620ifstat;
 	edev->ctl = ga620ctl;
 
@@ -1268,6 +1266,8 @@
 	edev->promiscuous = ga620promiscuous;
 	edev->multicast = ga620multicast;
 	edev->shutdown = ga620shutdown;
+
+	intrenable(edev->irq, ga620interrupt, edev, edev->tbdf, edev->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/etherif.h
+++ /dev/null
@@ -1,39 +1,0 @@
-enum {
-	MaxEther	= 64,
-	Ntypes		= 8,
-};
-
-typedef struct Ether Ether;
-struct Ether {
-	ISAConf;			/* hardware info */
-
-	int	ctlrno;
-	int	tbdf;			/* type+busno+devno+funcno */
-	int	minmtu;
-	int 	maxmtu;
-	uchar	ea[Eaddrlen];
-
-	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))
--- a/sys/src/9/pc/etherigbe.c
+++ b/sys/src/9/pc/etherigbe.c
@@ -24,8 +24,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ethermii.h"
 
 enum {
@@ -2020,7 +2020,6 @@
 	 */
 	edev->attach = igbeattach;
 	edev->transmit = igbetransmit;
-	edev->interrupt = igbeinterrupt;
 	edev->ifstat = igbeifstat;
 	edev->ctl = igbectl;
 
@@ -2028,6 +2027,8 @@
 	edev->promiscuous = igbepromiscuous;
 	edev->shutdown = igbeshutdown;
 	edev->multicast = igbemulticast;
+
+	intrenable(edev->irq, igbeinterrupt, edev, edev->tbdf, edev->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/etheriwl.c
+++ b/sys/src/9/pc/etheriwl.c
@@ -14,10 +14,9 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
+#include "../port/wifi.h"
 
-#include "etherif.h"
-#include "wifi.h"
-
 enum {
 	MaxQueue	= 24*1024,	/* total buffer is 2*MaxQueue: 48k at 22Mbit ≅ 20ms */
 
@@ -2534,7 +2533,6 @@
 	edev->irq = ctlr->pdev->intl;
 	edev->tbdf = ctlr->pdev->tbdf;
 	edev->arg = edev;
-	edev->interrupt = iwlinterrupt;
 	edev->attach = iwlattach;
 	edev->ifstat = iwlifstat;
 	edev->ctl = iwlctl;
@@ -2547,6 +2545,8 @@
 		edev->ctlr = nil;
 		goto again;
 	}
+
+	intrenable(edev->irq, iwlinterrupt, edev, edev->tbdf, edev->name);
 	
 	return 0;
 }
--- a/sys/src/9/pc/etherm10g.c
+++ b/sys/src/9/pc/etherm10g.c
@@ -14,8 +14,7 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
-
-#include "../pc/etherif.h"
+#include "../port/etherif.h"
 
 #ifndef KiB
 #define KiB		1024u			/* Kibi 0x0000000000000400 */
--- a/sys/src/9/pc/ethermii.c
+++ b/sys/src/9/pc/ethermii.c
@@ -6,8 +6,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ethermii.h"
 
 int
--- a/sys/src/9/pc/etherrt2860.c
+++ b/sys/src/9/pc/etherrt2860.c
@@ -14,10 +14,9 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
+#include "../port/wifi.h"
 
-#include "etherif.h"
-#include "wifi.h"
-
 /* for consistency */
 typedef signed char s8int;
 
@@ -3544,7 +3543,6 @@
 	edev->irq = ctlr->pdev->intl;
 	edev->tbdf = ctlr->pdev->tbdf;
 	edev->arg = edev;
-	edev->interrupt = rt2860interrupt;
 	edev->attach = rt2860attach;
 	edev->ifstat = rt2860ifstat;
 	edev->ctl = rt2860ctl;
@@ -3556,6 +3554,9 @@
 		edev->ctlr = nil;
 		goto again;
 	}
+
+	intrenable(edev->irq, rt2860interrupt, edev, edev->tbdf, edev->name);
+
 	return 0;
 }
 
--- a/sys/src/9/pc/ethersink.c
+++ /dev/null
@@ -1,65 +1,0 @@
-/*
- * An ethernet /dev/null.
- * Useful as a bridging target with ethernet-based VPN.
- */
-#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/netif.h"
-#include "etherif.h"
-
-static long
-ctl(Ether *ether, void *buf, long n)
-{
-	uchar ea[Eaddrlen];
-	Cmdbuf *cb;
-
-	cb = parsecmd(buf, n);
-	if(cb->nf >= 2
-	&& strcmp(cb->f[0], "ea")==0
-	&& parseether(ea, cb->f[1]) == 0){
-		free(cb);
-		memmove(ether->ea, ea, Eaddrlen);
-		memmove(ether->addr, ether->ea, Eaddrlen);
-		return 0;
-	}
-	free(cb);
-	error(Ebadctl);
-	return -1;	/* not reached */
-}
-
-static void
-nop(Ether*)
-{
-}
-
-static int
-reset(Ether* ether)
-{
-	uchar ea[Eaddrlen];
-
-	if(ether->type==nil)
-		return -1;
-	memset(ea, 0, sizeof ea);
-	ether->mbps = 1000;
-	ether->attach = nop;
-	ether->transmit = nop;
-	ether->irq = -1;
-	ether->interrupt = nil;
-	ether->ifstat = nil;
-	ether->ctl = ctl;
-	ether->promiscuous = nil;
-	ether->multicast = nil;
-	ether->arg = ether;
-	return 0;
-}
-
-void
-ethersinklink(void)
-{
-	addethercard("sink", reset);
-}
--- a/sys/src/9/pc/ethersmc.c
+++ b/sys/src/9/pc/ethersmc.c
@@ -10,7 +10,7 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 
 enum {
 	IoSize		= 0x10,		/* port pool size */
@@ -766,12 +766,15 @@
 
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->interrupt = interrupt;
 	ether->ifstat = ifstat;
 	ether->promiscuous = promiscuous;
 	ether->multicast = multicast;
 	ether->arg = ether;
+
 	iunlock(ctlr);
+
+	intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
+
 	return 0;
 }
 
--- a/sys/src/9/pc/ethervgbe.c
+++ b/sys/src/9/pc/ethervgbe.c
@@ -29,8 +29,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ethermii.h"
 
 #define DEBUG
@@ -1137,14 +1137,15 @@
 	memmove(edev->ea, ctlr->ea, Eaddrlen);
 	edev->attach = vgbeattach;
 	edev->transmit = vgbetransmit;
-	edev->interrupt = vgbeinterrupt;
 	edev->ifstat = vgbeifstat;
 //	edev->promiscuous = vgbepromiscuous;
 	edev->multicast = vgbemulticast;
 //	edev->shutdown = vgbeshutdown;
 	edev->ctl = vgbectl;
-
 	edev->arg = edev;
+
+	intrenable(edev->irq, vgbeinterrupt, edev, edev->tbdf, edev->name);
+
 	return 0;
 }
 
--- a/sys/src/9/pc/ethervirtio.c
+++ b/sys/src/9/pc/ethervirtio.c
@@ -6,7 +6,7 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 
 /*
  * virtio ethernet driver
@@ -661,7 +661,6 @@
 
 	edev->attach = attach;
 	edev->shutdown = shutdown;
-	edev->interrupt = interrupt;
 	edev->ifstat = ifstat;
 
 	if((ctlr->feat & (Fctrlvq|Fctrlrx)) == (Fctrlvq|Fctrlrx)){
@@ -668,6 +667,8 @@
 		edev->multicast = multicast;
 		edev->promiscuous = promiscuous;
 	}
+
+	intrenable(edev->irq, interrupt, edev, edev->tbdf, edev->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/ethervt6102.c
+++ b/sys/src/9/pc/ethervt6102.c
@@ -17,8 +17,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ethermii.h"
 
 enum {
@@ -1032,7 +1032,6 @@
 	 */
 	edev->attach = vt6102attach;
 	edev->transmit = vt6102transmit;
-	edev->interrupt = vt6102interrupt;
 	edev->ifstat = vt6102ifstat;
 	edev->ctl = nil;
 
@@ -1039,6 +1038,8 @@
 	edev->arg = edev;
 	edev->promiscuous = vt6102promiscuous;
 	edev->multicast = vt6102multicast;
+
+	intrenable(edev->irq, vt6102interrupt, edev, edev->tbdf, edev->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/ethervt6105m.c
+++ b/sys/src/9/pc/ethervt6105m.c
@@ -20,8 +20,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ethermii.h"
 
 enum {
@@ -1200,7 +1200,6 @@
 	 */
 	edev->attach = vt6105Mattach;
 	edev->transmit = vt6105Mtransmit;
-	edev->interrupt = vt6105Minterrupt;
 	edev->ifstat = vt6105Mifstat;
 	edev->ctl = nil;
 
@@ -1209,6 +1208,8 @@
 	edev->multicast = vt6105Mmulticast;
 
 	edev->maxmtu = ETHERMAXTU+Bslop;
+
+	intrenable(edev->irq, vt6105Minterrupt, edev, edev->tbdf, edev->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/etherwavelan.c
+++ b/sys/src/9/pc/etherwavelan.c
@@ -8,7 +8,7 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 
 #include "wavelan.h"
 
--- a/sys/src/9/pc/etherwpi.c
+++ b/sys/src/9/pc/etherwpi.c
@@ -6,10 +6,9 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
+#include "../port/wifi.h"
 
-#include "etherif.h"
-#include "wifi.h"
-
 enum {
 	MaxQueue	= 24*1024,	/* total buffer is 2*MaxQueue: 48k at 22Mbit ≅ 20ms */
 
@@ -1846,7 +1845,6 @@
 	edev->irq = ctlr->pdev->intl;
 	edev->tbdf = ctlr->pdev->tbdf;
 	edev->arg = edev;
-	edev->interrupt = wpiinterrupt;
 	edev->attach = wpiattach;
 	edev->ifstat = wpiifstat;
 	edev->ctl = wpictl;
@@ -1859,6 +1857,8 @@
 		edev->ctlr = nil;
 		goto again;
 	}
+
+	intrenable(edev->irq, wpiinterrupt, edev, edev->tbdf, edev->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/etheryuk.c
+++ b/sys/src/9/pc/etheryuk.c
@@ -10,7 +10,7 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 
 #define Pciwaddrh(x)	0
 #define Pciwaddrl(x)	PCIWADDR(x)
@@ -2247,11 +2247,12 @@
 	e->attach = attach;
 	e->ctl = ctl;
 	e->ifstat = ifstat;
-	e->interrupt = interrupt;
 	e->multicast = multicast;
 	e->promiscuous = promiscuous;
 	e->shutdown = shutdown;
 	e->transmit = nil;
+
+	intrenable(e->irq, interrupt, e, e->tbdf, e->name);
 
 	return 0;
 }
--- a/sys/src/9/pc/mkfile
+++ b/sys/src/9/pc/mkfile
@@ -100,7 +100,7 @@
 <../port/portmkfile
 <|../port/mkbootrules $CONF
 
-$ETHER: 			etherif.h ../port/netif.h
+$ETHER: 			../port/etherif.h ../port/netif.h
 $AUDIO:				../port/audioif.h
 ether8003.$O ether8390.$O:	ether8390.h
 etheryuk.$O:			yukdump.h
@@ -122,11 +122,9 @@
 trap.$O:			/sys/include/tos.h
 uartaxp.$O:			uartaxp.i
 etherm10g.$O:			etherm10g2k.i etherm10g4k.i
-etheriwl.$O:			wifi.h
-etherwpi.$O:			wifi.h
-etherrt2860.$O: 		wifi.h
-wifi.$O:			wifi.h etherif.h ../port/netif.h /sys/include/libsec.h
-wifi.$O:			../ip/ip.h ../ip/ipv6.h
+etheriwl.$O:			../port/wifi.h
+etherwpi.$O:			../port/wifi.h
+etherrt2860.$O: 		../port/wifi.h
 
 init.h:D:		../port/initcode.c init9.c
 	$CC ../port/initcode.c
--- a/sys/src/9/pc/trap.c
+++ b/sys/src/9/pc/trap.c
@@ -40,7 +40,6 @@
 			irq, tbdf, name);
 		return;
 	}
-
 	if(tbdf != BUSUNKNOWN && (irq == 0xff || irq == 0)){
 		print("intrenable: got unassigned irq %d, tbdf 0x%uX for %s\n",
 			irq, tbdf, name);
@@ -47,6 +46,14 @@
 		irq = -1;
 	}
 
+	/*
+	 * IRQ2 doesn't really exist, it's used to gang the interrupt
+	 * controllers together. A device set to IRQ2 will appear on
+	 * the second interrupt controller as IRQ9.
+	 */
+	if(irq == 2)
+		irq = 9;
+
 	if((v = xalloc(sizeof(Vctl))) == nil)
 		panic("intrenable: out of memory");
 	v->isintr = 1;
@@ -83,6 +90,8 @@
 	Vctl **pv, *v;
 	int vno;
 
+	if(irq == 2)
+		irq = 9;
 	if(arch->intrvecno == nil || (tbdf != BUSUNKNOWN && (irq == 0xff || irq == 0))){
 		/*
 		 * on APIC machine, irq is pretty meaningless
--- a/sys/src/9/pc/usbehcipc.c
+++ b/sys/src/9/pc/usbehcipc.c
@@ -272,16 +272,6 @@
 	ehcilinkage(hp);
 	hp->shutdown = shutdown;
 	hp->debug = setdebug;
-	if(hp->interrupt == nil)
-		return 0;
-
-	/*
-	 * IRQ2 doesn't really exist, it's used to gang the interrupt
-	 * controllers together. A device set to IRQ2 will appear on
-	 * the second interrupt controller as IRQ9.
-	 */
-	if(hp->irq == 2)
-		hp->irq = 9;
 	intrenable(hp->irq, hp->interrupt, hp, hp->tbdf, hp->type);
 
 	return 0;
--- a/sys/src/9/pc/usbohci.c
+++ b/sys/src/9/pc/usbohci.c
@@ -2608,14 +2608,6 @@
 	hp->shutdown = shutdown;
 	hp->debug = usbdebug;
 	hp->type = "ohci";
-
-	/*
-	 * IRQ2 doesn't really exist, it's used to gang the interrupt
-	 * controllers together. A device set to IRQ2 will appear on
-	 * the second interrupt controller as IRQ9.
-	 */
-	if(hp->irq == 2)
-		hp->irq = 9;
 	intrenable(hp->irq, hp->interrupt, hp, hp->tbdf, hp->type);
 
 	return 0;
--- a/sys/src/9/pc/usbuhci.c
+++ b/sys/src/9/pc/usbuhci.c
@@ -2345,14 +2345,6 @@
 	hp->shutdown = shutdown;
 	hp->debug = setdebug;
 	hp->type = "uhci";
-
-	/*
-	 * IRQ2 doesn't really exist, it's used to gang the interrupt
-	 * controllers together. A device set to IRQ2 will appear on
-	 * the second interrupt controller as IRQ9.
-	 */
-	if(hp->irq == 2)
-		hp->irq = 9;
 	intrenable(hp->irq, hp->interrupt, hp, hp->tbdf, hp->type);
 
 	return 0;
--- a/sys/src/9/pc/wavelan.c
+++ b/sys/src/9/pc/wavelan.c
@@ -25,7 +25,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
+
 #include "wavelan.h"
 
 enum
@@ -1234,7 +1235,6 @@
 	ether->mbps = 10;
 	ether->attach = w_attach;
 	ether->detach = w_detach;
-	ether->interrupt = w_interrupt;
 	ether->transmit = w_transmit;
 	ether->ifstat = w_ifstat;
 	ether->ctl = w_ctl;
@@ -1243,6 +1243,8 @@
 	ether->multicast = w_multicast;
 	ether->scanbs = w_scanbs;
 	ether->arg = ether;
+
+	intrenable(ether->irq, w_interrupt, ether, ether->tbdf, ether->name);
 
 	DEBUG("#l%d: irq %d port %lx type %s",
 		ether->ctlrno, ether->irq, ether->port,	ether->type);
--- a/sys/src/9/pc/wifi.c
+++ /dev/null
@@ -1,1881 +1,0 @@
-#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"
-#include "../port/netif.h"
-
-#include "etherif.h"
-#include "wifi.h"
-
-#include <libsec.h>
-
-typedef struct SNAP SNAP;
-struct SNAP
-{
-	uchar	dsap;
-	uchar	ssap;
-	uchar	control;
-	uchar	orgcode[3];
-	uchar	type[2];
-};
-
-enum {
-	WIFIHDRSIZE = 2+2+3*6+2,
-	SNAPHDRSIZE = 8,
-};
-
-static char Sconn[] = "connecting";
-static char Sauth[] = "authenticated";
-static char Sneedauth[] = "need authentication";
-static char Sunauth[] = "unauthenticated";
-
-static char Sassoc[] = "associated";
-static char Sunassoc[] = "unassociated";
-static char Sblocked[] = "blocked";	/* no keys negotiated. only pass EAPOL frames */
-
-static uchar basicrates[] = {
-	0x80 | 2,	/* 1.0	Mb/s */
-	0x80 | 4,	/* 2.0	Mb/s */
-	0x80 | 11,	/* 5.5	Mb/s */
-	0x80 | 22,	/* 11.0	Mb/s */
-
-	0
-};
-
-static Block* wifidecrypt(Wifi *, Wnode *, Block *);
-static Block* wifiencrypt(Wifi *, Wnode *, Block *);
-static void freewifikeys(Wifi *, Wnode *);
-
-static void dmatproxy(Block *bp, int upstream, uchar proxy[Eaddrlen], DMAT *t);
-
-static uchar*
-srcaddr(Wifipkt *w)
-{
-	if((w->fc[1] & 0x02) == 0)
-		return w->a2;
-	if((w->fc[1] & 0x01) == 0)
-		return w->a3;
-	return w->a4;
-}
-static uchar*
-dstaddr(Wifipkt *w)
-{
-	if((w->fc[1] & 0x01) != 0)
-		return w->a3;
-	return w->a1;
-}
-
-int
-wifihdrlen(Wifipkt *w)
-{
-	int n;
-
-	n = WIFIHDRSIZE;
-	if((w->fc[0] & 0x0c) == 0x08)
-		if((w->fc[0] & 0xf0) == 0x80){	/* QOS */
-			n += 2;
-			if(w->fc[1] & 0x80)
-				n += 4;
-		}
-	if((w->fc[1] & 3) == 0x03)
-		n += Eaddrlen;
-	return n;
-}
-
-void
-wifiiq(Wifi *wifi, Block *b)
-{
-	SNAP s;
-	Wifipkt h, *w;
-	Etherpkt *e;
-	int hdrlen;
-
-	if(BLEN(b) < WIFIHDRSIZE)
-		goto drop;
-	w = (Wifipkt*)b->rp;
-	hdrlen = wifihdrlen(w);
-	if(BLEN(b) < hdrlen)
-		goto drop;
-	if(memcmp(srcaddr(w), wifi->ether->ea, Eaddrlen) == 0)
-		goto drop;
-	if(w->fc[1] & 0x40){
-		/* encrypted */
-		qpass(wifi->iq, b);
-		return;
-	}
-	switch(w->fc[0] & 0x0c){
-	case 0x00:	/* management */
-		if((w->fc[1] & 3) != 0x00)	/* STA->STA */
-			break;
-		qpass(wifi->iq, b);
-		return;
-	case 0x04:	/* control */
-		break;
-	case 0x08:	/* data */
-		b->rp += hdrlen;
-		switch(w->fc[0] & 0xf0){
-		default:
-			goto drop;
-		case 0x80:	/* QOS */
-		case 0x00:
-			break;
-		}
-		if(BLEN(b) < SNAPHDRSIZE)
-			break;
-		memmove(&s, b->rp, SNAPHDRSIZE);
-		if(s.dsap != 0xAA || s.ssap != 0xAA || s.control != 3)
-			break;
-		if(s.orgcode[0] != 0 || s.orgcode[1] != 0 || s.orgcode[2] != 0)
-			break;
-		b->rp += SNAPHDRSIZE-ETHERHDRSIZE;
-		h = *w;
-		e = (Etherpkt*)b->rp;
-		memmove(e->d, dstaddr(&h), Eaddrlen);
-		memmove(e->s, srcaddr(&h), Eaddrlen);
-		memmove(e->type, s.type, 2);
-
-		dmatproxy(b, 0, wifi->ether->ea, &wifi->dmat);
-
-		etheriq(wifi->ether, b, 1);
-		return;
-	}
-drop:
-	freeb(b);
-}
-
-static void
-wifitx(Wifi *wifi, Wnode *wn, Block *b)
-{
-	Wifipkt *w;
-	uint seq;
-
-	wn->lastsend = MACHP(0)->ticks;
-
-	seq = incref(&wifi->txseq);
-	seq <<= 4;
-
-	w = (Wifipkt*)b->rp;
-	w->dur[0] = 0;
-	w->dur[1] = 0;
-	w->seq[0] = seq;
-	w->seq[1] = seq>>8;
-
-	if((w->fc[0] & 0x0c) != 0x00){
-		b = wifiencrypt(wifi, wn, b);
-		if(b == nil)
-			return;
-	}
-
-	if((wn->txcount++ & 255) == 255 && wn->actrate != nil && wn->actrate != wn->maxrate){
-		uchar *a, *p;
-
-		for(a = wn->maxrate, p = wifi->rates; *p; p++){
-			if(*p < *a && *p > *wn->actrate)
-				a = p;
-		}
-		wn->actrate = a;
-	}
-
-	(*wifi->transmit)(wifi, wn, b);
-}
-
-static Wnode*
-nodelookup(Wifi *wifi, uchar *bssid, int new)
-{
-	Wnode *wn, *nn;
-
-	if(memcmp(bssid, wifi->ether->bcast, Eaddrlen) == 0)
-		return nil;
-	if((wn = wifi->bss) != nil){
-		if(memcmp(wn->bssid, bssid, Eaddrlen) == 0)
-			return wn;
-	}
-	if((nn = wifi->node) == wn)
-		nn++;
-	for(wn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++){
-		if(wn == wifi->bss)
-			continue;
-		if(memcmp(wn->bssid, bssid, Eaddrlen) == 0)
-			return wn;
-		if((long)(wn->lastsend - nn->lastsend) < 0
-		|| (long)(wn->lastseen - nn->lastseen) < 0)
-			nn = wn;
-	}
-	if(!new)
-		return nil;
-	freewifikeys(wifi, nn);
-	memset(nn, 0, sizeof(Wnode));
-	memmove(nn->bssid, bssid, Eaddrlen);
-	return nn;
-}
-
-void
-wifitxfail(Wifi *wifi, Block *b)
-{
-	Wifipkt *w;
-	Wnode *wn;
-
-	if(b == nil)
-		return;
-	w = (Wifipkt*)b->rp;
-	wn = nodelookup(wifi, w->a1, 0);
-	if(wn == nil)
-		return;
-	wn->txerror++;
-	if(wn->actrate != nil && wn->minrate != wn->actrate){
-		uchar *a, *p;
-
-		for(a = wn->minrate, p = wifi->rates; *p; p++){
-			if(*p > *a && *p < *wn->actrate)
-				a = p;
-		}
-		wn->actrate = a;
-	}
-}
-
-static uchar*
-putrates(uchar *p, uchar *rates)
-{
-	int n, m;
-
-	n = m = strlen((char*)rates);
-	if(n > 8)
-		n = 8;
-	/* supported rates */
-	*p++ = 1;
-	*p++ = n;
-	memmove(p, rates, n);
-	p += n;
-	if(m > 8){
-		/* extended supported rates */
-		*p++ = 50;
-		*p++ = m;
-		memmove(p, rates, m);
-		p += m;
-	}
-	return p;
-}
-
-static void
-wifiprobe(Wifi *wifi, Wnode *wn)
-{
-	Wifipkt *w;
-	Block *b;
-	uchar *p;
-	int n;
-
-	n = strlen(wifi->essid);
-	if(n == 0){
-		/* no specific essid, just tell driver to tune channel */
-		(*wifi->transmit)(wifi, wn, nil);
-		return;
-	}
-
-	b = allocb(WIFIHDRSIZE + 512);
-	w = (Wifipkt*)b->wp;
-	w->fc[0] = 0x40;	/* probe request */
-	w->fc[1] = 0x00;	/* STA->STA */
-	memmove(w->a1, wifi->ether->bcast, Eaddrlen);	/* ??? */
-	memmove(w->a2, wifi->ether->ea, Eaddrlen);
-	memmove(w->a3, wifi->ether->bcast, Eaddrlen);
-	b->wp += WIFIHDRSIZE;
-	p = b->wp;
-
-	*p++ = 0;	/* set */
-	*p++ = n;
-	memmove(p, wifi->essid, n);
-	p += n;
-
-	p = putrates(p, wifi->rates);
-
-	*p++ = 3;	/* ds parameter set */
-	*p++ = 1;
-	*p++ = wn->channel;
-
-	b->wp = p;
-	wifitx(wifi, wn, b);
-}
-
-static void
-sendauth(Wifi *wifi, Wnode *bss)
-{
-	Wifipkt *w;
-	Block *b;
-	uchar *p;
-
-	b = allocb(WIFIHDRSIZE + 3*2);
-	w = (Wifipkt*)b->wp;
-	w->fc[0] = 0xB0;	/* auth request */
-	w->fc[1] = 0x00;	/* STA->STA */
-	memmove(w->a1, bss->bssid, Eaddrlen);	/* ??? */
-	memmove(w->a2, wifi->ether->ea, Eaddrlen);
-	memmove(w->a3, bss->bssid, Eaddrlen);
-	b->wp += WIFIHDRSIZE;
-	p = b->wp;
-	*p++ = 0;	/* alg */
-	*p++ = 0;
-	*p++ = 1;	/* seq */
-	*p++ = 0;
-	*p++ = 0;	/* status */
-	*p++ = 0;
-	b->wp = p;
-
-	bss->aid = 0;
-
-	wifitx(wifi, bss, b);
-}
-
-static void
-sendassoc(Wifi *wifi, Wnode *bss)
-{
-	Wifipkt *w;
-	Block *b;
-	uchar *p;
-	int cap, n;
-
-	b = allocb(WIFIHDRSIZE + 512);
-	w = (Wifipkt*)b->wp;
-	w->fc[0] = 0x00;	/* assoc request */
-	w->fc[1] = 0x00;	/* STA->STA */
-	memmove(w->a1, bss->bssid, Eaddrlen);	/* ??? */
-	memmove(w->a2, wifi->ether->ea, Eaddrlen);
-	memmove(w->a3, bss->bssid, Eaddrlen);
-	b->wp += WIFIHDRSIZE;
-	p = b->wp;
-
-	/* capinfo */
-	cap = 1;				// ESS
-	cap |= (1<<5);				// Short Preamble
-	cap |= (1<<10) & bss->cap;		// Short Slot Time
-	*p++ = cap;
-	*p++ = cap>>8;
-
-	/* interval */
-	*p++ = 16;
-	*p++ = 16>>8;
-
-	n = strlen(bss->ssid);
-	*p++ = 0;	/* SSID */
-	*p++ = n;
-	memmove(p, bss->ssid, n);
-	p += n;
-
-	p = putrates(p, wifi->rates);
-
-	n = bss->rsnelen;
-	if(n > 0){
-		memmove(p, bss->rsne, n);
-		p += n;
-	}
-
-	b->wp = p;
-	wifitx(wifi, bss, b);
-}
-
-static void
-setstatus(Wifi *wifi, Wnode *wn, char *new)
-{
-	char *old;
-
-	old = wn->status;
-	wn->status = new;
-	if(wifi->debug && new != old)
-		print("#l%d: status %E: %.12ld %.12ld: %s -> %s (from pc=%#p)\n",
-			wifi->ether->ctlrno, 
-			wn->bssid, 
-			TK2MS(MACHP(0)->ticks), TK2MS(MACHP(0)->ticks - wn->lastsend),
-			old, new,
-			getcallerpc(&wifi));
-}
-
-static void
-recvassoc(Wifi *wifi, Wnode *wn, uchar *d, int len)
-{
-	uint s;
-
-	if(len < 2+2+2)
-		return;
-
-	d += 2;	/* caps */
-	s = d[0] | d[1]<<8;
-	d += 2;
-	switch(s){
-	case 0x00:
-		wn->aid = d[0] | d[1]<<8;
-		if(wn->rsnelen > 0)
-			setstatus(wifi, wn, Sblocked);
-		else
-			setstatus(wifi, wn, Sassoc);
-		break;
-	default:
-		wn->aid = 0;
-		setstatus(wifi, wn, Sunassoc);
-	}
-}
-
-static void
-recvbeacon(Wifi *wifi, Wnode *wn, uchar *d, int len)
-{
-	static uchar wpa1oui[4] = { 0x00, 0x50, 0xf2, 0x01 };
-	uchar *e, *x, *p, t;
-	int rsnset;
-
-	len -= 8+2+2;
-	if(len < 0)
-		return;
-
-	d += 8;	/* timestamp */
-	wn->ival = d[0] | d[1]<<8;
-	d += 2;
-	wn->cap = d[0] | d[1]<<8;
-	d += 2;
-
-	rsnset = 0;
-	for(e = d + len; d+2 <= e; d = x){
-		d += 2;
-		x = d + d[-1];
-		if(x > e)			
-			break;	/* truncated */
-		t = d[-2];
-		switch(t){
-		case 0:		/* SSID */
-			len = 0;
-			while(len < Essidlen && d+len < x && d[len] != 0)
-				len++;
-			if(len == 0)
-				continue;
-			if(len != strlen(wn->ssid) || strncmp(wn->ssid, (char*)d, len) != 0){
-				strncpy(wn->ssid, (char*)d, len);
-				wn->ssid[len] = 0;
-			}
-			break;
-		case 1:		/* supported rates */
-		case 50:	/* extended rates */
-			if(wn->actrate != nil || wifi->rates == nil)
-				break;	/* already set */
-			while(d < x){
-				t = *d++ | 0x80;
-				for(p = wifi->rates; *p != 0; p++){
-					if(*p == t){
-						if(wn->minrate == nil || t < *wn->minrate)
-							wn->minrate = p;
-						if(wn->maxrate == nil || t > *wn->maxrate)
-							wn->maxrate = p;
-						break;
-					}
-				}
-				wn->actrate = wn->maxrate;
-			}
-			break;
-		case 3:		/* DSPARAMS */
-			if(d != x)
-				wn->channel = d[0];
-			break;
-		case 221:	/* vendor specific */
-			len = x - d;
-			if(rsnset || len < sizeof(wpa1oui) || memcmp(d, wpa1oui, sizeof(wpa1oui)) != 0)
-				break;
-			/* no break */
-		case 48:	/* RSN information */
-			len = x - &d[-2];
-			memmove(wn->brsne, &d[-2], len);
-			wn->brsnelen = len;
-			rsnset = 1;
-			break;
-		}
-	}
-}
-
-static void
-freewifikeys(Wifi *wifi, Wnode *wn)
-{
-	int i;
-
-	wlock(&wifi->crypt);
-	for(i=0; i<nelem(wn->rxkey); i++){
-		secfree(wn->rxkey[i]);
-		wn->rxkey[i] = nil;
-	}
-	for(i=0; i<nelem(wn->txkey); i++){
-		secfree(wn->txkey[i]);
-		wn->txkey[i] = nil;
-	}
-	wunlock(&wifi->crypt);
-}
-
-static void
-wifideauth(Wifi *wifi, Wnode *wn)
-{
-	Ether *ether;
-	Netfile *f;
-	int i;
-
-	/* deassociate node, clear keys */
-	setstatus(wifi, wn, Sunauth);
-	freewifikeys(wifi, wn);
-	memset(&wifi->dmat, 0, sizeof(wifi->dmat));
-	wn->aid = 0;
-
-	if(wn == wifi->bss){
-		/* notify driver about node aid association */
-		(*wifi->transmit)(wifi, wn, nil);
-
-		/* notify aux/wpa with a zero length packet that we got deassociated from the ap */
-		ether = wifi->ether;
-		for(i=0; i<ether->nfile; i++){
-			f = ether->f[i];
-			if(f == nil || f->in == nil || f->inuse == 0 || f->type != 0x888e)
-				continue;
-			qflush(f->in);
-			qwrite(f->in, 0, 0);
-		}
-		qflush(ether->oq);
-	}
-}
-
-/* check if a node qualifies as our bss matching bssid and essid */
-static int
-goodbss(Wifi *wifi, Wnode *wn)
-{
-	if(memcmp(wifi->bssid, wifi->ether->bcast, Eaddrlen) != 0){
-		if(memcmp(wifi->bssid, wn->bssid, Eaddrlen) != 0)
-			return 0;	/* bssid doesnt match */
-	} else if(wifi->essid[0] == 0)
-		return 0;	/* both bssid and essid unspecified */
-	if(wifi->essid[0] != 0 && strcmp(wifi->essid, wn->ssid) != 0)
-		return 0;	/* essid doesnt match */
-	return 1;
-}
-
-static void
-wifiproc(void *arg)
-{
-	Wifi *wifi;
-	Wifipkt *w;
-	Wnode *wn;
-	Block *b;
-
-	b = nil;
-	wifi = arg;
-	while(waserror())
-		;
-	for(;;){
-		if(b != nil){
-			freeb(b);
-			b = nil;
-			continue;
-		}
-		if((b = qbread(wifi->iq, 100000)) == nil)
-			break;
-		w = (Wifipkt*)b->rp;
-		if(w->fc[1] & 0x40){
-			/* encrypted */
-			if((wn = nodelookup(wifi, w->a2, 0)) == nil)
-				continue;
-			wn->lastseen = MACHP(0)->ticks;
-			if((b = wifidecrypt(wifi, wn, b)) != nil){
-				w = (Wifipkt*)b->rp;
-				if(w->fc[1] & 0x40)
-					continue;
-				wifiiq(wifi, b);
-				b = nil;
-			}
-			continue;
-		}
-		/* management */
-		if((w->fc[0] & 0x0c) != 0x00)
-			continue;
-
-		switch(w->fc[0] & 0xf0){
-		case 0x50:	/* probe response */
-			if(wifi->debug)
-				print("#l%d: got probe from %E\n", wifi->ether->ctlrno, w->a3);
-			/* no break */
-		case 0x80:	/* beacon */
-			if((wn = nodelookup(wifi, w->a3, 1)) == nil)
-				continue;
-			wn->lastseen = MACHP(0)->ticks;
-			b->rp += wifihdrlen(w);
-			recvbeacon(wifi, wn, b->rp, BLEN(b));
-
-			if(wifi->bss == nil
-			&& TK2MS(MACHP(0)->ticks - wn->lastsend) > 1000
-			&& goodbss(wifi, wn)){
-				setstatus(wifi, wn, Sconn);
-				sendauth(wifi, wn);
-				wifi->lastauth = wn->lastsend;
-			}
-			continue;
-		}
-
-		if(memcmp(w->a1, wifi->ether->ea, Eaddrlen))
-			continue;
-		if((wn = nodelookup(wifi, w->a3, 0)) == nil)
-			continue;
-		wn->lastseen = MACHP(0)->ticks;
-		switch(w->fc[0] & 0xf0){
-		case 0x10:	/* assoc response */
-		case 0x30:	/* reassoc response */
-			b->rp += wifihdrlen(w);
-			recvassoc(wifi, wn, b->rp, BLEN(b));
-			/* notify driver about node aid association */
-			if(wn == wifi->bss)
-				(*wifi->transmit)(wifi, wn, nil);
-			break;
-		case 0xb0:	/* auth */
-			if(wifi->debug)
-				print("#l%d: got auth from %E\n", wifi->ether->ctlrno, wn->bssid);
-			if(wn->brsnelen > 0 && wn->rsnelen == 0)
-				setstatus(wifi, wn, Sneedauth);
-			else
-				setstatus(wifi, wn, Sauth);
-			if(wifi->bss == nil && goodbss(wifi, wn)){
-				wifi->bss = wn;
-				if(wn->status == Sauth)
-					sendassoc(wifi, wn);
-			}
-			break;
-		case 0xc0:	/* deauth */
-			if(wifi->debug)
-				print("#l%d: got deauth from %E\n", wifi->ether->ctlrno, wn->bssid);
-			wifideauth(wifi, wn);
-			break;
-		}
-	}
-	pexit("wifi in queue closed", 1);
-}
-
-static void
-wifietheroq(Wifi *wifi, Block *b)
-{
-	Etherpkt e;
-	Wifipkt h;
-	int hdrlen;
-	Wnode *wn;
-	SNAP *s;
-
-	if(BLEN(b) < ETHERHDRSIZE)
-		goto drop;
-	if((wn = wifi->bss) == nil)
-		goto drop;
-
-	dmatproxy(b, 1, wifi->ether->ea, &wifi->dmat);
-
-	memmove(&e, b->rp, ETHERHDRSIZE);
-	b->rp += ETHERHDRSIZE;
-	if(wn->status == Sblocked){
-		/* only pass EAPOL frames when port is blocked */
-		if((e.type[0]<<8 | e.type[1]) != 0x888e)
-			goto drop;
-	} else if(wn->status != Sassoc)
-		goto drop;
-
-	h.fc[0] = 0x08;	/* data */
-	memmove(h.a1, wn->bssid, Eaddrlen);
-	if(memcmp(e.s, wifi->ether->ea, Eaddrlen) == 0) {
-		h.fc[1] = 0x01;	/* STA->AP */
-	} else {
-		h.fc[1] = 0x03;	/* AP->AP (WDS) */
-		memmove(h.a2, wifi->ether->ea, Eaddrlen);
-	}
-	memmove(dstaddr(&h), e.d, Eaddrlen);
-	memmove(srcaddr(&h), e.s, Eaddrlen);
-
-	hdrlen = wifihdrlen(&h);
-	b = padblock(b, hdrlen + SNAPHDRSIZE);
-	memmove(b->rp, &h, hdrlen);
-	s = (SNAP*)(b->rp + hdrlen);
-	s->dsap = s->ssap = 0xAA;
-	s->control = 0x03;
-	s->orgcode[0] = 0;
-	s->orgcode[1] = 0;
-	s->orgcode[2] = 0;
-	memmove(s->type, e.type, 2);
-
-	wifitx(wifi, wn, b);
-	return;
-drop:
-	freeb(b);
-}
-
-static void
-wifoproc(void *arg)
-{
-	Ether *ether;
-	Wifi *wifi;
-	Block *b;
-
-	wifi = arg;
-	ether = wifi->ether;
-	while(waserror())
-		;
-	while((b = qbread(ether->oq, 1000000)) != nil)
-		wifietheroq(wifi, b);
-	pexit("ether out queue closed", 1);
-}
-
-static void
-wifsproc(void *arg)
-{
-	Ether *ether;
-	Wifi *wifi;
-	Wnode wnscan;
-	Wnode *wn;
-	ulong now, tmout;
-	uchar *rate;
-
-	wifi = arg;
-	ether = wifi->ether;
-
-	wn = &wnscan;
-	memset(wn, 0, sizeof(*wn));
-	memmove(wn->bssid, ether->bcast, Eaddrlen);
-
-	while(waserror())
-		;
-Scan:
-	/* scan for access point */
-	while(wifi->bss == nil){
-		ether->link = 0;
-		wnscan.channel = 1 + ((wnscan.channel+4) % 13);
-		wifiprobe(wifi, &wnscan);
-		do {
-			tsleep(&up->sleep, return0, 0, 200);
-			now = MACHP(0)->ticks;
-		} while(TK2MS(now-wifi->lastauth) < 1000);
-	}
-
-	/* maintain access point */
-	tmout = 0;
-	while((wn = wifi->bss) != nil){
-		ether->link = (wn->status == Sassoc) || (wn->status == Sblocked);
-		if(ether->link && (rate = wn->actrate) != nil)
-			ether->mbps = ((*rate & 0x7f)+3)/4;
-		now = MACHP(0)->ticks;
-		if(wn->status != Sneedauth && TK2SEC(now - wn->lastseen) > 20 || goodbss(wifi, wn) == 0){
-			wifideauth(wifi, wn);
-			wifi->bss = nil;
-			break;
-		}
-		if(TK2MS(now - wn->lastsend) > 1000){
-			if((wn->status == Sauth || wn->status == Sblocked) && (++tmout & 7) == 0)
-				wifideauth(wifi, wn);	/* stuck in auth, start over */
-			if(wn->status == Sconn || wn->status == Sunauth)
-				sendauth(wifi, wn);
-			if(wn->status == Sauth)
-				sendassoc(wifi, wn);
-		}
-		tsleep(&up->sleep, return0, 0, 500);
-	}
-	goto Scan;
-}
-
-Wifi*
-wifiattach(Ether *ether, void (*transmit)(Wifi*, Wnode*, Block*))
-{
-	char name[32];
-	Wifi *wifi;
-
-	wifi = malloc(sizeof(Wifi));
-	if(wifi == nil)
-		error(Enomem);
-	wifi->iq = qopen(ether->limit, 0, 0, 0);
-	if(wifi->iq == nil){
-		free(wifi);
-		error(Enomem);
-	}
-	wifi->ether = ether;
-	wifi->transmit = transmit;
-
-	wifi->rates = basicrates;
-
-	wifi->essid[0] = 0;
-	memmove(wifi->bssid, ether->bcast, Eaddrlen);
-
-	wifi->lastauth = MACHP(0)->ticks;
-
-	snprint(name, sizeof(name), "#l%dwifi", ether->ctlrno);
-	kproc(name, wifiproc, wifi);
-	snprint(name, sizeof(name), "#l%dwifo", ether->ctlrno);
-	kproc(name, wifoproc, wifi);
-	snprint(name, sizeof(name), "#l%dwifs", ether->ctlrno);
-	kproc(name, wifsproc, wifi);
-
-	return wifi;
-}
-
-static char *ciphers[] = {
-	[0]	"clear",
-	[TKIP]	"tkip",
-	[CCMP]	"ccmp",
-};
-
-static Wkey*
-parsekey(char *s)
-{
-	static char Ebadkey[] = "bad key";
-	uchar key[32];
-	int len, cipher;
-	char *e;
-	Wkey *k;
-
-	for(cipher=0; cipher<nelem(ciphers); cipher++){
-		if(strncmp(s, ciphers[cipher], len = strlen(ciphers[cipher])) == 0){
-			if(cipher == 0)	/* clear */
-				return nil;
-			if(s[len] == ':'){
-				s += len+1;
-				break;
-			}
-		}
-	}
-	if(cipher >= nelem(ciphers))
-		error(Ebadkey);
-
-	if((e = strchr(s, '@')) == nil)
-		e = strchr(s, 0);
-
-	len = dec16(key, sizeof(key), s, e - s);
-
-	switch(cipher){
-	case TKIP:
-		if(len != 32)
-			error(Ebadkey);
-		k = secalloc(sizeof(Wkey) + len);
-		memmove(k->key, key, len);
-		break;
-	case CCMP:
-		if(len != 16)
-			error(Ebadkey);
-		k = secalloc(sizeof(Wkey) + sizeof(AESstate));
-		setupAESstate((AESstate*)k->key, key, len, nil);
-		break;
-	default:
-		error(Ebadkey);
-		return nil;
-	}
-
-	memset(key, 0, sizeof(key));
-
-	if(*e++ == '@')
-		k->tsc = strtoull(e, nil, 16);
-	k->len = len;
-	k->cipher = cipher;
-
-	return k;
-}
-
-void
-wificfg(Wifi *wifi, char *opt)
-{
-	char *p, buf[64];
-	int n;
-
-	if(strncmp(opt, "debug=", 6))
-	if(strncmp(opt, "essid=", 6))
-	if(strncmp(opt, "bssid=", 6))
-		return;
-	if((p = strchr(opt, '=')) == nil)
-		return;
-	if(waserror())
-		return;
-	n = snprint(buf, sizeof(buf), "%.*s %q", (int)(p - opt), opt, p+1);
-	wifictl(wifi, buf, n);
-	poperror();
-}
-
-enum {
-	CMdebug,
-	CMessid,
-	CMauth,
-	CMbssid,
-	CMrxkey0,
-	CMrxkey1,
-	CMrxkey2,
-	CMrxkey3,
-	CMrxkey4,
-	CMtxkey0,
-};
-
-static Cmdtab wifictlmsg[] =
-{
-	CMdebug,	"debug",	0,
-	CMessid,	"essid",	0,
-	CMauth,		"auth",		0,
-	CMbssid,	"bssid",	0,
-
-	CMrxkey0,	"rxkey0",	0,	/* group keys */
-	CMrxkey1,	"rxkey1",	0,
-	CMrxkey2,	"rxkey2",	0,
-	CMrxkey3,	"rxkey3",	0,
-
-	CMrxkey4,	"rxkey",	0,	/* peerwise keys */
-	CMtxkey0,	"txkey",	0,
-
-	CMtxkey0,	"txkey0",	0,
-};
-
-long
-wifictl(Wifi *wifi, void *buf, long n)
-{
-	uchar addr[Eaddrlen];
-	Cmdbuf *cb;
-	Cmdtab *ct;
-	Wnode *wn;
-	Wkey *k, **kk;
-
-	cb = nil;
-	if(waserror()){
-		free(cb);
-		nexterror();
-	}
-	if(wifi->debug)
-		print("#l%d: wifictl: %.*s\n", wifi->ether->ctlrno, (int)n, buf);
-	memmove(addr, wifi->ether->bcast, Eaddrlen);
-	wn = wifi->bss;
-	cb = parsecmd(buf, n);
-	ct = lookupcmd(cb, wifictlmsg, nelem(wifictlmsg));
-	if(ct->index >= CMauth){
-		if(cb->nf > 1 && (ct->index == CMbssid || ct->index >= CMrxkey0)){
-			if(parseether(addr, cb->f[1]) == 0){
-				cb->f++;
-				cb->nf--;
-				wn = nodelookup(wifi, addr, 0);
-			}
-		}
-		if(wn == nil && ct->index != CMbssid)
-			error("missing node");
-	}
-	switch(ct->index){
-	case CMdebug:
-		if(cb->f[1] != nil)
-			wifi->debug = atoi(cb->f[1]);
-		else
-			wifi->debug ^= 1;
-		print("#l%d: debug: %d\n", wifi->ether->ctlrno, wifi->debug);
-		break;
-	case CMessid:
-		if(cb->f[1] != nil)
-			strncpy(wifi->essid, cb->f[1], Essidlen);
-		else
-			wifi->essid[0] = 0;
-	Findbss:
-		wn = wifi->bss;
-		if(wn != nil){
-			if(goodbss(wifi, wn))
-				break;
-			wifideauth(wifi, wn);
-		}
-		wifi->bss = nil;
-		if(wifi->essid[0] == 0 && memcmp(wifi->bssid, wifi->ether->bcast, Eaddrlen) == 0)
-			break;
-		for(wn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++)
-			if(goodbss(wifi, wn)){
-				setstatus(wifi, wn, Sconn);
-				sendauth(wifi, wn);
-			}
-		break;
-	case CMbssid:
-		memmove(wifi->bssid, addr, Eaddrlen);
-		goto Findbss;
-	case CMauth:
-		freewifikeys(wifi, wn);
-		if(cb->f[1] == nil)
-			wn->rsnelen = 0;
-		else
-			wn->rsnelen = dec16(wn->rsne, sizeof(wn->rsne), cb->f[1], strlen(cb->f[1]));
-		if(wn->aid == 0){
-			setstatus(wifi, wn, Sconn);
-			sendauth(wifi, wn);
-		} else {
-			setstatus(wifi, wn, Sauth);
-			sendassoc(wifi, wn);
-		}
-		break;
-	case CMrxkey0: case CMrxkey1: case CMrxkey2: case CMrxkey3: case CMrxkey4:
-	case CMtxkey0:
-		if(cb->f[1] == nil)
-			error(Ebadarg);
-		k = parsekey(cb->f[1]);
-		memset(cb->f[1], 0, strlen(cb->f[1]));
-		if(ct->index < CMtxkey0)
-			kk = &wn->rxkey[ct->index - CMrxkey0];
-		else
-			kk = &wn->txkey[ct->index - CMtxkey0];
-		wlock(&wifi->crypt);
-		secfree(*kk);
-		*kk = k;
-		wunlock(&wifi->crypt);
-		if(ct->index >= CMtxkey0 && wn->status == Sblocked)
-			setstatus(wifi, wn, Sassoc);
-		break;
-	}
-	poperror();
-	free(cb);
-	return n;
-}
-
-long
-wifistat(Wifi *wifi, void *buf, long n, ulong off)
-{
-	static uchar zeros[Eaddrlen];
-	char essid[Essidlen+1];
-	char *s, *p, *e;
-	Wnode *wn;
-	Wkey *k;
-	long now;
-	int i;
-
-	p = s = smalloc(4096);
-	e = s + 4096;
-
-	wn = wifi->bss;
-	if(wn != nil){
-		strncpy(essid, wn->ssid, Essidlen);
-		essid[Essidlen] = 0;
-		p = seprint(p, e, "essid: %s\n", essid);
-		p = seprint(p, e, "bssid: %E\n", wn->bssid);
-		p = seprint(p, e, "status: %s\n", wn->status);
-		p = seprint(p, e, "channel: %.2d\n", wn->channel);
-
-		/* only print key ciphers and key length */
-		rlock(&wifi->crypt);
-		for(i = 0; i<nelem(wn->rxkey); i++){
-			if((k = wn->rxkey[i]) != nil)
-				p = seprint(p, e, "rxkey%d: %s:[%d]\n", i,
-					ciphers[k->cipher], k->len);
-		}
-		for(i = 0; i<nelem(wn->txkey); i++){
-			if((k = wn->txkey[i]) != nil)
-				p = seprint(p, e, "txkey%d: %s:[%d]\n", i,
-					ciphers[k->cipher], k->len);
-		}
-		runlock(&wifi->crypt);
-
-		if(wn->brsnelen > 0){
-			p = seprint(p, e, "brsne: ");
-			for(i=0; i<wn->brsnelen; i++)
-				p = seprint(p, e, "%.2X", wn->brsne[i]);
-			p = seprint(p, e, "\n");
-		}
-	} else {
-		p = seprint(p, e, "essid: %s\n", wifi->essid);
-		p = seprint(p, e, "bssid: %E\n", wifi->bssid);
-	}
-
-	now = MACHP(0)->ticks;
-	for(wn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++){
-		if(wn->lastseen == 0)
-			continue;
-		strncpy(essid, wn->ssid, Essidlen);
-		essid[Essidlen] = 0;
-		p = seprint(p, e, "node: %E %.4x %-11ld %.2d %s\n",
-			wn->bssid, wn->cap, TK2MS(now - wn->lastseen), wn->channel, essid);
-	}
-	n = readstr(off, buf, n, s);
-	free(s);
-	return n;
-}
-
-static void tkipencrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc);
-static int tkipdecrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc);
-static void ccmpencrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc);
-static int ccmpdecrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc);
-
-static Block*
-wifiencrypt(Wifi *wifi, Wnode *wn, Block *b)
-{
-	uvlong tsc;
-	int n, kid;
-	Wifipkt *w;
-	Wkey *k;
-
-	rlock(&wifi->crypt);
-
-	kid = 0;
-	k = wn->txkey[kid];
-	if(k == nil){
-		runlock(&wifi->crypt);
-		return b;
-	}
-
-	n = wifihdrlen((Wifipkt*)b->rp);
-
-	b = padblock(b, 8);
-	b = padblock(b, -(8+4));
-
-	w = (Wifipkt*)b->rp;
-	memmove(w, b->rp+8, n);
-	b->rp += n;
-
-	tsc = ++k->tsc;
-
-	switch(k->cipher){
-	case TKIP:
-		b->rp[0] = tsc>>8;
-		b->rp[1] = (b->rp[0] | 0x20) & 0x7f;
-		b->rp[2] = tsc;
-		b->rp[3] = kid<<6 | 0x20;
-		b->rp[4] = tsc>>16;
-		b->rp[5] = tsc>>24;
-		b->rp[6] = tsc>>32;
-		b->rp[7] = tsc>>40;
-		b->rp += 8;
-		tkipencrypt(k, w, b, tsc);
-		break;
-	case CCMP:
-		b->rp[0] = tsc;
-		b->rp[1] = tsc>>8;
-		b->rp[2] = 0;
-		b->rp[3] = kid<<6 | 0x20;
-		b->rp[4] = tsc>>16;
-		b->rp[5] = tsc>>24;
-		b->rp[6] = tsc>>32;
-		b->rp[7] = tsc>>40;
-		b->rp += 8;
-		ccmpencrypt(k, w, b, tsc);
-		break;
-	}
-	runlock(&wifi->crypt);
-
-	b->rp = (uchar*)w;
-	w->fc[1] |= 0x40;
-	return b;
-}
-
-static Block*
-wifidecrypt(Wifi *wifi, Wnode *wn, Block *b)
-{
-	uvlong tsc;
-	int n, kid;
-	Wifipkt *w;
-	Wkey *k;
-
-	rlock(&wifi->crypt);
-
-	w = (Wifipkt*)b->rp;
-	n = wifihdrlen(w);
-	b->rp += n;
-	if(BLEN(b) < 8+8)
-		goto drop;
-
-	kid = b->rp[3]>>6;
-	if((b->rp[3] & 0x20) == 0)
-		goto drop;
-	if((w->a1[0] & 1) == 0)
-		kid = 4;	/* use peerwise key for non-unicast */
-
-	k = wn->rxkey[kid];
-	if(k == nil)
-		goto drop;
-	switch(k->cipher){
-	case TKIP:
-		tsc =	(uvlong)b->rp[7]<<40 |
-			(uvlong)b->rp[6]<<32 |
-			(uvlong)b->rp[5]<<24 |
-			(uvlong)b->rp[4]<<16 |
-			(uvlong)b->rp[0]<<8 |
-			(uvlong)b->rp[2];
-		b->rp += 8;
-		if(tsc <= k->tsc)
-			goto drop;
-		if(tkipdecrypt(k, w, b, tsc) != 0)
-			goto drop;
-		break;
-	case CCMP:
-		tsc =	(uvlong)b->rp[7]<<40 |
-			(uvlong)b->rp[6]<<32 |
-			(uvlong)b->rp[5]<<24 |
-			(uvlong)b->rp[4]<<16 |
-			(uvlong)b->rp[1]<<8 |
-			(uvlong)b->rp[0];
-		b->rp += 8;
-		if(tsc <= k->tsc)
-			goto drop;
-		if(ccmpdecrypt(k, w, b, tsc) != 0)
-			goto drop;
-		break;
-	default:
-	drop:
-		runlock(&wifi->crypt);
-		freeb(b);
-		return nil;
-	}
-	runlock(&wifi->crypt);
-
-	k->tsc = tsc;
-	b->rp -= n;
-	memmove(b->rp, w, n);
-	w = (Wifipkt*)b->rp;
-	w->fc[1] &= ~0x40;
-	return b;
-}
-
-static u16int Sbox[256] = {
-	0xC6A5, 0xF884, 0xEE99, 0xF68D, 0xFF0D, 0xD6BD, 0xDEB1, 0x9154,
-	0x6050, 0x0203, 0xCEA9, 0x567D, 0xE719, 0xB562, 0x4DE6, 0xEC9A,
-	0x8F45, 0x1F9D, 0x8940, 0xFA87, 0xEF15, 0xB2EB, 0x8EC9, 0xFB0B,
-	0x41EC, 0xB367, 0x5FFD, 0x45EA, 0x23BF, 0x53F7, 0xE496, 0x9B5B,
-	0x75C2, 0xE11C, 0x3DAE, 0x4C6A, 0x6C5A, 0x7E41, 0xF502, 0x834F,
-	0x685C, 0x51F4, 0xD134, 0xF908, 0xE293, 0xAB73, 0x6253, 0x2A3F,
-	0x080C, 0x9552, 0x4665, 0x9D5E, 0x3028, 0x37A1, 0x0A0F, 0x2FB5,
-	0x0E09, 0x2436, 0x1B9B, 0xDF3D, 0xCD26, 0x4E69, 0x7FCD, 0xEA9F,
-	0x121B, 0x1D9E, 0x5874, 0x342E, 0x362D, 0xDCB2, 0xB4EE, 0x5BFB,
-	0xA4F6, 0x764D, 0xB761, 0x7DCE, 0x527B, 0xDD3E, 0x5E71, 0x1397,
-	0xA6F5, 0xB968, 0x0000, 0xC12C, 0x4060, 0xE31F, 0x79C8, 0xB6ED,
-	0xD4BE, 0x8D46, 0x67D9, 0x724B, 0x94DE, 0x98D4, 0xB0E8, 0x854A,
-	0xBB6B, 0xC52A, 0x4FE5, 0xED16, 0x86C5, 0x9AD7, 0x6655, 0x1194,
-	0x8ACF, 0xE910, 0x0406, 0xFE81, 0xA0F0, 0x7844, 0x25BA, 0x4BE3,
-	0xA2F3, 0x5DFE, 0x80C0, 0x058A, 0x3FAD, 0x21BC, 0x7048, 0xF104,
-	0x63DF, 0x77C1, 0xAF75, 0x4263, 0x2030, 0xE51A, 0xFD0E, 0xBF6D,
-	0x814C, 0x1814, 0x2635, 0xC32F, 0xBEE1, 0x35A2, 0x88CC, 0x2E39,
-	0x9357, 0x55F2, 0xFC82, 0x7A47, 0xC8AC, 0xBAE7, 0x322B, 0xE695,
-	0xC0A0, 0x1998, 0x9ED1, 0xA37F, 0x4466, 0x547E, 0x3BAB, 0x0B83,
-	0x8CCA, 0xC729, 0x6BD3, 0x283C, 0xA779, 0xBCE2, 0x161D, 0xAD76,
-	0xDB3B, 0x6456, 0x744E, 0x141E, 0x92DB, 0x0C0A, 0x486C, 0xB8E4,
-	0x9F5D, 0xBD6E, 0x43EF, 0xC4A6, 0x39A8, 0x31A4, 0xD337, 0xF28B,
-	0xD532, 0x8B43, 0x6E59, 0xDAB7, 0x018C, 0xB164, 0x9CD2, 0x49E0,
-	0xD8B4, 0xACFA, 0xF307, 0xCF25, 0xCAAF, 0xF48E, 0x47E9, 0x1018,
-	0x6FD5, 0xF088, 0x4A6F, 0x5C72, 0x3824, 0x57F1, 0x73C7, 0x9751,
-	0xCB23, 0xA17C, 0xE89C, 0x3E21, 0x96DD, 0x61DC, 0x0D86, 0x0F85,
-	0xE090, 0x7C42, 0x71C4, 0xCCAA, 0x90D8, 0x0605, 0xF701, 0x1C12,
-	0xC2A3, 0x6A5F, 0xAEF9, 0x69D0, 0x1791, 0x9958, 0x3A27, 0x27B9,
-	0xD938, 0xEB13, 0x2BB3, 0x2233, 0xD2BB, 0xA970, 0x0789, 0x33A7,
-	0x2DB6, 0x3C22, 0x1592, 0xC920, 0x8749, 0xAAFF, 0x5078, 0xA57A,
-	0x038F, 0x59F8, 0x0980, 0x1A17, 0x65DA, 0xD731, 0x84C6, 0xD0B8,
-	0x82C3, 0x29B0, 0x5A77, 0x1E11, 0x7BCB, 0xA8FC, 0x6DD6, 0x2C3A
-};
-
-static void
-tkipk2tk(uchar key[16], u16int tk[8])
-{
-	tk[0] = (u16int)key[1]<<8 | key[0];
-	tk[1] = (u16int)key[3]<<8 | key[2];
-	tk[2] = (u16int)key[5]<<8 | key[4];
-	tk[3] = (u16int)key[7]<<8 | key[6];
-	tk[4] = (u16int)key[9]<<8 | key[8];
-	tk[5] = (u16int)key[11]<<8 | key[10];
-	tk[6] = (u16int)key[13]<<8 | key[12];
-	tk[7] = (u16int)key[15]<<8 | key[14];
-}
-
-static void
-tkipphase1(u32int tscu, uchar ta[Eaddrlen], u16int tk[8], u16int p1k[5])
-{
-	u16int *k, i, x0, x1, x2;
-
-	p1k[0] = tscu;
-	p1k[1] = tscu>>16;
-	p1k[2] = (u16int)ta[1]<<8 | ta[0];
-	p1k[3] = (u16int)ta[3]<<8 | ta[2];
-	p1k[4] = (u16int)ta[5]<<8 | ta[4];
-
-	for(i=0; i<8; i++){
-		k = &tk[i & 1];
-
-		x0 = p1k[4] ^ k[0];
-		x1 = Sbox[x0 >> 8];
-		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
-		p1k[0] += x2;
-		x0 = p1k[0] ^ k[2];
-		x1 = Sbox[x0 >> 8];
-		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
-		p1k[1] += x2;
-		x0 = p1k[1] ^ k[4];
-		x1 = Sbox[x0 >> 8];
-		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
-		p1k[2] += x2;
-		x0 = p1k[2] ^ k[6];
-		x1 = Sbox[x0 >> 8];
-		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
-		p1k[3] += x2;
-		x0 = p1k[3] ^ k[0];
-		x1 = Sbox[x0 >> 8];
-		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
-		p1k[4] += x2;
-
-		p1k[4] += i;
-	}
-}
-
-static void
-tkipphase2(u16int tscl, u16int p1k[5], u16int tk[8], uchar rc4key[16])
-{
-	u16int ppk[6], x0, x1, x2;
-
-	ppk[0] = p1k[0];
-	ppk[1] = p1k[1];
-	ppk[2] = p1k[2];
-	ppk[3] = p1k[3];
-	ppk[4] = p1k[4];
-	ppk[5] = p1k[4] + tscl;
-
-	x0 = ppk[5] ^ tk[0];
-	x1 = Sbox[x0 >> 8];
-	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
-	ppk[0] += x2;
-	x0 = ppk[0] ^ tk[1];
-	x1 = Sbox[x0 >> 8];
-	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
-	ppk[1] += x2;
-	x0 = ppk[1] ^ tk[2];
-	x1 = Sbox[x0 >> 8];
-	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
-	ppk[2] += x2;
-	x0 = ppk[2] ^ tk[3];
-	x1 = Sbox[x0 >> 8];
-	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
-	ppk[3] += x2;
-	x0 = ppk[3] ^ tk[4];
-	x1 = Sbox[x0 >> 8];
-	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
-	ppk[4] += x2;
-	x0 = ppk[4] ^ tk[5];
-	x1 = Sbox[x0 >> 8];
-	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
-	ppk[5] += x2;
-
-	x2 = ppk[5] ^ tk[6];
-	ppk[0] += (x2 >> 1) | (x2 << 15);
-	x2 = ppk[0] ^ tk[7];
-	ppk[1] += (x2 >> 1) | (x2 << 15);
-
-	x2 = ppk[1];
-	ppk[2] += (x2 >> 1) | (x2 << 15);
-	x2 = ppk[2];
-	ppk[3] += (x2 >> 1) | (x2 << 15);
-	x2 = ppk[3];
-	ppk[4] += (x2 >> 1) | (x2 << 15);
-	x2 = ppk[4];
-	ppk[5] += (x2 >> 1) | (x2 << 15);
-
-	rc4key[0] = tscl >> 8;
-	rc4key[1] = (rc4key[0] | 0x20) & 0x7F;
-	rc4key[2] = tscl;
-	rc4key[3] = (ppk[5] ^ tk[0]) >> 1;
-	rc4key[4]  = ppk[0];
-	rc4key[5]  = ppk[0] >> 8;
-	rc4key[6]  = ppk[1];
-	rc4key[7]  = ppk[1] >> 8;
-	rc4key[8]  = ppk[2];
-	rc4key[9]  = ppk[2] >> 8;
-	rc4key[10] = ppk[3];
-	rc4key[11] = ppk[3] >> 8;
-	rc4key[12] = ppk[4];
-	rc4key[13] = ppk[4] >> 8;
-	rc4key[14] = ppk[5];
-	rc4key[15] = ppk[5] >> 8;
-}
-
-typedef struct MICstate MICstate;
-struct MICstate
-{
-	u32int	l;
-	u32int	r;
-	u32int	m;
-	u32int	n;
-};
-
-static void
-micsetup(MICstate *s, uchar key[8])
-{
-	s->l =	(u32int)key[0] |
-		(u32int)key[1]<<8 |
-		(u32int)key[2]<<16 |
-		(u32int)key[3]<<24;
-	s->r =	(u32int)key[4] |
-		(u32int)key[5]<<8 |
-		(u32int)key[6]<<16 |
-		(u32int)key[7]<<24;
-	s->m = 0;
-	s->n = 0;
-}
-
-static void
-micupdate(MICstate *s, uchar *data, ulong len)
-{
-	u32int l, r, m, n, e;
-
-	l = s->l;
-	r = s->r;
-	m = s->m;
-	n = s->n;
-	e = n + len;
-	while(n != e){
-		m >>= 8;
-		m |= (u32int)*data++ << 24;
-		if(++n & 3)
-			continue;
-		l ^= m;
-		r ^= (l << 17) | (l >> 15);
-		l += r;
-		r ^= ((l & 0x00FF00FFUL)<<8) | ((l & 0xFF00FF00UL)>>8);
-		l += r;
-		r ^= (l << 3) | (l >> 29);
-		l += r;
-		r ^= (l >> 2) | (l << 30);
-		l += r;
-	}
-	s->l = l;
-	s->r = r;
-	s->m = m;
-	s->n = n;
-}
-
-static void
-micfinish(MICstate *s, uchar mic[8])
-{
-	static uchar pad[8] = { 0x5a, 0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, };
-
-	micupdate(s, pad, sizeof(pad));
-
-	mic[0] = s->l;
-	mic[1] = s->l>>8;
-	mic[2] = s->l>>16;
-	mic[3] = s->l>>24;
-	mic[4] = s->r;
-	mic[5] = s->r>>8;
-	mic[6] = s->r>>16;
-	mic[7] = s->r>>24;
-}
-
-static uchar pad4[4] = { 0x00, 0x00, 0x00, 0x00, };
-
-static void
-tkipencrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc)
-{
-	u16int tk[8], p1k[5];
-	uchar seed[16];
-	RC4state rs;
-	MICstate ms;
-	ulong crc;
-
-	micsetup(&ms, k->key+24);
-	micupdate(&ms, dstaddr(w), Eaddrlen);
-	micupdate(&ms, srcaddr(w), Eaddrlen);
-	micupdate(&ms, pad4, 4);
-	micupdate(&ms, b->rp, BLEN(b));
-	micfinish(&ms, b->wp);
-	b->wp += 8;
-
-	crc = ethercrc(b->rp, BLEN(b));
-	crc = ~crc;
-	b->wp[0] = crc;
-	b->wp[1] = crc>>8;
-	b->wp[2] = crc>>16;
-	b->wp[3] = crc>>24;
-	b->wp += 4;
-
-	tkipk2tk(k->key, tk);
-	tkipphase1(tsc >> 16, w->a2, tk, p1k);
-	tkipphase2(tsc & 0xFFFF, p1k, tk, seed);
-	setupRC4state(&rs, seed, sizeof(seed));
-	rc4(&rs, b->rp, BLEN(b));
-}
-
-static int
-tkipdecrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc)
-{
-	uchar seed[16], mic[8];
-	u16int tk[8], p1k[5];
-	RC4state rs;
-	MICstate ms;
-	ulong crc;
-
-	if(BLEN(b) < 8+4)
-		return -1;
-
-	tkipk2tk(k->key, tk);
-	tkipphase1(tsc >> 16, w->a2, tk, p1k);
-	tkipphase2(tsc & 0xFFFF, p1k, tk, seed);
-	setupRC4state(&rs, seed, sizeof(seed));
-	rc4(&rs, b->rp, BLEN(b));
-
-	b->wp -= 4;
-	crc =	(ulong)b->wp[0] |
-		(ulong)b->wp[1]<<8 |
-		(ulong)b->wp[2]<<16 |
-		(ulong)b->wp[3]<<24;
-	crc = ~crc;
-	crc ^= ethercrc(b->rp, BLEN(b));
-
-	b->wp -= 8;
-	micsetup(&ms, k->key+16);
-	micupdate(&ms, dstaddr(w), Eaddrlen);
-	micupdate(&ms, srcaddr(w), Eaddrlen);
-	micupdate(&ms, pad4, 4);
-	micupdate(&ms, b->rp, BLEN(b));
-	micfinish(&ms, mic);
-
-	return tsmemcmp(b->wp, mic, 8) | crc;
-}
-
-static uchar*
-putbe(uchar *p, int L, uint v)
-{
-	while(--L >= 0)
-		*p++ = (v >> L*8) & 0xFF;
-	return p;
-}
-
-static void
-xblock(int L, int M, uchar *N, uchar *a, int la, int lm, uchar t[16], AESstate *s)
-{
-	uchar l[8], *p, *x, *e;
-
-	assert(M >= 4 && M <= 16);
-	assert(L >= 2 && L <= 4);
-
-	t[0] = ((la > 0)<<6) | ((M-2)/2)<<3 | (L-1);	/* flags */
-	memmove(&t[1], N, 15-L);
-	putbe(&t[16-L], L, lm);
-	aes_encrypt(s->ekey, s->rounds, t, t);
-	
-	if(la > 0){
-		assert(la < 0xFF00);
-		for(p = l, e = putbe(l, 2, la), x = t; p < e; x++, p++)
-			*x ^= *p;
-		for(e = a + la; a < e; x = t){
-			for(; a < e && x < &t[16]; x++, a++)
-				*x ^= *a;
-			aes_encrypt(s->ekey, s->rounds, t, t);
-		}
-	}
-}
-
-static uchar*
-sblock(int L, uchar *N, uint i, uchar b[16], AESstate *s)
-{
-	b[0] = L-1;	/* flags */
-	memmove(&b[1], N, 15-L);
-	putbe(&b[16-L], L, i);
-	aes_encrypt(s->ekey, s->rounds, b, b);
-	return b;
-};
-
-static void
-aesCCMencrypt(int L, int M, uchar *N /* N[15-L] */,
-	uchar *a /* a[la] */, int la,
-	uchar *m /* m[lm+M] */, int lm,
-	AESstate *s)
-{
-	uchar t[16], b[16], *p, *x;
-	uint i;
-
-	xblock(L, M, N, a, la, lm, t, s);
-
-	for(i = 1; lm >= 16; i++, m += 16, lm -= 16){
-		sblock(L, N, i, b, s);
-
-		*((u32int*)&t[0]) ^= *((u32int*)&m[0]);
-		*((u32int*)&m[0]) ^= *((u32int*)&b[0]);
-		*((u32int*)&t[4]) ^= *((u32int*)&m[4]);
-		*((u32int*)&m[4]) ^= *((u32int*)&b[4]);
-		*((u32int*)&t[8]) ^= *((u32int*)&m[8]);
-		*((u32int*)&m[8]) ^= *((u32int*)&b[8]);
-		*((u32int*)&t[12]) ^= *((u32int*)&m[12]);
-		*((u32int*)&m[12]) ^= *((u32int*)&b[12]);
-
-		aes_encrypt(s->ekey, s->rounds, t, t);
-	}
-	if(lm > 0){
-		for(p = sblock(L, N, i, b, s), x = t; p < &b[lm]; x++, m++, p++){
-			*x ^= *m;
-			*m ^= *p;
-		}
-		aes_encrypt(s->ekey, s->rounds, t, t);
-	}
-
-	for(p = sblock(L, N, 0, b, s), x = t; p < &b[M]; x++, p++)
-		*x ^= *p;
-
-	memmove(m, t, M);
-}
-
-static int
-aesCCMdecrypt(int L, int M, uchar *N /* N[15-L] */,
-	uchar *a /* a[la] */, int la,
-	uchar *m /* m[lm+M] */, int lm,
-	AESstate *s)
-{
-	uchar t[16], b[16], *p, *x;
-	uint i;
-
-	xblock(L, M, N, a, la, lm, t, s);
-
-	for(i = 1; lm >= 16; i++, m += 16, lm -= 16){
-		sblock(L, N, i, b, s);
-
-		*((u32int*)&m[0]) ^= *((u32int*)&b[0]);
-		*((u32int*)&t[0]) ^= *((u32int*)&m[0]);
-		*((u32int*)&m[4]) ^= *((u32int*)&b[4]);
-		*((u32int*)&t[4]) ^= *((u32int*)&m[4]);
-		*((u32int*)&m[8]) ^= *((u32int*)&b[8]);
-		*((u32int*)&t[8]) ^= *((u32int*)&m[8]);
-		*((u32int*)&m[12]) ^= *((u32int*)&b[12]);
-		*((u32int*)&t[12]) ^= *((u32int*)&m[12]);
-
-		aes_encrypt(s->ekey, s->rounds, t, t);
-	}
-	if(lm > 0){
-		for(p = sblock(L, N, i, b, s), x = t; p < &b[lm]; x++, m++, p++){
-			*m ^= *p;
-			*x ^= *m;
-		}
-		aes_encrypt(s->ekey, s->rounds, t, t);
-	}
-
-	for(p = sblock(L, N, 0, b, s), x = t; p < &b[M]; x++, p++)
-		*x ^= *p;
-
-	return tsmemcmp(m, t, M);
-}
-
-static int
-setupCCMP(Wifipkt *w, uvlong tsc, uchar nonce[13], uchar auth[32])
-{
-	uchar *p;
-
-	nonce[0] = ((w->fc[0] & 0x0c) == 0x00) << 4;
-	memmove(&nonce[1], w->a2, Eaddrlen);
-	nonce[7]  = tsc >> 40;
-	nonce[8]  = tsc >> 32;
-	nonce[9]  = tsc >> 24;
-	nonce[10] = tsc >> 16;
-	nonce[11] = tsc >> 8;
-	nonce[12] = tsc;
-
-	p = auth;
-	*p++ = (w->fc[0] & (((w->fc[0] & 0x0c) == 0x08) ? 0x0f : 0xff));
-	*p++ = (w->fc[1] & ~0x38) | 0x40;
-	memmove(p, w->a1, Eaddrlen); p += Eaddrlen;
-	memmove(p, w->a2, Eaddrlen); p += Eaddrlen;
-	memmove(p, w->a3, Eaddrlen); p += Eaddrlen;
-	*p++ = w->seq[0] & 0x0f;
-	*p++ = 0;
-	if((w->fc[1] & 3) == 0x03) {
-		memmove(p, w->a4, Eaddrlen);
-		p += Eaddrlen;
-	}
-
-	return p - auth;
-}
-
-static void
-ccmpencrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc)
-{
-	uchar auth[32], nonce[13];
-
-	aesCCMencrypt(2, 8, nonce, auth,
-		setupCCMP(w, tsc, nonce, auth),
-		b->rp, BLEN(b), (AESstate*)k->key);
-	b->wp += 8;
-}
-
-static int
-ccmpdecrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc)
-{
-	uchar auth[32], nonce[13];
-
-	if(BLEN(b) < 8)
-		return -1;
-
-	b->wp -= 8;
-	return aesCCMdecrypt(2, 8, nonce, auth,
-		setupCCMP(w, tsc, nonce, auth),
-		b->rp, BLEN(b), (AESstate*)k->key);
-}
-
-/*
- * Dynamic Mac Address Translation (DMAT)
- *
- * Wifi does not allow spoofing of the source mac which breaks
- * bridging. To solve this we proxy mac addresses, maintaining
- * a translation table from ip address to destination mac address.
- * Upstream ARP and NDP packets get ther source mac address changed
- * to proxy and a translation entry is added with the original mac
- * for downstream translation. The proxy does not appear in the
- * table.
- */
-#include "../ip/ip.h"
-#include "../ip/ipv6.h"
-
-static void
-dmatproxy(Block *bp, int upstream, uchar proxy[Eaddrlen], DMAT *t)
-{
-	static uchar arp4[] = {
-		0x00, 0x01,
-		0x08, 0x00,
-		0x06, 0x04,
-		0x00,
-	};
-	uchar ip[IPaddrlen], mac[Eaddrlen], *targ, *end, *a, *o;
-	ulong csum, c, h;
-	Etherpkt *pkt;
-	int proto, i;
-	DMTE *te;
-
-	end = bp->wp;
-	pkt = (Etherpkt*)bp->rp;
-	a = pkt->data;
-	if(a >= end)
-		return;
-
-	if(upstream)
-		memmove(pkt->s, proxy, Eaddrlen);
-	else if(t->map == 0 || (pkt->d[0]&1) != 0 || memcmp(pkt->d, proxy, Eaddrlen) != 0)
-		return;
-
-	targ = nil;
-	switch(pkt->type[0]<<8 | pkt->type[1]){
-	default:
-		return;
-	case ETIP4:
-	case ETIP6:
-		switch(a[0]&0xF0){
-		default:
-			return;
-		case IP_VER4:
-			if(a+IP4HDR > end || (a[0]&15) < IP_HLEN4)
-				return;
-			v4tov6(ip, a+12+4*(upstream==0));
-			proto = a[9];
-			a += (a[0]&15)*4;
-			break;
-		case IP_VER6:
-			if(a+IP6HDR > end)
-				return;
-			memmove(ip, a+8+16*(upstream==0), 16);
-			proto = a[6];
-			a += IP6HDR;
-			break;
-		}
-		if(!upstream)
-			break;
-		switch(proto){
-		case ICMPv6:
-			if(a+8 > end)
-				return;
-			switch(a[0]){
-			default:
-				return;
-			case 133:	/* Router Solicitation */
-				o = a+8;
-				break;
-			case 134:	/* Router Advertisement */
-				o = a+8+8;
-				break;
-			case 136:	/* Neighbor Advertisement */
-				targ = a+8;
-				/* wet floor */
-			case 135:	/* Neighbor Solicitation */
-				o = a+8+16;
-				break;
-			case 137:	/* Redirect */
-				o = a+8+16+16;
-				break;
-			}
-			memset(mac, 0xFF, Eaddrlen);
-			csum = (a[2]<<8 | a[3])^0xFFFF;
-			while(o+8 <= end && o[1] != 0){
-				switch(o[0]){
-				case SRC_LLADDR:
-				case TARGET_LLADDR:
-					for(i=0; i<Eaddrlen; i += 2)
-						csum += (o[2+i]<<8 | o[3+i])^0xFFFF;
-					memmove(mac, o+2, Eaddrlen);
-					memmove(o+2, proxy, Eaddrlen);
-					for(i=0; i<Eaddrlen; i += 2)
-						csum += (o[2+i]<<8 | o[3+i]);
-					break;
-				}
-				o += o[1]*8;
-			}
-			while((c = csum >> 16) != 0)
-				csum = (csum & 0xFFFF) + c;
-			csum ^= 0xFFFF;
-			a[2] = csum>>8;
-			a[3] = csum;
-			break;
-		case UDP:	/* for BOOTP */
-			if(a+42 > end
-			|| (a[0]<<8 | a[1]) != 68
-			|| (a[2]<<8 | a[3]) != 67
-			|| a[8] != 1
-			|| a[9] != 1
-			|| a[10] != Eaddrlen
-			|| (a[18]&0x80) != 0
-			|| memcmp(a+36, proxy, Eaddrlen) == 0)
-				return;
-
-			csum = (a[6]<<8 | a[7])^0xFFFF;
-
-			/* set the broadcast flag so response reaches us */
-			csum += (a[18]<<8)^0xFFFF;
-			a[18] |= 0x80;
-			csum += (a[18]<<8);
-
-			while((c = csum >> 16) != 0)
-				csum = (csum & 0xFFFF) + c;
-			csum ^= 0xFFFF;
-
-			a[6] = csum>>8;
-			a[7] = csum;
-		default:
-			return;
-		}
-		break;
-	case ETARP:
-		if(a+26 > end || memcmp(a, arp4, sizeof(arp4)) != 0 || (a[7] != 1 && a[7] != 2))
-			return;
-		v4tov6(ip, a+14+10*(upstream==0));
-		if(upstream){
-			memmove(mac, a+8, Eaddrlen);
-			memmove(a+8, proxy, Eaddrlen);
-		}
-		break;
-	}
-
-Again:
-	h = (	(ip[IPaddrlen-1] ^ proxy[2])<<24 |
-		(ip[IPaddrlen-2] ^ proxy[3])<<16 |
-		(ip[IPaddrlen-3] ^ proxy[4])<<8  |
-		(ip[IPaddrlen-4] ^ proxy[5]) ) % nelem(t->tab);
-	te = &t->tab[h];
-	h &= 63;
-
-	if(upstream){
-		if((mac[0]&1) != 0 || memcmp(mac, proxy, Eaddrlen) == 0)
-			return;
-		for(i=0; te->valid && i<nelem(t->tab); i++){
-			if(memcmp(te->ip, ip, IPaddrlen) == 0)
-				break;
-			if(++te >= &t->tab[nelem(t->tab)])
-				te = t->tab;
-		}
-		memmove(te->mac, mac, Eaddrlen);
-		memmove(te->ip, ip, IPaddrlen);
-		te->valid = 1;
-		t->map |= 1ULL<<h;
-		if(targ != nil){
-			memmove(ip, targ, IPaddrlen);
-			targ = nil;
-			goto Again;
-		}
-	} else {
-		if((t->map>>h & 1) == 0)
-			return;
-		for(i=0; te->valid && i<nelem(t->tab); i++){
-			if(memcmp(te->ip, ip, IPaddrlen) == 0){
-				memmove(pkt->d, te->mac, Eaddrlen);
-				return;
-			}
-			if(++te >= &t->tab[nelem(t->tab)])
-				te = t->tab;
-		}
-	}
-}
--- a/sys/src/9/pc/wifi.h
+++ /dev/null
@@ -1,116 +1,0 @@
-typedef struct Wkey Wkey;
-typedef struct Wnode Wnode;
-typedef struct Wifi Wifi;
-typedef struct Wifipkt Wifipkt;
-typedef struct DMAT DMAT;
-typedef struct DMTE DMTE;
-
-enum {
-	Essidlen = 32,
-};
-
-/* cipher */
-enum {
-	TKIP	= 1,
-	CCMP	= 2,
-};
-
-struct Wkey
-{
-	int		cipher;
-	int		len;
-	uvlong		tsc;
-	uchar		key[];
-};
-
-struct Wnode
-{
-	uchar	bssid[Eaddrlen];
-	char	ssid[Essidlen+2];
-
-	char	*status;
-
-	int	rsnelen;
-	uchar	rsne[258];
-	Wkey	*txkey[1];
-	Wkey	*rxkey[5];
-
-	int	aid;		/* association id */
-	ulong	lastsend;
-	ulong	lastseen;
-
-	uchar	*minrate;	/* pointers into wifi->rates */
-	uchar	*maxrate;
-	uchar	*actrate;
-
-	ulong	txcount;	/* statistics for rate adaption */
-	ulong	txerror;
-
-	/* stuff from beacon */
-	int	ival;
-	int	cap;
-	int	channel;
-	int	brsnelen;
-	uchar	brsne[258];
-};
-
-struct DMTE
-{
-	uchar	ip[16];
-	uchar	mac[6];
-	uchar	valid;
-};
-
-struct DMAT
-{
-	DMTE	tab[127];	/* prime */
-	uvlong	map;
-};
-
-struct Wifi
-{
-	Ether	*ether;
-
-	int	debug;
-
-	RWlock	crypt;
-	Queue	*iq;
-	ulong	watchdog;
-	ulong	lastauth;
-	Ref	txseq;
-	void	(*transmit)(Wifi*, Wnode*, Block*);
-
-	/* for searching */
-	uchar	bssid[Eaddrlen];
-	char	essid[Essidlen+2];
-
-	/* supported data rates by hardware */
-	uchar	*rates;
-
-	/* effective base station */
-	Wnode	*bss;
-
-	Wnode	node[32];
-
-	DMAT	dmat;
-};
-
-struct Wifipkt
-{
-	uchar	fc[2];
-	uchar	dur[2];
-	uchar	a1[Eaddrlen];
-	uchar	a2[Eaddrlen];
-	uchar	a3[Eaddrlen];
-	uchar	seq[2];
-	uchar	a4[Eaddrlen];
-};
-
-Wifi *wifiattach(Ether *ether, void (*transmit)(Wifi*, Wnode*, Block*));
-void wifiiq(Wifi*, Block*);
-int wifihdrlen(Wifipkt*);
-void wifitxfail(Wifi*, Block*);
-
-long wifistat(Wifi*, void*, long, ulong);
-long wifictl(Wifi*, void*, long);
-void wificfg(Wifi*, char*);
--- a/sys/src/9/pc64/mkfile
+++ b/sys/src/9/pc64/mkfile
@@ -83,7 +83,7 @@
 
 
 # copies generated by the rule below
-PCHEADERS=wifi.h usbehci.h screen.h etherif.h ethermii.h mp.h io.h ahci.h \
+PCHEADERS=usbehci.h screen.h ethermii.h mp.h io.h ahci.h \
 	yukdump.h
 
 REPCH=`{echo $PCHEADERS | sed 's/\.h//g; s/ /|/g'}
@@ -100,7 +100,7 @@
 
 l.$O apbootstrap.$O:		mem.h
 
-$ETHER: 			etherif.h ../port/netif.h
+$ETHER: 			../port/etherif.h ../port/netif.h
 $AUDIO:				../port/audioif.h
 ether8003.$O ether8390.$O:	ether8390.h
 etheryuk.$O:			yukdump.h
@@ -122,11 +122,9 @@
 
 trap.$O:			/sys/include/tos.h
 ethermii.$O:			ethermii.h
-etheriwl.$O:			wifi.h
-etherwpi.$O:			wifi.h
-etherrt2860.$O: 		wifi.h
-wifi.$O:			wifi.h etherif.h ../port/netif.h /sys/include/libsec.h
-wifi.$O:			../ip/ip.h ../ip/ipv6.h
+etheriwl.$O:			../port/wifi.h
+etherwpi.$O:			../port/wifi.h
+etherrt2860.$O: 		../port/wifi.h
 
 init.h:D:		../port/initcode.c ../pc/init9.c
 	$CC ../port/initcode.c
--- a/sys/src/9/pc64/trap.c
+++ b/sys/src/9/pc64/trap.c
@@ -47,6 +47,15 @@
 		irq = -1;
 	}
 
+
+	/*
+	 * IRQ2 doesn't really exist, it's used to gang the interrupt
+	 * controllers together. A device set to IRQ2 will appear on
+	 * the second interrupt controller as IRQ9.
+	 */
+	if(irq == 2)
+		irq = 9;
+
 	if((v = xalloc(sizeof(Vctl))) == nil)
 		panic("intrenable: out of memory");
 	v->isintr = 1;
@@ -83,6 +92,8 @@
 	Vctl **pv, *v;
 	int vno;
 
+	if(irq == 2)
+		irq = 9;
 	if(arch->intrvecno == nil || (tbdf != BUSUNKNOWN && (irq == 0xff || irq == 0))){
 		/*
 		 * on APIC machine, irq is pretty meaningless
--- a/sys/src/9/port/devaoe.c
+++ b/sys/src/9/port/devaoe.c
@@ -12,7 +12,7 @@
 #include "ureg.h"
 #include "../port/error.h"
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 #include "../ip/ip.h"
 #include "../port/aoe.h"
 #include <fis.h>
--- /dev/null
+++ b/sys/src/9/port/devether.c
@@ -1,0 +1,653 @@
+#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 "../port/etherif.h"
+
+extern int eipfmt(Fmt*);
+
+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 loopback or bridged packets */
+			if(f->bridge && (tome || !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;
+					xbp->flag = bp->flag;
+					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);
+	if(!ether->f[NETID(chan->qid.path)]->bridge)
+		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++;
+}
+
+static Ether*
+etherprobe(int cardno, int ctlrno)
+{
+	int i, lg;
+	ulong mb, bsz;
+	Ether *ether;
+
+	ether = malloc(sizeof(Ether));
+	if(ether == nil){
+		print("etherprobe: no memory for Ether\n");
+		return nil;
+	}
+	memset(ether, 0, sizeof(Ether));
+	ether->tbdf = BUSUNKNOWN;
+	ether->irq = -1;
+	ether->ctlrno = ctlrno;
+	ether->mbps = 10;
+	ether->minmtu = ETHERMINTU;
+	ether->maxmtu = ETHERMAXTU;
+
+	if(cardno < 0){
+		if(isaconfig("ether", ctlrno, ether) == 0){
+			free(ether);
+			return nil;
+		}
+		for(cardno = 0; cards[cardno].type; cardno++){
+			if(cistrcmp(cards[cardno].type, ether->type))
+				continue;
+			for(i = 0; i < ether->nopt; i++){
+				if(strncmp(ether->opt[i], "ea=", 3))
+					continue;
+				if(parseether(ether->ea, &ether->opt[i][3]))
+					memset(ether->ea, 0, Eaddrlen);
+			}
+			break;
+		}
+	}
+
+	if(cardno >= MaxEther || cards[cardno].type == nil){
+		free(ether);
+		return nil;
+	}
+	snprint(ether->name, sizeof(ether->name), "ether%d", ctlrno);
+	if(cards[cardno].reset(ether) < 0){
+		free(ether);
+		return nil;
+	}
+
+	print("#l%d: %s: %dMbps port 0x%luX irq %d ea %E\n",
+		ctlrno, cards[cardno].type,
+		ether->mbps, ether->port, ether->irq, ether->ea);
+
+	/* 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, 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", ether->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;
+
+	fmtinstall('E', eipfmt);
+
+	for(ctlrno = 0; ctlrno < MaxEther; ctlrno++){
+		if((ether = etherprobe(-1, ctlrno)) == nil)
+			continue;
+		etherxx[ctlrno] = ether;
+	}
+
+	if(getconf("*noetherprobe"))
+		return;
+
+	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,
+};
+
+enum { PktHdr = 42 };
+typedef struct Netconsole Netconsole;
+struct Netconsole {
+	char buf[512];
+	int n;
+	Lock;
+	Ether *ether;
+};
+static Netconsole *netcons;
+
+extern ushort ipcsum(uchar *);
+
+void
+netconsputc(Uart *, int c)
+{
+	char *p;
+	u16int cs;
+
+	ilock(netcons);
+	netcons->buf[netcons->n++] = c;
+	if(c != '\n' && netcons->n < sizeof(netcons->buf)){
+		iunlock(netcons);
+		return;
+	}
+	p = netcons->buf;
+	p[16] = netcons->n - 14 >> 8;
+	p[17] = netcons->n - 14;
+	p[24] = 0;
+	p[25] = 0;
+	cs = ipcsum((uchar*) p + 14);
+	p[24] = cs >> 8;
+	p[25] = cs;
+	p[38] = netcons->n - 34 >> 8;
+	p[39] = netcons->n - 34;
+	memmove(p+Eaddrlen, netcons->ether->ea, Eaddrlen);
+	qiwrite(netcons->ether->oq, p, netcons->n);
+	netcons->n = PktHdr;
+	iunlock(netcons);
+	if(netcons->ether->transmit != nil)
+		netcons->ether->transmit(netcons->ether);
+}
+
+PhysUart netconsphys = {
+	.putc = netconsputc,
+};
+Uart netconsuart = { .phys = &netconsphys };
+
+void
+netconsole(void)
+{
+	char *p;
+	char *r;
+	int i;
+	int srcport, devno, dstport;
+	u8int srcip[4], dstip[4];
+	u64int dstmac;
+	Netconsole *nc;
+
+	if((p = getconf("console")) == nil || strncmp(p, "net ", 4) != 0)
+		return;
+	p += 4;
+	for(i = 0; i < 4; i++){
+		srcip[i] = strtol(p, &r, 0);
+		p = r + 1;
+		if(i == 3) break;
+		if(*r != '.') goto err;
+	}
+	if(*r == '!'){
+		srcport = strtol(p, &r, 0);
+		p = r + 1;
+	}else
+		srcport = 6665;
+	if(*r == '/'){
+		devno = strtol(p, &r, 0);
+		p = r + 1;
+	}else
+		devno = 0;
+	if(*r != ',') goto err;
+	for(i = 0; i < 4; i++){
+		dstip[i] = strtol(p, &r, 0);
+		p = r + 1;
+		if(i == 3) break;
+		if(*r != '.') goto err;
+	}
+	if(*r == '!'){
+		dstport = strtol(p, &r, 0);
+		p = r + 1;
+	}else
+		dstport = 6666;
+	if(*r == '/'){
+		dstmac = strtoull(p, &r, 16);
+		if(r - p != 12) goto err;
+	}else
+		dstmac = ((uvlong)-1) >> 16;
+	if(*r != 0) goto err;
+	
+	if(devno >= MaxEther || etherxx[devno] == nil){
+		print("netconsole: no device #l%d\n", devno);
+		return;
+	}
+	
+	nc = malloc(sizeof(Netconsole));
+	if(nc == nil){
+		print("netconsole: out of memory");
+		return;
+	}
+	memset(nc, 0, sizeof(Netconsole));
+	nc->ether = etherxx[devno];
+	
+	uchar header[PktHdr] = {
+		/* 0 */ dstmac >> 40, dstmac >> 32, dstmac >> 24, dstmac >> 16, dstmac >> 8, dstmac >> 0,
+		/* 6 */ 0, 0, 0, 0, 0, 0,
+		/* 12 */ 0x08, 0x00,
+		/* 14 */ 0x45, 0x00,
+		/* 16 */ 0x00, 0x00, /* total length */
+		/* 18 */ 0x00, 0x00, 0x00, 0x00,
+		/* 22 */ 64, /* ttl */
+		/* 23 */ 0x11, /* protocol */
+		/* 24 */ 0x00, 0x00, /* checksum */
+		/* 26 */ srcip[0], srcip[1], srcip[2], srcip[3],
+		/* 30 */ dstip[0], dstip[1], dstip[2], dstip[3],
+		/* 34 */ srcport >> 8, srcport, dstport >> 8, dstport,
+		/* 38 */ 0x00, 0x00, /* length */
+		/* 40 */ 0x00, 0x00 /* checksum */
+	};
+	
+	memmove(nc->buf, header, PktHdr);
+	nc->n = PktHdr;
+	
+	netcons = nc;
+	consuart = &netconsuart;
+	return;
+
+err:
+	print("netconsole: invalid string %#q\n", getconf("console"));
+	print("netconsole: usage: srcip[!srcport][/srcdev],dstip[!dstport][/dstmac]\n");
+}
--- /dev/null
+++ b/sys/src/9/port/ethersink.c
@@ -1,0 +1,60 @@
+/*
+ * An ethernet /dev/null.
+ * Useful as a bridging target with ethernet-based VPN.
+ */
+#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/netif.h"
+#include "../port/etherif.h"
+
+static long
+ctl(Ether *ether, void *buf, long n)
+{
+	uchar ea[Eaddrlen];
+	Cmdbuf *cb;
+
+	cb = parsecmd(buf, n);
+	if(cb->nf >= 2
+	&& strcmp(cb->f[0], "ea")==0
+	&& parseether(ea, cb->f[1]) == 0){
+		free(cb);
+		memmove(ether->ea, ea, Eaddrlen);
+		memmove(ether->addr, ether->ea, Eaddrlen);
+		return 0;
+	}
+	free(cb);
+	error(Ebadctl);
+	return -1;	/* not reached */
+}
+
+static void
+nop(Ether*)
+{
+}
+
+static int
+reset(Ether* ether)
+{
+	if(ether->type==nil)
+		return -1;
+	ether->mbps = 1000;
+	ether->attach = nop;
+	ether->transmit = nop;
+	ether->ifstat = nil;
+	ether->ctl = ctl;
+	ether->promiscuous = nil;
+	ether->multicast = nil;
+	ether->arg = ether;
+	return 0;
+}
+
+void
+ethersinklink(void)
+{
+	addethercard("sink", reset);
+}
--- a/sys/src/9/port/portmkfile
+++ b/sys/src/9/port/portmkfile
@@ -101,3 +101,6 @@
 sysproc.$O:	/sys/include/a.out.h
 syscallfmt.$O:	/sys/src/libc/9syscall/sys.h
 devusb.$O:	../port/usb.h
+devether.$O:	../port/etherif.h ../port/netif.h
+wifi.$O:	../port/etherif.h ../port/netif.h ../port/wifi.h /sys/include/libsec.h
+wifi.$O:	../ip/ip.h ../ip/ipv6.h
--- /dev/null
+++ b/sys/src/9/port/wifi.c
@@ -1,0 +1,1880 @@
+#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"
+#include "../port/netif.h"
+#include "../port/etherif.h"
+#include "../port/wifi.h"
+
+#include <libsec.h>
+
+typedef struct SNAP SNAP;
+struct SNAP
+{
+	uchar	dsap;
+	uchar	ssap;
+	uchar	control;
+	uchar	orgcode[3];
+	uchar	type[2];
+};
+
+enum {
+	WIFIHDRSIZE = 2+2+3*6+2,
+	SNAPHDRSIZE = 8,
+};
+
+static char Sconn[] = "connecting";
+static char Sauth[] = "authenticated";
+static char Sneedauth[] = "need authentication";
+static char Sunauth[] = "unauthenticated";
+
+static char Sassoc[] = "associated";
+static char Sunassoc[] = "unassociated";
+static char Sblocked[] = "blocked";	/* no keys negotiated. only pass EAPOL frames */
+
+static uchar basicrates[] = {
+	0x80 | 2,	/* 1.0	Mb/s */
+	0x80 | 4,	/* 2.0	Mb/s */
+	0x80 | 11,	/* 5.5	Mb/s */
+	0x80 | 22,	/* 11.0	Mb/s */
+
+	0
+};
+
+static Block* wifidecrypt(Wifi *, Wnode *, Block *);
+static Block* wifiencrypt(Wifi *, Wnode *, Block *);
+static void freewifikeys(Wifi *, Wnode *);
+
+static void dmatproxy(Block *bp, int upstream, uchar proxy[Eaddrlen], DMAT *t);
+
+static uchar*
+srcaddr(Wifipkt *w)
+{
+	if((w->fc[1] & 0x02) == 0)
+		return w->a2;
+	if((w->fc[1] & 0x01) == 0)
+		return w->a3;
+	return w->a4;
+}
+static uchar*
+dstaddr(Wifipkt *w)
+{
+	if((w->fc[1] & 0x01) != 0)
+		return w->a3;
+	return w->a1;
+}
+
+int
+wifihdrlen(Wifipkt *w)
+{
+	int n;
+
+	n = WIFIHDRSIZE;
+	if((w->fc[0] & 0x0c) == 0x08)
+		if((w->fc[0] & 0xf0) == 0x80){	/* QOS */
+			n += 2;
+			if(w->fc[1] & 0x80)
+				n += 4;
+		}
+	if((w->fc[1] & 3) == 0x03)
+		n += Eaddrlen;
+	return n;
+}
+
+void
+wifiiq(Wifi *wifi, Block *b)
+{
+	SNAP s;
+	Wifipkt h, *w;
+	Etherpkt *e;
+	int hdrlen;
+
+	if(BLEN(b) < WIFIHDRSIZE)
+		goto drop;
+	w = (Wifipkt*)b->rp;
+	hdrlen = wifihdrlen(w);
+	if(BLEN(b) < hdrlen)
+		goto drop;
+	if(memcmp(srcaddr(w), wifi->ether->ea, Eaddrlen) == 0)
+		goto drop;
+	if(w->fc[1] & 0x40){
+		/* encrypted */
+		qpass(wifi->iq, b);
+		return;
+	}
+	switch(w->fc[0] & 0x0c){
+	case 0x00:	/* management */
+		if((w->fc[1] & 3) != 0x00)	/* STA->STA */
+			break;
+		qpass(wifi->iq, b);
+		return;
+	case 0x04:	/* control */
+		break;
+	case 0x08:	/* data */
+		b->rp += hdrlen;
+		switch(w->fc[0] & 0xf0){
+		default:
+			goto drop;
+		case 0x80:	/* QOS */
+		case 0x00:
+			break;
+		}
+		if(BLEN(b) < SNAPHDRSIZE)
+			break;
+		memmove(&s, b->rp, SNAPHDRSIZE);
+		if(s.dsap != 0xAA || s.ssap != 0xAA || s.control != 3)
+			break;
+		if(s.orgcode[0] != 0 || s.orgcode[1] != 0 || s.orgcode[2] != 0)
+			break;
+		b->rp += SNAPHDRSIZE-ETHERHDRSIZE;
+		h = *w;
+		e = (Etherpkt*)b->rp;
+		memmove(e->d, dstaddr(&h), Eaddrlen);
+		memmove(e->s, srcaddr(&h), Eaddrlen);
+		memmove(e->type, s.type, 2);
+
+		dmatproxy(b, 0, wifi->ether->ea, &wifi->dmat);
+
+		etheriq(wifi->ether, b, 1);
+		return;
+	}
+drop:
+	freeb(b);
+}
+
+static void
+wifitx(Wifi *wifi, Wnode *wn, Block *b)
+{
+	Wifipkt *w;
+	uint seq;
+
+	wn->lastsend = MACHP(0)->ticks;
+
+	seq = incref(&wifi->txseq);
+	seq <<= 4;
+
+	w = (Wifipkt*)b->rp;
+	w->dur[0] = 0;
+	w->dur[1] = 0;
+	w->seq[0] = seq;
+	w->seq[1] = seq>>8;
+
+	if((w->fc[0] & 0x0c) != 0x00){
+		b = wifiencrypt(wifi, wn, b);
+		if(b == nil)
+			return;
+	}
+
+	if((wn->txcount++ & 255) == 255 && wn->actrate != nil && wn->actrate != wn->maxrate){
+		uchar *a, *p;
+
+		for(a = wn->maxrate, p = wifi->rates; *p; p++){
+			if(*p < *a && *p > *wn->actrate)
+				a = p;
+		}
+		wn->actrate = a;
+	}
+
+	(*wifi->transmit)(wifi, wn, b);
+}
+
+static Wnode*
+nodelookup(Wifi *wifi, uchar *bssid, int new)
+{
+	Wnode *wn, *nn;
+
+	if(memcmp(bssid, wifi->ether->bcast, Eaddrlen) == 0)
+		return nil;
+	if((wn = wifi->bss) != nil){
+		if(memcmp(wn->bssid, bssid, Eaddrlen) == 0)
+			return wn;
+	}
+	if((nn = wifi->node) == wn)
+		nn++;
+	for(wn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++){
+		if(wn == wifi->bss)
+			continue;
+		if(memcmp(wn->bssid, bssid, Eaddrlen) == 0)
+			return wn;
+		if((long)(wn->lastsend - nn->lastsend) < 0
+		|| (long)(wn->lastseen - nn->lastseen) < 0)
+			nn = wn;
+	}
+	if(!new)
+		return nil;
+	freewifikeys(wifi, nn);
+	memset(nn, 0, sizeof(Wnode));
+	memmove(nn->bssid, bssid, Eaddrlen);
+	return nn;
+}
+
+void
+wifitxfail(Wifi *wifi, Block *b)
+{
+	Wifipkt *w;
+	Wnode *wn;
+
+	if(b == nil)
+		return;
+	w = (Wifipkt*)b->rp;
+	wn = nodelookup(wifi, w->a1, 0);
+	if(wn == nil)
+		return;
+	wn->txerror++;
+	if(wn->actrate != nil && wn->minrate != wn->actrate){
+		uchar *a, *p;
+
+		for(a = wn->minrate, p = wifi->rates; *p; p++){
+			if(*p > *a && *p < *wn->actrate)
+				a = p;
+		}
+		wn->actrate = a;
+	}
+}
+
+static uchar*
+putrates(uchar *p, uchar *rates)
+{
+	int n, m;
+
+	n = m = strlen((char*)rates);
+	if(n > 8)
+		n = 8;
+	/* supported rates */
+	*p++ = 1;
+	*p++ = n;
+	memmove(p, rates, n);
+	p += n;
+	if(m > 8){
+		/* extended supported rates */
+		*p++ = 50;
+		*p++ = m;
+		memmove(p, rates, m);
+		p += m;
+	}
+	return p;
+}
+
+static void
+wifiprobe(Wifi *wifi, Wnode *wn)
+{
+	Wifipkt *w;
+	Block *b;
+	uchar *p;
+	int n;
+
+	n = strlen(wifi->essid);
+	if(n == 0){
+		/* no specific essid, just tell driver to tune channel */
+		(*wifi->transmit)(wifi, wn, nil);
+		return;
+	}
+
+	b = allocb(WIFIHDRSIZE + 512);
+	w = (Wifipkt*)b->wp;
+	w->fc[0] = 0x40;	/* probe request */
+	w->fc[1] = 0x00;	/* STA->STA */
+	memmove(w->a1, wifi->ether->bcast, Eaddrlen);	/* ??? */
+	memmove(w->a2, wifi->ether->ea, Eaddrlen);
+	memmove(w->a3, wifi->ether->bcast, Eaddrlen);
+	b->wp += WIFIHDRSIZE;
+	p = b->wp;
+
+	*p++ = 0;	/* set */
+	*p++ = n;
+	memmove(p, wifi->essid, n);
+	p += n;
+
+	p = putrates(p, wifi->rates);
+
+	*p++ = 3;	/* ds parameter set */
+	*p++ = 1;
+	*p++ = wn->channel;
+
+	b->wp = p;
+	wifitx(wifi, wn, b);
+}
+
+static void
+sendauth(Wifi *wifi, Wnode *bss)
+{
+	Wifipkt *w;
+	Block *b;
+	uchar *p;
+
+	b = allocb(WIFIHDRSIZE + 3*2);
+	w = (Wifipkt*)b->wp;
+	w->fc[0] = 0xB0;	/* auth request */
+	w->fc[1] = 0x00;	/* STA->STA */
+	memmove(w->a1, bss->bssid, Eaddrlen);	/* ??? */
+	memmove(w->a2, wifi->ether->ea, Eaddrlen);
+	memmove(w->a3, bss->bssid, Eaddrlen);
+	b->wp += WIFIHDRSIZE;
+	p = b->wp;
+	*p++ = 0;	/* alg */
+	*p++ = 0;
+	*p++ = 1;	/* seq */
+	*p++ = 0;
+	*p++ = 0;	/* status */
+	*p++ = 0;
+	b->wp = p;
+
+	bss->aid = 0;
+
+	wifitx(wifi, bss, b);
+}
+
+static void
+sendassoc(Wifi *wifi, Wnode *bss)
+{
+	Wifipkt *w;
+	Block *b;
+	uchar *p;
+	int cap, n;
+
+	b = allocb(WIFIHDRSIZE + 512);
+	w = (Wifipkt*)b->wp;
+	w->fc[0] = 0x00;	/* assoc request */
+	w->fc[1] = 0x00;	/* STA->STA */
+	memmove(w->a1, bss->bssid, Eaddrlen);	/* ??? */
+	memmove(w->a2, wifi->ether->ea, Eaddrlen);
+	memmove(w->a3, bss->bssid, Eaddrlen);
+	b->wp += WIFIHDRSIZE;
+	p = b->wp;
+
+	/* capinfo */
+	cap = 1;				// ESS
+	cap |= (1<<5);				// Short Preamble
+	cap |= (1<<10) & bss->cap;		// Short Slot Time
+	*p++ = cap;
+	*p++ = cap>>8;
+
+	/* interval */
+	*p++ = 16;
+	*p++ = 16>>8;
+
+	n = strlen(bss->ssid);
+	*p++ = 0;	/* SSID */
+	*p++ = n;
+	memmove(p, bss->ssid, n);
+	p += n;
+
+	p = putrates(p, wifi->rates);
+
+	n = bss->rsnelen;
+	if(n > 0){
+		memmove(p, bss->rsne, n);
+		p += n;
+	}
+
+	b->wp = p;
+	wifitx(wifi, bss, b);
+}
+
+static void
+setstatus(Wifi *wifi, Wnode *wn, char *new)
+{
+	char *old;
+
+	old = wn->status;
+	wn->status = new;
+	if(wifi->debug && new != old)
+		print("#l%d: status %E: %.12ld %.12ld: %s -> %s (from pc=%#p)\n",
+			wifi->ether->ctlrno, 
+			wn->bssid, 
+			TK2MS(MACHP(0)->ticks), TK2MS(MACHP(0)->ticks - wn->lastsend),
+			old, new,
+			getcallerpc(&wifi));
+}
+
+static void
+recvassoc(Wifi *wifi, Wnode *wn, uchar *d, int len)
+{
+	uint s;
+
+	if(len < 2+2+2)
+		return;
+
+	d += 2;	/* caps */
+	s = d[0] | d[1]<<8;
+	d += 2;
+	switch(s){
+	case 0x00:
+		wn->aid = d[0] | d[1]<<8;
+		if(wn->rsnelen > 0)
+			setstatus(wifi, wn, Sblocked);
+		else
+			setstatus(wifi, wn, Sassoc);
+		break;
+	default:
+		wn->aid = 0;
+		setstatus(wifi, wn, Sunassoc);
+	}
+}
+
+static void
+recvbeacon(Wifi *wifi, Wnode *wn, uchar *d, int len)
+{
+	static uchar wpa1oui[4] = { 0x00, 0x50, 0xf2, 0x01 };
+	uchar *e, *x, *p, t;
+	int rsnset;
+
+	len -= 8+2+2;
+	if(len < 0)
+		return;
+
+	d += 8;	/* timestamp */
+	wn->ival = d[0] | d[1]<<8;
+	d += 2;
+	wn->cap = d[0] | d[1]<<8;
+	d += 2;
+
+	rsnset = 0;
+	for(e = d + len; d+2 <= e; d = x){
+		d += 2;
+		x = d + d[-1];
+		if(x > e)			
+			break;	/* truncated */
+		t = d[-2];
+		switch(t){
+		case 0:		/* SSID */
+			len = 0;
+			while(len < Essidlen && d+len < x && d[len] != 0)
+				len++;
+			if(len == 0)
+				continue;
+			if(len != strlen(wn->ssid) || strncmp(wn->ssid, (char*)d, len) != 0){
+				strncpy(wn->ssid, (char*)d, len);
+				wn->ssid[len] = 0;
+			}
+			break;
+		case 1:		/* supported rates */
+		case 50:	/* extended rates */
+			if(wn->actrate != nil || wifi->rates == nil)
+				break;	/* already set */
+			while(d < x){
+				t = *d++ | 0x80;
+				for(p = wifi->rates; *p != 0; p++){
+					if(*p == t){
+						if(wn->minrate == nil || t < *wn->minrate)
+							wn->minrate = p;
+						if(wn->maxrate == nil || t > *wn->maxrate)
+							wn->maxrate = p;
+						break;
+					}
+				}
+				wn->actrate = wn->maxrate;
+			}
+			break;
+		case 3:		/* DSPARAMS */
+			if(d != x)
+				wn->channel = d[0];
+			break;
+		case 221:	/* vendor specific */
+			len = x - d;
+			if(rsnset || len < sizeof(wpa1oui) || memcmp(d, wpa1oui, sizeof(wpa1oui)) != 0)
+				break;
+			/* no break */
+		case 48:	/* RSN information */
+			len = x - &d[-2];
+			memmove(wn->brsne, &d[-2], len);
+			wn->brsnelen = len;
+			rsnset = 1;
+			break;
+		}
+	}
+}
+
+static void
+freewifikeys(Wifi *wifi, Wnode *wn)
+{
+	int i;
+
+	wlock(&wifi->crypt);
+	for(i=0; i<nelem(wn->rxkey); i++){
+		secfree(wn->rxkey[i]);
+		wn->rxkey[i] = nil;
+	}
+	for(i=0; i<nelem(wn->txkey); i++){
+		secfree(wn->txkey[i]);
+		wn->txkey[i] = nil;
+	}
+	wunlock(&wifi->crypt);
+}
+
+static void
+wifideauth(Wifi *wifi, Wnode *wn)
+{
+	Ether *ether;
+	Netfile *f;
+	int i;
+
+	/* deassociate node, clear keys */
+	setstatus(wifi, wn, Sunauth);
+	freewifikeys(wifi, wn);
+	memset(&wifi->dmat, 0, sizeof(wifi->dmat));
+	wn->aid = 0;
+
+	if(wn == wifi->bss){
+		/* notify driver about node aid association */
+		(*wifi->transmit)(wifi, wn, nil);
+
+		/* notify aux/wpa with a zero length packet that we got deassociated from the ap */
+		ether = wifi->ether;
+		for(i=0; i<ether->nfile; i++){
+			f = ether->f[i];
+			if(f == nil || f->in == nil || f->inuse == 0 || f->type != 0x888e)
+				continue;
+			qflush(f->in);
+			qwrite(f->in, 0, 0);
+		}
+		qflush(ether->oq);
+	}
+}
+
+/* check if a node qualifies as our bss matching bssid and essid */
+static int
+goodbss(Wifi *wifi, Wnode *wn)
+{
+	if(memcmp(wifi->bssid, wifi->ether->bcast, Eaddrlen) != 0){
+		if(memcmp(wifi->bssid, wn->bssid, Eaddrlen) != 0)
+			return 0;	/* bssid doesnt match */
+	} else if(wifi->essid[0] == 0)
+		return 0;	/* both bssid and essid unspecified */
+	if(wifi->essid[0] != 0 && strcmp(wifi->essid, wn->ssid) != 0)
+		return 0;	/* essid doesnt match */
+	return 1;
+}
+
+static void
+wifiproc(void *arg)
+{
+	Wifi *wifi;
+	Wifipkt *w;
+	Wnode *wn;
+	Block *b;
+
+	b = nil;
+	wifi = arg;
+	while(waserror())
+		;
+	for(;;){
+		if(b != nil){
+			freeb(b);
+			b = nil;
+			continue;
+		}
+		if((b = qbread(wifi->iq, 100000)) == nil)
+			break;
+		w = (Wifipkt*)b->rp;
+		if(w->fc[1] & 0x40){
+			/* encrypted */
+			if((wn = nodelookup(wifi, w->a2, 0)) == nil)
+				continue;
+			wn->lastseen = MACHP(0)->ticks;
+			if((b = wifidecrypt(wifi, wn, b)) != nil){
+				w = (Wifipkt*)b->rp;
+				if(w->fc[1] & 0x40)
+					continue;
+				wifiiq(wifi, b);
+				b = nil;
+			}
+			continue;
+		}
+		/* management */
+		if((w->fc[0] & 0x0c) != 0x00)
+			continue;
+
+		switch(w->fc[0] & 0xf0){
+		case 0x50:	/* probe response */
+			if(wifi->debug)
+				print("#l%d: got probe from %E\n", wifi->ether->ctlrno, w->a3);
+			/* no break */
+		case 0x80:	/* beacon */
+			if((wn = nodelookup(wifi, w->a3, 1)) == nil)
+				continue;
+			wn->lastseen = MACHP(0)->ticks;
+			b->rp += wifihdrlen(w);
+			recvbeacon(wifi, wn, b->rp, BLEN(b));
+
+			if(wifi->bss == nil
+			&& TK2MS(MACHP(0)->ticks - wn->lastsend) > 1000
+			&& goodbss(wifi, wn)){
+				setstatus(wifi, wn, Sconn);
+				sendauth(wifi, wn);
+				wifi->lastauth = wn->lastsend;
+			}
+			continue;
+		}
+
+		if(memcmp(w->a1, wifi->ether->ea, Eaddrlen))
+			continue;
+		if((wn = nodelookup(wifi, w->a3, 0)) == nil)
+			continue;
+		wn->lastseen = MACHP(0)->ticks;
+		switch(w->fc[0] & 0xf0){
+		case 0x10:	/* assoc response */
+		case 0x30:	/* reassoc response */
+			b->rp += wifihdrlen(w);
+			recvassoc(wifi, wn, b->rp, BLEN(b));
+			/* notify driver about node aid association */
+			if(wn == wifi->bss)
+				(*wifi->transmit)(wifi, wn, nil);
+			break;
+		case 0xb0:	/* auth */
+			if(wifi->debug)
+				print("#l%d: got auth from %E\n", wifi->ether->ctlrno, wn->bssid);
+			if(wn->brsnelen > 0 && wn->rsnelen == 0)
+				setstatus(wifi, wn, Sneedauth);
+			else
+				setstatus(wifi, wn, Sauth);
+			if(wifi->bss == nil && goodbss(wifi, wn)){
+				wifi->bss = wn;
+				if(wn->status == Sauth)
+					sendassoc(wifi, wn);
+			}
+			break;
+		case 0xc0:	/* deauth */
+			if(wifi->debug)
+				print("#l%d: got deauth from %E\n", wifi->ether->ctlrno, wn->bssid);
+			wifideauth(wifi, wn);
+			break;
+		}
+	}
+	pexit("wifi in queue closed", 1);
+}
+
+static void
+wifietheroq(Wifi *wifi, Block *b)
+{
+	Etherpkt e;
+	Wifipkt h;
+	int hdrlen;
+	Wnode *wn;
+	SNAP *s;
+
+	if(BLEN(b) < ETHERHDRSIZE)
+		goto drop;
+	if((wn = wifi->bss) == nil)
+		goto drop;
+
+	dmatproxy(b, 1, wifi->ether->ea, &wifi->dmat);
+
+	memmove(&e, b->rp, ETHERHDRSIZE);
+	b->rp += ETHERHDRSIZE;
+	if(wn->status == Sblocked){
+		/* only pass EAPOL frames when port is blocked */
+		if((e.type[0]<<8 | e.type[1]) != 0x888e)
+			goto drop;
+	} else if(wn->status != Sassoc)
+		goto drop;
+
+	h.fc[0] = 0x08;	/* data */
+	memmove(h.a1, wn->bssid, Eaddrlen);
+	if(memcmp(e.s, wifi->ether->ea, Eaddrlen) == 0) {
+		h.fc[1] = 0x01;	/* STA->AP */
+	} else {
+		h.fc[1] = 0x03;	/* AP->AP (WDS) */
+		memmove(h.a2, wifi->ether->ea, Eaddrlen);
+	}
+	memmove(dstaddr(&h), e.d, Eaddrlen);
+	memmove(srcaddr(&h), e.s, Eaddrlen);
+
+	hdrlen = wifihdrlen(&h);
+	b = padblock(b, hdrlen + SNAPHDRSIZE);
+	memmove(b->rp, &h, hdrlen);
+	s = (SNAP*)(b->rp + hdrlen);
+	s->dsap = s->ssap = 0xAA;
+	s->control = 0x03;
+	s->orgcode[0] = 0;
+	s->orgcode[1] = 0;
+	s->orgcode[2] = 0;
+	memmove(s->type, e.type, 2);
+
+	wifitx(wifi, wn, b);
+	return;
+drop:
+	freeb(b);
+}
+
+static void
+wifoproc(void *arg)
+{
+	Ether *ether;
+	Wifi *wifi;
+	Block *b;
+
+	wifi = arg;
+	ether = wifi->ether;
+	while(waserror())
+		;
+	while((b = qbread(ether->oq, 1000000)) != nil)
+		wifietheroq(wifi, b);
+	pexit("ether out queue closed", 1);
+}
+
+static void
+wifsproc(void *arg)
+{
+	Ether *ether;
+	Wifi *wifi;
+	Wnode wnscan;
+	Wnode *wn;
+	ulong now, tmout;
+	uchar *rate;
+
+	wifi = arg;
+	ether = wifi->ether;
+
+	wn = &wnscan;
+	memset(wn, 0, sizeof(*wn));
+	memmove(wn->bssid, ether->bcast, Eaddrlen);
+
+	while(waserror())
+		;
+Scan:
+	/* scan for access point */
+	while(wifi->bss == nil){
+		ether->link = 0;
+		wnscan.channel = 1 + ((wnscan.channel+4) % 13);
+		wifiprobe(wifi, &wnscan);
+		do {
+			tsleep(&up->sleep, return0, 0, 200);
+			now = MACHP(0)->ticks;
+		} while(TK2MS(now-wifi->lastauth) < 1000);
+	}
+
+	/* maintain access point */
+	tmout = 0;
+	while((wn = wifi->bss) != nil){
+		ether->link = (wn->status == Sassoc) || (wn->status == Sblocked);
+		if(ether->link && (rate = wn->actrate) != nil)
+			ether->mbps = ((*rate & 0x7f)+3)/4;
+		now = MACHP(0)->ticks;
+		if(wn->status != Sneedauth && TK2SEC(now - wn->lastseen) > 20 || goodbss(wifi, wn) == 0){
+			wifideauth(wifi, wn);
+			wifi->bss = nil;
+			break;
+		}
+		if(TK2MS(now - wn->lastsend) > 1000){
+			if((wn->status == Sauth || wn->status == Sblocked) && (++tmout & 7) == 0)
+				wifideauth(wifi, wn);	/* stuck in auth, start over */
+			if(wn->status == Sconn || wn->status == Sunauth)
+				sendauth(wifi, wn);
+			if(wn->status == Sauth)
+				sendassoc(wifi, wn);
+		}
+		tsleep(&up->sleep, return0, 0, 500);
+	}
+	goto Scan;
+}
+
+Wifi*
+wifiattach(Ether *ether, void (*transmit)(Wifi*, Wnode*, Block*))
+{
+	char name[32];
+	Wifi *wifi;
+
+	wifi = malloc(sizeof(Wifi));
+	if(wifi == nil)
+		error(Enomem);
+	wifi->iq = qopen(ether->limit, 0, 0, 0);
+	if(wifi->iq == nil){
+		free(wifi);
+		error(Enomem);
+	}
+	wifi->ether = ether;
+	wifi->transmit = transmit;
+
+	wifi->rates = basicrates;
+
+	wifi->essid[0] = 0;
+	memmove(wifi->bssid, ether->bcast, Eaddrlen);
+
+	wifi->lastauth = MACHP(0)->ticks;
+
+	snprint(name, sizeof(name), "#l%dwifi", ether->ctlrno);
+	kproc(name, wifiproc, wifi);
+	snprint(name, sizeof(name), "#l%dwifo", ether->ctlrno);
+	kproc(name, wifoproc, wifi);
+	snprint(name, sizeof(name), "#l%dwifs", ether->ctlrno);
+	kproc(name, wifsproc, wifi);
+
+	return wifi;
+}
+
+static char *ciphers[] = {
+	[0]	"clear",
+	[TKIP]	"tkip",
+	[CCMP]	"ccmp",
+};
+
+static Wkey*
+parsekey(char *s)
+{
+	static char Ebadkey[] = "bad key";
+	uchar key[32];
+	int len, cipher;
+	char *e;
+	Wkey *k;
+
+	for(cipher=0; cipher<nelem(ciphers); cipher++){
+		if(strncmp(s, ciphers[cipher], len = strlen(ciphers[cipher])) == 0){
+			if(cipher == 0)	/* clear */
+				return nil;
+			if(s[len] == ':'){
+				s += len+1;
+				break;
+			}
+		}
+	}
+	if(cipher >= nelem(ciphers))
+		error(Ebadkey);
+
+	if((e = strchr(s, '@')) == nil)
+		e = strchr(s, 0);
+
+	len = dec16(key, sizeof(key), s, e - s);
+
+	switch(cipher){
+	case TKIP:
+		if(len != 32)
+			error(Ebadkey);
+		k = secalloc(sizeof(Wkey) + len);
+		memmove(k->key, key, len);
+		break;
+	case CCMP:
+		if(len != 16)
+			error(Ebadkey);
+		k = secalloc(sizeof(Wkey) + sizeof(AESstate));
+		setupAESstate((AESstate*)k->key, key, len, nil);
+		break;
+	default:
+		error(Ebadkey);
+		return nil;
+	}
+
+	memset(key, 0, sizeof(key));
+
+	if(*e++ == '@')
+		k->tsc = strtoull(e, nil, 16);
+	k->len = len;
+	k->cipher = cipher;
+
+	return k;
+}
+
+void
+wificfg(Wifi *wifi, char *opt)
+{
+	char *p, buf[64];
+	int n;
+
+	if(strncmp(opt, "debug=", 6))
+	if(strncmp(opt, "essid=", 6))
+	if(strncmp(opt, "bssid=", 6))
+		return;
+	if((p = strchr(opt, '=')) == nil)
+		return;
+	if(waserror())
+		return;
+	n = snprint(buf, sizeof(buf), "%.*s %q", (int)(p - opt), opt, p+1);
+	wifictl(wifi, buf, n);
+	poperror();
+}
+
+enum {
+	CMdebug,
+	CMessid,
+	CMauth,
+	CMbssid,
+	CMrxkey0,
+	CMrxkey1,
+	CMrxkey2,
+	CMrxkey3,
+	CMrxkey4,
+	CMtxkey0,
+};
+
+static Cmdtab wifictlmsg[] =
+{
+	CMdebug,	"debug",	0,
+	CMessid,	"essid",	0,
+	CMauth,		"auth",		0,
+	CMbssid,	"bssid",	0,
+
+	CMrxkey0,	"rxkey0",	0,	/* group keys */
+	CMrxkey1,	"rxkey1",	0,
+	CMrxkey2,	"rxkey2",	0,
+	CMrxkey3,	"rxkey3",	0,
+
+	CMrxkey4,	"rxkey",	0,	/* peerwise keys */
+	CMtxkey0,	"txkey",	0,
+
+	CMtxkey0,	"txkey0",	0,
+};
+
+long
+wifictl(Wifi *wifi, void *buf, long n)
+{
+	uchar addr[Eaddrlen];
+	Cmdbuf *cb;
+	Cmdtab *ct;
+	Wnode *wn;
+	Wkey *k, **kk;
+
+	cb = nil;
+	if(waserror()){
+		free(cb);
+		nexterror();
+	}
+	if(wifi->debug)
+		print("#l%d: wifictl: %.*s\n", wifi->ether->ctlrno, (int)n, buf);
+	memmove(addr, wifi->ether->bcast, Eaddrlen);
+	wn = wifi->bss;
+	cb = parsecmd(buf, n);
+	ct = lookupcmd(cb, wifictlmsg, nelem(wifictlmsg));
+	if(ct->index >= CMauth){
+		if(cb->nf > 1 && (ct->index == CMbssid || ct->index >= CMrxkey0)){
+			if(parseether(addr, cb->f[1]) == 0){
+				cb->f++;
+				cb->nf--;
+				wn = nodelookup(wifi, addr, 0);
+			}
+		}
+		if(wn == nil && ct->index != CMbssid)
+			error("missing node");
+	}
+	switch(ct->index){
+	case CMdebug:
+		if(cb->f[1] != nil)
+			wifi->debug = atoi(cb->f[1]);
+		else
+			wifi->debug ^= 1;
+		print("#l%d: debug: %d\n", wifi->ether->ctlrno, wifi->debug);
+		break;
+	case CMessid:
+		if(cb->f[1] != nil)
+			strncpy(wifi->essid, cb->f[1], Essidlen);
+		else
+			wifi->essid[0] = 0;
+	Findbss:
+		wn = wifi->bss;
+		if(wn != nil){
+			if(goodbss(wifi, wn))
+				break;
+			wifideauth(wifi, wn);
+		}
+		wifi->bss = nil;
+		if(wifi->essid[0] == 0 && memcmp(wifi->bssid, wifi->ether->bcast, Eaddrlen) == 0)
+			break;
+		for(wn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++)
+			if(goodbss(wifi, wn)){
+				setstatus(wifi, wn, Sconn);
+				sendauth(wifi, wn);
+			}
+		break;
+	case CMbssid:
+		memmove(wifi->bssid, addr, Eaddrlen);
+		goto Findbss;
+	case CMauth:
+		freewifikeys(wifi, wn);
+		if(cb->f[1] == nil)
+			wn->rsnelen = 0;
+		else
+			wn->rsnelen = dec16(wn->rsne, sizeof(wn->rsne), cb->f[1], strlen(cb->f[1]));
+		if(wn->aid == 0){
+			setstatus(wifi, wn, Sconn);
+			sendauth(wifi, wn);
+		} else {
+			setstatus(wifi, wn, Sauth);
+			sendassoc(wifi, wn);
+		}
+		break;
+	case CMrxkey0: case CMrxkey1: case CMrxkey2: case CMrxkey3: case CMrxkey4:
+	case CMtxkey0:
+		if(cb->f[1] == nil)
+			error(Ebadarg);
+		k = parsekey(cb->f[1]);
+		memset(cb->f[1], 0, strlen(cb->f[1]));
+		if(ct->index < CMtxkey0)
+			kk = &wn->rxkey[ct->index - CMrxkey0];
+		else
+			kk = &wn->txkey[ct->index - CMtxkey0];
+		wlock(&wifi->crypt);
+		secfree(*kk);
+		*kk = k;
+		wunlock(&wifi->crypt);
+		if(ct->index >= CMtxkey0 && wn->status == Sblocked)
+			setstatus(wifi, wn, Sassoc);
+		break;
+	}
+	poperror();
+	free(cb);
+	return n;
+}
+
+long
+wifistat(Wifi *wifi, void *buf, long n, ulong off)
+{
+	static uchar zeros[Eaddrlen];
+	char essid[Essidlen+1];
+	char *s, *p, *e;
+	Wnode *wn;
+	Wkey *k;
+	long now;
+	int i;
+
+	p = s = smalloc(4096);
+	e = s + 4096;
+
+	wn = wifi->bss;
+	if(wn != nil){
+		strncpy(essid, wn->ssid, Essidlen);
+		essid[Essidlen] = 0;
+		p = seprint(p, e, "essid: %s\n", essid);
+		p = seprint(p, e, "bssid: %E\n", wn->bssid);
+		p = seprint(p, e, "status: %s\n", wn->status);
+		p = seprint(p, e, "channel: %.2d\n", wn->channel);
+
+		/* only print key ciphers and key length */
+		rlock(&wifi->crypt);
+		for(i = 0; i<nelem(wn->rxkey); i++){
+			if((k = wn->rxkey[i]) != nil)
+				p = seprint(p, e, "rxkey%d: %s:[%d]\n", i,
+					ciphers[k->cipher], k->len);
+		}
+		for(i = 0; i<nelem(wn->txkey); i++){
+			if((k = wn->txkey[i]) != nil)
+				p = seprint(p, e, "txkey%d: %s:[%d]\n", i,
+					ciphers[k->cipher], k->len);
+		}
+		runlock(&wifi->crypt);
+
+		if(wn->brsnelen > 0){
+			p = seprint(p, e, "brsne: ");
+			for(i=0; i<wn->brsnelen; i++)
+				p = seprint(p, e, "%.2X", wn->brsne[i]);
+			p = seprint(p, e, "\n");
+		}
+	} else {
+		p = seprint(p, e, "essid: %s\n", wifi->essid);
+		p = seprint(p, e, "bssid: %E\n", wifi->bssid);
+	}
+
+	now = MACHP(0)->ticks;
+	for(wn = wifi->node; wn != &wifi->node[nelem(wifi->node)]; wn++){
+		if(wn->lastseen == 0)
+			continue;
+		strncpy(essid, wn->ssid, Essidlen);
+		essid[Essidlen] = 0;
+		p = seprint(p, e, "node: %E %.4x %-11ld %.2d %s\n",
+			wn->bssid, wn->cap, TK2MS(now - wn->lastseen), wn->channel, essid);
+	}
+	n = readstr(off, buf, n, s);
+	free(s);
+	return n;
+}
+
+static void tkipencrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc);
+static int tkipdecrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc);
+static void ccmpencrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc);
+static int ccmpdecrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc);
+
+static Block*
+wifiencrypt(Wifi *wifi, Wnode *wn, Block *b)
+{
+	uvlong tsc;
+	int n, kid;
+	Wifipkt *w;
+	Wkey *k;
+
+	rlock(&wifi->crypt);
+
+	kid = 0;
+	k = wn->txkey[kid];
+	if(k == nil){
+		runlock(&wifi->crypt);
+		return b;
+	}
+
+	n = wifihdrlen((Wifipkt*)b->rp);
+
+	b = padblock(b, 8);
+	b = padblock(b, -(8+4));
+
+	w = (Wifipkt*)b->rp;
+	memmove(w, b->rp+8, n);
+	b->rp += n;
+
+	tsc = ++k->tsc;
+
+	switch(k->cipher){
+	case TKIP:
+		b->rp[0] = tsc>>8;
+		b->rp[1] = (b->rp[0] | 0x20) & 0x7f;
+		b->rp[2] = tsc;
+		b->rp[3] = kid<<6 | 0x20;
+		b->rp[4] = tsc>>16;
+		b->rp[5] = tsc>>24;
+		b->rp[6] = tsc>>32;
+		b->rp[7] = tsc>>40;
+		b->rp += 8;
+		tkipencrypt(k, w, b, tsc);
+		break;
+	case CCMP:
+		b->rp[0] = tsc;
+		b->rp[1] = tsc>>8;
+		b->rp[2] = 0;
+		b->rp[3] = kid<<6 | 0x20;
+		b->rp[4] = tsc>>16;
+		b->rp[5] = tsc>>24;
+		b->rp[6] = tsc>>32;
+		b->rp[7] = tsc>>40;
+		b->rp += 8;
+		ccmpencrypt(k, w, b, tsc);
+		break;
+	}
+	runlock(&wifi->crypt);
+
+	b->rp = (uchar*)w;
+	w->fc[1] |= 0x40;
+	return b;
+}
+
+static Block*
+wifidecrypt(Wifi *wifi, Wnode *wn, Block *b)
+{
+	uvlong tsc;
+	int n, kid;
+	Wifipkt *w;
+	Wkey *k;
+
+	rlock(&wifi->crypt);
+
+	w = (Wifipkt*)b->rp;
+	n = wifihdrlen(w);
+	b->rp += n;
+	if(BLEN(b) < 8+8)
+		goto drop;
+
+	kid = b->rp[3]>>6;
+	if((b->rp[3] & 0x20) == 0)
+		goto drop;
+	if((w->a1[0] & 1) == 0)
+		kid = 4;	/* use peerwise key for non-unicast */
+
+	k = wn->rxkey[kid];
+	if(k == nil)
+		goto drop;
+	switch(k->cipher){
+	case TKIP:
+		tsc =	(uvlong)b->rp[7]<<40 |
+			(uvlong)b->rp[6]<<32 |
+			(uvlong)b->rp[5]<<24 |
+			(uvlong)b->rp[4]<<16 |
+			(uvlong)b->rp[0]<<8 |
+			(uvlong)b->rp[2];
+		b->rp += 8;
+		if(tsc <= k->tsc)
+			goto drop;
+		if(tkipdecrypt(k, w, b, tsc) != 0)
+			goto drop;
+		break;
+	case CCMP:
+		tsc =	(uvlong)b->rp[7]<<40 |
+			(uvlong)b->rp[6]<<32 |
+			(uvlong)b->rp[5]<<24 |
+			(uvlong)b->rp[4]<<16 |
+			(uvlong)b->rp[1]<<8 |
+			(uvlong)b->rp[0];
+		b->rp += 8;
+		if(tsc <= k->tsc)
+			goto drop;
+		if(ccmpdecrypt(k, w, b, tsc) != 0)
+			goto drop;
+		break;
+	default:
+	drop:
+		runlock(&wifi->crypt);
+		freeb(b);
+		return nil;
+	}
+	runlock(&wifi->crypt);
+
+	k->tsc = tsc;
+	b->rp -= n;
+	memmove(b->rp, w, n);
+	w = (Wifipkt*)b->rp;
+	w->fc[1] &= ~0x40;
+	return b;
+}
+
+static u16int Sbox[256] = {
+	0xC6A5, 0xF884, 0xEE99, 0xF68D, 0xFF0D, 0xD6BD, 0xDEB1, 0x9154,
+	0x6050, 0x0203, 0xCEA9, 0x567D, 0xE719, 0xB562, 0x4DE6, 0xEC9A,
+	0x8F45, 0x1F9D, 0x8940, 0xFA87, 0xEF15, 0xB2EB, 0x8EC9, 0xFB0B,
+	0x41EC, 0xB367, 0x5FFD, 0x45EA, 0x23BF, 0x53F7, 0xE496, 0x9B5B,
+	0x75C2, 0xE11C, 0x3DAE, 0x4C6A, 0x6C5A, 0x7E41, 0xF502, 0x834F,
+	0x685C, 0x51F4, 0xD134, 0xF908, 0xE293, 0xAB73, 0x6253, 0x2A3F,
+	0x080C, 0x9552, 0x4665, 0x9D5E, 0x3028, 0x37A1, 0x0A0F, 0x2FB5,
+	0x0E09, 0x2436, 0x1B9B, 0xDF3D, 0xCD26, 0x4E69, 0x7FCD, 0xEA9F,
+	0x121B, 0x1D9E, 0x5874, 0x342E, 0x362D, 0xDCB2, 0xB4EE, 0x5BFB,
+	0xA4F6, 0x764D, 0xB761, 0x7DCE, 0x527B, 0xDD3E, 0x5E71, 0x1397,
+	0xA6F5, 0xB968, 0x0000, 0xC12C, 0x4060, 0xE31F, 0x79C8, 0xB6ED,
+	0xD4BE, 0x8D46, 0x67D9, 0x724B, 0x94DE, 0x98D4, 0xB0E8, 0x854A,
+	0xBB6B, 0xC52A, 0x4FE5, 0xED16, 0x86C5, 0x9AD7, 0x6655, 0x1194,
+	0x8ACF, 0xE910, 0x0406, 0xFE81, 0xA0F0, 0x7844, 0x25BA, 0x4BE3,
+	0xA2F3, 0x5DFE, 0x80C0, 0x058A, 0x3FAD, 0x21BC, 0x7048, 0xF104,
+	0x63DF, 0x77C1, 0xAF75, 0x4263, 0x2030, 0xE51A, 0xFD0E, 0xBF6D,
+	0x814C, 0x1814, 0x2635, 0xC32F, 0xBEE1, 0x35A2, 0x88CC, 0x2E39,
+	0x9357, 0x55F2, 0xFC82, 0x7A47, 0xC8AC, 0xBAE7, 0x322B, 0xE695,
+	0xC0A0, 0x1998, 0x9ED1, 0xA37F, 0x4466, 0x547E, 0x3BAB, 0x0B83,
+	0x8CCA, 0xC729, 0x6BD3, 0x283C, 0xA779, 0xBCE2, 0x161D, 0xAD76,
+	0xDB3B, 0x6456, 0x744E, 0x141E, 0x92DB, 0x0C0A, 0x486C, 0xB8E4,
+	0x9F5D, 0xBD6E, 0x43EF, 0xC4A6, 0x39A8, 0x31A4, 0xD337, 0xF28B,
+	0xD532, 0x8B43, 0x6E59, 0xDAB7, 0x018C, 0xB164, 0x9CD2, 0x49E0,
+	0xD8B4, 0xACFA, 0xF307, 0xCF25, 0xCAAF, 0xF48E, 0x47E9, 0x1018,
+	0x6FD5, 0xF088, 0x4A6F, 0x5C72, 0x3824, 0x57F1, 0x73C7, 0x9751,
+	0xCB23, 0xA17C, 0xE89C, 0x3E21, 0x96DD, 0x61DC, 0x0D86, 0x0F85,
+	0xE090, 0x7C42, 0x71C4, 0xCCAA, 0x90D8, 0x0605, 0xF701, 0x1C12,
+	0xC2A3, 0x6A5F, 0xAEF9, 0x69D0, 0x1791, 0x9958, 0x3A27, 0x27B9,
+	0xD938, 0xEB13, 0x2BB3, 0x2233, 0xD2BB, 0xA970, 0x0789, 0x33A7,
+	0x2DB6, 0x3C22, 0x1592, 0xC920, 0x8749, 0xAAFF, 0x5078, 0xA57A,
+	0x038F, 0x59F8, 0x0980, 0x1A17, 0x65DA, 0xD731, 0x84C6, 0xD0B8,
+	0x82C3, 0x29B0, 0x5A77, 0x1E11, 0x7BCB, 0xA8FC, 0x6DD6, 0x2C3A
+};
+
+static void
+tkipk2tk(uchar key[16], u16int tk[8])
+{
+	tk[0] = (u16int)key[1]<<8 | key[0];
+	tk[1] = (u16int)key[3]<<8 | key[2];
+	tk[2] = (u16int)key[5]<<8 | key[4];
+	tk[3] = (u16int)key[7]<<8 | key[6];
+	tk[4] = (u16int)key[9]<<8 | key[8];
+	tk[5] = (u16int)key[11]<<8 | key[10];
+	tk[6] = (u16int)key[13]<<8 | key[12];
+	tk[7] = (u16int)key[15]<<8 | key[14];
+}
+
+static void
+tkipphase1(u32int tscu, uchar ta[Eaddrlen], u16int tk[8], u16int p1k[5])
+{
+	u16int *k, i, x0, x1, x2;
+
+	p1k[0] = tscu;
+	p1k[1] = tscu>>16;
+	p1k[2] = (u16int)ta[1]<<8 | ta[0];
+	p1k[3] = (u16int)ta[3]<<8 | ta[2];
+	p1k[4] = (u16int)ta[5]<<8 | ta[4];
+
+	for(i=0; i<8; i++){
+		k = &tk[i & 1];
+
+		x0 = p1k[4] ^ k[0];
+		x1 = Sbox[x0 >> 8];
+		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+		p1k[0] += x2;
+		x0 = p1k[0] ^ k[2];
+		x1 = Sbox[x0 >> 8];
+		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+		p1k[1] += x2;
+		x0 = p1k[1] ^ k[4];
+		x1 = Sbox[x0 >> 8];
+		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+		p1k[2] += x2;
+		x0 = p1k[2] ^ k[6];
+		x1 = Sbox[x0 >> 8];
+		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+		p1k[3] += x2;
+		x0 = p1k[3] ^ k[0];
+		x1 = Sbox[x0 >> 8];
+		x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+		p1k[4] += x2;
+
+		p1k[4] += i;
+	}
+}
+
+static void
+tkipphase2(u16int tscl, u16int p1k[5], u16int tk[8], uchar rc4key[16])
+{
+	u16int ppk[6], x0, x1, x2;
+
+	ppk[0] = p1k[0];
+	ppk[1] = p1k[1];
+	ppk[2] = p1k[2];
+	ppk[3] = p1k[3];
+	ppk[4] = p1k[4];
+	ppk[5] = p1k[4] + tscl;
+
+	x0 = ppk[5] ^ tk[0];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[0] += x2;
+	x0 = ppk[0] ^ tk[1];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[1] += x2;
+	x0 = ppk[1] ^ tk[2];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[2] += x2;
+	x0 = ppk[2] ^ tk[3];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[3] += x2;
+	x0 = ppk[3] ^ tk[4];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[4] += x2;
+	x0 = ppk[4] ^ tk[5];
+	x1 = Sbox[x0 >> 8];
+	x2 = Sbox[x0 & 0xFF] ^ ((x1>>8) | (x1<<8));
+	ppk[5] += x2;
+
+	x2 = ppk[5] ^ tk[6];
+	ppk[0] += (x2 >> 1) | (x2 << 15);
+	x2 = ppk[0] ^ tk[7];
+	ppk[1] += (x2 >> 1) | (x2 << 15);
+
+	x2 = ppk[1];
+	ppk[2] += (x2 >> 1) | (x2 << 15);
+	x2 = ppk[2];
+	ppk[3] += (x2 >> 1) | (x2 << 15);
+	x2 = ppk[3];
+	ppk[4] += (x2 >> 1) | (x2 << 15);
+	x2 = ppk[4];
+	ppk[5] += (x2 >> 1) | (x2 << 15);
+
+	rc4key[0] = tscl >> 8;
+	rc4key[1] = (rc4key[0] | 0x20) & 0x7F;
+	rc4key[2] = tscl;
+	rc4key[3] = (ppk[5] ^ tk[0]) >> 1;
+	rc4key[4]  = ppk[0];
+	rc4key[5]  = ppk[0] >> 8;
+	rc4key[6]  = ppk[1];
+	rc4key[7]  = ppk[1] >> 8;
+	rc4key[8]  = ppk[2];
+	rc4key[9]  = ppk[2] >> 8;
+	rc4key[10] = ppk[3];
+	rc4key[11] = ppk[3] >> 8;
+	rc4key[12] = ppk[4];
+	rc4key[13] = ppk[4] >> 8;
+	rc4key[14] = ppk[5];
+	rc4key[15] = ppk[5] >> 8;
+}
+
+typedef struct MICstate MICstate;
+struct MICstate
+{
+	u32int	l;
+	u32int	r;
+	u32int	m;
+	u32int	n;
+};
+
+static void
+micsetup(MICstate *s, uchar key[8])
+{
+	s->l =	(u32int)key[0] |
+		(u32int)key[1]<<8 |
+		(u32int)key[2]<<16 |
+		(u32int)key[3]<<24;
+	s->r =	(u32int)key[4] |
+		(u32int)key[5]<<8 |
+		(u32int)key[6]<<16 |
+		(u32int)key[7]<<24;
+	s->m = 0;
+	s->n = 0;
+}
+
+static void
+micupdate(MICstate *s, uchar *data, ulong len)
+{
+	u32int l, r, m, n, e;
+
+	l = s->l;
+	r = s->r;
+	m = s->m;
+	n = s->n;
+	e = n + len;
+	while(n != e){
+		m >>= 8;
+		m |= (u32int)*data++ << 24;
+		if(++n & 3)
+			continue;
+		l ^= m;
+		r ^= (l << 17) | (l >> 15);
+		l += r;
+		r ^= ((l & 0x00FF00FFUL)<<8) | ((l & 0xFF00FF00UL)>>8);
+		l += r;
+		r ^= (l << 3) | (l >> 29);
+		l += r;
+		r ^= (l >> 2) | (l << 30);
+		l += r;
+	}
+	s->l = l;
+	s->r = r;
+	s->m = m;
+	s->n = n;
+}
+
+static void
+micfinish(MICstate *s, uchar mic[8])
+{
+	static uchar pad[8] = { 0x5a, 0x00,  0x00,  0x00,  0x00,  0x00,  0x00,  0x00, };
+
+	micupdate(s, pad, sizeof(pad));
+
+	mic[0] = s->l;
+	mic[1] = s->l>>8;
+	mic[2] = s->l>>16;
+	mic[3] = s->l>>24;
+	mic[4] = s->r;
+	mic[5] = s->r>>8;
+	mic[6] = s->r>>16;
+	mic[7] = s->r>>24;
+}
+
+static uchar pad4[4] = { 0x00, 0x00, 0x00, 0x00, };
+
+static void
+tkipencrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc)
+{
+	u16int tk[8], p1k[5];
+	uchar seed[16];
+	RC4state rs;
+	MICstate ms;
+	ulong crc;
+
+	micsetup(&ms, k->key+24);
+	micupdate(&ms, dstaddr(w), Eaddrlen);
+	micupdate(&ms, srcaddr(w), Eaddrlen);
+	micupdate(&ms, pad4, 4);
+	micupdate(&ms, b->rp, BLEN(b));
+	micfinish(&ms, b->wp);
+	b->wp += 8;
+
+	crc = ethercrc(b->rp, BLEN(b));
+	crc = ~crc;
+	b->wp[0] = crc;
+	b->wp[1] = crc>>8;
+	b->wp[2] = crc>>16;
+	b->wp[3] = crc>>24;
+	b->wp += 4;
+
+	tkipk2tk(k->key, tk);
+	tkipphase1(tsc >> 16, w->a2, tk, p1k);
+	tkipphase2(tsc & 0xFFFF, p1k, tk, seed);
+	setupRC4state(&rs, seed, sizeof(seed));
+	rc4(&rs, b->rp, BLEN(b));
+}
+
+static int
+tkipdecrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc)
+{
+	uchar seed[16], mic[8];
+	u16int tk[8], p1k[5];
+	RC4state rs;
+	MICstate ms;
+	ulong crc;
+
+	if(BLEN(b) < 8+4)
+		return -1;
+
+	tkipk2tk(k->key, tk);
+	tkipphase1(tsc >> 16, w->a2, tk, p1k);
+	tkipphase2(tsc & 0xFFFF, p1k, tk, seed);
+	setupRC4state(&rs, seed, sizeof(seed));
+	rc4(&rs, b->rp, BLEN(b));
+
+	b->wp -= 4;
+	crc =	(ulong)b->wp[0] |
+		(ulong)b->wp[1]<<8 |
+		(ulong)b->wp[2]<<16 |
+		(ulong)b->wp[3]<<24;
+	crc = ~crc;
+	crc ^= ethercrc(b->rp, BLEN(b));
+
+	b->wp -= 8;
+	micsetup(&ms, k->key+16);
+	micupdate(&ms, dstaddr(w), Eaddrlen);
+	micupdate(&ms, srcaddr(w), Eaddrlen);
+	micupdate(&ms, pad4, 4);
+	micupdate(&ms, b->rp, BLEN(b));
+	micfinish(&ms, mic);
+
+	return tsmemcmp(b->wp, mic, 8) | crc;
+}
+
+static uchar*
+putbe(uchar *p, int L, uint v)
+{
+	while(--L >= 0)
+		*p++ = (v >> L*8) & 0xFF;
+	return p;
+}
+
+static void
+xblock(int L, int M, uchar *N, uchar *a, int la, int lm, uchar t[16], AESstate *s)
+{
+	uchar l[8], *p, *x, *e;
+
+	assert(M >= 4 && M <= 16);
+	assert(L >= 2 && L <= 4);
+
+	t[0] = ((la > 0)<<6) | ((M-2)/2)<<3 | (L-1);	/* flags */
+	memmove(&t[1], N, 15-L);
+	putbe(&t[16-L], L, lm);
+	aes_encrypt(s->ekey, s->rounds, t, t);
+	
+	if(la > 0){
+		assert(la < 0xFF00);
+		for(p = l, e = putbe(l, 2, la), x = t; p < e; x++, p++)
+			*x ^= *p;
+		for(e = a + la; a < e; x = t){
+			for(; a < e && x < &t[16]; x++, a++)
+				*x ^= *a;
+			aes_encrypt(s->ekey, s->rounds, t, t);
+		}
+	}
+}
+
+static uchar*
+sblock(int L, uchar *N, uint i, uchar b[16], AESstate *s)
+{
+	b[0] = L-1;	/* flags */
+	memmove(&b[1], N, 15-L);
+	putbe(&b[16-L], L, i);
+	aes_encrypt(s->ekey, s->rounds, b, b);
+	return b;
+};
+
+static void
+aesCCMencrypt(int L, int M, uchar *N /* N[15-L] */,
+	uchar *a /* a[la] */, int la,
+	uchar *m /* m[lm+M] */, int lm,
+	AESstate *s)
+{
+	uchar t[16], b[16], *p, *x;
+	uint i;
+
+	xblock(L, M, N, a, la, lm, t, s);
+
+	for(i = 1; lm >= 16; i++, m += 16, lm -= 16){
+		sblock(L, N, i, b, s);
+
+		*((u32int*)&t[0]) ^= *((u32int*)&m[0]);
+		*((u32int*)&m[0]) ^= *((u32int*)&b[0]);
+		*((u32int*)&t[4]) ^= *((u32int*)&m[4]);
+		*((u32int*)&m[4]) ^= *((u32int*)&b[4]);
+		*((u32int*)&t[8]) ^= *((u32int*)&m[8]);
+		*((u32int*)&m[8]) ^= *((u32int*)&b[8]);
+		*((u32int*)&t[12]) ^= *((u32int*)&m[12]);
+		*((u32int*)&m[12]) ^= *((u32int*)&b[12]);
+
+		aes_encrypt(s->ekey, s->rounds, t, t);
+	}
+	if(lm > 0){
+		for(p = sblock(L, N, i, b, s), x = t; p < &b[lm]; x++, m++, p++){
+			*x ^= *m;
+			*m ^= *p;
+		}
+		aes_encrypt(s->ekey, s->rounds, t, t);
+	}
+
+	for(p = sblock(L, N, 0, b, s), x = t; p < &b[M]; x++, p++)
+		*x ^= *p;
+
+	memmove(m, t, M);
+}
+
+static int
+aesCCMdecrypt(int L, int M, uchar *N /* N[15-L] */,
+	uchar *a /* a[la] */, int la,
+	uchar *m /* m[lm+M] */, int lm,
+	AESstate *s)
+{
+	uchar t[16], b[16], *p, *x;
+	uint i;
+
+	xblock(L, M, N, a, la, lm, t, s);
+
+	for(i = 1; lm >= 16; i++, m += 16, lm -= 16){
+		sblock(L, N, i, b, s);
+
+		*((u32int*)&m[0]) ^= *((u32int*)&b[0]);
+		*((u32int*)&t[0]) ^= *((u32int*)&m[0]);
+		*((u32int*)&m[4]) ^= *((u32int*)&b[4]);
+		*((u32int*)&t[4]) ^= *((u32int*)&m[4]);
+		*((u32int*)&m[8]) ^= *((u32int*)&b[8]);
+		*((u32int*)&t[8]) ^= *((u32int*)&m[8]);
+		*((u32int*)&m[12]) ^= *((u32int*)&b[12]);
+		*((u32int*)&t[12]) ^= *((u32int*)&m[12]);
+
+		aes_encrypt(s->ekey, s->rounds, t, t);
+	}
+	if(lm > 0){
+		for(p = sblock(L, N, i, b, s), x = t; p < &b[lm]; x++, m++, p++){
+			*m ^= *p;
+			*x ^= *m;
+		}
+		aes_encrypt(s->ekey, s->rounds, t, t);
+	}
+
+	for(p = sblock(L, N, 0, b, s), x = t; p < &b[M]; x++, p++)
+		*x ^= *p;
+
+	return tsmemcmp(m, t, M);
+}
+
+static int
+setupCCMP(Wifipkt *w, uvlong tsc, uchar nonce[13], uchar auth[32])
+{
+	uchar *p;
+
+	nonce[0] = ((w->fc[0] & 0x0c) == 0x00) << 4;
+	memmove(&nonce[1], w->a2, Eaddrlen);
+	nonce[7]  = tsc >> 40;
+	nonce[8]  = tsc >> 32;
+	nonce[9]  = tsc >> 24;
+	nonce[10] = tsc >> 16;
+	nonce[11] = tsc >> 8;
+	nonce[12] = tsc;
+
+	p = auth;
+	*p++ = (w->fc[0] & (((w->fc[0] & 0x0c) == 0x08) ? 0x0f : 0xff));
+	*p++ = (w->fc[1] & ~0x38) | 0x40;
+	memmove(p, w->a1, Eaddrlen); p += Eaddrlen;
+	memmove(p, w->a2, Eaddrlen); p += Eaddrlen;
+	memmove(p, w->a3, Eaddrlen); p += Eaddrlen;
+	*p++ = w->seq[0] & 0x0f;
+	*p++ = 0;
+	if((w->fc[1] & 3) == 0x03) {
+		memmove(p, w->a4, Eaddrlen);
+		p += Eaddrlen;
+	}
+
+	return p - auth;
+}
+
+static void
+ccmpencrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc)
+{
+	uchar auth[32], nonce[13];
+
+	aesCCMencrypt(2, 8, nonce, auth,
+		setupCCMP(w, tsc, nonce, auth),
+		b->rp, BLEN(b), (AESstate*)k->key);
+	b->wp += 8;
+}
+
+static int
+ccmpdecrypt(Wkey *k, Wifipkt *w, Block *b, uvlong tsc)
+{
+	uchar auth[32], nonce[13];
+
+	if(BLEN(b) < 8)
+		return -1;
+
+	b->wp -= 8;
+	return aesCCMdecrypt(2, 8, nonce, auth,
+		setupCCMP(w, tsc, nonce, auth),
+		b->rp, BLEN(b), (AESstate*)k->key);
+}
+
+/*
+ * Dynamic Mac Address Translation (DMAT)
+ *
+ * Wifi does not allow spoofing of the source mac which breaks
+ * bridging. To solve this we proxy mac addresses, maintaining
+ * a translation table from ip address to destination mac address.
+ * Upstream ARP and NDP packets get ther source mac address changed
+ * to proxy and a translation entry is added with the original mac
+ * for downstream translation. The proxy does not appear in the
+ * table.
+ */
+#include "../ip/ip.h"
+#include "../ip/ipv6.h"
+
+static void
+dmatproxy(Block *bp, int upstream, uchar proxy[Eaddrlen], DMAT *t)
+{
+	static uchar arp4[] = {
+		0x00, 0x01,
+		0x08, 0x00,
+		0x06, 0x04,
+		0x00,
+	};
+	uchar ip[IPaddrlen], mac[Eaddrlen], *targ, *end, *a, *o;
+	ulong csum, c, h;
+	Etherpkt *pkt;
+	int proto, i;
+	DMTE *te;
+
+	end = bp->wp;
+	pkt = (Etherpkt*)bp->rp;
+	a = pkt->data;
+	if(a >= end)
+		return;
+
+	if(upstream)
+		memmove(pkt->s, proxy, Eaddrlen);
+	else if(t->map == 0 || (pkt->d[0]&1) != 0 || memcmp(pkt->d, proxy, Eaddrlen) != 0)
+		return;
+
+	targ = nil;
+	switch(pkt->type[0]<<8 | pkt->type[1]){
+	default:
+		return;
+	case ETIP4:
+	case ETIP6:
+		switch(a[0]&0xF0){
+		default:
+			return;
+		case IP_VER4:
+			if(a+IP4HDR > end || (a[0]&15) < IP_HLEN4)
+				return;
+			v4tov6(ip, a+12+4*(upstream==0));
+			proto = a[9];
+			a += (a[0]&15)*4;
+			break;
+		case IP_VER6:
+			if(a+IP6HDR > end)
+				return;
+			memmove(ip, a+8+16*(upstream==0), 16);
+			proto = a[6];
+			a += IP6HDR;
+			break;
+		}
+		if(!upstream)
+			break;
+		switch(proto){
+		case ICMPv6:
+			if(a+8 > end)
+				return;
+			switch(a[0]){
+			default:
+				return;
+			case 133:	/* Router Solicitation */
+				o = a+8;
+				break;
+			case 134:	/* Router Advertisement */
+				o = a+8+8;
+				break;
+			case 136:	/* Neighbor Advertisement */
+				targ = a+8;
+				/* wet floor */
+			case 135:	/* Neighbor Solicitation */
+				o = a+8+16;
+				break;
+			case 137:	/* Redirect */
+				o = a+8+16+16;
+				break;
+			}
+			memset(mac, 0xFF, Eaddrlen);
+			csum = (a[2]<<8 | a[3])^0xFFFF;
+			while(o+8 <= end && o[1] != 0){
+				switch(o[0]){
+				case SRC_LLADDR:
+				case TARGET_LLADDR:
+					for(i=0; i<Eaddrlen; i += 2)
+						csum += (o[2+i]<<8 | o[3+i])^0xFFFF;
+					memmove(mac, o+2, Eaddrlen);
+					memmove(o+2, proxy, Eaddrlen);
+					for(i=0; i<Eaddrlen; i += 2)
+						csum += (o[2+i]<<8 | o[3+i]);
+					break;
+				}
+				o += o[1]*8;
+			}
+			while((c = csum >> 16) != 0)
+				csum = (csum & 0xFFFF) + c;
+			csum ^= 0xFFFF;
+			a[2] = csum>>8;
+			a[3] = csum;
+			break;
+		case UDP:	/* for BOOTP */
+			if(a+42 > end
+			|| (a[0]<<8 | a[1]) != 68
+			|| (a[2]<<8 | a[3]) != 67
+			|| a[8] != 1
+			|| a[9] != 1
+			|| a[10] != Eaddrlen
+			|| (a[18]&0x80) != 0
+			|| memcmp(a+36, proxy, Eaddrlen) == 0)
+				return;
+
+			csum = (a[6]<<8 | a[7])^0xFFFF;
+
+			/* set the broadcast flag so response reaches us */
+			csum += (a[18]<<8)^0xFFFF;
+			a[18] |= 0x80;
+			csum += (a[18]<<8);
+
+			while((c = csum >> 16) != 0)
+				csum = (csum & 0xFFFF) + c;
+			csum ^= 0xFFFF;
+
+			a[6] = csum>>8;
+			a[7] = csum;
+		default:
+			return;
+		}
+		break;
+	case ETARP:
+		if(a+26 > end || memcmp(a, arp4, sizeof(arp4)) != 0 || (a[7] != 1 && a[7] != 2))
+			return;
+		v4tov6(ip, a+14+10*(upstream==0));
+		if(upstream){
+			memmove(mac, a+8, Eaddrlen);
+			memmove(a+8, proxy, Eaddrlen);
+		}
+		break;
+	}
+
+Again:
+	h = (	(ip[IPaddrlen-1] ^ proxy[2])<<24 |
+		(ip[IPaddrlen-2] ^ proxy[3])<<16 |
+		(ip[IPaddrlen-3] ^ proxy[4])<<8  |
+		(ip[IPaddrlen-4] ^ proxy[5]) ) % nelem(t->tab);
+	te = &t->tab[h];
+	h &= 63;
+
+	if(upstream){
+		if((mac[0]&1) != 0 || memcmp(mac, proxy, Eaddrlen) == 0)
+			return;
+		for(i=0; te->valid && i<nelem(t->tab); i++){
+			if(memcmp(te->ip, ip, IPaddrlen) == 0)
+				break;
+			if(++te >= &t->tab[nelem(t->tab)])
+				te = t->tab;
+		}
+		memmove(te->mac, mac, Eaddrlen);
+		memmove(te->ip, ip, IPaddrlen);
+		te->valid = 1;
+		t->map |= 1ULL<<h;
+		if(targ != nil){
+			memmove(ip, targ, IPaddrlen);
+			targ = nil;
+			goto Again;
+		}
+	} else {
+		if((t->map>>h & 1) == 0)
+			return;
+		for(i=0; te->valid && i<nelem(t->tab); i++){
+			if(memcmp(te->ip, ip, IPaddrlen) == 0){
+				memmove(pkt->d, te->mac, Eaddrlen);
+				return;
+			}
+			if(++te >= &t->tab[nelem(t->tab)])
+				te = t->tab;
+		}
+	}
+}
--- /dev/null
+++ b/sys/src/9/port/wifi.h
@@ -1,0 +1,116 @@
+typedef struct Wkey Wkey;
+typedef struct Wnode Wnode;
+typedef struct Wifi Wifi;
+typedef struct Wifipkt Wifipkt;
+typedef struct DMAT DMAT;
+typedef struct DMTE DMTE;
+
+enum {
+	Essidlen = 32,
+};
+
+/* cipher */
+enum {
+	TKIP	= 1,
+	CCMP	= 2,
+};
+
+struct Wkey
+{
+	int		cipher;
+	int		len;
+	uvlong		tsc;
+	uchar		key[];
+};
+
+struct Wnode
+{
+	uchar	bssid[Eaddrlen];
+	char	ssid[Essidlen+2];
+
+	char	*status;
+
+	int	rsnelen;
+	uchar	rsne[258];
+	Wkey	*txkey[1];
+	Wkey	*rxkey[5];
+
+	int	aid;		/* association id */
+	ulong	lastsend;
+	ulong	lastseen;
+
+	uchar	*minrate;	/* pointers into wifi->rates */
+	uchar	*maxrate;
+	uchar	*actrate;
+
+	ulong	txcount;	/* statistics for rate adaption */
+	ulong	txerror;
+
+	/* stuff from beacon */
+	int	ival;
+	int	cap;
+	int	channel;
+	int	brsnelen;
+	uchar	brsne[258];
+};
+
+struct DMTE
+{
+	uchar	ip[16];
+	uchar	mac[6];
+	uchar	valid;
+};
+
+struct DMAT
+{
+	DMTE	tab[127];	/* prime */
+	uvlong	map;
+};
+
+struct Wifi
+{
+	Ether	*ether;
+
+	int	debug;
+
+	RWlock	crypt;
+	Queue	*iq;
+	ulong	watchdog;
+	ulong	lastauth;
+	Ref	txseq;
+	void	(*transmit)(Wifi*, Wnode*, Block*);
+
+	/* for searching */
+	uchar	bssid[Eaddrlen];
+	char	essid[Essidlen+2];
+
+	/* supported data rates by hardware */
+	uchar	*rates;
+
+	/* effective base station */
+	Wnode	*bss;
+
+	Wnode	node[32];
+
+	DMAT	dmat;
+};
+
+struct Wifipkt
+{
+	uchar	fc[2];
+	uchar	dur[2];
+	uchar	a1[Eaddrlen];
+	uchar	a2[Eaddrlen];
+	uchar	a3[Eaddrlen];
+	uchar	seq[2];
+	uchar	a4[Eaddrlen];
+};
+
+Wifi *wifiattach(Ether *ether, void (*transmit)(Wifi*, Wnode*, Block*));
+void wifiiq(Wifi*, Block*);
+int wifihdrlen(Wifipkt*);
+void wifitxfail(Wifi*, Block*);
+
+long wifistat(Wifi*, void*, long, ulong);
+long wifictl(Wifi*, void*, long);
+void wificfg(Wifi*, char*);
--- a/sys/src/9/ppc/devether.c
+++ /dev/null
@@ -1,446 +1,0 @@
-#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"
-#include "../port/netif.h"
-
-#include "etherif.h"
-
-static Ether *etherxx[MaxEther];
-extern uchar etheraddr[];
-
-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);
-	chan->dev = ctlrno;
-	if(etherxx[ctlrno]->attach)
-		etherxx[ctlrno]->attach(etherxx[ctlrno]);
-	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)) && 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 loopback or bridged packets */
-			if(f->bridge && (tome || !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;
-					qpass(f->in, xbp);
-				}
-				else
-					ether->soverflows++;
-			}
-			else
-				etherrtrace(f, pkt, len);
-		}
-	}
-
-	if(fx){
-		if(qpass(fx->in, bp) < 0)
-			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;
-
-	ether = etherxx[chan->dev];
-	if(NETTYPE(chan->qid.path) != Ndataqid) {
-		nn = netifwrite(ether, chan, buf, n);
-		if(nn >= 0)
-			return nn;
-		if(n == sizeof("nonblocking")-1 && strncmp((char*)buf, "nonblocking", n) == 0){
-			qnoblock(ether->oq, 1);
-			return n;
-		}
-		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);
-	if(!ether->f[NETID(chan->qid.path)]->bridge)
-		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++;
-}
-
-static void
-etherreset(void)
-{
-	Ether *ether;
-	int i, n, ctlrno;
-	char name[32], buf[128];
-
-	for(ether = 0, ctlrno = 0; ctlrno < MaxEther; ctlrno++){
-		if(ether == 0)
-			ether = malloc(sizeof(Ether));
-		memset(ether, 0, sizeof(Ether));
-		ether->ctlrno = ctlrno;
-		ether->tbdf = BUSUNKNOWN;
-		ether->mbps = 10;
-		ether->minmtu = ETHERMINTU;
-		ether->maxmtu = ETHERMAXTU;
-		if(isaconfig("ether", ctlrno, ether) == 0)
-			continue;
-		for(n = 0; cards[n].type; n++){
-			if(cistrcmp(cards[n].type, ether->type))
-				continue;
-			memmove(ether->ea, etheraddr, 6);
-			for(i = 0; i < ether->nopt; i++){
-				if(strncmp(ether->opt[i], "ea=", 3))
-					continue;
-				if(parseether(ether->ea, &ether->opt[i][3]) == -1)
-					memset(ether->ea, 0, Eaddrlen);
-			}	
-			if(cards[n].reset(ether))
-				break;
-
-			/*
-			 * IRQ2 doesn't really exist, it's used to gang the interrupt
-			 * controllers together. A device set to IRQ2 will appear on
-			 * the second interrupt controller as IRQ9.
-			 */
-			if(ether->irq == 2 && BUSTYPE(ether->tbdf) != BusPCI)
-				ether->irq = 9;
-			snprint(name, sizeof(name), "ether%d", ctlrno);
-
-			/*
-			 * If ether->irq is <0, it is a hack to indicate no interrupt
-			 * used by ethersink.
-			 */
-			if(ether->irq >= 0)
-				intrenable(ether->irq, ether->interrupt, ether, name);
-			i = sprint(buf, "#l%d: %s: %dMbps port 0x%luX irq %d",
-				ctlrno, ether->type, ether->mbps, ether->port, ether->irq);
-			if(ether->mem)
-				i += sprint(buf+i, " addr 0x%luX", PADDR(ether->mem));
-			if(ether->size)
-				i += sprint(buf+i, " size 0x%luX", ether->size);
-			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);
-
-			if(ether->mbps >= 100){
-				netifinit(ether, name, Ntypes, 256*1024);
-				if(ether->oq == 0)
-					ether->oq = qopen(256*1024, Qmsg, 0, 0);
-			}
-			else{
-				netifinit(ether, name, Ntypes, 65*1024);
-				if(ether->oq == 0)
-					ether->oq = qopen(65*1024, Qmsg, 0, 0);
-			}
-			if(ether->oq == 0)
-				panic("etherreset %s", name);
-			ether->alen = Eaddrlen;
-			memmove(ether->addr, ether->ea, Eaddrlen);
-			memset(ether->bcast, 0xFF, Eaddrlen);
-
-			etherxx[ctlrno] = ether;
-			ether = 0;
-			break;
-		}
-	}
-	if(ether)
-		free(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,
-	devshutdown,
-	etherattach,
-	etherwalk,
-	etherstat,
-	etheropen,
-	ethercreate,
-	etherclose,
-	etherread,
-	etherbread,
-	etherwrite,
-	etherbwrite,
-	devremove,
-	etherwstat,
-};
--- a/sys/src/9/ppc/etherfcc.c
+++ b/sys/src/9/ppc/etherfcc.c
@@ -11,8 +11,8 @@
 #include "imm.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "../ppc/ethermii.h"
 
 #define DBG 1
@@ -719,7 +719,6 @@
 	ether->mbps = 100;	/* TO DO: could be 10mbps */
 	ether->attach = attach;
 	ether->transmit = transmit;
-	ether->interrupt = interrupt;
 	ether->ifstat = ifstat;
 
 	ether->arg = ether;
@@ -735,6 +734,8 @@
 		print("no ether address");
 		return -1;
 	}
+
+	intrenable(ether->irq, interrupt, ether, ether->name);
 
 	return 0;
 }
--- a/sys/src/9/ppc/etherif.h
+++ /dev/null
@@ -1,35 +1,0 @@
-enum {
-	MaxEther	= 24,
-	Ntypes		= 8,
-};
-
-typedef struct Ether Ether;
-struct Ether {
-	ISAConf;			/* hardware info */
-
-	int	ctlrno;
-	int	tbdf;			/* type+busno+devno+funcno */
-	int	minmtu;
-	int 	maxmtu;
-	uchar	ea[Eaddrlen];
-
-	void	(*attach)(Ether*);	/* filled in by reset routine */
-	void	(*transmit)(Ether*);
-	void	(*interrupt)(Ureg*, void*);
-	long	(*ifstat)(Ether*, void*, long, ulong);
-	long 	(*ctl)(Ether*, void*, long); /* custom ctl messages */
-	void	*ctlr;
-
-	Queue*	oq;
-
-	Netif;
-};
-
-extern Block* etheriq(Ether*, Block*, int);
-extern void addethercard(char*, int(*)(Ether*));
-extern ulong ethercrc(uchar*, int);
-
-#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))
--- a/sys/src/9/ppc/ethersaturn.c
+++ b/sys/src/9/ppc/ethersaturn.c
@@ -6,10 +6,10 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
+
 #include "msaturn.h"
 
-#include "etherif.h"
-
 enum{
 	Etcr = Saturn + 0x0c00,
 	Etsr = Saturn + 0x0c02,
@@ -209,7 +209,6 @@
 
 	ether->ctlr = ctlr;
 	ether->transmit = transmit;
-	ether->interrupt = interrupt;
 	ether->irq = Vecether;
 	ether->arg = ether;
 	memmove(ether->ea, (ushort*)Emacaddr0, Eaddrlen);
@@ -218,6 +217,9 @@
 	*eimr = Ei_rxdone|Ei_txretry|Ei_txdone;
 	
 	iprint("reset: ercr %.4uX\n", *ercr);
+
+	intrenable(ether->irq, interrupt, ether, ether->name);
+
 	return 0;
 }
 
--- a/sys/src/9/ppc/mkfile
+++ b/sys/src/9/ppc/mkfile
@@ -52,7 +52,6 @@
 HFILES=\
 	dat.h\
 	errstr.h\
-	etherif.h\
 	fns.h\
 	init.h\
 	io.h\
@@ -94,7 +93,7 @@
 
 %.$O:	$HFILES
 
-$ETHER: 			etherif.h ../port/netif.h
+$ETHER: 			../port/etherif.h ../port/netif.h
 
 init.h:	../port/initcode.c init9.s
 	$CC ../port/initcode.c
--- a/sys/src/9/sgi/dat.h
+++ b/sys/src/9/sgi/dat.h
@@ -13,6 +13,7 @@
 typedef struct Softtlb	Softtlb;
 typedef struct Ureg	Ureg;
 typedef struct Proc	Proc;
+typedef struct ISAConf	ISAConf;
 typedef uvlong		Tval;
 
 #define MAXSYSARG	5	/* for mount(fd, afd, mpt, flag, arg) */
@@ -69,6 +70,16 @@
 	int	monitor;
 	int	keyboard;
 };
+
+struct ISAConf
+{
+	char	*type;
+	ulong	port;
+	int	irq;
+	int	nopt;
+	char	*opt[1];
+};
+#define BUSUNKNOWN -1
 
 /*
  * floating point registers
--- a/sys/src/9/sgi/devether.c
+++ /dev/null
@@ -1,489 +1,0 @@
-#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 loopback or bridged packets */
-			if(f->bridge && (tome || !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);
-	if(!ether->f[NETID(chan->qid.path)]->bridge)
-		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++;
-}
-
-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->irqlevel, ether->interrupt, ether);
-
-	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;
-
-	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,
-};
--- a/sys/src/9/sgi/etherif.h
+++ /dev/null
@@ -1,40 +1,0 @@
-enum {
-	MaxEther	= 1,
-	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))
--- a/sys/src/9/sgi/etherseeq.c
+++ b/sys/src/9/sgi/etherseeq.c
@@ -5,7 +5,7 @@
 #include "fns.h"
 #include "io.h"
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 
 typedef struct Hio Hio;
 typedef struct Desc Desc;
@@ -451,19 +451,21 @@
 	edev->ctlr = &ct;
 	edev->port = HPC3_ETHER;
 	edev->irq = IRQENET;
-	edev->irqlevel = hpc3irqlevel(edev->irq);
 	edev->ctlr = &ct;
 	edev->promiscuous = promiscuous;
 	edev->multicast = multicast;
-	edev->interrupt = interrupt;
 	edev->attach = attach;
 	edev->arg = edev;
 	edev->mbps = 10;
 	edev->link = 1;
+
 	if(init(edev) < 0){
 		edev->ctlr = nil;
 		return -1;
 	}
+
+	intrenable(hpc3irqlevel(edev->irq), interrupt, edev);
+
 	return 0;
 }
 
--- a/sys/src/9/sgi/fns.h
+++ b/sys/src/9/sgi/fns.h
@@ -22,6 +22,7 @@
 int	gettlbp(ulong, ulong*);
 ulong	gettlbvirt(int);
 int	hpc3irqlevel(int);
+int	isaconfig(char*, int, ISAConf*);
 void	icflush(void *, ulong);
 void	idlehands(void);
 void	introff(int);
--- a/sys/src/9/sgi/indy
+++ b/sys/src/9/sgi/indy
@@ -10,8 +10,8 @@
 	env
 	pipe
 	dup
-	ether netif
-	ip		arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium inferno
+	ether	netif
+	ip	arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium inferno
 	ssl
 	tls
 	cap
--- a/sys/src/9/sgi/main.c
+++ b/sys/src/9/sgi/main.c
@@ -502,3 +502,9 @@
 	if(n > 0)
 		error("no watchpoints");
 }
+
+int
+isaconfig(char *, int, ISAConf*)
+{
+	return 0;
+}
--- a/sys/src/9/teg2/archtegra.c
+++ b/sys/src/9/teg2/archtegra.c
@@ -12,7 +12,7 @@
 #include "arm.h"
 
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 #include "../port/flashif.h"
 #include "../port/usb.h"
 #include "../port/portusbehci.h"
--- a/sys/src/9/teg2/devether.c
+++ /dev/null
@@ -1,502 +1,0 @@
-#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/netif.h"
-#include "etherif.h"
-
-static Ether *etherxx[MaxEther];
-
-Chan*
-etherattach(char* spec)
-{
-	int ctlrno;
-	char *p;
-	Chan *chan;
-
-	ctlrno = 0;
-	if(spec && *spec){
-		ctlrno = strtoul(spec, &p, 0);
-		if((ctlrno == 0 && p == spec) || *p != 0)
-			error(Ebadarg);
-		if(ctlrno < 0 || 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) != nil && (f->type == type || f->type < 0) &&
-		    (tome || multi || f->prom)){
-			/* Don't want to hear loopback or bridged packets */
-			if(f->bridge && (tome || !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)
-						ether->soverflows++;
-				}
-				else
-					ether->soverflows++;
-			}
-			else
-				etherrtrace(f, pkt, len);
-		}
-	}
-
-	if(fx){
-		if(qpass(fx->in, bp) < 0)
-			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);
-	if(!ether->f[NETID(chan->qid.path)]->bridge)
-		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++;
-}
-
-static void
-etherreset(void)
-{
-	Ether *ether;
-	int i, n, ctlrno;
-	char name[KNAMELEN], buf[128];
-
-	for(ether = 0, ctlrno = 0; ctlrno < MaxEther; ctlrno++){
-		if(ether == 0)
-			ether = malloc(sizeof(Ether));
-		memset(ether, 0, sizeof(Ether));
-		ether->ctlrno = ctlrno;
-		ether->mbps = 10;
-		ether->minmtu = ETHERMINTU;
-		ether->maxmtu = ETHERMAXTU;
-
-		if(archether(ctlrno, ether) <= 0)
-			continue;
-
-		if(isaconfig("ether", ctlrno, ether) == 0){
-//			free(ether);
-//			return nil;
-			continue;
-		}
-		for(n = 0; cards[n].type; n++){
-			if(cistrcmp(cards[n].type, ether->type))
-				continue;
-			for(i = 0; i < ether->nopt; i++)
-				if(cistrncmp(ether->opt[i], "ea=", 3) == 0){
-					if(parseether(ether->ea,
-					    &ether->opt[i][3]) == -1)
-						memset(ether->ea, 0, Eaddrlen);
-				} else if(cistrcmp(ether->opt[i],
-				    "100BASE-TXFD") == 0)
-					ether->mbps = 100;
-			if(cards[n].reset(ether))
-				break;
-			snprint(name, sizeof(name), "ether%d", ctlrno);
-
-			if(ether->interrupt != nil && ether->irq >= 0)
-				intrenable(ether->irq, ether->interrupt,
-					ether, 0, name);
-
-			i = snprint(buf, sizeof buf,
-				"#l%d: %s: %dMbps port %#lux irq %d",
-				ctlrno, ether->type, ether->mbps, ether->port,
-				ether->irq);
-			if(ether->mem)
-				i += snprint(buf+i, sizeof buf - i,
-					" addr %#lux", PADDR(ether->mem));
-			if(ether->size)
-				i += snprint(buf+i, sizeof buf - i,
-					" size %#luX", ether->size);
-			i += snprint(buf+i, sizeof 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]);
-			snprint(buf+i, sizeof buf - i, "\n");
-			iprint("%s", buf);  /* it may be too early for print */
-
-			if(ether->mbps >= 1000)
-				netifinit(ether, name, Ntypes, 4*1024*1024);
-			else if(ether->mbps >= 100)
-				netifinit(ether, name, Ntypes, 1024*1024);
-			else
-				netifinit(ether, name, Ntypes, 65*1024);
-			if(ether->oq == 0)
-				ether->oq = qopen(ether->limit, Qmsg, 0, 0);
-			if(ether->oq == 0)
-				panic("etherreset %s", name);
-			ether->alen = Eaddrlen;
-			memmove(ether->addr, ether->ea, Eaddrlen);
-			memset(ether->bcast, 0xFF, Eaddrlen);
-
-			etherxx[ctlrno] = ether;
-			ether = 0;
-			break;
-		}
-	}
-	if(ether)
-		free(ether);
-}
-
-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;
-}
-
-void
-dumpoq(Queue *oq)
-{
-	if (oq == nil)
-		print("no outq! ");
-	else if (qisclosed(oq))
-		print("outq closed ");
-	else if (qfull(oq))
-		print("outq full ");
-	else
-		print("outq %d ", qlen(oq));
-}
-
-void
-dumpnetif(Netif *netif)
-{
-	print("netif %s ", netif->name);
-	print("limit %d mbps %d link %d ",
-		netif->limit, netif->mbps, netif->link);
-	print("inpkts %lld outpkts %lld errs %d\n",
-		netif->inpackets, netif->outpackets,
-		netif->crcs + netif->oerrs + netif->frames + netif->overflows +
-		netif->buffs + netif->soverflows);
-}
-
-Dev etherdevtab = {
-	'l',
-	"ether",
-
-	etherreset,
-	devinit,
-	ethershutdown,
-	etherattach,
-	etherwalk,
-	etherstat,
-	etheropen,
-	ethercreate,
-	etherclose,
-	etherread,
-	etherbread,
-	etherwrite,
-	etherbwrite,
-	devremove,
-	etherwstat,
-};
--- a/sys/src/9/teg2/ether8169.c
+++ b/sys/src/9/teg2/ether8169.c
@@ -15,8 +15,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ethermii.h"
 
 typedef struct Ctlr Ctlr;
@@ -1655,7 +1655,6 @@
 
 	edev->attach = rtl8169attach;
 	edev->transmit = rtl8169transmit;
-	edev->interrupt = rtl8169interrupt;
 	edev->ifstat = rtl8169ifstat;
 
 	edev->arg = edev;
@@ -1666,6 +1665,9 @@
 	ilock(&ctlr->reglock);
 	rtl8169link(edev);
 	iunlock(&ctlr->reglock);
+
+	intrenable(edev->irq, rtl8169interrupt, edev, 0, edev->name);
+
 	return 0;
 }
 
--- a/sys/src/9/teg2/etherif.h
+++ /dev/null
@@ -1,42 +1,0 @@
-enum
-{
-	MaxEther	= 4,
-	Ntypes		= 8,
-};
-
-typedef struct Ether Ether;
-struct Ether {
-	RWlock;
-	ISAConf;			/* hardware info */
-
-	int	ctlrno;
-	ulong	tbdf;
-	int	minmtu;
-	int 	maxmtu;
-
-	Netif;
-
-	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;
-	uchar	ea[Eaddrlen];
-	void*	address;
-	int	irq;
-
-	Queue*	oq;
-};
-
-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)
--- a/sys/src/9/teg2/ethermii.c
+++ b/sys/src/9/teg2/ethermii.c
@@ -6,8 +6,8 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
+#include "../port/etherif.h"
 
-#include "etherif.h"
 #include "ethermii.h"
 
 int
--- a/sys/src/9/teg2/mem.h
+++ b/sys/src/9/teg2/mem.h
@@ -5,9 +5,6 @@
 #define MiB		1048576u		/* Mebi 0x0000000000100000 */
 #define GiB		1073741824u		/* Gibi 000000000040000000 */
 
-#define HOWMANY(x, y)	(((x)+((y)-1))/(y))
-#define ROUNDUP(x, y)	(HOWMANY((x), (y))*(y))	/* ceiling */
-#define ROUNDDN(x, y)	(((x)/(y))*(y))		/* floor */
 #define MIN(a, b)	((a) < (b)? (a): (b))
 #define MAX(a, b)	((a) > (b)? (a): (b))
 
@@ -36,7 +33,7 @@
  */
 #define	BY2PG		(4*KiB)			/* bytes per page */
 #define	PGSHIFT		12			/* log(BY2PG) */
-#define	PGROUND(s)	ROUNDUP(s, BY2PG)
+#define	PGROUND(s)	ROUND(s, BY2PG)
 #define	ROUND(s, sz)	(((s)+(sz-1))&~(sz-1))
 
 /* max # of cpus system can run.  tegra2 cpu ids are two bits wide. */
@@ -85,7 +82,6 @@
 
 #define	UZERO		0			/* user segment */
 #define	UTZERO		(UZERO+BY2PG)		/* user text start */
-#define UTROUND(t)	ROUNDUP((t), BY2PG)
 /*
  * moved USTKTOP down to 1GB to keep MMIO space out of user space.
  * moved it down another MB to utterly avoid KADDR(stack_base) mapping
--- a/sys/src/9/teg2/mkfile
+++ b/sys/src/9/teg2/mkfile
@@ -117,7 +117,7 @@
 arch.$O clock.$O fpiarm.$O main.$O mmu.$O screen.$O sdscsi.$O syscall.$O \
 	trap.$O: /$objtype/include/ureg.h
 
-archtegra.$O devether.$0 ether9221.$O: etherif.h ../port/netif.h
+archtegra.$O devether.$0 ether9221.$O: ../port/etherif.h ../port/netif.h
 archtegra.$O devflash.$O flashtegra.$O flashigep.$O: ../port/flashif.h
 ecc.$O flashtegra.$O flashigep.$O: ../port/nandecc.h io.h
 fpi.$O fpiarm.$O fpimem.$O: fpi.h
--- a/sys/src/9/teg2/mmu.c
+++ b/sys/src/9/teg2/mmu.c
@@ -693,7 +693,7 @@
 	o = pa & (BY2PG-1);
 	pa -= o;
 	size += o;
-	size = ROUNDUP(size, BY2PG);
+	size = PGROUND(size);
 
 	va = kseg0|pa;
 	pae = mmukmap(va, pa, size);
--- a/sys/src/9/xen/etherxen.c
+++ b/sys/src/9/xen/etherxen.c
@@ -10,7 +10,7 @@
 #include "io.h"
 #include "../port/error.h"
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 
 #define LOG(a)
 
@@ -478,12 +478,14 @@
 	ether->transmit = etherxentransmit;
 	ether->irq = -1;
 	ether->tbdf = BUSUNKNOWN;
-	ether->interrupt = etherxenintr;
 	ether->ifstat = ifstat;
 	ether->ctl = etherxenctl;
 	ether->promiscuous = nil;
 	ether->multicast = etherxenmulticast;
 	ether->arg = ether;
+
+	intrenable(ether->irq, etherxenintr, ether, ether->tbdf, ether->name);
+
 	return 0;
 }
 
--- a/sys/src/9/xen/mkfile
+++ b/sys/src/9/xen/mkfile
@@ -114,7 +114,7 @@
 	# import lookout / /n/lookout && cp $p$CONF $p$CONF.gz /n/lookout/$objtype/
 
 # copies generated by the rule below
-PCHEADERS=uncached.h etherif.h ethermii.h mp.h io.h
+PCHEADERS=uncached.h ethermii.h mp.h io.h
 
 REPCH=`{echo $PCHEADERS | sed 's/\.h//g; s/ /|/g'}
 ^($REPCH)\.h:R:	'../pc/\1.h'
@@ -132,7 +132,7 @@
 	$AS $AFLAGS ../pc/ptclbsum386.s
 
 # we inherited these.. revisit.
-$ETHER: 	etherif.h ../port/netif.h
+$ETHER: 	../port/etherif.h ../port/netif.h
 $SDEV:	../port/sd.h
 main.$O:	init.h reboot.h
 trap.$O:	/sys/include/tos.h
--- a/sys/src/9/xen/xenpcf
+++ b/sys/src/9/xen/xenpcf
@@ -20,7 +20,7 @@
 
 	xenstore
 	ether		netif
-	ip			arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum386 inferno
+	ip		arp chandial ip ipv6 ipaux iproute netlog nullmedium pktmedium ptclbsum386 inferno
 
 	sd
 
--- a/sys/src/9/zynq/dat.h
+++ b/sys/src/9/zynq/dat.h
@@ -161,10 +161,11 @@
 
 struct ISAConf
 {
-	int dummy;
-	char *type;
-	ulong port;
-	int irq;
+	char	*type;
+	ulong	port;
+	int	irq;
+	int	nopt;
+	char	*opt[1];
 };
 #define BUSUNKNOWN -1
 
--- a/sys/src/9/zynq/devether.c
+++ /dev/null
@@ -1,489 +1,0 @@
-#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 loopback or bridged packets */
-			if(f->bridge && (tome || !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);
-	if(!ether->f[NETID(chan->qid.path)]->bridge)
-		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++;
-}
-
-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;
-
-	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,
-};
--- a/sys/src/9/zynq/etherif.h
+++ /dev/null
@@ -1,40 +1,0 @@
-enum {
-	MaxEther	= 1,
-	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))
--- a/sys/src/9/zynq/etherzynq.c
+++ b/sys/src/9/zynq/etherzynq.c
@@ -5,7 +5,7 @@
 #include "fns.h"
 #include "io.h"
 #include "../port/netif.h"
-#include "etherif.h"
+#include "../port/etherif.h"
 
 #define Rbsz		ROUNDUP(sizeof(Etherpkt)+16, 64)
 
@@ -409,9 +409,7 @@
 	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->promiscuous = ethprom;
@@ -423,6 +421,8 @@
 		edev->ctlr = nil;
 		return -1;
 	}
+
+	intrenable(edev->irq, ethirq, edev, LEVEL, edev->name);
 	return 0;
 }
 
--- a/sys/src/9/zynq/main.c
+++ b/sys/src/9/zynq/main.c
@@ -306,7 +306,7 @@
 int
 isaconfig(char *, int, ISAConf*)
 {
-	return 1;
+	return 0;
 }
 
 void