Skip to content

Commit 758c1b8

Browse files
committed
allow users to provide DMA aligned buffer
Reusing this buffer gives the additional option of only having to allocate the transaction buffer once instead of for every transaction, while still keeping the improved transaction time. To give users the maximum amount of control, the Kconfig option for the transaction buffer size applies only to the temporary buffer, which is only allocated if the DMA aligned buffer has not been pre-allocated.
1 parent 3b3d530 commit 758c1b8

File tree

3 files changed

+63
-28
lines changed

3 files changed

+63
-28
lines changed

components/sdmmc/Kconfig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ menu "SD Protocol Layer Configuration"
1111
default 1
1212
help
1313
The maximum size in blocks of the chunks a SDMMC read/write with an unaligned buffer will be split into.
14-
The SDMMC driver requires aligned buffers for DMA access. If unaligned buffers are passed, an aligned
15-
temporary buffer must be allocated for the actual transfer.
14+
The SDMMC driver requires aligned buffers for DMA access. If unaligned buffers are passed and the host's
15+
dma_aligned_buffer is NULL, an aligned temporary buffer must be allocated for the actual transfer.
1616
This option defines the maximum size for the temporary buffer, which equals this option's value multiplied
1717
with the block size (typically 512 Bytes). A value of 16 therefore leads to up to 8192 bytes being
1818
allocated on the heap for each transfer. The allocated buffer will never be larger than the number of bytes

components/sdmmc/include/sd_protocol_types.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,17 @@ typedef struct {
224224
sdmmc_delay_phase_t input_delay_phase; /*!< input delay phase, this will only take into effect when the host works in SDMMC_FREQ_HIGHSPEED or SDMMC_FREQ_52M. Driver will print out how long the delay is*/
225225
esp_err_t (*set_input_delay)(int slot, sdmmc_delay_phase_t delay_phase); /*!< set input delay phase */
226226
esp_err_t (*set_input_delayline)(int slot, sdmmc_delay_line_t delay_line); /*!< set input delay line */
227-
void* dma_aligned_buffer; /*!< Leave it NULL. Reserved for cache aligned buffers for SDIO mode */
227+
/**
228+
* @brief Cache aligned buffer for multi-block RW and IO commands
229+
*
230+
* Use cases:
231+
* - Temporary buffer for multi-block read/write transactions to/from unaligned buffers.
232+
* Allocate with DMA capable memory, size should be an integer multiple of your card's sector size.
233+
* See also Kconfig option SD_UNALIGNED_MULTI_BLOCK_RW_MAX_CHUNK_SIZE.
234+
* - Cache aligned buffer for IO commands in SDIO mode.
235+
* If you allocate manually, make sure it is at least SDMMC_IO_BLOCK_SIZE bytes large.
236+
*/
237+
void* dma_aligned_buffer;
228238
sd_pwr_ctrl_handle_t pwr_ctrl_handle; /*!< Power control handle */
229239
bool (*check_buffer_alignment)(int slot, const void *buf, size_t size); /*!< Check if buffer meets alignment requirements */
230240
esp_err_t (*is_slot_set_to_uhs1)(int slot, bool *is_uhs1); /*!< host slot is set to uhs1 or not*/

components/sdmmc/sdmmc_cmd.c

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -469,31 +469,44 @@ esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src,
469469
// separate (multi) block writes, if needed, and allocate a temporary
470470
// DMA-capable buffer.
471471
size_t blocks_per_write = MIN(CONFIG_SD_UNALIGNED_MULTI_BLOCK_RW_MAX_CHUNK_SIZE, block_count);
472-
void *tmp_buf = NULL;
473-
size_t actual_size = 0;
474-
// We don't want to force the allocation into SPIRAM, the allocator
475-
// will decide based on the buffer size and memory availability.
476-
tmp_buf = heap_caps_malloc(block_size * blocks_per_write, MALLOC_CAP_DMA);
477-
if (!tmp_buf) {
478-
ESP_LOGE(TAG, "%s: not enough mem, err=0x%x", __func__, ESP_ERR_NO_MEM);
479-
return ESP_ERR_NO_MEM;
472+
473+
// prefer using DMA aligned buffer if available over allocating local temporary buffer
474+
bool use_dma_aligned_buffer = (card->host.dma_aligned_buffer != NULL);
475+
void* buf = use_dma_aligned_buffer ? card->host.dma_aligned_buffer : NULL;
476+
477+
// only allocate temporary buffer if we can't use the dma_aligned buffer
478+
if (!use_dma_aligned_buffer) {
479+
// We don't want to force the allocation into SPIRAM, the allocator
480+
// will decide based on the buffer size and memory availability.
481+
buf = heap_caps_malloc(block_size * blocks_per_write, MALLOC_CAP_DMA);
482+
if (!buf) {
483+
ESP_LOGE(TAG, "%s: not enough mem, err=0x%x", __func__, ESP_ERR_NO_MEM);
484+
return ESP_ERR_NO_MEM;
485+
}
486+
}
487+
size_t actual_size = heap_caps_get_allocated_size(buf);
488+
blocks_per_write = actual_size / card->csd.sector_size;
489+
if (blocks_per_write == 0) {
490+
ESP_LOGE(TAG, "%s: buffer smaller than sector size: buf=%d, sector=%d", actual_size, card->csd.sector_size);
491+
return ESP_ERR_INVALID_SIZE;
480492
}
481-
actual_size = heap_caps_get_allocated_size(tmp_buf);
482493

483494
const uint8_t* cur_src = (const uint8_t*) src;
484495
for (size_t i = 0; i < block_count; i += blocks_per_write) {
485496
// make sure not to write more than the remaining blocks, i.e. block_count - i
486497
blocks_per_write = MIN(blocks_per_write, (block_count - i));
487-
memcpy(tmp_buf, cur_src, block_size * blocks_per_write);
498+
memcpy(buf, cur_src, block_size * blocks_per_write);
488499
cur_src += block_size * blocks_per_write;
489-
err = sdmmc_write_sectors_dma(card, tmp_buf, start_block + i, blocks_per_write, actual_size);
500+
err = sdmmc_write_sectors_dma(card, buf, start_block + i, blocks_per_write, actual_size);
490501
if (err != ESP_OK) {
491502
ESP_LOGD(TAG, "%s: error 0x%x writing blocks %d+[%d..%d]",
492503
__func__, err, start_block, i, i + blocks_per_write - 1);
493504
break;
494505
}
495506
}
496-
free(tmp_buf);
507+
if (!use_dma_aligned_buffer) {
508+
free(buf);
509+
}
497510
}
498511
return err;
499512
}
@@ -610,35 +623,47 @@ esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst,
610623
// separate (multi) block reads, if needed, and allocate a temporary
611624
// DMA-capable buffer.
612625
size_t blocks_per_read = MIN(CONFIG_SD_UNALIGNED_MULTI_BLOCK_RW_MAX_CHUNK_SIZE, block_count);
613-
void *tmp_buf = NULL;
614-
size_t actual_size = 0;
615-
// We don't want to force the allocation into SPIRAM, the allocator
616-
// will decide based on the buffer size and memory availability.
617-
tmp_buf = heap_caps_malloc(block_size * blocks_per_read, MALLOC_CAP_DMA);
618-
if (!tmp_buf) {
619-
ESP_LOGE(TAG, "%s: not enough mem, err=0x%x", __func__, ESP_ERR_NO_MEM);
620-
return ESP_ERR_NO_MEM;
626+
627+
// prefer using DMA aligned buffer if available over allocating local temporary buffer
628+
bool use_dma_aligned_buffer = (card->host.dma_aligned_buffer != NULL);
629+
void* buf = use_dma_aligned_buffer ? card->host.dma_aligned_buffer : NULL;
630+
631+
// only allocate temporary buffer if we can't use the dma_aligned buffer
632+
if (!use_dma_aligned_buffer) {
633+
// We don't want to force the allocation into SPIRAM, the allocator
634+
// will decide based on the buffer size and memory availability.
635+
buf = heap_caps_malloc(block_size * blocks_per_read, MALLOC_CAP_DMA);
636+
if (!buf) {
637+
ESP_LOGE(TAG, "%s: not enough mem, err=0x%x", __func__, ESP_ERR_NO_MEM);
638+
return ESP_ERR_NO_MEM;
639+
}
640+
}
641+
size_t actual_size = heap_caps_get_allocated_size(buf);
642+
blocks_per_read = actual_size / card->csd.sector_size;
643+
if (blocks_per_read == 0) {
644+
ESP_LOGE(TAG, "%s: buffer smaller than sector size: buf=%d, sector=%d", actual_size, card->csd.sector_size);
645+
return ESP_ERR_INVALID_SIZE;
621646
}
622-
actual_size = heap_caps_get_allocated_size(tmp_buf);
623647

624648
uint8_t* cur_dst = (uint8_t*) dst;
625649
for (size_t i = 0; i < block_count; i += blocks_per_read) {
626650
// make sure not to read more than the remaining blocks, i.e. block_count - i
627651
blocks_per_read = MIN(blocks_per_read, (block_count - i));
628-
err = sdmmc_read_sectors_dma(card, tmp_buf, start_block + i, blocks_per_read, actual_size);
652+
err = sdmmc_read_sectors_dma(card, buf, start_block + i, blocks_per_read, actual_size);
629653
if (err != ESP_OK) {
630654
ESP_LOGD(TAG, "%s: error 0x%x reading blocks %d+[%d..%d]",
631655
__func__, err, start_block, i, i + blocks_per_read - 1);
632656
break;
633657
}
634-
memcpy(cur_dst, tmp_buf, block_size * blocks_per_read);
658+
memcpy(cur_dst, buf, block_size * blocks_per_read);
635659
cur_dst += block_size * blocks_per_read;
636660
}
637-
free(tmp_buf);
661+
if (!use_dma_aligned_buffer) {
662+
free(buf);
663+
}
638664
}
639665
return err;
640666
}
641-
642667
esp_err_t sdmmc_read_sectors_dma(sdmmc_card_t* card, void* dst,
643668
size_t start_block, size_t block_count, size_t buffer_len)
644669
{

0 commit comments

Comments
 (0)