| .. _mcumgr_callbacks: |
| |
| MCUmgr Callbacks |
| ################ |
| |
| Overview |
| ******** |
| |
| MCUmgr has a customisable callback/notification system that allows application |
| (and module) code to receive callbacks for MCUmgr events that they are |
| interested in and react to them or return a status code to the calling function |
| that provides control over if the action should be allowed or not. An example |
| of this is with the fs_mgmt group, whereby file access can be gated, the |
| callback allows the application to inspect the request path and allow or deny |
| access to said file, or it can rewrite the provided path to a different path |
| for transparent file redirection support. |
| |
| Implementation |
| ************** |
| |
| Enabling |
| ======== |
| |
| The base callback/notification system can be enabled using |
| :kconfig:option:`CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS` which will compile the |
| registration and notification system into the code. This will not provide any |
| callbacks by default as the callbacks that are supported by a build must also |
| be selected by enabling the Kconfig's for the required callbacks (see |
| :ref:`mcumgr_cb_events` for further details). A callback function with the |
| :c:type:`mgmt_cb` type definition can then be declared and registered by |
| calling :c:func:`mgmt_callback_register` for the desired event inside of a |
| :c:struct`mgmt_callback` structure. Handlers are called in the order that they |
| were registered. |
| |
| With the system enabled, a basic handler can be set up and defined in |
| application code as per: |
| |
| .. code-block:: c |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/mgmt/mcumgr/mgmt/mgmt.h> |
| #include <zephyr/mgmt/mcumgr/mgmt/callbacks.h> |
| |
| struct mgmt_callback my_callback; |
| |
| enum mgmt_cb_return my_function(uint32_t event, enum mgmt_cb_return prev_status, |
| int32_t *rc, uint16_t *group, bool *abort_more, |
| void *data, size_t data_size) |
| { |
| if (event == MGMT_EVT_OP_CMD_DONE) { |
| /* This is the event we registered for */ |
| } |
| |
| /* Return OK status code to continue with acceptance to underlying handler */ |
| return MGMT_CB_OK; |
| } |
| |
| int main() |
| { |
| my_callback.callback = my_function; |
| my_callback.event_id = MGMT_EVT_OP_CMD_DONE; |
| mgmt_callback_register(&my_callback); |
| } |
| |
| This code registers a handler for the :c:enumerator:`MGMT_EVT_OP_CMD_DONE` |
| event, which will be called after a MCUmgr command has been processed and |
| output generated, note that this requires that |
| :kconfig:option:`CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS` be enabled to receive |
| this callback. |
| |
| Multiple callbacks can be setup to use a single function as a common callback, |
| and many different functions can be used for each event by registering each |
| group once, or all notifications for a whole group can be enabled by using one |
| of the ``MGMT_EVT_OP_*_ALL`` events, alternatively a handler can setup for |
| every notification by using :c:enumerator:`MGMT_EVT_OP_ALL`. When setting up |
| handlers, events can be combined that are in the same group only, for example |
| 5 img_mgmt callbacks can be setup with a single registration call, but to also |
| setup a callback for an os_mgmt callback, this must be done as a separate |
| registration. Group IDs are numerical increments, event IDs are bitmask values, |
| hence the restriction. |
| |
| As an example, the following registration is allowed, which will register for 3 |
| SMP events with a single callback function in a single registration: |
| |
| .. code-block:: c |
| |
| my_callback.callback = my_function; |
| my_callback.event_id = (MGMT_EVT_OP_CMD_RECV | |
| MGMT_EVT_OP_CMD_STATUS | |
| MGMT_EVT_OP_CMD_DONE); |
| mgmt_callback_register(&my_callback); |
| |
| The following code is not allowed, and will cause undefined operation, because |
| it mixes the IMG management group with the OS management group whereby the |
| group is **not** a bitmask value, only the event is: |
| |
| .. code-block:: c |
| |
| my_callback.callback = my_function; |
| my_callback.event_id = (MGMT_EVT_OP_IMG_MGMT_DFU_STARTED | |
| MGMT_EVT_OP_OS_MGMT_RESET); |
| mgmt_callback_register(&my_callback); |
| |
| .. _mcumgr_cb_events: |
| |
| Events |
| ====== |
| |
| Events can be selected by enabling their corresponding Kconfig option: |
| |
| - :kconfig:option:`CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS` |
| MCUmgr command status (:c:enumerator:`MGMT_EVT_OP_CMD_RECV`, |
| :c:enumerator:`MGMT_EVT_OP_CMD_STATUS`, |
| :c:enumerator:`MGMT_EVT_OP_CMD_DONE`) |
| - :kconfig:option:`CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK` |
| fs_mgmt file access (:c:enumerator:`MGMT_EVT_OP_FS_MGMT_FILE_ACCESS`) |
| - :kconfig:option:`CONFIG_MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK` |
| img_mgmt upload check (:c:enumerator:`MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK`) |
| - :kconfig:option:`CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS` |
| img_mgmt upload status (:c:enumerator:`MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED`, |
| :c:enumerator:`MGMT_EVT_OP_IMG_MGMT_DFU_STARTED`, |
| :c:enumerator:`MGMT_EVT_OP_IMG_MGMT_DFU_PENDING`, |
| :c:enumerator:`MGMT_EVT_OP_IMG_MGMT_DFU_CONFIRMED`) |
| - :kconfig:option:`CONFIG_MCUMGR_GRP_OS_RESET_HOOK` |
| os_mgmt reset check (:c:enumerator:`MGMT_EVT_OP_OS_MGMT_RESET`) |
| - :kconfig:option:`CONFIG_MCUMGR_GRP_SETTINGS_ACCESS_HOOK` |
| settings_mgmt access (:c:enumerator:`MGMT_EVT_OP_SETTINGS_MGMT_ACCESS`) |
| |
| Actions |
| ======= |
| |
| Some callbacks expect a return status to either allow or disallow an operation, |
| an example is the fs_mgmt access hook which allows for access to files to be |
| allowed or denied. With these handlers, the first non-OK error code returned |
| by a handler will be returned to the MCUmgr client. |
| |
| An example of selectively denying file access: |
| |
| .. code-block:: c |
| |
| #include <zephyr/kernel.h> |
| #include <zephyr/mgmt/mcumgr/mgmt/mgmt.h> |
| #include <zephyr/mgmt/mcumgr/mgmt/callbacks.h> |
| #include <string.h> |
| |
| struct mgmt_callback my_callback; |
| |
| enum mgmt_cb_return my_function(uint32_t event, enum mgmt_cb_return prev_status, |
| int32_t *rc, uint16_t *group, bool *abort_more, |
| void *data, size_t data_size) |
| { |
| /* Only run this handler if a previous handler has not failed */ |
| if (event == MGMT_EVT_OP_FS_MGMT_FILE_ACCESS && prev_status == MGMT_CB_OK) { |
| struct fs_mgmt_file_access *fs_data = (struct fs_mgmt_file_access *)data; |
| |
| /* Check if this is an upload and deny access if it is, otherwise check |
| * the path and deny if is matches a name |
| */ |
| if (fs_data->access == FS_MGMT_FILE_ACCESS_WRITE) { |
| /* Return an access denied error code to the client and abort calling |
| * further handlers |
| */ |
| *abort_more = true; |
| *rc = MGMT_ERR_EACCESSDENIED; |
| |
| return MGMT_CB_ERROR_RC; |
| } else if (strcmp(fs_data->filename, "/lfs1/false_deny.txt") == 0) { |
| /* Return a no entry error code to the client, call additional handlers |
| * (which will have failed set to true) |
| */ |
| *rc = MGMT_ERR_ENOENT; |
| |
| return MGMT_CB_ERROR_RC; |
| } |
| } |
| |
| /* Return OK status code to continue with acceptance to underlying handler */ |
| return MGMT_CB_OK; |
| } |
| |
| int main() |
| { |
| my_callback.callback = my_function; |
| my_callback.event_id = MGMT_EVT_OP_FS_MGMT_FILE_ACCESS; |
| mgmt_callback_register(&my_callback); |
| } |
| |
| This code registers a handler for the |
| :c:enumerator:`MGMT_EVT_OP_FS_MGMT_FILE_ACCESS` event, which will be called |
| after a fs_mgmt file read/write command has been received to check if access to |
| the file should be allowed or not, note that this requires that |
| :kconfig:option:`CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK` be enabled to receive |
| this callback. |
| Two types of errors can be returned, the ``rc`` parameter can be set to an |
| :c:enumerator:`mcumgr_err_t` error code and :c:enumerator:`MGMT_CB_ERROR_RC` |
| can be returned, or a group error code (introduced with version 2 of the MCUmgr |
| protocol) can be set by setting the ``group`` value to the group and ``rc`` |
| value to the group error code and returning :c:enumerator:`MGMT_CB_ERROR_ERR`. |
| |
| MCUmgr Command Callback Usage/Adding New Event Types |
| ==================================================== |
| |
| To add a callback to a MCUmgr command, :c:func:`mgmt_callback_notify` can be |
| called with the event ID and, optionally, a data struct to pass to the callback |
| (which can be modified by handlers). If no data needs to be passed back, |
| ``NULL`` can be used instead, and size of the data set to 0. |
| |
| An example MCUmgr command handler: |
| |
| .. code-block:: c |
| |
| #include <zephyr/kernel.h> |
| #include <zcbor_common.h> |
| #include <zcbor_encode.h> |
| #include <zephyr/mgmt/mcumgr/smp/smp.h> |
| #include <zephyr/mgmt/mcumgr/mgmt/mgmt.h> |
| #include <zephyr/mgmt/mcumgr/mgmt/callbacks.h> |
| |
| #define MGMT_EVT_GRP_USER_ONE MGMT_EVT_GRP_USER_CUSTOM_START |
| |
| enum user_one_group_events { |
| /** Callback on first post, data is test_struct. */ |
| MGMT_EVT_OP_USER_ONE_FIRST = MGMT_DEF_EVT_OP_ID(MGMT_EVT_GRP_USER_ONE, 0), |
| |
| /** Callback on second post, data is test_struct. */ |
| MGMT_EVT_OP_USER_ONE_SECOND = MGMT_DEF_EVT_OP_ID(MGMT_EVT_GRP_USER_ONE, 1), |
| |
| /** Used to enable all user_one events. */ |
| MGMT_EVT_OP_USER_ONE_ALL = MGMT_DEF_EVT_OP_ALL(MGMT_EVT_GRP_USER_ONE), |
| }; |
| |
| struct test_struct { |
| uint8_t some_value; |
| }; |
| |
| static int test_command(struct mgmt_ctxt *ctxt) |
| { |
| int rc; |
| int err_rc; |
| uint16_t err_group; |
| zcbor_state_t *zse = ctxt->cnbe->zs; |
| bool ok; |
| struct test_struct test_data = { |
| .some_value = 8, |
| }; |
| |
| rc = mgmt_callback_notify(MGMT_EVT_OP_USER_ONE_FIRST, &test_data, |
| sizeof(test_data), &err_rc, &err_group); |
| |
| if (rc != MGMT_CB_OK) { |
| /* A handler returned a failure code */ |
| if (rc == MGMT_CB_ERROR_RC) { |
| /* The failure code is the RC value */ |
| return err_rc; |
| } |
| |
| /* The failure is a group and ID error value */ |
| ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc); |
| goto end; |
| } |
| |
| /* All handlers returned success codes */ |
| ok = zcbor_tstr_put_lit(zse, "output_value") && |
| zcbor_int32_put(zse, 1234); |
| |
| end: |
| rc = (ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE); |
| |
| return rc; |
| } |
| |
| If no response is required for the callback, the function call be called and |
| casted to void. |
| |
| .. _mcumgr_cb_migration: |
| |
| Migration |
| ********* |
| |
| If there is existing code using the previous callback system(s) in Zephyr 3.2 |
| or earlier, then it will need to be migrated to the new system. To migrate |
| code, the following callback registration functions will need to be migrated |
| to register for callbacks using :c:func:`mgmt_callback_register` (note that |
| :kconfig:option:`CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS` will need to be set to |
| enable the new notification system in addition to any migrations): |
| |
| * mgmt_evt |
| Using :c:enumerator:`MGMT_EVT_OP_CMD_RECV`, |
| :c:enumerator:`MGMT_EVT_OP_CMD_STATUS`, or |
| :c:enumerator:`MGMT_EVT_OP_CMD_DONE` as drop-in replacements for events of |
| the same name, where the provided data is :c:struct:`mgmt_evt_op_cmd_arg`. |
| :kconfig:option:`CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS` needs to be set. |
| * fs_mgmt_register_evt_cb |
| Using :c:enumerator:`MGMT_EVT_OP_FS_MGMT_FILE_ACCESS` where the provided |
| data is :c:struct:`fs_mgmt_file_access`. Instead of returning true to allow |
| the action or false to deny, a MCUmgr result code needs to be returned, |
| :c:enumerator:`MGMT_ERR_EOK` will allow the action, any other return code |
| will disallow it and return that code to the client |
| (:c:enumerator:`MGMT_ERR_EACCESSDENIED` can be used for an access denied |
| error). :kconfig:option:`CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS` needs to be |
| set. |
| * img_mgmt_register_callbacks |
| Using :c:enumerator:`MGMT_EVT_OP_IMG_MGMT_DFU_STARTED` if |
| ``dfu_started_cb`` was used, |
| :c:enumerator:`MGMT_EVT_OP_IMG_MGMT_DFU_STOPPED` if ``dfu_stopped_cb`` was |
| used, :c:enumerator:`MGMT_EVT_OP_IMG_MGMT_DFU_PENDING` if |
| ``dfu_pending_cb`` was used or |
| :c:enumerator:`MGMT_EVT_OP_IMG_MGMT_DFU_CONFIRMED` if ``dfu_confirmed_cb`` |
| was used. These callbacks do not have any return status. |
| :kconfig:option:`CONFIG_MCUMGR_GRP_IMG_STATUS_HOOKS` needs to be set. |
| * img_mgmt_set_upload_cb |
| Using :c:enumerator:`MGMT_EVT_OP_IMG_MGMT_DFU_CHUNK` where the provided |
| data is :c:struct:`img_mgmt_upload_check`. Instead of returning true to |
| allow the action or false to deny, a MCUmgr result code needs to be |
| returned, :c:enumerator:`MGMT_ERR_EOK` will allow the action, any other |
| return code will disallow it and return that code to the client |
| (:c:enumerator:`MGMT_ERR_EACCESSDENIED` can be used for an access denied |
| error). :kconfig:option:`CONFIG_MCUMGR_GRP_IMG_UPLOAD_CHECK_HOOK` needs to |
| be set. |
| * os_mgmt_register_reset_evt_cb |
| Using :c:enumerator:`MGMT_EVT_OP_OS_MGMT_RESET`. Instead of returning |
| true to allow the action or false to deny, a MCUmgr result code needs to be |
| returned, :c:enumerator:`MGMT_ERR_EOK` will allow the action, any other |
| return code will disallow it and return that code to the client |
| (:c:enumerator:`MGMT_ERR_EACCESSDENIED` can be used for an access denied |
| error). :kconfig:option:`CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS` needs to |
| be set. |
| |
| API Reference |
| ************* |
| |
| .. doxygengroup:: mcumgr_callback_api |
| :inner: |