sdmmc: better calculation of erase timeout

Previous version of the code used a fixed constant (500 ms) for the
erase timeout and added 1 ms for each sector erased.
This commit improves timeouts calculation:
- For SD cards, check if erase timeout information is present in the
  SSR register. If yes, use it for erase timeout calculation.
  Otherwise assume 250ms per erase block, same as Linux does.
- For eMMC assume 250ms per erase block (but no less than 1 second).
  This has to be improved later to use the erase timeout info in the
  extended CSD register.
This commit is contained in:
Ivan Grokhotkov 2022-05-30 15:37:40 +02:00
parent a28828a6f4
commit 79659e3096
No known key found for this signature in database
GPG Key ID: 1E050E141B280628
7 changed files with 85 additions and 10 deletions

View File

@ -372,6 +372,9 @@
/* SSR (SD Status Register) */
#define SSR_DAT_BUS_WIDTH(ssr) MMC_RSP_BITS((ssr), 510, 2)
#define SSR_AU_SIZE(ssr) MMC_RSP_BITS((ssr), 428, 4)
#define SSR_ERASE_SIZE(ssr) MMC_RSP_BITS((ssr), 408, 16)
#define SSR_ERASE_TIMEOUT(ssr) MMC_RSP_BITS((ssr), 402, 6)
#define SSR_ERASE_OFFSET(ssr) MMC_RSP_BITS((ssr), 400, 2)
#define SSR_DISCARD_SUPPORT(ssr) MMC_RSP_BITS((ssr), 313, 1)
#define SSR_FULE_SUPPORT(ssr) MMC_RSP_BITS((ssr), 312, 1)

View File

@ -71,10 +71,14 @@ typedef struct {
* Note: When new member is added, update reserved bits accordingly
*/
typedef struct {
uint32_t alloc_unit_kb: 16; /*!< Allocation unit of the card, in multiples of kB (1024 bytes) */
uint32_t erase_size_au: 16; /*!< Erase size for the purpose of timeout calculation, in multiples of allocation unit */
uint32_t cur_bus_width: 2; /*!< SD current bus width */
uint32_t discard_support: 1; /*!< SD discard feature support */
uint32_t fule_support: 1; /*!< SD FULE (Full User Area Logical Erase) feature support */
uint32_t reserved: 28; /*!< reserved for future expansion */
uint32_t erase_timeout: 6; /*!< Timeout (in seconds) for erase of a single allocation unit */
uint32_t erase_offset: 2; /*!< Constant timeout offset (in seconds) for any erase operation */
uint32_t reserved: 20; /*!< reserved for future expansion */
} sdmmc_ssr_t;
/**

View File

@ -572,7 +572,7 @@ esp_err_t sdmmc_erase_sectors(sdmmc_card_t* card, size_t start_sector,
esp_err_t err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
ESP_LOGE(TAG, "%s: sdmmc_send_cmd (ERASE_GROUP_START) returned 0x%x", __func__, err);
return err;
}
@ -582,7 +582,7 @@ esp_err_t sdmmc_erase_sectors(sdmmc_card_t* card, size_t start_sector,
err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
ESP_LOGE(TAG, "%s: sdmmc_send_cmd (ERASE_GROUP_END) returned 0x%x", __func__, err);
return err;
}
@ -591,13 +591,11 @@ esp_err_t sdmmc_erase_sectors(sdmmc_card_t* card, size_t start_sector,
cmd.flags = SCF_CMD_AC | SCF_RSP_R1B | SCF_WAIT_BUSY;
cmd.opcode = MMC_ERASE;
cmd.arg = cmd38_arg;
// TODO: best way, application to compute timeout value. For this card
// structure should have a place holder for erase_timeout.
cmd.timeout_ms = (SDMMC_ERASE_BLOCK_TIMEOUT_MS + sector_count);
cmd.timeout_ms = sdmmc_get_erase_timeout_ms(card, cmd38_arg, sector_count * card->csd.sector_size / 1024);
err = sdmmc_send_cmd(card, &cmd);
if (err != ESP_OK) {
ESP_LOGE(TAG, "%s: sdmmc_send_cmd returned 0x%x", __func__, err);
ESP_LOGE(TAG, "%s: sdmmc_send_cmd (ERASE) returned 0x%x", __func__, err);
return err;
}
@ -684,9 +682,10 @@ esp_err_t sdmmc_full_erase(sdmmc_card_t* card)
if (card->is_mmc) {
arg = sdmmc_mmc_can_sanitize(card) == ESP_OK ? SDMMC_MMC_DISCARD_ARG: SDMMC_MMC_TRIM_ARG;
}
err = sdmmc_erase_sectors(card, 0, card->csd.capacity, arg);
err = sdmmc_erase_sectors(card, 0, card->csd.capacity, arg);
if ((err == ESP_OK) && (arg == SDMMC_MMC_DISCARD_ARG)) {
return sdmmc_mmc_sanitize(card, SDMMC_ERASE_BLOCK_TIMEOUT_MS + card->csd.capacity);
uint32_t timeout_ms = sdmmc_get_erase_timeout_ms(card, SDMMC_MMC_DISCARD_ARG, card->csd.capacity * ((uint64_t) card->csd.sector_size) / 1024);
return sdmmc_mmc_sanitize(card, timeout_ms);
}
return err;
}

View File

@ -318,3 +318,12 @@ esp_err_t sdmmc_fix_host_flags(sdmmc_card_t* card)
}
return ESP_OK;
}
uint32_t sdmmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb)
{
if (card->is_mmc) {
return sdmmc_mmc_get_erase_timeout_ms(card, arg, erase_size_kb);
} else {
return sdmmc_sd_get_erase_timeout_ms(card, arg, erase_size_kb);
}
}

View File

@ -37,7 +37,9 @@
*/
#define SDMMC_DEFAULT_CMD_TIMEOUT_MS 1000 // Max timeout of ordinary commands
#define SDMMC_WRITE_CMD_TIMEOUT_MS 5000 // Max timeout of write commands
#define SDMMC_ERASE_BLOCK_TIMEOUT_MS 500 // Max timeout of erase per block
#define SDMMC_SD_DISCARD_TIMEOUT 250 // SD erase (discard) timeout
/* Maximum retry/error count for SEND_OP_COND (CMD1).
* These are somewhat arbitrary, values originate from OpenBSD driver.
@ -79,6 +81,7 @@ esp_err_t sdmmc_write_sectors_dma(sdmmc_card_t* card, const void* src,
size_t start_block, size_t block_count);
esp_err_t sdmmc_read_sectors_dma(sdmmc_card_t* card, void* dst,
size_t start_block, size_t block_count);
uint32_t sdmmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb);
/* SD specific */
esp_err_t sdmmc_check_scr(sdmmc_card_t* card);
@ -86,6 +89,7 @@ esp_err_t sdmmc_decode_cid(sdmmc_response_t resp, sdmmc_cid_t* out_cid);
esp_err_t sdmmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd);
esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr);
esp_err_t sdmmc_decode_ssr(uint32_t *raw_ssr, sdmmc_ssr_t* out_ssr);
uint32_t sdmmc_sd_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb);
/* SDIO specific */
esp_err_t sdmmc_io_reset(sdmmc_card_t* card);
@ -103,6 +107,7 @@ esp_err_t sdmmc_mmc_switch(sdmmc_card_t* card, uint8_t set, uint8_t index, uint8
esp_err_t sdmmc_mmc_decode_cid(int mmc_ver, sdmmc_response_t resp, sdmmc_cid_t* out_cid);
esp_err_t sdmmc_mmc_decode_csd(sdmmc_response_t response, sdmmc_csd_t* out_csd);
esp_err_t sdmmc_mmc_enable_hs_mode(sdmmc_card_t* card);
uint32_t sdmmc_mmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb);
/* Parts of card initialization flow */
esp_err_t sdmmc_init_sd_if_cond(sdmmc_card_t* card);

View File

@ -288,3 +288,13 @@ out:
free(ext_csd);
return err;
}
uint32_t sdmmc_mmc_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb)
{
/* TODO: calculate erase timeout based on ext_csd (trim_timeout) */
uint32_t timeout_ms = SDMMC_SD_DISCARD_TIMEOUT * erase_size_kb / card->csd.sector_size;
timeout_ms = MAX(1000, timeout_ms);
ESP_LOGD(TAG, "%s: erase timeout %u s (erasing %u kB, %ums per sector)",
__func__, timeout_ms / 1000, erase_size_kb, SDMMC_SD_DISCARD_TIMEOUT);
return timeout_ms;
}

View File

@ -387,6 +387,16 @@ esp_err_t sdmmc_decode_scr(uint32_t *raw_scr, sdmmc_scr_t* out_scr)
return ESP_OK;
}
static const uint32_t s_au_to_size_kb[] = {
0, 16, 32, 64,
128, 256, 512, 1024,
2 * 1024, 4 * 1024,
8 * 1024, 12 * 1024,
16 * 1024, 24 * 1024,
32 * 1024, 64 * 1024
};
_Static_assert(sizeof(s_au_to_size_kb)/sizeof(s_au_to_size_kb[0]) == 16, "invalid number of elements in s_au_to_size_kb");
esp_err_t sdmmc_decode_ssr(uint32_t *raw_ssr, sdmmc_ssr_t* out_ssr)
{
uint32_t ssr[(SD_SSR_SIZE/sizeof(uint32_t))] = { 0 };
@ -399,6 +409,41 @@ esp_err_t sdmmc_decode_ssr(uint32_t *raw_ssr, sdmmc_ssr_t* out_ssr)
out_ssr->cur_bus_width = SSR_DAT_BUS_WIDTH(ssr);
out_ssr->discard_support = SSR_DISCARD_SUPPORT(ssr);
out_ssr->fule_support = SSR_FULE_SUPPORT(ssr);
uint32_t au = SSR_AU_SIZE(ssr);
out_ssr->alloc_unit_kb = s_au_to_size_kb[au];
out_ssr->erase_timeout = SSR_ERASE_TIMEOUT(ssr);
out_ssr->erase_size_au = SSR_ERASE_SIZE(ssr);
out_ssr->erase_offset = SSR_ERASE_OFFSET(ssr);
return ESP_OK;
}
uint32_t sdmmc_sd_get_erase_timeout_ms(const sdmmc_card_t* card, int arg, size_t erase_size_kb)
{
if (arg == SDMMC_SD_DISCARD_ARG) {
return SDMMC_SD_DISCARD_TIMEOUT;
} else if (arg == SDMMC_SD_ERASE_ARG) {
if (card->ssr.alloc_unit_kb != 0 &&
card->ssr.erase_size_au != 0 &&
card->ssr.erase_timeout != 0 &&
card->ssr.erase_offset != 0) {
/* Card supports erase timeout estimation. See the erase timeout equation in SD spec. */
uint32_t timeout_sec = card->ssr.erase_offset +
card->ssr.erase_timeout * (erase_size_kb + card->ssr.alloc_unit_kb - 1) /
(card->ssr.erase_size_au * card->ssr.alloc_unit_kb);
ESP_LOGD(TAG, "%s: erase timeout %u s (erasing %u kB, ES=%u, ET=%u, EO=%u, AU=%u kB)",
__func__, timeout_sec, erase_size_kb, card->ssr.erase_size_au,
card->ssr.erase_timeout, card->ssr.erase_offset, card->ssr.alloc_unit_kb);
return timeout_sec * 1000;
} else {
uint32_t timeout_ms = SDMMC_SD_DISCARD_TIMEOUT * erase_size_kb / card->csd.sector_size;
timeout_ms = MAX(1000, timeout_ms);
ESP_LOGD(TAG, "%s: erase timeout %u s (erasing %u kB, %ums per sector)",
__func__, timeout_ms / 1000, erase_size_kb, SDMMC_SD_DISCARD_TIMEOUT);
return timeout_ms;
}
} else {
assert(false && "unexpected SD erase argument");
return 0;
}
}