//
//  astris_armv7mcpu.h
//  Astris
//
//  Created by Kip Walker on 1/5/12.
//  Copyright (c) 2012-2016  Apple Inc. All rights reserved.
//

#ifndef Astris_astris_armv7m_h
#define Astris_astris_armv7m_h

#include "astris_armcpu.h"
#include <vector>
#include <string>

// Debug register definitions (in SCS space at 0xE000E000)

#define R_v7M_SCS_CPUID 0xD00
#define R_v7M_DFSR      0xD30
#define R_v7M_CPACR     0xD88
#define R_v7M_DHCSR     0xDF0
#define R_v7M_DCRSR     0xDF4
#define R_v7M_DCRDR     0xDF8
#define R_v7M_DEMCR     0xDFC

#define M_v7M_DFSR_HALTED   _MM_MAKEMASK1(0)
#define M_v7M_DFSR_BKPT     _MM_MAKEMASK1(1)
#define M_v7M_DFSR_DWTTRAP  _MM_MAKEMASK1(2)
#define M_v7M_DFSR_VCATCH   _MM_MAKEMASK1(3)
#define M_v7M_DFSR_EXTERNAL _MM_MAKEMASK1(4)

#define K_v7M_DHCSR_DBGKEY 0xA05F0000

#define M_v7M_DHCSR_C_DEBUG_EN  _MM_MAKEMASK1(0)
#define M_v7M_DHCSR_C_HALT      _MM_MAKEMASK1(1)
#define M_v7M_DHCSR_C_STEP      _MM_MAKEMASK1(2)
#define M_v7M_DHCSR_C_MASKINTS  _MM_MAKEMASK1(3)
#define M_v7M_DHCSR_C_SNAPSTALL _MM_MAKEMASK1(4)

#define M_v7M_DHCSR_S_REGRDY    _MM_MAKEMASK1(16)
#define M_v7M_DHCSR_S_HALT      _MM_MAKEMASK1(17)
#define M_v7M_DHCSR_S_SLEEP     _MM_MAKEMASK1(18)
#define M_v7M_DHCSR_S_LOCKUP    _MM_MAKEMASK1(19)
#define M_v7M_DHCSR_S_RETIRE_ST _MM_MAKEMASK1(24)
#define M_v7M_DHCSR_S_RESET_ST  _MM_MAKEMASK1(25)

#define M_v7M_DCRSR_REGWnR    _MM_MAKEMASK1(16)
#define V_v7M_DCRSR_REGSEL(x) (x)
#define G_v7M_DCRSR_REGSEL(x) ((x) & 0x01F)

#define K_v7M_DCRSR_PSR   0x10
#define K_v7M_DCRSR_MSP   0x11
#define K_v7M_DCRSR_PSP   0x12
#define K_v7M_DCRSR_RAZ   0x13
#define K_v7M_DCRSR_SFR   0x14 // CONTROL bits [31:24] FAULTMASK bits [23:16] BASEPRI bits [15:8] PRIMASK bits [7:0]
#define K_v7M_DCRSR_FPSCR 0x21 // Floating-point Status and Control Register, FPSCR
#define K_v7M_DCRSR_S0    0x40 // Floating point register S0
#define K_v7M_DCRSR_S31   0x5F // Floating point register S31

#define K_v8M_MSP_NS    0x18
#define K_v8M_PSP_NS    0x19
#define K_v8M_MSP_S     0x1A
#define K_v8M_PSP_S     0x1B
#define K_v8M_MSPLIM_S  0x1C
#define K_v8M_PSPLIM_S  0x1D
#define K_v8M_MSPLIM_NS 0x1E
#define K_v8M_PSPLIM_NS 0x1F

#define K_v8M_SPECIAL_NS 0x22
#define K_v8M_SPECIAL_S  0x23

#define M_v7M_DEMCR_TRCENA _MM_MAKEMASK1(24)

#define S_v7M_SCS_CPUID_PARTNO    4
#define M_v7M_SCS_CPUID_PARTNO    _MM_MAKEMASK(12, S_v7M_SCS_CPUID_PARTNO)
#define V_v7M_SCS_CPUID_PARTNO(v) _MM_MAKEVALUE((v), S_v7M_SCS_CPUID_PARTNO)
#define G_v7M_SCS_CPUID_PARTNO(v) _MM_GETVALUE((v), S_v7M_SCS_CPUID_PARTNO, M_v7M_SCS_CPUID_PARTNO)

#define K_v7M_SCS_CPUID_PARTNO_CORTEX_M0  0xC20
#define K_v7M_SCS_CPUID_PARTNO_CORTEX_M0P 0xC60
#define K_v7M_SCS_CPUID_PARTNO_CORTEX_M3  0xC23
#define K_v7M_SCS_CPUID_PARTNO_CORTEX_M4  0xC24
#define K_v7M_SCS_CPUID_PARTNO_CORTEX_M7  0xC27
#define K_v7M_SCS_CPUID_PARTNO_CORTEX_M33 0xD21
#define K_v7M_SCS_CPUID_PARTNO_CORTEX_M23 0xD20

#define S_v7M_SCS_CPUID_REV    0
#define M_v7M_SCS_CPUID_REV    _MM_MAKEMASK(4, S_v7M_SCS_CPUID_REV)
#define V_v7M_SCS_CPUID_REV(v) _MM_MAKEVALUE((v), S_v7M_SCS_CPUID_REV)
#define G_v7M_SCS_CPUID_REV(v) _MM_GETVALUE((v), S_v7M_SCS_CPUID_REV, M_v7M_SCS_CPUID_REV)

// System Control Space Cache-related register definitions

#define R_v7M_CLIDR    0xD78
#define R_v7M_CCSIDR   0xD80
#define R_v7M_CSSELR   0xD84
#define R_v7M_ICIALLU  0xF50
#define R_v7M_ICIMVAU  0xF58
#define R_v7M_DCIMVAC  0xF5C
#define R_v7M_DCISW    0xF60
#define R_v7M_DCCMVAU  0xF64
#define R_v7M_DCCMVAC  0xF68
#define R_v7M_DCCSW    0xF6C
#define R_v7M_DCCIMVAC 0xF70
#define R_v7M_DCCISW   0xF74

// Flash patch / breakpoint register definitions

#define A_v7M_FPB_BASE 0xE0002000

#define R_v7M_FPB_CTRL     0x000
#define R_v7M_FPB_REMAP    0x004
#define R_v7M_FPB_COMP(_n) (0x008 + ((_n) * 4))

#define M_v7M_FPCR_ENABLE       _MM_MAKEMASK1(0)
#define M_v7M_FPCR_KEY          _MM_MAKEMASK1(1)
#define S_v7M_FPCR_NUM_CODE1    4
#define M_v7M_FPCR_NUM_CODE1    _MM_MAKEMASK(4, S_v7M_FPCR_NUM_CODE1)
#define G_v7M_FPCR_NUM_CODE1(v) _MM_GETVALUE((v), S_v7M_FPCR_NUM_CODE1, M_v7M_FPCR_NUM_CODE1)
#define S_v7M_FPCR_NUM_CODE2    12
#define M_v7M_FPCR_NUM_CODE2    _MM_MAKEMASK(2, S_v7M_FPCR_NUM_CODE2)
#define G_v7M_FPCR_NUM_CODE2(v) _MM_GETVALUE((v), S_v7M_FPCR_NUM_CODE2, M_v7M_FPCR_NUM_CODE2)
#define S_v7M_FPCR_REV          28
#define M_v7M_FPCR_REV          _MM_MAKEMASK(4, S_v7M_FPCR_REV)
#define G_v7M_FPCR_REV(v)       _MM_GETVALUE(v, S_v7M_FPCR_REV, M_v7M_FPCR_REV)

#define V6M_NUM_BP(v) G_v7M_FPCR_NUM_CODE1(v)
#define V7M_NUM_BP(v) ((G_v7M_FPCR_NUM_CODE2(v) << 4) | G_v7M_FPCR_NUM_CODE1(v))

#define M_v7M_FPCOMP_ENABLED    _MM_MAKEMASK1(0)
#define S_v7M_FPCOMP_COMP       2
#define M_v7M_FPCOMP_COMP       _MM_MAKEMASK(27, S_v7M_FPCOMP_COMP)
#define S_v7M_FPCOMP_REPLACE    30
#define M_v7M_FPCOMP_REPLACE    _MM_MAKEMASK(2, S_v7M_FPCOMP_REPLACE)
#define V_v7M_FPCOMP_REPLACE(v) _MM_MAKEVALUE((v), S_v7M_FPCOMP_REPLACE)
#define G_v7M_FPCOMP_REPLACE(v) _MM_GETVALUE((v), S_v7M_FPCOMP_REPLACE, M_v7M_FPCOMP_REPLACE)

#define K_v7M_FPCOMP_REPLACE_LOW  1
#define K_v7M_FPCOMP_REPLACE_HIGH 2
#define K_v7M_FPCOMP_REPLACE_BOTH 3

// Rev 2 Of FPB
#define S_v7M_FPCOMP_BPADDR 1
#define M_v7M_FPCOMP_BPADDR 0xFFFFFFFE

// Data watchpoint / trace register definitions

#define A_v7M_DWT_BASE 0xE0001000

#define R_v7M_DWT_CTRL         0x000
#define R_v7M_DWT_COMP(_n)     (0x020 + ((_n) * 0x10))
#define R_v7M_DWT_MASK(_n)     (0x024 + ((_n) * 0x10))
#define R_v7M_DWT_FUNCTION(_n) (0x028 + ((_n) * 0x10))

#define S_v7M_DWTCTRL_NUMCOMP    28
#define M_v7M_DWTCTRL_NUMCOMP    _MM_MAKEMASK(4, S_v7M_DWTCTRL_NUMCOMP)
#define G_v7M_DWTCTRL_NUMCOMP(v) _MM_GETVALUE((v), S_v7M_DWTCTRL_NUMCOMP, M_v7M_DWTCTRL_NUMCOMP)

#define S_v7M_DWTFUNC_FUNC    0
#define M_v7M_DWTFUNC_FUNC    _MM_MAKEMASK(4, S_v7M_DWTFUNC_FUNC)
#define V_v7M_DWTFUNC_FUNC(v) _MM_MAKEVALUE((v), S_v7M_DWTFUNC_FUNC)
#define G_v7M_DWTFUNC_FUNC(v) _MM_GETVALUE((v), S_v7M_DWTFUNC_FUNC, M_v7M_DWTFUNC_FUNC)

#define K_v7M_DWTFUNC_FUNC_DISABLED 0
#define K_v7M_DWTFUNC_FUNC_READ     5
#define K_v7M_DWTFUNC_FUNC_WRITE    6
#define K_v7M_DWTFUNC_FUNC_ANY      7

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

typedef enum {
    K_MEMMAP_IMEM,       // 0
    K_MEMMAP_DMEM,       // 1
    K_MEMMAP_PERIPH,     // 2
    K_MEMMAP_DWT_FPB,    // 3
    K_MEMMAP_SCS,        // 4
    K_MEMMAP_FLATMAPPED, // 5     // 1 to 1 mapped
    K_MEMMAP_CUSTOM,     // 6     // Custom region uses regular mapping
    K_MEMMAP_GENERIC,    // 7     // Generic which means take offset as start of address
    K_MEMMAP_END,        // 8
} MemMapType_t;

typedef struct MemMap_s {
    AXmemaddr_t  baseAddr; // base of range of Cortex-M3 CPU addresses
    AXmemaddr_t  range;    // size of range
    AXmemaddr_t  offset;   // offset within mapped APB-DBG address space
    std::string  name;
    MemMapType_t type;
} MemMap_t;

// XXX this should be under ARM32cpu_t
class ARMv7mcpu_t:
    public ARMcpu_t {
public:
    ARMv7mcpu_t(AXtgt_t *tgt, const char *name, uint32_t id);
    virtual ~ARMv7mcpu_t();

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

    virtual int cpu_mode(const char **mode, uint32_t cpsr) {*mode = K_ARM_MODE_THUMB; return ASTRIS_ERR_OK;}
    virtual int read_dcc(int *got_val, uint32_t *val)      {return ASTRIS_ERR_OK;}

    virtual int translate(AXmemaddr_t va, AXmemaddr_t *pa, bool write, int *result);

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

    virtual void add_region(MemMapType_t type, AXmemaddr_t base_address, uint64_t range, uint64_t offset, const char *name);

    virtual int  create_address_map(struct cpu_desc_t *cpu_des) {return ASTRIS_ERR_OK;}
    virtual bool if_error(struct cpu_desc_t *cpu_des)           {return false;}
    virtual int  r0unmap_and_aslr_translate(AXmemaddr_t va, AXmemaddr_t *translated_va) {return ASTRIS_ERR_NOTSUPPORTED;}

    bool _configured = true;

private:
    char *_log_key; // ARMv7M CPU logging domain

protected:
    uint8_t _fpb_rev;

    std::vector <MemMap_t> _map;
    AXmemaddr_t _baseaddr; // Address of internal SCS registers.   Memory is mapped relative to this.

    uint32_t m_hasFPU;
    uint32_t m_hasCache;
    uint32_t m_cputype;
    uint32_t m_arch;

    // Section B3.1 ARMv7m Architecture Specification
    // The END mappings are inclusive
    static constexpr AXmemaddr_t CODE_START = 0x00000000;
    static constexpr AXmemaddr_t CODE_END   = 0x20000000;

    static constexpr AXmemaddr_t SRAM_START = 0x20000000;
    static constexpr AXmemaddr_t SRAM_END   = 0x40000000;

    // There are 2 "RAM" sections this one has following cache attributes:
    // Memory with write-back, write allocate cache attribute for L2/L3 cache support
    static constexpr AXmemaddr_t RAM0_START = 0x60000000;
    static constexpr AXmemaddr_t RAM0_END   = 0x80000000;

    // This RAM section has following cache attributes:
    // Write-through cache attributes
    static constexpr AXmemaddr_t RAM1_START = 0x80000000;
    static constexpr AXmemaddr_t RAM1_END   = 0xA0000000;

    static constexpr AXmemaddr_t DEVICE_START = 0xA0000000;
    static constexpr AXmemaddr_t DEVICE_END   = 0xE0000000;

    virtual int _get_type_hw() {return m_cputype;}
    virtual int _get_arch_hw() {return m_arch;}

    virtual bool _init_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 void _init_params();

    //
    // Memory access helpers
    //
    virtual int         _memory_quantum()                                      {return ARMV7M_MEMORY_QUANTUM_DEFAULT;}
    virtual AXmemaddr_t _memory_max()                                          {return UINT32_MAX;}
    virtual bool        _memory_fastmode(AXmemaddr_t address, uint32_t length) {return (length > 11) || (!(address & (_memory_quantum() - 1)) && !(length & (_memory_quantum() - 1)));}
    virtual int         _memory_set_base(AXmemaddr_t address)                  {return ASTRIS_ERR_OK;}
    virtual int         _read_memory_slow(AXmemaddr_t address, uint32_t *length, uint8_t *buffer);
    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(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_l2cache_setway(uint32_t set, uint32_t way);

    // Helper function to perform a sequence of cache operations via memory-mapped I/O:
    virtual int _iterate_v7m_cacheop_write(uint32_t cache_register, AXmemaddr_t addr, uint32_t len, int linebytes);

    //
    // Low-level breakpoint/watchpoint operations
    //
    virtual int _get_num_hwbp_hw(int *numbp);
    virtual int _get_num_hwwp_hw(int *numwp);
    virtual int _setup_bp(bpwp_info_t *info, int *bp_state);
    virtual int _software_breakpoint(uint32_t *len, uint32_t *inst);
    virtual int _setup_wp(bpwp_info_t *info, int *wp_state);
    virtual int _set_bpwp(int op, bpwp_info_t *info);
    virtual int _insert_breakpoint(bpwp_info_t *info, AXmemaddr_t address);
    virtual int _remove_breakpoint(int which_bp);
    virtual int _insert_watchpoint(bpwp_info_t *info, AXmemaddr_t address, uint32_t len, int rdwr);
    virtual int _remove_watchpoint(int which_wp);
    virtual int _write_executable_memory(bpwp_info_t *bp_info);

    //
    // Miscellaneous operations
    //
    virtual int _init_demcr_val(uint32_t *new_demcr);

    virtual int address_remap(AXmemaddr_t *addr); // Function to take an M3 standard address and compute the actual one.

    int _read_dbg(uint32_t offset, uint32_t *val)
    {
        return _read_dap(offset, val, 1);
    }

    int _write_dbg(uint32_t offset, uint32_t val)
    {
        return _write_dap(offset, &val, 1);
    }

    int _read_fpb(uint32_t offset, uint32_t *val)
    {
        AXmemaddr_t address = A_v7M_FPB_BASE + offset;
        address_remap(&address);
        return AXmemap_read(_memap, address, val);
    }

    int _write_fpb(uint32_t offset, uint32_t val)
    {
        AXmemaddr_t address = A_v7M_FPB_BASE + offset;
        address_remap(&address);
        return AXmemap_write(_memap, address, val);
    }

    int _read_dwt(uint32_t offset, uint32_t *val)
    {
        AXmemaddr_t address = A_v7M_DWT_BASE + offset;
        address_remap(&address);
        return AXmemap_read(_memap, address, val);
    }

    int _write_dwt(uint32_t offset, uint32_t val)
    {
        AXmemaddr_t address = A_v7M_DWT_BASE + offset;
        address_remap(&address);
        return AXmemap_write(_memap, address, val);
    }

    int _get_gpr(uint32_t gpr, uint32_t *val);
    int _set_gpr(uint32_t gpr, uint32_t val);
    int _update_register(uint32_t reg, uint32_t val, int commit);

private:

    // This is CPU register-cache, not I$/D$...
    void _clear_cache(void)
    {
        gpr_cached = 0;
        gpr_dirty  = 0;
        reg_cached = 0;
        reg_dirty  = 0;
    }

    uint32_t gpr_cached;
    uint32_t gpr_dirty;
    uint32_t gpr[16];
    uint32_t reg_cached;
    uint32_t reg_dirty;
    uint32_t cpsr;

    const char *m_cpuname;

    static const int m_wptype_map[];

};

#endif // ifndef Astris_astris_armv7m_h
