ref: 75c92428225428c8fde2d015f010e608a0b12f1d
dir: /os/pc/ether79c960.c/
/* * 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); }