esp_flash: refactor to support various type of yield

There is a periodically yield in the esp_flash driver, to ensure the
cache will not be disabled for too long on ESP32.

On ESP32-S2 and later, we need to support more different kind of yield:

1. polling conditions, including timeout, SW read request, etc.
2. wait for events, including HW done/error/auto-suspend, timeout
semaphore, etc.

The check_yield() and yield() is separated into two parts, because we
may need to insert suspend, etc. between them.
This commit is contained in:
Michael (XIAO Xufeng)
2020-09-11 18:20:08 +08:00
parent 33e7784806
commit 8ae09194ac
5 changed files with 258 additions and 77 deletions

View File

@@ -22,8 +22,8 @@
#include "esp_log.h"
#include "sdkconfig.h"
#include "esp_flash_internal.h"
#include "esp_private/system_internal.h"
#include "spi_flash_chip_generic.h" //for spi_flash_chip_generic_yield()
static const char TAG[] = "spi_flash";
@@ -73,11 +73,13 @@ esp_err_t esp_flash_read_chip_id(esp_flash_t* chip, uint32_t* flash_id);
static esp_err_t spiflash_start_default(esp_flash_t *chip);
static esp_err_t spiflash_end_default(esp_flash_t *chip, esp_err_t err);
static esp_err_t check_chip_pointer_default(esp_flash_t **inout_chip);
static esp_err_t flash_end_flush_cache(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length);
typedef struct {
esp_err_t (*start)(esp_flash_t *chip);
esp_err_t (*end)(esp_flash_t *chip, esp_err_t err);
esp_err_t (*chip_check)(esp_flash_t **inout_chip);
esp_err_t (*flash_end_flush_cache)(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length);
} rom_spiflash_api_func_t;
// These functions can be placed in the ROM. For now we use the code in IDF.
@@ -85,6 +87,7 @@ DRAM_ATTR static rom_spiflash_api_func_t default_spiflash_rom_api = {
.start = spiflash_start_default,
.end = spiflash_end_default,
.chip_check = check_chip_pointer_default,
.flash_end_flush_cache = flash_end_flush_cache,
};
DRAM_ATTR rom_spiflash_api_func_t *rom_spiflash_api_funcs = &default_spiflash_rom_api;
@@ -134,6 +137,26 @@ static esp_err_t check_chip_pointer_default(esp_flash_t **inout_chip)
return ESP_OK;
}
static IRAM_ATTR esp_err_t flash_end_flush_cache(esp_flash_t* chip, esp_err_t err, bool bus_acquired, uint32_t address, uint32_t length)
{
if (!bus_acquired) {
// Try to acquire the bus again to flush the cache before exit.
esp_err_t acquire_err = rom_spiflash_api_funcs->start(chip);
if (acquire_err != ESP_OK) {
return (err == ESP_OK)? acquire_err: err;
}
}
if (chip->host->driver->flush_cache) {
esp_err_t flush_err = chip->host->driver->flush_cache(chip->host, address, length);
if (err == ESP_OK) {
err = flush_err;
}
}
return rom_spiflash_api_funcs->end(chip, err);
}
/* Return true if regions 'a' and 'b' overlap at all, based on their start offsets and lengths. */
inline static bool regions_overlap(uint32_t a_start, uint32_t a_len,uint32_t b_start, uint32_t b_len);
@@ -334,12 +357,24 @@ esp_err_t IRAM_ATTR esp_flash_erase_chip(esp_flash_t *chip)
VERIFY_CHIP_OP(erase_chip);
CHECK_WRITE_ADDRESS(chip, 0, chip->size);
//check before the operation, in case this is called too close to the last operation
err = spi_flash_chip_generic_yield(chip, false);
if (err != ESP_OK) {
return err;
}
err = rom_spiflash_api_funcs->start(chip);
if (err != ESP_OK) {
return err;
}
err = chip->chip_drv->erase_chip(chip);
if (chip->host->driver->flush_cache) {
esp_err_t flush_cache_err = chip->host->driver->flush_cache(chip->host, 0, chip->size);
if (err == ESP_OK) {
err = flush_cache_err;
}
}
return rom_spiflash_api_funcs->end(chip, err);
}
@@ -387,47 +422,58 @@ esp_err_t IRAM_ATTR esp_flash_erase_region(esp_flash_t *chip, uint32_t start, ui
// Don't lock the SPI flash for the entire erase, as this may be very long
err = rom_spiflash_api_funcs->end(chip, err);
}
if (err != ESP_OK) {
return err;
}
uint32_t erase_addr = start;
uint32_t len_remain = len;
// Indicate whether the bus is acquired by the driver, needs to be released before return
bool bus_acquired = false;
while (1) {
//check before the operation, in case this is called too close to the last operation
err = spi_flash_chip_generic_yield(chip, false);
if (err != ESP_OK) {
break;
}
#ifdef CONFIG_SPI_FLASH_YIELD_DURING_ERASE
int64_t no_yield_time_us = 0;
#endif
while (err == ESP_OK && len >= sector_size) {
#ifdef CONFIG_SPI_FLASH_YIELD_DURING_ERASE
int64_t start_time_us = esp_system_get_time();
#endif
err = rom_spiflash_api_funcs->start(chip);
if (err != ESP_OK) {
return err;
break;
}
bus_acquired = true;
#ifndef CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE
// If possible erase an entire multi-sector block
if (block_erase_size > 0 && len >= block_erase_size && (start % block_erase_size) == 0) {
err = chip->chip_drv->erase_block(chip, start);
start += block_erase_size;
len -= block_erase_size;
if (block_erase_size > 0 && len_remain >= block_erase_size && (erase_addr % block_erase_size) == 0) {
err = chip->chip_drv->erase_block(chip, erase_addr);
erase_addr += block_erase_size;
len_remain -= block_erase_size;
} else
#endif
{
// Otherwise erase individual sector only
err = chip->chip_drv->erase_sector(chip, start);
start += sector_size;
len -= sector_size;
err = chip->chip_drv->erase_sector(chip, erase_addr);
erase_addr += sector_size;
len_remain -= sector_size;
}
err = rom_spiflash_api_funcs->end(chip, err);
#ifdef CONFIG_SPI_FLASH_YIELD_DURING_ERASE
no_yield_time_us += (esp_system_get_time() - start_time_us);
if (no_yield_time_us / 1000 >= CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS) {
no_yield_time_us = 0;
if (chip->os_func->yield) {
chip->os_func->yield(chip->os_func_data);
}
if (err != ESP_OK || len_remain == 0) {
// On ESP32, the cache re-enable is in the end() function, while flush_cache should
// happen when the cache is still disabled on ESP32. Break before the end() function and
// do end() later
assert(bus_acquired);
break;
}
#endif
err = rom_spiflash_api_funcs->end(chip, ESP_OK);
if (err != ESP_OK) {
break;
}
bus_acquired = false;
}
return err;
return rom_spiflash_api_funcs->flash_end_flush_cache(chip, err, bus_acquired, start, len);
}
esp_err_t IRAM_ATTR esp_flash_get_chip_write_protect(esp_flash_t *chip, bool *out_write_protected)
@@ -638,38 +684,62 @@ esp_err_t IRAM_ATTR esp_flash_write(esp_flash_t *chip, const void *buffer, uint3
//when the cache is disabled, only the DRAM can be read, check whether we need to copy the data first
bool direct_write = chip->host->driver->supports_direct_write(chip->host, buffer);
// Indicate whether the bus is acquired by the driver, needs to be released before return
bool bus_acquired = false;
err = ESP_OK;
/* Write output in chunks, either by buffering on stack or
by artificially cutting into MAX_WRITE_CHUNK parts (in an OS
environment, this prevents writing from causing interrupt or higher priority task
starvation.) */
do {
uint32_t write_addr = address;
uint32_t len_remain = length;
while (1) {
uint32_t write_len;
const void *write_buf;
uint32_t temp_buf[8];
if (direct_write) {
write_len = MIN(length, MAX_WRITE_CHUNK);
write_len = MIN(len_remain, MAX_WRITE_CHUNK);
write_buf = buffer;
} else {
write_len = MIN(length, sizeof(temp_buf));
write_len = MIN(len_remain, sizeof(temp_buf));
memcpy(temp_buf, buffer, write_len);
write_buf = temp_buf;
}
err = rom_spiflash_api_funcs->start(chip);
//check before the operation, in case this is called too close to the last operation
err = spi_flash_chip_generic_yield(chip, false);
if (err != ESP_OK) {
return err;
break;
}
err = chip->chip_drv->write(chip, write_buf, address, write_len);
err = rom_spiflash_api_funcs->start(chip);
if (err != ESP_OK) {
break;
}
bus_acquired = true;
address += write_len;
buffer = (void *)((intptr_t)buffer + write_len);
length -= write_len;
err = chip->chip_drv->write(chip, write_buf, write_addr, write_len);
len_remain -= write_len;
if (err != ESP_OK || len_remain == 0) {
// On ESP32, the cache re-enable is in the end() function, while flush_cache should
// happen when the cache is still disabled on ESP32. Break before the end() function and
// do end() later
assert(bus_acquired);
break;
}
err = rom_spiflash_api_funcs->end(chip, err);
} while (err == ESP_OK && length > 0);
return err;
if (err != ESP_OK) {
break;
}
bus_acquired = false;
write_addr += write_len;
buffer = (void *)((intptr_t)buffer + write_len);
}
return rom_spiflash_api_funcs->flash_end_flush_cache(chip, err, bus_acquired, address, length);
}
//currently the legacy implementation is used, from flash_ops.c