/* * AM79C970 * PCnet-PCI Single-Chip Ethernet Controller for PCI Local Bus * To do: * only issue transmit interrupt if necessary? * dynamically increase rings as necessary? * use Block's as receive buffers? */ #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" enum { Lognrdre = 6, Nrdre = (1<port; outl(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); for(i = 0; i < Nrdre; i++){ ctlr->rdr[i].rbadr = x; x += Rbsize; ctlr->rdr[i].rmd1 = Own|(-Rbsize & 0xFFFF); } 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); for(i = 0; i < Ntdre; i++){ ctlr->tdr[i].tbadr = x; x += Rbsize; } 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); outl(port+Rdp, Stop); outl(port+Rap, 15); x = inl(port+Rdp) & ~Prom; if(on) x |= Prom; outl(port+Rdp, x); outl(port+Rap, 0); ringinit(ctlr); outl(port+Rdp, Iena|Strt); iunlock(&ctlr->raplock); qunlock(ðer->tlock); } static int owntdre(void* arg) { return (((Tdre*)arg)->tmd1 & Own) == 0; } static long write79c970(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); 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->tmd2 = 0; tdre->tmd1 = Own|Stp|Enp|(-n & 0xFFFF); ctlr->tdrx = NEXT(ctlr->tdrx, Ntdre); outl(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 = inl(port+Rdp); outl(port+Rdp, Babl|Cerr|Miss|Merr|Rint|Tint|Iena); if(csr0 & (Babl|Miss|Merr)) print("AMD70C970#%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); etherrloop(ether, pkt, (rdre->rmd2 & 0x0FFF)-4); } /* * Finished with this descriptor, reinitialise it, * give it back to the chip, then on to the next... */ rdre->rmd2 = 0; rdre->rmd1 = Own|(-Rbsize & 0xFFFF); 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); } typedef struct Adapter Adapter; struct Adapter { Adapter* next; int port; PCIcfg* pcicfg; }; static Adapter *adapter; static PCIcfg* amd79c90(Ether* ether) { PCIcfg *pcicfg; static int devno = 0; int port; Adapter *ap; pcicfg = malloc(sizeof(PCIcfg)); for(;;){ pcicfg->vid = 0x1022; pcicfg->did = 0x2000; if((devno = pcimatch(0, devno, pcicfg)) == -1) break; port = pcicfg->baseaddr[0] & ~0x01; if(ether->port == 0 || ether->port == port) return pcicfg; ap = malloc(sizeof(Adapter)); ap->pcicfg = pcicfg; ap->port = port; ap->next = adapter; adapter = ap; pcicfg = malloc(sizeof(PCIcfg)); } free(pcicfg); return 0; } static int reset(Ether* ether) { int port, x; PCIcfg *pcicfg; Adapter *ap, **app; uchar ea[Eaddrlen]; Ctlr *ctlr; /* * Any adapter matches if no ether->port is supplied, otherwise the * ports must match. First see if we've already found an adapter that fits * the bill. If not then scan for another. */ port = 0; pcicfg = 0; for(app = &adapter, ap = *app; ap; app = &ap->next, ap = ap->next){ if(ether->port == 0 || ether->port == ap->port){ port = ap->port; pcicfg = ap->pcicfg; *app = ap->next; free(ap); break; } } if(port == 0 && (pcicfg = amd79c90(ether))){ port = pcicfg->baseaddr[0] & ~0x01; ether->irq = pcicfg->irq; } if(port == 0) return -1; if(pcicfg) free(pcicfg); /* * How can we tell what mode we're in at this point - if we're in WORD * mode then the only 32-bit access we are allowed to make is a write to * the RDP, which forces the chip to DWORD mode; if we're in DWORD mode * then we're not allowed to make any non-DWORD accesses? * Assuming we do a DWORD write to the RDP, how can we tell what we're * about to overwrite as we can't reliably access the RAP? * * Force DWORD mode by writing to RDP, doing a reset then writing to RDP * again; at least we know what state we're in now. The value of RAP after * a reset is 0, so the second DWORD write will be to CSR0. * Set the software style in BCR20 to be PCnet-PCI to ensure 32-bit access. * Set the auto pad transmit in CSR4. */ outl(port+Rdp, 0x00); inl(port+Sreset); outl(port+Rdp, Stop); outl(port+Rap, 20); outl(port+Bdp, 0x0002); outl(port+Rap, 4); x = inl(port+Rdp) & 0xFFFF; outl(port+Rdp, ApadXmt|x); outl(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){ x = inl(port+Aprom); ether->ea[0] = x & 0xFF; ether->ea[1] = (x>>8) & 0xFF; ether->ea[2] = (x>>16) & 0xFF; ether->ea[3] = (x>>24) & 0xFF; x = inl(port+Aprom+4); ether->ea[4] = x & 0xFF; ether->ea[5] = (x>>8) & 0xFF; } /* * 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<<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 we're ready to attach to the network. */ x = PADDR(&ctlr->iblock); outl(port+Rap, 1); outl(port+Rdp, x & 0xFFFF); outl(port+Rap, 2); outl(port+Rdp, (x>>16) & 0xFFFF); outl(port+Rap, 3); outl(port+Rdp, Idonm); outl(port+Rap, 0); outl(port+Rdp, Init); while((inl(port+Rdp) & Idon) == 0) ; outl(port+Rdp, Idon|Stop); ether->port = port; ether->attach = attach; ether->write = write79c970; ether->interrupt = interrupt; ether->promiscuous = promiscuous; ether->arg = ether; return 0; } void ether79c970link(void) { addethercard("AMD79C970", reset); }