/* * 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 #include #include #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/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" using namespace std; /////////////////////////////////////////////////////////////////////// // // EtherDev PCI Device // EtherDev::EtherDev(const string &_name, DmaEngine *de, bool use_interface, IntrControl *i, MemoryController *mmu, PhysicalMemory *pmem, PCIConfigAll *cf, PciConfigData *cd, Tsunami *t, uint32_t bus, uint32_t dev, uint32_t func, bool rx_filter, const int eaddr[6], Tick tx_delay, Tick rx_delay, Addr addr, Addr mask) : PciDev(_name, mmu, cf, cd, bus, dev, func), tsunami(t), addr(addr), mask(mask), txPacketLen(0), txPacketBufPtr(NULL), rxPacketBufPtr(NULL), rxDescBufPtr(NULL), fragLen(0), rxCopied(0), txState(txIdle), CTDD(false), txFifoCnt(0), txFifoAvail(MAX_TX_FIFO_SIZE), txHalt(false), txPacketFlag(false), txFragPtr(0), txDescCnt(0), rxState(rxIdle), CRDD(false), rxPktBytes(0), rxFifoCnt(0), rxHalt(false), rxPacketFlag(false), rxFragPtr(0), rxDescCnt(0), extstsEnable(false), maxTxBurst(0), maxRxBurst(0), physmem(pmem), rxDescDoneCB(this), rxDoneCB(this), txDescDoneCB(this), txDoneCB(this), dma(de), readRequest(use_interface), writeRequest(use_interface), readDescRequest(use_interface), writeDescRequest(use_interface), interface(NULL), intctrl(i), txDelay(tx_delay), rxDelay(rx_delay), txEvent(this), cpuPendingIntr(false), rxFilterEnable(rx_filter), acceptBroadcast(false), acceptMulticast(false), acceptUnicast(false), acceptPerfect(false), acceptArp(false) { tsunami->ethernet = this; memset(®s, 0, sizeof(regs)); regsReset(); regs.perfectMatch[0] = eaddr[0]; regs.perfectMatch[1] = eaddr[1]; regs.perfectMatch[2] = eaddr[2]; regs.perfectMatch[3] = eaddr[3]; regs.perfectMatch[4] = eaddr[4]; regs.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; } 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"); } } 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"); } Fault EtherDev::read(MemReqPtr req, uint8_t *data) { DPRINTF(Ethernet, "read va=%#x size=%d\n", req->vaddr, req->size); Addr daddr = req->paddr - addr; if (daddr > LAST) panic("Accessing reserved register"); switch (req->size) { case sizeof(uint32_t): { uint32_t ® = *(uint32_t *)data; switch (daddr) { case CR: reg = regs.command; 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; case RFCR: reg = regs.rfcr; break; case RFDR: switch (regs.rfcr & RFCR_RFADDR) { case 0x000: reg = regs.perfectMatch[1] << 8; reg += regs.perfectMatch[0]; break; case 0x002: reg = regs.perfectMatch[3] << 8; reg += regs.perfectMatch[2]; break; case 0x004: reg = regs.perfectMatch[5] << 8; reg += regs.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(Ethernet, "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) { DPRINTF(Ethernet, "write va=%#x size=%d\n", req->vaddr, req->size); Addr daddr = req->paddr - addr; if (daddr > LAST && daddr <= RESERVED) panic("Accessing reserved register"); if (daddr > RESERVED) panic("higher memory accesses not implemented!\n"); if (req->size == sizeof(uint32_t)) { uint32_t reg = *(uint32_t *)data; DPRINTF(Ethernet, "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) { 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: regs.config = reg; 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"); #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) ; #endif 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 0 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; /* these control BISTs for various parts of chip - we don't care or do */ 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 #if 0 /* current 2.6 driver doesn't use these. if we upgrade, may need these */ if (reg & TXCFG_MXDMA1024) maxTxBurst = 1024; if (reg & TXCFG_MXDMA8) maxTxBurst = 8; if (reg & TXCFG_MXDMA16) maxTxBurst = 16; if (reg & TXCFG_MXDMA32) maxTxBurst = 32; if (reg & TXCFG_MXDMA64) maxTxBurst = 64; if (reg & TXCFG_MXDMA128) maxTxBurst = 128; if (reg & TXCFG_MXDMA256) maxTxBurst = 256; #endif if (reg & TXCFG_MXDMA512) maxTxBurst = 512; break; case GPIOR: regs.gpior = reg; /* these just control general purpose i/o pins, don't matter */ 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 if (reg & RXCFG_MXDMA512) maxRxBurst = 512; #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; 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); 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; 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)) cpuIntrPost(); } 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)) cpuIntrPost(); if (!(regs.isr & regs.imr)) cpuIntrClear(); } void EtherDev::devIntrChangeMask() { DPRINTF(Ethernet, "iterrupt mask changed\n"); if (regs.isr & regs.imr) cpuIntrPost(); else cpuIntrClear(); } void EtherDev::cpuIntrPost() { if (!cpuPendingIntr) { if (regs.ier) { cpuPendingIntr = true; intctrl->post(TheISA::INTLEVEL_IRQ1, TheISA::INTINDEX_ETHERNET); } } } void EtherDev::cpuIntrClear() { if (cpuPendingIntr) { cpuPendingIntr = false; intctrl->clear(TheISA::INTLEVEL_IRQ1, TheISA::INTINDEX_ETHERNET); } } bool EtherDev::cpuIntrPending() const { return cpuPendingIntr; } void EtherDev::txReset() { DPRINTF(Ethernet, "transmit reset\n"); txPacketFlag = false; CTDD = false; txFifoCnt = 0; txFifoAvail = 0; txHalt = false; txFifo.clear(); descAddrFifo.clear(); regs.command &= ~CR_TXE; txState = txIdle; } void EtherDev::rxReset() { DPRINTF(Ethernet, "receive reset\n"); rxPacketFlag = false; CRDD = false; fragLen = 0; rxFifoCnt = 0; rxHalt = false; rxFifo.clear(); regs.command &= ~CR_RXE; rxState = rxIdle; } /** * This sets up a DMA transfer to read one data segment from the rxFifo into * the buffer indicated by rxDescCache.bufptr. Assumes the value of rxFragPtr * is already correctly set. */ void EtherDev::writeOneFrag() { /* i think there is no need for an "in use" warning here like in old */ fragLen = rxFifo.front()->length; //length of whole packet fragLen = (fragLen < rxDescCnt) ? fragLen : rxDescCnt; writePhys.addr = rxFragPtr; writePhys.length = fragLen; // Set up DMA request area writeRequest.init(&rxDoneCB, 0, false, &writePhys, 1, fragLen, rxDescBufPtr, fragLen, curTick); dma->doTransfer(&readRequest); } void EtherDev::rxKick() { DPRINTF(Ethernet, "receive state machine activated!\n"); if (CRDD) { rxState = rxDescRefr; readOneDesc(rx, LINK_LEN); } else { rxState = rxDescRead; readOneDesc(rx); } } EtherDev::RxDescDone::RxDescDone(EtherDev *e) : ethernet(e) { } std::string EtherDev::RxDescDone::name() const { return ethernet->name() + ".rxDescDoneCB"; } void EtherDev::RxDescDone::process() { DPRINTF(Ethernet, "receive descriptor done callback\n"); ethernet->rxDescDone(); } void EtherDev::rxDescDone() { if (rxState == rxDescRefr) { if (rxDescCache.link == 0) { rxState = rxIdle; regs.command &= ~CR_RXE; devIntrPost(ISR_RXIDLE); return; } else { rxState = rxDescRead; regs.rxdp = rxDescCache.link; CRDD = false; readOneDesc(rx); } } else if (rxState == rxDescRead) { if (rxDescCache.cmdsts & CMDSTS_OWN) { rxState = rxIdle; regs.command &= ~CR_RXE; devIntrPost(ISR_RXIDLE); } else { rxState = rxFifoBlock; rxFragPtr = rxDescCache.bufptr; rxDescCnt = rxDescCache.cmdsts & CMDSTS_LEN_MASK; if (!rxFifo.empty()) { rxState = rxFragWrite; if (!rxPacketFlag) { // reading a new packet rxPacketBufPtr = rxFifo.front()->data; rxPacketBufPtr -= rxDescCnt; rxDescBufPtr = rxPacketBufPtr; rxCopied = 0; } else { rxDescBufPtr = rxPacketBufPtr - rxDescCnt; } writeOneFrag(); } } } else if (rxState == rxDescWrite) { devIntrPost(ISR_RXOK); if (rxDescCache.cmdsts & CMDSTS_INTR) devIntrPost(ISR_RXDESC); if (rxDescCache.link == 0 || ((rxPktBytes != 0) && rxHalt)) { rxState = rxIdle; regs.command &= ~CR_RXE; devIntrPost(ISR_RXIDLE); rxHalt = false; } else { rxState = rxDescRead; regs.rxdp = rxDescCache.link; CRDD = false; readOneDesc(rx); } } } EtherDev::RxDone::RxDone(EtherDev *e) : ethernet(e) { } std::string EtherDev::RxDone::name() const { return ethernet->name() + ".rxDoneCB"; } void EtherDev::RxDone::process() { DPRINTF(Ethernet, "receive done callback\n"); ethernet->rxDone(); } void EtherDev::rxDone() { DPRINTF(Ethernet, "packet received to host memory\n"); if (!rxDescCache.cmdsts & CMDSTS_OWN) panic("This descriptor is already owned by the driver!\n"); rxState = rxFifoBlock; rxCopied += fragLen; rxFifoCnt -= fragLen; if (rxDescCnt) { /* there is still data left in the descriptor */ rxState = rxFragWrite; rxDescBufPtr += fragLen; writeOneFrag(); } else { rxState = rxDescWrite; if (rxPktBytes == 0) { /* packet is done */ rxDescCache.cmdsts |= CMDSTS_OWN; rxDescCache.cmdsts &= ~CMDSTS_MORE; rxDescCache.cmdsts |= CMDSTS_OK; rxDescCache.cmdsts += rxCopied; //i.e. set CMDSTS_SIZE rxPacketFlag = false; 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; } PacketPtr &pkt = rxFifo.front(); eth_header *eth = (eth_header *) pkt->data; if (eth->type == 0x800 && extstsEnable) { rxDescCache.extsts |= EXTSTS_IPPKT; if (!ipChecksum(pkt, false)) rxDescCache.extsts |= EXTSTS_IPERR; ip_header *ip = rxFifo.front()->getIpHdr(); if (ip->protocol == 6) { rxDescCache.extsts |= EXTSTS_TCPPKT; if (!tcpChecksum(pkt, false)) rxDescCache.extsts |= EXTSTS_TCPERR; } else if (ip->protocol == 17) { rxDescCache.extsts |= EXTSTS_UDPPKT; if (!udpChecksum(pkt, false)) rxDescCache.extsts |= EXTSTS_UDPERR; } } rxFifo.front() = NULL; rxFifo.pop_front(); } else { /* just the descriptor is done */ rxDescCache.cmdsts |= CMDSTS_OWN; rxDescCache.cmdsts |= CMDSTS_MORE; } writeDescPhys.addr = regs.rxdp + LINK_LEN + BUFPTR_LEN; writeDescPhys.length = CMDSTS_LEN; writeDescRequest.init(&rxDescDoneCB, 0, true, &writeDescPhys, 1, CMDSTS_LEN, (uint8_t *) &rxDescCache.cmdsts, CMDSTS_LEN, curTick); } } /** * This sets up a DMA transfer to read one descriptor into the network device. */ void EtherDev::readOneDesc(dir_t dir, uint32_t len) { readDescPhys.addr = (dir == tx) ? regs.txdp : regs.rxdp; readDescPhys.length = len; ns_desc *cache = (dir == tx) ? &txDescCache : &rxDescCache; /* THIS ASSUMES THAT DESC_LEN < regs.txcfg's maxdma value, which is 512 bytes in the driver, so i'll just hard code it here */ readDescRequest.init(&txDescDoneCB, 0, false, &readDescPhys, 1, len, (uint8_t *) cache , len, curTick); dma->doTransfer(&readDescRequest); } /** * This sets up a DMA transfer to read one data segment of the descriptor in * txDescCache. Assumes the value of txFragPtr is already correctly set */ void EtherDev::readOneFrag() { /* i think there is no need for an "in use" warning here like in old */ fragLen = (txDescCnt < txFifoAvail) ? txDescCnt : txFifoAvail; readPhys.addr = txFragPtr; readPhys.length = fragLen; // Set up DMA request area readRequest.init(&txDoneCB, 0, false, &readPhys, 1, fragLen, txPacketBufPtr, fragLen, curTick); dma->doTransfer(&readRequest); } void EtherDev::transmit() { if (txFifo.empty()) { DPRINTF(Ethernet, "nothing to transmit\n"); return; } if (interface->sendPacket(txFifo.front())) { DPRINTF(Ethernet, "transmit packet\n"); txBytes += txFifo.front()->length; txPackets++; txFifoCnt -= txFifo.front()->length; txFifo.front() = NULL; txFifo.pop_front(); txDescCache.cmdsts &= ~CMDSTS_OK; } else { txDescCache.cmdsts &= ~CMDSTS_ERR; } txDescCache.cmdsts &= ~CMDSTS_OWN; writeDescPhys.addr = descAddrFifo.front() + LINK_LEN + BUFPTR_LEN; writeDescPhys.length = CMDSTS_LEN; descAddrFifo.front() = 0; descAddrFifo.pop_front(); writeDescRequest.init(&txDescDoneCB, 0, true, &writeDescPhys, 1, writeDescPhys.length, (uint8_t *) &(txDescCache.cmdsts), writeDescPhys.length, curTick); dma->doTransfer(&writeDescRequest); transmit(); } void EtherDev::txKick() { DPRINTF(Ethernet, "transmit state machine activated\n"); #if 0 if (DTRACE(Ethernet)) txDump(); #endif if (CTDD) { txState = txDescRefr; readOneDesc(tx, LINK_LEN); } else { txState = txDescRead; readOneDesc(tx); } } EtherDev::TxDescDone::TxDescDone(EtherDev *e) : ethernet(e) { } std::string EtherDev::TxDescDone::name() const { return ethernet->name() + ".txDescDoneCB"; } void EtherDev::TxDescDone::process() { DPRINTF(Ethernet, "transmit descriptor done callback\n"); ethernet->txDescDone(); } void EtherDev::txDescDone() { if (txState == txFifoBlock) { if (txDescCache.cmdsts & CMDSTS_OK) { devIntrPost(ISR_TXOK); } else if (txDescCache.cmdsts & CMDSTS_ERR) { devIntrPost(ISR_TXERR); } } else if (txState == txDescRefr || txState == txDescWrite) { if (txState == txDescWrite) { if (txDescCache.cmdsts & CMDSTS_INTR) { devIntrPost(ISR_TXDESC); } } if (txDescCache.link == 0) { txState = txIdle; regs.command &= ~CR_TXE; devIntrPost(ISR_TXIDLE); return; } else { txState = txDescRead; regs.txdp = txDescCache.link; CTDD = false; readOneDesc(tx); } } else if (txState == txDescRead) { if (txDescCache.cmdsts & CMDSTS_OWN) { txState = txFifoBlock; txFragPtr = txDescCache.bufptr; txDescCnt = txDescCache.cmdsts & CMDSTS_LEN_MASK; if (txFifoAvail >= ((regs.txcfg & TXCFG_FLTH_MASK) >> 8)) { txState = txFragRead; if (!txPacketFlag) { txPacketFlag = true; /* find the total length of this packet */ txPacketLen = txDescCnt; bool more = txDescCache.cmdsts & CMDSTS_MORE; uint8_t *addr = (uint8_t *) regs.txdp; while (more) { addr = physmem->dma_addr(((ns_desc *) addr)->link, sizeof(ns_desc)); /* !!!!!!mask needed? */ txPacketLen += ((ns_desc *)addr)->cmdsts & CMDSTS_LEN_MASK; more = ((ns_desc *) addr)->cmdsts & CMDSTS_MORE; } PacketPtr &packet = txDoneCB.packet; packet = new EtherPacket; packet->length = txPacketLen; packet->data = new uint8_t[txPacketLen]; txPacketBufPtr = packet->data; } readOneFrag(); } } else { txState = txIdle; regs.command &= ~CR_TXE; devIntrPost(ISR_TXIDLE); } } } EtherDev::TxDone::TxDone(EtherDev *e) : ethernet(e) { } std::string EtherDev::TxDone::name() const { return ethernet->name() + ".txDoneCB"; } void EtherDev::TxDone::process() { DPRINTF(Ethernet, "transmit done callback\n"); ethernet->txDone(packet); } void EtherDev::txDone(PacketPtr packet) { DPRINTF(Ethernet, "transmit done\n"); if (!txDescCache.cmdsts & CMDSTS_OWN) panic("This descriptor is already owned by the driver!\n"); txState = txFifoBlock; txPacketBufPtr += fragLen; /* hope this ptr manipulation is right! */ txDescCnt -= fragLen; txFifoCnt += fragLen; if (txFifoCnt >= (regs.txcfg & TXCFG_DRTH_MASK)) { if (txFifo.empty()) { txFifoCnt -= (uint32_t) (txPacketBufPtr - packet->data); } else { transmit(); } } if (txDescCnt) { /* if there is still more data to go in this desc */ if (txFifoAvail >= regs.txcfg & TXCFG_FLTH_MASK) { txState = txFragRead; readOneFrag(); } } else { /* this descriptor is done */ /* but there is more descriptors for this packet */ if (txDescCache.cmdsts & CMDSTS_MORE) { txState = txDescWrite; txDescCache.cmdsts &= ~CMDSTS_OWN; writeDescPhys.addr = regs.txdp + LINK_LEN + BUFPTR_LEN; writeDescPhys.length = CMDSTS_LEN; writeDescRequest.init(&txDescDoneCB, 0, true, &writeDescPhys, 1, writeDescPhys.length, (uint8_t*) &txDescCache.cmdsts, writeDescPhys.length, curTick); } else { /* this packet is totally done */ /* deal with the the packet that just finished */ if (regs.vtcr & VTCR_PPCHK && extstsEnable) { if (txDescCache.extsts & EXTSTS_UDPPKT) { udpChecksum(packet, true); } else if (txDescCache.extsts & EXTSTS_TCPPKT) { tcpChecksum(packet, true); } else if (txDescCache.extsts & EXTSTS_IPPKT) { ipChecksum(packet, true); } } txFifo.push_back(packet); transmit(); txPacketFlag = false; descAddrFifo.push_back(regs.txdp); /* if there is not another descriptor ready for reading, go idle */ if (txDescCache.link == 0 || txHalt) { txState = txIdle; devIntrPost(ISR_TXIDLE); txHalt = false; } else { /* else go read next descriptor */ txState = txDescRead; regs.txdp = txDescCache.link; CTDD = false; readOneDesc(tx); } } } } void EtherDev::transferDone() { if (txFifo.empty()) return; DPRINTF(Ethernet, "schedule transmit\n"); if (txEvent.scheduled()) txEvent.reschedule(curTick + 1); else txEvent.schedule(curTick + 1); } void EtherDev::txDump() const { #if 0 int i = tx_ptr; for (int loop = 0; loop < tx_ring_len; loop++) { es_desc *desc = &tx_ring[i]; if (desc->addr) cprintf("desc[%d]: addr=%#x, len=%d, flags=%#x\n", i, desc->addr, desc->length, desc->flags); if (++i >= tx_ring_len) i = 0; } #endif } void EtherDev::rxDump() const { #if 0 int i = rx_ptr; for (int loop = 0; loop < rx_ring_len; loop++) { es_desc *desc = &rx_ring[i]; if (desc->addr) cprintf("desc[%d]: addr=%#x, len=%d, flags=%#x\n", i, desc->addr, desc->length, desc->flags); if (++i >= rx_ring_len) i = 0; } #endif } 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(regs.perfectMatch, packet->data, sizeof(regs.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); rxPktBytes = packet->length; rxFifoCnt += packet->length; interface->recvDone(); return true; } 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 dp_regs::serialize(ostream &os) { SERIALIZE_SCALAR(command); SERIALIZE_SCALAR(config); SERIALIZE_SCALAR(isr); SERIALIZE_SCALAR(imr); } void dp_regs::unserialize(Checkpoint *cp, const std::string §ion) { UNSERIALIZE_SCALAR(command); UNSERIALIZE_SCALAR(config); UNSERIALIZE_SCALAR(isr); UNSERIALIZE_SCALAR(imr); #if 0 UNSERIALIZE_SCALAR(tx_ring); UNSERIALIZE_SCALAR(rx_ring); UNSERIALIZE_SCALAR(tx_ring_len); UNSERIALIZE_SCALAR(rx_ring_len); UNSERIALIZE_SCALAR(rom_addr); UNSERIALIZE_SCALAR(rom_data); UNSERIALIZE_SCALAR(rxfilt_ctl); UNSERIALIZE_SCALAR(rxfilt_data); UNSERIALIZE_ARRAY(perfect,EADDR_LEN); UNSERIALIZE_ARRAY(hash_table,ES_HASH_SIZE); UNSERIALIZE_SCALAR(tx_ring_ptr); UNSERIALIZE_SCALAR(rx_ring_ptr); #endif } //--------------------------------------- void EtherPacket::serialize(ostream &os) { SERIALIZE_SCALAR(length); SERIALIZE_ARRAY(data, length); } void EtherPacket::unserialize(Checkpoint *cp, const std::string §ion) { UNSERIALIZE_SCALAR(length); data = new uint8_t[length]; UNSERIALIZE_ARRAY(data, length); } //--------------------------------------- void EtherDev::serialize(ostream &os) { #if 0 regs.serialize(os); // tx_ring & rx_ring are contained in the physmem... SERIALIZE_SCALAR(cpuPendingIntr); SERIALIZE_SCALAR(tx_ptr); SERIALIZE_SCALAR(rx_ptr); SERIALIZE_SCALAR(rxDoneCB.ptr); SERIALIZE_SCALAR(rxDoneCB.ignore); SERIALIZE_SCALAR(txDoneCB.ptr); SERIALIZE_SCALAR(txDoneCB.ignore); for (int i=0; iserialize(os); } // Serialize rxPacket, because its data is needed for writeRequest if (rxPacketExists) { nameOut(os, csprintf("%s.rxPacket", name())); rxPacket->serialize(os); } // create a section for the readRequest nameOut(os, readRequest.name()); paramOut(os, csprintf("parent"), name()); paramOut(os, csprintf("id"), 0); readRequest.serialize(os); // create a section for the writeRequest nameOut(os, writeRequest.name()); paramOut(os, csprintf("parent"), name()); paramOut(os, csprintf("id"), 1); writeRequest.serialize(os); //Redo the buffers, this time outputing them to the file numTxPkts = 0; for (pktiter_t p=txbuf.begin(); p!=txbuf.end(); ++p) { nameOut(os, csprintf("%s.txbuf%d", name(),numTxPkts++)); (*p)->serialize(os); } numRxPkts = 0; for (pktiter_t p=rxbuf.begin(); p!=rxbuf.end(); ++p) { nameOut(os, csprintf("%s.rxbuf%d", name(),numRxPkts++)); (*p)->serialize(os); } #endif } void EtherDev::unserialize(Checkpoint *cp, const std::string §ion) { #if 0 regs.unserialize(cp, section); UNSERIALIZE_SCALAR(cpuPendingIntr); // initialize the tx_ring txReset(); // initialize the rx_ring rxReset(); UNSERIALIZE_SCALAR(tx_ptr); UNSERIALIZE_SCALAR(rx_ptr); PacketPtr p; UNSERIALIZE_SCALAR(txbuf_len); int numTxPkts; UNSERIALIZE_SCALAR(numTxPkts); for (int i=0; iunserialize(cp, csprintf("%s.txbuf%d", section, i)); txbuf.push_back(p); } UNSERIALIZE_SCALAR(rxbuf_len); int numRxPkts; UNSERIALIZE_SCALAR(numRxPkts); for (int i=0; iunserialize(cp, csprintf("%s.rxbuf%d", section, i)); rxbuf.push_back(p); } UNSERIALIZE_SCALAR(rxDoneCB.ptr); UNSERIALIZE_SCALAR(rxDoneCB.ignore); UNSERIALIZE_SCALAR(txDoneCB.ptr); UNSERIALIZE_SCALAR(txDoneCB.ignore); for (int i=0; iunserialize(cp, csprintf("%s.txPacket", section)); } // Unserialize the current rxPacket bool rxPacketExists; UNSERIALIZE_SCALAR(rxPacketExists); rxPacket = NULL; if (rxPacketExists) { rxPacket = new EtherPacket; rxPacket->unserialize(cp, csprintf("%s.rxPacket", section)); } std::string readReqName, writeReqName; UNSERIALIZE_SCALAR(readReqName); UNSERIALIZE_SCALAR(writeReqName); // Unserialize and fixup the readRequest readRequest.unserialize(cp, readReqName); readRequest.phys = readPhys; readRequest.bufferCB = 0; readRequest.dmaDoneCB = &txDoneCB; readRequest.data = NULL; if (txDoneCB.packet) readRequest.data = txDoneCB.packet->data; // Unserialize and fixup the writeRequest writeRequest.unserialize(cp, writeReqName); writeRequest.phys = writePhys; writeRequest.bufferCB = 0; writeRequest.dmaDoneCB = &rxDoneCB; writeRequest.data = NULL; if (rxPacket) writeRequest.data = rxPacket->data; #endif } //===================================================================== BEGIN_DECLARE_SIM_OBJECT_PARAMS(EtherDevInt) SimObjectParam peer; SimObjectParam 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 tx_delay; Param rx_delay; SimObjectParam engine; Param use_interface; SimObjectParam intr_ctrl; SimObjectParam mmu; SimObjectParam physmem; Param addr; Param mask; Param rx_filter; Param hardware_address; SimObjectParam configspace; SimObjectParam configdata; SimObjectParam tsunami; Param pci_bus; Param pci_dev; Param 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(engine, "DMA Engine"), INIT_PARAM_DFLT(use_interface, "Use DMA Interface", true), INIT_PARAM(intr_ctrl, "Interrupt Controller"), INIT_PARAM(mmu, "Memory Controller"), INIT_PARAM(physmem, "Physical Memory"), INIT_PARAM(addr, "Device Address"), INIT_PARAM(mask, "Address Mask"), 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(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(), engine, use_interface, intr_ctrl, mmu, physmem, configspace, configdata, tsunami, pci_bus, pci_dev, pci_func, rx_filter, eaddr, tx_delay, rx_delay, addr, mask); } REGISTER_SIM_OBJECT("EtherDev", EtherDev)