MCUmgr 回调

概述

MCUmgr 具有可定制的回调/通知系统,允许应用程序(和模块)代码接收其感兴趣的 MCUmgr 事件的回调,并对这些事件作出反应,或向调用函数返回状态码,从而控制是否应允许该操作。fs_mgmt 组就是一个例子:文件访问可以被门控,回调允许应用程序检查请求路径并允许或拒绝对该文件的访问,或者可以将提供的路径重写为其他路径,以支持透明文件重定向。

实现

启用

可以使用 CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS 启用基础回调/通知系统,这会将注册和通知系统编译到代码中。默认情况下,这不会提供任何回调,因为构建所支持的回调还必须通过为所需回调启用相应的 Kconfig 来选择(有关更多详细信息,请参阅 事件)。然后可以声明一个使用 mgmt_cb 类型定义的回调函数,并通过在 mgmt_callback 结构体内为所需事件调用 mgmt_callback_register() 来注册该函数。处理程序按注册顺序调用。

启用该系统后,可以按照如下方式在应用程序代码中设置并定义基本处理程序:

#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);
}

此代码为 MGMT_EVT_OP_CMD_DONE 事件注册一个处理程序,该处理程序将在 MCUmgr 命令处理完并生成输出后调用;请注意,这需要启用 CONFIG_MCUMGR_SMP_COMMAND_STATUS_HOOKS 才能接收此回调。

可以设置多个回调使用同一个函数作为公共回调;也可以通过为每个组注册一次,为每个事件使用许多不同的函数;或者通过使用 MGMT_EVT_OP_*_ALL 事件之一来为整个组启用所有通知;另外,处理程序还可以通过使用 MGMT_EVT_OP_ALL 为每个通知进行设置。设置处理程序时,只能组合属于同一组的事件;例如,可以通过一次注册调用设置 5 个 img_mgmt 回调,但如果还要为 os_mgmt 回调设置回调,则必须作为单独的注册来完成。组 ID 是数字递增的,事件 ID 是位掩码值,因此存在此限制。

例如,以下注册是允许的,它将在一次注册中使用单个回调函数注册 3 个 SMP 事件:

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);

以下代码是不允许的,并且会导致未定义的操作,因为它将 IMG 管理组与 OS 管理组合在一起,而组 不是 位掩码值,只有事件才是:

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);

事件

可以通过启用相应 Kconfig 选项来选择事件:

操作

某些回调期望返回状态以允许或禁止某项操作,例如 fs_mgmt 访问钩子,它允许允许或拒绝对文件的访问。对于这些处理程序,处理程序返回的第一个非 OK 错误码将返回给 MCUmgr 客户端。

选择性拒绝文件访问的示例:

#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);
}

此代码为 MGMT_EVT_OP_FS_MGMT_FILE_ACCESS 事件注册一个处理程序,该处理程序将在收到 fs_mgmt 文件读/写命令后调用,以检查是否应允许对该文件的访问;请注意,这需要启用 CONFIG_MCUMGR_GRP_FS_FILE_ACCESS_HOOK 才能接收此回调。可以返回两类错误:可以将 rc 参数设置为 mcumgr_err_t 错误码并返回 MGMT_CB_ERROR_RC,或者可以将 group 值设置为组,将 rc 值设置为组错误码并返回 MGMT_CB_ERROR_ERR,从而设置组错误码(在 MCUmgr 协议版本 2 中引入)。

MCUmgr 命令回调用法/添加新事件类型

要为 MCUmgr 命令添加回调,可以使用事件 ID 调用 mgmt_callback_notify(),并可选地传入要传递给回调的数据结构(处理程序可以修改该结构)。如果不需要传回任何数据,可以改用 NULL,并将数据大小设置为 0。

示例 MCUmgr 命令处理程序:

#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;
}

如果回调不需要响应,可以调用该函数并将其强制转换为 void。

迁移

如果现有代码使用了 Zephyr 3.2 或更早版本中的旧回调系统,则需要将其迁移到新系统。要迁移代码,需要将以下回调注册函数改为使用 mgmt_callback_register() 注册回调(请注意,除了进行任何迁移外,还需要设置 CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS 以启用新的通知系统):

API 参考

Callbacks