fix(sdmmc): Validate DMA buffer address range

Closes https://github.com/espressif/esp-idf/issues/18714
This commit is contained in:
Adam Múdry
2026-07-28 15:55:03 +02:00
parent 5000b928d5
commit e244cd54fb
9 changed files with 206 additions and 4 deletions
+27 -2
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -1286,13 +1286,38 @@ esp_err_t sdmmc_host_get_dma_info(int slot, esp_dma_mem_info_t *dma_mem_info)
return ESP_OK;
}
static bool sdmmc_dma_accessible(const void *ptr)
{
if (esp_ptr_external_ram(ptr)) {
#if SOC_SDMMC_PSRAM_DMA_CAPABLE
return esp_ptr_dma_ext_capable(ptr);
#else
return false;
#endif
}
return esp_ptr_dma_capable(ptr);
}
bool sdmmc_host_check_buffer_alignment(int slot, const void *buf, size_t size)
{
//for future-proof
(void)slot;
if (!buf || !size) {
return ESP_FAIL;
return false;
}
uintptr_t start = (uintptr_t)buf;
if (size - 1 > UINTPTR_MAX - start) {
return false;
}
const void *end = (const void *)(start + size - 1);
bool not_dma_accessible = !sdmmc_dma_accessible(buf) || !sdmmc_dma_accessible(end);
bool different_memory_types = esp_ptr_external_ram(buf) != esp_ptr_external_ram(end);
if (not_dma_accessible || different_memory_types) {
return false;
}
esp_err_t ret = ESP_FAIL;
@@ -73,6 +73,31 @@ void sdmmc_test_rw_highprio_task(sdmmc_card_t* card);
*/
void sdmmc_test_rw_unaligned_buffer_multiblock(sdmmc_card_t* card, size_t chunk_size);
/**
* @brief Test read/write with PSRAM-allocated buffers
*
* This function verifies that the driver correctly handles buffers allocated in PSRAM.
* When the host reports the PSRAM buffer as directly usable (via check_buffer_alignment),
* data is transferred directly without intermediate copying. Otherwise, the driver uses
* double-buffering through internal RAM.
*
* The test covers:
* - Writing from PSRAM, reading to internal RAM
* - Writing from internal RAM, reading to PSRAM
* - Both writing and reading from PSRAM
*
* If PSRAM is not enabled (CONFIG_SPIRAM), this function is a no-op. Callers
* must skip the test (TEST_IGNORE) BEFORE initializing the slot/controller, so
* that this function is not relied upon to abort the test. Doing the skip here
* would longjmp out of the test before the caller's cleanup runs and leak the
* SD slot.
*
* This test function works both with SDMMC and SDSPI hosts.
*
* @param card Pointer to the card object, must be initialized before calling this function.
*/
void sdmmc_test_rw_psram_buffer(sdmmc_card_t *card);
#ifdef __cplusplus
};
#endif
@@ -10,6 +10,7 @@
#include <unistd.h>
#include <sys/time.h>
#include "esp_heap_caps.h"
#include "esp_memory_utils.h"
#include "esp_timer.h"
#include "test_utils.h"
#include "sdkconfig.h"
@@ -294,3 +295,71 @@ void sdmmc_test_rw_unaligned_buffer_multiblock(sdmmc_card_t* card, size_t chunk_
free(buffer);
}
void sdmmc_test_rw_psram_buffer(sdmmc_card_t *card)
{
#if !CONFIG_SPIRAM
/* Callers must skip before initializing the hardware so cleanup is not bypassed. */
(void)card;
#else
const size_t block_size = card->csd.sector_size;
const size_t block_count = 8;
const size_t buffer_size = block_size * block_count;
uint8_t *psram_buf = heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED);
TEST_ASSERT_NOT_NULL_MESSAGE(psram_buf, "Failed to allocate PSRAM buffer; is PSRAM enabled?");
TEST_ASSERT_MESSAGE(esp_ptr_external_ram(psram_buf), "Buffer not in PSRAM");
uint8_t *internal_buf = heap_caps_malloc(buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED);
TEST_ASSERT_NOT_NULL(internal_buf);
bool host_can_use_psram_directly =
card->host.check_buffer_alignment(card->host.slot, psram_buf, buffer_size);
printf("Testing PSRAM buffer R/W: %d blocks, host can use PSRAM directly=%s\n",
(int)block_count, host_can_use_psram_directly ? "true" : "false");
const uint32_t seed_a = 0xABCD1234;
fill_buffer(seed_a, psram_buf, buffer_size / sizeof(uint32_t));
TEST_ESP_OK(sdmmc_write_sectors(card, psram_buf, 0, block_count));
memset(internal_buf, 0xcc, buffer_size);
TEST_ESP_OK(sdmmc_read_sectors(card, internal_buf, 0, block_count));
check_buffer(seed_a, internal_buf, buffer_size / sizeof(uint32_t));
const uint32_t seed_b = 0x5678EFAB;
fill_buffer(seed_b, internal_buf, buffer_size / sizeof(uint32_t));
TEST_ESP_OK(sdmmc_write_sectors(card, internal_buf, 0, block_count));
memset(psram_buf, 0xcc, buffer_size);
TEST_ESP_OK(sdmmc_read_sectors(card, psram_buf, 0, block_count));
check_buffer(seed_b, psram_buf, buffer_size / sizeof(uint32_t));
const uint32_t seed_c = 0xDEAD9876;
fill_buffer(seed_c, psram_buf, buffer_size / sizeof(uint32_t));
TEST_ESP_OK(sdmmc_write_sectors(card, psram_buf, 0, block_count));
memset(psram_buf, 0xcc, buffer_size);
TEST_ESP_OK(sdmmc_read_sectors(card, psram_buf, 0, block_count));
check_buffer(seed_c, psram_buf, buffer_size / sizeof(uint32_t));
const size_t misalign = 1;
uint8_t *unaligned_psram = heap_caps_malloc(buffer_size + misalign,
MALLOC_CAP_SPIRAM | MALLOC_CAP_DMA);
TEST_ASSERT_NOT_NULL_MESSAGE(unaligned_psram, "Failed to allocate PSRAM buffer");
uint8_t *src = unaligned_psram + misalign;
TEST_ASSERT_MESSAGE(esp_ptr_external_ram(src), "Buffer not in PSRAM");
const uint32_t seed_d = 0x13572468;
for (size_t i = 0; i < buffer_size; i++) {
src[i] = (uint8_t)(seed_d + i);
}
TEST_ESP_OK(sdmmc_write_sectors(card, src, 0, block_count));
memset(internal_buf, 0xcc, buffer_size);
TEST_ESP_OK(sdmmc_read_sectors(card, internal_buf, 0, block_count));
for (size_t i = 0; i < buffer_size; i++) {
TEST_ASSERT_EQUAL_HEX8((uint8_t)(seed_d + i), internal_buf[i]);
}
free(unaligned_psram);
free(psram_buf);
free(internal_buf);
printf("PSRAM buffer R/W test passed\n");
#endif
}
@@ -15,6 +15,7 @@ endif()
set(priv_requires "sdmmc"
"esp_driver_sdmmc"
"esp_psram"
"sdmmc_test_boards"
"common_test_flows"
"unity"
@@ -1,11 +1,12 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <stddef.h>
#include "unity.h"
#include "esp_psram.h"
#include "sdmmc_cmd.h"
#include "sdmmc_test_begin_end_sd.h"
#include "sdmmc_test_rw_common.h"
@@ -128,3 +129,30 @@ TEST_CASE("sdmmc read/write with concurrent high-prio task, slot 1, 4-bit", "[sd
{
do_one_sdmmc_rw_test_highprio_task(1, 4);
}
static void do_one_sdmmc_rw_test_psram_dma_buffer(int slot, int width)
{
sdmmc_card_t card;
int freq_khz = SDMMC_FREQ_HIGHSPEED;
#if !CONFIG_SPIRAM
TEST_IGNORE_MESSAGE("PSRAM is not enabled");
#else
if (!esp_psram_is_initialized()) {
TEST_IGNORE_MESSAGE("PSRAM is not available");
}
#endif
sdmmc_test_sd_skip_if_board_incompatible(slot, width, freq_khz, NO_DDR, NO_EMMC);
sdmmc_test_sd_begin(slot, width, freq_khz, 0, &card);
sdmmc_test_rw_psram_buffer(&card);
sdmmc_test_sd_end(&card);
}
TEST_CASE("sdmmc read/write using PSRAM DMA accessible buffer, slot 0, 4-bit", "[sdmmc]")
{
do_one_sdmmc_rw_test_psram_dma_buffer(0, 4);
}
TEST_CASE("sdmmc read/write using PSRAM DMA accessible buffer, slot 1, 4-bit", "[sdmmc]")
{
do_one_sdmmc_rw_test_psram_dma_buffer(1, 4);
}
@@ -1,4 +1,4 @@
set(srcs "test_app_main.c")
set(srcs "test_app_main.c" "test_sdmmc_buffer.c")
set(priv_requires
# tests reside in this component, also available for `sdmmc_console`
@@ -7,6 +7,7 @@ set(priv_requires
unity
# for PSRAM tests
esp_psram
esp_driver_sdmmc
)
idf_component_register(SRCS ${srcs}
@@ -0,0 +1,49 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "sdkconfig.h"
#include "unity.h"
#include "driver/sdmmc_host.h"
#include "soc/soc.h"
#include "esp_heap_caps.h"
#include "esp_memory_utils.h"
#include "esp_psram.h"
#if SOC_RTC_FAST_MEM_SUPPORTED
TEST_CASE("SDMMC rejects an RTC fast RAM DMA buffer", "[sdmmc]")
{
const size_t buffer_size = 512;
void *internal_dma_buf = heap_caps_malloc(buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED);
const size_t offset = 0x20;
const void *rtc_fast_ram_buf = (const void *)(SOC_RTC_DRAM_LOW + offset);
TEST_ASSERT_NOT_NULL(internal_dma_buf);
TEST_ASSERT_TRUE(sdmmc_host_check_buffer_alignment(0, internal_dma_buf, buffer_size));
TEST_ASSERT_FALSE(sdmmc_host_check_buffer_alignment(0, rtc_fast_ram_buf, buffer_size));
TEST_ASSERT_FALSE(sdmmc_host_check_buffer_alignment(0, (const void *)(UINTPTR_MAX - 255), buffer_size));
heap_caps_free(internal_dma_buf);
}
#endif
#if CONFIG_SPIRAM && !SOC_SDMMC_PSRAM_DMA_CAPABLE
TEST_CASE("SDMMC rejects a PSRAM buffer when PSRAM is not DMA capable", "[sdmmc]")
{
const size_t buffer_size = 512;
if (!esp_psram_is_initialized()) {
TEST_IGNORE_MESSAGE("PSRAM is not available");
}
void *psram_buf = heap_caps_malloc(buffer_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_CACHE_ALIGNED);
TEST_ASSERT_NOT_NULL(psram_buf);
TEST_ASSERT_TRUE(esp_ptr_external_ram(psram_buf));
TEST_ASSERT_FALSE(sdmmc_host_check_buffer_alignment(0, psram_buf, buffer_size));
heap_caps_free(psram_buf);
}
#endif
@@ -0,0 +1,2 @@
CONFIG_SPIRAM=y
CONFIG_SPIRAM_IGNORE_NOTFOUND=y
@@ -0,0 +1,2 @@
CONFIG_SPIRAM=y
CONFIG_SPIRAM_IGNORE_NOTFOUND=y