//
//  astris_arm64cpu.h
//  Astris
//
//  Created by Dan Heller on 10/21/11.
//  Copyright (c) 2011-2012 Apple, Inc. All rights reserved.
//
//      General note: "_hw" variants of member functions
//	don't assume that cached state is correct--they
//	are low-level primitives that do exactly what they
//	say, whereas higher-level routines may read from cache.
//
#ifndef Astris_astris_arm64cpu_h
#define Astris_astris_arm64cpu_h

#include "astris_armcpu.h"

#define AXASSERT_ARM64(assertion, fmt, ...)                \
    AXASSERT_BOOL(_log_key, AXA_AREA_CPU_ARM64, assertion, \
                  fmt, ## __VA_ARGS__)

#define AXREQUIRE_RADAR_ARM64(check, err, code, label, fmt, ...) \
    AXREQUIRE_BOOL_ERR_RADAR(AXLOG_M_CPU, AXCONN_FLG_DEBUG_CPU,  \
                             check, err, code, label,            \
                             fmt, ## __VA_ARGS__)

#define MAX_EDSCR_FC_CLEARS 16  // Max consecutive attempts to clear flow ctl
#define MAX_EDSCR_FC_CHECKS 128 // Max consecutive reads waiting lift of flow ctl

// Define the most efficient memory transfer size supported for ARM64 CPUs.
// Classes derived from ARM64 may provide a debug memory access unit other
// than the default for ARM CPUs so the following base literal has been defined.
#define ARM64_MEMORY_QUANTUM_DEFAULT ARMCPU_MEMORY_QUANTUM_DEFAULT

class ARM64cpu_t:
    public ARMcpu_t {
public:
    ARM64cpu_t(AXtgt_t *tgt, const char *name, uint32_t id, uint64_t bus_offset);
    virtual ~ARM64cpu_t();

    virtual int read_register(AXregid_t reg, AXregval_t *val);
    virtual int write_register(AXregid_t reg, AXregval_t val);

    virtual int set_cti_offset(AXmemaddr_t offset);
    virtual int get_cti_offset(AXmemaddr_t *offset);

    virtual int read_dcc(int *got_val, uint32_t *val);
    virtual int cpu_mode(const char **mode, uint32_t cpsr);

    virtual int translate_par(AXmemaddr_t va, AXregval_t *par, bool write, int *result);
    virtual int translate_par_with_stage(AXmemaddr_t va, AXregval_t *par, bool write, int *result, int stage = 1);
    virtual int translate(AXmemaddr_t va, AXmemaddr_t *pa, bool write, int *result);
    virtual int translate_with_stage(AXmemaddr_t va, AXmemaddr_t *pa, bool write, uint8_t stage, int *result);
    virtual int cache_cleaninv(AXcpu_cache_level_t cache_level);

    /*!
     @function print_user_regs
     @discussion Dumps the GPRs, FP, LR, SP, PC, and a diassembly at the PC
     to stdout in a format identical to how Tcl does it.
     @result astris error code
     */
    virtual int print_user_regs();

    /*!
     @function print_debug_regs
     @discussion Helper function, to be used, if we want to print debug
     regs during reg dumping.
     @result astris error code
     */
    int print_debug_regs();

    /*!
     @function decode_cpsr
     @discussion Decode the given value as a CPSR value.  The caller should take
     care to ensure that the 'decoded' param has enough memory allocated to it
     to safely return the string. Something like 32 bytes should suffice.
     @param val The value to decode
     @param decoded String of the decoding
     @result astris error code
     */
    virtual int decode_cpsr(AXregval_t val, char *decoded);

    virtual CPUFamily get_cpu_family() {return CPUFamily::kARM64;}

    /*!
     @function _a64cpu_raise_el_hw
     @discussion Raise the EL to a given level while in Debug State.
     This function is used internally to increase EL when reading registers or when
     first entering debug state.
     This function is only public, so we can implement internal API for power users.
     @param func Name of function calling this
     @param el Target EL level to lower to
     @result astris error code
     */
    virtual int _a64cpu_raise_el_hw(const char *func, uint32_t el);

    /*!
     @function _a64cpu_lower_el_hw
     @discussion Lower the EL to a given level while in Debug State.
     This function should be AVOIDED in most uses, _a64cpu_restore_el_and_spsr
     is the current function. This is a power user function, and in regular debug
     we should be restoring EL to previous level, instead of a specific level.
     This function is only public, so we can implement internal API for power users.
     @param func Name of function calling this
     @param el Target EL level to lower to
     @result astris error code
     */
    virtual int _a64cpu_lower_el_hw(const char *func, uint32_t el);

protected:
    //
    // Structures and Data
    //

    struct arm64_regs {
        cpureg_t a64r_gprs[ARM64_GPR_COUNT];

        // Generic
        cpureg_t a64r_dlr_el0;
        cpureg_t a64r_dspsr_el0;

        cpureg_t a64r_par_el1;

        // EL1 regs corrupted by exceptions and drop to EL0
        int      a64r_latched_el1;
        cpureg_t a64r_esr_el1;
        cpureg_t a64r_elr_el1;
        cpureg_t a64r_far_el1;
        cpureg_t a64r_spsr_el1;

        // EL2 regs corrupted by exceptions and drop to EL0, EL1
        int      a64r_latched_el2;
        cpureg_t a64r_esr_el2;  // Helps us get exception syndrom
        cpureg_t a64r_elr_el2;  // Tells us return address, exception link register
        cpureg_t a64r_far_el2;  // Fault address register, virtual address
        cpureg_t a64r_spsr_el2; // Saved program state register
        cpureg_t a64r_hcr_el2;  // Hypervisor Config Register

        // EL3 regs corrupted by exceptions and drop to EL0, EL1
        int      a64r_latched_el3;
        cpureg_t a64r_esr_el3;
        cpureg_t a64r_elr_el3;
        cpureg_t a64r_far_el3;
        cpureg_t a64r_spsr_el3;

        // XXX figure out parts of CPSR to stash
    } m_regstate;

    // Keep track of the MSRs that should read/write cached copies instead of HW
    struct arm64_cached_msrs {
        AXregid_t reg;
        cpureg_t *cache;
    };
    static const uint32_t size_msr_cache = 13;
    struct arm64_cached_msrs m_aarch64_msr_cache[size_msr_cache];

    static const uint32_t m_aarch64_msr_mrs_sysreg_encodings[];
    static const uint32_t m_aarch32_coproc_encodings[];

    static const int32_t m_arm_statemap[];
    uint32_t m_exception_level;
    bool m_current_rw_is_64;
    bool m_handling_exception;
    bool m_halting_debug_en = false;

    /*
        EL1 should be the EL we usually raise to
        if we are caught in EL0. However, if EL2
        is implemeted and HCR_EL2.TGE is set, then exceptions are trapped
        from EL0 to EL2 skipping EL1.
    */
    uint8_t min_operating_el = 1;
    uint64_t m_pfr0;

    // The "slow" base/data registers should match what's used implicitly during memory access mode.
    // The scratch register is chosen so the base won't be corrupted during abort handling.
    static const AXregid_t m_base_reg    = ARM64_REG_GPR_0;
    static const AXregid_t m_data_reg    = ARM64_REG_GPR_0 + 1;
    static const AXregid_t m_scratch_reg = ARM64_REG_GPR_0 + 1;

    const char *_log_key; // ARM64 CPU logging domain
    AXmemaddr_t _cti_base_offset;

    virtual int _get_regwidth_hw() {return 8;}
    virtual int _get_arch_hw()     {return AXCPUARCH_ARMV8;}
    virtual int _get_type_hw();

    virtual bool _init_hw();
    virtual bool _cleanup_hw();
    virtual int  _status_hw(uint32_t *status, uint32_t *flags);
    virtual int  _halt_hw();
    virtual int  _collect_hw(int timeout);
    virtual int  _resume_hw(bool step);
    virtual int  _flush_state_hw();
    virtual int  _is_halted(bool *halted);

    virtual int _software_breakpoint(uint32_t *len, uint32_t *inst);
    virtual int _write_executable_memory(bpwp_info_t *bp_info);

    //
    // Memory helpers
    //
    virtual int  _memory_quantum() {return ARM64_MEMORY_QUANTUM_DEFAULT;}
    virtual void _memory_clip(AXmemaddr_t address, uint32_t *length)
    {
        if (m_current_rw_is_64) {
            // An address of 0x0 will overflow and cause us to incorrectly adjust
            // length.  We'll catch that case here.
            if ((address > 0x0) && (((AXmemaddr_t)UINT64_MAX - address) + 1 < *length)) {
                *length = (uint32_t)(((AXmemaddr_t)UINT64_MAX) - address) + 1;
            }
        } else {
            ARMcpu_t::_memory_clip(address, length);
        }
    }

    virtual bool _memory_fastmode(AXmemaddr_t address, uint32_t length);

    virtual int _memory_set_base(AXmemaddr_t address) {return _a64cpu_write_gpr_hw(m_base_reg, address, m_current_rw_is_64, "", "", "_memory_set_base");}
    virtual int _read_memory_slow(AXmemaddr_t address, uint32_t *length, uint8_t *buffer);
    virtual int _read_memory_fast_try(AXmemaddr_t address, uint32_t *length, uint8_t *buffer);
    virtual int _read_memory_fast_try_with_chunk(AXmemaddr_t address, uint32_t *length, uint8_t *buffer, uint8_t chunk_size);
    virtual int _read_memory_fast(AXmemaddr_t address, uint32_t *length, uint8_t *buffer);
    virtual int _write_memory_slow(AXmemaddr_t address, uint32_t *length, uint8_t *buffer, int width);
    virtual int _write_memory_fast_try(AXmemaddr_t address, uint32_t *length, uint8_t *buffer);
    virtual int _write_memory_fast(AXmemaddr_t address, uint32_t *length, uint8_t *buffer);

    //
    // Cache operations
    //
    virtual int _clean_dcache_setway(uint32_t set, uint32_t way);
    virtual int _clean_dcache_vaddr(AXmemaddr_t addr);
    virtual int _clean_invalidate_dcache_setway(uint32_t set, uint32_t way);
    virtual int _clean_invalidate_dcache_vaddr(AXmemaddr_t addr);
    virtual int _invalidate_icache_all();
    virtual int _invalidate_icache_vaddr(AXmemaddr_t addr);
    virtual int _clean_invalidate_l2cache_setway(uint32_t set, uint32_t way);

    //
    // Low-level breakpoint/watchpoint operations
    //
    virtual int _insert_watchpoint(bpwp_info_t *info, AXmemaddr_t address, uint32_t len, int rdwr);
    virtual int _get_num_hwbp_hw(int *numbp);
    virtual int _get_num_hwwp_hw(int *numwp);
    virtual int _get_bpcr_hw(uint32_t index, uint32_t *bpcr);
    virtual int _get_wpcr_hw(uint32_t index, uint32_t *wpcr);
    virtual int _set_bpcr_hw(uint32_t index, uint32_t bpcr);
    virtual int _set_wpcr_hw(uint32_t index, uint32_t wpcr);
    virtual int _get_bpaddr_hw(uint32_t index, AXmemaddr_t *bpaddr);
    virtual int _get_wpaddr_hw(uint32_t index, AXmemaddr_t *wpaddr);
    virtual int _set_bpaddr_hw(uint32_t index, AXmemaddr_t bpaddr);
    virtual int _set_wpaddr_hw(uint32_t index, AXmemaddr_t wpaddr);

    //
    // Address translation control implementation
    //
    virtual int _suspend_translation_hw(void);
    virtual int _resume_translation_hw(void);

    //
    // Memory access control implementation
    //
    virtual int _suspend_permission_checks(void);
    virtual int _resume_permission_checks(void);

    //
    // Misc.
    //
    virtual int _exec_recover(AXinstr_t instr, bool autoRecover);
    virtual int _exec(AXinstr_t instr);
    virtual int _exec(AXinstr_t instr, const char *fmt, ...);
    virtual int _exec_fast(AXinstr_t instr, const char *fmt, ...);

    virtual int  _set_halting(bool enable);
    virtual int  _set_corenpdnrq(bool enable);
    virtual int  _check_abort();
    virtual int  _synchronize_context();
    virtual int  _sstep();
    virtual void _init_params();

    //
    // Methods below are specific to ARM64cpu_t and its subclasses
    //

    //
    // Register operations
    //
    virtual int _a64cpu_read_gpr_cached(AXregid_t gpr, AXregval_t *val, const char *regName, const char *func);
    virtual int _a64cpu_read_gpr_hw(AXregid_t gpr, AXregval_t *val, bool aarch64, const char *regName, const char *func);
    virtual int _a64cpu_write_gpr_cached(AXregid_t reg, AXregval_t val, bool commit, const char *regName, const char *valDesc, const char *func);
    virtual int _a64cpu_write_gpr_hw(AXregid_t gpr, AXregval_t val, bool aarch64, const char *regName, const char *valDesc, const char *func);
    virtual int _a64cpu_read_sysreg_hw(AXregid_t sysreg, AXregval_t *val, bool aarch64, const char *regName, const char *func);
    virtual int _a64cpu_write_sysreg_hw(AXregid_t sysreg, AXregval_t val, bool aarch64, const char *regName, const char *valDesc, const char *func);
    virtual int _a64cpu_read_vfpreg_hw(AXregid_t sysreg, AXregval_t *val, bool aarch64, const char *regName, const char *func);
    virtual int _a64cpu_write_vfpreg_hw(AXregid_t sysreg, AXregval_t val, bool aarch64, const char *regName, const char *valDesc, const char *func);
    virtual int _a64cpu_write_aarch64_sp_hw(AXregval_t sp, const char *regName, const char *valDesc, const char *func);
    virtual int _a64cpu_read_aarch64_sp_hw(AXregval_t *sp, const char *regName, const char *func);

    static bool _a64cpu_reg_needs_flush(cpureg_t *reg) {return reg->cached && reg->dirty;}

    //
    // Executing instructions
    //
    virtual int _a64cpu_wait_ite_set(const char *func);
    virtual int _a64cpu_is_transfer_from_cpu_available(bool *available);
    virtual int _a64cpu_can_transfer_to_cpu(bool *can_transfer);
    virtual int _a64cpu_get_current_el_hw(uint32_t *el);
    virtual int _a64cpu_is_current_state_aarch64_hw(bool *is_64_bit);
    virtual int _a64cpu_read_edscr(const char *func, uint32_t *value, bool *logErr);
    virtual int _a64cpu_check_edscr_err(uint32_t mask, uint32_t match);
    virtual int _a64cpu_clear_edscr_err(const char *func);

    /*!
     @function _a64cpu_modify_edscr
     @discussion Handing function to modify the EDSCR which we do lot. This function is similar to set_mmreg_bit
                 but only used for EDSCR. Usually used to modify the bits defined by M_EDSCR_CONTROL
     @param func name of function calling
     @param mask Mask for the bit to be set
     @param value Value to set it, offset to the right bit (so its usually just the mask again or 0)
     @param valDesc Desc of the bit being set.
     @param confirm Confirm if the bit actually got set.
     @result astris error code , will return ASTRIS_ERR_NOTFOUND if confirm flag was set and bit was not set.
     */
    virtual int _a64cpu_modify_edscr(const char *func, uint32_t mask, uint32_t value, const char *valDesc, bool confirm = false);

    //
    // Exceptions and exception levels
    //

    virtual int _get_el_implemented(void);

    virtual int _check_exception(const char *func, bool aborts_only);

    virtual int _handle_exception_for_el(uint32_t edscr, uint32_t current_el, bool aborts_only, int &category);
    virtual int _a64cpu_get_highest_el();

    virtual int _a64cpu_clear_async(const char *func) {ax_os_log(_log_key, AXL_STD, "{%s->%s}", func, __FUNCTION__); return ASTRIS_ERR_OK;}
    virtual int _a64cpu_el_fault_reg_latch(uint32_t el, bool for_fault);

//    virtual int _a64cpu_raise_el_hw(const char *func, uint32_t el);
    virtual int _a64cpu_raise_el_hw_safe(const char *func, uint32_t el);

//    virtual int _a64cpu_lower_el_hw(const char *func, uint32_t el);
    virtual int _a64cpu_lower_el_hw_safe(const char *func, uint32_t target_el);
    virtual int _a64cpu_restore_el_and_spsr(const char *func, uint32_t el);

    //
    // Encoding enstructions
    //
    virtual uint32_t _a64cpu_msr_mrs_encoding_for_sysreg_id(AXregid_t reg, bool aarch64);
    virtual uint32_t _a64cpu_encode_msr(AXregid_t sysreg, AXregid_t src_gpr, bool aarch64);
    virtual uint32_t _a64cpu_encode_mrs(AXregid_t dest_gpr, AXregid_t sysreg, bool aarch64);
    virtual uint32_t _a64cpu_encode_mov_vfp_to_gpr(AXregid_t dest_gpr, AXregid_t src_vect, bool top, bool aarch64);
    virtual uint32_t _a64cpu_encode_mov_gpr_to_vfp(AXregid_t dest_vect, AXregid_t src_gpr, bool top, bool aarch64);
    virtual uint32_t _a64cpu_encode_aarch64_add_imm(AXregid_t dest_gpr, AXregid_t src_gpr);
    virtual uint32_t _a64cpu_encode_ldrstr_postinc(AXregid_t dest_reg, AXregid_t base_reg, uint32_t size, uint32_t postinc, bool is_store, bool arch64);
    virtual uint32_t _a64cpu_encode_hint(uint32_t crm, uint32_t op2);

    //
    // Halt, resume, data transfer
    //
    virtual int _a64cpu_read_transfer_half_duplex_fast(const char *func, uint64_t *val);
    virtual int _a64cpu_read_transfer_full_duplex_fast(const char *func, uint32_t *val);
    virtual int _a64cpu_read_transfer_half_duplex(const char *func, uint64_t *val);
    virtual int _a64cpu_read_transfer_full_duplex(const char *func, uint32_t *val);
    virtual int _a64cpu_read_transfer_safe(const char *func, bool full_duplex, uint32_t *val32, uint64_t *val64);
    virtual int _a64cpu_write_transfer_half_duplex_fast(const char *func, uint64_t val);
    virtual int _a64cpu_write_transfer_full_duplex_fast(const char *func, uint32_t val);
    virtual int _a64cpu_write_transfer_half_duplex(const char *func, uint64_t val);
    virtual int _a64cpu_write_transfer_full_duplex(const char *func, uint32_t val);
    virtual int _a64cpu_write_transfer_safe(const char *func, bool full_duplex, uint32_t val32, uint64_t val64);
    virtual int _a64cpu_unhalt_cpu(const char *func);
    virtual int _a64cpu_set_singlestep(bool step);

    //
    // DAP
    //
    virtual int _a64cpu_read_edreg32(AXregaddr_t reg, uint32_t *value, const char *func, const char *regName);
    virtual int _a64cpu_write_edreg32(AXregaddr_t reg, uint32_t value, const char *func, const char *regName, const char *valDesc);
    virtual int _a64cpu_read_edreg64(AXregaddr_t reg, uint64_t *value, const char *func, const char *regName);
    virtual int _a64cpu_write_edreg64(AXregaddr_t reg, uint64_t value, const char *func, const char *regName, const char *valDesc);
    virtual int _a64cpu_read_cti32(AXregaddr_t reg, uint32_t *value, const char *func, const char *regName);
    virtual int _a64cpu_write_cti32(AXregaddr_t reg, uint32_t value, const char *func, const char *regName, const char *valDesc);

    //
    // Misc
    //
    virtual int _a64cpu_assert_state_valid_entry_exit();
    virtual int _a64cpu_assert_rw_matches_hw(bool believed_64bit);

    static const uint32_t G_ARM64_EDSCR_STATUS(uint32_t edscr) {return _MM_GETVALUE(edscr, S_EDSCR_STATUS, M_EDSCR_STATUS);}

    static uint32_t G_AA64DFR0_BRP_COUNT(uint32_t dfr) {return _MM_GETVALUE(dfr, S_AA64DFR0_BRP_COUNT, M_AA64DFR0_BRP_COUNT) + 1;}
    static uint32_t G_AA64DFR0_WRP_COUNT(uint32_t dfr) {return _MM_GETVALUE(dfr, S_AA64DFR0_WRP_COUNT, M_AA64DFR0_WRP_COUNT) + 1;}

    static uint32_t G_CCSIDR_LINESIZE(uint32_t ccsidr) {return _MM_GETVALUE(ccsidr, S_CCSIDR_LINESIZE, M_CCSIDR_LINESIZE);}
    static uint32_t G_CCSIDR_NUMSETS(uint32_t ccsidr)  {return _MM_GETVALUE(ccsidr, S_CCSIDR_NUMSETS, M_CCSIDR_NUMSETS);}

    static void _get_clidr_max_cache_levels(uint32_t clidr, uint32_t *maxi, uint32_t *maxd)
    {
        int i;

        for (i = 0; i < 7; i++) {
            if ((clidr & 0x7) == 0) {
                break;
            } else {
                if ((clidr & 0x2) != 0) {
                    *maxd = i + 1;
                }
                if ((clidr & 0x1) != 0) {
                    *maxi = i + 1;
                }

            }

            clidr >>= 3;
        }
    }

    uint32_t m_ic_max_level = 0; // Maximum I-cache level
    uint32_t m_dc_max_level = 0; // Maximum D-cache level

    static uint32_t _a64cpu_esr_to_ec(AXregval_t esr) {return (uint32_t)((esr & ARM64_ESR_EC_MASK) >> ARM64_ESR_EC_SHIFT);}

    const uint64_t debug_sys_offset;

    static const AXregid_t ARM64_REG_CURRENT_EL = AARCH64_SYSREG_ENCODING(3, 0, 4, 2, 2);
    static const AXregid_t ARM64_REG_DAIF       = AARCH64_SYSREG_ENCODING(3, 3, 4, 2, 1);
    static const AXregid_t ARM64_REG_SPSEL      = AARCH64_SYSREG_ENCODING(3, 0, 4, 2, 0);
    static const AXregid_t ARM64_REG_NZVC       = AARCH64_SYSREG_ENCODING(3, 3, 4, 2, 0);
};

#endif /* _ASTRIS_ARMCPU_H */
