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 8d57d5d09f..5bd4a15c01 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 @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 * @@ -155,6 +155,29 @@ TEST(partition_api, test_partition_mmap) TEST_ASSERT_EQUAL(err, ESP_ERR_INVALID_SIZE); } +TEST(partition_api, test_partition_mmap_support_for_greater_than_4M) +{ + // Scenario: Not specified flash size but provided partition table > 4M (default size supported) + // esp_partition_mmap should calculate partition size from the binary, create mmap_flash_file and return ESP_OK + + // unmap file to have correct initial conditions, regardless of result + esp_partition_file_munmap(); + + // get and initialize the control structure for file mmap + esp_partition_file_mmap_ctrl_t *p_file_mmap_ctrl = esp_partition_get_file_mmap_ctrl_input(); + TEST_ASSERT_NOT_NULL(p_file_mmap_ctrl); + + memset(p_file_mmap_ctrl, 0, sizeof(*p_file_mmap_ctrl)); + strlcpy(p_file_mmap_ctrl->partition_file_name, BUILD_DIR"/partition_table/partition-table_8M.bin", sizeof(p_file_mmap_ctrl->partition_file_name)); + + // esp_partition_find_first calls the esp_partition_file_mmap in the background + 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); + + // cleanup after test + esp_partition_file_munmap(); +} + TEST(partition_api, test_partition_mmap_diff_size) { // Scenario: default temporary flash file, explicitly specified size and file with partition table @@ -336,14 +359,10 @@ TEST(partition_api, test_partition_mmap_name_size) memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input)); } -/* Negative TC to ensure mmap setup checks presence of partition file name (partition table binary file) - * if flash size parameter was specified. - * This test case specifies just flash file size but omits partition table binary file name. - */ TEST(partition_api, test_partition_mmap_size_no_partition) { - // Negative Scenario: conflicting settings - flash_file_name empty, flash_file_size set and partition_file_name not set - // esp_partition_file_mmap should return ESP_ERR_INVALID_ARG + // Scenario: flash_file_name empty, incorrect flash_file_size set and partition_file_name not set + // esp_partition_file_mmap should calculate correct flash_file_size based on default partition table and return ESP_OK // unmap file to have correct initial conditions, regardless of result esp_partition_file_munmap(); @@ -357,22 +376,17 @@ TEST(partition_api, test_partition_mmap_size_no_partition) const uint8_t *p_mem_block = NULL; esp_err_t err = esp_partition_file_mmap(&p_mem_block); - - // expected result is invalid argument - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err); + TEST_ESP_OK(err); // cleanup after test esp_partition_file_munmap(); memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input)); } -/* Negative TC to ensure mmap setup checks presence of flash size parameter if partition file name (partition table binary file) was specified. - * This test case specifies just partition table binary file name but omits flash file size. - */ TEST(partition_api, test_partition_mmap_no_size_partition) { - // Negative Scenario: conflicting settings - flash_file_name empty, flash_file_size not set and partition_file_name set - // esp_partition_file_mmap should return ESP_ERR_INVALID_ARG + // Scenario: - flash_file_name empty, flash_file_size not set and partition_file_name set + // esp_partition_file_mmap() will calculate flash_file_size based on given partition_table and return ESP_OK // unmap file to have correct initial conditions, regardless of result esp_partition_file_munmap(); @@ -382,14 +396,12 @@ TEST(partition_api, test_partition_mmap_no_size_partition) TEST_ASSERT_NOT_NULL(p_file_mmap_ctrl_input); memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input)); - const char *partition_file_name = "/tmp/xyz.bin"; + const char *partition_file_name = BUILD_DIR"/partition_table/partition-table.bin"; strlcpy(p_file_mmap_ctrl_input->partition_file_name, partition_file_name, sizeof(p_file_mmap_ctrl_input->partition_file_name)); const uint8_t *p_mem_block = NULL; esp_err_t err = esp_partition_file_mmap(&p_mem_block); - - // expected result is invalid argument - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, err); + TEST_ESP_OK(err); // cleanup after test esp_partition_file_munmap(); @@ -463,38 +475,6 @@ TEST(partition_api, test_partition_mmap_pfile_nf) memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input)); } -/* Negative TC to check that requested size of emulated flash is at least so big to be able to load binary partition table. - * Too small emulated flash size is introduced and respective error code is evaluated after mmap call. - */ -TEST(partition_api, test_partition_mmap_size_too_small) -{ - // Negative Scenario: specified flash file size too small to hold at least partition table at default offset - // esp_partition_file_mmap should return ESP_ERR_INVALID_SIZE - - // unmap file to have correct initial conditions, regardless of result - esp_partition_file_munmap(); - - // get and initialize the control structure for file mmap - esp_partition_file_mmap_ctrl_t *p_file_mmap_ctrl_input = esp_partition_get_file_mmap_ctrl_input(); - TEST_ASSERT_NOT_NULL(p_file_mmap_ctrl_input); - - memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input)); - - // set valid partition table name and very small flash size - strlcpy(p_file_mmap_ctrl_input->partition_file_name, BUILD_DIR "/partition_table/partition-table.bin", sizeof(p_file_mmap_ctrl_input->partition_file_name)); - p_file_mmap_ctrl_input->flash_file_size = 1; - - const uint8_t *p_mem_block = NULL; - esp_err_t err = esp_partition_file_mmap(&p_mem_block); - - // expected result is invalid argument - TEST_ASSERT_EQUAL(ESP_ERR_INVALID_SIZE, err); - - // cleanup after test - esp_partition_file_munmap(); - memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input)); -} - typedef struct { size_t read_ops; size_t write_ops; @@ -772,6 +752,7 @@ TEST_GROUP_RUNNER(partition_api) RUN_TEST_CASE(partition_api, test_partition_find_first); RUN_TEST_CASE(partition_api, test_partition_ops); RUN_TEST_CASE(partition_api, test_partition_mmap); + RUN_TEST_CASE(partition_api, test_partition_mmap_support_for_greater_than_4M); RUN_TEST_CASE(partition_api, test_partition_mmap_diff_size); RUN_TEST_CASE(partition_api, test_partition_mmap_reopen); RUN_TEST_CASE(partition_api, test_partition_mmap_remove); @@ -780,7 +761,6 @@ TEST_GROUP_RUNNER(partition_api) RUN_TEST_CASE(partition_api, test_partition_mmap_no_size_partition); RUN_TEST_CASE(partition_api, test_partition_mmap_ffile_nf); RUN_TEST_CASE(partition_api, test_partition_mmap_pfile_nf); - RUN_TEST_CASE(partition_api, test_partition_mmap_size_too_small); RUN_TEST_CASE(partition_api, test_partition_stats); RUN_TEST_CASE(partition_api, test_partition_power_off_emulation); RUN_TEST_CASE(partition_api, test_partition_copy); diff --git a/components/esp_partition/partition_linux.c b/components/esp_partition/partition_linux.c index daeba66931..7507a2b4d3 100644 --- a/components/esp_partition/partition_linux.c +++ b/components/esp_partition/partition_linux.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -109,6 +110,64 @@ const char *esp_partition_subtype_to_str(const uint32_t type, const uint32_t sub } } +// Calculate required emulated flash size from a partition table binary. +// Returns 0 on failure. +static size_t esp_partition_calc_required_flash_size_from_file(const char *partition_file_path) +{ + if (partition_file_path == NULL || partition_file_path[0] == '\0') { + return 0; + } + + FILE *fp = fopen(partition_file_path, "rb"); + if (fp == NULL) { + return 0; + } + + // Determine file size as an additional lower bound + if (fseek(fp, 0L, SEEK_END) != 0) { + fclose(fp); + return 0; + } + long file_size = ftell(fp); + if (file_size < 0) { + fclose(fp); + return 0; + } + if (fseek(fp, 0L, SEEK_SET) != 0) { + fclose(fp); + return 0; + } + + size_t max_end = 0; + size_t max_entries = file_size / sizeof(esp_partition_info_t); + for (size_t i = 0; i < max_entries; i++) { + esp_partition_info_t entry; + size_t r = fread(&entry, 1, sizeof(entry), fp); + if (r != sizeof(entry) || entry.magic != ESP_PARTITION_MAGIC) { + break; + } + uint32_t end = entry.pos.offset + entry.pos.size; + if (end > max_end) { + max_end = end; + } + } + + fclose(fp); + + // Also ensure the flash holds the partition table itself at its offset + size_t min_from_table_blob = (size_t)file_size + ESP_PARTITION_TABLE_OFFSET; + size_t required = (max_end > min_from_table_blob) ? max_end : min_from_table_blob; + + // Round up to emulated sector size + size_t sector = ESP_PARTITION_EMULATED_SECTOR_SIZE; + size_t rem = required % sector; + if (rem != 0) { + required += (sector - rem); + } + + return required; +} + esp_err_t esp_partition_file_mmap(const uint8_t **part_desc_addr_start) { // temporary file is used only if control structure doesn't specify file name. @@ -135,21 +194,10 @@ esp_err_t esp_partition_file_mmap(const uint8_t **part_desc_addr_start) open_existing_file = true; } else { - // Open temporary file. If size was specified, also partition table has to be specified, otherwise raise error. - // If none of size, partition table were specified, defaults are used. - // Name of temporary file is available in s_esp_partition_file_mmap_ctrl.flash_file_name - + // name of temporary file and its size is available in s_esp_partition_file_mmap_ctrl.flash_file_name and s_esp_partition_file_mmap_ctrl_input.flash_file_size respectively bool has_partfile = (strlen(s_esp_partition_file_mmap_ctrl_input.partition_file_name) > 0); bool has_len = (s_esp_partition_file_mmap_ctrl_input.flash_file_size > 0); - // conflicting input - if (has_partfile != has_len) { - ESP_LOGE(TAG, "Invalid combination of Partition file name: %s flash file size: %" PRIu32 " was specified. Use either both parameters or none.", - s_esp_partition_file_mmap_ctrl_input.partition_file_name, - (uint32_t) s_esp_partition_file_mmap_ctrl_input.flash_file_size); - return ESP_ERR_INVALID_ARG; - } - // check if partition file is present, if not, use default if (!has_partfile) { strlcpy(s_esp_partition_file_mmap_ctrl_act.partition_file_name, BUILD_DIR "/partition_table/partition-table.bin", sizeof(s_esp_partition_file_mmap_ctrl_act.partition_file_name)); @@ -157,10 +205,13 @@ esp_err_t esp_partition_file_mmap(const uint8_t **part_desc_addr_start) strlcpy(s_esp_partition_file_mmap_ctrl_act.partition_file_name, s_esp_partition_file_mmap_ctrl_input.partition_file_name, sizeof(s_esp_partition_file_mmap_ctrl_act.partition_file_name)); } - // check if flash size is present, if not set to default - if (!has_len) { - s_esp_partition_file_mmap_ctrl_act.flash_file_size = ESP_PARTITION_DEFAULT_EMULATED_FLASH_SIZE; - } else { + // derive the partition size from the s_esp_partition_file_mmap_ctrl_act.partition_file_name + size_t derived_size = esp_partition_calc_required_flash_size_from_file(s_esp_partition_file_mmap_ctrl_act.partition_file_name); + // if derived size is zero, use default partition size + s_esp_partition_file_mmap_ctrl_act.flash_file_size = (derived_size > 0) ? derived_size : ESP_PARTITION_DEFAULT_EMULATED_FLASH_SIZE; + + // if the size of the temporary file is specified, check if the given partition size fits within it + if (has_len && s_esp_partition_file_mmap_ctrl_input.flash_file_size > derived_size) { s_esp_partition_file_mmap_ctrl_act.flash_file_size = s_esp_partition_file_mmap_ctrl_input.flash_file_size; } @@ -381,7 +432,7 @@ esp_err_t esp_partition_file_munmap(void) esp_err_t esp_partition_write(const esp_partition_t *partition, size_t dst_offset, const void *src, size_t size) { - assert(partition != NULL && s_spiflash_mem_file_buf != NULL); + assert(partition != NULL && s_spiflash_mem_file_buf != NULL && src != NULL); if (partition->readonly) { return ESP_ERR_NOT_ALLOWED; @@ -396,6 +447,15 @@ esp_err_t esp_partition_write(const esp_partition_t *partition, size_t dst_offse return ESP_ERR_INVALID_SIZE; } + // Ensure write stays within mapped flash file size + if (s_esp_partition_file_mmap_ctrl_act.flash_file_size > 0) { + size_t start = (size_t)partition->address + dst_offset; + size_t max_len = s_esp_partition_file_mmap_ctrl_act.flash_file_size; + if ((start > max_len) || ((size + start) > max_len)) { + return ESP_ERR_INVALID_SIZE; + } + } + void *dst_addr = s_spiflash_mem_file_buf + partition->address + dst_offset; ESP_LOGV(TAG, "esp_partition_write(): partition=%s dst_offset=%" PRIu32 " src=%p size=%" PRIu32 " (real dst address: %p)", partition->label, (uint32_t) dst_offset, src, (uint32_t) size, dst_addr); @@ -431,7 +491,7 @@ esp_err_t esp_partition_write(const esp_partition_t *partition, size_t dst_offse esp_err_t esp_partition_read(const esp_partition_t *partition, size_t src_offset, void *dst, size_t size) { - assert(partition != NULL && s_spiflash_mem_file_buf != NULL); + assert(partition != NULL && s_spiflash_mem_file_buf != NULL && dst != NULL); if (partition->encrypted) { return ESP_ERR_NOT_SUPPORTED; @@ -443,6 +503,15 @@ esp_err_t esp_partition_read(const esp_partition_t *partition, size_t src_offset return ESP_ERR_INVALID_SIZE; } + // Ensure read stays within mapped flash file size + if (s_esp_partition_file_mmap_ctrl_act.flash_file_size > 0) { + size_t start = (size_t)partition->address + src_offset; + size_t max_len = s_esp_partition_file_mmap_ctrl_act.flash_file_size; + if ((start > max_len) || ((size + start) > max_len)) { + return ESP_ERR_INVALID_SIZE; + } + } + void *src_addr = s_spiflash_mem_file_buf + partition->address + src_offset; ESP_LOGV(TAG, "esp_partition_read(): partition=%s src_offset=%" PRIu32 " dst=%p size=%" PRIu32 " (real src address: %p)", partition->label, (uint32_t) src_offset, dst, (uint32_t) size, src_addr); @@ -467,7 +536,7 @@ esp_err_t esp_partition_write_raw(const esp_partition_t *partition, size_t dst_o esp_err_t esp_partition_erase_range(const esp_partition_t *partition, size_t offset, size_t size) { - assert(partition != NULL); + assert(partition != NULL && s_spiflash_mem_file_buf != NULL); if (partition->readonly) { return ESP_ERR_NOT_ALLOWED; @@ -479,6 +548,15 @@ esp_err_t esp_partition_erase_range(const esp_partition_t *partition, size_t off return ESP_ERR_INVALID_SIZE; } + // Ensure erase stays within mapped flash file size + if (s_esp_partition_file_mmap_ctrl_act.flash_file_size > 0) { + size_t start = (size_t)partition->address + offset; + size_t max_len = s_esp_partition_file_mmap_ctrl_act.flash_file_size; + if ((start > max_len) || ((size + start) > max_len)) { + return ESP_ERR_INVALID_SIZE; + } + } + void *target_addr = s_spiflash_mem_file_buf + partition->address + offset; ESP_LOGV(TAG, "esp_partition_erase_range(): partition=%s offset=%" PRIu32 " size=%" PRIu32 " (real target address: %p)", partition->label, (uint32_t) offset, (uint32_t) size, target_addr);