diff --git a/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in index 9cd2467670b..a2459909d5d 100644 --- a/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s31/include/soc/Kconfig.soc_caps.in @@ -1211,6 +1211,10 @@ config SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE bool default y +config SOC_LP_CORE_HAS_PMP + bool + default y + config SOC_LP_TIMER_BIT_WIDTH_LO int default 32 diff --git a/components/soc/esp32s31/include/soc/soc.h b/components/soc/esp32s31/include/soc/soc.h index 8c8da22ad16..8cc595e3bf0 100644 --- a/components/soc/esp32s31/include/soc/soc.h +++ b/components/soc/esp32s31/include/soc/soc.h @@ -198,8 +198,9 @@ #define SOC_PERIPHERAL_LOW 0x50000000 //TODO need update #define SOC_PERIPHERAL_HIGH 0x50100000 //TODO need update -#define SOC_LP_PERIPH_LOW 0x50110000 //TODO need update -#define SOC_LP_PERIPH_HIGH 0x50130000 //TODO need update +/** LP subsystem from ``LP_SYS`` through ``LP_DAC``*/ +#define SOC_LP_PERIPH_LOW DR_REG_LP_SYS_BASE +#define SOC_LP_PERIPH_HIGH (DR_REG_LP_DAC_BASE + 0x2000) // CPU sub-system region, contains interrupt config registers #define SOC_CPU_SUBSYSTEM_LOW 0x10000000 diff --git a/components/soc/esp32s31/include/soc/soc_caps.h b/components/soc/esp32s31/include/soc/soc_caps.h index ec85d5c17e8..2609f0ba986 100644 --- a/components/soc/esp32s31/include/soc/soc_caps.h +++ b/components/soc/esp32s31/include/soc/soc_caps.h @@ -479,6 +479,7 @@ #define SOC_LP_CORE_CONFIGURABLE_BOOT_ADDR (1) /*!< LP Core has no LP ROM; HP must write the reset_vector address (LP_RAM_BASE+0x80) to LP_SYS.lp_core_boot_addr before triggering LP wake */ //#define SOC_LP_CORE_SUPPORT_I2C (1) /*!< LP Core supports I2C */ TODO IDF-14635 #define SOC_LP_CORE_HW_AUTO_CLRWAKEUPCAUSE (1) /*!< LP core requests sleep, PMU clears both HP and LP wakeup causes */ +#define SOC_LP_CORE_HAS_PMP (1) /*!< LP Core RISC-V has 16 PMP entries (128-byte granularity, RISC-V v1.10) */ /*-------------------------- LP_TIMER CAPS ----------------------------------*/ #define SOC_LP_TIMER_BIT_WIDTH_LO 32 // Bit width of lp_timer low part diff --git a/components/ulp/Kconfig b/components/ulp/Kconfig index f019da87680..029026fc1a3 100644 --- a/components/ulp/Kconfig +++ b/components/ulp/Kconfig @@ -129,6 +129,17 @@ menu "Ultra Low Power (ULP) Co-processor" Note: For LP ROM prints to work properly, make sure that the LP core boots from the LP ROM. + config ULP_LP_CORE_MEMPROT + bool "Enable LP Core memory protection (PMP)" + depends on ULP_COPROC_TYPE_LP_CORE && SOC_LP_CORE_HAS_PMP && !ULP_COPROC_RUN_FROM_HP_MEM + default n + help + Configures the LP CPU's PMP entries to enforce memory protection. + LP RAM is split into executable (.text) and data (.data/.bss/stack) + regions. Regions not explicitly permitted are inaccessible. + Not available when "Run LP Core from HP memory" is enabled. + Disable this if you need full control over PMP configuration. + config ULP_TRAP_WAKEUP depends on ULP_COPROC_TYPE_LP_CORE bool diff --git a/components/ulp/cmake/IDFULPProject.cmake b/components/ulp/cmake/IDFULPProject.cmake index 2e1bff36d4f..9c5353cff1c 100644 --- a/components/ulp/cmake/IDFULPProject.cmake +++ b/components/ulp/cmake/IDFULPProject.cmake @@ -171,6 +171,7 @@ function(ulp_apply_default_sources ulp_app_name) "${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_memory_shared.c" "${IDF_PATH}/components/ulp/lp_core/shared/ulp_lp_core_lp_timer_shared.c" "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_startup.c" + "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_pmp.c" "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_utils.c" "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_print.c" "${IDF_PATH}/components/ulp/lp_core/lp_core/lp_core_panic.c" diff --git a/components/ulp/ld/lp_core_riscv.ld b/components/ulp/ld/lp_core_riscv.ld index 0695b507a4e..315faefb5bc 100644 --- a/components/ulp/ld/lp_core_riscv.ld +++ b/components/ulp/ld/lp_core_riscv.ld @@ -18,7 +18,6 @@ #define ULP_MEM_START_ADDRESS (SOC_RTC_DRAM_LOW) #endif - #define ALIGN_DOWN(SIZE, AL) (SIZE & ~(AL - 1)) /* Ensure the end where the shared memory starts is aligned to 8 bytes if updating this also update the same in ulp_lp_core_memory_shared.c @@ -29,8 +28,8 @@ ENTRY(reset_vector) MEMORY { - /*first 128byte for exception/interrupt vectors*/ - vector_table(RX) : ORIGIN = ULP_MEM_START_ADDRESS , LENGTH = 0x80 + /* First 128 bytes for exception/interrupt vectors */ + vector_table(RX) : ORIGIN = ULP_MEM_START_ADDRESS, LENGTH = 0x80 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 @@ -48,24 +47,27 @@ SECTIONS { .vector.text : { - /*exception/interrupt vectors*/ + /* Exception/interrupt vectors */ __mtvec_base = .; KEEP (*(.init.vector .init.vector.*)) } > vector_table . = 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. */ + /* Interrupt/exception handlers stay in LP RAM (reachable on wakeup before HP SRAM is up). */ .rtc_text ALIGN(4): { + _lp_text_start = .; *(.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); + /* End of LP-resident code (handlers only); align to 128 bytes. */ + . = ALIGN(128); + _lp_text_end = .; + . = ORIGIN(hp_ram); #endif .text ALIGN(4): @@ -80,6 +82,13 @@ SECTIONS *(.rodata*) } > default_app_seg + /* 128-byte alignment required for PMP TOR granularity (SOC_CPU_PMP_REGION_GRANULARITY) */ + . = ALIGN(128); +#if !CONFIG_ULP_COPROC_RUN_FROM_HP_MEM + _lp_text_end = .; +#endif + _lp_data_start = .; + .data ALIGN(4): { _data_start = .; diff --git a/components/ulp/lp_core/lp_core/include/ulp_lp_core_pmp.h b/components/ulp/lp_core/lp_core/include/ulp_lp_core_pmp.h new file mode 100644 index 00000000000..08ea9e2d6e3 --- /dev/null +++ b/components/ulp/lp_core/lp_core/include/ulp_lp_core_pmp.h @@ -0,0 +1,12 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include "sdkconfig.h" + +#if CONFIG_ULP_LP_CORE_MEMPROT +void lp_core_configure_pmp(void); +#endif diff --git a/components/ulp/lp_core/lp_core/lp_core_panic.c b/components/ulp/lp_core/lp_core/lp_core_panic.c index f85f6f30889..6b42962c4a2 100644 --- a/components/ulp/lp_core/lp_core/lp_core_panic.c +++ b/components/ulp/lp_core/lp_core/lp_core_panic.c @@ -41,7 +41,7 @@ static const char *desc[] = { static const char *reason[] = { NULL, - NULL, + "Instruction access fault", "Illegal instruction", "Breakpoint", "Load address misaligned", diff --git a/components/ulp/lp_core/lp_core/lp_core_pmp.c b/components/ulp/lp_core/lp_core/lp_core_pmp.c new file mode 100644 index 00000000000..0ef1711e1a2 --- /dev/null +++ b/components/ulp/lp_core/lp_core/lp_core_pmp.c @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "sdkconfig.h" +#include "soc/soc.h" +#include "riscv/encoding.h" +#include "riscv/csr.h" + +#if CONFIG_ULP_LP_CORE_MEMPROT + +/* + * Default PMP layout for the LP-CPU (RISC-V, 16 entries, 128-byte TOR granularity). + * + * The LP-CPU always runs in M-mode. PMP entries with the L bit set apply to M-mode. + * A catch-all entry with L+NONE at the end denies all unmatched accesses. + * + * Normal mode (code and data in LP RAM): + * Entry 0-1: [SOC_RTC_DRAM_LOW, _lp_text_end) → RX (vector table, handlers, .text, .rodata) + * Entry 2: [_lp_text_end, SOC_RTC_DRAM_HIGH) → RW (.data, .bss, stack, shared_mem) + * Entry 3-4: LP Peripherals → RW + * Entry 5-6: HP UART (if CONFIG_ULP_HP_UART_CONSOLE_PRINT) → RW + * + * Entry 15 (catch-all) → NONE (deny-by-default for all unmatched regions) + */ + +/* Linker-exported PMP boundary symbols (see lp_core_riscv.ld) */ +extern int _lp_text_end; + +#if CONFIG_ULP_HP_UART_CONSOLE_PRINT +/* Cover UART0 and UART1 (each 0x1000) so CONFIG_ESP_CONSOLE_UART_NUM 0 or 1 works. */ +#define HP_UART_PMP_END (DR_REG_UART1_BASE + 0x1000UL) +#endif + +void lp_core_configure_pmp(void) +{ + /* + * PMP_ENTRY_SET(ENTRY, ADDR, CFG) expands to inline assembly where ENTRY + * must be a compile-time integer literal — a variable cannot be used. + * Entry numbers are therefore hardcoded explicitly below. + */ + const unsigned NONE = PMP_L; + const unsigned RX = PMP_L | PMP_R | PMP_X; + const unsigned RW = PMP_L | PMP_R | PMP_W; + + /* --- LP RAM text region: vector table + handlers (+ .text/.rodata in normal mode) --- + * Entry 0: lower TOR bound (LP RAM base), no permissions + * Entry 1: upper bound = _lp_text_end, permissions = RX */ + PMP_ENTRY_SET(0, SOC_RTC_DRAM_LOW, NONE); + PMP_ENTRY_SET(1, (uintptr_t)&_lp_text_end, PMP_TOR | RX); + + /* --- LP RAM data region: .data/.bss/stack/shared_mem --- + * Entry 2: upper bound = LP RAM high, permissions = RW */ + PMP_ENTRY_SET(2, SOC_RTC_DRAM_HIGH, PMP_TOR | RW); + + /* --- LP Peripherals --- + * Entry 3: lower TOR bound + * Entry 4: upper bound, permissions = RW */ + PMP_ENTRY_SET(3, SOC_LP_PERIPH_LOW, NONE); + PMP_ENTRY_SET(4, SOC_LP_PERIPH_HIGH, PMP_TOR | RW); + +#if CONFIG_ULP_HP_UART_CONSOLE_PRINT + /* --- HP UART (entries 5-6) --- */ + PMP_ENTRY_SET(5, DR_REG_UART0_BASE, NONE); + PMP_ENTRY_SET(6, HP_UART_PMP_END, PMP_TOR | RW); +#endif + + /* --- Catch-all: deny all unmatched addresses --- + * Entry 15 covers the entire 32-bit address space with no permissions. + * Any access that didn't match the entries above is denied. */ + PMP_ENTRY_SET(15, PMPADDR_ALL, PMP_NAPOT | NONE); +} + +#endif /* CONFIG_ULP_LP_CORE_MEMPROT */ diff --git a/components/ulp/lp_core/lp_core/lp_core_startup.c b/components/ulp/lp_core/lp_core/lp_core_startup.c index ed59afd436d..10e0497bf67 100644 --- a/components/ulp/lp_core/lp_core/lp_core_startup.c +++ b/components/ulp/lp_core/lp_core/lp_core_startup.c @@ -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 */ @@ -11,6 +11,9 @@ #include "ulp_lp_core_lp_timer_shared.h" #include "ulp_lp_core_memory_shared.h" #include "ulp_lp_core_print.h" +#if CONFIG_ULP_LP_CORE_MEMPROT +#include "ulp_lp_core_pmp.h" +#endif extern void main(); @@ -24,6 +27,10 @@ void lp_core_startup() ulp_lp_core_update_wakeup_cause(); +#if CONFIG_ULP_LP_CORE_MEMPROT + lp_core_configure_pmp(); +#endif + main(); ulp_lp_core_memory_shared_cfg_t* shared_mem = ulp_lp_core_memory_shared_cfg_get(); diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/CMakeLists.txt b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/CMakeLists.txt index a1ca21afaf9..68d052b40b0 100644 --- a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/CMakeLists.txt +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/CMakeLists.txt @@ -2,7 +2,7 @@ set(app_sources "test_app_main.c" "test_lp_core.c") set(lp_core_sources "lp_core/test_hello_main.c") set(lp_core_sources_panic "lp_core/test_panic_main.c") set(lp_core_sources_shared_mem "lp_core/test_shared_mem_main.c") -set(lp_core_sources_lp_rom "lp_core/test_lp_rom_main.c") +set(lp_core_sources_lp_rom "lp_core/test_lp_rom_main.c") idf_component_register(SRCS ${app_sources} INCLUDE_DIRS "lp_core" @@ -18,3 +18,10 @@ ulp_embed_binary(lp_core_test_app_shared_mem "${lp_core_sources_shared_mem}" "${ if(CONFIG_ESP_ROM_HAS_LP_ROM) ulp_embed_binary(lp_core_test_app_lp_rom "${lp_core_sources_lp_rom}" "${lp_core_exp_dep_srcs}") endif() + +if(CONFIG_SOC_LP_CORE_HAS_PMP) + ulp_embed_binary(lp_core_test_pmp_positive "lp_core/test_main_pmp_positive.c" "${lp_core_exp_dep_srcs}") + ulp_embed_binary(lp_core_test_pmp_write_text "lp_core/test_main_pmp_write_to_text.c" "${lp_core_exp_dep_srcs}") + ulp_embed_binary(lp_core_test_pmp_exec_data "lp_core/test_main_pmp_exec_from_data.c" "${lp_core_exp_dep_srcs}") + ulp_embed_binary(lp_core_test_pmp_unmapped "lp_core/test_main_pmp_access_unmapped.c" "${lp_core_exp_dep_srcs}") +endif() diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_access_unmapped.c b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_access_unmapped.c new file mode 100644 index 00000000000..d74dd702553 --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_access_unmapped.c @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * LP-side firmware: PMP denied-region access violation test. + * Reads from an HP peripheral address not covered by any permitted PMP entry. + * With PMP deny-by-default (catch-all NONE entry), this triggers a + * load access fault (mcause=5). + */ + +#include +#include "ulp_lp_core_print.h" +#include "soc/reg_base.h" + +int main(void) +{ + lp_core_print_str("PMP access-unmapped test: start\n"); + + /* Access HP MMIO outside allow-listed regions — should trigger load access fault */ + volatile uint32_t *unmapped = (volatile uint32_t *)DR_REG_GPIO_BASE; + __attribute__((unused)) uint32_t val = *unmapped; + + /* Should never reach here */ + while (1) {} +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_exec_from_data.c b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_exec_from_data.c new file mode 100644 index 00000000000..6bfdab6e4ce --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_exec_from_data.c @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * LP-side firmware: PMP execute-from-data violation test. + * Copies a RISC-V ret instruction (0x00008067) into a .data buffer, + * then jumps to it. With PMP enabled, this triggers an instruction access + * fault (mcause=1) since .data is RW-only. + */ + +#include +#include +#include "ulp_lp_core_print.h" + +typedef void (*func_t)(void); + +/* Buffer in .data to hold the injected instruction */ +static uint32_t exec_buf[4]; + +int main(void) +{ + lp_core_print_str("PMP exec-from-data test: start\n"); + + /* RISC-V compressed ret: c.jr ra = 0x8082 (16-bit), or full ret = 0x00008067 */ + exec_buf[0] = 0x00008067U; /* jalr x0, 0(ra) — standard ret */ + + /* Jump into the data buffer — PMP violation: execute from RW region */ + func_t f = (func_t)(uintptr_t)exec_buf; + f(); + + /* Should never reach here */ + while (1) {} +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_positive.c b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_positive.c new file mode 100644 index 00000000000..c7bab6291ba --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_positive.c @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * LP-side firmware: positive PMP test. + * Verifies that normal operations (data R/W, function call, peripheral register read) + * succeed when PMP is enabled. Sets shared variable to signal success to HP-side. + */ + +#include +#include "ulp_lp_core_utils.h" +#include "ulp_lp_core_print.h" +#include "soc/lp_system_reg.h" + +volatile uint32_t pmp_positive_result = 0; + +/* Small data buffer in .data to test read/write */ +static volatile uint32_t data_buf[4] = {0xDEAD, 0xBEEF, 0xCAFE, 0xBABE}; + +static uint32_t sum_buffer(void) +{ + uint32_t s = 0; + for (int i = 0; i < 4; i++) { + s += data_buf[i]; + } + return s; +} + +int main(void) +{ + lp_core_print_str("PMP positive test: start\n"); + + /* Read/write data in .data section */ + data_buf[0] = 0x1; + data_buf[1] = 0x2; + data_buf[2] = 0x3; + data_buf[3] = 0x4; + + uint32_t s = sum_buffer(); + if (s != 10) { + return 1; + } + + /* Read an LP-system peripheral register listed in LP memory map headers */ + volatile uint32_t *lp_scratch = + (volatile uint32_t *)LP_SYSTEM_REG_LP_STORE0_REG; + __attribute__((unused)) uint32_t scratch = *lp_scratch; + + /* Signal success to HP core via shared variable */ + pmp_positive_result = 0xA5A5A5A5; + + return 0; +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_write_to_text.c b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_write_to_text.c new file mode 100644 index 00000000000..babaf99993e --- /dev/null +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/lp_core/test_main_pmp_write_to_text.c @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* + * LP-side firmware: PMP store-to-text violation test. + * Attempts to write to an address in the .text (RX-only) region. + * With PMP enabled, this triggers a store access fault (mcause=7). + */ + +#include +#include "ulp_lp_core_print.h" + +/* Must survive optimization: noinline keeps it as a real symbol, used prevents DCE */ +static void __attribute__((noinline, used)) dummy_func(void) +{ + asm volatile("" ::: "memory"); +} + +int main(void) +{ + lp_core_print_str("PMP write-to-text test: start\n"); + + /* Obtain a pointer into .text and write through it — PMP violation: store to RX region */ + void (*fn)(void) = dummy_func; + volatile uint32_t *text_addr = (volatile uint32_t *)(uintptr_t)fn; + *text_addr = 0xDEADBEEF; + + /* Should never reach here — PMP fault expected above */ + while (1) {} +} diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/test_lp_core.c b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/test_lp_core.c index 3567f4a65f7..88984b34ed8 100644 --- a/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/test_lp_core.c +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/main/test_lp_core.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -23,6 +23,10 @@ #include "lp_core_test_app_lp_rom.h" #endif +#if SOC_LP_CORE_HAS_PMP +#include "lp_core_test_pmp_positive.h" +#endif + extern const uint8_t lp_core_main_bin_start[] asm("_binary_lp_core_test_app_bin_start"); extern const uint8_t lp_core_main_bin_end[] asm("_binary_lp_core_test_app_bin_end"); @@ -37,6 +41,17 @@ extern const uint8_t lp_core_lp_rom_bin_start[] asm("_binary_lp_core_test_app_lp extern const uint8_t lp_core_lp_rom_bin_end[] asm("_binary_lp_core_test_app_lp_rom_bin_end"); #endif +#if SOC_LP_CORE_HAS_PMP +extern const uint8_t lp_core_pmp_positive_bin_start[] asm("_binary_lp_core_test_pmp_positive_bin_start"); +extern const uint8_t lp_core_pmp_positive_bin_end[] asm("_binary_lp_core_test_pmp_positive_bin_end"); +extern const uint8_t lp_core_pmp_write_text_bin_start[] asm("_binary_lp_core_test_pmp_write_text_bin_start"); +extern const uint8_t lp_core_pmp_write_text_bin_end[] asm("_binary_lp_core_test_pmp_write_text_bin_end"); +extern const uint8_t lp_core_pmp_exec_data_bin_start[] asm("_binary_lp_core_test_pmp_exec_data_bin_start"); +extern const uint8_t lp_core_pmp_exec_data_bin_end[] asm("_binary_lp_core_test_pmp_exec_data_bin_end"); +extern const uint8_t lp_core_pmp_unmapped_bin_start[] asm("_binary_lp_core_test_pmp_unmapped_bin_start"); +extern const uint8_t lp_core_pmp_unmapped_bin_end[] asm("_binary_lp_core_test_pmp_unmapped_bin_end"); +#endif + static void load_and_start_lp_core_firmware(ulp_lp_core_cfg_t* cfg, const uint8_t* firmware_start, const uint8_t* firmware_end) { TEST_ASSERT(ulp_lp_core_load_binary(firmware_start, @@ -123,3 +138,48 @@ TEST_CASE("LP-Core LP-ROM", "[lp_core]") printf("LP ROM test passed\n"); } #endif + +#if SOC_LP_CORE_HAS_PMP +TEST_CASE("LP core PMP: normal operation succeeds", "[lp_core][pmp]") +{ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + + ulp_pmp_positive_result = 0; + load_and_start_lp_core_firmware(&cfg, lp_core_pmp_positive_bin_start, lp_core_pmp_positive_bin_end); + + int timeout_ms = 2000; + while (ulp_pmp_positive_result == 0 && timeout_ms-- > 0) { + vTaskDelay(pdMS_TO_TICKS(1)); + } + TEST_ASSERT_EQUAL_HEX32(0xA5A5A5A5, ulp_pmp_positive_result); +} + +TEST_CASE("LP core PMP: write to .text triggers Store access fault", "[lp_core][pmp]") +{ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&cfg, lp_core_pmp_write_text_bin_start, lp_core_pmp_write_text_bin_end); + vTaskDelay(pdMS_TO_TICKS(1000)); +} + +TEST_CASE("LP core PMP: execute from .data triggers Instruction access fault", "[lp_core][pmp]") +{ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&cfg, lp_core_pmp_exec_data_bin_start, lp_core_pmp_exec_data_bin_end); + vTaskDelay(pdMS_TO_TICKS(1000)); +} + +TEST_CASE("LP core PMP: access unmapped region triggers Load access fault", "[lp_core][pmp]") +{ + ulp_lp_core_cfg_t cfg = { + .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_HP_CPU, + }; + load_and_start_lp_core_firmware(&cfg, lp_core_pmp_unmapped_bin_start, lp_core_pmp_unmapped_bin_end); + vTaskDelay(pdMS_TO_TICKS(1000)); +} +#endif /* SOC_LP_CORE_HAS_PMP */ diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/pytest_lp_core_hp_uart.py b/components/ulp/test_apps/lp_core/lp_core_hp_uart/pytest_lp_core_hp_uart.py index 92d70c7aff0..6d71b928d6c 100644 --- a/components/ulp/test_apps/lp_core/lp_core_hp_uart/pytest_lp_core_hp_uart.py +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/pytest_lp_core_hp_uart.py @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD # SPDX-License-Identifier: CC0-1.0 import pytest from pytest_embedded import Dut @@ -52,3 +52,38 @@ def test_lp_core_lp_rom(dut: Dut) -> None: dut.write('"LP-Core LP-ROM"') dut.expect_exact('ULP: all tests passed') dut.expect_exact('LP ROM test passed') + + +_PMP_TARGETS = soc_filtered_targets('SOC_LP_CORE_HAS_PMP == 1') + + +@pytest.mark.generic +@idf_parametrize('target', _PMP_TARGETS, indirect=['target']) +def test_lp_core_pmp_positive(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('"LP core PMP: normal operation succeeds"') + dut.expect_exact('LP core PMP: normal operation succeeds:PASS') + + +@pytest.mark.generic +@idf_parametrize('target', _PMP_TARGETS, indirect=['target']) +def test_lp_core_pmp_write_to_text(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('"LP core PMP: write to .text triggers Store access fault"') + dut.expect_exact("Guru Meditation Error: LP Core panic'ed Store access fault") + + +@pytest.mark.generic +@idf_parametrize('target', _PMP_TARGETS, indirect=['target']) +def test_lp_core_pmp_exec_from_data(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('"LP core PMP: execute from .data triggers Instruction access fault"') + dut.expect_exact("Guru Meditation Error: LP Core panic'ed Instruction access fault") + + +@pytest.mark.generic +@idf_parametrize('target', _PMP_TARGETS, indirect=['target']) +def test_lp_core_pmp_access_unmapped(dut: Dut) -> None: + dut.expect_exact('Press ENTER to see the list of tests') + dut.write('"LP core PMP: access unmapped region triggers Load access fault"') + dut.expect_exact("Guru Meditation Error: LP Core panic'ed Load access fault") diff --git a/components/ulp/test_apps/lp_core/lp_core_hp_uart/sdkconfig.defaults b/components/ulp/test_apps/lp_core/lp_core_hp_uart/sdkconfig.defaults index e3c08c60fa9..7901b695905 100644 --- a/components/ulp/test_apps/lp_core/lp_core_hp_uart/sdkconfig.defaults +++ b/components/ulp/test_apps/lp_core/lp_core_hp_uart/sdkconfig.defaults @@ -5,3 +5,4 @@ CONFIG_ULP_COPROC_TYPE_LP_CORE=y CONFIG_ULP_COPROC_RESERVE_MEM=12000 CONFIG_ULP_PANIC_OUTPUT_ENABLE=y CONFIG_ULP_HP_UART_CONSOLE_PRINT=y +CONFIG_ULP_LP_CORE_MEMPROT=y diff --git a/docs/en/api-reference/system/ulp-lp-core.rst b/docs/en/api-reference/system/ulp-lp-core.rst index c97f8f7a70d..49457d21603 100644 --- a/docs/en/api-reference/system/ulp-lp-core.rst +++ b/docs/en/api-reference/system/ulp-lp-core.rst @@ -13,6 +13,10 @@ The ULP LP core coprocessor has the following features: * Can access all of the High-power (HP) SRAM and peripherals when the entire system is active. * Can access the Low-power (LP) SRAM and peripherals when the HP system is in sleep mode. +.. only:: SOC_LP_CORE_HAS_PMP + + On supported targets, the LP core includes RISC-V Physical Memory Protection (PMP). Enable :ref:`CONFIG_ULP_LP_CORE_MEMPROT` to apply a deny-by-default layout at LP-core startup: LP RAM is split into an executable region (code and read-only data) and a read-write region (writable data, stack, and shared memory), LP peripheral address space is read-write, and an optional region covers HP UART MMIO when using :ref:`CONFIG_ULP_HP_UART_CONSOLE_PRINT`. It cannot be used together with :ref:`CONFIG_ULP_COPROC_RUN_FROM_HP_MEM`. Addresses that do not fall into an allowed region cause a load, store, or instruction access fault. + Compiling Code for the ULP LP Core ---------------------------------- @@ -211,6 +215,8 @@ When this option is enabled, :ref:`CONFIG_ULP_COPROC_RESERVE_HP_MEM_BYTES` reser 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. +:ref:`CONFIG_ULP_LP_CORE_MEMPROT` cannot be enabled together with this HP-memory mode. + 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 9a5ee64f2fe..dc1818ab9fa 100644 --- a/docs/zh_CN/api-reference/system/ulp-lp-core.rst +++ b/docs/zh_CN/api-reference/system/ulp-lp-core.rst @@ -13,6 +13,10 @@ ULP LP 内核协处理器具有以下功能: * 当整个系统处于 active 模式时,可以访问所有的高功耗 (HP) SRAM 和外设。 * 当 HP 系统处于睡眠模式时,可以访问低功耗 (LP) SRAM 和外设。 +.. only:: SOC_LP_CORE_HAS_PMP + + 在支持的芯片上,LP 内核提供 RISC-V 物理内存保护(PMP)。启用 :ref:`CONFIG_ULP_LP_CORE_MEMPROT` 后,LP 内核启动时会配置默认拒绝访问的 PMP 布局:LP RAM 分为可执行区(代码与只读数据)与可读写区(可写数据、栈与共享内存),LP 外设地址空间为可读写;启用 :ref:`CONFIG_ULP_HP_UART_CONSOLE_PRINT` 时还会为 HP UART MMIO 增加相应条目。PMP 不能与 :ref:`CONFIG_ULP_COPROC_RUN_FROM_HP_MEM` 同时启用。未落入允许区域的访问将触发加载、存储或取指访问异常。 + 编译 ULP LP 内核代码 -------------------- @@ -211,6 +215,8 @@ ULP LP 内核代码会与 ESP-IDF 项目共同编译,生成一个单独的二 该模式有一个重要限制:芯片进入 Deep-sleep 后,LP 内核无法继续运行,因为该睡眠模式下 HP SRAM 会被断电。因此,这种模式适用于 LP 内核只需要在 HP 系统保持上电时运行的场景;如果应用需要在 Deep-sleep 期间继续运行,则应继续使用默认的纯 LP 内存模式。 +:ref:`CONFIG_ULP_LP_CORE_MEMPROT` 不能与 HP 内存模式同时启用。 + ULP LP 内核程序流程 -------------------