diff options
Diffstat (limited to 'dev/ns_gige.cc')
-rw-r--r-- | dev/ns_gige.cc | 2396 |
1 files changed, 2396 insertions, 0 deletions
diff --git a/dev/ns_gige.cc b/dev/ns_gige.cc new file mode 100644 index 000000000..36f17c4fb --- /dev/null +++ b/dev/ns_gige.cc @@ -0,0 +1,2396 @@ +/* + * Copyright (c) 2003 The Regents of The University of Michigan + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer; + * redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution; + * neither the name of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* @file + * Device module for modelling the National Semiconductor + * DP83820 ethernet controller. Does not support priority queueing + */ +#include <cstdio> +#include <deque> +#include <string> + +#include "base/inet.hh" +#include "cpu/exec_context.hh" +#include "cpu/intr_control.hh" +#include "dev/dma.hh" +#include "dev/ns_gige.hh" +#include "dev/etherlink.hh" +#include "mem/bus/bus.hh" +#include "mem/bus/dma_interface.hh" +#include "mem/bus/pio_interface.hh" +#include "mem/bus/pio_interface_impl.hh" +#include "mem/functional_mem/memory_control.hh" +#include "mem/functional_mem/physical_memory.hh" +#include "sim/builder.hh" +#include "sim/host.hh" +#include "sim/sim_stats.hh" +#include "targetarch/vtophys.hh" +#include "dev/pciconfigall.hh" +#include "dev/tsunami_cchip.hh" + +const char *NsRxStateStrings[] = +{ + "rxIdle", + "rxDescRefr", + "rxDescRead", + "rxFifoBlock", + "rxFragWrite", + "rxDescWrite", + "rxAdvance" +}; + +const char *NsTxStateStrings[] = +{ + "txIdle", + "txDescRefr", + "txDescRead", + "txFifoBlock", + "txFragRead", + "txDescWrite", + "txAdvance" +}; + +const char *NsDmaState[] = +{ + "dmaIdle", + "dmaReading", + "dmaWriting", + "dmaReadWaiting", + "dmaWriteWaiting" +}; + +using namespace std; + +/////////////////////////////////////////////////////////////////////// +// +// EtherDev PCI Device +// +EtherDev::EtherDev(const std::string &name, IntrControl *i, Tick intr_delay, + PhysicalMemory *pmem, Tick tx_delay, Tick rx_delay, + MemoryController *mmu, HierParams *hier, Bus *header_bus, + Bus *payload_bus, Tick pio_latency, bool dma_desc_free, + bool dma_data_free, Tick dma_read_delay, Tick dma_write_delay, + Tick dma_read_factor, Tick dma_write_factor, PciConfigAll *cf, + PciConfigData *cd, Tsunami *t, uint32_t bus, uint32_t dev, + uint32_t func, bool rx_filter, const int eaddr[6], Addr addr) + : PciDev(name, mmu, cf, cd, bus, dev, func), tsunami(t), + addr(addr), txPacketBufPtr(NULL), rxPacketBufPtr(NULL), + txXferLen(0), rxXferLen(0), txPktXmitted(0), txState(txIdle), CTDD(false), + txFifoCnt(0), txFifoAvail(MAX_TX_FIFO_SIZE), txHalt(false), + txFragPtr(0), txDescCnt(0), txDmaState(dmaIdle), rxState(rxIdle), + CRDD(false), rxPktBytes(0), rxFifoCnt(0), rxHalt(false), + rxFragPtr(0), rxDescCnt(0), rxDmaState(dmaIdle), extstsEnable(false), + rxDmaReadEvent(this), rxDmaWriteEvent(this), + txDmaReadEvent(this), txDmaWriteEvent(this), + dmaDescFree(dma_desc_free), dmaDataFree(dma_data_free), + txDelay(tx_delay), rxDelay(rx_delay), rxKickTick(0), txKickTick(0), + txEvent(this), rxFilterEnable(rx_filter), acceptBroadcast(false), + acceptMulticast(false), acceptUnicast(false), + acceptPerfect(false), acceptArp(false), + physmem(pmem), intctrl(i), intrTick(0), + cpuPendingIntr(false), intrEvent(0), interface(0), pioLatency(pio_latency) +{ + mmu->add_child(this, Range<Addr>(addr, addr + size)); + tsunami->ethernet = this; + + if (header_bus) { + pioInterface = newPioInterface(name, hier, header_bus, this, + &EtherDev::cacheAccess); + pioInterface->addAddrRange(addr, addr + size - 1); + if (payload_bus) + dmaInterface = new DMAInterface<Bus>(name + ".dma", + header_bus, payload_bus, 1); + else + dmaInterface = new DMAInterface<Bus>(name + ".dma", + header_bus, header_bus, 1); + } else if (payload_bus) { + pioInterface = newPioInterface(name, hier, payload_bus, this, + &EtherDev::cacheAccess); + pioInterface->addAddrRange(addr, addr + size - 1); + dmaInterface = new DMAInterface<Bus>(name + ".dma", + payload_bus, payload_bus, 1); + + } + + + intrDelay = US2Ticks(intr_delay); + dmaReadDelay = dma_read_delay; + dmaWriteDelay = dma_write_delay; + dmaReadFactor = dma_read_factor; + dmaWriteFactor = dma_write_factor; + + memset(®s, 0, sizeof(regs)); + regsReset(); + rom.perfectMatch[0] = eaddr[0]; + rom.perfectMatch[1] = eaddr[1]; + rom.perfectMatch[2] = eaddr[2]; + rom.perfectMatch[3] = eaddr[3]; + rom.perfectMatch[4] = eaddr[4]; + rom.perfectMatch[5] = eaddr[5]; +} + +EtherDev::~EtherDev() +{} + +void +EtherDev::regStats() +{ + txBytes + .name(name() + ".txBytes") + .desc("Bytes Transmitted") + .prereq(txBytes) + ; + + rxBytes + .name(name() + ".rxBytes") + .desc("Bytes Received") + .prereq(rxBytes) + ; + + txPackets + .name(name() + ".txPackets") + .desc("Number of Packets Transmitted") + .prereq(txBytes) + ; + + rxPackets + .name(name() + ".rxPackets") + .desc("Number of Packets Received") + .prereq(rxBytes) + ; + + txBandwidth + .name(name() + ".txBandwidth") + .desc("Transmit Bandwidth (bits/s)") + .precision(0) + .prereq(txBytes) + ; + + rxBandwidth + .name(name() + ".rxBandwidth") + .desc("Receive Bandwidth (bits/s)") + .precision(0) + .prereq(rxBytes) + ; + + txPacketRate + .name(name() + ".txPPS") + .desc("Packet Tranmission Rate (packets/s)") + .precision(0) + .prereq(txBytes) + ; + + rxPacketRate + .name(name() + ".rxPPS") + .desc("Packet Reception Rate (packets/s)") + .precision(0) + .prereq(rxBytes) + ; + + txBandwidth = txBytes * Statistics::constant(8) / simSeconds; + rxBandwidth = rxBytes * Statistics::constant(8) / simSeconds; + txPacketRate = txPackets / simSeconds; + rxPacketRate = rxPackets / simSeconds; +} + +/** + * This is to read the PCI general configuration registers + */ +void +EtherDev::ReadConfig(int offset, int size, uint8_t *data) +{ + if (offset < PCI_DEVICE_SPECIFIC) + PciDev::ReadConfig(offset, size, data); + else { + panic("need to do this\n"); + } +} + +/** + * This is to write to the PCI general configuration registers + */ +void +EtherDev::WriteConfig(int offset, int size, uint32_t data) +{ + if (offset < PCI_DEVICE_SPECIFIC) + PciDev::WriteConfig(offset, size, data); + else + panic("Need to do that\n"); +} + +/** + * This reads the device registers, which are detailed in the NS83820 + * spec sheet + */ +Fault +EtherDev::read(MemReqPtr &req, uint8_t *data) +{ + //The mask is to give you only the offset into the device register file + Addr daddr = req->paddr & 0xfff; + DPRINTF(EthernetPIO, "read da=%#x pa=%#x va=%#x size=%d\n", + daddr, req->paddr, req->vaddr, req->size); + + + //there are some reserved registers, you can see ns_gige_reg.h and + //the spec sheet for details + if (daddr > LAST && daddr <= RESERVED) { + panic("Accessing reserved register"); + } else if (daddr > RESERVED && daddr <= 0x3FC) { + ReadConfig(daddr & 0xff, req->size, data); + return No_Fault; + } else if (daddr >= MIB_START && daddr <= MIB_END) { + // don't implement all the MIB's. hopefully the kernel + // doesn't actually DEPEND upon their values + // MIB are just hardware stats keepers + uint32_t ® = *(uint32_t *) data; + reg = 0; + return No_Fault; + } else if (daddr > 0x3FC) + panic("Something is messed up!\n"); + + switch (req->size) { + case sizeof(uint32_t): + { + uint32_t ® = *(uint32_t *)data; + + switch (daddr) { + case CR: + reg = regs.command; + //these are supposed to be cleared on a read + reg &= ~(CR_RXD | CR_TXD | CR_TXR | CR_RXR); + break; + + case CFG: + reg = regs.config; + break; + + case MEAR: + reg = regs.mear; + break; + + case PTSCR: + reg = regs.ptscr; + break; + + case ISR: + reg = regs.isr; + regs.isr = 0; + break; + + case IMR: + reg = regs.imr; + break; + + case IER: + reg = regs.ier; + break; + + case IHR: + reg = regs.ihr; + break; + + case TXDP: + reg = regs.txdp; + break; + + case TXDP_HI: + reg = regs.txdp_hi; + break; + + case TXCFG: + reg = regs.txcfg; + break; + + case GPIOR: + reg = regs.gpior; + break; + + case RXDP: + reg = regs.rxdp; + break; + + case RXDP_HI: + reg = regs.rxdp_hi; + break; + + case RXCFG: + reg = regs.rxcfg; + break; + + case PQCR: + reg = regs.pqcr; + break; + + case WCSR: + reg = regs.wcsr; + break; + + case PCR: + reg = regs.pcr; + break; + + //see the spec sheet for how RFCR and RFDR work + //basically, you write to RFCR to tell the machine what you want to do next + //then you act upon RFDR, and the device will be prepared b/c + //of what you wrote to RFCR + case RFCR: + reg = regs.rfcr; + break; + + case RFDR: + DPRINTF(Ethernet, "reading from RFDR\n"); + switch (regs.rfcr & RFCR_RFADDR) { + case 0x000: + reg = rom.perfectMatch[1]; + reg = reg << 8; + reg += rom.perfectMatch[0]; + break; + case 0x002: + reg = rom.perfectMatch[3] << 8; + reg += rom.perfectMatch[2]; + break; + case 0x004: + reg = rom.perfectMatch[5] << 8; + reg += rom.perfectMatch[4]; + break; + default: + panic("reading from RFDR for something for other than PMATCH!\n"); + //didn't implement other RFDR functionality b/c driver didn't use + } + break; + + case SRR: + reg = regs.srr; + break; + + case MIBC: + reg = regs.mibc; + reg &= ~(MIBC_MIBS | MIBC_ACLR); + break; + + case VRCR: + reg = regs.vrcr; + break; + + case VTCR: + reg = regs.vtcr; + break; + + case VDR: + reg = regs.vdr; + break; + + case CCSR: + reg = regs.ccsr; + break; + + case TBICR: + reg = regs.tbicr; + break; + + case TBISR: + reg = regs.tbisr; + break; + + case TANAR: + reg = regs.tanar; + break; + + case TANLPAR: + reg = regs.tanlpar; + break; + + case TANER: + reg = regs.taner; + break; + + case TESR: + reg = regs.tesr; + break; + + default: + panic("reading unimplemented register: addr = %#x", daddr); + } + + DPRINTF(EthernetPIO, "read from %#x: data=%d data=%#x\n", daddr, reg, reg); + } + break; + + default: + panic("accessing register with invalid size: addr=%#x, size=%d", + daddr, req->size); + } + + return No_Fault; +} + +Fault +EtherDev::write(MemReqPtr &req, const uint8_t *data) +{ + Addr daddr = req->paddr & 0xfff; + DPRINTF(EthernetPIO, "write da=%#x pa=%#x va=%#x size=%d\n", + daddr, req->paddr, req->vaddr, req->size); + + if (daddr > LAST && daddr <= RESERVED) { + panic("Accessing reserved register"); + } else if (daddr > RESERVED && daddr <= 0x3FC) { + WriteConfig(daddr & 0xff, req->size, *(uint32_t *)data); + return No_Fault; + } else if (daddr > 0x3FC) + panic("Something is messed up!\n"); + + if (req->size == sizeof(uint32_t)) { + uint32_t reg = *(uint32_t *)data; + DPRINTF(EthernetPIO, "write data=%d data=%#x\n", reg, reg); + + switch (daddr) { + case CR: + regs.command = reg; + if ((reg & (CR_TXE | CR_TXD)) == (CR_TXE | CR_TXD)) { + txHalt = true; + } else if (reg & CR_TXE) { + //the kernel is enabling the transmit machine + if (txState == txIdle) + txKick(); + } else if (reg & CR_TXD) { + txHalt = true; + } + + if ((reg & (CR_RXE | CR_RXD)) == (CR_RXE | CR_RXD)) { + rxHalt = true; + } else if (reg & CR_RXE) { + if (rxState == rxIdle) { + rxKick(); + } + } else if (reg & CR_RXD) { + rxHalt = true; + } + + if (reg & CR_TXR) + txReset(); + + if (reg & CR_RXR) + rxReset(); + + if (reg & CR_SWI) + devIntrPost(ISR_SWI); + + if (reg & CR_RST) { + txReset(); + rxReset(); + + regsReset(); + } + break; + + case CFG: + if (reg & CFG_LNKSTS || reg & CFG_SPDSTS || reg & CFG_DUPSTS + || reg & CFG_RESERVED || reg & CFG_T64ADDR + || reg & CFG_PCI64_DET) + panic("writing to read-only or reserved CFG bits!\n"); + + regs.config |= reg & ~(CFG_LNKSTS | CFG_SPDSTS | CFG_DUPSTS | CFG_RESERVED | + CFG_T64ADDR | CFG_PCI64_DET); + +// all these #if 0's are because i don't THINK the kernel needs to have these implemented +// if there is a problem relating to one of these, you may need to add functionality in +#if 0 + if (reg & CFG_TBI_EN) ; + if (reg & CFG_MODE_1000) ; +#endif + + if (reg & CFG_AUTO_1000) + panic("CFG_AUTO_1000 not implemented!\n"); + +#if 0 + if (reg & CFG_PINT_DUPSTS || reg & CFG_PINT_LNKSTS || reg & CFG_PINT_SPDSTS) ; + if (reg & CFG_TMRTEST) ; + if (reg & CFG_MRM_DIS) ; + if (reg & CFG_MWI_DIS) ; + + if (reg & CFG_T64ADDR) + panic("CFG_T64ADDR is read only register!\n"); + + if (reg & CFG_PCI64_DET) + panic("CFG_PCI64_DET is read only register!\n"); + + if (reg & CFG_DATA64_EN) ; + if (reg & CFG_M64ADDR) ; + if (reg & CFG_PHY_RST) ; + if (reg & CFG_PHY_DIS) ; +#endif + + if (reg & CFG_EXTSTS_EN) + extstsEnable = true; + else + extstsEnable = false; + +#if 0 + if (reg & CFG_REQALG) ; + if (reg & CFG_SB) ; + if (reg & CFG_POW) ; + if (reg & CFG_EXD) ; + if (reg & CFG_PESEL) ; + if (reg & CFG_BROM_DIS) ; + if (reg & CFG_EXT_125) ; + if (reg & CFG_BEM) ; +#endif + break; + + case MEAR: + regs.mear = reg; + /* since phy is completely faked, MEAR_MD* don't matter + and since the driver never uses MEAR_EE*, they don't matter */ +#if 0 + if (reg & MEAR_EEDI) ; + if (reg & MEAR_EEDO) ; //this one is read only + if (reg & MEAR_EECLK) ; + if (reg & MEAR_EESEL) ; + if (reg & MEAR_MDIO) ; + if (reg & MEAR_MDDIR) ; + if (reg & MEAR_MDC) ; +#endif + break; + + case PTSCR: + regs.ptscr = reg & ~(PTSCR_RBIST_RDONLY); + /* these control BISTs for various parts of chip - we don't care or do + just fake that the BIST is done */ + if (reg & PTSCR_RBIST_EN) + regs.ptscr |= PTSCR_RBIST_DONE; + if (reg & PTSCR_EEBIST_EN) + regs.ptscr &= ~PTSCR_EEBIST_EN; + if (reg & PTSCR_EELOAD_EN) + regs.ptscr &= ~PTSCR_EELOAD_EN; + break; + + case ISR: /* writing to the ISR has no effect */ + panic("ISR is a read only register!\n"); + + case IMR: + regs.imr = reg; + devIntrChangeMask(); + break; + + case IER: + regs.ier = reg; + break; + + case IHR: + regs.ihr = reg; + /* not going to implement real interrupt holdoff */ + break; + + case TXDP: + regs.txdp = (reg & 0xFFFFFFFC); + assert(txState == txIdle); + CTDD = false; + break; + + case TXDP_HI: + regs.txdp_hi = reg; + break; + + case TXCFG: + regs.txcfg = reg; +#if 0 + if (reg & TXCFG_CSI) ; + if (reg & TXCFG_HBI) ; + if (reg & TXCFG_MLB) ; + if (reg & TXCFG_ATP) ; + if (reg & TXCFG_ECRETRY) ; /* this could easily be implemented, but + considering the network is just a fake + pipe, wouldn't make sense to do this */ + + if (reg & TXCFG_BRST_DIS) ; +#endif + + + /* we handle our own DMA, ignore the kernel's exhortations */ + if (reg & TXCFG_MXDMA) ; + + break; + + case GPIOR: + regs.gpior = reg; + /* these just control general purpose i/o pins, don't matter */ + break; + + case RXDP: + regs.rxdp = reg; + break; + + case RXDP_HI: + regs.rxdp_hi = reg; + break; + + case RXCFG: + regs.rxcfg = reg; +#if 0 + if (reg & RXCFG_AEP) ; + if (reg & RXCFG_ARP) ; + if (reg & RXCFG_STRIPCRC) ; + if (reg & RXCFG_RX_RD) ; + if (reg & RXCFG_ALP) ; + if (reg & RXCFG_AIRL) ; +#endif + + /* we handle our own DMA, ignore what kernel says about it */ + if (reg & RXCFG_MXDMA) ; + +#if 0 + if (reg & (RXCFG_DRTH | RXCFG_DRTH0)) ; +#endif + break; + + case PQCR: + /* there is no priority queueing used in the linux 2.6 driver */ + regs.pqcr = reg; + break; + + case WCSR: + /* not going to implement wake on LAN */ + regs.wcsr = reg; + break; + + case PCR: + /* not going to implement pause control */ + regs.pcr = reg; + break; + + case RFCR: + regs.rfcr = reg; + DPRINTF(Ethernet, "Writing to RFCR, RFADDR is %#x\n", reg & RFCR_RFADDR); + + rxFilterEnable = (reg & RFCR_RFEN) ? true : false; + + acceptBroadcast = (reg & RFCR_AAB) ? true : false; + + acceptMulticast = (reg & RFCR_AAM) ? true : false; + + acceptUnicast = (reg & RFCR_AAU) ? true : false; + + acceptPerfect = (reg & RFCR_APM) ? true : false; + + acceptArp = (reg & RFCR_AARP) ? true : false; + + if (reg & RFCR_APAT) ; +// panic("RFCR_APAT not implemented!\n"); + + if (reg & RFCR_MHEN || reg & RFCR_UHEN) + panic("hash filtering not implemented!\n"); + + if (reg & RFCR_ULM) + panic("RFCR_ULM not implemented!\n"); + + break; + + case RFDR: + panic("the driver never writes to RFDR, something is wrong!\n"); + + case BRAR: + panic("the driver never uses BRAR, something is wrong!\n"); + + case BRDR: + panic("the driver never uses BRDR, something is wrong!\n"); + + case SRR: + panic("SRR is read only register!\n"); + + case MIBC: + panic("the driver never uses MIBC, something is wrong!\n"); + + case VRCR: + regs.vrcr = reg; + break; + + case VTCR: + regs.vtcr = reg; + break; + + case VDR: + panic("the driver never uses VDR, something is wrong!\n"); + break; + + case CCSR: + /* not going to implement clockrun stuff */ + regs.ccsr = reg; + break; + + case TBICR: + regs.tbicr = reg; + if (reg & TBICR_MR_LOOPBACK) + panic("TBICR_MR_LOOPBACK never used, something wrong!\n"); + + if (reg & TBICR_MR_AN_ENABLE) { + regs.tanlpar = regs.tanar; + regs.tbisr |= (TBISR_MR_AN_COMPLETE | TBISR_MR_LINK_STATUS); + } + +#if 0 + if (reg & TBICR_MR_RESTART_AN) ; +#endif + + break; + + case TBISR: + panic("TBISR is read only register!\n"); + + case TANAR: + regs.tanar = reg; + if (reg & TANAR_PS2) + panic("this isn't used in driver, something wrong!\n"); + + if (reg & TANAR_PS1) + panic("this isn't used in driver, something wrong!\n"); + break; + + case TANLPAR: + panic("this should only be written to by the fake phy!\n"); + + case TANER: + panic("TANER is read only register!\n"); + + case TESR: + regs.tesr = reg; + break; + + default: + panic("thought i covered all the register, what is this? addr=%#x", + daddr); + } + } else + panic("Invalid Request Size"); + + return No_Fault; +} + +void +EtherDev::devIntrPost(uint32_t interrupts) +{ + DPRINTF(Ethernet, "interrupt posted intr=%#x isr=%#x imr=%#x\n", + interrupts, regs.isr, regs.imr); + + bool delay = false; + + if (interrupts & ISR_RESERVE) + panic("Cannot set a reserved interrupt"); + + if (interrupts & ISR_TXRCMP) + regs.isr |= ISR_TXRCMP; + + if (interrupts & ISR_RXRCMP) + regs.isr |= ISR_RXRCMP; + +//ISR_DPERR not implemented +//ISR_SSERR not implemented +//ISR_RMABT not implemented +//ISR_RXSOVR not implemented +//ISR_HIBINT not implemented +//ISR_PHY not implemented +//ISR_PME not implemented + + if (interrupts & ISR_SWI) + regs.isr |= ISR_SWI; + +//ISR_MIB not implemented +//ISR_TXURN not implemented + + if (interrupts & ISR_TXIDLE) + regs.isr |= ISR_TXIDLE; + + if (interrupts & ISR_TXERR) + regs.isr |= ISR_TXERR; + + if (interrupts & ISR_TXDESC) + regs.isr |= ISR_TXDESC; + + if (interrupts & ISR_TXOK) { + regs.isr |= ISR_TXOK; + delay = true; + } + + if (interrupts & ISR_RXORN) + regs.isr |= ISR_RXORN; + + if (interrupts & ISR_RXIDLE) + regs.isr |= ISR_RXIDLE; + +//ISR_RXEARLY not implemented + + if (interrupts & ISR_RXERR) + regs.isr |= ISR_RXERR; + + if (interrupts & ISR_RXOK) { + delay = true; + regs.isr |= ISR_RXOK; + } + + if ((regs.isr & regs.imr)) { + Tick when = curTick; + if (delay) + when += intrDelay; + cpuIntrPost(when); + } +} + +void +EtherDev::devIntrClear(uint32_t interrupts) +{ + DPRINTF(Ethernet, "interrupt cleared intr=%x isr=%x imr=%x\n", + interrupts, regs.isr, regs.imr); + + if (interrupts & ISR_RESERVE) + panic("Cannot clear a reserved interrupt"); + + if (interrupts & ISR_TXRCMP) + regs.isr &= ~ISR_TXRCMP; + + if (interrupts & ISR_RXRCMP) + regs.isr &= ~ISR_RXRCMP; + +//ISR_DPERR not implemented +//ISR_SSERR not implemented +//ISR_RMABT not implemented +//ISR_RXSOVR not implemented +//ISR_HIBINT not implemented +//ISR_PHY not implemented +//ISR_PME not implemented + + if (interrupts & ISR_SWI) + regs.isr &= ~ISR_SWI; + +//ISR_MIB not implemented +//ISR_TXURN not implemented + + if (interrupts & ISR_TXIDLE) + regs.isr &= ~ISR_TXIDLE; + + if (interrupts & ISR_TXERR) + regs.isr &= ~ISR_TXERR; + + if (interrupts & ISR_TXDESC) + regs.isr &= ~ISR_TXDESC; + + if (interrupts & ISR_TXOK) + regs.isr &= ~ISR_TXOK; + + if (interrupts & ISR_RXORN) + regs.isr &= ~ISR_RXORN; + + if (interrupts & ISR_RXIDLE) + regs.isr &= ~ISR_RXIDLE; + +//ISR_RXEARLY not implemented + + if (interrupts & ISR_RXERR) + regs.isr &= ~ISR_RXERR; + + if (interrupts & ISR_RXOK) + regs.isr &= ~ISR_RXOK; + + if (!(regs.isr & regs.imr)) + cpuIntrClear(); +} + +void +EtherDev::devIntrChangeMask() +{ + DPRINTF(Ethernet, "interrupt mask changed\n"); + + if (regs.isr & regs.imr) + cpuIntrPost(curTick); + else + cpuIntrClear(); +} + +void +EtherDev::cpuIntrPost(Tick when) +{ + if (when > intrTick && intrTick != 0) + return; + + intrTick = when; + + if (intrEvent) { + intrEvent->squash(); + intrEvent = 0; + } + + if (when < curTick) { + cpuInterrupt(); + } else { + intrEvent = new IntrEvent(this, true); + intrEvent->schedule(intrTick); + } +} + +void +EtherDev::cpuInterrupt() +{ + // Don't send an interrupt if there's already one + if (cpuPendingIntr) + return; + + // Don't send an interrupt if it's supposed to be delayed + if (intrTick > curTick) + return; + + // Whether or not there's a pending interrupt, we don't care about + // it anymore + intrEvent = 0; + intrTick = 0; + + // Send interrupt + cpuPendingIntr = true; + /** @todo rework the intctrl to be tsunami ok */ + //intctrl->post(TheISA::INTLEVEL_IRQ1, TheISA::INTINDEX_ETHERNET); + tsunami->cchip->postDRIR(configData->config.hdr.pci0.interruptLine); +} + +void +EtherDev::cpuIntrClear() +{ + if (cpuPendingIntr) { + cpuPendingIntr = false; + /** @todo rework the intctrl to be tsunami ok */ + //intctrl->clear(TheISA::INTLEVEL_IRQ1, TheISA::INTINDEX_ETHERNET); + tsunami->cchip->clearDRIR(configData->config.hdr.pci0.interruptLine); + } +} + +bool +EtherDev::cpuIntrPending() const +{ return cpuPendingIntr; } + +void +EtherDev::txReset() +{ + + DPRINTF(Ethernet, "transmit reset\n"); + + CTDD = false; + txFifoCnt = 0; + txFifoAvail = MAX_TX_FIFO_SIZE; + txHalt = false; + txFragPtr = 0; + assert(txDescCnt == 0); + txFifo.clear(); + regs.command &= ~CR_TXE; + txState = txIdle; + assert(txDmaState == dmaIdle); +} + +void +EtherDev::rxReset() +{ + DPRINTF(Ethernet, "receive reset\n"); + + CRDD = false; + assert(rxPktBytes == 0); + rxFifoCnt = 0; + rxHalt = false; + rxFragPtr = 0; + assert(rxDescCnt == 0); + assert(rxDmaState == dmaIdle); + rxFifo.clear(); + regs.command &= ~CR_RXE; + rxState = rxIdle; +} + +void +EtherDev::rxDmaReadCopy() +{ + assert(rxDmaState == dmaReading); + + memcpy(rxDmaData, physmem->dma_addr(rxDmaAddr, rxDmaLen), rxDmaLen); + rxDmaState = dmaIdle; + + DPRINTF(EthernetDMA, "rx dma read paddr=%#x len=%d\n", + rxDmaAddr, rxDmaLen); + DDUMP(EthernetDMA, rxDmaData, rxDmaLen); +} + +bool +EtherDev::doRxDmaRead() +{ + assert(rxDmaState == dmaIdle || rxDmaState == dmaReadWaiting); + rxDmaState = dmaReading; + + if (dmaInterface && !rxDmaFree) { + if (dmaInterface->busy()) + rxDmaState = dmaReadWaiting; + else + dmaInterface->doDMA(Read, rxDmaAddr, rxDmaLen, curTick, + &rxDmaReadEvent); + return true; + } + + if (dmaReadDelay == 0 && dmaReadFactor == 0) { + rxDmaReadCopy(); + return false; + } + + Tick factor = ((rxDmaLen + ULL(63)) >> ULL(6)) * dmaReadFactor; + Tick start = curTick + dmaReadDelay + factor; + rxDmaReadEvent.schedule(start); + return true; +} + +void +EtherDev::rxDmaReadDone() +{ + assert(rxDmaState == dmaReading); + rxDmaReadCopy(); + + // If the transmit state machine has a pending DMA, let it go first + if (txDmaState == dmaReadWaiting || txDmaState == dmaWriteWaiting) + txKick(); + + rxKick(); +} + +void +EtherDev::rxDmaWriteCopy() +{ + assert(rxDmaState == dmaWriting); + + memcpy(physmem->dma_addr(rxDmaAddr, rxDmaLen), rxDmaData, rxDmaLen); + rxDmaState = dmaIdle; + + DPRINTF(EthernetDMA, "rx dma write paddr=%#x len=%d\n", + rxDmaAddr, rxDmaLen); + DDUMP(EthernetDMA, rxDmaData, rxDmaLen); +} + +bool +EtherDev::doRxDmaWrite() +{ + assert(rxDmaState == dmaIdle || rxDmaState == dmaWriteWaiting); + rxDmaState = dmaWriting; + + if (dmaInterface && !rxDmaFree) { + if (dmaInterface->busy()) + rxDmaState = dmaWriteWaiting; + else + dmaInterface->doDMA(WriteInvalidate, rxDmaAddr, rxDmaLen, curTick, + &rxDmaWriteEvent); + return true; + } + + if (dmaWriteDelay == 0 && dmaWriteFactor == 0) { + rxDmaWriteCopy(); + return false; + } + + Tick factor = ((rxDmaLen + ULL(63)) >> ULL(6)) * dmaWriteFactor; + Tick start = curTick + dmaWriteDelay + factor; + rxDmaWriteEvent.schedule(start); + return true; +} + +void +EtherDev::rxDmaWriteDone() +{ + assert(rxDmaState == dmaWriting); + rxDmaWriteCopy(); + + // If the transmit state machine has a pending DMA, let it go first + if (txDmaState == dmaReadWaiting || txDmaState == dmaWriteWaiting) + txKick(); + + rxKick(); +} + +void +EtherDev::rxKick() +{ + DPRINTF(Ethernet, "receive kick state=%s (rxBuf.size=%d)\n", + NsRxStateStrings[rxState], rxFifo.size()); + + if (rxKickTick > curTick) { + DPRINTF(Ethernet, "receive kick exiting, can't run till %d\n", + rxKickTick); + return; + } + + next: + switch(rxDmaState) { + case dmaReadWaiting: + if (doRxDmaRead()) + goto exit; + break; + case dmaWriteWaiting: + if (doRxDmaWrite()) + goto exit; + break; + default: + break; + } + + // see state machine from spec for details + // the way this works is, if you finish work on one state and can go directly to + // another, you do that through jumping to the label "next". however, if you have + // intermediate work, like DMA so that you can't go to the next state yet, you go to + // exit and exit the loop. however, when the DMA is done it will trigger an + // event and come back to this loop. + switch (rxState) { + case rxIdle: + if (!regs.command & CR_RXE) { + DPRINTF(Ethernet, "Receive Disabled! Nothing to do.\n"); + goto exit; + } + + if (CRDD) { + rxState = rxDescRefr; + + rxDmaAddr = regs.rxdp & 0x3fffffff; + rxDmaData = &rxDescCache + offsetof(ns_desc, link); + rxDmaLen = sizeof(rxDescCache.link); + rxDmaFree = dmaDescFree; + + if (doRxDmaRead()) + goto exit; + } else { + rxState = rxDescRead; + + rxDmaAddr = regs.rxdp & 0x3fffffff; + rxDmaData = &rxDescCache; + rxDmaLen = sizeof(ns_desc); + rxDmaFree = dmaDescFree; + + if (doRxDmaRead()) + goto exit; + } + break; + + case rxDescRefr: + if (rxDmaState != dmaIdle) + goto exit; + + rxState = rxAdvance; + break; + + case rxDescRead: + if (rxDmaState != dmaIdle) + goto exit; + + if (rxDescCache.cmdsts & CMDSTS_OWN) { + rxState = rxIdle; + } else { + rxState = rxFifoBlock; + rxFragPtr = rxDescCache.bufptr; + rxDescCnt = rxDescCache.cmdsts & CMDSTS_LEN_MASK; + } + break; + + case rxFifoBlock: + if (!rxPacket) { + /** + * @todo in reality, we should be able to start processing + * the packet as it arrives, and not have to wait for the + * full packet ot be in the receive fifo. + */ + if (rxFifo.empty()) + goto exit; + + // If we don't have a packet, grab a new one from the fifo. + rxPacket = rxFifo.front(); + rxPktBytes = rxPacket->length; + rxPacketBufPtr = rxPacket->data; + + // sanity check - i think the driver behaves like this + assert(rxDescCnt >= rxPktBytes); + + // Must clear the value before popping to decrement the + // reference count + rxFifo.front() = NULL; + rxFifo.pop_front(); + } + + + // dont' need the && rxDescCnt > 0 if driver sanity check above holds + if (rxPktBytes > 0) { + rxState = rxFragWrite; + // don't need min<>(rxPktBytes,rxDescCnt) if above sanity check holds + rxXferLen = rxPktBytes; + + rxDmaAddr = rxFragPtr & 0x3fffffff; + rxDmaData = rxPacketBufPtr; + rxDmaLen = rxXferLen; + rxDmaFree = dmaDataFree; + + if (doRxDmaWrite()) + goto exit; + + } else { + rxState = rxDescWrite; + + //if (rxPktBytes == 0) { /* packet is done */ + assert(rxPktBytes == 0); + + rxFifoCnt -= rxPacket->length; + rxPacket = 0; + + rxDescCache.cmdsts |= CMDSTS_OWN; + rxDescCache.cmdsts &= ~CMDSTS_MORE; + rxDescCache.cmdsts |= CMDSTS_OK; + rxDescCache.cmdsts += rxPacket->length; //i.e. set CMDSTS_SIZE + +#if 0 + /* all the driver uses these are for its own stats keeping + which we don't care about, aren't necessary for functionality + and doing this would just slow us down. if they end up using + this in a later version for functional purposes, just undef + */ + if (rxFilterEnable) { + rxDescCache.cmdsts &= ~CMDSTS_DEST_MASK; + if (rxFifo.front()->IsUnicast()) + rxDescCache.cmdsts |= CMDSTS_DEST_SELF; + if (rxFifo.front()->IsMulticast()) + rxDescCache.cmdsts |= CMDSTS_DEST_MULTI; + if (rxFifo.front()->IsBroadcast()) + rxDescCache.cmdsts |= CMDSTS_DEST_MASK; + } +#endif + + eth_header *eth = (eth_header *) rxPacket->data; + // eth->type 0x800 indicated that it's an ip packet. + if (eth->type == 0x800 && extstsEnable) { + rxDescCache.extsts |= EXTSTS_IPPKT; + if (!ipChecksum(rxPacket, false)) + rxDescCache.extsts |= EXTSTS_IPERR; + ip_header *ip = rxFifo.front()->getIpHdr(); + + if (ip->protocol == 6) { + rxDescCache.extsts |= EXTSTS_TCPPKT; + if (!tcpChecksum(rxPacket, false)) + rxDescCache.extsts |= EXTSTS_TCPERR; + } else if (ip->protocol == 17) { + rxDescCache.extsts |= EXTSTS_UDPPKT; + if (!udpChecksum(rxPacket, false)) + rxDescCache.extsts |= EXTSTS_UDPERR; + } + } + + /* the driver seems to always receive into desc buffers + of size 1514, so you never have a pkt that is split + into multiple descriptors on the receive side, so + i don't implement that case, hence the assert above. + */ + + rxDmaAddr = (regs.rxdp + offsetof(ns_desc, cmdsts)) & 0x3fffffff; + rxDmaData = &(rxDescCache.cmdsts); + rxDmaLen = sizeof(rxDescCache.cmdsts) + sizeof(rxDescCache.extsts); + rxDmaFree = dmaDescFree; + + if (doRxDmaWrite()) + goto exit; + } + break; + + case rxFragWrite: + if (rxDmaState != dmaIdle) + goto exit; + + rxPacketBufPtr += rxXferLen; + rxFragPtr += rxXferLen; + rxPktBytes -= rxXferLen; + + rxState = rxFifoBlock; + break; + + case rxDescWrite: + if (rxDmaState != dmaIdle) + goto exit; + + assert(rxDescCache.cmdsts & CMDSTS_OWN); + + assert(rxPacket == 0); + devIntrPost(ISR_RXOK); + + if (rxDescCache.cmdsts & CMDSTS_INTR) + devIntrPost(ISR_RXDESC); + + if (rxHalt) { + rxState = rxIdle; + rxHalt = false; + } else + rxState = rxAdvance; + break; + + case rxAdvance: + if (rxDescCache.link == 0) { + rxState = rxIdle; + return; + } else { + rxState = rxDescRead; + regs.rxdp = rxDescCache.link; + CRDD = false; + + rxDmaAddr = regs.rxdp & 0x3fffffff; + rxDmaData = &rxDescCache; + rxDmaLen = sizeof(ns_desc); + rxDmaFree = dmaDescFree; + + if (doRxDmaRead()) + goto exit; + } + break; + + default: + panic("Invalid rxState!"); + } + + + DPRINTF(Ethernet, "entering next rx state = %s\n", + NsRxStateStrings[rxState]); + + if (rxState == rxIdle) { + regs.command &= ~CR_RXE; + devIntrPost(ISR_RXIDLE); + return; + } + + goto next; + + exit: + /** + * @todo do we want to schedule a future kick? + */ + DPRINTF(Ethernet, "rx state machine exited state=%s\n", + NsRxStateStrings[rxState]); +} + +void +EtherDev::transmit() +{ + if (txFifo.empty()) { + DPRINTF(Ethernet, "nothing to transmit\n"); + return; + } + + if (interface->sendPacket(txFifo.front())) { + DPRINTF(Ethernet, "transmit packet\n"); + DDUMP(Ethernet, txFifo.front()->data, txFifo.front()->length); + txBytes += txFifo.front()->length; + txPackets++; + + txFifoCnt -= (txFifo.front()->length - txPktXmitted); + txPktXmitted = 0; + txFifo.front() = NULL; + txFifo.pop_front(); + + /* normally do a writeback of the descriptor here, and ONLY after that is + done, send this interrupt. but since our stuff never actually fails, + just do this interrupt here, otherwise the code has to stray from this + nice format. besides, it's functionally the same. + */ + devIntrPost(ISR_TXOK); + } + + if (!txFifo.empty() && !txEvent.scheduled()) { + DPRINTF(Ethernet, "reschedule transmit\n"); + txEvent.schedule(curTick + 1000); + } +} + +void +EtherDev::txDmaReadCopy() +{ + assert(txDmaState == dmaReading); + + memcpy(txDmaData, physmem->dma_addr(txDmaAddr, txDmaLen), txDmaLen); + txDmaState = dmaIdle; + + DPRINTF(EthernetDMA, "tx dma read paddr=%#x len=%d\n", + txDmaAddr, txDmaLen); + DDUMP(EthernetDMA, txDmaData, txDmaLen); +} + +bool +EtherDev::doTxDmaRead() +{ + assert(txDmaState == dmaIdle || txDmaState == dmaReadWaiting); + txDmaState = dmaReading; + + if (dmaInterface && !txDmaFree) { + if (dmaInterface->busy()) + txDmaState = dmaReadWaiting; + else + dmaInterface->doDMA(Read, txDmaAddr, txDmaLen, curTick, + &txDmaReadEvent); + return true; + } + + if (dmaReadDelay == 0 && dmaReadFactor == 0.0) { + txDmaReadCopy(); + return false; + } + + Tick factor = ((txDmaLen + ULL(63)) >> ULL(6)) * dmaReadFactor; + Tick start = curTick + dmaReadDelay + factor; + txDmaReadEvent.schedule(start); + return true; +} + +void +EtherDev::txDmaReadDone() +{ + assert(txDmaState == dmaReading); + txDmaReadCopy(); + + // If the receive state machine has a pending DMA, let it go first + if (rxDmaState == dmaReadWaiting || rxDmaState == dmaWriteWaiting) + rxKick(); + + txKick(); +} + +void +EtherDev::txDmaWriteCopy() +{ + assert(txDmaState == dmaWriting); + + memcpy(physmem->dma_addr(txDmaAddr, txDmaLen), txDmaData, txDmaLen); + txDmaState = dmaIdle; + + DPRINTF(EthernetDMA, "tx dma write paddr=%#x len=%d\n", + txDmaAddr, txDmaLen); + DDUMP(EthernetDMA, txDmaData, txDmaLen); +} + +bool +EtherDev::doTxDmaWrite() +{ + assert(txDmaState == dmaIdle || txDmaState == dmaWriteWaiting); + txDmaState = dmaWriting; + + if (dmaInterface && !txDmaFree) { + if (dmaInterface->busy()) + txDmaState = dmaWriteWaiting; + else + dmaInterface->doDMA(WriteInvalidate, txDmaAddr, txDmaLen, curTick, + &txDmaWriteEvent); + return true; + } + + if (dmaWriteDelay == 0 && dmaWriteFactor == 0.0) { + txDmaWriteCopy(); + return false; + } + + Tick factor = ((txDmaLen + ULL(63)) >> ULL(6)) * dmaWriteFactor; + Tick start = curTick + dmaWriteDelay + factor; + txDmaWriteEvent.schedule(start); + return true; +} + +void +EtherDev::txDmaWriteDone() +{ + assert(txDmaState == dmaWriting); + txDmaWriteCopy(); + + // If the receive state machine has a pending DMA, let it go first + if (rxDmaState == dmaReadWaiting || rxDmaState == dmaWriteWaiting) + rxKick(); + + txKick(); +} + +void +EtherDev::txKick() +{ + DPRINTF(Ethernet, "transmit kick state=%s\n", NsTxStateStrings[txState]); + + if (rxKickTick > curTick) { + DPRINTF(Ethernet, "receive kick exiting, can't run till %d\n", + rxKickTick); + + return; + } + + next: + switch(txDmaState) { + case dmaReadWaiting: + if (doTxDmaRead()) + goto exit; + break; + case dmaWriteWaiting: + if (doTxDmaWrite()) + goto exit; + break; + default: + break; + } + + switch (txState) { + case txIdle: + if (!regs.command & CR_TXE) { + DPRINTF(Ethernet, "Transmit disabled. Nothing to do.\n"); + goto exit; + } + + if (CTDD) { + txState = txDescRefr; + + txDmaAddr = txDescCache.link & 0x3fffffff; + txDmaData = &txDescCache; + txDmaLen = sizeof(txDescCache.link); + txDmaFree = dmaDescFree; + + if (doTxDmaRead()) + goto exit; + + } else { + txState = txDescRead; + + txDmaAddr = regs.txdp & 0x3fffffff; + txDmaData = &txDescCache + offsetof(ns_desc, link); + txDmaLen = sizeof(ns_desc); + txDmaFree = dmaDescFree; + + if (doTxDmaRead()) + goto exit; + } + break; + + case txDescRefr: + if (txDmaState != dmaIdle) + goto exit; + + txState = txAdvance; + break; + + case txDescRead: + if (txDmaState != dmaIdle) + goto exit; + + if (txDescCache.cmdsts & CMDSTS_OWN) { + txState = txFifoBlock; + txFragPtr = txDescCache.bufptr; + txDescCnt = txDescCache.cmdsts & CMDSTS_LEN_MASK; + } else { + txState = txIdle; + } + break; + + case txFifoBlock: + if (!txPacket) { + DPRINTF(Ethernet, "starting the tx of a new packet\n"); + txPacket = new EtherPacket; + txPacket->data = new uint8_t[16384]; + txPacketBufPtr = txPacket->data; + } + + if (txDescCnt == 0) { + DPRINTF(Ethernet, "the txDescCnt == 0, done with descriptor\n"); + if (txDescCache.cmdsts & CMDSTS_MORE) { + DPRINTF(Ethernet, "there are more descriptors to come\n"); + txState = txDescWrite; + + txDescCache.cmdsts &= ~CMDSTS_OWN; + + txDmaAddr = (regs.txdp + offsetof(ns_desc, cmdsts)) & 0x3fffffff; + txDmaData = &(txDescCache.cmdsts); + txDmaLen = sizeof(txDescCache.cmdsts); + txDmaFree = dmaDescFree; + + if (doTxDmaWrite()) + goto exit; + + } else { /* this packet is totally done */ + DPRINTF(Ethernet, "This packet is done, let's wrap it up\n"); + /* deal with the the packet that just finished */ + if ((regs.vtcr & VTCR_PPCHK) && extstsEnable) { + if (txDescCache.extsts & EXTSTS_UDPPKT) { + udpChecksum(txPacket, true); + } else if (txDescCache.extsts & EXTSTS_TCPPKT) { + tcpChecksum(txPacket, true); + } else if (txDescCache.extsts & EXTSTS_IPPKT) { + ipChecksum(txPacket, true); + } + } + + txPacket->length = txPacketBufPtr - txPacket->data; + /* this is just because the receive can't handle a packet bigger + want to make sure */ + assert(txPacket->length <= 1514); + txFifo.push_back(txPacket); + + + /* this following section is not to spec, but functionally shouldn't + be any different. normally, the chip will wait til the transmit has + occurred before writing back the descriptor because it has to wait + to see that it was successfully transmitted to decide whether to set + CMDSTS_OK or not. however, in the simulator since it is always + successfully transmitted, and writing it exactly to spec would + complicate the code, we just do it here + */ + txDescCache.cmdsts &= ~CMDSTS_OWN; + txDescCache.cmdsts |= CMDSTS_OK; + + txDmaAddr = regs.txdp & 0x3fffffff; + txDmaData = &txDescCache + offsetof(ns_desc, cmdsts); + txDmaLen = sizeof(txDescCache.cmdsts) + sizeof(txDescCache.extsts); + txDmaFree = dmaDescFree; + + + if (doTxDmaWrite()) + goto exit; + + txPacket = 0; + transmit(); + + if (txHalt) { + txState = txIdle; + txHalt = false; + } else + txState = txAdvance; + } + } else { + DPRINTF(Ethernet, "this descriptor isn't done yet\n"); + /* the fill thresh is in units of 32 bytes, shift right by 8 to get the + value, shift left by 5 to get the real number of bytes */ + if (txFifoAvail < ((regs.txcfg & TXCFG_FLTH_MASK) >> 3)) { + DPRINTF(Ethernet, "txFifoAvail=%d, regs.txcfg & TXCFG_FLTH_MASK = %#x\n", + txFifoAvail, regs.txcfg & TXCFG_FLTH_MASK); + goto exit; + } + + txState = txFragRead; + + /* The number of bytes transferred is either whatever is left + in the descriptor (txDescCnt), or if there is not enough + room in the fifo, just whatever room is left in the fifo + */ + txXferLen = min<uint32_t>(txDescCnt, txFifoAvail); + + txDmaAddr = txFragPtr & 0x3fffffff; + txDmaData = txPacketBufPtr; + txDmaLen = txXferLen; + txDmaFree = dmaDataFree; + + if (doTxDmaRead()) + goto exit; + } + break; + + case txFragRead: + if (txDmaState != dmaIdle) + goto exit; + + txPacketBufPtr += txXferLen; + txFragPtr += txXferLen; + txFifoCnt += txXferLen; + txDescCnt -= txXferLen; + + txState = txFifoBlock; + break; + + case txDescWrite: + if (txDmaState != dmaIdle) + goto exit; + + if (txFifoCnt >= ((regs.txcfg & TXCFG_DRTH_MASK) << 5)) { + if (txFifo.empty()) { + uint32_t xmitted = (uint32_t) (txPacketBufPtr - txPacket->data - txPktXmitted); + txFifoCnt -= xmitted; + txPktXmitted += xmitted; + } else { + transmit(); + } + } + + if (txDescCache.cmdsts & CMDSTS_INTR) { + devIntrPost(ISR_TXDESC); + } + + txState = txAdvance; + break; + + case txAdvance: + if (txDescCache.link == 0) { + txState = txIdle; + } else { + txState = txDescRead; + regs.txdp = txDescCache.link; + CTDD = false; + + txDmaAddr = txDescCache.link & 0x3fffffff; + txDmaData = &txDescCache; + txDmaLen = sizeof(ns_desc); + txDmaFree = dmaDescFree; + + if (doTxDmaRead()) + goto exit; + } + break; + + default: + panic("invalid state"); + } + + DPRINTF(Ethernet, "entering next tx state=%s\n", + NsTxStateStrings[txState]); + + if (txState == txIdle) { + regs.command &= ~CR_TXE; + devIntrPost(ISR_TXIDLE); + return; + } + + goto next; + + exit: + /** + * @todo do we want to schedule a future kick? + */ + DPRINTF(Ethernet, "tx state machine exited state=%s\n", + NsTxStateStrings[txState]); +} + +void +EtherDev::transferDone() +{ + if (txFifo.empty()) + return; + + DPRINTF(Ethernet, "schedule transmit\n"); + + if (txEvent.scheduled()) + txEvent.reschedule(curTick + 1); + else + txEvent.schedule(curTick + 1); +} + +bool +EtherDev::rxFilter(PacketPtr packet) +{ + bool drop = true; + string type; + + if (packet->IsUnicast()) { + type = "unicast"; + + // If we're accepting all unicast addresses + if (acceptUnicast) + drop = false; + + // If we make a perfect match + if ((acceptPerfect) + && (memcmp(rom.perfectMatch, packet->data, sizeof(rom.perfectMatch)) == 0)) + drop = false; + + eth_header *eth = (eth_header *) packet->data; + if ((acceptArp) && (eth->type == 0x806)) + drop = false; + + } else if (packet->IsBroadcast()) { + type = "broadcast"; + + // if we're accepting broadcasts + if (acceptBroadcast) + drop = false; + + } else if (packet->IsMulticast()) { + type = "multicast"; + + // if we're accepting all multicasts + if (acceptMulticast) + drop = false; + + } else { + type = "unknown"; + + // oh well, punt on this one + } + + if (drop) { + DPRINTF(Ethernet, "rxFilter drop\n"); + DDUMP(EthernetData, packet->data, packet->length); + } + + return drop; +} + +bool +EtherDev::recvPacket(PacketPtr packet) +{ + rxBytes += packet->length; + rxPackets++; + + if (rxState == rxIdle) { + DPRINTF(Ethernet, "receive disabled...packet dropped\n"); + interface->recvDone(); + return true; + } + + if (rxFilterEnable && rxFilter(packet)) { + DPRINTF(Ethernet, "packet filtered...dropped\n"); + interface->recvDone(); + return true; + } + + if (rxFifoCnt + packet->length >= MAX_RX_FIFO_SIZE) { + DPRINTF(Ethernet, + "packet will not fit in receive buffer...packet dropped\n"); + devIntrPost(ISR_RXORN); + return false; + } + + rxFifo.push_back(packet); + rxFifoCnt += packet->length; + interface->recvDone(); + + rxKick(); + return true; +} + +/** + * does a udp checksum. if gen is true, then it generates it and puts it in the right place + * else, it just checks what it calculates against the value in the header in packet + */ +bool +EtherDev::udpChecksum(PacketPtr packet, bool gen) +{ + udp_header *hdr = (udp_header *) packet->getTransportHdr(); + + ip_header *ip = packet->getIpHdr(); + + pseudo_header *pseudo = new pseudo_header; + + pseudo->src_ip_addr = ip->src_ip_addr; + pseudo->dest_ip_addr = ip->dest_ip_addr; + pseudo->protocol = ip->protocol; + pseudo->len = hdr->len; + + uint16_t cksum = checksumCalc((uint16_t *) pseudo, (uint16_t *) hdr, + (uint32_t) hdr->len); + + delete pseudo; + if (gen) + hdr->chksum = cksum; + else + if (cksum != 0) + return false; + + return true; +} + +bool +EtherDev::tcpChecksum(PacketPtr packet, bool gen) +{ + tcp_header *hdr = (tcp_header *) packet->getTransportHdr(); + + ip_header *ip = packet->getIpHdr(); + + pseudo_header *pseudo = new pseudo_header; + + pseudo->src_ip_addr = ip->src_ip_addr; + pseudo->dest_ip_addr = ip->dest_ip_addr; + pseudo->protocol = ip->protocol; + pseudo->len = ip->dgram_len - (ip->vers_len & 0xf); + + uint16_t cksum = checksumCalc((uint16_t *) pseudo, (uint16_t *) hdr, + (uint32_t) pseudo->len); + + delete pseudo; + if (gen) + hdr->chksum = cksum; + else + if (cksum != 0) + return false; + + return true; +} + +bool +EtherDev::ipChecksum(PacketPtr packet, bool gen) +{ + ip_header *hdr = packet->getIpHdr(); + + uint16_t cksum = checksumCalc(NULL, (uint16_t *) hdr, (hdr->vers_len & 0xf)); + + if (gen) + hdr->hdr_chksum = cksum; + else + if (cksum != 0) + return false; + + return true; +} + +uint16_t +EtherDev::checksumCalc(uint16_t *pseudo, uint16_t *buf, uint32_t len) +{ + uint32_t sum = 0; + + uint16_t last_pad = 0; + if (len & 1) { + last_pad = buf[len/2] & 0xff; + len--; + sum += last_pad; + } + + if (pseudo) { + sum = pseudo[0] + pseudo[1] + pseudo[2] + + pseudo[3] + pseudo[4] + pseudo[5]; + } + + for (int i=0; i < (len/2); ++i) { + sum += buf[i]; + } + + while (sum >> 16) + sum = (sum >> 16) + (sum & 0xffff); + + return ~sum; +} + +//===================================================================== +// +// +void +EtherDev::serialize(ostream &os) +{ + /* + * Finalize any DMA events now. + */ + if (rxDmaReadEvent.scheduled()) + rxDmaReadCopy(); + if (rxDmaWriteEvent.scheduled()) + rxDmaWriteCopy(); + if (txDmaReadEvent.scheduled()) + txDmaReadCopy(); + if (txDmaWriteEvent.scheduled()) + txDmaWriteCopy(); + + /* + * Serialize the device registers + */ + SERIALIZE_SCALAR(regs.command); + SERIALIZE_SCALAR(regs.config); + SERIALIZE_SCALAR(regs.mear); + SERIALIZE_SCALAR(regs.ptscr); + SERIALIZE_SCALAR(regs.isr); + SERIALIZE_SCALAR(regs.imr); + SERIALIZE_SCALAR(regs.ier); + SERIALIZE_SCALAR(regs.ihr); + SERIALIZE_SCALAR(regs.txdp); + SERIALIZE_SCALAR(regs.txdp_hi); + SERIALIZE_SCALAR(regs.txcfg); + SERIALIZE_SCALAR(regs.gpior); + SERIALIZE_SCALAR(regs.rxdp); + SERIALIZE_SCALAR(regs.rxdp_hi); + SERIALIZE_SCALAR(regs.rxcfg); + SERIALIZE_SCALAR(regs.pqcr); + SERIALIZE_SCALAR(regs.wcsr); + SERIALIZE_SCALAR(regs.pcr); + SERIALIZE_SCALAR(regs.rfcr); + SERIALIZE_SCALAR(regs.rfdr); + SERIALIZE_SCALAR(regs.srr); + SERIALIZE_SCALAR(regs.mibc); + SERIALIZE_SCALAR(regs.vrcr); + SERIALIZE_SCALAR(regs.vtcr); + SERIALIZE_SCALAR(regs.vdr); + SERIALIZE_SCALAR(regs.ccsr); + SERIALIZE_SCALAR(regs.tbicr); + SERIALIZE_SCALAR(regs.tbisr); + SERIALIZE_SCALAR(regs.tanar); + SERIALIZE_SCALAR(regs.tanlpar); + SERIALIZE_SCALAR(regs.taner); + SERIALIZE_SCALAR(regs.tesr); + + SERIALIZE_ARRAY(rom.perfectMatch, EADDR_LEN); + + /* + * Serialize the various helper variables + */ + uint32_t txPktBufPtr = (uint32_t) txPacketBufPtr; + SERIALIZE_SCALAR(txPktBufPtr); + uint32_t rxPktBufPtr = (uint32_t) rxPktBufPtr; + SERIALIZE_SCALAR(rxPktBufPtr); + SERIALIZE_SCALAR(txXferLen); + SERIALIZE_SCALAR(rxXferLen); + SERIALIZE_SCALAR(txPktXmitted); + + bool txPacketExists = txPacket; + SERIALIZE_SCALAR(txPacketExists); + bool rxPacketExists = rxPacket; + SERIALIZE_SCALAR(rxPacketExists); + + /* + * Serialize DescCaches + */ + SERIALIZE_SCALAR(txDescCache.link); + SERIALIZE_SCALAR(txDescCache.bufptr); + SERIALIZE_SCALAR(txDescCache.cmdsts); + SERIALIZE_SCALAR(txDescCache.extsts); + SERIALIZE_SCALAR(rxDescCache.link); + SERIALIZE_SCALAR(rxDescCache.bufptr); + SERIALIZE_SCALAR(rxDescCache.cmdsts); + SERIALIZE_SCALAR(rxDescCache.extsts); + + /* + * Serialize tx state machine + */ + int txNumPkts = txFifo.size(); + SERIALIZE_SCALAR(txNumPkts); + int txState = this->txState; + SERIALIZE_SCALAR(txState); + SERIALIZE_SCALAR(CTDD); + SERIALIZE_SCALAR(txFifoCnt); + SERIALIZE_SCALAR(txFifoAvail); + SERIALIZE_SCALAR(txHalt); + SERIALIZE_SCALAR(txFragPtr); + SERIALIZE_SCALAR(txDescCnt); + int txDmaState = this->txDmaState; + SERIALIZE_SCALAR(txDmaState); + + /* + * Serialize rx state machine + */ + int rxNumPkts = rxFifo.size(); + SERIALIZE_SCALAR(rxNumPkts); + int rxState = this->rxState; + SERIALIZE_SCALAR(rxState); + SERIALIZE_SCALAR(CRDD); + SERIALIZE_SCALAR(rxPktBytes); + SERIALIZE_SCALAR(rxFifoCnt); + SERIALIZE_SCALAR(rxHalt); + SERIALIZE_SCALAR(rxDescCnt); + int rxDmaState = this->rxDmaState; + SERIALIZE_SCALAR(rxDmaState); + + SERIALIZE_SCALAR(extstsEnable); + + /* + * If there's a pending transmit, store the time so we can + * reschedule it later + */ + Tick transmitTick = txEvent.scheduled() ? txEvent.when() - curTick : 0; + SERIALIZE_SCALAR(transmitTick); + + /* + * Keep track of pending interrupt status. + */ + SERIALIZE_SCALAR(intrTick); + SERIALIZE_SCALAR(cpuPendingIntr); + Tick intrEventTick = 0; + if (intrEvent) + intrEventTick = intrEvent->when(); + SERIALIZE_SCALAR(intrEventTick); + + int i = 0; + for (pktiter_t p = rxFifo.begin(); p != rxFifo.end(); ++p) { + nameOut(os, csprintf("%s.rxFifo%d", name(), i++)); + (*p)->serialize(os); + } + if (rxPacketExists) { + nameOut(os, csprintf("%s.rxPacket", name())); + rxPacket->serialize(os); + } + i = 0; + for (pktiter_t p = txFifo.begin(); p != txFifo.end(); ++p) { + nameOut(os, csprintf("%s.txFifo%d", name(), i++)); + (*p)->serialize(os); + } + if (txPacketExists) { + nameOut(os, csprintf("%s.txPacket", name())); + txPacket->serialize(os); + } +} + +void +EtherDev::unserialize(Checkpoint *cp, const std::string §ion) +{ + UNSERIALIZE_SCALAR(regs.command); + UNSERIALIZE_SCALAR(regs.config); + UNSERIALIZE_SCALAR(regs.mear); + UNSERIALIZE_SCALAR(regs.ptscr); + UNSERIALIZE_SCALAR(regs.isr); + UNSERIALIZE_SCALAR(regs.imr); + UNSERIALIZE_SCALAR(regs.ier); + UNSERIALIZE_SCALAR(regs.ihr); + UNSERIALIZE_SCALAR(regs.txdp); + UNSERIALIZE_SCALAR(regs.txdp_hi); + UNSERIALIZE_SCALAR(regs.txcfg); + UNSERIALIZE_SCALAR(regs.gpior); + UNSERIALIZE_SCALAR(regs.rxdp); + UNSERIALIZE_SCALAR(regs.rxdp_hi); + UNSERIALIZE_SCALAR(regs.rxcfg); + UNSERIALIZE_SCALAR(regs.pqcr); + UNSERIALIZE_SCALAR(regs.wcsr); + UNSERIALIZE_SCALAR(regs.pcr); + UNSERIALIZE_SCALAR(regs.rfcr); + UNSERIALIZE_SCALAR(regs.rfdr); + UNSERIALIZE_SCALAR(regs.srr); + UNSERIALIZE_SCALAR(regs.mibc); + UNSERIALIZE_SCALAR(regs.vrcr); + UNSERIALIZE_SCALAR(regs.vtcr); + UNSERIALIZE_SCALAR(regs.vdr); + UNSERIALIZE_SCALAR(regs.ccsr); + UNSERIALIZE_SCALAR(regs.tbicr); + UNSERIALIZE_SCALAR(regs.tbisr); + UNSERIALIZE_SCALAR(regs.tanar); + UNSERIALIZE_SCALAR(regs.tanlpar); + UNSERIALIZE_SCALAR(regs.taner); + UNSERIALIZE_SCALAR(regs.tesr); + + UNSERIALIZE_ARRAY(rom.perfectMatch, EADDR_LEN); + + /* + * unserialize the various helper variables + */ + uint32_t txPktBufPtr; + UNSERIALIZE_SCALAR(txPktBufPtr); + txPacketBufPtr = (uint8_t *) txPktBufPtr; + uint32_t rxPktBufPtr; + UNSERIALIZE_SCALAR(rxPktBufPtr); + rxPacketBufPtr = (uint8_t *) rxPktBufPtr; + UNSERIALIZE_SCALAR(txXferLen); + UNSERIALIZE_SCALAR(rxXferLen); + UNSERIALIZE_SCALAR(txPktXmitted); + + bool txPacketExists; + UNSERIALIZE_SCALAR(txPacketExists); + bool rxPacketExists; + UNSERIALIZE_SCALAR(rxPacketExists); + + /* + * Unserialize DescCaches + */ + UNSERIALIZE_SCALAR(txDescCache.link); + UNSERIALIZE_SCALAR(txDescCache.bufptr); + UNSERIALIZE_SCALAR(txDescCache.cmdsts); + UNSERIALIZE_SCALAR(txDescCache.extsts); + UNSERIALIZE_SCALAR(rxDescCache.link); + UNSERIALIZE_SCALAR(rxDescCache.bufptr); + UNSERIALIZE_SCALAR(rxDescCache.cmdsts); + UNSERIALIZE_SCALAR(rxDescCache.extsts); + + /* + * unserialize tx state machine + */ + int txNumPkts; + UNSERIALIZE_SCALAR(txNumPkts); + int txState; + UNSERIALIZE_SCALAR(txState); + this->txState = (TxState) txState; + UNSERIALIZE_SCALAR(CTDD); + UNSERIALIZE_SCALAR(txFifoCnt); + UNSERIALIZE_SCALAR(txFifoAvail); + UNSERIALIZE_SCALAR(txHalt); + UNSERIALIZE_SCALAR(txFragPtr); + UNSERIALIZE_SCALAR(txDescCnt); + int txDmaState; + UNSERIALIZE_SCALAR(txDmaState); + this->txDmaState = (DmaState) txDmaState; + + /* + * unserialize rx state machine + */ + int rxNumPkts; + UNSERIALIZE_SCALAR(rxNumPkts); + int rxState; + UNSERIALIZE_SCALAR(rxState); + this->rxState = (RxState) rxState; + UNSERIALIZE_SCALAR(CRDD); + UNSERIALIZE_SCALAR(rxPktBytes); + UNSERIALIZE_SCALAR(rxFifoCnt); + UNSERIALIZE_SCALAR(rxHalt); + UNSERIALIZE_SCALAR(rxDescCnt); + int rxDmaState; + UNSERIALIZE_SCALAR(rxDmaState); + this->rxDmaState = (DmaState) rxDmaState; + + UNSERIALIZE_SCALAR(extstsEnable); + + /* + * If there's a pending transmit, store the time so we can + * reschedule it later + */ + Tick transmitTick; + UNSERIALIZE_SCALAR(transmitTick); + if (transmitTick) + txEvent.schedule(curTick + transmitTick); + + /* + * Keep track of pending interrupt status. + */ + UNSERIALIZE_SCALAR(intrTick); + UNSERIALIZE_SCALAR(cpuPendingIntr); + Tick intrEventTick; + UNSERIALIZE_SCALAR(intrEventTick); + if (intrEventTick) { + intrEvent = new IntrEvent(this, true); + intrEvent->schedule(intrEventTick); + } + + for (int i = 0; i < rxNumPkts; ++i) { + PacketPtr p = new EtherPacket; + p->unserialize(cp, csprintf("%s.rxFifo%d", section, i)); + rxFifo.push_back(p); + } + rxPacket = NULL; + if (rxPacketExists) { + rxPacket = new EtherPacket; + rxPacket->unserialize(cp, csprintf("%s.rxPacket", section)); + } + for (int i = 0; i < txNumPkts; ++i) { + PacketPtr p = new EtherPacket; + p->unserialize(cp, csprintf("%s.rxFifo%d", section, i)); + txFifo.push_back(p); + } + if (txPacketExists) { + txPacket = new EtherPacket; + txPacket->unserialize(cp, csprintf("%s.txPacket", section)); + } +} + + +Tick +EtherDev::cacheAccess(MemReqPtr &req) +{ + DPRINTF(EthernetPIO, "timing access to paddr=%#x (daddr=%#x)\n", + req->paddr, req->paddr - addr); + return curTick + pioLatency; +} +//===================================================================== + + +BEGIN_DECLARE_SIM_OBJECT_PARAMS(EtherDevInt) + + SimObjectParam<EtherInt *> peer; + SimObjectParam<EtherDev *> device; + +END_DECLARE_SIM_OBJECT_PARAMS(EtherDevInt) + +BEGIN_INIT_SIM_OBJECT_PARAMS(EtherDevInt) + + INIT_PARAM_DFLT(peer, "peer interface", NULL), + INIT_PARAM(device, "Ethernet device of this interface") + +END_INIT_SIM_OBJECT_PARAMS(EtherDevInt) + +CREATE_SIM_OBJECT(EtherDevInt) +{ + EtherDevInt *dev_int = new EtherDevInt(getInstanceName(), device); + + EtherInt *p = (EtherInt *)peer; + if (p) { + dev_int->setPeer(p); + p->setPeer(dev_int); + } + + return dev_int; +} + +REGISTER_SIM_OBJECT("EtherDevInt", EtherDevInt) + + +BEGIN_DECLARE_SIM_OBJECT_PARAMS(EtherDev) + + Param<Tick> tx_delay; + Param<Tick> rx_delay; + SimObjectParam<IntrControl *> intr_ctrl; + Param<Tick> intr_delay; + SimObjectParam<MemoryController *> mmu; + SimObjectParam<PhysicalMemory *> physmem; + Param<Addr> addr; + Param<bool> rx_filter; + Param<string> hardware_address; + SimObjectParam<Bus*> header_bus; + SimObjectParam<Bus*> payload_bus; + SimObjectParam<HierParams *> hier; + Param<Tick> pio_latency; + Param<bool> dma_desc_free; + Param<bool> dma_data_free; + Param<Tick> dma_read_delay; + Param<Tick> dma_write_delay; + Param<Tick> dma_read_factor; + Param<Tick> dma_write_factor; + SimObjectParam<PciConfigAll *> configspace; + SimObjectParam<PciConfigData *> configdata; + SimObjectParam<Tsunami *> tsunami; + Param<uint32_t> pci_bus; + Param<uint32_t> pci_dev; + Param<uint32_t> pci_func; + +END_DECLARE_SIM_OBJECT_PARAMS(EtherDev) + +BEGIN_INIT_SIM_OBJECT_PARAMS(EtherDev) + + INIT_PARAM_DFLT(tx_delay, "Transmit Delay", 1000), + INIT_PARAM_DFLT(rx_delay, "Receive Delay", 1000), + INIT_PARAM(intr_ctrl, "Interrupt Controller"), + INIT_PARAM_DFLT(intr_delay, "Interrupt Delay in microseconds", 0), + INIT_PARAM(mmu, "Memory Controller"), + INIT_PARAM(physmem, "Physical Memory"), + INIT_PARAM(addr, "Device Address"), + INIT_PARAM_DFLT(rx_filter, "Enable Receive Filter", true), + INIT_PARAM_DFLT(hardware_address, "Ethernet Hardware Address", + "00:99:00:00:00:01"), + INIT_PARAM_DFLT(header_bus, "The IO Bus to attach to for headers", NULL), + INIT_PARAM_DFLT(payload_bus, "The IO Bus to attach to for payload", NULL), + INIT_PARAM_DFLT(hier, "Hierarchy global variables", &defaultHierParams), + INIT_PARAM_DFLT(pio_latency, "Programmed IO latency", 1000), + INIT_PARAM_DFLT(dma_desc_free, "DMA of Descriptors is free", false), + INIT_PARAM_DFLT(dma_data_free, "DMA of Data is free", false), + INIT_PARAM_DFLT(dma_read_delay, "fixed delay for dma reads", 0), + INIT_PARAM_DFLT(dma_write_delay, "fixed delay for dma writes", 0), + INIT_PARAM_DFLT(dma_read_factor, "multiplier for dma reads", 0), + INIT_PARAM_DFLT(dma_write_factor, "multiplier for dma writes", 0), + INIT_PARAM(configspace, "PCI Configspace"), + INIT_PARAM(configdata, "PCI Config data"), + INIT_PARAM(tsunami, "Tsunami"), + INIT_PARAM(pci_bus, "PCI bus"), + INIT_PARAM(pci_dev, "PCI device number"), + INIT_PARAM(pci_func, "PCI function code") + +END_INIT_SIM_OBJECT_PARAMS(EtherDev) + + +CREATE_SIM_OBJECT(EtherDev) +{ + int eaddr[6]; + sscanf(((string)hardware_address).c_str(), "%x:%x:%x:%x:%x:%x", + &eaddr[0], &eaddr[1], &eaddr[2], &eaddr[3], &eaddr[4], &eaddr[5]); + + return new EtherDev(getInstanceName(), intr_ctrl, intr_delay, + physmem, tx_delay, rx_delay, mmu, hier, header_bus, + payload_bus, pio_latency, dma_desc_free, dma_data_free, + dma_read_delay, dma_write_delay, dma_read_factor, + dma_write_factor, configspace, configdata, + tsunami, pci_bus, pci_dev, pci_func, rx_filter, eaddr, + addr); +} + +REGISTER_SIM_OBJECT("EtherDev", EtherDev) |