Subversion Repositories planix.SVN

Rev

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

/*
 * National Semiconductor DP83815
 *
 * Supports only internal PHY and has been tested on:
 *      Netgear FA311TX (using Netgear DS108 10/100 hub)
 *      SiS 900 within SiS 630
 * To do:
 *      check Ethernet address;
 *      test autonegotiation on 10 Mbit, and 100 Mbit full duplex;
 *      external PHY via MII (should be common code for MII);
 *      thresholds;
 *      ring sizing;
 *      physical link changes/disconnect;
 *      push initialisation back to attach.
 *
 * C H Forsyth, forsyth@vitanuova.com, 18th June 2001.
 */

#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)

typedef struct Des {
        ulong   next;
        int     cmdsts;
        ulong   addr;
        Block*  bp;
} Des;

enum {  /* cmdsts */
        Own     = 1<<31,        /* set by data producer to hand to consumer */
        More    = 1<<30,        /* more of packet in next descriptor */
        Intr    = 1<<29,        /* interrupt when device is done with it */
        Supcrc  = 1<<28,        /* suppress crc on transmit */
        Inccrc  = 1<<28,        /* crc included on receive (always) */
        Ok      = 1<<27,        /* packet ok */
        Size    = 0xFFF,        /* packet size in bytes */

        /* transmit */
        Txa     = 1<<26,        /* transmission aborted */
        Tfu     = 1<<25,        /* transmit fifo underrun */
        Crs     = 1<<24,        /* carrier sense lost */
        Td      = 1<<23,        /* transmission deferred */
        Ed      = 1<<22,        /* excessive deferral */
        Owc     = 1<<21,        /* out of window collision */
        Ec      = 1<<20,        /* excessive collisions */
        /* 19-16 collision count */

        /* receive */
        Rxa     = 1<<26,        /* receive aborted (same as Rxo) */
        Rxo     = 1<<25,        /* receive overrun */
        Dest    = 3<<23,        /* destination class */
          Drej= 0<<23,           /* packet was rejected */
          Duni= 1<<23,          /* unicast */
          Dmulti=       2<<23,          /* multicast */
          Dbroad=       3<<23,          /* broadcast */
        Long = 1<<22,           /* too long packet received */
        Runt =  1<<21,          /* packet less than 64 bytes */
        Ise =   1<<20,          /* invalid symbol */
        Crce =  1<<19,          /* invalid crc */
        Fae =   1<<18,          /* frame alignment error */
        Lbp =   1<<17,          /* loopback packet */
        Col =   1<<16,          /* collision during receive */
};

enum {                          /* PCI vendor & device IDs */
        Nat83815        = (0x0020<<16)|0x100B,
        SiS =   0x1039,
        SiS900 =        (0x0900<<16)|SiS,
        SiS7016 =       (0x7016<<16)|SiS,

        SiS630bridge    = 0x0008,

        /* SiS 900 PCI revision codes */
        SiSrev630s =    0x81,
        SiSrev630e =    0x82,
        SiSrev630ea1 =  0x83,

        SiSeenodeaddr = 8,              /* short addr of SiS eeprom mac addr */
        SiS630eenodeaddr =      9,      /* likewise for the 630 */
        Nseenodeaddr =  6,              /* " for NS eeprom */
        Nat83815avng =  0x403,
        Nat83816avng =  0x505,          /* 83816 acts like submodel of 83815 */
                                        /* using reg. 0x58 to disambiguate. */
};

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

        ushort  srom[0xB+1];
        uchar   sromea[Eaddrlen];       /* MAC address */

        uchar   fd;                     /* option or auto negotiation */

        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;

        ulong   rxa;                    /* receive statistics */
        ulong   rxo;
        ulong   rlong;
        ulong   runt;
        ulong   ise;
        ulong   crce;
        ulong   fae;
        ulong   lbp;
        ulong   col;
        ulong   rxsovr;
        ulong   rxorn;

        ulong   txa;                    /* transmit statistics */
        ulong   tfu;
        ulong   crs;
        ulong   td;
        ulong   ed;
        ulong   owc;
        ulong   ec;
        ulong   txurn;

        ulong   dperr;                  /* system errors */
        ulong   rmabt;
        ulong   rtabt;
        ulong   sserr;
        ulong   rxsover;

        ulong   version;                /* silicon version; register 0x58h */
} Ctlr;

static Ctlr* ctlrhead;
static Ctlr* ctlrtail;

enum {
        /* registers (could memory map) */
        Rcr=    0x00,           /* command register */
          Rst=          1<<8,
          Rxr=          1<<5,   /* receiver reset */
          Txr=          1<<4,   /* transmitter reset */
          Rxd=          1<<3,   /* receiver disable */
          Rxe=          1<<2,   /* receiver enable */
          Txd=          1<<1,   /* transmitter disable */
          Txe=          1<<0,   /* transmitter enable */
        Rcfg=   0x04,           /* configuration */
          Lnksts=       1<<31,  /* link good */
          Speed100=     1<<30,  /* 100 Mb/s link */
          Fdup=         1<<29,  /* full duplex */
          Pol=          1<<28,  /* polarity reversal (10baseT) */
          Aneg_dn=      1<<27,  /* autonegotiation done */
          Pint_acen=    1<<17,  /* PHY interrupt auto clear enable */
          Pause_adv=    1<<16,  /* advertise pause during auto neg */
          Paneg_ena=    1<<13,  /* auto negotiation enable */
          Paneg_all=    7<<13,  /* auto negotiation enable 10/100 half & full */
          Ext_phy=      1<<12,  /* enable MII for external PHY */
          Phy_rst=      1<<10,  /* reset internal PHY */
          Phy_dis=      1<<9,   /* disable internal PHY (eg, low power) */
          Req_alg=      1<<7,   /* PCI bus request: set means less aggressive */
          Sb=           1<<6,   /* single slot back-off not random */
          Pow=          1<<5,   /* out of window timer selection */
          Exd=          1<<4,   /* disable excessive deferral timer */
          Pesel=        1<<3,   /* parity error algorithm selection */
          Brom_dis=     1<<2,   /* disable boot rom interface */
          Bem=          1<<0,   /* big-endian mode */
        Rmear=  0x08,           /* eeprom access */
          Mdc=          1<<6,   /* MII mangement check */
          Mddir=        1<<5,   /* MII management direction */
          Mdio=         1<<4,   /* MII mangement data */
          Eesel=        1<<3,   /* EEPROM chip select */
          Eeclk=        1<<2,   /* EEPROM clock */
          Eedo=         1<<1,   /* EEPROM data out (from chip) */
          Eedi=         1<<0,   /* EEPROM data in (to chip) */
        Rptscr= 0x0C,           /* pci test control */
        Risr=   0x10,           /* interrupt status */
          Txrcmp=       1<<25,  /* transmit reset complete */
          Rxrcmp=       1<<24,  /* receiver reset complete */
          Dperr=        1<<23,  /* detected parity error */
          Sserr=        1<<22,  /* signalled system error */
          Rmabt=        1<<21,  /* received master abort */
          Rtabt=        1<<20,  /* received target abort */
          Rxsovr=       1<<16,  /* RX status FIFO overrun */
          Hiberr=       1<<15,  /* high bits error set (OR of 25-16) */
          Phy=          1<<14,  /* PHY interrupt */
          Pme=          1<<13,  /* power management event (wake online) */
          Swi=          1<<12,  /* software interrupt */
          Mib=          1<<11,  /* MIB service */
          Txurn=        1<<10,  /* TX underrun */
          Txidle=       1<<9,   /* TX idle */
          Txerr=        1<<8,   /* TX packet error */
          Txdesc=       1<<7,   /* TX descriptor (with Intr bit done) */
          Txok=         1<<6,   /* TX ok */
          Rxorn=        1<<5,   /* RX overrun */
          Rxidle=       1<<4,   /* RX idle */
          Rxearly=      1<<3,   /* RX early threshold */
          Rxerr=        1<<2,   /* RX packet error */
          Rxdesc=       1<<1,   /* RX descriptor (with Intr bit done) */
          Rxok=         1<<0,   /* RX ok */
        Rimr=   0x14,           /* interrupt mask */
        Rier=   0x18,           /* interrupt enable */
          Ie=           1<<0,   /* interrupt enable */
        Rtxdp=  0x20,           /* transmit descriptor pointer */
        Rtxcfg= 0x24,           /* transmit configuration */
          Csi=          1<<31,  /* carrier sense ignore (needed for full duplex) */
          Hbi=          1<<30,  /* heartbeat ignore (needed for full duplex) */
          Atp=          1<<28,  /* automatic padding of runt packets */
          Mxdma=        7<<20,  /* maximum dma transfer field */
          Mxdma32=      4<<20,  /* 4x32-bit words (32 bytes) */
          Mxdma64=      5<<20,  /* 8x32-bit words (64 bytes) */
          Flth=         0x3F<<8,/* Tx fill threshold, units of 32 bytes (must be > Mxdma) */
          Drth=         0x3F<<0,/* Tx drain threshold (units of 32 bytes) */
          Flth128=      4<<8,   /* fill at 128 bytes */
          Drth512=      16<<0,  /* drain at 512 bytes */
        Rrxdp=  0x30,           /* receive descriptor pointer */
        Rrxcfg= 0x34,           /* receive configuration */
          Atx=          1<<28,  /* accept transmit packets (needed for full duplex) */
          Rdrth=        0x1F<<1,/* Rx drain threshold (units of 32 bytes) */
          Rdrth64=      2<<1,   /* drain at 64 bytes */
        Rccsr=  0x3C,           /* CLKRUN control/status */
          Pmests=       1<<15,  /* PME status */
        Rwcsr=  0x40,           /* wake on lan control/status */
        Rpcr=   0x44,           /* pause control/status */
        Rrfcr=  0x48,           /* receive filter/match control */
          Rfen=         1<<31,  /* receive filter enable */
          Aab=          1<<30,  /* accept all broadcast */
          Aam=          1<<29,  /* accept all multicast */
          Aau=          1<<28,  /* accept all unicast */
          Apm=          1<<27,  /* accept on perfect match */
          Apat=         0xF<<23,/* accept on pattern match */
          Aarp=         1<<22,  /* accept ARP */
          Mhen=         1<<21,  /* multicast hash enable */
          Uhen=         1<<20,  /* unicast hash enable */
          Ulm=          1<<19,  /* U/L bit mask */
                                /* bits 0-9 are rfaddr */
        Rrfdr=  0x4C,           /* receive filter/match data */
        Rbrar=  0x50,           /* boot rom address */
        Rbrdr=  0x54,           /* boot rom data */
        Rsrr=   0x58,           /* silicon revision */
        Rmibc=  0x5C,           /* MIB control */
                                /* 60-78 MIB data */

        /* PHY registers */
        Rbmcr=  0x80,           /* basic mode configuration */
          Reset=        1<<15,
          Sel100=       1<<13,  /* select 100Mb/sec if no auto neg */
          Anena=        1<<12,  /* auto negotiation enable */
          Anrestart=    1<<9,   /* restart auto negotiation */
          Selfdx=       1<<8,   /* select full duplex if no auto neg */
        Rbmsr=  0x84,           /* basic mode status */
          Ancomp=       1<<5,   /* autonegotiation complete */
        Rphyidr1= 0x88,
        Rphyidr2= 0x8C,
        Ranar=  0x90,           /* autonegotiation advertisement */
        Ranlpar= 0x94,          /* autonegotiation link partner ability */
        Raner=  0x98,           /* autonegotiation expansion */
        Rannptr= 0x9C,          /* autonegotiation next page TX */
        Rphysts= 0xC0,          /* PHY status */
        Rmicr=  0xC4,           /* MII control */
          Inten=        1<<1,   /* PHY interrupt enable */
        Rmisr=  0xC8,           /* MII status */
        Rfcscr= 0xD0,           /* false carrier sense counter */
        Rrecr=  0xD4,           /* receive error counter */
        Rpcsr=  0xD8,           /* 100Mb config/status */
        Rphycr= 0xE4,           /* PHY control */
        Rtbscr= 0xE8,           /* 10BaseT status/control */
};

/*
 * eeprom addresses
 *      7 to 9 (16 bit words): mac address, shifted and reversed
 */

#define csr32r(c, r)    (inl((c)->port+(r)))
#define csr32w(c, r, l) (outl((c)->port+(r), (ulong)(l)))
#define csr16r(c, r)    (ins((c)->port+(r)))
#define csr16w(c, r, l) (outs((c)->port+(r), (ulong)(l)))

static void
dumpcregs(Ctlr *ctlr)
{
        int i;

        for(i=0; i<=0x5C; i+=4)
                print("%2.2ux %8.8lux\n", i, csr32r(ctlr, i));
}

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

        ctlr = ((Ether*)arg)->ctlr;
        ilock(&ctlr->lock);
        w = csr32r(ctlr, Rrfcr);
        if(on != ((w&Aau)!=0)){
                csr32w(ctlr, Rrfcr, w & ~Rfen);
                csr32w(ctlr, Rrfcr, Rfen | (w ^ Aau));
        }
        iunlock(&ctlr->lock);
}

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

        ctlr = ether->ctlr;
        ilock(&ctlr->lock);
        if(0)
                dumpcregs(ctlr);
        csr32w(ctlr, Rcr, Rxe);
        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->crce;
        ether->frames = ctlr->runt+ctlr->ise+ctlr->rlong+ctlr->fae;
        ether->buffs = ctlr->rxorn+ctlr->tfu;
        ether->overflows = ctlr->rxsovr;

        if(n == 0)
                return 0;

        p = malloc(READSTR);
        if(p == nil)
                error(Enomem);
        l = snprint(p, READSTR, "Rxa: %lud\n", ctlr->rxa);
        l += snprint(p+l, READSTR-l, "Rxo: %lud\n", ctlr->rxo);
        l += snprint(p+l, READSTR-l, "Rlong: %lud\n", ctlr->rlong);
        l += snprint(p+l, READSTR-l, "Runt: %lud\n", ctlr->runt);
        l += snprint(p+l, READSTR-l, "Ise: %lud\n", ctlr->ise);
        l += snprint(p+l, READSTR-l, "Fae: %lud\n", ctlr->fae);
        l += snprint(p+l, READSTR-l, "Lbp: %lud\n", ctlr->lbp);
        l += snprint(p+l, READSTR-l, "Tfu: %lud\n", ctlr->tfu);
        l += snprint(p+l, READSTR-l, "Txa: %lud\n", ctlr->txa);
        l += snprint(p+l, READSTR-l, "CRC Error: %lud\n", ctlr->crce);
        l += snprint(p+l, READSTR-l, "Collision Seen: %lud\n", ctlr->col);
        l += snprint(p+l, READSTR-l, "Frame Too Long: %lud\n", ctlr->rlong);
        l += snprint(p+l, READSTR-l, "Runt Frame: %lud\n", ctlr->runt);
        l += snprint(p+l, READSTR-l, "Rx Underflow Error: %lud\n", ctlr->rxorn);
        l += snprint(p+l, READSTR-l, "Tx Underrun: %lud\n", ctlr->txurn);
        l += snprint(p+l, READSTR-l, "Excessive Collisions: %lud\n", ctlr->ec);
        l += snprint(p+l, READSTR-l, "Late Collision: %lud\n", ctlr->owc);
        l += snprint(p+l, READSTR-l, "Loss of Carrier: %lud\n", ctlr->crs);
        l += snprint(p+l, READSTR-l, "Parity: %lud\n", ctlr->dperr);
        l += snprint(p+l, READSTR-l, "Aborts: %lud\n", ctlr->rmabt+ctlr->rtabt);
        l += snprint(p+l, READSTR-l, "RX Status overrun: %lud\n", ctlr->rxsover);
        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 < nelem(ctlr->srom); i++){
                if(i && ((i & 0x0F) == 0))
                        l += snprint(p+l, READSTR-l, "\n     ");
                l += snprint(p+l, READSTR-l, " %4.4uX", 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 started;

        ctlr = ether->ctlr;
        started = 0;
        while(ctlr->ntq < ctlr->ntdr-1){
                bp = qget(ether->oq);
                if(bp == nil)
                        break;
                des = &ctlr->tdr[ctlr->tdrh];
                des->bp = bp;
                des->addr = PADDR(bp->rp);
                ctlr->ntq++;
                coherence();
                des->cmdsts = Own | BLEN(bp);
                ctlr->tdrh = NEXT(ctlr->tdrh, ctlr->ntdr);
                started = 1;
        }
        if(started){
                coherence();
                csr32w(ctlr, Rcr, Txe); /* prompt */
        }

        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
txrxcfg(Ctlr *ctlr, int txdrth)
{
        ulong rx, tx;

        rx = csr32r(ctlr, Rrxcfg);
        tx = csr32r(ctlr, Rtxcfg);
        if(ctlr->fd){
                rx |= Atx;
                tx |= Csi | Hbi;
        }else{
                rx &= ~Atx;
                tx &= ~(Csi | Hbi);
        }
        tx &= ~(Mxdma|Drth|Flth);
        tx |= Mxdma64 | Flth128 | txdrth;
        csr32w(ctlr, Rtxcfg, tx);
        rx &= ~(Mxdma|Rdrth);
        rx |= Mxdma64 | Rdrth64;
        csr32w(ctlr, Rrxcfg, rx);
}

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

        ether = arg;
        ctlr = ether->ctlr;

        while((status = csr32r(ctlr, Risr)) != 0){

                status &= ~(Pme|Mib);

                if(status & Hiberr){
                        if(status & Rxsovr)
                                ctlr->rxsover++;
                        if(status & Sserr)
                                ctlr->sserr++;
                        if(status & Dperr)
                                ctlr->dperr++;
                        if(status & Rmabt)
                                ctlr->rmabt++;
                        if(status & Rtabt)
                                ctlr->rtabt++;
                        status &= ~(Hiberr|Txrcmp|Rxrcmp|Rxsovr|Dperr|Sserr|Rmabt|Rtabt);
                }

                /* update link state */
                if(status&Phy){
                        status &= ~Phy;
                        csr32r(ctlr, Rcfg);
                        n = csr32r(ctlr, Rcfg);
//                      iprint("83815 phy %x %x\n", n, n&Lnksts);
                        ether->link = (n&Lnksts) != 0;
                }

                /*
                 * Received packets.
                 */
                if(status & (Rxdesc|Rxok|Rxerr|Rxearly|Rxorn)){
                        des = &ctlr->rdr[ctlr->rdrx];
                        while((cmdsts = des->cmdsts) & Own){
                                if((cmdsts&Ok) == 0){
                                        if(cmdsts & Rxa)
                                                ctlr->rxa++;
                                        if(cmdsts & Rxo)
                                                ctlr->rxo++;
                                        if(cmdsts & Long)
                                                ctlr->rlong++;
                                        if(cmdsts & Runt)
                                                ctlr->runt++;
                                        if(cmdsts & Ise)
                                                ctlr->ise++;
                                        if(cmdsts & Crce)
                                                ctlr->crce++;
                                        if(cmdsts & Fae)
                                                ctlr->fae++;
                                        if(cmdsts & Lbp)
                                                ctlr->lbp++;
                                        if(cmdsts & Col)
                                                ctlr->col++;
                                }
                                else if(bp = iallocb(Rbsz)){
                                        len = (cmdsts&Size)-4;
                                        if(len <= 0){
                                                debug("ns83815: packet len %d <=0\n", len);
                                                freeb(des->bp);
                                        }else{
                                                des->bp->wp = des->bp->rp+len;
                                                etheriq(ether, des->bp, 1);
                                        }
                                        des->bp = bp;
                                        des->addr = PADDR(bp->rp);
                                        coherence();
                                }else{
                                        debug("ns83815: interrupt: iallocb for input buffer failed\n");
                                        des->bp->next = 0;
                                }

                                des->cmdsts = Rbsz;
                                coherence();

                                ctlr->rdrx = NEXT(ctlr->rdrx, ctlr->nrdr);
                                des = &ctlr->rdr[ctlr->rdrx];
                        }
                        status &= ~(Rxdesc|Rxok|Rxerr|Rxearly|Rxorn);
                }

                /*
                 * 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 & Txurn){
                        ctlr->txurn++;
                        ilock(&ctlr->lock);
                        /* change threshold */
                        iunlock(&ctlr->lock);
                        status &= ~(Txurn);
                }

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

                        if((cmdsts & Ok) == 0){
                                if(cmdsts & Txa)
                                        ctlr->txa++;
                                if(cmdsts & Tfu)
                                        ctlr->tfu++;
                                if(cmdsts & Td)
                                        ctlr->td++;
                                if(cmdsts & Ed)
                                        ctlr->ed++;
                                if(cmdsts & Owc)
                                        ctlr->owc++;
                                if(cmdsts & Ec)
                                        ctlr->ec++;
                                ether->oerrs++;
                        }

                        freeb(des->bp);
                        des->bp = nil;
                        des->cmdsts = 0;

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

                status &= ~(Txurn|Txidle|Txerr|Txdesc|Txok);

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

static void
ctlrinit(Ether* ether)
{
        Ctlr *ctlr;
        Des *des, *last;

        ctlr = ether->ctlr;

        /*
         * Allocate suitable aligned descriptors
         * for the transmit and receive rings;
         * initialise the receive ring;
         * initialise the transmit ring;
         * unmask interrupts and start the transmit side.
         */
        des = xspanalloc((ctlr->nrdr+ctlr->ntdr)*sizeof(Des), 32, 0);
        ctlr->tdr = des;
        ctlr->rdr = des+ctlr->ntdr;

        last = nil;
        for(des = ctlr->rdr; des < &ctlr->rdr[ctlr->nrdr]; des++){
                des->bp = iallocb(Rbsz);
                if(des->bp == nil)
                        error(Enomem);
                des->cmdsts = Rbsz;
                des->addr = PADDR(des->bp->rp);
                if(last != nil)
                        last->next = PADDR(des);
                last = des;
        }
        ctlr->rdr[ctlr->nrdr-1].next = PADDR(ctlr->rdr);
        ctlr->rdrx = 0;
        csr32w(ctlr, Rrxdp, PADDR(ctlr->rdr));

        last = nil;
        for(des = ctlr->tdr; des < &ctlr->tdr[ctlr->ntdr]; des++){
                des->cmdsts = 0;
                des->bp = nil;
                des->addr = ~0;
                if(last != nil)
                        last->next = PADDR(des);
                last = des;
        }
        ctlr->tdr[ctlr->ntdr-1].next = PADDR(ctlr->tdr);
        ctlr->tdrh = 0;
        ctlr->tdri = 0;
        csr32w(ctlr, Rtxdp, PADDR(ctlr->tdr));

        txrxcfg(ctlr, Drth512);

        csr32w(ctlr, Rimr, Dperr|Sserr|Rmabt|Rtabt|Rxsovr|Hiberr|Txurn|Txerr|
                Txdesc|Txok|Rxorn|Rxerr|Rxdesc|Rxok);   /* Phy|Pme|Mib */
        csr32w(ctlr, Rmicr, Inten);     /* enable phy interrupts */
        csr32r(ctlr, Risr);             /* clear status */
        csr32w(ctlr, Rier, Ie);
}

static void
eeclk(Ctlr *ctlr, int clk)
{
        csr32w(ctlr, Rmear, Eesel | clk);
        microdelay(2);
}

static void
eeidle(Ctlr *ctlr)
{
        int i;

        eeclk(ctlr, 0);
        eeclk(ctlr, Eeclk);
        for(i=0; i<25; i++){
                eeclk(ctlr, 0);
                eeclk(ctlr, Eeclk);
        }
        eeclk(ctlr, 0);
        csr32w(ctlr, Rmear, 0);
        microdelay(2);
}

static ushort
eegetw(Ctlr *ctlr, int a)
{
        int d, i, w;

        eeidle(ctlr);
        eeclk(ctlr, 0);
        eeclk(ctlr, Eeclk);
        d = 0x180 | a;
        for(i=0x400; i; i>>=1){
                if(d & i)
                        csr32w(ctlr, Rmear, Eesel|Eedi);
                else
                        csr32w(ctlr, Rmear, Eesel);
                eeclk(ctlr, Eeclk);
                eeclk(ctlr, 0);
                microdelay(2);
        }
        w = 0;
        for(i=0x8000; i; i >>= 1){
                eeclk(ctlr, Eeclk);
                if(csr32r(ctlr, Rmear) & Eedo)
                        w |= i;
                microdelay(2);
                eeclk(ctlr, 0);
        }
        eeidle(ctlr);
        return w;
}

static int
resetctlr(Ctlr *ctlr)
{
        int i;

        /*
         * Soft-reset the controller
         */
        csr32w(ctlr, Rcr, Rst);
        for(i=0;; i++){
                if(i > 100){
                        print("ns83815: soft reset did not complete\n");
                        return -1;
                }
                microdelay(250);
                if((csr32r(ctlr, Rcr) & Rst) == 0)
                        break;
                delay(1);
        }
        return 0;
}

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

print("ether83815 shutting down\n");
        csr32w(ctlr, Rcr, Rxd|Txd);     /* disable transceiver */
        resetctlr(ctlr);
}

static int
softreset(Ctlr* ctlr, int resetphys)
{
        int i, w;

        /*
         * Soft-reset the controller
         */
        resetctlr(ctlr);
        csr32w(ctlr, Rccsr, Pmests);
        csr32w(ctlr, Rccsr, 0);
        csr32w(ctlr, Rcfg, csr32r(ctlr, Rcfg) | Pint_acen);
        ctlr->version = csr32r(ctlr, Rsrr);
        if(resetphys){
                /*
                 * Soft-reset the PHY
                 */
                csr32w(ctlr, Rbmcr, Reset);
                for(i=0;; i++){
                        if(i > 100){
                                print("ns83815: PHY soft reset time out\n");
                                return -1;
                        }
                        if((csr32r(ctlr, Rbmcr) & Reset) == 0)
                                break;
                        delay(1);
                }
        }

        /*
         * Initialisation values, in sequence (see 4.4 Recommended Registers Configuration)
         */
        csr16w(ctlr, 0xCC, 0x0001);     /* PGSEL */
        csr16w(ctlr, 0xE4, 0x189C);     /* PMCCSR */
        csr16w(ctlr, 0xFC, 0x0000);     /* TSTDAT */
        csr16w(ctlr, 0xF4, 0x5040);     /* DSPCFG */
        csr16w(ctlr, 0xF8, 0x008C);     /* SDCFG */

        /*
         * Auto negotiate
         */
        csr16r(ctlr, Rbmsr);            /* clear latched bits */
        debug("anar: %4.4ux\n", csr16r(ctlr, Ranar));
        csr16w(ctlr, Rbmcr, Anena);
        if(csr16r(ctlr, Ranar) == 0 || (csr32r(ctlr, Rcfg) & Aneg_dn) == 0){
                csr16w(ctlr, Rbmcr, Anena|Anrestart);
                for(i=0;; i++){
                        if(i > 3000){
                                print("ns83815: auto neg timed out\n");
                                return -1;
                        }
                        if((w = csr16r(ctlr, Rbmsr)) & Ancomp)
                                break;
                        delay(1);
                }
                debug("%d ms\n", i);
                w &= 0xFFFF;
                debug("bmsr: %4.4ux\n", w);
                USED(w);
        }
        debug("anar: %4.4ux\n", csr16r(ctlr, Ranar));
        debug("anlpar: %4.4ux\n", csr16r(ctlr, Ranlpar));
        debug("aner: %4.4ux\n", csr16r(ctlr, Raner));
        debug("physts: %4.4ux\n", csr16r(ctlr, Rphysts));
        debug("tbscr: %4.4ux\n", csr16r(ctlr, Rtbscr));
        return 0;
}

static int
media(Ether* ether)
{
        Ctlr* ctlr;
        ulong cfg;

        ctlr = ether->ctlr;
        cfg = csr32r(ctlr, Rcfg);
        ctlr->fd = (cfg & Fdup) != 0;
        ether->link = (cfg&Lnksts) != 0;
        return (cfg&(Lnksts|Speed100)) == Lnksts? 10: 100;
}

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 int
is630(ulong id, Pcidev *p)
{
        if(id == SiS900)
                switch (p->rid) {
                case SiSrev630s:
                case SiSrev630e:
                case SiSrev630ea1:
                        return 1;
                }
        return 0;
}

enum {
        MagicReg = 0x48,
        MagicRegSz = 1,
        Magicrden = 0x40,       /* read enable, apparently */
        Paddr=          0x70,   /* address port */
        Pdata=          0x71,   /* data port */
};

/* rcmos() originally from LANL's SiS 900 driver's rcmos() */
static int
sisrdcmos(Ctlr *ctlr)
{
        int i;
        unsigned reg;
        ulong port;
        Pcidev *p;

        debug("ns83815: SiS 630 rev. %ux reading mac address from cmos\n", ctlr->pcidev->rid);
        p = pcimatch(nil, SiS, SiS630bridge);
        if(p == nil) {
                print("ns83815: no SiS 630 rev. %ux bridge for mac addr\n",
                        ctlr->pcidev->rid);
                return 0;
        }
        port = p->mem[0].bar & ~0x01;
        debug("ns83815: SiS 630 rev. %ux reading mac addr from cmos via bridge at port 0x%lux\n", ctlr->pcidev->rid, port);

        reg = pcicfgr8(p, MagicReg);
        pcicfgw8(p, MagicReg, reg|Magicrden);

        for (i = 0; i < Eaddrlen; i++) {
                outb(port+Paddr, SiS630eenodeaddr + i);
                ctlr->sromea[i] = inb(port+Pdata);
        }

        pcicfgw8(p, MagicReg, reg & ~Magicrden);
        return 1;
}

/*
 * If this is a SiS 630E chipset with an embedded SiS 900 controller,
 * we have to read the MAC address from the APC CMOS RAM. - sez freebsd.
 * However, CMOS *is* NVRAM normally.  See devrtc.c:440, memory.c:88.
 */
static void
sissrom(Ctlr *ctlr)
{
        union {
                uchar   eaddr[Eaddrlen];
                ushort  alignment;
        } ee;
        int i, off = SiSeenodeaddr, cnt = sizeof ee.eaddr / sizeof(short);
        ushort *shp = (ushort *)ee.eaddr;

        if(!is630(ctlr->id, ctlr->pcidev) || !sisrdcmos(ctlr)) {
                for (i = 0; i < cnt; i++)
                        *shp++ = eegetw(ctlr, off++);
                memmove(ctlr->sromea, ee.eaddr, sizeof ctlr->sromea);
        }
}

ushort
søkrisee(Ctlr *c, int n)
{
        int i;
        uint cmd;
        ushort r;

        csr32w(c, Rmear, Eesel);

        cmd = 0x180|n;
        for(i = 10; i >= 0; i--){
                n = 1<<3;
                if(cmd&(1<<i))
                        n |= 1;
                csr32w(c, Rmear, n);
                csr32r(c, Rmear);
                csr32w(c, Rmear, n|4);
                csr32r(c, Rmear);
        }

        csr32w(c, Rmear, 1<<3);
        csr32r(c, Rmear);

        r = 0;
        for(i = 0; i < 16; i++){
                csr32w(c, Rmear, 1<<3 | 1<<2);
                csr32r(c, Rmear);
                if(csr32r(c, Rmear) & 2)
                        r |= 1<<i;
                csr32w(c, Rmear, 1<<3);
                csr32r(c, Rmear);
        }

        csr32w(c, Rmear, 1<<3);
        csr32w(c, Rmear, 0);

        return r;
}

static void
nsnormalea(Ctlr *ctlr)
{
        int i, j;

        /*
         * the MAC address is reversed, straddling word boundaries
         */
        j = Nseenodeaddr*16 + 15;
        for(i = 0; i < 48; i++){
                ctlr->sromea[i>>3] |= ((ctlr->srom[j>>4] >> (15-(j&0xF))) & 1) << (i&7);
                j++;
        }
}

static void
ns403ea(Ctlr *ctlr)
{
        int i;
        ushort s, t;

        s = ctlr->srom[6];
        for(i = 0; i < 3; i++){
                t = ctlr->srom[i+7];
                ctlr->sromea[i*2]   = t<<1 | s>>15;
                ctlr->sromea[i*2+1] = t>>7;
                s = t;
        }
}

static void
nssrom(Ctlr* ctlr)
{
        int i, ns403;
        ulong vers;
        ushort (*ee)(Ctlr*, int);

        vers = ctlr->version;
        ns403 = vers == Nat83815avng || vers == Nat83816avng;
        if(ns403){
                ee = søkrisee;
                print("soekris %lx\n", vers);
        }else
                ee = eegetw;

        for(i = 0; i < nelem(ctlr->srom); i++)
                ctlr->srom[i] = ee(ctlr, i);

        if(ns403)
                ns403ea(ctlr);
        else
                nsnormalea(ctlr);
}

static void
srom(Ctlr* ctlr)
{
        memset(ctlr->sromea, 0, sizeof(ctlr->sromea));
        switch (ctlr->id) {
        case SiS900:
        case SiS7016:
                sissrom(ctlr);
                break;
        case Nat83815:
                nssrom(ctlr);
                break;
        default:
                print("ns83815: srom: unknown id 0x%ux\n", ctlr->id);
                break;
        }
}

static void
scanpci83815(void)
{
        Ctlr *ctlr;
        Pcidev *p;
        ulong id;

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

                case Nat83815:
                        break;
                case SiS900:
                        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 = id;

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

                if(softreset(ctlr, 0) == -1){
                        free(ctlr);
                        continue;
                }
                srom(ctlr);

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

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

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

        if(scandone == 0){
                scanpci83815();
                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);
        for(i=0; i<Eaddrlen; i+=2){
                x = ether->ea[i] | (ether->ea[i+1]<<8);
                ctladdr = (ctlr->id == Nat83815? i: i<<15);
                csr32w(ctlr, Rrfcr, ctladdr);
                csr32w(ctlr, Rrfdr, x);
        }
        csr32w(ctlr, Rrfcr, Rfen|Apm|Aab|Aam);

        ether->mbps = media(ether);

        /*
         * Look for a medium override in case there's no autonegotiation
         * 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]) == 0){
                                if(x != 4 && x >= 3)
                                        ether->mbps = 100;
                                else
                                        ether->mbps = 10;
                                switch(x){
                                default:
                                        ctlr->fd = 0;
                                        break;

                                case 0x04:              /* 10BASE-TFD */
                                case 0x05:              /* 100BASE-TXFD */
                                case 0x08:              /* 100BASE-FXFD */
                                        ctlr->fd = 1;
                                        break;
                                }
                                break;
                        }
                }
        }

        /*
         * 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->promiscuous = promiscuous;
        ether->multicast = multicast;
        ether->shutdown = shutdown;
        return 0;
}

void
ether83815link(void)
{
        addethercard("83815",  reset);
}