Skip to content

Commit e1e8d53

Browse files
committed
feat(sdmmc): support multi-block read/writes
This has the potential of speeding up SD card access significantly. Reusing the DMA aligned buffer gives the additional option of having to allocate the transaction buffer only 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 5c5eb99 commit e1e8d53

File tree

3 files changed

+106
-34
lines changed

3 files changed

+106
-34
lines changed

components/sdmmc/Kconfig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,21 @@ menu "SD Protocol Layer Configuration"
55
help
66
Enable SDIO support.
77
Disabling this will skip SDIO-specific initialization steps
8+
config SD_UNALIGNED_MULTI_BLOCK_RW_MAX_CHUNK_SIZE
9+
int "The maximum size of the chunks a SDMMC read/write to/from an unaligned buffer will be split into"
10+
range 1 99999
11+
default 1
12+
help
13+
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 and the host's
15+
dma_aligned_buffer is NULL, an aligned temporary buffer must be allocated for the actual transfer.
16+
This option defines the maximum size for the temporary buffer, which equals this option's value multiplied
17+
with the block size (typically 512 Bytes). A value of 16 therefore leads to up to 8192 bytes being
18+
allocated on the heap for each transfer. The allocated buffer will never be larger than the number of bytes
19+
to transfer in total.
20+
It also decides whether single (value == 1) or multi block read/write (value > 1) commands are used.
21+
With the default value of 1 single-block read/write commands will be used with the allocated buffer size
22+
matching the block size.
23+
You should keep this option at 1 if your card or configuration doesn't support the read or write multiple
24+
blocks commands (CMD18 & CMD25).
825
endmenu

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: 78 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
*/
66

77
#include <inttypes.h>
8+
#include <sys/param.h> // for MIN/MAX
89
#include "esp_private/sdmmc_common.h"
910

11+
1012
static const char* TAG = "sdmmc_cmd";
1113

1214

@@ -462,31 +464,52 @@ esp_err_t sdmmc_write_sectors(sdmmc_card_t* card, const void* src,
462464
err = sdmmc_write_sectors_dma(card, src, start_block, block_count, block_size * block_count);
463465
} else {
464466
// SDMMC peripheral needs DMA-capable buffers. Split the write into
465-
// separate single block writes, if needed, and allocate a temporary
467+
// separate (multi) block writes, if needed, and allocate a temporary
466468
// DMA-capable buffer.
467-
void *tmp_buf = NULL;
468-
size_t actual_size = 0;
469-
// We don't want to force the allocation into SPIRAM, the allocator
470-
// will decide based on the buffer size and memory availability.
471-
tmp_buf = heap_caps_malloc(block_size, MALLOC_CAP_DMA);
472-
if (!tmp_buf) {
473-
ESP_LOGE(TAG, "%s: not enough mem, err=0x%x", __func__, ESP_ERR_NO_MEM);
474-
return ESP_ERR_NO_MEM;
469+
size_t blocks_per_write = MIN(CONFIG_SD_UNALIGNED_MULTI_BLOCK_RW_MAX_CHUNK_SIZE, block_count);
470+
471+
// prefer using DMA aligned buffer if available over allocating local temporary buffer
472+
bool use_dma_aligned_buffer = (card->host.dma_aligned_buffer != NULL);
473+
void* buf = use_dma_aligned_buffer ? card->host.dma_aligned_buffer : NULL;
474+
475+
// only allocate temporary buffer if we can't use the dma_aligned buffer
476+
if (!use_dma_aligned_buffer) {
477+
// We don't want to force the allocation into SPIRAM, the allocator
478+
// will decide based on the buffer size and memory availability.
479+
buf = heap_caps_malloc(block_size * blocks_per_write, MALLOC_CAP_DMA);
480+
if (!buf) {
481+
ESP_LOGE(TAG, "%s: not enough mem, err=0x%x", __func__, ESP_ERR_NO_MEM);
482+
return ESP_ERR_NO_MEM;
483+
}
484+
}
485+
size_t actual_size = heap_caps_get_allocated_size(buf);
486+
blocks_per_write = actual_size / card->csd.sector_size;
487+
// we should still respect the user configured maximum size
488+
blocks_per_write = MIN(CONFIG_SD_UNALIGNED_MULTI_BLOCK_RW_MAX_CHUNK_SIZE, blocks_per_write);
489+
if (blocks_per_write == 0) {
490+
if (!use_dma_aligned_buffer) {
491+
free(buf);
492+
}
493+
ESP_LOGE(TAG, "%s: buffer smaller than sector size: buf=%d, sector=%d", __func__, actual_size, card->csd.sector_size);
494+
return ESP_ERR_INVALID_SIZE;
475495
}
476-
actual_size = heap_caps_get_allocated_size(tmp_buf);
477496

478497
const uint8_t* cur_src = (const uint8_t*) src;
479-
for (size_t i = 0; i < block_count; ++i) {
480-
memcpy(tmp_buf, cur_src, block_size);
481-
cur_src += block_size;
482-
err = sdmmc_write_sectors_dma(card, tmp_buf, start_block + i, 1, actual_size);
498+
for (size_t i = 0; i < block_count; i += blocks_per_write) {
499+
// make sure not to write more than the remaining blocks, i.e. block_count - i
500+
blocks_per_write = MIN(blocks_per_write, (block_count - i));
501+
memcpy(buf, cur_src, block_size * blocks_per_write);
502+
cur_src += block_size * blocks_per_write;
503+
err = sdmmc_write_sectors_dma(card, buf, start_block + i, blocks_per_write, actual_size);
483504
if (err != ESP_OK) {
484-
ESP_LOGD(TAG, "%s: error 0x%x writing block %d+%d",
485-
__func__, err, start_block, i);
505+
ESP_LOGD(TAG, "%s: error 0x%x writing blocks %d+[%d..%d]",
506+
__func__, err, start_block, i, i + blocks_per_write - 1);
486507
break;
487508
}
488509
}
489-
free(tmp_buf);
510+
if (!use_dma_aligned_buffer) {
511+
free(buf);
512+
}
490513
}
491514
return err;
492515
}
@@ -600,33 +623,55 @@ esp_err_t sdmmc_read_sectors(sdmmc_card_t* card, void* dst,
600623
err = sdmmc_read_sectors_dma(card, dst, start_block, block_count, block_size * block_count);
601624
} else {
602625
// SDMMC peripheral needs DMA-capable buffers. Split the read into
603-
// separate single block reads, if needed, and allocate a temporary
626+
// separate (multi) block reads, if needed, and allocate a temporary
604627
// DMA-capable buffer.
605-
void *tmp_buf = NULL;
606-
size_t actual_size = 0;
607-
tmp_buf = heap_caps_malloc(block_size, MALLOC_CAP_DMA);
608-
if (!tmp_buf) {
609-
ESP_LOGE(TAG, "%s: not enough mem, err=0x%x", __func__, ESP_ERR_NO_MEM);
610-
return ESP_ERR_NO_MEM;
628+
size_t blocks_per_read = MIN(CONFIG_SD_UNALIGNED_MULTI_BLOCK_RW_MAX_CHUNK_SIZE, block_count);
629+
630+
// prefer using DMA aligned buffer if available over allocating local temporary buffer
631+
bool use_dma_aligned_buffer = (card->host.dma_aligned_buffer != NULL);
632+
void* buf = use_dma_aligned_buffer ? card->host.dma_aligned_buffer : NULL;
633+
634+
// only allocate temporary buffer if we can't use the dma_aligned buffer
635+
if (!use_dma_aligned_buffer) {
636+
// We don't want to force the allocation into SPIRAM, the allocator
637+
// will decide based on the buffer size and memory availability.
638+
buf = heap_caps_malloc(block_size * blocks_per_read, MALLOC_CAP_DMA);
639+
if (!buf) {
640+
ESP_LOGE(TAG, "%s: not enough mem, err=0x%x", __func__, ESP_ERR_NO_MEM);
641+
return ESP_ERR_NO_MEM;
642+
}
643+
}
644+
size_t actual_size = heap_caps_get_allocated_size(buf);
645+
blocks_per_read = actual_size / card->csd.sector_size;
646+
// we should still respect the user configured maximum size
647+
blocks_per_read = MIN(CONFIG_SD_UNALIGNED_MULTI_BLOCK_RW_MAX_CHUNK_SIZE, blocks_per_read);
648+
if (blocks_per_read == 0) {
649+
if (!use_dma_aligned_buffer) {
650+
free(buf);
651+
}
652+
ESP_LOGE(TAG, "%s: buffer smaller than sector size: buf=%d, sector=%d", __func__, actual_size, card->csd.sector_size);
653+
return ESP_ERR_INVALID_SIZE;
611654
}
612-
actual_size = heap_caps_get_allocated_size(tmp_buf);
613655

614656
uint8_t* cur_dst = (uint8_t*) dst;
615-
for (size_t i = 0; i < block_count; ++i) {
616-
err = sdmmc_read_sectors_dma(card, tmp_buf, start_block + i, 1, actual_size);
657+
for (size_t i = 0; i < block_count; i += blocks_per_read) {
658+
// make sure not to read more than the remaining blocks, i.e. block_count - i
659+
blocks_per_read = MIN(blocks_per_read, (block_count - i));
660+
err = sdmmc_read_sectors_dma(card, buf, start_block + i, blocks_per_read, actual_size);
617661
if (err != ESP_OK) {
618-
ESP_LOGD(TAG, "%s: error 0x%x writing block %d+%d",
619-
__func__, err, start_block, i);
662+
ESP_LOGD(TAG, "%s: error 0x%x reading blocks %d+[%d..%d]",
663+
__func__, err, start_block, i, i + blocks_per_read - 1);
620664
break;
621665
}
622-
memcpy(cur_dst, tmp_buf, block_size);
623-
cur_dst += block_size;
666+
memcpy(cur_dst, buf, block_size * blocks_per_read);
667+
cur_dst += block_size * blocks_per_read;
668+
}
669+
if (!use_dma_aligned_buffer) {
670+
free(buf);
624671
}
625-
free(tmp_buf);
626672
}
627673
return err;
628674
}
629-
630675
esp_err_t sdmmc_read_sectors_dma(sdmmc_card_t* card, void* dst,
631676
size_t start_block, size_t block_count, size_t buffer_len)
632677
{

0 commit comments

Comments
 (0)