Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * SMSC 9221 Ethernet driver
 * specifically for the ISEE IGEPv2 board,
 * where it is assigned to Chip Select 5,
 * its registers are at 0x2c000000 (inherited from u-boot),
 * and irq is 34 from gpio pin 176, thus gpio module 6.
 *
 * it's slow due to the use of fifos instead of buffer rings.
 * the slow system dma just makes it worse.
 *
 * igepv2 u-boot uses pin 64 on gpio 3 as an output pin to reset the 9221.
 */
#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"

/* currently using kprocs is a lot slower than not (87 s. to boot vs 60) */
#undef USE_KPROCS

enum {
        Vid9221 = 0x9221,
        Slop    = 4,                    /* beyond ETHERMAXTU */
};

typedef struct Regs Regs;
struct Regs {
        /* fifo ports */
        ulong   rxdata;
        uchar   _pad0[0x20 - 4];
        ulong   txdata;
        uchar   _pad1[0x40 - 0x24];
        ulong   rxsts;
        ulong   rxstspeek;
        ulong   txsts;
        ulong   txstspeek;

        /* control & status */
        ushort  rev;                    /* chip revision */
        ushort  id;                     /* chip id, 0x9221 */
        ulong   irqcfg;
        ulong   intsts;
        ulong   inten;
        ulong   _pad2;
        ulong   bytetest;
        ulong   fifoint;                /* fifo level interrupts */
        ulong   rxcfg;
        ulong   txcfg;
        ulong   hwcfg;
        ulong   rxdpctl;                /* rx data path control */
        ulong   rxfifoinf;
        ulong   txfifoinf;
        ulong   pmtctl;                 /* power mgmt. control */
        ulong   gpiocfg;
        ulong   gptcfg;                 /* timer */
        ulong   gptcnt;
        ulong   _pad3;
        ulong   wordswap;
        ulong   freerun;                /* counters */
        ulong   rxdrop;

        /*
         * mac registers are accessed indirectly via the mac csr registers.
         * phy registers are doubly indirect, via the mac csr mii_acc &
         * mii_data mac csr registers.
         */
        ulong   maccsrcmd;              /* mac csr synchronizer */
        ulong   maccsrdata;
        ulong   afccfg;                 /* automatic flow control cfg. */
        ulong   eepcmd;                 /* eeprom */
        ulong   eepdata;
        /* 0xb8 */
};

enum {
        Nstatistics     = 128,
};

enum {
        /* txcmda bits */
        Intcompl        = 1<<31,
        Bufendalign     = 3<<24,        /* mask */
        Datastoff       = 037<<16,      /* mask */
        Firstseg        = 1<<13,
        Lastseg         = 1<<12,
        Bufsize         = MASK(11),

        /* txcmdb bits */
        Pkttag          = MASK(16) << 16,
        Txcksumen       = 1<<14,
        Addcrcdis       = 1<<13,
        Framepaddis     = 1<<12,
        Pktlen          = (1<<1) - 1,   /* mask */

        /* txcfg bits */
        Txsdump         = 1<<15,        /* flush tx status fifo */
        Txddump         = 1<<14,        /* flush tx data fifo */
        Txon            = 1<<1,
        Stoptx          = 1<<0,

        /* hwcfg bits */
        Mbo             = 1<<20,        /* must be one */
        Srstto          = 1<<1,         /* soft reset time-out */
        Srst            = 1<<0,

        /* rxcfg bits */
        Rxdmacntshift   = 16,           /* ulong count, 12 bits wide */
        Rxdmacntmask    = MASK(12) << Rxdmacntshift,
        Rxdump          = 1<<15,        /* flush rx fifos */

        /* rxsts bits */
        Rxpktlenshift   = 16,           /* byte count */
        Rxpktlenmask    = MASK(14) << Rxpktlenshift,
        Rxerr           = 1<<15,

        /* rxfifoinf bits */
        Rxstsusedshift  = 16,           /* ulong count */
        Rxstsusedmask   = MASK(8) << Rxstsusedshift,
        Rxdatausedmask  = MASK(16),     /* byte count */

        /* txfifoinf bits */
        Txstsusedshift  = 16,           /* ulong count */
        Txstsusedmask   = MASK(8) << Txstsusedshift,
        Txdatafreemask  = MASK(16),     /* byte count */

        /* pmtctl bits */
        Dready          = 1<<0,

        /* maccsrcmd bits */
        Csrbusy         = 1<<31,
        Csrread         = 1<<30,        /* not write */
        Csraddrshift    = 0,
        Csraddrmask     = MASK(8) - 1,

        /* mac registers' indices */
        Maccr           = 1,
        Macaddrh,
        Macaddrl,
        Machashh,
        Machashl,
        Macmiiacc,                      /* for doubly-indirect phy access */
        Macmiidata,
        Macflow,
        Macvlan1,
        Macvlan2,
        Macwuff,
        Macwucsr,
        Maccoe,

        /* Maccr bits */
        Rxall           = 1<<31,
        Rcvown          = 1<<23,        /* don't receive own transmissions */
        Fdpx            = 1<<20,        /* full duplex */
        Mcpas           = 1<<19,        /* pass all multicast */
        Prms            = 1<<18,        /* promiscuous */
        Ho              = 1<<15,        /* hash-only filtering */
        Hpfilt          = 1<<13,        /* hash/perfect filtering */
        Padstr          = 1<<8,         /* strip padding & fcs (crc) */
        Txen            = 1<<3,
        Rxen            = 1<<2,

        /* irqcfg bits */
        Irqdeasclr      = 1<<14,        /* deassertion intv'l clear */
        Irqdeassts      = 1<<13,        /* deassertion intv'l status */
        Irqint          = 1<<12,        /* intr being asserted? (ro) */
        Irqen           = 1<<8,
        Irqpol          = 1<<4,         /* irq output is active high */
        Irqpushpull     = 1<<0,         /* irq output is push/pull driver */

        /* intsts/inten bits */
        Swint           = 1<<31,        /* generate an interrupt */
        Txstop          = 1<<25,
        Rxstop          = 1<<24,
        Txioc           = 1<<21,
        Rxdma           = 1<<20,
        Gptimer         = 1<<19,
        Phy             = 1<<18,
        Rxe             = 1<<14,        /* errors */
        Txe             = 1<<13,
        Tdfo            = 1<<10,        /* tx data fifo overrun */
        Tdfa            = 1<<9,         /* tx data fifo available */
        Tsff            = 1<<8,         /* tx status fifo full */
        Tsfl            = 1<<7,         /* tx status fifo level */
        Rsff            = 1<<4,         /* rx status fifo full */
        Rsfl            = 1<<3,         /* rx status fifo level */

        /* eepcmd bits */
        Epcbusy         = 1<<31,
        Epccmdshift     = 28,           /* interesting one is Reload (7) */
        Epctimeout      = 1<<9,
        Epcmacloaded    = 1<<8,
        Epcaddrshift    = 0,
};

enum {
        Rxintrs         = Rsff | Rsfl | Rxe,
        Txintrs         = Tsff | Tsfl | Txe | Txioc,
};

/* wake-up frame filter */
struct Wakeup {
        ulong   bytemask[4];            /* index is filter # */
        uchar   filt0cmd;               /* filter 0 command */
        uchar   _pad0;
        uchar   filt1cmd;
        uchar   _pad1;
        uchar   filt2cmd;
        uchar   _pad2;
        uchar   filt3cmd;
        uchar   _pad3;
        uchar   offset[4];              /* index is filter # */
        ushort  crc16[4];               /* " */
};

typedef struct Ctlr Ctlr;
struct Ctlr {
        int     port;
        Ctlr*   next;
        Ether*  edev;
        Regs*   regs;
        int     active;
        int     started;
        int     inited;
        int     id;
        int     cls;
        ushort  eeprom[0x40];

        QLock   alock;                  /* attach */
        int     nrb;                    /* how many this Ctlr has in the pool */

        int*    nic;
        Lock    imlock;
        int     im;                     /* interrupt mask */

//      Mii*    mii;
//      Rendez  lrendez;
        int     lim;

        int     link;

        QLock   slock;
        uint    statistics[Nstatistics];
        uint    lsleep;
        uint    lintr;
        uint    rsleep;
        uint    rintr;
        int     tsleep;
        uint    tintr;

        uchar   ra[Eaddrlen];           /* receive address */
        ulong   mta[128];               /* multicast table array */

        Rendez  rrendez;
        int     gotinput;
        int     rdcpydone;

        Rendez  trendez;
        int     gotoutput;
        int     wrcpydone;

        Lock    tlock;
};

#define csr32r(c, r)    (*((c)->nic+((r)/4)))
#define csr32w(c, r, v) (*((c)->nic+((r)/4)) = (v))

static Ctlr *smcctlrhead, *smcctlrtail;

static char* statistics[Nstatistics] = { "dummy", };

static uchar mymac[] = { 0xb0, 0x0f, 0xba, 0xbe, 0x00, 0x00, };

static void etherclock(void);
static void smcreceive(Ether *edev);
static void smcinterrupt(Ureg*, void* arg);

static Ether *thisether;
static int attached;

static void
smconce(Ether *edev)
{
        static int beenhere;
        static Lock l;

        ilock(&l);
        if (!beenhere && edev != nil) {
                beenhere = 1;
                /* simulate interrupts if we don't know the irq */
                if (edev->irq < 0) {            /* poll as backup */
                        thisether = edev;
                        addclock0link(etherclock, 1000/HZ);
                        iprint(" polling");
                }
        }
        iunlock(&l);
}

/*
 * indirect (mac) register access
 */

static void
macwait(Regs *regs)
{
        long bound;

        for (bound = 400*Mhz; regs->maccsrcmd & Csrbusy && bound > 0; bound--)
                ;
        if (bound <= 0)
                iprint("smc: mac registers didn't come ready\n");
}

static ulong
macrd(Regs *regs, uchar index)
{
        macwait(regs);
        regs->maccsrcmd = Csrbusy | Csrread | index;
        coherence();            /* back-to-back write/read delay per §6.2.1 */
        macwait(regs);
        return regs->maccsrdata;
}

static void
macwr(Regs *regs, uchar index, ulong val)
{
        macwait(regs);
        regs->maccsrdata = val;
        regs->maccsrcmd = Csrbusy | index;      /* fire */
        macwait(regs);
}


static long
smcifstat(Ether* edev, void* a, long n, ulong offset)
{
        Ctlr *ctlr;
        char *p, *s;
        int i, l, r;

        ctlr = edev->ctlr;
        qlock(&ctlr->slock);
        p = malloc(READSTR);
        l = 0;
        for(i = 0; i < Nstatistics; i++){
                // read regs->rxdrop TODO
                r = 0;
                if((s = statistics[i]) == nil)
                        continue;
                switch(i){
                default:
                        ctlr->statistics[i] += r;
                        if(ctlr->statistics[i] == 0)
                                continue;
                        l += snprint(p+l, READSTR-l, "%s: %ud %ud\n",
                                s, ctlr->statistics[i], r);
                        break;
                }
        }

        l += snprint(p+l, READSTR-l, "lintr: %ud %ud\n",
                ctlr->lintr, ctlr->lsleep);
        l += snprint(p+l, READSTR-l, "rintr: %ud %ud\n",
                ctlr->rintr, ctlr->rsleep);
        l += snprint(p+l, READSTR-l, "tintr: %ud %ud\n",
                ctlr->tintr, ctlr->tsleep);

        l += snprint(p+l, READSTR-l, "eeprom:");
        for(i = 0; i < 0x40; i++){
                if(i && ((i & 0x07) == 0))
                        l += snprint(p+l, READSTR-l, "\n       ");
                l += snprint(p+l, READSTR-l, " %4.4uX", ctlr->eeprom[i]);
        }
        l += snprint(p+l, READSTR-l, "\n");
        USED(l);

        n = readstr(offset, a, n, p);
        free(p);
        qunlock(&ctlr->slock);

        return n;
}

static void
smcpromiscuous(void* arg, int on)
{
        int rctl;
        Ctlr *ctlr;
        Ether *edev;
        Regs *regs;

        edev = arg;
        ctlr = edev->ctlr;
        regs = ctlr->regs;
        rctl = macrd(regs, Maccr);
        if(on)
                rctl |= Prms;
        else
                rctl &= ~Prms;
        macwr(regs, Maccr, rctl);
}

static void
smcmulticast(void*, uchar*, int)
{
        /* nothing to do, we allow all multicast packets in */
}

static int
iswrcpydone(void *arg)
{
        return ((Ctlr *)arg)->wrcpydone;
}

static int
smctxstart(Ctlr *ctlr, uchar *ubuf, uint len)
{
        uint wds, ruplen;
        ulong *wdp, *txdp;
        Regs *regs;
        static ulong buf[ROUNDUP(ETHERMAXTU, sizeof(ulong)) / sizeof(ulong)];

        if (!ctlr->inited) {
                iprint("smctxstart: too soon to send\n");
                return -1;              /* toss it */
        }
        regs = ctlr->regs;

        /* is there room for a packet in the tx data fifo? */
        if (len < ETHERMINTU)
                iprint("sending too-short (%d) pkt\n", len);
        else if (len > ETHERMAXTU)
                iprint("sending jumbo (%d) pkt\n", len);

        ruplen = ROUNDUP(len, sizeof(ulong));
        coherence();    /* back-to-back read/read delay per §6.2.2 */
        if ((regs->txfifoinf & Txdatafreemask) < ruplen + 2*sizeof(ulong))
                return -1;      /* not enough room for data + command words */

        if ((uintptr)ubuf & MASK(2)) {          /* ensure word alignment */
                memmove(buf, ubuf, len);
                ubuf = (uchar *)buf;
        }

        /* tx cmd a: length is bytes in this buffer */
        txdp = &regs->txdata;
        *txdp = Intcompl | Firstseg | Lastseg | len;
        /* tx cmd b: length is bytes in this packet (could be multiple buf.s) */
        *txdp = len;

        /* shovel pkt into tx fifo, which triggers transmission due to Txon */
        wdp = (ulong *)ubuf;
        for (wds = ruplen / sizeof(ulong) + 1; --wds > 0; )
                *txdp = *wdp++;

        regs->intsts = Txintrs;         /* dismiss intr */
        coherence();
        regs->inten |= Txintrs;
        coherence();            /* back-to-back write/read delay per §6.2.1 */
        return 0;
}

static void
smctransmit(Ether* edev)
{
        Block *bp;
        Ctlr *ctlr;

        ctlr = edev->ctlr;
        if (ctlr == nil)
                panic("smctransmit: nil ctlr");
        ilock(&ctlr->tlock);
        /*
         * Try to fill the chip's buffers back up, via the tx fifo.
         */
        while ((bp = qget(edev->oq)) != nil)
                if (smctxstart(ctlr, bp->rp, BLEN(bp)) < 0) {
                        qputback(edev->oq, bp); /* retry the block later */
                        iprint("smctransmit: tx data fifo full\n");
                        break;
                } else
                        freeb(bp);
        iunlock(&ctlr->tlock);
}

static void
smctransmitcall(Ether *edev)            /* called from devether.c */
{
        Ctlr *ctlr;

        ctlr = edev->ctlr;
        ctlr->gotoutput = 1;
#ifdef USE_KPROCS
        wakeup(&ctlr->trendez);
#else
        smctransmit(edev);
#endif
}

static int
smcrim(void* ctlr)
{
        return ((Ctlr*)ctlr)->gotinput;
}

static void
smcrproc(void* arg)
{
        Ctlr *ctlr;
        Ether *edev;

        edev = arg;
        ctlr = edev->ctlr;
        for(;;){
                ctlr->rsleep++;
                sleep(&ctlr->rrendez, smcrim, ctlr);

                /* process any newly-arrived packets and pass to etheriq */
                ctlr->gotinput = 0;
                smcreceive(edev);
        }
}

static int
smcgotout(void* ctlr)
{
        return ((Ctlr*)ctlr)->gotoutput;
}

static void
smctproc(void* arg)
{
        Ctlr *ctlr;
        Ether *edev;

        edev = arg;
        ctlr = edev->ctlr;
        for(;;){
                ctlr->tsleep++;
                sleep(&ctlr->trendez, smcgotout, ctlr);

                /* process any newly-arrived packets and pass to etheriq */
                ctlr->gotoutput = 0;
                smctransmit(edev);
        }
}

void    gpioirqclr(void);

static void
smcattach(Ether* edev)
{
#ifdef USE_KPROCS
        char name[KNAMELEN];
#endif
        Ctlr *ctlr;

        ctlr = edev->ctlr;
        qlock(&ctlr->alock);
        if(waserror()){
                qunlock(&ctlr->alock);
                nexterror();
        }
        if (!ctlr->inited) {
                ctlr->inited = 1;
#ifdef USE_KPROCS
                snprint(name, KNAMELEN, "#l%drproc", edev->ctlrno);
                kproc(name, smcrproc, edev);

                snprint(name, KNAMELEN, "#l%dtproc", edev->ctlrno);
                kproc(name, smctproc, edev);
#endif

iprint("smcattach:");
#ifdef USE_KPROCS
iprint(" with kprocs");
#else
iprint(" no kprocs");
#endif
iprint(", no dma");
                /* can now accept real or simulated interrupts */

                smconce(edev);
                attached = 1;
iprint("\n");
        }
        qunlock(&ctlr->alock);
        poperror();
}

static int
isrdcpydone(void *arg)
{
        return ((Ctlr *)arg)->rdcpydone;
}

static void
smcreceive(Ether *edev)
{
        uint wds, len, sts;
        ulong *wdp, *rxdp;
        Block *bp;
        Ctlr *ctlr;
        Regs *regs;

        ctlr = edev->ctlr;
        regs = ctlr->regs;
        coherence();            /* back-to-back read/read delay per §6.2.2 */
        /*
         * is there a full packet in the rx data fifo?
         */
        while (((regs->rxfifoinf & Rxstsusedmask) >> Rxstsusedshift) != 0) {
                coherence();
                sts = regs->rxsts;              /* pop rx status */
                if(sts & Rxerr)
                        iprint("smcreceive: rx error\n");
                len = (sts & Rxpktlenmask) >> Rxpktlenshift;
                if (len > ETHERMAXTU + Slop)
                        iprint("smcreceive: oversized rx pkt (%d)\n", len);
                else if (len < ETHERMINTU)
                        iprint("smcreceive: too-short (%d) pkt\n", len);
                wds = ROUNDUP(len, sizeof(ulong)) / sizeof(ulong);
                if (wds > 0) {
                        /* copy aligned words from rx fifo into a Block */
                        bp = iallocb(len + sizeof(ulong) /* - 1 */);
                        if (bp == nil)
                                panic("smcreceive: nil Block*");

                        /* bp->rp should be 32-byte aligned, more than we need */
                        assert(((uintptr)bp->rp & (sizeof(ulong) - 1)) == 0);
                        wdp = (ulong *)bp->rp;
                        rxdp = &regs->rxdata;
                        wds = ROUNDUP(len, sizeof(ulong)) / sizeof(ulong) + 1;
                        while (--wds > 0)
                                *wdp++ = *rxdp;
                        bp->wp = bp->rp + len;

                        /* and push the Block upstream */
                        if (ctlr->inited)
                                etheriq(edev, bp, 1);
                        else
                                freeb(bp);

                        regs->intsts = Rxintrs;         /* dismiss intr */
                        coherence();
                        regs->inten |= Rxintrs;
                }
                coherence();
        }
        regs->inten |= Rxintrs;
        coherence();
}

/*
 * disable the stsclr bits in inten and write them to intsts to ack and dismiss
 * the interrupt source.
 */
void
ackintr(Regs *regs, ulong stsclr)
{
        if (stsclr == 0)
                return;

        regs->inten &= ~stsclr;
        coherence();

//      regs->intsts = stsclr;          /* acknowledge & clear intr(s) */
//      coherence();
}

static void
smcinterrupt(Ureg*, void* arg)
{
        int junk;
        unsigned intsts, intr;
        Ctlr *ctlr;
        Ether *edev;
        Regs *regs;

        edev = arg;
        ctlr = edev->ctlr;
        ilock(&ctlr->imlock);
        regs = ctlr->regs;

        gpioirqclr();

        coherence();            /* back-to-back read/read delay per §6.2.2 */
        intsts = regs->intsts;
        coherence();

        intsts &= ~MASK(3);             /* ignore gpio bits */
        if (0 && intsts == 0) {
                coherence();
                iprint("smc: interrupt without a cause; insts %#ux (vs inten %#lux)\n",
                        intsts, regs->inten);
        }

        intr = intsts & Rxintrs;
        if(intr) {
                /* disable interrupt sources; kproc/smcreceive will reenable */
                ackintr(regs, intr);

                ctlr->rintr++;
                ctlr->gotinput = 1;
#ifdef USE_KPROCS
                wakeup(&ctlr->rrendez);
#else
                smcreceive(edev);
#endif
        }

        while(((regs->txfifoinf & Txstsusedmask) >> Txstsusedshift) != 0) {
                /* probably indicates tx completion, just toss it */
                junk = regs->txsts;             /* pop tx sts */
                USED(junk);
                coherence();
        }

        intr = intsts & Txintrs;
        if (ctlr->gotoutput || intr) {
                /* disable interrupt sources; kproc/smctransmit will reenable */
                ackintr(regs, intr);

                ctlr->tintr++;
                ctlr->gotoutput = 1;
#ifdef USE_KPROCS
                wakeup(&ctlr->trendez);
#else
                smctransmit(edev);
#endif
        }

        iunlock(&ctlr->imlock);
}

static void
etherclock(void)
{
        smcinterrupt(nil, thisether);
}

static int
smcmii(Ctlr *)
{
        return 0;
}

static int
smcdetach(Ctlr* ctlr)
{
        Regs *regs;

        if (ctlr == nil || ctlr->regs == nil)
                return -1;
        regs = ctlr->regs;
        /* verify that it's real by reading a few registers */
        switch (regs->id) {
        case Vid9221:
                break;
        default:
                print("smc: unknown chip id %#ux\n", regs->id);
                return -1;
        }
        regs->inten = 0;                /* no interrupts */
        regs->intsts = ~0;              /* clear any pending */
        regs->gptcfg = 0;
        coherence();
        regs->rxcfg = Rxdump;
        regs->txcfg = Txsdump | Txddump;
        regs->irqcfg &= ~Irqen;
        coherence();
        return 0;
}

static void
smcshutdown(Ether* ether)
{
        smcdetach(ether->ctlr);
}

static void
powerwait(Regs *regs)
{
        long bound;

        regs->bytetest = 0;                     /* bring power on */
        for (bound = 400*Mhz; !(regs->pmtctl & Dready) && bound > 0; bound--)
                ;
        if (bound <= 0)
                iprint("smc: pmtctl didn't come ready\n");
}

static int
smcreset(Ctlr* ctlr)
{
        int r;
        Regs *regs;
        static char zea[Eaddrlen];

        regs = ctlr->regs;
        powerwait(regs);

        if(smcdetach(ctlr))
                return -1;

        /* verify that it's real by reading a few registers */
        switch (regs->id) {
        case Vid9221:
                break;
        default:
                print("smc: unknown chip id %#ux\n", regs->id);
                return -1;
        }
        if (regs->bytetest != 0x87654321) {
                print("smc: bytetest reg %#p (%#lux) != 0x87654321\n",
                        &regs->bytetest, regs->bytetest);
                return -1;
        }

#ifdef TODO                     /* read MAC from EEPROM */
//      int ctrl, i, pause, swdpio, txcw;
        /*
         * Snarf and set up the receive addresses.
         * There are 16 addresses. The first should be the MAC address.
         * The others are cleared and not marked valid (MS bit of Rah).
         */
        for(i = Ea; i < Eaddrlen/2; i++){
                ctlr->ra[2*i] = ctlr->eeprom[i];
                ctlr->ra[2*i+1] = ctlr->eeprom[i]>>8;
        }

        /*
         * Clear the Multicast Table Array.
         * It's a 4096 bit vector accessed as 128 32-bit registers.
         */
        memset(ctlr->mta, 0, sizeof(ctlr->mta));
        for(i = 0; i < 128; i++)
                csr32w(ctlr, Mta+i*4, 0);
#endif
        regs->hwcfg |= Mbo;

        /* don't overwrite existing ea */
//      if (memcmp(edev->ea, zea, Eaddrlen) == 0)
//              memmove(edev->ea, ctlr->ra, Eaddrlen);

        r = ctlr->ra[3]<<24 | ctlr->ra[2]<<16 | ctlr->ra[1]<<8 | ctlr->ra[0];
        macwr(regs, Macaddrl, r);
        macwr(regs, Macaddrh, ctlr->ra[5]<<8 | ctlr->ra[4]);

        /* turn on the controller */
        macwr(regs, Maccoe, 0);
        regs->inten = 0;                /* no interrupts yet */
        regs->intsts = ~0;              /* clear any pending */
        regs->gptcfg = 0;
        coherence();
        regs->rxcfg = Rxdump;
        regs->txcfg = Txsdump | Txddump | Txon;
        regs->fifoint = 72<<24;         /* default values */
        macwr(regs, Maccr, Rxall | Rcvown | Fdpx | Mcpas | Txen | Rxen);
        coherence();            /* back-to-back write/read delay per §6.2.1 */
        regs->irqcfg = 1<<24 | Irqen | Irqpushpull;  /* deas for 10µs (linux) */
        coherence();            /* back-to-back write/read delay per §6.2.1 */
        regs->inten = Rxintrs | Txintrs;
        coherence();

        if(smcmii(ctlr) < 0)
                return -1;
        return 0;
}

static void
smcpci(void)
{
        Ctlr *ctlr;
        static int beenhere;

        if (beenhere)
                return;
        beenhere = 1;

        if (probeaddr(PHYSETHER) < 0)
                return;
        ctlr = malloc(sizeof(Ctlr));
        ctlr->id = Vid9221<<16 | 0x0424;        /* smsc 9221 */
        ctlr->port = PHYSETHER;
        ctlr->nic = (int *)PHYSETHER;
        ctlr->regs = (Regs *)PHYSETHER;

        if(smcreset(ctlr)){
                free(ctlr);
                return;
        }
        if(smcctlrhead != nil)
                smcctlrtail->next = ctlr;
        else
                smcctlrhead = ctlr;
        smcctlrtail = ctlr;
}

static int
smcpnp(Ether* edev)
{
        Ctlr *ctlr;
        static char zea[Eaddrlen];

        if(smcctlrhead == nil)
                smcpci();

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

        edev->ctlr = ctlr;
        ctlr->edev = edev;                      /* point back to Ether* */
        edev->port = ctlr->port;
        edev->irq = 34;
// TODO: verify speed (100Mb/s) and duplicity (full-duplex)
        edev->mbps = 100;

        /* don't overwrite existing ea */
        if (memcmp(edev->ea, zea, Eaddrlen) == 0)
                memmove(edev->ea, ctlr->ra, Eaddrlen);

        /*
         * Linkage to the generic ethernet driver.
         */
        edev->attach = smcattach;
        edev->transmit = smctransmitcall;
        edev->interrupt = smcinterrupt;
        edev->ifstat = smcifstat;
/*      edev->ctl = smcctl;                     /* no ctl msgs supported */

        edev->arg = edev;
        edev->promiscuous = smcpromiscuous;
        edev->multicast = smcmulticast;
        edev->shutdown = smcshutdown;
        return 0;
}

void
ether9221link(void)
{
        addethercard("9221", smcpnp);
}