Merge branch 'feat/support_rv_fp_retention_v5.4' into 'release/v5.4'

feat(esp_hw_support): support rv fp retention (v5.4)

See merge request espressif/esp-idf!46523
This commit is contained in:
Jiang Jiang Jian
2026-03-13 12:24:13 +08:00
11 changed files with 322 additions and 8 deletions
@@ -13,6 +13,13 @@ if(CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP OR
APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u rv_core_critical_regs_save")
set_property(TARGET ${COMPONENT_LIB}
APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u rv_core_critical_regs_restore")
if(CONFIG_SOC_CPU_HAS_FPU AND CONFIG_SOC_PM_FPU_RETENTION_BY_SW)
list(APPEND srcs "port/${target}/sleep_fpu_asm.S")
set_property(TARGET ${COMPONENT_LIB}
APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u rv_core_fpu_save")
set_property(TARGET ${COMPONENT_LIB}
APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u rv_core_fpu_restore")
endif()
endif()
endif()
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -88,6 +88,40 @@ STRUCT_BEGIN
STRUCT_FIELD (long, 4, RV_SLP_CTX_MIE, mie) /* Machine intr enable */
STRUCT_FIELD (long, 4, RV_SLP_CTX_MIP, mip) /* Machine intr pending */
/* FPU context */
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT0, fpu_ft0)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT1, fpu_ft1)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT2, fpu_ft2)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT3, fpu_ft3)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT4, fpu_ft4)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT5, fpu_ft5)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT6, fpu_ft6)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT7, fpu_ft7)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS0, fpu_fs0)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS1, fpu_fs1)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FA0, fpu_fa0)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FA1, fpu_fa1)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FA2, fpu_fa2)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FA3, fpu_fa3)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FA4, fpu_fa4)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FA5, fpu_fa5)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FA6, fpu_fa6)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FA7, fpu_fa7)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS2, fpu_fs2)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS3, fpu_fs3)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS4, fpu_fs4)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS5, fpu_fs5)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS6, fpu_fs6)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS7, fpu_fs7)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS8, fpu_fs8)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS9, fpu_fs9)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS10, fpu_fs10)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FS11, fpu_fs11)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT8, fpu_ft8)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT9, fpu_ft9)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT10, fpu_ft10)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FT11, fpu_ft11)
STRUCT_FIELD (long, 4, RV_SLP_CTX_FPU_FCSR, fpu_fcsr)
STRUCT_FIELD (long, 4, RV_SLP_CTX_PMUFUNC, pmufunc) /* A field is used to identify whether it is going
* to sleep or has just been awakened. We use the
* lowest 2 bits as indication information, 3 means
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -19,6 +19,7 @@
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_heap_caps.h"
#include "freertos/portmacro.h"
#include "riscv/csr.h"
#include "soc/clic_reg.h"
#include "soc/rtc_periph.h"
@@ -54,6 +55,8 @@ typedef enum {
static TCM_DRAM_ATTR smp_retention_state_t s_smp_retention_state[portNUM_PROCESSORS];
#endif
static bool s_fpu_saved[portNUM_PROCESSORS];
static __attribute__((unused)) const char *TAG = "sleep";
typedef struct {
@@ -376,17 +379,22 @@ static TCM_IRAM_ATTR void validate_retention_frame_crc(uint32_t *frame_ptr, uint
extern RvCoreCriticalSleepFrame * rv_core_critical_regs_save(void);
extern RvCoreCriticalSleepFrame * rv_core_critical_regs_restore(void);
extern void rv_core_fpu_save(RvCoreCriticalSleepFrame *frame);
extern void rv_core_fpu_restore(RvCoreCriticalSleepFrame *frame);
typedef uint32_t (* sleep_cpu_entry_cb_t)(uint32_t, uint32_t, uint32_t, bool);
static TCM_IRAM_ATTR esp_err_t do_cpu_retention(sleep_cpu_entry_cb_t goto_sleep,
uint32_t wakeup_opt, uint32_t reject_opt, uint32_t lslp_mem_inf_fpu, bool dslp)
{
uint8_t core_id = esp_cpu_get_core_id();
RvCoreCriticalSleepFrame *frame = s_cpu_retention.retent.critical_frame[core_id];
/* mstatus is core privated CSR, do it near the core critical regs restore */
uint32_t mstatus = save_mstatus_and_disable_global_int();
s_fpu_saved[core_id] = xPortFPUContextIsDirty(core_id);
if (s_fpu_saved[core_id]) {
rv_core_fpu_save(frame);
}
rv_core_critical_regs_save();
RvCoreCriticalSleepFrame * frame = s_cpu_retention.retent.critical_frame[core_id];
if ((frame->pmufunc & 0x3) == 0x1) {
esp_sleep_execute_event_callbacks(SLEEP_EVENT_SW_CPU_TO_MEM_END, (void *)0);
#if CONFIG_PM_CHECK_SLEEP_RETENTION_FRAME
@@ -413,6 +421,9 @@ static TCM_IRAM_ATTR esp_err_t do_cpu_retention(sleep_cpu_entry_cb_t goto_sleep,
validate_retention_frame_crc((uint32_t*)frame, RV_SLEEP_CTX_SZ1 - 2 * sizeof(long), (uint32_t *)(&frame->frame_crc));
}
#endif
if (s_fpu_saved[core_id]) {
rv_core_fpu_restore(frame);
}
restore_mstatus(mstatus);
return pmu_sleep_finish(dslp);
}
@@ -524,9 +535,13 @@ static TCM_IRAM_ATTR void smp_core_do_retention(void)
atomic_store(&s_smp_retention_state[core_id], SMP_BACKUP_START);
rv_core_noncritical_regs_save();
cpu_domain_dev_regs_save(s_cpu_retention.retent.clic_frame[core_id]);
uint32_t mstatus = save_mstatus_and_disable_global_int();
rv_core_critical_regs_save();
RvCoreCriticalSleepFrame *frame_critical = s_cpu_retention.retent.critical_frame[core_id];
uint32_t mstatus = save_mstatus_and_disable_global_int();
s_fpu_saved[core_id] = xPortFPUContextIsDirty(core_id);
if (s_fpu_saved[core_id]) {
rv_core_fpu_save(frame_critical);
}
rv_core_critical_regs_save();
if ((frame_critical->pmufunc & 0x3) == 0x1) {
atomic_store(&s_smp_retention_state[core_id], SMP_BACKUP_DONE);
// wait another core trigger sleep and wakeup
@@ -544,6 +559,9 @@ static TCM_IRAM_ATTR void smp_core_do_retention(void)
REG_CLR_BIT(HP_SYS_CLKRST_HP_RST_EN0_REG, HP_SYS_CLKRST_REG_RST_EN_CORE1_GLOBAL);
}
atomic_store(&s_smp_retention_state[core_id], SMP_RESTORE_START);
if (s_fpu_saved[core_id]) {
rv_core_fpu_restore(frame_critical);
}
restore_mstatus(mstatus);
cpu_domain_dev_regs_restore(s_cpu_retention.retent.clic_frame[core_id]);
rv_core_noncritical_regs_restore();
@@ -0,0 +1,118 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "rvsleep-frames.h"
#include "freertos/FreeRTOSConfig.h"
#include "sdkconfig.h"
.section .tcm.text,"ax"
.global rv_core_fpu_save
.type rv_core_fpu_save,@function
.align 4
/* Bit to set in mstatus to enable the FPU */
#define CSR_MSTATUS_FPU_ENABLE (1 << 13)
/* Bit to clear in mstatus to disable the FPU */
#define CSR_MSTATUS_FPU_DISABLE (3 << 13)
.macro fpu_enable reg
li \reg, CSR_MSTATUS_FPU_ENABLE
csrs mstatus, \reg
.endm
rv_core_fpu_save:
fpu_enable t0
mv t0, a0 /* t0 = frame (arg0) */
fsw ft0, RV_SLP_CTX_FPU_FT0(t0)
fsw ft1, RV_SLP_CTX_FPU_FT1(t0)
fsw ft2, RV_SLP_CTX_FPU_FT2(t0)
fsw ft3, RV_SLP_CTX_FPU_FT3(t0)
fsw ft4, RV_SLP_CTX_FPU_FT4(t0)
fsw ft5, RV_SLP_CTX_FPU_FT5(t0)
fsw ft6, RV_SLP_CTX_FPU_FT6(t0)
fsw ft7, RV_SLP_CTX_FPU_FT7(t0)
fsw fs0, RV_SLP_CTX_FPU_FS0(t0)
fsw fs1, RV_SLP_CTX_FPU_FS1(t0)
fsw fa0, RV_SLP_CTX_FPU_FA0(t0)
fsw fa1, RV_SLP_CTX_FPU_FA1(t0)
fsw fa2, RV_SLP_CTX_FPU_FA2(t0)
fsw fa3, RV_SLP_CTX_FPU_FA3(t0)
fsw fa4, RV_SLP_CTX_FPU_FA4(t0)
fsw fa5, RV_SLP_CTX_FPU_FA5(t0)
fsw fa6, RV_SLP_CTX_FPU_FA6(t0)
fsw fa7, RV_SLP_CTX_FPU_FA7(t0)
fsw fs2, RV_SLP_CTX_FPU_FS2(t0)
fsw fs3, RV_SLP_CTX_FPU_FS3(t0)
fsw fs4, RV_SLP_CTX_FPU_FS4(t0)
fsw fs5, RV_SLP_CTX_FPU_FS5(t0)
fsw fs6, RV_SLP_CTX_FPU_FS6(t0)
fsw fs7, RV_SLP_CTX_FPU_FS7(t0)
fsw fs8, RV_SLP_CTX_FPU_FS8(t0)
fsw fs9, RV_SLP_CTX_FPU_FS9(t0)
fsw fs10, RV_SLP_CTX_FPU_FS10(t0)
fsw fs11, RV_SLP_CTX_FPU_FS11(t0)
fsw ft8, RV_SLP_CTX_FPU_FT8(t0)
fsw ft9, RV_SLP_CTX_FPU_FT9(t0)
fsw ft10, RV_SLP_CTX_FPU_FT10(t0)
fsw ft11, RV_SLP_CTX_FPU_FT11(t0)
csrr t1, fcsr
sw t1, RV_SLP_CTX_FPU_FCSR(t0)
/* Chip will go to sleep and FPU will be powered down, not necessary to disable FPU here. */
ret
.size rv_core_fpu_save, . - rv_core_fpu_save
/*
--------------------------------------------------------------------------------
FPU restore: a0 = RvCoreCriticalSleepFrame *. Restore all FP registers from the frame.
--------------------------------------------------------------------------------
*/
.section .iram1,"ax"
.global rv_core_fpu_restore
.type rv_core_fpu_restore,@function
.align 4
rv_core_fpu_restore:
fpu_enable t0
mv t0, a0 /* t0 = frame (arg0) */
flw ft0, RV_SLP_CTX_FPU_FT0(t0)
flw ft1, RV_SLP_CTX_FPU_FT1(t0)
flw ft2, RV_SLP_CTX_FPU_FT2(t0)
flw ft3, RV_SLP_CTX_FPU_FT3(t0)
flw ft4, RV_SLP_CTX_FPU_FT4(t0)
flw ft5, RV_SLP_CTX_FPU_FT5(t0)
flw ft6, RV_SLP_CTX_FPU_FT6(t0)
flw ft7, RV_SLP_CTX_FPU_FT7(t0)
flw fs0, RV_SLP_CTX_FPU_FS0(t0)
flw fs1, RV_SLP_CTX_FPU_FS1(t0)
flw fa0, RV_SLP_CTX_FPU_FA0(t0)
flw fa1, RV_SLP_CTX_FPU_FA1(t0)
flw fa2, RV_SLP_CTX_FPU_FA2(t0)
flw fa3, RV_SLP_CTX_FPU_FA3(t0)
flw fa4, RV_SLP_CTX_FPU_FA4(t0)
flw fa5, RV_SLP_CTX_FPU_FA5(t0)
flw fa6, RV_SLP_CTX_FPU_FA6(t0)
flw fa7, RV_SLP_CTX_FPU_FA7(t0)
flw fs2, RV_SLP_CTX_FPU_FS2(t0)
flw fs3, RV_SLP_CTX_FPU_FS3(t0)
flw fs4, RV_SLP_CTX_FPU_FS4(t0)
flw fs5, RV_SLP_CTX_FPU_FS5(t0)
flw fs6, RV_SLP_CTX_FPU_FS6(t0)
flw fs7, RV_SLP_CTX_FPU_FS7(t0)
flw fs8, RV_SLP_CTX_FPU_FS8(t0)
flw fs9, RV_SLP_CTX_FPU_FS9(t0)
flw fs10, RV_SLP_CTX_FPU_FS10(t0)
flw fs11, RV_SLP_CTX_FPU_FS11(t0)
flw ft8, RV_SLP_CTX_FPU_FT8(t0)
flw ft9, RV_SLP_CTX_FPU_FT9(t0)
flw ft10, RV_SLP_CTX_FPU_FT10(t0)
flw ft11, RV_SLP_CTX_FPU_FT11(t0)
lw t1, RV_SLP_CTX_FPU_FCSR(t0)
csrw fcsr, t1
/* Caller restores mstatus (and thus FPU state); not necessary to disable FPU here. */
ret
.size rv_core_fpu_restore, . - rv_core_fpu_restore
@@ -1,6 +1,10 @@
set(sources "test_app_main.c"
"test_pm.c")
if(CONFIG_SOC_CPU_HAS_FPU AND CONFIG_IDF_TARGET_ARCH_RISCV AND CONFIG_SOC_PM_FPU_RETENTION_BY_SW)
list(APPEND sources "test_fpu_retention.c")
endif()
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component must be registered as a WHOLE_ARCHIVE
idf_component_register(SRCS ${sources}
@@ -0,0 +1,105 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "sdkconfig.h"
#include <math.h>
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "unity.h"
#include "esp_private/esp_clk.h"
#include "esp_pm.h"
#include "esp_task.h"
#include "soc/soc_caps.h"
#define MHZ (1000 * 1000)
#define FP_EPS 1e-5f
#define FP_CTX_ITERATIONS 20
#define TEST_TASKS 4
/* Context Switch Test */
static struct {
int fail_count;
SemaphoreHandle_t done;
float seed;
} s_fp_ctx[TEST_TASKS];
static void fp_context_task(void *arg)
{
vTaskDelay(2);
int idx = (int)(intptr_t)arg;
volatile float seed = s_fp_ctx[idx].seed;
for (int i = 0; i < FP_CTX_ITERATIONS; i++) {
volatile float v0 = seed * 6.789f;
vTaskDelay(10);
volatile float v1 = v0 + 8.987f;
vTaskDelay(10);
volatile float v2 = v1 * v1;
vTaskDelay(10);
volatile float v3 = sqrt(v2);
vTaskDelay(10);
volatile float v4 = v3 - 8.987f;
vTaskDelay(10);
volatile float result = v4 / 6.789f;
if (fabsf(result - seed) > FP_EPS) {
s_fp_ctx[idx].fail_count++;
}
}
xSemaphoreGive(s_fp_ctx[idx].done);
vTaskDelete(NULL);
}
TEST_CASE("Test PD_CPU lightsleep preserves FP registers", "[rv_fp]")
{
#if CONFIG_PM_ENABLE
int cur_freq_mhz = esp_clk_cpu_freq() / MHZ;
int xtal_freq = esp_clk_xtal_freq() / MHZ;
esp_pm_config_t pm_config = {
.max_freq_mhz = cur_freq_mhz,
.min_freq_mhz = xtal_freq,
.light_sleep_enable = true
};
ESP_ERROR_CHECK( esp_pm_configure(&pm_config) );
#endif
const float seeds = 3.1415926f;
SemaphoreHandle_t sem = xSemaphoreCreateCounting(TEST_TASKS, 0);
TEST_ASSERT_NOT_NULL(sem);
for (int i = 0; i < TEST_TASKS; i++) {
s_fp_ctx[i].fail_count = 0;
s_fp_ctx[i].done = sem;
s_fp_ctx[i].seed = seeds * i;
TEST_ASSERT_EQUAL(pdPASS, xTaskCreatePinnedToCore(
fp_context_task, "fp", 4096, (void*)(intptr_t)i,
ESP_TASK_MAIN_PRIO, NULL, i % CONFIG_FREERTOS_NUMBER_OF_CORES));
}
for (int i = 0; i < TEST_TASKS; i++) {
TEST_ASSERT_TRUE(xSemaphoreTake(sem, pdMS_TO_TICKS(20000)));
}
vSemaphoreDelete(sem);
int total = 0;
for (int i = 0; i < TEST_TASKS; i++) {
total += s_fp_ctx[i].fail_count;
}
TEST_ASSERT_EQUAL_MESSAGE(0, total, "FPU context corruption detected");
printf("FPU context retention test passed\n");
#if CONFIG_PM_ENABLE
// Disable lightsleep and DFS
pm_config.min_freq_mhz = cur_freq_mhz;
pm_config.light_sleep_enable = false;
ESP_ERROR_CHECK( esp_pm_configure(&pm_config) );
#endif
}
@@ -6,7 +6,7 @@
*
* SPDX-License-Identifier: MIT
*
* SPDX-FileContributor: 2023-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileContributor: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -758,6 +758,21 @@ extern volatile UBaseType_t xPortSwitchFlag[portNUM_PROCESSORS];
#define os_task_switch_is_pended(_cpu_) (false)
#endif
// -------------- FPU softerware retention ------------------
#if (SOC_CPU_COPROC_NUM > 0) && SOC_CPU_HAS_FPU && SOC_PM_FPU_RETENTION_BY_SW
/**
* @brief Whether the FPU context is dirty on the given core.
*
* Returns non-zero if any task has used the FPU, such context should be
* saved during sleep retention.
*
* @param core_id Core id
* @return pdTRUE FPU context is dirty
* @return pdFALSE FPU was not used
*/
BaseType_t xPortFPUContextIsDirty(BaseType_t core_id);
#endif
#ifdef __cplusplus
}
#endif
@@ -6,7 +6,7 @@
*
* SPDX-License-Identifier: MIT
*
* SPDX-FileContributor: 2023-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileContributor: 2023-2026 Espressif Systems (Shanghai) CO LTD
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal in
@@ -886,3 +886,9 @@ void vPortCoprocUsedInISR(void* frame)
/* ---------------------------------------------- Misc Implementations -------------------------------------------------
*
* ------------------------------------------------------------------------------------------------------------------ */
#if (SOC_CPU_COPROC_NUM > 0) && SOC_CPU_HAS_FPU && SOC_PM_FPU_RETENTION_BY_SW
BaseType_t xPortFPUContextIsDirty(BaseType_t core_id)
{
return (BaseType_t)(port_uxCoprocOwner[core_id][FPU_COPROC_IDX] != NULL);
}
#endif /* (SOC_CPU_COPROC_NUM > 0) && SOC_CPU_HAS_FPU && SOC_PM_FPU_RETENTION_BY_SW */
+2
View File
@@ -238,3 +238,5 @@ entries:
if FREERTOS_TLSP_DELETION_CALLBACKS = y:
port:vPortTLSPointersDelCb (default)
port:vPortTCBPreDeleteHook (default)
if (SOC_CPU_COPROC_NUM > 0) && SOC_CPU_HAS_FPU && SOC_PM_FPU_RETENTION_BY_SW:
port:xPortFPUContextIsDirty (noflash_text)
@@ -2015,6 +2015,10 @@ config SOC_PM_CPU_RETENTION_BY_SW
bool
default y
config SOC_PM_FPU_RETENTION_BY_SW
bool
default y
config SOC_PM_CACHE_RETENTION_BY_PAU
bool
default y
@@ -745,6 +745,7 @@
#define SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_ONLY (1) /*!<Supports CRC only the stub code in RTC memory */
#define SOC_PM_CPU_RETENTION_BY_SW (1)
#define SOC_PM_FPU_RETENTION_BY_SW (1)
#define SOC_PM_CACHE_RETENTION_BY_PAU (1)
#define SOC_PM_PAU_LINK_NUM (4)