/*
 * Copyright (c) 2017-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.
 *
 * 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: Matteo Andreozzi
 */

#include "mem_ctrl.hh"

#include "turnaround_policy.hh"

namespace QoS {

MemCtrl::MemCtrl(const QoSMemCtrlParams * p)
  : AbstractMemory(p),
    policy(p->qos_policy),
    turnPolicy(p->qos_turnaround_policy),
    queuePolicy(QueuePolicy::create(p)),
    _numPriorities(p->qos_priorities),
    qosPriorityEscalation(p->qos_priority_escalation),
    qosSyncroScheduler(p->qos_syncro_scheduler),
    totalReadQueueSize(0), totalWriteQueueSize(0),
    busState(READ), busStateNext(READ)
{
    // Set the priority policy
    if (policy) {
        policy->setMemCtrl(this);
    }

    // Set the queue priority policy
    if (queuePolicy) {
        queuePolicy->setMemCtrl(this);
    }

    // Set the bus turnaround policy
    if (turnPolicy) {
        turnPolicy->setMemCtrl(this);
    }

    readQueueSizes.resize(_numPriorities);
    writeQueueSizes.resize(_numPriorities);
    serviceTick.resize(_numPriorities);
}

MemCtrl::~MemCtrl()
{}

void
MemCtrl::init()
{
    AbstractMemory::init();
}

void
MemCtrl::logRequest(BusState dir, MasterID m_id, uint8_t qos,
                    Addr addr, uint64_t entries)
{
    // If needed, initialize all counters and statistics
    // for this master
    addMaster(m_id);

    DPRINTF(QOS,
            "QoSMemCtrl::logRequest MASTER %s [id %d] address %d"
            " prio %d this master q packets %d"
            " - queue size %d - requested entries %d\n",
            masters[m_id], m_id, addr, qos, packetPriorities[m_id][qos],
            (dir == READ) ? readQueueSizes[qos]: writeQueueSizes[qos],
            entries);

    if (dir == READ) {
        readQueueSizes[qos] += entries;
        totalReadQueueSize += entries;
    } else if (dir == WRITE) {
        writeQueueSizes[qos] += entries;
        totalWriteQueueSize += entries;
    }

    packetPriorities[m_id][qos] += entries;
    for (auto j = 0; j < entries; ++j) {
        requestTimes[m_id][addr].push_back(curTick());
    }

    // Record statistics
    avgPriority[m_id].sample(qos);

    // Compute avg priority distance

    for (uint8_t i = 0; i < packetPriorities[m_id].size(); ++i) {
        uint8_t distance =
            (abs(int(qos) - int(i))) * packetPriorities[m_id][i];

        if (distance > 0) {
            avgPriorityDistance[m_id].sample(distance);
            DPRINTF(QOS,
                    "QoSMemCtrl::logRequest MASTER %s [id %d]"
                    " registering priority distance %d for priority %d"
                    " (packets %d)\n",
                    masters[m_id], m_id, distance, i,
                    packetPriorities[m_id][i]);
        }
    }

    DPRINTF(QOS,
            "QoSMemCtrl::logRequest MASTER %s [id %d] prio %d "
            "this master q packets %d - new queue size %d\n",
            masters[m_id], m_id, qos, packetPriorities[m_id][qos],
            (dir == READ) ? readQueueSizes[qos]: writeQueueSizes[qos]);

}

void
MemCtrl::logResponse(BusState dir, MasterID m_id, uint8_t qos,
                     Addr addr, uint64_t entries, double delay)
{
    panic_if(!hasMaster(m_id),
        "Logging response with invalid master\n");

    DPRINTF(QOS,
            "QoSMemCtrl::logResponse MASTER %s [id %d] address %d prio"
            " %d this master q packets %d"
            " - queue size %d - requested entries %d\n",
            masters[m_id], m_id, addr, qos, packetPriorities[m_id][qos],
            (dir == READ) ? readQueueSizes[qos]: writeQueueSizes[qos],
            entries);

    if (dir == READ) {
        readQueueSizes[qos] -= entries;
        totalReadQueueSize -= entries;
    } else if (dir == WRITE) {
        writeQueueSizes[qos] -= entries;
        totalWriteQueueSize -= entries;
    }

    panic_if(packetPriorities[m_id][qos] == 0,
             "QoSMemCtrl::logResponse master %s negative packets for priority"
             " %d", masters[m_id], qos);

    packetPriorities[m_id][qos] -= entries;

    for (auto j = 0; j < entries; ++j) {
        auto it = requestTimes[m_id].find(addr);
        panic_if(it == requestTimes[m_id].end(),
                 "QoSMemCtrl::logResponse master %s unmatched response for"
                 " address %d received", masters[m_id], addr);

        // Load request time
        uint64_t requestTime = it->second.front();

        // Remove request entry
        it->second.pop_front();

        // Remove whole address entry if last one
        if (it->second.empty()) {
            requestTimes[m_id].erase(it);
        }
        // Compute latency
        double latency = (double) (curTick() + delay - requestTime)
                / SimClock::Float::s;

        if (latency > 0) {
            // Record per-priority latency stats
            if (priorityMaxLatency[qos].value() < latency) {
                priorityMaxLatency[qos] = latency;
            }

            if (priorityMinLatency[qos].value() > latency
                    || priorityMinLatency[qos].value() == 0) {
                priorityMinLatency[qos] = latency;
            }
        }
    }

    DPRINTF(QOS,
            "QoSMemCtrl::logResponse MASTER %s [id %d] prio %d "
            "this master q packets %d - new queue size %d\n",
            masters[m_id], m_id, qos, packetPriorities[m_id][qos],
            (dir == READ) ? readQueueSizes[qos]: writeQueueSizes[qos]);
}

uint8_t
MemCtrl::schedule(MasterID m_id, uint64_t data)
{
    if (policy) {
        return policy->schedule(m_id, data);
    } else {
        DPRINTF(QOS,
                "QoSScheduler::schedule master ID [%d] "
                "data received [%d], but QoS scheduler not initialized\n",
                m_id,data);
        return 0;
    }
}

uint8_t
MemCtrl::schedule(const PacketPtr pkt)
{
    assert(pkt->req);

    if (policy) {
        return schedule(pkt->req->masterId(), pkt->getSize());
    } else {
        DPRINTF(QOS, "QoSScheduler::schedule Packet received [Qv %d], "
                "but QoS scheduler not initialized\n",
                pkt->qosValue());
        return pkt->qosValue();
    }
}

MemCtrl::BusState
MemCtrl::selectNextBusState()
{
    auto bus_state = getBusState();

    if (turnPolicy) {
        DPRINTF(QOS,
                "QoSMemoryTurnaround::selectBusState running policy %s\n",
                turnPolicy->name());

        bus_state = turnPolicy->selectBusState();
    } else {
        DPRINTF(QOS,
                "QoSMemoryTurnaround::selectBusState running "
                "default bus direction selection policy\n");

        if ((!getTotalReadQueueSize() && bus_state == MemCtrl::READ) ||
            (!getTotalWriteQueueSize() && bus_state == MemCtrl::WRITE)) {
            // READ/WRITE turnaround
            bus_state = (bus_state == MemCtrl::READ) ? MemCtrl::WRITE :
                                                       MemCtrl::READ;

        }
    }

    return bus_state;
}

void
MemCtrl::addMaster(MasterID m_id)
{
    if (!hasMaster(m_id)) {
        masters.emplace(m_id, _system->getMasterName(m_id));
        packetPriorities[m_id].resize(numPriorities(), 0);

        DPRINTF(QOS,
                "QoSMemCtrl::addMaster registering"
                " Master %s [id %d]\n",
                masters[m_id], m_id);
    }
}

void
MemCtrl::regStats()
{
    AbstractMemory::regStats();

    using namespace Stats;

    // Initializes per master statistics
    avgPriority.init(_system->maxMasters()).name(name() + ".avgPriority")
        .desc("Average QoS priority value for accepted requests")
        .flags(nozero | nonan).precision(2);

    avgPriorityDistance.init(_system->maxMasters())
        .name(name() + ".avgPriorityDistance")
        .desc("Average QoS priority distance between assigned and "
        "queued values").flags(nozero | nonan);

    priorityMinLatency.init(numPriorities())
        .name(name() + ".priorityMinLatency")
        .desc("per QoS priority minimum request to response latency (s)")
        .precision(12);

    priorityMaxLatency.init(numPriorities())
        .name(name() + ".priorityMaxLatency")
        .desc("per QoS priority maximum request to response latency (s)")
        .precision(12);

    numReadWriteTurnArounds.name(name() + ".numReadWriteTurnArounds")
        .desc("Number of turnarounds from READ to WRITE");

    numWriteReadTurnArounds.name(name() + ".numWriteReadTurnArounds")
        .desc("Number of turnarounds from WRITE to READ");

    numStayReadState.name(name() + ".numStayReadState")
        .desc("Number of times bus staying in READ state");

    numStayWriteState.name(name() + ".numStayWriteState")
        .desc("Number of times bus staying in WRITE state");

    for (int i = 0; i < _system->maxMasters(); i++) {
        const std::string master = _system->getMasterName(i);
        avgPriority.subname(i, master);
        avgPriorityDistance.subname(i, master);
    }

    for (int j = 0; j < numPriorities(); ++j) {
        priorityMinLatency.subname(j, std::to_string(j));
        priorityMaxLatency.subname(j, std::to_string(j));
    }
}

void
MemCtrl::recordTurnaroundStats()
{
    if (busStateNext != busState) {
        if (busState == READ) {
            numWriteReadTurnArounds++;
        } else if (busState == WRITE) {
            numReadWriteTurnArounds++;
        }
    } else {
        if (busState == READ) {
            numStayReadState++;
        } else if (busState == WRITE) {
            numStayWriteState++;
        }
    }
}

} // namespace QoS