plan9front/sys/src/9/pc/ether79c970.c

693 lines
15 KiB
C

/*
* AMD79C970
* PCnet-PCI Single-Chip Ethernet Controller for PCI Local Bus
* To do:
* finish this rewrite
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "../port/pci.h"
#include "../port/error.h"
#include "../port/netif.h"
#include "../port/etherif.h"
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 { /* DWIO I/O resource map */
Aprom = 0x0000, /* physical address */
Rdp = 0x0010, /* register data port */
Rap = 0x0014, /* register address port */
Sreset = 0x0018, /* software reset */
Bdp = 0x001C, /* bus configuration register data port */
};
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 */
Bswp = 0x0004, /* byte swap */
Emba = 0x0008, /* enable modified back-off algorithm */
Dxmt2pd = 0x0010, /* disable transmit two part deferral */
Lappen = 0x0020, /* look-ahead packet processing enable */
};
enum { /* CSR4 */
ApadXmt = 0x0800, /* auto pad transmit */
};
enum { /* CSR15 */
Prom = 0x8000, /* promiscuous mode */
};
typedef struct Iblock Iblock;
struct Iblock { /* Initialisation Block */
ushort mode;
uchar rlen; /* upper 4 bits */
uchar tlen; /* upper 4 bits */
uchar padr[6];
uchar res[2];
uchar ladr[8];
ulong rdra;
ulong tdra;
};
typedef struct Dre Dre;
struct Dre { /* descriptor ring entry */
ulong addr;
ulong md1; /* status|bcnt */
ulong md2; /* rcc|rpc|mcnt */
ulong aux;
};
enum { /* md1 */
Enp = 0x01000000, /* end of packet */
Stp = 0x02000000, /* start of packet */
RxBuff = 0x04000000, /* buffer error */
Def = 0x04000000, /* deferred */
Crc = 0x08000000, /* CRC error */
One = 0x08000000, /* one retry needed */
Oflo = 0x10000000, /* overflow error */
More = 0x10000000, /* more than one retry needed */
Fram = 0x20000000, /* framing error */
RxErr = 0x40000000, /* Fram|Oflo|Crc|RxBuff */
TxErr = 0x40000000, /* Uflo|Lcol|Lcar|Rtry */
Own = 0x80000000,
};
enum { /* md2 */
Rtry = 0x04000000, /* failed after repeated retries */
Lcar = 0x08000000, /* loss of carrier */
Lcol = 0x10000000, /* late collision */
Uflo = 0x40000000, /* underflow error */
TxBuff = 0x80000000, /* buffer error */
};
typedef struct Ctlr Ctlr;
struct Ctlr {
Lock;
int port;
Pcidev* pcidev;
Ctlr* next;
int active;
int init; /* initialisation in progress */
Iblock iblock;
Block** rb;
Dre* rdr; /* receive descriptor ring */
int rdrx;
Block** tb;
Dre* tdr; /* transmit descriptor ring */
int tdrh; /* host index into tdr */
int tdri; /* interface index into tdr */
int ntq; /* descriptors active */
ulong rxbuff; /* receive statistics */
ulong crc;
ulong oflo;
ulong fram;
ulong rtry; /* transmit statistics */
ulong lcar;
ulong lcol;
ulong uflo;
ulong txbuff;
ulong merr; /* bobf is such a whiner */
ulong miss;
ulong babl;
int (*ior)(Ctlr*, int);
void (*iow)(Ctlr*, int, int);
};
static Ctlr* ctlrhead;
static Ctlr* ctlrtail;
/*
* The Rdp, Rap, Sreset, Bdp ports are 32-bit port offset in the enumeration above.
* To get to 16-bit offsets, scale down with 0x10 staying the same.
*/
static int
io16r(Ctlr *c, int r)
{
if(r >= Rdp)
r = (r-Rdp)/2+Rdp;
return ins(c->port+r);
}
static void
io16w(Ctlr *c, int r, int v)
{
if(r >= Rdp)
r = (r-Rdp)/2+Rdp;
outs(c->port+r, v);
}
static int
io32r(Ctlr *c, int r)
{
return inl(c->port+r);
}
static void
io32w(Ctlr *c, int r, int v)
{
outl(c->port+r, v);
}
static void
attach(Ether*)
{
}
static long
ifstat(Ether* ether, void* a, long n, ulong offset)
{
char *p;
int len;
Ctlr *ctlr;
ctlr = ether->ctlr;
ether->crcs = ctlr->crc;
ether->frames = ctlr->fram;
ether->buffs = ctlr->rxbuff+ctlr->txbuff;
ether->overflows = ctlr->oflo;
if(n == 0)
return 0;
p = smalloc(READSTR);
len = snprint(p, READSTR, "Rxbuff: %ld\n", ctlr->rxbuff);
len += snprint(p+len, READSTR-len, "Crc: %ld\n", ctlr->crc);
len += snprint(p+len, READSTR-len, "Oflo: %ld\n", ctlr->oflo);
len += snprint(p+len, READSTR-len, "Fram: %ld\n", ctlr->fram);
len += snprint(p+len, READSTR-len, "Rtry: %ld\n", ctlr->rtry);
len += snprint(p+len, READSTR-len, "Lcar: %ld\n", ctlr->lcar);
len += snprint(p+len, READSTR-len, "Lcol: %ld\n", ctlr->lcol);
len += snprint(p+len, READSTR-len, "Uflo: %ld\n", ctlr->uflo);
len += snprint(p+len, READSTR-len, "Txbuff: %ld\n", ctlr->txbuff);
len += snprint(p+len, READSTR-len, "Merr: %ld\n", ctlr->merr);
len += snprint(p+len, READSTR-len, "Miss: %ld\n", ctlr->miss);
snprint(p+len, READSTR-len, "Babl: %ld\n", ctlr->babl);
n = readstr(offset, a, n, p);
free(p);
return n;
}
static void
ringinit(Ctlr* ctlr)
{
Block *bp;
Dre *dre;
int i;
/*
* 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->rb == nil)
ctlr->rb = malloc(Nrdre*sizeof(Block*));
if(ctlr->rdr == 0){
ctlr->rdr = xspanalloc(Nrdre*sizeof(Dre), 0x10, 0);
for(i=0; i<Nrdre; i++){
bp = iallocb(Rbsize);
if(bp == nil)
panic("can't allocate ethernet receive ring");
ctlr->rb[i] = bp;
dre = &ctlr->rdr[i];
dre->addr = PADDR(bp->rp);
dre->md2 = 0;
dre->md1 = Own|(-Rbsize & 0xFFFF);
dre->aux = 0;
}
}
ctlr->rdrx = 0;
if(ctlr->tb == nil)
ctlr->tb = malloc(Ntdre*sizeof(Block*));
if(ctlr->tdr == 0)
ctlr->tdr = xspanalloc(Ntdre*sizeof(Dre), 0x10, 0);
memset(ctlr->tdr, 0, Ntdre*sizeof(Dre));
ctlr->tdrh = ctlr->tdri = 0;
}
static void
promiscuous(void* arg, int on)
{
Ether *ether;
int x;
Ctlr *ctlr;
ether = arg;
ctlr = ether->ctlr;
/*
* Put the chip into promiscuous mode. First must wait until
* anyone transmitting is done, then 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);
while(ctlr->ntq)
;
ctlr->iow(ctlr, Rdp, Stop);
ctlr->iow(ctlr, Rap, 15);
x = ctlr->ior(ctlr, Rdp) & ~Prom;
if(on || ether->nmaddr > 0)
x |= Prom;
ctlr->iow(ctlr, Rdp, x);
ctlr->iow(ctlr, Rap, 0);
ringinit(ctlr);
ilock(ctlr);
ctlr->init = 0;
ctlr->iow(ctlr, Rdp, Iena|Strt);
iunlock(ctlr);
}
static void
multicast(void* arg, uchar*, int)
{
Ether *ether = arg;
promiscuous(arg, ether->prom);
}
static void
txstart(Ether* ether)
{
Ctlr *ctlr;
Block *bp;
Dre *dre;
int i;
ctlr = ether->ctlr;
if(ctlr->init)
return;
while(ctlr->ntq < (Ntdre-1)){
bp = qget(ether->oq);
if(bp == nil)
break;
/*
* Give ownership of the descriptor to the chip,
* increment the software ring descriptor pointer
* and tell the chip to poll.
* There's no need to pad to ETHERMINTU
* here as ApadXmt is set in CSR4.
*/
i = ctlr->tdrh;
if(ctlr->tb[i] != nil)
break;
dre = &ctlr->tdr[i];
ctlr->tb[i] = bp;
dre->addr = PADDR(bp->rp);
dre->md2 = 0;
dre->md1 = Own|Stp|Enp|(-BLEN(bp) & 0xFFFF);
ctlr->ntq++;
ctlr->iow(ctlr, Rdp, Iena|Tdmd);
ctlr->tdrh = NEXT(ctlr->tdrh, Ntdre);
}
}
static void
transmit(Ether* ether)
{
Ctlr *ctlr;
ctlr = ether->ctlr;
ilock(ctlr);
txstart(ether);
iunlock(ctlr);
}
static void
interrupt(Ureg*, void* arg)
{
Ctlr *ctlr;
Ether *ether;
int csr0, len, i;
Dre *dre;
Block *bp, *bb;
ether = arg;
ctlr = ether->ctlr;
/*
* Acknowledge all interrupts and whine about those that shouldn't
* happen.
*/
intrloop:
csr0 = ctlr->ior(ctlr, Rdp) & 0xFFFF;
ctlr->iow(ctlr, Rdp, Babl|Cerr|Miss|Merr|Rint|Tint|Iena);
if(csr0 & Merr)
ctlr->merr++;
if(csr0 & Miss)
ctlr->miss++;
if(csr0 & Babl)
ctlr->babl++;
//if(csr0 & (Babl|Miss|Merr))
// print("#l%d: csr0 = 0x%uX\n", ether->ctlrno, csr0);
if(!(csr0 & (Rint|Tint)))
return;
/*
* Receiver interrupt: run round the descriptor ring logging
* errors and passing valid receive data up to the higher levels
* until a descriptor is encountered still owned by the chip.
*/
if(csr0 & Rint){
ilock(ctlr);
i = ctlr->rdrx;
dre = &ctlr->rdr[i];
while(!(dre->md1 & Own)){
if(dre->md1 & RxErr){
if(dre->md1 & RxBuff)
ctlr->rxbuff++;
if(dre->md1 & Crc)
ctlr->crc++;
if(dre->md1 & Oflo)
ctlr->oflo++;
if(dre->md1 & Fram)
ctlr->fram++;
}
else if(bp = iallocb(Rbsize)){
bb = ctlr->rb[i];
ctlr->rb[i] = bp;
if(bb != nil){
len = (dre->md2 & 0x0FFF)-4;
bb->wp = bb->rp+len;
etheriq(ether, bb);
}
dre->addr = PADDR(bp->rp);
}
/*
* Finished with this descriptor, reinitialise it,
* give it back to the chip, then on to the next...
*/
dre->md2 = 0;
dre->md1 = Own|(-Rbsize & 0xFFFF);
i = ctlr->rdrx = NEXT(ctlr->rdrx, Nrdre);
dre = &ctlr->rdr[i];
}
iunlock(ctlr);
}
/*
* Transmitter interrupt: wakeup anyone waiting for a free descriptor.
*/
if(csr0 & Tint){
ilock(ctlr);
while(ctlr->ntq){
i = ctlr->tdri;
dre = &ctlr->tdr[i];
if(dre->md1 & Own)
break;
if(dre->md1 & TxErr){
if(dre->md2 & Rtry)
ctlr->rtry++;
if(dre->md2 & Lcar)
ctlr->lcar++;
if(dre->md2 & Lcol)
ctlr->lcol++;
if(dre->md2 & Uflo)
ctlr->uflo++;
if(dre->md2 & TxBuff)
ctlr->txbuff++;
ether->oerrs++;
}
bp = ctlr->tb[i];
if(bp != nil){
ctlr->tb[i] = nil;
freeb(bp);
}
ctlr->ntq--;
ctlr->tdri = NEXT(ctlr->tdri, Ntdre);
}
txstart(ether);
iunlock(ctlr);
}
goto intrloop;
}
static void
amd79c970pci(void)
{
int port;
Ctlr *ctlr;
Pcidev *p;
p = nil;
while(p = pcimatch(p, 0x1022, 0x2000)){
port = p->mem[0].bar & ~3;
if(ioalloc(port, p->mem[0].size, 0, "amd79c970") < 0){
print("amd79c970: port 0x%uX in use\n", port);
continue;
}
ctlr = malloc(sizeof(Ctlr));
if(ctlr == nil){
print("amd79c970: can't allocate memory\n");
iofree(port);
continue;
}
ctlr->port = port;
ctlr->pcidev = p;
if(ctlrhead != nil)
ctlrtail->next = ctlr;
else
ctlrhead = ctlr;
ctlrtail = ctlr;
}
}
static int
reset(Ether* ether)
{
int x;
uchar ea[Eaddrlen];
Ctlr *ctlr;
if(ctlrhead == nil)
amd79c970pci();
/*
* Any adapter matches if no port is supplied,
* otherwise the ports must match.
*/
for(ctlr = ctlrhead; ctlr != nil; ctlr = ctlr->next){
if(ctlr->active)
continue;
if(ether->port == 0 || ether->port == ctlr->port){
ctlr->active = 1;
break;
}
}
if(ctlr == nil)
return -1;
/*
* Allocate a controller structure and start to initialise it.
*/
ether->ctlr = ctlr;
ether->port = ctlr->port;
ether->irq = ctlr->pcidev->intl;
ether->tbdf = ctlr->pcidev->tbdf;
ilock(ctlr);
ctlr->init = 1;
pcienable(ctlr->pcidev);
pcisetbme(ctlr->pcidev);
io32r(ctlr, Sreset);
io16r(ctlr, Sreset);
if(io16w(ctlr, Rap, 0), io16r(ctlr, Rdp) == 4){
ctlr->ior = io16r;
ctlr->iow = io16w;
}else if(io32w(ctlr, Rap, 0), io32r(ctlr, Rdp) == 4){
ctlr->ior = io32r;
ctlr->iow = io32w;
}else{
print("#l%d: card doesn't talk right\n", ether->ctlrno);
iunlock(ctlr);
return -1;
}
ctlr->iow(ctlr, Rap, 88);
x = ctlr->ior(ctlr, Rdp);
ctlr->iow(ctlr, Rap, 89);
x |= ctlr->ior(ctlr, Rdp)<<16;
switch(x&0xFFFFFFF){
case 0x2420003: /* PCnet/PCI 79C970 */
case 0x2621003: /* PCnet/PCI II 79C970A */
ether->mbps = 10;
break;
case 0x2625003: /* PCnet-FAST III 79C973 */
ether->mbps = 100;
break;
default:
print("#l%d: unknown PCnet card version 0x%.7ux\n",
ether->ctlrno, x&0xFFFFFFF);
iunlock(ctlr);
return -1;
}
/*
* Set the software style in BCR20 to be PCnet-PCI to ensure 32-bit access.
* Set the auto pad transmit in CSR4.
*/
ctlr->iow(ctlr, Rap, 20);
ctlr->iow(ctlr, Bdp, 0x0002);
ctlr->iow(ctlr, Rap, 4);
x = ctlr->ior(ctlr, Rdp) & 0xFFFF;
ctlr->iow(ctlr, Rdp, ApadXmt|x);
ctlr->iow(ctlr, Rap, 0);
/*
* Check if the adapter's station address is to be overridden.
* 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)){
x = ctlr->ior(ctlr, Aprom);
ether->ea[0] = x;
ether->ea[1] = x>>8;
if(ctlr->ior == io16r)
x = ctlr->ior(ctlr, Aprom+2);
else
x >>= 16;
ether->ea[2] = x;
ether->ea[3] = x>>8;
x = ctlr->ior(ctlr, Aprom+4);
ether->ea[4] = x;
ether->ea[5] = x>>8;
}
/* VMware */
x = ether->ea[0]<<16 | ether->ea[1]<<8 | ether->ea[2];
switch(x){
case 0x0569:
case 0x0C29:
case 0x5056:
ether->mbps = 1000;
}
/*
* Start to fill in the initialisation block
* (must be DWORD aligned).
*/
ctlr->iblock.rlen = Lognrdre<<4;
ctlr->iblock.tlen = Logntdre<<4;
memmove(ctlr->iblock.padr, ether->ea, sizeof(ctlr->iblock.padr));
ringinit(ctlr);
ctlr->iblock.rdra = PADDR(ctlr->rdr);
ctlr->iblock.tdra = PADDR(ctlr->tdr);
/*
* 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 attaching to the network.
*/
x = PADDR(&ctlr->iblock);
ctlr->iow(ctlr, Rap, 1);
ctlr->iow(ctlr, Rdp, x & 0xFFFF);
ctlr->iow(ctlr, Rap, 2);
ctlr->iow(ctlr, Rdp, (x>>16) & 0xFFFF);
ctlr->iow(ctlr, Rap, 3);
ctlr->iow(ctlr, Rdp, Idon);
ctlr->iow(ctlr, Rap, 0);
ctlr->iow(ctlr, Rdp, Init);
while(!(ctlr->ior(ctlr, Rdp) & Idon))
;
/*
* We used to set CSR0 to Idon|Stop here, and then
* in attach change it to Iena|Strt. Apparently the simulated
* 79C970 in VMware never enables after a write of Idon|Stop,
* so we enable the device here now.
*/
ctlr->iow(ctlr, Rdp, Iena|Strt);
ctlr->init = 0;
iunlock(ctlr);
/*
* Linkage to the generic ethernet driver.
*/
ether->attach = attach;
ether->transmit = transmit;
ether->ifstat = ifstat;
ether->arg = ether;
ether->promiscuous = promiscuous;
ether->multicast = multicast;
// ether->shutdown = shutdown;
intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
return 0;
}
void
ether79c970link(void)
{
addethercard("AMD79C970", reset);
}