Storage: ESP Partition extended options for the SPI Flash emulation

Various extensions and fixes to improve Linux target SPI Flash emulation. Used by the host tests
This commit is contained in:
radek.tandler 2023-01-20 17:58:52 +01:00 committed by BOT
parent 370e250072
commit e9e388a085
10 changed files with 834 additions and 146 deletions

View File

@ -21,6 +21,16 @@ idf_component_register(SRCS "${srcs}"
REQUIRES ${reqs}
PRIV_REQUIRES ${priv_reqs})
if(${target} STREQUAL "linux")
# link bsd library for strlcpy
find_library(LIB_BSD bsd)
if(LIB_BSD)
target_link_libraries(${COMPONENT_LIB} PRIVATE ${LIB_BSD})
elseif(NOT CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
message(WARNING "Missing LIBBSD library. Install libbsd-dev package and/or check linker directories.")
endif()
endif()
if(CMAKE_C_COMPILER_ID MATCHES "GNU")
# These flags are GCC specific
set_property(SOURCE ${cache_srcs} APPEND_STRING PROPERTY COMPILE_FLAGS

View File

@ -1,10 +1,10 @@
menu "ESP_PARTITION"
menu "Partition API Configuration"
# This option enables gathering statistics and flash wear levelling simulation.
# Linux target only (host tests).
config ESP_PARTITION_ENABLE_STATS
bool "Enable esp_partition statistics gathering"
default n
bool
depends on IDF_TARGET_LINUX
help
This option enables statistics gathering and flash wear simulation. Linux only.
default n
endmenu

View File

@ -7,3 +7,38 @@ set(COMPONENTS main)
list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/tools/mocks/freertos/")
project(partition_api_test)
#extra step to build 8M partition table on top of (default) 4M partition table built by partition-table dependency
set(flashsize_opt --flash-size 8MB)
set(partition_csv "partition_table_8M.csv")
set(partition_bin "partition-table_8M.bin")
idf_build_get_property(build_dir BUILD_DIR)
idf_build_get_property(python PYTHON)
set(gen_partition_table "${python}" "${CMAKE_CURRENT_SOURCE_DIR}/../../../partition_table/gen_esp32part.py" "-q"
"${flashsize_opt}" "--")
set(partition_table_display
COMMAND ${CMAKE_COMMAND} -E echo "Partition table binary generated. Contents:"
COMMAND ${CMAKE_COMMAND} -E echo "*******************************************************************************"
COMMAND ${gen_partition_table} "${build_dir}/partition_table/${partition_bin}"
COMMAND ${CMAKE_COMMAND} -E echo "*******************************************************************************"
)
add_custom_command(OUTPUT "${build_dir}/partition_table/${partition_bin}"
COMMAND ${gen_partition_table}
"${CMAKE_CURRENT_SOURCE_DIR}/${partition_csv}"
"${build_dir}/partition_table/${partition_bin}"
${partition_table_display}
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${partition_csv}
VERBATIM)
add_custom_target(partition_table_bin_8M DEPENDS "${build_dir}/partition_table/${partition_bin}"
)
add_custom_target(partition-table-8M
DEPENDS partition_table_bin_8M
${partition_table_display}
VERBATIM)
add_dependencies(partition_api_test.elf partition-table partition-table-8M)

View File

@ -7,6 +7,11 @@
*/
#include <string.h>
#if __has_include(<bsd/string.h>)
// for strlcpy
#include <bsd/string.h>
#endif
#include <unistd.h>
#include "esp_err.h"
#include "esp_partition.h"
#include "esp_private/partition_linux.h"
@ -144,7 +149,331 @@ TEST(partition_api, test_partition_mmap)
TEST_ASSERT_EQUAL(err,ESP_ERR_INVALID_SIZE);
}
#define EMULATED_VIRTUAL_SECTOR_COUNT (ESP_PARTITION_EMULATED_FLASH_SIZE / ESP_PARTITION_EMULATED_SECTOR_SIZE)
TEST(partition_api, test_partition_mmap_diff_size)
{
// Scenario: default temporary flash file, explicitly specified size and file with partition table
// Check the size of "storage" partition. Should be 6M = 6*1024*1024
// 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));
p_file_mmap_ctrl->flash_file_size = 0x800000; // 8MB
strlcpy(p_file_mmap_ctrl->partition_file_name, "./build/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);
// Check partition size
size_t exp_size = 0x600000; // 6MB
size_t act_size = partition_data->size;
TEST_ASSERT_EQUAL(exp_size, act_size);
// cleanup after test
esp_partition_file_munmap();
}
TEST(partition_api, test_partition_mmap_reopen)
{
// Scenario: default temporary flash file, write some data
// Remember name of temporary file, reset remove flag, unmmap file.
// Set file name from previous step, mmap file, read and compare data
// 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));
// 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);
const char *test_string = "Is HAL6000 an IBM6000 ?";
size_t test_string_len = strlen(test_string) + 1;
// write test string
esp_err_t err = esp_partition_write(partition_data, 0, test_string, test_string_len);
TEST_ESP_OK(err);
// remember memory mapped file name
esp_partition_file_mmap_ctrl_t *p_file_mmap_ctrl_act = esp_partition_get_file_mmap_ctrl_act();
TEST_ASSERT_NOT_NULL(p_file_mmap_ctrl_act);
char generated_file_name[PATH_MAX];
strlcpy(generated_file_name, p_file_mmap_ctrl_act->flash_file_name, sizeof(generated_file_name));
// ensure remove flag is not set
p_file_mmap_ctrl_input->remove_dump = false;
// unmap
err = esp_partition_file_munmap();
TEST_ESP_OK(err);
// initialize control struct with memory mapped file name
memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input));
strlcpy(p_file_mmap_ctrl_input->flash_file_name, generated_file_name, sizeof(p_file_mmap_ctrl_input->flash_file_name));
// get partiton
partition_data = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
TEST_ASSERT_NOT_NULL(partition_data);
// read verify string
char *verify_string = malloc(test_string_len);
err = esp_partition_read(partition_data, 0, verify_string, test_string_len);
TEST_ESP_OK(err);
// compare strings
bool strings_equal = (strncmp(test_string, verify_string, test_string_len) == 0);
TEST_ASSERT_EQUAL(strings_equal,true);
free(verify_string);
// cleanup after test
esp_partition_file_munmap();
}
TEST(partition_api, test_partition_mmap_remove)
{
// Scenario: default temporary flash file, write some data
// Remember name of temporary file, set remove flag, unmmap file.
// Set file name from previous step, try to get partition "storage", should fail with NULL returned
// 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));
// 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);
const char *test_string = "This text should dismiss after esp_partition_file_munmap";
size_t test_string_len = strlen(test_string) + 1;
// write test string
esp_err_t err = esp_partition_write(partition_data, 0, test_string, test_string_len);
TEST_ESP_OK(err);
// remember memory mapped file name
esp_partition_file_mmap_ctrl_t *p_file_mmap_ctrl_act = esp_partition_get_file_mmap_ctrl_act();
TEST_ASSERT_NOT_NULL(p_file_mmap_ctrl_act);
char generated_file_name[PATH_MAX];
strlcpy(generated_file_name, p_file_mmap_ctrl_act->flash_file_name, sizeof(generated_file_name));
// ensure remove flag is set
p_file_mmap_ctrl_input->remove_dump = true;
// unmap
err = esp_partition_file_munmap();
TEST_ESP_OK(err);
// initialize control struct with memory mapped file name
memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input));
strlcpy(p_file_mmap_ctrl_input->flash_file_name, generated_file_name, sizeof(p_file_mmap_ctrl_input->flash_file_name));
// get partiton, should fail with NULL returned
partition_data = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage");
TEST_ASSERT_EQUAL(NULL, partition_data);
// cleanup after test
esp_partition_file_munmap();
memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input));
}
TEST(partition_api, test_partition_mmap_name_size)
{
// Negative Scenario: conflicting settings - flash_file_name together with one or both of
// flash_file_size, partition_file_name
// esp_partition_file_mmap should return ESP_ERR_INVALID_ARG
// 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));
const char *flash_file_name = "/tmp/xyz";
strlcpy(p_file_mmap_ctrl_input->flash_file_name, flash_file_name, sizeof(p_file_mmap_ctrl_input->flash_file_name));
p_file_mmap_ctrl_input->flash_file_size = 1; // anything different from 0
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);
// cleanup after test
esp_partition_file_munmap();
memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input));
}
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
// 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));
p_file_mmap_ctrl_input->flash_file_size = 1; // anything different from 0
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);
// cleanup after test
esp_partition_file_munmap();
memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input));
}
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
// 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));
const char *partition_file_name = "/tmp/xyz.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);
// cleanup after test
esp_partition_file_munmap();
memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input));
}
TEST(partition_api, test_partition_mmap_ffile_nf)
{
// Negative Scenario: specified flash_file_name file not found
// esp_partition_file_mmap should return ESP_ERR_NOT_FOUND
// 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));
const char *flash_file_name = "/tmp/strangefilename.tmp";
// make sure file doesn't exist
if (access(flash_file_name, F_OK) == 0) {
// our strange file exists, skip rest of test
} else {
strlcpy(p_file_mmap_ctrl_input->flash_file_name, flash_file_name, sizeof(p_file_mmap_ctrl_input->flash_file_name));
const uint8_t *p_mem_block = NULL;
esp_err_t err = esp_partition_file_mmap(&p_mem_block);
// expected result is file not found
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, err);
// cleanup after test
esp_partition_file_munmap();
memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input));
}
}
TEST(partition_api, test_partition_mmap_pfile_nf)
{
// Negative Scenario: specified partition_file_name file not found
// esp_partition_file_mmap should return ESP_ERR_NOT_FOUND
// 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));
const char *partition_file_name = "/tmp/strangefilename.tmp";
// make sure file doesn't exist
if (access(partition_file_name, F_OK) == 0) {
// our strange file exists, skip rest of test
} else {
strlcpy(p_file_mmap_ctrl_input->partition_file_name, partition_file_name, sizeof(p_file_mmap_ctrl_input->partition_file_name));
p_file_mmap_ctrl_input->flash_file_size = 0x10000; // any non zero value to pass validation
const uint8_t *p_mem_block = NULL;
esp_err_t err = esp_partition_file_mmap(&p_mem_block);
// expected result is file not found
TEST_ASSERT_EQUAL(ESP_ERR_NOT_FOUND, err);
// cleanup after test
esp_partition_file_munmap();
memset(p_file_mmap_ctrl_input, 0, sizeof(*p_file_mmap_ctrl_input));
}
}
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/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
{
@ -154,9 +483,24 @@ typedef struct
size_t read_bytes;
size_t write_bytes;
size_t total_time;
size_t sector_erase_count[EMULATED_VIRTUAL_SECTOR_COUNT];
size_t *sector_erase_count;
size_t sector_erase_count_size;
} t_stats;
void init_stats(t_stats *p_stats)
{
memset(p_stats, 0, sizeof(t_stats));
p_stats->sector_erase_count_size = esp_partition_get_file_mmap_ctrl_act()->flash_file_size / ESP_PARTITION_EMULATED_SECTOR_SIZE;
p_stats->sector_erase_count = calloc(p_stats->sector_erase_count_size, sizeof(size_t));
}
void dispose_stats(t_stats *p_stats)
{
if(p_stats->sector_erase_count != NULL){
free(p_stats->sector_erase_count);
}
}
void print_stats(const t_stats *p_stats)
{
ESP_LOGI(TAG, "read_ops:%06lu write_ops:%06lu erase_ops:%06lu read_bytes:%06lu write_bytes:%06lu total_time:%06lu\n",
@ -177,7 +521,7 @@ void read_stats(t_stats *p_stats)
p_stats->write_bytes = esp_partition_get_write_bytes();
p_stats->total_time = esp_partition_get_total_time();
for(size_t i = 0; i < EMULATED_VIRTUAL_SECTOR_COUNT; i++)
for(size_t i = 0; i < p_stats->sector_erase_count_size; i++)
p_stats->sector_erase_count[i] = esp_partition_get_sector_erase_count(i);
}
@ -198,7 +542,7 @@ bool evaluate_stats(const t_stats *p_initial_stats, const t_stats *p_final_stats
if(p_expected_difference_stats->total_time != SIZE_MAX)
TEST_ASSERT_EQUAL(p_initial_stats->total_time + p_expected_difference_stats->total_time, p_final_stats->total_time);
for(size_t i = 0; i < EMULATED_VIRTUAL_SECTOR_COUNT; i++)
for(size_t i = 0; i < p_initial_stats->sector_erase_count_size; i++)
{
if(p_expected_difference_stats->sector_erase_count[i] != SIZE_MAX)
{
@ -220,7 +564,11 @@ TEST(partition_api, test_partition_stats)
t_stats initial_stats;
t_stats final_stats;
t_stats zero_stats = {0};
t_stats zero_stats;
init_stats(&initial_stats);
init_stats(&final_stats);
init_stats(&zero_stats);
// get actual statistics
read_stats(&initial_stats);
@ -256,15 +604,16 @@ TEST(partition_api, test_partition_stats)
if((non_aligned_portions % ESP_PARTITION_EMULATED_SECTOR_SIZE) > 0)
erase_ops += 1;
t_stats expected_difference_stats = {
.read_ops = 1,
.write_ops = 1,
.erase_ops = erase_ops,
.read_bytes = size,
.write_bytes = size,
.total_time = SIZE_MAX
};
for (size_t i = 0; i < EMULATED_VIRTUAL_SECTOR_COUNT; i++)
t_stats expected_difference_stats;
init_stats(&expected_difference_stats);
expected_difference_stats.read_ops = 1;
expected_difference_stats.write_ops = 1;
expected_difference_stats.erase_ops = erase_ops;
expected_difference_stats.read_bytes = size;
expected_difference_stats.write_bytes = size;
expected_difference_stats.total_time = SIZE_MAX;
for (size_t i = 0; i < expected_difference_stats.sector_erase_count_size; i++)
expected_difference_stats.sector_erase_count[i] = SIZE_MAX;
evaluate_stats(&initial_stats, &final_stats, &expected_difference_stats);
@ -276,15 +625,20 @@ TEST(partition_api, test_partition_stats)
// evaluate zero statistics
evaluate_stats(&zero_stats, &final_stats, &zero_stats);
// free symanically allocated space
dispose_stats(&initial_stats);
dispose_stats(&final_stats);
dispose_stats(&zero_stats);
dispose_stats(&expected_difference_stats);
free(test_data_ptr);
}
TEST(partition_api, test_partition_wear_emulation)
TEST(partition_api, test_partition_power_off_emulation)
{
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);
// no offset, map whole partition
//no offset, map whole partition
size_t offset = 0;
size_t size = partition_data->size;
@ -293,9 +647,9 @@ TEST(partition_api, test_partition_wear_emulation)
TEST_ASSERT_NOT_NULL(test_data_ptr);
memset(test_data_ptr, 0xff, size);
// --- wear off ---
// ensure wear emulation is off
esp_partition_fail_after(SIZE_MAX);
// --- power-off off ---
// ensure power-off emulation is off
esp_partition_fail_after(SIZE_MAX, 0);
// erase partition data
esp_err_t err = esp_partition_erase_range(partition_data, offset, size);
@ -309,40 +663,39 @@ TEST(partition_api, test_partition_wear_emulation)
err = esp_partition_erase_range(partition_data, offset, size);
TEST_ESP_OK(err);
// --- wear on, write ---
// ensure wear emulation is on, below the limit for size
// esp_partition_write consumes one wear cycle per 4 bytes written
esp_partition_fail_after(size / 4 - 1);
// --- power-off on, write ---
// ensure power-off emulation is on, below the limit for size
// esp_partition_write consumes one power off failure cycle per 4 bytes written
esp_partition_fail_after(size / 4 - 1, ESP_PARTITION_FAIL_AFTER_MODE_BOTH);
// write data - should fail
err = esp_partition_write(partition_data, offset, test_data_ptr, size);
TEST_ASSERT_EQUAL(ESP_FAIL, err);
// --- wear on, erase has just enough wear cycles available---
// ensure wear emulation is on, at the limit for size
// esp_partition_erase_range consumes one wear cycle per one virtual sector erased
esp_partition_fail_after(size / ESP_PARTITION_EMULATED_SECTOR_SIZE);
// --- power-off on, erase has just enough power off failure cycles available---
// ensure power-off emulation is on, at the limit for size
// esp_partition_erase_range consumes one power-off emulation cycle per one virtual sector erased
esp_partition_fail_after(size / ESP_PARTITION_EMULATED_SECTOR_SIZE, ESP_PARTITION_FAIL_AFTER_MODE_BOTH);
// write data - should be ok
err = esp_partition_erase_range(partition_data, offset, size);
TEST_ASSERT_EQUAL(ESP_OK, err);
// --- wear on, erase has one cycle less than required---
// ensure wear emulation is on, below the limit for size
// esp_partition_erase_range consumes one wear cycle per one virtual sector erased
esp_partition_fail_after(size / ESP_PARTITION_EMULATED_SECTOR_SIZE - 1);
// --- power-off on, erase has one cycle less than required---
// ensure power-off emulation is on, below the limit for size
// esp_partition_erase_range consumes one power-off emulation cycle per one virtual sector erased
esp_partition_fail_after(size / ESP_PARTITION_EMULATED_SECTOR_SIZE - 1, ESP_PARTITION_FAIL_AFTER_MODE_BOTH);
// write data - should fail
err = esp_partition_erase_range(partition_data, offset, size);
TEST_ASSERT_EQUAL(ESP_FAIL, err);
// ---cleanup ---
// disable wear emulation
esp_partition_fail_after(SIZE_MAX);
// disable power-off emulation
esp_partition_fail_after(SIZE_MAX, 0);
free(test_data_ptr);
}
TEST_GROUP_RUNNER(partition_api)
{
RUN_TEST_CASE(partition_api, test_partition_find_basic);
@ -351,8 +704,17 @@ 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_diff_size);
RUN_TEST_CASE(partition_api, test_partition_mmap_reopen);
RUN_TEST_CASE(partition_api, test_partition_mmap_remove);
RUN_TEST_CASE(partition_api, test_partition_mmap_name_size);
RUN_TEST_CASE(partition_api, test_partition_mmap_size_no_partition);
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_wear_emulation);
RUN_TEST_CASE(partition_api, test_partition_power_off_emulation);
}
static void run_all_tests(void)

View File

@ -0,0 +1,6 @@
# 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,
storage, data, , , 6M,
1 # Name, Type, SubType, Offset, Size, Flags
2 # Note: if you have increased the bootloader size, make sure to update the offsets to avoid overlap
3 nvs, data, nvs, 0x9000, 0x6000,
4 phy_init, data, phy, 0xf000, 0x1000,
5 factory, app, factory, 0x10000, 1M,
6 storage, data, , , 6M,

View File

@ -1,4 +1,5 @@
CONFIG_IDF_TARGET="linux"
IDF_TARGET_LINUX=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y
CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=n
CONFIG_UNITY_ENABLE_FIXTURE=y

View File

@ -8,6 +8,7 @@
#include <stdint.h>
#include <stdbool.h>
#include <limits.h>
#include "esp_err.h"
#ifdef __cplusplus
@ -24,7 +25,12 @@ extern "C" {
#define ESP_PARTITION_EMULATED_SECTOR_SIZE 0x1000
/** @brief emulated whole flash size for the partition API on Linux */
#define ESP_PARTITION_EMULATED_FLASH_SIZE 0x400000 //4MB fixed
#define ESP_PARTITION_DEFAULT_EMULATED_FLASH_SIZE 0x400000 //4MB fixed
/** @brief mode of fail after */
#define ESP_PARTITION_FAIL_AFTER_MODE_ERASE 0x01
#define ESP_PARTITION_FAIL_AFTER_MODE_WRITE 0x02
#define ESP_PARTITION_FAIL_AFTER_MODE_BOTH 0x03
/**
* @brief Partition type to string conversion routine
@ -166,21 +172,22 @@ size_t esp_partition_get_write_bytes(void);
size_t esp_partition_get_total_time(void);
/**
* @brief Initializes emulation of failure caused by wear on behalf of write/erase operations
* @brief Initializes emulation of lost power failure in write/erase operations
*
* Function initializes down counter emulating remaining write / erase cycles.
* Once this counter reaches 0, emulation of all subsequent write / erase operations fails
* Function initializes down counter emulating power off failure during write / erase operations.
* Once this counter reaches 0, actual as well as all subsequent write / erase operations fail
* Initial state of down counter is disabled.
*
* @param[in] count Number of remaining write / erase cycles before failure. Call with SIZE_MAX to disable simulation of flash wear.
* @param[in] count Number of remaining write / erase cycles before emulated failure. Call with SIZE_MAX to disable failure emulation.
* @param[in] mode Controls whether remaining cycles are applied to erase, write or both operations
*
*/
void esp_partition_fail_after(size_t count);
void esp_partition_fail_after(size_t count, uint8_t mode);
/**
* @brief Returns count of erase operations performed on virtual emulated sector
*
* Function returns number of erase operatinos performed on virtual sector specified by the parameter sector.
* Function returns number of erase operations performed on virtual sector specified by the parameter sector.
* The esp_parttion mapped address space is virtually split into sectors of the size ESP_PARTITION_EMULATED_SECTOR_SIZE.
* Calls to the esp_partition_erase_range are impacting one or multiple virtual sectors, for each of them, the respective
* count is incremented.
@ -193,6 +200,46 @@ void esp_partition_fail_after(size_t count);
*/
size_t esp_partition_get_sector_erase_count(size_t sector);
typedef struct {
char flash_file_name[PATH_MAX]; /*!< name of flash dump file, zero-terminated ASCII string */
size_t flash_file_size; /*!< size of flash dump file in bytes */
char partition_file_name[PATH_MAX]; /*!< name of file containing binary representation of partition table, zero-terminated ASCII string */
bool remove_dump; /*!< flag is set to true if dump file has to be removed after esp_partition_file_munmap */
} esp_partition_file_mmap_ctrl_t;
/**
* @brief Returns pointer to the structure controlling mapping of flash file
*
* Function returns pointer to structure used by esp_partition_file_mmap and esp_partition_file_munmap
* Caller can change this structure members prior calls involving the above functions to
* Specify existing flash file which will represent the content of flash memory after mapping
* Specify size and partition file name used to create empty flash memory
* Control whether the actual flash file will be deleted of kept after call to esp_partition_file_munmap
*
* @return
* - pointer to flash file mapping control structure
*
*/
esp_partition_file_mmap_ctrl_t* esp_partition_get_file_mmap_ctrl_input(void);
/**
* @brief Returns pointer to the structure reflecting actual settings of flash file emulation
*
* Function returns pointer to structure containing:
* flash file name representing emulated flash memory
* size of file representing emulated flash memory
* file name holding binary used to initialize partition table (if it was used)
*
* @return
* - pointer to flash file mapping actuall values in control structure
*
*/
esp_partition_file_mmap_ctrl_t* esp_partition_get_file_mmap_ctrl_act(void);
// private function in partition.c to unload partitions and free space allocated by them
void unload_partitions(void);
#ifdef __cplusplus
}
#endif

View File

@ -225,6 +225,19 @@ static esp_err_t load_partitions(void)
return err;
}
void unload_partitions(void)
{
_lock_acquire(&s_partition_list_lock);
partition_list_item_t *it;
SLIST_FOREACH(it, &s_partition_list, next) {
SLIST_REMOVE(&s_partition_list, it, partition_list_item_, next);
free(it);
}
_lock_release(&s_partition_list_lock);
assert(SLIST_EMPTY(&s_partition_list));
}
static esp_err_t ensure_partitions_loaded(void)
{
esp_err_t err = ESP_OK;

View File

@ -7,8 +7,13 @@
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#if __has_include(<bsd/string.h>)
// for strlcpy
#include <bsd/string.h>
#endif
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <limits.h>
#include <errno.h>
@ -19,21 +24,29 @@
#include "esp_log.h"
static const char *TAG = "linux_spiflash";
static void *s_spiflash_mem_file_buf = NULL;
static int s_spiflash_mem_file_fd = -1;
static const esp_partition_mmap_handle_t s_default_partition_mmap_handle = 0;
// input control structure, always contains what was specified by caller
static esp_partition_file_mmap_ctrl_t s_esp_partition_file_mmap_ctrl_input = {0};
// actual control structure, contains what is actually used by the esp_partition
static esp_partition_file_mmap_ctrl_t s_esp_partition_file_mmap_ctrl_act = {0};
#ifdef CONFIG_ESP_PARTITION_ENABLE_STATS
// variables holding stats and controlling wear emulation
size_t s_esp_partition_stat_read_ops = 0;
size_t s_esp_partition_stat_write_ops = 0;
size_t s_esp_partition_stat_read_bytes = 0;
size_t s_esp_partition_stat_write_bytes = 0;
size_t s_esp_partition_stat_erase_ops = 0;
size_t s_esp_partition_stat_total_time = 0;
size_t s_esp_partition_emulated_flash_life = SIZE_MAX;
// variables holding stats and controlling power-off emulation
static size_t s_esp_partition_stat_read_ops = 0;
static size_t s_esp_partition_stat_write_ops = 0;
static size_t s_esp_partition_stat_read_bytes = 0;
static size_t s_esp_partition_stat_write_bytes = 0;
static size_t s_esp_partition_stat_erase_ops = 0;
static size_t s_esp_partition_stat_total_time = 0;
static size_t s_esp_partition_emulated_power_off_counter = SIZE_MAX;
static uint8_t s_esp_partition_emulated_power_off_mode = 0;
// tracking erase count individually for each emulated sector
size_t s_esp_partition_stat_sector_erase_count[ESP_PARTITION_EMULATED_FLASH_SIZE / ESP_PARTITION_EMULATED_SECTOR_SIZE] = {0};
static size_t *s_esp_partition_stat_sector_erase_count = NULL;
// forward declaration of hooks
static void esp_partition_hook_read(const void *srcAddr, const size_t size);
@ -86,71 +99,191 @@ const char *esp_partition_subtype_to_str(const uint32_t type, const uint32_t sub
esp_err_t esp_partition_file_mmap(const uint8_t **part_desc_addr_start)
{
//create temporary file to hold complete SPIFLASH size
char temp_spiflash_mem_file_name[PATH_MAX] = {"/tmp/idf-partition-XXXXXX"};
int spiflash_mem_file_fd = mkstemp(temp_spiflash_mem_file_name);
// temporary file is used only if control structure doesn't specify file name.
bool open_existing_file = false;
if (spiflash_mem_file_fd == -1) {
ESP_LOGE(TAG, "Failed to create SPI FLASH emulation file %s: %s", temp_spiflash_mem_file_name, strerror(errno));
return ESP_ERR_NOT_FINISHED;
if(strlen(s_esp_partition_file_mmap_ctrl_input.flash_file_name) > 0) {
// Open existing file. If size or partition table file were specified, raise errors
if(s_esp_partition_file_mmap_ctrl_input.flash_file_size > 0) {
ESP_LOGE(TAG, "Flash emulation file size: %u was specified while together with the file name: %s (illegal). Use file size = 0",
s_esp_partition_file_mmap_ctrl_input.flash_file_size,
s_esp_partition_file_mmap_ctrl_input.flash_file_name);
return ESP_ERR_INVALID_ARG;
}
if(strlen(s_esp_partition_file_mmap_ctrl_input.partition_file_name) > 0) {
ESP_LOGE(TAG, "Partition file name: %s was specified together with the flash emulation file name: %s (illegal). Use empty partition file name",
s_esp_partition_file_mmap_ctrl_input.partition_file_name,
s_esp_partition_file_mmap_ctrl_input.flash_file_name);
return ESP_ERR_INVALID_ARG;
}
// copy flash file name to actual control struct
strlcpy(s_esp_partition_file_mmap_ctrl_act.flash_file_name, s_esp_partition_file_mmap_ctrl_input.flash_file_name, sizeof(s_esp_partition_file_mmap_ctrl_act.flash_file_name));
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
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: %u was specified. Use either both parameters or none.",
s_esp_partition_file_mmap_ctrl_input.partition_file_name,
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/partition_table/partition-table.bin", sizeof(s_esp_partition_file_mmap_ctrl_act.partition_file_name));
} else {
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 {
s_esp_partition_file_mmap_ctrl_act.flash_file_size = s_esp_partition_file_mmap_ctrl_input.flash_file_size;
}
// specify pattern file name for temporary flash file
strlcpy(s_esp_partition_file_mmap_ctrl_act.flash_file_name, "/tmp/idf-partition-XXXXXX", sizeof(s_esp_partition_file_mmap_ctrl_act.flash_file_name));
}
if (ftruncate(spiflash_mem_file_fd, ESP_PARTITION_EMULATED_FLASH_SIZE) != 0) {
ESP_LOGE(TAG, "Failed to set size of SPI FLASH memory emulation file %s: %s", temp_spiflash_mem_file_name, strerror(errno));
return ESP_ERR_INVALID_SIZE;
esp_err_t ret = ESP_OK;
if(open_existing_file) {
s_spiflash_mem_file_fd = open(s_esp_partition_file_mmap_ctrl_act.flash_file_name, O_RDWR);
if (s_spiflash_mem_file_fd == -1) {
ESP_LOGE(TAG, "Failed to open SPI FLASH emulation file %s: %s", s_esp_partition_file_mmap_ctrl_act.flash_file_name, strerror(errno));
return ESP_ERR_NOT_FOUND;
}
do {
// seek to the end
off_t size = lseek(s_spiflash_mem_file_fd, 0L, SEEK_END);
if(size < 0) {
ESP_LOGE(TAG, "Failed to seek in SPI FLASH emulation file %s: %s", s_esp_partition_file_mmap_ctrl_act.flash_file_name, strerror(errno));
ret = ESP_ERR_NOT_FINISHED;
break;
}
s_esp_partition_file_mmap_ctrl_act.flash_file_size = size;
// seek to beginning
size = lseek(s_spiflash_mem_file_fd, 0L, SEEK_SET);
if(size < 0) {
ESP_LOGE(TAG, "Failed to seek in SPI FLASH emulation file %s: %s", s_esp_partition_file_mmap_ctrl_act.flash_file_name, strerror(errno));
ret = ESP_ERR_NOT_FINISHED;
break;
}
//create memory-mapping for the flash holder file
if ((s_spiflash_mem_file_buf = mmap(NULL, s_esp_partition_file_mmap_ctrl_act.flash_file_size, PROT_READ | PROT_WRITE, MAP_SHARED, s_spiflash_mem_file_fd, 0)) == MAP_FAILED) {
ESP_LOGE(TAG, "Failed to mmap() SPI FLASH memory emulation file %s: %s", s_esp_partition_file_mmap_ctrl_act.flash_file_name, strerror(errno));
ret = ESP_ERR_NOT_FINISHED;
break;
}
} while(false);
} else {
//create temporary file to hold complete SPIFLASH size
s_spiflash_mem_file_fd = mkstemp(s_esp_partition_file_mmap_ctrl_act.flash_file_name);
if (s_spiflash_mem_file_fd == -1) {
ESP_LOGE(TAG, "Failed to create SPI FLASH emulation file %s: %s", s_esp_partition_file_mmap_ctrl_act.flash_file_name, strerror(errno));
return ESP_ERR_NOT_FINISHED;
}
do {
// resize file
if (ftruncate(s_spiflash_mem_file_fd, s_esp_partition_file_mmap_ctrl_act.flash_file_size) != 0) {
ESP_LOGE(TAG, "Failed to set size of SPI FLASH memory emulation file %s: %s", s_esp_partition_file_mmap_ctrl_act.flash_file_name, strerror(errno));
ret = ESP_ERR_INVALID_SIZE;
break;
}
ESP_LOGV(TAG, "SPIFLASH memory emulation file created: %s (size: %d B)", s_esp_partition_file_mmap_ctrl_act.flash_file_name, s_esp_partition_file_mmap_ctrl_act.flash_file_size);
// create memory-mapping for the flash holder file
if ((s_spiflash_mem_file_buf = mmap(NULL, s_esp_partition_file_mmap_ctrl_act.flash_file_size, PROT_READ | PROT_WRITE, MAP_SHARED, s_spiflash_mem_file_fd, 0)) == MAP_FAILED) {
ESP_LOGE(TAG, "Failed to mmap() SPI FLASH memory emulation file %s: %s", s_esp_partition_file_mmap_ctrl_act.flash_file_name, strerror(errno));
ret = ESP_ERR_NO_MEM;
break;
}
// initialize whole range with bit-1 (NOR FLASH default)
memset(s_spiflash_mem_file_buf, 0xFF, s_esp_partition_file_mmap_ctrl_act.flash_file_size);
// upload partition table to the mmap file at real offset as in SPIFLASH
FILE *f_partition_table = fopen(s_esp_partition_file_mmap_ctrl_act.partition_file_name, "r+");
if (f_partition_table == NULL) {
ESP_LOGE(TAG, "Failed to open partition table file %s: %s", s_esp_partition_file_mmap_ctrl_act.partition_file_name, strerror(errno));
ret = ESP_ERR_NOT_FOUND;
break;
}
if (fseek(f_partition_table, 0L, SEEK_END) != 0) {
ESP_LOGE(TAG, "Failed to seek in partition table file %s: %s", s_esp_partition_file_mmap_ctrl_act.partition_file_name, strerror(errno));
ret = ESP_ERR_INVALID_SIZE;
break;
}
int partition_table_file_size = ftell(f_partition_table);
ESP_LOGV(TAG, "Using partition table file %s (size: %d B):", s_esp_partition_file_mmap_ctrl_act.partition_file_name, partition_table_file_size);
// check whether partition table fits into the memory mapped file
if(partition_table_file_size + ESP_PARTITION_TABLE_OFFSET > s_esp_partition_file_mmap_ctrl_act.flash_file_size) {
ESP_LOGE(TAG, "Flash file: %s (size: %d B) cannot hold partition table requiring %d B",
s_esp_partition_file_mmap_ctrl_act.flash_file_name,
s_esp_partition_file_mmap_ctrl_act.flash_file_size,
partition_table_file_size + ESP_PARTITION_TABLE_OFFSET);
ret = ESP_ERR_INVALID_SIZE;
break;
}
//copy partition table from the file to emulated SPIFLASH memory space
if (fseek(f_partition_table, 0L, SEEK_SET) != 0) {
ESP_LOGE(TAG, "Failed to seek in partition table file %s: %s", s_esp_partition_file_mmap_ctrl_act.partition_file_name, strerror(errno));
ret = ESP_ERR_INVALID_SIZE;
break;
}
uint8_t *part_table_in_spiflash = s_spiflash_mem_file_buf + ESP_PARTITION_TABLE_OFFSET;
size_t res = fread(part_table_in_spiflash, 1, partition_table_file_size, f_partition_table);
fclose(f_partition_table);
if (res != partition_table_file_size) {
ESP_LOGE(TAG, "Failed to read partition table file %s", s_esp_partition_file_mmap_ctrl_act.partition_file_name);
ret = ESP_ERR_INVALID_STATE;
break;
}
} while(false);
}
ESP_LOGV(TAG, "SPIFLASH memory emulation file created: %s (size: %d B)", temp_spiflash_mem_file_name, ESP_PARTITION_EMULATED_FLASH_SIZE);
if (ret != ESP_OK) {
if(close(s_spiflash_mem_file_fd)) {
ESP_LOGE(TAG, "Failed to close() SPIFLASH memory emulation file: %s", strerror(errno));
}
s_spiflash_mem_file_fd = -1;
//create memory-mapping for the partitions holder file
if ((s_spiflash_mem_file_buf = mmap(NULL, ESP_PARTITION_EMULATED_FLASH_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, spiflash_mem_file_fd, 0)) == MAP_FAILED) {
ESP_LOGE(TAG, "Failed to mmap() SPI FLASH memory emulation file: %s", strerror(errno));
return ESP_ERR_NO_MEM;
}
//initialize whole range with bit-1 (NOR FLASH default)
memset(s_spiflash_mem_file_buf, 0xFF, ESP_PARTITION_EMULATED_FLASH_SIZE);
//upload partition table to the mmap file at real offset as in SPIFLASH
const char *partition_table_file_name = "build/partition_table/partition-table.bin";
FILE *f_partition_table = fopen(partition_table_file_name, "r+");
if (f_partition_table == NULL) {
ESP_LOGE(TAG, "Failed to open partition table file %s: %s", partition_table_file_name, strerror(errno));
return ESP_ERR_NOT_FOUND;
}
if (fseek(f_partition_table, 0L, SEEK_END) != 0) {
ESP_LOGE(TAG, "Failed to seek in partition table file %s: %s", partition_table_file_name, strerror(errno));
return ESP_ERR_INVALID_SIZE;
}
int partition_table_file_size = ftell(f_partition_table);
ESP_LOGV(TAG, "Using partition table file %s (size: %d B):", partition_table_file_name, partition_table_file_size);
uint8_t *part_table_in_spiflash = s_spiflash_mem_file_buf + ESP_PARTITION_TABLE_OFFSET;
//copy partition table from the file to emulated SPIFLASH memory space
if (fseek(f_partition_table, 0L, SEEK_SET) != 0) {
ESP_LOGE(TAG, "Failed to seek in partition table file %s: %s", partition_table_file_name, strerror(errno));
return ESP_ERR_INVALID_SIZE;
}
size_t res = fread(part_table_in_spiflash, 1, partition_table_file_size, f_partition_table);
fclose(f_partition_table);
if (res != partition_table_file_size) {
ESP_LOGE(TAG, "Failed to read partition table file %s", partition_table_file_name);
return ESP_ERR_INVALID_STATE;
return ret;
}
#ifdef CONFIG_LOG_DEFAULT_LEVEL_VERBOSE
uint8_t *part_ptr = part_table_in_spiflash;
uint8_t *part_end_ptr = part_table_in_spiflash + partition_table_file_size;
uint8_t *part_ptr = s_spiflash_mem_file_buf + ESP_PARTITION_TABLE_OFFSET;
ESP_LOGV(TAG, "");
ESP_LOGV(TAG, "Partition table sucessfully imported, partitions found:");
while (part_ptr < part_end_ptr) {
while (true) {
esp_partition_info_t *p_part_item = (esp_partition_info_t *)part_ptr;
if (p_part_item->magic != ESP_PARTITION_MAGIC ) {
break;
@ -170,9 +303,17 @@ esp_err_t esp_partition_file_mmap(const uint8_t **part_desc_addr_start)
ESP_LOGV(TAG, "");
#endif
#ifdef CONFIG_ESP_PARTITION_ENABLE_STATS
free(s_esp_partition_stat_sector_erase_count);
s_esp_partition_stat_sector_erase_count = malloc(sizeof(size_t) * s_esp_partition_file_mmap_ctrl_act.flash_file_size / ESP_PARTITION_EMULATED_SECTOR_SIZE);
#endif
//return mmapped file starting address
*part_desc_addr_start = s_spiflash_mem_file_buf;
// clear input control structure
memset(&s_esp_partition_file_mmap_ctrl_input, 0, sizeof(s_esp_partition_file_mmap_ctrl_input));
return ESP_OK;
}
@ -181,26 +322,54 @@ esp_err_t esp_partition_file_munmap(void)
if (s_spiflash_mem_file_buf == NULL) {
return ESP_ERR_NO_MEM;
}
if (ESP_PARTITION_EMULATED_FLASH_SIZE == 0) {
if (s_esp_partition_file_mmap_ctrl_act.flash_file_size == 0) {
return ESP_ERR_INVALID_SIZE;
}
if(s_spiflash_mem_file_fd == -1) {
return ESP_ERR_NOT_FOUND;
}
if (munmap(s_spiflash_mem_file_buf, ESP_PARTITION_EMULATED_FLASH_SIZE) != 0) {
ESP_LOGE(TAG, "Failed to munmap() SPI FLASH memory emulation file: %s", strerror(errno));
unload_partitions();
#ifdef CONFIG_ESP_PARTITION_ENABLE_STATS
free(s_esp_partition_stat_sector_erase_count);
s_esp_partition_stat_sector_erase_count = NULL;
#endif
// unmap the flash emulation memory file
if (munmap(s_spiflash_mem_file_buf, s_esp_partition_file_mmap_ctrl_act.flash_file_size) != 0) {
ESP_LOGE(TAG, "Failed to munmap() SPIFLASH memory emulation file %s: %s", s_esp_partition_file_mmap_ctrl_act.flash_file_name, strerror(errno));
return ESP_ERR_INVALID_RESPONSE;
}
// close memory mapped file
if(close(s_spiflash_mem_file_fd)) {
ESP_LOGE(TAG, "Failed to close() SPIFLASH memory emulation file %s: %s", s_esp_partition_file_mmap_ctrl_act.flash_file_name, strerror(errno));
return ESP_ERR_INVALID_RESPONSE;
}
if(s_esp_partition_file_mmap_ctrl_input.remove_dump) {
// delete spi flash file
if(remove(s_esp_partition_file_mmap_ctrl_act.flash_file_name) != 0) {
ESP_LOGE(TAG, "Failed to remove() SPI FLASH memory emulation file %s: %s", s_esp_partition_file_mmap_ctrl_act.flash_file_name, strerror(errno));
return ESP_ERR_INVALID_RESPONSE;
}
}
// cleanup
memset(&s_esp_partition_file_mmap_ctrl_act, 0, sizeof(s_esp_partition_file_mmap_ctrl_act));
s_spiflash_mem_file_buf = NULL;
s_spiflash_mem_file_fd = -1;
return ESP_OK;
}
esp_err_t esp_partition_write(const esp_partition_t *partition, size_t dst_offset, const void *src, size_t size)
{
assert(partition != NULL);
assert(partition != NULL && s_spiflash_mem_file_buf != NULL);
if (partition->encrypted) {
return ESP_ERR_NOT_SUPPORTED;
return ESP_ERR_NOT_SUPPORTED;
}
if (dst_offset > partition->size) {
return ESP_ERR_INVALID_ARG;
@ -217,8 +386,9 @@ esp_err_t esp_partition_write(const esp_partition_t *partition, size_t dst_offse
void *dst_addr = s_spiflash_mem_file_buf + partition->address + dst_offset;
ESP_LOGV(TAG, "esp_partition_write(): partition=%s dst_offset=%zu src=%p size=%zu (real dst address: %p)", partition->label, dst_offset, src, size, dst_addr);
// hook gathers statistics and can emulate limited number of write cycles
// hook gathers statistics and can emulate power-off
if (!ESP_PARTITION_HOOK_WRITE(dst_addr, size)) {
free(write_buf);
return ESP_FAIL;
}
@ -235,7 +405,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);
assert(partition != NULL && s_spiflash_mem_file_buf != NULL);
if (partition->encrypted) {
return ESP_ERR_NOT_SUPPORTED;
@ -248,7 +418,6 @@ esp_err_t esp_partition_read(const esp_partition_t *partition, size_t src_offset
}
void *src_addr = s_spiflash_mem_file_buf + partition->address + src_offset;
ESP_LOGV(TAG, "esp_partition_read(): partition=%s src_offset=%zu dst=%p size=%zu (real src address: %p)", partition->label, src_offset, dst, size, src_addr);
memcpy(dst, src_addr, size);
@ -284,7 +453,7 @@ esp_err_t esp_partition_erase_range(const esp_partition_t *partition, size_t off
void *target_addr = s_spiflash_mem_file_buf + partition->address + offset;
ESP_LOGV(TAG, "esp_partition_erase_range(): partition=%s offset=%zu size=%zu (real target address: %p)", partition->label, offset, size, target_addr);
// hook gathers statistics and can emulate limited number of write/erase cycles
// hook gathers statistics and can emulate power-off
if (!ESP_PARTITION_HOOK_ERASE(target_addr, size)) {
return ESP_FAIL;
}
@ -328,7 +497,6 @@ esp_err_t esp_partition_mmap(const esp_partition_t *partition, size_t offset, si
// check if memory mapped file is already present, if not, map it now
if (s_spiflash_mem_file_buf == NULL) {
ESP_LOGE(TAG, "esp_partition_mmap(): in esp_partition_file_mmap");
uint8_t *part_desc_addr_start = NULL;
rc = esp_partition_file_mmap((const uint8_t **) &part_desc_addr_start);
}
@ -338,16 +506,25 @@ esp_err_t esp_partition_mmap(const esp_partition_t *partition, size_t offset, si
*out_ptr = (void *) (s_spiflash_mem_file_buf + req_flash_addr);
*out_handle = s_default_partition_mmap_handle;
} else {
*out_ptr = (void *) NULL;
*out_ptr = NULL;
*out_handle = 0;
}
return rc;
}
// Intentionally does nothing.
void esp_partition_munmap(esp_partition_mmap_handle_t handle)
void esp_partition_munmap(esp_partition_mmap_handle_t handle __attribute__((unused)))
{
;
}
esp_partition_file_mmap_ctrl_t* esp_partition_get_file_mmap_ctrl_input(void)
{
return &s_esp_partition_file_mmap_ctrl_input;
}
esp_partition_file_mmap_ctrl_t* esp_partition_get_file_mmap_ctrl_act(void)
{
return &s_esp_partition_file_mmap_ctrl_act;
}
#ifdef CONFIG_ESP_PARTITION_ENABLE_STATS
@ -372,7 +549,7 @@ static size_t esp_partition_stat_time_interpolate(uint32_t bytes, size_t *lut)
}
// Registers read access statistics of emulated SPI FLASH device (Linux host)
// Ffunction increases nmuber of read operations, accumulates number of read bytes
// Function increases nmuber of read operations, accumulates number of read bytes
// and accumulates emulated read operation time (size dependent)
static void esp_partition_hook_read(const void *srcAddr, const size_t size)
{
@ -385,40 +562,63 @@ static void esp_partition_hook_read(const void *srcAddr, const size_t size)
}
// Registers write access statistics of emulated SPI FLASH device (Linux host)
// If enabled by the esp_partition_fail_after, function emulates physical limitation of write/erase operations by
// decrementing the s_esp_partition_emulated_life for each 4 bytes written
// If enabled by the esp_partition_fail_after, function emulates power-off event during write/erase operations by
// decrementing the s_esp_partition_emulated_power_off_counter for each 4 bytes written
// If zero threshold is reached, false is returned.
// Else the function increases nmuber of write operations, accumulates number
// of bytes written and accumulates emulated write operation time (size dependent) and returns true.
static bool esp_partition_hook_write(const void *dstAddr, const size_t size)
{
ESP_LOGV(TAG, "esp_partition_hook_write()");
ESP_LOGV(TAG, "%s", __FUNCTION__);
// wear emulation
// power-off emulation
for (size_t i = 0; i < size / 4; ++i) {
if (s_esp_partition_emulated_flash_life != SIZE_MAX && s_esp_partition_emulated_flash_life-- == 0) {
if (s_esp_partition_emulated_power_off_counter != SIZE_MAX && s_esp_partition_emulated_power_off_counter-- == 0) {
return false;
}
}
bool ret_val = true;
// one power down cycle per 4 bytes written
size_t write_cycles = size / 4;
// check whether power off simulation is active for write
if(s_esp_partition_emulated_power_off_counter != SIZE_MAX &&
s_esp_partition_emulated_power_off_counter & ESP_PARTITION_FAIL_AFTER_MODE_WRITE) {
// check if power down happens during this call
if(s_esp_partition_emulated_power_off_counter >= write_cycles) {
// OK
s_esp_partition_emulated_power_off_counter -= write_cycles;
} else {
// failure in this call - reduce cycle count to the number of remainint power on cycles
write_cycles = s_esp_partition_emulated_power_off_counter;
// clear remaining cycles
s_esp_partition_emulated_power_off_counter = 0;
// final result value will be false
ret_val = false;
}
}
// stats
++s_esp_partition_stat_write_ops;
s_esp_partition_stat_write_bytes += size;
s_esp_partition_stat_total_time += esp_partition_stat_time_interpolate((uint32_t) size, s_esp_partition_stat_write_times);
s_esp_partition_stat_write_bytes += write_cycles * 4;
s_esp_partition_stat_total_time += esp_partition_stat_time_interpolate((uint32_t) (write_cycles * 4), s_esp_partition_stat_write_times);
return true;
return ret_val;
}
// Registers erase access statistics of emulated SPI FLASH device (Linux host)
// If enabled by the esp_partition_fail_after, function emulates physical limitation of write/erase operations by
// decrementing the s_esp_partition_emulated_life for each erased virtual sector.
// If enabled by 'esp_partition_fail_after' parameter, the function emulates a power-off event during write/erase
// operations by decrementing the s_esp_partition_emulated_power_off_counterpower for each erased virtual sector.
// If zero threshold is reached, false is returned.
// Else, for statistics purpose, the impacted virtual sectors are identified based on
// ESP_PARTITION_EMULATED_SECTOR_SIZE and their respective counts of erase operations are incremented
// Total number of erase operations is increased by the number of impacted virtual sectors
static bool esp_partition_hook_erase(const void *dstAddr, const size_t size)
{
ESP_LOGV(TAG, "esp_partition_hook_erase()");
ESP_LOGV(TAG, "%s", __FUNCTION__);
if (size == 0) {
return true;
@ -430,19 +630,34 @@ static bool esp_partition_hook_erase(const void *dstAddr, const size_t size)
size_t last_sector_idx = (offset + size - 1) / ESP_PARTITION_EMULATED_SECTOR_SIZE;
size_t sector_count = 1 + last_sector_idx - first_sector_idx;
for (size_t sector_index = first_sector_idx; sector_index < first_sector_idx + sector_count; sector_index++) {
// wear emulation
if (s_esp_partition_emulated_flash_life != SIZE_MAX && s_esp_partition_emulated_flash_life-- == 0) {
return false;
}
bool ret_val = true;
// stats
// check whether power off simulation is active for erase
if(s_esp_partition_emulated_power_off_counter != SIZE_MAX &&
s_esp_partition_emulated_power_off_counter & ESP_PARTITION_FAIL_AFTER_MODE_ERASE) {
// check if power down happens during this call
if(s_esp_partition_emulated_power_off_counter >= sector_count) {
// OK
s_esp_partition_emulated_power_off_counter -= sector_count;
} else {
// failure in this call - reduce sector_count to the number of remainint power on cycles
sector_count = s_esp_partition_emulated_power_off_counter;
// clear remaining cycles
s_esp_partition_emulated_power_off_counter = 0;
// final result value will be false
ret_val = false;
}
}
// update statistcs for all sectors until power down cycle
for (size_t sector_index = first_sector_idx; sector_index < first_sector_idx + sector_count; sector_index++) {
++s_esp_partition_stat_erase_ops;
s_esp_partition_stat_sector_erase_count[sector_index]++;
s_esp_partition_stat_total_time += s_esp_partition_stat_block_erase_time;
}
return true;
return ret_val;
}
void esp_partition_clear_stats(void)
@ -454,7 +669,7 @@ void esp_partition_clear_stats(void)
s_esp_partition_stat_write_ops = 0;
s_esp_partition_stat_total_time = 0;
memset(s_esp_partition_stat_sector_erase_count, 0, sizeof(s_esp_partition_stat_sector_erase_count));
memset(s_esp_partition_stat_sector_erase_count, 0, sizeof(size_t) * s_esp_partition_file_mmap_ctrl_act.flash_file_size / ESP_PARTITION_EMULATED_SECTOR_SIZE);
}
size_t esp_partition_get_read_ops(void)
@ -487,9 +702,10 @@ size_t esp_partition_get_total_time(void)
return s_esp_partition_stat_total_time;
}
void esp_partition_fail_after(size_t count)
void esp_partition_fail_after(size_t count, uint8_t mode)
{
s_esp_partition_emulated_flash_life = count;
s_esp_partition_emulated_power_off_counter = count;
s_esp_partition_emulated_power_off_mode = mode;
}
size_t esp_partition_get_sector_erase_count(size_t sector)

View File

@ -132,7 +132,6 @@ static void check_spiffs_files(spiffs *fs, const char *base_path, char *cur_path
struct stat sb;
stat(path, &sb);
if (S_ISDIR(sb.st_mode)) {
if (!strcmp(name, ".") || !strcmp(name, "..")) {
continue;
@ -249,7 +248,6 @@ TEST(spiffs, can_read_spiffs_image)
char *img = (char *) malloc(img_size);
TEST_ASSERT(fread(img, 1, img_size, img_file) == img_size);
fclose(img_file);
TEST_ASSERT_TRUE(partition->size == img_size);
esp_partition_erase_range(partition, 0, partition->size);
@ -265,7 +263,7 @@ TEST(spiffs, can_read_spiffs_image)
spiffs_res = SPIFFS_check(&fs);
TEST_ASSERT_TRUE(spiffs_res == SPIFFS_OK);
char path_buf[PATH_MAX];
char path_buf[PATH_MAX] = {0};
// The image is created from the spiffs source directory. Compare the files in that
// directory to the files read from the SPIFFS image.