summaryrefslogtreecommitdiff
path: root/os/pc/ether82557.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/pc/ether82557.c')
-rw-r--r--os/pc/ether82557.c1325
1 files changed, 1325 insertions, 0 deletions
diff --git a/os/pc/ether82557.c b/os/pc/ether82557.c
new file mode 100644
index 00000000..b61e3e1c
--- /dev/null
+++ b/os/pc/ether82557.c
@@ -0,0 +1,1325 @@
+/*
+ * 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/error.h"
+#include "../port/netif.h"
+
+#include "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 0x%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;
+ 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){
+ print("%s: exiting\n", up->text);
+ pexit("disabled", 0);
+ }
+
+ ilock(&ctlr->cblock);
+ if(ctlr->tick++){
+ ctlr->action = CbMAS;
+ txstart(ether);
+ }
+ iunlock(&ctlr->cblock);
+ }
+}
+
+static void
+attach(Ether* ether)
+{
+ Ctlr *ctlr;
+ char name[KNAMELEN];
+
+ ctlr = ether->ctlr;
+ lock(&ctlr->slock);
+ if(ctlr->state == 0){
+ ilock(&ctlr->rlock);
+ csr8w(ctlr, Interrupt, 0);
+ iunlock(&ctlr->rlock);
+ command(ctlr, RUstart, PADDR(ctlr->rfdhead->rp));
+ ctlr->state = 1;
+
+ /*
+ * 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, 0);
+ }
+ }
+ unlock(&ctlr->slock);
+}
+
+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);
+
+ /*
+ * Start the command then
+ * wait for completion status,
+ * should be 0xA005.
+ */
+ ctlr->dump[16] = 0;
+ command(ctlr, DumpSC, 0);
+ while(ctlr->dump[16] == 0)
+ ;
+
+ ether->oerrs = ctlr->dump[1]+ctlr->dump[2]+ctlr->dump[3];
+ ether->crcs = ctlr->dump[10];
+ ether->frames = ctlr->dump[11];
+ ether->buffs = ctlr->dump[12]+ctlr->dump[15];
+ ether->overflows = ctlr->dump[13];
+
+ if(n == 0){
+ unlock(&ctlr->dlock);
+ return 0;
+ }
+
+ memmove(dump, ctlr->dump, sizeof(dump));
+ unlock(&ctlr->dlock);
+
+ p = malloc(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 0x%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)
+{
+ configure(arg, on);
+}
+
+static void
+multicast(void* arg, uchar *addr, int on)
+{
+ USED(addr, on);
+ configure(arg, 1);
+}
+
+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+sizeof(Rfd)-sizeof(rfd->data), count);
+ pbp->wp = pbp->rp + count;
+
+ rfd->count = 0;
+ rfd->field = 0;
+ }
+ else if(xbp = rfdalloc(rfd->link)){
+ bp->rp += sizeof(Rfd)-sizeof(rfd->data);
+ bp->wp = bp->rp + count;
+
+ xbp->next = bp->next;
+ bp->next = 0;
+
+ pbp = bp;
+ bp = xbp;
+ }
+ if(pbp != nil)
+ etheriq(ether, pbp, 1);
+ }
+ 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\n", 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(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));
+ 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));
+ goto reread;
+ }
+
+ return data;
+}
+
+static void
+i82557pci(void)
+{
+ Pcidev *p;
+ Ctlr *ctlr;
+ int i, nop, port;
+
+ p = nil;
+ nop = 0;
+ while(p = pcimatch(p, 0x8086, 0)){
+ switch(p->did){
+ default:
+ continue;
+ case 0x1031: /* Intel 82562EM */
+ case 0x1050: /* Intel 82562EZ */
+ case 0x2449: /* Intel 82562ET */
+ nop = 1;
+ /*FALLTHROUGH*/
+ case 0x1209: /* Intel 82559ER */
+ case 0x1229: /* Intel 8255[789] */
+ case 0x1030: /* Intel 82559 InBusiness 10/100 */
+ case 0x1039: /* Intel 82801BD PRO/100 VE */
+ case 0x103A: /* Intel 82562 PRO/100 VE */
+ break;
+ }
+
+ if(pcigetpms(p) > 0){
+ pcisetpms(p, 0);
+
+ for(i = 0; i < 6; i++)
+ pcicfgw32(p, PciBAR0+i*4, p->mem[i].bar);
+ pcicfgw8(p, PciINTL, p->intl);
+ pcicfgw8(p, PciLTR, p->ltr);
+ pcicfgw8(p, PciCLS, p->cls);
+ pcicfgw16(p, PciPCR, p->pcr);
+ }
+
+ /*
+ * 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 & ~0x01;
+ if(ioalloc(port, p->mem[1].size, 0, "i82557") < 0){
+ print("i82557: port 0x%uX in use\n", port);
+ continue;
+ }
+
+ ctlr = malloc(sizeof(Ctlr));
+ ctlr->port = port;
+ ctlr->pcidev = p;
+ ctlr->nop = nop;
+
+ if(ctlrhead != nil)
+ ctlrtail->next = ctlr;
+ else
+ ctlrhead = ctlr;
+ ctlrtail = ctlr;
+
+ pcisetbme(p);
+ }
+}
+
+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;
+
+ /*
+ * 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 - 0x%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)){
+ miiw(ctlr, phyaddr, 0x1A, 0x2010);
+ x = miir(ctlr, phyaddr, 0);
+ miiw(ctlr, phyaddr, 0, 0x0200|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->interrupt = interrupt;
+ ether->ifstat = ifstat;
+ ether->shutdown = shutdown;
+
+ ether->promiscuous = promiscuous;
+ ether->multicast = multicast;
+ ether->arg = ether;
+
+ return 0;
+}
+
+void
+ether82557link(void)
+{
+ addethercard("i82557", reset);
+}