Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * FCCn ethernet
 */

#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "io.h"
#include "imm.h"
#include "../port/error.h"
#include "../port/netif.h"

#include "etherif.h"
#include "../ppc/ethermii.h"

#define DBG 1

enum {
        Nrdre           = 128,                  /* receive descriptor ring entries */
        Ntdre           = 128,                  /* transmit descriptor ring entries */

        Rbsize          = ETHERMAXTU+4,         /* ring buffer size (+4 for CRC) */
        Bufsize         = Rbsize+CACHELINESZ,   /* extra room for alignment */
};

enum {

        /* ether-specific Rx BD bits */
        RxMiss=         SBIT(7),
        RxeLG=          SBIT(10),
        RxeNO=          SBIT(11),
        RxeSH=          SBIT(12),
        RxeCR=          SBIT(13),
        RxeOV=          SBIT(14),
        RxeCL=          SBIT(15),
        RxError=        (RxeLG|RxeNO|RxeSH|RxeCR|RxeOV|RxeCL),  /* various error flags */

        /* ether-specific Tx BD bits */
        TxPad=          SBIT(1),        /* pad short frames */
        TxTC=           SBIT(5),        /* transmit CRC */
        TxeDEF=         SBIT(6),
        TxeHB=          SBIT(7),
        TxeLC=          SBIT(8),
        TxeRL=          SBIT(9),
        TxeUN=          SBIT(14),
        TxeCSL=         SBIT(15),

        /* psmr */
        CRCE=           BIT(24),        /* Ethernet CRC */
        FCE=            BIT(10),        /* flow control */
        PRO=            BIT(9),         /* promiscuous mode */
        FDE=            BIT(5),         /* full duplex ethernet */
        LPB=            BIT(3),         /* local protect bit */

        /* gfmr */
        ENET=           0xc,            /* ethernet mode */
        ENT=            BIT(27),
        ENR=            BIT(26),
        TCI=            BIT(2),

        /* FCC function code register */
        GBL=            0x20,
        BO=             0x18,
        EB=             0x10,           /* Motorola byte order */
        TC2=            0x04,
        DTB=            0x02,
        BDB=            0x01,

        /* FCC Event/Mask bits */
        GRA=            SBIT(8),
        RXC=            SBIT(9),
        TXC=            SBIT(10),
        TXE=            SBIT(11),
        RXF=            SBIT(12),
        BSY=            SBIT(13),
        TXB=            SBIT(14),
        RXB=            SBIT(15),
};

enum {          /* Mcr */
        MDIread =       0x60020000,     /* read opcode */
        MDIwrite =      0x50020000,     /* write opcode */
};

typedef struct Etherparam Etherparam;
struct Etherparam {
/*0x00*/        FCCparam;
/*0x3c*/        ulong   stat_buf;
/*0x40*/        ulong   cam_ptr;
/*0x44*/        ulong   cmask;
/*0x48*/        ulong   cpres;
/*0x4c*/        ulong   crcec;
/*0x50*/        ulong   alec;
/*0x54*/        ulong   disfc;
/*0x58*/        ushort  retlim;
/*0x5a*/        ushort  retcnt;
/*0x5c*/        ushort  p_per;
/*0x5e*/        ushort  boff_cnt;
/*0x60*/        ulong   gaddr[2];
/*0x68*/        ushort  tfcstat;
/*0x6a*/        ushort  tfclen;
/*0x6c*/        ulong   tfcptr;
/*0x70*/        ushort  mflr;
/*0x72*/        ushort  paddr[3];
/*0x78*/        ushort  ibd_cnt;
/*0x7a*/        ushort  ibd_start;
/*0x7c*/        ushort  ibd_end;
/*0x7e*/        ushort  tx_len;
/*0x80*/        uchar   ibd_base[32];
/*0xa0*/        ulong   iaddr[2];
/*0xa8*/        ushort  minflr;
/*0xaa*/        ushort  taddr[3];
/*0xb0*/        ushort  padptr;
/*0xb2*/        ushort  Rsvdb2;
/*0xb4*/        ushort  cf_range;
/*0xb6*/        ushort  max_b;
/*0xb8*/        ushort  maxd1;
/*0xba*/        ushort  maxd2;
/*0xbc*/        ushort  maxd;
/*0xbe*/        ushort  dma_cnt;
/*0xc0*/        ulong   octc;
/*0xc4*/        ulong   colc;
/*0xc8*/        ulong   broc;
/*0xcc*/        ulong   mulc;
/*0xd0*/        ulong   uspc;
/*0xd4*/        ulong   frgc;
/*0xd8*/        ulong   ospc;
/*0xdc*/        ulong   jbrc;
/*0xe0*/        ulong   p64c;
/*0xe4*/        ulong   p65c;
/*0xe8*/        ulong   p128c;
/*0xec*/        ulong   p256c;
/*0xf0*/        ulong   p512c;
/*0xf4*/        ulong   p1024c;
/*0xf8*/        ulong   cam_buf;
/*0xfc*/        ulong   Rsvdfc;
/*0x100*/
};

typedef struct Ctlr Ctlr;
struct Ctlr {
        Lock;
        int     fccid;
        int     port;
        ulong   pmdio;
        ulong   pmdck;
        int     init;
        int     active;
        int     duplex;         /* 1 == full */
        FCC*    fcc;

        Ring;
        Block*  rcvbufs[Nrdre];
        Mii*    mii;
        Timer;

        ulong   interrupts;     /* statistics */
        ulong   deferred;
        ulong   heartbeat;
        ulong   latecoll;
        ulong   retrylim;
        ulong   underrun;
        ulong   overrun;
        ulong   carrierlost;
        ulong   retrycount;
};

static  int     fccirq[] = {0x20, 0x21, 0x22};
static  int     fccid[] = {FCC1ID, FCC2ID, FCC3ID};

#ifdef DBG
ulong fccrhisto[16];
ulong fccthisto[16];
ulong fccrthisto[16];
ulong fcctrhisto[16];
ulong ehisto[0x80];
#endif

static int fccmiimir(Mii*, int, int);
static int fccmiimiw(Mii*, int, int, int);
static void fccltimer(Ureg*, Timer*);

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

        ctlr = ether->ctlr;
        ilock(ctlr);
        ctlr->active = 1;
        ctlr->fcc->gfmr |= ENR|ENT;
        iunlock(ctlr);
        ctlr->tmode = Tperiodic;
        ctlr->tf = fccltimer;
        ctlr->ta = ether;
        ctlr->tns = 5000000000LL;       /* 5 seconds */
        timeradd(ctlr);
}

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

        ctlr = ether->ctlr;
        ilock(ctlr);
        ctlr->active = 0;
        ctlr->fcc->gfmr &= ~(ENR|ENT);
        iunlock(ctlr);
        print("Ether closed\n");
}

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

        ether = (Ether*)arg;
        ctlr = ether->ctlr;

        ilock(ctlr);
        if(on || ether->nmaddr)
                ctlr->fcc->fpsmr |= PRO;
        else
                ctlr->fcc->fpsmr &= ~PRO;
        iunlock(ctlr);
}

static void
multicast(void* arg, uchar *addr, int on)
{
        Ether *ether;
        Ctlr *ctlr;

        USED(addr, on); /* if on, could SetGroupAddress; if !on, it's hard */

        ether = (Ether*)arg;
        ctlr = ether->ctlr;

        ilock(ctlr);
        if(ether->prom || ether->nmaddr)
                ctlr->fcc->fpsmr |= PRO;
        else
                ctlr->fcc->fpsmr &= ~PRO;
        iunlock(ctlr);
}

static void
txstart(Ether *ether)
{
        int len;
        Ctlr *ctlr;
        Block *b;
        BD *dre;

        ctlr = ether->ctlr;
        if(ctlr->init)
                return;
        while(ctlr->ntq < Ntdre-1){
                b = qget(ether->oq);
                if(b == 0)
                        break;

                dre = &ctlr->tdr[ctlr->tdrh];
                dczap(dre, sizeof(BD));
                if(dre->status & BDReady)
                        panic("ether: txstart");

                /*
                 * Give ownership of the descriptor to the chip, increment the
                 * software ring descriptor pointer and tell the chip to poll.
                 */
                len = BLEN(b);
                if(ctlr->txb[ctlr->tdrh] != nil)
                        panic("fcc/ether: txstart");
                ctlr->txb[ctlr->tdrh] = b;
                if((ulong)b->rp&1)
                        panic("fcc/ether: txstart align");      /* TO DO: ensure alignment */
                dre->addr = PADDR(b->rp);
                dre->length = len;
                dcflush(b->rp, len);
                dcflush(dre, sizeof(BD));
                dre->status = (dre->status & BDWrap) | BDReady|TxPad|BDInt|BDLast|TxTC;
                dcflush(dre, sizeof(BD));
/*              ctlr->fcc->ftodr = 1<<15;       /* transmit now; Don't do this according to errata */
                ctlr->ntq++;
                ctlr->tdrh = NEXT(ctlr->tdrh, Ntdre);
        }
}

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

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

static void
interrupt(Ureg*, void *arg)
{
        int len, status, rcvd, xmtd, restart;
        ushort events;
        Ctlr *ctlr;
        BD *dre;
        Block *b, *nb;
        Ether *ether = arg;

        ctlr = ether->ctlr;
        if(!ctlr->active)
                return; /* not ours */

        /*
         * Acknowledge all interrupts and whine about those that shouldn't
         * happen.
         */
        events = ctlr->fcc->fcce;
        ctlr->fcc->fcce = events;               /* clear events */

#ifdef DBG
        ehisto[events & 0x7f]++;
#endif

        ctlr->interrupts++;

        if(events & BSY)
                ctlr->overrun++;
        if(events & TXE)
                ether->oerrs++;

#ifdef DBG
        rcvd = xmtd = 0;
#endif
        /*
         * Receiver interrupt: run round the descriptor ring logging
         * errors and passing valid receive data up to the higher levels
         * until we encounter a descriptor still owned by the chip.
         */
        if(events & RXF){
                dre = &ctlr->rdr[ctlr->rdrx];
                dczap(dre, sizeof(BD));
                while(((status = dre->status) & BDEmpty) == 0){
                        rcvd++;
                        if(status & RxError || (status & (BDFirst|BDLast)) != (BDFirst|BDLast)){
                                if(status & (RxeLG|RxeSH))
                                        ether->buffs++;
                                if(status & RxeNO)
                                        ether->frames++;
                                if(status & RxeCR)
                                        ether->crcs++;
                                if(status & RxeOV)
                                        ether->overflows++;
                                print("eth rx: %ux\n", status);
                        }else{
                                /*
                                 * We have a packet. Read it in.
                                 */
                                len = dre->length-4;
                                b = ctlr->rcvbufs[ctlr->rdrx];
                                assert(dre->addr == PADDR(b->rp));
                                dczap(b->rp, len);
                                if(nb = iallocb(Bufsize)){
                                        b->wp += len;
                                        etheriq(ether, b, 1);
                                        b = nb;
                                        b->rp = (uchar*)(((ulong)b->rp + CACHELINESZ-1) & ~(CACHELINESZ-1));
                                        b->wp = b->rp;
                                        ctlr->rcvbufs[ctlr->rdrx] = b;
                                        ctlr->rdr[ctlr->rdrx].addr = PADDR(b->wp);
                                }else
                                        ether->soverflows++;
                        }

                        /*
                         * Finished with this descriptor, reinitialise it,
                         * give it back to the chip, then on to the next...
                         */
                        dre->length = 0;
                        dre->status = (status & BDWrap) | BDEmpty | BDInt;
                        dcflush(dre, sizeof(BD));

                        ctlr->rdrx = NEXT(ctlr->rdrx, Nrdre);
                        dre = &ctlr->rdr[ctlr->rdrx];
                        dczap(dre, sizeof(BD));
                }
        }

        /*
         * Transmitter interrupt: handle anything queued for a free descriptor.
         */
        if(events & (TXB|TXE)){
                ilock(ctlr);
                restart = 0;
                while(ctlr->ntq){
                        dre = &ctlr->tdr[ctlr->tdri];
                        dczap(dre, sizeof(BD));
                        status = dre->status;
                        if(status & BDReady)
                                break;
                        if(status & TxeDEF)
                                ctlr->deferred++;
                        if(status & TxeHB)
                                ctlr->heartbeat++;
                        if(status & TxeLC)
                                ctlr->latecoll++;
                        if(status & TxeRL)
                                ctlr->retrylim++;
                        if(status & TxeUN)
                                ctlr->underrun++;
                        if(status & TxeCSL)
                                ctlr->carrierlost++;
                        if(status & (TxeLC|TxeRL|TxeUN))
                                restart = 1;
                        ctlr->retrycount += (status>>2)&0xF;
                        b = ctlr->txb[ctlr->tdri];
                        if(b == nil)
                                panic("fcce/interrupt: bufp");
                        ctlr->txb[ctlr->tdri] = nil;
                        freeb(b);
                        ctlr->ntq--;
                        ctlr->tdri = NEXT(ctlr->tdri, Ntdre);
                        xmtd++;
                }

                if(restart){
                        ctlr->fcc->gfmr &= ~ENT;
                        delay(10);
                        ctlr->fcc->gfmr |= ENT;
                        cpmop(RestartTx, ctlr->fccid, 0xc);
                }
                txstart(ether);
                iunlock(ctlr);
        }
#ifdef DBG
        if(rcvd >= nelem(fccrhisto))
                rcvd = nelem(fccrhisto) - 1;
        if(xmtd >= nelem(fccthisto))
                xmtd = nelem(fccthisto) - 1;
        if(rcvd)
                fcctrhisto[xmtd]++;
        else
                fccthisto[xmtd]++;
        if(xmtd)
                fccrthisto[rcvd]++;
        else
                fccrhisto[rcvd]++;
#endif
}

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

        if(n == 0)
                return 0;

        ctlr = ether->ctlr;

        p = malloc(READSTR);
        len = snprint(p, READSTR, "interrupts: %lud\n", ctlr->interrupts);
        len += snprint(p+len, READSTR-len, "carrierlost: %lud\n", ctlr->carrierlost);
        len += snprint(p+len, READSTR-len, "heartbeat: %lud\n", ctlr->heartbeat);
        len += snprint(p+len, READSTR-len, "retrylimit: %lud\n", ctlr->retrylim);
        len += snprint(p+len, READSTR-len, "retrycount: %lud\n", ctlr->retrycount);
        len += snprint(p+len, READSTR-len, "latecollisions: %lud\n", ctlr->latecoll);
        len += snprint(p+len, READSTR-len, "rxoverruns: %lud\n", ctlr->overrun);
        len += snprint(p+len, READSTR-len, "txunderruns: %lud\n", ctlr->underrun);
        len += snprint(p+len, READSTR-len, "framesdeferred: %lud\n", ctlr->deferred);
        miistatus(ctlr->mii);
        phy = ctlr->mii->curphy;
        len += snprint(p+len, READSTR-len, "phy: link=%d, tfc=%d, rfc=%d, speed=%d, fd=%d\n",
                phy->link, phy->tfc, phy->rfc, phy->speed, phy->fd);

#ifdef DBG
        if(ctlr->mii != nil && ctlr->mii->curphy != nil){
                len += snprint(p+len, READSTR, "phy:   ");
                for(i = 0; i < NMiiPhyr; i++){
                        if(i && ((i & 0x07) == 0))
                                len += snprint(p+len, READSTR-len, "\n       ");
                        r = miimir(ctlr->mii, i);
                        len += snprint(p+len, READSTR-len, " %4.4uX", r);
                }
                snprint(p+len, READSTR-len, "\n");
        }
#endif
        snprint(p+len, READSTR-len, "\n");

        n = readstr(offset, a, n, p);
        free(p);

        return n;
}

IMM* imm;

/*
 * This follows the MPC8260 user guide: section28.9's initialisation sequence.
 */
static int
fccsetup(Ctlr *ctlr, FCC *fcc, uchar *ea)
{
        int i;
        Etherparam *p;
        MiiPhy *phy;

        /* Turn Ethernet off */
        fcc->gfmr &= ~(ENR | ENT);

        ioplock();
        switch(ctlr->port) {
        default:
                iopunlock();
                return -1;
        case 0:
                /* Step 1 (Section 28.9), write the parallel ports */
                ctlr->pmdio = 0x01000000;
                ctlr->pmdck = 0x08000000;
                imm->port[0].pdir &= ~A1dir0;
                imm->port[0].pdir |= A1dir1;
                imm->port[0].psor &= ~A1psor0;
                imm->port[0].psor |= A1psor1;
                imm->port[0].ppar |= (A1dir0 | A1dir1);
                /* Step 2, Port C clocks */
                imm->port[2].psor &= ~0x00000c00;
                imm->port[2].pdir &= ~0x00000c00;
                imm->port[2].ppar |= 0x00000c00;
                imm->port[3].pdat |= (ctlr->pmdio | ctlr->pmdck);
                imm->port[3].podr |= ctlr->pmdio;
                imm->port[3].pdir |= (ctlr->pmdio | ctlr->pmdck);
                imm->port[3].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
                eieio();
                /* Step 3, Serial Interface clock routing */
                imm->cmxfcr &= ~0xff000000;     /* Clock mask */
                imm->cmxfcr |= 0x37000000;      /* Clock route */
                break;

        case 1:
                /* Step 1 (Section 28.9), write the parallel ports */
                ctlr->pmdio = 0x00400000;
                ctlr->pmdck = 0x00200000;
                imm->port[1].pdir &= ~B2dir0;
                imm->port[1].pdir |= B2dir1;
                imm->port[1].psor &= ~B2psor0;
                imm->port[1].psor |= B2psor1;
                imm->port[1].ppar |= (B2dir0 | B2dir1);
                /* Step 2, Port C clocks */
                imm->port[2].psor &= ~0x00003000;
                imm->port[2].pdir &= ~0x00003000;
                imm->port[2].ppar |= 0x00003000;

                imm->port[2].pdat |= (ctlr->pmdio | ctlr->pmdck);
                imm->port[2].podr |= ctlr->pmdio;
                imm->port[2].pdir |= (ctlr->pmdio | ctlr->pmdck);
                imm->port[2].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
                eieio();
                /* Step 3, Serial Interface clock routing */
                imm->cmxfcr &= ~0x00ff0000;
                imm->cmxfcr |= 0x00250000;
                break;

        case 2:
                /* Step 1 (Section 28.9), write the parallel ports */
                imm->port[1].pdir &= ~B3dir0;
                imm->port[1].pdir |= B3dir1;
                imm->port[1].psor &= ~B3psor0;
                imm->port[1].psor |= B3psor1;
                imm->port[1].ppar |= (B3dir0 | B3dir1);
                /* Step 2, Port C clocks */
                imm->port[2].psor &= ~0x0000c000;
                imm->port[2].pdir &= ~0x0000c000;
                imm->port[2].ppar |= 0x0000c000;
                imm->port[3].pdat |= (ctlr->pmdio | ctlr->pmdck);
                imm->port[3].podr |= ctlr->pmdio;
                imm->port[3].pdir |= (ctlr->pmdio | ctlr->pmdck);
                imm->port[3].ppar &= ~(ctlr->pmdio | ctlr->pmdck);
                eieio();
                /* Step 3, Serial Interface clock routing */
                imm->cmxfcr &= ~0x0000ff00;
                imm->cmxfcr |= 0x00003700;
                break;
        }
        iopunlock();

        p = (Etherparam*)(m->immr->prmfcc + ctlr->port);
        memset(p, 0, sizeof(Etherparam));

        /* Step 4 */
        fcc->gfmr |= ENET;

        /* Step 5 */
        fcc->fpsmr = CRCE | FDE | LPB;  /* full duplex operation */
        ctlr->duplex = ~0;

        /* Step 6 */
        fcc->fdsr = 0xd555;

        /* Step 7, initialize parameter ram */
        p->rbase = PADDR(ctlr->rdr);
        p->tbase = PADDR(ctlr->tdr);
        p->rstate = (GBL | EB) << 24;
        p->tstate = (GBL | EB) << 24;

        p->cmask = 0xdebb20e3;
        p->cpres = 0xffffffff;

        p->retlim = 15; /* retry limit */

        p->mrblr = (Rbsize+0x1f)&~0x1f;         /* multiple of 32 */
        p->mflr = Rbsize;
        p->minflr = ETHERMINTU;
        p->maxd1 = (Rbsize+7) & ~7;
        p->maxd2 = (Rbsize+7) & ~7;

        for(i=0; i<Eaddrlen; i+=2)
                p->paddr[2-i/2] = (ea[i+1]<<8)|ea[i];

        /* Step 7, initialize parameter ram, configuration-dependent values */
        p->riptr = m->immr->fccextra[ctlr->port].ri - (uchar*)IMMR;
        p->tiptr = m->immr->fccextra[ctlr->port].ti - (uchar*)IMMR;
        p->padptr = m->immr->fccextra[ctlr->port].pad - (uchar*)IMMR;
        memset(m->immr->fccextra[ctlr->port].pad, 0x88, 0x20);

        /* Step 8, clear out events */
        fcc->fcce = ~0;

        /* Step 9, Interrupt enable */
        fcc->fccm = TXE | RXF | TXB;

        /* Step 10, Configure interrupt priority (not done here) */
        /* Step 11, Clear out current events */
        /* Step 12, Enable interrupts to the CP interrupt controller */

        /* Step 13, Issue the Init Tx and Rx command, specifying 0xc for ethernet*/
        cpmop(InitRxTx, fccid[ctlr->port], 0xc);

        /* Step 14, Link management */
        if((ctlr->mii = malloc(sizeof(Mii))) == nil)
                return -1;
        ctlr->mii->mir = fccmiimir;
        ctlr->mii->miw = fccmiimiw;
        ctlr->mii->ctlr = ctlr;

        if(mii(ctlr->mii, ~0) == 0 || (phy = ctlr->mii->curphy) == nil){
                free(ctlr->mii);
                ctlr->mii = nil;
                return -1;
        }
        miiane(ctlr->mii, ~0, ~0, ~0);
#ifdef DBG
        print("oui=%X, phyno=%d, ", phy->oui, phy->phyno);
        print("anar=%ux, ", phy->anar);
        print("fc=%ux, ", phy->fc);
        print("mscr=%ux, ", phy->mscr);

        print("link=%ux, ", phy->link);
        print("speed=%ux, ", phy->speed);
        print("fd=%ux, ", phy->fd);
        print("rfc=%ux, ", phy->rfc);
        print("tfc=%ux\n", phy->tfc);
#endif
        /* Step 15, Enable ethernet: done at attach time */
        return 0;
}

static int
reset(Ether* ether)
{
        uchar ea[Eaddrlen];
        Ctlr *ctlr;
        FCC *fcc;
        Block *b;
        int i;

        if(m->cpuhz < 24000000){
                print("%s ether: system speed must be >= 24MHz for ether use\n", ether->type);
                return -1;
        }

        if(ether->port > 3){
                print("%s ether: no FCC port %ld\n", ether->type, ether->port);
                return -1;
        }
        ether->irq = fccirq[ether->port];
        ether->tbdf = BusPPC;
        fcc = imm->fcc + ether->port;

        ctlr = malloc(sizeof(*ctlr));
        ether->ctlr = ctlr;
        memset(ctlr, 0, sizeof(*ctlr));
        ctlr->fcc = fcc;
        ctlr->port = ether->port;
        ctlr->fccid = fccid[ether->port];

        /* Ioringinit will allocate the buffer descriptors in normal memory
         * and NOT in Dual-Ported Ram, as prescribed by the MPC8260
         * PowerQUICC II manual (Section 28.6).  When they are allocated
         * in DPram and the Dcache is enabled, the processor will hang
         */
        if(ioringinit(ctlr, Nrdre, Ntdre, 0) < 0)
                panic("etherfcc init");
        for(i = 0; i < Nrdre; i++){
                b = iallocb(Bufsize);
                b->rp = (uchar*)(((ulong)b->rp + CACHELINESZ-1) & ~(CACHELINESZ-1));
                b->wp = b->rp;
                ctlr->rcvbufs[i] = b;
                ctlr->rdr[i].addr = PADDR(b->wp);
        }

        fccsetup(ctlr, fcc, ether->ea);

        ether->mbps = 100;      /* TO DO: could be 10mbps */
        ether->attach = attach;
        ether->transmit = transmit;
        ether->interrupt = interrupt;
        ether->ifstat = ifstat;

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

        /*
         * Until we know where to find it, insist that the plan9.ini
         * entry holds the Ethernet address.
         */
        memset(ea, 0, Eaddrlen);
        if(memcmp(ea, ether->ea, Eaddrlen) == 0){
                print("no ether address");
                return -1;
        }

        return 0;
}

void
etherfcclink(void)
{
        addethercard("fcc", reset);
}

static void
nanodelay(void)
{
        static int count;
        int i;

        for(i = 0; i < 500; i++)
                count++;
        return;
}

static
void miiwriteloop(Ctlr *ctlr, Port *port, int cnt, ulong cmd)
{
        int i;

        for(i = 0; i < cnt; i++){
                port->pdat &= ~ctlr->pmdck;
                if(cmd & BIT(i))
                        port->pdat |= ctlr->pmdio;
                else
                        port->pdat &= ~ctlr->pmdio;
                nanodelay();
                port->pdat |= ctlr->pmdck;
                nanodelay();
        }
}

static int
fccmiimiw(Mii *mii, int pa, int ra, int data)
{
        int x;
        Port *port;
        ulong cmd;
        Ctlr *ctlr;

        /*
         * MII Management Interface Write.
         */

        ctlr = mii->ctlr;
        port = imm->port + 3;
        cmd = MDIwrite | (pa<<(5+2+16))| (ra<<(2+16)) | (data & 0xffff);

        x = splhi();

        port->pdir |= (ctlr->pmdio|ctlr->pmdck);
        nanodelay();

        miiwriteloop(ctlr, port, 32, ~0);
        miiwriteloop(ctlr, port, 32, cmd);

        port->pdir |= (ctlr->pmdio|ctlr->pmdck);
        nanodelay();

        miiwriteloop(ctlr, port, 32, ~0);

        splx(x);
        return 1;
}

static int
fccmiimir(Mii *mii, int pa, int ra)
{
        int data, i, x;
        Port *port;
        ulong cmd;
        Ctlr *ctlr;

        ctlr = mii->ctlr;
        port = imm->port + 3;

        cmd = MDIread | pa<<(5+2+16) | ra<<(2+16);

        x = splhi();
        port->pdir |= (ctlr->pmdio|ctlr->pmdck);
        nanodelay();

        miiwriteloop(ctlr, port, 32, ~0);

        /* Clock out the first 14 MS bits of the command */
        miiwriteloop(ctlr, port, 14, cmd);

        /* Turn-around */
        port->pdat &= ~ctlr->pmdck;
        port->pdir &= ~ctlr->pmdio;
        nanodelay();

        /* For read, clock in 18 bits, use 16 */
        data = 0;
        for(i=0; i<18; i++){
                data <<= 1;
                if(port->pdat & ctlr->pmdio)
                        data |= 1;
                port->pdat |= ctlr->pmdck;
                nanodelay();
                port->pdat &= ~ctlr->pmdck;
                nanodelay();
        }
        port->pdir |= (ctlr->pmdio|ctlr->pmdck);
        nanodelay();
        miiwriteloop(ctlr, port, 32, ~0);
        splx(x);
        return data & 0xffff;
}

static void
fccltimer(Ureg*, Timer *t)
{
        Ether *ether;
        Ctlr *ctlr;
        MiiPhy *phy;
        ulong gfmr;

        ether = t->ta;
        ctlr = ether->ctlr;
        if(ctlr->mii == nil || ctlr->mii->curphy == nil)
                return;
        phy = ctlr->mii->curphy;
        if(miistatus(ctlr->mii) < 0){
                print("miistatus failed\n");
                return;
        }
        if(phy->link == 0){
                print("link lost\n");
                return;
        }
        ether->mbps = phy->speed;

        if(phy->fd != ctlr->duplex)
                print("set duplex\n");
        ilock(ctlr);
        gfmr = ctlr->fcc->gfmr;
        if(phy->fd != ctlr->duplex){
                ctlr->fcc->gfmr &= ~(ENR|ENT);
                if(phy->fd)
                        ctlr->fcc->fpsmr |= FDE | LPB;          /* full duplex operation */
                else
                        ctlr->fcc->fpsmr &= ~(FDE | LPB);       /* half duplex operation */
                ctlr->duplex = phy->fd;
        }
        ctlr->fcc->gfmr = gfmr;
        iunlock(ctlr);
}