/*
 * Copyright (c) 2004 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.
 */

#include <cstdio>
#include <deque>
#include <string>

#include "base/inet.hh"
#include "cpu/exec_context.hh"
#include "cpu/intr_control.hh"
#include "dev/etherlink.hh"
#include "dev/sinic.hh"
#include "dev/pciconfigall.hh"
#include "mem/bus/bus.hh"
#include "mem/bus/dma_interface.hh"
#include "mem/bus/pio_interface.hh"
#include "mem/bus/pio_interface_impl.hh"
#include "mem/functional/memory_control.hh"
#include "mem/functional/physical.hh"
#include "sim/builder.hh"
#include "sim/debug.hh"
#include "sim/eventq.hh"
#include "sim/host.hh"
#include "sim/stats.hh"
#include "targetarch/vtophys.hh"

using namespace Net;

namespace Sinic {

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

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


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

Device::Device(Params *p)
    : Base(p), plat(p->plat), physmem(p->physmem),
      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)
{
    reset();

    if (p->io_bus) {
        pioInterface = newPioInterface(p->name, p->hier, p->io_bus, this,
                                       &Device::cacheAccess);

        pioLatency = p->pio_latency * p->io_bus->clockRate;

        if (p->payload_bus)
            dmaInterface = new DMAInterface<Bus>(p->name + ".dma", p->io_bus,
                                                 p->payload_bus, 1,
                                                 p->dma_no_allocate);
        else
            dmaInterface = new DMAInterface<Bus>(p->name + ".dma", p->io_bus,
                                                 p->io_bus, 1,
                                                 p->dma_no_allocate);
    } else if (p->payload_bus) {
        pioInterface = newPioInterface(p->name, p->hier, p->payload_bus, this,
                                       &Device::cacheAccess);

        pioLatency = p->pio_latency * p->payload_bus->clockRate;

        dmaInterface = new DMAInterface<Bus>(p->name + ".dma", p->payload_bus,
                                             p->payload_bus, 1,
                                             p->dma_no_allocate);
    }
}

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;
}

/**
 * This is to write to the PCI general configuration registers
 */
void
Device::WriteConfig(int offset, int size, uint32_t data)
{
    switch (offset) {
      case PCI0_BASE_ADDR0:
        // Need to catch writes to BARs to update the PIO interface
        PciDev::WriteConfig(offset, size, data);
        if (BARAddrs[0] != 0) {
            if (pioInterface)
                pioInterface->addAddrRange(RangeSize(BARAddrs[0], BARSize[0]));

            BARAddrs[0] &= EV5::PAddrUncachedMask;
        }
        break;

      default:
        PciDev::WriteConfig(offset, size, data);
    }
}

/**
 * This reads the device registers, which are detailed in the NS83820
 * spec sheet
 */
Fault
Device::read(MemReqPtr &req, uint8_t *data)
{
    assert(config.hdr.command & PCI_CMD_MSE);

    //The mask is to give you only the offset into the device register file
    Addr daddr = req->paddr & 0xfff;

    if (Regs::regSize(daddr) == 0)
        panic("invalid address: da=%#x pa=%#x va=%#x size=%d",
              daddr, req->paddr, req->vaddr, req->size);

    if (req->size != Regs::regSize(daddr))
        panic("invalid size for reg %s: da=%#x pa=%#x va=%#x size=%d",
              Regs::regName(daddr), daddr, req->paddr, req->vaddr, req->size);

    DPRINTF(EthernetPIO, "read reg=%s da=%#x pa=%#x va=%#x size=%d\n",
            Regs::regName(daddr), daddr, req->paddr, req->vaddr, req->size);

    uint32_t &reg32 = *(uint32_t *)data;
    uint64_t &reg64 = *(uint64_t *)data;

    switch (daddr) {
      case Regs::Config:
        reg32 = regs.Config;
        break;

      case Regs::RxMaxCopy:
        reg32 = regs.RxMaxCopy;
        break;

      case Regs::TxMaxCopy:
        reg32 = regs.TxMaxCopy;
        break;

      case Regs::RxThreshold:
        reg32 = regs.RxThreshold;
        break;

      case Regs::TxThreshold:
        reg32 = regs.TxThreshold;
        break;

      case Regs::IntrStatus:
        reg32 = regs.IntrStatus;
        devIntrClear();
        break;

      case Regs::IntrMask:
        reg32 = regs.IntrMask;
        break;

      case Regs::RxData:
        reg64 = regs.RxData;
        break;

      case Regs::RxDone:
      case Regs::RxWait:
        reg64 = Regs::set_RxDone_FifoLen(regs.RxDone,
                                         min(rxFifo.packets(), 255));
        break;

      case Regs::TxData:
        reg64 = regs.TxData;
        break;

      case Regs::TxDone:
      case Regs::TxWait:
        reg64 = Regs::set_TxDone_FifoLen(regs.TxDone,
                                         min(txFifo.packets(), 255));
        break;

      case Regs::HwAddr:
        reg64 = params()->eaddr;
        break;

      default:
        panic("reading write only register %s: da=%#x pa=%#x va=%#x size=%d",
              Regs::regName(daddr), daddr, req->paddr, req->vaddr, req->size);
    }

    DPRINTF(EthernetPIO, "read reg=%s done val=%#x\n", Regs::regName(daddr),
            Regs::regSize(daddr) == 4 ? reg32 : reg64);

    return No_Fault;
}

Fault
Device::write(MemReqPtr &req, const uint8_t *data)
{
    assert(config.hdr.command & PCI_CMD_MSE);
    Addr daddr = req->paddr & 0xfff;

    if (Regs::regSize(daddr) == 0)
        panic("invalid address: da=%#x pa=%#x va=%#x size=%d",
              daddr, req->paddr, req->vaddr, req->size);

    if (req->size != Regs::regSize(daddr))
        panic("invalid size: reg=%s da=%#x pa=%#x va=%#x size=%d",
              Regs::regName(daddr), daddr, req->paddr, req->vaddr, req->size);

    uint32_t reg32 = *(uint32_t *)data;
    uint64_t reg64 = *(uint64_t *)data;

    DPRINTF(EthernetPIO, "write reg=%s val=%#x da=%#x pa=%#x va=%#x size=%d\n",
            Regs::regName(daddr), Regs::regSize(daddr) == 4 ? reg32 : reg64,
            daddr, req->paddr, req->vaddr, req->size);


    switch (daddr) {
      case Regs::Config:
        changeConfig(reg32);
        break;

      case Regs::RxThreshold:
        regs.RxThreshold = reg32;
        break;

      case Regs::TxThreshold:
        regs.TxThreshold = reg32;
        break;

      case Regs::IntrMask:
        devIntrChangeMask(reg32);
        break;

      case Regs::RxData:
        if (rxState != rxIdle)
            panic("receive machine busy with another request!");

        regs.RxDone = 0;
        regs.RxData = reg64;
        if (rxEnable) {
            rxState = rxFifoBlock;
            rxKick();
        }
        break;

      case Regs::TxData:
        if (txState != txIdle)
            panic("transmit machine busy with another request!");

        regs.TxDone = 0;
        regs.TxData = reg64;
        if (txEnable) {
            txState = txFifoBlock;
            txKick();
        }
        break;

      default:
        panic("writing read only register %s: da=%#x pa=%#x va=%#x size=%d",
              Regs::regName(daddr), daddr, req->paddr, req->vaddr, req->size);
    }

    return No_Fault;
}

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);

    if ((regs.IntrStatus & regs.IntrMask)) {
        Tick when = curTick;
        if ((regs.IntrStatus & regs.IntrMask & 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_break();
        intrTick = curTick;
    }

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

    if (intrEvent)
        intrEvent->squash();
    intrEvent = new IntrEvent(this, true);
    intrEvent->schedule(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_Reset)) {
        assert(regs.Config & Regs::Config_Reset);
        reset();
        regs.Config &= ~Regs::Config_Reset;
    }

    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::reset()
{
    using namespace Regs;
    memset(&regs, 0, sizeof(regs));
    regs.RxMaxCopy = params()->rx_max_copy;
    regs.TxMaxCopy = params()->tx_max_copy;
    regs.IntrMask = Intr_TxFifo | Intr_RxFifo | Intr_RxData;

    rxState = rxIdle;
    txState = txIdle;

    rxFifo.clear();
    txFifo.clear();
}

void
Device::rxDmaCopy()
{
    assert(rxState == rxCopy);
    rxState = rxCopyDone;
    physmem->dma_write(rxDmaAddr, (uint8_t *)rxDmaData, rxDmaLen);
    DPRINTF(EthernetDMA, "rx dma write paddr=%#x len=%d\n",
            rxDmaAddr, rxDmaLen);
    DDUMP(EthernetDMA, rxDmaData, rxDmaLen);
}

void
Device::rxDmaDone()
{
    rxDmaCopy();
    rxKick();
}

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

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

  next:
    switch (rxState) {
      case rxIdle:
        if (rxPioRequest) {
            pioInterface->respond(rxPioRequest, curTick);
            rxPioRequest = 0;
        }
        goto exit;

      case rxFifoBlock:
        if (rxPacket) {
            rxState = rxBeginCopy;
            break;
        }

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

        // Grab a new packet from the fifo.
        rxPacket = rxFifo.front();
        rxPacketBufPtr = rxPacket->data;
        rxPktBytes = rxPacket->length;
        assert(rxPktBytes);

        rxDoneData = 0;
        /* scope for variables */ {
            IpPtr ip(rxPacket);
            if (ip) {
                rxDoneData |= Regs::RxDone_IpPacket;
                rxIpChecksums++;
                if (cksum(ip) != 0) {
                    DPRINTF(EthernetCksum, "Rx IP Checksum Error\n");
                    rxDoneData |= Regs::RxDone_IpError;
                }
                TcpPtr tcp(ip);
                UdpPtr udp(ip);
                if (tcp) {
                    rxDoneData |= Regs::RxDone_TcpPacket;
                    rxTcpChecksums++;
                    if (cksum(tcp) != 0) {
                        DPRINTF(EthernetCksum, "Rx TCP Checksum Error\n");
                        rxDoneData |= Regs::RxDone_TcpError;
                    }
                } else if (udp) {
                    rxDoneData |= Regs::RxDone_UdpPacket;
                    rxUdpChecksums++;
                    if (cksum(udp) != 0) {
                        DPRINTF(EthernetCksum, "Rx UDP Checksum Error\n");
                        rxDoneData |= Regs::RxDone_UdpError;
                    }
                }
            }
        }
        rxState = rxBeginCopy;
        break;

      case rxBeginCopy:
        rxDmaAddr = plat->pciToDma(Regs::get_RxData_Addr(regs.RxData));
        rxDmaLen = min<int>(Regs::get_RxData_Len(regs.RxData), rxPktBytes);
        rxDmaData = rxPacketBufPtr;

        if (dmaInterface) {
            if (!dmaInterface->busy()) {
                dmaInterface->doDMA(WriteInvalidate, rxDmaAddr, rxDmaLen,
                                    curTick, &rxDmaEvent, true);
                rxState = rxCopy;
            }
            goto exit;
        }

        rxState = rxCopy;
        if (dmaWriteDelay != 0 || dmaWriteFactor != 0) {
            Tick factor = ((rxDmaLen + ULL(63)) >> ULL(6)) * dmaWriteFactor;
            Tick start = curTick + dmaWriteDelay + factor;
            rxDmaEvent.schedule(start);
            goto exit;
        }

        rxDmaCopy();
        break;

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

      case rxCopyDone:
        regs.RxDone = rxDoneData | rxDmaLen;

        if (rxPktBytes == rxDmaLen) {
            rxPacket = NULL;
            rxFifo.pop();
        } else {
            regs.RxDone |= Regs::RxDone_More;
            rxPktBytes -= rxDmaLen;
            rxPacketBufPtr += rxDmaLen;
        }

        regs.RxDone |= Regs::RxDone_Complete;
        devIntrPost(Regs::Intr_RxData);
        rxState = rxIdle;
        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::txDmaCopy()
{
    assert(txState == txCopy);
    txState = txCopyDone;
    physmem->dma_read((uint8_t *)txDmaData, txDmaAddr, txDmaLen);
    DPRINTF(EthernetDMA, "tx dma read paddr=%#x len=%d\n",
            txDmaAddr, txDmaLen);
    DDUMP(EthernetDMA, txDmaData, txDmaLen);
}

void
Device::txDmaDone()
{
    txDmaCopy();
    txKick();
}

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

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

    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\n",
                        tcp->sport(), tcp->dport());
            }
        }
    }
#endif

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

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

    if (txFifo.size() <= params()->tx_fifo_threshold)
        devIntrPost(Regs::Intr_TxFifo);

    devIntrPost(Regs::Intr_TxDone);

  reschedule:
   if (!txFifo.empty() && !txEvent.scheduled()) {
       DPRINTF(Ethernet, "reschedule transmit\n");
       txEvent.schedule(curTick + retryTime);
   }
}

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

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

  next:
    switch (txState) {
      case txIdle:
        if (txPioRequest) {
            pioInterface->respond(txPioRequest, curTick + pioLatency);
            txPioRequest = 0;
        }
        goto exit;

      case txFifoBlock:
        if (!txPacket) {
            // Grab a new packet from the fifo.
            txPacket = new PacketData(16384);
            txPacketBufPtr = txPacket->data;
        }

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

        txState = txBeginCopy;
        break;

      case txBeginCopy:
        txDmaAddr = plat->pciToDma(Regs::get_TxData_Addr(regs.TxData));
        txDmaLen = Regs::get_TxData_Len(regs.TxData);
        txDmaData = txPacketBufPtr;

        if (dmaInterface) {
            if (!dmaInterface->busy()) {
                dmaInterface->doDMA(Read, txDmaAddr, txDmaLen,
                                    curTick, &txDmaEvent, true);
                txState = txCopy;
            }

            goto exit;
        }

        txState = txCopy;
        if (dmaReadDelay != 0 || dmaReadFactor != 0) {
            Tick factor = ((txDmaLen + ULL(63)) >> ULL(6)) * dmaReadFactor;
            Tick start = curTick + dmaReadDelay + factor;
            txDmaEvent.schedule(start);
            goto exit;
        }

        txDmaCopy();
        break;

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

      case txCopyDone:
        txPacket->length += txDmaLen;
        if ((regs.TxData & Regs::TxData_More)) {
            txPacketBufPtr += txDmaLen;
        } else {
            assert(txPacket->length <= txFifo.avail());
            if ((regs.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);
            txPacket = 0;
            transmit();
        }

        regs.TxDone = txDmaLen | Regs::TxDone_Complete;
        devIntrPost(Regs::Intr_TxData);
        txState = txIdle;
        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");

    if (txEvent.scheduled())
        txEvent.reschedule(curTick + cycles(1));
    else
        txEvent.schedule(curTick + cycles(1));
}

bool
Device::rxFilter(const PacketPtr &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(PacketPtr 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");
        interface->recvDone();
        return true;
    }

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

    if (rxFifo.size() >= params()->rx_fifo_threshold)
        devIntrPost(Regs::Intr_RxFifo);

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

    interface->recvDone();
    devIntrPost(Regs::Intr_RxDone);
    rxKick();
    return true;
}

//=====================================================================
//
//
void
Base::serialize(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);
        intrEvent->schedule(intrEventTick);
    }
}

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

    if (rxDmaEvent.scheduled())
        rxDmaCopy();

    if (txDmaEvent.scheduled())
        txDmaCopy();

    /*
     * Serialize the device registers
     */
    SERIALIZE_SCALAR(regs.Config);
    SERIALIZE_SCALAR(regs.RxMaxCopy);
    SERIALIZE_SCALAR(regs.TxMaxCopy);
    SERIALIZE_SCALAR(regs.RxThreshold);
    SERIALIZE_SCALAR(regs.TxThreshold);
    SERIALIZE_SCALAR(regs.IntrStatus);
    SERIALIZE_SCALAR(regs.IntrMask);
    SERIALIZE_SCALAR(regs.RxData);
    SERIALIZE_SCALAR(regs.RxDone);
    SERIALIZE_SCALAR(regs.TxData);
    SERIALIZE_SCALAR(regs.TxDone);

    /*
     * Serialize rx state machine
     */
    int rxState = this->rxState;
    SERIALIZE_SCALAR(rxState);
    rxFifo.serialize("rxFifo", os);
    bool rxPacketExists = rxPacket;
    SERIALIZE_SCALAR(rxPacketExists);
    if (rxPacketExists) {
        rxPacket->serialize("rxPacket", os);
        uint32_t rxPktBufPtr = (uint32_t) (rxPacketBufPtr - rxPacket->data);
        SERIALIZE_SCALAR(rxPktBufPtr);
        SERIALIZE_SCALAR(rxPktBytes);
    }
    SERIALIZE_SCALAR(rxDoneData);

    /*
     * Serialize tx state machine
     */
    int txState = this->txState;
    SERIALIZE_SCALAR(txState);
    txFifo.serialize("txFifo", os);
    bool txPacketExists = txPacket;
    SERIALIZE_SCALAR(txPacketExists);
    if (txPacketExists) {
        txPacket->serialize("txPacket", os);
        uint32_t txPktBufPtr = (uint32_t) (txPacketBufPtr - txPacket->data);
        SERIALIZE_SCALAR(txPktBufPtr);
        SERIALIZE_SCALAR(txPktBytes);
    }

    /*
     * 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
     */
    UNSERIALIZE_SCALAR(regs.Config);
    UNSERIALIZE_SCALAR(regs.RxMaxCopy);
    UNSERIALIZE_SCALAR(regs.TxMaxCopy);
    UNSERIALIZE_SCALAR(regs.RxThreshold);
    UNSERIALIZE_SCALAR(regs.TxThreshold);
    UNSERIALIZE_SCALAR(regs.IntrStatus);
    UNSERIALIZE_SCALAR(regs.IntrMask);
    UNSERIALIZE_SCALAR(regs.RxData);
    UNSERIALIZE_SCALAR(regs.RxDone);
    UNSERIALIZE_SCALAR(regs.TxData);
    UNSERIALIZE_SCALAR(regs.TxDone);

    /*
     * Unserialize rx state machine
     */
    int rxState;
    UNSERIALIZE_SCALAR(rxState);
    this->rxState = (RxState) rxState;
    rxFifo.unserialize("rxFifo", cp, section);
    bool rxPacketExists;
    UNSERIALIZE_SCALAR(rxPacketExists);
    rxPacket = 0;
    if (rxPacketExists) {
        rxPacket = new PacketData(16384);
        rxPacket->unserialize("rxPacket", cp, section);
        uint32_t rxPktBufPtr;
        UNSERIALIZE_SCALAR(rxPktBufPtr);
        this->rxPacketBufPtr = (uint8_t *) rxPacket->data + rxPktBufPtr;
        UNSERIALIZE_SCALAR(rxPktBytes);
    }
    UNSERIALIZE_SCALAR(rxDoneData);

    /*
     * Unserialize tx state machine
     */
    int txState;
    UNSERIALIZE_SCALAR(txState);
    this->txState = (TxState) txState;
    txFifo.unserialize("txFifo", cp, section);
    bool txPacketExists;
    UNSERIALIZE_SCALAR(txPacketExists);
    txPacket = 0;
    if (txPacketExists) {
        txPacket = new PacketData(16384);
        txPacket->unserialize("txPacket", cp, section);
        uint32_t txPktBufPtr;
        UNSERIALIZE_SCALAR(txPktBufPtr);
        this->txPacketBufPtr = (uint8_t *) txPacket->data + txPktBufPtr;
        UNSERIALIZE_SCALAR(txPktBytes);
    }

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

    /*
     * re-add addrRanges to bus bridges
     */
    if (pioInterface)
        pioInterface->addAddrRange(RangeSize(BARAddrs[0], BARSize[0]));
}

Tick
Device::cacheAccess(MemReqPtr &req)
{
    //The mask is to give you only the offset into the device register file
    Addr daddr = req->paddr - addr;

    DPRINTF(EthernetPIO, "timing access to paddr=%#x (daddr=%#x)\n",
            req->paddr, daddr);

    Tick when = curTick + pioLatency;

    switch (daddr) {
      case Regs::RxDone:
        if (rxState != rxIdle) {
            rxPioRequest = req;
            when = 0;
        }
        break;

      case Regs::TxDone:
        if (txState != txIdle) {
            txPioRequest = req;
            when = 0;
        }
        break;
    }

    return when;
}

BEGIN_DECLARE_SIM_OBJECT_PARAMS(Interface)

    SimObjectParam<EtherInt *> peer;
    SimObjectParam<Device *> device;

END_DECLARE_SIM_OBJECT_PARAMS(Interface)

BEGIN_INIT_SIM_OBJECT_PARAMS(Interface)

    INIT_PARAM_DFLT(peer, "peer interface", NULL),
    INIT_PARAM(device, "Ethernet device of this interface")

END_INIT_SIM_OBJECT_PARAMS(Interface)

CREATE_SIM_OBJECT(Interface)
{
    Interface *dev_int = new Interface(getInstanceName(), device);

    EtherInt *p = (EtherInt *)peer;
    if (p) {
        dev_int->setPeer(p);
        p->setPeer(dev_int);
    }

    return dev_int;
}

REGISTER_SIM_OBJECT("SinicInt", Interface)


BEGIN_DECLARE_SIM_OBJECT_PARAMS(Device)

    Param<Addr> addr;
    Param<Tick> cycle_time;
    Param<Tick> tx_delay;
    Param<Tick> rx_delay;
    Param<Tick> intr_delay;
    SimObjectParam<MemoryController *> mmu;
    SimObjectParam<PhysicalMemory *> physmem;
    Param<bool> rx_filter;
    Param<string> hardware_address;
    SimObjectParam<Bus*> io_bus;
    SimObjectParam<Bus*> payload_bus;
    SimObjectParam<HierParams *> hier;
    Param<Tick> pio_latency;
    SimObjectParam<PciConfigAll *> configspace;
    SimObjectParam<PciConfigData *> configdata;
    SimObjectParam<Platform *> platform;
    Param<uint32_t> pci_bus;
    Param<uint32_t> pci_dev;
    Param<uint32_t> pci_func;
    Param<uint32_t> rx_max_copy;
    Param<uint32_t> tx_max_copy;
    Param<uint32_t> rx_fifo_size;
    Param<uint32_t> tx_fifo_size;
    Param<uint32_t> rx_fifo_threshold;
    Param<uint32_t> tx_fifo_threshold;
    Param<Tick> dma_read_delay;
    Param<Tick> dma_read_factor;
    Param<Tick> dma_write_delay;
    Param<Tick> dma_write_factor;
    Param<bool> dma_no_allocate;

END_DECLARE_SIM_OBJECT_PARAMS(Device)

BEGIN_INIT_SIM_OBJECT_PARAMS(Device)

    INIT_PARAM(addr, "Device Address"),
    INIT_PARAM(cycle_time, "State machine cycle time"),
    INIT_PARAM_DFLT(tx_delay, "Transmit Delay", 1000),
    INIT_PARAM_DFLT(rx_delay, "Receive Delay", 1000),
    INIT_PARAM_DFLT(intr_delay, "Interrupt Delay in microseconds", 0),
    INIT_PARAM(mmu, "Memory Controller"),
    INIT_PARAM(physmem, "Physical Memory"),
    INIT_PARAM_DFLT(rx_filter, "Enable Receive Filter", true),
    INIT_PARAM_DFLT(hardware_address, "Ethernet Hardware Address",
                    "00:99:00:00:00:01"),
    INIT_PARAM_DFLT(io_bus, "The IO Bus to attach to for headers", NULL),
    INIT_PARAM_DFLT(payload_bus, "The IO Bus to attach to for payload", NULL),
    INIT_PARAM_DFLT(hier, "Hierarchy global variables", &defaultHierParams),
    INIT_PARAM_DFLT(pio_latency, "Programmed IO latency in bus cycles", 1),
    INIT_PARAM(configspace, "PCI Configspace"),
    INIT_PARAM(configdata, "PCI Config data"),
    INIT_PARAM(platform, "Platform"),
    INIT_PARAM(pci_bus, "PCI bus"),
    INIT_PARAM(pci_dev, "PCI device number"),
    INIT_PARAM(pci_func, "PCI function code"),
    INIT_PARAM_DFLT(rx_max_copy, "rx max copy", 16*1024),
    INIT_PARAM_DFLT(tx_max_copy, "rx max copy", 16*1024),
    INIT_PARAM_DFLT(rx_fifo_size, "max size in bytes of rxFifo", 64*1024),
    INIT_PARAM_DFLT(tx_fifo_size, "max size in bytes of txFifo", 64*1024),
    INIT_PARAM_DFLT(rx_fifo_threshold, "max size in bytes of rxFifo", 48*1024),
    INIT_PARAM_DFLT(tx_fifo_threshold, "max size in bytes of txFifo", 16*1024),
    INIT_PARAM_DFLT(dma_read_delay, "fixed delay for dma reads", 0),
    INIT_PARAM_DFLT(dma_read_factor, "multiplier for dma reads", 0),
    INIT_PARAM_DFLT(dma_write_delay, "fixed delay for dma writes", 0),
    INIT_PARAM_DFLT(dma_write_factor, "multiplier for dma writes", 0),
    INIT_PARAM_DFLT(dma_no_allocate, "Should we allocat on read in cache", true)

END_INIT_SIM_OBJECT_PARAMS(Device)


CREATE_SIM_OBJECT(Device)
{
    Device::Params *params = new Device::Params;
    params->name = getInstanceName();
    params->intr_delay = intr_delay;
    params->physmem = physmem;
    params->cycle_time = cycle_time;
    params->tx_delay = tx_delay;
    params->rx_delay = rx_delay;
    params->mmu = mmu;
    params->hier = hier;
    params->io_bus = io_bus;
    params->payload_bus = payload_bus;
    params->pio_latency = pio_latency;
    params->configSpace = configspace;
    params->configData = configdata;
    params->plat = platform;
    params->busNum = pci_bus;
    params->deviceNum = pci_dev;
    params->functionNum = pci_func;
    params->rx_filter = rx_filter;
    params->eaddr = hardware_address;
    params->rx_max_copy = rx_max_copy;
    params->tx_max_copy = tx_max_copy;
    params->rx_fifo_size = rx_fifo_size;
    params->tx_fifo_size = tx_fifo_size;
    params->rx_fifo_threshold = rx_fifo_threshold;
    params->tx_fifo_threshold = tx_fifo_threshold;
    params->dma_read_delay = dma_read_delay;
    params->dma_read_factor = dma_read_factor;
    params->dma_write_delay = dma_write_delay;
    params->dma_write_factor = dma_write_factor;
    params->dma_no_allocate = dma_no_allocate;
    return new Device(params);
}

REGISTER_SIM_OBJECT("Sinic", Device)

/* namespace Sinic */ }