Introduce a DT-based manifest

These are first steps towards a new manifest format. A new "device_tree"
build target is introduced to compile DTS files to DTB, and
`generate_initrd.py` now does not produce a "vms.txt" file. Instead
"initrd" targets are expected to provide a path to a DTS manifest in the
format:

    /dts-v1/;

    / {
      hypervisor {
        vm1 {
	  debug_name = "primary";
	};

	vm2 {
	  debug_name = "secondary1";
	  kernel_filename = "filename";
	  vcpu_count = <N>;
          mem_size = <M>;
	};

	...
      };
    };

The information provided in the manifest matches "vms.txt".

Bug: 117551352
Test: manifest_test.cc
Test: used by hftest
Change-Id: I6b70bd44d2b110c4f7a6b971018c834084b6d8c4
diff --git a/src/BUILD.gn b/src/BUILD.gn
index 8c5b6e5..f6f55dc 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -48,6 +48,7 @@
     "abort.c",
     "api.c",
     "cpu.c",
+    "manifest.c",
     "panic.c",
     "spci_architected_message.c",
     "vm.c",
@@ -151,6 +152,7 @@
     "api_test.cc",
     "fdt_handler_test.cc",
     "fdt_test.cc",
+    "manifest_test.cc",
     "mm_test.cc",
     "mpool_test.cc",
     "spci_test.cc",
diff --git a/src/fdt.c b/src/fdt.c
index 0ba1177..d9cd71d 100644
--- a/src/fdt.c
+++ b/src/fdt.c
@@ -16,8 +16,10 @@
 
 #include "hf/fdt.h"
 
+#include <stdalign.h>
 #include <stdint.h>
 
+#include "hf/check.h"
 #include "hf/dlog.h"
 #include "hf/std.h"
 
@@ -56,6 +58,8 @@
 #define FDT_VERSION 17
 #define FDT_MAGIC 0xd00dfeed
 
+#define FDT_TOKEN_ALIGNMENT sizeof(uint32_t)
+
 static void fdt_tokenizer_init(struct fdt_tokenizer *t, const char *strs,
 			       const char *begin, const char *end)
 {
@@ -66,7 +70,7 @@
 
 static void fdt_tokenizer_align(struct fdt_tokenizer *t)
 {
-	t->cur = (char *)align_up(t->cur, 4);
+	t->cur = (char *)align_up(t->cur, FDT_TOKEN_ALIGNMENT);
 }
 
 static bool fdt_tokenizer_uint32(struct fdt_tokenizer *t, uint32_t *res)
@@ -276,6 +280,43 @@
 	return false;
 }
 
+/**
+ * Helper method for parsing 32/64-bit uints from FDT data.
+ */
+bool fdt_parse_number(const char *data, uint32_t size, uint64_t *value)
+{
+	union {
+		volatile uint64_t v;
+		char a[8];
+	} t;
+
+	/* FDT values should be aligned to 32-bit boundary. */
+	CHECK(is_aligned(data, FDT_TOKEN_ALIGNMENT));
+
+	switch (size) {
+	case sizeof(uint32_t):
+		/*
+		 * Assert that `data` is already sufficiently aligned to
+		 * dereference as uint32_t. We cannot use static_assert()
+		 * because alignof() is not an expression under ISO C11.
+		 */
+		CHECK(alignof(uint32_t) <= FDT_TOKEN_ALIGNMENT);
+		*value = be32toh(*(uint32_t *)data);
+		return true;
+	case sizeof(uint64_t):
+		/*
+		 * ARMv8 requires `data` to be realigned to 64-bit boundary
+		 * to dereference as uint64_t. May not be needed on other
+		 * architectures.
+		 */
+		memcpy_s(t.a, sizeof(t.a), data, sizeof(uint64_t));
+		*value = be64toh(t.v);
+		return true;
+	default:
+		return false;
+	}
+}
+
 bool fdt_first_child(struct fdt_node *node, const char **child_name)
 {
 	struct fdt_tokenizer t;
@@ -333,7 +374,7 @@
 	return false;
 }
 
-void fdt_dump(struct fdt_header *hdr)
+void fdt_dump(const struct fdt_header *hdr)
 {
 	uint32_t token;
 	size_t depth = 0;
diff --git a/src/fdt_handler.c b/src/fdt_handler.c
index db8c0ef..900f766 100644
--- a/src/fdt_handler.c
+++ b/src/fdt_handler.c
@@ -17,6 +17,7 @@
 #include "hf/fdt_handler.h"
 
 #include "hf/boot_params.h"
+#include "hf/check.h"
 #include "hf/cpu.h"
 #include "hf/dlog.h"
 #include "hf/fdt.h"
@@ -24,24 +25,6 @@
 #include "hf/mm.h"
 #include "hf/std.h"
 
-static uint64_t convert_number(const char *data, uint32_t size)
-{
-	union {
-		volatile uint64_t v;
-		char a[8];
-	} t;
-
-	switch (size) {
-	case sizeof(uint32_t):
-		return be32toh(*(uint32_t *)data);
-	case sizeof(uint64_t):
-		memcpy_s(t.a, sizeof(t.a), data, sizeof(uint64_t));
-		return be64toh(t.v);
-	default:
-		return 0;
-	}
-}
-
 static bool fdt_read_number(const struct fdt_node *node, const char *name,
 			    uint64_t *value)
 {
@@ -55,7 +38,7 @@
 	switch (size) {
 	case sizeof(uint32_t):
 	case sizeof(uint64_t):
-		*value = convert_number(data, size);
+		CHECK(fdt_parse_number(data, size, value));
 		break;
 
 	default:
@@ -163,13 +146,18 @@
 
 		/* Get all entries for this CPU. */
 		while (size >= address_size) {
+			uint64_t value;
+
 			if (*cpu_count >= MAX_CPUS) {
 				dlog("Found more than %d CPUs\n", MAX_CPUS);
 				return;
 			}
 
-			cpu_ids[(*cpu_count)++] =
-				convert_number(data, address_size);
+			if (!fdt_parse_number(data, address_size, &value)) {
+				dlog("Could not parse CPU id\n");
+				return;
+			}
+			cpu_ids[(*cpu_count)++] = value;
 
 			size -= address_size;
 			data += address_size;
@@ -219,9 +207,12 @@
 
 		/* Traverse all memory ranges within this node. */
 		while (size >= entry_size) {
-			uintpaddr_t addr = convert_number(data, address_size);
-			size_t len =
-				convert_number(data + address_size, size_size);
+			uintpaddr_t addr;
+			size_t len;
+
+			CHECK(fdt_parse_number(data, address_size, &addr));
+			CHECK(fdt_parse_number(data + address_size, size_size,
+					       &len));
 
 			if (mem_range_index < MAX_MEM_RANGES) {
 				p->mem_ranges[mem_range_index].begin =
diff --git a/src/load.c b/src/load.c
index 602cfa8..341f276 100644
--- a/src/load.c
+++ b/src/load.c
@@ -22,6 +22,7 @@
 #include "hf/boot_params.h"
 #include "hf/dlog.h"
 #include "hf/layout.h"
+#include "hf/manifest.h"
 #include "hf/memiter.h"
 #include "hf/mm.h"
 #include "hf/plat/console.h"
@@ -40,8 +41,10 @@
  * so the data must be available without the cache.
  */
 static bool copy_to_unmapped(struct mm_stage1_locked stage1_locked, paddr_t to,
-			     const void *from, size_t size, struct mpool *ppool)
+			     struct memiter *from_it, struct mpool *ppool)
 {
+	const void *from = memiter_base(from_it);
+	size_t size = memiter_size(from_it);
 	paddr_t to_end = pa_add(to, size);
 	void *ptr;
 
@@ -120,8 +123,7 @@
 	}
 
 	dlog("Copying primary to %p\n", pa_addr(primary_begin));
-	if (!copy_to_unmapped(stage1_locked, primary_begin, it.next,
-			      it.limit - it.next, ppool)) {
+	if (!copy_to_unmapped(stage1_locked, primary_begin, &it, ppool)) {
 		dlog("Unable to relocate kernel for primary vm.\n");
 		return false;
 	}
@@ -254,12 +256,11 @@
 		    struct boot_params_update *update, struct mpool *ppool)
 {
 	struct vm *primary;
-	struct memiter it;
-	struct memiter name;
-	uint64_t mem;
-	uint64_t cpu;
+	struct manifest manifest;
+	struct memiter manifest_fdt;
 	struct mem_range mem_ranges_available[MAX_MEM_RANGES];
 	size_t i;
+	enum manifest_return_code manifest_ret;
 
 	static_assert(
 		sizeof(mem_ranges_available) == sizeof(params->mem_ranges),
@@ -272,61 +273,74 @@
 
 	primary = vm_find(HF_PRIMARY_VM_ID);
 
-	if (!find_file(cpio, "vms.txt", &it)) {
-		dlog("vms.txt is missing\n");
-		return true;
-	}
-
 	/* Round the last addresses down to the page size. */
 	for (i = 0; i < params->mem_ranges_count; ++i) {
 		mem_ranges_available[i].end = pa_init(align_down(
 			pa_addr(mem_ranges_available[i].end), PAGE_SIZE));
 	}
 
-	while (memiter_parse_uint(&it, &mem) && memiter_parse_uint(&it, &cpu) &&
-	       memiter_parse_str(&it, &name)) {
+	if (!find_file(cpio, "manifest.dtb", &manifest_fdt)) {
+		dlog("Could not find \"manifest.dtb\" in cpio.\n");
+		return false;
+	}
+
+	manifest_ret = manifest_init(&manifest, &manifest_fdt);
+	if (manifest_ret != MANIFEST_SUCCESS) {
+		dlog("Could not parse manifest: %s.\n",
+		     manifest_strerror(manifest_ret));
+		return false;
+	}
+
+	for (i = 0; i < manifest.num_vms; ++i) {
+		struct manifest_vm *manifest_vm = &manifest.vm[i];
+		spci_vm_id_t vm_id = HF_VM_ID_OFFSET + i;
+		struct vm *vm;
+		struct vcpu *vcpu;
 		struct memiter kernel;
+		uint64_t mem_size;
 		paddr_t secondary_mem_begin;
 		paddr_t secondary_mem_end;
 		ipaddr_t secondary_entry;
-		const char *p;
-		struct vm *vm;
-		struct vcpu *vcpu;
 
-		dlog("Loading ");
-		for (p = name.next; p != name.limit; ++p) {
-			dlog("%c", *p);
-		}
-		dlog("\n");
-
-		if (!memiter_find_file(cpio, &name, &kernel)) {
-			dlog("Unable to load kernel\n");
+		if (vm_id == HF_PRIMARY_VM_ID) {
 			continue;
 		}
 
-		/* Round up to page size. */
-		mem = (mem + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
+		dlog("Loading VM%d: ", (int)vm_id);
+		memiter_dlog_str(&manifest_vm->debug_name);
+		dlog(".\n");
 
-		if (mem < kernel.limit - kernel.next) {
+		if (!memiter_find_file(cpio,
+				       &manifest_vm->secondary.kernel_filename,
+				       &kernel)) {
+			dlog("Could not find kernel file \"");
+			memiter_dlog_str(
+				&manifest_vm->secondary.kernel_filename);
+			dlog("\".\n");
+			continue;
+		}
+
+		mem_size = align_up(manifest_vm->secondary.mem_size, PAGE_SIZE);
+		if (mem_size < memiter_size(&kernel)) {
 			dlog("Kernel is larger than available memory\n");
 			continue;
 		}
 
-		if (!carve_out_mem_range(
-			    mem_ranges_available, params->mem_ranges_count, mem,
-			    &secondary_mem_begin, &secondary_mem_end)) {
-			dlog("Not enough memory (%u bytes)\n", mem);
+		if (!carve_out_mem_range(mem_ranges_available,
+					 params->mem_ranges_count, mem_size,
+					 &secondary_mem_begin,
+					 &secondary_mem_end)) {
+			dlog("Not enough memory (%u bytes)\n", mem_size);
 			continue;
 		}
 
 		if (!copy_to_unmapped(stage1_locked, secondary_mem_begin,
-				      kernel.next, kernel.limit - kernel.next,
-				      ppool)) {
+				      &kernel, ppool)) {
 			dlog("Unable to copy kernel\n");
 			continue;
 		}
 
-		if (!vm_init(cpu, ppool, &vm)) {
+		if (!vm_init(manifest_vm->secondary.vcpu_count, ppool, &vm)) {
 			dlog("Unable to initialise VM\n");
 			continue;
 		}
@@ -347,7 +361,8 @@
 			return false;
 		}
 
-		dlog("Loaded with %u vcpus, entry at %#x\n", cpu,
+		dlog("Loaded with %u vcpus, entry at %#x\n",
+		     manifest_vm->secondary.vcpu_count,
 		     pa_addr(secondary_mem_begin));
 
 		vcpu = vm_get_vcpu(vm, 0);
diff --git a/src/manifest.c b/src/manifest.c
new file mode 100644
index 0000000..e5c62a1
--- /dev/null
+++ b/src/manifest.c
@@ -0,0 +1,218 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "hf/manifest.h"
+
+#include "hf/addr.h"
+#include "hf/check.h"
+#include "hf/fdt.h"
+#include "hf/static_assert.h"
+#include "hf/std.h"
+
+#define TRY(expr)                                            \
+	do {                                                 \
+		enum manifest_return_code ret_code = (expr); \
+		if (ret_code != MANIFEST_SUCCESS) {          \
+			return ret_code;                     \
+		}                                            \
+	} while (0)
+
+#define VM_NAME_BUF_SIZE (2 + 5 + 1) /* "vm" + number + null terminator */
+static_assert(MAX_VMS <= 99999, "Insufficient VM_NAME_BUF_SIZE");
+
+/**
+ * Generates a string with the two letters "vm" followed by an integer.
+ * Assumes `buf` is of size VM_NAME_BUF_SIZE.
+ */
+static const char *generate_vm_node_name(char *buf, spci_vm_id_t vm_id)
+{
+	static const char *digits = "0123456789";
+	char *ptr = buf + VM_NAME_BUF_SIZE;
+
+	*(--ptr) = '\0';
+	do {
+		*(--ptr) = digits[vm_id % 10];
+		vm_id /= 10;
+	} while (vm_id);
+	*(--ptr) = 'm';
+	*(--ptr) = 'v';
+
+	return ptr;
+}
+
+static enum manifest_return_code read_string(const struct fdt_node *node,
+					     const char *property,
+					     struct memiter *out)
+{
+	const char *data;
+	uint32_t size;
+
+	if (!fdt_read_property(node, property, &data, &size)) {
+		return MANIFEST_ERROR_PROPERTY_NOT_FOUND;
+	}
+
+	if (data[size - 1] != '\0') {
+		return MANIFEST_ERROR_MALFORMED_STRING;
+	}
+
+	memiter_init(out, data, size - 1);
+	return MANIFEST_SUCCESS;
+}
+
+static enum manifest_return_code read_uint64(const struct fdt_node *node,
+					     const char *property,
+					     uint64_t *out)
+{
+	const char *data;
+	uint32_t size;
+
+	if (!fdt_read_property(node, property, &data, &size)) {
+		return MANIFEST_ERROR_PROPERTY_NOT_FOUND;
+	}
+
+	if (!fdt_parse_number(data, size, out)) {
+		return MANIFEST_ERROR_MALFORMED_INTEGER;
+	}
+
+	return MANIFEST_SUCCESS;
+}
+
+static enum manifest_return_code read_uint16(const struct fdt_node *node,
+					     const char *property,
+					     uint16_t *out)
+{
+	uint64_t value;
+
+	TRY(read_uint64(node, property, &value));
+
+	if (value > UINT16_MAX) {
+		return MANIFEST_ERROR_INTEGER_OVERFLOW;
+	}
+
+	*out = (uint16_t)value;
+	return MANIFEST_SUCCESS;
+}
+
+static enum manifest_return_code parse_vm(struct fdt_node *node,
+					  struct manifest_vm *vm,
+					  spci_vm_id_t vm_id)
+{
+	TRY(read_string(node, "debug_name", &vm->debug_name));
+	if (vm_id != HF_PRIMARY_VM_ID) {
+		TRY(read_string(node, "kernel_filename",
+				&vm->secondary.kernel_filename));
+		TRY(read_uint64(node, "mem_size", &vm->secondary.mem_size));
+		TRY(read_uint16(node, "vcpu_count", &vm->secondary.vcpu_count));
+	}
+	return MANIFEST_SUCCESS;
+}
+
+/**
+ * Parse manifest from FDT.
+ */
+enum manifest_return_code manifest_init(struct manifest *manifest,
+					struct memiter *fdt)
+{
+	char vm_name_buf[VM_NAME_BUF_SIZE];
+	struct fdt_node hyp_node;
+	size_t i = 0;
+	bool found_primary_vm = false;
+
+	memset_s(manifest, sizeof(*manifest), 0, sizeof(*manifest));
+
+	/* Find hypervisor node. */
+	if (!fdt_root_node(&hyp_node,
+			   (const struct fdt_header *)memiter_base(fdt))) {
+		return MANIFEST_ERROR_CORRUPTED_FDT;
+	}
+	if (!fdt_find_child(&hyp_node, "")) {
+		return MANIFEST_ERROR_NO_ROOT_FDT_NODE;
+	}
+	if (!fdt_find_child(&hyp_node, "hypervisor")) {
+		return MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE;
+	}
+
+	/* Iterate over reserved VM IDs and check no such nodes exist. */
+	for (i = 0; i < HF_VM_ID_OFFSET; i++) {
+		spci_vm_id_t vm_id = (spci_vm_id_t)i;
+		struct fdt_node vm_node = hyp_node;
+		const char *vm_name = generate_vm_node_name(vm_name_buf, vm_id);
+
+		if (fdt_find_child(&vm_node, vm_name)) {
+			return MANIFEST_ERROR_RESERVED_VM_ID;
+		}
+	}
+
+	/* Iterate over VM nodes until we find one that does not exist. */
+	for (i = 0; i <= MAX_VMS; ++i) {
+		spci_vm_id_t vm_id = HF_VM_ID_OFFSET + i;
+		struct fdt_node vm_node = hyp_node;
+		const char *vm_name = generate_vm_node_name(vm_name_buf, vm_id);
+
+		if (!fdt_find_child(&vm_node, vm_name)) {
+			break;
+		}
+
+		if (i == MAX_VMS) {
+			return MANIFEST_ERROR_TOO_MANY_VMS;
+		}
+
+		if (vm_id == HF_PRIMARY_VM_ID) {
+			CHECK(found_primary_vm == false); /* sanity check */
+			found_primary_vm = true;
+		}
+
+		manifest->num_vms = i + 1;
+		TRY(parse_vm(&vm_node, &manifest->vm[i], vm_id));
+	}
+
+	if (!found_primary_vm) {
+		return MANIFEST_ERROR_NO_PRIMARY_VM;
+	}
+
+	return MANIFEST_SUCCESS;
+}
+
+const char *manifest_strerror(enum manifest_return_code ret_code)
+{
+	switch (ret_code) {
+	case MANIFEST_SUCCESS:
+		return "Success";
+	case MANIFEST_ERROR_CORRUPTED_FDT:
+		return "Manifest failed FDT validation";
+	case MANIFEST_ERROR_NO_ROOT_FDT_NODE:
+		return "Could not find root node of manifest";
+	case MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE:
+		return "Could not find \"hypervisor\" node in manifest";
+	case MANIFEST_ERROR_RESERVED_VM_ID:
+		return "Manifest defines a VM with a reserved ID";
+	case MANIFEST_ERROR_NO_PRIMARY_VM:
+		return "Manifest does not contain a primary VM entry";
+	case MANIFEST_ERROR_TOO_MANY_VMS:
+		return "Manifest specifies more VMs than Hafnium has "
+		       "statically allocated space for";
+	case MANIFEST_ERROR_PROPERTY_NOT_FOUND:
+		return "Property not found";
+	case MANIFEST_ERROR_MALFORMED_STRING:
+		return "Malformed string property";
+	case MANIFEST_ERROR_MALFORMED_INTEGER:
+		return "Malformed integer property";
+	case MANIFEST_ERROR_INTEGER_OVERFLOW:
+		return "Integer overflow";
+	}
+
+	panic("Unexpected manifest return code.");
+}
diff --git a/src/manifest_test.cc b/src/manifest_test.cc
new file mode 100644
index 0000000..3a86a7a
--- /dev/null
+++ b/src/manifest_test.cc
@@ -0,0 +1,305 @@
+/*
+ * Copyright 2019 The Hafnium Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gmock/gmock.h>
+
+extern "C" {
+#include "hf/manifest.h"
+}
+
+namespace
+{
+using ::testing::Eq;
+using ::testing::NotNull;
+
+/*
+ * DTB files compiled with:
+ *   $ dtc -I dts -O dtb --out-version 17 test.dts | xxd -i
+ */
+
+/*
+ * /dts-v1/;
+ *
+ * / {
+ * };
+ *
+ */
+constexpr uint8_t dtb_empty_root[] = {
+	0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x38,
+	0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11,
+	0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09};
+
+TEST(manifest, empty_root)
+{
+	struct manifest m;
+	struct memiter it;
+
+	memiter_init(&it, dtb_empty_root, sizeof(dtb_empty_root));
+	ASSERT_EQ(manifest_init(&m, &it),
+		  MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE);
+}
+
+/*
+ * /dts-v1/;
+ *
+ * / {
+ * 	hypervisor {
+ * 	};
+ * };
+ *
+ */
+constexpr uint8_t dtb_no_vms[] = {
+	0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x38,
+	0x00, 0x00, 0x00, 0x5c, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11,
+	0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x68, 0x79, 0x70, 0x65,
+	0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+	0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09};
+
+TEST(manifest, no_vms)
+{
+	struct manifest m;
+	struct memiter it;
+
+	memiter_init(&it, dtb_no_vms, sizeof(dtb_no_vms));
+	ASSERT_EQ(manifest_init(&m, &it), MANIFEST_ERROR_NO_PRIMARY_VM);
+}
+
+/*
+ * /dts-v1/;
+ *
+ * / {
+ * 	hypervisor {
+ * 		vm1 {
+ * 			debug_name = "primary_vm";
+ * 		};
+ * 		vm0 {
+ * 			debug_name = "reserved_vm";
+ * 			vcpu_count = <1>;
+ * 			mem_size = <4096>;
+ * 			kernel_filename = "kernel";
+ * 		};
+ * 	};
+ * };
+ *
+ */
+constexpr uint8_t dtb_reserved_vmid[] = {
+	0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, 0x38,
+	0x00, 0x00, 0x00, 0xd8, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11,
+	0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f,
+	0x00, 0x00, 0x00, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x68, 0x79, 0x70, 0x65,
+	0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x76, 0x6d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b,
+	0x00, 0x00, 0x00, 0x00, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f,
+	0x76, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
+	0x76, 0x6d, 0x30, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0c,
+	0x00, 0x00, 0x00, 0x00, 0x72, 0x65, 0x73, 0x65, 0x72, 0x76, 0x65, 0x64,
+	0x5f, 0x76, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
+	0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
+	0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x10, 0x00,
+	0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1f,
+	0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+	0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09,
+	0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x76,
+	0x63, 0x70, 0x75, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x00, 0x6d, 0x65,
+	0x6d, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x6b, 0x65, 0x72, 0x6e, 0x65,
+	0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x00};
+
+TEST(manifest, reserved_vmid)
+{
+	struct manifest m;
+	struct memiter it;
+
+	memiter_init(&it, dtb_reserved_vmid, sizeof(dtb_reserved_vmid));
+	ASSERT_EQ(manifest_init(&m, &it), MANIFEST_ERROR_RESERVED_VM_ID);
+}
+
+/*
+ * /dts-v1/;
+ *
+ * / {
+ * 	hypervisor {
+ * 		vm1 {
+ * 			debug_name = "";
+ * 		};
+ * 		vm2 {
+ * 			debug_name = "";
+ * 			vcpu_count = <65535>;
+ * 			mem_size = <0>;
+ * 			kernel_filename = "";
+ * 		};
+ * 	};
+ * };
+ *
+ */
+constexpr uint8_t dtb_last_valid_vcpu_count[] = {
+	0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x00, 0xf3, 0x00, 0x00, 0x00, 0x38,
+	0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11,
+	0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f,
+	0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x68, 0x79, 0x70, 0x65,
+	0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x76, 0x6d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+	0x00, 0x00, 0x00, 0x01, 0x76, 0x6d, 0x32, 0x00, 0x00, 0x00, 0x00, 0x03,
+	0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0b,
+	0x00, 0x00, 0xff, 0xff, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
+	0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+	0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02,
+	0x00, 0x00, 0x00, 0x09, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x6e, 0x61,
+	0x6d, 0x65, 0x00, 0x76, 0x63, 0x70, 0x75, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
+	0x74, 0x00, 0x6d, 0x65, 0x6d, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x6b,
+	0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61,
+	0x6d, 0x65, 0x00};
+
+/* Same as above, set "vcpu_count" to 65536. */
+constexpr uint8_t dtb_first_invalid_vcpu_count[] = {
+	0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x00, 0xf3, 0x00, 0x00, 0x00, 0x38,
+	0x00, 0x00, 0x00, 0xc4, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11,
+	0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f,
+	0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x68, 0x79, 0x70, 0x65,
+	0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x76, 0x6d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+	0x00, 0x00, 0x00, 0x01, 0x76, 0x6d, 0x32, 0x00, 0x00, 0x00, 0x00, 0x03,
+	0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0b,
+	0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
+	0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+	0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02,
+	0x00, 0x00, 0x00, 0x09, 0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x6e, 0x61,
+	0x6d, 0x65, 0x00, 0x76, 0x63, 0x70, 0x75, 0x5f, 0x63, 0x6f, 0x75, 0x6e,
+	0x74, 0x00, 0x6d, 0x65, 0x6d, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x6b,
+	0x65, 0x72, 0x6e, 0x65, 0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61,
+	0x6d, 0x65, 0x00};
+
+TEST(manifest, vcpu_count_limit)
+{
+	struct manifest m;
+	struct memiter it;
+
+	memiter_init(&it, dtb_last_valid_vcpu_count,
+		     sizeof(dtb_last_valid_vcpu_count));
+	ASSERT_EQ(manifest_init(&m, &it), MANIFEST_SUCCESS);
+	ASSERT_EQ(m.num_vms, 2);
+	ASSERT_EQ(m.vm[1].secondary.vcpu_count, UINT16_MAX);
+
+	memiter_init(&it, dtb_first_invalid_vcpu_count,
+		     sizeof(dtb_first_invalid_vcpu_count));
+	ASSERT_EQ(manifest_init(&m, &it), MANIFEST_ERROR_INTEGER_OVERFLOW);
+}
+
+/*
+ * /dts-v1/;
+ *
+ * / {
+ * 	hypervisor {
+ * 		vm1 {
+ * 			debug_name = "primary_vm";
+ * 		};
+ * 		vm3 {
+ * 			debug_name = "second_secondary_vm";
+ * 			vcpu_count = <43>;
+ * 			mem_size = <0x12345>;
+ * 			kernel_filename = "second_kernel";
+ * 		};
+ * 		vm2 {
+ * 			debug_name = "first_secondary_vm";
+ * 			vcpu_count = <42>;
+ * 			mem_size = <12345>;
+ * 			kernel_filename = "first_kernel";
+ * 		};
+ * 	};
+ * };
+ *
+ */
+constexpr uint8_t dtb_valid[] = {
+	0xd0, 0x0d, 0xfe, 0xed, 0x00, 0x00, 0x01, 0x7f, 0x00, 0x00, 0x00, 0x38,
+	0x00, 0x00, 0x01, 0x50, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x11,
+	0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2f,
+	0x00, 0x00, 0x01, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x68, 0x79, 0x70, 0x65,
+	0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+	0x76, 0x6d, 0x31, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b,
+	0x00, 0x00, 0x00, 0x00, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f,
+	0x76, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01,
+	0x76, 0x6d, 0x33, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x14,
+	0x00, 0x00, 0x00, 0x00, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x5f, 0x73,
+	0x65, 0x63, 0x6f, 0x6e, 0x64, 0x61, 0x72, 0x79, 0x5f, 0x76, 0x6d, 0x00,
+	0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0b,
+	0x00, 0x00, 0x00, 0x2b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04,
+	0x00, 0x00, 0x00, 0x16, 0x00, 0x01, 0x23, 0x45, 0x00, 0x00, 0x00, 0x03,
+	0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x1f, 0x73, 0x65, 0x63, 0x6f,
+	0x6e, 0x64, 0x5f, 0x6b, 0x65, 0x72, 0x6e, 0x65, 0x6c, 0x00, 0x00, 0x00,
+	0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x76, 0x6d, 0x32, 0x00,
+	0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00,
+	0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64,
+	0x61, 0x72, 0x79, 0x5f, 0x76, 0x6d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03,
+	0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x2a,
+	0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x16,
+	0x00, 0x00, 0x30, 0x39, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0d,
+	0x00, 0x00, 0x00, 0x1f, 0x66, 0x69, 0x72, 0x73, 0x74, 0x5f, 0x6b, 0x65,
+	0x72, 0x6e, 0x65, 0x6c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+	0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x09,
+	0x64, 0x65, 0x62, 0x75, 0x67, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x00, 0x76,
+	0x63, 0x70, 0x75, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x00, 0x6d, 0x65,
+	0x6d, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x00, 0x6b, 0x65, 0x72, 0x6e, 0x65,
+	0x6c, 0x5f, 0x66, 0x69, 0x6c, 0x65, 0x6e, 0x61, 0x6d, 0x65, 0x00};
+
+TEST(manifest, valid)
+{
+	struct manifest m;
+	struct manifest_vm *vm;
+	struct memiter it;
+
+	memiter_init(&it, dtb_valid, sizeof(dtb_valid));
+
+	ASSERT_EQ(manifest_init(&m, &it), MANIFEST_SUCCESS);
+	ASSERT_EQ(m.num_vms, 3);
+
+	vm = &m.vm[0];
+	ASSERT_TRUE(memiter_iseq(&vm->debug_name, "primary_vm"));
+
+	vm = &m.vm[1];
+	ASSERT_TRUE(memiter_iseq(&vm->debug_name, "first_secondary_vm"));
+	ASSERT_EQ(vm->secondary.vcpu_count, 42);
+	ASSERT_EQ(vm->secondary.mem_size, 12345);
+	ASSERT_TRUE(
+		memiter_iseq(&vm->secondary.kernel_filename, "first_kernel"));
+
+	vm = &m.vm[2];
+	ASSERT_TRUE(memiter_iseq(&vm->debug_name, "second_secondary_vm"));
+	ASSERT_EQ(vm->secondary.vcpu_count, 43);
+	ASSERT_EQ(vm->secondary.mem_size, 0x12345);
+	ASSERT_TRUE(
+		memiter_iseq(&vm->secondary.kernel_filename, "second_kernel"));
+}
+
+} /* namespace */
diff --git a/src/memiter.c b/src/memiter.c
index 5ff54ee..3dec9c6 100644
--- a/src/memiter.c
+++ b/src/memiter.c
@@ -16,6 +16,7 @@
 
 #include "hf/memiter.h"
 
+#include "hf/dlog.h"
 #include "hf/std.h"
 
 /**
@@ -93,6 +94,19 @@
 }
 
 /**
+ * Prints the contents of memory covered by the iterator to dlog. It does *not*
+ * assume that the string is null-terminated.
+ */
+void memiter_dlog_str(struct memiter *it)
+{
+	const char *p;
+
+	for (p = it->next; p < it->limit; ++p) {
+		dlog("%c", *p);
+	}
+}
+
+/**
  * Parses the next string that represents a 64-bit number.
  */
 bool memiter_parse_uint(struct memiter *it, uint64_t *value)
@@ -138,3 +152,16 @@
 
 	return true;
 }
+
+const void *memiter_base(struct memiter *it)
+{
+	return (const void *)it->next;
+}
+
+/**
+ * Returns the number of bytes in interval [it.next, it.limit).
+ */
+size_t memiter_size(struct memiter *it)
+{
+	return it->limit - it->next;
+}