~kris/9p

9hist

774f0f5d9eac9404e39c6ae351358d5f5d520f58 — David du Colombier 30 years ago bf82bfd
Plan 9 from Bell Labs 1996-06-07
13 files changed, 1598 insertions(+), 986 deletions(-)

M pc/devether.c
M pc/dma.c
D pc/ether509.c
M pc/ether589.c
M pc/ether79c970.c
M pc/ether82557.c
A pc/etherelnk3.c
M pc/etherif.h
M pc/fns.h
M pc/scsi.c
M port/devsd.c
M port/portdat.h
M port/portfns.h
M pc/devether.c => pc/devether.c +17 -3
@@ 78,7 78,21 @@ etherclose(Chan *c)
long
etherread(Chan *c, void *buf, long n, ulong offset)
{
	return netifread(ether[c->dev], c, buf, n, offset);
	Ether *ctlr;

	ctlr = ether[c->dev];
	if((c->qid.path & CHDIR) == 0 && ctlr->ifstat){
		/*
		 * With some controllers it is necessary to reach
		 * into the chip to extract statistics.
		 */
		if(NETTYPE(c->qid.path) == Nifstatqid)
			return (*ctlr->ifstat)(ctlr, buf, n, offset);
		else if(NETTYPE(c->qid.path) == Nstatqid)
			(*ctlr->ifstat)(ctlr, buf, 0, offset);
	}

	return netifread(ctlr, c, buf, n, offset);
}

Block*


@@ 233,7 247,7 @@ etherreset(void)
				ctlr->irq = 9;
			setvec(Int0vec+ctlr->irq, ctlr->interrupt, ctlr);

			print("ether%d: %s: %dMbps port 0x%luX irq %d",
			print("ether#%d: %s: %dMbps port 0x%luX irq %d",
				ctlrno, ctlr->type, ctlr->mbps, ctlr->port, ctlr->irq);
			if(ctlr->mem)
				print(" addr 0x%luX", ctlr->mem & ~KZERO);


@@ 245,7 259,7 @@ etherreset(void)
			print("\n");

			if(ctlr->mbps == 100)
				netifinit(ctlr, "ether", Ntypes, 100*1024);
				netifinit(ctlr, "ether", Ntypes, 128*1024);
			else
				netifinit(ctlr, "ether", Ntypes, 32*1024);
			ctlr->alen = Eaddrlen;

M pc/dma.c => pc/dma.c +13 -24
@@ 4,9 4,6 @@
#include	"dat.h"
#include	"fns.h"

/*
 *  headland chip set for the safari.
 */
typedef struct DMAport	DMAport;
typedef struct DMA	DMA;
typedef struct DMAxfer	DMAxfer;


@@ 33,8 30,9 @@ enum
 */
struct DMAxfer
{
	Page	pg;		/* page used by dma */
	void	*va;		/* virtual address destination/src */
	ulong	bpa;		/* bounce buffer physical address */
	void*	bva;		/* bounce buffer virtual address */
	void*	va;		/* virtual address destination/src */
	long	len;		/* bytes to be transferred */
	int	isread;
};


@@ 95,8 93,8 @@ dmainit(void)
		dp = &dma[i];
		for(chan = 0; chan < 4; chan++){
			xp = &dp->x[chan];
			xp->pg.pa = (ulong)xspanalloc(BY2PG, BY2PG, 0);
			xp->pg.va = KZERO|xp->pg.pa;
			xp->bva = xspanalloc(BY2PG, BY2PG, 0);
			xp->bpa = PADDR(xp->bva);
			xp->len = 0;
			xp->isread = 0;
		}


@@ 117,9 115,9 @@ long
dmasetup(int chan, void *va, long len, int isread)
{
	DMA *dp;
	DMAxfer *xp;
	ulong pa;
	uchar mode;
	DMAxfer *xp;

	dp = &dma[(chan>>2)&1];
	chan = chan & 3;


@@ 132,16 130,17 @@ dmasetup(int chan, void *va, long len, int isread)
	pa = PADDR(va);
	if((((ulong)va)&0xF0000000) != KZERO
	|| (pa&0xFFFF0000) != ((pa+len)&0xFFFF0000)
	|| pa > 16*MB){
	|| pa > 16*MB) {
		if(len > BY2PG)
			len = BY2PG;
		if(!isread)
			memmove((void*)(xp->pg.va), va, len);
			memmove(xp->bva, va, len);
		xp->va = va;
		xp->len = len;
		xp->isread = isread;
		pa = xp->pg.pa;
	} else
		pa = xp->bpa;
	}
	else
		xp->len = 0;

	/*


@@ 149,7 148,7 @@ dmasetup(int chan, void *va, long len, int isread)
	 */
	ilock(dp);
	mode = (isread ? 0x44 : 0x48) | chan;
	outb(dp->mode, mode);		/* single mode dma (give CPU a chance at mem) */
	outb(dp->mode, mode);	/* single mode dma (give CPU a chance at mem) */
	outb(dp->page[chan], pa>>16);
	outb(dp->cbp, 0);		/* set count & address to their first byte */
	outb(dp->addr[chan], pa>>dp->shift);		/* set address */


@@ 203,16 202,6 @@ dmaend(int chan)
	/*
	 *  copy out of temporary page
	 */
	memmove(xp->va, (void*)(xp->pg.va), xp->len);
	memmove(xp->va, xp->bva, xp->len);
	xp->len = 0;
}

void
dmaemode(int chan, int value)
{
	if(chan < 4)
		outb(0x40b, value|chan);
	else
		outb(0x4d6, value|chan);

}

D pc/ether509.c => pc/ether509.c +0 -785
@@ 1,785 0,0 @@
#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"

enum {
	IDport		= 0x0110,	/* anywhere between 0x0100 and 0x01F0 */
					/* Commands */
	GlobalReset	= 0x00,		/* Global Reset */
	SelectWindow	= 0x01,		/* SelectWindow command */
	StartCoax	= 0x02,		/* Start Coaxial Transceiver */
	RxDisable	= 0x03,		/* RX Disable */
	RxEnable	= 0x04,		/* RX Enable */
	RxReset		= 0x05,		/* RX Reset */
	RxDiscard	= 0x08,		/* RX Discard Top Packet */
	TxEnable	= 0x09,		/* TX Enable */
	TxDisable	= 0x0A,		/* TX Disable */
	TxReset		= 0x0B,		/* TX Reset */
	AckIntr		= 0x0D,		/* Acknowledge Interrupt */
	SetIntrMask	= 0x0E,		/* Set Interrupt Mask */
	SetReadZeroMask	= 0x0F,		/* Set Read Zero Mask */
	SetRxFilter	= 0x10,		/* Set RX Filter */
	SetTxAvailable	= 0x12,		/* Set TX Available Threshold */

					/* RX Filter Command Bits */
	MyEtherAddr	= 0x01,		/* Individual address */
	Multicast	= 0x02,		/* Group (multicast) addresses */
	Broadcast	= 0x04,		/* Broadcast address */
	Promiscuous	= 0x08,		/* All addresses (promiscuous mode) */

					/* Window Register Offsets */
	Command		= 0x0E,		/* all windows */
	Status		= 0x0E,

	ManufacturerID	= 0x00,		/* window 0 */
	ProductID	= 0x02,
	ConfigControl	= 0x04,
	AddressConfig	= 0x06,
	ResourceConfig	= 0x08,
	EEPROMcmd	= 0x0A,
	EEPROMdata	= 0x0C,

					/* AddressConfig Bits */
	XcvrTypeMask	= 0xC000,	/* Transceiver Type Select */
	Xcvr10BaseT	= 0x0000,
	XcvrAUI		= 0x4000,
	XcvrBNC		= 0xC000,

					/* ConfigControl */
	Rst		= 0x04,		/* Reset Adapter */
	Ena		= 0x01,		/* Enable Adapter */

	Fifo		= 0x00,		/* window 1 */
	RxStatus	= 0x08,
	Timer		= 0x0A,
	TxStatus	= 0x0B,
	TxFreeBytes	= 0x0C,

					/* Status/Interrupt Bits */
	Latch		= 0x0001,	/* Interrupt Latch */
	Failure		= 0x0002,	/* Adapter Failure */
	TxComplete	= 0x0004,	/* TX Complete */
	TxAvailable	= 0x0008,	/* TX Available */
	RxComplete	= 0x0010,	/* RX Complete */
	Update		= 0x0080,	/* Update Statistics */
	AllIntr		= 0x00FE,	/* All Interrupt Bits */
	CmdInProgress	= 0x1000,	/* Command In Progress */

					/* TxStatus Bits */
	TxJabber	= 0x20,		/* Jabber Error */
	TxUnderrun	= 0x10,		/* Underrun */
	TxMaxColl	= 0x08,		/* Maximum Collisions */

					/* RxStatus Bits */
	RxByteMask	= 0x07FF,	/* RX Bytes (0-1514) */
	RxErrMask	= 0x3800,	/* Type of Error: */
	RxErrOverrun	= 0x0000,	/*   Overrrun */
	RxErrOversize	= 0x0800,	/*   Oversize Packet (>1514) */
	RxErrDribble	= 0x1000,	/*   Dribble Bit(s) */
	RxErrRunt	= 0x1800,	/*   Runt Packet */
	RxErrFraming	= 0x2000,	/*   Alignment (Framing) */
	RxErrCRC	= 0x2800,	/*   CRC */
	RxError		= 0x4000,	/* Error */
	RxEmpty		= 0x8000,	/* Incomplete or FIFO empty */

	InternalCgf	= 0x00,		/* window 3 */

	FIFOdiag	= 0x04,		/* window 4 */
	MediaStatus	= 0x0A,

					/* FIFOdiag bits */
	TxOverrun	= 0x0400,	/* TX Overrrun */
	RxOverrun	= 0x0800,	/* RX Overrun */
	RxStatusOverrun	= 0x1000,	/* RX Status Overrun */
	RxUnderrun	= 0x2000,	/* RX Underrun */
	RxReceiving	= 0x8000,	/* RX Receiving */

					/* MediaStatus bits */
	JabberEna	= 0x0040,	/* Jabber Enabled (writeable) */
	LinkBeatEna	= 0x0080,	/* Link Beat Enabled (writeable) */
	LinkBeatOk	= 0x0800,	/* Valid link beat detected (ro) */
};

#define COMMAND(port, cmd, a)	outs(port+Command, ((cmd)<<11)|(a))

static void
attach(Ether *ether)
{
	ulong port;
	int x;

	port = ether->port;
	/*
	 * Set the receiver packet filter for our own and
	 * and broadcast addresses, set the interrupt masks
	 * for all interrupts, and enable the receiver and transmitter.
	 * The only interrupt we should see under normal conditions
	 * is the receiver interrupt. If the transmit FIFO fills up,
	 * we will also see TxAvailable interrupts.
	 * Disable Update interrupts for now.
	 */
	x = Broadcast|MyEtherAddr;
	if(ether->prom)
		x |= Promiscuous;
	COMMAND(port, SetRxFilter, x);
	COMMAND(port, SetReadZeroMask, (AllIntr|Latch) & ~Update);
	COMMAND(port, SetIntrMask, (AllIntr|Latch) & ~Update);
	COMMAND(port, RxEnable, 0);
	COMMAND(port, TxEnable, 0);
}

static void
promiscuous(void *arg, int on)
{
	ulong port;

	port = ((Ether*)arg)->port;
	if(on)
		COMMAND(port, SetRxFilter, Promiscuous|Broadcast|MyEtherAddr);
	else
		COMMAND(port, SetRxFilter, Broadcast|MyEtherAddr);
}

static void
receive(Ether *ether)
{
	ushort status;
	int len;
	ulong port;

	port = ether->port;
	while(((status = ins(port+RxStatus)) & RxEmpty) == 0){
		/*
		 * If we had an error, log it and continue.
		 */
		if(status & RxError){
			switch(status & RxErrMask){

			case RxErrOverrun:	/* Overrrun */
				ether->overflows++;
				break;

			case RxErrOversize:	/* Oversize Packet (>1514) */
			case RxErrRunt:		/* Runt Packet */
				ether->buffs++;
				break;
			case RxErrFraming:	/* Alignment (Framing) */
				ether->frames++;
				break;

			case RxErrCRC:		/* CRC */
				ether->crcs++;
				break;
			}
		}
		else {
			/*
			 * We have a packet. Read it into the
			 * buffer. The CRC is already stripped off.
			 * Must read len bytes padded to a
			 * doubleword. We can pick them out 32-bits
			 * at a time.
			 */
			ether->inpackets++;
			len = (status & RxByteMask);
			insl(port+Fifo, &ether->rpkt, HOWMANY(len, 4));

			/*
			 * Copy the packet to whoever wants it.
			 */
			etherrloop(ether, &ether->rpkt, len);
		}

		/*
		 * Discard the packet as we're done with it.
		 * Wait for discard to complete.
		 */
		COMMAND(port, RxDiscard, 0);
		while(ins(port+Status) & CmdInProgress)
			;
	}
}

static int
istxfifo(void *arg)
{
	Ether *ether;
	ushort len;
	ulong port;

	ether = arg;
	port = ether->port;
	/*
	 * If there's no room in the FIFO for this packet,
	 * set up an interrupt for when space becomes available.
	 * Output packet must be a multiple of 4 in length and
	 * we need 4 bytes for the preamble.
	 * Assume here that when we are called (via tsleep) that
	 * we are safe from interrupts.
	 */
	len = ROUNDUP(ether->tlen, 4);
	if(len+4 > ins(port+TxFreeBytes)){
		COMMAND(port, SetTxAvailable, len);
		return 0;
	}
	return 1;
}

static long
write(Ether *ether, void *buf, long n)
{
	ushort len;
	ulong port;

	port = ether->port;

	ether->tlen = n;
	len = ROUNDUP(ether->tlen, 4);
	tsleep(&ether->tr, istxfifo, ether, 10000);
	if(len+4 > ins(port+TxFreeBytes)){
		print("ether509: transmitter jammed\n");
		return 0;
	}

	/*
	 * We know there's room, copy the packet to the FIFO.
	 * To save copying the packet into a local buffer just
	 * so we can set the source address, stuff the packet
	 * into the FIFO in 3 pieces.
	 * Transmission won't start until the entire packet is
	 * in the FIFO, so it's OK to fault here.
	 */
	outs(port+Fifo, ether->tlen);
	outs(port+Fifo, 0);
	outss(port+Fifo, buf, Eaddrlen/2);
	outss(port+Fifo, ether->ea, Eaddrlen/2);
	outsl(port+Fifo, (uchar*)buf+2*Eaddrlen, (len-2*Eaddrlen)/4);
	ether->outpackets++;

	return n;
}

static ushort
getdiag(Ether *ether)
{
	ushort bytes;
	ulong port;

	port = ether->port;
	COMMAND(port, SelectWindow, 4);
	bytes = ins(port+FIFOdiag);
	COMMAND(port, SelectWindow, 1);
	return bytes & 0xFFFF;
}

static void
interrupt(Ureg *ur, void *arg)
{
	ushort status, diag;
	uchar txstatus, x;
	ulong port;
	Ether *ether;

	USED(ur);
	ether = arg;
	port = ether->port;

	for(;;){
		/*
		 * Clear the interrupt latch.
		 * It's possible to receive a packet and for another
		 * to become complete before we exit the interrupt
		 * handler so this must be done first to ensure another
		 * interrupt will occur.
		 */
		COMMAND(port, AckIntr, Latch);
		status = ins(port+Status);
		if((status & AllIntr) == 0)
			break;
	
		if(status & Failure){
			/*
			 * Adapter failure, try to find out why.
			 * Reset if necessary.
			 * What happens if Tx is active and we reset,
			 * need to retransmit?
			 * This probably isn't right.
			 */
			diag = getdiag(ether);
			print("ether509: status #%ux, diag #%ux\n", status, diag);
	
			if(diag & TxOverrun){
				COMMAND(port, TxReset, 0);
				COMMAND(port, TxEnable, 0);
				wakeup(&ether->tr);
			}
	
			if(diag & RxUnderrun){
				COMMAND(port, RxReset, 0);
				attach(ether);
			}
	
			return;
		}
	
		if(status & RxComplete){
			receive(ether);
			status &= ~RxComplete;
		}
	
		if(status & TxComplete){
			/*
			 * Pop the TX Status stack, accumulating errors.
			 * If there was a Jabber or Underrun error, reset
			 * the transmitter. For all conditions enable
			 * the transmitter.
			 */
			txstatus = 0;
			do{
				if(x = inb(port+TxStatus))
					outb(port+TxStatus, 0);
				txstatus |= x;
			}while(ins(port+Status) & TxComplete);
	
			if(txstatus & (TxJabber|TxUnderrun))
				COMMAND(port, TxReset, 0);
			COMMAND(port, TxEnable, 0);
			ether->oerrs++;
		}
	
		if(status & (TxAvailable|TxComplete)){
			/*
			 * Reset the Tx FIFO threshold.
			 */
			if(status & TxAvailable){
				COMMAND(port, AckIntr, TxAvailable);
				wakeup(&ether->tr);
			}
			status &= ~(TxAvailable|TxComplete);
		}
	
		/*
		 * Panic if there are any interrupt bits on we haven't
		 * dealt with. Should deal with UP (Update Statistics)
		 * for happier coexistence with Windows drivers.
		 */
		if(status & AllIntr)
			panic("ether509 interrupt: #%lux, #%ux\n", status, getdiag(ether));
	}
}

typedef struct Adapter Adapter;
struct Adapter {
	Adapter*	next;
	ulong		port;
	PCIcfg*		pcicfg;
};
static Adapter *adapter;

/*
 * Write two 0 bytes to identify the IDport and then reset the
 * ID sequence. Then send the ID sequence to the card to get
 * the card into command state.
 */
static void
idseq(void)
{
	int i;
	uchar al;
	static int reset, untag;

	/*
	 * One time only:
	 *	reset any adapters listening
	 */
	if(reset == 0){
		outb(IDport, 0);
		outb(IDport, 0);
		outb(IDport, 0xC0);
		delay(20);
		reset = 1;
	}

	outb(IDport, 0);
	outb(IDport, 0);
	for(al = 0xFF, i = 0; i < 255; i++){
		outb(IDport, al);
		if(al & 0x80){
			al <<= 1;
			al ^= 0xCF;
		}
		else
			al <<= 1;
	}

	/*
	 * One time only:
	 *	write ID sequence to get the attention of all adapters;
	 *	untag all adapters.
	 * If we do a global reset here on all adapters we'll confuse any
	 * ISA cards configured for EISA mode.
	 */
	if(untag == 0){
		outb(IDport, 0xD0);
		untag = 1;
	}
}

static ulong
activate(void)
{
	int i;
	ushort x, acr;

	/*
	 * Do the little configuration dance:
	 *
	 * 2. write the ID sequence to get to command state.
	 */
	idseq();

	/*
	 * 3. Read the Manufacturer ID from the EEPROM.
	 *    This is done by writing the IDPort with 0x87 (0x80
	 *    is the 'read EEPROM' command, 0x07 is the offset of
	 *    the Manufacturer ID field in the EEPROM).
	 *    The data comes back 1 bit at a time.
	 *    We seem to need a delay here between reading the bits.
	 *
	 * If the ID doesn't match, there are no more adapters.
	 */
	outb(IDport, 0x87);
	delay(20);
	for(x = 0, i = 0; i < 16; i++){
		delay(20);
		x <<= 1;
		x |= inb(IDport) & 0x01;
	}
	if(x != 0x6D50)
		return 0;

	/*
	 * 3. Read the Address Configuration from the EEPROM.
	 *    The Address Configuration field is at offset 0x08 in the EEPROM).
	 */
	outb(IDport, 0x88);
	for(acr = 0, i = 0; i < 16; i++){
		delay(20);
		acr <<= 1;
		acr |= inb(IDport) & 0x01;
	}

	return (acr & 0x1F)*0x10 + 0x200;
}

static ulong
tcm509(Ether *ether)
{
	ulong port;
	Adapter *ap;

	/*
	 * Attempt to activate adapters until one matches the
	 * address criteria. If adapter is set for EISA mode (0x3F0),
	 * tag it and ignore. Otherwise, activate it fully.
	 */
	while(port = activate()){
		/*
		 * 6. Tag the adapter so it won't respond in future.
		 */
		outb(IDport, 0xD1);
		if(port == 0x3F0)
			continue;

		/*
		 * 6. Activate the adapter by writing the Activate command
		 *    (0xFF).
		 */
		outb(IDport, 0xFF);
		delay(20);

		/*
		 * 8. Can now talk to the adapter's I/O base addresses.
		 *    Use the I/O base address from the acr just read.
		 *
		 *    Enable the adapter.
		 */
		while(ins(port+Status) & CmdInProgress)
			;
		COMMAND(port, SelectWindow, 0);
		outs(port+ConfigControl, Ena);

		COMMAND(port, TxReset, 0);
		COMMAND(port, RxReset, 0);
		COMMAND(port, AckIntr, 0xFF);
		
		if(ether->port == 0 || ether->port == port)
			return port;

		ap = malloc(sizeof(Adapter));
		ap->port = port;
		ap->next = adapter;
		adapter = ap;
	}

	return 0;
}

static ulong
tcm579(Ether *ether)
{
	static int slot = 1;
	ushort x;
	ulong port;
	Adapter *ap;

	/*
	 * First time through, check if this is an EISA machine.
	 * If not, nothing to do.
	 */
	if(slot == 1 && strncmp((char*)(KZERO|0xFFFD9), "EISA", 4))
		return 0;

	/*
	 * Continue through the EISA slots looking for a match on both
	 * 3COM as the manufacturer and 3C579 or 3C579-TP as the product.
	 * If we find an adapter, select window 0, enable it and clear
	 * out any lingering status and interrupts.
	 * Trying to do a GlobalReset here to re-init the card (as in the
	 * 509 code) doesn't seem to work.
	 */
	while(slot < MaxEISA){
		port = slot++*0x1000;
		if(ins(port+0xC80+ManufacturerID) != 0x6D50)
			continue;
		x = ins(port+0xC80+ProductID);
		if((x & 0xF0FF) != 0x9050/* || (x != 0x9350 && x != 0x9250)*/)
			continue;

		COMMAND(port, SelectWindow, 0);
		outs(port+ConfigControl, Ena);

		COMMAND(port, TxReset, 0);
		COMMAND(port, RxReset, 0);
		COMMAND(port, AckIntr, 0xFF);

		if(ether->port == 0 || ether->port == port)
			return port;

		ap = malloc(sizeof(Adapter));
		ap->port = port;
		ap->next = adapter;
		adapter = ap;
	}

	return 0;
}

static PCIcfg*
tcm590(Ether *ether)
{
	PCIcfg* pcicfg;
	static int devno = 0;
	int port;
	Adapter *ap;

	pcicfg = malloc(sizeof(PCIcfg));
	for(;;){
		pcicfg->vid = 0x10B7;
		pcicfg->did = 0;
		if((devno = pcimatch(0, devno, pcicfg)) == -1)
			break;

		port = pcicfg->baseaddr[0] & ~0x01;
		COMMAND(port, GlobalReset, 0);
		while(ins(port+Status) & CmdInProgress)
			;

		if(ether->port == 0 || ether->port == port)
			return pcicfg;

		ap = malloc(sizeof(Adapter));
		ap->pcicfg = pcicfg;
		ap->port = port;
		ap->next = adapter;
		adapter = ap;

		pcicfg = malloc(sizeof(PCIcfg));
	}
	free(pcicfg);

	return 0;
}

static ulong
tcm589(ISAConf *isa)
{
	if(strcmp(isa->type, "3C589") != 0)
		return 0;

	/*
	 *  The 3com manual register description says that this a noop for
	 *  PCMCIA but the flow chart at the end shows it.
	 */
	COMMAND(isa->port, SelectWindow, 0);
	outs(isa->port+ConfigControl, Ena);
	return isa->port;
}

/*
 * Get configuration parameters.
 */
int
ether509reset(Ether *ether)
{
	int i, eax;
	uchar ea[Eaddrlen];
	ushort x, acr;
	ulong l, port;
	Adapter *ap, **app;
	PCIcfg *pcicfg;

	/*
	 * Any adapter matches if no ether->port is supplied,
	 * otherwise the ports must match.
	 * See if we've already found an adapter that fits
	 * the bill.
	 * If no match then try for an EISA card, an ISA card
	 * and finally for a PCMCIA card.
	 */
	port = 0;
	pcicfg = 0;
	for(app = &adapter, ap = *app; ap; app = &ap->next, ap = ap->next){
		if(ether->port == 0 || ether->port == ap->port){
			port = ap->port;
			pcicfg = ap->pcicfg;
			*app = ap->next;
			free(ap);
			break;
		}
	}
	if(port == 0)
		port = tcm589(ether);
	if(port == 0 && (pcicfg = tcm590(ether)))
		port = pcicfg->baseaddr[0] & ~0x01;
	if(port == 0)
		port = tcm579(ether);
	if(port == 0)
		port = tcm509(ether);
	if(port == 0)
		return -1;

	/*
	 * Read the IRQ from the Resource Configuration Register,
	 * the ethernet address from the EEPROM, and the address configuration.
	 * The EEPROM command is 8 bits, the lower 6 bits being
	 * the address offset.
	 */
	COMMAND(port, SelectWindow, 0);
	if(strcmp(ether->type, "3C589") != 0 && pcicfg == 0)
		ether->irq = (ins(port+ResourceConfig)>>12) & 0x0F;
	for(eax = 0, i = 0; i < 3; i++, eax += 2){
		while(ins(port+EEPROMcmd) & 0x8000)
			;
		outs(port+EEPROMcmd, (2<<6)|i);
		while(ins(port+EEPROMcmd) & 0x8000)
			;
		x = ins(port+EEPROMdata);
		ea[eax] = (x>>8) & 0xFF;
		ea[eax+1] = x & 0xFF;
	}
	if(pcicfg){
		ether->irq = pcicfg->irq;
		COMMAND(port, SelectWindow, 3);
		acr = XcvrAUI;
		l = inl(port+InternalCgf);
		switch(l & 0x700000){

		case 0x000000:
			acr = Xcvr10BaseT;
			break;

		case 0x300000:
			acr = XcvrBNC;
			break;
		}
		free(pcicfg);
	}
	else
		acr = ins(port+AddressConfig);

	/*
	 * Finished with window 0. Now set the ethernet address
	 * in window 2.
	 * Commands have the format 'CCCCCAAAAAAAAAAA' where C
	 * is a bit in the command and A is a bit in the argument.
	 */
	COMMAND(port, SelectWindow, 2);
	if((ether->ea[0]|ether->ea[1]|ether->ea[2]|ether->ea[3]|ether->ea[4]|ether->ea[5]) == 0){
		for(i = 0; i < sizeof(ether->ea); i++)
			ether->ea[i] = ea[i];
	}
	for(i = 0; i < Eaddrlen; i++)
		outb(port+i, ether->ea[i]);

	/*
	 * Enable the transceiver if necessary.
	 */
	COMMAND(port, SelectWindow, 4);
	x = ins(port+MediaStatus) & ~(LinkBeatEna|JabberEna);
	outs(port+MediaStatus, x);
	switch(acr & XcvrTypeMask){

	case Xcvr10BaseT:
		/*
		 * Enable Link Beat and Jabber to start the
		 * transceiver.
		 */
		outs(port+MediaStatus, x|LinkBeatEna|JabberEna);
		break;

	case XcvrBNC:
		/*
		 * Start the DC-DC converter.
		 * Wait > 800 microseconds.
		 */
		COMMAND(port, StartCoax, 0);
		delay(1);
		break;
	}

	/*
	 * Set window 1 for normal operation.
	 * Clear out any lingering Tx status.
	 */
	COMMAND(port, SelectWindow, 1);
	while(inb(port+TxStatus))
		outb(port+TxStatus, 0);

	ether->port = port;

	/*
	 * Set up the software configuration.
	 */
	ether->attach = attach;
	ether->write = write;
	ether->interrupt = interrupt;

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

	return 0;
}

void
ether509link(void)
{
	addethercard("3C509",  ether509reset);
}

M pc/ether589.c => pc/ether589.c +8 -16
@@ 40,7 40,7 @@ enum {

#define COMMAND(port, cmd, a)	outs(port+Command, ((cmd)<<11)|(a))

extern int ether509reset(Ether*);
extern int etherelnk3reset(Ether*);

static int
configASIC(Ether *ether, int port, int xcvr)


@@ 50,25 50,17 @@ configASIC(Ether *ether, int port, int xcvr)
	/* set Window 0 configuration registers */
	COMMAND(port, SelectWindow, 0);

	x = ins(port+ConfigControl);
	outs(port+ConfigControl, x|1);

	/* ROM size & base - must be set before we can access ROM */
	/* transceiver type is 2 for 'figure it out'  */
	x = ins(port + AddressConfig);
	outs(port + AddressConfig, (x & 0xf0) | xcvr);
	outs(port + AddressConfig, xcvr);

	/* IRQ must be 3 on 3C589 */
	x = ins(port + ResourceConfig);
	outs(port + ResourceConfig, 0x3f00 | (x&0xff));

	/* move product ID to register */
	while(ins(port+EEPROMcmd) & 0x8000)
		;
	outs(port+EEPROMcmd, (2<<6)|3);
	while(ins(port+EEPROMcmd) & 0x8000)
		;
	x = ins(port+EEPROMdata);
	outs(port + ProductID, x);

	return ether509reset(ether);
	outs(port + ResourceConfig, 0x3F00);

	return etherelnk3reset(ether);
}

static int

M pc/ether79c970.c => pc/ether79c970.c +30 -38
@@ 2,6 2,8 @@
 * AM79C970
 * PCnet-PCI Single-Chip Ethernet Controller for PCI Local Bus
 * To do:
 *	bag the tsleep in write, buffer them up;
 *	loop in interrupt routine until all done;
 *	only issue transmit interrupt if necessary?
 *	dynamically increase rings as necessary?
 *	use Block's as receive buffers?


@@ 236,9 238,9 @@ write(Ether* ether, void* buf, long n)
		return 0;

	/*
	 * Copy the packet to the transmit buffer and fill in our
	 * Copy the packet to the transmit buffer and fill in the
	 * source ethernet address. There's no need to pad to ETHERMINTU
	 * here as we set ApadXmit in CSR4.
	 * here as ApadXmit is set in CSR4.
	 */
	pkt = KADDR(tdre->tbadr);
	memmove(pkt->d, buf, n);


@@ 324,38 326,37 @@ typedef struct Adapter Adapter;
struct Adapter {
	Adapter*	next;
	int		port;
	PCIcfg*		pcicfg;
	int		irq;
};
static Adapter *adapter;

static PCIcfg*
static int
amd79c90(Ether* ether)
{
	PCIcfg *pcicfg;
	PCIcfg pcicfg;
	static int devno = 0;
	int port;
	int irq, port;
	Adapter *ap;

	pcicfg = malloc(sizeof(PCIcfg));
	for(;;){
		pcicfg->vid = 0x1022;
		pcicfg->did = 0x2000;
		if((devno = pcimatch(0, devno, pcicfg)) == -1)
		pcicfg.vid = 0x1022;
		pcicfg.did = 0x2000;
		if((devno = pcimatch(0, devno, &pcicfg)) == -1)
			break;

		port = pcicfg->baseaddr[0] & ~0x01;
		if(ether->port == 0 || ether->port == port)
			return pcicfg;
		port = pcicfg.baseaddr[0] & ~0x01;
		irq = pcicfg.irq;
		if(ether->port == 0 || ether->port == port){
			ether->irq = irq;
			return port;
		}

		ap = malloc(sizeof(Adapter));
		ap->pcicfg = pcicfg;
		ap->port = port;
		ap->irq = irq;
		ap->next = adapter;
		adapter = ap;

		pcicfg = malloc(sizeof(PCIcfg));
	}
	free(pcicfg);

	return 0;
}


@@ 364,7 365,6 @@ static int
reset(Ether* ether)
{
	int port, x;
	PCIcfg *pcicfg;
	Adapter *ap, **app;
	uchar ea[Eaddrlen];
	Ctlr *ctlr;


@@ 375,37 375,29 @@ reset(Ether* ether)
	 * the bill. If not then scan for another.
	 */
	port = 0;
	pcicfg = 0;
	for(app = &adapter, ap = *app; ap; app = &ap->next, ap = ap->next){
		if(ether->port == 0 || ether->port == ap->port){
			port = ap->port;
			pcicfg = ap->pcicfg;
			ether->irq = ap->irq;
			*app = ap->next;
			free(ap);
			break;
		}
	}
	if(port == 0 && (pcicfg = amd79c90(ether))){
		port = pcicfg->baseaddr[0] & ~0x01;
		ether->irq = pcicfg->irq;
	}
	if(port == 0)
	if(port == 0 && (port = amd79c90(ether)) == 0)
		return -1;

	if(pcicfg)
		free(pcicfg);

	/*
	 * How can we tell what mode we're in at this point - if we're in WORD
	 * mode then the only 32-bit access we are allowed to make is a write to
	 * the RDP, which forces the chip to DWORD mode; if we're in DWORD mode
	 * then we're not allowed to make any non-DWORD accesses?
	 * Assuming we do a DWORD write to the RDP, how can we tell what we're
	 * about to overwrite as we can't reliably access the RAP?
	 * How to tell what mode the chip is in at this point - if it's in WORD
	 * mode then the only 32-bit access allowedis a write to the RDP, which
	 * forces the chip to DWORD mode; if it's in DWORD mode then only DWORD
	 * accesses are allowed?
	 * Assuming a DWORD write is done to the RDP, what will be overwritten as
	 * the RAP can't reliably be accessed?
	 *
	 * Force DWORD mode by writing to RDP, doing a reset then writing to RDP
	 * again; at least we know what state we're in now. The value of RAP after
	 * a reset is 0, so the second DWORD write will be to CSR0.
	 * again. The value of RAP after a reset is 0, so the second DWORD write
	 * will be to CSR0.
	 * Set the software style in BCR20 to be PCnet-PCI to ensure 32-bit access.
	 * Set the auto pad transmit in CSR4.
	 */


@@ 423,7 415,7 @@ reset(Ether* ether)
	outl(port+Rap, 0);

	/*
	 * Check if we are going to override the adapter's station address.
	 * Check if the adapter's station address is to be over-ridden.
	 * If not, read it from the I/O-space and set in ether->ea prior to loading the
	 * station address in the initialisation block.
	 */


@@ 457,7 449,7 @@ reset(Ether* ether)
	/*
	 * Point the chip at the initialisation block and tell it to go.
	 * Mask the Idon interrupt and poll for completion. Strt and interrupt
	 * enables will be set later when we're ready to attach to the network.
	 * enables will be set later when attaching to the network.
	 */
	x = PADDR(&ctlr->iblock);
	outl(port+Rap, 1);

M pc/ether82557.c => pc/ether82557.c +116 -34
@@ 6,9 6,7 @@
 *	the PCI scanning code could be made common to other adapters;
 *	PCI code needs rewritten to handle byte, word, dword accesses
 *	  and using the devno as a bus+dev+function triplet;
 *	tidy/fix locking;
 *	optionally use memory-mapped registers;
 *	stats.
 *	optionally use memory-mapped registers.
 */
#include "u.h"
#include "../port/lib.h"


@@ 141,7 139,7 @@ enum {					/* count */
typedef struct Cb {
	int	command;
	ulong	link;
	uchar	data[24];	/* CbIAS + CbConfigure */
	uchar	data[24];		/* CbIAS + CbConfigure */
} Cb;

typedef struct TxCB {


@@ 185,16 183,18 @@ typedef struct Ctlr {

	uchar	configdata[24];

	Lock	rlock;
	Lock	rlock;			/* registers */

	Block*	rfd[Nrfd];
	Block*	rfd[Nrfd];		/* receive side */
	int	rfdl;
	int	rfdx;

	Lock	cbqlock;
	Block*	cbqhead;
	Block*	cbqhead;		/* transmit side */
	Block*	cbqtail;
	int	cbqbusy;

	Lock	dlock;		/* dump statistical counters */
	ulong	dump[17];
} Ctlr;

static uchar configdata[24] = {


@@ 239,9 239,9 @@ custart(Ctlr* ctlr)
	}
	ctlr->cbqbusy = 1;

	csr32w(ctlr, General, PADDR(ctlr->cbqhead->rp));
	while(csr8r(ctlr, Command))
		;
	csr32w(ctlr, General, PADDR(ctlr->cbqhead->rp));
	csr8w(ctlr, Command, CUstart);
}



@@ 250,7 250,6 @@ action(Ctlr* ctlr, Block* bp)
{
	Cb *cb;

	ilock(&ctlr->cbqlock);
	cb = (Cb*)bp->rp;
	cb->command |= CbEL;



@@ 266,8 265,6 @@ action(Ctlr* ctlr, Block* bp)

	if(ctlr->cbqbusy == 0)
		custart(ctlr);

	iunlock(&ctlr->cbqlock);
}

static void


@@ 280,23 277,20 @@ attach(Ether* ether)
	ilock(&ctlr->rlock);
	status = csr16r(ctlr, Status);
	if((status & RUstatus) == RUidle){
		csr32w(ctlr, General, PADDR(ctlr->rfd[ctlr->rfdx]->rp));
		while(csr8r(ctlr, Command))
			;
		csr32w(ctlr, General, PADDR(ctlr->rfd[ctlr->rfdx]->rp));
		csr8w(ctlr, Command, RUstart);
	}
	iunlock(&ctlr->rlock);
}

static void
configure(void* arg, int promiscuous)
static Block*
configure(Ctlr* ctlr, int promiscuous)
{
	Ctlr *ctlr;
	Block *bp;
	Cb *cb;

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

	bp = allocb(sizeof(Cb));
	cb = (Cb*)bp->rp;
	bp->wp += sizeof(Cb);


@@ 306,12 300,84 @@ configure(void* arg, int promiscuous)
	memmove(cb->data, ctlr->configdata, sizeof(ctlr->configdata));
	if(promiscuous)
		cb->data[15] |= 0x01;

	return bp;
}

static void
promiscuous(void* arg, int on)
{
	Ctlr *ctlr;
	Block *bp;

	ctlr = ((Ether*)arg)->ctlr;
	bp = configure(ctlr, on);

	ilock(&ctlr->rlock);
	action(ctlr, bp);
	iunlock(&ctlr->rlock);
}

static long
ifstat(Ether* ether, void* a, long n, ulong offset)
{
	Ctlr *ctlr;
	char buf[512];
	int len;

	ctlr = ether->ctlr;
	lock(&ctlr->dlock);
	ctlr->dump[16] = 0;

	ilock(&ctlr->rlock);
	while(csr8r(ctlr, Command))
		;
	csr8w(ctlr, Command, DumpSC);
	iunlock(&ctlr->rlock);

	/*
	 * Wait for completion status, should be 0xA005.
	 */
	while(ctlr->dump[16] == 0)
		;

	ether->oerrs = ctlr->dump[1]+ctlr->dump[2]+ctlr->dump[3];
	ether->crcs = ctlr->dump[10];
	ether->frames = ctlr->dump[11];
	ether->buffs = ctlr->dump[12]+ctlr->dump[15];
	ether->overflows = ctlr->dump[13];

	if(n == 0){
		unlock(&ctlr->dlock);
		return 0;
	}

	len = sprint(buf, "transmit good frames: %ld\n", ctlr->dump[0]);
	len += sprint(buf+len, "transmit maximum collisions errors: %ld\n", ctlr->dump[1]);
	len += sprint(buf+len, "transmit late collisions errors: %ld\n", ctlr->dump[2]);
	len += sprint(buf+len, "transmit underrun errors: %ld\n", ctlr->dump[3]);
	len += sprint(buf+len, "transmit lost carrier sense: %ld\n", ctlr->dump[4]);
	len += sprint(buf+len, "transmit deferred: %ld\n", ctlr->dump[5]);
	len += sprint(buf+len, "transmit single collisions: %ld\n", ctlr->dump[6]);
	len += sprint(buf+len, "transmit multiple collisions: %ld\n", ctlr->dump[7]);
	len += sprint(buf+len, "transmit total collisions: %ld\n", ctlr->dump[8]);
	len += sprint(buf+len, "receive good frames: %ld\n", ctlr->dump[9]);
	len += sprint(buf+len, "receive CRC errors: %ld\n", ctlr->dump[10]);
	len += sprint(buf+len, "receive alignment errors: %ld\n", ctlr->dump[11]);
	len += sprint(buf+len, "receive resource errors: %ld\n", ctlr->dump[12]);
	len += sprint(buf+len, "receive overrun errors: %ld\n", ctlr->dump[13]);
	len += sprint(buf+len, "receive collision detect errors: %ld\n", ctlr->dump[14]);
	sprint(buf+len, "receive short frame errors: %ld\n", ctlr->dump[15]);

	unlock(&ctlr->dlock);

	return readstr(offset, a, n, buf);
}

static long
write(Ether* ether, void* buf, long n)
{
	Ctlr *ctlr;
	Block *bp;
	TxCB *txcb;



@@ 330,7 396,10 @@ write(Ether* ether, void* buf, long n)
	memmove(bp->wp+Eaddrlen, ether->ea, Eaddrlen);
	bp->wp += n;

	action(ether->ctlr, bp);
	ctlr = ether->ctlr;
	ilock(&ctlr->rlock);
	action(ctlr, bp);
	iunlock(&ctlr->rlock);

	ether->outpackets++;



@@ 349,19 418,23 @@ interrupt(Ureg*, void* arg)
	ether = arg;
	ctlr = ether->ctlr;

	ilock(&ctlr->rlock);
	for(;;){
		status = csr16r(ctlr, Status);
		csr8w(ctlr, Ack, (status>>8) & 0xFF);

		if((status & (StatCX|StatFR|StatCNA|StatRNR)) == 0)
			return;
		if((status & (StatCX|StatFR|StatCNA|StatRNR|StatMDI|StatSWI)) == 0)
			break;

		if(status & StatFR){
			bp = ctlr->rfd[ctlr->rfdx];
			rfd = (Rfd*)bp->rp;
			while(rfd->field & RfdC){
				etherrloop(ether, rfd, rfd->count & 0x3FFF);
				ether->inpackets++;
				if(rfd->field & RfdOK){
					etherrloop(ether, rfd, rfd->count & 0x3FFF);
					ether->inpackets++;
				}
else print("%s#%d: rfd->field %uX\n", ctlr->type,  ctlr->ctlrno, rfd->field);

				/*
				 * Reinitialise the frame for reception and bump


@@ 390,12 463,12 @@ interrupt(Ureg*, void* arg)
			while(csr8r(ctlr, Command))
				;
			csr8w(ctlr, Command, RUresume);
print("%s#%d: status %uX\n", ctlr->type,  ctlr->ctlrno, status);

			status &= ~StatRNR;
		}

		if(status & StatCNA){
			lock(&ctlr->cbqlock);
			while(bp = ctlr->cbqhead){
				if((((Cb*)bp->rp)->command & CbC) == 0)
					break;


@@ 403,7 476,6 @@ interrupt(Ureg*, void* arg)
				freeb(bp);
			}
			custart(ctlr);
			unlock(&ctlr->cbqlock);

			status &= ~StatCNA;
		}


@@ 411,6 483,7 @@ interrupt(Ureg*, void* arg)
		if(status & (StatCX|StatFR|StatCNA|StatRNR|StatMDI|StatSWI))
			panic("%s#%d: status %uX\n", ctlr->type,  ctlr->ctlrno, status);
	}
	iunlock(&ctlr->rlock);
}

static void


@@ 612,21 685,26 @@ reset(Ether* ether)
	ctlr->type = ether->type;
	ctlr->port = port;

	ilock(&ctlr->rlock);

	csr32w(ctlr, Port, 0);
	delay(1);

	pcicfgr(0, pcidevno, 0, 0x04, &x, 4);
	if((x & 0x05) != 0x05)
		print("PCI command = %uX\n", x);

	csr32w(ctlr, General, 0);
	while(csr8r(ctlr, Command))
		;
	csr32w(ctlr, General, 0);
	csr8w(ctlr, Command, LoadRUB);

	while(csr8r(ctlr, Command))
		;
	csr8w(ctlr, Command, LoadCUB);

	while(csr8r(ctlr, Command))
		;
	csr32w(ctlr, General, PADDR(ctlr->dump));
	csr8w(ctlr, Command, LoadDCA);


	/*
	 * Initialise the receive frame and configuration areas.
	 */


@@ 638,8 716,8 @@ reset(Ether* ether)
	 * configuration. However, should check for the existence of the PHY
	 * and, if found, check whether to use 82503 (serial) or MII (nibble)
	 * mode. Verify the PHY is a National Semiconductor DP83840 by looking
	 * at the Organizationally Unique Identifier (OUI) in registers 2 and 3
	 * which should be 0x80017.
	 * at the Organizationally Unique Identifier (OUI) in registers 2 and
	 * 3 which should be 0x80017.
	 */
	for(i = 1; i < 32; i++){
		if((x = dp83840r(ctlr, i, 2)) == 0xFFFF)


@@ 661,7 739,8 @@ reset(Ether* ether)
	/*
	 * Load the chip configuration
	 */
	configure(ether, 0);
	bp = configure(ctlr, 0);
	action(ctlr, bp);

	/*
	 * Check if the adapter's station address is to be overridden.


@@ 686,6 765,8 @@ reset(Ether* ether)
	memmove(cb->data, ether->ea, Eaddrlen);
	action(ctlr, bp);

	iunlock(&ctlr->rlock);

	/*
	 * Linkage to the generic ethernet driver.
	 */


@@ 693,8 774,9 @@ reset(Ether* ether)
	ether->attach = attach;
	ether->write = write;
	ether->interrupt = interrupt;
	ether->ifstat = ifstat;

	ether->promiscuous = configure;
	ether->promiscuous = promiscuous;
	ether->arg = ether;

	return 0;

A pc/etherelnk3.c => pc/etherelnk3.c +1235 -0
@@ 0,0 1,1235 @@
/*
 * Etherlink III and Fast EtherLink adapters.
 * To do:
 *	check robustness in the face of errors;
 *	autoSelect;
 *	PCI latency timer and master enable;
 *	errata list.
 *
 * Product ID:
 *	9150 ISA	3C509[B]
 *	9050 ISA	3C509[B]-TP
 *	9450 ISA	3C509[B]-COMBO
 *	9550 ISA	3C509[B]-TPO
 *
 *	9350 EISA	3C579
 *	9250 EISA	3C579-TP
 *
 *	5920 EISA	3C592-[TP|COMBO|TPO]
 *	5970 EISA	3C597-TX	Fast Etherlink 10BASE-T/100BASE-TX
 *	5971 EISA	3C597-T4	Fast Etherlink 10BASE-T/100BASE-T4
 *	5972 EISA	3C597-MII	Fast Etherlink 10BASE-T/MII
 *
 *	5900 PCI	3C590-[TP|COMBO|TPO]
 *	5950 PCI	3C595-TX	Fast Etherlink Shared 10BASE-T/100BASE-TX
 *	5951 PCI	3C595-T4	Fast Etherlink Shared 10BASE-T/100BASE-T4
 *	5952 PCI	3C595-MII	Fast Etherlink 10BASE-T/MII
 *
 *	9058 PCMCIA	3C589[B]-[TP|COMBO]
 *
 *	627C MCA	3C529
 *	627D MCA	3C529-TP
 */
#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"

enum {
	IDport			= 0x0110,	/* anywhere between 0x0100 and 0x01F0 */
};

enum {						/* all windows */
	Command			= 0x000E,
	IntStatus		= 0x000E,
};

enum {						/* Commands */
	GlobalReset		= 0x0000,
	SelectRegisterWindow	= 0x0001,	
	EnableDcConverter	= 0x0002,
	RxDisable		= 0x0003,
	RxEnable		= 0x0004,
	RxReset			= 0x0005,
	TxDone			= 0x0007,	
	RxDiscard		= 0x0008,
	TxEnable		= 0x0009,
	TxDisable		= 0x000A,
	TxReset			= 0x000B,
	RequestInterrupt	= 0x000C,
	AcknowledgeInterrupt	= 0x000D,
	SetInterruptEnable	= 0x000E,
	SetIndicationEnable	= 0x000F,	/* SetReadZeroMask */
	SetRxFilter		= 0x0010,
	SetRxEarlyThresh	= 0x0011,
	SetTxAvailableThresh	= 0x0012,
	SetTxStartThresh	= 0x0013,
	StartDma		= 0x0014,	/* initiate busmaster operation */
	StatisticsEnable	= 0x0015,
	StatisticsDisable	= 0x0016,
	DisableDcConverter	= 0x0017,
	SetTxReclaimThresh	= 0x0018,	/* PIO-only adapters */
	PowerUp			= 0x001B,	/* not all adapters */
	PowerDownFull		= 0x001C,	/* not all adapters */
	PowerAuto		= 0x001D,	/* not all adapters */
};

enum {						/* (Global|Rx|Tx)Reset command bits */
	tpAuiReset		= 0x0001,	/* 10BaseT and AUI transceivers */
	endecReset		= 0x0002,	/* internal Ethernet encoder/decoder */
	networkReset		= 0x0004,	/* network interface logic */
	fifoReset		= 0x0008,	/* FIFO control logic */
	aismReset		= 0x0010,	/* autoinitialise state-machine logic */
	hostReset		= 0x0020,	/* bus interface logic */
	dmaReset		= 0x0040,	/* bus master logic */
	vcoReset		= 0x0080,	/* on-board 10Mbps VCO */

	resetMask		= 0x00FF,
};

enum {						/* SetRxFilter command bits */
	receiveIndividual	= 0x0001,	/* match station address */
	receiveMulticast	= 0x0002,
	receiveBroadcast	= 0x0004,
	receiveAllFrames	= 0x0008,	/* promiscuous */
};

enum {						/* StartDma command bits */
	Upload			= 0x0000,	/* transfer data from adapter to memory */
	Download		= 0x0001,	/* transfer data from memory to adapter */
};

enum {						/* IntStatus bits */
	interruptLatch		= 0x0001,
	hostError		= 0x0002,	/* Adapter Failure */
	txComplete		= 0x0004,
	txAvailable		= 0x0008,
	rxComplete		= 0x0010,
	rxEarly			= 0x0020,
	intRequested		= 0x0040,
	updateStats		= 0x0080,
	transferInt		= 0x0100,	/* Bus Master Transfer Complete */
	busMasterInProgress	= 0x0800,
	commandInProgress	= 0x1000,

	interruptMask		= 0x01FE,
};

#define COMMAND(port, cmd, a)	outs((port)+Command, ((cmd)<<11)|(a))
#define STATUS(port)		ins((port)+IntStatus)

enum {						/* Window 0 - setup */
	Wsetup			= 0x0000,
						/* registers */
	ManufacturerID		= 0x0000,	/* 3C5[08]*, 3C59[27] */
	ProductID		= 0x0002,	/* 3C5[08]*, 3C59[27] */
	ConfigControl		= 0x0004,	/* 3C5[08]*, 3C59[27] */
	AddressConfig		= 0x0006,	/* 3C5[08]*, 3C59[27] */
	ResourceConfig		= 0x0008,	/* 3C5[08]*, 3C59[27] */
	EepromCommand		= 0x000A,
	EepromData		= 0x000C,
						/* AddressConfig Bits */
	xcvrMask9		= 0xC000,
						/* ConfigControl bits */
	Ena			= 0x0001,
						/* EepromCommand bits */
	EepromReadRegister	= 0x0080,
	EepromBusy		= 0x8000,
};

#define EEPROMCMD(port, cmd, a)	outs((port)+EepromCommand, (cmd)|(a))
#define EEPROMBUSY(port)	(ins((port)+EepromCommand) & EepromBusy)
#define EEPROMDATA(port)	ins((port)+EepromData)

enum {						/* Window 1 - operating set */
	Wop			= 0x0001,
						/* registers */
	Fifo			= 0x0000,
	RxError			= 0x0004,	/* 3C59[0257] only */
	RxStatus		= 0x0008,
	Timer			= 0x000A,
	TxStatus		= 0x000B,
	TxFree			= 0x000C,
						/* RxError bits */
	rxOverrun		= 0x0001,
	runtFrame		= 0x0002,
	alignmentError		= 0x0004,	/* Framing */
	crcError		= 0x0008,
	oversizedFrame		= 0x0010,
	dribbleBits		= 0x0080,
						/* RxStatus bits */
	rxBytes			= 0x1FFF,	/* 3C59[0257] mask */
	rxBytes9		= 0x07FF,	/* 3C5[078]9 mask */
	rxError9		= 0x3800,	/* 3C5[078]9 error mask */
	rxOverrun9		= 0x0000,
	oversizedFrame9		= 0x0800,
	dribbleBits9		= 0x1000,
	runtFrame9		= 0x1800,
	alignmentError9		= 0x2000,	/* Framing */
	crcError9		= 0x2800,
	rxError			= 0x4000,
	rxIncomplete		= 0x8000,
						/* TxStatus Bits */
	txStatusOverflow	= 0x0004,
	maxCollisions		= 0x0008,
	txUnderrun		= 0x0010,
	txJabber		= 0x0020,
	interruptRequested	= 0x0040,
	txStatusComplete	= 0x0080,
};

enum {						/* Window 2 - station address */
	Wstation		= 0x0002,
};

enum {						/* Window 3 - FIFO management */
	Wfifo			= 0x0003,
						/* registers */
	InternalConfig		= 0x0000,	/* 3C509B, 3C589, 3C59[0257] */
	OtherInt		= 0x0004,	/* 3C59[0257] */
	RomControl		= 0x0006,	/* 3C509B, 3C59[27] */
	MacControl		= 0x0006,	/* 3C59[0257] */
	ResetOptions		= 0x0008,	/* 3C59[0257] */
	RxFree			= 0x000A,
						/* InternalConfig bits */
	xcvr10BaseT		= 0x00000000,
	xcvrAui			= 0x00100000,	/* 10BASE5 */
	xcvr10Base2		= 0x00300000,
	xcvr100BaseTX		= 0x00400000,
	xcvr100BaseFX		= 0x00500000,
	xcvrMii			= 0x00600000,
	xcvrMask		= 0x00700000,
	autoSelect		= 0x01000000,
						/* MacControl bits */
	deferExtendEnable	= 0x0001,
	deferTimerSelect	= 0x001E,	/* mask */
	fullDuplexEnable	= 0x0020,
	allowLargePackets	= 0x0040,
						/* ResetOptions bits */
	baseT4Available		= 0x0001,
	baseTXAvailable		= 0x0002,
	baseFXAvailable		= 0x0004,
	base10TAvailable	= 0x0008,
	coaxAvailable		= 0x0010,
	auiAvailable		= 0x0020,
	miiAvailable		= 0x0040,
};

enum {						/* Window 4 - diagnostic */
	Wdiagnostic		= 0x0004,
						/* registers */
	VcoDiagnostic		= 0x0002,
	FifoDiagnostic		= 0x0004,
	NetworkDiagnostic	= 0x0006,
	PhysicalMgmt		= 0x0008,
	MediaStatus		= 0x000A,
	BadSSD			= 0x000C,
						/* FifoDiagnostic bits */
	txOverrun		= 0x0400,
	rxUnderrun		= 0x2000,
	receiving		= 0x8000,
						/* MediaStatus bits */
	dataRate100		= 0x0002,
	crcStripDisable		= 0x0004,
	enableSqeStats		= 0x0008,
	collisionDetect		= 0x0010,
	carrierSense		= 0x0020,
	jabberGuardEnable	= 0x0040,
	linkBeatEnable		= 0x0080,
	jabberDetect		= 0x0200,
	polarityReversed	= 0x0400,
	linkBeatDetect		= 0x0800,
	txInProg		= 0x1000,
	dcConverterEnabled	= 0x4000,
	auiDisable		= 0x8000,
};

enum {						/* Window 5 - internal state */
	Wstate			= 0x0005,
						/* registers */
	TxStartThresh		= 0x0000,
	TxAvalableThresh	= 0x0002,
	RxEarlyThresh		= 0x0006,
	RxFilter		= 0x0008,
	InterruptEnable		= 0x000A,
	IndicationEnable	= 0x000C,
};

enum {						/* Window 6 - statistics */
	Wstatistics		= 0x0006,
						/* registers */
	CarrierLost		= 0x0000,
	SqeErrors		= 0x0001,
	MultipleColls		= 0x0002,
	SingleCollFrames	= 0x0003,
	LateCollisions		= 0x0004,
	RxOverruns		= 0x0005,
	FramesXmittedOk		= 0x0006,
	FramesRcvdOk		= 0x0007,
	FramesDeferred		= 0x0008,
	UpperFramesOk		= 0x0009,
	BytesRcvdOk		= 0x000A,
	BytesXmittedOk		= 0x000C,
};

enum {						/* Window 7 - bus master operations */
	Wmaster			= 0x0007,
						/* registers */
	MasterAddress		= 0x0000,
	MasterLen		= 0x0006,
	MasterStatus		= 0x000C,
						/* MasterStatus bits */
	masterAbort		= 0x0001,
	targetAbort		= 0x0002,
	targetRetry		= 0x0004,
	targetDisc		= 0x0008,
	masterDownload		= 0x1000,
	masterUpload		= 0x4000,
	masterInProgress	= 0x8000,

	masterMask		= 0xD00F,
};	

typedef struct {
	Lock	wlock;				/* window access */

	int	attached;
	int	busmaster;
	Block*	rbp[2];				/* receive buffers */
	int	rbpix;

	Block*	txqhead;			/* transmit queue */
	Block*	txqtail;
	int	txthreshold;
	int	txbusy;

	long	interrupts;			/* statistics */
	long	timer;
	
	long	carrierlost;
	long	sqeerrors;
	long	multiplecolls;
	long	singlecollframes;
	long	latecollisions;
	long	rxoverruns;
	long	framesxmittedok;
	long	framesrcvdok;
	long	framesdeferred;
	long	bytesrcvdok;
	long	bytesxmittedok;
	long	badssd;

	int	xcvr;				/* transceiver type */
	int	rxstatus9;			/* old-style RxStatus register */
} Ctlr;

static Block*
allocrbp(void)
{
	Block *bp;
	ulong addr;

	/*
	 * The receive buffers must be on a 32-byte
	 * boundary for EISA busmastering.
	 */
	bp = allocb(ROUNDUP(sizeof(Etherpkt), 4) + 31);
	addr = (ulong)bp->base;
	addr = ROUNDUP(addr, 32);
	bp->rp = (uchar*)addr;

	return bp;
}

static uchar*
startdma(Ether* ether, ulong address)
{
	int port, status, w;
	uchar *wp;

	port = ether->port;

	w = (STATUS(port)>>13) & 0x07;
	COMMAND(port, SelectRegisterWindow, Wmaster);

	wp = KADDR(inl(port+MasterAddress));
	status = ins(port+MasterStatus);
	if(status & (masterInProgress|targetAbort|masterAbort))
		print("elnk3#%d: BM status 0x%uX\n", ether->ctlrno, status);
	outs(port+MasterStatus, masterMask);
	outl(port+MasterAddress, address);
	outs(port+MasterLen, sizeof(Etherpkt));
	COMMAND(port, StartDma, Upload);

	COMMAND(port, SelectRegisterWindow, w);
	return wp;
}

static void
promiscuous(void* arg, int on)
{
	int filter, port;

	port = ((Ether*)arg)->port;
	
	filter = receiveBroadcast|receiveIndividual;
	if(on)
		filter |= receiveAllFrames;
	COMMAND(port, SetRxFilter, filter);
}

static void
attach(Ether* ether)
{
	int port, x;
	Ctlr *ctlr;

	ctlr = ether->ctlr;
	ilock(&ctlr->wlock);
	if(ctlr->attached){
		iunlock(&ctlr->wlock);
		return;
	}

	port = ether->port;

	/*
	 * Set the receiver packet filter for this and broadcast addresses,
	 * set the interrupt masks for all interrupts, enable the receiver
	 * and transmitter.
	 */
	promiscuous(ether, ether->prom);

	x = interruptMask|interruptLatch;
	if(ctlr->busmaster)
		x &= ~(rxEarly|rxComplete);
	COMMAND(port, SetIndicationEnable, x);
	COMMAND(port, SetInterruptEnable, x);

	COMMAND(port, RxEnable, 0);
	COMMAND(port, TxEnable, 0);

	/*
	 * Prime the busmaster channel for receiving directly into a
	 * receive packet buffer if necessary.
	 */
	ctlr->rbpix = 0;
	if(ctlr->busmaster)
		startdma(ether, PADDR(ctlr->rbp[ctlr->rbpix]->rp));

	ctlr->attached = 1;
	iunlock(&ctlr->wlock);
}

static void
statistics(Ether* ether)
{
	int port, u, w;
	Ctlr *ctlr;

	port = ether->port;
	ctlr = ether->ctlr;

	/*
	 * 3C59[27] require a read between a PIO write and
	 * reading a statistics register.
	 */
	w = (STATUS(port)>>13) & 0x07;
	COMMAND(port, SelectRegisterWindow, Wstatistics);
	STATUS(port);

	ctlr->carrierlost += inb(port+CarrierLost) & 0xFF;
	ctlr->sqeerrors += inb(port+SqeErrors) & 0xFF;
	ctlr->multiplecolls += inb(port+MultipleColls) & 0xFF;
	ctlr->singlecollframes += inb(port+SingleCollFrames) & 0xFF;
	ctlr->latecollisions += inb(port+LateCollisions) & 0xFF;
	ctlr->rxoverruns += inb(port+RxOverruns) & 0xFF;
	ctlr->framesxmittedok += inb(port+FramesXmittedOk) & 0xFF;
	ctlr->framesrcvdok += inb(port+FramesRcvdOk) & 0xFF;
	u = inb(port+UpperFramesOk) & 0xFF;
	ctlr->framesxmittedok += (u & 0x30)<<4;
	ctlr->framesrcvdok += (u & 0x03)<<8;
	ctlr->framesdeferred += inb(port+FramesDeferred) & 0xFF;
	ctlr->bytesrcvdok += ins(port+BytesRcvdOk) & 0xFFFF;
	ctlr->bytesxmittedok += ins(port+BytesXmittedOk) & 0xFFFF;

	if(ctlr->xcvr == xcvr100BaseTX || ctlr->xcvr == xcvr100BaseFX){
		COMMAND(port, SelectRegisterWindow, Wdiagnostic);
		STATUS(port);
		ctlr->badssd += inb(port+BadSSD);
	}

	COMMAND(port, SelectRegisterWindow, w);
}

static void
transmit(Ether* ether)
{
	int port, len;
	Ctlr *ctlr;
	Block *bp;

	port = ether->port;
	ctlr = ether->ctlr;

	/*
	 * Attempt to top-up the transmit FIFO. If there's room simply
	 * stuff in the packet length (unpadded to a dword boundary), the
	 * packet data (padded) and remove the packet from the queue.
	 * If there's no room post an interrupt for when there is.
	 * This routine is called both from the top level and from interrupt
	 * level and expects to be called with ctlr->wlock already locked
	 * and the correct register window (Wop) in place.
	 */
	while(bp = ctlr->txqhead){
		len = ROUNDUP(BLEN(bp), 4);
		if(len+4 <= ins(port+TxFree)){
			outl(port+Fifo, BLEN(bp));
			outsl(port+Fifo, bp->rp, len/4);

			ctlr->txqhead = bp->next;
			freeb(bp);

			ether->outpackets++;
		}
		else if(ctlr->txbusy == 0){
			ctlr->txbusy = 1;
			COMMAND(port, SetTxAvailableThresh, len);
			return;
		}
	}
}

static long
write(Ether* ether, void* buf, long n)
{
	Ctlr *ctlr;
	Block *bp;
	int port, w;

	port = ether->port;
	ctlr = ether->ctlr;

	/*
	 * Pack the write request up in a buffer, give it a source address
	 * and place it on the end of the transmit queue. The data written to the
	 * FIFO must be padded to a dword boundary, hence the ROUNDUP allocation.
	 * Call transmit() to stuff it into the TxFIFO if possible. 
	 */
	bp = allocb(ROUNDUP(n, 4));
	memmove(bp->wp, buf, n);
	memmove(bp->wp+Eaddrlen, ether->ea, Eaddrlen);
	bp->wp += n;

	ilock(&ctlr->wlock);
	w = (STATUS(port)>>13) & 0x07;
	COMMAND(port, SelectRegisterWindow, Wop);
	if(ctlr->txqhead == 0)
		ctlr->txqhead = bp;
	else
		ctlr->txqtail->next = bp;
	ctlr->txqtail = bp;
	if(ctlr->txbusy == 0)
		transmit(ether);
	COMMAND(port, SelectRegisterWindow, w);
	iunlock(&ctlr->wlock);

	return n;
}

static void
receive(Ether* ether)
{
	int len, port, rxerror, rxstatus;
	Ctlr *ctlr;
	Block *bp;

	port = ether->port;
	ctlr = ether->ctlr;

	while(((rxstatus = ins(port+RxStatus)) & rxIncomplete) == 0){
		if(ctlr->busmaster && (STATUS(port) & busMasterInProgress))
			break;

		/*
		 * If there was an error, log it and continue.
		 * Unfortunately the 3C5[078]9 has the error info in the status register
		 * and the 3C59[0257] implement a separate RxError register.
		 */
		if(rxstatus & rxError){
			if(ctlr->rxstatus9){
iprint("R%uX|", rxstatus);
				switch(rxstatus & rxError9){

				case rxOverrun9:
					ether->overflows++;
					break;

				case oversizedFrame9:
				case runtFrame9:
					ether->buffs++;
					break;

				case alignmentError9:
					ether->frames++;
					break;

				case crcError9:
					ether->crcs++;
					break;

				}
			}
			else{
				rxerror = inb(port+RxError);
iprint("R%uX|", rxerror);
				if(rxerror & rxOverrun)
					ether->overflows++;
				if(rxerror & (oversizedFrame|runtFrame))
					ether->buffs++;
				if(rxerror & alignmentError)
					ether->frames++;
				if(rxerror & crcError)
					ether->crcs++;
			}

			COMMAND(port, RxDiscard, 0);
			while(STATUS(port) & commandInProgress)
				;

			if(ctlr->busmaster)
				startdma(ether, PADDR(ctlr->rbp[ctlr->rbpix]->rp));
		}
		else{
			ether->inpackets++;
			bp = ctlr->rbp[ctlr->rbpix];

			if(ctlr->busmaster == 0){
				len = (rxstatus & rxBytes9);
				bp->wp = bp->rp + len;
				insl(port+Fifo, bp->rp, HOWMANY(len, 4));
			}

			COMMAND(port, RxDiscard, 0);
			while(STATUS(port) & commandInProgress)
				;

			if(ctlr->busmaster){
				ctlr->rbpix ^= 1;
				bp->wp = startdma(ether, PADDR(ctlr->rbp[ctlr->rbpix]->rp));
			}

			etherrloop(ether, (Etherpkt*)bp->rp, BLEN(bp));
		}
	}
}

static void
interrupt(Ureg*, void* arg)
{
	Ether *ether;
	int port, status, txstatus, w, x;
	Ctlr *ctlr;

	ether = arg;
	port = ether->port;
	ctlr = ether->ctlr;

	lock(&ctlr->wlock);
	w = (STATUS(port)>>13) & 0x07;
	COMMAND(port, SelectRegisterWindow, Wop);

	ctlr->interrupts++;
	ctlr->timer += inb(port+Timer) & 0xFF;
	for(;;){
		/*
		 * Clear the interrupt latch.
		 * It's possible to receive a packet and for another
		 * to become complete before exiting the interrupt
		 * handler so this must be done first to ensure another
		 * interrupt will occur.
		 */
		COMMAND(port, AcknowledgeInterrupt, interruptLatch);
		status = STATUS(port);
		if((status & interruptMask) == 0)
			break;

		if(status & hostError){
			/*
			 * Adapter failure, try to find out why, reset if
			 * necessary. What happens if Tx is active and a reset
			 * occurs, need to retransmit? This probably isn't right.
			 */
			COMMAND(port, SelectRegisterWindow, Wdiagnostic);
			x = ins(port+FifoDiagnostic);
			COMMAND(port, SelectRegisterWindow, Wop);
			print("elnk3#%d: status 0x%uX, diag 0x%uX\n",
			    ether->ctlrno, status, x);

			if(x & txOverrun){
				COMMAND(port, TxReset, 0);
				COMMAND(port, TxEnable, 0);
				wakeup(&ether->tr);
			}

			if(x & rxUnderrun){
				/*
				 * This shouldn't happen...
				 * Need to restart any busmastering?
				 */
				COMMAND(port, RxReset, 0);
				while(STATUS(port) & commandInProgress)
					;
				COMMAND(port, RxEnable, 0);
			}

			status &= ~hostError;
		}

		if(status & (transferInt|rxComplete)){
			receive(ether);
			status &= ~(transferInt|rxComplete);
		}

		if(status & txComplete){
			/*
			 * Pop the TxStatus stack, accumulating errors.
			 * Adjust the TX start threshold if there was an underrun.
			 * If there was a Jabber or Underrun error, reset
			 * the transmitter.
			 * For all conditions enable the transmitter.
			 */
			txstatus = 0;
			do{
				if(x = inb(port+TxStatus))
					outb(port+TxStatus, 0);
				txstatus |= x;
			}while(STATUS(port) & txComplete);

			if(txstatus & txUnderrun){
				COMMAND(port, SelectRegisterWindow, Wdiagnostic);
				while(ins(port+MediaStatus) & txInProg)
					;
				COMMAND(port, SelectRegisterWindow, Wop);
				if(ctlr->txthreshold < ETHERMAXTU)
					ctlr->txthreshold += ETHERMINTU;
			}

			if(txstatus & (txJabber|txUnderrun)){
				COMMAND(port, TxReset, 0);
				while(STATUS(port) & commandInProgress)
					;
				COMMAND(port, SetTxStartThresh, ctlr->txthreshold);
			}
			COMMAND(port, TxEnable, 0);
			ether->oerrs++;
			status &= ~txComplete;
			status |= txAvailable;
		}

		if(status & txAvailable){
			COMMAND(port, AcknowledgeInterrupt, txAvailable);
			ctlr->txbusy = 0;
			transmit(ether);
			status &= ~txAvailable;
		}

		if(status & updateStats){
			statistics(ether);
			status &= ~updateStats;
		}

		/*
		 * Panic if there are any interrupts not dealt with.
		 */
		if(status & interruptMask)
			panic("elnk3#%d: interrupt mask 0x%uX\n", ether->ctlrno, status);
	}

	COMMAND(port, SelectRegisterWindow, w);
	unlock(&ctlr->wlock);
}

static long
ifstat(Ether* ether, void* a, long n, ulong offset)
{
	Ctlr *ctlr;
	char buf[512];
	int len;

	if(n == 0)
		return 0;

	ctlr = ether->ctlr;

	ilock(&ctlr->wlock);
	statistics(ether);
	iunlock(&ctlr->wlock);

	len = sprint(buf, "interrupts: %ld\n", ctlr->interrupts);
	len += sprint(buf+len, "timer: %ld\n", ctlr->timer);
	len += sprint(buf+len, "carrierlost: %ld\n", ctlr->carrierlost);
	len += sprint(buf+len, "sqeerrors: %ld\n", ctlr->sqeerrors);
	len += sprint(buf+len, "multiplecolls: %ld\n", ctlr->multiplecolls);
	len += sprint(buf+len, "singlecollframes: %ld\n", ctlr->singlecollframes);
	len += sprint(buf+len, "latecollisions: %ld\n", ctlr->latecollisions);
	len += sprint(buf+len, "rxoverruns: %ld\n", ctlr->rxoverruns);
	len += sprint(buf+len, "framesxmittedok: %ld\n", ctlr->framesxmittedok);
	len += sprint(buf+len, "framesrcvdok: %ld\n", ctlr->framesrcvdok);
	len += sprint(buf+len, "framesdeferred: %ld\n", ctlr->framesdeferred);
	len += sprint(buf+len, "bytesrcvdok: %ld\n", ctlr->bytesrcvdok);
	len += sprint(buf+len, "bytesxmittedok: %ld\n", ctlr->bytesxmittedok);
	sprint(buf+len, "badssd: %ld\n", ctlr->badssd);

	return readstr(offset, a, n, buf);
}

typedef struct Adapter Adapter;
struct Adapter {
	Adapter*	next;
	int		port;
	int		irq;
};
static Adapter *adapter;

/*
 * Write two 0 bytes to identify the IDport and then reset the
 * ID sequence. Then send the ID sequence to the card to get
 * the card into command state.
 */
static void
idseq(void)
{
	int i;
	uchar al;
	static int reset, untag;

	/*
	 * One time only:
	 *	reset any adapters listening
	 */
	if(reset == 0){
		outb(IDport, 0);
		outb(IDport, 0);
		outb(IDport, 0xC0);
		delay(20);
		reset = 1;
	}

	outb(IDport, 0);
	outb(IDport, 0);
	for(al = 0xFF, i = 0; i < 255; i++){
		outb(IDport, al);
		if(al & 0x80){
			al <<= 1;
			al ^= 0xCF;
		}
		else
			al <<= 1;
	}

	/*
	 * One time only:
	 *	write ID sequence to get the attention of all adapters;
	 *	untag all adapters.
	 * If we do a global reset here on all adapters we'll confuse any
	 * ISA cards configured for EISA mode.
	 */
	if(untag == 0){
		outb(IDport, 0xD0);
		untag = 1;
	}
}

static ulong
activate(void)
{
	int i;
	ushort x, acr;

	/*
	 * Do the little configuration dance:
	 *
	 * 2. write the ID sequence to get to command state.
	 */
	idseq();

	/*
	 * 3. Read the Manufacturer ID from the EEPROM.
	 *    This is done by writing the IDPort with 0x87 (0x80
	 *    is the 'read EEPROM' command, 0x07 is the offset of
	 *    the Manufacturer ID field in the EEPROM).
	 *    The data comes back 1 bit at a time.
	 *    We seem to need a delay here between reading the bits.
	 *
	 * If the ID doesn't match, there are no more adapters.
	 */
	outb(IDport, 0x87);
	delay(20);
	for(x = 0, i = 0; i < 16; i++){
		delay(20);
		x <<= 1;
		x |= inb(IDport) & 0x01;
	}
	if(x != 0x6D50)
		return 0;

	/*
	 * 3. Read the Address Configuration from the EEPROM.
	 *    The Address Configuration field is at offset 0x08 in the EEPROM).
	 */
	outb(IDport, 0x88);
	for(acr = 0, i = 0; i < 16; i++){
		delay(20);
		acr <<= 1;
		acr |= inb(IDport) & 0x01;
	}

	return (acr & 0x1F)*0x10 + 0x200;
}

static ulong
tcm509isa(Ether* ether)
{
	int irq, port;
	Adapter *ap;

	/*
	 * Attempt to activate adapters until one matches the
	 * address criteria. If adapter is set for EISA mode (0x3F0),
	 * tag it and ignore. Otherwise, activate it fully.
	 */
	while(port = activate()){
		/*
		 * 6. Tag the adapter so it won't respond in future.
		 */
		outb(IDport, 0xD1);
		if(port == 0x3F0)
			continue;

		/*
		 * 6. Activate the adapter by writing the Activate command
		 *    (0xFF).
		 */
		outb(IDport, 0xFF);
		delay(20);

		/*
		 * 8. Can now talk to the adapter's I/O base addresses.
		 *    Use the I/O base address from the acr just read.
		 *
		 *    Enable the adapter and clear out any lingering status
		 *    and interrupts.
		 */
		while(STATUS(port) & commandInProgress)
			;
		COMMAND(port, SelectRegisterWindow, Wsetup);
		outs(port+ConfigControl, Ena);

		COMMAND(port, TxReset, 0);
		COMMAND(port, RxReset, 0);
		COMMAND(port, AcknowledgeInterrupt, 0xFF);

		irq = (ins(port+ResourceConfig)>>12) & 0x0F;
		if(ether->port == 0 || ether->port == port){
			ether->irq = irq;
			return port;
		}

		ap = malloc(sizeof(Adapter));
		ap->port = port;
		ap->irq = irq;
		ap->next = adapter;
		adapter = ap;
	}

	return 0;
}

static int
tcm5XXeisa(Ether* ether)
{
	static int slot = 1;
	ushort x;
	int irq, port;
	Adapter *ap;

	/*
	 * First time through, check if this is an EISA machine.
	 * If not, nothing to do.
	 */
	if(slot == 1 && strncmp((char*)(KZERO|0xFFFD9), "EISA", 4))
		return 0;

	/*
	 * Continue through the EISA slots looking for a match on both
	 * 3COM as the manufacturer and 3C579-* or 3C59[27]-* as the product.
	 * If we find an adapter, select window 0, enable it and clear
	 * out any lingering status and interrupts.
	 */
	while(slot < MaxEISA){
		port = slot++*0x1000;
		if(ins(port+0xC80+ManufacturerID) != 0x6D50)
			continue;
		x = ins(port+0xC80+ProductID);
		if((x & 0xF0FF) != 0x9050 && (x & 0xFF00) != 0x5900)
			continue;

		COMMAND(port, SelectRegisterWindow, Wsetup);
		outs(port+ConfigControl, Ena);

		COMMAND(port, TxReset, 0);
		COMMAND(port, RxReset, 0);
		COMMAND(port, AcknowledgeInterrupt, 0xFF);

		irq = (ins(port+ResourceConfig)>>12) & 0x0F;
		if(ether->port == 0 || ether->port == port){
			ether->irq = irq;
			return port;
		}

		ap = malloc(sizeof(Adapter));
		ap->port = port;
		ap->irq = irq;
		ap->next = adapter;
		adapter = ap;
	}

	return 0;
}

static int
tcm59Xpci(Ether* ether)
{
	PCIcfg pcicfg;
	static int devno = 0;
	int irq, port;
	Adapter *ap;

	for(;;){
		pcicfg.vid = 0x10B7;
		pcicfg.did = 0;
		if((devno = pcimatch(0, devno, &pcicfg)) == -1)
			break;
		port = pcicfg.baseaddr[0] & ~0x01;
		COMMAND(port, GlobalReset, 0);
		while(STATUS(port) & commandInProgress)
			;
		irq = pcicfg.irq;
		if(ether->port == 0 || ether->port == port){
			ether->irq = irq;
			return port;
		}

		ap = malloc(sizeof(Adapter));
		ap->port = port;
		ap->irq = irq;
		ap->next = adapter;
		adapter = ap;
	}

	return 0;
}

static int
tcm5XXpcmcia(Ether* ether)
{
	if(cistrcmp(ether->type, "3C589") == 0)
		return ether->port;

	return 0;
}

int
etherelnk3reset(Ether* ether)
{
	int busmaster, i, port, rxstatus9, x, xcvr;
	Adapter *ap, **app;
	uchar ea[Eaddrlen];
	Ctlr *ctlr;

	/*
	 * Any adapter matches if no ether->port is supplied, otherwise the
	 * ports must match. First see if an adapter that fits the bill has
	 * already been found. If not, scan for adapter on PCI, EISA and finally
	 * using the little ISA configuration dance. The EISA and ISA scan
	 * routines leave Wsetup mapped.
	 * If an adapter is found save the IRQ and transceiver type.
	 */
	port = 0;
	rxstatus9 = 0;
	xcvr = 0;
	for(app = &adapter, ap = *app; ap; app = &ap->next, ap = ap->next){
		if(ether->port == 0 || ether->port == ap->port){
			port = ap->port;
			ether->irq = ap->irq;
			*app = ap->next;
			free(ap);
			break;
		}
	}
	if(port == 0 && (port = tcm5XXpcmcia(ether))){
		xcvr = ((ins(port+AddressConfig) & xcvrMask9)>>14)<<20;
		rxstatus9 = 1;
	}
	else if(port == 0 && (port = tcm59Xpci(ether))){
		COMMAND(port, SelectRegisterWindow, Wfifo);
		xcvr = inl(port+InternalConfig) & xcvrMask;
	}
	else if(port == 0 && (port = tcm5XXeisa(ether))){
		x = ins(port+ProductID);
		if((x & 0xFF00) == 0x5900){
			COMMAND(port, SelectRegisterWindow, Wfifo);
			xcvr = inl(port+InternalConfig) & xcvrMask;
		}
		else{
			xcvr = ((ins(port+AddressConfig) & xcvrMask9)>>14)<<20;
			rxstatus9 = 1;
		}
	}
	else if(port == 0 && (port = tcm509isa(ether))){
		xcvr = ((ins(port+AddressConfig) & xcvrMask9)>>14)<<20;
		rxstatus9 = 1;
	}

	if(port == 0)
		return -1;

	/*
	 * 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 Wstation. The EEPROM returns 16-bits at a time.
	 */
	memset(ea, 0, Eaddrlen);
	if(memcmp(ea, ether->ea, Eaddrlen) == 0){
		COMMAND(port, SelectRegisterWindow, Wsetup);
		while(EEPROMBUSY(port))
			;
		for(i = 0; i < Eaddrlen/2; i++){
			EEPROMCMD(port, EepromReadRegister, i);
			while(EEPROMBUSY(port))
				;
			x = EEPROMDATA(port);
			ether->ea[2*i] = (x>>8) & 0xFF;
			ether->ea[2*i+1] = x & 0xFF;
		}
	}

	COMMAND(port, SelectRegisterWindow, Wstation);
	for(i = 0; i < Eaddrlen; i++)
		outb(port+i, ether->ea[i]);

	/*
	 * Enable the transceiver if necessary and determine whether
	 * busmastering can be used. Due to bugs in the first revision
	 * of the 3C59[05], don't use busmastering at 10Mbps.
	 */
	COMMAND(port, SelectRegisterWindow, Wdiagnostic);
	x = ins(port+MediaStatus) & ~(linkBeatEnable|jabberGuardEnable);
	outs(port+MediaStatus, x);
	if(x & dataRate100)
		busmaster = 1;
	else
		busmaster = 0;
	switch(xcvr){

	case xcvr100BaseTX:
	case xcvr100BaseFX:
		ether->mbps = 100;
		/*FALLTHROUGH*/
	case xcvr10BaseT:
		/*
		 * Enable Link Beat and Jabber to start the
		 * transceiver.
		 */
		x |= linkBeatEnable|jabberGuardEnable;
		outs(port+MediaStatus, x);
		break;

	case xcvr10Base2:
		/*
		 * Start the DC-DC converter.
		 * Wait > 800 microseconds.
		 */
		COMMAND(port, EnableDcConverter, 0);
		delay(1);
		break;
	}

	/*
	 * Wop is the normal operating register set.
	 * The 3C59[0257] adapters allow access to more than one register window
	 * at a time, but there are situations where switching still needs to be
	 * done, so just do it.
	 * Clear out any lingering Tx status.
	 */
	COMMAND(port, SelectRegisterWindow, Wop);
	while(inb(port+TxStatus))
		outb(port+TxStatus, 0);

	/*
	 * Allocate a controller structure, clear out the
	 * adapter statistics, clear the statistics logged into ctlr
	 * and enable statistics collection. Xcvr is needed in order
	 * to collect the BadSSD statistics.
	 */
	ether->ctlr = malloc(sizeof(Ctlr));
	ctlr = ether->ctlr;

	ilock(&ctlr->wlock);
	ctlr->xcvr = xcvr;
	statistics(ether);
	memset(ctlr, 0, sizeof(Ctlr));

	ctlr->busmaster = busmaster;
	ctlr->xcvr = xcvr;
	ctlr->rxstatus9 = rxstatus9;

	COMMAND(port, StatisticsEnable, 0);

	/*
	 * Allocate the receive buffers.
	 */
	ctlr->rbpix = 0;
	ctlr->rbp[0] = allocrbp();
	if(ctlr->busmaster)
		ctlr->rbp[1] = allocrbp();

	/*
	 * Set a base TxStartThresh which will be incremented
	 * if any txUnderrun errors occur and ensure no RxEarly
	 * interrupts happen.
	 */
	ctlr->txthreshold = ETHERMINTU;
	COMMAND(port, SetTxStartThresh, ETHERMINTU);
	COMMAND(port, SetRxEarlyThresh, ETHERMAXTU);

	iunlock(&ctlr->wlock);

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

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

	return 0;
}

void
etherelnk3link(void)
{
	addethercard("elnk3",  etherelnk3reset);
	addethercard("3C509",  etherelnk3reset);
}

M pc/etherif.h => pc/etherif.h +2 -1
@@ 1,5 1,5 @@
enum {
	MaxEther	= 8,
	MaxEther	= 24,
	Ntypes		= 8,
};



@@ 12,6 12,7 @@ struct Ether {
	void	(*attach)(Ether*);	/* filled in by reset routine */
	long	(*write)(Ether*, void*, long);
	void	(*interrupt)(Ureg*, void*);
	long	(*ifstat)(Ether*, void*, long, ulong);
	void	*ctlr;

	Etherpkt tpkt;			/* transmit buffer */

M pc/fns.h => pc/fns.h +0 -4
@@ 129,7 129,3 @@ ulong	ifunroute(ulong);
void	parseip(char*, char*);

#define	dcflush(a, b)

int scsibtissue(int, int, uchar*, int, uchar*, int);
Scsiio scsibt24(int, int, ISAConf*);
Scsiio scsibt32(int, int, ISAConf*);

M pc/scsi.c => pc/scsi.c +92 -15
@@ 78,7 78,7 @@ scsireset(void)
			if((ctlr->io = (*lp->reset)(ctlrno, ctlr)) == 0)
				break;

			print("scsi%d: %s: port 0x%luX irq %d",
			print("scsi#%d: %s: port 0x%luX irq %d",
				ctlrno, ctlr->type, ctlr->port,
				ctlr->irq, ctlr->mem, ctlr->size);
			if(ctlr->mem)


@@ 106,7 106,29 @@ scsireset(void)
int
scsiexec(Target *t, int rw, uchar *cmd, int cbytes, void *data, int *dbytes)
{
	return (*scsi[t->ctlrno]->io)(t, rw, cmd, cbytes, data, dbytes);
	int s;

	/*
	 * Call the device-specific I/O routine.
	 * There should be no calls to 'error()' below this
	 * which percolate back up.
	 */
	switch(s = (*scsi[t->ctlrno]->io)(t, rw, cmd, cbytes, data, dbytes)){

	default:
		/*
		 * It's more complicated than this. There are conditions which
		 * are 'ok' but for which the returned status code is not 'STok'.
		 * Also, not all conditions require a reqsense, there may be a
		 * need to do a reqsense here when necessary and making it
		 * available to the caller somehow.
		 *
		 * Later.
		 */
		break;
	}

	return s;
}

Target*


@@ 143,7 165,7 @@ scsiprobe(Ctlr *ctlr)
		nbytes = Nscratch;
		s = scsireqsense(t, 0, t->scratch, &nbytes, 0);
		if(s != STok){
			print("scsi%d: unit %d unavailable, status %d\n", t->ctlrno, i, s);
			print("scsi#%d: unit %d unavailable, status %d\n", t->ctlrno, i, s);
			continue;
		}



@@ 154,11 176,11 @@ scsiprobe(Ctlr *ctlr)
		memset(t->inq, 0, Ninq);
		nbytes = Ninq;
		s = scsiinquiry(t, 0, t->inq, &nbytes);
		if(s < 0) {
			print("scsi%d: unit %d inquire failed, status %d\n", t->ctlrno, i, s);
		if(s != STok) {
			print("scsi#%d: unit %d inquire failed, status %d\n", t->ctlrno, i, s);
			continue;
		}
		print("scsi%d: unit %d: %s\n", t->ctlrno, i, t->inq+8);
		print("scsi#%d: unit %d: %s\n", t->ctlrno, i, t->inq+8);
		t->ok = 1;
	}
}


@@ 264,7 286,7 @@ scsicap(Target *t, char lun, ulong *size, ulong *bsize)
	nbytes = 8;
	d = scsialloc(nbytes);
	if(d == 0)
		error(Enomem);
		return -1;

	s = scsiexec(t, SCSIread, cmd, sizeof(cmd), d, &nbytes);
	if(s == STok) {


@@ 302,10 324,10 @@ scsibio(Target *t, char lun, int dir, void *b, long n, long bsize, long bno)
	}
	nbytes = n*bsize;
	s = scsiexec(t, dir, cmd, cdbsiz, b, &nbytes);
	if(s < 0) {
	if(s != STok) {
		nbytes = Nscratch;
		scsireqsense(t, lun, t->scratch, &nbytes, 0);
		return -1;
		return scsierrstr(s);
	}
	return nbytes;
}


@@ 391,7 413,7 @@ scsireqsense(Target *t, char lun, void *data, int *nbytes, int quiet)
buggery:
	if(quiet == 0){
		s = key[sense[2]&0x0F];
		print("scsi%d: unit %d reqsense: '%s' code #%2.2ux #%2.2ux\n",
		print("scsi#%d: unit %d reqsense: '%s' code #%2.2ux #%2.2ux\n",
			t->ctlrno, t->target, s, sense[12], sense[13]);
	}
	free(sense);


@@ 414,7 436,7 @@ scsidiskinfo(Target *t, char lun, int track, uchar *data)

	d = scsialloc(nbytes);
	if(d == 0)
		error(Enomem);
		return scsierrstr(STnomem);

	memset(d, 0, nbytes);
	s = scsiexec(t, SCSIread, cmd, sizeof(cmd), d, &nbytes);


@@ 439,7 461,7 @@ scsitrackinfo(Target *t, char lun, int track, uchar *data)

	d = scsialloc(nbytes);
	if(d == 0)
		error(Enomem);
		return scsierrstr(STnomem);

	memset(d, 0, nbytes);
	s = scsiexec(t, SCSIread, cmd, sizeof(cmd), d, &nbytes);


@@ 465,7 487,7 @@ scsibufsize(Target *t, char lun, int size)

	d = scsialloc(nbytes);
	if(d == 0)
		error(Enomem);
		return scsierrstr(STnomem);

	memset(d, 0, nbytes);
	d[3] = 8;


@@ 491,10 513,65 @@ scsireadcdda(Target *t, char lun, int, void *b, long n, long bsize, long bno)

	nbytes = n*bsize;
	s = scsiexec(t, SCSIread, cmd, sizeof(cmd), b, &nbytes);
	if(s < 0) {
	if(s != STok) {
		nbytes = Nscratch;
		scsireqsense(t, lun, t->scratch, &nbytes, 0);
		return -1;
		return scsierrstr(s);
	}
	return nbytes;
}

int
scsierrstr(int errno)
{
	char *p;

	switch(errno){
	case STnomem:
		p = Enomem;
		break;
	case STtimeout:
		p = "bus timeout";
		break;
	case STownid:
		p = "playing with myself";
		break;
	case STharderr:
		p = Eio;
		break;
	case STok:
		p = Enoerror;
		break;
	case STcheck:
		p = "check condition";
		break;
	case STcondmet:
		p = "condition met/good";
		break;
	case STbusy:
		p = "busy";
		break;
	case STintok:
		p = "intermediate/good";
		break;
	case STintcondmet:
		p = "intermediate/condition met/good";
		break;
	case STresconf:
		p = "reservation conflict";
		break;
	case STterminated:
		p = "command terminated";
		break;
	case STqfull:
		p = "queue full";
		break;

	default:
		p = "unknown SCSI error";
		break;
	}
	strncpy(up->error, p, NAMELEN);

	return -1;
}

M port/devsd.c => port/devsd.c +71 -53
@@ 47,6 47,8 @@ struct Disk
	char	vol[NAMELEN];

	uchar*	inquire;
	int	partok;
	ulong	version;
	ulong	size;
	ulong	bsize;



@@ 57,7 59,7 @@ struct Disk
int	ndisk;
Disk	disk[Ndisk];

static	void	sdrdpart(Disk*);
static	int	sdrdpart(Disk*);
static	long	sdio(Chan*, int, char*, ulong, ulong);

static int types[] =


@@ 137,9 139,6 @@ sdinit(void)
			 * their act together after a reset.
			 * If 'not ready' comes back, try starting a TypeDA and punt
			 * the get capacity until the drive is attached.
			 * It might be possible to be smarter here, and look at the
			 * response from a test-unit-ready which would show if the
			 * target was in the process of becoming ready.
			 */
			if(scsicap(d->t, d->lun, &d->size, &d->bsize) != STok) {
				nbytes = sizeof(scratch);


@@ 187,8 186,12 @@ sdattach(char *spec)
{
	int i;

	for(i = 0; i < ndisk; i++)
		sdrdpart(&disk[i]);
	for(i = 0; i < ndisk; i++){
		qlock(&disk[i]);
		if(disk[i].partok == 0)
			sdrdpart(&disk[i]);
		qunlock(&disk[i]);
	}
	
	return devattach('w', spec);
}


@@ 246,8 249,12 @@ sdclose(Chan *c)

	d = &disk[DRIVE(c->qid)];
	p = &d->table[PART(c->qid)];
	if((c->mode&3) != OREAD && strcmp(p->name, "partition") == 0)
	if((c->mode&3) != OREAD && strcmp(p->name, "partition") == 0){
		qlock(d);
		d->partok = 0;
		sdrdpart(d);
		qunlock(d);
	}
}

long


@@ 268,6 275,12 @@ sdbread(Chan *c, long n, ulong offset)
long
sdwrite(Chan *c, char *a, long n, ulong offset)
{
	Disk *d;

	d = &disk[DRIVE(c->qid)];

	if((d->inquire[0] & 0x1F) == TypeCD)
		error(Eperm);
	return sdio(c, 1, a, n, offset);
}



@@ 277,7 290,7 @@ sdbwrite(Chan *c, Block *bp, ulong offset)
	return devbwrite(c, bp, offset);
}

static void
static int
sdrdpart(Disk *d)
{
	Part *p;


@@ 285,30 298,36 @@ sdrdpart(Disk *d)
	char *b, *line[Npart+2], *field[3];
	static char MAGIC[] = "plan9 partitions";

	if(d->partok)
		return STok;

	p = d->table;
	strcpy(p->name, "disk");
	p->beg = 0;
	p->end = 0;
	d->npart = 1;

	/*
	 * If the drive wasn't ready when we tried to do a
	 * read-capacity earlier (in sdinit()), try again.
	 * It might be possible to be smarter here, and look at the
	 * response from a test-unit-ready which would show if the
	 * target was in the process of becoming ready.
	 * If the drive wasn't ready when a read-capacity was tried
	 * earlier (in sdinit()), try again.
	 */
	if(d->size == 0 || d->bsize == 0){
		if(scsicap(d->t, d->lun, &d->size, &d->bsize) != STok){
		n = scsicap(d->t, d->lun, &d->size, &d->bsize);
		if(n != STok){
			d->size = d->bsize = 0;
			error(Eio);
			return scsierrstr(n);
		}
	}

	p->end = d->size + 1;

	if((d->inquire[0] & 0x1F) == TypeCD)
		return STok;

	b = scsialloc(d->bsize);
	if(b == 0)
		error(Enomem);
	qlock(d);
	
	p = d->table;
		return scsierrstr(STnomem);

	strcpy(p->name, "disk");
	p->beg = 0;
	p->end = d->size + 1;
	p++;
	strcpy(p->name, "partition");
	p->beg = d->table[0].end - 1;


@@ 316,12 335,6 @@ sdrdpart(Disk *d)
	p++;
	d->npart = 2;

	if((d->inquire[0] & 0x1F) == TypeCD){
		scsifree(b);
		qunlock(d);
		return;
	}

	scsibio(d->t, d->lun, SCSIread, b, 1, d->bsize, d->table[0].end-1);
	b[d->bsize-1] = '\0';



@@ 331,26 344,24 @@ sdrdpart(Disk *d)
	n = parsefields(b, line, Npart+2, "\n");
	if(n > 0 && strncmp(line[0], MAGIC, sizeof(MAGIC)-1) == 0) {
		for(i = 1; i < n; i++) {
			switch(parsefields(line[i], field, 3, " ")) {
			case 2:
				if(strcmp(field[0], "unit") == 0)
					strncpy(d->vol, field[1], NAMELEN);
				break;	
			case 3:
				if(p >= &d->table[Npart])
					break;
				strncpy(p->name, field[0], NAMELEN);
				p->beg = strtoul(field[1], 0, 0);
				p->end = strtoul(field[2], 0, 0);
				if(p->beg > p->end || p->beg >= d->table[0].end)
					break;
				p++;
			}
			if(parsefields(line[i], field, 3, " ") != 3)
				break;
			if(p >= &d->table[Npart])
				break;
			strncpy(p->name, field[0], NAMELEN);
			p->beg = strtoul(field[1], 0, 0);
			p->end = strtoul(field[2], 0, 0);
			if(p->beg > p->end || p->beg >= d->table[0].end)
				break;
			p++;
		}
	}
	d->npart = p - d->table;
	scsifree(b);
	qunlock(d);

	d->partok = 1;

	return STok;
}

static long


@@ 364,9 375,6 @@ sdio(Chan *c, int write, char *a, ulong len, ulong offset)
	d = &disk[DRIVE(c->qid)];
	p = &d->table[PART(c->qid)];

	if(write && (d->inquire[0] & 0x1F) == TypeCD)
		error(Eperm);

	block = (offset / d->bsize) + p->beg;
	n = (offset + len + d->bsize - 1) / d->bsize + p->beg - block;
	max = SCSImaxxfer / d->bsize;


@@ 379,15 387,16 @@ sdio(Chan *c, int write, char *a, ulong len, ulong offset)

	b = scsialloc(n*d->bsize);
	if(b == 0)
		error(Enomem);
	if(waserror()) {
		scsifree(b);
		nexterror();
	}
		return scsierrstr(STnomem);

	offset %= d->bsize;
	if(write) {
		if(offset || len % d->bsize) {
			x = scsibio(d->t, d->lun, SCSIread, b, n, d->bsize, block);
			if(x < 0){
				len = -1;
				goto buggery;
			}
			if(x < n * d->bsize) {
				n = x / d->bsize;
				x = n * d->bsize - offset;


@@ 397,6 406,10 @@ sdio(Chan *c, int write, char *a, ulong len, ulong offset)
		}
		memmove(b + offset, a, len);
		x = scsibio(d->t, d->lun, SCSIwrite, b, n, d->bsize, block);
		if(x < 0){
			len = -1;
			goto buggery;
		}
		if(x < offset)
			len = 0;
		else


@@ 405,6 418,10 @@ sdio(Chan *c, int write, char *a, ulong len, ulong offset)
	}
	else {
		x = scsibio(d->t, d->lun, SCSIread, b, n, d->bsize, block);
		if(x < 0){
			len = -1;
			goto buggery;
		}
		if(x < offset)
			len = 0;
		else


@@ 412,7 429,8 @@ sdio(Chan *c, int write, char *a, ulong len, ulong offset)
			len = x - offset;
		memmove(a, b+offset, len);
	}
	poperror();

buggery:
	scsifree(b);
	return len;
}

M port/portdat.h => port/portdat.h +13 -13
@@ 670,19 670,19 @@ enum
/* Things scsi */
enum
{
	STtimeout	=-3,
	STownid		=-2,
	STharderr	=-1,	/* Command status error returns */
	STok		= 0,
	STcheck		= 2,
	STcondmet	= 4,
	STbusy		= 8,
	STintok		= 16,
	STconflict	= 18,
	STintcondmet	= 20,
	STterminated	= 22,
	STresconf	= 24,
	STqfull		= 28,
	STnomem		=-4,		/* buffer allocation failed */
	STtimeout	=-3,		/* bus timeout */
	STownid		=-2,		/* playing with myself */
	STharderr	=-1,		/* controller error of some kind */
	STok		= 0,		/* good */
	STcheck		= 0x02,		/* check condition */
	STcondmet	= 0x04,		/* condition met/good */
	STbusy		= 0x08,		/* busy */
	STintok		= 0x10,		/* intermediate/good */
	STintcondmet	= 0x14,		/* intermediate/condition met/good */
	STresconf	= 0x18,		/* reservation conflict */
	STterminated	= 0x22,		/* command terminated */
	STqfull		= 0x28,		/* queue full */

	SCSIread	= 0,
	SCSIwrite,

M port/portfns.h => port/portfns.h +1 -0
@@ 247,6 247,7 @@ int		screenbits(void);
#define		scsialloc(n)	mallocz((n)+512, 0)
int		scsibio(Target*, char, int, void*, long, long, long);
int		scsicap(Target*, char, ulong*, ulong*);
int		scsierrstr(int);
int		scsiexec(Target*, int, uchar*, int, void*, int*);
#define		scsifree(p)	free(p)
int		scsiinquiry(Target*, char, void*, int*);