From dac289f9779bcd9e637bd34ea651ac21467ccf90 Mon Sep 17 00:00:00 2001 From: Jamie McCrae Date: Mon, 27 Oct 2025 20:51:53 +0000 Subject: [PATCH 1/4] mgmt: mcumgr: transport: Add zcbor buffer reset function Adds a common zcbor reset buffer function, this allows for the buffer to be reset after data has been added already to add a different response, e.g. an error response Signed-off-by: Jamie McCrae --- .../mgmt/mcumgr/transport/smp_internal.h | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/smp_internal.h b/subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/smp_internal.h index b231855892849..70dd48d691950 100644 --- a/subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/smp_internal.h +++ b/subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/smp_internal.h @@ -12,6 +12,7 @@ #include #include #include +#include #ifdef __cplusplus extern "C" { @@ -79,6 +80,30 @@ void *smp_alloc_rsp(const void *req, void *arg); */ void smp_free_buf(void *buf, void *arg); +/** + * @brief Reeset a zcbor encoder to allow a new response. + * + * If a response has already been (partially) generated than this will allow resetting back to + * the default state so that new response can be used (e.g. an error). + * + * @param streamer The streamer providing the required SMP callbacks. + * + * @return true on success, false on failure (memory error). + */ +static inline bool smp_mgmt_reset_zse(struct smp_streamer *streamer) +{ + zcbor_state_t *zse = streamer->writer->zs; + + /* Because there is already data in the buffer, it must be cleared first */ + net_buf_reset(streamer->writer->nb); + streamer->writer->nb->len = sizeof(struct smp_hdr); + zcbor_new_encode_state(zse, ARRAY_SIZE(streamer->writer->zs), + streamer->writer->nb->data + sizeof(struct smp_hdr), + net_buf_tailroom(streamer->writer->nb), 0); + + return zcbor_map_start_encode(zse, CONFIG_MCUMGR_SMP_CBOR_MAX_MAIN_MAP_ENTRIES); +} + #ifdef __cplusplus } #endif From c3c9a9eb1cb878c097601e803feda3dfde2aaed6 Mon Sep 17 00:00:00 2001 From: Jamie McCrae Date: Mon, 27 Oct 2025 20:53:36 +0000 Subject: [PATCH 2/4] mgmt: mcumgr: grp: img_mgmt: Use common reset function Replaces the img_mgmt specific function with the common SMP function for resetting the zcbor buffer and allowing a new response to be sent Signed-off-by: Jamie McCrae --- .../mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c | 20 ++----------------- .../mgmt/mcumgr/transport/smp_internal.h | 1 + 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c index c301d427fe498..6836bb8ef1bdb 100644 --- a/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c +++ b/subsys/mgmt/mcumgr/grp/img_mgmt/src/img_mgmt.c @@ -130,22 +130,6 @@ void img_mgmt_release_lock(void) #endif } -#if defined(CONFIG_MCUMGR_GRP_IMG_SLOT_INFO_HOOKS) -static bool img_mgmt_reset_zse(struct smp_streamer *ctxt) -{ - zcbor_state_t *zse = ctxt->writer->zs; - - /* Because there is already data in the buffer, it must be cleared first */ - net_buf_reset(ctxt->writer->nb); - ctxt->writer->nb->len = sizeof(struct smp_hdr); - zcbor_new_encode_state(zse, ARRAY_SIZE(ctxt->writer->zs), - ctxt->writer->nb->data + sizeof(struct smp_hdr), - net_buf_tailroom(ctxt->writer->nb), 0); - - return zcbor_map_start_encode(zse, CONFIG_MCUMGR_SMP_CBOR_MAX_MAIN_MAP_ENTRIES); -} -#endif - #if defined(CONFIG_MCUMGR_GRP_IMG_TOO_LARGE_SYSBUILD) static bool img_mgmt_slot_max_size(size_t *area_sizes, zcbor_state_t *zse) { @@ -597,7 +581,7 @@ static int img_mgmt_slot_info(struct smp_streamer *ctxt) return err_rc; } - ok = img_mgmt_reset_zse(ctxt) && + ok = smp_mgmt_reset_zse(ctxt) && smp_add_cmd_err(zse, err_group, (uint16_t)err_rc); goto finish; @@ -645,7 +629,7 @@ static int img_mgmt_slot_info(struct smp_streamer *ctxt) return err_rc; } - ok = img_mgmt_reset_zse(ctxt) && + ok = smp_mgmt_reset_zse(ctxt) && smp_add_cmd_err(zse, err_group, (uint16_t)err_rc); goto finish; diff --git a/subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/smp_internal.h b/subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/smp_internal.h index 70dd48d691950..c1ed49db98689 100644 --- a/subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/smp_internal.h +++ b/subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/smp_internal.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include From 2879bd98e7f6b7d454a0d32976a28946380a7ae0 Mon Sep 17 00:00:00 2001 From: Jamie McCrae Date: Mon, 27 Oct 2025 16:47:25 +0000 Subject: [PATCH 3/4] mgmt: mcumgr: grp: os_mgmt: Add memory pool statistics command support Adds support for the memory pool statistics (mpstat) command, which will list details on various memory heaps on the device Signed-off-by: Jamie McCrae --- .../zephyr/mgmt/mcumgr/grp/os_mgmt/os_mgmt.h | 3 + subsys/mgmt/mcumgr/grp/os_mgmt/Kconfig | 20 ++++ subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c | 98 +++++++++++++++++++ 3 files changed, 121 insertions(+) diff --git a/include/zephyr/mgmt/mcumgr/grp/os_mgmt/os_mgmt.h b/include/zephyr/mgmt/mcumgr/grp/os_mgmt/os_mgmt.h index 38222906e1322..86c6d10a7de4e 100644 --- a/include/zephyr/mgmt/mcumgr/grp/os_mgmt/os_mgmt.h +++ b/include/zephyr/mgmt/mcumgr/grp/os_mgmt/os_mgmt.h @@ -61,6 +61,9 @@ enum os_mgmt_err_code_t { /** Query was recognized but there is no valid value for the response. */ OS_MGMT_ERR_QUERY_RESPONSE_VALUE_NOT_VALID, + + /** Heap statistic fetch failed. */ + OS_MGMT_ERR_HEAP_STATS_FETCH_FAILED, }; /** diff --git a/subsys/mgmt/mcumgr/grp/os_mgmt/Kconfig b/subsys/mgmt/mcumgr/grp/os_mgmt/Kconfig index 1d250369e20f3..15df1cc7eb39e 100644 --- a/subsys/mgmt/mcumgr/grp/os_mgmt/Kconfig +++ b/subsys/mgmt/mcumgr/grp/os_mgmt/Kconfig @@ -137,6 +137,26 @@ config MCUMGR_GRP_OS_TASKSTAT_STACK_INFO endif +config MCUMGR_GRP_OS_MPSTAT + bool "Support for memory pool statistics command" + depends on SYS_HEAP_RUNTIME_STATS + depends on SYS_HEAP_ARRAY_SIZE > 0 + select MCUMGR_SMP_CBOR_MIN_ENCODING_LEVEL_2 + select MCUMGR_SMP_CBOR_MIN_ENCODING_LEVEL_3 if ZCBOR_CANONICAL + help + The memory pool statistics (mpstat) command will list the various heap memory pools + on the device, including sizes, free space and minimum amount of free space. + +config MCUMGR_GRP_OS_MPSTAT_ONLY_SUPPORTED_STATS + bool "Send only data gathered by Zephyr" + depends on MCUMGR_GRP_OS_MPSTAT + default y + help + Response will not include the "blksiz" field which Zephyr does not support (as it has a + value of 1). Enable this if your client software is able to process "mpstat" response + without the "blksiz" field for each memory pool. Enabling this option will slightly + reduce code size. + config MCUMGR_GRP_OS_ECHO bool "Support for echo command" default y diff --git a/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c b/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c index 058950a30b9cc..c8798edd0b361 100644 --- a/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c +++ b/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c @@ -25,6 +25,11 @@ #include +#ifdef CONFIG_MCUMGR_GRP_OS_MPSTAT +#include +#include +#endif + #ifdef CONFIG_REBOOT #include #endif @@ -145,6 +150,22 @@ struct datetime_parser { extern uint8_t *MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME; #endif +#ifdef CONFIG_MCUMGR_GRP_OS_MPSTAT +/* Specifies the maximum characters in the memory pool ID, allowing up to 999 outputs */ +#define MPSTAT_ID_KEY_SIZE 4 + +#ifdef CONFIG_MCUMGR_GRP_OS_MPSTAT_ONLY_SUPPORTED_STATS +/* Number of items per memory pool output */ +#define MPSTAT_KEY_MAP_ENTRIES 3 +#else +/* Number of items per memory pool output */ +#define MPSTAT_KEY_MAP_ENTRIES 4 + +/* Specifies the block size of memory pools, zephyr used byte-level allocation */ +#define MPSTAT_BLOCK_SIZE 1 +#endif +#endif + /** * Command handler: os echo */ @@ -355,6 +376,76 @@ static int os_mgmt_taskstat_read(struct smp_streamer *ctxt) } #endif /* CONFIG_MCUMGR_GRP_OS_TASKSTAT */ +#ifdef CONFIG_MCUMGR_GRP_OS_MPSTAT +/** + * Command handler: os mpstat + */ +static int os_mgmt_mpstat_read(struct smp_streamer *ctxt) +{ + zcbor_state_t *zse = ctxt->writer->zs; + struct sys_heap **heap; + uint8_t key[MPSTAT_ID_KEY_SIZE] = { 0 }; + int heap_elements; + int i = 0; + bool ok; + + heap_elements = sys_heap_array_get(&heap); + ok = zcbor_tstr_put_lit(zse, "tasks") && + zcbor_map_start_encode(zse, heap_elements); + + if (!ok) { + goto end; + } + + while (i < heap_elements) { + struct sys_memory_stats heap_stats; + uint32_t heap_total_size; + int rc; + uint8_t key_size; + + rc = sys_heap_runtime_stats_get(heap[i], &heap_stats); + + if (rc != 0) { + ok = smp_mgmt_reset_zse(ctxt) && + smp_add_cmd_err(zse, MGMT_GROUP_ID_OS, + OS_MGMT_ERR_HEAP_STATS_FETCH_FAILED); + LOG_ERR("Failed to get heap stats from address %p: %d", heap[i], rc); + goto end; + } + + key_size = u8_to_dec(key, sizeof(key), i); + heap_total_size = heap_stats.allocated_bytes + heap_stats.free_bytes; + + ok = zcbor_tstr_encode_ptr(zse, key, (size_t)key_size) && + zcbor_map_start_encode(zse, MPSTAT_KEY_MAP_ENTRIES) && +#ifndef CONFIG_MCUMGR_GRP_OS_MPSTAT_ONLY_SUPPORTED_STATS + zcbor_tstr_put_lit(zse, "blksiz") && + zcbor_uint32_put(zse, MPSTAT_BLOCK_SIZE) && +#endif + zcbor_tstr_put_lit(zse, "nblks") && + zcbor_uint32_put(zse, heap_total_size) && + zcbor_tstr_put_lit(zse, "nfree") && + zcbor_uint32_put(zse, heap_stats.free_bytes) && + zcbor_tstr_put_lit(zse, "min") && + zcbor_uint32_put(zse, (heap_total_size - heap_stats.max_allocated_bytes)) && + zcbor_map_end_encode(zse, MPSTAT_KEY_MAP_ENTRIES); + + if (!ok) { + break; + } + + ++i; + } + + if (ok == true) { + ok = zcbor_map_end_encode(zse, heap_elements); + } + +end: + return MGMT_RETURN_CHECK(ok); +} +#endif /* CONFIG_MCUMGR_GRP_OS_MPSTAT */ + #ifdef CONFIG_REBOOT /** * Command handler: os reset @@ -1117,6 +1208,7 @@ static int os_mgmt_translate_error_code(uint16_t err) case OS_MGMT_ERR_QUERY_YIELDS_NO_ANSWER: case OS_MGMT_ERR_RTC_NOT_SET: case OS_MGMT_ERR_QUERY_RESPONSE_VALUE_NOT_VALID: + case OS_MGMT_ERR_HEAP_STATS_FETCH_FAILED: rc = MGMT_ERR_ENOENT; break; @@ -1142,6 +1234,12 @@ static const struct mgmt_handler os_mgmt_group_handlers[] = { }, #endif +#ifdef CONFIG_MCUMGR_GRP_OS_MPSTAT + [OS_MGMT_ID_MPSTAT] = { + os_mgmt_mpstat_read, NULL + }, +#endif + #ifdef CONFIG_MCUMGR_GRP_OS_DATETIME [OS_MGMT_ID_DATETIME_STR] = { os_mgmt_datetime_read, os_mgmt_datetime_write From 44b3d5fce82ce7e3b7d679764fe5555fe9569d3c Mon Sep 17 00:00:00 2001 From: Jamie McCrae Date: Mon, 27 Oct 2025 18:14:23 +0000 Subject: [PATCH 4/4] tests: mgmt: mcumgr: Add os mgmt mpstat test Adds a test to check that the memory pool statistics command works Signed-off-by: Jamie McCrae --- .../mgmt/mcumgr/os_mgmt_mpstat/CMakeLists.txt | 14 + .../mgmt/mcumgr/os_mgmt_mpstat/prj.conf | 24 + .../mgmt/mcumgr/os_mgmt_mpstat/src/main.c | 428 ++++++++++++++++++ .../mcumgr/os_mgmt_mpstat/src/smp_test_util.c | 42 ++ .../mcumgr/os_mgmt_mpstat/src/smp_test_util.h | 19 + .../mgmt/mcumgr/os_mgmt_mpstat/testcase.yaml | 25 + 6 files changed, 552 insertions(+) create mode 100644 tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/CMakeLists.txt create mode 100644 tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/prj.conf create mode 100644 tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/main.c create mode 100644 tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/smp_test_util.c create mode 100644 tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/smp_test_util.h create mode 100644 tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/testcase.yaml diff --git a/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/CMakeLists.txt b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/CMakeLists.txt new file mode 100644 index 0000000000000..63dc2ce38d268 --- /dev/null +++ b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(os_mgmt_mpstat) + +FILE(GLOB app_sources src/*.c) + +target_sources(app PRIVATE ${app_sources}) +target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/) diff --git a/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/prj.conf b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/prj.conf new file mode 100644 index 0000000000000..a4942b5cbceef --- /dev/null +++ b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/prj.conf @@ -0,0 +1,24 @@ +# +# Copyright (c) 2025 Jamie McCrae +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_ZTEST=y +CONFIG_NET_BUF=y +CONFIG_BASE64=y +CONFIG_ZCBOR=y +CONFIG_CRC=y +CONFIG_MINIMAL_LIBC=y +CONFIG_COMMON_LIBC_MALLOC=y +CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE=1024 +CONFIG_HEAP_MEM_POOL_SIZE=256 +CONFIG_SYS_HEAP_RUNTIME_STATS=y +CONFIG_SYS_HEAP_ARRAY_SIZE=32 +CONFIG_MCUMGR=y +CONFIG_MCUMGR_TRANSPORT_DUMMY=y +CONFIG_MCUMGR_TRANSPORT_DUMMY_RX_BUF_SIZE=384 +CONFIG_MCUMGR_GRP_OS=y +CONFIG_MCUMGR_GRP_OS_ECHO=n +CONFIG_MCUMGR_GRP_OS_MPSTAT=y +CONFIG_ZTEST_STACK_SIZE=2048 diff --git a/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/main.c b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/main.c new file mode 100644 index 0000000000000..141d273a02ed3 --- /dev/null +++ b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/main.c @@ -0,0 +1,428 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * Copyright (c) 2025, Jamie McCrae + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "smp_test_util.h" + +#define SMP_RESPONSE_WAIT_TIME 3 +#define ZCBOR_BUFFER_SIZE 128 +#define OUTPUT_BUFFER_SIZE 384 +#define ZCBOR_HISTORY_ARRAY_SIZE 8 +#define TEST_MAX_HEAPS 2 + +struct heap_info_t { + uint32_t block_size; + uint32_t total_blocks; + uint32_t free_blocks; + uint32_t minimum_blocks; +}; + +struct memory_pools_info_t { + struct heap_info_t heaps[TEST_MAX_HEAPS]; + uint8_t current_heap; +}; + +static struct net_buf *nb; +static void *malloc_test_object; +static void *k_malloc_test_object; + +static void cleanup_test(void *p) +{ + if (nb != NULL) { + net_buf_reset(nb); + net_buf_unref(nb); + nb = NULL; + } + + if (malloc_test_object != NULL) { + free(malloc_test_object); + malloc_test_object = NULL; + } + + if (k_malloc_test_object != NULL) { + k_free(k_malloc_test_object); + k_malloc_test_object = NULL; + } +} + +static bool parse_heap_entries(zcbor_state_t *state, void *user_data) +{ + struct memory_pools_info_t *receive_data = (struct memory_pools_info_t *)user_data; + bool ok; + + receive_data->current_heap = 0; + + if (!zcbor_map_start_decode(state)) { + return false; + } + + while (!zcbor_array_at_end(state)) { + size_t decoded = 0; + struct heap_info_t *heap_data = &receive_data->heaps[receive_data->current_heap]; + struct zcbor_string block_name; + uint8_t expected_block_name[8]; + uint8_t expected_block_name_size; + + zassert_not_equal(receive_data->current_heap, TEST_MAX_HEAPS, + "More heaps than were expected"); + + struct zcbor_map_decode_key_val output_decode[] = { + ZCBOR_MAP_DECODE_KEY_DECODER("blksiz", zcbor_uint32_decode, + &heap_data->block_size), + ZCBOR_MAP_DECODE_KEY_DECODER("nblks", zcbor_uint32_decode, + &heap_data->total_blocks), + ZCBOR_MAP_DECODE_KEY_DECODER("nfree", zcbor_uint32_decode, + &heap_data->free_blocks), + ZCBOR_MAP_DECODE_KEY_DECODER("min", zcbor_uint32_decode, + &heap_data->minimum_blocks), + }; + + ok = zcbor_tstr_decode(state, &block_name); + zassert_true(ok, "Expected to get name of memory block"); + + expected_block_name_size = u8_to_dec(expected_block_name, + sizeof(expected_block_name), + receive_data->current_heap); + zassert_equal(expected_block_name_size, block_name.len, + "Expected memory block name size to match expected value size"); + zassert_mem_equal(expected_block_name, block_name.value, block_name.len, + "Expected memory block name to match expected value"); + + ok = zcbor_map_decode_bulk(state, output_decode, ARRAY_SIZE(output_decode), + &decoded) == 0; + zassert_true(ok, "Expected decode to be successful"); + +#ifdef CONFIG_MCUMGR_GRP_OS_MPSTAT_ONLY_SUPPORTED_STATS + zassert_true((decoded == 3), + "Expected to receive 3 decoded zcbor elements"); + zassert_false(zcbor_map_decode_bulk_key_found(output_decode, + ARRAY_SIZE(output_decode), "blksiz"), + "Did not expect to find blksize value"); +#else + zassert_true((decoded == 4), + "Expected to receive 4 decoded zcbor elements"); + zassert_equal(heap_data->block_size, 1, + "Expected memory block size to match expected value"); +#endif + + zassert_true(zcbor_map_decode_bulk_key_found(output_decode, + ARRAY_SIZE(output_decode), "nblks"), + "Expected to find nblks value"); + zassert_true(zcbor_map_decode_bulk_key_found(output_decode, + ARRAY_SIZE(output_decode), "nfree"), + "Expected to find nfree value"); + zassert_true(zcbor_map_decode_bulk_key_found(output_decode, + ARRAY_SIZE(output_decode), "min"), + "Expected to find min value"); + + ++receive_data->current_heap; + } + + (void)zcbor_map_end_decode(state); + + zassert_equal(receive_data->current_heap, TEST_MAX_HEAPS, "Less heaps than were expected"); + + return true; +} + +ZTEST(os_mgmt_mpstat, test_read) +{ + uint8_t buffer[ZCBOR_BUFFER_SIZE]; + uint8_t buffer_out[OUTPUT_BUFFER_SIZE]; + bool ok; + uint16_t buffer_size; + zcbor_state_t zse[ZCBOR_HISTORY_ARRAY_SIZE] = { 0 }; + zcbor_state_t zsd[ZCBOR_HISTORY_ARRAY_SIZE] = { 0 }; + bool received; + struct smp_hdr *header; + struct memory_pools_info_t receive_response = { 0 }; + size_t decoded = 0; + uint8_t i = 0; + uint8_t common_malloc_index = 255; + uint8_t kernel_malloc_index = 255; + uint32_t common_malloc_normal_size = 0; + uint32_t kernel_malloc_normal_size = 0; + uint32_t common_malloc_diff_size = 0; + uint32_t kernel_malloc_diff_size = 0; + bool found_common_malloc_area = false; + bool found_kernel_malloc_area = false; + + struct zcbor_map_decode_key_val output_decode[] = { + ZCBOR_MAP_DECODE_KEY_DECODER("tasks", parse_heap_entries, &receive_response), + }; + + memset(buffer, 0, sizeof(buffer)); + memset(buffer_out, 0, sizeof(buffer_out)); + buffer_size = 0; + memset(zse, 0, sizeof(zse)); + memset(zsd, 0, sizeof(zsd)); + + /* Test 1: Get the unused default memory pool values as a baseline */ + zcbor_new_encode_state(zse, 2, buffer, ARRAY_SIZE(buffer), 0); + ok = create_os_mgmt_mpstat_packet(zse, buffer, buffer_out, &buffer_size); + zassert_true(ok, "Expected packet creation to be successful"); + + /* Enable dummy SMP backend and ready for usage */ + smp_dummy_enable(); + smp_dummy_clear_state(); + + /* Send query command to dummy SMP backend */ + (void)smp_dummy_tx_pkt(buffer_out, buffer_size); + smp_dummy_add_data(); + + /* For a short duration to see if response has been received */ + received = smp_dummy_wait_for_data(SMP_RESPONSE_WAIT_TIME); + zassert_true(received, "Expected to receive data but timed out"); + + /* Retrieve response buffer */ + nb = smp_dummy_get_outgoing(); + smp_dummy_disable(); + + /* Check response is as expected */ + header = net_buf_pull_mem(nb, sizeof(struct smp_hdr)); + + zassert_equal(header->nh_flags, 0, "SMP header flags mismatch"); + zassert_equal(header->nh_op, MGMT_OP_READ_RSP, "SMP header operation mismatch"); + zassert_equal(header->nh_group, sys_cpu_to_be16(MGMT_GROUP_ID_OS), + "SMP header group mismatch"); + zassert_equal(header->nh_seq, 1, "SMP header sequence number mismatch"); + zassert_equal(header->nh_id, OS_MGMT_ID_MPSTAT, "SMP header command ID mismatch"); + zassert_equal(header->nh_version, 1, "SMP header version mismatch"); + + /* Get the response value to compare */ + zcbor_new_decode_state(zsd, 6, nb->data, nb->len, 1, NULL, 0); + ok = zcbor_map_decode_bulk(zsd, output_decode, ARRAY_SIZE(output_decode), &decoded) == 0; + zassert_true(ok, "Expected decode to be successful"); + zassert_equal(decoded, 1, "Expected to receive 1 decoded zcbor element"); + + while (i < receive_response.current_heap) { + struct heap_info_t *current_heap = + &receive_response.heaps[i]; + + if (current_heap->total_blocks < CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE && + current_heap->total_blocks > (CONFIG_COMMON_LIBC_MALLOC_ARENA_SIZE / 4)) { + zassert_false(found_common_malloc_area, + "Already found common malloc heap area"); + found_common_malloc_area = true; + common_malloc_normal_size = current_heap->free_blocks; + common_malloc_index = i; + } else if (current_heap->total_blocks < CONFIG_HEAP_MEM_POOL_SIZE && + current_heap->total_blocks > (CONFIG_HEAP_MEM_POOL_SIZE / 4)) { + zassert_false(found_kernel_malloc_area, + "Already found kernel malloc heap area"); + found_kernel_malloc_area = true; + kernel_malloc_normal_size = current_heap->free_blocks; + kernel_malloc_index = i; + } else { + zassert_true(false, "Cannot determine heap owner"); + } + + ++i; + } + + /* Clean up test */ + memset(buffer, 0, sizeof(buffer)); + memset(buffer_out, 0, sizeof(buffer_out)); + buffer_size = 0; + memset(zse, 0, sizeof(zse)); + memset(zsd, 0, sizeof(zsd)); + output_decode[0].found = false; + cleanup_test(NULL); + + /* Test 2: Malloc in the libc common area and ensure only that memory pool changes */ + malloc_test_object = malloc(32); + zcbor_new_encode_state(zse, 2, buffer, ARRAY_SIZE(buffer), 0); + ok = create_os_mgmt_mpstat_packet(zse, buffer, buffer_out, &buffer_size); + zassert_true(ok, "Expected packet creation to be successful"); + + /* Enable dummy SMP backend and ready for usage */ + smp_dummy_enable(); + smp_dummy_clear_state(); + + /* Send query command to dummy SMP backend */ + (void)smp_dummy_tx_pkt(buffer_out, buffer_size); + smp_dummy_add_data(); + + /* For a short duration to see if response has been received */ + received = smp_dummy_wait_for_data(SMP_RESPONSE_WAIT_TIME); + zassert_true(received, "Expected to receive data but timed out"); + + /* Retrieve response buffer */ + nb = smp_dummy_get_outgoing(); + smp_dummy_disable(); + + /* Check response is as expected */ + header = net_buf_pull_mem(nb, sizeof(struct smp_hdr)); + + zassert_equal(header->nh_flags, 0, "SMP header flags mismatch"); + zassert_equal(header->nh_op, MGMT_OP_READ_RSP, "SMP header operation mismatch"); + zassert_equal(header->nh_group, sys_cpu_to_be16(MGMT_GROUP_ID_OS), + "SMP header group mismatch"); + zassert_equal(header->nh_seq, 1, "SMP header sequence number mismatch"); + zassert_equal(header->nh_id, OS_MGMT_ID_MPSTAT, "SMP header command ID mismatch"); + zassert_equal(header->nh_version, 1, "SMP header version mismatch"); + + /* Get the response value to compare */ + zcbor_new_decode_state(zsd, 6, nb->data, nb->len, 1, NULL, 0); + ok = zcbor_map_decode_bulk(zsd, output_decode, ARRAY_SIZE(output_decode), &decoded) == 0; + zassert_true(ok, "Expected decode to be successful"); + zassert_equal(decoded, 1, "Expected to receive 1 decoded zcbor element"); + + /* Check that the common libc heap size has reduced and that the kernel heap size is + * unchanged + */ + zassert_true(receive_response.heaps[common_malloc_index].free_blocks < + common_malloc_normal_size, + "Expected non-kernel memory heap free block reduction"); + zassert_true(receive_response.heaps[kernel_malloc_index].free_blocks == + kernel_malloc_normal_size, + "Did not expect kernel memory heap free block reduction"); + common_malloc_diff_size = receive_response.heaps[common_malloc_index].total_blocks - + receive_response.heaps[common_malloc_index].free_blocks; + + /* Clean up test */ + memset(buffer, 0, sizeof(buffer)); + memset(buffer_out, 0, sizeof(buffer_out)); + buffer_size = 0; + memset(zse, 0, sizeof(zse)); + memset(zsd, 0, sizeof(zsd)); + output_decode[0].found = false; + cleanup_test(NULL); + + /* Test 3: Malloc in the kernel area and ensure only that memory pool changes */ + k_malloc_test_object = k_malloc(4); + zcbor_new_encode_state(zse, 2, buffer, ARRAY_SIZE(buffer), 0); + ok = create_os_mgmt_mpstat_packet(zse, buffer, buffer_out, &buffer_size); + zassert_true(ok, "Expected packet creation to be successful"); + + /* Enable dummy SMP backend and ready for usage */ + smp_dummy_enable(); + smp_dummy_clear_state(); + + /* Send query command to dummy SMP backend */ + (void)smp_dummy_tx_pkt(buffer_out, buffer_size); + smp_dummy_add_data(); + + /* For a short duration to see if response has been received */ + received = smp_dummy_wait_for_data(SMP_RESPONSE_WAIT_TIME); + zassert_true(received, "Expected to receive data but timed out"); + + /* Retrieve response buffer */ + nb = smp_dummy_get_outgoing(); + smp_dummy_disable(); + + /* Check response is as expected */ + header = net_buf_pull_mem(nb, sizeof(struct smp_hdr)); + + zassert_equal(header->nh_flags, 0, "SMP header flags mismatch"); + zassert_equal(header->nh_op, MGMT_OP_READ_RSP, "SMP header operation mismatch"); + zassert_equal(header->nh_group, sys_cpu_to_be16(MGMT_GROUP_ID_OS), + "SMP header group mismatch"); + zassert_equal(header->nh_seq, 1, "SMP header sequence number mismatch"); + zassert_equal(header->nh_id, OS_MGMT_ID_MPSTAT, "SMP header command ID mismatch"); + zassert_equal(header->nh_version, 1, "SMP header version mismatch"); + + /* Get the response value to compare */ + zcbor_new_decode_state(zsd, 6, nb->data, nb->len, 1, NULL, 0); + ok = zcbor_map_decode_bulk(zsd, output_decode, ARRAY_SIZE(output_decode), &decoded) == 0; + zassert_true(ok, "Expected decode to be successful"); + zassert_equal(decoded, 1, "Expected to receive 1 decoded zcbor element"); + + /* Check that the kernel heap size has reduced and that the common libc heap size is + * unchanged + */ + zassert_true(receive_response.heaps[common_malloc_index].free_blocks == + common_malloc_normal_size, + "Did not expect non-kernel memory heap free block reduction"); + zassert_true(receive_response.heaps[kernel_malloc_index].free_blocks < + kernel_malloc_normal_size, + "Expected kernel memory heap free block reduction"); + kernel_malloc_diff_size = receive_response.heaps[kernel_malloc_index].total_blocks - + receive_response.heaps[kernel_malloc_index].free_blocks; + + /* Clean up test */ + memset(buffer, 0, sizeof(buffer)); + memset(buffer_out, 0, sizeof(buffer_out)); + buffer_size = 0; + memset(zse, 0, sizeof(zse)); + memset(zsd, 0, sizeof(zsd)); + output_decode[0].found = false; + cleanup_test(NULL); + + /* Test 4: Check after both mallocs and frees that the values match the default */ + zcbor_new_encode_state(zse, 2, buffer, ARRAY_SIZE(buffer), 0); + ok = create_os_mgmt_mpstat_packet(zse, buffer, buffer_out, &buffer_size); + zassert_true(ok, "Expected packet creation to be successful"); + + /* Enable dummy SMP backend and ready for usage */ + smp_dummy_enable(); + smp_dummy_clear_state(); + + /* Send query command to dummy SMP backend */ + (void)smp_dummy_tx_pkt(buffer_out, buffer_size); + smp_dummy_add_data(); + + /* For a short duration to see if response has been received */ + received = smp_dummy_wait_for_data(SMP_RESPONSE_WAIT_TIME); + zassert_true(received, "Expected to receive data but timed out"); + + /* Retrieve response buffer */ + nb = smp_dummy_get_outgoing(); + smp_dummy_disable(); + + /* Check response is as expected */ + header = net_buf_pull_mem(nb, sizeof(struct smp_hdr)); + + zassert_equal(header->nh_flags, 0, "SMP header flags mismatch"); + zassert_equal(header->nh_op, MGMT_OP_READ_RSP, "SMP header operation mismatch"); + zassert_equal(header->nh_group, sys_cpu_to_be16(MGMT_GROUP_ID_OS), + "SMP header group mismatch"); + zassert_equal(header->nh_seq, 1, "SMP header sequence number mismatch"); + zassert_equal(header->nh_id, OS_MGMT_ID_MPSTAT, "SMP header command ID mismatch"); + zassert_equal(header->nh_version, 1, "SMP header version mismatch"); + + /* Get the response value to compare */ + zcbor_new_decode_state(zsd, 6, nb->data, nb->len, 1, NULL, 0); + ok = zcbor_map_decode_bulk(zsd, output_decode, ARRAY_SIZE(output_decode), &decoded) == 0; + zassert_true(ok, "Expected decode to be successful"); + zassert_equal(decoded, 1, "Expected to receive 1 decoded zcbor element"); + + /* Check that both heap sizes are unchanged from the original values */ + zassert_true(receive_response.heaps[common_malloc_index].free_blocks == + common_malloc_normal_size, + "Did not expect non-kernel memory heap free block reduction"); + zassert_true(receive_response.heaps[kernel_malloc_index].free_blocks == + kernel_malloc_normal_size, + "Did not expect kernel memory heap free block reduction"); + + /* Clean up test */ + memset(buffer, 0, sizeof(buffer)); + memset(buffer_out, 0, sizeof(buffer_out)); + buffer_size = 0; + memset(zse, 0, sizeof(zse)); + memset(zsd, 0, sizeof(zsd)); + output_decode[0].found = false; + + /* Ensure that a smaller malloc used less free blocks */ + zassert_true(kernel_malloc_diff_size < common_malloc_diff_size, + "Expected small kernel malloc to be smaller than larger libc malloc"); +} + +ZTEST_SUITE(os_mgmt_mpstat, NULL, NULL, NULL, cleanup_test, NULL); diff --git a/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/smp_test_util.c b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/smp_test_util.c new file mode 100644 index 0000000000000..bff4c2833e34d --- /dev/null +++ b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/smp_test_util.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022-2023 Nordic Semiconductor ASA + * Copyright (c) 2025, Jamie McCrae + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "smp_test_util.h" +#include +#include +#include +#include + +/* SMP header function for generating MCUmgr command header with sequence number set to 1 */ +static void smp_make_hdr(struct smp_hdr *rsp_hdr, size_t len, uint8_t type) +{ + *rsp_hdr = (struct smp_hdr) { + .nh_len = sys_cpu_to_be16(len), + .nh_flags = 0, + .nh_op = MGMT_OP_READ, + .nh_group = sys_cpu_to_be16(MGMT_GROUP_ID_OS), + .nh_seq = 1, + .nh_id = type, + .nh_version = 1, + }; +} + +bool create_os_mgmt_mpstat_packet(zcbor_state_t *zse, uint8_t *buffer, uint8_t *output_buffer, + uint16_t *buffer_size) +{ + bool ok; + + ok = zcbor_map_start_encode(zse, 2) && + zcbor_map_end_encode(zse, 2); + + *buffer_size = (zse->payload_mut - buffer); + smp_make_hdr((struct smp_hdr *)output_buffer, *buffer_size, OS_MGMT_ID_MPSTAT); + memcpy(&output_buffer[sizeof(struct smp_hdr)], buffer, *buffer_size); + *buffer_size += sizeof(struct smp_hdr); + + return ok; +} diff --git a/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/smp_test_util.h b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/smp_test_util.h new file mode 100644 index 0000000000000..ecb4449d39f33 --- /dev/null +++ b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/src/smp_test_util.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef H_SMP_TEST_UTIL_ +#define H_SMP_TEST_UTIL_ + +#include +#include +#include +#include + +/* Function for creating an os_mgmt memorypool command */ +bool create_os_mgmt_mpstat_packet(zcbor_state_t *zse, uint8_t *buffer, uint8_t *output_buffer, + uint16_t *buffer_size); + +#endif diff --git a/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/testcase.yaml b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/testcase.yaml new file mode 100644 index 0000000000000..3632d2c60aeaf --- /dev/null +++ b/tests/subsys/mgmt/mcumgr/os_mgmt_mpstat/testcase.yaml @@ -0,0 +1,25 @@ +# +# Copyright (c) 2025, Jamie McCrae +# +# SPDX-License-Identifier: Apache-2.0 +# + +tests: + os.mgmt.mpstat: + platform_allow: + - native_sim + - native_sim/native/64 + - nrf52840dk/nrf52840 + tags: + - os_mgmt + - mcumgr + os.mgmt.mpstat.all_fields: + platform_allow: + - native_sim + - native_sim/native/64 + - nrf52840dk/nrf52840 + extra_configs: + - CONFIG_MCUMGR_GRP_OS_MPSTAT_ONLY_SUPPORTED_STATS=n + tags: + - os_mgmt + - mcumgr