diff --git a/components/sdmmc/Kconfig b/components/sdmmc/Kconfig index 0d2ed43feee..ff098697741 100644 --- a/components/sdmmc/Kconfig +++ b/components/sdmmc/Kconfig @@ -5,4 +5,39 @@ menu "SD Protocol Layer Configuration" help Enable SDIO support. Disabling this will skip SDIO-specific initialization steps + + config SD_READY_POLL_PERIOD_START_US + int "Initial delay between card status polls (us)" + default 100 + range 1 10000 + help + While waiting for the card to leave its busy state (for example after a write), + the driver polls it with CMD13 (SEND_STATUS). This is the delay before the + first re-poll; the delay then doubles after every poll, up to one FreeRTOS + tick period. + + Polling back-to-back without a delay turns every write into a storm of + hundreds of CMD13 commands, which occupies the host controller and slows down + unrelated work on both cores. + + The exponential backoff means a card that becomes ready after 1.5 ms is + detected at about 1.5 ms rather than at the next FreeRTOS tick, using a + handful of commands instead of hundreds. Once the delay reaches one tick + period it stops growing, so a long busy period costs one poll per tick and + is detected within one tick of the card becoming ready. + + Lower this value to detect very short busy periods sooner, at the cost of + more commands on the bus. Values larger than one FreeRTOS tick period are + clamped to it, so the largest useful setting depends on CONFIG_FREERTOS_HZ. + + Delays shorter than one FreeRTOS tick period are spent busy-waiting, since + there is no shorter blocking sleep available; a delay of one tick blocks on + vTaskDelay() and lets other tasks run. + + The busy-waited part of the ramp does not yield, so lower-priority tasks on + that core are held off for its duration. It lasts at most until the backoff + reaches one tick: with the default 100 us start, about 1.5 ms at a 1 kHz + tick (100+200+400+800 us) and about 12.7 ms at a 100 Hz tick. A card that + becomes ready sooner ends the wait sooner. No CMD13 is issued during the + delays, so the host controller and the bus stay idle throughout. endmenu diff --git a/components/sdmmc/include/esp_private/sdmmc_common.h b/components/sdmmc/include/esp_private/sdmmc_common.h index 83dc1df664e..2b4c768883b 100644 --- a/components/sdmmc/include/esp_private/sdmmc_common.h +++ b/components/sdmmc/include/esp_private/sdmmc_common.h @@ -17,6 +17,7 @@ #pragma once #include +#include "sdkconfig.h" #include "esp_log.h" #include "esp_check.h" #include "esp_heap_caps.h" @@ -38,6 +39,27 @@ extern "C" { #define SDMMC_INIT_WAIT_DATA_READY_TIMEOUT_US (5000 * 1000) #define SDMMC_READY_FOR_DATA_TIMEOUT_US (5000 * 1000) +/* Delay between two consecutive card status (CMD13) polls while waiting for the + * card to become ready. Starts at SDMMC_READY_POLL_PERIOD_START_US and doubles + * after every poll, up to one FreeRTOS tick period. + * + * Polling without a delay turns every write into a storm of hundreds of CMD13 + * commands, which occupies the host controller and slows down unrelated work. + * + * While the delay is below one tick period it is busy-waited and doubles, so a + * handful of commands covers a busy period of a few milliseconds and a card that + * becomes ready early is noticed without waiting for the next tick. + * + * Once the delay reaches one tick period it stops growing: from there on + * vTaskDelay() already yields and one command per tick is not a storm, while a + * longer delay would only delay noticing that the card is ready. Long busy + * periods therefore cost one poll per tick and are detected within one tick. + * + * One tick period is a hard cap: a configured start period larger than that is + * clamped to it as well. + */ +#define SDMMC_READY_POLL_PERIOD_START_US CONFIG_SD_READY_POLL_PERIOD_START_US + /* These delay values are mostly useful for cases when CD pin is not used, and * the card is removed. In this case, SDMMC peripheral may not always return * CMD_DONE / DATA_DONE interrupts after signaling the error. These timeouts work @@ -184,6 +206,19 @@ static inline bool sdmmc_ready_for_data(uint32_t status) return (status & MMC_R1_READY_FOR_DATA) && (MMC_R1_CURRENT_STATE_STATUS(status) == MMC_R1_CURRENT_STATE_TRAN); } +/** + * @brief Wait before the next card status poll, and back off for the poll after that + * + * Delays for *period_us, then doubles *period_us. Both the delay and the stored + * period are capped at one FreeRTOS tick period, so a *period_us larger than that + * on entry is clamped rather than used as-is. A delay shorter than one tick period + * is busy-waited, since no shorter blocking sleep is available; one tick period + * blocks on vTaskDelay() so that other tasks can run. + * + * @param[in,out] period_us Delay to apply now; updated to the delay to apply next time + */ +void sdmmc_poll_delay_and_backoff(uint32_t* period_us); + void sdmmc_flip_byte_order(uint32_t* response, size_t size); esp_err_t sdmmc_fix_host_flags(sdmmc_card_t* card); diff --git a/components/sdmmc/sdmmc_common.c b/components/sdmmc/sdmmc_common.c index 498aac22061..c5558748b57 100644 --- a/components/sdmmc/sdmmc_common.c +++ b/components/sdmmc/sdmmc_common.c @@ -18,6 +18,7 @@ #include #include "esp_log.h" #include "freertos/FreeRTOS.h" +#include "esp_rom_sys.h" #include "esp_timer.h" #include "esp_private/sdmmc_common.h" @@ -441,12 +442,33 @@ uint32_t sdmmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t er } } +void sdmmc_poll_delay_and_backoff(uint32_t* period_us) +{ + const uint32_t us_per_tick = portTICK_PERIOD_MS * 1000; + /* Clamp on entry as well: a configured start period longer than a tick would + * otherwise be used as-is and never brought back down to the cap. */ + uint32_t delay_us = MIN(*period_us, us_per_tick); + + if (delay_us < us_per_tick) { + /* No blocking sleep with sub-tick resolution is available, busy-wait instead. + * The point of the delay is to keep CMD13 off the bus, which this still does. */ + esp_rom_delay_us(delay_us); + } else { + vTaskDelay(1); + } + + /* Stop growing once the delay reaches one tick period. At that point vTaskDelay() + * already yields and one command per tick is not a storm, so a longer delay would + * only add overshoot to the time the card is detected as ready. */ + *period_us = MIN(delay_us * 2, us_per_tick); +} + esp_err_t sdmmc_wait_for_idle(sdmmc_card_t* card, uint32_t status) { assert(!host_is_spi(card)); esp_err_t err = ESP_OK; size_t count = 0; - int64_t yield_delay_us = 100 * 1000; // initially 100ms + uint32_t poll_period_us = SDMMC_READY_POLL_PERIOD_START_US; int64_t t0 = esp_timer_get_time(); int64_t t1 = 0; /* SD mode: wait for the card to become idle based on R1 status */ @@ -455,10 +477,7 @@ esp_err_t sdmmc_wait_for_idle(sdmmc_card_t* card, uint32_t status) if (t1 - t0 > SDMMC_READY_FOR_DATA_TIMEOUT_US) { return ESP_ERR_TIMEOUT; } - if (t1 - t0 > yield_delay_us) { - yield_delay_us *= 2; - vTaskDelay(1); - } + sdmmc_poll_delay_and_backoff(&poll_period_us); err = sdmmc_send_cmd_send_status(card, &status); if (err != ESP_OK) { ESP_LOGE(TAG, "%s: sdmmc_send_cmd_send_status returned 0x%x", __func__, err); diff --git a/components/sdmmc/sdmmc_sd.c b/components/sdmmc/sdmmc_sd.c index ece0e2054dd..17d670b7211 100644 --- a/components/sdmmc/sdmmc_sd.c +++ b/components/sdmmc/sdmmc_sd.c @@ -150,7 +150,7 @@ esp_err_t sdmmc_init_sd_wait_data_ready(sdmmc_card_t* card) /* Wait for the card to be ready for data transfers */ uint32_t status = 0; uint32_t count = 0; - int64_t yield_delay_us = 100 * 1000; // initially 100ms + uint32_t poll_period_us = SDMMC_READY_POLL_PERIOD_START_US; int64_t t0 = esp_timer_get_time(); int64_t t1 = 0; while (!host_is_spi(card) && !(status & MMC_R1_READY_FOR_DATA)) { @@ -159,10 +159,7 @@ esp_err_t sdmmc_init_sd_wait_data_ready(sdmmc_card_t* card) ESP_LOGE(TAG, "init wait data ready - timeout"); return ESP_ERR_TIMEOUT; } - if (t1 - t0 > yield_delay_us) { - yield_delay_us *= 2; - vTaskDelay(1); - } + sdmmc_poll_delay_and_backoff(&poll_period_us); esp_err_t err = sdmmc_send_cmd_send_status(card, &status); if (err != ESP_OK) { return err; @@ -396,7 +393,7 @@ static esp_err_t read_tuning_block(sdmmc_card_t *card) uint32_t status = 0; size_t count = 0; - int64_t yield_delay_us = 100 * 1000; // initially 100ms + uint32_t poll_period_us = SDMMC_READY_POLL_PERIOD_START_US; int64_t t0 = esp_timer_get_time(); int64_t t1 = 0; while (!host_is_spi(card) && !(status & MMC_R1_READY_FOR_DATA)) { @@ -405,10 +402,7 @@ static esp_err_t read_tuning_block(sdmmc_card_t *card) ESP_LOGW(TAG, "read sectors dma - timeout"); return ESP_ERR_TIMEOUT; } - if (t1 - t0 > yield_delay_us) { - yield_delay_us *= 2; - vTaskDelay(1); - } + sdmmc_poll_delay_and_backoff(&poll_period_us); ret = sdmmc_send_cmd_send_status(card, &status); if (ret != ESP_OK) { ESP_LOGW(TAG, "%s: sdmmc_send_cmd_send_status returned 0x%x", __func__, ret);