From b5891edb9b2c1734e9f131134e37c4b4553c8cd6 Mon Sep 17 00:00:00 2001 From: "nilesh.kale" Date: Thu, 4 Jun 2026 14:49:01 +0530 Subject: [PATCH 1/6] feat: enable cpu region protection for esp32s31 --- .../port/esp32s31/cpu_region_protect.c | 301 ++++++++++++++++-- .../esp32s31/private_include/pmp_layout.h | 56 ++++ components/soc/esp32s31/include/soc/soc.h | 9 +- .../system/panic/panic_base/pytest_panic.py | 22 +- .../panic_base/sdkconfig.ci.memprot_esp32s31 | 8 + .../sdkconfig.ci.memprot_spiram_xip_esp32s31 | 13 + 6 files changed, 371 insertions(+), 38 deletions(-) create mode 100644 components/esp_hw_support/port/esp32s31/private_include/pmp_layout.h create mode 100644 tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_esp32s31 create mode 100644 tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_spiram_xip_esp32s31 diff --git a/components/esp_hw_support/port/esp32s31/cpu_region_protect.c b/components/esp_hw_support/port/esp32s31/cpu_region_protect.c index 17506593ae5..bc9be39ba87 100644 --- a/components/esp_hw_support/port/esp32s31/cpu_region_protect.c +++ b/components/esp_hw_support/port/esp32s31/cpu_region_protect.c @@ -9,42 +9,287 @@ #include "soc/soc.h" #include "esp_cpu.h" #include "esp_fault.h" -#include "hal/cache_ll.h" -#include "riscv/csr.h" -#include "esp_macros.h" -#if !BOOTLOADER_BUILD && CONFIG_SPIRAM -#include "esp_private/esp_psram_extram.h" -#endif /* !BOOTLOADER_BUILD && CONFIG_SPIRAM */ +#include "esp32s31/rom/rom_layout.h" +#include "pmp_layout.h" -/* TODO: [ESP32S31] IDF-15238 */ +#define ALIGN_UP_TO_MMU_PAGE_SIZE(addr) (((addr) + (SOC_MMU_PAGE_SIZE) - 1) & ~((SOC_MMU_PAGE_SIZE) - 1)) -#ifdef BOOTLOADER_BUILD -// Without L bit set -#define CONDITIONAL_NONE 0x0 -#define CONDITIONAL_R PMP_R -#define CONDITIONAL_RX PMP_R | PMP_X -#define CONDITIONAL_RW PMP_R | PMP_W -#define CONDITIONAL_RWX PMP_R | PMP_W | PMP_X +static void esp_cpu_configure_invalid_regions(void) +{ + const unsigned PMA_NONE = PMA_L | PMA_EN; + __attribute__((unused)) const unsigned PMA_RW = PMA_L | PMA_EN | PMA_R | PMA_W; + __attribute__((unused)) const unsigned PMA_RX = PMA_L | PMA_EN | PMA_R | PMA_X; + __attribute__((unused)) const unsigned PMA_RWX = 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. + // The reset-and-set PMA macros are safe to use even though ESP32-S31 does not tolerate + // overlapping *PMP* regions: PMA configures memory attributes (not locked protection), so + // the transient pmpaddr=0 inside the macro does not raise a fault here (same as ESP32-H4). + + // 0. Gap at bottom of address space + PMA_RESET_AND_ENTRY_SET_NAPOT(0, 0, SOC_CPU_SUBSYSTEM_LOW, PMA_NAPOT | PMA_NONE); + + // [SOC_CPU_SUBSYSTEM_LOW .. SOC_RTC_IRAM_HIGH) is all valid (CPU subsystem, on-chip + // peripherals and the LP TCM) and is configured using PMP below, so no PMA entry is needed. + + // 1. Gap between LP TCM (RTC SRAM) and HP TCM (internal SRAM) + PMA_RESET_AND_ENTRY_SET_TOR(1, SOC_RTC_IRAM_HIGH, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(2, SOC_IRAM_LOW, PMA_TOR | PMA_NONE); + + // 2. Gap between HP TCM (internal SRAM) and ROM-Cache + PMA_RESET_AND_ENTRY_SET_TOR(3, SOC_IRAM_HIGH, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(4, SOC_IROM_MASK_LOW, PMA_TOR | PMA_NONE); + + // 3. ROM has configured the ROM region to be cacheable, lock the configuration as RX. + // Configuring it here (as a valid RX region) lets the I/D-ROM PMP entries below use the + // DROM-split optimization and save a PMP entry. + PMA_RESET_AND_ENTRY_SET_TOR(5, SOC_IROM_MASK_LOW, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(6, SOC_DROM_MASK_HIGH, PMA_TOR | PMA_RX); + + // 4. Gap between ROM-Cache and External flash (I/D-Cache) + PMA_RESET_AND_ENTRY_SET_TOR(7, SOC_DROM_MASK_HIGH, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(8, SOC_IROM_LOW, PMA_TOR | PMA_NONE); + + // 5. ROM has configured the external flash MSPI region with RX permission; lock it as RX and + // make it cacheable. This is a valid region but is configured using PMA (instead of only + // PMP) because the cacheable attribute can only be set through PMA. + PMA_RESET_AND_ENTRY_SET_NAPOT(9, SOC_IROM_LOW, (SOC_IROM_HIGH - SOC_IROM_LOW), PMA_NAPOT | PMA_RX); + + // 6. Gap between External flash (I/D-Cache) and External PSRAM + PMA_RESET_AND_ENTRY_SET_TOR(10, SOC_IROM_HIGH, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(11, SOC_EXTRAM_LOW, PMA_TOR | PMA_NONE); + + // 7. ROM has configured the external PSRAM MSPI region with RX permission; add the W attribute + // and lock it, and make it cacheable. As above, this valid region is configured using PMA + // so that it can be marked cacheable (RWX so that PSRAM data and XIP-from-PSRAM both work). + PMA_RESET_AND_ENTRY_SET_NAPOT(12, SOC_EXTRAM_LOW, (SOC_EXTRAM_HIGH - SOC_EXTRAM_LOW), PMA_NAPOT | PMA_RWX); + + // 8. Non-cacheable (cache-bypass) alias of External PSRAM at +SOC_NON_CACHEABLE_OFFSET_PSRAM, + // used by GDMA to reach PSRAM-resident descriptors/buffers coherently. Marked RW at a higher + // priority (lower index) than the catch-all below so it stays a valid region. + PMA_RESET_AND_ENTRY_SET_NAPOT(13, (uint32_t)SOC_EXTRAM_LOW + (uint32_t)SOC_NON_CACHEABLE_OFFSET_PSRAM, (SOC_EXTRAM_HIGH - SOC_EXTRAM_LOW), PMA_NAPOT | PMA_RW); + + // 9. End of address space (everything above External PSRAM except the non-cacheable alias above) + PMA_RESET_AND_ENTRY_SET_TOR(14, SOC_EXTRAM_HIGH, PMA_NONE); + PMA_RESET_AND_ENTRY_SET_TOR(15, UINT32_MAX, PMA_TOR | PMA_NONE); +} + +#ifndef BOOTLOADER_BUILD +static void esp_cpu_configure_valid_regions(void) +{ + /* There are 3 configuration scenarios for SRAM + * + * 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 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 + * + * 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 + * + * 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 + */ + const unsigned NONE = PMP_L; + __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; + + /* Reset every PMP entry before programming it. + * + * The bootloader no longer configures any PMP entry, so on entry to the application each PMP + * register still holds whatever the ROM left in it. PMP_ENTRY_SET only ORs configuration bits, + * so stale permission bits must be cleared first (e.g. an entry that must end up as NONE would + * otherwise keep any R/W/X bit the ROM set). + * + * ESP32-S31 does not support overlapping PMP regions, so the per-entry PMP_RESET_AND_ENTRY_SET + * macro cannot be used for TOR entries (the transient pmpaddr=0 it writes creates a fleeting + * wrapping overlap that faults on this CPU). Instead every entry is reset to a disabled state + * up front and the entries are then programmed strictly in ascending-address order, so each TOR + * base is already in place before the entry that uses it is enabled, and no overlapping + * intermediate configuration 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 pmpaddr_cpu_subsystem = PMPADDR_NAPOT(SOC_CPU_SUBSYSTEM_LOW, SOC_CPU_SUBSYSTEM_HIGH); + PMP_ENTRY_SET(PMP_ENTRY_CPU_SUBSYSTEM, pmpaddr_cpu_subsystem, 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 + 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 also makes it cacheable. Thus, we + // save on one PMP entry. + PMP_ENTRY_SET(PMP_ENTRY_ROM_LOW, drom_start, NONE); + PMP_ENTRY_SET(PMP_ENTRY_ROM_HIGH, SOC_DROM_MASK_HIGH, PMP_TOR | R); + } else +#endif + { + PMP_ENTRY_SET(PMP_ENTRY_ROM_LOW, SOC_IROM_MASK_LOW, NONE); + PMP_ENTRY_SET(PMP_ENTRY_ROM_HIGH, SOC_IROM_MASK_HIGH, PMP_TOR | RX); + _Static_assert(SOC_IROM_MASK_LOW < SOC_IROM_MASK_HIGH, "Invalid I/D-ROM region"); + } + + // 3. IRAM and DRAM (HP TCM) + if (esp_cpu_dbgr_is_attached()) { + // 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_IRAM_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 + extern int _iram_text_end; + PMP_ENTRY_SET(3, SOC_IRAM_LOW, NONE); + PMP_ENTRY_SET(4, (int)&_iram_text_end, PMP_TOR | RX); + PMP_ENTRY_SET(5, SOC_DRAM_HIGH, PMP_TOR | RW); #else -// With L bit set -#define CONDITIONAL_NONE NONE -#define CONDITIONAL_R R -#define CONDITIONAL_RX RX -#define CONDITIONAL_RW RW -#define CONDITIONAL_RWX RWX + PMP_ENTRY_SET(3, SOC_IRAM_LOW, NONE); + PMP_ENTRY_SET(4, SOC_IRAM_HIGH, PMP_TOR | RWX); + _Static_assert(SOC_IRAM_LOW < SOC_IRAM_HIGH, "Invalid RAM region"); +#endif + } + + // 4. I_Cache / D_Cache (external flash) + // The marker-based split uses _instruction_reserved_end / _rodata_reserved_end, which lie in + // the flash aperture only when .text/.rodata are flash-resident. Under XIP-from-PSRAM they + // move into PSRAM, so this higher-priority flash entry would shadow the PSRAM RWX entry + // (section 5) and make the heap-reused page-alignment gaps read-only. So split only when + // flash-resident; otherwise map the flash aperture as a single RX window and let section 5 + // govern the XIP code/rodata/gaps. +#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && !CONFIG_SPIRAM_FETCH_INSTRUCTIONS && !CONFIG_SPIRAM_RODATA + 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)); + 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, page_aligned_irom_resv_end, PMP_TOR | RX); + PMP_ENTRY_SET(8, page_aligned_drom_resv_end, PMP_TOR | R); +#else + const uint32_t pmpaddr_flash = PMPADDR_NAPOT(SOC_IROM_LOW, SOC_IROM_HIGH); + PMP_ENTRY_SET(6, pmpaddr_flash, PMP_NAPOT | RX); + _Static_assert(SOC_IROM_LOW < SOC_IROM_HIGH, "Invalid I/D_Cache region"); #endif -#define ALIGN_UP_TO_MMU_PAGE_SIZE(addr) ESP_ALIGN_UP(addr, SOC_MMU_PAGE_SIZE) -#define ALIGN_DOWN_TO_MMU_PAGE_SIZE(addr) ESP_ALIGN_DOWN(addr, SOC_MMU_PAGE_SIZE) + // 5. I_EXTRAM / D_EXTRAM (external PSRAM). The PMA entry configured above already makes this + // region cacheable; the PSRAM cache-bypass alias is kept valid by PMA (an address unmatched + // by PMP is allowed by PMP in M-mode, so it needs no extra PMP entry). +#if CONFIG_SPIRAM +#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && (CONFIG_SPIRAM_FETCH_INSTRUCTIONS || CONFIG_SPIRAM_RODATA) + /* XIP-from-PSRAM: .text is executed directly out of PSRAM, so enforce W^X instead of a single + * RWX window. Map [EXTRAM_LOW, _instruction_reserved_end) as RX (code) and the remainder + * (rodata, the page-alignment gaps and the reclaimed heap) as RW - i.e. non-executable - so an + * execute access into the rodata / alignment-gap region faults (Instruction access fault), as + * the memory-protection tests expect. A full P4-style per-section split (separate R rodata and + * heap-only entries) does not fit the 16-entry PMP budget once the LP split takes 4 entries, but + * this two-region split already enforces W^X. It uses entries 7-8 (left free by the flash + * aperture's single-entry mapping under XIP) plus entry 9, programmed in ascending order. */ + extern int _instruction_reserved_end; + PMP_ENTRY_SET(7, SOC_EXTRAM_LOW, NONE); + PMP_ENTRY_SET(8, (uint32_t)(&_instruction_reserved_end), PMP_TOR | RX); + PMP_ENTRY_SET(9, SOC_EXTRAM_HIGH, PMP_TOR | RW); + _Static_assert(SOC_EXTRAM_LOW < SOC_EXTRAM_HIGH, "Invalid I/D_EXTRAM region"); +#else + /* PSRAM used as data only (no XIP): a single RWX NAPOT window. */ + const uint32_t pmpaddr_extram = PMPADDR_NAPOT(SOC_EXTRAM_LOW, SOC_EXTRAM_HIGH); + PMP_ENTRY_SET(9, pmpaddr_extram, PMP_NAPOT | RWX); + _Static_assert(SOC_EXTRAM_LOW < SOC_EXTRAM_HIGH, "Invalid I/D_EXTRAM region"); +#endif +#endif /* CONFIG_SPIRAM */ + + // 6. LP memory (LP TCM / RTC SRAM) +#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP + extern int _rtc_text_start; + extern int _rtc_text_end; + /* LP TCM split into three regions (mirroring ESP32-P4 / ESP32-C6): + * [LOW, _rtc_text_start) RW : RTC-reserved mem + ULP/LP-core image (the HP core writes + * the program here, so it must stay writable). + * [_rtc_text_start, _rtc_text_end) RX : HP core rtc.text (e.g. deep-sleep wake stub). + * [_rtc_text_end, HIGH) RW : RTC data. + * Programmed in ascending-address order (S31 faults on overlapping PMP entries); every entry was + * already reset to a disabled state at the top of this function. */ + PMP_ENTRY_SET(10, SOC_RTC_IRAM_LOW, NONE); +#if CONFIG_ESP_SYSTEM_MEMPROT_PMP_LP_CORE_RESERVE_MEM_EXEC + PMP_ENTRY_SET(11, (int)&_rtc_text_start, PMP_TOR | RWX); +#else + PMP_ENTRY_SET(11, (int)&_rtc_text_start, PMP_TOR | RW); +#endif + PMP_ENTRY_SET(12, (int)&_rtc_text_end, PMP_TOR | RX); + PMP_ENTRY_SET(13, SOC_RTC_IRAM_HIGH, PMP_TOR | RW); +#else + const uint32_t pmpaddr_rtc = PMPADDR_NAPOT(SOC_RTC_IRAM_LOW, SOC_RTC_IRAM_HIGH); + PMP_ENTRY_SET(10, pmpaddr_rtc, PMP_NAPOT | RWX); + _Static_assert(SOC_RTC_IRAM_LOW < SOC_RTC_IRAM_HIGH, "Invalid RTC IRAM region"); +#endif + + // 7. Peripheral addresses (on-chip peripherals, CPU peripheral, cache-data memory and the + // debug address space). The LP peripherals are contiguous with the rest and are covered by + // this single window as well. Peripherals use the last PMP entries (14-15) so that the entry + // count used by the regions above (which varies with the build configuration) never collides. + // Both indices are frozen by pmp_layout.h. + PMP_ENTRY_SET(PMP_ENTRY_PERIPHERAL_LOW, SOC_PERIPHERAL_LOW, NONE); + PMP_ENTRY_SET(PMP_ENTRY_PERIPHERAL_HIGH, SOC_PERIPHERAL_HIGH, PMP_TOR | RW); + _Static_assert(SOC_PERIPHERAL_LOW < SOC_PERIPHERAL_HIGH, "Invalid peripheral region"); +} +#endif // BOOTLOADER_BUILD void esp_cpu_configure_region_protection(void) { - /* TODO: [ESP32S31] IDF-15238 */ + /* Notes on implementation: + * + * 1) Note: ESP32-S31 CPU doesn't support overlapping PMP regions. Hence, like ESP32-H4, we + * must avoid the reset-and-set PMP macros for TOR entries (the transient pmpaddr=0 inside + * them creates a fleeting wrapping overlap that faults on this CPU). We use the non-reset + * PMP_ENTRY_SET and reset the relevant entries up front using PMP_ENTRY_CFG_RESET. + * + * 2) ESP32-S31 supports 16 PMA regions so we use this feature to block all the invalid address + * ranges and to mark the external flash/PSRAM and ROM regions as cacheable. + * + * 3) ESP32-S31 supports 16 PMP regions (writing pmpaddr>=16 raises an illegal-instruction fault, + * confirmed on rev v0.0 silicon). We use a combination of NAPOT (Naturally Aligned Power Of Two) + * and TOR (top of range) entries to map all the valid address space, bottom to top. The whole + * region set must therefore fit within entries 0-15, with the peripherals using the last entry. + * + * 4) Unlike ESP32-P4, the internal memories on ESP32-S31 are not aliased into a cached/uncached + * address pair, so each region needs only a single PMP entry (no CACHED_AND_UNCACHED helper). + */ - __attribute__((unused)) const unsigned PMA_RW = PMA_L | PMA_EN | PMA_R | PMA_W; - __attribute__((unused)) const unsigned PMA_RX = PMA_L | PMA_EN | PMA_R | PMA_X; - __attribute__((unused)) const unsigned PMA_RWX = PMA_L | PMA_EN | PMA_R | PMA_W | PMA_X; + /* The invalid (PMA) regions are configured in both the bootloader and the application. The valid + * (PMP) regions are configured and locked by the application only: an unlocked PMP entry does not + * constrain machine mode anyway, and a bootloader-locked entry could not be reconfigured until the + * next CPU reset - which would freeze the entry layout into a bootloader <-> application ABI. The + * bootloader therefore programs no PMP entry and the application owns and freezes the whole PMP + * layout (see pmp_layout.h). */ + esp_cpu_configure_invalid_regions(); - //without setting this, psram cannot be reached - PMA_RESET_AND_ENTRY_SET_NAPOT(7, SOC_EXTRAM_LOW, (SOC_EXTRAM_HIGH - SOC_EXTRAM_LOW), PMA_NAPOT | PMA_RWX); +#ifndef BOOTLOADER_BUILD + esp_cpu_configure_valid_regions(); +#endif } diff --git a/components/esp_hw_support/port/esp32s31/private_include/pmp_layout.h b/components/esp_hw_support/port/esp32s31/private_include/pmp_layout.h new file mode 100644 index 00000000000..08f644cf1ea --- /dev/null +++ b/components/esp_hw_support/port/esp32s31/private_include/pmp_layout.h @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "soc/soc.h" +#include "esp_assert.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * ESP32-S31 PMP entry layout: a bootloader <-> application ABI. + * + * A PMP entry that the (non-updatable) second stage bootloader locks cannot be reconfigured until + * the next CPU reset, so the index of any entry a shipped bootloader locks would be frozen for the + * lifetime of that device. To avoid such drift, the ESP-IDF bootloader for ESP32-S31 configures no + * PMP entry at all (it configures only PMA); the application programs and locks the entire PMP + * layout during early startup (see cpu_region_protect.c). + * + * No bootloader that locks a PMP entry has ever shipped for this target, so today no index is + * constrained by a deployed device. The boundary entries below are nonetheless frozen as a + * forward-compatibility contract and pinned with ESP_STATIC_ASSERTs, so a locked entry a future + * bootloader or a custom (non-ESP-IDF) application might rely on can never be silently renumbered. + * + * Frozen entries: + * 0 : CPU subsystem (NAPOT RWX) + * 1 - 2 : I/D-ROM (ROM-Cache) TOR pair + * 14 - 15 : peripherals TOR pair (on-chip peripherals, CPU peripheral, cache-data memory, + * LP peripherals and the debug address space) + * + * Entries 3 - 13 are application-owned (the SRAM IRAM/DRAM split, the flash I/D-cache split, the + * external PSRAM region and the LP-TCM split). Their count varies with the build configuration, + * they are programmed by plain index in cpu_region_protect.c, and they are NOT part of the ABI - + * they may change between versions. + */ +#define PMP_ENTRY_CPU_SUBSYSTEM 0 +#define PMP_ENTRY_ROM_LOW 1 +#define PMP_ENTRY_ROM_HIGH 2 +#define PMP_ENTRY_PERIPHERAL_LOW 14 +#define PMP_ENTRY_PERIPHERAL_HIGH 15 + +ESP_STATIC_ASSERT(PMP_ENTRY_CPU_SUBSYSTEM == 0 + && PMP_ENTRY_ROM_LOW == 1 + && PMP_ENTRY_ROM_HIGH == 2 + && PMP_ENTRY_PERIPHERAL_LOW == 14 + && PMP_ENTRY_PERIPHERAL_HIGH == 15, + "ESP32-S31 PMP entries that a bootloader may lock are a frozen ABI and must never move"); + +#ifdef __cplusplus +} +#endif diff --git a/components/soc/esp32s31/include/soc/soc.h b/components/soc/esp32s31/include/soc/soc.h index e32604a5f4c..dc47bc6003d 100644 --- a/components/soc/esp32s31/include/soc/soc.h +++ b/components/soc/esp32s31/include/soc/soc.h @@ -193,9 +193,12 @@ #define CPU_PERIPH_LOW 0x2C000000 #define CPU_PERIPH_HIGH 0x2C020000 -// Region of address space that holds peripherals, HP APB peripherals -#define SOC_PERIPHERAL_LOW 0x50000000 //TODO need update -#define SOC_PERIPHERAL_HIGH 0x50100000 //TODO need update +// Region of address space that holds the on-chip peripherals (MODEM/HP/LP APB +// peripherals, security peripherals, CPU peripheral, cache-data memory and the +// debug address space). Per the S31 bus address map this spans from the start of +// the "On-Chip Peripherals" aperture up to the base of the LP TCM (SOC_RTC_IRAM_LOW). +#define SOC_PERIPHERAL_LOW 0x20000000 +#define SOC_PERIPHERAL_HIGH 0x2E000000 /** LP subsystem from ``LP_SYS`` through ``LP_DAC``*/ #define SOC_LP_PERIPH_LOW DR_REG_LP_SYS_BASE 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 adfdbb438af..52f4ca51c30 100644 --- a/tools/test_apps/system/panic/panic_base/pytest_panic.py +++ b/tools/test_apps/system/panic/panic_base/pytest_panic.py @@ -625,8 +625,9 @@ CONFIGS_MEMPROT_IDRAM = list( 'memprot_esp32h2', 'memprot_esp32p4', 'memprot_esp32h21', + 'memprot_esp32s31', ], - ['esp32s2', 'esp32c3', 'esp32c2', 'esp32c5', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h21'], + ['esp32s2', 'esp32c3', 'esp32c2', 'esp32c5', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h21', 'esp32s31'], ) ) + [ ('memprot_esp32p4_rev_less_than_v3', 'esp32p4', P4_REV_LESS_THAN_V3_MARKER), @@ -646,8 +647,9 @@ CONFIGS_MEMPROT_RTC_FAST_MEM = list( 'memprot_esp32h2', 'memprot_esp32p4', 'memprot_esp32h21', + 'memprot_esp32s31', ], - ['esp32s2', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32h2', 'esp32p4', 'esp32h21'], + ['esp32s2', 'esp32c3', 'esp32c5', 'esp32c6', 'esp32h2', 'esp32p4', 'esp32h21', 'esp32s31'], ) ) + [ ('memprot_esp32p4_rev_less_than_v3', 'esp32p4', P4_REV_LESS_THAN_V3_MARKER), @@ -664,8 +666,9 @@ CONFIGS_MEMPROT_FLASH_IDROM = list( 'memprot_esp32h2', 'memprot_esp32p4', 'memprot_esp32h21', + 'memprot_esp32s31', ], - ['esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h21'], + ['esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h21', 'esp32s31'], ) ) + [ ('memprot_esp32p4_rev_less_than_v3', 'esp32p4', P4_REV_LESS_THAN_V3_MARKER), @@ -679,8 +682,9 @@ CONFIGS_MEMPROT_SPIRAM_XIP_IROM_ALIGNMENT_HEAP = list( 'memprot_spiram_xip_esp32c5', 'memprot_spiram_xip_esp32c61', 'memprot_spiram_xip_esp32p4', + 'memprot_spiram_xip_esp32s31', ], - ['esp32c5', 'esp32c61', 'esp32p4'], + ['esp32c5', 'esp32c61', 'esp32p4', 'esp32s31'], ) ) + [ ('memprot_spiram_xip_esp32p4_rev_less_than_v3', 'esp32p4', P4_REV_LESS_THAN_V3_MARKER), @@ -701,8 +705,9 @@ CONFIGS_MEMPROT_SPIRAM_XIP_DROM_ALIGNMENT_HEAP = list( 'memprot_spiram_xip_esp32c5', 'memprot_spiram_xip_esp32c61', 'memprot_spiram_xip_esp32p4', + 'memprot_spiram_xip_esp32s31', ], - ['esp32s3', 'esp32c5', 'esp32c61', 'esp32p4'], + ['esp32s3', 'esp32c5', 'esp32c61', 'esp32p4', 'esp32s31'], ) ) + [ ('memprot_spiram_xip_esp32p4_rev_less_than_v3', 'esp32p4', P4_REV_LESS_THAN_V3_MARKER), @@ -724,8 +729,9 @@ CONFIGS_MEMPROT_INVALID_REGION_PROTECTION_USING_PMA = list( 'memprot_esp32h2', 'memprot_esp32p4', 'memprot_esp32h21', + 'memprot_esp32s31', ], - ['esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h21'], + ['esp32c5', 'esp32c6', 'esp32c61', 'esp32h2', 'esp32p4', 'esp32h21', 'esp32s31'], ) ) + [ ('memprot_esp32p4_rev_less_than_v3', 'esp32p4', P4_REV_LESS_THAN_V3_MARKER), @@ -972,7 +978,7 @@ def test_rtc_fast_reg1_execute_violation(dut: PanicTestDut, test_func_name: str) @pytest.mark.generic @pytest.mark.temp_skip( - targets=['esp32c5', 'esp32c6', 'esp32h2', 'esp32p4', 'esp32h21'], + targets=['esp32c5', 'esp32c6', 'esp32h2', 'esp32p4', 'esp32h21', 'esp32s31'], reason='Not a violation condition, no PMS peripheral cases', ) @idf_parametrize('config, target', CONFIGS_MEMPROT_RTC_FAST_MEM, indirect=['config', 'target']) @@ -1144,6 +1150,7 @@ def spiram_xip_irom_alignment_reg_execute_violation(dut: PanicTestDut, test_func @pytest.mark.generic +@pytest.mark.temp_skip_ci(targets=['esp32s31'], reason='no PSRAM-equipped runner for v0.0 silicon yet') @idf_parametrize('config, target', CONFIGS_MEMPROT_SPIRAM_XIP_IROM_ALIGNMENT_HEAP, indirect=['config', 'target']) def test_spiram_xip_irom_alignment_reg_execute_violation(dut: PanicTestDut, test_func_name: str) -> None: spiram_xip_irom_alignment_reg_execute_violation(dut, test_func_name) @@ -1168,6 +1175,7 @@ def spiram_xip_drom_alignment_reg_execute_violation(dut: PanicTestDut, test_func @pytest.mark.generic +@pytest.mark.temp_skip_ci(targets=['esp32s31'], reason='no PSRAM-equipped runner for v0.0 silicon yet') @idf_parametrize('config, target', CONFIGS_MEMPROT_SPIRAM_XIP_DROM_ALIGNMENT_HEAP, indirect=['config', 'target']) def test_spiram_xip_drom_alignment_reg_execute_violation(dut: PanicTestDut, test_func_name: str) -> None: spiram_xip_drom_alignment_reg_execute_violation(dut, test_func_name) diff --git a/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_esp32s31 b/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_esp32s31 new file mode 100644 index 00000000000..5eb52cface4 --- /dev/null +++ b/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_esp32s31 @@ -0,0 +1,8 @@ +# Restricting to ESP32S31 +CONFIG_IDF_TARGET="esp32s31" + +# Enabling memory protection +CONFIG_ESP_SYSTEM_MEMPROT=y + +# Enable memprot test +CONFIG_TEST_MEMPROT=y diff --git a/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_spiram_xip_esp32s31 b/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_spiram_xip_esp32s31 new file mode 100644 index 00000000000..5a746fb49c8 --- /dev/null +++ b/tools/test_apps/system/panic/panic_base/sdkconfig.ci.memprot_spiram_xip_esp32s31 @@ -0,0 +1,13 @@ +# Restricting to ESP32S31 +CONFIG_IDF_TARGET="esp32s31" + +# 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 From 3ee34cdac543ab5fe34809d416e363180f93add8 Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Thu, 6 Aug 2026 12:27:35 +0530 Subject: [PATCH 2/6] change(esp_hw_support): drop the frozen PMP entry layout for ESP32-S31 Freezing PMP entry indices as a bootloader<->application ABI (pmp_layout.h) is only needed on targets where an already-shipped bootloader locks PMP entries (C5, C6, C61, H2, P4). No such bootloader has ever shipped for ESP32-S31 and, per the v6.2 PMP ownership policy, the bootloader never configures or locks any PMP entry on this target - so there is no ABI to freeze. Program all entries by plain index (as on ESP32-H4/H21) and state that the application-owned layout is not an ABI. --- .../port/esp32s31/cpu_region_protect.c | 60 ++++++++++--------- .../esp32s31/private_include/pmp_layout.h | 56 ----------------- 2 files changed, 33 insertions(+), 83 deletions(-) delete mode 100644 components/esp_hw_support/port/esp32s31/private_include/pmp_layout.h diff --git a/components/esp_hw_support/port/esp32s31/cpu_region_protect.c b/components/esp_hw_support/port/esp32s31/cpu_region_protect.c index bc9be39ba87..9ac7d1bf824 100644 --- a/components/esp_hw_support/port/esp32s31/cpu_region_protect.c +++ b/components/esp_hw_support/port/esp32s31/cpu_region_protect.c @@ -10,16 +10,24 @@ #include "esp_cpu.h" #include "esp_fault.h" #include "esp32s31/rom/rom_layout.h" -#include "pmp_layout.h" +#include "esp_macros.h" -#define ALIGN_UP_TO_MMU_PAGE_SIZE(addr) (((addr) + (SOC_MMU_PAGE_SIZE) - 1) & ~((SOC_MMU_PAGE_SIZE) - 1)) +#define ALIGN_UP_TO_MMU_PAGE_SIZE(addr) ESP_ALIGN_UP(addr, SOC_MMU_PAGE_SIZE) static void esp_cpu_configure_invalid_regions(void) { - const unsigned PMA_NONE = PMA_L | PMA_EN; - __attribute__((unused)) const unsigned PMA_RW = PMA_L | PMA_EN | PMA_R | PMA_W; - __attribute__((unused)) const unsigned PMA_RX = PMA_L | PMA_EN | PMA_R | PMA_X; - __attribute__((unused)) const unsigned PMA_RWX = 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. // The reset-and-set PMA macros are safe to use even though ESP32-S31 does not tolerate @@ -40,7 +48,7 @@ static void esp_cpu_configure_invalid_regions(void) PMA_RESET_AND_ENTRY_SET_TOR(3, SOC_IRAM_HIGH, PMA_NONE); PMA_RESET_AND_ENTRY_SET_TOR(4, SOC_IROM_MASK_LOW, PMA_TOR | PMA_NONE); - // 3. ROM has configured the ROM region to be cacheable, lock the configuration as RX. + // 3. ROM has configured the ROM region to be cacheable, keep the configuration as RX. // Configuring it here (as a valid RX region) lets the I/D-ROM PMP entries below use the // DROM-split optimization and save a PMP entry. PMA_RESET_AND_ENTRY_SET_TOR(5, SOC_IROM_MASK_LOW, PMA_NONE); @@ -50,7 +58,7 @@ static void esp_cpu_configure_invalid_regions(void) PMA_RESET_AND_ENTRY_SET_TOR(7, SOC_DROM_MASK_HIGH, PMA_NONE); PMA_RESET_AND_ENTRY_SET_TOR(8, SOC_IROM_LOW, PMA_TOR | PMA_NONE); - // 5. ROM has configured the external flash MSPI region with RX permission; lock it as RX and + // 5. ROM has configured the external flash MSPI region with RX permission; keep it RX and // make it cacheable. This is a valid region but is configured using PMA (instead of only // PMP) because the cacheable attribute can only be set through PMA. PMA_RESET_AND_ENTRY_SET_NAPOT(9, SOC_IROM_LOW, (SOC_IROM_HIGH - SOC_IROM_LOW), PMA_NAPOT | PMA_RX); @@ -60,8 +68,8 @@ static void esp_cpu_configure_invalid_regions(void) PMA_RESET_AND_ENTRY_SET_TOR(11, SOC_EXTRAM_LOW, PMA_TOR | PMA_NONE); // 7. ROM has configured the external PSRAM MSPI region with RX permission; add the W attribute - // and lock it, and make it cacheable. As above, this valid region is configured using PMA - // so that it can be marked cacheable (RWX so that PSRAM data and XIP-from-PSRAM both work). + // and make it cacheable. As above, this valid region is configured using PMA so that it can + // be marked cacheable (RWX so that PSRAM data and XIP-from-PSRAM both work). PMA_RESET_AND_ENTRY_SET_NAPOT(12, SOC_EXTRAM_LOW, (SOC_EXTRAM_HIGH - SOC_EXTRAM_LOW), PMA_NAPOT | PMA_RWX); // 8. Non-cacheable (cache-bypass) alias of External PSRAM at +SOC_NON_CACHEABLE_OFFSET_PSRAM, @@ -110,9 +118,10 @@ static void esp_cpu_configure_valid_regions(void) * ESP32-S31 does not support overlapping PMP regions, so the per-entry PMP_RESET_AND_ENTRY_SET * macro cannot be used for TOR entries (the transient pmpaddr=0 it writes creates a fleeting * wrapping overlap that faults on this CPU). Instead every entry is reset to a disabled state - * up front and the entries are then programmed strictly in ascending-address order, so each TOR - * base is already in place before the entry that uses it is enabled, and no overlapping - * intermediate configuration is ever visible. + * up front (so no region is active while programming) and the entries are then programmed in + * ascending index order, so each TOR base address is already in place before the entry that + * uses it is enabled. The final regions themselves are disjoint, so no overlapping + * configuration - transient or final - is ever visible. */ PMP_ENTRY_CFG_RESET(0); PMP_ENTRY_CFG_RESET(1); @@ -133,7 +142,7 @@ static void esp_cpu_configure_valid_regions(void) // 1. CPU Subsystem region - contains debug mode code and interrupt config registers const uint32_t pmpaddr_cpu_subsystem = PMPADDR_NAPOT(SOC_CPU_SUBSYSTEM_LOW, SOC_CPU_SUBSYSTEM_HIGH); - PMP_ENTRY_SET(PMP_ENTRY_CPU_SUBSYSTEM, pmpaddr_cpu_subsystem, PMP_NAPOT | RWX); + PMP_ENTRY_SET(0, pmpaddr_cpu_subsystem, PMP_NAPOT | RWX); _Static_assert(SOC_CPU_SUBSYSTEM_LOW < SOC_CPU_SUBSYSTEM_HIGH, "Invalid CPU subsystem region"); // 2. I/D-ROM (ROM-Cache) @@ -144,13 +153,13 @@ static void esp_cpu_configure_valid_regions(void) // 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 also makes it cacheable. Thus, we // save on one PMP entry. - PMP_ENTRY_SET(PMP_ENTRY_ROM_LOW, drom_start, NONE); - PMP_ENTRY_SET(PMP_ENTRY_ROM_HIGH, SOC_DROM_MASK_HIGH, PMP_TOR | R); + PMP_ENTRY_SET(1, drom_start, NONE); + PMP_ENTRY_SET(2, SOC_DROM_MASK_HIGH, PMP_TOR | R); } else #endif { - PMP_ENTRY_SET(PMP_ENTRY_ROM_LOW, SOC_IROM_MASK_LOW, NONE); - PMP_ENTRY_SET(PMP_ENTRY_ROM_HIGH, SOC_IROM_MASK_HIGH, PMP_TOR | RX); + 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/D-ROM region"); } @@ -253,9 +262,8 @@ static void esp_cpu_configure_valid_regions(void) // debug address space). The LP peripherals are contiguous with the rest and are covered by // this single window as well. Peripherals use the last PMP entries (14-15) so that the entry // count used by the regions above (which varies with the build configuration) never collides. - // Both indices are frozen by pmp_layout.h. - PMP_ENTRY_SET(PMP_ENTRY_PERIPHERAL_LOW, SOC_PERIPHERAL_LOW, NONE); - PMP_ENTRY_SET(PMP_ENTRY_PERIPHERAL_HIGH, SOC_PERIPHERAL_HIGH, PMP_TOR | RW); + PMP_ENTRY_SET(14, SOC_PERIPHERAL_LOW, NONE); + PMP_ENTRY_SET(15, SOC_PERIPHERAL_HIGH, PMP_TOR | RW); _Static_assert(SOC_PERIPHERAL_LOW < SOC_PERIPHERAL_HIGH, "Invalid peripheral region"); } #endif // BOOTLOADER_BUILD @@ -281,12 +289,10 @@ void esp_cpu_configure_region_protection(void) * address pair, so each region needs only a single PMP entry (no CACHED_AND_UNCACHED helper). */ - /* The invalid (PMA) regions are configured in both the bootloader and the application. The valid - * (PMP) regions are configured and locked by the application only: an unlocked PMP entry does not - * constrain machine mode anyway, and a bootloader-locked entry could not be reconfigured until the - * next CPU reset - which would freeze the entry layout into a bootloader <-> application ABI. The - * bootloader therefore programs no PMP entry and the application owns and freezes the whole PMP - * layout (see pmp_layout.h). */ + /* 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 diff --git a/components/esp_hw_support/port/esp32s31/private_include/pmp_layout.h b/components/esp_hw_support/port/esp32s31/private_include/pmp_layout.h deleted file mode 100644 index 08f644cf1ea..00000000000 --- a/components/esp_hw_support/port/esp32s31/private_include/pmp_layout.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#pragma once - -#include "soc/soc.h" -#include "esp_assert.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * ESP32-S31 PMP entry layout: a bootloader <-> application ABI. - * - * A PMP entry that the (non-updatable) second stage bootloader locks cannot be reconfigured until - * the next CPU reset, so the index of any entry a shipped bootloader locks would be frozen for the - * lifetime of that device. To avoid such drift, the ESP-IDF bootloader for ESP32-S31 configures no - * PMP entry at all (it configures only PMA); the application programs and locks the entire PMP - * layout during early startup (see cpu_region_protect.c). - * - * No bootloader that locks a PMP entry has ever shipped for this target, so today no index is - * constrained by a deployed device. The boundary entries below are nonetheless frozen as a - * forward-compatibility contract and pinned with ESP_STATIC_ASSERTs, so a locked entry a future - * bootloader or a custom (non-ESP-IDF) application might rely on can never be silently renumbered. - * - * Frozen entries: - * 0 : CPU subsystem (NAPOT RWX) - * 1 - 2 : I/D-ROM (ROM-Cache) TOR pair - * 14 - 15 : peripherals TOR pair (on-chip peripherals, CPU peripheral, cache-data memory, - * LP peripherals and the debug address space) - * - * Entries 3 - 13 are application-owned (the SRAM IRAM/DRAM split, the flash I/D-cache split, the - * external PSRAM region and the LP-TCM split). Their count varies with the build configuration, - * they are programmed by plain index in cpu_region_protect.c, and they are NOT part of the ABI - - * they may change between versions. - */ -#define PMP_ENTRY_CPU_SUBSYSTEM 0 -#define PMP_ENTRY_ROM_LOW 1 -#define PMP_ENTRY_ROM_HIGH 2 -#define PMP_ENTRY_PERIPHERAL_LOW 14 -#define PMP_ENTRY_PERIPHERAL_HIGH 15 - -ESP_STATIC_ASSERT(PMP_ENTRY_CPU_SUBSYSTEM == 0 - && PMP_ENTRY_ROM_LOW == 1 - && PMP_ENTRY_ROM_HIGH == 2 - && PMP_ENTRY_PERIPHERAL_LOW == 14 - && PMP_ENTRY_PERIPHERAL_HIGH == 15, - "ESP32-S31 PMP entries that a bootloader may lock are a frozen ABI and must never move"); - -#ifdef __cplusplus -} -#endif From a03ec273f0cb2ea4e789791877673c2394e95c5e Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Thu, 6 Aug 2026 21:39:24 +0530 Subject: [PATCH 3/6] fix(esp_hw_support): fix ESP32-S31 CPU lockup with flash encryption during PMA setup Run esp_cpu_configure_invalid_regions() from IRAM: it reprograms the PMA entry that keeps the flash aperture cacheable, so an instruction fetched from flash inside the reprogramming window bypasses the cache and, with flash encryption enabled, executes ciphertext (illegal instruction -> CPU lockup). Also move the PSRAM PMA entry to index 7 so that the application layout matches the entry earlier bootloaders programmed and locked, keeping the full layout effective under such bootloaders. --- .../port/esp32s31/cpu_region_protect.c | 80 +++++++++++-------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/components/esp_hw_support/port/esp32s31/cpu_region_protect.c b/components/esp_hw_support/port/esp32s31/cpu_region_protect.c index 9ac7d1bf824..c96a1480441 100644 --- a/components/esp_hw_support/port/esp32s31/cpu_region_protect.c +++ b/components/esp_hw_support/port/esp32s31/cpu_region_protect.c @@ -14,7 +14,7 @@ #define ALIGN_UP_TO_MMU_PAGE_SIZE(addr) ESP_ALIGN_UP(addr, SOC_MMU_PAGE_SIZE) -static void esp_cpu_configure_invalid_regions(void) +NOINLINE_ATTR IRAM_ATTR static void esp_cpu_configure_invalid_regions(void) { #ifdef BOOTLOADER_BUILD /* Don't lock PMA entries in the bootloader: an enabled entry constrains M-mode even without @@ -29,57 +29,73 @@ static void esp_cpu_configure_invalid_regions(void) __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. - // The reset-and-set PMA macros are safe to use even though ESP32-S31 does not tolerate - // overlapping *PMP* regions: PMA configures memory attributes (not locked protection), so - // the transient pmpaddr=0 inside the macro does not raise a fault here (same as ESP32-H4). + /* 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); // [SOC_CPU_SUBSYSTEM_LOW .. SOC_RTC_IRAM_HIGH) is all valid (CPU subsystem, on-chip // peripherals and the LP TCM) and is configured using PMP below, so no PMA entry is needed. // 1. Gap between LP TCM (RTC SRAM) and HP TCM (internal SRAM) - PMA_RESET_AND_ENTRY_SET_TOR(1, SOC_RTC_IRAM_HIGH, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(2, SOC_IRAM_LOW, PMA_TOR | PMA_NONE); + PMA_ENTRY_SET_TOR(1, SOC_RTC_IRAM_HIGH, PMA_NONE); + PMA_ENTRY_SET_TOR(2, SOC_IRAM_LOW, PMA_TOR | PMA_NONE); // 2. Gap between HP TCM (internal SRAM) and ROM-Cache - PMA_RESET_AND_ENTRY_SET_TOR(3, SOC_IRAM_HIGH, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(4, SOC_IROM_MASK_LOW, PMA_TOR | PMA_NONE); + PMA_ENTRY_SET_TOR(3, SOC_IRAM_HIGH, PMA_NONE); + PMA_ENTRY_SET_TOR(4, SOC_IROM_MASK_LOW, PMA_TOR | PMA_NONE); // 3. ROM has configured the ROM region to be cacheable, keep the configuration as RX. // Configuring it here (as a valid RX region) lets the I/D-ROM PMP entries below use the // DROM-split optimization and save a PMP entry. - PMA_RESET_AND_ENTRY_SET_TOR(5, SOC_IROM_MASK_LOW, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(6, SOC_DROM_MASK_HIGH, PMA_TOR | PMA_RX); + PMA_ENTRY_SET_TOR(5, SOC_IROM_MASK_LOW, PMA_NONE); + PMA_ENTRY_SET_TOR(6, SOC_DROM_MASK_HIGH, PMA_TOR | PMA_RX); - // 4. Gap between ROM-Cache and External flash (I/D-Cache) - PMA_RESET_AND_ENTRY_SET_TOR(7, SOC_DROM_MASK_HIGH, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(8, SOC_IROM_LOW, PMA_TOR | PMA_NONE); - - // 5. ROM has configured the external flash MSPI region with RX permission; keep it RX and - // make it cacheable. This is a valid region but is configured using PMA (instead of only - // PMP) because the cacheable attribute can only be set through PMA. - PMA_RESET_AND_ENTRY_SET_NAPOT(9, SOC_IROM_LOW, (SOC_IROM_HIGH - SOC_IROM_LOW), PMA_NAPOT | PMA_RX); - - // 6. Gap between External flash (I/D-Cache) and External PSRAM - PMA_RESET_AND_ENTRY_SET_TOR(10, SOC_IROM_HIGH, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(11, SOC_EXTRAM_LOW, PMA_TOR | PMA_NONE); - - // 7. ROM has configured the external PSRAM MSPI region with RX permission; add the W attribute + // 4. ROM has configured the external PSRAM MSPI region with RX permission; add the W attribute // and make it cacheable. As above, this valid region is configured using PMA so that it can // be marked cacheable (RWX so that PSRAM data and XIP-from-PSRAM both work). - PMA_RESET_AND_ENTRY_SET_NAPOT(12, SOC_EXTRAM_LOW, (SOC_EXTRAM_HIGH - SOC_EXTRAM_LOW), PMA_NAPOT | PMA_RWX); + PMA_ENTRY_SET_NAPOT(7, SOC_EXTRAM_LOW, (SOC_EXTRAM_HIGH - SOC_EXTRAM_LOW), PMA_NAPOT | PMA_RWX); + + // 5. Gap between ROM-Cache and External flash (I/D-Cache) + PMA_ENTRY_SET_TOR(8, SOC_DROM_MASK_HIGH, PMA_NONE); + PMA_ENTRY_SET_TOR(9, SOC_IROM_LOW, PMA_TOR | PMA_NONE); + + // 6. ROM has configured the external flash MSPI region with RX permission; keep it RX and + // make it cacheable. This is a valid region but is configured using PMA (instead of only + // PMP) because the cacheable attribute can only be set through PMA. + PMA_ENTRY_SET_NAPOT(10, SOC_IROM_LOW, (SOC_IROM_HIGH - SOC_IROM_LOW), PMA_NAPOT | PMA_RX); + + // 7. Gap between External flash (I/D-Cache) and External PSRAM + PMA_ENTRY_SET_TOR(11, SOC_IROM_HIGH, PMA_NONE); + PMA_ENTRY_SET_TOR(12, SOC_EXTRAM_LOW, PMA_TOR | PMA_NONE); // 8. Non-cacheable (cache-bypass) alias of External PSRAM at +SOC_NON_CACHEABLE_OFFSET_PSRAM, // used by GDMA to reach PSRAM-resident descriptors/buffers coherently. Marked RW at a higher // priority (lower index) than the catch-all below so it stays a valid region. - PMA_RESET_AND_ENTRY_SET_NAPOT(13, (uint32_t)SOC_EXTRAM_LOW + (uint32_t)SOC_NON_CACHEABLE_OFFSET_PSRAM, (SOC_EXTRAM_HIGH - SOC_EXTRAM_LOW), PMA_NAPOT | PMA_RW); + PMA_ENTRY_SET_NAPOT(13, (uint32_t)SOC_EXTRAM_LOW + (uint32_t)SOC_NON_CACHEABLE_OFFSET_PSRAM, + (SOC_EXTRAM_HIGH - SOC_EXTRAM_LOW), PMA_NAPOT | PMA_RW); // 9. End of address space (everything above External PSRAM except the non-cacheable alias above) - PMA_RESET_AND_ENTRY_SET_TOR(14, SOC_EXTRAM_HIGH, PMA_NONE); - PMA_RESET_AND_ENTRY_SET_TOR(15, UINT32_MAX, PMA_TOR | PMA_NONE); + PMA_ENTRY_SET_TOR(14, SOC_EXTRAM_HIGH, PMA_NONE); + PMA_ENTRY_SET_TOR(15, UINT32_MAX, PMA_TOR | PMA_NONE); } #ifndef BOOTLOADER_BUILD @@ -93,8 +109,8 @@ static void esp_cpu_configure_valid_regions(void) * We also lock these entries so the R/W/X permissions are enforced even for machine mode * * 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. + * - 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 * * 3. CPU is in OCD debug mode From dae395527902a9881c0a0442f54b566320f3e6c1 Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Thu, 6 Aug 2026 21:39:31 +0530 Subject: [PATCH 4/6] fix(bootloader_support): do not gate application region protection on bootloader config CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE is bootloader-scoped; disabling it must not strip the application's PMP/PMA configuration. The application now always applies region protection. --- components/bootloader_support/src/bootloader_mem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/bootloader_support/src/bootloader_mem.c b/components/bootloader_support/src/bootloader_mem.c index 068ccdd955f..aed75e45a29 100644 --- a/components/bootloader_support/src/bootloader_mem.c +++ b/components/bootloader_support/src/bootloader_mem.c @@ -58,7 +58,7 @@ void bootloader_init_mem(void) } #endif -#if CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE +#if !BOOTLOADER_BUILD || CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE // protect memory region esp_cpu_configure_region_protection(); #endif From 50d1fb07734ba75d1f1233344c6431f22606afa2 Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Thu, 6 Aug 2026 21:41:59 +0530 Subject: [PATCH 5/6] fix(esp_hw_support): run ESP32-P4 PMA programming from IRAM The bootloader locks PMA entries on core 0 only (PMA is per-hart), so core 1 applies its PMA configuration with effective writes. Executing that from flash momentarily makes the flash aperture non-cacheable while reprogramming its entry, which executes ciphertext with flash encryption enabled (illegal instruction -> CPU lockup). --- components/esp_hw_support/port/esp32p4/cpu_region_protect.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esp_hw_support/port/esp32p4/cpu_region_protect.c b/components/esp_hw_support/port/esp32p4/cpu_region_protect.c index 93433940489..081c3ba2c8d 100644 --- a/components/esp_hw_support/port/esp32p4/cpu_region_protect.c +++ b/components/esp_hw_support/port/esp32p4/cpu_region_protect.c @@ -23,7 +23,7 @@ #define ALIGN_UP_TO_MMU_PAGE_SIZE(addr) ESP_ALIGN_UP(addr, SOC_MMU_PAGE_SIZE) #define ALIGN_DOWN_TO_MMU_PAGE_SIZE(addr) ESP_ALIGN_DOWN(addr, SOC_MMU_PAGE_SIZE) -static void esp_cpu_configure_invalid_regions(void) +NOINLINE_ATTR IRAM_ATTR static void esp_cpu_configure_invalid_regions(void) { const unsigned PMA_NONE = PMA_L | PMA_EN; __attribute__((unused)) const unsigned PMA_RW = PMA_L | PMA_EN | PMA_R | PMA_W; From 01b86f126968e20a3cbe0a230d94e97df77faf37 Mon Sep 17 00:00:00 2001 From: "harshal.patil" Date: Tue, 18 Aug 2026 15:51:18 +0530 Subject: [PATCH 6/6] change(esp_hw_support): harden the ESP32-S31 PSRAM region permissions Both PSRAM layouts were mapped as a single RWX window, so everything in external RAM - the heap included - was executable. PSRAM used as data only is now RW, and under XIP-from-PSRAM it is split per section as ESP32-P4 does: .text RX, .rodata read-only, and the MMU-page alignment gaps and the reclaimed heap RW, so neither is executable. Both describe the layout that esp_psram_init() produces, and the entries are locked, so - again as on ESP32-P4 - they are only narrowed when CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION says that layout applies. Without it the application owns the region and PSRAM stays RWX. The per-section entries cost one PMP entry more than the 16 available, so the CPU subsystem and peripheral windows are chained as TOR entries, taking one entry instead of three. soc.h is corrected against the S31 bus address map: the peripheral window base was 1 MB too low, and the LP peripheral top, derived from a register base plus a size rather than from the map, was 16 KB short. SOC_NON_CACHEABLE_OFFSET_FLASH is added. --- .../port/esp32s31/cpu_region_protect.c | 246 +++++++++++------- components/soc/esp32s31/include/soc/soc.h | 7 +- .../system/panic/panic_base/pytest_panic.py | 2 - 3 files changed, 150 insertions(+), 105 deletions(-) diff --git a/components/esp_hw_support/port/esp32s31/cpu_region_protect.c b/components/esp_hw_support/port/esp32s31/cpu_region_protect.c index c96a1480441..98b7041c680 100644 --- a/components/esp_hw_support/port/esp32s31/cpu_region_protect.c +++ b/components/esp_hw_support/port/esp32s31/cpu_region_protect.c @@ -11,6 +11,10 @@ #include "esp_fault.h" #include "esp32s31/rom/rom_layout.h" #include "esp_macros.h" +#include "esp_attr.h" +#if !BOOTLOADER_BUILD && CONFIG_SPIRAM +#include "esp_private/esp_psram_extram.h" +#endif /* !BOOTLOADER_BUILD && CONFIG_SPIRAM */ #define ALIGN_UP_TO_MMU_PAGE_SIZE(addr) ESP_ALIGN_UP(addr, SOC_MMU_PAGE_SIZE) @@ -53,7 +57,8 @@ NOINLINE_ATTR IRAM_ATTR static void esp_cpu_configure_invalid_regions(void) PMA_ENTRY_SET_NAPOT(0, 0, SOC_CPU_SUBSYSTEM_LOW, PMA_NAPOT | PMA_NONE); // [SOC_CPU_SUBSYSTEM_LOW .. SOC_RTC_IRAM_HIGH) is all valid (CPU subsystem, on-chip - // peripherals and the LP TCM) and is configured using PMP below, so no PMA entry is needed. + // peripherals and the LP TCM) and is configured using PMP below, so no PMA entry is needed. + // Due to less PMA entries we cannot configure all the invalid region gaps in this region. // 1. Gap between LP TCM (RTC SRAM) and HP TCM (internal SRAM) PMA_ENTRY_SET_TOR(1, SOC_RTC_IRAM_HIGH, PMA_NONE); @@ -65,37 +70,38 @@ NOINLINE_ATTR IRAM_ATTR static void esp_cpu_configure_invalid_regions(void) // 3. ROM has configured the ROM region to be cacheable, keep the configuration as RX. // Configuring it here (as a valid RX region) lets the I/D-ROM PMP entries below use the - // DROM-split optimization and save a PMP entry. - PMA_ENTRY_SET_TOR(5, SOC_IROM_MASK_LOW, PMA_NONE); - PMA_ENTRY_SET_TOR(6, SOC_DROM_MASK_HIGH, PMA_TOR | PMA_RX); + // DROM-split optimization and save a PMP entry. This region's base is entry 4's address. + PMA_ENTRY_SET_TOR(5, SOC_DROM_MASK_HIGH, PMA_TOR | PMA_RX); - // 4. ROM has configured the external PSRAM MSPI region with RX permission; add the W attribute - // and make it cacheable. As above, this valid region is configured using PMA so that it can - // be marked cacheable (RWX so that PSRAM data and XIP-from-PSRAM both work). - PMA_ENTRY_SET_NAPOT(7, SOC_EXTRAM_LOW, (SOC_EXTRAM_HIGH - SOC_EXTRAM_LOW), PMA_NAPOT | PMA_RWX); + // 4. Gap between ROM-Cache and External flash (I/D-Cache) + PMA_ENTRY_SET_TOR(6, SOC_IROM_LOW, PMA_TOR | PMA_NONE); - // 5. Gap between ROM-Cache and External flash (I/D-Cache) - PMA_ENTRY_SET_TOR(8, SOC_DROM_MASK_HIGH, PMA_NONE); - PMA_ENTRY_SET_TOR(9, SOC_IROM_LOW, PMA_TOR | PMA_NONE); + // 5. External flash (I/D-Cache): kept RX and made cacheable. The cacheable attribute can only + // be set through PMA, which is why this valid region is configured here and not only by PMP. + PMA_ENTRY_SET_TOR(7, SOC_IROM_HIGH, PMA_TOR | PMA_RX); - // 6. ROM has configured the external flash MSPI region with RX permission; keep it RX and - // make it cacheable. This is a valid region but is configured using PMA (instead of only - // PMP) because the cacheable attribute can only be set through PMA. - PMA_ENTRY_SET_NAPOT(10, SOC_IROM_LOW, (SOC_IROM_HIGH - SOC_IROM_LOW), PMA_NAPOT | PMA_RX); + // 6. Gap between External flash and External PSRAM. This range is decoded as flash but lies + // beyond the MMU-mappable window, so it would alias onto the mapped flash: deny it. + PMA_ENTRY_SET_TOR(8, SOC_EXTRAM_LOW, PMA_TOR | PMA_NONE); - // 7. Gap between External flash (I/D-Cache) and External PSRAM - PMA_ENTRY_SET_TOR(11, SOC_IROM_HIGH, PMA_NONE); - PMA_ENTRY_SET_TOR(12, SOC_EXTRAM_LOW, PMA_TOR | PMA_NONE); + // 7. External PSRAM: cacheable and RWX so that PSRAM data and XIP-from-PSRAM both work. + PMA_ENTRY_SET_TOR(9, SOC_EXTRAM_HIGH, PMA_TOR | PMA_RWX); - // 8. Non-cacheable (cache-bypass) alias of External PSRAM at +SOC_NON_CACHEABLE_OFFSET_PSRAM, - // used by GDMA to reach PSRAM-resident descriptors/buffers coherently. Marked RW at a higher - // priority (lower index) than the catch-all below so it stays a valid region. - PMA_ENTRY_SET_NAPOT(13, (uint32_t)SOC_EXTRAM_LOW + (uint32_t)SOC_NON_CACHEABLE_OFFSET_PSRAM, - (SOC_EXTRAM_HIGH - SOC_EXTRAM_LOW), PMA_NAPOT | PMA_RW); + // 8. Gap between External PSRAM and the cache-bypass aliases + PMA_ENTRY_SET_TOR(10, (uint32_t)SOC_IROM_LOW + (uint32_t)SOC_NON_CACHEABLE_OFFSET_FLASH, PMA_TOR | PMA_NONE); - // 9. End of address space (everything above External PSRAM except the non-cacheable alias above) - PMA_ENTRY_SET_TOR(14, SOC_EXTRAM_HIGH, PMA_NONE); - PMA_ENTRY_SET_TOR(15, UINT32_MAX, PMA_TOR | PMA_NONE); + // 9. Non-cacheable (cache-bypass) alias of External flash + PMA_ENTRY_SET_TOR(11, (uint32_t)SOC_IROM_HIGH + (uint32_t)SOC_NON_CACHEABLE_OFFSET_FLASH, PMA_TOR | PMA_RX); + + // 10. Gap between the flash and PSRAM cache-bypass aliases + PMA_ENTRY_SET_TOR(12, (uint32_t)SOC_EXTRAM_LOW + (uint32_t)SOC_NON_CACHEABLE_OFFSET_PSRAM, PMA_TOR | PMA_NONE); + + // 11. Non-cacheable (cache-bypass) alias of External PSRAM, used by GDMA to reach + // PSRAM-resident descriptors/buffers coherently + PMA_ENTRY_SET_TOR(13, (uint32_t)SOC_EXTRAM_HIGH + (uint32_t)SOC_NON_CACHEABLE_OFFSET_PSRAM, PMA_TOR | PMA_RW); + + // 12. End of address space + PMA_ENTRY_SET_TOR(14, UINT32_MAX, PMA_TOR | PMA_NONE); } #ifndef BOOTLOADER_BUILD @@ -109,8 +115,8 @@ static void esp_cpu_configure_valid_regions(void) * We also lock these entries so the R/W/X permissions are enforced even for machine mode * * 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. + * - 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 * * 3. CPU is in OCD debug mode @@ -156,10 +162,24 @@ static void esp_cpu_configure_valid_regions(void) 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 pmpaddr_cpu_subsystem = PMPADDR_NAPOT(SOC_CPU_SUBSYSTEM_LOW, SOC_CPU_SUBSYSTEM_HIGH); - PMP_ENTRY_SET(0, pmpaddr_cpu_subsystem, PMP_NAPOT | RWX); + // 1. CPU Subsystem region - contains debug mode code and interrupt config registers - and the + // peripherals (on-chip peripherals, CPU peripheral, cache-data memory, the debug address + // space and the LP peripherals, which are all contiguous with each other). + // + // These two are programmed as a TOR chain rather than as separate NAPOT/TOR groups so that + // the peripheral window costs a single entry: entry 0's region is [0, SOC_CPU_SUBSYSTEM_HIGH) + // (its TOR base is implicitly 0) and entry 1 then chains off entry 0's address. That frees + // the entry the PSRAM section split below needs. The [0, SOC_CPU_SUBSYSTEM_LOW) part that + // entry 0 over-covers is denied by PMA, so it stays inaccessible. + // + // The peripheral window must not be widened to a NAPOT: rounding its top up + // would cover ROM, HP TCM and LP TCM, and in particular the ROM text that the DROM-split + // optimisation below deliberately leaves PMP-unmatched, which would then lose execute + // permission and lock the CPU up on the first ROM fetch. + PMP_ENTRY_SET(0, SOC_CPU_SUBSYSTEM_HIGH, PMP_TOR | RWX); + PMP_ENTRY_SET(1, SOC_PERIPHERAL_HIGH, PMP_TOR | RW); _Static_assert(SOC_CPU_SUBSYSTEM_LOW < SOC_CPU_SUBSYSTEM_HIGH, "Invalid CPU subsystem region"); + _Static_assert(SOC_PERIPHERAL_LOW < SOC_PERIPHERAL_HIGH, "Invalid peripheral region"); // 2. I/D-ROM (ROM-Cache) #if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP @@ -168,14 +188,15 @@ static void esp_cpu_configure_valid_regions(void) // 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 also makes it 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); + // save on one PMP entry. Note that this leaves the ROM text PMP-unmatched on purpose (an + // address unmatched by PMP is allowed in M-mode), so no later entry may cover it. + PMP_ENTRY_SET(2, drom_start, NONE); + 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 | RX); + PMP_ENTRY_SET(2, SOC_IROM_MASK_LOW, NONE); + PMP_ENTRY_SET(3, SOC_IROM_MASK_HIGH, PMP_TOR | RX); _Static_assert(SOC_IROM_MASK_LOW < SOC_IROM_MASK_HIGH, "Invalid I/D-ROM region"); } @@ -184,103 +205,128 @@ static void esp_cpu_configure_valid_regions(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_IRAM_HIGH, PMP_TOR | RWX); + const uint32_t pmpaddr_sram = PMPADDR_NAPOT(SOC_IRAM_LOW, SOC_IRAM_HIGH); + PMP_ENTRY_SET(4, pmpaddr_sram, PMP_NAPOT | RWX); _Static_assert(SOC_IRAM_LOW < SOC_IRAM_HIGH, "Invalid RAM region"); } else { #if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP extern int _iram_text_end; - PMP_ENTRY_SET(3, SOC_IRAM_LOW, NONE); - PMP_ENTRY_SET(4, (int)&_iram_text_end, PMP_TOR | RX); - PMP_ENTRY_SET(5, SOC_DRAM_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_DRAM_HIGH, PMP_TOR | RW); #else - PMP_ENTRY_SET(3, SOC_IRAM_LOW, NONE); - PMP_ENTRY_SET(4, SOC_IRAM_HIGH, PMP_TOR | RWX); + const uint32_t pmpaddr_sram = PMPADDR_NAPOT(SOC_IRAM_LOW, SOC_IRAM_HIGH); + PMP_ENTRY_SET(4, pmpaddr_sram, PMP_NAPOT | RWX); _Static_assert(SOC_IRAM_LOW < SOC_IRAM_HIGH, "Invalid RAM region"); #endif } - // 4. I_Cache / D_Cache (external flash) - // The marker-based split uses _instruction_reserved_end / _rodata_reserved_end, which lie in - // the flash aperture only when .text/.rodata are flash-resident. Under XIP-from-PSRAM they - // move into PSRAM, so this higher-priority flash entry would shadow the PSRAM RWX entry - // (section 5) and make the heap-reused page-alignment gaps read-only. So split only when - // flash-resident; otherwise map the flash aperture as a single RX window and let section 5 - // govern the XIP code/rodata/gaps. -#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && !CONFIG_SPIRAM_FETCH_INSTRUCTIONS && !CONFIG_SPIRAM_RODATA + // 4. External flash (I/D-Cache) and External PSRAM (I/D-EXTRAM) +#if CONFIG_SPIRAM && (CONFIG_SPIRAM_FETCH_INSTRUCTIONS || CONFIG_SPIRAM_RODATA) +#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION + /* XIP-from-PSRAM: split PSRAM by section, mirroring ESP32-P4. Flash needs no PMP entry here, + * the PMA entry above already grants it RX. Each combination of the two section markers is + * handled separately, since which markers exist decides which boundaries are valid. + * + * The reclaimed heap starts at the MMU page boundary after the last XIP section, because + * esp_mmu_map_reserve_block_with_caps() hands out MMU-page-aligned blocks from the free head. + * The heap top is therefore page_aligned_
_resv_end + heap size, not the unaligned + * section end. This split describes the layout that esp_psram_init() produces, so it is only + * programmed when CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION says that layout applies; + * otherwise PSRAM is left as one permissive window below, as on ESP32-P4. */ + const size_t available_psram_heap = esp_psram_get_heap_size_to_protect(); + + PMP_ENTRY_SET(7, SOC_EXTRAM_LOW, NONE); + +#if CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_RODATA 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)); - 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, page_aligned_irom_resv_end, PMP_TOR | RX); - PMP_ENTRY_SET(8, page_aligned_drom_resv_end, PMP_TOR | R); + PMP_ENTRY_SET(8, (uint32_t)(&_instruction_reserved_end), PMP_TOR | RX); + PMP_ENTRY_SET(9, page_aligned_irom_resv_end, PMP_TOR | RW); + PMP_ENTRY_SET(10, (uint32_t)(&_rodata_reserved_end), PMP_TOR | R); + const uint32_t page_aligned_drom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)(&_rodata_reserved_end)); + PMP_ENTRY_SET(11, ESP_ALIGN_UP(page_aligned_drom_resv_end + available_psram_heap, SOC_CPU_PMP_REGION_GRANULARITY), PMP_TOR | RW); + +#elif CONFIG_SPIRAM_FETCH_INSTRUCTIONS + /* Only .text moved to PSRAM; .rodata stays flash-resident. */ + extern int _instruction_reserved_end; + const uint32_t page_aligned_irom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)(&_instruction_reserved_end)); + + PMP_ENTRY_SET(8, (uint32_t)(&_instruction_reserved_end), PMP_TOR | RX); + PMP_ENTRY_SET(9, page_aligned_irom_resv_end, PMP_TOR | RW); + PMP_ENTRY_SET(10, ESP_ALIGN_UP(page_aligned_irom_resv_end + available_psram_heap, SOC_CPU_PMP_REGION_GRANULARITY), PMP_TOR | RW); + +#else /* CONFIG_SPIRAM_RODATA */ + /* Only .rodata moved to PSRAM; nothing in PSRAM is executable. */ + extern int _rodata_reserved_end; + + PMP_ENTRY_SET(8, (uint32_t)(&_rodata_reserved_end), PMP_TOR | R); + const uint32_t page_aligned_drom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)(&_rodata_reserved_end)); + PMP_ENTRY_SET(9, ESP_ALIGN_UP(page_aligned_drom_resv_end + available_psram_heap, SOC_CPU_PMP_REGION_GRANULARITY), PMP_TOR | RW); + _Static_assert(SOC_EXTRAM_LOW < SOC_EXTRAM_HIGH, "Invalid I/D_EXTRAM region"); +#endif +#else + /* Memory protection disabled, or the pre-configured PSRAM layout does not apply: a single + * permissive NAPOT window. It must carry X because .text is executed directly out of PSRAM + * in this configuration. */ + const uint32_t pmpaddr_extram = PMPADDR_NAPOT(SOC_EXTRAM_LOW, SOC_EXTRAM_HIGH); + PMP_ENTRY_SET(7, pmpaddr_extram, PMP_NAPOT | RWX); + _Static_assert(SOC_EXTRAM_LOW < SOC_EXTRAM_HIGH, "Invalid I/D_EXTRAM region"); +#endif +#else +#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP + extern int _instruction_reserved_end; + const uint32_t page_aligned_irom_resv_end = ALIGN_UP_TO_MMU_PAGE_SIZE((uint32_t)(&_instruction_reserved_end)); + + PMP_ENTRY_SET(7, SOC_IROM_LOW, NONE); + PMP_ENTRY_SET(8, page_aligned_irom_resv_end, PMP_TOR | RX); + PMP_ENTRY_SET(9, SOC_DROM_HIGH, PMP_TOR | R); + _Static_assert(SOC_IROM_LOW < SOC_IROM_HIGH, "Invalid I/D_Cache region"); #else const uint32_t pmpaddr_flash = PMPADDR_NAPOT(SOC_IROM_LOW, SOC_IROM_HIGH); - PMP_ENTRY_SET(6, pmpaddr_flash, PMP_NAPOT | RX); + PMP_ENTRY_SET(7, pmpaddr_flash, PMP_NAPOT | RX); _Static_assert(SOC_IROM_LOW < SOC_IROM_HIGH, "Invalid I/D_Cache region"); #endif - // 5. I_EXTRAM / D_EXTRAM (external PSRAM). The PMA entry configured above already makes this - // region cacheable; the PSRAM cache-bypass alias is kept valid by PMA (an address unmatched - // by PMP is allowed by PMP in M-mode, so it needs no extra PMP entry). #if CONFIG_SPIRAM -#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && (CONFIG_SPIRAM_FETCH_INSTRUCTIONS || CONFIG_SPIRAM_RODATA) - /* XIP-from-PSRAM: .text is executed directly out of PSRAM, so enforce W^X instead of a single - * RWX window. Map [EXTRAM_LOW, _instruction_reserved_end) as RX (code) and the remainder - * (rodata, the page-alignment gaps and the reclaimed heap) as RW - i.e. non-executable - so an - * execute access into the rodata / alignment-gap region faults (Instruction access fault), as - * the memory-protection tests expect. A full P4-style per-section split (separate R rodata and - * heap-only entries) does not fit the 16-entry PMP budget once the LP split takes 4 entries, but - * this two-region split already enforces W^X. It uses entries 7-8 (left free by the flash - * aperture's single-entry mapping under XIP) plus entry 9, programmed in ascending order. */ - extern int _instruction_reserved_end; - PMP_ENTRY_SET(7, SOC_EXTRAM_LOW, NONE); - PMP_ENTRY_SET(8, (uint32_t)(&_instruction_reserved_end), PMP_TOR | RX); - PMP_ENTRY_SET(9, SOC_EXTRAM_HIGH, PMP_TOR | RW); - _Static_assert(SOC_EXTRAM_LOW < SOC_EXTRAM_HIGH, "Invalid I/D_EXTRAM region"); -#else - /* PSRAM used as data only (no XIP): a single RWX NAPOT window. */ + /* PSRAM used as data only (no XIP): a single NAPOT window, RW so that nothing in external RAM + * is executable. The PMA entry configured above already makes this region cacheable; the PSRAM + * cache-bypass alias is kept valid by PMA (an address unmatched by PMP is allowed by PMP in + * M-mode, so it needs no extra PMP entry). + * + * The entry is locked, so it is only narrowed to RW when the pre-configured PSRAM layout + * applies. Otherwise the application owns the region - it may map and execute its own code + * there - and the window stays RWX, as on ESP32-P4. */ const uint32_t pmpaddr_extram = PMPADDR_NAPOT(SOC_EXTRAM_LOW, SOC_EXTRAM_HIGH); - PMP_ENTRY_SET(9, pmpaddr_extram, PMP_NAPOT | RWX); - _Static_assert(SOC_EXTRAM_LOW < SOC_EXTRAM_HIGH, "Invalid I/D_EXTRAM region"); +#if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP && CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION + PMP_ENTRY_SET(10, pmpaddr_extram, PMP_NAPOT | RW); +#else + PMP_ENTRY_SET(10, pmpaddr_extram, PMP_NAPOT | RWX); #endif + _Static_assert(SOC_EXTRAM_LOW < SOC_EXTRAM_HIGH, "Invalid EXTRAM region"); #endif /* CONFIG_SPIRAM */ - // 6. LP memory (LP TCM / RTC SRAM) +#endif /* CONFIG_SPIRAM && (CONFIG_SPIRAM_FETCH_INSTRUCTIONS || CONFIG_SPIRAM_RODATA) */ + + // 5. LP memory (LP TCM / RTC SRAM) #if CONFIG_ESP_SYSTEM_MEMPROT && CONFIG_ESP_SYSTEM_MEMPROT_PMP extern int _rtc_text_start; extern int _rtc_text_end; - /* LP TCM split into three regions (mirroring ESP32-P4 / ESP32-C6): - * [LOW, _rtc_text_start) RW : RTC-reserved mem + ULP/LP-core image (the HP core writes - * the program here, so it must stay writable). - * [_rtc_text_start, _rtc_text_end) RX : HP core rtc.text (e.g. deep-sleep wake stub). - * [_rtc_text_end, HIGH) RW : RTC data. - * Programmed in ascending-address order (S31 faults on overlapping PMP entries); every entry was - * already reset to a disabled state at the top of this function. */ - PMP_ENTRY_SET(10, SOC_RTC_IRAM_LOW, NONE); + PMP_ENTRY_SET(12, SOC_RTC_IRAM_LOW, NONE); #if CONFIG_ESP_SYSTEM_MEMPROT_PMP_LP_CORE_RESERVE_MEM_EXEC - PMP_ENTRY_SET(11, (int)&_rtc_text_start, PMP_TOR | RWX); + PMP_ENTRY_SET(13, (int)&_rtc_text_start, PMP_TOR | RWX); #else - PMP_ENTRY_SET(11, (int)&_rtc_text_start, PMP_TOR | RW); + PMP_ENTRY_SET(13, (int)&_rtc_text_start, PMP_TOR | RW); #endif - PMP_ENTRY_SET(12, (int)&_rtc_text_end, PMP_TOR | RX); - PMP_ENTRY_SET(13, SOC_RTC_IRAM_HIGH, PMP_TOR | RW); + PMP_ENTRY_SET(14, (int)&_rtc_text_end, PMP_TOR | RX); + PMP_ENTRY_SET(15, SOC_RTC_IRAM_HIGH, PMP_TOR | RW); #else const uint32_t pmpaddr_rtc = PMPADDR_NAPOT(SOC_RTC_IRAM_LOW, SOC_RTC_IRAM_HIGH); - PMP_ENTRY_SET(10, pmpaddr_rtc, PMP_NAPOT | RWX); + PMP_ENTRY_SET(12, pmpaddr_rtc, PMP_NAPOT | RWX); _Static_assert(SOC_RTC_IRAM_LOW < SOC_RTC_IRAM_HIGH, "Invalid RTC IRAM region"); #endif - - // 7. Peripheral addresses (on-chip peripherals, CPU peripheral, cache-data memory and the - // debug address space). The LP peripherals are contiguous with the rest and are covered by - // this single window as well. Peripherals use the last PMP entries (14-15) so that the entry - // count used by the regions above (which varies with the build configuration) never collides. - PMP_ENTRY_SET(14, SOC_PERIPHERAL_LOW, NONE); - PMP_ENTRY_SET(15, SOC_PERIPHERAL_HIGH, PMP_TOR | RW); - _Static_assert(SOC_PERIPHERAL_LOW < SOC_PERIPHERAL_HIGH, "Invalid peripheral region"); } #endif // BOOTLOADER_BUILD diff --git a/components/soc/esp32s31/include/soc/soc.h b/components/soc/esp32s31/include/soc/soc.h index dc47bc6003d..e907211cd11 100644 --- a/components/soc/esp32s31/include/soc/soc.h +++ b/components/soc/esp32s31/include/soc/soc.h @@ -197,12 +197,12 @@ // peripherals, security peripherals, CPU peripheral, cache-data memory and the // debug address space). Per the S31 bus address map this spans from the start of // the "On-Chip Peripherals" aperture up to the base of the LP TCM (SOC_RTC_IRAM_LOW). -#define SOC_PERIPHERAL_LOW 0x20000000 +#define SOC_PERIPHERAL_LOW 0x20100000 #define SOC_PERIPHERAL_HIGH 0x2E000000 /** 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) +#define SOC_LP_PERIPH_LOW 0x20700000 +#define SOC_LP_PERIPH_HIGH 0x20820000 // CPU sub-system region, contains interrupt config registers #define SOC_CPU_SUBSYSTEM_LOW 0x10000000 @@ -213,6 +213,7 @@ #define SOC_ROM_STACK_SIZE 0x2000 // non-cacheable offset for memory behind the cache +#define SOC_NON_CACHEABLE_OFFSET_FLASH 0x60000000 #define SOC_NON_CACHEABLE_OFFSET_PSRAM 0x70000000 //On RISC-V CPUs, the interrupt sources are all external interrupts, whose type, source and priority are configured by SW. 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 52f4ca51c30..4b5f68e32e3 100644 --- a/tools/test_apps/system/panic/panic_base/pytest_panic.py +++ b/tools/test_apps/system/panic/panic_base/pytest_panic.py @@ -1150,7 +1150,6 @@ def spiram_xip_irom_alignment_reg_execute_violation(dut: PanicTestDut, test_func @pytest.mark.generic -@pytest.mark.temp_skip_ci(targets=['esp32s31'], reason='no PSRAM-equipped runner for v0.0 silicon yet') @idf_parametrize('config, target', CONFIGS_MEMPROT_SPIRAM_XIP_IROM_ALIGNMENT_HEAP, indirect=['config', 'target']) def test_spiram_xip_irom_alignment_reg_execute_violation(dut: PanicTestDut, test_func_name: str) -> None: spiram_xip_irom_alignment_reg_execute_violation(dut, test_func_name) @@ -1175,7 +1174,6 @@ def spiram_xip_drom_alignment_reg_execute_violation(dut: PanicTestDut, test_func @pytest.mark.generic -@pytest.mark.temp_skip_ci(targets=['esp32s31'], reason='no PSRAM-equipped runner for v0.0 silicon yet') @idf_parametrize('config, target', CONFIGS_MEMPROT_SPIRAM_XIP_DROM_ALIGNMENT_HEAP, indirect=['config', 'target']) def test_spiram_xip_drom_alignment_reg_execute_violation(dut: PanicTestDut, test_func_name: str) -> None: spiram_xip_drom_alignment_reg_execute_violation(dut, test_func_name)