From 36cb08c379a9e972aba37b90e1f59a3b480d65f6 Mon Sep 17 00:00:00 2001 From: Marius Vikhammer Date: Tue, 29 Nov 2022 16:32:55 +0800 Subject: [PATCH] heap: add linux target support Add wrappers for public heap API for the linux target. --- .gitlab/ci/host-test.yml | 8 + .../test_esp_system/main/esp_system_test.c | 6 +- components/esp_system/port/esp_system_linux.c | 9 +- components/heap/.build-test-rules.yml | 3 + components/heap/CMakeLists.txt | 9 + components/heap/heap_caps_linux.c | 225 ++++++++++++++++++ .../host_test/host_test_linux/CMakeLists.txt | 9 + .../heap/host_test/host_test_linux/README.md | 3 + .../host_test_linux/main/CMakeLists.txt | 3 + .../host_test_linux/main/test_heap_linux.c | 112 +++++++++ .../host_test_linux/sdkconfig.defaults | 1 + tools/cmake/build.cmake | 2 +- 12 files changed, 381 insertions(+), 9 deletions(-) create mode 100644 components/heap/heap_caps_linux.c create mode 100644 components/heap/host_test/host_test_linux/CMakeLists.txt create mode 100644 components/heap/host_test/host_test_linux/README.md create mode 100644 components/heap/host_test/host_test_linux/main/CMakeLists.txt create mode 100644 components/heap/host_test/host_test_linux/main/test_heap_linux.c create mode 100644 components/heap/host_test/host_test_linux/sdkconfig.defaults diff --git a/.gitlab/ci/host-test.yml b/.gitlab/ci/host-test.yml index 776f68c2b8..f666dd0718 100644 --- a/.gitlab/ci/host-test.yml +++ b/.gitlab/ci/host-test.yml @@ -385,6 +385,14 @@ test_esp_system: - echo "*" | timeout 5 build/test_esp_system.elf | tee log.txt || true - grep "6 Tests 0 Failures 0 Ignored" log.txt +test_heap_linux: + extends: .host_test_template + script: + - cd ${IDF_PATH}/components/heap/host_test/host_test_linux/ + - idf.py build + - echo "*" | timeout 5 build/test_heap.elf | tee log.txt || true + - grep "4 Tests 0 Failures 0 Ignored" log.txt + test_esp_timer_cxx: extends: .host_test_template script: diff --git a/components/esp_system/host_test/test_esp_system/main/esp_system_test.c b/components/esp_system/host_test/test_esp_system/main/esp_system_test.c index 738542500c..3d123c2602 100644 --- a/components/esp_system/host_test/test_esp_system/main/esp_system_test.c +++ b/components/esp_system/host_test/test_esp_system/main/esp_system_test.c @@ -103,9 +103,9 @@ TEST_CASE("register_too_many_shutdown_handler_fails", "[esp_system]") TEST_CASE("heap_size_stubs", "[esp_system]") { - TEST_ASSERT_EQUAL(47000, esp_get_free_heap_size()); - TEST_ASSERT_EQUAL(47000, esp_get_free_internal_heap_size()); - TEST_ASSERT_EQUAL(47000, esp_get_minimum_free_heap_size()); + TEST_ASSERT_EQUAL(UINT32_MAX, esp_get_free_heap_size()); + TEST_ASSERT_EQUAL(UINT32_MAX, esp_get_free_internal_heap_size()); + TEST_ASSERT_EQUAL(UINT32_MAX, esp_get_minimum_free_heap_size()); } void app_main(void) diff --git a/components/esp_system/port/esp_system_linux.c b/components/esp_system/port/esp_system_linux.c index b2dc4df1b0..b764f4082a 100644 --- a/components/esp_system/port/esp_system_linux.c +++ b/components/esp_system/port/esp_system_linux.c @@ -12,8 +12,7 @@ #include #include #include "esp_private/system_internal.h" - -static const uint32_t MAGIC_HEAP_SIZE = 47000; +#include "esp_heap_caps.h" // dummy, we should never get here on Linux void esp_restart_noos_dig(void) @@ -23,17 +22,17 @@ void esp_restart_noos_dig(void) uint32_t esp_get_free_heap_size( void ) { - return MAGIC_HEAP_SIZE; + return heap_caps_get_free_size( MALLOC_CAP_DEFAULT ); } uint32_t esp_get_free_internal_heap_size( void ) { - return MAGIC_HEAP_SIZE; + return heap_caps_get_free_size( MALLOC_CAP_8BIT | MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL ); } uint32_t esp_get_minimum_free_heap_size( void ) { - return MAGIC_HEAP_SIZE; + return heap_caps_get_minimum_free_size( MALLOC_CAP_DEFAULT ); } const char *esp_get_idf_version(void) diff --git a/components/heap/.build-test-rules.yml b/components/heap/.build-test-rules.yml index 5d7183fedc..90a31dbc77 100644 --- a/components/heap/.build-test-rules.yml +++ b/components/heap/.build-test-rules.yml @@ -1,5 +1,8 @@ # Documentation: .gitlab/ci/README.md#manifest-file-to-control-the-buildtest-apps +components/heap/host_test/host_test_linux: + enable: + - if: IDF_TARGET == "linux" components/heap/test_apps: disable_test: - if: IDF_TARGET == "esp32c6" diff --git a/components/heap/CMakeLists.txt b/components/heap/CMakeLists.txt index 61cb81219a..2e1a5178bb 100644 --- a/components/heap/CMakeLists.txt +++ b/components/heap/CMakeLists.txt @@ -1,3 +1,12 @@ +idf_build_get_property(target IDF_TARGET) + +# On Linux, we only support a few features, hence this simple component registration +if(${target} STREQUAL "linux") + idf_component_register(SRCS "heap_caps_linux.c" + INCLUDE_DIRS "include") + return() +endif() + set(srcs "heap_caps.c" "heap_caps_init.c" diff --git a/components/heap/heap_caps_linux.c b/components/heap/heap_caps_linux.c new file mode 100644 index 0000000000..2adb3298ab --- /dev/null +++ b/components/heap/heap_caps_linux.c @@ -0,0 +1,225 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + +#include "esp_attr.h" +#include "esp_heap_caps.h" + +static esp_alloc_failed_hook_t alloc_failed_callback; + +static const uint32_t MAGIC_HEAP_SIZE = UINT32_MAX; + +esp_err_t heap_caps_register_failed_alloc_callback(esp_alloc_failed_hook_t callback) +{ + if (callback == NULL) { + return ESP_ERR_INVALID_ARG; + } + + alloc_failed_callback = callback; + + return ESP_OK; +} + +static void heap_caps_alloc_failed(size_t requested_size, uint32_t caps, const char *function_name) +{ + if (alloc_failed_callback) { + alloc_failed_callback(requested_size, caps, function_name); + } + +#ifdef CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS + esp_system_abort("Memory allocation failed"); +#endif +} + +/* +Routine to allocate a bit of memory with certain capabilities. caps is a bitfield of MALLOC_CAP_* bits. +*/ +static void *heap_caps_malloc_base( size_t size, uint32_t caps) +{ + + void *ptr = malloc(size); + + if (!ptr && size > 0) { + heap_caps_alloc_failed(size, caps, __func__); + } + + return ptr; +} + +void *heap_caps_malloc( size_t size, uint32_t caps) +{ + return heap_caps_malloc_base(size, caps); +} + + + +void heap_caps_malloc_extmem_enable(size_t limit) +{ + (void)limit; + // No distinction between external vs internal memory on linux +} + +void *heap_caps_malloc_default( size_t size ) +{ + return heap_caps_malloc_base(size, MALLOC_CAP_DEFAULT); +} + +void *heap_caps_malloc_prefer( size_t size, size_t num, ... ) +{ + return heap_caps_malloc(size, MALLOC_CAP_DEFAULT); +} + + +static void *heap_caps_realloc_base( void *ptr, size_t size, uint32_t caps) +{ + ptr = realloc(ptr, caps); + + if (ptr == NULL && size > 0) { + heap_caps_alloc_failed(size, caps, __func__); + } + + return ptr; +} + +void *heap_caps_realloc( void *ptr, size_t size, uint32_t caps) +{ + return heap_caps_realloc_base(ptr, size, caps); +} + +void *heap_caps_realloc_default( void *ptr, size_t size ) +{ + return heap_caps_realloc_base(ptr, size, MALLOC_CAP_DEFAULT); +} + +void *heap_caps_realloc_prefer( void *ptr, size_t size, size_t num, ... ) +{ + return heap_caps_realloc_base(ptr, size, MALLOC_CAP_DEFAULT); +} + +void heap_caps_free( void *ptr) +{ + free(ptr); +} + +static void *heap_caps_calloc_base( size_t n, size_t size, uint32_t caps) +{ + size_t size_bytes; + + if (__builtin_mul_overflow(n, size, &size_bytes)) { + return NULL; + } + + return calloc(n, size); +} + +void *heap_caps_calloc( size_t n, size_t size, uint32_t caps) +{ + void *ptr = heap_caps_calloc_base(n, size, caps); + + if (!ptr && size > 0) { + heap_caps_alloc_failed(size, caps, __func__); + } + + return ptr; +} + + +void *heap_caps_calloc_prefer( size_t n, size_t size, size_t num, ... ) +{ + return heap_caps_calloc_base(n, size, MALLOC_CAP_DEFAULT); +} + +size_t heap_caps_get_total_size(uint32_t caps) +{ + return MAGIC_HEAP_SIZE; +} + +size_t heap_caps_get_free_size( uint32_t caps ) +{ + return MAGIC_HEAP_SIZE; +} + +size_t heap_caps_get_minimum_free_size( uint32_t caps ) +{ + return MAGIC_HEAP_SIZE; +} + +size_t heap_caps_get_largest_free_block( uint32_t caps ) +{ + return MAGIC_HEAP_SIZE; +} + +void heap_caps_get_info( multi_heap_info_t *info, uint32_t caps ) +{ + bzero(info, sizeof(multi_heap_info_t)); +} + +void heap_caps_print_heap_info( uint32_t caps ) +{ + printf("No heap summary available when building for the linux target"); +} + +bool heap_caps_check_integrity(uint32_t caps, bool print_errors) +{ + return true; +} + +bool heap_caps_check_integrity_all(bool print_errors) +{ + return heap_caps_check_integrity(MALLOC_CAP_INVALID, print_errors); +} + +bool heap_caps_check_integrity_addr(intptr_t addr, bool print_errors) +{ + return true; +} + +void heap_caps_dump(uint32_t caps) +{ +} + +void heap_caps_dump_all(void) +{ + heap_caps_dump(MALLOC_CAP_INVALID); +} + +size_t heap_caps_get_allocated_size( void *ptr ) +{ + return 0; +} + +void *heap_caps_aligned_alloc(size_t alignment, size_t size, uint32_t caps) +{ + void *ptr = aligned_alloc(alignment, size); + + if (!ptr && size > 0) { + heap_caps_alloc_failed(size, caps, __func__); + } + + return ptr; +} + +void heap_caps_aligned_free(void *ptr) +{ + heap_caps_free(ptr); +} + +void *heap_caps_aligned_calloc(size_t alignment, size_t n, size_t size, uint32_t caps) +{ + size_t size_bytes; + if (__builtin_mul_overflow(n, size, &size_bytes)) { + return NULL; + } + + void *ptr = heap_caps_aligned_alloc(alignment, size_bytes, caps); + if (ptr != NULL) { + memset(ptr, 0, size_bytes); + } + + return ptr; +} diff --git a/components/heap/host_test/host_test_linux/CMakeLists.txt b/components/heap/host_test/host_test_linux/CMakeLists.txt new file mode 100644 index 0000000000..38f4d7ac17 --- /dev/null +++ b/components/heap/host_test/host_test_linux/CMakeLists.txt @@ -0,0 +1,9 @@ +# For more information about build system see +# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html +# The following five lines of boilerplate have to be in your project's +# CMakeLists in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.16) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +set(COMPONENTS main) +project(test_heap) diff --git a/components/heap/host_test/host_test_linux/README.md b/components/heap/host_test/host_test_linux/README.md new file mode 100644 index 0000000000..ab0780283a --- /dev/null +++ b/components/heap/host_test/host_test_linux/README.md @@ -0,0 +1,3 @@ +| Supported Targets | Linux | +| ----------------- | ----- | + diff --git a/components/heap/host_test/host_test_linux/main/CMakeLists.txt b/components/heap/host_test/host_test_linux/main/CMakeLists.txt new file mode 100644 index 0000000000..6001764c70 --- /dev/null +++ b/components/heap/host_test/host_test_linux/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "test_heap_linux.c" + INCLUDE_DIRS "." + PRIV_REQUIRES unity) diff --git a/components/heap/host_test/host_test_linux/main/test_heap_linux.c b/components/heap/host_test/host_test_linux/main/test_heap_linux.c new file mode 100644 index 0000000000..b17a45518d --- /dev/null +++ b/components/heap/host_test/host_test_linux/main/test_heap_linux.c @@ -0,0 +1,112 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include +#include "sdkconfig.h" +#include "esp_heap_caps.h" +#include "unity.h" + +#define MALLOC_LEN 1000 +#define TEST_VAL 0xAB + +TEST_CASE("Alloc APIs", "[heap]") +{ + uint8_t * p = heap_caps_malloc(MALLOC_LEN, MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(p); + memset(p, TEST_VAL, MALLOC_LEN); + + p = heap_caps_realloc(p, 2*MALLOC_LEN, MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(p); + TEST_ASSERT_EACH_EQUAL_HEX8(TEST_VAL, p, MALLOC_LEN); + + heap_caps_realloc(p, 0, MALLOC_CAP_DEFAULT); + + p = heap_caps_calloc(MALLOC_LEN, sizeof(uint8_t), MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(p); + TEST_ASSERT_EACH_EQUAL_HEX8(0, p, MALLOC_LEN); + + /*Alloc prefer APIs*/ + p = heap_caps_malloc_prefer(MALLOC_LEN, MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(p); + memset(p, TEST_VAL, MALLOC_LEN); + + p = heap_caps_realloc_prefer(p, 2*MALLOC_LEN, MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(p); + TEST_ASSERT_EACH_EQUAL_HEX8(TEST_VAL, p, MALLOC_LEN); + + heap_caps_realloc_prefer(p, 0, MALLOC_CAP_DEFAULT); + + p = heap_caps_calloc_prefer(MALLOC_LEN, sizeof(uint8_t), MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(p); + TEST_ASSERT_EACH_EQUAL_HEX8(0, p, MALLOC_LEN); + + heap_caps_free(p); +} + +TEST_CASE("Size APIs", "[heap]") +{ + /* These functions doesnt provide any useful information on linux + These "tests" are simply included to check that all the functions in the header + are actually mocked + */ + printf("heap_caps_get_total_size: %zu\n", heap_caps_get_total_size(MALLOC_CAP_DEFAULT)); + printf("heap_caps_get_free_size: %zu\n", heap_caps_get_free_size(MALLOC_CAP_DEFAULT)); + printf("heap_caps_get_minimum_free_size: %zu\n", heap_caps_get_minimum_free_size(MALLOC_CAP_DEFAULT)); + printf("heap_caps_get_largest_free_block: %zu\n", heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT)); + + multi_heap_info_t info; + heap_caps_get_info(&info, MALLOC_CAP_DEFAULT); + heap_caps_check_integrity_all(true); + heap_caps_check_integrity(MALLOC_CAP_DEFAULT, true); + heap_caps_check_integrity_addr((intptr_t)&info, true); + heap_caps_malloc_extmem_enable(100); + + heap_caps_dump(MALLOC_CAP_DEFAULT); + heap_caps_dump_all(); + TEST_ASSERT_TRUE(heap_caps_get_allocated_size(&info) == 0); +} + +#define TEST_ALIGNMENT 0x1F + +TEST_CASE("Aligned alloc APIs", "[heap]") +{ + uint8_t * p = heap_caps_aligned_alloc(TEST_ALIGNMENT, MALLOC_LEN, MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(p); + TEST_ASSERT_TRUE(((intptr_t)p & (0x1F -1)) == 0); + memset(p, TEST_VAL, MALLOC_LEN); + TEST_ASSERT_EACH_EQUAL_HEX8(TEST_VAL, p, MALLOC_LEN); + heap_caps_free(p); + + p = heap_caps_aligned_calloc(TEST_ALIGNMENT, MALLOC_LEN, sizeof(uint8_t), MALLOC_CAP_DEFAULT); + TEST_ASSERT_NOT_NULL(p); + TEST_ASSERT_TRUE(((intptr_t)p & (0x1F -1)) == 0); + TEST_ASSERT_EACH_EQUAL_HEX8(0, p, MALLOC_LEN); + heap_caps_free(p); +} + +static bool alloc_failed; + +void alloc_failed_cb(size_t size, uint32_t caps, const char * function_name) +{ + printf("Failed alloc of size %zu bytes at %s", size, function_name); + alloc_failed = true; +} + +TEST_CASE("Misc APIs", "[heap]") +{ + heap_caps_register_failed_alloc_callback(alloc_failed_cb); + + heap_caps_malloc(SIZE_MAX, MALLOC_CAP_DEFAULT); + TEST_ASSERT_TRUE(alloc_failed); +} + +void app_main(void) +{ + printf("Running heap linux API host test app"); + unity_run_menu(); +} diff --git a/components/heap/host_test/host_test_linux/sdkconfig.defaults b/components/heap/host_test/host_test_linux/sdkconfig.defaults new file mode 100644 index 0000000000..9b39f10b99 --- /dev/null +++ b/components/heap/host_test/host_test_linux/sdkconfig.defaults @@ -0,0 +1 @@ +CONFIG_IDF_TARGET="linux" diff --git a/tools/cmake/build.cmake b/tools/cmake/build.cmake index 7980e99c3d..a46aa63f7d 100644 --- a/tools/cmake/build.cmake +++ b/tools/cmake/build.cmake @@ -226,7 +226,7 @@ function(__build_init idf_path) endforeach() if("${target}" STREQUAL "linux") - set(requires_common freertos log esp_rom esp_common esp_system linux) + set(requires_common freertos heap log esp_rom esp_common esp_system linux) idf_build_set_property(__COMPONENT_REQUIRES_COMMON "${requires_common}") else() # Set components required by all other components in the build