diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 21:39:35 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 21:39:35 +0000 |
| commit | 74a4d8c26dd3c1e9febcb717cfd6cb6512991a7a (patch) | |
| tree | c6e220ba61db3a6ea4052e6841296d829654e664 /os/boot/pc/ether79c970.c | |
| parent | 46439007cf417cbd9ac8049bb4122c890097a0fa (diff) | |
20060303
Diffstat (limited to 'os/boot/pc/ether79c970.c')
| -rw-r--r-- | os/boot/pc/ether79c970.c | 539 |
1 files changed, 539 insertions, 0 deletions
diff --git a/os/boot/pc/ether79c970.c b/os/boot/pc/ether79c970.c new file mode 100644 index 00000000..6a171b24 --- /dev/null +++ b/os/boot/pc/ether79c970.c @@ -0,0 +1,539 @@ +/* + * AMD79C970 + * PCnet-PCI Single-Chip Ethernet Controller for PCI Local Bus + * To do: + * finish this rewrite + */ +#include "u.h" +#include "lib.h" +#include "mem.h" +#include "dat.h" +#include "fns.h" +#include "io.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 { /* 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; +} Iblock; + +typedef struct { /* descriptor ring entry */ + ulong addr; + ulong md1; /* status|bcnt */ + ulong md2; /* rcc|rpc|mcnt */ + void* data; +} Dre; + +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 void +detach(Ether* ether) +{ + Ctlr *ctlr; + + ctlr = ether->ctlr; + ctlr->iow(ctlr, Rdp, Iena|Stop); +} + +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 = ialloc(Nrdre*sizeof(Dre), 0x10); + for(dre = ctlr->rdr; dre < &ctlr->rdr[Nrdre]; dre++){ + dre->data = malloc(Rbsize); + dre->addr = PADDR(dre->data); + dre->md2 = 0; + dre->md1 = Own|(-Rbsize & 0xFFFF); + } + } + ctlr->rdrx = 0; + + if(ctlr->tdr == 0) + ctlr->tdr = ialloc(Ntdre*sizeof(Dre), 0x10); + memset(ctlr->tdr, 0, Ntdre*sizeof(Dre)); + ctlr->tdrh = ctlr->tdri = 0; +} + +static void +transmit(Ether* ether) +{ + Ctlr *ctlr; + Block *bp; + Dre *dre; + RingBuf *tb; + + ctlr = ether->ctlr; + + if(ctlr->init) + return; + + while(ctlr->ntq < (Ntdre-1)){ + tb = ðer->tb[ether->ti]; + if(tb->owner != Interface) + break; + + bp = allocb(tb->len); + memmove(bp->wp, tb->pkt, tb->len); + memmove(bp->wp+Eaddrlen, ether->ea, Eaddrlen); + bp->wp += tb->len; + + /* + * 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->data = bp; + dre->addr = PADDR(bp->rp); + dre->md2 = 0; + dre->md1 = Own|Stp|Enp|Oflo|(-BLEN(bp) & 0xFFFF); + ctlr->ntq++; + ctlr->iow(ctlr, Rap, 0); + ctlr->iow(ctlr, Rdp, Iena|Tdmd); + ctlr->tdrh = NEXT(ctlr->tdrh, Ntdre); + + tb->owner = Host; + ether->ti = NEXT(ether->ti, ether->ntb); + } +} + +static void +interrupt(Ureg*, void* arg) +{ + Ctlr *ctlr; + Ether *ether; + int csr0; + Dre *dre; + RingBuf *rb; + + 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)){ + rb = ðer->rb[ether->ri]; + 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(rb->owner == Interface){ + rb->owner = Host; + rb->len = (dre->md2 & 0x0FFF)-4; + memmove(rb->pkt, dre->data, rb->len); + ether->ri = NEXT(ether->ri, ether->nrb); + } + + /* + * 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++; + } + + freeb(dre->data); + + ctlr->ntq--; + ctlr->tdri = NEXT(ctlr->tdri, Ntdre); + } + transmit(ether); + unlock(ctlr); + } + goto intrloop; +} + +static void +amd79c970pci(void) +{ + Ctlr *ctlr; + Pcidev *p; + + p = nil; + while(p = pcimatch(p, 0x1022, 0x2000)){ + 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; + } +} + +int +amd79c970reset(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); + 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("unknown PCnet card version %.7ux\n", 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->detach = detach; + + return 0; +} |
