shithub: riscv

ref: 6540a9a21ae4f62f512f3e95063561d87e80701c
dir: /sys/src/9/pc/ethervt6105m.c/

View raw version
/*
 * VIA VT6105M Fast Ethernet Controller (Rhine III).
 * To do:
 *	cache-line size alignments - done
 *	reduce tx interrupts - done
 *	reorganise initialisation/shutdown/reset
 *	adjust Tx FIFO threshold on underflow - untested
 *	why does the link status never cause an interrupt?
 *	use the lproc as a periodic timer for stalls, etc.
 *	checksum offload - done
 *	take non-HW stuff out of descriptor for 64-bit
 *	cleanliness
 *	why does the receive buffer alloc have a +3?
 */
#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 "../port/etherif.h"

#include "ethermii.h"

enum {
	Par0		= 0x00,			/* Ethernet Address */
	Rcr		= 0x06,			/* Receive Configuration */
	Tcr		= 0x07,			/* Transmit Configuration */
	Cr		= 0x08,			/* Control */
	Tqw		= 0x0A,			/* Transmit Queue Wake */
	Isr		= 0x0C,			/* Interrupt Status */
	Imr		= 0x0E,			/* Interrupt Mask */
	Mcfilt0		= 0x10,			/* Multicast Filter 0 */
	Mcfilt1		= 0x14,			/* Multicast Filter 1 */
	Rxdaddr		= 0x18,			/* Current Rd Address */
	Txdaddr		= 0x1C,			/* Current Td Address */
	Phyadr		= 0x6C,			/* Phy Address */
	Miisr		= 0x6D,			/* MII Status */
	Bcr0		= 0x6E,			/* Bus Control */
	Bcr1		= 0x6F,
	Miicr		= 0x70,			/* MII Control */
	Miiadr		= 0x71,			/* MII Address */
	Miidata		= 0x72,			/* MII Data */
	Eecsr		= 0x74,			/* EEPROM Control and Status */
	CfgA		= 0x78,			/* Chip Configuration A */
	CfgB		= 0x79,
	CfgC		= 0x7A,
	CfgD		= 0x7B,
	Cr0		= 0x80,			/* Miscellaneous Control */
	Cr1		= 0x81,
	Pmcc		= 0x82,			/* Power Mgmt Capability Control */
	Stickhw		= 0x83,			/* Sticky Hardware Control */
	Misr		= 0x84,			/* MII Interrupt Control */
	Mimr		= 0x85,			/* MII Interrupt Mask */
	Wolcrclr	= 0xA4,
	Wolcgclr	= 0xA7,
	Pwrcsrclr	= 0xAC,
};

enum {						/* Rcr */
	Sep		= 0x01,			/* Accept Error Packets */
	Ar		= 0x02,			/* Accept Small Packets */
	Am		= 0x04,			/* Accept Multicast */
	Ab		= 0x08,			/* Accept Broadcast */
	Prom		= 0x10,			/* Accept Physical Address Packets */
	RrftMASK	= 0xE0,			/* Receive FIFO Threshold */
	RrftSHIFT	= 5,
	Rrft64		= 0<<RrftSHIFT,
	Rrft32		= 1<<RrftSHIFT,
	Rrft128		= 2<<RrftSHIFT,
	Rrft256		= 3<<RrftSHIFT,
	Rrft512		= 4<<RrftSHIFT,
	Rrft768		= 5<<RrftSHIFT,
	Rrft1024	= 6<<RrftSHIFT,
	RrftSAF		= 7<<RrftSHIFT,
};

enum {						/* Tcr */
	Lb0		= 0x02,			/* Loopback Mode */
	Lb1		= 0x04,
	Ofset		= 0x08,			/* Select Back-off Priority */
	RtsfMASK	= 0xE0,			/* Transmit FIFO Threshold */
	RtsfSHIFT	= 5,
	Rtsf128		= 0<<RtsfSHIFT,
	Rtsf256		= 1<<RtsfSHIFT,
	Rtsf512		= 2<<RtsfSHIFT,
	Rtsf1024	= 3<<RtsfSHIFT,
	RtsfSAF		= 7<<RtsfSHIFT,
};

enum {						/* Cr */
	Init		= 0x0001,		/* INIT Process Begin */
	Strt		= 0x0002,		/* Start NIC */
	Stop		= 0x0004,		/* Stop NIC */
	Rxon		= 0x0008,		/* Turn on Receive Process */
	Txon		= 0x0010,		/* Turn on Transmit Process */
	Tdmd		= 0x0020,		/* Transmit Poll Demand */
	Rdmd		= 0x0040,		/* Receive Poll Demand */
	Eren		= 0x0100,		/* Early Receive Enable */
	Fdx		= 0x0400,		/* Set MAC to Full Duplex */
	Dpoll		= 0x0800,		/* Disable Td/Rd Auto Polling */
	Tdmd1		= 0x2000,		/* Transmit Poll Demand 1 */
	Rdmd1		= 0x4000,		/* Receive Poll Demand 1 */
	Sfrst		= 0x8000,		/* Software Reset */
};

enum {						/* Isr/Imr */
	Prx		= 0x0001,		/* Packet Received OK */
	Ptx		= 0x0002,		/* Packet Transmitted OK */
	Rxe		= 0x0004,		/* Receive Error */
	Txe		= 0x0008,		/* Transmit Error */
	Tu		= 0x0010,		/* Transmit Buffer Underflow */
	Ru		= 0x0020,		/* Receive Buffer Link Error */
	Be		= 0x0040,		/* PCI Bus Error */
	Cnt		= 0x0080,		/* Counter Overflow */
	Eri		= 0x0100,		/* Early Receive Interrupt */
	Udfi		= 0x0200,		/* Tx FIFO Underflow */
	Ovfi		= 0x0400,		/* Receive FIFO Overflow */
	Pktrace		= 0x0800,		/* Hmmm... */
	Norbf		= 0x1000,		/* No Receive Buffers */
	Abti		= 0x2000,		/* Transmission Abort */
	Srci		= 0x4000,		/* Port State Change */
	Geni		= 0x8000,		/* General Purpose Interrupt */
};

enum {						/* Phyadr */
	PhyadMASK	= 0x1F,			/* PHY Address */
	PhyadSHIFT	= 0,
	Mfdc		= 0x20,			/* Accelerate MDC Speed */
	Mpo0		= 0x40,			/* MII Polling Timer Interval */
	Mpo1		= 0x80,
};

enum {						/* Bcr0 */
	DmaMASK		= 0x07,			/* DMA Length */
	DmaSHIFT	= 0,
	Dma32		= 0<<DmaSHIFT,
	Dma64		= 1<<DmaSHIFT,
	Dma128		= 2<<DmaSHIFT,
	Dma256		= 3<<DmaSHIFT,
	Dma512		= 4<<DmaSHIFT,
	Dma1024		= 5<<DmaSHIFT,
	DmaSAF		= 7<<DmaSHIFT,
	CrftMASK	= 0x38,			/* Rx FIFO Threshold */
	CrftSHIFT	= 3,
	Crft64		= 1<<CrftSHIFT,
	Crft128		= 2<<CrftSHIFT,
	Crft256		= 3<<CrftSHIFT,
	Crft512		= 4<<CrftSHIFT,
	Crft1024	= 5<<CrftSHIFT,
	CrftSAF		= 7<<CrftSHIFT,
	Extled		= 0x40,			/* Extra LED Support Control */
	Med2		= 0x80,			/* Medium Select Control */
};

enum {						/* Bcr1 */
	PotMASK		= 0x07,			/* Polling Timer Interval */
	PotSHIFT	= 0,
	CtftMASK	= 0x38,			/* Tx FIFO Threshold */
	CtftSHIFT	= 3,
	Ctft64		= 1<<CtftSHIFT,
	Ctft128		= 2<<CtftSHIFT,
	Ctft256		= 3<<CtftSHIFT,
	Ctft512		= 4<<CtftSHIFT,
	Ctft1024	= 5<<CtftSHIFT,
	CtftSAF		= 7<<CtftSHIFT,
};

enum {						/* Miicr */
	Mdc		= 0x01,			/* Clock */
	Mdi		= 0x02,			/* Data In */
	Mdo		= 0x04,			/* Data Out */
	Mout		= 0x08,			/* Output Enable */
	Mdpm		= 0x10,			/* Direct Program Mode Enable */
	Wcmd		= 0x20,			/* Write Enable */
	Rcmd		= 0x40,			/* Read Enable */
	Mauto		= 0x80,			/* Auto Polling Enable */
};

enum {						/* Miiadr */
	MadMASK		= 0x1F,			/* MII Port Address */
	MadSHIFT	= 0,
	Mdone		= 0x20,			/* Accelerate MDC Speed */
	Msrcen		= 0x40,			/* MII Polling Timer Interval */
	Midle		= 0x80,
};

enum {						/* Eecsr */
	Edo		= 0x01,			/* Data Out */
	Edi		= 0x02,			/* Data In */
	Eck		= 0x04,			/* Clock */
	Ecs		= 0x08,			/* Chip Select */
	Dpm		= 0x10,			/* Direct Program Mode Enable */
	Autold		= 0x20,			/* Dynamic Reload */
	Embp		= 0x40,			/* Embedded Program Enable */
	Eepr		= 0x80,			/* Programmed */
};

/*
 * Ring descriptor. The space allocated for each
 * of these will be rounded up to a cache-line boundary.
 * The first 4 elements are known to the hardware.
 */
typedef struct Ds Ds;
typedef struct Ds {
	u32int	status;
	u32int	control;
	u32int	addr;
	u32int	branch;

	Block*	bp;
	Ds*	next;
	Ds*	prev;
} Ds;

enum {						/* Rx Ds status */
	Rerr		= 0x00000001,		/* Buff|Rxserr|Fov|Fae|Crc */
	Crc		= 0x00000002,		/* CRC Error */
	Fae		= 0x00000004,		/* Frame Alignment Error */
	Fov		= 0x00000008,		/* FIFO Overflow */
	Long		= 0x00000010,		/* A Long Packet */
	Runt		= 0x00000020,		/* A Runt Packet */
	Rxserr		= 0x00000040,		/* System Error */
	Buff		= 0x00000080,		/* Buffer Underflow Error */
	Rxedp		= 0x00000100,		/* End of Packet Buffer */
	Rxstp		= 0x00000200,		/* Packet Start */
	Chn		= 0x00000400,		/* Chain Buffer */
	Phy		= 0x00000800,		/* Physical Address Packet */
	Bar		= 0x00001000,		/* Broadcast Packet */
	Mar		= 0x00002000,		/* Multicast Packet */
	Rxok		= 0x00008000,		/* Packet Received OK */
	LengthMASK	= 0x07FF0000,		/* Received Packet Length */
	LengthSHIFT	= 16,

	Own		= 0x80000000,		/* Descriptor Owned by NIC */
};

enum {						/* Rx Ds control */
	RbsizeMASK	= 0x000007FF,		/* Receive Buffer Size */
	RbsizeSHIFT	= 0,
	Tag		= 0x00010000,		/* Receive a Tagged Packet */
	Udpkt		= 0x00020000,		/* Receive a UDP Packet */
	Tcpkt		= 0x00040000,		/* Receive a TCP Packet */
	Ipkt		= 0x00080000,		/* Receive an IP Packet */
	Tuok		= 0x00100000,		/* TCP/UDP Checksum OK */
	Ipok		= 0x00200000,		/* IP Checksum OK */
	Snaptag		= 0x00400000,		/* Snap Packet + 802.1q Tag */
	Rxlerr		= 0x00800000,		/* Receive Length Check Error */
	IpktMASK	= 0xff000000,		/* Interesting Packet */
	IpktSHIFT	= 24,
};

enum {						/* Tx Ds status */
	NcrMASK		= 0x0000000F,		/* Collision Retry Count */
	NcrSHIFT	= 0,
	Cols		= 0x00000010,		/* Experienced Collisions */
	Cdh		= 0x00000080,		/* CD Heartbeat */
	Abt		= 0x00000100,		/* Aborted after Excessive Collisions */
	Owc		= 0x00000200,		/* Out of Window Collision */
	Crs		= 0x00000400,		/* Carrier Sense Lost */
	Udf		= 0x00000800,		/* FIFO Underflow */
	Tbuff		= 0x00001000,		/* Invalid Td */
	Txserr		= 0x00002000,		/* System Error */
	Terr		= 0x00008000,		/* Excessive Collisions */
};

enum {						/* Tx Ds control */
	TbsMASK		= 0x000007FF,		/* Tx Buffer Size */
	TbsSHIFT	= 0,
	Chain		= 0x00008000,		/* Chain Buffer */
	Crcdisable	= 0x00010000,		/* Disable CRC generation */
	Stp		= 0x00200000,		/* Start of Packet */
	Edp		= 0x00400000,		/* End of Packet */
	Ic		= 0x00800000,		/* Interrupt Control */
};

enum {						/* Tx Ds branch */
	Tdctl		= 0x00000001,		/* No Interrupt Generated */
};

enum {
	Nrd		= 196,
	Ntd		= 128,
	Crcsz		= 4,
	Bslop		= 48,
	Rdbsz		= ETHERMAXTU+Crcsz+Bslop,

	Nrxstats	= 8,
	Ntxstats	= 9,

	Txcopy		= 128,
};

typedef struct Ctlr Ctlr;
typedef struct Ctlr {
	int	port;
	Pcidev*	pcidev;
	Ctlr*	next;
	int	active;
	int	id;
	uchar	par[Eaddrlen];

	QLock	alock;				/* attach */
	void*	alloc;				/* descriptors, etc. */
	int	cls;				/* alignment */
	int	nrd;
	int	ntd;

	Ds*	rd;
	Ds*	rdh;

	Lock	tlock;
	Ds*	td;
	Ds*	tdh;
	Ds*	tdt;
	int	tdused;

	Lock	clock;				/*  */
	int	cr;
	int	imr;
	int	tft;				/* Tx threshold */

	Mii*	mii;
	Rendez	lrendez;
	int	lwakeup;

	uint	rxstats[Nrxstats];		/* statistics */
	uint	txstats[Ntxstats];
	ulong	totalt;
	uint	intr;
	uint	lintr;			
	uint	lsleep;
	uint	rintr;
	uint	tintr;
	uint	txdw;
	int	tdumax;

	uint	abt;
	uint	tbuff;
	uint	udf;

	uint	abti;
	uint	udfi;
	uint	tu;

	uint	tuok;
	uint	ipok;
} Ctlr;

static Ctlr* vt6105Mctlrhead;
static Ctlr* vt6105Mctlrtail;

#define csr8r(c, r)	(inb((c)->port+(r)))
#define csr16r(c, r)	(ins((c)->port+(r)))
#define csr32r(c, r)	(inl((c)->port+(r)))
#define csr8w(c, r, b)	(outb((c)->port+(r), (int)(b)))
#define csr16w(c, r, w)	(outs((c)->port+(r), (ushort)(w)))
#define csr32w(c, r, w)	(outl((c)->port+(r), (ulong)(w)))

static Lock vt6105Mrblock;			/* receive Block freelist */
static Block* vt6105Mrbpool;
static uint vt6105Mrbpoolsz;

typedef struct Regs Regs;
typedef struct Regs {
	char*	name;
	int	offset;
	int	size;
} Regs;

static Regs regs[] = {
//	"Par0",		Par0,	1,
//	"Par1",		Par0+1,	1,
//	"Par2",		Par0+2,	1,
//	"Par3",		Par0+3,	1,
//	"Par4",		Par0+4,	1,
//	"Par5",		Par0+5,	1,
	"Rcr",		Rcr,	1,
	"Tcr",		Tcr,	1,
	"Cr0",		Cr,	1,
	"Cr1",		Cr+1,	1,
	"Isr0",		Isr,	1,
	"Isr1",		Isr+1,	1,
	"Imr0",		Imr,	1,
	"Imr1",		Imr+1,	1,
//	"Mcfilt0",	Mcfilt0,4,
//	"Mcfilt1",	Mcfilt1,4,
//	"Rxdaddr",	Rxdaddr,4,
//	"Txdaddr",	Txdaddr,4,
	"Phyadr",	Phyadr,	1,
	"Miisr",	Miisr,	1,
	"Bcr0",		Bcr0,	1,
	"Bcr1",		Bcr1,	1,
	"Miicr",	Miicr,	1,
	"Miiadr",	Miiadr,	1,
//	"Miidata",	Miidata,2,
	"Eecsr",	Eecsr,	1,
	"CfgA",		CfgA,	1,
	"CfgB",		CfgB,	1,
	"CfgC",		CfgC,	1,
	"CfgD",		CfgD,	1,
	"Cr0",		Cr0,	1,
	"Cr1",		Cr1,	1,
	"Pmcc",		Pmcc,	1,
	"Stickhw",	Stickhw,1,
	"Misr",		Misr,	1,
	"Mimr",		Mimr,	1,
	nil,
};

static char* rxstats[Nrxstats] = {
	"Receiver Error",
	"CRC Error",
	"Frame Alignment Error",
	"FIFO Overflow",
	"Long Packet",
	"Runt Packet",
	"System Error",
	"Buffer Underflow Error",
};
static char* txstats[Ntxstats] = {
	"Aborted after Excessive Collisions",
	"Out of Window Collision Seen",
	"Carrier Sense Lost",
	"FIFO Underflow",
	"Invalid Td",
	"System Error",
	nil,
	"Excessive Collisions",
};

static long
vt6105Mifstat(Ether* edev, void* a, long n, ulong offset)
{
	int i, r;
	Ctlr *ctlr;
	char *alloc, *e, *p;

	ctlr = edev->ctlr;
	
	p = alloc = smalloc(READSTR);
	e = p + READSTR;
	for(i = 0; i < Nrxstats; i++){
		p = seprint(p, e, "%s: %ud\n", rxstats[i], ctlr->rxstats[i]);
	}
	for(i = 0; i < Ntxstats; i++){
		if(txstats[i] == nil)
			continue;
		p = seprint(p, e, "%s: %ud\n", txstats[i], ctlr->txstats[i]);
	}
	p = seprint(p, e, "cls: %ud\n", ctlr->cls);
	p = seprint(p, e, "intr: %ud\n", ctlr->intr);
	p = seprint(p, e, "lintr: %ud\n", ctlr->lintr);
	p = seprint(p, e, "lsleep: %ud\n", ctlr->lsleep);
	p = seprint(p, e, "rintr: %ud\n", ctlr->rintr);
	p = seprint(p, e, "tintr: %ud\n", ctlr->tintr);
	p = seprint(p, e, "txdw: %ud\n", ctlr->txdw);
	p = seprint(p, e, "tdumax: %ud\n", ctlr->tdumax);
	p = seprint(p, e, "tft: %ud\n", ctlr->tft);

	p = seprint(p, e, "abt: %ud\n", ctlr->abt);
	p = seprint(p, e, "tbuff: %ud\n", ctlr->tbuff);
	p = seprint(p, e, "udf: %ud\n", ctlr->udf);
	p = seprint(p, e, "abti: %ud\n", ctlr->abti);
	p = seprint(p, e, "udfi: %ud\n", ctlr->udfi);
	p = seprint(p, e, "tu: %ud\n", ctlr->tu);

	p = seprint(p, e, "tuok: %ud\n", ctlr->tuok);
	p = seprint(p, e, "ipok: %ud\n", ctlr->ipok);

	p = seprint(p, e, "rbpoolsz: %ud\n", vt6105Mrbpoolsz);
	p = seprint(p, e, "totalt: %uld\n", ctlr->totalt);

	for(i = 0; regs[i].name != nil; i++){
		p = seprint(p, e, "%s: %2.2x\n",
			regs[i].name,  csr8r(ctlr, regs[i].offset));
	}

	if(ctlr->mii != nil && ctlr->mii->curphy != nil){
		p = seprint(p, e, "phy:   ");
		for(i = 0; i < NMiiPhyr; i++){
			if(i && ((i & 0x07) == 0))
				p = seprint(p, e, "\n       ");
			r = miimir(ctlr->mii, i);
			p = seprint(p, e, " %4.4uX", r);
		}
		seprint(p, e, "\n");
	}

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

	return n;
}

static void
vt6105Mpromiscuous(void* arg, int on)
{
	int rcr;
	Ctlr *ctlr;
	Ether *edev;

	edev = arg;
	ctlr = edev->ctlr;
	rcr = csr8r(ctlr, Rcr);
	if(on)
		rcr |= Prom;
	else
		rcr &= ~Prom;
	csr8w(ctlr, Rcr, rcr);
}

static void
vt6105Mmulticast(void* arg, uchar* addr, int on)
{
	/*
	 * For now Am is set in Rcr.
	 * Will need to interlock with promiscuous
	 * when this gets filled in.
	 */
	USED(arg, addr, on);
}

static int
vt6105Mwakeup(void* v)
{
	return *((int*)v) != 0;
}

static void
vt6105Mimr(Ctlr* ctlr, int imr)
{
	ilock(&ctlr->clock);
	ctlr->imr |= imr;
	csr16w(ctlr, Imr, ctlr->imr);
	iunlock(&ctlr->clock);
}

static void
vt6105Mlproc(void* arg)
{
	Ctlr *ctlr;
	Ether *edev;
	MiiPhy *phy;

	edev = arg;
	ctlr = edev->ctlr;
	while(waserror())
		;
	for(;;){
		if(ctlr->mii == nil || ctlr->mii->curphy == nil)
			break;
		if(miistatus(ctlr->mii) < 0)
			goto enable;

		phy = ctlr->mii->curphy;
		ilock(&ctlr->clock);
		csr16w(ctlr, Cr, ctlr->cr & ~(Txon|Rxon));
		if(phy->fd)
			ctlr->cr |= Fdx;
		else
			ctlr->cr &= ~Fdx;
		csr16w(ctlr, Cr, ctlr->cr);
		iunlock(&ctlr->clock);
enable:
		ctlr->lwakeup = 0;
		vt6105Mimr(ctlr, Srci);

		ctlr->lsleep++;
		sleep(&ctlr->lrendez, vt6105Mwakeup, &ctlr->lwakeup);

	}
	pexit("vt6105Mlproc: done", 1);
}

static void
vt6105Mrbfree(Block* bp)
{
	bp->rp = bp->lim - (Rdbsz+3);
	bp->wp = bp->rp;
 	bp->flag &= ~(Bipck | Budpck | Btcpck | Bpktck);

	ilock(&vt6105Mrblock);
	bp->next = vt6105Mrbpool;
	vt6105Mrbpool = bp;
	iunlock(&vt6105Mrblock);
}

static Block*
vt6105Mrballoc(void)
{
	Block *bp;

	ilock(&vt6105Mrblock);
	if((bp = vt6105Mrbpool) != nil){
		vt6105Mrbpool = bp->next;
		bp->next = nil;
	}
	iunlock(&vt6105Mrblock);

	if(bp == nil && (bp = iallocb(Rdbsz+3)) != nil){
		bp->free = vt6105Mrbfree;
		vt6105Mrbpoolsz++;
	}
	return bp;
}

static void
vt6105Mattach(Ether* edev)
{
	Ctlr *ctlr;
	uchar *alloc;
	Ds *ds, *prev;
	int dsz, i, timeo;
	char name[KNAMELEN];

	ctlr = edev->ctlr;
	qlock(&ctlr->alock);
	if(ctlr->alloc != nil){
		qunlock(&ctlr->alock);
		return;
	}

	/*
	 * Descriptor space.
	 * Receive descriptors should all be aligned on a 4-byte boundary,
	 * but try to do cache-line alignment.
	 */
	ctlr->nrd = Nrd;
	ctlr->ntd = Ntd;
	dsz = ROUNDUP(sizeof(Ds), ctlr->cls);
	alloc = mallocalign((ctlr->nrd+ctlr->ntd)*dsz, dsz, 0, 0);
	if(alloc == nil){
		qunlock(&ctlr->alock);
		error(Enomem);
	}
	ctlr->alloc = alloc;

	ctlr->rd = (Ds*)alloc;

	if(waserror()){
		ds = ctlr->rd;
		for(i = 0; i < ctlr->nrd; i++){
			if(ds->bp != nil){
				freeb(ds->bp);
				ds->bp = nil;
			}
			if((ds = ds->next) == nil)
				break;
		}
		free(ctlr->alloc);
		ctlr->alloc = nil;
		qunlock(&ctlr->alock);
		nexterror();
	}

	prev = (Ds*)(alloc + (ctlr->nrd-1)*dsz);
	for(i = 0; i < ctlr->nrd; i++){
		ds = (Ds*)alloc;
		alloc += dsz;

		ds->control = Ipkt|Tcpkt|Udpkt|Rdbsz;
		ds->branch = PCIWADDR(alloc);

		ds->bp = vt6105Mrballoc();
		if(ds->bp == nil)
			error("vt6105M: can't allocate receive ring\n");
		ds->bp->rp = (uchar*)ROUNDUP((ulong)ds->bp->rp, 4);
		ds->addr = PCIWADDR(ds->bp->rp);

		ds->next = (Ds*)alloc;
		ds->prev = prev;
		prev = ds;

		ds->status = Own;
	}
	prev->branch = 0;
	prev->next = ctlr->rd;
	prev->status = 0;
	ctlr->rdh = ctlr->rd;

	ctlr->td = (Ds*)alloc;
	prev = (Ds*)(alloc + (ctlr->ntd-1)*dsz);
	for(i = 0; i < ctlr->ntd; i++){
		ds = (Ds*)alloc;
		alloc += dsz;

		ds->next = (Ds*)alloc;
		ds->prev = prev;
		prev = ds;
	}
	prev->next = ctlr->td;
	ctlr->tdh = ctlr->tdt = ctlr->td;
	ctlr->tdused = 0;

	ctlr->cr = Dpoll|Rdmd/*|Txon|Rxon*/|Strt;
	/*Srci|Abti|Norbf|Pktrace|Ovfi|Udfi|Be|Ru|Tu|Txe|Rxe|Ptx|Prx*/
	ctlr->imr = Abti|Norbf|Pktrace|Ovfi|Udfi|Be|Ru|Tu|Txe|Rxe|Ptx|Prx;

	ilock(&ctlr->clock);
	csr32w(ctlr, Rxdaddr, PCIWADDR(ctlr->rd));
	csr32w(ctlr, Txdaddr, PCIWADDR(ctlr->td));
	csr16w(ctlr, Isr, ~0);
	csr16w(ctlr, Imr, ctlr->imr);
	csr16w(ctlr, Cr, ctlr->cr);
	iunlock(&ctlr->clock);

	/*
	 * Wait for link to be ready.
	 */
	for(timeo = 0; timeo < 350; timeo++){
		if(miistatus(ctlr->mii) == 0)
			break;
		tsleep(&up->sleep, return0, 0, 10);
	}
//	phy = ctlr->mii->curphy;
//	print("%s: speed %d fd %d link %d rfc %d tfc %d\n",
//		edev->name, phy->speed, phy->fd, phy->link, phy->rfc, phy->tfc);

	ilock(&ctlr->clock);
	ctlr->cr |= Txon|Rxon;
	csr16w(ctlr, Cr, ctlr->cr);
	iunlock(&ctlr->clock);

	snprint(name, KNAMELEN, "#l%dlproc", edev->ctlrno);
	kproc(name, vt6105Mlproc, edev);

	qunlock(&ctlr->alock);
	poperror();
}

static void
vt6105Mtransmit(Ether* edev)
{
	Block *bp;
	Ctlr *ctlr;
	Ds *ds, *next;
	int control, i, size, tdused, timeo;
	long t;

	ctlr = edev->ctlr;

	ilock(&ctlr->tlock);
	t = lcycles();

	/*
	 * Free any completed packets
	 */
	ds = ctlr->tdh;
	for(tdused = ctlr->tdused; tdused > 0; tdused--){
		/*
		 * For some errors the chip will turn the Tx engine
		 * off. Wait for that to happen.
		 * Could reset and re-init the chip here if it doesn't
		 * play fair.
		 * To do: adjust Tx FIFO threshold on underflow.
		 */
		if(ds->status & (Abt|Tbuff|Udf)){
			if(ds->status & Abt)
				ctlr->abt++;
			if(ds->status & Tbuff)
				ctlr->tbuff++;
			if(ds->status & Udf)
				ctlr->udf++;
			for(timeo = 0; timeo < 1000; timeo++){
				if(!(csr16r(ctlr, Cr) & Txon))
					break;
				microdelay(1);
			}
			ds->status = Own;
			csr32w(ctlr, Txdaddr, PCIWADDR(ds));
		}

		if(ds->status & Own)
			break;
		ds->addr = 0;
		ds->branch = 0;

		if(ds->bp != nil){
			freeb(ds->bp);
			ds->bp = nil;
		}
		for(i = 0; i < Ntxstats-1; i++){
			if(ds->status & (1<<i))
				ctlr->txstats[i]++;
		}
		ctlr->txstats[i] += (ds->status & NcrMASK)>>NcrSHIFT;

		ds = ds->next;
	}
	ctlr->tdh = ds;

	/*
	 * Try to fill the ring back up.
	 */
	ds = ctlr->tdt;
	while(tdused < ctlr->ntd-2){
		if((bp = qget(edev->oq)) == nil)
			break;
		tdused++;

		size = BLEN(bp);

		next = ds->next;
		ds->branch = PCIWADDR(ds->next)|Tdctl;

		ds->bp = bp;
		ds->addr = PCIWADDR(bp->rp);
		control = Edp|Stp|((size<<TbsSHIFT) & TbsMASK);

		ds->control = control;
		if(tdused >= ctlr->ntd-2){
			ctlr->txdw++;
			ds->branch &= ~Tdctl;
		}
		coherence();
		ds->status = Own;

		ds = next;
	}
	ctlr->tdt = ds;
	ctlr->tdused = tdused;
	if(ctlr->tdused){
		csr16w(ctlr, Cr, Tdmd|ctlr->cr);
		if(tdused > ctlr->tdumax)
			ctlr->tdumax = tdused;
	}

	ctlr->totalt += lcycles() - t;
	iunlock(&ctlr->tlock);
}

static void
vt6105Mreceive(Ether* edev)
{
	Ds *ds;
	Block *bp;
	Ctlr *ctlr;
	int i, len;

	ctlr = edev->ctlr;

	ds = ctlr->rdh;
	while(!(ds->status & Own) && ds->status != 0){
		/*
		 * Can Long packets be received OK?
		 * What happens to the Rxok bit?
		 */
		if(ds->status & Rerr){
			for(i = 0; i < Nrxstats; i++){
				if(ds->status & (1<<i))
					ctlr->rxstats[i]++;
			}
		}
		else if(bp = vt6105Mrballoc()){
			if(ds->control & Tuok){
				ds->bp->flag |= Btcpck|Budpck;
				ctlr->tuok++;
			}
			if(ds->control & Ipok){
				ds->bp->flag |= Bipck;
				ctlr->ipok++;
			}
			len = ((ds->status & LengthMASK)>>LengthSHIFT)-4;
			ds->bp->wp = ds->bp->rp+len;
			etheriq(edev, ds->bp);
			bp->rp = (uchar*)ROUNDUP((ulong)bp->rp, 4);
			ds->addr = PCIWADDR(bp->rp);
			ds->bp = bp;
		}
		ds->control = Ipkt|Tcpkt|Udpkt|Rdbsz;
		ds->branch = 0;
		ds->status = 0;

		ds->prev->branch = PCIWADDR(ds);
		coherence();
		ds->prev->status = Own;

		ds = ds->next;
	}
	ctlr->rdh = ds;

	csr16w(ctlr, Cr, ctlr->cr);
}

static void
vt6105Minterrupt(Ureg*, void* arg)
{
	Ctlr *ctlr;
	Ether *edev;
	int imr, isr, r, timeo;
	long t;

	edev = arg;
	ctlr = edev->ctlr;

	ilock(&ctlr->clock);
	t = lcycles();

	csr16w(ctlr, Imr, 0);
	imr = ctlr->imr;
	ctlr->intr++;
	for(;;){
		if((isr = csr16r(ctlr, Isr)) != 0)
			csr16w(ctlr, Isr, isr);
		if((isr & ctlr->imr) == 0)
			break;
			
		if(isr & Srci){
			imr &= ~Srci;
			ctlr->lwakeup = isr & Srci;
			wakeup(&ctlr->lrendez);
			isr &= ~Srci;
			ctlr->lintr++;
		}
		if(isr & (Norbf|Pktrace|Ovfi|Ru|Rxe|Prx)){
			vt6105Mreceive(edev);
			isr &= ~(Norbf|Pktrace|Ovfi|Ru|Rxe|Prx);
			ctlr->rintr++;
		}
		if(isr & (Abti|Udfi|Tu|Txe|Ptx)){
			if(isr & (Abti|Udfi|Tu)){
				if(isr & Abti)
					ctlr->abti++;
				if(isr & Udfi)
					ctlr->udfi++;
				if(isr & Tu)
					ctlr->tu++;
				for(timeo = 0; timeo < 1000; timeo++){
					if(!(csr16r(ctlr, Cr) & Txon))
						break;
					microdelay(1);
				}

				if((isr & Udfi) && ctlr->tft < CtftSAF){
					ctlr->tft += 1<<CtftSHIFT;
					r = csr8r(ctlr, Bcr1) & ~CtftMASK;
					csr8w(ctlr, Bcr1, r|ctlr->tft);
				}
			}
			
			
			ctlr->totalt += lcycles() - t;
			vt6105Mtransmit(edev);
			t = lcycles();
			isr &= ~(Abti|Udfi|Tu|Txe|Ptx);
			ctlr->tintr++;
		}
		if(isr)
			panic("vt6105M: isr %4.4uX", isr);
	}
	ctlr->imr = imr;
	csr16w(ctlr, Imr, ctlr->imr);
	
	ctlr->totalt += lcycles() - t;
	iunlock(&ctlr->clock);
}

static int
vt6105Mmiimicmd(Mii* mii, int pa, int ra, int cmd, int data)
{
	Ctlr *ctlr;
	int r, timeo;

	ctlr = mii->ctlr;

	csr8w(ctlr, Miicr, 0);
	r = csr8r(ctlr, Phyadr);
	csr8w(ctlr, Phyadr, (r & ~PhyadMASK)|pa);
	csr8w(ctlr, Phyadr, pa);
	csr8w(ctlr, Miiadr, ra);
	if(cmd == Wcmd)
		csr16w(ctlr, Miidata, data);
	csr8w(ctlr, Miicr, cmd);

	for(timeo = 0; timeo < 10000; timeo++){
		if(!(csr8r(ctlr, Miicr) & cmd))
			break;
		microdelay(1);
	}
	if(timeo >= 10000)
		return -1;

	if(cmd == Wcmd)
		return 0;
	return csr16r(ctlr, Miidata);
}

static int
vt6105Mmiimir(Mii* mii, int pa, int ra)
{
	return vt6105Mmiimicmd(mii, pa, ra, Rcmd, 0);
}

static int
vt6105Mmiimiw(Mii* mii, int pa, int ra, int data)
{
	return vt6105Mmiimicmd(mii, pa, ra, Wcmd, data);
}

static int
vt6105Mdetach(Ctlr* ctlr)
{
	int revid, timeo;

	/*
	 * Reset power management registers.
	 */
	revid = pcicfgr8(ctlr->pcidev, PciRID);
	if(revid >= 0x40){
		/* Set power state D0. */
		csr8w(ctlr, Stickhw, csr8r(ctlr, Stickhw) & 0xFC);

		/* Disable force PME-enable. */
		csr8w(ctlr, Wolcgclr, 0x80);

		/* Clear WOL config and status bits. */
		csr8w(ctlr, Wolcrclr, 0xFF);
		csr8w(ctlr, Pwrcsrclr, 0xFF);
	}

	/*
	 * Soft reset the controller.
	 */
	csr16w(ctlr, Cr, Stop);
	csr16w(ctlr, Cr, Stop|Sfrst);
	/* limit used to be 10000, but that wasn't enough for our Soekris 5501s */
	for(timeo = 0; timeo < 100000; timeo++){
		if(!(csr16r(ctlr, Cr) & Sfrst))
			break;
		microdelay(1);
	}
	if(timeo >= 100000)
		return -1;

	return 0;
}

static int
vt6105Mreset(Ctlr* ctlr)
{
	MiiPhy *phy;
	int i, r, timeo;

	if(vt6105Mdetach(ctlr) < 0)
		return -1;

	/*
	 * Load the MAC address into the PAR[01]
	 * registers.
	 */
	r = csr8r(ctlr, Eecsr);
	csr8w(ctlr, Eecsr, Autold|r);
	/* limit used to be 100, but that wasn't enough for our Soekris 5501s */
	for(timeo = 0; timeo < 100000; timeo++){
		if(!(csr8r(ctlr, Cr) & Autold))
			break;
		microdelay(1);
	}
	if(timeo >= 100000)
		return -1;

	for(i = 0; i < Eaddrlen; i++)
		ctlr->par[i] = csr8r(ctlr, Par0+i);

	/*
	 * Configure DMA and Rx/Tx thresholds.
	 * If the Rx/Tx threshold bits in Bcr[01] are 0 then
	 * the thresholds are determined by Rcr/Tcr.
	 */
	r = csr8r(ctlr, Bcr0) & ~(CrftMASK|DmaMASK);
	csr8w(ctlr, Bcr0, r|Crft128|DmaSAF);
	r = csr8r(ctlr, Bcr1) & ~CtftMASK;
	csr8w(ctlr, Bcr1, r|ctlr->tft);

	r = csr8r(ctlr, Rcr) & ~(RrftMASK|Prom|Ar|Sep);
	csr8w(ctlr, Rcr, r|Ab|Am);
	csr32w(ctlr, Mcfilt0, ~0UL);	/* accept all multicast */
	csr32w(ctlr, Mcfilt1, ~0UL);

	r = csr8r(ctlr, Tcr) & ~(RtsfMASK|Ofset|Lb1|Lb0);
	csr8w(ctlr, Tcr, r);

	/*
	 * Link management.
	 */
	if((ctlr->mii = malloc(sizeof(Mii))) == nil)
		return -1;
	ctlr->mii->mir = vt6105Mmiimir;
	ctlr->mii->miw = vt6105Mmiimiw;
	ctlr->mii->ctlr = ctlr;

	if(mii(ctlr->mii, ~0) == 0 || (phy = ctlr->mii->curphy) == nil){
		free(ctlr->mii);
		ctlr->mii = nil;
		return -1;
	}
//	print("oui %X phyno %d\n", phy->oui, phy->phyno);
	USED(phy);

	if(miistatus(ctlr->mii) < 0){
//		miireset(ctlr->mii);
		miiane(ctlr->mii, ~0, ~0, ~0);
	}

	return 0;
}

static void
vt6105Mpci(void)
{
	Pcidev *p;
	Ctlr *ctlr;
	int cls, port;

	p = nil;
	while(p = pcimatch(p, 0, 0)){
		if(p->ccrb != Pcibcnet || p->ccru != Pciscether)
			continue;

		switch((p->did<<16)|p->vid){
		default:
			continue;
		case (0x3053<<16)|0x1106:	/* Rhine III-M vt6105M */
			break;
		}

		port = p->mem[0].bar & ~0x01;
		if(ioalloc(port, p->mem[0].size, 0, "vt6105M") < 0){
			print("vt6105M: port 0x%uX in use\n", port);
			continue;
		}
		ctlr = malloc(sizeof(Ctlr));
		if(ctlr == nil){
			print("vt6105M: can't allocate memory\n");
			iofree(port);
			continue;
		}
		ctlr->port = port;
		ctlr->pcidev = p;
		pcienable(p);
		ctlr->id = (p->did<<16)|p->vid;
		if((cls = pcicfgr8(p, PciCLS)) == 0 || cls == 0xFF)
			cls = 0x10;
		ctlr->cls = cls*4;
		ctlr->tft = CtftSAF;

		if(vt6105Mreset(ctlr)){
			iofree(port);
			free(ctlr);
			continue;
		}
		pcisetbme(p);

		if(vt6105Mctlrhead != nil)
			vt6105Mctlrtail->next = ctlr;
		else
			vt6105Mctlrhead = ctlr;
		vt6105Mctlrtail = ctlr;
	}
}

static int
vt6105Mpnp(Ether* edev)
{
	Ctlr *ctlr;

	if(vt6105Mctlrhead == nil)
		vt6105Mpci();

	/*
	 * Any adapter matches if no edev->port is supplied,
	 * otherwise the ports must match.
	 */
	for(ctlr = vt6105Mctlrhead; 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;
	/*
	 * Set to 1000Mb/s to fool the bsz calculation.  We need 
	 * something better, though.
	 */
	edev->mbps = 1000;
	memmove(edev->ea, ctlr->par, Eaddrlen);

	/*
	 * Linkage to the generic ethernet driver.
	 */
	edev->attach = vt6105Mattach;
	edev->transmit = vt6105Mtransmit;
	edev->ifstat = vt6105Mifstat;
	edev->ctl = nil;

	edev->arg = edev;
	edev->promiscuous = vt6105Mpromiscuous;
	edev->multicast = vt6105Mmulticast;

	edev->maxmtu = ETHERMAXTU+Bslop;

	intrenable(edev->irq, vt6105Minterrupt, edev, edev->tbdf, edev->name);

	return 0;
}

void
ethervt6105mlink(void)
{
	addethercard("vt6105M", vt6105Mpnp);
}