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.
This commit is contained in:
Mahavir Jain
2026-05-06 19:53:47 +05:30
parent cc532288e8
commit 23aba459f5
8 changed files with 204 additions and 7 deletions

View File

@@ -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.

View File

@@ -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;

View File

@@ -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
*

View File

@@ -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
*

View File

@@ -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
*

View File

@@ -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
*

View File

@@ -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);

View File

@@ -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