shithub: riscv

ref: 7c5ca77a6e4d442b4c026e2cd9e483a692f65def
dir: /sys/src/9/pc/ether83815.c/

View raw version
/*
 * National Semiconductor DP83815
 *
 * Supports only internal PHY and has been tested on:
 *	Netgear FA311TX (using Netgear DS108 10/100 hub)
 *	SiS 900 within SiS 630
 * To do:
 *	check Ethernet address;
 *	test autonegotiation on 10 Mbit, and 100 Mbit full duplex;
 *	external PHY via MII (should be common code for MII);
 *	thresholds;
 *	ring sizing;
 *	physical link changes/disconnect;
 *	push initialisation back to attach.
 *
 * C H Forsyth, [email protected], 18th June 2001.
 */

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

#include "etherif.h"

#define DEBUG		0
#define debug		if(DEBUG)print

enum {
	Nrde		= 64,
	Ntde		= 64,
};

#define Rbsz		ROUNDUP(sizeof(Etherpkt)+4, 4)

typedef struct Des {
	ulong	next;
	int	cmdsts;
	ulong	addr;
	Block*	bp;
} Des;

enum {	/* cmdsts */
	Own	= 1<<31,	/* set by data producer to hand to consumer */
	More	= 1<<30,	/* more of packet in next descriptor */
	Intr	= 1<<29,	/* interrupt when device is done with it */
	Supcrc	= 1<<28,	/* suppress crc on transmit */
	Inccrc	= 1<<28,	/* crc included on receive (always) */
	Ok	= 1<<27,	/* packet ok */
	Size	= 0xFFF,	/* packet size in bytes */

	/* transmit */
	Txa	= 1<<26,	/* transmission aborted */
	Tfu	= 1<<25,	/* transmit fifo underrun */
	Crs	= 1<<24,	/* carrier sense lost */
	Td	= 1<<23,	/* transmission deferred */
	Ed	= 1<<22,	/* excessive deferral */
	Owc	= 1<<21,	/* out of window collision */
	Ec	= 1<<20,	/* excessive collisions */
	/* 19-16 collision count */

	/* receive */
	Rxa	= 1<<26,	/* receive aborted (same as Rxo) */
	Rxo	= 1<<25,	/* receive overrun */
	Dest	= 3<<23,	/* destination class */
	  Drej=	0<<23,		/* packet was rejected */
	  Duni=	1<<23,		/* unicast */
	  Dmulti=	2<<23,		/* multicast */
	  Dbroad=	3<<23,		/* broadcast */
	Long = 1<<22,		/* too long packet received */
	Runt =  1<<21,		/* packet less than 64 bytes */
	Ise =	1<<20,		/* invalid symbol */
	Crce =	1<<19,		/* invalid crc */
	Fae =	1<<18,		/* frame alignment error */
	Lbp =	1<<17,		/* loopback packet */
	Col =	1<<16,		/* collision during receive */
};

enum {				/* PCI vendor & device IDs */
	Nat83815	= (0x0020<<16)|0x100B,
	SiS = 	0x1039,
	SiS900 =	(0x0900<<16)|SiS,
	SiS7016 =	(0x7016<<16)|SiS,

	SiS630bridge	= 0x0008,

	/* SiS 900 PCI revision codes */
	SiSrev630s =	0x81,
	SiSrev630e =	0x82,
	SiSrev630ea1 =	0x83,
	SiSrev635 =	0x90,

	SiSeenodeaddr =	8,		/* short addr of SiS eeprom mac addr */
	SiS630eenodeaddr =	9,	/* likewise for the 630 */
	Nseenodeaddr =	6,		/* " for NS eeprom */
	Nat83815avng =	0x403,
	Nat83816avng =	0x505,		/* 83816 acts like submodel of 83815 */
					/* using reg. 0x58 to disambiguate. */
};

typedef struct Ctlr Ctlr;
typedef struct Ctlr {
	int	port;
	Pcidev*	pcidev;
	Ctlr*	next;
	int	active;
	int	id;			/* (pcidev->did<<16)|pcidev->vid */

	ushort	srom[0xB+1];
	uchar	sromea[Eaddrlen];	/* MAC address */

	uchar	fd;			/* option or auto negotiation */

	int	mbps;

	Lock	lock;

	Des*	rdr;			/* receive descriptor ring */
	int	nrdr;			/* size of rdr */
	int	rdrx;			/* index into rdr */

	Lock	tlock;
	Des*	tdr;			/* transmit descriptor ring */
	int	ntdr;			/* size of tdr */
	int	tdrh;			/* host index into tdr */
	int	tdri;			/* interface index into tdr */
	int	ntq;			/* descriptors active */
	int	ntqmax;

	ulong	rxa;			/* receive statistics */
	ulong	rxo;
	ulong	rlong;
	ulong	runt;
	ulong	ise;
	ulong	crce;
	ulong	fae;
	ulong	lbp;
	ulong	col;
	ulong	rxsovr;
	ulong	rxorn;

	ulong	txa;			/* transmit statistics */
	ulong	tfu;
	ulong	crs;
	ulong	td;
	ulong	ed;
	ulong	owc;
	ulong	ec;
	ulong	txurn;

	ulong	dperr;			/* system errors */
	ulong	rmabt;
	ulong	rtabt;
	ulong	sserr;
	ulong	rxsover;

	ulong	version;		/* silicon version; register 0x58h */
} Ctlr;

static Ctlr* ctlrhead;
static Ctlr* ctlrtail;

enum {
	/* registers (could memory map) */
	Rcr=	0x00,		/* command register */
	  Rld=		1<<10,	/* reload */
	  Rst=		1<<8,
	  Rxr=		1<<5,	/* receiver reset */
	  Txr=		1<<4,	/* transmitter reset */
	  Rxd=		1<<3,	/* receiver disable */
	  Rxe=		1<<2,	/* receiver enable */
	  Txd=		1<<1,	/* transmitter disable */
	  Txe=		1<<0,	/* transmitter enable */
	Rcfg=	0x04,		/* configuration */
	  Lnksts=	1<<31,	/* link good */
	  Speed100=	1<<30,	/* 100 Mb/s link */
	  Fdup=		1<<29,	/* full duplex */
	  Pol=		1<<28,	/* polarity reversal (10baseT) */
	  Aneg_dn=	1<<27,	/* autonegotiation done */
	  Pint_acen=	1<<17,	/* PHY interrupt auto clear enable */
	  Pause_adv=	1<<16,	/* advertise pause during auto neg */
	  Paneg_ena=	1<<13,	/* auto negotiation enable */
	  Paneg_all=	7<<13,	/* auto negotiation enable 10/100 half & full */
	  Ext_phy=	1<<12,	/* enable MII for external PHY */
	  Phy_rst=	1<<10,	/* reset internal PHY */
	  Phy_dis=	1<<9,	/* disable internal PHY (eg, low power) */
	  Req_alg=	1<<7,	/* PCI bus request: set means less aggressive */
	  Sb=		1<<6,	/* single slot back-off not random */
	  Pow=		1<<5,	/* out of window timer selection */
	  Exd=		1<<4,	/* disable excessive deferral timer */
	  Pesel=	1<<3,	/* parity error algorithm selection */
	  Brom_dis=	1<<2,	/* disable boot rom interface */
	  Bem=		1<<0,	/* big-endian mode */
	Rmear=	0x08,		/* eeprom access */
	  Mdc=		1<<6,	/* MII mangement check */
	  Mddir=	1<<5,	/* MII management direction */
	  Mdio=		1<<4,	/* MII mangement data */
	  Eesel=	1<<3,	/* EEPROM chip select */
	  Eeclk=	1<<2,	/* EEPROM clock */
	  Eedo=		1<<1,	/* EEPROM data out (from chip) */
	  Eedi=		1<<0,	/* EEPROM data in (to chip) */
	Rptscr=	0x0C,		/* pci test control */
	Risr=	0x10,		/* interrupt status */
	  Txrcmp=	1<<25,	/* transmit reset complete */
	  Rxrcmp=	1<<24,	/* receiver reset complete */
	  Dperr=	1<<23,	/* detected parity error */
	  Sserr=	1<<22,	/* signalled system error */
	  Rmabt=	1<<21,	/* received master abort */
	  Rtabt=	1<<20,	/* received target abort */
	  Rxsovr=	1<<16,	/* RX status FIFO overrun */
	  Hiberr=	1<<15,	/* high bits error set (OR of 25-16) */
	  Phy=		1<<14,	/* PHY interrupt */
	  Pme=		1<<13,	/* power management event (wake online) */
	  Swi=		1<<12,	/* software interrupt */
	  Mib=		1<<11,	/* MIB service */
	  Txurn=	1<<10,	/* TX underrun */
	  Txidle=	1<<9,	/* TX idle */
	  Txerr=	1<<8,	/* TX packet error */
	  Txdesc=	1<<7,	/* TX descriptor (with Intr bit done) */
	  Txok=		1<<6,	/* TX ok */
	  Rxorn=	1<<5,	/* RX overrun */
	  Rxidle=	1<<4,	/* RX idle */
	  Rxearly=	1<<3,	/* RX early threshold */
	  Rxerr=	1<<2,	/* RX packet error */
	  Rxdesc=	1<<1,	/* RX descriptor (with Intr bit done) */
	  Rxok=		1<<0,	/* RX ok */
	Rimr=	0x14,		/* interrupt mask */
	Rier=	0x18,		/* interrupt enable */
	  Ie=		1<<0,	/* interrupt enable */
	Rtxdp=	0x20,		/* transmit descriptor pointer */
	Rtxcfg=	0x24,		/* transmit configuration */
	  Csi=		1<<31,	/* carrier sense ignore (needed for full duplex) */
	  Hbi=		1<<30,	/* heartbeat ignore (needed for full duplex) */
	  Atp=		1<<28,	/* automatic padding of runt packets */
	  Mxdma=	7<<20,	/* maximum dma transfer field */
	  Mxdma32=	4<<20,	/* 4x32-bit words (32 bytes) */
	  Mxdma64=	5<<20,	/* 8x32-bit words (64 bytes) */
	  Flth=		0x3F<<8,/* Tx fill threshold, units of 32 bytes (must be > Mxdma) */
	  Drth=		0x3F<<0,/* Tx drain threshold (units of 32 bytes) */
	  Flth128=	4<<8,	/* fill at 128 bytes */
	  Drth512=	16<<0,	/* drain at 512 bytes */
	Rrxdp=	0x30,		/* receive descriptor pointer */
	Rrxcfg=	0x34,		/* receive configuration */
	  Atx=		1<<28,	/* accept transmit packets (needed for full duplex) */
	  Rdrth=	0x1F<<1,/* Rx drain threshold (units of 32 bytes) */
	  Rdrth64=	2<<1,	/* drain at 64 bytes */
	Rccsr=	0x3C,		/* CLKRUN control/status */
	  Pmests=	1<<15,	/* PME status */
	Rwcsr=	0x40,		/* wake on lan control/status */
	Rpcr=	0x44,		/* pause control/status */
	Rrfcr=	0x48,		/* receive filter/match control */
	  Rfen=		1<<31,	/* receive filter enable */
	  Aab=		1<<30,	/* accept all broadcast */
	  Aam=		1<<29,	/* accept all multicast */
	  Aau=		1<<28,	/* accept all unicast */
	  Apm=		1<<27,	/* accept on perfect match */
	  Apat=		0xF<<23,/* accept on pattern match */
	  Aarp=		1<<22,	/* accept ARP */
	  Mhen=		1<<21,	/* multicast hash enable */
	  Uhen=		1<<20,	/* unicast hash enable */
	  Ulm=		1<<19,	/* U/L bit mask */
				/* bits 0-9 are rfaddr */
	Rrfdr=	0x4C,		/* receive filter/match data */
	Rbrar=	0x50,		/* boot rom address */
	Rbrdr=	0x54,		/* boot rom data */
	Rsrr=	0x58,		/* silicon revision */
	Rmibc=	0x5C,		/* MIB control */
				/* 60-78 MIB data */

	/* PHY registers */
	Rbmcr=	0x80,		/* basic mode configuration */
	  Reset=	1<<15,
	  Sel100=	1<<13,	/* select 100Mb/sec if no auto neg */
	  Anena=	1<<12,	/* auto negotiation enable */
	  Anrestart=	1<<9,	/* restart auto negotiation */
	  Selfdx=	1<<8,	/* select full duplex if no auto neg */
	Rbmsr=	0x84,		/* basic mode status */
	  Ancomp=	1<<5,	/* autonegotiation complete */
	Rphyidr1= 0x88,
	Rphyidr2= 0x8C,
	Ranar=	0x90,		/* autonegotiation advertisement */
	Ranlpar= 0x94,		/* autonegotiation link partner ability */
	Raner=	0x98,		/* autonegotiation expansion */
	Rannptr= 0x9C,		/* autonegotiation next page TX */
	Rphysts= 0xC0,		/* PHY status */
	Rmicr=	0xC4,		/* MII control */
	  Inten=	1<<1,	/* PHY interrupt enable */
	Rmisr=	0xC8,		/* MII status */
	Rfcscr=	0xD0,		/* false carrier sense counter */
	Rrecr=	0xD4,		/* receive error counter */
	Rpcsr=	0xD8,		/* 100Mb config/status */
	Rphycr=	0xE4,		/* PHY control */
	Rtbscr=	0xE8,		/* 10BaseT status/control */
};

/*
 * eeprom addresses
 * 	7 to 9 (16 bit words): mac address, shifted and reversed
 */

#define csr32r(c, r)	(inl((c)->port+(r)))
#define csr32w(c, r, l)	(outl((c)->port+(r), (ulong)(l)))
#define csr16r(c, r)	(ins((c)->port+(r)))
#define csr16w(c, r, l)	(outs((c)->port+(r), (ulong)(l)))

static void
dumpcregs(Ctlr *ctlr)
{
	int i;

	for(i=0; i<=0x5C; i+=4)
		print("%2.2ux %8.8lux\n", i, csr32r(ctlr, i));
}

static void
promiscuous(void* arg, int on)
{
	Ctlr *ctlr;
	ulong w;

	ctlr = ((Ether*)arg)->ctlr;
	ilock(&ctlr->lock);
	w = csr32r(ctlr, Rrfcr);
	if(on != ((w&Aau)!=0)){
		csr32w(ctlr, Rrfcr, w & ~Rfen);
		csr32w(ctlr, Rrfcr, Rfen | (w ^ Aau));
	}
	iunlock(&ctlr->lock);
}

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

	ctlr = ether->ctlr;
	ilock(&ctlr->lock);
	if(0)
		dumpcregs(ctlr);
	csr32w(ctlr, Rcr, Rxe);
	iunlock(&ctlr->lock);
}

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

	ctlr = ether->ctlr;

	ether->crcs = ctlr->crce;
	ether->frames = ctlr->runt+ctlr->ise+ctlr->rlong+ctlr->fae;
	ether->buffs = ctlr->rxorn+ctlr->tfu;
	ether->overflows = ctlr->rxsovr;

	if(n == 0)
		return 0;

	p = smalloc(READSTR);
	l = snprint(p, READSTR, "Rxa: %lud\n", ctlr->rxa);
	l += snprint(p+l, READSTR-l, "Rxo: %lud\n", ctlr->rxo);
	l += snprint(p+l, READSTR-l, "Rlong: %lud\n", ctlr->rlong);
	l += snprint(p+l, READSTR-l, "Runt: %lud\n", ctlr->runt);
	l += snprint(p+l, READSTR-l, "Ise: %lud\n", ctlr->ise);
	l += snprint(p+l, READSTR-l, "Fae: %lud\n", ctlr->fae);
	l += snprint(p+l, READSTR-l, "Lbp: %lud\n", ctlr->lbp);
	l += snprint(p+l, READSTR-l, "Tfu: %lud\n", ctlr->tfu);
	l += snprint(p+l, READSTR-l, "Txa: %lud\n", ctlr->txa);
	l += snprint(p+l, READSTR-l, "CRC Error: %lud\n", ctlr->crce);
	l += snprint(p+l, READSTR-l, "Collision Seen: %lud\n", ctlr->col);
	l += snprint(p+l, READSTR-l, "Frame Too Long: %lud\n", ctlr->rlong);
	l += snprint(p+l, READSTR-l, "Runt Frame: %lud\n", ctlr->runt);
	l += snprint(p+l, READSTR-l, "Rx Underflow Error: %lud\n", ctlr->rxorn);
	l += snprint(p+l, READSTR-l, "Tx Underrun: %lud\n", ctlr->txurn);
	l += snprint(p+l, READSTR-l, "Excessive Collisions: %lud\n", ctlr->ec);
	l += snprint(p+l, READSTR-l, "Late Collision: %lud\n", ctlr->owc);
	l += snprint(p+l, READSTR-l, "Loss of Carrier: %lud\n", ctlr->crs);
	l += snprint(p+l, READSTR-l, "Parity: %lud\n", ctlr->dperr);
	l += snprint(p+l, READSTR-l, "Aborts: %lud\n", ctlr->rmabt+ctlr->rtabt);
	l += snprint(p+l, READSTR-l, "RX Status overrun: %lud\n", ctlr->rxsover);
	snprint(p+l, READSTR-l, "ntqmax: %d\n", ctlr->ntqmax);
	ctlr->ntqmax = 0;
	buf = a;
	len = readstr(offset, buf, n, p);
	if(offset > l)
		offset -= l;
	else
		offset = 0;
	buf += len;
	n -= len;

	l = snprint(p, READSTR, "srom:");
	for(i = 0; i < nelem(ctlr->srom); i++){
		if(i && ((i & 0x0F) == 0))
			l += snprint(p+l, READSTR-l, "\n     ");
		l += snprint(p+l, READSTR-l, " %4.4uX", ctlr->srom[i]);
	}

	snprint(p+l, READSTR-l, "\n");
	len += readstr(offset, buf, n, p);
	free(p);

	return len;
}

static void
txstart(Ether* ether)
{
	Ctlr *ctlr;
	Block *bp;
	Des *des;
	int started;

	ctlr = ether->ctlr;
	started = 0;
	while(ctlr->ntq < ctlr->ntdr-1){
		bp = qget(ether->oq);
		if(bp == nil)
			break;
		des = &ctlr->tdr[ctlr->tdrh];
		des->bp = bp;
		des->addr = PADDR(bp->rp);
		ctlr->ntq++;
		coherence();
		des->cmdsts = Own | BLEN(bp);
		ctlr->tdrh = NEXT(ctlr->tdrh, ctlr->ntdr);
		started = 1;
	}
	if(started){
		coherence();
		csr32w(ctlr, Rcr, Txe);	/* prompt */
	}

	if(ctlr->ntq > ctlr->ntqmax)
		ctlr->ntqmax = ctlr->ntq;
}

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

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

static void
txrxcfg(Ctlr *ctlr, int txdrth)
{
	ulong rx, tx;

	rx = csr32r(ctlr, Rrxcfg);
	tx = csr32r(ctlr, Rtxcfg);
	if(ctlr->fd){
		rx |= Atx;
		tx |= Csi | Hbi;
	}else{
		rx &= ~Atx;
		tx &= ~(Csi | Hbi);
	}
	tx &= ~(Mxdma|Drth|Flth);
	tx |= Mxdma64 | Flth128 | txdrth;
	csr32w(ctlr, Rtxcfg, tx);
	rx &= ~(Mxdma|Rdrth);
	rx |= Mxdma64 | Rdrth64;
	csr32w(ctlr, Rrxcfg, rx);
}

static void
interrupt(Ureg*, void* arg)
{
	int len, status, cmdsts, n;
	Ctlr *ctlr;
	Ether *ether;
	Des *des;
	Block *bp;

	ether = arg;
	ctlr = ether->ctlr;

	while((status = csr32r(ctlr, Risr)) != 0){

		status &= ~(Pme|Mib);

		if(status & Hiberr){
			if(status & Rxsovr)
				ctlr->rxsover++;
			if(status & Sserr)
				ctlr->sserr++;
			if(status & Dperr)
				ctlr->dperr++;
			if(status & Rmabt)
				ctlr->rmabt++;
			if(status & Rtabt)
				ctlr->rtabt++;
			status &= ~(Hiberr|Txrcmp|Rxrcmp|Rxsovr|Dperr|Sserr|Rmabt|Rtabt);
		}

		/* update link state */
		if(status&Phy){
			status &= ~Phy;
			csr32r(ctlr, Rcfg);
			n = csr32r(ctlr, Rcfg);
//			iprint("83815 phy %x %x\n", n, n&Lnksts);
			ether->link = (n&Lnksts) != 0;
		}

		/*
		 * Received packets.
		 */
		if(status & (Rxdesc|Rxok|Rxerr|Rxearly|Rxorn)){
			des = &ctlr->rdr[ctlr->rdrx];
			while((cmdsts = des->cmdsts) & Own){
				if((cmdsts&Ok) == 0){
					if(cmdsts & Rxa)
						ctlr->rxa++;
					if(cmdsts & Rxo)
						ctlr->rxo++;
					if(cmdsts & Long)
						ctlr->rlong++;
					if(cmdsts & Runt)
						ctlr->runt++;
					if(cmdsts & Ise)
						ctlr->ise++;
					if(cmdsts & Crce)
						ctlr->crce++;
					if(cmdsts & Fae)
						ctlr->fae++;
					if(cmdsts & Lbp)
						ctlr->lbp++;
					if(cmdsts & Col)
						ctlr->col++;
				}
				else if(bp = iallocb(Rbsz)){
					len = (cmdsts&Size)-4;
					if(len <= 0){
						debug("ns83815: packet len %d <=0\n", len);
						freeb(des->bp);
					}else{
						des->bp->wp = des->bp->rp+len;
						etheriq(ether, des->bp, 1);
					}
					des->bp = bp;
					des->addr = PADDR(bp->rp);
					coherence();
				}else{
					debug("ns83815: interrupt: iallocb for input buffer failed\n");
					des->bp->next = 0;
				}

				des->cmdsts = Rbsz;
				coherence();

				ctlr->rdrx = NEXT(ctlr->rdrx, ctlr->nrdr);
				des = &ctlr->rdr[ctlr->rdrx];
			}
			status &= ~(Rxdesc|Rxok|Rxerr|Rxearly|Rxorn);
		}

		/*
		 * Check the transmit side:
		 *	check for Transmit Underflow and Adjust
		 *	the threshold upwards;
		 *	free any transmitted buffers and try to
		 *	top-up the ring.
		 */
		if(status & Txurn){
			ctlr->txurn++;
			ilock(&ctlr->lock);
			/* change threshold */
			iunlock(&ctlr->lock);
			status &= ~(Txurn);
		}

		ilock(&ctlr->tlock);
		while(ctlr->ntq){
			des = &ctlr->tdr[ctlr->tdri];
			cmdsts = des->cmdsts;
			if(cmdsts & Own)
				break;

			if((cmdsts & Ok) == 0){
				if(cmdsts & Txa)
					ctlr->txa++;
				if(cmdsts & Tfu)
					ctlr->tfu++;
				if(cmdsts & Td)
					ctlr->td++;
				if(cmdsts & Ed)
					ctlr->ed++;
				if(cmdsts & Owc)
					ctlr->owc++;
				if(cmdsts & Ec)
					ctlr->ec++;
				ether->oerrs++;
			}

			freeb(des->bp);
			des->bp = nil;
			des->cmdsts = 0;

			ctlr->ntq--;
			ctlr->tdri = NEXT(ctlr->tdri, ctlr->ntdr);
		}
		txstart(ether);
		iunlock(&ctlr->tlock);

		status &= ~(Txurn|Txidle|Txerr|Txdesc|Txok);

		/*
		 * Anything left not catered for?
		 */
		if(status)
			print("#l%d: status %8.8uX\n", ether->ctlrno, status);
	}
}

static void
ctlrinit(Ether* ether)
{
	Ctlr *ctlr;
	Des *des, *last;

	ctlr = ether->ctlr;

	/*
	 * Allocate suitable aligned descriptors
	 * for the transmit and receive rings;
	 * initialise the receive ring;
	 * initialise the transmit ring;
	 * unmask interrupts and start the transmit side.
	 */
	des = xspanalloc((ctlr->nrdr+ctlr->ntdr)*sizeof(Des), 32, 0);
	ctlr->tdr = des;
	ctlr->rdr = des+ctlr->ntdr;

	last = nil;
	for(des = ctlr->rdr; des < &ctlr->rdr[ctlr->nrdr]; des++){
		des->bp = iallocb(Rbsz);
		if(des->bp == nil)
			panic("ether83815: can't allocate receive buffer");
		des->cmdsts = Rbsz;
		des->addr = PADDR(des->bp->rp);
		if(last != nil)
			last->next = PADDR(des);
		last = des;
	}
	ctlr->rdr[ctlr->nrdr-1].next = PADDR(ctlr->rdr);
	ctlr->rdrx = 0;
	csr32w(ctlr, Rrxdp, PADDR(ctlr->rdr));

	last = nil;
	for(des = ctlr->tdr; des < &ctlr->tdr[ctlr->ntdr]; des++){
		des->cmdsts = 0;
		des->bp = nil;
		des->addr = ~0;
		if(last != nil)
			last->next = PADDR(des);
		last = des;
	}
	ctlr->tdr[ctlr->ntdr-1].next = PADDR(ctlr->tdr);
	ctlr->tdrh = 0;
	ctlr->tdri = 0;
	csr32w(ctlr, Rtxdp, PADDR(ctlr->tdr));

	txrxcfg(ctlr, Drth512);

	csr32w(ctlr, Rimr, Dperr|Sserr|Rmabt|Rtabt|Rxsovr|Hiberr|Txurn|Txerr|
		Txdesc|Txok|Rxorn|Rxerr|Rxdesc|Rxok);	/* Phy|Pme|Mib */
	csr32w(ctlr, Rmicr, Inten);	/* enable phy interrupts */
	csr32r(ctlr, Risr);		/* clear status */
	csr32w(ctlr, Rier, Ie);
}

static void
eeclk(Ctlr *ctlr, int clk)
{
	csr32w(ctlr, Rmear, Eesel | clk);
	microdelay(2);
}

static void
eeidle(Ctlr *ctlr)
{
	int i;

	eeclk(ctlr, 0);
	eeclk(ctlr, Eeclk);
	for(i=0; i<25; i++){
		eeclk(ctlr, 0);
		eeclk(ctlr, Eeclk);
	}
	eeclk(ctlr, 0);
	csr32w(ctlr, Rmear, 0);
	microdelay(2);
}

static ushort
eegetw(Ctlr *ctlr, int a)
{
	int d, i, w;

	eeidle(ctlr);
	eeclk(ctlr, 0);
	eeclk(ctlr, Eeclk);
	d = 0x180 | a;
	for(i=0x400; i; i>>=1){
		if(d & i)
			csr32w(ctlr, Rmear, Eesel|Eedi);
		else
			csr32w(ctlr, Rmear, Eesel);
		eeclk(ctlr, Eeclk);
		eeclk(ctlr, 0);
		microdelay(2);
	}
	w = 0;
	for(i=0x8000; i; i >>= 1){
		eeclk(ctlr, Eeclk);
		if(csr32r(ctlr, Rmear) & Eedo)
			w |= i;
		microdelay(2);
		eeclk(ctlr, 0);
	}
	eeidle(ctlr);
	return w;
}

static int
resetctlr(Ctlr *ctlr)
{
	int i;

	/*
	 * Soft-reset the controller
	 */
	csr32w(ctlr, Rcr, Rst);
	for(i=0;; i++){
		if(i > 100){
			print("ns83815: soft reset did not complete\n");
			return -1;
		}
		microdelay(250);
		if((csr32r(ctlr, Rcr) & Rst) == 0)
			break;
		delay(1);
	}
	return 0;
}

static void
shutdown(Ether* ether)
{
	Ctlr *ctlr = ether->ctlr;

print("ether83815 shutting down\n");
	csr32w(ctlr, Rcr, Rxd|Txd);	/* disable transceiver */
	resetctlr(ctlr);
}

static int
softreset(Ctlr* ctlr, int resetphys)
{
	int i, w;

	/*
	 * Soft-reset the controller
	 */
	resetctlr(ctlr);
	if(ctlr->id != Nat83815)
		return 0;
	csr32w(ctlr, Rccsr, Pmests);
	csr32w(ctlr, Rccsr, 0);
	csr32w(ctlr, Rcfg, csr32r(ctlr, Rcfg) | Pint_acen);
	ctlr->version = csr32r(ctlr, Rsrr);
	if(resetphys){
		/*
		 * Soft-reset the PHY
		 */
		csr32w(ctlr, Rbmcr, Reset);
		for(i=0;; i++){
			if(i > 100){
				print("ns83815: PHY soft reset time out\n");
				return -1;
			}
			if((csr32r(ctlr, Rbmcr) & Reset) == 0)
				break;
			delay(1);
		}
	}

	/*
	 * Initialisation values, in sequence (see 4.4 Recommended Registers Configuration)
	 */
	csr16w(ctlr, 0xCC, 0x0001);	/* PGSEL */
	csr16w(ctlr, 0xE4, 0x189C);	/* PMCCSR */
	csr16w(ctlr, 0xFC, 0x0000);	/* TSTDAT */
	csr16w(ctlr, 0xF4, 0x5040);	/* DSPCFG */
	csr16w(ctlr, 0xF8, 0x008C);	/* SDCFG */

	/*
	 * Auto negotiate
	 */
	csr16r(ctlr, Rbmsr);		/* clear latched bits */
	debug("anar: %4.4ux\n", csr16r(ctlr, Ranar));
	csr16w(ctlr, Rbmcr, Anena);
	if(csr16r(ctlr, Ranar) == 0 || (csr32r(ctlr, Rcfg) & Aneg_dn) == 0){
		csr16w(ctlr, Rbmcr, Anena|Anrestart);
		for(i=0;; i++){
			if(i > 3000){
				print("ns83815: auto neg timed out\n");
				return -1;
			}
			if((w = csr16r(ctlr, Rbmsr)) & Ancomp)
				break;
			delay(1);
		}
		debug("%d ms\n", i);
		w &= 0xFFFF;
		debug("bmsr: %4.4ux\n", w);
		USED(w);
	}
	debug("anar: %4.4ux\n", csr16r(ctlr, Ranar));
	debug("anlpar: %4.4ux\n", csr16r(ctlr, Ranlpar));
	debug("aner: %4.4ux\n", csr16r(ctlr, Raner));
	debug("physts: %4.4ux\n", csr16r(ctlr, Rphysts));
	debug("tbscr: %4.4ux\n", csr16r(ctlr, Rtbscr));
	return 0;
}

static int
media(Ether* ether)
{
	Ctlr* ctlr;
	ulong cfg;

	ctlr = ether->ctlr;
	cfg = csr32r(ctlr, Rcfg);
	ctlr->fd = (cfg & Fdup) != 0;
	ether->link = (cfg&Lnksts) != 0;
	return (cfg&(Lnksts|Speed100)) == Lnksts? 10: 100;
}

static char* mediatable[9] = {
	"10BASE-T",				/* TP */
	"10BASE-2",				/* BNC */
	"10BASE-5",				/* AUI */
	"100BASE-TX",
	"10BASE-TFD",
	"100BASE-TXFD",
	"100BASE-T4",
	"100BASE-FX",
	"100BASE-FXFD",
};

static int
is630(ulong id, Pcidev *p)
{
	if(id == SiS900)
		switch (p->rid) {
		case SiSrev630s:
		case SiSrev630e:
	  	case SiSrev630ea1:
			return 1;
		}
	return 0;
}

enum {
	MagicReg = 0x48,
	MagicRegSz = 1,
	Magicrden = 0x40,	/* read enable, apparently */
	Paddr=		0x70,	/* address port */
	Pdata=		0x71,	/* data port */
};

/* rcmos() originally from LANL's SiS 900 driver's rcmos() */
static int
sisrdcmos(Ctlr *ctlr)
{
	int i;
	unsigned reg;
	ulong port;
	Pcidev *p;

	debug("ns83815: SiS 630 rev. %ux reading mac address from cmos\n", ctlr->pcidev->rid);
	p = pcimatch(nil, SiS, SiS630bridge);
	if(p == nil) {
		print("ns83815: no SiS 630 rev. %ux bridge for mac addr\n",
			ctlr->pcidev->rid);
		return 0;
	}
	port = p->mem[0].bar & ~0x01;
	debug("ns83815: SiS 630 rev. %ux reading mac addr from cmos via bridge at port 0x%lux\n", ctlr->pcidev->rid, port);

	reg = pcicfgr8(p, MagicReg);
	pcicfgw8(p, MagicReg, reg|Magicrden);

	for (i = 0; i < Eaddrlen; i++) {
		outb(port+Paddr, SiS630eenodeaddr + i);
		ctlr->sromea[i] = inb(port+Pdata);
	}

	pcicfgw8(p, MagicReg, reg & ~Magicrden);
	return 1;
}

/*
 * If this is a SiS 630E chipset with an embedded SiS 900 controller,
 * we have to read the MAC address from the APC CMOS RAM. - sez freebsd.
 * However, CMOS *is* NVRAM normally.  See devrtc.c:440, memory.c:88.
 */
static void
sissrom(Ctlr *ctlr)
{
	union {
		uchar	eaddr[Eaddrlen];
		ushort	alignment;
	} ee;
	int i, off = SiSeenodeaddr, cnt = sizeof ee.eaddr / sizeof(short);
	ushort *shp = (ushort *)ee.eaddr;

	if(ctlr->id == SiS900 && ctlr->pcidev->rid == SiSrev635) {
		csr32w(ctlr, Rcr, csr32r(ctlr, Rcr) | Rld);
		csr32w(ctlr, Rcr, csr32r(ctlr, Rcr) & ~Rld);
		csr32w(ctlr, Rrfcr, csr32r(ctlr, Rrfcr) & ~Rfen);

		csr32w(ctlr, Rrfcr, 0);
		*shp++ = csr32r(ctlr, Rrfdr);
		csr32w(ctlr, Rrfcr, 1<<16);
		*shp++ = csr32r(ctlr, Rrfdr);
		csr32w(ctlr, Rrfcr, 1<<17);
		*shp = csr32r(ctlr, Rrfdr);

		csr32w(ctlr, Rrfcr, csr32r(ctlr, Rrfcr) | Rfen);
		memmove(ctlr->sromea, ee.eaddr, sizeof ctlr->sromea);
	} else if(!is630(ctlr->id, ctlr->pcidev) || !sisrdcmos(ctlr)) {
		for (i = 0; i < cnt; i++)
			*shp++ = eegetw(ctlr, off++);
		memmove(ctlr->sromea, ee.eaddr, sizeof ctlr->sromea);
	}
}

ushort
søkrisee(Ctlr *c, int n)
{
	int i;
	uint cmd;
	ushort r;

   	csr32w(c, Rmear, Eesel);

	cmd = 0x180|n;
	for(i = 10; i >= 0; i--){
		n = 1<<3;
		if(cmd&(1<<i))
			n |= 1;
		csr32w(c, Rmear, n);
		csr32r(c, Rmear);
		csr32w(c, Rmear, n|4);
		csr32r(c, Rmear);
	}

	csr32w(c, Rmear, 1<<3);
	csr32r(c, Rmear);

	r = 0;
	for(i = 0; i < 16; i++){
		csr32w(c, Rmear, 1<<3 | 1<<2);
		csr32r(c, Rmear);
		if(csr32r(c, Rmear) & 2)
			r |= 1<<i;
		csr32w(c, Rmear, 1<<3);
		csr32r(c, Rmear);
	}

	csr32w(c, Rmear, 1<<3);
	csr32w(c, Rmear, 0);

	return r;
}

static void
nsnormalea(Ctlr *ctlr)
{
	int i, j;

	/*
	 * the MAC address is reversed, straddling word boundaries
	 */
	j = Nseenodeaddr*16 + 15;
	for(i = 0; i < 48; i++){
		ctlr->sromea[i>>3] |= ((ctlr->srom[j>>4] >> (15-(j&0xF))) & 1) << (i&7);
		j++;
	}
}

static void
ns403ea(Ctlr *ctlr)
{
	int i;
	ushort s, t;

	s = ctlr->srom[6];
	for(i = 0; i < 3; i++){
		t = ctlr->srom[i+7];
		ctlr->sromea[i*2]   = t<<1 | s>>15;
		ctlr->sromea[i*2+1] = t>>7;
		s = t;
	}
}

static void
nssrom(Ctlr* ctlr)
{
	int i, ns403;
	ulong vers;
	ushort (*ee)(Ctlr*, int);

	vers = ctlr->version;
	ns403 = vers == Nat83815avng || vers == Nat83816avng;
	if(ns403){
		ee = søkrisee;
		print("soekris %lx\n", vers);
	}else
		ee = eegetw;

	for(i = 0; i < nelem(ctlr->srom); i++)
		ctlr->srom[i] = ee(ctlr, i);

	if(ns403)
		ns403ea(ctlr);
	else
		nsnormalea(ctlr);
}

static void
srom(Ctlr* ctlr)
{
	memset(ctlr->sromea, 0, sizeof(ctlr->sromea));
	switch (ctlr->id) {
	case SiS900:
	case SiS7016:
		sissrom(ctlr);
		break;
	case Nat83815:
		nssrom(ctlr);
		break;
	default:
		print("ns83815: srom: unknown id 0x%ux\n", ctlr->id);
		break;
	}
}

static void
scanpci83815(void)
{
	Ctlr *ctlr;
	Pcidev *p;
	ulong id;

	p = nil;
	while(p = pcimatch(p, 0, 0)){
		if(p->ccrb != Pcibcnet || p->ccru != 0)
			continue;
		id = (p->did<<16)|p->vid;
		switch(id){
		default:
			continue;

		case Nat83815:
			break;
		case SiS900:
			break;
		}

		/*
		 * bar[0] is the I/O port register address and
		 * bar[1] is the memory-mapped register address.
		 */
		ctlr = malloc(sizeof(Ctlr));
		if(ctlr == nil){
			print("ns83815: can't allocate memory\n");
			continue;
		}
		ctlr->port = p->mem[0].bar & ~0x01;
		ctlr->pcidev = p;
		ctlr->id = id;

		if(ioalloc(ctlr->port, p->mem[0].size, 0, "ns83815") < 0){
			print("ns83815: port 0x%uX in use\n", ctlr->port);
			free(ctlr);
			continue;
		}

		if(softreset(ctlr, 0) == -1){
			free(ctlr);
			continue;
		}
		srom(ctlr);

		if(ctlrhead != nil)
			ctlrtail->next = ctlr;
		else
			ctlrhead = ctlr;
		ctlrtail = ctlr;
	}
}

/* multicast already on, don't need to do anything */
static void
multicast(void*, uchar*, int)
{
}

static int
reset(Ether* ether)
{
	Ctlr *ctlr;
	int i, x;
	ulong ctladdr;
	uchar ea[Eaddrlen];
	static int scandone;

	if(scandone == 0){
		scanpci83815();
		scandone = 1;
	}

	/*
	 * Any adapter matches if no ether->port is supplied,
	 * otherwise the ports must match.
	 */
	for(ctlr = ctlrhead; ctlr != nil; ctlr = ctlr->next){
		if(ctlr->active)
			continue;
		if(ether->port == 0 || ether->port == ctlr->port){
			ctlr->active = 1;
			break;
		}
	}
	if(ctlr == nil)
		return -1;

	ether->ctlr = ctlr;
	ether->port = ctlr->port;
	ether->irq = ctlr->pcidev->intl;
	ether->tbdf = ctlr->pcidev->tbdf;

	/*
	 * Check if the adapter's station address is to be overridden.
	 * If not, read it from the EEPROM and set in ether->ea prior to
	 * loading the station address in the hardware.
	 */
	memset(ea, 0, Eaddrlen);
	if(memcmp(ea, ether->ea, Eaddrlen) == 0)
		memmove(ether->ea, ctlr->sromea, Eaddrlen);
	for(i=0; i<Eaddrlen; i+=2){
		x = ether->ea[i] | (ether->ea[i+1]<<8);
		ctladdr = (ctlr->id == Nat83815? i: i<<15);
		csr32w(ctlr, Rrfcr, ctladdr);
		csr32w(ctlr, Rrfdr, x);
	}
	csr32w(ctlr, Rrfcr, Rfen|Apm|Aab|Aam);

	ether->mbps = media(ether);

	/*
	 * Look for a medium override in case there's no autonegotiation
	 * the autonegotiation fails.
	 */

	for(i = 0; i < ether->nopt; i++){
		if(cistrcmp(ether->opt[i], "FD") == 0){
			ctlr->fd = 1;
			continue;
		}
		for(x = 0; x < nelem(mediatable); x++){
			debug("compare <%s> <%s>\n", mediatable[x],
				ether->opt[i]);
			if(cistrcmp(mediatable[x], ether->opt[i]) == 0){
				if(x != 4 && x >= 3)
					ether->mbps = 100;
				else
					ether->mbps = 10;
				switch(x){
				default:
					ctlr->fd = 0;
					break;

				case 0x04:		/* 10BASE-TFD */
				case 0x05:		/* 100BASE-TXFD */
				case 0x08:		/* 100BASE-FXFD */
					ctlr->fd = 1;
					break;
				}
				break;
			}
		}
	}

	/*
	 * Initialise descriptor rings, ethernet address.
	 */
	ctlr->nrdr = Nrde;
	ctlr->ntdr = Ntde;
	pcisetbme(ctlr->pcidev);
	ctlrinit(ether);

	/*
	 * Linkage to the generic ethernet driver.
	 */
	ether->attach = attach;
	ether->transmit = transmit;
	ether->interrupt = interrupt;
	ether->ifstat = ifstat;

	ether->arg = ether;
	ether->promiscuous = promiscuous;
	ether->multicast = multicast;
	ether->shutdown = shutdown;
	return 0;
}

void
ether83815link(void)
{
	addethercard("83815",  reset);
}