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 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; 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..98b7041c680 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,355 @@ #include "soc/soc.h" #include "esp_cpu.h" #include "esp_fault.h" -#include "hal/cache_ll.h" -#include "riscv/csr.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 */ -/* TODO: [ESP32S31] IDF-15238 */ +#define ALIGN_UP_TO_MMU_PAGE_SIZE(addr) ESP_ALIGN_UP(addr, SOC_MMU_PAGE_SIZE) +NOINLINE_ATTR IRAM_ATTR static void esp_cpu_configure_invalid_regions(void) +{ #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 + /* 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 -// With L bit set -#define CONDITIONAL_NONE NONE -#define CONDITIONAL_R R -#define CONDITIONAL_RX RX -#define CONDITIONAL_RW RW -#define CONDITIONAL_RWX RWX + 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; + + /* 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_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. + // 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); + PMA_ENTRY_SET_TOR(2, SOC_IRAM_LOW, PMA_TOR | PMA_NONE); + + // 2. Gap between HP TCM (internal SRAM) and ROM-Cache + 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. This region's base is entry 4's address. + PMA_ENTRY_SET_TOR(5, SOC_DROM_MASK_HIGH, PMA_TOR | PMA_RX); + + // 4. Gap between ROM-Cache and External flash (I/D-Cache) + PMA_ENTRY_SET_TOR(6, 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. 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. 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. 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. 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 +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 (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); + 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 - 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 + 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. 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(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"); + } + + // 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()); + + 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(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 + 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. 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)); + + 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(7, 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) +#if CONFIG_SPIRAM + /* 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); +#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 */ + +#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; + PMP_ENTRY_SET(12, SOC_RTC_IRAM_LOW, NONE); +#if CONFIG_ESP_SYSTEM_MEMPROT_PMP_LP_CORE_RESERVE_MEM_EXEC + PMP_ENTRY_SET(13, (int)&_rtc_text_start, PMP_TOR | RWX); +#else + PMP_ENTRY_SET(13, (int)&_rtc_text_start, PMP_TOR | RW); +#endif + 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(12, pmpaddr_rtc, PMP_NAPOT | RWX); + _Static_assert(SOC_RTC_IRAM_LOW < SOC_RTC_IRAM_HIGH, "Invalid RTC IRAM region"); +#endif +} +#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 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(); - //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/soc/esp32s31/include/soc/soc.h b/components/soc/esp32s31/include/soc/soc.h index e32604a5f4c..e907211cd11 100644 --- a/components/soc/esp32s31/include/soc/soc.h +++ b/components/soc/esp32s31/include/soc/soc.h @@ -193,13 +193,16 @@ #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 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 @@ -210,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 adfdbb438af..4b5f68e32e3 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']) 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