summaryrefslogtreecommitdiff
path: root/os/pc/ether79c970.c
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 21:39:35 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 21:39:35 +0000
commit74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a (patch)
treec6e220ba61db3a6ea4052e6841296d829654e664 /os/pc/ether79c970.c
parent46439007cf417cbd9ac8049bb4122c890097a0fa (diff)
20060303
Diffstat (limited to 'os/pc/ether79c970.c')
-rw-r--r--os/pc/ether79c970.c640
1 files changed, 640 insertions, 0 deletions
diff --git a/os/pc/ether79c970.c b/os/pc/ether79c970.c
new file mode 100644
index 00000000..25bf4185
--- /dev/null
+++ b/os/pc/ether79c970.c
@@ -0,0 +1,640 @@
+/*
+ * AMD79C970
+ * PCnet-PCI Single-Chip Ethernet Controller for PCI Local Bus
+ * To do:
+ * finish this rewrite
+ */
+#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 {
+ Lognrdre = 6,
+ Nrdre = (1<<Lognrdre),/* receive descriptor ring entries */
+ Logntdre = 4,
+ Ntdre = (1<<Logntdre),/* transmit descriptor ring entries */
+
+ Rbsize = ETHERMAXTU+4, /* ring buffer size (+4 for CRC) */
+};
+
+enum { /* DWIO I/O resource map */
+ Aprom = 0x0000, /* physical address */
+ Rdp = 0x0010, /* register data port */
+ Rap = 0x0014, /* register address port */
+ Sreset = 0x0018, /* software reset */
+ Bdp = 0x001C, /* bus configuration register data port */
+};
+
+enum { /* CSR0 */
+ Init = 0x0001, /* begin initialisation */
+ Strt = 0x0002, /* enable chip */
+ Stop = 0x0004, /* disable chip */
+ Tdmd = 0x0008, /* transmit demand */
+ Txon = 0x0010, /* transmitter on */
+ Rxon = 0x0020, /* receiver on */
+ Iena = 0x0040, /* interrupt enable */
+ Intr = 0x0080, /* interrupt flag */
+ Idon = 0x0100, /* initialisation done */
+ Tint = 0x0200, /* transmit interrupt */
+ Rint = 0x0400, /* receive interrupt */
+ Merr = 0x0800, /* memory error */
+ Miss = 0x1000, /* missed frame */
+ Cerr = 0x2000, /* collision */
+ Babl = 0x4000, /* transmitter timeout */
+ Err = 0x8000, /* Babl|Cerr|Miss|Merr */
+};
+
+enum { /* CSR3 */
+ Bswp = 0x0004, /* byte swap */
+ Emba = 0x0008, /* enable modified back-off algorithm */
+ Dxmt2pd = 0x0010, /* disable transmit two part deferral */
+ Lappen = 0x0020, /* look-ahead packet processing enable */
+};
+
+enum { /* CSR4 */
+ ApadXmt = 0x0800, /* auto pad transmit */
+};
+
+enum { /* CSR15 */
+ Prom = 0x8000, /* promiscuous mode */
+};
+
+typedef struct Iblock Iblock;
+struct Iblock { /* Initialisation Block */
+ ushort mode;
+ uchar rlen; /* upper 4 bits */
+ uchar tlen; /* upper 4 bits */
+ uchar padr[6];
+ uchar res[2];
+ uchar ladr[8];
+ ulong rdra;
+ ulong tdra;
+};
+
+typedef struct Dre Dre;
+struct Dre { /* descriptor ring entry */
+ ulong addr;
+ ulong md1; /* status|bcnt */
+ ulong md2; /* rcc|rpc|mcnt */
+ Block* bp;
+};
+
+enum { /* md1 */
+ Enp = 0x01000000, /* end of packet */
+ Stp = 0x02000000, /* start of packet */
+ RxBuff = 0x04000000, /* buffer error */
+ Def = 0x04000000, /* deferred */
+ Crc = 0x08000000, /* CRC error */
+ One = 0x08000000, /* one retry needed */
+ Oflo = 0x10000000, /* overflow error */
+ More = 0x10000000, /* more than one retry needed */
+ Fram = 0x20000000, /* framing error */
+ RxErr = 0x40000000, /* Fram|Oflo|Crc|RxBuff */
+ TxErr = 0x40000000, /* Uflo|Lcol|Lcar|Rtry */
+ Own = 0x80000000,
+};
+
+enum { /* md2 */
+ Rtry = 0x04000000, /* failed after repeated retries */
+ Lcar = 0x08000000, /* loss of carrier */
+ Lcol = 0x10000000, /* late collision */
+ Uflo = 0x40000000, /* underflow error */
+ TxBuff = 0x80000000, /* buffer error */
+};
+
+typedef struct Ctlr Ctlr;
+struct Ctlr {
+ Lock;
+ int port;
+ Pcidev* pcidev;
+ Ctlr* next;
+ int active;
+
+ int init; /* initialisation in progress */
+ Iblock iblock;
+
+ Dre* rdr; /* receive descriptor ring */
+ int rdrx;
+
+ Dre* tdr; /* transmit descriptor ring */
+ int tdrh; /* host index into tdr */
+ int tdri; /* interface index into tdr */
+ int ntq; /* descriptors active */
+
+ ulong rxbuff; /* receive statistics */
+ ulong crc;
+ ulong oflo;
+ ulong fram;
+
+ ulong rtry; /* transmit statistics */
+ ulong lcar;
+ ulong lcol;
+ ulong uflo;
+ ulong txbuff;
+
+ ulong merr; /* bobf is such a whiner */
+ ulong miss;
+ ulong babl;
+
+ int (*ior)(Ctlr*, int);
+ void (*iow)(Ctlr*, int, int);
+};
+
+static Ctlr* ctlrhead;
+static Ctlr* ctlrtail;
+
+/*
+ * The Rdp, Rap, Sreset, Bdp ports are 32-bit port offset in the enumeration above.
+ * To get to 16-bit offsets, scale down with 0x10 staying the same.
+ */
+static int
+io16r(Ctlr *c, int r)
+{
+ if(r >= Rdp)
+ r = (r-Rdp)/2+Rdp;
+ return ins(c->port+r);
+}
+
+static void
+io16w(Ctlr *c, int r, int v)
+{
+ if(r >= Rdp)
+ r = (r-Rdp)/2+Rdp;
+ outs(c->port+r, v);
+}
+
+static int
+io32r(Ctlr *c, int r)
+{
+ return inl(c->port+r);
+}
+
+static void
+io32w(Ctlr *c, int r, int v)
+{
+ outl(c->port+r, v);
+}
+
+static void
+attach(Ether*)
+{
+}
+
+static long
+ifstat(Ether* ether, void* a, long n, ulong offset)
+{
+ char *p;
+ int len;
+ Ctlr *ctlr;
+
+ ctlr = ether->ctlr;
+
+ ether->crcs = ctlr->crc;
+ ether->frames = ctlr->fram;
+ ether->buffs = ctlr->rxbuff+ctlr->txbuff;
+ ether->overflows = ctlr->oflo;
+
+ if(n == 0)
+ return 0;
+
+ p = malloc(READSTR);
+ len = snprint(p, READSTR, "Rxbuff: %ld\n", ctlr->rxbuff);
+ len += snprint(p+len, READSTR-len, "Crc: %ld\n", ctlr->crc);
+ len += snprint(p+len, READSTR-len, "Oflo: %ld\n", ctlr->oflo);
+ len += snprint(p+len, READSTR-len, "Fram: %ld\n", ctlr->fram);
+ len += snprint(p+len, READSTR-len, "Rtry: %ld\n", ctlr->rtry);
+ len += snprint(p+len, READSTR-len, "Lcar: %ld\n", ctlr->lcar);
+ len += snprint(p+len, READSTR-len, "Lcol: %ld\n", ctlr->lcol);
+ len += snprint(p+len, READSTR-len, "Uflo: %ld\n", ctlr->uflo);
+ len += snprint(p+len, READSTR-len, "Txbuff: %ld\n", ctlr->txbuff);
+ len += snprint(p+len, READSTR-len, "Merr: %ld\n", ctlr->merr);
+ len += snprint(p+len, READSTR-len, "Miss: %ld\n", ctlr->miss);
+ snprint(p+len, READSTR-len, "Babl: %ld\n", ctlr->babl);
+
+ n = readstr(offset, a, n, p);
+ free(p);
+
+ return n;
+}
+
+static void
+ringinit(Ctlr* ctlr)
+{
+ Dre *dre;
+
+ /*
+ * Initialise the receive and transmit buffer rings.
+ * The ring entries must be aligned on 16-byte boundaries.
+ *
+ * This routine is protected by ctlr->init.
+ */
+ if(ctlr->rdr == 0){
+ ctlr->rdr = xspanalloc(Nrdre*sizeof(Dre), 0x10, 0);
+ for(dre = ctlr->rdr; dre < &ctlr->rdr[Nrdre]; dre++){
+ dre->bp = iallocb(Rbsize);
+ if(dre->bp == nil)
+ panic("can't allocate ethernet receive ring\n");
+ dre->addr = PADDR(dre->bp->rp);
+ dre->md2 = 0;
+ dre->md1 = Own|(-Rbsize & 0xFFFF);
+ }
+ }
+ ctlr->rdrx = 0;
+
+ if(ctlr->tdr == 0)
+ ctlr->tdr = xspanalloc(Ntdre*sizeof(Dre), 0x10, 0);
+ memset(ctlr->tdr, 0, Ntdre*sizeof(Dre));
+ ctlr->tdrh = ctlr->tdri = 0;
+}
+
+static void
+promiscuous(void* arg, int on)
+{
+ Ether *ether;
+ int x;
+ Ctlr *ctlr;
+
+ ether = arg;
+ ctlr = ether->ctlr;
+
+ /*
+ * Put the chip into promiscuous mode. First must wait until
+ * anyone transmitting is done, then stop the chip and put
+ * it in promiscuous mode. Restarting is made harder by the chip
+ * reloading the transmit and receive descriptor pointers with their
+ * base addresses when Strt is set (unlike the older Lance chip),
+ * so the rings must be re-initialised.
+ */
+ ilock(ctlr);
+ if(ctlr->init){
+ iunlock(ctlr);
+ return;
+ }
+ ctlr->init = 1;
+ iunlock(ctlr);
+
+ while(ctlr->ntq)
+ ;
+
+ ctlr->iow(ctlr, Rdp, Stop);
+
+ ctlr->iow(ctlr, Rap, 15);
+ x = ctlr->ior(ctlr, Rdp) & ~Prom;
+ if(on)
+ x |= Prom;
+ ctlr->iow(ctlr, Rdp, x);
+ ctlr->iow(ctlr, Rap, 0);
+
+ ringinit(ctlr);
+
+ ilock(ctlr);
+ ctlr->init = 0;
+ ctlr->iow(ctlr, Rdp, Iena|Strt);
+ iunlock(ctlr);
+}
+
+static void
+txstart(Ether* ether)
+{
+ Ctlr *ctlr;
+ Block *bp;
+ Dre *dre;
+
+ ctlr = ether->ctlr;
+
+ if(ctlr->init)
+ return;
+
+ while(ctlr->ntq < (Ntdre-1)){
+ bp = qget(ether->oq);
+ if(bp == nil)
+ break;
+
+ /*
+ * Give ownership of the descriptor to the chip,
+ * increment the software ring descriptor pointer
+ * and tell the chip to poll.
+ * There's no need to pad to ETHERMINTU
+ * here as ApadXmt is set in CSR4.
+ */
+ dre = &ctlr->tdr[ctlr->tdrh];
+ dre->bp = bp;
+ dre->addr = PADDR(bp->rp);
+ dre->md2 = 0;
+ dre->md1 = Own|Stp|Enp|(-BLEN(bp) & 0xFFFF);
+ ctlr->ntq++;
+ ctlr->iow(ctlr, Rdp, Iena|Tdmd);
+ ctlr->tdrh = NEXT(ctlr->tdrh, Ntdre);
+ }
+}
+
+static void
+transmit(Ether* ether)
+{
+ Ctlr *ctlr;
+
+ ctlr = ether->ctlr;
+ ilock(ctlr);
+ txstart(ether);
+ iunlock(ctlr);
+}
+
+static void
+interrupt(Ureg*, void* arg)
+{
+ Ctlr *ctlr;
+ Ether *ether;
+ int csr0, len;
+ Dre *dre;
+ Block *bp;
+
+ ether = arg;
+ ctlr = ether->ctlr;
+
+ /*
+ * Acknowledge all interrupts and whine about those that shouldn't
+ * happen.
+ */
+intrloop:
+ csr0 = ctlr->ior(ctlr, Rdp) & 0xFFFF;
+ ctlr->iow(ctlr, Rdp, Babl|Cerr|Miss|Merr|Rint|Tint|Iena);
+ if(csr0 & Merr)
+ ctlr->merr++;
+ if(csr0 & Miss)
+ ctlr->miss++;
+ if(csr0 & Babl)
+ ctlr->babl++;
+ //if(csr0 & (Babl|Miss|Merr))
+ // print("#l%d: csr0 = 0x%uX\n", ether->ctlrno, csr0);
+ if(!(csr0 & (Rint|Tint)))
+ return;
+
+ /*
+ * Receiver interrupt: run round the descriptor ring logging
+ * errors and passing valid receive data up to the higher levels
+ * until a descriptor is encountered still owned by the chip.
+ */
+ if(csr0 & Rint){
+ dre = &ctlr->rdr[ctlr->rdrx];
+ while(!(dre->md1 & Own)){
+ if(dre->md1 & RxErr){
+ if(dre->md1 & RxBuff)
+ ctlr->rxbuff++;
+ if(dre->md1 & Crc)
+ ctlr->crc++;
+ if(dre->md1 & Oflo)
+ ctlr->oflo++;
+ if(dre->md1 & Fram)
+ ctlr->fram++;
+ }
+ else if(bp = iallocb(Rbsize)){
+ len = (dre->md2 & 0x0FFF)-4;
+ dre->bp->wp = dre->bp->rp+len;
+ etheriq(ether, dre->bp, 1);
+ dre->bp = bp;
+ dre->addr = PADDR(bp->rp);
+ }
+
+ /*
+ * Finished with this descriptor, reinitialise it,
+ * give it back to the chip, then on to the next...
+ */
+ dre->md2 = 0;
+ dre->md1 = Own|(-Rbsize & 0xFFFF);
+
+ ctlr->rdrx = NEXT(ctlr->rdrx, Nrdre);
+ dre = &ctlr->rdr[ctlr->rdrx];
+ }
+ }
+
+ /*
+ * Transmitter interrupt: wakeup anyone waiting for a free descriptor.
+ */
+ if(csr0 & Tint){
+ lock(ctlr);
+ while(ctlr->ntq){
+ dre = &ctlr->tdr[ctlr->tdri];
+ if(dre->md1 & Own)
+ break;
+
+ if(dre->md1 & TxErr){
+ if(dre->md2 & Rtry)
+ ctlr->rtry++;
+ if(dre->md2 & Lcar)
+ ctlr->lcar++;
+ if(dre->md2 & Lcol)
+ ctlr->lcol++;
+ if(dre->md2 & Uflo)
+ ctlr->uflo++;
+ if(dre->md2 & TxBuff)
+ ctlr->txbuff++;
+ ether->oerrs++;
+ }
+
+ freeb(dre->bp);
+
+ ctlr->ntq--;
+ ctlr->tdri = NEXT(ctlr->tdri, Ntdre);
+ }
+ txstart(ether);
+ unlock(ctlr);
+ }
+ goto intrloop;
+}
+
+static void
+amd79c970pci(void)
+{
+ int port;
+ Ctlr *ctlr;
+ Pcidev *p;
+
+ p = nil;
+ while(p = pcimatch(p, 0x1022, 0x2000)){
+ port = p->mem[0].bar & ~0x01;
+ if(ioalloc(port, p->mem[0].size, 0, "amd79c970") < 0){
+ print("amd79c970: port 0x%uX in use\n", port);
+ continue;
+ }
+ ctlr = malloc(sizeof(Ctlr));
+ ctlr->port = p->mem[0].bar & ~0x01;
+ ctlr->pcidev = p;
+
+ if(ctlrhead != nil)
+ ctlrtail->next = ctlr;
+ else
+ ctlrhead = ctlr;
+ ctlrtail = ctlr;
+ }
+}
+
+static int
+reset(Ether* ether)
+{
+ int x;
+ uchar ea[Eaddrlen];
+ Ctlr *ctlr;
+
+ if(ctlrhead == nil)
+ amd79c970pci();
+
+ /*
+ * Any adapter matches if no 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;
+
+ /*
+ * Allocate a controller structure and start to initialise it.
+ */
+ ether->ctlr = ctlr;
+ ether->port = ctlr->port;
+ ether->irq = ctlr->pcidev->intl;
+ ether->tbdf = ctlr->pcidev->tbdf;
+ pcisetbme(ctlr->pcidev);
+ ilock(ctlr);
+ ctlr->init = 1;
+
+ io32r(ctlr, Sreset);
+ io16r(ctlr, Sreset);
+
+ if(io16w(ctlr, Rap, 0), io16r(ctlr, Rdp) == 4){
+ ctlr->ior = io16r;
+ ctlr->iow = io16w;
+ }else if(io32w(ctlr, Rap, 0), io32r(ctlr, Rdp) == 4){
+ ctlr->ior = io32r;
+ ctlr->iow = io32w;
+ }else{
+ print("#l%d: card doesn't talk right\n", ether->ctlrno);
+iprint("#l%d: card doesn't talk right\n", ether->ctlrno);
+ iunlock(ctlr);
+ return -1;
+ }
+
+ ctlr->iow(ctlr, Rap, 88);
+ x = ctlr->ior(ctlr, Rdp);
+ ctlr->iow(ctlr, Rap, 89);
+ x |= ctlr->ior(ctlr, Rdp)<<16;
+
+ switch(x&0xFFFFFFF){
+ case 0x2420003: /* PCnet/PCI 79C970 */
+ case 0x2621003: /* PCnet/PCI II 79C970A */
+ break;
+ default:
+ print("#l%d: unknown PCnet card version %.7ux\n",
+ ether->ctlrno, x&0xFFFFFFF);
+iprint("#l%d: unknown PCnet card version %.7ux\n",
+ ether->ctlrno, x&0xFFFFFFF);
+ iunlock(ctlr);
+ return -1;
+ }
+
+ /*
+ * Set the software style in BCR20 to be PCnet-PCI to ensure 32-bit access.
+ * Set the auto pad transmit in CSR4.
+ */
+ ctlr->iow(ctlr, Rap, 20);
+ ctlr->iow(ctlr, Bdp, 0x0002);
+
+ ctlr->iow(ctlr, Rap, 4);
+ x = ctlr->ior(ctlr, Rdp) & 0xFFFF;
+ ctlr->iow(ctlr, Rdp, ApadXmt|x);
+
+ ctlr->iow(ctlr, Rap, 0);
+
+ /*
+ * Check if the adapter's station address is to be overridden.
+ * If not, read it from the I/O-space and set in ether->ea prior to
+ * loading the station address in the initialisation block.
+ */
+ memset(ea, 0, Eaddrlen);
+ if(!memcmp(ea, ether->ea, Eaddrlen)){
+ x = ctlr->ior(ctlr, Aprom);
+ ether->ea[0] = x;
+ ether->ea[1] = x>>8;
+ if(ctlr->ior == io16r)
+ x = ctlr->ior(ctlr, Aprom+2);
+ else
+ x >>= 16;
+ ether->ea[2] = x;
+ ether->ea[3] = x>>8;
+ x = ctlr->ior(ctlr, Aprom+4);
+ ether->ea[4] = x;
+ ether->ea[5] = x>>8;
+ }
+
+ /*
+ * Start to fill in the initialisation block
+ * (must be DWORD aligned).
+ */
+ ctlr->iblock.rlen = Lognrdre<<4;
+ ctlr->iblock.tlen = Logntdre<<4;
+ memmove(ctlr->iblock.padr, ether->ea, sizeof(ctlr->iblock.padr));
+
+ ringinit(ctlr);
+ ctlr->iblock.rdra = PADDR(ctlr->rdr);
+ ctlr->iblock.tdra = PADDR(ctlr->tdr);
+
+ /*
+ * Point the chip at the initialisation block and tell it to go.
+ * Mask the Idon interrupt and poll for completion. Strt and interrupt
+ * enables will be set later when attaching to the network.
+ */
+ x = PADDR(&ctlr->iblock);
+ ctlr->iow(ctlr, Rap, 1);
+ ctlr->iow(ctlr, Rdp, x & 0xFFFF);
+ ctlr->iow(ctlr, Rap, 2);
+ ctlr->iow(ctlr, Rdp, (x>>16) & 0xFFFF);
+ ctlr->iow(ctlr, Rap, 3);
+ ctlr->iow(ctlr, Rdp, Idon);
+ ctlr->iow(ctlr, Rap, 0);
+ ctlr->iow(ctlr, Rdp, Init);
+
+ while(!(ctlr->ior(ctlr, Rdp) & Idon))
+ ;
+
+ /*
+ * We used to set CSR0 to Idon|Stop here, and then
+ * in attach change it to Iena|Strt. Apparently the simulated
+ * 79C970 in VMware never enables after a write of Idon|Stop,
+ * so we enable the device here now.
+ */
+ ctlr->iow(ctlr, Rdp, Iena|Strt);
+ ctlr->init = 0;
+ iunlock(ctlr);
+
+ /*
+ * Linkage to the generic ethernet driver.
+ */
+ ether->attach = attach;
+ ether->transmit = transmit;
+ ether->interrupt = interrupt;
+ ether->ifstat = ifstat;
+
+ ether->arg = ether;
+ ether->promiscuous = promiscuous;
+
+ return 0;
+}
+
+void
+ether79c970link(void)
+{
+ addethercard("AMD79C970", reset);
+}