/**
 * @file thread_context.h
 * @brief AArch64 register dump format and related definitions.
 * @author TuxSH
 * @copyright libnx Authors
 */

#pragma once
#include "../types.h"

/// Armv8 CPU register.
typedef union {
    u64 x; ///< 64-bit AArch64 register view.
    u32 w; ///< 32-bit AArch64 register view.
    u32 r; ///< AArch32 register view.
} CpuRegister;

/// Armv8 NEON register.
typedef union {
    u128    v; ///< 128-bit vector view.
    double  d; ///< 64-bit double-precision view.
    float   s; ///< 32-bit single-precision view.
} FpuRegister;

/// Armv8 register group. @ref svcGetThreadContext3 uses @ref RegisterGroup_All.
typedef enum {
    RegisterGroup_CpuGprs = BIT(0), ///< General-purpose CPU registers (x0..x28 or r0..r10,r12).
    RegisterGroup_CpuSprs = BIT(1), ///< Special-purpose CPU registers (fp, lr, sp, pc, PSTATE or cpsr, TPIDR_EL0).
    RegisterGroup_FpuGprs = BIT(2), ///< General-purpose NEON registers.
    RegisterGroup_FpuSprs = BIT(3), ///< Special-purpose NEON registers.

    RegisterGroup_CpuAll  = RegisterGroup_CpuGprs | RegisterGroup_CpuSprs, ///< All CPU registers.
    RegisterGroup_FpuAll  = RegisterGroup_FpuGprs | RegisterGroup_FpuSprs, ///< All NEON registers.
    RegisterGroup_All     = RegisterGroup_CpuAll  | RegisterGroup_FpuAll,  ///< All registers.
} RegisterGroup;

/// This is for \ref ThreadExceptionDump error_desc.
typedef enum {
    ThreadExceptionDesc_InstructionAbort = 0x100, ///< Instruction abort
    ThreadExceptionDesc_MisalignedPC     = 0x102, ///< Misaligned PC
    ThreadExceptionDesc_MisalignedSP     = 0x103, ///< Misaligned SP
    ThreadExceptionDesc_SError           = 0x106, ///< SError [not in 1.0.0?]
    ThreadExceptionDesc_BadSVC           = 0x301, ///< Bad SVC
    ThreadExceptionDesc_Trap             = 0x104, ///< Uncategorized, CP15RTTrap, CP15RRTTrap, CP14RTTrap, CP14RRTTrap, IllegalState, SystemRegisterTrap
    ThreadExceptionDesc_Other            = 0x101, ///< None of the above, EC <= 0x34 and not a breakpoint
} ThreadExceptionDesc;

/// Thread context structure (register dump)
typedef struct {
    CpuRegister cpu_gprs[29];   ///< GPRs 0..28. Note: also contains AArch32 SPRs.
    u64 fp;                     ///< Frame pointer (x29) (AArch64). For AArch32, check r11.
    u64 lr;                     ///< Link register (x30) (AArch64). For AArch32, check r14.
    u64 sp;                     ///< Stack pointer (AArch64). For AArch32, check r13.
    CpuRegister pc;             ///< Program counter.
    u32         psr;            ///< PSTATE or cpsr.

    FpuRegister fpu_gprs[32];   ///< 32 general-purpose NEON registers.
    u32         fpcr;           ///< Floating-point control register.
    u32         fpsr;           ///< Floating-point status register.

    u64         tpidr;          ///< EL0 Read/Write Software Thread ID Register.
} ThreadContext;

/// Thread exception dump structure.
typedef struct {
    u32 error_desc;             ///< See \ref ThreadExceptionDesc.
    u32 pad[3];

    CpuRegister cpu_gprs[29];   ///< GPRs 0..28. Note: also contains AArch32 registers.
    CpuRegister fp;             ///< Frame pointer.
    CpuRegister lr;             ///< Link register.
    CpuRegister sp;             ///< Stack pointer.
    CpuRegister pc;             ///< Program counter (elr_el1).

    u64 padding;

    FpuRegister fpu_gprs[32];   ///< 32 general-purpose NEON registers.

    u32 pstate;                 ///< pstate & 0xFF0FFE20
    u32 afsr0;
    u32 afsr1;
    u32 esr;

    CpuRegister far;            ///< Fault Address Register.
} ThreadExceptionDump;

typedef struct {
    u64 cpu_gprs[9]; ///< GPRs 0..8.
    u64 lr;
    u64 sp;
    u64 elr_el1;
    u32 pstate;       ///< pstate & 0xFF0FFE20
    u32 afsr0;
    u32 afsr1;
    u32 esr;
    u64 far;
} ThreadExceptionFrameA64;

typedef struct {
    u32 cpu_gprs[8]; ///< GPRs 0..7.
    u32 sp;
    u32 lr;
    u32 elr_el1;
    u32 tpidr_el0;    ///< tpidr_el0 = 1
    u32 cpsr;         ///< cpsr & 0xFF0FFE20
    u32 afsr0;
    u32 afsr1;
    u32 esr;
    u32 far;
} ThreadExceptionFrameA32;

/**
 * @brief Determines whether a thread context belong to an AArch64 process based on the PSR.
 * @param[in] ctx Thread context to which PSTATE/cspr has been dumped to.
 * @return true if and only if the thread context belongs to an AArch64 process.
 */
static inline bool threadContextIsAArch64(const ThreadContext *ctx)
{
    return (ctx->psr & 0x10) == 0;
}

/**
 * @brief Determines whether a ThreadExceptionDump belongs to an AArch64 process based on the PSTATE.
 * @param[in] ctx ThreadExceptionDump.
 * @return true if and only if the ThreadExceptionDump belongs to an AArch64 process.
 */
static inline bool threadExceptionIsAArch64(const ThreadExceptionDump *ctx)
{
    return (ctx->pstate & 0x10) == 0;
}