summaryrefslogtreecommitdiff
path: root/os/manga/etherks8695.c
diff options
context:
space:
mode:
Diffstat (limited to 'os/manga/etherks8695.c')
-rw-r--r--os/manga/etherks8695.c1169
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
+ */