Browse Source

import gpl nix into /sys/src/9

David du Colombier 5 years ago
parent
commit
f4a1a0881a
100 changed files with 42119 additions and 0 deletions
  1. 87 0
      sys/src/9/386/aoe.h
  2. 554 0
      sys/src/9/386/devether.c
  3. 469 0
      sys/src/9/386/devrtc.c
  4. 1215 0
      sys/src/9/386/ether8169.c
  5. 1342 0
      sys/src/9/386/ether82557.c
  6. 1745 0
      sys/src/9/386/ether82563.c
  7. 2003 0
      sys/src/9/386/etherigbe.c
  8. 1646 0
      sys/src/9/386/etherm10g.c
  9. 648 0
      sys/src/9/386/kbd.c
  10. 767 0
      sys/src/9/386/pci.c
  11. 146 0
      sys/src/9/386/random.c
  12. 794 0
      sys/src/9/386/uarti8250.c
  13. 189 0
      sys/src/9/386/uartpci.c
  14. 22 0
      sys/src/9/bench/1/kern
  15. 20 0
      sys/src/9/bench/1/output
  16. 26 0
      sys/src/9/bench/1/runbench
  17. 15 0
      sys/src/9/bench/Benchs
  18. 29 0
      sys/src/9/bench/Locks
  19. 5 0
      sys/src/9/bench/Mean
  20. 85 0
      sys/src/9/bench/README
  21. 23 0
      sys/src/9/bench/Time
  22. 42 0
      sys/src/9/bench/runbenchs
  23. 37 0
      sys/src/9/bench/tools
  24. 193 0
      sys/src/9/boot/aux.c
  25. 356 0
      sys/src/9/boot/boot.c
  26. 81 0
      sys/src/9/boot/boot.h
  27. 82 0
      sys/src/9/boot/bootauth.c
  28. 89 0
      sys/src/9/boot/bootcache.c
  29. 213 0
      sys/src/9/boot/bootip.c
  30. 135 0
      sys/src/9/boot/doauthenticate.c
  31. 83 0
      sys/src/9/boot/embed.c
  32. 52 0
      sys/src/9/boot/getpasswd.c
  33. 284 0
      sys/src/9/boot/local.c
  34. 61 0
      sys/src/9/boot/nopsession.c
  35. 76 0
      sys/src/9/boot/paq.c
  36. 31 0
      sys/src/9/boot/printstub.c
  37. 59 0
      sys/src/9/boot/sac.c
  38. 158 0
      sys/src/9/boot/settime.c
  39. 689 0
      sys/src/9/ip/arp.c
  40. 133 0
      sys/src/9/ip/chandial.c
  41. 1425 0
      sys/src/9/ip/devip.c
  42. 794 0
      sys/src/9/ip/ethermedium.c
  43. 290 0
      sys/src/9/ip/gre.c
  44. 501 0
      sys/src/9/ip/icmp.c
  45. 908 0
      sys/src/9/ip/icmp6.c
  46. 50 0
      sys/src/9/ip/inferno.c
  47. 814 0
      sys/src/9/ip/ip.c
  48. 654 0
      sys/src/9/ip/ip.h
  49. 377 0
      sys/src/9/ip/ipaux.c
  50. 1663 0
      sys/src/9/ip/ipifc.c
  51. 861 0
      sys/src/9/ip/iproute.c
  52. 738 0
      sys/src/9/ip/ipv6.c
  53. 194 0
      sys/src/9/ip/ipv6.h
  54. 129 0
      sys/src/9/ip/loopbackmedium.c
  55. 162 0
      sys/src/9/ip/netdevmedium.c
  56. 272 0
      sys/src/9/ip/netlog.c
  57. 48 0
      sys/src/9/ip/nullmedium.c
  58. 88 0
      sys/src/9/ip/pktmedium.c
  59. 81 0
      sys/src/9/ip/ptclbsum.c
  60. 3264 0
      sys/src/9/ip/tcp.c
  61. 660 0
      sys/src/9/ip/udp.c
  62. 2 0
      sys/src/9/k10/Linux
  63. 332 0
      sys/src/9/k10/acore.c
  64. 391 0
      sys/src/9/k10/acore.c.old
  65. 419 0
      sys/src/9/k10/acpi.h
  66. 205 0
      sys/src/9/k10/amd64.h
  67. 416 0
      sys/src/9/k10/apic.c
  68. 101 0
      sys/src/9/k10/apic.h
  69. 115 0
      sys/src/9/k10/arch.c
  70. 380 0
      sys/src/9/k10/archk10.c
  71. 256 0
      sys/src/9/k10/archk8.c
  72. 438 0
      sys/src/9/k10/asm.c
  73. 5 0
      sys/src/9/k10/boot.fs
  74. 172 0
      sys/src/9/k10/cga.c
  75. 25 0
      sys/src/9/k10/cpuidamd64.s
  76. 138 0
      sys/src/9/k10/crap.c
  77. 430 0
      sys/src/9/k10/dat.h
  78. 1721 0
      sys/src/9/k10/devacpi.c
  79. 632 0
      sys/src/9/k10/devarch.c
  80. 2160 0
      sys/src/9/k10/ether82563.c
  81. 895 0
      sys/src/9/k10/etherbcm.c
  82. 60 0
      sys/src/9/k10/etherif.h
  83. 261 0
      sys/src/9/k10/fns.h
  84. 549 0
      sys/src/9/k10/fpu.c
  85. 475 0
      sys/src/9/k10/fpu.c.old
  86. 176 0
      sys/src/9/k10/i8254.c
  87. 238 0
      sys/src/9/k10/i8259.c
  88. 16 0
      sys/src/9/k10/init9.c
  89. 275 0
      sys/src/9/k10/io.h
  90. 490 0
      sys/src/9/k10/ioapic.c
  91. 24 0
      sys/src/9/k10/iob.h
  92. 198 0
      sys/src/9/k10/k8cpu
  93. 193 0
      sys/src/9/k10/k8cpufs
  94. 202 0
      sys/src/9/k10/k8cpukexec
  95. 235 0
      sys/src/9/k10/l32p.s
  96. 341 0
      sys/src/9/k10/l64acidt.s
  97. 80 0
      sys/src/9/k10/l64acsyscall.s
  98. 26 0
      sys/src/9/k10/l64cpuid.s
  99. 46 0
      sys/src/9/k10/l64fpu.s
  100. 344 0
      sys/src/9/k10/l64idt.s

+ 87 - 0
sys/src/9/386/aoe.h

@@ -0,0 +1,87 @@
+/* 
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+/*
+ * ATA-over-Ethernet (AoE) protocol
+ */
+enum {
+	ACata,
+	ACconfig,
+};
+
+enum {
+	AQCread,
+	AQCtest,
+	AQCprefix,
+	AQCset,
+	AQCfset,
+};
+
+enum {
+	AEcmd	= 1,
+	AEarg,
+	AEdev,
+	AEcfg,
+	AEver,
+};
+
+enum {
+	Aoetype	= 0x88a2,
+	Aoesectsz = 512,			/* standard sector size */
+	Aoever	= 1,
+
+	AFerr	= 1<<2,
+	AFrsp	= 1<<3,
+
+	AAFwrite= 1,
+	AAFext	= 1<<6,
+};
+
+typedef struct {
+	uchar	dst[Eaddrlen];
+	uchar	src[Eaddrlen];
+	uchar	type[2];
+	uchar	verflag;
+	uchar	error;
+	uchar	major[2];
+	uchar	minor;
+	uchar	cmd;
+	uchar	tag[4];
+	uchar	payload[];
+} Aoehdr;
+
+#define AOEHDRSZ	offsetof(Aoehdr, payload[0])
+
+typedef struct {
+	Aoehdr;
+	uchar	aflag;
+	uchar	errfeat;
+	uchar	scnt;
+	uchar	cmdstat;
+	uchar	lba[6];
+	uchar	res[2];
+	uchar	payload[];
+} Aoeata;
+
+#define AOEATASZ	offsetof(Aoeata, payload[0])
+
+typedef struct {
+	Aoehdr;
+	uchar	bufcnt[2];
+	uchar	fwver[2];
+	uchar	scnt;
+	uchar	verccmd;
+	uchar	cslen[2];
+	uchar	payload[];
+} Aoeqc;
+
+#define AOEQCSZ		offsetof(Aoeqc, payload[0])
+
+extern char Echange[];
+extern char Enotup[];

+ 554 - 0
sys/src/9/386/devether.c

@@ -0,0 +1,554 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#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)
+{
+	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->devno = 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->devno], chan, nchan, name, nname);
+}
+
+static long
+etherstat(Chan* chan, uchar* dp, long n)
+{
+	return netifstat(etherxx[chan->devno], chan, dp, n);
+}
+
+static Chan*
+etheropen(Chan* chan, int omode)
+{
+	return netifopen(etherxx[chan->devno], chan, omode);
+}
+
+static void
+ethercreate(Chan*, char*, int, int)
+{
+}
+
+static void
+etherclose(Chan* chan)
+{
+	netifclose(etherxx[chan->devno], chan);
+}
+
+static long
+etherread(Chan* chan, void* buf, long n, vlong off)
+{
+	Ether *ether;
+	ulong offset = off;
+
+	ether = etherxx[chan->devno];
+	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, vlong offset)
+{
+	return netifbread(etherxx[chan->devno], chan, n, offset);
+}
+
+static long
+etherwstat(Chan* chan, uchar* dp, long n)
+{
+	return netifwstat(etherxx[chan->devno], chan, dp, n);
+}
+
+static void
+etherrtrace(Netfile* f, Etherpkt* pkt, int len)
+{
+	int i, n;
+	Block *bp;
+
+	if(qwindow(f->iq) <= 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(sys->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->iq, 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 || f->bridge & 2){
+			/* Don't want to hear bridged packets */
+			if(f->bridge && !fromwire && !fromme)
+				continue;
+			if(!f->headersonly){
+				if(fromwire && fx == 0)
+					fx = f;
+				else if(xbp = iallocb(len)){
+					memmove(xbp->wp, pkt, len);
+					xbp->wp += len;
+					if(qpass(f->iq, xbp) < 0)
+						ether->soverflows++;
+				}
+				else
+					ether->soverflows++;
+			}
+			else
+				etherrtrace(f, pkt, len);
+		}
+	}
+
+	if(fx){
+		if(qpass(fx->iq, bp) < 0)
+			ether->soverflows++;
+		return 0;
+	}
+	if(fromwire){
+		freeb(bp);
+		return 0;
+	}
+
+	return bp;
+}
+
+static int
+etheroq(Ether* ether, Block* bp)
+{
+	int len, loopback, s;
+	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){
+		s = splhi();
+		etheriq(ether, bp, 0);
+		splx(s);
+	}
+
+	if(!loopback){
+		qbwrite(ether->oq, bp);
+		if(ether->transmit != nil)
+			ether->transmit(ether);
+	} else
+		freeb(bp);
+
+	return len;
+}
+
+static long
+etherwrite(Chan* chan, void* buf, long n, vlong)
+{
+	Ether *ether;
+	Block *bp;
+	int nn, onoff;
+	Cmdbuf *cb;
+
+	ether = etherxx[chan->devno];
+	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->mtu)
+		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 & 2) == 0)
+		memmove(bp->rp+Eaddrlen, ether->ea, Eaddrlen);
+	poperror();
+	bp->wp += n;
+
+	return etheroq(ether, bp);
+}
+
+static long
+etherbwrite(Chan* chan, Block* bp, vlong)
+{
+	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->devno];
+
+	if(n > ether->mtu){
+		freeb(bp);
+		error(Etoobig);
+	}
+	if(n < ether->minmtu){
+		freeb(bp);
+		error(Etoosmall);
+	}
+
+	return etheroq(ether, bp);
+}
+
+static struct {
+	char*	type;
+	int	(*reset)(Ether*);
+} cards[MaxEther+1];
+
+void
+addethercard(char* t, int (*r)(Ether*))
+{
+	static int ncard;
+
+	if(ncard == MaxEther)
+		panic("too many ether cards");
+	cards[ncard].type = t;
+	cards[ncard].reset = r;
+	ncard++;
+}
+
+int
+parseether(uchar *to, char *from)
+{
+	char nip[4];
+	char *p;
+	int i;
+
+	p = from;
+	for(i = 0; i < Eaddrlen; i++){
+		if(*p == 0)
+			return -1;
+		nip[0] = *p++;
+		if(*p == 0)
+			return -1;
+		nip[1] = *p++;
+		nip[2] = 0;
+		to[i] = strtoul(nip, 0, 16);
+		if(*p == ':')
+			p++;
+	}
+	return 0;
+}
+
+static Ether*
+etherprobe(int cardno, int ctlrno)
+{
+	int i, j;
+	Ether *ether;
+	char buf[128], name[32];
+
+	ether = malloc(sizeof(Ether));
+	memset(ether, 0, sizeof(Ether));
+	ether->ctlrno = ctlrno;
+	ether->tbdf = BUSUNKNOWN;
+	ether->mbps = 10;
+	ether->minmtu = ETHERMINTU;
+	ether->mtu = ETHERMAXTU;
+	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 %#p irq %d tu %d",
+		ctlrno, cards[cardno].type, ether->mbps, ether->port, ether->irq, ether->mtu);
+	if(ether->mem)
+		i += sprint(buf+i, " addr %#p", 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);
+
+	j = ether->mbps;
+	if(j > 1000)
+		j *= 10;
+	for(i = 0; j >= 100; i++)
+		j /= 10;
+	i = (128<<i)*1024;
+	netifinit(ether, name, Ntypes, i);
+	if(ether->oq == 0)
+		ether->oq = qopen(i, 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);
+
+	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)
+{
+	char name[32];
+	int i;
+	Ether *ether;
+
+	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;
+		}
+		snprint(name, sizeof(name), "ether%d", i);
+		if(ether->irq >= 0){
+		//	intrdisable(ether->irq, ether->interrupt, ether, ether->tbdf, name);
+		}
+		(*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,
+};

+ 469 - 0
sys/src/9/386/devrtc.c

@@ -0,0 +1,469 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+#include	"u.h"
+#include	"../port/lib.h"
+#include	"mem.h"
+#include	"dat.h"
+#include	"fns.h"
+#include	"../port/error.h"
+
+/*
+ *  real time clock and non-volatile ram
+ */
+
+enum {
+	Paddr=		0x70,	/* address port */
+	Pdata=		0x71,	/* data port */
+
+	Seconds=	0x00,
+	Minutes=	0x02,
+	Hours=		0x04,
+	Mday=		0x07,
+	Month=		0x08,
+	Year=		0x09,
+	Status=		0x0A,
+
+	Nvoff=		128,	/* where usable nvram lives */
+	Nvsize=		256,
+
+	Nbcd=		6,
+};
+
+typedef struct Rtc	Rtc;
+struct Rtc
+{
+	int	sec;
+	int	min;
+	int	hour;
+	int	mday;
+	int	mon;
+	int	year;
+};
+
+
+enum{
+	Qdir = 0,
+	Qrtc,
+	Qnvram,
+};
+
+Dirtab rtcdir[]={
+	".",	{Qdir, 0, QTDIR},	0,	0555,
+	"nvram",	{Qnvram, 0},	Nvsize,	0664,
+	"rtc",		{Qrtc, 0},	0,	0664,
+};
+
+static ulong rtc2sec(Rtc*);
+static void sec2rtc(ulong, Rtc*);
+
+void
+rtcinit(void)
+{
+	if(ioalloc(Paddr, 2, 0, "rtc/nvr") < 0)
+		panic("rtcinit: ioalloc failed");
+}
+
+static Chan*
+rtcattach(char* spec)
+{
+	return devattach('r', spec);
+}
+
+static Walkqid*	 
+rtcwalk(Chan* c, Chan *nc, char** name, int nname)
+{
+	return devwalk(c, nc, name, nname, rtcdir, nelem(rtcdir), devgen);
+}
+
+static long	 
+rtcstat(Chan* c, uchar* dp, long n)
+{
+	return devstat(c, dp, n, rtcdir, nelem(rtcdir), devgen);
+}
+
+static Chan*
+rtcopen(Chan* c, int omode)
+{
+	omode = openmode(omode);
+	switch((ulong)c->qid.path){
+	case Qrtc:
+		if(strcmp(up->user, eve)!=0 && omode!=OREAD)
+			error(Eperm);
+		break;
+	case Qnvram:
+		if(strcmp(up->user, eve)!=0)
+			error(Eperm);
+	}
+	return devopen(c, omode, rtcdir, nelem(rtcdir), devgen);
+}
+
+static void	 
+rtcclose(Chan*)
+{
+}
+
+#define GETBCD(o) ((bcdclock[o]&0xf) + 10*(bcdclock[o]>>4))
+
+static long	 
+rtcextract(void)
+{
+	uchar bcdclock[Nbcd];
+	Rtc rtc;
+	int i;
+
+	/* don't do the read until the clock is no longer busy */
+	for(i = 0; i < 10000; i++){
+		outb(Paddr, Status);
+		if(inb(Pdata) & 0x80)
+			continue;
+
+		/* read clock values */
+		outb(Paddr, Seconds);	bcdclock[0] = inb(Pdata);
+		outb(Paddr, Minutes);	bcdclock[1] = inb(Pdata);
+		outb(Paddr, Hours);	bcdclock[2] = inb(Pdata);
+		outb(Paddr, Mday);	bcdclock[3] = inb(Pdata);
+		outb(Paddr, Month);	bcdclock[4] = inb(Pdata);
+		outb(Paddr, Year);	bcdclock[5] = inb(Pdata);
+
+		outb(Paddr, Status);
+		if((inb(Pdata) & 0x80) == 0)
+			break;
+	}
+
+	/*
+	 *  convert from BCD
+	 */
+	rtc.sec = GETBCD(0);
+	rtc.min = GETBCD(1);
+	rtc.hour = GETBCD(2);
+	rtc.mday = GETBCD(3);
+	rtc.mon = GETBCD(4);
+	rtc.year = GETBCD(5);
+
+	/*
+	 *  the world starts jan 1 1970
+	 */
+	if(rtc.year < 70)
+		rtc.year += 2000;
+	else
+		rtc.year += 1900;
+	return rtc2sec(&rtc);
+}
+
+static Lock nvrtlock;
+
+long
+rtctime(void)
+{
+	int i;
+	long t, ot;
+
+	ilock(&nvrtlock);
+
+	/* loop till we get two reads in a row the same */
+	t = rtcextract();
+	for(i = 0; i < 100; i++){
+		ot = rtcextract();
+		if(ot == t)
+			break;
+	}
+	iunlock(&nvrtlock);
+
+	if(i == 100) print("we are boofheads\n");
+
+	return t;
+}
+
+static long	 
+rtcread(Chan* c, void* buf, long n, vlong off)
+{
+	ulong t;
+	char *a, *start;
+	ulong offset = off;
+
+	if(c->qid.type & QTDIR)
+		return devdirread(c, buf, n, rtcdir, nelem(rtcdir), devgen);
+
+	switch((ulong)c->qid.path){
+	case Qrtc:
+		t = rtctime();
+		n = readnum(offset, buf, n, t, 12);
+		return n;
+	case Qnvram:
+		if(n == 0)
+			return 0;
+		if(n > Nvsize)
+			n = Nvsize;
+		a = start = smalloc(n);
+
+		ilock(&nvrtlock);
+		for(t = offset; t < offset + n; t++){
+			if(t >= Nvsize)
+				break;
+			outb(Paddr, Nvoff+t);
+			*a++ = inb(Pdata);
+		}
+		iunlock(&nvrtlock);
+
+		if(waserror()){
+			free(start);
+			nexterror();
+		}
+		memmove(buf, start, t - offset);
+		poperror();
+
+		free(start);
+		return t - offset;
+	}
+	error(Ebadarg);
+	return 0;
+}
+
+#define PUTBCD(n,o) bcdclock[o] = (n % 10) | (((n / 10) % 10)<<4)
+
+static long	 
+rtcwrite(Chan* c, void* buf, long n, vlong off)
+{
+	int t;
+	char *a, *start;
+	Rtc rtc;
+	ulong secs;
+	uchar bcdclock[Nbcd];
+	char *cp, *ep;
+	ulong offset = off;
+
+	if(offset!=0)
+		error(Ebadarg);
+
+
+	switch((ulong)c->qid.path){
+	case Qrtc:
+		/*
+		 *  read the time
+		 */
+		cp = ep = buf;
+		ep += n;
+		while(cp < ep){
+			if(*cp>='0' && *cp<='9')
+				break;
+			cp++;
+		}
+		secs = strtoul(cp, 0, 0);
+
+		/*
+		 *  convert to bcd
+		 */
+		sec2rtc(secs, &rtc);
+		PUTBCD(rtc.sec, 0);
+		PUTBCD(rtc.min, 1);
+		PUTBCD(rtc.hour, 2);
+		PUTBCD(rtc.mday, 3);
+		PUTBCD(rtc.mon, 4);
+		PUTBCD(rtc.year, 5);
+
+		/*
+		 *  write the clock
+		 */
+		ilock(&nvrtlock);
+		outb(Paddr, Seconds);	outb(Pdata, bcdclock[0]);
+		outb(Paddr, Minutes);	outb(Pdata, bcdclock[1]);
+		outb(Paddr, Hours);	outb(Pdata, bcdclock[2]);
+		outb(Paddr, Mday);	outb(Pdata, bcdclock[3]);
+		outb(Paddr, Month);	outb(Pdata, bcdclock[4]);
+		outb(Paddr, Year);	outb(Pdata, bcdclock[5]);
+		iunlock(&nvrtlock);
+		return n;
+	case Qnvram:
+		if(n == 0)
+			return 0;
+		if(n > Nvsize)
+			n = Nvsize;
+	
+		start = a = smalloc(n);
+		if(waserror()){
+			free(start);
+			nexterror();
+		}
+		memmove(a, buf, n);
+		poperror();
+
+		ilock(&nvrtlock);
+		for(t = offset; t < offset + n; t++){
+			if(t >= Nvsize)
+				break;
+			outb(Paddr, Nvoff+t);
+			outb(Pdata, *a++);
+		}
+		iunlock(&nvrtlock);
+
+		free(start);
+		return t - offset;
+	}
+	error(Ebadarg);
+	return 0;
+}
+
+Dev rtcdevtab = {
+	'r',
+	"rtc",
+
+	devreset,
+	rtcinit,
+	devshutdown,
+	rtcattach,
+	rtcwalk,
+	rtcstat,
+	rtcopen,
+	devcreate,
+	rtcclose,
+	rtcread,
+	devbread,
+	rtcwrite,
+	devbwrite,
+	devremove,
+	devwstat,
+};
+
+#define SEC2MIN 60L
+#define SEC2HOUR (60L*SEC2MIN)
+#define SEC2DAY (24L*SEC2HOUR)
+
+/*
+ *  days per month plus days/year
+ */
+static	int	dmsize[] =
+{
+	365, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+static	int	ldmsize[] =
+{
+	366, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+};
+
+/*
+ *  return the days/month for the given year
+ */
+static int*
+yrsize(int y)
+{
+	if((y%4) == 0 && ((y%100) != 0 || (y%400) == 0))
+		return ldmsize;
+	else
+		return dmsize;
+}
+
+/*
+ *  compute seconds since Jan 1 1970
+ */
+static ulong
+rtc2sec(Rtc *rtc)
+{
+	ulong secs;
+	int i;
+	int *d2m;
+
+	secs = 0;
+
+	/*
+	 *  seconds per year
+	 */
+	for(i = 1970; i < rtc->year; i++){
+		d2m = yrsize(i);
+		secs += d2m[0] * SEC2DAY;
+	}
+
+	/*
+	 *  seconds per month
+	 */
+	d2m = yrsize(rtc->year);
+	for(i = 1; i < rtc->mon; i++)
+		secs += d2m[i] * SEC2DAY;
+
+	secs += (rtc->mday-1) * SEC2DAY;
+	secs += rtc->hour * SEC2HOUR;
+	secs += rtc->min * SEC2MIN;
+	secs += rtc->sec;
+
+	return secs;
+}
+
+/*
+ *  compute rtc from seconds since Jan 1 1970
+ */
+static void
+sec2rtc(ulong secs, Rtc *rtc)
+{
+	int d;
+	long hms, day;
+	int *d2m;
+
+	/*
+	 * break initial number into days
+	 */
+	hms = secs % SEC2DAY;
+	day = secs / SEC2DAY;
+	if(hms < 0) {
+		hms += SEC2DAY;
+		day -= 1;
+	}
+
+	/*
+	 * generate hours:minutes:seconds
+	 */
+	rtc->sec = hms % 60;
+	d = hms / 60;
+	rtc->min = d % 60;
+	d /= 60;
+	rtc->hour = d;
+
+	/*
+	 * year number
+	 */
+	if(day >= 0)
+		for(d = 1970; day >= *yrsize(d); d++)
+			day -= *yrsize(d);
+	else
+		for (d = 1970; day < 0; d--)
+			day += *yrsize(d-1);
+	rtc->year = d;
+
+	/*
+	 * generate month
+	 */
+	d2m = yrsize(rtc->year);
+	for(d = 1; day >= d2m[d]; d++)
+		day -= d2m[d];
+	rtc->mday = day + 1;
+	rtc->mon = d;
+
+	return;
+}
+
+uchar
+nvramread(int addr)
+{
+	uchar data;
+
+	ilock(&nvrtlock);
+	outb(Paddr, addr);
+	data = inb(Pdata);
+	iunlock(&nvrtlock);
+
+	return data;
+}
+
+void
+nvramwrite(int addr, uchar data)
+{
+	ilock(&nvrtlock);
+	outb(Paddr, addr);
+	outb(Pdata, data);
+	iunlock(&nvrtlock);
+}

+ 1215 - 0
sys/src/9/386/ether8169.c

@@ -0,0 +1,1215 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+/*
+ * Realtek RTL8110S/8169S.
+ * Mostly there. There are some magic register values used
+ * which are not described in any datasheet or driver but seem
+ * to be necessary.
+ * No tuning has been done. Only tested on an RTL8110S, there
+ * are slight differences between the chips in the series so some
+ * tweaks may be needed.
+ */
+#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/ethermii.h"
+#include "../port/netif.h"
+
+#include "etherif.h"
+
+enum {					/* registers */
+	Idr0		= 0x00,		/* MAC address */
+	Mar0		= 0x08,		/* Multicast address */
+	Dtccr		= 0x10,		/* Dump Tally Counter Command */
+	Tnpds		= 0x20,		/* Transmit Normal Priority Descriptors */
+	Thpds		= 0x28,		/* Transmit High Priority Descriptors */
+	Flash		= 0x30,		/* Flash Memory Read/Write */
+	Erbcr		= 0x34,		/* Early Receive Byte Count */
+	Ersr		= 0x36,		/* Early Receive Status */
+	Cr		= 0x37,		/* Command Register */
+	Tppoll		= 0x38,		/* Transmit Priority Polling */
+	Imr		= 0x3C,		/* Interrupt Mask */
+	Isr		= 0x3E,		/* Interrupt Status */
+	Tcr		= 0x40,		/* Transmit Configuration */
+	Rcr		= 0x44,		/* Receive Configuration */
+	Tctr		= 0x48,		/* Timer Count */
+	Mpc		= 0x4C,		/* Missed Packet Counter */
+	Cr9346		= 0x50,		/* 9346 Command Register */
+	Config0		= 0x51,		/* Configuration Register 0 */
+	Config1		= 0x52,		/* Configuration Register 1 */
+	Config2		= 0x53,		/* Configuration Register 2 */
+	Config3		= 0x54,		/* Configuration Register 3 */
+	Config4		= 0x55,		/* Configuration Register 4 */
+	Config5		= 0x56,		/* Configuration Register 5 */
+	Timerint	= 0x58,		/* Timer Interrupt */
+	Mulint		= 0x5C,		/* Multiple Interrupt Select */
+	Phyar		= 0x60,		/* PHY Access */
+	Tbicsr0		= 0x64,		/* TBI Control and Status */
+	Tbianar		= 0x68,		/* TBI Auto-Negotiation Advertisment */
+	Tbilpar		= 0x6A,		/* TBI Auto-Negotiation Link Partner */
+
+	Rms		= 0xDA,		/* Receive Packet Maximum Size */
+	Cplusc		= 0xE0,		/* C+ Command */
+	Rdsar		= 0xE4,		/* Receive Descriptor Start Address */
+	Mtps		= 0xEC,		/* Max. Transmit Packet Size */
+};
+
+enum {					/* Dtccr */
+	Cmd		= 0x00000008,	/* Command */
+};
+
+enum {					/* Cr */
+	Te		= 0x04,		/* Transmitter Enable */
+	Re		= 0x08,		/* Receiver Enable */
+	Rst		= 0x10,		/* Software Reset */
+};
+
+enum {					/* Tppoll */
+	Fswint		= 0x01,		/* Forced Software Interrupt */
+	Npq		= 0x40,		/* Normal Priority Queue polling */
+	Hpq		= 0x80,		/* High Priority Queue polling */
+};
+
+enum {					/* Imr/Isr */
+	Rok		= 0x0001,	/* Receive OK */
+	Rer		= 0x0002,	/* Receive Error */
+	Tok		= 0x0004,	/* Transmit OK */
+	Ter		= 0x0008,	/* Transmit Error */
+	Rdu		= 0x0010,	/* Receive Descriptor Unavailable */
+	Punlc		= 0x0020,	/* Packet Underrun or Link Change */
+	Fovw		= 0x0040,	/* Receive FIFO Overflow */
+	Tdu		= 0x0080,	/* Transmit Descriptor Unavailable */
+	Swint		= 0x0100,	/* Software Interrupt */
+	Timeout		= 0x4000,	/* Timer */
+	Serr		= 0x8000,	/* System Error */
+};
+
+enum {					/* Tcr */
+	MtxdmaSHIFT	= 8,		/* Max. DMA Burst Size */
+	MtxdmaMASK	= 0x00000700,
+	Mtxdmaunlimited	= 0x00000700,
+	Acrc		= 0x00010000,	/* Append CRC (not) */
+	Lbk0		= 0x00020000,	/* Loopback Test 0 */
+	Lbk1		= 0x00040000,	/* Loopback Test 1 */
+	Ifg2		= 0x00080000,	/* Interframe Gap 2 */
+	HwveridSHIFT	= 23,		/* Hardware Version ID */
+	HwveridMASK	= 0x7C800000,
+	Macv01		= 0x00000000,	/* RTL8169 */
+	Macv02		= 0x00800000,	/* RTL8169S/8110S */
+	Macv03		= 0x04000000,	/* RTL8169S/8110S */
+	Macv04		= 0x10000000,	/* RTL8169SB/8110SB */
+	Macv05		= 0x18000000,	/* RTL8169SC/8110SC */
+	Macv11		= 0x30000000,	/* RTL8168B/8111B */
+	Macv12		= 0x38000000,	/* RTL8169B/8111B */
+	Macv13		= 0x34000000,	/* RTL8101E */
+	Macv14		= 0x30800000,	/* RTL8100E */
+	Macv15		= 0x38800000,	/* RTL8100E */
+	Ifg0		= 0x01000000,	/* Interframe Gap 0 */
+	Ifg1		= 0x02000000,	/* Interframe Gap 1 */
+};
+
+enum {					/* Rcr */
+	Aap		= 0x00000001,	/* Accept All Packets */
+	Apm		= 0x00000002,	/* Accept Physical Match */
+	Am		= 0x00000004,	/* Accept Multicast */
+	Ab		= 0x00000008,	/* Accept Broadcast */
+	Ar		= 0x00000010,	/* Accept Runt */
+	Aer		= 0x00000020,	/* Accept Error */
+	Sel9356		= 0x00000040,	/* 9356 EEPROM used */
+	MrxdmaSHIFT	= 8,		/* Max. DMA Burst Size */
+	MrxdmaMASK	= 0x00000700,
+	Mrxdmaunlimited	= 0x00000700,
+	RxfthSHIFT	= 13,		/* Receive Buffer Length */
+	RxfthMASK	= 0x0000E000,
+	Rxfth256	= 0x00008000,
+	Rxfthnone	= 0x0000E000,
+	Rer8		= 0x00010000,	/* Accept Error Packets > 8 bytes */
+	MulERINT	= 0x01000000,	/* Multiple Early Interrupt Select */
+};
+
+enum {					/* Cr9346 */
+	Eedo		= 0x01,		/* */
+	Eedi		= 0x02,		/* */
+	Eesk		= 0x04,		/* */
+	Eecs		= 0x08,		/* */
+	Eem0		= 0x40,		/* Operating Mode */
+	Eem1		= 0x80,
+};
+
+enum {					/* Phyar */
+	DataMASK	= 0x0000FFFF,	/* 16-bit GMII/MII Register Data */
+	DataSHIFT	= 0,
+	RegaddrMASK	= 0x001F0000,	/* 5-bit GMII/MII Register Address */
+	RegaddrSHIFT	= 16,
+	Flag		= 0x80000000,	/* */
+};
+
+enum {					/* Cplusc */
+	Mulrw		= 0x0008,	/* PCI Multiple R/W Enable */
+	Dac		= 0x0010,	/* PCI Dual Address Cycle Enable */
+	Rxchksum	= 0x0020,	/* Receive Checksum Offload Enable */
+	Rxvlan		= 0x0040,	/* Receive VLAN De-tagging Enable */
+	Endian		= 0x0200,	/* Endian Mode */
+};
+
+typedef struct D D;			/* Transmit/Receive Descriptor */
+struct D {
+	u32int	control;
+	u32int	vlan;
+	u32int	addrlo;
+	u32int	addrhi;
+};
+
+enum {					/* Transmit Descriptor control */
+	TxflMASK	= 0x0000FFFF,	/* Transmit Frame Length */
+	TxflSHIFT	= 0,
+	Tcps		= 0x00010000,	/* TCP Checksum Offload */
+	Udpcs		= 0x00020000,	/* UDP Checksum Offload */
+	Ipcs		= 0x00040000,	/* IP Checksum Offload */
+	Lgsen		= 0x08000000,	/* Large Send */
+};
+
+enum {					/* Receive Descriptor control */
+	RxflMASK	= 0x00003FFF,	/* Receive Frame Length */
+	RxflSHIFT	= 0,
+	Tcpf		= 0x00004000,	/* TCP Checksum Failure */
+	Udpf		= 0x00008000,	/* UDP Checksum Failure */
+	Ipf		= 0x00010000,	/* IP Checksum Failure */
+	Pid0		= 0x00020000,	/* Protocol ID0 */
+	Pid1		= 0x00040000,	/* Protocol ID1 */
+	Crce		= 0x00080000,	/* CRC Error */
+	Runt		= 0x00100000,	/* Runt Packet */
+	Res		= 0x00200000,	/* Receive Error Summary */
+	Rwt		= 0x00400000,	/* Receive Watchdog Timer Expired */
+	Fovf		= 0x00800000,	/* FIFO Overflow */
+	Bovf		= 0x01000000,	/* Buffer Overflow */
+	Bar		= 0x02000000,	/* Broadcast Address Received */
+	Pam		= 0x04000000,	/* Physical Address Matched */
+	Mar		= 0x08000000,	/* Multicast Address Received */
+};
+
+enum {					/* General Descriptor control */
+	Ls		= 0x10000000,	/* Last Segment Descriptor */
+	Fs		= 0x20000000,	/* First Segment Descriptor */
+	Eor		= 0x40000000,	/* End of Descriptor Ring */
+	Own		= 0x80000000,	/* Ownership */
+};
+
+/*
+ */
+enum {					/* Ring sizes  (<= 1024) */
+	Ntd		= 32,		/* Transmit Ring */
+	Nrd		= 128,		/* Receive Ring */
+
+	Mps		= ROUNDUP(ETHERMAXTU+4, 128),
+};
+
+typedef struct Dtcc Dtcc;
+struct Dtcc {
+	u64int	txok;
+	u64int	rxok;
+	u64int	txer;
+	u32int	rxer;
+	u16int	misspkt;
+	u16int	fae;
+	u32int	tx1col;
+	u32int	txmcol;
+	u64int	rxokph;
+	u64int	rxokbrd;
+	u32int	rxokmu;
+	u16int	txabt;
+	u16int	txundrn;
+};
+
+enum {						/* Variants */
+	Rtl8100e	= (0x8136<<16)|0x10EC,	/* RTL810[01]E: pci -e */
+	Rtl8169c	= (0x0116<<16)|0x16EC,	/* RTL8169C+ (USR997902) */
+	Rtl8169sc	= (0x8167<<16)|0x10EC,	/* RTL8169SC */
+	Rtl8168b	= (0x8168<<16)|0x10EC,	/* RTL8168B: pci-e */
+	Rtl8169		= (0x8169<<16)|0x10EC,	/* RTL8169 */
+};
+
+typedef struct Ctlr Ctlr;
+typedef struct Ctlr {
+	int	port;
+	Pcidev*	pcidev;
+	Ctlr*	next;
+	int	active;
+
+	QLock	alock;			/* attach */
+	Lock	ilock;			/* init */
+	int	init;			/*  */
+
+	int	pciv;			/*  */
+	int	macv;			/* MAC version */
+	int	phyv;			/* PHY version */
+	int	pcie;			/* flag: pci-express device? */
+
+	uvlong	mchash;			/* multicast hash */
+
+	Mii*	mii;
+
+	Lock	tlock;			/* transmit */
+	D*	td;			/* descriptor ring */
+	Block**	tb;			/* transmit buffers */
+	int	ntd;
+
+	int	tdh;			/* head - producer index (host) */
+	int	tdt;			/* tail - consumer index (NIC) */
+	int	ntdfree;
+	int	ntq;
+
+	int	mtps;			/* Max. Transmit Packet Size */
+
+	Lock	rlock;			/* receive */
+	D*	rd;			/* descriptor ring */
+	Block**	rb;			/* receive buffers */
+	int	nrd;
+
+	int	rdh;			/* head - producer index (NIC) */
+	int	rdt;			/* tail - consumer index (host) */
+	int	nrdfree;
+
+	int	tcr;			/* transmit configuration register */
+	int	rcr;			/* receive configuration register */
+	int	imr;
+
+	QLock	slock;			/* statistics */
+	Dtcc*	dtcc;
+	uint	txdu;
+	uint	tcpf;
+	uint	udpf;
+	uint	ipf;
+	uint	fovf;
+	uint	ierrs;
+	uint	rer;
+	uint	rdu;
+	uint	punlc;
+	uint	fovw;
+	uint	mcast;
+} Ctlr;
+
+static Ctlr* rtl8169ctlrhead;
+static Ctlr* rtl8169ctlrtail;
+
+#define csr8r(c, r)	(inb((c)->port+(r)))
+#define csr16r(c, r)	(ins((c)->port+(r)))
+#define csr32r(c, r)	(inl((c)->port+(r)))
+#define csr8w(c, r, b)	(outb((c)->port+(r), (u8int)(b)))
+#define csr16w(c, r, w)	(outs((c)->port+(r), (u16int)(w)))
+#define csr32w(c, r, l)	(outl((c)->port+(r), (u32int)(l)))
+
+static int
+rtl8169miimir(Ctlr* ctlr, int pa, int ra)
+{
+	uint r;
+	int timeo;
+
+	if(pa != 1)
+		return -1;
+
+	r = (ra<<16) & RegaddrMASK;
+	csr32w(ctlr, Phyar, r);
+	delay(1);
+	for(timeo = 0; timeo < 2000; timeo++){
+		if((r = csr32r(ctlr, Phyar)) & Flag)
+			break;
+		microdelay(100);
+	}
+	if(!(r & Flag))
+		return -1;
+
+	return (r & DataMASK)>>DataSHIFT;
+}
+
+static int
+rtl8169miimiw(Ctlr* ctlr, int pa, int ra, int data)
+{
+	uint r;
+	int timeo;
+
+	if(pa != 1)
+		return -1;
+
+	r = Flag|((ra<<16) & RegaddrMASK)|((data<<DataSHIFT) & DataMASK);
+	csr32w(ctlr, Phyar, r);
+	delay(1);
+	for(timeo = 0; timeo < 2000; timeo++){
+		if(!((r = csr32r(ctlr, Phyar)) & Flag))
+			break;
+		microdelay(100);
+	}
+	if(r & Flag)
+		return -1;
+
+	return 0;
+}
+
+static int
+rtl8169miirw(Mii* mii, int write, int pa, int ra, int data)
+{
+	if(write)
+		return rtl8169miimiw(mii->ctlr, pa, ra, data);
+
+	return rtl8169miimir(mii->ctlr, pa, ra);
+}
+
+static Mii*
+rtl8169mii(Ctlr* ctlr)
+{
+	Mii* mii;
+	MiiPhy *phy;
+
+	/*
+	 * Link management.
+	 *
+	 * Get rev number out of Phyidr2 so can config properly.
+	 * There's probably more special stuff for Macv0[234] needed here.
+	 */
+	ctlr->phyv = rtl8169miimir(ctlr, 1, Phyidr2) & 0x0F;
+	if(ctlr->macv == Macv02){
+		csr8w(ctlr, 0x82, 1);				/* magic */
+		rtl8169miimiw(ctlr, 1, 0x0B, 0x0000);		/* magic */
+	}
+	if((mii = miiattach(ctlr, (1<<1), rtl8169miirw)) == nil)
+		return nil;
+
+	phy = mii->curphy;
+	print("oui %#ux phyno %d, macv = %#8.8ux phyv = %#4.4ux\n",
+		phy->oui, phy->phyno, ctlr->macv, ctlr->phyv);
+
+	if(miistatus(mii) < 0){
+		miireset(mii);
+		miiane(mii, ~0, ~0, ~0);
+	}
+
+	return mii;
+}
+
+static void
+rtl8169promiscuous(void* arg, int on)
+{
+	Ether *edev;
+	Ctlr * ctlr;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+	ilock(&ctlr->ilock);
+
+	if(on)
+		ctlr->rcr |= Aap;
+	else
+		ctlr->rcr &= ~Aap;
+	csr32w(ctlr, Rcr, ctlr->rcr);
+	iunlock(&ctlr->ilock);
+}
+
+enum {
+	/* everyone else uses 0x04c11db7, but they both produce the same crc */
+	Etherpolybe = 0x04c11db6,
+	Bytemask = (1<<8) - 1,
+};
+
+static ulong
+ethercrcbe(uchar *addr, long len)
+{
+	int i, j;
+	ulong c, crc, carry;
+
+	crc = ~0UL;
+	for (i = 0; i < len; i++) {
+		c = addr[i];
+		for (j = 0; j < 8; j++) {
+			carry = ((crc & (1UL << 31))? 1: 0) ^ (c & 1);
+			crc <<= 1;
+			c >>= 1;
+			if (carry)
+				crc = (crc ^ Etherpolybe) | carry;
+		}
+	}
+	return crc;
+}
+
+static ulong
+swabl(ulong l)
+{
+	return l>>24 | (l>>8) & (Bytemask<<8) |
+		(l<<8) & (Bytemask<<16) | l<<24;
+}
+
+static void
+rtl8169multicast(void* ether, uchar *eaddr, int add)
+{
+	Ether *edev;
+	Ctlr *ctlr;
+
+	if (!add)
+		return;	/* ok to keep receiving on old mcast addrs */
+
+	edev = ether;
+	ctlr = edev->ctlr;
+	ilock(&ctlr->ilock);
+
+	ctlr->mchash |= 1ULL << (ethercrcbe(eaddr, Eaddrlen) >> 26);
+
+	ctlr->rcr |= Am;
+	csr32w(ctlr, Rcr, ctlr->rcr);
+
+	/* pci-e variants reverse the order of the hash byte registers */
+	if (ctlr->pcie) {
+		csr32w(ctlr, Mar0,   swabl(ctlr->mchash>>32));
+		csr32w(ctlr, Mar0+4, swabl(ctlr->mchash));
+	} else {
+		csr32w(ctlr, Mar0,   ctlr->mchash);
+		csr32w(ctlr, Mar0+4, ctlr->mchash>>32);
+	}
+
+	iunlock(&ctlr->ilock);
+}
+
+static long
+rtl8169ifstat(Ether* edev, void* a, long n, ulong offset)
+{
+	Ctlr *ctlr;
+	Dtcc *dtcc;
+	int timeo;
+	char *alloc, *e, *p;
+
+	ctlr = edev->ctlr;
+	qlock(&ctlr->slock);
+
+	alloc = nil;
+	if(waserror()){
+		qunlock(&ctlr->slock);
+		free(alloc);
+		nexterror();
+	}
+
+	csr32w(ctlr, Dtccr+4, 0);
+	csr32w(ctlr, Dtccr, PCIWADDR(ctlr->dtcc)|Cmd);
+	for(timeo = 0; timeo < 1000; timeo++){
+		if(!(csr32r(ctlr, Dtccr) & Cmd))
+			break;
+		delay(1);
+	}
+	if(csr32r(ctlr, Dtccr) & Cmd)
+		error(Eio);
+	dtcc = ctlr->dtcc;
+
+	edev->oerrs = dtcc->txer;
+	edev->crcs = dtcc->rxer;
+	edev->frames = dtcc->fae;
+	edev->buffs = dtcc->misspkt;
+	edev->overflows = ctlr->txdu+ctlr->rdu;
+
+	if(n == 0){
+		qunlock(&ctlr->slock);
+		poperror();
+		return 0;
+	}
+
+	if((alloc = malloc(READSTR)) == nil)
+		error(Enomem);
+	e = alloc+READSTR;
+
+	p = seprint(alloc, e, "TxOk: %llud\n", dtcc->txok);
+	p = seprint(p, e, "RxOk: %llud\n", dtcc->rxok);
+	p = seprint(p, e, "TxEr: %llud\n", dtcc->txer);
+	p = seprint(p, e, "RxEr: %ud\n", dtcc->rxer);
+	p = seprint(p, e, "MissPkt: %ud\n", dtcc->misspkt);
+	p = seprint(p, e, "FAE: %ud\n", dtcc->fae);
+	p = seprint(p, e, "Tx1Col: %ud\n", dtcc->tx1col);
+	p = seprint(p, e, "TxMCol: %ud\n", dtcc->txmcol);
+	p = seprint(p, e, "RxOkPh: %llud\n", dtcc->rxokph);
+	p = seprint(p, e, "RxOkBrd: %llud\n", dtcc->rxokbrd);
+	p = seprint(p, e, "RxOkMu: %ud\n", dtcc->rxokmu);
+	p = seprint(p, e, "TxAbt: %ud\n", dtcc->txabt);
+	p = seprint(p, e, "TxUndrn: %ud\n", dtcc->txundrn);
+
+	p = seprint(p, e, "txdu: %ud\n", ctlr->txdu);
+	p = seprint(p, e, "tcpf: %ud\n", ctlr->tcpf);
+	p = seprint(p, e, "udpf: %ud\n", ctlr->udpf);
+	p = seprint(p, e, "ipf: %ud\n", ctlr->ipf);
+	p = seprint(p, e, "fovf: %ud\n", ctlr->fovf);
+	p = seprint(p, e, "ierrs: %ud\n", ctlr->ierrs);
+	p = seprint(p, e, "rer: %ud\n", ctlr->rer);
+	p = seprint(p, e, "rdu: %ud\n", ctlr->rdu);
+	p = seprint(p, e, "punlc: %ud\n", ctlr->punlc);
+	p = seprint(p, e, "fovw: %ud\n", ctlr->fovw);
+
+	p = seprint(p, e, "tcr: %#8.8ux\n", ctlr->tcr);
+	p = seprint(p, e, "rcr: %#8.8ux\n", ctlr->rcr);
+	p = seprint(p, e, "multicast: %ud\n", ctlr->mcast);
+
+	if(ctlr->mii != nil && ctlr->mii->curphy != nil)
+		miidumpphy(ctlr->mii, p, e);
+
+	n = readstr(offset, a, n, alloc);
+
+	qunlock(&ctlr->slock);
+	poperror();
+	free(alloc);
+
+	return n;
+}
+
+static void
+rtl8169halt(Ctlr* ctlr)
+{
+	csr8w(ctlr, Cr, 0);
+	csr16w(ctlr, Imr, 0);
+	csr16w(ctlr, Isr, ~0);
+}
+
+static int
+rtl8169reset(Ctlr* ctlr)
+{
+	u32int r;
+	int timeo;
+
+	/*
+	 * Soft reset the controller.
+	 */
+	csr8w(ctlr, Cr, Rst);
+	for(r = timeo = 0; timeo < 1000; timeo++){
+		r = csr8r(ctlr, Cr);
+		if(!(r & Rst))
+			break;
+		delay(1);
+	}
+	rtl8169halt(ctlr);
+
+	if(r & Rst)
+		return -1;
+	return 0;
+}
+
+static void
+rtl8169replenish(Ctlr* ctlr)
+{
+	D *d;
+	int rdt;
+	Block *bp;
+
+	rdt = ctlr->rdt;
+	while(NEXT(rdt, ctlr->nrd) != ctlr->rdh){
+		d = &ctlr->rd[rdt];
+		if(ctlr->rb[rdt] == nil){
+			/*
+			 * Simple allocation for now.
+			 * This better be aligned on 8.
+			 */
+			bp = iallocb(Mps);
+			if(bp == nil){
+				iprint("no available buffers\n");
+				break;
+			}
+			ctlr->rb[rdt] = bp;
+			d->addrlo = PCIWADDR(bp->rp);
+			d->addrhi = 0;
+		}
+		coherence();
+		d->control |= Own|Mps;
+		rdt = NEXT(rdt, ctlr->nrd);
+		ctlr->nrdfree++;
+	}
+	ctlr->rdt = rdt;
+}
+
+static int
+rtl8169init(Ether* edev)
+{
+	int i;
+	u32int r;
+	Block *bp;
+	Ctlr *ctlr;
+	u8int cplusc;
+
+	ctlr = edev->ctlr;
+	ilock(&ctlr->ilock);
+
+	rtl8169halt(ctlr);
+
+	/*
+	 * MAC Address.
+	 * Must put chip into config register write enable mode.
+	 */
+	csr8w(ctlr, Cr9346, Eem1|Eem0);
+	r = (edev->ea[3]<<24)|(edev->ea[2]<<16)|(edev->ea[1]<<8)|edev->ea[0];
+	csr32w(ctlr, Idr0, r);
+	r = (edev->ea[5]<<8)|edev->ea[4];
+	csr32w(ctlr, Idr0+4, r);
+
+	/*
+	 * Transmitter.
+	 */
+	memset(ctlr->td, 0, sizeof(D)*ctlr->ntd);
+	ctlr->tdh = ctlr->tdt = 0;
+	ctlr->td[ctlr->ntd-1].control = Eor;
+
+	/*
+	 * Receiver.
+	 * Need to do something here about the multicast filter.
+	 */
+	memset(ctlr->rd, 0, sizeof(D)*ctlr->nrd);
+	ctlr->nrdfree = ctlr->rdh = ctlr->rdt = 0;
+	ctlr->rd[ctlr->nrd-1].control = Eor;
+
+	for(i = 0; i < ctlr->nrd; i++){
+		if((bp = ctlr->rb[i]) != nil){
+			ctlr->rb[i] = nil;
+			freeb(bp);
+		}
+	}
+	rtl8169replenish(ctlr);
+	ctlr->rcr = Rxfthnone|Mrxdmaunlimited|Ab|Am|Apm;
+
+	/*
+	 * Mtps is in units of 128 except for the RTL8169
+	 * where is is 32. If using jumbo frames should be
+	 * set to 0x3F.
+	 * Setting Mulrw in Cplusc disables the Tx/Rx DMA burst
+	 * settings in Tcr/Rcr; the (1<<14) is magic.
+	 */
+	ctlr->mtps = HOWMANY(Mps, 128);
+	cplusc = csr16r(ctlr, Cplusc) & ~(1<<14);
+	cplusc |= /*Rxchksum|*/Mulrw;
+	switch(ctlr->macv){
+	default:
+		return -1;
+	case Macv01:
+		ctlr->mtps = HOWMANY(Mps, 32);
+		break;
+	case Macv02:
+	case Macv03:
+		cplusc |= (1<<14);			/* magic */
+		break;
+	case Macv05:
+		/*
+		 * This is interpreted from clearly bogus code
+		 * in the manufacturer-supplied driver, it could
+		 * be wrong. Untested.
+		 */
+		r = csr8r(ctlr, Config2) & 0x07;
+		if(r == 0x01)				/* 66MHz PCI */
+			csr32w(ctlr, 0x7C, 0x0007FFFF);	/* magic */
+		else
+			csr32w(ctlr, 0x7C, 0x0007FF00);	/* magic */
+		pciclrmwi(ctlr->pcidev);
+		break;
+	case Macv13:
+		/*
+		 * This is interpreted from clearly bogus code
+		 * in the manufacturer-supplied driver, it could
+		 * be wrong. Untested.
+		 */
+		pcicfgw8(ctlr->pcidev, 0x68, 0x00);	/* magic */
+		pcicfgw8(ctlr->pcidev, 0x69, 0x08);	/* magic */
+		break;
+	case Macv04:
+	case Macv11:
+	case Macv12:
+	case Macv14:
+	case Macv15:
+		break;
+	}
+
+	/*
+	 * Enable receiver/transmitter.
+	 * Need to do this first or some of the settings below
+	 * won't take.
+	 */
+	switch(ctlr->pciv){
+	default:
+		csr8w(ctlr, Cr, Te|Re);
+		csr32w(ctlr, Tcr, Ifg1|Ifg0|Mtxdmaunlimited);
+		csr32w(ctlr, Rcr, ctlr->rcr);
+		csr32w(ctlr, Mar0,   0);
+		csr32w(ctlr, Mar0+4, 0);
+		ctlr->mchash = 0;
+	case Rtl8169sc:
+	case Rtl8168b:
+		break;
+	}
+
+	/*
+	 * Interrupts.
+	 * Disable Tdu|Tok for now, the transmit routine will tidy.
+	 * Tdu means the NIC ran out of descriptors to send, so it
+	 * doesn't really need to ever be on.
+	 */
+	csr32w(ctlr, Timerint, 0);
+	ctlr->imr = Serr|Timeout|Fovw|Punlc|Rdu|Ter|Rer|Rok;
+	csr16w(ctlr, Imr, ctlr->imr);
+
+	/*
+	 * Clear missed-packet counter;
+	 * initial early transmit threshold value;
+	 * set the descriptor ring base addresses;
+	 * set the maximum receive packet size;
+	 * no early-receive interrupts.
+	 */
+	csr32w(ctlr, Mpc, 0);
+	csr8w(ctlr, Mtps, ctlr->mtps);
+	csr32w(ctlr, Tnpds+4, 0);
+	csr32w(ctlr, Tnpds, PCIWADDR(ctlr->td));
+	csr32w(ctlr, Rdsar+4, 0);
+	csr32w(ctlr, Rdsar, PCIWADDR(ctlr->rd));
+	csr16w(ctlr, Rms, Mps);
+	r = csr16r(ctlr, Mulint) & 0xF000;
+	csr16w(ctlr, Mulint, r);
+	csr16w(ctlr, Cplusc, cplusc);
+
+	/*
+	 * Set configuration.
+	 */
+	switch(ctlr->pciv){
+	default:
+		break;
+	case Rtl8169sc:
+		csr16w(ctlr, 0xE2, 0);			/* magic */
+		csr8w(ctlr, Cr, Te|Re);
+		csr32w(ctlr, Tcr, Ifg1|Ifg0|Mtxdmaunlimited);
+		csr32w(ctlr, Rcr, ctlr->rcr);
+		break;
+	case Rtl8168b:
+	case Rtl8169c:
+		csr16w(ctlr, 0xE2, 0);			/* magic */
+		csr16w(ctlr, Cplusc, 0x2000);		/* magic */
+		csr8w(ctlr, Cr, Te|Re);
+		csr32w(ctlr, Tcr, Ifg1|Ifg0|Mtxdmaunlimited);
+		csr32w(ctlr, Rcr, ctlr->rcr);
+		csr16w(ctlr, Rms, 0x0800);
+		csr8w(ctlr, Mtps, 0x3F);
+		break;
+	}
+	ctlr->tcr = csr32r(ctlr, Tcr);
+	csr8w(ctlr, Cr9346, 0);
+
+	iunlock(&ctlr->ilock);
+
+//	rtl8169mii(ctlr);
+
+	return 0;
+}
+
+static void
+rtl8169attach(Ether* edev)
+{
+	int timeo;
+	Ctlr *ctlr;
+	MiiPhy *phy;
+
+	ctlr = edev->ctlr;
+	qlock(&ctlr->alock);
+	if(ctlr->init == 0){
+		/*
+		 * Handle allocation/init errors here.
+		 */
+		ctlr->td = mallocalign(sizeof(D)*Ntd, 256, 0, 0);
+		ctlr->tb = malloc(Ntd*sizeof(Block*));
+		ctlr->ntd = Ntd;
+		ctlr->rd = mallocalign(sizeof(D)*Nrd, 256, 0, 0);
+		ctlr->rb = malloc(Nrd*sizeof(Block*));
+		ctlr->nrd = Nrd;
+		ctlr->dtcc = mallocalign(sizeof(Dtcc), 64, 0, 0);
+		rtl8169init(edev);
+		ctlr->init = 1;
+	}
+	qunlock(&ctlr->alock);
+
+	/*
+	 * Wait for link to be ready.
+	 */
+	for(timeo = 0; timeo < 350; timeo++){
+		if(miistatus(ctlr->mii) == 0)
+			break;
+		tsleep(&up->sleep, return0, 0, 10);
+	}
+	phy = ctlr->mii->curphy;
+	print("%s: speed %d fd %d link %d rfc %d tfc %d\n",
+		edev->name, phy->speed, phy->fd, phy->link, phy->rfc, phy->tfc);
+}
+
+static void
+rtl8169link(Ether* edev)
+{
+	int limit;
+	Ctlr *ctlr;
+	MiiPhy *phy;
+
+	ctlr = edev->ctlr;
+
+	/*
+	 * Maybe the link changed - do we care very much?
+	 * Could stall transmits if no link, maybe?
+	 */
+	if(ctlr->mii == nil || ctlr->mii->curphy == nil)
+		return;
+
+	phy = ctlr->mii->curphy;
+	if(miistatus(ctlr->mii) < 0){
+		iprint("%slink n: speed %d fd %d link %d rfc %d tfc %d\n",
+			edev->name, phy->speed, phy->fd, phy->link,
+			phy->rfc, phy->tfc);
+		edev->link = 0;
+		return;
+	}
+	edev->link = 1;
+
+	limit = 256*1024;
+	if(phy->speed == 10){
+		edev->mbps = 10;
+		limit = 65*1024;
+	}
+	else if(phy->speed == 100)
+		edev->mbps = 100;
+	else if(phy->speed == 1000)
+		edev->mbps = 1000;
+	iprint("%slink y: speed %d fd %d link %d rfc %d tfc %d\n",
+		edev->name, phy->speed, phy->fd, phy->link,
+		phy->rfc, phy->tfc);
+
+	if(edev->oq != nil)
+		qsetlimit(edev->oq, limit);
+}
+
+static void
+rtl8169transmit(Ether* edev)
+{
+	D *d;
+	Block *bp;
+	Ctlr *ctlr;
+	int control, x;
+
+	ctlr = edev->ctlr;
+
+	ilock(&ctlr->tlock);
+	for(x = ctlr->tdh; ctlr->ntq > 0; x = NEXT(x, ctlr->ntd)){
+		d = &ctlr->td[x];
+		if((control = d->control) & Own)
+			break;
+
+		/*
+		 * Check errors and log here.
+		 */
+		USED(control);
+
+		/*
+		 * Free it up.
+		 * Need to clean the descriptor here? Not really.
+		 * Simple freeb for now (no chain and freeblist).
+		 * Use ntq count for now.
+		 */
+		freeb(ctlr->tb[x]);
+		ctlr->tb[x] = nil;
+		d->control &= Eor;
+
+		ctlr->ntq--;
+	}
+	ctlr->tdh = x;
+
+	x = ctlr->tdt;
+	while(ctlr->ntq < (ctlr->ntd-1)){
+		if((bp = qget(edev->oq)) == nil)
+			break;
+
+		d = &ctlr->td[x];
+		d->addrlo = PCIWADDR(bp->rp);
+		d->addrhi = 0;
+		ctlr->tb[x] = bp;
+		coherence();
+		d->control |= Own|Fs|Ls|((BLEN(bp)<<TxflSHIFT) & TxflMASK);
+
+		x = NEXT(x, ctlr->ntd);
+		ctlr->ntq++;
+	}
+	if(x != ctlr->tdt){
+		ctlr->tdt = x;
+		csr8w(ctlr, Tppoll, Npq);
+	}
+	else if(ctlr->ntq >= (ctlr->ntd-1))
+		ctlr->txdu++;
+
+	iunlock(&ctlr->tlock);
+}
+
+static void
+rtl8169receive(Ether* edev)
+{
+	D *d;
+	int rdh;
+	Block *bp;
+	Ctlr *ctlr;
+	u32int control;
+
+	ctlr = edev->ctlr;
+
+	rdh = ctlr->rdh;
+	for(;;){
+		d = &ctlr->rd[rdh];
+
+		if(d->control & Own)
+			break;
+
+		control = d->control;
+		if((control & (Fs|Ls|Res)) == (Fs|Ls)){
+			bp = ctlr->rb[rdh];
+			ctlr->rb[rdh] = nil;
+			bp->wp = bp->rp + ((control & RxflMASK)>>RxflSHIFT)-4;
+			bp->next = nil;
+
+			if(control & Fovf)
+				ctlr->fovf++;
+			if(control & Mar)
+				ctlr->mcast++;
+
+			switch(control & (Pid1|Pid0)){
+			default:
+				break;
+			case Pid0:
+				if(control & Tcpf){
+					ctlr->tcpf++;
+					break;
+				}
+				bp->flag |= Btcpck;
+				break;
+			case Pid1:
+				if(control & Udpf){
+					ctlr->udpf++;
+					break;
+				}
+				bp->flag |= Budpck;
+				break;
+			case Pid1|Pid0:
+				if(control & Ipf){
+					ctlr->ipf++;
+					break;
+				}
+				bp->flag |= Bipck;
+				break;
+			}
+			etheriq(edev, bp, 1);
+		}
+		else{
+			/*
+			 * Error stuff here.
+			print("control %#8.8ux\n", control);
+			 */
+		}
+		d->control &= Eor;
+		ctlr->nrdfree--;
+		rdh = NEXT(rdh, ctlr->nrd);
+
+		if(ctlr->nrdfree < ctlr->nrd/2)
+			rtl8169replenish(ctlr);
+	}
+	ctlr->rdh = rdh;
+}
+
+static void
+rtl8169interrupt(Ureg*, void* arg)
+{
+	Ctlr *ctlr;
+	Ether *edev;
+	u32int isr;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+
+	while((isr = csr16r(ctlr, Isr)) != 0 && isr != 0xFFFF){
+		csr16w(ctlr, Isr, isr);
+		if((isr & ctlr->imr) == 0)
+			break;
+		if(isr & (Fovw|Punlc|Rdu|Rer|Rok)){
+			rtl8169receive(edev);
+			if(!(isr & (Punlc|Rok)))
+				ctlr->ierrs++;
+			if(isr & Rer)
+				ctlr->rer++;
+			if(isr & Rdu)
+				ctlr->rdu++;
+			if(isr & Punlc)
+				ctlr->punlc++;
+			if(isr & Fovw)
+				ctlr->fovw++;
+			isr &= ~(Fovw|Rdu|Rer|Rok);
+		}
+
+		if(isr & (Tdu|Ter|Tok)){
+			rtl8169transmit(edev);
+			isr &= ~(Tdu|Ter|Tok);
+		}
+
+		if(isr & Punlc){
+			rtl8169link(edev);
+			isr &= ~Punlc;
+		}
+
+		/*
+		 * Some of the reserved bits get set sometimes...
+		 */
+		if(isr & (Serr|Timeout|Tdu|Fovw|Punlc|Rdu|Ter|Tok|Rer|Rok))
+			panic("rtl8169interrupt: imr %#4.4ux isr %#4.4ux\n",
+				csr16r(ctlr, Imr), isr);
+	}
+}
+
+static void
+rtl8169pci(void)
+{
+	Pcidev *p;
+	Ctlr *ctlr;
+	int i, port, pcie;
+
+	p = nil;
+	while(p = pcimatch(p, 0, 0)){
+		if(p->ccrb != 0x02 || p->ccru != 0)
+			continue;
+
+		pcie = 0;
+		switch(i = ((p->did<<16)|p->vid)){
+		default:
+			continue;
+		case Rtl8100e:			/* RTL810[01]E ? */
+		case Rtl8168b:			/* RTL8168B */
+			pcie = 1;
+			break;
+		case Rtl8169c:			/* RTL8169C */
+		case Rtl8169sc:			/* RTL8169SC */
+		case Rtl8169:			/* RTL8169 */
+			break;
+		case (0xC107<<16)|0x1259:	/* Corega CG-LAPCIGT */
+			i = Rtl8169;
+			break;
+		}
+
+		port = p->mem[0].bar & ~0x01;
+		if(ioalloc(port, p->mem[0].size, 0, "rtl8169") < 0){
+			print("rtl8169: port %#ux in use\n", port);
+			continue;
+		}
+
+		ctlr = malloc(sizeof(Ctlr));
+		ctlr->port = port;
+		ctlr->pcidev = p;
+		ctlr->pciv = i;
+		ctlr->pcie = pcie;
+
+		if(pcigetpms(p) > 0){
+			pcisetpms(p, 0);
+
+			for(i = 0; i < 6; i++)
+				pcicfgw32(p, PciBAR0+i*4, p->mem[i].bar);
+			pcicfgw8(p, PciINTL, p->intl);
+			pcicfgw8(p, PciLTR, p->ltr);
+			pcicfgw8(p, PciCLS, p->cls);
+			pcicfgw16(p, PciPCR, p->pcr);
+		}
+
+		if(rtl8169reset(ctlr)){
+			iofree(port);
+			free(ctlr);
+			continue;
+		}
+
+		/*
+		 * Extract the chip hardware version,
+		 * needed to configure each properly.
+		 */
+		ctlr->macv = csr32r(ctlr, Tcr) & HwveridMASK;
+		if((ctlr->mii = rtl8169mii(ctlr)) == nil){
+			iofree(port);
+			free(ctlr);
+			continue;
+		}
+
+		pcisetbme(p);
+
+		if(rtl8169ctlrhead != nil)
+			rtl8169ctlrtail->next = ctlr;
+		else
+			rtl8169ctlrhead = ctlr;
+		rtl8169ctlrtail = ctlr;
+	}
+}
+
+static int
+rtl8169pnp(Ether* edev)
+{
+	u32int r;
+	Ctlr *ctlr;
+	uchar ea[Eaddrlen];
+
+	if(rtl8169ctlrhead == nil)
+		rtl8169pci();
+
+	/*
+	 * Any adapter matches if no edev->port is supplied,
+	 * otherwise the ports must match.
+	 */
+	for(ctlr = rtl8169ctlrhead; ctlr != nil; ctlr = ctlr->next){
+		if(ctlr->active)
+			continue;
+		if(edev->port == 0 || edev->port == ctlr->port){
+			ctlr->active = 1;
+			break;
+		}
+	}
+	if(ctlr == nil)
+		return -1;
+
+	edev->ctlr = ctlr;
+	edev->port = ctlr->port;
+	edev->irq = ctlr->pcidev->intl;
+	edev->tbdf = ctlr->pcidev->tbdf;
+	edev->mbps = 100;
+
+	/*
+	 * Check if the adapter's station address is to be overridden.
+	 * If not, read it from the device and set in edev->ea.
+	 */
+	memset(ea, 0, Eaddrlen);
+	if(memcmp(ea, edev->ea, Eaddrlen) == 0){
+		r = csr32r(ctlr, Idr0);
+		edev->ea[0] = r;
+		edev->ea[1] = r>>8;
+		edev->ea[2] = r>>16;
+		edev->ea[3] = r>>24;
+		r = csr32r(ctlr, Idr0+4);
+		edev->ea[4] = r;
+		edev->ea[5] = r>>8;
+	}
+
+	edev->attach = rtl8169attach;
+	edev->transmit = rtl8169transmit;
+	edev->interrupt = rtl8169interrupt;
+	edev->ifstat = rtl8169ifstat;
+
+	edev->arg = edev;
+	edev->promiscuous = rtl8169promiscuous;
+	edev->multicast = rtl8169multicast;
+//	edev->shutdown = rtl8169shutdown;
+
+	rtl8169link(edev);
+
+	return 0;
+}
+
+void
+ether8169link(void)
+{
+	addethercard("rtl8169", rtl8169pnp);
+}

+ 1342 - 0
sys/src/9/386/ether82557.c

@@ -0,0 +1,1342 @@
+/*
+ * This file is part of the UCB release of Plan 9. It is subject to the license
+ * terms in the LICENSE file found in the top-level directory of this
+ * distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
+ * part of the UCB release of Plan 9, including this file, may be copied,
+ * modified, propagated, or distributed except according to the terms contained
+ * in the LICENSE file.
+ */
+
+/*
+ * Intel 82557 Fast Ethernet PCI Bus LAN Controller
+ * as found on the Intel EtherExpress PRO/100B. This chip is full
+ * of smarts, unfortunately they're not all in the right place.
+ * To do:
+ *	the PCI scanning code could be made common to other adapters;
+ *	auto-negotiation, full-duplex;
+ *	optionally use memory-mapped registers;
+ *	detach for PCI reset problems (also towards loadable drivers).
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+
+#include "../port/netif.h"
+
+#include "etherif.h"
+#include "io.h"
+
+enum {
+	Nrfd		= 64,		/* receive frame area */
+	Ncb		= 64,		/* maximum control blocks queued */
+
+	NullPointer	= 0xFFFFFFFF,	/* 82557 NULL pointer */
+};
+
+enum {					/* CSR */
+	Status		= 0x00,		/* byte or word (word includes Ack) */
+	Ack		= 0x01,		/* byte */
+	CommandR	= 0x02,		/* byte or word (word includes Interrupt) */
+	Interrupt	= 0x03,		/* byte */
+	General		= 0x04,		/* dword */
+	Port		= 0x08,		/* dword */
+	Fcr		= 0x0C,		/* Flash control register */
+	Ecr		= 0x0E,		/* EEPROM control register */
+	Mcr		= 0x10,		/* MDI control register */
+	Gstatus		= 0x1D,		/* General status register */
+};
+
+enum {					/* Status */
+	RUidle		= 0x0000,
+	RUsuspended	= 0x0004,
+	RUnoresources	= 0x0008,
+	RUready		= 0x0010,
+	RUrbd		= 0x0020,	/* bit */
+	RUstatus	= 0x003F,	/* mask */
+
+	CUidle		= 0x0000,
+	CUsuspended	= 0x0040,
+	CUactive	= 0x0080,
+	CUstatus	= 0x00C0,	/* mask */
+
+	StatSWI		= 0x0400,	/* SoftWare generated Interrupt */
+	StatMDI		= 0x0800,	/* MDI r/w done */
+	StatRNR		= 0x1000,	/* Receive unit Not Ready */
+	StatCNA		= 0x2000,	/* Command unit Not Active (Active->Idle) */
+	StatFR		= 0x4000,	/* Finished Receiving */
+	StatCX		= 0x8000,	/* Command eXecuted */
+	StatTNO		= 0x8000,	/* Transmit NOT OK */
+};
+
+enum {					/* Command (byte) */
+	CUnop		= 0x00,
+	CUstart		= 0x10,
+	CUresume	= 0x20,
+	LoadDCA		= 0x40,		/* Load Dump Counters Address */
+	DumpSC		= 0x50,		/* Dump Statistical Counters */
+	LoadCUB		= 0x60,		/* Load CU Base */
+	ResetSA		= 0x70,		/* Dump and Reset Statistical Counters */
+
+	RUstart		= 0x01,
+	RUresume	= 0x02,
+	RUabort		= 0x04,
+	LoadHDS		= 0x05,		/* Load Header Data Size */
+	LoadRUB		= 0x06,		/* Load RU Base */
+	RBDresume	= 0x07,		/* Resume frame reception */
+};
+
+enum {					/* Interrupt (byte) */
+	InterruptM	= 0x01,		/* interrupt Mask */
+	InterruptSI	= 0x02,		/* Software generated Interrupt */
+};
+
+enum {					/* Ecr */
+	EEsk		= 0x01,		/* serial clock */
+	EEcs		= 0x02,		/* chip select */
+	EEdi		= 0x04,		/* serial data in */
+	EEdo		= 0x08,		/* serial data out */
+
+	EEstart		= 0x04,		/* start bit */
+	EEread		= 0x02,		/* read opcode */
+};
+
+enum {					/* Mcr */
+	MDIread		= 0x08000000,	/* read opcode */
+	MDIwrite	= 0x04000000,	/* write opcode */
+	MDIready	= 0x10000000,	/* ready bit */
+	MDIie		= 0x20000000,	/* interrupt enable */
+};
+
+typedef struct Rfd {
+	int	field;
+	ulong	link;
+	ulong	rbd;
+	ushort	count;
+	ushort	size;
+
+	uchar	data[1700];
+} Rfd;
+
+enum {					/* field */
+	RfdCollision	= 0x00000001,
+	RfdIA		= 0x00000002,	/* IA match */
+	RfdRxerr	= 0x00000010,	/* PHY character error */
+	RfdType		= 0x00000020,	/* Type frame */
+	RfdRunt		= 0x00000080,
+	RfdOverrun	= 0x00000100,
+	RfdBuffer	= 0x00000200,
+	RfdAlignment	= 0x00000400,
+	RfdCRC		= 0x00000800,
+
+	RfdOK		= 0x00002000,	/* frame received OK */
+	RfdC		= 0x00008000,	/* reception Complete */
+	RfdSF		= 0x00080000,	/* Simplified or Flexible (1) Rfd */
+	RfdH		= 0x00100000,	/* Header RFD */
+
+	RfdI		= 0x20000000,	/* Interrupt after completion */
+	RfdS		= 0x40000000,	/* Suspend after completion */
+	RfdEL		= 0x80000000,	/* End of List */
+};
+
+enum {					/* count */
+	RfdF		= 0x4000,
+	RfdEOF		= 0x8000,
+};
+
+typedef struct Cb Cb;
+typedef struct Cb {
+	ushort	status;
+	ushort	command;
+	ulong	link;
+	union {
+		uchar	data[24];	/* CbIAS + CbConfigure */
+		struct {
+			ulong	tbd;
+			ushort	count;
+			uchar	threshold;
+			uchar	number;
+
+			ulong	tba;
+			ushort	tbasz;
+			ushort	pad;
+		};
+	};
+
+	Block*	bp;
+	Cb*	next;
+} Cb;
+
+enum {					/* action command */
+	CbU		= 0x1000,	/* transmit underrun */
+	CbOK		= 0x2000,	/* DMA completed OK */
+	CbC		= 0x8000,	/* execution Complete */
+
+	CbNOP		= 0x0000,
+	CbIAS		= 0x0001,	/* Individual Address Setup */
+	CbConfigure	= 0x0002,
+	CbMAS		= 0x0003,	/* Multicast Address Setup */
+	CbTransmit	= 0x0004,
+	CbDump		= 0x0006,
+	CbDiagnose	= 0x0007,
+	CbCommand	= 0x0007,	/* mask */
+
+	CbSF		= 0x0008,	/* Flexible-mode CbTransmit */
+
+	CbI		= 0x2000,	/* Interrupt after completion */
+	CbS		= 0x4000,	/* Suspend after completion */
+	CbEL		= 0x8000,	/* End of List */
+};
+
+enum {					/* CbTransmit count */
+	CbEOF		= 0x8000,
+};
+
+typedef struct Ctlr Ctlr;
+typedef struct Ctlr {
+	Lock	slock;			/* attach */
+	int	state;
+
+	int	port;
+	Pcidev*	pcidev;
+	Ctlr*	next;
+	int	active;
+
+	int	eepromsz;		/* address size in bits */
+	ushort*	eeprom;
+
+	Lock	miilock;
+
+	int	tick;
+
+	Lock	rlock;			/* registers */
+	int	command;		/* last command issued */
+
+	Block*	rfdhead;		/* receive side */
+	Block*	rfdtail;
+	int	nrfd;
+
+	Lock	cblock;			/* transmit side */
+	int	action;
+	int	nop;
+	uchar	configdata[24];
+	int	threshold;
+	int	ncb;
+	Cb*	cbr;
+	Cb*	cbhead;
+	Cb*	cbtail;
+	int	cbq;
+	int	cbqmax;
+	int	cbqmaxhw;
+
+	Lock	dlock;			/* dump statistical counters */
+	ulong	dump[17];
+} Ctlr;
+
+static Ctlr* ctlrhead;
+static Ctlr* ctlrtail;
+
+static uchar configdata[24] = {
+	0x16,				/* byte count */
+	0x08,				/* Rx/Tx FIFO limit */
+	0x00,				/* adaptive IFS */
+	0x00,
+	0x00,				/* Rx DMA maximum byte count */
+//	0x80,				/* Tx DMA maximum byte count */
+	0x00,				/* Tx DMA maximum byte count */
+	0x32,				/* !late SCB, CNA interrupts */
+	0x03,				/* discard short Rx frames */
+	0x00,				/* 503/MII */
+
+	0x00,	
+	0x2E,				/* normal operation, NSAI */
+	0x00,				/* linear priority */
+	0x60,				/* inter-frame spacing */
+	0x00,	
+	0xF2,	
+	0xC8,				/* 503, promiscuous mode off */
+	0x00,	
+	0x40,	
+	0xF3,				/* transmit padding enable */
+	0x80,				/* full duplex pin enable */
+	0x3F,				/* no Multi IA */
+	0x05,				/* no Multi Cast ALL */
+};
+
+#define csr8r(c, r)	(inb((c)->port+(r)))
+#define csr16r(c, r)	(ins((c)->port+(r)))
+#define csr32r(c, r)	(inl((c)->port+(r)))
+#define csr8w(c, r, b)	(outb((c)->port+(r), (int)(b)))
+#define csr16w(c, r, w)	(outs((c)->port+(r), (ushort)(w)))
+#define csr32w(c, r, l)	(outl((c)->port+(r), (ulong)(l)))
+
+static void
+command(Ctlr* ctlr, int c, int v)
+{
+	int timeo;
+
+	ilock(&ctlr->rlock);
+
+	/*
+	 * Only back-to-back CUresume can be done
+	 * without waiting for any previous command to complete.
+	 * This should be the common case.
+	 * Unfortunately there's a chip errata where back-to-back
+	 * CUresumes can be lost, the fix is to always wait.
+	if(c == CUresume && ctlr->command == CUresume){
+		csr8w(ctlr, CommandR, c);
+		iunlock(&ctlr->rlock);
+		return;
+	}
+	 */
+
+	for(timeo = 0; timeo < 100; timeo++){
+		if(!csr8r(ctlr, CommandR))
+			break;
+		microdelay(1);
+	}
+	if(timeo >= 100){
+		ctlr->command = -1;
+		iunlock(&ctlr->rlock);
+		iprint("i82557: command %#ux %#ux timeout\n", c, v);
+		return;
+	}
+
+	switch(c){
+
+	case CUstart:
+	case LoadDCA:
+	case LoadCUB:
+	case RUstart:
+	case LoadHDS:
+	case LoadRUB:
+		csr32w(ctlr, General, v);
+		break;
+
+	/*
+	case CUnop:
+	case CUresume:
+	case DumpSC:
+	case ResetSA:
+	case RUresume:
+	case RUabort:
+	 */
+	default:
+		break;
+	}
+	csr8w(ctlr, CommandR, c);
+	ctlr->command = c;
+
+	iunlock(&ctlr->rlock);
+}
+
+static Block*
+rfdalloc(ulong link)
+{
+	Block *bp;
+	Rfd *rfd;
+
+	if(bp = iallocb(sizeof(Rfd))){
+		rfd = (Rfd*)bp->rp;
+		rfd->field = 0;
+		rfd->link = link;
+		rfd->rbd = NullPointer;
+		rfd->count = 0;
+		rfd->size = sizeof(Etherpkt);
+	}
+
+	return bp;
+}
+
+static void
+watchdog(void* arg)
+{
+	Ether *ether;
+	Ctlr *ctlr;
+	static void txstart(Ether*);
+
+	ether = arg;
+	for(;;){
+		tsleep(&up->sleep, return0, 0, 4000);
+
+		/*
+		 * Hmmm. This doesn't seem right. Currently
+		 * the device can't be disabled but it may be in
+		 * the future.
+		 */
+		ctlr = ether->ctlr;
+		if(ctlr == nil || ctlr->state == 0){
+			print("%s: exiting\n", up->text);
+			pexit("disabled", 0);
+		}
+
+		ilock(&ctlr->cblock);
+		if(ctlr->tick++){
+			ctlr->action = CbMAS;
+			txstart(ether);
+		}
+		iunlock(&ctlr->cblock);
+	}
+}
+
+static void
+attach(Ether* ether)
+{
+	Ctlr *ctlr;
+	char name[KNAMELEN];
+
+	ctlr = ether->ctlr;
+	lock(&ctlr->slock);
+	if(ctlr->state == 0){
+		ilock(&ctlr->rlock);
+		csr8w(ctlr, Interrupt, 0);
+		iunlock(&ctlr->rlock);
+		command(ctlr, RUstart, PADDR(ctlr->rfdhead->rp));
+		ctlr->state = 1;
+
+		/*
+		 * Start the watchdog timer for the receive lockup errata
+		 * unless the EEPROM compatibility word indicates it may be
+		 * omitted.
+		 */
+		if((ctlr->eeprom[0x03] & 0x0003) != 0x0003){
+			snprint(name, KNAMELEN, "#l%dwatchdog", ether->ctlrno);
+			kproc(name, watchdog, ether);
+		}
+	}
+	unlock(&ctlr->slock);
+}
+
+static long
+ifstat(Ether* ether, void* a, long n, ulong offset)
+{
+	char *p;
+	int i, len, phyaddr;
+	Ctlr *ctlr;
+	ulong dump[17];
+
+	ctlr = ether->ctlr;
+	lock(&ctlr->dlock);
+
+	/*
+	 * Start the command then
+	 * wait for completion status,
+	 * should be 0xA005.
+	 */
+	ctlr->dump[16] = 0;
+	command(ctlr, DumpSC, 0);
+	while(ctlr->dump[16] == 0)
+		;
+
+	ether->oerrs = ctlr->dump[1]+ctlr->dump[2]+ctlr->dump[3];
+	ether->crcs = ctlr->dump[10];
+	ether->frames = ctlr->dump[11];
+	ether->buffs = ctlr->dump[12]+ctlr->dump[15];
+	ether->overflows = ctlr->dump[13];
+
+	if(n == 0){
+		unlock(&ctlr->dlock);
+		return 0;
+	}
+
+	memmove(dump, ctlr->dump, sizeof(dump));
+	unlock(&ctlr->dlock);
+
+	p = malloc(READSTR);
+	len = snprint(p, READSTR, "transmit good frames: %lud\n", dump[0]);
+	len += snprint(p+len, READSTR-len, "transmit maximum collisions errors: %lud\n", dump[1]);
+	len += snprint(p+len, READSTR-len, "transmit late collisions errors: %lud\n", dump[2]);
+	len += snprint(p+len, READSTR-len, "transmit underrun errors: %lud\n", dump[3]);
+	len += snprint(p+len, READSTR-len, "transmit lost carrier sense: %lud\n", dump[4]);
+	len += snprint(p+len, READSTR-len, "transmit deferred: %lud\n", dump[5]);
+	len += snprint(p+len, READSTR-len, "transmit single collisions: %lud\n", dump[6]);
+	len += snprint(p+len, READSTR-len, "transmit multiple collisions: %lud\n", dump[7]);
+	len += snprint(p+len, READSTR-len, "transmit total collisions: %lud\n", dump[8]);
+	len += snprint(p+len, READSTR-len, "receive good frames: %lud\n", dump[9]);
+	len += snprint(p+len, READSTR-len, "receive CRC errors: %lud\n", dump[10]);
+	len += snprint(p+len, READSTR-len, "receive alignment errors: %lud\n", dump[11]);
+	len += snprint(p+len, READSTR-len, "receive resource errors: %lud\n", dump[12]);
+	len += snprint(p+len, READSTR-len, "receive overrun errors: %lud\n", dump[13]);
+	len += snprint(p+len, READSTR-len, "receive collision detect errors: %lud\n", dump[14]);
+	len += snprint(p+len, READSTR-len, "receive short frame errors: %lud\n", dump[15]);
+	len += snprint(p+len, READSTR-len, "nop: %d\n", ctlr->nop);
+	if(ctlr->cbqmax > ctlr->cbqmaxhw)
+		ctlr->cbqmaxhw = ctlr->cbqmax;
+	len += snprint(p+len, READSTR-len, "cbqmax: %d\n", ctlr->cbqmax);
+	ctlr->cbqmax = 0;
+	len += snprint(p+len, READSTR-len, "threshold: %d\n", ctlr->threshold);
+
+	len += snprint(p+len, READSTR-len, "eeprom:");
+	for(i = 0; i < (1<<ctlr->eepromsz); i++){
+		if(i && ((i & 0x07) == 0))
+			len += snprint(p+len, READSTR-len, "\n       ");
+		len += snprint(p+len, READSTR-len, " %4.4ux", ctlr->eeprom[i]);
+	}
+
+	if((ctlr->eeprom[6] & 0x1F00) && !(ctlr->eeprom[6] & 0x8000)){
+		phyaddr = ctlr->eeprom[6] & 0x00FF;
+		len += snprint(p+len, READSTR-len, "\nphy %2d:", phyaddr);
+		for(i = 0; i < 6; i++){
+			static int miir(Ctlr*, int, int);
+
+			len += snprint(p+len, READSTR-len, " %4.4ux",
+				miir(ctlr, phyaddr, i));
+		}
+	}
+
+	snprint(p+len, READSTR-len, "\n");
+	n = readstr(offset, a, n, p);
+	free(p);
+
+	return n;
+}
+
+static void
+txstart(Ether* ether)
+{
+	Ctlr *ctlr;
+	Block *bp;
+	Cb *cb;
+
+	ctlr = ether->ctlr;
+	while(ctlr->cbq < (ctlr->ncb-1)){
+		cb = ctlr->cbhead->next;
+		if(ctlr->action == 0){
+			bp = qget(ether->oq);
+			if(bp == nil)
+				break;
+
+			cb->command = CbS|CbSF|CbTransmit;
+			cb->tbd = PADDR(&cb->tba);
+			cb->count = 0;
+			cb->threshold = ctlr->threshold;
+			cb->number = 1;
+			cb->tba = PADDR(bp->rp);
+			cb->bp = bp;
+			cb->tbasz = BLEN(bp);
+		}
+		else if(ctlr->action == CbConfigure){
+			cb->command = CbS|CbConfigure;
+			memmove(cb->data, ctlr->configdata, sizeof(ctlr->configdata));
+			ctlr->action = 0;
+		}
+		else if(ctlr->action == CbIAS){
+			cb->command = CbS|CbIAS;
+			memmove(cb->data, ether->ea, Eaddrlen);
+			ctlr->action = 0;
+		}
+		else if(ctlr->action == CbMAS){
+			cb->command = CbS|CbMAS;
+			memset(cb->data, 0, sizeof(cb->data));
+			ctlr->action = 0;
+		}
+		else{
+			print("#l%d: action %#ux\n", ether->ctlrno, ctlr->action);
+			ctlr->action = 0;
+			break;
+		}
+		cb->status = 0;
+
+		coherence();
+		ctlr->cbhead->command &= ~CbS;
+		ctlr->cbhead = cb;
+		ctlr->cbq++;
+	}
+
+	/*
+	 * Workaround for some broken HUB chips
+	 * when connected at 10Mb/s half-duplex.
+	 */
+	if(ctlr->nop){
+		command(ctlr, CUnop, 0);
+		microdelay(1);
+	}
+	command(ctlr, CUresume, 0);
+
+	if(ctlr->cbq > ctlr->cbqmax)
+		ctlr->cbqmax = ctlr->cbq;
+}
+
+static void
+configure(Ether* ether, int promiscuous)
+{
+	Ctlr *ctlr;
+
+	ctlr = ether->ctlr;
+	ilock(&ctlr->cblock);
+	if(promiscuous){
+		ctlr->configdata[6] |= 0x80;		/* Save Bad Frames */
+		//ctlr->configdata[6] &= ~0x40;		/* !Discard Overrun Rx Frames */
+		ctlr->configdata[7] &= ~0x01;		/* !Discard Short Rx Frames */
+		ctlr->configdata[15] |= 0x01;		/* Promiscuous mode */
+		ctlr->configdata[18] &= ~0x01;		/* (!Padding enable?), !stripping enable */
+		ctlr->configdata[21] |= 0x08;		/* Multi Cast ALL */
+	}
+	else{
+		ctlr->configdata[6] &= ~0x80;
+		//ctlr->configdata[6] |= 0x40;
+		ctlr->configdata[7] |= 0x01;
+		ctlr->configdata[15] &= ~0x01;
+		ctlr->configdata[18] |= 0x01;		/* 0x03? */
+		ctlr->configdata[21] &= ~0x08;
+	}
+	ctlr->action = CbConfigure;
+	txstart(ether);
+	iunlock(&ctlr->cblock);
+}
+
+static void
+promiscuous(void* arg, int on)
+{
+	configure(arg, on);
+}
+
+static void
+multicast(void* ether, uchar *addr, int add)
+{
+	USED(addr);
+	/*
+	 * TODO: if (add) add addr to list of mcast addrs in controller
+	 *	else remove addr from list of mcast addrs in controller
+	 * enable multicast input (see CbMAS) instead of promiscuous mode.
+	 */