Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * National Semiconductor DP83820
 * 10/100/1000 Mb/s Ethernet Network Interface Controller
 * (Gig-NIC).
 * Driver assumes little-endian and 32-bit host throughout.
 */
#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"
#include "ethermii.h"

enum {                                  /* Registers */
        Cr              = 0x00,         /* Command */
        Cfg             = 0x04,         /* Configuration and Media Status */
        Mear            = 0x08,         /* MII/EEPROM Access */
        Ptscr           = 0x0C,         /* PCI Test Control */
        Isr             = 0x10,         /* Interrupt Status */
        Imr             = 0x14,         /* Interrupt Mask */
        Ier             = 0x18,         /* Interrupt Enable */
        Ihr             = 0x1C,         /* Interrupt Holdoff */
        Txdp            = 0x20,         /* Transmit Descriptor Pointer */
        Txdphi          = 0x24,         /* Transmit Descriptor Pointer Hi */
        Txcfg           = 0x28,         /* Transmit Configuration */
        Gpior           = 0x2C,         /* General Purpose I/O Control */
        Rxdp            = 0x30,         /* Receive Descriptor Pointer */
        Rxdphi          = 0x34,         /* Receive Descriptor Pointer Hi */
        Rxcfg           = 0x38,         /* Receive Configuration */
        Pqcr            = 0x3C,         /* Priority Queueing Control */
        Wcsr            = 0x40,         /* Wake on LAN Control/Status */
        Pcr             = 0x44,         /* Pause Control/Status */
        Rfcr            = 0x48,         /* Receive Filter/Match Control */
        Rfdr            = 0x4C,         /* Receive Filter/Match Data */
        Brar            = 0x50,         /* Boot ROM Address */
        Brdr            = 0x54,         /* Boot ROM Data */
        Srr             = 0x58,         /* Silicon Revision */
        Mibc            = 0x5C,         /* MIB Control */
        Mibd            = 0x60,         /* MIB Data */
        Txdp1           = 0xA0,         /* Txdp Priority 1 */
        Txdp2           = 0xA4,         /* Txdp Priority 2 */
        Txdp3           = 0xA8,         /* Txdp Priority 3 */
        Rxdp1           = 0xB0,         /* Rxdp Priority 1 */
        Rxdp2           = 0xB4,         /* Rxdp Priority 2 */
        Rxdp3           = 0xB8,         /* Rxdp Priority 3 */
        Vrcr            = 0xBC,         /* VLAN/IP Receive Control */
        Vtcr            = 0xC0,         /* VLAN/IP Transmit Control */
        Vdr             = 0xC4,         /* VLAN Data */
        Ccsr            = 0xCC,         /* Clockrun Control/Status */
        Tbicr           = 0xE0,         /* TBI Control */
        Tbisr           = 0xE4,         /* TBI Status */
        Tanar           = 0xE8,         /* TBI ANAR */
        Tanlpar         = 0xEC,         /* TBI ANLPAR */
        Taner           = 0xF0,         /* TBI ANER */
        Tesr            = 0xF4,         /* TBI ESR */
};

enum {                                  /* Cr */
        Txe             = 0x00000001,   /* Transmit Enable */
        Txd             = 0x00000002,   /* Transmit Disable */
        Rxe             = 0x00000004,   /* Receiver Enable */
        Rxd             = 0x00000008,   /* Receiver Disable */
        Txr             = 0x00000010,   /* Transmitter Reset */
        Rxr             = 0x00000020,   /* Receiver Reset */
        Swien           = 0x00000080,   /* Software Interrupt Enable */
        Rst             = 0x00000100,   /* Reset */
        TxpriSHFT       = 9,            /* Tx Priority Queue Select */
        TxpriMASK       = 0x00001E00,
        RxpriSHFT       = 13,           /* Rx Priority Queue Select */
        RxpriMASK       = 0x0001E000,
};

enum {                                  /* Configuration and Media Status */
        Bem             = 0x00000001,   /* Big Endian Mode */
        Ext125          = 0x00000002,   /* External 125MHz reference Select */
        Bromdis         = 0x00000004,   /* Disable Boot ROM interface */
        Pesel           = 0x00000008,   /* Parity Error Detection Action */
        Exd             = 0x00000010,   /* Excessive Deferral Abort */
        Pow             = 0x00000020,   /* Program Out of Window Timer */
        Sb              = 0x00000040,   /* Single Back-off */
        Reqalg          = 0x00000080,   /* PCI Bus Request Algorithm */
        Extstsen        = 0x00000100,   /* Extended Status Enable */
        Phydis          = 0x00000200,   /* Disable PHY */
        Phyrst          = 0x00000400,   /* Reset PHY */
        M64addren       = 0x00000800,   /* Master 64-bit Addressing Enable */
        Data64en        = 0x00001000,   /* 64-bit Data Enable */
        Pci64det        = 0x00002000,   /* PCI 64-bit Bus Detected */
        T64addren       = 0x00004000,   /* Target 64-bit Addressing Enable */
        Mwidis          = 0x00008000,   /* MWI Disable */
        Mrmdis          = 0x00010000,   /* MRM Disable */
        Tmrtest         = 0x00020000,   /* Timer Test Mode */
        Spdstsien       = 0x00040000,   /* PHY Spdsts Interrupt Enable */
        Lnkstsien       = 0x00080000,   /* PHY Lnksts Interrupt Enable */
        Dupstsien       = 0x00100000,   /* PHY Dupsts Interrupt Enable */
        Mode1000        = 0x00400000,   /* 1000Mb/s Mode Control */
        Tbien           = 0x01000000,   /* Ten-Bit Interface Enable */
        Dupsts          = 0x10000000,   /* Full Duplex Status */
        Spdsts100       = 0x20000000,   /* SPEED100 Input Pin Status */
        Spdsts1000      = 0x40000000,   /* SPEED1000 Input Pin Status */
        Lnksts          = 0x80000000,   /* Link Status */
};

enum {                                  /* MII/EEPROM Access */
        Eedi            = 0x00000001,   /* EEPROM Data In */
        Eedo            = 0x00000002,   /* EEPROM Data Out */
        Eeclk           = 0x00000004,   /* EEPROM Serial Clock */
        Eesel           = 0x00000008,   /* EEPROM Chip Select */
        Mdio            = 0x00000010,   /* MII Management Data */
        Mddir           = 0x00000020,   /* MII Management Direction */
        Mdc             = 0x00000040,   /* MII Management Clock */
};

enum {                                  /* Interrupts */
        Rxok            = 0x00000001,   /* Rx OK */
        Rxdesc          = 0x00000002,   /* Rx Descriptor */
        Rxerr           = 0x00000004,   /* Rx Packet Error */
        Rxearly         = 0x00000008,   /* Rx Early Threshold */
        Rxidle          = 0x00000010,   /* Rx Idle */
        Rxorn           = 0x00000020,   /* Rx Overrun */
        Txok            = 0x00000040,   /* Tx Packet OK */
        Txdesc          = 0x00000080,   /* Tx Descriptor */
        Txerr           = 0x00000100,   /* Tx Packet Error */
        Txidle          = 0x00000200,   /* Tx Idle */
        Txurn           = 0x00000400,   /* Tx Underrun */
        Mib             = 0x00000800,   /* MIB Service */
        Swi             = 0x00001000,   /* Software Interrupt */
        Pme             = 0x00002000,   /* Power Management Event */
        Phy             = 0x00004000,   /* PHY Interrupt */
        Hibint          = 0x00008000,   /* High Bits Interrupt Set */
        Rxsovr          = 0x00010000,   /* Rx Status FIFO Overrun */
        Rtabt           = 0x00020000,   /* Received Target Abort */
        Rmabt           = 0x00040000,   /* Received Master Abort */
        Sserr           = 0x00080000,   /* Signalled System Error */
        Dperr           = 0x00100000,   /* Detected Parity Error */
        Rxrcmp          = 0x00200000,   /* Receive Reset Complete */
        Txrcmp          = 0x00400000,   /* Transmit Reset Complete */
        Rxdesc0         = 0x00800000,   /* Rx Descriptor for Priority Queue 0 */
        Rxdesc1         = 0x01000000,   /* Rx Descriptor for Priority Queue 1 */
        Rxdesc2         = 0x02000000,   /* Rx Descriptor for Priority Queue 2 */
        Rxdesc3         = 0x04000000,   /* Rx Descriptor for Priority Queue 3 */
        Txdesc0         = 0x08000000,   /* Tx Descriptor for Priority Queue 0 */
        Txdesc1         = 0x10000000,   /* Tx Descriptor for Priority Queue 1 */
        Txdesc2         = 0x20000000,   /* Tx Descriptor for Priority Queue 2 */
        Txdesc3         = 0x40000000,   /* Tx Descriptor for Priority Queue 3 */
};

enum {                                  /* Interrupt Enable */
        Ien             = 0x00000001,   /* Interrupt Enable */
};

enum {                                  /* Interrupt Holdoff */
        IhSHFT          = 0,            /* Interrupt Holdoff */
        IhMASK          = 0x000000FF,
        Ihctl           = 0x00000100,   /* Interrupt Holdoff Control */
};

enum {                                  /* Transmit Configuration */
        TxdrthSHFT      = 0,            /* Tx Drain Threshold */
        TxdrthMASK      = 0x000000FF,
        FlthSHFT        = 16,           /* Tx Fill Threshold */
        FlthMASK        = 0x0000FF00,
        Brstdis         = 0x00080000,   /* 1000Mb/s Burst Disable */
        MxdmaSHFT       = 20,           /* Max Size per Tx DMA Burst */
        MxdmaMASK       = 0x00700000,
        Ecretryen       = 0x00800000,   /* Excessive Collision Retry Enable */
        Atp             = 0x10000000,   /* Automatic Transmit Padding */
        Mlb             = 0x20000000,   /* MAC Loopback */
        Hbi             = 0x40000000,   /* Heartbeat Ignore */
        Csi             = 0x80000000,   /* Carrier Sense Ignore */
};

enum {                                  /* Receive Configuration */
        RxdrthSHFT      = 1,            /* Rx Drain Threshold */
        RxdrthMASK      = 0x0000003E,
        Airl            = 0x04000000,   /* Accept In-Range Length Errored */
        Alp             = 0x08000000,   /* Accept Long Packets */
        Rxfd            = 0x10000000,   /* Receive Full Duplex */
        Stripcrc        = 0x20000000,   /* Strip CRC */
        Arp             = 0x40000000,   /* Accept Runt Packets */
        Aep             = 0x80000000,   /* Accept Errored Packets */
};

enum {                                  /* Priority Queueing Control */
        Txpqen          = 0x00000001,   /* Transmit Priority Queuing Enable */
        Txfairen        = 0x00000002,   /* Transmit Fairness Enable */
        RxpqenSHFT      = 2,            /* Receive Priority Queue Enable */
        RxpqenMASK      = 0x0000000C,
};

enum {                                  /* Pause Control/Status */
        PscntSHFT       = 0,            /* Pause Counter Value */
        PscntMASK       = 0x0000FFFF,
        Pstx            = 0x00020000,   /* Transmit Pause Frame */
        PsffloSHFT      = 18,           /* Rx Data FIFO Lo Threshold */
        PsffloMASK      = 0x000C0000,
        PsffhiSHFT      = 20,           /* Rx Data FIFO Hi Threshold */
        PsffhiMASK      = 0x00300000,
        PsstloSHFT      = 22,           /* Rx Stat FIFO Hi Threshold */
        PsstloMASK      = 0x00C00000,
        PssthiSHFT      = 24,           /* Rx Stat FIFO Hi Threshold */
        PssthiMASK      = 0x03000000,
        Psrcvd          = 0x08000000,   /* Pause Frame Received */
        Psact           = 0x10000000,   /* Pause Active */
        Psda            = 0x20000000,   /* Pause on Destination Address */
        Psmcast         = 0x40000000,   /* Pause on Multicast */
        Psen            = 0x80000000,   /* Pause Enable */
};

enum {                                  /* Receive Filter/Match Control */
        RfaddrSHFT      = 0,            /* Extended Register Address */
        RfaddrMASK      = 0x000003FF,
        Ulm             = 0x00080000,   /* U/L bit mask */
        Uhen            = 0x00100000,   /* Unicast Hash Enable */
        Mhen            = 0x00200000,   /* Multicast Hash Enable */
        Aarp            = 0x00400000,   /* Accept ARP Packets */
        ApatSHFT        = 23,           /* Accept on Pattern Match */
        ApatMASK        = 0x07800000,
        Apm             = 0x08000000,   /* Accept on Perfect Match */
        Aau             = 0x10000000,   /* Accept All Unicast */
        Aam             = 0x20000000,   /* Accept All Multicast */
        Aab             = 0x40000000,   /* Accept All Broadcast */
        Rfen            = 0x80000000,   /* Rx Filter Enable */
};

enum {                                  /* Receive Filter/Match Data */
        RfdataSHFT      = 0,            /* Receive Filter Data */
        RfdataMASK      = 0x0000FFFF,
        BmaskSHFT       = 16,           /* Byte Mask */
        BmaskMASK       = 0x00030000,
};

enum {                                  /* MIB Control */
        Wrn             = 0x00000001,   /* Warning Test Indicator */
        Frz             = 0x00000002,   /* Freeze All Counters */
        Aclr            = 0x00000004,   /* Clear All Counters */
        Mibs            = 0x00000008,   /* MIB Counter Strobe */
};

enum {                                  /* MIB Data */
        Nmibd           = 11,           /* Number of MIB Data Registers */
};

enum {                                  /* VLAN/IP Receive Control */
        Vtden           = 0x00000001,   /* VLAN Tag Detection Enable */
        Vtren           = 0x00000002,   /* VLAN Tag Removal Enable */
        Dvtf            = 0x00000004,   /* Discard VLAN Tagged Frames */
        Dutf            = 0x00000008,   /* Discard Untagged Frames */
        Ipen            = 0x00000010,   /* IP Checksum Enable */
        Ripe            = 0x00000020,   /* Reject IP Checksum Errors */
        Rtcpe           = 0x00000040,   /* Reject TCP Checksum Errors */
        Rudpe           = 0x00000080,   /* Reject UDP Checksum Errors */
};

enum {                                  /* VLAN/IP Transmit Control */
        Vgti            = 0x00000001,   /* VLAN Global Tag Insertion */
        Vppti           = 0x00000002,   /* VLAN Per-Packet Tag Insertion */
        Gchk            = 0x00000004,   /* Global Checksum Generation */
        Ppchk           = 0x00000008,   /* Per-Packet Checksum Generation */
};

enum {                                  /* VLAN Data */
        VtypeSHFT       = 0,            /* VLAN Type Field */
        VtypeMASK       = 0x0000FFFF,
        VtciSHFT        = 16,           /* VLAN Tag Control Information */
        VtciMASK        = 0xFFFF0000,
};

enum {                                  /* Clockrun Control/Status */
        Clkrunen        = 0x00000001,   /* CLKRUN Enable */
        Pmeen           = 0x00000100,   /* PME Enable */
        Pmests          = 0x00008000,   /* PME Status */
};

typedef struct {
        u32int  link;                   /* Link to the next descriptor */
        u32int  bufptr;                 /* pointer to data Buffer */
        int     cmdsts;                 /* Command/Status */
        int     extsts;                 /* optional Extended Status */

        Block*  bp;                     /* Block containing bufptr */
        u32int  unused;                 /* pad to 64-bit */
} Desc;

enum {                                  /* Common cmdsts bits */
        SizeMASK        = 0x0000FFFF,   /* Descriptor Byte Count */
        SizeSHFT        = 0,
        Ok              = 0x08000000,   /* Packet OK */
        Crc             = 0x10000000,   /* Suppress/Include CRC */
        Intr            = 0x20000000,   /* Interrupt on ownership transfer */
        More            = 0x40000000,   /* not last descriptor in a packet */
        Own             = 0x80000000,   /* Descriptor Ownership */
};

enum {                                  /* Transmit cmdsts bits */
        CcntMASK        = 0x000F0000,   /* Collision Count */
        CcntSHFT        = 16,
        Ec              = 0x00100000,   /* Excessive Collisions */
        Owc             = 0x00200000,   /* Out of Window Collision */
        Ed              = 0x00400000,   /* Excessive Deferral */
        Td              = 0x00800000,   /* Transmit Deferred */
        Crs             = 0x01000000,   /* Carrier Sense Lost */
        Tfu             = 0x02000000,   /* Transmit FIFO Underrun */
        Txa             = 0x04000000,   /* Transmit Abort */
};

enum {                                  /* Receive cmdsts bits */
        Irl             = 0x00010000,   /* In-Range Length Error */
        Lbp             = 0x00020000,   /* Loopback Packet */
        Fae             = 0x00040000,   /* Frame Alignment Error */
        Crce            = 0x00080000,   /* CRC Error */
        Ise             = 0x00100000,   /* Invalid Symbol Error */
        Runt            = 0x00200000,   /* Runt Packet Received */
        Long            = 0x00400000,   /* Too Long Packet Received */
        DestMASK        = 0x01800000,   /* Destination Class */
        DestSHFT        = 23,
        Rxo             = 0x02000000,   /* Receive Overrun */
        Rxa             = 0x04000000,   /* Receive Aborted */
};

enum {                                  /* extsts bits */
        EvtciMASK       = 0x0000FFFF,   /* VLAN Tag Control Information */
        EvtciSHFT       = 0,
        Vpkt            = 0x00010000,   /* VLAN Packet */
        Ippkt           = 0x00020000,   /* IP Packet */
        Iperr           = 0x00040000,   /* IP Checksum Error */
        Tcppkt          = 0x00080000,   /* TCP Packet */
        Tcperr          = 0x00100000,   /* TCP Checksum Error */
        Udppkt          = 0x00200000,   /* UDP Packet */
        Udperr          = 0x00400000,   /* UDP Checksum Error */
};

enum {
        Rbsz            = ROUNDUP(sizeof(Etherpkt)+8, 8),
        /* were 256, 4*Nrd & 64, but 52, 253 and 9 are ample. */
        Nrd             = 128,
        Nrb             = 512,
        Ntd             = 32,
};

typedef struct Ctlr Ctlr;
typedef struct Ctlr {
        int     port;
        Pcidev* pcidev;
        Ctlr*   next;
        int     active;
        int     id;

        int     eepromsz;               /* address size in bits */
        ushort* eeprom;

        int*    nic;
        int     cfg;
        int     imr;

        QLock   alock;                  /* attach */
        Lock    ilock;                  /* init */
        void*   alloc;                  /* base of per-Ctlr allocated data */

        Mii*    mii;

        Lock    rdlock;                 /* receive */
        Desc*   rd;
        int     nrd;
        int     nrb;
        int     rdx;
        int     rxcfg;

        Lock    tlock;                  /* transmit */
        Desc*   td;
        int     ntd;
        int     tdh;
        int     tdt;
        int     ntq;
        int     txcfg;

        int     rxidle;

        uint    mibd[Nmibd];

        int     ec;
        int     owc;
        int     ed;
        int     crs;
        int     tfu;
        int     txa;
} Ctlr;

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

static Ctlr* dp83820ctlrhead;
static Ctlr* dp83820ctlrtail;

static Lock dp83820rblock;              /* free receive Blocks */
static Block* dp83820rbpool;

static char* dp83820mibs[Nmibd] = {
        "RXErroredPkts",
        "RXFCSErrors",
        "RXMsdPktErrors",
        "RXFAErrors",
        "RXSymbolErrors",
        "RXFrameToLong",
        "RXIRLErrors",
        "RXBadOpcodes",
        "RXPauseFrames",
        "TXPauseFrames",
        "TXSQEErrors",
};

static int
mdior(Ctlr* ctlr, int n)
{
        int data, i, mear, r;

        mear = csr32r(ctlr, Mear);
        r = ~(Mdc|Mddir) & mear;
        data = 0;
        for(i = n-1; i >= 0; i--){
                if(csr32r(ctlr, Mear) & Mdio)
                        data |= (1<<i);
                csr32w(ctlr, Mear, Mdc|r);
                csr32w(ctlr, Mear, r);
        }
        csr32w(ctlr, Mear, mear);

        return data;
}

static void
mdiow(Ctlr* ctlr, int bits, int n)
{
        int i, mear, r;

        mear = csr32r(ctlr, Mear);
        r = Mddir|(~Mdc & mear);
        for(i = n-1; i >= 0; i--){
                if(bits & (1<<i))
                        r |= Mdio;
                else
                        r &= ~Mdio;
                csr32w(ctlr, Mear, r);
                csr32w(ctlr, Mear, Mdc|r);
        }
        csr32w(ctlr, Mear, mear);
}

static int
dp83820miimir(Mii* mii, int pa, int ra)
{
        int data;
        Ctlr *ctlr;

        ctlr = mii->ctlr;

        /*
         * MII Management Interface Read.
         *
         * Preamble;
         * ST+OP+PA+RA;
         * LT + 16 data bits.
         */
        mdiow(ctlr, 0xFFFFFFFF, 32);
        mdiow(ctlr, 0x1800|(pa<<5)|ra, 14);
        data = mdior(ctlr, 18);

        if(data & 0x10000)
                return -1;

        return data & 0xFFFF;
}

static int
dp83820miimiw(Mii* mii, int pa, int ra, int data)
{
        Ctlr *ctlr;

        ctlr = mii->ctlr;

        /*
         * MII Management Interface Write.
         *
         * Preamble;
         * ST+OP+PA+RA+LT + 16 data bits;
         * Z.
         */
        mdiow(ctlr, 0xFFFFFFFF, 32);
        data &= 0xFFFF;
        data |= (0x05<<(5+5+2+16))|(pa<<(5+2+16))|(ra<<(2+16))|(0x02<<16);
        mdiow(ctlr, data, 32);

        return 0;
}

static Block *
dp83820rballoc(Desc* desc)
{
        Block *bp;

        if(desc->bp == nil){
                ilock(&dp83820rblock);
                if((bp = dp83820rbpool) == nil){
                        iunlock(&dp83820rblock);
                        desc->bp = nil;
                        desc->cmdsts = Own;
                        return nil;
                }
                dp83820rbpool = bp->next;
                bp->next = nil;
                _xinc(&bp->ref);        /* prevent bp from being freed */
                iunlock(&dp83820rblock);
        
                desc->bufptr = PCIWADDR(bp->rp);
                desc->bp = bp;
        }
        else{
                bp = desc->bp;
                bp->rp = bp->lim - Rbsz;
                bp->wp = bp->rp;
        }

        coherence();
        desc->cmdsts = Intr|Rbsz;

        return bp;
}

static void
dp83820rbfree(Block *bp)
{
        bp->rp = bp->lim - Rbsz;
        bp->wp = bp->rp;

        ilock(&dp83820rblock);
        bp->next = dp83820rbpool;
        dp83820rbpool = bp;
        iunlock(&dp83820rblock);
}

static void
dp83820halt(Ctlr* ctlr)
{
        int i, timeo;

        ilock(&ctlr->ilock);
        csr32w(ctlr, Imr, 0);
        csr32w(ctlr, Ier, 0);
        csr32w(ctlr, Cr, Rxd|Txd);
        for(timeo = 0; timeo < 1000; timeo++){
                if(!(csr32r(ctlr, Cr) & (Rxe|Txe)))
                        break;
                microdelay(1);
        }
        csr32w(ctlr, Mibc, Frz);
        iunlock(&ctlr->ilock);

        if(ctlr->rd != nil){
                for(i = 0; i < ctlr->nrd; i++){
                        if(ctlr->rd[i].bp == nil)
                                continue;
                        freeb(ctlr->rd[i].bp);
                        ctlr->rd[i].bp = nil;
                }
        }
        if(ctlr->td != nil){
                for(i = 0; i < ctlr->ntd; i++){
                        if(ctlr->td[i].bp == nil)
                                continue;
                        freeb(ctlr->td[i].bp);
                        ctlr->td[i].bp = nil;
                }
        }
}

static void
dp83820cfg(Ctlr* ctlr)
{
        int cfg;

        /*
         * Don't know how to deal with a TBI yet.
         */
        if(ctlr->mii == nil)
                return;

        /*
         * The polarity of these bits is at the mercy
         * of the board designer.
         * The correct answer for all speed and duplex questions
         * should be to query the phy.
         */
        cfg = csr32r(ctlr, Cfg);
        if(!(cfg & Dupsts)){
                ctlr->rxcfg |= Rxfd;
                ctlr->txcfg |= Csi|Hbi;
                iprint("83820: full duplex, ");
        }
        else{
                ctlr->rxcfg &= ~Rxfd;
                ctlr->txcfg &= ~(Csi|Hbi);
                iprint("83820: half duplex, ");
        }
        csr32w(ctlr, Rxcfg, ctlr->rxcfg);
        csr32w(ctlr, Txcfg, ctlr->txcfg);

        switch(cfg & (Spdsts1000|Spdsts100)){
        case Spdsts1000:                /* 100Mbps */
        default:                        /* 10Mbps */
                ctlr->cfg &= ~Mode1000;
                if((cfg & (Spdsts1000|Spdsts100)) == Spdsts1000)
                        iprint("100Mb/s\n");
                else
                        iprint("10Mb/s\n");
                break;
        case Spdsts100:                 /* 1Gbps */
                ctlr->cfg |= Mode1000;
                iprint("1Gb/s\n");
                break;
        }
        csr32w(ctlr, Cfg, ctlr->cfg);
}

static void
dp83820init(Ether* edev)
{
        int i;
        Ctlr *ctlr;
        Desc *desc;
        uchar *alloc;

        ctlr = edev->ctlr;

        dp83820halt(ctlr);

        /*
         * Receiver
         */
        alloc = (uchar*)ROUNDUP((ulong)ctlr->alloc, 8);
        ctlr->rd = (Desc*)alloc;
        alloc += ctlr->nrd*sizeof(Desc);
        memset(ctlr->rd, 0, ctlr->nrd*sizeof(Desc));
        ctlr->rdx = 0;
        for(i = 0; i < ctlr->nrd; i++){
                desc = &ctlr->rd[i];
                desc->link = PCIWADDR(&ctlr->rd[NEXT(i, ctlr->nrd)]);
                if(dp83820rballoc(desc) == nil)
                        continue;
        }
        csr32w(ctlr, Rxdphi, 0);
        csr32w(ctlr, Rxdp, PCIWADDR(ctlr->rd));

        for(i = 0; i < Eaddrlen; i += 2){
                csr32w(ctlr, Rfcr, i);
                csr32w(ctlr, Rfdr, (edev->ea[i+1]<<8)|edev->ea[i]);
        }
        csr32w(ctlr, Rfcr, Rfen|Aab|Aam|Apm);

        ctlr->rxcfg = Stripcrc|(((2*(ETHERMINTU+4))/8)<<RxdrthSHFT);
        ctlr->imr |= Rxorn|Rxidle|Rxearly|Rxdesc|Rxok;

        /*
         * Transmitter.
         */
        ctlr->td = (Desc*)alloc;
        memset(ctlr->td, 0, ctlr->ntd*sizeof(Desc));
        ctlr->tdh = ctlr->tdt = ctlr->ntq = 0;
        for(i = 0; i < ctlr->ntd; i++){
                desc = &ctlr->td[i];
                desc->link = PCIWADDR(&ctlr->td[NEXT(i, ctlr->ntd)]);
        }
        csr32w(ctlr, Txdphi, 0);
        csr32w(ctlr, Txdp, PCIWADDR(ctlr->td));

        ctlr->txcfg = Atp|(((2*(ETHERMINTU+4))/32)<<FlthSHFT)|((4096/32)<<TxdrthSHFT);
        ctlr->imr |= Txurn|Txidle|Txdesc|Txok;

        ilock(&ctlr->ilock);

        dp83820cfg(ctlr);

        csr32w(ctlr, Mibc, Aclr);
        ctlr->imr |= Mib;

        csr32w(ctlr, Imr, ctlr->imr);

        /* try coalescing adjacent interrupts; use hold-off interval of 100µs */
        csr32w(ctlr, Ihr, Ihctl|(1<<IhSHFT));

        csr32w(ctlr, Ier, Ien);
        csr32w(ctlr, Cr, Rxe|Txe);

        iunlock(&ctlr->ilock);
}

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

        ctlr = edev->ctlr;
        qlock(&ctlr->alock);
        if(ctlr->alloc != nil){
                qunlock(&ctlr->alock);
                return;
        }

        if(waserror()){
                if(ctlr->mii != nil){
                        free(ctlr->mii);
                        ctlr->mii = nil;
                }
                if(ctlr->alloc != nil){
                        free(ctlr->alloc);
                        ctlr->alloc = nil;
                }
                qunlock(&ctlr->alock);
                nexterror();
        }

        if(!(ctlr->cfg & Tbien)){
                if((ctlr->mii = malloc(sizeof(Mii))) == nil)
                        error(Enomem);
                ctlr->mii->ctlr = ctlr;
                ctlr->mii->mir = dp83820miimir;
                ctlr->mii->miw = dp83820miimiw;
                if(mii(ctlr->mii, ~0) == 0)
                        error("no PHY");
                ctlr->cfg |= Dupstsien|Lnkstsien|Spdstsien;
                ctlr->imr |= Phy;
        }

        ctlr->nrd = Nrd;
        ctlr->nrb = Nrb;
        ctlr->ntd = Ntd;
        ctlr->alloc = mallocz((ctlr->nrd+ctlr->ntd)*sizeof(Desc) + 7, 0);
        if(ctlr->alloc == nil)
                error(Enomem);

        for(ctlr->nrb = 0; ctlr->nrb < Nrb; ctlr->nrb++){
                if((bp = allocb(Rbsz)) == nil)
                        break;
                bp->free = dp83820rbfree;
                dp83820rbfree(bp);
        }

        dp83820init(edev);

        qunlock(&ctlr->alock);
        poperror();
}

static void
dp83820transmit(Ether* edev)
{
        Block *bp;
        Ctlr *ctlr;
        Desc *desc;
        int cmdsts, r, x;

        ctlr = edev->ctlr;

        ilock(&ctlr->tlock);

        bp = nil;
        for(x = ctlr->tdh; ctlr->ntq; x = NEXT(x, ctlr->ntd)){
                desc = &ctlr->td[x];
                if((cmdsts = desc->cmdsts) & Own)
                        break;
                if(!(cmdsts & Ok)){
                        if(cmdsts & Ec)
                                ctlr->ec++;
                        if(cmdsts & Owc)
                                ctlr->owc++;
                        if(cmdsts & Ed)
                                ctlr->ed++;
                        if(cmdsts & Crs)
                                ctlr->crs++;
                        if(cmdsts & Tfu)
                                ctlr->tfu++;
                        if(cmdsts & Txa)
                                ctlr->txa++;
                        edev->oerrs++;
                }
                desc->bp->next = bp;
                bp = desc->bp;
                desc->bp = nil;

                ctlr->ntq--;
        }
        ctlr->tdh = x;
        if(bp != nil)
                freeblist(bp);

        x = ctlr->tdt;
        while(ctlr->ntq < (ctlr->ntd-1)){
                if((bp = qget(edev->oq)) == nil)
                        break;

                desc = &ctlr->td[x];
                desc->bufptr = PCIWADDR(bp->rp);
                desc->bp = bp;
                ctlr->ntq++;
                coherence();
                desc->cmdsts = Own|Intr|BLEN(bp);

                x = NEXT(x, ctlr->ntd);
        }
        if(x != ctlr->tdt){
                ctlr->tdt = x;
                r = csr32r(ctlr, Cr);
                csr32w(ctlr, Cr, Txe|r);
        }

        iunlock(&ctlr->tlock);
}

static void
dp83820interrupt(Ureg*, void* arg)
{
        Block *bp;
        Ctlr *ctlr;
        Desc *desc;
        Ether *edev;
        int cmdsts, i, isr, r, x;

        edev = arg;
        ctlr = edev->ctlr;

        for(isr = csr32r(ctlr, Isr); isr & ctlr->imr; isr = csr32r(ctlr, Isr)){
                if(isr & (Rxorn|Rxidle|Rxearly|Rxerr|Rxdesc|Rxok)){
                        x = ctlr->rdx;
                        desc = &ctlr->rd[x];
                        while((cmdsts = desc->cmdsts) & Own){
                                if((cmdsts & Ok) && desc->bp != nil){
                                        bp = desc->bp;
                                        desc->bp = nil;
                                        bp->wp += cmdsts & SizeMASK;
                                        etheriq(edev, bp, 1);
                                }
                                else if(0 && !(cmdsts & Ok)){
                                        iprint("dp83820: rx %8.8uX:", cmdsts);
                                        bp = desc->bp;
                                        for(i = 0; i < 20; i++)
                                                iprint(" %2.2uX", bp->rp[i]);
                                        iprint("\n");
                                }
                                dp83820rballoc(desc);

                                x = NEXT(x, ctlr->nrd);
                                desc = &ctlr->rd[x];
                        }
                        ctlr->rdx = x;

                        if(isr & Rxidle){
                                r = csr32r(ctlr, Cr);
                                csr32w(ctlr, Cr, Rxe|r);
                                ctlr->rxidle++;
                        }

                        isr &= ~(Rxorn|Rxidle|Rxearly|Rxerr|Rxdesc|Rxok);
                }

                if(isr & Txurn){
                        x = (ctlr->txcfg & TxdrthMASK)>>TxdrthSHFT;
                        r = (ctlr->txcfg & FlthMASK)>>FlthSHFT;
                        if(x < ((TxdrthMASK)>>TxdrthSHFT)
                        && x < (2048/32 - r)){
                                ctlr->txcfg &= ~TxdrthMASK;
                                x++;
                                ctlr->txcfg |= x<<TxdrthSHFT;
                                csr32w(ctlr, Txcfg, ctlr->txcfg);
                        }
                }

                if(isr & (Txurn|Txidle|Txdesc|Txok)){
                        dp83820transmit(edev);
                        isr &= ~(Txurn|Txidle|Txdesc|Txok);
                }

                if(isr & Mib){
                        for(i = 0; i < Nmibd; i++){
                                r = csr32r(ctlr, Mibd+(i*sizeof(int)));
                                ctlr->mibd[i] += r & 0xFFFF;
                        }
                        isr &= ~Mib;
                }

                if((isr & Phy) && ctlr->mii != nil){
                        ctlr->mii->mir(ctlr->mii, 1, Bmsr);
                        print("phy: cfg %8.8uX bmsr %4.4uX\n",
                                csr32r(ctlr, Cfg),
                                ctlr->mii->mir(ctlr->mii, 1, Bmsr));
                        dp83820cfg(ctlr);
                        isr &= ~Phy;
                }
                if(isr)
                        iprint("dp83820: isr %8.8uX\n", isr);
        }
}

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

        ctlr = edev->ctlr;

        edev->crcs = ctlr->mibd[Mibd+(1*sizeof(int))];
        edev->frames = ctlr->mibd[Mibd+(3*sizeof(int))];
        edev->buffs = ctlr->mibd[Mibd+(5*sizeof(int))];
        edev->overflows = ctlr->mibd[Mibd+(2*sizeof(int))];

        if(n == 0)
                return 0;

        p = malloc(READSTR);
        if(p == nil)
                error(Enomem);
        l = 0;
        for(i = 0; i < Nmibd; i++){
                r = csr32r(ctlr, Mibd+(i*sizeof(int)));
                ctlr->mibd[i] += r & 0xFFFF;
                if(ctlr->mibd[i] != 0 && dp83820mibs[i] != nil)
                        l += snprint(p+l, READSTR-l, "%s: %ud %ud\n",
                                dp83820mibs[i], ctlr->mibd[i], r);
        }
        l += snprint(p+l, READSTR-l, "rxidle %d\n", ctlr->rxidle);
        l += snprint(p+l, READSTR-l, "ec %d\n", ctlr->ec);
        l += snprint(p+l, READSTR-l, "owc %d\n", ctlr->owc);
        l += snprint(p+l, READSTR-l, "ed %d\n", ctlr->ed);
        l += snprint(p+l, READSTR-l, "crs %d\n", ctlr->crs);
        l += snprint(p+l, READSTR-l, "tfu %d\n", ctlr->tfu);
        l += snprint(p+l, READSTR-l, "txa %d\n", ctlr->txa);

        l += snprint(p+l, READSTR, "rom:");
        for(i = 0; i < 0x10; 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");

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

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

        return n;
}

static void
dp83820promiscuous(void* arg, int on)
{
        USED(arg, on);
}

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

static int
dp83820detach(Ctlr* ctlr)
{
        /*
         * Soft reset the controller.
         */
        csr32w(ctlr, Cr, Rst);
        delay(1);
        while(csr32r(ctlr, Cr) & Rst)
                delay(1);
        return 0;
}

static void
dp83820shutdown(Ether* ether)
{
print("dp83820shutdown\n");
        dp83820detach(ether->ctlr);
}

static int
atc93c46r(Ctlr* ctlr, int address)
{
        int data, i, mear, r, size;

        /*
         * Analog Technology, Inc. ATC93C46
         * or equivalent serial EEPROM.
         */
        mear = csr32r(ctlr, Mear);
        mear &= ~(Eesel|Eeclk|Eedo|Eedi);
        r = Eesel|mear;

reread:
        csr32w(ctlr, Mear, r);
        data = 0x06;
        for(i = 3-1; i >= 0; i--){
                if(data & (1<<i))
                        r |= Eedi;
                else
                        r &= ~Eedi;
                csr32w(ctlr, Mear, r);
                csr32w(ctlr, Mear, Eeclk|r);
                microdelay(1);
                csr32w(ctlr, Mear, r);
                microdelay(1);
        }

        /*
         * First time through must work out the EEPROM size.
         */
        if((size = ctlr->eepromsz) == 0)
                size = 8;

        for(size = size-1; size >= 0; size--){
                if(address & (1<<size))
                        r |= Eedi;
                else
                        r &= ~Eedi;
                csr32w(ctlr, Mear, r);
                microdelay(1);
                csr32w(ctlr, Mear, Eeclk|r);
                microdelay(1);
                csr32w(ctlr, Mear, r);
                microdelay(1);
                if(!(csr32r(ctlr, Mear) & Eedo))
                        break;
        }
        r &= ~Eedi;

        data = 0;
        for(i = 16-1; i >= 0; i--){
                csr32w(ctlr, Mear, Eeclk|r);
                microdelay(1);
                if(csr32r(ctlr, Mear) & Eedo)
                        data |= (1<<i);
                csr32w(ctlr, Mear, r);
                microdelay(1);
        }

        csr32w(ctlr, Mear, mear);

        if(ctlr->eepromsz == 0){
                ctlr->eepromsz = 8-size;
                ctlr->eeprom = malloc((1<<ctlr->eepromsz)*sizeof(ushort));
                if(ctlr->eeprom == nil)
                        error(Enomem);
                goto reread;
        }

        return data;
}

static int
dp83820reset(Ctlr* ctlr)
{
        int i, r;
        unsigned char sum;

        /*
         * Soft reset the controller;
         * read the EEPROM to get the initial settings
         * of the Cfg and Gpior bits which should be cleared by
         * the reset.
         */
        dp83820detach(ctlr);

        atc93c46r(ctlr, 0);
        if(ctlr->eeprom == nil) {
                print("dp83820reset: no eeprom\n");
                return -1;
        }
        sum = 0;
        for(i = 0; i < 0x0E; i++){
                r = atc93c46r(ctlr, i);
                ctlr->eeprom[i] = r;
                sum += r;
                sum += r>>8;
        }

        if(sum != 0){
                print("dp83820reset: bad EEPROM checksum\n");
                return -1;
        }

#ifdef notdef
        csr32w(ctlr, Gpior, ctlr->eeprom[4]);

        cfg = Extstsen|Exd;
        r = csr32r(ctlr, Cfg);
        if(ctlr->eeprom[5] & 0x0001)
                cfg |= Ext125;
        if(ctlr->eeprom[5] & 0x0002)
                cfg |= M64addren;
        if((ctlr->eeprom[5] & 0x0004) && (r & Pci64det))
                cfg |= Data64en;
        if(ctlr->eeprom[5] & 0x0008)
                cfg |= T64addren;
        if(!(pcicfgr16(ctlr->pcidev, PciPCR) & 0x10))
                cfg |= Mwidis;
        if(ctlr->eeprom[5] & 0x0020)
                cfg |= Mrmdis;
        if(ctlr->eeprom[5] & 0x0080)
                cfg |= Mode1000;
        if(ctlr->eeprom[5] & 0x0200)
                cfg |= Tbien|Mode1000;
        /*
         * What about RO bits we might have destroyed with Rst?
         * What about Exd, Tmrtest, Extstsen, Pintctl?
         * Why does it think it has detected a 64-bit bus when
         * it hasn't?
         */
#else
        // r = csr32r(ctlr, Cfg);
        // r &= ~(Mode1000|T64addren|Data64en|M64addren);
        // csr32w(ctlr, Cfg, r);
        // csr32w(ctlr, Cfg, 0x2000);
#endif                                          /* notdef */
        ctlr->cfg = csr32r(ctlr, Cfg);
print("cfg %8.8uX pcicfg %8.8uX\n", ctlr->cfg, pcicfgr32(ctlr->pcidev, PciPCR));
        ctlr->cfg &= ~(T64addren|Data64en|M64addren);
        csr32w(ctlr, Cfg, ctlr->cfg);
        csr32w(ctlr, Mibc, Aclr|Frz);

        return 0;
}

static void
dp83820pci(void)
{
        void *mem;
        Pcidev *p;
        Ctlr *ctlr;

        p = nil;
        while(p = pcimatch(p, 0, 0)){
                if(p->ccrb != Pcibcnet || p->ccru != Pciscether)
                        continue;

                switch((p->did<<16)|p->vid){
                default:
                        continue;
                case (0x0022<<16)|0x100B:       /* DP83820 (Gig-NIC) */
                        break;
                }

                mem = vmap(p->mem[1].bar & ~0x0F, p->mem[1].size);
                if(mem == 0){
                        print("DP83820: can't map %8.8luX\n", p->mem[1].bar);
                        continue;
                }

                ctlr = malloc(sizeof(Ctlr));
                if(ctlr == nil) {
                        vunmap(mem, p->mem[1].size);
                        error(Enomem);
                }
                ctlr->port = p->mem[1].bar & ~0x0F;
                ctlr->pcidev = p;
                ctlr->id = (p->did<<16)|p->vid;

                ctlr->nic = mem;
                if(dp83820reset(ctlr)){
                        free(ctlr);
                        vunmap(mem, p->mem[1].size);
                        continue;
                }
                pcisetbme(p);

                if(dp83820ctlrhead != nil)
                        dp83820ctlrtail->next = ctlr;
                else
                        dp83820ctlrhead = ctlr;
                dp83820ctlrtail = ctlr;
        }
}

static int
dp83820pnp(Ether* edev)
{
        int i;
        Ctlr *ctlr;
        uchar ea[Eaddrlen];

        if(dp83820ctlrhead == nil)
                dp83820pci();

        /*
         * Any adapter matches if no edev->port is supplied,
         * otherwise the ports must match.
         */
        for(ctlr = dp83820ctlrhead; 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;
        edev->port = ctlr->port;
        edev->irq = ctlr->pcidev->intl;
        edev->tbdf = ctlr->pcidev->tbdf;
        edev->mbps = 1000;

        /*
         * 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, edev->ea, Eaddrlen) == 0)
                for(i = 0; i < Eaddrlen/2; i++){
                        edev->ea[2*i] = ctlr->eeprom[0x0C-i];
                        edev->ea[2*i+1] = ctlr->eeprom[0x0C-i]>>8;
                }

        edev->attach = dp83820attach;
        edev->transmit = dp83820transmit;
        edev->interrupt = dp83820interrupt;
        edev->ifstat = dp83820ifstat;

        edev->arg = edev;
        edev->promiscuous = dp83820promiscuous;
        edev->multicast = dp83820multicast;
        edev->shutdown = dp83820shutdown;

        return 0;
}

void
etherdp83820link(void)
{
        addethercard("DP83820", dp83820pnp);
}