#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "io.h" #include "../port/netif.h" #include "etherif.h" #define Rbsz ROUNDUP(sizeof(Etherpkt)+16, 64) enum { RXRING = 0x200, TXRING = 0x200, Linkdelay = 500, MDC_DIV = 6, }; enum { NET_CTRL, NET_CFG, NET_STATUS, DMA_CFG = 4, TX_STATUS, RX_QBAR, TX_QBAR, RX_STATUS, INTR_STATUS, INTR_EN, INTR_DIS, INTR_MASK, PHY_MAINT, RX_PAUSEQ, TX_PAUSEQ, HASH_BOT = 32, HASH_TOP, SPEC_ADDR1_BOT, SPEC_ADDR1_TOP, }; enum { MDCTRL, MDSTATUS, MDID1, MDID2, MDAUTOADV, MDAUTOPART, MDAUTOEX, MDAUTONEXT, MDAUTOLINK, MDGCTRL, MDGSTATUS, MDPHYCTRL = 0x1f, }; enum { /* NET_CTRL */ RXEN = 1<<2, TXEN = 1<<3, MDEN = 1<<4, STARTTX = 1<<9, /* NET_CFG */ SPEED = 1<<0, FDEN = 1<<1, RX1536EN = 1<<8, GIGE_EN = 1<<10, RXCHKSUMEN = 1<<24, /* NET_STATUS */ PHY_IDLE = 1<<2, /* DMA_CFG */ TXCHKSUMEN = 1<<11, /* TX_STATUS */ TXCOMPL = 1<<5, /* INTR_{EN,DIS} */ MGMTDONE = 1<<0, RXCOMPL = 1<<1, RXUSED = 1<<2, TXUNDER = 1<<4, RXOVER = 1<<10, /* MDCTRL */ MDRESET = 1<<15, AUTONEG = 1<<12, FULLDUP = 1<<8, /* MDSTATUS */ LINK = 1<<2, /* MDGSTATUS */ RECVOK = 3<<12, }; enum { RxUsed = 1, TxUsed = 1<<31, FrameEnd = 1<<15, }; typedef struct Ctlr Ctlr; struct Ctlr { ulong *r; Rendez phy; int phyaddr; int rxconsi, rxprodi, txi; ulong *rxr, *txr; Block **rxs, **txs; Lock txlock; int attach; }; static int phyidle(void *v) { return ((Ctlr*)v)->r[NET_STATUS] & PHY_IDLE; } static void mdwrite(Ctlr *c, int r, u16int v) { sleep(&c->phy, phyidle, c); c->r[PHY_MAINT] = 1<<30 | 1<<28 | 1<<17 | c->phyaddr << 23 | r << 18 | v; sleep(&c->phy, phyidle, c); } static u16int mdread(Ctlr *c, int r) { sleep(&c->phy, phyidle, c); c->r[PHY_MAINT] = 1<<30 | 1<< 29 | 1<<17 | c->phyaddr << 23 | r << 18; sleep(&c->phy, phyidle, c); return c->r[PHY_MAINT]; } static void ethproc(void *ved) { Ether *edev; Ctlr *c; char *sp, *dpl; u16int v; edev = ved; c = edev->ctlr; mdwrite(c, MDCTRL, AUTONEG); for(;;){ if((mdread(c, MDSTATUS) & LINK) == 0){ edev->link = 0; print("eth: no link\n"); while((mdread(c, MDSTATUS) & LINK) == 0) tsleep(&up->sleep, return0, nil, Linkdelay); } v = mdread(c, MDPHYCTRL); if((v & 0x40) != 0){ sp = "1000BASE-T"; while((mdread(c, MDGSTATUS) & RECVOK) != RECVOK) ; edev->mbps = 1000; c->r[NET_CFG] |= GIGE_EN; }else if((v & 0x20) != 0){ sp = "100BASE-TX"; edev->mbps = 100; c->r[NET_CFG] = NET_CFG & ~GIGE_EN | SPEED; }else if((v & 0x10) != 0){ sp = "10BASE-T"; edev->mbps = 10; c->r[NET_CFG] = NET_CFG & ~(GIGE_EN | SPEED); }else sp = "???"; if((v & 0x08) != 0){ dpl = "full"; c->r[NET_CFG] |= FDEN; }else{ dpl = "half"; c->r[NET_CFG] &= ~FDEN; } edev->link = 1; print("eth: %s %s duplex link\n", sp, dpl); while((mdread(c, MDSTATUS) & LINK) != 0) tsleep(&up->sleep, return0, nil, Linkdelay); } } static int replenish(Ctlr *c) { Block *bp; int i; ulong *r; while(c->rxprodi != c->rxconsi){ i = c->rxprodi; bp = iallocb(Rbsz); if(bp == nil){ print("eth: out of memory for receive buffers\n"); return -1; } c->rxs[i] = bp; r = &c->rxr[2 * i]; r[0] = RxUsed | PADDR(bp->rp); if(i == RXRING - 1) r[0] |= 2; r[1] = 0; cleandse(bp->base, bp->lim); clean2pa(PADDR(bp->base), PADDR(bp->lim)); r[0] &= ~RxUsed; c->rxprodi = (c->rxprodi + 1) & (RXRING - 1); } return 0; } static void ethrx(Ether *edev) { Ctlr *c; ulong *r; Block *bp; c = edev->ctlr; // print("rx! %p %p\n", PADDR(&c->rxr[2 * c->rxconsi]), c->r[RX_QBAR]); for(;;){ r = &c->rxr[2 * c->rxconsi]; if((r[0] & RxUsed) == 0) break; if((r[1] & FrameEnd) == 0) print("eth: partial frame received -- shouldn't happen\n"); bp = c->rxs[c->rxconsi]; bp->wp = bp->rp + (r[1] & 0x1fff); invaldse(bp->rp, bp->wp); inval2pa(PADDR(bp->rp), PADDR(bp->wp)); etheriq(edev, bp, 1); c->rxconsi = (c->rxconsi + 1) & (RXRING - 1); replenish(c); } } static void ethtx(Ether *edev) { Ctlr *c; ulong *r; Block *bp; c = edev->ctlr; ilock(&c->txlock); for(;;){ r = &c->txr[2 * c->txi]; if((r[1] & TxUsed) == 0){ print("eth: transmit buffer full\n"); break; } bp = qget(edev->oq); if(bp == nil) break; if(c->txs[c->txi] != nil) freeb(c->txs[c->txi]); c->txs[c->txi] = bp; cleandse(bp->rp, bp->wp); clean2pa(PADDR(bp->rp), PADDR(bp->wp)); r[0] = PADDR(bp->rp); r[1] = BLEN(bp) | FrameEnd | TxUsed; if(r == c->txr + 2 * (TXRING - 1)) r[1] |= 1<<30; coherence(); r[1] &= ~TxUsed; coherence(); c->r[NET_CTRL] |= STARTTX; c->txi = (c->txi + 1) & (TXRING - 1); } iunlock(&c->txlock); } static void ethirq(Ureg *, void *arg) { Ether *edev; Ctlr *c; ulong fl; edev = arg; c = edev->ctlr; fl = c->r[INTR_STATUS]; c->r[INTR_STATUS] = fl; if((fl & MGMTDONE) != 0) wakeup(&c->phy); if((fl & TXUNDER) != 0) ethtx(edev); if((fl & RXCOMPL) != 0) ethrx(edev); if((fl & RXUSED) != 0) print("eth: DMA read RX descriptor with used bit set, shouldn't happen\n"); if((fl & RXOVER) != 0) print("eth: RX overrun, shouldn't happen\n"); } static int ethinit(Ether *edev) { Ctlr *c; int i; c = edev->ctlr; c->r[NET_CTRL] = 0; c->r[RX_STATUS] = 0xf; c->r[TX_STATUS] = 0xff; c->r[INTR_DIS] = 0x7FFFEFF; c->r[NET_CFG] = MDC_DIV << 18 | FDEN | SPEED | RX1536EN | GIGE_EN | RXCHKSUMEN; c->r[SPEC_ADDR1_BOT] = edev->ea[0] | edev->ea[1] << 8 | edev->ea[2] << 16 | edev->ea[3] << 24; c->r[SPEC_ADDR1_TOP] = edev->ea[4] | edev->ea[5] << 8; c->r[DMA_CFG] = TXCHKSUMEN | (Rbsz/64) << 16 | 1 << 10 | 3 << 8 | 0x10; c->rxr = ucalloc(8 * RXRING); c->txr = ucalloc(8 * TXRING); c->rxs = xspanalloc(4 * RXRING, 4, 0); c->txs = xspanalloc(4 * TXRING, 4, 0); for(i = 0; i < 2 * RXRING; ){ c->rxr[i++] = 1; c->rxr[i++] = 0; } c->rxconsi = 1; replenish(c); c->rxconsi = 0; replenish(c); for(i = 0; i < 2 * TXRING; ){ c->txr[i++] = 0; c->txr[i++] = 1<<31; } c->txr[2 * (TXRING - 1)] |= 1<<30; c->r[RX_QBAR] = PADDR(c->rxr); c->r[TX_QBAR] = PADDR(c->txr); c->r[NET_CTRL] = MDEN | TXEN | RXEN; c->r[INTR_EN] = MGMTDONE | TXUNDER | RXCOMPL | RXUSED | RXOVER; return 0; } static void ethattach(Ether *edev) { Ctlr *c; c = edev->ctlr; if(c->attach) return; c->attach = 1; kproc("ethproc", ethproc, edev); } static int etherpnp(Ether *edev) { static Ctlr ct; static uchar mac[] = {0x0e, 0xa7, 0xde, 0xad, 0xbe, 0xef}; if(ct.r != nil) return -1; memmove(edev->ea, mac, 6); edev->ctlr = &ct; edev->port = ETH0_BASE; ct.r = vmap(edev->port, BY2PG); edev->irq = ETH0IRQ; edev->irqlevel = LEVEL; edev->ctlr = &ct; edev->interrupt = ethirq; edev->transmit = ethtx; edev->attach = ethattach; edev->arg = edev; edev->mbps = 1000; if(ethinit(edev) < 0){ edev->ctlr = nil; return -1; } return 0; } void etherzynqlink(void) { addethercard("eth", etherpnp); }