fix(mmap): fixed some API read wrong data via mmap when flash being erased/written while XIP on PSRAM

Before:

The cache won't be disabled when XIP on psram. But during flash
erasing/programming, read data will be courrupt.

When XIP in psram is enabled, the image is not mapped to the cache so
usually there will be no flash access. The only way to read from flash
is via the driver or use mmap. The driver has protection during erasing,
while th mmap region not.

Now:

Mmap APIs provide a flag to make mmap->unmap region mutually exclusive
to flash erase/programming when XIP from psram. SPI Flash write APIs
will benefit from this. When the flag is used, no concurrent access to
mapped region will happen while writing; otherwise the cache will be
disable to avoid data corruption.

Most ESP-IDF APIs calls mmap with this flag. As for users calling
mmap-like APIs directly, they can choose whether to enable this by a
flag.

Closes https://github.com/espressif/esp-idf/issues/14897
This commit is contained in:
Xiao Xufeng
2026-07-15 18:57:08 +08:00
committed by Michael (XIAO Xufeng)
parent 39a219331c
commit 3e8389cc31
73 changed files with 1678 additions and 487 deletions
+1 -1
View File
@@ -289,7 +289,7 @@ menu "SPI Flash driver"
config SPI_FLASH_SHARE_SPI1_BUS
bool "Support other devices attached to SPI1 bus"
default n
depends on IDF_TARGET_ESP32
depends on IDF_TARGET_ESP32 && !CONFIG_APP_BUILD_TYPE_RAM
select SPI_MASTER_ISR_IN_IRAM
help
Each SPI bus needs a lock for arbitration among devices. This allows multiple
+58 -60
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -137,7 +137,7 @@ static ESP_LOG_ATTR const char io_mode_str[][IO_STR_LEN] = {
_Static_assert(sizeof(io_mode_str)/IO_STR_LEN == SPI_FLASH_READ_MODE_MAX, "the io_mode_str should be consistent with the esp_flash_io_mode_t defined in spi_flash_types.h");
typedef struct {
esp_err_t (*start)(esp_flash_t *chip);
esp_err_t (*start_prog)(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);
@@ -155,14 +155,14 @@ extern rom_spiflash_api_func_t *esp_flash_api_funcs;
#if !CONFIG_SPI_FLASH_ROM_IMPL
// API funcs case 1: Not using ROM - define our own pointer and all functions
static esp_err_t spiflash_start_default(esp_flash_t *chip);
static esp_err_t spiflash_start_prog(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);
// These functions can be placed in the ROM. For now we use the code in IDF.
DRAM_ATTR static rom_spiflash_api_func_t esp_flash_api_funcs_patched = {
.start = spiflash_start_default,
.start_prog = spiflash_start_prog,
.end = spiflash_end_default,
.chip_check = check_chip_pointer_default,
.flash_end_flush_cache = flash_end_flush_cache,
@@ -172,44 +172,27 @@ DRAM_ATTR static rom_spiflash_api_func_t *esp_flash_api_funcs_patched_ptr = &esp
#else // CONFIG_SPI_FLASH_ROM_IMPL
// Using ROM implementation
# if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
// API funcs case 2: Using ROM APIs but patch start function to support flags parameter
static esp_err_t spiflash_start_default(esp_flash_t *chip);
// All ROM impl cases patch the start function to spiflash_start_prog, so that
// the flags parameter (esp_flash_os_functions_t.start) is always passed correctly.
// The ROM's original start does not pass flags, which would leave the parameter undefined.
static esp_err_t spiflash_start_prog(esp_flash_t *chip);
DRAM_ATTR static rom_spiflash_api_func_t esp_flash_api_funcs_patched;
// Copy ROM structure to RAM and patch start function to support flags
void esp_flash_rom_api_funcs_init(void)
{
rom_spiflash_api_func_t *rom_ptr = esp_flash_api_funcs;
memcpy(&esp_flash_api_funcs_patched, rom_ptr, sizeof(rom_spiflash_api_func_t));
esp_flash_api_funcs_patched.start = spiflash_start_default;
esp_flash_api_funcs = &esp_flash_api_funcs_patched;
}
# elif ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV
// API funcs case 3: Using ROM APIs but patch flash_end_flush_cache function
// When ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV, the api_funcs provided by ROM does not have flash_end_flush_cache member.
# if ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV
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);
DRAM_ATTR static rom_spiflash_api_func_t esp_flash_api_funcs_patched;
# endif
// Copy ROM structure to RAM and patch flash_end_flush_cache function
void esp_flash_rom_api_funcs_init(void)
{
rom_spiflash_api_func_t *rom_ptr = esp_flash_api_funcs;
memcpy(&esp_flash_api_funcs_patched, rom_ptr, sizeof(rom_spiflash_api_func_t));
esp_flash_api_funcs_patched.start_prog = spiflash_start_prog;
# if ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV
esp_flash_api_funcs_patched.flash_end_flush_cache = flash_end_flush_cache;
# endif
esp_flash_api_funcs = &esp_flash_api_funcs_patched;
}
# else
// API funcs case 4: Using All ROM APIs directly
void esp_flash_rom_api_funcs_init(void)
{
// Do nothing
}
# endif // CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
#endif // !CONFIG_SPI_FLASH_ROM_IMPL
/* Static function to notify OS of a new SPI flash operation.
@@ -217,7 +200,6 @@ void esp_flash_rom_api_funcs_init(void)
If returns an error result, caller must abort. If returns ESP_OK, caller must
call rom_spiflash_api_funcs->end() before returning.
*/
#if !CONFIG_SPI_FLASH_ROM_IMPL || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
//Avoid constprop issue that place this function into flash.
__attribute__((optimize("O0"))) //IDF-14941
static esp_err_t spiflash_start_core(esp_flash_t *chip, uint32_t flags)
@@ -232,11 +214,17 @@ static esp_err_t spiflash_start_core(esp_flash_t *chip, uint32_t flags)
return ESP_OK;
}
static esp_err_t spiflash_start_default(esp_flash_t *chip)
// Prog start: used by write/erase and misc operations (via rom_spiflash_api_funcs->start_prog).
// Sets ESP_FLASH_START_FLAG_NO_READ to avoid concurrent read operations.
static esp_err_t spiflash_start_prog(esp_flash_t *chip)
{
return spiflash_start_core(chip, ESP_FLASH_START_FLAG_NO_READ);
}
static esp_err_t spiflash_start_read(esp_flash_t *chip)
{
return spiflash_start_core(chip, 0);
}
#endif //!CONFIG_SPI_FLASH_ROM_IMPL || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
#if !CONFIG_SPI_FLASH_ROM_IMPL
/* Static function to notify OS that SPI flash operation is complete.
@@ -277,7 +265,7 @@ static esp_err_t flash_end_flush_cache(esp_flash_t* chip, esp_err_t err, bool bu
{
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);
esp_err_t acquire_err = rom_spiflash_api_funcs->start_prog(chip);
if (acquire_err != ESP_OK) {
return (err == ESP_OK)? acquire_err: err;
}
@@ -355,7 +343,7 @@ esp_err_t esp_flash_init(esp_flash_t *chip)
}
ESP_LOGI(TAG, "flash io: %s", io_mode_str[chip->read_mode]);
err = rom_spiflash_api_funcs->start(chip);
err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
return err;
}
@@ -435,7 +423,7 @@ esp_err_t esp_flash_init_main(esp_flash_t *chip)
}
ESP_EARLY_LOGI(TAG, "flash io: %s", io_mode_str[chip->read_mode]);
err = rom_spiflash_api_funcs->start(chip);
err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
return err;
}
@@ -455,7 +443,8 @@ esp_err_t esp_flash_init_main(esp_flash_t *chip)
static esp_err_t IRAM_ATTR read_id_core(esp_flash_t* chip, uint32_t* out_id, bool sanity_check)
{
bool installed = esp_flash_chip_driver_initialized(chip);
esp_err_t err = rom_spiflash_api_funcs->start(chip);
//Should be read-only. But keep `start_prog` to avoid the need of patching ROM functions.
esp_err_t err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
return err;
}
@@ -507,7 +496,8 @@ esp_err_t esp_flash_read_id(esp_flash_t* chip, uint32_t* out_id)
static esp_err_t NOINLINE_ATTR read_unique_id(esp_flash_t* chip, uint64_t* out_uid)
{
esp_err_t err = rom_spiflash_api_funcs->start(chip);
//Should be read-only. But keep `start_prog` to avoid the need of patching ROM functions.
esp_err_t err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
return err;
}
@@ -553,7 +543,7 @@ static esp_err_t detect_spi_flash_chip(esp_flash_t *chip)
// and also so esp_flash_registered_flash_drivers can live in flash
ESP_EARLY_LOGD(TAG, "trying chip: %s", chip->chip_drv->name);
err = rom_spiflash_api_funcs->start(chip);
err = spiflash_start_read(chip);
if (err != ESP_OK) {
return err;
}
@@ -587,7 +577,8 @@ esp_err_t esp_flash_get_physical_size(esp_flash_t *chip, uint32_t *flash_size)
return ESP_ERR_INVALID_ARG;
}
err = rom_spiflash_api_funcs->start(chip);
//Should be read-only. But keep `start_prog` to avoid the need of patching ROM functions.
err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
return err;
}
@@ -681,7 +672,7 @@ esp_err_t esp_flash_erase_region(esp_flash_t *chip, uint32_t start, uint32_t len
if (chip->chip_drv->get_protected_regions != NULL &&
chip->chip_drv->num_protectable_regions > 0) {
err = rom_spiflash_api_funcs->start(chip);
err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
return err;
}
@@ -716,7 +707,7 @@ esp_err_t esp_flash_erase_region(esp_flash_t *chip, uint32_t start, uint32_t len
}
}
err = rom_spiflash_api_funcs->start(chip);
err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
break;
}
@@ -810,7 +801,8 @@ esp_err_t esp_flash_get_chip_write_protect(esp_flash_t *chip, bool *out_write_pr
return ESP_ERR_INVALID_ARG;
}
err = rom_spiflash_api_funcs->start(chip);
//Should be read-only. But keep `start_prog` to avoid the need of patching ROM functions.
err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
return err;
}
@@ -826,7 +818,7 @@ esp_err_t esp_flash_set_chip_write_protect(esp_flash_t *chip, bool write_protect
VERIFY_CHIP_OP(set_chip_write_protect);
//TODO: skip writing if already locked or unlocked
err = rom_spiflash_api_funcs->start(chip);
err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
return err;
}
@@ -885,7 +877,8 @@ esp_err_t esp_flash_get_protected_region(esp_flash_t *chip, const esp_flash_regi
}
uint64_t protection_mask = 0;
err = rom_spiflash_api_funcs->start(chip);
//Should be read-only. But keep `start_prog` to avoid the need of patching ROM functions.
err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
return err;
}
@@ -910,7 +903,7 @@ esp_err_t esp_flash_set_protected_region(esp_flash_t *chip, const esp_flash_regi
}
uint64_t protection_mask = 0;
err = rom_spiflash_api_funcs->start(chip);
err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
return err;
}
@@ -927,7 +920,12 @@ esp_err_t esp_flash_set_protected_region(esp_flash_t *chip, const esp_flash_regi
return rom_spiflash_api_funcs->end(chip, err);
}
#endif // !CONFIG_SPI_FLASH_ROM_IMPL
/* ROM and patch information
* Latest: patched to use spiflash_start_read instead of rom_spiflash_api_funcs->start_prog
* V1: Added to ROM (Not used)
*/
esp_err_t esp_flash_read(esp_flash_t *chip, void *buffer, uint32_t address, uint32_t length)
{
esp_err_t err = rom_spiflash_api_funcs->chip_check(&chip);
@@ -966,7 +964,7 @@ esp_err_t esp_flash_read(esp_flash_t *chip, void *buffer, uint32_t address, uint
err = ESP_OK;
do {
err = rom_spiflash_api_funcs->start(chip);
err = spiflash_start_read(chip);
if (err != ESP_OK) {
break;
}
@@ -1003,7 +1001,6 @@ esp_err_t esp_flash_read(esp_flash_t *chip, void *buffer, uint32_t address, uint
COUNTER_STOP(read);
return err;
}
#endif //!CONFIG_SPI_FLASH_ROM_IMPL
#ifndef CONFIG_SPI_FLASH_ROM_IMPL
//This checking is available only when !CONFIG_SPI_FLASH_ROM_IMPL
@@ -1169,7 +1166,7 @@ esp_err_t esp_flash_write(esp_flash_t *chip, const void *buffer, uint32_t addres
}
}
err = rom_spiflash_api_funcs->start(chip);
err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
goto restore_cache;
}
@@ -1248,7 +1245,9 @@ esp_err_t IRAM_ATTR esp_flash_write(esp_flash_t *chip, const void *buffer, uint3
}
#endif //!CONFIG_SPI_FLASH_ROM_IMPL
#ifndef CONFIG_SPI_FLASH_ROM_IMPL
/* ROM and patch information
* Latest: Call mmap that has block write flag
*/
esp_err_t esp_flash_read_encrypted(esp_flash_t *chip, uint32_t address, void *out_buffer, uint32_t length)
{
esp_err_t err = rom_spiflash_api_funcs->chip_check(&chip);
@@ -1269,7 +1268,7 @@ esp_err_t esp_flash_read_encrypted(esp_flash_t *chip, uint32_t address, void *ou
size_t map_src = address & ~(SPI_FLASH_MMU_PAGE_SIZE - 1);
size_t map_size = length + (address - map_src);
err = spi_flash_mmap(map_src, map_size, SPI_FLASH_MMAP_DATA, (const void **)&map, &map_handle);
err = spi_flash_mmap(map_src, map_size, SPI_FLASH_MMAP_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, (const void **)&map, &map_handle);
if (err != ESP_OK) {
return err;
}
@@ -1281,6 +1280,7 @@ esp_err_t esp_flash_read_encrypted(esp_flash_t *chip, uint32_t address, void *ou
return err;
}
#if !CONFIG_SPI_FLASH_ROM_IMPL
// test only, non-public
esp_err_t esp_flash_get_io_mode(esp_flash_t* chip, bool* qe)
{
@@ -1288,7 +1288,7 @@ esp_err_t esp_flash_get_io_mode(esp_flash_t* chip, bool* qe)
VERIFY_CHIP_OP(get_io_mode);
esp_flash_io_mode_t io_mode;
err = rom_spiflash_api_funcs->start(chip);
err = spiflash_start_read(chip);
if (err != ESP_OK) {
return err;
}
@@ -1306,7 +1306,7 @@ esp_err_t esp_flash_set_io_mode(esp_flash_t* chip, bool qe)
VERIFY_CHIP_OP(set_io_mode);
chip->read_mode = (qe? SPI_FLASH_QOUT: SPI_FLASH_SLOWRD);
err = rom_spiflash_api_funcs->start(chip);
err = rom_spiflash_api_funcs->start_prog(chip);
if (err != ESP_OK) {
return err;
}
@@ -1317,17 +1317,15 @@ esp_err_t esp_flash_set_io_mode(esp_flash_t* chip, bool qe)
#if !CONFIG_SPI_FLASH_ROM_IMPL || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV || CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
// use `esp_flash_write_encrypted` ROM version on chips later than C3, S3
// For ESP32-C5, use IDF implementation when CPU frequency is 240MHz (calling start() with arg is required)
FORCE_INLINE_ATTR esp_err_t s_encryption_write_lock(esp_flash_t *chip)
{
#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
return spiflash_start_core(chip, ESP_FLASH_START_FLAG_NO_READ | ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ);
#else
#if CONFIG_IDF_TARGET_ESP32S2
esp_crypto_dma_lock_acquire();
#endif //CONFIG_IDF_TARGET_ESP32S2
#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
// Use start_core with LIMIT_CPU_FREQ flag to trigger freq_limit_lock in OS layer
return spiflash_start_core(chip, ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ);
#else
return rom_spiflash_api_funcs->start(chip);
#endif
return rom_spiflash_api_funcs->start_prog(chip);
#endif
}
+2 -4
View File
@@ -176,7 +176,7 @@ static IRAM_ATTR NOINLINE_ATTR void cs_initialize(esp_flash_t *chip, const esp_f
//To avoid the panic caused by flash data line conflicts during cs line
//initialization, disable the cache temporarily
chip->os_func->start(chip->os_func_data, 0);
chip->os_func->start(chip->os_func_data, ESP_FLASH_START_FLAG_NO_READ);
gpio_hal_input_enable(&gpio_hal, cs_io_num);
if (cs_use_iomux) {
gpio_hal_func_sel(&gpio_hal, cs_io_num, spics_func);
@@ -632,10 +632,8 @@ esp_err_t esp_flash_app_init(void)
#if CONFIG_SPI_FLASH_ENABLE_COUNTERS
esp_flash_reset_counters();
#endif
#if CONFIG_SPI_FLASH_SHARE_SPI1_BUS
err = esp_flash_init_main_bus_lock();
err = esp_flash_app_init_os_functions();
if (err != ESP_OK) return err;
#endif
err = esp_flash_app_enable_os_functions(&default_chip);
return err;
}
+329 -11
View File
@@ -9,6 +9,7 @@
#include <string.h>
#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
#include "sdkconfig.h"
#include "esp_bit_defs.h"
#include "esp_attr.h"
@@ -39,6 +40,7 @@
#include "esp_private/cache_utils.h"
#include "spi_flash_mmap.h"
#include "esp_private/flash_mmap.h"
#if CONFIG_SPIRAM_FETCH_INSTRUCTIONS
extern char _instruction_reserved_start;
@@ -50,18 +52,217 @@ extern char _rodata_reserved_start;
extern char _rodata_reserved_end;
#endif
#if !ESP_ROM_HAS_SPI_FLASH_MMAP || !CONFIG_SPI_FLASH_ROM_IMPL
/* 0x1000000, 16MB */
#define FLASH_MMAP_ADDR_24BIT_MAX (BIT(24))
#if !CONFIG_IDF_TARGET_ESP32
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Mmap lock implementation.
// This lock allows external caller (flash driver) freezing the mmap flash pages when erasing.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct {
_lock_t outer_mux;
bool frozen;
int freezing_wait_count;
int acquired_count; //minus value means number of waiting callers
SemaphoreHandle_t semphr_freeze;
SemaphoreHandle_t semphr_acq;
} mmap_lock_t;
static mmap_lock_t s_mmap_lock;
static esp_err_t mmap_lock_init(void)
{
esp_err_t ret = ESP_OK;
_lock_init(&s_mmap_lock.outer_mux);
s_mmap_lock.frozen = false;
s_mmap_lock.freezing_wait_count = 0;
s_mmap_lock.acquired_count = 0;
s_mmap_lock.semphr_freeze = xSemaphoreCreateBinary();
if (s_mmap_lock.semphr_freeze == NULL) {
ret = ESP_ERR_NO_MEM;
goto err;
}
s_mmap_lock.semphr_acq = xSemaphoreCreateBinary();
if (s_mmap_lock.semphr_acq == NULL) {
ret = ESP_ERR_NO_MEM;
goto err;
}
return ESP_OK;
err:
if (s_mmap_lock.semphr_freeze != NULL) {
vSemaphoreDelete(s_mmap_lock.semphr_freeze);
s_mmap_lock.semphr_freeze = NULL;
}
return ret;
}
static void mmap_lock_acquire(void)
{
mmap_lock_t* const lock = &s_mmap_lock;
bool wait = false;
_lock_acquire(&lock->outer_mux);
if (!lock->frozen) {
assert(lock->acquired_count >= 0);
lock->acquired_count++;
} else {
//Register one event
assert(lock->acquired_count <= 0);
lock->acquired_count--;
wait = true;
}
_lock_release(&lock->outer_mux);
if (wait) {
//Wait for event
xSemaphoreTake(lock->semphr_acq, portMAX_DELAY);
}
}
static void mmap_lock_release(void)
{
mmap_lock_t* const lock = &s_mmap_lock;
bool wakeup_freeze = false;
_lock_acquire(&lock->outer_mux);
assert(lock->acquired_count > 0);
assert(lock->frozen == false);
lock->acquired_count--;
if (lock->acquired_count == 0 && lock->freezing_wait_count > 0) {
//All acquiring nodes have released, and there are waiting freezing requests
//Go to the freezing state and wake up one freeze request
lock->freezing_wait_count--;
lock->frozen = true;
wakeup_freeze = true;
}
_lock_release(&lock->outer_mux);
if (wakeup_freeze) {
//Wake up one freezing request
xSemaphoreGive(lock->semphr_freeze);
}
}
static void mmap_lock_freeze(void)
{
mmap_lock_t* const lock = &s_mmap_lock;
bool wait = false;
_lock_acquire(&lock->outer_mux);
if (lock->acquired_count > 0 || lock->frozen) {
//If frozen, or already acquired, register one event and wait for it
lock->freezing_wait_count++;
wait = true;
} else {
lock->frozen = true;
}
_lock_release(&lock->outer_mux);
if (wait) {
//Wait for event trigger
xSemaphoreTake(lock->semphr_freeze, portMAX_DELAY);
}
}
static void mmap_lock_unfreeze(void)
{
mmap_lock_t* const lock = &s_mmap_lock;
bool wakeup_frozen = false;
int wakeup_acq_count = 0;
assert(lock->frozen);
assert(lock->acquired_count <= 0);
_lock_acquire(&lock->outer_mux);
if (lock->acquired_count < 0) {
//acquiring requests has higher priority than freezing request
lock->frozen = false;
lock->acquired_count = -lock->acquired_count;
wakeup_acq_count = lock->acquired_count;
} else if (lock->freezing_wait_count > 0) {
lock->freezing_wait_count--;
lock->frozen = true;
wakeup_frozen = true;
} else {
//otherwise no one owns the lock
lock->frozen = false;
}
_lock_release(&lock->outer_mux);
if (wakeup_frozen) {
//Wake one freezing request
xSemaphoreGive(lock->semphr_freeze);
} else {
//Wake up all acquiring requests
for (int i = 0; i < wakeup_acq_count; i++) {
xSemaphoreGive(lock->semphr_acq);
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Interfaces for mmap API and external caller (flash driver).
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//the count and the mapping table (esp_mmu_map) can only be touched when the mmap lock is acquired
static int s_mmap_remain_count; //number of mmap regions that are still in use
#define MMAP_CNT_INCREASE() do { \
assert(s_mmap_remain_count >= 0); \
s_mmap_remain_count++; \
} while (0)
#define MMAP_CNT_DECREASE() do { \
s_mmap_remain_count--; \
assert(s_mmap_remain_count >= 0); \
} while (0)
esp_err_t flash_mmap_lock_init(void)
{
return mmap_lock_init();
}
bool flash_mmap_remain(void)
{
return s_mmap_remain_count > 0;
}
void flash_mmap_lock_freeze(void)
{
mmap_lock_freeze();
}
void flash_mmap_lock_unfreeze(void)
{
mmap_lock_unfreeze();
}
#else //!CONFIG_IDF_TARGET_ESP32
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Empty interfaces for mmap APIs (ESP32 only).
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#define mmap_lock_acquire() do {} while (0)
#define mmap_lock_release() do {} while (0)
#define MMAP_CNT_INCREASE() do {} while (0)
#define MMAP_CNT_DECREASE() do {} while (0)
#endif //!CONFIG_IDF_TARGET_ESP32
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Mmap operations
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#if !MMAP_ROM_IMPL_ENABLED
typedef struct mmap_block_t {
uint32_t *vaddr_list;
int list_num;
uint32_t permanent; //When this flag is set, the mmap region will last for a very long time. Don't wait for the unmap and release the mmap lock immediately when exit mmap calls.
} mmap_block_t;
esp_err_t spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_memory_t memory,
/* ROM and patch information
* Latest: Add OS function to avoid concurrent access with erase/program when XIP from PSRAM
* V1: added to ROM
*/
// Called from esp_flash_read_encrypted which is also a ROM function.
esp_err_t spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_flag_t flags,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle)
{
#if !CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH && !CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_OCTAL_FLASH
@@ -89,12 +290,15 @@ esp_err_t spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_memory_t m
}
block->vaddr_list = vaddr_list;
block->permanent = !(flags & SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE);
if (memory == SPI_FLASH_MMAP_INST) {
if (flags & SPI_FLASH_MMAP_FLAG_INST) {
caps = MMU_MEM_CAP_EXEC | MMU_MEM_CAP_32BIT;
} else {
caps = MMU_MEM_CAP_READ | MMU_MEM_CAP_8BIT;
}
mmap_lock_acquire();
ret = esp_mmu_map(src_addr, size, MMU_TARGET_FLASH0, caps, ESP_MMU_MMAP_FLAG_PADDR_SHARED, &ptr);
if (ret == ESP_OK) {
vaddr_list[0] = (uint32_t)ptr;
@@ -109,12 +313,18 @@ esp_err_t spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_memory_t m
*/
block->list_num = 0;
} else {
mmap_lock_release();
goto err;
}
MMAP_CNT_INCREASE();
*out_ptr = ptr;
*out_handle = (uint32_t)block;
if (block->permanent) {
//If the mmap is permanent, the lock is released without waiting for the unmap.
mmap_lock_release();
}
return ESP_OK;
err:
@@ -172,7 +382,12 @@ static void s_pages_to_bytes(int (*blocks)[2], int block_nums)
}
}
esp_err_t spi_flash_mmap_pages(const int *pages, size_t page_count, spi_flash_mmap_memory_t memory,
/* ROM and patch information
* Latest: Add OS function to avoid concurrent access with erase/program
* V1: added to ROM
*/
// Called from esp_flash_read_encrypted which is also a ROM function.
esp_err_t spi_flash_mmap_pages(const int *pages, size_t page_count, spi_flash_mmap_flag_t flags,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle)
{
#if !CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH && !CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_OCTAL_FLASH
@@ -189,6 +404,7 @@ esp_err_t spi_flash_mmap_pages(const int *pages, size_t page_count, spi_flash_mm
mmap_block_t *block = NULL;
uint32_t *vaddr_list = NULL;
int successful_cnt = 0;
bool mmap_lock_acquired = false;
int block_num = s_find_non_contiguous_block_nums(pages, page_count);
int paddr_blocks[block_num][2];
@@ -207,11 +423,14 @@ esp_err_t spi_flash_mmap_pages(const int *pages, size_t page_count, spi_flash_mm
goto err;
}
if (memory == SPI_FLASH_MMAP_INST) {
if (flags & SPI_FLASH_MMAP_FLAG_INST) {
caps = MMU_MEM_CAP_EXEC | MMU_MEM_CAP_32BIT;
} else {
caps = MMU_MEM_CAP_READ | MMU_MEM_CAP_8BIT;
}
mmap_lock_acquire();
mmap_lock_acquired = true;
for (int i = 0; i < block_num; i++) {
void *ptr = NULL;
ret = esp_mmu_map(paddr_blocks[i][0], paddr_blocks[i][1], MMU_TARGET_FLASH0, caps, ESP_MMU_MMAP_FLAG_PADDR_SHARED, &ptr);
@@ -229,9 +448,12 @@ esp_err_t spi_flash_mmap_pages(const int *pages, size_t page_count, spi_flash_mm
vaddr_list[i] = (uint32_t)ptr;
}
block->permanent = !(flags & SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE);
block->vaddr_list = vaddr_list;
block->list_num = successful_cnt;
MMAP_CNT_INCREASE();
/**
* We get a contiguous vaddr block, but may contain multiple esp_mmu handles.
* The first handle vaddr is the start address of this contiguous vaddr block.
@@ -239,6 +461,11 @@ esp_err_t spi_flash_mmap_pages(const int *pages, size_t page_count, spi_flash_mm
*out_ptr = (void *)vaddr_list[0];
*out_handle = (uint32_t)block;
if (block->permanent) {
//If the mmap is permanent, the lock is released without waiting for the unmap.
mmap_lock_release();
}
return ESP_OK;
err:
@@ -248,36 +475,125 @@ err:
if (vaddr_list) {
free(vaddr_list);
}
if (mmap_lock_acquired) {
mmap_lock_release();
}
if (block) {
free(block);
}
return ret;
}
/* ROM and patch information
* Latest: Add OS function to avoid concurrent access with erase/program
* V1: added to ROM
*/
// Called from esp_flash_read_encrypted which is also a ROM function.
void spi_flash_munmap(spi_flash_mmap_handle_t handle)
{
esp_err_t ret = ESP_FAIL;
mmap_block_t *block = (void *)handle;
if (block->permanent) {
mmap_lock_acquire();
}
for (int i = 0; i < block->list_num; i++) {
ret = esp_mmu_unmap((void *)block->vaddr_list[i]);
if (ret == ESP_ERR_NOT_FOUND) {
assert(0 && "invalid handle, or handle already unmapped");
}
}
MMAP_CNT_DECREASE();
mmap_lock_release();
free(block->vaddr_list);
free(block);
}
#else //!MMAP_ROM_IMPL_ENABLED
//Using ROM v1, which can't understand other flags like SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE.
//Handle the BLOCKS_WRITE flag and lock in the wrapper, then call ROM impl.
//
//The "permanent" state (i.e. no BLOCKS_WRITE, lock released immediately after mmap) is encoded
//in BIT(31) of the returned handle, mirroring the mmap_block_t::permanent field in the IDF
//implementation above.
extern esp_err_t rom_spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_flag_t flags,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle);
extern esp_err_t rom_spi_flash_mmap_pages(const int *pages, size_t page_count, spi_flash_mmap_flag_t flags,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle);
extern void rom_spi_flash_munmap(spi_flash_mmap_handle_t handle);
#define ROM_MMAP_HANDLE_PERMANENT_BIT BIT(31)
esp_err_t spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_flag_t flags,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle)
{
bool permanent = !(flags & SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE);
flags &= ~SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE;
mmap_lock_acquire();
esp_err_t ret = rom_spi_flash_mmap(src_addr, size, flags, out_ptr, out_handle);
if (ret != ESP_OK) {
mmap_lock_release();
return ret;
}
MMAP_CNT_INCREASE();
if (permanent) {
assert((*out_handle & ROM_MMAP_HANDLE_PERMANENT_BIT) == 0);
*out_handle |= ROM_MMAP_HANDLE_PERMANENT_BIT;
mmap_lock_release();
}
return ESP_OK;
}
esp_err_t spi_flash_mmap_pages(const int *pages, size_t page_count, spi_flash_mmap_flag_t flags,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle)
{
bool permanent = !(flags & SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE);
flags &= ~SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE;
mmap_lock_acquire();
esp_err_t ret = rom_spi_flash_mmap_pages(pages, page_count, flags, out_ptr, out_handle);
if (ret != ESP_OK) {
mmap_lock_release();
return ret;
}
MMAP_CNT_INCREASE();
if (permanent) {
assert((*out_handle & ROM_MMAP_HANDLE_PERMANENT_BIT) == 0);
*out_handle |= ROM_MMAP_HANDLE_PERMANENT_BIT;
mmap_lock_release();
}
return ESP_OK;
}
void spi_flash_munmap(spi_flash_mmap_handle_t handle)
{
bool permanent = handle & ROM_MMAP_HANDLE_PERMANENT_BIT;
spi_flash_mmap_handle_t rom_handle = handle & ~ROM_MMAP_HANDLE_PERMANENT_BIT;
if (permanent) {
mmap_lock_acquire();
}
rom_spi_flash_munmap(rom_handle);
MMAP_CNT_DECREASE();
mmap_lock_release();
}
#endif //!MMAP_ROM_IMPL_ENABLED
#if !MMAP_ROM_IMPL_ENABLED
void spi_flash_mmap_dump(void)
{
esp_mmu_map_dump_mapped_blocks(stdout);
}
uint32_t spi_flash_mmap_get_free_pages(spi_flash_mmap_memory_t memory)
{
mmu_mem_caps_t caps = 0;
@@ -345,9 +661,9 @@ IRAM_ATTR bool spi_flash_check_and_flush_cache(size_t start_addr, size_t length)
}
return ret;
}
#endif // !ESP_ROM_HAS_SPI_FLASH_MMAP || !CONFIG_SPI_FLASH_ROM_IMPL
#endif //!MMAP_ROM_IMPL_ENABLED
#if !ESP_ROM_HAS_SPI_FLASH_MMAP || !CONFIG_SPI_FLASH_ROM_IMPL || CONFIG_SPIRAM_FETCH_INSTRUCTIONS || CONFIG_SPIRAM_RODATA
#if !MMAP_ROM_IMPL_ENABLED || CONFIG_SPIRAM_FETCH_INSTRUCTIONS || CONFIG_SPIRAM_RODATA
/* ROM and patch information
* Latest: Add the mapping from psram physical address to flash when CONFIG_SPIRAM_FETCH_INSTRUCTIONS or CONFIG_SPIRAM_RODATA enabled
* V1 (Latest): added to ROM
@@ -432,4 +748,6 @@ const void * spi_flash_phys2cache(size_t phys_offs, spi_flash_mmap_memory_t memo
assert(ret == ESP_OK);
return (const void *)ptr;
}
#endif //!ESP_ROM_HAS_SPI_FLASH_MMAP || !CONFIG_SPI_FLASH_ROM_IMPL || CONFIG_SPIRAM_FETCH_INSTRUCTIONS || CONFIG_SPIRAM_RODATA
#endif //!MMAP_ROM_IMPL_ENABLED || CONFIG_SPIRAM_FETCH_INSTRUCTIONS || CONFIG_SPIRAM_RODATA
ESP_STATIC_ASSERT(SPI_FLASH_MMAP_FLAG_DATA + SPI_FLASH_MMAP_FLAG_INST < SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, "spi_flash_mmap_memory_t not compatible with spi_flash_mmap_flag_t");
@@ -31,6 +31,10 @@ typedef struct esp_flash_os_functions_t {
/** Limit CPU frequency during flash operations (ESP32-C5 only, 240MHz).
*/
#define ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ BIT(0)
/** Indicates that this operation forbids flash from being read (e.g., write/erase).
* The OS layer implementation needs to take appropriate measures to avoid concurrent read operations.
*/
#define ESP_FLASH_START_FLAG_NO_READ BIT(1)
/**
* Called before commencing any flash operation. Does not need to be
* recursive (ie is called at most once for each call to 'end').
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -27,6 +27,12 @@ extern "C" {
*/
esp_err_t esp_flash_init_default_chip(void);
/**
* @brief Initialize main flash
* @param chip Pointer to main SPI flash(SPI1 CS0) chip to use..
*/
esp_err_t esp_flash_init_main(esp_flash_t *chip);
/**
* Enable OS-level SPI flash protections in IDF
*
@@ -76,7 +82,7 @@ esp_err_t esp_flash_deinit_os_functions(esp_flash_t* chip, spi_bus_lock_dev_hand
*
* @return esp_err_t always ESP_OK.
*/
esp_err_t esp_flash_init_main_bus_lock(void);
esp_err_t esp_flash_app_init_os_functions(void);
/**
* Initialize OS-level functions for the main flash chip.
@@ -0,0 +1,47 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include "sdkconfig.h"
#include "esp_rom_caps.h"
#include "esp_err.h"
#ifdef __cplusplus
extern "C" {
#endif
//executables are placed on flash
#define MMAP_EXECUTABLES_FROM_FLASH (!((CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_RODATA) || CONFIG_APP_BUILD_TYPE_RAM))
#define MMAP_ROM_IMPL_ENABLED (CONFIG_SPI_FLASH_ROM_IMPL && ESP_ROM_HAS_SPI_FLASH_MMAP)
#if !CONFIG_IDF_TARGET_ESP32
//No mmap lock for ESP32, since it's useless to block while can't read from flash with SPI0 (cache) and SPI1 (driver) at the same time.
/**
* Initialize the internal lock earlier to bypass the memory leak check.
*/
esp_err_t flash_mmap_lock_init(void);
/**
* Freeze the mmap table
*/
void flash_mmap_lock_freeze(void);
/**
* Unfreeze the mmap table
*/
void flash_mmap_lock_unfreeze(void);
/**
* Return whether there is remaining mmap regions, must be called between flash_mmap_lock_freeze() and flash_mmap_lock_unfreeze().
*/
bool flash_mmap_remain(void);
#endif //!CONFIG_IDF_TARGET_ESP32
#ifdef __cplusplus
}
#endif
@@ -78,12 +78,6 @@ uint8_t esp_mspi_get_io(esp_mspi_io_t io);
*/
void spi_flash_set_rom_required_regs(void);
/**
* @brief Initialize main flash
* @param chip Pointer to main SPI flash(SPI1 CS0) chip to use..
*/
esp_err_t esp_flash_init_main(esp_flash_t *chip);
/**
* @brief Should be only used by SPI1 Flash driver to know the necessary timing registers
* @param out_timing_config Pointer to timing_tuning parameters.
+49 -10
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -15,7 +15,10 @@
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#include <esp_bit_defs.h>
#include <esp_assert.h>
#include "esp_err.h"
#include "esp_attr.h"
#include "sdkconfig.h"
#include "esp_spi_flash_counters.h"
@@ -31,12 +34,40 @@ extern "C" {
#define SPI_FLASH_MMU_PAGE_SIZE CONFIG_MMU_PAGE_SIZE /**< Flash cache MMU mapping page size */
/**
* @brief Enumeration which specifies memory space requested in an mmap call
* @brief Flags for spi_flash_mmap and spi_flash_mmap_pages calls
*/
typedef enum {
SPI_FLASH_MMAP_DATA, /**< map to data memory, allows byte-aligned access*/
SPI_FLASH_MMAP_INST, /**< map to instruction memory, allows only 4-byte-aligned access*/
} spi_flash_mmap_memory_t;
SPI_FLASH_MMAP_FLAG_DATA = 0, /**< map to data memory, allows byte-aligned access*/
SPI_FLASH_MMAP_FLAG_INST = 1, /**< map to instruction memory, allows only 4-byte-aligned access*/
SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE = BIT(1),
/**< Blocks flash erasing/programming until spi_flash_munmap when necessary. Flash can't be read when there is
* erasing/programming in progress, even if the regions to be read from/written to don't overlap.
*
* This flag helps avoid cache disabling and its influence to system when when XIP from PSRAM
* (`CONFIG_SPIRAM_XIP_FROM_PSRAM`) is enabled.
*
* Call mmap with this flag unless you want to do erasing/programming between this mmap and its munmap, or want to
* create a mapping that will last for very long.
*
* Ignored on ESP32.
*/
} spi_flash_mmap_flag_t;
/** @def SPI_FLASH_MMAP_DATA
*
* Data memory.
*/
#define SPI_FLASH_MMAP_DATA SPI_FLASH_MMAP_FLAG_DATA
/** @def SPI_FLASH_MMAP_INST
*
* Instruction memory.
*/
#define SPI_FLASH_MMAP_INST SPI_FLASH_MMAP_FLAG_INST
/** Enumeration which specifies memory space requested. SPI_FLASH_MMAP_DATA or SPI_FLASH_MMAP_INST */
typedef spi_flash_mmap_flag_t spi_flash_mmap_memory_t;
/**
* @brief Opaque handle for memory region obtained from spi_flash_mmap.
@@ -54,18 +85,21 @@ typedef uint32_t spi_flash_mmap_handle_t;
* may become fragmented. To troubleshoot issues with page allocation, use
* spi_flash_mmap_dump() function.
*
* Call with SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE flag when you only want to read some data from the flash without writing
* to it before the spi_flash_munmap. This can reduce the influence to system due to cache disabling on non-ESP32 chips.
*
* @param src_addr Physical address in flash where requested region starts.
* This address *must* be aligned to 64kB boundary
* (SPI_FLASH_MMU_PAGE_SIZE)
* @param size Size of region to be mapped. This size will be rounded
* up to a 64kB boundary
* @param memory Address space where the region should be mapped (data or instruction)
* @param flags Flags of the mapping, including address space where the region should be mapped (data or instruction)
* @param[out] out_ptr Output, pointer to the mapped memory region
* @param[out] out_handle Output, handle which should be used for spi_flash_munmap call
*
* @return ESP_OK on success, ESP_ERR_NO_MEM if pages can not be allocated
*/
esp_err_t spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_memory_t memory,
esp_err_t spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_flag_t flags,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle);
/**
@@ -76,12 +110,15 @@ esp_err_t spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_memory_t m
* In this respect, it works in a similar way as spi_flash_mmap() but it allows mapping
* a (maybe non-contiguous) set of pages into a contiguous region of memory.
*
* Call with SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE flag when you only want to read some data from the flash without writing
* to it before the spi_flash_munmap. This can reduce the influence to system due to cache disabling on non-ESP32 chips.
*
* @param pages An array of numbers indicating the 64kB pages in flash to be mapped
* contiguously into memory. These indicate the indexes of the 64kB pages,
* not the byte-size addresses as used in other functions.
* Array must be located in internal memory.
* @param page_count Number of entries in the pages array
* @param memory Address space where the region should be mapped (instruction or data)
* @param flags Flags of the mapping, including address space where the region should be mapped (data or instruction)
* @param[out] out_ptr Output, pointer to the mapped memory region
* @param[out] out_handle Output, handle which should be used for spi_flash_munmap call
*
@@ -91,7 +128,7 @@ esp_err_t spi_flash_mmap(size_t src_addr, size_t size, spi_flash_mmap_memory_t m
* - ESP_ERR_INVALID_ARG if pagecount is zero or pages array is not in
* internal memory
*/
esp_err_t spi_flash_mmap_pages(const int *pages, size_t page_count, spi_flash_mmap_memory_t memory,
esp_err_t spi_flash_mmap_pages(const int *pages, size_t page_count, spi_flash_mmap_flag_t flags,
const void** out_ptr, spi_flash_mmap_handle_t* out_handle);
@@ -125,7 +162,7 @@ void spi_flash_mmap_dump(void);
*
* @param memory memory type of MMU table free page
*
* @return number of free pages which can be mmaped
* @return number of free pages which can be mapped
*/
uint32_t spi_flash_mmap_get_free_pages(spi_flash_mmap_memory_t memory);
@@ -168,3 +205,5 @@ const void *spi_flash_phys2cache(size_t phys_offs, spi_flash_mmap_memory_t memor
#ifdef __cplusplus
}
#endif
FLAG_ATTR(spi_flash_mmap_flag_t)
+4 -4
View File
@@ -49,9 +49,10 @@ entries:
if SPI_FLASH_ROM_IMPL = n || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV = y || SPI_FLASH_FREQ_LIMIT_C5_240MHZ = y:
esp_flash_api: esp_flash_write_encrypted (noflash)
if SPI_FLASH_ROM_IMPL = n || SPI_FLASH_FREQ_LIMIT_C5_240MHZ = y:
esp_flash_api: spiflash_start_default (noflash)
esp_flash_api: spiflash_start_core (noflash)
esp_flash_api: spiflash_start_prog (noflash)
esp_flash_api: spiflash_start_core (noflash)
esp_flash_api: spiflash_start_read (noflash)
esp_flash_api: esp_flash_read (noflash)
if SPI_FLASH_ROM_IMPL = n || ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV = y:
esp_flash_api: flash_end_flush_cache (noflash)
@@ -65,7 +66,6 @@ entries:
esp_flash_api: esp_flash_set_chip_write_protect (noflash)
esp_flash_api: esp_flash_get_protected_region (noflash)
esp_flash_api: esp_flash_set_protected_region (noflash)
esp_flash_api: esp_flash_read (noflash)
esp_flash_api: esp_flash_write (noflash)
esp_flash_api: esp_flash_read_encrypted (noflash)
esp_flash_api: esp_flash_get_io_mode (noflash)
+177 -46
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2015-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -23,18 +23,13 @@
#include "esp_private/spi_flash_os.h"
#include "esp_private/cache_utils.h"
#include "esp_private/flash_mmap.h"
#include "esp_private/spi_share_hw_ctrl.h"
// For C5 encrypted write workaround
// Functions are only available when CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ is true
#include "esp_private/spi_flash_freq_limit_cbs.h"
#define SPI_FLASH_CACHE_NO_DISABLE (CONFIG_SPI_FLASH_AUTO_SUSPEND || (CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_RODATA) || CONFIG_APP_BUILD_TYPE_RAM)
static const char TAG[] = "spi_flash";
#if SPI_FLASH_CACHE_NO_DISABLE
static _lock_t s_spi1_flash_mutex;
#endif // #if SPI_FLASH_CACHE_NO_DISABLE
/*
* OS functions providing delay service and arbitration among chips, and with the cache.
@@ -43,6 +38,14 @@ static _lock_t s_spi1_flash_mutex;
* into the IRAM,and their data should be put into the DRAM.
*/
typedef enum {
OP_TYPE_MUTEX = 0,
OP_TYPE_MMAP_LOCK = 1,
OP_TYPE_SCHEDULER_DIS = 2,
OP_TYPE_CACHE_DIS = 3,
OP_TYPE_BUS_LOCK = 4,
} spi1_op_type_t;
/*
* Time yield algorithm:
* Every time spi_flash_os_check_yield() is called:
@@ -56,18 +59,22 @@ static _lock_t s_spi1_flash_mutex;
*/
typedef struct {
spi_bus_lock_dev_handle_t dev_lock;
bool no_protect; //to decide whether to check protected region (for the main chip) or not.
uint32_t no_protect : 1; //to decide whether to check protected region (for the main chip) or not.
uint32_t current_op_type : 3; //Whether the mmap lock is already taken, only for SPI1.
uint32_t reserved : 28;
uint32_t acquired_since_us; // Time since last explicit yield()
uint32_t released_since_us; // Time since last end() (implicit yield)
uint32_t start_flags; // Flags passed to start() function, used to determine if freq_limit was called
} app_func_arg_t;
static const char TAG[] = "spi_flash";
static inline void on_spi_released(app_func_arg_t* ctx);
static inline void on_spi_acquired(app_func_arg_t* ctx);
static inline void on_spi_yielded(app_func_arg_t* ctx);
static inline bool on_spi_check_yield(app_func_arg_t* ctx);
#if !SPI_FLASH_CACHE_NO_DISABLE
#if !CONFIG_SPI_FLASH_AUTO_SUSPEND
IRAM_ATTR static void cache_enable(void* arg)
{
spi_flash_enable_interrupts_caches_and_other_cpu();
@@ -77,7 +84,7 @@ IRAM_ATTR static void cache_disable(void* arg)
{
spi_flash_disable_interrupts_caches_and_other_cpu();
}
#endif //#if !SPI_FLASH_CACHE_NO_DISABLE
#endif //#if !CONFIG_SPI_FLASH_AUTO_SUSPEND
static IRAM_ATTR esp_err_t acquire_spi_bus_lock(void *arg)
{
@@ -112,10 +119,11 @@ static esp_err_t spi23_end(void *arg)
return ret;
}
#if CONFIG_IDF_TARGET_ESP32
static IRAM_ATTR esp_err_t spi1_start(void *arg, uint32_t flags)
{
esp_err_t ret = ESP_OK;
app_func_arg_t* ctx = (app_func_arg_t*)arg;
esp_err_t ret = ESP_OK;
ctx->start_flags = flags;
/**
@@ -128,26 +136,133 @@ static IRAM_ATTR esp_err_t spi1_start(void *arg, uint32_t flags)
*/
#if CONFIG_SPI_FLASH_SHARE_SPI1_BUS
//use the lock to disable the cache and interrupts before using the SPI bus
ret = acquire_spi_bus_lock(arg);
#elif SPI_FLASH_CACHE_NO_DISABLE
_lock_acquire(&s_spi1_flash_mutex);
ret = acquire_spi_bus_lock(ctx);
ctx->current_op_type = OP_TYPE_BUS_LOCK;
#else
//directly disable the cache and interrupts when lock is not used
// For RAM App, it's possible to keep cache enabled when there is no mapping exists, or mutex between erasing and
// the mmap. However this will increase the complexity of mmap lock and decrease the performance.
// Not doing this for now.
cache_disable(NULL);
ctx->current_op_type = OP_TYPE_CACHE_DIS;
#endif //CONFIG_SPI_FLASH_SHARE_SPI1_BUS
on_spi_acquired(ctx);
return ret;
}
static IRAM_ATTR esp_err_t spi1_end(void *arg)
{
esp_err_t ret = ESP_OK;
app_func_arg_t* ctx = (app_func_arg_t*)arg;
/**
* There are three ways for ESP Flash API lock, see `spi1_start`
*/
#if CONFIG_SPI_FLASH_SHARE_SPI1_BUS
assert(ctx->current_op_type == OP_TYPE_BUS_LOCK);
ret = release_spi_bus_lock(ctx);
#else
assert(ctx->current_op_type == OP_TYPE_CACHE_DIS);
cache_enable(NULL);
#endif
on_spi_released(ctx);
return ret;
}
#else //CONFIG_IDF_TARGET_ESP32
#if CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND
// Disable scheduler
static void disable_scheduler(void)
{
if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
#ifdef CONFIG_FREERTOS_SMP
# ifdef CONFIG_FREERTOS_SMP
//Note: Scheduler suspension behavior changed in FreeRTOS SMP
vTaskPreemptionDisable(NULL);
#else
# else
// Disable scheduler on the current CPU
vTaskSuspendAll();
#endif // CONFIG_FREERTOS_SMP
# endif // CONFIG_FREERTOS_SMP
}
#endif // CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND
}
static void restore_scheduler(void)
{
if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
# ifdef CONFIG_FREERTOS_SMP
//Note: Scheduler suspension behavior changed in FreeRTOS SMP
vTaskPreemptionEnable(NULL);
# else
xTaskResumeAll();
# endif // CONFIG_FREERTOS_SMP
}
}
#endif //CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND
static _lock_t s_spi1_flash_mutex; //Lock preventing concurrent access to SPI1 Flash operations.
static IRAM_ATTR esp_err_t spi1_start(void *arg, uint32_t flags)
{
//context members can only be accessed after the lock is acquired
app_func_arg_t* ctx = (app_func_arg_t*)arg;
esp_err_t ret = ESP_OK;
/**
* There are different locks used in the SPI Flash API:
* 1. Mutex protecting concurrent access to SPI1. (s_spi1_flash_mutex)
* 2. Mmap lock (from flash_mmap.c), avoid access from SPI0 due to mmap
* 3. Disable scheduler, this is used in auto-suspend mode when we want to disable the scheduler to avoid the suspend caused by tasks
* 4. Cache lock (from cache_utils.h), this is used when we need to disable Cache to avoid cache access from CPU via SPI0
* From 1 to 4, the lock overhead increases.
*
* Different modes take different locks:
*
* Mode Write/Erase Read
* !EXEC_FROM_FLASH Mutex + Mmap Lock (+ Disable Cache) Mutex only
* Auto suspend Mutex + Mmap Lock + Dis. Scheduler Mutex + Dis. Scheduler
* EXEC_FROM_FLASH Mmap Lock + Disable Cache Disable Cache
*/
if (flags & ESP_FLASH_START_FLAG_NO_READ) {
/**
* Take the mmap lock to minimize remain mmap pages. If there is no mmap page remain, we can keep the cache
* enabled. For Auto suspend, though the cache is able to read, we still wait for the mmap to finish to avoid
* unnecessary suspend.
*
* Write/Erase path: take mmap lock before SPI1 mutex, so that when erase/prog is blocked by mmap, read is still
* available.
*/
flash_mmap_lock_freeze();
}
#if !CONFIG_SPI_FLASH_AUTO_SUSPEND
# if !MMAP_EXECUTABLES_FROM_FLASH
// XIP from PSRAM/RAM app: cache accesses go to PSRAM/RAM, not flash.
// Mutex serializes concurrent SPI1 access.
_lock_acquire(&s_spi1_flash_mutex);
if (!(flags & ESP_FLASH_START_FLAG_NO_READ) || !flash_mmap_remain()) {
// When read, or write while no mmap page remaining, we can keep the cache enabled.
ctx->current_op_type = OP_TYPE_MMAP_LOCK;
} else {
// Otherwise, we still need to disable the cache.
cache_disable(NULL);
ctx->current_op_type = OP_TYPE_CACHE_DIS;
}
# else
// Code runs from flash: cache_disable already ensures the serialization. No extra mutex needed here.
cache_disable(NULL);
ctx->current_op_type = OP_TYPE_CACHE_DIS;
# endif
#else //CONFIG_SPI_FLASH_AUTO_SUSPEND
_lock_acquire(&s_spi1_flash_mutex);
# if CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND
disable_scheduler();
# endif // CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND
ctx->current_op_type = OP_TYPE_SCHEDULER_DIS;
#endif //CONFIG_SPI_FLASH_AUTO_SUSPEND
ctx->start_flags = flags;
#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
if (flags & ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ) {
@@ -163,41 +278,48 @@ static IRAM_ATTR esp_err_t spi1_end(void *arg)
{
esp_err_t ret = ESP_OK;
app_func_arg_t* ctx = (app_func_arg_t*)arg;
// Call freq_limit_unlock if needed, before releasing the lock
#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
uint32_t flags = ctx->start_flags;
#if CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ
if (flags & ESP_FLASH_START_FLAG_LIMIT_CPU_FREQ) {
esp_flash_freq_unlimit_cb();
}
#endif
/**
* There are three ways for ESP Flash API lock, see `spi1_start`
* There are different lock paths, see `spi1_start`
*/
#if CONFIG_SPI_FLASH_SHARE_SPI1_BUS
ret = release_spi_bus_lock(arg);
#elif SPI_FLASH_CACHE_NO_DISABLE
_lock_release(&s_spi1_flash_mutex);
#else
cache_enable(NULL);
#endif
#if CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND
if (xTaskGetSchedulerState() == taskSCHEDULER_RUNNING) {
#ifdef CONFIG_FREERTOS_SMP
//Note: Scheduler suspension behavior changed in FreeRTOS SMP
vTaskPreemptionEnable(NULL);
#else
xTaskResumeAll();
#endif // CONFIG_FREERTOS_SMP
#if !CONFIG_SPI_FLASH_AUTO_SUSPEND
# if !MMAP_EXECUTABLES_FROM_FLASH
if (ctx->current_op_type != OP_TYPE_MMAP_LOCK) {
assert(ctx->current_op_type == OP_TYPE_CACHE_DIS);
cache_enable(NULL);
}
_lock_release(&s_spi1_flash_mutex);
# else
assert(ctx->current_op_type == OP_TYPE_CACHE_DIS);
cache_enable(NULL);
# endif
#else //!CONFIG_SPI_FLASH_AUTO_SUSPEND
# if CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND
restore_scheduler();
# endif // CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND
assert(ctx->current_op_type == OP_TYPE_SCHEDULER_DIS);
_lock_release(&s_spi1_flash_mutex);
#endif //!CONFIG_SPI_FLASH_AUTO_SUSPEND
if (flags & ESP_FLASH_START_FLAG_NO_READ) {
flash_mmap_lock_unfreeze();
}
#endif // CONFIG_SPI_FLASH_DISABLE_SCHEDULER_IN_SUSPEND
on_spi_released(ctx);
return ret;
}
#endif // !CONFIG_IDF_TARGET_ESP32
static esp_err_t spi_flash_os_check_yield(void *arg, uint32_t chip_status, uint32_t* out_request)
{
assert (chip_status == 0); //TODO: support suspend
@@ -376,8 +498,18 @@ esp_err_t esp_flash_deinit_os_functions(esp_flash_t* chip, spi_bus_lock_dev_hand
return ESP_OK;
}
esp_err_t esp_flash_init_main_bus_lock(void)
esp_err_t esp_flash_app_init_os_functions(void)
{
esp_err_t err = ESP_OK;
#if !CONFIG_IDF_TARGET_ESP32
_lock_init(&s_spi1_flash_mutex);
err = flash_mmap_lock_init();
if (err != ESP_OK) {
return err;
}
#endif
/* The following called functions are only defined if CONFIG_SPI_FLASH_SHARE_SPI1_BUS
* is set. Thus, we must not call them if the macro is not defined, else the linker
* would trigger errors. */
@@ -385,14 +517,13 @@ esp_err_t esp_flash_init_main_bus_lock(void)
/* bus_lock is registered by `spi_bus_lock_init_main_bus` constructor in spi_common.c */
spi_bus_lock_set_bg_control(g_main_spi_bus_lock, cache_enable, cache_disable, NULL);
esp_err_t err = spi_bus_lock_init_main_dev();
err = spi_bus_lock_init_main_dev();
if (err != ESP_OK) {
return err;
}
return ESP_OK;
#else
return ESP_ERR_NOT_SUPPORTED;
#endif
(void)err;
return ESP_OK;
}
esp_err_t esp_flash_app_enable_os_functions(esp_flash_t* chip)
@@ -1,8 +1,11 @@
set(srcs "test_app_main.c"
"test_flash_mmap.c")
"test_mmap_utils.c"
"test_flash_mmap.c"
"test_mmap_api_concurrent.c")
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
idf_component_register(SRCS ${srcs}
PRIV_REQUIRES unity test_utils spi_flash esp_partition efuse
PRIV_REQUIRES unity test_utils spi_flash bootloader_support esp_partition esp_mm efuse
PRIV_INCLUDE_DIRS .
WHOLE_ARCHIVE)
@@ -20,114 +20,35 @@
#include "esp_flash.h"
#include "test_utils.h"
#include "test_mmap_utils.h"
static uint32_t buffer[1024];
/* read-only region used for mmap tests, initialised in setup_mmap_tests() */
static uint32_t start;
static uint32_t end;
static spi_flash_mmap_handle_t handle1, handle2, handle3;
static esp_err_t spi_flash_read_maybe_encrypted(size_t src_addr, void *des_addr, size_t size)
{
if (!esp_efuse_is_flash_encryption_enabled()) {
return esp_flash_read(NULL, des_addr, src_addr, size);
} else {
return esp_flash_read_encrypted(NULL, src_addr, des_addr, size);
}
}
static esp_err_t spi_flash_write_maybe_encrypted(size_t des_addr, const void *src_addr, size_t size)
{
if (!esp_efuse_is_flash_encryption_enabled()) {
return esp_flash_write(NULL, src_addr, des_addr, size);
} else {
return esp_flash_write_encrypted(NULL, des_addr, src_addr, size);
}
}
static void setup_mmap_tests(void)
{
if (start == 0) {
const esp_partition_t *part = get_test_data_partition();
start = part->address;
end = part->address + part->size;
printf("Test data partition @ 0x%"PRIx32" - 0x%"PRIx32"\n", start, end);
}
TEST_ASSERT(end > start);
TEST_ASSERT(end - start >= 512 * 1024);
/* clean up any mmap handles left over from failed tests */
if (handle1) {
spi_flash_munmap(handle1);
handle1 = 0;
}
if (handle2) {
spi_flash_munmap(handle2);
handle2 = 0;
}
if (handle3) {
spi_flash_munmap(handle3);
handle3 = 0;
}
/* prepare flash contents */
srand(0);
for (int block = start / 0x10000; block < end / 0x10000; ++block) {
for (int sector = 0; sector < 16; ++sector) {
uint32_t abs_sector = (block * 16) + sector;
uint32_t sector_offs = abs_sector * SPI_FLASH_SEC_SIZE;
bool sector_needs_write = false;
TEST_ESP_OK( spi_flash_read_maybe_encrypted(sector_offs, buffer, sizeof(buffer)) );
for (uint32_t word = 0; word < 1024; ++word) {
uint32_t val = rand();
if (block == start / 0x10000 && sector == 0 && word == 0) {
printf("setup_mmap_tests(): first prepped word: 0x%08"PRIx32" (flash holds 0x%08"PRIx32")\n", val, buffer[word]);
}
if (buffer[word] != val) {
buffer[word] = val;
sector_needs_write = true;
}
}
/* Only rewrite the sector if it has changed */
if (sector_needs_write) {
TEST_ESP_OK( esp_flash_erase_region(NULL, (uint16_t) abs_sector * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE) );
TEST_ESP_OK( spi_flash_write_maybe_encrypted(sector_offs, (const uint8_t *) buffer, sizeof(buffer)) );
}
}
}
}
TEST_CASE("Can get correct data in existing mapped region", "[spi_flash][mmap]")
{
setup_mmap_tests();
printf("Mapping %"PRIx32" (+%"PRIx32")\n", start, end - start);
printf("Mapping %"PRIx32" (+%"PRIx32")\n", test_start, test_end - test_start);
const void *ptr1;
TEST_ESP_OK( spi_flash_mmap(start, end - start, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
TEST_ESP_OK( spi_flash_mmap(test_start, test_end - test_start, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr1, &handle1) );
printf("mmap_res: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle1, ptr1);
/* Remap in the previously mapped region itself */
uint32_t new_start = start + CONFIG_MMU_PAGE_SIZE;
printf("Mapping %"PRIx32" (+%"PRIx32")\n", new_start, end - new_start);
uint32_t new_start = test_start + CONFIG_MMU_PAGE_SIZE;
printf("Mapping %"PRIx32" (+%"PRIx32")\n", new_start, test_end - new_start);
const void *ptr2;
TEST_ESP_OK( spi_flash_mmap(new_start, end - new_start, SPI_FLASH_MMAP_DATA, &ptr2, &handle2) );
TEST_ESP_OK( spi_flash_mmap(new_start, test_end - new_start, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr2, &handle2) );
printf("mmap_res: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle2, ptr2);
const void *src1 = (void *) ((uint32_t) ptr1 + CONFIG_MMU_PAGE_SIZE);
const void *src2 = ptr2;
/* Memory contents should be identical - as the region is same */
TEST_ASSERT_EQUAL(0, memcmp(src1, src2, end - new_start));
TEST_ASSERT_EQUAL(0, memcmp(src1, src2, test_end - new_start));
spi_flash_munmap(handle1);
handle1 = 0;
spi_flash_munmap(handle2);
handle2 = 0;
TEST_ASSERT_EQUAL_PTR(NULL, spi_flash_phys2cache(start, SPI_FLASH_MMAP_DATA));
TEST_ASSERT_EQUAL_PTR(NULL, spi_flash_phys2cache(test_start, SPI_FLASH_MMAP_DATA));
}
TEST_CASE("Can mmap into data address space", "[spi_flash][mmap]")
@@ -135,14 +56,14 @@ TEST_CASE("Can mmap into data address space", "[spi_flash][mmap]")
esp_err_t ret = ESP_FAIL;
setup_mmap_tests();
printf("Mapping %"PRIx32" (+%"PRIx32")\n", start, end - start);
printf("Mapping %"PRIx32" (+%"PRIx32")\n", test_start, test_end - test_start);
const void *ptr1;
TEST_ESP_OK( spi_flash_mmap(start, end - start, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
TEST_ESP_OK( spi_flash_mmap(test_start, test_end - test_start, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr1, &handle1) );
printf("mmap_res: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle1, ptr1);
srand(0);
const uint32_t *data = (const uint32_t *) ptr1;
for (int block = 0; block < (end - start) / 0x10000; ++block) {
for (int block = 0; block < (test_end - test_start) / 0x10000; ++block) {
printf("block %d\n", block);
for (int sector = 0; sector < 16; ++sector) {
printf("sector %d\n", sector);
@@ -151,25 +72,25 @@ TEST_CASE("Can mmap into data address space", "[spi_flash][mmap]")
}
}
}
printf("Mapping %"PRIx32" (+%x)\n", start - 0x10000, 0x20000);
printf("Mapping %"PRIx32" (+%x)\n", test_start - 0x10000, 0x20000);
const void *ptr2;
TEST_ESP_OK( spi_flash_mmap(start - 0x10000, 0x20000, SPI_FLASH_MMAP_DATA, &ptr2, &handle2) );
TEST_ESP_OK( spi_flash_mmap(test_start - 0x10000, 0x20000, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr2, &handle2) );
printf("mmap_res: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle2, ptr2);
TEST_ASSERT_EQUAL_HEX32(start - 0x10000, spi_flash_cache2phys(ptr2));
TEST_ASSERT_EQUAL_HEX32(test_start - 0x10000, spi_flash_cache2phys(ptr2));
TEST_ASSERT_EQUAL_PTR(ptr2, spi_flash_phys2cache(start - 0x10000, SPI_FLASH_MMAP_DATA));
TEST_ASSERT_EQUAL_PTR(ptr2, spi_flash_phys2cache(test_start - 0x10000, SPI_FLASH_MMAP_DATA));
printf("Mapping %"PRIx32" (+%x)\n", start, 0x10000);
printf("Mapping %"PRIx32" (+%x)\n", test_start, 0x10000);
const void *ptr3;
ret = spi_flash_mmap(start, 0x10000, SPI_FLASH_MMAP_DATA, &ptr3, &handle3);
ret = spi_flash_mmap(test_start, 0x10000, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr3, &handle3);
printf("ret: 0x%x\n", ret);
TEST_ASSERT(ret == ESP_OK);
printf("mmap_res: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle3, ptr3);
TEST_ASSERT_EQUAL_HEX32(start, spi_flash_cache2phys(ptr3));
TEST_ASSERT_EQUAL_PTR(ptr3, spi_flash_phys2cache(start, SPI_FLASH_MMAP_DATA));
TEST_ASSERT_EQUAL_PTR((intptr_t)ptr3 + 0x4444, spi_flash_phys2cache(start + 0x4444, SPI_FLASH_MMAP_DATA));
TEST_ASSERT_EQUAL_HEX32(test_start, spi_flash_cache2phys(ptr3));
TEST_ASSERT_EQUAL_PTR(ptr3, spi_flash_phys2cache(test_start, SPI_FLASH_MMAP_DATA));
TEST_ASSERT_EQUAL_PTR((intptr_t)ptr3 + 0x4444, spi_flash_phys2cache(test_start + 0x4444, SPI_FLASH_MMAP_DATA));
printf("Unmapping handle1\n");
spi_flash_munmap(handle1);
@@ -183,8 +104,8 @@ TEST_CASE("Can mmap into data address space", "[spi_flash][mmap]")
spi_flash_munmap(handle3);
handle3 = 0;
printf("start corresponding vaddr: 0x%x\n", (int)spi_flash_phys2cache(start, SPI_FLASH_MMAP_DATA));
TEST_ASSERT_EQUAL_PTR(NULL, spi_flash_phys2cache(start, SPI_FLASH_MMAP_DATA));
printf("start corresponding vaddr: 0x%x\n", (int)spi_flash_phys2cache(test_start, SPI_FLASH_MMAP_DATA));
TEST_ASSERT_EQUAL_PTR(NULL, spi_flash_phys2cache(test_start, SPI_FLASH_MMAP_DATA));
}
#if !CONFIG_SPI_FLASH_ROM_IMPL //flash mmap API in ROM does not support mmap into instruction address
@@ -192,47 +113,47 @@ TEST_CASE("Can mmap into instruction address space", "[spi_flash][mmap]")
{
setup_mmap_tests();
printf("Mapping %"PRIx32" (+%"PRIx32")\n", start, end - start);
spi_flash_mmap_handle_t handle1;
printf("Mapping %"PRIx32" (+%"PRIx32")\n", test_start, test_end - test_start);
const void *ptr1;
TEST_ESP_OK( spi_flash_mmap(start, end - start, SPI_FLASH_MMAP_INST, &ptr1, &handle1) );
TEST_ESP_OK( spi_flash_mmap(test_start, test_end - test_start, SPI_FLASH_MMAP_FLAG_INST | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr1, &handle1) );
printf("mmap_res: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle1, ptr1);
srand(0);
const uint32_t *data = (const uint32_t *) ptr1;
for (int block = 0; block < (end - start) / 0x10000; ++block) {
for (int block = 0; block < (test_end - test_start) / 0x10000; ++block) {
for (int sector = 0; sector < 16; ++sector) {
for (uint32_t word = 0; word < 1024; ++word) {
TEST_ASSERT_EQUAL_UINT32(rand(), data[(block * 16 + sector) * 1024 + word]);
}
}
}
printf("Mapping %"PRIx32" (+%x)\n", start - 0x10000, 0x20000);
spi_flash_mmap_handle_t handle2;
printf("Mapping %"PRIx32" (+%x)\n", test_start - 0x10000, 0x20000);
const void *ptr2;
TEST_ESP_OK( spi_flash_mmap(start - 0x10000, 0x20000, SPI_FLASH_MMAP_INST, &ptr2, &handle2) );
TEST_ESP_OK( spi_flash_mmap(test_start - 0x10000, 0x20000, SPI_FLASH_MMAP_FLAG_INST | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr2, &handle2) );
printf("mmap_res: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle2, ptr2);
TEST_ASSERT_EQUAL_HEX32(start - 0x10000, spi_flash_cache2phys(ptr2));
TEST_ASSERT_EQUAL_PTR(ptr2, spi_flash_phys2cache(start - 0x10000, SPI_FLASH_MMAP_INST));
TEST_ASSERT_EQUAL_HEX32(test_start - 0x10000, spi_flash_cache2phys(ptr2));
TEST_ASSERT_EQUAL_PTR(ptr2, spi_flash_phys2cache(test_start - 0x10000, SPI_FLASH_MMAP_INST));
printf("Mapping %"PRIx32" (+%x)\n", start, 0x10000);
spi_flash_mmap_handle_t handle3;
printf("Mapping %"PRIx32" (+%x)\n", test_start, 0x10000);
const void *ptr3;
TEST_ESP_OK( spi_flash_mmap(start, 0x10000, SPI_FLASH_MMAP_INST, &ptr3, &handle3) );
TEST_ESP_OK( spi_flash_mmap(test_start, 0x10000, SPI_FLASH_MMAP_FLAG_INST | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr3, &handle3) );
printf("mmap_res: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle3, ptr3);
TEST_ASSERT_EQUAL_HEX32(start, spi_flash_cache2phys(ptr3));
TEST_ASSERT_EQUAL_PTR(ptr3, spi_flash_phys2cache(start, SPI_FLASH_MMAP_INST));
TEST_ASSERT_EQUAL_HEX32(test_start, spi_flash_cache2phys(ptr3));
TEST_ASSERT_EQUAL_PTR(ptr3, spi_flash_phys2cache(test_start, SPI_FLASH_MMAP_INST));
printf("Unmapping handle1\n");
spi_flash_munmap(handle1);
handle1 = 0;
printf("Unmapping handle2\n");
spi_flash_munmap(handle2);
handle2 = 0;
printf("Unmapping handle3\n");
spi_flash_munmap(handle3);
handle3 = 0;
}
#endif // !CONFIG_SPI_FLASH_ROM_IMPL
@@ -243,10 +164,10 @@ TEST_CASE("Can mmap unordered pages into contiguous memory", "[spi_flash][mmap]"
int startpage;
setup_mmap_tests();
nopages = (end - start) / SPI_FLASH_MMU_PAGE_SIZE;
nopages = (test_end - test_start) / SPI_FLASH_MMU_PAGE_SIZE;
pages = alloca(sizeof(int) * nopages);
startpage = start / SPI_FLASH_MMU_PAGE_SIZE;
startpage = test_start / SPI_FLASH_MMU_PAGE_SIZE;
//make inverse mapping: virt 0 -> page (nopages-1), virt 1 -> page (nopages-2), ...
for (int i = 0; i < nopages; i++) {
@@ -256,9 +177,8 @@ TEST_CASE("Can mmap unordered pages into contiguous memory", "[spi_flash][mmap]"
printf("Attempting mapping of unordered pages to contiguous memory area\n");
spi_flash_mmap_handle_t handle1;
const void *ptr1;
TEST_ESP_OK( spi_flash_mmap_pages(pages, nopages, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
TEST_ESP_OK( spi_flash_mmap_pages(pages, nopages, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr1, &handle1) );
printf("mmap_res: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle1, ptr1);
#if (CONFIG_MMU_PAGE_SIZE == 0x10000)
@@ -283,6 +203,7 @@ TEST_CASE("Can mmap unordered pages into contiguous memory", "[spi_flash][mmap]"
printf("Unmapping handle1\n");
spi_flash_munmap(handle1);
handle1 = 0;
}
TEST_CASE("flash_mmap invalidates just-written data", "[spi_flash][mmap]")
@@ -297,10 +218,10 @@ TEST_CASE("flash_mmap invalidates just-written data", "[spi_flash][mmap]")
TEST_IGNORE_MESSAGE("flash encryption enabled, spi_flash_write_encrypted() test won't pass as-is");
}
TEST_ESP_OK( esp_flash_erase_region(NULL, start, SPI_FLASH_SEC_SIZE) );
TEST_ESP_OK( esp_flash_erase_region(NULL, test_start, SPI_FLASH_SEC_SIZE) );
/* map erased test region to ptr1 */
TEST_ESP_OK( spi_flash_mmap(start, test_size, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
TEST_ESP_OK( spi_flash_mmap(test_start, test_size, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr1, &handle1) );
printf("mmap_res ptr1: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle1, ptr1);
/* verify it's all 0xFF */
@@ -315,14 +236,14 @@ TEST_CASE("flash_mmap invalidates just-written data", "[spi_flash][mmap]")
/* write flash region to 0xEE */
uint8_t buf[test_size];
memset(buf, 0xEE, test_size);
TEST_ESP_OK( esp_flash_write(NULL, buf, start, test_size) );
TEST_ESP_OK( esp_flash_write(NULL, buf, test_start, test_size) );
/* re-map the test region at ptr1.
this is a fresh mmap call so should trigger a cache flush,
ensuring we see the updated flash.
*/
TEST_ESP_OK( spi_flash_mmap(start, test_size, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
TEST_ESP_OK( spi_flash_mmap(test_start, test_size, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr1, &handle1) );
printf("mmap_res ptr1 #2: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle1, ptr1);
/* assert that ptr1 now maps to the new values on flash,
@@ -339,14 +260,14 @@ TEST_CASE("flash_mmap can mmap after get enough free MMU pages", "[spi_flash][mm
//this test case should make flash size >= 4MB, because max size of Dcache can mapped is 4MB
setup_mmap_tests();
printf("Mapping %"PRIx32" (+%"PRIx32")\n", start, end - start);
printf("Mapping %"PRIx32" (+%"PRIx32")\n", test_start, test_end - test_start);
const void *ptr1;
TEST_ESP_OK( spi_flash_mmap(start, end - start, SPI_FLASH_MMAP_DATA, &ptr1, &handle1) );
TEST_ESP_OK( spi_flash_mmap(test_start, test_end - test_start, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr1, &handle1) );
printf("mmap_res: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle1, ptr1);
srand(0);
const uint32_t *data = (const uint32_t *) ptr1;
for (int block = 0; block < (end - start) / 0x10000; ++block) {
for (int block = 0; block < (test_end - test_start) / 0x10000; ++block) {
printf("block %d\n", block);
for (int sector = 0; sector < 16; ++sector) {
printf("sector %d\n", sector);
@@ -363,7 +284,7 @@ TEST_CASE("flash_mmap can mmap after get enough free MMU pages", "[spi_flash][mm
printf("Mapping %x (+%"PRIx32")\n", 0, free_pages * SPI_FLASH_MMU_PAGE_SIZE);
const void *ptr2;
TEST_ESP_OK( spi_flash_mmap(0, free_pages * SPI_FLASH_MMU_PAGE_SIZE, SPI_FLASH_MMAP_DATA, &ptr2, &handle2) );
TEST_ESP_OK( spi_flash_mmap(0, free_pages * SPI_FLASH_MMU_PAGE_SIZE, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr2, &handle2) );
printf("mmap_res: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle2, ptr2);
printf("Unmapping handle1\n");
@@ -374,7 +295,7 @@ TEST_CASE("flash_mmap can mmap after get enough free MMU pages", "[spi_flash][mm
spi_flash_munmap(handle2);
handle2 = 0;
TEST_ASSERT_EQUAL_PTR(NULL, spi_flash_phys2cache(start, SPI_FLASH_MMAP_DATA));
TEST_ASSERT_EQUAL_PTR(NULL, spi_flash_phys2cache(test_start, SPI_FLASH_MMAP_DATA));
}
TEST_CASE("phys2cache/cache2phys basic checks", "[spi_flash][mmap]")
@@ -432,15 +353,15 @@ TEST_CASE("mmap consistent with phys2cache/cache2phys", "[spi_flash][mmap]")
TEST_ASSERT_EQUAL_HEX(SPI_FLASH_CACHE2PHYS_FAIL, spi_flash_cache2phys(ptr));
TEST_ESP_OK( spi_flash_mmap(start, test_size, SPI_FLASH_MMAP_DATA, &ptr, &handle1) );
TEST_ESP_OK( spi_flash_mmap(test_start, test_size, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr, &handle1) );
TEST_ASSERT_NOT_NULL(ptr);
TEST_ASSERT_NOT_EQUAL(0, handle1);
TEST_ASSERT_EQUAL_HEX(start, spi_flash_cache2phys(ptr));
TEST_ASSERT_EQUAL_HEX(start + 1024, spi_flash_cache2phys((void *)((intptr_t)ptr + 1024)));
TEST_ASSERT_EQUAL_HEX(start + 3000, spi_flash_cache2phys((void *)((intptr_t)ptr + 3000)));
TEST_ASSERT_EQUAL_HEX(test_start, spi_flash_cache2phys(ptr));
TEST_ASSERT_EQUAL_HEX(test_start + 1024, spi_flash_cache2phys((void *)((intptr_t)ptr + 1024)));
TEST_ASSERT_EQUAL_HEX(test_start + 3000, spi_flash_cache2phys((void *)((intptr_t)ptr + 3000)));
/* this pointer lands in a different MMU table entry */
TEST_ASSERT_EQUAL_HEX(start + test_size - 4, spi_flash_cache2phys((void *)((intptr_t)ptr + test_size - 4)));
TEST_ASSERT_EQUAL_HEX(test_start + test_size - 4, spi_flash_cache2phys((void *)((intptr_t)ptr + test_size - 4)));
spi_flash_munmap(handle1);
handle1 = 0;
@@ -468,14 +389,16 @@ TEST_CASE("munmap followed by mmap flushes cache", "[spi_flash][mmap]")
const uint32_t *data;
esp_partition_mmap_handle_t handle;
TEST_ESP_OK( esp_partition_mmap(p, 0, SPI_FLASH_MMU_PAGE_SIZE,
ESP_PARTITION_MMAP_DATA, (const void **) &data, &handle) );
ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, (const void **) &data, &handle) );
uint32_t buf[16];
memcpy(buf, data, sizeof(buf));
esp_partition_munmap(handle);
TEST_ESP_OK( esp_partition_mmap(p, SPI_FLASH_MMU_PAGE_SIZE, SPI_FLASH_MMU_PAGE_SIZE,
ESP_PARTITION_MMAP_DATA, (const void **) &data, &handle) );
ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, (const void **) &data, &handle) );
TEST_ASSERT_NOT_EQUAL(0, memcmp(buf, data, sizeof(buf)));
esp_partition_munmap(handle);
}
TEST_CASE("no stale data read post mmap and write partition", "[spi_flash][mmap]")
@@ -0,0 +1,381 @@
/*
* SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <inttypes.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "test_utils.h"
#include "test_mmap_utils.h"
#include "spi_flash_mmap.h"
#include "esp_flash.h"
#include "esp_cache.h"
//XIP_PSRAM or RAM_APP enabled
#if (CONFIG_SPIRAM_FETCH_INSTRUCTIONS && CONFIG_SPIRAM_RODATA) || CONFIG_APP_BUILD_TYPE_RAM
#define EXECUTE_IN_FLASH 0
#else
#define EXECUTE_IN_FLASH 1
#endif
TEST_CASE("flash_mmap allows esp_flash erase/write/read when mapped", "[spi_flash][mmap]")
{
const void *ptr1;
const size_t test_size = 128;
spi_flash_mmap_handle_t handle1;
setup_mmap_tests();
/* map erased test region to ptr1 */
TEST_ESP_OK( spi_flash_mmap(test_start, test_size, SPI_FLASH_MMAP_FLAG_DATA, &ptr1, &handle1) );
printf("mmap_res ptr1: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle1, ptr1);
//Make sure ptr1 is cached
volatile uint8_t val = 0xff;
for (int i = 0; i < test_size; i++) {
val = val ^ ((uint8_t *)ptr1)[i];
}
TEST_ESP_OK( esp_flash_erase_region(NULL, test_start, SPI_FLASH_SEC_SIZE) );
/* verify it's all 0xFF after the erase operation */
for (int i = 0; i < test_size; i++) {
TEST_ASSERT_EQUAL_HEX(0xFF, ((uint8_t *)ptr1)[i]);
}
/* unmap the erased region */
spi_flash_munmap(handle1);
handle1 = 0;
/* write flash region to 0xEE */
uint8_t buf[test_size];
uint8_t read_buf[test_size];
memset(buf, 0xEE, test_size);
TEST_ESP_OK( esp_flash_write(NULL, buf, test_start, test_size) );
/* re-map the test region at ptr1.
this is a fresh mmap call so should trigger a cache flush,
ensuring we see the updated flash.
*/
TEST_ESP_OK( spi_flash_mmap(test_start, test_size, SPI_FLASH_MMAP_FLAG_DATA, &ptr1, &handle1) );
printf("mmap_res ptr1 #2: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle1, ptr1);
/* assert that ptr1 now maps to the new values on flash,
ie contents of buf array.
*/
TEST_ASSERT_EQUAL_HEX8_ARRAY(buf, ptr1, test_size);
memset(buf, 0x66, test_size); //this data is select on purpose (can be modified from 0xEE set above)
TEST_ESP_OK( esp_flash_write(NULL, buf, test_start, test_size) );
TEST_ASSERT_EQUAL_HEX8_ARRAY(buf, ptr1, test_size);
memset(read_buf, 0x33, test_size);
TEST_ESP_OK( esp_flash_read(NULL, read_buf, test_start, test_size) );
TEST_ASSERT_EQUAL_HEX8_ARRAY(buf, read_buf, test_size);
spi_flash_munmap(handle1);
handle1 = 0;
}
typedef struct {
uint8_t expected_data[128];
const size_t test_size;
SemaphoreHandle_t mmap_start; //Given from main to mmap, to start mmap
SemaphoreHandle_t mmap_end; //Given from mmap to main indicating end of mmap and start another round
bool finish;
bool use_flag_blocks_write;
} test_mmap_concurrent_ctx_t;
//Delay time should be longer than the erasing time
#define MMAP_DELAY (1000 / portTICK_PERIOD_MS)
static void mmap_task(void* args)
{
test_mmap_concurrent_ctx_t *ctx = (test_mmap_concurrent_ctx_t*)args;
const uint32_t mmap_start = test_start;
const size_t mmap_len = CONFIG_MMU_PAGE_SIZE;
while (1) {
xSemaphoreTake(ctx->mmap_start, portMAX_DELAY);
if (ctx->finish) {
break;
}
uint32_t extra_flags = ctx->use_flag_blocks_write? SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE: 0;
const void *ptr1;
TEST_ESP_OK( spi_flash_mmap(mmap_start, mmap_len, SPI_FLASH_MMAP_FLAG_DATA | extra_flags, &ptr1, &handle1) );
printf("mmap_res ptr1: handle=%"PRIx32" ptr=%p\n", (uint32_t)handle1, ptr1);
TEST_ASSERT_EQUAL_UINT8_ARRAY(ctx->expected_data, ptr1, ctx->test_size);
//Delay enough time so that API on other task must happen if it can.
vTaskDelay(MMAP_DELAY);
TEST_ASSERT_EQUAL_UINT8_ARRAY(ctx->expected_data, ptr1, ctx->test_size);
/* unmap the region for test*/
spi_flash_munmap(handle1);
handle1 = 0;
xSemaphoreGive(ctx->mmap_end);
}
vTaskDelete(NULL);
}
/*
```mermaid
sequenceDiagram
activate main
main ->>+ mmap : Semphr start
mmap -> mmap : mmap
mmap -> mmap : read & verify data
mmap ->>+ mmap : Delay start
main ->>+ main : flash erase (block starts)
mmap ->>- mmap : Delay end
mmap-> mmap : read & verify data
mmap -> mmap : unmap
mmap ->> main : unblocks
deactivate main
main -> main : flash erase starts
main ->- main : check if unmapped
mmap ->>- main : Semphr ret
activate main
main ->>+ mmap : Semphr start
mmap -> mmap : ...
deactivate mmap
deactivate main
```
*/
static void check_mmap_executed_and_wait(test_mmap_concurrent_ctx_t* ctx, bool expect_unmap)
{
portBASE_TYPE unmapped = xSemaphoreTake(ctx->mmap_end, 0);
if (expect_unmap) {
TEST_ASSERT_EQUAL_INT(pdTRUE, unmapped);
} else {
TEST_ASSERT_EQUAL_INT(pdFALSE, unmapped);
//wait until we can start the next operation
xSemaphoreTake(ctx->mmap_end, portMAX_DELAY);
}
}
static void test_concurrent_mmap_core(test_mmap_concurrent_ctx_t *ctx, int test_size, bool use_flag_blocks_write)
{
const uint32_t api_addr = test_start + CONFIG_MMU_PAGE_SIZE;
uint8_t read_buf[test_size];
uint8_t buf[test_size];
bool expect_blocked = false;
ctx->use_flag_blocks_write = use_flag_blocks_write;
// For erase operation, it should take the mmap mutex, and will start after unmap.
printf("test erase in mmap...\n");
xSemaphoreGive(ctx->mmap_start);
esp_rom_delay_us(1000); //delay 1ms to make sure mmap is done
TEST_ESP_OK(esp_flash_erase_region(NULL, api_addr, SPI_FLASH_SEC_SIZE));
expect_blocked = use_flag_blocks_write;
#if CONFIG_IDF_TARGET_ESP32
expect_blocked = false;
#endif
check_mmap_executed_and_wait(ctx, expect_blocked);
// Read operations do NOT set ESP_FLASH_START_FLAG_NO_READ, so they are never blocked by mmap.
printf("test read in mmap...\n");
xSemaphoreGive(ctx->mmap_start);
esp_rom_delay_us(1000); //delay 1ms to make sure mmap is done
TEST_ESP_OK(esp_flash_read(NULL, read_buf, api_addr, test_size));
check_mmap_executed_and_wait(ctx, false);
// For write operation, it's same as erase operation.
printf("test write in mmap...\n");
xSemaphoreGive(ctx->mmap_start);
/* write flash region to 0xEE */
memset(buf, 0xEE, test_size);
esp_rom_delay_us(1000); //delay 1ms to make sure mmap is done
TEST_ESP_OK(esp_flash_write(NULL, buf, api_addr, test_size));
expect_blocked = use_flag_blocks_write;
#if CONFIG_IDF_TARGET_ESP32
expect_blocked = false;
#endif
check_mmap_executed_and_wait(ctx, expect_blocked);
printf("test read in mmap...\n");
xSemaphoreGive(ctx->mmap_start);
esp_rom_delay_us(1000); //delay 1ms to make sure mmap is done
TEST_ESP_OK(esp_flash_read(NULL, read_buf, api_addr, test_size));
check_mmap_executed_and_wait(ctx, false);
}
TEST_CASE("flash_mmap concurrent access to flash erase/prog", "[spi_flash][mmap]")
{
setup_mmap_tests();
const int test_size = 128;
test_mmap_concurrent_ctx_t ctx = {
.finish = false,
.test_size = test_size,
.use_flag_blocks_write = true
};
ctx.mmap_start = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_NULL(ctx.mmap_start);
ctx.mmap_end = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_NULL(ctx.mmap_end);
TEST_ESP_OK(esp_flash_read(NULL, ctx.expected_data, test_start, test_size));
TaskHandle_t task_handle;
//Create task with higher priority so that once semphr given, task is unblocked immediately.
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(mmap_task, "mmap_task", 4096, &ctx, 5, &task_handle));
for (int i = 0; i < 3; i++) {
test_concurrent_mmap_core(&ctx, test_size, true);
test_concurrent_mmap_core(&ctx, test_size, false);
}
ctx.finish = true;
xSemaphoreGive(ctx.mmap_start);
vTaskDelay(10 / portTICK_PERIOD_MS);//wait for mmap task to delete
//recycle the context
vSemaphoreDelete(ctx.mmap_start);
vSemaphoreDelete(ctx.mmap_end);
}
/* Number of erase rounds for the non-overlapping concurrent erase test */
#define CONCURRENT_ERASE_ROUNDS 10
typedef struct {
uint8_t expected_data[128];
uint32_t region_a_start;
uint32_t region_b_start;
size_t test_size;
bool finish;
bool use_flag_blocks_write;
bool data_ok;
SemaphoreHandle_t erase_done;
SemaphoreHandle_t erase_op_start;
} test_mmap_erase_concurrent_ctx_t;
static void mmap_verify_task(void *args)
{
test_mmap_erase_concurrent_ctx_t *ctx = (test_mmap_erase_concurrent_ctx_t *)args;
uint32_t extra_flags = ctx->use_flag_blocks_write? SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE: 0;
xSemaphoreTake(ctx->erase_op_start, portMAX_DELAY);
while (!ctx->finish) {
//For single core case, delay 1ms to allow erase task to start and finish
vTaskDelay(1);
spi_flash_mmap_handle_t verify_handle;
const void *ptr;
esp_err_t err = spi_flash_mmap(ctx->region_a_start, ctx->test_size, SPI_FLASH_MMAP_FLAG_DATA | extra_flags, &ptr, &verify_handle);
if (err != ESP_OK) {
ctx->data_ok = false;
break;
}
if (memcmp(ctx->expected_data, ptr, ctx->test_size) != 0) {
ctx->data_ok = false;
spi_flash_munmap(verify_handle);
break;
}
spi_flash_munmap(verify_handle);
printf("mmap_verify_task: data ok, calling munmap\n");
}
vTaskDelete(NULL);
}
static void erase_task(void *args)
{
test_mmap_erase_concurrent_ctx_t *ctx = (test_mmap_erase_concurrent_ctx_t *)args;
xSemaphoreGive(ctx->erase_op_start);
for (int i = 0; i < CONCURRENT_ERASE_ROUNDS; i++) {
printf("erase region B: round %d/%d\n", i + 1, CONCURRENT_ERASE_ROUNDS);
TEST_ESP_OK(esp_flash_erase_region(NULL, ctx->region_b_start, SPI_FLASH_SEC_SIZE));
printf("erase region B: round %d done\n", i + 1);
}
ctx->finish = true;
xSemaphoreGive(ctx->erase_done);
vTaskDelete(NULL);
}
static void test_concurrent_erase_core(test_mmap_erase_concurrent_ctx_t *ctx, bool use_flag_blocks_write)
{
ctx->finish = false;
ctx->use_flag_blocks_write = use_flag_blocks_write;
ctx->erase_op_start = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_NULL(ctx->erase_op_start);
ctx->erase_done = xSemaphoreCreateBinary();
TEST_ASSERT_NOT_NULL(ctx->erase_done);
printf("test_concurrent_erase_core: use_flag_blocks_write=%d\n", use_flag_blocks_write);
//Let mmap happen during erasing
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(mmap_verify_task, "mmap_verify", 4096, ctx, 6, NULL));
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(erase_task, "erase_task", 4096, ctx, 5, NULL));
//Wait for erase task to complete all rounds, then stop mmap task
xSemaphoreTake(ctx->erase_done, portMAX_DELAY);
vTaskDelay(50 / portTICK_PERIOD_MS);
vSemaphoreDelete(ctx->erase_done);
vSemaphoreDelete(ctx->erase_op_start);
}
TEST_CASE("flash_mmap region unaffected by concurrent flash erase of separate region", "[spi_flash][mmap]")
{
setup_mmap_tests();
const size_t test_size = 128;
/* Region A: beginning of test partition (mmap + verify target) */
uint32_t region_a = test_start;
/* Region B: second sector of test partition (erase target), does not overlap with region A */
uint32_t region_b = test_start + SPI_FLASH_SEC_SIZE;
//Prepare random data to write to region A
uint8_t write_buf[128];
srand(789);
for (int i = 0; i < test_size; i ++) {
write_buf[i] = rand() % 0xff;
}
TEST_ESP_OK(esp_flash_erase_region(NULL, region_a, SPI_FLASH_SEC_SIZE));
TEST_ESP_OK(esp_flash_write(NULL, write_buf, region_a, test_size));
test_mmap_erase_concurrent_ctx_t ctx = {
.region_a_start = region_a,
.region_b_start = region_b,
.test_size = test_size,
.data_ok = true,
};
memcpy(ctx.expected_data, write_buf, test_size);
test_concurrent_erase_core(&ctx, false);
test_concurrent_erase_core(&ctx, true);
TEST_ASSERT_TRUE_MESSAGE(ctx.data_ok, "Region A data was corrupted during concurrent erase of region B");
}
@@ -0,0 +1,94 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#include <inttypes.h>
#include <esp_partition.h>
#include <esp_efuse.h>
#include "test_utils.h"
#include "test_mmap_utils.h"
#include "esp_flash.h"
/* read-only region used for mmap tests, initialised in setup_mmap_tests() */
uint32_t test_start;
uint32_t test_end;
spi_flash_mmap_handle_t handle1, handle2, handle3;
static uint32_t buffer[1024];
esp_err_t spi_flash_read_maybe_encrypted(size_t src_addr, void *des_addr, size_t size)
{
if (!esp_efuse_is_flash_encryption_enabled()) {
return esp_flash_read(NULL, des_addr, src_addr, size);
} else {
return esp_flash_read_encrypted(NULL, src_addr, des_addr, size);
}
}
esp_err_t spi_flash_write_maybe_encrypted(size_t des_addr, const void *src_addr, size_t size)
{
if (!esp_efuse_is_flash_encryption_enabled()) {
return esp_flash_write(NULL, src_addr, des_addr, size);
} else {
return esp_flash_write_encrypted(NULL, des_addr, src_addr, size);
}
}
void setup_mmap_tests(void)
{
if (test_start == 0) {
const esp_partition_t *part = get_test_data_partition();
test_start = part->address;
test_end = part->address + part->size;
printf("Test data partition @ 0x%"PRIx32" - 0x%"PRIx32"\n", test_start, test_end);
}
TEST_ASSERT(test_end > test_start);
TEST_ASSERT(test_end - test_start >= 512 * 1024);
/* clean up any mmap handles left over from failed tests */
if (handle1) {
spi_flash_munmap(handle1);
handle1 = 0;
}
if (handle2) {
spi_flash_munmap(handle2);
handle2 = 0;
}
if (handle3) {
spi_flash_munmap(handle3);
handle3 = 0;
}
/* prepare flash contents */
srand(0);
for (int block = test_start / 0x10000; block < test_end / 0x10000; ++block) {
for (int sector = 0; sector < 16; ++sector) {
uint32_t abs_sector = (block * 16) + sector;
uint32_t sector_offs = abs_sector * SPI_FLASH_SEC_SIZE;
bool sector_needs_write = false;
TEST_ESP_OK( spi_flash_read_maybe_encrypted(sector_offs, buffer, sizeof(buffer)) );
for (uint32_t word = 0; word < 1024; ++word) {
uint32_t val = rand();
if (block == test_start / 0x10000 && sector == 0 && word == 0) {
printf("setup_mmap_tests(): first prepped word: 0x%08"PRIx32" (flash holds 0x%08"PRIx32")\n", val, buffer[word]);
}
if (buffer[word] != val) {
buffer[word] = val;
sector_needs_write = true;
}
}
/* Only rewrite the sector if it has changed */
if (sector_needs_write) {
TEST_ESP_OK( esp_flash_erase_region(NULL, (uint16_t) abs_sector * SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE) );
TEST_ESP_OK( spi_flash_write_maybe_encrypted(sector_offs, (const uint8_t *) buffer, sizeof(buffer)) );
}
}
}
}
@@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
#pragma once
#include "spi_flash_mmap.h"
extern void setup_mmap_tests(void);
extern uint32_t test_start;
extern uint32_t test_end;
//Use and maintain these handles. When next test starts, setup_mmap_tests will unmap these handle if they are not zero.
extern spi_flash_mmap_handle_t handle1, handle2, handle3;
esp_err_t spi_flash_read_maybe_encrypted(size_t src_addr, void *des_addr, size_t size);
esp_err_t spi_flash_write_maybe_encrypted(size_t des_addr, const void *src_addr, size_t size);
@@ -68,3 +68,16 @@ def test_flash_mmap_psram(dut: Dut) -> None:
@idf_parametrize('target', ['supported_targets'], indirect=['target'])
def test_flash_mmap_xip_psram_rom_impl(dut: Dut) -> None:
dut.run_all_single_board_cases(timeout=30)
@pytest.mark.flash_suspend
@pytest.mark.parametrize(
'config',
[
'suspend_with_rom_impl',
],
indirect=True,
)
@idf_parametrize('target', ['esp32c3'], indirect=['target'])
def test_flash_mmap_suspend_with_rom_impl(dut: Dut) -> None:
dut.run_all_single_board_cases(timeout=30)
@@ -0,0 +1,3 @@
CONFIG_SPI_FLASH_ROM_IMPL=y
CONFIG_SPI_FLASH_AUTO_SUSPEND=y
CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND=y