mirror of
				https://github.com/espressif/esp-idf.git
				synced 2025-10-31 21:14:37 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			396 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			ArmAsm
		
	
	
	
	
	
			
		
		
	
	
			396 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			ArmAsm
		
	
	
	
	
	
| /*
 | |
|  * 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 */
 | |
| #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 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
 | 
