Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mgmt: mcumgr: grp: os_mgmt: Add datetime get/set functions #64934

Merged
merged 4 commits into from Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions doc/releases/release-notes-3.6.rst
Expand Up @@ -247,6 +247,9 @@ Libraries / Subsystems
* Fixed an issue in MCUmgr which would cause a user data buffer overflow if the UDP transport was
enabled on IPv4 only but IPv6 support was enabled in the kernel.

* Implemented datetime functionality in MCUmgr OS management group, this makes use of the RTC
driver API.

* File systems

* Modem modules
Expand Down
2 changes: 1 addition & 1 deletion doc/services/device_mgmt/smp_groups/smp_group_0.rst
Expand Up @@ -20,7 +20,7 @@ OS management group defines following commands:
+-------------------+-----------------------------------------------+
| ``3`` | Memory pool statistics |
+-------------------+-----------------------------------------------+
| ``4`` | Date-time string; unimplemented by Zephyr |
| ``4`` | Date-time string |
+-------------------+-----------------------------------------------+
| ``5`` | System reset |
+-------------------+-----------------------------------------------+
Expand Down
6 changes: 6 additions & 0 deletions include/zephyr/mgmt/mcumgr/grp/os_mgmt/os_mgmt.h
Expand Up @@ -41,6 +41,12 @@ enum os_mgmt_err_code_t {

/** Query was not recognized. */
OS_MGMT_ERR_QUERY_YIELDS_NO_ANSWER,

/** RTC is not set */
OS_MGMT_ERR_RTC_NOT_SET,

/** RTC command failed */
OS_MGMT_ERR_RTC_COMMAND_FAILED,
};

/* Bitmask values used by the os info command handler. Note that the width of this variable is
Expand Down
6 changes: 6 additions & 0 deletions include/zephyr/mgmt/mcumgr/mgmt/callbacks.h
Expand Up @@ -197,6 +197,12 @@ enum os_mgmt_group_events {
/** Callback when an info command needs to output data, data is os_mgmt_info_append. */
MGMT_EVT_OP_OS_MGMT_INFO_APPEND = MGMT_DEF_EVT_OP_ID(MGMT_EVT_GRP_OS, 2),

/** Callback when a datetime get command has been received. */
MGMT_EVT_OP_OS_MGMT_DATETIME_GET = MGMT_DEF_EVT_OP_ID(MGMT_EVT_GRP_OS, 3),

/** Callback when a datetime set command has been received, data is struct rtc_time(). */
MGMT_EVT_OP_OS_MGMT_DATETIME_SET = MGMT_DEF_EVT_OP_ID(MGMT_EVT_GRP_OS, 4),

/** Used to enable all os_mgmt_group events. */
MGMT_EVT_OP_OS_MGMT_ALL = MGMT_DEF_EVT_OP_ALL(MGMT_EVT_GRP_OS),
};
Expand Down
21 changes: 21 additions & 0 deletions subsys/mgmt/mcumgr/grp/os_mgmt/Kconfig
Expand Up @@ -135,6 +135,27 @@ config MCUMGR_GRP_OS_ECHO
default y
select MCUMGR_SMP_CBOR_MIN_DECODING_LEVEL_2

config MCUMGR_GRP_OS_DATETIME
bool "Support for datetime command"
depends on RTC
depends on $(dt_alias_enabled,rtc)
select MCUMGR_SMP_CBOR_MIN_DECODING_LEVEL_2
nordicjm marked this conversation as resolved.
Show resolved Hide resolved
help
Enables support for the datetime get and set functions, this allows for using the
`rtc` alias device as a timesource from which the current time can be written or read.

config MCUMGR_GRP_OS_DATETIME_MS
bool "Support millisecond field in datetime commands"
depends on MCUMGR_GRP_OS_DATETIME

config MCUMGR_GRP_OS_DATETIME_HOOK
bool "Datetime hook"
depends on MCUMGR_GRP_OS_DATETIME
depends on MCUMGR_MGMT_NOTIFICATION_HOOKS
help
Allows applications to control and get notifications of when a datetime set/get
command has been issued via an MCUmgr command.

config MCUMGR_GRP_OS_MCUMGR_PARAMS
bool "MCUMGR Parameters retrieval command"

Expand Down
268 changes: 268 additions & 0 deletions subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c
Expand Up @@ -33,6 +33,11 @@
#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>
Expand Down Expand Up @@ -78,6 +83,52 @@ struct thread_iterator_info {
};
#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 | \
Expand Down Expand Up @@ -744,6 +795,210 @@ static int os_mgmt_info(struct smp_streamer *ctxt)
}
#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
Expand All @@ -761,7 +1016,13 @@ static int os_mgmt_translate_error_code(uint16_t err)
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;
}
Expand All @@ -781,6 +1042,13 @@ static const struct mgmt_handler os_mgmt_group_handlers[] = {
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
Expand Down
17 changes: 17 additions & 0 deletions tests/subsys/mgmt/mcumgr/os_mgmt_datetime/CMakeLists.txt
@@ -0,0 +1,17 @@
#
# Copyright (c) 2023 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: Apache-2.0
#

cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(os_mgmt_info)

FILE(GLOB app_sources
src/*.c
)

target_sources(app PRIVATE ${app_sources})
target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/)
target_include_directories(app PRIVATE ${ZEPHYR_BASE}/subsys/mgmt/mcumgr/grp/os_mgmt/include/)