Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Digital Semiconductor DECchip 2114x PCI Fast Ethernet LAN Controller.
 * To do:
 *      thresholds;
 *      ring sizing;
 *      handle more error conditions;
 *      tidy setup packet mess;
 *      push initialisation back to attach;
 *      full SROM decoding.
 */
#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"

#define DEBUG           (0)
#define debug           if(DEBUG)print

enum {
        Nrde            = 64,
        Ntde            = 64,
};

#define Rbsz            ROUNDUP(sizeof(Etherpkt)+4, 4)

enum {                                  /* CRS0 - Bus Mode */
        Swr             = 0x00000001,   /* Software Reset */
        Bar             = 0x00000002,   /* Bus Arbitration */
        Dsl             = 0x0000007C,   /* Descriptor Skip Length (field) */
        Ble             = 0x00000080,   /* Big/Little Endian */
        Pbl             = 0x00003F00,   /* Programmable Burst Length (field) */
        Cal             = 0x0000C000,   /* Cache Alignment (field) */
        Cal8            = 0x00004000,   /* 8 longword boundary alignment */
        Cal16           = 0x00008000,   /* 16 longword boundary alignment */
        Cal32           = 0x0000C000,   /* 32 longword boundary alignment */
        Tap             = 0x000E0000,   /* Transmit Automatic Polling (field) */
        Dbo             = 0x00100000,   /* Descriptor Byte Ordering Mode */
        Rml             = 0x00200000,   /* Read Multiple */
}; 

enum {                                  /* CSR[57] - Status and Interrupt Enable */
        Ti              = 0x00000001,   /* Transmit Interrupt */
        Tps             = 0x00000002,   /* Transmit Process Stopped */
        Tu              = 0x00000004,   /* Transmit buffer Unavailable */
        Tjt             = 0x00000008,   /* Transmit Jabber Timeout */
        Unf             = 0x00000020,   /* transmit UNderFlow */
        Ri              = 0x00000040,   /* Receive Interrupt */
        Ru              = 0x00000080,   /* Receive buffer Unavailable */
        Rps             = 0x00000100,   /* Receive Process Stopped */
        Rwt             = 0x00000200,   /* Receive Watchdog Timeout */
        Eti             = 0x00000400,   /* Early Transmit Interrupt */
        Gte             = 0x00000800,   /* General purpose Timer Expired */
        Fbe             = 0x00002000,   /* Fatal Bit Error */
        Ais             = 0x00008000,   /* Abnormal Interrupt Summary */
        Nis             = 0x00010000,   /* Normal Interrupt Summary */
        Rs              = 0x000E0000,   /* Receive process State (field) */
        Ts              = 0x00700000,   /* Transmit process State (field) */
        Eb              = 0x03800000,   /* Error bits */
};

enum {                                  /* CSR6 - Operating Mode */
        Hp              = 0x00000001,   /* Hash/Perfect receive filtering mode */
        Sr              = 0x00000002,   /* Start/stop Receive */
        Ho              = 0x00000004,   /* Hash-Only filtering mode */
        Pb              = 0x00000008,   /* Pass Bad frames */
        If              = 0x00000010,   /* Inverse Filtering */
        Sb              = 0x00000020,   /* Start/stop Backoff counter */
        Pr              = 0x00000040,   /* Promiscuous Mode */
        Pm              = 0x00000080,   /* Pass all Multicast */
        Fd              = 0x00000200,   /* Full Duplex mode */
        Om              = 0x00000C00,   /* Operating Mode (field) */
        Fc              = 0x00001000,   /* Force Collision */
        St              = 0x00002000,   /* Start/stop Transmission Command */
        Tr              = 0x0000C000,   /* ThReshold control bits (field) */
        Tr128           = 0x00000000,
        Tr256           = 0x00004000,
        Tr512           = 0x00008000,
        Tr1024          = 0x0000C000,
        Ca              = 0x00020000,   /* CApture effect enable */
        Ps              = 0x00040000,   /* Port Select */
        Hbd             = 0x00080000,   /* HeartBeat Disable */
        Imm             = 0x00100000,   /* IMMediate mode */
        Sf              = 0x00200000,   /* Store and Forward */
        Ttm             = 0x00400000,   /* Transmit Threshold Mode */
        Pcs             = 0x00800000,   /* PCS function */
        Scr             = 0x01000000,   /* SCRambler mode */
        Mbo             = 0x02000000,   /* Must Be One */
        Ra              = 0x40000000,   /* Receive All */
        Sc              = 0x80000000,   /* Special Capture effect enable */

        TrMODE          = Tr512,        /* default transmission threshold */
};

enum {                                  /* CSR9 - ROM and MII Management */
        Scs             = 0x00000001,   /* serial ROM chip select */
        Sclk            = 0x00000002,   /* serial ROM clock */
        Sdi             = 0x00000004,   /* serial ROM data in */
        Sdo             = 0x00000008,   /* serial ROM data out */
        Ss              = 0x00000800,   /* serial ROM select */
        Wr              = 0x00002000,   /* write */
        Rd              = 0x00004000,   /* read */

        Mdc             = 0x00010000,   /* MII management clock */
        Mdo             = 0x00020000,   /* MII management write data */
        Mii             = 0x00040000,   /* MII management operation mode (W) */
        Mdi             = 0x00080000,   /* MII management data in */
};

enum {                                  /* CSR12 - General-Purpose Port */
        Gpc             = 0x00000100,   /* General Purpose Control */
};

typedef struct Des {
        int     status;
        int     control;
        ulong   addr;
        Block*  bp;
} Des;

enum {                                  /* status */
        Of              = 0x00000001,   /* Rx: OverFlow */
        Ce              = 0x00000002,   /* Rx: CRC Error */
        Db              = 0x00000004,   /* Rx: Dribbling Bit */
        Re              = 0x00000008,   /* Rx: Report on MII Error */
        Rw              = 0x00000010,   /* Rx: Receive Watchdog */
        Ft              = 0x00000020,   /* Rx: Frame Type */
        Cs              = 0x00000040,   /* Rx: Collision Seen */
        Tl              = 0x00000080,   /* Rx: Frame too Long */
        Ls              = 0x00000100,   /* Rx: Last deScriptor */
        Fs              = 0x00000200,   /* Rx: First deScriptor */
        Mf              = 0x00000400,   /* Rx: Multicast Frame */
        Rf              = 0x00000800,   /* Rx: Runt Frame */
        Dt              = 0x00003000,   /* Rx: Data Type (field) */
        De              = 0x00004000,   /* Rx: Descriptor Error */
        Fl              = 0x3FFF0000,   /* Rx: Frame Length (field) */
        Ff              = 0x40000000,   /* Rx: Filtering Fail */

        Def             = 0x00000001,   /* Tx: DEFerred */
        Uf              = 0x00000002,   /* Tx: UnderFlow error */
        Lf              = 0x00000004,   /* Tx: Link Fail report */
        Cc              = 0x00000078,   /* Tx: Collision Count (field) */
        Hf              = 0x00000080,   /* Tx: Heartbeat Fail */
        Ec              = 0x00000100,   /* Tx: Excessive Collisions */
        Lc              = 0x00000200,   /* Tx: Late Collision */
        Nc              = 0x00000400,   /* Tx: No Carrier */
        Lo              = 0x00000800,   /* Tx: LOss of carrier */
        To              = 0x00004000,   /* Tx: Transmission jabber timeOut */

        Es              = 0x00008000,   /* [RT]x: Error Summary */
        Own             = 0x80000000,   /* [RT]x: OWN bit */
};

enum {                                  /* control */
        Bs1             = 0x000007FF,   /* [RT]x: Buffer 1 Size */
        Bs2             = 0x003FF800,   /* [RT]x: Buffer 2 Size */

        Ch              = 0x01000000,   /* [RT]x: second address CHained */
        Er              = 0x02000000,   /* [RT]x: End of Ring */

        Ft0             = 0x00400000,   /* Tx: Filtering Type 0 */
        Dpd             = 0x00800000,   /* Tx: Disabled PaDding */
        Ac              = 0x04000000,   /* Tx: Add CRC disable */
        Set             = 0x08000000,   /* Tx: SETup packet */
        Ft1             = 0x10000000,   /* Tx: Filtering Type 1 */
        Fseg            = 0x20000000,   /* Tx: First SEGment */
        Lseg            = 0x40000000,   /* Tx: Last SEGment */
        Ic              = 0x80000000,   /* Tx: Interrupt on Completion */
};

enum {                                  /* PHY registers */
        Bmcr            = 0,            /* Basic Mode Control */
        Bmsr            = 1,            /* Basic Mode Status */
        Phyidr1         = 2,            /* PHY Identifier #1 */
        Phyidr2         = 3,            /* PHY Identifier #2 */
        Anar            = 4,            /* Auto-Negotiation Advertisment */
        Anlpar          = 5,            /* Auto-Negotiation Link Partner Ability */
        Aner            = 6,            /* Auto-Negotiation Expansion */
};

enum {                                  /* Variants */
        Tulip0          = (0x0009<<16)|0x1011,
        Tulip1          = (0x0014<<16)|0x1011,
        Tulip3          = (0x0019<<16)|0x1011,
        Pnic            = (0x0002<<16)|0x11AD,
        Pnic2           = (0xC115<<16)|0x11AD,
        CentaurP        = (0x0985<<16)|0x1317,
        CentaurPcb      = (0x1985<<16)|0x1317,
};

typedef struct Ctlr Ctlr;
typedef struct Ctlr {
        int     port;
        Pcidev* pcidev;
        Ctlr*   next;
        int     active;
        int     id;                     /* (pcidev->did<<16)|pcidev->vid */

        uchar*  srom;
        int     sromsz;                 /* address size in bits */
        uchar*  sromea;                 /* MAC address */
        uchar*  leaf;
        int     sct;                    /* selected connection type */
        int     k;                      /* info block count */
        uchar*  infoblock[16];
        int     sctk;                   /* sct block index */
        int     curk;                   /* current block index */
        uchar*  type5block;

        int     phy[32];                /* logical to physical map */
        int     phyreset;               /* reset bitmap */
        int     curphyad;
        int     fdx;
        int     ttm;

        uchar   fd;                     /* option */
        int     medium;                 /* option */

        int     csr6;                   /* CSR6 - operating mode */
        int     mask;                   /* CSR[57] - interrupt mask */
        int     mbps;

        Lock    lock;

        Des*    rdr;                    /* receive descriptor ring */
        int     nrdr;                   /* size of rdr */
        int     rdrx;                   /* index into rdr */

        Lock    tlock;
        Des*    tdr;                    /* transmit descriptor ring */
        int     ntdr;                   /* size of tdr */
        int     tdrh;                   /* host index into tdr */
        int     tdri;                   /* interface index into tdr */
        int     ntq;                    /* descriptors active */
        int     ntqmax;
        Block*  setupbp;

        ulong   of;                     /* receive statistics */
        ulong   ce;
        ulong   cs;
        ulong   tl;
        ulong   rf;
        ulong   de;

        ulong   ru;
        ulong   rps;
        ulong   rwt;

        ulong   uf;                     /* transmit statistics */
        ulong   ec;
        ulong   lc;
        ulong   nc;
        ulong   lo;
        ulong   to;

        ulong   tps;
        ulong   tu;
        ulong   tjt;
        ulong   unf;
} Ctlr;

static Ctlr* ctlrhead;
static Ctlr* ctlrtail;

#define csr32r(c, r)    (inl((c)->port+((r)*8)))
#define csr32w(c, r, l) (outl((c)->port+((r)*8), (ulong)(l)))

static void
promiscuous(void* arg, int on)
{
        Ctlr *ctlr;

        ctlr = ((Ether*)arg)->ctlr;
        ilock(&ctlr->lock);
        if(on)
                ctlr->csr6 |= Pr;
        else
                ctlr->csr6 &= ~Pr;
        csr32w(ctlr, 6, ctlr->csr6);
        iunlock(&ctlr->lock);
}

/* multicast already on, don't need to do anything */
static void
multicast(void*, uchar*, int)
{
}

static void
attach(Ether* ether)
{
        Ctlr *ctlr;

        ctlr = ether->ctlr;
        ilock(&ctlr->lock);
        if(!(ctlr->csr6 & Sr)){
                ctlr->csr6 |= Sr;
                csr32w(ctlr, 6, ctlr->csr6);
        }
        iunlock(&ctlr->lock);
}

static long
ifstat(Ether* ether, void* a, long n, ulong offset)
{
        Ctlr *ctlr;
        char *buf, *p;
        int i, l, len;

        ctlr = ether->ctlr;

        ether->crcs = ctlr->ce;
        ether->frames = ctlr->rf+ctlr->cs;
        ether->buffs = ctlr->de+ctlr->tl;
        ether->overflows = ctlr->of;

        if(n == 0)
                return 0;

        p = malloc(READSTR);
        if(p == nil)
                error(Enomem);
        l = snprint(p, READSTR, "Overflow: %lud\n", ctlr->of);
        l += snprint(p+l, READSTR-l, "Ru: %lud\n", ctlr->ru);
        l += snprint(p+l, READSTR-l, "Rps: %lud\n", ctlr->rps);
        l += snprint(p+l, READSTR-l, "Rwt: %lud\n", ctlr->rwt);
        l += snprint(p+l, READSTR-l, "Tps: %lud\n", ctlr->tps);
        l += snprint(p+l, READSTR-l, "Tu: %lud\n", ctlr->tu);
        l += snprint(p+l, READSTR-l, "Tjt: %lud\n", ctlr->tjt);
        l += snprint(p+l, READSTR-l, "Unf: %lud\n", ctlr->unf);
        l += snprint(p+l, READSTR-l, "CRC Error: %lud\n", ctlr->ce);
        l += snprint(p+l, READSTR-l, "Collision Seen: %lud\n", ctlr->cs);
        l += snprint(p+l, READSTR-l, "Frame Too Long: %lud\n", ctlr->tl);
        l += snprint(p+l, READSTR-l, "Runt Frame: %lud\n", ctlr->rf);
        l += snprint(p+l, READSTR-l, "Descriptor Error: %lud\n", ctlr->de);
        l += snprint(p+l, READSTR-l, "Underflow Error: %lud\n", ctlr->uf);
        l += snprint(p+l, READSTR-l, "Excessive Collisions: %lud\n", ctlr->ec);
        l += snprint(p+l, READSTR-l, "Late Collision: %lud\n", ctlr->lc);
        l += snprint(p+l, READSTR-l, "No Carrier: %lud\n", ctlr->nc);
        l += snprint(p+l, READSTR-l, "Loss of Carrier: %lud\n", ctlr->lo);
        l += snprint(p+l, READSTR-l, "Transmit Jabber Timeout: %lud\n",
                ctlr->to);
        l += snprint(p+l, READSTR-l, "csr6: %luX %uX\n", csr32r(ctlr, 6),
                ctlr->csr6);
        snprint(p+l, READSTR-l, "ntqmax: %d\n", ctlr->ntqmax);
        ctlr->ntqmax = 0;
        buf = a;
        len = readstr(offset, buf, n, p);
        if(offset > l)
                offset -= l;
        else
                offset = 0;
        buf += len;
        n -= len;

        l = snprint(p, READSTR, "srom:");
        for(i = 0; i < (1<<(ctlr->sromsz)*sizeof(ushort)); i++){
                if(i && ((i & 0x0F) == 0))
                        l += snprint(p+l, READSTR-l, "\n     ");
                l += snprint(p+l, READSTR-l, " %2.2uX", ctlr->srom[i]);
        }

        snprint(p+l, READSTR-l, "\n");
        len += readstr(offset, buf, n, p);
        free(p);

        return len;
}

static void
txstart(Ether* ether)
{
        Ctlr *ctlr;
        Block *bp;
        Des *des;
        int control;

        ctlr = ether->ctlr;
        while(ctlr->ntq < (ctlr->ntdr-1)){
                if(ctlr->setupbp){
                        bp = ctlr->setupbp;
                        ctlr->setupbp = 0;
                        control = Ic|Set|BLEN(bp);
                }
                else{
                        bp = qget(ether->oq);
                        if(bp == nil)
                                break;
                        control = Ic|Lseg|Fseg|BLEN(bp);
                }

                ctlr->tdr[PREV(ctlr->tdrh, ctlr->ntdr)].control &= ~Ic;
                des = &ctlr->tdr[ctlr->tdrh];
                des->bp = bp;
                des->addr = PCIWADDR(bp->rp);
                des->control |= control;
                ctlr->ntq++;
                coherence();
                des->status = Own;
                csr32w(ctlr, 1, 0);
                ctlr->tdrh = NEXT(ctlr->tdrh, ctlr->ntdr);
        }

        if(ctlr->ntq > ctlr->ntqmax)
                ctlr->ntqmax = ctlr->ntq;
}

static void
transmit(Ether* ether)
{
        Ctlr *ctlr;

        ctlr = ether->ctlr;
        ilock(&ctlr->tlock);
        txstart(ether);
        iunlock(&ctlr->tlock);
}

static void
interrupt(Ureg*, void* arg)
{
        Ctlr *ctlr;
        Ether *ether;
        int len, status;
        Des *des;
        Block *bp;

        ether = arg;
        ctlr = ether->ctlr;

        while((status = csr32r(ctlr, 5)) & (Nis|Ais)){
                /*
                 * Acknowledge the interrupts and mask-out
                 * the ones that are implicitly handled.
                 */
                csr32w(ctlr, 5, status);
                status &= (ctlr->mask & ~(Nis|Ti));

                if(status & Ais){
                        if(status & Tps)
                                ctlr->tps++;
                        if(status & Tu)
                                ctlr->tu++;
                        if(status & Tjt)
                                ctlr->tjt++;
                        if(status & Ru)
                                ctlr->ru++;
                        if(status & Rps)
                                ctlr->rps++;
                        if(status & Rwt)
                                ctlr->rwt++;
                        status &= ~(Ais|Rwt|Rps|Ru|Tjt|Tu|Tps);
                }

                /*
                 * Received packets.
                 */
                if(status & Ri){
                        des = &ctlr->rdr[ctlr->rdrx];
                        while(!(des->status & Own)){
                                if(des->status & Es){
                                        if(des->status & Of)
                                                ctlr->of++;
                                        if(des->status & Ce)
                                                ctlr->ce++;
                                        if(des->status & Cs)
                                                ctlr->cs++;
                                        if(des->status & Tl)
                                                ctlr->tl++;
                                        if(des->status & Rf)
                                                ctlr->rf++;
                                        if(des->status & De)
                                                ctlr->de++;
                                }
                                else if(bp = iallocb(Rbsz)){
                                        len = ((des->status & Fl)>>16)-4;
                                        des->bp->wp = des->bp->rp+len;
                                        etheriq(ether, des->bp, 1);
                                        des->bp = bp;
                                        des->addr = PCIWADDR(bp->rp);
                                }

                                des->control &= Er;
                                des->control |= Rbsz;
                                coherence();
                                des->status = Own;

                                ctlr->rdrx = NEXT(ctlr->rdrx, ctlr->nrdr);
                                des = &ctlr->rdr[ctlr->rdrx];
                        }
                        status &= ~Ri;
                }

                /*
                 * Check the transmit side:
                 *      check for Transmit Underflow and Adjust
                 *      the threshold upwards;
                 *      free any transmitted buffers and try to
                 *      top-up the ring.
                 */
                if(status & Unf){
                        ctlr->unf++;
                        ilock(&ctlr->lock);
                        csr32w(ctlr, 6, ctlr->csr6 & ~St);
                        switch(ctlr->csr6 & Tr){
                        case Tr128:
                                len = Tr256;
                                break;
                        case Tr256:
                                len = Tr512;
                                break;
                        case Tr512:
                                len = Tr1024;
                                break;
                        default:
                        case Tr1024:
                                len = Sf;
                                break;
                        }
                        ctlr->csr6 = (ctlr->csr6 & ~Tr)|len;
                        csr32w(ctlr, 6, ctlr->csr6);
                        iunlock(&ctlr->lock);
                        csr32w(ctlr, 5, Tps);
                        status &= ~(Unf|Tps);
                }

                ilock(&ctlr->tlock);
                while(ctlr->ntq){
                        des = &ctlr->tdr[ctlr->tdri];
                        if(des->status & Own)
                                break;

                        if(des->status & Es){
                                if(des->status & Uf)
                                        ctlr->uf++;
                                if(des->status & Ec)
                                        ctlr->ec++;
                                if(des->status & Lc)
                                        ctlr->lc++;
                                if(des->status & Nc)
                                        ctlr->nc++;
                                if(des->status & Lo)
                                        ctlr->lo++;
                                if(des->status & To)
                                        ctlr->to++;
                                ether->oerrs++;
                        }

                        freeb(des->bp);
                        des->control &= Er;

                        ctlr->ntq--;
                        ctlr->tdri = NEXT(ctlr->tdri, ctlr->ntdr);
                }
                txstart(ether);
                iunlock(&ctlr->tlock);

                /*
                 * Anything left not catered for?
                 */
                if(status)
                        panic("#l%d: status %8.8uX\n", ether->ctlrno, status);
        }
}

static void
ctlrinit(Ether* ether)
{
        Ctlr *ctlr;
        Des *des;
        Block *bp;
        int i;
        uchar bi[Eaddrlen*2];

        ctlr = ether->ctlr;

        /*
         * Allocate and initialise the receive ring;
         * allocate and initialise the transmit ring;
         * unmask interrupts and start the transmit side;
         * create and post a setup packet to initialise
         * the physical ethernet address.
         */
        ctlr->rdr = xspanalloc(ctlr->nrdr*sizeof(Des), 8*sizeof(ulong), 0);
        for(des = ctlr->rdr; des < &ctlr->rdr[ctlr->nrdr]; des++){
                des->bp = iallocb(Rbsz);
                if(des->bp == nil)
                        panic("can't allocate ethernet receive ring\n");
                des->status = Own;
                des->control = Rbsz;
                des->addr = PCIWADDR(des->bp->rp);
        }
        ctlr->rdr[ctlr->nrdr-1].control |= Er;
        ctlr->rdrx = 0;
        csr32w(ctlr, 3, PCIWADDR(ctlr->rdr));

        ctlr->tdr = xspanalloc(ctlr->ntdr*sizeof(Des), 8*sizeof(ulong), 0);
        ctlr->tdr[ctlr->ntdr-1].control |= Er;
        ctlr->tdrh = 0;
        ctlr->tdri = 0;
        csr32w(ctlr, 4, PCIWADDR(ctlr->tdr));

        /*
         * Clear any bits in the Status Register (CSR5) as
         * the PNIC has a different reset value from a true 2114x.
         */
        ctlr->mask = Nis|Ais|Fbe|Rwt|Rps|Ru|Ri|Unf|Tjt|Tps|Ti;
        csr32w(ctlr, 5, ctlr->mask);
        csr32w(ctlr, 7, ctlr->mask);
        ctlr->csr6 |= St|Pm;
        csr32w(ctlr, 6, ctlr->csr6);

        for(i = 0; i < Eaddrlen/2; i++){
                bi[i*4] = ether->ea[i*2];
                bi[i*4+1] = ether->ea[i*2+1];
                bi[i*4+2] = ether->ea[i*2+1];
                bi[i*4+3] = ether->ea[i*2];
        }
        bp = iallocb(Eaddrlen*2*16);
        if(bp == nil)
                panic("can't allocate ethernet setup buffer\n");
        memset(bp->rp, 0xFF, sizeof(bi));
        for(i = sizeof(bi); i < sizeof(bi)*16; i += sizeof(bi))
                memmove(bp->rp+i, bi, sizeof(bi));
        bp->wp += sizeof(bi)*16;

        ctlr->setupbp = bp;
        ether->oq = qopen(64*1024, Qmsg, 0, 0);
        transmit(ether);
}

static void
csr9w(Ctlr* ctlr, int data)
{
        csr32w(ctlr, 9, data);
        microdelay(1);
}

static int
miimdi(Ctlr* ctlr, int n)
{
        int data, i;

        /*
         * Read n bits from the MII Management Register.
         */
        data = 0;
        for(i = n-1; i >= 0; i--){
                if(csr32r(ctlr, 9) & Mdi)
                        data |= (1<<i);
                csr9w(ctlr, Mii|Mdc);
                csr9w(ctlr, Mii);
        }
        csr9w(ctlr, 0);

        return data;
}

static void
miimdo(Ctlr* ctlr, int bits, int n)
{
        int i, mdo;

        /*
         * Write n bits to the MII Management Register.
         */
        for(i = n-1; i >= 0; i--){
                if(bits & (1<<i))
                        mdo = Mdo;
                else
                        mdo = 0;
                csr9w(ctlr, mdo);
                csr9w(ctlr, mdo|Mdc);
                csr9w(ctlr, mdo);
        }
}

static int
miir(Ctlr* ctlr, int phyad, int regad)
{
        int data, i;

        if(ctlr->id == Pnic){
                i = 1000;
                csr32w(ctlr, 20, 0x60020000|(phyad<<23)|(regad<<18));
                do{
                        microdelay(1);
                        data = csr32r(ctlr, 20);
                }while((data & 0x80000000) && --i);

                if(i == 0)
                        return -1;
                return data & 0xFFFF;
        }

        /*
         * Preamble;
         * ST+OP+PHYAD+REGAD;
         * TA + 16 data bits.
         */
        miimdo(ctlr, 0xFFFFFFFF, 32);
        miimdo(ctlr, 0x1800|(phyad<<5)|regad, 14);
        data = miimdi(ctlr, 18);

        if(data & 0x10000)
                return -1;

        return data & 0xFFFF;
}

static void
miiw(Ctlr* ctlr, int phyad, int regad, int data)
{
        /*
         * Preamble;
         * ST+OP+PHYAD+REGAD+TA + 16 data bits;
         * Z.
         */
        miimdo(ctlr, 0xFFFFFFFF, 32);
        data &= 0xFFFF;
        data |= (0x05<<(5+5+2+16))|(phyad<<(5+2+16))|(regad<<(2+16))|(0x02<<16);
        miimdo(ctlr, data, 32);
        csr9w(ctlr, Mdc);
        csr9w(ctlr, 0);
}

static int
sromr(Ctlr* ctlr, int r)
{
        int i, op, data, size;

        if(ctlr->id == Pnic){
                i = 1000;
                csr32w(ctlr, 19, 0x600|r);
                do{
                        microdelay(1);
                        data = csr32r(ctlr, 19);
                }while((data & 0x80000000) && --i);

                if(ctlr->sromsz == 0)
                        ctlr->sromsz = 6;

                return csr32r(ctlr, 9) & 0xFFFF;
        }

        /*
         * This sequence for reading a 16-bit register 'r'
         * in the EEPROM is taken (pretty much) straight from Section
         * 7.4 of the 21140 Hardware Reference Manual.
         */
reread:
        csr9w(ctlr, Rd|Ss);
        csr9w(ctlr, Rd|Ss|Scs);
        csr9w(ctlr, Rd|Ss|Sclk|Scs);
        csr9w(ctlr, Rd|Ss);

        op = 0x06;
        for(i = 3-1; i >= 0; i--){
                data = Rd|Ss|(((op>>i) & 0x01)<<2)|Scs;
                csr9w(ctlr, data);
                csr9w(ctlr, data|Sclk);
                csr9w(ctlr, data);
        }

        /*
         * First time through must work out the EEPROM size.
         * This doesn't seem to work on the 21041 as implemented
         * in Virtual PC for the Mac, so wire any 21041 to 6,
         * it's the only 21041 this code will ever likely see.
         */
        if((size = ctlr->sromsz) == 0){
                if(ctlr->id == Tulip1)
                        ctlr->sromsz = size = 6;
                else
                        size = 8;
        }

        for(size = size-1; size >= 0; size--){
                data = Rd|Ss|(((r>>size) & 0x01)<<2)|Scs;
                csr9w(ctlr, data);
                csr9w(ctlr, data|Sclk);
                csr9w(ctlr, data);
                microdelay(1);
                if(ctlr->sromsz == 0 && !(csr32r(ctlr, 9) & Sdo))
                        break;
        }

        data = 0;
        for(i = 16-1; i >= 0; i--){
                csr9w(ctlr, Rd|Ss|Sclk|Scs);
                if(csr32r(ctlr, 9) & Sdo)
                        data |= (1<<i);
                csr9w(ctlr, Rd|Ss|Scs);
        }

        csr9w(ctlr, 0);

        if(ctlr->sromsz == 0){
                ctlr->sromsz = 8-size;
                goto reread;
        }

        return data & 0xFFFF;
}

static void
shutdown(Ether* ether)
{
        Ctlr *ctlr = ether->ctlr;

print("ether2114x shutting down\n");
        csr32w(ctlr, 0, Swr);
}

static void
softreset(Ctlr* ctlr)
{
        /*
         * Soft-reset the controller and initialise bus mode.
         * Delay should be >= 50 PCI cycles (2×S @ 25MHz).
         */
        csr32w(ctlr, 0, Swr);
        microdelay(10);
        csr32w(ctlr, 0, Rml|Cal16);
        delay(1);
}

static int
type5block(Ctlr* ctlr, uchar* block)
{
        int csr15, i, len;

        /*
         * Reset or GPR sequence. Reset should be once only,
         * before the GPR sequence.
         * Note 'block' is not a pointer to the block head but
         * a pointer to the data in the block starting at the
         * reset length value so type5block can be used for the
         * sequences contained in type 1 and type 3 blocks.
         * The SROM docs state the 21140 type 5 block is the
         * same as that for the 21143, but the two controllers
         * use different registers and sequence-element lengths
         * so the 21140 code here is a guess for a real type 5
         * sequence.
         */
        len = *block++;
        if(ctlr->id != Tulip3){
                for(i = 0; i < len; i++){
                        csr32w(ctlr, 12, *block);
                        block++;
                }
                return len;
        }

        for(i = 0; i < len; i++){
                csr15 = *block++<<16;
                csr15 |= *block++<<24;
                csr32w(ctlr, 15, csr15);
                debug("%8.8uX ", csr15);
        }
        return 2*len;
}

static int
typephylink(Ctlr* ctlr, uchar*)
{
        int an, bmcr, bmsr, csr6, x;

        /*
         * Fail if
         *      auto-negotiataion enabled but not complete;
         *      no valid link established.
         */
        bmcr = miir(ctlr, ctlr->curphyad, Bmcr);
        miir(ctlr, ctlr->curphyad, Bmsr);
        bmsr = miir(ctlr, ctlr->curphyad, Bmsr);
        debug("bmcr 0x%2.2uX bmsr 0x%2.2uX\n", bmcr, bmsr);
        if(((bmcr & 0x1000) && !(bmsr & 0x0020)) || !(bmsr & 0x0004))
                return 0;

        if(bmcr & 0x1000){
                an = miir(ctlr, ctlr->curphyad, Anar);
                an &= miir(ctlr, ctlr->curphyad, Anlpar) & 0x3E0;
                debug("an 0x%2.uX 0x%2.2uX 0x%2.2uX\n",
                        miir(ctlr, ctlr->curphyad, Anar),
                        miir(ctlr, ctlr->curphyad, Anlpar),
                        an);
        
                if(an & 0x0100)
                        x = 0x4000;
                else if(an & 0x0080)
                        x = 0x2000;
                else if(an & 0x0040)
                        x = 0x1000;
                else if(an & 0x0020)
                        x = 0x0800;
                else
                        x = 0;
        }
        else if((bmcr & 0x2100) == 0x2100)
                x = 0x4000;
        else if(bmcr & 0x2000){
                /*
                 * If FD capable, force it if necessary.
                 */
                if((bmsr & 0x4000) && ctlr->fd){
                        miiw(ctlr, ctlr->curphyad, Bmcr, 0x2100);
                        x = 0x4000;
                }
                else
                        x = 0x2000;
        }
        else if(bmcr & 0x0100)
                x = 0x1000;
        else
                x = 0x0800;

        csr6 = Sc|Mbo|Hbd|Ps|Ca|TrMODE|Sb;
        if(ctlr->fdx & x)
                csr6 |= Fd;
        if(ctlr->ttm & x)
                csr6 |= Ttm;
        debug("csr6 0x%8.8uX 0x%8.8uX 0x%8.8luX\n",
                csr6, ctlr->csr6, csr32r(ctlr, 6));
        if(csr6 != ctlr->csr6){
                ctlr->csr6 = csr6;
                csr32w(ctlr, 6, csr6);
        }

        return 1;
}

static int
typephymode(Ctlr* ctlr, uchar* block, int wait)
{
        uchar *p;
        int len, mc, nway, phyx, timeo;

        if(DEBUG){
                int i;

                len = (block[0] & ~0x80)+1;
                for(i = 0; i < len; i++)
                        debug("%2.2uX ", block[i]);
                debug("\n");
        }

        if(block[1] == 1)
                len = 1;
        else if(block[1] == 3)
                len = 2;
        else
                return -1;

        /*
         * Snarf the media capabilities, nway advertisment,
         * FDX and TTM bitmaps.
         */
        p = &block[5+len*block[3]+len*block[4+len*block[3]]];
        mc = *p++;
        mc |= *p++<<8;
        nway = *p++;
        nway |= *p++<<8;
        ctlr->fdx = *p++;
        ctlr->fdx |= *p++<<8;
        ctlr->ttm = *p++;
        ctlr->ttm |= *p<<8;
        debug("mc %4.4uX nway %4.4uX fdx %4.4uX ttm %4.4uX\n",
                mc, nway, ctlr->fdx, ctlr->ttm);
        USED(mc);

        phyx = block[2];
        ctlr->curphyad = ctlr->phy[phyx];

        ctlr->csr6 = 0;         /* Sc|Mbo|Hbd|Ps|Ca|TrMODE|Sb; */
        // csr32w(ctlr, 6, ctlr->csr6);
        if(typephylink(ctlr, block))
                return 0;

        if(!(ctlr->phyreset & (1<<phyx))){
                debug("reset seq: len %d: ", block[3]);
                if(ctlr->type5block)
                        type5block(ctlr, &ctlr->type5block[2]);
                else
                        type5block(ctlr, &block[4+len*block[3]]);
                debug("\n");
                ctlr->phyreset |= (1<<phyx);
        }

        /*
         * GPR sequence.
         */
        debug("gpr seq: len %d: ", block[3]);
        type5block(ctlr, &block[3]);
        debug("\n");

        ctlr->csr6 = 0;         /* Sc|Mbo|Hbd|Ps|Ca|TrMODE|Sb; */
        // csr32w(ctlr, 6, ctlr->csr6);
        if(typephylink(ctlr, block))
                return 0;

        /*
         * Turn off auto-negotiation, set the auto-negotiation
         * advertisment register then start the auto-negotiation
         * process again.
         */
        miiw(ctlr, ctlr->curphyad, Bmcr, 0);
        miiw(ctlr, ctlr->curphyad, Anar, nway|1);
        miiw(ctlr, ctlr->curphyad, Bmcr, 0x1000);

        if(!wait)
                return 0;

        for(timeo = 0; timeo < 45; timeo++){
                if(typephylink(ctlr, block))
                        return 0;
                delay(100);
        }

        return -1;
}

static int
typesymmode(Ctlr *ctlr, uchar *block, int wait)
{
        uint gpmode, gpdata, command;

        USED(wait);
        gpmode = block[3] | ((uint) block[4] << 8);
        gpdata = block[5] | ((uint) block[6] << 8);
        command = (block[7] | ((uint) block[8] << 8)) & 0x71;
        if (command & 0x8000) {
                print("ether2114x.c: FIXME: handle type 4 mode blocks where cmd.active_invalid != 0\n");
                return -1;
        }
        csr32w(ctlr, 15, gpmode);
        csr32w(ctlr, 15, gpdata);
        ctlr->csr6 = (command & 0x71) << 18;
        csr32w(ctlr, 6, ctlr->csr6);
        return 0;
}

static int
type2mode(Ctlr* ctlr, uchar* block, int)
{
        uchar *p;
        int csr6, csr13, csr14, csr15, gpc, gpd;

        csr6 = Sc|Mbo|Ca|TrMODE|Sb;
        debug("type2mode: medium 0x%2.2uX\n", block[2]);

        /*
         * Don't attempt full-duplex
         * unless explicitly requested.
         */
        if((block[2] & 0x3F) == 0x04){  /* 10BASE-TFD */
                if(!ctlr->fd)
                        return -1;
                csr6 |= Fd;
        }

        /*
         * Operating mode programming values from the datasheet
         * unless media specific data is explicitly given.
         */
        p = &block[3];
        if(block[2] & 0x40){
                csr13 = (block[4]<<8)|block[3];
                csr14 = (block[6]<<8)|block[5];
                csr15 = (block[8]<<8)|block[7];
                p += 6;
        }
        else switch(block[2] & 0x3F){
        default:
                return -1;
        case 0x00:                      /* 10BASE-T */
                csr13 = 0x00000001;
                csr14 = 0x00007F3F;
                csr15 = 0x00000008;
                break;
        case 0x01:                      /* 10BASE-2 */
                csr13 = 0x00000009;
                csr14 = 0x00000705;
                csr15 = 0x00000006;
                break;
        case 0x02:                      /* 10BASE-5 (AUI) */
                csr13 = 0x00000009;
                csr14 = 0x00000705;
                csr15 = 0x0000000E;
                break;
        case 0x04:                      /* 10BASE-TFD */
                csr13 = 0x00000001;
                csr14 = 0x00007F3D;
                csr15 = 0x00000008;
                break;
        }
        gpc = *p++<<16;
        gpc |= *p++<<24;
        gpd = *p++<<16;
        gpd |= *p<<24;

        csr32w(ctlr, 13, 0);
        csr32w(ctlr, 14, csr14);
        csr32w(ctlr, 15, gpc|csr15);
        delay(10);
        csr32w(ctlr, 15, gpd|csr15);
        csr32w(ctlr, 13, csr13);

        ctlr->csr6 = csr6;
        csr32w(ctlr, 6, ctlr->csr6);

        debug("type2mode: csr13 %8.8uX csr14 %8.8uX csr15 %8.8uX\n",
                csr13, csr14, csr15);
        debug("type2mode: gpc %8.8uX gpd %8.8uX csr6 %8.8uX\n",
                gpc, gpd, csr6);

        return 0;
}

static int
type0link(Ctlr* ctlr, uchar* block)
{
        int m, polarity, sense;

        m = (block[3]<<8)|block[2];
        sense = 1<<((m & 0x000E)>>1);
        if(m & 0x0080)
                polarity = sense;
        else
                polarity = 0;

        return (csr32r(ctlr, 12) & sense)^polarity;
}

static int
type0mode(Ctlr* ctlr, uchar* block, int wait)
{
        int csr6, m, timeo;

        csr6 = Sc|Mbo|Hbd|Ca|TrMODE|Sb;
debug("type0: medium 0x%uX, fd %d: 0x%2.2uX 0x%2.2uX 0x%2.2uX 0x%2.2uX\n",
    ctlr->medium, ctlr->fd, block[0], block[1], block[2], block[3]); 
        switch(block[0]){
        default:
                break;

        case 0x04:                      /* 10BASE-TFD */
        case 0x05:                      /* 100BASE-TXFD */
        case 0x08:                      /* 100BASE-FXFD */
                /*
                 * Don't attempt full-duplex
                 * unless explicitly requested.
                 */
                if(!ctlr->fd)
                        return -1;
                csr6 |= Fd;
                break;
        }

        m = (block[3]<<8)|block[2];
        if(m & 0x0001)
                csr6 |= Ps;
        if(m & 0x0010)
                csr6 |= Ttm;
        if(m & 0x0020)
                csr6 |= Pcs;
        if(m & 0x0040)
                csr6 |= Scr;

        csr32w(ctlr, 12, block[1]);
        microdelay(10);
        csr32w(ctlr, 6, csr6);
        ctlr->csr6 = csr6;

        if(!wait)
                return 0;

        for(timeo = 0; timeo < 30; timeo++){
                if(type0link(ctlr, block))
                        return 0;
                delay(100);
        }

        return -1;
}

static int
media21041(Ether* ether, int wait)
{
        Ctlr* ctlr;
        uchar *block;
        int csr6, csr13, csr14, csr15, medium, timeo;

        ctlr = ether->ctlr;
        block = ctlr->infoblock[ctlr->curk];
        debug("media21041: block[0] %2.2uX, medium %4.4uX sct %4.4uX\n",
                block[0], ctlr->medium, ctlr->sct);

        medium = block[0] & 0x3F;
        if(ctlr->medium >= 0 && medium != ctlr->medium)
                return 0;
        if(ctlr->sct != 0x0800 && (ctlr->sct & 0x3F) != medium)
                return 0;

        csr6 = Sc|Mbo|Ca|TrMODE|Sb;
        if(block[0] & 0x40){
                csr13 = (block[2]<<8)|block[1];
                csr14 = (block[4]<<8)|block[3];
                csr15 = (block[6]<<8)|block[5];
        }
        else switch(medium){
        default:
                return -1;
        case 0x00:              /* 10BASE-T */
                csr13 = 0xEF01;
                csr14 = 0xFF3F;
                csr15 = 0x0008;
                break;
        case 0x01:              /* 10BASE-2 */
                csr13 = 0xEF09;
                csr14 = 0xF73D;
                csr15 = 0x0006;
                break;
        case 0x02:              /* 10BASE-5 */
                csr13 = 0xEF09;
                csr14 = 0xF73D;
                csr15 = 0x000E;
                break;
        case 0x04:              /* 10BASE-TFD */
                csr13 = 0xEF01;
                csr14 = 0xFF3D;
                csr15 = 0x0008;
                break;
        }

        csr32w(ctlr, 13, 0);
        csr32w(ctlr, 14, csr14);
        csr32w(ctlr, 15, csr15);
        csr32w(ctlr, 13, csr13);
        delay(10);

        if(medium == 0x04)
                csr6 |= Fd;
        ctlr->csr6 = csr6;
        csr32w(ctlr, 6, ctlr->csr6);

        debug("media21041: csr6 %8.8uX csr13 %4.4uX csr14 %4.4uX csr15 %4.4uX\n",
                csr6, csr13, csr14, csr15);

        if(!wait)
                return 0;

        for(timeo = 0; timeo < 30; timeo++){
                if(!(csr32r(ctlr, 12) & 0x0002)){
                        debug("media21041: ok: csr12 %4.4luX timeo %d\n",
                                csr32r(ctlr, 12), timeo);
                        return 10;
                }
                delay(100);
        }
        debug("media21041: !ok: csr12 %4.4luX\n", csr32r(ctlr, 12));

        return -1;
}

static int
mediaxx(Ether* ether, int wait)
{
        Ctlr* ctlr;
        uchar *block;

        ctlr = ether->ctlr;
        block = ctlr->infoblock[ctlr->curk];
        if(block[0] & 0x80){
                switch(block[1]){
                default:
                        return -1;
                case 0:
                        if(ctlr->medium >= 0 && block[2] != ctlr->medium)
                                return 0;
/* need this test? */   if(ctlr->sct != 0x0800 && (ctlr->sct & 0x3F) != block[2])
                                return 0;
                        if(type0mode(ctlr, block+2, wait))
                                return 0;
                        break;
                case 1:
                        if(typephymode(ctlr, block, wait))
                                return 0;
                        break;
                case 2:
                        debug("type2: medium %d block[2] %d\n",
                                ctlr->medium, block[2]);
                        if(ctlr->medium >= 0 && ((block[2] & 0x3F) != ctlr->medium))
                                return 0;
                        if(type2mode(ctlr, block, wait))
                                return 0;
                        break;
                case 3:
                        if(typephymode(ctlr, block, wait))
                                return 0;
                        break;
                case 4:
                        debug("type4: medium %d block[2] %d\n",
                                ctlr->medium, block[2]);
                        if(ctlr->medium >= 0 && ((block[2] & 0x3F) != ctlr->medium))
                                return 0;
                        if(typesymmode(ctlr, block, wait))
                                return 0;
                        break;
                }
        }
        else{
                if(ctlr->medium >= 0 && block[0] != ctlr->medium)
                        return 0;
/* need this test? */if(ctlr->sct != 0x0800 && (ctlr->sct & 0x3F) != block[0])
                        return 0;
                if(type0mode(ctlr, block, wait))
                        return 0;
        }

        if(ctlr->csr6){
                if(!(ctlr->csr6 & Ps) || (ctlr->csr6 & Ttm))
                        return 10;
                return 100;
        }

        return 0;
}

static int
media(Ether* ether, int wait)
{
        Ctlr* ctlr;
        int k, mbps;

        ctlr = ether->ctlr;
        for(k = 0; k < ctlr->k; k++){
                switch(ctlr->id){
                default:
                        mbps = mediaxx(ether, wait);
                        break;
                case Tulip1:                    /* 21041 */
                        mbps = media21041(ether, wait);
                        break;
                }
                if(mbps > 0)
                        return mbps;
                if(ctlr->curk == 0)
                        ctlr->curk = ctlr->k-1;
                else
                        ctlr->curk--;
        }

        return 0;
}

static char* mediatable[9] = {
        "10BASE-T",                             /* TP */
        "10BASE-2",                             /* BNC */
        "10BASE-5",                             /* AUI */
        "100BASE-TX",
        "10BASE-TFD",
        "100BASE-TXFD",
        "100BASE-T4",
        "100BASE-FX",
        "100BASE-FXFD",
};

static uchar en1207[] = {               /* Accton EN1207-COMBO */
        0x00, 0x00, 0xE8,               /* [0]  vendor ethernet code */
        0x00,                           /* [3]  spare */

        0x00, 0x08,                     /* [4]  connection (LSB+MSB = 0x0800) */
        0x1F,                           /* [6]  general purpose control */
        2,                              /* [7]  block count */

        0x00,                           /* [8]  media code (10BASE-TX) */
        0x0B,                           /* [9]  general purpose port data */
        0x9E, 0x00,                     /* [10] command (LSB+MSB = 0x009E) */

        0x03,                           /* [8]  media code (100BASE-TX) */
        0x1B,                           /* [9]  general purpose port data */
        0x6D, 0x00,                     /* [10] command (LSB+MSB = 0x006D) */

                                        /* There is 10BASE-2 as well, but... */
};

static uchar ana6910fx[] = {            /* Adaptec (Cogent) ANA-6910FX */
        0x00, 0x00, 0x92,               /* [0]  vendor ethernet code */
        0x00,                           /* [3]  spare */

        0x00, 0x08,                     /* [4]  connection (LSB+MSB = 0x0800) */
        0x3F,                           /* [6]  general purpose control */
        1,                              /* [7]  block count */

        0x07,                           /* [8]  media code (100BASE-FX) */
        0x03,                           /* [9]  general purpose port data */
        0x2D, 0x00                      /* [10] command (LSB+MSB = 0x000D) */
};

static uchar smc9332[] = {              /* SMC 9332 */
        0x00, 0x00, 0xC0,               /* [0]  vendor ethernet code */
        0x00,                           /* [3]  spare */

        0x00, 0x08,                     /* [4]  connection (LSB+MSB = 0x0800) */
        0x1F,                           /* [6]  general purpose control */
        2,                              /* [7]  block count */

        0x00,                           /* [8]  media code (10BASE-TX) */
        0x00,                           /* [9]  general purpose port data */
        0x9E, 0x00,                     /* [10] command (LSB+MSB = 0x009E) */

        0x03,                           /* [8]  media code (100BASE-TX) */
        0x09,                           /* [9]  general purpose port data */
        0x6D, 0x00,                     /* [10] command (LSB+MSB = 0x006D) */
};

static uchar* leaf21140[] = {
        en1207,                         /* Accton EN1207-COMBO */
        ana6910fx,                      /* Adaptec (Cogent) ANA-6910FX */
        smc9332,                        /* SMC 9332 */
        nil,
};

/*
 * Copied to ctlr->srom at offset 20.
 */
static uchar leafpnic[] = {
        0x00, 0x00, 0x00, 0x00,         /* MAC address */
        0x00, 0x00,
        0x00,                           /* controller 0 device number */
        0x1E, 0x00,                     /* controller 0 info leaf offset */
        0x00,                           /* reserved */
        0x00, 0x08,                     /* selected connection type */
        0x00,                           /* general purpose control */
        0x01,                           /* block count */

        0x8C,                           /* format indicator and count */
        0x01,                           /* block type */
        0x00,                           /* PHY number */
        0x00,                           /* GPR sequence length */
        0x00,                           /* reset sequence length */
        0x00, 0x78,                     /* media capabilities */
        0xE0, 0x01,                     /* Nway advertisment */
        0x00, 0x50,                     /* FDX bitmap */
        0x00, 0x18,                     /* TTM bitmap */
};

static int
srom(Ctlr* ctlr)
{
        int i, k, oui, phy, x;
        uchar *p;

        /*
         * This is a partial decoding of the SROM format described in
         * 'Digital Semiconductor 21X4 Serial ROM Format, Version 4.05,
         * 2-Mar-98'. Only the 2114[03] are handled, support for other
         * controllers can be added as needed.
         * Do a dummy read first to get the size and allocate ctlr->srom.
         */
        sromr(ctlr, 0);
        if(ctlr->srom == nil)
                ctlr->srom = malloc((1<<ctlr->sromsz)*sizeof(ushort));
        if(ctlr->srom == nil)
                error(Enomem);
        for(i = 0; i < (1<<ctlr->sromsz); i++){
                x = sromr(ctlr, i);
                ctlr->srom[2*i] = x;
                ctlr->srom[2*i+1] = x>>8;
        }

        if(DEBUG){
                print("srom:");
                for(i = 0; i < ((1<<ctlr->sromsz)*sizeof(ushort)); i++){
                        if(i && ((i & 0x0F) == 0))
                                print("\n     ");
                        print(" %2.2uX", ctlr->srom[i]);
                }
                print("\n");
        }

        /*
         * There are at least 2 SROM layouts:
         *      e.g. Digital EtherWORKS station address at offset 20;
         *                              this complies with the 21140A SROM
         *                              application note from Digital;
         *      e.g. SMC9332            station address at offset 0 followed by
         *                              2 additional bytes, repeated at offset
         *                              6; the 8 bytes are also repeated in
         *                              reverse order at offset 8.
         * To check which it is, read the SROM and check for the repeating
         * patterns of the non-compliant cards; if that fails use the one at
         * offset 20.
         */
        ctlr->sromea = ctlr->srom;
        for(i = 0; i < 8; i++){
                x = ctlr->srom[i];
                if(x != ctlr->srom[15-i] || x != ctlr->srom[16+i]){
                        ctlr->sromea = &ctlr->srom[20];
                        break;
                }
        }

        /*
         * Fake up the SROM for the PNIC and AMDtek.
         * They look like a 21140 with a PHY.
         * The MAC address is byte-swapped in the orginal
         * PNIC SROM data.
         */
        if(ctlr->id == Pnic){
                memmove(&ctlr->srom[20], leafpnic, sizeof(leafpnic));
                for(i = 0; i < Eaddrlen; i += 2){
                        ctlr->srom[20+i] = ctlr->srom[i+1];
                        ctlr->srom[20+i+1] = ctlr->srom[i];
                }
        }
        if(ctlr->id == CentaurP || ctlr->id == CentaurPcb){
                memmove(&ctlr->srom[20], leafpnic, sizeof(leafpnic));
                for(i = 0; i < Eaddrlen; i += 2){
                        ctlr->srom[20+i] = ctlr->srom[8+i];
                        ctlr->srom[20+i+1] = ctlr->srom[8+i+1];
                }
        }

        /*
         * Next, try to find the info leaf in the SROM for media detection.
         * If it's a non-conforming card try to match the vendor ethernet code
         * and point p at a fake info leaf with compact 21140 entries.
         */
        if(ctlr->sromea == ctlr->srom){
                p = nil;
                for(i = 0; leaf21140[i] != nil; i++){
                        if(memcmp(leaf21140[i], ctlr->sromea, 3) == 0){
                                p = &leaf21140[i][4];
                                break;
                        }
                }
                if(p == nil)
                        return -1;
        }
        else
                p = &ctlr->srom[(ctlr->srom[28]<<8)|ctlr->srom[27]];

        /*
         * Set up the info needed for later media detection.
         * For the 21140, set the general-purpose mask in CSR12.
         * The info block entries are stored in order of increasing
         * precedence, so detection will work backwards through the
         * stored indexes into ctlr->srom.
         * If an entry is found which matches the selected connection
         * type, save the index. Otherwise, start at the last entry.
         * If any MII entries are found (type 1 and 3 blocks), scan
         * for PHYs.
         */
        ctlr->leaf = p;
        ctlr->sct = *p++;
        ctlr->sct |= *p++<<8;
        if(ctlr->id != Tulip3 && ctlr->id != Tulip1){
                csr32w(ctlr, 12, Gpc|*p++);
                delay(200);
        }
        ctlr->k = *p++;
        if(ctlr->k >= nelem(ctlr->infoblock))
                ctlr->k = nelem(ctlr->infoblock)-1;
        ctlr->sctk = ctlr->k-1;
        phy = 0;
        for(k = 0; k < ctlr->k; k++){
                ctlr->infoblock[k] = p;
                if(ctlr->id == Tulip1){
                        debug("type21041: 0x%2.2uX\n", p[0]); 
                        if(ctlr->sct != 0x0800 && *p == (ctlr->sct & 0xFF))
                                ctlr->sctk = k;
                        if(*p & 0x40)
                                p += 7;
                        else
                                p += 1;
                }
                /*
                 * The RAMIX PMC665 has a badly-coded SROM,
                 * hence the test for 21143 and type 3.
                 */
                else if((*p & 0x80) || (ctlr->id == Tulip3 && *(p+1) == 3)){
                        *p |= 0x80;
                        if(*(p+1) == 1 || *(p+1) == 3)
                                phy = 1;
                        if(*(p+1) == 5)
                                ctlr->type5block = p;
                        p += (*p & ~0x80)+1;
                }
                else{
                        debug("type0: 0x%2.2uX 0x%2.2uX 0x%2.2uX 0x%2.2uX\n",
                                p[0], p[1], p[2], p[3]); 
                        if(ctlr->sct != 0x0800 && *p == (ctlr->sct & 0xFF))
                                ctlr->sctk = k;
                        p += 4;
                }
        }
        ctlr->curk = ctlr->sctk;
        debug("sct 0x%uX medium 0x%uX k %d curk %d phy %d\n",
                ctlr->sct, ctlr->medium, ctlr->k, ctlr->curk, phy);

        if(phy){
                x = 0;
                for(k = 0; k < nelem(ctlr->phy); k++){
                        if((ctlr->id == CentaurP || ctlr->id == CentaurPcb) && k != 1)
                                continue;
                        if((oui = miir(ctlr, k, 2)) == -1 || oui == 0)
                                continue;
                        debug("phy reg 2 %4.4uX\n", oui);
                        if(DEBUG){
                                oui = (oui & 0x3FF)<<6;
                                oui |= miir(ctlr, k, 3)>>10;
                                miir(ctlr, k, 1);
                                debug("phy%d: index %d oui %uX reg1 %uX\n",
                                        x, k, oui, miir(ctlr, k, 1));
                                USED(oui);
                        }
                        ctlr->phy[x] = k;
                }
        }

        ctlr->fd = 0;
        ctlr->medium = -1;

        return 0;
}

static void
dec2114xpci(void)
{
        Ctlr *ctlr;
        Pcidev *p;
        int x;

        p = nil;
        while(p = pcimatch(p, 0, 0)){
                if(p->ccrb != 0x02 || p->ccru != 0)
                        continue;
                switch((p->did<<16)|p->vid){
                default:
                        continue;

                case Tulip3:                    /* 21143 */
                        /*
                         * Exit sleep mode.
                         */
                        x = pcicfgr32(p, 0x40);
                        x &= ~0xC0000000;
                        pcicfgw32(p, 0x40, x);
                        /*FALLTHROUGH*/

                case Tulip0:                    /* 21140 */
                case Tulip1:                    /* 21041 */
                case Pnic:                      /* PNIC */
                case Pnic2:                     /* PNIC-II */
                case CentaurP:                  /* ADMtek */
                case CentaurPcb:                /* ADMtek CardBus */
                        break;
                }

                /*
                 * bar[0] is the I/O port register address and
                 * bar[1] is the memory-mapped register address.
                 */
                ctlr = malloc(sizeof(Ctlr));
                if(ctlr == nil)
                        error(Enomem);
                ctlr->port = p->mem[0].bar & ~0x01;
                ctlr->pcidev = p;
                ctlr->id = (p->did<<16)|p->vid;

                if(ioalloc(ctlr->port, p->mem[0].size, 0, "dec2114x") < 0){
                        print("dec2114x: port 0x%uX in use\n", ctlr->port);
                        free(ctlr);
                        continue;
                }

                /*
                 * Some cards (e.g. ANA-6910FX) seem to need the Ps bit
                 * set or they don't always work right after a hardware
                 * reset.
                 */
                csr32w(ctlr, 6, Mbo|Ps);
                softreset(ctlr);

                if(srom(ctlr)){
                        iofree(ctlr->port);
                        free(ctlr);
                        continue;
                }

                switch(ctlr->id){
                default:
                        break;
                case Pnic:                      /* PNIC */
                        /*
                         * Turn off the jabber timer.
                         */
                        csr32w(ctlr, 15, 0x00000001);
                        break;
                case CentaurP:
                case CentaurPcb:
                        /*
                         * Nice - the register offsets change from *8 to *4
                         * for CSR16 and up...
                         * CSR25/26 give the MAC address read from the SROM.
                         * Don't really need to use this other than as a check,
                         * the SROM will be read in anyway so the value there
                         * can be used directly.
                         */
                        debug("csr25 %8.8luX csr26 %8.8luX\n",
                                inl(ctlr->port+0xA4), inl(ctlr->port+0xA8));
                        debug("phyidr1 %4.4luX phyidr2 %4.4luX\n",
                                inl(ctlr->port+0xBC), inl(ctlr->port+0xC0));
                        break;
                }

                if(ctlrhead != nil)
                        ctlrtail->next = ctlr;
                else
                        ctlrhead = ctlr;
                ctlrtail = ctlr;
        }
}

static int
reset(Ether* ether)
{
        Ctlr *ctlr;
        int i, x;
        uchar ea[Eaddrlen];
        static int scandone;

        if(scandone == 0){
                dec2114xpci();
                scandone = 1;
        }

        /*
         * Any adapter matches if no ether->port is supplied,
         * otherwise the ports must match.
         */
        for(ctlr = ctlrhead; ctlr != nil; ctlr = ctlr->next){
                if(ctlr->active)
                        continue;
                if(ether->port == 0 || ether->port == ctlr->port){
                        ctlr->active = 1;
                        break;
                }
        }
        if(ctlr == nil)
                return -1;

        ether->ctlr = ctlr;
        ether->port = ctlr->port;
        ether->irq = ctlr->pcidev->intl;
        ether->tbdf = ctlr->pcidev->tbdf;

        /*
         * 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, ether->ea, Eaddrlen) == 0)
                memmove(ether->ea, ctlr->sromea, Eaddrlen);

        /*
         * Look for a medium override in case there's no autonegotiation
         * (no MII) or the autonegotiation fails.
         */
        for(i = 0; i < ether->nopt; i++){
                if(cistrcmp(ether->opt[i], "FD") == 0){
                        ctlr->fd = 1;
                        continue;
                }
                for(x = 0; x < nelem(mediatable); x++){
                        debug("compare <%s> <%s>\n", mediatable[x],
                                ether->opt[i]);
                        if(cistrcmp(mediatable[x], ether->opt[i]))
                                continue;
                        ctlr->medium = x;
        
                        switch(ctlr->medium){
                        default:
                                ctlr->fd = 0;
                                break;
        
                        case 0x04:              /* 10BASE-TFD */
                        case 0x05:              /* 100BASE-TXFD */
                        case 0x08:              /* 100BASE-FXFD */
                                ctlr->fd = 1;
                                break;
                        }
                        break;
                }
        }

        ether->mbps = media(ether, 1);

        /*
         * Initialise descriptor rings, ethernet address.
         */
        ctlr->nrdr = Nrde;
        ctlr->ntdr = Ntde;
        pcisetbme(ctlr->pcidev);
        ctlrinit(ether);

        /*
         * Linkage to the generic ethernet driver.
         */
        ether->attach = attach;
        ether->transmit = transmit;
        ether->interrupt = interrupt;
        ether->ifstat = ifstat;

        ether->arg = ether;
        ether->shutdown = shutdown;
        ether->multicast = multicast;
        ether->promiscuous = promiscuous;

        return 0;
}

void
ether2114xlink(void)
{
        addethercard("2114x",  reset);
        addethercard("21140",  reset);
}