/* * Copyright (c) 2010-2016 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: Ali Saidi * Giacomo Gabrielli */ #ifndef __ARCH_ARM_TABLE_WALKER_HH__ #define __ARCH_ARM_TABLE_WALKER_HH__ #include #include "arch/arm/miscregs.hh" #include "arch/arm/system.hh" #include "arch/arm/tlb.hh" #include "mem/request.hh" #include "params/ArmTableWalker.hh" #include "sim/eventq.hh" class ThreadContext; class DmaPort; namespace ArmISA { class Translation; class TLB; class Stage2MMU; class TableWalker : public MemObject { public: class WalkerState; class DescriptorBase { public: /** Current lookup level for this descriptor */ LookupLevel lookupLevel; virtual Addr pfn() const = 0; virtual TlbEntry::DomainType domain() const = 0; virtual bool xn() const = 0; virtual uint8_t ap() const = 0; virtual bool global(WalkerState *currState) const = 0; virtual uint8_t offsetBits() const = 0; virtual bool secure(bool have_security, WalkerState *currState) const = 0; virtual std::string dbgHeader() const = 0; virtual uint64_t getRawData() const = 0; virtual uint8_t texcb() const { panic("texcb() not implemented for this class\n"); } virtual bool shareable() const { panic("shareable() not implemented for this class\n"); } }; class L1Descriptor : public DescriptorBase { public: /** Type of page table entry ARM DDI 0406B: B3-8*/ enum EntryType { Ignore, PageTable, Section, Reserved }; /** The raw bits of the entry */ uint32_t data; /** This entry has been modified (access flag set) and needs to be * written back to memory */ bool _dirty; /** Default ctor */ L1Descriptor() : data(0), _dirty(false) { lookupLevel = L1; } virtual uint64_t getRawData() const { return (data); } virtual std::string dbgHeader() const { return "Inserting Section Descriptor into TLB\n"; } virtual uint8_t offsetBits() const { return 20; } EntryType type() const { return (EntryType)(data & 0x3); } /** Is the page a Supersection (16MB)?*/ bool supersection() const { return bits(data, 18); } /** Return the physcal address of the entry, bits in position*/ Addr paddr() const { if (supersection()) panic("Super sections not implemented\n"); return mbits(data, 31, 20); } /** Return the physcal address of the entry, bits in position*/ Addr paddr(Addr va) const { if (supersection()) panic("Super sections not implemented\n"); return mbits(data, 31, 20) | mbits(va, 19, 0); } /** Return the physical frame, bits shifted right */ Addr pfn() const { if (supersection()) panic("Super sections not implemented\n"); return bits(data, 31, 20); } /** Is the translation global (no asid used)? */ bool global(WalkerState *currState) const { return !bits(data, 17); } /** Is the translation not allow execution? */ bool xn() const { return bits(data, 4); } /** Three bit access protection flags */ uint8_t ap() const { return (bits(data, 15) << 2) | bits(data, 11, 10); } /** Domain Client/Manager: ARM DDI 0406B: B3-31 */ TlbEntry::DomainType domain() const { return static_cast(bits(data, 8, 5)); } /** Address of L2 descriptor if it exists */ Addr l2Addr() const { return mbits(data, 31, 10); } /** Memory region attributes: ARM DDI 0406B: B3-32. * These bits are largly ignored by M5 and only used to * provide the illusion that the memory system cares about * anything but cachable vs. uncachable. */ uint8_t texcb() const { return bits(data, 2) | bits(data, 3) << 1 | bits(data, 14, 12) << 2; } /** If the section is shareable. See texcb() comment. */ bool shareable() const { return bits(data, 16); } /** Set access flag that this entry has been touched. Mark * the entry as requiring a writeback, in the future. */ void setAp0() { data |= 1 << 10; _dirty = true; } /** This entry needs to be written back to memory */ bool dirty() const { return _dirty; } /** * Returns true if this entry targets the secure physical address * map. */ bool secure(bool have_security, WalkerState *currState) const { if (have_security) { if (type() == PageTable) return !bits(data, 3); else return !bits(data, 19); } return false; } }; /** Level 2 page table descriptor */ class L2Descriptor : public DescriptorBase { public: /** The raw bits of the entry. */ uint32_t data; L1Descriptor *l1Parent; /** This entry has been modified (access flag set) and needs to be * written back to memory */ bool _dirty; /** Default ctor */ L2Descriptor() : data(0), l1Parent(nullptr), _dirty(false) { lookupLevel = L2; } L2Descriptor(L1Descriptor &parent) : data(0), l1Parent(&parent), _dirty(false) { lookupLevel = L2; } virtual uint64_t getRawData() const { return (data); } virtual std::string dbgHeader() const { return "Inserting L2 Descriptor into TLB\n"; } virtual TlbEntry::DomainType domain() const { return l1Parent->domain(); } bool secure(bool have_security, WalkerState *currState) const { return l1Parent->secure(have_security, currState); } virtual uint8_t offsetBits() const { return large() ? 16 : 12; } /** Is the entry invalid */ bool invalid() const { return bits(data, 1, 0) == 0; } /** What is the size of the mapping? */ bool large() const { return bits(data, 1) == 0; } /** Is execution allowed on this mapping? */ bool xn() const { return large() ? bits(data, 15) : bits(data, 0); } /** Is the translation global (no asid used)? */ bool global(WalkerState *currState) const { return !bits(data, 11); } /** Three bit access protection flags */ uint8_t ap() const { return bits(data, 5, 4) | (bits(data, 9) << 2); } /** Memory region attributes: ARM DDI 0406B: B3-32 */ uint8_t texcb() const { return large() ? (bits(data, 2) | (bits(data, 3) << 1) | (bits(data, 14, 12) << 2)) : (bits(data, 2) | (bits(data, 3) << 1) | (bits(data, 8, 6) << 2)); } /** Return the physical frame, bits shifted right */ Addr pfn() const { return large() ? bits(data, 31, 16) : bits(data, 31, 12); } /** Return complete physical address given a VA */ Addr paddr(Addr va) const { if (large()) return mbits(data, 31, 16) | mbits(va, 15, 0); else return mbits(data, 31, 12) | mbits(va, 11, 0); } /** If the section is shareable. See texcb() comment. */ bool shareable() const { return bits(data, 10); } /** Set access flag that this entry has been touched. Mark * the entry as requiring a writeback, in the future. */ void setAp0() { data |= 1 << 4; _dirty = true; } /** This entry needs to be written back to memory */ bool dirty() const { return _dirty; } }; // Granule sizes for AArch64 long descriptors enum GrainSize { Grain4KB = 12, Grain16KB = 14, Grain64KB = 16, ReservedGrain = 0 }; /** Long-descriptor format (LPAE) */ class LongDescriptor : public DescriptorBase { public: /** Descriptor type */ enum EntryType { Invalid, Table, Block, Page }; /** The raw bits of the entry */ uint64_t data; /** This entry has been modified (access flag set) and needs to be * written back to memory */ bool _dirty; virtual uint64_t getRawData() const { return (data); } virtual std::string dbgHeader() const { if (type() == LongDescriptor::Page) { assert(lookupLevel == L3); return "Inserting Page descriptor into TLB\n"; } else { assert(lookupLevel < L3); return "Inserting Block descriptor into TLB\n"; } } /** * Returns true if this entry targets the secure physical address * map. */ bool secure(bool have_security, WalkerState *currState) const { assert(type() == Block || type() == Page); return have_security && (currState->secureLookup && !bits(data, 5)); } /** True if the current lookup is performed in AArch64 state */ bool aarch64; /** Width of the granule size in bits */ GrainSize grainSize; /** Return the descriptor type */ EntryType type() const { switch (bits(data, 1, 0)) { case 0x1: // In AArch64 blocks are not allowed at L0 for the 4 KB granule // and at L1 for 16/64 KB granules if (grainSize > Grain4KB) return lookupLevel == L2 ? Block : Invalid; return lookupLevel == L0 || lookupLevel == L3 ? Invalid : Block; case 0x3: return lookupLevel == L3 ? Page : Table; default: return Invalid; } } /** Return the bit width of the page/block offset */ uint8_t offsetBits() const { if (type() == Block) { switch (grainSize) { case Grain4KB: return lookupLevel == L1 ? 30 /* 1 GB */ : 21 /* 2 MB */; case Grain16KB: return 25 /* 32 MB */; case Grain64KB: return 29 /* 512 MB */; default: panic("Invalid AArch64 VM granule size\n"); } } else if (type() == Page) { switch (grainSize) { case Grain4KB: case Grain16KB: case Grain64KB: return grainSize; /* enum -> uint okay */ default: panic("Invalid AArch64 VM granule size\n"); } } else { panic("AArch64 page table entry must be block or page\n"); } } /** Return the physical frame, bits shifted right */ Addr pfn() const { if (aarch64) return bits(data, 47, offsetBits()); return bits(data, 39, offsetBits()); } /** Return the complete physical address given a VA */ Addr paddr(Addr va) const { int n = offsetBits(); if (aarch64) return mbits(data, 47, n) | mbits(va, n - 1, 0); return mbits(data, 39, n) | mbits(va, n - 1, 0); } /** Return the physical address of the entry */ Addr paddr() const { if (aarch64) return mbits(data, 47, offsetBits()); return mbits(data, 39, offsetBits()); } /** Return the address of the next page table */ Addr nextTableAddr() const { assert(type() == Table); if (aarch64) return mbits(data, 47, grainSize); else return mbits(data, 39, 12); } /** Return the address of the next descriptor */ Addr nextDescAddr(Addr va) const { assert(type() == Table); Addr pa = 0; if (aarch64) { int stride = grainSize - 3; int va_lo = stride * (3 - (lookupLevel + 1)) + grainSize; int va_hi = va_lo + stride - 1; pa = nextTableAddr() | (bits(va, va_hi, va_lo) << 3); } else { if (lookupLevel == L1) pa = nextTableAddr() | (bits(va, 29, 21) << 3); else // lookupLevel == L2 pa = nextTableAddr() | (bits(va, 20, 12) << 3); } return pa; } /** Is execution allowed on this mapping? */ bool xn() const { assert(type() == Block || type() == Page); return bits(data, 54); } /** Is privileged execution allowed on this mapping? (LPAE only) */ bool pxn() const { assert(type() == Block || type() == Page); return bits(data, 53); } /** Contiguous hint bit. */ bool contiguousHint() const { assert(type() == Block || type() == Page); return bits(data, 52); } /** Is the translation global (no asid used)? */ bool global(WalkerState *currState) const { assert(currState && (type() == Block || type() == Page)); if (!currState->aarch64 && (currState->isSecure && !currState->secureLookup)) { return false; // ARM ARM issue C B3.6.3 } else if (currState->aarch64) { if (currState->el == EL2 || currState->el == EL3) { return true; // By default translations are treated as global // in AArch64 EL2 and EL3 } else if (currState->isSecure && !currState->secureLookup) { return false; } } return !bits(data, 11); } /** Returns true if the access flag (AF) is set. */ bool af() const { assert(type() == Block || type() == Page); return bits(data, 10); } /** 2-bit shareability field */ uint8_t sh() const { assert(type() == Block || type() == Page); return bits(data, 9, 8); } /** 2-bit access protection flags */ uint8_t ap() const { assert(type() == Block || type() == Page); // Long descriptors only support the AP[2:1] scheme return bits(data, 7, 6); } /** Read/write access protection flag */ bool rw() const { assert(type() == Block || type() == Page); return !bits(data, 7); } /** User/privileged level access protection flag */ bool user() const { assert(type() == Block || type() == Page); return bits(data, 6); } /** Return the AP bits as compatible with the AP[2:0] format. Utility * function used to simplify the code in the TLB for performing * permission checks. */ static uint8_t ap(bool rw, bool user) { return ((!rw) << 2) | (user << 1); } TlbEntry::DomainType domain() const { // Long-desc. format only supports Client domain assert(type() == Block || type() == Page); return TlbEntry::DomainType::Client; } /** Attribute index */ uint8_t attrIndx() const { assert(type() == Block || type() == Page); return bits(data, 4, 2); } /** Memory attributes, only used by stage 2 translations */ uint8_t memAttr() const { assert(type() == Block || type() == Page); return bits(data, 5, 2); } /** Set access flag that this entry has been touched. Mark the entry as * requiring a writeback, in the future. */ void setAf() { data |= 1 << 10; _dirty = true; } /** This entry needs to be written back to memory */ bool dirty() const { return _dirty; } /** Whether the subsequent levels of lookup are secure */ bool secureTable() const { assert(type() == Table); return !bits(data, 63); } /** Two bit access protection flags for subsequent levels of lookup */ uint8_t apTable() const { assert(type() == Table); return bits(data, 62, 61); } /** R/W protection flag for subsequent levels of lookup */ uint8_t rwTable() const { assert(type() == Table); return !bits(data, 62); } /** User/privileged mode protection flag for subsequent levels of * lookup */ uint8_t userTable() const { assert(type() == Table); return !bits(data, 61); } /** Is execution allowed on subsequent lookup levels? */ bool xnTable() const { assert(type() == Table); return bits(data, 60); } /** Is privileged execution allowed on subsequent lookup levels? */ bool pxnTable() const { assert(type() == Table); return bits(data, 59); } }; class WalkerState { public: /** Thread context that we're doing the walk for */ ThreadContext *tc; /** If the access is performed in AArch64 state */ bool aarch64; /** Current exception level */ ExceptionLevel el; /** Current physical address range in bits */ int physAddrRange; /** Request that is currently being serviced */ RequestPtr req; /** ASID that we're servicing the request under */ uint16_t asid; uint8_t vmid; bool isHyp; /** Translation state for delayed requests */ TLB::Translation *transState; /** The fault that we are going to return */ Fault fault; /** The virtual address that is being translated with tagging removed.*/ Addr vaddr; /** The virtual address that is being translated */ Addr vaddr_tainted; /** Cached copy of the sctlr as it existed when translation began */ SCTLR sctlr; /** Cached copy of the scr as it existed when translation began */ SCR scr; /** Cached copy of the cpsr as it existed when translation began */ CPSR cpsr; /** Cached copy of ttbcr/tcr as it existed when translation began */ union { TTBCR ttbcr; // AArch32 translations TCR tcr; // AArch64 translations }; /** Cached copy of the htcr as it existed when translation began. */ HTCR htcr; /** Cached copy of the htcr as it existed when translation began. */ HCR hcr; /** Cached copy of the vtcr as it existed when translation began. */ VTCR_t vtcr; /** If the access is a write */ bool isWrite; /** If the access is a fetch (for execution, and no-exec) must be checked?*/ bool isFetch; /** If the access comes from the secure state. */ bool isSecure; /** Helper variables used to implement hierarchical access permissions * when the long-desc. format is used (LPAE only) */ bool secureLookup; bool rwTable; bool userTable; bool xnTable; bool pxnTable; /** Flag indicating if a second stage of lookup is required */ bool stage2Req; /** A pointer to the stage 2 translation that's in progress */ TLB::Translation *stage2Tran; /** If the mode is timing or atomic */ bool timing; /** If the atomic mode should be functional */ bool functional; /** Save mode for use in delayed response */ BaseTLB::Mode mode; /** The translation type that has been requested */ TLB::ArmTranslationType tranType; /** Short-format descriptors */ L1Descriptor l1Desc; L2Descriptor l2Desc; /** Long-format descriptor (LPAE and AArch64) */ LongDescriptor longDesc; /** Whether the response is delayed in timing mode due to additional * lookups */ bool delayed; TableWalker *tableWalker; /** Timestamp for calculating elapsed time in service (for stats) */ Tick startTime; /** Page entries walked during service (for stats) */ unsigned levels; void doL1Descriptor(); void doL2Descriptor(); void doLongDescriptor(); WalkerState(); std::string name() const { return tableWalker->name(); } }; protected: /** Queues of requests for all the different lookup levels */ std::list stateQueues[MAX_LOOKUP_LEVELS]; /** Queue of requests that have passed are waiting because the walker is * currently busy. */ std::list pendingQueue; /** The MMU to forward second stage look upts to */ Stage2MMU *stage2Mmu; /** Port shared by the two table walkers. */ DmaPort* port; /** Master id assigned by the MMU. */ MasterID masterId; /** Indicates whether this table walker is part of the stage 2 mmu */ const bool isStage2; /** TLB that is initiating these table walks */ TLB *tlb; /** Cached copy of the sctlr as it existed when translation began */ SCTLR sctlr; WalkerState *currState; /** If a timing translation is currently in progress */ bool pending; /** The number of walks belonging to squashed instructions that can be * removed from the pendingQueue per cycle. */ unsigned numSquashable; /** Cached copies of system-level properties */ bool haveSecurity; bool _haveLPAE; bool _haveVirtualization; uint8_t physAddrRange; bool _haveLargeAsid64; /** Statistics */ Stats::Scalar statWalks; Stats::Scalar statWalksShortDescriptor; Stats::Scalar statWalksLongDescriptor; Stats::Vector statWalksShortTerminatedAtLevel; Stats::Vector statWalksLongTerminatedAtLevel; Stats::Scalar statSquashedBefore; Stats::Scalar statSquashedAfter; Stats::Histogram statWalkWaitTime; Stats::Histogram statWalkServiceTime; Stats::Histogram statPendingWalks; // essentially "L" of queueing theory Stats::Vector statPageSizes; Stats::Vector2d statRequestOrigin; mutable unsigned pendingReqs; mutable Tick pendingChangeTick; static const unsigned REQUESTED = 0; static const unsigned COMPLETED = 1; public: typedef ArmTableWalkerParams Params; TableWalker(const Params *p); virtual ~TableWalker(); const Params * params() const { return dynamic_cast(_params); } void init() override; bool haveLPAE() const { return _haveLPAE; } bool haveVirtualization() const { return _haveVirtualization; } bool haveLargeAsid64() const { return _haveLargeAsid64; } /** Checks if all state is cleared and if so, completes drain */ void completeDrain(); DrainState drain() override; void drainResume() override; BaseMasterPort& getMasterPort(const std::string &if_name, PortID idx = InvalidPortID) override; void regStats() override; Fault 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); void setTlb(TLB *_tlb) { tlb = _tlb; } TLB* getTlb() { return tlb; } void setMMU(Stage2MMU *m, MasterID master_id); void memAttrs(ThreadContext *tc, TlbEntry &te, SCTLR sctlr, uint8_t texcb, bool s); void memAttrsLPAE(ThreadContext *tc, TlbEntry &te, LongDescriptor &lDescriptor); void memAttrsAArch64(ThreadContext *tc, TlbEntry &te, LongDescriptor &lDescriptor); static LookupLevel toLookupLevel(uint8_t lookup_level_as_int); private: void doL1Descriptor(); void doL1DescriptorWrapper(); EventFunctionWrapper doL1DescEvent; void doL2Descriptor(); void doL2DescriptorWrapper(); EventFunctionWrapper doL2DescEvent; void doLongDescriptor(); void doL0LongDescriptorWrapper(); EventFunctionWrapper doL0LongDescEvent; void doL1LongDescriptorWrapper(); EventFunctionWrapper doL1LongDescEvent; void doL2LongDescriptorWrapper(); EventFunctionWrapper doL2LongDescEvent; void doL3LongDescriptorWrapper(); EventFunctionWrapper doL3LongDescEvent; void doLongDescriptorWrapper(LookupLevel curr_lookup_level); Event* LongDescEventByLevel[4]; bool fetchDescriptor(Addr descAddr, uint8_t *data, int numBytes, Request::Flags flags, int queueIndex, Event *event, void (TableWalker::*doDescriptor)()); void insertTableEntry(DescriptorBase &descriptor, bool longDescriptor); Fault processWalk(); Fault processWalkLPAE(); static unsigned adjustTableSizeAArch64(unsigned tsz); /// Returns true if the address exceeds the range permitted by the /// system-wide setting or by the TCR_ELx IPS/PS setting static bool checkAddrSizeFaultAArch64(Addr addr, int currPhysAddrRange); Fault processWalkAArch64(); void processWalkWrapper(); EventFunctionWrapper doProcessEvent; void nextWalk(ThreadContext *tc); void pendingChange(); static uint8_t pageSizeNtoStatBin(uint8_t N); Fault testWalk(Addr pa, Addr size, TlbEntry::DomainType domain, LookupLevel lookup_level); }; } // namespace ArmISA #endif //__ARCH_ARM_TABLE_WALKER_HH__