Merge branch 'feat/riscv-spiram-stack-pointer-update-before-restart_v6.0' into 'release/v6.0'

fix(esp_system): fix PSRAM stack crash in esp_restart_noos on RISC-V SPIRAM chips (v6.0)

See merge request espressif/esp-idf!48067
This commit is contained in:
Jiang Jiang Jian
2026-05-16 08:22:38 +08:00
6 changed files with 249 additions and 133 deletions

View File

@@ -27,6 +27,10 @@
#include "hal/modem_lpcon_ll.h"
#endif
#include "esp_private/cache_err_int.h"
#include "esp_memory_utils.h"
#define ALIGN_DOWN(val, align) ((val) & ~((align) - 1))
extern int _bss_end;
#include "esp32c5/rom/cache.h"
#include "esp32c5/rom/rtc.h"
@@ -100,6 +104,24 @@ void esp_system_reset_modules_on_exit(void)
uart_ll_sclk_enable(&UART0);
}
static void IRAM_ATTR __attribute__((noinline, noreturn)) esp_restart_noos_inner(void)
{
// Disable cache
Cache_Disable_Cache();
esp_system_reset_modules_on_exit();
// Set CPU back to XTAL source, same as hard reset, but keep BBPLL on so that USB Serial JTAG can log at 1st stage bootloader.
#if !CONFIG_IDF_ENV_FPGA
rtc_clk_cpu_set_to_default_config();
#endif
// Reset PRO CPU
esp_rom_software_reset_cpu(0);
ESP_INFINITE_LOOP();
}
/* "inner" restart function for after RTOS, interrupts & anything else on this
* core are already stopped. Stalls other core, resets hardware,
* triggers restart.
@@ -132,25 +154,15 @@ void esp_restart_noos(void)
wdt_hal_disable(&wdt1_context);
wdt_hal_write_protect_enable(&wdt1_context);
// Disable cache
Cache_Disable_Cache();
// Reset wifi/bluetooth/ethernet/sdio (bb/mac)
// Moved to module internal
// SET_PERI_REG_MASK(SYSTEM_CORE_RST_EN_REG,
// SYSTEM_SDIO_RST | // SDIO_HINF_HINF_SDIO_RST?
// SYSTEM_EMAC_RST | SYSTEM_MACPWR_RST | // TODO: IDF-5325 (ethernet)
// REG_WRITE(SYSTEM_CORE_RST_EN_REG, 0);
esp_system_reset_modules_on_exit();
// Set CPU back to XTAL source, same as hard reset, but keep BBPLL on so that USB Serial JTAG can log at 1st stage bootloader.
#if !CONFIG_IDF_ENV_FPGA
rtc_clk_cpu_set_to_default_config();
#ifdef CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM
if (esp_ptr_external_ram(esp_cpu_get_sp())) {
// If stack is in external RAM (CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM), switch SP to
// internal RAM before disabling the cache to avoid a "Cache disabled but cached memory
// region accessed" crash.
uint32_t new_sp = ALIGN_DOWN((uint32_t)&_bss_end, 16);
rv_utils_set_sp((void *)new_sp);
}
#endif
// Reset PRO CPU
esp_rom_software_reset_cpu(0);
ESP_INFINITE_LOOP();
esp_restart_noos_inner();
}

View File

@@ -23,6 +23,10 @@
#include "hal/wdt_hal.h"
#include "hal/uart_ll.h"
#include "esp_private/cache_err_int.h"
#include "esp_memory_utils.h"
#define ALIGN_DOWN(val, align) ((val) & ~((align) - 1))
extern int _bss_end;
#if SOC_MODEM_CLOCK_SUPPORTED
#include "hal/modem_syscon_ll.h"
@@ -99,6 +103,23 @@ void esp_system_reset_modules_on_exit(void)
uart_ll_sclk_enable(&UART0);
}
static void IRAM_ATTR __attribute__((noinline, noreturn)) esp_restart_noos_inner(void)
{
// Disable cache
Cache_Disable_Cache();
esp_system_reset_modules_on_exit();
// Set CPU back to XTAL source, same as hard reset, but keep BBPLL on so that USB Serial JTAG can log at 1st stage bootloader.
#if !CONFIG_IDF_ENV_FPGA
rtc_clk_cpu_set_to_default_config();
#endif
// Reset PRO CPU
esp_rom_software_reset_cpu(0);
ESP_INFINITE_LOOP();
}
/* "inner" restart function for after RTOS, interrupts & anything else on this
* core are already stopped. Stalls other core, resets hardware,
* triggers restart.
@@ -131,25 +152,15 @@ void esp_restart_noos(void)
wdt_hal_disable(&wdt1_context);
wdt_hal_write_protect_enable(&wdt1_context);
// Disable cache
Cache_Disable_Cache();
//TODO: [ESP32C61] IDF-9553, inherit from verify code
// Reset wifi/bluetooth/ethernet/sdio (bb/mac)
// Moved to module internal
// SET_PERI_REG_MASK(SYSTEM_CORE_RST_EN_REG,
// SYSTEM_SDIO_RST | // SDIO_HINF_HINF_SDIO_RST?
// SYSTEM_EMAC_RST | SYSTEM_MACPWR_RST | // TODO: IDF-5325 (ethernet)
// REG_WRITE(SYSTEM_CORE_RST_EN_REG, 0);
esp_system_reset_modules_on_exit();
// Set CPU back to XTAL source, same as hard reset, but keep BBPLL on so that USB Serial JTAG can log at 1st stage bootloader.
#if !CONFIG_IDF_ENV_FPGA
rtc_clk_cpu_set_to_default_config();
#ifdef CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM
if (esp_ptr_external_ram(esp_cpu_get_sp())) {
// If stack is in external RAM (CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM), switch SP to
// internal RAM before disabling the cache to avoid a "Cache disabled but cached memory
// region accessed" crash.
uint32_t new_sp = ALIGN_DOWN((uint32_t)&_bss_end, 16);
rv_utils_set_sp((void *)new_sp);
}
#endif
// Reset PRO CPU
esp_rom_software_reset_cpu(0);
ESP_INFINITE_LOOP();
esp_restart_noos_inner();
}

View File

@@ -22,6 +22,10 @@
#include "soc/uart_reg.h"
#include "hal/wdt_hal.h"
#include "hal/uart_ll.h"
#include "esp_memory_utils.h"
#define ALIGN_DOWN(val, align) ((val) & ~((align) - 1))
extern int _bss_end;
#include "esp32h4/rom/cache.h"
// TODO: IDF-11911 need refactor
@@ -84,42 +88,9 @@ void esp_system_reset_modules_on_exit(void)
uart_ll_sclk_enable(&UART0);
}
/* "inner" restart function for after RTOS, interrupts & anything else on this
* core are already stopped. Stalls other core, resets hardware,
* triggers restart.
*/
void esp_restart_noos(void)
static void IRAM_ATTR __attribute__((noinline, noreturn)) esp_restart_noos_inner(void)
{
// Disable interrupts
rv_utils_intr_global_disable();
// Enable RTC watchdog for 1 second
wdt_hal_context_t rtc_wdt_ctx;
wdt_hal_init(&rtc_wdt_ctx, WDT_RWDT, 0, false);
uint32_t stage_timeout_ticks = (uint32_t)(1000ULL * rtc_clk_slow_freq_get_hz() / 1000ULL);
wdt_hal_write_protect_disable(&rtc_wdt_ctx);
wdt_hal_config_stage(&rtc_wdt_ctx, WDT_STAGE0, stage_timeout_ticks, WDT_STAGE_ACTION_RESET_SYSTEM);
wdt_hal_config_stage(&rtc_wdt_ctx, WDT_STAGE1, stage_timeout_ticks, WDT_STAGE_ACTION_RESET_RTC);
//Enable flash boot mode so that flash booting after restart is protected by the RTC WDT.
wdt_hal_set_flashboot_en(&rtc_wdt_ctx, true);
wdt_hal_write_protect_enable(&rtc_wdt_ctx);
const uint32_t core_id = esp_cpu_get_core_id();
#if !CONFIG_FREERTOS_UNICORE
const uint32_t other_core_id = (core_id == 0) ? 1 : 0;
esp_cpu_reset(other_core_id);
esp_cpu_stall(other_core_id);
#endif
// Disable TG0/TG1 watchdogs
wdt_hal_context_t wdt0_context = {.inst = WDT_MWDT0, .mwdt_dev = &TIMERG0};
wdt_hal_write_protect_disable(&wdt0_context);
wdt_hal_disable(&wdt0_context);
wdt_hal_write_protect_enable(&wdt0_context);
wdt_hal_context_t wdt1_context = {.inst = WDT_MWDT1, .mwdt_dev = &TIMERG1};
wdt_hal_write_protect_disable(&wdt1_context);
wdt_hal_disable(&wdt1_context);
wdt_hal_write_protect_enable(&wdt1_context);
// Disable cache
Cache_Disable_Cache(CACHE_MAP_ALL);
@@ -150,3 +121,57 @@ void esp_restart_noos(void)
#endif
ESP_INFINITE_LOOP();
}
/* "inner" restart function for after RTOS, interrupts & anything else on this
* core are already stopped. Stalls other core, resets hardware,
* triggers restart.
*/
void esp_restart_noos(void)
{
// Disable interrupts
rv_utils_intr_global_disable();
#if SOC_RTC_WDT_SUPPORTED
// Enable RTC watchdog for 1 second
wdt_hal_context_t rtc_wdt_ctx;
wdt_hal_init(&rtc_wdt_ctx, WDT_RWDT, 0, false);
uint32_t stage_timeout_ticks = (uint32_t)(1000ULL * rtc_clk_slow_freq_get_hz() / 1000ULL);
wdt_hal_write_protect_disable(&rtc_wdt_ctx);
wdt_hal_config_stage(&rtc_wdt_ctx, WDT_STAGE0, stage_timeout_ticks, WDT_STAGE_ACTION_RESET_SYSTEM);
wdt_hal_config_stage(&rtc_wdt_ctx, WDT_STAGE1, stage_timeout_ticks, WDT_STAGE_ACTION_RESET_RTC);
//Enable flash boot mode so that flash booting after restart is protected by the RTC WDT.
wdt_hal_set_flashboot_en(&rtc_wdt_ctx, true);
wdt_hal_write_protect_enable(&rtc_wdt_ctx);
#endif /* SOC_RTC_WDT_SUPPORTED */
#if !CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE
const uint32_t core_id = esp_cpu_get_core_id();
const uint32_t other_core_id = (core_id == 0) ? 1 : 0;
esp_cpu_reset(other_core_id);
esp_cpu_stall(other_core_id);
#endif
#if SOC_WDT_SUPPORTED
// Disable TG0/TG1 watchdogs
wdt_hal_context_t wdt0_context = {.inst = WDT_MWDT0, .mwdt_dev = &TIMERG0};
wdt_hal_write_protect_disable(&wdt0_context);
wdt_hal_disable(&wdt0_context);
wdt_hal_write_protect_enable(&wdt0_context);
wdt_hal_context_t wdt1_context = {.inst = WDT_MWDT1, .mwdt_dev = &TIMERG1};
wdt_hal_write_protect_disable(&wdt1_context);
wdt_hal_disable(&wdt1_context);
wdt_hal_write_protect_enable(&wdt1_context);
#endif /* SOC_WDT_SUPPORTED */
#ifdef CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM
if (esp_ptr_external_ram(esp_cpu_get_sp())) {
// If stack is in external RAM (CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM), switch SP to
// internal RAM before disabling the cache to avoid a "Cache disabled but cached memory
// region accessed" crash.
uint32_t new_sp = ALIGN_DOWN((uint32_t)&_bss_end, 16);
rv_utils_set_sp((void *)new_sp);
}
#endif
esp_restart_noos_inner();
}

View File

@@ -33,6 +33,10 @@
#include "hal/dw_gdma_ll.h"
#include "hal/dma2d_ll.h"
#include "hal/efuse_hal.h"
#include "esp_memory_utils.h"
#define ALIGN_DOWN(val, align) ((val) & ~((align) - 1))
extern int _bss_end;
void esp_system_reset_modules_on_exit(void)
{
@@ -149,42 +153,9 @@ void esp_system_reset_modules_on_exit(void)
#endif
}
/* "inner" restart function for after RTOS, interrupts & anything else on this
* core are already stopped. Stalls other core, resets hardware,
* triggers restart.
*/
void esp_restart_noos(void)
static void IRAM_ATTR __attribute__((noinline, noreturn)) esp_restart_noos_inner(void)
{
// Disable interrupts
rv_utils_intr_global_disable();
// Enable RTC watchdog for 1 second
wdt_hal_context_t rtc_wdt_ctx;
wdt_hal_init(&rtc_wdt_ctx, WDT_RWDT, 0, false);
uint32_t stage_timeout_ticks = (uint32_t)(1000ULL * rtc_clk_slow_freq_get_hz() / 1000ULL);
wdt_hal_write_protect_disable(&rtc_wdt_ctx);
wdt_hal_config_stage(&rtc_wdt_ctx, WDT_STAGE0, stage_timeout_ticks, WDT_STAGE_ACTION_RESET_SYSTEM);
wdt_hal_config_stage(&rtc_wdt_ctx, WDT_STAGE1, stage_timeout_ticks, WDT_STAGE_ACTION_RESET_RTC);
//Enable flash boot mode so that flash booting after restart is protected by the RTC WDT.
wdt_hal_set_flashboot_en(&rtc_wdt_ctx, true);
wdt_hal_write_protect_enable(&rtc_wdt_ctx);
const uint32_t core_id = esp_cpu_get_core_id();
#if !CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE
const uint32_t other_core_id = (core_id == 0) ? 1 : 0;
esp_cpu_reset(other_core_id);
esp_cpu_stall(other_core_id);
#endif
// Disable TG0/TG1 watchdogs
wdt_hal_context_t wdt0_context = {.inst = WDT_MWDT0, .mwdt_dev = &TIMERG0};
wdt_hal_write_protect_disable(&wdt0_context);
wdt_hal_disable(&wdt0_context);
wdt_hal_write_protect_enable(&wdt0_context);
wdt_hal_context_t wdt1_context = {.inst = WDT_MWDT1, .mwdt_dev = &TIMERG1};
wdt_hal_write_protect_disable(&wdt1_context);
wdt_hal_disable(&wdt1_context);
wdt_hal_write_protect_enable(&wdt1_context);
// Disable cache
#if CONFIG_SPIRAM
@@ -230,3 +201,57 @@ void esp_restart_noos(void)
ESP_INFINITE_LOOP();
}
/* "inner" restart function for after RTOS, interrupts & anything else on this
* core are already stopped. Stalls other core, resets hardware,
* triggers restart.
*/
void esp_restart_noos(void)
{
// Disable interrupts
rv_utils_intr_global_disable();
#if SOC_RTC_WDT_SUPPORTED
// Enable RTC watchdog for 1 second
wdt_hal_context_t rtc_wdt_ctx;
wdt_hal_init(&rtc_wdt_ctx, WDT_RWDT, 0, false);
uint32_t stage_timeout_ticks = (uint32_t)(1000ULL * rtc_clk_slow_freq_get_hz() / 1000ULL);
wdt_hal_write_protect_disable(&rtc_wdt_ctx);
wdt_hal_config_stage(&rtc_wdt_ctx, WDT_STAGE0, stage_timeout_ticks, WDT_STAGE_ACTION_RESET_SYSTEM);
wdt_hal_config_stage(&rtc_wdt_ctx, WDT_STAGE1, stage_timeout_ticks, WDT_STAGE_ACTION_RESET_RTC);
//Enable flash boot mode so that flash booting after restart is protected by the RTC WDT.
wdt_hal_set_flashboot_en(&rtc_wdt_ctx, true);
wdt_hal_write_protect_enable(&rtc_wdt_ctx);
#endif /* SOC_RTC_WDT_SUPPORTED */
#if !CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE
const uint32_t core_id = esp_cpu_get_core_id();
const uint32_t other_core_id = (core_id == 0) ? 1 : 0;
esp_cpu_reset(other_core_id);
esp_cpu_stall(other_core_id);
#endif
#if SOC_WDT_SUPPORTED
// Disable TG0/TG1 watchdogs
wdt_hal_context_t wdt0_context = {.inst = WDT_MWDT0, .mwdt_dev = &TIMERG0};
wdt_hal_write_protect_disable(&wdt0_context);
wdt_hal_disable(&wdt0_context);
wdt_hal_write_protect_enable(&wdt0_context);
wdt_hal_context_t wdt1_context = {.inst = WDT_MWDT1, .mwdt_dev = &TIMERG1};
wdt_hal_write_protect_disable(&wdt1_context);
wdt_hal_disable(&wdt1_context);
wdt_hal_write_protect_enable(&wdt1_context);
#endif /* SOC_WDT_SUPPORTED */
#ifdef CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM
if (esp_ptr_external_ram(esp_cpu_get_sp())) {
// If stack is in external RAM (CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM), switch SP to
// internal RAM before disabling the cache to avoid a "Cache disabled but cached memory
// region accessed" crash.
uint32_t new_sp = ALIGN_DOWN((uint32_t)&_bss_end, 16);
rv_utils_set_sp((void *)new_sp);
}
#endif
esp_restart_noos_inner();
}

View File

@@ -323,15 +323,31 @@ TEST_CASE_MULTIPLE_STAGES("reset reason ESP_RST_BROWNOUT after brownout event",
check_reset_reason_brownout);
#ifdef CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM
#ifndef CONFIG_FREERTOS_UNICORE
#if (defined(CONFIG_IDF_TARGET_ARCH_XTENSA) && !defined(CONFIG_FREERTOS_UNICORE)) || defined(CONFIG_IDF_TARGET_ARCH_RISCV)
#include "esp_memory_utils.h"
#include "freertos/task.h"
#if CONFIG_IDF_TARGET_ARCH_XTENSA
#include "xt_instr_macros.h"
#include "xtensa/config/xt_specreg.h"
#endif
/* IDF's xTaskCreateStatic[PinnedToCore]() takes the stack size in BYTES
* (not in words like upstream FreeRTOS). */
static int size_stack = 1024 * 4;
static StackType_t *start_addr_stack;
static int fibonacci(int n, void* func(void))
static void func_do_exception(void)
{
*((volatile int *) 0x01) = 0;
}
#if CONFIG_IDF_TARGET_ARCH_XTENSA
/* On Xtensa (windowed ABI), recursing forces the register window to wrap,
* which triggers overflow/underflow exceptions that spill/fill registers
* to/from the (SPIRAM-backed) task stack. This makes sure SP is genuinely
* pointing into SPIRAM and that spilled frames live there too when func()
* is finally called from deep inside the recursion. */
static int fibonacci(int n, void *func(void))
{
int tmp1 = n, tmp2 = n;
uint32_t base, start;
@@ -349,6 +365,7 @@ static int fibonacci(int n, void* func(void))
printf("fib = %d\n", (tmp1 - tmp2) + fib);
return fib;
}
#endif // CONFIG_IDF_TARGET_ARCH_XTENSA
static void test_task(void *func)
{
@@ -358,30 +375,13 @@ static void test_task(void *func)
} else {
printf("restart_task: uses internal stack, addr_stack = %p\n", start_addr_stack);
}
#if CONFIG_IDF_TARGET_ARCH_XTENSA
fibonacci(35, func);
}
static void func_do_exception(void)
{
*((int *) 0) = 0;
}
static void init_restart_task(void)
{
StackType_t *stack_for_task = (StackType_t *) heap_caps_calloc(1, size_stack, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
printf("init_task: current addr_stack = %p, stack_for_task = %p\n", esp_cpu_get_sp(), stack_for_task);
static StaticTask_t task_buf;
xTaskCreateStaticPinnedToCore(test_task, "test_task", size_stack, esp_restart, 5, stack_for_task, &task_buf, 1);
while (1) { };
}
static void init_task_do_exception(void)
{
StackType_t *stack_for_task = (StackType_t *) heap_caps_calloc(1, size_stack, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
printf("init_task: current addr_stack = %p, stack_for_task = %p\n", esp_cpu_get_sp(), stack_for_task);
static StaticTask_t task_buf;
xTaskCreateStaticPinnedToCore(test_task, "test_task", size_stack, func_do_exception, 5, stack_for_task, &task_buf, 1);
while (1) { };
#else
/* RISC-V has no register windows, so no need to recurse. Just invoke
* the test function directly from this task (whose stack is in SPIRAM). */
((void (*)(void))func)();
#endif
}
static void test1_finish(void)
@@ -396,6 +396,38 @@ static void test2_finish(void)
printf("test - OK\n");
}
#if CONFIG_IDF_TARGET_ARCH_XTENSA
#ifndef CONFIG_FREERTOS_UNICORE
#define CREATE_TEST_TASK(stack, buf, entry) \
xTaskCreateStaticPinnedToCore(test_task, "test_task", size_stack, (entry), 5, (stack), (buf), 1)
#endif // !CONFIG_FREERTOS_UNICORE
#else // RISCV
#define CREATE_TEST_TASK(stack, buf, entry) \
xTaskCreateStatic(test_task, "test_task", size_stack, (entry), 5, (stack), (buf))
#endif
#if defined(CREATE_TEST_TASK)
static void init_restart_task(void)
{
StackType_t *stack_for_task = (StackType_t *) heap_caps_calloc(1, size_stack, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(stack_for_task);
printf("init_task: current addr_stack = %p, stack_for_task = %p\n", esp_cpu_get_sp(), stack_for_task);
static StaticTask_t task_buf;
CREATE_TEST_TASK(stack_for_task, &task_buf, esp_restart);
while (1) { }
}
static void init_task_do_exception(void)
{
StackType_t *stack_for_task = (StackType_t *) heap_caps_calloc(1, size_stack, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
TEST_ASSERT_NOT_NULL(stack_for_task);
printf("init_task: current addr_stack = %p, stack_for_task = %p\n", esp_cpu_get_sp(), stack_for_task);
static StaticTask_t task_buf;
CREATE_TEST_TASK(stack_for_task, &task_buf, func_do_exception);
while (1) { }
}
TEST_CASE_MULTIPLE_STAGES("reset reason ESP_RST_SW after restart in a task with spiram stack", "[spiram_stack]",
init_restart_task,
test1_finish);
@@ -404,8 +436,8 @@ TEST_CASE_MULTIPLE_STAGES("reset reason ESP_RST_PANIC after an exception in a ta
init_task_do_exception,
test2_finish);
#endif //CONFIG_IDF_TARGET_ARCH_XTENSA
#endif // CONFIG_FREERTOS_UNICORE
#endif // CREATE_TEST_TASK
#endif // (XTENSA && !UNICORE) || RISCV
#endif // CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM
/* Not tested here: ESP_RST_SDIO */

View File

@@ -106,6 +106,17 @@ FORCE_INLINE_ATTR void *rv_utils_get_sp(void)
return sp;
}
/* Switch the stack pointer to a new address.
*
* Unlike the Xtensa SET_STACK, no window register management is required on
* RISC-V; a plain register move is sufficient. The "memory" clobber prevents
* the compiler from reordering any accesses across the switch.
*/
FORCE_INLINE_ATTR void rv_utils_set_sp(void *new_sp)
{
asm volatile ("mv sp, %0" :: "r"(new_sp) : "memory");
}
FORCE_INLINE_ATTR uint32_t __attribute__((always_inline)) rv_utils_get_cycle_count(void)
{
#if !SOC_CPU_HAS_CSR_PC