plan9fox/sys/src/9/pc/ether82557.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

1344 lines
30 KiB
C

/*
* Intel 82557 Fast Ethernet PCI Bus LAN Controller
* as found on the Intel EtherExpress PRO/100B. This chip is full
* of smarts, unfortunately they're not all in the right place.
* To do:
* the PCI scanning code could be made common to other adapters;
* auto-negotiation, full-duplex;
* optionally use memory-mapped registers;
* detach for PCI reset problems (also towards loadable drivers).
*/
#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"
enum {
Nrfd = 64, /* receive frame area */
Ncb = 64, /* maximum control blocks queued */
NullPointer = 0xFFFFFFFF, /* 82557 NULL pointer */
};
enum { /* CSR */
Status = 0x00, /* byte or word (word includes Ack) */
Ack = 0x01, /* byte */
CommandR = 0x02, /* byte or word (word includes Interrupt) */
Interrupt = 0x03, /* byte */
General = 0x04, /* dword */
Port = 0x08, /* dword */
Fcr = 0x0C, /* Flash control register */
Ecr = 0x0E, /* EEPROM control register */
Mcr = 0x10, /* MDI control register */
Gstatus = 0x1D, /* General status register */
};
enum { /* Status */
RUidle = 0x0000,
RUsuspended = 0x0004,
RUnoresources = 0x0008,
RUready = 0x0010,
RUrbd = 0x0020, /* bit */
RUstatus = 0x003F, /* mask */
CUidle = 0x0000,
CUsuspended = 0x0040,
CUactive = 0x0080,
CUstatus = 0x00C0, /* mask */
StatSWI = 0x0400, /* SoftWare generated Interrupt */
StatMDI = 0x0800, /* MDI r/w done */
StatRNR = 0x1000, /* Receive unit Not Ready */
StatCNA = 0x2000, /* Command unit Not Active (Active->Idle) */
StatFR = 0x4000, /* Finished Receiving */
StatCX = 0x8000, /* Command eXecuted */
StatTNO = 0x8000, /* Transmit NOT OK */
};
enum { /* Command (byte) */
CUnop = 0x00,
CUstart = 0x10,
CUresume = 0x20,
LoadDCA = 0x40, /* Load Dump Counters Address */
DumpSC = 0x50, /* Dump Statistical Counters */
LoadCUB = 0x60, /* Load CU Base */
ResetSA = 0x70, /* Dump and Reset Statistical Counters */
RUstart = 0x01,
RUresume = 0x02,
RUabort = 0x04,
LoadHDS = 0x05, /* Load Header Data Size */
LoadRUB = 0x06, /* Load RU Base */
RBDresume = 0x07, /* Resume frame reception */
};
enum { /* Interrupt (byte) */
InterruptM = 0x01, /* interrupt Mask */
InterruptSI = 0x02, /* Software generated Interrupt */
};
enum { /* Ecr */
EEsk = 0x01, /* serial clock */
EEcs = 0x02, /* chip select */
EEdi = 0x04, /* serial data in */
EEdo = 0x08, /* serial data out */
EEstart = 0x04, /* start bit */
EEread = 0x02, /* read opcode */
};
enum { /* Mcr */
MDIread = 0x08000000, /* read opcode */
MDIwrite = 0x04000000, /* write opcode */
MDIready = 0x10000000, /* ready bit */
MDIie = 0x20000000, /* interrupt enable */
};
typedef struct Rfd {
int field;
ulong link;
ulong rbd;
ushort count;
ushort size;
uchar data[1700];
} Rfd;
enum { /* field */
RfdCollision = 0x00000001,
RfdIA = 0x00000002, /* IA match */
RfdRxerr = 0x00000010, /* PHY character error */
RfdType = 0x00000020, /* Type frame */
RfdRunt = 0x00000080,
RfdOverrun = 0x00000100,
RfdBuffer = 0x00000200,
RfdAlignment = 0x00000400,
RfdCRC = 0x00000800,
RfdOK = 0x00002000, /* frame received OK */
RfdC = 0x00008000, /* reception Complete */
RfdSF = 0x00080000, /* Simplified or Flexible (1) Rfd */
RfdH = 0x00100000, /* Header RFD */
RfdI = 0x20000000, /* Interrupt after completion */
RfdS = 0x40000000, /* Suspend after completion */
RfdEL = 0x80000000, /* End of List */
};
enum { /* count */
RfdF = 0x4000,
RfdEOF = 0x8000,
};
typedef struct Cb Cb;
typedef struct Cb {
ushort status;
ushort command;
ulong link;
union {
uchar data[24]; /* CbIAS + CbConfigure */
struct {
ulong tbd;
ushort count;
uchar threshold;
uchar number;
ulong tba;
ushort tbasz;
ushort pad;
};
};
Block* bp;
Cb* next;
} Cb;
enum { /* action command */
CbU = 0x1000, /* transmit underrun */
CbOK = 0x2000, /* DMA completed OK */
CbC = 0x8000, /* execution Complete */
CbNOP = 0x0000,
CbIAS = 0x0001, /* Individual Address Setup */
CbConfigure = 0x0002,
CbMAS = 0x0003, /* Multicast Address Setup */
CbTransmit = 0x0004,
CbDump = 0x0006,
CbDiagnose = 0x0007,
CbCommand = 0x0007, /* mask */
CbSF = 0x0008, /* Flexible-mode CbTransmit */
CbI = 0x2000, /* Interrupt after completion */
CbS = 0x4000, /* Suspend after completion */
CbEL = 0x8000, /* End of List */
};
enum { /* CbTransmit count */
CbEOF = 0x8000,
};
typedef struct Ctlr Ctlr;
typedef struct Ctlr {
Lock slock; /* attach */
int state;
int port;
Pcidev* pcidev;
Ctlr* next;
int active;
int eepromsz; /* address size in bits */
ushort* eeprom;
Lock miilock;
int tick;
Lock rlock; /* registers */
int command; /* last command issued */
Block* rfdhead; /* receive side */
Block* rfdtail;
int nrfd;
Lock cblock; /* transmit side */
int action;
int nop;
uchar configdata[24];
int threshold;
int ncb;
Cb* cbr;
Cb* cbhead;
Cb* cbtail;
int cbq;
int cbqmax;
int cbqmaxhw;
Lock dlock; /* dump statistical counters */
ulong dump[17];
} Ctlr;
static Ctlr* ctlrhead;
static Ctlr* ctlrtail;
static uchar configdata[24] = {
0x16, /* byte count */
0x08, /* Rx/Tx FIFO limit */
0x00, /* adaptive IFS */
0x00,
0x00, /* Rx DMA maximum byte count */
// 0x80, /* Tx DMA maximum byte count */
0x00, /* Tx DMA maximum byte count */
0x32, /* !late SCB, CNA interrupts */
0x03, /* discard short Rx frames */
0x00, /* 503/MII */
0x00,
0x2E, /* normal operation, NSAI */
0x00, /* linear priority */
0x60, /* inter-frame spacing */
0x00,
0xF2,
0xC8, /* 503, promiscuous mode off */
0x00,
0x40,
0xF3, /* transmit padding enable */
0x80, /* full duplex pin enable */
0x3F, /* no Multi IA */
0x05, /* no Multi Cast ALL */
};
#define csr8r(c, r) (inb((c)->port+(r)))
#define csr16r(c, r) (ins((c)->port+(r)))
#define csr32r(c, r) (inl((c)->port+(r)))
#define csr8w(c, r, b) (outb((c)->port+(r), (int)(b)))
#define csr16w(c, r, w) (outs((c)->port+(r), (ushort)(w)))
#define csr32w(c, r, l) (outl((c)->port+(r), (ulong)(l)))
static void
command(Ctlr* ctlr, int c, int v)
{
int timeo;
ilock(&ctlr->rlock);
/*
* Only back-to-back CUresume can be done
* without waiting for any previous command to complete.
* This should be the common case.
* Unfortunately there's a chip errata where back-to-back
* CUresumes can be lost, the fix is to always wait.
if(c == CUresume && ctlr->command == CUresume){
csr8w(ctlr, CommandR, c);
iunlock(&ctlr->rlock);
return;
}
*/
for(timeo = 0; timeo < 100; timeo++){
if(!csr8r(ctlr, CommandR))
break;
microdelay(1);
}
if(timeo >= 100){
ctlr->command = -1;
iunlock(&ctlr->rlock);
iprint("i82557: command %#ux %#ux timeout\n", c, v);
return;
}
switch(c){
case CUstart:
case LoadDCA:
case LoadCUB:
case RUstart:
case LoadHDS:
case LoadRUB:
csr32w(ctlr, General, v);
break;
/*
case CUnop:
case CUresume:
case DumpSC:
case ResetSA:
case RUresume:
case RUabort:
*/
default:
break;
}
csr8w(ctlr, CommandR, c);
ctlr->command = c;
iunlock(&ctlr->rlock);
}
static Block*
rfdalloc(ulong link)
{
Block *bp;
Rfd *rfd;
if(bp = iallocb(sizeof(Rfd))){
rfd = (Rfd*)bp->rp;
rfd->field = 0;
rfd->link = link;
rfd->rbd = NullPointer;
rfd->count = 0;
rfd->size = sizeof(Etherpkt);
}
return bp;
}
static void
watchdog(void* arg)
{
Ether *ether;
Ctlr *ctlr;
static void txstart(Ether*);
ether = arg;
while(waserror())
;
for(;;){
tsleep(&up->sleep, return0, 0, 4000);
/*
* Hmmm. This doesn't seem right. Currently
* the device can't be disabled but it may be in
* the future.
*/
ctlr = ether->ctlr;
if(ctlr == nil || ctlr->state == 0)
break;
ilock(&ctlr->cblock);
if(ctlr->tick++){
ctlr->action = CbMAS;
txstart(ether);
}
iunlock(&ctlr->cblock);
}
print("%s: exiting\n", up->text);
pexit("disabled", 1);
}
static void
attach(Ether* ether)
{
Ctlr *ctlr;
char name[KNAMELEN];
ctlr = ether->ctlr;
lock(&ctlr->slock);
if(ctlr->state){
unlock(&ctlr->slock);
return;
}
ilock(&ctlr->rlock);
csr8w(ctlr, Interrupt, 0);
iunlock(&ctlr->rlock);
command(ctlr, RUstart, PADDR(ctlr->rfdhead->rp));
ctlr->state = 1;
unlock(&ctlr->slock);
/*
* Start the watchdog timer for the receive lockup errata
* unless the EEPROM compatibility word indicates it may be
* omitted.
*/
if((ctlr->eeprom[0x03] & 0x0003) != 0x0003){
snprint(name, KNAMELEN, "#l%dwatchdog", ether->ctlrno);
kproc(name, watchdog, ether);
}
}
static long
ifstat(Ether* ether, void* a, long n, ulong offset)
{
char *p;
int i, len, phyaddr;
Ctlr *ctlr;
ulong dump[17];
ctlr = ether->ctlr;
lock(&ctlr->dlock);
if(waserror()){
unlock(&ctlr->dlock);
nexterror();
}
/*
* Start the command then
* wait for completion status,
* should be 0xA005.
*/
ctlr->dump[16] = 0;
command(ctlr, DumpSC, 0);
for(i = 0; i < 1000 && ctlr->dump[16] == 0; i++)
microdelay(100);
if(i == 1000)
error("command timeout");
memmove(dump, ctlr->dump, sizeof(dump));
ether->oerrs = dump[1]+dump[2]+dump[3];
ether->crcs = dump[10];
ether->frames = dump[11];
ether->buffs = dump[12]+dump[15];
ether->overflows = dump[13];
poperror();
unlock(&ctlr->dlock);
if(n == 0)
return 0;
p = smalloc(READSTR);
len = snprint(p, READSTR, "transmit good frames: %lud\n", dump[0]);
len += snprint(p+len, READSTR-len, "transmit maximum collisions errors: %lud\n", dump[1]);
len += snprint(p+len, READSTR-len, "transmit late collisions errors: %lud\n", dump[2]);
len += snprint(p+len, READSTR-len, "transmit underrun errors: %lud\n", dump[3]);
len += snprint(p+len, READSTR-len, "transmit lost carrier sense: %lud\n", dump[4]);
len += snprint(p+len, READSTR-len, "transmit deferred: %lud\n", dump[5]);
len += snprint(p+len, READSTR-len, "transmit single collisions: %lud\n", dump[6]);
len += snprint(p+len, READSTR-len, "transmit multiple collisions: %lud\n", dump[7]);
len += snprint(p+len, READSTR-len, "transmit total collisions: %lud\n", dump[8]);
len += snprint(p+len, READSTR-len, "receive good frames: %lud\n", dump[9]);
len += snprint(p+len, READSTR-len, "receive CRC errors: %lud\n", dump[10]);
len += snprint(p+len, READSTR-len, "receive alignment errors: %lud\n", dump[11]);
len += snprint(p+len, READSTR-len, "receive resource errors: %lud\n", dump[12]);
len += snprint(p+len, READSTR-len, "receive overrun errors: %lud\n", dump[13]);
len += snprint(p+len, READSTR-len, "receive collision detect errors: %lud\n", dump[14]);
len += snprint(p+len, READSTR-len, "receive short frame errors: %lud\n", dump[15]);
len += snprint(p+len, READSTR-len, "nop: %d\n", ctlr->nop);
if(ctlr->cbqmax > ctlr->cbqmaxhw)
ctlr->cbqmaxhw = ctlr->cbqmax;
len += snprint(p+len, READSTR-len, "cbqmax: %d\n", ctlr->cbqmax);
ctlr->cbqmax = 0;
len += snprint(p+len, READSTR-len, "threshold: %d\n", ctlr->threshold);
len += snprint(p+len, READSTR-len, "eeprom:");
for(i = 0; i < (1<<ctlr->eepromsz); i++){
if(i && ((i & 0x07) == 0))
len += snprint(p+len, READSTR-len, "\n ");
len += snprint(p+len, READSTR-len, " %4.4ux", ctlr->eeprom[i]);
}
if((ctlr->eeprom[6] & 0x1F00) && !(ctlr->eeprom[6] & 0x8000)){
phyaddr = ctlr->eeprom[6] & 0x00FF;
len += snprint(p+len, READSTR-len, "\nphy %2d:", phyaddr);
for(i = 0; i < 6; i++){
static int miir(Ctlr*, int, int);
len += snprint(p+len, READSTR-len, " %4.4ux",
miir(ctlr, phyaddr, i));
}
}
snprint(p+len, READSTR-len, "\n");
n = readstr(offset, a, n, p);
free(p);
return n;
}
static void
txstart(Ether* ether)
{
Ctlr *ctlr;
Block *bp;
Cb *cb;
ctlr = ether->ctlr;
while(ctlr->cbq < (ctlr->ncb-1)){
cb = ctlr->cbhead->next;
if(ctlr->action == 0){
bp = qget(ether->oq);
if(bp == nil)
break;
cb->command = CbS|CbSF|CbTransmit;
cb->tbd = PADDR(&cb->tba);
cb->count = 0;
cb->threshold = ctlr->threshold;
cb->number = 1;
cb->tba = PADDR(bp->rp);
cb->bp = bp;
cb->tbasz = BLEN(bp);
}
else if(ctlr->action == CbConfigure){
cb->command = CbS|CbConfigure;
memmove(cb->data, ctlr->configdata, sizeof(ctlr->configdata));
ctlr->action = 0;
}
else if(ctlr->action == CbIAS){
cb->command = CbS|CbIAS;
memmove(cb->data, ether->ea, Eaddrlen);
ctlr->action = 0;
}
else if(ctlr->action == CbMAS){
cb->command = CbS|CbMAS;
memset(cb->data, 0, sizeof(cb->data));
ctlr->action = 0;
}
else{
print("#l%d: action %#ux\n", ether->ctlrno, ctlr->action);
ctlr->action = 0;
break;
}
cb->status = 0;
coherence();
ctlr->cbhead->command &= ~CbS;
ctlr->cbhead = cb;
ctlr->cbq++;
}
/*
* Workaround for some broken HUB chips
* when connected at 10Mb/s half-duplex.
*/
if(ctlr->nop){
command(ctlr, CUnop, 0);
microdelay(1);
}
command(ctlr, CUresume, 0);
if(ctlr->cbq > ctlr->cbqmax)
ctlr->cbqmax = ctlr->cbq;
}
static void
configure(Ether* ether, int promiscuous)
{
Ctlr *ctlr;
ctlr = ether->ctlr;
ilock(&ctlr->cblock);
if(promiscuous){
ctlr->configdata[6] |= 0x80; /* Save Bad Frames */
//ctlr->configdata[6] &= ~0x40; /* !Discard Overrun Rx Frames */
ctlr->configdata[7] &= ~0x01; /* !Discard Short Rx Frames */
ctlr->configdata[15] |= 0x01; /* Promiscuous mode */
ctlr->configdata[18] &= ~0x01; /* (!Padding enable?), !stripping enable */
ctlr->configdata[21] |= 0x08; /* Multi Cast ALL */
}
else{
ctlr->configdata[6] &= ~0x80;
//ctlr->configdata[6] |= 0x40;
ctlr->configdata[7] |= 0x01;
ctlr->configdata[15] &= ~0x01;
ctlr->configdata[18] |= 0x01; /* 0x03? */
ctlr->configdata[21] &= ~0x08;
}
ctlr->action = CbConfigure;
txstart(ether);
iunlock(&ctlr->cblock);
}
static void
promiscuous(void* arg, int on)
{
Ether *ether = arg;
configure(ether, on || ether->nmaddr > 0);
}
static void
multicast(void* arg, uchar *, int)
{
Ether *ether = arg;
configure(ether, ether->prom || ether->nmaddr > 0);
}
static void
transmit(Ether* ether)
{
Ctlr *ctlr;
ctlr = ether->ctlr;
ilock(&ctlr->cblock);
txstart(ether);
iunlock(&ctlr->cblock);
}
static void
receive(Ether* ether)
{
Rfd *rfd;
Ctlr *ctlr;
int count;
Block *bp, *pbp, *xbp;
ctlr = ether->ctlr;
bp = ctlr->rfdhead;
for(rfd = (Rfd*)bp->rp; rfd->field & RfdC; rfd = (Rfd*)bp->rp){
/*
* If it's an OK receive frame
* 1) save the count
* 2) if it's small, try to allocate a block and copy
* the data, then adjust the necessary fields for reuse;
* 3) if it's big, try to allocate a new Rfd and if
* successful
* adjust the received buffer pointers for the
* actual data received;
* initialise the replacement buffer to point to
* the next in the ring;
* initialise bp to point to the replacement;
* 4) if there's a good packet, pass it on for disposal.
*/
if(rfd->field & RfdOK){
pbp = nil;
count = rfd->count & 0x3FFF;
if((count < ETHERMAXTU/4) && (pbp = iallocb(count))){
memmove(pbp->rp, bp->rp+offsetof(Rfd, data[0]), count);
pbp->wp = pbp->rp + count;
rfd->count = 0;
rfd->field = 0;
}
else if(xbp = rfdalloc(rfd->link)){
bp->rp += offsetof(Rfd, data[0]);
bp->wp = bp->rp + count;
xbp->next = bp->next;
bp->next = 0;
pbp = bp;
bp = xbp;
}
if(pbp != nil)
etheriq(ether, pbp);
}
else{
rfd->count = 0;
rfd->field = 0;
}
/*
* The ring tail pointer follows the head with with one
* unused buffer in between to defeat hardware prefetch;
* once the tail pointer has been bumped on to the next
* and the new tail has the Suspend bit set, it can be
* removed from the old tail buffer.
* As a replacement for the current head buffer may have
* been allocated above, ensure that the new tail points
* to it (next and link).
*/
rfd = (Rfd*)ctlr->rfdtail->rp;
ctlr->rfdtail = ctlr->rfdtail->next;
ctlr->rfdtail->next = bp;
((Rfd*)ctlr->rfdtail->rp)->link = PADDR(bp->rp);
((Rfd*)ctlr->rfdtail->rp)->field |= RfdS;
coherence();
rfd->field &= ~RfdS;
/*
* Finally done with the current (possibly replaced)
* head, move on to the next and maintain the sentinel
* between tail and head.
*/
ctlr->rfdhead = bp->next;
bp = ctlr->rfdhead;
}
}
static void
interrupt(Ureg*, void* arg)
{
Cb* cb;
Ctlr *ctlr;
Ether *ether;
int status;
ether = arg;
ctlr = ether->ctlr;
for(;;){
ilock(&ctlr->rlock);
status = csr16r(ctlr, Status);
csr8w(ctlr, Ack, (status>>8) & 0xFF);
iunlock(&ctlr->rlock);
if(!(status & (StatCX|StatFR|StatCNA|StatRNR|StatMDI|StatSWI)))
break;
/*
* If the watchdog timer for the receiver lockup errata is running,
* let it know the receiver is active.
*/
if(status & (StatFR|StatRNR)){
ilock(&ctlr->cblock);
ctlr->tick = 0;
iunlock(&ctlr->cblock);
}
if(status & StatFR){
receive(ether);
status &= ~StatFR;
}
if(status & StatRNR){
command(ctlr, RUresume, 0);
status &= ~StatRNR;
}
if(status & StatCNA){
ilock(&ctlr->cblock);
cb = ctlr->cbtail;
while(ctlr->cbq){
if(!(cb->status & CbC))
break;
if(cb->bp){
freeb(cb->bp);
cb->bp = nil;
}
if((cb->status & CbU) && ctlr->threshold < 0xE0)
ctlr->threshold++;
ctlr->cbq--;
cb = cb->next;
}
ctlr->cbtail = cb;
txstart(ether);
iunlock(&ctlr->cblock);
status &= ~StatCNA;
}
if(status & (StatCX|StatFR|StatCNA|StatRNR|StatMDI|StatSWI))
panic("#l%d: status %#ux", ether->ctlrno, status);
}
}
static void
ctlrinit(Ctlr* ctlr)
{
int i;
Block *bp;
Rfd *rfd;
ulong link;
/*
* Create the Receive Frame Area (RFA) as a ring of allocated
* buffers.
* A sentinel buffer is maintained between the last buffer in
* the ring (marked with RfdS) and the head buffer to defeat the
* hardware prefetch of the next RFD and allow dynamic buffer
* allocation.
*/
link = NullPointer;
for(i = 0; i < Nrfd; i++){
bp = rfdalloc(link);
if(bp == nil)
panic("i82557: can't allocate rfd buffer");
if(ctlr->rfdhead == nil)
ctlr->rfdtail = bp;
bp->next = ctlr->rfdhead;
ctlr->rfdhead = bp;
link = PADDR(bp->rp);
}
ctlr->rfdtail->next = ctlr->rfdhead;
rfd = (Rfd*)ctlr->rfdtail->rp;
rfd->link = PADDR(ctlr->rfdhead->rp);
rfd->field |= RfdS;
ctlr->rfdhead = ctlr->rfdhead->next;
/*
* Create a ring of control blocks for the
* transmit side.
*/
ilock(&ctlr->cblock);
ctlr->cbr = malloc(ctlr->ncb*sizeof(Cb));
if(ctlr->cbr == nil)
panic("i82557: can't allocate cbr");
for(i = 0; i < ctlr->ncb; i++){
ctlr->cbr[i].status = CbC|CbOK;
ctlr->cbr[i].command = CbS|CbNOP;
ctlr->cbr[i].link = PADDR(&ctlr->cbr[NEXT(i, ctlr->ncb)].status);
ctlr->cbr[i].next = &ctlr->cbr[NEXT(i, ctlr->ncb)];
}
ctlr->cbhead = ctlr->cbr;
ctlr->cbtail = ctlr->cbr;
ctlr->cbq = 0;
memmove(ctlr->configdata, configdata, sizeof(configdata));
ctlr->threshold = 80;
ctlr->tick = 0;
iunlock(&ctlr->cblock);
}
static int
miir(Ctlr* ctlr, int phyadd, int regadd)
{
int mcr, timo;
lock(&ctlr->miilock);
csr32w(ctlr, Mcr, MDIread|(phyadd<<21)|(regadd<<16));
mcr = 0;
for(timo = 64; timo; timo--){
mcr = csr32r(ctlr, Mcr);
if(mcr & MDIready)
break;
microdelay(1);
}
unlock(&ctlr->miilock);
if(mcr & MDIready)
return mcr & 0xFFFF;
return -1;
}
static int
miiw(Ctlr* ctlr, int phyadd, int regadd, int data)
{
int mcr, timo;
lock(&ctlr->miilock);
csr32w(ctlr, Mcr, MDIwrite|(phyadd<<21)|(regadd<<16)|(data & 0xFFFF));
mcr = 0;
for(timo = 64; timo; timo--){
mcr = csr32r(ctlr, Mcr);
if(mcr & MDIready)
break;
microdelay(1);
}
unlock(&ctlr->miilock);
if(mcr & MDIready)
return 0;
return -1;
}
static int
hy93c46r(Ctlr* ctlr, int r)
{
int data, i, op, size;
/*
* Hyundai HY93C46 or equivalent serial EEPROM.
* This sequence for reading a 16-bit register 'r'
* in the EEPROM is taken straight from Section
* 3.3.4.2 of the Intel 82557 User's Guide.
*/
reread:
csr16w(ctlr, Ecr, EEcs);
op = EEstart|EEread;
for(i = 2; i >= 0; i--){
data = (((op>>i) & 0x01)<<2)|EEcs;
csr16w(ctlr, Ecr, data);
csr16w(ctlr, Ecr, data|EEsk);
microdelay(1);
csr16w(ctlr, Ecr, data);
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--){
data = (((r>>size) & 0x01)<<2)|EEcs;
csr16w(ctlr, Ecr, data);
csr16w(ctlr, Ecr, data|EEsk);
delay(1);
csr16w(ctlr, Ecr, data);
microdelay(1);
if(!(csr16r(ctlr, Ecr) & EEdo))
break;
}
data = 0;
for(i = 15; i >= 0; i--){
csr16w(ctlr, Ecr, EEcs|EEsk);
microdelay(1);
if(csr16r(ctlr, Ecr) & EEdo)
data |= (1<<i);
csr16w(ctlr, Ecr, EEcs);
microdelay(1);
}
csr16w(ctlr, Ecr, 0);
if(ctlr->eepromsz == 0){
ctlr->eepromsz = 8-size;
ctlr->eeprom = malloc((1<<ctlr->eepromsz)*sizeof(ushort));
if(ctlr->eeprom == nil)
panic("i82557: can't allocate eeprom");
goto reread;
}
return data;
}
static void
i82557pci(void)
{
Pcidev *p;
Ctlr *ctlr;
int nop, port;
p = nil;
nop = 0;
while(p = pcimatch(p, 0x8086, 0)){
switch(p->did){
default:
continue;
case 0x1031: /* Intel 82562EM */
case 0x103B: /* Intel 82562EM */
case 0x103C: /* Intel 82562EM */
case 0x1050: /* Intel 82562EZ */
case 0x1039: /* Intel 82801BD PRO/100 VE */
case 0x103A: /* Intel 82562 PRO/100 VE */
case 0x103D: /* Intel 82562 PRO/100 VE */
case 0x1064: /* Intel 82562 PRO/100 VE */
case 0x2449: /* Intel 82562ET */
case 0x27DC: /* Intel 82801G PRO/100 VE */
nop = 1;
/*FALLTHROUGH*/
case 0x1209: /* Intel 82559ER */
case 0x1229: /* Intel 8255[789] */
case 0x1030: /* Intel 82559 InBusiness 10/100 */
break;
}
/*
* bar[0] is the memory-mapped register address (4KB),
* bar[1] is the I/O port register address (32 bytes) and
* bar[2] is for the flash ROM (1MB).
*/
port = p->mem[1].bar & ~3;
if(ioalloc(port, p->mem[1].size, 0, "i82557") < 0){
print("i82557: port %#ux in use\n", port);
continue;
}
ctlr = malloc(sizeof(Ctlr));
if(ctlr == nil){
print("i82557: can't allocate memory\n");
iofree(port);
continue;
}
ctlr->port = port;
ctlr->pcidev = p;
ctlr->nop = nop;
if(ctlrhead != nil)
ctlrtail->next = ctlr;
else
ctlrhead = ctlr;
ctlrtail = ctlr;
}
}
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
scanphy(Ctlr* ctlr)
{
int i, oui, x;
for(i = 0; i < 32; i++){
if((oui = miir(ctlr, i, 2)) == -1 || oui == 0 || oui == 0xFFFF)
continue;
oui <<= 6;
x = miir(ctlr, i, 3);
oui |= x>>10;
//print("phy%d: oui %#ux reg1 %#ux\n", i, oui, miir(ctlr, i, 1));
ctlr->eeprom[6] = i;
if(oui == 0xAA00)
ctlr->eeprom[6] |= 0x07<<8;
else if(oui == 0x80017){
if(x & 0x01)
ctlr->eeprom[6] |= 0x0A<<8;
else
ctlr->eeprom[6] |= 0x04<<8;
}
return i;
}
return -1;
}
static void
shutdown(Ether* ether)
{
Ctlr *ctlr = ether->ctlr;
print("ether82557 shutting down\n");
csr32w(ctlr, Port, 0);
delay(1);
csr8w(ctlr, Interrupt, InterruptM);
}
static int
reset(Ether* ether)
{
int anar, anlpar, bmcr, bmsr, i, k, medium, phyaddr, x;
unsigned short sum;
uchar ea[Eaddrlen];
Ctlr *ctlr;
if(ctlrhead == nil)
i82557pci();
/*
* 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;
pcienable(ctlr->pcidev);
pcisetbme(ctlr->pcidev);
/*
* Initialise the Ctlr structure.
* Perform a software reset after which should ensure busmastering
* is still enabled. The EtherExpress PRO/100B appears to leave
* the PCI configuration alone (see the 'To do' list above) so punt
* for now.
* Load the RUB and CUB registers for linear addressing (0).
*/
ether->ctlr = ctlr;
ether->port = ctlr->port;
ether->irq = ctlr->pcidev->intl;
ether->tbdf = ctlr->pcidev->tbdf;
ilock(&ctlr->rlock);
csr32w(ctlr, Port, 0);
delay(1);
csr8w(ctlr, Interrupt, InterruptM);
iunlock(&ctlr->rlock);
command(ctlr, LoadRUB, 0);
command(ctlr, LoadCUB, 0);
command(ctlr, LoadDCA, PADDR(ctlr->dump));
/*
* Initialise the receive frame, transmit ring and configuration areas.
*/
ctlr->ncb = Ncb;
ctlrinit(ctlr);
/*
* Read the EEPROM.
* Do a dummy read first to get the size
* and allocate ctlr->eeprom.
*/
hy93c46r(ctlr, 0);
sum = 0;
for(i = 0; i < (1<<ctlr->eepromsz); i++){
x = hy93c46r(ctlr, i);
ctlr->eeprom[i] = x;
sum += x;
}
if(sum != 0xBABA)
print("#l%d: EEPROM checksum - %#4.4ux\n", ether->ctlrno, sum);
/*
* Eeprom[6] indicates whether there is a PHY and whether
* it's not 10Mb-only, in which case use the given PHY address
* to set any PHY specific options and determine the speed.
* Unfortunately, sometimes the EEPROM is blank except for
* the ether address and checksum; in this case look at the
* controller type and if it's am 82558 or 82559 it has an
* embedded PHY so scan for that.
* If no PHY, assume 82503 (serial) operation.
*/
if((ctlr->eeprom[6] & 0x1F00) && !(ctlr->eeprom[6] & 0x8000))
phyaddr = ctlr->eeprom[6] & 0x00FF;
else
switch(ctlr->pcidev->rid){
case 0x01: /* 82557 A-step */
case 0x02: /* 82557 B-step */
case 0x03: /* 82557 C-step */
default:
phyaddr = -1;
break;
case 0x04: /* 82558 A-step */
case 0x05: /* 82558 B-step */
case 0x06: /* 82559 A-step */
case 0x07: /* 82559 B-step */
case 0x08: /* 82559 C-step */
case 0x09: /* 82559ER A-step */
phyaddr = scanphy(ctlr);
break;
}
if(phyaddr >= 0){
/*
* Resolve the highest common ability of the two
* link partners. In descending order:
* 0x0100 100BASE-TX Full Duplex
* 0x0200 100BASE-T4
* 0x0080 100BASE-TX
* 0x0040 10BASE-T Full Duplex
* 0x0020 10BASE-T
*/
anar = miir(ctlr, phyaddr, 0x04);
anlpar = miir(ctlr, phyaddr, 0x05) & 0x03E0;
anar &= anlpar;
bmcr = 0;
if(anar & 0x380)
bmcr = 0x2000;
if(anar & 0x0140)
bmcr |= 0x0100;
switch((ctlr->eeprom[6]>>8) & 0x001F){
case 0x04: /* DP83840 */
case 0x0A: /* DP83840A */
/*
* The DP83840[A] requires some tweaking for
* reliable operation.
* The manual says bit 10 should be unconditionally
* set although it supposedly only affects full-duplex
* operation (an & 0x0140).
*/
x = miir(ctlr, phyaddr, 0x17) & ~0x0520;
x |= 0x0420;
for(i = 0; i < ether->nopt; i++){
if(cistrcmp(ether->opt[i], "congestioncontrol"))
continue;
x |= 0x0100;
break;
}
miiw(ctlr, phyaddr, 0x17, x);
/*
* If the link partner can't autonegotiate, determine
* the speed from elsewhere.
*/
if(anlpar == 0){
miir(ctlr, phyaddr, 0x01);
bmsr = miir(ctlr, phyaddr, 0x01);
x = miir(ctlr, phyaddr, 0x19);
if((bmsr & 0x0004) && !(x & 0x0040))
bmcr = 0x2000;
}
break;
case 0x07: /* Intel 82555 */
/*
* Auto-negotiation may fail if the other end is
* a DP83840A and the cable is short.
*/
miir(ctlr, phyaddr, 0x01);
bmsr = miir(ctlr, phyaddr, 0x01);
if((miir(ctlr, phyaddr, 0) & 0x1000) && (bmsr & 0x0020))
break;
miiw(ctlr, phyaddr, 0x1A, 0x2010);
x = miir(ctlr, phyaddr, 0);
miiw(ctlr, phyaddr, 0, 0x1200|x);
for(i = 0; i < 3000; i++){
delay(1);
if(miir(ctlr, phyaddr, 0x01) & 0x0020)
break;
}
miiw(ctlr, phyaddr, 0x1A, 0x2000);
anar = miir(ctlr, phyaddr, 0x04);
anlpar = miir(ctlr, phyaddr, 0x05) & 0x03E0;
anar &= anlpar;
bmcr = 0;
if(anar & 0x380)
bmcr = 0x2000;
if(anar & 0x0140)
bmcr |= 0x0100;
break;
}
/*
* Force speed and duplex if no auto-negotiation.
*/
if(anlpar == 0){
medium = -1;
for(i = 0; i < ether->nopt; i++){
for(k = 0; k < nelem(mediatable); k++){
if(cistrcmp(mediatable[k], ether->opt[i]))
continue;
medium = k;
break;
}
switch(medium){
default:
break;
case 0x00: /* 10BASE-T */
case 0x01: /* 10BASE-2 */
case 0x02: /* 10BASE-5 */
bmcr &= ~(0x2000|0x0100);
ctlr->configdata[19] &= ~0x40;
break;
case 0x03: /* 100BASE-TX */
case 0x06: /* 100BASE-T4 */
case 0x07: /* 100BASE-FX */
ctlr->configdata[19] &= ~0x40;
bmcr |= 0x2000;
break;
case 0x04: /* 10BASE-TFD */
bmcr = (bmcr & ~0x2000)|0x0100;
ctlr->configdata[19] |= 0x40;
break;
case 0x05: /* 100BASE-TXFD */
case 0x08: /* 100BASE-FXFD */
bmcr |= 0x2000|0x0100;
ctlr->configdata[19] |= 0x40;
break;
}
}
if(medium != -1)
miiw(ctlr, phyaddr, 0x00, bmcr);
}
if(bmcr & 0x2000)
ether->mbps = 100;
ctlr->configdata[8] = 1;
ctlr->configdata[15] &= ~0x80;
}
else{
ctlr->configdata[8] = 0;
ctlr->configdata[15] |= 0x80;
}
/*
* Workaround for some broken HUB chips when connected at 10Mb/s
* half-duplex.
* This is a band-aid, but as there's no dynamic auto-negotiation
* code at the moment, only deactivate the workaround code in txstart
* if the link is 100Mb/s.
*/
if(ether->mbps != 10)
ctlr->nop = 0;
/*
* Load the chip configuration and start it off.
*/
if(ether->oq == 0)
ether->oq = qopen(256*1024, Qmsg, 0, 0);
configure(ether, 0);
command(ctlr, CUstart, PADDR(&ctlr->cbr->status));
/*
* 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 with the Individual Address Setup command.
*/
memset(ea, 0, Eaddrlen);
if(memcmp(ea, ether->ea, Eaddrlen) == 0){
for(i = 0; i < Eaddrlen/2; i++){
x = ctlr->eeprom[i];
ether->ea[2*i] = x;
ether->ea[2*i+1] = x>>8;
}
}
ilock(&ctlr->cblock);
ctlr->action = CbIAS;
txstart(ether);
iunlock(&ctlr->cblock);
/*
* Linkage to the generic ethernet driver.
*/
ether->attach = attach;
ether->transmit = transmit;
ether->ifstat = ifstat;
ether->shutdown = shutdown;
ether->promiscuous = promiscuous;
ether->multicast = multicast;
ether->arg = ether;
intrenable(ether->irq, interrupt, ether, ether->tbdf, ether->name);
return 0;
}
void
ether82557link(void)
{
addethercard("i82557", reset);
}