fix: use malloc() for LVGL pool on CONFIG_SPIRAM_USE_MALLOC builds - #354
fix: use malloc() for LVGL pool on CONFIG_SPIRAM_USE_MALLOC builds#354MuddyPaws2 wants to merge 1 commit into
Conversation
With CONFIG_SPIRAM_USE_MALLOC=y the ESP-IDF integrates PSRAM into the regular heap via the standard allocator. In this mode the PSRAM regions do NOT carry the MALLOC_CAP_SPIRAM capability, so: heap_caps_aligned_alloc(32, size, MALLOC_CAP_SPIRAM) returns NULL, causing LVGL's TLSF pool init to crash (null deref at offset 0x0b into the pool). Using plain malloc() is correct: the ESP-IDF CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL threshold ensures large allocations (the pool is typically 1+ MB) go to PSRAM automatically without needing the cap. Affects any ESP32-S3 variant built with CONFIG_SPIRAM_USE_MALLOC=y (the default for arduino-esp32 3.x / IDF 5.x boards with PSRAM).
|
|
📝 WalkthroughWalkthroughThe PSRAM heap configuration in ChangesPSRAM allocator
Estimated code review effort: 1 (Trivial) | ~5 minutes Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@include/lv_conf.h`:
- Around line 71-77: Update the BOARD_HAS_PSRAM allocator conditional in
include/lv_conf.h so the plain malloc() definition is used only when
CONFIG_SPIRAM_USE_MALLOC is enabled. Preserve the existing
heap_caps_aligned_alloc(..., MALLOC_CAP_SPIRAM) path for BOARD_HAS_PSRAM builds
without that configuration, while keeping the non-PSRAM allocator behavior
unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: a606c44e-ab36-4edb-baff-ef73db89e5ac
📒 Files selected for processing (1)
include/lv_conf.h
| #if defined BOARD_HAS_PSRAM | ||
| #define LV_MEM_POOL_INCLUDE <esp_heap_caps.h> | ||
| #define LV_MEM_POOL_ALLOC(size) heap_caps_aligned_alloc(32, size, MALLOC_CAP_SPIRAM) | ||
| /* CONFIG_SPIRAM_USE_MALLOC=y integrates PSRAM into the regular heap so | ||
| * MALLOC_CAP_SPIRAM capability is not set; heap_caps_aligned_alloc with | ||
| * that cap returns NULL. Use plain malloc() — large allocations route to | ||
| * PSRAM automatically. */ | ||
| #define LV_MEM_POOL_INCLUDE <stdlib.h> | ||
| #define LV_MEM_POOL_ALLOC(size) malloc(size) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Repo files matching lv_conf.h:"
fd -a 'lv_conf\.h$' . || true
echo
echo "Relevant includes and config files:"
for f in $(fd -a 'lv_conf\.h$' .); do
echo "--- $f ($(wc -l < "$f") lines) ---"
sed -n '1,130p' "$f"
done
echo
echo "Search for MALLOC_CAP_SPIRAM / LV_MEM_POOL_ALLOC / CONFIG_SPIRAM_USE_MALLOC:"
rg -n "MALLOC_CAP_SPIRAM|LV_MEM_POOL_ALLOC|LV_MEM_POOL_INCLUDE|CONFIG_SPIRAM_USE_MALLOC|BOARD_HAS_PSRAM|heap_caps_aligned_alloc" .Repository: meshtastic/device-ui
Length of output: 6289
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "LGFXDriver relevant lines:"
sed -n '230,295p' include/graphics/driver/LGFXDriver.h
echo
echo "Files mentioning CONFIG_SPIRAM_USE_MALLOC:"
rg -n "CONFIG_SPIRAM_USE_MALLOC|CONFIG_SPIRAM_USE_MALLOC" .
echo
echo "ESP-IDF config/platform indicators for default PSRAM allocator behavior:"
rg -n "CONFIG_SPIRAM_ALLOC_CAP|CONFIG_SPIRAM_USE_MALLOC|SPIRAM_USE_MALLOC|MALLOC_CAP_SPIRAM" -g '*.conf' -g '*.h' -g '*.c' -g '*.md' -g '*.txt' .Repository: meshtastic/device-ui
Length of output: 4548
🌐 Web query:
ESP-IDF CONFIG_SPIRAM_USE_MALLOC heap_caps_aligned_alloc MALLOC_CAP_SPIRAM returns NULL
💡 Result:
In ESP-IDF, when CONFIG_SPIRAM_USE_MALLOC is enabled, the heap allocator adds PSRAM to the pool of memory capable of satisfying malloc() and heap_caps_malloc() requests [1][2]. If heap_caps_aligned_alloc(alignment, size, MALLOC_CAP_SPIRAM) returns NULL, it indicates that the allocator was unable to find a contiguous block in the external PSRAM that meets both the size and alignment requirements [3][4][5]. Common reasons and troubleshooting steps include: 1. PSRAM Initialization Failure: Ensure that the PSRAM is successfully initialized during boot. Check your serial logs for messages like "PSRAM initialized" or "Adding pool of... of external SPI memory to heap allocator" [6][7]. If the PSRAM is not initialized, MALLOC_CAP_SPIRAM allocations will fail [7]. 2. Alignment and Size Constraints: The heap_caps_aligned_alloc function requires a contiguous block of memory [4][5]. Even if the total free PSRAM appears sufficient, heap fragmentation may prevent the allocation of a single block of the requested size and alignment [8]. You can check the largest free block in PSRAM using heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM) to verify if the requested size can actually be satisfied [6]. 3. Misunderstanding MALLOC_CAP_SPIRAM vs. MALLOC_CAP_DEFAULT: While CONFIG_SPIRAM_USE_MALLOC allows malloc() to use PSRAM, explicitly requesting MALLOC_CAP_SPIRAM tells the allocator to only look in PSRAM [1][2]. If PSRAM is full or fragmented, this call will return NULL even if internal memory is available [3][5]. 4. Configuration Conflicts: Ensure your sdkconfig settings are correct for your specific module. Check CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL, which defines a threshold below which allocations prefer internal memory [1][9]. Also, verify that other components (like WiFi/LWIP) are not exhausting the PSRAM pool if you have enabled options like CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP [10]. To diagnose, try the following: - Use heap_caps_get_free_size(MALLOC_CAP_SPIRAM) and heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM) to see the current state of the PSRAM heap [9][6]. - If you suspect fragmentation, consider if you can use EXT_RAM_BSS_ATTR for static buffers instead of dynamic allocation [8][2]. - Verify that your alignment value is a power of two, as required by the API [4].
Citations:
- 1: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/external-ram.html
- 2: https://danielmangum.com/posts/static-alloc-external-ram-esp32/
- 3: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/mem_alloc.html?highlight=malloc
- 4: https://github.com/espressif/esp-idf/blob/v5.1-rc1/components/heap/include/esp_heap_caps.h
- 5: https://github.com/espressif/esp-idf/blob/692c1fcc52b9b936c73dead4ef0c2ea1fbdfb602/components/heap/heap_caps.c
- 6: https://sourcevu.sysprogs.com/espressif/esp-idf/files/components/esp_psram/esp_psram.c
- 7: PSRAM allocation doesn't work (IDFGH-3492) espressif/esp-idf#5449
- 8: SDK Configuration Option "Make RAM allocatable using heap_caps_malloc(..., MALLOC_CAP_SPIRAM)" doesn't work as expected espressif/esp-idf#18391
- 9: Enabling PSRAM with "Make RAM allocatable using malloc() as well" doesn't seem to use external memory (IDFGH-13095) espressif/esp-idf#14041
- 10: https://github.com/espressif/esp-idf/blob/12f36a02/components/esp_psram/Kconfig.spiram.common
Gate the malloc() allocator on CONFIG_SPIRAM_USE_MALLOC.
BOARD_HAS_PSRAM also covers builds where CONFIG_SPIRAM_USE_MALLOC is disabled, in which ESP-IDF does not automatically integrate PSRAM into the default heap. In those builds, LV_MEM_POOL_ALLOC currently routes PSRAM-backed LVGL draw buffers through malloc() instead of the existing heap_caps_aligned_alloc(..., MALLOC_CAP_SPIRAM) path, so allocations can consume internal RAM or fail.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@include/lv_conf.h` around lines 71 - 77, Update the BOARD_HAS_PSRAM allocator
conditional in include/lv_conf.h so the plain malloc() definition is used only
when CONFIG_SPIRAM_USE_MALLOC is enabled. Preserve the existing
heap_caps_aligned_alloc(..., MALLOC_CAP_SPIRAM) path for BOARD_HAS_PSRAM builds
without that configuration, while keeping the non-PSRAM allocator behavior
unchanged.
There was a problem hiding this comment.
Pull request overview
This PR adjusts LVGL’s memory-pool allocation on ESP32 targets with PSRAM so it doesn’t rely on MALLOC_CAP_SPIRAM in builds where PSRAM is integrated into the regular heap via CONFIG_SPIRAM_USE_MALLOC=y, preventing a NULL allocation that can crash LVGL’s pool initialization.
Changes:
- Switch LVGL pool allocation for
BOARD_HAS_PSRAMfromheap_caps_aligned_alloc(..., MALLOC_CAP_SPIRAM)to a standard-heap allocation approach. - Update the pool allocator include accordingly.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /* CONFIG_SPIRAM_USE_MALLOC=y integrates PSRAM into the regular heap so | ||
| * MALLOC_CAP_SPIRAM capability is not set; heap_caps_aligned_alloc with | ||
| * that cap returns NULL. Use plain malloc() — large allocations route to | ||
| * PSRAM automatically. */ | ||
| #define LV_MEM_POOL_INCLUDE <stdlib.h> | ||
| #define LV_MEM_POOL_ALLOC(size) malloc(size) |
With CONFIG_SPIRAM_USE_MALLOC=y the ESP-IDF integrates PSRAM into the regular heap via the standard allocator. In this mode the PSRAM regions do NOT carry the MALLOC_CAP_SPIRAM capability, so:
heap_caps_aligned_alloc(32, size, MALLOC_CAP_SPIRAM)
returns NULL, causing LVGL's TLSF pool init to crash (null deref at offset 0x0b into the pool).
Using plain malloc() is correct: the ESP-IDF CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL threshold ensures large allocations (the pool is typically 1+ MB) go to PSRAM automatically without needing the cap.
Affects any ESP32-S3 variant built with CONFIG_SPIRAM_USE_MALLOC=y (the default for arduino-esp32 3.x / IDF 5.x boards with PSRAM).
Summary by CodeRabbit