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-21 20:54:30 +05:30
parent 8f3d8b41c3
commit e7cd4a1500
8 changed files with 204 additions and 7 deletions
@@ -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.
+68 -2
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
@@ -277,6 +283,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
@@ -366,6 +382,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!
*----------------------------------------------------------------------------*/
@@ -490,6 +533,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
@@ -534,6 +593,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;