The latest revision of the VIA Velocity Gigabit Ethernet driver. It has been tested on a 100Mbps Full duplex network and seems to work quite well. Reference: /n/sources/patch/applied/ethervgbe Date: Tue Jan 24 10:21:10 CET 2006 --- /sys/src/9/pc/ethervgbe.c Thu Jan 1 00:00:00 1970 +++ /sys/src/9/pc/ethervgbe.c Tue Jan 24 10:19:25 2006 @@ -0,0 +1,1135 @@ +/* + * VIA Velocity gigabit ethernet. + * Register info has been stolen from FreeBSD driver. + * + * Has been tested on: + * - VIA8237 (ABIT AV8): 100Mpbs Full duplex only. + * It works enough to run replica/pull, vncv, ... + * + * To do: + * - 64/48 bits + * - autonegotiation + * - thresholds + * - dynamic ring sizing ?? + * - link status change + * - multicast + * - promiscuous + * - report error + * - Rx/Tx Csum + * - Jumbo frames + * + * Philippe Anel, xigh@free.fr + */ + +#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 "ethermii.h" + +#define DEBUG + +enum +{ + DumpIntr = (1<<0), + DumpRx = (1<<1), + DumpTx = (1<<2), +}; + +#define htole16(x) (x) +#define htole32(x) (x) +#define le32toh(x) (x) + +enum +{ + Timeout = 50000, + RxCount = 256, + TxCount = 256, + RxSize = 2048, + + EthAddr = 0x00, + + /* Command registers. */ + Cr0S = 0x08, /* Global command 0 (Set) */ + Cr0C = 0x0c, /* Global command 0 (Clear) */ + Cr0_Start = 0x01, /* - start MAC */ + Cr0_Stop = 0x02, /* - stop MAC */ + Cr0_EnableRx = 0x04, /* - turn on Rx engine */ + Cr0_EnableTx = 0x08, /* - turn on Tx engine */ + + Cr1S = 0x09, /* Global command 1 (Set) */ + Cr1C = 0x0d, /* Global command 1 (Clear) */ + Cr1_NoPool = 0x08, /* - disable Rx/Tx desc pool */ + Cr1_reset = 0x80, /* - software reset */ + + Cr2S = 0x0a, /* Global command 2 (Set) */ + Cr2_XonEnable = 0x80, /* - 802.3x XON/XOFF flow control */ + + Cr3S = 0x0b, /* Global command 3 (Set) */ + Cr3C = 0x0f, /* Global command 3 (Set) */ + Cr3_IntMask = 0x02, /* - Mask all interrupts */ + + /* Eeprom registers. */ + Eecsr = 0x93, /* EEPROM control/status */ + Eecsr_Autold = 0x20, /* - trigger reload from EEPROM */ + + /* Mii registers. */ + MiiStatus = 0x6D, /* MII port status */ + MiiStatus_idle = 0x80, /* - idle */ + + MiiCmd = 0x70, /* MII command */ + MiiCmd_write = 0x20, /* - write */ + MiiCmd_read = 0x40, /* - read */ + MiiCmd_auto = 0x80, /* - enable autopolling */ + + MiiAddr = 0x71, /* MII address */ + MiiData = 0x72, /* MII data */ + + /* 64 bits related registers. */ + TxDescHi = 0x18, + DataBufHi = 0x1d, + + /* Rx engine registers. */ + RxDescLo = 0x38, /* Rx descriptor base address (lo 32 bits) */ + RxCsrS = 0x32, /* Rx descriptor queue control/status (Set) */ + RxCsrC = 0x36, /* Rx descriptor queue control/status (Clear) */ + RxCsr_RunQueue = 0x01, /* - enable queue */ + RxCsr_Active = 0x02, /* - queue active indicator */ + RxCsr_Wakeup = 0x04, /* - wake up queue */ + RxCsr_Dead = 0x08, /* - queue dead indicator */ + RxNum = 0x50, /* Size of Rx desc ring */ + RxDscIdx = 0x3c, /* Current Rx descriptor index */ + RxResCnt = 0x5e, /* Rx descriptor residue count */ + RxHostErr = 0x23, /* Rx host error status */ + RxTimer = 0x3e, /* Rx queue timer pend */ + RxControl = 0x06, /* MAC Rx control */ + RxControl_BadFrame = 0x01, /* - accept CRC error frames */ + RxControl_Runt = 0x02, /* - accept runts */ + RxControl_MultiCast = 0x04, /* - accept multicasts */ + RxControl_BroadCast = 0x08, /* - accept broadcasts */ + RxControl_Promisc = 0x10, /* - promisc mode */ + RxControl_Giant = 0x20, /* - accept VLAN tagged frames */ + RxControl_UniCast = 0x40, /* - use perfect filtering */ + RxControl_SymbolErr = 0x80, /* - accept symbol err packet */ + RxConfig = 0x7e, /* MAC Rx config */ + RxConfig_VlanFilter = 0x01, /* - filter VLAN ID mismatches */ + RxConfig_VlanOpt0 = (0<<1), /* - TX: no tag insert, RX: all, no extr */ + RxConfig_VlanOpt1 = (1<<1), /* - TX: no tag insert, RX: tagged pkts, no extr */ + RxConfig_VlanOpt2 = (2<<1), /* - TX: tag insert, RX: all, extract tags */ + RxConfig_VlanOpt3 = (3<<1), /* - TX: tag insert, RX: tagged pkts, with extr */ + RxConfig_FifoLowWat = 0x08, /* - RX FIFO low watermark (7QW/15QW) */ + RxConfig_FifoTh128 = (0<<4), /* - RX FIFO threshold 128 bytes */ + RxConfig_FifoTh512 = (1<<4), /* - RX FIFO threshold 512 bytes */ + RxConfig_FifoTh1024 = (2<<4), /* - RX FIFO threshold 1024 bytes */ + RxConfig_FifoThFwd = (3<<4), /* - RX FIFO threshold ??? */ + RxConfig_ArbPrio = 0x80, /* - arbitration priority */ + + /* Tx engine registers. */ + TxDescLo = 0x40, /* Tx descriptor base address (lo 32 bits) */ + TxCsrS = 0x30, /* Tx descriptor queue control/status (Set) */ + TxCsrC = 0x38, /* Tx descriptor queue control/status (Clear) */ + TxCsr_RunQueue = 0x01, /* - enable queue */ + TxCsr_Active = 0x02, /* - queue active indicator */ + TxCsr_Wakeup = 0x04, /* - wake up queue */ + TxCsr_Dead = 0x08, /* - queue dead indicator */ + TxNum = 0x52, /* Size of Tx desc ring */ + TxDscIdx = 0x54, /* Current Tx descriptor index */ + TxHostErr = 0x22, /* Tx host error status */ + TxTimer = 0x3f, /* Tx queue timer pend */ + TxControl = 0x07, /* MAC Rx control */ + TxControl_LC_Off = (0<<0), /* - loopback control off */ + TxControl_LC_Mac = (1<<0), /* - loopback control MAC internal */ + TxControl_LC_Ext = (2<<0), /* - loopback control external */ + TxControl_Coll16 = (0<<2), /* - one set of 16 retries */ + TxControl_Coll32 = (1<<2), /* - two sets of 16 retries */ + TxControl_Coll48 = (2<<2), /* - three sets of 16 retries */ + TxControl_CollInf = (3<<2), /* - retry forever */ + + TxConfig = 0x7f, /* MAC Tx config */ + TxConfig_SnapOpt = 0x01, /* - 1 == insert VLAN tag at 13th byte, */ + /* 0 == insert VLAN tag after SNAP header (21st byte) */ + TxConfig_NonBlk = 0x02, /* - priority TX/non-blocking mode */ + TxConfig_Blk64 = (0<<3), /* - non-blocking threshold 64 packets */ + TxConfig_Blk32 = (1<<3), /* - non-blocking threshold 32 packets */ + TxConfig_Blk128 = (2<<3), /* - non-blocking threshold 128 packets */ + TxConfig_Blk8 = (3<<3), /* - non-blocking threshold 8 packets */ + TxConfig_ArbPrio = 0x80, /* - arbitration priority */ + + /* Timer registers. */ + Timer0 = 0x74, /* single-shot timer */ + Timer1 = 0x76, /* periodic timer */ + + /* Chip config registers. */ + ChipCfgA = 0x78, /* chip config A */ + ChipCfgB = 0x79, /* chip config B */ + ChipCfgC = 0x7a, /* chip config C */ + ChipCfgD = 0x7b, /* chip config D */ + + /* DMA config registers. */ + DmaCfg0 = 0x7C, /* DMA config 0 */ + DmaCfg1 = 0x7D, /* DMA config 1 */ + + /* Interrupt registers. */ + IntCtl = 0x20, /* Interrupt control */ + Imr = 0x28, /* Interrupt mask */ + Isr = 0x24, /* Interrupt status */ + Isr_RxHiPrio = (1<<0), /* - hi prio Rx int */ + Isr_TxHiPrio = (1<<1), /* - hi prio Tx int */ + Isr_RxComplete = (1<<2), /* - Rx queue completed */ + Isr_TxComplete = (1<<3), /* - One of Tx queues completed */ + + Isr_TxComplete0 = (1<<4), /* - Tx queue 0 completed */ + Isr_TxComplete1 = (1<<5), /* - Tx queue 1 completed */ + Isr_TxComplete2 = (1<<6), /* - Tx queue 2 completed */ + Isr_TxComplete3 = (1<<7), /* - Tx queue 3 completed */ + + Isr_Reserved8 = (1<<8), /* - reserved */ + Isr_Reserver9 = (1<<9), /* - reserved */ + Isr_RxCountOvflow = (1<<10), /* - Rx packet count overflow */ + Isr_RxPause = (1<<11), /* - pause frame Rx */ + + Isr_RxFifoOvflow = (1<<12), /* - RX FIFO overflow */ + Isr_RxNoDesc = (1<<13), /* - ran out of Rx descriptors */ + Isr_RxNoDescWar = (1<<14), /* - running out of Rx descriptors */ + Isr_LinkStatus = (1<<15), /* - link status change */ + + Isr_Timer0 = (1<<16), /* - one shot timer expired */ + Isr_Timer1 = (1<<17), /* - periodic timer expired */ + Isr_Power = (1<<18), /* - wake up power event */ + Isr_PhyIntr = (1<<19), /* - PHY interrupt */ + + Isr_Stopped = (1<<20), /* - software shutdown complete */ + Isr_MibOvflow = (1<<21), /* - MIB counter overflow warning */ + Isr_SoftIntr = (1<<22), /* - software interrupt */ + Isr_HoldOffReload = (1<<23), /* - reload hold timer */ + + Isr_RxDmaStall = (1<<24), /* - Rx DMA stall */ + Isr_TxDmaStall = (1<<25), /* - Tx DMA stall */ + Isr_Reserved26 = (1<<26), /* - reserved */ + Isr_Reserved27 = (1<<27), /* - reserved */ + + Isr_Source0 = (1<<28), /* - interrupt source indication */ + Isr_Source1 = (1<<29), /* - interrupt source indication */ + Isr_Source2 = (1<<30), /* - interrupt source indication */ + Isr_Source3 = (1<<31), /* - interrupt source indication */ + + Isr_Mask = Isr_TxComplete0|Isr_RxComplete|Isr_Stopped| + Isr_RxFifoOvflow|Isr_PhyIntr|Isr_LinkStatus| + Isr_RxNoDesc|Isr_RxDmaStall|Isr_TxDmaStall +}; + +typedef struct Frag Frag; +struct Frag +{ + ulong addr_lo; + ushort addr_hi; + ushort length; +}; + +typedef struct RxDesc RxDesc; +struct RxDesc +{ + ulong status; + ulong control; + Frag; +}; + +typedef struct TxDesc TxDesc; +struct TxDesc +{ + ulong status; + ulong control; + Frag frags[7]; +}; + +enum +{ + RxDesc_Status_VidMiss = (1<<0), /* VLAN tag filter miss */ + RxDesc_Status_CrcErr = (1<<1), /* bad CRC error */ + RxDesc_Status_FrAlErr = (1<<3), /* frame alignment error */ + RxDesc_Status_CsumErr = (1<<3), /* bad TCP/IP checksum */ + RxDesc_Status_RxLenErr = (1<<4), /* Rx length error */ + RxDesc_Status_SymErr = (1<<5), /* PCS symbol error */ + RxDesc_Status_SnTag = (1<<6), /* RX'ed tagged SNAP pkt */ + RxDesc_Status_DeTag = (1<<7), /* VLAN tag extracted */ + + RxDesc_Status_OneFrag = (0<<8), /* only one fragment */ + RxDesc_Status_FirstFrag = (1<<8), /* first frag in frame */ + RxDesc_Status_LastFrag = (2<<8), /* last frag in frame */ + RxDesc_Status_MidFrag = (3<<8), /* intermediate frag */ + + RxDesc_Status_Vtag = (1<<10), /* VLAN tag indicator */ + RxDesc_Status_UniCast = (1<<11), /* unicast frame */ + RxDesc_Status_BroadCast = (1<<12), /* broadcast frame */ + RxDesc_Status_MultiCast = (1<<13), /* multicast frame */ + RxDesc_Status_Perfect = (1<<14), /* perfect filter hit */ + RxDesc_Status_Goodframe = (1<<15), /* frame is good. */ + + RxDesc_Status_SizShift = 16, /* received frame len shift */ + RxDesc_Status_SizMask = 0x3FFF, /* received frame len mask */ + + RxDesc_Status_Shutdown = (1<<30), /* shutdown during RX */ + RxDesc_Status_Own = (1<<31), /* own bit */ + + /* ... */ + TxDesc_Status_Own = (1<<31), /* own bit */ + + /* ... */ + TxDesc_Control_Intr = (1<<23), /* Tx intr request */ + TxDesc_Control_Normal = (3<<24), /* normal frame */ +}; + +typedef struct Stats Stats; +struct Stats +{ + ulong rx; + ulong tx; + ulong txe; + ulong intr; +}; + +typedef struct Ctlr Ctlr; +struct Ctlr +{ + Ctlr* link; + Pcidev* pdev; + int port; + + int inited; + Lock init_lock; + + ulong debugflags; + ulong debugcount; + + Mii* mii; + int active; + uchar ea[6]; + + RxDesc* rx_ring; + Block* rx_blocks[RxCount]; + + Lock tx_lock; + TxDesc* tx_ring; + Block* tx_blocks[TxCount]; + ulong tx_count; + + Stats stats; +}; + +static Ctlr* vgbehead; +static Ctlr* vgbetail; + +#define riob(c, r) inb(c->port + r) +#define riow(c, r) ins(c->port + r) +#define riol(c, r) inl(c->port + r) +#define wiob(c, r, d) outb(c->port + r, d) +#define wiow(c, r, d) outs(c->port + r, d) +#define wiol(c, r, d) outl(c->port + r, d) + +#define siob(c, r, b) wiob(c, r, riob(c, r) | b) +#define siow(c, r, b) wiow(c, r, riob(c, r) | b) +#define siol(c, r, b) wiol(c, r, riob(c, r) | b) +#define ciob(c, r, b) wiob(c, r, riob(c, r) & ~b) +#define ciow(c, r, b) wiow(c, r, riob(c, r) & ~b) +#define ciol(c, r, b) wiol(c, r, riob(c, r) & ~b) + +static int +vgbemiiw(Mii* mii, int phy, int addr, int data) +{ + Ctlr* ctlr; + int i; + + if(phy != 1) + return -1; + + ctlr = mii->ctlr; + + wiob(ctlr, MiiAddr, addr); + wiow(ctlr, MiiData, (ushort) data); + wiob(ctlr, MiiCmd, MiiCmd_write); + + for(i = 0; i < Timeout; i++) + if((riob(ctlr, MiiCmd) & MiiCmd_write) == 0) + break; + + if(i >= Timeout){ + print("vgbe: miiw timeout\n"); + return -1; + } + + return 0; +} + +static int +vgbemiir(Mii* mii, int phy, int addr) +{ + Ctlr* ctlr; + int i; + + if(phy != 1) + return -1; + + ctlr = mii->ctlr; + + wiob(ctlr, MiiAddr, addr); + wiob(ctlr, MiiCmd, MiiCmd_read); + + for(i = 0; i < Timeout; i++) + if((riob(ctlr, MiiCmd) & MiiCmd_read) == 0) + break; + + if(i >= Timeout){ + print("vgbe: miir timeout\n"); + return -1; + } + + return riow(ctlr, MiiData); +} + +static long +vgbeifstat(Ether* edev, void* a, long n, ulong offset) +{ + char* p; + Ctlr* ctlr; + int l; + + ctlr = edev->ctlr; + + p = malloc(2*READSTR); + l = 0; + l += snprint(p+l, 2*READSTR-l, "tx: %uld\n", ctlr->stats.tx); + l += snprint(p+l, 2*READSTR-l, "tx [errs]: %uld\n", ctlr->stats.txe); + l += snprint(p+l, 2*READSTR-l, "rx: %uld\n", ctlr->stats.rx); + l += snprint(p+l, 2*READSTR-l, "intr: %uld\n", ctlr->stats.intr); + snprint(p+l, 2*READSTR-l, "\n"); + + n = readstr(offset, a, n, p); + free(p); + + return n; +} + +static char* vgbeisr_info[] = { + "hi prio Rx int", + "hi prio Tx int", + "Rx queue completed", + "One of Tx queues completed", + "Tx queue 0 completed", + "Tx queue 1 completed", + "Tx queue 2 completed", + "Tx queue 3 completed", + "reserved", + "reserved", + "Rx packet count overflow", + "pause frame Rx'ed", + "RX FIFO overflow", + "ran out of Rx descriptors", + "running out of Rx descriptors", + "link status change", + "one shot timer expired", + "periodic timer expired", + "wake up power event", + "PHY interrupt", + "software shutdown complete", + "MIB counter overflow warning", + "software interrupt", + "reload hold timer", + "Rx DMA stall", + "Tx DMA stall", + "reserved", + "reserved", + "interrupt source indication 0", + "interrupt source indication 1", + "interrupt source indication 2", + "interrupt source indication 3", +}; + +static void +vgbedumpisr(ulong isr) +{ + int i; + + for(i = 0; i < 32; i++){ + ulong mask; + + mask = 1<rx_blocks[i] = block; + + /* Initialize Rx descriptor. (TODO: 48/64 bits support ?) */ + desc = &ctlr->rx_ring[i]; + desc->status = htole32(RxDesc_Status_Own); + desc->control = htole32(0); + + desc->addr_lo = htole32((ulong)PCIWADDR(block->rp)); + desc->addr_hi = htole16(0); + desc->length = htole16(RxSize | 0x8000); + + return 0; +} + +static void +vgberxeof(Ether* edev) +{ + Ctlr* ctlr; + int i; + Block* block; + ulong length, status; + RxDesc* desc; + + ctlr = edev->ctlr; + + if(ctlr->debugflags & DumpRx) + print("vgbe: rx_eof\n"); + + for(i = 0; i < RxCount; i++){ + /* Remember that block. */ + desc = &ctlr->rx_ring[i]; + + status = le32toh(desc->status); + + if(status & RxDesc_Status_Own) + continue; + + if(status & RxDesc_Status_Goodframe){ + length = status >> RxDesc_Status_SizShift; + length &= RxDesc_Status_SizMask; + + if(ctlr->debugflags & DumpRx) + print("vgbe: Rx-desc[%03d] status=%#08ulx ctl=%#08ulx len=%uld bytes\n", + i, status, desc->control, length); + + block = ctlr->rx_blocks[i]; + block->wp = block->rp + length; + + ctlr->stats.rx++; + etheriq(edev, block, 0); + } + else + print("vgbe: Rx-desc[%#02x] *BAD FRAME* status=%#08ulx ctl=%#08ulx\n", + i, status, desc->control); + + /* reset packet ... */ + desc->status = htole32(RxDesc_Status_Own); + desc->control = htole32(0); + } + + if(ctlr->debugflags & DumpRx) + print("vgbe: rx_eof: done\n"); + + wiow(ctlr, RxResCnt, RxCount); + wiob(ctlr, RxCsrS, RxCsr_Wakeup); +} + +static void +vgbetxeof(Ether* edev) +{ + Ctlr* ctlr; + int i, count; + Block* block; + ulong status; + + ctlr = edev->ctlr; + + ilock(&ctlr->tx_lock); + + if(ctlr->debugflags & DumpTx) + print("vgbe: tx_eof\n"); + + for(count = 0, i = 0; i < TxCount; i++){ + block = ctlr->tx_blocks[i]; + if(block == nil) + continue; + + status = le32toh(ctlr->tx_ring[i].status); + if(status & TxDesc_Status_Own) + continue; + + /* Todo add info if it failed */ + ctlr->stats.tx++; + + if(ctlr->debugflags & DumpTx) + print("vgbe: Block[%03d]:%#p has been sent\n", i, block); + + count++; + ctlr->tx_blocks[i] = nil; + freeb(block); + + if(ctlr->debugflags & DumpTx) + print("vgbe: Block[%03d]:%#p has been freed\n", i, block); + } + ctlr->tx_count -= count; + + if(ctlr->debugflags & DumpTx) + print("vgbe: tx_eof: done [count=%d]\n", count); + + iunlock(&ctlr->tx_lock); + + if(ctlr->tx_count) + wiob(ctlr, TxCsrS, TxCsr_Wakeup); +} + +static void +vgbeinterrupt(Ureg *, void* arg) +{ + Ether* edev; + Ctlr* ctlr; + ulong status; + + edev = (Ether *) arg; + if(edev == nil) + return; + + ctlr = edev->ctlr; + if(ctlr == nil) + return; + + /* Mask interrupts. */ + wiol(ctlr, Imr, 0); + + status = riol(ctlr, Isr); + if(status == 0xffff) + goto end; + + /* acknowledge */ + if(status) + wiol(ctlr, Isr, status); + + if((status & Isr_Mask) == 0) + goto end; + + ctlr->stats.intr++; + + if(ctlr->debugflags & DumpIntr) + if(ctlr->debugcount){ + print("vgbe: irq: status = %#08ulx\n", status); + vgbedumpisr(status); + ctlr->debugcount--; + } + + if(status & Isr_RxComplete) + vgberxeof(edev); + + if(status & Isr_TxComplete0) + vgbetxeof(edev); + + if(status & Isr_Stopped) + print("vgbe: irq: software shutdown complete\n"); + + if(status & Isr_RxFifoOvflow) + print("vgbe: irq: RX FIFO overflow\n"); + + if(status & Isr_PhyIntr) + print("vgbe: irq: PHY interrupt\n"); + + if(status & Isr_LinkStatus) + print("vgbe: irq: link status change\n"); + + if(status & Isr_RxNoDesc) + print("vgbe: irq: ran out of Rx descriptors\n"); + + if(status & Isr_RxDmaStall){ + print("vgbe: irq: Rx DMA stall\n"); + wiol(ctlr, Cr3C, Cr3_IntMask); + return; + } + + if(status & Isr_TxDmaStall){ + print("vgbe: irq: Tx DMA stall\n"); + wiol(ctlr, Cr3C, Cr3_IntMask); + return; + } + +end: + /* Unmask interrupts. */ + wiol(ctlr, Imr, ~0); +} + +static void +vgbetransmit(Ether* edev) +{ + Block* block; + Ctlr* ctlr; + int i, index, start, count; + TxDesc* desc; + ulong status, length; + + ctlr = edev->ctlr; + + ilock(&ctlr->tx_lock); + + start = riow(ctlr, TxDscIdx); + + if(ctlr->debugflags & DumpTx) + print("vgbe: transmit (start=%d)\n", start); + + /* find empty slot */ + for(count = 0, i = 0; i < TxCount; i++){ + index = (i + start) % TxCount; + + if(ctlr->tx_blocks[index]) + continue; + + desc = &ctlr->tx_ring[index]; + + status = le32toh(desc->status); + if(status & TxDesc_Status_Own) + continue; + + block = qget(edev->oq); + if(block == nil) + break; + + count++; + + length = BLEN(block); + + if(ctlr->debugflags & DumpTx) + print("vgbe: Tx-Desc[%03d] Block:%#p, addr=%#08ulx, len:%ld\n", index, block, + PCIWADDR(block->rp), length); + + ctlr->tx_blocks[index] = block; + + /* Initialize Tx descriptor. */ + desc->status = htole32((length<<16)|TxDesc_Status_Own); + desc->control = htole32(TxDesc_Control_Intr|TxDesc_Control_Normal|((1+1)<<28)); + + desc->frags[0].addr_lo = htole32((ulong) PCIWADDR(block->rp)); + desc->frags[0].addr_hi = htole16(0); + desc->frags[0].length = htole16(length); + } + ctlr->tx_count += count; + + if(ctlr->debugflags & DumpTx) + print("vgbe: transmit: done [count=%d]\n", count); + + iunlock(&ctlr->tx_lock); + + if(ctlr->tx_count) + wiob(ctlr, TxCsrS, TxCsr_Wakeup); + + if(count == 0) + print("vgbe: transmit: no Tx entry available\n"); +} + +static void +vgbeattach(Ether* edev) +{ + Ctlr* ctlr; + RxDesc* rxdesc; + TxDesc* txdesc; + int i; + + ctlr = edev->ctlr; + + lock(&ctlr->init_lock); + if(ctlr->inited){ + unlock(&ctlr->init_lock); + return; + } + +// print("vgbe: attach\n"); + + /* Allocate Rx ring. (TODO: Alignment ?) */ + rxdesc = mallocalign(RxCount* sizeof(RxDesc), 256, 0, 0); + if(rxdesc == nil){ + print("vgbe: unable to alloc Rx ring\n"); + unlock(&ctlr->init_lock); + return; + } + ctlr->rx_ring = rxdesc; + + /* Allocate Rx blocks, initialize Rx ring. */ + for(i = 0; i < RxCount; i++) + vgbenewrx(ctlr, i); + + /* Init Rx MAC. */ + wiob(ctlr, RxControl, + RxControl_MultiCast|RxControl_BroadCast|RxControl_UniCast); + wiob(ctlr, RxConfig, RxConfig_VlanOpt0); + + /* Load Rx ring. */ + wiol(ctlr, RxDescLo, (ulong) PCIWADDR(rxdesc)); + wiow(ctlr, RxNum, RxCount - 1); + wiow(ctlr, RxDscIdx, 0); + wiow(ctlr, RxResCnt, RxCount); + + /* Allocate Tx ring. */ + txdesc = mallocalign(TxCount* sizeof(TxDesc), 256, 0, 0); + if(txdesc == nil){ + print("vgbe: unable to alloc Tx ring\n"); + unlock(&ctlr->init_lock); + return; + } + ctlr->tx_ring = txdesc; + + /* Init DMAs */ + wiob(ctlr, DmaCfg0, 4); + + /* Init Tx MAC. */ + wiob(ctlr, TxControl, 0); + wiob(ctlr, TxConfig, TxConfig_NonBlk|TxConfig_ArbPrio); + + /* Load Tx ring. */ + wiol(ctlr, TxDescLo, (ulong) PCIWADDR(txdesc)); + wiow(ctlr, TxNum, TxCount - 1); + wiow(ctlr, TxDscIdx, 0); + + /* Enable Xon/Xoff */ + wiob(ctlr, Cr2S, 0xb|Cr2_XonEnable); + + /* Enable Rx queue */ + wiob(ctlr, RxCsrS, RxCsr_RunQueue); + + /* Enable Tx queue */ + wiob(ctlr, TxCsrS, TxCsr_RunQueue); + + /* Done */ + ctlr->inited = 1; + unlock(&ctlr->init_lock); + + /* Enable interrupts */ + wiol(ctlr, Isr, 0xffffffff); + wiob(ctlr, Cr3S, Cr3_IntMask); + + /* Wake up Rx queue */ + wiob(ctlr, RxCsrS, RxCsr_Wakeup); +} + +static void +vgbereset(Ctlr* ctlr) +{ +// MiiPhy* phy; + int timeo, i; + +// print("vgbe: reset\n"); + + /* Soft reset the controller. */ + wiob(ctlr, Cr1S, Cr1_reset); + + for(timeo = 0; timeo < Timeout; timeo++) + if((riob(ctlr, Cr1S) & Cr1_reset) == 0) + break; + + if(timeo >= Timeout){ + print("vgbe: softreset timeout\n"); + return; + } + + /* Reload eeprom. */ + siob(ctlr, Eecsr, Eecsr_Autold); + + for(timeo = 0; timeo < Timeout; timeo++) + if((riob(ctlr, Eecsr) & Eecsr_Autold) == 0) + break; + + if(timeo >= Timeout){ + print("vgbe: eeprom reload timeout\n"); + return; + } + + /* Load the MAC address. */ + for(i = 0; i < Eaddrlen; i++) + ctlr->ea[i] = riob(ctlr, EthAddr+i); + + /* Initialize interrupts. */ + wiol(ctlr, Isr, 0xffffffff); + wiol(ctlr, Imr, 0xffffffff); + + /* Disable interrupts. */ + wiol(ctlr, Cr3C, Cr3_IntMask); + + /* 32 bits addresses only. (TODO: 64 bits ?) */ + wiol(ctlr, TxDescHi, 0); + wiow(ctlr, DataBufHi, 0); + + /* Enable MAC (turning off Rx/Tx engines for the moment). */ + wiob(ctlr, Cr0C, Cr0_Stop|Cr0_EnableRx|Cr0_EnableTx); + wiob(ctlr, Cr0S, Cr0_Start); + + /* Initialize Rx engine. */ + wiow(ctlr, RxCsrC, RxCsr_RunQueue); + + /* Initialize Tx engine. */ + wiow(ctlr, TxCsrC, TxCsr_RunQueue); + + /* Enable Rx/Tx engines. */ + wiob(ctlr, Cr0S, Cr0_EnableRx|Cr0_EnableTx); + + /* Initialize link management. */ + ctlr->mii = malloc(sizeof(Mii)); + if(ctlr->mii == nil){ + print("vgbe: unable to alloc Mii\n"); + return; + } + + ctlr->mii->mir = vgbemiir; + ctlr->mii->miw = vgbemiiw; + ctlr->mii->ctlr = ctlr; + + if(mii(ctlr->mii, 1<<1) == 0){ + print("vgbe: no phy found\n"); + return; + } + +// phy = ctlr->mii->curphy; +// print("vgbe: phy:oui %#x\n", phy->oui); +} + +static void +vgbepci(void) +{ + Pcidev* pdev; + +// print("vgbe: pci\n"); + + pdev = nil; + while(pdev = pcimatch(pdev, 0, 0)){ + Ctlr* ctlr; + int port, size; + + if(pdev->ccrb != 0x02 || pdev->ccru != 0) + continue; + + switch((pdev->did<<16) | pdev->vid){ + default: + continue; + + case (0x3119<<16)|0x1106: /* VIA Velocity (VT6122) */ + break; + } + + if((pdev->pcr & 1) == 0){ + print("vgbe: io not enabled [pcr=%#lux]\n", (ulong)pdev->pcr); + continue; + } + + pcisetbme(pdev); + pcisetpms(pdev, 0); + + port = pdev->mem[0].bar; + size = pdev->mem[0].size; + + if((port & 1) == 0){ + print("vgbe: bar[0]=%#x is not io\n", port); + continue; + } + + if(port > 0xff00){ + print("vgbe: invalid port %#ux\n", port); + continue; + } + + port &= 0xfffe; + + if(size != 256){ + print("vgbe: invalid io size: %d\n", size); + continue; + } + + if(ioalloc(port, size, 0, "vge") < 0){ + print("vgbe: port %#ux already in use\n", port); + continue; + } + + ctlr = malloc(sizeof(Ctlr)); + if(ctlr == nil){ + print("vgbe: unable to alloc Ctlr\n"); + iofree(port); + continue; + } + + ctlr->pdev = pdev; + ctlr->port = port; + ctlr->inited = 0; + + if(vgbehead != nil) + vgbetail->link = ctlr; + else + vgbehead = ctlr; + vgbetail = ctlr; + } +} + +static long +vgbectl(Ether* edev, void* buf, long n) +{ + Cmdbuf* cb; + Ctlr* ctlr; + ulong index; + char* rptr; + RxDesc* rd; + TxDesc* td; + uchar* p; + + ctlr = edev->ctlr; + + cb = parsecmd(buf, n); + if(waserror()){ + free(cb); + nexterror(); + } + + if(cistrcmp(cb->f[0], "reset") == 0){ + vgbereset(ctlr); + wiob(ctlr, Cr3S, Cr3_IntMask); + wiob(ctlr, RxCsrS, RxCsr_RunQueue); + wiob(ctlr, RxCsrS, RxCsr_Wakeup); + } + else if(cistrcmp(cb->f[0], "dumpintr") == 0){ + if(cb->nf < 2) + error(Ecmdargs); + + if(cistrcmp(cb->f[1], "on") == 0){ + ctlr->debugflags |= DumpIntr; + ctlr->debugcount = ~0; + } + else if(cistrcmp(cb->f[1], "off") == 0) + ctlr->debugflags &= ~DumpIntr; + else{ + ulong count; + char* rptr; + + count = strtoul(cb->f[1], &rptr, 0); + if(rptr == cb->f[1]) + error("invalid control request"); + + ctlr->debugflags |= DumpIntr; + ctlr->debugcount = count; + + print("vgbe: debugcount set to %uld\n", count); + } + } + else if(cistrcmp(cb->f[0], "dumprx") == 0){ + if(cb->nf < 2) + error(Ecmdargs); + + if(cistrcmp(cb->f[1], "on") == 0) + ctlr->debugflags |= DumpRx; + else if(cistrcmp(cb->f[1], "off") == 0) + ctlr->debugflags &= ~DumpRx; + else{ + index = strtoul(cb->f[1], &rptr, 0); + if((rptr == cb->f[1]) || (index >= RxCount)) + error("invalid control request"); + + rd = &ctlr->rx_ring[index]; + print("vgbe: DumpRx[%03uld] status=%#08ulx ctl=%#08ulx len=%#04ux bytes\n", + index, rd->status, rd->control, rd->length); + } + } + else if(cistrcmp(cb->f[0], "dumptx") == 0){ + if(cb->nf < 2) + error(Ecmdargs); + + if(cistrcmp(cb->f[1], "on") == 0) + ctlr->debugflags |= DumpTx; + else if(cistrcmp(cb->f[1], "off") == 0) + ctlr->debugflags &= ~DumpTx; + else{ + index = strtoul(cb->f[1], &rptr, 0); + if((rptr == cb->f[1]) || (index >= TxCount)) + error("invalid control request"); + + td = &ctlr->tx_ring[index]; + print("vgbe: DumpTx[%03uld] status=%#08ulx ctl=%#08ulx len=%#04ux bytes", + index, td->status, td->control, td->frags[0].length); + + p = (uchar*)td; + for(index = 0; index < sizeof(TxDesc); index++){ + if((index % 16) == 0) + print("\nvgbe: "); + else + print(" "); + print("%#02x", p[index]); + } + } + } + else if(cistrcmp(cb->f[0], "dumpall") == 0){ + if(cb->nf < 2) + error(Ecmdargs); + + if(cistrcmp(cb->f[1], "on") == 0){ + ctlr->debugflags = ~0; + ctlr->debugcount = ~0; + } + else if(cistrcmp(cb->f[1], "off") == 0) + ctlr->debugflags = 0; + else error("invalid control request"); + } + else + error(Ebadctl); + + free(cb); + poperror(); + + return n; +} + +static int +vgbepnp(Ether* edev) +{ + Ctlr* ctlr; + +// print("vgbe: pnp\n"); + + if(vgbehead == nil) + vgbepci(); + + for(ctlr = vgbehead; ctlr != nil; ctlr = ctlr->link){ + if(ctlr->active) + continue; + + if(edev->port == 0 || edev->port == ctlr->port){ + ctlr->active = 1; + break; + } + } + + if(ctlr == nil) + return -1; + + vgbereset(ctlr); + + edev->ctlr = ctlr; + edev->port = ctlr->port; + edev->irq = ctlr->pdev->intl; + edev->tbdf = ctlr->pdev->tbdf; + edev->mbps = 1000; + memmove(edev->ea, ctlr->ea, Eaddrlen); + edev->attach = vgbeattach; + edev->transmit = vgbetransmit; + edev->interrupt = vgbeinterrupt; + edev->ifstat = vgbeifstat; + edev->ctl = vgbectl; + + edev->arg = edev; + return 0; +} + +void +ethervgbelink(void) +{ + addethercard("vgbe", vgbepnp); +}