/* * Copyright (c) 2018-2021 mcumgr authors * Copyright (c) 2021-2024 Nordic Semiconductor ASA * Copyright (c) 2022 Laird Connectivity * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_REBOOT #include #endif #ifdef CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS #include #endif #ifdef CONFIG_MCUMGR_GRP_OS_DATETIME #include #include #endif #if defined(CONFIG_MCUMGR_GRP_OS_INFO) || defined(CONFIG_MCUMGR_GRP_OS_BOOTLOADER_INFO) #include #include #if defined(CONFIG_MCUMGR_GRP_OS_INFO) #include #endif #if defined(CONFIG_MCUMGR_GRP_OS_BOOTLOADER_INFO) #include #endif #include #if defined(CONFIG_NET_HOSTNAME_ENABLE) #include #elif defined(CONFIG_BT) #include #endif #endif LOG_MODULE_REGISTER(mcumgr_os_grp, CONFIG_MCUMGR_GRP_OS_LOG_LEVEL); #ifdef CONFIG_REBOOT static void os_mgmt_reset_work_handler(struct k_work *work); K_WORK_DELAYABLE_DEFINE(os_mgmt_reset_work, os_mgmt_reset_work_handler); #endif /* This is passed to zcbor_map_start/end_endcode as a number of * expected "columns" (tid, priority, and so on) * The value here does not affect memory allocation is used * to predict how big the map may be. If you increase number * of "columns" the taskstat sends you may need to increase the * value otherwise zcbor_map_end_encode may return with error. */ #define TASKSTAT_COLUMNS_MAX 20 #ifdef CONFIG_MCUMGR_GRP_OS_TASKSTAT /* Thread iterator information passing structure */ struct thread_iterator_info { zcbor_state_t *zse; int thread_idx; bool ok; }; #endif #ifdef CONFIG_MCUMGR_GRP_OS_DATETIME /* Iterator for extracting values from the provided datetime string, min and max values are * checked against the provided value, then the offset is added after. If the value is not * within the min and max values, the set operation will be aborted. */ struct datetime_parser { int *value; int min_value; int max_value; int offset; }; /* RTC device alias to use for datetime functions, "rtc" */ #define RTC_DEVICE DEVICE_DT_GET(DT_ALIAS(rtc)) #define RTC_DATETIME_YEAR_OFFSET 1900 #define RTC_DATETIME_MONTH_OFFSET 1 #define RTC_DATETIME_NUMERIC_BASE 10 #define RTC_DATETIME_MS_TO_NS 1000000 #define RTC_DATETIME_YEAR_MIN 1900 #define RTC_DATETIME_YEAR_MAX 11899 #define RTC_DATETIME_MONTH_MIN 1 #define RTC_DATETIME_MONTH_MAX 12 #define RTC_DATETIME_DAY_MIN 1 #define RTC_DATETIME_DAY_MAX 31 #define RTC_DATETIME_HOUR_MIN 0 #define RTC_DATETIME_HOUR_MAX 23 #define RTC_DATETIME_MINUTE_MIN 0 #define RTC_DATETIME_MINUTE_MAX 59 #define RTC_DATETIME_SECOND_MIN 0 #define RTC_DATETIME_SECOND_MAX 59 #define RTC_DATETIME_MILLISECOND_MIN 0 #define RTC_DATETIME_MILLISECOND_MAX 999 /* Size used for datetime creation buffer */ #ifdef CONFIG_MCUMGR_GRP_OS_DATETIME_MS #define RTC_DATETIME_STRING_SIZE 32 #else #define RTC_DATETIME_STRING_SIZE 26 #endif /* Minimum/maximum size of a datetime string that a client can provide */ #define RTC_DATETIME_MIN_STRING_SIZE 19 #define RTC_DATETIME_MAX_STRING_SIZE 26 #endif /* Specifies what the "all" ('a') of info parameter shows */ #define OS_MGMT_INFO_FORMAT_ALL \ OS_MGMT_INFO_FORMAT_KERNEL_NAME | OS_MGMT_INFO_FORMAT_NODE_NAME | \ OS_MGMT_INFO_FORMAT_KERNEL_RELEASE | OS_MGMT_INFO_FORMAT_KERNEL_VERSION | \ (IS_ENABLED(CONFIG_MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME) ? \ OS_MGMT_INFO_FORMAT_BUILD_DATE_TIME : 0) | \ OS_MGMT_INFO_FORMAT_MACHINE | OS_MGMT_INFO_FORMAT_PROCESSOR | \ OS_MGMT_INFO_FORMAT_HARDWARE_PLATFORM | OS_MGMT_INFO_FORMAT_OPERATING_SYSTEM #ifdef CONFIG_MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME extern uint8_t *MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME; #endif /** * Command handler: os echo */ #ifdef CONFIG_MCUMGR_GRP_OS_ECHO static int os_mgmt_echo(struct smp_streamer *ctxt) { bool ok; zcbor_state_t *zsd = ctxt->reader->zs; zcbor_state_t *zse = ctxt->writer->zs; struct zcbor_string data = { 0 }; size_t decoded; struct zcbor_map_decode_key_val echo_decode[] = { ZCBOR_MAP_DECODE_KEY_DECODER("d", zcbor_tstr_decode, &data), }; ok = zcbor_map_decode_bulk(zsd, echo_decode, ARRAY_SIZE(echo_decode), &decoded) == 0; if (!ok) { return MGMT_ERR_EINVAL; } ok = zcbor_tstr_put_lit(zse, "r") && zcbor_tstr_encode(zse, &data); return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; } #endif #ifdef CONFIG_MCUMGR_GRP_OS_TASKSTAT #ifdef CONFIG_MCUMGR_GRP_OS_TASKSTAT_USE_THREAD_NAME_FOR_NAME static inline bool os_mgmt_taskstat_encode_thread_name(zcbor_state_t *zse, int idx, const struct k_thread *thread) { size_t name_len = strlen(thread->name); ARG_UNUSED(idx); if (name_len > CONFIG_MCUMGR_GRP_OS_TASKSTAT_THREAD_NAME_LEN) { name_len = CONFIG_MCUMGR_GRP_OS_TASKSTAT_THREAD_NAME_LEN; } return zcbor_tstr_encode_ptr(zse, thread->name, name_len); } #else static inline bool os_mgmt_taskstat_encode_thread_name(zcbor_state_t *zse, int idx, const struct k_thread *thread) { char thread_name[CONFIG_MCUMGR_GRP_OS_TASKSTAT_THREAD_NAME_LEN + 1]; #if defined(CONFIG_MCUMGR_GRP_OS_TASKSTAT_USE_THREAD_PRIO_FOR_NAME) idx = (int)thread->base.prio; #elif defined(CONFIG_MCUMGR_GRP_OS_TASKSTAT_USE_THREAD_IDX_FOR_NAME) ARG_UNUSED(thread); #else #error Unsupported option for taskstat thread name #endif snprintf(thread_name, sizeof(thread_name) - 1, "%d", idx); thread_name[sizeof(thread_name) - 1] = 0; return zcbor_tstr_put_term(zse, thread_name, sizeof(thread_name)); } #endif static inline bool os_mgmt_taskstat_encode_stack_info(zcbor_state_t *zse, const struct k_thread *thread) { #ifdef CONFIG_MCUMGR_GRP_OS_TASKSTAT_STACK_INFO size_t stack_size = 0; size_t stack_used = 0; bool ok = true; #ifdef CONFIG_THREAD_STACK_INFO stack_size = thread->stack_info.size / 4; #ifdef CONFIG_INIT_STACKS unsigned int stack_unused; if (k_thread_stack_space_get(thread, &stack_unused) == 0) { stack_used = (thread->stack_info.size - stack_unused) / 4; } #endif /* CONFIG_INIT_STACKS */ #endif /* CONFIG_THREAD_STACK_INFO */ ok = zcbor_tstr_put_lit(zse, "stksiz") && zcbor_uint64_put(zse, stack_size) && zcbor_tstr_put_lit(zse, "stkuse") && zcbor_uint64_put(zse, stack_used); return ok; #else return true; #endif /* CONFIG_MCUMGR_GRP_OS_TASKSTAT_STACK_INFO */ } static inline bool os_mgmt_taskstat_encode_runtime_info(zcbor_state_t *zse, const struct k_thread *thread) { bool ok = true; #if defined(CONFIG_SCHED_THREAD_USAGE) k_thread_runtime_stats_t thread_stats; k_thread_runtime_stats_get((struct k_thread *)thread, &thread_stats); ok = zcbor_tstr_put_lit(zse, "runtime") && zcbor_uint64_put(zse, thread_stats.execution_cycles); #elif !defined(CONFIG_MCUMGR_GRP_OS_TASKSTAT_ONLY_SUPPORTED_STATS) ok = zcbor_tstr_put_lit(zse, "runtime") && zcbor_uint32_put(zse, 0); #endif return ok; } static inline bool os_mgmt_taskstat_encode_unsupported(zcbor_state_t *zse) { bool ok = true; if (!IS_ENABLED(CONFIG_MCUMGR_GRP_OS_TASKSTAT_ONLY_SUPPORTED_STATS)) { ok = zcbor_tstr_put_lit(zse, "cswcnt") && zcbor_uint32_put(zse, 0) && zcbor_tstr_put_lit(zse, "last_checkin") && zcbor_uint32_put(zse, 0) && zcbor_tstr_put_lit(zse, "next_checkin") && zcbor_uint32_put(zse, 0); } else { ARG_UNUSED(zse); } return ok; } static inline bool os_mgmt_taskstat_encode_priority(zcbor_state_t *zse, const struct k_thread *thread) { return (zcbor_tstr_put_lit(zse, "prio") && IS_ENABLED(CONFIG_MCUMGR_GRP_OS_TASKSTAT_SIGNED_PRIORITY) ? zcbor_int32_put(zse, (int)thread->base.prio) : zcbor_uint32_put(zse, (unsigned int)thread->base.prio) & 0xff); } /** * Encodes a single taskstat entry. */ static void os_mgmt_taskstat_encode_one(const struct k_thread *thread, void *user_data) { /* * Threads are sent as map where thread name is key and value is map * of thread parameters */ struct thread_iterator_info *iterator_ctx = (struct thread_iterator_info *)user_data; if (iterator_ctx->ok == true) { iterator_ctx->ok = os_mgmt_taskstat_encode_thread_name(iterator_ctx->zse, iterator_ctx->thread_idx, thread) && zcbor_map_start_encode(iterator_ctx->zse, TASKSTAT_COLUMNS_MAX) && os_mgmt_taskstat_encode_priority(iterator_ctx->zse, thread) && zcbor_tstr_put_lit(iterator_ctx->zse, "tid") && zcbor_uint32_put(iterator_ctx->zse, iterator_ctx->thread_idx) && zcbor_tstr_put_lit(iterator_ctx->zse, "state") && zcbor_uint32_put(iterator_ctx->zse, thread->base.thread_state) && os_mgmt_taskstat_encode_stack_info(iterator_ctx->zse, thread) && os_mgmt_taskstat_encode_runtime_info(iterator_ctx->zse, thread) && os_mgmt_taskstat_encode_unsupported(iterator_ctx->zse) && zcbor_map_end_encode(iterator_ctx->zse, TASKSTAT_COLUMNS_MAX); ++iterator_ctx->thread_idx; } } /** * Command handler: os taskstat */ static int os_mgmt_taskstat_read(struct smp_streamer *ctxt) { zcbor_state_t *zse = ctxt->writer->zs; struct thread_iterator_info iterator_ctx = { .zse = zse, .thread_idx = 0, .ok = true, }; zcbor_tstr_put_lit(zse, "tasks"); zcbor_map_start_encode(zse, CONFIG_MCUMGR_GRP_OS_TASKSTAT_MAX_NUM_THREADS); /* Iterate the list of tasks, encoding each. */ k_thread_foreach(os_mgmt_taskstat_encode_one, (void *)&iterator_ctx); if (!iterator_ctx.ok) { LOG_ERR("Task iterator status is not OK"); } if (!iterator_ctx.ok || !zcbor_map_end_encode(zse, CONFIG_MCUMGR_GRP_OS_TASKSTAT_MAX_NUM_THREADS)) { return MGMT_ERR_EMSGSIZE; } return 0; } #endif /* CONFIG_MCUMGR_GRP_OS_TASKSTAT */ #ifdef CONFIG_REBOOT /** * Command handler: os reset */ static void os_mgmt_reset_work_handler(struct k_work *work) { ARG_UNUSED(work); sys_reboot(SYS_REBOOT_WARM); } static int os_mgmt_reset(struct smp_streamer *ctxt) { #if defined(CONFIG_MCUMGR_GRP_OS_RESET_HOOK) zcbor_state_t *zsd = ctxt->reader->zs; zcbor_state_t *zse = ctxt->writer->zs; size_t decoded; enum mgmt_cb_return status; int32_t err_rc; uint16_t err_group; struct os_mgmt_reset_data reboot_data = { .force = false }; struct zcbor_map_decode_key_val reset_decode[] = { ZCBOR_MAP_DECODE_KEY_DECODER("force", zcbor_bool_decode, &reboot_data.force), }; /* Since this is a core command, if we fail to decode the data, ignore the error and * continue with the default parameter of force being false. */ (void)zcbor_map_decode_bulk(zsd, reset_decode, ARRAY_SIZE(reset_decode), &decoded); status = mgmt_callback_notify(MGMT_EVT_OP_OS_MGMT_RESET, &reboot_data, sizeof(reboot_data), &err_rc, &err_group); if (status != MGMT_CB_OK) { bool ok; if (status == MGMT_CB_ERROR_RC) { return err_rc; } ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc); return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; } #endif /* Reboot the system from the system workqueue thread. */ k_work_schedule(&os_mgmt_reset_work, K_MSEC(CONFIG_MCUMGR_GRP_OS_RESET_MS)); return 0; } #endif #ifdef CONFIG_MCUMGR_GRP_OS_MCUMGR_PARAMS static int os_mgmt_mcumgr_params(struct smp_streamer *ctxt) { zcbor_state_t *zse = ctxt->writer->zs; bool ok; ok = zcbor_tstr_put_lit(zse, "buf_size") && zcbor_uint32_put(zse, CONFIG_MCUMGR_TRANSPORT_NETBUF_SIZE) && zcbor_tstr_put_lit(zse, "buf_count") && zcbor_uint32_put(zse, CONFIG_MCUMGR_TRANSPORT_NETBUF_COUNT); return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; } #endif #if defined(CONFIG_MCUMGR_GRP_OS_BOOTLOADER_INFO) #if defined(CONFIG_BOOTLOADER_MCUBOOT) #if defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SINGLE_APP) #define BOOTLOADER_MODE MCUBOOT_MODE_SINGLE_SLOT #elif defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_SCRATCH) #define BOOTLOADER_MODE MCUBOOT_MODE_SWAP_USING_SCRATCH #elif defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_OVERWRITE_ONLY) #define BOOTLOADER_MODE MCUBOOT_MODE_UPGRADE_ONLY #elif defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_WITHOUT_SCRATCH) #define BOOTLOADER_MODE MCUBOOT_MODE_SWAP_USING_MOVE #elif defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP) #define BOOTLOADER_MODE MCUBOOT_MODE_DIRECT_XIP #elif defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT) #define BOOTLOADER_MODE MCUBOOT_MODE_DIRECT_XIP_WITH_REVERT #elif defined(CONFIG_MCUBOOT_BOOTLOADER_MODE_FIRMWARE_UPDATER) #define BOOTLOADER_MODE MCUBOOT_MODE_FIRMWARE_LOADER #else #define BOOTLOADER_MODE -1 #endif #endif static int os_mgmt_bootloader_info(struct smp_streamer *ctxt) { zcbor_state_t *zse = ctxt->writer->zs; zcbor_state_t *zsd = ctxt->reader->zs; struct zcbor_string query = { 0 }; size_t decoded; bool ok; bool has_output = false; #if defined(CONFIG_MCUMGR_GRP_OS_BOOTLOADER_INFO_HOOK) enum mgmt_cb_return status; int32_t err_rc; uint16_t err_group; struct os_mgmt_bootloader_info_data bootloader_info_data = { .zse = zse, .decoded = &decoded, .query = &query, .has_output = &has_output }; #endif struct zcbor_map_decode_key_val bootloader_info[] = { ZCBOR_MAP_DECODE_KEY_DECODER("query", zcbor_tstr_decode, &query), }; if (zcbor_map_decode_bulk(zsd, bootloader_info, ARRAY_SIZE(bootloader_info), &decoded)) { return MGMT_ERR_EINVAL; } #if defined(CONFIG_MCUMGR_GRP_OS_BOOTLOADER_INFO_HOOK) status = mgmt_callback_notify(MGMT_EVT_OP_OS_MGMT_BOOTLOADER_INFO, &bootloader_info_data, sizeof(bootloader_info_data), &err_rc, &err_group); if (status != MGMT_CB_OK) { if (status == MGMT_CB_ERROR_RC) { return err_rc; } ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc); return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; } #endif /* If no parameter is recognized then just introduce the bootloader. */ if (!has_output) { #if defined(CONFIG_BOOTLOADER_MCUBOOT) if (decoded == 0) { ok = zcbor_tstr_put_lit(zse, "bootloader") && zcbor_tstr_put_lit(zse, "MCUboot"); has_output = true; } else if (zcbor_map_decode_bulk_key_found(bootloader_info, ARRAY_SIZE(bootloader_info), "query") && (sizeof("mode") - 1) == query.len && memcmp("mode", query.value, query.len) == 0) { ok = zcbor_tstr_put_lit(zse, "mode") && zcbor_int32_put(zse, BOOTLOADER_MODE); #ifdef CONFIG_MCUBOOT_BOOTLOADER_NO_DOWNGRADE ok = ok && zcbor_tstr_put_lit(zse, "no-downgrade") && zcbor_bool_encode(zse, &(bool){true}); #endif has_output = true; } #endif } if (!has_output) { ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_OS, OS_MGMT_ERR_QUERY_YIELDS_NO_ANSWER); } return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; } #endif #ifdef CONFIG_MCUMGR_GRP_OS_INFO /** * Command handler: os info */ static int os_mgmt_info(struct smp_streamer *ctxt) { struct zcbor_string format = { 0 }; uint8_t output[CONFIG_MCUMGR_GRP_OS_INFO_MAX_RESPONSE_SIZE] = { 0 }; zcbor_state_t *zse = ctxt->writer->zs; zcbor_state_t *zsd = ctxt->reader->zs; uint32_t format_bitmask = 0; bool prior_output = false; size_t i = 0; size_t decoded; bool custom_os_name = false; int rc; uint16_t output_length = 0; uint16_t valid_formats = 0; struct zcbor_map_decode_key_val fs_info_decode[] = { ZCBOR_MAP_DECODE_KEY_DECODER("format", zcbor_tstr_decode, &format), }; #ifdef CONFIG_MCUMGR_GRP_OS_INFO_CUSTOM_HOOKS struct os_mgmt_info_check check_data = { .format = &format, .format_bitmask = &format_bitmask, .valid_formats = &valid_formats, .custom_os_name = &custom_os_name, }; struct os_mgmt_info_append append_data = { .format_bitmask = &format_bitmask, .all_format_specified = false, .output = output, .output_length = &output_length, .buffer_size = sizeof(output), .prior_output = &prior_output, }; #endif #ifdef CONFIG_MCUMGR_GRP_OS_INFO_CUSTOM_HOOKS enum mgmt_cb_return status; int32_t err_rc; uint16_t err_group; #endif if (zcbor_map_decode_bulk(zsd, fs_info_decode, ARRAY_SIZE(fs_info_decode), &decoded)) { return MGMT_ERR_EINVAL; } /* Process all input characters in format value */ while (i < format.len) { switch (format.value[i]) { case 'a': { #ifdef CONFIG_MCUMGR_GRP_OS_INFO_CUSTOM_HOOKS append_data.all_format_specified = true; #endif format_bitmask = OS_MGMT_INFO_FORMAT_ALL; ++valid_formats; break; } case 's': { format_bitmask |= OS_MGMT_INFO_FORMAT_KERNEL_NAME; ++valid_formats; break; } case 'n': { format_bitmask |= OS_MGMT_INFO_FORMAT_NODE_NAME; ++valid_formats; break; } case 'r': { format_bitmask |= OS_MGMT_INFO_FORMAT_KERNEL_RELEASE; ++valid_formats; break; } case 'v': { format_bitmask |= OS_MGMT_INFO_FORMAT_KERNEL_VERSION; ++valid_formats; break; } #ifdef CONFIG_MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME case 'b': { format_bitmask |= OS_MGMT_INFO_FORMAT_BUILD_DATE_TIME; ++valid_formats; break; } #endif case 'm': { format_bitmask |= OS_MGMT_INFO_FORMAT_MACHINE; ++valid_formats; break; } case 'p': { format_bitmask |= OS_MGMT_INFO_FORMAT_PROCESSOR; ++valid_formats; break; } case 'i': { format_bitmask |= OS_MGMT_INFO_FORMAT_HARDWARE_PLATFORM; ++valid_formats; break; } case 'o': { format_bitmask |= OS_MGMT_INFO_FORMAT_OPERATING_SYSTEM; ++valid_formats; break; } default: { break; } } ++i; } #ifdef CONFIG_MCUMGR_GRP_OS_INFO_CUSTOM_HOOKS /* Run callbacks to see if any additional handlers will add options */ (void)mgmt_callback_notify(MGMT_EVT_OP_OS_MGMT_INFO_CHECK, &check_data, sizeof(check_data), &err_rc, &err_group); #endif if (valid_formats != format.len) { /* A provided format specifier is not valid */ bool ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_OS, OS_MGMT_ERR_INVALID_FORMAT); return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; } else if (format_bitmask == 0) { /* If no value is provided, use default of kernel name */ format_bitmask = OS_MGMT_INFO_FORMAT_KERNEL_NAME; } /* Process all options in order and append to output string */ if (format_bitmask & OS_MGMT_INFO_FORMAT_KERNEL_NAME) { rc = snprintf(output, (sizeof(output) - output_length), "Zephyr"); if (rc < 0 || rc >= (sizeof(output) - output_length)) { goto fail; } else { output_length += (uint16_t)rc; } prior_output = true; } if (format_bitmask & OS_MGMT_INFO_FORMAT_NODE_NAME) { /* Get hostname, if enabled */ #if defined(CONFIG_NET_HOSTNAME_ENABLE) /* From network */ rc = snprintf(&output[output_length], (sizeof(output) - output_length), (prior_output == true ? " %s" : "%s"), net_hostname_get()); #elif defined(CONFIG_BT) /* From Bluetooth */ rc = snprintf(&output[output_length], (sizeof(output) - output_length), (prior_output == true ? " %s" : "%s"), bt_get_name()); #else /* Not available */ rc = snprintf(&output[output_length], (sizeof(output) - output_length), "%sunknown", (prior_output == true ? " " : "")); #endif if (rc < 0 || rc >= (sizeof(output) - output_length)) { goto fail; } else { output_length += (uint16_t)rc; } prior_output = true; format_bitmask &= ~OS_MGMT_INFO_FORMAT_NODE_NAME; } if (format_bitmask & OS_MGMT_INFO_FORMAT_KERNEL_RELEASE) { #ifdef BUILD_VERSION rc = snprintf(&output[output_length], (sizeof(output) - output_length), (prior_output == true ? " %s" : "%s"), STRINGIFY(BUILD_VERSION)); #else rc = snprintf(&output[output_length], (sizeof(output) - output_length), "%sunknown", (prior_output == true ? " " : "")); #endif if (rc < 0 || rc >= (sizeof(output) - output_length)) { goto fail; } else { output_length += (uint16_t)rc; } prior_output = true; format_bitmask &= ~OS_MGMT_INFO_FORMAT_KERNEL_RELEASE; } if (format_bitmask & OS_MGMT_INFO_FORMAT_KERNEL_VERSION) { rc = snprintf(&output[output_length], (sizeof(output) - output_length), (prior_output == true ? " %s" : "%s"), KERNEL_VERSION_STRING); if (rc < 0 || rc >= (sizeof(output) - output_length)) { goto fail; } else { output_length += (uint16_t)rc; } prior_output = true; format_bitmask &= ~OS_MGMT_INFO_FORMAT_KERNEL_VERSION; } #ifdef CONFIG_MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME if (format_bitmask & OS_MGMT_INFO_FORMAT_BUILD_DATE_TIME) { rc = snprintf(&output[output_length], (sizeof(output) - output_length), (prior_output == true ? " %s" : "%s"), MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME); if (rc < 0 || rc >= (sizeof(output) - output_length)) { goto fail; } else { output_length += (uint16_t)rc; } prior_output = true; format_bitmask &= ~OS_MGMT_INFO_FORMAT_BUILD_DATE_TIME; } #endif if (format_bitmask & OS_MGMT_INFO_FORMAT_MACHINE) { rc = snprintf(&output[output_length], (sizeof(output) - output_length), (prior_output == true ? " %s" : "%s"), CONFIG_ARCH); if (rc < 0 || rc >= (sizeof(output) - output_length)) { goto fail; } else { output_length += (uint16_t)rc; } prior_output = true; format_bitmask &= ~OS_MGMT_INFO_FORMAT_MACHINE; } if (format_bitmask & OS_MGMT_INFO_FORMAT_PROCESSOR) { rc = snprintf(&output[output_length], (sizeof(output) - output_length), (prior_output == true ? " %s" : "%s"), PROCESSOR_NAME); if (rc < 0 || rc >= (sizeof(output) - output_length)) { goto fail; } else { output_length += (uint16_t)rc; } prior_output = true; format_bitmask &= ~OS_MGMT_INFO_FORMAT_PROCESSOR; } if (format_bitmask & OS_MGMT_INFO_FORMAT_HARDWARE_PLATFORM) { rc = snprintf(&output[output_length], (sizeof(output) - output_length), (prior_output == true ? " %s%s%s" : "%s%s%s"), CONFIG_BOARD, (sizeof(CONFIG_BOARD_REVISION) > 1 ? "@" : ""), CONFIG_BOARD_REVISION); if (rc < 0 || rc >= (sizeof(output) - output_length)) { goto fail; } else { output_length += (uint16_t)rc; } prior_output = true; format_bitmask &= ~OS_MGMT_INFO_FORMAT_HARDWARE_PLATFORM; } /* If custom_os_name is not set (by extension code) then return the default OS name of * Zephyr */ if (format_bitmask & OS_MGMT_INFO_FORMAT_OPERATING_SYSTEM && custom_os_name == false) { rc = snprintf(&output[output_length], (sizeof(output) - output_length), "%sZephyr", (prior_output == true ? " " : "")); if (rc < 0 || rc >= (sizeof(output) - output_length)) { goto fail; } else { output_length += (uint16_t)rc; } prior_output = true; format_bitmask &= ~OS_MGMT_INFO_FORMAT_OPERATING_SYSTEM; } #ifdef CONFIG_MCUMGR_GRP_OS_INFO_CUSTOM_HOOKS /* Call custom handler command for additional output/processing */ status = mgmt_callback_notify(MGMT_EVT_OP_OS_MGMT_INFO_APPEND, &append_data, sizeof(append_data), &err_rc, &err_group); if (status != MGMT_CB_OK) { bool ok; if (status == MGMT_CB_ERROR_RC) { return err_rc; } ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc); return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; } #endif if (zcbor_tstr_put_lit(zse, "output") && zcbor_tstr_encode_ptr(zse, output, output_length)) { return MGMT_ERR_EOK; } fail: return MGMT_ERR_EMSGSIZE; } #endif #ifdef CONFIG_MCUMGR_GRP_OS_DATETIME /** * Command handler: os datetime get */ static int os_mgmt_datetime_read(struct smp_streamer *ctxt) { zcbor_state_t *zse = ctxt->writer->zs; struct rtc_time current_time; char date_string[RTC_DATETIME_STRING_SIZE]; int rc; bool ok; #if defined(CONFIG_MCUMGR_GRP_OS_DATETIME_HOOK) enum mgmt_cb_return status; int32_t err_rc; uint16_t err_group; status = mgmt_callback_notify(MGMT_EVT_OP_OS_MGMT_DATETIME_GET, NULL, 0, &err_rc, &err_group); if (status != MGMT_CB_OK) { if (status == MGMT_CB_ERROR_RC) { return err_rc; } ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc); return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; } #endif rc = rtc_get_time(RTC_DEVICE, ¤t_time); if (rc == -ENODATA) { /* RTC not set */ ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_OS, OS_MGMT_ERR_RTC_NOT_SET); goto finished; } else if (rc != 0) { /* Other RTC error */ ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_OS, OS_MGMT_ERR_RTC_COMMAND_FAILED); goto finished; } sprintf(date_string, "%4d-%02d-%02dT%02d:%02d:%02d" #ifdef CONFIG_MCUMGR_GRP_OS_DATETIME_MS ".%03d" #endif , (uint16_t)(current_time.tm_year + RTC_DATETIME_YEAR_OFFSET), (uint8_t)(current_time.tm_mon + RTC_DATETIME_MONTH_OFFSET), (uint8_t)current_time.tm_mday, (uint8_t)current_time.tm_hour, (uint8_t)current_time.tm_min, (uint8_t)current_time.tm_sec #ifdef CONFIG_MCUMGR_GRP_OS_DATETIME_MS , (uint16_t)(current_time.tm_nsec / RTC_DATETIME_MS_TO_NS) #endif ); ok = zcbor_tstr_put_lit(zse, "datetime") && zcbor_tstr_encode_ptr(zse, date_string, strlen(date_string)); finished: return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; } /** * Command handler: os datetime set */ static int os_mgmt_datetime_write(struct smp_streamer *ctxt) { zcbor_state_t *zsd = ctxt->reader->zs; zcbor_state_t *zse = ctxt->writer->zs; size_t decoded; struct zcbor_string datetime = { 0 }; int rc; uint8_t i = 0; bool ok = true; char *pos; char *new_pos; char date_string[RTC_DATETIME_MAX_STRING_SIZE]; struct rtc_time new_time = { .tm_wday = -1, .tm_yday = -1, .tm_isdst = -1, .tm_nsec = 0, }; struct datetime_parser parser[] = { { .value = &new_time.tm_year, .min_value = RTC_DATETIME_YEAR_MIN, .max_value = RTC_DATETIME_YEAR_MAX, .offset = -RTC_DATETIME_YEAR_OFFSET, }, { .value = &new_time.tm_mon, .min_value = RTC_DATETIME_MONTH_MIN, .max_value = RTC_DATETIME_MONTH_MAX, .offset = -RTC_DATETIME_MONTH_OFFSET, }, { .value = &new_time.tm_mday, .min_value = RTC_DATETIME_DAY_MIN, .max_value = RTC_DATETIME_DAY_MAX, }, { .value = &new_time.tm_hour, .min_value = RTC_DATETIME_HOUR_MIN, .max_value = RTC_DATETIME_HOUR_MAX, }, { .value = &new_time.tm_min, .min_value = RTC_DATETIME_MINUTE_MIN, .max_value = RTC_DATETIME_MINUTE_MAX, }, { .value = &new_time.tm_sec, .min_value = RTC_DATETIME_SECOND_MIN, .max_value = RTC_DATETIME_SECOND_MAX, }, }; #if defined(CONFIG_MCUMGR_GRP_OS_DATETIME_HOOK) enum mgmt_cb_return status; int32_t err_rc; uint16_t err_group; #endif struct zcbor_map_decode_key_val datetime_decode[] = { ZCBOR_MAP_DECODE_KEY_DECODER("datetime", zcbor_tstr_decode, &datetime), }; if (zcbor_map_decode_bulk(zsd, datetime_decode, ARRAY_SIZE(datetime_decode), &decoded)) { return MGMT_ERR_EINVAL; } else if (datetime.len < RTC_DATETIME_MIN_STRING_SIZE || datetime.len >= RTC_DATETIME_MAX_STRING_SIZE) { return MGMT_ERR_EINVAL; } memcpy(date_string, datetime.value, datetime.len); date_string[datetime.len] = '\0'; pos = date_string; while (i < ARRAY_SIZE(parser)) { if (pos == (date_string + datetime.len)) { /* Encountered end of string early, this is invalid */ return MGMT_ERR_EINVAL; } *parser[i].value = strtol(pos, &new_pos, RTC_DATETIME_NUMERIC_BASE); if (pos == new_pos) { /* Missing or unable to convert field */ return MGMT_ERR_EINVAL; } if (*parser[i].value < parser[i].min_value || *parser[i].value > parser[i].max_value) { /* Value is not within the allowed bounds of this field */ return MGMT_ERR_EINVAL; } *parser[i].value += parser[i].offset; /* Skip a character as there is always a delimiter between the fields */ ++i; pos = new_pos + 1; } #ifdef CONFIG_MCUMGR_GRP_OS_DATETIME_MS if (*(pos - 1) == '.' && *pos != '\0') { /* Provided value has a ms value, extract it */ new_time.tm_nsec = strtol(pos, &new_pos, RTC_DATETIME_NUMERIC_BASE); if (new_time.tm_nsec < RTC_DATETIME_MILLISECOND_MIN || new_time.tm_nsec > RTC_DATETIME_MILLISECOND_MAX) { return MGMT_ERR_EINVAL; } new_time.tm_nsec *= RTC_DATETIME_MS_TO_NS; } #endif #if defined(CONFIG_MCUMGR_GRP_OS_DATETIME_HOOK) status = mgmt_callback_notify(MGMT_EVT_OP_OS_MGMT_DATETIME_SET, &new_time, sizeof(new_time), &err_rc, &err_group); if (status != MGMT_CB_OK) { if (status == MGMT_CB_ERROR_RC) { return err_rc; } ok = smp_add_cmd_err(zse, err_group, (uint16_t)err_rc); return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; } #endif rc = rtc_set_time(RTC_DEVICE, &new_time); if (rc != 0) { ok = smp_add_cmd_err(zse, MGMT_GROUP_ID_OS, OS_MGMT_ERR_RTC_COMMAND_FAILED); } return ok ? MGMT_ERR_EOK : MGMT_ERR_EMSGSIZE; } #endif #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL /* * @brief Translate OS mgmt group error code into MCUmgr error code * * @param ret #os_mgmt_err_code_t error code * * @return #mcumgr_err_t error code */ static int os_mgmt_translate_error_code(uint16_t err) { int rc; switch (err) { case OS_MGMT_ERR_INVALID_FORMAT: rc = MGMT_ERR_EINVAL; break; case OS_MGMT_ERR_QUERY_YIELDS_NO_ANSWER: case OS_MGMT_ERR_RTC_NOT_SET: case OS_MGMT_ERR_QUERY_RESPONSE_VALUE_NOT_VALID: rc = MGMT_ERR_ENOENT; break; case OS_MGMT_ERR_UNKNOWN: case OS_MGMT_ERR_RTC_COMMAND_FAILED: default: rc = MGMT_ERR_EUNKNOWN; } return rc; } #endif static const struct mgmt_handler os_mgmt_group_handlers[] = { #ifdef CONFIG_MCUMGR_GRP_OS_ECHO [OS_MGMT_ID_ECHO] = { os_mgmt_echo, os_mgmt_echo }, #endif #ifdef CONFIG_MCUMGR_GRP_OS_TASKSTAT [OS_MGMT_ID_TASKSTAT] = { os_mgmt_taskstat_read, NULL }, #endif #ifdef CONFIG_MCUMGR_GRP_OS_DATETIME [OS_MGMT_ID_DATETIME_STR] = { os_mgmt_datetime_read, os_mgmt_datetime_write }, #endif #ifdef CONFIG_REBOOT [OS_MGMT_ID_RESET] = { NULL, os_mgmt_reset }, #endif #ifdef CONFIG_MCUMGR_GRP_OS_MCUMGR_PARAMS [OS_MGMT_ID_MCUMGR_PARAMS] = { os_mgmt_mcumgr_params, NULL }, #endif #ifdef CONFIG_MCUMGR_GRP_OS_INFO [OS_MGMT_ID_INFO] = { os_mgmt_info, NULL }, #endif #ifdef CONFIG_MCUMGR_GRP_OS_BOOTLOADER_INFO [OS_MGMT_ID_BOOTLOADER_INFO] = { os_mgmt_bootloader_info, NULL }, #endif }; #define OS_MGMT_GROUP_SZ ARRAY_SIZE(os_mgmt_group_handlers) static struct mgmt_group os_mgmt_group = { .mg_handlers = os_mgmt_group_handlers, .mg_handlers_count = OS_MGMT_GROUP_SZ, .mg_group_id = MGMT_GROUP_ID_OS, #ifdef CONFIG_MCUMGR_SMP_SUPPORT_ORIGINAL_PROTOCOL .mg_translate_error = os_mgmt_translate_error_code, #endif #ifdef CONFIG_MCUMGR_GRP_ENUM_DETAILS_NAME .mg_group_name = "os mgmt", #endif }; static void os_mgmt_register_group(void) { mgmt_register_group(&os_mgmt_group); } MCUMGR_HANDLER_DEFINE(os_mgmt, os_mgmt_register_group);