fix(sdmmc): Add a better handling of devices accessing PSRAM through DMA

Closes https://github.com/espressif/esp-idf/issues/18412
This commit is contained in:
Adam Múdry
2026-07-21 15:03:07 +02:00
parent 406ac81a03
commit d52b12fe11
9 changed files with 182 additions and 36 deletions
@@ -460,6 +460,16 @@ bool sd_host_check_buffer_alignment(sd_host_sdmmc_slot_t *slot, const void *buf,
return false;
}
#if !SOC_SDMMC_PSRAM_DMA_CAPABLE
// The SDMMC peripheral's DMA cannot reach PSRAM on this target, so a PSRAM
// buffer can never be used directly regardless of its alignment. Reporting
// it as not directly usable makes the protocol layer fall back to an
// internal DMA-capable buffer.
if (esp_ptr_external_ram(buf)) {
return false;
}
#endif
esp_err_t ret = ESP_FAIL;
int cache_flags = 0;
size_t cache_alignment_bytes = 0;
@@ -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
@@ -14,6 +14,7 @@
#include "test_utils.h"
#include "sdkconfig.h"
#include "soc/soc_caps.h"
#include "esp_memory_utils.h"
#include "unity.h"
#include "sd_protocol_defs.h"
#include "sdmmc_cmd.h"
@@ -295,3 +296,64 @@ 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
/* The PSRAM-disabled skip is intentionally NOT done here via TEST_IGNORE.
* This function runs after the slot/controller has been initialized, and
* TEST_IGNORE would longjmp out before the caller's *_end() cleanup runs,
* leaking the slot. Callers must skip before initializing the hardware.
*/
(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;
/* Allocate buffer in PSRAM */
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");
/* Also allocate a reference buffer in internal RAM for comparison */
uint8_t *internal_buf = heap_caps_malloc(buffer_size, MALLOC_CAP_DMA | MALLOC_CAP_CACHE_ALIGNED);
TEST_ASSERT_NOT_NULL(internal_buf);
/* A PSRAM buffer can be transferred directly only if the host reports it as
* usable; otherwise the protocol layer transparently bounce-buffers it. This
* test verifies correctness for both cases. */
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");
/* Test A: Write from PSRAM buffer, read back to internal buffer, verify */
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));
/* Test B: Write from internal buffer, read back to PSRAM buffer, verify */
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));
/* Test C: Both write and read from PSRAM buffer */
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));
free(psram_buf);
free(internal_buf);
printf("PSRAM buffer R/W test passed\n");
#endif // CONFIG_SPIRAM
}
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2022-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2022-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -128,3 +128,26 @@ 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");
#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);
}