/* * SMC EtherEZ (SMC91cXX chip) PCMCIA card support. */ #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 { IoSize = 0x10, /* port pool size */ TxTimeout = 150, }; enum { /* PCMCIA related */ TupleFunce = 0x22, TfNodeId = 0x04, }; enum { /* bank 0 registers */ Tcr = 0x0000, /* transmit control */ Eph = 0x0002, /* ethernet protocol handler */ Rcr = 0x0004, /* receiver control */ Counter = 0x0006, /* statistics counter */ MemInfo = 0x0008, MemCfg = 0x000A, }; enum { /* bank 1 registers */ Config = 0x0000, BaseAddr = 0x0002, Addr0 = 0x0004, /* ethernet address */ Addr1 = 0x0006, Addr2 = 0x0008, General = 0x000A, Control = 0x000C, }; enum { /* bank 2 registers */ MmuCmd = 0x0000, PktNo = 0x0002, AllocRes = 0x0003, FifoPorts = 0x0004, Pointer = 0x0006, Data1 = 0x0008, Interrupt = 0x000C, IntrMask = 0x000D, }; enum { /* bank 3 registers */ Mcast0 = 0x0000, Mcast2 = 0x0002, Mcast4 = 0x0004, Mcast6 = 0x0006, Revision = 0x000A, }; enum { BankSelect = 0x000E /* bank select register */ }; enum { BsrMask = 0xFF00, /* mask for chip identification */ BsrId = 0x3300, }; enum { /* Tcr values */ TcrClear = 0x0000, TcrEnable = 0x0001, /* enable transmit */ TcrLoop = 0x0002, /* enable internal analogue loopback */ TcrForceCol = 0x0004, /* force collision on next tx */ TcrPadEn = 0x0080, /* pad short packets to 64 bytes */ TcrNoCrc = 0x0100, /* do not append CRC */ TcrMonCns = 0x0400, /* monitor carrier status */ TcrFduplx = 0x0800, TcrStpSqet = 0x1000, TcrEphLoop = 0x2000, TcrNormal = TcrEnable, }; enum { /* Eph values */ EphTxOk = 0x0001, Eph1Col = 0x0002, /* single collision */ EphMCol = 0x0004, /* multiple collisions */ EphTxMcast = 0x0008, /* multicast transmit */ Eph16Col = 0x0010, /* 16 collisions, tx disabled */ EphSqet = 0x0020, /* SQE test failed, tx disabled */ EphTxBcast = 0x0040, /* broadcast tx */ EphDefr = 0x0080, /* deffered tx */ EphLatCol = 0x0200, /* late collision, tx disabled */ EphLostCarr = 0x0400, /* lost carrier, tx disabled */ EphExcDefr = 0x0800, /* excessive defferals */ EphCntRol = 0x1000, /* ECR counter(s) rolled over */ EphRxOvrn = 0x2000, /* receiver overrun, packets dropped */ EphLinkOk = 0x4000, EphTxUnrn = 0x8000, /* tx underrun */ }; enum { /* Rcr values */ RcrClear = 0x0000, RcrPromisc = 0x0002, RcrAllMcast = 0x0004, RcrEnable = 0x0100, RcrStripCrc = 0x0200, RcrSoftReset = 0x8000, RcrNormal = RcrStripCrc | RcrEnable, }; enum { /* Counter value masks */ CntColMask = 0x000F, /* collisions */ CntMColMask = 0x00F0, /* multiple collisions */ CntDtxMask = 0x0F00, /* deferred transmits */ CntExDtxMask = 0xF000, /* excessively deferred transmits */ CntColShr = 1, CntMColShr = 4, CntDtxShr = 8, }; enum { /* MemInfo value masks */ MirTotalMask = 0x00FF, MirFreeMask = 0xFF00, }; enum { /* Config values */ CfgIrqSel0 = 0x0002, CfgIrqSel1 = 0x0004, CfgDisLink = 0x0040, /* disable 10BaseT link test */ Cfg16Bit = 0x0080, CfgAuiSelect = 0x0100, CfgSetSqlch = 0x0200, CfgFullStep = 0x0400, CfgNoWait = 0x1000, CfgMiiSelect = 0x8000, }; enum { /* Control values */ CtlStore = 0x0001, /* store to EEPROM */ CtlReload = 0x0002, /* reload EEPROM into registers */ CtlEeSelect = 0x0004, /* select registers for reload/store */ CtlTeEnable = 0x0020, /* tx error detection via eph irq */ CtlCrEnable = 0x0040, /* counter rollover via eph irq */ CtlLeEnable = 0x0080, /* link error detection via eph irq*/ CtlAutoRls = 0x0800, /* auto release mode */ CtlPowerDn = 0x2000, }; enum { /* MmuCmd values */ McBusy = 0x0001, McAlloc = 0x0020, /* | with number of 256 byte packets - 1 */ McReset = 0x0040, McRelease = 0x0080, /* dequeue (but not free) current rx packet */ McFreePkt = 0x00A0, /* dequeue and free current rx packet */ McEnqueue = 0x00C0, /* enqueue the packet for tx */ McTxReset = 0x00E0, /* reset transmit queues */ }; enum { /* AllocRes values */ ArFailed = 0x80, }; enum { /* FifoPorts values */ FpTxEmpty = 0x0080, FpRxEmpty = 0x8000, FpTxMask = 0x007F, FpRxMask = 0x7F00, }; enum { /* Pointer values */ PtrRead = 0x2000, PtrAutoInc = 0x4000, PtrRcv = 0x8000, }; enum { /* Interrupt values */ IntRcv = 0x0001, IntTxError = 0x0002, IntTxEmpty = 0x0004, IntAlloc = 0x0008, IntRxOvrn = 0x0010, IntEph = 0x0020, }; enum { /* transmit status bits */ TsSuccess = 0x0001, Ts16Col = 0x00A0, TsLatCol = 0x0200, TsLostCar = 0x0400, }; enum { /* receive status bits */ RsMcast = 0x0001, RsTooShort = 0x0400, RsTooLong = 0x0800, RsOddFrame = 0x1000, RsBadCrc = 0x2000, RsAlgnErr = 0x8000, RsError = RsAlgnErr | RsBadCrc | RsTooLong | RsTooShort, }; enum { RxLenMask = 0x07FF, /* significant rx len bits */ HdrSize = 6, /* packet header length */ PageSize = 256, /* page length */ }; typedef struct Smc91xx Smc91xx; struct Smc91xx { Lock; ushort rev; int attached; Block *txbp; ulong txtime; ulong rovrn; ulong lcar; ulong col; ulong scol; ulong mcol; ulong lcol; ulong dfr; }; #define SELECT_BANK(x) outs(port + BankSelect, x) static int readnodeid(int slot, Ether* ether) { uchar data[Eaddrlen + 1]; int len; len = sizeof(data); if (pcmcistuple(slot, TupleFunce, TfNodeId, data, len) != len) return -1; if (data[0] != Eaddrlen) return -1; memmove(ether->ea, &data[1], Eaddrlen); return 0; } static void chipreset(Ether* ether) { int port; int i; port = ether->port; /* reset the chip */ SELECT_BANK(0); outs(port + Rcr, RcrSoftReset); delay(1); outs(port + Rcr, RcrClear); outs(port + Tcr, TcrClear); SELECT_BANK(1); outs(port + Control, CtlAutoRls | CtlTeEnable | CtlCrEnable); for(i = 0; i < 6; i++) { outb(port + Addr0 + i, ether->ea[i]); } SELECT_BANK(2); outs(port + MmuCmd, McReset); } static void chipenable(Ether* ether) { int port; port = ether->port; SELECT_BANK(0); outs(port + Tcr, TcrNormal); outs(port + Rcr, RcrNormal); SELECT_BANK(2); outb(port + IntrMask, IntEph | IntRxOvrn | IntRcv); } static void attach(Ether *ether) { Smc91xx* ctlr; ctlr = ether->ctlr; ilock(ctlr); if (ctlr->attached) { iunlock(ctlr); return; } chipenable(ether); ctlr->attached = 1; iunlock(ctlr); } static void txstart(Ether* ether) { int port; Smc91xx* ctlr; Block* bp; int len, npages; int pno; /* assumes ctlr is locked and bank 2 is selected */ /* leaves bank 2 selected on return */ port = ether->port; ctlr = ether->ctlr; if (ctlr->txbp) { bp = ctlr->txbp; ctlr->txbp = 0; } else { bp = qget(ether->oq); if (bp == 0) return; len = BLEN(bp); npages = (len + HdrSize) / PageSize; outs(port + MmuCmd, McAlloc | npages); } pno = inb(port + AllocRes); if (pno & ArFailed) { outb(port + IntrMask, inb(port + IntrMask) | IntAlloc); ctlr->txbp = bp; ctlr->txtime = MACHP(0)->ticks; return; } outb(port + PktNo, pno); outs(port + Pointer, PtrAutoInc); len = BLEN(bp); outs(port + Data1, 0); outb(port + Data1, (len + HdrSize) & 0xFF); outb(port + Data1, (len + HdrSize) >> 8); outss(port + Data1, bp->rp, len / 2); if ((len & 1) == 0) { outs(port + Data1, 0); } else { outb(port + Data1, bp->rp[len - 1]); outb(port + Data1, 0x20); /* no info what 0x20 means */ } outb(port + IntrMask, inb(port + IntrMask) | IntTxError | IntTxEmpty); outs(port + MmuCmd, McEnqueue); freeb(bp); } static void receive(Ether* ether) { int port; Block* bp; int pktno, status, len; /* assumes ctlr is locked and bank 2 is selected */ /* leaves bank 2 selected on return */ port = ether->port; pktno = ins(port + FifoPorts); if (pktno & FpRxEmpty) { return; } outs(port + Pointer, PtrRead | PtrRcv | PtrAutoInc); status = ins(port + Data1); len = ins(port + Data1) & RxLenMask - HdrSize; if (status & RsOddFrame) len++; if ((status & RsError) || (bp = iallocb(len)) == 0) { if (status & RsAlgnErr) ether->frames++; if (status & (RsTooShort | RsTooLong)) ether->buffs++; if (status & RsBadCrc) ether->crcs++; outs(port + MmuCmd, McRelease); return; } /* packet length is padded to word */ inss(port + Data1, bp->rp, len / 2); bp->wp = bp->rp + (len & ~1); if (len & 1) { *bp->wp = inb(port + Data1); bp->wp++; } etheriq(ether, bp, 1); ether->inpackets++; outs(port + MmuCmd, McRelease); } static void txerror(Ether* ether) { int port; Smc91xx* ctlr; int save_pkt; int pktno, status; /* assumes ctlr is locked and bank 2 is selected */ /* leaves bank 2 selected on return */ port = ether->port; ctlr = ether->ctlr; save_pkt = inb(port + PktNo); pktno = ins(port + FifoPorts) & FpTxMask; outb(port + PktNo, pktno); outs(port + Pointer, PtrAutoInc | PtrRead); status = ins(port + Data1); if (status & TsLostCar) ctlr->lcar++; if (status & TsLatCol) ctlr->lcol++; if (status & Ts16Col) ctlr->scol++; ether->oerrs++; SELECT_BANK(0); outs(port + Tcr, ins(port + Tcr) | TcrEnable); SELECT_BANK(2); outs(port + MmuCmd, McFreePkt); outb(port + PktNo, save_pkt); } static void eph_irq(Ether* ether) { int port; Smc91xx* ctlr; ushort status; int n; /* assumes ctlr is locked and bank 2 is selected */ /* leaves bank 2 selected on return */ port = ether->port; ctlr = ether->ctlr; SELECT_BANK(0); status = ins(port + Eph); if (status & EphCntRol) { /* read the counter register even if we don't need it */ /* otherwise we will keep getting this interrupt */ n = ins(port + Counter); ctlr->col += (n & CntColMask) >> CntColShr; ctlr->mcol += (n & CntMColMask) >> CntMColShr; ctlr->dfr += (n & CntDtxMask) >> CntDtxShr; } /* if there was a transmit error, Tcr is disabled */ outs(port + Tcr, ins(port + Tcr) | TcrEnable); /* clear a link error interrupt */ SELECT_BANK(1); outs(port + Control, CtlAutoRls); outs(port + Control, CtlAutoRls | CtlTeEnable | CtlCrEnable); SELECT_BANK(2); } static void transmit(Ether* ether) { Smc91xx* ctlr; int port, n; ctlr = ether->ctlr; port = ether->port; ilock(ctlr); if (ctlr->txbp) { n = TK2MS(MACHP(0)->ticks - ctlr->txtime); if (n > TxTimeout) { chipreset(ether); chipenable(ether); freeb(ctlr->txbp); ctlr->txbp = 0; } iunlock(ctlr); return; } SELECT_BANK(2); txstart(ether); iunlock(ctlr); } static void interrupt(Ureg*, void *arg) { int port; Smc91xx* ctlr; Ether* ether; int save_bank; int save_pointer; int mask, status; ether = arg; port = ether->port; ctlr = ether->ctlr; ilock(ctlr); save_bank = ins(port + BankSelect); SELECT_BANK(2); save_pointer = ins(port + Pointer); mask = inb(port + IntrMask); outb(port + IntrMask, 0); while ((status = inb(port + Interrupt) & mask) != 0) { if (status & IntRcv) { receive(ether); } if (status & IntTxError) { txerror(ether); } if (status & IntTxEmpty) { outb(port + Interrupt, IntTxEmpty); outb(port + IntrMask, mask & ~IntTxEmpty); txstart(ether); mask = inb(port + IntrMask); } if (status & IntAlloc) { outb(port + IntrMask, mask & ~IntAlloc); txstart(ether);; mask = inb(port + IntrMask); } if (status & IntRxOvrn) { ctlr->rovrn++; ether->misses++; outb(port + Interrupt,IntRxOvrn); } if (status & IntEph) eph_irq(ether); } outb(port + IntrMask, mask); outs(port + Pointer, save_pointer); outs(port + BankSelect, save_bank); iunlock(ctlr); } static void promiscuous(void* arg, int on) { int port; Smc91xx *ctlr; Ether* ether; ushort x; ether = arg; port = ether->port; ctlr = ether->ctlr; ilock(ctlr); SELECT_BANK(0); x = ins(port + Rcr); if (on) x |= RcrPromisc; else x &= ~RcrPromisc; outs(port + Rcr, x); iunlock(ctlr); } static void multicast(void* arg, uchar *addr, int on) { int port; Smc91xx*ctlr; Ether *ether; ushort x; USED(addr, on); ether = arg; port = ether->port; ctlr = ether->ctlr; ilock(ctlr); SELECT_BANK(0); x = ins(port + Rcr); if (ether->nmaddr) x |= RcrAllMcast; else x &= ~RcrAllMcast; outs(port + Rcr, x); iunlock(ctlr); } static long ifstat(Ether* ether, void* a, long n, ulong offset) { static char *chiprev[] = { [3] "92", [5] "95", [7] "100", [8] "100-FD", [9] "110", }; Smc91xx* ctlr; char* p; int r, len; char* s; if (n == 0) return 0; ctlr = ether->ctlr; p = malloc(READSTR); s = 0; if (ctlr->rev > 0) { r = ctlr->rev >> 4; if (r < nelem(chiprev)) s = chiprev[r]; if (r == 4) { if ((ctlr->rev & 0x0F) >= 6) s = "96"; else s = "94"; } } len = snprint(p, READSTR, "rev: 91c%s\n", (s) ? s : "???"); len += snprint(p + len, READSTR - len, "rxovrn: %uld\n", ctlr->rovrn); len += snprint(p + len, READSTR - len, "lcar: %uld\n", ctlr->lcar); len += snprint(p + len, READSTR - len, "col: %uld\n", ctlr->col); len += snprint(p + len, READSTR - len, "16col: %uld\n", ctlr->scol); len += snprint(p + len, READSTR - len, "mcol: %uld\n", ctlr->mcol); len += snprint(p + len, READSTR - len, "lcol: %uld\n", ctlr->lcol); len += snprint(p + len, READSTR - len, "dfr: %uld\n", ctlr->dfr); USED(len); n = readstr(offset, a, n, p); free(p); return n; } static int reset(Ether* ether) { int port; int i, x; char* type; Smc91xx* ctlr; int slot; uchar ea[Eaddrlen]; if (ether->irq == 0) ether->irq = 9; if (ether->port == 0) ether->port = 0x100; type = "8020"; for(i = 0; i < ether->nopt; i++) { if (cistrncmp(ether->opt[i], "id=", 3)) continue; type = ðer->opt[i][3]; break; } if ((slot = pcmspecial(type, ether)) < 0) return -1; if (ioalloc(ether->port, IoSize, 0, "smc91cXX") < 0) { pcmspecialclose(slot); return -1; } ether->ctlr = malloc(sizeof(Smc91xx)); ctlr = ether->ctlr; if (ctlr == 0) { iofree(ether->port); pcmspecialclose(slot); return -1; } ilock(ctlr); ctlr->rev = 0; ctlr->txbp = nil; ctlr->attached = 0; ctlr->rovrn = 0; ctlr->lcar = 0; ctlr->col = 0; ctlr->scol = 0; ctlr->mcol = 0; ctlr->lcol = 0; ctlr->dfr = 0; port = ether->port; SELECT_BANK(1); if ((ins(port + BankSelect) & BsrMask) != BsrId) { outs(port + Control, 0); /* try powering up the chip */ delay(55); } outs(port + Config, ins(port + Config) | Cfg16Bit); x = ins(port + BaseAddr); if (((ins(port + BankSelect) & BsrMask) != BsrId) || ((x >> 8) == (x & 0xFF))) { iunlock(ctlr); iofree(port); pcmspecialclose(slot); return -1; } SELECT_BANK(3); ctlr->rev = ins(port + Revision) & 0xFF; memset(ea, 0, Eaddrlen); if (memcmp(ea, ether->ea, Eaddrlen) == 0) { if (readnodeid(slot, ether) < 0) { print("Smc91cXX: cannot find ethernet address\n"); iunlock(ctlr); iofree(port); pcmspecialclose(slot); return -1; } } chipreset(ether); ether->attach = attach; ether->transmit = transmit; ether->interrupt = interrupt; ether->ifstat = ifstat; ether->promiscuous = promiscuous; ether->multicast = multicast; ether->arg = ether; iunlock(ctlr); return 0; } void ethersmclink(void) { addethercard("smc91cXX", reset); }