From e244cd54fb5a6c766956cd20c13e30b47e6f4910 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20M=C3=BAdry?= Date: Fri, 24 Jul 2026 13:20:11 +0200 Subject: [PATCH] fix(sdmmc): Validate DMA buffer address range Closes https://github.com/espressif/esp-idf/issues/18714 --- components/esp_driver_sdmmc/src/sdmmc_host.c | 29 +++++++- .../include/sdmmc_test_rw_common.h | 25 +++++++ .../common_test_flows/sdmmc_test_rw_common.c | 69 +++++++++++++++++++ .../components/sdmmc_tests/CMakeLists.txt | 1 + .../components/sdmmc_tests/sdmmc_test_rw_sd.c | 30 +++++++- .../test_apps/sdmmc/main/CMakeLists.txt | 3 +- .../test_apps/sdmmc/main/test_sdmmc_buffer.c | 49 +++++++++++++ .../test_apps/sdmmc/sdkconfig.defaults.esp32 | 2 + .../sdmmc/sdkconfig.defaults.esp32s3 | 2 + 9 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 components/esp_driver_sdmmc/test_apps/sdmmc/main/test_sdmmc_buffer.c create mode 100644 components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32 create mode 100644 components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32s3 diff --git a/components/esp_driver_sdmmc/src/sdmmc_host.c b/components/esp_driver_sdmmc/src/sdmmc_host.c index 4a84723e212..111a6855332 100644 --- a/components/esp_driver_sdmmc/src/sdmmc_host.c +++ b/components/esp_driver_sdmmc/src/sdmmc_host.c @@ -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; diff --git a/components/esp_driver_sdmmc/test_apps/sd_test_utils/components/common_test_flows/include/sdmmc_test_rw_common.h b/components/esp_driver_sdmmc/test_apps/sd_test_utils/components/common_test_flows/include/sdmmc_test_rw_common.h index d31c917b2e2..6061b821ea3 100644 --- a/components/esp_driver_sdmmc/test_apps/sd_test_utils/components/common_test_flows/include/sdmmc_test_rw_common.h +++ b/components/esp_driver_sdmmc/test_apps/sd_test_utils/components/common_test_flows/include/sdmmc_test_rw_common.h @@ -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 diff --git a/components/esp_driver_sdmmc/test_apps/sd_test_utils/components/common_test_flows/sdmmc_test_rw_common.c b/components/esp_driver_sdmmc/test_apps/sd_test_utils/components/common_test_flows/sdmmc_test_rw_common.c index 6a336e74f7a..617e8397af5 100644 --- a/components/esp_driver_sdmmc/test_apps/sd_test_utils/components/common_test_flows/sdmmc_test_rw_common.c +++ b/components/esp_driver_sdmmc/test_apps/sd_test_utils/components/common_test_flows/sdmmc_test_rw_common.c @@ -10,6 +10,7 @@ #include #include #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 +} diff --git a/components/esp_driver_sdmmc/test_apps/sdmmc/components/sdmmc_tests/CMakeLists.txt b/components/esp_driver_sdmmc/test_apps/sdmmc/components/sdmmc_tests/CMakeLists.txt index 96fe18ea964..8e184bdd07c 100644 --- a/components/esp_driver_sdmmc/test_apps/sdmmc/components/sdmmc_tests/CMakeLists.txt +++ b/components/esp_driver_sdmmc/test_apps/sdmmc/components/sdmmc_tests/CMakeLists.txt @@ -15,6 +15,7 @@ endif() set(priv_requires "sdmmc" "esp_driver_sdmmc" + "esp_psram" "sdmmc_test_boards" "common_test_flows" "unity" diff --git a/components/esp_driver_sdmmc/test_apps/sdmmc/components/sdmmc_tests/sdmmc_test_rw_sd.c b/components/esp_driver_sdmmc/test_apps/sdmmc/components/sdmmc_tests/sdmmc_test_rw_sd.c index 8c1a5c36a76..3e04d2ed1dc 100644 --- a/components/esp_driver_sdmmc/test_apps/sdmmc/components/sdmmc_tests/sdmmc_test_rw_sd.c +++ b/components/esp_driver_sdmmc/test_apps/sdmmc/components/sdmmc_tests/sdmmc_test_rw_sd.c @@ -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 #include #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); +} diff --git a/components/esp_driver_sdmmc/test_apps/sdmmc/main/CMakeLists.txt b/components/esp_driver_sdmmc/test_apps/sdmmc/main/CMakeLists.txt index e253a3431bb..b5274231927 100644 --- a/components/esp_driver_sdmmc/test_apps/sdmmc/main/CMakeLists.txt +++ b/components/esp_driver_sdmmc/test_apps/sdmmc/main/CMakeLists.txt @@ -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} diff --git a/components/esp_driver_sdmmc/test_apps/sdmmc/main/test_sdmmc_buffer.c b/components/esp_driver_sdmmc/test_apps/sdmmc/main/test_sdmmc_buffer.c new file mode 100644 index 00000000000..d3448959a5f --- /dev/null +++ b/components/esp_driver_sdmmc/test_apps/sdmmc/main/test_sdmmc_buffer.c @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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 diff --git a/components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32 b/components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32 new file mode 100644 index 00000000000..f18d2429dae --- /dev/null +++ b/components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32 @@ -0,0 +1,2 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_IGNORE_NOTFOUND=y diff --git a/components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32s3 b/components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32s3 new file mode 100644 index 00000000000..f18d2429dae --- /dev/null +++ b/components/esp_driver_sdmmc/test_apps/sdmmc/sdkconfig.defaults.esp32s3 @@ -0,0 +1,2 @@ +CONFIG_SPIRAM=y +CONFIG_SPIRAM_IGNORE_NOTFOUND=y