344 lines
12 KiB
C++
344 lines
12 KiB
C++
|
/*
|
||
|
* 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);
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
}
|