mirror of
https://github.com/espressif/esp-idf.git
synced 2026-10-02 11:10:54 +03:00
Merge branch 'fix/sdmmc_wait_for_idle_busy_poll_starving_cpu_cores_v6.0' into 'release/v6.0'
fix(sdmmc): back off between CMD13 polls while waiting for card to be ready (v6.0) See merge request espressif/esp-idf!52533
This commit is contained in:
@@ -5,4 +5,39 @@ menu "SD Protocol Layer Configuration"
|
|||||||
help
|
help
|
||||||
Enable SDIO support.
|
Enable SDIO support.
|
||||||
Disabling this will skip SDIO-specific initialization steps
|
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
|
endmenu
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "sdkconfig.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_check.h"
|
#include "esp_check.h"
|
||||||
#include "esp_heap_caps.h"
|
#include "esp_heap_caps.h"
|
||||||
@@ -38,6 +39,27 @@ extern "C" {
|
|||||||
#define SDMMC_INIT_WAIT_DATA_READY_TIMEOUT_US (5000 * 1000)
|
#define SDMMC_INIT_WAIT_DATA_READY_TIMEOUT_US (5000 * 1000)
|
||||||
#define SDMMC_READY_FOR_DATA_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
|
/* 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
|
* 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
|
* 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);
|
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);
|
void sdmmc_flip_byte_order(uint32_t* response, size_t size);
|
||||||
|
|
||||||
esp_err_t sdmmc_fix_host_flags(sdmmc_card_t* card);
|
esp_err_t sdmmc_fix_host_flags(sdmmc_card_t* card);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <inttypes.h>
|
#include <inttypes.h>
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "freertos/FreeRTOS.h"
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "esp_rom_sys.h"
|
||||||
#include "esp_timer.h"
|
#include "esp_timer.h"
|
||||||
#include "esp_private/sdmmc_common.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)
|
esp_err_t sdmmc_wait_for_idle(sdmmc_card_t* card, uint32_t status)
|
||||||
{
|
{
|
||||||
assert(!host_is_spi(card));
|
assert(!host_is_spi(card));
|
||||||
esp_err_t err = ESP_OK;
|
esp_err_t err = ESP_OK;
|
||||||
size_t count = 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 t0 = esp_timer_get_time();
|
||||||
int64_t t1 = 0;
|
int64_t t1 = 0;
|
||||||
/* SD mode: wait for the card to become idle based on R1 status */
|
/* 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) {
|
if (t1 - t0 > SDMMC_READY_FOR_DATA_TIMEOUT_US) {
|
||||||
return ESP_ERR_TIMEOUT;
|
return ESP_ERR_TIMEOUT;
|
||||||
}
|
}
|
||||||
if (t1 - t0 > yield_delay_us) {
|
sdmmc_poll_delay_and_backoff(&poll_period_us);
|
||||||
yield_delay_us *= 2;
|
|
||||||
vTaskDelay(1);
|
|
||||||
}
|
|
||||||
err = sdmmc_send_cmd_send_status(card, &status);
|
err = sdmmc_send_cmd_send_status(card, &status);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "%s: sdmmc_send_cmd_send_status returned 0x%x", __func__, err);
|
ESP_LOGE(TAG, "%s: sdmmc_send_cmd_send_status returned 0x%x", __func__, err);
|
||||||
|
|||||||
@@ -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 */
|
/* Wait for the card to be ready for data transfers */
|
||||||
uint32_t status = 0;
|
uint32_t status = 0;
|
||||||
uint32_t count = 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 t0 = esp_timer_get_time();
|
||||||
int64_t t1 = 0;
|
int64_t t1 = 0;
|
||||||
while (!host_is_spi(card) && !(status & MMC_R1_READY_FOR_DATA)) {
|
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");
|
ESP_LOGE(TAG, "init wait data ready - timeout");
|
||||||
return ESP_ERR_TIMEOUT;
|
return ESP_ERR_TIMEOUT;
|
||||||
}
|
}
|
||||||
if (t1 - t0 > yield_delay_us) {
|
sdmmc_poll_delay_and_backoff(&poll_period_us);
|
||||||
yield_delay_us *= 2;
|
|
||||||
vTaskDelay(1);
|
|
||||||
}
|
|
||||||
esp_err_t err = sdmmc_send_cmd_send_status(card, &status);
|
esp_err_t err = sdmmc_send_cmd_send_status(card, &status);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
return err;
|
return err;
|
||||||
@@ -396,7 +393,7 @@ static esp_err_t read_tuning_block(sdmmc_card_t *card)
|
|||||||
|
|
||||||
uint32_t status = 0;
|
uint32_t status = 0;
|
||||||
size_t count = 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 t0 = esp_timer_get_time();
|
||||||
int64_t t1 = 0;
|
int64_t t1 = 0;
|
||||||
while (!host_is_spi(card) && !(status & MMC_R1_READY_FOR_DATA)) {
|
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");
|
ESP_LOGW(TAG, "read sectors dma - timeout");
|
||||||
return ESP_ERR_TIMEOUT;
|
return ESP_ERR_TIMEOUT;
|
||||||
}
|
}
|
||||||
if (t1 - t0 > yield_delay_us) {
|
sdmmc_poll_delay_and_backoff(&poll_period_us);
|
||||||
yield_delay_us *= 2;
|
|
||||||
vTaskDelay(1);
|
|
||||||
}
|
|
||||||
ret = sdmmc_send_cmd_send_status(card, &status);
|
ret = sdmmc_send_cmd_send_status(card, &status);
|
||||||
if (ret != ESP_OK) {
|
if (ret != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "%s: sdmmc_send_cmd_send_status returned 0x%x", __func__, ret);
|
ESP_LOGW(TAG, "%s: sdmmc_send_cmd_send_status returned 0x%x", __func__, ret);
|
||||||
|
|||||||
Reference in New Issue
Block a user