/*
 * Copyright (c) 2010, 2012-2018 ARM Limited
 * All rights reserved
 *
 * The license below extends only to copyright in the software and shall
 * not be construed as granting a license to any other intellectual
 * property including but not limited to intellectual property relating
 * to a hardware implementation of the functionality of the software
 * licensed hereunder.  You may use the software subject to the license
 * terms below provided that you ensure that this notice is replicated
 * unmodified and in its entirety in all distributions of the software,
 * modified or unmodified, in source code or in binary form.
 *
 * Copyright (c) 2009 The Regents of The University of Michigan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Gabe Black
 */

#ifndef __ARCH_ARM_ISA_HH__
#define __ARCH_ARM_ISA_HH__

#include "arch/arm/isa_device.hh"
#include "arch/arm/miscregs.hh"
#include "arch/arm/registers.hh"
#include "arch/arm/system.hh"
#include "arch/arm/tlb.hh"
#include "arch/arm/types.hh"
#include "arch/generic/traits.hh"
#include "debug/Checkpoint.hh"
#include "enums/VecRegRenameMode.hh"
#include "sim/sim_object.hh"
#include "enums/DecoderFlavour.hh"

struct ArmISAParams;
struct DummyArmISADeviceParams;
class ThreadContext;
class Checkpoint;
class EventManager;

namespace ArmISA
{
    class ISA : public SimObject
    {
      protected:
        // Parent system
        ArmSystem *system;

        // Micro Architecture
        const Enums::DecoderFlavour _decoderFlavour;
        const Enums::VecRegRenameMode _vecRegRenameMode;

        /** Dummy device for to handle non-existing ISA devices */
        DummyISADevice dummyDevice;

        // PMU belonging to this ISA
        BaseISADevice *pmu;

        // Generic timer interface belonging to this ISA
        std::unique_ptr<BaseISADevice> timer;

        // Cached copies of system-level properties
        bool highestELIs64;
        bool haveSecurity;
        bool haveLPAE;
        bool haveVirtualization;
        bool haveLargeAsid64;
        uint8_t physAddrRange;

        /**
         * If true, accesses to IMPLEMENTATION DEFINED registers are treated
         * as NOP hence not causing UNDEFINED INSTRUCTION.
         */
        bool impdefAsNop;

        /** MiscReg metadata **/
        struct MiscRegLUTEntry {
            uint32_t lower;  // Lower half mapped to this register
            uint32_t upper;  // Upper half mapped to this register
            uint64_t _reset; // value taken on reset (i.e. initialization)
            uint64_t _res0;  // reserved
            uint64_t _res1;  // reserved
            uint64_t _raz;   // read as zero (fixed at 0)
            uint64_t _rao;   // read as one (fixed at 1)
          public:
            MiscRegLUTEntry() :
              lower(0), upper(0),
              _reset(0), _res0(0), _res1(0), _raz(0), _rao(0) {}
            uint64_t reset() const { return _reset; }
            uint64_t res0()  const { return _res0; }
            uint64_t res1()  const { return _res1; }
            uint64_t raz()   const { return _raz; }
            uint64_t rao()   const { return _rao; }
            // raz/rao implies writes ignored
            uint64_t wi()    const { return _raz | _rao; }
        };

        /** Metadata table accessible via the value of the register */
        static std::vector<struct MiscRegLUTEntry> lookUpMiscReg;

        class MiscRegLUTEntryInitializer {
            struct MiscRegLUTEntry &entry;
            std::bitset<NUM_MISCREG_INFOS> &info;
            typedef const MiscRegLUTEntryInitializer& chain;
          public:
            chain mapsTo(uint32_t l, uint32_t u = 0) const {
                entry.lower = l;
                entry.upper = u;
                return *this;
            }
            chain res0(uint64_t mask) const {
                entry._res0 = mask;
                return *this;
            }
            chain res1(uint64_t mask) const {
                entry._res1 = mask;
                return *this;
            }
            chain raz(uint64_t mask) const {
                entry._raz  = mask;
                return *this;
            }
            chain rao(uint64_t mask) const {
                entry._rao  = mask;
                return *this;
            }
            chain implemented(bool v = true) const {
                info[MISCREG_IMPLEMENTED] = v;
                return *this;
            }
            chain unimplemented() const {
                return implemented(false);
            }
            chain unverifiable(bool v = true) const {
                info[MISCREG_UNVERIFIABLE] = v;
                return *this;
            }
            chain warnNotFail(bool v = true) const {
                info[MISCREG_WARN_NOT_FAIL] = v;
                return *this;
            }
            chain mutex(bool v = true) const {
                info[MISCREG_MUTEX] = v;
                return *this;
            }
            chain banked(bool v = true) const {
                info[MISCREG_BANKED] = v;
                return *this;
            }
            chain bankedChild(bool v = true) const {
                info[MISCREG_BANKED_CHILD] = v;
                return *this;
            }
            chain userNonSecureRead(bool v = true) const {
                info[MISCREG_USR_NS_RD] = v;
                return *this;
            }
            chain userNonSecureWrite(bool v = true) const {
                info[MISCREG_USR_NS_WR] = v;
                return *this;
            }
            chain userSecureRead(bool v = true) const {
                info[MISCREG_USR_S_RD] = v;
                return *this;
            }
            chain userSecureWrite(bool v = true) const {
                info[MISCREG_USR_S_WR] = v;
                return *this;
            }
            chain user(bool v = true) const {
                userNonSecureRead(v);
                userNonSecureWrite(v);
                userSecureRead(v);
                userSecureWrite(v);
                return *this;
            }
            chain privNonSecureRead(bool v = true) const {
                info[MISCREG_PRI_NS_RD] = v;
                return *this;
            }
            chain privNonSecureWrite(bool v = true) const {
                info[MISCREG_PRI_NS_WR] = v;
                return *this;
            }
            chain privNonSecure(bool v = true) const {
                privNonSecureRead(v);
                privNonSecureWrite(v);
                return *this;
            }
            chain privSecureRead(bool v = true) const {
                info[MISCREG_PRI_S_RD] = v;
                return *this;
            }
            chain privSecureWrite(bool v = true) const {
                info[MISCREG_PRI_S_WR] = v;
                return *this;
            }
            chain privSecure(bool v = true) const {
                privSecureRead(v);
                privSecureWrite(v);
                return *this;
            }
            chain priv(bool v = true) const {
                privSecure(v);
                privNonSecure(v);
                return *this;
            }
            chain hypRead(bool v = true) const {
                info[MISCREG_HYP_RD] = v;
                return *this;
            }
            chain hypWrite(bool v = true) const {
                info[MISCREG_HYP_WR] = v;
                return *this;
            }
            chain hyp(bool v = true) const {
                hypRead(v);
                hypWrite(v);
                return *this;
            }
            chain monSecureRead(bool v = true) const {
                info[MISCREG_MON_NS0_RD] = v;
                return *this;
            }
            chain monSecureWrite(bool v = true) const {
                info[MISCREG_MON_NS0_WR] = v;
                return *this;
            }
            chain monNonSecureRead(bool v = true) const {
                info[MISCREG_MON_NS1_RD] = v;
                return *this;
            }
            chain monNonSecureWrite(bool v = true) const {
                info[MISCREG_MON_NS1_WR] = v;
                return *this;
            }
            chain mon(bool v = true) const {
                monSecureRead(v);
                monSecureWrite(v);
                monNonSecureRead(v);
                monNonSecureWrite(v);
                return *this;
            }
            chain monSecure(bool v = true) const {
                monSecureRead(v);
                monSecureWrite(v);
                return *this;
            }
            chain monNonSecure(bool v = true) const {
                monNonSecureRead(v);
                monNonSecureWrite(v);
                return *this;
            }
            chain allPrivileges(bool v = true) const {
                userNonSecureRead(v);
                userNonSecureWrite(v);
                userSecureRead(v);
                userSecureWrite(v);
                privNonSecureRead(v);
                privNonSecureWrite(v);
                privSecureRead(v);
                privSecureWrite(v);
                hypRead(v);
                hypWrite(v);
                monSecureRead(v);
                monSecureWrite(v);
                monNonSecureRead(v);
                monNonSecureWrite(v);
                return *this;
            }
            chain nonSecure(bool v = true) const {
                userNonSecureRead(v);
                userNonSecureWrite(v);
                privNonSecureRead(v);
                privNonSecureWrite(v);
                hypRead(v);
                hypWrite(v);
                monNonSecureRead(v);
                monNonSecureWrite(v);
                return *this;
            }
            chain secure(bool v = true) const {
                userSecureRead(v);
                userSecureWrite(v);
                privSecureRead(v);
                privSecureWrite(v);
                monSecureRead(v);
                monSecureWrite(v);
                return *this;
            }
            chain reads(bool v) const {
                userNonSecureRead(v);
                userSecureRead(v);
                privNonSecureRead(v);
                privSecureRead(v);
                hypRead(v);
                monSecureRead(v);
                monNonSecureRead(v);
                return *this;
            }
            chain writes(bool v) const {
                userNonSecureWrite(v);
                userSecureWrite(v);
                privNonSecureWrite(v);
                privSecureWrite(v);
                hypWrite(v);
                monSecureWrite(v);
                monNonSecureWrite(v);
                return *this;
            }
            chain exceptUserMode() const {
                user(0);
                return *this;
            }
            MiscRegLUTEntryInitializer(struct MiscRegLUTEntry &e,
                                       std::bitset<NUM_MISCREG_INFOS> &i)
              : entry(e),
                info(i)
            {
                // force unimplemented registers to be thusly declared
                implemented(1);
            }
        };

        const MiscRegLUTEntryInitializer InitReg(uint32_t reg) {
            return MiscRegLUTEntryInitializer(lookUpMiscReg[reg],
                                              miscRegInfo[reg]);
        }

        void initializeMiscRegMetadata();

        MiscReg miscRegs[NumMiscRegs];
        const IntRegIndex *intRegMap;

        void
        updateRegMap(CPSR cpsr)
        {
            if (cpsr.width == 0) {
                intRegMap = IntReg64Map;
            } else {
                switch (cpsr.mode) {
                  case MODE_USER:
                  case MODE_SYSTEM:
                    intRegMap = IntRegUsrMap;
                    break;
                  case MODE_FIQ:
                    intRegMap = IntRegFiqMap;
                    break;
                  case MODE_IRQ:
                    intRegMap = IntRegIrqMap;
                    break;
                  case MODE_SVC:
                    intRegMap = IntRegSvcMap;
                    break;
                  case MODE_MON:
                    intRegMap = IntRegMonMap;
                    break;
                  case MODE_ABORT:
                    intRegMap = IntRegAbtMap;
                    break;
                  case MODE_HYP:
                    intRegMap = IntRegHypMap;
                    break;
                  case MODE_UNDEFINED:
                    intRegMap = IntRegUndMap;
                    break;
                  default:
                    panic("Unrecognized mode setting in CPSR.\n");
                }
            }
        }

        BaseISADevice &getGenericTimer(ThreadContext *tc);


      private:
        inline void assert32(ThreadContext *tc) {
            CPSR cpsr M5_VAR_USED = readMiscReg(MISCREG_CPSR, tc);
            assert(cpsr.width);
        }

        inline void assert64(ThreadContext *tc) {
            CPSR cpsr M5_VAR_USED = readMiscReg(MISCREG_CPSR, tc);
            assert(!cpsr.width);
        }

      public:
        void clear();

      protected:
        void clear64(const ArmISAParams *p);
        void initID32(const ArmISAParams *p);
        void initID64(const ArmISAParams *p);

      public:
        MiscReg readMiscRegNoEffect(int misc_reg) const;
        MiscReg readMiscReg(int misc_reg, ThreadContext *tc);
        void setMiscRegNoEffect(int misc_reg, const MiscReg &val);
        void setMiscReg(int misc_reg, const MiscReg &val, ThreadContext *tc);

        RegId
        flattenRegId(const RegId& regId) const
        {
            switch (regId.classValue()) {
              case IntRegClass:
                return RegId(IntRegClass, flattenIntIndex(regId.index()));
              case FloatRegClass:
                return RegId(FloatRegClass, flattenFloatIndex(regId.index()));
              case VecRegClass:
                return RegId(VecRegClass, flattenVecIndex(regId.index()));
              case VecElemClass:
                return RegId(VecElemClass, flattenVecElemIndex(regId.index()));
              case CCRegClass:
                return RegId(CCRegClass, flattenCCIndex(regId.index()));
              case MiscRegClass:
                return RegId(MiscRegClass, flattenMiscIndex(regId.index()));
            }
            return RegId();
        }

        int
        flattenIntIndex(int reg) const
        {
            assert(reg >= 0);
            if (reg < NUM_ARCH_INTREGS) {
                return intRegMap[reg];
            } else if (reg < NUM_INTREGS) {
                return reg;
            } else if (reg == INTREG_SPX) {
                CPSR cpsr = miscRegs[MISCREG_CPSR];
                ExceptionLevel el = opModeToEL(
                    (OperatingMode) (uint8_t) cpsr.mode);
                if (!cpsr.sp && el != EL0)
                    return INTREG_SP0;
                switch (el) {
                  case EL3:
                    return INTREG_SP3;
                  case EL2:
                    return INTREG_SP2;
                  case EL1:
                    return INTREG_SP1;
                  case EL0:
                    return INTREG_SP0;
                  default:
                    panic("Invalid exception level");
                    return 0;  // Never happens.
                }
            } else {
                return flattenIntRegModeIndex(reg);
            }
        }

        int
        flattenFloatIndex(int reg) const
        {
            assert(reg >= 0);
            return reg;
        }

        int
        flattenVecIndex(int reg) const
        {
            assert(reg >= 0);
            return reg;
        }

        int
        flattenVecElemIndex(int reg) const
        {
            assert(reg >= 0);
            return reg;
        }

        int
        flattenCCIndex(int reg) const
        {
            assert(reg >= 0);
            return reg;
        }

        int
        flattenMiscIndex(int reg) const
        {
            assert(reg >= 0);
            int flat_idx = reg;

            if (reg == MISCREG_SPSR) {
                CPSR cpsr = miscRegs[MISCREG_CPSR];
                switch (cpsr.mode) {
                  case MODE_EL0T:
                    warn("User mode does not have SPSR\n");
                    flat_idx = MISCREG_SPSR;
                    break;
                  case MODE_EL1T:
                  case MODE_EL1H:
                    flat_idx = MISCREG_SPSR_EL1;
                    break;
                  case MODE_EL2T:
                  case MODE_EL2H:
                    flat_idx = MISCREG_SPSR_EL2;
                    break;
                  case MODE_EL3T:
                  case MODE_EL3H:
                    flat_idx = MISCREG_SPSR_EL3;
                    break;
                  case MODE_USER:
                    warn("User mode does not have SPSR\n");
                    flat_idx = MISCREG_SPSR;
                    break;
                  case MODE_FIQ:
                    flat_idx = MISCREG_SPSR_FIQ;
                    break;
                  case MODE_IRQ:
                    flat_idx = MISCREG_SPSR_IRQ;
                    break;
                  case MODE_SVC:
                    flat_idx = MISCREG_SPSR_SVC;
                    break;
                  case MODE_MON:
                    flat_idx = MISCREG_SPSR_MON;
                    break;
                  case MODE_ABORT:
                    flat_idx = MISCREG_SPSR_ABT;
                    break;
                  case MODE_HYP:
                    flat_idx = MISCREG_SPSR_HYP;
                    break;
                  case MODE_UNDEFINED:
                    flat_idx = MISCREG_SPSR_UND;
                    break;
                  default:
                    warn("Trying to access SPSR in an invalid mode: %d\n",
                         cpsr.mode);
                    flat_idx = MISCREG_SPSR;
                    break;
                }
            } else if (miscRegInfo[reg][MISCREG_MUTEX]) {
                // Mutually exclusive CP15 register
                switch (reg) {
                  case MISCREG_PRRR_MAIR0:
                  case MISCREG_PRRR_MAIR0_NS:
                  case MISCREG_PRRR_MAIR0_S:
                    {
                        TTBCR ttbcr = readMiscRegNoEffect(MISCREG_TTBCR);
                        // If the muxed reg has been flattened, work out the
                        // offset and apply it to the unmuxed reg
                        int idxOffset = reg - MISCREG_PRRR_MAIR0;
                        if (ttbcr.eae)
                            flat_idx = flattenMiscIndex(MISCREG_MAIR0 +
                                                        idxOffset);
                        else
                            flat_idx = flattenMiscIndex(MISCREG_PRRR +
                                                        idxOffset);
                    }
                    break;
                  case MISCREG_NMRR_MAIR1:
                  case MISCREG_NMRR_MAIR1_NS:
                  case MISCREG_NMRR_MAIR1_S:
                    {
                        TTBCR ttbcr = readMiscRegNoEffect(MISCREG_TTBCR);
                        // If the muxed reg has been flattened, work out the
                        // offset and apply it to the unmuxed reg
                        int idxOffset = reg - MISCREG_NMRR_MAIR1;
                        if (ttbcr.eae)
                            flat_idx = flattenMiscIndex(MISCREG_MAIR1 +
                                                        idxOffset);
                        else
                            flat_idx = flattenMiscIndex(MISCREG_NMRR +
                                                        idxOffset);
                    }
                    break;
                  case MISCREG_PMXEVTYPER_PMCCFILTR:
                    {
                        PMSELR pmselr = miscRegs[MISCREG_PMSELR];
                        if (pmselr.sel == 31)
                            flat_idx = flattenMiscIndex(MISCREG_PMCCFILTR);
                        else
                            flat_idx = flattenMiscIndex(MISCREG_PMXEVTYPER);
                    }
                    break;
                  default:
                    panic("Unrecognized misc. register.\n");
                    break;
                }
            } else {
                if (miscRegInfo[reg][MISCREG_BANKED]) {
                    bool secureReg = haveSecurity && !highestELIs64 &&
                                     inSecureState(miscRegs[MISCREG_SCR],
                                                   miscRegs[MISCREG_CPSR]);
                    flat_idx += secureReg ? 2 : 1;
                }
            }
            return flat_idx;
        }

        std::pair<int,int> getMiscIndices(int misc_reg) const
        {
            // Note: indexes of AArch64 registers are left unchanged
            int flat_idx = flattenMiscIndex(misc_reg);

            if (lookUpMiscReg[flat_idx].lower == 0) {
                return std::make_pair(flat_idx, 0);
            }

            // do additional S/NS flattenings if mapped to NS while in S
            bool S = haveSecurity && !highestELIs64 &&
                     inSecureState(miscRegs[MISCREG_SCR],
                                   miscRegs[MISCREG_CPSR]);
            int lower = lookUpMiscReg[flat_idx].lower;
            int upper = lookUpMiscReg[flat_idx].upper;
            // upper == 0, which is CPSR, is not MISCREG_BANKED_CHILD (no-op)
            lower += S && miscRegInfo[lower][MISCREG_BANKED_CHILD];
            upper += S && miscRegInfo[upper][MISCREG_BANKED_CHILD];
            return std::make_pair(lower, upper);
        }

        void serialize(CheckpointOut &cp) const
        {
            DPRINTF(Checkpoint, "Serializing Arm Misc Registers\n");
            SERIALIZE_ARRAY(miscRegs, NUM_PHYS_MISCREGS);

            SERIALIZE_SCALAR(highestELIs64);
            SERIALIZE_SCALAR(haveSecurity);
            SERIALIZE_SCALAR(haveLPAE);
            SERIALIZE_SCALAR(haveVirtualization);
            SERIALIZE_SCALAR(haveLargeAsid64);
            SERIALIZE_SCALAR(physAddrRange);
        }
        void unserialize(CheckpointIn &cp)
        {
            DPRINTF(Checkpoint, "Unserializing Arm Misc Registers\n");
            UNSERIALIZE_ARRAY(miscRegs, NUM_PHYS_MISCREGS);
            CPSR tmp_cpsr = miscRegs[MISCREG_CPSR];
            updateRegMap(tmp_cpsr);

            UNSERIALIZE_SCALAR(highestELIs64);
            UNSERIALIZE_SCALAR(haveSecurity);
            UNSERIALIZE_SCALAR(haveLPAE);
            UNSERIALIZE_SCALAR(haveVirtualization);
            UNSERIALIZE_SCALAR(haveLargeAsid64);
            UNSERIALIZE_SCALAR(physAddrRange);
        }

        void startup(ThreadContext *tc);

        Enums::DecoderFlavour decoderFlavour() const { return _decoderFlavour; }

        Enums::VecRegRenameMode
        vecRegRenameMode() const
        {
            return _vecRegRenameMode;
        }

        /// Explicitly import the otherwise hidden startup
        using SimObject::startup;

        typedef ArmISAParams Params;

        const Params *params() const;

        ISA(Params *p);
    };
}

template<>
struct initRenameMode<ArmISA::ISA>
{
    static Enums::VecRegRenameMode mode(const ArmISA::ISA* isa)
    {
        return isa->vecRegRenameMode();
    }
    static bool equals(const ArmISA::ISA* isa1, const ArmISA::ISA* isa2)
    {
        return mode(isa1) == mode(isa2);
    }
};

#endif