blob: 29e222d1de9593d179c96377df4394deb7002ea1 [file] [log] [blame]
/*
* Copyright (c) 2018-2021 mcumgr authors
* Copyright (c) 2021-2023 Nordic Semiconductor ASA
* Copyright (c) 2022 Laird Connectivity
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/sys/util.h>
#include <zephyr/kernel.h>
#include <zephyr/debug/object_tracing.h>
#include <zephyr/kernel_structs.h>
#include <zephyr/mgmt/mcumgr/mgmt/mgmt.h>
#include <zephyr/mgmt/mcumgr/smp/smp.h>
#include <zephyr/mgmt/mcumgr/mgmt/handlers.h>
#include <zephyr/mgmt/mcumgr/grp/os_mgmt/os_mgmt.h>
#include <zephyr/logging/log.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <zcbor_common.h>
#include <zcbor_encode.h>
#include <zcbor_decode.h>
#include <mgmt/mcumgr/util/zcbor_bulk.h>
#ifdef CONFIG_REBOOT
#include <zephyr/sys/reboot.h>
#endif
#ifdef CONFIG_MCUMGR_MGMT_NOTIFICATION_HOOKS
#include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
#endif
#ifdef CONFIG_MCUMGR_GRP_OS_DATETIME
#include <stdlib.h>
#include <zephyr/drivers/rtc.h>
#endif
#if defined(CONFIG_MCUMGR_GRP_OS_INFO) || defined(CONFIG_MCUMGR_GRP_OS_BOOTLOADER_INFO)
#include <stdio.h>
#include <version.h>
#if defined(CONFIG_MCUMGR_GRP_OS_INFO)
#include <os_mgmt_processor.h>
#endif
#if defined(CONFIG_MCUMGR_GRP_OS_BOOTLOADER_INFO)
#include <bootutil/boot_status.h>
#endif
#include <mgmt/mcumgr/util/zcbor_bulk.h>
#if defined(CONFIG_NET_HOSTNAME_ENABLE)
#include <zephyr/net/hostname.h>
#elif defined(CONFIG_BT)
#include <zephyr/bluetooth/bluetooth.h>
#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 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 IS_ENABLED(CONFIG_MCUBOOT_BOOTLOADER_MODE_SINGLE_APP)
#define BOOTLOADER_MODE MCUBOOT_MODE_SINGLE_SLOT
#elif IS_ENABLED(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_SCRATCH)
#define BOOTLOADER_MODE MCUBOOT_MODE_SWAP_USING_SCRATCH
#elif IS_ENABLED(CONFIG_MCUBOOT_BOOTLOADER_MODE_OVERWRITE_ONLY)
#define BOOTLOADER_MODE MCUBOOT_MODE_UPGRADE_ONLY
#elif IS_ENABLED(CONFIG_MCUBOOT_BOOTLOADER_MODE_SWAP_WITHOUT_SCRATCH)
#define BOOTLOADER_MODE MCUBOOT_MODE_SWAP_USING_MOVE
#elif IS_ENABLED(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP)
#define BOOTLOADER_MODE MCUBOOT_MODE_DIRECT_XIP
#elif IS_ENABLED(CONFIG_MCUBOOT_BOOTLOADER_MODE_DIRECT_XIP_WITH_REVERT)
#define BOOTLOADER_MODE MCUBOOT_MODE_DIRECT_XIP_WITH_REVERT
#elif IS_ENABLED(CONFIG_MCUBOOT_BOOTLOADER_MODE_FIRMWARE_UPDATER)
#define BOOTLOADER_MODE MCUBOOT_MODE_FIRMWARE_LOADER
#else
#define BOOTLOADER_MODE -1
#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;
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 no parameter is recognized then just introduce the bootloader. */
if (decoded == 0) {
ok = zcbor_tstr_put_lit(zse, "bootloader") &&
zcbor_tstr_put_lit(zse, "MCUboot");
} 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);
#if IS_ENABLED(CONFIG_MCUBOOT_BOOTLOADER_NO_DOWNGRADE)
ok = zcbor_tstr_put_lit(zse, "no-downgrade") &&
zcbor_bool_encode(zse, true);
#endif
} else {
return 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, &current_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
".%d"
#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:
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
};
static void os_mgmt_register_group(void)
{
mgmt_register_group(&os_mgmt_group);
}
MCUMGR_HANDLER_DEFINE(os_mgmt, os_mgmt_register_group);