Subversion Repositories planix.SVN

Rev

Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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, *s;
        int r, len;
        
        if (n == 0)
                return 0;

        ctlr = ether->ctlr;
        p = malloc(READSTR);
        if(p == nil)
                error(Enomem);

        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 = &ether->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->shutdown = chipreset;
        ether->arg = ether;
        iunlock(ctlr);
        return 0;
}

void
ethersmclink(void)
{
        addethercard("smc91cXX", reset);
}