| /* |
| * Copyright (c) 2014 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: Anthony Gutierrez |
| * Mohammad Alian |
| */ |
| |
| /* @file |
| * Device model for an ethernet switch |
| */ |
| |
| #include "dev/net/etherswitch.hh" |
| |
| #include "base/random.hh" |
| #include "base/trace.hh" |
| #include "debug/EthernetAll.hh" |
| #include "sim/core.hh" |
| |
| using namespace std; |
| |
| EtherSwitch::EtherSwitch(const Params *p) |
| : SimObject(p), ttl(p->time_to_live) |
| { |
| for (int i = 0; i < p->port_interface_connection_count; ++i) { |
| std::string interfaceName = csprintf("%s.interface%d", name(), i); |
| Interface *interface = new Interface(interfaceName, this, |
| p->output_buffer_size, p->delay, |
| p->delay_var, p->fabric_speed, i); |
| interfaces.push_back(interface); |
| } |
| } |
| |
| EtherSwitch::~EtherSwitch() |
| { |
| for (auto it : interfaces) |
| delete it; |
| |
| interfaces.clear(); |
| } |
| |
| EtherInt* |
| EtherSwitch::getEthPort(const std::string &if_name, int idx) |
| { |
| if (idx < 0 || idx >= interfaces.size()) |
| return nullptr; |
| |
| Interface *interface = interfaces.at(idx); |
| panic_if(interface->getPeer(), "interface already connected\n"); |
| |
| return interface; |
| } |
| |
| bool |
| EtherSwitch::Interface::PortFifo::push(EthPacketPtr ptr, unsigned senderId) |
| { |
| assert(ptr->length); |
| |
| _size += ptr->length; |
| fifo.emplace_hint(fifo.end(), ptr, curTick(), senderId); |
| |
| // Drop the extra pushed packets from end of the fifo |
| while (avail() < 0) { |
| DPRINTF(Ethernet, "Fifo is full. Drop packet: len=%d\n", |
| std::prev(fifo.end())->packet->length); |
| |
| _size -= std::prev(fifo.end())->packet->length; |
| fifo.erase(std::prev(fifo.end())); |
| } |
| |
| if (empty()) { |
| warn("EtherSwitch: Packet length (%d) exceeds the maximum storage " |
| "capacity of port fifo (%d)", ptr->length, _maxsize); |
| } |
| |
| // Return true if the newly pushed packet gets inserted |
| // at the head of the queue, otherwise return false |
| // We need this information to deschedule the event that has been |
| // scheduled for the old head of queue packet and schedule a new one |
| if (!empty() && fifo.begin()->packet == ptr) { |
| return true; |
| } |
| return false; |
| } |
| |
| void |
| EtherSwitch::Interface::PortFifo::pop() |
| { |
| if (empty()) |
| return; |
| |
| assert(_size >= fifo.begin()->packet->length); |
| // Erase the packet at the head of the queue |
| _size -= fifo.begin()->packet->length; |
| fifo.erase(fifo.begin()); |
| } |
| |
| void |
| EtherSwitch::Interface::PortFifo::clear() |
| { |
| fifo.clear(); |
| _size = 0; |
| } |
| |
| EtherSwitch::Interface::Interface(const std::string &name, |
| EtherSwitch *etherSwitch, |
| uint64_t outputBufferSize, Tick delay, |
| Tick delay_var, double rate, unsigned id) |
| : EtherInt(name), ticksPerByte(rate), switchDelay(delay), |
| delayVar(delay_var), interfaceId(id), parent(etherSwitch), |
| outputFifo(name + ".outputFifo", outputBufferSize), |
| txEvent([this]{ transmit(); }, name) |
| { |
| } |
| |
| bool |
| EtherSwitch::Interface::recvPacket(EthPacketPtr packet) |
| { |
| Net::EthAddr destMacAddr(packet->data); |
| Net::EthAddr srcMacAddr(&packet->data[6]); |
| |
| learnSenderAddr(srcMacAddr, this); |
| Interface *receiver = lookupDestPort(destMacAddr); |
| |
| if (!receiver || destMacAddr.multicast() || destMacAddr.broadcast()) { |
| for (auto it : parent->interfaces) |
| if (it != this) |
| it->enqueue(packet, interfaceId); |
| } else { |
| DPRINTF(Ethernet, "sending packet from MAC %x on port " |
| "%s to MAC %x on port %s\n", uint64_t(srcMacAddr), |
| this->name(), uint64_t(destMacAddr), receiver->name()); |
| |
| receiver->enqueue(packet, interfaceId); |
| } |
| // At the output port, we either have buffer space (no drop) or |
| // don't (drop packet); in both cases packet is received on |
| // the interface successfully and there is no notion of busy |
| // interface here (as we don't have inputFifo) |
| return true; |
| } |
| |
| void |
| EtherSwitch::Interface::enqueue(EthPacketPtr packet, unsigned senderId) |
| { |
| // assuming per-interface transmission events, |
| // if the newly push packet gets inserted at the head of the queue |
| // (either there was nothing in the queue or the priority of the new |
| // packet was higher than the packets already in the fifo) |
| // then we need to schedule an event at |
| // "curTick" + "switchingDelay of the packet at the head of the fifo" |
| // to send this packet out the external link |
| // otherwise, there is already a txEvent scheduled |
| if (outputFifo.push(packet, senderId)) { |
| parent->reschedule(txEvent, curTick() + switchingDelay(), true); |
| } |
| } |
| |
| void |
| EtherSwitch::Interface::transmit() |
| { |
| // there should be something in the output queue |
| assert(!outputFifo.empty()); |
| |
| if (!sendPacket(outputFifo.front())) { |
| DPRINTF(Ethernet, "output port busy...retry later\n"); |
| if (!txEvent.scheduled()) |
| parent->schedule(txEvent, curTick() + retryTime); |
| } else { |
| DPRINTF(Ethernet, "packet sent: len=%d\n", outputFifo.front()->length); |
| outputFifo.pop(); |
| // schedule an event to send the pkt at |
| // the head of queue, if there is any |
| if (!outputFifo.empty()) { |
| parent->schedule(txEvent, curTick() + switchingDelay()); |
| } |
| } |
| } |
| |
| Tick |
| EtherSwitch::Interface::switchingDelay() |
| { |
| Tick delay = (Tick)ceil(((double)outputFifo.front()->simLength |
| * ticksPerByte) + 1.0); |
| if (delayVar != 0) |
| delay += random_mt.random<Tick>(0, delayVar); |
| delay += switchDelay; |
| return delay; |
| } |
| |
| EtherSwitch::Interface* |
| EtherSwitch::Interface::lookupDestPort(Net::EthAddr destMacAddr) |
| { |
| auto it = parent->forwardingTable.find(uint64_t(destMacAddr)); |
| |
| if (it == parent->forwardingTable.end()) { |
| DPRINTF(Ethernet, "no entry in forwaring table for MAC: " |
| "%x\n", uint64_t(destMacAddr)); |
| return nullptr; |
| } |
| |
| // check if this entry is valid based on TTL and lastUseTime |
| if ((curTick() - it->second.lastUseTime) > parent->ttl) { |
| // TTL for this mapping has been expired, so this item is not |
| // valide anymore, let's remove it from the map |
| parent->forwardingTable.erase(it); |
| return nullptr; |
| } |
| |
| DPRINTF(Ethernet, "found entry for MAC address %x on port %s\n", |
| uint64_t(destMacAddr), it->second.interface->name()); |
| return it->second.interface; |
| } |
| |
| void |
| EtherSwitch::Interface::learnSenderAddr(Net::EthAddr srcMacAddr, |
| Interface *sender) |
| { |
| // learn the port for the sending MAC address |
| auto it = parent->forwardingTable.find(uint64_t(srcMacAddr)); |
| |
| // if the port for sender's MAC address is not cached, |
| // cache it now, otherwise just update lastUseTime time |
| if (it == parent->forwardingTable.end()) { |
| DPRINTF(Ethernet, "adding forwarding table entry for MAC " |
| " address %x on port %s\n", uint64_t(srcMacAddr), |
| sender->name()); |
| EtherSwitch::SwitchTableEntry forwardingTableEntry; |
| forwardingTableEntry.interface = sender; |
| forwardingTableEntry.lastUseTime = curTick(); |
| parent->forwardingTable.insert(std::make_pair(uint64_t(srcMacAddr), |
| forwardingTableEntry)); |
| } else { |
| it->second.lastUseTime = curTick(); |
| } |
| } |
| |
| void |
| EtherSwitch::serialize(CheckpointOut &cp) const |
| { |
| for (auto it : interfaces) |
| it->serializeSection(cp, it->name()); |
| |
| } |
| |
| void |
| EtherSwitch::unserialize(CheckpointIn &cp) |
| { |
| for (auto it : interfaces) |
| it->unserializeSection(cp, it->name()); |
| |
| } |
| |
| void |
| EtherSwitch::Interface::serialize(CheckpointOut &cp) const |
| { |
| bool event_scheduled = txEvent.scheduled(); |
| SERIALIZE_SCALAR(event_scheduled); |
| |
| if (event_scheduled) { |
| Tick event_time = txEvent.when(); |
| SERIALIZE_SCALAR(event_time); |
| } |
| outputFifo.serializeSection(cp, "outputFifo"); |
| } |
| |
| void |
| EtherSwitch::Interface::unserialize(CheckpointIn &cp) |
| { |
| bool event_scheduled; |
| UNSERIALIZE_SCALAR(event_scheduled); |
| |
| if (event_scheduled) { |
| Tick event_time; |
| UNSERIALIZE_SCALAR(event_time); |
| parent->schedule(txEvent, event_time); |
| } |
| outputFifo.unserializeSection(cp, "outputFifo"); |
| } |
| |
| void |
| EtherSwitch::Interface::PortFifoEntry::serialize(CheckpointOut &cp) const |
| { |
| packet->serialize("packet", cp); |
| SERIALIZE_SCALAR(recvTick); |
| SERIALIZE_SCALAR(srcId); |
| } |
| |
| void |
| EtherSwitch::Interface::PortFifoEntry::unserialize(CheckpointIn &cp) |
| { |
| packet = make_shared<EthPacketData>(16384); |
| packet->unserialize("packet", cp); |
| UNSERIALIZE_SCALAR(recvTick); |
| UNSERIALIZE_SCALAR(srcId); |
| } |
| |
| void |
| EtherSwitch::Interface::PortFifo::serialize(CheckpointOut &cp) const |
| { |
| SERIALIZE_SCALAR(_size); |
| int fifosize = fifo.size(); |
| |
| SERIALIZE_SCALAR(fifosize); |
| |
| int i = 0; |
| for (const auto &entry : fifo) |
| entry.serializeSection(cp, csprintf("entry%d", i++)); |
| } |
| |
| void |
| EtherSwitch::Interface::PortFifo::unserialize(CheckpointIn &cp) |
| { |
| UNSERIALIZE_SCALAR(_size); |
| int fifosize; |
| |
| UNSERIALIZE_SCALAR(fifosize); |
| fifo.clear(); |
| |
| for (int i = 0; i < fifosize; ++i) { |
| PortFifoEntry entry(nullptr, 0, 0); |
| |
| entry.unserializeSection(cp, csprintf("entry%d", i)); |
| |
| fifo.insert(entry); |
| |
| } |
| } |
| |
| EtherSwitch * |
| EtherSwitchParams::create() |
| { |
| return new EtherSwitch(this); |
| } |