diff --git a/components/spi_flash/CMakeLists.txt b/components/spi_flash/CMakeLists.txt index 234e8ef966a..24c39d15c46 100644 --- a/components/spi_flash/CMakeLists.txt +++ b/components/spi_flash/CMakeLists.txt @@ -5,8 +5,9 @@ if(${target} STREQUAL "linux") idf_component_register(SRCS "linux/spi_flash_linux.c" "linux/cache_utils.c" "linux/flash_mmap.c" + "spi_flash_blockdev.c" INCLUDE_DIRS include - REQUIRES esp_hal_mspi + REQUIRES esp_hal_mspi esp_blockdev PRIV_INCLUDE_DIRS include/spi_flash) return() endif() @@ -37,7 +38,8 @@ else() "spi_flash_chip_boya.c" "spi_flash_chip_mxic_opi.c" "spi_flash_chip_th.c" - "memspi_host_driver.c") + "memspi_host_driver.c" + "spi_flash_blockdev.c") set(cache_srcs "cache_utils.c" @@ -61,7 +63,7 @@ else() endif() idf_component_register(SRCS "${srcs}" - REQUIRES hal esp_hal_mspi + REQUIRES hal esp_hal_mspi esp_blockdev PRIV_REQUIRES "${priv_requires}" INCLUDE_DIRS include PRIV_INCLUDE_DIRS include/spi_flash diff --git a/components/spi_flash/include/esp_flash.h b/components/spi_flash/include/esp_flash.h index 31c68611d45..44e325adc97 100644 --- a/components/spi_flash/include/esp_flash.h +++ b/components/spi_flash/include/esp_flash.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 */ @@ -9,6 +9,7 @@ #include #include #include "hal/spi_flash_types.h" +#include "esp_blockdev.h" #ifdef __cplusplus extern "C" { @@ -382,6 +383,24 @@ static inline bool esp_flash_is_quad_mode(const esp_flash_t *chip) return (chip->read_mode == SPI_FLASH_QIO) || (chip->read_mode == SPI_FLASH_QOUT); } +/******************************************************************************* + * BDL Functions + ******************************************************************************/ + +/** + * @brief Get block device handle for given flash chip. + * + * @param chip Pointer to SPI flash chip to use. To use the default chip, pass `esp_flash_default_chip`. Has to be initialized. + * @param[out] out_handle Pointer to receive block device handle. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG if chip or out_handle is NULL + * - ESP_ERR_NO_MEM if out of memory + * - a flash error code if any underlying esp_flash operation failed. + */ +esp_err_t esp_flash_get_blockdev(esp_flash_t* chip, esp_blockdev_handle_t* out_handle); + #ifdef __cplusplus } #endif diff --git a/components/spi_flash/spi_flash_blockdev.c b/components/spi_flash/spi_flash_blockdev.c new file mode 100644 index 00000000000..9b9c5e0298f --- /dev/null +++ b/components/spi_flash/spi_flash_blockdev.c @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "esp_flash.h" +#include "spi_flash_chip_driver.h" +#include "esp_heap_caps.h" +#include "esp_blockdev.h" + +static inline esp_err_t esp_flash_blockdev_read(esp_blockdev_handle_t handle, uint8_t* dst_buf, size_t dst_buf_size, uint64_t src_addr, size_t data_read_len) +{ + if (handle == NULL || dst_buf_size < data_read_len) { + return ESP_ERR_INVALID_ARG; + } + if(handle->device_flags.encrypted) { + return esp_flash_read_encrypted((esp_flash_t*) handle->ctx, (uint32_t) src_addr, dst_buf, (uint32_t) data_read_len); + } + return esp_flash_read((esp_flash_t*) handle->ctx, dst_buf, (uint32_t) src_addr, (uint32_t) data_read_len); +} + +static inline esp_err_t esp_flash_blockdev_write(esp_blockdev_handle_t handle, const uint8_t* src_buf, uint64_t dst_addr, size_t data_write_len) +{ + if (handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (handle->device_flags.read_only) { + return ESP_ERR_NOT_ALLOWED; + } + if(handle->device_flags.encrypted) { + return esp_flash_write_encrypted((esp_flash_t*) handle->ctx, (uint32_t) dst_addr, src_buf, (uint32_t) data_write_len); + } + return esp_flash_write((esp_flash_t*) handle->ctx, src_buf, (uint32_t) dst_addr, (uint32_t) data_write_len); +} + +static inline esp_err_t esp_flash_blockdev_erase(esp_blockdev_handle_t handle, uint64_t start_addr, size_t erase_len) +{ + if (handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + if (handle->device_flags.read_only) { + return ESP_ERR_NOT_ALLOWED; + } + return esp_flash_erase_region((esp_flash_t*) handle->ctx, (uint32_t) start_addr, (uint32_t) erase_len); +} + +static esp_err_t esp_flash_blockdev_ioctl(esp_blockdev_handle_t handle, const uint8_t cmd, void* args) +{ + if (handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + (void) cmd; + (void) args; + + return ESP_ERR_NOT_SUPPORTED; +} + +static inline esp_err_t esp_flash_blockdev_sync_noop(esp_blockdev_handle_t handle) +{ + if (handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + return ESP_OK; // NOOP, write operations are always synchronous +} + +static esp_err_t esp_flash_release_blockdev(esp_blockdev_handle_t handle) +{ + if (handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + free(handle); + return ESP_OK; +} + +static const esp_blockdev_ops_t esp_flash_blockdev_ops = { + .read = esp_flash_blockdev_read, + .write = esp_flash_blockdev_write, + .erase = esp_flash_blockdev_erase, + .ioctl = esp_flash_blockdev_ioctl, + .sync = esp_flash_blockdev_sync_noop, + .release = esp_flash_release_blockdev, +}; + +esp_err_t esp_flash_get_blockdev(esp_flash_t* chip, esp_blockdev_handle_t* out_handle) +{ + if (chip == NULL || out_handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + *out_handle = ESP_BLOCKDEV_HANDLE_INVALID; + + esp_err_t err; + uint32_t size; + err = esp_flash_get_size(chip, &size); + if (err != ESP_OK) { + return err; + } + + esp_blockdev_handle_t out = (esp_blockdev_handle_t) heap_caps_calloc(1, sizeof(esp_blockdev_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA); + if (out == NULL) { + return ESP_ERR_NO_MEM; + } + out->ctx = (void*) chip; + + out->device_flags.read_only = 0; + out->device_flags.erase_before_write = 1; + out->device_flags.and_type_write = 1; + out->device_flags.default_val_after_erase = 1; + + out->geometry.disk_size = (uint64_t) size; + out->geometry.read_size = 1; + out->geometry.write_size = 1; + out->geometry.erase_size = (size_t) chip->chip_drv->sector_size; + out->geometry.recommended_erase_size = (size_t) chip->chip_drv->block_erase_size; + + out->ops = &esp_flash_blockdev_ops; + + *out_handle = out; + return ESP_OK; +} diff --git a/components/spi_flash/test_apps/.build-test-rules.yml b/components/spi_flash/test_apps/.build-test-rules.yml index 64f792d0b79..b45c66fc93e 100644 --- a/components/spi_flash/test_apps/.build-test-rules.yml +++ b/components/spi_flash/test_apps/.build-test-rules.yml @@ -16,6 +16,14 @@ components/spi_flash/test_apps/esp_flash: - esptool_py # Some flash related kconfigs are listed here. - esp_hal_mspi +components/spi_flash/test_apps/esp_flash_blockdev: + disable_test: + - if: IDF_TARGET not in ["esp32", "esp32c3"] + temporary: false + reason: should be sufficient to test on one Xtensa and one RISC-V target + depends_components: + - spi_flash + components/spi_flash/test_apps/esp_flash_stress: disable: - if: IDF_TARGET == "esp32h4" diff --git a/components/spi_flash/test_apps/esp_flash_blockdev/CMakeLists.txt b/components/spi_flash/test_apps/esp_flash_blockdev/CMakeLists.txt new file mode 100644 index 00000000000..cef427bf76b --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_blockdev/CMakeLists.txt @@ -0,0 +1,12 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.22) + +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/components/spi_flash/test_apps/components") + +# "Trim" the build. Include the minimal set of components, main, and anything it depends on. We also depend on +# esptool_py as we set CONFIG_ESPTOOLPY_... options. +set(COMPONENTS main esptool_py) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +project(test_esp_flash_blockdev) diff --git a/components/spi_flash/test_apps/esp_flash_blockdev/README.md b/components/spi_flash/test_apps/esp_flash_blockdev/README.md new file mode 100644 index 00000000000..44f3780f1d6 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_blockdev/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-H2 | ESP32-H21 | ESP32-H4 | ESP32-P4 | ESP32-S2 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | --------- | -------- | -------- | -------- | -------- | diff --git a/components/spi_flash/test_apps/esp_flash_blockdev/main/CMakeLists.txt b/components/spi_flash/test_apps/esp_flash_blockdev/main/CMakeLists.txt new file mode 100644 index 00000000000..528ad2f3c1c --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_blockdev/main/CMakeLists.txt @@ -0,0 +1,8 @@ +set(srcs "test_app_main.c" + "test_spi_flash.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 spi_flash esp_driver_spi test_flash_utils + WHOLE_ARCHIVE) diff --git a/components/spi_flash/test_apps/esp_flash_blockdev/main/test_app_main.c b/components/spi_flash/test_apps/esp_flash_blockdev/main/test_app_main.c new file mode 100644 index 00000000000..175115c844e --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_blockdev/main/test_app_main.c @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_utils.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in esp_flash test, especially ccomp timer test, the threshold is left for that case +#define TEST_MEMORY_LEAK_THRESHOLD (700) + +void setUp(void) +{ + unity_utils_record_free_mem(); +} + +void tearDown(void) +{ + unity_utils_evaluate_leaks_direct(TEST_MEMORY_LEAK_THRESHOLD); +} + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/spi_flash/test_apps/esp_flash_blockdev/main/test_spi_flash.c b/components/spi_flash/test_apps/esp_flash_blockdev/main/test_spi_flash.c new file mode 100644 index 00000000000..3bb408426ff --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_blockdev/main/test_spi_flash.c @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include +#include + +#include "esp_log.h" +#include "unity.h" +#include "spi_flash_chip_driver.h" +#include "test_flash_utils.h" + +TEST_CASE("spi_flash BDL test", "[esp_flash]") +{ + const esp_partition_t* part = get_test_flash_partition(); + esp_flash_t* chip = part->flash_chip; + uint32_t offs = part->address; + + /* chip should be initialised */ + TEST_ASSERT(esp_flash_default_chip != NULL + && esp_flash_chip_driver_initialized(esp_flash_default_chip)); + + if (chip == NULL) { + chip = esp_flash_default_chip; //use the default chip + } + + esp_blockdev_handle_t esp_flash_blockdev = NULL; + TEST_ESP_OK(esp_flash_get_blockdev(chip, &esp_flash_blockdev)); + TEST_ASSERT_NOT_NULL(esp_flash_blockdev); + + uint32_t sector_size = chip->chip_drv->sector_size; + TEST_ASSERT(sector_size % 4096 == 0); + + const uint32_t target_addr = offs; + const size_t data_size = sector_size; // a multiple of 4096 (sector size) + + //needs erase before write + TEST_ESP_OK(esp_flash_blockdev->ops->erase(esp_flash_blockdev, target_addr, data_size)); + + //write to the blockdev + uint8_t* test_data = malloc(data_size); + memset((void*)test_data, 'A', data_size); + TEST_ESP_OK(esp_flash_blockdev->ops->write(esp_flash_blockdev, test_data, target_addr, data_size)); + + //read from the blockdev the data written before + uint8_t* data_buffer = malloc(data_size); + memset((void*)data_buffer, 'X', data_size); + TEST_ESP_OK(esp_flash_blockdev->ops->read(esp_flash_blockdev, data_buffer, data_size, target_addr, data_size)); + TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer, data_size)); + + //erase the data from the blockdev and check it's really wiped + TEST_ESP_OK(esp_flash_blockdev->ops->erase(esp_flash_blockdev, target_addr, data_size)); + TEST_ESP_OK(esp_flash_blockdev->ops->read(esp_flash_blockdev, data_buffer, data_size, target_addr, data_size)); + { + uint8_t ten_erased[10] = {[0 ... 9] = 0xFF}; + TEST_ASSERT_EQUAL(0, memcmp(ten_erased, data_buffer, 10)); + } + free(data_buffer); + free(test_data); + + TEST_ESP_OK(esp_flash_blockdev->ops->release(esp_flash_blockdev)); +} diff --git a/components/spi_flash/test_apps/esp_flash_blockdev/partitions.csv b/components/spi_flash/test_apps/esp_flash_blockdev/partitions.csv new file mode 100644 index 00000000000..9903f621fb7 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_blockdev/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap +nvs, data, nvs, 0x9000, 0x6000, +factory, 0, 0, 0x10000, 1M +flash_test, data, fat, , 700K diff --git a/components/spi_flash/test_apps/esp_flash_blockdev/pytest_esp_flash_blockdev.py b/components/spi_flash/test_apps/esp_flash_blockdev/pytest_esp_flash_blockdev.py new file mode 100644 index 00000000000..3ae137ae533 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_blockdev/pytest_esp_flash_blockdev.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'release', + ], + indirect=True, +) +@idf_parametrize('target', ['supported_targets'], indirect=['target']) +def test_esp_flash_blockdev(dut: Dut) -> None: + dut.run_all_single_board_cases(group='esp_flash') diff --git a/components/spi_flash/test_apps/esp_flash_blockdev/sdkconfig.ci.release b/components/spi_flash/test_apps/esp_flash_blockdev/sdkconfig.ci.release new file mode 100644 index 00000000000..e20ceb05da1 --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_blockdev/sdkconfig.ci.release @@ -0,0 +1,6 @@ +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_SPI_FLASH_ENABLE_COUNTERS=y diff --git a/components/spi_flash/test_apps/esp_flash_blockdev/sdkconfig.defaults b/components/spi_flash/test_apps/esp_flash_blockdev/sdkconfig.defaults new file mode 100644 index 00000000000..554a994f88d --- /dev/null +++ b/components/spi_flash/test_apps/esp_flash_blockdev/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_ESP_TASK_WDT_EN=n +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" diff --git a/tools/test_apps/system/g1_components/check_dependencies.py b/tools/test_apps/system/g1_components/check_dependencies.py index 87fa8b6c5d6..abb6ff6d230 100644 --- a/tools/test_apps/system/g1_components/check_dependencies.py +++ b/tools/test_apps/system/g1_components/check_dependencies.py @@ -54,7 +54,7 @@ g1_g0_components = g1_g0_components_base + get_all_esp_hal_components() # Global expected dependency violations that apply to all targets expected_dep_violations = { 'esp_system': ['esp_timer', 'bootloader_support', 'esp_pm', 'esp_usb_cdc_rom_console'], - 'spi_flash': ['bootloader_support'], + 'spi_flash': ['bootloader_support', 'esp_blockdev'], 'esp_hw_support': ['efuse', 'bootloader_support', 'esp_driver_gpio', 'esp_timer', 'esp_pm'], 'cxx': ['pthread'], }