/*
 * Copyright (c) 2004-2005 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: Nathan Binkert
 */

#include <deque>
#include <limits>
#include <string>

#include "arch/vtophys.hh"
#include "base/compiler.hh"
#include "base/debug.hh"
#include "base/inet.hh"
#include "base/types.hh"
#include "config/the_isa.hh"
#include "cpu/intr_control.hh"
#include "cpu/thread_context.hh"
#include "debug/EthernetAll.hh"
#include "dev/etherlink.hh"
#include "dev/sinic.hh"
#include "mem/packet.hh"
#include "mem/packet_access.hh"
#include "sim/eventq.hh"
#include "sim/stats.hh"

using namespace std;
using namespace Net;
using namespace TheISA;

namespace Sinic {

const char *RxStateStrings[] =
{
    "rxIdle",
    "rxFifoBlock",
    "rxBeginCopy",
    "rxCopy",
    "rxCopyDone"
};

const char *TxStateStrings[] =
{
    "txIdle",
    "txFifoBlock",
    "txBeginCopy",
    "txCopy",
    "txCopyDone"
};


///////////////////////////////////////////////////////////////////////
//
// Sinic PCI Device
//
Base::Base(const Params *p)
    : PciDev(p), rxEnable(false), txEnable(false),
      intrDelay(p->intr_delay), intrTick(0), cpuIntrEnable(false),
      cpuPendingIntr(false), intrEvent(0), interface(NULL)
{
}

Device::Device(const Params *p)
    : Base(p), rxUnique(0), txUnique(0),
      virtualRegs(p->virtual_count < 1 ? 1 : p->virtual_count),
      rxFifo(p->rx_fifo_size), txFifo(p->tx_fifo_size),
      rxKickTick(0), txKickTick(0),
      txEvent(this), rxDmaEvent(this), txDmaEvent(this),
      dmaReadDelay(p->dma_read_delay), dmaReadFactor(p->dma_read_factor),
      dmaWriteDelay(p->dma_write_delay), dmaWriteFactor(p->dma_write_factor)
{
    interface = new Interface(name() + ".int0", this);
    reset();

}

Device::~Device()
{}

void
Device::regStats()
{
    rxBytes
        .name(name() + ".rxBytes")
        .desc("Bytes Received")
        .prereq(rxBytes)
        ;

    rxBandwidth
        .name(name() + ".rxBandwidth")
        .desc("Receive Bandwidth (bits/s)")
        .precision(0)
        .prereq(rxBytes)
        ;

    rxPackets
        .name(name() + ".rxPackets")
        .desc("Number of Packets Received")
        .prereq(rxBytes)
        ;

    rxPacketRate
        .name(name() + ".rxPPS")
        .desc("Packet Reception Rate (packets/s)")
        .precision(0)
        .prereq(rxBytes)
        ;

    rxIpPackets
        .name(name() + ".rxIpPackets")
        .desc("Number of IP Packets Received")
        .prereq(rxBytes)
        ;

    rxTcpPackets
        .name(name() + ".rxTcpPackets")
        .desc("Number of Packets Received")
        .prereq(rxBytes)
        ;

    rxUdpPackets
        .name(name() + ".rxUdpPackets")
        .desc("Number of UDP Packets Received")
        .prereq(rxBytes)
        ;

    rxIpChecksums
        .name(name() + ".rxIpChecksums")
        .desc("Number of rx IP Checksums done by device")
        .precision(0)
        .prereq(rxBytes)
        ;

    rxTcpChecksums
        .name(name() + ".rxTcpChecksums")
        .desc("Number of rx TCP Checksums done by device")
        .precision(0)
        .prereq(rxBytes)
        ;

    rxUdpChecksums
        .name(name() + ".rxUdpChecksums")
        .desc("Number of rx UDP Checksums done by device")
        .precision(0)
        .prereq(rxBytes)
        ;

    totBandwidth
        .name(name() + ".totBandwidth")
        .desc("Total Bandwidth (bits/s)")
        .precision(0)
        .prereq(totBytes)
        ;

    totPackets
        .name(name() + ".totPackets")
        .desc("Total Packets")
        .precision(0)
        .prereq(totBytes)
        ;

    totBytes
        .name(name() + ".totBytes")
        .desc("Total Bytes")
        .precision(0)
        .prereq(totBytes)
        ;

    totPacketRate
        .name(name() + ".totPPS")
        .desc("Total Tranmission Rate (packets/s)")
        .precision(0)
        .prereq(totBytes)
        ;

    txBytes
        .name(name() + ".txBytes")
        .desc("Bytes Transmitted")
        .prereq(txBytes)
        ;

    txBandwidth
        .name(name() + ".txBandwidth")
        .desc("Transmit Bandwidth (bits/s)")
        .precision(0)
        .prereq(txBytes)
        ;

    txPackets
        .name(name() + ".txPackets")
        .desc("Number of Packets Transmitted")
        .prereq(txBytes)
        ;

    txPacketRate
        .name(name() + ".txPPS")
        .desc("Packet Tranmission Rate (packets/s)")
        .precision(0)
        .prereq(txBytes)
        ;

    txIpPackets
        .name(name() + ".txIpPackets")
        .desc("Number of IP Packets Transmitted")
        .prereq(txBytes)
        ;

    txTcpPackets
        .name(name() + ".txTcpPackets")
        .desc("Number of TCP Packets Transmitted")
        .prereq(txBytes)
        ;

    txUdpPackets
        .name(name() + ".txUdpPackets")
        .desc("Number of Packets Transmitted")
        .prereq(txBytes)
        ;

    txIpChecksums
        .name(name() + ".txIpChecksums")
        .desc("Number of tx IP Checksums done by device")
        .precision(0)
        .prereq(txBytes)
        ;

    txTcpChecksums
        .name(name() + ".txTcpChecksums")
        .desc("Number of tx TCP Checksums done by device")
        .precision(0)
        .prereq(txBytes)
        ;

    txUdpChecksums
        .name(name() + ".txUdpChecksums")
        .desc("Number of tx UDP Checksums done by device")
        .precision(0)
        .prereq(txBytes)
        ;

    txBandwidth = txBytes * Stats::constant(8) / simSeconds;
    rxBandwidth = rxBytes * Stats::constant(8) / simSeconds;
    totBandwidth = txBandwidth + rxBandwidth;
    totBytes = txBytes + rxBytes;
    totPackets = txPackets + rxPackets;
    txPacketRate = txPackets / simSeconds;
    rxPacketRate = rxPackets / simSeconds;

    _maxVnicDistance = 0;

    maxVnicDistance
        .name(name() + ".maxVnicDistance")
        .desc("maximum vnic distance")
        ;

    totalVnicDistance
        .name(name() + ".totalVnicDistance")
        .desc("total vnic distance")
        ;
    numVnicDistance
        .name(name() + ".numVnicDistance")
        .desc("number of vnic distance measurements")
        ;

    avgVnicDistance
        .name(name() + ".avgVnicDistance")
        .desc("average vnic distance")
        ;

    avgVnicDistance = totalVnicDistance / numVnicDistance;
}

void
Device::resetStats()
{
    _maxVnicDistance = 0;
}

EtherInt*
Device::getEthPort(const std::string &if_name, int idx)
{
    if (if_name == "interface") {
        if (interface->getPeer())
            panic("interface already connected to\n");

        return interface;
    }
    return NULL;
}


void
Device::prepareIO(int cpu, int index)
{
    int size = virtualRegs.size();
    if (index > size)
        panic("Trying to access a vnic that doesn't exist %d > %d\n",
              index, size);
}

//add stats for head of line blocking
//add stats for average fifo length
//add stats for average number of vnics busy

void
Device::prepareRead(int cpu, int index)
{
    using namespace Regs;
    prepareIO(cpu, index);

    VirtualReg &vnic = virtualRegs[index];

    // update rx registers
    uint64_t rxdone = vnic.RxDone;
    rxdone = set_RxDone_Packets(rxdone, rxFifo.countPacketsAfter(rxFifoPtr));
    rxdone = set_RxDone_Empty(rxdone, rxFifo.empty());
    rxdone = set_RxDone_High(rxdone, rxFifo.size() > regs.RxFifoHigh);
    rxdone = set_RxDone_NotHigh(rxdone, rxLow);
    regs.RxData = vnic.RxData;
    regs.RxDone = rxdone;
    regs.RxWait = rxdone;

    // update tx regsiters
    uint64_t txdone = vnic.TxDone;
    txdone = set_TxDone_Packets(txdone, txFifo.packets());
    txdone = set_TxDone_Full(txdone, txFifo.avail() < regs.TxMaxCopy);
    txdone = set_TxDone_Low(txdone, txFifo.size() < regs.TxFifoLow);
    regs.TxData = vnic.TxData;
    regs.TxDone = txdone;
    regs.TxWait = txdone;

    int head = 0xffff;

    if (!rxFifo.empty()) {
        int vnic = rxFifo.begin()->priv;
        if (vnic != -1 && virtualRegs[vnic].rxPacketOffset > 0)
            head = vnic;
    }

    regs.RxStatus = set_RxStatus_Head(regs.RxStatus, head);
    regs.RxStatus = set_RxStatus_Busy(regs.RxStatus, rxBusyCount);
    regs.RxStatus = set_RxStatus_Mapped(regs.RxStatus, rxMappedCount);
    regs.RxStatus = set_RxStatus_Dirty(regs.RxStatus, rxDirtyCount);
}

void
Device::prepareWrite(int cpu, int index)
{
    prepareIO(cpu, index);
}

/**
 * I/O read of device register
 */
Tick
Device::read(PacketPtr pkt)
{
    assert(config.command & PCI_CMD_MSE);
    assert(pkt->getAddr() >= BARAddrs[0] && pkt->getSize() < BARSize[0]);

    int cpu = pkt->req->contextId();
    Addr daddr = pkt->getAddr() - BARAddrs[0];
    Addr index = daddr >> Regs::VirtualShift;
    Addr raddr = daddr & Regs::VirtualMask;

    pkt->allocate();

    if (!regValid(raddr))
        panic("invalid register: cpu=%d vnic=%d da=%#x pa=%#x size=%d",
              cpu, index, daddr, pkt->getAddr(), pkt->getSize());

    const Regs::Info &info = regInfo(raddr);
    if (!info.read)
        panic("read %s (write only): "
              "cpu=%d vnic=%d da=%#x pa=%#x size=%d",
              info.name, cpu, index, daddr, pkt->getAddr(), pkt->getSize());

        panic("read %s (invalid size): "
              "cpu=%d vnic=%d da=%#x pa=%#x size=%d",
              info.name, cpu, index, daddr, pkt->getAddr(), pkt->getSize());

    prepareRead(cpu, index);

    uint64_t value M5_VAR_USED = 0;
    if (pkt->getSize() == 4) {
        uint32_t reg = regData32(raddr);
        pkt->set(reg);
        value = reg;
    }

    if (pkt->getSize() == 8) {
        uint64_t reg = regData64(raddr);
        pkt->set(reg);
        value = reg;
    }

    DPRINTF(EthernetPIO,
            "read %s: cpu=%d vnic=%d da=%#x pa=%#x size=%d val=%#x\n",
            info.name, cpu, index, daddr, pkt->getAddr(), pkt->getSize(), value);

    // reading the interrupt status register has the side effect of
    // clearing it
    if (raddr == Regs::IntrStatus)
        devIntrClear();

    return pioDelay;
}

/**
 * IPR read of device register

    Fault
Device::iprRead(Addr daddr, int cpu, uint64_t &result)
{
    if (!regValid(daddr))
        panic("invalid address: da=%#x", daddr);

    const Regs::Info &info = regInfo(daddr);
    if (!info.read)
        panic("reading %s (write only): cpu=%d da=%#x", info.name, cpu, daddr);

    DPRINTF(EthernetPIO, "IPR read %s: cpu=%d da=%#x\n",
            info.name, cpu, daddr);

    prepareRead(cpu, 0);

    if (info.size == 4)
        result = regData32(daddr);

    if (info.size == 8)
        result = regData64(daddr);

    DPRINTF(EthernetPIO, "IPR read %s: cpu=%s da=%#x val=%#x\n",
            info.name, cpu, result);

    return NoFault;
}
*/
/**
 * I/O write of device register
 */
Tick
Device::write(PacketPtr pkt)
{
    assert(config.command & PCI_CMD_MSE);
    assert(pkt->getAddr() >= BARAddrs[0] && pkt->getSize() < BARSize[0]);

    int cpu = pkt->req->contextId();
    Addr daddr = pkt->getAddr() - BARAddrs[0];
    Addr index = daddr >> Regs::VirtualShift;
    Addr raddr = daddr & Regs::VirtualMask;

    if (!regValid(raddr))
        panic("invalid register: cpu=%d, da=%#x pa=%#x size=%d",
                cpu, daddr, pkt->getAddr(), pkt->getSize());

    const Regs::Info &info = regInfo(raddr);
    if (!info.write)
        panic("write %s (read only): "
              "cpu=%d vnic=%d da=%#x pa=%#x size=%d",
              info.name, cpu, index, daddr, pkt->getAddr(), pkt->getSize());

    if (pkt->getSize() != info.size)
        panic("write %s (invalid size): "
              "cpu=%d vnic=%d da=%#x pa=%#x size=%d",
              info.name, cpu, index, daddr, pkt->getAddr(), pkt->getSize());

    VirtualReg &vnic = virtualRegs[index];

    DPRINTF(EthernetPIO,
            "write %s vnic %d: cpu=%d val=%#x da=%#x pa=%#x size=%d\n",
            info.name, index, cpu, info.size == 4 ? pkt->get<uint32_t>() :
            pkt->get<uint64_t>(), daddr, pkt->getAddr(), pkt->getSize());

    prepareWrite(cpu, index);

    switch (raddr) {
      case Regs::Config:
        changeConfig(pkt->get<uint32_t>());
        break;

      case Regs::Command:
        command(pkt->get<uint32_t>());
        break;

      case Regs::IntrStatus:
        devIntrClear(regs.IntrStatus & pkt->get<uint32_t>());
        break;

      case Regs::IntrMask:
        devIntrChangeMask(pkt->get<uint32_t>());
        break;

      case Regs::RxData:
        if (Regs::get_RxDone_Busy(vnic.RxDone))
            panic("receive machine busy with another request! rxState=%s",
                  RxStateStrings[rxState]);

        vnic.rxUnique = rxUnique++;
        vnic.RxDone = Regs::RxDone_Busy;
        vnic.RxData = pkt->get<uint64_t>();
        rxBusyCount++;

        if (Regs::get_RxData_Vaddr(pkt->get<uint64_t>())) {
            panic("vtophys not implemented in newmem");
#ifdef SINIC_VTOPHYS
            Addr vaddr = Regs::get_RxData_Addr(reg64);
            Addr paddr = vtophys(req->xc, vaddr);
            DPRINTF(EthernetPIO, "write RxData vnic %d (rxunique %d): "
                    "vaddr=%#x, paddr=%#x\n",
                    index, vnic.rxUnique, vaddr, paddr);

            vnic.RxData = Regs::set_RxData_Addr(vnic.RxData, paddr);
#endif
        } else {
            DPRINTF(EthernetPIO, "write RxData vnic %d (rxunique %d)\n",
                    index, vnic.rxUnique);
        }

        if (vnic.rxIndex == rxFifo.end()) {
            DPRINTF(EthernetPIO, "request new packet...appending to rxList\n");
            rxList.push_back(index);
        } else {
            DPRINTF(EthernetPIO, "packet exists...appending to rxBusy\n");
            rxBusy.push_back(index);
        }

        if (rxEnable && (rxState == rxIdle || rxState == rxFifoBlock)) {
            rxState = rxFifoBlock;
            rxKick();
        }
        break;

      case Regs::TxData:
        if (Regs::get_TxDone_Busy(vnic.TxDone))
            panic("transmit machine busy with another request! txState=%s",
                  TxStateStrings[txState]);

        vnic.txUnique = txUnique++;
        vnic.TxDone = Regs::TxDone_Busy;

        if (Regs::get_TxData_Vaddr(pkt->get<uint64_t>())) {
            panic("vtophys won't work here in newmem.\n");
#ifdef SINIC_VTOPHYS
            Addr vaddr = Regs::get_TxData_Addr(reg64);
            Addr paddr = vtophys(req->xc, vaddr);
            DPRINTF(EthernetPIO, "write TxData vnic %d (txunique %d): "
                    "vaddr=%#x, paddr=%#x\n",
                    index, vnic.txUnique, vaddr, paddr);

            vnic.TxData = Regs::set_TxData_Addr(vnic.TxData, paddr);
#endif
        } else {
            DPRINTF(EthernetPIO, "write TxData vnic %d (txunique %d)\n",
                    index, vnic.txUnique);
        }

        if (txList.empty() || txList.front() != index)
            txList.push_back(index);
        if (txEnable && txState == txIdle && txList.front() == index) {
            txState = txFifoBlock;
            txKick();
        }
        break;
    }

    return pioDelay;
}

void
Device::devIntrPost(uint32_t interrupts)
{
    if ((interrupts & Regs::Intr_Res))
        panic("Cannot set a reserved interrupt");

    regs.IntrStatus |= interrupts;

    DPRINTF(EthernetIntr,
            "interrupt written to intStatus: intr=%#x status=%#x mask=%#x\n",
            interrupts, regs.IntrStatus, regs.IntrMask);

    interrupts = regs.IntrStatus & regs.IntrMask;

    // Intr_RxHigh is special, we only signal it if we've emptied the fifo
    // and then filled it above the high watermark
    if (rxEmpty)
        rxEmpty = false;
    else
        interrupts &= ~Regs::Intr_RxHigh;

    // Intr_TxLow is special, we only signal it if we've filled up the fifo
    // and then dropped below the low watermark
    if (txFull)
        txFull = false;
    else
        interrupts &= ~Regs::Intr_TxLow;

    if (interrupts) {
        Tick when = curTick();
        if ((interrupts & Regs::Intr_NoDelay) == 0)
            when += intrDelay;
        cpuIntrPost(when);
    }
}

void
Device::devIntrClear(uint32_t interrupts)
{
    if ((interrupts & Regs::Intr_Res))
        panic("Cannot clear a reserved interrupt");

    regs.IntrStatus &= ~interrupts;

    DPRINTF(EthernetIntr,
            "interrupt cleared from intStatus: intr=%x status=%x mask=%x\n",
            interrupts, regs.IntrStatus, regs.IntrMask);

    if (!(regs.IntrStatus & regs.IntrMask))
        cpuIntrClear();
}

void
Device::devIntrChangeMask(uint32_t newmask)
{
    if (regs.IntrMask == newmask)
        return;

    regs.IntrMask = newmask;

    DPRINTF(EthernetIntr,
            "interrupt mask changed: intStatus=%x intMask=%x masked=%x\n",
            regs.IntrStatus, regs.IntrMask, regs.IntrStatus & regs.IntrMask);

    if (regs.IntrStatus & regs.IntrMask)
        cpuIntrPost(curTick());
    else
        cpuIntrClear();
}

void
Base::cpuIntrPost(Tick when)
{
    // If the interrupt you want to post is later than an interrupt
    // already scheduled, just let it post in the coming one and don't
    // schedule another.
    // HOWEVER, must be sure that the scheduled intrTick is in the
    // future (this was formerly the source of a bug)
    /**
     * @todo this warning should be removed and the intrTick code should
     * be fixed.
     */
    assert(when >= curTick());
    assert(intrTick >= curTick() || intrTick == 0);
    if (!cpuIntrEnable) {
        DPRINTF(EthernetIntr, "interrupts not enabled.\n",
                intrTick);
        return;
    }

    if (when > intrTick && intrTick != 0) {
        DPRINTF(EthernetIntr, "don't need to schedule event...intrTick=%d\n",
                intrTick);
        return;
    }

    intrTick = when;
    if (intrTick < curTick()) {
        Debug::breakpoint();
        intrTick = curTick();
    }

    DPRINTF(EthernetIntr, "going to schedule an interrupt for intrTick=%d\n",
            intrTick);

    if (intrEvent)
        intrEvent->squash();
    intrEvent = new IntrEvent(this, true);
    schedule(intrEvent, intrTick);
}

void
Base::cpuInterrupt()
{
    assert(intrTick == curTick());

    // Whether or not there's a pending interrupt, we don't care about
    // it anymore
    intrEvent = 0;
    intrTick = 0;

    // Don't send an interrupt if there's already one
    if (cpuPendingIntr) {
        DPRINTF(EthernetIntr,
                "would send an interrupt now, but there's already pending\n");
    } else {
        // Send interrupt
        cpuPendingIntr = true;

        DPRINTF(EthernetIntr, "posting interrupt\n");
        intrPost();
    }
}

void
Base::cpuIntrClear()
{
    if (!cpuPendingIntr)
        return;

    if (intrEvent) {
        intrEvent->squash();
        intrEvent = 0;
    }

    intrTick = 0;

    cpuPendingIntr = false;

    DPRINTF(EthernetIntr, "clearing cchip interrupt\n");
    intrClear();
}

bool
Base::cpuIntrPending() const
{ return cpuPendingIntr; }

void
Device::changeConfig(uint32_t newconf)
{
    uint32_t changed = regs.Config ^ newconf;
    if (!changed)
        return;

    regs.Config = newconf;

    if ((changed & Regs::Config_IntEn)) {
        cpuIntrEnable = regs.Config & Regs::Config_IntEn;
        if (cpuIntrEnable) {
            if (regs.IntrStatus & regs.IntrMask)
                cpuIntrPost(curTick());
        } else {
            cpuIntrClear();
        }
    }

    if ((changed & Regs::Config_TxEn)) {
        txEnable = regs.Config & Regs::Config_TxEn;
        if (txEnable)
            txKick();
    }

    if ((changed & Regs::Config_RxEn)) {
        rxEnable = regs.Config & Regs::Config_RxEn;
        if (rxEnable)
            rxKick();
    }
}

void
Device::command(uint32_t command)
{
    if (command & Regs::Command_Intr)
        devIntrPost(Regs::Intr_Soft);

    if (command & Regs::Command_Reset)
        reset();
}

void
Device::reset()
{
    using namespace Regs;

    memset(&regs, 0, sizeof(regs));

    regs.Config = 0;
    if (params()->rx_thread)
        regs.Config |= Config_RxThread;
    if (params()->tx_thread)
        regs.Config |= Config_TxThread;
    if (params()->rss)
        regs.Config |= Config_RSS;
    if (params()->zero_copy)
        regs.Config |= Config_ZeroCopy;
    if (params()->delay_copy)
        regs.Config |= Config_DelayCopy;
    if (params()->virtual_addr)
        regs.Config |= Config_Vaddr;

    if (params()->delay_copy && params()->zero_copy)
        panic("Can't delay copy and zero copy");

    regs.IntrMask = Intr_Soft | Intr_RxHigh | Intr_RxPacket | Intr_TxLow;
    regs.RxMaxCopy = params()->rx_max_copy;
    regs.TxMaxCopy = params()->tx_max_copy;
    regs.ZeroCopySize = params()->zero_copy_size;
    regs.ZeroCopyMark = params()->zero_copy_threshold;
    regs.VirtualCount = params()->virtual_count;
    regs.RxMaxIntr = params()->rx_max_intr;
    regs.RxFifoSize = params()->rx_fifo_size;
    regs.TxFifoSize = params()->tx_fifo_size;
    regs.RxFifoLow = params()->rx_fifo_low_mark;
    regs.TxFifoLow = params()->tx_fifo_threshold;
    regs.RxFifoHigh = params()->rx_fifo_threshold;
    regs.TxFifoHigh = params()->tx_fifo_high_mark;
    regs.HwAddr = params()->hardware_address;

    if (regs.RxMaxCopy < regs.ZeroCopyMark)
        panic("Must be able to copy at least as many bytes as the threshold");

    if (regs.ZeroCopySize >= regs.ZeroCopyMark)
        panic("The number of bytes to copy must be less than the threshold");

    rxList.clear();
    rxBusy.clear();
    rxActive = -1;
    txList.clear();
    rxBusyCount = 0;
    rxDirtyCount = 0;
    rxMappedCount = 0;

    rxState = rxIdle;
    txState = txIdle;

    rxFifo.clear();
    rxFifoPtr = rxFifo.end();
    txFifo.clear();
    rxEmpty = false;
    rxLow = true;
    txFull = false;

    int size = virtualRegs.size();
    virtualRegs.clear();
    virtualRegs.resize(size);
    for (int i = 0; i < size; ++i)
        virtualRegs[i].rxIndex = rxFifo.end();
}

void
Device::rxDmaDone()
{
    assert(rxState == rxCopy);
    rxState = rxCopyDone;
    DPRINTF(EthernetDMA, "end rx dma write paddr=%#x len=%d\n",
            rxDmaAddr, rxDmaLen);
    DDUMP(EthernetData, rxDmaData, rxDmaLen);

    // If the transmit state machine  has a pending DMA, let it go first
    if (txState == txBeginCopy)
        txKick();

    rxKick();
}

void
Device::rxKick()
{
    VirtualReg *vnic = NULL;

    DPRINTF(EthernetSM, "rxKick: rxState=%s (rxFifo.size=%d)\n",
            RxStateStrings[rxState], rxFifo.size());

    if (rxKickTick > curTick()) {
        DPRINTF(EthernetSM, "rxKick: exiting, can't run till %d\n",
                rxKickTick);
        return;
    }

  next:
    rxFifo.check();
    if (rxState == rxIdle)
        goto exit;

    if (rxActive == -1) {
        if (rxState != rxFifoBlock)
            panic("no active vnic while in state %s", RxStateStrings[rxState]);

        DPRINTF(EthernetSM, "processing rxState=%s\n",
                RxStateStrings[rxState]);
    } else {
        vnic = &virtualRegs[rxActive];
        DPRINTF(EthernetSM,
                "processing rxState=%s for vnic %d (rxunique %d)\n",
                RxStateStrings[rxState], rxActive, vnic->rxUnique);
    }

    switch (rxState) {
      case rxFifoBlock:
        if (DTRACE(EthernetSM)) {
            PacketFifo::iterator end = rxFifo.end();
            int size = virtualRegs.size();
            for (int i = 0; i < size; ++i) {
                VirtualReg *vn = &virtualRegs[i];
                bool busy = Regs::get_RxDone_Busy(vn->RxDone);
                if (vn->rxIndex != end) {
#ifndef NDEBUG
                    bool dirty = vn->rxPacketOffset > 0;
                    const char *status;

                    if (busy && dirty)
                        status = "busy,dirty";
                    else if (busy)
                        status = "busy";
                    else if (dirty)
                        status = "dirty";
                    else
                        status = "mapped";

                    DPRINTF(EthernetSM,
                            "vnic %d %s (rxunique %d), packet %d, slack %d\n",
                            i, status, vn->rxUnique,
                            rxFifo.countPacketsBefore(vn->rxIndex),
                            vn->rxIndex->slack);
#endif
                } else if (busy) {
                    DPRINTF(EthernetSM, "vnic %d unmapped (rxunique %d)\n",
                            i, vn->rxUnique);
                }
            }
        }

        if (!rxBusy.empty()) {
            rxActive = rxBusy.front();
            rxBusy.pop_front();
            vnic = &virtualRegs[rxActive];

            if (vnic->rxIndex == rxFifo.end())
                panic("continuing vnic without packet\n");

            DPRINTF(EthernetSM,
                    "continue processing for vnic %d (rxunique %d)\n",
                    rxActive, vnic->rxUnique);

            rxState = rxBeginCopy;

            int vnic_distance = rxFifo.countPacketsBefore(vnic->rxIndex);
            totalVnicDistance += vnic_distance;
            numVnicDistance += 1;
            if (vnic_distance > _maxVnicDistance) {
                maxVnicDistance = vnic_distance;
                _maxVnicDistance = vnic_distance;
            }

            break;
        }

        if (rxFifoPtr == rxFifo.end()) {
            DPRINTF(EthernetSM, "receive waiting for data.  Nothing to do.\n");
            goto exit;
        }

        if (rxList.empty())
            panic("Not idle, but nothing to do!");

        assert(!rxFifo.empty());

        rxActive = rxList.front();
        rxList.pop_front();
        vnic = &virtualRegs[rxActive];

        DPRINTF(EthernetSM,
                "processing new packet for vnic %d (rxunique %d)\n",
                rxActive, vnic->rxUnique);

        // Grab a new packet from the fifo.
        vnic->rxIndex = rxFifoPtr++;
        vnic->rxIndex->priv = rxActive;
        vnic->rxPacketOffset = 0;
        vnic->rxPacketBytes = vnic->rxIndex->packet->length;
        assert(vnic->rxPacketBytes);
        rxMappedCount++;

        vnic->rxDoneData = 0;
        /* scope for variables */ {
            IpPtr ip(vnic->rxIndex->packet);
            if (ip) {
                DPRINTF(Ethernet, "ID is %d\n", ip->id());
                vnic->rxDoneData |= Regs::RxDone_IpPacket;
                rxIpChecksums++;
                if (cksum(ip) != 0) {
                    DPRINTF(EthernetCksum, "Rx IP Checksum Error\n");
                    vnic->rxDoneData |= Regs::RxDone_IpError;
                }
                TcpPtr tcp(ip);
                UdpPtr udp(ip);
                if (tcp) {
                    DPRINTF(Ethernet,
                            "Src Port=%d, Dest Port=%d, Seq=%d, Ack=%d\n",
                            tcp->sport(), tcp->dport(), tcp->seq(),
                            tcp->ack());
                    vnic->rxDoneData |= Regs::RxDone_TcpPacket;
                    rxTcpChecksums++;
                    if (cksum(tcp) != 0) {
                        DPRINTF(EthernetCksum, "Rx TCP Checksum Error\n");
                        vnic->rxDoneData |= Regs::RxDone_TcpError;
                    }
                } else if (udp) {
                    vnic->rxDoneData |= Regs::RxDone_UdpPacket;
                    rxUdpChecksums++;
                    if (cksum(udp) != 0) {
                        DPRINTF(EthernetCksum, "Rx UDP Checksum Error\n");
                        vnic->rxDoneData |= Regs::RxDone_UdpError;
                    }
                }
            }
        }
        rxState = rxBeginCopy;
        break;

      case rxBeginCopy:
        if (dmaPending() || getState() != Running)
            goto exit;

        rxDmaAddr = params()->platform->pciToDma(
                Regs::get_RxData_Addr(vnic->RxData));
        rxDmaLen = min<unsigned>(Regs::get_RxData_Len(vnic->RxData),
                                 vnic->rxPacketBytes);

        /*
         * if we're doing zero/delay copy and we're below the fifo
         * threshold, see if we should try to do the zero/defer copy
         */
        if ((Regs::get_Config_ZeroCopy(regs.Config) ||
             Regs::get_Config_DelayCopy(regs.Config)) &&
            !Regs::get_RxData_NoDelay(vnic->RxData) && rxLow) {
            if (rxDmaLen > regs.ZeroCopyMark)
                rxDmaLen = regs.ZeroCopySize;
        }
        rxDmaData = vnic->rxIndex->packet->data + vnic->rxPacketOffset;
        rxState = rxCopy;
        if (rxDmaAddr == 1LL) {
            rxState = rxCopyDone;
            break;
        }

        dmaWrite(rxDmaAddr, rxDmaLen, &rxDmaEvent, rxDmaData);
        break;

      case rxCopy:
        DPRINTF(EthernetSM, "receive machine still copying\n");
        goto exit;

      case rxCopyDone:
        vnic->RxDone = vnic->rxDoneData;
        vnic->RxDone |= Regs::RxDone_Complete;
        rxBusyCount--;

        if (vnic->rxPacketBytes == rxDmaLen) {
            if (vnic->rxPacketOffset)
                rxDirtyCount--;

            // Packet is complete.  Indicate how many bytes were copied
            vnic->RxDone = Regs::set_RxDone_CopyLen(vnic->RxDone, rxDmaLen);

            DPRINTF(EthernetSM,
                    "rxKick: packet complete on vnic %d (rxunique %d)\n",
                    rxActive, vnic->rxUnique);
            rxFifo.remove(vnic->rxIndex);
            vnic->rxIndex = rxFifo.end();
            rxMappedCount--;
        } else {
            if (!vnic->rxPacketOffset)
                rxDirtyCount++;

            vnic->rxPacketBytes -= rxDmaLen;
            vnic->rxPacketOffset += rxDmaLen;
            vnic->RxDone |= Regs::RxDone_More;
            vnic->RxDone = Regs::set_RxDone_CopyLen(vnic->RxDone,
                                                    vnic->rxPacketBytes);
            DPRINTF(EthernetSM,
                    "rxKick: packet not complete on vnic %d (rxunique %d): "
                    "%d bytes left\n",
                    rxActive, vnic->rxUnique, vnic->rxPacketBytes);
        }

        rxActive = -1;
        rxState = rxBusy.empty() && rxList.empty() ? rxIdle : rxFifoBlock;

        if (rxFifo.empty()) {
            devIntrPost(Regs::Intr_RxEmpty);
            rxEmpty = true;
        }

        if (rxFifo.size() < regs.RxFifoLow)
            rxLow = true;

        if (rxFifo.size() > regs.RxFifoHigh)
            rxLow = false;

        devIntrPost(Regs::Intr_RxDMA);
        break;

      default:
        panic("Invalid rxState!");
    }

    DPRINTF(EthernetSM, "entering next rxState=%s\n",
            RxStateStrings[rxState]);

    goto next;

  exit:
    /**
     * @todo do we want to schedule a future kick?
     */
    DPRINTF(EthernetSM, "rx state machine exited rxState=%s\n",
            RxStateStrings[rxState]);
}

void
Device::txDmaDone()
{
    assert(txState == txCopy);
    txState = txCopyDone;
    DPRINTF(EthernetDMA, "tx dma read paddr=%#x len=%d\n",
            txDmaAddr, txDmaLen);
    DDUMP(EthernetData, txDmaData, txDmaLen);

    // If the receive state machine  has a pending DMA, let it go first
    if (rxState == rxBeginCopy)
        rxKick();

    txKick();
}

void
Device::transmit()
{
    if (txFifo.empty()) {
        DPRINTF(Ethernet, "nothing to transmit\n");
        return;
    }

    uint32_t interrupts;
    EthPacketPtr packet = txFifo.front();
    if (!interface->sendPacket(packet)) {
        DPRINTF(Ethernet, "Packet Transmit: failed txFifo available %d\n",
                txFifo.avail());
        return;
    }

    txFifo.pop();
#if TRACING_ON
    if (DTRACE(Ethernet)) {
        IpPtr ip(packet);
        if (ip) {
            DPRINTF(Ethernet, "ID is %d\n", ip->id());
            TcpPtr tcp(ip);
            if (tcp) {
                DPRINTF(Ethernet,
                        "Src Port=%d, Dest Port=%d, Seq=%d, Ack=%d\n",
                        tcp->sport(), tcp->dport(), tcp->seq(),
                        tcp->ack());
            }
        }
    }
#endif

    DDUMP(EthernetData, packet->data, packet->length);
    txBytes += packet->length;
    txPackets++;

    DPRINTF(Ethernet, "Packet Transmit: successful txFifo Available %d\n",
            txFifo.avail());

    interrupts = Regs::Intr_TxPacket;
    if (txFifo.size() < regs.TxFifoLow)
        interrupts |= Regs::Intr_TxLow;
    devIntrPost(interrupts);
}

void
Device::txKick()
{
    VirtualReg *vnic;
    DPRINTF(EthernetSM, "txKick: txState=%s (txFifo.size=%d)\n",
            TxStateStrings[txState], txFifo.size());

    if (txKickTick > curTick()) {
        DPRINTF(EthernetSM, "txKick: exiting, can't run till %d\n",
                txKickTick);
        return;
    }

  next:
    if (txState == txIdle)
        goto exit;

    assert(!txList.empty());
    vnic = &virtualRegs[txList.front()];

    switch (txState) {
      case txFifoBlock:
        assert(Regs::get_TxDone_Busy(vnic->TxDone));
        if (!txPacket) {
            // Grab a new packet from the fifo.
            txPacket = new EthPacketData(16384);
            txPacketOffset = 0;
        }

        if (txFifo.avail() - txPacket->length <
            Regs::get_TxData_Len(vnic->TxData)) {
            DPRINTF(EthernetSM, "transmit fifo full.  Nothing to do.\n");
            goto exit;
        }

        txState = txBeginCopy;
        break;

      case txBeginCopy:
        if (dmaPending() || getState() != Running)
            goto exit;

        txDmaAddr = params()->platform->pciToDma(
                Regs::get_TxData_Addr(vnic->TxData));
        txDmaLen = Regs::get_TxData_Len(vnic->TxData);
        txDmaData = txPacket->data + txPacketOffset;
        txState = txCopy;

        dmaRead(txDmaAddr, txDmaLen, &txDmaEvent, txDmaData);
        break;

      case txCopy:
        DPRINTF(EthernetSM, "transmit machine still copying\n");
        goto exit;

      case txCopyDone:
        vnic->TxDone = txDmaLen | Regs::TxDone_Complete;
        txPacket->length += txDmaLen;
        if ((vnic->TxData & Regs::TxData_More)) {
            txPacketOffset += txDmaLen;
            txState = txIdle;
            devIntrPost(Regs::Intr_TxDMA);
            break;
        }

        assert(txPacket->length <= txFifo.avail());
        if ((vnic->TxData & Regs::TxData_Checksum)) {
            IpPtr ip(txPacket);
            if (ip) {
                TcpPtr tcp(ip);
                if (tcp) {
                    tcp->sum(0);
                    tcp->sum(cksum(tcp));
                    txTcpChecksums++;
                }

                UdpPtr udp(ip);
                if (udp) {
                    udp->sum(0);
                    udp->sum(cksum(udp));
                    txUdpChecksums++;
                }

                ip->sum(0);
                ip->sum(cksum(ip));
                txIpChecksums++;
            }
        }

        txFifo.push(txPacket);
        if (txFifo.avail() < regs.TxMaxCopy) {
            devIntrPost(Regs::Intr_TxFull);
            txFull = true;
        }
        txPacket = 0;
        transmit();
        txList.pop_front();
        txState = txList.empty() ? txIdle : txFifoBlock;
        devIntrPost(Regs::Intr_TxDMA);
        break;

      default:
        panic("Invalid txState!");
    }

    DPRINTF(EthernetSM, "entering next txState=%s\n",
            TxStateStrings[txState]);

    goto next;

  exit:
    /**
     * @todo do we want to schedule a future kick?
     */
    DPRINTF(EthernetSM, "tx state machine exited txState=%s\n",
            TxStateStrings[txState]);
}

void
Device::transferDone()
{
    if (txFifo.empty()) {
        DPRINTF(Ethernet, "transfer complete: txFifo empty...nothing to do\n");
        return;
    }

    DPRINTF(Ethernet, "transfer complete: data in txFifo...schedule xmit\n");

    reschedule(txEvent, curTick() + clockPeriod(), true);
}

bool
Device::rxFilter(const EthPacketPtr &packet)
{
    if (!Regs::get_Config_Filter(regs.Config))
        return false;

    panic("receive filter not implemented\n");
    bool drop = true;

#if 0
    string type;

    EthHdr *eth = packet->eth();
    if (eth->unicast()) {
        // If we're accepting all unicast addresses
        if (acceptUnicast)
            drop = false;

        // If we make a perfect match
        if (acceptPerfect && params->eaddr == eth.dst())
            drop = false;

        if (acceptArp && eth->type() == ETH_TYPE_ARP)
            drop = false;

    } else if (eth->broadcast()) {
        // if we're accepting broadcasts
        if (acceptBroadcast)
            drop = false;

    } else if (eth->multicast()) {
        // if we're accepting all multicasts
        if (acceptMulticast)
            drop = false;

    }

    if (drop) {
        DPRINTF(Ethernet, "rxFilter drop\n");
        DDUMP(EthernetData, packet->data, packet->length);
    }
#endif
    return drop;
}

bool
Device::recvPacket(EthPacketPtr packet)
{
    rxBytes += packet->length;
    rxPackets++;

    DPRINTF(Ethernet, "Receiving packet from wire, rxFifo Available is %d\n",
            rxFifo.avail());

    if (!rxEnable) {
        DPRINTF(Ethernet, "receive disabled...packet dropped\n");
        return true;
    }

    if (rxFilter(packet)) {
        DPRINTF(Ethernet, "packet filtered...dropped\n");
        return true;
    }

    if (rxFifo.size() >= regs.RxFifoHigh)
        devIntrPost(Regs::Intr_RxHigh);

    if (!rxFifo.push(packet)) {
        DPRINTF(Ethernet,
                "packet will not fit in receive buffer...packet dropped\n");
        return false;
    }

    // If we were at the last element, back up one ot go to the new
    // last element of the list.
    if (rxFifoPtr == rxFifo.end())
        --rxFifoPtr;

    devIntrPost(Regs::Intr_RxPacket);
    rxKick();
    return true;
}

void
Device::resume()
{
    SimObject::resume();

    // During drain we could have left the state machines in a waiting state and
    // they wouldn't get out until some other event occured to kick them.
    // This way they'll get out immediately
    txKick();
    rxKick();
}

//=====================================================================
//
//
void
Base::serialize(std::ostream &os)
{
    // Serialize the PciDev base class
    PciDev::serialize(os);

    SERIALIZE_SCALAR(rxEnable);
    SERIALIZE_SCALAR(txEnable);
    SERIALIZE_SCALAR(cpuIntrEnable);

    /*
     * Keep track of pending interrupt status.
     */
    SERIALIZE_SCALAR(intrTick);
    SERIALIZE_SCALAR(cpuPendingIntr);
    Tick intrEventTick = 0;
    if (intrEvent)
        intrEventTick = intrEvent->when();
    SERIALIZE_SCALAR(intrEventTick);
}

void
Base::unserialize(Checkpoint *cp, const std::string &section)
{
    // Unserialize the PciDev base class
    PciDev::unserialize(cp, section);

    UNSERIALIZE_SCALAR(rxEnable);
    UNSERIALIZE_SCALAR(txEnable);
    UNSERIALIZE_SCALAR(cpuIntrEnable);

    /*
     * Keep track of pending interrupt status.
     */
    UNSERIALIZE_SCALAR(intrTick);
    UNSERIALIZE_SCALAR(cpuPendingIntr);
    Tick intrEventTick;
    UNSERIALIZE_SCALAR(intrEventTick);
    if (intrEventTick) {
        intrEvent = new IntrEvent(this, true);
        schedule(intrEvent, intrEventTick);
    }
}

void
Device::serialize(std::ostream &os)
{
    int count;

    // Serialize the PciDev base class
    Base::serialize(os);

    if (rxState == rxCopy)
        panic("can't serialize with an in flight dma request rxState=%s",
              RxStateStrings[rxState]);

    if (txState == txCopy)
        panic("can't serialize with an in flight dma request txState=%s",
              TxStateStrings[txState]);

    /*
     * Serialize the device registers that could be modified by the OS.
     */
    SERIALIZE_SCALAR(regs.Config);
    SERIALIZE_SCALAR(regs.IntrStatus);
    SERIALIZE_SCALAR(regs.IntrMask);
    SERIALIZE_SCALAR(regs.RxData);
    SERIALIZE_SCALAR(regs.TxData);

    /*
     * Serialize the virtual nic state
     */
    int virtualRegsSize = virtualRegs.size();
    SERIALIZE_SCALAR(virtualRegsSize);
    for (int i = 0; i < virtualRegsSize; ++i) {
        VirtualReg *vnic = &virtualRegs[i];

        std::string reg = csprintf("vnic%d", i);
        paramOut(os, reg + ".RxData", vnic->RxData);
        paramOut(os, reg + ".RxDone", vnic->RxDone);
        paramOut(os, reg + ".TxData", vnic->TxData);
        paramOut(os, reg + ".TxDone", vnic->TxDone);

        bool rxPacketExists = vnic->rxIndex != rxFifo.end();
        paramOut(os, reg + ".rxPacketExists", rxPacketExists);
        if (rxPacketExists) {
            int rxPacket = 0;
            PacketFifo::iterator i = rxFifo.begin();
            while (i != vnic->rxIndex) {
                assert(i != rxFifo.end());
                ++i;
                ++rxPacket;
            }

            paramOut(os, reg + ".rxPacket", rxPacket);
            paramOut(os, reg + ".rxPacketOffset", vnic->rxPacketOffset);
            paramOut(os, reg + ".rxPacketBytes", vnic->rxPacketBytes);
        }
        paramOut(os, reg + ".rxDoneData", vnic->rxDoneData);
    }

    int rxFifoPtr = -1;
    if (this->rxFifoPtr != rxFifo.end())
        rxFifoPtr = rxFifo.countPacketsBefore(this->rxFifoPtr);
    SERIALIZE_SCALAR(rxFifoPtr);

    SERIALIZE_SCALAR(rxActive);
    SERIALIZE_SCALAR(rxBusyCount);
    SERIALIZE_SCALAR(rxDirtyCount);
    SERIALIZE_SCALAR(rxMappedCount);

    VirtualList::iterator i, end;
    for (count = 0, i = rxList.begin(), end = rxList.end(); i != end; ++i)
        paramOut(os, csprintf("rxList%d", count++), *i);
    int rxListSize = count;
    SERIALIZE_SCALAR(rxListSize);

    for (count = 0, i = rxBusy.begin(), end = rxBusy.end(); i != end; ++i)
        paramOut(os, csprintf("rxBusy%d", count++), *i);
    int rxBusySize = count;
    SERIALIZE_SCALAR(rxBusySize);

    for (count = 0, i = txList.begin(), end = txList.end(); i != end; ++i)
        paramOut(os, csprintf("txList%d", count++), *i);
    int txListSize = count;
    SERIALIZE_SCALAR(txListSize);

    /*
     * Serialize rx state machine
     */
    int rxState = this->rxState;
    SERIALIZE_SCALAR(rxState);
    SERIALIZE_SCALAR(rxEmpty);
    SERIALIZE_SCALAR(rxLow);
    rxFifo.serialize("rxFifo", os);

    /*
     * Serialize tx state machine
     */
    int txState = this->txState;
    SERIALIZE_SCALAR(txState);
    SERIALIZE_SCALAR(txFull);
    txFifo.serialize("txFifo", os);
    bool txPacketExists = txPacket;
    SERIALIZE_SCALAR(txPacketExists);
    if (txPacketExists) {
        txPacket->serialize("txPacket", os);
        SERIALIZE_SCALAR(txPacketOffset);
        SERIALIZE_SCALAR(txPacketBytes);
    }

    /*
     * If there's a pending transmit, store the time so we can
     * reschedule it later
     */
    Tick transmitTick = txEvent.scheduled() ? txEvent.when() - curTick() : 0;
    SERIALIZE_SCALAR(transmitTick);
}

void
Device::unserialize(Checkpoint *cp, const std::string &section)
{
    // Unserialize the PciDev base class
    Base::unserialize(cp, section);

    /*
     * Unserialize the device registers that may have been written by the OS.
     */
    UNSERIALIZE_SCALAR(regs.Config);
    UNSERIALIZE_SCALAR(regs.IntrStatus);
    UNSERIALIZE_SCALAR(regs.IntrMask);
    UNSERIALIZE_SCALAR(regs.RxData);
    UNSERIALIZE_SCALAR(regs.TxData);

    UNSERIALIZE_SCALAR(rxActive);
    UNSERIALIZE_SCALAR(rxBusyCount);
    UNSERIALIZE_SCALAR(rxDirtyCount);
    UNSERIALIZE_SCALAR(rxMappedCount);

    int rxListSize;
    UNSERIALIZE_SCALAR(rxListSize);
    rxList.clear();
    for (int i = 0; i < rxListSize; ++i) {
        int value;
        paramIn(cp, section, csprintf("rxList%d", i), value);
        rxList.push_back(value);
    }

    int rxBusySize;
    UNSERIALIZE_SCALAR(rxBusySize);
    rxBusy.clear();
    for (int i = 0; i < rxBusySize; ++i) {
        int value;
        paramIn(cp, section, csprintf("rxBusy%d", i), value);
        rxBusy.push_back(value);
    }

    int txListSize;
    UNSERIALIZE_SCALAR(txListSize);
    txList.clear();
    for (int i = 0; i < txListSize; ++i) {
        int value;
        paramIn(cp, section, csprintf("txList%d", i), value);
        txList.push_back(value);
    }

    /*
     * Unserialize rx state machine
     */
    int rxState;
    UNSERIALIZE_SCALAR(rxState);
    UNSERIALIZE_SCALAR(rxEmpty);
    UNSERIALIZE_SCALAR(rxLow);
    this->rxState = (RxState) rxState;
    rxFifo.unserialize("rxFifo", cp, section);

    int rxFifoPtr;
    UNSERIALIZE_SCALAR(rxFifoPtr);
    if (rxFifoPtr >= 0) {
        this->rxFifoPtr = rxFifo.begin();
        for (int i = 0; i < rxFifoPtr; ++i)
            ++this->rxFifoPtr;
    } else {
        this->rxFifoPtr = rxFifo.end();
    }

    /*
     * Unserialize tx state machine
     */
    int txState;
    UNSERIALIZE_SCALAR(txState);
    UNSERIALIZE_SCALAR(txFull);
    this->txState = (TxState) txState;
    txFifo.unserialize("txFifo", cp, section);
    bool txPacketExists;
    UNSERIALIZE_SCALAR(txPacketExists);
    txPacket = 0;
    if (txPacketExists) {
        txPacket = new EthPacketData(16384);
        txPacket->unserialize("txPacket", cp, section);
        UNSERIALIZE_SCALAR(txPacketOffset);
        UNSERIALIZE_SCALAR(txPacketBytes);
    }

    /*
     * unserialize the virtual nic registers/state
     *
     * this must be done after the unserialization of the rxFifo
     * because the packet iterators depend on the fifo being populated
     */
    int virtualRegsSize;
    UNSERIALIZE_SCALAR(virtualRegsSize);
    virtualRegs.clear();
    virtualRegs.resize(virtualRegsSize);
    for (int i = 0; i < virtualRegsSize; ++i) {
        VirtualReg *vnic = &virtualRegs[i];
        std::string reg = csprintf("vnic%d", i);

        paramIn(cp, section, reg + ".RxData", vnic->RxData);
        paramIn(cp, section, reg + ".RxDone", vnic->RxDone);
        paramIn(cp, section, reg + ".TxData", vnic->TxData);
        paramIn(cp, section, reg + ".TxDone", vnic->TxDone);

        vnic->rxUnique = rxUnique++;
        vnic->txUnique = txUnique++;

        bool rxPacketExists;
        paramIn(cp, section, reg + ".rxPacketExists", rxPacketExists);
        if (rxPacketExists) {
            int rxPacket;
            paramIn(cp, section, reg + ".rxPacket", rxPacket);
            vnic->rxIndex = rxFifo.begin();
            while (rxPacket--)
                ++vnic->rxIndex;

            paramIn(cp, section, reg + ".rxPacketOffset",
                    vnic->rxPacketOffset);
            paramIn(cp, section, reg + ".rxPacketBytes", vnic->rxPacketBytes);
        } else {
            vnic->rxIndex = rxFifo.end();
        }
        paramIn(cp, section, reg + ".rxDoneData", vnic->rxDoneData);
    }

    /*
     * If there's a pending transmit, reschedule it now
     */
    Tick transmitTick;
    UNSERIALIZE_SCALAR(transmitTick);
    if (transmitTick)
        schedule(txEvent, curTick() + transmitTick);

    pioPort.sendRangeChange();

}

} // namespace Sinic

Sinic::Device *
SinicParams::create()
{
    return new Sinic::Device(this);
}
