ref: e4191b8d11ae31efe758abfa5b48bf0b99c912a4
dir: /sys/src/9/pc/etherga620.c/
/* * Netgear GA620 Gigabit Ethernet Card. * Specific for the Alteon Tigon 2 and Intel Pentium or later. * To Do: * cache alignment for PCI Write-and-Invalidate * mini ring (what size)? * tune coalescing values * statistics formatting * don't update Spi if nothing to send * receive ring alignment * watchdog for link management? */ #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" #define malign(n) xspanalloc((n), 32, 0) #include "etherga620fw.h" enum { Mhc = 0x0040, /* Miscellaneous Host Control */ Mlc = 0x0044, /* Miscellaneous Local Control */ Mc = 0x0050, /* Miscellaneous Configuration */ Ps = 0x005C, /* PCI State */ Wba = 0x0068, /* Window Base Address */ Wd = 0x006C, /* Window Data */ DMAas = 0x011C, /* DMA Assist State */ CPUAstate = 0x0140, /* CPU A State */ CPUApc = 0x0144, /* CPU A Programme Counter */ CPUBstate = 0x0240, /* CPU B State */ Hi = 0x0504, /* Host In Interrupt Handler */ Cpi = 0x050C, /* Command Producer Index */ Spi = 0x0514, /* Send Producer Index */ Rspi = 0x051C, /* Receive Standard Producer Index */ Rjpi = 0x0524, /* Receive Jumbo Producer Index */ Rmpi = 0x052C, /* Receive Mini Producer Index */ Mac = 0x0600, /* MAC Address */ Gip = 0x0608, /* General Information Pointer */ Om = 0x0618, /* Operating Mode */ DMArc = 0x061C, /* DMA Read Configuration */ DMAwc = 0x0620, /* DMA Write Configuration */ Tbr = 0x0624, /* Transmit Buffer Ratio */ Eci = 0x0628, /* Event Consumer Index */ Cci = 0x062C, /* Command Consumer Index */ Rct = 0x0630, /* Receive Coalesced Ticks */ Sct = 0x0634, /* Send Coalesced Ticks */ St = 0x0638, /* Stat Ticks */ SmcBD = 0x063C, /* Send Max. Coalesced BDs */ RmcBD = 0x0640, /* Receive Max. Coalesced BDs */ Nt = 0x0644, /* NIC Tracing */ Gln = 0x0648, /* Gigabit Link Negotiation */ Fln = 0x064C, /* 10/100 Link Negotiation */ Ifx = 0x065C, /* Interface Index */ IfMTU = 0x0660, /* Interface MTU */ Mi = 0x0664, /* Mask Interrupts */ Gls = 0x0668, /* Gigabit Link State */ Fls = 0x066C, /* 10/100 Link State */ Cr = 0x0700, /* Command Ring */ Lmw = 0x0800, /* Local Memory Window */ }; enum { /* Mhc */ Is = 0x00000001, /* Interrupt State */ Ci = 0x00000002, /* Clear Interrupt */ Hr = 0x00000008, /* Hard Reset */ Eebs = 0x00000010, /* Enable Endian Byte Swap */ Eews = 0x00000020, /* Enable Endian Word (64-bit) swap */ Mpio = 0x00000040, /* Mask PCI Interrupt Output */ }; enum { /* Mlc */ SRAM512 = 0x00000200, /* SRAM Bank Size of 512KB */ SRAMmask = 0x00000300, EEclk = 0x00100000, /* Serial EEPROM Clock Output */ EEdoe = 0x00200000, /* Serial EEPROM Data Out Enable */ EEdo = 0x00400000, /* Serial EEPROM Data Out Value */ EEdi = 0x00800000, /* Serial EEPROM Data Input */ }; enum { /* Mc */ SyncSRAM = 0x00100000, /* Set Synchronous SRAM Timing */ }; enum { /* Ps */ PCIwm32 = 0x000000C0, /* Write Max DMA 32 */ PCImrm = 0x00020000, /* Use Memory Read Multiple Command */ PCI66 = 0x00080000, PCI32 = 0x00100000, PCIrcmd = 0x06000000, /* PCI Read Command */ PCIwcmd = 0x70000000, /* PCI Write Command */ }; enum { /* CPUAstate */ CPUrf = 0x00000010, /* ROM Fail */ CPUhalt = 0x00010000, /* Halt the internal CPU */ CPUhie = 0x00040000, /* HALT instruction executed */ }; enum { /* Om */ BswapBD = 0x00000002, /* Byte Swap Buffer Descriptors */ WswapBD = 0x00000004, /* Word Swap Buffer Descriptors */ Warn = 0x00000008, BswapDMA = 0x00000010, /* Byte Swap DMA Data */ Only1DMA = 0x00000040, /* Only One DMA Active at a time */ NoJFrag = 0x00000200, /* Don't Fragment Jumbo Frames */ Fatal = 0x40000000, }; enum { /* Lmw */ Lmwsz = 2*1024, /* Local Memory Window Size */ /* * legal values are 0x3800 iff Nsr is 128, 0x3000 iff Nsr is 256, * or 0x2000 iff Nsr is 512. */ Sr = 0x2000, /* Send Ring (accessed via Lmw) */ }; enum { /* Link */ Lpref = 0x00008000, /* Preferred Link */ L10MB = 0x00010000, L100MB = 0x00020000, L1000MB = 0x00040000, Lfd = 0x00080000, /* Full Duplex */ Lhd = 0x00100000, /* Half Duplex */ Lefc = 0x00200000, /* Emit Flow Control Packets */ Lofc = 0x00800000, /* Obey Flow Control Packets */ Lean = 0x20000000, /* Enable Autonegotiation/Sensing */ Le = 0x40000000, /* Link Enable */ }; typedef struct Host64 { uint hi; uint lo; } Host64; typedef struct Ere { /* Event Ring Element */ int event; /* event<<24 | code<<12 | index */ int unused; } Ere; typedef int Cmd; /* cmd<<24 | flags<<12 | index */ typedef struct Rbd { /* Receive Buffer Descriptor */ Host64 addr; int indexlen; /* ring-index<<16 | buffer-length */ int flags; /* only lower 16-bits */ int checksum; /* ip<<16 | tcp/udp */ int error; /* only upper 16-bits */ int reserved; void* opaque; /* passed to receive return ring */ } Rbd; typedef struct Sbd { /* Send Buffer Descriptor */ Host64 addr; int lenflags; /* len<<16 | flags */ int reserved; } Sbd; enum { /* Buffer Descriptor Flags */ Fend = 0x00000004, /* Frame Ends in this Buffer */ Frjr = 0x00000010, /* Receive Jumbo Ring Buffer */ Funicast = 0x00000020, /* Unicast packet (2-bit field) */ Fmulticast = 0x00000040, /* Multicast packet */ Fbroadcast = 0x00000060, /* Broadcast packet */ Ferror = 0x00000400, /* Frame Has Error */ Frmr = 0x00001000, /* Receive Mini Ring Buffer */ }; enum { /* Buffer Error Flags */ Ecrc = 0x00010000, /* bad CRC */ Ecollision = 0x00020000, /* collision */ Elink = 0x00040000, /* link lost */ Ephy = 0x00080000, /* unspecified PHY frame decode error */ Eodd = 0x00100000, /* odd number of nibbles */ Emac = 0x00200000, /* unspecified MAC abort */ Elen64 = 0x00400000, /* short packet */ Eresources = 0x00800000, /* MAC out of internal resources */ Egiant = 0x01000000, /* packet too big */ }; typedef struct Rcb { /* Ring Control Block */ Host64 addr; /* points to the Rbd ring */ int control; /* max_len<<16 | flags */ int unused; } Rcb; enum { TcpUdpCksum = 0x0001, /* Perform TCP or UDP checksum */ IpCksum = 0x0002, /* Perform IP checksum */ NoPseudoHdrCksum= 0x0008, /* Don't include the pseudo header */ VlanAssist = 0x0010, /* Enable VLAN tagging */ CoalUpdateOnly = 0x0020, /* Coalesce transmit interrupts */ HostRing = 0x0040, /* Sr in host memory */ SnapCksum = 0x0080, /* Parse + offload 802.3 SNAP frames */ UseExtRxBd = 0x0100, /* Extended Rbd for Jumbo frames */ RingDisabled = 0x0200, /* Jumbo or Mini RCB only */ }; typedef struct Gib { /* General Information Block */ int statistics[256]; /* Statistics */ Rcb ercb; /* Event Ring */ Rcb crcb; /* Command Ring */ Rcb srcb; /* Send Ring */ Rcb rsrcb; /* Receive Standard Ring */ Rcb rjrcb; /* Receive Jumbo Ring */ Rcb rmrcb; /* Receive Mini Ring */ Rcb rrrcb; /* Receive Return Ring */ Host64 epp; /* Event Producer */ Host64 rrrpp; /* Receive Return Ring Producer */ Host64 scp; /* Send Consumer */ Host64 rsp; /* Refresh Stats */ } Gib; /* * these sizes are all fixed in the card, * except for Nsr, which has only 3 valid sizes. */ enum { /* Host/NIC Interface ring sizes */ Ner = 256, /* event ring */ Ncr = 64, /* command ring */ Nsr = 512, /* send ring: 128, 256 or 512 */ Nrsr = 512, /* receive standard ring */ Nrjr = 256, /* receive jumbo ring */ Nrmr = 1024, /* receive mini ring, optional */ Nrrr = 2048, /* receive return ring */ }; enum { NrsrHI = 72, /* Fill-level of Rsr (m.b. < Nrsr) */ NrsrLO = 54, /* Level at which to top-up ring */ NrjrHI = 0, /* Fill-level of Rjr (m.b. < Nrjr) */ NrjrLO = 0, /* Level at which to top-up ring */ NrmrHI = 0, /* Fill-level of Rmr (m.b. < Nrmr) */ NrmrLO = 0, /* Level at which to top-up ring */ }; typedef struct Ctlr Ctlr; struct Ctlr { uvlong port; Pcidev* pcidev; Ctlr* next; int active; int id; uchar ea[Eaddrlen]; int* nic; Gib* gib; Ere* er; Lock srlock; Sbd* sr; Block** srb; int nsr; /* currently in send ring */ Rbd* rsr; int nrsr; /* currently in Receive Standard Ring */ Rbd* rjr; int nrjr; /* currently in Receive Jumbo Ring */ Rbd* rmr; int nrmr; /* currently in Receive Mini Ring */ Rbd* rrr; int rrrci; /* Receive Return Ring Consumer Index */ int epi[2]; /* Event Producer Index */ int rrrpi[2]; /* Receive Return Ring Producer Index */ int sci[3]; /* Send Consumer Index ([2] is host) */ int interrupts; /* statistics */ int mi; uvlong ticks; int coalupdateonly; /* tuning */ int hardwarecksum; int rct; /* Receive Coalesce Ticks */ int sct; /* Send Coalesce Ticks */ int st; /* Stat Ticks */ int smcbd; /* Send Max. Coalesced BDs */ int rmcbd; /* Receive Max. Coalesced BDs */ }; static Ctlr* ctlrhead; static Ctlr* ctlrtail; #define csr32r(c, r) (*((c)->nic+((r)/4))) #define csr32w(c, r, v) (*((c)->nic+((r)/4)) = (v)) static void sethost64(Host64* host64, void* addr) { uvlong uvl; uvl = PCIWADDR(addr); host64->hi = uvl>>32; host64->lo = uvl & 0xFFFFFFFFL; } static void ga620command(Ctlr* ctlr, int cmd, int flags, int index) { int cpi; cpi = csr32r(ctlr, Cpi); csr32w(ctlr, Cr+(cpi*4), cmd<<24 | flags<<12 | index); cpi = NEXT(cpi, Ncr); csr32w(ctlr, Cpi, cpi); } static void ga620attach(Ether* edev) { Ctlr *ctlr; ctlr = edev->ctlr; USED(ctlr); } static long ga620ifstat(Ether* edev, void* a, long n, ulong offset) { char *p; Ctlr *ctlr; int i, l, r; ctlr = edev->ctlr; if(n == 0) return 0; p = smalloc(READSTR); l = 0; for(i = 0; i < 256; i++){ if((r = ctlr->gib->statistics[i]) == 0) continue; l += snprint(p+l, READSTR-l, "%d: %ud\n", i, r); } l += snprint(p+l, READSTR-l, "interrupts: %ud\n", ctlr->interrupts); l += snprint(p+l, READSTR-l, "mi: %ud\n", ctlr->mi); l += snprint(p+l, READSTR-l, "ticks: %llud\n", ctlr->ticks); l += snprint(p+l, READSTR-l, "coalupdateonly: %d\n", ctlr->coalupdateonly); l += snprint(p+l, READSTR-l, "hardwarecksum: %d\n", ctlr->hardwarecksum); l += snprint(p+l, READSTR-l, "rct: %d\n", ctlr->rct); l += snprint(p+l, READSTR-l, "sct: %d\n", ctlr->sct); l += snprint(p+l, READSTR-l, "smcbd: %d\n", ctlr->smcbd); snprint(p+l, READSTR-l, "rmcbd: %d\n", ctlr->rmcbd); n = readstr(offset, a, n, p); free(p); return n; } static long ga620ctl(Ether* edev, void* buf, long n) { char *p; Cmdbuf *cb; Ctlr *ctlr; int control, i, r; ctlr = edev->ctlr; if(ctlr == nil) error(Enonexist); r = 0; cb = parsecmd(buf, n); if(cb->nf < 2) r = -1; else if(cistrcmp(cb->f[0], "coalupdateonly") == 0){ if(cistrcmp(cb->f[1], "off") == 0){ control = ctlr->gib->srcb.control; control &= ~CoalUpdateOnly; ctlr->gib->srcb.control = control; ctlr->coalupdateonly = 0; } else if(cistrcmp(cb->f[1], "on") == 0){ control = ctlr->gib->srcb.control; control |= CoalUpdateOnly; ctlr->gib->srcb.control = control; ctlr->coalupdateonly = 1; } else r = -1; } else if(cistrcmp(cb->f[0], "hardwarecksum") == 0){ if(cistrcmp(cb->f[1], "off") == 0){ control = ctlr->gib->srcb.control; control &= ~(TcpUdpCksum|NoPseudoHdrCksum); ctlr->gib->srcb.control = control; control = ctlr->gib->rsrcb.control; control &= ~(TcpUdpCksum|NoPseudoHdrCksum); ctlr->gib->rsrcb.control = control; ctlr->hardwarecksum = 0; } else if(cistrcmp(cb->f[1], "on") == 0){ control = ctlr->gib->srcb.control; control |= (TcpUdpCksum|NoPseudoHdrCksum); ctlr->gib->srcb.control = control; control = ctlr->gib->rsrcb.control; control |= (TcpUdpCksum|NoPseudoHdrCksum); ctlr->gib->rsrcb.control = control; ctlr->hardwarecksum = 1; } else r = -1; } else if(cistrcmp(cb->f[0], "rct") == 0){ i = strtol(cb->f[1], &p, 0); if(i < 0 || p == cb->f[1]) r = -1; else{ ctlr->rct = i; csr32w(ctlr, Rct, ctlr->rct); } } else if(cistrcmp(cb->f[0], "sct") == 0){ i = strtol(cb->f[1], &p, 0); if(i < 0 || p == cb->f[1]) r = -1; else{ ctlr->sct = i; csr32w(ctlr, Sct, ctlr->sct); } } else if(cistrcmp(cb->f[0], "st") == 0){ i = strtol(cb->f[1], &p, 0); if(i < 0 || p == cb->f[1]) r = -1; else{ ctlr->st = i; csr32w(ctlr, St, ctlr->st); } } else if(cistrcmp(cb->f[0], "smcbd") == 0){ i = strtol(cb->f[1], &p, 0); if(i < 0 || p == cb->f[1]) r = -1; else{ ctlr->smcbd = i; csr32w(ctlr, SmcBD, ctlr->smcbd); } } else if(cistrcmp(cb->f[0], "rmcbd") == 0){ i = strtol(cb->f[1], &p, 0); if(i < 0 || p == cb->f[1]) r = -1; else{ ctlr->rmcbd = i; csr32w(ctlr, RmcBD, ctlr->rmcbd); } } else r = -1; free(cb); if(r == 0) return n; return r; } static int _ga620transmit(Ether* edev) { Sbd *sbd; Block *bp; Ctlr *ctlr; int sci, spi, work; /* * For now there are no smarts here, just empty the * ring and try to fill it back up. Tuning comes later. */ ctlr = edev->ctlr; ilock(&ctlr->srlock); /* * Free any completed packets. * Ctlr->sci[0] is where the NIC has got to consuming the ring. * Ctlr->sci[2] is where the host has got to tidying up after the * NIC has done with the packets. */ work = 0; for(sci = ctlr->sci[2]; sci != ctlr->sci[0]; sci = NEXT(sci, Nsr)){ if(ctlr->srb[sci] == nil) continue; freeb(ctlr->srb[sci]); ctlr->srb[sci] = nil; work++; } ctlr->sci[2] = sci; sci = PREV(sci, Nsr); for(spi = csr32r(ctlr, Spi); spi != sci; spi = NEXT(spi, Nsr)){ if((bp = qget(edev->oq)) == nil) break; sbd = &ctlr->sr[spi]; sethost64(&sbd->addr, bp->rp); sbd->lenflags = BLEN(bp)<<16 | Fend; ctlr->srb[spi] = bp; work++; } csr32w(ctlr, Spi, spi); iunlock(&ctlr->srlock); return work; } static void ga620transmit(Ether* edev) { _ga620transmit(edev); } static void ga620replenish(Ctlr* ctlr) { Rbd *rbd; int rspi; Block *bp; rspi = csr32r(ctlr, Rspi); while(ctlr->nrsr < NrsrHI){ if((bp = iallocb(ETHERMAXTU+4)) == nil) break; rbd = &ctlr->rsr[rspi]; sethost64(&rbd->addr, bp->rp); rbd->indexlen = rspi<<16 | (ETHERMAXTU+4); rbd->flags = 0; rbd->opaque = bp; rspi = NEXT(rspi, Nrsr); ctlr->nrsr++; } csr32w(ctlr, Rspi, rspi); } static void ga620event(Ether *edev, int eci, int epi) { unsigned event, code; Ctlr *ctlr; ctlr = edev->ctlr; while(eci != epi){ event = ctlr->er[eci].event; code = (event >> 12) & ((1<<12)-1); switch(event>>24){ case 0x01: /* firmware operational */ /* host stack (us) is up. 3rd arg of 2 means down. */ ga620command(ctlr, 0x01, 0x01, 0x00); /* * link negotiation: any speed is okay. * 3rd arg of 1 selects gigabit only; 2 10/100 only. */ ga620command(ctlr, 0x0B, 0x00, 0x00); print("#l%d: ga620: port %8.8lluX: firmware is up\n", edev->ctlrno, ctlr->port); break; case 0x04: /* statistics updated */ break; case 0x06: /* link state changed */ switch (code) { case 1: edev->mbps = 1000; break; case 2: print("#l%d: link down\n", edev->ctlrno); break; case 3: edev->mbps = 100; /* it's 10 or 100 */ break; } if (code != 2) print("#l%d: %dMbps link up\n", edev->ctlrno, edev->mbps); break; case 0x07: /* event error */ default: print("#l%d: ga620: er[%d] = %8.8uX\n", edev->ctlrno, eci, event); break; } eci = NEXT(eci, Ner); } csr32w(ctlr, Eci, eci); } static void ga620receive(Ether* edev) { int len; Rbd *rbd; Block *bp; Ctlr* ctlr; ctlr = edev->ctlr; while(ctlr->rrrci != ctlr->rrrpi[0]){ rbd = &ctlr->rrr[ctlr->rrrci]; /* * Errors are collected in the statistics block so * no need to tally them here, let ifstat do the work. */ len = rbd->indexlen & 0xFFFF; if(!(rbd->flags & Ferror) && len != 0){ bp = rbd->opaque; bp->wp = bp->rp+len; etheriq(edev, bp); } else freeb(rbd->opaque); rbd->opaque = nil; if(rbd->flags & Frjr) ctlr->nrjr--; else if(rbd->flags & Frmr) ctlr->nrmr--; else ctlr->nrsr--; ctlr->rrrci = NEXT(ctlr->rrrci, Nrrr); } } static void ga620interrupt(Ureg*, void* arg) { int csr, ie, work; Ctlr *ctlr; Ether *edev; uvlong tsc0, tsc1; edev = arg; ctlr = edev->ctlr; if(!(csr32r(ctlr, Mhc) & Is)) return; cycles(&tsc0); ctlr->interrupts++; csr32w(ctlr, Hi, 1); ie = 0; work = 0; while(ie < 2){ if(ctlr->rrrci != ctlr->rrrpi[0]){ ga620receive(edev); work = 1; } if(_ga620transmit(edev) != 0) work = 1; csr = csr32r(ctlr, Eci); if(csr != ctlr->epi[0]){ ga620event(edev, csr, ctlr->epi[0]); work = 1; } if(ctlr->nrsr <= NrsrLO) ga620replenish(ctlr); if(work == 0){ if(ie == 0) csr32w(ctlr, Hi, 0); ie++; } work = 0; } cycles(&tsc1); ctlr->ticks += tsc1-tsc0; } static void ga620lmw(Ctlr* ctlr, int addr, int* data, int len) { int i, l, lmw, v; /* * Write to or clear ('data' == nil) 'len' bytes of the NIC * local memory at address 'addr'. * The destination address and count should be 32-bit aligned. */ v = 0; while(len > 0){ /* * 1) Set the window. The (Lmwsz-1) bits are ignored * in Wba when accessing through the local memory window; * 2) Find the minimum of how many bytes still to * transfer and how many left in this window; * 3) Create the offset into the local memory window in the * shared memory space then copy (or zero) the data; * 4) Bump the counts. */ csr32w(ctlr, Wba, addr); l = ROUNDUP(addr+1, Lmwsz) - addr; if(l > len) l = len; lmw = Lmw + (addr & (Lmwsz-1)); for(i = 0; i < l; i += 4){ if(data != nil) v = *data++; csr32w(ctlr, lmw+i, v); } len -= l; addr += l; } } static int ga620init(Ether* edev) { Ctlr *ctlr; Host64 host64; int csr, ea, i, flags; ctlr = edev->ctlr; /* * Load the MAC address. */ ea = edev->ea[0]<<8 | edev->ea[1]; csr32w(ctlr, Mac, ea); ea = edev->ea[2]<<24 | edev->ea[3]<<16 | edev->ea[4]<<8 | edev->ea[5]; csr32w(ctlr, Mac+4, ea); /* * General Information Block. */ ctlr->gib = malloc(sizeof(Gib)); sethost64(&host64, ctlr->gib); csr32w(ctlr, Gip, host64.hi); csr32w(ctlr, Gip+4, host64.lo); /* * Event Ring. * This is located in host memory. Allocate the ring, * tell the NIC where it is and initialise the indices. */ ctlr->er = malign(sizeof(Ere)*Ner); sethost64(&ctlr->gib->ercb.addr, ctlr->er); sethost64(&ctlr->gib->epp, ctlr->epi); csr32w(ctlr, Eci, 0); /* * Command Ring. * This is located in the General Communications Region * and so the value placed in the Rcb is unused, the NIC * knows where it is. Stick in the value according to * the datasheet anyway. * Initialise the ring and indices. */ ctlr->gib->crcb.addr.lo = Cr-0x400; for(i = 0; i < Ncr*4; i += 4) csr32w(ctlr, Cr+i, 0); csr32w(ctlr, Cpi, 0); csr32w(ctlr, Cci, 0); /* * Send Ring. * This ring is either in NIC memory at a fixed location depending * on how big the ring is or it is in host memory. If in NIC * memory it is accessed via the Local Memory Window; with a send * ring size of 128 the window covers the whole ring and then need * only be set once: * ctlr->sr = (uchar*)ctlr->nic+Lmw; * ga620lmw(ctlr, Sr, nil, sizeof(Sbd)*Nsr); * ctlr->gib->srcb.addr.lo = Sr; * There is nowhere in the Sbd to hold the Block* associated * with this entry so an external array must be kept. */ ctlr->sr = malign(sizeof(Sbd)*Nsr); sethost64(&ctlr->gib->srcb.addr, ctlr->sr); if(ctlr->hardwarecksum) flags = TcpUdpCksum|NoPseudoHdrCksum|HostRing; else flags = HostRing; if(ctlr->coalupdateonly) flags |= CoalUpdateOnly; ctlr->gib->srcb.control = Nsr<<16 | flags; sethost64(&ctlr->gib->scp, ctlr->sci); csr32w(ctlr, Spi, 0); ctlr->srb = malloc(sizeof(Block*)*Nsr); /* * Receive Standard Ring. */ ctlr->rsr = malign(sizeof(Rbd)*Nrsr); sethost64(&ctlr->gib->rsrcb.addr, ctlr->rsr); if(ctlr->hardwarecksum) flags = TcpUdpCksum|NoPseudoHdrCksum; else flags = 0; ctlr->gib->rsrcb.control = (ETHERMAXTU+4)<<16 | flags; csr32w(ctlr, Rspi, 0); /* * Jumbo and Mini Rings. Unused for now. */ ctlr->gib->rjrcb.control = RingDisabled; ctlr->gib->rmrcb.control = RingDisabled; /* * Receive Return Ring. * This is located in host memory. Allocate the ring, * tell the NIC where it is and initialise the indices. */ ctlr->rrr = malign(sizeof(Rbd)*Nrrr); sethost64(&ctlr->gib->rrrcb.addr, ctlr->rrr); ctlr->gib->rrrcb.control = Nrrr<<16 | 0; sethost64(&ctlr->gib->rrrpp, ctlr->rrrpi); ctlr->rrrci = 0; /* * Refresh Stats Pointer. * For now just point it at the existing statistics block. */ sethost64(&ctlr->gib->rsp, ctlr->gib->statistics); /* * DMA configuration. * Use the recommended values. */ csr32w(ctlr, DMArc, 0x80); csr32w(ctlr, DMAwc, 0x80); /* * Transmit Buffer Ratio. * Set to 1/3 of available buffer space (units are 1/64ths) * if using Jumbo packets, ~64KB otherwise (assume 1MB on NIC). */ if(NrjrHI > 0 || Nsr > 128) csr32w(ctlr, Tbr, 64/3); else csr32w(ctlr, Tbr, 4); /* * Tuneable parameters. * These defaults are based on the tuning hints in the Alteon * Host/NIC Software Interface Definition and example software. */ ctlr->rct = 1/*100*/; csr32w(ctlr, Rct, ctlr->rct); ctlr->sct = 0; csr32w(ctlr, Sct, ctlr->sct); ctlr->st = 1000000; csr32w(ctlr, St, ctlr->st); ctlr->smcbd = Nsr/4; csr32w(ctlr, SmcBD, ctlr->smcbd); ctlr->rmcbd = 4/*6*/; csr32w(ctlr, RmcBD, ctlr->rmcbd); /* * Enable DMA Assist Logic. */ csr = csr32r(ctlr, DMAas) & ~0x03; csr32w(ctlr, DMAas, csr|0x01); /* * Link negotiation. * The bits are set here but the NIC must be given a command * once it is running to set negotiation in motion. */ csr32w(ctlr, Gln, Le|Lean|Lofc|Lfd|L1000MB|Lpref); csr32w(ctlr, Fln, Le|Lean|Lhd|Lfd|L100MB|L10MB); /* * A unique index for this controller and the maximum packet * length expected. * For now only standard packets are expected. */ csr32w(ctlr, Ifx, 1); csr32w(ctlr, IfMTU, ETHERMAXTU+4); /* * Enable Interrupts. * There are 3 ways to mask interrupts - a bit in the Mhc (which * is already cleared), the Mi register and the Hi mailbox. * Writing to the Hi mailbox has the side-effect of clearing the * PCI interrupt. */ csr32w(ctlr, Mi, 0); csr32w(ctlr, Hi, 0); /* * Start the firmware. */ csr32w(ctlr, CPUApc, tigon2FwStartAddr); csr = csr32r(ctlr, CPUAstate) & ~CPUhalt; csr32w(ctlr, CPUAstate, csr); return 0; } static int at24c32io(Ctlr* ctlr, char* op, int data) { char *lp, *p; int i, loop, mlc, r; mlc = csr32r(ctlr, Mlc); r = 0; loop = -1; lp = nil; for(p = op; *p != '\0'; p++){ switch(*p){ default: return -1; case ' ': continue; case ':': /* start of 8-bit loop */ if(lp != nil) return -1; lp = p; loop = 7; continue; case ';': /* end of 8-bit loop */ if(lp == nil) return -1; loop--; if(loop >= 0) p = lp; else lp = nil; continue; case 'C': /* assert clock */ mlc |= EEclk; break; case 'c': /* deassert clock */ mlc &= ~EEclk; break; case 'D': /* next bit in 'data' byte */ if(loop < 0) return -1; if(data & (1<<loop)) mlc |= EEdo; else mlc &= ~EEdo; break; case 'E': /* enable data output */ mlc |= EEdoe; break; case 'e': /* disable data output */ mlc &= ~EEdoe; break; case 'I': /* input bit */ i = (csr32r(ctlr, Mlc) & EEdi) != 0; if(loop >= 0) r |= (i<<loop); else r = i; continue; case 'O': /* assert data output */ mlc |= EEdo; break; case 'o': /* deassert data output */ mlc &= ~EEdo; break; } csr32w(ctlr, Mlc, mlc); microdelay(1); } if(loop >= 0) return -1; return r; } static int at24c32r(Ctlr* ctlr, int addr) { int data; /* * Read a byte at address 'addr' from the Atmel AT24C32 * Serial EEPROM. The 2-wire EEPROM access is controlled * by 4 bits in Mlc. See the AT24C32 datasheet for * protocol details. */ /* * Start condition - a high to low transition of data * with the clock high must precede any other command. */ at24c32io(ctlr, "OECoc", 0); /* * Perform a random read at 'addr'. A dummy byte * write sequence is performed to clock in the device * and data word addresses (0 and 'addr' respectively). */ data = -1; if(at24c32io(ctlr, "oE :DCc; oeCIc", 0xA0) != 0) goto stop; if(at24c32io(ctlr, "oE :DCc; oeCIc", addr>>8) != 0) goto stop; if(at24c32io(ctlr, "oE :DCc; oeCIc", addr) != 0) goto stop; /* * Now send another start condition followed by a * request to read the device. The EEPROM responds * by clocking out the data. */ at24c32io(ctlr, "OECoc", 0); if(at24c32io(ctlr, "oE :DCc; oeCIc", 0xA1) != 0) goto stop; data = at24c32io(ctlr, ":CIc;", 0xA1); stop: /* * Stop condition - a low to high transition of data * with the clock high is a stop condition. After a read * sequence, the stop command will place the EEPROM in * a standby power mode. */ at24c32io(ctlr, "oECOc", 0); return data; } static int ga620detach(Ctlr* ctlr) { int timeo; /* * Hard reset (don't know which endian so catch both); * enable for little-endian mode; * wait for code to be loaded from serial EEPROM or flash; * make sure CPU A is halted. */ csr32w(ctlr, Mhc, Hr<<24 | Hr); csr32w(ctlr, Mhc, (Eews|Ci)<<24 | Eews|Ci); microdelay(1); for(timeo = 0; timeo < 500000; timeo++){ if((csr32r(ctlr, CPUAstate) & (CPUhie|CPUrf)) == CPUhie) break; microdelay(1); } if((csr32r(ctlr, CPUAstate) & (CPUhie|CPUrf)) != CPUhie) return -1; csr32w(ctlr, CPUAstate, CPUhalt); /* * After reset, CPU B seems to be stuck in 'CPUrf'. * Worry about it later. */ csr32w(ctlr, CPUBstate, CPUhalt); return 0; } static void ga620shutdown(Ether* ether) { print("ga620shutdown\n"); ga620detach(ether->ctlr); } static int ga620reset(Ctlr* ctlr) { int csr, i, r; if(ga620detach(ctlr) < 0) return -1; /* * Tigon 2 PCI NICs have 512KB SRAM per bank. * Clear out any lingering serial EEPROM state * bits. */ csr = csr32r(ctlr, Mlc) & ~(EEdi|EEdo|EEdoe|EEclk|SRAMmask); csr32w(ctlr, Mlc, SRAM512|csr); csr = csr32r(ctlr, Mc); csr32w(ctlr, Mc, SyncSRAM|csr); /* * Initialise PCI State register. * If PCI Write-and-Invalidate is enabled set the max write DMA * value to the host cache-line size (32 on Pentium or later). */ csr = csr32r(ctlr, Ps) & (PCI32|PCI66); csr |= PCIwcmd|PCIrcmd|PCImrm; if(ctlr->pcidev->pcr & 0x0010){ if(ctlr->pcidev->cls != 32/4){ ctlr->pcidev->cls = 32/4; pcicfgw8(ctlr->pcidev, PciCLS, ctlr->pcidev->cls); } csr |= PCIwm32; } csr32w(ctlr, Ps, csr); /* * Operating Mode. */ csr32w(ctlr, Om, Fatal|NoJFrag|BswapDMA|WswapBD); /* * Snarf the MAC address from the serial EEPROM. */ for(i = 0; i < Eaddrlen; i++){ if((r = at24c32r(ctlr, 0x8E+i)) == -1) return -1; ctlr->ea[i] = r; } /* * Load the firmware. */ ga620lmw(ctlr, tigon2FwTextAddr, tigon2FwText, tigon2FwTextLen); ga620lmw(ctlr, tigon2FwRodataAddr, tigon2FwRodata, tigon2FwRodataLen); ga620lmw(ctlr, tigon2FwDataAddr, tigon2FwData, tigon2FwDataLen); ga620lmw(ctlr, tigon2FwSbssAddr, nil, tigon2FwSbssLen); ga620lmw(ctlr, tigon2FwBssAddr, nil, tigon2FwBssLen); return 0; } static void ga620pci(void) { void *mem; Pcidev *p; Ctlr *ctlr; p = nil; while(p = pcimatch(p, 0, 0)){ if(p->ccrb != 0x02 || p->ccru != 0) continue; if(p->mem[0].bar & 1) continue; switch(p->did<<16 | p->vid){ default: continue; case 0x620A<<16 | 0x1385: /* Netgear GA620 fiber */ case 0x630A<<16 | 0x1385: /* Netgear GA620T copper */ case 0x0001<<16 | 0x12AE: /* Alteon Acenic fiber * and DEC DEGPA-SA */ case 0x0002<<16 | 0x12AE: /* Alteon Acenic copper */ case 0x0009<<16 | 0x10A9: /* SGI Acenic */ break; } mem = vmap(p->mem[0].bar & ~0xF, p->mem[0].size); if(mem == nil){ print("ga620: can't map %llux\n", p->mem[0].bar & ~0xF); continue; } ctlr = malloc(sizeof(Ctlr)); if(ctlr == nil){ print("ga620: can't allocate memory\n"); continue; } ctlr->port = p->mem[0].bar & ~0xF; ctlr->pcidev = p; pcienable(p); ctlr->id = p->did<<16 | p->vid; ctlr->nic = mem; if(ga620reset(ctlr)){ free(ctlr); continue; } pcisetbme(p); if(ctlrhead != nil) ctlrtail->next = ctlr; else ctlrhead = ctlr; ctlrtail = ctlr; } } static void ga620promiscuous(void *arg, int on) { Ether *ether = arg; /* 3rd arg: 1 enables, 2 disables */ ga620command(ether->ctlr, 0xa, (on? 1: 2), 0); } static void ga620multicast(void *arg, uchar *addr, int add) { Ether *ether = arg; USED(addr); if (add) ga620command(ether->ctlr, 0xe, 1, 0); /* 1 == enable */ } static int ga620pnp(Ether* edev) { Ctlr *ctlr; uchar ea[Eaddrlen]; if(ctlrhead == nil) ga620pci(); /* * Any adapter matches if no edev->port is supplied, * otherwise the ports must match. */ for(ctlr = ctlrhead; ctlr != nil; ctlr = ctlr->next){ if(ctlr->active) continue; if(edev->port == 0 || edev->port == ctlr->port){ ctlr->active = 1; break; } } if(ctlr == nil) return -1; edev->ctlr = ctlr; edev->port = ctlr->port; edev->irq = ctlr->pcidev->intl; edev->tbdf = ctlr->pcidev->tbdf; edev->mbps = 1000; /* placeholder */ /* * Check if the adapter's station address is to be overridden. * If not, read it from the EEPROM and set in ether->ea prior to * loading the station address in the hardware. */ memset(ea, 0, Eaddrlen); if(memcmp(ea, edev->ea, Eaddrlen) == 0) memmove(edev->ea, ctlr->ea, Eaddrlen); ga620init(edev); /* * Linkage to the generic ethernet driver. */ edev->attach = ga620attach; edev->transmit = ga620transmit; edev->ifstat = ga620ifstat; edev->ctl = ga620ctl; edev->arg = edev; edev->promiscuous = ga620promiscuous; edev->multicast = ga620multicast; edev->shutdown = ga620shutdown; intrenable(edev->irq, ga620interrupt, edev, edev->tbdf, edev->name); return 0; } void etherga620link(void) { addethercard("GA620", ga620pnp); }