diff --git a/components/esp_partition/Kconfig b/components/esp_partition/Kconfig new file mode 100644 index 0000000000..d70173b208 --- /dev/null +++ b/components/esp_partition/Kconfig @@ -0,0 +1,10 @@ +menu "ESP_PARTITION" + + config ESP_PARTITION_ENABLE_STATS + bool "Enable esp_partition statistics gathering" + default n + depends on IDF_TARGET_LINUX + help + This option enables statistics gathering and flash wear simulation. Linux only. + +endmenu 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 2f0db43efa..a54d4170f6 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 @@ -13,6 +13,9 @@ #include "unity.h" #include "unity_fixture.h" +#include "esp_log.h" +const char *TAG = "partition_api_test"; + TEST_GROUP(partition_api); @@ -108,6 +111,238 @@ TEST(partition_api, test_partition_ops) TEST_ASSERT_NOT_NULL(verified_partition); } +TEST(partition_api, test_partition_mmap) +{ + 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); + + esp_partition_mmap_memory_t memory = ESP_PARTITION_MMAP_DATA; + void *out_ptr = NULL; + esp_partition_mmap_handle_t out_handle = 0; + + // no offset, complete length + size_t offset = 0; + size_t size = partition_data->size; + + esp_err_t err = esp_partition_mmap(partition_data, offset, size, memory, (const void **) &out_ptr, &out_handle); + TEST_ESP_OK(err); + TEST_ASSERT_NOT_NULL(out_ptr); + esp_partition_munmap(out_handle); + + // offset out of partition size + offset = partition_data->size+1; + size = 1; + + err = esp_partition_mmap(partition_data, offset, size, memory, (const void **) &out_ptr, &out_handle); + TEST_ASSERT_EQUAL(err,ESP_ERR_INVALID_ARG); + + // mapped length beyond partition size + offset = 1; + size = partition_data->size; + + err = esp_partition_mmap(partition_data, offset, size, memory, (const void **) &out_ptr, &out_handle); + TEST_ASSERT_EQUAL(err,ESP_ERR_INVALID_SIZE); +} + +#define EMULATED_VIRTUAL_SECTOR_COUNT (ESP_PARTITION_EMULATED_FLASH_SIZE / ESP_PARTITION_EMULATED_SECTOR_SIZE) + +typedef struct +{ + size_t read_ops; + size_t write_ops; + size_t erase_ops; + size_t read_bytes; + size_t write_bytes; + size_t total_time; + size_t sector_erase_count[EMULATED_VIRTUAL_SECTOR_COUNT]; +} t_stats; + +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", + p_stats->read_ops, + p_stats->write_ops, + p_stats->erase_ops, + p_stats->read_bytes, + p_stats->write_bytes, + p_stats->total_time); +} + +void read_stats(t_stats *p_stats) +{ + p_stats->read_ops = esp_partition_get_read_ops(); + p_stats->write_ops = esp_partition_get_write_ops(); + p_stats->erase_ops = esp_partition_get_erase_ops(); + p_stats->read_bytes = esp_partition_get_read_bytes(); + 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++) + p_stats->sector_erase_count[i] = esp_partition_get_sector_erase_count(i); +} + +// evaluates if final stats differ from initial stats by expected difference stats. +// if there is no need to evaluate some stats, set respective expeted difference stats members to SIZE_MAX +bool evaluate_stats(const t_stats *p_initial_stats, const t_stats *p_final_stats, const t_stats *p_expected_difference_stats) +{ + if(p_expected_difference_stats->read_ops != SIZE_MAX) + TEST_ASSERT_EQUAL(p_initial_stats->read_ops + p_expected_difference_stats->read_ops, p_final_stats->read_ops); + if(p_expected_difference_stats->write_ops != SIZE_MAX) + TEST_ASSERT_EQUAL(p_initial_stats->write_ops + p_expected_difference_stats->write_ops, p_final_stats->write_ops); + if(p_expected_difference_stats->erase_ops != SIZE_MAX) + TEST_ASSERT_EQUAL(p_initial_stats->erase_ops + p_expected_difference_stats->erase_ops, p_final_stats->erase_ops); + if(p_expected_difference_stats->read_bytes != SIZE_MAX) + TEST_ASSERT_EQUAL(p_initial_stats->read_bytes + p_expected_difference_stats->read_bytes, p_final_stats->read_bytes); + if(p_expected_difference_stats->write_bytes != SIZE_MAX) + TEST_ASSERT_EQUAL(p_initial_stats->write_bytes + p_expected_difference_stats->write_bytes, p_final_stats->write_bytes); + 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++) + { + if(p_expected_difference_stats->sector_erase_count[i] != SIZE_MAX) + { + size_t expected_value = p_initial_stats->sector_erase_count[i] + p_expected_difference_stats->sector_erase_count[i]; + size_t final_value = p_final_stats->sector_erase_count[i]; + + TEST_ASSERT_EQUAL(expected_value, final_value); + } + } + + return true; +} + +TEST(partition_api, test_partition_stats) +{ + // get storage partition + 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); + + t_stats initial_stats; + t_stats final_stats; + t_stats zero_stats = {0}; + + // get actual statistics + read_stats(&initial_stats); + + // prepare buffer for r/w + size_t size = partition_data->size; + size_t part_offset = partition_data->address; // this is offset of partition data from flash beginning + void *test_data_ptr = malloc(size); + TEST_ASSERT_NOT_NULL(test_data_ptr); + + // do some writes + memset(test_data_ptr, 0xff, size); + esp_err_t err = esp_partition_write(partition_data, 0, test_data_ptr, size); + TEST_ESP_OK(err); + + // do some reads + err = esp_partition_read(partition_data , 0, test_data_ptr, size); + TEST_ESP_OK(err); + + // do erase + err = esp_partition_erase_range(partition_data, 0, size); + TEST_ESP_OK(err); + + // get actual statistics + read_stats(&final_stats); + + // evaluate expected results + // erase operations are per virtual sectors touched + // erase ops size / sector + (part_offset % sector + size % sector) / sector + 1 if ((part_offset % sector + size % sector) % sector > 0) + size_t non_aligned_portions = (part_offset % ESP_PARTITION_EMULATED_SECTOR_SIZE) + (size % ESP_PARTITION_EMULATED_SECTOR_SIZE); + size_t erase_ops = size / ESP_PARTITION_EMULATED_SECTOR_SIZE; + erase_ops += non_aligned_portions / ESP_PARTITION_EMULATED_SECTOR_SIZE; + 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++) + expected_difference_stats.sector_erase_count[i] = SIZE_MAX; + + evaluate_stats(&initial_stats, &final_stats, &expected_difference_stats); + + // clear statistics + esp_partition_clear_stats(); + read_stats(&final_stats); + + // evaluate zero statistics + evaluate_stats(&zero_stats, &final_stats, &zero_stats); + + free(test_data_ptr); +} + +TEST(partition_api, test_partition_wear_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 + size_t offset = 0; + size_t size = partition_data->size; + + // prepare test data block + void *test_data_ptr = malloc(size); + 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); + + // erase partition data + esp_err_t err = esp_partition_erase_range(partition_data, offset, size); + TEST_ESP_OK(err); + + // write data - should pass + err = esp_partition_write(partition_data, offset, test_data_ptr, size); + TEST_ESP_OK(err); + + // erase partition data + 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); + + // 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); + + // 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); + + // 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); + free(test_data_ptr); +} + + TEST_GROUP_RUNNER(partition_api) { RUN_TEST_CASE(partition_api, test_partition_find_basic); @@ -115,6 +350,9 @@ TEST_GROUP_RUNNER(partition_api) RUN_TEST_CASE(partition_api, test_partition_find_data); 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_stats); + RUN_TEST_CASE(partition_api, test_partition_wear_emulation); } static void run_all_tests(void) diff --git a/components/esp_partition/host_test/partition_api_test/sdkconfig.defaults b/components/esp_partition/host_test/partition_api_test/sdkconfig.defaults index 275e768c6c..d621ed02e0 100644 --- a/components/esp_partition/host_test/partition_api_test/sdkconfig.defaults +++ b/components/esp_partition/host_test/partition_api_test/sdkconfig.defaults @@ -6,3 +6,4 @@ CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partition_table.csv" CONFIG_ESPTOOLPY_FLASHSIZE="4MB" CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_ESP_PARTITION_ENABLE_STATS=y diff --git a/components/esp_partition/include/esp_private/partition_linux.h b/components/esp_partition/include/esp_private/partition_linux.h index efb1e65b0f..ef7174a596 100644 --- a/components/esp_partition/include/esp_private/partition_linux.h +++ b/components/esp_partition/include/esp_private/partition_linux.h @@ -7,6 +7,7 @@ #pragma once #include +#include #include "esp_err.h" #ifdef __cplusplus @@ -22,6 +23,9 @@ extern "C" { /** @brief emulated sector size for the partition API on Linux */ #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 + /** * @brief Partition type to string conversion routine * @@ -29,7 +33,7 @@ extern "C" { * * @return string equivalent of given partition type or "unknown" on mismatch */ -const char* esp_partition_type_to_str(const uint32_t type); +const char *esp_partition_type_to_str(const uint32_t type); /** * @brief Partition subtype to string conversion routine @@ -90,6 +94,105 @@ esp_err_t esp_partition_file_mmap(const uint8_t **part_desc_addr_start); */ esp_err_t esp_partition_file_munmap(void); +/** + * Functions for host tests +*/ + +/** + * @brief Clears statistics gathered by emulated partition read/write/erase operations + * + */ +void esp_partition_clear_stats(); + +/** + * @brief Returns number of read operations called + * + * Function returns number of calls to the function esp_partition_read + * + * @return + * - number of calls to esp_partition_read since recent esp_partition_clear_stats + */ +size_t esp_partition_get_read_ops(); + +/** + * @brief Returns number of write operations called + * + * Function returns number of calls to the function esp_partition_write + * + * @return + * - number of calls to esp_partition_write since recent esp_partition_clear_stats + */ +size_t esp_partition_get_write_ops(); + +/** + * @brief Returns number of erase operations performed on behalf of calls to esp_partition_erase_range + * + * Function returns accumulated number of sectors erased on behalf of esp_partition_erase_range + * + * @return + * - total number of emulated sector erase operations on behalf of esp_partition_erase_range since recent esp_partition_clear_stats + */ +size_t esp_partition_get_erase_ops(); + +/** + * @brief Returns total number of bytes read on behalf of esp_partition_read + * + * Function returns number of bytes read by esp_partition_read + * + * @return + * - total number of bytes read on behalf of esp_partition_read since recent esp_partition_clear_stats + */ +size_t esp_partition_get_read_bytes(); + +/** + * @brief Returns total number of bytes written on behalf of esp_partition_write + * + * Function returns number of bytes written by esp_partition_write + * + * @return + * - total number of bytes written on behalf of esp_partition_write since recent esp_partition_clear_stats + */ +size_t esp_partition_get_write_bytes(); + +/** + * @brief Returns estimated total time spent on partition operations. + * + * Function returns estimated total time spent in esp_partition_read, + * esp_partition_write and esp_partition_erase_range operations. + * + * @return + * - estimated total time spent in read/write/erase operations in miliseconds + */ +size_t esp_partition_get_total_time(); + +/** + * @brief Initializes emulation of failure caused by wear on behalf of 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 + * 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. + * +*/ +void esp_partition_fail_after(size_t count); + +/** + * @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. + * 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. + * + * @param[in] sector Virtual sector number to return erase count for + * + * @return + * - count of erase operations performed on virtual emulated sector + * +*/ +size_t esp_partition_get_sector_erase_count(size_t sector); + #ifdef __cplusplus } #endif diff --git a/components/esp_partition/partition_linux.c b/components/esp_partition/partition_linux.c index d20ccb5155..c351333ca2 100644 --- a/components/esp_partition/partition_linux.c +++ b/components/esp_partition/partition_linux.c @@ -20,7 +20,36 @@ static const char *TAG = "linux_spiflash"; static void *s_spiflash_mem_file_buf = NULL; -static uint32_t s_spiflash_mem_file_size = 0x400000; //4MB fixed +static const esp_partition_mmap_handle_t s_default_partition_mmap_handle = 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; + +// 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}; + +// forward declaration of hooks +static void esp_partition_hook_read(const void *srcAddr, const size_t size); +static bool esp_partition_hook_write(const void *dstAddr, const size_t size); +static bool esp_partition_hook_erase(const void *dstAddr, const size_t size); + +// redirect hooks to functions +#define ESP_PARTITION_HOOK_READ(srcAddr, size) esp_partition_hook_read(srcAddr, size) +#define ESP_PARTITION_HOOK_WRITE(dstAddr, size) esp_partition_hook_write(dstAddr, size) +#define ESP_PARTITION_HOOK_ERASE(dstAddr, size) esp_partition_hook_erase(dstAddr, size) +#else +// redirect hooks to "do nothing code" +#define ESP_PARTITION_HOOK_READ(srcAddr, size) +#define ESP_PARTITION_HOOK_WRITE(dstAddr, size) true +#define ESP_PARTITION_HOOK_ERASE(dstAddr, size) true +#endif const char *esp_partition_type_to_str(const uint32_t type) { @@ -66,21 +95,21 @@ esp_err_t esp_partition_file_mmap(const uint8_t **part_desc_addr_start) return ESP_ERR_NOT_FINISHED; } - if (ftruncate(spiflash_mem_file_fd, s_spiflash_mem_file_size) != 0) { + 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_LOGV(TAG, "SPIFLASH memory emulation file created: %s (size: %d B)", temp_spiflash_mem_file_name, s_spiflash_mem_file_size); + ESP_LOGV(TAG, "SPIFLASH memory emulation file created: %s (size: %d B)", temp_spiflash_mem_file_name, ESP_PARTITION_EMULATED_FLASH_SIZE); //create memory-mapping for the partitions holder file - if ((s_spiflash_mem_file_buf = mmap(NULL, s_spiflash_mem_file_size, PROT_READ | PROT_WRITE, MAP_SHARED, spiflash_mem_file_fd, 0)) == MAP_FAILED) { + 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, s_spiflash_mem_file_size); + 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"; @@ -152,11 +181,11 @@ esp_err_t esp_partition_file_munmap() if (s_spiflash_mem_file_buf == NULL) { return ESP_ERR_NO_MEM; } - if (s_spiflash_mem_file_size == 0) { + if (ESP_PARTITION_EMULATED_FLASH_SIZE == 0) { return ESP_ERR_INVALID_SIZE; } - if (munmap(s_spiflash_mem_file_buf, s_spiflash_mem_file_size) != 0) { + 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)); return ESP_ERR_INVALID_RESPONSE; } @@ -188,6 +217,11 @@ 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 + if (!ESP_PARTITION_HOOK_WRITE(dst_addr, size)) { + return ESP_FAIL; + } + //read the contents first, AND with the write buffer (to emulate real NOR FLASH behavior) memcpy(write_buf, dst_addr, size); for (size_t x = 0; x < size; x++) { @@ -219,6 +253,8 @@ esp_err_t esp_partition_read(const esp_partition_t *partition, size_t src_offset memcpy(dst, src_addr, size); + ESP_PARTITION_HOOK_READ(src_addr, size); // statistics + return ESP_OK; } @@ -248,8 +284,216 @@ 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 + if (!ESP_PARTITION_HOOK_ERASE(target_addr, size)) { + return ESP_FAIL; + } + //set all bits to 1 (NOR FLASH default) memset(target_addr, 0xFF, size); return ESP_OK; } + +/* + * Exposes direct pointer to the memory mapped file created by esp_partition_file_mmap + * No address alignment is performed + * Default handle is always returned + * Returns: + * ESP_ERR_INVALID_ARG - offset exceeds size of partition + * ESP_ERR_INVALID_SIZE - address range defined by offset + size is beyond the size of partition + * ESP_ERR_NOT_SUPPORTED - flash_chip of partition is not NULL + * ESP_OK - calculated out parameters hold pointer to the requested memory area and default handle respectively + */ +esp_err_t esp_partition_mmap(const esp_partition_t *partition, size_t offset, size_t size, + esp_partition_mmap_memory_t memory, + const void **out_ptr, esp_partition_mmap_handle_t *out_handle) +{ + ESP_LOGV(TAG, "esp_partition_mmap(): partition=%s offset=%zu size=%zu", partition->label, offset, size); + + assert(partition != NULL); + if (offset > partition->size) { + return ESP_ERR_INVALID_ARG; + } + if (offset + size > partition->size) { + return ESP_ERR_INVALID_SIZE; + } + if (partition->flash_chip != NULL) { + return ESP_ERR_NOT_SUPPORTED; + } + // required starting address in flash aka offset from the flash beginning + size_t req_flash_addr = (size_t)(partition->address) + offset; + + esp_err_t rc = ESP_OK; + + // 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); + } + + // adjust memory mapped pointer to the required offset + if (rc == ESP_OK) { + *out_ptr = (void *) (s_spiflash_mem_file_buf + req_flash_addr); + *out_handle = s_default_partition_mmap_handle; + } else { + *out_ptr = (void *) NULL; + *out_handle = 0; + } + return rc; +} + +// Intentionally does nothing. +void esp_partition_munmap(esp_partition_mmap_handle_t handle) +{ + ; +} + +#ifdef CONFIG_ESP_PARTITION_ENABLE_STATS +// timing data for ESP8266, 160MHz CPU frequency, 80MHz flash requency +// all values in microseconds +// values are for block sizes starting at 4 bytes and going up to 4096 bytes +static size_t s_esp_partition_stat_read_times[] = {7, 5, 6, 7, 11, 18, 32, 60, 118, 231, 459}; +static size_t s_esp_partition_stat_write_times[] = {19, 23, 35, 57, 106, 205, 417, 814, 1622, 3200, 6367}; +static size_t s_esp_partition_stat_block_erase_time = 37142; + +static size_t esp_partition_stat_time_interpolate(uint32_t bytes, size_t *lut) +{ + const int lut_size = sizeof(s_esp_partition_stat_read_times) / sizeof(s_esp_partition_stat_read_times[0]); + int lz = __builtin_clz(bytes / 4); + int log_size = 32 - lz; + size_t x2 = 1 << (log_size + 2); + size_t upper_index = (log_size < lut_size - 1) ? log_size : lut_size - 1; + size_t y2 = lut[upper_index]; + size_t x1 = 1 << (log_size + 1); + size_t y1 = lut[log_size - 1]; + return (bytes - x1) * (y2 - y1) / (x2 - x1) + y1; +} + +// Registers read access statistics of emulated SPI FLASH device (Linux host) +// Ffunction 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) +{ + ESP_LOGV(TAG, "esp_partition_hook_read()"); + + // stats + ++s_esp_partition_stat_read_ops; + s_esp_partition_stat_read_bytes += size; + s_esp_partition_stat_total_time += esp_partition_stat_time_interpolate((uint32_t) size, s_esp_partition_stat_read_times); +} + +// 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 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()"); + + // wear 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) { + return 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); + + return true; +} + +// 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 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()"); + + if (size == 0) { + return true; + } + + // cycle over virtual sectors + ptrdiff_t offset = dstAddr - s_spiflash_mem_file_buf; + size_t first_sector_idx = offset / ESP_PARTITION_EMULATED_SECTOR_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; + } + + // stats + ++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; +} + +void esp_partition_clear_stats() +{ + s_esp_partition_stat_read_bytes = 0; + s_esp_partition_stat_write_bytes = 0; + s_esp_partition_stat_erase_ops = 0; + s_esp_partition_stat_read_ops = 0; + 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)); +} + +size_t esp_partition_get_read_ops() +{ + return s_esp_partition_stat_read_ops; +} + +size_t esp_partition_get_write_ops() +{ + return s_esp_partition_stat_write_ops; +} + +size_t esp_partition_get_erase_ops() +{ + return s_esp_partition_stat_erase_ops; +} + +size_t esp_partition_get_read_bytes() +{ + return s_esp_partition_stat_read_bytes; +} + +size_t esp_partition_get_write_bytes() +{ + return s_esp_partition_stat_write_bytes; +} + +size_t esp_partition_get_total_time() +{ + return s_esp_partition_stat_total_time; +} + +void esp_partition_fail_after(size_t count) +{ + s_esp_partition_emulated_flash_life = count; +} + +size_t esp_partition_get_sector_erase_count(size_t sector) +{ + return s_esp_partition_stat_sector_erase_count[sector]; +} +#endif diff --git a/components/heap/heap_caps_linux.c b/components/heap/heap_caps_linux.c index 2adb3298ab..622b220334 100644 --- a/components/heap/heap_caps_linux.c +++ b/components/heap/heap_caps_linux.c @@ -4,7 +4,10 @@ * SPDX-License-Identifier: Apache-2.0 */ #include +#include +#ifdef HAVE_MALLOC_H #include +#endif #include #include "esp_attr.h"