diff options
author | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
---|---|---|
committer | Taru Karttunen <taruti@taruti.net> | 2011-03-30 15:46:40 +0300 |
commit | e5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch) | |
tree | d8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/9/pc/ethersmc.c |
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/9/pc/ethersmc.c')
-rwxr-xr-x | sys/src/9/pc/ethersmc.c | 781 |
1 files changed, 781 insertions, 0 deletions
diff --git a/sys/src/9/pc/ethersmc.c b/sys/src/9/pc/ethersmc.c new file mode 100755 index 000000000..65ebffcac --- /dev/null +++ b/sys/src/9/pc/ethersmc.c @@ -0,0 +1,781 @@ +/* + * 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); +} |