shithub: riscv

Download patch

ref: 3bc4e5a6d568889eeeb2d0b7d443c0a3e02fb499
parent: 2a4c767c41025b3fd07790cb031f3c12fc25dd21
author: cinap_lenrek <[email protected]>
date: Thu Jul 25 05:11:53 EDT 2019

bcm64: work in progress genet ethernet driver for raspberry pi 4

--- /dev/null
+++ b/sys/src/9/bcm64/ethergenet.c
@@ -1,0 +1,1075 @@
+#include "u.h"
+#include "../port/lib.h"
+#include "mem.h"
+#include "dat.h"
+#include "fns.h"
+#include "io.h"
+#include "../port/netif.h"
+#include "../port/etherif.h"
+#include "../port/ethermii.h"
+
+enum
+{
+	Rbsz		= 2048,
+	Maxtu		= 1536,
+
+	DmaOWN		= 0x8000,
+	DmaSOP		= 0x2000,
+	DmaEOP		= 0x4000,
+	DmaRxLg		= 0x10,
+	DmaRxNo		= 0x08,
+	DmaRxErr	= 0x04,
+	DmaRxCrc	= 0x02,
+	DmaRxOv		= 0x01,
+	DmaRxErrors	= DmaRxLg|DmaRxNo|DmaRxErr|DmaRxCrc|DmaRxOv,
+
+	DmaTxQtag	= 0x1F80,
+	DmaTxUnderrun	= 0x0200,
+	DmaTxAppendCrc	= 0x0040,
+	DmaTxOwCrc	= 0x0020,
+	DmaTxDoCsum	= 0x0010,
+
+	/* Ctlr->regs */
+	SysRevision	= 0x00/4,
+	SysPortCtrl	= 0x04/4,
+		PortModeIntEphy	= 0,
+		PortModeIntGphy = 1,
+		PortModeExtEphy = 2,
+		PortModeExtGphy = 3,
+		PortModeExtRvmii50 = 4,
+		PortModeExtRvmii25 = 16 | 4,
+		LedActSourceMac = 1 << 9,
+
+	SysRbufFlushCtrl	= 0x08/4,
+	SysTbufFlushCtrl	= 0x0C/4,
+
+	ExtRgmiiOobCtrl	= 0x8C/4,
+		RgmiiLink	= 1 << 4,
+		OobDisable	= 1 << 5,
+		RgmiiModeEn	= 1 << 6,
+		IdModeDis	= 1 << 16,
+
+	Intrl0		= 0x200/4,
+		IrqScb		= 1 << 0,
+		IrqEphy		= 1 << 1,
+		IrqPhyDetR	= 1 << 2,
+		IrqPhyDetF	= 1 << 3,
+		IrqLinkUp	= 1 << 4,
+		IrqLinkDown	= 1 << 5,
+		IrqUmac		= 1 << 6,
+		IrqUmacTsv	= 1 << 7,
+		IrqTbufUnderrun	= 1 << 8,
+		IrqRbufOverflow	= 1 << 9,
+		IrqHfbSm	= 1 << 10,
+		IrqHfbMm	= 1 << 11,
+		IrqMpdR		= 1 << 12,
+		IrqRxDmaDone	= 1 << 13,
+		IrqRxDmaPDone	= 1 << 14,
+		IrqRxDmaBDone	= 1 << 15,
+		IrqTxDmaDone	= 1 << 16,
+		IrqTxDmaPDone	= 1 << 17,
+		IrqTxDmaBDone	= 1 << 18,
+		IrqMdioDone	= 1 << 23,
+		IrqMdioError	= 1 << 24,
+	Intrl1		= 0x240/4,
+		/* Intrl0/1 + ... */
+		IntrSts		= 0x00/4,
+		IntrSet		= 0x04/4,
+		IntrClr		= 0x08/4,
+		IntrMaskSts	= 0x0C/4,
+		IntrMaskSet	= 0x10/4,
+		IntrMaskClr	= 0x14/4,
+
+	RbufCtrl	= 0x300/4,
+		Rbuf64En	= 1 << 0,
+		RbufAlign2B	= 1 << 1,
+		RbufBadDis	= 1 << 2,
+
+	RbufChkCtrl	= 0x314/4,
+		RbufChkRxChkEn	= 1 << 0,
+		RbufChkSkipFcs	= 1 << 4,
+
+	RbufOvflCnt	= 0x394/4,
+	RbufErrCnt	= 0x398/4,
+
+	RbufEnergyCtrl	= 0x39c/4,
+		RbufEeeEn	= 1 << 0,
+		RbufPmEn	= 1 << 1,
+
+	RbufTbufSizeCtrl= 0x3b4/4,
+
+	TbufCtrl	= 0x600/4,
+	TbufBpMc	= 0x60C/4,
+	TbufEnergyCtrl	= 0x614/4,
+
+	UmacCmd		= 0x808/4,
+		CmdTxEn		= 1 << 0,
+		CmdRxEn		= 1 << 1,
+		CmdSpeed10	= 0 << 2,
+		CmdSpeed100	= 1 << 2,
+		CmdSpeed1000	= 2 << 2,
+		CmdSpeedMask	= 3 << 2,
+		CmdProm		= 1 << 4,
+		CmdPadEn	= 1 << 5,
+		CmdCrcFwd	= 1 << 6,
+		CmdPauseFwd	= 1 << 7,
+		CmdRxPauseIgn	= 1 << 8,
+		CmdTxAddrIn	= 1 << 9,
+		CmdHdEn		= 1 << 10,
+		CmdSwReset	= 1 << 13,
+		CmdLclLoopEn	= 1 << 15,
+		CmdAutoConfig	= 1 << 22,
+		CmdCntlFrmEn	= 1 << 23,
+		CmdNoLenChk	= 1 << 24,
+		CmdRmtLoopEn	= 1 << 25,
+		CmdPrblEn	= 1 << 27,
+		CmdTxPauseIgn	= 1 << 28,
+		CmdTxRxEn	= 1 << 29,
+		CmdRuntFilterDis= 1 << 30,
+
+	UmacMac0	= 0x80C/4,
+	UmacMac1	= 0x810/4,
+	UmacMaxFrameLen	= 0x814/4,
+
+	UmacEeeCtrl	= 0x864/4,	
+		UmacEeeEn	= 1<<3,
+
+	UmacEeeLpiTimer	= 0x868/4,
+	UmacEeeWakeTimer= 0x86C/4,
+	UmacEeeRefCount	= 0x870/4,
+		EeeRefCountMask = 0xFFFF,
+
+	UmacTxFlush	= 0xb34/4,
+
+	UmacMibCtrl	= 0xd80/4,
+		MibResetRx	= 1 << 0,
+		MibResetRunt	= 1 << 1,
+		MibResetTx	= 1 << 2,
+
+	MdioCmd		= 0xe14/4,
+		MdioStartBusy	= 1 << 29,
+		MdioReadFail	= 1 << 28,
+		MdioRead	= 2 << 26,
+		MdioWrite	= 1 << 26,
+		MdioPhyShift	= 21,
+		MdioPhyMask	= 0x1F,
+		MdioAddrShift	= 16,
+		MdioAddrMask	= 0x1F,
+
+	UmacMpdCtrl	= 0xe20/4,
+		MpdEn	= 1 << 0,
+		MpdPwEn	= 1 << 27,
+
+	UmacMdfCtrl	= 0xe50/4,
+	UmacMdfAddr0	= 0xe54/4,
+
+	RdmaOffset	= 0x2000/4,
+	TdmaOffset	= 0x4000/4,
+	HfbOffset	= 0x8000/4,
+
+	HfbCtlr		= 0xFC00/4,
+	HfbFltEnable	= 0xFC04/4,
+	HfbFltLen	= 0xFC1C/4,
+
+	/* common Ring->regs */
+	RdmaWP		= 0x00/4,
+	TdmaRP		= 0x00/4,
+	RxWP		= 0x08/4,
+	TxRP		= 0x08/4,
+	TxWP		= 0x0C/4,
+	RxRP		= 0x0C/4,
+	DmaRingBufSize	= 0x10/4,
+	DmaStart	= 0x14/4,
+	DmaEnd		= 0x1C/4,
+	DmaDoneThresh	= 0x24/4,
+	TdmaFlowPeriod	= 0x28/4,
+	RdmaXonXoffThresh=0x28/4,
+	TdmaWP		= 0x2C/4,
+	RdmaRP		= 0x2C/4,
+
+	/*
+	 * reg offsets only for RING16
+	 * ctlr->rx->regs / ctlr->tx->regs
+	 */
+	RingCfg		= 0x40/4,
+		RxRingCfgMask	= 0x10000,
+		TxRingCfgMask	= 0x1000F,
+
+	DmaCtrl		= 0x44/4,
+		DmaCtrlEn	= 1 << 0,
+	DmaStatus	= 0x48/4,
+		DmaStatusDis	= 1 << 0,
+	DmaScbBurstSize	= 0x4C/4,
+
+	TdmaArbCtrl	= 0x6C/4,
+	TdmaPriority0	= 0x70/4,
+	TdmaPriority1	= 0x74/4,
+	TdmaPriority2	= 0x78/4,
+
+	RdmaTimeout0	= 0x6C/4,
+	RdmaIndex2Ring0	= 0xB0/4,
+};
+
+typedef struct Desc Desc;
+typedef struct Ring Ring;
+typedef struct Ctlr Ctlr;
+
+struct Desc
+{
+	u32int	*d;	/* hw descriptor */
+	Block	*b;
+};
+
+struct Ring
+{
+	Rendez;
+	u32int	*regs;
+	u32int	*intregs;
+	u32int	intmask;
+
+	Desc	*d;
+
+	u32int	m;
+	u32int	cp;
+	u32int	rp;
+	u32int	wp;
+
+	int	num;
+};
+
+struct Ctlr
+{
+	Lock;
+	u32int	*regs;
+
+	Desc	rd[256];
+	Desc	td[256];
+
+	Ring	rx[1+0];
+	Ring	tx[1+0];
+
+	Rendez	avail[1];
+	Rendez	link[1];
+	struct {
+		Mii;
+		Rendez;
+	}	mii[1];
+
+	QLock;
+	char	attached;
+};
+
+static Block *scratch;
+
+/*
+this driver causes the ethernet controller to stall the gisb bus
+which causes other devices to fail.
+*/
+#ifdef XXXDEBUG
+
+static u32int *lastgenetregaddr;
+static uintptr lastgenetregpc;
+static ulong lastgenetregtime;
+static Ctlr *xxx;
+
+#define REG(x)	*(logreg(&(x)))
+
+static u32int*
+logreg(u32int *x)
+{
+	coherence();
+	lastgenetregtime = MACHP(0)->ticks;
+	lastgenetregpc = getcallerpc(&x);
+	lastgenetregaddr = x;
+	return x;
+}
+
+static void
+dumparb(void)
+{
+	static int once;
+	static u32int *regs = (u32int*)(VIRTIO + 0x9800);
+
+	if(!once){
+		once = 1;
+		regs[0x00/4] |= 1;
+		regs[0x40/4] = (regs[0x40/4] & ~0x1F) | 9 | 0x40000000;
+	}
+	iprint("arb %.8ux %.8ux %.8ux %.8ux; "
+		"%.8ux %.8ux %.8ux %.8ux; "
+		"%.8ux %.8ux %.8ux\n",
+		regs[0x40/4], regs[0x44/4], regs[0x48/4], regs[0x4C/4],
+		regs[0x50/4], regs[0x54/4], regs[0x58/4], regs[0x5C/4],
+		regs[0x60/4], regs[0x64/4], regs[0x68/4]);
+}
+
+void
+genetclock(void)
+{
+	static int ctr;
+
+	if(xxx == nil)
+		return;
+
+	if((++ctr & 0xFF) != 0)
+		return;
+	iprint("%d %#p @ %#p; "
+		"rx=(%.2ux %.2ux [%.2ux]); "
+		"tx=(%.2ux %.2ux %.2ux [%.2ux]); "
+		"(%lud)\n",
+		m->machno,
+		lastgenetregaddr, lastgenetregpc,
+		xxx->rx->rp, xxx->rx->wp, xxx->rx->wp - xxx->rx->rp,
+		xxx->tx->cp, xxx->tx->rp, xxx->tx->wp, xxx->tx->wp - xxx->tx->rp,
+		tk2ms(MACHP(0)->ticks-lastgenetregtime));
+	dumparb();
+}
+
+#else
+
+#define	REG(x)	(x)
+
+#endif
+
+static void
+interrupt0(Ureg*, void *arg)
+{
+	Ether *edev = arg;
+	Ctlr *ctlr = edev->ctlr;
+	u32int sts;
+
+	sts = REG(ctlr->regs[Intrl0 + IntrSts]) & ~REG(ctlr->regs[Intrl0 + IntrMaskSts]);
+	REG(ctlr->regs[Intrl0 + IntrClr]) = sts;
+	REG(ctlr->regs[Intrl0 + IntrMaskSet]) = sts;
+
+	if(sts & ctlr->rx->intmask)
+		wakeup(ctlr->rx);
+	if(sts & ctlr->tx->intmask)
+		wakeup(ctlr->tx);
+
+	if(sts & (IrqMdioDone|IrqMdioError))
+		wakeup(ctlr->mii);
+	if(sts & (IrqLinkUp|IrqLinkDown))
+		wakeup(ctlr->link);
+}
+
+static void
+interrupt1(Ureg*, void *arg)
+{
+	Ether *edev = arg;
+	Ctlr *ctlr = edev->ctlr;
+	u32int sts;
+	int i;
+
+	sts = REG(ctlr->regs[Intrl1 + IntrSts]) & ~REG(ctlr->regs[Intrl1 + IntrMaskSts]);
+	REG(ctlr->regs[Intrl1 + IntrClr]) = sts;
+	REG(ctlr->regs[Intrl1 + IntrMaskSet]) = sts;
+
+	for(i = 1; i < nelem(ctlr->rx); i++)
+		if(sts & ctlr->rx[i].intmask)
+			wakeup(&ctlr->rx[i]);
+
+	for(i = 1; i < nelem(ctlr->tx); i++)
+		if(sts & ctlr->tx[i].intmask)
+			wakeup(&ctlr->tx[i]);
+}
+
+static void
+setdma(Desc *d, void *v)
+{
+	u64int pa = PADDR(v);
+	REG(d->d[1]) = pa;
+	REG(d->d[2]) = pa >> 32;
+}
+
+static void
+replenish(Desc *d)
+{
+	d->b = allocb(Rbsz);
+	dmaflush(1, d->b->rp, Rbsz);
+	setdma(d, d->b->rp);
+}
+
+static int
+rxdone(void *arg)
+{
+	Ring *r = arg;
+
+	r->wp = REG(r->regs[RxWP]) & 0xFFFF;
+	if(r->rp != r->wp)
+		return 1;
+	REG(r->intregs[IntrMaskClr]) = r->intmask;
+	return 0;
+}
+
+static void
+recvproc(void *arg)
+{
+	Ether *edev = arg;
+	Ctlr *ctlr = edev->ctlr;
+	Desc *d;
+	Block *b;
+	u32int s;
+
+#ifdef XXXDEBUG
+	procwired(up, 1);
+	sched();
+#endif
+
+	while(waserror())
+		;
+
+	for(;;){
+		if(ctlr->rx->rp == ctlr->rx->wp){
+			sleep(ctlr->rx, rxdone, ctlr->rx);
+			continue;
+		}
+		d = &ctlr->rx->d[ctlr->rx->rp & ctlr->rx->m];
+		b = d->b;
+		dmaflush(0, b->rp, Rbsz);
+		s = REG(d->d[0]);
+		replenish(d);
+		coherence();
+		ctlr->rx->rp = (ctlr->rx->rp + 1) & 0xFFFF;
+		REG(ctlr->rx->regs[RxRP]) = ctlr->rx->rp;
+		if((s & (DmaSOP|DmaEOP|DmaRxErrors)) != (DmaSOP|DmaEOP)){
+			freeb(b);
+			continue;
+		}
+		b->wp += (s & 0x0FFF0000) >> 16;
+		etheriq(edev, b);
+	}
+}
+
+static int
+txavail(void *arg)
+{
+	Ring *r = arg;
+
+	return ((r->wp+1) & r->m) != (r->cp & r->m);
+}
+
+static void
+sendproc(void *arg)
+{
+	Ether *edev = arg;
+	Ctlr *ctlr = edev->ctlr;
+	Desc *d;
+	Block *b;
+
+#ifdef XXXDEBUG
+	procwired(up, 1);
+	sched();
+#endif
+
+	while(waserror())
+		;
+
+	for(;;){
+		if(!txavail(ctlr->tx)){
+			sleep(ctlr->avail, txavail, ctlr->tx);
+			continue;
+		}
+		if((b = qbread(edev->oq, 100000)) == nil)
+			break;
+		d = &ctlr->tx->d[ctlr->tx->wp & ctlr->tx->m];
+		assert(d->b == nil);
+		d->b = b;
+		dmaflush(1, b->rp, BLEN(b));
+		setdma(d, b->rp);
+		REG(d->d[0]) = BLEN(b)<<16 | DmaTxQtag | DmaSOP | DmaEOP | DmaTxAppendCrc;
+		coherence();
+		ctlr->tx->wp = (ctlr->tx->wp+1) & 0xFFFF;
+		REG(ctlr->tx->regs[TxWP]) = ctlr->tx->wp;
+	}
+}
+
+static int
+txdone(void *arg)
+{
+	Ring *r = arg;
+
+	if(r->cp != r->wp){
+		r->rp = REG(r->regs[TxRP]) & 0xFFFF;
+		if(r->cp != r->rp)
+			return 1;
+	}
+	REG(r->intregs[IntrMaskClr]) = r->intmask;
+	return 0;
+}
+
+static void
+freeproc(void *arg)
+{
+	Ether *edev = arg;
+	Ctlr *ctlr = edev->ctlr;
+	Desc *d;
+
+#ifdef XXXDEBUG
+	procwired(up, 1);
+	sched();
+#endif
+
+	while(waserror())
+		;
+
+	for(;;){
+		if(ctlr->tx->cp == ctlr->tx->rp){
+			wakeup(ctlr->avail);
+			sleep(ctlr->tx, txdone, ctlr->tx);
+			continue;
+		}
+		d = &ctlr->tx->d[ctlr->tx->cp & ctlr->tx->m];
+		assert(d->b != nil);
+		freeb(d->b);
+		d->b = nil;
+		coherence();
+		ctlr->tx->cp = (ctlr->tx->cp+1) & 0xFFFF;
+	}
+}
+
+static void
+initring(Ring *ring, Desc *desc, int start, int size)
+{
+	ring->d = &desc[start];
+	ring->m = size - 1;
+	ring->cp = ring->rp = ring->wp = 0;
+	REG(ring->regs[RxWP]) = 0;
+	REG(ring->regs[RxRP]) = 0;
+	REG(ring->regs[DmaStart]) = start*3;
+	REG(ring->regs[DmaEnd]) = (start+size)*3 - 1;
+	REG(ring->regs[RdmaWP]) = start*3;
+	REG(ring->regs[RdmaRP]) = start*3;
+	REG(ring->regs[DmaRingBufSize]) = (size << 16) | Rbsz;
+	REG(ring->regs[DmaDoneThresh]) = 1;
+}
+
+static void
+introff(Ctlr *ctlr)
+{
+	REG(ctlr->regs[Intrl0 + IntrMaskSet]) = -1;
+	REG(ctlr->regs[Intrl0 + IntrClr]) = -1;
+	REG(ctlr->regs[Intrl1 + IntrMaskSet]) = -1;
+	REG(ctlr->regs[Intrl1 + IntrClr]) = -1;
+}
+
+static void
+dmaoff(Ctlr *ctlr)
+{
+	REG(ctlr->rx->regs[DmaCtrl]) &= ~(RxRingCfgMask<<1 | DmaCtrlEn);
+	REG(ctlr->tx->regs[DmaCtrl]) &= ~(TxRingCfgMask<<1 | DmaCtrlEn);
+
+	REG(ctlr->regs[UmacTxFlush]) = 1;
+	microdelay(10);
+	REG(ctlr->regs[UmacTxFlush]) = 0;
+
+	while((REG(ctlr->rx->regs[DmaStatus]) & DmaStatusDis) == 0)
+		microdelay(10);
+	while((REG(ctlr->tx->regs[DmaStatus]) & DmaStatusDis) == 0)
+		microdelay(10);
+}
+
+static void
+dmaon(Ctlr *ctlr)
+{
+	REG(ctlr->rx->regs[DmaCtrl]) |= DmaCtrlEn;
+	REG(ctlr->tx->regs[DmaCtrl]) |= DmaCtrlEn;
+
+	while(REG(ctlr->rx->regs[DmaStatus]) & DmaStatusDis)
+		microdelay(10);
+	while(REG(ctlr->tx->regs[DmaStatus]) & DmaStatusDis)
+		microdelay(10);
+}
+
+static void
+allocbufs(Ctlr *ctlr)
+{
+	int i;
+
+	if(scratch == nil){
+		scratch = allocb(Rbsz);
+		memset(scratch->rp, 0xFF, Rbsz);
+		dmaflush(1, scratch->rp, Rbsz);
+	}
+
+	for(i = 0; i < nelem(ctlr->rd); i++){
+		ctlr->rd[i].d = &ctlr->regs[RdmaOffset + i*3];
+		replenish(&ctlr->rd[i]);
+	}
+
+	for(i = 0; i < nelem(ctlr->td); i++){
+		ctlr->td[i].d = &ctlr->regs[TdmaOffset + i*3];
+		setdma(&ctlr->td[i], scratch->rp);
+		REG(ctlr->td[i].d[0]) = DmaTxUnderrun;
+	}
+}
+
+static void
+freebufs(Ctlr *ctlr)
+{
+	int i;
+
+	for(i = 0; i < nelem(ctlr->rd); i++){
+		if(ctlr->rd[i].b != nil){
+			freeb(ctlr->rd[i].b);
+			ctlr->rd[i].b = nil;
+		}
+	}
+	for(i = 0; i < nelem(ctlr->td); i++){
+		if(ctlr->td[i].b != nil){
+			freeb(ctlr->td[i].b);
+			ctlr->td[i].b = nil;
+		}
+	}
+}
+
+static void
+initrings(Ctlr *ctlr)
+{
+	u32int rcfg, tcfg, dmapri[3];
+	int i;
+
+	ctlr->rx->intregs = &ctlr->regs[Intrl0];
+	ctlr->rx->intmask = IrqRxDmaDone;
+	ctlr->rx->num = 16;
+	rcfg = 1<<16;
+	for(i = 1; i < nelem(ctlr->rx); i++){
+		ctlr->rx[i].regs = &ctlr->regs[RdmaOffset + nelem(ctlr->rd)*3 + (i-1)*RingCfg];
+		ctlr->rx[i].intregs = &ctlr->regs[Intrl1];
+		ctlr->rx[i].intmask = 0x10000 << (i - 1);
+		ctlr->rx[i].num = i - 1;
+		rcfg |= 1<<(i-1);
+	}
+	assert(rcfg && (rcfg & ~RxRingCfgMask) == 0);
+
+	ctlr->tx->intregs = &ctlr->regs[Intrl0];
+	ctlr->tx->intmask = IrqTxDmaDone;
+	ctlr->tx->num = 16;
+	tcfg = 1<<16;
+	for(i = 1; i < nelem(ctlr->tx); i++){
+		ctlr->tx[i].regs = &ctlr->regs[TdmaOffset + nelem(ctlr->td)*3 + (i-1)*RingCfg];
+		ctlr->tx[i].intregs = &ctlr->regs[Intrl1];
+		ctlr->tx[i].intmask = 1 << (i - 1);
+		ctlr->tx[i].num = i - 1;
+		tcfg |= 1<<(i-1);
+	}
+	assert(tcfg && (tcfg & ~TxRingCfgMask) == 0);
+
+	REG(ctlr->rx->regs[DmaScbBurstSize]) = 0x08;
+	for(i = 1; i < nelem(ctlr->rx); i++)
+		initring(&ctlr->rx[i], ctlr->rd, (i-1)*32, 32);
+	initring(ctlr->rx, ctlr->rd, (i-1)*32, nelem(ctlr->rd) - (i-1)*32);
+
+	for(i = 0; i < nelem(ctlr->rx); i++){		 
+		REG(ctlr->rx[i].regs[DmaDoneThresh]) = 1;
+		REG(ctlr->rx[i].regs[RdmaXonXoffThresh]) = (5 << 16) | ((ctlr->rx[i].m+1) >> 4);
+
+		// set dma timeout to 50µs
+		REG(ctlr->rx->regs[RdmaTimeout0 + ctlr->rx[i].num]) = ((50*1000 + 8191)/8192);
+	}
+
+	REG(ctlr->tx->regs[DmaScbBurstSize]) = 0x08;
+	for(i = 1; i < nelem(ctlr->tx); i++)
+		initring(&ctlr->tx[i], ctlr->td, (i-1)*32, 32);
+	initring(ctlr->tx, ctlr->td, (i-1)*32, nelem(ctlr->td) - (i-1)*32);
+
+	dmapri[0] = dmapri[1] = dmapri[2] = 0;
+	for(i = 0; i < nelem(ctlr->tx); i++){
+		REG(ctlr->tx[i].regs[DmaDoneThresh]) = 10;
+		REG(ctlr->tx[i].regs[TdmaFlowPeriod]) = i ? 0 : Maxtu << 16;
+		dmapri[ctlr->tx[i].num/6] |= i << ((ctlr->tx[i].num%6)*5);
+	}
+
+	REG(ctlr->tx->regs[TdmaArbCtrl]) = 2;
+	REG(ctlr->tx->regs[TdmaPriority0]) = dmapri[0];
+	REG(ctlr->tx->regs[TdmaPriority1]) = dmapri[1];
+	REG(ctlr->tx->regs[TdmaPriority2]) = dmapri[2];
+
+	REG(ctlr->rx->regs[RingCfg]) = rcfg;
+	REG(ctlr->tx->regs[RingCfg]) = tcfg;
+
+	REG(ctlr->rx->regs[DmaCtrl]) |= rcfg<<1;
+	REG(ctlr->tx->regs[DmaCtrl]) |= tcfg<<1;
+}
+
+static void
+umaccmd(Ctlr *ctlr, u32int set, u32int clr)
+{
+	ilock(ctlr);
+	REG(ctlr->regs[UmacCmd]) = (REG(ctlr->regs[UmacCmd]) & ~clr) | set;
+	iunlock(ctlr);
+}
+
+static void
+reset(Ctlr *ctlr)
+{
+	u32int r;
+
+	// reset umac
+	r = REG(ctlr->regs[SysRbufFlushCtrl]);
+	REG(ctlr->regs[SysRbufFlushCtrl]) = r | 2;
+	microdelay(10);
+	REG(ctlr->regs[SysRbufFlushCtrl]) = r & ~2;
+	microdelay(10);
+
+	// umac reset
+	REG(ctlr->regs[SysRbufFlushCtrl]) = 0;
+	microdelay(10);
+
+	REG(ctlr->regs[UmacCmd]) = 0;
+	REG(ctlr->regs[UmacCmd]) = CmdSwReset | CmdLclLoopEn;
+	microdelay(2);
+	REG(ctlr->regs[UmacCmd]) = 0;
+}
+
+static void
+setmac(Ctlr *ctlr, uchar *ea)
+{
+	REG(ctlr->regs[UmacMac0]) = ea[0]<<24 | ea[1]<<16 | ea[2]<<8 | ea[3];
+	REG(ctlr->regs[UmacMac1]) = ea[4]<<8 | ea[5];
+}
+
+static void
+sethfb(Ctlr *ctlr)
+{
+	int i;
+
+	REG(ctlr->regs[HfbCtlr]) = 0;
+	REG(ctlr->regs[HfbFltEnable]) = 0;
+	REG(ctlr->regs[HfbFltEnable+1]) = 0;
+
+	for(i = 0; i < 8; i++)
+		REG(ctlr->rx->regs[RdmaIndex2Ring0+i]) = 0;
+
+	for(i = 0; i < 48/4; i++)
+		REG(ctlr->regs[HfbFltLen + i]) = 0;
+
+	for(i = 0; i < 48*128; i++)
+		REG(ctlr->regs[HfbOffset + i]) = 0;
+}
+
+static int
+mdiodone(void *arg)
+{
+	Ctlr *ctlr = arg;
+	REG(ctlr->regs[Intrl0 + IntrMaskClr]) = (IrqMdioDone|IrqMdioError);
+	return (REG(ctlr->regs[MdioCmd]) & MdioStartBusy) == 0;
+}
+
+static int
+mdiowait(Ctlr *ctlr)
+{
+	REG(ctlr->regs[MdioCmd]) |= MdioStartBusy;
+	while(REG(ctlr->regs[MdioCmd]) & MdioStartBusy)
+		tsleep(ctlr->mii, mdiodone, ctlr, 10);
+	return 0;
+}
+
+static int
+mdiow(Mii* mii, int phy, int addr, int data)
+{
+	Ctlr *ctlr = mii->ctlr;
+
+	if(phy > MdioPhyMask)
+		return -1;
+	addr &= MdioAddrMask;
+	REG(ctlr->regs[MdioCmd]) = MdioWrite
+		| (phy << MdioPhyShift) | (addr << MdioAddrShift) | (data & 0xFFFF);
+	return mdiowait(ctlr);
+}
+
+static int
+mdior(Mii* mii, int phy, int addr)
+{
+	Ctlr *ctlr = mii->ctlr;
+
+	if(phy > MdioPhyMask)
+		return -1;
+	addr &= MdioAddrMask;
+	REG(ctlr->regs[MdioCmd]) = MdioRead
+		| (phy << MdioPhyShift) | (addr << MdioAddrShift);
+	if(mdiowait(ctlr) < 0)
+		return -1;
+	if(REG(ctlr->regs[MdioCmd]) & MdioReadFail)
+		return -1;
+	return REG(ctlr->regs[MdioCmd]) & 0xFFFF;
+}
+
+static int
+bcmshdr(Mii *mii, int reg)
+{
+	miimiw(mii, 0x1C, (reg & 0x1F) << 10);
+	return miimir(mii, 0x1C) & 0x3FF;
+}
+
+static int
+bcmshdw(Mii *mii, int reg, int dat)
+{
+	return miimiw(mii, 0x1C, 0x8000 | (reg & 0x1F) << 10 | (dat & 0x3FF));
+}
+
+static int
+linkevent(void *arg)
+{
+	Ctlr *ctlr = arg;
+	REG(ctlr->regs[Intrl0 + IntrMaskClr]) = IrqLinkUp|IrqLinkDown;
+	return 0;
+}
+
+static void
+linkproc(void *arg)
+{
+	Ether *edev = arg;
+	Ctlr *ctlr = edev->ctlr;
+	MiiPhy *phy;
+	int link = -1;
+
+#ifdef XXXDEBUG
+	procwired(up, 1);
+	sched();
+#endif
+
+	while(waserror())
+		;
+
+	for(;;){
+		tsleep(ctlr->link, linkevent, ctlr, 1000);
+		miistatus(ctlr->mii);
+		phy = ctlr->mii->curphy;
+		if(phy == nil || phy->link == link)
+			continue;
+		link = phy->link;
+		if(link){
+			u32int cmd = CmdRxEn|CmdTxEn;
+			switch(phy->speed){
+			case 1000:	cmd |= CmdSpeed1000; break;
+			case 100:	cmd |= CmdSpeed100; break;
+			case 10:	cmd |= CmdSpeed10; break;
+			}
+			if(!phy->fd)
+				cmd |= CmdHdEn;
+			if(!phy->rfc)
+				cmd |= CmdRxPauseIgn;
+			if(!phy->tfc)
+				cmd |= CmdTxPauseIgn;
+
+			REG(ctlr->regs[ExtRgmiiOobCtrl]) = (REG(ctlr->regs[ExtRgmiiOobCtrl]) & ~OobDisable) | RgmiiLink;
+			umaccmd(ctlr, cmd, CmdSpeedMask|CmdHdEn|CmdRxPauseIgn|CmdTxPauseIgn);
+
+			edev->mbps = phy->speed;
+		}
+		edev->link = link;
+		// print("#l%d: link %d speed %d\n", edev->ctlrno, edev->link, edev->mbps);
+	}
+}
+
+static void
+setmdfaddr(Ctlr *ctlr, int i, uchar *ea)
+{
+	REG(ctlr->regs[UmacMdfAddr0 + i*2 + 0]) = ea[0] << 8  | ea[1];
+	REG(ctlr->regs[UmacMdfAddr0 + i*2 + 1]) = ea[2] << 24 | ea[3] << 16 | ea[4] << 8 | ea[5];
+}
+
+static void
+rxmode(Ether *edev, int prom)
+{
+	Ctlr *ctlr = edev->ctlr;
+	Netaddr *na;
+	int i;
+
+	if(prom || edev->nmaddr > 16-2){
+		REG(ctlr->regs[UmacMdfCtrl]) = 0;
+		umaccmd(ctlr, CmdProm, 0);
+		return;
+	}
+	setmdfaddr(ctlr, 0, edev->bcast);
+	setmdfaddr(ctlr, 1, edev->ea);
+	for(i = 2, na = edev->maddr; na != nil; na = na->next, i++)
+		setmdfaddr(ctlr, i, na->addr);
+	REG(ctlr->regs[UmacMdfCtrl]) = (-0x10000 >> i) & 0x1FFFF;
+	umaccmd(ctlr, 0, CmdProm);
+}
+
+static void
+shutdown(Ether *edev)
+{
+	Ctlr *ctlr = edev->ctlr;
+
+	dmaoff(ctlr);
+	introff(ctlr);
+}
+
+static void
+attach(Ether *edev)
+{
+	Ctlr *ctlr = edev->ctlr;
+
+	eqlock(ctlr);
+	if(ctlr->attached){
+		qunlock(ctlr);
+		return;
+	}
+	if(waserror()){
+		print("#l%d: %s\n", edev->ctlrno, up->errstr);
+		shutdown(edev);
+		freebufs(ctlr);
+		qunlock(ctlr);
+		nexterror();
+	}
+
+	// statistics
+	REG(ctlr->regs[UmacMibCtrl]) = MibResetRx | MibResetTx | MibResetRunt;
+	REG(ctlr->regs[UmacMibCtrl]) = 0;
+
+	// wol
+	REG(ctlr->regs[UmacMpdCtrl]) &= ~(MpdPwEn|MpdEn);
+
+	// power
+	REG(ctlr->regs[UmacEeeCtrl]) &= ~UmacEeeEn;
+	REG(ctlr->regs[RbufEnergyCtrl]) &= ~(RbufEeeEn|RbufPmEn);
+	REG(ctlr->regs[TbufEnergyCtrl]) &= ~(RbufEeeEn|RbufPmEn);
+	REG(ctlr->regs[TbufBpMc]) = 0;
+
+	REG(ctlr->regs[UmacMaxFrameLen]) = Maxtu;
+
+	REG(ctlr->regs[RbufTbufSizeCtrl]) = 1;
+
+	REG(ctlr->regs[TbufCtrl]) &= ~(Rbuf64En);
+	REG(ctlr->regs[RbufCtrl]) &= ~(Rbuf64En|RbufAlign2B);
+	REG(ctlr->regs[RbufChkCtrl]) &= ~(RbufChkRxChkEn|RbufChkSkipFcs);
+
+	allocbufs(ctlr);
+	initrings(ctlr);
+	dmaon(ctlr);
+
+	setmac(ctlr, edev->ea);
+	sethfb(ctlr);
+	rxmode(edev, 0);
+
+	REG(ctlr->regs[SysPortCtrl]) = PortModeExtGphy;
+	REG(ctlr->regs[ExtRgmiiOobCtrl]) |= RgmiiModeEn | IdModeDis;
+
+	ctlr->mii->ctlr = ctlr;
+	ctlr->mii->mir = mdior;
+	ctlr->mii->miw = mdiow;
+	mii(ctlr->mii, ~0);
+
+	if(ctlr->mii->curphy == nil)
+		error("no phy");
+
+	print("#l%d: phy%d id %.8ux oui %x\n", 
+		edev->ctlrno, ctlr->mii->curphy->phyno, 
+		ctlr->mii->curphy->id, ctlr->mii->curphy->oui);
+
+	miireset(ctlr->mii);
+
+	switch(ctlr->mii->curphy->id){
+	case 0x600d84a2:	/* BCM54312PE */
+		/* mask interrupts */
+		miimiw(ctlr->mii, 0x10, miimir(ctlr->mii, 0x10) | 0x1000);
+
+		/* SCR3: clear DLLAPD_DIS */
+		bcmshdw(ctlr->mii, 0x05, bcmshdr(ctlr->mii, 0x05) &~0x0002);
+		/* APD: set APD_EN */
+		bcmshdw(ctlr->mii, 0x0a, bcmshdr(ctlr->mii, 0x0a) | 0x0020);
+
+		/* blinkenlights */
+		bcmshdw(ctlr->mii, 0x09, bcmshdr(ctlr->mii, 0x09) | 0x0010);
+		bcmshdw(ctlr->mii, 0x0d, 3<<0 | 0<<4);
+		break;
+	}
+
+	/* don't advertise EEE */
+	miimmdw(ctlr->mii, 7, 60, 0);
+
+	miiane(ctlr->mii, ~0, ~0, ~0);
+
+#ifdef XXXDEBUG
+	xxx = ctlr;
+#endif
+
+	ctlr->attached = 1;
+
+	kproc("genet-recv", recvproc, edev);
+	kproc("genet-send", sendproc, edev);
+	kproc("genet-free", freeproc, edev);
+	kproc("genet-link", linkproc, edev);
+
+	qunlock(ctlr);
+	poperror();
+}
+
+static void
+prom(void *arg, int on)
+{
+	Ether *edev = arg;
+	rxmode(edev, on);
+}
+
+static void
+multi(void *arg, uchar*, int)
+{
+	Ether *edev = arg;
+	rxmode(edev, edev->prom > 0);
+}
+
+static long
+ctl(Ether *edev, void *data, long len)
+{
+	Ctlr *ctlr = edev->ctlr;
+	char *s = data;
+
+	if(len >= 4 && strncmp(s, "tron", 4) == 0){
+		umaccmd(ctlr, CmdTxEn, 0);
+	} else if(len >= 5 && strncmp(s, "troff", 5) == 0){
+		umaccmd(ctlr, 0, CmdTxEn);
+	} else if(len >= 3 && strncmp(s, "ron", 3) == 0){
+		umaccmd(ctlr, CmdRxEn, 0);
+	} else if(len >= 4 && strncmp(s, "roff", 4) == 0){
+		umaccmd(ctlr, 0, CmdRxEn);
+	}
+
+	return len;
+}
+
+static int
+pnp(Ether *edev)
+{
+	static Ctlr ctlr[1];
+
+	if(ctlr->regs != nil)
+		return -1;
+
+	ctlr->regs = (u32int*)(VIRTIO1 + 0x580000);
+	ctlr->rx->regs = &ctlr->regs[RdmaOffset + nelem(ctlr->rd)*3 + 16*RingCfg];
+	ctlr->tx->regs = &ctlr->regs[TdmaOffset + nelem(ctlr->td)*3 + 16*RingCfg];
+
+	edev->port = (uintptr)ctlr->regs;
+	edev->irq = IRQether;
+	edev->ctlr = ctlr;
+	edev->attach = attach;
+	edev->shutdown = shutdown;
+	edev->promiscuous = prom;
+	edev->multicast = multi;
+	edev->ctl = ctl;
+	edev->arg = edev;
+	edev->mbps = 1000;
+	edev->maxmtu = Maxtu;
+
+	parseether(edev->ea, getethermac());
+
+	reset(ctlr);
+	dmaoff(ctlr);
+	introff(ctlr);
+
+	intrenable(edev->irq+0, interrupt0, edev, BUSUNKNOWN, edev->name);
+	intrenable(edev->irq+1, interrupt1, edev, BUSUNKNOWN, edev->name);
+
+	return 0;
+}
+
+void
+ethergenetlink(void)
+{
+	addethercard("genet", pnp);
+}
--- a/sys/src/9/port/ethermii.c
+++ b/sys/src/9/port/ethermii.c
@@ -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;
 
@@ -232,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/sys/src/9/port/ethermii.h
+++ b/sys/src/9/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);