From 3e8389cc31dec485718aa09e8f98ea2aa1b3ae9a Mon Sep 17 00:00:00 2001 From: Xiao Xufeng Date: Fri, 22 Mar 2024 16:07:11 +0800 Subject: [PATCH 1/3] 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 --- components/app_update/esp_ota_ops.c | 4 +- .../include/bootloader_flash_priv.h | 4 +- .../bootloader_flash/src/bootloader_flash.c | 2 +- .../include/bootloader_common.h | 2 + .../src/bootloader_common.c | 1 + .../src/bootloader_utility.c | 3 - components/bt/controller/esp32c2/bt.c | 2 +- components/bt/controller/esp32c3/bt.c | 2 +- components/bt/controller/esp32c5/bt.c | 2 +- components/bt/controller/esp32c6/bt.c | 2 +- components/bt/controller/esp32h2/bt.c | 2 +- components/esp_common/include/esp_attr.h | 3 +- .../mspi_timing_tuning/mspi_timing_tuning.c | 1 + .../main/partition_api_test.c | 12 +- .../esp_partition/include/esp_partition.h | 28 +- components/esp_partition/partition.c | 2 +- components/esp_partition/partition_linux.c | 2 +- components/esp_partition/partition_target.c | 11 +- .../esp_partition/test/test_partition.c | 2 +- .../esp_partition/test/test_partitions.c | 2 +- components/esp_rom/esp32c2/ld/esp32c2.rom.ld | 6 +- components/esp_rom/esp32c3/ld/esp32c3.rom.ld | 7 +- .../esp32c5/ld/esp32c5.rom.spiflash.ld | 10 +- .../esp32c6/ld/esp32c6.rom.spiflash.ld | 10 +- .../esp32c61/ld/esp32c61.rom.spiflash.ld | 10 +- .../esp32h2/ld/esp32h2.rom.spiflash.ld | 10 +- .../esp32h21/ld/esp32h21.rom.spiflash.ld | 10 +- .../esp32h4/ld/esp32h4.rom.spiflash.ld | 4 +- components/esp_rom/esp32s3/ld/esp32s3.rom.ld | 7 +- .../esp32s31/ld/esp32s31.rom.spiflash.ld | 4 +- .../main/test_esp_tee_flash_prot.c | 16 +- components/espcoredump/src/core_dump_elf.c | 2 +- .../test_apps/mbedtls_ut/main/test_sha.c | 2 +- components/spi_flash/Kconfig | 2 +- components/spi_flash/esp_flash_api.c | 118 +++--- components/spi_flash/esp_flash_spi_init.c | 6 +- components/spi_flash/flash_mmap.c | 340 +++++++++++++++- .../include/esp_flash_chips/esp_flash_types.h | 4 + .../include/esp_private/esp_flash_internal.h | 10 +- .../include/esp_private/flash_mmap.h | 47 +++ .../include/esp_private/spi_flash_os.h | 6 - components/spi_flash/include/spi_flash_mmap.h | 59 ++- components/spi_flash/linker.lf | 8 +- components/spi_flash/spi_flash_os_func_app.c | 223 +++++++--- .../test_apps/flash_mmap/main/CMakeLists.txt | 7 +- .../flash_mmap/main/test_flash_mmap.c | 193 +++------ .../main/test_mmap_api_concurrent.c | 381 ++++++++++++++++++ .../flash_mmap/main/test_mmap_utils.c | 94 +++++ .../flash_mmap/main/test_mmap_utils.h | 18 + .../test_apps/flash_mmap/pytest_flash_mmap.py | 13 + .../sdkconfig.ci.suspend_with_rom_impl | 3 + .../mspi_test/sdkconfig.ci.xip_psram | 0 .../peripherals/spi_flash/index.rst | 25 ++ .../spi_flash/spi_flash_concurrency.rst | 93 +++-- .../spi_flash/spi_flash_optional_feature.rst | 2 +- .../peripherals/spi_flash/xip_from_psram.inc | 17 +- .../peripherals/spi_flash/index.rst | 25 ++ .../spi_flash/spi_flash_concurrency.rst | 94 +++-- .../spi_flash/spi_flash_optional_feature.rst | 2 +- .../peripherals/spi_flash/xip_from_psram.inc | 15 +- .../partition_api/partition_mmap/main/main.c | 2 +- examples/system/.build-test-rules.yml | 1 + .../simple_ota_example/pytest_simple_ota.py | 1 + .../simple_ota_example/sdkconfig.ci.xip_psram | 14 + tools/test_apps/system/.build-test-rules.yml | 9 +- .../panic/coredump/pytest_panic_coredump.py | 83 +++- ...config.ci.coredump_flash_extram_stack_bss} | 1 - ...ci.coredump_flash_extram_stack_bss_esp32s3 | 11 - ...ig.ci.coredump_flash_extram_stack_bss_xip} | 2 +- ...onfig.ci.coredump_flash_extram_stack_heap} | 1 - ...i.coredump_flash_extram_stack_heap_esp32s2 | 7 - ...i.coredump_flash_extram_stack_heap_esp32s3 | 7 - .../main/ram_loadable_app_test.c | 34 +- 73 files changed, 1678 insertions(+), 487 deletions(-) create mode 100644 components/spi_flash/include/esp_private/flash_mmap.h create mode 100644 components/spi_flash/test_apps/flash_mmap/main/test_mmap_api_concurrent.c create mode 100644 components/spi_flash/test_apps/flash_mmap/main/test_mmap_utils.c create mode 100644 components/spi_flash/test_apps/flash_mmap/main/test_mmap_utils.h create mode 100644 components/spi_flash/test_apps/flash_mmap/sdkconfig.ci.suspend_with_rom_impl delete mode 100644 components/spi_flash/test_apps/mspi_test/sdkconfig.ci.xip_psram create mode 100644 examples/system/ota/simple_ota_example/sdkconfig.ci.xip_psram rename tools/test_apps/system/panic/coredump/{sdkconfig.ci.coredump_flash_extram_stack_bss_esp32 => sdkconfig.ci.coredump_flash_extram_stack_bss} (93%) delete mode 100644 tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_esp32s3 rename tools/test_apps/system/panic/coredump/{sdkconfig.ci.coredump_flash_extram_stack_bss_esp32s2 => sdkconfig.ci.coredump_flash_extram_stack_bss_xip} (92%) rename tools/test_apps/system/panic/coredump/{sdkconfig.ci.coredump_flash_extram_stack_heap_esp32 => sdkconfig.ci.coredump_flash_extram_stack_heap} (88%) delete mode 100644 tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap_esp32s2 delete mode 100644 tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap_esp32s3 diff --git a/components/app_update/esp_ota_ops.c b/components/app_update/esp_ota_ops.c index f5f81e35993..dcf81bc38e0 100644 --- a/components/app_update/esp_ota_ops.c +++ b/components/app_update/esp_ota_ops.c @@ -97,7 +97,7 @@ static const esp_partition_t *read_otadata(esp_ota_select_entry_t *two_otadata) esp_partition_mmap_handle_t ota_data_map; const void *result = NULL; - esp_err_t err = esp_partition_mmap(otadata_partition, 0, otadata_partition->size, ESP_PARTITION_MMAP_DATA, &result, &ota_data_map); + esp_err_t err = esp_partition_mmap(otadata_partition, 0, otadata_partition->size, ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, &result, &ota_data_map); if (err != ESP_OK) { ESP_LOGE(TAG, "mmap otadata filed. Err=0x%8x", err); return NULL; @@ -563,7 +563,7 @@ static esp_err_t ota_verify_partition(ota_ops_entry_t *ota_ops) } else if (ota_ops->partition.final->type == ESP_PARTITION_TYPE_PARTITION_TABLE) { const esp_partition_info_t *partition_table = NULL; esp_partition_mmap_handle_t partition_table_map; - ret = esp_partition_mmap(ota_ops->partition.staging, 0, ESP_PARTITION_TABLE_MAX_LEN, ESP_PARTITION_MMAP_DATA, (const void**)&partition_table, &partition_table_map); + ret = esp_partition_mmap(ota_ops->partition.staging, 0, ESP_PARTITION_TABLE_MAX_LEN, ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, (const void**)&partition_table, &partition_table_map); if (ret == ESP_OK) { int num_partitions; if (esp_partition_table_verify(partition_table, true, &num_partitions) != ESP_OK) { diff --git a/components/bootloader_support/bootloader_flash/include/bootloader_flash_priv.h b/components/bootloader_support/bootloader_flash/include/bootloader_flash_priv.h index 304ef071603..936b2ab144c 100644 --- a/components/bootloader_support/bootloader_flash/include/bootloader_flash_priv.h +++ b/components/bootloader_support/bootloader_flash/include/bootloader_flash_priv.h @@ -87,7 +87,9 @@ uint32_t bootloader_mmap_get_free_pages(void); * * Call bootloader_munmap once for each successful call to bootloader_mmap. * - * In esp-idf app, this function maps directly to spi_flash_mmap. + * In esp-idf app, this function maps directly to spi_flash_mmap with the @ref + * spi_flash_mmap_flag_t::SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE flag set. When XIP on PSRAM (`CONFIG_SPIRAM_XIP_FROM_PSRAM`) enabled, flash erasing/writing + * will be blocked until unmap. * * @param offset - Starting flash offset to map to memory. * @param length - Length of data to map. diff --git a/components/bootloader_support/bootloader_flash/src/bootloader_flash.c b/components/bootloader_support/bootloader_flash/src/bootloader_flash.c index b9af5588f04..f1abd50036d 100644 --- a/components/bootloader_support/bootloader_flash/src/bootloader_flash.c +++ b/components/bootloader_support/bootloader_flash/src/bootloader_flash.c @@ -66,7 +66,7 @@ const void *bootloader_mmap(uint32_t src_addr, uint32_t size) const void *result = NULL; uint32_t src_page = src_addr & ~(SPI_FLASH_MMU_PAGE_SIZE - 1); size += (src_addr - src_page); - esp_err_t err = spi_flash_mmap(src_page, size, SPI_FLASH_MMAP_DATA, &result, &map); + esp_err_t err = spi_flash_mmap(src_page, size, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &result, &map); if (err != ESP_OK) { ESP_EARLY_LOGE(TAG, "spi_flash_mmap failed: 0x%x", err); return NULL; diff --git a/components/bootloader_support/include/bootloader_common.h b/components/bootloader_support/include/bootloader_common.h index ce678d162b6..40365f123b1 100644 --- a/components/bootloader_support/include/bootloader_common.h +++ b/components/bootloader_support/include/bootloader_common.h @@ -109,6 +109,8 @@ esp_comm_gpio_hold_t bootloader_common_check_long_hold_gpio_level(uint32_t num_p /** * @brief Erase the partition data that is specified in the transferred list. * + * @note This function can't be called in app. + * * @param[in] list_erase String containing a list of cleared partitions. Like this "nvs, phy". The string must be null-terminal. * @param[in] ota_data_erase If true then the OTA data partition will be cleared (if there is it in partition table). * @return Returns true on success, false otherwise. diff --git a/components/bootloader_support/src/bootloader_common.c b/components/bootloader_support/src/bootloader_common.c index edf3d5005e2..2327cb78a26 100644 --- a/components/bootloader_support/src/bootloader_common.c +++ b/components/bootloader_support/src/bootloader_common.c @@ -86,6 +86,7 @@ bool bootloader_common_label_search(const char *list, char *label) return false; } +//This function erases while mmap is not unmapped yet. Can't be called in the app while XIP on PSRAM. bool bootloader_common_erase_part_type_data(const char *list_erase, bool ota_data_erase) { const esp_partition_info_t *partitions; diff --git a/components/bootloader_support/src/bootloader_utility.c b/components/bootloader_support/src/bootloader_utility.c index 0916ae9a3d5..2a4151d529e 100644 --- a/components/bootloader_support/src/bootloader_utility.c +++ b/components/bootloader_support/src/bootloader_utility.c @@ -36,9 +36,6 @@ #include "esp_app_desc.h" #include "esp_secure_boot.h" #include "esp_flash_encrypt.h" -#ifndef BOOTLOADER_BUILD -#include "spi_flash_mmap.h" -#endif #include "esp_flash_partitions.h" #include "bootloader_flash_priv.h" #include "bootloader_random.h" diff --git a/components/bt/controller/esp32c2/bt.c b/components/bt/controller/esp32c2/bt.c index 4f7a44083d9..d7fb87eb60b 100644 --- a/components/bt/controller/esp32c2/bt.c +++ b/components/bt/controller/esp32c2/bt.c @@ -428,7 +428,7 @@ void esp_bt_read_ctrl_log_from_flash(bool output) print_len = 0; max_print_len = 4096; - err = esp_partition_mmap(log_partition, 0, MAX_STORAGE_SIZE, ESP_PARTITION_MMAP_DATA, &mapped_ptr, &mmap_handle); + err = esp_partition_mmap(log_partition, 0, MAX_STORAGE_SIZE, ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, &mapped_ptr, &mmap_handle); if (err != ESP_OK) { ESP_LOGE("FLASH", "Mmap failed: %s", esp_err_to_name(err)); return; diff --git a/components/bt/controller/esp32c3/bt.c b/components/bt/controller/esp32c3/bt.c index 72acdcfbcf5..72c02864934 100644 --- a/components/bt/controller/esp32c3/bt.c +++ b/components/bt/controller/esp32c3/bt.c @@ -759,7 +759,7 @@ void esp_bt_read_ctrl_log_from_flash(bool output) print_len = 0; max_print_len = 4096; - err = esp_partition_mmap(log_partition, 0, MAX_STORAGE_SIZE, ESP_PARTITION_MMAP_DATA, &mapped_ptr, &mmap_handle); + err = esp_partition_mmap(log_partition, 0, MAX_STORAGE_SIZE, ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, &mapped_ptr, &mmap_handle); if (err != ESP_OK) { ESP_LOGE("FLASH", "Mmap failed: %s", esp_err_to_name(err)); return; diff --git a/components/bt/controller/esp32c5/bt.c b/components/bt/controller/esp32c5/bt.c index 5983adb06d9..ade948be125 100644 --- a/components/bt/controller/esp32c5/bt.c +++ b/components/bt/controller/esp32c5/bt.c @@ -422,7 +422,7 @@ void esp_bt_read_ctrl_log_from_flash(bool output) print_len = 0; max_print_len = 4096; - err = esp_partition_mmap(log_partition, 0, MAX_STORAGE_SIZE, ESP_PARTITION_MMAP_DATA, &mapped_ptr, &mmap_handle); + err = esp_partition_mmap(log_partition, 0, MAX_STORAGE_SIZE, ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, &mapped_ptr, &mmap_handle); if (err != ESP_OK) { ESP_LOGE("FLASH", "Mmap failed: %s", esp_err_to_name(err)); return; diff --git a/components/bt/controller/esp32c6/bt.c b/components/bt/controller/esp32c6/bt.c index 9263d5d2944..56f38faf548 100644 --- a/components/bt/controller/esp32c6/bt.c +++ b/components/bt/controller/esp32c6/bt.c @@ -437,7 +437,7 @@ void esp_bt_read_ctrl_log_from_flash(bool output) print_len = 0; max_print_len = 4096; - err = esp_partition_mmap(log_partition, 0, MAX_STORAGE_SIZE, ESP_PARTITION_MMAP_DATA, &mapped_ptr, &mmap_handle); + err = esp_partition_mmap(log_partition, 0, MAX_STORAGE_SIZE, ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, &mapped_ptr, &mmap_handle); if (err != ESP_OK) { ESP_LOGE("FLASH", "Mmap failed: %s", esp_err_to_name(err)); return; diff --git a/components/bt/controller/esp32h2/bt.c b/components/bt/controller/esp32h2/bt.c index f5946d50aa2..c0d8266e8b5 100644 --- a/components/bt/controller/esp32h2/bt.c +++ b/components/bt/controller/esp32h2/bt.c @@ -435,7 +435,7 @@ void esp_bt_read_ctrl_log_from_flash(bool output) print_len = 0; max_print_len = 4096; - err = esp_partition_mmap(log_partition, 0, MAX_STORAGE_SIZE, ESP_PARTITION_MMAP_DATA, &mapped_ptr, &mmap_handle); + err = esp_partition_mmap(log_partition, 0, MAX_STORAGE_SIZE, ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, &mapped_ptr, &mmap_handle); if (err != ESP_OK) { ESP_LOGE("FLASH", "Mmap failed: %s", esp_err_to_name(err)); return; diff --git a/components/esp_common/include/esp_attr.h b/components/esp_common/include/esp_attr.h index 450e52290ae..c8c6c4d5f0c 100644 --- a/components/esp_common/include/esp_attr.h +++ b/components/esp_common/include/esp_attr.h @@ -179,7 +179,8 @@ extern "C" { #endif // This allows using enum as flags in C++ -// Format: FLAG_ATTR(flag_enum_t) +// Format: FLAG_ATTR(flag_enum_t). +// Please use out of the extern "C"{} block of the header, otherwise there will be definition conflicts. #ifdef __cplusplus // Inline is required here to avoid multiple definition error in linker diff --git a/components/esp_hw_support/mspi/mspi_timing_tuning/mspi_timing_tuning.c b/components/esp_hw_support/mspi/mspi_timing_tuning/mspi_timing_tuning.c index 74de95e3088..d709392f6f4 100644 --- a/components/esp_hw_support/mspi/mspi_timing_tuning/mspi_timing_tuning.c +++ b/components/esp_hw_support/mspi/mspi_timing_tuning/mspi_timing_tuning.c @@ -17,6 +17,7 @@ #include "hal/mspi_ll.h" #if !ESP_TEE_BUILD #include "esp_private/esp_cache_private.h" +#include "hal/cache_ll.h" #else #include "hal/cache_ll.h" #include "hal/cache_hal.h" diff --git a/components/esp_partition/host_test/partition_api_test/main/partition_api_test.c b/components/esp_partition/host_test/partition_api_test/main/partition_api_test.c index 658834fcbb4..64dc42baa1d 100644 --- a/components/esp_partition/host_test/partition_api_test/main/partition_api_test.c +++ b/components/esp_partition/host_test/partition_api_test/main/partition_api_test.c @@ -236,7 +236,6 @@ TEST(partition_api, test_partition_mmap) const esp_partition_t *partition_data = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage"); TEST_ASSERT_NOT_NULL(partition_data); - esp_partition_mmap_memory_t memory = ESP_PARTITION_MMAP_DATA; void *out_ptr = NULL; esp_partition_mmap_handle_t out_handle = 0; @@ -244,7 +243,12 @@ TEST(partition_api, test_partition_mmap) size_t offset = 0; size_t size = partition_data->size; - esp_err_t err = esp_partition_mmap(partition_data, offset, size, memory, (const void **) &out_ptr, &out_handle); + esp_err_t err = esp_partition_mmap(partition_data, offset, size, ESP_PARTITION_MMAP_DATA, (const void **) &out_ptr, &out_handle); + TEST_ESP_OK(err); + TEST_ASSERT_NOT_NULL(out_ptr); + esp_partition_munmap(out_handle); + + err = esp_partition_mmap(partition_data, offset, size, ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, (const void **) &out_ptr, &out_handle); TEST_ESP_OK(err); TEST_ASSERT_NOT_NULL(out_ptr); esp_partition_munmap(out_handle); @@ -253,14 +257,14 @@ TEST(partition_api, test_partition_mmap) offset = partition_data->size + 1; size = 1; - err = esp_partition_mmap(partition_data, offset, size, memory, (const void **) &out_ptr, &out_handle); + err = esp_partition_mmap(partition_data, offset, size, ESP_PARTITION_MMAP_DATA, (const void **) &out_ptr, &out_handle); TEST_ASSERT_EQUAL(err, ESP_ERR_INVALID_ARG); // mapped length beyond partition size offset = 1; size = partition_data->size; - err = esp_partition_mmap(partition_data, offset, size, memory, (const void **) &out_ptr, &out_handle); + err = esp_partition_mmap(partition_data, offset, size, ESP_PARTITION_MMAP_DATA, (const void **) &out_ptr, &out_handle); TEST_ASSERT_EQUAL(err, ESP_ERR_INVALID_SIZE); } diff --git a/components/esp_partition/include/esp_partition.h b/components/esp_partition/include/esp_partition.h index 55733809890..ec747c9ab30 100644 --- a/components/esp_partition/include/esp_partition.h +++ b/components/esp_partition/include/esp_partition.h @@ -10,8 +10,10 @@ #include #include #include +#include "esp_bit_defs.h" #include "esp_err.h" #include "esp_blockdev.h" +#include "esp_attr.h" #ifdef __cplusplus extern "C" { @@ -26,13 +28,19 @@ extern "C" { typedef struct esp_flash_t esp_flash_t; /** @endcond */ + /** - * @brief Enumeration which specifies memory space requested in an mmap call + * @brief Flags for @ref esp_partition_mmap() calls */ typedef enum { - ESP_PARTITION_MMAP_DATA, /**< map to data memory (Vaddr0), allows byte-aligned access, (4 MB total - only for esp32) */ - ESP_PARTITION_MMAP_INST, /**< map to instruction memory (Vaddr1-3), allows only 4-byte-aligned access, (11 MB total - only for esp32) */ -} esp_partition_mmap_memory_t; + ESP_PARTITION_MMAP_DATA = 0, /**< map to data memory (Vaddr0), allows byte-aligned access, (4 MB total - only for esp32) */ + ESP_PARTITION_MMAP_INST = BIT(0), /**< map to instruction memory (Vaddr1-3), allows only 4-byte-aligned access, (11 MB total - only for esp32) */ + //Enum above are also used in the ROM, don't change the value. + ESP_PARTITION_MMAP_BLOCKS_WRITE = BIT(1), /**< Blocks flash erasing/writing until unmap to avoid the cache disabling. See SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE. */ +} esp_partition_mmap_flag_t; + +/** Deprecated type. */ +#define esp_partition_mmap_memory_t _Pragma("GCC warning \"'esp_partition_mmap_memory_t' enum is deprecated.\"") esp_partition_mmap_flag_t /** * @brief Opaque handle for memory region obtained from esp_partition_mmap. @@ -477,19 +485,25 @@ esp_err_t esp_partition_erase_range(const esp_partition_t* partition, * To release mapped memory, pass handle returned via out_handle argument to * esp_partition_munmap function. * + * If there is any mapped region while SPI Flash is being erased or programmed, the cache will be disabled and the + * system will be suspended by default. (for more info see SPI flash docs about cache disabling. It will decrease the + * performance of the system when it's possible for the cache to be kept enabled (for example, when XIP on PSRAM). + * Please specify @ref ESP_PARTITION_MMAP_BLOCKS_WRITE flag to block flash erasing/writing APIs until this region is + * unmapped. See SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE for more details. + * * @param partition Pointer to partition structure obtained using * esp_partition_find_first or esp_partition_get. * Must be non-NULL. * @param offset Offset from the beginning of partition where mapping should start. * @param size Size of the area to be mapped. - * @param memory Memory space where the region should be mapped + * @param flags Flags of the mapping, including address space where the region should be mapped (data or instruction) * @param out_ptr Output, pointer to the mapped memory region * @param out_handle Output, handle which should be used for esp_partition_munmap call * * @return ESP_OK, if successful */ esp_err_t esp_partition_mmap(const esp_partition_t* partition, size_t offset, size_t size, - esp_partition_mmap_memory_t memory, + esp_partition_mmap_flag_t flags, const void** out_ptr, esp_partition_mmap_handle_t* out_handle); /** @@ -663,4 +677,6 @@ esp_err_t esp_partition_ptr_get_blockdev(const esp_partition_t *partition, esp_b } #endif +FLAG_ATTR(esp_partition_mmap_flag_t) + #endif /* __ESP_PARTITION_H__ */ diff --git a/components/esp_partition/partition.c b/components/esp_partition/partition.c index d7ce987050e..b3dc33bf392 100644 --- a/components/esp_partition/partition.c +++ b/components/esp_partition/partition.c @@ -132,7 +132,7 @@ static esp_err_t load_partitions(void) size_t mapped_size = ESP_PARTITION_EMULATED_SECTOR_SIZE; #else esp_err_t err = spi_flash_mmap(partition_align_pg_size, - SPI_FLASH_SEC_SIZE, SPI_FLASH_MMAP_DATA, (const void **)&p_start, &handle); + SPI_FLASH_SEC_SIZE, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, (const void **)&p_start, &handle); size_t mapped_size = SPI_FLASH_SEC_SIZE; #endif diff --git a/components/esp_partition/partition_linux.c b/components/esp_partition/partition_linux.c index 5e8cffc7b0b..6c05685be5c 100644 --- a/components/esp_partition/partition_linux.c +++ b/components/esp_partition/partition_linux.c @@ -698,7 +698,7 @@ esp_err_t esp_partition_erase_range(const esp_partition_t *partition, size_t off * ESP_OK - calculated out parameters hold pointer to the requested memory area and default handle respectively */ esp_err_t esp_partition_mmap(const esp_partition_t *partition, size_t offset, size_t size, - esp_partition_mmap_memory_t memory, + esp_partition_mmap_flag_t flags, const void **out_ptr, esp_partition_mmap_handle_t *out_handle) { ESP_LOGV(TAG, "esp_partition_mmap(): partition=%s offset=%" PRIu32 " size=%" PRIu32 "", partition->label, (uint32_t) offset, (uint32_t) size); diff --git a/components/esp_partition/partition_target.c b/components/esp_partition/partition_target.c index 04f8fdf06ce..0bcd97f1aa0 100644 --- a/components/esp_partition/partition_target.c +++ b/components/esp_partition/partition_target.c @@ -48,7 +48,7 @@ esp_err_t esp_partition_read(const esp_partition_t *partition, esp_partition_mmap_handle_t handle; esp_err_t err = esp_partition_mmap(partition, src_offset, size, - SPI_FLASH_MMAP_DATA, &buf, &handle); + ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, &buf, &handle); if (err != ESP_OK) { return err; } @@ -152,7 +152,7 @@ esp_err_t esp_partition_erase_range(const esp_partition_t *partition, * mapped pointers, and a single handle for all these regions. */ esp_err_t esp_partition_mmap(const esp_partition_t *partition, size_t offset, size_t size, - esp_partition_mmap_memory_t memory, + esp_partition_mmap_flag_t flags, const void **out_ptr, esp_partition_mmap_handle_t *out_handle) { assert(partition != NULL); @@ -169,7 +169,7 @@ esp_err_t esp_partition_mmap(const esp_partition_t *partition, size_t offset, si // offset within mmu page size block size_t region_offset = phys_addr & (CONFIG_MMU_PAGE_SIZE - 1); size_t mmap_addr = phys_addr & ~(CONFIG_MMU_PAGE_SIZE - 1); - esp_err_t rc = spi_flash_mmap(mmap_addr, size + region_offset, (spi_flash_mmap_memory_t) memory, out_ptr, (spi_flash_mmap_handle_t*) out_handle); + esp_err_t rc = spi_flash_mmap(mmap_addr, size + region_offset, (spi_flash_mmap_flag_t) flags, out_ptr, (spi_flash_mmap_handle_t*) out_handle); // adjust returned pointer to point to the correct offset if (rc == ESP_OK) { *out_ptr = (void *) (((ptrdiff_t) * out_ptr) + region_offset); @@ -177,6 +177,11 @@ esp_err_t esp_partition_mmap(const esp_partition_t *partition, size_t offset, si return rc; } +ESP_STATIC_ASSERT((int)ESP_PARTITION_MMAP_DATA == (int)SPI_FLASH_MMAP_FLAG_DATA && + (int)ESP_PARTITION_MMAP_INST == (int)SPI_FLASH_MMAP_FLAG_INST && + (int)ESP_PARTITION_MMAP_BLOCKS_WRITE == (int)SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, + "esp_partition_mmap_flag_t enum not equal to spi_flash_mmap_flag_t."); + void esp_partition_munmap(esp_partition_mmap_handle_t handle) { spi_flash_munmap((spi_flash_mmap_handle_t) handle); diff --git a/components/esp_partition/test/test_partition.c b/components/esp_partition/test/test_partition.c index d1fcb1c4aac..48ce8cc3a51 100644 --- a/components/esp_partition/test/test_partition.c +++ b/components/esp_partition/test/test_partition.c @@ -232,7 +232,7 @@ TEST_CASE("Can write, read, mmap partition", "[partition][ignore]") esp_partition_mmap_handle_t mmap_handle; size_t begin = 3000; size_t size = 64000; //chosen so size is smaller than 64K but the mmap straddles 2 MMU blocks - TEST_ASSERT_EQUAL(ESP_OK, esp_partition_mmap(p, begin, size, ESP_PARTITION_MMAP_DATA, + TEST_ASSERT_EQUAL(ESP_OK, esp_partition_mmap(p, begin, size, ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, (const void **)&mmap_data, &mmap_handle)); srand(0); for (size_t offset = 0; offset < p->size; offset += block_size) { diff --git a/components/esp_partition/test/test_partitions.c b/components/esp_partition/test/test_partitions.c index 7f62bc0ca15..e5c233ac707 100644 --- a/components/esp_partition/test/test_partitions.c +++ b/components/esp_partition/test/test_partitions.c @@ -147,7 +147,7 @@ TEST_CASE("Test esp_partition_get_sha256() that it can handle a big partition", esp_err_t err = ESP_FAIL; for (; mapped_pages_countsize, ESP_PARTITION_MMAP_DATA, &outptr, &out_handle)); + TEST_ESP_OK(esp_partition_mmap(part, 0, part->size, ESP_PARTITION_MMAP_DATA | extra_flags, &outptr, &out_handle)); CHECK_MMU_OP_FAIL(outptr); ESP_LOG_BUFFER_HEXDUMP(TAG, outptr, 0x20, ESP_LOG_INFO); TEST_FAIL_MESSAGE("System fault should have been generated"); @@ -104,7 +105,7 @@ static void test_esp_partition_mmap_api(void) case 3: part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_TEE_1, NULL); TEST_ASSERT_NOT_NULL(part); - TEST_ESP_OK(esp_partition_mmap(part, 0, part->size, ESP_PARTITION_MMAP_INST, &outptr, &out_handle)); + TEST_ESP_OK(esp_partition_mmap(part, 0, part->size, ESP_PARTITION_MMAP_INST | extra_flags, &outptr, &out_handle)); CHECK_MMU_OP_FAIL(outptr); ESP_LOG_BUFFER_HEXDUMP(TAG, outptr, 0x20, ESP_LOG_INFO); TEST_FAIL_MESSAGE("System fault should have been generated"); @@ -112,7 +113,7 @@ static void test_esp_partition_mmap_api(void) case 4: part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_TEE_OTA, NULL); TEST_ASSERT_NOT_NULL(part); - TEST_ESP_OK(esp_partition_mmap(part, 0, part->size, ESP_PARTITION_MMAP_DATA, &outptr, &out_handle)); + TEST_ESP_OK(esp_partition_mmap(part, 0, part->size, ESP_PARTITION_MMAP_DATA | extra_flags, &outptr, &out_handle)); CHECK_MMU_OP_FAIL(outptr); ESP_LOG_BUFFER_HEXDUMP(TAG, outptr, 0x20, ESP_LOG_INFO); TEST_FAIL_MESSAGE("System fault should have been generated"); @@ -120,7 +121,7 @@ static void test_esp_partition_mmap_api(void) case 5: part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, ESP_TEE_SEC_STG_PART_LABEL); TEST_ASSERT_NOT_NULL(part); - TEST_ESP_OK(esp_partition_mmap(part, 0, part->size, ESP_PARTITION_MMAP_DATA, &outptr, &out_handle)); + TEST_ESP_OK(esp_partition_mmap(part, 0, part->size, ESP_PARTITION_MMAP_DATA | extra_flags, &outptr, &out_handle)); CHECK_MMU_OP_FAIL(outptr); ESP_LOG_BUFFER_HEXDUMP(TAG, outptr, 0x20, ESP_LOG_INFO); TEST_FAIL_MESSAGE("System fault should have been generated"); @@ -206,12 +207,13 @@ static void test_spi_flash_mmap_api(void) const esp_partition_t *part = NULL; spi_flash_mmap_handle_t handle; const void *ptr = NULL; + uint32_t extra_flags = SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE; switch (boot_count) { case 2: part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_TEE_0, NULL); TEST_ASSERT_NOT_NULL(part); - TEST_ESP_OK(spi_flash_mmap(part->address, part->size, SPI_FLASH_MMAP_DATA, &ptr, &handle)); + TEST_ESP_OK(spi_flash_mmap(part->address, part->size, SPI_FLASH_MMAP_DATA | extra_flags, &ptr, &handle)); CHECK_MMU_OP_FAIL(ptr); ESP_LOG_BUFFER_HEXDUMP(TAG, ptr, 0x20, ESP_LOG_INFO); TEST_FAIL_MESSAGE("System fault should have been generated"); @@ -219,7 +221,7 @@ static void test_spi_flash_mmap_api(void) case 3: part = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_TEE_1, NULL); TEST_ASSERT_NOT_NULL(part); - TEST_ESP_OK(spi_flash_mmap(part->address, part->size, SPI_FLASH_MMAP_INST, &ptr, &handle)); + TEST_ESP_OK(spi_flash_mmap(part->address, part->size, SPI_FLASH_MMAP_INST | extra_flags, &ptr, &handle)); CHECK_MMU_OP_FAIL(ptr); ESP_LOG_BUFFER_HEXDUMP(TAG, ptr, 0x20, ESP_LOG_INFO); TEST_FAIL_MESSAGE("System fault should have been generated"); @@ -227,7 +229,7 @@ static void test_spi_flash_mmap_api(void) case 4: part = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_TEE_OTA, NULL); TEST_ASSERT_NOT_NULL(part); - TEST_ESP_OK(spi_flash_mmap(part->address, part->size, SPI_FLASH_MMAP_DATA, &ptr, &handle)); + TEST_ESP_OK(spi_flash_mmap(part->address, part->size, SPI_FLASH_MMAP_DATA | extra_flags, &ptr, &handle)); CHECK_MMU_OP_FAIL(ptr); ESP_LOG_BUFFER_HEXDUMP(TAG, ptr, 0x20, ESP_LOG_INFO); TEST_FAIL_MESSAGE("System fault should have been generated"); diff --git a/components/espcoredump/src/core_dump_elf.c b/components/espcoredump/src/core_dump_elf.c index 18e24f10946..1d412ca490d 100644 --- a/components/espcoredump/src/core_dump_elf.c +++ b/components/espcoredump/src/core_dump_elf.c @@ -907,7 +907,7 @@ static esp_err_t elf_core_dump_image_mmap(esp_partition_mmap_handle_t* core_data } /* map the full core dump partition, including the checksum. */ - return esp_partition_mmap(core_part, 0, out_size, ESP_PARTITION_MMAP_DATA, + return esp_partition_mmap(core_part, 0, out_size, ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, map_addr, core_data_handle); } diff --git a/components/mbedtls/test_apps/mbedtls_ut/main/test_sha.c b/components/mbedtls/test_apps/mbedtls_ut/main/test_sha.c index e14182975c5..3f90a9ca73b 100644 --- a/components/mbedtls/test_apps/mbedtls_ut/main/test_sha.c +++ b/components/mbedtls/test_apps/mbedtls_ut/main/test_sha.c @@ -138,7 +138,7 @@ TEST_CASE("Test esp_sha() function with long input", "[hw_crypto]") const size_t LEN = 1024 * 1024; /* mmap() 1MB of flash, we don't care what it is really */ - esp_err_t err = spi_flash_mmap(0x0, LEN, SPI_FLASH_MMAP_DATA, &ptr, &handle); + esp_err_t err = spi_flash_mmap(0x0, LEN, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr, &handle); TEST_ASSERT_EQUAL_HEX32(ESP_OK, err); TEST_ASSERT_NOT_NULL(ptr); diff --git a/components/spi_flash/Kconfig b/components/spi_flash/Kconfig index 1637c4e7d88..e99d071f06f 100644 --- a/components/spi_flash/Kconfig +++ b/components/spi_flash/Kconfig @@ -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 diff --git a/components/spi_flash/esp_flash_api.c b/components/spi_flash/esp_flash_api.c index 04924cc9c9b..9d00ae471f0 100644 --- a/components/spi_flash/esp_flash_api.c +++ b/components/spi_flash/esp_flash_api.c @@ -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 } diff --git a/components/spi_flash/esp_flash_spi_init.c b/components/spi_flash/esp_flash_spi_init.c index 1c2247a631c..64106ccabea 100644 --- a/components/spi_flash/esp_flash_spi_init.c +++ b/components/spi_flash/esp_flash_spi_init.c @@ -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; } diff --git a/components/spi_flash/flash_mmap.c b/components/spi_flash/flash_mmap.c index 61aa295ee27..ce75fbf7f39 100644 --- a/components/spi_flash/flash_mmap.c +++ b/components/spi_flash/flash_mmap.c @@ -9,6 +9,7 @@ #include #include #include +#include #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"); diff --git a/components/spi_flash/include/esp_flash_chips/esp_flash_types.h b/components/spi_flash/include/esp_flash_chips/esp_flash_types.h index ab99ded3efe..5d274a5cf8e 100644 --- a/components/spi_flash/include/esp_flash_chips/esp_flash_types.h +++ b/components/spi_flash/include/esp_flash_chips/esp_flash_types.h @@ -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'). diff --git a/components/spi_flash/include/esp_private/esp_flash_internal.h b/components/spi_flash/include/esp_private/esp_flash_internal.h index 14e9169a0a7..b8e14e452ec 100644 --- a/components/spi_flash/include/esp_private/esp_flash_internal.h +++ b/components/spi_flash/include/esp_private/esp_flash_internal.h @@ -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. diff --git a/components/spi_flash/include/esp_private/flash_mmap.h b/components/spi_flash/include/esp_private/flash_mmap.h new file mode 100644 index 00000000000..308ee97706a --- /dev/null +++ b/components/spi_flash/include/esp_private/flash_mmap.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#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 diff --git a/components/spi_flash/include/esp_private/spi_flash_os.h b/components/spi_flash/include/esp_private/spi_flash_os.h index 4100f862998..e835c1f1e94 100644 --- a/components/spi_flash/include/esp_private/spi_flash_os.h +++ b/components/spi_flash/include/esp_private/spi_flash_os.h @@ -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. diff --git a/components/spi_flash/include/spi_flash_mmap.h b/components/spi_flash/include/spi_flash_mmap.h index 32e4ead2d3c..c05ae3ee296 100644 --- a/components/spi_flash/include/spi_flash_mmap.h +++ b/components/spi_flash/include/spi_flash_mmap.h @@ -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 #include #include +#include +#include #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) diff --git a/components/spi_flash/linker.lf b/components/spi_flash/linker.lf index 0135f8ec3a1..64f1d2e797b 100644 --- a/components/spi_flash/linker.lf +++ b/components/spi_flash/linker.lf @@ -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) diff --git a/components/spi_flash/spi_flash_os_func_app.c b/components/spi_flash/spi_flash_os_func_app.c index 8e6f28e1d35..e633b18b083 100644 --- a/components/spi_flash/spi_flash_os_func_app.c +++ b/components/spi_flash/spi_flash_os_func_app.c @@ -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) diff --git a/components/spi_flash/test_apps/flash_mmap/main/CMakeLists.txt b/components/spi_flash/test_apps/flash_mmap/main/CMakeLists.txt index 1c3c3a14e03..9e6bccc9f90 100644 --- a/components/spi_flash/test_apps/flash_mmap/main/CMakeLists.txt +++ b/components/spi_flash/test_apps/flash_mmap/main/CMakeLists.txt @@ -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) diff --git a/components/spi_flash/test_apps/flash_mmap/main/test_flash_mmap.c b/components/spi_flash/test_apps/flash_mmap/main/test_flash_mmap.c index 9115ea6c29d..3ed646f9963 100644 --- a/components/spi_flash/test_apps/flash_mmap/main/test_flash_mmap.c +++ b/components/spi_flash/test_apps/flash_mmap/main/test_flash_mmap.c @@ -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]") diff --git a/components/spi_flash/test_apps/flash_mmap/main/test_mmap_api_concurrent.c b/components/spi_flash/test_apps/flash_mmap/main/test_mmap_api_concurrent.c new file mode 100644 index 00000000000..1d301b0c4d5 --- /dev/null +++ b/components/spi_flash/test_apps/flash_mmap/main/test_mmap_api_concurrent.c @@ -0,0 +1,381 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include + +#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"); +} diff --git a/components/spi_flash/test_apps/flash_mmap/main/test_mmap_utils.c b/components/spi_flash/test_apps/flash_mmap/main/test_mmap_utils.c new file mode 100644 index 00000000000..532e2c4f1f7 --- /dev/null +++ b/components/spi_flash/test_apps/flash_mmap/main/test_mmap_utils.c @@ -0,0 +1,94 @@ +/* + * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include +#include + +#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)) ); + } + } + } +} diff --git a/components/spi_flash/test_apps/flash_mmap/main/test_mmap_utils.h b/components/spi_flash/test_apps/flash_mmap/main/test_mmap_utils.h new file mode 100644 index 00000000000..0a7233fa8a7 --- /dev/null +++ b/components/spi_flash/test_apps/flash_mmap/main/test_mmap_utils.h @@ -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); diff --git a/components/spi_flash/test_apps/flash_mmap/pytest_flash_mmap.py b/components/spi_flash/test_apps/flash_mmap/pytest_flash_mmap.py index ea73906afda..2c64831db7a 100644 --- a/components/spi_flash/test_apps/flash_mmap/pytest_flash_mmap.py +++ b/components/spi_flash/test_apps/flash_mmap/pytest_flash_mmap.py @@ -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) diff --git a/components/spi_flash/test_apps/flash_mmap/sdkconfig.ci.suspend_with_rom_impl b/components/spi_flash/test_apps/flash_mmap/sdkconfig.ci.suspend_with_rom_impl new file mode 100644 index 00000000000..9d7597f0c2c --- /dev/null +++ b/components/spi_flash/test_apps/flash_mmap/sdkconfig.ci.suspend_with_rom_impl @@ -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 diff --git a/components/spi_flash/test_apps/mspi_test/sdkconfig.ci.xip_psram b/components/spi_flash/test_apps/mspi_test/sdkconfig.ci.xip_psram deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/docs/en/api-reference/peripherals/spi_flash/index.rst b/docs/en/api-reference/peripherals/spi_flash/index.rst index 2badb657b1c..82026efd8ee 100644 --- a/docs/en/api-reference/peripherals/spi_flash/index.rst +++ b/docs/en/api-reference/peripherals/spi_flash/index.rst @@ -172,6 +172,31 @@ Note that since memory mapping happens in pages, it may be possible to read data mmap is supported by cache, so it can only be used on main flash. +.. only:: not esp32 + + .. _blocks_write_flag: + + About the :cpp:enumerator:`SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE` flag + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + When flash erasing/writing happen while cache mapping exists, it often causes some cache region to be invalidated and reloaded again. To improve the performance, it is suggested to specify this flag when you are sure: + + 1. This mapping will end in a short time, and + 2. Before the mapping ends, you don't need to erase or write the flash. + + This flag will prevent all writes until the corresponding munmap is called. Most ESP-IDF APIs that rely on the mapping to flash internally use this flag. + + .. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND or SOC_SPIRAM_XIP_SUPPORTED + + This flag also helps to prevent the cache being disabled when you are using following modes. See their documentation for more details. + + .. list:: + + :SOC_SPIRAM_XIP_SUPPORTED: - :ref:`xip_from_psram` + :SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND: - :ref:`auto-suspend` + + This flag is implemented by a lock in the SPI Flash driver. The lock is taken when :cpp:func:`spi_flash_mmap` (or mmap-like APIs) is called with the flag and released until corresponding unmap is called. There is a reference counter internally allowing concurrent mapping to the flash. Only after the last mapping to Flash with the flag is revoked (counter equals 0) can the flash erasing/writing APIs start execution. + SPI Flash Implementation ------------------------ diff --git a/docs/en/api-reference/peripherals/spi_flash/spi_flash_concurrency.rst b/docs/en/api-reference/peripherals/spi_flash/spi_flash_concurrency.rst index 6db75246cf7..245d326357d 100644 --- a/docs/en/api-reference/peripherals/spi_flash/spi_flash_concurrency.rst +++ b/docs/en/api-reference/peripherals/spi_flash/spi_flash_concurrency.rst @@ -1,61 +1,90 @@ .. _concurrency-constraints-flash: -Concurrency Constraints for Flash on SPI1 -========================================= +Concurrency Constraints for Flash on SPI0/1 +=========================================== :link_to_translation:`zh_CN:[中文]` -The SPI0/1 bus is shared between the instruction & data cache (for firmware execution) and the SPI1 peripheral (controlled by the drivers including this SPI Flash driver). Hence, operations to SPI1 will cause significant influence to the whole system. This kind of operations include calling SPI Flash API or other drivers on SPI1 bus, any operations like read/write/erase or other user defined SPI operations, regardless to the main flash or other SPI slave devices. +The SPI0/1 bus is shared between the cache and the SPI1 peripheral (controlled by the drivers including this SPI Flash driver). Operations to SPI1 may cause significant influence to the cache and hence the whole system. There are no such constraints and impacts for flash chips connected to other SPI buses, which are not covered in this document. -.. only:: not (esp32c3 or SOC_SPIRAM_XIP_SUPPORTED) +There are three kinds of activities that can happen on SPI0/1 bus: - On {IDF_TARGET_NAME}, these caches must be disabled while reading/writing/erasing. +- Flash writing operations (via SPI1). For example, erasing, page programming, or status register writing commands (e.g., ``SE``, ``PP``, and ``WRSR``). During these commands, the flash is in a unreadable state. The CPU and the cache have to wait until the writing command is completed. APIs below can trigger writing commands: -.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND + - Calling non_encrypted SPI flash write API (:cpp:func:`esp_flash_write`, :cpp:func:`esp_flash_erase_region`, etc.) + - Calling :cpp:func:`esp_flash_write_encrypted` - On {IDF_TARGET_NAME}, the config option :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND` allows the cache to read flash concurrently with SPI1 operations. This is an optional feature that depends on special SPI Flash models, hence disabled by default. See :ref:`auto-suspend` for more details. +- Short operations (via SPI1, includes non-writing flash commands). APIs below can trigger short operations: - If this option is disabled, the caches must be disabled while reading/writing/erasing operations. There are some constraints using driver on the SPI1 bus, see :ref:`impact_disabled_cache`. These constraints will cause more IRAM/DRAM usages. + .. list:: -.. only:: SOC_SPIRAM_XIP_SUPPORTED + - Calling non_encrypted SPI flash read API (:cpp:func:`esp_flash_read`, etc.) + :esp32: - Or other drivers on SPI1 bus for user defined SPI operations (enable experimental feature :ref:`CONFIG_SPI_FLASH_SHARE_SPI1_BUS`) - On {IDF_TARGET_NAME}, the config options :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM` (disabled by default) allows the cache to read/write PSRAM concurrently with SPI1 operations. See :ref:`xip_from_psram` for more details. +- Cache read (via SPI0). Following API and operations can trigger cache read: - If these options are disabled, the caches must be disabled while reading/writing/erasing operations. There are some constraints using driver on the SPI1 bus, see :ref:`impact_disabled_cache`. These constraints will cause more IRAM/DRAM usages. + - Code execution from SPI Flash or PSRAM + - Fetch static data of .data/.rodata/.bss segment from SPI Flash or PSRAM + - All other read/write operation to the PSRAM via the heap or `esp_himem` + - Read from area mapped to SPI Flash, includes: -.. _impact_disabled_cache: + - mmap-like functions: :cpp:func:`spi_flash_mmap`, :cpp:func:`spi_flash_mmap_pages`, :cpp:func:`esp_mmu_map`, :cpp:func:`bootloader_mmap`, and :cpp:func:`esp_partition_mmap`. + - Functions relying on :cpp:func:`spi_flash_mmap`: :cpp:func:`esp_partition_find`, :cpp:func:`esp_partition_register_external`. + - Encrypted flash read/write APIs :cpp:func:`esp_flash_read_encrypted` and :cpp:func:`esp_flash_write_encrypted` (on esp32, or for data validation). -When the Caches Are Disabled ----------------------------- +.. only:: esp32 -Under this condition, all CPUs should always execute code and access data from internal RAM. The APIs documented in this file will disable the caches automatically and transparently. + Caches are disabled during all SPI1 operations. Most tasks will be disabled, and access to Flash/PSRAM is forbidden. See :ref:`cache_disabled` for more details. -.. only:: esp32c3 +.. only:: not esp32 - .. note:: + All SPI flash APIs are exclusive to each other by some internal mutex provided by the driver. - When :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND` is enabled, these APIs will not disable the caches. The hardware will handle the arbitration between them. + For all SPI1 operations (read/write), caches are disabled during these operations by default. Most tasks will be disabled, and access to Flash/PSRAM is forbidden. See :ref:`cache_disabled` for more details. -.. only:: SOC_SPIRAM_XIP_SUPPORTED - .. note:: +.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND or SOC_SPIRAM_XIP_SUPPORTED - When :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM` is enabled, these APIs will not disable the caches. + Some options help reduce the impact of cache disabling. The impact of write operations differs between modes. + + .. only:: SOC_SPIRAM_XIP_SUPPORTED + + - **XIP from PSRAM**: In this mode, all segments that were previously executed from Flash are loaded and executed from PSRAM instead. As a result, the cache can remain enabled while the flash is being erased or written, and code execution is not affected by write operations in most cases. See :ref:`xip_from_psram` for more details. + + .. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND + + - **Auto Suspend**: In this mode, when cache access to flash misses during flash erase/write operations, it is allowed to suspend the flash writing to read from it transparently with some latency. As a result, caches are kept enabled and code execution won't be affected so much during writing operations. + + This is an optional feature that depends on special SPI Flash models, hence disabled by default. See :doc:`spi_flash_optional_feature` and :ref:`auto-suspend` for more details. + + +See :ref:`esp_flash_os_func` and :ref:`spi_bus_lock` for the detailed information of software implementation. + + +.. _cache_disabled: + +Cache Disabled (Default) +------------------------ + +.. only:: esp32 + + Caches are disabled during SPI1 operations. All SPI1 operations will automatically and transparently disable the caches. + +.. only:: not esp32 + + By default, caches are disabled during SPI1 operations (read/write). All SPI1 operations will automatically and transparently disable the caches. .. only:: SOC_HP_CPU_HAS_MULTIPLE_CORES - The way that these APIs disable the caches suspends all the other tasks. Besides, all non-IRAM-safe interrupts will be disabled. The other core will be polling in a busy loop. These will be restored until the Flash operation completes. + When the caches are disabled, all non-IRAM-safe interrupts will be disabled, and all other tasks are suspended. The other core will be polling in a busy loop. Only IRAM-safe interrupt handlers will be executed. These will be restored when the Flash operation completes. .. only:: not SOC_HP_CPU_HAS_MULTIPLE_CORES - The way that these APIs disable the caches also disables non-IRAM-safe interrupts. These will be restored until the Flash operation completes. + When the caches are disabled, all non-IRAM-safe interrupts will be disabled, and all other tasks are suspended. Only IRAM-safe interrupt handlers will be executed. These will be restored when the Flash operation completes. -See also :ref:`esp_flash_os_func` and :ref:`spi_bus_lock`. - -There are no such constraints and impacts for flash chips on other SPI buses than SPI0/1. - -For differences between internal RAM (e.g., IRAM, DRAM) and flash cache, please refer to the :ref:`application memory layout ` documentation. +See :ref:`iram-safe-interrupt-handlers` for information on how to prevent an interrupt handler from being disabled when the cache is disabled. +When the cache is disabled, all CPUs should execute code and access data only from internal RAM. For differences between internal RAM (e.g., IRAM, DRAM) and flash cache, please refer to the :ref:`application memory layout ` documentation. .. _iram-safe-interrupt-handlers: @@ -66,7 +95,7 @@ For interrupt handlers which need to execute when the cache is disabled (e.g., f You must ensure that all data and functions accessed by these interrupt handlers, including the ones that handlers call, are located in IRAM or DRAM. See :ref:`how-to-place-code-in-iram`. -If a function or symbol is not correctly put into IRAM/DRAM, and the interrupt handler reads from the flash cache during a flash operation, it will cause a crash due to Illegal Instruction exception (for code which should be in IRAM) or garbage data to be read (for constant data which should be in DRAM). +If a function or symbol is not correctly put into IRAM/DRAM, and the interrupt handler reads from the flash cache during a flash operation, it will cause a crash. This may be due to an Illegal Instruction exception (for code which should be in IRAM) or garbage data being read (for constant data which should be in DRAM). .. note:: @@ -75,15 +104,16 @@ If a function or symbol is not correctly put into IRAM/DRAM, and the interrupt h Non-IRAM-Safe Interrupt Handlers ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If the ``ESP_INTR_FLAG_IRAM`` flag is not set when registering, the interrupt handler will not get executed when the caches are disabled. Once the caches are restored, the non-IRAM-safe interrupts will be re-enabled. After this moment, the interrupt handler will run normally again. This means that as long as caches are disabled, users will not see the corresponding hardware event happening. +If the ``ESP_INTR_FLAG_IRAM`` flag is not set when registering, the interrupt handler will not be executed when the caches are disabled. Once the caches are restored, the non-IRAM-safe interrupts will be re-enabled. After this moment, the interrupt handler will run normally again. This means that as long as caches are disabled, the corresponding hardware events will not occur. .. only:: SOC_DMA_CAN_ACCESS_FLASH When DMA Read Data from Flash ----------------------------- - When DMA is reading data from Flash, erase/write operations from SPI1 take higher priority in hardware, resulting in unpredictable data read by DMA if auto-suspend is not enabled. It is recommended to stop DMA access to Flash before erasing or writing to it. If DMA cannot be stopped (for example, the LCD needs to continuously refresh image data stored in Flash), it is advisable to copy such data to PSRAM or internal SRAM. + The Flash device doesn't allow reading while it is being erased/programmed, even when the data is not in the region being erased/programmed. + When the flash is being erased/programmed, the Flash data read by DMA is unpredictable. It is recommended to stop DMA access to Flash before erasing or writing to it. If DMA cannot be stopped (for example, the LCD needs to continuously refresh image data stored in Flash), it is advisable to copy such data to PSRAM or internal SRAM. .. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND @@ -92,3 +122,4 @@ If the ``ESP_INTR_FLAG_IRAM`` flag is not set when registering, the interrupt ha .. only:: SOC_SPIRAM_XIP_SUPPORTED .. include:: xip_from_psram.inc + diff --git a/docs/en/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst b/docs/en/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst index a112881a16f..9232c1dd7a3 100644 --- a/docs/en/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst +++ b/docs/en/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst @@ -40,7 +40,7 @@ This feature is supported on all Espressif chips except ESP32 and ESP32-S2. List of flash chips that support this feature: - 1. XM25QxxD series + 1. XM25xxD series 2. GD25QxxE series 3. FM25Q32 diff --git a/docs/en/api-reference/peripherals/spi_flash/xip_from_psram.inc b/docs/en/api-reference/peripherals/spi_flash/xip_from_psram.inc index e4e0a6acb38..8495fc256da 100644 --- a/docs/en/api-reference/peripherals/spi_flash/xip_from_psram.inc +++ b/docs/en/api-reference/peripherals/spi_flash/xip_from_psram.inc @@ -1,10 +1,17 @@ .. _xip_from_psram: -XIP from PSRAM Feature ----------------------- +Executing Code from PSRAM +------------------------- -If :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM` is enabled, the flash ``.text`` sections (used for instructions) and the flash ``.rodata`` sections (used for read only data) will be placed in PSRAM. +Select :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM` config to enable this mode. In this mode, code is executed from PSRAM, and the cache will not be disabled during write APIs in most cases. -The corresponding virtual memory range will be mapped to PSRAM. +In this mode, the flash ``.text`` sections (used for instructions) and the flash ``.rodata`` sections (used for read-only data) will be loaded into PSRAM at startup. The corresponding virtual addresses will be mapped to PSRAM. You do not need to ensure that code and data executed while the flash is being erased or programmed reside in IRAM. -If both of the above options are enabled, the Cache won't be disabled during an SPI1 Flash operation. You don't need to make sure ISRs, ISR callbacks and involved data are placed in internal RAM. +Exception: Cache-Mapped Regions in Flash +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Due to the restriction from SPI Nor Flash parts, access to cache mapped regions in flash (mapped via APIs like spi_flash_mmap) is still not allowed while the flash is being erased/written, regardless of whether the erase/write region and the mapped region overlap. In this case, cache should still be disabled to prevent reading corrupted data from the cache. + +To prevent cache disabling, a lock is implemented inside the SPI Flash driver to ensure mutual exclusion between cache mapping and flash writing, and most ESP-IDF APIs that perform flash mapping use this flag. If mmap-like APIs are called by yourself, you can specify this flag :cpp:enumerator:`SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE` to prevent cache disabling. You cannot use this flag in a task that uses ``esp_flash_erase_*`` or ``esp_flash_write`` between ``spi_flash_mmap`` and ``spi_flash_munmap`` (regardless of whether the write region and mapped region overlap), otherwise it will cause a deadlock. See :ref:`blocks_write_flag` for more details about the flag. + +If mmap-like APIs are called without this flag, the cache will still be disabled when flash erasing or writing happens. diff --git a/docs/zh_CN/api-reference/peripherals/spi_flash/index.rst b/docs/zh_CN/api-reference/peripherals/spi_flash/index.rst index b69e06e1065..250d329d42d 100644 --- a/docs/zh_CN/api-reference/peripherals/spi_flash/index.rst +++ b/docs/zh_CN/api-reference/peripherals/spi_flash/index.rst @@ -172,6 +172,31 @@ flash 在 {IDF_TARGET_CACHE_SIZE} 页进行映射。内存映射硬件既可将 由于 mmap 是由 cache 支持的,因此,mmap 也仅能用在主 flash 上。 +.. only:: not esp32 + + .. _blocks_write_flag: + + 关于 :cpp:enumerator:`SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE` 标志 + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + 当缓存映射存在时发生 flash 擦除/写入,通常会导致某些缓存区域被无效化并重新加载。为提高性能,在确定以下条件时,建议指定此标志: + + 1. 此映射将在短时间内结束,且 + 2. 在映射结束之前,不需要擦除或写入 flash。 + + 此标志将阻止所有写入,直到对应的 munmap 被调用。大多数依赖于 flash 映射的 ESP-IDF API 内部使用此标志。 + + .. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND or SOC_SPIRAM_XIP_SUPPORTED + + 此标志还有助于在使用以下模式时防止缓存被禁用。有关更多详细信息,请参阅相应文档。 + + .. list:: + + :SOC_SPIRAM_XIP_SUPPORTED: - :ref:`xip_from_psram` + :SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND: - :ref:`auto-suspend` + + 此标志通过 SPI Flash 驱动程序中的锁来实现。当调用带有此标志的 :cpp:func:`spi_flash_mmap` (或类似 mmap 的 API)时获取锁,直到对应的 unmap 被调用时释放。内部有一个引用计数器,允许对 flash 进行并发映射。只有在最后一个带有此标志的 flash 映射被撤销(计数器等于 0)后,flash 擦除/写入 API 才能开始执行。 + SPI flash 实现 -------------- diff --git a/docs/zh_CN/api-reference/peripherals/spi_flash/spi_flash_concurrency.rst b/docs/zh_CN/api-reference/peripherals/spi_flash/spi_flash_concurrency.rst index eb3b3dab14a..1a9b8dd969f 100644 --- a/docs/zh_CN/api-reference/peripherals/spi_flash/spi_flash_concurrency.rst +++ b/docs/zh_CN/api-reference/peripherals/spi_flash/spi_flash_concurrency.rst @@ -1,60 +1,90 @@ .. _concurrency-constraints-flash: -SPI1 flash 并发约束 -========================================= +SPI0/1 上 Flash 的并发约束 +=========================================== -:link_to_translation:`en:[English]` +:link_to_translation:`zh_CN:[中文]` -指令/数据 cache(用以执行固件)与 SPI1 外设(由像 SPI flash 驱动一样的驱动程序控制)共享 SPI0/1 总线。因此,SPI1 外设上的操作会对整个系统造成显著的影响。这类操作包括调用 SPI flash API 或者 SPI1 总线上的其他驱动、任何 flash 操作(如读取、写入、擦除)或是由其他用户定义的 SPI 操作(对主 flash 或是其他 SPI 从机)。 +SPI0/1 总线在缓存和 SPI1 外设(由包括此 SPI Flash 驱动在内的驱动程序控制)之间共享。对 SPI1 的操作可能会对缓存以及整个系统造成重大影响。连接到其他 SPI 总线的 flash 芯片没有此类约束和影响,不在本文档的讨论范围中。 -.. only:: not (esp32c3 or SOC_SPIRAM_XIP_SUPPORTED) +SPI0/1 总线上可能发生三种活动: - 在 {IDF_TARGET_NAME} 上,flash 读取/写入/擦除时,必须禁用 cache。 +- Flash 写入操作(通过 SPI1)。例如,擦除、页面编程或状态寄存器写入命令(例如,``SE``、``PP`` 和 ``WRSR``)。在这些命令期间,flash 处于不可读状态。CPU 和缓存必须等待直到写入命令完成。以下 API 可以触发写入命令: -.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND + - 调用非加密 SPI flash 写入 API(:cpp:func:`esp_flash_write`、:cpp:func:`esp_flash_erase_region` 等) + - 调用 :cpp:func:`esp_flash_write_encrypted` - 在 {IDF_TARGET_NAME} 上,配置选项 :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND` 允许 flash/PSRAM 的 cache 访问和 SPI1 的操作并发执行。该选项是可选的,依赖于特定的 SPI Flash 型号,因此默认是关闭的。请参阅 :ref:`auto-suspend`,查看详细信息。 +- 短操作(通过 SPI1,包括非写入 flash 命令)。以下 API 可以触发短操作: - 禁用该选项时,在读取/写入/擦除 flash 期间,必须禁用 cache。使用驱动访问 SPI1 的相关约束参见 :ref:`impact_disabled_cache`。这些约束会带来更多的 IRAM/DRAM 消耗。 + .. list:: -.. only:: SOC_SPIRAM_XIP_SUPPORTED + - 调用非加密 SPI flash 读取 API(:cpp:func:`esp_flash_read` 等) + :esp32: - 或 SPI1 总线上的其他驱动程序用于用户定义的 SPI 操作(启用实验性功能 :ref:`CONFIG_SPI_FLASH_SHARE_SPI1_BUS`) - 在 {IDF_TARGET_NAME} 上,启用配置选项 :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM` (默认禁用)后将允许 flash/PSRAM 的 cache 访问和 SPI1 的操作并发执行。请参阅 :ref:`xip_from_psram`,查看详细信息。 +- 缓存读取(通过 SPI0)。以下 API 和操作可以触发缓存读取: - 禁用该选项时,在读取/写入/擦除 flash 期间,必须禁用 cache。使用驱动访问 SPI1 的相关约束参见 :ref:`impact_disabled_cache`。这些约束会带来更多的 IRAM/DRAM 消耗。 + - 从 SPI Flash 或 PSRAM 执行代码 + - 从 SPI Flash 或 PSRAM 获取 .data/.rodata/.bss 段的静态数据 + - 通过堆或 `esp_himem` 对 PSRAM 的所有其他读/写操作 + - 从映射到 SPI Flash 的区域读取,包括: -.. _impact_disabled_cache: + - 类似 mmap 的函数::cpp:func:`spi_flash_mmap`、:cpp:func:`spi_flash_mmap_pages`、:cpp:func:`esp_mmu_map`、:cpp:func:`bootloader_mmap` 和 :cpp:func:`esp_partition_mmap`。 + - 依赖 :cpp:func:`spi_flash_mmap` 的函数::cpp:func:`esp_partition_find`、:cpp:func:`esp_partition_register_external`。 + - 加密 flash 读/写 API::cpp:func:`esp_flash_read_encrypted` 和 :cpp:func:`esp_flash_write_encrypted` (在 esp32 上,或用于数据验证)。 -禁用 cache 时 ----------------------------- +.. only:: esp32 -此时,在 flash 擦写操作中,所有的 CPU 都只能执行 IRAM 中的代码,而且必须从 DRAM 中读取数据。如果使用本文档中的 API 函数,上述限制将自动生效且透明(无需额外关注),但这些限制可能会影响系统中的其他任务的性能。 + 在所有 SPI1 操作期间缓存会被禁用,因此无法访问 Flash/PSRAM,大多数任务将被禁用。有关更多详细信息,请参阅 :ref:`cache_disabled`。 -.. only:: esp32c3 +.. only:: not esp32 - .. note:: + 所有 SPI flash API 通过驱动程序提供的某些内部互斥锁实现互斥访问。 - 启用 :ref:`CONFIG_SPI_FLASH_AUTO_SUSPEND` 时,不会禁用 cache,其中的操作将通过硬件仲裁器来协调。 + 对于所有 SPI1 操作(读/写),默认情况下在这些操作期间缓存会被禁用,因此无法访问 Flash/PSRAM,大多数任务将被禁用。有关更多详细信息,请参阅 :ref:`cache_disabled`。 -.. only:: SOC_SPIRAM_XIP_SUPPORTED - .. note:: +.. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND or SOC_SPIRAM_XIP_SUPPORTED - 启用 :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM` 选项后,不会禁用 cache。 + 有一些选项可以帮助减轻缓存禁用的影响。写入操作的影响在不同模式下是不同的。 + + .. only:: SOC_SPIRAM_XIP_SUPPORTED + + - **XIP from PSRAM**:在此模式下,所有过去从 Flash 执行的段都改为从 PSRAM 加载和执行。因此,缓存能在 flash 擦除/写入期间保持启用状态,代码执行在大多数情况下不会受到写入操作的影响。有关更多详细信息,请参阅 :ref:`xip_from_psram`。 + + .. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND + + - **Auto Suspend**:在此模式下,当 flash 区域发生缓存未命中时,允许暂停 flash 写入以透明地从中读取,但会有一些延迟。因此,缓存保持启用状态,代码在写入操作期间影响不会很大。 + + 这是一个可选功能,依赖于特殊的 SPI Flash 型号,因此默认禁用。有关更多详细信息,请参阅 :doc:`spi_flash_optional_feature` 和 :ref:`auto-suspend`。 + + +有关软件实现的详细信息,请参阅 :ref:`esp_flash_os_func` 和 :ref:`spi_bus_lock`。 + + +.. _cache_disabled: + +缓存禁用(默认) +------------------------ + +.. only:: esp32 + + 在 SPI1 操作期间缓存会被禁用。所有 SPI1 操作将自动透明地禁用缓存。 + +.. only:: not esp32 + + 默认情况下,在 SPI1 操作期间缓存会被禁用。所有 SPI1 操作将自动透明地禁用缓存。 .. only:: SOC_HP_CPU_HAS_MULTIPLE_CORES - 为避免意外读取 flash cache,一个 CPU 在启动 flash 写入或擦除操作时,另一个 CPU 将阻塞。在 flash 操作完成前,会禁用所有在 CPU 上非 IRAM 安全的中断。 + 当禁用缓存时,所有非 IRAM 安全的中断将被禁用,所有其他任务将被暂停。另一个核心将在一个忙循环中空转。只有 IRAM 安全的中断处理程序将被执行。这些将在 Flash 操作完成时恢复。 .. only:: not SOC_HP_CPU_HAS_MULTIPLE_CORES - 为避免意外读取 flash cache,在 flash 操作完成前,所有 CPU 上,会禁用所有在 CPU 上非 IRAM 安全的中断。 + 当禁用缓存时,所有非 IRAM 安全的中断将被禁用,所有其他任务将被暂停。只有 IRAM 安全的中断处理程序将被执行。这些将在 Flash 操作完成时恢复。 -另请参阅 :ref:`esp_flash_os_func` 和 :ref:`spi_bus_lock`。 +有关如何在禁用缓存时防止中断处理程序被禁用的信息,请参阅 :ref:`iram-safe-interrupt-handlers`。 -除 SPI0/1 以外,SPI 总线上的其他 flash 芯片则不受这种限制。 - -请参阅 :ref:`应用程序内存分布 `,查看内部 RAM(如 IRAM、DRAM)和 flash cache 的区别。 +当禁用缓存时,所有 CPU 应该只从内部 RAM 执行代码和访问数据。有关内部 RAM(例如,IRAM、DRAM)和 flash 缓存之间的差异,请参阅 :ref:`应用程序内存布局 ` 文档。 .. _iram-safe-interrupt-handlers: @@ -79,10 +109,12 @@ IRAM 安全中断处理程序 .. only:: SOC_DMA_CAN_ACCESS_FLASH - 当 DMA 也可以访问 Flash 中的数据时 - ---------------------------------- + 当 DMA 从 Flash 读取数据时 + ----------------------------- - 当 DMA 正在从 Flash 中读取数据时,来自 SPI1 的擦/写操作优先级会更高,如果 Flash 的 auto-suspend 功能没有开启,将会导致 DMA 读到错误的数据。建议在擦写 Flash 之前先停止 DMA 对 Flash 的访问。如果 DMA 不可以停止,比如 LCD 需要持续刷新保存在 Flash 中的图像数据,建议将此类数据拷贝到 PSRAM 或者内部的 SRAM 中。 + Flash 器件不允许在擦除/编程时读取,即使数据不在正在被擦除/编程的区域中。 + + 当 flash 正在被擦除/编程时,DMA 读取的 Flash 数据是不可预测的。建议在擦除或写入之前停止 DMA 对 Flash 的访问。如果无法停止 DMA(例如,LCD 需要持续刷新存储在 Flash 中的图像数据),建议将此类数据复制到 PSRAM 或内部 SRAM。 .. only:: SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND diff --git a/docs/zh_CN/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst b/docs/zh_CN/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst index 971961da3fa..f83950d2f9e 100644 --- a/docs/zh_CN/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst +++ b/docs/zh_CN/api-reference/peripherals/spi_flash/spi_flash_optional_feature.rst @@ -40,7 +40,7 @@ flash 的可选功能 支持此功能的 flash 芯片列表: - 1. XM25QxxD 系列 + 1. XM25xxD 系列 2. GD25QxxE 系列 3. FM25Q32 diff --git a/docs/zh_CN/api-reference/peripherals/spi_flash/xip_from_psram.inc b/docs/zh_CN/api-reference/peripherals/spi_flash/xip_from_psram.inc index ec11ccda61b..c79f08c831f 100644 --- a/docs/zh_CN/api-reference/peripherals/spi_flash/xip_from_psram.inc +++ b/docs/zh_CN/api-reference/peripherals/spi_flash/xip_from_psram.inc @@ -1,10 +1,17 @@ .. _xip_from_psram: -在 PSRAM 中执行代码 +从 PSRAM 执行代码功能 ---------------------- -启用 :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM` 选项后,flash 中 ``.text`` 部分的数据(用于指令)和 flash 中 ``.rodata`` 部分的数据(用于只读数据)将被放入 PSRAM。 +选择 :ref:`CONFIG_SPIRAM_XIP_FROM_PSRAM` 配置以启用此模式。在此模式下,代码从 PSRAM 执行,在大多数情况下,缓存不会在写入 API 期间被禁用。 -相应的虚拟内存地址将被映射到 PSRAM。 +在此模式下,flash ``.text`` 段(用于指令)和 flash ``.rodata`` 段(用于只读数据)将在启动时加载到 PSRAM。相应的虚拟地址将映射到 PSRAM。您无需确保在 flash 被擦除/编程时执行的代码/数据位于 IRAM 中。 -如果同时启用以上两个选项,则在 SPI1 flash 操作期间 cache 不会被禁用,无需确保 ISR、ISR 回调及相关数据放置在内部 RAM 中。 +例外:当缓存映射 flash 中的区域时 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +由于 SPI Nor Flash 器件的限制,在 flash 被擦除/写入时,仍然不允许访问 Flash 中的缓存映射区域(通过 spi_flash_mmap 等 API 映射),无论擦写区域和映射区域是否重合。在这种情况下,在擦写 Flash 时仍应禁用缓存以防止从缓存读取错误的数据。 + +为了防止缓存禁用,在 SPI Flash 驱动程序中实现了一个锁,以确保缓存映射的存在和 Flash 写入是互斥的,大多数进行了 Flash 映射的 ESP-IDF API 都使用了该标志。如果您自己调用类似 mmap 的 API,可以指定此标志 :cpp:enumerator:`SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE` 以防止缓存禁用。您不能在 ``spi_flash_mmap`` 和 ``spi_flash_munmap`` 之间使用 ``esp_flash_erase_*`` 或者 ``esp_flash_write`` 的任务中使用此标志(无论写入区域和映射区域是否重叠),否则会造成死锁。有关该标志的详细信息,请参阅 :ref:`blocks_write_flag`。 + +如果在没有此标志的情况下调用类似 mmap 的 API,当 Flash 发生擦除或者写入时,缓存仍将被禁用。 diff --git a/examples/storage/partition_api/partition_mmap/main/main.c b/examples/storage/partition_api/partition_mmap/main/main.c index a7df552c8e4..8be40fbc2e5 100644 --- a/examples/storage/partition_api/partition_mmap/main/main.c +++ b/examples/storage/partition_api/partition_mmap/main/main.c @@ -38,7 +38,7 @@ void app_main(void) esp_partition_mmap_handle_t map_handle; // Map the partition to data memory - ESP_ERROR_CHECK(esp_partition_mmap(partition, 0, partition->size, ESP_PARTITION_MMAP_DATA, &map_ptr, &map_handle)); + ESP_ERROR_CHECK(esp_partition_mmap(partition, 0, partition->size, ESP_PARTITION_MMAP_DATA | ESP_PARTITION_MMAP_BLOCKS_WRITE, &map_ptr, &map_handle)); ESP_LOGI(TAG, "Mapped partition to data memory address %p", map_ptr); // Read back the written verification data using the mapped memory pointer diff --git a/examples/system/.build-test-rules.yml b/examples/system/.build-test-rules.yml index dd7ee32fb88..1dc5cd67cdd 100644 --- a/examples/system/.build-test-rules.yml +++ b/examples/system/.build-test-rules.yml @@ -224,6 +224,7 @@ examples/system/ota/simple_ota_example: temporary: true reason: not supported yet - if: CONFIG_NAME == "spiram" and SOC_SPIRAM_SUPPORTED != 1 + - if: CONFIG_NAME == "xip_psram" and SOC_SPIRAM_XIP_SUPPORTED != 1 - if: CONFIG_NAME == "on_update_no_sb_ecdsa_p256" and SOC_SECURE_BOOT_V2_ECC != 1 reason: Secure Boot V2 with ECDSA signatures is not supported. - if: CONFIG_NAME == "on_update_no_sb_ecdsa_p384" and SOC_ECDSA_SUPPORT_CURVE_P384 != 1 diff --git a/examples/system/ota/simple_ota_example/pytest_simple_ota.py b/examples/system/ota/simple_ota_example/pytest_simple_ota.py index 59ad9da452a..a8f8e6eb20d 100644 --- a/examples/system/ota/simple_ota_example/pytest_simple_ota.py +++ b/examples/system/ota/simple_ota_example/pytest_simple_ota.py @@ -212,6 +212,7 @@ def test_examples_protocol_simple_ota_example(dut: Dut) -> None: 'config', [ 'spiram', + 'xip_psram', ], indirect=True, ) diff --git a/examples/system/ota/simple_ota_example/sdkconfig.ci.xip_psram b/examples/system/ota/simple_ota_example/sdkconfig.ci.xip_psram new file mode 100644 index 00000000000..fb4389960a7 --- /dev/null +++ b/examples/system/ota/simple_ota_example/sdkconfig.ci.xip_psram @@ -0,0 +1,14 @@ +CONFIG_EXAMPLE_FIRMWARE_UPGRADE_URL="FROM_STDIN" +CONFIG_EXAMPLE_SKIP_COMMON_NAME_CHECK=y +CONFIG_SPIRAM=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y +CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y +CONFIG_EXAMPLE_CONNECT_ETHERNET=y +CONFIG_EXAMPLE_CONNECT_WIFI=n +CONFIG_ETHERNET_INTERNAL_SUPPORT=y +CONFIG_ETHERNET_PHY_GENERIC=y +CONFIG_ETHERNET_MDC_GPIO=23 +CONFIG_ETHERNET_MDIO_GPIO=18 +CONFIG_ETHERNET_PHY_RST_GPIO=5 +CONFIG_ETHERNET_PHY_ADDR=1 +CONFIG_EXAMPLE_CONNECT_IPV6=y diff --git a/tools/test_apps/system/.build-test-rules.yml b/tools/test_apps/system/.build-test-rules.yml index 94ab300d305..afbb008a186 100644 --- a/tools/test_apps/system/.build-test-rules.yml +++ b/tools/test_apps/system/.build-test-rules.yml @@ -142,11 +142,16 @@ tools/test_apps/system/no_embedded_paths: reason: the other targets are not tested yet tools/test_apps/system/panic/coredump: - enable: - - if: IDF_TARGET in ["esp32", "esp32c2", "esp32c3", "esp32c5", "esp32c6", "esp32c61", "esp32h2", "esp32p4", "esp32s2", "esp32s3", "esp32s31"] + disable: + - if: CONFIG_NAME in ["coredump_flash_extram_stack_bss", "coredump_flash_extram_stack_heap"] and SOC_SPIRAM_SUPPORTED != 1 + - if: CONFIG_NAME == "coredump_flash_extram_stack_bss_xip" and SOC_SPIRAM_XIP_SUPPORTED != 1 + - if: IDF_TARGET in ["esp32h4", "esp32h21"] + temporary: true + reason: IDF-12308, IDF-11543 depends_components: - espcoredump - esp_system + - spi_flash tools/test_apps/system/panic/panic_base: enable: diff --git a/tools/test_apps/system/panic/coredump/pytest_panic_coredump.py b/tools/test_apps/system/panic/coredump/pytest_panic_coredump.py index 615f7280fb9..b913769dbb5 100644 --- a/tools/test_apps/system/panic/coredump/pytest_panic_coredump.py +++ b/tools/test_apps/system/panic/coredump/pytest_panic_coredump.py @@ -7,6 +7,7 @@ from pathlib import Path import pytest from pytest_embedded_idf.utils import idf_parametrize +from pytest_embedded_idf.utils import soc_filtered_targets PANIC_BASE_APP = Path(__file__).resolve().parent.parent / 'panic_base' sys.path.insert(0, str(PANIC_BASE_APP)) @@ -78,30 +79,17 @@ def test_task_wdt_cpu1(dut: PanicTestDut, config: str, test_func_name: str) -> N panic_tests.test_task_wdt_cpu1(dut, config, test_func_name) -@pytest.mark.parametrize( - 'app_path, config, target', - [ - pytest.param(COREDUMP_APP, 'coredump_flash_extram_stack_heap_esp32', 'esp32', marks=(pytest.mark.psram,)), - pytest.param(COREDUMP_APP, 'coredump_flash_extram_stack_heap_esp32s2', 'esp32s2', marks=(pytest.mark.generic,)), - pytest.param( - COREDUMP_APP, 'coredump_flash_extram_stack_heap_esp32s3', 'esp32s3', marks=(pytest.mark.quad_psram,) - ), - pytest.param(COREDUMP_APP, 'coredump_flash_extram_stack_bss_esp32', 'esp32', marks=(pytest.mark.psram,)), - pytest.param(COREDUMP_APP, 'coredump_flash_extram_stack_bss_esp32s2', 'esp32s2', marks=(pytest.mark.generic,)), - pytest.param( - COREDUMP_APP, 'coredump_flash_extram_stack_bss_esp32s3', 'esp32s3', marks=(pytest.mark.quad_psram,) - ), - ], - indirect=True, -) -def test_panic_extram_stack(dut: PanicTestDut, config: str) -> None: +def _test_panic_extram_stack_impl(dut: PanicTestDut, config: str) -> None: if 'heap' in config: dut.run_test_func('test_panic_extram_stack_heap') else: dut.run_test_func('test_panic_extram_stack_bss') dut.expect_none('Allocated stack is not in external RAM') dut.expect_none('Guru Meditation') - dut.expect_backtrace() + if dut.is_xtensa: + dut.expect_backtrace() + else: + dut.expect_stack_dump() dut.expect_elf_sha256() if dut.target == 'esp32': @@ -112,13 +100,70 @@ def test_panic_extram_stack(dut: PanicTestDut, config: str) -> None: coredump_pattern = re.compile( '.coredump.tasks.data (0x3[fF][5-9a-fA-F][0-7][0-9a-fA-F]{4}) (0x[a-fA-F0-9]+) RW' ) - else: + elif dut.target == 'esp32s3': # ESP32-S3 External data memory range [0x3c000000-0x3e000000) coredump_pattern = re.compile('.coredump.tasks.data (0x3[c-dC-D][0-9a-fA-F]{6}) (0x[a-fA-F0-9]+) RW') + else: + # RISC-V targets (esp32c5, esp32c61, etc.) External data memory range [0x42000000-0x44000000) + coredump_pattern = re.compile('.coredump.tasks.data (0x4[2-3][0-9a-fA-F]{6}) (0x[a-fA-F0-9]+) RW') common_test(dut, config, expected_backtrace=None, expected_coredump=[coredump_pattern]) +def get_psram_marker(target: str) -> pytest.mark: + if target == 'esp32': + return pytest.mark.psram + elif target == 'esp32s3': + return pytest.mark.quad_psram + + return pytest.mark.generic + + +@pytest.mark.parametrize( + 'app_path, config, target', + [ + pytest.param(COREDUMP_APP, 'coredump_flash_extram_stack_heap', target, marks=(get_psram_marker(target),)) + for target in soc_filtered_targets('SOC_SPIRAM_SUPPORTED == 1') + ], + indirect=True, +) +@pytest.mark.temp_skip_ci(targets=['esp32p4'], reason='p4 rev3 migration') +@pytest.mark.temp_skip_ci(targets=['esp32c5', 'esp32c61', 'esp32p4', 'esp32s31'], reason='TODO: IDF-15623') +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='IDF-12308') +def test_panic_extram_stack_heap_psram(dut: PanicTestDut, config: str) -> None: + _test_panic_extram_stack_impl(dut, config) + + +@pytest.mark.parametrize( + 'app_path, config, target', + [ + pytest.param(COREDUMP_APP, 'coredump_flash_extram_stack_bss', target, marks=(get_psram_marker(target),)) + for target in soc_filtered_targets('SOC_SPIRAM_SUPPORTED == 1') + ], + indirect=True, +) +@pytest.mark.temp_skip_ci(targets=['esp32p4'], reason='p4 rev3 migration') +@pytest.mark.temp_skip_ci(targets=['esp32c5', 'esp32c61', 'esp32p4', 'esp32s31'], reason='TODO: IDF-15623') +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='IDF-12308') +def test_panic_extram_stack_heap_bss(dut: PanicTestDut, config: str) -> None: + _test_panic_extram_stack_impl(dut, config) + + +@pytest.mark.parametrize( + 'app_path, config, target', + [ + pytest.param(COREDUMP_APP, 'coredump_flash_extram_stack_bss_xip', target, marks=(get_psram_marker(target),)) + for target in soc_filtered_targets('SOC_SPIRAM_XIP_SUPPORTED == 1') + ], + indirect=True, +) +@pytest.mark.temp_skip_ci(targets=['esp32p4'], reason='p4 rev3 migration') +@pytest.mark.temp_skip_ci(targets=['esp32c5', 'esp32c61', 'esp32p4', 'esp32s31'], reason='TODO: IDF-15623') +@pytest.mark.temp_skip_ci(targets=['esp32h4'], reason='IDF-12308') +def test_panic_extram_stack_heap_bss_xip(dut: PanicTestDut, config: str) -> None: + _test_panic_extram_stack_impl(dut, config) + + @pytest.mark.generic @pytest.mark.parametrize('app_path, config', CONFIGS, indirect=True) @idf_parametrize('target', COREDUMP_TARGETS_ALL, indirect=['target']) diff --git a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_esp32 b/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss similarity index 93% rename from tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_esp32 rename to tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss index dfc32c40f80..5bf3fa6b05a 100644 --- a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_esp32 +++ b/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss @@ -1,4 +1,3 @@ -CONFIG_IDF_TARGET="esp32" CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y # We need to have the coredump info log CONFIG_LOG_DEFAULT_LEVEL_INFO=y diff --git a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_esp32s3 b/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_esp32s3 deleted file mode 100644 index 73121a75ab4..00000000000 --- a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_esp32s3 +++ /dev/null @@ -1,11 +0,0 @@ -CONFIG_IDF_TARGET="esp32s3" -CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y -# We need to have the coredump info log -CONFIG_LOG_DEFAULT_LEVEL_INFO=y -CONFIG_SPIRAM=y -CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y -CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y -CONFIG_ESP_COREDUMP_USE_STACK_SIZE=y -CONFIG_ESP_COREDUMP_CAPTURE_DRAM=y -CONFIG_PARTITION_TABLE_CUSTOM=y -CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions_capture_dram.csv" diff --git a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_esp32s2 b/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_xip similarity index 92% rename from tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_esp32s2 rename to tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_xip index 70155aa1991..a1b3b26dbec 100644 --- a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_esp32s2 +++ b/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_bss_xip @@ -1,8 +1,8 @@ -CONFIG_IDF_TARGET="esp32s2" CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y # We need to have the coredump info log CONFIG_LOG_DEFAULT_LEVEL_INFO=y CONFIG_SPIRAM=y +CONFIG_SPIRAM_XIP_FROM_PSRAM=y CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=y CONFIG_ESP_COREDUMP_USE_STACK_SIZE=y diff --git a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap_esp32 b/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap similarity index 88% rename from tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap_esp32 rename to tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap index b84f8b6cd6f..2a619f2a468 100644 --- a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap_esp32 +++ b/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap @@ -1,4 +1,3 @@ -CONFIG_IDF_TARGET="esp32" CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y # We need to have the coredump info log CONFIG_LOG_DEFAULT_LEVEL_INFO=y diff --git a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap_esp32s2 b/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap_esp32s2 deleted file mode 100644 index 173078cabfe..00000000000 --- a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap_esp32s2 +++ /dev/null @@ -1,7 +0,0 @@ -CONFIG_IDF_TARGET="esp32s2" -CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y -# We need to have the coredump info log -CONFIG_LOG_DEFAULT_LEVEL_INFO=y -CONFIG_SPIRAM=y -CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y -CONFIG_ESP_COREDUMP_USE_STACK_SIZE=y diff --git a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap_esp32s3 b/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap_esp32s3 deleted file mode 100644 index c3bf37673f2..00000000000 --- a/tools/test_apps/system/panic/coredump/sdkconfig.ci.coredump_flash_extram_stack_heap_esp32s3 +++ /dev/null @@ -1,7 +0,0 @@ -CONFIG_IDF_TARGET="esp32s3" -CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH=y -# We need to have the coredump info log -CONFIG_LOG_DEFAULT_LEVEL_INFO=y -CONFIG_SPIRAM=y -CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y -CONFIG_ESP_COREDUMP_USE_STACK_SIZE=y diff --git a/tools/test_apps/system/ram_loadable_app/main/ram_loadable_app_test.c b/tools/test_apps/system/ram_loadable_app/main/ram_loadable_app_test.c index fa96f283eed..3e16ea2cfd4 100644 --- a/tools/test_apps/system/ram_loadable_app/main/ram_loadable_app_test.c +++ b/tools/test_apps/system/ram_loadable_app/main/ram_loadable_app_test.c @@ -41,7 +41,7 @@ static void s_test_ext_vaddr(void) } } -static void s_test_flash_mmap_data_integrity(void) +static void s_test_flash_mmap_data_integrity_nonblock(void) { char src_p_1[32] = "Test data pattern 123456789"; char src_p_2[32] = "Test data pattern 987654321"; @@ -50,16 +50,41 @@ static void s_test_flash_mmap_data_integrity(void) spi_flash_mmap_handle_t handle1; const void *ptr1; - TEST_ESP_OK(spi_flash_mmap(addr, SPI_FLASH_SEC_SIZE, SPI_FLASH_MMAP_DATA, &ptr1, &handle1)); + TEST_ESP_OK(spi_flash_mmap(addr, SPI_FLASH_SEC_SIZE, SPI_FLASH_MMAP_FLAG_DATA, &ptr1, &handle1)); + TEST_ESP_OK(esp_flash_erase_region(NULL, addr, SPI_FLASH_SEC_SIZE)); TEST_ESP_OK(esp_flash_write(NULL, src_p_1, addr, sizeof(src_p_1))); memcpy(buf, ptr1, sizeof(buf)); - TEST_ASSERT_EQUAL(0, memcmp(buf, src_p_1, sizeof(buf))); + TEST_ESP_OK(esp_flash_erase_region(NULL, addr, SPI_FLASH_SEC_SIZE)); TEST_ESP_OK(esp_flash_write(NULL, src_p_2, addr, sizeof(src_p_2))); memcpy(buf, ptr1, sizeof(buf)); + TEST_ASSERT_EQUAL(0, memcmp(buf, src_p_2, sizeof(buf))); + spi_flash_munmap(handle1); +} +static void s_test_flash_mmap_data_integrity_block(void) +{ + char src_p_1[32] = "Test data pattern 123456789"; + char src_p_2[32] = "Test data pattern 987654321"; + char buf[32]; + const int addr = 0x10000; + + spi_flash_mmap_handle_t handle1; + const void *ptr1; + + TEST_ESP_OK(esp_flash_erase_region(NULL, addr, SPI_FLASH_SEC_SIZE)); + TEST_ESP_OK(esp_flash_write(NULL, src_p_1, addr, sizeof(src_p_1))); + TEST_ESP_OK(spi_flash_mmap(addr, SPI_FLASH_SEC_SIZE, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr1, &handle1)); + memcpy(buf, ptr1, sizeof(buf)); + TEST_ASSERT_EQUAL(0, memcmp(buf, src_p_1, sizeof(buf))); + spi_flash_munmap(handle1); + + TEST_ESP_OK(esp_flash_erase_region(NULL, addr, SPI_FLASH_SEC_SIZE)); + TEST_ESP_OK(esp_flash_write(NULL, src_p_2, addr, sizeof(src_p_2))); + TEST_ESP_OK(spi_flash_mmap(addr, SPI_FLASH_SEC_SIZE, SPI_FLASH_MMAP_FLAG_DATA | SPI_FLASH_MMAP_FLAG_BLOCKS_WRITE, &ptr1, &handle1)); + memcpy(buf, ptr1, sizeof(buf)); TEST_ASSERT_EQUAL(0, memcmp(buf, src_p_2, sizeof(buf))); spi_flash_munmap(handle1); } @@ -85,7 +110,8 @@ void app_main(void) #if !CONFIG_APP_BUILD_TYPE_PURE_RAM_APP s_test_ext_vaddr(); - s_test_flash_mmap_data_integrity(); + s_test_flash_mmap_data_integrity_block(); + s_test_flash_mmap_data_integrity_nonblock(); #endif uint32_t uptime = 0; From 8b7b2008da7306d42320055e0a1cc0f171644a97 Mon Sep 17 00:00:00 2001 From: Xiao Xufeng Date: Fri, 6 Mar 2026 16:51:59 +0800 Subject: [PATCH 2/3] bootloader_utils: fixed missing unmap in load_partition_table when table verify failed --- components/bootloader_support/src/bootloader_utility.c | 1 + 1 file changed, 1 insertion(+) diff --git a/components/bootloader_support/src/bootloader_utility.c b/components/bootloader_support/src/bootloader_utility.c index 2a4151d529e..4718be1a522 100644 --- a/components/bootloader_support/src/bootloader_utility.c +++ b/components/bootloader_support/src/bootloader_utility.c @@ -154,6 +154,7 @@ bool bootloader_utility_load_partition_table(bootloader_state_t *bs) err = esp_partition_table_verify(partitions, true, &num_partitions); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to verify partition table"); + bootloader_munmap(partitions); return false; } From 48f5135a8d727b5422157b938493a28d9c414cbf Mon Sep 17 00:00:00 2001 From: "Michael.B" Date: Sun, 26 Apr 2026 00:52:18 +0800 Subject: [PATCH 3/3] spi_flash: fix PSRAM rodata phys2cache mapping Use the full rodata page range when converting flash physical addresses back to cache addresses for XIP PSRAM. RISC-V extram stack coredump tests are temporarily skipped in CI until coredump supports PSRAM task stacks (IDF-15623). --- components/spi_flash/flash_mmap.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/spi_flash/flash_mmap.c b/components/spi_flash/flash_mmap.c index ce75fbf7f39..3ac89b5dde8 100644 --- a/components/spi_flash/flash_mmap.c +++ b/components/spi_flash/flash_mmap.c @@ -733,7 +733,7 @@ const void * spi_flash_phys2cache(size_t phys_offs, spi_flash_mmap_memory_t memo #endif #if CONFIG_SPIRAM_RODATA - if (phys_page >= rodata_flash_start_page_get() && phys_page <= rodata_flash_start_page_get()) { + if (phys_page >= rodata_flash_start_page_get() && phys_page <= rodata_flash_end_page_get()) { target = MMU_TARGET_PSRAM0; phys_offs -= rodata_flash2spiram_offset() * CONFIG_MMU_PAGE_SIZE; }