Files
gem5/src/arch/arm/tlb.cc
Giacomo Travaglini 458c98082c arch-arm: Replace EL based translation with regimes
This is the final step in the transformation process.
We limit the use of the "managing Exception Level" for
a translation in favour of the more standard "Translation
Regime"
This greatly simplifies our code, especially with VHE
where the managing el (EL2) could handle to different
translation regimes (EL and EL2&0).

We can therefore remove the isHost flag wherever it got
used. That case is automatically handled by the proper
regime value (EL2&0)

Change-Id: Iafd1d2ce4757cfa6598656759694e5e7b05267ad
Signed-off-by: Giacomo Travaglini <giacomo.travaglini@arm.com>
2024-02-01 13:43:47 +00:00

411 lines
13 KiB
C++

/*
* Copyright (c) 2010-2013, 2016-2023 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) 2001-2005 The Regents of The University of Michigan
* 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.
*/
#include "arch/arm/tlb.hh"
#include <memory>
#include <string>
#include <vector>
#include "arch/arm/table_walker.hh"
#include "arch/arm/tlbi_op.hh"
#include "arch/arm/utility.hh"
#include "base/trace.hh"
#include "cpu/thread_context.hh"
#include "debug/TLB.hh"
#include "debug/TLBVerbose.hh"
#include "params/ArmTLB.hh"
namespace gem5
{
using namespace ArmISA;
TLB::TLB(const ArmTLBParams &p)
: BaseTLB(p), table(new TlbEntry[p.size]), size(p.size),
isStage2(p.is_stage2),
_walkCache(false),
tableWalker(nullptr),
stats(*this), rangeMRU(1), vmid(0)
{
for (int lvl = LookupLevel::L0;
lvl < LookupLevel::Num_ArmLookupLevel; lvl++) {
auto it = std::find(
p.partial_levels.begin(),
p.partial_levels.end(),
lvl);
auto lookup_lvl = static_cast<LookupLevel>(lvl);
if (it != p.partial_levels.end()) {
// A partial entry from of the current LookupLevel can be
// cached within the TLB
partialLevels[lookup_lvl] = true;
// Make sure this is not the last level (complete translation)
if (lvl != LookupLevel::Num_ArmLookupLevel - 1) {
_walkCache = true;
}
} else {
partialLevels[lookup_lvl] = false;
}
}
}
TLB::~TLB()
{
delete[] table;
}
void
TLB::setTableWalker(TableWalker *table_walker)
{
tableWalker = table_walker;
tableWalker->setTlb(this);
}
TlbEntry*
TLB::match(const Lookup &lookup_data)
{
// Vector of TLB entry candidates.
// Only one of them will be assigned to retval and will
// be returned to the MMU (in case of a hit)
// The vector has one entry per lookup level as it stores
// both complete and partial matches
std::vector<std::pair<int, const TlbEntry*>> hits{
LookupLevel::Num_ArmLookupLevel, {0, nullptr}};
int x = 0;
while (x < size) {
if (table[x].match(lookup_data)) {
const TlbEntry &entry = table[x];
hits[entry.lookupLevel] = std::make_pair(x, &entry);
// This is a complete translation, no need to loop further
if (!entry.partial)
break;
}
++x;
}
// Loop over the list of TLB entries matching our translation
// request, starting from the highest lookup level (complete
// translation) and iterating backwards (using reverse iterators)
for (auto it = hits.rbegin(); it != hits.rend(); it++) {
const auto& [idx, entry] = *it;
if (!entry) {
// No match for the current LookupLevel
continue;
}
// Maintaining LRU array
// We only move the hit entry ahead when the position is higher
// than rangeMRU
if (idx > rangeMRU && !lookup_data.functional) {
TlbEntry tmp_entry = *entry;
for (int i = idx; i > 0; i--)
table[i] = table[i - 1];
table[0] = tmp_entry;
return &table[0];
} else {
return &table[idx];
}
}
return nullptr;
}
TlbEntry*
TLB::lookup(const Lookup &lookup_data)
{
const auto mode = lookup_data.mode;
TlbEntry *retval = match(lookup_data);
DPRINTF(TLBVerbose, "Lookup %#x, asn %#x -> %s vmn 0x%x secure %d "
"ppn %#x size: %#x pa: %#x ap:%d ns:%d nstid:%d g:%d asid: %d "
"regime: %s\n",
lookup_data.va, lookup_data.asn, retval ? "hit" : "miss",
lookup_data.vmid, lookup_data.secure,
retval ? retval->pfn : 0, retval ? retval->size : 0,
retval ? retval->pAddr(lookup_data.va) : 0,
retval ? retval->ap : 0,
retval ? retval->ns : 0, retval ? retval->nstid : 0,
retval ? retval->global : 0, retval ? retval->asid : 0,
retval ? regimeToStr(retval->regime) : 0);
// Updating stats if this was not a functional lookup
if (!lookup_data.functional) {
if (!retval) {
if (mode == BaseMMU::Execute) {
stats.instMisses++;
} else if (mode == BaseMMU::Write) {
stats.writeMisses++;
} else {
stats.readMisses++;
}
} else {
if (retval->partial) {
stats.partialHits++;
}
if (mode == BaseMMU::Execute) {
stats.instHits++;
} else if (mode == BaseMMU::Write) {
stats.writeHits++;
} else {
stats.readHits++;
}
}
}
return retval;
}
TlbEntry*
TLB::multiLookup(const Lookup &lookup_data)
{
TlbEntry* te = lookup(lookup_data);
if (te) {
checkPromotion(te, lookup_data.mode);
} else {
if (auto tlb = static_cast<TLB*>(nextLevel())) {
te = tlb->multiLookup(lookup_data);
if (te && !lookup_data.functional &&
(!te->partial || partialLevels[te->lookupLevel])) {
// Insert entry only if this is not a functional
// lookup and if the translation is complete (unless this
// TLB caches partial translations)
insert(*te);
}
}
}
return te;
}
void
TLB::checkPromotion(TlbEntry *entry, BaseMMU::Mode mode)
{
TypeTLB acc_type = (mode == BaseMMU::Execute) ?
TypeTLB::instruction : TypeTLB::data;
// Hitting an instruction TLB entry on a data access or
// a data TLB entry on an instruction access:
// promoting the entry to unified
if (!(entry->type & acc_type))
entry->type = TypeTLB::unified;
}
// insert a new TLB entry
void
TLB::insert(TlbEntry &entry)
{
DPRINTF(TLB, "Inserting entry into TLB with pfn:%#x size:%#x vpn: %#x"
" asid:%d vmid:%d N:%d global:%d valid:%d nc:%d xn:%d"
" ap:%#x domain:%#x ns:%d nstid:%d, regime: %s\n", entry.pfn,
entry.size, entry.vpn, entry.asid, entry.vmid, entry.N,
entry.global, entry.valid, entry.nonCacheable, entry.xn,
entry.ap, static_cast<uint8_t>(entry.domain), entry.ns,
entry.nstid, regimeToStr(entry.regime));
if (table[size - 1].valid)
DPRINTF(TLB, " - Replacing Valid entry %#x, asn %d vmn %d ppn %#x "
"size: %#x ap:%d ns:%d nstid:%d g:%d regime: %s\n",
table[size-1].vpn << table[size-1].N, table[size-1].asid,
table[size-1].vmid, table[size-1].pfn << table[size-1].N,
table[size-1].size, table[size-1].ap, table[size-1].ns,
table[size-1].nstid, table[size-1].global,
regimeToStr(table[size-1].regime));
// inserting to MRU position and evicting the LRU one
for (int i = size - 1; i > 0; --i)
table[i] = table[i-1];
table[0] = entry;
stats.inserts++;
ppRefills->notify(1);
}
void
TLB::multiInsert(TlbEntry &entry)
{
// Insert a partial translation only if the TLB is configured
// as a walk cache
if (!entry.partial || partialLevels[entry.lookupLevel]) {
insert(entry);
}
if (auto next_level = static_cast<TLB*>(nextLevel())) {
next_level->multiInsert(entry);
}
}
void
TLB::printTlb() const
{
int x = 0;
TlbEntry *te;
DPRINTF(TLB, "Current TLB contents:\n");
while (x < size) {
te = &table[x];
if (te->valid)
DPRINTF(TLB, " * %s\n", te->print());
++x;
}
}
void
TLB::flushAll()
{
DPRINTF(TLB, "Flushing all TLB entries\n");
int x = 0;
TlbEntry *te;
while (x < size) {
te = &table[x];
if (te->valid) {
DPRINTF(TLB, " - %s\n", te->print());
te->valid = false;
stats.flushedEntries++;
}
++x;
}
stats.flushTlb++;
}
void
TLB::flush(const TLBIOp& tlbi_op)
{
int x = 0;
TlbEntry *te;
while (x < size) {
te = &table[x];
if (tlbi_op.match(te, vmid)) {
DPRINTF(TLB, " - %s\n", te->print());
te->valid = false;
stats.flushedEntries++;
}
++x;
}
stats.flushTlb++;
}
void
TLB::takeOverFrom(BaseTLB *_otlb)
{
}
TLB::TlbStats::TlbStats(TLB &parent)
: statistics::Group(&parent), tlb(parent),
ADD_STAT(partialHits, statistics::units::Count::get(),
"partial translation hits"),
ADD_STAT(instHits, statistics::units::Count::get(), "Inst hits"),
ADD_STAT(instMisses, statistics::units::Count::get(), "Inst misses"),
ADD_STAT(readHits, statistics::units::Count::get(), "Read hits"),
ADD_STAT(readMisses, statistics::units::Count::get(), "Read misses"),
ADD_STAT(writeHits, statistics::units::Count::get(), "Write hits"),
ADD_STAT(writeMisses, statistics::units::Count::get(), "Write misses"),
ADD_STAT(inserts, statistics::units::Count::get(),
"Number of times an entry is inserted into the TLB"),
ADD_STAT(flushTlb, statistics::units::Count::get(),
"Number of times a TLB invalidation was requested"),
ADD_STAT(flushedEntries, statistics::units::Count::get(),
"Number of entries that have been flushed from TLB"),
ADD_STAT(readAccesses, statistics::units::Count::get(), "Read accesses",
readHits + readMisses),
ADD_STAT(writeAccesses, statistics::units::Count::get(), "Write accesses",
writeHits + writeMisses),
ADD_STAT(instAccesses, statistics::units::Count::get(), "Inst accesses",
instHits + instMisses),
ADD_STAT(hits, statistics::units::Count::get(),
"Total TLB (inst and data) hits",
readHits + writeHits + instHits),
ADD_STAT(misses, statistics::units::Count::get(),
"Total TLB (inst and data) misses",
readMisses + writeMisses + instMisses),
ADD_STAT(accesses, statistics::units::Count::get(),
"Total TLB (inst and data) accesses",
readAccesses + writeAccesses + instAccesses)
{
// If this is a pure Data TLB, mark the instruction
// stats as nozero, so that they won't make it in
// into the final stats file
if (tlb.type() == TypeTLB::data) {
instHits.flags(statistics::nozero);
instMisses.flags(statistics::nozero);
instAccesses.flags(statistics::nozero);
}
// If this is a pure Instruction TLB, mark the data
// stats as nozero, so that they won't make it in
// into the final stats file
if (tlb.type() & TypeTLB::instruction) {
readHits.flags(statistics::nozero);
readMisses.flags(statistics::nozero);
writeHits.flags(statistics::nozero);
writeMisses.flags(statistics::nozero);
readAccesses.flags(statistics::nozero);
writeAccesses.flags(statistics::nozero);
}
partialHits.flags(statistics::nozero);
}
void
TLB::regProbePoints()
{
ppRefills.reset(new probing::PMU(getProbeManager(), "Refills"));
}
Port *
TLB::getTableWalkerPort()
{
return &tableWalker->getTableWalkerPort();
}
} // namespace gem5