/* * SPDX-FileCopyrightText: 2017-2024 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 #if ( SOC_CPU_COPROC_NUM > 0 ) /* Targets with coprocessors present a special CSR to get Illegal Instruction exception reason */ .equ EXT_ILL_CSR, 0x7F0 /* 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 save_mepc csrr t0, mepc sw t0, RV_STK_MEPC(sp) .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 restore_mepc lw t0, RV_STK_MEPC(sp) csrw mepc, t0 .endm .global rtos_int_enter .global rtos_int_exit .global rtos_save_fpu_coproc .global _global_interrupt_handler #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) csrr t0, mepc sw t0, RV_STK_MEPC(sp) 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) /* Keep mcause in s0, only the exception code and interrupt bit are relevant */ csrr s0, mcause 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 /* Hardware loop cannot be treated lazily, so we should never end here if a HWLP instruction is used */ #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 */ #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 #if SOC_CPU_HAS_FPU_EXT_ILL_BUG /* 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_mepc /* 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 .type _interrupt_handler, @function _interrupt_handler: /* Start by saving the general purpose registers and the PC value before * the interrupt happened. */ save_general_regs save_mepc /* 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 */ csrr s1, mcause csrr s2, mstatus #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 csrsi mstatus, 0x8 /* 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 */ csrci mstatus, 0x8 /* 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 #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 */ csrw mcause, s1 csrw mstatus, a0 restore_mepc restore_general_regs /* exit, this will also re-enable the interrupts */ mret .size _interrupt_handler, .-_interrupt_handler