code: 9ferno

Download patch

ref: 967c74fc21d01478cf7fb64e6aedce90fe5fbee9
parent: 6bfe4cf8107de7a01ff8a0667db7f28f08e4d238
author: joe9 <joe9mail@gmail.com>
date: Wed Jul 14 12:43:27 EDT 2021

ethernet drivers from 9front

--- /dev/null
+++ b/os/pc/ether8169.c
@@ -1,0 +1,1243 @@
+/*
+ * Realtek RTL8110S/8169S Gigabit Ethernet Controllers.
+ * 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/pci.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+#include "../port/etherif.h"
+#include "../port/ethermii.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 */
+	Phystatus	= 0x6C,		/* PHY Status */
+	Pmch		= 0x6F,		/* power management */
+	Ldps		= 0x82,		/* link down power saving */
+
+	Rms		= 0xDA,		/* Receive Packet Maximum Size */
+	Cplusc		= 0xE0,		/* C+ Command */
+	Coal		= 0xE2,		/* Interrupt Mitigation (Coalesce) */
+	Rdsar		= 0xE4,		/* Receive Descriptor Start Address */
+	Etx		= 0xEC,		/* Early Transmit Threshold */
+};
+
+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 */
+	Macv07		= 0x24800000,	/* RTL8102e */
+	Macv07a		= 0x34800000,	/* RTL8102e */
+	Macv11		= 0x30000000,	/* RTL8168B/8111B */
+	Macv12		= 0x38000000,	/* RTL8169B/8111B */
+	Macv12a		= 0x3c000000,	/* RTL8169C/8111C */
+	Macv13		= 0x34000000,	/* RTL8101E */
+	Macv14		= 0x30800000,	/* RTL8100E */
+	Macv15		= 0x38800000,	/* RTL8100E */
+//	Macv19		= 0x3c000000,	/* dup Macv12a: RTL8111c-gr */
+	Macv25		= 0x28000000,	/* RTL8168D */
+	Macv26		= 0x48000000,	/* RTL8111/8168B */
+	Macv27		= 0x2c800000,	/* RTL8111e */
+	Macv28		= 0x2c000000,	/* RTL8111/8168B */
+	Macv29		= 0x40800000,	/* RTL8101/8102E */
+	Macv30		= 0x24000000,	/* RTL8101E? (untested) */
+	Macv39		= 0x44800000,	/* RTL8106E */
+	Macv40		= 0x4c000000,	/* RTL8168G */
+	Macv42		= 0x50800000,	/* RTL8168GU */
+	Macv44		= 0x5c800000,	/* RTL8411B */
+	Macv45		= 0x54000000,	/* RTL8111HN */
+
+	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 {					/* Phystatus */
+	Fd		= 0x01,		/* Full Duplex */
+	Linksts		= 0x02,		/* Link Status */
+	Speed10		= 0x04,		/* */
+	Speed100	= 0x08,		/* */
+	Speed1000	= 0x10,		/* */
+	Rxflow		= 0x20,		/* */
+	Txflow		= 0x40,		/* */
+	Entbi		= 0x80,		/* */
+};
+
+enum {					/* Cplusc */
+	Txenb		= 0x0001,	/* enable C+ transmit mode */
+	Rxenb		= 0x0002,	/* enable C+ receive mode */
+	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 */
+	Macstatdis	= 0x0080,	/* Disable Mac Statistics */
+	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,	/* TSO; WARNING: contains lark's vomit */
+};
+
+enum {					/* Receive Descriptor control */
+	RxflMASK	= 0x00001FFF,	/* Receive Frame Length */
+	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		= 64,		/* Transmit Ring */
+	Nrd		= 256,		/* Receive Ring */
+
+	Mtu		= ETHERMAXTU,
+	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 {
+	Lock;
+
+	int	port;
+	Pcidev*	pcidev;
+	Ctlr*	next;
+	int	active;
+
+	QLock	alock;			/* attach */
+	int	init;			/*  */
+	Rendez	reset;
+
+	int	pciv;			/*  */
+	int	macv;			/* MAC version */
+	int	phyv;			/* PHY version */
+	int	pcie;			/* flag: pci-express device? */
+
+	uvlong	mchash;			/* multicast hash */
+
+	Mii*	mii;
+
+	D*	td;			/* descriptor ring */
+	Block**	tb;			/* transmit buffers */
+	int	ntd;
+
+	int	tdh;			/* head - producer index (host) */
+	int	tdt;			/* tail - consumer index (NIC) */
+	int	ntq;
+
+	D*	rd;			/* descriptor ring */
+	Block**	rb;			/* receive buffers */
+	int	nrd;
+
+	int	rdh;			/* head - producer index (NIC) */
+	int	rdt;			/* tail - consumer index (host) */
+	int	nrq;
+
+	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	rer;
+	uint	rdu;
+	uint	punlc;
+	uint	serr;
+	uint	fovw;
+	uint	mcast;
+	uint	frag;			/* partial packets; rb was too small */
+} 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(Mii* mii, int pa, int ra)
+{
+	uint r;
+	int timeo;
+	Ctlr *ctlr;
+
+	if(pa != 1)
+		return -1;
+	ctlr = mii->ctlr;
+
+	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(Mii* mii, int pa, int ra, int data)
+{
+	uint r;
+	int timeo;
+	Ctlr *ctlr;
+
+	if(pa != 1)
+		return -1;
+	ctlr = mii->ctlr;
+
+	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 void
+rtl8169mii(Ether *edev)
+{
+	Ctlr *ctlr = edev->ctlr;
+	MiiPhy *phy;
+
+	/*
+	 * Link management.
+	 */
+	ctlr->mii = smalloc(sizeof(Mii));
+	ctlr->mii->mir = rtl8169miimir;
+	ctlr->mii->miw = rtl8169miimiw;
+	ctlr->mii->ctlr = ctlr;
+
+	/*
+	 * PHY wakeup
+	 */
+	switch(ctlr->macv){
+	case Macv25:
+	case Macv28:
+	case Macv29:
+	case Macv30:
+		csr8w(ctlr, Pmch, csr8r(ctlr, Pmch) | 0x80);
+		break;
+	}
+	rtl8169miimiw(ctlr->mii, 1, 0x1f, 0);
+	rtl8169miimiw(ctlr->mii, 1, 0x0e, 0);
+
+	/*
+	 * 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->mii, 1, Phyidr2) & 0x0F;
+	if(ctlr->macv == Macv02){
+		csr8w(ctlr, Ldps, 1);				/* magic */
+		rtl8169miimiw(ctlr->mii, 1, 0x0B, 0x0000);	/* magic */
+	}
+	
+	if(mii(ctlr->mii, (1<<1)) == 0 || (phy = ctlr->mii->curphy) == nil){
+		error("no phy");
+		return;
+	}
+
+	print("#l%d: rtl8169: oui %#ux phyno %d, macv = %#8.8ux phyv = %#4.4ux\n",
+		edev->ctlrno, phy->oui, phy->phyno, ctlr->macv, ctlr->phyv);
+
+	miireset(ctlr->mii);
+
+	microdelay(100);
+
+	miiane(ctlr->mii, ~0, ~0, ~0);
+}
+
+static void
+rtl8169promiscuous(void* arg, int on)
+{
+	Ether *edev;
+	Ctlr * ctlr;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+	ilock(ctlr);
+	if(on)
+		ctlr->rcr |= Aap;
+	else
+		ctlr->rcr &= ~Aap;
+	csr32w(ctlr, Rcr, ctlr->rcr);
+	iunlock(ctlr);
+}
+
+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);
+
+	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);
+}
+
+static long
+rtl8169ifstat(Ether* edev, void* a, long n, ulong offset)
+{
+	char *p;
+	Ctlr *ctlr;
+	Dtcc *dtcc;
+	int i, l, r, timeo;
+
+	p = smalloc(READSTR);
+
+	ctlr = edev->ctlr;
+	qlock(&ctlr->slock);
+
+	if(waserror()){
+		qunlock(&ctlr->slock);
+		free(p);
+		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();
+		free(p);
+		return 0;
+	}
+
+	l = snprint(p, READSTR, "TxOk: %llud\n", dtcc->txok);
+	l += snprint(p+l, READSTR-l, "RxOk: %llud\n", dtcc->rxok);
+	l += snprint(p+l, READSTR-l, "TxEr: %llud\n", dtcc->txer);
+	l += snprint(p+l, READSTR-l, "RxEr: %ud\n", dtcc->rxer);
+	l += snprint(p+l, READSTR-l, "MissPkt: %ud\n", dtcc->misspkt);
+	l += snprint(p+l, READSTR-l, "FAE: %ud\n", dtcc->fae);
+	l += snprint(p+l, READSTR-l, "Tx1Col: %ud\n", dtcc->tx1col);
+	l += snprint(p+l, READSTR-l, "TxMCol: %ud\n", dtcc->txmcol);
+	l += snprint(p+l, READSTR-l, "RxOkPh: %llud\n", dtcc->rxokph);
+	l += snprint(p+l, READSTR-l, "RxOkBrd: %llud\n", dtcc->rxokbrd);
+	l += snprint(p+l, READSTR-l, "RxOkMu: %ud\n", dtcc->rxokmu);
+	l += snprint(p+l, READSTR-l, "TxAbt: %ud\n", dtcc->txabt);
+	l += snprint(p+l, READSTR-l, "TxUndrn: %ud\n", dtcc->txundrn);
+
+	l += snprint(p+l, READSTR-l, "serr: %ud\n", ctlr->serr);
+	l += snprint(p+l, READSTR-l, "fovw: %ud\n", ctlr->fovw);
+
+	l += snprint(p+l, READSTR-l, "txdu: %ud\n", ctlr->txdu);
+	l += snprint(p+l, READSTR-l, "tcpf: %ud\n", ctlr->tcpf);
+	l += snprint(p+l, READSTR-l, "udpf: %ud\n", ctlr->udpf);
+	l += snprint(p+l, READSTR-l, "ipf: %ud\n", ctlr->ipf);
+	l += snprint(p+l, READSTR-l, "fovf: %ud\n", ctlr->fovf);
+	l += snprint(p+l, READSTR-l, "rer: %ud\n", ctlr->rer);
+	l += snprint(p+l, READSTR-l, "rdu: %ud\n", ctlr->rdu);
+	l += snprint(p+l, READSTR-l, "punlc: %ud\n", ctlr->punlc);
+
+	l += snprint(p+l, READSTR-l, "tcr: %#8.8ux\n", ctlr->tcr);
+	l += snprint(p+l, READSTR-l, "rcr: %#8.8ux\n", ctlr->rcr);
+	l += snprint(p+l, READSTR-l, "multicast: %ud\n", ctlr->mcast);
+
+	if(ctlr->mii != nil && ctlr->mii->curphy != nil){
+		l += snprint(p+l, READSTR-l, "phy:   ");
+		for(i = 0; i < NMiiPhyr; i++){
+			if(i && ((i & 0x07) == 0))
+				l += snprint(p+l, READSTR-l, "\n       ");
+			r = miimir(ctlr->mii, i);
+			l += snprint(p+l, READSTR-l, " %4.4ux", r);
+		}
+		snprint(p+l, READSTR-l, "\n");
+	}
+
+	n = readstr(offset, a, n, p);
+
+	qunlock(&ctlr->slock);
+	poperror();
+	free(p);
+
+	return n;
+}
+
+static void
+rtl8169halt(Ctlr* ctlr)
+{
+	csr8w(ctlr, Cr, 0);
+
+	ctlr->imr = 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 x;
+	Block *bp;
+	u64int pa;
+
+	x = ctlr->rdt;
+	while(NEXT(x, ctlr->nrd) != ctlr->rdh){
+		bp = iallocb(Mps);
+		if(bp == nil){
+			iprint("rtl8169: no available buffers\n");
+			break;
+		}
+		ctlr->rb[x] = bp;
+		ctlr->nrq++;
+		pa = PCIWADDR(bp->rp);
+		d = &ctlr->rd[x];
+		d->addrlo = pa;
+		d->addrhi = pa >> 32;
+		coherence();
+		d->control = (d->control & Eor) | Own | BALLOC(bp);
+		x = NEXT(x, ctlr->nrd);
+		ctlr->rdt = x;
+	}
+}
+
+static void
+rtl8169init(Ether* edev)
+{
+	int i;
+	u32int r;
+	Block *bp;
+	Ctlr *ctlr;
+	u64int pa;
+	u16int cplusc;
+
+	ctlr = edev->ctlr;
+	ilock(ctlr);
+	if(rtl8169reset(ctlr) < 0){
+		iunlock(ctlr);
+		error("reset failed");
+	}
+
+	memset(ctlr->td, 0, sizeof(D)*ctlr->ntd);
+	ctlr->tdh = ctlr->tdt = ctlr->ntq = 0;
+	ctlr->td[ctlr->ntd-1].control = Eor;
+	for(i = 0; i < ctlr->ntd; i++)
+		if(bp = ctlr->tb[i]){
+			ctlr->tb[i] = nil;
+			freeb(bp);
+		}
+
+	memset(ctlr->rd, 0, sizeof(D)*ctlr->nrd);
+	ctlr->rdh = ctlr->rdt = ctlr->nrq = 0;
+	ctlr->rd[ctlr->nrd-1].control = Eor;
+	for(i = 0; i < ctlr->nrd; i++)
+		if(bp = ctlr->rb[i]){
+			ctlr->rb[i] = nil;
+			freeb(bp);
+		}
+
+	rtl8169replenish(ctlr);
+
+	cplusc = csr16r(ctlr, Cplusc);
+	cplusc &= ~(Endian|Rxchksum);
+	cplusc |= Txenb|Mulrw;
+	switch(ctlr->macv){
+	case Macv40:
+	case Macv44:
+		cplusc |= Macstatdis;
+		break;
+	default:
+		cplusc |= Rxenb;
+		break;
+	}
+	csr16w(ctlr, Cplusc, cplusc);
+
+	pa = PCIWADDR(ctlr->td);
+	csr32w(ctlr, Tnpds+4, pa>>32);
+	csr32w(ctlr, Tnpds, pa);
+
+	pa = PCIWADDR(ctlr->rd);
+	csr32w(ctlr, Rdsar+4, pa>>32);
+	csr32w(ctlr, Rdsar, pa);
+
+	csr8w(ctlr, Cr, Te|Re);
+
+	csr32w(ctlr, Tcr, Ifg1|Ifg0|Mtxdmaunlimited);
+	ctlr->tcr = csr32r(ctlr, Tcr);
+	switch(ctlr->macv){
+	case Macv42:
+	case Macv45:
+		ctlr->rcr = Rxfth256|Mrxdmaunlimited|Ab|Am|Apm;
+		break;
+	default:
+		ctlr->rcr = Rxfthnone|Mrxdmaunlimited|Ab|Am|Apm;
+		break;
+	}
+	ctlr->mchash = 0;
+	csr32w(ctlr, Mar0,   0);
+	csr32w(ctlr, Mar0+4, 0);
+	csr32w(ctlr, Rcr, ctlr->rcr);
+
+	/* maximum packet sizes, unlimited */
+	csr8w(ctlr, Etx, 0x3f);
+	csr16w(ctlr, Rms, 0x3fff);
+
+	csr16w(ctlr, Coal, 0);
+
+	/* no early rx interrupts */
+	r = csr16r(ctlr, Mulint) & 0xF000;
+	csr16w(ctlr, Mulint, r);
+
+	ctlr->imr = Serr|Fovw|Punlc|Rdu|Ter|Rer|Rok|Tdu;
+	csr16w(ctlr, Imr, ctlr->imr);
+
+	csr32w(ctlr, Mpc, 0);
+
+	iunlock(ctlr);
+}
+
+static void
+rtl8169reseter(void *arg)
+{
+	Ether *edev;
+	Ctlr *ctlr;
+
+	edev = arg;
+	while(waserror())
+		;
+	for(;;){
+		ctlr = edev->ctlr;
+		sleep(&ctlr->reset, return0, nil);
+		rtl8169init(edev);
+	}
+}
+
+static void rtl8169interrupt(Ureg*, void* arg);
+
+static void
+rtl8169attach(Ether* edev)
+{
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	qlock(&ctlr->alock);
+	if(ctlr->init){
+		qunlock(&ctlr->alock);
+		return;
+	}
+	if(waserror()){
+		print("#l%d: rtl8169: %s\n", edev->ctlrno, up->errstr);
+		qunlock(&ctlr->alock);
+		nexterror();
+	}
+	ctlr->ntd = Ntd;
+	ctlr->nrd = Nrd;
+
+	ctlr->tb = malloc(ctlr->ntd*sizeof(Block*));
+	ctlr->rb = malloc(ctlr->nrd*sizeof(Block*));
+	ctlr->td = mallocalign(sizeof(D)*ctlr->ntd, 256, 0, 0);
+	ctlr->rd = mallocalign(sizeof(D)*ctlr->nrd, 256, 0, 0);
+	ctlr->dtcc = mallocalign(sizeof(Dtcc), 64, 0, 0);
+	if(waserror()){
+		free(ctlr->tb);
+		ctlr->tb = nil;
+		free(ctlr->rb);
+		ctlr->rb = nil;
+		free(ctlr->td);
+		ctlr->td = nil;
+		free(ctlr->rd);
+		ctlr->rd = nil;
+		free(ctlr->dtcc);
+		ctlr->dtcc = nil;
+		nexterror();
+	}
+
+	if(ctlr->tb == nil || ctlr->rb == nil 
+	|| ctlr->td == nil || ctlr->rd == nil
+	|| ctlr->dtcc == nil)
+		error(Enomem);
+
+	pcisetbme(ctlr->pcidev);
+	intrenable(edev->irq, rtl8169interrupt, edev, edev->tbdf, edev->name);
+	if(waserror()){
+		rtl8169halt(ctlr);
+		pciclrbme(ctlr->pcidev);
+		intrdisable(edev->irq, rtl8169interrupt, edev, edev->tbdf, edev->name);
+		nexterror();
+	}
+
+	rtl8169init(edev);
+	rtl8169mii(edev);
+	ctlr->init = 1;
+
+	poperror();
+	poperror();
+
+	kproc("rtl8169", rtl8169reseter, edev);
+
+	qunlock(&ctlr->alock);
+	poperror();
+}
+
+static void
+rtl8169link(Ether* edev)
+{
+	uint r;
+	int limit;
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+
+	r = csr8r(ctlr, Phystatus);
+	/*
+	 * Maybe the link changed - do we care very much?
+	 * Could stall transmits if no link, maybe?
+	 */
+	edev->link = (r & Linksts) != 0;
+
+	limit = 256*1024;
+	if(r & Speed10){
+		edev->mbps = 10;
+		limit = 65*1024;
+	} else if(r & Speed100)
+		edev->mbps = 100;
+	else if(r & Speed1000)
+		edev->mbps = 1000;
+
+	if(edev->oq != nil)
+		qsetlimit(edev->oq, limit);
+}
+
+static void
+rtl8169transmit(Ether* edev)
+{
+	D *d;
+	Block *bp;
+	Ctlr *ctlr;
+	u64int pa;
+	int x;
+
+	ctlr = edev->ctlr;
+
+	if(!canlock(ctlr))
+		return;
+	for(x = ctlr->tdh; ctlr->ntq > 0; x = NEXT(x, ctlr->ntd)){
+		d = &ctlr->td[x];
+		if(d->control & Own)
+			break;
+
+		/*
+		 * 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;
+		ctlr->ntq--;
+	}
+	ctlr->tdh = x;
+
+	x = ctlr->tdt;
+	while(ctlr->ntq < (ctlr->ntd-1)){
+		if((bp = qget(edev->oq)) == nil)
+			break;
+
+		pa = PCIWADDR(bp->rp);
+		d = &ctlr->td[x];
+		d->addrlo = pa;
+		d->addrhi = pa >> 32;
+		coherence();
+		d->control = (d->control & Eor) | Own | Fs | Ls | BLEN(bp);
+
+		ctlr->tb[x] = bp;
+		ctlr->ntq++;
+
+		x = NEXT(x, ctlr->ntd);
+	}
+	if(x != ctlr->tdt)
+		ctlr->tdt = x;
+	else if(ctlr->ntq >= (ctlr->ntd-1))
+		ctlr->txdu++;
+
+	if(ctlr->ntq > 0){
+		coherence();
+		csr8w(ctlr, Tppoll, Npq);
+	}
+	unlock(ctlr);
+}
+
+static void
+rtl8169receive(Ether* edev)
+{
+	D *d;
+	Block *bp;
+	Ctlr *ctlr;
+	u32int control;
+	int x;
+
+	ctlr = edev->ctlr;
+	if(ctlr->nrq < ctlr->nrd/2)
+		rtl8169replenish(ctlr);
+
+	for(x = ctlr->rdh; x != ctlr->rdt;){
+		d = &ctlr->rd[x];
+		if((control = d->control) & Own)
+			break;
+
+		bp = ctlr->rb[x];
+		ctlr->rb[x] = nil;
+		ctlr->nrq--;
+
+		x = NEXT(x, ctlr->nrd);
+		ctlr->rdh = x;
+
+		if(ctlr->nrq < ctlr->nrd/2)
+			rtl8169replenish(ctlr);
+
+		if((control & (Fs|Ls|Res)) == (Fs|Ls)){
+			bp->wp = bp->rp + (control & RxflMASK) - 4;
+
+			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);
+		}else{
+			if(!(control & Res))
+				ctlr->frag++;
+			freeb(bp);
+		}
+	}
+}
+
+static void
+rtl8169restart(Ctlr *ctlr)
+{
+	rtl8169halt(ctlr);
+	wakeup(&ctlr->reset);
+}
+
+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 & Serr)
+			ctlr->serr++;
+		if(isr & Fovw)
+			ctlr->fovw++;
+		if(isr & Rer)
+			ctlr->rer++;
+		if(isr & Rdu)
+			ctlr->rdu++;
+		if(isr & Punlc)
+			ctlr->punlc++;
+
+		if(isr & (Serr|Fovw)){
+			rtl8169restart(ctlr);
+			break;
+		}
+
+		if(isr & (Punlc|Rdu|Rer|Rok))
+			rtl8169receive(edev);
+
+		if(isr & (Tdu|Ter|Tok))
+			rtl8169transmit(edev);
+
+		if(isr & Punlc)
+			rtl8169link(edev);
+	}
+}
+
+static void
+rtl8169shutdown(Ether *edev)
+{
+	Ctlr *ctlr = edev->ctlr;
+
+	rtl8169halt(ctlr);
+}
+
+int
+vetmacv(Ctlr *ctlr, uint *macv)
+{
+	*macv = csr32r(ctlr, Tcr) & HwveridMASK;
+	switch(*macv){
+	default:
+		return -1;
+	case Macv01:
+	case Macv02:
+	case Macv03:
+	case Macv04:
+	case Macv05:
+	case Macv07:
+	case Macv07a:
+	case Macv11:
+	case Macv12:
+	case Macv12a:
+	case Macv13:
+	case Macv14:
+	case Macv15:
+	case Macv25:
+	case Macv26:
+	case Macv27:
+	case Macv28:
+	case Macv29:
+	case Macv30:
+	case Macv39:
+	case Macv40:
+	case Macv42:
+	case Macv44:
+	case Macv45:
+		break;
+	}
+	return 0;
+}
+
+static void
+rtl8169pci(void)
+{
+	Pcidev *p;
+	Ctlr *ctlr;
+	int i, port, pcie;
+	uint macv;
+
+	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;
+		}
+
+		if(p->mem[0].size == 0 || (p->mem[0].bar & 1) == 0)
+			continue;
+		port = p->mem[0].bar & ~3;
+		if(ioalloc(port, p->mem[0].size, 0, "rtl8169") < 0){
+			print("rtl8169: port %#ux in use\n", port);
+			continue;
+		}
+
+		ctlr = malloc(sizeof(Ctlr));
+		if(ctlr == nil){
+			print("rtl8169: can't allocate memory\n");
+			iofree(port);
+			continue;
+		}
+		ctlr->port = port;
+		ctlr->pcidev = p;
+		ctlr->pciv = i;
+		ctlr->pcie = pcie;
+
+		pcienable(p);
+		if(vetmacv(ctlr, &macv) == -1){
+			print("rtl8169: %T: unknown mac %.4ux %.8ux\n", p->tbdf, p->did, macv);
+			pcidisable(p);
+			iofree(port);
+			free(ctlr);
+			continue;
+		}
+		rtl8169halt(ctlr);
+
+		/*
+		 * Extract the chip hardware version,
+		 * needed to configure each properly.
+		 */
+		ctlr->macv = macv;
+		if(rtl8169ctlrhead != nil)
+			rtl8169ctlrtail->next = ctlr;
+		else
+			rtl8169ctlrhead = ctlr;
+		rtl8169ctlrtail = ctlr;
+	}
+}
+
+static int
+rtl8169pnp(Ether* edev)
+{
+	u32int r;
+	Ctlr *ctlr;
+	uchar ea[Eaddrlen];
+	static int once;
+
+	if(once == 0){
+		once = 1;
+		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;
+	edev->maxmtu = Mtu;
+
+	/*
+	 * 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->ifstat = rtl8169ifstat;
+	edev->shutdown = rtl8169shutdown;
+
+	edev->arg = edev;
+	edev->promiscuous = rtl8169promiscuous;
+	edev->multicast = rtl8169multicast;
+
+	rtl8169link(edev);
+
+	return 0;
+}
+
+void
+ether8169link(void)
+{
+	addethercard("rtl8169", rtl8169pnp);
+}
--- /dev/null
+++ b/os/pc/ether82563.c
@@ -1,0 +1,2280 @@
+/*
+ * Intel 8256[367], 8257[1-9], 8258[03], i350
+ *	Gigabit Ethernet PCI-Express Controllers
+ * Coraid EtherDrive® hba
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/pci.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+#include "../port/etherif.h"
+
+/*
+ * note: the 82575, 82576 and 82580 are operated using registers aliased
+ * to the 82563-style architecture.  many features seen in the 82598
+ * are also seen in the 82575 part.
+ */
+
+enum {
+	/* General */
+
+	Ctrl		= 0x0000,	/* Device Control */
+	Status		= 0x0008,	/* Device Status */
+	Eec		= 0x0010,	/* EEPROM/Flash Control/Data */
+	Eerd		= 0x0014,	/* EEPROM Read */
+	Ctrlext		= 0x0018,	/* Extended Device Control */
+	Fla		= 0x001c,	/* Flash Access */
+	Mdic		= 0x0020,	/* MDI Control */
+	Fcal		= 0x0028,	/* Flow Control Address Low */
+	Fcah		= 0x002C,	/* Flow Control Address High */
+	Fct		= 0x0030,	/* Flow Control Type */
+	Kumctrlsta	= 0x0034,	/* Kumeran Control and Status Register */
+	Connsw		= 0x0034,	/* copper / fiber switch control; 82575/82576 */
+	Vet		= 0x0038,	/* VLAN EtherType */
+	Fcttv		= 0x0170,	/* Flow Control Transmit Timer Value */
+	Txcw		= 0x0178,	/* Transmit Configuration Word */
+	Rxcw		= 0x0180,	/* Receive Configuration Word */
+	Ledctl		= 0x0E00,	/* LED control */
+	Pba		= 0x1000,	/* Packet Buffer Allocation */
+	Pbs		= 0x1008,	/* Packet Buffer Size */
+
+	/* Interrupt */
+
+	Icr		= 0x00C0,	/* Interrupt Cause Read */
+	Itr		= 0x00c4,	/* Interrupt Throttling Rate */
+	Ics		= 0x00C8,	/* Interrupt Cause Set */
+	Ims		= 0x00D0,	/* Interrupt Mask Set/Read */
+	Imc		= 0x00D8,	/* Interrupt mask Clear */
+	Iam		= 0x00E0,	/* Interrupt acknowledge Auto Mask */
+	Eitr		= 0x1680,	/* Extended itr; 82575/6 80 only */
+
+	/* Receive */
+
+	Rctl		= 0x0100,	/* Control */
+	Ert		= 0x2008,	/* Early Receive Threshold (573[EVL], 82578 only) */
+	Fcrtl		= 0x2160,	/* Flow Control RX Threshold Low */
+	Fcrth		= 0x2168,	/* Flow Control Rx Threshold High */
+	Psrctl		= 0x2170,	/* Packet Split Receive Control */
+	Drxmxod		= 0x2540,	/* dma max outstanding bytes (82575) */
+	Rdbal		= 0x2800,	/* Rdesc Base Address Low Queue 0 */
+	Rdbah		= 0x2804,	/* Rdesc Base Address High Queue 0 */
+	Rdlen		= 0x2808,	/* Descriptor Length Queue 0 */
+	Srrctl		= 0x280c,	/* split and replication rx control (82575) */
+	Rdh		= 0x2810,	/* Descriptor Head Queue 0 */
+	Rdt		= 0x2818,	/* Descriptor Tail Queue 0 */
+	Rdtr		= 0x2820,	/* Descriptor Timer Ring */
+	Rxdctl		= 0x2828,	/* Descriptor Control */
+	Radv		= 0x282C,	/* Interrupt Absolute Delay Timer */
+	Rsrpd		= 0x2c00,	/* Small Packet Detect */
+	Raid		= 0x2c08,	/* ACK interrupt delay */
+	Cpuvec		= 0x2c10,	/* CPU Vector */
+	Rxcsum		= 0x5000,	/* Checksum Control */
+	Rmpl		= 0x5004,	/* rx maximum packet length (82575) */
+	Rfctl		= 0x5008,	/* Filter Control */
+	Mta		= 0x5200,	/* Multicast Table Array */
+	Ral		= 0x5400,	/* Receive Address Low */
+	Rah		= 0x5404,	/* Receive Address High */
+	Vfta		= 0x5600,	/* VLAN Filter Table Array */
+	Mrqc		= 0x5818,	/* Multiple Receive Queues Command */
+
+	/* Transmit */
+
+	Tctl		= 0x0400,	/* Transmit Control */
+	Tipg		= 0x0410,	/* Transmit IPG */
+	Tkabgtxd	= 0x3004,	/* glci afe band gap transmit ref data, or something */
+	Tdbal		= 0x3800,	/* Tdesc Base Address Low */
+	Tdbah		= 0x3804,	/* Tdesc Base Address High */
+	Tdlen		= 0x3808,	/* Descriptor Length */
+	Tdh		= 0x3810,	/* Descriptor Head */
+	Tdt		= 0x3818,	/* Descriptor Tail */
+	Tidv		= 0x3820,	/* Interrupt Delay Value */
+	Txdctl		= 0x3828,	/* Descriptor Control */
+	Tadv		= 0x382C,	/* Interrupt Absolute Delay Timer */
+	Tarc0		= 0x3840,	/* Arbitration Counter Queue 0 */
+
+	/* Statistics */
+
+	Statistics	= 0x4000,	/* Start of Statistics Area */
+	Gorcl		= 0x88/4,	/* Good Octets Received Count */
+	Gotcl		= 0x90/4,	/* Good Octets Transmitted Count */
+	Torl		= 0xC0/4,	/* Total Octets Received */
+	Totl		= 0xC8/4,	/* Total Octets Transmitted */
+	Nstatistics	= 0x124/4,
+
+	/* iNVM (i211) */
+	Invmdata0	= 0x12120,
+};
+
+enum {					/* Ctrl */
+	Lrst		= 1<<3,		/* link reset */
+	Slu		= 1<<6,		/* Set Link Up */
+	Devrst		= 1<<26,	/* Device Reset */
+	Rfce		= 1<<27,	/* Receive Flow Control Enable */
+	Tfce		= 1<<28,	/* Transmit Flow Control Enable */
+	Phyrst		= 1<<31,	/* Phy Reset */
+};
+
+enum {					/* Status */
+	Lu		= 1<<1,		/* Link Up */
+	Lanid		= 3<<2,		/* mask for Lan ID. */
+	Txoff		= 1<<4,		/* Transmission Paused */
+	Tbimode		= 1<<5,		/* TBI Mode Indication */
+	Phyra		= 1<<10,	/* PHY Reset Asserted */
+	GIOme		= 1<<19,	/* GIO Master Enable Status */
+};
+
+enum {					/* Eec */
+	Nvpres		= 1<<8,
+	Autord		= 1<<9,
+	Flupd		= 1<<19,
+	Sec1val		= 1<<22,
+};
+
+enum {					/* Eerd */
+	EEstart		= 1<<0,		/* Start Read */
+	EEdone		= 1<<1,		/* Read done */
+};
+
+enum {					/* Ctrlext */
+	Eerst		= 1<<13,	/* EEPROM Reset */
+	Linkmode	= 3<<22,	/* linkmode */
+	Internalphy	= 0<<22,	/* " internal phy (copper) */
+	Sgmii		= 2<<22,	/* " sgmii */
+	Serdes		= 3<<22,	/* " serdes */
+};
+
+enum {
+	/* Connsw */
+	Enrgirq		= 1<<2,	/* interrupt on power detect (enrgsrc) */
+};
+
+enum {					/* EEPROM content offsets */
+	Ea		= 0x00,		/* Ethernet Address */
+};
+
+enum {					/* Mdic */
+	MDIdMASK	= 0x0000FFFF,	/* Data */
+	MDIdSHIFT	= 0,
+	MDIrMASK	= 0x001F0000,	/* PHY Register Address */
+	MDIrSHIFT	= 16,
+	MDIpMASK	= 0x03E00000,	/* PHY Address */
+	MDIpSHIFT	= 21,
+	MDIwop		= 0x04000000,	/* Write Operation */
+	MDIrop		= 0x08000000,	/* Read Operation */
+	MDIready	= 0x10000000,	/* End of Transaction */
+	MDIie		= 0x20000000,	/* Interrupt Enable */
+	MDIe		= 0x40000000,	/* Error */
+};
+
+enum {					/* phy interface */
+	Phyctl		= 0,		/* phy ctl register */
+	Physr		= 1,		/* phy status register */
+	Phyid1		= 2,		/* phy id1 */
+	Phyid2		= 3,		/* phy id2 */
+	Phyisr		= 19,		/* 82563 phy interrupt status register */
+	Phylhr		= 19,		/* 8257[12] link health register */
+	Physsr		= 17,		/* phy secondary status register */
+	Phyprst		= 193<<8 | 17,	/* 8256[34] phy port reset */
+	Phyier		= 18,		/* 82573 phy interrupt enable register */
+	Phypage		= 22,		/* 8256[34] page register */
+	Phystat		= 26,		/* 82580 phy status */
+	Phyapage	= 29,
+	Rtlink		= 1<<10,	/* realtime link status */
+	Phyan		= 1<<11,	/* phy has autonegotiated */
+
+	/* Phyctl bits */
+	Ran		= 1<<9,	/* restart auto negotiation */
+	Ean		= 1<<12,	/* enable auto negotiation */
+
+	/* Phyprst bits */
+	Prst		= 1<<0,	/* reset the port */
+
+	/* 82573 Phyier bits */
+	Lscie		= 1<<10,	/* link status changed ie */
+	Ancie		= 1<<11,	/* auto negotiation complete ie */
+	Spdie		= 1<<14,	/* speed changed ie */
+	Panie		= 1<<15,	/* phy auto negotiation error ie */
+
+	/* Phylhr/Phyisr bits */
+	Anf		= 1<<6,	/* lhr: auto negotiation fault */
+	Ane		= 1<<15,	/* isr: auto negotiation error */
+
+	/* 82580 Phystat bits */
+	Ans	= 1<<14 | 1<<15,	/* 82580 autoneg. status */
+	Link	= 1<<6,		/* 82580 Link */
+
+	/* Rxcw builtin serdes */
+	Anc		= 1<<31,
+	Rxsynch		= 1<<30,
+	Rxcfg		= 1<<29,
+	Rxcfgch		= 1<<28,
+	Rxcfgbad	= 1<<27,
+	Rxnc		= 1<<26,
+
+	/* Txcw */
+	Txane		= 1<<31,
+	Txcfg		= 1<<30,
+};
+
+enum {					/* fiber (pcs) interface */
+	Pcsctl	= 0x4208,		/* pcs control */
+	Pcsstat	= 0x420c,		/* pcs status */
+
+	/* Pcsctl bits */
+	Pan	= 1<<16,		/* autoegotiate */
+	Prestart	= 1<<17,		/* restart an (self clearing) */
+
+	/* Pcsstat bits */
+	Linkok	= 1<<0,		/* link is okay */
+	Andone	= 1<<16,		/* an phase is done see below for success */
+	Anbad	= 1<<19 | 1<<20,	/* Anerror | Anremfault */
+};
+
+enum {					/* Icr, Ics, Ims, Imc */
+	Txdw		= 0x00000001,	/* Transmit Descriptor Written Back */
+	Txqe		= 0x00000002,	/* Transmit Queue Empty */
+	Lsc		= 0x00000004,	/* Link Status Change */
+	Rxseq		= 0x00000008,	/* Receive Sequence Error */
+	Rxdmt0		= 0x00000010,	/* Rdesc Minimum Threshold Reached */
+	Rxo		= 0x00000040,	/* Receiver Overrun */
+	Rxt0		= 0x00000080,	/* Receiver Timer Interrupt; !82575/6/80 only */
+	Rxdw		= 0x00000080,	/* Rdesc write back; 82575/6/80 only */
+	Mdac		= 0x00000200,	/* MDIO Access Completed */
+	Rxcfgset		= 0x00000400,	/* Receiving /C/ ordered sets */
+	Ack		= 0x00020000,	/* Receive ACK frame */
+	Omed		= 1<<20,	/* media change; pcs interface */
+};
+
+enum {					/* Txcw */
+	TxcwFd		= 0x00000020,	/* Full Duplex */
+	TxcwHd		= 0x00000040,	/* Half Duplex */
+	TxcwPauseMASK	= 0x00000180,	/* Pause */
+	TxcwPauseSHIFT	= 7,
+	TxcwPs		= 1<<TxcwPauseSHIFT,	/* Pause Supported */
+	TxcwAs		= 2<<TxcwPauseSHIFT,	/* Asymmetric FC desired */
+	TxcwRfiMASK	= 0x00003000,	/* Remote Fault Indication */
+	TxcwRfiSHIFT	= 12,
+	TxcwNpr		= 0x00008000,	/* Next Page Request */
+	TxcwConfig	= 0x40000000,	/* Transmit COnfig Control */
+	TxcwAne		= 0x80000000,	/* Auto-Negotiation Enable */
+};
+
+enum {					/* Rctl */
+	Rrst		= 0x00000001,	/* Receiver Software Reset */
+	Ren		= 0x00000002,	/* Receiver Enable */
+	Sbp		= 0x00000004,	/* Store Bad Packets */
+	Upe		= 0x00000008,	/* Unicast Promiscuous Enable */
+	Mpe		= 0x00000010,	/* Multicast Promiscuous Enable */
+	Lpe		= 0x00000020,	/* Long Packet Reception Enable */
+	RdtmsMASK	= 0x00000300,	/* Rdesc Minimum Threshold Size */
+	RdtmsHALF	= 0x00000000,	/* Threshold is 1/2 Rdlen */
+	RdtmsQUARTER	= 0x00000100,	/* Threshold is 1/4 Rdlen */
+	RdtmsEIGHTH	= 0x00000200,	/* Threshold is 1/8 Rdlen */
+	MoMASK		= 0x00003000,	/* Multicast Offset */
+	Bam		= 0x00008000,	/* Broadcast Accept Mode */
+	BsizeMASK	= 0x00030000,	/* Receive Buffer Size */
+	Bsize16384	= 0x00010000,	/* Bsex = 1 */
+	Bsize8192	= 0x00020000, 	/* Bsex = 1 */
+	Bsize2048	= 0x00000000,
+	Bsize1024	= 0x00010000,
+	Bsize512	= 0x00020000,
+	Bsize256	= 0x00030000,
+	BsizeFlex	= 0x08000000,	/* Flexable Bsize in 1kb increments */
+	Vfe		= 0x00040000,	/* VLAN Filter Enable */
+	Cfien		= 0x00080000,	/* Canonical Form Indicator Enable */
+	Cfi		= 0x00100000,	/* Canonical Form Indicator value */
+	Dpf		= 0x00400000,	/* Discard Pause Frames */
+	Pmcf		= 0x00800000,	/* Pass MAC Control Frames */
+	Bsex		= 0x02000000,	/* Buffer Size Extension */
+	Secrc		= 0x04000000,	/* Strip CRC from incoming packet */
+};
+
+enum {					/* Srrctl */
+	Dropen		= 1<<31,
+};
+
+enum {					/* Tctl */
+	Trst		= 0x00000001,	/* Transmitter Software Reset */
+	Ten		= 0x00000002,	/* Transmit Enable */
+	Psp		= 0x00000008,	/* Pad Short Packets */
+	Mulr		= 0x10000000,	/* Allow multiple concurrent requests */
+	CtMASK		= 0x00000FF0,	/* Collision Threshold */
+	CtSHIFT		= 4,
+	ColdMASK	= 0x003FF000,	/* Collision Distance */
+	ColdSHIFT	= 12,
+	Swxoff		= 0x00400000,	/* Sofware XOFF Transmission */
+	Pbe		= 0x00800000,	/* Packet Burst Enable */
+	Rtlc		= 0x01000000,	/* Re-transmit on Late Collision */
+	Nrtu		= 0x02000000,	/* No Re-transmit on Underrrun */
+};
+
+enum {					/* [RT]xdctl */
+	PthreshMASK	= 0x0000003F,	/* Prefetch Threshold */
+	PthreshSHIFT	= 0,
+	HthreshMASK	= 0x00003F00,	/* Host Threshold */
+	HthreshSHIFT	= 8,
+	WthreshMASK	= 0x003F0000,	/* Writeback Threshold */
+	WthreshSHIFT	= 16,
+	Gran		= 0x01000000,	/* Granularity; not 82575 */
+	Enable		= 0x02000000,
+};
+
+enum {					/* Rxcsum */
+	Ipofl		= 0x0100,	/* IP Checksum Off-load Enable */
+	Tuofl		= 0x0200,	/* TCP/UDP Checksum Off-load Enable */
+};
+
+typedef struct Rd {			/* Receive Descriptor */
+	u32int	addr[2];
+	u16int	length;
+	u16int	checksum;
+	uchar	status;
+	uchar	errors;
+	u16int	special;
+} Rd;
+
+enum {					/* Rd status */
+	Rdd		= 0x01,		/* Descriptor Done */
+	Reop		= 0x02,		/* End of Packet */
+	Ixsm		= 0x04,		/* Ignore Checksum Indication */
+	Vp		= 0x08,		/* Packet is 802.1Q (matched VET) */
+	Tcpcs		= 0x20,		/* TCP Checksum Calculated on Packet */
+	Ipcs		= 0x40,		/* IP Checksum Calculated on Packet */
+	Pif		= 0x80,		/* Passed in-exact filter */
+};
+
+enum {					/* Rd errors */
+	Ce		= 0x01,		/* CRC Error or Alignment Error */
+	Se		= 0x02,		/* Symbol Error */
+	Seq		= 0x04,		/* Sequence Error */
+	Cxe		= 0x10,		/* Carrier Extension Error */
+	Tcpe		= 0x20,		/* TCP/UDP Checksum Error */
+	Ipe		= 0x40,		/* IP Checksum Error */
+	Rxe		= 0x80,		/* RX Data Error */
+};
+
+typedef struct {			/* Transmit Descriptor */
+	u32int	addr[2];		/* Data */
+	u32int	control;
+	u32int	status;
+} Td;
+
+enum {					/* Tdesc control */
+	LenMASK		= 0x000FFFFF,	/* Data/Packet Length Field */
+	LenSHIFT	= 0,
+	DtypeCD		= 0x00000000,	/* Data Type 'Context Descriptor' */
+	DtypeDD		= 0x00100000,	/* Data Type 'Data Descriptor' */
+	PtypeTCP	= 0x01000000,	/* TCP/UDP Packet Type (CD) */
+	Teop		= 0x01000000,	/* End of Packet (DD) */
+	PtypeIP		= 0x02000000,	/* IP Packet Type (CD) */
+	Ifcs		= 0x02000000,	/* Insert FCS (DD) */
+	Tse		= 0x04000000,	/* TCP Segmentation Enable */
+	Rs		= 0x08000000,	/* Report Status */
+	Rps		= 0x10000000,	/* Report Status Sent */
+	Dext		= 0x20000000,	/* Descriptor Extension */
+	Vle		= 0x40000000,	/* VLAN Packet Enable */
+	Ide		= 0x80000000,	/* Interrupt Delay Enable */
+};
+
+enum {					/* Tdesc status */
+	Tdd		= 0x0001,	/* Descriptor Done */
+	Ec		= 0x0002,	/* Excess Collisions */
+	Lc		= 0x0004,	/* Late Collision */
+	Tu		= 0x0008,	/* Transmit Underrun */
+	CssMASK		= 0xFF00,	/* Checksum Start Field */
+	CssSHIFT	= 8,
+};
+
+typedef struct {
+	u16int	*reg;
+	u32int	*reg32;
+	uint	base;
+	uint	lim;
+} Flash;
+
+enum {
+	/* 16 and 32-bit flash registers for ich flash parts */
+	Bfpr	= 0x00/4,		/* flash base 0:12; lim 16:28 */
+	Fsts	= 0x04/2,		/* flash status; Hsfsts */
+	Fctl	= 0x06/2,		/* flash control; Hsfctl */
+	Faddr	= 0x08/4,		/* flash address to r/w */
+	Fdata	= 0x10/4,		/* data @ address */
+
+	/* status register */
+	Fdone	= 1<<0,			/* flash cycle done */
+	Fcerr	= 1<<1,			/* cycle error; write 1 to clear */
+	Ael	= 1<<2,			/* direct access error log; 1 to clear */
+	Scip	= 1<<5,			/* spi cycle in progress */
+	Fvalid	= 1<<14,		/* flash descriptor valid */
+
+	/* control register */
+	Fgo	= 1<<0,			/* start cycle */
+	Flcycle	= 1<<1,			/* two bits: r=0; w=2 */
+	Fdbc	= 1<<8,			/* bytes to read; 5 bits */
+};
+
+enum {
+	Nrd		= 256,		/* power of two */
+	Ntd		= 128,		/* power of two */
+	Nrb		= 512+512,	/* private receive buffers per Ctlr */
+	Rbalign		= BY2PG,	/* rx buffer alignment */
+};
+
+/*
+ * cavet emptor: 82577/78 have been entered speculatitively.
+ * awating datasheet from intel.
+ */
+enum {
+	i82563,
+	i82566,
+	i82567,
+	i82567m,
+	i82571,
+	i82572,
+	i82573,
+	i82574,
+	i82575,
+	i82576,
+	i82577,
+	i82577m,	
+	i82578,
+	i82578m,
+	i82579,
+	i82580,
+	i82583,
+	i210,
+	i217,
+	i218,
+	i219,
+	i350,
+	Nctlrtype,
+};
+
+enum {
+	Fload	= 1<<0,
+	Fert	= 1<<1,
+	F75	= 1<<2,
+	Fpba	= 1<<3,
+	Fflashea= 1<<4,
+	F79phy	= 1<<5,
+	Fnofct	= 1<<6,
+	Fbadcsum= 1<<7,
+	Fnofca	= 1<<8,
+};
+
+typedef struct Ctlrtype Ctlrtype;
+struct Ctlrtype {
+	char	*name;
+	int	mtu;
+	int	flag;
+};
+
+static Ctlrtype cttab[Nctlrtype] = {
+[i82563]	"i82563",	9014,	Fpba,
+[i82566]	"i82566",	1514,	Fload,
+[i82567]	"i82567",	9234,	Fload,
+[i82567m]	"i82567m",	1514,	Fload,
+[i82571]	"i82571",	9234,	Fpba,
+[i82572]	"i82572",	9234,	Fpba,
+[i82573]	"i82573",	8192,	Fert|Fbadcsum,		/* terrible perf above 8k */
+[i82574]	"i82574",	9018,	0,
+[i82575]	"i82575",	9728,	F75|Fflashea,
+[i82576]	"i82576",	9728,	F75,
+[i82577]	"i82577",	4096,	Fload|Fert,
+[i82577m]	"i82577",	1514,	Fload|Fert,
+[i82578]	"i82578",	4096,	Fload|Fert,
+[i82578m]	"i82578",	1514,	Fload|Fert,
+[i82579]	"i82579",	9018,	Fload|Fert|F79phy|Fnofct,
+[i82580]	"i82580",	9728,	F75|F79phy,
+[i82583]	"i82583",	1514,	0,
+[i210]		"i210",		9728,	F75|Fnofct|Fert,
+[i217]		"i217",		2048,	Fload|Fert|F79phy|Fnofct|Fnofca|Fbadcsum,/* 9018, but unstable above 2k */
+[i218]		"i218",		9018,	Fload|Fert|F79phy|Fnofct|Fnofca|Fbadcsum,
+[i219]		"i219",		9018,	Fload|Fert|F79phy|Fnofct|Fnofca|Fbadcsum,
+[i350]		"i350",		9728,	F75|F79phy|Fnofct,
+};
+
+typedef void (*Freefn)(Block*);
+
+typedef struct Ctlr Ctlr;
+struct Ctlr {
+	uvlong	port;
+	Pcidev	*pcidev;
+	Ctlr	*next;
+	int	active;
+	int	type;
+	u16int	eeprom[0x40];
+
+	QLock	alock;			/* attach */
+	void	*alloc;			/* receive/transmit descriptors */
+	int	nrd;
+	int	ntd;
+	int	rbsz;
+
+	u32int	*nic;
+	Lock	imlock;
+	int	im;			/* interrupt mask */
+
+	Rendez	lrendez;
+	int	lim;
+
+	QLock	slock;
+	u32int	statistics[Nstatistics];
+	uint	lsleep;
+	uint	lintr;
+	uint	rsleep;
+	uint	rintr;
+	uint	txdw;
+	uint	tintr;
+	uint	ixsm;
+	uint	ipcs;
+	uint	tcpcs;
+	uint	speeds[4];
+	uint	phyerrata;
+
+	uchar	ra[Eaddrlen];		/* receive address */
+	u32int	mta[128];		/* multicast table array */
+
+	Rendez	rrendez;
+	int	rim;
+	int	rdfree;
+	Rd	*rdba;			/* receive descriptor base address */
+	Block	**rb;			/* receive buffers */
+	uint	rdh;			/* receive descriptor head */
+	uint	rdt;			/* receive descriptor tail */
+	int	rdtr;			/* receive delay timer ring value */
+	int	radv;			/* receive interrupt absolute delay timer */
+
+	Rendez	trendez;
+	QLock	tlock;
+	int	tbusy;
+	Td	*tdba;			/* transmit descriptor base address */
+	Block	**tb;			/* transmit buffers */
+	int	tdh;			/* transmit descriptor head */
+	int	tdt;			/* transmit descriptor tail */
+
+	int	fcrtl;
+	int	fcrth;
+
+	u32int	pba;			/* packet buffer allocation */
+};
+
+#define csr32r(c, r)	(*((c)->nic+((r)/4)))
+#define csr32w(c, r, v)	(*((c)->nic+((r)/4)) = (v))
+
+static	Ctlr	*i82563ctlrhead;
+static	Ctlr	*i82563ctlrtail;
+
+static char *statistics[Nstatistics] = {
+	"CRC Error",
+	"Alignment Error",
+	"Symbol Error",
+	"RX Error",
+	"Missed Packets",
+	"Single Collision",
+	"Excessive Collisions",
+	"Multiple Collision",
+	"Late Collisions",
+	nil,
+	"Collision",
+	"Transmit Underrun",
+	"Defer",
+	"Transmit - No CRS",
+	"Sequence Error",
+	"Carrier Extension Error",
+	"Receive Error Length",
+	nil,
+	"XON Received",
+	"XON Transmitted",
+	"XOFF Received",
+	"XOFF Transmitted",
+	"FC Received Unsupported",
+	"Packets Received (64 Bytes)",
+	"Packets Received (65-127 Bytes)",
+	"Packets Received (128-255 Bytes)",
+	"Packets Received (256-511 Bytes)",
+	"Packets Received (512-1023 Bytes)",
+	"Packets Received (1024-mtu Bytes)",
+	"Good Packets Received",
+	"Broadcast Packets Received",
+	"Multicast Packets Received",
+	"Good Packets Transmitted",
+	nil,
+	"Good Octets Received",
+	nil,
+	"Good Octets Transmitted",
+	nil,
+	nil,
+	nil,
+	"Receive No Buffers",
+	"Receive Undersize",
+	"Receive Fragment",
+	"Receive Oversize",
+	"Receive Jabber",
+	"Management Packets Rx",
+	"Management Packets Drop",
+	"Management Packets Tx",
+	"Total Octets Received",
+	nil,
+	"Total Octets Transmitted",
+	nil,
+	"Total Packets Received",
+	"Total Packets Transmitted",
+	"Packets Transmitted (64 Bytes)",
+	"Packets Transmitted (65-127 Bytes)",
+	"Packets Transmitted (128-255 Bytes)",
+	"Packets Transmitted (256-511 Bytes)",
+	"Packets Transmitted (512-1023 Bytes)",
+	"Packets Transmitted (1024-mtu Bytes)",
+	"Multicast Packets Transmitted",
+	"Broadcast Packets Transmitted",
+	"TCP Segmentation Context Transmitted",
+	"TCP Segmentation Context Fail",
+	"Interrupt Assertion",
+	"Interrupt Rx Pkt Timer",
+	"Interrupt Rx Abs Timer",
+	"Interrupt Tx Pkt Timer",
+	"Interrupt Tx Abs Timer",
+	"Interrupt Tx Queue Empty",
+	"Interrupt Tx Desc Low",
+	"Interrupt Rx Min",
+	"Interrupt Rx Overrun",
+};
+
+static char*
+cname(Ctlr *c)
+{
+	return cttab[c->type].name;
+}
+
+static long
+i82563ifstat(Ether *edev, void *a, long n, ulong offset)
+{
+	char *s, *p, *e, *stat;
+	int i, r;
+	uvlong tuvl, ruvl;
+	Ctlr *ctlr;
+
+	p = s = smalloc(READSTR);
+	e = p + READSTR;
+
+	ctlr = edev->ctlr;
+	qlock(&ctlr->slock);
+
+	for(i = 0; i < Nstatistics; i++){
+		r = csr32r(ctlr, Statistics + i*4);
+		if((stat = statistics[i]) == nil)
+			continue;
+		switch(i){
+		case Gorcl:
+		case Gotcl:
+		case Torl:
+		case Totl:
+			ruvl = r;
+			ruvl += (uvlong)csr32r(ctlr, Statistics+(i+1)*4) << 32;
+			tuvl = ruvl;
+			tuvl += ctlr->statistics[i];
+			tuvl += (uvlong)ctlr->statistics[i+1] << 32;
+			if(tuvl == 0)
+				continue;
+			ctlr->statistics[i] = tuvl;
+			ctlr->statistics[i+1] = tuvl >> 32;
+			p = seprint(p, e, "%s: %llud %llud\n", stat, tuvl, ruvl);
+			i++;
+			break;
+
+		default:
+			ctlr->statistics[i] += r;
+			if(ctlr->statistics[i] == 0)
+				continue;
+			p = seprint(p, e, "%s: %ud %ud\n", stat,
+				ctlr->statistics[i], r);
+			break;
+		}
+	}
+
+	p = seprint(p, e, "lintr: %ud %ud\n", ctlr->lintr, ctlr->lsleep);
+	p = seprint(p, e, "rintr: %ud %ud\n", ctlr->rintr, ctlr->rsleep);
+	p = seprint(p, e, "tintr: %ud %ud\n", ctlr->tintr, ctlr->txdw);
+	p = seprint(p, e, "ixcs: %ud %ud %ud\n", ctlr->ixsm, ctlr->ipcs, ctlr->tcpcs);
+	p = seprint(p, e, "rdtr: %ud\n", ctlr->rdtr);
+	p = seprint(p, e, "radv: %ud\n", ctlr->radv);
+	p = seprint(p, e, "ctrl: %.8ux\n", csr32r(ctlr, Ctrl));
+	p = seprint(p, e, "ctrlext: %.8ux\n", csr32r(ctlr, Ctrlext));
+	p = seprint(p, e, "status: %.8ux\n", csr32r(ctlr, Status));
+	p = seprint(p, e, "txcw: %.8ux\n", csr32r(ctlr, Txcw));
+	p = seprint(p, e, "txdctl: %.8ux\n", csr32r(ctlr, Txdctl));
+	p = seprint(p, e, "pba: %.8ux\n", ctlr->pba);
+
+	p = seprint(p, e, "speeds: 10:%ud 100:%ud 1000:%ud ?:%ud\n",
+		ctlr->speeds[0], ctlr->speeds[1], ctlr->speeds[2], ctlr->speeds[3]);
+	p = seprint(p, e, "type: %s\n", cname(ctlr));
+
+	p = seprint(p, e, "eeprom:");
+	for(i = 0; i < 0x40; i++){
+		if(i && ((i & 7) == 0))
+			p = seprint(p, e, "\n       ");
+		p = seprint(p, e, " %4.4ux", ctlr->eeprom[i]);
+	}
+	p = seprint(p, e, "\n");
+
+	USED(p);
+	n = readstr(offset, a, n, s);
+	free(s);
+	qunlock(&ctlr->slock);
+
+	return n;
+}
+
+static void
+i82563promiscuous(void *arg, int on)
+{
+	int rctl;
+	Ctlr *ctlr;
+	Ether *edev;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+
+	rctl = csr32r(ctlr, Rctl);
+	rctl &= ~MoMASK;
+	if(on)
+		rctl |= Upe|Mpe;
+	else
+		rctl &= ~(Upe|Mpe);
+	csr32w(ctlr, Rctl, rctl);
+}
+
+static void
+i82563multicast(void *arg, uchar *addr, int on)
+{
+	int bit, x;
+	Ctlr *ctlr;
+	Ether *edev;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+
+	switch(ctlr->type){
+	case i82566:
+	case i82567:
+	case i82567m:
+	case i82577:
+	case i82577m:
+	case i82579:
+	case i217:
+	case i218:
+	case i219:
+		bit = (addr[5]<<2)|(addr[4]>>6);
+		x = (bit>>5) & 31;
+		break;
+	default:
+		bit = (addr[5]<<4)|(addr[4]>>4);
+		x = (bit>>5) & 127;
+		break;
+	}
+	bit &= 31;
+
+	/*
+	 * multiple ether addresses can hash to the same filter bit,
+	 * so it's never safe to clear a filter bit.
+	 * if we want to clear filter bits, we need to keep track of
+	 * all the multicast addresses in use, clear all the filter bits,
+	 * then set the ones corresponding to in-use addresses.
+	 */
+	if(on)
+		ctlr->mta[x] |= 1<<bit;
+
+	csr32w(ctlr, Mta+x*4, ctlr->mta[x]);
+}
+
+static void
+i82563im(Ctlr *ctlr, int im)
+{
+	ilock(&ctlr->imlock);
+	ctlr->im |= im;
+	csr32w(ctlr, Ims, ctlr->im);
+	iunlock(&ctlr->imlock);
+}
+
+static void
+i82563txinit(Ctlr *ctlr)
+{
+	u32int r;
+	Block *b;
+	int i;
+
+	if(cttab[ctlr->type].flag & F75)
+		csr32w(ctlr, Tctl, 0x0F<<CtSHIFT | Psp);
+	else
+		csr32w(ctlr, Tctl, 0x0F<<CtSHIFT | Psp | 66<<ColdSHIFT | Mulr);
+	csr32w(ctlr, Tipg, 6<<20 | 8<<10 | 8);		/* yb sez: 0x702008 */
+	for(i = 0; i < ctlr->ntd; i++){
+		if((b = ctlr->tb[i]) != nil){
+			ctlr->tb[i] = nil;
+			freeb(b);
+		}
+		memset(&ctlr->tdba[i], 0, sizeof(Td));
+	}
+	csr32w(ctlr, Tdbal, PCIWADDR(ctlr->tdba));
+	csr32w(ctlr, Tdbah, 0);
+	csr32w(ctlr, Tdlen, ctlr->ntd * sizeof(Td));
+	ctlr->tdh = PREV(0, ctlr->ntd);
+	csr32w(ctlr, Tdh, 0);
+	ctlr->tdt = 0;
+	csr32w(ctlr, Tdt, 0);
+	csr32w(ctlr, Tidv, 128);
+	csr32w(ctlr, Tadv, 64);
+	csr32w(ctlr, Tctl, csr32r(ctlr, Tctl) | Ten);
+	r = csr32r(ctlr, Txdctl) & ~WthreshMASK;
+	r |= 4<<WthreshSHIFT | 4<<PthreshSHIFT;
+	if(cttab[ctlr->type].flag & F75)
+		r |= Enable;
+	csr32w(ctlr, Txdctl, r);
+}
+
+static uint
+i82563cleanup(Ctlr *c)
+{
+	Block *b;
+	uint tdh, n;
+
+	tdh = c->tdh;
+	while(c->tdba[n = NEXT(tdh, c->ntd)].status & Tdd){
+		tdh = n;
+		if((b = c->tb[tdh]) != nil){
+			c->tb[tdh] = nil;
+			freeb(b);
+		}else
+			iprint("82563 tx underrun!\n");
+		c->tdba[tdh].status = 0;
+	}
+
+	return c->tdh = tdh;
+}
+
+static int
+notrim(void *v)
+{
+	Ctlr *c;
+
+	c = v;
+	return (c->im & Txdw) == 0;
+}
+
+static void
+i82563tproc(void *v)
+{
+	Td *td;
+	Block *bp;
+	Ether *edev;
+	Ctlr *ctlr;
+	uint tdt, n;
+
+	edev = v;
+	ctlr = edev->ctlr;
+	i82563txinit(ctlr);
+
+	tdt = ctlr->tdt;
+	while(waserror())
+		;
+	for(;;){
+		n = NEXT(tdt, ctlr->ntd);
+		if(n == i82563cleanup(ctlr)){
+			ctlr->txdw++;
+			i82563im(ctlr, Txdw);
+			sleep(&ctlr->trendez, notrim, ctlr);
+			continue;
+		}
+		bp = qbread(edev->oq, 100000);
+		td = &ctlr->tdba[tdt];
+		td->addr[0] = PCIWADDR(bp->rp);
+		td->addr[1] = 0;
+		td->control = Ide|Rs|Ifcs|Teop|BLEN(bp);
+		coherence();
+		ctlr->tb[tdt] = bp;
+		ctlr->tdt = tdt = n;
+		csr32w(ctlr, Tdt, tdt);
+	}
+}
+
+static void
+i82563replenish(Ctlr *ctlr)
+{
+	uint rdt, i;
+	Block *bp;
+	Rd *rd;
+
+	i = 0;
+	for(rdt = ctlr->rdt; NEXT(rdt, ctlr->nrd) != ctlr->rdh; rdt = NEXT(rdt, ctlr->nrd)){
+		rd = &ctlr->rdba[rdt];
+		if(ctlr->rb[rdt] != nil){
+			iprint("82563: tx overrun\n");
+			break;
+		}
+		i++;
+		bp = allocb(ctlr->rbsz + Rbalign);
+		bp->rp = bp->wp = (uchar*)ROUND((uintptr)bp->base, Rbalign);
+		ctlr->rb[rdt] = bp;
+		rd->addr[0] = PCIWADDR(bp->rp);
+		rd->addr[1] = 0;
+		rd->status = 0;
+		ctlr->rdfree++;
+	}
+	if(i != 0){
+		coherence();
+		ctlr->rdt = rdt;
+		csr32w(ctlr, Rdt, rdt);
+	}
+}
+
+static void
+i82563rxinit(Ctlr *ctlr)
+{
+	int i;
+	Block *bp;
+
+	if(ctlr->rbsz <= 2048)
+		csr32w(ctlr, Rctl, Dpf|Bsize2048|Bam|RdtmsHALF);
+	else{
+		i = ctlr->rbsz / 1024;
+		if(cttab[ctlr->type].flag & F75){
+			csr32w(ctlr, Rctl, Lpe|Dpf|Bsize2048|Bam|RdtmsHALF|Secrc);
+			if(ctlr->type != i82575)
+				i |= (ctlr->nrd/2>>4)<<20;		/* RdmsHalf */
+			csr32w(ctlr, Srrctl, i | Dropen);
+			csr32w(ctlr, Rmpl, ctlr->rbsz);
+//			csr32w(ctlr, Drxmxod, 0x7ff);
+		}else
+			csr32w(ctlr, Rctl, Lpe|Dpf|BsizeFlex*i|Bam|RdtmsHALF|Secrc);
+	}
+
+	if(cttab[ctlr->type].flag & Fert)
+		csr32w(ctlr, Ert, 1024/8);
+
+	if(ctlr->type == i82566)
+		csr32w(ctlr, Pbs, 16);
+
+	csr32w(ctlr, Rdbal, PCIWADDR(ctlr->rdba));
+	csr32w(ctlr, Rdbah, 0);
+	csr32w(ctlr, Rdlen, ctlr->nrd * sizeof(Rd));
+	ctlr->rdh = 0;
+	csr32w(ctlr, Rdh, 0);
+	ctlr->rdt = 0;
+	csr32w(ctlr, Rdt, 0);
+	ctlr->rdtr = 25;
+	ctlr->radv = 500;
+	csr32w(ctlr, Rdtr, ctlr->rdtr);
+	csr32w(ctlr, Radv, ctlr->radv);
+
+	for(i = 0; i < ctlr->nrd; i++)
+		if((bp = ctlr->rb[i]) != nil){
+			ctlr->rb[i] = nil;
+			freeb(bp);
+		}
+	if(cttab[ctlr->type].flag & F75)
+		csr32w(ctlr, Rxdctl, 1<<WthreshSHIFT | 8<<PthreshSHIFT | 1<<HthreshSHIFT | Enable);
+	else
+		csr32w(ctlr, Rxdctl, 2<<WthreshSHIFT | 2<<PthreshSHIFT);
+
+	/*
+	 * Enable checksum offload.
+	 */
+	csr32w(ctlr, Rxcsum, Tuofl | Ipofl | ETHERHDRSIZE);
+}
+
+static int
+i82563rim(void *v)
+{
+	return ((Ctlr*)v)->rim != 0;
+}
+
+static void
+i82563rproc(void *arg)
+{
+	uint rdh, rim, im;
+	Block *bp;
+	Ctlr *ctlr;
+	Ether *edev;
+	Rd *rd;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+
+	i82563rxinit(ctlr);
+	csr32w(ctlr, Rctl, csr32r(ctlr, Rctl) | Ren);
+	if(cttab[ctlr->type].flag & F75){
+		csr32w(ctlr, Rxdctl, csr32r(ctlr, Rxdctl) | Enable);
+		im = Rxt0|Rxo|Rxdmt0|Rxseq|Ack;
+	}else
+		im = Rxt0|Rxo|Rxdmt0|Rxseq|Ack;
+
+	while(waserror())
+		;
+	for(;;){
+		i82563im(ctlr, im);
+		ctlr->rsleep++;
+		i82563replenish(ctlr);
+		sleep(&ctlr->rrendez, i82563rim, ctlr);
+
+		rdh = ctlr->rdh;
+		for(;;){
+			rim = ctlr->rim;
+			ctlr->rim = 0;
+			rd = &ctlr->rdba[rdh];
+			if(!(rd->status & Rdd))
+				break;
+
+			/*
+			 * Accept eop packets with no errors.
+			 * With no errors and the Ixsm bit set,
+			 * the descriptor status Tpcs and Ipcs bits give
+			 * an indication of whether the checksums were
+			 * calculated and valid.
+			 */
+			bp = ctlr->rb[rdh];
+			if((rd->status & Reop) && rd->errors == 0){
+				bp->wp += rd->length;
+				if(!(rd->status & Ixsm)){
+					ctlr->ixsm++;
+					if(rd->status & Ipcs){
+						/*
+						 * IP checksum calculated
+						 * (and valid as errors == 0).
+						 */
+						ctlr->ipcs++;
+						bp->flag |= Bipck;
+					}
+					if(rd->status & Tcpcs){
+						/*
+						 * TCP/UDP checksum calculated
+						 * (and valid as errors == 0).
+						 */
+						ctlr->tcpcs++;
+						bp->flag |= Btcpck|Budpck;
+					}
+					bp->checksum = rd->checksum;
+					bp->flag |= Bpktck;
+				}
+				etheriq(edev, bp);
+			} else
+				freeb(bp);
+			ctlr->rb[rdh] = nil;
+			ctlr->rdfree--;
+			ctlr->rdh = rdh = NEXT(rdh, ctlr->nrd);
+			if(ctlr->nrd-ctlr->rdfree >= 32 || (rim & Rxdmt0))
+				i82563replenish(ctlr);
+		}
+	}
+}
+
+static int
+i82563lim(void *v)
+{
+	return ((Ctlr*)v)->lim != 0;
+}
+
+static int speedtab[] = {
+	10, 100, 1000, 0
+};
+
+static uint
+phyread(Ctlr *c, int phyno, int reg)
+{
+	uint phy, i;
+
+	csr32w(c, Mdic, MDIrop | phyno<<MDIpSHIFT | reg<<MDIrSHIFT);
+	phy = 0;
+	for(i = 0; i < 64; i++){
+		phy = csr32r(c, Mdic);
+		if(phy & (MDIe|MDIready))
+			break;
+		microdelay(1);
+	}
+	if((phy & (MDIe|MDIready)) != MDIready){
+		print("%s: phy %d wedged %.8ux\n", cname(c), phyno, phy);
+		return ~0;
+	}
+	return phy & 0xffff;
+}
+
+static uint
+phywrite0(Ctlr *c, int phyno, int reg, ushort val)
+{
+	uint phy, i;
+
+	csr32w(c, Mdic, MDIwop | phyno<<MDIpSHIFT | reg<<MDIrSHIFT | val);
+	phy = 0;
+	for(i = 0; i < 64; i++){
+		phy = csr32r(c, Mdic);
+		if(phy & (MDIe|MDIready))
+			break;
+		microdelay(1);
+	}
+	if((phy & (MDIe|MDIready)) != MDIready)
+		return ~0;
+	return 0;
+}
+
+static uint
+setpage(Ctlr *c, uint phyno, uint p, uint r)
+{
+	uint pr;
+
+	if(c->type == i82563){
+		if(r >= 16 && r <= 28 && r != 22)
+			pr = Phypage;
+		else if(r == 30 || r == 31)
+			pr = Phyapage;
+		else
+			return 0;
+		return phywrite0(c, phyno, pr, p);
+	}else if(p == 0)
+		return 0;
+	return ~0;
+}
+
+static uint
+phywrite(Ctlr *c, uint phyno, uint reg, ushort v)
+{
+	if(setpage(c, phyno, reg>>8, reg & 0xff) == ~0)
+		panic("%s: bad phy reg %.4ux", cname(c), reg);
+	return phywrite0(c, phyno, reg & 0xff, v);
+}
+
+static void
+phyerrata(Ether *e, Ctlr *c, uint phyno)
+{
+	if(e->mbps == 0){
+		if(c->phyerrata == 0){
+			c->phyerrata++;
+			phywrite(c, phyno, Phyprst, Prst);	/* try a port reset */
+			print("%s: phy port reset\n", cname(c));
+		}
+	}else
+		c->phyerrata = 0;
+}
+
+static uint
+phyprobe(Ctlr *c, uint mask)
+{
+	uint phy, phyno;
+
+	for(phyno=0; mask != 0; phyno++, mask>>=1){
+		if((mask & 1) == 0)
+			continue;
+		if(phyread(c, phyno, Physr) == ~0)
+			continue;
+		phy = (phyread(c, phyno, Phyid1) & 0x3FFF)<<6;
+		phy |= phyread(c, phyno, Phyid2) >> 10;
+		if(phy == 0xFFFFF || phy == 0)
+			continue;
+		print("%s: phy%d oui %#ux\n", cname(c), phyno, phy);
+		return phyno;
+	}
+	print("%s: no phy\n", cname(c));
+	return ~0;
+}
+
+static void
+lsleep(Ctlr *c, uint m)
+{
+	c->lim = 0;
+	i82563im(c, m);
+	c->lsleep++;
+	sleep(&c->lrendez, i82563lim, c);
+}
+
+static void
+phyl79proc(void *v)
+{
+	uint i, r, phy, phyno;
+	Ctlr *c;
+	Ether *e;
+
+	e = v;
+	c = e->ctlr;
+	while(waserror())
+		;
+
+	while((phyno = phyprobe(c, 3<<1)) == ~0)
+		lsleep(c, Lsc);
+
+	for(;;){
+		phy = 0;
+		for(i=0; i<4; i++){
+			tsleep(&up->sleep, return0, 0, 150);
+			phy = phyread(c, phyno, Phystat);
+			if(phy == ~0)
+				continue;
+			if(phy & Ans){
+				r = phyread(c, phyno, Phyctl);
+				if(r == ~0)
+					continue;
+				phywrite(c, phyno, Phyctl, r | Ran | Ean);
+			}
+			break;
+		}
+		i = (phy>>8) & 3;
+		e->link = i != 3 && (phy & Link) != 0;
+		if(e->link == 0)
+			i = 3;
+		c->speeds[i]++;
+		e->mbps = speedtab[i];
+		lsleep(c, Lsc);
+	}
+}
+
+static void
+phylproc(void *v)
+{
+	uint a, i, phy, phyno;
+	Ctlr *c;
+	Ether *e;
+
+	e = v;
+	c = e->ctlr;
+	while(waserror())
+		;
+
+	while((phyno = phyprobe(c, 3<<1)) == ~0)
+		lsleep(c, Lsc);
+
+	if(c->type == i82573 && (phy = phyread(c, phyno, Phyier)) != ~0)
+		phywrite(c, phyno, Phyier, phy | Lscie | Ancie | Spdie | Panie);
+
+	for(;;){
+		phy = phyread(c, phyno, Physsr);
+		if(phy == ~0){
+			phy = 0;
+			i = 3;
+			goto next;
+		}
+		i = (phy>>14) & 3;
+		switch(c->type){
+		default:
+			a = 0;
+			break;
+		case i82563:
+		case i82578:
+		case i82578m:
+		case i82583:
+			a = phyread(c, phyno, Phyisr) & Ane;
+			break;
+		case i82571:
+		case i82572:
+		case i82575:
+		case i82576:
+			a = phyread(c, phyno, Phylhr) & Anf;
+			i = (i-1) & 3;
+			break;
+		}
+		if(a)
+			phywrite(c, phyno, Phyctl, phyread(c, phyno, Phyctl) | Ran | Ean);
+next:
+		e->link = (phy & Rtlink) != 0;
+		if(e->link == 0)
+			i = 3;
+		c->speeds[i]++;
+		e->mbps = speedtab[i];
+		if(c->type == i82563)
+			phyerrata(e, c, phyno);
+		lsleep(c, Lsc);
+	}
+}
+
+static void
+pcslproc(void *v)
+{
+	uint i, phy;
+	Ctlr *c;
+	Ether *e;
+
+	e = v;
+	c = e->ctlr;
+	while(waserror())
+		;
+
+	if(c->type == i82575 || c->type == i82576)
+		csr32w(c, Connsw, Enrgirq);
+	for(;;){
+		phy = csr32r(c, Pcsstat);
+		e->link = phy & Linkok;
+		i = 3;
+		if(e->link)
+			i = (phy & 6) >> 1;
+		else if(phy & Anbad)
+			csr32w(c, Pcsctl, csr32r(c, Pcsctl) | Pan | Prestart);
+		c->speeds[i]++;
+		e->mbps = speedtab[i];
+		lsleep(c, Lsc | Omed);
+	}
+}
+
+static void
+serdeslproc(void *v)
+{
+	uint i, tx, rx;
+	Ctlr *c;
+	Ether *e;
+
+	e = v;
+	c = e->ctlr;
+	while(waserror())
+		;
+	for(;;){
+		rx = csr32r(c, Rxcw);
+		tx = csr32r(c, Txcw);
+		USED(tx);
+		e->link = (rx & 1<<31) != 0;
+//		e->link = (csr32r(c, Status) & Lu) != 0;
+		i = 3;
+		if(e->link)
+			i = 2;
+		c->speeds[i]++;
+		e->mbps = speedtab[i];
+		lsleep(c, Lsc);
+	}
+}
+
+static void
+i82563attach(Ether *edev)
+{
+	char name[KNAMELEN];
+	Ctlr *ctlr;
+
+	ctlr = edev->ctlr;
+	qlock(&ctlr->alock);
+	if(ctlr->alloc != nil){
+		qunlock(&ctlr->alock);
+		return;
+	}
+
+	ctlr->nrd = Nrd;
+	ctlr->ntd = Ntd;
+	ctlr->alloc = malloc(ctlr->nrd*sizeof(Rd)+ctlr->ntd*sizeof(Td) + 255);
+	ctlr->rb = malloc(ctlr->nrd * sizeof(Block*));
+	ctlr->tb = malloc(ctlr->ntd * sizeof(Block*));
+	if(ctlr->alloc == nil || ctlr->rb == nil || ctlr->tb == nil){
+		free(ctlr->rb);
+		ctlr->rb = nil;
+		free(ctlr->tb);
+		ctlr->tb = nil;
+		free(ctlr->alloc);
+		ctlr->alloc = nil;
+		qunlock(&ctlr->alock);
+		error(Enomem);
+	}
+	ctlr->rdba = (Rd*)ROUNDUP((uintptr)ctlr->alloc, 256);
+	ctlr->tdba = (Td*)(ctlr->rdba + ctlr->nrd);
+
+	if(waserror()){
+		free(ctlr->tb);
+		ctlr->tb = nil;
+		free(ctlr->rb);
+		ctlr->rb = nil;
+		free(ctlr->alloc);
+		ctlr->alloc = nil;
+		qunlock(&ctlr->alock);
+		nexterror();
+	}
+
+	snprint(name, sizeof name, "#l%dl", edev->ctlrno);
+	if(csr32r(ctlr, Status) & Tbimode)
+		kproc(name, serdeslproc, edev, 0);		/* mac based serdes */
+	else if((csr32r(ctlr, Ctrlext) & Linkmode) == Serdes)
+		kproc(name, pcslproc, edev, 0);		/* phy based serdes */
+	else if(cttab[ctlr->type].flag & F79phy)
+		kproc(name, phyl79proc, edev, 0);
+	else
+		kproc(name, phylproc, edev, 0);
+
+	snprint(name, sizeof name, "#l%dr", edev->ctlrno);
+	kproc(name, i82563rproc, edev, 0);
+
+	snprint(name, sizeof name, "#l%dt", edev->ctlrno);
+	kproc(name, i82563tproc, edev, 0);
+
+	qunlock(&ctlr->alock);
+	poperror();
+}
+
+static void
+i82563interrupt(Ureg*, void *arg)
+{
+	Ctlr *ctlr;
+	Ether *edev;
+	int icr, im;
+
+	edev = arg;
+	ctlr = edev->ctlr;
+
+	ilock(&ctlr->imlock);
+	csr32w(ctlr, Imc, ~0);
+	im = ctlr->im;
+
+	while(icr = csr32r(ctlr, Icr) & ctlr->im){
+		if(icr & (Lsc | Omed)){
+			im &= ~(Lsc | Omed);
+			ctlr->lim = icr & (Lsc | Omed);
+			wakeup(&ctlr->lrendez);
+			ctlr->lintr++;
+		}
+		if(icr & (Rxt0|Rxo|Rxdmt0|Rxseq|Ack)){
+			ctlr->rim = icr & (Rxt0|Rxo|Rxdmt0|Rxseq|Ack);
+			im &= ~(Rxt0|Rxo|Rxdmt0|Rxseq|Ack);
+			wakeup(&ctlr->rrendez);
+			ctlr->rintr++;
+		}
+		if(icr & Txdw){
+			im &= ~Txdw;
+			ctlr->tintr++;
+			wakeup(&ctlr->trendez);
+		}
+	}
+
+	ctlr->im = im;
+	csr32w(ctlr, Ims, im);
+	iunlock(&ctlr->imlock);
+}
+
+static int
+i82563detach(Ctlr *ctlr)
+{
+	int r, timeo;
+
+	/* balance rx/tx packet buffer; survives reset */
+	if(ctlr->rbsz > 8192 && cttab[ctlr->type].flag & Fpba){
+		ctlr->pba = csr32r(ctlr, Pba);
+		r = ctlr->pba >> 16;
+		r += ctlr->pba & 0xffff;
+		r >>= 1;
+		csr32w(ctlr, Pba, r);
+	}else if(ctlr->type == i82573 && ctlr->rbsz > 1514)
+		csr32w(ctlr, Pba, 14);
+	ctlr->pba = csr32r(ctlr, Pba);
+
+	/*
+	 * Perform a device reset to get the chip back to the
+	 * power-on state, followed by an EEPROM reset to read
+	 * the defaults for some internal registers.
+	 */
+	csr32w(ctlr, Imc, ~0);
+	csr32w(ctlr, Rctl, 0);
+	csr32w(ctlr, Tctl, csr32r(ctlr, Tctl) & ~Ten);
+
+	delay(10);
+
+	r = csr32r(ctlr, Ctrl);
+	if(ctlr->type == i82566 || ctlr->type == i82579)
+		r |= Phyrst;
+	/*
+	 * hack: 82579LM on lenovo X230 is stuck at 10mbps after
+	 * reseting the phy, but works fine if we dont reset.
+	 */
+	if(ctlr->pcidev->did == 0x1502)
+		r &= ~Phyrst;
+	csr32w(ctlr, Ctrl, Devrst | r);
+	delay(1);
+	for(timeo = 0;; timeo++){
+		if((csr32r(ctlr, Ctrl) & (Devrst|Phyrst)) == 0)
+			break;
+		if(timeo >= 1000)
+			return -1;
+		delay(1);
+	}
+
+	r = csr32r(ctlr, Ctrl);
+	csr32w(ctlr, Ctrl, Slu|r);
+
+	r = csr32r(ctlr, Ctrlext);
+	csr32w(ctlr, Ctrlext, r|Eerst);
+	delay(1);
+	for(timeo = 0; timeo < 1000; timeo++){
+		if(!(csr32r(ctlr, Ctrlext) & Eerst))
+			break;
+		delay(1);
+	}
+	if(csr32r(ctlr, Ctrlext) & Eerst)
+		return -1;
+
+	csr32w(ctlr, Imc, ~0);
+	delay(1);
+	for(timeo = 0; timeo < 1000; timeo++){
+		if((csr32r(ctlr, Icr) & ~Rxcfg) == 0)
+			break;
+		delay(1);
+	}
+	if(csr32r(ctlr, Icr) & ~Rxcfg)
+		return -1;
+
+	return 0;
+}
+
+static void
+i82563shutdown(Ether *edev)
+{
+	i82563detach(edev->ctlr);
+}
+
+static int
+eeread(Ctlr *ctlr, int adr)
+{
+	int timeout;
+
+	csr32w(ctlr, Eerd, EEstart | adr << 2);
+	timeout = 1000;
+	while ((csr32r(ctlr, Eerd) & EEdone) == 0 && timeout--)
+		microdelay(5);
+	if (timeout < 0) {
+		print("%s: eeread timeout\n", cname(ctlr));
+		return -1;
+	}
+	return (csr32r(ctlr, Eerd) >> 16) & 0xffff;
+}
+
+static int
+eeload(Ctlr *ctlr)
+{
+	u16int sum;
+	int data, adr;
+
+	sum = 0;
+	for (adr = 0; adr < 0x40; adr++) {
+		data = eeread(ctlr, adr);
+		if(data == -1) return -1;
+		ctlr->eeprom[adr] = data;
+		sum += data;
+	}
+	return sum;
+}
+
+static int
+fread16(Ctlr *c, Flash *f, int ladr)
+{
+	u16int s;
+	int timeout;
+
+	delay(1);
+	s = f->reg[Fsts];
+	if((s&Fvalid) == 0)
+		return -1;
+	f->reg[Fsts] |= Fcerr | Ael;
+	for(timeout = 0; timeout < 10; timeout++){
+		if((s&Scip) == 0)
+			goto done;
+		delay(1);
+		s = f->reg[Fsts];
+	}
+	return -1;
+done:
+	f->reg[Fsts] |= Fdone;
+	f->reg32[Faddr] = ladr;
+
+	/* setup flash control register */
+	s = f->reg[Fctl] & ~0x3ff;
+	f->reg[Fctl] = s | 1<<8 | Fgo;	/* 2 byte read */
+	timeout = 1000;
+	while((f->reg[Fsts] & Fdone) == 0 && timeout--)
+		microdelay(5);
+	if(timeout < 0){
+		print("%s: fread timeout\n", cname(c));
+		return -1;
+	}
+	if(f->reg[Fsts] & (Fcerr|Ael))
+		return -1;
+	return f->reg32[Fdata] & 0xffff;
+}
+
+static int
+fread32(Ctlr *c, Flash *f, int ladr, u32int *data)
+{
+	u32int s;
+	int timeout;
+
+	delay(1);
+	s = f->reg32[Fsts/2];
+	if((s&Fvalid) == 0)
+		return -1;
+	f->reg32[Fsts/2] |= Fcerr | Ael;
+	for(timeout = 0; timeout < 10; timeout++){
+		if((s&Scip) == 0)
+			goto done;
+		delay(1);
+		s = f->reg32[Fsts/2];
+	}
+	return -1;
+done:
+	f->reg32[Fsts/2] |= Fdone;
+	f->reg32[Faddr] = ladr;
+
+	/* setup flash control register */
+	s = (f->reg32[Fctl/2] >> 16) & ~0x3ff;
+	f->reg32[Fctl/2] = (s | 3<<8 | Fgo) << 16;	/* 4 byte read */
+	timeout = 1000;
+	while((f->reg32[Fsts/2] & Fdone) == 0 && timeout--)
+		microdelay(5);
+	if(timeout < 0){
+		print("%s: fread timeout\n", cname(c));
+		return -1;
+	}
+	if(f->reg32[Fsts/2] & (Fcerr|Ael))
+		return -1;
+	*data = f->reg32[Fdata];
+	return 0;
+}
+
+static int
+fload32(Ctlr *c)
+{
+	int r, adr;
+	u16int sum;
+	u32int w;
+	Flash f;
+
+	f.reg32 = &c->nic[0xe000/4];
+	f.reg = nil;
+	f.base = 0;
+	f.lim = (((csr32r(c, 0xC) >> 1) & 0x1F) + 1) << 12;
+	r = f.lim >> 1;
+	if(fread32(c, &f, r + 0x24, &w) == -1  || (w & 0xC000) != 0x8000)
+		r = 0;
+	sum = 0;
+	for(adr = 0; adr < 0x20; adr++) {
+		if(fread32(c, &f, r + adr*4, &w) == -1)
+			return -1;
+		c->eeprom[adr*2+0] = w;
+		c->eeprom[adr*2+1] = w>>16;
+		sum += w & 0xFFFF;
+		sum += w >> 16;
+	}
+	return sum;
+}
+
+static int
+fload(Ctlr *c)
+{
+	int data, r, adr;
+	u16int sum;
+	void *va;
+	Flash f;
+
+	memset(c->eeprom, 0xFF, sizeof(c->eeprom));
+	if(c->pcidev->mem[1].bar == 0)
+		return fload32(c);	/* i219 */
+
+	if(c->pcidev->mem[1].bar & 1)
+		return -1;
+
+	va = vmap(c->pcidev->mem[1].bar & ~0xF, c->pcidev->mem[1].size);
+	if(va == nil)
+		return -1;
+	f.reg = va;
+	f.reg32 = va;
+	f.base = f.reg32[Bfpr] & 0x1fff;
+	f.lim = f.reg32[Bfpr]>>16 & 0x1fff;
+	if(csr32r(c, Eec) & Sec1val)
+		f.base += f.lim+1 - f.base >> 1;
+	r = f.base << 12;
+	sum = 0;
+	for(adr = 0; adr < 0x40; adr++) {
+		data = fread16(c, &f, r + adr*2);
+		if(data == -1)
+			goto out;
+		c->eeprom[adr] = data;
+		sum += data;
+	}
+out:
+	vunmap(va, c->pcidev->mem[1].size);
+	return sum;
+}
+
+static int
+invmload(Ctlr *c)
+{
+	int i, a;
+	u32int w;
+
+	memset(c->eeprom, 0xFF, sizeof(c->eeprom));
+	for(i=0; i<64; i++){
+		w = csr32r(c, Invmdata0 + i*4);
+		switch(w & 7){
+		case 0:	// uninitialized structure
+			break;
+		case 1:	// word auto load
+			a = (w & 0xFE00) >> 9;
+			if(a < nelem(c->eeprom))
+				c->eeprom[a] = w >> 16;
+			continue;
+		case 2:	// csr auto load
+			i++;
+		case 3:	// phy auto load
+			continue;
+		case 4:	// rsa key sha256
+			i += 256/32;
+		case 5:	// invalidated structure
+			continue;
+		default:
+			print("invm: %.2x %.8ux\n", i, w);
+			continue;
+		}
+		break;
+	}
+	return 0;
+}
+
+static void
+defaultea(Ctlr *ctlr, uchar *ra)
+{
+	uint i, r;
+	uvlong u;
+	static uchar nilea[Eaddrlen];
+
+	if(memcmp(ra, nilea, Eaddrlen) != 0)
+		return;
+	if(cttab[ctlr->type].flag & Fflashea){
+		/* intel mb bug */
+		u = (uvlong)csr32r(ctlr, Rah)<<32u | (ulong)csr32r(ctlr, Ral);
+		for(i = 0; i < Eaddrlen; i++)
+			ra[i] = u >> 8*i;
+	}
+	if(memcmp(ra, nilea, Eaddrlen) != 0)
+		return;
+	for(i = 0; i < Eaddrlen/2; i++){
+		ra[2*i] = ctlr->eeprom[Ea+i];
+		ra[2*i+1] = ctlr->eeprom[Ea+i] >> 8;
+	}
+	r = (csr32r(ctlr, Status) & Lanid) >> 2;
+	ra[5] += r;				/* ea ctlr[n] = ea ctlr[0]+n */
+}
+
+static int
+i82563reset(Ctlr *ctlr)
+{
+	uchar *ra;
+	int i, r, flag;
+
+	if(i82563detach(ctlr))
+		return -1;
+	flag = cttab[ctlr->type].flag;
+
+	if(ctlr->type == i210 && (csr32r(ctlr, Eec) & Flupd) == 0)
+		r = invmload(ctlr);
+	else if(flag & Fload)
+		r = fload(ctlr);
+	else
+		r = eeload(ctlr);
+
+	if(r != 0 && r != 0xbaba){
+		print("%s: bad eeprom checksum - %#.4ux", cname(ctlr), r);
+		if(flag & Fbadcsum)
+			print("; ignored\n");
+		else {
+			print("\n");
+			return -1;
+		}
+	}
+
+	ra = ctlr->ra;
+	defaultea(ctlr, ra);
+	csr32w(ctlr, Ral, ra[3]<<24 | ra[2]<<16 | ra[1]<<8 | ra[0]);
+	csr32w(ctlr, Rah, 1<<31 | ra[5]<<8 | ra[4]);
+	for(i = 1; i < 16; i++){
+		csr32w(ctlr, Ral+i*8, 0);
+		csr32w(ctlr, Rah+i*8, 0);
+	}
+	memset(ctlr->mta, 0, sizeof(ctlr->mta));
+	for(i = 0; i < 128; i++)
+		csr32w(ctlr, Mta + i*4, 0);
+	if((flag & Fnofca) == 0){
+		csr32w(ctlr, Fcal, 0x00C28001);
+		csr32w(ctlr, Fcah, 0x0100);
+	}
+	if((flag & Fnofct) == 0)
+		csr32w(ctlr, Fct, 0x8808);
+	csr32w(ctlr, Fcttv, 0x0100);
+	csr32w(ctlr, Fcrtl, ctlr->fcrtl);
+	csr32w(ctlr, Fcrth, ctlr->fcrth);
+	if(flag & F75)
+		csr32w(ctlr, Eitr, 128<<2);		/* 128 ¼ microsecond intervals */
+	return 0;
+}
+
+enum {
+	CMrdtr,
+	CMradv,
+	CMpause,
+	CMan,
+};
+
+static Cmdtab i82563ctlmsg[] = {
+	CMrdtr,	"rdtr",	2,
+	CMradv,	"radv",	2,
+	CMpause, "pause", 1,
+	CMan,	"an",	1,
+};
+
+static long
+i82563ctl(Ether *edev, void *buf, long n)
+{
+	char *p;
+	ulong v;
+	Ctlr *ctlr;
+	Cmdbuf *cb;
+	Cmdtab *ct;
+
+	if((ctlr = edev->ctlr) == nil)
+		error(Enonexist);
+
+	cb = parsecmd(buf, n);
+	if(waserror()){
+		free(cb);
+		nexterror();
+	}
+
+	ct = lookupcmd(cb, i82563ctlmsg, nelem(i82563ctlmsg));
+	switch(ct->index){
+	case CMrdtr:
+		v = strtoul(cb->f[1], &p, 0);
+		if(*p || v > 0xffff)
+			error(Ebadarg);
+		ctlr->rdtr = v;
+		csr32w(ctlr, Rdtr, v);
+		break;
+	case CMradv:
+		v = strtoul(cb->f[1], &p, 0);
+		if(*p || v > 0xffff)
+			error(Ebadarg);
+		ctlr->radv = v;
+		csr32w(ctlr, Radv, v);
+		break;
+	case CMpause:
+		csr32w(ctlr, Ctrl, csr32r(ctlr, Ctrl) ^ (Rfce | Tfce));
+		break;
+	case CMan:
+		csr32w(ctlr, Ctrl, csr32r(ctlr, Ctrl) | Lrst | Phyrst);
+		break;
+	}
+	free(cb);
+	poperror();
+
+	return n;
+}
+
+static int
+didtype(int d)
+{
+	/*
+	 * Some names and did values are from
+	 * OpenBSD's em(4) Intel driver.
+	 */
+	switch(d){
+	case 0x1096:		/* copper */
+	case 0x10ba:		/* copper “gilgal” */
+	case 0x1098:		/* serdes; not seen */
+	case 0x10bb:		/* serdes */
+		return i82563;
+	case 0x1049:		/* ich8; mm */
+	case 0x104a:		/* ich8; dm */
+	case 0x104b:		/* ich8; dc */
+	case 0x104d:		/* ich8; v “ninevah” */
+	case 0x10bd:		/* ich9; dm-2 */
+	case 0x294c:		/* ich9 */
+	case 0x104c:		/* ich8; untested */
+	case 0x10c4:		/* ich8; untested */
+	case 0x10c5:		/* ich8; untested */
+		return i82566;
+	case 0x10de:		/* lm ich10d */
+	case 0x10df:		/* lf ich10 */
+	case 0x10e5:		/* lm ich9 */
+	case 0x10f5:		/* lm ich9m; “boazman” */
+	case 0x10ce:		/* v ich10 */
+	case 0x10c0:		/* ich9 */
+	case 0x10c2:		/* ich9; untested */
+	case 0x10c3:		/* ich9; untested */
+	case 0x1501:		/* ich8; untested */
+		return i82567;
+	case 0x10bf:		/* lf ich9m */
+	case 0x10cb:		/* v ich9m */
+	case 0x10cd:		/* lf ich10 */
+	case 0x10cc:		/* lm ich10 */
+		return i82567m;
+	case 0x105e:		/* eb copper */
+	case 0x105f:		/* eb fiber */
+	case 0x1060:		/* eb serdes */
+	case 0x10a4:		/* eb copper */
+	case 0x10a5:		/* eb fiber */
+	case 0x10bc:		/* eb copper */
+	case 0x10d9:		/* eb serdes */
+	case 0x10da:		/* eb serdes “ophir” */
+	case 0x10a0:		/* eb; untested */
+	case 0x10a1:		/* eb; untested */
+	case 0x10d5:		/* copper; untested */
+		return i82571;
+	case 0x107d:		/* ei copper */
+	case 0x107e:		/* ei fiber */
+	case 0x107f:		/* ei serdes */
+	case 0x10b9:		/* ei “rimon” */
+		return i82572;
+	case 0x108b:		/* e “vidalia” */
+	case 0x108c:		/* e (iamt) */
+	case 0x109a:		/* l “tekoa” */
+	case 0x10b0:		/* l; untested */
+	case 0x10b2:		/* v; untested */
+	case 0x10b3:		/* e; untested */
+	case 0x10b4:		/* l; untested */
+		return i82573;
+	case 0x10d3:		/* l or it; “hartwell” */
+	case 0x10f6:		/* la; untested */
+		return i82574;
+	case 0x10a7:		/* eb */
+	case 0x10a9:		/* eb fiber/serdes */
+	case 0x10d6:		/* untested */
+	case 0x10e2:		/* untested */
+		return i82575;
+	case 0x10c9:		/* copper */
+	case 0x10e6:		/* fiber */
+	case 0x10e7:		/* serdes; “kawela” */
+	case 0x10e8:		/* copper; untested */
+	case 0x150a:		/* untested */
+	case 0x150d:		/* serdes backplane */
+	case 0x1518:		/* serdes; untested */
+	case 0x1526:		/* untested */
+		return i82576;
+	case 0x10ea:		/* lc “calpella”; aka pch lan */
+		return i82577;
+	case 0x10eb:		/* lm “calpella” */
+		return i82577m;
+	case 0x10ef:		/* dc “piketon” */
+		return i82578;
+	case 0x1502:		/* lm */
+	case 0x1503:		/* v “lewisville” */
+		return i82579;
+	case 0x10f0:		/* dm “king's creek” */
+		return i82578m;
+	case 0x150e:		/* copper “barton hills” */
+	case 0x150f:		/* fiber */
+	case 0x1510:		/* serdes backplane */
+	case 0x1511:		/* sgmii sfp */
+	case 0x1516:		/* copper */
+		return i82580;
+	case 0x1506:		/* v */
+	case 0x150c:		/* untested */
+		return i82583;
+	case 0x1533:		/* i210-t1 */
+	case 0x1534:		/* i210 */
+	case 0x1536:		/* i210-fiber */
+	case 0x1537:		/* i210-backplane */
+	case 0x1538:		/* i210 sgmii */
+	case 0x1539:		/* i211 copper */
+	case 0x157b:		/* i210 copper flashless */
+	case 0x157c:		/* i210 serdes flashless */
+		return i210;
+	case 0x153a:		/* i217-lm */
+	case 0x153b:		/* i217-v */
+		return i217;
+	case 0x1559:		/* i218-v */
+	case 0x155a:		/* i218-lm */
+	case 0x15a0:		/* i218-lm */
+	case 0x15a1:		/* i218-v */
+	case 0x15a2:		/* i218-lm */
+	case 0x15a3:		/* i218-v */
+		return i218;
+	case 0x156f:		/* i219-lm */
+	case 0x15b7:		/* i219-lm */
+	case 0x1570:		/* i219-v */
+	case 0x15b8:		/* i219-v */
+	case 0x15b9:		/* i219-lm */
+	case 0x15bb:		/* i219-lm */
+	case 0x15d6:		/* i219-v */
+	case 0x15d7:		/* i219-lm */
+	case 0x15d8:		/* i219-v */
+	case 0x15e3:		/* i219-lm */
+		return i219;
+	case 0x151f:		/* i350 “powerville” eeprom-less */
+	case 0x1521:		/* i350 copper */
+	case 0x1522:		/* i350 fiber */
+	case 0x1523:		/* i350 serdes */
+	case 0x1524:		/* i350 sgmii */
+	case 0x1546:		/* i350 DA4 (untested) */
+	case 0x1f40:		/* i354 backplane */
+	case 0x1f41:		/* i354 sgmii */
+	case 0x1f42:		/* i354 sgmii (c2000) */
+	case 0x1f45:		/* i354 backplane 2.5 */
+		return i350;
+	}
+	return -1;
+}
+
+static void
+hbafixup(Pcidev *p)
+{
+	uint i;
+
+	i = pcicfgr32(p, PciSVID);
+	if((i & 0xffff) == 0x1b52 && p->did == 1)
+		p->did = i>>16;
+}
+
+static void
+i82563pci(void)
+{
+	int type;
+	Ctlr *ctlr;
+	Pcidev *p;
+
+	for(p = nil; p = pcimatch(p, 0x8086, 0);){
+		hbafixup(p);
+		if(p->mem[0].bar & 1)
+			continue;
+		if((type = didtype(p->did)) == -1)
+			continue;
+		ctlr = malloc(sizeof(Ctlr));
+		if(ctlr == nil){
+			print("%s: can't allocate memory\n", cttab[type].name);
+			continue;
+		}
+		ctlr->type = type;
+		ctlr->pcidev = p;
+		ctlr->rbsz = ROUND(cttab[type].mtu, 1024);
+		ctlr->port = p->mem[0].bar & ~0xF;
+		if(i82563ctlrhead != nil)
+			i82563ctlrtail->next = ctlr;
+		else
+			i82563ctlrhead = ctlr;
+		i82563ctlrtail = ctlr;
+	}
+}
+
+static int
+setup(Ctlr *ctlr)
+{
+	Pcidev *p;
+
+	p = ctlr->pcidev;
+	ctlr->nic = vmap(ctlr->port, p->mem[0].size);
+	if(ctlr->nic == nil){
+		print("%s: can't map %llux\n", cname(ctlr), ctlr->port);
+		return -1;
+	}
+	pcienable(p);
+	if(i82563reset(ctlr)){
+		pcidisable(p);
+		vunmap(ctlr->nic, p->mem[0].size);
+		return -1;
+	}
+	pcisetbme(p);
+	return 0;
+}
+
+static int
+pnp(Ether *edev, int type)
+{
+	Ctlr *ctlr;
+	static int done;
+
+	if(!done) {
+		i82563pci();
+		done = 1;
+	}
+
+	/*
+	 * Any adapter matches if no edev->port is supplied,
+	 * otherwise the ports must match.
+	 */
+	for(ctlr = i82563ctlrhead; ; ctlr = ctlr->next){
+		if(ctlr == nil)
+			return -1;
+		if(ctlr->active)
+			continue;
+		if(type != -1 && ctlr->type != type)
+			continue;
+		if(edev->port == 0 || edev->port == ctlr->port){
+			ctlr->active = 1;
+			memmove(ctlr->ra, edev->ea, Eaddrlen);
+			if(setup(ctlr) == 0)
+				break;
+		}
+	}
+
+	edev->ctlr = ctlr;
+	edev->port = ctlr->port;
+	edev->irq = ctlr->pcidev->intl;
+	edev->tbdf = ctlr->pcidev->tbdf;
+	edev->mbps = 1000;
+	edev->maxmtu = cttab[ctlr->type].mtu;
+	memmove(edev->ea, ctlr->ra, Eaddrlen);
+
+	/*
+	 * Linkage to the generic ethernet driver.
+	 */
+	edev->attach = i82563attach;
+//	edev->transmit = i82563transmit;
+	edev->ifstat = i82563ifstat;
+	edev->ctl = i82563ctl;
+
+	edev->arg = edev;
+	edev->promiscuous = i82563promiscuous;
+	edev->shutdown = i82563shutdown;
+	edev->multicast = i82563multicast;
+
+	intrenable(edev->irq, i82563interrupt, edev, edev->tbdf, edev->name);
+
+	return 0;
+}
+
+static int
+anypnp(Ether *e)
+{
+	return pnp(e, -1);
+}
+
+static int
+i82563pnp(Ether *e)
+{
+	return pnp(e, i82563);
+}
+
+static int
+i82566pnp(Ether *e)
+{
+	return pnp(e, i82566);
+}
+
+static int
+i82567pnp(Ether *e)
+{
+	return pnp(e, i82567m) & pnp(e, i82567);
+}
+
+static int
+i82571pnp(Ether *e)
+{
+	return pnp(e, i82571);
+}
+
+static int
+i82572pnp(Ether *e)
+{
+	return pnp(e, i82572);
+}
+
+static int
+i82573pnp(Ether *e)
+{
+	return pnp(e, i82573);
+}
+
+static int
+i82574pnp(Ether *e)
+{
+	return pnp(e, i82574);
+}
+
+static int
+i82575pnp(Ether *e)
+{
+	return pnp(e, i82575);
+}
+
+static int
+i82576pnp(Ether *e)
+{
+	return pnp(e, i82576);
+}
+
+static int
+i82577pnp(Ether *e)
+{
+	return pnp(e, i82577m) & pnp(e, i82577);
+}
+
+static int
+i82578pnp(Ether *e)
+{
+	return pnp(e, i82578m) & pnp(e, i82578);
+}
+
+static int
+i82579pnp(Ether *e)
+{
+	return pnp(e, i82579);
+}
+
+static int
+i82580pnp(Ether *e)
+{
+	return pnp(e, i82580);
+}
+
+static int
+i82583pnp(Ether *e)
+{
+	return pnp(e, i82583);
+}
+
+static int
+i210pnp(Ether *e)
+{
+	return pnp(e, i210);
+}
+
+static int
+i217pnp(Ether *e)
+{
+	return pnp(e, i217);
+}
+
+static int
+i218pnp(Ether *e)
+{
+	return pnp(e, i218);
+}
+
+static int
+i219pnp(Ether *e)
+{
+	return pnp(e, i219);
+}
+
+static int
+i350pnp(Ether *e)
+{
+	return pnp(e, i350);
+}
+
+void
+ether82563link(void)
+{
+	/*
+	 * recognise lots of model numbers for debugging
+	 * also good for forcing onboard nic(s) as ether0
+	 * try to make that unnecessary by listing lom first.
+	 */
+	addethercard("i82563", i82563pnp);
+	addethercard("i82566", i82566pnp);
+	addethercard("i82574", i82574pnp);
+	addethercard("i82576", i82576pnp);
+	addethercard("i82567", i82567pnp);
+	addethercard("i82573", i82573pnp);
+
+	addethercard("i82571", i82571pnp);
+	addethercard("i82572", i82572pnp);
+	addethercard("i82575", i82575pnp);
+	addethercard("i82577", i82577pnp);
+	addethercard("i82578", i82578pnp);
+	addethercard("i82579", i82579pnp);
+	addethercard("i82580", i82580pnp);
+	addethercard("i82583", i82583pnp);
+	addethercard("i210", i210pnp);
+	addethercard("i217", i217pnp);
+	addethercard("i218", i218pnp);
+	addethercard("i219", i219pnp);
+	addethercard("i350", i350pnp);
+	addethercard("igbepcie", anypnp);
+}
--- /dev/null
+++ b/os/pc/etherx550.c
@@ -1,0 +1,919 @@
+/*
+ * intel 10GB ethernet pci-express driver
+ * 6.0.0:	net  02.00.00 8086/15c8  11 0:dfc0000c 2097152 4:dfe0400c 16384
+ *	Intel Corporation Ethernet Connection X553/X550-AT 10GBASE-T
+ */
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/pci.h"
+#include "../port/error.h"
+#include "../port/netif.h"
+#include "../port/etherif.h"
+
+
+enum {
+	/* general */
+	Ctrl		= 0x00000/4,	/* Device Control */
+	Status		= 0x00008/4,	/* Device Status */
+	Ctrlext		= 0x00018/4,	/* Extended Device Control */
+	Tcptimer	= 0x0004c/4,	/* tcp timer */
+
+	/* nvm */
+	Eec		= 0x10010/4,	/* eeprom/flash control */
+	Eemngctl	= 0x10110/4,	/* Manageability EEPROM-Mode Control */
+
+	/* interrupt */
+	Icr		= 0x00800/4,	/* interrupt cause read */
+	Ics		= 0x00808/4,	/* " set */
+	Ims		= 0x00880/4,	/* " mask read/set */
+	Imc		= 0x00888/4,	/* " mask clear */
+	Iac		= 0x00810/4,	/* " auto clear */
+	Iam		= 0x00890/4,	/* " auto mask enable */
+	Itr		= 0x00820/4,	/* " throttling rate (0-19) */
+	Ivar		= 0x00900/4,	/* " vector allocation regs. */
+
+	/* rx dma */
+	Rdbal		= 0x01000/4,	/* rx desc base low (0-63) +0x40n */
+	Rdbah		= 0x01004/4,	/* " high */
+	Rdlen		= 0x01008/4,	/* " length */
+	Rdh		= 0x01010/4,	/* " head */
+	Rdt		= 0x01018/4,	/* " tail */
+	Rxdctl		= 0x01028/4,	/* " control */
+
+	Srrctl		= 0x02100/4,	/* split and replication rx ctl. */
+	Rdrxctl		= 0x02f00/4,	/* rx dma control */
+	Rxpbsize	= 0x03c00/4,	/* rx packet buffer size */
+	Rxctrl		= 0x03000/4,	/* rx control */
+
+	/* rx */
+	Rxcsum		= 0x05000/4,	/* rx checksum control */
+	Mcstctrl	= 0x05090/4,	/* multicast control register */
+	Mta		= 0x05200/4,	/* multicast table array (0-127) */
+	Ral		= 0x05400/4,	/* rx address low */
+	Rah		= 0x05404/4,
+	Vfta		= 0x0a000/4,	/* vlan filter table array. */
+	Fctrl		= 0x05080/4,	/* filter control */
+
+	/* tx */
+	Tdbal		= 0x06000/4,	/* tx desc base low +0x40n */
+	Tdbah		= 0x06004/4,	/* " high */
+	Tdlen		= 0x06008/4,	/* " len */
+	Tdh		= 0x06010/4,	/* " head */
+	Tdt		= 0x06018/4,	/* " tail */
+	Txdctl		= 0x06028/4,	/* " control */
+	Dmatxctl	= 0x04a80/4,
+
+	/* mac */
+	Hlreg0		= 0x04240/4,	/* highlander control reg 0 */
+	Hlreg1		= 0x04244/4,	/* highlander control reg 1 (ro) */
+	Maxfrs		= 0x04268/4,	/* max frame size */
+	Links		= 0x042a4/4,	/* link status */
+};
+
+enum {
+	/* Ctrl */
+	Rst		= 1<<26,	/* full nic reset */
+	
+	/* Ctrlext */
+	Drvload		= 1<<28,	/* Driver Load */
+	
+	/* Eec */
+	AutoRd		= 1<<9,		/* NVM auto read done */
+
+	/* Eemngctl */
+	CfgDone0	= 1<<18,	/* Configuration Done Port 0 */
+	CfgDone1	= 1<<19,	/* Configuration Done Port 1 */
+
+	/* Txdctl */
+	Pthresh		= 0,		/* prefresh threshold shift in bits */
+	Hthresh		= 8,		/* host buffer minimum threshold */
+	Wthresh		= 16,		/* writeback threshold */
+	Ten		= 1<<25,
+
+	/* Fctrl */
+	Bam		= 1<<10,	/* broadcast accept mode */
+	Upe 		= 1<<9,		/* unicast promiscuous */
+	Mpe 		= 1<<8,		/* multicast promiscuous */
+
+	/* Rxdctl */
+	Renable		= 1<<25,
+
+	/* Dmatxctl */
+	Txen		= 1<<0,
+
+	/* Rxctl */
+	Rxen		= 1<<0,
+
+	/* Rdrxctl */
+	Dmaidone	= 1<<3,
+
+	/* Rxcsum */
+	Ippcse		= 1<<12,	/* ip payload checksum enable */
+
+	/* Mcstctrl */
+	Mo		= 0,		/* multicast offset 47:36 */
+	Mfe		= 1<<2,		/* multicast filter enable */
+
+	/* Rah */
+	Av		= 1<<31,
+
+	/* interrupts */
+	Irx0		= 1<<0,		/* driver defined - rx interrupt */
+	Itx0		= 1<<1,		/* driver defined - tx interrupt */
+	Lsc		= 1<<20,	/* link status change */
+	
+	/* Ivar Interrupt Vector Allocation Register */
+	Intalloc0	= 0,		/* Map the 0th queue Rx interrupt to the 0th bit of EICR register */
+	Intallocval0	= 1<<7,
+	intalloc1	= 1<<8,		/* Map the 0th queue Tx interrupt to the 1st bit of EICR register */
+	Intallocval1	= 1<<15,
+
+	/* Links */
+	Lnkup	= 1<<30,
+	Lnkspd	= 1<<29,
+
+	/* Hlreg0 */
+	Jumboen	= 1<<2,
+};
+
+typedef struct {
+	uint	reg;
+	char	*name;
+} Stat;
+
+static
+Stat stattab[] = {
+	0x4000,	"crc error",
+	0x4004,	"illegal byte",
+	0x4008,	"short packet",
+	0x3fa0,	"missed pkt0",
+	0x4034,	"mac local flt",
+	0x4038,	"mac rmt flt",
+	0x4040,	"rx length err",
+	0x405c,	"rx 040",
+	0x4060,	"rx 07f",
+	0x4064,	"rx 100",
+	0x4068,	"rx 200",
+	0x406c,	"rx 3ff",
+	0x4070,	"rx big",
+	0x4074,	"rx ok",
+	0x4078,	"rx bcast",
+	0x407c,	"rx mcast",
+	0x4080,	"tx ok",
+	0x40a4,	"rx runt",
+	0x40a8,	"rx frag",
+	0x40ac,	"rx ovrsz",
+	0x40b0,	"rx jab",
+	0x40d0,	"rx pkt",
+	0x40d4,	"tx pkt",
+	0x40d8,	"tx 040",
+	0x40dc,	"tx 07f",
+	0x40e0,	"tx 100",
+	0x40e4,	"tx 200",
+	0x40e8,	"tx 3ff",
+	0x40ec,	"tx big",
+	0x40f0,	"tx mcast",
+	0x40f4,	"tx bcast",
+	0x4120,	"xsum err",
+};
+
+/* status */
+enum {
+	Pif	= 1<<7,	/* past exact filter (sic) */
+	Ipcs	= 1<<6,	/* ip checksum calcuated */
+	L4cs	= 1<<5,	/* layer 2 */
+	Udpcs	= 1<<4,	/* udp checksum calcuated */
+	Vp	= 1<<3,	/* 802.1q packet matched vet */
+	Reop	= 1<<1,	/* end of packet */
+	Rdd	= 1<<0,	/* descriptor done */
+};
+
+typedef struct {
+	u32int	addr[2];
+	ushort	length;
+	ushort	cksum;
+	uchar	status;
+	uchar	errors;
+	ushort	vlan;
+} Rd;
+
+enum {
+	/* Td cmd */
+	Rs	= 1<<3,
+	Ic	= 1<<2,
+	Ifcs	= 1<<1,
+	Teop	= 1<<0,
+
+	/* Td status */
+	Tdd	= 1<<0,
+};
+
+typedef struct {
+	u32int	addr[2];
+	u16int	length;
+	uchar	cso;
+	uchar	cmd;
+	uchar	status;
+	uchar	css;
+	ushort	vlan;
+} Td;
+
+enum {
+	Factive		= 1<<0,
+	Fstarted	= 1<<1,
+};
+
+typedef struct {
+	Pcidev	*p;
+	Ether	*edev;
+	uintptr	io;
+	u32int	*reg;
+	u32int	*regmsi;
+	uchar	flag;
+	int	nrd;
+	int	ntd;
+	int	rbsz;
+	Lock	slock;
+	Lock	alock;
+	QLock	tlock;
+	Rendez	lrendez;
+	Rendez	trendez;
+	Rendez	rrendez;
+	uint	im;
+	uint	lim;
+	uint	rim;
+	uint	tim;
+	Lock	imlock;
+	char	*alloc;
+
+	Rd	*rdba;
+	Block	**rb;
+	uint	rdt;
+	uint	rdfree;
+
+	Td	*tdba;
+	uint	tdh;
+	uint	tdt;
+	Block	**tb;
+
+	uchar	ra[Eaddrlen];
+	u32int	mta[128];
+	ulong	stats[nelem(stattab)];
+	uint	speeds[3];
+} Ctlr;
+
+/* tweakable paramaters */
+enum {
+	Mtu	= 12*1024,
+	Nrd	= 256,
+	Ntd	= 256,
+	Nrb	= 256,
+};
+
+static	Ctlr	*ctlrtab[4];
+static	int	nctlr;
+
+static void
+readstats(Ctlr *c)
+{
+	int i;
+
+	lock(&c->slock);
+	for(i = 0; i < nelem(c->stats); i++)
+		c->stats[i] += c->reg[stattab[i].reg >> 2];
+	unlock(&c->slock);
+}
+
+static int speedtab[] = {
+	0,
+	1000,
+	10000,
+};
+
+static long
+ifstat(Ether *e, void *a, long n, ulong offset)
+{
+	uint i, *t;
+	char *s, *p, *q;
+	Ctlr *c;
+
+	p = s = smalloc(READSTR);
+	q = p + READSTR;
+
+	c = e->ctlr;
+	readstats(c);
+	for(i = 0; i < nelem(stattab); i++)
+		if(c->stats[i] > 0)
+			p = seprint(p, q, "%.10s  %uld\n", stattab[i].name,
+					c->stats[i]);
+	t = c->speeds;
+	p = seprint(p, q, "speeds: 0:%d 1000:%d 10000:%d\n", t[0], t[1], t[2]);
+	seprint(p, q, "rdfree %d rdh %d rdt %d\n", c->rdfree, c->reg[Rdt],
+		c->reg[Rdh]);
+	n = readstr(offset, a, n, s);
+	free(s);
+
+	return n;
+}
+
+static void
+im(Ctlr *c, int i)
+{
+	ilock(&c->imlock);
+	c->im |= i;
+	c->reg[Ims] = c->im;
+	iunlock(&c->imlock);
+}
+
+static int
+lim(void *v)
+{
+	return ((Ctlr*)v)->lim != 0;
+}
+
+static void
+lproc(void *v)
+{
+	int r, i;
+	Ctlr *c;
+	Ether *e;
+
+	e = v;
+	c = e->ctlr;
+	while(waserror())
+		;
+	for (;;) {
+		r = c->reg[Links];
+		e->link = (r & Lnkup) != 0;
+		i = 0;
+		if(e->link)
+			i = 1 + ((r & Lnkspd) != 0);
+		c->speeds[i]++;
+		e->mbps = speedtab[i];
+		c->lim = 0;
+		im(c, Lsc);
+		sleep(&c->lrendez, lim, c);
+		c->lim = 0;
+	}
+}
+
+static long
+ctl(Ether *, void *, long)
+{
+	error(Ebadarg);
+	return -1;
+}
+
+#define Next(x, m)	(((x)+1) & (m))
+
+static int
+cleanup(Ctlr *c, int tdh)
+{
+	Block *b;
+	uint m, n;
+
+	m = c->ntd - 1;
+	while(c->tdba[n = Next(tdh, m)].status & Tdd){
+		tdh = n;
+		b = c->tb[tdh];
+		c->tb[tdh] = 0;
+		freeb(b);
+		c->tdba[tdh].status = 0;
+	}
+	return tdh;
+}
+
+static void
+transmit(Ether *e)
+{
+	uint i, m, tdt, tdh;
+	Ctlr *c;
+	Block *b;
+	Td *t;
+
+	c = e->ctlr;
+	if(!canqlock(&c->tlock)){
+		im(c, Itx0);
+		return;
+	}
+	tdh = c->tdh = cleanup(c, c->tdh);
+	tdt = c->tdt;
+	m = c->ntd - 1;
+	for(i = 0; i < 8; i++){
+		if(Next(tdt, m) == tdh){
+			im(c, Itx0);
+			break;
+		}
+		if(!(b = qget(e->oq)))
+			break;
+		t = c->tdba + tdt;
+		t->addr[0] = PCIWADDR(b->rp);
+		t->length = BLEN(b);
+		t->cmd = Rs | Ifcs | Teop;
+		c->tb[tdt] = b;
+		tdt = Next(tdt, m);
+	}
+	if(i){
+		c->tdt = tdt;
+		c->reg[Tdt] = tdt;
+	}
+	qunlock(&c->tlock);
+}
+
+static int
+tim(void *c)
+{
+	return ((Ctlr*)c)->tim != 0;
+}
+
+static void
+tproc(void *v)
+{
+	Ctlr *c;
+	Ether *e;
+
+	e = v;
+	c = e->ctlr;
+	while(waserror())
+		;
+	for (;;) {
+		sleep(&c->trendez, tim, c);	/* transmit kicks us */
+		c->tim = 0;
+		transmit(e);
+	}
+}
+
+static void
+rxinit(Ctlr *c)
+{
+	int i;
+	Block *b;
+
+	c->reg[Rxctrl] &= ~Rxen;
+	/* Pg 144 Step 2
+		Receive buffers of appropriate size should be allocated
+		and pointers to these buffers should be stored in the
+		descriptor ring - replinish() does this? */
+	for(i = 0; i < c->nrd; i++){
+		b = c->rb[i];
+		c->rb[i] = 0;
+		if(b)
+			freeb(b);
+	}
+	c->rdfree = 0;
+
+	c->reg[Fctrl] |= Bam;
+	c->reg[Rxcsum] |= Ippcse;
+	c->reg[Srrctl] = c->rbsz / 1024;
+	c->reg[Maxfrs] = c->rbsz << 16;
+	c->reg[Hlreg0] |= Jumboen;
+
+	c->reg[Rdbal] = PCIWADDR(c->rdba);
+	c->reg[Rdbah] = 0;
+	c->reg[Rdlen] = c->nrd*sizeof(Rd);
+	c->reg[Rdh] = 0;
+	c->reg[Rdt] = c->rdt = 0;
+
+	c->reg[Rxdctl] = Renable;
+	while((c->reg[Rxdctl] & Renable) == 0)
+		;
+	/* TODO? bump the tail pointer RDT to enable descriptors
+		fetching by setting it to the ring length minus 1. Pg 145 */
+	c->reg[Rxctrl] |= Rxen;
+}
+
+static void
+replenish(Ctlr *c, uint rdh)
+{
+	int rdt, m, i;
+	Block *b;
+	Rd *r;
+
+	m = c->nrd - 1;
+	i = 0;
+	for(rdt = c->rdt; Next(rdt, m) != rdh; rdt = Next(rdt, m)){
+		b = allocb(c->rbsz+BY2PG);
+		b->rp = (uchar*)PGROUND((uintptr)b->base);
+		b->wp = b->rp;
+		c->rb[rdt] = b;
+		r = c->rdba + rdt;
+		r->addr[0] = PCIWADDR(b->rp);
+		r->status = 0;
+		c->rdfree++;
+		i++;
+	}
+	if(i)
+		c->reg[Rdt] = c->rdt = rdt;
+}
+
+static int
+rim(void *v)
+{
+	return ((Ctlr*)v)->rim != 0;
+}
+
+static uchar zeroea[Eaddrlen];
+
+static void
+rproc(void *v)
+{
+	uint m, rdh;
+	Block *b;
+	Ctlr *c;
+	Ether *e;
+	Rd *r;
+
+	e = v;
+	c = e->ctlr;
+	m = c->nrd - 1;
+	rdh = 0;
+	while(waserror())
+		;
+loop:
+	replenish(c, rdh);
+	im(c, Irx0);
+	sleep(&c->rrendez, rim, c);
+loop1:
+	c->rim = 0;
+	if(c->nrd - c->rdfree >= 16)
+		replenish(c, rdh);
+	r = c->rdba + rdh;
+	if(!(r->status & Rdd))
+		goto loop;		/* UGH */
+	b = c->rb[rdh];
+	c->rb[rdh] = 0;
+	b->wp += r->length;
+	if((r->status & 1)){
+		if(r->status & Ipcs)
+			b->flag |= Bipck;
+		b->checksum = r->cksum;
+	}
+//	r->status = 0;
+	etheriq(e, b);
+	c->rdfree--;
+	rdh = Next(rdh, m);
+	goto loop1;			/* UGH */
+}
+
+static void
+promiscuous(void *a, int on)
+{
+	Ctlr *c;
+	Ether *e;
+
+	e = a;
+	c = e->ctlr;
+	if(on)
+		c->reg[Fctrl] |= Upe | Mpe;
+	else
+		c->reg[Fctrl] &= ~(Upe | Mpe);
+}
+
+static void
+multicast(void *a, uchar *ea, int on)
+{
+	int b, i;
+	Ctlr *c;
+	Ether *e;
+
+	e = a;
+	c = e->ctlr;
+
+	/*
+	 * multiple ether addresses can hash to the same filter bit,
+	 * so it's never safe to clear a filter bit.
+	 * if we want to clear filter bits, we need to keep track of
+	 * all the multicast addresses in use, clear all the filter bits,
+	 * then set the ones corresponding to in-use addresses.
+	 *
+	 *  Extracts the 12 bits, from a multicast address, to determine which
+	 *  bit-vector to set in the multicast table. The hardware uses 12 bits, from
+	 *  incoming rx multicast addresses, to determine the bit-vector to check in
+	 *  the MTA. Which of the 4 combination, of 12-bits, the hardware uses is set
+	 *  by the MO field of the MCSTCTRL. The MO field is set during initialization
+	 *  to mc_filter_type.
+	 *
+	 * The MTA is a register array of 128 32-bit registers. It is treated
+	 * like an array of 4096 bits.  We want to set bit
+	 * BitArray[vector_value]. So we figure out what register the bit is
+	 * in, read it, OR in the new bit, then write back the new value.  The
+	 * register is determined by the upper 7 bits of the vector value and
+	 * the bit within that register are determined by the lower 5 bits of
+	 * the value.
+	 *
+	 * when Mcstctrl.Mo == 0, use bits [47:36] of the address
+	 * register index = bits [47:41]
+	 * which bit in the above register = bits [40:36]
+	 */
+	i = ea[5] >> 1;			/* register index = 47:41 (7 bits) */
+	b = (ea[5]&1)<<4 | ea[4]>>4;	/* which bit in the above register = 40:36 (5 bits) */
+	b = 1 << b;
+	if(on)
+		c->mta[i] |= b;
+//	else
+//		c->mta[i] &= ~b;
+	c->reg[Mta+i] = c->mta[i];
+	c->reg[Mcstctrl] = Mfe;
+	/* for(i = 0; i < 128; i++) c->reg[Mta + i] = -1; brute force it to work for testing */
+}
+
+static int
+detach(Ctlr *c)
+{
+	int i;
+	u32int l, h;
+
+	l = c->reg[Ral];
+	h = c->reg[Rah];
+	if (h & Av) {
+		c->ra[0] = l & 0xFF;
+		c->ra[1] = l>>8 & 0xFF;
+		c->ra[2] = l>>16 & 0xFF;
+		c->ra[3] = l>>24 & 0xFF;
+		c->ra[4] = h & 0xFF;
+		c->ra[5] = h>>8 & 0xFF;
+	}
+	c->reg[Imc] = ~0;
+	c->reg[Ctrl] |= Rst;
+	for(i = 0; i < 100; i++){
+		delay(1);
+		if((c->reg[Ctrl] & Rst) == 0)
+			break;
+	}
+	if (i >= 100)
+		return -1;
+	delay(10);
+
+	/* not cleared by reset; kill it manually. */
+	for(i = 1; i < 16; i++)
+		c->reg[Rah + i] &= ~(1 << 31);
+	for(i = 0; i < 128; i++)
+		c->reg[Mta + i] = 0;
+	for(i = 1; i < 640; i++)
+		c->reg[Vfta + i] = 0;
+	c->reg[Ctrlext] &= ~Drvload; /* driver works without this */
+	return 0;
+}
+
+static void
+shutdown(Ether *e)
+{
+	detach(e->ctlr);
+}
+
+static int
+reset(Ctlr *c)
+{
+	int i;
+
+	while((c->reg[Eec] & AutoRd) == 0)
+		;
+	while((c->reg[Eemngctl] & CfgDone0) == 0)
+		;
+	while((c->reg[Eemngctl] & CfgDone1) == 0)
+		;
+	while((c->reg[Rdrxctl] & Dmaidone) == 0)
+		;
+	if(detach(c)){
+		print("iX550: reset timeout\n");
+		return -1;
+	}
+	while((c->reg[Eec] & AutoRd) == 0)
+		;
+	while((c->reg[Eemngctl] & CfgDone0) == 0)
+		;
+	while((c->reg[Eemngctl] & CfgDone1) == 0)
+		;
+	while((c->reg[Rdrxctl] & Dmaidone) == 0)
+		;
+	readstats(c);
+	for(i = 0; i<nelem(c->stats); i++)
+		c->stats[i] = 0;
+
+	/* configure interrupt mapping */
+	c->reg[Ivar] =   Intalloc0 | Intallocval0 | intalloc1 | Intallocval1;
+
+	/* interrupt throttling goes here. */
+	for(i = Itr; i < Itr + 20; i++)
+		c->reg[i] = 1<<3;	/* 1 interval */
+	return 0;
+}
+
+static void
+txinit(Ctlr *c)
+{
+	Block *b;
+	int i;
+
+	c->reg[Txdctl] = 16<<Wthresh | 16<<Pthresh;
+	for(i = 0; i < c->ntd; i++){
+		b = c->tb[i];
+		c->tb[i] = 0;
+		if(b)
+			freeb(b);
+	}
+	memset(c->tdba, 0, c->ntd * sizeof(Td));
+	c->reg[Tdbal] = PCIWADDR(c->tdba);
+	c->reg[Tdbah] = 0;
+	c->reg[Tdlen] = c->ntd*sizeof(Td);
+	c->reg[Tdh] = 0;
+	c->reg[Tdt] = 0;
+	c->tdh = c->ntd - 1;
+	c->tdt = 0;
+
+	c->reg[Txdctl] |= Ten;
+	c->reg[Dmatxctl] |= Txen;
+
+}
+
+static void
+attach(Ether *e)
+{
+	Ctlr *c;
+	int t;
+	char buf[KNAMELEN];
+
+	c = e->ctlr;
+	c->edev = e;			/* point back to Ether* */
+	lock(&c->alock);
+	if(c->alloc){
+		unlock(&c->alock);
+		return;
+	}
+
+	c->nrd = Nrd;
+	c->ntd = Ntd;
+	t  = c->nrd * sizeof *c->rdba + 255;
+	t += c->ntd * sizeof *c->tdba + 255;
+	t += (c->ntd + c->nrd) * sizeof(Block*);
+	c->alloc = malloc(t);
+	unlock(&c->alock);
+	if(c->alloc == nil)
+		error(Enomem);
+
+	c->rdba = (Rd*)ROUNDUP((uintptr)c->alloc, 256);
+	c->tdba = (Td*)ROUNDUP((uintptr)(c->rdba + c->nrd), 256);
+	c->rb = (Block**)(c->tdba + c->ntd);
+	c->tb = (Block**)(c->rb + c->nrd);
+
+	rxinit(c);
+	txinit(c);
+
+	c->reg[Ctrlext] |= Drvload; /* driver works without this */
+	snprint(buf, sizeof buf, "#l%dl", e->ctlrno);
+	kproc(buf, lproc, e, 0);
+	snprint(buf, sizeof buf, "#l%dr", e->ctlrno);
+	kproc(buf, rproc, e, 0);
+	snprint(buf, sizeof buf, "#l%dt", e->ctlrno);
+	kproc(buf, tproc, e, 0);
+}
+
+static void
+interrupt(Ureg*, void *v)
+{
+	int icr, im;
+	Ctlr *c;
+	Ether *e;
+
+	e = v;
+	c = e->ctlr;
+	ilock(&c->imlock);
+	c->reg[Imc] = ~0;
+	im = c->im;
+	while((icr = c->reg[Icr] & c->im) != 0){
+		if(icr & Lsc){
+			im &= ~Lsc;
+			c->lim = icr & Lsc;
+			wakeup(&c->lrendez);
+		}
+		if(icr & Irx0){
+			im &= ~Irx0;
+			c->rim = icr & Irx0;
+			wakeup(&c->rrendez);
+		}
+		if(icr & Itx0){
+			im &= ~Itx0;
+			c->tim = icr & Itx0;
+			wakeup(&c->trendez);
+		}
+	}
+	c->reg[Ims] = c->im = im;
+	iunlock(&c->imlock);
+}
+
+static void
+scan(void)
+{
+	uvlong io, iomsi;
+	void *mem, *memmsi;
+	int pciregs, pcimsix;
+	Ctlr *c;
+	Pcidev *p;
+
+	p = 0;
+	while(p = pcimatch(p, 0x8086, 0x15c8)){	/* X553/X550-AT 10GBASE-T */
+		pcimsix = 4;
+		pciregs = 0;
+		if((p->mem[pciregs].bar & 1) != 0
+		|| (p->mem[pcimsix].bar & 1) != 0)
+			continue;
+		if(nctlr == nelem(ctlrtab)){
+			print("iX550: too many controllers\n");
+			return;
+		}
+		c = malloc(sizeof *c);
+		if(c == nil){
+			print("iX550: can't allocate memory\n");
+			continue;
+		}
+		io = p->mem[pciregs].bar & ~0xF;
+		mem = vmap(io, p->mem[pciregs].size);
+		if(mem == nil){
+			print("iX550: can't map regs %llux\n", io);
+			free(c);
+			continue;
+		}
+		iomsi = p->mem[pcimsix].bar & ~0xF;
+		memmsi = vmap(iomsi, p->mem[pcimsix].size);
+		if(memmsi == nil){
+			print("iX550: can't map msi-x regs %llux\n", iomsi);
+			vunmap(mem, p->mem[pciregs].size);
+			free(c);
+			continue;
+		}
+		pcienable(p);
+		c->p = p;
+		c->io = io;
+		c->reg = (u32int*)mem;
+		c->regmsi = (u32int*)memmsi;
+		c->rbsz = ROUND(Mtu, 1024);
+		if(reset(c)){
+			print("iX550: can't reset\n");
+			vunmap(mem, p->mem[pciregs].size);
+			vunmap(memmsi, p->mem[pcimsix].size);
+			free(c);
+			continue;
+		}
+		pcisetbme(p);
+		ctlrtab[nctlr++] = c;
+	}
+}
+
+static int
+pnp(Ether *e)
+{
+	static uchar zeros[Eaddrlen];
+	int i;
+	Ctlr *c = nil;
+	uchar *p;
+
+	if(nctlr == 0)
+		scan();
+	for(i = 0; i < nctlr; i++){
+		c = ctlrtab[i];
+		if(c == nil || c->flag & Factive)
+			continue;
+		if(e->port == 0 || e->port == c->io)
+			break;
+	}
+	if (i >= nctlr)
+		return -1;
+
+	if(memcmp(c->ra, zeros, Eaddrlen) != 0)
+		memmove(e->ea, c->ra, Eaddrlen);
+
+	p = e->ea;
+	c->reg[Ral] = p[3]<<24 | p[2]<<16 | p[1]<<8 | p[0];
+	c->reg[Rah] = p[5]<<8 | p[4] | 1<<31;
+
+	c->flag |= Factive;
+	e->ctlr = c;
+	e->port = (uintptr)c->reg;
+	e->irq = c->p->intl;
+	e->tbdf = c->p->tbdf;
+	e->mbps = 10000;
+	e->maxmtu = Mtu;
+
+	e->arg = e;
+	e->attach = attach;
+	e->ctl = ctl;
+	e->ifstat = ifstat;
+	e->multicast = multicast;
+	e->promiscuous = promiscuous;
+	e->shutdown = shutdown;
+	e->transmit = transmit;
+
+	intrenable(e->irq, interrupt, e, e->tbdf, e->name);
+
+	return 0;
+}
+
+void
+etherx550link(void)
+{
+	addethercard("iX550", pnp);
+}
--- a/os/port/ethermii.c
+++ b/os/port/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
@@ -14,7 +14,8 @@
 mii(Mii* mii, int mask)
 {
 	MiiPhy *miiphy;
-	int bit, oui, phyno, r, rmask;
+	int bit, oui, phyno, rmask;
+	u32int id;
 
 	/*
 	 * Probe through mii for PHYs in mask;
@@ -33,10 +34,9 @@
 		}
 		if(mii->mir(mii, phyno, Bmsr) == -1)
 			continue;
-		r = mii->mir(mii, phyno, Phyidr1);
-		oui = (r & 0x3FFF)<<6;
-		r = mii->mir(mii, phyno, Phyidr2);
-		oui |= r>>10;
+		id = mii->mir(mii, phyno, Phyidr1) << 16;
+		id |= mii->mir(mii, phyno, Phyidr2);
+		oui = (id & 0x3FFFFC00)>>10;
 		if(oui == 0xFFFFF || oui == 0)
 			continue;
 
@@ -44,6 +44,7 @@
 			continue;
 
 		miiphy->mii = mii;
+		miiphy->id = id;
 		miiphy->oui = oui;
 		miiphy->phyno = phyno;
 
@@ -88,8 +89,7 @@
 	bmcr = mii->mir(mii, mii->curphy->phyno, Bmcr);
 	bmcr |= BmcrR;
 	mii->miw(mii, mii->curphy->phyno, Bmcr, bmcr);
-/*	microdelay(1);*/
-	microdelay(500);	/* DP83847, at least */
+	microdelay(1);
 
 	return 0;
 }
@@ -175,15 +175,14 @@
 	 * (Read status twice as the Ls bit is sticky).
 	 */
 	bmsr = mii->mir(mii, phyno, Bmsr);
-	if(!(bmsr & (BmsrAnc|BmsrAna)))
-{
-print("miistatus: auto-neg incomplete\n");
+	if(!(bmsr & (BmsrAnc|BmsrAna))) {
+		// print("miistatus: auto-neg incomplete\n");
 		return -1;
-}
+	}
 
 	bmsr = mii->mir(mii, phyno, Bmsr);
 	if(!(bmsr & BmsrLs)){
-print("miistatus: link down\n");
+		// print("miistatus: link down\n");
 		phy->link = 0;
 		return -1;
 	}
@@ -215,11 +214,10 @@
 		else if(r & Ana10HD)
 			phy->speed = 10;
 	}
-	if(phy->speed == 0)
-{
-print("miistatus: phy speed 0\n");
+	if(phy->speed == 0) {
+		// print("miistatus: phy speed 0\n");
 		return -1;
-}
+	}
 
 	if(phy->fd){
 		p = phy->fc;
@@ -235,4 +233,30 @@
 	phy->link = 1;
 
 	return 0;
+}
+
+int
+miimmdr(Mii* mii, int a, int r)
+{
+	a &= 0x1F;
+	if(miimiw(mii, Mmdctrl, a) == -1)
+		return -1;
+	if(miimiw(mii, Mmddata, r) == -1)
+		return -1;
+	if(miimiw(mii, Mmdctrl, a | 0x4000) == -1)
+		return -1;
+	return miimir(mii, Mmddata);
+}
+
+int
+miimmdw(Mii* mii, int a, int r, int data)
+{
+	a &= 0x1F;
+	if(miimiw(mii, Mmdctrl, a) == -1)
+		return -1;
+	if(miimiw(mii, Mmddata, r) == -1)
+		return -1;
+	if(miimiw(mii, Mmdctrl, a | 0x4000) == -1)
+		return -1;
+	return miimiw(mii, Mmddata, data);
 }
--- a/os/port/ethermii.h
+++ b/os/port/ethermii.h
@@ -13,6 +13,8 @@
 	Annprr		= 0x08,		/* AN Next Page RX */
 	Mscr		= 0x09,		/* MASTER-SLAVE Control */
 	Mssr		= 0x0A,		/* MASTER-SLAVE Status */
+	Mmdctrl		= 0x0D,		/* MMD Access Control */
+	Mmddata		= 0x0E,		/* MMD Access Data Register */
 	Esr		= 0x0F,		/* Extended Status */
 
 	NMiiPhyr	= 32,
@@ -94,6 +96,7 @@
 
 typedef struct MiiPhy {
 	Mii*	mii;
+	u32int	id;
 	int	oui;
 	int	phyno;
 
@@ -114,3 +117,6 @@
 extern int miimiw(Mii*, int, int);
 extern int miireset(Mii*);
 extern int miistatus(Mii*);
+
+extern int miimmdr(Mii*, int, int);
+extern int miimmdw(Mii*, int, int, int);