shithub: purgatorio

ref: ec35f468e0eba87c9f09cbbe5fa8af2591e6f914
dir: /os/pc/ether79c960.c/

View raw version
/*
 * AM79C960
 * PCnet Single-Chip Ethernet Controller for ISA Bus
 * To do:
 *	only issue transmit interrupt if necessary?
 *	dynamically increase rings as necessary?
 *	use Blocks as receive buffers?
 *	currently hardwires 10Base-T
 */
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/error.h"
#include "../port/netif.h"

#include "etherif.h"

#define	chatty 1
#define	DPRINT	if(chatty)print

enum {
	Lognrdre	= 6,
	Nrdre		= (1<<Lognrdre),	/* receive descriptor ring entries */
	Logntdre	= 4,
	Ntdre		= (1<<Logntdre),	/* transmit descriptor ring entries */

	Rbsize		= ETHERMAXTU+4,		/* ring buffer size (+4 for CRC) */
};

enum {						/* I/O resource map */
	Aprom		= 0x0000,		/* physical address */
	Rdp		= 0x0010,		/* register data port */
	Rap		= 0x0012,		/* register address port */
	Sreset		= 0x0014,		/* software reset */
	/*Bdp		= 0x001C,		/* bus configuration register data port */
	Idp		= 0x0016,		/* ISA data port */
};

enum {						/* ISACSR2 */
	Isa10		= 0x0001,		/* 10base-T */
	Isamedia		= 0x0003,		/* media selection mask */
	Isaawake		= 0x0004,		/* Auto-Wake */
};

enum {						/* CSR0 */
	Init		= 0x0001,		/* begin initialisation */
	Strt		= 0x0002,		/* enable chip */
	Stop		= 0x0004,		/* disable chip */
	Tdmd		= 0x0008,		/* transmit demand */
	Txon		= 0x0010,		/* transmitter on */
	Rxon		= 0x0020,		/* receiver on */
	Iena		= 0x0040,		/* interrupt enable */
	Intr		= 0x0080,		/* interrupt flag */
	Idon		= 0x0100,		/* initialisation done */
	Tint		= 0x0200,		/* transmit interrupt */
	Rint		= 0x0400,		/* receive interrupt */
	Merr		= 0x0800,		/* memory error */
	Miss		= 0x1000,		/* missed frame */
	Cerr		= 0x2000,		/* collision */
	Babl		= 0x4000,		/* transmitter timeout */
	Err		= 0x8000,		/* Babl|Cerr|Miss|Merr */
};
	
enum {						/* CSR3 */
	Emba		= 0x0008,		/* enable modified back-off algorithm */
	Dxmt2pd		= 0x0010,		/* disable transmit two part deferral */
	Lappen		= 0x0020,		/* look-ahead packet processing enable */
	Idonm		= 0x0100,		/* initialisation done mask */
	Tintm		= 0x0200,		/* transmit interrupt mask */
	Rintm		= 0x0400,		/* receive interrupt mask */
	Merrm		= 0x0800,		/* memory error mask */
	Missm		= 0x1000,		/* missed frame mask */
	Bablm		= 0x4000,		/* babl mask */
};

enum {						/* CSR4 */
	ApadXmt		= 0x0800,		/* auto pad transmit */
};

enum {						/* CSR15 */
	Prom		= 0x8000,		/* promiscuous mode */
	TenBaseT		= 0x0080,		/* 10Base-T */
};

typedef struct {				/* Initialisation Block */
	ushort	mode;
	uchar	padr[6];
	uchar	ladr[8];
	ushort	rdra0;			/* bits 0-15 */
	uchar	rdra16;			/* bits 16-23 */
	uchar	rlen;				/* upper 3 bits */
	ushort	tdra0;			/* bits 0-15 */
	uchar	tdra16;			/* bits 16-23 */
	uchar	tlen;				/* upper 3 bits */
} Iblock;

typedef struct {				/* receive descriptor ring entry */
	ushort	rbadr;				/* buffer address 0-15 */
	ushort	rmd1;				/* status|buffer address 16-23 */
	ushort	rmd2;				/* bcnt */
	ushort	rmd3;				/* mcnt */
} Rdre;

typedef struct {				/* transmit descriptor ring entry */
	ushort	tbadr;				/* buffer address 0-15 */
	ushort	tmd1;				/* status|buffer address 16-23 */
	ushort	tmd2;				/* bcnt */
	ushort	tmd3;				/* errors */
} Tdre;

enum {						/* [RT]dre status bits */
	Enp		= 0x0100,		/* end of packet */
	Stp		= 0x0200,		/* start of packet */
	RxBuff		= 0x0400,		/* buffer error */
	TxDef		= 0x0400,		/* deferred */
	RxCrc		= 0x0800,		/* CRC error */
	TxOne		= 0x0800,		/* one retry needed */
	RxOflo		= 0x1000,		/* overflow error */
	TxMore		= 0x1000,		/* more than one retry needed */
	Fram		= 0x2000,		/* framing error */
	RxErr		= 0x4000,		/* Fram|Oflo|Crc|RxBuff */
	TxErr		= 0x4000,		/* Uflo|Lcol|Lcar|Rtry */
	Own			= 0x8000,
};

typedef struct {
	Lock;

	int	init;			/* initialisation in progress */
	Iblock	iblock;

	Rdre*	rdr;				/* receive descriptor ring */
	void*	rrb;				/* receive ring buffers */
	int	rdrx;				/* index into rdr */

	Tdre*	tdr;				/* transmit descriptor ring */
	void*	trb;				/* transmit ring buffers */
	int	tdrx;				/* index into tdr */
} Ctlr;

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

	ctlr = ether->ctlr;
	ilock(ctlr);
	if(ctlr->init){
		iunlock(ctlr);
		return;
	}
	port = ether->port;
	outs(port+Rdp, Iena|Strt);
	iunlock(ctlr);
}

static void
ringinit(Ctlr* ctlr)
{
	int i, x;

	/*
	 * Initialise the receive and transmit buffer rings. The ring
	 * entries must be aligned on 16-byte boundaries.
	 *
	 * This routine is protected by ctlr->init.
	 */
	if(ctlr->rdr == 0)
		ctlr->rdr = xspanalloc(Nrdre*sizeof(Rdre), 0x10, 0);
	if(ctlr->rrb == 0)
		ctlr->rrb = xalloc(Nrdre*Rbsize);

	x = PADDR(ctlr->rrb);
	if ((x >> 24)&0xFF)
		panic("ether79c960: address>24bit");
	for(i = 0; i < Nrdre; i++){
		ctlr->rdr[i].rbadr = x&0xFFFF;
		ctlr->rdr[i].rmd1 = Own|(x>>16)&0xFF;
		x += Rbsize;
		ctlr->rdr[i].rmd2 = 0xF000|-Rbsize&0x0FFF;
		ctlr->rdr[i].rmd3 = 0;
	}
	ctlr->rdrx = 0;

	if(ctlr->tdr == 0)
		ctlr->tdr = xspanalloc(Ntdre*sizeof(Tdre), 0x10, 0);
	if(ctlr->trb == 0)
		ctlr->trb = xalloc(Ntdre*Rbsize);

	x = PADDR(ctlr->trb);
	if ((x >> 24)&0xFF)
		panic("ether79c960: address>24bit");
	for(i = 0; i < Ntdre; i++){
		ctlr->tdr[i].tbadr = x&0xFFFF;
		ctlr->tdr[i].tmd1 = (x>>16)&0xFF;
		x += Rbsize;
		ctlr->tdr[i].tmd2 = 0xF000|-Rbsize&0x0FFF;
	}
	ctlr->tdrx = 0;
}

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

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

	/*
	 * Put the chip into promiscuous mode. First we must wait until
	 * anyone transmitting is done, then we can stop the chip and put
	 * it in promiscuous mode. Restarting is made harder by the chip
	 * reloading the transmit and receive descriptor pointers with their
	 * base addresses when Strt is set (unlike the older Lance chip),
	 * so the rings must be re-initialised.
	 */
	ilock(ctlr);
	if(ctlr->init){
		iunlock(ctlr);
		return;
	}
	ctlr->init = 1;
	iunlock(ctlr);

	outs(port+Rdp, Stop);

	outs(port+Rap, 15);
	x = ins(port+Rdp) & ~Prom;
	if(on)
		x |= Prom;	/* BUG: multicast ... */
	outs(port+Rdp, x);
	outs(port+Rap, 0);

	ringinit(ctlr);

	ilock(ctlr);
	ctlr->init = 0;
	outs(port+Rdp, Iena|Strt);
	iunlock(ctlr);
}

static int
owntdre(void* arg)
{
	return (((Tdre*)arg)->tmd1 & Own) == 0;
}

static void
txstart(Ether *ether)
{
	int port;
	Ctlr *ctlr;
	Tdre *tdre;
	Etherpkt *pkt;
	Block *bp;
	int n;

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

	if(ctlr->init)
		return;

	/*
	 * Take the next transmit buffer, if it is free.
	 */
	tdre = &ctlr->tdr[ctlr->tdrx];
	if(owntdre(tdre) == 0)
		return;
	bp = qget(ether->oq);
	if(bp == nil)
		return;

	/*
	 * Copy the packet to the transmit buffer and fill in our
	 * source ethernet address. There's no need to pad to ETHERMINTU
	 * here as we set ApadXmit in CSR4.
	 */
	n = BLEN(bp);
	pkt = KADDR(tdre->tbadr|(tdre->tmd1&0xFF)<<16);
	memmove(pkt->d, bp->rp, n);
	memmove(pkt->s, ether->ea, sizeof(pkt->s));
	freeb(bp);

	/*
	 * Give ownership of the descriptor to the chip, increment the
	 * software ring descriptor pointer and tell the chip to poll.
	 */
	tdre->tmd3 = 0;
	tdre->tmd2 = 0xF000|(-n)&0x0FFF;
	tdre->tmd1 |= Own|Stp|Enp;
	ctlr->tdrx = NEXT(ctlr->tdrx, Ntdre);
	outs(port+Rdp, Iena|Tdmd);

	ether->outpackets++;
}

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

	ctlr = ether->ctlr;

	ilock(ctlr);
	txstart(ether);
	iunlock(ctlr);
}

static void
interrupt(Ureg*, void* arg)
{
	Ether *ether;
	int port, csr0, status;
	Ctlr *ctlr;
	Rdre *rdre;
	Etherpkt *pkt;
	Block *bp;
	int len;

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

	/*
	 * Acknowledge all interrupts and whine about those that shouldn't
	 * happen.
	 */
	csr0 = ins(port+Rdp);
	outs(port+Rdp, Babl|Cerr|Miss|Merr|Rint|Tint|Iena);
	if(csr0 & (Babl|Miss|Merr))
		print("AMD70C960#%d: csr0 = 0x%uX\n", ether->ctlrno, csr0);

	/*
	 * Receiver interrupt: run round the descriptor ring logging
	 * errors and passing valid receive data up to the higher levels
	 * until we encounter a descriptor still owned by the chip.
	 */
	if(csr0 & Rint){
		rdre = &ctlr->rdr[ctlr->rdrx];
		while(((status = rdre->rmd1) & Own) == 0){
			if(status & RxErr){
				if(status & RxBuff)
					ether->buffs++;
				if(status & RxCrc)
					ether->crcs++;
				if(status & RxOflo)
					ether->overflows++;
			}
			else {
				len = (rdre->rmd3 & 0x0FFF)-4;
				if((bp = iallocb(len)) != nil){
					ether->inpackets++;
					pkt = KADDR(rdre->rbadr|(rdre->rmd1&0xFF)<<16);
					memmove(bp->wp, pkt, len);
					bp->wp += len;
					etheriq(ether, bp, 1);
				}
			}

			/*
			 * Finished with this descriptor, reinitialise it,
			 * give it back to the chip, then on to the next...
			 */
			rdre->rmd3 = 0;
			rdre->rmd2 = 0xF000|-Rbsize&0x0FFF;
			rdre->rmd1 |= Own;	

			ctlr->rdrx = NEXT(ctlr->rdrx, Nrdre);
			rdre = &ctlr->rdr[ctlr->rdrx];
		}
	}

	/*
	 * Transmitter interrupt: start next block if waiting for free descriptor.
	 */
	if(csr0 & Tint){
		lock(ctlr);
		txstart(ether);
		unlock(ctlr);
	}
}

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

	if(ether->port == 0)
		ether->port = 0x300;
	if(ether->irq == 0)
		ether->irq = 10;
	if(ether->irq == 2)
		ether->irq = 9;
	if(ether->dma == 0)
		ether->dma = 5;
	port = ether->port;

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

	/*
	 * Allocate a controller structure and start to fill in the
	 * initialisation block (must be DWORD aligned).
	 */
	ether->ctlr = malloc(sizeof(Ctlr));
	ctlr = ether->ctlr;

	ilock(ctlr);
	ctlr->init = 1;

	/*
	 * Set the auto pad transmit in CSR4.
	 */
	/*outs(port+Rdp, 0x00);/**/
	ins(port+Sreset); /**/
	delay(1);
	outs(port+Rap, 0);
	outs(port+Rdp, Stop);

	outs(port+Rap, 4);
	x = ins(port+Rdp) & 0xFFFF;
	outs(port+Rdp, ApadXmt|x);

	outs(port+Rap, 0);

	/*
	 * Check if we are going to override the adapter's station address.
	 * If not, read it from the I/O-space and set in ether->ea prior to loading the
	 * station address in the initialisation block.
	 */
	memset(ea, 0, Eaddrlen);
	if(memcmp(ea, ether->ea, Eaddrlen) == 0){
		for(i=0; i<6; i++)
			ether->ea[i] = inb(port + Aprom + i);
	}

	ctlr->iblock.rlen = Lognrdre<<5;
	ctlr->iblock.tlen = Logntdre<<5;
	memmove(ctlr->iblock.padr, ether->ea, sizeof(ctlr->iblock.padr));

	ringinit(ctlr);

	x = PADDR(ctlr->rdr);
	ctlr->iblock.rdra0 = x&0xFFFF;
	ctlr->iblock.rdra16 = (x >> 16)&0xFF;
	x = PADDR(ctlr->tdr);
	ctlr->iblock.tdra0 = x&0xFFFF;
	ctlr->iblock.tdra16 = (x >> 16)&0xFF;

	/*
	 * set the DMA controller to cascade mode for bus master
	 */
	switch(ether->dma){
	case 5:
		outb(0xd6, 0xc1); outb(0xd4, 1); break;
	case 6:
		outb(0xd6, 0xc2); outb(0xd4, 2); break;
	case 7:
		outb(0xd6, 0xc3); outb(0xd4, 3); break;
	}

	/*
	  * Ensure 10Base-T (for now)
	  */
	ctlr->iblock.mode = TenBaseT;
	outs(port+Rap, 2);
	x = ins(port+Idp);
	x &= ~Isamedia;
	x |= Isa10;
	x |= Isaawake;
	outs(port+Idp, x);

	/*
	 * 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.
	 */
	x = PADDR(&ctlr->iblock);
	if((x>>24)&0xFF)
		panic("ether79c960: address>24bit");
	outs(port+Rap, 1);
	outs(port+Rdp, x & 0xFFFF);
	outs(port+Rap, 2);
	outs(port+Rdp, (x>>16) & 0xFF);
	outs(port+Rap, 3);
	outs(port+Rdp, Idonm);
	outs(port+Rap, 0);
	outs(port+Rdp, Init);

	while((ins(port+Rdp) & Idon) == 0)
		;
	outs(port+Rdp, Idon|Stop);
	ctlr->init = 0;
	iunlock(ctlr);

	ether->port = port;
	ether->attach = attach;
	ether->transmit = transmit;
	ether->interrupt = interrupt;
	ether->ifstat = 0;

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

	return 0;
}

void
ether79c960link(void)
{
	addethercard("AMD79C960",  reset);
}