From 261bd160aadfee1ba9490696c3febb3e6b8c434e Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Thu, 9 Apr 2026 10:49:06 +0800 Subject: [PATCH] feat(ulp): support placing ULP app in HP-RAM --- .../esp_system/ld/esp32c5/ld.hp_mem_defs | 15 ++ components/esp_system/ld/esp32c5/memory.ld.in | 13 +- .../esp_system/ld/esp32c6/ld.hp_mem_defs | 15 ++ components/esp_system/ld/esp32c6/memory.ld.in | 13 +- .../esp_system/ld/esp32p4/ld.hp_mem_defs | 22 +++ components/esp_system/ld/esp32p4/memory.ld.in | 14 ++ .../esp_system/ld/esp32s31/ld.hp_mem_defs | 15 ++ .../esp_system/ld/esp32s31/memory.ld.in | 13 +- components/heap/port/esp32c5/memory_layout.c | 14 +- components/heap/port/esp32c6/memory_layout.c | 8 +- components/heap/port/esp32p4/memory_layout.c | 6 + components/heap/port/esp32s31/memory_layout.c | 6 + components/ulp/CMakeLists.txt | 1 + components/ulp/Kconfig | 26 +++ components/ulp/cmake/IDFULPProject.cmake | 18 +- components/ulp/esp32ulp_mapgen.py | 35 +++- components/ulp/ld/lp_core_riscv.ld | 46 ++++- components/ulp/lp_core/lp_core.c | 133 +++++++++++++- .../lp_core/include/ulp_lp_core_interrupts.h | 2 +- .../ulp/lp_core/lp_core/lp_core_interrupt.c | 2 +- .../ulp/test_apps/.build-test-rules.yml | 10 ++ .../lp_core/lp_core_hp_mem/CMakeLists.txt | 14 ++ .../lp_core/lp_core_hp_mem/README.md | 3 + .../lp_core_hp_mem/main/CMakeLists.txt | 11 ++ .../lp_core_hp_mem/main/lp_core/test_main.c | 29 +++ .../lp_core_hp_mem/main/test_app_main.c | 41 +++++ .../lp_core_hp_mem/main/test_lp_core_hp_mem.c | 170 ++++++++++++++++++ .../lp_core_hp_mem/pytest_lp_core_hp_mem.py | 11 ++ .../lp_core/lp_core_hp_mem/sdkconfig.defaults | 8 + docs/en/api-reference/system/ulp-lp-core.rst | 9 + .../api-reference/system/ulp-lp-core.rst | 9 + 31 files changed, 698 insertions(+), 34 deletions(-) create mode 100644 components/esp_system/ld/esp32c5/ld.hp_mem_defs create mode 100644 components/esp_system/ld/esp32c6/ld.hp_mem_defs create mode 100644 components/esp_system/ld/esp32p4/ld.hp_mem_defs create mode 100644 components/esp_system/ld/esp32s31/ld.hp_mem_defs create mode 100644 components/ulp/test_apps/lp_core/lp_core_hp_mem/CMakeLists.txt create mode 100644 components/ulp/test_apps/lp_core/lp_core_hp_mem/README.md create mode 100644 components/ulp/test_apps/lp_core/lp_core_hp_mem/main/CMakeLists.txt create mode 100644 components/ulp/test_apps/lp_core/lp_core_hp_mem/main/lp_core/test_main.c create mode 100644 components/ulp/test_apps/lp_core/lp_core_hp_mem/main/test_app_main.c create mode 100644 components/ulp/test_apps/lp_core/lp_core_hp_mem/main/test_lp_core_hp_mem.c create mode 100644 components/ulp/test_apps/lp_core/lp_core_hp_mem/pytest_lp_core_hp_mem.py create mode 100644 components/ulp/test_apps/lp_core/lp_core_hp_mem/sdkconfig.defaults diff --git a/components/esp_system/ld/esp32c5/ld.hp_mem_defs b/components/esp_system/ld/esp32c5/ld.hp_mem_defs new file mode 100644 index 00000000000..6b9f49869f3 --- /dev/null +++ b/components/esp_system/ld/esp32c5/ld.hp_mem_defs @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* ESP32-C5 target-specific HP memory definitions shared between HP app and LP Core linker scripts */ + +#define SRAM_SEG_END 0x4084E5A0 /* 2nd stage bootloader iram_loader_seg start address */ + +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +#define ULP_HP_MEM_SIZE CONFIG_ULP_COPROC_RESERVE_HP_MEM_BYTES +#define ULP_HP_MEM_START (SRAM_SEG_END - ULP_HP_MEM_SIZE) +#define ULP_HP_MEM_END SRAM_SEG_END +#endif diff --git a/components/esp_system/ld/esp32c5/memory.ld.in b/components/esp_system/ld/esp32c5/memory.ld.in index 9f6d6c84a21..b606e1acb63 100644 --- a/components/esp_system/ld/esp32c5/memory.ld.in +++ b/components/esp_system/ld/esp32c5/memory.ld.in @@ -14,6 +14,7 @@ #include "sdkconfig.h" #include "ld.common" +#include "ld.hp_mem_defs" #if !CONFIG_SECURE_ENABLE_TEE #define SRAM_SEG_START (0x40800000) @@ -22,8 +23,11 @@ #define FLASH_SEG_OFFSET (CONFIG_SECURE_TEE_IROM_SIZE + CONFIG_SECURE_TEE_DROM_SIZE) #endif // CONFIG_SECURE_ENABLE_TEE -#define SRAM_SEG_END 0x4084E5A0 /* 2nd stage bootloader iram_loader_seg start address */ -#define SRAM_SEG_SIZE SRAM_SEG_END - SRAM_SEG_START +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +#define SRAM_SEG_SIZE ((ULP_HP_MEM_START) - (SRAM_SEG_START)) +#else +#define SRAM_SEG_SIZE ((SRAM_SEG_END) - (SRAM_SEG_START)) +#endif /* * IDRAM0_2_SEG_SIZE_DEFAULT is used when page size is 64KB @@ -163,3 +167,8 @@ REGION_ALIAS("dram_seg", sram_seg); ALIGN_VECTOR_TABLE = 0x100; OFFSET_TEE = 0x2b0; + +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +_ulp_hp_mem_resv_start = ULP_HP_MEM_START; +_ulp_hp_mem_resv_end = ULP_HP_MEM_END; +#endif diff --git a/components/esp_system/ld/esp32c6/ld.hp_mem_defs b/components/esp_system/ld/esp32c6/ld.hp_mem_defs new file mode 100644 index 00000000000..1ba14f51711 --- /dev/null +++ b/components/esp_system/ld/esp32c6/ld.hp_mem_defs @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* ESP32-C6 target-specific HP memory definitions shared between HP app and LP Core linker scripts */ + +#define SRAM_SEG_END 0x4086E610 /* 2nd stage bootloader iram_loader_seg start address */ + +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +#define ULP_HP_MEM_SIZE CONFIG_ULP_COPROC_RESERVE_HP_MEM_BYTES +#define ULP_HP_MEM_START (SRAM_SEG_END - ULP_HP_MEM_SIZE) +#define ULP_HP_MEM_END SRAM_SEG_END +#endif diff --git a/components/esp_system/ld/esp32c6/memory.ld.in b/components/esp_system/ld/esp32c6/memory.ld.in index 14b5779bff1..3a6d4707f43 100644 --- a/components/esp_system/ld/esp32c6/memory.ld.in +++ b/components/esp_system/ld/esp32c6/memory.ld.in @@ -14,6 +14,7 @@ #include "sdkconfig.h" #include "ld.common" +#include "ld.hp_mem_defs" #if !CONFIG_SECURE_ENABLE_TEE #define SRAM_SEG_START (0x40800000) @@ -22,8 +23,11 @@ #define FLASH_SEG_OFFSET (CONFIG_SECURE_TEE_IROM_SIZE + CONFIG_SECURE_TEE_DROM_SIZE) #endif // CONFIG_SECURE_ENABLE_TEE -#define SRAM_SEG_END 0x4086E610 /* 2nd stage bootloader iram_loader_seg start address */ -#define SRAM_SEG_SIZE SRAM_SEG_END - SRAM_SEG_START +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +#define SRAM_SEG_SIZE ((ULP_HP_MEM_START) - (SRAM_SEG_START)) +#else +#define SRAM_SEG_SIZE ((SRAM_SEG_END) - (SRAM_SEG_START)) +#endif #if CONFIG_APP_BUILD_USE_FLASH_SECTIONS /* @@ -161,3 +165,8 @@ REGION_ALIAS("dram_seg", sram_seg); ALIGN_VECTOR_TABLE = 0x100; OFFSET_TEE = 0x2e0; + +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +_ulp_hp_mem_resv_start = ULP_HP_MEM_START; +_ulp_hp_mem_resv_end = ULP_HP_MEM_END; +#endif diff --git a/components/esp_system/ld/esp32p4/ld.hp_mem_defs b/components/esp_system/ld/esp32p4/ld.hp_mem_defs new file mode 100644 index 00000000000..18ac6b5979f --- /dev/null +++ b/components/esp_system/ld/esp32p4/ld.hp_mem_defs @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* ESP32-P4 target-specific HP memory definitions shared between HP app and LP Core linker scripts */ + +#if CONFIG_ESP32P4_SELECTS_REV_LESS_V3 +#define SRAM_MAIN_END 0x4FF2CBD0 /* 2nd stage bootloader iram_loader_seg start address (rev < 3) */ +#else +#define SRAM_MAIN_END 0x4FFAEFC0 /* 2nd stage bootloader iram_loader_seg start address (rev3+) */ +#endif + +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +#define ULP_HP_MEM_SIZE CONFIG_ULP_COPROC_RESERVE_HP_MEM_BYTES +/* Align down to 64-byte cache line boundary */ +#define ULP_HP_MEM_START ((SRAM_MAIN_END - ULP_HP_MEM_SIZE) & ~(64 - 1)) +/* Align down to 64-byte cache line boundary so that ULP_HP_MEM_END - ULP_HP_MEM_START + * is always a multiple of the cache line size, satisfying esp_cache_msync alignment */ +#define ULP_HP_MEM_END (SRAM_MAIN_END & ~(64 - 1)) +#endif diff --git a/components/esp_system/ld/esp32p4/memory.ld.in b/components/esp_system/ld/esp32p4/memory.ld.in index 78770994f71..39eb420e31d 100644 --- a/components/esp_system/ld/esp32p4/memory.ld.in +++ b/components/esp_system/ld/esp32p4/memory.ld.in @@ -14,14 +14,23 @@ #include "sdkconfig.h" #include "ld.common" +#include "ld.hp_mem_defs" #if !CONFIG_ESP32P4_SELECTS_REV_LESS_V3 #define SRAM_START 0x4FF00000 + CONFIG_CACHE_L2_CACHE_SIZE +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +#define SRAM_END ULP_HP_MEM_START +#else #define SRAM_END 0x4FFAEFC0 /* 2nd stage bootloader iram_loader_seg start address */ +#endif #define SRAM_SIZE SRAM_END - SRAM_START #else #define SRAM_LOW_START 0x4FF00000 +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +#define SRAM_LOW_END ULP_HP_MEM_START +#else #define SRAM_LOW_END 0x4FF2CBD0 /* 2nd stage bootloader iram_loader_seg start address */ +#endif #define SRAM_LOW_SIZE SRAM_LOW_END - SRAM_LOW_START /* If the cache size is less than 512KB, then there is a region of RAM @@ -209,3 +218,8 @@ REGION_ALIAS("rtc_reserved_seg", lp_reserved_seg ); #endif //#if CONFIG_SPIRAM_XIP_FROM_PSRAM ALIGN_VECTOR_TABLE = 0x40; + +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +_ulp_hp_mem_resv_start = ULP_HP_MEM_START; +_ulp_hp_mem_resv_end = ULP_HP_MEM_END; +#endif diff --git a/components/esp_system/ld/esp32s31/ld.hp_mem_defs b/components/esp_system/ld/esp32s31/ld.hp_mem_defs new file mode 100644 index 00000000000..ece5005fcef --- /dev/null +++ b/components/esp_system/ld/esp32s31/ld.hp_mem_defs @@ -0,0 +1,15 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* ESP32-S31 target-specific HP memory definitions shared between HP app and LP Core linker scripts */ + +#define SRAM_SEG_END 0x2F07AFC0 /* 2nd stage bootloader iram_loader_seg start address */ + +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +#define ULP_HP_MEM_SIZE CONFIG_ULP_COPROC_RESERVE_HP_MEM_BYTES +#define ULP_HP_MEM_START (SRAM_SEG_END - ULP_HP_MEM_SIZE) +#define ULP_HP_MEM_END SRAM_SEG_END +#endif diff --git a/components/esp_system/ld/esp32s31/memory.ld.in b/components/esp_system/ld/esp32s31/memory.ld.in index 834d369032f..5c472a46a77 100644 --- a/components/esp_system/ld/esp32s31/memory.ld.in +++ b/components/esp_system/ld/esp32s31/memory.ld.in @@ -14,10 +14,14 @@ #include "sdkconfig.h" #include "ld.common" +#include "ld.hp_mem_defs" #define SRAM_START 0x2F000000 -#define SRAM_END 0x2F07AFC0 /* 2nd stage bootloader iram_loader_seg start address */ -#define SRAM_SIZE SRAM_END - SRAM_START +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +#define SRAM_SIZE (ULP_HP_MEM_START - SRAM_START) +#else +#define SRAM_SIZE (SRAM_SEG_END - SRAM_START) +#endif #define IDROM_SEG_SIZE (CONFIG_MMU_PAGE_SIZE << 10) @@ -116,3 +120,8 @@ REGION_ALIAS("rtc_reserved_seg", lp_reserved_seg); #endif //#if CONFIG_SPIRAM_XIP_FROM_PSRAM ALIGN_VECTOR_TABLE = 0x40; + +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +_ulp_hp_mem_resv_start = ULP_HP_MEM_START; +_ulp_hp_mem_resv_end = ULP_HP_MEM_END; +#endif diff --git a/components/heap/port/esp32c5/memory_layout.c b/components/heap/port/esp32c5/memory_layout.c index cbc36de3d56..dfa07c89bb2 100644 --- a/components/heap/port/esp32c5/memory_layout.c +++ b/components/heap/port/esp32c5/memory_layout.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -115,10 +115,16 @@ SOC_RESERVE_MEMORY_REGION((intptr_t)&_data_start, (intptr_t)&_heap_start, dram_d // Target has a shared D/IRAM virtual address, no need to calculate I_D_OFFSET like previous chips SOC_RESERVE_MEMORY_REGION((intptr_t)&_iram_start, (intptr_t)&_iram_end, iram_code); +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +extern uint32_t _ulp_hp_mem_resv_start; +extern uint32_t _ulp_hp_mem_resv_end; +SOC_RESERVE_MEMORY_REGION((intptr_t)&_ulp_hp_mem_resv_start, (intptr_t)&_ulp_hp_mem_resv_end, ulp_hp_mem); +#endif + /* NOTE: When ESP-TEE is enabled, the start of the internal SRAM -* is used by the TEE and is protected from any REE access using -* memory protection mechanisms employed by ESP-TEE. -*/ + * is used by the TEE and is protected from any REE access using + * memory protection mechanisms employed by ESP-TEE. + */ #if CONFIG_SECURE_ENABLE_TEE SOC_RESERVE_MEMORY_REGION((intptr_t)SRAM_DIRAM_TEE_ORG, (intptr_t)(SRAM_DIRAM_TEE_END), tee_diram); #endif diff --git a/components/heap/port/esp32c6/memory_layout.c b/components/heap/port/esp32c6/memory_layout.c index 17543568785..92c5d323d89 100644 --- a/components/heap/port/esp32c6/memory_layout.c +++ b/components/heap/port/esp32c6/memory_layout.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -112,6 +112,12 @@ SOC_RESERVE_MEMORY_REGION((intptr_t)&_data_start, (intptr_t)&_heap_start, dram_d // Target has a shared D/IRAM virtual address, no need to calculate I_D_OFFSET like previous chips SOC_RESERVE_MEMORY_REGION((intptr_t)&_iram_start, (intptr_t)&_iram_end, iram_code); +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +extern uint32_t _ulp_hp_mem_resv_start; +extern uint32_t _ulp_hp_mem_resv_end; +SOC_RESERVE_MEMORY_REGION((intptr_t)&_ulp_hp_mem_resv_start, (intptr_t)&_ulp_hp_mem_resv_end, ulp_hp_mem); +#endif + /* NOTE: When ESP-TEE is enabled, the start of the internal SRAM * is used by the TEE and is protected from any REE access using * memory protection mechanisms employed by ESP-TEE. diff --git a/components/heap/port/esp32p4/memory_layout.c b/components/heap/port/esp32p4/memory_layout.c index d05ffc51096..1be6be5057a 100644 --- a/components/heap/port/esp32p4/memory_layout.c +++ b/components/heap/port/esp32p4/memory_layout.c @@ -146,6 +146,12 @@ SOC_RESERVE_MEMORY_REGION((intptr_t)&_data_start, (intptr_t)&_heap_start, dram_d // Target has a shared D/IRAM virtual address, no need to calculate I_D_OFFSET like previous chips SOC_RESERVE_MEMORY_REGION((intptr_t)&_iram_start, (intptr_t)&_iram_end, iram_code); +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +extern uint32_t _ulp_hp_mem_resv_start; +extern uint32_t _ulp_hp_mem_resv_end; +SOC_RESERVE_MEMORY_REGION((intptr_t)&_ulp_hp_mem_resv_start, (intptr_t)&_ulp_hp_mem_resv_end, ulp_hp_mem); +#endif + SOC_RESERVE_MEMORY_REGION((intptr_t)&_spm_text_start, (intptr_t)&_spm_data_end, spm_code_data); #ifdef CONFIG_SPIRAM diff --git a/components/heap/port/esp32s31/memory_layout.c b/components/heap/port/esp32s31/memory_layout.c index ad3e7f3d906..90040ea191a 100644 --- a/components/heap/port/esp32s31/memory_layout.c +++ b/components/heap/port/esp32s31/memory_layout.c @@ -97,6 +97,12 @@ SOC_RESERVE_MEMORY_REGION((intptr_t)&_data_start, (intptr_t)&_heap_start, dram_d // Target has a shared D/IRAM virtual address, no need to calculate I_D_OFFSET like previous chips SOC_RESERVE_MEMORY_REGION((intptr_t)&_iram_start, (intptr_t)&_iram_end, iram_code); +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +extern uint32_t _ulp_hp_mem_resv_start; +extern uint32_t _ulp_hp_mem_resv_end; +SOC_RESERVE_MEMORY_REGION((intptr_t)&_ulp_hp_mem_resv_start, (intptr_t)&_ulp_hp_mem_resv_end, ulp_hp_mem); +#endif + #ifdef CONFIG_SPIRAM SOC_RESERVE_MEMORY_REGION( SOC_EXTRAM_LOW, SOC_EXTRAM_HIGH, extram_region); #endif diff --git a/components/ulp/CMakeLists.txt b/components/ulp/CMakeLists.txt index 6535bbf73b4..f3ba3d86646 100644 --- a/components/ulp/CMakeLists.txt +++ b/components/ulp/CMakeLists.txt @@ -100,4 +100,5 @@ idf_component_register( INCLUDE_DIRS ${includes} REQUIRES esp_adc esp_driver_gpio esp_driver_uart esp_driver_i2s esp_hal_i2c esp_hal_touch_sens esp_hal_gpspi esp_hal_pmu esp_hal_rtc_timer esp_hal_clock + PRIV_REQUIRES bootloader_support esp_mm ) diff --git a/components/ulp/Kconfig b/components/ulp/Kconfig index 6eca1766e63..09e88a81699 100644 --- a/components/ulp/Kconfig +++ b/components/ulp/Kconfig @@ -81,6 +81,32 @@ menu "Ultra Low Power (ULP) Co-processor" Size of the shared memory defined in ulp_lp_core_memory_shared.c. Size should be kept in-sync with the size of the struct defined there. + menu "LP Core HP Memory" + depends on ULP_COPROC_TYPE_LP_CORE + + config ULP_COPROC_RUN_FROM_HP_MEM + bool "Run LP Core from HP memory" + depends on !SECURE_ENABLE_TEE + default n + help + Enable this option to allow the LP Core application to use HP SRAM for + code and data. This allows much larger LP Core applications than would + fit in LP RAM. + + Note: When this option is enabled, the LP Core cannot run during deep + sleep because HP SRAM is powered off during sleep. + + config ULP_COPROC_RESERVE_HP_MEM_BYTES + hex "HP SRAM size reserved for LP Core (bytes)" + depends on ULP_COPROC_RUN_FROM_HP_MEM + default 0x10000 + range 0x1000 0x80000 + help + Amount of HP SRAM (in bytes) to carve out from the top of the HP SRAM + region for the LP Core application. This memory is not available to the + HP application. + endmenu + config ULP_ROM_PRINT_ENABLE depends on ULP_COPROC_TYPE_LP_CORE && ESP_ROM_HAS_LP_ROM bool diff --git a/components/ulp/cmake/IDFULPProject.cmake b/components/ulp/cmake/IDFULPProject.cmake index dbb8d8a121f..2e1bff36d4f 100644 --- a/components/ulp/cmake/IDFULPProject.cmake +++ b/components/ulp/cmake/IDFULPProject.cmake @@ -70,6 +70,7 @@ function(ulp_apply_default_sources ulp_app_name) list(APPEND ULP_PREPRO_ARGS -I${COMPONENT_DIR}) list(APPEND ULP_PREPRO_ARGS -I${sdkconfig_dir}) list(APPEND ULP_PREPRO_ARGS -I${IDF_PATH}/components/esp_system/ld) + list(APPEND ULP_PREPRO_ARGS -I${IDF_PATH}/components/esp_system/ld/${IDF_TARGET}) target_include_directories(${ulp_app_name} PRIVATE ${COMPONENT_INCLUDES} ${sdkconfig_dir}) @@ -262,14 +263,23 @@ function(ulp_add_build_binary_targets ulp_app_name) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) # Dump the binary for inclusion into the project - add_custom_command(OUTPUT ${ulp_app_name}.bin - COMMAND ${CMAKE_OBJCOPY} -O binary $ ${ulp_app_name}.bin - DEPENDS ${ulp_app_name} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + if(BUILD_LP_CORE AND CONFIG_ULP_COPROC_RUN_FROM_HP_MEM) + add_custom_command(OUTPUT ${ulp_app_name}.bin + COMMAND ${PYTHON} -m esptool --chip ${IDF_TARGET} elf2image + --output ${ulp_app_name}.bin $ + DEPENDS ${ulp_app_name} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + else() + add_custom_command(OUTPUT ${ulp_app_name}.bin + COMMAND ${CMAKE_OBJCOPY} -O binary $ ${ulp_app_name}.bin + DEPENDS ${ulp_app_name} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + endif() add_custom_command(OUTPUT ${ulp_app_name}.ld ${ulp_app_name}.h COMMAND ${ULP_MAP_GEN} -s ${ulp_app_name}.sym -o ${ulp_app_name} --base ${ULP_BASE_ADDR} --prefix ${ULP_PREFIX} + --target ${IDF_TARGET} DEPENDS ${ulp_app_name}.sym WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/components/ulp/esp32ulp_mapgen.py b/components/ulp/esp32ulp_mapgen.py index f841c436e9f..fec5671e4ca 100755 --- a/components/ulp/esp32ulp_mapgen.py +++ b/components/ulp/esp32ulp_mapgen.py @@ -1,5 +1,5 @@ #!/usr/bin/env python -# SPDX-FileCopyrightText: 2016-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2016-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: Apache-2.0 # # esp32ulp_mapgen utility converts a symbol list provided by nm into an export script @@ -19,8 +19,29 @@ def name_mangling(name: str) -> str: return f'_ZN{len(ns)}{ns}{len(n)}{n}E' +# On ESP32-P4 the LP CPU accesses HP SRAM via the L2 cache at the same +# address as the HP CPU cacheable alias. The HP application should access +# LP Core variables through the non-cacheable alias to avoid coherency +# issues with the LP CPU. The non-cacheable alias is obtained by adding +# the offset 0x40000000 to addresses below 0x50000000. +_P4_NON_CACHEABLE_OFFSET = 0x40000000 +_P4_LP_RAM_BASE = 0x50000000 + + +def map_to_hp_cpu_addr(addr: int, target: str | None) -> int: + """Translate an LP-CPU VMA to the address the HP CPU should use to access it.""" + if target == 'esp32p4' and addr < _P4_LP_RAM_BASE: + return addr + _P4_NON_CACHEABLE_OFFSET + return addr + + def gen_ld_h_from_sym( - f_sym: typing.TextIO, f_ld: typing.TextIO, f_h: typing.TextIO, base_addr: int, prefix: str + f_sym: typing.TextIO, + f_ld: typing.TextIO, + f_h: typing.TextIO, + base_addr: int, + prefix: str, + target: str | None = None, ) -> None: f_ld.write( textwrap.dedent( @@ -82,7 +103,7 @@ def gen_ld_h_from_sym( continue # Extract the symbol information - addr = int(groups.group('address'), 16) + base_addr + addr = map_to_hp_cpu_addr(int(groups.group('address'), 16) + base_addr, target) size = int(groups.group('size')) sym_name = groups.group('name') @@ -128,11 +149,17 @@ def main() -> None: ) parser.add_argument('--base-addr', required=True, help='base address of the ULP memory, to be added to each symbol') parser.add_argument('-p', '--prefix', required=False, help='prefix for generated header file', default='ulp_') + parser.add_argument( + '--target', + required=False, + help='IDF target chip name (e.g. esp32p4), used for address translation', + default=None, + ) args = parser.parse_args() with open(args.outputfile + '.h', 'w') as f_h, open(args.outputfile + '.ld', 'w') as f_ld: - gen_ld_h_from_sym(args.symfile, f_ld, f_h, int(args.base_addr, 0), args.prefix) + gen_ld_h_from_sym(args.symfile, f_ld, f_h, int(args.base_addr, 0), args.prefix, args.target) if __name__ == '__main__': diff --git a/components/ulp/ld/lp_core_riscv.ld b/components/ulp/ld/lp_core_riscv.ld index 05d84951b68..0695b507a4e 100644 --- a/components/ulp/ld/lp_core_riscv.ld +++ b/components/ulp/ld/lp_core_riscv.ld @@ -1,11 +1,14 @@ /* - * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2023-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ #include "sdkconfig.h" #include "soc/soc.h" #include "ld.common" +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +#include "ld.hp_mem_defs" +#endif #if CONFIG_ESP_ROM_HAS_LP_ROM /* With LP-ROM memory layout is different due to LP ROM stack/data */ @@ -28,10 +31,19 @@ MEMORY { /*first 128byte for exception/interrupt vectors*/ vector_table(RX) : ORIGIN = ULP_MEM_START_ADDRESS , LENGTH = 0x80 - ram(RWX) : ORIGIN = ULP_MEM_START_ADDRESS + 0x80, LENGTH = ALIGNED_COPROC_MEM - 0x80 - CONFIG_ULP_SHARED_MEM + lp_ram(RWX) : ORIGIN = ULP_MEM_START_ADDRESS + 0x80, LENGTH = ALIGNED_COPROC_MEM - 0x80 - CONFIG_ULP_SHARED_MEM shared_mem_ram(RW) : ORIGIN = ULP_MEM_START_ADDRESS + ALIGNED_COPROC_MEM - CONFIG_ULP_SHARED_MEM, LENGTH = CONFIG_ULP_SHARED_MEM +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM + hp_ram(RWX) : ORIGIN = ULP_HP_MEM_START, LENGTH = ULP_HP_MEM_SIZE +#endif } +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +REGION_ALIAS("default_app_seg", hp_ram); +#else +REGION_ALIAS("default_app_seg", lp_ram); +#endif + SECTIONS { .vector.text : @@ -41,39 +53,55 @@ SECTIONS KEEP (*(.init.vector .init.vector.*)) } > vector_table - . = ORIGIN(ram); + . = ORIGIN(lp_ram); + + /* Interrupt/exception handlers and reset vector must remain in LP RAM so they + * are accessible immediately on wakeup, before HP RAM is powered on. */ + .rtc_text ALIGN(4): + { + *(.text.vectors) /* Default reset vector must link to offset 0x80 */ + *(.text.handlers) + *(.text.handlers.*) + } > lp_ram + +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM + . = ORIGIN(default_app_seg); +#endif .text ALIGN(4): { - *(.text.vectors) /* Default reset vector must link to offset 0x80 */ *(.text) *(.text*) - } >ram + } > default_app_seg .rodata ALIGN(4): { *(.rodata) *(.rodata*) - } > ram + } > default_app_seg .data ALIGN(4): { + _data_start = .; *(.data) *(.data*) *(.sdata) *(.sdata*) - } > ram + _data_end = .; + } > default_app_seg .bss ALIGN(4) : { + _bss_start = .; *(.bss) *(.bss*) *(.sbss) *(.sbss*) PROVIDE(end = .); - } >ram + _bss_end = .; + } > default_app_seg - __stack_top = ORIGIN(ram) + LENGTH(ram); + __stack_top = ORIGIN(lp_ram) + LENGTH(lp_ram); . = ORIGIN(shared_mem_ram); .shared_mem (ALIGN(4)) : diff --git a/components/ulp/lp_core/lp_core.c b/components/ulp/lp_core/lp_core.c index 52bd762e38e..f9a0dd71721 100644 --- a/components/ulp/lp_core/lp_core.c +++ b/components/ulp/lp_core/lp_core.c @@ -30,6 +30,15 @@ extern uint8_t _rtc_ulp_memory_start[]; #endif //ESP_ROM_HAS_LP_ROM +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +#include "esp_image_format.h" +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE +#include "esp_cache.h" +#endif +extern uint32_t _ulp_hp_mem_resv_start; +extern uint32_t _ulp_hp_mem_resv_end; +#endif //CONFIG_ULP_COPROC_RUN_FROM_HP_MEM + #if SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE #include "hal/lp_aon_hal.h" #include "rom/rtc.h" @@ -53,6 +62,33 @@ static uint32_t wakeup_src_sw_to_hw_flag_lookup[WAKEUP_SOURCE_MAX_NUMBER] = { #endif }; +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +static uintptr_t ulp_lp_core_lp_mem_start(void) +{ +#if ESP_ROM_HAS_LP_ROM + return (uintptr_t)&_rtc_ulp_memory_start; +#else + return (uintptr_t)RTC_SLOW_MEM; +#endif +} + +static uintptr_t ulp_lp_core_lp_mem_end(void) +{ + return ulp_lp_core_lp_mem_start() + CONFIG_ULP_COPROC_RESERVE_MEM; +} + +static bool ulp_lp_core_addr_range_in_region(uintptr_t start, size_t len, uintptr_t region_start, uintptr_t region_end) +{ + uintptr_t end; + + if (__builtin_add_overflow(start, (uintptr_t)len, &end)) { + return false; + } + + return start >= region_start && end <= region_end; +} +#endif //CONFIG_ULP_COPROC_RUN_FROM_HP_MEM + /* Convert the wake-up sources defined in ulp_lp_core.h to the actual HW wake-up source values */ static uint32_t lp_core_get_wakeup_source_hw_flags(uint32_t flags) { @@ -159,28 +195,117 @@ esp_err_t ulp_lp_core_run(ulp_lp_core_cfg_t* cfg) return ESP_OK; } +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM +static esp_err_t ulp_lp_core_load_segments(const uint8_t *program_binary, size_t program_size_bytes) +{ + if (program_size_bytes < sizeof(esp_image_header_t)) { + ESP_LOGE(TAG, "Binary too small for image header"); + return ESP_ERR_INVALID_SIZE; + } + + const esp_image_header_t *image_header = (const esp_image_header_t *)program_binary; + const uintptr_t lp_mem_start = ulp_lp_core_lp_mem_start(); + const uintptr_t lp_mem_end = ulp_lp_core_lp_mem_end(); + const uintptr_t hp_mem_start = (uintptr_t)&_ulp_hp_mem_resv_start; + const uintptr_t hp_mem_end = (uintptr_t)&_ulp_hp_mem_resv_end; + + if (image_header->magic != ESP_IMAGE_HEADER_MAGIC) { + ESP_LOGE(TAG, "Invalid image magic 0x%02x, expected 0x%02x", image_header->magic, ESP_IMAGE_HEADER_MAGIC); + return ESP_ERR_IMAGE_INVALID; + } + + if (image_header->segment_count > ESP_IMAGE_MAX_SEGMENTS) { + ESP_LOGE(TAG, "Segment count %d exceeds max %d", image_header->segment_count, ESP_IMAGE_MAX_SEGMENTS); + return ESP_ERR_IMAGE_INVALID; + } + + const uint8_t *seg_ptr = program_binary + sizeof(esp_image_header_t); + for (int i = 0; i < image_header->segment_count; i++) { + size_t seg_offset = (size_t)(seg_ptr - program_binary); + if (__builtin_add_overflow(seg_offset, sizeof(esp_image_segment_header_t), &seg_offset) + || seg_offset > program_size_bytes) { + ESP_LOGE(TAG, "Segment %d header extends beyond binary size", i); + return ESP_ERR_INVALID_SIZE; + } + + const esp_image_segment_header_t *seg_hdr = (const esp_image_segment_header_t *)seg_ptr; + seg_ptr += sizeof(esp_image_segment_header_t); + + size_t data_offset = (size_t)(seg_ptr - program_binary); + size_t data_end_offset; + if (__builtin_add_overflow(data_offset, (size_t)seg_hdr->data_len, &data_end_offset) + || data_end_offset > program_size_bytes) { + ESP_LOGE(TAG, "Segment %d extends beyond binary size", i); + return ESP_ERR_INVALID_SIZE; + } + + uintptr_t load_addr = (uintptr_t)seg_hdr->load_addr; + uintptr_t load_end; + if (__builtin_add_overflow(load_addr, (uintptr_t)seg_hdr->data_len, &load_end)) { + ESP_LOGE(TAG, "Segment %d load range overflows", i); + return ESP_ERR_INVALID_SIZE; + } + + bool load_in_lp_mem = ulp_lp_core_addr_range_in_region(load_addr, seg_hdr->data_len, lp_mem_start, lp_mem_end); + bool load_in_hp_mem = ulp_lp_core_addr_range_in_region(load_addr, seg_hdr->data_len, hp_mem_start, hp_mem_end); + + if (!load_in_lp_mem && !load_in_hp_mem) { + ESP_LOGE(TAG, "Segment %d load range [0x%08x, 0x%08x) is outside LP/HP ULP memory", i, + (unsigned)load_addr, (unsigned)load_end); + return ESP_ERR_INVALID_ARG; + } + + ESP_LOGD(TAG, "Loading segment %d: load_addr=0x%08x size=0x%x", + i, (unsigned)seg_hdr->load_addr, (unsigned)seg_hdr->data_len); + hal_memcpy((void *)(uintptr_t)seg_hdr->load_addr, seg_ptr, seg_hdr->data_len); + seg_ptr += seg_hdr->data_len; + } + +#if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE + /* Flush HP CPU write-back cache so the LP CPU sees the data written above. + * Both _ulp_hp_mem_resv_start and _ulp_hp_mem_resv_end are aligned to the + * L1 cache line size in the linker script, so the size is always a multiple + * of the cache line size as required by esp_cache_msync. */ + size_t hp_mem_size = (uintptr_t)&_ulp_hp_mem_resv_end - (uintptr_t)&_ulp_hp_mem_resv_start; + esp_cache_msync(&_ulp_hp_mem_resv_start, hp_mem_size, + ESP_CACHE_MSYNC_FLAG_DIR_C2M | ESP_CACHE_MSYNC_FLAG_INVALIDATE); +#endif + + return ESP_OK; +} +#endif /* CONFIG_ULP_COPROC_RUN_FROM_HP_MEM */ + esp_err_t ulp_lp_core_load_binary(const uint8_t* program_binary, size_t program_size_bytes) { if (program_binary == NULL) { return ESP_ERR_INVALID_ARG; } - if (program_size_bytes > CONFIG_ULP_COPROC_RESERVE_MEM) { - return ESP_ERR_INVALID_SIZE; - } /* Turn off LP CPU before loading binary */ ulp_lp_core_stop(); + #if ESP_ROM_HAS_LP_ROM uint32_t* base = (uint32_t*)_rtc_ulp_memory_start; #else uint32_t* base = RTC_SLOW_MEM; #endif - //Start by clearing memory reserved with zeros, this will also will initialize the bss: + /* Clear LP RAM — also initializes LP-side bss */ hal_memset(base, 0, CONFIG_ULP_COPROC_RESERVE_MEM); + +#if CONFIG_ULP_COPROC_RUN_FROM_HP_MEM + ESP_LOGW(TAG, "Running ULP app from HP memory. ULP will not be able to run if system enters deep sleep and power down HP memory."); + /* Clear HP RAM — initializes HP-side bss before the LP core runs */ + hal_memset(&_ulp_hp_mem_resv_start, 0, CONFIG_ULP_COPROC_RESERVE_HP_MEM_BYTES); + return ulp_lp_core_load_segments(program_binary, program_size_bytes); +#else + if (program_size_bytes > CONFIG_ULP_COPROC_RESERVE_MEM) { + return ESP_ERR_INVALID_SIZE; + } hal_memcpy(base, program_binary, program_size_bytes); return ESP_OK; +#endif } void ulp_lp_core_sleep_start(void) diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h index 8d20d1f43c0..f04ef04ef70 100644 --- a/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_interrupts.h @@ -16,7 +16,7 @@ extern "C" { #if SOC_LP_CORE_SINGLE_INTERRUPT_VECTOR #define LP_CORE_ISR_ATTR // On chips with just a single interrupt entry point registers are saved by us before calling the ISR #else -#define LP_CORE_ISR_ATTR __attribute__((interrupt)) +#define LP_CORE_ISR_ATTR __attribute__((interrupt, section(".text.handlers"))) #endif /** diff --git a/components/ulp/lp_core/lp_core/lp_core_interrupt.c b/components/ulp/lp_core/lp_core/lp_core_interrupt.c index 66bcb86b013..bc227166449 100644 --- a/components/ulp/lp_core/lp_core/lp_core_interrupt.c +++ b/components/ulp/lp_core/lp_core/lp_core_interrupt.c @@ -44,7 +44,7 @@ void __attribute__((weak)) ulp_lp_core_panic_handler(RvExcFrame *frame, int exc ulp_lp_core_abort(); } -static void ulp_lp_core_default_intr_handler(void) +static void __attribute__((section(".text.handlers"))) ulp_lp_core_default_intr_handler(void) { ulp_lp_core_abort(); } diff --git a/components/ulp/test_apps/.build-test-rules.yml b/components/ulp/test_apps/.build-test-rules.yml index 6f4d636641d..f83aeacf71d 100644 --- a/components/ulp/test_apps/.build-test-rules.yml +++ b/components/ulp/test_apps/.build-test-rules.yml @@ -19,6 +19,16 @@ components/ulp/test_apps/lp_core/lp_core_basic_tests: - esp_hw_support +components/ulp/test_apps/lp_core/lp_core_hp_mem: + disable: + - if: SOC_LP_CORE_SUPPORTED != 1 + depends_components: + - ulp + - esp_hal_uart + - esp_hal_pmu + - esp_hw_support + + components/ulp/test_apps/lp_core/lp_core_hp_uart: disable: - if: SOC_LP_CORE_SUPPORTED != 1 diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_mem/CMakeLists.txt b/components/ulp/test_apps/lp_core/lp_core_hp_mem/CMakeLists.txt new file mode 100644 index 00000000000..076b83244e1 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_mem/CMakeLists.txt @@ -0,0 +1,14 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.22) + +list(PREPEND SDKCONFIG_DEFAULTS "$ENV{IDF_PATH}/tools/test_apps/configs/sdkconfig.debug_helpers" "sdkconfig.defaults") + +set(EXTRA_COMPONENT_DIRS + "$ENV{IDF_PATH}/tools/test_apps/components" +) + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. +set(COMPONENTS main) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(lp_core_hp_mem_test) diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_mem/README.md b/components/ulp/test_apps/lp_core/lp_core_hp_mem/README.md new file mode 100644 index 00000000000..6b3e0794514 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_mem/README.md @@ -0,0 +1,3 @@ +| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-P4 | ESP32-S31 | +| ----------------- | -------- | -------- | -------- | --------- | + diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/CMakeLists.txt b/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/CMakeLists.txt new file mode 100644 index 00000000000..c585ccdf162 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/CMakeLists.txt @@ -0,0 +1,11 @@ +set(app_sources "test_app_main.c" "test_lp_core_hp_mem.c") +set(lp_core_sources "lp_core/test_main.c") + +idf_component_register(SRCS ${app_sources} + INCLUDE_DIRS "lp_core" + REQUIRES bootloader_support esp_hw_support ulp unity + WHOLE_ARCHIVE) + +set(lp_core_exp_dep_srcs ${app_sources}) + +ulp_embed_binary(lp_core_hp_mem_app "${lp_core_sources}" "${lp_core_exp_dep_srcs}") diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/lp_core/test_main.c b/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/lp_core/test_main.c new file mode 100644 index 00000000000..c64a92dec96 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/lp_core/test_main.c @@ -0,0 +1,29 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "ulp_lp_core_print.h" + +volatile uint32_t hp_mem_data_marker = 0x12345678; +volatile uint32_t hp_mem_bss_marker; +volatile uint32_t hp_mem_done_marker; +const uint32_t hp_mem_rodata_marker __attribute__((used)) = 0xA5A5F00D; + +/* Pad rodata to push the LP Core binary above the LP RAM reservation limit + * (CONFIG_ULP_COPROC_RESERVE_MEM), proving this binary can only run from HP RAM. */ +static const volatile uint8_t s_hp_mem_size_padding[50 * 1024] __attribute__((used)) = {[0] = 0xAA}; + +int main(void) +{ + lp_core_printf("LP core running from HP memory\n"); + hp_mem_data_marker = 0x89ABCDEF; + hp_mem_bss_marker = 0x13579BDF; + hp_mem_done_marker = 0x2468ACE0; + + lp_core_printf("s_hp_mem_size_padding[0] = 0x%02X\n", s_hp_mem_size_padding[0]); + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/test_app_main.c b/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/test_app_main.c new file mode 100644 index 00000000000..043cc46e4b9 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/test_app_main.c @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in the sleep code, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (-500) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/test_lp_core_hp_mem.c b/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/test_lp_core_hp_mem.c new file mode 100644 index 00000000000..d504330f130 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_mem/main/test_lp_core_hp_mem.c @@ -0,0 +1,170 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "sdkconfig.h" +#include "unity.h" +#include "esp_image_format.h" +#include "esp_memory_utils.h" +#include "soc/soc.h" +#include "lp_core_hp_mem_app.h" +#include "ulp_lp_core.h" + +#define HP_MEM_DATA_INIT UINT32_C(0x12345678) +#define HP_MEM_DATA_DONE UINT32_C(0x89ABCDEF) +#define HP_MEM_BSS_DONE UINT32_C(0x13579BDF) +#define HP_MEM_DONE_MARKER UINT32_C(0x2468ACE0) + +#if CONFIG_IDF_TARGET_ESP32P4 +extern uint32_t _ulp_hp_mem_resv_end; +#endif + +extern uint32_t _ulp_hp_mem_resv_start; + +extern const uint8_t lp_core_hp_mem_bin_start[] asm("_binary_lp_core_hp_mem_app_bin_start"); +extern const uint8_t lp_core_hp_mem_bin_end[] asm("_binary_lp_core_hp_mem_app_bin_end"); + +static uintptr_t hp_mem_raw_start(void) +{ + return (uintptr_t)&_ulp_hp_mem_resv_start; +} + +static uintptr_t hp_mem_raw_end(void) +{ +#if CONFIG_IDF_TARGET_ESP32P4 + return (uintptr_t)&_ulp_hp_mem_resv_end; +#else + return hp_mem_raw_start() + CONFIG_ULP_COPROC_RESERVE_HP_MEM_BYTES; +#endif +} + +static uintptr_t hp_mem_access_start(void) +{ + uintptr_t start = hp_mem_raw_start(); +#if CONFIG_IDF_TARGET_ESP32P4 + start += SOC_NON_CACHEABLE_OFFSET_SRAM; +#endif + return start; +} + +static uintptr_t hp_mem_access_end(void) +{ + uintptr_t end = hp_mem_raw_end(); +#if CONFIG_IDF_TARGET_ESP32P4 + end += SOC_NON_CACHEABLE_OFFSET_SRAM; +#endif + return end; +} + +static bool addr_in_range(uintptr_t addr, uintptr_t start, uintptr_t end) +{ + return addr >= start && addr < end; +} + +/* Verify that an LP Core variable pointer is in HP SRAM, not LP SRAM. + * On P4, mapgen applies the non-cacheable alias offset (+0x40000000) to HP SRAM + * symbols so the HP CPU can access them without L1 cache coherency issues. + * Strip that offset before calling esp_ptr_in_dram(), which checks the HP SRAM range. */ +static bool lp_var_in_hp_sram(const void *ptr) +{ +#if CONFIG_IDF_TARGET_ESP32P4 + ptr = (const void *)((uintptr_t)ptr - SOC_NON_CACHEABLE_OFFSET_SRAM); +#endif + return esp_ptr_in_dram(ptr); +} + +static uint32_t read_ulp_word(const uint32_t *symbol) +{ + return *(volatile const uint32_t *)symbol; +} + +static void assert_segment_layout(const uint8_t *program_binary, size_t program_size_bytes) +{ + const esp_image_header_t *image_header = (const esp_image_header_t *)program_binary; + const uintptr_t hp_start = hp_mem_raw_start(); + const uintptr_t hp_end = hp_mem_raw_end(); + const uintptr_t lp_start = SOC_RTC_DRAM_LOW; + const uintptr_t lp_end = lp_start + CONFIG_ULP_COPROC_RESERVE_MEM; + const uint8_t *seg_ptr = program_binary + sizeof(esp_image_header_t); + bool saw_lp_segment = false; + bool saw_hp_segment = false; + + TEST_ASSERT_EQUAL_HEX8(ESP_IMAGE_HEADER_MAGIC, image_header->magic); + + for (int i = 0; i < image_header->segment_count; i++) { + const esp_image_segment_header_t *seg_hdr = (const esp_image_segment_header_t *)seg_ptr; + seg_ptr += sizeof(esp_image_segment_header_t); + + TEST_ASSERT_TRUE(((seg_ptr - program_binary) + seg_hdr->data_len) <= program_size_bytes); + + printf("LP segment %d load_addr=0x%08" PRIx32 " size=0x%08" PRIx32 "\n", + i, seg_hdr->load_addr, seg_hdr->data_len); + + if (addr_in_range(seg_hdr->load_addr, hp_start, hp_end)) { + saw_hp_segment = true; + } else if (addr_in_range(seg_hdr->load_addr, lp_start, lp_end)) { + saw_lp_segment = true; + } + + seg_ptr += seg_hdr->data_len; + } + + TEST_ASSERT_TRUE(saw_hp_segment); + TEST_ASSERT_TRUE(saw_lp_segment); +} + +TEST_CASE("LP-Core can run from HP memory", "[lp_core][hp_mem]") +{ + const uint8_t *program_binary = lp_core_hp_mem_bin_start; + const size_t program_size_bytes = lp_core_hp_mem_bin_end - lp_core_hp_mem_bin_start; + const uintptr_t hp_access_start = hp_mem_access_start(); + const uintptr_t hp_access_end = hp_mem_access_end(); + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + /* The LP binary must exceed the LP RAM reservation — it can only run from HP RAM */ + TEST_ASSERT_GREATER_THAN(CONFIG_ULP_COPROC_RESERVE_MEM, program_size_bytes); + printf("LP Core binary size: 0x%08" PRIx32 " bytes\n", (uint32_t)program_size_bytes); + + assert_segment_layout(program_binary, program_size_bytes); + + printf("HP raw memory range: 0x%08" PRIxPTR " - 0x%08" PRIxPTR "\n", hp_mem_raw_start(), hp_mem_raw_end()); + printf("HP access memory range: 0x%08" PRIxPTR " - 0x%08" PRIxPTR "\n", hp_access_start, hp_access_end); + printf("ULP reset vector: %p\n", &ulp_reset_vector); + printf("ULP data marker: %p\n", &ulp_hp_mem_data_marker); + printf("ULP bss marker: %p\n", &ulp_hp_mem_bss_marker); + printf("ULP done marker: %p\n", &ulp_hp_mem_done_marker); + + /* Address range checks using the HP access alias */ + TEST_ASSERT_FALSE(addr_in_range((uintptr_t)&ulp_reset_vector, hp_access_start, hp_access_end)); + TEST_ASSERT_TRUE(addr_in_range((uintptr_t)&ulp_hp_mem_data_marker, hp_access_start, hp_access_end)); + TEST_ASSERT_TRUE(addr_in_range((uintptr_t)&ulp_hp_mem_bss_marker, hp_access_start, hp_access_end)); + TEST_ASSERT_TRUE(addr_in_range((uintptr_t)&ulp_hp_mem_done_marker, hp_access_start, hp_access_end)); + + /* Verify HP mem variables are in HP SRAM (DRAM), not LP SRAM */ + TEST_ASSERT_TRUE(lp_var_in_hp_sram(&ulp_hp_mem_data_marker)); + TEST_ASSERT_TRUE(lp_var_in_hp_sram(&ulp_hp_mem_bss_marker)); + TEST_ASSERT_TRUE(lp_var_in_hp_sram(&ulp_hp_mem_done_marker)); + TEST_ASSERT_FALSE(lp_var_in_hp_sram(&ulp_reset_vector)); + + TEST_ASSERT_EQUAL(ESP_OK, ulp_lp_core_load_binary(program_binary, program_size_bytes)); + + TEST_ASSERT_EQUAL_HEX32(HP_MEM_DATA_INIT, read_ulp_word(&ulp_hp_mem_data_marker)); + TEST_ASSERT_EQUAL_HEX32(0, read_ulp_word(&ulp_hp_mem_bss_marker)); + TEST_ASSERT_EQUAL_HEX32(0, read_ulp_word(&ulp_hp_mem_done_marker)); + + TEST_ASSERT_EQUAL(ESP_OK, ulp_lp_core_run(&cfg)); + vTaskDelay(pdMS_TO_TICKS(1000)); + + TEST_ASSERT_EQUAL_HEX32(HP_MEM_DATA_DONE, read_ulp_word(&ulp_hp_mem_data_marker)); + TEST_ASSERT_EQUAL_HEX32(HP_MEM_BSS_DONE, read_ulp_word(&ulp_hp_mem_bss_marker)); + TEST_ASSERT_EQUAL_HEX32(HP_MEM_DONE_MARKER, read_ulp_word(&ulp_hp_mem_done_marker)); +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_mem/pytest_lp_core_hp_mem.py b/components/ulp/test_apps/lp_core/lp_core_hp_mem/pytest_lp_core_hp_mem.py new file mode 100644 index 00000000000..aed48687039 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_mem/pytest_lp_core_hp_mem.py @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@idf_parametrize('target', ['esp32c5', 'esp32c6', 'esp32p4', 'esp32s31'], indirect=['target']) +def test_lp_core_hp_mem(dut: Dut) -> None: + dut.run_all_single_board_cases() diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_mem/sdkconfig.defaults b/components/ulp/test_apps/lp_core/lp_core_hp_mem/sdkconfig.defaults new file mode 100644 index 00000000000..f43a762b79e --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_mem/sdkconfig.defaults @@ -0,0 +1,8 @@ +CONFIG_ESP_TASK_WDT_INIT=n + +CONFIG_ULP_COPROC_ENABLED=y +CONFIG_ULP_COPROC_TYPE_LP_CORE=y +CONFIG_ULP_COPROC_RESERVE_MEM=12000 +CONFIG_ULP_COPROC_RUN_FROM_HP_MEM=y +CONFIG_ULP_COPROC_RESERVE_HP_MEM_BYTES=0x10000 +CONFIG_ULP_HP_UART_CONSOLE_PRINT=y diff --git a/docs/en/api-reference/system/ulp-lp-core.rst b/docs/en/api-reference/system/ulp-lp-core.rst index 6a8c8c02439..c97f8f7a70d 100644 --- a/docs/en/api-reference/system/ulp-lp-core.rst +++ b/docs/en/api-reference/system/ulp-lp-core.rst @@ -202,6 +202,15 @@ Once the program is loaded into LP memory, the application can be configured and ESP_ERROR_CHECK( ulp_lp_core_run(&cfg) ); +Running the LP Core from HP Memory +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:ref:`CONFIG_ULP_COPROC_RUN_FROM_HP_MEM` allows placing most of the LP core application in reserved HP SRAM instead of LP RAM. This can be useful when the application is too large to fit in LP RAM, while still keeping the LP reset and handler code in LP memory. + +When this option is enabled, :ref:`CONFIG_ULP_COPROC_RESERVE_HP_MEM_BYTES` reserves a window at the top of HP SRAM for the LP core binary. During :cpp:func:`ulp_lp_core_load_binary`, LP-memory segments are still loaded into the reserved LP region, while data and code segments mapped to the HP-memory window are copied into the reserved HP SRAM region. + +This mode has an important limitation: the LP core cannot keep running while the chip is in Deep-sleep, because HP SRAM is powered down in that sleep mode. Use this mode for cases where the LP core only needs to run while the HP system remains powered, and keep the default LP-memory-only mode for Deep-sleep use cases. + ULP LP Core Program Flow ------------------------ diff --git a/docs/zh_CN/api-reference/system/ulp-lp-core.rst b/docs/zh_CN/api-reference/system/ulp-lp-core.rst index db47bb63338..9a5ee64f2fe 100644 --- a/docs/zh_CN/api-reference/system/ulp-lp-core.rst +++ b/docs/zh_CN/api-reference/system/ulp-lp-core.rst @@ -202,6 +202,15 @@ ULP LP 内核代码会与 ESP-IDF 项目共同编译,生成一个单独的二 ESP_ERROR_CHECK( ulp_lp_core_run(&cfg) ); +从 HP 内存运行 LP 内核 +~~~~~~~~~~~~~~~~~~~~~~ + +:ref:`CONFIG_ULP_COPROC_RUN_FROM_HP_MEM` 允许将 LP 内核应用的大部分代码和数据放到预留的 HP SRAM 中,而不是仅放在 LP RAM 中。当应用程序过大、无法完全放入 LP RAM 时,这种方式会很有用,同时仍然会将 LP 复位和处理程序代码保留在 LP 内存中。 + +启用该选项后,:ref:`CONFIG_ULP_COPROC_RESERVE_HP_MEM_BYTES` 会在 HP SRAM 顶部预留一段专供 LP 内核使用的内存窗口。在调用 :cpp:func:`ulp_lp_core_load_binary` 时,位于 LP 内存的段仍会加载到预留的 LP 区域,而映射到 HP 内存窗口的代码段和数据段则会复制到预留的 HP SRAM 区域。 + +该模式有一个重要限制:芯片进入 Deep-sleep 后,LP 内核无法继续运行,因为该睡眠模式下 HP SRAM 会被断电。因此,这种模式适用于 LP 内核只需要在 HP 系统保持上电时运行的场景;如果应用需要在 Deep-sleep 期间继续运行,则应继续使用默认的纯 LP 内存模式。 + ULP LP 内核程序流程 -------------------