/* * AM79C960 * PCnet Single-Chip Ethernet Controller for ISA Bus * To do: * only issue transmit interrupt if necessary? * dynamically increase rings as necessary? * use Block's 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<port; outs(port+Rdp, Iena|Strt); } 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. */ 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. */ qlock(ðer->tlock); ilock(&ctlr->raplock); outs(port+Rdp, Stop); outs(port+Rap, 15); x = ins(port+Rdp) & ~Prom; if(on) x |= Prom; outs(port+Rdp, x); outs(port+Rap, 0); ringinit(ctlr); outs(port+Rdp, Iena|Strt); iunlock(&ctlr->raplock); qunlock(ðer->tlock); } static int owntdre(void* arg) { return (((Tdre*)arg)->tmd1 & Own) == 0; } static long write79c960(Ether* ether, void* buf, long n) { int port; Ctlr *ctlr; Tdre *tdre; Etherpkt *pkt; port = ether->port; ctlr = ether->ctlr; /* * Wait for a transmit ring descriptor (and hence a buffer) to become * free. If none become free after a reasonable period, give up. */ tdre = &ctlr->tdr[ctlr->tdrx]; tsleep(&ctlr->trendez, owntdre, tdre, 100); if(owntdre(tdre) == 0) return 0; /* * 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. */ pkt = KADDR(tdre->tbadr|(tdre->tmd1&0xFF)<<16); memmove(pkt->d, buf, n); memmove(pkt->s, ether->ea, sizeof(pkt->s)); /* * 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++; return n; } static void interrupt(Ureg*, void* arg) { Ether *ether; int port, csr0, status; Ctlr *ctlr; Rdre *rdre; Etherpkt *pkt; 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{ ether->inpackets++; pkt = KADDR(rdre->rbadr|(rdre->rmd1&0xFF)<<16); etherrloop(ether, pkt, (rdre->rmd3 & 0x0FFF)-4); } /* * 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: wakeup anyone waiting for a free descriptor. */ if(csr0 & Tint) wakeup(&ctlr->trendez); } 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; /* * Set the auto pad transmit in CSR4. */ /* outs(port+Rdp, 0x00);*/ /* ins(port+Sreset); */ 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); print("lance addr: "); for(i=0; i<6; i++) print("%.2x:", ether->ea[i]); print("\n"); } /* * Allocate a controller structure and start to fill in the * initialisation block (must be DWORD aligned). */ ether->ctlr = malloc(sizeof(Ctlr)); ctlr = ether->ctlr; 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); ether->port = port; ether->attach = attach; ether->write = write79c960; ether->interrupt = interrupt; ether->promiscuous = promiscuous; ether->arg = ether; return 0; } void ether79c960link(void) { addethercard("AMD79C960", reset); }