boot_serial: Add image state set/get
Adds optional image state set/get functionality to serial recovery
mode which allows for listing image states and marking images to
be tested or as confirmed.
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 5b90fa5..b993751 100644
--- a/boot/boot_serial/src/boot_serial.c
+++ b/boot/boot_serial/src/boot_serial.c
@@ -87,7 +87,20 @@
#define MCUBOOT_SERIAL_MAX_RECEIVE_SIZE 512
#endif
-#define BOOT_SERIAL_OUT_MAX (160 * BOOT_IMAGE_NUMBER)
+#ifdef MCUBOOT_SERIAL_IMG_GRP_IMAGE_STATE
+#define BOOT_SERIAL_IMAGE_STATE_SIZE_MAX 48
+#else
+#define BOOT_SERIAL_IMAGE_STATE_SIZE_MAX 0
+#endif
+#ifdef MCUBOOT_SERIAL_IMG_GRP_HASH
+#define BOOT_SERIAL_HASH_SIZE_MAX 36
+#else
+#define BOOT_SERIAL_HASH_SIZE_MAX 0
+#endif
+
+#define BOOT_SERIAL_OUT_MAX ((128 + BOOT_SERIAL_IMAGE_STATE_SIZE_MAX + \
+ BOOT_SERIAL_HASH_SIZE_MAX) * BOOT_IMAGE_NUMBER)
+
#define BOOT_SERIAL_FRAME_MTU 124 /* 127 - pkt start (2 bytes) and stop (1 byte) */
#ifdef __ZEPHYR__
@@ -232,7 +245,6 @@
bs_list(char *buf, int len)
{
struct image_header hdr;
- uint8_t tmpbuf[64];
uint32_t slot, area_id;
const struct flash_area *fap;
uint8_t image_index;
@@ -245,7 +257,20 @@
zcbor_list_start_encode(cbor_state, 5);
image_index = 0;
IMAGES_ITER(image_index) {
+#ifdef MCUBOOT_SERIAL_IMG_GRP_IMAGE_STATE
+ int swap_status = boot_swap_type_multi(image_index);
+#endif
+
for (slot = 0; slot < 2; slot++) {
+ uint8_t tmpbuf[64];
+
+#ifdef MCUBOOT_SERIAL_IMG_GRP_IMAGE_STATE
+ bool active = false;
+ bool confirmed = false;
+ bool pending = false;
+ bool permanent = false;
+#endif
+
area_id = flash_area_id_from_multi_image_slot(image_index, slot);
if (flash_area_open(area_id, &fap)) {
continue;
@@ -258,10 +283,10 @@
flash_area_read(fap, 0, &hdr, sizeof(hdr));
}
- FIH_DECLARE(fih_rc, FIH_FAILURE);
-
if (hdr.ih_magic == IMAGE_MAGIC)
{
+ FIH_DECLARE(fih_rc, FIH_FAILURE);
+
BOOT_HOOK_CALL_FIH(boot_image_check_hook,
FIH_BOOT_HOOK_REGULAR,
fih_rc, image_index, slot);
@@ -281,6 +306,10 @@
FIH_CALL(bootutil_img_validate, fih_rc, NULL, 0, &hdr, fap, tmpbuf, sizeof(tmpbuf),
NULL, 0, NULL);
}
+
+ if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
+ continue;
+ }
}
#ifdef MCUBOOT_SERIAL_IMG_GRP_HASH
@@ -289,11 +318,6 @@
#endif
flash_area_close(fap);
-
- if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
- continue;
- }
-
zcbor_map_start_encode(cbor_state, 20);
#if (BOOT_IMAGE_NUMBER > 1)
@@ -301,6 +325,59 @@
zcbor_uint32_put(cbor_state, image_index);
#endif
+#ifdef MCUBOOT_SERIAL_IMG_GRP_IMAGE_STATE
+ if (swap_status == BOOT_SWAP_TYPE_NONE) {
+ if (slot == BOOT_PRIMARY_SLOT) {
+ confirmed = true;
+ active = true;
+ }
+ } else if (swap_status == BOOT_SWAP_TYPE_TEST) {
+ if (slot == BOOT_PRIMARY_SLOT) {
+ confirmed = true;
+ } else {
+ pending = true;
+ }
+ } else if (swap_status == BOOT_SWAP_TYPE_PERM) {
+ if (slot == BOOT_PRIMARY_SLOT) {
+ confirmed = true;
+ } else {
+ pending = true;
+ permanent = true;
+ }
+ } else if (swap_status == BOOT_SWAP_TYPE_REVERT) {
+ if (slot == BOOT_PRIMARY_SLOT) {
+ active = true;
+ } else {
+ confirmed = true;
+ }
+ }
+
+ if (!(hdr.ih_flags & IMAGE_F_NON_BOOTABLE)) {
+ zcbor_tstr_put_lit_cast(cbor_state, "bootable");
+ zcbor_bool_put(cbor_state, 1);
+ }
+
+ if (confirmed) {
+ zcbor_tstr_put_lit_cast(cbor_state, "confirmed");
+ zcbor_bool_put(cbor_state, true);
+ }
+
+ if (active) {
+ zcbor_tstr_put_lit_cast(cbor_state, "active");
+ zcbor_bool_put(cbor_state, true);
+ }
+
+ if (pending) {
+ zcbor_tstr_put_lit_cast(cbor_state, "pending");
+ zcbor_bool_put(cbor_state, true);
+ }
+
+ if (permanent) {
+ zcbor_tstr_put_lit_cast(cbor_state, "permanent");
+ zcbor_bool_put(cbor_state, true);
+ }
+#endif
+
zcbor_tstr_put_lit_cast(cbor_state, "slot");
zcbor_uint32_put(cbor_state, slot);
@@ -324,8 +401,162 @@
boot_serial_output();
}
-#ifdef MCUBOOT_ERASE_PROGRESSIVELY
+#ifdef MCUBOOT_SERIAL_IMG_GRP_IMAGE_STATE
+/*
+ * Set image state.
+ */
+static void
+bs_set(char *buf, int len)
+{
+ /*
+ * Expected data format.
+ * {
+ * "confirm":<true for confirm, false for test>
+ * "hash":<hash of image (OPTIONAL for single image only)>
+ * }
+ */
+ uint8_t image_index = 0;
+ size_t decoded = 0;
+ uint8_t hash[32];
+ bool confirm;
+ struct zcbor_string img_hash;
+ bool ok;
+ int rc;
+#ifdef MCUBOOT_SERIAL_IMG_GRP_HASH
+ bool found = false;
+#endif
+
+ zcbor_state_t zsd[4];
+ zcbor_new_state(zsd, sizeof(zsd) / sizeof(zcbor_state_t), (uint8_t *)buf, len, 1);
+
+ struct zcbor_map_decode_key_val image_set_state_decode[] = {
+ ZCBOR_MAP_DECODE_KEY_DECODER("confirm", zcbor_uint32_decode, &confirm),
+#ifdef MCUBOOT_SERIAL_IMG_GRP_HASH
+ ZCBOR_MAP_DECODE_KEY_DECODER("hash", zcbor_bstr_decode, &img_hash),
+#endif
+ };
+
+ ok = zcbor_map_decode_bulk(zsd, image_set_state_decode, ARRAY_SIZE(image_set_state_decode),
+ &decoded) == 0;
+
+ if (!ok || len != decoded) {
+ rc = MGMT_ERR_EINVAL;
+ goto out;
+ }
+
+#ifdef MCUBOOT_SERIAL_IMG_GRP_HASH
+ if ((img_hash.len != sizeof(hash) && img_hash.len != 0) ||
+ (img_hash.len == 0 && BOOT_IMAGE_NUMBER > 1)) {
+ /* Hash is required and was not provided or is invalid size */
+ rc = MGMT_ERR_EINVAL;
+ goto out;
+ }
+
+ if (img_hash.len != 0) {
+ for (image_index = 0; image_index < BOOT_IMAGE_NUMBER; ++image_index) {
+ struct image_header hdr;
+ uint32_t area_id;
+ const struct flash_area *fap;
+ uint8_t tmpbuf[64];
+
+ area_id = flash_area_id_from_multi_image_slot(image_index, 1);
+ if (flash_area_open(area_id, &fap)) {
+ BOOT_LOG_ERR("Failed to open flash area ID %d", area_id);
+ continue;
+ }
+
+ rc = BOOT_HOOK_CALL(boot_read_image_header_hook,
+ BOOT_HOOK_REGULAR, image_index, 1, &hdr);
+ if (rc == BOOT_HOOK_REGULAR)
+ {
+ flash_area_read(fap, 0, &hdr, sizeof(hdr));
+ }
+
+ if (hdr.ih_magic == IMAGE_MAGIC)
+ {
+ FIH_DECLARE(fih_rc, FIH_FAILURE);
+
+ BOOT_HOOK_CALL_FIH(boot_image_check_hook,
+ FIH_BOOT_HOOK_REGULAR,
+ fih_rc, image_index, 1);
+ if (FIH_EQ(fih_rc, FIH_BOOT_HOOK_REGULAR))
+ {
+ FIH_CALL(bootutil_img_validate, fih_rc, NULL, 0, &hdr, fap,
+ tmpbuf, sizeof(tmpbuf), NULL, 0, NULL);
+ }
+
+ if (FIH_NOT_EQ(fih_rc, FIH_SUCCESS)) {
+ continue;
+ }
+ }
+
+ /* Retrieve SHA256 hash of image for identification */
+ rc = boot_serial_get_hash(&hdr, fap, hash);
+ flash_area_close(fap);
+
+ if (rc == 0 && memcmp(hash, img_hash.value, sizeof(hash)) == 0) {
+ /* Hash matches, set this slot for test or confirmation */
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ /* Image was not found with specified hash */
+ BOOT_LOG_ERR("Did not find image with specified hash");
+ rc = MGMT_ERR_ENOENT;
+ goto out;
+ }
+ }
+#endif
+
+ rc = boot_set_pending_multi(image_index, confirm);
+
+out:
+ if (rc == 0) {
+ /* Success - return updated list of images */
+ bs_list(buf, len);
+ } else {
+ /* Error code, only return the error */
+ zcbor_map_start_encode(cbor_state, 10);
+ zcbor_tstr_put_lit_cast(cbor_state, "rc");
+ zcbor_int32_put(cbor_state, rc);
+ zcbor_map_end_encode(cbor_state, 10);
+
+ boot_serial_output();
+ }
+}
+#endif
+
+/*
+ * Send rc code only.
+ */
+static void
+bs_rc_rsp(int rc_code)
+{
+ zcbor_map_start_encode(cbor_state, 10);
+ zcbor_tstr_put_lit_cast(cbor_state, "rc");
+ zcbor_int32_put(cbor_state, rc_code);
+ zcbor_map_end_encode(cbor_state, 10);
+ boot_serial_output();
+}
+
+static void
+bs_list_set(uint8_t op, char *buf, int len)
+{
+ if (op == NMGR_OP_READ) {
+ bs_list(buf, len);
+ } else {
+#ifdef MCUBOOT_SERIAL_IMG_GRP_IMAGE_STATE
+ bs_set(buf, len);
+#else
+ bs_rc_rsp(MGMT_ERR_ENOTSUP);
+#endif
+ }
+}
+
+#ifdef MCUBOOT_ERASE_PROGRESSIVELY
/** Erases range of flash, aligned to sector size
*
* Function will erase all sectors withing [start, end] range; it does not check
@@ -630,20 +861,6 @@
#endif //#ifdef MCUBOOT_ENC_IMAGES
}
-/*
- * Send rc code only.
- */
-static void
-bs_rc_rsp(int rc_code)
-{
- zcbor_map_start_encode(cbor_state, 10);
- zcbor_tstr_put_lit_cast(cbor_state, "rc");
- zcbor_int32_put(cbor_state, rc_code);
- zcbor_map_end_encode(cbor_state, 10);
- boot_serial_output();
-}
-
-
#ifdef MCUBOOT_BOOT_MGMT_ECHO
static void
bs_echo(char *buf, int len)
@@ -758,7 +975,7 @@
if (hdr->nh_group == MGMT_GROUP_ID_IMAGE) {
switch (hdr->nh_id) {
case IMGMGR_NMGR_ID_STATE:
- bs_list(buf, len);
+ bs_list_set(hdr->nh_op, buf, len);
break;
case IMGMGR_NMGR_ID_UPLOAD:
bs_upload(buf, len);
diff --git a/boot/boot_serial/src/boot_serial_priv.h b/boot/boot_serial/src/boot_serial_priv.h
index 1714cfd..a9ff1ed 100644
--- a/boot/boot_serial/src/boot_serial_priv.h
+++ b/boot/boot_serial/src/boot_serial_priv.h
@@ -40,6 +40,7 @@
#define MGMT_ERR_EUNKNOWN 1
#define MGMT_ERR_ENOMEM 2
#define MGMT_ERR_EINVAL 3
+#define MGMT_ERR_ENOENT 5
#define MGMT_ERR_ENOTSUP 8
#define MGMT_ERR_EBUSY 10
diff --git a/boot/zephyr/Kconfig.serial_recovery b/boot/zephyr/Kconfig.serial_recovery
index c73573b..4d8f92f 100644
--- a/boot/zephyr/Kconfig.serial_recovery
+++ b/boot/zephyr/Kconfig.serial_recovery
@@ -207,4 +207,12 @@
If y, image list responses will include the image hash (adds ~100
bytes of flash).
+config BOOT_SERIAL_IMG_GRP_IMAGE_STATE
+ bool "Image state support"
+ depends on !SINGLE_APPLICATION_SLOT
+ select BOOT_SERIAL_IMG_GRP_HASH if UPDATEABLE_IMAGE_NUMBER > 1
+ help
+ 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.
+
endif # MCUBOOT_SERIAL
diff --git a/boot/zephyr/include/mcuboot_config/mcuboot_config.h b/boot/zephyr/include/mcuboot_config/mcuboot_config.h
index f4cd969..d69cf5d 100644
--- a/boot/zephyr/include/mcuboot_config/mcuboot_config.h
+++ b/boot/zephyr/include/mcuboot_config/mcuboot_config.h
@@ -213,6 +213,10 @@
#define MCUBOOT_SERIAL_IMG_GRP_HASH
#endif
+#ifdef CONFIG_BOOT_SERIAL_IMG_GRP_IMAGE_STATE
+#define MCUBOOT_SERIAL_IMG_GRP_IMAGE_STATE
+#endif
+
/*
* The option enables code, currently in boot_serial, that attempts
* to erase flash progressively, as update fragments are received,