plan9fox/sys/src/9/pc/etherdp83820.c
cinap_lenrek 4f85115526 kernel: massive pci code rewrite
The new pci code is moved to port/pci.[hc] and shared by
all ports.

Each port has its own PCI controller implementation,
providing the pcicfgrw*() functions for low level pci
config space access. The locking for pcicfgrw*() is now
done by the caller (only port/pci.c).

Device drivers now need to include "../port/pci.h" in
addition to "io.h".

The new code now checks bridge windows and membars,
while enumerating the bus, giving the pc driver a chance
to re-assign them. This is needed because some UEFI
implementations fail to assign the bars for some devices,
so we need to do it outselfs. (See pcireservemem()).

While working on this, it was discovered that the pci
code assimed the smallest I/O bar size is 16 (pcibarsize()),
which is wrong. I/O bars can be as small as 4 bytes.
Bit 1 in an I/O bar is also reserved and should be masked off,
making the port mask: port = bar & ~3;
2020-09-13 20:33:17 +02:00

1255 lines
29 KiB
C

/*
* 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/pci.h"
#include "../port/error.h"
#include "../port/netif.h"
#include "../port/etherif.h"
#include "../port/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 {
Nrd = 256,
Nrb = 4*Nrd,
Rbsz = ROUNDUP(sizeof(Etherpkt)+8, 8),
Ntd = 128,
};
typedef struct Ctlr Ctlr;
typedef struct Ctlr {
uvlong 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;
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);
}
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 = smalloc(READSTR);
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-l, "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-l, "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));
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;
if(p->mem[1].bar & 1)
continue;
switch((p->did<<16)|p->vid){
default:
continue;
case (0x0022<<16)|0x100B: /* DP83820 (Gig-NIC) */
break;
}
mem = vmap(p->mem[1].bar & ~0xF, p->mem[1].size);
if(mem == nil){
print("DP83820: can't map %llux\n", p->mem[1].bar & ~0xF);
continue;
}
ctlr = malloc(sizeof(Ctlr));
if(ctlr == nil){
print("DP83820: can't allocate memory\n");
continue;
}
ctlr->port = p->mem[1].bar & ~0xF;
ctlr->pcidev = p;
pcienable(p);
ctlr->id = (p->did<<16)|p->vid;
ctlr->nic = mem;
if(dp83820reset(ctlr)){
free(ctlr);
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->ifstat = dp83820ifstat;
edev->arg = edev;
edev->promiscuous = dp83820promiscuous;
edev->multicast = dp83820multicast;
edev->shutdown = dp83820shutdown;
intrenable(edev->irq, dp83820interrupt, edev, edev->tbdf, edev->name);
return 0;
}
void
etherdp83820link(void)
{
addethercard("DP83820", dp83820pnp);
}