MCUmgr handlers

Overview

MCUmgr functions by having group handlers which identify a group of functions relating to a specific management area, which is addressed with a 16-bit identification value, mcumgr_group_t contains the management groups available in Zephyr with their corresponding group ID values. The group ID is included in SMP headers to identify which group a command belongs to, there is also an 8-bit command ID which identifies the function of that group to execute - see SMP Protocol Specification for details on the SMP protocol and header. There can only be one registered group per unique ID.

Implementation

MCUmgr handlers can be added externally by application code or by module code, they do not have to reside in the upstream Zephyr tree to be usable. The first step to creating a handler is to create the folder structure for it, the typical Zephyr MCUmgr group layout is as follows:

<dir>/grp/<grp_name>_mgmt/
├── CMakeLists.txt
├── Kconfig
├── include
├──── <grp_name>_mgmt.h
├──── <grp_name>_mgmt_callbacks.h
├── src
└──── <grp_name>_mgmt.c

Note that the header files in upstream Zephyr MCUmgr handlers reside in the zephyr/include/zephyr/mgmt/mcumgr/grp/<grp_name>_mgmt directory to allow the files to be globally included by applications.

Initial header <grp_name>_mgmt.h

The purpose of the header file is to provide defines which can be used by the MCUmgr handler itself and application code, e.g. to reference the command IDs for executing functions. An example file would look similar to:

 1/*
 2 * Copyright (c) 2023 Nordic Semiconductor ASA
 3 *
 4 * SPDX-License-Identifier: Apache-2.0
 5 */
 6#ifndef H_EXAMPLE_MGMT_
 7#define H_EXAMPLE_MGMT_
 8
 9#ifdef __cplusplus
10extern "C" {
11#endif
12
13/**
14 * Group ID for example management group.
15 */
16#define MGMT_GROUP_ID_EXAMPLE  MGMT_GROUP_ID_PERUSER
17
18/**
19 * Command IDs for example management group.
20 */
21#define EXAMPLE_MGMT_ID_TEST   0
22#define EXAMPLE_MGMT_ID_OTHER  1
23
24/**
25 * Command result codes for example management group.
26 */
27enum example_mgmt_err_code_t {
28	/** No error, this is implied if there is no ret value in the response */
29	EXAMPLE_MGMT_ERR_OK = 0,
30
31	/** Unknown error occurred. */
32	EXAMPLE_MGMT_ERR_UNKNOWN,
33
34	/** The provided value is not wanted at this time. */
35	EXAMPLE_MGMT_ERR_NOT_WANTED,
36
37	/** The provided value was rejected by a hook. */
38	EXAMPLE_MGMT_ERR_REJECTED_BY_HOOK,
39};
40
41#ifdef __cplusplus
42}
43#endif
44
45#endif

This provides the defines for 2 command test and other and sets up the SMP version 2 error responses (which have unique error codes per group as opposed to the legacy SMP version 1 error responses that return a mcumgr_err_t - there should always be an OK error code with the value 0 and an unknown error code with the value 1. The above example then adds an error code of not wanted with value 2. In addition, the group ID is set to be MGMT_GROUP_ID_PERUSER, which is the start group ID for user-defined groups, note that group IDs need to be unique so other custom groups should use different values, a central index header file (as upstream Zephyr has) can be used to distribute group IDs more easily.

Initial header <grp_name>_mgmt_callbacks.h

The purpose of the header file is to provide defines which can be used by the MCUmgr handler itself and application code, e.g. to reference the command IDs for executing functions. An example file would look similar to:

 1/*
 2 * Copyright (c) 2023 Nordic Semiconductor ASA
 3 *
 4 * SPDX-License-Identifier: Apache-2.0
 5 */
 6#ifndef H_MCUMGR_EXAMPLE_MGMT_CALLBACKS_
 7#define H_MCUMGR_EXAMPLE_MGMT_CALLBACKS_
 8#include <stdint.h>
 9#include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
10
11#ifdef __cplusplus
12extern "C" {
13#endif
14
15/* This is the event ID for the example group */
16#define MGMT_EVT_GRP_EXAMPLE MGMT_EVT_GRP_USER_CUSTOM_START
17
18/* MGMT event opcodes for example management group */
19enum example_mgmt_group_events {
20	/* Callback when the other command is received, data is example_mgmt_other_data */
21	MGMT_EVT_OP_EXAMPLE_OTHER = MGMT_DEF_EVT_OP_ID(MGMT_EVT_GRP_EXAMPLE, 0),
22
23	/* Used to enable all smp_group events */
24	MGMT_EVT_OP_EXAMPLE_MGMT_ALL  = MGMT_DEF_EVT_OP_ALL(MGMT_EVT_GRP_EXAMPLE),
25};
26
27/* Structure provided in the #MGMT_EVT_OP_EXAMPLE_OTHER notification callback */
28struct example_mgmt_other_data {
29	/* Contains the user supplied value */
30	uint32_t user_value;
31};
32
33#ifdef __cplusplus
34}
35#endif
36
37#endif

This sets up a single event which application (or module) code can register for to receive a callback when the function handler is executed, which allows the flow of the handler to be changed (i.e. to return an error instead of continuing). The event group ID is set to MGMT_EVT_GRP_USER_CUSTOM_START, which is the start event ID for user-defined groups, note that event IDs need to be unique so other custom groups should use different values, a central index header file (as upstream Zephyr has) can be used to distribute event IDs more easily.

Initial source <grp_name>_mgmt.c

The purpose of this source file is to handle the incoming MCUmgr commands, provide responses, and register the transport with MCUmgr so that commands will be sent to it. An example file would look similar to:

  1/*
  2 * Copyright (c) 2023 Nordic Semiconductor ASA
  3 *
  4 * SPDX-License-Identifier: Apache-2.0
  5 */
  6#include <zephyr/kernel.h>
  7#include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
  8#include <zephyr/mgmt/mcumgr/smp/smp.h>
  9#include <zephyr/mgmt/mcumgr/mgmt/handlers.h>
 10/* The below should be updated with the real name of the file */
 11#include <example_mgmt.h>
 12#include <zephyr/logging/log.h>
 13#include <assert.h>
 14#include <limits.h>
 15#include <string.h>
 16#include <stdio.h>
 17#include <zcbor_common.h>
 18#include <zcbor_decode.h>
 19#include <zcbor_encode.h>
 20#include <mgmt/mcumgr/util/zcbor_bulk.h>
 21
 22#if defined(CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS)
 23#include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
 24#if defined(CONFIG_MCUMGR_GRP_EXAMPLE_OTHER_HOOK)
 25/* The below should be updated with the real name of the file */
 26#include <example_mgmt_callbacks.h>
 27#endif
 28#endif
 29
 30LOG_MODULE_REGISTER(mcumgr_example_grp, CONFIG_MCUMGR_GRP_EXAMPLE_LOG_LEVEL);
 31/* Example function with "read" command support, requires both parameters are supplied */
 32static int example_mgmt_test(struct smp_streamer *ctxt)
 33{
 34	uint32_t uint_value = 0;
 35	zcbor_state_t *zse = ctxt->writer->zs;
 36	zcbor_state_t *zsd = ctxt->reader->zs;
 37	bool ok;
 38	struct zcbor_string string_value = { 0 };
 39	size_t decoded;
 40	struct zcbor_map_decode_key_val example_test_decode[] = {
 41		ZCBOR_MAP_DECODE_KEY_DECODER("uint_key", zcbor_uint32_decode, &uint_value),
 42		ZCBOR_MAP_DECODE_KEY_DECODER("string_key", zcbor_tstr_decode, &string_value),
 43	};
 44
 45	LOG_DBG("Example test function called");
 46
 47	ok = zcbor_map_decode_bulk(zsd, example_test_decode, ARRAY_SIZE(example_test_decode),
 48				   &decoded) == 0;
 49	/* Check that both parameters were supplied and that the value of "string_key" is not
 50	 * empty
 51	 */
 52	if (!ok || string_value.len == 0 || !zcbor_map_decode_bulk_key_found(
 53			   example_test_decode, ARRAY_SIZE(example_test_decode), "uint_key")) {
 54		return MGMT_ERR_EINVAL;
 55	}
 56
 57	/* If the value of "uint_key" is over 50, return an error of "not wanted" */
 58	if (uint_value > 50) {
 59		ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_EXAMPLE, EXAMPLE_MGMT_ERR_NOT_WANTED);
 60		goto end;
 61	}
 62
 63	/* Otherwise, return an integer value of 4691 */
 64	ok = zcbor_tstr_put_lit(zse, "return_int") &&
 65	     zcbor_int32_put(zse, 4691);
 66
 67end:
 68	/* If "ok" is false, then there was an error processing the output cbor message, which
 69	 * likely indicates a lack of available memory
 70	 */
 71	return (ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE);
 72}
 73
 74/* Example function with "write" command support */
 75static int example_mgmt_other(struct smp_streamer *ctxt)
 76{
 77	uint32_t user_value = 0;
 78	zcbor_state_t *zse = ctxt->writer->zs;
 79	zcbor_state_t *zsd = ctxt->reader->zs;
 80	bool ok;
 81	size_t decoded;
 82	struct zcbor_map_decode_key_val example_other_decode[] = {
 83		ZCBOR_MAP_DECODE_KEY_DECODER("user_value", zcbor_uint32_decode, &user_value),
 84	};
 85
 86#if defined(CONFIG_MCUMGR_GRP_EXAMPLE_OTHER_HOOK)
 87	struct example_mgmt_other_data other_data;
 88	enum mgmt_cb_return status;
 89	int32_t err_rc;
 90	uint16_t err_group;
 91#endif
 92
 93	LOG_DBG("Example other function called");
 94
 95	ok = zcbor_map_decode_bulk(zsd, example_other_decode, ARRAY_SIZE(example_other_decode),
 96				   &decoded) == 0;
 97
 98	/* The supplied value is optional, therefore do not return an error if it was not
 99	 * provided
100	 */
101	if (!ok) {
102		return MGMT_ERR_EINVAL;
103	}
104
105#if defined(CONFIG_MCUMGR_GRP_EXAMPLE_OTHER_HOOK)
106	/* Send request to application to check what to do */
107	other_data.user_value = user_value;
108	status = mgmt_callback_notify(MGMT_EVT_OP_EXAMPLE_OTHER, &other_data, sizeof(other_data),
109				      &err_rc, &err_group);
110	if (status != MGMT_CB_OK) {
111		/* If a callback returned an RC error, exit out, if it returned a group error
112		 * code, add the error code to the response and return to the calling function to
113		 * have it sent back to the client
114		 */
115		if (status == MGMT_CB_ERROR_RC) {
116			return err_rc;
117		}
118
119		ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc);
120		goto end;
121	}
122#endif
123	/* Return some dummy data to the client */
124	ok = zcbor_tstr_put_lit(zse, "return_string") &&
125	     zcbor_tstr_put_lit(zse, "some dummy data!");
126
127#if defined(CONFIG_MCUMGR_GRP_EXAMPLE_OTHER_HOOK)
128end:
129#endif
130	/* If "ok" is false, then there was an error processing the output cbor message, which
131	 * likely indicates a lack of available memory
132	 */
133	return (ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE);
134}
135
136#ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
137/* This is a lookup function that converts from SMP version 2 group error codes to legacy
138 * MCUmgr error codes, it is only included if support for the original protocol is enabled.
139 * Note that in SMP version 2, MCUmgr error codes can still be returned, but are to be used
140 * only for general SMP/MCUmgr errors. The success/OK error code is not used in translation
141 * functions as it is automatically handled by the base SMP code.
142 */
143static int example_mgmt_translate_error_code(uint16_t err)
144{
145	int rc;
146
147	switch (err) {
148	case EXAMPLE_MGMT_ERR_NOT_WANTED:
149		rc = MGMT_ERR_ENOENT;
150		break;
151
152	case EXAMPLE_MGMT_ERR_REJECTED_BY_HOOK:
153		rc = MGMT_ERR_EBADSTATE;
154		break;
155
156	case EXAMPLE_MGMT_ERR_UNKNOWN:
157	default:
158		rc = MGMT_ERR_EUNKNOWN;
159	}
160
161	return rc;
162}
163#endif
164
165static const struct mgmt_handler example_mgmt_handlers[] = {
166	[EXAMPLE_MGMT_ID_TEST] = {
167		.mh_read = example_mgmt_test,
168		.mh_write = NULL,
169	},
170	[EXAMPLE_MGMT_ID_OTHER] = {
171		.mh_read = NULL,
172		.mh_write = example_mgmt_other,
173	},
174};
175
176static struct mgmt_group example_mgmt_group = {
177	.mg_handlers = example_mgmt_handlers,
178	.mg_handlers_count = ARRAY_SIZE(example_mgmt_handlers),
179	.mg_group_id = MGMT_GROUP_ID_EXAMPLE,
180#ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL
181	.mg_translate_error = example_mgmt_translate_error_code,
182#endif
183};
184
185static void example_mgmt_register_group(void)
186{
187	/* This function is called during system init before main() is invoked, if the
188	 * handler needs to set anything up before it can be used, it should do it here.
189	 * This register the group so that clients can call the function handlers.
190	 */
191	mgmt_register_group(&example_mgmt_group);
192}
193
194MCUMGR_HANDLER_DEFINE(example_mgmt, example_mgmt_register_group);

The above code creates 2 function handlers, test which supports read requests and takes 2 required parameters, and other which supports write requests and takes 1 optional parameter, this function handler has an optional notification callback feature that allows other parts of the code to listen for the event and take any required actions that are necessary or prevent further execution of the function by returning an error, further details on MCUmgr callback functionality can be found on MCUmgr Callbacks.

Note that other code referencing callbacks for custom MCUmgr handlers needs to include both the base Zephyr callback include file and the custom handler callback file, only in-tree Zephyr handler headers are included when including the upstream Zephyr callback header file.

Initial Kconfig

The purpose of the Kconfig file is to provide options which users can enable or change relating to the functionality of the handler being implemented. An example file would look similar to:

# Copyright Nordic Semiconductor ASA 2023. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
# The Kconfig file is dedicated to example management group of
# of MCUmgr subsystem and provides Kconfig options to configure
# group commands behaviour and other aspects.
#
# Options defined in this file should be prefixed:
#  MCUMGR_GRP_EXAMPLE_ -- general group options;
#
# When adding Kconfig options, that control the same feature,
# try to group them together by the same stem after prefix.
if MCUMGR

menuconfig MCUMGR_GRP_EXAMPLE_APP
	bool "MCUmgr handlers for example management (app)"
	select MCUMGR_SMP_CBOR_MIN_DECODING_LEVEL_2
	default y
	help
	  Enables MCUmgr handlers for example management. This demonstrates the
	  file at application-level.

if MCUMGR_GRP_EXAMPLE_APP
config MCUMGR_GRP_EXAMPLE_OTHER_HOOK
	bool "Other hook"
	depends on MCUMGR_MGMT_NOTIFICATION_HOOKS
	help
	  Allows applications to receive callback when the "other" example
	  management function is called

module = MCUMGR_GRP_EXAMPLE
module-str = mcumgr_grp_example
source "subsys/logging/Kconfig.template.log_config"

endif

endif

source "Kconfig.zephyr"

Initial CMakeLists.txt

The CMakeLists.txt file is used by the build system to setup files to compile, include directories to add and specify options that can be changed. A basic file only need to include the source files if the Kconfig options are enabled. An example file would look similar to:

# Copyright (c) 2023 Nordic Semiconductor ASA
# SPDX-License-Identifier: Apache-2.0

if(CONFIG_MCUMGR_GRP_EXAMPLE_MODULE)
  zephyr_library(mgmt_mcumgr_grp_example)
  # The below should be updated with the real name of the file
  zephyr_library_sources(src/example_mgmt.c)
  zephyr_include_directories(include)
endif()

Including from application

Application-specific MCUmgr handlers can be added by creating/editing application build files. Example modifications are shown below.

Example CMakeLists.txt

The application CMakeLists.txt file can load the CMake file for the example MCUmgr handler by adding the following:

add_subdirectory(mcumgr/grp/<grp_name>)

Example Kconfig

The application Kconfig file can include the Kconfig file for the example MCUmgr handler by adding the following to the Kconfig file in the application directory (or creating it if it does not exist):

rsource "mcumgr/grp/<grp_name>/Kconfig"

# Include Zephyr's Kconfig
source "Kconfig.zephyr"

Including from Zephyr Module

Zephyr Modules (External projects) can be used to add custom MCUmgr handlers to multiple different applications without needing to duplicate the code in each application’s source tree, see Module yaml file description for details on how to set up the module files. Example files are shown below.

Example zephyr/module.yml

This is an example file which can be used to load the Kconfig and CMake files from the root of the module directory, and would be placed at zephyr/module.yml:

build:
  kconfig: Kconfig
  cmake: .

Example CMakeLists.txt

This is an example CMakeLists.txt file which loads the CMake file for the example MCUmgr handler, and would be placed at CMakeLists.txt:

add_subdirectory(mcumgr/grp/<grp_name>)

Example Kconfig

This is an example Kconfig file which loads the Kconfig file for the example MCUmgr handler, and would be placed at Kconfig:

rsource "mcumgr/grp/<grp_name>/Kconfig"

Demonstration handler

There is a demonstration project which includes configuration for both application and zephyr module-MCUmgr handlers which can be used as a basis for created your own in tests/subsys/mgmt/mcumgr/handler_demo/.