From 23aba459f52848b0fccd0fac5902cc17a5f113ba Mon Sep 17 00:00:00 2001 From: Mahavir Jain Date: Wed, 6 May 2026 19:53:47 +0530 Subject: [PATCH] feat(esp_psram): add option to carve unencrypted PSRAM region Adds CONFIG_SPIRAM_ENC_EXEMPT, available on chips that support per-page PSRAM encryption configuration (esp32c5, esp32c61, esp32p4). When enabled, esp_psram carves CONFIG_SPIRAM_ENC_EXEMPT_SIZE off the top of PSRAM and maps it via the new mmu_hal_map_region_no_enc() helper, which writes MMU entries without the SENSITIVE bit. The region is registered as a separate heap pool reachable only through the new MALLOC_CAP_SPIRAM_NO_ENC capability bit, so default SPIRAM allocations cannot accidentally land there. PSRAM encryption imposes alignment constraints that some DMA engines (e.g. 2D-DMA) cannot satisfy. This option lets such workloads place their buffers in unencrypted PSRAM while keeping the rest of PSRAM (and flash) encrypted. Default disabled; security implications are documented in the Kconfig help text. --- components/esp_psram/Kconfig.spiram.common | 37 ++++++++++ components/esp_psram/system_layer/esp_psram.c | 70 ++++++++++++++++++- components/hal/esp32c5/include/hal/mmu_ll.h | 19 ++++- components/hal/esp32c61/include/hal/mmu_ll.h | 20 +++++- components/hal/esp32p4/include/hal/mmu_ll.h | 23 +++++- components/hal/include/hal/mmu_hal.h | 15 +++- components/hal/mmu_hal.c | 26 ++++++- components/heap/include/esp_heap_caps.h | 1 + 8 files changed, 204 insertions(+), 7 deletions(-) diff --git a/components/esp_psram/Kconfig.spiram.common b/components/esp_psram/Kconfig.spiram.common index 2d6c36831c7..e492ec659fc 100644 --- a/components/esp_psram/Kconfig.spiram.common +++ b/components/esp_psram/Kconfig.spiram.common @@ -156,3 +156,40 @@ config SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY Note the values placed into this section will not be initialized at startup and should keep its value after software restart. + +config SPIRAM_ENC_EXEMPT + bool "Reserve a PSRAM region exempt from encryption" + default n + depends on SPIRAM && SECURE_FLASH_ENC_ENABLED && SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE + help + !!! SECURITY WARNING !!! + + Enabling this option carves out a region at the top of PSRAM that is mapped + WITHOUT encryption, even when flash encryption is enabled. Memory allocated + from this region (via MALLOC_CAP_SPIRAM_NO_ENC) is stored in plaintext in + PSRAM and can be observed by an attacker with physical access to the PSRAM + interface. + + DO NOT enable this unless you have weighed the trade-off: + - You accept that any data placed in this region is unprotected at rest. + - You will only allocate workloads that are not security-sensitive (e.g. + video frame buffers, intermediate codec scratch buffers) into this region. + - You understand that PSRAM-resident TLS state, keys, or other secrets + MUST NOT be allocated with MALLOC_CAP_SPIRAM_NO_ENC. + + Use case: PSRAM encryption imposes alignment constraints on buffers that + cross PSRAM. Some DMA engines (e.g. 2D-DMA) cannot satisfy these alignment + requirements, so DMA into encrypted PSRAM fails. This option lets such + workloads place their buffers in an unencrypted PSRAM region while keeping + the rest of PSRAM (and flash) encrypted. + +config SPIRAM_ENC_EXEMPT_SIZE + int "Size of the unencrypted PSRAM region (KB)" + default 256 + range 64 65536 + depends on SPIRAM_ENC_EXEMPT + help + Size of the PSRAM region carved out at the top of PSRAM and mapped without + encryption. Rounded up to a multiple of the MMU page size (typically 64 KB). + The region is registered as a separate heap pool, accessible only via + MALLOC_CAP_SPIRAM_NO_ENC. diff --git a/components/esp_psram/system_layer/esp_psram.c b/components/esp_psram/system_layer/esp_psram.c index 285b0dfec6d..594c6d4c9d8 100644 --- a/components/esp_psram/system_layer/esp_psram.c +++ b/components/esp_psram/system_layer/esp_psram.c @@ -49,13 +49,19 @@ #define BYTES_TO_MMU_PAGE(bytes) ((bytes) / MMU_PAGE_SIZE) /** - * Two types of PSRAM memory regions for now: + * PSRAM memory regions: * - 8bit aligned * - 32bit aligned + * - Optional: encryption-exempt carve-out (top of PSRAM) */ -#define PSRAM_MEM_TYPE_NUM 2 #define PSRAM_MEM_8BIT_ALIGNED 0 #define PSRAM_MEM_32BIT_ALIGNED 1 +#if CONFIG_SPIRAM_ENC_EXEMPT +#define PSRAM_MEM_ENC_EXEMPT 2 +#define PSRAM_MEM_TYPE_NUM 3 +#else +#define PSRAM_MEM_TYPE_NUM 2 +#endif #if CONFIG_SPIRAM_FLASH_LOAD_TO_PSRAM #define PSRAM_EARLY_LOGI ESP_DRAM_LOGI @@ -283,6 +289,16 @@ static void s_xip_psram_placement(uint32_t *psram_available_size, uint32_t *out_ static void s_psram_mapping(uint32_t psram_available_size, uint32_t start_page) { esp_err_t ret = ESP_FAIL; +#if CONFIG_SPIRAM_ENC_EXEMPT + size_t enc_exempt_size = ALIGN_UP_BY((size_t)CONFIG_SPIRAM_ENC_EXEMPT_SIZE * 1024, MMU_PAGE_SIZE); + if (enc_exempt_size >= psram_available_size) { + ESP_EARLY_LOGE(TAG, "SPIRAM_ENC_EXEMPT_SIZE (%dKB) >= available PSRAM (%dKB); disabling carve-out", + (int)(enc_exempt_size / 1024), (int)(psram_available_size / 1024)); + enc_exempt_size = 0; + } else { + psram_available_size -= enc_exempt_size; + } +#endif //----------------------------------Map the PSRAM physical range to MMU-----------------------------// /** * @note 2 @@ -372,6 +388,33 @@ static void s_psram_mapping(uint32_t psram_available_size, uint32_t start_page) ESP_EARLY_LOGW(TAG, "Virtual address not enough for PSRAM, map as much as we can. %dMB is mapped", total_mapped_size / 1024 / 1024); } +#if CONFIG_SPIRAM_ENC_EXEMPT + if (enc_exempt_size) { + const void *v_start_no_enc = NULL; + ret = esp_mmu_map_reserve_block_with_caps(enc_exempt_size, + MMU_MEM_CAP_READ | MMU_MEM_CAP_WRITE | MMU_MEM_CAP_8BIT | MMU_MEM_CAP_32BIT, + MMU_TARGET_PSRAM0, &v_start_no_enc); + assert(ret == ESP_OK); + + mmu_hal_map_region_no_enc((uint32_t)v_start_no_enc, MMU_PAGE_TO_BYTES(start_page), enc_exempt_size); + + cache_bus_mask_t bus_mask = cache_ll_l1_get_bus(0, (uint32_t)v_start_no_enc, enc_exempt_size); + cache_ll_l1_enable_bus(0, bus_mask); +#if !CONFIG_ESP_SYSTEM_SINGLE_CORE_MODE + bus_mask = cache_ll_l1_get_bus(1, (uint32_t)v_start_no_enc, enc_exempt_size); + cache_ll_l1_enable_bus(1, bus_mask); +#endif + + s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].size = enc_exempt_size; + s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].vaddr_start = (intptr_t)v_start_no_enc; + s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].vaddr_end = (intptr_t)v_start_no_enc + enc_exempt_size; + s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].size = enc_exempt_size; + s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].vaddr_start = (intptr_t)v_start_no_enc; + s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].vaddr_end = (intptr_t)v_start_no_enc + enc_exempt_size; + ESP_EARLY_LOGI(TAG, "PSRAM unencrypted region: 0x%x B at %p", (unsigned)enc_exempt_size, v_start_no_enc); + } +#endif /* CONFIG_SPIRAM_ENC_EXEMPT */ + /*------------------------------------------------------------------------------ * After mapping, we DON'T care about the PSRAM PHYSICAL ADDRESS ANYMORE! *----------------------------------------------------------------------------*/ @@ -500,6 +543,22 @@ esp_err_t esp_psram_extram_add_to_heap_allocator(void) ESP_EARLY_LOGI(TAG, "Adding pool of %dK of PSRAM memory to heap allocator", (s_psram_ctx.regions_to_heap[PSRAM_MEM_8BIT_ALIGNED].size + s_psram_ctx.regions_to_heap[PSRAM_MEM_32BIT_ALIGNED].size) / 1024); +#if CONFIG_SPIRAM_ENC_EXEMPT + if (s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].size) { + // Only MALLOC_CAP_SPIRAM_NO_ENC: any other bit here would let generic 8BIT/32BIT + // allocations fall through to this region as a low-priority match. + uint32_t no_enc_caps[] = {MALLOC_CAP_SPIRAM_NO_ENC, 0, 0}; + ret = heap_caps_add_region_with_caps(no_enc_caps, + s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].vaddr_start, + s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].vaddr_end); + if (ret != ESP_OK) { + return ret; + } + ESP_EARLY_LOGI(TAG, "Adding pool of %dK of unencrypted PSRAM memory to heap allocator", + (int)(s_psram_ctx.regions_to_heap[PSRAM_MEM_ENC_EXEMPT].size / 1024)); + } +#endif + // To allow using the page alignment gaps created while mapping the flash segments, // the alignment gaps must be configured with correct memory protection configurations. #if CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION @@ -544,6 +603,13 @@ bool IRAM_ATTR esp_psram_check_ptr_addr(const void *p) return true; } +#if CONFIG_SPIRAM_ENC_EXEMPT + if ((intptr_t)p >= s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].vaddr_start && + (intptr_t)p < s_psram_ctx.mapped_regions[PSRAM_MEM_ENC_EXEMPT].vaddr_end) { + return true; + } +#endif + #if CONFIG_SPIRAM_RODATA if (mmu_psram_check_ptr_addr_in_xip_psram_rodata_region(p)) { return true; diff --git a/components/hal/esp32c5/include/hal/mmu_ll.h b/components/hal/esp32c5/include/hal/mmu_ll.h index 79df1a70676..ccbd9c3ab9d 100644 --- a/components/hal/esp32c5/include/hal/mmu_ll.h +++ b/components/hal/esp32c5/include/hal/mmu_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -237,6 +237,23 @@ __attribute__((always_inline)) static inline void mmu_ll_write_entry(uint32_t mm } } +#if SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE +/** + * Write a PSRAM MMU entry without the SENSITIVE bit, used only for the + * carved-out unencrypted region (see CONFIG_SPIRAM_ENC_EXEMPT). + * + * No anti-FI check: the SENSITIVE bit is intentionally clear, and an FI flip + * that sets it would force decryption of plaintext data (garbage, fails safe). + */ +__attribute__((always_inline)) static inline void mmu_ll_write_entry_no_enc(uint32_t mmu_id, uint32_t entry_id, uint32_t mmu_val) +{ + (void)mmu_id; + uint32_t mmu_raw_value = mmu_val | SOC_MMU_ACCESS_SPIRAM | SOC_MMU_VALID; + REG_WRITE(SPI_MEM_MMU_ITEM_INDEX_REG(0), entry_id); + REG_WRITE(SPI_MEM_MMU_ITEM_CONTENT_REG(0), mmu_raw_value); +} +#endif + /** * Read the raw value from MMU table * diff --git a/components/hal/esp32c61/include/hal/mmu_ll.h b/components/hal/esp32c61/include/hal/mmu_ll.h index b2a2c893f3c..551baa6395c 100644 --- a/components/hal/esp32c61/include/hal/mmu_ll.h +++ b/components/hal/esp32c61/include/hal/mmu_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -10,6 +10,7 @@ #include "soc/spi_mem_reg.h" #include "soc/ext_mem_defs.h" +#include "soc/soc_caps.h" #include "hal/assert.h" #include "hal/mmu_types.h" #include "hal/efuse_ll.h" @@ -237,6 +238,23 @@ __attribute__((always_inline)) static inline void mmu_ll_write_entry(uint32_t mm } } +#if SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE +/** + * Write a PSRAM MMU entry without the SENSITIVE bit, used only for the + * carved-out unencrypted region (see CONFIG_SPIRAM_ENC_EXEMPT). + * + * No anti-FI check: the SENSITIVE bit is intentionally clear, and an FI flip + * that sets it would force decryption of plaintext data (garbage, fails safe). + */ +__attribute__((always_inline)) static inline void mmu_ll_write_entry_no_enc(uint32_t mmu_id, uint32_t entry_id, uint32_t mmu_val) +{ + (void)mmu_id; + uint32_t mmu_raw_value = mmu_val | SOC_MMU_ACCESS_SPIRAM | SOC_MMU_VALID; + REG_WRITE(SPI_MEM_MMU_ITEM_INDEX_REG(0), entry_id); + REG_WRITE(SPI_MEM_MMU_ITEM_CONTENT_REG(0), mmu_raw_value); +} +#endif + /** * Read the raw value from MMU table * diff --git a/components/hal/esp32p4/include/hal/mmu_ll.h b/components/hal/esp32p4/include/hal/mmu_ll.h index e86d1a18fbd..aeebe6eed86 100644 --- a/components/hal/esp32p4/include/hal/mmu_ll.h +++ b/components/hal/esp32p4/include/hal/mmu_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,6 +11,7 @@ #include "soc/spi_mem_c_reg.h" #include "soc/spi_mem_s_reg.h" #include "soc/ext_mem_defs.h" +#include "soc/soc_caps.h" #include "hal/assert.h" #include "hal/mmu_types.h" #include "hal/efuse_ll.h" @@ -301,6 +302,26 @@ __attribute__((always_inline)) static inline void mmu_ll_write_entry(uint32_t mm } } +#if SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE +/** + * Write a PSRAM MMU entry without the SENSITIVE bit, used only for the + * carved-out unencrypted region (see CONFIG_SPIRAM_ENC_EXEMPT). + * + * No anti-FI check: the SENSITIVE bit is intentionally clear, and an FI flip + * that sets it would force decryption of plaintext data (garbage, fails safe). + */ +__attribute__((always_inline)) static inline void mmu_ll_write_entry_no_enc(uint32_t mmu_id, uint32_t entry_id, uint32_t mmu_val) +{ + HAL_ASSERT(mmu_id == MMU_LL_PSRAM_MMU_ID); + + mmu_val |= SOC_MMU_PSRAM_VALID; + mmu_val |= SOC_MMU_ACCESS_PSRAM; + + REG_WRITE(SPI_MEM_S_MMU_ITEM_INDEX_REG, entry_id); + REG_WRITE(SPI_MEM_S_MMU_ITEM_CONTENT_REG, mmu_val); +} +#endif + /** * Read the raw value from MMU table * diff --git a/components/hal/include/hal/mmu_hal.h b/components/hal/include/hal/mmu_hal.h index 6d8563a1fef..779af2d068e 100644 --- a/components/hal/include/hal/mmu_hal.h +++ b/components/hal/include/hal/mmu_hal.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2010-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2010-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -81,6 +81,19 @@ uint32_t mmu_hal_bytes_to_pages(uint32_t mmu_id, uint32_t bytes); */ void mmu_hal_map_region(uint32_t mmu_id, mmu_target_t mem_type, uint32_t vaddr, uint32_t paddr, uint32_t len, uint32_t *out_len); +#if SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE +/** + * Map a PSRAM physical range to virtual memory without setting the encryption + * SENSITIVE bit on each MMU entry. Used only for the explicitly carved-out + * unencrypted PSRAM region (see CONFIG_SPIRAM_ENC_EXEMPT). + * + * @param vaddr start virtual address (MMU-page-aligned) + * @param paddr start physical address (MMU-page-aligned) + * @param len length in bytes + */ +void mmu_hal_map_region_no_enc(uint32_t vaddr, uint32_t paddr, uint32_t len); +#endif + /** * To unmap a virtual address block that is mapped to a physical memory block previously * diff --git a/components/hal/mmu_hal.c b/components/hal/mmu_hal.c index 95441f7463e..5c83a9681fd 100644 --- a/components/hal/mmu_hal.c +++ b/components/hal/mmu_hal.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2026 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -113,6 +113,30 @@ void mmu_hal_map_region(uint32_t mmu_id, mmu_target_t mem_type, uint32_t vaddr, } } +#if SOC_PSRAM_ENCRYPTION_PAGE_CONFIGURABLE +void mmu_hal_map_region_no_enc(uint32_t vaddr, uint32_t paddr, uint32_t len) +{ + uint32_t mmu_id = MMU_LL_PSRAM_MMU_ID; + uint32_t page_size_in_bytes = mmu_hal_pages_to_bytes(mmu_id, 1); + HAL_ASSERT(vaddr % page_size_in_bytes == 0); + HAL_ASSERT(paddr % page_size_in_bytes == 0); + HAL_ASSERT(mmu_ll_check_valid_paddr_region(mmu_id, paddr, len)); + // Restrict to data vaddr space — unencrypted PSRAM must never back code/rodata. + HAL_ASSERT(mmu_hal_check_valid_ext_vaddr_region(mmu_id, vaddr, len, MMU_VADDR_DATA)); + + uint32_t page_num = (len + page_size_in_bytes - 1) / page_size_in_bytes; + uint32_t mmu_val = mmu_ll_format_paddr(mmu_id, paddr, MMU_TARGET_PSRAM0); + + while (page_num) { + uint32_t entry_id = mmu_ll_get_entry_id(mmu_id, vaddr); + mmu_ll_write_entry_no_enc(mmu_id, entry_id, mmu_val); + vaddr += page_size_in_bytes; + mmu_val++; + page_num--; + } +} +#endif + void mmu_hal_unmap_region(uint32_t mmu_id, uint32_t vaddr, uint32_t len) { uint32_t page_size_in_bytes = mmu_hal_pages_to_bytes(mmu_id, 1); diff --git a/components/heap/include/esp_heap_caps.h b/components/heap/include/esp_heap_caps.h index 08e87426446..f753d72c7e7 100644 --- a/components/heap/include/esp_heap_caps.h +++ b/components/heap/include/esp_heap_caps.h @@ -50,6 +50,7 @@ extern "C" { #define MALLOC_CAP_DMA_DESC_AXI (1<<18) ///< Memory must be capable of containing AXI DMA descriptors #define MALLOC_CAP_CACHE_ALIGNED (1<<19) ///< Memory must be aligned to the cache line size of any intermediate caches #define MALLOC_CAP_SIMD (1<<20) ///< Memory must be capable of being used for SIMD instructions (i.e. allow for SIMD-specific-bit data accesses) +#define MALLOC_CAP_SPIRAM_NO_ENC (1<<21) ///< Memory must be in the PSRAM region exempt from flash encryption (plaintext in PSRAM; see CONFIG_SPIRAM_ENC_EXEMPT) #define MALLOC_CAP_INVALID (1<<31) ///< Memory can't be used / list end marker