diff options
Diffstat (limited to 'os/manga/etherks8695.c')
| -rw-r--r-- | os/manga/etherks8695.c | 1169 |
1 files changed, 1169 insertions, 0 deletions
diff --git a/os/manga/etherks8695.c b/os/manga/etherks8695.c new file mode 100644 index 00000000..06e7de9e --- /dev/null +++ b/os/manga/etherks8695.c @@ -0,0 +1,1169 @@ +/* + * KS8695P ethernet + * WAN port, LAN port to 4-port switch + */ + +#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" +#include "ureg.h" + +#define DBG if(0)iprint +#define MIIDBG if(0)iprint + +enum { + Nrdre = 64, /* receive descriptor ring entries */ + Ntdre = 32, /* transmit descriptor ring entries */ + + Rbsize = ROUNDUP(ETHERMAXTU+4, 4), /* ring buffer size (+4 for CRC), must be multiple of 4 */ + Bufsize = ROUNDUP(Rbsize, CACHELINESZ), /* keep start and end at cache lines */ +}; + +typedef struct DmaReg DmaReg; +struct DmaReg { + ulong dtxc; /* transmit control register */ + ulong drxc; /* receive control register */ + ulong dtsc; /* transmit start command register */ + ulong drsc; /* receive start command register */ + ulong tdlb; /* transmit descriptor list base address */ + ulong rdlb; /* receive descriptor list base address */ + ulong mal; /* mac address low (4 bytes) */ + ulong mah; /* mac address high (2 bytes) */ + ulong pad[0x80-0x20]; + + /* pad to 0x80 for */ + ulong maal[16][2]; /* additional mac addresses */ +}; + +enum { + /* dtxc */ + TxSoftReset= 1<<31, + /* 29:24 is burst size in words; 0, 1, 2, 4, 8, 16, 32; 0=unlimited */ + TxUDPck= 1<<18, /* generate UDP, TCP, IP check sum */ + TxTCPck= 1<<17, + TxIPck= 1<<16, + TxFCE= 1<<9, /* transmit flow control enable */ + TxLB= 1<<8, /* loop back */ + TxEP= 1<<2, /* enable padding */ + TxCrc= 1<<1, /* add CRC */ + TxEnable= 1<<0, /* enable Tx block */ + + /* drxc */ + /* 29:24 is burst size in words */ + RxUDPck= 1<<18, /* check UDP, TCP, IP check sum */ + RxTCPck= 1<<17, + RxIPck= 1<<16, + RxFCE= 1<<9, /* flow control enable */ + RxRB= 1<<6, /* receive broadcast */ + RxRM= 1<<5, /* receive multicast (including broadcast) */ + RxRU= 1<<4, /* receive unicast */ + RxAE= 1<<3, /* receive error frames */ + RxRA= 1<<2, /* receive all */ + RxEnable= 1<<0, /* enable Rx block */ + +}; + +typedef struct WanPhy WanPhy; +struct WanPhy { + ulong did; /* device ID */ + ulong rid; /* revision ID */ + ulong pad0; /* miscellaneous control in plain 8695 (not P or X) */ + ulong wmc; /* WAN miscellaneous control */ + ulong wppm; /* phy power management */ + ulong wpc; /* phys ctl */ + ulong wps; /* phys status */ + ulong pps; /* phy power save */ +}; + +enum { + /* wmc */ + WAnc= 1<<30, /* auto neg complete */ + WAnr= 1<<29, /* auto neg restart */ + WAnaP= 1<<28, /* advertise pause */ + WAna100FD= 1<<27, /* advertise 100BASE-TX FD */ + WAna100HD= 1<<26, /* advertise 100BASE-TX */ + WAna10FD= 1<<25, /* advertise 10BASE-TX FD */ + WAna10HD= 1<<24, /* advertise 10BASE-TX */ + WLs= 1<<23, /* link status */ + WDs= 1<<22, /* duplex status (resolved) */ + WSs= 1<<21, /* speed status (resolved) */ + WLparP= 1<<20, /* link partner pause */ + WLpar100FD= 1<<19, /* link partner 100BASE-TX FD */ + WLpar100HD= 1<<18, + WLpar10FD= 1<<17, + WLpar10HD= 1<<16, + WAnDis= 1<<15, /* auto negotiation disable */ + WForce100= 1<<14, + WForceFD= 1<<13, + /* 6:4 LED1 select */ + /* 2:0 LED0 select */ + + /* LED select */ + LedSpeed= 0, + LedLink, + LedFD, /* full duplex */ + LedColl, /* collision */ + LedTxRx, /* activity */ + LedFDColl, /* FD/collision */ + LedLinkTxRx, /* link and activity */ + + /* ppm */ + WLpbk= 1<<14, /* local (MAC) loopback */ + WRlpblk= 1<<13, /* remote (PHY) loopback */ + WPhyIso= 1<<12, /* isolate PHY from MII and Tx+/Tx- */ + WPhyLink= 1<<10, /* force link in PHY */ + WMdix= 1<<9, /* =1, MDIX, =0, MDX */ + WFef= 1<<8, /* far end fault */ + WAmdixp= 1<<7, /* disable IEEE spec for auto-neg MDIX */ + WTxdis= 1<<6, /* disable port's transmitter */ + WDfef= 1<<5, /* disable far end fault detection */ + Wpd= 1<<4, /* power down */ + WDmdx= 1<<3, /* disable auto MDI/MDIX */ + WFmdx= 1<<2, /* if auto disabled, force MDIX */ + WMlpbk= 1<<1, /* local loopback */ + + /* pps */ + Ppsm= 1<<0, /* enable PHY power save mode */ +}; + +#define DMABURST(n) ((n)<<24) + +typedef struct { + Lock; + int port; + int init; + int active; + int reading; /* device read process is active */ + ulong anap; /* auto negotiate result */ + DmaReg* regs; + WanPhy* wphy; + + Ring; + + ulong interrupts; /* statistics */ + ulong deferred; + ulong heartbeat; + ulong latecoll; + ulong retrylim; + ulong underrun; + ulong overrun; + ulong carrierlost; + ulong retrycount; +} Ctlr; + +static void switchinit(uchar*); +static void switchdump(void); + +static void +attach(Ether *ether) +{ + Ctlr *ctlr; + + ctlr = ether->ctlr; + ilock(ctlr); + if(!ctlr->active){ + /* TO DO: rx/tx enable */ + ctlr->regs->dtxc |= TxEnable; + ctlr->regs->drxc |= RxEnable; + microdelay(10); + ctlr->regs->drsc = 1; /* start read process */ + microdelay(10); + ctlr->reading = (INTRREG->st & (1<<IRQwmrps)) == 0; + ctlr->active = 1; + } + iunlock(ctlr); +} + +static void +closed(Ether *ether) +{ + Ctlr *ctlr; + + ctlr = ether->ctlr; + if(ctlr->active){ + ilock(ctlr); +iprint("ether closed\n"); + ctlr->regs->dtxc &= ~TxEnable; + ctlr->regs->drxc &= ~RxEnable; + /* TO DO: reset ring? */ + /* TO DO: could wait? */ + ctlr->active = 0; + iunlock(ctlr); + } +} + +static void +promiscuous(void* arg, int on) +{ + Ether *ether; + Ctlr *ctlr; + ulong w; + + ether = (Ether*)arg; + ctlr = ether->ctlr; + + ilock(ctlr); + /* TO DO: must disable reader */ + w = ctlr->regs->drxc; + if(on != ((w&RxRA)!=0)){ + /* TO DO: must disable reader */ + ctlr->regs->drxc = w ^ RxRA; + /* TO DO: restart reader */ + } + iunlock(ctlr); +} + +static void +multicast(void* arg, uchar *addr, int on) +{ + Ether *ether; + Ctlr *ctlr; + + USED(addr, on); /* if on, could SetGroupAddress; if !on, it's hard */ + + ether = (Ether*)arg; + ctlr = ether->ctlr; + + ilock(ctlr); + /* TO DO: must disable reader */ + /* TO DO: use internal multicast tables? (probably needs LRU or some such) */ + if(ether->nmaddr) + ctlr->regs->drxc |= RxRM; + else + ctlr->regs->drxc &= ~RxRM; + iunlock(ctlr); +} + +static void +txstart(Ether *ether) +{ + int len; + Ctlr *ctlr; + Block *b; + BD *dre; + + ctlr = ether->ctlr; + while(ctlr->ntq < ctlr->ntdre-1){ + b = qget(ether->oq); + if(b == 0) + break; + + dre = &ctlr->tdr[ctlr->tdrh]; + if(dre->ctrl & BdBusy) + panic("ether: txstart"); + + /* + * Give ownership of the descriptor to the chip, increment the + * software ring descriptor pointer and tell the chip to poll. + */ + len = BLEN(b); + if(ctlr->txb[ctlr->tdrh] != nil) + panic("etherks8695: txstart"); + ctlr->txb[ctlr->tdrh] = b; + dcflush(b->rp, len); + dre->addr = PADDR(b->rp); + dre->size = TxIC|TxFS|TxLS | len; + dre->ctrl = BdBusy; + ctlr->regs->dtsc = 1; /* go for it */ + ctlr->ntq++; + ctlr->tdrh = NEXT(ctlr->tdrh, ctlr->ntdre); + } +} + +static void +transmit(Ether* ether) +{ + Ctlr *ctlr; + + ctlr = ether->ctlr; + ilock(ctlr); + txstart(ether); + iunlock(ctlr); +} + +/* + * allocate receive buffer space on cache-line boundaries + */ +static Block* +clallocb(void) +{ + Block *b; + + b = iallocb(Bufsize+CACHELINESZ-1); + if(b == nil) + return b; + dcflush(b->base, BALLOC(b)); + b->wp = b->rp = (uchar*)(((ulong)b->base + CACHELINESZ - 1) & ~(CACHELINESZ-1)); + return b; +} + + +static void +rxring(Ureg*, void *arg) +{ + Ether *ether; + ulong status; + Ctlr *ctlr; + BD *dre; + Block *b, *rb; + + ether = arg; + ctlr = ether->ctlr; + ctlr->interrupts++; + + /* + * Receiver interrupt: run round the descriptor ring logging + * errors and passing valid receive data up to the higher levels + * until we encounter a descriptor still owned by the chip. + * We rely on the descriptor accesses being uncached. + */ + dre = &ctlr->rdr[ctlr->rdrx]; + while(((status = dre->ctrl) & BdBusy) == 0){ + if(status & RxES || (status & (RxFS|RxLS)) != (RxFS|RxLS)){ + if(status & (RxRF|RxTL)) + ether->buffs++; + if(status & RxRE) + ether->frames++; + if(status & RxCE) + ether->crcs++; + //if(status & RxOverrun) + // ether->overflows++; + iprint("eth rx: %lux\n", status); + }else{ + /* + * We have a packet. Read it in. + */ + b = clallocb(); + if(b != nil){ + rb = ctlr->rxb[ctlr->rdrx]; + rb->wp += (dre->ctrl & RxFL)-4; + etheriq(ether, rb, 1); + ctlr->rxb[ctlr->rdrx] = b; + dre->addr = PADDR(b->wp); + }else + ether->soverflows++; + } + + /* + * Finished with this descriptor, + * give it back to the chip, then on to the next... + */ + dre->ctrl = BdBusy; + + ctlr->rdrx = NEXT(ctlr->rdrx, ctlr->nrdre); + dre = &ctlr->rdr[ctlr->rdrx]; + } +} + +static void +txring(Ureg*, void *arg) +{ + Ether *ether; + Ctlr *ctlr; + BD *dre; + Block *b; + + ether = arg; + ctlr = ether->ctlr; + ctlr->interrupts++; + + /* + * Transmitter interrupt: handle anything queued for a free descriptor. + */ + lock(ctlr); + while(ctlr->ntq){ + dre = &ctlr->tdr[ctlr->tdri]; + if(dre->ctrl & BdBusy) + break; + /* statistics are kept inside the device, but only for LAN */ + /* there seems to be no per-packet error status for transmission */ + b = ctlr->txb[ctlr->tdri]; + if(b == nil) + panic("etherks8695: bufp"); + ctlr->txb[ctlr->tdri] = nil; + freeb(b); + ctlr->ntq--; + ctlr->tdri = NEXT(ctlr->tdri, ctlr->ntdre); + } + txstart(ether); + unlock(ctlr); +} + +/* + * receive buffer unavailable (overrun) + */ +static void +rbuintr(Ureg*, void *arg) +{ + Ether *ether; + Ctlr *ctlr; + + ether = arg; + ctlr = ether->ctlr; + + ctlr->interrupts++; + if(ctlr->active) + ctlr->overrun++; + ctlr->reading = 0; +} + +/* + * read process (in device) stopped + */ +static void +rxstopintr(Ureg*, void *arg) +{ + Ether *ether; + Ctlr *ctlr; + + ether = arg; + ctlr = ether->ctlr; + + ctlr->interrupts++; + if(!ctlr->active) + return; + +iprint("rxstopintr\n"); + ctlr->regs->drsc = 1; + /* just restart it? need to fiddle with ring? */ +} + +static void +txstopintr(Ureg*, void *arg) +{ + Ether *ether; + Ctlr *ctlr; + + ether = arg; + ctlr = ether->ctlr; + + ctlr->interrupts++; + if(!ctlr->active) + return; + +iprint("txstopintr\n"); + ctlr->regs->dtsc = 1; + /* just restart it? need to fiddle with ring? */ +} + + +static void +linkchangeintr(Ureg*, void*) +{ + iprint("link change\n"); +} + +static long +ifstat(Ether* ether, void* a, long n, ulong offset) +{ + char *p; + int len; + Ctlr *ctlr; + + if(n == 0) + return 0; + + ctlr = ether->ctlr; + + p = malloc(READSTR); + len = snprint(p, READSTR, "interrupts: %lud\n", ctlr->interrupts); + len += snprint(p+len, READSTR-len, "carrierlost: %lud\n", ctlr->carrierlost); + len += snprint(p+len, READSTR-len, "heartbeat: %lud\n", ctlr->heartbeat); + len += snprint(p+len, READSTR-len, "retrylimit: %lud\n", ctlr->retrylim); + len += snprint(p+len, READSTR-len, "retrycount: %lud\n", ctlr->retrycount); + len += snprint(p+len, READSTR-len, "latecollisions: %lud\n", ctlr->latecoll); + len += snprint(p+len, READSTR-len, "rxoverruns: %lud\n", ctlr->overrun); + len += snprint(p+len, READSTR-len, "txunderruns: %lud\n", ctlr->underrun); +{DmaReg *d = ctlr->regs; len += snprint(p+len, READSTR-len, "dtxc=%8.8lux drxc=%8.8lux\n", d->dtxc, d->drxc);} + snprint(p+len, READSTR-len, "framesdeferred: %lud\n", ctlr->deferred); + n = readstr(offset, a, n, p); + free(p); + + if(ctlr->port == 1) + switchdump(); + return n; +} + +static void +physinit(Ether *ether, int force) +{ + Ctlr *ctlr; + WanPhy *p; + ulong anap; + int i; + + ctlr = ether->ctlr; + p = ctlr->wphy; + if(p == nil){ + if(ctlr->port){ + ether->mbps = 100; + ether->fullduplex = 1; + switchinit(nil); + } + return; + } + iprint("phy%d: wmc=%8.8lux wpm=%8.8lux wpc=%8.8lux wps=%8.8lux pps=%8.8lux\n", ctlr->port, p->wmc, p->wppm, p->wpc, p->wps, p->pps); + + p->wppm = 0; /* enable power, other defaults seem fine */ + if(p->rid & 7) + p->wpc = 0x0200b000; /* magic */ + else + p->wpc = 0xb000; + if(p->wppm & WFef) + iprint("ether%d: far end fault\n", ctlr->port); + + if((p->wmc & WLs) == 0){ + iprint("ether%d: no link\n", ctlr->port); + ether->mbps = 100; /* could use 10, but this is 2005 */ + ether->fullduplex = 0; + return; + } + + if((p->wmc & WAnc) == 0 || force){ + p->wmc = WAnr | WAnaP | WAna100FD | WAna100HD | WAna10FD | WAna10HD | (p->wmc & 0x7F); + microdelay(10); + if(p->wmc & WLs){ + for(i=0;; i++){ + if(i > 600){ + iprint("ether%d: auto negotiation failed\n", ctlr->port); + ether->mbps = 10; /* we'll assume it's stupid */ + ether->fullduplex = 0; + return; + } + if(p->wmc & WAnc){ + microdelay(10); + break; + } + delay(1); + } + } + } + anap = p->wmc; + ether->mbps = anap & WSs? 100: 10; + if(anap & (WLpar100FD|WLpar10FD) && anap & WDs) + ether->fullduplex = 1; + else + ether->fullduplex = 0; + ctlr->anap = anap; + + iprint("ks8695%d mii: fd=%d speed=%d wmc=%8.8lux\n", ctlr->port, ether->fullduplex, ether->mbps, anap); +} + +static void +ctlrinit(Ctlr *ctlr, Ether *ether) +{ + int i; + DmaReg *em; + ulong mode; + + em = ctlr->regs; + + /* soft reset */ + em->dtxc = TxSoftReset; + microdelay(10); + for(i=0; em->dtxc & TxSoftReset; i++){ + if(i > 20){ + iprint("etherks8695.%d: soft reset failed\n", ctlr->port); + i=0; + } + microdelay(100); + } +iprint("%d: rx=%8.8lux tx=%8.8lux\n", ctlr->port, PADDR(ctlr->rdr), PADDR(ctlr->tdr)); + + physinit(ether, 0); + + /* set ether address */ + em->mah = (ether->ea[0]<<8) | ether->ea[1]; + em->mal = (ether->ea[2]<<24) | (ether->ea[3]<<16) | (ether->ea[4]<<8) | ether->ea[5]; + if(ctlr->port == 0){ + /* clear other addresses for now */ + for(i=0; i<nelem(em->maal); i++){ + em->maal[i][0] = 0; + em->maal[i][1] = 0; + } + } + + /* transmitter, enabled later by attach */ + em->tdlb = PADDR(ctlr->tdr); + em->dtxc = DMABURST(8) | TxFCE | TxCrc; /* don't set TxEP: there is a h/w bug and it's anyway done by higher levels */ + + /* receiver, enabled later by attach */ + em->rdlb = PADDR(ctlr->rdr); + mode = DMABURST(8) | RxRB | RxRU | RxAE; /* RxAE just there for testing */ + if(ether->fullduplex) + mode |= RxFCE; + em->drxc = mode; + + /* tx/rx enable is deferred until attach */ +} + +static int +reset(Ether* ether) +{ + uchar ea[Eaddrlen]; + char name[KNAMELEN]; + Ctlr *ctlr; + int i, irqdelta; + + snprint(name, sizeof(name), "ether%d", ether->ctlrno); + + /* + * Insist that the platform-specific code provide the Ethernet address + */ + memset(ea, 0, Eaddrlen); + if(memcmp(ea, ether->ea, Eaddrlen) == 0){ + print("%s (%s %ld): no ether address", name, ether->type, ether->port); + return -1; + } + + ctlr = malloc(sizeof(*ctlr)); + ctlr->port = ether->port; + + switch(ether->port){ + case 0: + ctlr->regs = KADDR(PHYSWANDMA); + ctlr->wphy = KADDR(PHYSMISC); + ctlr->wphy->wmc = (ctlr->wphy->wmc & ~0x7F) | (LedLinkTxRx<<0) | (LedSpeed<<4); + break; + case 1: + ctlr->regs = KADDR(PHYSLANDMA); + ctlr->wphy = nil; + break; + default: + print("%s: %s ether: no port %lud\n", name, ether->type, ether->port); + free(ctlr); + return -1; + } + + ether->ctlr = ctlr; + irqdelta = ether->irq - IRQwmrps; + + physinit(ether, 0); + + if(ioringinit(ctlr, Nrdre, Ntdre) < 0) + panic("etherks8695 initring"); + + for(i = 0; i < ctlr->nrdre; i++){ + if(ctlr->rxb[i] == nil) + ctlr->rxb[i] = clallocb(); + ctlr->rdr[i].addr = PADDR(ctlr->rxb[i]->wp); + ctlr->rdr[i].size = Rbsize; + ctlr->rdr[i].ctrl = BdBusy; + } + + ctlrinit(ctlr, ether); + + ether->attach = attach; + ether->closed = closed; + ether->transmit = transmit; + ether->ifstat = ifstat; + + /* there is more than one interrupt: we must enable some ourselves */ + ether->irq = irqdelta + IRQwmrs; /* set main IRQ to receive status */ + ether->interrupt = rxring; + intrenable(IRQ, irqdelta+IRQwmts, txring, ether, "ethertx"); +// intrenable(IRQ, irqdelta+IRQwmtbu, tbuintr, ether, "ethertbu"); /* don't care? */ + intrenable(IRQ, irqdelta+IRQwmrbu, rbuintr, ether, "etherrbu"); + intrenable(IRQ, irqdelta+IRQwmrps, rxstopintr, ether, "etherrps"); + intrenable(IRQ, irqdelta+IRQwmtps, txstopintr, ether, "ethertps"); + if(ether->port == 0) + intrenable(IRQ, IRQwmlc, linkchangeintr, ether, "etherwanlink"); + + ether->arg = ether; + ether->promiscuous = promiscuous; + ether->multicast = multicast; + + return 0; +} + +/* + * switch engine registers + * a 10 microsecond delay is required after each (write?) access + */ +typedef struct Switch Switch; +struct Switch { + ulong sec0; /* control register 0 */ + ulong sec1; /* control register 1 */ + ulong sec2; /* control register 2, factory default, do not change */ + ulong cfg[5][3]; /* port configuration registers */ + ulong an[2]; /* ports 1 to 4 auto negotiation [1,2][3,4] */ + ulong seiac; /* indirect access control register */ + ulong seiadh2; /* indirect access data register 2 (4:0 is 68-64 of data) */ + ulong seiadh1; /* indirect access data register 1 (63-32 of data) */ + ulong seiadl; /* indirect access data register low */ + ulong seafc; /* advanced feature control */ + ulong scph; /* services code priority high (ie, TOS priority) */ + ulong scpl; /* services code priority low */ + ulong mah; /* switch MAC address high */ + ulong mal; /* switch MAC address low */ + ulong ppm[2]; /* ports 1 to 4 PHY power management */ +}; + +enum { + /* Sec0 */ + Nbe= 1<<31, /* new backoff (designed for UNH) enable */ + /* 30:28 802.1p base priority */ + /* 27:25 LAN LED1 select */ + /* 24:22 LAN LED0 select */ + Unh= 1<<21, /* =1, drop packets with type 8808 or DA=0180c2000001; =0, drop flow control */ + Lca= 1<<20, /* link change age: faster aging for link->no link transition */ + Paf= 1<<19, /* pass all frames, including bad ones */ + Sfce= 1<<18, /* switch MII full-duplex flow control enable */ + Flfc= 1<<17, /* frame length field check in IEEE (drop invalid ones) */ + Bsm= 1<<16, /* =1, share all buffers; =0, use only 1/5 of pool */ + Age= 1<<15, /* enable age function */ + Agef= 1<<14, /* enable fast ageing */ + Aboe= 1<<13, /* aggressive backoff enable */ + Uvmd= 1<<12, /* unicast port-VLAN mismatch discard */ + Mspd= 1<<11, /* multicast storm protection disable */ + Bpm= 1<<10, /* =1, carrier sense backpressure; =0, collision backpressure */ + Fair= 1<<9, /* fair flow control and back pressure */ + Ncd= 1<<8, /* no excessive collision drop */ + Lmpsd= 1<<7, /* 1=, drop packet sizes over 1536 bytes; =0, 1522 for tagged, 1518 untagged */ + Pbr= 1<<6, /* priority buffer reserved */ + Sbpe= 1<<5, /* switch back pressure enable */ + Shdm= 1<<4, /* switch half duplex mode */ + PrioHi= 0<<2, /* always deliver high priority first */ + Prio10_1= 1<<2, /* high/low at 10:1 */ + Prio5_1= 2<<2, /* high/low at 5:1 */ + Prio2_1= 3<<2, /* high/low at 2:1 */ + Etm= 1<<1, /* enable tag mask */ + Esf= 1<<0, /* enable switch function */ + + /* sec1 */ + /* 31:21 */ /* broadcast storm protection, byte count */ + IEEEneg= 1<<11, /* follow IEEE spec for auto neg */ + Tpid= 1<<10, /* special TPID mode used for direct forwarding from port 5 */ + PhyEn= 1<<8, /* enable PHY MII */ + TfcDis= 1<<7, /* disable IEEE transmit flow control */ + RfcDis= 1<<6, /* disable IEEE receive flow control */ + Hps= 1<<5, /* huge packet support: allow packets up to 1916 bytes */ + VlanEn= 1<<4, /* 802.1Q VLAN enable; recommended when priority queue on */ + Sw10BT= 1<<1, /* switch in 10 Mbps mode not 100 Mbps */ + VIDrep= 1<<0, /* replace null VID with port VID (otherwise no replacement) */ + +}; +#define BASEPRIO(n) (((n)&7)<<28) + + +enum { + /* cfg[n][0] (SEP1C1-SEP4C1) p. 89 */ + /* 31:16 default tag: 31:29=userprio, 28=CFI bit, 27:16=VID[11:0] */ + AnegDis= 1<<15, /* disable auto negotiation */ + Force100= 1<<14, /* force 100BT when auto neg is disabled */ + ForceFD= 1<<13, /* force full duplex when auto neg is disabled */ + /* 12:8 port VLAN membership: bit 8 is port 1, bit 12 is port 5, 1=member */ + STTxEn= 1<<7, /* spanning tree transmit enable */ + STRxEn= 1<<6, /* spanning tree receive enable */ + STLnDis= 1<<5, /* spanning tree learn disnable */ + Bsp= 1<<4, /* enable broadcast storm protection */ + Pce= 1<<3, /* priority classification enable */ + Dpce= 1<<2, /* diffserv priority classification enable */ + IEEEpce= 1<<1, /* IEEE (802.1p) classification enable */ + PrioEn= 1<<0, /* enable priority function on port */ + + /* cfg[n][1] (SEP1C2-SEP4C2) p. 91*/ + IngressFilter= 1<<28, /* discard packets from ingress port not in VLAN */ + DiscardNonPVID= 1<<27, /* discard packets whose VID does not match port default VID */ + ForcePortFC= 1<<26, /* force flow control */ + EnablePortBP= 1<<25, /* enable back pressure */ + /* 23:12 transmit high priority rate control */ + /* 11:0 transmit low priority rate control */ + + /* cfg[n][2] */ + /* 13:20 receive high priority rate control */ + /* 19:8 receive low priority rate control */ + Rdprc= 1<<7, /* receive differential priority rate control */ + Lprrc= 1<<6, /* low priority receive rate control */ + Hprrc= 1<<5, /* high priority receive rate control */ + Lprfce= 1<<4, /* low priority receive flow control enable */ + Hprfce= 1<<3, /* high priority ... */ + Tdprc= 1<<2, /* transmit differential priority rate control */ + Lptrc= 1<<1, /* low priority transmit rate control */ + Hptrc= 1<<0, /* high priority transmit rate control */ + + /* seiac */ + Cread= 1<<12, + Cwrite= 0<<12, + StaticMacs= 0<<10, /* static mac address table used */ + VLANs= 1<<10, /* VLAN table */ + DynMacs= 2<<10, /* dynamic address table */ + MibCounter= 3<<10, /* MIB counter selected */ + /* 0:9, table index */ + + /* seafc */ + /* 26:22 1<<(n+22-1) = removal for port 0 to 4 */ +}; + +/* + * indirect access to + * static MAC address table (3.10.23, p. 107) + * VLAN table (3.10.24, p. 108) + * dynamic MAC address table (3.10.25, p. 109) + * MIB counters (3.10.26, p. 110) + */ +enum { + /* VLAN table */ + VlanValid= 1<<21, /* entry is valid */ + /* 20:16 are bits for VLAN membership */ + /* 15:12 are bits for FID (filter id) for up to 16 active VLANs */ + /* 11:0 has 802.1Q 12 bit VLAN ID */ + + /* Dynamic MAC table (1024 entries) */ + MACempty= 1<<(68-2*32), + /* 67:58 is number of valid entries-1 */ + /* 57:56 ageing time stamp */ + NotReady= 1<<(55-32), + /* 54:52 source port 0 to 5 */ + /* 51:48 FID */ + /* 47:0 MAC */ + + NVlans= 16, + NSMacs= 8, +}; + +/* + * per-port counters, table 3, 3.10.26, p. 110 + * cleared when read + * port counters at n*0x20 [n=0-3] + */ +static char* portmibnames[] = { + "RxLoPriorityByte", + "RxHiPriorityByte", + "RxUndersizePkt", + "RxFragments", + "RxOversize", + "RxJabbers", + "RxSymbolError", + "RxCRCerror", + "RxAlignmentError", + "RxControl8808Pkts", + "RxPausePkts", + "RxBroadcast", + "RxMulticast", + "RxUnicast", + "Rx64Octets", + "Rx65to127Octets", + "Rx128to255Octets", + "Rx256to511Octets", + "Rx512to1023Octets", + "Rx1024to1522Octets", + "TxLoPriorityByte", + "TxHiPriorityByte", + "TxLateCollision", + "TxPausePkts", + "TxBroadcastPkts", + "TxMulticastPkts", + "TxUnicastPkts", + "TxDeferred", + "TxTotalCollision", /* like, totally */ + "TxExcessiveCollision", + "TxSingleCollision", + "TxMultipleCollision", +}; +enum { + /* per-port MIB counter format */ + MibOverflow= 1<<31, + MibValid= 1<<30, + /* 29:0 counter value */ +}; + +/* + * 16 bit `all port' counters, not automatically cleared + * offset 0x100 and up + */ + +static char* allportnames[] = { + "Port1TxDropPackets", + "Port2TxDropPackets", + "Port3TxDropPackets", + "Port4TxDropPackets", + "LanTxDropPackets", /* ie, internal port 5 */ + "Port1RxDropPackets", + "Port2RxDropPackets", + "Port3RxDropPackets", + "Port4RxDropPackets", + "LanRxDropPackets", +}; + +static void +switchinit(uchar *ea) +{ + Switch *sw; + int i; + ulong an; + + /* TO DO: LED gpio setting */ + + GPIOREG->iopm |= 0xF0; /* bits 4-7 are LAN(?) */ +iprint("switch init...\n"); + sw = KADDR(PHYSSWITCH); + if(sw->sec0 & Esf){ + iprint("already inited\n"); + return; + } + sw->seafc = 0; + microdelay(10); + sw->scph = 0; + microdelay(10); + sw->scpl = 0; + microdelay(10); + if(ea != nil){ + sw->mah = (ea[0]<<8) | ea[1]; + microdelay(10); + sw->mal = (ea[2]<<24) | (ea[3]<<16) | (ea[4]<<8) | ea[5]; + microdelay(10); + } + for(i = 0; i < 5; i++){ + sw->cfg[i][0] = (0x1F<<8) | STTxEn | STRxEn | Bsp; /* port is member of all vlans */ + microdelay(10); + sw->cfg[i][1] = 0; + microdelay(10); + sw->cfg[i][2] = 0; + microdelay(10); + } + sw->ppm[0] = 0; /* perhaps soft reset? */ + microdelay(10); + sw->ppm[1] = 0; + microdelay(10); + an = WAnr | WAnaP | WAna100FD | WAna100HD | WAna10FD | WAna10HD; + sw->an[0] = an | (an >> 16); + microdelay(10); + sw->an[1] = an | (an >> 16); + microdelay(10); + sw->sec1 = (0x4A<<21) | PhyEn; + microdelay(10); + sw->sec0 = Nbe | (0<<28) | (LedSpeed<<25) | (LedLinkTxRx<<22) | Sfce | Bsm | Age | Aboe | Bpm | Fair | Sbpe | Shdm | Esf; + microdelay(10); + + /* off we go */ +} + +typedef struct Vidmap Vidmap; +struct Vidmap { + uchar ports; /* bit mask for ports 0 to 4 */ + uchar fid; /* switch's filter id */ + ushort vid; /* 802.1Q vlan id; 0=not valid */ +}; + +static Vidmap +getvidmap(Switch *sw, int i) +{ + ulong w; + Vidmap v; + + v.ports = 0; + v.fid = 0; + v.vid = 0; + if(i < 0 || i >= NVlans) + return v; + sw->seiac = Cread | VLANs | i; + microdelay(10); + w = sw->seiadl; + if((w & VlanValid) == 0) + return v; + v.vid = w & 0xFFFF; + v.fid = (w>>12) & 0xF; + v.ports = (w>>16) & 0x1F; + return v; +} + +static void +putvidmap(Switch *sw, int i, Vidmap v) +{ + ulong w; + + w = ((v.ports & 0x1F)<<16) | ((v.fid & 0xF)<<12) | (v.vid & 0xFFFF); + if(v.vid != 0) + w |= VlanValid; + sw->seiadl = w; + microdelay(10); + sw->seiac = Cwrite | VLANs | i; + microdelay(10); +} + +typedef struct StaticMac StaticMac; +struct StaticMac { + uchar valid; + uchar fid; + uchar usefid; + uchar override; /* override spanning tree tx/rx disable */ + uchar ports; /* forward to this set of ports */ + uchar mac[Eaddrlen]; +}; + +static StaticMac +getstaticmac(Switch *sw, int i) +{ + StaticMac s; + ulong w; + + memset(&s, 0, sizeof(s)); + if(i < 0 || i >= NSMacs) + return s; + sw->seiac = Cread | StaticMacs | i; + microdelay(10); + w = sw->seiadh1; + if((w & (1<<(53-32))) == 0) + return s; /* entry not valid */ + s.valid = 1; + s.fid= (w>>(57-32)) & 0xF; + s.usefid = (w & (1<<(56-32))) != 0; + s.override = (w & (1<<(54-32))) != 0; + s.ports = (w>>(48-32)) & 0x1F; + s.mac[5] = w >> 8; + s.mac[4] = w; + w = sw->seiadl; + s.mac[3] = w>>24; + s.mac[2] = w>>16; + s.mac[1] = w>>8; + s.mac[0] = w; + return s; +} + +static void +putstaticmac(Switch *sw, int i, StaticMac s) +{ + ulong w; + + if(s.valid){ + w = 1<<(53-32); /* entry valid */ + if(s.usefid) + w |= 1<<(55-32); + if(s.override) + w |= 1<<(54-32); + w |= (s.fid & 0xF) << (56-32); + w |= (s.ports & 0x1F) << (48-32); + w |= (s.mac[5] << 8) | s.mac[4]; + sw->seiadh1 = w; + microdelay(10); + w = (s.mac[3]<<24) | (s.mac[2]<<16) | (s.mac[1]<<8) | s.mac[0]; + sw->seiadl = w; + microdelay(10); + }else{ + sw->seiadh1 = 0; /* valid bit is 0; rest doesn't matter */ + microdelay(10); + } + sw->seiac = Cwrite | StaticMacs | i; + microdelay(10); +} + +typedef struct DynMac DynMac; +struct DynMac { + ushort nentry; + uchar valid; + uchar age; + uchar port; /* source port (0 origin) */ + uchar fid; /* filter id */ + uchar mac[Eaddrlen]; +}; + +static DynMac +getdynmac(Switch *sw, int i) +{ + DynMac d; + ulong w; + int n, l; + + memset(&d, 0, sizeof d); + l = 0; + do{ + if(++l > 100) + return d; + sw->seiac = Cread | DynMacs | i; + microdelay(10); + w = sw->seiadh2; + /* peculiar encoding of table size */ + if(w & MACempty) + return d; + n = w & 0xF; + w = sw->seiadh1; + }while(w & NotReady); /* TO DO: how long might it delay? */ + d.nentry = ((n<<6) | (w>>(58-32))) + 1; + if(i < 0 || i >= d.nentry) + return d; + d.valid = 1; + d.age = (w>>(56-32)) & 3; + d.port = (w>>(52-32)) & 7; + d.fid = (w>>(48-32)) & 0xF; + d.mac[5] = w>>8; + d.mac[4] = w; + w = sw->seiadl; + d.mac[3] = w>>24; + d.mac[2] = w>>16; + d.mac[1] = w>>8; + d.mac[0] = w; + return d; +} + +static void +switchdump(void) +{ + Switch *sw; + int i, j; + ulong w; + + sw = KADDR(PHYSSWITCH); + iprint("sec0 %8.8lux\n", sw->sec0); + iprint("sec1 %8.8lux\n", sw->sec1); + for(i = 0; i < 5; i++){ + iprint("cfg%d", i); + for(j = 0; j < 3; j++){ + w = sw->cfg[i][j]; + iprint(" %8.8lux", w); + } + iprint("\n"); + if(i < 2){ + w = sw->an[i]; + iprint(" an=%8.8lux pm=%8.8lux\n", w, sw->ppm[i]); + } + } + for(i = 0; i < 8; i++){ + sw->seiac = Cread | DynMacs | i; + microdelay(10); + w = sw->seiadh2; + microdelay(10); + iprint("dyn%d: %8.8lux", i, w); + w = sw->seiadh1; + microdelay(10); + iprint(" %8.8lux", w); + w = sw->seiadl; + microdelay(10); + iprint(" %8.8lux\n", w); + } + for(i=0; i<0x20; i++){ + sw->seiac = Cread | MibCounter | i; + microdelay(10); + w = sw->seiadl; + microdelay(10); + if(w & (1<<30)) + iprint("%.2ux: %s: %lud\n", i, portmibnames[i], w & ~(3<<30)); + } +} + +static void +switchstatproc(void*) +{ + for(;;){ + tsleep(&up->sleep, return0, nil, 30*1000); + } +} + +void +etherks8695link(void) +{ + addethercard("ks8695", reset); +} + +/* + * notes: + * switch control + * read stats every 30 seconds or so + */ |
