/*
 * Copyright (c) 2010-2018 ARM Limited
 * All rights reserved.
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * Copyright (c) 2002-2005 The Regents of The University of Michigan
 * Copyright (c) 2010,2015 Advanced Micro Devices, Inc.
 * 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
 *          Dave Greene
 *          Nathan Binkert
 *          Steve Reinhardt
 *          Ron Dreslinski
 *          Andreas Sandberg
 *          Nikos Nikoleris
 */

/**
 * @file
 * Cache definitions.
 */

#include "mem/cache/noncoherent_cache.hh"

#include <cassert>

#include "base/logging.hh"
#include "base/trace.hh"
#include "base/types.hh"
#include "debug/Cache.hh"
#include "mem/cache/blk.hh"
#include "mem/cache/mshr.hh"
#include "params/NoncoherentCache.hh"

NoncoherentCache::NoncoherentCache(const NoncoherentCacheParams *p)
    : BaseCache(p, p->system->cacheLineSize())
{
}

void
NoncoherentCache::satisfyRequest(PacketPtr pkt, CacheBlk *blk, bool, bool)
{
    // As this a non-coherent cache located below the point of
    // coherency, we do not expect requests that are typically used to
    // keep caches coherent (e.g., InvalidateReq or UpdateReq).
    assert(pkt->isRead() || pkt->isWrite());
    BaseCache::satisfyRequest(pkt, blk);
}

bool
NoncoherentCache::access(PacketPtr pkt, CacheBlk *&blk, Cycles &lat,
                         PacketList &writebacks)
{
    bool success = BaseCache::access(pkt, blk, lat, writebacks);

    if (pkt->isWriteback() || pkt->cmd == MemCmd::WriteClean) {
        assert(blk && blk->isValid());
        // Writeback and WriteClean can allocate and fill even if the
        // referenced block was not present or it was invalid. If that
        // is the case, make sure that the new block is marked as
        // writable
        blk->status |= BlkWritable;
    }

    return success;
}

void
NoncoherentCache::doWritebacks(PacketList& writebacks, Tick forward_time)
{
    while (!writebacks.empty()) {
        PacketPtr wb_pkt = writebacks.front();
        allocateWriteBuffer(wb_pkt, forward_time);
        writebacks.pop_front();
    }
}

void
NoncoherentCache::doWritebacksAtomic(PacketList& writebacks)
{
    while (!writebacks.empty()) {
        PacketPtr wb_pkt = writebacks.front();
        memSidePort.sendAtomic(wb_pkt);
        writebacks.pop_front();
        delete wb_pkt;
    }
}

void
NoncoherentCache::handleTimingReqMiss(PacketPtr pkt, CacheBlk *blk,
                                      Tick forward_time, Tick request_time)
{
    // miss
    Addr blk_addr = pkt->getBlockAddr(blkSize);
    MSHR *mshr = mshrQueue.findMatch(blk_addr, pkt->isSecure(), false);

    // We can always write to a non coherent cache if the block is
    // present and therefore if we have reached this point then the
    // block should not be in the cache.
    assert(mshr || !blk || !blk->isValid());

    BaseCache::handleTimingReqMiss(pkt, mshr, blk, forward_time, request_time);
}

void
NoncoherentCache::recvTimingReq(PacketPtr pkt)
{
    panic_if(pkt->cacheResponding(), "Should not see packets where cache "
             "is responding");

    panic_if(!(pkt->isRead() || pkt->isWrite()),
             "Should only see read and writes at non-coherent cache\n");

    BaseCache::recvTimingReq(pkt);
}

PacketPtr
NoncoherentCache::createMissPacket(PacketPtr cpu_pkt, CacheBlk *blk,
                                   bool needs_writable) const
{
    // We also fill for writebacks from the coherent caches above us,
    // and they do not need responses
    assert(cpu_pkt->needsResponse());

    // A miss can happen only due to missing block
    assert(!blk || !blk->isValid());

    PacketPtr pkt = new Packet(cpu_pkt->req, MemCmd::ReadReq, blkSize);

    // the packet should be block aligned
    assert(pkt->getAddr() == pkt->getBlockAddr(blkSize));

    pkt->allocate();
    DPRINTF(Cache, "%s created %s from %s\n", __func__, pkt->print(),
            cpu_pkt->print());
    return pkt;
}


Cycles
NoncoherentCache::handleAtomicReqMiss(PacketPtr pkt, CacheBlk *blk,
                                      PacketList &writebacks)
{
    PacketPtr bus_pkt = createMissPacket(pkt, blk, true);
    DPRINTF(Cache, "Sending an atomic %s\n", bus_pkt->print());

    Cycles latency = ticksToCycles(memSidePort.sendAtomic(bus_pkt));

    assert(bus_pkt->isResponse());
    // At the moment the only supported downstream requests we issue
    // are ReadReq and therefore here we should only see the
    // corresponding responses
    assert(bus_pkt->isRead());
    assert(pkt->cmd != MemCmd::UpgradeResp);
    assert(!bus_pkt->isInvalidate());
    assert(!bus_pkt->hasSharers());

    // We are now dealing with the response handling
    DPRINTF(Cache, "Receive response: %s\n", bus_pkt->print());

    if (!bus_pkt->isError()) {
        // Any reponse that does not have an error should be filling,
        // afterall it is a read response
        DPRINTF(Cache, "Block for addr %#llx being updated in Cache\n",
                bus_pkt->getAddr());
        blk = handleFill(bus_pkt, blk, writebacks, allocOnFill(bus_pkt->cmd));
        assert(blk);
    }
    satisfyRequest(pkt, blk);

    maintainClusivity(true, blk);

    // Use the separate bus_pkt to generate response to pkt and
    // then delete it.
    if (!pkt->isWriteback() && pkt->cmd != MemCmd::WriteClean) {
        assert(pkt->needsResponse());
        pkt->makeAtomicResponse();
        if (bus_pkt->isError()) {
            pkt->copyError(bus_pkt);
        }
    }

    delete bus_pkt;

    return latency;
}

Tick
NoncoherentCache::recvAtomic(PacketPtr pkt)
{
    panic_if(pkt->cacheResponding(), "Should not see packets where cache "
             "is responding");

    panic_if(!(pkt->isRead() || pkt->isWrite()),
             "Should only see read and writes at non-coherent cache\n");

    return BaseCache::recvAtomic(pkt);
}


void
NoncoherentCache::functionalAccess(PacketPtr pkt, bool from_cpu_side)
{
    panic_if(!from_cpu_side, "Non-coherent cache received functional snoop"
             " request\n");

    BaseCache::functionalAccess(pkt, from_cpu_side);
}

void
NoncoherentCache::serviceMSHRTargets(MSHR *mshr, const PacketPtr pkt,
                                     CacheBlk *blk, PacketList &writebacks)
{
    MSHR::Target *initial_tgt = mshr->getTarget();
    // First offset for critical word first calculations
    const int initial_offset = initial_tgt->pkt->getOffset(blkSize);

    MSHR::TargetList targets = mshr->extractServiceableTargets(pkt);
    for (auto &target: targets) {
        Packet *tgt_pkt = target.pkt;

        switch (target.source) {
          case MSHR::Target::FromCPU:
            // handle deferred requests comming from a cache or core
            // above

            Tick completion_time;
            // Here we charge on completion_time the delay of the xbar if the
            // packet comes from it, charged on headerDelay.
            completion_time = pkt->headerDelay;

            satisfyRequest(tgt_pkt, blk);

            // How many bytes past the first request is this one
            int transfer_offset;
            transfer_offset = tgt_pkt->getOffset(blkSize) - initial_offset;
            if (transfer_offset < 0) {
                transfer_offset += blkSize;
            }
            // If not critical word (offset) return payloadDelay.
            // responseLatency is the latency of the return path
            // from lower level caches/memory to an upper level cache or
            // the core.
            completion_time += clockEdge(responseLatency) +
                (transfer_offset ? pkt->payloadDelay : 0);

            assert(tgt_pkt->req->masterId() < system->maxMasters());
            missLatency[tgt_pkt->cmdToIndex()][tgt_pkt->req->masterId()] +=
                completion_time - target.recvTime;

            tgt_pkt->makeTimingResponse();
            if (pkt->isError())
                tgt_pkt->copyError(pkt);

            // Reset the bus additional time as it is now accounted for
            tgt_pkt->headerDelay = tgt_pkt->payloadDelay = 0;
            cpuSidePort.schedTimingResp(tgt_pkt, completion_time, true);
            break;

          case MSHR::Target::FromPrefetcher:
            // handle deferred requests comming from a prefetcher
            // attached to this cache
            assert(tgt_pkt->cmd == MemCmd::HardPFReq);

            if (blk)
                blk->status |= BlkHWPrefetched;

            // We have filled the block and the prefetcher does not
            // require responses.
            delete tgt_pkt;
            break;

          default:
            // we should never see FromSnoop Targets as this is a
            // non-coherent cache
            panic("Illegal target->source enum %d\n", target.source);
        }
    }

    // Reponses are filling and bring in writable blocks, therefore
    // there should be no deferred targets and all the non-deferred
    // targets are now serviced.
    assert(mshr->getNumTargets() == 0);
}

void
NoncoherentCache::recvTimingResp(PacketPtr pkt)
{
    assert(pkt->isResponse());
    // At the moment the only supported downstream requests we issue
    // are ReadReq and therefore here we should only see the
    // corresponding responses
    assert(pkt->isRead());
    assert(pkt->cmd != MemCmd::UpgradeResp);
    assert(!pkt->isInvalidate());
    // This cache is non-coherent and any memories below are
    // non-coherent too (non-coherent caches or the main memory),
    // therefore the fetched block can be marked as writable.
    assert(!pkt->hasSharers());

    BaseCache::recvTimingResp(pkt);
}

PacketPtr
NoncoherentCache::evictBlock(CacheBlk *blk)
{
    // A dirty block is always written back.

    // A clean block can we written back, if we turned on writebacks
    // for clean blocks. This could be useful if there is a cache
    // below and we want to make sure the block is cached but if the
    // memory below is the main memory WritebackCleans are
    // unnecessary.

    // If we clean writebacks are not enabled, we do not take any
    // further action for evictions of clean blocks (i.e., CleanEvicts
    // are unnecessary).
    PacketPtr pkt = (blk->isDirty() || writebackClean) ?
        writebackBlk(blk) : nullptr;

    invalidateBlock(blk);

    return pkt;
}

void
NoncoherentCache::evictBlock(CacheBlk *blk, PacketList &writebacks)
{
    PacketPtr pkt = evictBlock(blk);
    if (pkt) {
        writebacks.push_back(pkt);
    }
}

NoncoherentCache*
NoncoherentCacheParams::create()
{
    assert(tags);
    assert(replacement_policy);

    return new NoncoherentCache(this);
}