/*
 * Copyright (c) 2003-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: Erik Hallnor
 */

/**
 * @file
 * Definitions of a simple buffer for a blocking cache.
 */

#include "cpu/smt.hh" //for maxThreadsPerCPU
#include "mem/cache/base_cache.hh"
#include "mem/cache/miss/blocking_buffer.hh"
#include "mem/cache/prefetch/base_prefetcher.hh"
#include "sim/eventq.hh" // for Event declaration.
#include "mem/request.hh"

using namespace TheISA;

/**
 * @todo Move writebacks into shared BaseBuffer class.
 */
void
BlockingBuffer::regStats(const std::string &name)
{
    using namespace Stats;
    writebacks
        .init(maxThreadsPerCPU)
        .name(name + ".writebacks")
        .desc("number of writebacks")
        .flags(total)
        ;
}

void
BlockingBuffer::setCache(BaseCache *_cache)
{
    cache = _cache;
    blkSize = cache->getBlockSize();
}

void
BlockingBuffer::setPrefetcher(BasePrefetcher *_prefetcher)
{
    prefetcher = _prefetcher;
}
void
BlockingBuffer::handleMiss(Packet * &pkt, int blk_size, Tick time)
{
    Addr blk_addr = pkt->getAddr() & ~(Addr)(blk_size - 1);
    if (pkt->isWrite() && (pkt->req->isUncacheable() || !writeAllocate ||
                               !pkt->needsResponse())) {
        if (!pkt->needsResponse()) {
            wb.allocateAsBuffer(pkt);
        } else {
            wb.allocate(pkt->cmd, blk_addr, pkt->req->getAsid(), blk_size, pkt);
        }

        memcpy(wb.pkt->getPtr<uint8_t>(), pkt->getPtr<uint8_t>(), blk_size);

        cache->setBlocked(Blocked_NoWBBuffers);
        cache->setMasterRequest(Request_WB, time);
        return;
    }

    if (!pkt->needsResponse()) {
        miss.allocateAsBuffer(pkt);
    } else {
        miss.allocate(pkt->cmd, blk_addr, pkt->req->getAsid(), blk_size, pkt);
    }
    if (!pkt->req->isUncacheable()) {
        miss.pkt->flags |= CACHE_LINE_FILL;
    }
    cache->setBlocked(Blocked_NoMSHRs);
    cache->setMasterRequest(Request_MSHR, time);
}

Packet *
BlockingBuffer::getPacket()
{
    if (miss.pkt && !miss.inService) {
        return miss.pkt;
    }
    return wb.pkt;
}

void
BlockingBuffer::setBusCmd(Packet * &pkt, Packet::Command cmd)
{
    MSHR *mshr = (MSHR*) pkt->senderState;
    mshr->originalCmd = pkt->cmd;
    if (pkt->isCacheFill())
        pkt->cmdOverride(cmd);
}

void
BlockingBuffer::restoreOrigCmd(Packet * &pkt)
{
    pkt->cmdOverride(((MSHR*)(pkt->senderState))->originalCmd);
}

void
BlockingBuffer::markInService(Packet * &pkt)
{
    if (!pkt->isCacheFill() && pkt->isWrite()) {
        // Forwarding a write/ writeback, don't need to change
        // the command
        assert((MSHR*)pkt->senderState == &wb);
        cache->clearMasterRequest(Request_WB);
        if (!pkt->needsResponse()) {
            assert(wb.getNumTargets() == 0);
            wb.deallocate();
            cache->clearBlocked(Blocked_NoWBBuffers);
        } else {
            wb.inService = true;
        }
    } else {
        assert((MSHR*)pkt->senderState == &miss);
        cache->clearMasterRequest(Request_MSHR);
        if (!pkt->needsResponse()) {
            assert(miss.getNumTargets() == 0);
            miss.deallocate();
            cache->clearBlocked(Blocked_NoMSHRs);
        } else {
            //mark in service
            miss.inService = true;
        }
    }
}

void
BlockingBuffer::handleResponse(Packet * &pkt, Tick time)
{
    if (pkt->isCacheFill()) {
        // targets were handled in the cache tags
        assert((MSHR*)pkt->senderState == &miss);
        miss.deallocate();
        cache->clearBlocked(Blocked_NoMSHRs);
    } else {
        if (((MSHR*)(pkt->senderState))->hasTargets()) {
            // Should only have 1 target if we had any
            assert(((MSHR*)(pkt->senderState))->getNumTargets() == 1);
            Packet * target = ((MSHR*)(pkt->senderState))->getTarget();
            ((MSHR*)(pkt->senderState))->popTarget();
            if (pkt->isRead()) {
                memcpy(target->getPtr<uint8_t>(), pkt->getPtr<uint8_t>(), target->getSize());
            }
            cache->respond(target, time);
            assert(!((MSHR*)(pkt->senderState))->hasTargets());
        }

        if (pkt->isWrite()) {
            assert(((MSHR*)(pkt->senderState)) == &wb);
            wb.deallocate();
            cache->clearBlocked(Blocked_NoWBBuffers);
        } else {
            miss.deallocate();
            cache->clearBlocked(Blocked_NoMSHRs);
        }
    }
}

void
BlockingBuffer::squash(int threadNum)
{
    if (miss.threadNum == threadNum) {
        Packet * target = miss.getTarget();
        miss.popTarget();
        assert(target->req->getThreadNum() == threadNum);
        target = NULL;
        assert(!miss.hasTargets());
        miss.ntargets=0;
        if (!miss.inService) {
            miss.deallocate();
            cache->clearBlocked(Blocked_NoMSHRs);
            cache->clearMasterRequest(Request_MSHR);
        }
    }
}

void
BlockingBuffer::doWriteback(Addr addr, int asid,
                            int size, uint8_t *data, bool compressed)
{
    // Generate request
    Request * req = new Request(addr, size, 0);
    Packet * pkt = new Packet(req, Packet::Writeback, -1);
    pkt->allocate();
    if (data) {
        memcpy(pkt->getPtr<uint8_t>(), data, size);
    }

    if (compressed) {
        pkt->flags |= COMPRESSED;
    }

    ///All writebacks charged to same thread @todo figure this out
    writebacks[pkt->req->getThreadNum()]++;

    wb.allocateAsBuffer(pkt);
    cache->setMasterRequest(Request_WB, curTick);
    cache->setBlocked(Blocked_NoWBBuffers);
}



void
BlockingBuffer::doWriteback(Packet * &pkt)
{
    writebacks[pkt->req->getThreadNum()]++;

    wb.allocateAsBuffer(pkt);

    // Since allocate as buffer copies the request,
    // need to copy data here.
        memcpy(wb.pkt->getPtr<uint8_t>(), pkt->getPtr<uint8_t>(), pkt->getSize());

    cache->setBlocked(Blocked_NoWBBuffers);
    cache->setMasterRequest(Request_WB, curTick);
}
