/* * 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 "mem/mem_checker.hh" #include 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); }