boot_serial: Add optional img mgmt slot info feature

Adds a minimal version of the slot info feature to serial recovery,
and enables it by default.

Signed-off-by: Jamie McCrae <jamie.mccrae@nordicsemi.no>
diff --git a/boot/boot_serial/src/boot_serial.c b/boot/boot_serial/src/boot_serial.c
index fb73e6b..2e9df80 100644
--- a/boot/boot_serial/src/boot_serial.c
+++ b/boot/boot_serial/src/boot_serial.c
@@ -95,12 +95,28 @@
 #else
 #define BOOT_SERIAL_HASH_SIZE_MAX 0
 #endif
+#ifdef MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO
+#define BOOT_SERIAL_SLOT_INFO_SIZE_MAX 164
+#else
+#define BOOT_SERIAL_SLOT_INFO_SIZE_MAX 0
+#endif
 
-#define BOOT_SERIAL_OUT_MAX     ((128 + BOOT_SERIAL_IMAGE_STATE_SIZE_MAX + \
-                                  BOOT_SERIAL_HASH_SIZE_MAX) * BOOT_IMAGE_NUMBER)
+#if (128 + BOOT_SERIAL_IMAGE_STATE_SIZE_MAX + BOOT_SERIAL_HASH_SIZE_MAX) > \
+    BOOT_SERIAL_SLOT_INFO_SIZE_MAX
+#define BOOT_SERIAL_MAX_MESSAGE_SIZE (128 + BOOT_SERIAL_IMAGE_STATE_SIZE_MAX + \
+        BOOT_SERIAL_HASH_SIZE_MAX)
+#else
+#define BOOT_SERIAL_MAX_MESSAGE_SIZE BOOT_SERIAL_SLOT_INFO_SIZE_MAX
+#endif
+
+#define BOOT_SERIAL_OUT_MAX     (BOOT_SERIAL_MAX_MESSAGE_SIZE * BOOT_IMAGE_NUMBER)
 
 #define BOOT_SERIAL_FRAME_MTU   124 /* 127 - pkt start (2 bytes) and stop (1 byte) */
 
+/* Number of estimated CBOR elements for responses */
+#define CBOR_ENTRIES_SLOT_INFO_IMAGE_MAP 4
+#define CBOR_ENTRIES_SLOT_INFO_SLOTS_MAP 3
+
 #ifdef __ZEPHYR__
 /* base64 lib encodes data to null-terminated string */
 #define BASE64_ENCODE_SIZE(in_size) ((((((in_size) - 1) / 3) * 4) + 4) + 1)
@@ -259,11 +275,7 @@
         int swap_status = boot_swap_type_multi(image_index);
 #endif
 
-#ifdef MCUBOOT_SINGLE_APPLICATION_SLOT
-        for (slot = 0; slot < 1; slot++) {
-#else
-        for (slot = 0; slot < 2; slot++) {
-#endif
+        for (slot = 0; slot < MCUBOOT_IMAGE_NUMBER; slot++) {
             FIH_DECLARE(fih_rc, FIH_FAILURE);
             uint8_t tmpbuf[64];
 
@@ -576,6 +588,139 @@
     }
 }
 
+#ifdef MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO
+static void
+bs_slot_info(uint8_t op, char *buf, int len)
+{
+    uint32_t slot, area_id;
+    const struct flash_area *fap;
+    uint8_t image_index = 0;
+    int rc;
+    bool ok = true;
+    const struct image_max_size *image_max_sizes;
+
+    if (op != NMGR_OP_READ) {
+        bs_rc_rsp(MGMT_ERR_ENOTSUP);
+    }
+
+    image_max_sizes = boot_get_max_app_size();
+
+    zcbor_map_start_encode(cbor_state, 1);
+    zcbor_tstr_put_lit_cast(cbor_state, "images");
+    zcbor_list_start_encode(cbor_state, MCUBOOT_IMAGE_NUMBER);
+
+    IMAGES_ITER(image_index) {
+        for (slot = 0; slot < MCUBOOT_IMAGE_NUMBER; slot++) {
+            if (slot == 0) {
+                    ok = zcbor_map_start_encode(cbor_state, CBOR_ENTRIES_SLOT_INFO_IMAGE_MAP) &&
+                         zcbor_tstr_put_lit(cbor_state, "image") &&
+                         zcbor_uint32_put(cbor_state, (uint32_t)image_index) &&
+                         zcbor_tstr_put_lit(cbor_state, "slots") &&
+                         zcbor_list_start_encode(cbor_state, MCUBOOT_IMAGE_NUMBER);
+
+                    if (!ok) {
+                            goto finish;
+                    }
+                }
+
+                ok = zcbor_map_start_encode(cbor_state, CBOR_ENTRIES_SLOT_INFO_SLOTS_MAP) &&
+                     zcbor_tstr_put_lit(cbor_state, "slot") &&
+                     zcbor_uint32_put(cbor_state, slot);
+
+                if (!ok) {
+                    goto finish;
+                }
+
+                area_id = flash_area_id_from_multi_image_slot(image_index, slot);
+                rc = flash_area_open(area_id, &fap);
+
+                if (rc) {
+                    ok = zcbor_tstr_put_lit(cbor_state, "rc") &&
+                         zcbor_int32_put(cbor_state, rc);
+                } else {
+                    if (sizeof(fap->fa_size) == sizeof(uint64_t)) {
+                        ok = zcbor_tstr_put_lit(cbor_state, "size") &&
+                             zcbor_uint64_put(cbor_state, fap->fa_size);
+                    } else {
+                        ok = zcbor_tstr_put_lit(cbor_state, "size") &&
+                             zcbor_uint32_put(cbor_state, fap->fa_size);
+                    }
+
+                    if (!ok) {
+                        flash_area_close(fap);
+                        goto finish;
+                    }
+
+                    /*
+                     * Check if we support uploading to this slot and if so, return the
+                     * image ID
+                     */
+#if defined(MCUBOOT_SINGLE_APPLICATION_SLOT)
+                    ok = zcbor_tstr_put_lit(cbor_state, "upload_image_id") &&
+                         zcbor_uint32_put(cbor_state, (image_index + 1));
+#elif defined(MCUBOOT_SERIAL_DIRECT_IMAGE_UPLOAD)
+                    ok = zcbor_tstr_put_lit(cbor_state, "upload_image_id") &&
+                         zcbor_uint32_put(cbor_state, (image_index * 2 + slot + 1));
+#else
+                    if (slot == 1) {
+                        ok = zcbor_tstr_put_lit(cbor_state, "upload_image_id") &&
+                             zcbor_uint32_put(cbor_state, (image_index * 2 + 1));
+                    }
+#endif
+
+                    flash_area_close(fap);
+
+                    if (!ok) {
+                        goto finish;
+                    }
+
+                    ok = zcbor_map_end_encode(cbor_state, CBOR_ENTRIES_SLOT_INFO_SLOTS_MAP);
+
+                    if (!ok) {
+                            goto finish;
+                    }
+
+                    if (slot == (MCUBOOT_IMAGE_NUMBER - 1)) {
+                        ok = zcbor_list_end_encode(cbor_state, MCUBOOT_IMAGE_NUMBER);
+
+                        if (!ok) {
+                            goto finish;
+                        }
+
+                        if (image_max_sizes[image_index].calculated == true) {
+                            ok = zcbor_tstr_put_lit(cbor_state, "max_image_size") &&
+                                 zcbor_uint32_put(cbor_state,
+                                                  image_max_sizes[image_index].max_size);
+
+                            if (!ok) {
+                                goto finish;
+                            }
+                        }
+
+                        ok = zcbor_map_end_encode(cbor_state, CBOR_ENTRIES_SLOT_INFO_IMAGE_MAP);
+
+                    }
+                }
+
+                if (!ok) {
+                    goto finish;
+                }
+            }
+        }
+
+        ok = zcbor_list_end_encode(cbor_state, MCUBOOT_IMAGE_NUMBER) &&
+             zcbor_map_end_encode(cbor_state, 1);
+
+finish:
+    if (!ok) {
+        reset_cbor_state();
+        bs_rc_rsp(MGMT_ERR_ENOMEM);
+    }
+
+    boot_serial_output();
+}
+#endif
+
 #ifdef MCUBOOT_ERASE_PROGRESSIVELY
 /** Erases range of flash, aligned to sector size
  *
@@ -1019,6 +1164,11 @@
         case IMGMGR_NMGR_ID_UPLOAD:
             bs_upload(buf, len);
             break;
+#ifdef MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO
+        case IMGMGR_NMGR_ID_SLOT_INFO:
+            bs_slot_info(hdr->nh_op, buf, len);
+            break;
+#endif
         default:
             bs_rc_rsp(MGMT_ERR_ENOTSUP);
             break;
diff --git a/boot/boot_serial/src/boot_serial_priv.h b/boot/boot_serial/src/boot_serial_priv.h
index 7056aef..1801da1 100644
--- a/boot/boot_serial/src/boot_serial_priv.h
+++ b/boot/boot_serial/src/boot_serial_priv.h
@@ -81,6 +81,7 @@
  */
 #define IMGMGR_NMGR_ID_STATE            0
 #define IMGMGR_NMGR_ID_UPLOAD           1
+#define IMGMGR_NMGR_ID_SLOT_INFO        6
 
 void boot_serial_input(char *buf, int len);
 extern const struct boot_uart_funcs *boot_uf;
diff --git a/boot/bootutil/src/loader.c b/boot/bootutil/src/loader.c
index 4b0299f..8663fbf 100644
--- a/boot/bootutil/src/loader.c
+++ b/boot/bootutil/src/loader.c
@@ -63,10 +63,23 @@
 
 static struct boot_loader_state boot_data;
 
-#if defined(MCUBOOT_DATA_SHARING)
+#if defined(MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO) || defined(MCUBOOT_DATA_SHARING)
 static struct image_max_size image_max_sizes[BOOT_IMAGE_NUMBER] = {0};
 #endif
 
+#if !defined(__BOOTSIM__)
+/* Used for holding static buffers in multiple functions to work around issues
+ * in older versions of gcc (e.g. 4.8.4)
+ */
+struct sector_buffer_t {
+    boot_sector_t *primary;
+    boot_sector_t *secondary;
+#if MCUBOOT_SWAP_USING_SCRATCH
+    boot_sector_t *scratch;
+#endif
+};
+#endif
+
 #if (BOOT_IMAGE_NUMBER > 1)
 #define IMAGES_ITER(x) for ((x) = 0; (x) < BOOT_IMAGE_NUMBER; ++(x))
 #else
@@ -1840,7 +1853,7 @@
     int rc;
     FIH_DECLARE(fih_rc, FIH_FAILURE);
 
-#if defined(MCUBOOT_DATA_SHARING)
+#if defined(MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO) || defined(MCUBOOT_DATA_SHARING)
     int max_size;
 #endif
 
@@ -1870,7 +1883,7 @@
         return;
     }
 
-#if defined(MCUBOOT_DATA_SHARING)
+#if defined(MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO) || defined(MCUBOOT_DATA_SHARING)
     /* Fetch information on maximum sizes for later usage, if needed */
     max_size = app_max_size(state);
 
@@ -2100,11 +2113,36 @@
 #endif
 }
 
+#if !defined(__BOOTSIM__)
+static void boot_get_sector_buffers(struct sector_buffer_t *buffers)
+{
+    /* The array of slot sectors are defined here (as opposed to file scope) so
+     * that they don't get allocated for non-boot-loader apps.  This is
+     * necessary because the gcc option "-fdata-sections" doesn't seem to have
+     * any effect in older gcc versions (e.g., 4.8.4).
+     */
+    static boot_sector_t primary_slot_sectors[BOOT_IMAGE_NUMBER][BOOT_MAX_IMG_SECTORS];
+    static boot_sector_t secondary_slot_sectors[BOOT_IMAGE_NUMBER][BOOT_MAX_IMG_SECTORS];
+#if MCUBOOT_SWAP_USING_SCRATCH
+    static boot_sector_t scratch_sectors[BOOT_MAX_IMG_SECTORS];
+#endif
+
+    buffers->primary = (boot_sector_t *)&primary_slot_sectors;
+    buffers->secondary = (boot_sector_t *)&secondary_slot_sectors;
+#if MCUBOOT_SWAP_USING_SCRATCH
+    buffers->scratch = (boot_sector_t *)&scratch_sectors;
+#endif
+}
+#endif
+
 fih_ret
 context_boot_go(struct boot_loader_state *state, struct boot_rsp *rsp)
 {
     size_t slot;
     struct boot_status bs;
+#if !defined(__BOOTSIM__)
+    struct sector_buffer_t sector_buffers;
+#endif
     int rc = -1;
     FIH_DECLARE(fih_rc, FIH_FAILURE);
     int fa_id;
@@ -2112,6 +2150,7 @@
     bool has_upgrade;
     volatile int fih_cnt;
 
+#if defined(__BOOTSIM__)
     /* The array of slot sectors are defined here (as opposed to file scope) so
      * that they don't get allocated for non-boot-loader apps.  This is
      * necessary because the gcc option "-fdata-sections" doesn't seem to have
@@ -2122,6 +2161,7 @@
 #if MCUBOOT_SWAP_USING_SCRATCH
     TARGET_STATIC boot_sector_t scratch_sectors[BOOT_MAX_IMG_SECTORS];
 #endif
+#endif
 
     has_upgrade = false;
 
@@ -2129,6 +2169,10 @@
     (void)has_upgrade;
 #endif
 
+#if !defined(__BOOTSIM__)
+    boot_get_sector_buffers(&sector_buffers);
+#endif
+
     /* Iterate over all the images. By the end of the loop the swap type has
      * to be determined for each image and all aborted swaps have to be
      * completed.
@@ -2149,6 +2193,15 @@
 
         image_index = BOOT_CURR_IMG(state);
 
+#if !defined(__BOOTSIM__)
+        BOOT_IMG(state, BOOT_PRIMARY_SLOT).sectors =
+            &sector_buffers.primary[image_index];
+        BOOT_IMG(state, BOOT_SECONDARY_SLOT).sectors =
+            &sector_buffers.secondary[image_index];
+#if MCUBOOT_SWAP_USING_SCRATCH
+        state->scratch.sectors = sector_buffers.scratch;
+#endif
+#else
         BOOT_IMG(state, BOOT_PRIMARY_SLOT).sectors =
             primary_slot_sectors[image_index];
         BOOT_IMG(state, BOOT_SECONDARY_SLOT).sectors =
@@ -2156,6 +2209,7 @@
 #if MCUBOOT_SWAP_USING_SCRATCH
         state->scratch.sectors = scratch_sectors;
 #endif
+#endif
 
         /* Open primary and secondary image areas for the duration
          * of this call.
@@ -3337,12 +3391,100 @@
     }
 }
 
-#if defined(MCUBOOT_DATA_SHARING)
+#if defined(MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO)
 /**
-* Fetches the maximum allowed size of the image
-*/
+ * Reads image data to find out the maximum application sizes. Only needs to
+ * be called in serial recovery mode, as the state informatio is unpopulated
+ * at that time
+ */
+static void boot_fetch_slot_state_sizes(void)
+{
+    struct sector_buffer_t sector_buffers;
+    size_t slot;
+    int rc = -1;
+    int fa_id;
+    int image_index;
+
+    boot_get_sector_buffers(&sector_buffers);
+
+    IMAGES_ITER(BOOT_CURR_IMG(&boot_data)) {
+        int max_size = 0;
+
+        image_index = BOOT_CURR_IMG(&boot_data);
+
+        BOOT_IMG(&boot_data, BOOT_PRIMARY_SLOT).sectors =
+            &sector_buffers.primary[image_index];
+        BOOT_IMG(&boot_data, BOOT_SECONDARY_SLOT).sectors =
+            &sector_buffers.secondary[image_index];
+#if MCUBOOT_SWAP_USING_SCRATCH
+        boot_data.scratch.sectors = sector_buffers.scratch;;
+#endif
+
+        /* Open primary and secondary image areas for the duration
+         * of this call.
+         */
+        for (slot = 0; slot < BOOT_NUM_SLOTS; slot++) {
+            fa_id = flash_area_id_from_multi_image_slot(image_index, slot);
+            rc = flash_area_open(fa_id, &BOOT_IMG_AREA(&boot_data, slot));
+            assert(rc == 0);
+
+            if (rc != 0) {
+                goto finish;
+            }
+        }
+
+#if MCUBOOT_SWAP_USING_SCRATCH
+        rc = flash_area_open(FLASH_AREA_IMAGE_SCRATCH,
+                             &BOOT_SCRATCH_AREA(&boot_data));
+        assert(rc == 0);
+
+        if (rc != 0) {
+            goto finish;
+        }
+#endif
+
+        /* Determine the sector layout of the image slots and scratch area. */
+        rc = boot_read_sectors(&boot_data);
+
+        if (rc == 0) {
+            max_size = app_max_size(&boot_data);
+
+            if (max_size > 0) {
+                image_max_sizes[image_index].calculated = true;
+                image_max_sizes[image_index].max_size = max_size;
+            }
+        }
+    }
+
+finish:
+    close_all_flash_areas(&boot_data);
+    memset(&boot_data, 0x00, sizeof(boot_data));
+}
+#endif
+
+#if defined(MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO) || defined(MCUBOOT_DATA_SHARING)
+/**
+ * Fetches the maximum allowed size of the image
+ */
 const struct image_max_size *boot_get_max_app_size(void)
 {
+#if defined(MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO)
+    uint8_t i = 0;
+
+    while (i < BOOT_IMAGE_NUMBER) {
+        if (image_max_sizes[i].calculated == true) {
+            break;
+        }
+
+        ++i;
+    }
+
+    if (i == BOOT_IMAGE_NUMBER) {
+        /* Information not available, need to fetch it */
+        boot_fetch_slot_state_sizes();
+    }
+#endif
+
     return image_max_sizes;
 }
 #endif
diff --git a/boot/zephyr/Kconfig.serial_recovery b/boot/zephyr/Kconfig.serial_recovery
index fe82697..7ed9a7d 100644
--- a/boot/zephyr/Kconfig.serial_recovery
+++ b/boot/zephyr/Kconfig.serial_recovery
@@ -202,4 +202,11 @@
 	  If y, image states will be included with image lists and the set
 	  state command can be used to mark an image as test/confirmed.
 
+config BOOT_SERIAL_IMG_GRP_SLOT_INFO
+	bool "Slot info"
+	default y if UPDATEABLE_IMAGE_NUMBER > 1
+	help
+	  If y, will include the slot info command which lists what available
+	  slots there are in the system.
+
 endif # MCUBOOT_SERIAL
diff --git a/boot/zephyr/include/mcuboot_config/mcuboot_config.h b/boot/zephyr/include/mcuboot_config/mcuboot_config.h
index ab2e8ba..fe1ec2e 100644
--- a/boot/zephyr/include/mcuboot_config/mcuboot_config.h
+++ b/boot/zephyr/include/mcuboot_config/mcuboot_config.h
@@ -233,6 +233,10 @@
 #define MCUBOOT_SERIAL_IMG_GRP_IMAGE_STATE
 #endif
 
+#ifdef CONFIG_BOOT_SERIAL_IMG_GRP_SLOT_INFO
+#define MCUBOOT_SERIAL_IMG_GRP_SLOT_INFO
+#endif
+
 #ifdef CONFIG_MCUBOOT_SERIAL
 #define MCUBOOT_SERIAL_RECOVERY
 #endif