#19099·esp-idf

ESP32-P4: TLS with mbedTLS memory in PSRAM aborts in mspi_psram_isr_handler_wrapper (AES-DMA on unaligned PSRAM buffers) (IDFGH-18295)

Author: igkrEnergusCreated Sep 17, 2026Updated Sep 17, 2026
LabelsType: BugStatus: Opened

Answers checklist.

  • I have read the documentation ESP-IDF Programming Guide and the issue is not addressed there.
  • I have updated my IDF branch (master or release) to the latest version and checked that the issue is present there.
  • I have searched the issue tracker for a similar issue and not found a similar issue.

IDF version.

v6.1 (tag v6.1, commit fff9895c82d, espressif/idf:v6.1 Docker image)

Espressif SoC revision.

ESP32-P4 chip revision v1.3 (ROM esp32p4-eco2-20240710), 32 MB AP HEX PSRAM at 200 MHz, 16 MB flash

Operating System used.

Windows

How did you build your project?

Other (please specify in More Information)

If you are using Windows, please specify command line type.

CMD

Development Kit.

Custom PCB

Power Supply used.

External 3.3V

What is the expected behavior?

With CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y and hardware AES/SHA enabled (the defaults), TLS connections served by esp_https_server complete and data flows normally. The AES-DMA and SHA-DMA drivers handle buffers anywhere in PSRAM, including at offsets that are not 16-byte aligned. This should hold whether CONFIG_SPIRAM_ECC_ENABLE is on or off and whether flash encryption is enabled.

What is the actual behavior?

A TLS connection that negotiates TLS_AES_256_GCM_SHA384 aborts the device during the handshake. It is usually the first connection after boot, never later than the third.

abort() was called at PC 0x4000d863 on core 0

0x4000d863 decodes to mspi_psram_isr_handler_wrapper (components/esp_psram/system_layer/esp_psram_mspi.c:79), the MSPI PSRAM error interrupt. The handler's own MSPI PSRAM error message and event bits do not show up on the console before the abort.

Same firmware and load (200 TLS 1.3 connections, each fetching a 448 KB response):

mbedTLS memory CONFIG_SPIRAM_ECC_ENABLE AES/SHA DMA drivers Cipher suite Result
PSRAM y v6.1 as shipped TLS_AES_256_GCM_SHA384 abort on the first connection
PSRAM n v6.1 as shipped TLS_AES_256_GCM_SHA384 abort on the first connection
PSRAM y patched (see below) TLS_AES_256_GCM_SHA384 200/200 OK
PSRAM y v6.1 as shipped TLS_CHACHA20_POLY1305_SHA256 only 200/200 OK

With mbedTLS memory in internal RAM (CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y), the same server runs without aborting.

The patched row changes the four efuse_hal_flash_encryption_enabled() checks in mbedtls/port/aes/dma/esp_aes_dma_core.c and mbedtls/port/sha/core/sha.c, so the existing 16-byte realignment also applies when CONFIG_SPIRAM_ECC_ENABLE is set.

esp_crypto_shared_gdma.c configures the crypto TX channel with max_data_burst_size = 16. 16-byte bursts that start at an unaligned PSRAM address appear to trigger the PSRAM controller error. In v6.1 the drivers only realign PSRAM buffers when flash encryption is enabled.

Commit 305d434bda5 on release/v6.1 ("feat(mspi): split dma and mspi alignment") appears to fix the ECC-on case through esp_mspi_get_alignment(). For PSRAM with ECC off and no flash encryption, that helper returns 1, so the second row may still reproduce there.

Steps to reproduce.

  1. ESP32-P4 v1.3 with 32 MB HEX PSRAM, ESP-IDF v6.1, with flash encryption and secure boot disabled.
  2. Relevant sdkconfig options:
    CONFIG_SPIRAM=y
    CONFIG_SPIRAM_MODE_HEX=y
    CONFIG_SPIRAM_SPEED_200M=y
    CONFIG_SPIRAM_USE_MALLOC=y
    CONFIG_SPIRAM_ECC_ENABLE=y        # also reproduces with it disabled
    CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
    CONFIG_MBEDTLS_HARDWARE_AES=y
    CONFIG_MBEDTLS_HARDWARE_SHA=y
    CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
    CONFIG_MBEDTLS_SSL_PROTO_TLS1_3=y
  3. Start esp_https_server (httpd_ssl_start) on port 443:
    • a self-signed ECDSA P-256 certificate (our key is a PSA key passed through server_key);
    • tls_version = ESP_TLS_VER_TLS_1_3 (also reproduces with ESP_TLS_VER_ANY);
    • any GET handler.
  4. From a PC, connect with a TLS 1.3 client that negotiates TLS_AES_256_GCM_SHA384. We used Python's ssl module (OpenSSL 3.0); openssl s_client -connect <device-ip>:443 -tls1_3 -ciphersuites TLS_AES_256_GCM_SHA384 should behave the same.
  5. The device aborts during the handshake of the first connection, or at the latest the third.

We reproduced this with our application's HTTPS server, not yet with examples/protocols/https_server.

Debug Logs.

plain
I (28) boot: ESP-IDF v6.1 2nd stage bootloader
I (29) boot: chip revision: v1.3
I (468) hex_psram: vendor id    : 0x0d (AP)
I (641) esp_psram: Found 32MB PSRAM device
...
I (1970-01-01 00:01:13.046) [esp_https_server] performing session handshake
I (1970-01-01 00:01:13.053) [esp-tls-mbedtls] Setting server TLS version to 0x 304

abort() was called at PC 0x4000d863 on core 0
Core  0 register dump:
MEPC    : 0x4ff0bd78  RA      : 0x4ff0bd32  SP      : 0x4ff228c0  GP      : 0x4ff1e900
TP      : 0x4ff3fac0  T0      : 0x37363534  T1      : 0x7271706f  T2      : 0x33323130
S0/FP   : 0x4ff228fc  S1      : 0x4ff228fc  A0      : 0x4ff228fc  A1      : 0x4ff228de
A2      : 0x00000000  A3      : 0x4ff41000  A4      : 0x00000001  A5      : 0x4ff41000
A6      : 0x00000000  A7      : 0x76757473  S2      : 0x4ff228e0  S3      : 0x4ff252d0
S4      : 0x00000000  S5      : 0x00000000  S6      : 0x4ff3d294  S7      : 0x4ff256ec
S8      : 0x00000000  S9      : 0x00000000  S10     : 0x00000000  S11     : 0x00000000
T3      : 0x6e6d6c6b  T4      : 0x6a696867  T5      : 0x66656463  T6      : 0x62613938
MSTATUS : 0x00001880  MTVEC   : 0x4ff00003  MCAUSE  : 0x00000002  MTVAL   : 0x00000000
MHARTID : 0x00000000

Stack memory:
4ff228c0: 0x00000000 0x00000000 0x4ff228fc 0x4ff095e2 0x4ff3d294 0x00000000 0x00000000 0x4ff20030
4ff228e0: 0x30303034 0x33363864 0x00000000 0x4ff214d8 0x4ff228e0 0x4ff214f4 0x4ff228dc 0x726f6261
4ff22900: 0x20292874 0x20736177 0x6c6c6163 0x61206465 0x43502074 0x34783020 0x64303030 0x20333638
4ff22920: 0x63206e6f 0x2065726f 0x00000030 0x00000000 0x00001880 0xb8000010 0x00000098 0x4000d866
4ff22940: 0x4ff1187e 0xb8000010 0x00000000 0x4ff002d8 0x00000000 0x00000000 0x00000000 0x00000000


addr2line:

0x4000d863: mspi_psram_isr_handler_wrapper at components/esp_psram/system_layer/esp_psram_mspi.c:79
0x4ff0bd78: panic_abort at components/esp_system/panic.c:509

Diagnostic report archive.

Created with idf.py diag (recipes environment, idf, manager, python, system, tools, plus the project sdkconfig in the reproducing configuration). The board's serial port was not reachable from the build container, so target information was not collected; the chip revision is in the boot log above.

ESP-IDF

Item Value
idf.py --version ESP-IDF v6.1
git -C $IDF_PATH describe v6.1
IDF Component Manager 3.0.3
Target esp32p4, CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y, CONFIG_ESP32P4_REV_MIN_FULL=100

Host

Item Value
Environment Docker espressif/idf:v6.1 (Ubuntu 24.04.4 LTS)
Kernel Linux 5.15.167.4-microsoft-standard-WSL2, x86_64
CPU AMD Ryzen 7 7840HS
Python 3.12.3 (/opt/esp/python_env/idf6.1_py3.12_env)

Tools (idf_tools.py check)

Tool Version
riscv32-esp-elf esp-15.2.0_20251204
riscv32-esp-elf-gdb 17.1_20260402
cmake 4.0.3
ninja 1.11.1
openocd-esp32 v0.12.0-esp32-20260703
esp-rom-elfs 20241011

Managed components (dependencies.lock): espressif/cbor 0.6.14, espressif/cjson 1.7.192, espressif/dp83848 1.1.0, espressif/mqtt 1.1.0, joltwallet/littlefs 1.22.3

Relevant sdkconfig
CONFIG_IDF_TARGET="esp32p4"
CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y
CONFIG_ESP32P4_REV_MIN_FULL=100
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=360
CONFIG_FREERTOS_HZ=100
CONFIG_ESPTOOLPY_FLASHMODE="dio"
CONFIG_ESPTOOLPY_FLASHFREQ="80m"
CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
CONFIG_SPIRAM=y
CONFIG_SPIRAM_MODE_HEX=y
CONFIG_SPIRAM_SPEED_200M=y
CONFIG_SPIRAM_SPEED=200
CONFIG_SPIRAM_ECC_ENABLE=y
CONFIG_SPIRAM_BOOT_INIT=y
# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
CONFIG_SPIRAM_USE_MALLOC=y
CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=16384
CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768
CONFIG_ESP_TLS_USING_MBEDTLS=y
CONFIG_HTTPD_WS_SUPPORT=y
# CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC is not set
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096
CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y
CONFIG_MBEDTLS_SSL_PROTO_TLS1_3=y
CONFIG_MBEDTLS_HARDWARE_AES=y
CONFIG_MBEDTLS_AES_USE_INTERRUPT=y
CONFIG_MBEDTLS_HARDWARE_SHA=y
CONFIG_MBEDTLS_HARDWARE_MPI=y
CONFIG_MBEDTLS_HARDWARE_ECC=y
# CONFIG_FLASH_ENCRYPTION_ENABLED is not set
# CONFIG_SECURE_BOOT is not set

More Information.

Test setup

  • Custom board: ESP32-P4 v1.3, 32 MB AP HEX PSRAM, 16 MB flash, Ethernet (DP83848 PHY over RMII). Code runs from flash (no XIP from PSRAM).
  • Load per run: 200 rounds. Each round opens a new TLS 1.3 connection and fetches a ~1 KB JSON response and a 448 KB static file. Every fifth round also opens a WebSocket over TLS and exchanges one message.
  • Client: Python 3.14 ssl (OpenSSL 3.0.20) on Windows 11.

Where it seems to come from

  • mbedtls/port/crypto_shared_gdma/esp_crypto_shared_gdma.c configures the crypto TX channel with .max_data_burst_size = 16 and .access_ext_mem = true. This does not depend on flash encryption or PSRAM ECC.
  • In v6.1, esp_aes_dma_core.c (3 places) and sha.c (1 place) realign external-RAM buffers to SOC_GDMA_EXT_MEM_ENC_ALIGNMENT only when efuse_hal_flash_encryption_enabled().
  • gdma_config_transfer() in esp_driver_dma/src/gdma.c already treats PSRAM ECC like flash encryption for external-memory alignment. The crypto drivers build their DMA descriptors themselves, so that rule is not applied to them.
  • With CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y, mbedTLS record buffers are in PSRAM, and the payload pointers passed to AES sit after the record header, so they are rarely 16-byte aligned.

Ruled out

  • Public-key crypto: only the AES and SHA ports use DMA; the ECC and bignum accelerators (ECDHE, ECDSA, RSA) do not. The crashing and passing runs used the same ECDSA P-256 key and ECDHE key exchange.
  • A task-specific fault: the abort comes from the PSRAM error interrupt, and the task running at the time varied (httpd, logger).
  • Chip errata: none of the ESP32-P4 v1.3 errata match (APM-560, ECDSA_DS-837, I2C-308, RMT-176). MSPI-749/750/751 apply to v3.0.

Diagnosability

Only an empty line precedes the abort on the console: neither the MSPI PSRAM error line nor the event bits appear. intr_events is logged with ESP_DRAM_LOGD, so the type of controller error (for example PSRAM_CTRLR_LL_EVENT_AXI_RADDR_ERR) is not visible at the default log level. Printing the event bits at error level would make this class of crash much easier to identify.

Workarounds that avoid the abort on v6.1

  • mbedTLS memory in internal RAM (CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC=y).
  • Server restricted to TLS_CHACHA20_POLY1305_SHA256: this protects only that server. Any other mbedTLS user in the same firmware that negotiates AES-GCM still hits the AES-DMA path with PSRAM buffers.
  • The local patch below (PSRAM ECC on).

Related

  • a6de2c79ed4 "fix(mbedtls/port): Align AES and SHA DMA buffers to 16 when SPIRAM encryption is enabled" (in v6.1)
  • ef4387144ee "test(aes): add aes psram ecc test" and 305d434bda5 "feat(mspi): split dma and mspi alignment" (on release/v6.1, after the v6.1 tag)
  • #18739: same MSPI PSRAM error / "psram read address invalid or misaligned" abort on ESP32-S31, caused by an LCD frame buffer at a misaligned PSRAM address
  • #17708: ESP32-P4 PSRAM buffer corruption with flash encryption enabled
Local patch used for the "patched" row (ESP-IDF v6.1)
diff
diff --git a/components/mbedtls/port/aes/dma/esp_aes_dma_core.c b/components/mbedtls/port/aes/dma/esp_aes_dma_core.c
--- a/components/mbedtls/port/aes/dma/esp_aes_dma_core.c
+++ b/components/mbedtls/port/aes/dma/esp_aes_dma_core.c
@@ -45,6 +45,12 @@
 
 #ifdef SOC_GDMA_EXT_MEM_ENC_ALIGNMENT
 #include "hal/efuse_hal.h"
+/* PSRAM ECC needs the same 16-byte DMA alignment as flash encryption. */
+#if CONFIG_SPIRAM_ECC_ENABLE
+#define TRIAL_EXT_MEM_NEEDS_MSPI_ALIGNMENT() (true)
+#else
+#define TRIAL_EXT_MEM_NEEDS_MSPI_ALIGNMENT() efuse_hal_flash_encryption_enabled()
+#endif
 #endif /* SOC_GDMA_EXT_MEM_ENC_ALIGNMENT */
 
@@ -253,7 +259,7 @@ static int esp_aes_process_dma_ext_ram(esp_aes_context *ctx, const unsigned char
 #if SOC_CACHE_INTERNAL_MEM_VIA_L1CACHE
-    if (efuse_hal_flash_encryption_enabled()) {
+    if (TRIAL_EXT_MEM_NEEDS_MSPI_ALIGNMENT()) {
         if (esp_ptr_external_ram(input) || esp_ptr_external_ram(output) || esp_ptr_in_drom(input) || esp_ptr_in_drom(output)) {
@@ -567,7 +573,7 @@ int esp_aes_process_dma(esp_aes_context *ctx, const unsigned char *input, unsign
 #ifdef SOC_GDMA_EXT_MEM_ENC_ALIGNMENT
-    if (efuse_hal_flash_encryption_enabled()) {
+    if (TRIAL_EXT_MEM_NEEDS_MSPI_ALIGNMENT()) {
         if (esp_ptr_external_ram(input) || esp_ptr_external_ram(output) || esp_ptr_in_drom(input) || esp_ptr_in_drom(output)) {
@@ -1061,7 +1067,7 @@ int esp_aes_process_dma(esp_aes_context *ctx, const unsigned char *input, unsign
 #ifdef SOC_GDMA_EXT_MEM_ENC_ALIGNMENT
-        if (efuse_hal_flash_encryption_enabled()) {
+        if (TRIAL_EXT_MEM_NEEDS_MSPI_ALIGNMENT()) {
             if (esp_ptr_external_ram(input) || esp_ptr_in_drom(input)) {
diff --git a/components/mbedtls/port/sha/core/sha.c b/components/mbedtls/port/sha/core/sha.c
--- a/components/mbedtls/port/sha/core/sha.c
+++ b/components/mbedtls/port/sha/core/sha.c
@@ -46,6 +46,12 @@
 
 #ifdef SOC_GDMA_EXT_MEM_ENC_ALIGNMENT
 #include "hal/efuse_hal.h"
+/* PSRAM ECC needs the same 16-byte DMA alignment as flash encryption. */
+#if CONFIG_SPIRAM_ECC_ENABLE
+#define TRIAL_EXT_MEM_NEEDS_MSPI_ALIGNMENT() (true)
+#else
+#define TRIAL_EXT_MEM_NEEDS_MSPI_ALIGNMENT() efuse_hal_flash_encryption_enabled()
+#endif
 #endif /* SOC_GDMA_EXT_MEM_ENC_ALIGNMENT */
 
@@ -235,7 +241,7 @@ static esp_err_t esp_sha_dma_process(esp_sha_type sha_type, const void *input, u
 #ifdef SOC_GDMA_EXT_MEM_ENC_ALIGNMENT
-    if (efuse_hal_flash_encryption_enabled()) {
+    if (TRIAL_EXT_MEM_NEEDS_MSPI_ALIGNMENT()) {
         if (esp_ptr_external_ram(input) || esp_ptr_external_ram(buf) || esp_ptr_in_drom(input) || esp_ptr_in_drom(buf)) {