freertos: save/restore PS and EPC1 around window spilling

Since in b0491307, which has introduced the optimized window spill
procedure, _xt_context_save did not work correctly when called from
_xt_syscall_exc. This was because unlike _xt_lowint1, _xt_syscall_exc
does not save PS and EPC1. The new version of _xt_context_save
modified PS (on purpose) and EPC1 (accidentally, due to window
overflow exceptions), which resulted in a crash upon 'rfi' from the
syscall.

This commit adds restoring of PS and EPC1 in _xt_context_save. It also
slightly reduces the number of instructions used to prepare PS for
window spill.

Unit test for setjmp/longjmp (which were broken by this regression)
is added.

Closes https://github.com/espressif/esp-idf/issues/4541
This commit is contained in:
Ivan Grokhotkov 2019-12-26 15:57:23 +01:00
parent b5b30736de
commit 891eb3b020
2 changed files with 59 additions and 17 deletions

View File

@ -166,27 +166,34 @@ _xt_context_save:
s32i a9, sp, XT_STK_OVLY /* save overlay state */
#endif
rsr a2, PS /* We need to enable window exceptions to */
movi a3, PS_INTLEVEL_MASK /* perform spill registers*/
and a2, a2, a3
bnez a2, _not_l1
rsr a2, PS
movi a3, PS_INTLEVEL(1) /* For some curious reason the level 1 interrupts */
or a2, a2, a3 /* dont set the intlevel correctly on PS, we need to */
wsr a2, PS /* do this manually */
rsync
_not_l1:
rsr a2, PS /* finally umask the window exceptions */
movi a3, ~(PS_EXCM_MASK)
and a2, a2, a3
wsr a2, PS
rsync
/* SPILL_ALL_WINDOWS macro requires window overflow exceptions to be enabled,
* i.e. PS.EXCM cleared and PS.WOE set.
* Since we are going to clear PS.EXCM, we also need to increase INTLEVEL
* at least to XCHAL_EXCM_LEVEL. This matches that value of effective INTLEVEL
* at entry (CINTLEVEL=max(PS.INTLEVEL, XCHAL_EXCM_LEVEL) when PS.EXCM is set.
* Since WindowOverflow exceptions will trigger inside SPILL_ALL_WINDOWS,
* need to save/restore EPC1 as well.
*/
rsr a2, PS /* to be restored after SPILL_ALL_WINDOWS */
movi a4, PS_INTLEVEL_MASK
and a3, a2, a4 /* get the current INTLEVEL */
bgeui a3, XCHAL_EXCM_LEVEL, 1f /* calculate max(INTLEVEL, XCHAL_EXCM_LEVEL) */
movi a3, XCHAL_EXCM_LEVEL
1:
movi a4, PS_UM | PS_WOE /* clear EXCM, enable window overflow, set new INTLEVEL */
or a3, a3, a4
wsr a3, ps
rsr a4, EPC1 /* to be restored after SPILL_ALL_WINDOWS */
addi sp, sp, XT_STK_FRMSZ /* go back to spill register region */
SPILL_ALL_WINDOWS /* place the live register windows there */
addi sp, sp, -XT_STK_FRMSZ /* return the current stack pointer and proceed with context save*/
#endif
wsr a2, PS /* restore to the value at entry */
rsync
wsr a4, EPC1 /* likewise */
#endif /* __XTENSA_CALL0_ABI__ */
l32i a12, sp, XT_STK_TMP0 /* restore the temp saved registers */
l32i a13, sp, XT_STK_TMP1 /* our return address is there */

View File

@ -0,0 +1,35 @@
#include <setjmp.h>
#include <stdio.h>
#include "unity.h"
#include "esp_system.h"
typedef struct {
jmp_buf jmp_env;
uint32_t retval;
volatile bool inner_called;
} setjmp_test_ctx_t;
static __attribute__((noreturn)) void inner(setjmp_test_ctx_t *ctx)
{
printf("inner, retval=0x%x\n", ctx->retval);
ctx->inner_called = true;
longjmp(ctx->jmp_env, ctx->retval);
TEST_FAIL_MESSAGE("Should not reach here");
}
TEST_CASE("setjmp and longjmp", "[newlib]")
{
const uint32_t expected = 0x12345678;
setjmp_test_ctx_t ctx = {
.retval = expected
};
uint32_t ret = setjmp(ctx.jmp_env);
if (!ctx.inner_called) {
TEST_ASSERT_EQUAL(0, ret);
inner(&ctx);
} else {
TEST_ASSERT_EQUAL(expected, ret);
}
TEST_ASSERT(ctx.inner_called);
}