shithub: riscv

ref: ad26b06c8f2bd574d39d74a3fc47cfadbd0bfca0
dir: /sys/src/9/ppc/etherfcc.c/

View raw version
/*
 * FCCn ethernet
 */

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "imm.h"
#include "../port/error.h"
#include "../port/netif.h"
#include "../port/etherif.h"

#include "../ppc/ethermii.h"

#define DBG 1

enum {
	Nrdre		= 128,			/* receive descriptor ring entries */
	Ntdre		= 128,			/* transmit descriptor ring entries */

	Rbsize		= ETHERMAXTU+4,		/* ring buffer size (+4 for CRC) */
	Bufsize		= Rbsize+CACHELINESZ,	/* extra room for alignment */
};

enum {

	/* ether-specific Rx BD bits */
	RxMiss=		SBIT(7),
	RxeLG=		SBIT(10),
	RxeNO=		SBIT(11),
	RxeSH=		SBIT(12),
	RxeCR=		SBIT(13),
	RxeOV=		SBIT(14),
	RxeCL=		SBIT(15),
	RxError=	(RxeLG|RxeNO|RxeSH|RxeCR|RxeOV|RxeCL),	/* various error flags */

	/* ether-specific Tx BD bits */
	TxPad=		SBIT(1),	/* pad short frames */
	TxTC=		SBIT(5),	/* transmit CRC */
	TxeDEF=		SBIT(6),
	TxeHB=		SBIT(7),
	TxeLC=		SBIT(8),
	TxeRL=		SBIT(9),
	TxeUN=		SBIT(14),
	TxeCSL=		SBIT(15),

	/* psmr */
	CRCE=		BIT(24),	/* Ethernet CRC */
	FCE=		BIT(10),	/* flow control */
	PRO=		BIT(9),		/* promiscuous mode */
	FDE=		BIT(5),		/* full duplex ethernet */
	LPB=		BIT(3),		/* local protect bit */

	/* gfmr */
	ENET=		0xc,		/* ethernet mode */
	ENT=		BIT(27),
	ENR=		BIT(26),
	TCI=		BIT(2),

	/* FCC function code register */
	GBL=		0x20,
	BO=		0x18,
	EB=		0x10,		/* Motorola byte order */
	TC2=		0x04,
	DTB=		0x02,
	BDB=		0x01,

	/* FCC Event/Mask bits */
	GRA=		SBIT(8),
	RXC=		SBIT(9),
	TXC=		SBIT(10),
	TXE=		SBIT(11),
	RXF=		SBIT(12),
	BSY=		SBIT(13),
	TXB=		SBIT(14),
	RXB=		SBIT(15),
};

enum {		/* Mcr */
	MDIread	=	0x60020000,	/* read opcode */
	MDIwrite =	0x50020000,	/* write opcode */
};

typedef struct Etherparam Etherparam;
struct Etherparam {
/*0x00*/	FCCparam;
/*0x3c*/	ulong	stat_buf;
/*0x40*/	ulong	cam_ptr;
/*0x44*/	ulong	cmask;
/*0x48*/	ulong	cpres;
/*0x4c*/	ulong	crcec;
/*0x50*/	ulong	alec;
/*0x54*/	ulong	disfc;
/*0x58*/	ushort	retlim;
/*0x5a*/	ushort	retcnt;
/*0x5c*/	ushort	p_per;
/*0x5e*/	ushort	boff_cnt;
/*0x60*/	ulong	gaddr[2];
/*0x68*/	ushort	tfcstat;
/*0x6a*/	ushort	tfclen;
/*0x6c*/	ulong	tfcptr;
/*0x70*/	ushort	mflr;
/*0x72*/	ushort	paddr[3];
/*0x78*/	ushort	ibd_cnt;
/*0x7a*/	ushort	ibd_start;
/*0x7c*/	ushort	ibd_end;
/*0x7e*/	ushort	tx_len;
/*0x80*/	uchar	ibd_base[32];
/*0xa0*/	ulong	iaddr[2];
/*0xa8*/	ushort	minflr;
/*0xaa*/	ushort	taddr[3];
/*0xb0*/	ushort	padptr;
/*0xb2*/	ushort	Rsvdb2;
/*0xb4*/	ushort	cf_range;
/*0xb6*/	ushort	max_b;
/*0xb8*/	ushort	maxd1;
/*0xba*/	ushort	maxd2;
/*0xbc*/	ushort	maxd;
/*0xbe*/	ushort	dma_cnt;
/*0xc0*/	ulong	octc;
/*0xc4*/	ulong	colc;
/*0xc8*/	ulong	broc;
/*0xcc*/	ulong	mulc;
/*0xd0*/	ulong	uspc;
/*0xd4*/	ulong	frgc;
/*0xd8*/	ulong	ospc;
/*0xdc*/	ulong	jbrc;
/*0xe0*/	ulong	p64c;
/*0xe4*/	ulong	p65c;
/*0xe8*/	ulong	p128c;
/*0xec*/	ulong	p256c;
/*0xf0*/	ulong	p512c;
/*0xf4*/	ulong	p1024c;
/*0xf8*/	ulong	cam_buf;
/*0xfc*/	ulong	Rsvdfc;
/*0x100*/
};

typedef struct Ctlr Ctlr;
struct Ctlr {
	Lock;
	int	fccid;
	int	port;
	ulong	pmdio;
	ulong	pmdck;
	int	init;
	int	active;
	int	duplex;		/* 1 == full */
	FCC*	fcc;

	Ring;
	Block*	rcvbufs[Nrdre];
	Mii*	mii;
	Timer;

	ulong	interrupts;	/* statistics */
	ulong	deferred;
	ulong	heartbeat;
	ulong	latecoll;
	ulong	retrylim;
	ulong	underrun;
	ulong	overrun;
	ulong	carrierlost;
	ulong	retrycount;
};

static	int	fccirq[] = {0x20, 0x21, 0x22};
static	int	fccid[] = {FCC1ID, FCC2ID, FCC3ID};

#ifdef DBG
ulong fccrhisto[16];
ulong fccthisto[16];
ulong fccrthisto[16];
ulong fcctrhisto[16];
ulong ehisto[0x80];
#endif

static int fccmiimir(Mii*, int, int);
static int fccmiimiw(Mii*, int, int, int);
static void fccltimer(Ureg*, Timer*);

static void
attach(Ether *ether)
{
	Ctlr *ctlr;

	ctlr = ether->ctlr;
	ilock(ctlr);
	ctlr->active = 1;
	ctlr->fcc->gfmr |= ENR|ENT;
	iunlock(ctlr);
	ctlr->tmode = Tperiodic;
	ctlr->tf = fccltimer;
	ctlr->ta = ether;
	ctlr->tns = 5000000000LL;	/* 5 seconds */
	timeradd(ctlr);
}

static void
closed(Ether *ether)
{
	Ctlr *ctlr;

	ctlr = ether->ctlr;
	ilock(ctlr);
	ctlr->active = 0;
	ctlr->fcc->gfmr &= ~(ENR|ENT);
	iunlock(ctlr);
	print("Ether closed\n");
}

static void
promiscuous(void* arg, int on)
{
	Ether *ether;
	Ctlr *ctlr;

	ether = (Ether*)arg;
	ctlr = ether->ctlr;

	ilock(ctlr);
	if(on || ether->nmaddr)
		ctlr->fcc->fpsmr |= PRO;
	else
		ctlr->fcc->fpsmr &= ~PRO;
	iunlock(ctlr);
}

static void
multicast(void* arg, uchar *addr, int on)
{
	Ether *ether;
	Ctlr *ctlr;

	USED(addr, on);	/* if on, could SetGroupAddress; if !on, it's hard */

	ether = (Ether*)arg;
	ctlr = ether->ctlr;

	ilock(ctlr);
	if(ether->prom || ether->nmaddr)
		ctlr->fcc->fpsmr |= PRO;
	else
		ctlr->fcc->fpsmr &= ~PRO;
	iunlock(ctlr);
}

static void
txstart(Ether *ether)
{
	int len;
	Ctlr *ctlr;
	Block *b;
	BD *dre;

	ctlr = ether->ctlr;
	if(ctlr->init)
		return;
	while(ctlr->ntq < Ntdre-1){
		b = qget(ether->oq);
		if(b == 0)
			break;

		dre = &ctlr->tdr[ctlr->tdrh];
		dczap(dre, sizeof(BD));
		if(dre->status & BDReady)
			panic("ether: txstart");

		/*
		 * Give ownership of the descriptor to the chip, increment the
		 * software ring descriptor pointer and tell the chip to poll.
		 */
		len = BLEN(b);
		if(ctlr->txb[ctlr->tdrh] != nil)
			panic("fcc/ether: txstart");
		ctlr->txb[ctlr->tdrh] = b;
		if((ulong)b->rp&1)
			panic("fcc/ether: txstart align");	/* TO DO: ensure alignment */
		dre->addr = PADDR(b->rp);
		dre->length = len;
		dcflush(b->rp, len);
		dcflush(dre, sizeof(BD));
		dre->status = (dre->status & BDWrap) | BDReady|TxPad|BDInt|BDLast|TxTC;
		dcflush(dre, sizeof(BD));
/*		ctlr->fcc->ftodr = 1<<15;	/* transmit now; Don't do this according to errata */
		ctlr->ntq++;
		ctlr->tdrh = NEXT(ctlr->tdrh, Ntdre);
	}
}

static void
transmit(Ether* ether)
{
	Ctlr *ctlr;

	ctlr = ether->ctlr;
	ilock(ctlr);
	txstart(ether);
	iunlock(ctlr);
}

static void
interrupt(Ureg*, void *arg)
{
	int len, status, rcvd, xmtd, restart;
	ushort events;
	Ctlr *ctlr;
	BD *dre;
	Block *b, *nb;
	Ether *ether = arg;

	ctlr = ether->ctlr;
	if(!ctlr->active)
		return;	/* not ours */

	/*
	 * Acknowledge all interrupts and whine about those that shouldn't
	 * happen.
	 */
	events = ctlr->fcc->fcce;
	ctlr->fcc->fcce = events;		/* clear events */

#ifdef DBG
	ehisto[events & 0x7f]++;
#endif

	ctlr->interrupts++;

	if(events & BSY)
		ctlr->overrun++;
	if(events & TXE)
		ether->oerrs++;

#ifdef DBG
	rcvd = xmtd = 0;
#endif
	/*
	 * Receiver interrupt: run round the descriptor ring logging
	 * errors and passing valid receive data up to the higher levels
	 * until we encounter a descriptor still owned by the chip.
	 */
	if(events & RXF){
		dre = &ctlr->rdr[ctlr->rdrx];
		dczap(dre, sizeof(BD));
		while(((status = dre->status) & BDEmpty) == 0){
			rcvd++;
			if(status & RxError || (status & (BDFirst|BDLast)) != (BDFirst|BDLast)){
				if(status & (RxeLG|RxeSH))
					ether->buffs++;
				if(status & RxeNO)
					ether->frames++;
				if(status & RxeCR)
					ether->crcs++;
				if(status & RxeOV)
					ether->overflows++;
				print("eth rx: %ux\n", status);
			}else{
				/*
				 * We have a packet. Read it in.
				 */
				len = dre->length-4;
				b = ctlr->rcvbufs[ctlr->rdrx];
				assert(dre->addr == PADDR(b->rp));
				dczap(b->rp, len);
				if(nb = iallocb(Bufsize)){
					b->wp += len;
					etheriq(ether, b);
					b = nb;
					b->rp = (uchar*)(((ulong)b->rp + CACHELINESZ-1) & ~(CACHELINESZ-1));
					b->wp = b->rp;
					ctlr->rcvbufs[ctlr->rdrx] = b;
					ctlr->rdr[ctlr->rdrx].addr = PADDR(b->wp);
				}else
					ether->soverflows++;
			}

			/*
			 * Finished with this descriptor, reinitialise it,
			 * give it back to the chip, then on to the next...
			 */
			dre->length = 0;
			dre->status = (status & BDWrap) | BDEmpty | BDInt;
			dcflush(dre, sizeof(BD));

			ctlr->rdrx = NEXT(ctlr->rdrx, Nrdre);
			dre = &ctlr->rdr[ctlr->rdrx];
			dczap(dre, sizeof(BD));
		}
	}

	/*
	 * Transmitter interrupt: handle anything queued for a free descriptor.
	 */
	if(events & (TXB|TXE)){
		ilock(ctlr);
		restart = 0;
		while(ctlr->ntq){
			dre = &ctlr->tdr[ctlr->tdri];
			dczap(dre, sizeof(BD));
			status = dre->status;
			if(status & BDReady)
				break;
			if(status & TxeDEF)
				ctlr->deferred++;
			if(status & TxeHB)
				ctlr->heartbeat++;
			if(status & TxeLC)
				ctlr->latecoll++;
			if(status & TxeRL)
				ctlr->retrylim++;
			if(status & TxeUN)
				ctlr->underrun++;
			if(status & TxeCSL)
				ctlr->carrierlost++;
			if(status & (TxeLC|TxeRL|TxeUN))
				restart = 1;
			ctlr->retrycount += (status>>2)&0xF;
			b = ctlr->txb[ctlr->tdri];
			if(b == nil)
				panic("fcce/interrupt: bufp");
			ctlr->txb[ctlr->tdri] = nil;
			freeb(b);
			ctlr->ntq--;
			ctlr->tdri = NEXT(ctlr->tdri, Ntdre);
			xmtd++;
		}

		if(restart){
			ctlr->fcc->gfmr &= ~ENT;
			delay(10);
			ctlr->fcc->gfmr |= ENT;
			cpmop(RestartTx, ctlr->fccid, 0xc);
		}
		txstart(ether);
		iunlock(ctlr);
	}
#ifdef DBG
	if(rcvd >= nelem(fccrhisto))
		rcvd = nelem(fccrhisto) - 1;
	if(xmtd >= nelem(fccthisto))
		xmtd = nelem(fccthisto) - 1;
	if(rcvd)
		fcctrhisto[xmtd]++;
	else
		fccthisto[xmtd]++;
	if(xmtd)
		fccrthisto[rcvd]++;
	else
		fccrhisto[rcvd]++;
#endif
}

static long
ifstat(Ether* ether, void* a, long n, ulong offset)
{
	char *p;
	int len, i, r;
	Ctlr *ctlr;
	MiiPhy *phy;

	if(n == 0)
		return 0;

	ctlr = ether->ctlr;

	p = malloc(READSTR);
	len = snprint(p, READSTR, "interrupts: %lud\n", ctlr->interrupts);
	len += snprint(p+len, READSTR-len, "carrierlost: %lud\n", ctlr->carrierlost);
	len += snprint(p+len, READSTR-len, "heartbeat: %lud\n", ctlr->heartbeat);
	len += snprint(p+len, READSTR-len, "retrylimit: %lud\n", ctlr->retrylim);
	len += snprint(p+len, READSTR-len, "retrycount: %lud\n", ctlr->retrycount);
	len += snprint(p+len, READSTR-len, "latecollisions: %lud\n", ctlr->latecoll);
	len += snprint(p+len, READSTR-len, "rxoverruns: %lud\n", ctlr->overrun);
	len += snprint(p+len, READSTR-len, "txunderruns: %lud\n", ctlr->underrun);
	len += snprint(p+len, READSTR-len, "framesdeferred: %lud\n", ctlr->deferred);
	miistatus(ctlr->mii);
	phy = ctlr->mii->curphy;
	len += snprint(p+len, READSTR-len, "phy: link=%d, tfc=%d, rfc=%d, speed=%d, fd=%d\n",
		phy->link, phy->tfc, phy->rfc, phy->speed, phy->fd);

#ifdef DBG
	if(ctlr->mii != nil && ctlr->mii->curphy != nil){
		len += snprint(p+len, READSTR, "phy:   ");
		for(i = 0; i < NMiiPhyr; i++){
			if(i && ((i & 0x07) == 0))
				len += snprint(p+len, READSTR-len, "\n       ");
			r = miimir(ctlr->mii, i);
			len += snprint(p+len, READSTR-len, " %4.4uX", r);
		}
		snprint(p+len, READSTR-len, "\n");
	}
#endif
	snprint(p+len, READSTR-len, "\n");

	n = readstr(offset, a, n, p);
	free(p);

	return n;
}

/*
 * This follows the MPC8260 user guide: section28.9's initialisation sequence.
 */
static int
fccsetup(Ctlr *ctlr, FCC *fcc, uchar *ea)
{
	int i;
	Etherparam *p;
	MiiPhy *phy;

	/* Turn Ethernet off */
	fcc->gfmr &= ~(ENR | ENT);

	ioplock();
	switch(ctlr->port) {
	default:
		iopunlock();
		return -1;
	case 0:
		/* Step 1 (Section 28.9), write the parallel ports */
		ctlr->pmdio = 0x01000000;
		ctlr->pmdck = 0x08000000;
		imm->port[0].pdir &= ~A1dir0;
		imm->port[0].pdir |= A1dir1;
		imm->port[0].psor &= ~A1psor0;
		imm->port[0].psor |= A1psor1;
		imm->port[0].ppar |= (A1dir0 | A1dir1);
		/* Step 2, Port C clocks */
		imm->port[2].psor &= ~0x00000c00;
		imm->port[2].pdir &= ~0x00000c00;
		imm->port[2].ppar |= 0x00000c00;
		imm->port[3].pdat |= (ctlr->pmdio | ctlr->pmdck);
		imm->port[3].podr |= ctlr->pmdio;
		imm->port[3].pdir |= (ctlr->pmdio | ctlr->pmdck);
		imm->port[3].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
		eieio();
		/* Step 3, Serial Interface clock routing */
		imm->cmxfcr &= ~0xff000000;	/* Clock mask */
		imm->cmxfcr |= 0x37000000;	/* Clock route */
		break;

	case 1:
		/* Step 1 (Section 28.9), write the parallel ports */
		ctlr->pmdio = 0x00400000;
		ctlr->pmdck = 0x00200000;
		imm->port[1].pdir &= ~B2dir0;
		imm->port[1].pdir |= B2dir1;
		imm->port[1].psor &= ~B2psor0;
		imm->port[1].psor |= B2psor1;
		imm->port[1].ppar |= (B2dir0 | B2dir1);
		/* Step 2, Port C clocks */
		imm->port[2].psor &= ~0x00003000;
		imm->port[2].pdir &= ~0x00003000;
		imm->port[2].ppar |= 0x00003000;

		imm->port[2].pdat |= (ctlr->pmdio | ctlr->pmdck);
		imm->port[2].podr |= ctlr->pmdio;
		imm->port[2].pdir |= (ctlr->pmdio | ctlr->pmdck);
		imm->port[2].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
		eieio();
		/* Step 3, Serial Interface clock routing */
		imm->cmxfcr &= ~0x00ff0000;
		imm->cmxfcr |= 0x00250000;
		break;

	case 2:
		/* Step 1 (Section 28.9), write the parallel ports */
		imm->port[1].pdir &= ~B3dir0;
		imm->port[1].pdir |= B3dir1;
		imm->port[1].psor &= ~B3psor0;
		imm->port[1].psor |= B3psor1;
		imm->port[1].ppar |= (B3dir0 | B3dir1);
		/* Step 2, Port C clocks */
		imm->port[2].psor &= ~0x0000c000;
		imm->port[2].pdir &= ~0x0000c000;
		imm->port[2].ppar |= 0x0000c000;
		imm->port[3].pdat |= (ctlr->pmdio | ctlr->pmdck);
		imm->port[3].podr |= ctlr->pmdio;
		imm->port[3].pdir |= (ctlr->pmdio | ctlr->pmdck);
		imm->port[3].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
		eieio();
		/* Step 3, Serial Interface clock routing */
		imm->cmxfcr &= ~0x0000ff00;
		imm->cmxfcr |= 0x00003700;
		break;
	}
	iopunlock();

	p = (Etherparam*)(m->immr->prmfcc + ctlr->port);
	memset(p, 0, sizeof(Etherparam));

	/* Step 4 */
	fcc->gfmr |= ENET;

	/* Step 5 */
	fcc->fpsmr = CRCE | FDE | LPB;	/* full duplex operation */
	ctlr->duplex = ~0;

	/* Step 6 */
	fcc->fdsr = 0xd555;

	/* Step 7, initialize parameter ram */
	p->rbase = PADDR(ctlr->rdr);
	p->tbase = PADDR(ctlr->tdr);
	p->rstate = (GBL | EB) << 24;
	p->tstate = (GBL | EB) << 24;

	p->cmask = 0xdebb20e3;
	p->cpres = 0xffffffff;

	p->retlim = 15;	/* retry limit */

	p->mrblr = (Rbsize+0x1f)&~0x1f;		/* multiple of 32 */
	p->mflr = Rbsize;
	p->minflr = ETHERMINTU;
	p->maxd1 = (Rbsize+7) & ~7;
	p->maxd2 = (Rbsize+7) & ~7;

	for(i=0; i<Eaddrlen; i+=2)
		p->paddr[2-i/2] = (ea[i+1]<<8)|ea[i];

	/* Step 7, initialize parameter ram, configuration-dependent values */
	p->riptr = m->immr->fccextra[ctlr->port].ri - (uchar*)IMMR;
	p->tiptr = m->immr->fccextra[ctlr->port].ti - (uchar*)IMMR;
	p->padptr = m->immr->fccextra[ctlr->port].pad - (uchar*)IMMR;
	memset(m->immr->fccextra[ctlr->port].pad, 0x88, 0x20);

	/* Step 8, clear out events */
	fcc->fcce = ~0;

	/* Step 9, Interrupt enable */
	fcc->fccm = TXE | RXF | TXB;

	/* Step 10, Configure interrupt priority (not done here) */
	/* Step 11, Clear out current events */
	/* Step 12, Enable interrupts to the CP interrupt controller */

	/* Step 13, Issue the Init Tx and Rx command, specifying 0xc for ethernet*/
	cpmop(InitRxTx, fccid[ctlr->port], 0xc);

	/* Step 14, Link management */
	if((ctlr->mii = malloc(sizeof(Mii))) == nil)
		return -1;
	ctlr->mii->mir = fccmiimir;
	ctlr->mii->miw = fccmiimiw;
	ctlr->mii->ctlr = ctlr;

	if(mii(ctlr->mii, ~0) == 0 || (phy = ctlr->mii->curphy) == nil){
		free(ctlr->mii);
		ctlr->mii = nil;
		return -1;
	}
	miiane(ctlr->mii, ~0, ~0, ~0);
#ifdef DBG
	print("oui=%X, phyno=%d, ", phy->oui, phy->phyno);
	print("anar=%ux, ", phy->anar);
	print("fc=%ux, ", phy->fc);
	print("mscr=%ux, ", phy->mscr);

	print("link=%ux, ", phy->link);
	print("speed=%ux, ", phy->speed);
	print("fd=%ux, ", phy->fd);
	print("rfc=%ux, ", phy->rfc);
	print("tfc=%ux\n", phy->tfc);
#endif
	/* Step 15, Enable ethernet: done at attach time */
	return 0;
}

static int
reset(Ether* ether)
{
	uchar ea[Eaddrlen];
	Ctlr *ctlr;
	FCC *fcc;
	Block *b;
	int i;

	if(m->cpuhz < 24000000){
		print("%s ether: system speed must be >= 24MHz for ether use\n", ether->type);
		return -1;
	}

	if(ether->port > 3){
		print("%s ether: no FCC port %ld\n", ether->type, ether->port);
		return -1;
	}
	ether->irq = fccirq[ether->port];
	ether->tbdf = BusPPC;
	fcc = imm->fcc + ether->port;

	ctlr = malloc(sizeof(*ctlr));
	ether->ctlr = ctlr;
	memset(ctlr, 0, sizeof(*ctlr));
	ctlr->fcc = fcc;
	ctlr->port = ether->port;
	ctlr->fccid = fccid[ether->port];

	/* Ioringinit will allocate the buffer descriptors in normal memory
	 * and NOT in Dual-Ported Ram, as prescribed by the MPC8260
	 * PowerQUICC II manual (Section 28.6).  When they are allocated
	 * in DPram and the Dcache is enabled, the processor will hang
	 */
	if(ioringinit(ctlr, Nrdre, Ntdre, 0) < 0)
		panic("etherfcc init");
	for(i = 0; i < Nrdre; i++){
		b = iallocb(Bufsize);
		b->rp = (uchar*)(((ulong)b->rp + CACHELINESZ-1) & ~(CACHELINESZ-1));
		b->wp = b->rp;
		ctlr->rcvbufs[i] = b;
		ctlr->rdr[i].addr = PADDR(b->wp);
	}

	fccsetup(ctlr, fcc, ether->ea);

	ether->mbps = 100;	/* TO DO: could be 10mbps */
	ether->attach = attach;
	ether->transmit = transmit;
	ether->ifstat = ifstat;

	ether->arg = ether;
	ether->promiscuous = promiscuous;
	ether->multicast = multicast;

	/*
	 * Until we know where to find it, insist that the plan9.ini
	 * entry holds the Ethernet address.
	 */
	memset(ea, 0, Eaddrlen);
	if(memcmp(ea, ether->ea, Eaddrlen) == 0){
		print("no ether address");
		return -1;
	}

	intrenable(ether->irq, interrupt, ether, ether->name);

	return 0;
}

void
etherfcclink(void)
{
	addethercard("fcc", reset);
}

static void
nanodelay(void)
{
	static int count;
	int i;

	for(i = 0; i < 500; i++)
		count++;
	return;
}

static
void miiwriteloop(Ctlr *ctlr, Port *port, int cnt, ulong cmd)
{
	int i;

	for(i = 0; i < cnt; i++){
		port->pdat &= ~ctlr->pmdck;
		if(cmd & BIT(i))
			port->pdat |= ctlr->pmdio;
		else
			port->pdat &= ~ctlr->pmdio;
		nanodelay();
		port->pdat |= ctlr->pmdck;
		nanodelay();
	}
}

static int
fccmiimiw(Mii *mii, int pa, int ra, int data)
{
	int x;
	Port *port;
	ulong cmd;
	Ctlr *ctlr;

	/*
	 * MII Management Interface Write.
	 */

	ctlr = mii->ctlr;
	port = imm->port + 3;
	cmd = MDIwrite | (pa<<(5+2+16))| (ra<<(2+16)) | (data & 0xffff);

	x = splhi();

	port->pdir |= (ctlr->pmdio|ctlr->pmdck);
	nanodelay();

	miiwriteloop(ctlr, port, 32, ~0);
	miiwriteloop(ctlr, port, 32, cmd);

	port->pdir |= (ctlr->pmdio|ctlr->pmdck);
	nanodelay();

	miiwriteloop(ctlr, port, 32, ~0);

	splx(x);
	return 1;
}

static int
fccmiimir(Mii *mii, int pa, int ra)
{
	int data, i, x;
	Port *port;
	ulong cmd;
	Ctlr *ctlr;

	ctlr = mii->ctlr;
	port = imm->port + 3;

	cmd = MDIread | pa<<(5+2+16) | ra<<(2+16);

	x = splhi();
	port->pdir |= (ctlr->pmdio|ctlr->pmdck);
	nanodelay();

	miiwriteloop(ctlr, port, 32, ~0);

	/* Clock out the first 14 MS bits of the command */
	miiwriteloop(ctlr, port, 14, cmd);

	/* Turn-around */
	port->pdat &= ~ctlr->pmdck;
	port->pdir &= ~ctlr->pmdio;
	nanodelay();

	/* For read, clock in 18 bits, use 16 */
	data = 0;
	for(i=0; i<18; i++){
		data <<= 1;
		if(port->pdat & ctlr->pmdio)
			data |= 1;
		port->pdat |= ctlr->pmdck;
		nanodelay();
		port->pdat &= ~ctlr->pmdck;
		nanodelay();
	}
	port->pdir |= (ctlr->pmdio|ctlr->pmdck);
	nanodelay();
	miiwriteloop(ctlr, port, 32, ~0);
	splx(x);
	return data & 0xffff;
}

static void
fccltimer(Ureg*, Timer *t)
{
	Ether *ether;
	Ctlr *ctlr;
	MiiPhy *phy;
	ulong gfmr;

	ether = t->ta;
	ctlr = ether->ctlr;
	if(ctlr->mii == nil || ctlr->mii->curphy == nil)
		return;
	phy = ctlr->mii->curphy;
	if(miistatus(ctlr->mii) < 0){
		print("miistatus failed\n");
		return;
	}
	if(phy->link == 0){
		print("link lost\n");
		return;
	}
	ether->mbps = phy->speed;

	if(phy->fd != ctlr->duplex)
		print("set duplex\n");
	ilock(ctlr);
	gfmr = ctlr->fcc->gfmr;
	if(phy->fd != ctlr->duplex){
		ctlr->fcc->gfmr &= ~(ENR|ENT);
		if(phy->fd)
			ctlr->fcc->fpsmr |= FDE | LPB;		/* full duplex operation */
		else
			ctlr->fcc->fpsmr &= ~(FDE | LPB);	/* half duplex operation */
		ctlr->duplex = phy->fd;
	}
	ctlr->fcc->gfmr = gfmr;
	iunlock(ctlr);
}