mirror of
https://github.com/espressif/esp-idf.git
synced 2025-11-03 14:01:53 +00:00
feat(storage): Added Block Device Layer support for esp_partition component
This commit is contained in:
@@ -1,65 +1,67 @@
|
||||
idf_build_get_property(esp_tee_build ESP_TEE_BUILD)
|
||||
|
||||
set(reqs esp_blockdev)
|
||||
|
||||
# bootloader build simplified version
|
||||
if(BOOTLOADER_BUILD)
|
||||
set(srcs "partition_bootloader.c")
|
||||
set(reqs "spi_flash")
|
||||
set(priv_reqs "bootloader_support")
|
||||
set(srcs "partition_bootloader.c")
|
||||
list(APPEND reqs spi_flash)
|
||||
set(priv_reqs "bootloader_support")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${private_include_dirs}
|
||||
REQUIRES ${reqs}
|
||||
PRIV_REQUIRES ${priv_reqs})
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${private_include_dirs}
|
||||
REQUIRES ${reqs}
|
||||
PRIV_REQUIRES ${priv_reqs})
|
||||
|
||||
# esp-tee build simplified version
|
||||
elseif(esp_tee_build)
|
||||
set(srcs "partition_tee.c")
|
||||
set(reqs "spi_flash")
|
||||
set(priv_reqs "tee_flash_mgr")
|
||||
set(srcs "partition_tee.c")
|
||||
list(APPEND reqs spi_flash)
|
||||
set(priv_reqs "tee_flash_mgr")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${private_include_dirs}
|
||||
REQUIRES ${reqs}
|
||||
PRIV_REQUIRES ${priv_reqs})
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${private_include_dirs}
|
||||
REQUIRES ${reqs}
|
||||
PRIV_REQUIRES ${priv_reqs})
|
||||
|
||||
# regular, OS build
|
||||
else()
|
||||
set(srcs "partition.c")
|
||||
set(priv_reqs esp_system spi_flash partition_table)
|
||||
set(reqs)
|
||||
set(private_include_dirs)
|
||||
set(srcs "partition.c")
|
||||
set(priv_reqs esp_system spi_flash partition_table)
|
||||
set(reqs esp_blockdev)
|
||||
set(private_include_dirs)
|
||||
|
||||
idf_build_get_property(build_dir BUILD_DIR)
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
idf_build_get_property(build_dir BUILD_DIR)
|
||||
idf_build_get_property(target IDF_TARGET)
|
||||
|
||||
if(${target} STREQUAL "linux")
|
||||
list(APPEND srcs "partition_linux.c")
|
||||
if(${target} STREQUAL "linux")
|
||||
list(APPEND srcs "partition_linux.c")
|
||||
|
||||
# Steal some include directories from bootloader_support components:
|
||||
idf_component_get_property(bootloader_support_dir bootloader_support COMPONENT_DIR)
|
||||
set(private_include_dirs ${bootloader_support_dir}/include)
|
||||
else()
|
||||
list(APPEND priv_reqs bootloader_support app_update)
|
||||
list(APPEND srcs "partition_target.c")
|
||||
endif()
|
||||
# Steal some include directories from bootloader_support components:
|
||||
idf_component_get_property(bootloader_support_dir bootloader_support COMPONENT_DIR)
|
||||
set(private_include_dirs ${bootloader_support_dir}/include)
|
||||
else()
|
||||
list(APPEND priv_reqs bootloader_support app_update)
|
||||
list(APPEND srcs "partition_target.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${private_include_dirs}
|
||||
REQUIRES ${reqs}
|
||||
PRIV_REQUIRES ${priv_reqs})
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS "include"
|
||||
PRIV_INCLUDE_DIRS ${private_include_dirs}
|
||||
REQUIRES ${reqs}
|
||||
PRIV_REQUIRES ${priv_reqs})
|
||||
|
||||
if(${target} STREQUAL "linux")
|
||||
# set BUILD_DIR because partition_linux.c uses a file created in the build directory
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE "BUILD_DIR=\"${build_dir}\"")
|
||||
endif()
|
||||
if(${target} STREQUAL "linux")
|
||||
# set BUILD_DIR because partition_linux.c uses a file created in the build directory
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE "BUILD_DIR=\"${build_dir}\"")
|
||||
endif()
|
||||
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
|
||||
# These flags are GCC specific
|
||||
set_property(SOURCE ${cache_srcs} APPEND_STRING PROPERTY COMPILE_FLAGS
|
||||
" -fno-inline-small-functions -fno-inline-functions-called-once")
|
||||
endif()
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
|
||||
# These flags are GCC specific
|
||||
set_property(SOURCE ${cache_srcs} APPEND_STRING PROPERTY COMPILE_FLAGS
|
||||
" -fno-inline-small-functions -fno-inline-functions-called-once")
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
@@ -7,3 +7,11 @@ components/esp_partition/host_test/partition_api_test:
|
||||
depends_components:
|
||||
- spi_flash
|
||||
- esp_partition
|
||||
|
||||
components/esp_partition/host_test/partition_bdl_test:
|
||||
enable:
|
||||
- if: IDF_TARGET == "linux"
|
||||
reason: only test on linux
|
||||
depends_components:
|
||||
- spi_flash
|
||||
- esp_partition
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
| Supported Targets | Linux |
|
||||
| ----------------- | ----- |
|
||||
|
||||
This is a test project for partition-related APIs on Linux target (CONFIG_IDF_TARGET_LINUX).
|
||||
This is a test project for verification of esp_partition component APIs on Linux target (CONFIG_IDF_TARGET_LINUX).
|
||||
It verifies all important APIs and properties, and prints the results.
|
||||
The Block-Device Layer tests have names with a prefix 'test_parition_bdl', available in 'components/esp_partition/host_test/partition_bdl_test', and the tests check all the BDL operations and commands related to 'esp_partition' (on host side)
|
||||
|
||||
# Build
|
||||
Source the IDF environment as usual.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
set(COMPONENTS main)
|
||||
# Freertos is included via common components, however, currently only the mock component is compatible with linux
|
||||
# target.
|
||||
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
|
||||
|
||||
project(partition_bdl_test)
|
||||
@@ -0,0 +1,19 @@
|
||||
| Supported Targets | Linux |
|
||||
| ----------------- | ----- |
|
||||
|
||||
This is a test project for esp_partition Block Device Layer interface on Linux target (CONFIG_IDF_TARGET_LINUX).
|
||||
|
||||
# Build
|
||||
Source the IDF environment as usual.
|
||||
|
||||
Once this is done, build the application:
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
# Run
|
||||
```bash
|
||||
idf.py monitor
|
||||
```
|
||||
|
||||
The tests will be executed and the summary will be printed.
|
||||
@@ -0,0 +1,5 @@
|
||||
idf_component_register(SRCS "partition_bdl_test.c"
|
||||
PRIV_REQUIRES esp_partition unity spi_flash)
|
||||
|
||||
# set BUILD_DIR because test uses a file created in the build directory
|
||||
target_compile_definitions(${COMPONENT_LIB} PRIVATE "BUILD_DIR=\"${build_dir}\"")
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*
|
||||
* Linux host partition API test
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include "esp_partition.h"
|
||||
#include "esp_private/partition_linux.h"
|
||||
#include "unity.h"
|
||||
#include "unity_fixture.h"
|
||||
|
||||
const char *TAG = "partition_bdl_test";
|
||||
|
||||
TEST_GROUP(partition_bdl);
|
||||
|
||||
TEST_SETUP(partition_bdl)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_TEAR_DOWN(partition_bdl)
|
||||
{
|
||||
}
|
||||
|
||||
/* Test all the basic Block Device Layer APIs working correctly over emulated esp_partition instance.
|
||||
* NOR flash behavior expected.
|
||||
* */
|
||||
TEST(partition_bdl, test_partition_bdl_ops)
|
||||
{
|
||||
//get the block-device interface instance
|
||||
esp_blockdev_handle_t part_blockdev = NULL;
|
||||
TEST_ESP_OK(esp_partition_get_blockdev(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage1", &part_blockdev));
|
||||
|
||||
const size_t data_size = 256;
|
||||
uint8_t test_data[data_size];
|
||||
const off_t target_addr = 3*4*1024;
|
||||
uint8_t data_buffer[data_size];
|
||||
memset((void*)data_buffer, 0, data_size);
|
||||
|
||||
//erase the first sector data from the blockdev and check it's really wiped
|
||||
TEST_ESP_OK(part_blockdev->erase(part_blockdev, target_addr, part_blockdev->geometry.erase_size));
|
||||
memset((void*)test_data, 0xFF, data_size); //erased NOR flash sector contains only 1s
|
||||
|
||||
TEST_ESP_OK(part_blockdev->read(part_blockdev, data_buffer, sizeof(data_buffer), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer, data_size));
|
||||
|
||||
//write to the blockdev
|
||||
memset((void*)test_data, 'A', data_size);
|
||||
TEST_ESP_OK(part_blockdev->write(part_blockdev, test_data, target_addr, data_size));
|
||||
|
||||
//read from the blockdev the data written before
|
||||
TEST_ESP_OK(part_blockdev->read(part_blockdev, data_buffer, sizeof(data_buffer), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer, data_size));
|
||||
|
||||
//release the BDL object - nothing to check here, the BDL memory is just freed
|
||||
esp_partition_release_blockdev(part_blockdev);
|
||||
}
|
||||
|
||||
/* Test parallel access to two independent partitions through BDL interface.
|
||||
* Use both 'esp_partition_t' and type-subtype-label BDL getters.
|
||||
* NOR flash behavior expected.
|
||||
* */
|
||||
TEST(partition_bdl, test_two_partitions_bdl_ops)
|
||||
{
|
||||
//get the block-device interface instance for partition 'storage1'
|
||||
esp_blockdev_handle_t part_blockdev_1 = NULL;
|
||||
TEST_ESP_OK(esp_partition_get_blockdev(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage1", &part_blockdev_1));
|
||||
|
||||
//get pointer to esp_partition_t object for partition 'storage2'
|
||||
esp_partition_iterator_t iter = esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage2");
|
||||
TEST_ASSERT_NOT_NULL(iter);
|
||||
esp_partition_t *part = (esp_partition_t*)esp_partition_get(iter);
|
||||
TEST_ASSERT_NOT_NULL(part);
|
||||
esp_partition_iterator_release(iter);
|
||||
|
||||
esp_blockdev_handle_t part_blockdev_2 = NULL;
|
||||
TEST_ESP_OK(esp_partition_ptr_get_blockdev(part, &part_blockdev_2));
|
||||
|
||||
//erase & write & read data on both partitions in parallel
|
||||
const size_t data_size = 256;
|
||||
uint8_t test_data[data_size];
|
||||
const off_t target_addr = 3*4*1024;
|
||||
uint8_t data_buffer_1[data_size];
|
||||
uint8_t data_buffer_2[data_size];
|
||||
memset((void*)data_buffer_1, 0, data_size);
|
||||
memset((void*)data_buffer_2, 0, data_size);
|
||||
|
||||
//erase the first sector data from the blockdev and check it's really wiped
|
||||
TEST_ESP_OK(part_blockdev_1->erase(part_blockdev_1, target_addr, part_blockdev_1->geometry.erase_size));
|
||||
TEST_ESP_OK(part_blockdev_2->erase(part_blockdev_2, target_addr, part_blockdev_2->geometry.erase_size));
|
||||
memset((void*)test_data, 0xFF, data_size); //erased NOR flash sector contains only 1s
|
||||
|
||||
TEST_ESP_OK(part_blockdev_1->read(part_blockdev_1, data_buffer_1, sizeof(data_buffer_1), target_addr, data_size));
|
||||
TEST_ESP_OK(part_blockdev_2->read(part_blockdev_2, data_buffer_2, sizeof(data_buffer_2), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer_1, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer_2, data_size));
|
||||
|
||||
//write to the blockdev 1
|
||||
memset((void*)test_data, 'A', data_size);
|
||||
TEST_ESP_OK(part_blockdev_1->write(part_blockdev_1, test_data, target_addr, data_size));
|
||||
|
||||
//read the data written before from the blockdev 1
|
||||
TEST_ESP_OK(part_blockdev_1->read(part_blockdev_1, data_buffer_1, sizeof(data_buffer_1), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer_1, data_size));
|
||||
|
||||
//write to the blockdev 2
|
||||
memset((void*)test_data, 'B', data_size);
|
||||
TEST_ESP_OK(part_blockdev_2->write(part_blockdev_2, test_data, target_addr, data_size));
|
||||
|
||||
//read the data written before from the blockdev 2
|
||||
TEST_ESP_OK(part_blockdev_2->read(part_blockdev_2, data_buffer_2, sizeof(data_buffer_2), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer_2, data_size));
|
||||
|
||||
//release the BDL object - nothing to check here, the BDL memory is just freed
|
||||
esp_partition_release_blockdev(part_blockdev_1);
|
||||
esp_partition_release_blockdev(part_blockdev_2);
|
||||
}
|
||||
|
||||
TEST_GROUP_RUNNER(partition_bdl)
|
||||
{
|
||||
RUN_TEST_CASE(partition_bdl, test_partition_bdl_ops);
|
||||
RUN_TEST_CASE(partition_bdl, test_two_partitions_bdl_ops);
|
||||
}
|
||||
|
||||
static void run_all_tests(void)
|
||||
{
|
||||
RUN_TEST_GROUP(partition_bdl);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
UNITY_MAIN_FUNC(run_all_tests);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
# 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,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
storage1, data, , 0x110000, 512K
|
||||
storage2, data, , 0x190000, 512K,
|
||||
|
@@ -0,0 +1,11 @@
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.host_test
|
||||
@idf_parametrize('target', ['linux'], indirect=['target'])
|
||||
def test_esp_partition_bdl(dut: Dut) -> None:
|
||||
dut.expect_unity_test_output(timeout=5)
|
||||
@@ -0,0 +1,8 @@
|
||||
CONFIG_IDF_TARGET="linux"
|
||||
CONFIG_IDF_TARGET_LINUX=y
|
||||
CONFIG_COMPILER_CXX_EXCEPTIONS=y
|
||||
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
|
||||
CONFIG_UNITY_ENABLE_FIXTURE=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table.csv"
|
||||
CONFIG_ESP_PARTITION_ENABLE_STATS=y
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include "esp_err.h"
|
||||
#include "esp_blockdev.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
@@ -613,6 +614,63 @@ uint32_t esp_partition_get_main_flash_sector_size(void);
|
||||
*/
|
||||
esp_err_t esp_partition_copy(const esp_partition_t* dest_part, uint32_t dest_offset, const esp_partition_t* src_part, uint32_t src_offset, size_t size);
|
||||
|
||||
|
||||
/* *************************************************************************************
|
||||
* Block Device Layer interface
|
||||
* *************************************************************************************/
|
||||
|
||||
/**
|
||||
* @brief Creates block device instance associated with the desired partition and returns the corresponding handle. Convenient version.
|
||||
*
|
||||
* The Block Device Layer (BDL) interface allows generic access to each partition, represented by its unique parameters or by existing 'esp_partition_t' object.
|
||||
* Each BDL structure instance is employed as an extension to existing partition object, ie the partition object is not created if not found.
|
||||
* The BDL instance is to be created and destroyed only via the corresponding create/release APIs. Ignoring this requirement may lead to memory leaks and similar troubles.
|
||||
*
|
||||
* New BDL structure instance is created on the application's heap, it is initialized with the partition's holder details (esp_partition_t) and the caller obtains
|
||||
* corresponding BDL handle in 'out_bdl_handle' output parameter. The life-cycle of such BDL object is fully managed by the owner (caller) and is not checked during
|
||||
* possible esp_partition_t changes or instance destruction.
|
||||
*
|
||||
* @param[in] type Partition type, one of esp_partition_type_t values or an 8-bit unsigned integer.
|
||||
* To find all partitions, no matter the type, use ESP_PARTITION_TYPE_ANY, and set
|
||||
* subtype argument to ESP_PARTITION_SUBTYPE_ANY.
|
||||
* @param[in] subtype Partition subtype, one of esp_partition_subtype_t values or an 8-bit unsigned integer
|
||||
* To find all partitions of given type, use ESP_PARTITION_SUBTYPE_ANY.
|
||||
* @param[in] label (optional) Partition label. Set this value if looking
|
||||
* for partition with a specific name. Pass NULL otherwise.
|
||||
* @param[out] out_bdl_handle Block device instance handle for "native" BDL control
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NO_MEM if memory allocation has failed
|
||||
* - ESP_ERR_NOT_FOUND if the partition defined by the parameters cannot be found
|
||||
*/
|
||||
esp_err_t esp_partition_get_blockdev(const esp_partition_type_t type, const esp_partition_subtype_t subtype, const char *label, esp_blockdev_handle_t *out_bdl_handle);
|
||||
|
||||
/**
|
||||
* @brief The same as esp_partition_get_blockdev(), it just accepts existing partition holder as an parameter and doesn't provide the partition search by itself.
|
||||
*
|
||||
* @param[in] partition Pointer to existing esp_partition_t instance
|
||||
* @param[out] out_bdl_handle Block device instance handle for "native" BDL control
|
||||
*
|
||||
* @return
|
||||
* - ESP_OK on success
|
||||
* - ESP_ERR_NO_MEM if memory allocation has failed
|
||||
* - ESP_ERR_NOT_FOUND if the partition defined by the parameters cannot be found
|
||||
*/
|
||||
esp_err_t esp_partition_ptr_get_blockdev(const esp_partition_t *partition, esp_blockdev_handle_t *out_bdl_handle);
|
||||
|
||||
/**
|
||||
* @brief Release BDL instance associated with specific partition
|
||||
*
|
||||
* Releases BDL structure instance and related internal data. It is strongly recommended to use this API for releasing the BDL handles
|
||||
* created through 'esp_partition_get_blockdev()' or 'esp_partition_ptr_get_blockdev()'calls, due to possible internal dependencies
|
||||
* and memory allocations not "visible" outside of the 'esp_partition' public BDL interface.
|
||||
*
|
||||
* @param[in] dev_handle Block device instance handle to close
|
||||
*
|
||||
*/
|
||||
void esp_partition_release_blockdev(esp_blockdev_handle_t dev_handle);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -67,6 +67,12 @@ typedef struct esp_partition_iterator_opaque_ {
|
||||
esp_partition_t *info; // pointer to info (it is redundant, but makes code more readable)
|
||||
} esp_partition_iterator_opaque_t;
|
||||
|
||||
typedef struct {
|
||||
esp_blockdev_t blockdev;
|
||||
const esp_partition_t *partition;
|
||||
} esp_partition_bdl_t;
|
||||
|
||||
|
||||
static SLIST_HEAD(partition_list_head_, partition_list_item_) s_partition_list = SLIST_HEAD_INITIALIZER(s_partition_list);
|
||||
static _lock_t s_partition_list_lock;
|
||||
|
||||
@@ -546,6 +552,7 @@ esp_err_t esp_partition_deregister_external(const esp_partition_t *partition)
|
||||
result = ESP_ERR_INVALID_ARG;
|
||||
break;
|
||||
}
|
||||
//remove the external partition record
|
||||
SLIST_REMOVE(&s_partition_list, it, partition_list_item_, next);
|
||||
free(it);
|
||||
result = ESP_OK;
|
||||
@@ -627,3 +634,104 @@ esp_err_t esp_partition_copy(const esp_partition_t* dest_part, uint32_t dest_off
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
/* *************************************************************************************
|
||||
* Block Device Layer interface
|
||||
* *************************************************************************************/
|
||||
|
||||
static esp_err_t esp_partition_blockdev_read(esp_blockdev_handle_t dev_handle, uint8_t* dst_buf, size_t dst_buf_size, uint64_t src_addr, size_t data_read_len)
|
||||
{
|
||||
//the simplest boundary check. Should be replaced by auxiliary geometry mapping function
|
||||
if (src_addr % dev_handle->geometry.read_size != 0 || data_read_len % dev_handle->geometry.read_size) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (dst_buf_size > data_read_len) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
const esp_partition_t* partition = ((esp_partition_bdl_t*)dev_handle)->partition;
|
||||
assert(partition != NULL);
|
||||
|
||||
esp_err_t res = esp_partition_read(partition, src_addr, dst_buf, data_read_len);
|
||||
ESP_LOGV(TAG, "esp_partition_read - src_addr=0x%.16" PRIx64 ", size=0x%08" PRIx32 ", result=0x%08x", src_addr, (uint32_t)data_read_len, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static esp_err_t esp_partition_blockdev_write(esp_blockdev_handle_t dev_handle, const uint8_t* src_buf, uint64_t dst_addr, size_t data_write_len)
|
||||
{
|
||||
//the simplest boundary check. Should be replaced by auxiliary geometry mapping function
|
||||
if (dst_addr % dev_handle->geometry.write_size != 0 || data_write_len % dev_handle->geometry.write_size) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
const esp_partition_t* partition = ((esp_partition_bdl_t*)dev_handle)->partition;
|
||||
assert(partition != NULL);
|
||||
|
||||
esp_err_t res = esp_partition_write(partition, dst_addr, src_buf, data_write_len);
|
||||
ESP_LOGV(TAG, "esp_partition_write - dst_addr=0x%.16" PRIx64 ", size=0x%08" PRIx32 ", result=0x%08x", dst_addr, (uint32_t)data_write_len, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static esp_err_t esp_partition_blockdev_erase(esp_blockdev_handle_t dev_handle, uint64_t start_addr, size_t erase_len)
|
||||
{
|
||||
//the simplest boundary check. Should be replaced by auxiliary geometry mapping function
|
||||
if (start_addr % dev_handle->geometry.erase_size != 0 || erase_len % dev_handle->geometry.erase_size) {
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
const esp_partition_t* partition = ((esp_partition_bdl_t*)dev_handle)->partition;
|
||||
assert(partition != NULL);
|
||||
|
||||
esp_err_t res = esp_partition_erase_range(partition, start_addr, erase_len);
|
||||
ESP_LOGV(TAG, "esp_partition_blockdev_erase - src_addr=0x%.16" PRIx64 ", size=0x%08" PRIx32 ", result=0x%08x", start_addr, (uint32_t)erase_len, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
esp_err_t esp_partition_ptr_get_blockdev(const esp_partition_t* partition, esp_blockdev_handle_t *out_bdl_handle_ptr)
|
||||
{
|
||||
esp_partition_bdl_t *out = calloc(1, sizeof(esp_partition_bdl_t));
|
||||
if (out == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
//! the flags should be read from the bottom device - TBD (required for NAND flash etc)
|
||||
ESP_BLOCKDEV_FLAGS_INST_CONFIG_DEFAULT(out->blockdev.device_flags);
|
||||
|
||||
out->blockdev.read = &esp_partition_blockdev_read;
|
||||
out->blockdev.write = &esp_partition_blockdev_write;
|
||||
out->blockdev.erase = &esp_partition_blockdev_erase;
|
||||
out->blockdev.geometry.disk_size = partition->size;
|
||||
out->blockdev.geometry.write_size = 1;
|
||||
out->blockdev.geometry.read_size = 1;
|
||||
out->blockdev.geometry.erase_size = partition->erase_size;
|
||||
out->blockdev.geometry.recommended_write_size = 1;
|
||||
out->blockdev.geometry.recommended_read_size = 1;
|
||||
out->blockdev.geometry.recommended_erase_size = partition->erase_size;
|
||||
out->partition = partition;
|
||||
|
||||
*out_bdl_handle_ptr = (esp_blockdev_handle_t)out;
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_partition_get_blockdev(const esp_partition_type_t type, const esp_partition_subtype_t subtype, const char *label, esp_blockdev_handle_t *out_bdl_handle_ptr)
|
||||
{
|
||||
esp_partition_iterator_t it = esp_partition_find(type, subtype, label);
|
||||
if (it == NULL) {
|
||||
return ESP_ERR_NOT_FOUND;
|
||||
}
|
||||
|
||||
esp_err_t res = esp_partition_ptr_get_blockdev(it->info, out_bdl_handle_ptr);
|
||||
esp_partition_iterator_release(it);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void esp_partition_release_blockdev(esp_blockdev_handle_t dev_handle)
|
||||
{
|
||||
free((esp_partition_bdl_t*)dev_handle);
|
||||
}
|
||||
|
||||
7
components/esp_partition/test_apps/.build-test-rules.yml
Normal file
7
components/esp_partition/test_apps/.build-test-rules.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
# Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps
|
||||
|
||||
components/esp_partition/test_apps:
|
||||
enable:
|
||||
- if: IDF_TARGET in ["esp32", "esp32c3"]
|
||||
temporary: true
|
||||
reason: the other targets are not tested yet
|
||||
8
components/esp_partition/test_apps/CMakeLists.txt
Normal file
8
components/esp_partition/test_apps/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# This is the project CMakeLists.txt file for the test subproject
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
set(COMPONENTS main)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(esp_partition_test)
|
||||
18
components/esp_partition/test_apps/README.md
Normal file
18
components/esp_partition/test_apps/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
| Supported Targets | ESP32 | ESP32-C3 |
|
||||
| ----------------- | ----- | -------- |
|
||||
|
||||
This is a test app for esp_partition component. It verifies all important APIs and properties, and prints the results.
|
||||
The Block Device Layer related tests have names with a prefix 'test_bdl', and they check all the BDL operations related to 'esp_partition' including parallel access to 2 partitions.
|
||||
|
||||
In CI, it is sufficient to run this test for one chip of each architecture.
|
||||
|
||||
# Building and Running
|
||||
|
||||
To run locally:
|
||||
|
||||
```bash
|
||||
idf.py build flash monitor
|
||||
```
|
||||
|
||||
The tests will be executed and the summary will be printed.
|
||||
Note, when the Python test script is executed in internal CI, it will test each configuration one by one. When executing this script locally, it will use whichever binary is already built and available in `build` directory.
|
||||
5
components/esp_partition/test_apps/main/CMakeLists.txt
Normal file
5
components/esp_partition/test_apps/main/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
idf_component_register(SRCS test_part_app.c
|
||||
PRIV_INCLUDE_DIRS .
|
||||
PRIV_REQUIRES unity esp_partition spi_flash
|
||||
WHOLE_ARCHIVE
|
||||
)
|
||||
157
components/esp_partition/test_apps/main/test_part_app.c
Normal file
157
components/esp_partition/test_apps/main/test_part_app.c
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "unity.h"
|
||||
#include "unity_fixture.h"
|
||||
#include "esp_flash.h"
|
||||
#include "esp_partition.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
TEST_GROUP(esp_partition);
|
||||
|
||||
TEST_SETUP(esp_partition)
|
||||
{
|
||||
}
|
||||
|
||||
TEST_TEAR_DOWN(esp_partition)
|
||||
{
|
||||
}
|
||||
|
||||
TEST(esp_partition, test_bdl_interface)
|
||||
{
|
||||
//get the block-device interface instance
|
||||
esp_blockdev_handle_t part_blockdev = NULL;
|
||||
TEST_ESP_OK(esp_partition_get_blockdev(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage1", &part_blockdev));
|
||||
|
||||
const size_t data_size = 256;
|
||||
uint8_t test_data[data_size];
|
||||
const off_t target_addr = 3*4*1024;
|
||||
uint8_t data_buffer[data_size];
|
||||
memset((void*)data_buffer, 0, data_size);
|
||||
|
||||
//erase the first sector data from the blockdev and check it's really wiped
|
||||
TEST_ESP_OK(part_blockdev->erase(part_blockdev, target_addr, part_blockdev->geometry.erase_size));
|
||||
memset((void*)test_data, 0xFF, data_size); //erased NOR flash sector contains only 1s
|
||||
TEST_ESP_OK(part_blockdev->read(part_blockdev, data_buffer, sizeof(data_buffer), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer, data_size));
|
||||
|
||||
//write to the blockdev
|
||||
memset((void*)test_data, 'A', data_size);
|
||||
TEST_ESP_OK(part_blockdev->write(part_blockdev, test_data, target_addr, data_size));
|
||||
|
||||
//read from the blockdev the data written before
|
||||
TEST_ESP_OK(part_blockdev->read(part_blockdev, data_buffer, sizeof(data_buffer), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer, data_size));
|
||||
|
||||
//release the BDL object - nothing to check here, the BDL memory is just freed
|
||||
esp_partition_release_blockdev(part_blockdev);
|
||||
}
|
||||
|
||||
TEST(esp_partition, test_bdl_interface_external)
|
||||
{
|
||||
//register external partition
|
||||
const esp_partition_t* ext_partition;
|
||||
TEST_ESP_OK(esp_partition_register_external(NULL, 0x1D0000, 0x20000, "storage3", ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, &ext_partition));
|
||||
|
||||
//get the block-device interface instance and test all the ops over external partition
|
||||
esp_blockdev_handle_t part_blockdev = NULL;
|
||||
TEST_ESP_OK(esp_partition_ptr_get_blockdev(ext_partition, &part_blockdev));
|
||||
|
||||
const size_t data_size = 256;
|
||||
uint8_t test_data[data_size];
|
||||
const off_t target_addr = 3*4*1024;
|
||||
uint8_t data_buffer[data_size];
|
||||
memset((void*)data_buffer, 0, data_size);
|
||||
|
||||
//erase the first sector data from the blockdev and check it's really wiped
|
||||
TEST_ESP_OK(part_blockdev->erase(part_blockdev, target_addr, part_blockdev->geometry.erase_size));
|
||||
memset((void*)test_data, 0xFF, data_size); //erased NOR flash sector contains only 1s
|
||||
TEST_ESP_OK(part_blockdev->read(part_blockdev, data_buffer, sizeof(data_buffer), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer, data_size));
|
||||
|
||||
//write to the blockdev
|
||||
memset((void*)test_data, 'A', data_size);
|
||||
TEST_ESP_OK(part_blockdev->write(part_blockdev, test_data, target_addr, data_size));
|
||||
|
||||
//read from the blockdev the data written before
|
||||
TEST_ESP_OK(part_blockdev->read(part_blockdev, data_buffer, sizeof(data_buffer), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer, data_size));
|
||||
|
||||
//release the BDL object - nothing to check here, the BDL memory is just freed
|
||||
esp_partition_release_blockdev(part_blockdev);
|
||||
|
||||
//deregister the external partition
|
||||
TEST_ESP_OK(esp_partition_deregister_external(ext_partition));
|
||||
}
|
||||
|
||||
TEST(esp_partition, test_bdl_two_partitions)
|
||||
{
|
||||
//get the block-device interface instance for partition 'storage1'
|
||||
esp_blockdev_handle_t part_blockdev_1 = NULL;
|
||||
TEST_ESP_OK(esp_partition_get_blockdev(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage1", &part_blockdev_1));
|
||||
|
||||
//get pointer to esp_partition_t object for partition 'storage2'
|
||||
esp_partition_iterator_t iter = esp_partition_find(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage2");
|
||||
TEST_ASSERT_NOT_NULL(iter);
|
||||
const esp_partition_t *part = esp_partition_get(iter);
|
||||
TEST_ASSERT_NOT_NULL(part);
|
||||
esp_partition_iterator_release(iter);
|
||||
|
||||
esp_blockdev_handle_t part_blockdev_2 = NULL;
|
||||
TEST_ESP_OK(esp_partition_ptr_get_blockdev(part, &part_blockdev_2));
|
||||
|
||||
//erase & write & read data on both partitions in parallel
|
||||
const size_t data_size = 256;
|
||||
uint8_t test_data[data_size];
|
||||
const off_t target_addr = 3*4*1024;
|
||||
uint8_t data_buffer_1[data_size];
|
||||
uint8_t data_buffer_2[data_size];
|
||||
memset((void*)data_buffer_1, 0, data_size);
|
||||
memset((void*)data_buffer_2, 0, data_size);
|
||||
|
||||
//erase the first sector data from the blockdev and check it's really wiped
|
||||
TEST_ESP_OK(part_blockdev_1->erase(part_blockdev_1, target_addr, part_blockdev_1->geometry.erase_size));
|
||||
TEST_ESP_OK(part_blockdev_2->erase(part_blockdev_2, target_addr, part_blockdev_2->geometry.erase_size));
|
||||
memset((void*)test_data, 0xFF, data_size); //erased NOR flash sector contains only 1s
|
||||
|
||||
TEST_ESP_OK(part_blockdev_1->read(part_blockdev_1, data_buffer_1, sizeof(data_buffer_1), target_addr, data_size));
|
||||
TEST_ESP_OK(part_blockdev_2->read(part_blockdev_2, data_buffer_2, sizeof(data_buffer_2), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer_1, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer_2, data_size));
|
||||
|
||||
//write to the blockdev 1
|
||||
memset((void*)test_data, 'A', data_size);
|
||||
TEST_ESP_OK(part_blockdev_1->write(part_blockdev_1, test_data, target_addr, data_size));
|
||||
|
||||
//read the data written before from the blockdev 1
|
||||
TEST_ESP_OK(part_blockdev_1->read(part_blockdev_1, data_buffer_1, sizeof(data_buffer_1), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer_1, data_size));
|
||||
|
||||
//write to the blockdev 2
|
||||
memset((void*)test_data, 'B', data_size);
|
||||
TEST_ESP_OK(part_blockdev_2->write(part_blockdev_2, test_data, target_addr, data_size));
|
||||
|
||||
//read the data written before from the blockdev 2
|
||||
TEST_ESP_OK(part_blockdev_2->read(part_blockdev_2, data_buffer_2, sizeof(data_buffer_2), target_addr, data_size));
|
||||
TEST_ASSERT_EQUAL(0, memcmp(test_data, data_buffer_2, data_size));
|
||||
|
||||
//release the BDL object - nothing to check here, the BDL memory is just freed
|
||||
esp_partition_release_blockdev(part_blockdev_1);
|
||||
esp_partition_release_blockdev(part_blockdev_2);
|
||||
}
|
||||
|
||||
TEST_GROUP_RUNNER(esp_partition)
|
||||
{
|
||||
RUN_TEST_CASE(esp_partition, test_bdl_interface)
|
||||
RUN_TEST_CASE(esp_partition, test_bdl_interface_external)
|
||||
RUN_TEST_CASE(esp_partition, test_bdl_two_partitions)
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
UNITY_MAIN(esp_partition);
|
||||
}
|
||||
7
components/esp_partition/test_apps/partitions.csv
Normal file
7
components/esp_partition/test_apps/partitions.csv
Normal file
@@ -0,0 +1,7 @@
|
||||
# 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,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
storage1, data, , 0x110000, 512K
|
||||
storage2, data, , 0x190000, 256K,
|
||||
|
11
components/esp_partition/test_apps/pytest_esp_partition.py
Normal file
11
components/esp_partition/test_apps/pytest_esp_partition.py
Normal file
@@ -0,0 +1,11 @@
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
from pytest_embedded_idf.utils import idf_parametrize
|
||||
|
||||
|
||||
@pytest.mark.generic
|
||||
@idf_parametrize('target', ['esp32', 'esp32c3'], indirect=['target'])
|
||||
def test_esp_partition(dut: Dut) -> None:
|
||||
dut.expect_unity_test_output()
|
||||
7
components/esp_partition/test_apps/sdkconfig.defaults
Normal file
7
components/esp_partition/test_apps/sdkconfig.defaults
Normal file
@@ -0,0 +1,7 @@
|
||||
# unity framework
|
||||
CONFIG_UNITY_ENABLE_FIXTURE=y
|
||||
|
||||
# use custom partition table
|
||||
CONFIG_PARTITION_TABLE_OFFSET=0x8000
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
Reference in New Issue
Block a user