/* * SiS 900 * * some registers not appearing in the documentation * were 'stolen' from freebsd's if_sis.c (found at * the end of the enums -- the uncommented ones). * eeprom reading routines come from the same driver * but are untested (the chipset revision we have here * reads the mac address from cmos). * * andrey mirtchovski -- andrey@lanl.gov * * TODO: handle cards with different revision chipsets * make sure sis_eeprom reads correct MAC */ #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 { RxCnt = 16, /* receiver list count */ TxCnt = 16, /* tarnsmitter list count */ TxLen = ROUNDUP(sizeof(Etherpkt), 4), RxLen = ROUNDUP(sizeof(Etherpkt), 4), }; enum { /* MAC operational registers */ Cr = 0x00, /* command register */ Cfg = 0x04, /* configuration register */ Eeprom = 0x08, /* EEPROM access register */ Pcitest = 0x0C, /* PCI test control register */ Isr = 0x10, /* interrupt status register */ Imr = 0x14, /* interrupt mask register */ Ier = 0x18, /* interrupt enable register */ Ephy = 0x1C, /* enhanced PHY access register */ Tdr = 0x20, /* transmit descriptor pointer */ Tcr = 0x24, /* transmit configuration register */ Rdr = 0x30, /* recieve descriptor pointer */ Rcr = 0x34, /* receive configuration register */ Fcr = 0x38, /* flow control register */ Rfcr = 0x48, /* receive filter control register */ Rfdr = 0x4C, /* receive filter data register */ Pmcr = 0xB0, /* power management control register */ Pmwer = 0xB4, /* power management wake-up event register */ Wakesfcr = 0xBC, /* wake-up sample frame CRC register */ Wakesfmr = 0xC0, /* wake-up sample frame mask registers */ }; enum { /* eeprom related stuff */ EEread = 0x0180, EEwrite = 0x0140, EEerase = 0x01C0, EEwriteEnable = 0x0130, EEwriteDisable = 0x0100, EEeraseAll = 0x0120, EEwriteAll = 0x0110, EEaddrMask = 0x013F, EEcmdShift = 16 }; enum { /* eeprom access */ Csel = 0x08, /* chip select */ Clk = 0x04, /* serial clock */ Dout = 0x02, /* data out */ Din = 0x01, /* data in */ }; enum { /* PHY configuration registers */ M0Cr = 0x00, /* MI 0 Control register */ M1Sr = 0x01, /* MI 1 Status register */ M2Phy1 = 0x02, /* MI 2 PHY ID #1 */ M3Phy2 = 0x03, /* MI 3 PHY ID #2 */ M4Ana = 0x04, /* MI 4 Auto negotiation advertisement */ M5Anrec = 0x05, /* MI 5 Auto negotiation remote end capabilities */ M16C1 = 0x10, /* MI 16 Configuration 1 */ M17C2 = 0x11, /* MI 17 Configuration 2 */ M18So = 0x12, /* MI 18 Status Output */ M19m = 0x13, /* MI 19 Mask */ M20r = 0x14, /* MI 20 Reserved */ }; enum { /* mac register space */ PhySoftReset= 0x200, /* HomePHY Software reset */ Rst = 0x100, /* Reset */ Intr = 0x80, /* software interrupt */ Rxrst = 0x20, /* receiver reset */ Txrst = 0x10, /* transmit reset */ Rxdis = 0x08, /* receiver disable */ Rxen = 0x04, /* receiver enable */ Txdis = 0x02, /* transmit disable */ Txen = 0x01, /* transmit enable */ }; enum { /* configuration registers */ BusRqAlg = 0x80, /* pci bus request algorithm */ SnglBckoff = 0x40, /* single backoff */ Prgoowt = 0x20, /* program out of window timer */ Xdtd = 0x10, /* excessive deferral timer disable */ Peda = 0x08, /* parity error detection action */ BigEnd = 0x01, /* big endian mode */ }; enum { /* interrupt status */ Wkevnt = 0x10000000, /* wake-up event */ Etp = 0x08000000, /* end of transmission pause */ Stp = 0x04000000, /* start of transmission pause */ Trc = 0x02000000, /* transmit reset complete */ Rrc = 0x01000000, /* receive reset complete */ Dpe = 0x00800000, /* detected parity error */ Sse = 0x00400000, /* sidnaled system error */ Rma = 0x00200000, /* received master abort */ Rta = 0x00100000, /* received target abort */ FifoOvrrun = 0x00010000, /* rx status FIFO overrun */ Hbes = 0x00008000, /* high bits error set */ Sintr = 0x00000800, /* software interrupt */ Txundr = 0x00000400, /* transmit underrun */ Txidle = 0x00000200, /* transmit idle */ Txerr = 0x00000100, /* transmit packet error */ Txdesc = 0x00000080, /* transmit descriptor */ TxOK = 0x00000040, /* transmit packet ok */ RxOvrrun = 0x00000020, /* receiver fifo overrun */ Rxidle = 0x00000010, /* receiver idle */ Rxth = 0x00000008, /* receiver early threshold */ Rxerr = 0x00000004, /* receiver packet error */ Rxdesc = 0x00000002, /* receiver descriptor */ RxOK = 0x00000001, /* receiver packet ok */ }; enum { Intrenbl = 0x01, /* interrupt enable */ }; enum { /* phy registers */ PhyData = 0xFFFF0000, /* phy R/W data */ Phyaddr = 0x0000F800, /* register address of PHY */ Phyregaddr = 0x000007C0, /* register address of PHY */ Phycmd = 0x00000020, /* access cmd to phy */ SwHw = 0x00000010, /* SW access request/HW done */ }; enum { /* transmit configuration */ CarrIgn = 0x80000000, /* carrier sence ignore */ HbeatIgn = 0x40000000, /* heart beat ignore */ MacLoopbk = 0x20000000, /* MAC Loopback */ AutoTP = 0x10000000, /* automatic teansmit padding */ TxDMA = 0x00700000, /* dma burst size */ RxFill = 0x00003F00, /* TX Fill threshold */ TxDrain = 0x0000003F, /* TX Drain threshold */ }; enum { /* receive configuration */ AccptErr = 0x80000000, /* accept error packets */ AccptRunt = 0x40000000, /* accept runt packets */ AccptTx = 0x10000000, /* accept TX packets */ AccptJabb = 0x08000000, /* accept jabber packets */ RxDMA = 0x00700000, /* receive dma burst size */ RxDrain = 0x0000003E, /* RX drain threshold */ }; enum { /* receive filter control */ FiltEn = 0x80000000, /* receive filter enable */ BroadEn = 0x40000000, /* broadcast enable */ MultiEn = 0x20000000, /* accept all multicast */ PhysEn = 0x10000000, /* accept all physical */ HomePHY = 0x08000000, /* HomePHY/802.3PHY select */ }; enum { /* mac address */ Par0 = 0x00000000, /* node address octets 1-0 */ Par1 = 0x00010000, /* node address octets 3-2 */ Par2 = 0x00020000, /* node address octets 5-4 */ Mar0 = 0x00040000, /* multicast hash table bits */ Mar1 = 0x00050000, Mar2 = 0x00060000, Mar3 = 0x00070000, Mar4 = 0x00080000, Mar5 = 0x00090000, Mar6 = 0x000A0000, Mar7 = 0x000B0000, }; enum { /* buffer descriptor status */ Own = 0x80000000, More = 0x40000000, Intrpt = 0x20000000, Crc = 0x10000000, PktOK = 0x08000000, Buflen = 0x00000FFF, }; enum { /* rx buffer descriptor status */ Ovrrun = 0x02000000, Dest = 0x00800000, Bcast = 0x01800000, Mcast = 0x01000000, Unimatch = 0x00800000, Toolong = 0x00400000, Runt = 0x00200000, Rxiserr = 0x00100000, Crcerr = 0x00080000, Faerr = 0x00040000, Loopbk = 0x00020000, Rxcoll = 0x00010000, }; enum { /* tx buffer descriptor status */ Abrt = 0x04000000, Undr = 0x02000000, Nocarr = 0x01000000, Deferd = 0x00800000, Exdefer = 0x00400000, Owcoll = 0x00200000, Excoll = 0x00100000, Txcoll = 0x000F0000, }; #define Ints (RxOvrrun|Txundr|TxOK|Txidle|RxOK|Rxerr|Sse) typedef struct { uint link; uint cmdsts; uint bufptr; } Desc; typedef struct Ctlr Ctlr; typedef struct Ctlr { int port; Pcidev* pcidev; Ctlr* next; int active; int id; QLock alock; /* attach */ Lock ilock; /* interrupt */ Lock tlock; /* transmit */ uint rctrl; /* receiver control */ Desc *td; /* transmit descriptors */ Desc *rd; /* receive descriptors */ uchar *tdbuff[TxCnt]; uchar *rdbuff[RxCnt]; ulong currd; ulong dirtyrd; ulong curtd; ulong dirtytd; int tdfull; uint tdpacks; /* transmitted packets */ uint rdpacks; /* received packets */ uint errs; /* errors */ uint coll; /* collisions */ int dis; /* disconnect counter */ int fcsc; /* false carrier sense counter */ int rxerr; /* RX_ER counter */ } Ctlr; static Ctlr* ctlrhead; static Ctlr* ctlrtail; #define csr8r(c, r) (inb((c)->port+(r))) #define csr16r(c, r) (ins((c)->port+(r))) #define csr32r(c, r) (inl((c)->port+(r))) #define csr8w(c, r, b) (outb((c)->port+(r), (int)(b))) #define csr16w(c, r, w) (outs((c)->port+(r), (ushort)(w))) #define csr32w(c, r, l) (outl((c)->port+(r), (ulong)(l))) static void rcmos(Ctlr *ctlr, Ether *edev) { Pcidev *p; uint reg; int i; ulong port; USED(ctlr); p = pcimatch(nil, 0x1039, 0x0008); if (p == nil) error("sis: cannot find isa bridge"); port = p->mem[0].bar & ~0x01; reg = pcicfgr8(p, 0x48); pcicfgw8(p, 0x48, reg|0x40); for (i = 0; i < 6; i++) { outb(port+0x70, 0x09 + i); edev->ea[i] = inb(port+0x71); } pcicfgw8(p, 0x48, reg & ~0x40); return; } static void sis_delay(Ctlr *ctlr) { int idx; for (idx = (300 / 33) + 1; idx > 0; idx--) csr32r(ctlr, Cr); return; } #define Eeset(x) csr32w(ctlr, Eeprom, csr32r(ctlr, Eeprom) | x) #define Eeclr(x) csr32w(ctlr, Eeprom, csr32r(ctlr, Eeprom) & ~x) static void sis_eeprom_idle(Ctlr *ctlr) { register int i; Eeset(Csel); sis_delay(ctlr); Eeset(Clk); sis_delay(ctlr); for (i = 0; i < 25; i++) { Eeclr(Clk); sis_delay(ctlr); Eeset(Clk); sis_delay(ctlr); } Eeclr(Clk); sis_delay(ctlr); Eeclr(Csel); sis_delay(ctlr); csr32w(ctlr, Eeprom, 0x00000000); return; } static void sis_eeprom_putbyte(Ctlr *ctlr, int addr) { register int d, i; d = addr | EEread; /* * Feed in each bit and stobe the clock. */ for (i = 0x400; i; i >>= 1) { if (d & i) { Eeset(Din); } else { Eeclr(Din); } sis_delay(ctlr); Eeset(Clk); sis_delay(ctlr); Eeclr(Clk); sis_delay(ctlr); } return; } /* * Read a word of data stored in the EEPROM at address 'addr.' */ static void sis_eeprom_getword(Ctlr *ctlr, int addr, uchar *dest) { register int i; uint word = 0; /* Force EEPROM to idle state. */ sis_eeprom_idle(ctlr); /* Enter EEPROM access mode. */ sis_delay(ctlr); Eeclr(Clk); sis_delay(ctlr); Eeset(Csel); sis_delay(ctlr); /* * Send address of word we want to read. */ sis_eeprom_putbyte(ctlr, addr); /* * Start reading bits from EEPROM. */ for (i = 0x8000; i; i >>= 1) { Eeset(Clk); sis_delay(ctlr); if (csr32r(ctlr, Eeprom) & Dout) word |= i; sis_delay(ctlr); Eeclr(Clk); sis_delay(ctlr); } /* Turn off EEPROM access mode. */ sis_eeprom_idle(ctlr); *dest = word; //print("eeprom_readword: %4.4ux\n", word); return; } static void sis_read_eeprom(Ctlr *ctlr, uchar *dest, int off, int cnt, int swap) { int i; uchar word = 0, *ptr; USED(swap); for (i = 0; i < cnt; i++) { sis_eeprom_getword(ctlr, off + i, &word); ptr = dest + (i * 2); *ptr = word; } return; } static void sispromiscuous(void* arg, int on) { Ether *edev; Ctlr * ctlr; edev = arg; ctlr = edev->ctlr; ilock(&ctlr->ilock); if(on) ctlr->rctrl |= PhysEn; else ctlr->rctrl &= ~PhysEn; csr32w(ctlr, Rfcr, ctlr->rctrl); iunlock(&ctlr->ilock); } static long sisifstat(Ether* edev, void* a, long n, ulong offset) { int l; char *p; Ctlr *ctlr; ctlr = edev->ctlr; p = malloc(READSTR); l = snprint(p, READSTR, "Errors: %ud\n", ctlr->errs); l += snprint(p+l, READSTR-l, "sent: %d\n", ctlr->coll); l += snprint(p+l, READSTR-l, "received: %d\n", ctlr->rdpacks); l += snprint(p+l, READSTR-l, "transmitted: %d\n", ctlr->tdpacks); snprint(p+l, READSTR-l, "collisions: %d\n", ctlr->coll); n = readstr(offset, a, n, p); free(p); return n; } static int sisreset(Ctlr* ctlr) { /* * Soft reset the controller. */ csr32w(ctlr, Ier, 0); csr32w(ctlr, Imr, 0); csr32w(ctlr, Rfcr, 0); csr32w(ctlr, Cr, Txrst | Rxrst | Rst); while(!csr32r(ctlr, Isr) & Isr) ; csr32w(ctlr, Cfg, 0x08); /* ? */ return 0; } static void sishalt(Ctlr* ctlr) { csr32w(ctlr, Ier, 0); csr32w(ctlr, Imr, 0); csr32w(ctlr, Rfcr, 0); csr32w(ctlr, Cr, Txdis | Rxdis); csr32w(ctlr, Tdr, 0); csr32w(ctlr, Rdr, 0); } static void sisinit(Ether* edev) { int i; Ctlr *ctlr; ctlr = edev->ctlr; ilock(&ctlr->ilock); //sishalt(ctlr); /* * MAC Address. */ csr32w(ctlr, Rfcr, Par0); csr32w(ctlr, Rfdr, (edev->ea[1]<<8)|edev->ea[0]); csr32w(ctlr, Rfcr, Par1); csr32w(ctlr, Rfdr, (edev->ea[3]<<8)|edev->ea[2]); csr32w(ctlr, Rfcr, Par2); csr32w(ctlr, Rfdr, (edev->ea[5]<<8)|edev->ea[4]); /* receiver and transmitter descriptors are as follows: * link: 32bit pointer to the next descriptor (circular in this case) * cmdsts: command status * bufptr: pointer to first byte in packet */ /* * Receiver */ ctlr->currd = 0; ctlr->dirtyrd = 0; for(i = 0; i < RxCnt; i++) { uchar *r; Desc *d; d = ctlr->rd+i; d->link = PCIWADDR(i == RxCnt - 1 ? ctlr->rd : d + 1); d->cmdsts = 0; d->bufptr = 0; if((r = mallocz(RxLen+32+16, 0)) == nil) error("sis: not enough memory for receive buffers\n"); ctlr->rdbuff[i] = (uchar *)ROUNDUP((ulong)r, 32); d->cmdsts = RxLen; d->bufptr = PCIWADDR(ctlr->rdbuff[i]); } csr32w(ctlr, Rdr, PCIWADDR(ctlr->rd)); /* * Transmitter. */ ctlr->tdfull = 0; ctlr->dirtytd = ctlr->curtd = 0; for(i = 0; i < TxCnt; i++){ uchar *r; Desc *d; d = ctlr->td + i; d->link = PCIWADDR(i == TxCnt - 1 ? ctlr->td : d + 1); d->cmdsts = 0; d->bufptr = 0; if((r = mallocz(TxLen+32+16, 0)) == nil) error("sis: not enough memory for transmit buffers\n"); ctlr->tdbuff[i] = (uchar *)ROUNDUP((ulong)r, 32); } csr32w(ctlr, Tdr, PCIWADDR(ctlr->td)); ctlr->rctrl = BroadEn | FiltEn |PhysEn | HomePHY; csr32w(ctlr, Rfcr, csr32r(ctlr, Rfcr) | ctlr->rctrl); /* * Interrupts. */ csr32w(ctlr, Imr, 0xffffffff); csr32w(ctlr, Ier, 0x01); csr32w(ctlr, Cr, (Txen | Rxen)); csr32w(ctlr, Rcr, (0x00700000 | (((64 >> 3) << 1) & 0x0000003E))); /* set 100baseT */ csr32w(ctlr, Tcr, (0x00500000|0x10000000|(((64 >> 5) << 8) & 0x00003F00)|((1536 >> 5) & 0x0000003F))); csr32w(ctlr, Tcr, csr32r(ctlr, Tcr) | HbeatIgn | CarrIgn); csr32w(ctlr, Rcr, csr32r(ctlr, Rcr) | AccptTx); iunlock(&ctlr->ilock); } static void sisattach(Ether* edev) { Ctlr *ctlr; ctlr = edev->ctlr; qlock(&ctlr->alock); if(ctlr->rd == nil || ctlr->td == nil){ ctlr->rd = (Desc *)mallocz(RxCnt * sizeof(Desc)+ 32, 0); ctlr->td = (Desc *)mallocz(RxCnt * sizeof(Desc)+ 32, 0); sisinit(edev); } qunlock(&ctlr->alock); } static void sistxstart(Ether* edev) { int size, num; Block *bp; Ctlr *ctlr; Desc *td; uchar *buf; ctlr = edev->ctlr; if(ctlr->curtd - ctlr->dirtytd > TxCnt) { /* we're full.. discard packet */ return; } bp = qget(edev->oq); if(bp == nil) return; size = BLEN(bp); num = ctlr->curtd % TxCnt; buf = ctlr->tdbuff[num]; td = ctlr->td + num; if(((int)bp->rp) & 0x03){ memmove(buf, bp->rp, size); freeb(bp); td->bufptr = PCIWADDR(buf); } else{ td->bufptr = PCIWADDR(bp->rp); } td->cmdsts = (Own | size); csr32w(ctlr, Cr, Txen); ctlr->curtd++; } static void sistransmit(Ether* edev) { Ctlr *ctlr; ctlr = edev->ctlr; ilock(&ctlr->tlock); sistxstart(edev); iunlock(&ctlr->tlock); } static void sisreceive(Ether* edev) { Block *bp; Ctlr *ctlr; uint size; int entry; int status; ctlr = edev->ctlr; entry = ctlr->currd % RxCnt; status = ctlr->rd[entry].cmdsts; size = status & Buflen - 4; while(status & Own) { if (!(status & PktOK)) { ctlr->errs++; if (status & Rxcoll) ctlr->coll++; ctlr->currd++; ctlr->dirtyrd++; entry = ctlr->currd % RxCnt; status = ctlr->rd[entry].cmdsts; continue; } if(ctlr->rdbuff[entry] == nil) { print("sis: null pointer in receive ring, skipping..."); break; } bp = iallocb(size); if(size > 0 && bp != nil){ memmove(bp->wp, ctlr->rdbuff[entry], size); //bp->rp = ctlr->rdbuff[entry]; bp->wp += size; } ctlr->rd[entry].cmdsts = RxLen; if(bp != nil) etheriq(edev, bp, 1); ctlr->currd++; ctlr->dirtyrd++; entry = ctlr->currd % RxCnt; status = ctlr->rd[entry].cmdsts; } csr32w(ctlr, Cr, Rxen); } static void sisinterrupt(Ureg*, void* arg) { Ctlr *ctlr; Ether *edev; uint isr, status; edev = arg; ctlr = edev->ctlr; ilock(&ctlr->ilock); do { isr = csr32r(ctlr, Isr); if ((isr & Ints) == 0) break; if (isr & (Rxdesc | RxOK)) { /* Rx interrupt */ sisreceive(edev); } if(isr & (TxOK | Txdesc)) { /* see how the last packet was tx-ed */ status = ctlr->rd[ctlr->dirtytd].cmdsts; if(status & PktOK) ctlr->tdpacks++; ctlr->coll += (status & Txcoll) >> 16; ctlr->dirtytd++; } if (isr & (Txundr | Txerr | RxOvrrun | Rxerr)) { ctlr->errs++; } /* something strange happened !!! */ if (isr & 0x8000) { print("sis: abnormal interrupt, status %#8.8x.\n", isr); break; } } while (1); iunlock(&ctlr->ilock); } static Ctlr* sismatch(Ether* edev, int id) { int port; Pcidev *p; Ctlr *ctlr; /* * 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; p = ctlr->pcidev; if(((p->did<<16)|p->vid) != id) continue; port = p->mem[0].bar & ~0x01; if(edev->port != 0 && edev->port != port) continue; if(ioalloc(port, p->mem[0].size, 0, "sis") < 0){ print("sis: port 0x%uX in use\n", port); continue; } ctlr->port = port; if(sisreset(ctlr)) continue; pcisetbme(p); ctlr->active = 1; return ctlr; } return nil; } static struct { char* name; int id; } sispci[] = { { "sis", (0x0900<<16)|0x1039, }, /* sis 900 */ { nil }, }; static int sispnp(Ether* edev) { int i, id; Pcidev *p; Ctlr *ctlr; uchar ea[Eaddrlen]; /* * Make a list of all ethernet controllers * if not already done. */ if(ctlrhead == nil){ p = nil; while(p = pcimatch(p, 0, 0)){ if(p->ccrb != 0x02 || p->ccru != 0) continue; ctlr = malloc(sizeof(Ctlr)); ctlr->pcidev = p; ctlr->id = (p->did<<16)|p->vid; if(ctlrhead != nil) ctlrtail->next = ctlr; else ctlrhead = ctlr; ctlrtail = ctlr; } } id = 0; for(i = 0; i < edev->nopt; i++){ if(cistrncmp(edev->opt[i], "id=", 3) == 0) id = strtol(&edev->opt[i][3], nil, 0); } ctlr = nil; if(id != 0) ctlr = sismatch(edev, id); else for(i = 0; sispci[i].name; i++){ if((ctlr = sismatch(edev, sispci[i].id)) != nil) break; } if(ctlr == nil) return -1; edev->ctlr = ctlr; edev->port = ctlr->port; edev->irq = ctlr->pcidev->intl; edev->tbdf = ctlr->pcidev->tbdf; memset(ea, 0, Eaddrlen); if(memcmp(ea, edev->ea, Eaddrlen) == 0){ if(ctlr->pcidev->rid == 0x82) { /* 630S revision ID supported */ rcmos(ctlr, edev); } else { print("sis: unsupported revision id.. cannot set mac address\n"); } } edev->attach = sisattach; edev->transmit = sistransmit; edev->interrupt = sisinterrupt; edev->ifstat = sisifstat; edev->arg = edev; edev->promiscuous = sispromiscuous; edev->mbps = 100; /* hardwire... */ return 0; } void ethersislink(void) { addethercard("sis", sispnp); }