zephyr: Allow user-defined boot serial extensions

This allows for out-of-tree modules to define their own boot serial
functions by using iterable sections.
Note that this also removes the custom img list command, which was
not used in-tree.

Signed-off-by: Jamie McCrae <jamie.mccrae@nordicsemi.no>
diff --git a/boot/zephyr/CMakeLists.txt b/boot/zephyr/CMakeLists.txt
index 638bd91..159ef5d 100644
--- a/boot/zephyr/CMakeLists.txt
+++ b/boot/zephyr/CMakeLists.txt
@@ -85,6 +85,17 @@
   zephyr_library_sources(
     boot_serial_extensions.c
     )
+
+  zephyr_linker_sources_ifdef(
+    CONFIG_ENABLE_MGMT_PERUSER
+    SECTIONS include/boot_serial/boot_serial.ld
+  )
+
+  if(DEFINED CONFIG_BOOT_MGMT_CUSTOM_STORAGE_ERASE OR DEFINED CONFIG_BOOT_MGMT_CUSTOM_IMG_LIST)
+    zephyr_library_sources(
+      boot_serial_extension_zephyr_basic.c
+      )
+  endif()
 endif()
 
 if(NOT DEFINED CONFIG_FLASH_PAGE_LAYOUT)
diff --git a/boot/zephyr/Kconfig.serial_recovery b/boot/zephyr/Kconfig.serial_recovery
index 52ec3b0..c73badd 100644
--- a/boot/zephyr/Kconfig.serial_recovery
+++ b/boot/zephyr/Kconfig.serial_recovery
@@ -128,12 +128,6 @@
 	  Note that the storage partition needs to be defined, in DTS, otherwise
 	  enabling the option will cause a compilation to fail.
 
-config BOOT_MGMT_CUSTOM_IMG_LIST
-	bool "Enable custom image list command"
-	help
-	  The option enables command which returns versions and installation
-	  statuses (custom property) for all images.
-
 endif # ENABLE_MGMT_PERUSER
 
 menu "Entrance methods"
diff --git a/boot/zephyr/boot_serial_extension_zephyr_basic.c b/boot/zephyr/boot_serial_extension_zephyr_basic.c
new file mode 100644
index 0000000..b0c75f4
--- /dev/null
+++ b/boot/zephyr/boot_serial_extension_zephyr_basic.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2021-2023 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdio.h>
+#include <zephyr/kernel.h>
+#include <zephyr/drivers/flash.h>
+#include <zephyr/mgmt/mcumgr/mgmt/mgmt_defines.h>
+#include <zephyr/mgmt/mcumgr/grp/zephyr/zephyr_basic.h>
+#include <../subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/smp_internal.h>
+
+#include <flash_map_backend/flash_map_backend.h>
+#include <sysflash/sysflash.h>
+
+#include "bootutil/bootutil_log.h"
+#include "../boot_serial/src/boot_serial_priv.h"
+#include <zcbor_encode.h>
+
+#include "bootutil/image.h"
+#include "bootutil/bootutil_public.h"
+#include "bootutil/boot_hooks.h"
+
+#include <boot_serial/boot_serial_extensions.h>
+
+BOOT_LOG_MODULE_DECLARE(mcuboot);
+
+#ifdef CONFIG_BOOT_MGMT_CUSTOM_STORAGE_ERASE
+static int bs_custom_storage_erase(const struct nmgr_hdr *hdr,
+                                   const char *buffer, int len,
+                                   zcbor_state_t *cs)
+{
+    int rc;
+    const struct flash_area *fa;
+
+    (void)buffer;
+    (void)len;
+
+    if (hdr->nh_group != ZEPHYR_MGMT_GRP_BASIC || hdr->nh_op != NMGR_OP_WRITE ||
+        hdr->nh_id != ZEPHYR_MGMT_GRP_BASIC_CMD_ERASE_STORAGE) {
+        return MGMT_ERR_ENOTSUP;
+    }
+
+    rc = flash_area_open(FIXED_PARTITION_ID(storage_partition), &fa);
+
+    if (rc < 0) {
+        BOOT_LOG_ERR("failed to open flash area");
+    } else {
+        rc = flash_area_erase(fa, 0, flash_area_get_size(fa));
+        if (rc < 0) {
+            BOOT_LOG_ERR("failed to erase flash area");
+        }
+        flash_area_close(fa);
+    }
+    if (rc == 0) {
+        rc = MGMT_ERR_OK;
+    } else {
+        rc = MGMT_ERR_EUNKNOWN;
+    }
+
+    zcbor_map_start_encode(cs, 10);
+    zcbor_tstr_put_lit(cs, "rc");
+    zcbor_uint32_put(cs, rc);
+    zcbor_map_end_encode(cs, 10);
+
+    return rc;
+}
+
+MCUMGR_HANDLER_DEFINE(storage_erase, bs_custom_storage_erase);
+#endif
diff --git a/boot/zephyr/boot_serial_extensions.c b/boot/zephyr/boot_serial_extensions.c
index b8bcd3e..abbb651 100644
--- a/boot/zephyr/boot_serial_extensions.c
+++ b/boot/zephyr/boot_serial_extensions.c
@@ -1,161 +1,30 @@
 /*
- * Copyright (c) 2021 Nordic Semiconductor ASA
+ * Copyright (c) 2021-2023 Nordic Semiconductor ASA
  *
  * SPDX-License-Identifier: Apache-2.0
  */
 
-#include <stdio.h>
 #include <zephyr/kernel.h>
-#include <zephyr/drivers/flash.h>
-#include <zephyr/mgmt/mcumgr/mgmt/mgmt_defines.h>
-#include <zephyr/mgmt/mcumgr/grp/zephyr/zephyr_basic.h>
-#include <../subsys/mgmt/mcumgr/transport/include/mgmt/mcumgr/transport/smp_internal.h>
-
-#include <flash_map_backend/flash_map_backend.h>
-#include <sysflash/sysflash.h>
 
 #include "bootutil/bootutil_log.h"
 #include "../boot_serial/src/boot_serial_priv.h"
 #include <zcbor_encode.h>
-
-#include "bootutil/image.h"
-#include "bootutil/bootutil_public.h"
-#include "bootutil/boot_hooks.h"
+#include <boot_serial/boot_serial_extensions.h>
 
 BOOT_LOG_MODULE_DECLARE(mcuboot);
 
-#ifdef CONFIG_BOOT_MGMT_CUSTOM_STORAGE_ERASE
-static int bs_custom_storage_erase(zcbor_state_t *cs)
-{
-    int rc;
-
-    const struct flash_area *fa;
-
-    rc = flash_area_open(FIXED_PARTITION_ID(storage_partition), &fa);
-
-    if (rc < 0) {
-        BOOT_LOG_ERR("failed to open flash area");
-    } else {
-        rc = flash_area_erase(fa, 0, flash_area_get_size(fa));
-        if (rc < 0) {
-            BOOT_LOG_ERR("failed to erase flash area");
-        }
-        flash_area_close(fa);
-    }
-    if (rc == 0) {
-        rc = MGMT_ERR_OK;
-    } else {
-        rc = MGMT_ERR_EUNKNOWN;
-    }
-
-    zcbor_map_start_encode(cs, 10);
-    zcbor_tstr_put_lit(cs, "rc");
-    zcbor_uint32_put(cs, rc);
-    zcbor_map_end_encode(cs, 10);
-
-    return rc;
-}
-#endif
-
-#ifdef MCUBOOT_MGMT_CUSTOM_IMG_LIST
-static int custom_img_status(int image_index, uint32_t slot,char *buffer,
-                        ssize_t len)
-{
-    uint32_t area_id;
-    struct flash_area const *fap;
-    struct image_header hdr;
-    int rc;
-    int img_install_stat;
-
-    rc = BOOT_HOOK_CALL(boot_img_install_stat_hook, BOOT_HOOK_REGULAR,
-                        image_index, slot, &img_install_stat);
-    if (rc == BOOT_HOOK_REGULAR)
-    {
-        img_install_stat = 0;
-    }
-
-    rc = BOOT_HOOK_CALL(boot_read_image_header_hook, BOOT_HOOK_REGULAR,
-                        image_index, slot, &hdr);
-    if (rc == BOOT_HOOK_REGULAR)
-    {
-        area_id = flash_area_id_from_multi_image_slot(image_index, slot);
-
-        rc = flash_area_open(area_id, &fap);
-        if (rc) {
-            return rc;
-        }
-
-        rc = flash_area_read(fap, 0, &hdr, sizeof(hdr));
-
-        flash_area_close(fap);
-    }
-
-    if (rc == 0) {
-        if (hdr.ih_magic == IMAGE_MAGIC) {
-            snprintf(buffer, len, "ver=%d.%d.%d.%d,install_stat=%d",
-                    hdr.ih_ver.iv_major,
-                    hdr.ih_ver.iv_minor,
-                    hdr.ih_ver.iv_revision,
-                    hdr.ih_ver.iv_build_num,
-                    img_install_stat);
-        } else {
-            rc = 1;
-        }
-    }
-
-    return rc;
-}
-
-static int bs_custom_img_list(zcbor_state_t *cs)
-{
-    int rc = 0;
-    char tmpbuf[64];	/* Buffer should fit version and flags */
-
-    zcbor_map_start_encode(cs, 10);
-
-    for (int img = 0; img < MCUBOOT_IMAGE_NUMBER; img++) {
-        for (int slot = 0; slot < 2; slot++) {
-            rc = custom_img_status(img, slot, tmpbuf, sizeof(tmpbuf));
-
-            zcbor_int32_put(cs, img * 2 + slot + 1);
-            if (rc == 0) {
-                zcbor_tstr_put_term(cs, tmpbuf);
-            } else {
-                zcbor_tstr_put_lit(cs, "");
-            }
-        }
-    }
-
-    zcbor_tstr_put_lit(cs, "rc");
-    zcbor_uint32_put(cs, MGMT_ERR_OK);
-    zcbor_map_end_encode(cs, 10);
-
-    return rc;
-}
-
-#ifndef ZEPHYR_MGMT_GRP_BASIC_CMD_IMAGE_LIST
-    #define ZEPHYR_MGMT_GRP_BASIC_CMD_IMAGE_LIST 1
-#endif
-#endif /*MCUBOOT_MGMT_CUSTOM_IMG_LIST*/
-
 int bs_peruser_system_specific(const struct nmgr_hdr *hdr, const char *buffer,
                                int len, zcbor_state_t *cs)
 {
     int mgmt_rc = MGMT_ERR_ENOTSUP;
 
-    if (hdr->nh_group == ZEPHYR_MGMT_GRP_BASIC) {
-        if (hdr->nh_op == NMGR_OP_WRITE) {
-#ifdef CONFIG_BOOT_MGMT_CUSTOM_STORAGE_ERASE
-            if (hdr->nh_id == ZEPHYR_MGMT_GRP_BASIC_CMD_ERASE_STORAGE) {
-                mgmt_rc = bs_custom_storage_erase(cs);
+    STRUCT_SECTION_FOREACH(mcuboot_bs_custom_handlers, function) {
+        if (function->handler) {
+            mgmt_rc = function->handler(hdr, buffer, len, cs);
+
+            if (mgmt_rc != MGMT_ERR_ENOTSUP) {
+                break;
             }
-#endif
-        } else if (hdr->nh_op == NMGR_OP_READ) {
-#ifdef MCUBOOT_MGMT_CUSTOM_IMG_LIST
-            if (hdr->nh_id == ZEPHYR_MGMT_GRP_BASIC_CMD_IMAGE_LIST) {
-                mgmt_rc = bs_custom_img_list(cs);
-            }
-#endif
         }
     }
 
diff --git a/boot/zephyr/include/boot_serial/boot_serial.ld b/boot/zephyr/include/boot_serial/boot_serial.ld
new file mode 100644
index 0000000..c0e82ad
--- /dev/null
+++ b/boot/zephyr/include/boot_serial/boot_serial.ld
@@ -0,0 +1,9 @@
+/*
+ * Copyright (c) 2023 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr/linker/iterable_sections.h>
+
+ITERABLE_SECTION_ROM(mcuboot_bs_custom_handlers, 4)
diff --git a/boot/zephyr/include/boot_serial/boot_serial_extensions.h b/boot/zephyr/include/boot_serial/boot_serial_extensions.h
new file mode 100644
index 0000000..6eea574
--- /dev/null
+++ b/boot/zephyr/include/boot_serial/boot_serial_extensions.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2023 Nordic Semiconductor ASA
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef H_BOOT_SERIAL_EXTENTIONS_
+#define H_BOOT_SERIAL_EXTENTIONS_
+
+#include <zephyr/kernel.h>
+#include <zephyr/sys/util_macro.h>
+#include <zephyr/sys/iterable_sections.h>
+
+/**
+ * Callback handler prototype for boot serial extensions.
+ *
+ * @param[in]   hdr       MCUmgr header
+ * @param[in]   buffer    Buffer with first MCUmgr message
+ * @param[in]   len       Length of data in buffer
+ * @param[out]  cs        Response
+ *
+ * @return      MGMT_ERR_ENOTSUP to run other handlers, other MGMT_ERR_* value
+ *              when expected handler has ran.
+ */
+typedef int (*bs_custom_handler_cb)(const struct nmgr_hdr *hdr,
+                                    const char *buffer, int len,
+                                    zcbor_state_t *cs);
+
+struct mcuboot_bs_custom_handlers {
+    const bs_custom_handler_cb handler;
+};
+
+/* Used to create an iterable section containing a boot serial handler
+ * function
+ */
+#define MCUMGR_HANDLER_DEFINE(name, _handler)                          \
+        STRUCT_SECTION_ITERABLE(mcuboot_bs_custom_handlers, name) = {  \
+            .handler = _handler,                                       \
+        }
+
+#endif /* H_BOOT_SERIAL_EXTENTIONS_ */