Move manifest to initrd

Supporting the manifest as part of FDT is problematic for testing
under the Android boot flow because it requires compiling and flashing
a different Hafnium image per test suite. Moving it back to the initrd
allows us to run different test suites against the same Hafnium image,
only by swapping the RAM disks.

Hafnium's one_time_init() routine will now expect to find the manifest
under "manifest.dtb".

Change-Id: Ifabffb50b06ffaba786046733d575adc9d43f4ad
diff --git a/build/image/image.gni b/build/image/image.gni
index 1ff8777..85cc3eb 100644
--- a/build/image/image.gni
+++ b/build/image/image.gni
@@ -187,30 +187,26 @@
 }
 
 template("device_tree") {
-  action_foreach(target_name) {
+  action(target_name) {
     forward_variables_from(invoker,
                            [
                              "testonly",
-                             "sources",
                              "deps",
                            ])
     script = "//build/image/dtc.py"
 
-    if (defined(invoker.output_pattern)) {
-      output_pattern = invoker.output_pattern
-    } else {
-      output_pattern = "${target_out_dir}/{{source_name_part}}.dtb"
-    }
-
+    sources = [
+      invoker.source,
+    ]
     outputs = [
-      output_pattern,
+      invoker.output,
     ]
     args = [
       "compile",
       "-i",
-      "{{source}}",
+      rebase_path(sources[0]),
       "-o",
-      rebase_path(output_pattern),
+      rebase_path(outputs[0]),
     ]
   }
 }
@@ -236,20 +232,14 @@
 
 # Build the initial RAM disk for the hypervisor.
 template("initrd") {
-  assert(defined(invoker.primary_name),
-         "initrd() must specify a \"primary_name\" value")
-  assert(defined(invoker.primary_vm),
-         "initrd() must specify a \"primary_vm\" value")
-
-  manifest_target = "${target_name}__manifest"
   base_out_dir = "${target_out_dir}/${target_name}"
+  manifest_target = "${target_name}__manifest"
+  manifest_file = "${base_out_dir}/manifest.dtb"
 
   # Generate manifest.dtbo
   device_tree(manifest_target) {
-    sources = [
-      invoker.manifest,
-    ]
-    output_pattern = "${base_out_dir}/{{source_name_part}}.dtbo"
+    source = invoker.manifest
+    output = manifest_file
   }
 
   action(target_name) {
@@ -259,32 +249,41 @@
     initrd_file = "${base_out_dir}/initrd.img"
     initrd_staging = "${base_out_dir}/initrd"
 
-    # Cannot get target outputs here as they are defined in a different file.
-    primary_vm_image = get_label_info(invoker.primary_vm, "target_out_dir") +
-                       "/" + get_label_info(invoker.primary_vm, "name") + ".bin"
-
     deps = [
       ":${manifest_target}",
-      invoker.primary_vm,
     ]
     args = [
-      "--file",
-      invoker.primary_name,
-      rebase_path(primary_vm_image),
       "--staging",
       rebase_path(initrd_staging),
       "--output",
       rebase_path(initrd_file),
+      "--file",
+      "manifest.dtb",
+      rebase_path(manifest_file),
     ]
 
-    if (defined(invoker.primary_initrd)) {
-      deps += [ invoker.primary_initrd ]
-      primary_initrd_outputs = get_target_outputs(invoker.primary_initrd)
+    if (defined(invoker.primary_vm)) {
+      # Cannot get target outputs here as they are defined in a different file.
+      primary_vm_image =
+          get_label_info(invoker.primary_vm, "target_out_dir") + "/" +
+          get_label_info(invoker.primary_vm, "name") + ".bin"
+
+      deps += [ invoker.primary_vm ]
       args += [
         "--file",
-        "initrd.img",
-        rebase_path(primary_initrd_outputs[0]),
+        invoker.primary_name,
+        rebase_path(primary_vm_image),
       ]
+
+      if (defined(invoker.primary_initrd)) {
+        deps += [ invoker.primary_initrd ]
+        primary_initrd_outputs = get_target_outputs(invoker.primary_initrd)
+        args += [
+          "--file",
+          "initrd.img",
+          rebase_path(primary_initrd_outputs[0]),
+        ]
+      }
     }
 
     # Add the info about the secondary VMs. The information about the VMs is
diff --git a/docs/GettingStarted.md b/docs/GettingStarted.md
index bc090c7..e997ecb 100644
--- a/docs/GettingStarted.md
+++ b/docs/GettingStarted.md
@@ -50,16 +50,14 @@
 ```
 
 Though it is admittedly not very useful because it doesn't have any virtual
-machines to run. Follow the [Hafnium RAM disk](HafniumRamDisk.md) instructions
-to create an initial RAM disk for Hafnium with Linux as the primary VM.
+machines to run.
 
 Next, you need to create a manifest which will describe the VM to Hafnium.
-Follow the [Manifest](Manifest.md) instructions and build a DTBO with:
+Follow the [Manifest](Manifest.md) instructions and build a DTB with:
 ```
 /dts-v1/;
-/plugin/;
 
-&{/} {
+/ {
 	hypervisor {
 		compatible = "hafnium,hafnium";
 		vm1 {
@@ -71,17 +69,14 @@
 };
 ```
 
-Dump the DTB used by QEMU:
-```shell
-qemu-system-aarch64 -M virt,gic_version=3 -cpu cortex-a57 -nographic -machine virtualization=true -kernel out/reference/qemu_aarch64_clang/hafnium.bin -initrd initrd.img -append "rdinit=/sbin/init" -machine dumpdtb=qemu.dtb
-```
-and follow instructions in [Manifest](Manifest.md) to overlay it with the manifest.
+Follow the [Hafnium RAM disk](HafniumRamDisk.md) instructions
+to create an initial RAM disk for Hafnium with Linux as the primary VM.
 
 The following command line will run Hafnium, with the RAM disk just created,
 which will then boot into the primary Linux VM:
 
 ```shell
-qemu-system-aarch64 -M virt,gic_version=3 -cpu cortex-a57 -nographic -machine virtualization=true -kernel out/reference/qemu_aarch64_clang/hafnium.bin -initrd initrd.img -append "rdinit=/sbin/init" -dtb qemu_with_manifest.dtb
+qemu-system-aarch64 -M virt,gic_version=3 -cpu cortex-a57 -nographic -machine virtualization=true -kernel out/reference/qemu_aarch64_clang/hafnium.bin -initrd initrd.img -append "rdinit=/sbin/init"
 ```
 
 ## Running tests
diff --git a/docs/HafniumRamDisk.md b/docs/HafniumRamDisk.md
index c09568d..6bd9c4f 100644
--- a/docs/HafniumRamDisk.md
+++ b/docs/HafniumRamDisk.md
@@ -3,9 +3,9 @@
 Hafnium expects to find the following files in the root directory of its RAM
 disk:
 
-*   `vmlinuz` -- the kernel of the primary VM.
-*   `initrd.img` -- the initial ramdisk of the primary VM.
-*   kernels for the secondary VMs, whose names are described in the manifest.
+*   `manifest.dtb` -- configuration file in DeviceTree format (required)
+*   kernels for the VMs, whose names are described in the manifest (optional)
+*   initrd of the primary VM, whose name is described in the manifest (optional)
 
 Follow the [preparing Linux](PreparingLinux.md) instructions to produce
 `vmlinuz` and `initrd.img` for a basic Linux primary VM.
diff --git a/docs/Manifest.md b/docs/Manifest.md
index 8552fb1..0d9d16e 100644
--- a/docs/Manifest.md
+++ b/docs/Manifest.md
@@ -8,9 +8,8 @@
 
 ```
 /dts-v1/;
-/plugin/;
 
-&{/} {
+/ {
 	hypervisor {
 		compatible = "hafnium,hafnium";
 
@@ -31,10 +30,6 @@
 };
 ```
 
-Note: `&{/}` is a syntactic sugar expanded by the DTC compiler. Make sure to
-use the DTC in `prebuilts/` as the version packaged with your OS may not support
-it yet.
-
 ## Example
 
 The following manifest defines a primary VM with two secondary VMs. The first
@@ -47,9 +42,8 @@
 
 ```
 /dts-v1/;
-/plugin/;
 
-&{/} {
+/ {
 	hypervisor {
 		compatible = "hafnium,hafnium";
 
@@ -84,15 +78,10 @@
 
 ## Compiling
 
-Hafnium expects the manifest as part of the board FDT, i.e. DeviceTree in binary
-format (DTB).
+Hafnium expects the manifest inside its [RAM disk](HafniumRamDisk.md),
+in DeviceTree's binary format (DTB).
 
-First, compile the manifest into a DTBO (binary overlay):
+Compile the manifest's source file into a DTB with:
 ```shell
-prebuilts/linux-x64/dtc/dtc -I dts -O dtb --out-version 17 -o manifest.dtbo <manifest_source_file>
-```
-
-Then overlay it with the DTB of your board:
-```shell
-prebuilts/linux-x64/dtc/fdtoverlay -i <board DTB> -o <output DTB> manifest.dtbo
+prebuilts/linux-x64/dtc/dtc -I dts -O dtb --out-version 17 -o manifest.dtb <manifest_source_file>
 ```
diff --git a/inc/hf/boot_flow.h b/inc/hf/boot_flow.h
index 39d7060..9bf82c1 100644
--- a/inc/hf/boot_flow.h
+++ b/inc/hf/boot_flow.h
@@ -21,8 +21,8 @@
 #include "hf/memiter.h"
 #include "hf/mm.h"
 
-bool boot_flow_init(const struct fdt_node *fdt_root, struct manifest *manifest,
-		    struct boot_params *boot_params);
+bool boot_flow_get_params(struct boot_params *p,
+			  const struct fdt_node *fdt_root);
 
 bool boot_flow_update(struct mm_stage1_locked stage1_locked,
 		      const struct manifest *manifest,
diff --git a/inc/hf/fdt.h b/inc/hf/fdt.h
index 8f72856..2dbb1d9 100644
--- a/inc/hf/fdt.h
+++ b/inc/hf/fdt.h
@@ -28,7 +28,7 @@
 };
 
 size_t fdt_header_size(void);
-uint32_t fdt_total_size(struct fdt_header *hdr);
+uint32_t fdt_total_size(const struct fdt_header *hdr);
 void fdt_dump(const struct fdt_header *hdr);
 bool fdt_root_node(struct fdt_node *node, const struct fdt_header *hdr);
 bool fdt_find_child(struct fdt_node *node, const char *child);
diff --git a/inc/hf/manifest.h b/inc/hf/manifest.h
index 0e628e5..2609973 100644
--- a/inc/hf/manifest.h
+++ b/inc/hf/manifest.h
@@ -16,7 +16,6 @@
 
 #pragma once
 
-#include "hf/fdt.h"
 #include "hf/memiter.h"
 #include "hf/spci.h"
 #include "hf/string.h"
@@ -54,6 +53,8 @@
 
 enum manifest_return_code {
 	MANIFEST_SUCCESS = 0,
+	MANIFEST_ERROR_FILE_SIZE,
+	MANIFEST_ERROR_NO_ROOT_NODE,
 	MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE,
 	MANIFEST_ERROR_NOT_COMPATIBLE,
 	MANIFEST_ERROR_RESERVED_VM_ID,
@@ -70,6 +71,6 @@
 };
 
 enum manifest_return_code manifest_init(struct manifest *manifest,
-					const struct fdt_node *fdt_root);
+					struct memiter *manifest_fdt);
 
 const char *manifest_strerror(enum manifest_return_code ret_code);
diff --git a/src/boot_flow/common.c b/src/boot_flow/common.c
index 834f746..c256eac 100644
--- a/src/boot_flow/common.c
+++ b/src/boot_flow/common.c
@@ -22,8 +22,8 @@
 /**
  * Extract the boot parameters from the FDT and the boot-flow driver.
  */
-static bool boot_params_init(struct boot_params *p,
-			     const struct fdt_node *fdt_root)
+bool boot_flow_get_params(struct boot_params *p,
+			  const struct fdt_node *fdt_root)
 {
 	p->mem_ranges_count = 0;
 	p->kernel_arg = plat_boot_flow_get_kernel_arg();
@@ -35,32 +35,6 @@
 }
 
 /**
- * Parses information from FDT needed to initialize Hafnium.
- * FDT is mapped at the beginning and unmapped before exiting the function.
- */
-bool boot_flow_init(const struct fdt_node *fdt_root, struct manifest *manifest,
-		    struct boot_params *boot_params)
-{
-	enum manifest_return_code manifest_ret;
-
-	/* Get the memory map from the FDT. */
-
-	manifest_ret = manifest_init(manifest, fdt_root);
-	if (manifest_ret != MANIFEST_SUCCESS) {
-		dlog("Could not parse manifest: %s.\n",
-		     manifest_strerror(manifest_ret));
-		return false;
-	}
-
-	if (!boot_params_init(boot_params, fdt_root)) {
-		dlog("Could not parse boot params.\n");
-		return false;
-	}
-
-	return true;
-}
-
-/**
  * Takes action on any updates that were generated.
  */
 bool boot_flow_update(struct mm_stage1_locked stage1_locked,
diff --git a/src/fdt.c b/src/fdt.c
index 752e9d8..b046662 100644
--- a/src/fdt.c
+++ b/src/fdt.c
@@ -463,7 +463,7 @@
 	return sizeof(struct fdt_header);
 }
 
-uint32_t fdt_total_size(struct fdt_header *hdr)
+uint32_t fdt_total_size(const struct fdt_header *hdr)
 {
 	return be32toh(hdr->totalsize);
 }
diff --git a/src/init.c b/src/init.c
index 897039f..34cabd9 100644
--- a/src/init.c
+++ b/src/init.c
@@ -71,12 +71,15 @@
  */
 void one_time_init(void)
 {
+	struct string manifest_fname = STRING_INIT("manifest.dtb");
 	struct fdt_header *fdt;
 	struct fdt_node fdt_root;
 	struct manifest manifest;
+	enum manifest_return_code manifest_ret;
 	struct boot_params params;
 	struct boot_params_update update;
 	struct memiter cpio;
+	struct memiter manifest_it;
 	void *initrd;
 	size_t i;
 	struct mm_stage1_locked mm_stage1_locked;
@@ -92,27 +95,17 @@
 	fdt = fdt_map(mm_stage1_locked, plat_boot_flow_get_fdt_addr(),
 		      &fdt_root, &ppool);
 	if (fdt == NULL) {
-		panic("Unable to map FDT.\n");
+		panic("Unable to map FDT.");
 	}
 
 	if (!fdt_find_child(&fdt_root, "")) {
-		panic("Unable to find FDT root node.\n");
+		panic("Unable to find FDT root node.");
 	}
 
-	if (!boot_flow_init(&fdt_root, &manifest, &params)) {
-		panic("Could not parse data from FDT.");
+	if (!boot_flow_get_params(&params, &fdt_root)) {
+		panic("Could not parse boot params.");
 	}
 
-	if (!plat_iommu_init(&fdt_root, mm_stage1_locked, &ppool)) {
-		panic("Could not initialize IOMMUs.");
-	}
-
-	if (!fdt_unmap(mm_stage1_locked, fdt, &ppool)) {
-		panic("Unable to unmap FDT.\n");
-	}
-
-	cpu_module_init(params.cpu_ids, params.cpu_count);
-
 	for (i = 0; i < params.mem_ranges_count; ++i) {
 		dlog("Memory range:  %#x - %#x\n",
 		     pa_addr(params.mem_ranges[i].begin),
@@ -132,6 +125,26 @@
 	memiter_init(&cpio, initrd,
 		     pa_difference(params.initrd_begin, params.initrd_end));
 
+	if (!cpio_get_file(&cpio, &manifest_fname, &manifest_it)) {
+		panic("Could not find manifest in initrd.");
+	}
+
+	manifest_ret = manifest_init(&manifest, &manifest_it);
+	if (manifest_ret != MANIFEST_SUCCESS) {
+		panic("Could not parse manifest: %s.",
+		      manifest_strerror(manifest_ret));
+	}
+
+	if (!plat_iommu_init(&fdt_root, mm_stage1_locked, &ppool)) {
+		panic("Could not initialize IOMMUs.");
+	}
+
+	if (!fdt_unmap(mm_stage1_locked, fdt, &ppool)) {
+		panic("Unable to unmap FDT.");
+	}
+
+	cpu_module_init(params.cpu_ids, params.cpu_count);
+
 	/* Load all VMs. */
 	update.reserved_ranges_count = 0;
 	if (!load_vms(mm_stage1_locked, &manifest, &cpio, &params, &update,
diff --git a/src/manifest.c b/src/manifest.c
index d574e55..93560c5 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -306,9 +306,10 @@
  * Parse manifest from FDT.
  */
 enum manifest_return_code manifest_init(struct manifest *manifest,
-					const struct fdt_node *fdt_root)
+					struct memiter *manifest_fdt)
 {
 	char vm_name_buf[VM_NAME_BUF_SIZE];
+	const struct fdt_header *fdt;
 	struct fdt_node hyp_node;
 	struct stringlist_iter compatible_list;
 	size_t i = 0;
@@ -316,8 +317,18 @@
 
 	memset_s(manifest, sizeof(*manifest), 0, sizeof(*manifest));
 
+	fdt = (const struct fdt_header *)memiter_base(manifest_fdt);
+	if (memiter_size(manifest_fdt) != fdt_total_size(fdt)) {
+		return MANIFEST_ERROR_FILE_SIZE;
+	}
+
 	/* Find hypervisor node. */
-	hyp_node = *fdt_root;
+	if (!fdt_root_node(&hyp_node, fdt)) {
+		return MANIFEST_ERROR_NO_ROOT_NODE;
+	}
+	if (!fdt_find_child(&hyp_node, "")) {
+		return MANIFEST_ERROR_NO_ROOT_NODE;
+	}
 	if (!fdt_find_child(&hyp_node, "hypervisor")) {
 		return MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE;
 	}
@@ -374,6 +385,10 @@
 	switch (ret_code) {
 	case MANIFEST_SUCCESS:
 		return "Success";
+	case MANIFEST_ERROR_FILE_SIZE:
+		return "Total size in header does not match file size";
+	case MANIFEST_ERROR_NO_ROOT_NODE:
+		return "Could not find root node in manifest";
 	case MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE:
 		return "Could not find \"hypervisor\" node in manifest";
 	case MANIFEST_ERROR_NOT_COMPATIBLE:
diff --git a/src/manifest_test.cc b/src/manifest_test.cc
index fdfd7f5..097c93e 100644
--- a/src/manifest_test.cc
+++ b/src/manifest_test.cc
@@ -260,31 +260,27 @@
 	std::stringstream dts_;
 };
 
-static bool get_fdt_root(const std::vector<char> &dtb,
-			 struct fdt_node *fdt_root)
+static enum manifest_return_code manifest_from_vec(struct manifest *m,
+						   const std::vector<char> &vec)
 {
-	const struct fdt_header *fdt_header;
+	struct memiter it;
 
-	fdt_header = reinterpret_cast<const struct fdt_header *>(dtb.data());
-	return fdt_root_node(fdt_root, fdt_header) &&
-	       fdt_find_child(fdt_root, "");
+	memiter_init(&it, vec.data(), vec.size());
+	return manifest_init(m, &it);
 }
 
 TEST(manifest, no_hypervisor_node)
 {
 	struct manifest m;
-	struct fdt_node fdt_root;
 	std::vector<char> dtb = ManifestDtBuilder().Build();
 
-	ASSERT_TRUE(get_fdt_root(dtb, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root),
+	ASSERT_EQ(manifest_from_vec(&m, dtb),
 		  MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE);
 }
 
 TEST(manifest, no_compatible_property)
 {
 	struct manifest m;
-	struct fdt_node fdt_root;
 
 	/* clang-format off */
 	std::vector<char> dtb = ManifestDtBuilder()
@@ -293,15 +289,13 @@
 		.Build();
 	/* clang-format on */
 
-	ASSERT_TRUE(get_fdt_root(dtb, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root),
+	ASSERT_EQ(manifest_from_vec(&m, dtb),
 		  MANIFEST_ERROR_PROPERTY_NOT_FOUND);
 }
 
 TEST(manifest, not_compatible)
 {
 	struct manifest m;
-	struct fdt_node fdt_root;
 
 	/* clang-format off */
 	std::vector<char> dtb = ManifestDtBuilder()
@@ -311,14 +305,12 @@
 		.Build();
 	/* clang-format on */
 
-	ASSERT_TRUE(get_fdt_root(dtb, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root), MANIFEST_ERROR_NOT_COMPATIBLE);
+	ASSERT_EQ(manifest_from_vec(&m, dtb), MANIFEST_ERROR_NOT_COMPATIBLE);
 }
 
 TEST(manifest, compatible_one_of_many)
 {
 	struct manifest m;
-	struct fdt_node fdt_root;
 
 	/* clang-format off */
 	std::vector<char> dtb = ManifestDtBuilder()
@@ -331,14 +323,12 @@
 		.Build();
 	/* clang-format on */
 
-	ASSERT_TRUE(get_fdt_root(dtb, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root), MANIFEST_SUCCESS);
+	ASSERT_EQ(manifest_from_vec(&m, dtb), MANIFEST_SUCCESS);
 }
 
 TEST(manifest, no_vm_nodes)
 {
 	struct manifest m;
-	struct fdt_node fdt_root;
 
 	/* clang-format off */
 	std::vector<char> dtb = ManifestDtBuilder()
@@ -348,8 +338,7 @@
 		.Build();
 	/* clang-format on */
 
-	ASSERT_TRUE(get_fdt_root(dtb, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root), MANIFEST_ERROR_NO_PRIMARY_VM);
+	ASSERT_EQ(manifest_from_vec(&m, dtb), MANIFEST_ERROR_NO_PRIMARY_VM);
 }
 
 static std::vector<char> gen_long_string_dtb(bool valid)
@@ -374,22 +363,17 @@
 TEST(manifest, long_string)
 {
 	struct manifest m;
-	struct fdt_node fdt_root;
-
 	std::vector<char> dtb_last_valid = gen_long_string_dtb(true);
 	std::vector<char> dtb_first_invalid = gen_long_string_dtb(false);
 
-	ASSERT_TRUE(get_fdt_root(dtb_last_valid, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root), MANIFEST_SUCCESS);
-
-	ASSERT_TRUE(get_fdt_root(dtb_first_invalid, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root), MANIFEST_ERROR_STRING_TOO_LONG);
+	ASSERT_EQ(manifest_from_vec(&m, dtb_last_valid), MANIFEST_SUCCESS);
+	ASSERT_EQ(manifest_from_vec(&m, dtb_first_invalid),
+		  MANIFEST_ERROR_STRING_TOO_LONG);
 }
 
 TEST(manifest, reserved_vm_id)
 {
 	struct manifest m;
-	struct fdt_node fdt_root;
 
 	/* clang-format off */
 	std::vector<char> dtb = ManifestDtBuilder()
@@ -408,8 +392,7 @@
 		.Build();
 	/* clang-format on */
 
-	ASSERT_TRUE(get_fdt_root(dtb, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root), MANIFEST_ERROR_RESERVED_VM_ID);
+	ASSERT_EQ(manifest_from_vec(&m, dtb), MANIFEST_ERROR_RESERVED_VM_ID);
 }
 
 static std::vector<char> gen_vcpu_count_limit_dtb(uint32_t vcpu_count)
@@ -435,25 +418,21 @@
 TEST(manifest, vcpu_count_limit)
 {
 	struct manifest m;
-	struct fdt_node fdt_root;
 	std::vector<char> dtb_last_valid = gen_vcpu_count_limit_dtb(UINT16_MAX);
 	std::vector<char> dtb_first_invalid =
 		gen_vcpu_count_limit_dtb(UINT16_MAX + 1);
 
-	ASSERT_TRUE(get_fdt_root(dtb_last_valid, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root), MANIFEST_SUCCESS);
+	ASSERT_EQ(manifest_from_vec(&m, dtb_last_valid), MANIFEST_SUCCESS);
 	ASSERT_EQ(m.vm_count, 2);
 	ASSERT_EQ(m.vm[1].secondary.vcpu_count, UINT16_MAX);
 
-	ASSERT_TRUE(get_fdt_root(dtb_first_invalid, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root),
+	ASSERT_EQ(manifest_from_vec(&m, dtb_first_invalid),
 		  MANIFEST_ERROR_INTEGER_OVERFLOW);
 }
 
 TEST(manifest, no_ramdisk_primary)
 {
 	struct manifest m;
-	struct fdt_node fdt_root;
 
 	/* clang-format off */
 	std::vector<char> dtb = ManifestDtBuilder()
@@ -466,8 +445,7 @@
 		.Build();
 	/* clang-format on */
 
-	ASSERT_TRUE(get_fdt_root(dtb, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root), MANIFEST_SUCCESS);
+	ASSERT_EQ(manifest_from_vec(&m, dtb), MANIFEST_SUCCESS);
 	ASSERT_EQ(m.vm_count, 1);
 	ASSERT_STREQ(string_data(&m.vm[0].debug_name), "primary_vm");
 	ASSERT_STREQ(string_data(&m.vm[0].primary.ramdisk_filename), "");
@@ -492,27 +470,19 @@
 TEST(manifest, malformed_booleans)
 {
 	struct manifest m;
-	struct fdt_node fdt_root;
 
 	std::vector<char> dtb_false = gen_malformed_boolean_dtb("\"false\"");
 	std::vector<char> dtb_true = gen_malformed_boolean_dtb("\"true\"");
 	std::vector<char> dtb_0 = gen_malformed_boolean_dtb("\"<0>\"");
 	std::vector<char> dtb_1 = gen_malformed_boolean_dtb("\"<1>\"");
 
-	ASSERT_TRUE(get_fdt_root(dtb_false, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root),
+	ASSERT_EQ(manifest_from_vec(&m, dtb_false),
 		  MANIFEST_ERROR_MALFORMED_BOOLEAN);
-
-	ASSERT_TRUE(get_fdt_root(dtb_true, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root),
+	ASSERT_EQ(manifest_from_vec(&m, dtb_true),
 		  MANIFEST_ERROR_MALFORMED_BOOLEAN);
-
-	ASSERT_TRUE(get_fdt_root(dtb_0, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root),
+	ASSERT_EQ(manifest_from_vec(&m, dtb_0),
 		  MANIFEST_ERROR_MALFORMED_BOOLEAN);
-
-	ASSERT_TRUE(get_fdt_root(dtb_1, &fdt_root));
-	ASSERT_EQ(manifest_init(&m, &fdt_root),
+	ASSERT_EQ(manifest_from_vec(&m, dtb_1),
 		  MANIFEST_ERROR_MALFORMED_BOOLEAN);
 }
 
@@ -520,7 +490,6 @@
 {
 	struct manifest m;
 	struct manifest_vm *vm;
-	struct fdt_node fdt_root;
 
 	/* clang-format off */
 	std::vector<char> dtb = ManifestDtBuilder()
@@ -549,9 +518,7 @@
 		.Build();
 	/* clang-format on */
 
-	ASSERT_TRUE(get_fdt_root(dtb, &fdt_root));
-
-	ASSERT_EQ(manifest_init(&m, &fdt_root), MANIFEST_SUCCESS);
+	ASSERT_EQ(manifest_from_vec(&m, dtb), MANIFEST_SUCCESS);
 	ASSERT_EQ(m.vm_count, 3);
 
 	vm = &m.vm[0];
diff --git a/test/hftest/hftest.py b/test/hftest/hftest.py
index 17a3b25..cd60585 100755
--- a/test/hftest/hftest.py
+++ b/test/hftest/hftest.py
@@ -115,7 +115,6 @@
         "artifacts",
         "kernel",
         "initrd",
-        "manifest",
         "vm_args",
         "cpu"
     ])
@@ -185,14 +184,6 @@
             log_content + "\r\n\r\n")
         return log_content
 
-    def overlay_dtb(self, run_state, base_dtb, overlay_dtb, out_dtb):
-        """Overlay `overlay_dtb` over `base_dtb` into `out_dtb`."""
-        dtc_args = [
-            DTC_SCRIPT, "overlay",
-            out_dtb, base_dtb, overlay_dtb,
-        ]
-        self.exec_logged(run_state, dtc_args)
-
 
 class QemuDriver(Driver):
     """Driver which runs tests in QEMU."""
@@ -200,8 +191,7 @@
     def __init__(self, args):
         Driver.__init__(self, args)
 
-    def gen_exec_args(self, test_args, is_long_running, dtb_path=None,
-            dumpdtb_path=None):
+    def gen_exec_args(self, test_args, is_long_running):
         """Generate command line arguments for QEMU."""
         time_limit = "120s" if is_long_running else "10s"
         # If no CPU configuration is selected, then test against the maximum
@@ -216,12 +206,6 @@
             "-d", "unimp", "-kernel", self.args.kernel,
         ]
 
-        if dtb_path:
-            exec_args += ["-dtb", dtb_path]
-
-        if dumpdtb_path:
-            exec_args += ["-machine", "dumpdtb=" + dumpdtb_path]
-
         if self.args.initrd:
             exec_args += ["-initrd", self.args.initrd]
 
@@ -231,29 +215,13 @@
 
         return exec_args
 
-    def dump_dtb(self, run_state, test_args, path):
-        dumpdtb_args = self.gen_exec_args(test_args, False, dumpdtb_path=path)
-        self.exec_logged(run_state, dumpdtb_args)
-
     def run(self, run_name, test_args, is_long_running):
         """Run test given by `test_args` in QEMU."""
         run_state = self.start_run(run_name)
 
         try:
-            dtb_path = None
-
-            # If manifest DTBO specified, dump DTB from QEMU and overlay them.
-            if self.args.manifest:
-                base_dtb_path = self.args.artifacts.create_file(
-                    run_name, ".base.dtb")
-                dtb_path = self.args.artifacts.create_file(run_name, ".dtb")
-                self.dump_dtb(run_state, test_args, base_dtb_path)
-                self.overlay_dtb(
-                    run_state, base_dtb_path, self.args.manifest, dtb_path)
-
             # Execute test in QEMU..
-            exec_args = self.gen_exec_args(test_args, is_long_running,
-                    dtb_path=dtb_path)
+            exec_args = self.gen_exec_args(test_args, is_long_running)
             self.exec_logged(run_state, exec_args)
         except DriverRunException:
             pass
@@ -335,8 +303,7 @@
     def run(self, run_name, test_args, is_long_running):
         run_state = self.start_run(run_name)
 
-        base_dts_path = self.args.artifacts.create_file(run_name, ".base.dts")
-        base_dtb_path = self.args.artifacts.create_file(run_name, ".base.dtb")
+        dts_path = self.args.artifacts.create_file(run_name, ".dts")
         dtb_path = self.args.artifacts.create_file(run_name, ".dtb")
         uart0_log_path = self.args.artifacts.create_file(run_name, ".uart0.log")
         uart1_log_path = self.args.artifacts.create_file(run_name, ".uart1.log")
@@ -349,21 +316,14 @@
 
         try:
             # Create a DT to pass to FVP.
-            self.gen_dts(base_dts_path, test_args, initrd_start, initrd_end)
+            self.gen_dts(dts_path, test_args, initrd_start, initrd_end)
 
             # Compile DTS to DTB.
             dtc_args = [
-                DTC_SCRIPT, "compile", "-i", base_dts_path, "-o", base_dtb_path,
+                DTC_SCRIPT, "compile", "-i", dts_path, "-o", dtb_path,
             ]
             self.exec_logged(run_state, dtc_args)
 
-            # If manifest DTBO specified, overlay it.
-            if self.args.manifest:
-                self.overlay_dtb(
-                    run_state, base_dtb_path, self.args.manifest, dtb_path)
-            else:
-                dtb_path = base_dtb_path
-
             # Run FVP.
             fvp_args = self.gen_fvp_args(
                 is_long_running, initrd_start, uart0_log_path, uart1_log_path,
@@ -552,12 +512,10 @@
     # Resolve some paths.
     image = os.path.join(args.out, args.image + ".bin")
     initrd = None
-    manifest = None
     image_name = args.image
     if args.initrd:
         initrd_dir = os.path.join(args.out_initrd, "obj", args.initrd)
         initrd = os.path.join(initrd_dir, "initrd.img")
-        manifest = os.path.join(initrd_dir, "manifest.dtbo")
         image_name += "_" + args.initrd
     vm_args = args.vm_args or ""
 
@@ -565,7 +523,7 @@
     artifacts = ArtifactsManager(os.path.join(args.log, image_name))
 
     # Create a driver for the platform we want to test on.
-    driver_args = DriverArgs(artifacts, image, initrd, manifest, vm_args,
+    driver_args = DriverArgs(artifacts, image, initrd, vm_args,
         args.cpu)
     if args.fvp:
         driver = FvpDriver(driver_args)
diff --git a/test/linux/manifest.dts b/test/linux/manifest.dts
index 29e2075..d6a204d 100644
--- a/test/linux/manifest.dts
+++ b/test/linux/manifest.dts
@@ -15,9 +15,8 @@
  */
 
 /dts-v1/;
-/plugin/;
 
-&{/} {
+/ {
 	hypervisor {
 		compatible = "hafnium,hafnium";
 		vm1 {
diff --git a/test/vmapi/arch/aarch64/gicv3/manifest.dts b/test/vmapi/arch/aarch64/gicv3/manifest.dts
index ad500e3..e25ed69 100644
--- a/test/vmapi/arch/aarch64/gicv3/manifest.dts
+++ b/test/vmapi/arch/aarch64/gicv3/manifest.dts
@@ -15,9 +15,8 @@
  */
 
 /dts-v1/;
-/plugin/;
 
-&{/} {
+/ {
 	hypervisor {
 		compatible = "hafnium,hafnium";
 		vm1 {
diff --git a/test/vmapi/arch/aarch64/manifest.dts b/test/vmapi/arch/aarch64/manifest.dts
index 2d56429..ff84844 100644
--- a/test/vmapi/arch/aarch64/manifest.dts
+++ b/test/vmapi/arch/aarch64/manifest.dts
@@ -15,9 +15,8 @@
  */
 
 /dts-v1/;
-/plugin/;
 
-&{/} {
+/ {
 	hypervisor {
 		compatible = "hafnium,hafnium";
 		vm1 {
diff --git a/test/vmapi/primary_only/manifest.dts b/test/vmapi/primary_only/manifest.dts
index f83e057..d0fcdfe 100644
--- a/test/vmapi/primary_only/manifest.dts
+++ b/test/vmapi/primary_only/manifest.dts
@@ -15,9 +15,8 @@
  */
 
 /dts-v1/;
-/plugin/;
 
-&{/} {
+/ {
 	hypervisor {
 		compatible = "hafnium,hafnium";
 		vm1 {
diff --git a/test/vmapi/primary_with_secondaries/manifest.dts b/test/vmapi/primary_with_secondaries/manifest.dts
index 3d9a73a..0f9ba1c 100644
--- a/test/vmapi/primary_with_secondaries/manifest.dts
+++ b/test/vmapi/primary_with_secondaries/manifest.dts
@@ -15,9 +15,8 @@
  */
 
 /dts-v1/;
-/plugin/;
 
-&{/} {
+/ {
 	hypervisor {
 		compatible = "hafnium,hafnium";
 		vm1 {