freertos: Xtensa FreeRTOS saves threadptr in solicited stack frame

The Xtensa FreeRTOS port does not save the threadptr register when
doing a voluntary yield. This can result in a crash when multiple
tasks used the threadptr register and call "taskYIELD()".

This commit adds the threadptr register to the solicited stack frame.
This commit is contained in:
Darian Leung
2022-07-04 18:16:13 +08:00
parent 42ea9b7cb1
commit 0a894c2b30
2 changed files with 46 additions and 22 deletions

View File

@ -86,16 +86,16 @@ NOTE: The Xtensa architecture requires stack pointer alignment to 16 bytes.
INTERRUPT/EXCEPTION STACK FRAME FOR A THREAD OR NESTED INTERRUPT INTERRUPT/EXCEPTION STACK FRAME FOR A THREAD OR NESTED INTERRUPT
A stack frame of this structure is allocated for any interrupt or exception. A stack frame of this structure is allocated for any interrupt or exception.
It goes on the current stack. If the RTOS has a system stack for handling It goes on the current stack. If the RTOS has a system stack for handling
interrupts, every thread stack must allow space for just one interrupt stack interrupts, every thread stack must allow space for just one interrupt stack
frame, then nested interrupt stack frames go on the system stack. frame, then nested interrupt stack frames go on the system stack.
The frame includes basic registers (explicit) and "extra" registers introduced The frame includes basic registers (explicit) and "extra" registers introduced
by user TIE or the use of the MAC16 option in the user's Xtensa config. by user TIE or the use of the MAC16 option in the user's Xtensa config.
The frame size is minimized by omitting regs not applicable to user's config. The frame size is minimized by omitting regs not applicable to user's config.
For Windowed ABI, this stack frame includes the interruptee's base save area, For Windowed ABI, this stack frame includes the interruptee's base save area,
another base save area to manage gcc nested functions, and a little temporary another base save area to manage gcc nested functions, and a little temporary
space to help manage the spilling of the register windows. space to help manage the spilling of the register windows.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
*/ */
@ -163,7 +163,7 @@ STRUCT_END(XtExcFrame)
#else #else
#define XT_STK_NEXT2 XT_STK_NEXT1 #define XT_STK_NEXT2 XT_STK_NEXT1
#endif #endif
@ -180,20 +180,28 @@ STRUCT_END(XtExcFrame)
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
SOLICITED STACK FRAME FOR A THREAD SOLICITED STACK FRAME FOR A THREAD
A stack frame of this structure is allocated whenever a thread enters the A stack frame of this structure is allocated whenever a thread enters the
RTOS kernel intentionally (and synchronously) to submit to thread scheduling. RTOS kernel intentionally (and synchronously) to submit to thread scheduling.
It goes on the current thread's stack. It goes on the current thread's stack.
The solicited frame only includes registers that are required to be preserved The solicited frame only includes registers that are required to be preserved
by the callee according to the compiler's ABI conventions, some space to save by the callee according to the compiler's ABI conventions, some space to save
the return address for returning to the caller, and the caller's PS register. the return address for returning to the caller, and the caller's PS register.
Note: Although the xtensa ABI considers the threadptr as "global" across
functions (meaning it is neither caller or callee saved), it is treated as a
callee-saved register in a solicited stack frame. This omits the need for the
OS to include extra logic to save "global" registers on each context switch.
Only the threadptr register is treated as callee-saved, as all other NCP
(non-coprocessor extra) registers are caller-saved. See "tie.h" for more
details.
For Windowed ABI, this stack frame includes the caller's base save area. For Windowed ABI, this stack frame includes the caller's base save area.
Note on XT_SOL_EXIT field: Note on XT_SOL_EXIT field:
It is necessary to distinguish a solicited from an interrupt stack frame. It is necessary to distinguish a solicited from an interrupt stack frame.
This field corresponds to XT_STK_EXIT in the interrupt stack frame and is This field corresponds to XT_STK_EXIT in the interrupt stack frame and is
always at the same offset (0). It can be written with a code (usually 0) always at the same offset (0). It can be written with a code (usually 0)
to distinguish a solicted frame from an interrupt frame. An RTOS port may to distinguish a solicted frame from an interrupt frame. An RTOS port may
opt to ignore this field if it has another way of distinguishing frames. opt to ignore this field if it has another way of distinguishing frames.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@ -204,7 +212,11 @@ STRUCT_BEGIN
STRUCT_FIELD (long, 4, XT_SOL_EXIT, exit) STRUCT_FIELD (long, 4, XT_SOL_EXIT, exit)
STRUCT_FIELD (long, 4, XT_SOL_PC, pc) STRUCT_FIELD (long, 4, XT_SOL_PC, pc)
STRUCT_FIELD (long, 4, XT_SOL_PS, ps) STRUCT_FIELD (long, 4, XT_SOL_PS, ps)
STRUCT_FIELD (long, 4, XT_SOL_NEXT, next) #if XCHAL_HAVE_THREADPTR
STRUCT_FIELD (long, 4, XT_SOL_THREADPTR, threadptr)
#else
STRUCT_FIELD (long, 4, XT_SOL_NEXT, next) /* Dummy register for 16-byte alignment */
#endif
STRUCT_FIELD (long, 4, XT_SOL_A12, a12) /* should be on 16-byte alignment */ STRUCT_FIELD (long, 4, XT_SOL_A12, a12) /* should be on 16-byte alignment */
STRUCT_FIELD (long, 4, XT_SOL_A13, a13) STRUCT_FIELD (long, 4, XT_SOL_A13, a13)
STRUCT_FIELD (long, 4, XT_SOL_A14, a14) STRUCT_FIELD (long, 4, XT_SOL_A14, a14)
@ -213,7 +225,11 @@ STRUCT_FIELD (long, 4, XT_SOL_A15, a15)
STRUCT_FIELD (long, 4, XT_SOL_EXIT, exit) STRUCT_FIELD (long, 4, XT_SOL_EXIT, exit)
STRUCT_FIELD (long, 4, XT_SOL_PC, pc) STRUCT_FIELD (long, 4, XT_SOL_PC, pc)
STRUCT_FIELD (long, 4, XT_SOL_PS, ps) STRUCT_FIELD (long, 4, XT_SOL_PS, ps)
STRUCT_FIELD (long, 4, XT_SOL_NEXT, next) #if XCHAL_HAVE_THREADPTR
STRUCT_FIELD (long, 4, XT_SOL_THREADPTR, threadptr)
#else
STRUCT_FIELD (long, 4, XT_SOL_NEXT, next) /* Dummy register for 16-byte alignment */
#endif
STRUCT_FIELD (long, 4, XT_SOL_A0, a0) /* should be on 16-byte alignment */ STRUCT_FIELD (long, 4, XT_SOL_A0, a0) /* should be on 16-byte alignment */
STRUCT_FIELD (long, 4, XT_SOL_A1, a1) STRUCT_FIELD (long, 4, XT_SOL_A1, a1)
STRUCT_FIELD (long, 4, XT_SOL_A2, a2) STRUCT_FIELD (long, 4, XT_SOL_A2, a2)
@ -238,7 +254,7 @@ STRUCT_END(XtSolFrame)
and the context switch of that co-processor is then peformed by the handler. and the context switch of that co-processor is then peformed by the handler.
Ownership represents which thread's state is currently in the co-processor. Ownership represents which thread's state is currently in the co-processor.
Co-processors may not be used by interrupt or exception handlers. If an Co-processors may not be used by interrupt or exception handlers. If an
co-processor instruction is executed by an interrupt or exception handler, co-processor instruction is executed by an interrupt or exception handler,
the co-processor exception handler will trigger a kernel panic and freeze. the co-processor exception handler will trigger a kernel panic and freeze.
This restriction is introduced to reduce the overhead of saving and restoring This restriction is introduced to reduce the overhead of saving and restoring
@ -249,7 +265,7 @@ STRUCT_END(XtSolFrame)
such as in the thread control block or above the thread stack area. It need such as in the thread control block or above the thread stack area. It need
not be in the interrupt stack frame since interrupts don't use co-processors. not be in the interrupt stack frame since interrupts don't use co-processors.
Along with the save area for each co-processor, two bitmasks with flags per Along with the save area for each co-processor, two bitmasks with flags per
co-processor (laid out as in the CPENABLE reg) help manage context-switching co-processor (laid out as in the CPENABLE reg) help manage context-switching
co-processors as efficiently as possible: co-processors as efficiently as possible:
@ -265,10 +281,10 @@ STRUCT_END(XtSolFrame)
XT_CPSTORED XT_CPSTORED
A bitmask with the same layout as CPENABLE, a bit per co-processor. A bitmask with the same layout as CPENABLE, a bit per co-processor.
Indicates whether the state of each co-processor is saved in the state Indicates whether the state of each co-processor is saved in the state
save area. When a thread enters the kernel, only the state of co-procs save area. When a thread enters the kernel, only the state of co-procs
still enabled in CPENABLE is saved. When the co-processor exception still enabled in CPENABLE is saved. When the co-processor exception
handler assigns ownership of a co-processor to a thread, it restores handler assigns ownership of a co-processor to a thread, it restores
the saved state only if this bit is set, and clears this bit. the saved state only if this bit is set, and clears this bit.
XT_CP_CS_ST XT_CP_CS_ST
@ -349,7 +365,7 @@ STRUCT_END(XtSolFrame)
For framed functions the frame is created and the return address saved at For framed functions the frame is created and the return address saved at
base of frame (Call0 ABI) or as determined by hardware (Windowed ABI). base of frame (Call0 ABI) or as determined by hardware (Windowed ABI).
For frameless functions, there is no frame and return address remains in a0. For frameless functions, there is no frame and return address remains in a0.
Note: Because CPP macros expand to a single line, macros requiring multi-line Note: Because CPP macros expand to a single line, macros requiring multi-line
expansions are implemented as assembler macros. expansions are implemented as assembler macros.
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
*/ */
@ -362,7 +378,7 @@ STRUCT_END(XtSolFrame)
addi sp, sp, -\size addi sp, sp, -\size
s32i a0, sp, 0 s32i a0, sp, 0
.endm .endm
#define ENTRY0 #define ENTRY0
#define RET(sz) ret1 sz #define RET(sz) ret1 sz
.macro ret1 size=0x10 .macro ret1 size=0x10
l32i a0, sp, 0 l32i a0, sp, 0

View File

@ -56,10 +56,10 @@ port_switch_flag:
******************************************************************************* *******************************************************************************
* _frxt_setup_switch * _frxt_setup_switch
* void _frxt_setup_switch(void); * void _frxt_setup_switch(void);
* *
* Sets an internal flag indicating that a task switch is required on return * Sets an internal flag indicating that a task switch is required on return
* from interrupt handling. * from interrupt handling.
* *
******************************************************************************* *******************************************************************************
*/ */
.global _frxt_setup_switch .global _frxt_setup_switch
@ -152,7 +152,7 @@ _frxt_int_enter:
#if XCHAL_CP_NUM > 0 #if XCHAL_CP_NUM > 0
movi a3, 0 /* whilst ISRs pending keep CPENABLE exception active */ movi a3, 0 /* whilst ISRs pending keep CPENABLE exception active */
wsr a3, CPENABLE wsr a3, CPENABLE
rsync rsync
#endif #endif
#endif #endif
@ -192,10 +192,10 @@ _frxt_int_exit:
s32i a2, a3, 0 /* save nesting count */ s32i a2, a3, 0 /* save nesting count */
bnez a2, .Lnesting /* !=0 after decr so still nested */ bnez a2, .Lnesting /* !=0 after decr so still nested */
#ifdef CONFIG_FREERTOS_FPU_IN_ISR #ifdef CONFIG_FREERTOS_FPU_IN_ISR
#if XCHAL_CP_NUM > 0 #if XCHAL_CP_NUM > 0
l32i a3, sp, 0 /* Grab last CPENABLE before leave ISR */ l32i a3, sp, 0 /* Grab last CPENABLE before leave ISR */
addi sp, sp, 4 addi sp, sp, 4
wsr a3, CPENABLE wsr a3, CPENABLE
rsync /* ensure CPENABLE was modified */ rsync /* ensure CPENABLE was modified */
#endif #endif
@ -444,6 +444,10 @@ _frxt_dispatch:
.L_frxt_dispatch_sol: .L_frxt_dispatch_sol:
/* Solicited stack frame. Restore minimal context and return from vPortYield(). */ /* Solicited stack frame. Restore minimal context and return from vPortYield(). */
#if XCHAL_HAVE_THREADPTR
l32i a2, sp, XT_SOL_THREADPTR
wur.threadptr a2
#endif
l32i a3, sp, XT_SOL_PS l32i a3, sp, XT_SOL_PS
#ifdef __XTENSA_CALL0_ABI__ #ifdef __XTENSA_CALL0_ABI__
l32i a12, sp, XT_SOL_A12 l32i a12, sp, XT_SOL_A12
@ -531,6 +535,10 @@ vPortYield:
rsr a2, PS rsr a2, PS
s32i a0, sp, XT_SOL_PC s32i a0, sp, XT_SOL_PC
s32i a2, sp, XT_SOL_PS s32i a2, sp, XT_SOL_PS
#if XCHAL_HAVE_THREADPTR
rur.threadptr a2
s32i a2, sp, XT_SOL_THREADPTR
#endif
#ifdef __XTENSA_CALL0_ABI__ #ifdef __XTENSA_CALL0_ABI__
s32i a12, sp, XT_SOL_A12 /* save callee-saved registers */ s32i a12, sp, XT_SOL_A12 /* save callee-saved registers */
s32i a13, sp, XT_SOL_A13 s32i a13, sp, XT_SOL_A13