Boot: Add dependency check to multi-image boot

This patch adds the capability to check image dependencies in case
of multi-image boot. The dependencies are described with a new type
of TLV in the manifest.

Change-Id: I10ab77ca0b196a1fc9fe51050744f716fd39a574
Signed-off-by: David Vincze <david.vincze@arm.com>
diff --git a/bl2/ext/mcuboot/bootutil/include/bootutil/image.h b/bl2/ext/mcuboot/bootutil/include/bootutil/image.h
index d9eb5320..6afcc48 100644
--- a/bl2/ext/mcuboot/bootutil/include/bootutil/image.h
+++ b/bl2/ext/mcuboot/bootutil/include/bootutil/image.h
@@ -68,6 +68,7 @@
 #define IMAGE_TLV_SHA256            0x10   /* SHA256 of image hdr and body */
 #define IMAGE_TLV_RSA2048_PSS       0x20   /* RSA2048 of hash output */
 #define IMAGE_TLV_RSA3072_PSS       0x23   /* RSA3072 of hash output */
+#define IMAGE_TLV_DEPENDENCY        0x40   /* Image depends on other image */
 #define IMAGE_TLV_SEC_CNT           0x50   /* security counter */
 
 #define IMAGE_VER_MAJOR_LENGTH      8
@@ -82,6 +83,16 @@
     uint32_t iv_build_num;
 };
 
+struct image_dependency {
+    uint8_t image_id;                       /* Image index (from 0) */
+    uint8_t _pad1;
+    uint16_t _pad2;
+    struct image_version image_min_version; /* Indicates at minimum which
+                                             * version of firmware must be
+                                             * available to satisfy compliance
+                                             */
+};
+
 /** Image header.  All fields are in little endian byte order. */
 struct image_header {
     uint32_t ih_magic;
diff --git a/bl2/ext/mcuboot/bootutil/src/bootutil_misc.c b/bl2/ext/mcuboot/bootutil/src/bootutil_misc.c
index 94fdc30..ecd2874 100644
--- a/bl2/ext/mcuboot/bootutil/src/bootutil_misc.c
+++ b/bl2/ext/mcuboot/bootutil/src/bootutil_misc.c
@@ -660,3 +660,38 @@
     flash_area_close(fap);
     return rc;
 }
+
+#if (BOOT_IMAGE_NUMBER > 1)
+/**
+ * Check if the version of the image is not older than required.
+ *
+ * @param req         Required minimal image version.
+ * @param ver         Version of the image to be checked.
+ *
+ * @return            0 if the version is sufficient, nonzero otherwise.
+ */
+int
+boot_is_version_sufficient(struct image_version *req,
+                           struct image_version *ver)
+{
+    if (ver->iv_major > req->iv_major) {
+        return 0;
+    }
+    if (ver->iv_major < req->iv_major) {
+        return BOOT_EBADVERSION;
+    }
+    /* The major version numbers are equal. */
+    if (ver->iv_minor > req->iv_minor) {
+        return 0;
+    }
+    if (ver->iv_minor < req->iv_minor) {
+        return BOOT_EBADVERSION;
+    }
+    /* The minor version numbers are equal. */
+    if (ver->iv_revision < req->iv_revision) {
+        return BOOT_EBADVERSION;
+    }
+
+    return 0;
+}
+#endif /* BOOT_IMAGE_NUMBER > 1 */
diff --git a/bl2/ext/mcuboot/bootutil/src/bootutil_priv.h b/bl2/ext/mcuboot/bootutil/src/bootutil_priv.h
index 2d3d625..6a6fad6 100644
--- a/bl2/ext/mcuboot/bootutil/src/bootutil_priv.h
+++ b/bl2/ext/mcuboot/bootutil/src/bootutil_priv.h
@@ -44,14 +44,15 @@
 
 struct flash_area;
 
-#define BOOT_EFLASH     1
-#define BOOT_EFILE      2
-#define BOOT_EBADIMAGE  3
-#define BOOT_EBADVECT   4
-#define BOOT_EBADSTATUS 5
-#define BOOT_ENOMEM     6
-#define BOOT_EBADARGS   7
-#define BOOT_EBADMAGIC  8
+#define BOOT_EFLASH      1
+#define BOOT_EFILE       2
+#define BOOT_EBADIMAGE   3
+#define BOOT_EBADVECT    4
+#define BOOT_EBADSTATUS  5
+#define BOOT_ENOMEM      6
+#define BOOT_EBADARGS    7
+#define BOOT_EBADMAGIC   8
+#define BOOT_EBADVERSION 9
 
 #define BOOT_TMPBUF_SZ  256
 
@@ -224,6 +225,10 @@
                          uint8_t image_num);
 int boot_write_swap_size(const struct flash_area *fap, uint32_t swap_size);
 int boot_read_swap_size(uint32_t *swap_size);
+#if (BOOT_IMAGE_NUMBER > 1)
+int boot_is_version_sufficient(struct image_version *req,
+                               struct image_version *ver);
+#endif
 
 /*
  * Accessors for the contents of struct boot_loader_state.
diff --git a/bl2/ext/mcuboot/bootutil/src/image_validate.c b/bl2/ext/mcuboot/bootutil/src/image_validate.c
index 20a89ed..561d1d0 100644
--- a/bl2/ext/mcuboot/bootutil/src/image_validate.c
+++ b/bl2/ext/mcuboot/bootutil/src/image_validate.c
@@ -67,9 +67,9 @@
     /* Hash is computed over image header and image itself. */
     size = hdr->ih_img_size + hdr->ih_hdr_size;
 
-    /* If a security counter TLV is present then the TLV info header and the
-     * security counter are also protected and must be included in the hash
-     * calculation.
+    /* If a security counter TLV and/or a dependency TLV(s) are present then the
+     * TLV info header, the security counter TLV and/or the dependency TLV(s)
+     * are also protected and must be included in the hash calculation.
      */
     if (hdr->ih_protect_tlv_size != 0) {
         size += hdr->ih_protect_tlv_size;
diff --git a/bl2/ext/mcuboot/bootutil/src/loader.c b/bl2/ext/mcuboot/bootutil/src/loader.c
index c86f291..be0582e 100644
--- a/bl2/ext/mcuboot/bootutil/src/loader.c
+++ b/bl2/ext/mcuboot/bootutil/src/loader.c
@@ -1481,6 +1481,198 @@
 }
 #endif /* !MCUBOOT_OVERWRITE_ONLY */
 
+#if (BOOT_IMAGE_NUMBER > 1)
+/**
+ * Check the image dependency whether it is satisfied and modify
+ * the swap type if necessary.
+ *
+ * @param dep               Image dependency which has to be verified.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+static int
+boot_verify_single_dependency(struct image_dependency *dep)
+{
+    struct image_version *dep_version;
+    size_t dep_slot;
+    int rc;
+
+    /* Determine the source of the image which is the subject of
+     * the dependency and get it's version. */
+    dep_slot = (boot_data.swap_type[dep->image_id] != BOOT_SWAP_TYPE_NONE) ?
+                BOOT_SECONDARY_SLOT : BOOT_PRIMARY_SLOT;
+    dep_version = &boot_data.imgs[dep->image_id][dep_slot].hdr.ih_ver;
+
+    rc = boot_is_version_sufficient(&dep->image_min_version, dep_version);
+    if (rc != 0) {
+        /* Dependency not satisfied.
+         * Modify the swap type to decrease the version number of the image
+         * (which will be located in the primary slot after the boot process),
+         * consequently the number of unsatisfied dependencies will be
+         * decreased or remain the same.
+         */
+        switch (BOOT_SWAP_TYPE(&boot_data)) {
+        case BOOT_SWAP_TYPE_TEST:
+        case BOOT_SWAP_TYPE_PERM:
+            BOOT_SWAP_TYPE(&boot_data) = BOOT_SWAP_TYPE_NONE;
+            break;
+        case BOOT_SWAP_TYPE_NONE:
+            BOOT_SWAP_TYPE(&boot_data) = BOOT_SWAP_TYPE_REVERT;
+            break;
+        default:
+            break;
+        }
+    }
+
+    return rc;
+}
+
+/**
+ * Read all dependency TLVs of an image from the flash and verify
+ * one after another to see if they are all satisfied.
+ *
+ * @param slot              Image slot number.
+ *
+ * @return                  0 on success; nonzero on failure.
+ */
+static int
+boot_verify_all_dependency(uint32_t slot)
+{
+    const struct flash_area *fap;
+    struct image_header *hdr;
+    struct image_tlv_info info;
+    struct image_tlv tlv;
+    struct image_dependency dep;
+    uint32_t off;
+    uint32_t end;
+    bool dep_tlvs_found = false;
+    int rc;
+
+    rc = flash_area_open(flash_area_id_from_image_slot(slot), &fap);
+    if (rc != 0) {
+        rc = BOOT_EFLASH;
+        goto done;
+    }
+
+    hdr = boot_img_hdr(&boot_data, slot);
+    /* The TLVs come after the image. */
+    off = hdr->ih_hdr_size + hdr->ih_img_size;
+
+    /* The TLV area always starts with an image_tlv_info structure. */
+    rc = flash_area_read(fap, off, &info, sizeof(info));
+    if (rc != 0) {
+        rc = BOOT_EFLASH;
+        goto done;
+    }
+
+    if (info.it_magic != IMAGE_TLV_INFO_MAGIC) {
+        rc = BOOT_EBADIMAGE;
+        goto done;
+    }
+    end = off + info.it_tlv_tot;
+    off += sizeof(info);
+
+    /* Traverse through all of the TLVs to find the dependency TLVs. */
+    for (; off < end; off += sizeof(tlv) + tlv.it_len) {
+        rc = flash_area_read(fap, off, &tlv, sizeof(tlv));
+        if (rc != 0) {
+             rc = BOOT_EFLASH;
+             goto done;
+         }
+
+        if (tlv.it_type == IMAGE_TLV_DEPENDENCY) {
+            if (!dep_tlvs_found) {
+                dep_tlvs_found = true;
+            }
+
+            if (tlv.it_len != sizeof(dep)) {
+                rc = BOOT_EBADIMAGE;
+                goto done;
+            }
+
+            rc = flash_area_read(fap, off + sizeof(tlv), &dep, tlv.it_len);
+            if (rc != 0) {
+                rc = BOOT_EFLASH;
+                goto done;
+            }
+
+            /* Verify dependency and modify the swap type if not satisfied. */
+            rc = boot_verify_single_dependency(&dep);
+            if (rc != 0) {
+                /* Dependency not satisfied. */
+                goto done;
+            }
+
+            /* Dependency satisfied, no action needed.
+             * Continue with the next TLV entry.
+             */
+        } else if (dep_tlvs_found) {
+            /* The dependency TLVs are contiguous in the TLV area. If a
+             * dependency had already been found and the last read TLV
+             * has a different type then there are no more dependency TLVs.
+             * The search can be finished.
+             */
+            break;
+        }
+    }
+
+done:
+    flash_area_close(fap);
+    return rc;
+}
+
+/**
+ * Verify whether the image dependencies in the TLV area are
+ * all satisfied and modify the swap type if necessary.
+ *
+ * @return                  0 if all dependencies are satisfied,
+ *                          nonzero otherwise.
+ */
+static int
+boot_verify_single_image_dependency(void)
+{
+    size_t slot;
+
+    /* Determine the source of the dependency TLVs. Those dependencies have to
+     * be checked which belong to the image that will be located in the primary
+     * slot after the firmware update process.
+     */
+    if (BOOT_SWAP_TYPE(&boot_data) != BOOT_SWAP_TYPE_NONE &&
+        BOOT_SWAP_TYPE(&boot_data) != BOOT_SWAP_TYPE_FAIL) {
+        slot = BOOT_SECONDARY_SLOT;
+    } else {
+        slot = BOOT_PRIMARY_SLOT;
+    }
+
+    return boot_verify_all_dependency(slot);
+}
+
+/**
+ * Iterate over all the images and verify whether the image dependencies in the
+ * TLV area are all satisfied and update the related swap type if necessary.
+ */
+static void
+boot_verify_all_image_dependency(void)
+{
+    current_image = 0;
+    int rc;
+
+    while (current_image < BOOT_IMAGE_NUMBER) {
+        rc = boot_verify_single_image_dependency();
+        if (rc == 0) {
+            /* All dependencies've been satisfied, continue with next image. */
+            current_image++;
+        } else if (rc == BOOT_EBADVERSION) {
+            /* Dependency check needs to be restarted. */
+            current_image = 0;
+        } else {
+            /* Other error happened, images are inconsistent */
+            return;
+        }
+    }
+}
+#endif /* (BOOT_IMAGE_NUMBER > 1) */
+
 /**
  * Performs a clean (not aborted) image update.
  *
@@ -1809,6 +2001,13 @@
         boot_prepare_image_for_update(&bs);
     }
 
+#if (BOOT_IMAGE_NUMBER > 1)
+    /* Iterate over all the images and verify whether the image dependencies
+     * are all satisfied and update swap type if necessary.
+     */
+    boot_verify_all_image_dependency();
+#endif
+
     /* Iterate over all the images. At this point there are no aborted swaps
      * and the swap types are determined for each image. By the end of the loop
      * all required update operations will have been finished.
diff --git a/bl2/ext/mcuboot/scripts/imgtool_lib/image.py b/bl2/ext/mcuboot/scripts/imgtool_lib/image.py
index b721fa3..f64ad4c 100644
--- a/bl2/ext/mcuboot/scripts/imgtool_lib/image.py
+++ b/bl2/ext/mcuboot/scripts/imgtool_lib/image.py
@@ -138,7 +138,7 @@
         if dependencies_num != 0:
             for i in range(dependencies_num):
                 payload = struct.pack(
-                                '<'+'I'+'BBHI',
+                                '<'+'B3x'+'BBHI',
                                 int(dependencies[DEP_IMAGES_KEY][i]),
                                 dependencies[DEP_VERSIONS_KEY][i].major,
                                 dependencies[DEP_VERSIONS_KEY][i].minor,