| /* |
| * Copyright (c) 2010, 2012-2019 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. |
| */ |
| #include "arch/arm/table_walker.hh" |
| |
| #include <memory> |
| |
| #include "arch/arm/faults.hh" |
| #include "arch/arm/stage2_mmu.hh" |
| #include "arch/arm/system.hh" |
| #include "arch/arm/tlb.hh" |
| #include "cpu/base.hh" |
| #include "cpu/thread_context.hh" |
| #include "debug/Checkpoint.hh" |
| #include "debug/Drain.hh" |
| #include "debug/TLB.hh" |
| #include "debug/TLBVerbose.hh" |
| #include "dev/dma_device.hh" |
| #include "sim/system.hh" |
| |
| using namespace ArmISA; |
| |
| TableWalker::TableWalker(const Params *p) |
| : ClockedObject(p), |
| stage2Mmu(NULL), port(NULL), masterId(Request::invldMasterId), |
| isStage2(p->is_stage2), tlb(NULL), |
| currState(NULL), pending(false), |
| numSquashable(p->num_squash_per_cycle), |
| pendingReqs(0), |
| pendingChangeTick(curTick()), |
| doL1DescEvent([this]{ doL1DescriptorWrapper(); }, name()), |
| doL2DescEvent([this]{ doL2DescriptorWrapper(); }, name()), |
| doL0LongDescEvent([this]{ doL0LongDescriptorWrapper(); }, name()), |
| doL1LongDescEvent([this]{ doL1LongDescriptorWrapper(); }, name()), |
| doL2LongDescEvent([this]{ doL2LongDescriptorWrapper(); }, name()), |
| doL3LongDescEvent([this]{ doL3LongDescriptorWrapper(); }, name()), |
| LongDescEventByLevel { &doL0LongDescEvent, &doL1LongDescEvent, |
| &doL2LongDescEvent, &doL3LongDescEvent }, |
| doProcessEvent([this]{ processWalkWrapper(); }, name()) |
| { |
| sctlr = 0; |
| |
| // Cache system-level properties |
| if (FullSystem) { |
| ArmSystem *armSys = dynamic_cast<ArmSystem *>(p->sys); |
| assert(armSys); |
| haveSecurity = armSys->haveSecurity(); |
| _haveLPAE = armSys->haveLPAE(); |
| _haveVirtualization = armSys->haveVirtualization(); |
| physAddrRange = armSys->physAddrRange(); |
| _haveLargeAsid64 = armSys->haveLargeAsid64(); |
| } else { |
| haveSecurity = _haveLPAE = _haveVirtualization = false; |
| _haveLargeAsid64 = false; |
| physAddrRange = 32; |
| } |
| |
| } |
| |
| TableWalker::~TableWalker() |
| { |
| ; |
| } |
| |
| void |
| TableWalker::setMMU(Stage2MMU *m, MasterID master_id) |
| { |
| stage2Mmu = m; |
| port = &m->getDMAPort(); |
| masterId = master_id; |
| } |
| |
| void |
| TableWalker::init() |
| { |
| fatal_if(!stage2Mmu, "Table walker must have a valid stage-2 MMU\n"); |
| fatal_if(!port, "Table walker must have a valid port\n"); |
| fatal_if(!tlb, "Table walker must have a valid TLB\n"); |
| } |
| |
| Port & |
| TableWalker::getPort(const std::string &if_name, PortID idx) |
| { |
| if (if_name == "port") { |
| if (!isStage2) { |
| return *port; |
| } else { |
| fatal("Cannot access table walker port through stage-two walker\n"); |
| } |
| } |
| return ClockedObject::getPort(if_name, idx); |
| } |
| |
| TableWalker::WalkerState::WalkerState() : |
| tc(nullptr), aarch64(false), el(EL0), physAddrRange(0), req(nullptr), |
| asid(0), vmid(0), isHyp(false), transState(nullptr), |
| vaddr(0), vaddr_tainted(0), |
| sctlr(0), scr(0), cpsr(0), tcr(0), |
| htcr(0), hcr(0), vtcr(0), |
| isWrite(false), isFetch(false), isSecure(false), |
| isUncacheable(false), |
| secureLookup(false), rwTable(false), userTable(false), xnTable(false), |
| pxnTable(false), hpd(false), stage2Req(false), |
| stage2Tran(nullptr), timing(false), functional(false), |
| mode(BaseTLB::Read), tranType(TLB::NormalTran), l2Desc(l1Desc), |
| delayed(false), tableWalker(nullptr) |
| { |
| } |
| |
| void |
| TableWalker::completeDrain() |
| { |
| if (drainState() == DrainState::Draining && |
| stateQueues[L0].empty() && stateQueues[L1].empty() && |
| stateQueues[L2].empty() && stateQueues[L3].empty() && |
| pendingQueue.empty()) { |
| |
| DPRINTF(Drain, "TableWalker done draining, processing drain event\n"); |
| signalDrainDone(); |
| } |
| } |
| |
| DrainState |
| TableWalker::drain() |
| { |
| bool state_queues_not_empty = false; |
| |
| for (int i = 0; i < MAX_LOOKUP_LEVELS; ++i) { |
| if (!stateQueues[i].empty()) { |
| state_queues_not_empty = true; |
| break; |
| } |
| } |
| |
| if (state_queues_not_empty || pendingQueue.size()) { |
| DPRINTF(Drain, "TableWalker not drained\n"); |
| return DrainState::Draining; |
| } else { |
| DPRINTF(Drain, "TableWalker free, no need to drain\n"); |
| return DrainState::Drained; |
| } |
| } |
| |
| void |
| TableWalker::drainResume() |
| { |
| if (params()->sys->isTimingMode() && currState) { |
| delete currState; |
| currState = NULL; |
| pendingChange(); |
| } |
| } |
| |
| Fault |
| TableWalker::walk(const RequestPtr &_req, ThreadContext *_tc, uint16_t _asid, |
| uint8_t _vmid, bool _isHyp, TLB::Mode _mode, |
| TLB::Translation *_trans, bool _timing, bool _functional, |
| bool secure, TLB::ArmTranslationType tranType, |
| bool _stage2Req) |
| { |
| assert(!(_functional && _timing)); |
| ++statWalks; |
| |
| WalkerState *savedCurrState = NULL; |
| |
| if (!currState && !_functional) { |
| // For atomic mode, a new WalkerState instance should be only created |
| // once per TLB. For timing mode, a new instance is generated for every |
| // TLB miss. |
| DPRINTF(TLBVerbose, "creating new instance of WalkerState\n"); |
| |
| currState = new WalkerState(); |
| currState->tableWalker = this; |
| } else if (_functional) { |
| // If we are mixing functional mode with timing (or even |
| // atomic), we need to to be careful and clean up after |
| // ourselves to not risk getting into an inconsistent state. |
| DPRINTF(TLBVerbose, "creating functional instance of WalkerState\n"); |
| savedCurrState = currState; |
| currState = new WalkerState(); |
| currState->tableWalker = this; |
| } else if (_timing) { |
| // This is a translation that was completed and then faulted again |
| // because some underlying parameters that affect the translation |
| // changed out from under us (e.g. asid). It will either be a |
| // misprediction, in which case nothing will happen or we'll use |
| // this fault to re-execute the faulting instruction which should clean |
| // up everything. |
| if (currState->vaddr_tainted == _req->getVaddr()) { |
| ++statSquashedBefore; |
| return std::make_shared<ReExec>(); |
| } |
| } |
| pendingChange(); |
| |
| currState->startTime = curTick(); |
| currState->tc = _tc; |
| // ARM DDI 0487A.f (ARMv8 ARM) pg J8-5672 |
| // aarch32/translation/translation/AArch32.TranslateAddress dictates |
| // even AArch32 EL0 will use AArch64 translation if EL1 is in AArch64. |
| if (isStage2) { |
| currState->el = EL1; |
| currState->aarch64 = ELIs64(_tc, EL2); |
| } else { |
| currState->el = |
| TLB::tranTypeEL(_tc->readMiscReg(MISCREG_CPSR), tranType); |
| currState->aarch64 = |
| ELIs64(_tc, currState->el == EL0 ? EL1 : currState->el); |
| } |
| currState->transState = _trans; |
| currState->req = _req; |
| currState->fault = NoFault; |
| currState->asid = _asid; |
| currState->vmid = _vmid; |
| currState->isHyp = _isHyp; |
| currState->timing = _timing; |
| currState->functional = _functional; |
| currState->mode = _mode; |
| currState->tranType = tranType; |
| currState->isSecure = secure; |
| currState->physAddrRange = physAddrRange; |
| |
| /** @todo These should be cached or grabbed from cached copies in |
| the TLB, all these miscreg reads are expensive */ |
| currState->vaddr_tainted = currState->req->getVaddr(); |
| if (currState->aarch64) |
| currState->vaddr = purifyTaggedAddr(currState->vaddr_tainted, |
| currState->tc, currState->el, |
| currState->mode==TLB::Execute); |
| else |
| currState->vaddr = currState->vaddr_tainted; |
| |
| if (currState->aarch64) { |
| if (isStage2) { |
| currState->sctlr = currState->tc->readMiscReg(MISCREG_SCTLR_EL1); |
| currState->vtcr = currState->tc->readMiscReg(MISCREG_VTCR_EL2); |
| } else switch (currState->el) { |
| case EL0: |
| case EL1: |
| currState->sctlr = currState->tc->readMiscReg(MISCREG_SCTLR_EL1); |
| currState->tcr = currState->tc->readMiscReg(MISCREG_TCR_EL1); |
| break; |
| case EL2: |
| assert(_haveVirtualization); |
| currState->sctlr = currState->tc->readMiscReg(MISCREG_SCTLR_EL2); |
| currState->tcr = currState->tc->readMiscReg(MISCREG_TCR_EL2); |
| break; |
| case EL3: |
| assert(haveSecurity); |
| currState->sctlr = currState->tc->readMiscReg(MISCREG_SCTLR_EL3); |
| currState->tcr = currState->tc->readMiscReg(MISCREG_TCR_EL3); |
| break; |
| default: |
| panic("Invalid exception level"); |
| break; |
| } |
| currState->hcr = currState->tc->readMiscReg(MISCREG_HCR_EL2); |
| } else { |
| currState->sctlr = currState->tc->readMiscReg(snsBankedIndex( |
| MISCREG_SCTLR, currState->tc, !currState->isSecure)); |
| currState->ttbcr = currState->tc->readMiscReg(snsBankedIndex( |
| MISCREG_TTBCR, currState->tc, !currState->isSecure)); |
| currState->htcr = currState->tc->readMiscReg(MISCREG_HTCR); |
| currState->hcr = currState->tc->readMiscReg(MISCREG_HCR); |
| currState->vtcr = currState->tc->readMiscReg(MISCREG_VTCR); |
| } |
| sctlr = currState->sctlr; |
| |
| currState->isFetch = (currState->mode == TLB::Execute); |
| currState->isWrite = (currState->mode == TLB::Write); |
| |
| statRequestOrigin[REQUESTED][currState->isFetch]++; |
| |
| currState->stage2Req = _stage2Req && !isStage2; |
| |
| bool long_desc_format = currState->aarch64 || _isHyp || isStage2 || |
| longDescFormatInUse(currState->tc); |
| |
| if (long_desc_format) { |
| // Helper variables used for hierarchical permissions |
| currState->secureLookup = currState->isSecure; |
| currState->rwTable = true; |
| currState->userTable = true; |
| currState->xnTable = false; |
| currState->pxnTable = false; |
| |
| ++statWalksLongDescriptor; |
| } else { |
| ++statWalksShortDescriptor; |
| } |
| |
| if (!currState->timing) { |
| Fault fault = NoFault; |
| if (currState->aarch64) |
| fault = processWalkAArch64(); |
| else if (long_desc_format) |
| fault = processWalkLPAE(); |
| else |
| fault = processWalk(); |
| |
| // If this was a functional non-timing access restore state to |
| // how we found it. |
| if (currState->functional) { |
| delete currState; |
| currState = savedCurrState; |
| } |
| return fault; |
| } |
| |
| if (pending || pendingQueue.size()) { |
| pendingQueue.push_back(currState); |
| currState = NULL; |
| pendingChange(); |
| } else { |
| pending = true; |
| pendingChange(); |
| if (currState->aarch64) |
| return processWalkAArch64(); |
| else if (long_desc_format) |
| return processWalkLPAE(); |
| else |
| return processWalk(); |
| } |
| |
| return NoFault; |
| } |
| |
| void |
| TableWalker::processWalkWrapper() |
| { |
| assert(!currState); |
| assert(pendingQueue.size()); |
| pendingChange(); |
| currState = pendingQueue.front(); |
| |
| // Check if a previous walk filled this request already |
| // @TODO Should this always be the TLB or should we look in the stage2 TLB? |
| TlbEntry* te = tlb->lookup(currState->vaddr, currState->asid, |
| currState->vmid, currState->isHyp, currState->isSecure, true, false, |
| currState->el); |
| |
| // Check if we still need to have a walk for this request. If the requesting |
| // instruction has been squashed, or a previous walk has filled the TLB with |
| // a match, we just want to get rid of the walk. The latter could happen |
| // when there are multiple outstanding misses to a single page and a |
| // previous request has been successfully translated. |
| if (!currState->transState->squashed() && !te) { |
| // We've got a valid request, lets process it |
| pending = true; |
| pendingQueue.pop_front(); |
| // Keep currState in case one of the processWalk... calls NULLs it |
| WalkerState *curr_state_copy = currState; |
| Fault f; |
| if (currState->aarch64) |
| f = processWalkAArch64(); |
| else if (longDescFormatInUse(currState->tc) || |
| currState->isHyp || isStage2) |
| f = processWalkLPAE(); |
| else |
| f = processWalk(); |
| |
| if (f != NoFault) { |
| curr_state_copy->transState->finish(f, curr_state_copy->req, |
| curr_state_copy->tc, curr_state_copy->mode); |
| |
| delete curr_state_copy; |
| } |
| return; |
| } |
| |
| |
| // If the instruction that we were translating for has been |
| // squashed we shouldn't bother. |
| unsigned num_squashed = 0; |
| ThreadContext *tc = currState->tc; |
| while ((num_squashed < numSquashable) && currState && |
| (currState->transState->squashed() || te)) { |
| pendingQueue.pop_front(); |
| num_squashed++; |
| statSquashedBefore++; |
| |
| DPRINTF(TLB, "Squashing table walk for address %#x\n", |
| currState->vaddr_tainted); |
| |
| if (currState->transState->squashed()) { |
| // finish the translation which will delete the translation object |
| currState->transState->finish( |
| std::make_shared<UnimpFault>("Squashed Inst"), |
| currState->req, currState->tc, currState->mode); |
| } else { |
| // translate the request now that we know it will work |
| statWalkServiceTime.sample(curTick() - currState->startTime); |
| tlb->translateTiming(currState->req, currState->tc, |
| currState->transState, currState->mode); |
| |
| } |
| |
| // delete the current request |
| delete currState; |
| |
| // peak at the next one |
| if (pendingQueue.size()) { |
| currState = pendingQueue.front(); |
| te = tlb->lookup(currState->vaddr, currState->asid, |
| currState->vmid, currState->isHyp, currState->isSecure, true, |
| false, currState->el); |
| } else { |
| // Terminate the loop, nothing more to do |
| currState = NULL; |
| } |
| } |
| pendingChange(); |
| |
| // if we still have pending translations, schedule more work |
| nextWalk(tc); |
| currState = NULL; |
| } |
| |
| Fault |
| TableWalker::processWalk() |
| { |
| Addr ttbr = 0; |
| |
| // For short descriptors, translation configs are held in |
| // TTBR1. |
| RegVal ttbr1 = currState->tc->readMiscReg(snsBankedIndex( |
| MISCREG_TTBR1, currState->tc, !currState->isSecure)); |
| |
| const auto irgn0_mask = 0x1; |
| const auto irgn1_mask = 0x40; |
| currState->isUncacheable = (ttbr1 & (irgn0_mask | irgn1_mask)) == 0; |
| |
| // If translation isn't enabled, we shouldn't be here |
| assert(currState->sctlr.m || isStage2); |
| const bool is_atomic = currState->req->isAtomic(); |
| |
| DPRINTF(TLB, "Beginning table walk for address %#x, TTBCR: %#x, bits:%#x\n", |
| currState->vaddr_tainted, currState->ttbcr, mbits(currState->vaddr, 31, |
| 32 - currState->ttbcr.n)); |
| |
| statWalkWaitTime.sample(curTick() - currState->startTime); |
| |
| if (currState->ttbcr.n == 0 || !mbits(currState->vaddr, 31, |
| 32 - currState->ttbcr.n)) { |
| DPRINTF(TLB, " - Selecting TTBR0\n"); |
| // Check if table walk is allowed when Security Extensions are enabled |
| if (haveSecurity && currState->ttbcr.pd0) { |
| if (currState->isFetch) |
| return std::make_shared<PrefetchAbort>( |
| currState->vaddr_tainted, |
| ArmFault::TranslationLL + L1, |
| isStage2, |
| ArmFault::VmsaTran); |
| else |
| return std::make_shared<DataAbort>( |
| currState->vaddr_tainted, |
| TlbEntry::DomainType::NoAccess, |
| is_atomic ? false : currState->isWrite, |
| ArmFault::TranslationLL + L1, isStage2, |
| ArmFault::VmsaTran); |
| } |
| ttbr = currState->tc->readMiscReg(snsBankedIndex( |
| MISCREG_TTBR0, currState->tc, !currState->isSecure)); |
| } else { |
| DPRINTF(TLB, " - Selecting TTBR1\n"); |
| // Check if table walk is allowed when Security Extensions are enabled |
| if (haveSecurity && currState->ttbcr.pd1) { |
| if (currState->isFetch) |
| return std::make_shared<PrefetchAbort>( |
| currState->vaddr_tainted, |
| ArmFault::TranslationLL + L1, |
| isStage2, |
| ArmFault::VmsaTran); |
| else |
| return std::make_shared<DataAbort>( |
| currState->vaddr_tainted, |
| TlbEntry::DomainType::NoAccess, |
| is_atomic ? false : currState->isWrite, |
| ArmFault::TranslationLL + L1, isStage2, |
| ArmFault::VmsaTran); |
| } |
| ttbr = ttbr1; |
| currState->ttbcr.n = 0; |
| } |
| |
| Addr l1desc_addr = mbits(ttbr, 31, 14 - currState->ttbcr.n) | |
| (bits(currState->vaddr, 31 - currState->ttbcr.n, 20) << 2); |
| DPRINTF(TLB, " - Descriptor at address %#x (%s)\n", l1desc_addr, |
| currState->isSecure ? "s" : "ns"); |
| |
| // Trickbox address check |
| Fault f; |
| f = testWalk(l1desc_addr, sizeof(uint32_t), |
| TlbEntry::DomainType::NoAccess, L1); |
| if (f) { |
| DPRINTF(TLB, "Trickbox check caused fault on %#x\n", currState->vaddr_tainted); |
| if (currState->timing) { |
| pending = false; |
| nextWalk(currState->tc); |
| currState = NULL; |
| } else { |
| currState->tc = NULL; |
| currState->req = NULL; |
| } |
| return f; |
| } |
| |
| Request::Flags flag = Request::PT_WALK; |
| if (currState->sctlr.c == 0 || currState->isUncacheable) { |
| flag.set(Request::UNCACHEABLE); |
| } |
| |
| if (currState->isSecure) { |
| flag.set(Request::SECURE); |
| } |
| |
| bool delayed; |
| delayed = fetchDescriptor(l1desc_addr, (uint8_t*)&currState->l1Desc.data, |
| sizeof(uint32_t), flag, L1, &doL1DescEvent, |
| &TableWalker::doL1Descriptor); |
| if (!delayed) { |
| f = currState->fault; |
| } |
| |
| return f; |
| } |
| |
| Fault |
| TableWalker::processWalkLPAE() |
| { |
| Addr ttbr, ttbr0_max, ttbr1_min, desc_addr; |
| int tsz, n; |
| LookupLevel start_lookup_level = L1; |
| |
| DPRINTF(TLB, "Beginning table walk for address %#x, TTBCR: %#x\n", |
| currState->vaddr_tainted, currState->ttbcr); |
| |
| statWalkWaitTime.sample(curTick() - currState->startTime); |
| |
| Request::Flags flag = Request::PT_WALK; |
| if (currState->isSecure) |
| flag.set(Request::SECURE); |
| |
| // work out which base address register to use, if in hyp mode we always |
| // use HTTBR |
| if (isStage2) { |
| DPRINTF(TLB, " - Selecting VTTBR (long-desc.)\n"); |
| ttbr = currState->tc->readMiscReg(MISCREG_VTTBR); |
| tsz = sext<4>(currState->vtcr.t0sz); |
| start_lookup_level = currState->vtcr.sl0 ? L1 : L2; |
| currState->isUncacheable = currState->vtcr.irgn0 == 0; |
| } else if (currState->isHyp) { |
| DPRINTF(TLB, " - Selecting HTTBR (long-desc.)\n"); |
| ttbr = currState->tc->readMiscReg(MISCREG_HTTBR); |
| tsz = currState->htcr.t0sz; |
| currState->isUncacheable = currState->htcr.irgn0 == 0; |
| } else { |
| assert(longDescFormatInUse(currState->tc)); |
| |
| // Determine boundaries of TTBR0/1 regions |
| if (currState->ttbcr.t0sz) |
| ttbr0_max = (1ULL << (32 - currState->ttbcr.t0sz)) - 1; |
| else if (currState->ttbcr.t1sz) |
| ttbr0_max = (1ULL << 32) - |
| (1ULL << (32 - currState->ttbcr.t1sz)) - 1; |
| else |
| ttbr0_max = (1ULL << 32) - 1; |
| if (currState->ttbcr.t1sz) |
| ttbr1_min = (1ULL << 32) - (1ULL << (32 - currState->ttbcr.t1sz)); |
| else |
| ttbr1_min = (1ULL << (32 - currState->ttbcr.t0sz)); |
| |
| const bool is_atomic = currState->req->isAtomic(); |
| |
| // The following code snippet selects the appropriate translation table base |
| // address (TTBR0 or TTBR1) and the appropriate starting lookup level |
| // depending on the address range supported by the translation table (ARM |
| // ARM issue C B3.6.4) |
| if (currState->vaddr <= ttbr0_max) { |
| DPRINTF(TLB, " - Selecting TTBR0 (long-desc.)\n"); |
| // Check if table walk is allowed |
| if (currState->ttbcr.epd0) { |
| if (currState->isFetch) |
| return std::make_shared<PrefetchAbort>( |
| currState->vaddr_tainted, |
| ArmFault::TranslationLL + L1, |
| isStage2, |
| ArmFault::LpaeTran); |
| else |
| return std::make_shared<DataAbort>( |
| currState->vaddr_tainted, |
| TlbEntry::DomainType::NoAccess, |
| is_atomic ? false : currState->isWrite, |
| ArmFault::TranslationLL + L1, |
| isStage2, |
| ArmFault::LpaeTran); |
| } |
| ttbr = currState->tc->readMiscReg(snsBankedIndex( |
| MISCREG_TTBR0, currState->tc, !currState->isSecure)); |
| tsz = currState->ttbcr.t0sz; |
| currState->isUncacheable = currState->ttbcr.irgn0 == 0; |
| if (ttbr0_max < (1ULL << 30)) // Upper limit < 1 GB |
| start_lookup_level = L2; |
| } else if (currState->vaddr >= ttbr1_min) { |
| DPRINTF(TLB, " - Selecting TTBR1 (long-desc.)\n"); |
| // Check if table walk is allowed |
| if (currState->ttbcr.epd1) { |
| if (currState->isFetch) |
| return std::make_shared<PrefetchAbort>( |
| currState->vaddr_tainted, |
| ArmFault::TranslationLL + L1, |
| isStage2, |
| ArmFault::LpaeTran); |
| else |
| return std::make_shared<DataAbort>( |
| currState->vaddr_tainted, |
| TlbEntry::DomainType::NoAccess, |
| is_atomic ? false : currState->isWrite, |
| ArmFault::TranslationLL + L1, |
| isStage2, |
| ArmFault::LpaeTran); |
| } |
| ttbr = currState->tc->readMiscReg(snsBankedIndex( |
| MISCREG_TTBR1, currState->tc, !currState->isSecure)); |
| tsz = currState->ttbcr.t1sz; |
| currState->isUncacheable = currState->ttbcr.irgn1 == 0; |
| // Lower limit >= 3 GB |
| if (ttbr1_min >= (1ULL << 31) + (1ULL << 30)) |
| start_lookup_level = L2; |
| } else { |
| // Out of boundaries -> translation fault |
| if (currState->isFetch) |
| return std::make_shared<PrefetchAbort>( |
| currState->vaddr_tainted, |
| ArmFault::TranslationLL + L1, |
| isStage2, |
| ArmFault::LpaeTran); |
| else |
| return std::make_shared<DataAbort>( |
| currState->vaddr_tainted, |
| TlbEntry::DomainType::NoAccess, |
| is_atomic ? false : currState->isWrite, |
| ArmFault::TranslationLL + L1, |
| isStage2, ArmFault::LpaeTran); |
| } |
| |
| } |
| |
| // Perform lookup (ARM ARM issue C B3.6.6) |
| if (start_lookup_level == L1) { |
| n = 5 - tsz; |
| desc_addr = mbits(ttbr, 39, n) | |
| (bits(currState->vaddr, n + 26, 30) << 3); |
| DPRINTF(TLB, " - Descriptor at address %#x (%s) (long-desc.)\n", |
| desc_addr, currState->isSecure ? "s" : "ns"); |
| } else { |
| // Skip first-level lookup |
| n = (tsz >= 2 ? 14 - tsz : 12); |
| desc_addr = mbits(ttbr, 39, n) | |
| (bits(currState->vaddr, n + 17, 21) << 3); |
| DPRINTF(TLB, " - Descriptor at address %#x (%s) (long-desc.)\n", |
| desc_addr, currState->isSecure ? "s" : "ns"); |
| } |
| |
| // Trickbox address check |
| Fault f = testWalk(desc_addr, sizeof(uint64_t), |
| TlbEntry::DomainType::NoAccess, start_lookup_level); |
| if (f) { |
| DPRINTF(TLB, "Trickbox check caused fault on %#x\n", currState->vaddr_tainted); |
| if (currState->timing) { |
| pending = false; |
| nextWalk(currState->tc); |
| currState = NULL; |
| } else { |
| currState->tc = NULL; |
| currState->req = NULL; |
| } |
| return f; |
| } |
| |
| if (currState->sctlr.c == 0 || currState->isUncacheable) { |
| flag.set(Request::UNCACHEABLE); |
| } |
| |
| currState->longDesc.lookupLevel = start_lookup_level; |
| currState->longDesc.aarch64 = false; |
| currState->longDesc.grainSize = Grain4KB; |
| |
| bool delayed = fetchDescriptor(desc_addr, (uint8_t*)&currState->longDesc.data, |
| sizeof(uint64_t), flag, start_lookup_level, |
| LongDescEventByLevel[start_lookup_level], |
| &TableWalker::doLongDescriptor); |
| if (!delayed) { |
| f = currState->fault; |
| } |
| |
| return f; |
| } |
| |
| unsigned |
| TableWalker::adjustTableSizeAArch64(unsigned tsz) |
| { |
| if (tsz < 25) |
| return 25; |
| if (tsz > 48) |
| return 48; |
| return tsz; |
| } |
| |
| bool |
| TableWalker::checkAddrSizeFaultAArch64(Addr addr, int currPhysAddrRange) |
| { |
| return (currPhysAddrRange != MaxPhysAddrRange && |
| bits(addr, MaxPhysAddrRange - 1, currPhysAddrRange)); |
| } |
| |
| Fault |
| TableWalker::processWalkAArch64() |
| { |
| assert(currState->aarch64); |
| |
| DPRINTF(TLB, "Beginning table walk for address %#llx, TCR: %#llx\n", |
| currState->vaddr_tainted, currState->tcr); |
| |
| static const GrainSize GrainMap_tg0[] = |
| { Grain4KB, Grain64KB, Grain16KB, ReservedGrain }; |
| static const GrainSize GrainMap_tg1[] = |
| { ReservedGrain, Grain16KB, Grain4KB, Grain64KB }; |
| |
| statWalkWaitTime.sample(curTick() - currState->startTime); |
| |
| // Determine TTBR, table size, granule size and phys. address range |
| Addr ttbr = 0; |
| int tsz = 0, ps = 0; |
| GrainSize tg = Grain4KB; // grain size computed from tg* field |
| bool fault = false; |
| |
| LookupLevel start_lookup_level = MAX_LOOKUP_LEVELS; |
| |
| switch (currState->el) { |
| case EL0: |
| case EL1: |
| if (isStage2) { |
| DPRINTF(TLB, " - Selecting VTTBR0 (AArch64 stage 2)\n"); |
| ttbr = currState->tc->readMiscReg(MISCREG_VTTBR_EL2); |
| tsz = 64 - currState->vtcr.t0sz64; |
| tg = GrainMap_tg0[currState->vtcr.tg0]; |
| // ARM DDI 0487A.f D7-2148 |
| // The starting level of stage 2 translation depends on |
| // VTCR_EL2.SL0 and VTCR_EL2.TG0 |
| LookupLevel __ = MAX_LOOKUP_LEVELS; // invalid level |
| uint8_t sl_tg = (currState->vtcr.sl0 << 2) | currState->vtcr.tg0; |
| static const LookupLevel SLL[] = { |
| L2, L3, L3, __, // sl0 == 0 |
| L1, L2, L2, __, // sl0 == 1, etc. |
| L0, L1, L1, __, |
| __, __, __, __ |
| }; |
| start_lookup_level = SLL[sl_tg]; |
| panic_if(start_lookup_level == MAX_LOOKUP_LEVELS, |
| "Cannot discern lookup level from vtcr.{sl0,tg0}"); |
| ps = currState->vtcr.ps; |
| currState->isUncacheable = currState->vtcr.irgn0 == 0; |
| } else { |
| switch (bits(currState->vaddr, 63,48)) { |
| case 0: |
| DPRINTF(TLB, " - Selecting TTBR0 (AArch64)\n"); |
| ttbr = currState->tc->readMiscReg(MISCREG_TTBR0_EL1); |
| tsz = adjustTableSizeAArch64(64 - currState->tcr.t0sz); |
| tg = GrainMap_tg0[currState->tcr.tg0]; |
| currState->hpd = currState->tcr.hpd0; |
| currState->isUncacheable = currState->tcr.irgn0 == 0; |
| if (bits(currState->vaddr, 63, tsz) != 0x0 || |
| currState->tcr.epd0) |
| fault = true; |
| break; |
| case 0xffff: |
| DPRINTF(TLB, " - Selecting TTBR1 (AArch64)\n"); |
| ttbr = currState->tc->readMiscReg(MISCREG_TTBR1_EL1); |
| tsz = adjustTableSizeAArch64(64 - currState->tcr.t1sz); |
| tg = GrainMap_tg1[currState->tcr.tg1]; |
| currState->hpd = currState->tcr.hpd1; |
| currState->isUncacheable = currState->tcr.irgn1 == 0; |
| if (bits(currState->vaddr, 63, tsz) != mask(64-tsz) || |
| currState->tcr.epd1) |
| fault = true; |
| break; |
| default: |
| // top two bytes must be all 0s or all 1s, else invalid addr |
| fault = true; |
| } |
| ps = currState->tcr.ips; |
| } |
| break; |
| case EL2: |
| switch(bits(currState->vaddr, 63,48)) { |
| case 0: |
| DPRINTF(TLB, " - Selecting TTBR0 (AArch64)\n"); |
| ttbr = currState->tc->readMiscReg(MISCREG_TTBR0_EL2); |
| tsz = adjustTableSizeAArch64(64 - currState->tcr.t0sz); |
| tg = GrainMap_tg0[currState->tcr.tg0]; |
| currState->hpd = currState->hcr.e2h ? |
| currState->tcr.hpd0 : currState->tcr.hpd; |
| currState->isUncacheable = currState->tcr.irgn0 == 0; |
| break; |
| |
| case 0xffff: |
| DPRINTF(TLB, " - Selecting TTBR1 (AArch64)\n"); |
| ttbr = currState->tc->readMiscReg(MISCREG_TTBR1_EL2); |
| tsz = adjustTableSizeAArch64(64 - currState->tcr.t1sz); |
| tg = GrainMap_tg1[currState->tcr.tg1]; |
| currState->hpd = currState->tcr.hpd1; |
| currState->isUncacheable = currState->tcr.irgn1 == 0; |
| if (bits(currState->vaddr, 63, tsz) != mask(64-tsz) || |
| currState->tcr.epd1 || !currState->hcr.e2h) |
| fault = true; |
| break; |
| |
| default: |
| // invalid addr if top two bytes are not all 0s |
| fault = true; |
| } |
| ps = currState->tcr.ps; |
| break; |
| case EL3: |
| switch(bits(currState->vaddr, 63,48)) { |
| case 0: |
| DPRINTF(TLB, " - Selecting TTBR0 (AArch64)\n"); |
| ttbr = currState->tc->readMiscReg(MISCREG_TTBR0_EL3); |
| tsz = adjustTableSizeAArch64(64 - currState->tcr.t0sz); |
| tg = GrainMap_tg0[currState->tcr.tg0]; |
| currState->hpd = currState->tcr.hpd; |
| currState->isUncacheable = currState->tcr.irgn0 == 0; |
| break; |
| default: |
| // invalid addr if top two bytes are not all 0s |
| fault = true; |
| } |
| ps = currState->tcr.ps; |
| break; |
| } |
| |
| const bool is_atomic = currState->req->isAtomic(); |
| |
| if (fault) { |
| Fault f; |
| if (currState->isFetch) |
| f = std::make_shared<PrefetchAbort>( |
| currState->vaddr_tainted, |
| ArmFault::TranslationLL + L0, isStage2, |
| ArmFault::LpaeTran); |
| else |
| f = std::make_shared<DataAbort>( |
| currState->vaddr_tainted, |
| TlbEntry::DomainType::NoAccess, |
| is_atomic ? false : currState->isWrite, |
| ArmFault::TranslationLL + L0, |
| isStage2, ArmFault::LpaeTran); |
| |
| if (currState->timing) { |
| pending = false; |
| nextWalk(currState->tc); |
| currState = NULL; |
| } else { |
| currState->tc = NULL; |
| currState->req = NULL; |
| } |
| return f; |
| |
| } |
| |
| if (tg == ReservedGrain) { |
| warn_once("Reserved granule size requested; gem5's IMPLEMENTATION " |
| "DEFINED behavior takes this to mean 4KB granules\n"); |
| tg = Grain4KB; |
| } |
| |
| // Determine starting lookup level |
| // See aarch64/translation/walk in Appendix G: ARMv8 Pseudocode Library |
| // in ARM DDI 0487A. These table values correspond to the cascading tests |
| // to compute the lookup level and are of the form |
| // (grain_size + N*stride), for N = {1, 2, 3}. |
| // A value of 64 will never succeed and a value of 0 will always succeed. |
| if (start_lookup_level == MAX_LOOKUP_LEVELS) { |
| struct GrainMap { |
| GrainSize grain_size; |
| unsigned lookup_level_cutoff[MAX_LOOKUP_LEVELS]; |
| }; |
| static const GrainMap GM[] = { |
| { Grain4KB, { 39, 30, 0, 0 } }, |
| { Grain16KB, { 47, 36, 25, 0 } }, |
| { Grain64KB, { 64, 42, 29, 0 } } |
| }; |
| |
| const unsigned *lookup = NULL; // points to a lookup_level_cutoff |
| |
| for (unsigned i = 0; i < 3; ++i) { // choose entry of GM[] |
| if (tg == GM[i].grain_size) { |
| lookup = GM[i].lookup_level_cutoff; |
| break; |
| } |
| } |
| assert(lookup); |
| |
| for (int L = L0; L != MAX_LOOKUP_LEVELS; ++L) { |
| if (tsz > lookup[L]) { |
| start_lookup_level = (LookupLevel) L; |
| break; |
| } |
| } |
| panic_if(start_lookup_level == MAX_LOOKUP_LEVELS, |
| "Table walker couldn't find lookup level\n"); |
| } |
| |
| int stride = tg - 3; |
| |
| // Determine table base address |
| int base_addr_lo = 3 + tsz - stride * (3 - start_lookup_level) - tg; |
| Addr base_addr = mbits(ttbr, 47, base_addr_lo); |
| |
| // Determine physical address size and raise an Address Size Fault if |
| // necessary |
| int pa_range = decodePhysAddrRange64(ps); |
| // Clamp to lower limit |
| if (pa_range > physAddrRange) |
| currState->physAddrRange = physAddrRange; |
| else |
| currState->physAddrRange = pa_range; |
| if (checkAddrSizeFaultAArch64(base_addr, currState->physAddrRange)) { |
| DPRINTF(TLB, "Address size fault before any lookup\n"); |
| Fault f; |
| if (currState->isFetch) |
| f = std::make_shared<PrefetchAbort>( |
| currState->vaddr_tainted, |
| ArmFault::AddressSizeLL + start_lookup_level, |
| isStage2, |
| ArmFault::LpaeTran); |
| else |
| f = std::make_shared<DataAbort>( |
| currState->vaddr_tainted, |
| TlbEntry::DomainType::NoAccess, |
| is_atomic ? false : currState->isWrite, |
| ArmFault::AddressSizeLL + start_lookup_level, |
| isStage2, |
| ArmFault::LpaeTran); |
| |
| |
| if (currState->timing) { |
| pending = false; |
| nextWalk(currState->tc); |
| currState = NULL; |
| } else { |
| currState->tc = NULL; |
| currState->req = NULL; |
| } |
| return f; |
| |
| } |
| |
| // Determine descriptor address |
| Addr desc_addr = base_addr | |
| (bits(currState->vaddr, tsz - 1, |
| stride * (3 - start_lookup_level) + tg) << 3); |
| |
| // Trickbox address check |
| Fault f = testWalk(desc_addr, sizeof(uint64_t), |
| TlbEntry::DomainType::NoAccess, start_lookup_level); |
| if (f) { |
| DPRINTF(TLB, "Trickbox check caused fault on %#x\n", currState->vaddr_tainted); |
| if (currState->timing) { |
| pending = false; |
| nextWalk(currState->tc); |
| currState = NULL; |
| } else { |
| currState->tc = NULL; |
| currState->req = NULL; |
| } |
| return f; |
| } |
| |
| Request::Flags flag = Request::PT_WALK; |
| if (currState->sctlr.c == 0 || currState->isUncacheable) { |
| flag.set(Request::UNCACHEABLE); |
| } |
| |
| if (currState->isSecure) { |
| flag.set(Request::SECURE); |
| } |
| |
| currState->longDesc.lookupLevel = start_lookup_level; |
| currState->longDesc.aarch64 = true; |
| currState->longDesc.grainSize = tg; |
| |
| if (currState->timing) { |
| fetchDescriptor(desc_addr, (uint8_t*) &currState->longDesc.data, |
| sizeof(uint64_t), flag, start_lookup_level, |
| LongDescEventByLevel[start_lookup_level], NULL); |
| } else { |
| fetchDescriptor(desc_addr, (uint8_t*)&currState->longDesc.data, |
| sizeof(uint64_t), flag, -1, NULL, |
| &TableWalker::doLongDescriptor); |
| f = currState->fault; |
| } |
| |
| return f; |
| } |
| |
| void |
| TableWalker::memAttrs(ThreadContext *tc, TlbEntry &te, SCTLR sctlr, |
| uint8_t texcb, bool s) |
| { |
| // Note: tc and sctlr local variables are hiding tc and sctrl class |
| // variables |
| DPRINTF(TLBVerbose, "memAttrs texcb:%d s:%d\n", texcb, s); |
| te.shareable = false; // default value |
| te.nonCacheable = false; |
| te.outerShareable = false; |
| if (sctlr.tre == 0 || ((sctlr.tre == 1) && (sctlr.m == 0))) { |
| switch(texcb) { |
| case 0: // Stongly-ordered |
| te.nonCacheable = true; |
| te.mtype = TlbEntry::MemoryType::StronglyOrdered; |
| te.shareable = true; |
| te.innerAttrs = 1; |
| te.outerAttrs = 0; |
| break; |
| case 1: // Shareable Device |
| te.nonCacheable = true; |
| te.mtype = TlbEntry::MemoryType::Device; |
| te.shareable = true; |
| te.innerAttrs = 3; |
| te.outerAttrs = 0; |
| break; |
| case 2: // Outer and Inner Write-Through, no Write-Allocate |
| te.mtype = TlbEntry::MemoryType::Normal; |
| te.shareable = s; |
| te.innerAttrs = 6; |
| te.outerAttrs = bits(texcb, 1, 0); |
| break; |
| case 3: // Outer and Inner Write-Back, no Write-Allocate |
| te.mtype = TlbEntry::MemoryType::Normal; |
| te.shareable = s; |
| te.innerAttrs = 7; |
| te.outerAttrs = bits(texcb, 1, 0); |
| break; |
| case 4: // Outer and Inner Non-cacheable |
| te.nonCacheable = true; |
| te.mtype = TlbEntry::MemoryType::Normal; |
| te.shareable = s; |
| te.innerAttrs = 0; |
| te.outerAttrs = bits(texcb, 1, 0); |
| break; |
| case 5: // Reserved |
| panic("Reserved texcb value!\n"); |
| break; |
| case 6: // Implementation Defined |
| panic("Implementation-defined texcb value!\n"); |
| break; |
| case 7: // Outer and Inner Write-Back, Write-Allocate |
| te.mtype = TlbEntry::MemoryType::Normal; |
| te.shareable = s; |
| te.innerAttrs = 5; |
| te.outerAttrs = 1; |
| break; |
| case 8: // Non-shareable Device |
| te.nonCacheable = true; |
| te.mtype = TlbEntry::MemoryType::Device; |
| te.shareable = false; |
| te.innerAttrs = 3; |
| te.outerAttrs = 0; |
| break; |
| case 9 ... 15: // Reserved |
| panic("Reserved texcb value!\n"); |
| break; |
| case 16 ... 31: // Cacheable Memory |
| te.mtype = TlbEntry::MemoryType::Normal; |
| te.shareable = s; |
| if (bits(texcb, 1,0) == 0 || bits(texcb, 3,2) == 0) |
| te.nonCacheable = true; |
| te.innerAttrs = bits(texcb, 1, 0); |
| te.outerAttrs = bits(texcb, 3, 2); |
| break; |
| default: |
| panic("More than 32 states for 5 bits?\n"); |
| } |
| } else { |
| assert(tc); |
| PRRR prrr = tc->readMiscReg(snsBankedIndex(MISCREG_PRRR, |
| currState->tc, !currState->isSecure)); |
| NMRR nmrr = tc->readMiscReg(snsBankedIndex(MISCREG_NMRR, |
| currState->tc, !currState->isSecure)); |
| DPRINTF(TLBVerbose, "memAttrs PRRR:%08x NMRR:%08x\n", prrr, nmrr); |
| uint8_t curr_tr = 0, curr_ir = 0, curr_or = 0; |
| switch(bits(texcb, 2,0)) { |
| case 0: |
| curr_tr = prrr.tr0; |
| curr_ir = nmrr.ir0; |
| curr_or = nmrr.or0; |
| te.outerShareable = (prrr.nos0 == 0); |
| break; |
| case 1: |
| curr_tr = prrr.tr1; |
| curr_ir = nmrr.ir1; |
| curr_or = nmrr.or1; |
| te.outerShareable = (prrr.nos1 == 0); |
| break; |
| case 2: |
| curr_tr = prrr.tr2; |
| curr_ir = nmrr.ir2; |
| curr_or = nmrr.or2; |
| te.outerShareable = (prrr.nos2 == 0); |
| break; |
| case 3: |
| curr_tr = prrr.tr3; |
| curr_ir = nmrr.ir3; |
| curr_or = nmrr.or3; |
| te.outerShareable = (prrr.nos3 == 0); |
| break; |
| case 4: |
| curr_tr = prrr.tr4; |
| curr_ir = nmrr.ir4; |
| curr_or = nmrr.or4; |
| te.outerShareable = (prrr.nos4 == 0); |
| break; |
| case 5: |
| curr_tr = prrr.tr5; |
| curr_ir = nmrr.ir5; |
| curr_or = nmrr.or5; |
| te.outerShareable = (prrr.nos5 == 0); |
| break; |
| case 6: |
| panic("Imp defined type\n"); |
| case 7: |
| curr_tr = prrr.tr7; |
| curr_ir = nmrr.ir7; |
| curr_or = nmrr.or7; |
| te.outerShareable = (prrr.nos7 == 0); |
| break; |
| } |
| |
| switch(curr_tr) { |
| case 0: |
| DPRINTF(TLBVerbose, "StronglyOrdered\n"); |
| te.mtype = TlbEntry::MemoryType::StronglyOrdered; |
| te.nonCacheable = true; |
| te.innerAttrs = 1; |
| te.outerAttrs = 0; |
| te.shareable = true; |
| break; |
| case 1: |
| DPRINTF(TLBVerbose, "Device ds1:%d ds0:%d s:%d\n", |
| prrr.ds1, prrr.ds0, s); |
| te.mtype = TlbEntry::MemoryType::Device; |
| te.nonCacheable = true; |
| te.innerAttrs = 3; |
| te.outerAttrs = 0; |
| if (prrr.ds1 && s) |
| te.shareable = true; |
| if (prrr.ds0 && !s) |
| te.shareable = true; |
| break; |
| case 2: |
| DPRINTF(TLBVerbose, "Normal ns1:%d ns0:%d s:%d\n", |
| prrr.ns1, prrr.ns0, s); |
| te.mtype = TlbEntry::MemoryType::Normal; |
| if (prrr.ns1 && s) |
| te.shareable = true; |
| if (prrr.ns0 && !s) |
| te.shareable = true; |
| break; |
| case 3: |
| panic("Reserved type"); |
| } |
| |
| if (te.mtype == TlbEntry::MemoryType::Normal){ |
| switch(curr_ir) { |
| case 0: |
| te.nonCacheable = true; |
| te.innerAttrs = 0; |
| break; |
| case 1: |
| te.innerAttrs = 5; |
| break; |
| case 2: |
| te.innerAttrs = 6; |
| break; |
| case 3: |
| te.innerAttrs = 7; |
| break; |
| } |
| |
| switch(curr_or) { |
| case 0: |
| te.nonCacheable = true; |
| te.outerAttrs = 0; |
| break; |
| case 1: |
| te.outerAttrs = 1; |
| break; |
| case 2: |
| te.outerAttrs = 2; |
| break; |
| case 3: |
| te.outerAttrs = 3; |
| break; |
| } |
| } |
| } |
| DPRINTF(TLBVerbose, "memAttrs: shareable: %d, innerAttrs: %d, " |
| "outerAttrs: %d\n", |
| te.shareable, te.innerAttrs, te.outerAttrs); |
| te.setAttributes(false); |
| } |
| |
| void |
| TableWalker::memAttrsLPAE(ThreadContext *tc, TlbEntry &te, |
| LongDescriptor &lDescriptor) |
| { |
| assert(_haveLPAE); |
| |
| uint8_t attr; |
| uint8_t sh = lDescriptor.sh(); |
| // Different format and source of attributes if this is a stage 2 |
| // translation |
| if (isStage2) { |
| attr = lDescriptor.memAttr(); |
| uint8_t attr_3_2 = (attr >> 2) & 0x3; |
| uint8_t attr_1_0 = attr & 0x3; |
| |
| DPRINTF(TLBVerbose, "memAttrsLPAE MemAttr:%#x sh:%#x\n", attr, sh); |
| |
| if (attr_3_2 == 0) { |
| te.mtype = attr_1_0 == 0 ? TlbEntry::MemoryType::StronglyOrdered |
| : TlbEntry::MemoryType::Device; |
| te.outerAttrs = 0; |
| te.innerAttrs = attr_1_0 == 0 ? 1 : 3; |
| te.nonCacheable = true; |
| } else { |
| te.mtype = TlbEntry::MemoryType::Normal; |
| te.outerAttrs = attr_3_2 == 1 ? 0 : |
| attr_3_2 == 2 ? 2 : 1; |
| te.innerAttrs = attr_1_0 == 1 ? 0 : |
| attr_1_0 == 2 ? 6 : 5; |
| te.nonCacheable = (attr_3_2 == 1) || (attr_1_0 == 1); |
| } |
| } else { |
| uint8_t attrIndx = lDescriptor.attrIndx(); |
| |
| // LPAE always uses remapping of memory attributes, irrespective of the |
| // value of SCTLR.TRE |
| MiscRegIndex reg = attrIndx & 0x4 ? MISCREG_MAIR1 : MISCREG_MAIR0; |
| int reg_as_int = snsBankedIndex(reg, currState->tc, |
| !currState->isSecure); |
| uint32_t mair = currState->tc->readMiscReg(reg_as_int); |
| attr = (mair >> (8 * (attrIndx % 4))) & 0xff; |
| uint8_t attr_7_4 = bits(attr, 7, 4); |
| uint8_t attr_3_0 = bits(attr, 3, 0); |
| DPRINTF(TLBVerbose, "memAttrsLPAE AttrIndx:%#x sh:%#x, attr %#x\n", attrIndx, sh, attr); |
| |
| // Note: the memory subsystem only cares about the 'cacheable' memory |
| // attribute. The other attributes are only used to fill the PAR register |
| // accordingly to provide the illusion of full support |
| te.nonCacheable = false; |
| |
| switch (attr_7_4) { |
| case 0x0: |
| // Strongly-ordered or Device memory |
| if (attr_3_0 == 0x0) |
| te.mtype = TlbEntry::MemoryType::StronglyOrdered; |
| else if (attr_3_0 == 0x4) |
| te.mtype = TlbEntry::MemoryType::Device; |
| else |
| panic("Unpredictable behavior\n"); |
| te.nonCacheable = true; |
| te.outerAttrs = 0; |
| break; |
| case 0x4: |
| // Normal memory, Outer Non-cacheable |
| te.mtype = TlbEntry::MemoryType::Normal; |
| te.outerAttrs = 0; |
| if (attr_3_0 == 0x4) |
| // Inner Non-cacheable |
| te.nonCacheable = true; |
| else if (attr_3_0 < 0x8) |
| panic("Unpredictable behavior\n"); |
| break; |
| case 0x8: |
| case 0x9: |
| case 0xa: |
| case 0xb: |
| case 0xc: |
| case 0xd: |
| case 0xe: |
| case 0xf: |
| if (attr_7_4 & 0x4) { |
| te.outerAttrs = (attr_7_4 & 1) ? 1 : 3; |
| } else { |
| te.outerAttrs = 0x2; |
| } |
| // Normal memory, Outer Cacheable |
| te.mtype = TlbEntry::MemoryType::Normal; |
| if (attr_3_0 != 0x4 && attr_3_0 < 0x8) |
| panic("Unpredictable behavior\n"); |
| break; |
| default: |
| panic("Unpredictable behavior\n"); |
| break; |
| } |
| |
| switch (attr_3_0) { |
| case 0x0: |
| te.innerAttrs = 0x1; |
| break; |
| case 0x4: |
| te.innerAttrs = attr_7_4 == 0 ? 0x3 : 0; |
| break; |
| case 0x8: |
| case 0x9: |
| case 0xA: |
| case 0xB: |
| te.innerAttrs = 6; |
| break; |
| case 0xC: |
| case 0xD: |
| case 0xE: |
| case 0xF: |
| te.innerAttrs = attr_3_0 & 1 ? 0x5 : 0x7; |
| break; |
| default: |
| panic("Unpredictable behavior\n"); |
| break; |
| } |
| } |
| |
| te.outerShareable = sh == 2; |
| te.shareable = (sh & 0x2) ? true : false; |
| te.setAttributes(true); |
| te.attributes |= (uint64_t) attr << 56; |
| } |
| |
| void |
| TableWalker::memAttrsAArch64(ThreadContext *tc, TlbEntry &te, |
| LongDescriptor &lDescriptor) |
| { |
| uint8_t attr; |
| uint8_t attr_hi; |
| uint8_t attr_lo; |
| uint8_t sh = lDescriptor.sh(); |
| |
| if (isStage2) { |
| attr = lDescriptor.memAttr(); |
| uint8_t attr_hi = (attr >> 2) & 0x3; |
| uint8_t attr_lo = attr & 0x3; |
| |
| DPRINTF(TLBVerbose, "memAttrsAArch64 MemAttr:%#x sh:%#x\n", attr, sh); |
| |
| if (attr_hi == 0) { |
| te.mtype = attr_lo == 0 ? TlbEntry::MemoryType::StronglyOrdered |
| : TlbEntry::MemoryType::Device; |
| te.outerAttrs = 0; |
| te.innerAttrs = attr_lo == 0 ? 1 : 3; |
| te.nonCacheable = true; |
| } else { |
| te.mtype = TlbEntry::MemoryType::Normal; |
| te.outerAttrs = attr_hi == 1 ? 0 : |
| attr_hi == 2 ? 2 : 1; |
| te.innerAttrs = attr_lo == 1 ? 0 : |
| attr_lo == 2 ? 6 : 5; |
| // Treat write-through memory as uncacheable, this is safe |
| // but for performance reasons not optimal. |
| te.nonCacheable = (attr_hi == 1) || (attr_hi == 2) || |
| (attr_lo == 1) || (attr_lo == 2); |
| } |
| } else { |
| uint8_t attrIndx = lDescriptor.attrIndx(); |
| |
| DPRINTF(TLBVerbose, "memAttrsAArch64 AttrIndx:%#x sh:%#x\n", attrIndx, sh); |
| |
| // Select MAIR |
| uint64_t mair; |
| switch (currState->el) { |
| case EL0: |
| case EL1: |
| mair = tc->readMiscReg(MISCREG_MAIR_EL1); |
| break; |
| case EL2: |
| mair = tc->readMiscReg(MISCREG_MAIR_EL2); |
| break; |
| case EL3: |
| mair = tc->readMiscReg(MISCREG_MAIR_EL3); |
| break; |
| default: |
| panic("Invalid exception level"); |
| break; |
| } |
| |
| // Select attributes |
| attr = bits(mair, 8 * attrIndx + 7, 8 * attrIndx); |
| attr_lo = bits(attr, 3, 0); |
| attr_hi = bits(attr, 7, 4); |
| |
| // Memory type |
| te.mtype = attr_hi == 0 ? TlbEntry::MemoryType::Device : TlbEntry::MemoryType::Normal; |
| |
| // Cacheability |
| te.nonCacheable = false; |
| if (te.mtype == TlbEntry::MemoryType::Device) { // Device memory |
| te.nonCacheable = true; |
| } |
| // Treat write-through memory as uncacheable, this is safe |
| // but for performance reasons not optimal. |
| switch (attr_hi) { |
| case 0x1 ... 0x3: // Normal Memory, Outer Write-through transient |
| case 0x4: // Normal memory, Outer Non-cacheable |
| case 0x8 ... 0xb: // Normal Memory, Outer Write-through non-transient |
| te.nonCacheable = true; |
| } |
| switch (attr_lo) { |
| case 0x1 ... 0x3: // Normal Memory, Inner Write-through transient |
| case 0x9 ... 0xb: // Normal Memory, Inner Write-through non-transient |
| warn_if(!attr_hi, "Unpredictable behavior"); |
| M5_FALLTHROUGH; |
| case 0x4: // Device-nGnRE memory or |
| // Normal memory, Inner Non-cacheable |
| case 0x8: // Device-nGRE memory or |
| // Normal memory, Inner Write-through non-transient |
| te.nonCacheable = true; |
| } |
| |
| te.shareable = sh == 2; |
| te.outerShareable = (sh & 0x2) ? true : false; |
| // Attributes formatted according to the 64-bit PAR |
| te.attributes = ((uint64_t) attr << 56) | |
| (1 << 11) | // LPAE bit |
| (te.ns << 9) | // NS bit |
| (sh << 7); |
| } |
| } |
| |
| void |
| TableWalker::doL1Descriptor() |
| { |
| if (currState->fault != NoFault) { |
| return; |
| } |
| |
| currState->l1Desc.data = htog(currState->l1Desc.data, |
| byteOrder(currState->tc)); |
| |
| DPRINTF(TLB, "L1 descriptor for %#x is %#x\n", |
| currState->vaddr_tainted, currState->l1Desc.data); |
| TlbEntry te; |
| |
| const bool is_atomic = currState->req->isAtomic(); |
| |
| switch (currState->l1Desc.type()) { |
| case L1Descriptor::Ignore: |
| case L1Descriptor::Reserved: |
| if (!currState->timing) { |
| currState->tc = NULL; |
| currState->req = NULL; |
| } |
| DPRINTF(TLB, "L1 Descriptor Reserved/Ignore, causing fault\n"); |
| if (currState->isFetch) |
| currState->fault = |
| std::make_shared<PrefetchAbort>( |
| currState->vaddr_tainted, |
| ArmFault::TranslationLL + L1, |
| isStage2, |
| ArmFault::VmsaTran); |
| else |
| currState->fault = |
| std::make_shared<DataAbort>( |
| currState->vaddr_tainted, |
| TlbEntry::DomainType::NoAccess, |
| is_atomic ? false : currState->isWrite, |
| ArmFault::TranslationLL + L1, isStage2, |
| ArmFault::VmsaTran); |
| return; |
| case L1Descriptor::Section: |
| if (currState->sctlr.afe && bits(currState->l1Desc.ap(), 0) == 0) { |
| /** @todo: check sctlr.ha (bit[17]) if Hardware Access Flag is |
| * enabled if set, do l1.Desc.setAp0() instead of generating |
| * AccessFlag0 |
| */ |
| |
| currState->fault = std::make_shared<DataAbort>( |
| currState->vaddr_tainted, |
| currState->l1Desc.domain(), |
| is_atomic ? false : currState->isWrite, |
| ArmFault::AccessFlagLL + L1, |
| isStage2, |
| ArmFault::VmsaTran); |
| } |
| if (currState->l1Desc.supersection()) { |
| panic("Haven't implemented supersections\n"); |
| } |
| insertTableEntry(currState->l1Desc, false); |
| return; |
| case L1Descriptor::PageTable: |
| { |
| Addr l2desc_addr; |
| l2desc_addr = currState->l1Desc.l2Addr() | |
| (bits(currState->vaddr, 19, 12) << 2); |
| DPRINTF(TLB, "L1 descriptor points to page table at: %#x (%s)\n", |
| l2desc_addr, currState->isSecure ? "s" : "ns"); |
| |
| // Trickbox address check |
| currState->fault = testWalk(l2desc_addr, sizeof(uint32_t), |
| currState->l1Desc.domain(), L2); |
| |
| if (currState->fault) { |
| if (!currState->timing) { |
| currState->tc = NULL; |
| currState->req = NULL; |
| } |
| return; |
| } |
| |
| Request::Flags flag = Request::PT_WALK; |
| |
| if (currState->sctlr.c == 0 || currState->isUncacheable) { |
| flag.set(Request::UNCACHEABLE); |
| } |
| |
| if (currState->isSecure) |
| flag.set(Request::SECURE); |
| |
| bool delayed; |
| delayed = fetchDescriptor(l2desc_addr, |
| (uint8_t*)&currState->l2Desc.data, |
| sizeof(uint32_t), flag, -1, &doL2DescEvent, |
| &TableWalker::doL2Descriptor); |
| if (delayed) { |
| currState->delayed = true; |
| } |
| |
| return; |
| } |
| default: |
| panic("A new type in a 2 bit field?\n"); |
| } |
| } |
| |
| Fault |
| TableWalker::generateLongDescFault(ArmFault::FaultSource src) |
| { |
| if (currState->isFetch) { |
| return std::make_shared<PrefetchAbort>( |
| currState->vaddr_tainted, |
| src + currState->longDesc.lookupLevel, |
| isStage2, |
| ArmFault::LpaeTran); |
| } else { |
| return std::make_shared<DataAbort>( |
| currState->vaddr_tainted, |
| TlbEntry::DomainType::NoAccess, |
| currState->req->isAtomic() ? false : currState->isWrite, |
| src + currState->longDesc.lookupLevel, |
| isStage2, |
| ArmFault::LpaeTran); |
| } |
| } |
| |
| void |
| TableWalker::doLongDescriptor() |
| { |
| if (currState->fault != NoFault) { |
| return; |
| } |
| |
| currState->longDesc.data = htog(currState->longDesc.data, |
| byteOrder(currState->tc)); |
| |
| DPRINTF(TLB, "L%d descriptor for %#llx is %#llx (%s)\n", |
| currState->longDesc.lookupLevel, currState->vaddr_tainted, |
| currState->longDesc.data, |
| currState->aarch64 ? "AArch64" : "long-desc."); |
| |
| if ((currState->longDesc.type() == LongDescriptor::Block) || |
| (currState->longDesc.type() == LongDescriptor::Page)) { |
| DPRINTF(TLBVerbose, "Analyzing L%d descriptor: %#llx, pxn: %d, " |
| "xn: %d, ap: %d, af: %d, type: %d\n", |
| currState->longDesc.lookupLevel, |
| currState->longDesc.data, |
| currState->longDesc.pxn(), |
| currState->longDesc.xn(), |
| currState->longDesc.ap(), |
| currState->longDesc.af(), |
| currState->longDesc.type()); |
| } else { |
| DPRINTF(TLBVerbose, "Analyzing L%d descriptor: %#llx, type: %d\n", |
| currState->longDesc.lookupLevel, |
| currState->longDesc.data, |
| currState->longDesc.type()); |
| } |
| |
| TlbEntry te; |
| |
| switch (currState->longDesc.type()) { |
| case LongDescriptor::Invalid: |
| DPRINTF(TLB, "L%d descriptor Invalid, causing fault type %d\n", |
| currState->longDesc.lookupLevel, |
| ArmFault::TranslationLL + currState->longDesc.lookupLevel); |
| |
| currState->fault = generateLongDescFault(ArmFault::TranslationLL); |
| if (!currState->timing) { |
| currState->tc = NULL; |
| currState->req = NULL; |
| } |
| return; |
| |
| case LongDescriptor::Block: |
| case LongDescriptor::Page: |
| { |
| auto fault_source = ArmFault::FaultSourceInvalid; |
| // Check for address size fault |
| if (checkAddrSizeFaultAArch64( |
| mbits(currState->longDesc.data, MaxPhysAddrRange - 1, |
| currState->longDesc.offsetBits()), |
| currState->physAddrRange)) { |
| |
| DPRINTF(TLB, "L%d descriptor causing Address Size Fault\n", |
| currState->longDesc.lookupLevel); |
| fault_source = ArmFault::AddressSizeLL; |
| |
| // Check for access fault |
| } else if (currState->longDesc.af() == 0) { |
| |
| DPRINTF(TLB, "L%d descriptor causing Access Fault\n", |
| currState->longDesc.lookupLevel); |
| fault_source = ArmFault::AccessFlagLL; |
| } |
| |
| if (fault_source != ArmFault::FaultSourceInvalid) { |
| currState->fault = generateLongDescFault(fault_source); |
| } else { |
| insertTableEntry(currState->longDesc, true); |
| } |
| } |
| return; |
| case LongDescriptor::Table: |
| { |
| // Set hierarchical permission flags |
| currState->secureLookup = currState->secureLookup && |
| currState->longDesc.secureTable(); |
| currState->rwTable = currState->rwTable && |
| (currState->longDesc.rwTable() || currState->hpd); |
| currState->userTable = currState->userTable && |
| (currState->longDesc.userTable() || currState->hpd); |
| currState->xnTable = currState->xnTable || |
| (currState->longDesc.xnTable() && !currState->hpd); |
| currState->pxnTable = currState->pxnTable || |
| (currState->longDesc.pxnTable() && !currState->hpd); |
| |
| // Set up next level lookup |
| Addr next_desc_addr = currState->longDesc.nextDescAddr( |
| currState->vaddr); |
| |
| DPRINTF(TLB, "L%d descriptor points to L%d descriptor at: %#x (%s)\n", |
| currState->longDesc.lookupLevel, |
| currState->longDesc.lookupLevel + 1, |
| next_desc_addr, |
| currState->secureLookup ? "s" : "ns"); |
| |
| // Check for address size fault |
| if (currState->aarch64 && checkAddrSizeFaultAArch64( |
| next_desc_addr, currState->physAddrRange)) { |
| DPRINTF(TLB, "L%d descriptor causing Address Size Fault\n", |
| currState->longDesc.lookupLevel); |
| |
| currState->fault = generateLongDescFault( |
| ArmFault::AddressSizeLL); |
| return; |
| } |
| |
| // Trickbox address check |
| currState->fault = testWalk( |
| next_desc_addr, sizeof(uint64_t), TlbEntry::DomainType::Client, |
| toLookupLevel(currState->longDesc.lookupLevel +1)); |
| |
| if (currState->fault) { |
| if (!currState->timing) { |
| currState->tc = NULL; |
| currState->req = NULL; |
| } |
| return; |
| } |
| |
| Request::Flags flag = Request::PT_WALK; |
| if (currState->secureLookup) |
| flag.set(Request::SECURE); |
| |
| if (currState->sctlr.c == 0 || currState->isUncacheable) { |
| flag.set(Request::UNCACHEABLE); |
| } |
| |
| LookupLevel L = currState->longDesc.lookupLevel = |
| (LookupLevel) (currState->longDesc.lookupLevel + 1); |
| Event *event = NULL; |
| switch (L) { |
| case L1: |
| assert(currState->aarch64); |
| case L2: |
| case L3: |
| event = LongDescEventByLevel[L]; |
| break; |
| default: |
| panic("Wrong lookup level in table walk\n"); |
| break; |
| } |
| |
| bool delayed; |
| delayed = fetchDescriptor(next_desc_addr, (uint8_t*)&currState->longDesc.data, |
| sizeof(uint64_t), flag, -1, event, |
| &TableWalker::doLongDescriptor); |
| if (delayed) { |
| currState->delayed = true; |
| } |
| } |
| return; |
| default: |
| panic("A new type in a 2 bit field?\n"); |
| } |
| } |
| |
| void |
| TableWalker::doL2Descriptor() |
| { |
| if (currState->fault != NoFault) { |
| return; |
| } |
| |
| currState->l2Desc.data = htog(currState->l2Desc.data, |
| byteOrder(currState->tc)); |
| |
| DPRINTF(TLB, "L2 descriptor for %#x is %#x\n", |
| currState->vaddr_tainted, currState->l2Desc.data); |
| TlbEntry te; |
| |
| const bool is_atomic = currState->req->isAtomic(); |
| |
| if (currState->l2Desc.invalid()) { |
| DPRINTF(TLB, "L2 descriptor invalid, causing fault\n"); |
| if (!currState->timing) { |
| currState->tc = NULL; |
| currState->req = NULL; |
| } |
| if (currState->isFetch) |
| currState->fault = std::make_shared<PrefetchAbort>( |
| currState->vaddr_tainted, |
| ArmFault::TranslationLL + L2, |
| isStage2, |
| ArmFault::VmsaTran); |
| else |
| currState->fault = std::make_shared<DataAbort>( |
| currState->vaddr_tainted, currState->l1Desc.domain(), |
| is_atomic ? false : currState->isWrite, |
| ArmFault::TranslationLL + L2, |
| isStage2, |
| ArmFault::VmsaTran); |
| return; |
| } |
| |
| if (currState->sctlr.afe && bits(currState->l2Desc.ap(), 0) == 0) { |
| /** @todo: check sctlr.ha (bit[17]) if Hardware Access Flag is enabled |
| * if set, do l2.Desc.setAp0() instead of generating AccessFlag0 |
| */ |
| DPRINTF(TLB, "Generating access fault at L2, afe: %d, ap: %d\n", |
| currState->sctlr.afe, currState->l2Desc.ap()); |
| |
| currState->fault = std::make_shared<DataAbort>( |
| currState->vaddr_tainted, |
| TlbEntry::DomainType::NoAccess, |
| is_atomic ? false : currState->isWrite, |
| ArmFault::AccessFlagLL + L2, isStage2, |
| ArmFault::VmsaTran); |
| } |
| |
| insertTableEntry(currState->l2Desc, false); |
| } |
| |
| void |
| TableWalker::doL1DescriptorWrapper() |
| { |
| currState = stateQueues[L1].front(); |
| currState->delayed = false; |
| // if there's a stage2 translation object we don't need it any more |
| if (currState->stage2Tran) { |
| delete currState->stage2Tran; |
| currState->stage2Tran = NULL; |
| } |
| |
| |
| DPRINTF(TLBVerbose, "L1 Desc object host addr: %p\n",&currState->l1Desc.data); |
| DPRINTF(TLBVerbose, "L1 Desc object data: %08x\n",currState->l1Desc.data); |
| |
| DPRINTF(TLBVerbose, "calling doL1Descriptor for vaddr:%#x\n", currState->vaddr_tainted); |
| doL1Descriptor(); |
| |
| stateQueues[L1].pop_front(); |
| // Check if fault was generated |
| if (currState->fault != NoFault) { |
| currState->transState->finish(currState->fault, currState->req, |
| currState->tc, currState->mode); |
| statWalksShortTerminatedAtLevel[0]++; |
| |
| pending = false; |
| nextWalk(currState->tc); |
| |
| currState->req = NULL; |
| currState->tc = NULL; |
| currState->delayed = false; |
| delete currState; |
| } |
| else if (!currState->delayed) { |
| // delay is not set so there is no L2 to do |
| // Don't finish the translation if a stage 2 look up is underway |
| statWalkServiceTime.sample(curTick() - currState->startTime); |
| DPRINTF(TLBVerbose, "calling translateTiming again\n"); |
| tlb->translateTiming(currState->req, currState->tc, |
| currState->transState, currState->mode); |
| statWalksShortTerminatedAtLevel[0]++; |
| |
| pending = false; |
| nextWalk(currState->tc); |
| |
| currState->req = NULL; |
| currState->tc = NULL; |
| currState->delayed = false; |
| delete currState; |
| } else { |
| // need to do L2 descriptor |
| stateQueues[L2].push_back(currState); |
| } |
| currState = NULL; |
| } |
| |
| void |
| TableWalker::doL2DescriptorWrapper() |
| { |
| currState = stateQueues[L2].front(); |
| assert(currState->delayed); |
| // if there's a stage2 translation object we don't need it any more |
| if (currState->stage2Tran) { |
| delete currState->stage2Tran; |
| currState->stage2Tran = NULL; |
| } |
| |
| DPRINTF(TLBVerbose, "calling doL2Descriptor for vaddr:%#x\n", |
| currState->vaddr_tainted); |
| doL2Descriptor(); |
| |
| // Check if fault was generated |
| if (currState->fault != NoFault) { |
| currState->transState->finish(currState->fault, currState->req, |
| currState->tc, currState->mode); |
| statWalksShortTerminatedAtLevel[1]++; |
| } else { |
| statWalkServiceTime.sample(curTick() - currState->startTime); |
| DPRINTF(TLBVerbose, "calling translateTiming again\n"); |
| tlb->translateTiming(currState->req, currState->tc, |
| currState->transState, currState->mode); |
| statWalksShortTerminatedAtLevel[1]++; |
| } |
| |
| |
| stateQueues[L2].pop_front(); |
| pending = false; |
| nextWalk(currState->tc); |
| |
| currState->req = NULL; |
| currState->tc = NULL; |
| currState->delayed = false; |
| |
| delete currState; |
| currState = NULL; |
| } |
| |
| void |
| TableWalker::doL0LongDescriptorWrapper() |
| { |
| doLongDescriptorWrapper(L0); |
| } |
| |
| void |
| TableWalker::doL1LongDescriptorWrapper() |
| { |
| doLongDescriptorWrapper(L1); |
| } |
| |
| void |
| TableWalker::doL2LongDescriptorWrapper() |
| { |
| doLongDescriptorWrapper(L2); |
| } |
| |
| void |
| TableWalker::doL3LongDescriptorWrapper() |
| { |
| doLongDescriptorWrapper(L3); |
| } |
| |
| void |
| TableWalker::doLongDescriptorWrapper(LookupLevel curr_lookup_level) |
| { |
| currState = stateQueues[curr_lookup_level].front(); |
| assert(curr_lookup_level == currState->longDesc.lookupLevel); |
| currState->delayed = false; |
| |
| // if there's a stage2 translation object we don't need it any more |
| if (currState->stage2Tran) { |
| delete currState->stage2Tran; |
| currState->stage2Tran = NULL; |
| } |
| |
| DPRINTF(TLBVerbose, "calling doLongDescriptor for vaddr:%#x\n", |
| currState->vaddr_tainted); |
| doLongDescriptor(); |
| |
| stateQueues[curr_lookup_level].pop_front(); |
| |
| if (currState->fault != NoFault) { |
| // A fault was generated |
| currState->transState->finish(currState->fault, currState->req, |
| currState->tc, currState->mode); |
| |
| pending = false; |
| nextWalk(currState->tc); |
| |
| currState->req = NULL; |
| currState->tc = NULL; |
| currState->delayed = false; |
| delete currState; |
| } else if (!currState->delayed) { |
| // No additional lookups required |
| DPRINTF(TLBVerbose, "calling translateTiming again\n"); |
| statWalkServiceTime.sample(curTick() - currState->startTime); |
| tlb->translateTiming(currState->req, currState->tc, |
| currState->transState, currState->mode); |
| statWalksLongTerminatedAtLevel[(unsigned) curr_lookup_level]++; |
| |
| pending = false; |
| nextWalk(currState->tc); |
| |
| currState->req = NULL; |
| currState->tc = NULL; |
| currState->delayed = false; |
| delete currState; |
| } else { |
| if (curr_lookup_level >= MAX_LOOKUP_LEVELS - 1) |
| panic("Max. number of lookups already reached in table walk\n"); |
| // Need to perform additional lookups |
| stateQueues[currState->longDesc.lookupLevel].push_back(currState); |
| } |
| currState = NULL; |
| } |
| |
| |
| void |
| TableWalker::nextWalk(ThreadContext *tc) |
| { |
| if (pendingQueue.size()) |
| schedule(doProcessEvent, clockEdge(Cycles(1))); |
| else |
| completeDrain(); |
| } |
| |
| bool |
| TableWalker::fetchDescriptor(Addr descAddr, uint8_t *data, int numBytes, |
| Request::Flags flags, int queueIndex, Event *event, |
| void (TableWalker::*doDescriptor)()) |
| { |
| bool isTiming = currState->timing; |
| |
| DPRINTF(TLBVerbose, "Fetching descriptor at address: 0x%x stage2Req: %d\n", |
| descAddr, currState->stage2Req); |
| |
| // If this translation has a stage 2 then we know descAddr is an IPA and |
| // needs to be translated before we can access the page table. Do that |
| // check here. |
| if (currState->stage2Req) { |
| Fault fault; |
| |
| if (isTiming) { |
| Stage2MMU::Stage2Translation *tran = new |
| Stage2MMU::Stage2Translation(*stage2Mmu, data, event, |
| currState->vaddr); |
| currState->stage2Tran = tran; |
| stage2Mmu->readDataTimed(currState->tc, descAddr, tran, numBytes, |
| flags); |
| fault = tran->fault; |
| } else { |
| fault = stage2Mmu->readDataUntimed(currState->tc, |
| currState->vaddr, descAddr, data, numBytes, flags, |
| currState->functional); |
| } |
| |
| if (fault != NoFault) { |
| currState->fault = fault; |
| } |
| if (isTiming) { |
| if (queueIndex >= 0) { |
| DPRINTF(TLBVerbose, "Adding to walker fifo: queue size before adding: %d\n", |
| stateQueues[queueIndex].size()); |
| stateQueues[queueIndex].push_back(currState); |
| currState = NULL; |
| } |
| } else { |
| (this->*doDescriptor)(); |
| } |
| } else { |
| if (isTiming) { |
| port->dmaAction(MemCmd::ReadReq, descAddr, numBytes, event, data, |
| currState->tc->getCpuPtr()->clockPeriod(),flags); |
| if (queueIndex >= 0) { |
| DPRINTF(TLBVerbose, "Adding to walker fifo: queue size before adding: %d\n", |
| stateQueues[queueIndex].size()); |
| stateQueues[queueIndex].push_back(currState); |
| currState = NULL; |
| } |
| } else if (!currState->functional) { |
| port->dmaAction(MemCmd::ReadReq, descAddr, numBytes, NULL, data, |
| currState->tc->getCpuPtr()->clockPeriod(), flags); |
| (this->*doDescriptor)(); |
| } else { |
| RequestPtr req = std::make_shared<Request>( |
| descAddr, numBytes, flags, masterId); |
| |
| req->taskId(ContextSwitchTaskId::DMA); |
| PacketPtr pkt = new Packet(req, MemCmd::ReadReq); |
| pkt->dataStatic(data); |
| port->sendFunctional(pkt); |
| (this->*doDescriptor)(); |
| delete pkt; |
| } |
| } |
| return (isTiming); |
| } |
| |
| void |
| TableWalker::insertTableEntry(DescriptorBase &descriptor, bool longDescriptor) |
| { |
| TlbEntry te; |
| |
| // Create and fill a new page table entry |
| te.valid = true; |
| te.longDescFormat = longDescriptor; |
| te.isHyp = currState->isHyp; |
| te.asid = currState->asid; |
| te.vmid = currState->vmid; |
| te.N = descriptor.offsetBits(); |
| te.vpn = currState->vaddr >> te.N; |
| te.size = (1<<te.N) - 1; |
| te.pfn = descriptor.pfn(); |
| te.domain = descriptor.domain(); |
| te.lookupLevel = descriptor.lookupLevel; |
| te.ns = !descriptor.secure(haveSecurity, currState) || isStage2; |
| te.nstid = !currState->isSecure; |
| te.xn = descriptor.xn(); |
| if (currState->aarch64) |
| te.el = currState->el; |
| else |
| te.el = EL1; |
| |
| statPageSizes[pageSizeNtoStatBin(te.N)]++; |
| statRequestOrigin[COMPLETED][currState->isFetch]++; |
| |
| // ASID has no meaning for stage 2 TLB entries, so mark all stage 2 entries |
| // as global |
| te.global = descriptor.global(currState) || isStage2; |
| if (longDescriptor) { |
| LongDescriptor lDescriptor = |
| dynamic_cast<LongDescriptor &>(descriptor); |
| |
| te.xn |= currState->xnTable; |
| te.pxn = currState->pxnTable || lDescriptor.pxn(); |
| if (isStage2) { |
| // this is actually the HAP field, but its stored in the same bit |
| // possitions as the AP field in a stage 1 translation. |
| te.hap = lDescriptor.ap(); |
| } else { |
| te.ap = ((!currState->rwTable || descriptor.ap() >> 1) << 1) | |
| (currState->userTable && (descriptor.ap() & 0x1)); |
| } |
| if (currState->aarch64) |
| memAttrsAArch64(currState->tc, te, lDescriptor); |
| else |
| memAttrsLPAE(currState->tc, te, lDescriptor); |
| } else { |
| te.ap = descriptor.ap(); |
| memAttrs(currState->tc, te, currState->sctlr, descriptor.texcb(), |
| descriptor.shareable()); |
| } |
| |
| // Debug output |
| DPRINTF(TLB, descriptor.dbgHeader().c_str()); |
| DPRINTF(TLB, " - N:%d pfn:%#x size:%#x global:%d valid:%d\n", |
| te.N, te.pfn, te.size, te.global, te.valid); |
| DPRINTF(TLB, " - vpn:%#x xn:%d pxn:%d ap:%d domain:%d asid:%d " |
| "vmid:%d hyp:%d nc:%d ns:%d\n", te.vpn, te.xn, te.pxn, |
| te.ap, static_cast<uint8_t>(te.domain), te.asid, te.vmid, te.isHyp, |
| te.nonCacheable, te.ns); |
| DPRINTF(TLB, " - domain from L%d desc:%d data:%#x\n", |
| descriptor.lookupLevel, static_cast<uint8_t>(descriptor.domain()), |
| descriptor.getRawData()); |
| |
| // Insert the entry into the TLB |
| tlb->insert(currState->vaddr, te); |
| if (!currState->timing) { |
| currState->tc = NULL; |
| currState->req = NULL; |
| } |
| } |
| |
| ArmISA::TableWalker * |
| ArmTableWalkerParams::create() |
| { |
| return new ArmISA::TableWalker(this); |
| } |
| |
| LookupLevel |
| TableWalker::toLookupLevel(uint8_t lookup_level_as_int) |
| { |
| switch (lookup_level_as_int) { |
| case L1: |
| return L1; |
| case L2: |
| return L2; |
| case L3: |
| return L3; |
| default: |
| panic("Invalid lookup level conversion"); |
| } |
| } |
| |
| /* this method keeps track of the table walker queue's residency, so |
| * needs to be called whenever requests start and complete. */ |
| void |
| TableWalker::pendingChange() |
| { |
| unsigned n = pendingQueue.size(); |
| if ((currState != NULL) && (currState != pendingQueue.front())) { |
| ++n; |
| } |
| |
| if (n != pendingReqs) { |
| Tick now = curTick(); |
| statPendingWalks.sample(pendingReqs, now - pendingChangeTick); |
| pendingReqs = n; |
| pendingChangeTick = now; |
| } |
| } |
| |
| Fault |
| TableWalker::testWalk(Addr pa, Addr size, TlbEntry::DomainType domain, |
| LookupLevel lookup_level) |
| { |
| return tlb->testWalk(pa, size, currState->vaddr, currState->isSecure, |
| currState->mode, domain, lookup_level); |
| } |
| |
| |
| uint8_t |
| TableWalker::pageSizeNtoStatBin(uint8_t N) |
| { |
| /* for statPageSizes */ |
| switch(N) { |
| case 12: return 0; // 4K |
| case 14: return 1; // 16K (using 16K granule in v8-64) |
| case 16: return 2; // 64K |
| case 20: return 3; // 1M |
| case 21: return 4; // 2M-LPAE |
| case 24: return 5; // 16M |
| case 25: return 6; // 32M (using 16K granule in v8-64) |
| case 29: return 7; // 512M (using 64K granule in v8-64) |
| case 30: return 8; // 1G-LPAE |
| default: |
| panic("unknown page size"); |
| return 255; |
| } |
| } |
| |
| void |
| TableWalker::regStats() |
| { |
| ClockedObject::regStats(); |
| |
| statWalks |
| .name(name() + ".walks") |
| .desc("Table walker walks requested") |
| ; |
| |
| statWalksShortDescriptor |
| .name(name() + ".walksShort") |
| .desc("Table walker walks initiated with short descriptors") |
| .flags(Stats::nozero) |
| ; |
| |
| statWalksLongDescriptor |
| .name(name() + ".walksLong") |
| .desc("Table walker walks initiated with long descriptors") |
| .flags(Stats::nozero) |
| ; |
| |
| statWalksShortTerminatedAtLevel |
| .init(2) |
| .name(name() + ".walksShortTerminationLevel") |
| .desc("Level at which table walker walks " |
| "with short descriptors terminate") |
| .flags(Stats::nozero) |
| ; |
| statWalksShortTerminatedAtLevel.subname(0, "Level1"); |
| statWalksShortTerminatedAtLevel.subname(1, "Level2"); |
| |
| statWalksLongTerminatedAtLevel |
| .init(4) |
| .name(name() + ".walksLongTerminationLevel") |
| .desc("Level at which table walker walks " |
| "with long descriptors terminate") |
| .flags(Stats::nozero) |
| ; |
| statWalksLongTerminatedAtLevel.subname(0, "Level0"); |
| statWalksLongTerminatedAtLevel.subname(1, "Level1"); |
| statWalksLongTerminatedAtLevel.subname(2, "Level2"); |
| statWalksLongTerminatedAtLevel.subname(3, "Level3"); |
| |
| statSquashedBefore |
| .name(name() + ".walksSquashedBefore") |
| .desc("Table walks squashed before starting") |
| .flags(Stats::nozero) |
| ; |
| |
| statSquashedAfter |
| .name(name() + ".walksSquashedAfter") |
| .desc("Table walks squashed after completion") |
| .flags(Stats::nozero) |
| ; |
| |
| statWalkWaitTime |
| .init(16) |
| .name(name() + ".walkWaitTime") |
| .desc("Table walker wait (enqueue to first request) latency") |
| .flags(Stats::pdf | Stats::nozero | Stats::nonan) |
| ; |
| |
| statWalkServiceTime |
| .init(16) |
| .name(name() + ".walkCompletionTime") |
| .desc("Table walker service (enqueue to completion) latency") |
| .flags(Stats::pdf | Stats::nozero | Stats::nonan) |
| ; |
| |
| statPendingWalks |
| .init(16) |
| .name(name() + ".walksPending") |
| .desc("Table walker pending requests distribution") |
| .flags(Stats::pdf | Stats::dist | Stats::nozero | Stats::nonan) |
| ; |
| |
| statPageSizes // see DDI 0487A D4-1661 |
| .init(9) |
| .name(name() + ".walkPageSizes") |
| .desc("Table walker page sizes translated") |
| .flags(Stats::total | Stats::pdf | Stats::dist | Stats::nozero) |
| ; |
| statPageSizes.subname(0, "4K"); |
| statPageSizes.subname(1, "16K"); |
| statPageSizes.subname(2, "64K"); |
| statPageSizes.subname(3, "1M"); |
| statPageSizes.subname(4, "2M"); |
| statPageSizes.subname(5, "16M"); |
| statPageSizes.subname(6, "32M"); |
| statPageSizes.subname(7, "512M"); |
| statPageSizes.subname(8, "1G"); |
| |
| statRequestOrigin |
| .init(2,2) // Instruction/Data, requests/completed |
| .name(name() + ".walkRequestOrigin") |
| .desc("Table walker requests started/completed, data/inst") |
| .flags(Stats::total) |
| ; |
| statRequestOrigin.subname(0,"Requested"); |
| statRequestOrigin.subname(1,"Completed"); |
| statRequestOrigin.ysubname(0,"Data"); |
| statRequestOrigin.ysubname(1,"Inst"); |
| } |