mgmt: mcumgr: lib: cmd: os: Add device information handler

The device information handler can be used to retrieve information about
the configuration of the configured device such as board name, board
revision, firmware version and build date.

Signed-off-by: Jamie McCrae <jamie.mccrae@nordicsemi.no>
diff --git a/include/zephyr/mgmt/mcumgr/grp/os_mgmt/os_mgmt.h b/include/zephyr/mgmt/mcumgr/grp/os_mgmt/os_mgmt.h
index 75d9dc7..cd474b2 100644
--- a/include/zephyr/mgmt/mcumgr/grp/os_mgmt/os_mgmt.h
+++ b/include/zephyr/mgmt/mcumgr/grp/os_mgmt/os_mgmt.h
@@ -22,6 +22,66 @@
 #define OS_MGMT_ID_DATETIME_STR		4
 #define OS_MGMT_ID_RESET		5
 #define OS_MGMT_ID_MCUMGR_PARAMS	6
+#define OS_MGMT_ID_INFO			7
+
+/* Bitmask values used by the os info command handler. Note that the width of this variable is
+ * 32-bits, allowing 32 flags, custom user-level implementations should start at
+ * OS_MGMT_INFO_FORMAT_USER_CUSTOM_START and reference that directly as additional format
+ * specifiers might be added to this list in the future.
+ */
+enum os_mgmt_info_formats {
+	OS_MGMT_INFO_FORMAT_KERNEL_NAME = BIT(0),
+	OS_MGMT_INFO_FORMAT_NODE_NAME = BIT(1),
+	OS_MGMT_INFO_FORMAT_KERNEL_RELEASE = BIT(2),
+	OS_MGMT_INFO_FORMAT_KERNEL_VERSION = BIT(3),
+	OS_MGMT_INFO_FORMAT_BUILD_DATE_TIME = BIT(4),
+	OS_MGMT_INFO_FORMAT_MACHINE = BIT(5),
+	OS_MGMT_INFO_FORMAT_PROCESSOR = BIT(6),
+	OS_MGMT_INFO_FORMAT_HARDWARE_PLATFORM = BIT(7),
+	OS_MGMT_INFO_FORMAT_OPERATING_SYSTEM = BIT(8),
+
+	OS_MGMT_INFO_FORMAT_USER_CUSTOM_START = BIT(9),
+};
+
+/* Structure provided in the MGMT_EVT_OP_OS_MGMT_INFO_CHECK notification callback */
+struct os_mgmt_info_check {
+	/* Input format string from the mcumgr client */
+	struct zcbor_string *format;
+	/* Bitmask of values specifying which outputs should be present */
+	uint32_t *format_bitmask;
+	/* Number of valid format characters parsed, must be incremented by 1 for each valid
+	 * character
+	 */
+	uint16_t *valid_formats;
+	/* Needs to be set to true if the OS name is being provided by external code */
+	bool *custom_os_name;
+};
+
+/* Structure provided in the MGMT_EVT_OP_OS_MGMT_INFO_APPEND notification callback */
+struct os_mgmt_info_append {
+	/* The format bitmask from the processed commands, the bits should be cleared once
+	 * processed, note that if all_format_specified is specified, the corrisponding bits here
+	 * will not be set
+	 */
+	uint32_t *format_bitmask;
+	/* Will be true if the all 'a' specifier was provided */
+	bool all_format_specified;
+	/* The output buffer which the responses should be appended to. If prior_output is true, a
+	 * space must be added prior to the output response
+	 */
+	uint8_t *output;
+	/* The current size of the output response in the output buffer, must be updated to be the
+	 * size of the output response after appending data
+	 */
+	uint16_t *output_length;
+	/* The size of the output buffer, including null terminator character, if the output
+	 * response would exceed this size, the function must abort and return false to return a
+	 * memory error to the client
+	 */
+	uint16_t buffer_size;
+	/* If there has been prior output, must be set to true if a response has been output */
+	bool *prior_output;
+};
 
 /**
  * @brief Registers the OS management command handler group.
diff --git a/include/zephyr/mgmt/mcumgr/mgmt/callbacks.h b/include/zephyr/mgmt/mcumgr/mgmt/callbacks.h
index 1cd6c94..aeca99e 100644
--- a/include/zephyr/mgmt/mcumgr/mgmt/callbacks.h
+++ b/include/zephyr/mgmt/mcumgr/mgmt/callbacks.h
@@ -146,6 +146,12 @@
 	/** Callback when a reset command has been received. */
 	MGMT_EVT_OP_OS_MGMT_RESET		= MGMT_DEF_EVT_OP_ID(MGMT_EVT_GRP_OS, 0),
 
+	/** Callback when an info command is processed, data is os_mgmt_info_check. */
+	MGMT_EVT_OP_OS_MGMT_INFO_CHECK		= MGMT_DEF_EVT_OP_ID(MGMT_EVT_GRP_OS, 1),
+
+	/** 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),
+
 	/** Used to enable all os_mgmt_group events. */
 	MGMT_EVT_OP_OS_MGMT_ALL			= MGMT_DEF_EVT_OP_ALL(MGMT_EVT_GRP_OS),
 };
diff --git a/subsys/mgmt/mcumgr/grp/os_mgmt/CMakeLists.txt b/subsys/mgmt/mcumgr/grp/os_mgmt/CMakeLists.txt
index 82e7a3a..e0a5bce 100644
--- a/subsys/mgmt/mcumgr/grp/os_mgmt/CMakeLists.txt
+++ b/subsys/mgmt/mcumgr/grp/os_mgmt/CMakeLists.txt
@@ -20,3 +20,10 @@
 endif()
 
 target_link_libraries(mgmt_mcumgr INTERFACE mgmt_mcumgr_grp_os)
+
+if(DEFINED CONFIG_MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME)
+  set(MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME_DIR ${PROJECT_BINARY_DIR}/os_mgmt_auto)
+  file(MAKE_DIRECTORY ${MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME_DIR})
+  file(WRITE ${MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME_DIR}/os_mgmt_build_date.c "/* Auto generated file, do not edit */\n#include <stdint.h>\nuint8_t *MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME = __TIMESTAMP__;")
+  zephyr_library_sources(${MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME_DIR}/os_mgmt_build_date.c)
+endif()
diff --git a/subsys/mgmt/mcumgr/grp/os_mgmt/Kconfig b/subsys/mgmt/mcumgr/grp/os_mgmt/Kconfig
index 381ac6c..df47d9c 100644
--- a/subsys/mgmt/mcumgr/grp/os_mgmt/Kconfig
+++ b/subsys/mgmt/mcumgr/grp/os_mgmt/Kconfig
@@ -123,4 +123,39 @@
 config OS_MGMT_MCUMGR_PARAMS
 	bool "MCUMGR Parameters retrieval command"
 
+config MCUMGR_GRP_OS_INFO
+	bool "Support for info command"
+	help
+	  Can be used similarly to the unix/linux uname command for retrieving system information
+	  including kernel version, processor architecture and board name.
+
+if MCUMGR_GRP_OS_INFO
+
+config MCUMGR_GRP_OS_INFO_MAX_RESPONSE_SIZE
+	int "Maximum response size for info command"
+	default 256
+	range 32 512
+	help
+	  The maximum size of the response to the info command, will use a stack buffer of this
+	  size to store the data in. If the output response is too big then the output will not be
+	  present in the response, which will just contain the result code (rc) of memory error.
+
+config MCUMGR_GRP_OS_INFO_CUSTOM_HOOKS
+	bool "Custom info hooks"
+	depends on MCUMGR_MGMT_NOTIFICATION_HOOKS
+	help
+	  Supports adding custom command/character processing to the info command by using
+	  registered callbacks. Data can be appended to the struct provided in the callback.
+
+config MCUMGR_GRP_OS_INFO_BUILD_DATE_TIME
+	bool "Show build date and time"
+	help
+	  Will allow returning the build date and time of the firmware by using the info with
+	  format option 'b' (will also be returned with all responses by using 'a').
+
+	  Note: This will invalidate reproducible builds of the firmware as it will embed the
+	  build date/time in the output firmware image.
+
+endif
+
 endif
diff --git a/subsys/mgmt/mcumgr/grp/os_mgmt/include/os_mgmt_processor.h b/subsys/mgmt/mcumgr/grp/os_mgmt/include/os_mgmt_processor.h
new file mode 100644
index 0000000..a44b9ab
--- /dev/null
+++ b/subsys/mgmt/mcumgr/grp/os_mgmt/include/os_mgmt_processor.h
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2022 Zephyr authors
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef H_OS_MGMT_PROCESSOR_
+#define H_OS_MGMT_PROCESSOR_
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * Processor name (used in uname output command)
+ * Will be unknown if processor type is not listed
+ * (List extracted from /cmake/gcc-m-cpu.cmake)
+ */
+#if defined(CONFIG_ARM)
+#if defined(CONFIG_CPU_CORTEX_M0)
+#define PROCESSOR_NAME "cortex-m0"
+#elif defined(CONFIG_CPU_CORTEX_M0PLUS)
+#define PROCESSOR_NAME "cortex-m0plus"
+#elif defined(CONFIG_CPU_CORTEX_M1)
+#define PROCESSOR_NAME "cortex-m1"
+#elif defined(CONFIG_CPU_CORTEX_M3)
+#define PROCESSOR_NAME "cortex-m3"
+#elif defined(CONFIG_CPU_CORTEX_M4)
+#define PROCESSOR_NAME "cortex-m4"
+#elif defined(CONFIG_CPU_CORTEX_M7)
+#define PROCESSOR_NAME "cortex-m7"
+#elif defined(CONFIG_CPU_CORTEX_M23)
+#define PROCESSOR_NAME "cortex-m23"
+#elif defined(CONFIG_CPU_CORTEX_M33)
+#if defined(CONFIG_ARMV8_M_DSP)
+#define PROCESSOR_NAME "cortex-m33"
+#else
+#define PROCESSOR_NAME "cortex-m33+nodsp"
+#endif
+#elif defined(CONFIG_CPU_CORTEX_M55)
+#if defined(CONFIG_ARMV8_1_M_MVEF)
+#define PROCESSOR_NAME "cortex-m55"
+#elif defined(CONFIG_ARMV8_1_M_MVEI)
+#define PROCESSOR_NAME "cortex-m55+nomve.fp"
+#elif defined(CONFIG_ARMV8_M_DSP)
+#define PROCESSOR_NAME "cortex-m55+nomve"
+#else
+#define PROCESSOR_NAME "cortex-m55+nodsp"
+#endif
+#elif defined(CONFIG_CPU_CORTEX_R4)
+#if defined(CONFIG_FPU) && defined(CONFIG_CPU_HAS_VFP)
+#define PROCESSOR_NAME "cortex-r4f"
+#else
+#define PROCESSOR_NAME "cortex-r4"
+#endif
+#elif defined(CONFIG_CPU_CORTEX_R5)
+#if defined(CONFIG_FPU) && defined(CONFIG_CPU_HAS_VFP)
+#if !defined(CONFIG_VFP_FEATURE_DOUBLE_PRECISION)
+#define PROCESSOR_NAME "cortex-r5+nofp.dp"
+#else
+#define PROCESSOR_NAME "cortex-r5"
+#endif
+#else
+#define PROCESSOR_NAME "cortex-r5+nofp"
+#endif
+#elif defined(CONFIG_CPU_CORTEX_R7)
+#if defined(CONFIG_FPU) && defined(CONFIG_CPU_HAS_VFP)
+#if !defined(CONFIG_VFP_FEATURE_DOUBLE_PRECISION)
+#define PROCESSOR_NAME "cortex-r7+nofp.dp"
+#else
+#define PROCESSOR_NAME "cortex-r7"
+#endif
+#else
+#define PROCESSOR_NAME "cortex-r7+nofp"
+#endif
+#elif defined(CONFIG_CPU_CORTEX_R52)
+#if defined(CONFIG_FPU) && defined(CONFIG_CPU_HAS_VFP)
+#if !defined(CONFIG_VFP_FEATURE_DOUBLE_PRECISION)
+#define PROCESSOR_NAME "cortex-r52+nofp.dp"
+#else
+#define PROCESSOR_NAME "cortex-r52"
+#endif
+#else
+#define PROCESSOR_NAME "cortex-r52"
+#endif
+#elif defined(CONFIG_CPU_CORTEX_A9)
+#define PROCESSOR_NAME "cortex-a9"
+#endif
+#elif defined(CONFIG_ARM64)
+#if defined(CONFIG_CPU_CORTEX_A53)
+#define PROCESSOR_NAME "cortex-a53"
+#if defined(CONFIG_CPU_CORTEX_A55)
+#define PROCESSOR_NAME "cortex-a55"
+#elif defined(CONFIG_CPU_CORTEX_A72)
+#define PROCESSOR_NAME "cortex-a72"
+#elif defined(CONFIG_CPU_CORTEX_R82)
+#define PROCESSOR_NAME "armv8.4-a+nolse"
+#endif
+#endif
+#elif defined(CONFIG_ARC)
+#if defined(CONFIG_CPU_EM4_FPUS)
+#define PROCESSOR_NAME "em4_fpus"
+#elif defined(CONFIG_CPU_EM4_DMIPS)
+#define PROCESSOR_NAME "em4_dmips"
+#elif defined(CONFIG_CPU_EM4_FPUDA)
+#define PROCESSOR_NAME "em4_fpuda"
+#elif defined(CONFIG_CPU_HS3X)
+#define PROCESSOR_NAME "archs"
+#elif defined(CONFIG_CPU_HS5X)
+#define PROCESSOR_NAME "hs5x"
+#elif defined(CONFIG_CPU_HS6X)
+#define PROCESSOR_NAME "hs6x"
+#elif defined(CONFIG_CPU_EM4)
+#define PROCESSOR_NAME "arcem"
+#elif defined(CONFIG_CPU_EM6)
+#define PROCESSOR_NAME "arcem"
+#endif
+#elif defined(CONFIG_X86)
+#if defined(CONFIG_X86_64)
+#define PROCESSOR_NAME "x86_64"
+#else
+#define PROCESSOR_NAME "x86"
+#endif
+#elif defined(CONFIG_RISCV)
+#define PROCESSOR_NAME "riscv"
+#endif
+
+#ifndef PROCESSOR_NAME
+#warning "Processor type could not be determined"
+#define PROCESSOR_NAME "unknown"
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* H_OS_MGMT_PROCESSOR_ */
diff --git a/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c b/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c
index 46de336..869f9e9 100644
--- a/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c
+++ b/subsys/mgmt/mcumgr/grp/os_mgmt/src/os_mgmt.c
@@ -28,6 +28,18 @@
 #include <zephyr/mgmt/mcumgr/mgmt/callbacks.h>
 #endif
 
+#ifdef CONFIG_MCUMGR_GRP_OS_INFO
+#include <stdio.h>
+#include <version.h>
+#include <os_mgmt_processor.h>
+#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
+
 #ifdef CONFIG_REBOOT
 static void os_mgmt_reset_work_handler(struct k_work *work);
 static void os_mgmt_reset_cb(struct k_timer *timer);
@@ -54,6 +66,19 @@
 };
 #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
  */
@@ -318,6 +343,302 @@
 }
 #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_VAL(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
+
+	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));
+#endif
+
+	if (valid_formats != format.len) {
+		/* A provided format specifier is not valid */
+		return MGMT_ERR_EINVAL;
+	} 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 */
+	rc = mgmt_callback_notify(MGMT_EVT_OP_OS_MGMT_INFO_APPEND, &append_data,
+				  sizeof(append_data));
+
+	if (rc != MGMT_ERR_EOK) {
+		return rc;
+	}
+#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
+
 static const struct mgmt_handler os_mgmt_group_handlers[] = {
 #ifdef CONFIG_OS_MGMT_ECHO
 	[OS_MGMT_ID_ECHO] = {
@@ -339,6 +660,11 @@
 		os_mgmt_mcumgr_params, NULL
 	},
 #endif
+#ifdef CONFIG_MCUMGR_GRP_OS_INFO
+	[OS_MGMT_ID_INFO] = {
+		os_mgmt_info, NULL
+	},
+#endif
 };
 
 #define OS_MGMT_GROUP_SZ ARRAY_SIZE(os_mgmt_group_handlers)