/* * Copyright (c) 2006 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. * * Authors: Ali Saidi */ /* @file * Device model for Intel's 8254x line of gigabit ethernet controllers. * In particular an 82547 revision 2 (82547GI) MAC because it seems to have the * fewest workarounds in the driver. It will probably work with most of the * other MACs with slight modifications. */ #include "dev/net/i8254xGBe.hh" /* * @todo really there are multiple dma engines.. we should implement them. */ #include #include #include "base/inet.hh" #include "base/trace.hh" #include "debug/Drain.hh" #include "debug/EthernetAll.hh" #include "mem/packet.hh" #include "mem/packet_access.hh" #include "params/IGbE.hh" #include "sim/stats.hh" #include "sim/system.hh" using namespace iGbReg; using namespace Net; IGbE::IGbE(const Params *p) : EtherDevice(p), etherInt(NULL), cpa(NULL), rxFifo(p->rx_fifo_size), txFifo(p->tx_fifo_size), inTick(false), rxTick(false), txTick(false), txFifoTick(false), rxDmaPacket(false), pktOffset(0), fetchDelay(p->fetch_delay), wbDelay(p->wb_delay), fetchCompDelay(p->fetch_comp_delay), wbCompDelay(p->wb_comp_delay), rxWriteDelay(p->rx_write_delay), txReadDelay(p->tx_read_delay), rdtrEvent([this]{ rdtrProcess(); }, name()), radvEvent([this]{ radvProcess(); }, name()), tadvEvent([this]{ tadvProcess(); }, name()), tidvEvent([this]{ tidvProcess(); }, name()), tickEvent([this]{ tick(); }, name()), interEvent([this]{ delayIntEvent(); }, name()), rxDescCache(this, name()+".RxDesc", p->rx_desc_cache_size), txDescCache(this, name()+".TxDesc", p->tx_desc_cache_size), lastInterrupt(0) { etherInt = new IGbEInt(name() + ".int", this); // Initialized internal registers per Intel documentation // All registers intialized to 0 by per register constructor regs.ctrl.fd(1); regs.ctrl.lrst(1); regs.ctrl.speed(2); regs.ctrl.frcspd(1); regs.sts.speed(3); // Say we're 1000Mbps regs.sts.fd(1); // full duplex regs.sts.lu(1); // link up regs.eecd.fwe(1); regs.eecd.ee_type(1); regs.imr = 0; regs.iam = 0; regs.rxdctl.gran(1); regs.rxdctl.wthresh(1); regs.fcrth(1); regs.tdwba = 0; regs.rlpml = 0; regs.sw_fw_sync = 0; regs.pba.rxa(0x30); regs.pba.txa(0x10); eeOpBits = 0; eeAddrBits = 0; eeDataBits = 0; eeOpcode = 0; // clear all 64 16 bit words of the eeprom memset(&flash, 0, EEPROM_SIZE*2); // Set the MAC address memcpy(flash, p->hardware_address.bytes(), ETH_ADDR_LEN); for (int x = 0; x < ETH_ADDR_LEN/2; x++) flash[x] = htobe(flash[x]); uint16_t csum = 0; for (int x = 0; x < EEPROM_SIZE; x++) csum += htobe(flash[x]); // Magic happy checksum value flash[EEPROM_SIZE-1] = htobe((uint16_t)(EEPROM_CSUM - csum)); // Store the MAC address as queue ID macAddr = p->hardware_address; rxFifo.clear(); txFifo.clear(); } IGbE::~IGbE() { delete etherInt; } void IGbE::init() { cpa = CPA::cpa(); PciDevice::init(); } EtherInt* IGbE::getEthPort(const std::string &if_name, int idx) { if (if_name == "interface") { if (etherInt->getPeer()) panic("Port already connected to\n"); return etherInt; } return NULL; } Tick IGbE::writeConfig(PacketPtr pkt) { int offset = pkt->getAddr() & PCI_CONFIG_SIZE; if (offset < PCI_DEVICE_SPECIFIC) PciDevice::writeConfig(pkt); else panic("Device specific PCI config space not implemented.\n"); // // Some work may need to be done here based for the pci COMMAND bits. // return configDelay; } // Handy macro for range-testing register access addresses #define IN_RANGE(val, base, len) (val >= base && val < (base + len)) Tick IGbE::read(PacketPtr pkt) { int bar; Addr daddr; if (!getBAR(pkt->getAddr(), bar, daddr)) panic("Invalid PCI memory access to unmapped memory.\n"); // Only Memory register BAR is allowed assert(bar == 0); // Only 32bit accesses allowed assert(pkt->getSize() == 4); DPRINTF(Ethernet, "Read device register %#X\n", daddr); // // Handle read of register here // switch (daddr) { case REG_CTRL: pkt->setLE(regs.ctrl()); break; case REG_STATUS: pkt->setLE(regs.sts()); break; case REG_EECD: pkt->setLE(regs.eecd()); break; case REG_EERD: pkt->setLE(regs.eerd()); break; case REG_CTRL_EXT: pkt->setLE(regs.ctrl_ext()); break; case REG_MDIC: pkt->setLE(regs.mdic()); break; case REG_ICR: DPRINTF(Ethernet, "Reading ICR. ICR=%#x IMR=%#x IAM=%#x IAME=%d\n", regs.icr(), regs.imr, regs.iam, regs.ctrl_ext.iame()); pkt->setLE(regs.icr()); if (regs.icr.int_assert() || regs.imr == 0) { regs.icr = regs.icr() & ~mask(30); DPRINTF(Ethernet, "Cleared ICR. ICR=%#x\n", regs.icr()); } if (regs.ctrl_ext.iame() && regs.icr.int_assert()) regs.imr &= ~regs.iam; chkInterrupt(); break; case REG_EICR: // This is only useful for MSI, but the driver reads it every time // Just don't do anything pkt->setLE(0); break; case REG_ITR: pkt->setLE(regs.itr()); break; case REG_RCTL: pkt->setLE(regs.rctl()); break; case REG_FCTTV: pkt->setLE(regs.fcttv()); break; case REG_TCTL: pkt->setLE(regs.tctl()); break; case REG_PBA: pkt->setLE(regs.pba()); break; case REG_WUC: case REG_WUFC: case REG_WUS: case REG_LEDCTL: pkt->setLE(0); // We don't care, so just return 0 break; case REG_FCRTL: pkt->setLE(regs.fcrtl()); break; case REG_FCRTH: pkt->setLE(regs.fcrth()); break; case REG_RDBAL: pkt->setLE(regs.rdba.rdbal()); break; case REG_RDBAH: pkt->setLE(regs.rdba.rdbah()); break; case REG_RDLEN: pkt->setLE(regs.rdlen()); break; case REG_SRRCTL: pkt->setLE(regs.srrctl()); break; case REG_RDH: pkt->setLE(regs.rdh()); break; case REG_RDT: pkt->setLE(regs.rdt()); break; case REG_RDTR: pkt->setLE(regs.rdtr()); if (regs.rdtr.fpd()) { rxDescCache.writeback(0); DPRINTF(EthernetIntr, "Posting interrupt because of RDTR.FPD write\n"); postInterrupt(IT_RXT); regs.rdtr.fpd(0); } break; case REG_RXDCTL: pkt->setLE(regs.rxdctl()); break; case REG_RADV: pkt->setLE(regs.radv()); break; case REG_TDBAL: pkt->setLE(regs.tdba.tdbal()); break; case REG_TDBAH: pkt->setLE(regs.tdba.tdbah()); break; case REG_TDLEN: pkt->setLE(regs.tdlen()); break; case REG_TDH: pkt->setLE(regs.tdh()); break; case REG_TXDCA_CTL: pkt->setLE(regs.txdca_ctl()); break; case REG_TDT: pkt->setLE(regs.tdt()); break; case REG_TIDV: pkt->setLE(regs.tidv()); break; case REG_TXDCTL: pkt->setLE(regs.txdctl()); break; case REG_TADV: pkt->setLE(regs.tadv()); break; case REG_TDWBAL: pkt->setLE(regs.tdwba & mask(32)); break; case REG_TDWBAH: pkt->setLE(regs.tdwba >> 32); break; case REG_RXCSUM: pkt->setLE(regs.rxcsum()); break; case REG_RLPML: pkt->setLE(regs.rlpml); break; case REG_RFCTL: pkt->setLE(regs.rfctl()); break; case REG_MANC: pkt->setLE(regs.manc()); break; case REG_SWSM: pkt->setLE(regs.swsm()); regs.swsm.smbi(1); break; case REG_FWSM: pkt->setLE(regs.fwsm()); break; case REG_SWFWSYNC: pkt->setLE(regs.sw_fw_sync); break; default: if (!IN_RANGE(daddr, REG_VFTA, VLAN_FILTER_TABLE_SIZE*4) && !IN_RANGE(daddr, REG_RAL, RCV_ADDRESS_TABLE_SIZE*8) && !IN_RANGE(daddr, REG_MTA, MULTICAST_TABLE_SIZE*4) && !IN_RANGE(daddr, REG_CRCERRS, STATS_REGS_SIZE)) panic("Read request to unknown register number: %#x\n", daddr); else pkt->setLE(0); }; pkt->makeAtomicResponse(); return pioDelay; } Tick IGbE::write(PacketPtr pkt) { int bar; Addr daddr; if (!getBAR(pkt->getAddr(), bar, daddr)) panic("Invalid PCI memory access to unmapped memory.\n"); // Only Memory register BAR is allowed assert(bar == 0); // Only 32bit accesses allowed assert(pkt->getSize() == sizeof(uint32_t)); DPRINTF(Ethernet, "Wrote device register %#X value %#X\n", daddr, pkt->getLE()); // // Handle write of register here // uint32_t val = pkt->getLE(); Regs::RCTL oldrctl; Regs::TCTL oldtctl; switch (daddr) { case REG_CTRL: regs.ctrl = val; if (regs.ctrl.tfce()) warn("TX Flow control enabled, should implement\n"); if (regs.ctrl.rfce()) warn("RX Flow control enabled, should implement\n"); break; case REG_CTRL_EXT: regs.ctrl_ext = val; break; case REG_STATUS: regs.sts = val; break; case REG_EECD: int oldClk; oldClk = regs.eecd.sk(); regs.eecd = val; // See if this is a eeprom access and emulate accordingly if (!oldClk && regs.eecd.sk()) { if (eeOpBits < 8) { eeOpcode = eeOpcode << 1 | regs.eecd.din(); eeOpBits++; } else if (eeAddrBits < 8 && eeOpcode == EEPROM_READ_OPCODE_SPI) { eeAddr = eeAddr << 1 | regs.eecd.din(); eeAddrBits++; } else if (eeDataBits < 16 && eeOpcode == EEPROM_READ_OPCODE_SPI) { assert(eeAddr>>1 < EEPROM_SIZE); DPRINTF(EthernetEEPROM, "EEPROM bit read: %d word: %#X\n", flash[eeAddr>>1] >> eeDataBits & 0x1, flash[eeAddr>>1]); regs.eecd.dout((flash[eeAddr>>1] >> (15-eeDataBits)) & 0x1); eeDataBits++; } else if (eeDataBits < 8 && eeOpcode == EEPROM_RDSR_OPCODE_SPI) { regs.eecd.dout(0); eeDataBits++; } else panic("What's going on with eeprom interface? opcode:" " %#x:%d addr: %#x:%d, data: %d\n", (uint32_t)eeOpcode, (uint32_t)eeOpBits, (uint32_t)eeAddr, (uint32_t)eeAddrBits, (uint32_t)eeDataBits); // Reset everything for the next command if ((eeDataBits == 16 && eeOpcode == EEPROM_READ_OPCODE_SPI) || (eeDataBits == 8 && eeOpcode == EEPROM_RDSR_OPCODE_SPI)) { eeOpBits = 0; eeAddrBits = 0; eeDataBits = 0; eeOpcode = 0; eeAddr = 0; } DPRINTF(EthernetEEPROM, "EEPROM: opcode: %#X:%d addr: %#X:%d\n", (uint32_t)eeOpcode, (uint32_t) eeOpBits, (uint32_t)eeAddr>>1, (uint32_t)eeAddrBits); if (eeOpBits == 8 && !(eeOpcode == EEPROM_READ_OPCODE_SPI || eeOpcode == EEPROM_RDSR_OPCODE_SPI )) panic("Unknown eeprom opcode: %#X:%d\n", (uint32_t)eeOpcode, (uint32_t)eeOpBits); } // If driver requests eeprom access, immediately give it to it regs.eecd.ee_gnt(regs.eecd.ee_req()); break; case REG_EERD: regs.eerd = val; if (regs.eerd.start()) { regs.eerd.done(1); assert(regs.eerd.addr() < EEPROM_SIZE); regs.eerd.data(flash[regs.eerd.addr()]); regs.eerd.start(0); DPRINTF(EthernetEEPROM, "EEPROM: read addr: %#X data %#x\n", regs.eerd.addr(), regs.eerd.data()); } break; case REG_MDIC: regs.mdic = val; if (regs.mdic.i()) panic("No support for interrupt on mdic complete\n"); if (regs.mdic.phyadd() != 1) panic("No support for reading anything but phy\n"); DPRINTF(Ethernet, "%s phy address %x\n", regs.mdic.op() == 1 ? "Writing" : "Reading", regs.mdic.regadd()); switch (regs.mdic.regadd()) { case PHY_PSTATUS: regs.mdic.data(0x796D); // link up break; case PHY_PID: regs.mdic.data(params()->phy_pid); break; case PHY_EPID: regs.mdic.data(params()->phy_epid); break; case PHY_GSTATUS: regs.mdic.data(0x7C00); break; case PHY_EPSTATUS: regs.mdic.data(0x3000); break; case PHY_AGC: regs.mdic.data(0x180); // some random length break; default: regs.mdic.data(0); } regs.mdic.r(1); break; case REG_ICR: DPRINTF(Ethernet, "Writing ICR. ICR=%#x IMR=%#x IAM=%#x IAME=%d\n", regs.icr(), regs.imr, regs.iam, regs.ctrl_ext.iame()); if (regs.ctrl_ext.iame()) regs.imr &= ~regs.iam; regs.icr = ~bits(val,30,0) & regs.icr(); chkInterrupt(); break; case REG_ITR: regs.itr = val; break; case REG_ICS: DPRINTF(EthernetIntr, "Posting interrupt because of ICS write\n"); postInterrupt((IntTypes)val); break; case REG_IMS: regs.imr |= val; chkInterrupt(); break; case REG_IMC: regs.imr &= ~val; chkInterrupt(); break; case REG_IAM: regs.iam = val; break; case REG_RCTL: oldrctl = regs.rctl; regs.rctl = val; if (regs.rctl.rst()) { rxDescCache.reset(); DPRINTF(EthernetSM, "RXS: Got RESET!\n"); rxFifo.clear(); regs.rctl.rst(0); } if (regs.rctl.en()) rxTick = true; restartClock(); break; case REG_FCTTV: regs.fcttv = val; break; case REG_TCTL: regs.tctl = val; oldtctl = regs.tctl; regs.tctl = val; if (regs.tctl.en()) txTick = true; restartClock(); if (regs.tctl.en() && !oldtctl.en()) { txDescCache.reset(); } break; case REG_PBA: regs.pba.rxa(val); regs.pba.txa(64 - regs.pba.rxa()); break; case REG_WUC: case REG_WUFC: case REG_WUS: case REG_LEDCTL: case REG_FCAL: case REG_FCAH: case REG_FCT: case REG_VET: case REG_AIFS: case REG_TIPG: ; // We don't care, so don't store anything break; case REG_IVAR0: warn("Writing to IVAR0, ignoring...\n"); break; case REG_FCRTL: regs.fcrtl = val; break; case REG_FCRTH: regs.fcrth = val; break; case REG_RDBAL: regs.rdba.rdbal( val & ~mask(4)); rxDescCache.areaChanged(); break; case REG_RDBAH: regs.rdba.rdbah(val); rxDescCache.areaChanged(); break; case REG_RDLEN: regs.rdlen = val & ~mask(7); rxDescCache.areaChanged(); break; case REG_SRRCTL: regs.srrctl = val; break; case REG_RDH: regs.rdh = val; rxDescCache.areaChanged(); break; case REG_RDT: regs.rdt = val; DPRINTF(EthernetSM, "RXS: RDT Updated.\n"); if (drainState() == DrainState::Running) { DPRINTF(EthernetSM, "RXS: RDT Fetching Descriptors!\n"); rxDescCache.fetchDescriptors(); } else { DPRINTF(EthernetSM, "RXS: RDT NOT Fetching Desc b/c draining!\n"); } break; case REG_RDTR: regs.rdtr = val; break; case REG_RADV: regs.radv = val; break; case REG_RXDCTL: regs.rxdctl = val; break; case REG_TDBAL: regs.tdba.tdbal( val & ~mask(4)); txDescCache.areaChanged(); break; case REG_TDBAH: regs.tdba.tdbah(val); txDescCache.areaChanged(); break; case REG_TDLEN: regs.tdlen = val & ~mask(7); txDescCache.areaChanged(); break; case REG_TDH: regs.tdh = val; txDescCache.areaChanged(); break; case REG_TXDCA_CTL: regs.txdca_ctl = val; if (regs.txdca_ctl.enabled()) panic("No support for DCA\n"); break; case REG_TDT: regs.tdt = val; DPRINTF(EthernetSM, "TXS: TX Tail pointer updated\n"); if (drainState() == DrainState::Running) { DPRINTF(EthernetSM, "TXS: TDT Fetching Descriptors!\n"); txDescCache.fetchDescriptors(); } else { DPRINTF(EthernetSM, "TXS: TDT NOT Fetching Desc b/c draining!\n"); } break; case REG_TIDV: regs.tidv = val; break; case REG_TXDCTL: regs.txdctl = val; break; case REG_TADV: regs.tadv = val; break; case REG_TDWBAL: regs.tdwba &= ~mask(32); regs.tdwba |= val; txDescCache.completionWriteback(regs.tdwba & ~mask(1), regs.tdwba & mask(1)); break; case REG_TDWBAH: regs.tdwba &= mask(32); regs.tdwba |= (uint64_t)val << 32; txDescCache.completionWriteback(regs.tdwba & ~mask(1), regs.tdwba & mask(1)); break; case REG_RXCSUM: regs.rxcsum = val; break; case REG_RLPML: regs.rlpml = val; break; case REG_RFCTL: regs.rfctl = val; if (regs.rfctl.exsten()) panic("Extended RX descriptors not implemented\n"); break; case REG_MANC: regs.manc = val; break; case REG_SWSM: regs.swsm = val; if (regs.fwsm.eep_fw_semaphore()) regs.swsm.swesmbi(0); break; case REG_SWFWSYNC: regs.sw_fw_sync = val; break; default: if (!IN_RANGE(daddr, REG_VFTA, VLAN_FILTER_TABLE_SIZE*4) && !IN_RANGE(daddr, REG_RAL, RCV_ADDRESS_TABLE_SIZE*8) && !IN_RANGE(daddr, REG_MTA, MULTICAST_TABLE_SIZE*4)) panic("Write request to unknown register number: %#x\n", daddr); }; pkt->makeAtomicResponse(); return pioDelay; } void IGbE::postInterrupt(IntTypes t, bool now) { assert(t); // Interrupt is already pending if (t & regs.icr() && !now) return; regs.icr = regs.icr() | t; Tick itr_interval = SimClock::Int::ns * 256 * regs.itr.interval(); DPRINTF(EthernetIntr, "EINT: postInterrupt() curTick(): %d itr: %d interval: %d\n", curTick(), regs.itr.interval(), itr_interval); if (regs.itr.interval() == 0 || now || lastInterrupt + itr_interval <= curTick()) { if (interEvent.scheduled()) { deschedule(interEvent); } cpuPostInt(); } else { Tick int_time = lastInterrupt + itr_interval; assert(int_time > 0); DPRINTF(EthernetIntr, "EINT: Scheduling timer interrupt for tick %d\n", int_time); if (!interEvent.scheduled()) { schedule(interEvent, int_time); } } } void IGbE::delayIntEvent() { cpuPostInt(); } void IGbE::cpuPostInt() { postedInterrupts++; if (!(regs.icr() & regs.imr)) { DPRINTF(Ethernet, "Interrupt Masked. Not Posting\n"); return; } DPRINTF(Ethernet, "Posting Interrupt\n"); if (interEvent.scheduled()) { deschedule(interEvent); } if (rdtrEvent.scheduled()) { regs.icr.rxt0(1); deschedule(rdtrEvent); } if (radvEvent.scheduled()) { regs.icr.rxt0(1); deschedule(radvEvent); } if (tadvEvent.scheduled()) { regs.icr.txdw(1); deschedule(tadvEvent); } if (tidvEvent.scheduled()) { regs.icr.txdw(1); deschedule(tidvEvent); } regs.icr.int_assert(1); DPRINTF(EthernetIntr, "EINT: Posting interrupt to CPU now. Vector %#x\n", regs.icr()); intrPost(); lastInterrupt = curTick(); } void IGbE::cpuClearInt() { if (regs.icr.int_assert()) { regs.icr.int_assert(0); DPRINTF(EthernetIntr, "EINT: Clearing interrupt to CPU now. Vector %#x\n", regs.icr()); intrClear(); } } void IGbE::chkInterrupt() { DPRINTF(Ethernet, "Checking interrupts icr: %#x imr: %#x\n", regs.icr(), regs.imr); // Check if we need to clear the cpu interrupt if (!(regs.icr() & regs.imr)) { DPRINTF(Ethernet, "Mask cleaned all interrupts\n"); if (interEvent.scheduled()) deschedule(interEvent); if (regs.icr.int_assert()) cpuClearInt(); } DPRINTF(Ethernet, "ITR = %#X itr.interval = %#X\n", regs.itr(), regs.itr.interval()); if (regs.icr() & regs.imr) { if (regs.itr.interval() == 0) { cpuPostInt(); } else { DPRINTF(Ethernet, "Possibly scheduling interrupt because of imr write\n"); if (!interEvent.scheduled()) { Tick t = curTick() + SimClock::Int::ns * 256 * regs.itr.interval(); DPRINTF(Ethernet, "Scheduling for %d\n", t); schedule(interEvent, t); } } } } ///////////////////////////// IGbE::DescCache ////////////////////////////// template IGbE::DescCache::DescCache(IGbE *i, const std::string n, int s) : igbe(i), _name(n), cachePnt(0), size(s), curFetching(0), wbOut(0), moreToWb(false), wbAlignment(0), pktPtr(NULL), wbDelayEvent([this]{ writeback1(); }, n), fetchDelayEvent([this]{ fetchDescriptors1(); }, n), fetchEvent([this]{ fetchComplete(); }, n), wbEvent([this]{ wbComplete(); }, n) { fetchBuf = new T[size]; wbBuf = new T[size]; } template IGbE::DescCache::~DescCache() { reset(); delete[] fetchBuf; delete[] wbBuf; } template void IGbE::DescCache::areaChanged() { if (usedCache.size() > 0 || curFetching || wbOut) panic("Descriptor Address, Length or Head changed. Bad\n"); reset(); } template void IGbE::DescCache::writeback(Addr aMask) { int curHead = descHead(); int max_to_wb = usedCache.size(); // Check if this writeback is less restrictive that the previous // and if so setup another one immediately following it if (wbOut) { if (aMask < wbAlignment) { moreToWb = true; wbAlignment = aMask; } DPRINTF(EthernetDesc, "Writing back already in process, returning\n"); return; } moreToWb = false; wbAlignment = aMask; DPRINTF(EthernetDesc, "Writing back descriptors head: %d tail: " "%d len: %d cachePnt: %d max_to_wb: %d descleft: %d\n", curHead, descTail(), descLen(), cachePnt, max_to_wb, descLeft()); if (max_to_wb + curHead >= descLen()) { max_to_wb = descLen() - curHead; moreToWb = true; // this is by definition aligned correctly } else if (wbAlignment != 0) { // align the wb point to the mask max_to_wb = max_to_wb & ~wbAlignment; } DPRINTF(EthernetDesc, "Writing back %d descriptors\n", max_to_wb); if (max_to_wb <= 0) { if (usedCache.size()) igbe->anBegin(annSmWb, "Wait Alignment", CPA::FL_WAIT); else igbe->anWe(annSmWb, annUsedCacheQ); return; } wbOut = max_to_wb; assert(!wbDelayEvent.scheduled()); igbe->schedule(wbDelayEvent, curTick() + igbe->wbDelay); igbe->anBegin(annSmWb, "Prepare Writeback Desc"); } template void IGbE::DescCache::writeback1() { // If we're draining delay issuing this DMA if (igbe->drainState() != DrainState::Running) { igbe->schedule(wbDelayEvent, curTick() + igbe->wbDelay); return; } DPRINTF(EthernetDesc, "Begining DMA of %d descriptors\n", wbOut); for (int x = 0; x < wbOut; x++) { assert(usedCache.size()); memcpy(&wbBuf[x], usedCache[x], sizeof(T)); igbe->anPq(annSmWb, annUsedCacheQ); igbe->anPq(annSmWb, annDescQ); igbe->anQ(annSmWb, annUsedDescQ); } igbe->anBegin(annSmWb, "Writeback Desc DMA"); assert(wbOut); igbe->dmaWrite(pciToDma(descBase() + descHead() * sizeof(T)), wbOut * sizeof(T), &wbEvent, (uint8_t*)wbBuf, igbe->wbCompDelay); } template void IGbE::DescCache::fetchDescriptors() { size_t max_to_fetch; if (curFetching) { DPRINTF(EthernetDesc, "Currently fetching %d descriptors, returning\n", curFetching); return; } if (descTail() >= cachePnt) max_to_fetch = descTail() - cachePnt; else max_to_fetch = descLen() - cachePnt; size_t free_cache = size - usedCache.size() - unusedCache.size(); if (!max_to_fetch) igbe->anWe(annSmFetch, annUnusedDescQ); else igbe->anPq(annSmFetch, annUnusedDescQ, max_to_fetch); if (max_to_fetch) { if (!free_cache) igbe->anWf(annSmFetch, annDescQ); else igbe->anRq(annSmFetch, annDescQ, free_cache); } max_to_fetch = std::min(max_to_fetch, free_cache); DPRINTF(EthernetDesc, "Fetching descriptors head: %d tail: " "%d len: %d cachePnt: %d max_to_fetch: %d descleft: %d\n", descHead(), descTail(), descLen(), cachePnt, max_to_fetch, descLeft()); // Nothing to do if (max_to_fetch == 0) return; // So we don't have two descriptor fetches going on at once curFetching = max_to_fetch; assert(!fetchDelayEvent.scheduled()); igbe->schedule(fetchDelayEvent, curTick() + igbe->fetchDelay); igbe->anBegin(annSmFetch, "Prepare Fetch Desc"); } template void IGbE::DescCache::fetchDescriptors1() { // If we're draining delay issuing this DMA if (igbe->drainState() != DrainState::Running) { igbe->schedule(fetchDelayEvent, curTick() + igbe->fetchDelay); return; } igbe->anBegin(annSmFetch, "Fetch Desc"); DPRINTF(EthernetDesc, "Fetching descriptors at %#x (%#x), size: %#x\n", descBase() + cachePnt * sizeof(T), pciToDma(descBase() + cachePnt * sizeof(T)), curFetching * sizeof(T)); assert(curFetching); igbe->dmaRead(pciToDma(descBase() + cachePnt * sizeof(T)), curFetching * sizeof(T), &fetchEvent, (uint8_t*)fetchBuf, igbe->fetchCompDelay); } template void IGbE::DescCache::fetchComplete() { T *newDesc; igbe->anBegin(annSmFetch, "Fetch Complete"); for (int x = 0; x < curFetching; x++) { newDesc = new T; memcpy(newDesc, &fetchBuf[x], sizeof(T)); unusedCache.push_back(newDesc); igbe->anDq(annSmFetch, annUnusedDescQ); igbe->anQ(annSmFetch, annUnusedCacheQ); igbe->anQ(annSmFetch, annDescQ); } #ifndef NDEBUG int oldCp = cachePnt; #endif cachePnt += curFetching; assert(cachePnt <= descLen()); if (cachePnt == descLen()) cachePnt = 0; curFetching = 0; DPRINTF(EthernetDesc, "Fetching complete cachePnt %d -> %d\n", oldCp, cachePnt); if ((descTail() >= cachePnt ? (descTail() - cachePnt) : (descLen() - cachePnt)) == 0) { igbe->anWe(annSmFetch, annUnusedDescQ); } else if (!(size - usedCache.size() - unusedCache.size())) { igbe->anWf(annSmFetch, annDescQ); } else { igbe->anBegin(annSmFetch, "Wait", CPA::FL_WAIT); } enableSm(); igbe->checkDrain(); } template void IGbE::DescCache::wbComplete() { igbe->anBegin(annSmWb, "Finish Writeback"); long curHead = descHead(); #ifndef NDEBUG long oldHead = curHead; #endif for (int x = 0; x < wbOut; x++) { assert(usedCache.size()); delete usedCache[0]; usedCache.pop_front(); igbe->anDq(annSmWb, annUsedCacheQ); igbe->anDq(annSmWb, annDescQ); } curHead += wbOut; wbOut = 0; if (curHead >= descLen()) curHead -= descLen(); // Update the head updateHead(curHead); DPRINTF(EthernetDesc, "Writeback complete curHead %d -> %d\n", oldHead, curHead); // If we still have more to wb, call wb now actionAfterWb(); if (moreToWb) { moreToWb = false; DPRINTF(EthernetDesc, "Writeback has more todo\n"); writeback(wbAlignment); } if (!wbOut) { igbe->checkDrain(); if (usedCache.size()) igbe->anBegin(annSmWb, "Wait", CPA::FL_WAIT); else igbe->anWe(annSmWb, annUsedCacheQ); } fetchAfterWb(); } template void IGbE::DescCache::reset() { DPRINTF(EthernetDesc, "Reseting descriptor cache\n"); for (typename CacheType::size_type x = 0; x < usedCache.size(); x++) delete usedCache[x]; for (typename CacheType::size_type x = 0; x < unusedCache.size(); x++) delete unusedCache[x]; usedCache.clear(); unusedCache.clear(); cachePnt = 0; } template void IGbE::DescCache::serialize(CheckpointOut &cp) const { SERIALIZE_SCALAR(cachePnt); SERIALIZE_SCALAR(curFetching); SERIALIZE_SCALAR(wbOut); SERIALIZE_SCALAR(moreToWb); SERIALIZE_SCALAR(wbAlignment); typename CacheType::size_type usedCacheSize = usedCache.size(); SERIALIZE_SCALAR(usedCacheSize); for (typename CacheType::size_type x = 0; x < usedCacheSize; x++) { arrayParamOut(cp, csprintf("usedCache_%d", x), (uint8_t*)usedCache[x],sizeof(T)); } typename CacheType::size_type unusedCacheSize = unusedCache.size(); SERIALIZE_SCALAR(unusedCacheSize); for (typename CacheType::size_type x = 0; x < unusedCacheSize; x++) { arrayParamOut(cp, csprintf("unusedCache_%d", x), (uint8_t*)unusedCache[x],sizeof(T)); } Tick fetch_delay = 0, wb_delay = 0; if (fetchDelayEvent.scheduled()) fetch_delay = fetchDelayEvent.when(); SERIALIZE_SCALAR(fetch_delay); if (wbDelayEvent.scheduled()) wb_delay = wbDelayEvent.when(); SERIALIZE_SCALAR(wb_delay); } template void IGbE::DescCache::unserialize(CheckpointIn &cp) { UNSERIALIZE_SCALAR(cachePnt); UNSERIALIZE_SCALAR(curFetching); UNSERIALIZE_SCALAR(wbOut); UNSERIALIZE_SCALAR(moreToWb); UNSERIALIZE_SCALAR(wbAlignment); typename CacheType::size_type usedCacheSize; UNSERIALIZE_SCALAR(usedCacheSize); T *temp; for (typename CacheType::size_type x = 0; x < usedCacheSize; x++) { temp = new T; arrayParamIn(cp, csprintf("usedCache_%d", x), (uint8_t*)temp,sizeof(T)); usedCache.push_back(temp); } typename CacheType::size_type unusedCacheSize; UNSERIALIZE_SCALAR(unusedCacheSize); for (typename CacheType::size_type x = 0; x < unusedCacheSize; x++) { temp = new T; arrayParamIn(cp, csprintf("unusedCache_%d", x), (uint8_t*)temp,sizeof(T)); unusedCache.push_back(temp); } Tick fetch_delay = 0, wb_delay = 0; UNSERIALIZE_SCALAR(fetch_delay); UNSERIALIZE_SCALAR(wb_delay); if (fetch_delay) igbe->schedule(fetchDelayEvent, fetch_delay); if (wb_delay) igbe->schedule(wbDelayEvent, wb_delay); } ///////////////////////////// IGbE::RxDescCache ////////////////////////////// IGbE::RxDescCache::RxDescCache(IGbE *i, const std::string n, int s) : DescCache(i, n, s), pktDone(false), splitCount(0), pktEvent([this]{ pktComplete(); }, n), pktHdrEvent([this]{ pktSplitDone(); }, n), pktDataEvent([this]{ pktSplitDone(); }, n) { annSmFetch = "RX Desc Fetch"; annSmWb = "RX Desc Writeback"; annUnusedDescQ = "RX Unused Descriptors"; annUnusedCacheQ = "RX Unused Descriptor Cache"; annUsedCacheQ = "RX Used Descriptor Cache"; annUsedDescQ = "RX Used Descriptors"; annDescQ = "RX Descriptors"; } void IGbE::RxDescCache::pktSplitDone() { splitCount++; DPRINTF(EthernetDesc, "Part of split packet done: splitcount now %d\n", splitCount); assert(splitCount <= 2); if (splitCount != 2) return; splitCount = 0; DPRINTF(EthernetDesc, "Part of split packet done: calling pktComplete()\n"); pktComplete(); } int IGbE::RxDescCache::writePacket(EthPacketPtr packet, int pkt_offset) { assert(unusedCache.size()); //if (!unusedCache.size()) // return false; pktPtr = packet; pktDone = false; unsigned buf_len, hdr_len; RxDesc *desc = unusedCache.front(); switch (igbe->regs.srrctl.desctype()) { case RXDT_LEGACY: assert(pkt_offset == 0); bytesCopied = packet->length; DPRINTF(EthernetDesc, "Packet Length: %d Desc Size: %d\n", packet->length, igbe->regs.rctl.descSize()); assert(packet->length < igbe->regs.rctl.descSize()); igbe->dmaWrite(pciToDma(desc->legacy.buf), packet->length, &pktEvent, packet->data, igbe->rxWriteDelay); break; case RXDT_ADV_ONEBUF: assert(pkt_offset == 0); bytesCopied = packet->length; buf_len = igbe->regs.rctl.lpe() ? igbe->regs.srrctl.bufLen() : igbe->regs.rctl.descSize(); DPRINTF(EthernetDesc, "Packet Length: %d srrctl: %#x Desc Size: %d\n", packet->length, igbe->regs.srrctl(), buf_len); assert(packet->length < buf_len); igbe->dmaWrite(pciToDma(desc->adv_read.pkt), packet->length, &pktEvent, packet->data, igbe->rxWriteDelay); desc->adv_wb.header_len = htole(0); desc->adv_wb.sph = htole(0); desc->adv_wb.pkt_len = htole((uint16_t)(pktPtr->length)); break; case RXDT_ADV_SPLIT_A: int split_point; buf_len = igbe->regs.rctl.lpe() ? igbe->regs.srrctl.bufLen() : igbe->regs.rctl.descSize(); hdr_len = igbe->regs.rctl.lpe() ? igbe->regs.srrctl.hdrLen() : 0; DPRINTF(EthernetDesc, "lpe: %d Packet Length: %d offset: %d srrctl: %#x " "hdr addr: %#x Hdr Size: %d desc addr: %#x Desc Size: %d\n", igbe->regs.rctl.lpe(), packet->length, pkt_offset, igbe->regs.srrctl(), desc->adv_read.hdr, hdr_len, desc->adv_read.pkt, buf_len); split_point = hsplit(pktPtr); if (packet->length <= hdr_len) { bytesCopied = packet->length; assert(pkt_offset == 0); DPRINTF(EthernetDesc, "Hdr split: Entire packet in header\n"); igbe->dmaWrite(pciToDma(desc->adv_read.hdr), packet->length, &pktEvent, packet->data, igbe->rxWriteDelay); desc->adv_wb.header_len = htole((uint16_t)packet->length); desc->adv_wb.sph = htole(0); desc->adv_wb.pkt_len = htole(0); } else if (split_point) { if (pkt_offset) { // we are only copying some data, header/data has already been // copied int max_to_copy = std::min(packet->length - pkt_offset, buf_len); bytesCopied += max_to_copy; DPRINTF(EthernetDesc, "Hdr split: Continuing data buffer copy\n"); igbe->dmaWrite(pciToDma(desc->adv_read.pkt), max_to_copy, &pktEvent, packet->data + pkt_offset, igbe->rxWriteDelay); desc->adv_wb.header_len = htole(0); desc->adv_wb.pkt_len = htole((uint16_t)max_to_copy); desc->adv_wb.sph = htole(0); } else { int max_to_copy = std::min(packet->length - split_point, buf_len); bytesCopied += max_to_copy + split_point; DPRINTF(EthernetDesc, "Hdr split: splitting at %d\n", split_point); igbe->dmaWrite(pciToDma(desc->adv_read.hdr), split_point, &pktHdrEvent, packet->data, igbe->rxWriteDelay); igbe->dmaWrite(pciToDma(desc->adv_read.pkt), max_to_copy, &pktDataEvent, packet->data + split_point, igbe->rxWriteDelay); desc->adv_wb.header_len = htole(split_point); desc->adv_wb.sph = 1; desc->adv_wb.pkt_len = htole((uint16_t)(max_to_copy)); } } else { panic("Header split not fitting within header buffer or " "undecodable packet not fitting in header unsupported\n"); } break; default: panic("Unimplemnted RX receive buffer type: %d\n", igbe->regs.srrctl.desctype()); } return bytesCopied; } void IGbE::RxDescCache::pktComplete() { assert(unusedCache.size()); RxDesc *desc; desc = unusedCache.front(); igbe->anBegin("RXS", "Update Desc"); uint16_t crcfixup = igbe->regs.rctl.secrc() ? 0 : 4 ; DPRINTF(EthernetDesc, "pktPtr->length: %d bytesCopied: %d " "stripcrc offset: %d value written: %d %d\n", pktPtr->length, bytesCopied, crcfixup, htole((uint16_t)(pktPtr->length + crcfixup)), (uint16_t)(pktPtr->length + crcfixup)); // no support for anything but starting at 0 assert(igbe->regs.rxcsum.pcss() == 0); DPRINTF(EthernetDesc, "Packet written to memory updating Descriptor\n"); uint16_t status = RXDS_DD; uint8_t err = 0; uint16_t ext_err = 0; uint16_t csum = 0; uint16_t ptype = 0; uint16_t ip_id = 0; assert(bytesCopied <= pktPtr->length); if (bytesCopied == pktPtr->length) status |= RXDS_EOP; IpPtr ip(pktPtr); Ip6Ptr ip6(pktPtr); if (ip || ip6) { if (ip) { DPRINTF(EthernetDesc, "Proccesing Ip packet with Id=%d\n", ip->id()); ptype |= RXDP_IPV4; ip_id = ip->id(); } if (ip6) ptype |= RXDP_IPV6; if (ip && igbe->regs.rxcsum.ipofld()) { DPRINTF(EthernetDesc, "Checking IP checksum\n"); status |= RXDS_IPCS; csum = htole(cksum(ip)); igbe->rxIpChecksums++; if (cksum(ip) != 0) { err |= RXDE_IPE; ext_err |= RXDEE_IPE; DPRINTF(EthernetDesc, "Checksum is bad!!\n"); } } TcpPtr tcp = ip ? TcpPtr(ip) : TcpPtr(ip6); if (tcp && igbe->regs.rxcsum.tuofld()) { DPRINTF(EthernetDesc, "Checking TCP checksum\n"); status |= RXDS_TCPCS; ptype |= RXDP_TCP; csum = htole(cksum(tcp)); igbe->rxTcpChecksums++; if (cksum(tcp) != 0) { DPRINTF(EthernetDesc, "Checksum is bad!!\n"); err |= RXDE_TCPE; ext_err |= RXDEE_TCPE; } } UdpPtr udp = ip ? UdpPtr(ip) : UdpPtr(ip6); if (udp && igbe->regs.rxcsum.tuofld()) { DPRINTF(EthernetDesc, "Checking UDP checksum\n"); status |= RXDS_UDPCS; ptype |= RXDP_UDP; csum = htole(cksum(udp)); igbe->rxUdpChecksums++; if (cksum(udp) != 0) { DPRINTF(EthernetDesc, "Checksum is bad!!\n"); ext_err |= RXDEE_TCPE; err |= RXDE_TCPE; } } } else { // if ip DPRINTF(EthernetSM, "Proccesing Non-Ip packet\n"); } switch (igbe->regs.srrctl.desctype()) { case RXDT_LEGACY: desc->legacy.len = htole((uint16_t)(pktPtr->length + crcfixup)); desc->legacy.status = htole(status); desc->legacy.errors = htole(err); // No vlan support at this point... just set it to 0 desc->legacy.vlan = 0; break; case RXDT_ADV_SPLIT_A: case RXDT_ADV_ONEBUF: desc->adv_wb.rss_type = htole(0); desc->adv_wb.pkt_type = htole(ptype); if (igbe->regs.rxcsum.pcsd()) { // no rss support right now desc->adv_wb.rss_hash = htole(0); } else { desc->adv_wb.id = htole(ip_id); desc->adv_wb.csum = htole(csum); } desc->adv_wb.status = htole(status); desc->adv_wb.errors = htole(ext_err); // no vlan support desc->adv_wb.vlan_tag = htole(0); break; default: panic("Unimplemnted RX receive buffer type %d\n", igbe->regs.srrctl.desctype()); } DPRINTF(EthernetDesc, "Descriptor complete w0: %#x w1: %#x\n", desc->adv_read.pkt, desc->adv_read.hdr); if (bytesCopied == pktPtr->length) { DPRINTF(EthernetDesc, "Packet completely written to descriptor buffers\n"); // Deal with the rx timer interrupts if (igbe->regs.rdtr.delay()) { Tick delay = igbe->regs.rdtr.delay() * igbe->intClock(); DPRINTF(EthernetSM, "RXS: Scheduling DTR for %d\n", delay); igbe->reschedule(igbe->rdtrEvent, curTick() + delay); } if (igbe->regs.radv.idv()) { Tick delay = igbe->regs.radv.idv() * igbe->intClock(); DPRINTF(EthernetSM, "RXS: Scheduling ADV for %d\n", delay); if (!igbe->radvEvent.scheduled()) { igbe->schedule(igbe->radvEvent, curTick() + delay); } } // if neither radv or rdtr, maybe itr is set... if (!igbe->regs.rdtr.delay() && !igbe->regs.radv.idv()) { DPRINTF(EthernetSM, "RXS: Receive interrupt delay disabled, posting IT_RXT\n"); igbe->postInterrupt(IT_RXT); } // If the packet is small enough, interrupt appropriately // I wonder if this is delayed or not?! if (pktPtr->length <= igbe->regs.rsrpd.idv()) { DPRINTF(EthernetSM, "RXS: Posting IT_SRPD beacuse small packet received\n"); igbe->postInterrupt(IT_SRPD); } bytesCopied = 0; } pktPtr = NULL; igbe->checkDrain(); enableSm(); pktDone = true; igbe->anBegin("RXS", "Done Updating Desc"); DPRINTF(EthernetDesc, "Processing of this descriptor complete\n"); igbe->anDq("RXS", annUnusedCacheQ); unusedCache.pop_front(); igbe->anQ("RXS", annUsedCacheQ); usedCache.push_back(desc); } void IGbE::RxDescCache::enableSm() { if (igbe->drainState() != DrainState::Draining) { igbe->rxTick = true; igbe->restartClock(); } } bool IGbE::RxDescCache::packetDone() { if (pktDone) { pktDone = false; return true; } return false; } bool IGbE::RxDescCache::hasOutstandingEvents() { return pktEvent.scheduled() || wbEvent.scheduled() || fetchEvent.scheduled() || pktHdrEvent.scheduled() || pktDataEvent.scheduled(); } void IGbE::RxDescCache::serialize(CheckpointOut &cp) const { DescCache::serialize(cp); SERIALIZE_SCALAR(pktDone); SERIALIZE_SCALAR(splitCount); SERIALIZE_SCALAR(bytesCopied); } void IGbE::RxDescCache::unserialize(CheckpointIn &cp) { DescCache::unserialize(cp); UNSERIALIZE_SCALAR(pktDone); UNSERIALIZE_SCALAR(splitCount); UNSERIALIZE_SCALAR(bytesCopied); } ///////////////////////////// IGbE::TxDescCache ////////////////////////////// IGbE::TxDescCache::TxDescCache(IGbE *i, const std::string n, int s) : DescCache(i,n, s), pktDone(false), isTcp(false), pktWaiting(false), pktMultiDesc(false), completionAddress(0), completionEnabled(false), useTso(false), tsoHeaderLen(0), tsoMss(0), tsoTotalLen(0), tsoUsedLen(0), tsoPrevSeq(0), tsoPktPayloadBytes(0), tsoLoadedHeader(false), tsoPktHasHeader(false), tsoDescBytesUsed(0), tsoCopyBytes(0), tsoPkts(0), pktEvent([this]{ pktComplete(); }, n), headerEvent([this]{ headerComplete(); }, n), nullEvent([this]{ nullCallback(); }, n) { annSmFetch = "TX Desc Fetch"; annSmWb = "TX Desc Writeback"; annUnusedDescQ = "TX Unused Descriptors"; annUnusedCacheQ = "TX Unused Descriptor Cache"; annUsedCacheQ = "TX Used Descriptor Cache"; annUsedDescQ = "TX Used Descriptors"; annDescQ = "TX Descriptors"; } void IGbE::TxDescCache::processContextDesc() { assert(unusedCache.size()); TxDesc *desc; DPRINTF(EthernetDesc, "Checking and processing context descriptors\n"); while (!useTso && unusedCache.size() && TxdOp::isContext(unusedCache.front())) { DPRINTF(EthernetDesc, "Got context descriptor type...\n"); desc = unusedCache.front(); DPRINTF(EthernetDesc, "Descriptor upper: %#x lower: %#X\n", desc->d1, desc->d2); // is this going to be a tcp or udp packet? isTcp = TxdOp::tcp(desc) ? true : false; // setup all the TSO variables, they'll be ignored if we don't use // tso for this connection tsoHeaderLen = TxdOp::hdrlen(desc); tsoMss = TxdOp::mss(desc); if (TxdOp::isType(desc, TxdOp::TXD_CNXT) && TxdOp::tse(desc)) { DPRINTF(EthernetDesc, "TCP offload enabled for packet hdrlen: " "%d mss: %d paylen %d\n", TxdOp::hdrlen(desc), TxdOp::mss(desc), TxdOp::getLen(desc)); useTso = true; tsoTotalLen = TxdOp::getLen(desc); tsoLoadedHeader = false; tsoDescBytesUsed = 0; tsoUsedLen = 0; tsoPrevSeq = 0; tsoPktHasHeader = false; tsoPkts = 0; tsoCopyBytes = 0; } TxdOp::setDd(desc); unusedCache.pop_front(); igbe->anDq("TXS", annUnusedCacheQ); usedCache.push_back(desc); igbe->anQ("TXS", annUsedCacheQ); } if (!unusedCache.size()) return; desc = unusedCache.front(); if (!useTso && TxdOp::isType(desc, TxdOp::TXD_ADVDATA) && TxdOp::tse(desc)) { DPRINTF(EthernetDesc, "TCP offload(adv) enabled for packet " "hdrlen: %d mss: %d paylen %d\n", tsoHeaderLen, tsoMss, TxdOp::getTsoLen(desc)); useTso = true; tsoTotalLen = TxdOp::getTsoLen(desc); tsoLoadedHeader = false; tsoDescBytesUsed = 0; tsoUsedLen = 0; tsoPrevSeq = 0; tsoPktHasHeader = false; tsoPkts = 0; } if (useTso && !tsoLoadedHeader) { // we need to fetch a header DPRINTF(EthernetDesc, "Starting DMA of TSO header\n"); assert(TxdOp::isData(desc) && TxdOp::getLen(desc) >= tsoHeaderLen); pktWaiting = true; assert(tsoHeaderLen <= 256); igbe->dmaRead(pciToDma(TxdOp::getBuf(desc)), tsoHeaderLen, &headerEvent, tsoHeader, 0); } } void IGbE::TxDescCache::headerComplete() { DPRINTF(EthernetDesc, "TSO: Fetching TSO header complete\n"); pktWaiting = false; assert(unusedCache.size()); TxDesc *desc = unusedCache.front(); DPRINTF(EthernetDesc, "TSO: len: %d tsoHeaderLen: %d\n", TxdOp::getLen(desc), tsoHeaderLen); if (TxdOp::getLen(desc) == tsoHeaderLen) { tsoDescBytesUsed = 0; tsoLoadedHeader = true; unusedCache.pop_front(); usedCache.push_back(desc); } else { DPRINTF(EthernetDesc, "TSO: header part of larger payload\n"); tsoDescBytesUsed = tsoHeaderLen; tsoLoadedHeader = true; } enableSm(); igbe->checkDrain(); } unsigned IGbE::TxDescCache::getPacketSize(EthPacketPtr p) { if (!unusedCache.size()) return 0; DPRINTF(EthernetDesc, "Starting processing of descriptor\n"); assert(!useTso || tsoLoadedHeader); TxDesc *desc = unusedCache.front(); if (useTso) { DPRINTF(EthernetDesc, "getPacket(): TxDescriptor data " "d1: %#llx d2: %#llx\n", desc->d1, desc->d2); DPRINTF(EthernetDesc, "TSO: use: %d hdrlen: %d mss: %d total: %d " "used: %d loaded hdr: %d\n", useTso, tsoHeaderLen, tsoMss, tsoTotalLen, tsoUsedLen, tsoLoadedHeader); if (tsoPktHasHeader) tsoCopyBytes = std::min((tsoMss + tsoHeaderLen) - p->length, TxdOp::getLen(desc) - tsoDescBytesUsed); else tsoCopyBytes = std::min(tsoMss, TxdOp::getLen(desc) - tsoDescBytesUsed); unsigned pkt_size = tsoCopyBytes + (tsoPktHasHeader ? 0 : tsoHeaderLen); DPRINTF(EthernetDesc, "TSO: descBytesUsed: %d copyBytes: %d " "this descLen: %d\n", tsoDescBytesUsed, tsoCopyBytes, TxdOp::getLen(desc)); DPRINTF(EthernetDesc, "TSO: pktHasHeader: %d\n", tsoPktHasHeader); DPRINTF(EthernetDesc, "TSO: Next packet is %d bytes\n", pkt_size); return pkt_size; } DPRINTF(EthernetDesc, "Next TX packet is %d bytes\n", TxdOp::getLen(unusedCache.front())); return TxdOp::getLen(desc); } void IGbE::TxDescCache::getPacketData(EthPacketPtr p) { assert(unusedCache.size()); TxDesc *desc; desc = unusedCache.front(); DPRINTF(EthernetDesc, "getPacketData(): TxDescriptor data " "d1: %#llx d2: %#llx\n", desc->d1, desc->d2); assert((TxdOp::isLegacy(desc) || TxdOp::isData(desc)) && TxdOp::getLen(desc)); pktPtr = p; pktWaiting = true; DPRINTF(EthernetDesc, "Starting DMA of packet at offset %d\n", p->length); if (useTso) { assert(tsoLoadedHeader); if (!tsoPktHasHeader) { DPRINTF(EthernetDesc, "Loading TSO header (%d bytes) into start of packet\n", tsoHeaderLen); memcpy(p->data, &tsoHeader,tsoHeaderLen); p->length +=tsoHeaderLen; tsoPktHasHeader = true; } } if (useTso) { DPRINTF(EthernetDesc, "Starting DMA of packet at offset %d length: %d\n", p->length, tsoCopyBytes); igbe->dmaRead(pciToDma(TxdOp::getBuf(desc)) + tsoDescBytesUsed, tsoCopyBytes, &pktEvent, p->data + p->length, igbe->txReadDelay); tsoDescBytesUsed += tsoCopyBytes; assert(tsoDescBytesUsed <= TxdOp::getLen(desc)); } else { igbe->dmaRead(pciToDma(TxdOp::getBuf(desc)), TxdOp::getLen(desc), &pktEvent, p->data + p->length, igbe->txReadDelay); } } void IGbE::TxDescCache::pktComplete() { TxDesc *desc; assert(unusedCache.size()); assert(pktPtr); igbe->anBegin("TXS", "Update Desc"); DPRINTF(EthernetDesc, "DMA of packet complete\n"); desc = unusedCache.front(); assert((TxdOp::isLegacy(desc) || TxdOp::isData(desc)) && TxdOp::getLen(desc)); DPRINTF(EthernetDesc, "TxDescriptor data d1: %#llx d2: %#llx\n", desc->d1, desc->d2); // Set the length of the data in the EtherPacket if (useTso) { DPRINTF(EthernetDesc, "TSO: use: %d hdrlen: %d mss: %d total: %d " "used: %d loaded hdr: %d\n", useTso, tsoHeaderLen, tsoMss, tsoTotalLen, tsoUsedLen, tsoLoadedHeader); pktPtr->simLength += tsoCopyBytes; pktPtr->length += tsoCopyBytes; tsoUsedLen += tsoCopyBytes; DPRINTF(EthernetDesc, "TSO: descBytesUsed: %d copyBytes: %d\n", tsoDescBytesUsed, tsoCopyBytes); } else { pktPtr->simLength += TxdOp::getLen(desc); pktPtr->length += TxdOp::getLen(desc); } if ((!TxdOp::eop(desc) && !useTso) || (pktPtr->length < ( tsoMss + tsoHeaderLen) && tsoTotalLen != tsoUsedLen && useTso)) { assert(!useTso || (tsoDescBytesUsed == TxdOp::getLen(desc))); igbe->anDq("TXS", annUnusedCacheQ); unusedCache.pop_front(); igbe->anQ("TXS", annUsedCacheQ); usedCache.push_back(desc); tsoDescBytesUsed = 0; pktDone = true; pktWaiting = false; pktMultiDesc = true; DPRINTF(EthernetDesc, "Partial Packet Descriptor of %d bytes Done\n", pktPtr->length); pktPtr = NULL; enableSm(); igbe->checkDrain(); return; } pktMultiDesc = false; // no support for vlans assert(!TxdOp::vle(desc)); // we only support single packet descriptors at this point if (!useTso) assert(TxdOp::eop(desc)); // set that this packet is done if (TxdOp::rs(desc)) TxdOp::setDd(desc); DPRINTF(EthernetDesc, "TxDescriptor data d1: %#llx d2: %#llx\n", desc->d1, desc->d2); if (useTso) { IpPtr ip(pktPtr); Ip6Ptr ip6(pktPtr); if (ip) { DPRINTF(EthernetDesc, "TSO: Modifying IP header. Id + %d\n", tsoPkts); ip->id(ip->id() + tsoPkts++); ip->len(pktPtr->length - EthPtr(pktPtr)->size()); } if (ip6) ip6->plen(pktPtr->length - EthPtr(pktPtr)->size()); TcpPtr tcp = ip ? TcpPtr(ip) : TcpPtr(ip6); if (tcp) { DPRINTF(EthernetDesc, "TSO: Modifying TCP header. old seq %d + %d\n", tcp->seq(), tsoPrevSeq); tcp->seq(tcp->seq() + tsoPrevSeq); if (tsoUsedLen != tsoTotalLen) tcp->flags(tcp->flags() & ~9); // clear fin & psh } UdpPtr udp = ip ? UdpPtr(ip) : UdpPtr(ip6); if (udp) { DPRINTF(EthernetDesc, "TSO: Modifying UDP header.\n"); udp->len(pktPtr->length - EthPtr(pktPtr)->size()); } tsoPrevSeq = tsoUsedLen; } if (DTRACE(EthernetDesc)) { IpPtr ip(pktPtr); if (ip) DPRINTF(EthernetDesc, "Proccesing Ip packet with Id=%d\n", ip->id()); else DPRINTF(EthernetSM, "Proccesing Non-Ip packet\n"); } // Checksums are only ofloaded for new descriptor types if (TxdOp::isData(desc) && (TxdOp::ixsm(desc) || TxdOp::txsm(desc))) { DPRINTF(EthernetDesc, "Calculating checksums for packet\n"); IpPtr ip(pktPtr); Ip6Ptr ip6(pktPtr); assert(ip || ip6); if (ip && TxdOp::ixsm(desc)) { ip->sum(0); ip->sum(cksum(ip)); igbe->txIpChecksums++; DPRINTF(EthernetDesc, "Calculated IP checksum\n"); } if (TxdOp::txsm(desc)) { TcpPtr tcp = ip ? TcpPtr(ip) : TcpPtr(ip6); UdpPtr udp = ip ? UdpPtr(ip) : UdpPtr(ip6); if (tcp) { tcp->sum(0); tcp->sum(cksum(tcp)); igbe->txTcpChecksums++; DPRINTF(EthernetDesc, "Calculated TCP checksum\n"); } else if (udp) { assert(udp); udp->sum(0); udp->sum(cksum(udp)); igbe->txUdpChecksums++; DPRINTF(EthernetDesc, "Calculated UDP checksum\n"); } else { panic("Told to checksum, but don't know how\n"); } } } if (TxdOp::ide(desc)) { // Deal with the rx timer interrupts DPRINTF(EthernetDesc, "Descriptor had IDE set\n"); if (igbe->regs.tidv.idv()) { Tick delay = igbe->regs.tidv.idv() * igbe->intClock(); DPRINTF(EthernetDesc, "setting tidv\n"); igbe->reschedule(igbe->tidvEvent, curTick() + delay, true); } if (igbe->regs.tadv.idv() && igbe->regs.tidv.idv()) { Tick delay = igbe->regs.tadv.idv() * igbe->intClock(); DPRINTF(EthernetDesc, "setting tadv\n"); if (!igbe->tadvEvent.scheduled()) { igbe->schedule(igbe->tadvEvent, curTick() + delay); } } } if (!useTso || TxdOp::getLen(desc) == tsoDescBytesUsed) { DPRINTF(EthernetDesc, "Descriptor Done\n"); igbe->anDq("TXS", annUnusedCacheQ); unusedCache.pop_front(); igbe->anQ("TXS", annUsedCacheQ); usedCache.push_back(desc); tsoDescBytesUsed = 0; } if (useTso && tsoUsedLen == tsoTotalLen) useTso = false; DPRINTF(EthernetDesc, "------Packet of %d bytes ready for transmission-------\n", pktPtr->length); pktDone = true; pktWaiting = false; pktPtr = NULL; tsoPktHasHeader = false; if (igbe->regs.txdctl.wthresh() == 0) { igbe->anBegin("TXS", "Desc Writeback"); DPRINTF(EthernetDesc, "WTHRESH == 0, writing back descriptor\n"); writeback(0); } else if (!igbe->regs.txdctl.gran() && igbe->regs.txdctl.wthresh() <= descInBlock(usedCache.size())) { DPRINTF(EthernetDesc, "used > WTHRESH, writing back descriptor\n"); igbe->anBegin("TXS", "Desc Writeback"); writeback((igbe->cacheBlockSize()-1)>>4); } else if (igbe->regs.txdctl.wthresh() <= usedCache.size()) { DPRINTF(EthernetDesc, "used > WTHRESH, writing back descriptor\n"); igbe->anBegin("TXS", "Desc Writeback"); writeback((igbe->cacheBlockSize()-1)>>4); } enableSm(); igbe->checkDrain(); } void IGbE::TxDescCache::actionAfterWb() { DPRINTF(EthernetDesc, "actionAfterWb() completionEnabled: %d\n", completionEnabled); igbe->postInterrupt(iGbReg::IT_TXDW); if (completionEnabled) { descEnd = igbe->regs.tdh(); DPRINTF(EthernetDesc, "Completion writing back value: %d to addr: %#x\n", descEnd, completionAddress); igbe->dmaWrite(pciToDma(mbits(completionAddress, 63, 2)), sizeof(descEnd), &nullEvent, (uint8_t*)&descEnd, 0); } } void IGbE::TxDescCache::serialize(CheckpointOut &cp) const { DescCache::serialize(cp); SERIALIZE_SCALAR(pktDone); SERIALIZE_SCALAR(isTcp); SERIALIZE_SCALAR(pktWaiting); SERIALIZE_SCALAR(pktMultiDesc); SERIALIZE_SCALAR(useTso); SERIALIZE_SCALAR(tsoHeaderLen); SERIALIZE_SCALAR(tsoMss); SERIALIZE_SCALAR(tsoTotalLen); SERIALIZE_SCALAR(tsoUsedLen); SERIALIZE_SCALAR(tsoPrevSeq);; SERIALIZE_SCALAR(tsoPktPayloadBytes); SERIALIZE_SCALAR(tsoLoadedHeader); SERIALIZE_SCALAR(tsoPktHasHeader); SERIALIZE_ARRAY(tsoHeader, 256); SERIALIZE_SCALAR(tsoDescBytesUsed); SERIALIZE_SCALAR(tsoCopyBytes); SERIALIZE_SCALAR(tsoPkts); SERIALIZE_SCALAR(completionAddress); SERIALIZE_SCALAR(completionEnabled); SERIALIZE_SCALAR(descEnd); } void IGbE::TxDescCache::unserialize(CheckpointIn &cp) { DescCache::unserialize(cp); UNSERIALIZE_SCALAR(pktDone); UNSERIALIZE_SCALAR(isTcp); UNSERIALIZE_SCALAR(pktWaiting); UNSERIALIZE_SCALAR(pktMultiDesc); UNSERIALIZE_SCALAR(useTso); UNSERIALIZE_SCALAR(tsoHeaderLen); UNSERIALIZE_SCALAR(tsoMss); UNSERIALIZE_SCALAR(tsoTotalLen); UNSERIALIZE_SCALAR(tsoUsedLen); UNSERIALIZE_SCALAR(tsoPrevSeq);; UNSERIALIZE_SCALAR(tsoPktPayloadBytes); UNSERIALIZE_SCALAR(tsoLoadedHeader); UNSERIALIZE_SCALAR(tsoPktHasHeader); UNSERIALIZE_ARRAY(tsoHeader, 256); UNSERIALIZE_SCALAR(tsoDescBytesUsed); UNSERIALIZE_SCALAR(tsoCopyBytes); UNSERIALIZE_SCALAR(tsoPkts); UNSERIALIZE_SCALAR(completionAddress); UNSERIALIZE_SCALAR(completionEnabled); UNSERIALIZE_SCALAR(descEnd); } bool IGbE::TxDescCache::packetAvailable() { if (pktDone) { pktDone = false; return true; } return false; } void IGbE::TxDescCache::enableSm() { if (igbe->drainState() != DrainState::Draining) { igbe->txTick = true; igbe->restartClock(); } } bool IGbE::TxDescCache::hasOutstandingEvents() { return pktEvent.scheduled() || wbEvent.scheduled() || fetchEvent.scheduled(); } ///////////////////////////////////// IGbE ///////////////////////////////// void IGbE::restartClock() { if (!tickEvent.scheduled() && (rxTick || txTick || txFifoTick) && drainState() == DrainState::Running) schedule(tickEvent, clockEdge(Cycles(1))); } DrainState IGbE::drain() { unsigned int count(0); if (rxDescCache.hasOutstandingEvents() || txDescCache.hasOutstandingEvents()) { count++; } txFifoTick = false; txTick = false; rxTick = false; if (tickEvent.scheduled()) deschedule(tickEvent); if (count) { DPRINTF(Drain, "IGbE not drained\n"); return DrainState::Draining; } else return DrainState::Drained; } void IGbE::drainResume() { Drainable::drainResume(); txFifoTick = true; txTick = true; rxTick = true; restartClock(); DPRINTF(EthernetSM, "resuming from drain"); } void IGbE::checkDrain() { if (drainState() != DrainState::Draining) return; txFifoTick = false; txTick = false; rxTick = false; if (!rxDescCache.hasOutstandingEvents() && !txDescCache.hasOutstandingEvents()) { DPRINTF(Drain, "IGbE done draining, processing drain event\n"); signalDrainDone(); } } void IGbE::txStateMachine() { if (!regs.tctl.en()) { txTick = false; DPRINTF(EthernetSM, "TXS: TX disabled, stopping ticking\n"); return; } // If we have a packet available and it's length is not 0 (meaning it's not // a multidescriptor packet) put it in the fifo, otherwise an the next // iteration we'll get the rest of the data if (txPacket && txDescCache.packetAvailable() && !txDescCache.packetMultiDesc() && txPacket->length) { anQ("TXS", "TX FIFO Q"); DPRINTF(EthernetSM, "TXS: packet placed in TX FIFO\n"); #ifndef NDEBUG bool success = #endif txFifo.push(txPacket); txFifoTick = true && drainState() != DrainState::Draining; assert(success); txPacket = NULL; anBegin("TXS", "Desc Writeback"); txDescCache.writeback((cacheBlockSize()-1)>>4); return; } // Only support descriptor granularity if (regs.txdctl.lwthresh() && txDescCache.descLeft() < (regs.txdctl.lwthresh() * 8)) { DPRINTF(EthernetSM, "TXS: LWTHRESH caused posting of TXDLOW\n"); postInterrupt(IT_TXDLOW); } if (!txPacket) { txPacket = std::make_shared(16384); } if (!txDescCache.packetWaiting()) { if (txDescCache.descLeft() == 0) { postInterrupt(IT_TXQE); anBegin("TXS", "Desc Writeback"); txDescCache.writeback(0); anBegin("TXS", "Desc Fetch"); anWe("TXS", txDescCache.annUnusedCacheQ); txDescCache.fetchDescriptors(); DPRINTF(EthernetSM, "TXS: No descriptors left in ring, forcing " "writeback stopping ticking and posting TXQE\n"); txTick = false; return; } if (!(txDescCache.descUnused())) { anBegin("TXS", "Desc Fetch"); txDescCache.fetchDescriptors(); anWe("TXS", txDescCache.annUnusedCacheQ); DPRINTF(EthernetSM, "TXS: No descriptors available in cache, " "fetching and stopping ticking\n"); txTick = false; return; } anPq("TXS", txDescCache.annUnusedCacheQ); txDescCache.processContextDesc(); if (txDescCache.packetWaiting()) { DPRINTF(EthernetSM, "TXS: Fetching TSO header, stopping ticking\n"); txTick = false; return; } unsigned size = txDescCache.getPacketSize(txPacket); if (size > 0 && txFifo.avail() > size) { anRq("TXS", "TX FIFO Q"); anBegin("TXS", "DMA Packet"); DPRINTF(EthernetSM, "TXS: Reserving %d bytes in FIFO and " "beginning DMA of next packet\n", size); txFifo.reserve(size); txDescCache.getPacketData(txPacket); } else if (size == 0) { DPRINTF(EthernetSM, "TXS: getPacketSize returned: %d\n", size); DPRINTF(EthernetSM, "TXS: No packets to get, writing back used descriptors\n"); anBegin("TXS", "Desc Writeback"); txDescCache.writeback(0); } else { anWf("TXS", "TX FIFO Q"); DPRINTF(EthernetSM, "TXS: FIFO full, stopping ticking until space " "available in FIFO\n"); txTick = false; } return; } DPRINTF(EthernetSM, "TXS: Nothing to do, stopping ticking\n"); txTick = false; } bool IGbE::ethRxPkt(EthPacketPtr pkt) { rxBytes += pkt->length; rxPackets++; DPRINTF(Ethernet, "RxFIFO: Receiving pcakte from wire\n"); anBegin("RXQ", "Wire Recv"); if (!regs.rctl.en()) { DPRINTF(Ethernet, "RxFIFO: RX not enabled, dropping\n"); anBegin("RXQ", "FIFO Drop", CPA::FL_BAD); return true; } // restart the state machines if they are stopped rxTick = true && drainState() != DrainState::Draining; if ((rxTick || txTick) && !tickEvent.scheduled()) { DPRINTF(EthernetSM, "RXS: received packet into fifo, starting ticking\n"); restartClock(); } if (!rxFifo.push(pkt)) { DPRINTF(Ethernet, "RxFIFO: Packet won't fit in fifo... dropped\n"); postInterrupt(IT_RXO, true); anBegin("RXQ", "FIFO Drop", CPA::FL_BAD); return false; } if (CPA::available() && cpa->enabled()) { assert(sys->numSystemsRunning <= 2); System *other_sys; if (sys->systemList[0] == sys) other_sys = sys->systemList[1]; else other_sys = sys->systemList[0]; cpa->hwDq(CPA::FL_NONE, sys, macAddr, "RXQ", "WireQ", 0, other_sys); anQ("RXQ", "RX FIFO Q"); cpa->hwWe(CPA::FL_NONE, sys, macAddr, "RXQ", "WireQ", 0, other_sys); } return true; } void IGbE::rxStateMachine() { if (!regs.rctl.en()) { rxTick = false; DPRINTF(EthernetSM, "RXS: RX disabled, stopping ticking\n"); return; } // If the packet is done check for interrupts/descriptors/etc if (rxDescCache.packetDone()) { rxDmaPacket = false; DPRINTF(EthernetSM, "RXS: Packet completed DMA to memory\n"); int descLeft = rxDescCache.descLeft(); DPRINTF(EthernetSM, "RXS: descLeft: %d rdmts: %d rdlen: %d\n", descLeft, regs.rctl.rdmts(), regs.rdlen()); // rdmts 2->1/8, 1->1/4, 0->1/2 int ratio = (1ULL << (regs.rctl.rdmts() + 1)); if (descLeft * ratio <= regs.rdlen()) { DPRINTF(Ethernet, "RXS: Interrupting (RXDMT) " "because of descriptors left\n"); postInterrupt(IT_RXDMT); } if (rxFifo.empty()) rxDescCache.writeback(0); if (descLeft == 0) { anBegin("RXS", "Writeback Descriptors"); rxDescCache.writeback(0); DPRINTF(EthernetSM, "RXS: No descriptors left in ring, forcing" " writeback and stopping ticking\n"); rxTick = false; } // only support descriptor granulaties assert(regs.rxdctl.gran()); if (regs.rxdctl.wthresh() >= rxDescCache.descUsed()) { DPRINTF(EthernetSM, "RXS: Writing back because WTHRESH >= descUsed\n"); anBegin("RXS", "Writeback Descriptors"); if (regs.rxdctl.wthresh() < (cacheBlockSize()>>4)) rxDescCache.writeback(regs.rxdctl.wthresh()-1); else rxDescCache.writeback((cacheBlockSize()-1)>>4); } if ((rxDescCache.descUnused() < regs.rxdctl.pthresh()) && ((rxDescCache.descLeft() - rxDescCache.descUnused()) > regs.rxdctl.hthresh())) { DPRINTF(EthernetSM, "RXS: Fetching descriptors because " "descUnused < PTHRESH\n"); anBegin("RXS", "Fetch Descriptors"); rxDescCache.fetchDescriptors(); } if (rxDescCache.descUnused() == 0) { anBegin("RXS", "Fetch Descriptors"); rxDescCache.fetchDescriptors(); anWe("RXS", rxDescCache.annUnusedCacheQ); DPRINTF(EthernetSM, "RXS: No descriptors available in cache, " "fetching descriptors and stopping ticking\n"); rxTick = false; } return; } if (rxDmaPacket) { DPRINTF(EthernetSM, "RXS: stopping ticking until packet DMA completes\n"); rxTick = false; return; } if (!rxDescCache.descUnused()) { anBegin("RXS", "Fetch Descriptors"); rxDescCache.fetchDescriptors(); anWe("RXS", rxDescCache.annUnusedCacheQ); DPRINTF(EthernetSM, "RXS: No descriptors available in cache, " "stopping ticking\n"); rxTick = false; DPRINTF(EthernetSM, "RXS: No descriptors available, fetching\n"); return; } anPq("RXS", rxDescCache.annUnusedCacheQ); if (rxFifo.empty()) { anWe("RXS", "RX FIFO Q"); DPRINTF(EthernetSM, "RXS: RxFIFO empty, stopping ticking\n"); rxTick = false; return; } anPq("RXS", "RX FIFO Q"); anBegin("RXS", "Get Desc"); EthPacketPtr pkt; pkt = rxFifo.front(); pktOffset = rxDescCache.writePacket(pkt, pktOffset); DPRINTF(EthernetSM, "RXS: Writing packet into memory\n"); if (pktOffset == pkt->length) { anBegin( "RXS", "FIFO Dequeue"); DPRINTF(EthernetSM, "RXS: Removing packet from FIFO\n"); pktOffset = 0; anDq("RXS", "RX FIFO Q"); rxFifo.pop(); } DPRINTF(EthernetSM, "RXS: stopping ticking until packet DMA completes\n"); rxTick = false; rxDmaPacket = true; anBegin("RXS", "DMA Packet"); } void IGbE::txWire() { txFifoTick = false; if (txFifo.empty()) { anWe("TXQ", "TX FIFO Q"); return; } anPq("TXQ", "TX FIFO Q"); if (etherInt->sendPacket(txFifo.front())) { anQ("TXQ", "WireQ"); if (DTRACE(EthernetSM)) { IpPtr ip(txFifo.front()); if (ip) DPRINTF(EthernetSM, "Transmitting Ip packet with Id=%d\n", ip->id()); else DPRINTF(EthernetSM, "Transmitting Non-Ip packet\n"); } anDq("TXQ", "TX FIFO Q"); anBegin("TXQ", "Wire Send"); DPRINTF(EthernetSM, "TxFIFO: Successful transmit, bytes available in fifo: %d\n", txFifo.avail()); txBytes += txFifo.front()->length; txPackets++; txFifo.pop(); } } void IGbE::tick() { DPRINTF(EthernetSM, "IGbE: -------------- Cycle --------------\n"); inTick = true; if (rxTick) rxStateMachine(); if (txTick) txStateMachine(); // If txWire returns and txFifoTick is still set, that means the data we // sent to the other end was already accepted and we can send another // frame right away. This is consistent with the previous behavior which // would send another frame if one was ready in ethTxDone. This version // avoids growing the stack with each frame sent which can cause stack // overflow. while (txFifoTick) txWire(); if (rxTick || txTick || txFifoTick) schedule(tickEvent, curTick() + clockPeriod()); inTick = false; } void IGbE::ethTxDone() { anBegin("TXQ", "Send Done"); // restart the tx state machines if they are stopped // fifo to send another packet // tx sm to put more data into the fifo txFifoTick = true && drainState() != DrainState::Draining; if (txDescCache.descLeft() != 0 && drainState() != DrainState::Draining) txTick = true; if (!inTick) restartClock(); DPRINTF(EthernetSM, "TxFIFO: Transmission complete\n"); } void IGbE::serialize(CheckpointOut &cp) const { PciDevice::serialize(cp); regs.serialize(cp); SERIALIZE_SCALAR(eeOpBits); SERIALIZE_SCALAR(eeAddrBits); SERIALIZE_SCALAR(eeDataBits); SERIALIZE_SCALAR(eeOpcode); SERIALIZE_SCALAR(eeAddr); SERIALIZE_SCALAR(lastInterrupt); SERIALIZE_ARRAY(flash,iGbReg::EEPROM_SIZE); rxFifo.serialize("rxfifo", cp); txFifo.serialize("txfifo", cp); bool txPktExists = txPacket != nullptr; SERIALIZE_SCALAR(txPktExists); if (txPktExists) txPacket->serialize("txpacket", cp); Tick rdtr_time = 0, radv_time = 0, tidv_time = 0, tadv_time = 0, inter_time = 0; if (rdtrEvent.scheduled()) rdtr_time = rdtrEvent.when(); SERIALIZE_SCALAR(rdtr_time); if (radvEvent.scheduled()) radv_time = radvEvent.when(); SERIALIZE_SCALAR(radv_time); if (tidvEvent.scheduled()) tidv_time = tidvEvent.when(); SERIALIZE_SCALAR(tidv_time); if (tadvEvent.scheduled()) tadv_time = tadvEvent.when(); SERIALIZE_SCALAR(tadv_time); if (interEvent.scheduled()) inter_time = interEvent.when(); SERIALIZE_SCALAR(inter_time); SERIALIZE_SCALAR(pktOffset); txDescCache.serializeSection(cp, "TxDescCache"); rxDescCache.serializeSection(cp, "RxDescCache"); } void IGbE::unserialize(CheckpointIn &cp) { PciDevice::unserialize(cp); regs.unserialize(cp); UNSERIALIZE_SCALAR(eeOpBits); UNSERIALIZE_SCALAR(eeAddrBits); UNSERIALIZE_SCALAR(eeDataBits); UNSERIALIZE_SCALAR(eeOpcode); UNSERIALIZE_SCALAR(eeAddr); UNSERIALIZE_SCALAR(lastInterrupt); UNSERIALIZE_ARRAY(flash,iGbReg::EEPROM_SIZE); rxFifo.unserialize("rxfifo", cp); txFifo.unserialize("txfifo", cp); bool txPktExists; UNSERIALIZE_SCALAR(txPktExists); if (txPktExists) { txPacket = std::make_shared(16384); txPacket->unserialize("txpacket", cp); } rxTick = true; txTick = true; txFifoTick = true; Tick rdtr_time, radv_time, tidv_time, tadv_time, inter_time; UNSERIALIZE_SCALAR(rdtr_time); UNSERIALIZE_SCALAR(radv_time); UNSERIALIZE_SCALAR(tidv_time); UNSERIALIZE_SCALAR(tadv_time); UNSERIALIZE_SCALAR(inter_time); if (rdtr_time) schedule(rdtrEvent, rdtr_time); if (radv_time) schedule(radvEvent, radv_time); if (tidv_time) schedule(tidvEvent, tidv_time); if (tadv_time) schedule(tadvEvent, tadv_time); if (inter_time) schedule(interEvent, inter_time); UNSERIALIZE_SCALAR(pktOffset); txDescCache.unserializeSection(cp, "TxDescCache"); rxDescCache.unserializeSection(cp, "RxDescCache"); } IGbE * IGbEParams::create() { return new IGbE(this); }