/*
 * SPDX-FileCopyrightText: 2017-2025 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include "soc/soc.h"
#include "soc/interrupt_reg.h"
#include "riscv/rvruntime-frames.h"
#include "soc/soc_caps.h"
#include "sdkconfig.h"
#include "esp_private/vectors_const.h"
#include "esp_private/panic_reason.h"

    .equ SAVE_REGS, 32
    .equ CONTEXT_SIZE, (SAVE_REGS * 4)
    .equ EXC_ILLEGAL_INSTRUCTION, 0x2
    .equ panic_from_exception, xt_unhandled_exception
    .equ panic_from_isr, panicHandler
    .equ CSR_UINTSTATUS, 0xCB1
    .equ CSR_UINTTHRESH, 0x047

#if ( SOC_CPU_COPROC_NUM > 0 )
    /* Targets with coprocessors present a special CSR to get Illegal Instruction exception reason */
#ifdef __clang__
    /* Clang does not support constant declared via `equ` as operand for csrrw
    * TODO: LLVM-369
    */
    #define EXT_ILL_CSR 0x7F0
#else
    .equ EXT_ILL_CSR, 0x7F0
#endif

    /* EXT_ILL CSR reasons are stored as follows:
     * - Bit 0: FPU core instruction (Load/Store instructions NOT concerned)
     * - Bit 1: Hardware Loop instructions
     * - Bit 2: PIE core */
    .equ EXT_ILL_RSN_FPU,  1
    .equ EXT_ILL_RSN_HWLP, 2
    .equ EXT_ILL_RSN_PIE,  4
#endif /* SOC_CPU_COPROC_NUM > 0 */

/* Macro which first allocates space on the stack to save general
 * purpose registers, and then save them. GP register is excluded.
 * The default size allocated on the stack is CONTEXT_SIZE, but it
 * can be overridden. */
.macro save_general_regs cxt_size=CONTEXT_SIZE
    addi sp, sp, -\cxt_size
    sw   ra, RV_STK_RA(sp)
    sw   tp, RV_STK_TP(sp)
    sw   t0, RV_STK_T0(sp)
    sw   t1, RV_STK_T1(sp)
    sw   t2, RV_STK_T2(sp)
    sw   s0, RV_STK_S0(sp)
    sw   s1, RV_STK_S1(sp)
    sw   a0, RV_STK_A0(sp)
    sw   a1, RV_STK_A1(sp)
    sw   a2, RV_STK_A2(sp)
    sw   a3, RV_STK_A3(sp)
    sw   a4, RV_STK_A4(sp)
    sw   a5, RV_STK_A5(sp)
    sw   a6, RV_STK_A6(sp)
    sw   a7, RV_STK_A7(sp)
    sw   s2, RV_STK_S2(sp)
    sw   s3, RV_STK_S3(sp)
    sw   s4, RV_STK_S4(sp)
    sw   s5, RV_STK_S5(sp)
    sw   s6, RV_STK_S6(sp)
    sw   s7, RV_STK_S7(sp)
    sw   s8, RV_STK_S8(sp)
    sw   s9, RV_STK_S9(sp)
    sw   s10, RV_STK_S10(sp)
    sw   s11, RV_STK_S11(sp)
    sw   t3, RV_STK_T3(sp)
    sw   t4, RV_STK_T4(sp)
    sw   t5, RV_STK_T5(sp)
    sw   t6, RV_STK_T6(sp)
.endm

/* Macro for saving special registers depending on the active execution
 * environment - REE - U-mode / TEE - M-mode */
/* Save the XEPC register  */
.macro save_xepc
    #if CONFIG_SECURE_ENABLE_TEE
    csrr t0, uepc
    #else
    csrr t0, mepc
    #endif
    sw   t0, RV_STK_MEPC(sp)
.endm

/* Save the required CSRs */
.macro save_xcsr
    #if CONFIG_SECURE_ENABLE_TEE
    csrr  t0, ustatus
    sw    t0, RV_STK_MSTATUS(sp)
    csrr  t0, utvec
    sw    t0, RV_STK_MTVEC(sp)
    /* NOTE: C6 and H2 possess the utval CSR, but rest of the SoCs don't */
    #if CONFIG_IDF_TARGET_ESP32C6 || CONFIG_IDF_TARGET_ESP32H2
    csrr  t0, utval
    sw    t0, RV_STK_MTVAL(sp)
    #endif
    #else
    csrr  t0, mstatus
    sw    t0, RV_STK_MSTATUS(sp)
    csrr  t0, mtvec
    sw    t0, RV_STK_MTVEC(sp)
    csrr  t0, mhartid
    sw    t0, RV_STK_MHARTID(sp)
    csrr  t0, mtval
    sw    t0, RV_STK_MTVAL(sp)
    #endif
.endm

/* Restore the general purpose registers (excluding gp) from the context on
 * the stack. The context is then deallocated. The default size is CONTEXT_SIZE
 * but it can be overridden. */
.macro restore_general_regs cxt_size=CONTEXT_SIZE
    lw   ra, RV_STK_RA(sp)
    lw   tp, RV_STK_TP(sp)
    lw   t0, RV_STK_T0(sp)
    lw   t1, RV_STK_T1(sp)
    lw   t2, RV_STK_T2(sp)
    lw   s0, RV_STK_S0(sp)
    lw   s1, RV_STK_S1(sp)
    lw   a0, RV_STK_A0(sp)
    lw   a1, RV_STK_A1(sp)
    lw   a2, RV_STK_A2(sp)
    lw   a3, RV_STK_A3(sp)
    lw   a4, RV_STK_A4(sp)
    lw   a5, RV_STK_A5(sp)
    lw   a6, RV_STK_A6(sp)
    lw   a7, RV_STK_A7(sp)
    lw   s2, RV_STK_S2(sp)
    lw   s3, RV_STK_S3(sp)
    lw   s4, RV_STK_S4(sp)
    lw   s5, RV_STK_S5(sp)
    lw   s6, RV_STK_S6(sp)
    lw   s7, RV_STK_S7(sp)
    lw   s8, RV_STK_S8(sp)
    lw   s9, RV_STK_S9(sp)
    lw   s10, RV_STK_S10(sp)
    lw   s11, RV_STK_S11(sp)
    lw   t3, RV_STK_T3(sp)
    lw   t4, RV_STK_T4(sp)
    lw   t5, RV_STK_T5(sp)
    lw   t6, RV_STK_T6(sp)
    addi sp,sp, \cxt_size
.endm

/* Macro for restoring special registers depending on the active execution
 * environment - REE - U-mode / TEE - M-mode */

/* Restore the XEPC register depending on the active execution
 * environment - REE - U-mode / TEE - M-mode */
.macro restore_xepc
    lw      t0, RV_STK_MEPC(sp)
#if CONFIG_SECURE_ENABLE_TEE
    csrw    uepc, t0
#else
    csrw    mepc, t0
#endif
.endm

/* Macros for enabling/disabling the global interrupts based on the
 * active execution environment - REE - U-mode / TEE - M-mode */
.macro enable_intr
#if CONFIG_SECURE_ENABLE_TEE
    li      t0, 0x1
    csrs    ustatus, t0
#else
    li      t0, 0x8
    csrs    mstatus, t0
#endif
.endm

.macro disable_intr
#if CONFIG_SECURE_ENABLE_TEE
    li      t0, 0x1
    csrc    ustatus, t0
#else
    li      t0, 0x8
    csrc    mstatus, t0
#endif
.endm

.macro xret
#if CONFIG_SECURE_ENABLE_TEE
    uret
#else
    mret
#endif
.endm

    .global rtos_int_enter
    .global rtos_int_exit
    .global rtos_save_fpu_coproc
    .global _global_interrupt_handler

/* TODO: IDF-12727 */
#if SOC_CPU_HAS_FPU
    .weak rtos_save_fpu_coproc
    .type rtos_save_fpu_coproc, @function

rtos_save_fpu_coproc:
    ret
#endif

#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
    .global gdbstub_handle_debug_int
#endif

    .section .exception_vectors.text, "ax"

    /* Exception handler.*/
    .type _panic_handler, @function
    .global _panic_handler
_panic_handler:
    /* Allocate space on the stack and store general purpose registers */
    save_general_regs RV_STK_FRMSZ

    /* As gp register is not saved by the macro, save it here */
    sw    gp, RV_STK_GP(sp)

    /* Same goes for the SP value before trapping */
    addi  t0, sp, RV_STK_FRMSZ /* restore sp with the value when trap happened */

    /* Save CSRs */
    sw    t0, RV_STK_SP(sp)
    save_xepc
    save_xcsr

    /* Keep mcause in s0, only the exception code and interrupt bit are relevant */
#if CONFIG_SECURE_ENABLE_TEE
    csrr  s0, ucause
#else
    csrr  s0, mcause
#endif
    li    t1, VECTORS_MCAUSE_INTBIT_MASK | VECTORS_MCAUSE_REASON_MASK
    and   s0, s0, t1

#if ( SOC_CPU_COPROC_NUM > 0 )
    /* Check if the exception was cause by a coprocessor instruction. If this is the case, we have
     * to lazily save the registers inside the current owner's save area */
    /* Check if the exception is Illegal instruction */
    li    a1, EXC_ILLEGAL_INSTRUCTION
    bne   s0, a1, _panic_handler_not_coproc
    /* In case this is due to a coprocessor, set ra right now to simplify the logic below */
    la    ra, _return_from_exception
    /* EXT_ILL CSR should contain the reason for the Illegal Instruction */
    csrrw a0, EXT_ILL_CSR, zero

#if SOC_CPU_HAS_PIE
    /* Check if the PIE bit is set. */
    andi a1, a0, EXT_ILL_RSN_PIE
    bnez a1, rtos_save_pie_coproc
#endif /* SOC_CPU_HAS_PIE */

    /* We cannot check the HWLP bit in a0 since a hardware bug may set this bit even though no HWLP
     * instruction was executed in the program at all, so check mtval (`t0`) */
#if SOC_CPU_HAS_HWLOOP
    /* HWLP instructions  all have an opcode of 0b0101011 */
    andi  a1, t0, 0b1111111
    addi  a1, a1, -0b0101011
    bnez a1, hwlp_not_used
    /* HWLP used in an ISR, abort */
    mv   a0, sp
    j    vPortCoprocUsedInISR
hwlp_not_used:
#endif /* SOC_CPU_HAS_HWLOOP */

#if SOC_CPU_HAS_FPU
    /* Check if the FPU bit is set. When targets have the FPU reason bug (SOC_CPU_HAS_FPU_EXT_ILL_BUG),
     * it is possible that another bit is set even if the reason is an FPU instruction.
     * For example, bit 1 can be set and bit 0 won't, even if the reason is an FPU instruction. */
    andi  a1, a0, EXT_ILL_RSN_FPU
    bnez  a1, rtos_save_fpu_coproc
    /* The FPU_EXT_ILL bug was fixed on revision 3.0 and higher */
#if SOC_CPU_HAS_FPU_EXT_ILL_BUG && CONFIG_ESP32P4_SELECTS_REV_LESS_V3
    /* If the SOC present the hardware EXT_ILL CSR bug, it doesn't support FPU load/store detection
     * so we have to check the instruction's opcode (in `mtval` = `t0`) */
    andi  a0, t0, 0b1011111
    li    a1, 0b0000111
    /* If opcode is of the form 0b0x00111, the instruction is FLW or FSW */
    beq   a0, a1, rtos_save_fpu_coproc
    /* Check the compressed instructions: C.FLW, C.FSW, C.FLWSP and C.FSWP.
     * All of them have their highest 3 bits to x11 and the lowest bit to 0 */
    li    a0, 0x6001
    and   a0, t0, a0    /* a0 = mtval & 0x6001 */
    li    a1, 0x6000
    beq   a0, a1, rtos_save_fpu_coproc
    /* Check if the instruction is CSR-related */
    andi  a0, t0, 0b1111111
    li    a1, 0b1110011
    bne   a0, a1, _panic_handler_not_fpu
    /* Check if it's CSR number 1 (fflags), 2 (frm) or 3 (fcsr) */
    srli  a0, t0, 20
    addi  a0, a0, -1
    li    a1, 3
    bltu  a0, a1, rtos_save_fpu_coproc
    /* The instruction was not an FPU one, continue the exception */
_panic_handler_not_fpu:

#endif /* SOC_CPU_HAS_FPU_EXT_ILL_BUG */
#endif /* SOC_CPU_HAS_FPU */

_panic_handler_not_coproc:

#endif /* ( SOC_CPU_COPROC_NUM > 0 ) */

    /* Call panic_from_exception(sp) or panic_from_isr(sp)
     * depending on whether we have a pseudo excause or not.
     * If mcause's highest bit is 1, then an interrupt called this routine,
     * so we have a pseudo excause. Else, it is due to a exception, we don't
     * have an pseudo excause */
    mv    a0, sp
    mv    a1, s0

    /* Branches instructions don't accept immediate values, so use t1 to
     * store our comparator */
    li    t0, 0x80000000
    bgeu  a1, t0, _call_panic_handler
    sw    a1, RV_STK_MCAUSE(sp)
#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
    li    t0, 3
    beq   a1, t0, _call_gdbstub_handler
#endif
    call  panic_from_exception
    /* We arrive here if the exception handler has returned. */
    j     _return_from_exception

#ifdef CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME
_call_gdbstub_handler:
    call  gdbstub_handle_debug_int
    j     _return_from_exception
#endif

_call_panic_handler:
    /* Remove highest bit from mcause (a1) register and save it in the structure */
    not   t0, t0
    and   a1, a1, t0
#if CONFIG_SOC_INT_CLIC_SUPPORTED
    /* When CLIC is supported, external interrupts are shifted by 16, deduct this difference from mcause */
    add   a1, a1, -16
#endif // CONFIG_SOC_INT_CLIC_SUPPORTED

#if CONFIG_ESP_INT_WDT_CHECK_CPU1
    /* Check if this was a INT WDT */
    li t0, PANIC_RSN_INTWDT_CPU0
    bne a1, t0, _store_mcause
    /* Check if the cause is the app cpu failing to tick, if so then update mcause to reflect this*/
    lw t0, int_wdt_cpu1_ticked
    bnez t0, _store_mcause
    li t0, PANIC_RSN_INTWDT_CPU1_FLAG
    add a1, a1, t0
#endif

_store_mcause:
    sw    a1, RV_STK_MCAUSE(sp)
    call  panic_from_isr
    /* We arrive here if the exception handler has returned. This means that
     * the exception was handled, and the execution flow should resume.
     * Restore the registers and return from the exception.
     */
_return_from_exception:
    restore_xepc
    /* MTVEC and SP are assumed to be unmodified.
     * MSTATUS, MHARTID, MTVAL are read-only and not restored.
     */
    lw gp,  RV_STK_GP(sp)
    restore_general_regs RV_STK_FRMSZ
    mret
    .size  _panic_handler, .-_panic_handler


    /* This is the interrupt handler.
     * It saves the registers on the stack, prepares for interrupt nesting, re-enables the interrupts,
     * then jumps to the C dispatcher in interrupt.c. Upon return, the register context will be restored
     * from the stack.
     */
    .global _interrupt_handler
    .global _tee_interrupt_handler
    .type _interrupt_handler, @function
_interrupt_handler:
_tee_interrupt_handler:
    /* Start by saving the general purpose registers and the PC value before
     * the interrupt happened. */
    save_general_regs
    save_xepc

    /* Though it is not necessary we save GP and SP here.
     * SP is necessary to help GDB to properly unwind
     * the backtrace of threads preempted by interrupts (OS tick etc.).
     * GP is saved just to have its proper value in GDB. */
    /* As gp register is not saved by the macro, save it here */
    sw      gp, RV_STK_GP(sp)
    /* Same goes for the SP value before trapping */
    addi    a0, sp, CONTEXT_SIZE /* restore sp with the value when interrupt happened */

    /* Save SP former value */
    sw      a0, RV_STK_SP(sp)

    /* Notify the RTOS that an interrupt occurred, it will save the current stack pointer
     * in the running TCB, no need to pass it as a parameter
     * Returns an abstract context in a0, needs to be passed to `rtos_int_exit` */
    call    rtos_int_enter
    mv      s4, a0
    /* If this is a non-nested interrupt, SP now points to the interrupt stack */

    /* Before dispatch c handler, restore interrupt to enable nested intr */
#if CONFIG_SECURE_ENABLE_TEE
    csrr    s1, ucause
    csrr    s2, ustatus
#else
    csrr    s1, mcause
    csrr    s2, mstatus
#endif

#if !SOC_INT_HW_NESTED_SUPPORTED
    /* Save the interrupt threshold level */
    li      t0, INTERRUPT_CURRENT_CORE_INT_THRESH_REG
    lw      s3, 0(t0)

    /* Increase interrupt threshold level */
    li      t2, VECTORS_MCAUSE_REASON_MASK
    and     t1, s1, t2       /* t1 = mcause & mask */
    slli    t1, t1, 2        /* t1 = mcause * 4 */
    li      t2, INTERRUPT_PRIO_REG(0)
    add     t1, t2, t1       /* t1 = INTERRUPT_PRIO_REG + 4 * mcause */
    lw      t2, 0(t1)        /* t2 = INTERRUPT_PRIO_REG[mcause] */
    addi    t2, t2, 1        /* t2 = t2 +1 */
    sw      t2, 0(t0)        /* INTERRUPT_CURRENT_CORE_INT_THRESH_REG = t2 */
    fence
#endif // !SOC_INT_HW_NESTED_SUPPORTED

    enable_intr
    /* MIE set. Nested interrupts can now occur */

    #ifdef CONFIG_PM_TRACE
    li      a0, 0       /* = ESP_PM_TRACE_IDLE */
    #if SOC_CPU_CORES_NUM == 1
    li      a1, 0       /* No need to check core ID on single core hardware */
    #else
    csrr    a1, mhartid
    #endif
    la      t0, esp_pm_trace_exit
    jalr    t0          /* absolute jump, avoid the 1 MiB range constraint */
    #endif

    #ifdef CONFIG_PM_ENABLE
    la      t0, esp_pm_impl_isr_hook
    jalr    t0          /* absolute jump, avoid the 1 MiB range constraint */
    #endif

    /* call the C dispatcher */
    mv      a0, sp      /* argument 1, stack pointer */
    mv      a1, s1      /* argument 2, interrupt number (mcause) */
    /* mask off the interrupt flag of mcause */
    li	    t0, VECTORS_MCAUSE_REASON_MASK
    and     a1, a1, t0
    jal     _global_interrupt_handler

    /* After dispatch c handler, disable interrupt to make freertos make context switch */

    disable_intr
    /* MIE cleared. Nested interrupts are disabled */

#if !SOC_INT_HW_NESTED_SUPPORTED
    /* restore the interrupt threshold level */
    li      t0, INTERRUPT_CURRENT_CORE_INT_THRESH_REG
    sw      s3, 0(t0)
    fence
#elif CONFIG_SECURE_ENABLE_TEE
    /* Restore the U-mode interrupt threshold level.
     *
     * If uintstatus == 0, it means we have returned here after servicing a
     * U-mode interrupt that was delegated via M-mode. In this case, we should
     * reset the threshold to the minimum valid level.
     *
     * If uintstatus != 0, we are currently handling a U-mode interrupt that
     * either originated directly in U-mode or is a nested U-mode interrupt.
     * In this case, the threshold must be retained, so we skip the reset.
     */
    csrr    t0, CSR_UINTSTATUS
    bnez    t0, _skip_thresh_reset
    csrr    t0, CSR_UINTTHRESH
    li      t1, (1 << (8 - NLBITS) - 1)
    beq     t0, t1, _skip_thresh_reset
    csrw    CSR_UINTTHRESH, t1
_skip_thresh_reset:
#endif // !SOC_INT_HW_NESTED_SUPPORTED

    /* The RTOS will restore the current TCB stack pointer. This routine will preserve s1 and s2.
     * Returns the new `mstatus` value. */
    mv      a0, s2      /* a0 = mstatus */
    mv      a1, s4      /* a1 = abstract context returned by `rtos_int_enter` */
    call    rtos_int_exit

    /* Restore the rest of the registers.
     * In case the target uses the CLIC, it is mandatory to restore `mcause` register since it contains
     * the former CPU priority. When executing `mret`, the hardware will restore the former threshold,
     * from `mcause` to `mintstatus` CSR */
#if CONFIG_SECURE_ENABLE_TEE
    csrw    ucause, s1
    csrw    ustatus, a0
#else
    csrw    mcause, s1
    csrw    mstatus, a0
#endif
    restore_xepc
    restore_general_regs
    /* exit, this will also re-enable the interrupts */
    xret
    .size  _interrupt_handler, .-_interrupt_handler
