diff options
Diffstat (limited to 'os/pc/ether79c960.c')
| -rw-r--r-- | os/pc/ether79c960.c | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/os/pc/ether79c960.c b/os/pc/ether79c960.c new file mode 100644 index 00000000..f74574cd --- /dev/null +++ b/os/pc/ether79c960.c @@ -0,0 +1,523 @@ +/* + * 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); +} |
