/*
 * 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 <list>

#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<TlbEntry::DomainType>(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;

        /** Indicates whether the translation has been passed onto the second
         *  stage mmu, and no more work is required from the first stage.
         */
        bool doingStage2;

        /** 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<WalkerState *> stateQueues[MAX_LOOKUP_LEVELS];

    /** Queue of requests that have passed are waiting because the walker is
     * currently busy. */
    std::list<WalkerState *> 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<const Params *>(_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(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();
    EventWrapper<TableWalker,
                 &TableWalker::doL1DescriptorWrapper> doL1DescEvent;

    void doL2Descriptor();
    void doL2DescriptorWrapper();
    EventWrapper<TableWalker,
                 &TableWalker::doL2DescriptorWrapper> doL2DescEvent;

    void doLongDescriptor();

    void doL0LongDescriptorWrapper();
    EventWrapper<TableWalker,
                 &TableWalker::doL0LongDescriptorWrapper> doL0LongDescEvent;
    void doL1LongDescriptorWrapper();
    EventWrapper<TableWalker,
                 &TableWalker::doL1LongDescriptorWrapper> doL1LongDescEvent;
    void doL2LongDescriptorWrapper();
    EventWrapper<TableWalker,
                 &TableWalker::doL2LongDescriptorWrapper> doL2LongDescEvent;
    void doL3LongDescriptorWrapper();
    EventWrapper<TableWalker,
                 &TableWalker::doL3LongDescriptorWrapper> 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();
    EventWrapper<TableWalker, &TableWalker::processWalkWrapper> 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__

