From 07debe6edcbf8a4c6c98dc83d447495bfcec4cd3 Mon Sep 17 00:00:00 2001 From: "harshal.patil" Date: Fri, 27 Mar 2026 12:07:33 +0530 Subject: [PATCH 1/3] feat(esp_hw_support): Memory region protection for ESP32-H4 --- .../port/esp32h4/cpu_region_protect.c | 216 ++++++++++++------ .../esp32h4/include/esp32h4/rom/rom_layout.h | 13 +- .../esp32h4/include/soc/Kconfig.soc_caps.in | 4 + components/soc/esp32h4/include/soc/soc.h | 12 +- components/soc/esp32h4/include/soc/soc_caps.h | 1 + .../system/panic/panic_base/pytest_panic.py | 17 +- .../panic_base/sdkconfig.ci.memprot_esp32h4 | 8 + 7 files changed, 180 insertions(+), 91 deletions(-) create mode 100644 tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_esp32h4 diff --git a/components/esp_hw_support/port/esp32h4/cpu_region_protect.c b/components/esp_hw_support/port/esp32h4/cpu_region_protect.c index 8c5bb8bf933..2358bf35515 100644 --- a/components/esp_hw_support/port/esp32h4/cpu_region_protect.c +++ b/components/esp_hw_support/port/esp32h4/cpu_region_protect.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,6 +9,10 @@ #include "soc/soc.h" #include "esp_cpu.h" #include "esp_fault.h" +#include "esp32h4/rom/rom_layout.h" +#if CONFIG_SPIRAM +#include "esp_private/esp_psram_extram.h" +#endif /* CONFIG_SPIRAM */ #ifdef BOOTLOADER_BUILD // Without L bit set @@ -16,26 +20,87 @@ #define CONDITIONAL_RX PMP_R | PMP_X #define CONDITIONAL_RW PMP_R | PMP_W #define CONDITIONAL_RWX PMP_R | PMP_W | PMP_X +// PMA entries must not be locked in the bootloader, otherwise the application cannot +// reconfigure them (e.g. a single-core application booted by a multi-core bootloader needs +// to extend the valid RAM window over the reclaimed ICache1 SRAM, see SOC_HP_RAM_HIGH below). +#define CONDITIONAL_PMA_L 0x0 #else // With L bit set #define CONDITIONAL_NONE NONE #define CONDITIONAL_RX RX #define CONDITIONAL_RW RW #define CONDITIONAL_RWX RWX +#define CONDITIONAL_PMA_L PMA_L #endif -//TODO: [ESP32H4] IDF-12588 +#define ALIGN_UP_TO_MMU_PAGE_SIZE(addr) (((addr) + (SOC_MMU_PAGE_SIZE) - 1) & ~((SOC_MMU_PAGE_SIZE) - 1)) +#define ALIGN_DOWN_TO_MMU_PAGE_SIZE(addr) ((addr) & ~((SOC_MMU_PAGE_SIZE) - 1)) +#define ALIGN_UP(addr, align) ((addr) & ~((align) - 1)) + +/* In single core mode, the ICache1 SRAM block (SOC_RAM_ICACHE1_LOW..SOC_RAM_ICACHE1_HIGH) is + * reclaimed and handed to the heap as regular RAM (see components/heap/port/esp32h4/memory_layout.c). + * It is contiguous with the top of the D/IRAM region, so the region protection must extend the valid + * RAM window up to the top of ICache1, otherwise the first access to that heap memory triggers a + * load/store access fault. In multi-core mode the same block is used as actual I-Cache and must stay + * outside the valid RAM window. + */ +#if CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE +#define SOC_HP_RAM_HIGH (SOC_RAM_ICACHE1_HIGH + 1) +_Static_assert(SOC_IRAM_HIGH == SOC_RAM_ICACHE1_LOW, "ICache1 RAM must be contiguous with D/IRAM"); +#else +#define SOC_HP_RAM_HIGH SOC_IRAM_HIGH +#endif static void esp_cpu_configure_invalid_regions(void) { - abort(); + const unsigned PMA_NONE = CONDITIONAL_PMA_L | PMA_EN; + __attribute__((unused)) const unsigned PMA_RW = CONDITIONAL_PMA_L | PMA_EN | PMA_R | PMA_W; + __attribute__((unused)) const unsigned PMA_RX = CONDITIONAL_PMA_L | PMA_EN | PMA_R | PMA_X; + __attribute__((unused)) const unsigned PMA_RWX = CONDITIONAL_PMA_L | PMA_EN | PMA_R | PMA_W | PMA_X; + + // ROM uses some PMA entries, so we need to clear them before using them in ESP-IDF + + // 0. Gap at bottom of address space + PMA_RESET_AND_ENTRY_SET_NAPOT(0, 0, SOC_CPU_SUBSYSTEM_LOW, PMA_NAPOT | PMA_NONE); + + // 1. Gap between CPU subsystem region & ROM-Cache + PMA_RESET_AND_ENTRY_SET_TOR(1, SOC_CPU_SUBSYSTEM_HIGH, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(2, SOC_IROM_MASK_LOW, PMA_TOR | PMA_NONE); + + // 2. ROM has configured the ROM region to be cacheable, lock the configuration + PMA_RESET_AND_ENTRY_SET_TOR(3, SOC_IROM_MASK_LOW, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(4, SOC_DROM_MASK_HIGH, PMA_TOR | PMA_RX); + + // 3. Gap between ROM-Cache & RAM + PMA_RESET_AND_ENTRY_SET_TOR(5, SOC_DROM_MASK_HIGH, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(6, SOC_IRAM_LOW, PMA_TOR | PMA_NONE); + + // 4. Gap between DRAM and I_Cache + // In single core mode SOC_HP_RAM_HIGH includes the ICache1 SRAM that is used as heap RAM, + // so the invalid gap starts above it. + PMA_RESET_AND_ENTRY_SET_TOR(7, SOC_HP_RAM_HIGH, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(8, SOC_IROM_LOW, PMA_TOR | PMA_NONE); + + // 5. ROM has configured the MSPI region with RX permission, add W attribute for PSRAM and lock + // This function sets invalid regions but this is a valid memory region configuration that could have + // been configured using PMP as well, but due to insufficient PMP entries we are configuring this using PMA. + // This entry is also required to be set using PMA because the region needs to be configured as cacheable. + PMA_RESET_AND_ENTRY_SET_NAPOT(9, SOC_IROM_LOW, (SOC_IROM_HIGH - SOC_IROM_LOW), PMA_NAPOT | PMA_RWX); + + // 6. Gap between D_Cache & peripheral addresses + PMA_RESET_AND_ENTRY_SET_TOR(10, SOC_DROM_HIGH, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(11, SOC_PERIPHERAL_LOW, PMA_TOR | PMA_NONE); + + // 7. End of address space + PMA_RESET_AND_ENTRY_SET_TOR(12, SOC_PERIPHERAL_HIGH, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(13, UINT32_MAX, PMA_TOR | PMA_NONE); + + PMA_ENTRY_CFG_RESET(14); + PMA_ENTRY_CFG_RESET(15); } void esp_cpu_configure_region_protection(void) { - // ROM has configured the MSPI region with RX permission, we should add W attribute for psram - PMA_ENTRY_SET_NAPOT(0, SOC_IROM_LOW, (SOC_IROM_HIGH - SOC_IROM_LOW), PMA_NAPOT | PMA_L | PMA_EN | PMA_R | PMA_W | PMA_X); - return; /* Notes on implementation: * * 1) Note: ESP32-H4 CPU doesn't support overlapping PMP regions @@ -58,7 +123,7 @@ void esp_cpu_configure_region_protection(void) * * 2. Application build with CONFIG_ESP_SYSTEM_MEMPROT enabled * - We split the SRAM into IRAM and DRAM such that IRAM region cannot be written to - * and DRAM region cannot be executed. We use _iram_end and _data_start markers to set the boundaries. + * and DRAM region cannot be executed. We use _iram_text_end and _data_start markers to set the boundaries. * We also lock these entries so the R/W/X permissions are enforced even for machine mode * * 3. Application build with CONFIG_ESP_SYSTEM_MEMPROT disabled @@ -72,10 +137,10 @@ void esp_cpu_configure_region_protection(void) * We also lock these entries so the R/W/X permissions are enforced even for machine mode */ const unsigned NONE = PMP_L; - const unsigned R = PMP_L | PMP_R; - const unsigned RW = PMP_L | PMP_R | PMP_W; - const unsigned RX = PMP_L | PMP_R | PMP_X; - const unsigned RWX = PMP_L | PMP_R | PMP_W | PMP_X; + __attribute__((unused)) const unsigned R = PMP_L | PMP_R; + __attribute__((unused)) const unsigned RW = PMP_L | PMP_R | PMP_W; + __attribute__((unused)) const unsigned RX = PMP_L | PMP_R | PMP_X; + __attribute__((unused)) const unsigned RWX = PMP_L | PMP_R | PMP_W | PMP_X; // // Configure all the invalid address regions using PMA @@ -86,91 +151,90 @@ void esp_cpu_configure_region_protection(void) // Configure all the valid address regions using PMP // - // 1. Debug region - const uint32_t pmpaddr0 = PMPADDR_NAPOT(SOC_DEBUG_LOW, SOC_DEBUG_HIGH); + // 1. CPU Subsystem region - contains debug mode code and interrupt config registers + const uint32_t pmpaddr0 = PMPADDR_NAPOT(SOC_CPU_SUBSYSTEM_LOW, SOC_CPU_SUBSYSTEM_HIGH); PMP_ENTRY_SET(0, pmpaddr0, PMP_NAPOT | RWX); - _Static_assert(SOC_DEBUG_LOW < SOC_DEBUG_HIGH, "Invalid CPU debug region"); + _Static_assert(SOC_CPU_SUBSYSTEM_LOW < SOC_CPU_SUBSYSTEM_HIGH, "Invalid CPU subsystem region"); - // 2.1 I-ROM - PMP_ENTRY_SET(1, SOC_IROM_MASK_LOW, NONE); - PMP_ENTRY_SET(2, SOC_IROM_MASK_HIGH, PMP_TOR | RX); - _Static_assert(SOC_IROM_MASK_LOW < SOC_IROM_MASK_HIGH, "Invalid I-ROM region"); - - // 2.2 D-ROM - PMP_ENTRY_SET(3, SOC_DROM_MASK_LOW, NONE); - PMP_ENTRY_SET(4, SOC_DROM_MASK_HIGH, PMP_TOR | R); - _Static_assert(SOC_DROM_MASK_LOW < SOC_DROM_MASK_HIGH, "Invalid D-ROM region"); + // 2. I/D-ROM (ROM-Cache) +#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && !BOOTLOADER_BUILD + const uint32_t drom_start = (uint32_t) (ets_rom_layout_p->drom_start); + if ((drom_start & (SOC_CPU_PMP_REGION_GRANULARITY - 1)) == 0) { + // We can skip configuring the PMP entry for the [SOC_IROM_MASK_LOW - drom_start] + // region as RX, as we already have configured a PMA entry with RX permissions for the + // [SOC_IROM_MASK_LOW - SOC_DROM_MASK_HIGH] region that helps us to also configure + // the region as cacheable. Thus, we save on one PMP entry. + PMP_ENTRY_SET(1, drom_start, NONE); + PMP_ENTRY_SET(2, SOC_DROM_MASK_HIGH, PMP_TOR | R); + } else +#endif + { + PMP_ENTRY_SET(1, SOC_IROM_MASK_LOW, NONE); + PMP_ENTRY_SET(2, SOC_IROM_MASK_HIGH, PMP_TOR | CONDITIONAL_RX); + _Static_assert(SOC_IROM_MASK_LOW < SOC_IROM_MASK_HIGH, "Invalid I/D-ROM region"); + } + // 3. IRAM and DRAM if (esp_cpu_dbgr_is_attached()) { // Anti-FI check that cpu is really in ocd mode ESP_FAULT_ASSERT(esp_cpu_dbgr_is_attached()); - // 5. IRAM and DRAM - // const uint32_t pmpaddr5 = PMPADDR_NAPOT(SOC_IRAM_LOW, SOC_IRAM_HIGH); - // PMP_ENTRY_SET(5, pmpaddr5, PMP_NAPOT | RWX); - // _Static_assert(SOC_IRAM_LOW < SOC_IRAM_HIGH, "Invalid RAM region"); - PMP_ENTRY_SET(5, SOC_IRAM_LOW, NONE); - PMP_ENTRY_SET(6, SOC_IRAM_HIGH, PMP_TOR | RWX); + PMP_ENTRY_SET(3, SOC_IRAM_LOW, NONE); + PMP_ENTRY_SET(4, SOC_HP_RAM_HIGH, PMP_TOR | RWX); _Static_assert(SOC_IRAM_LOW < SOC_IRAM_HIGH, "Invalid RAM region"); } else { #if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && !BOOTLOADER_BUILD - extern int _iram_end; - // 5. IRAM and DRAM - /* Reset the corresponding PMP config because PMP_ENTRY_SET only sets the given bits - * Bootloader might have given extra permissions and those won't be cleared - */ + extern int _iram_text_end; + PMP_ENTRY_CFG_RESET(3); + PMP_ENTRY_CFG_RESET(4); PMP_ENTRY_CFG_RESET(5); - PMP_ENTRY_CFG_RESET(6); - PMP_ENTRY_CFG_RESET(7); - PMP_ENTRY_SET(5, SOC_IRAM_LOW, NONE); - PMP_ENTRY_SET(6, (int)&_iram_end, PMP_TOR | RX); - PMP_ENTRY_SET(7, SOC_DRAM_HIGH, PMP_TOR | RW); + PMP_ENTRY_SET(3, SOC_IRAM_LOW, NONE); + PMP_ENTRY_SET(4, (int)&_iram_text_end, PMP_TOR | RX); + PMP_ENTRY_SET(5, SOC_HP_RAM_HIGH, PMP_TOR | RW); #else - // 5. IRAM and DRAM - // const uint32_t pmpaddr5 = PMPADDR_NAPOT(SOC_IRAM_LOW, SOC_IRAM_HIGH); - // PMP_ENTRY_SET(5, pmpaddr5, PMP_NAPOT | CONDITIONAL_RWX); - // _Static_assert(SOC_IRAM_LOW < SOC_IRAM_HIGH, "Invalid RAM region"); - PMP_ENTRY_SET(5, SOC_IRAM_LOW, NONE); - PMP_ENTRY_SET(6, SOC_IRAM_HIGH, PMP_TOR | RWX); + PMP_ENTRY_SET(3, SOC_IRAM_LOW, CONDITIONAL_NONE); + PMP_ENTRY_SET(4, SOC_HP_RAM_HIGH, PMP_TOR | CONDITIONAL_RWX); _Static_assert(SOC_IRAM_LOW < SOC_IRAM_HIGH, "Invalid RAM region"); #endif } - // 4. I_Cache (flash) - const uint32_t pmpaddr8 = PMPADDR_NAPOT(SOC_IROM_LOW, SOC_IROM_HIGH); - PMP_ENTRY_SET(8, pmpaddr8, PMP_NAPOT | RX); - _Static_assert(SOC_IROM_LOW < SOC_IROM_HIGH, "Invalid I_Cache region"); - - // 5. D_Cache (flash) - const uint32_t pmpaddr9 = PMPADDR_NAPOT(SOC_DROM_LOW, SOC_DROM_HIGH); - PMP_ENTRY_SET(9, pmpaddr9, PMP_NAPOT | R); - _Static_assert(SOC_DROM_LOW < SOC_DROM_HIGH, "Invalid D_Cache region"); - - // 6. LP memory + // 4. I_Cache / D_Cache (flash) #if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && !BOOTLOADER_BUILD - extern int _rtc_text_end; - /* Reset the corresponding PMP config because PMP_ENTRY_SET only sets the given bits - * Bootloader might have given extra permissions and those won't be cleared - */ - PMP_ENTRY_CFG_RESET(10); - PMP_ENTRY_CFG_RESET(11); - PMP_ENTRY_CFG_RESET(12); - PMP_ENTRY_CFG_RESET(13); - PMP_ENTRY_SET(10, SOC_RTC_IRAM_LOW, NONE); + extern int _instruction_reserved_end; + extern int _rodata_reserved_end; - // Repeat same previous entry, to ensure next entry has correct base address (TOR) - PMP_ENTRY_SET(11, SOC_RTC_IRAM_LOW, NONE); - PMP_ENTRY_SET(12, (int)&_rtc_text_end, PMP_TOR | RX); - PMP_ENTRY_SET(13, SOC_RTC_IRAM_HIGH, PMP_TOR | RW); + const uint32_t page_aligned_irom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)(&_instruction_reserved_end)); + __attribute__((unused)) const uint32_t page_aligned_drom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)(&_rodata_reserved_end)); + + PMP_ENTRY_SET(6, SOC_IROM_LOW, NONE); + +#if CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION + PMP_ENTRY_SET(7, (uint32_t)(&_instruction_reserved_end), PMP_TOR | RX); + PMP_ENTRY_SET(8, page_aligned_irom_resv_end, PMP_TOR | RW); #else - const uint32_t pmpaddr10 = PMPADDR_NAPOT(SOC_RTC_IRAM_LOW, SOC_RTC_IRAM_HIGH); - PMP_ENTRY_SET(10, pmpaddr10, PMP_NAPOT | CONDITIONAL_RWX); - _Static_assert(SOC_RTC_IRAM_LOW < SOC_RTC_IRAM_HIGH, "Invalid RTC IRAM region"); + PMP_ENTRY_SET(7, page_aligned_irom_resv_end, PMP_TOR | RX); + PMP_ENTRY_SET(8, page_aligned_irom_resv_end, NONE); +#endif /* CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION */ + +#if CONFIG_SPIRAM_RODATA && CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION + PMP_ENTRY_SET(9, (uint32_t)(&_rodata_reserved_end), PMP_TOR | R); +#else + PMP_ENTRY_SET(9, page_aligned_drom_resv_end, PMP_TOR | R); +#endif /* CONFIG_SPIRAM_RODATA && CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION */ + +#if CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION + size_t available_psram_heap = esp_psram_get_heap_size_to_protect(); + PMP_ENTRY_SET(10, ALIGN_UP(page_aligned_drom_resv_end + available_psram_heap, SOC_CPU_PMP_REGION_GRANULARITY), PMP_TOR | RW); +#endif /* CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION */ +#else + const uint32_t pmpaddr6 = PMPADDR_NAPOT(SOC_IROM_LOW, SOC_IROM_HIGH); + // Add the W attribute in the case of PSRAM + PMP_ENTRY_SET(6, pmpaddr6, PMP_NAPOT | CONDITIONAL_RWX); + _Static_assert(SOC_IROM_LOW < SOC_IROM_HIGH, "Invalid I/D_Cache region"); #endif - - // 7. Peripheral addresses - const uint32_t pmpaddr14 = PMPADDR_NAPOT(SOC_PERIPHERAL_LOW, SOC_PERIPHERAL_HIGH); - PMP_ENTRY_SET(14, pmpaddr14, PMP_NAPOT | RW); + // 5. Peripheral addresses + const uint32_t pmpaddr15 = PMPADDR_NAPOT(SOC_PERIPHERAL_LOW, SOC_PERIPHERAL_HIGH); + PMP_ENTRY_SET(15, pmpaddr15, PMP_NAPOT | RW); _Static_assert(SOC_PERIPHERAL_LOW < SOC_PERIPHERAL_HIGH, "Invalid peripheral region"); } diff --git a/components/esp_rom/esp32h4/include/esp32h4/rom/rom_layout.h b/components/esp_rom/esp32h4/include/esp32h4/rom/rom_layout.h index 0b7da625d73..e8a4c71ca35 100644 --- a/components/esp_rom/esp32h4/include/esp32h4/rom/rom_layout.h +++ b/components/esp_rom/esp32h4/include/esp32h4/rom/rom_layout.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -85,16 +85,15 @@ typedef struct { #if SUPPORT_USB_DWCOTG void *dram_start_usb_dwcotg_rom; void *dram_end_usb_dwcotg_rom; -#else - //Two reserved members are defined here, so the structure will not be broken, - //please keep in mind that there is no memory can be released between - //dram_start_usb_reserved_rom ~ dram_end_usb_reserved_rom. - void *dram_start_usb_reserved_rom; - void *dram_end_usb_reserved_rom; #endif void *dram_start_uart_rom; void *dram_end_uart_rom; + + void *eh_frame_vaddr_rom; + void *eh_frame_hdr_vaddr_rom; + + void *drom_start; } ets_rom_layout_t; extern const ets_rom_layout_t *const ets_rom_layout_p; diff --git a/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in index 8c65b2507ef..290a4a22de6 100644 --- a/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h4/include/soc/Kconfig.soc_caps.in @@ -419,6 +419,10 @@ config SOC_CPU_IDRAM_SPLIT_USING_PMP bool default y +config SOC_CPU_PMP_REGION_GRANULARITY + int + default 128 + config SOC_HP_CPU_HAS_MULTIPLE_CORES bool default y diff --git a/components/soc/esp32h4/include/soc/soc.h b/components/soc/esp32h4/include/soc/soc.h index 1daca7b6c6b..95d452909a5 100644 --- a/components/soc/esp32h4/include/soc/soc.h +++ b/components/soc/esp32h4/include/soc/soc.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 OR MIT */ @@ -142,9 +142,9 @@ #define SOC_DROM_LOW SOC_IROM_LOW #define SOC_DROM_HIGH SOC_IROM_HIGH #define SOC_IROM_MASK_LOW 0x40000000 -#define SOC_IROM_MASK_HIGH 0x40050000 +#define SOC_IROM_MASK_HIGH 0x40020000 #define SOC_DROM_MASK_LOW 0x40000000 -#define SOC_DROM_MASK_HIGH 0x40050000 +#define SOC_DROM_MASK_HIGH 0x40020000 #define SOC_IRAM_LOW 0x40810000 #define SOC_IRAM_HIGH 0x40860000 #define SOC_DRAM_LOW 0x40810000 @@ -186,9 +186,9 @@ #define SOC_PERIPHERAL_LOW 0x60000000 #define SOC_PERIPHERAL_HIGH 0x60100000 -// Debug region, not used by software -#define SOC_DEBUG_LOW 0x20000000 -#define SOC_DEBUG_HIGH 0x28000000 +// CPU subsystem region (debug mode code and interrupt config) +#define SOC_CPU_SUBSYSTEM_LOW 0x20000000 +#define SOC_CPU_SUBSYSTEM_HIGH 0x30000000 // Start (highest address) of ROM boot stack, only relevant during early boot #define SOC_ROM_STACK_START 0x4085d350 diff --git a/components/soc/esp32h4/include/soc/soc_caps.h b/components/soc/esp32h4/include/soc/soc_caps.h index c16826801d2..5d060c43334 100644 --- a/components/soc/esp32h4/include/soc/soc_caps.h +++ b/components/soc/esp32h4/include/soc/soc_caps.h @@ -173,6 +173,7 @@ #define SOC_CPU_HAS_PMA 1 #define SOC_CPU_IDRAM_SPLIT_USING_PMP 1 +#define SOC_CPU_PMP_REGION_GRANULARITY 128 #define SOC_HP_CPU_HAS_MULTIPLE_CORES 1 // Convenience boolean macro used to determine if a target has multiple cores. #define SOC_CPU_HAS_LOCKUP_RESET 1 diff --git a/tools/test_apps/system/panic/panic_base/pytest_panic.py b/tools/test_apps/system/panic/panic_base/pytest_panic.py index a4001c4cd8b..dbfd36807ca 100644 --- a/tools/test_apps/system/panic/panic_base/pytest_panic.py +++ b/tools/test_apps/system/panic/panic_base/pytest_panic.py @@ -635,8 +635,20 @@ CONFIGS_MEMPROT_IDRAM = list( 'memprot_esp32p4', 'memprot_esp32h21', 'memprot_esp32s31', + 'memprot_esp32h4', + ], + [ + 'esp32s2', + 'esp32c3', + 'esp32c2', + 'esp32c5', + 'esp32c61', + 'esp32h2', + 'esp32p4', + 'esp32h21', + 'esp32s31', + 'esp32h4', ], - ['esp32s2', 'esp32c3', 'esp32c2', 'esp32c5', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h21', 'esp32s31'], ) ) + [ ('memprot_esp32p4_rev_less_than_v3', 'esp32p4', P4_REV_LESS_THAN_V3_MARKER), @@ -676,8 +688,9 @@ CONFIGS_MEMPROT_FLASH_IDROM = list( 'memprot_esp32p4', 'memprot_esp32h21', 'memprot_esp32s31', + 'memprot_esp32h4', ], - ['esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h21', 'esp32s31'], + ['esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h21', 'esp32s31', 'esp32h4'], ) ) + [ ('memprot_esp32p4_rev_less_than_v3', 'esp32p4', P4_REV_LESS_THAN_V3_MARKER), diff --git a/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_esp32h4 b/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_esp32h4 new file mode 100644 index 00000000000..350dc3be521 --- /dev/null +++ b/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_esp32h4 @@ -0,0 +1,8 @@ +# Restricting to ESP32H4 +CONFIG_IDF_TARGET="esp32h4" + +# Enabling memory protection +CONFIG_ESP_SYSTEM_MEMPROT=y + +# Enable memprot test +CONFIG_TEST_MEMPROT=y From a7f4dbf35abdf78c631c9c639c155c19ca596af9 Mon Sep 17 00:00:00 2001 From: "harshal.patil" Date: Mon, 24 Aug 2026 13:25:58 +0530 Subject: [PATCH 2/3] fix(soc): make SOC_RAM_ICACHE1_HIGH an exclusive bound on ESP32-H4 Every other SOC_*_HIGH macro in soc.h is an exclusive region end; SOC_RAM_ICACHE1_HIGH was the inclusive last byte (0x40867fff). This also made the reclaimed ICache1 heap region in single core mode (components/heap/port/esp32h4/memory_layout.c, sized as HIGH - LOW) one byte short. Change the value to the exclusive end 0x40868000 and drop the +1 compensation at the SOC_HP_RAM_HIGH definition. --- components/esp_hw_support/port/esp32h4/cpu_region_protect.c | 2 +- components/soc/esp32h4/include/soc/soc.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esp_hw_support/port/esp32h4/cpu_region_protect.c b/components/esp_hw_support/port/esp32h4/cpu_region_protect.c index 2358bf35515..7cfa4be33f1 100644 --- a/components/esp_hw_support/port/esp32h4/cpu_region_protect.c +++ b/components/esp_hw_support/port/esp32h4/cpu_region_protect.c @@ -45,7 +45,7 @@ * outside the valid RAM window. */ #if CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE -#define SOC_HP_RAM_HIGH (SOC_RAM_ICACHE1_HIGH + 1) +#define SOC_HP_RAM_HIGH SOC_RAM_ICACHE1_HIGH _Static_assert(SOC_IRAM_HIGH == SOC_RAM_ICACHE1_LOW, "ICache1 RAM must be contiguous with D/IRAM"); #else #define SOC_HP_RAM_HIGH SOC_IRAM_HIGH diff --git a/components/soc/esp32h4/include/soc/soc.h b/components/soc/esp32h4/include/soc/soc.h index 95d452909a5..4f85684dc31 100644 --- a/components/soc/esp32h4/include/soc/soc.h +++ b/components/soc/esp32h4/include/soc/soc.h @@ -196,7 +196,7 @@ //ICache1 region #define SOC_RAM_ICACHE1_LOW 0x40860000 -#define SOC_RAM_ICACHE1_HIGH 0x40867fff +#define SOC_RAM_ICACHE1_HIGH 0x40868000 //On RISC-V CPUs, the interrupt sources are all external interrupts, whose type, source and priority are configured by SW. //There is no HW NMI conception. SW should controlled the masked levels through INT_THRESH_REG. From 53c94391618ae1c0e491e9a8dcb7eaeee8a0e0dc Mon Sep 17 00:00:00 2001 From: "harshal.patil" Date: Fri, 21 Aug 2026 12:32:41 +0530 Subject: [PATCH 3/3] change(esp_hw_support): rework ESP32-H4 region protection to the application-owned layout The bootloader programs only unlocked PMA entries (no PMP), so the layout is not a bootloader<->application ABI; the application resets all entries, programs them in ascending order from IRAM, and locks everything. Verified on ESP32-H4 v0.1. --- .../port/esp32h4/cpu_region_protect.c | 316 ++++++++++-------- .../system/panic/panic_base/pytest_panic.py | 9 +- .../sdkconfig.ci.memprot_spiram_xip_esp32h4 | 13 + 3 files changed, 203 insertions(+), 135 deletions(-) create mode 100644 tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_spiram_xip_esp32h4 diff --git a/components/esp_hw_support/port/esp32h4/cpu_region_protect.c b/components/esp_hw_support/port/esp32h4/cpu_region_protect.c index 7cfa4be33f1..ac22cbe3741 100644 --- a/components/esp_hw_support/port/esp32h4/cpu_region_protect.c +++ b/components/esp_hw_support/port/esp32h4/cpu_region_protect.c @@ -10,39 +10,17 @@ #include "esp_cpu.h" #include "esp_fault.h" #include "esp32h4/rom/rom_layout.h" -#if CONFIG_SPIRAM +#include "esp_macros.h" +#include "esp_attr.h" +#if !BOOTLOADER_BUILD && CONFIG_SPIRAM #include "esp_private/esp_psram_extram.h" -#endif /* CONFIG_SPIRAM */ +#endif /* !BOOTLOADER_BUILD && CONFIG_SPIRAM */ -#ifdef BOOTLOADER_BUILD -// Without L bit set -#define CONDITIONAL_NONE 0x0 -#define CONDITIONAL_RX PMP_R | PMP_X -#define CONDITIONAL_RW PMP_R | PMP_W -#define CONDITIONAL_RWX PMP_R | PMP_W | PMP_X -// PMA entries must not be locked in the bootloader, otherwise the application cannot -// reconfigure them (e.g. a single-core application booted by a multi-core bootloader needs -// to extend the valid RAM window over the reclaimed ICache1 SRAM, see SOC_HP_RAM_HIGH below). -#define CONDITIONAL_PMA_L 0x0 -#else -// With L bit set -#define CONDITIONAL_NONE NONE -#define CONDITIONAL_RX RX -#define CONDITIONAL_RW RW -#define CONDITIONAL_RWX RWX -#define CONDITIONAL_PMA_L PMA_L -#endif +#define ALIGN_UP_TO_MMU_PAGE_SIZE(addr) ESP_ALIGN_UP(addr, SOC_MMU_PAGE_SIZE) -#define ALIGN_UP_TO_MMU_PAGE_SIZE(addr) (((addr) + (SOC_MMU_PAGE_SIZE) - 1) & ~((SOC_MMU_PAGE_SIZE) - 1)) -#define ALIGN_DOWN_TO_MMU_PAGE_SIZE(addr) ((addr) & ~((SOC_MMU_PAGE_SIZE) - 1)) -#define ALIGN_UP(addr, align) ((addr) & ~((align) - 1)) - -/* In single core mode, the ICache1 SRAM block (SOC_RAM_ICACHE1_LOW..SOC_RAM_ICACHE1_HIGH) is - * reclaimed and handed to the heap as regular RAM (see components/heap/port/esp32h4/memory_layout.c). - * It is contiguous with the top of the D/IRAM region, so the region protection must extend the valid - * RAM window up to the top of ICache1, otherwise the first access to that heap memory triggers a - * load/store access fault. In multi-core mode the same block is used as actual I-Cache and must stay - * outside the valid RAM window. +/* In single core mode, the ICache1 SRAM block is reclaimed as heap RAM (contiguous with D/IRAM, + * see components/heap/port/esp32h4/memory_layout.c), so the valid RAM window must extend over it. + * In multi-core mode it is used as actual I-Cache and must stay outside the window. */ #if CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE #define SOC_HP_RAM_HIGH SOC_RAM_ICACHE1_HIGH @@ -51,87 +29,95 @@ _Static_assert(SOC_IRAM_HIGH == SOC_RAM_ICACHE1_LOW, "ICache1 RAM must be contig #define SOC_HP_RAM_HIGH SOC_IRAM_HIGH #endif -static void esp_cpu_configure_invalid_regions(void) +NOINLINE_ATTR IRAM_ATTR static void esp_cpu_configure_invalid_regions(void) { - const unsigned PMA_NONE = CONDITIONAL_PMA_L | PMA_EN; - __attribute__((unused)) const unsigned PMA_RW = CONDITIONAL_PMA_L | PMA_EN | PMA_R | PMA_W; - __attribute__((unused)) const unsigned PMA_RX = CONDITIONAL_PMA_L | PMA_EN | PMA_R | PMA_X; - __attribute__((unused)) const unsigned PMA_RWX = CONDITIONAL_PMA_L | PMA_EN | PMA_R | PMA_W | PMA_X; +#ifdef BOOTLOADER_BUILD + /* Don't lock PMA entries in the bootloader: an enabled entry constrains M-mode even without + * the lock bit (unlike PMP), and a locked entry would be frozen until CPU reset, turning the + * PMA layout into a bootloader<->application ABI. Only the application locks. */ + const unsigned PMA_LOCK = 0; +#else + const unsigned PMA_LOCK = PMA_L; +#endif + const unsigned PMA_NONE = PMA_LOCK | PMA_EN; + __attribute__((unused)) const unsigned PMA_RW = PMA_LOCK | PMA_EN | PMA_R | PMA_W; + __attribute__((unused)) const unsigned PMA_RX = PMA_LOCK | PMA_EN | PMA_R | PMA_X; + __attribute__((unused)) const unsigned PMA_RWX = PMA_LOCK | PMA_EN | PMA_R | PMA_W | PMA_X; - // ROM uses some PMA entries, so we need to clear them before using them in ESP-IDF + /* Disable every PMA entry before programming. + * Descending order helps to correctly reset the TOR settings (if any). + */ + PMA_ENTRY_CFG_RESET(15); + PMA_ENTRY_CFG_RESET(14); + PMA_ENTRY_CFG_RESET(13); + PMA_ENTRY_CFG_RESET(12); + PMA_ENTRY_CFG_RESET(11); + PMA_ENTRY_CFG_RESET(10); + PMA_ENTRY_CFG_RESET(9); + PMA_ENTRY_CFG_RESET(8); + PMA_ENTRY_CFG_RESET(7); + PMA_ENTRY_CFG_RESET(6); + PMA_ENTRY_CFG_RESET(5); + PMA_ENTRY_CFG_RESET(4); + PMA_ENTRY_CFG_RESET(3); + PMA_ENTRY_CFG_RESET(2); + PMA_ENTRY_CFG_RESET(1); + PMA_ENTRY_CFG_RESET(0); // 0. Gap at bottom of address space - PMA_RESET_AND_ENTRY_SET_NAPOT(0, 0, SOC_CPU_SUBSYSTEM_LOW, PMA_NAPOT | PMA_NONE); + PMA_ENTRY_SET_NAPOT(0, 0, SOC_CPU_SUBSYSTEM_LOW, PMA_NAPOT | PMA_NONE); - // 1. Gap between CPU subsystem region & ROM-Cache - PMA_RESET_AND_ENTRY_SET_TOR(1, SOC_CPU_SUBSYSTEM_HIGH, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(2, SOC_IROM_MASK_LOW, PMA_TOR | PMA_NONE); + // 1. Gap between CPU subsystem region (valid, configured using PMP below) and ROM + PMA_ENTRY_SET_TOR(1, SOC_CPU_SUBSYSTEM_HIGH, PMA_NONE); + PMA_ENTRY_SET_TOR(2, SOC_IROM_MASK_LOW, PMA_TOR | PMA_NONE); - // 2. ROM has configured the ROM region to be cacheable, lock the configuration - PMA_RESET_AND_ENTRY_SET_TOR(3, SOC_IROM_MASK_LOW, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(4, SOC_DROM_MASK_HIGH, PMA_TOR | PMA_RX); + // 2. ROM region, kept valid as RX so the deny chain does not swallow it; also the only no-W + // enforcement on the ROM in the bootloader stage, where no PMP entry exists. + PMA_ENTRY_SET_TOR(3, SOC_DROM_MASK_HIGH, PMA_TOR | PMA_RX); - // 3. Gap between ROM-Cache & RAM - PMA_RESET_AND_ENTRY_SET_TOR(5, SOC_DROM_MASK_HIGH, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(6, SOC_IRAM_LOW, PMA_TOR | PMA_NONE); + // 3. Gap between ROM and the D/IRAM region, including the SRAM0 block (DCache data memory), + // which ESP-IDF never assigns to the CPU. + PMA_ENTRY_SET_TOR(4, SOC_IRAM_LOW, PMA_TOR | PMA_NONE); - // 4. Gap between DRAM and I_Cache - // In single core mode SOC_HP_RAM_HIGH includes the ICache1 SRAM that is used as heap RAM, - // so the invalid gap starts above it. - PMA_RESET_AND_ENTRY_SET_TOR(7, SOC_HP_RAM_HIGH, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(8, SOC_IROM_LOW, PMA_TOR | PMA_NONE); + // 4. Gap between the valid RAM window (configured using PMP below) and the external memory + // window. In multi-core mode this gap starts at the ICache1 block. + PMA_ENTRY_SET_TOR(5, SOC_HP_RAM_HIGH, PMA_NONE); + PMA_ENTRY_SET_TOR(6, SOC_IROM_LOW, PMA_TOR | PMA_NONE); - // 5. ROM has configured the MSPI region with RX permission, add W attribute for PSRAM and lock - // This function sets invalid regions but this is a valid memory region configuration that could have - // been configured using PMP as well, but due to insufficient PMP entries we are configuring this using PMA. - // This entry is also required to be set using PMA because the region needs to be configured as cacheable. - PMA_RESET_AND_ENTRY_SET_NAPOT(9, SOC_IROM_LOW, (SOC_IROM_HIGH - SOC_IROM_LOW), PMA_NAPOT | PMA_RWX); + // 5. External memory window (unified for flash and PSRAM): the cacheable attribute can only + // be set through PMA. W is only needed when PSRAM is in use, but the bootloader cannot see + // CONFIG_SPIRAM and the application's early load-to-PSRAM copy runs under this (still + // bootloader-programmed) entry - so the bootloader always grants W, the application narrows. +#if defined(BOOTLOADER_BUILD) || CONFIG_SPIRAM + PMA_ENTRY_SET_TOR(7, SOC_IROM_HIGH, PMA_TOR | PMA_RWX); +#else + PMA_ENTRY_SET_TOR(7, SOC_IROM_HIGH, PMA_TOR | PMA_RX); +#endif - // 6. Gap between D_Cache & peripheral addresses - PMA_RESET_AND_ENTRY_SET_TOR(10, SOC_DROM_HIGH, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(11, SOC_PERIPHERAL_LOW, PMA_TOR | PMA_NONE); + // 6. Gap between the external memory window and the peripherals (no LP/RTC RAM on this target) + PMA_ENTRY_SET_TOR(8, SOC_PERIPHERAL_LOW, PMA_TOR | PMA_NONE); - // 7. End of address space - PMA_RESET_AND_ENTRY_SET_TOR(12, SOC_PERIPHERAL_HIGH, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(13, UINT32_MAX, PMA_TOR | PMA_NONE); - - PMA_ENTRY_CFG_RESET(14); - PMA_ENTRY_CFG_RESET(15); + // 7. End of address space; the peripherals below are valid, configured using PMP + PMA_ENTRY_SET_TOR(9, SOC_PERIPHERAL_HIGH, PMA_NONE); + PMA_ENTRY_SET_TOR(10, UINT32_MAX, PMA_TOR | PMA_NONE); } -void esp_cpu_configure_region_protection(void) +#ifndef BOOTLOADER_BUILD +static void esp_cpu_configure_valid_regions(void) { - /* Notes on implementation: + /* There are 3 configuration scenarios for SRAM * - * 1) Note: ESP32-H4 CPU doesn't support overlapping PMP regions - * - * 2) ESP32-H4 supports 16 PMA regions so we use this feature to block all the invalid address ranges - * - * 3) We use combination of NAPOT (Naturally Aligned Power Of Two) and TOR (top of range) - * entries to map all the valid address space, bottom to top. This leaves us with some extra PMP entries - * which can be used to provide more granular access - * - * 4) Entries are grouped in order with some static asserts to try and verify everything is - * correct. - */ - - /* There are 4 configuration scenarios for SRAM - * - * 1. Bootloader build: - * - We cannot set the lock bit as we need to reconfigure it again for the application. - * We configure PMP to cover entire valid IRAM and DRAM range. - * - * 2. Application build with CONFIG_ESP_SYSTEM_MEMPROT enabled + * 1. Application build with CONFIG_ESP_SYSTEM_MEMPROT enabled * - We split the SRAM into IRAM and DRAM such that IRAM region cannot be written to - * and DRAM region cannot be executed. We use _iram_text_end and _data_start markers to set the boundaries. + * and DRAM region cannot be executed. We use the _iram_text_end marker to set the boundary. * We also lock these entries so the R/W/X permissions are enforced even for machine mode * - * 3. Application build with CONFIG_ESP_SYSTEM_MEMPROT disabled + * 2. Application build with CONFIG_ESP_SYSTEM_MEMPROT disabled * - The IRAM-DRAM split is not enabled so we just need to ensure that access to only valid address ranges are successful * so for that we set PMP to cover entire valid IRAM and DRAM region. * We also lock these entries so the R/W/X permissions are enforced even for machine mode * - * 4. CPU is in OCD debug mode + * 3. CPU is in OCD debug mode * - The IRAM-DRAM split is not enabled so that OpenOCD can write and execute from IRAM. * We set PMP to cover entire valid IRAM and DRAM region. * We also lock these entries so the R/W/X permissions are enforced even for machine mode @@ -142,35 +128,50 @@ void esp_cpu_configure_region_protection(void) __attribute__((unused)) const unsigned RX = PMP_L | PMP_R | PMP_X; __attribute__((unused)) const unsigned RWX = PMP_L | PMP_R | PMP_W | PMP_X; - // - // Configure all the invalid address regions using PMA - // - esp_cpu_configure_invalid_regions(); - - // - // Configure all the valid address regions using PMP - // + /* Reset every PMP entry before programming: the bootloader configures no PMP, so the + * registers hold whatever the ROM left, and PMP_ENTRY_SET only ORs configuration bits. + * ESP32-H4 is not known to support overlapping PMP regions, so the reset-and-set macros + * cannot be used for TOR entries (their transient pmpaddr=0 creates a wrapping overlap); + * instead all entries are disabled up front and programmed in ascending index order, so no + * overlapping configuration - transient or final - is ever visible. + */ + PMP_ENTRY_CFG_RESET(0); + PMP_ENTRY_CFG_RESET(1); + PMP_ENTRY_CFG_RESET(2); + PMP_ENTRY_CFG_RESET(3); + PMP_ENTRY_CFG_RESET(4); + PMP_ENTRY_CFG_RESET(5); + PMP_ENTRY_CFG_RESET(6); + PMP_ENTRY_CFG_RESET(7); + PMP_ENTRY_CFG_RESET(8); + PMP_ENTRY_CFG_RESET(9); + PMP_ENTRY_CFG_RESET(10); + PMP_ENTRY_CFG_RESET(11); + PMP_ENTRY_CFG_RESET(12); + PMP_ENTRY_CFG_RESET(13); + PMP_ENTRY_CFG_RESET(14); + PMP_ENTRY_CFG_RESET(15); // 1. CPU Subsystem region - contains debug mode code and interrupt config registers const uint32_t pmpaddr0 = PMPADDR_NAPOT(SOC_CPU_SUBSYSTEM_LOW, SOC_CPU_SUBSYSTEM_HIGH); PMP_ENTRY_SET(0, pmpaddr0, PMP_NAPOT | RWX); _Static_assert(SOC_CPU_SUBSYSTEM_LOW < SOC_CPU_SUBSYSTEM_HIGH, "Invalid CPU subsystem region"); - // 2. I/D-ROM (ROM-Cache) -#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && !BOOTLOADER_BUILD + // 2. I/D-ROM +#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP const uint32_t drom_start = (uint32_t) (ets_rom_layout_p->drom_start); - if ((drom_start & (SOC_CPU_PMP_REGION_GRANULARITY - 1)) == 0) { - // We can skip configuring the PMP entry for the [SOC_IROM_MASK_LOW - drom_start] - // region as RX, as we already have configured a PMA entry with RX permissions for the - // [SOC_IROM_MASK_LOW - SOC_DROM_MASK_HIGH] region that helps us to also configure - // the region as cacheable. Thus, we save on one PMP entry. - PMP_ENTRY_SET(1, drom_start, NONE); - PMP_ENTRY_SET(2, SOC_DROM_MASK_HIGH, PMP_TOR | R); + if (((drom_start & (SOC_CPU_PMP_REGION_GRANULARITY - 1)) == 0) + && (drom_start > SOC_IROM_MASK_LOW) && (drom_start < SOC_DROM_MASK_HIGH)) { + // Split the ROM into text (RX) and data (R), fully PMP-matched (this target has spare + // entries, so the ROM text is not left PMP-unmatched as on ESP32-C5/S31). + PMP_ENTRY_SET(1, SOC_IROM_MASK_LOW, NONE); + PMP_ENTRY_SET(2, drom_start, PMP_TOR | RX); + PMP_ENTRY_SET(3, SOC_DROM_MASK_HIGH, PMP_TOR | R); } else #endif { PMP_ENTRY_SET(1, SOC_IROM_MASK_LOW, NONE); - PMP_ENTRY_SET(2, SOC_IROM_MASK_HIGH, PMP_TOR | CONDITIONAL_RX); + PMP_ENTRY_SET(2, SOC_IROM_MASK_HIGH, PMP_TOR | RX); _Static_assert(SOC_IROM_MASK_LOW < SOC_IROM_MASK_HIGH, "Invalid I/D-ROM region"); } @@ -179,57 +180,83 @@ void esp_cpu_configure_region_protection(void) // Anti-FI check that cpu is really in ocd mode ESP_FAULT_ASSERT(esp_cpu_dbgr_is_attached()); - PMP_ENTRY_SET(3, SOC_IRAM_LOW, NONE); - PMP_ENTRY_SET(4, SOC_HP_RAM_HIGH, PMP_TOR | RWX); + PMP_ENTRY_SET(4, SOC_IRAM_LOW, NONE); + PMP_ENTRY_SET(5, SOC_HP_RAM_HIGH, PMP_TOR | RWX); _Static_assert(SOC_IRAM_LOW < SOC_IRAM_HIGH, "Invalid RAM region"); } else { -#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && !BOOTLOADER_BUILD +#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP extern int _iram_text_end; - PMP_ENTRY_CFG_RESET(3); - PMP_ENTRY_CFG_RESET(4); - PMP_ENTRY_CFG_RESET(5); - PMP_ENTRY_SET(3, SOC_IRAM_LOW, NONE); - PMP_ENTRY_SET(4, (int)&_iram_text_end, PMP_TOR | RX); - PMP_ENTRY_SET(5, SOC_HP_RAM_HIGH, PMP_TOR | RW); + PMP_ENTRY_SET(4, SOC_IRAM_LOW, NONE); + PMP_ENTRY_SET(5, (int)&_iram_text_end, PMP_TOR | RX); + PMP_ENTRY_SET(6, SOC_HP_RAM_HIGH, PMP_TOR | RW); #else - PMP_ENTRY_SET(3, SOC_IRAM_LOW, CONDITIONAL_NONE); - PMP_ENTRY_SET(4, SOC_HP_RAM_HIGH, PMP_TOR | CONDITIONAL_RWX); + PMP_ENTRY_SET(4, SOC_IRAM_LOW, NONE); + PMP_ENTRY_SET(5, SOC_HP_RAM_HIGH, PMP_TOR | RWX); _Static_assert(SOC_IRAM_LOW < SOC_IRAM_HIGH, "Invalid RAM region"); #endif } - // 4. I_Cache / D_Cache (flash) -#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && !BOOTLOADER_BUILD + // 4. External memory window (unified I/D-Cache aperture for flash and PSRAM) +#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP extern int _instruction_reserved_end; extern int _rodata_reserved_end; const uint32_t page_aligned_irom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)(&_instruction_reserved_end)); __attribute__((unused)) const uint32_t page_aligned_drom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)(&_rodata_reserved_end)); - PMP_ENTRY_SET(6, SOC_IROM_LOW, NONE); + PMP_ENTRY_SET(7, SOC_IROM_LOW, NONE); + +/** + Virtual space layout (single window shared by flash and PSRAM mappings): + + _________ <- SOC_IROM_LOW + | | + |_______| <- _instruction_reserved_end + |_______| <- page_aligned_irom_resv_end + | | + |_______| <- _rodata_reserved_end + |_______| <- page_aligned_drom_resv_end + | | + | | + | | + |_______| <- page_aligned_drom_resv_end + available_psram_heap + | | + | | + | | + | | + |_______| <- SOC_DROM_HIGH + + if CONFIG_SPIRAM_FETCH_INSTRUCTIONS: [_instruction_reserved_end, page_aligned_irom_resv_end) in heap (RW) + if CONFIG_SPIRAM_RODATA: [_rodata_reserved_end, page_aligned_drom_resv_end) in heap (RW) + if CONFIG_SPIRAM: [_rodata_reserved_end, page_aligned_drom_resv_end + available_psram_heap] in heap / reserved for mapping (RW) +*/ #if CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION - PMP_ENTRY_SET(7, (uint32_t)(&_instruction_reserved_end), PMP_TOR | RX); - PMP_ENTRY_SET(8, page_aligned_irom_resv_end, PMP_TOR | RW); + PMP_ENTRY_SET(8, (uint32_t)(&_instruction_reserved_end), PMP_TOR | RX); + PMP_ENTRY_SET(9, page_aligned_irom_resv_end, PMP_TOR | RW); #else - PMP_ENTRY_SET(7, page_aligned_irom_resv_end, PMP_TOR | RX); - PMP_ENTRY_SET(8, page_aligned_irom_resv_end, NONE); + PMP_ENTRY_SET(8, page_aligned_irom_resv_end, PMP_TOR | RX); + PMP_ENTRY_SET(9, page_aligned_irom_resv_end, NONE); #endif /* CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION */ #if CONFIG_SPIRAM_RODATA && CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION - PMP_ENTRY_SET(9, (uint32_t)(&_rodata_reserved_end), PMP_TOR | R); + PMP_ENTRY_SET(10, (uint32_t)(&_rodata_reserved_end), PMP_TOR | R); #else - PMP_ENTRY_SET(9, page_aligned_drom_resv_end, PMP_TOR | R); + PMP_ENTRY_SET(10, page_aligned_drom_resv_end, PMP_TOR | R); #endif /* CONFIG_SPIRAM_RODATA && CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION */ #if CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION - size_t available_psram_heap = esp_psram_get_heap_size_to_protect(); - PMP_ENTRY_SET(10, ALIGN_UP(page_aligned_drom_resv_end + available_psram_heap, SOC_CPU_PMP_REGION_GRANULARITY), PMP_TOR | RW); + const size_t available_psram_heap = esp_psram_get_heap_size_to_protect(); + PMP_ENTRY_SET(11, ESP_ALIGN_UP(page_aligned_drom_resv_end + available_psram_heap, SOC_CPU_PMP_REGION_GRANULARITY), PMP_TOR | RW); #endif /* CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION */ #else - const uint32_t pmpaddr6 = PMPADDR_NAPOT(SOC_IROM_LOW, SOC_IROM_HIGH); - // Add the W attribute in the case of PSRAM - PMP_ENTRY_SET(6, pmpaddr6, PMP_NAPOT | CONDITIONAL_RWX); + const uint32_t pmpaddr7 = PMPADDR_NAPOT(SOC_IROM_LOW, SOC_IROM_HIGH); +#if CONFIG_SPIRAM + // The W permission is needed since PSRAM data is mapped into this window as well + PMP_ENTRY_SET(7, pmpaddr7, PMP_NAPOT | RWX); +#else + PMP_ENTRY_SET(7, pmpaddr7, PMP_NAPOT | RX); +#endif _Static_assert(SOC_IROM_LOW < SOC_IROM_HIGH, "Invalid I/D_Cache region"); #endif @@ -238,3 +265,28 @@ void esp_cpu_configure_region_protection(void) PMP_ENTRY_SET(15, pmpaddr15, PMP_NAPOT | RW); _Static_assert(SOC_PERIPHERAL_LOW < SOC_PERIPHERAL_HIGH, "Invalid peripheral region"); } +#endif // BOOTLOADER_BUILD + +void esp_cpu_configure_region_protection(void) +{ + /* Notes on implementation: + * + * 1) ESP32-H4 supports 16 PMA and 16 PMP regions. PMA blocks the invalid address ranges and + * marks the external memory window as cacheable; PMP maps the valid address space bottom + * to top with NAPOT and TOR entries, the peripherals using the last entry. + * + * 2) Unlike ESP32-S31/P4, external flash and PSRAM share a single 32 MB window + * ([SOC_IROM_LOW, SOC_IROM_HIGH)), so the PSRAM sections are carved out of that window. + * There is no LP/RTC RAM on this target, so no LP memory entries are needed. + */ + + /* The invalid (PMA) regions are configured by both the bootloader and the application; the valid + * (PMP) regions by the application only, since an unlocked PMP entry does not constrain M-mode. + * Only the application locks either, so the bootloader freezes nothing and the whole PMA/PMP + * layout stays application-owned - an implementation detail, not an ABI. */ + esp_cpu_configure_invalid_regions(); + +#ifndef BOOTLOADER_BUILD + esp_cpu_configure_valid_regions(); +#endif +} diff --git a/tools/test_apps/system/panic/panic_base/pytest_panic.py b/tools/test_apps/system/panic/panic_base/pytest_panic.py index dbfd36807ca..470807134ea 100644 --- a/tools/test_apps/system/panic/panic_base/pytest_panic.py +++ b/tools/test_apps/system/panic/panic_base/pytest_panic.py @@ -705,8 +705,9 @@ CONFIGS_MEMPROT_SPIRAM_XIP_IROM_ALIGNMENT_HEAP = list( 'memprot_spiram_xip_esp32c61', 'memprot_spiram_xip_esp32p4', 'memprot_spiram_xip_esp32s31', + 'memprot_spiram_xip_esp32h4', ], - ['esp32c5', 'esp32c61', 'esp32p4', 'esp32s31'], + ['esp32c5', 'esp32c61', 'esp32p4', 'esp32s31', 'esp32h4'], ) ) + [ ('memprot_spiram_xip_esp32p4_rev_less_than_v3', 'esp32p4', P4_REV_LESS_THAN_V3_MARKER), @@ -728,8 +729,9 @@ CONFIGS_MEMPROT_SPIRAM_XIP_DROM_ALIGNMENT_HEAP = list( 'memprot_spiram_xip_esp32c61', 'memprot_spiram_xip_esp32p4', 'memprot_spiram_xip_esp32s31', + 'memprot_spiram_xip_esp32h4', ], - ['esp32s3', 'esp32c5', 'esp32c61', 'esp32p4', 'esp32s31'], + ['esp32s3', 'esp32c5', 'esp32c61', 'esp32p4', 'esp32s31', 'esp32h4'], ) ) + [ ('memprot_spiram_xip_esp32p4_rev_less_than_v3', 'esp32p4', P4_REV_LESS_THAN_V3_MARKER), @@ -752,8 +754,9 @@ CONFIGS_MEMPROT_INVALID_REGION_PROTECTION_USING_PMA = list( 'memprot_esp32p4', 'memprot_esp32h21', 'memprot_esp32s31', + 'memprot_esp32h4', ], - ['esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h21', 'esp32s31'], + ['esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h21', 'esp32s31', 'esp32h4'], ) ) + [ ('memprot_esp32p4_rev_less_than_v3', 'esp32p4', P4_REV_LESS_THAN_V3_MARKER), diff --git a/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_spiram_xip_esp32h4 b/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_spiram_xip_esp32h4 new file mode 100644 index 00000000000..4e47dd311c8 --- /dev/null +++ b/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_spiram_xip_esp32h4 @@ -0,0 +1,13 @@ +# Restricting to ESP32H4 +CONFIG_IDF_TARGET="esp32h4" + +# Enabling memory protection +CONFIG_ESP_SYSTEM_MEMPROT=y + +# Enable memprot test +CONFIG_TEST_MEMPROT=y + +# Enable SPIRAM to check the alignment gap's memory protection +CONFIG_SPIRAM=y +CONFIG_SPIRAM_USE_CAPS_ALLOC=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y