From 6af5d2cb8ed983a727489aff9c5dba8ac627a632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20M=C3=BAdry?= Date: Thu, 20 Nov 2025 14:24:05 +0100 Subject: [PATCH 1/2] fix(esp_ext_part_tables): MBR restrisct to 4 partitions in esp_mbr_generate function --- esp_ext_part_tables/idf_component.yml | 2 +- esp_ext_part_tables/include/esp_mbr.h | 1 + esp_ext_part_tables/src/esp_mbr.c | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/esp_ext_part_tables/idf_component.yml b/esp_ext_part_tables/idf_component.yml index 34c1506096..64aa567a07 100644 --- a/esp_ext_part_tables/idf_component.yml +++ b/esp_ext_part_tables/idf_component.yml @@ -1,4 +1,4 @@ -version: "0.1.0" +version: "0.1.1" description: ESP External Partition Tables url: https://github.com/espressif/idf-extra-components/tree/master/esp_ext_part_tables issues: https://github.com/espressif/idf-extra-components/issues diff --git a/esp_ext_part_tables/include/esp_mbr.h b/esp_ext_part_tables/include/esp_mbr.h index 9ad3f90d84..b16722c48b 100644 --- a/esp_ext_part_tables/include/esp_mbr.h +++ b/esp_ext_part_tables/include/esp_mbr.h @@ -21,6 +21,7 @@ extern "C" { #define MBR_COPY_PROTECTED 0x5A5A #define MBR_PARTITION_TABLE_OFFSET 0x1BE #define MBR_PARTITION_STATUS_ACTIVE 0x80 +#define MBR_MAX_PARTITION_COUNT 4 // MBR partition entry structure - https://en.wikipedia.org/wiki/Master_boot_record#Partition_table_entries #pragma pack(push, 1) diff --git a/esp_ext_part_tables/src/esp_mbr.c b/esp_ext_part_tables/src/esp_mbr.c index 03df5dafc6..138882bcad 100644 --- a/esp_ext_part_tables/src/esp_mbr.c +++ b/esp_ext_part_tables/src/esp_mbr.c @@ -243,6 +243,10 @@ esp_err_t esp_mbr_generate(mbr_t *mbr, esp_ext_part_list_item_t *it = NULL; int i = 0; SLIST_FOREACH(it, &part_list->head, next) { + if (i >= MBR_MAX_PARTITION_COUNT) { + ESP_LOGW(TAG, "More than %d partitions in the list, only the first %d will be added to the MBR", MBR_MAX_PARTITION_COUNT, MBR_MAX_PARTITION_COUNT); + break; // MBR can only hold 4 partitions + } err = esp_mbr_partition_set(mbr, i, it, &args); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set partition %d: %s", i, esp_err_to_name(err)); From a30f8598d40a14cea4e4c3f1dc2a64f03aab7f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20M=C3=BAdry?= Date: Wed, 23 Jul 2025 17:26:27 +0200 Subject: [PATCH 2/2] feat(esp_ext_part_tables): Add support for BDL --- esp_ext_part_tables/CMakeLists.txt | 8 +- esp_ext_part_tables/idf_component.yml | 2 +- .../include/esp_ext_part_tables.h | 53 +++++ esp_ext_part_tables/src/esp_ext_part_tables.c | 76 +++++++ .../test_apps/main/CMakeLists.txt | 8 +- .../test_apps/main/test_esp_ext_part.c | 208 +++++++++++++++++- 6 files changed, 347 insertions(+), 8 deletions(-) diff --git a/esp_ext_part_tables/CMakeLists.txt b/esp_ext_part_tables/CMakeLists.txt index 26fca3f03d..059131cdda 100644 --- a/esp_ext_part_tables/CMakeLists.txt +++ b/esp_ext_part_tables/CMakeLists.txt @@ -2,6 +2,12 @@ set(srcs "src/esp_ext_part_tables.c" "src/esp_mbr.c" "src/esp_mbr_utils.c") +set(requires "log" "esp_common") + +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "6.0") + list(APPEND requires "esp_blockdev") +endif() + idf_component_register(SRCS ${srcs} INCLUDE_DIRS "include" - REQUIRES "log" "esp_common") + REQUIRES ${requires}) diff --git a/esp_ext_part_tables/idf_component.yml b/esp_ext_part_tables/idf_component.yml index 64aa567a07..2749d05825 100644 --- a/esp_ext_part_tables/idf_component.yml +++ b/esp_ext_part_tables/idf_component.yml @@ -1,4 +1,4 @@ -version: "0.1.1" +version: "0.2.0" description: ESP External Partition Tables url: https://github.com/espressif/idf-extra-components/tree/master/esp_ext_part_tables issues: https://github.com/espressif/idf-extra-components/issues diff --git a/esp_ext_part_tables/include/esp_ext_part_tables.h b/esp_ext_part_tables/include/esp_ext_part_tables.h index 5c1a4a28cd..fbab4e0c00 100644 --- a/esp_ext_part_tables/include/esp_ext_part_tables.h +++ b/esp_ext_part_tables/include/esp_ext_part_tables.h @@ -10,6 +10,11 @@ #include #include #include "esp_err.h" +#include "esp_idf_version.h" + +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) +#include "esp_blockdev.h" +#endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) #if __has_include() #include @@ -209,6 +214,54 @@ esp_err_t esp_ext_part_list_signature_get(esp_ext_part_list_t *part_list, void * */ esp_err_t esp_ext_part_list_signature_set(esp_ext_part_list_t *part_list, const void *signature, esp_ext_part_signature_type_t type); +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) +/** + * @brief Read a aprtition table and from a block device handle and parse it. + * + * This function reads the partition table from the specified block device and populates the provided partition list structure. + * The type of partition table to read is specified by the 'type' parameter. + * Additional arguments for parsing can be provided through the 'extra_args' parameter. + * + * @note This function is not thread-safe. + * + * @param[in] handle Block device handle to read from. + * @param[out] part_list Pointer to the partition list structure to populate from the partition table. + * @param[in] type Type of partition table to read (e.g., MBR). + * @param[in] extra_args Pointer to additional arguments for parsing dependent on the partition type (optional, can be NULL). + * + * @return + * - ESP_OK: Partition list was successfully loaded. + * - ESP_ERR_INVALID_ARG: `handle` or `part_list` is NULL. + * - ESP_ERR_NOT_SUPPORTED: Unsupported partition table type. + * - ESP_ERR_NO_MEM: Memory allocation failed. + * - propagated errors from BDL operations or partition table parsing functions. + */ +esp_err_t esp_ext_part_list_bdl_read(esp_blockdev_handle_t handle, esp_ext_part_list_t *part_list, esp_ext_part_signature_type_t type, void *extra_args); + +/** + * @brief Generate a partition table and write it to a block device handle. + * + * This function writes the provided partition list to the specified block device. + * The type of partition table to write is specified by the 'type' parameter. + * Additional arguments for generation can be provided through the 'extra_args' parameter. + * + * @note This function is not thread-safe. + * + * @param[in] handle Block device handle to write to. + * @param[in] part_list Pointer to the partition list structure generate the partition table from. + * @param[in] type Type of partition table to write (e.g., MBR). + * @param[in] extra_args Pointer to additional arguments for generation dependent on the partition type (optional, can be NULL). + * + * @return + * - ESP_OK: Partition list was successfully written. + * - ESP_ERR_INVALID_ARG: `handle` or `part_list` is NULL. + * - ESP_ERR_NOT_SUPPORTED: Unsupported partition table type. + * - ESP_ERR_NO_MEM: Memory allocation failed. + * - propagated errors from BDL operations or partition table generation functions. + */ +esp_err_t esp_ext_part_list_bdl_write(esp_blockdev_handle_t handle, esp_ext_part_list_t *part_list, esp_ext_part_signature_type_t type, void *extra_args); +#endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) + #ifdef __cplusplus } #endif diff --git a/esp_ext_part_tables/src/esp_ext_part_tables.c b/esp_ext_part_tables/src/esp_ext_part_tables.c index 3215806f39..fa24dc846c 100644 --- a/esp_ext_part_tables/src/esp_ext_part_tables.c +++ b/esp_ext_part_tables/src/esp_ext_part_tables.c @@ -9,8 +9,14 @@ #include "freertos/FreeRTOS.h" #include "esp_err.h" #include "esp_log.h" +#include "esp_idf_version.h" + +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) +#include "esp_blockdev.h" +#endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) #include "esp_ext_part_tables.h" +#include "esp_mbr.h" #if __has_include() #include @@ -153,3 +159,73 @@ esp_err_t esp_ext_part_list_signature_set(esp_ext_part_list_t *part_list, const } return ESP_OK; } + +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) +esp_err_t esp_ext_part_list_bdl_read(esp_blockdev_handle_t handle, esp_ext_part_list_t *part_list, esp_ext_part_signature_type_t type, void *extra_args) +{ + if (handle == NULL || part_list == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_err_t err = ESP_OK; + uint8_t *buf = NULL; + + switch (type) { + case ESP_EXT_PART_LIST_SIGNATURE_MBR: + buf = malloc(MBR_SIZE); + if (buf == NULL) { + return ESP_ERR_NO_MEM; + } + + err = handle->ops->read(handle, buf, MBR_SIZE, 0, MBR_SIZE); + if (err != ESP_OK) { + free(buf); + return err; + } + + err = esp_mbr_parse(buf, part_list, (esp_mbr_parse_extra_args_t *) extra_args); + free(buf); + break; + + default: + err = ESP_ERR_NOT_SUPPORTED; // Unsupported signature type + break; + } + + return err; +} + +esp_err_t esp_ext_part_list_bdl_write(esp_blockdev_handle_t handle, esp_ext_part_list_t *part_list, esp_ext_part_signature_type_t type, void *extra_args) +{ + if (handle == NULL || part_list == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_err_t err = ESP_OK; + uint8_t *buf = NULL; + + switch (type) { + case ESP_EXT_PART_LIST_SIGNATURE_MBR: + buf = malloc(MBR_SIZE); + if (buf == NULL) { + return ESP_ERR_NO_MEM; + } + + err = esp_mbr_generate((mbr_t *) buf, part_list, (esp_mbr_generate_extra_args_t *) extra_args); + if (err != ESP_OK) { + free(buf); + return err; + } + + err = handle->ops->write(handle, buf, 0, MBR_SIZE); + free(buf); + break; + + default: + err = ESP_ERR_NOT_SUPPORTED; // Unsupported signature type + break; + } + + return err; +} +#endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) diff --git a/esp_ext_part_tables/test_apps/main/CMakeLists.txt b/esp_ext_part_tables/test_apps/main/CMakeLists.txt index 8ae2cac7c8..8d100632c0 100644 --- a/esp_ext_part_tables/test_apps/main/CMakeLists.txt +++ b/esp_ext_part_tables/test_apps/main/CMakeLists.txt @@ -1,4 +1,10 @@ +set(priv_requires "unity") + +if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "6.0") + list(APPEND priv_requires "esp_blockdev") +endif() + idf_component_register(SRCS "test_esp_ext_part.c" INCLUDE_DIRS "." - PRIV_REQUIRES "unity" + PRIV_REQUIRES ${priv_requires} WHOLE_ARCHIVE) diff --git a/esp_ext_part_tables/test_apps/main/test_esp_ext_part.c b/esp_ext_part_tables/test_apps/main/test_esp_ext_part.c index 6b72c65478..ebbec8010e 100644 --- a/esp_ext_part_tables/test_apps/main/test_esp_ext_part.c +++ b/esp_ext_part_tables/test_apps/main/test_esp_ext_part.c @@ -10,19 +10,20 @@ #include #include #include "esp_err.h" -#include "esp_ext_part_tables.h" -#include "esp_mbr.h" - -#include "unity.h" -#include "unity_test_runner.h" #include "esp_heap_caps.h" +#include "esp_idf_version.h" #if !CONFIG_IDF_TARGET_LINUX #include "esp_newlib.h" #endif // !CONFIG_IDF_TARGET_LINUX +#include "unity.h" +#include "unity_test_runner.h" #include "unity_test_utils_memory.h" +#include "esp_ext_part_tables.h" +#include "esp_mbr.h" + void setUp(void) { unity_utils_record_free_mem(); @@ -358,6 +359,203 @@ TEST_CASE("Test esp_mbr_partition_set and esp_mbr_remove_gaps_between_partiton_e TEST_ESP_OK(esp_ext_part_list_deinit(&part_list_from_mbr_correct)); } +#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) +#include "esp_blockdev.h" + +// BDL simulated block device implementation for testing + +static esp_err_t bdl_simulated_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 == NULL) { + return ESP_ERR_INVALID_ARG; + } + + uint8_t *buffer = (uint8_t *) handle->ctx; + + if (src_addr + data_read_len > handle->geometry.disk_size || data_read_len > dst_buf_size) { + return ESP_ERR_INVALID_SIZE; + } + + memcpy(dst_buf, buffer + src_addr, data_read_len); + return ESP_OK; +} + +static esp_err_t bdl_simulated_write(esp_blockdev_handle_t handle, const uint8_t *src_buf, uint64_t dst_addr, size_t data_write_len) +{ + if (handle == NULL || src_buf == NULL) { + return ESP_ERR_INVALID_ARG; + } + + uint8_t *buffer = (uint8_t *) handle->ctx; + + if (dst_addr + data_write_len > handle->geometry.disk_size) { + return ESP_ERR_INVALID_SIZE; + } + + memcpy(buffer + dst_addr, src_buf, data_write_len); + return ESP_OK; +} + +static esp_err_t bdl_simulated_release_blockdev(esp_blockdev_handle_t handle) +{ + if (handle != NULL) { + free(handle); + } + return ESP_OK; +} + +static const esp_blockdev_ops_t bdl_simulated_blockdev_ops = { + .read = bdl_simulated_read, + .write = bdl_simulated_write, + .erase = NULL, // Not recommended to leave as NULL; just for test purposes + .ioctl = NULL, + .sync = NULL, + .release = bdl_simulated_release_blockdev, +}; + +static esp_err_t bdl_simulated_get_blockdev(uint8_t *buffer, size_t buffer_size, esp_blockdev_handle_t *out_handle) +{ + if (buffer == NULL || out_handle == NULL) { + return ESP_ERR_INVALID_ARG; + } + + esp_blockdev_handle_t out = (esp_blockdev_handle_t) calloc(1, sizeof(esp_blockdev_t)); + if (out == NULL) { + return ESP_ERR_NO_MEM; + } + out->ctx = (void *) buffer; + + out->device_flags.default_val_after_erase = 0; + + out->geometry.disk_size = buffer_size; + out->geometry.read_size = 1; + out->geometry.write_size = 1; + out->geometry.erase_size = 1; + + out->ops = &bdl_simulated_blockdev_ops; + + *out_handle = out; + return ESP_OK; +} + +TEST_CASE("Test with BDL (simulated in RAM) - basic operations", "[esp_ext_part_table]") +{ + size_t buffer_size = 3 * 1024; + uint8_t *buffer = (uint8_t *) malloc(buffer_size); + TEST_ASSERT_NOT_NULL(buffer); + esp_blockdev_handle_t handle = NULL; + esp_err_t err = bdl_simulated_get_blockdev(buffer, buffer_size, &handle); + TEST_ESP_OK(err); + TEST_ASSERT_NOT_NULL(handle); + + size_t sector_size = 512; + + // Write As to the first sector + uint8_t buf[] = {[0 ... 511] = 'A'}; // Fill buffer with 'A' + err = handle->ops->write(handle, buf, 0, sector_size); + TEST_ESP_OK(err); + + uint8_t read_buf[512] = {0}; + err = handle->ops->read(handle, read_buf, sector_size, 0, sector_size); + TEST_ESP_OK(err); + TEST_ASSERT_EQUAL_MEMORY(buf, read_buf, sizeof(buf)); + + // Write Bs to the emulated "first sector" (0 + start sector offset (2) == sector size (512) * 2) + { + uint8_t buf2[] = {[0 ... 511] = 'B'}; // Fill buffer with 'B' + err = handle->ops->write(handle, buf2, sector_size * 2, sector_size); + TEST_ESP_OK(err); + + err = handle->ops->read(handle, read_buf, sector_size, sector_size * 2, sector_size); + TEST_ESP_OK(err); + TEST_ASSERT_EQUAL_MEMORY(buf2, read_buf, sizeof(buf2)); + } + + // Read the first sector again, it should be 'A's + err = handle->ops->read(handle, read_buf, sector_size, 0, sector_size); + TEST_ESP_OK(err); + TEST_ASSERT_EQUAL_MEMORY(buf, read_buf, sizeof(buf)); + + // Visualize the first 5 sectors + for (int i = 0; i < 5; i++) { + // Read the first sector, it should be 'A's + err = handle->ops->read(handle, read_buf, sector_size, sector_size * i, sector_size); + TEST_ESP_OK(err); + for (int j = 0; j < sizeof(read_buf); j++) { + printf("%c", read_buf[j]); + } + printf("\n"); + fflush(stdout); + } + + handle->ops->release(handle); + handle = NULL; + free(buffer); + buffer = NULL; +} + +TEST_CASE("Test with BDL (simulated in RAM) - MBR related", "[esp_ext_part_table]") +{ + size_t buffer_size = 512; + uint8_t *buffer = (uint8_t *) malloc(buffer_size); + TEST_ASSERT_NOT_NULL(buffer); + esp_blockdev_handle_t handle = NULL; + esp_err_t err = bdl_simulated_get_blockdev(buffer, buffer_size, &handle); + TEST_ESP_OK(err); + TEST_ASSERT_NOT_NULL(handle); + + err = handle->ops->write(handle, mbr_bin, 0, mbr_bin_len); + TEST_ESP_OK(err); + + esp_mbr_parse_extra_args_t mbr_parse_args = { + .sector_size = ESP_EXT_PART_SECTOR_SIZE_512B + }; + + esp_ext_part_list_t part_list = {0}; + esp_ext_part_list_item_t *it = NULL; + err = esp_ext_part_list_bdl_read(handle, &part_list, ESP_EXT_PART_LIST_SIGNATURE_MBR, (void *) &mbr_parse_args); + TEST_ESP_OK(err); + + it = esp_ext_part_list_item_head(&part_list); + TEST_ASSERT_NOT_NULL(it); + printf("Partition list read from BDL simulated MBR:\n"); + print_esp_ext_part_list_items(it); + fflush(stdout); + + esp_mbr_generate_extra_args_t mbr_gen_args = { + .sector_size = ESP_EXT_PART_SECTOR_SIZE_512B, + .alignment = ESP_EXT_PART_ALIGN_1MiB + }; + + esp_ext_part_list_item_t partition_for_insertion = { + .info = { + .address = esp_ext_part_sector_count_to_bytes(20480, mbr_gen_args.sector_size), // 10 MiB offset + .size = 10 * 1024 * 1024, // 10 MiB + .type = ESP_EXT_PART_TYPE_LITTLEFS, + .extra = 4096, // LittleFS block size stored in CHS hack + .flags = ESP_EXT_PART_FLAG_EXTRA, // Extra flag set to indicate that the extra field is used + } + }; + TEST_ESP_OK(esp_ext_part_list_insert(&part_list, &partition_for_insertion)); + + err = esp_ext_part_list_bdl_write(handle, &part_list, ESP_EXT_PART_LIST_SIGNATURE_MBR, (void *) &mbr_gen_args); + TEST_ESP_OK(err); + + TEST_ESP_OK(esp_ext_part_list_deinit(&part_list)); + + err = esp_ext_part_list_bdl_read(handle, &part_list, ESP_EXT_PART_LIST_SIGNATURE_MBR, (void *) &mbr_parse_args); + TEST_ESP_OK(err); + + it = esp_ext_part_list_item_head(&part_list); + TEST_ASSERT_NOT_NULL(it); + printf("Partition list after writing new partition to BDL simulated MBR:\n"); + print_esp_ext_part_list_items(it); + fflush(stdout); + + TEST_ESP_OK(esp_ext_part_list_deinit(&part_list)); +} +#endif // (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0)) + void app_main(void) { printf("Running esp_ext_part_tables component tests\n");