/*
 * Copyright (c) 2014 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: Rune Holm
 *          Marco Elver
 */

#include <cassert>

#include "mem/mem_checker.hh"

void
MemChecker::WriteCluster::startWrite(MemChecker::Serial serial, Tick _start,
                                     uint8_t data)
{
    assert(!isComplete());

    if (start == TICK_FUTURE) {
        // Initialize a fresh write cluster
        start = _start;
    }
    chatty_assert(start <= _start, "WriteClusters must filled in order!");

    ++numIncomplete;

    if (complete != TICK_FUTURE) {
       // Reopen a closed write cluster
        assert(_start < complete);  // should open a new write cluster, instead;
        // also somewhat fishy wrt causality / ordering of calls vs time
        // progression TODO: Check me!
        complete = TICK_FUTURE;
    }

    // Create new transaction, and denote completion time to be in the future.
    writes.insert(std::make_pair(serial,
                  MemChecker::Transaction(serial, _start, TICK_FUTURE, data)));
}

void
MemChecker::WriteCluster::completeWrite(MemChecker::Serial serial, Tick _complete)
{
    auto it = writes.find(serial);

    if (it == writes.end()) {
        warn("Could not locate write transaction: serial = %d, complete = %d\n",
             serial, _complete);
        return;
    }

    // Record completion time of the write
    assert(it->second.complete == TICK_FUTURE);
    it->second.complete = _complete;

    // Update max completion time for the cluster
    if (completeMax < _complete) {
        completeMax = _complete;
    }

    if (--numIncomplete == 0) {
        // All writes have completed, this cluster is now complete and will be
        // assigned the max of completion tick values among all writes.
        //
        // Note that we cannot simply keep updating complete, because that would
        // count the cluster as closed already.  Instead, we keep TICK_FUTURE
        // until all writes have completed.
        complete = completeMax;
    }
}

void
MemChecker::WriteCluster::abortWrite(MemChecker::Serial serial)
{
    if (!writes.erase(serial)) {
        warn("Could not locate write transaction: serial = %d\n", serial);
        return;
    }

    if (--numIncomplete == 0 && !writes.empty()) {
        // This write cluster is now complete, and we can assign the current
        // completeMax value.
        complete = completeMax;
    }

    // Note: this WriteCluster is in pristine state if this was the only
    // write present; the cluster will get reused through
    // getIncompleteWriteCluster().
}

void
MemChecker::ByteTracker::startRead(MemChecker::Serial serial, Tick start)
{
    outstandingReads.insert(std::make_pair(serial,
            MemChecker::Transaction(serial, start, TICK_FUTURE)));
}

bool
MemChecker::ByteTracker::inExpectedData(Tick start, Tick complete, uint8_t data)
{
    _lastExpectedData.clear();

    bool wc_overlap = true;

    // Find the last value read from the location
    const Transaction& last_obs =
        *lastCompletedTransaction(&readObservations, start);
    bool last_obs_valid = (last_obs.complete != TICK_INITIAL);

    // Scan backwards through the write clusters to find the closest younger
    // preceding & overlapping writes.
    for (auto cluster = writeClusters.rbegin();
         cluster != writeClusters.rend() && wc_overlap; ++cluster) {
        for (const auto& addr_write : cluster->writes) {
            const Transaction& write = addr_write.second;

            if (write.complete < last_obs.start) {
                // If this write transaction completed before the last
                // observation, we ignore it as the last_observation has the
                // correct value
                continue;
            }

            if (write.data == data) {
                // Found a match, end search.
                return true;
            }

            // Record possible, but non-matching data for debugging
            _lastExpectedData.push_back(write.data);

            if (write.complete > start) {
                // This write overlapped with the transaction we want to check
                // -> continue checking the overlapping write cluster
                continue;
            }

            // This write cluster has writes that have completed before the
            // checked transaction. There is no need to check an earlier
            // write-cluster -> set the exit condition for the outer loop
            wc_overlap = false;

            if (last_obs.complete < write.start) {
                // We found a write which started after the last observed read,
                // therefore we can not longer consider the value seen by the
                // last observation as a valid expected value.
                //
                // Once all writes have been iterated through, we can check if
                // the last observation is still valid to compare against.
                last_obs_valid = false;
            }
        }
    }

    // We have not found any matching write, so far; check other sources of
    // confirmation
    if (last_obs_valid) {
        // The last observation is not outdated according to the writes we have
        // seen so far.
        assert(last_obs.complete <= start);
        if (last_obs.data == data) {
            // Matched data from last observation -> all good
            return true;
        }
        // Record non-matching, but possible value
        _lastExpectedData.push_back(last_obs.data);
    } else {
        // We have not seen any valid observation, and the only writes
        // observed are overlapping, so anything (in particular the
        // initialisation value) goes
        // NOTE: We can overlap with multiple write clusters, here
        if (!writeClusters.empty() && wc_overlap) {
            // ensure that all write clusters really overlap this read
            assert(writeClusters.begin()->start < complete &&
                   writeClusters.rbegin()->complete > start);
            return true;
        }
    }

    if (_lastExpectedData.empty()) {
        assert(last_obs.complete == TICK_INITIAL);
        // We have not found any possible (non-matching data). Can happen in
        // initial system state
        DPRINTF(MemChecker, "no last observation nor write! start = %d, "\
                "complete = %d, data = %#x\n", start, complete, data);
        return true;
    }
    return false;
}

bool
MemChecker::ByteTracker::completeRead(MemChecker::Serial serial,
                                      Tick complete, uint8_t data)
{
    auto it = outstandingReads.find(serial);

    if (it == outstandingReads.end()) {
        // Can happen if concurrent with reset_address_range
        warn("Could not locate read transaction: serial = %d, complete = %d\n",
             serial, complete);
        return true;
    }

    Tick start = it->second.start;
    outstandingReads.erase(it);

    // Verify data
    const bool result = inExpectedData(start, complete, data);

    readObservations.emplace_back(serial, start, complete, data);
    pruneTransactions();

    return result;
}

MemChecker::WriteCluster*
MemChecker::ByteTracker::getIncompleteWriteCluster()
{
    if (writeClusters.empty() || writeClusters.back().isComplete()) {
        writeClusters.emplace_back();
    }

    return &writeClusters.back();
}

void
MemChecker::ByteTracker::startWrite(MemChecker::Serial serial, Tick start,
                                    uint8_t data)
{
    getIncompleteWriteCluster()->startWrite(serial, start, data);
}

void
MemChecker::ByteTracker::completeWrite(MemChecker::Serial serial, Tick complete)
{
    getIncompleteWriteCluster()->completeWrite(serial, complete);
    pruneTransactions();
}

void
MemChecker::ByteTracker::abortWrite(MemChecker::Serial serial)
{
    getIncompleteWriteCluster()->abortWrite(serial);
}

void
MemChecker::ByteTracker::pruneTransactions()
{
    // Obtain tick of first outstanding read. If there are no outstanding
    // reads, we use curTick(), i.e. we will remove all readObservation except
    // the most recent one.
    const Tick before = outstandingReads.empty() ? curTick() :
                        outstandingReads.begin()->second.start;

    // Pruning of readObservations
    readObservations.erase(readObservations.begin(),
                           lastCompletedTransaction(&readObservations, before));

    // Pruning of writeClusters
    if (!writeClusters.empty()) {
        writeClusters.erase(writeClusters.begin(),
                            lastCompletedTransaction(&writeClusters, before));
    }
}

bool
MemChecker::completeRead(MemChecker::Serial serial, Tick complete,
                         Addr addr, size_t size, uint8_t *data)
{
    bool result = true;

    DPRINTF(MemChecker,
            "completing read: serial = %d, complete = %d, "
            "addr = %#llx, size = %d\n", serial, complete, addr, size);

    for (size_t i = 0; i < size; ++i) {
        ByteTracker *tracker = getByteTracker(addr + i);

        if (!tracker->completeRead(serial, complete, data[i])) {
            // Generate error message, and aggregate all failures for the bytes
            // considered in this transaction in one message.
            if (result) {
                result = false;
                errorMessage = "";
            } else {
                errorMessage += "\n";
            }

            errorMessage += csprintf("  Read transaction for address %#llx "
                                     "failed: received %#x, expected ",
                                     (unsigned long long)(addr + i), data[i]);

            for (size_t j = 0; j < tracker->lastExpectedData().size(); ++j) {
                errorMessage +=
                    csprintf("%#x%s",
                             tracker->lastExpectedData()[j],
                             (j == tracker->lastExpectedData().size() - 1)
                             ? "" : "|");
            }
        }
    }

    if (!result) {
        DPRINTF(MemChecker, "read of %#llx @ cycle %d failed:\n%s\n", addr,
                complete, errorMessage);
    }

    return result;
}

void
MemChecker::reset(Addr addr, size_t size)
{
    for (size_t i = 0; i < size; ++i) {
        byte_trackers.erase(addr + i);
    }
}

MemChecker*
MemCheckerParams::create()
{
    return new MemChecker(this);
}
