Browse Source

Merge branch 'feature/storage_esp_partition_linux' into 'master'

esp_partition: added missing functions for linux target

See merge request espressif/esp-idf!21762
Martin Vychodil 3 năm trước cách đây
mục cha
commit
1da27eb6fe

+ 10 - 0
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

+ 238 - 0
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)

+ 1 - 0
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

+ 104 - 1
components/esp_partition/include/esp_private/partition_linux.h

@@ -7,6 +7,7 @@
 #pragma once
 
 #include <stdint.h>
+#include <stdbool.h>
 #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

+ 251 - 7
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

+ 3 - 0
components/heap/heap_caps_linux.c

@@ -4,7 +4,10 @@
  * SPDX-License-Identifier: Apache-2.0
  */
 #include <stdbool.h>
+#include <stdlib.h>
+#ifdef HAVE_MALLOC_H
 #include <malloc.h>
+#endif
 #include <string.h>
 
 #include "esp_attr.h"