mirror of
https://github.com/espressif/esp-idf.git
synced 2024-10-05 20:47:46 -04:00
6eba7a536a
FreeRTOS tasks may now freely use the PIE coprocessor and HWLP feature. Just like the FPU, usiing these coprocessors result in the task being pinned to the core it is currently running on.
395 lines
13 KiB
ArmAsm
395 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 */
|
|
.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
|
|
|
|
#if SOC_CPU_HAS_HWLOOP
|
|
/* Check if the HWLOOP bit is set. */
|
|
andi a1, a0, EXT_ILL_RSN_HWLP
|
|
bnez a1, rtos_save_hwlp_coproc
|
|
#endif // SOC_CPU_HAS_HWLOOP
|
|
|
|
#if SOC_CPU_HAS_PIE
|
|
/* Check if the HWLOOP bit is set. */
|
|
andi a1, a0, EXT_ILL_RSN_PIE
|
|
bnez a1, rtos_save_pie_coproc
|
|
#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
|
|
#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
|