diff --git a/components/heap/heap_caps.c b/components/heap/heap_caps.c index a2bf7166db5d..ca5ec1338f76 100644 --- a/components/heap/heap_caps.c +++ b/components/heap/heap_caps.c @@ -574,6 +574,65 @@ size_t heap_caps_get_largest_free_block( uint32_t caps ) return info.largest_free_block; } +static struct { + size_t *values; // Array of minimum_free_bytes used to keep the different values when starting monitoring + size_t counter; // Keep count of registered heap when monitoring to prevent any added heap to create an out of bound access on values + multi_heap_lock_t mux; // protect access to min_free_bytes_monitoring fields in start/stop monitoring functions +} min_free_bytes_monitoring = {NULL, 0, MULTI_HEAP_LOCK_STATIC_INITIALIZER}; + +esp_err_t heap_caps_monitor_local_minimum_free_size_start(void) +{ + // update minimum_free_bytes on all affected heap, and store the "old value" + // as a snapshot of the heaps minimum_free_bytes state. + heap_t *heap = NULL; + MULTI_HEAP_LOCK(&min_free_bytes_monitoring.mux); + if (min_free_bytes_monitoring.values == NULL) { + SLIST_FOREACH(heap, ®istered_heaps, next) { + min_free_bytes_monitoring.counter++; + } + min_free_bytes_monitoring.values = heap_caps_malloc(sizeof(size_t) * min_free_bytes_monitoring.counter, MALLOC_CAP_DEFAULT); + assert(min_free_bytes_monitoring.values != NULL && "not enough memory to store min_free_bytes value"); + memset(min_free_bytes_monitoring.values, 0xFF, sizeof(size_t) * min_free_bytes_monitoring.counter); + } + + heap = SLIST_FIRST(®istered_heaps); + for (size_t counter = 0; counter < min_free_bytes_monitoring.counter; counter++) { + size_t old_minimum = multi_heap_reset_minimum_free_bytes(heap->heap); + + if (min_free_bytes_monitoring.values[counter] > old_minimum) { + min_free_bytes_monitoring.values[counter] = old_minimum; + } + + heap = SLIST_NEXT(heap, next); + } + MULTI_HEAP_UNLOCK(&min_free_bytes_monitoring.mux); + + return ESP_OK; +} + +esp_err_t heap_caps_monitor_local_minimum_free_size_stop(void) +{ + if (min_free_bytes_monitoring.values == NULL) { + return ESP_FAIL; + } + + MULTI_HEAP_LOCK(&min_free_bytes_monitoring.mux); + heap_t *heap = SLIST_FIRST(®istered_heaps); + for (size_t counter = 0; counter < min_free_bytes_monitoring.counter; counter++) { + multi_heap_restore_minimum_free_bytes(heap->heap, min_free_bytes_monitoring.values[counter]); + + heap = SLIST_NEXT(heap, next); + } + + heap_caps_free(min_free_bytes_monitoring.values); + min_free_bytes_monitoring.values = NULL; + min_free_bytes_monitoring.counter = 0; + MULTI_HEAP_UNLOCK(&min_free_bytes_monitoring.mux); + + return ESP_OK; +} + + void heap_caps_get_info( multi_heap_info_t *info, uint32_t caps ) { memset(info, 0, sizeof(multi_heap_info_t)); diff --git a/components/heap/include/esp_heap_caps.h b/components/heap/include/esp_heap_caps.h index 97f2ff7143ab..ec202359a334 100644 --- a/components/heap/include/esp_heap_caps.h +++ b/components/heap/include/esp_heap_caps.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2019-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2019-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -241,6 +241,27 @@ size_t heap_caps_get_minimum_free_size( uint32_t caps ); */ size_t heap_caps_get_largest_free_block( uint32_t caps ); +/** + * @brief Start monitoring the value of minimum_free_bytes from the moment this + * function is called instead of from startup. + * + * @note This allows to detect local lows of the minimum_free_bytes value + * that wouldn't be detected otherwise. + * + * @return esp_err_t ESP_OK if the function executed properly + * ESP_FAIL if called when monitoring already active + */ +esp_err_t heap_caps_monitor_local_minimum_free_size_start(void); + +/** + * @brief Stop monitoring the value of minimum_free_bytes. After this call + * the minimum_free_bytes value calculated from startup will be returned in + * heap_caps_get_info and heap_caps_get_minimum_free_size. + * + * @return esp_err_t ESP_OK if the function executed properly + * ESP_FAIL if called when monitoring not active + */ +esp_err_t heap_caps_monitor_local_minimum_free_size_stop(void); /** * @brief Get heap info for all regions with the given capabilities. diff --git a/components/heap/include/multi_heap.h b/components/heap/include/multi_heap.h index 2df9280ba02f..9fa1e090a27e 100644 --- a/components/heap/include/multi_heap.h +++ b/components/heap/include/multi_heap.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -190,6 +190,23 @@ void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info); */ void *multi_heap_aligned_alloc_offs(multi_heap_handle_t heap, size_t size, size_t alignment, size_t offset); +/** + * @brief Reset the minimum_free_bytes value (setting it to free_bytes) and return the former value + * + * @param heap The heap in which the reset is taking place + * @return size_t the value of minimum_free_bytes before it is reset + */ +size_t multi_heap_reset_minimum_free_bytes(multi_heap_handle_t heap); + +/** + * @brief Set the value of minimum_free_bytes to new_minimum_free_bytes_value or keep + * the current value of minimum_free_bytes if it is smaller than new_minimum_free_bytes_value + * + * @param heap The heap in which the restore is taking place + * @param new_minimum_free_bytes_value The value to restore the minimum_free_bytes to + */ +void multi_heap_restore_minimum_free_bytes(multi_heap_handle_t heap, const size_t new_minimum_free_bytes_value); + #ifdef __cplusplus } #endif diff --git a/components/heap/multi_heap.c b/components/heap/multi_heap.c index 9a1a93d7ce7c..151113ac0563 100644 --- a/components/heap/multi_heap.c +++ b/components/heap/multi_heap.c @@ -11,6 +11,7 @@ #include #include #include +#include #include "multi_heap.h" #include "multi_heap_internal.h" @@ -429,4 +430,22 @@ void multi_heap_get_info_impl(multi_heap_handle_t heap, multi_heap_info_t *info) info->largest_free_block = tlsf_fit_size(heap->heap_data, info->largest_free_block); multi_heap_internal_unlock(heap); } -#endif + +#endif // CONFIG_HEAP_TLSF_USE_ROM_IMPL + +size_t multi_heap_reset_minimum_free_bytes(multi_heap_handle_t heap) +{ + multi_heap_internal_lock(heap); + const size_t old_minimum = heap->minimum_free_bytes; + heap->minimum_free_bytes = heap->free_bytes; + multi_heap_internal_unlock(heap); + return old_minimum; +} + +void multi_heap_restore_minimum_free_bytes(multi_heap_handle_t heap, const size_t new_minimum_free_bytes_value) +{ + multi_heap_internal_lock(heap); + // keep the value of minimum_free_bytes if it is lower than the value passed as parameter + heap->minimum_free_bytes = MIN(heap->minimum_free_bytes, new_minimum_free_bytes_value); + multi_heap_internal_unlock(heap); +} diff --git a/components/heap/multi_heap_internal.h b/components/heap/multi_heap_internal.h index 2fd09a9ad066..3fcbb50a76d8 100644 --- a/components/heap/multi_heap_internal.h +++ b/components/heap/multi_heap_internal.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2015-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ diff --git a/components/heap/test_apps/heap_tests/main/test_malloc_caps.c b/components/heap/test_apps/heap_tests/main/test_malloc_caps.c index 255a4fa5b6a8..37441873eaf2 100644 --- a/components/heap/test_apps/heap_tests/main/test_malloc_caps.c +++ b/components/heap/test_apps/heap_tests/main/test_malloc_caps.c @@ -171,6 +171,62 @@ TEST_CASE("heap_caps metadata test", "[heap]") TEST_ASSERT(after.minimum_free_bytes < original.total_free_bytes); } +TEST_CASE("heap caps minimum free bytes monitoring", "[heap]") +{ + printf("heap caps minimum free bytes monitoring local minimum\n"); + + uint32_t caps = MALLOC_CAP_DEFAULT; + size_t minimum_free_size_reference = heap_caps_get_minimum_free_size(caps); + + // start monitoring the value of minimum free bytes + esp_err_t ret_val = heap_caps_monitor_local_minimum_free_size_start(); + TEST_ASSERT_EQUAL(ret_val, ESP_OK); + + // get the heap info and check that the value of minimum free bytes return + // is different from the previous one (before monitoring) + size_t local_minimum_free_size = heap_caps_get_minimum_free_size(caps); + TEST_ASSERT(local_minimum_free_size >= minimum_free_size_reference); + + // allocate and free 400 bytes of memory. + size_t alloc_size = 400; + void *ptr = heap_caps_malloc(400, caps); + TEST_ASSERT(ptr != NULL); + heap_caps_free(ptr); + // Check the new value of minimum free bytes to make sure + // it is now lower than the previous one. + TEST_ASSERT(heap_caps_get_minimum_free_size(caps) <= local_minimum_free_size - alloc_size); + + // stop monitoring + ret_val = heap_caps_monitor_local_minimum_free_size_stop(); + TEST_ASSERT_EQUAL(ret_val, ESP_OK); + + // get the heap info and check that the value of minimum free bytes is lower than + // the local minimum (since the local minimum didn't create a new all time minimum) + size_t free_size = heap_caps_get_minimum_free_size(caps); + TEST_ASSERT(local_minimum_free_size >= free_size); +} + +TEST_CASE("heap caps minimum free bytes fault cases", "[heap]") +{ + printf("heap caps minimum free bytes fault cases\n"); + + // start monitoring the value of minimum free bytes + esp_err_t ret_val = heap_caps_monitor_local_minimum_free_size_start(); + TEST_ASSERT_EQUAL(ret_val, ESP_OK); + + // calling start again should be allowed + ret_val = heap_caps_monitor_local_minimum_free_size_start(); + TEST_ASSERT_EQUAL(ret_val, ESP_OK); + + // stop the monitoring + ret_val = heap_caps_monitor_local_minimum_free_size_stop(); + TEST_ASSERT_EQUAL(ret_val, ESP_OK); + + // calling stop monitoring when monitoring is not active should fail + ret_val = heap_caps_monitor_local_minimum_free_size_stop(); + TEST_ASSERT_NOT_EQUAL(ret_val, ESP_OK); +} + /* Small function runs from IRAM to check that malloc/free/realloc all work OK when cache is disabled... */