--- /dev/null
+/*
+ * Note: Currently, the backtraces must still be checked manually. Therefore,
+ * these test cases should always pass.
+ * Todo: Automate the checking of backtrace addresses.
+ */
+#include <stdlib.h>
+#include "unity.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/xtensa_api.h"
+#include "esp_intr_alloc.h"
+
+#define SW_ISR_LEVEL_1 7
+#define SW_ISR_LEVEL_3 29
+#define RECUR_DEPTH 3
+#define ACTION_ABORT -1
+#define ACTION_INT_WDT -2
+
+// Set to (-1) for abort(), (-2) for interrupt watchdog
+static int backtrace_trigger_source;
+
+/*
+ * Recursive functions to generate a longer call stack. When the max specified
+ * recursion depth is reached, the following actions can be taken.
+ */
+static void __attribute__((__noinline__)) recursive_func(int recur_depth, int action)
+{
+ if (recur_depth > 1) {
+ recursive_func(recur_depth - 1, action);
+ } else if (action >= 0) {
+ xt_set_intset(1 << action);
+ } else if (action == ACTION_ABORT) {
+ abort();
+ // Todo: abort() causes problems in GDB Stub backtrace due to being 'non returning'.
+ } else if (action == ACTION_INT_WDT) {
+ portDISABLE_INTERRUPTS();
+ while (1) {
+ ;
+ }
+ }
+}
+
+static void level_three_isr (void *arg)
+{
+ xt_set_intclear(1 << SW_ISR_LEVEL_3); //Clear interrupt
+ recursive_func(RECUR_DEPTH, backtrace_trigger_source); //Abort at the max recursive depth
+}
+
+static void level_one_isr(void *arg)
+{
+ xt_set_intclear(1 << SW_ISR_LEVEL_1); //Clear interrupt
+ recursive_func(RECUR_DEPTH, SW_ISR_LEVEL_3); //Trigger nested interrupt max recursive depth
+}
+
+TEST_CASE("Test backtrace from abort", "[reset_reason][reset=SW_CPU_RESET]")
+{
+ //Allocate level one and three SW interrupts
+ esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, 0, level_one_isr, NULL, NULL); //Level 1 SW intr
+ esp_intr_alloc(ETS_INTERNAL_SW1_INTR_SOURCE, 0, level_three_isr, NULL, NULL); //Level 3 SW intr
+ backtrace_trigger_source = ACTION_ABORT;
+ recursive_func(RECUR_DEPTH, SW_ISR_LEVEL_1); //Trigger lvl 1 SW interrupt at max recursive depth
+}
+
+TEST_CASE("Test backtrace from interrupt watchdog timeout", "[reset_reason][reset=Interrupt wdt timeout on CPU0,SW_CPU_RESET]")
+{
+ //Allocate level one and three SW interrupts
+ esp_intr_alloc(ETS_INTERNAL_SW0_INTR_SOURCE, 0, level_one_isr, NULL, NULL); //Level 1 SW intr
+ esp_intr_alloc(ETS_INTERNAL_SW1_INTR_SOURCE, 0, level_three_isr, NULL, NULL); //Level 3 SW intr
+ backtrace_trigger_source = ACTION_INT_WDT;
+ recursive_func(RECUR_DEPTH, SW_ISR_LEVEL_1); //Trigger lvl 1 SW interrupt at max recursive depth
+}
#define TASKTCB_XCOREID_OFFSET (0x38+configMAX_TASK_NAME_LEN+3)&~3
.extern pxCurrentTCB
-/* Enable stack backtrace across exception/interrupt - see below */
+/*
+--------------------------------------------------------------------------------
+ In order for backtracing to be able to trace from the pre-exception stack
+ across to the exception stack (including nested interrupts), we need to create
+ a pseudo base-save area to make it appear like the exception dispatcher was
+ triggered by a CALL4 from the pre-exception code. In reality, the exception
+ dispatcher uses the same window as pre-exception code, and only CALL0s are
+ used within the exception dispatcher.
+
+ To create the pseudo base-save area, we need to store a copy of the pre-exception's
+ base save area (a0 to a4) below the exception dispatcher's SP. EXCSAVE_x will
+ be used to store a copy of the SP that points to the interrupted code's exception
+ frame just in case the exception dispatcher's SP does not point to the exception
+ frame (which is the case when switching from task to interrupt stack).
+
+ Clearing the pseudo base-save area is uncessary as the interrupt dispatcher
+ will restore the current SP to that of the pre-exception SP.
+--------------------------------------------------------------------------------
+*/
#ifdef CONFIG_FREERTOS_INTERRUPT_BACKTRACE
#define XT_DEBUG_BACKTRACE 1
#endif
/* This bit of code provides a nice debug backtrace in the debugger.
It does take a few more instructions, so undef XT_DEBUG_BACKTRACE
if you want to save the cycles.
+ At this point, the exception frame should have been allocated and filled,
+ and current sp points to the interrupt stack (for non-nested interrupt)
+ or below the allocated exception frame (for nested interrupts). Copy the
+ pre-exception's base save area below the current SP.
*/
#ifdef XT_DEBUG_BACKTRACE
#ifndef __XTENSA_CALL0_ABI__
+ rsr a0, EXCSAVE_1 + \level - 1 /* Get exception frame pointer stored in EXCSAVE_x */
+ l32i a3, a0, XT_STK_A0 /* Copy pre-exception a0 (return address) */
+ s32e a3, a1, -16
+ l32i a3, a0, XT_STK_A1 /* Copy pre-exception a1 (stack pointer) */
+ s32e a3, a1, -12
+ /* Backtracing only needs a0 and a1, no need to create full base save area.
+ Also need to change current frame's return address to point to pre-exception's
+ last run instruction.
+ */
rsr a0, EPC_1 + \level - 1 /* return address */
movi a4, 0xC0000000 /* constant with top 2 bits set (call size) */
or a0, a0, a4 /* set top 2 bits */
#endif
wsr a0, PS
+ /*
+ Create pseudo base save area. At this point, sp is still pointing to the
+ allocated and filled exception stack frame.
+ */
#ifdef XT_DEBUG_BACKTRACE
#ifndef __XTENSA_CALL0_ABI__
+ l32i a3, sp, XT_STK_A0 /* Copy pre-exception a0 (return address) */
+ s32e a3, sp, -16
+ l32i a3, sp, XT_STK_A1 /* Copy pre-exception a1 (stack pointer) */
+ s32e a3, sp, -12
rsr a0, EPC_1 /* return address for debug backtrace */
movi a5, 0xC0000000 /* constant with top 2 bits set (call size) */
rsync /* wait for WSR.PS to complete */
movi a0, _xt_user_exit /* save exit point for dispatch */
s32i a0, sp, XT_STK_EXIT
+ /* EXCSAVE_1 should now be free to use. Use it to keep a copy of the
+ current stack pointer that points to the exception frame (XT_STK_FRAME).*/
+ #ifdef XT_DEBUG_BACKTRACE
+ #ifndef __XTENSA_CALL0_ABI__
+ mov a0, sp
+ wsr a0, EXCSAVE_1
+ #endif
+ #endif
+
+
/* Save rest of interrupt context and enter RTOS. */
call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */
movi a0, _xt_medint2_exit /* save exit point for dispatch */
s32i a0, sp, XT_STK_EXIT
+ /* EXCSAVE_2 should now be free to use. Use it to keep a copy of the
+ current stack pointer that points to the exception frame (XT_STK_FRAME).*/
+ #ifdef XT_DEBUG_BACKTRACE
+ #ifndef __XTENSA_CALL0_ABI__
+ mov a0, sp
+ wsr a0, EXCSAVE_2
+ #endif
+ #endif
+
/* Save rest of interrupt context and enter RTOS. */
call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */
movi a0, _xt_medint3_exit /* save exit point for dispatch */
s32i a0, sp, XT_STK_EXIT
+ /* EXCSAVE_3 should now be free to use. Use it to keep a copy of the
+ current stack pointer that points to the exception frame (XT_STK_FRAME).*/
+ #ifdef XT_DEBUG_BACKTRACE
+ #ifndef __XTENSA_CALL0_ABI__
+ mov a0, sp
+ wsr a0, EXCSAVE_3
+ #endif
+ #endif
+
/* Save rest of interrupt context and enter RTOS. */
call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */
movi a0, _xt_medint4_exit /* save exit point for dispatch */
s32i a0, sp, XT_STK_EXIT
+ /* EXCSAVE_4 should now be free to use. Use it to keep a copy of the
+ current stack pointer that points to the exception frame (XT_STK_FRAME).*/
+ #ifdef XT_DEBUG_BACKTRACE
+ #ifndef __XTENSA_CALL0_ABI__
+ mov a0, sp
+ wsr a0, EXCSAVE_4
+ #endif
+ #endif
+
/* Save rest of interrupt context and enter RTOS. */
call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */
movi a0, _xt_medint5_exit /* save exit point for dispatch */
s32i a0, sp, XT_STK_EXIT
+ /* EXCSAVE_5 should now be free to use. Use it to keep a copy of the
+ current stack pointer that points to the exception frame (XT_STK_FRAME).*/
+ #ifdef XT_DEBUG_BACKTRACE
+ #ifndef __XTENSA_CALL0_ABI__
+ mov a0, sp
+ wsr a0, EXCSAVE_5
+ #endif
+ #endif
+
/* Save rest of interrupt context and enter RTOS. */
call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */
movi a0, _xt_medint6_exit /* save exit point for dispatch */
s32i a0, sp, XT_STK_EXIT
+ /* EXCSAVE_6 should now be free to use. Use it to keep a copy of the
+ current stack pointer that points to the exception frame (XT_STK_FRAME).*/
+ #ifdef XT_DEBUG_BACKTRACE
+ #ifndef __XTENSA_CALL0_ABI__
+ mov a0, sp
+ wsr a0, EXCSAVE_6
+ #endif
+ #endif
+
/* Save rest of interrupt context and enter RTOS. */
call0 XT_RTOS_INT_ENTER /* common RTOS interrupt entry */