Migrate to libfdt

Replace our custom FDT parser implementation with libfdt while retaining
the original API as a thin wrapper around libfdt. This minimizes the
changes to the rest of our code base and hides differences in coding
styles.

As a byproduct, this fixes an issue with unaligned memory accesses while
parsing as libfdt handles these correctly.

Bug: 150587116
Change-Id: I8d305d7094b1be04608048009d73d7c448a578a0
diff --git a/inc/hf/boot_flow.h b/inc/hf/boot_flow.h
index 9bf82c1..0e6a597 100644
--- a/inc/hf/boot_flow.h
+++ b/inc/hf/boot_flow.h
@@ -21,8 +21,7 @@
 #include "hf/memiter.h"
 #include "hf/mm.h"
 
-bool boot_flow_get_params(struct boot_params *p,
-			  const struct fdt_node *fdt_root);
+bool boot_flow_get_params(struct boot_params *p, const struct fdt *fdt);
 
 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 2dbb1d9..08f1741 100644
--- a/inc/hf/fdt.h
+++ b/inc/hf/fdt.h
@@ -16,27 +16,49 @@
 
 #pragma once
 
-#include <stdbool.h>
-#include <stddef.h>
-#include <stdint.h>
+#include "hf/memiter.h"
+#include "hf/string.h"
 
-struct fdt_node {
-	const struct fdt_header *hdr;
-	const char *begin;
-	const char *end;
-	const char *strs;
+/**
+ * Wrapper around a pointer to a Flattened Device Tree (FDT) structure located
+ * somewhere in mapped main memory. Sanity checks are performed on initilization
+ * to ensure it is pointing to a valid FDT and most libfdt API calls check for
+ * the presence of the FDT magic.
+ */
+struct fdt {
+	struct memiter buf;
 };
 
-size_t fdt_header_size(void);
-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);
-bool fdt_first_child(struct fdt_node *node, const char **child_name);
-bool fdt_next_sibling(struct fdt_node *node, const char **sibling_name);
-bool fdt_read_property(const struct fdt_node *node, const char *name,
-		       const char **buf, uint32_t *size);
-bool fdt_parse_number(const char *data, uint32_t size, uint64_t *value);
+/**
+ * Wrapper around a pointer to a valid Device Tree node inside a FDT structure.
+ */
+struct fdt_node {
+	struct fdt fdt;
+	int offset;
+};
 
-void fdt_add_mem_reservation(struct fdt_header *hdr, uint64_t addr,
-			     uint64_t len);
+#define FDT_V17_HEADER_SIZE (10 * sizeof(uint32_t))
+
+bool fdt_size_from_header(const void *ptr, size_t *val);
+
+bool fdt_init_from_ptr(struct fdt *fdt, const void *ptr, size_t len);
+bool fdt_init_from_memiter(struct fdt *fdt, const struct memiter *it);
+void fdt_fini(struct fdt *fdt);
+
+const void *fdt_base(const struct fdt *fdt);
+size_t fdt_size(const struct fdt *fdt);
+
+bool fdt_find_node(const struct fdt *fdt, const char *path,
+		   struct fdt_node *node);
+bool fdt_address_size(const struct fdt_node *node, size_t *addr_size);
+bool fdt_size_size(const struct fdt_node *node, size_t *size);
+
+bool fdt_first_child(struct fdt_node *node);
+bool fdt_next_sibling(struct fdt_node *node);
+bool fdt_find_child(struct fdt_node *node, const struct string *name);
+
+bool fdt_read_property(const struct fdt_node *node, const char *name,
+		       struct memiter *data);
+bool fdt_read_number(const struct fdt_node *node, const char *name,
+		     uint64_t *val);
+bool fdt_parse_number(struct memiter *data, size_t size, uint64_t *val);
diff --git a/inc/hf/fdt_handler.h b/inc/hf/fdt_handler.h
index 4e18afc..8cf3a4a 100644
--- a/inc/hf/fdt_handler.h
+++ b/inc/hf/fdt_handler.h
@@ -22,19 +22,15 @@
 #include "hf/mpool.h"
 #include "hf/string.h"
 
-struct fdt_header *fdt_map(struct mm_stage1_locked stage1_locked,
-			   paddr_t fdt_addr, struct fdt_node *n,
-			   struct mpool *ppool);
-bool fdt_unmap(struct mm_stage1_locked stage1_locked, struct fdt_header *fdt,
+#define FDT_PROP_INITRD_START "linux,initrd-start"
+#define FDT_PROP_INITRD_END "linux,initrd-end"
+
+bool fdt_map(struct fdt *fdt, struct mm_stage1_locked stage1_locked,
+	     paddr_t fdt_addr, struct mpool *ppool);
+bool fdt_unmap(struct fdt *fdt, struct mm_stage1_locked stage1_locked,
 	       struct mpool *ppool);
-bool fdt_find_cpus(const struct fdt_node *root, cpu_id_t *cpu_ids,
-		   size_t *cpu_count);
-bool fdt_find_memory_ranges(const struct fdt_node *root,
-			    struct string *device_type,
+bool fdt_find_cpus(const struct fdt *fdt, cpu_id_t *cpu_ids, size_t *cpu_count);
+bool fdt_find_memory_ranges(const struct fdt *fdt, struct string *device_type,
 			    struct mem_range *mem_ranges,
 			    size_t *mem_ranges_count, size_t mem_range_limit);
-bool fdt_find_initrd(const struct fdt_node *root, paddr_t *begin, paddr_t *end);
-
-/** Apply an update to the FDT. */
-bool fdt_patch(struct mm_stage1_locked stage1_locked, paddr_t fdt_addr,
-	       struct boot_params_update *p, struct mpool *ppool);
+bool fdt_find_initrd(const struct fdt *fdt, paddr_t *begin, paddr_t *end);
diff --git a/inc/hf/fdt_patch.h b/inc/hf/fdt_patch.h
new file mode 100644
index 0000000..5c639fe
--- /dev/null
+++ b/inc/hf/fdt_patch.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 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.
+ */
+
+#pragma once
+
+#include "hf/boot_params.h"
+#include "hf/mm.h"
+#include "hf/mpool.h"
+
+/** Apply an update to the FDT. */
+bool fdt_patch(struct mm_stage1_locked stage1_locked, paddr_t fdt_addr,
+	       struct boot_params_update *p, struct mpool *ppool);
diff --git a/inc/hf/memiter.h b/inc/hf/memiter.h
index e3f0db8..d0a830c 100644
--- a/inc/hf/memiter.h
+++ b/inc/hf/memiter.h
@@ -30,6 +30,8 @@
 bool memiter_parse_str(struct memiter *it, struct memiter *str);
 bool memiter_iseq(const struct memiter *it, const char *str);
 bool memiter_advance(struct memiter *it, size_t v);
+bool memiter_restrict(struct memiter *it, size_t v);
+bool memiter_consume(struct memiter *it, size_t v, struct memiter *newit);
 
 const void *memiter_base(const struct memiter *it);
 size_t memiter_size(const struct memiter *it);
diff --git a/inc/hf/plat/boot_flow.h b/inc/hf/plat/boot_flow.h
index b5d8145..13409f7 100644
--- a/inc/hf/plat/boot_flow.h
+++ b/inc/hf/plat/boot_flow.h
@@ -25,8 +25,8 @@
 
 paddr_t plat_boot_flow_get_fdt_addr(void);
 uintreg_t plat_boot_flow_get_kernel_arg(void);
-bool plat_boot_flow_get_initrd_range(const struct fdt_node *fdt_root,
-				     paddr_t *begin, paddr_t *end);
+bool plat_boot_flow_get_initrd_range(const struct fdt *fdt, paddr_t *begin,
+				     paddr_t *end);
 bool plat_boot_flow_update(struct mm_stage1_locked stage1_locked,
 			   const struct manifest *manifest,
 			   struct boot_params_update *p, struct memiter *cpio,
diff --git a/inc/hf/plat/iommu.h b/inc/hf/plat/iommu.h
index 0655232..b9eccf2 100644
--- a/inc/hf/plat/iommu.h
+++ b/inc/hf/plat/iommu.h
@@ -25,7 +25,7 @@
  * so that the driver can read from it. This can be used to map IOMMU devices
  * into the hypervisor's address space so they are accessible by the driver.
  */
-bool plat_iommu_init(const struct fdt_node *fdt_root,
+bool plat_iommu_init(const struct fdt *fdt,
 		     struct mm_stage1_locked stage1_locked,
 		     struct mpool *ppool);
 
diff --git a/inc/hf/string.h b/inc/hf/string.h
index 9c64632..6ab6323 100644
--- a/inc/hf/string.h
+++ b/inc/hf/string.h
@@ -19,6 +19,8 @@
 #include <stdbool.h>
 #include <stddef.h>
 
+#include "hf/memiter.h"
+
 /**
  * Maximum length of a string including the NULL terminator.
  * This is an arbitrary number and can be adjusted to fit use cases.
@@ -48,8 +50,9 @@
  */
 #define STRING_INIT(str) ((struct string){.data = str})
 
-enum string_return_code string_init(struct string *str, const char *data,
-				    size_t size);
+enum string_return_code string_init(struct string *str,
+				    const struct memiter *data);
 void string_init_empty(struct string *str);
 bool string_is_empty(const struct string *str);
 const char *string_data(const struct string *str);
+bool string_eq(const struct string *str, const struct memiter *data);
diff --git a/src/BUILD.gn b/src/BUILD.gn
index e74fd1e..04e0923 100644
--- a/src/BUILD.gn
+++ b/src/BUILD.gn
@@ -57,7 +57,6 @@
     "manifest.c",
     "panic.c",
     "spci_memory.c",
-    "string.c",
     "vcpu.c",
     "vm.c",
   ]
@@ -70,6 +69,7 @@
     ":memiter",
     ":mm",
     ":std",
+    ":string",
     "//src/arch/${plat_arch}/hypervisor",
     plat_boot_flow,
     plat_console,
@@ -124,6 +124,16 @@
   ]
 }
 
+source_set("string") {
+  sources = [
+    "string.c",
+  ]
+  deps = [
+    ":memiter",
+    ":std",
+  ]
+}
+
 source_set("fdt_handler") {
   sources = [
     "fdt_handler.c",
@@ -131,6 +141,7 @@
   deps = [
     ":dlog",
     ":fdt",
+    ":fdt_patch",
   ]
 }
 
@@ -141,8 +152,20 @@
   ]
 
   deps = [
+    ":memiter",
+    ":string",
+    "//third_party/dtc:libfdt",
+  ]
+}
+
+source_set("fdt_patch") {
+  sources = [
+    "fdt_patch.c",
+  ]
+  deps = [
     ":dlog",
-    ":std",
+    ":fdt",
+    "//third_party/dtc:libfdt",
   ]
 }
 
diff --git a/src/boot_flow/android.c b/src/boot_flow/android.c
index 88939bc..910d3bb 100644
--- a/src/boot_flow/android.c
+++ b/src/boot_flow/android.c
@@ -41,10 +41,10 @@
  * (b) a fixed address range known at build time (INITRD_ADDR and INITRD_SIZE
  *     are not zero).
  */
-bool plat_boot_flow_get_initrd_range(const struct fdt_node *fdt_root,
-				     paddr_t *begin, paddr_t *end)
+bool plat_boot_flow_get_initrd_range(const struct fdt *fdt, paddr_t *begin,
+				     paddr_t *end)
 {
-	(void)fdt_root;
+	(void)fdt;
 
 	uintpaddr_t initrd_addr = (uintpaddr_t)(INITRD_ADDR);
 	size_t initrd_size = (size_t)(INITRD_SIZE);
diff --git a/src/boot_flow/common.c b/src/boot_flow/common.c
index dace9af..0f263a9 100644
--- a/src/boot_flow/common.c
+++ b/src/boot_flow/common.c
@@ -22,8 +22,7 @@
 /**
  * Extract the boot parameters from the FDT and the boot-flow driver.
  */
-bool boot_flow_get_params(struct boot_params *p,
-			  const struct fdt_node *fdt_root)
+bool boot_flow_get_params(struct boot_params *p, const struct fdt *fdt)
 {
 	struct string memory = STRING_INIT("memory");
 	struct string device_memory = STRING_INIT("device-memory");
@@ -31,14 +30,14 @@
 	p->mem_ranges_count = 0;
 	p->kernel_arg = plat_boot_flow_get_kernel_arg();
 
-	return plat_boot_flow_get_initrd_range(fdt_root, &p->initrd_begin,
+	return plat_boot_flow_get_initrd_range(fdt, &p->initrd_begin,
 					       &p->initrd_end) &&
-	       fdt_find_cpus(fdt_root, p->cpu_ids, &p->cpu_count) &&
-	       fdt_find_memory_ranges(fdt_root, &memory, p->mem_ranges,
+	       fdt_find_cpus(fdt, p->cpu_ids, &p->cpu_count) &&
+	       fdt_find_memory_ranges(fdt, &memory, p->mem_ranges,
 				      &p->mem_ranges_count, MAX_MEM_RANGES) &&
-	       fdt_find_memory_ranges(
-		       fdt_root, &device_memory, p->device_mem_ranges,
-		       &p->device_mem_ranges_count, MAX_DEVICE_MEM_RANGES);
+	       fdt_find_memory_ranges(fdt, &device_memory, p->device_mem_ranges,
+				      &p->device_mem_ranges_count,
+				      MAX_DEVICE_MEM_RANGES);
 }
 
 /**
diff --git a/src/boot_flow/linux.c b/src/boot_flow/linux.c
index a3dff65..ffbfbec 100644
--- a/src/boot_flow/linux.c
+++ b/src/boot_flow/linux.c
@@ -18,6 +18,7 @@
 #include "hf/cpio.h"
 #include "hf/dlog.h"
 #include "hf/fdt_handler.h"
+#include "hf/fdt_patch.h"
 #include "hf/plat/boot_flow.h"
 #include "hf/std.h"
 
@@ -45,10 +46,10 @@
 /**
  * Load initrd range from the board FDT.
  */
-bool plat_boot_flow_get_initrd_range(const struct fdt_node *fdt_root,
-				     paddr_t *begin, paddr_t *end)
+bool plat_boot_flow_get_initrd_range(const struct fdt *fdt, paddr_t *begin,
+				     paddr_t *end)
 {
-	return fdt_find_initrd(fdt_root, begin, end);
+	return fdt_find_initrd(fdt, begin, end);
 }
 
 bool plat_boot_flow_update(struct mm_stage1_locked stage1_locked,
diff --git a/src/fdt.c b/src/fdt.c
index 85d1d0a..8f57cc8 100644
--- a/src/fdt.c
+++ b/src/fdt.c
@@ -16,454 +16,261 @@
 
 #include "hf/fdt.h"
 
-#include <stdalign.h>
-#include <stdint.h>
+#include <libfdt.h>
 
-#include "hf/check.h"
-#include "hf/dlog.h"
-#include "hf/std.h"
+#include "hf/static_assert.h"
 
-struct fdt_header {
-	uint32_t magic;
-	uint32_t totalsize;
-	uint32_t off_dt_struct;
-	uint32_t off_dt_strings;
-	uint32_t off_mem_rsvmap;
-	uint32_t version;
-	uint32_t last_comp_version;
-	uint32_t boot_cpuid_phys;
-	uint32_t size_dt_strings;
-	uint32_t size_dt_struct;
-};
-
-struct fdt_reserve_entry {
-	uint64_t address;
-	uint64_t size;
-};
-
-enum fdt_token {
-	FDT_BEGIN_NODE = 1,
-	FDT_END_NODE = 2,
-	FDT_PROP = 3,
-	FDT_NOP = 4,
-	FDT_END = 9,
-};
-
-struct fdt_tokenizer {
-	const char *cur;
-	const char *end;
-	const char *strs;
-};
-
-#define FDT_VERSION 17
-#define FDT_MAGIC 0xd00dfeed
-
-#define FDT_PROPERTY_NAME_MAX_SIZE 32
-
-#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)
+/** Returns pointer to the FDT buffer. */
+const void *fdt_base(const struct fdt *fdt)
 {
-	t->strs = strs;
-	t->cur = begin;
-	t->end = end;
+	return memiter_base(&fdt->buf);
 }
 
-static void fdt_tokenizer_align(struct fdt_tokenizer *t)
+/** Returns size of the FDT buffer. */
+size_t fdt_size(const struct fdt *fdt)
 {
-	t->cur = (char *)align_up(t->cur, FDT_TOKEN_ALIGNMENT);
-}
-
-static bool fdt_tokenizer_uint32(struct fdt_tokenizer *t, uint32_t *res)
-{
-	const char *next = t->cur + sizeof(*res);
-
-	if (next > t->end) {
-		return false;
-	}
-
-	*res = be32toh(*(uint32_t *)t->cur);
-	t->cur = next;
-
-	return true;
-}
-
-static bool fdt_tokenizer_token(struct fdt_tokenizer *t, uint32_t *res)
-{
-	uint32_t v;
-
-	while (fdt_tokenizer_uint32(t, &v)) {
-		if (v != FDT_NOP) {
-			*res = v;
-			return true;
-		}
-	}
-	return false;
-}
-
-static bool fdt_tokenizer_bytes(struct fdt_tokenizer *t, const char **res,
-				size_t size)
-{
-	const char *next = t->cur + size;
-
-	if (next > t->end) {
-		return false;
-	}
-
-	*res = t->cur;
-	t->cur = next;
-	fdt_tokenizer_align(t);
-
-	return true;
-}
-
-static bool fdt_tokenizer_str(struct fdt_tokenizer *t, const char **res)
-{
-	const char *p;
-
-	for (p = t->cur; p < t->end; p++) {
-		if (!*p) {
-			/* Found the end of the string. */
-			*res = t->cur;
-			t->cur = p + 1;
-			fdt_tokenizer_align(t);
-			return true;
-		}
-	}
-
-	return false;
-}
-
-bool fdt_root_node(struct fdt_node *node, const struct fdt_header *hdr)
-{
-	uint32_t max_ver;
-	uint32_t min_ver;
-	uint32_t begin = be32toh(hdr->off_dt_struct);
-	uint32_t size = be32toh(hdr->size_dt_struct);
-
-	memset_s(node, sizeof(*node), 0, sizeof(*node));
-
-	/* Check the magic number before anything else. */
-	if (hdr->magic != be32toh(FDT_MAGIC)) {
-		return false;
-	}
-
-	/* Check the version. */
-	max_ver = be32toh(hdr->version);
-	min_ver = be32toh(hdr->last_comp_version);
-	if (FDT_VERSION < min_ver || FDT_VERSION > max_ver) {
-		return false;
-	}
-
-	/* TODO: Verify that it is all within the fdt. */
-	node->begin = (const char *)hdr + begin;
-	node->end = node->begin + size;
-
-	/* TODO: Verify strings as well. */
-	node->strs = (char *)hdr + be32toh(hdr->off_dt_strings);
-
-	return true;
-}
-
-static bool fdt_next_property(struct fdt_tokenizer *t, const char **name,
-			      const char **buf, uint32_t *size)
-{
-	uint32_t token;
-	uint32_t nameoff;
-
-	if (!fdt_tokenizer_token(t, &token)) {
-		return false;
-	}
-
-	if (token != FDT_PROP) {
-		/* Rewind so that caller will get the same token. */
-		t->cur -= sizeof(uint32_t);
-		return false;
-	}
-
-	if (!fdt_tokenizer_uint32(t, size) ||
-	    !fdt_tokenizer_uint32(t, &nameoff) ||
-	    !fdt_tokenizer_bytes(t, buf, *size)) {
-		/*
-		 * Move cursor to the end so that caller won't get any new
-		 * tokens.
-		 */
-		t->cur = t->end;
-		return false;
-	}
-
-	/* TODO: Need to verify the strings. */
-	*name = t->strs + nameoff;
-
-	return true;
-}
-
-static bool fdt_next_subnode(struct fdt_tokenizer *t, const char **name)
-{
-	uint32_t token;
-
-	if (!fdt_tokenizer_token(t, &token)) {
-		return false;
-	}
-
-	if (token != FDT_BEGIN_NODE) {
-		/* Rewind so that caller will get the same token. */
-		t->cur -= sizeof(uint32_t);
-		return false;
-	}
-
-	if (!fdt_tokenizer_str(t, name)) {
-		/*
-		 * Move cursor to the end so that caller won't get any new
-		 * tokens.
-		 */
-		t->cur = t->end;
-		return false;
-	}
-
-	return true;
-}
-
-static void fdt_skip_properties(struct fdt_tokenizer *t)
-{
-	const char *name;
-	const char *buf;
-	uint32_t size;
-
-	while (fdt_next_property(t, &name, &buf, &size)) {
-		/* do nothing */
-	}
-}
-
-static bool fdt_skip_node(struct fdt_tokenizer *t)
-{
-	const char *name;
-	uint32_t token;
-	size_t pending = 1;
-
-	fdt_skip_properties(t);
-
-	do {
-		while (fdt_next_subnode(t, &name)) {
-			fdt_skip_properties(t);
-			pending++;
-		}
-
-		if (!fdt_tokenizer_token(t, &token)) {
-			return false;
-		}
-
-		if (token != FDT_END_NODE) {
-			t->cur = t->end;
-			return false;
-		}
-
-		pending--;
-	} while (pending);
-
-	return true;
-}
-
-bool fdt_read_property(const struct fdt_node *node, const char *name,
-		       const char **buf, uint32_t *size)
-{
-	struct fdt_tokenizer t;
-	const char *prop_name;
-
-	fdt_tokenizer_init(&t, node->strs, node->begin, node->end);
-
-	while (fdt_next_property(&t, &prop_name, buf, size)) {
-		if (!strncmp(prop_name, name, FDT_PROPERTY_NAME_MAX_SIZE)) {
-			return true;
-		}
-	}
-
-	return false;
+	return memiter_size(&fdt->buf);
 }
 
 /**
- * Helper method for parsing 32/64-bit uints from FDT data.
+ * Extracts total size of the FDT structure from its FDT header.
+ * Returns true on success, false if header validation failed.
  */
-bool fdt_parse_number(const char *data, uint32_t size, uint64_t *value)
+bool fdt_size_from_header(const void *ptr, size_t *val)
 {
-	union {
-		volatile uint64_t v;
-		char a[8];
-	} t;
+	if (fdt_check_header(ptr) != 0) {
+		return false;
+	}
 
-	/* FDT values should be aligned to 32-bit boundary. */
-	CHECK(is_aligned(data, FDT_TOKEN_ALIGNMENT));
+	*val = fdt_totalsize(ptr);
+	return true;
+}
+
+/**
+ * Initializes `struct fdt` to point to a given buffer.
+ * Returns true on success, false if FDT validation failed.
+ */
+bool fdt_init_from_ptr(struct fdt *fdt, const void *ptr, size_t len)
+{
+	if (fdt_check_full(ptr, len) != 0) {
+		return false;
+	}
+
+	memiter_init(&fdt->buf, ptr, len);
+	return true;
+}
+
+/**
+ * Initializes `struct fdt` to point to a given buffer.
+ * Returns true on success, false if FDT validation failed.
+ */
+bool fdt_init_from_memiter(struct fdt *fdt, const struct memiter *it)
+{
+	return fdt_init_from_ptr(fdt, memiter_base(it), memiter_size(it));
+}
+
+/**
+ * Invalidates the internal pointer to FDT buffer.
+ * This is meant to prevent use-after-free bugs.
+ */
+void fdt_fini(struct fdt *fdt)
+{
+	memiter_init(&fdt->buf, NULL, 0);
+}
+
+/**
+ * Finds a node of a given path in the device tree.
+ * Unit addresses of components may be omitted but result is undefined if
+ * the path is not unique.
+ * Returns true on success, false if not found or an error occurred.
+ */
+bool fdt_find_node(const struct fdt *fdt, const char *path,
+		   struct fdt_node *node)
+{
+	int offset = fdt_path_offset(fdt_base(fdt), path);
+
+	if (offset < 0) {
+		return false;
+	}
+
+	*node = (struct fdt_node){.fdt = *fdt, .offset = offset};
+	return true;
+}
+
+/**
+ * Retrieves address size for a bus represented in the device tree.
+ * Result is value of '#address-cells' at `node` multiplied by cell size.
+ * If '#address-cells' is not found, the default value is 2 cells.
+ * Returns true on success, false if an error occurred.
+ */
+bool fdt_address_size(const struct fdt_node *node, size_t *size)
+{
+	int s = fdt_address_cells(fdt_base(&node->fdt), node->offset);
+
+	if (s < 0) {
+		return false;
+	}
+
+	*size = (size_t)s * sizeof(uint32_t);
+	return true;
+}
+
+/**
+ * Retrieves address range size for a bus represented in the device tree.
+ * Result is value of '#size-cells' at `node` multiplied by cell size.
+ * If '#size-cells' is not found, the default value is 1 cell.
+ * Returns true on success, false if an error occurred.
+ */
+bool fdt_size_size(const struct fdt_node *node, size_t *size)
+{
+	int s = fdt_size_cells(fdt_base(&node->fdt), node->offset);
+
+	if (s < 0) {
+		return false;
+	}
+
+	*size = (size_t)s * sizeof(uint32_t);
+	return true;
+}
+
+/**
+ * Retrieves the buffer with value of property `name` at `node`.
+ * Returns true on success, false if not found or an error occurred.
+ */
+bool fdt_read_property(const struct fdt_node *node, const char *name,
+		       struct memiter *data)
+{
+	const void *ptr;
+	int lenp;
+
+	ptr = fdt_getprop(fdt_base(&node->fdt), node->offset, name, &lenp);
+	if (ptr == NULL) {
+		return false;
+	}
+
+	CHECK(lenp >= 0);
+	memiter_init(data, ptr, (size_t)lenp);
+	return true;
+}
+
+/**
+ * Reads the value of property `name` at `node` as a uint.
+ * The size of the uint is inferred from the size of the property's value.
+ * Returns true on success, false if property not found or an error occurred.
+ */
+bool fdt_read_number(const struct fdt_node *node, const char *name,
+		     uint64_t *val)
+{
+	struct memiter data;
+
+	return fdt_read_property(node, name, &data) &&
+	       fdt_parse_number(&data, memiter_size(&data), val) &&
+	       (memiter_size(&data) == 0);
+}
+
+/**
+ * Parses a uint of given `size` from the beginning of `data`.
+ * On success returns true and advances `data` by `size` bytes.
+ * Returns false if `data` is too short or uints of `size` are not supported.
+ */
+bool fdt_parse_number(struct memiter *data, size_t size, uint64_t *val)
+{
+	struct memiter data_int;
+	struct memiter data_rem;
+
+	data_rem = *data;
+	if (!memiter_consume(&data_rem, size, &data_int)) {
+		return false;
+	}
 
 	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:
+	case sizeof(uint32_t): {
+		static_assert(sizeof(uint32_t) == sizeof(fdt32_t),
+			      "Size mismatch");
+		*val = fdt32_ld((const fdt32_t *)memiter_base(&data_int));
+		break;
+	}
+	case sizeof(uint64_t): {
+		static_assert(sizeof(uint64_t) == sizeof(fdt64_t),
+			      "Size mismatch");
+		*val = fdt64_ld((const fdt64_t *)memiter_base(&data_int));
+		break;
+	}
+	default: {
 		return false;
 	}
-}
-
-bool fdt_first_child(struct fdt_node *node, const char **child_name)
-{
-	struct fdt_tokenizer t;
-
-	fdt_tokenizer_init(&t, node->strs, node->begin, node->end);
-
-	fdt_skip_properties(&t);
-
-	if (!fdt_next_subnode(&t, child_name)) {
-		return false;
 	}
 
-	node->begin = t.cur;
-
+	*data = data_rem;
 	return true;
 }
 
-bool fdt_next_sibling(struct fdt_node *node, const char **sibling_name)
+/**
+ * Finds first direct subnode of `node`.
+ * If found, makes `node` point to the subnode and returns true.
+ * Returns false if no subnode is found.
+ */
+bool fdt_first_child(struct fdt_node *node)
 {
-	struct fdt_tokenizer t;
+	int child_off = fdt_first_subnode(fdt_base(&node->fdt), node->offset);
 
-	fdt_tokenizer_init(&t, node->strs, node->begin, node->end);
-
-	if (!fdt_skip_node(&t)) {
+	if (child_off < 0) {
 		return false;
 	}
 
-	if (!fdt_next_subnode(&t, sibling_name)) {
-		return false;
-	}
-
-	node->begin = t.cur;
-
+	node->offset = child_off;
 	return true;
 }
 
-bool fdt_find_child(struct fdt_node *node, const char *child)
+/**
+ * Finds next sibling node of `node`. Call repeatedly to discover all siblings.
+ * If found, makes `node` point to the next sibling node and returns true.
+ * Returns false if no next sibling node is found.
+ */
+bool fdt_next_sibling(struct fdt_node *node)
 {
-	struct fdt_tokenizer t;
-	const char *name;
+	int sib_off = fdt_next_subnode(fdt_base(&node->fdt), node->offset);
 
-	fdt_tokenizer_init(&t, node->strs, node->begin, node->end);
-
-	fdt_skip_properties(&t);
-
-	while (fdt_next_subnode(&t, &name)) {
-		if (!strncmp(name, child, FDT_PROPERTY_NAME_MAX_SIZE)) {
-			node->begin = t.cur;
-			return true;
-		}
-
-		fdt_skip_node(&t);
+	if (sib_off < 0) {
+		return false;
 	}
 
-	return false;
+	node->offset = sib_off;
+	return true;
 }
 
-void fdt_dump(const struct fdt_header *hdr)
+/**
+ * Finds a node named `name` among subnodes of `node`.
+ * Returns true if found, false if not found or an error occurred.
+ */
+bool fdt_find_child(struct fdt_node *node, const struct string *name)
 {
-	uint32_t token;
-	size_t depth = 0;
-	const char *name;
-	struct fdt_tokenizer t;
-	struct fdt_node node;
+	struct fdt_node child = *node;
+	const void *base = fdt_base(&node->fdt);
 
-	/* Traverse the whole thing. */
-	if (!fdt_root_node(&node, hdr)) {
-		dlog_error("FDT failed validation.\n");
-		return;
+	if (!fdt_first_child(&child)) {
+		return false;
 	}
 
-	fdt_tokenizer_init(&t, node.strs, node.begin, node.end);
-
 	do {
-		while (fdt_next_subnode(&t, &name)) {
-			const char *buf;
-			uint32_t size;
+		const char *child_name;
+		int lenp;
+		struct memiter it;
 
-			dlog("%*sNew node: \"%s\"\n", 2 * depth, "", name);
-			depth++;
-			while (fdt_next_property(&t, &name, &buf, &size)) {
-				uint32_t i;
-
-				dlog("%*sproperty: \"%s\" (", 2 * depth, "",
-				     name);
-				for (i = 0; i < size; i++) {
-					dlog("%s%02x", i == 0 ? "" : " ",
-					     buf[i]);
-				}
-				dlog(")\n");
-			}
+		child_name = fdt_get_name(base, child.offset, &lenp);
+		if (child_name == NULL) {
+			/* Error */
+			return false;
 		}
 
-		if (!fdt_tokenizer_token(&t, &token)) {
-			return;
+		CHECK(lenp >= 0);
+		memiter_init(&it, child_name, (size_t)lenp);
+		if (string_eq(name, &it)) {
+			node->offset = child.offset;
+			return true;
 		}
+	} while (fdt_next_sibling(&child));
 
-		if (token != FDT_END_NODE) {
-			return;
-		}
-
-		depth--;
-	} while (depth);
-
-	dlog("fdt: off_mem_rsvmap=%u\n", be32toh(hdr->off_mem_rsvmap));
-	{
-		struct fdt_reserve_entry *e =
-			(struct fdt_reserve_entry
-				 *)((uintptr_t)hdr +
-				    be32toh(hdr->off_mem_rsvmap));
-		while (e->address || e->size) {
-			dlog("Entry: %p (%#x bytes)\n", be64toh(e->address),
-			     be64toh(e->size));
-			e++;
-		}
-	}
-}
-
-void fdt_add_mem_reservation(struct fdt_header *hdr, uint64_t addr,
-			     uint64_t len)
-{
-	/* TODO: Clean this up. */
-	uint8_t *begin = (uint8_t *)hdr + be32toh(hdr->off_mem_rsvmap);
-	struct fdt_reserve_entry *e = (struct fdt_reserve_entry *)begin;
-	size_t old_size =
-		be32toh(hdr->totalsize) - be32toh(hdr->off_mem_rsvmap);
-
-	hdr->totalsize = htobe32(be32toh(hdr->totalsize) +
-				 sizeof(struct fdt_reserve_entry));
-	hdr->off_dt_struct = htobe32(be32toh(hdr->off_dt_struct) +
-				     sizeof(struct fdt_reserve_entry));
-	hdr->off_dt_strings = htobe32(be32toh(hdr->off_dt_strings) +
-				      sizeof(struct fdt_reserve_entry));
-	memmove_s(begin + sizeof(struct fdt_reserve_entry), old_size, begin,
-		  old_size);
-	e->address = htobe64(addr);
-	e->size = htobe64(len);
-}
-
-size_t fdt_header_size(void)
-{
-	return sizeof(struct fdt_header);
-}
-
-uint32_t fdt_total_size(const struct fdt_header *hdr)
-{
-	return be32toh(hdr->totalsize);
+	/* Not found */
+	return false;
 }
diff --git a/src/fdt_handler.c b/src/fdt_handler.c
index 8af1401..75051ee 100644
--- a/src/fdt_handler.c
+++ b/src/fdt_handler.c
@@ -16,90 +16,34 @@
 
 #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"
-#include "hf/layout.h"
 #include "hf/mm.h"
 #include "hf/std.h"
 
-static bool fdt_read_number(const struct fdt_node *node, const char *name,
-			    uint64_t *value)
-{
-	const char *data;
-	uint32_t size;
-
-	if (!fdt_read_property(node, name, &data, &size)) {
-		return false;
-	}
-
-	switch (size) {
-	case sizeof(uint32_t):
-	case sizeof(uint64_t):
-		CHECK(fdt_parse_number(data, size, value));
-		break;
-
-	default:
-		return false;
-	}
-
-	return true;
-}
-
-static bool fdt_write_number(struct fdt_node *node, const char *name,
-			     uint64_t value)
-{
-	const char *data;
-	uint32_t size;
-	union {
-		volatile uint64_t v;
-		char a[8];
-	} t;
-
-	if (!fdt_read_property(node, name, &data, &size)) {
-		return false;
-	}
-
-	switch (size) {
-	case sizeof(uint32_t):
-		*(uint32_t *)data = be32toh(value);
-		break;
-
-	case sizeof(uint64_t):
-		t.v = be64toh(value);
-		memcpy_s((void *)data, size, t.a, sizeof(uint64_t));
-		break;
-
-	default:
-		return false;
-	}
-
-	return true;
-}
-
 /**
  * Finds the memory region where initrd is stored.
  */
-bool fdt_find_initrd(const struct fdt_node *root, paddr_t *begin, paddr_t *end)
+bool fdt_find_initrd(const struct fdt *fdt, paddr_t *begin, paddr_t *end)
 {
-	struct fdt_node n = *root;
+	struct fdt_node n;
 	uint64_t initrd_begin;
 	uint64_t initrd_end;
 
-	if (!fdt_find_child(&n, "chosen")) {
-		dlog_error("Unable to find 'chosen'\n");
+	if (!fdt_find_node(fdt, "/chosen", &n)) {
+		dlog_error("Unable to find '/chosen'\n");
 		return false;
 	}
 
-	if (!fdt_read_number(&n, "linux,initrd-start", &initrd_begin)) {
-		dlog_error("Unable to read linux,initrd-start\n");
+	if (!fdt_read_number(&n, FDT_PROP_INITRD_START, &initrd_begin)) {
+		dlog_error("Unable to read " FDT_PROP_INITRD_START "\n");
 		return false;
 	}
 
-	if (!fdt_read_number(&n, "linux,initrd-end", &initrd_end)) {
-		dlog_error("Unable to read linux,initrd-end\n");
+	if (!fdt_read_number(&n, FDT_PROP_INITRD_END, &initrd_end)) {
+		dlog_error("Unable to read " FDT_PROP_INITRD_END "\n");
 		return false;
 	}
 
@@ -109,43 +53,38 @@
 	return true;
 }
 
-bool fdt_find_cpus(const struct fdt_node *root, cpu_id_t *cpu_ids,
-		   size_t *cpu_count)
+bool fdt_find_cpus(const struct fdt *fdt, cpu_id_t *cpu_ids, size_t *cpu_count)
 {
-	struct fdt_node n = *root;
-	const char *name;
-	uint64_t address_size;
+	static const struct string str_cpu = STRING_INIT("cpu");
+	struct fdt_node n;
+	size_t addr_size;
 
 	*cpu_count = 0;
 
-	if (!fdt_find_child(&n, "cpus")) {
-		dlog_error("Unable to find 'cpus'\n");
+	if (!fdt_find_node(fdt, "/cpus", &n)) {
+		dlog_error("Unable to find '/cpus'\n");
 		return false;
 	}
 
-	if (fdt_read_number(&n, "#address-cells", &address_size)) {
-		address_size *= sizeof(uint32_t);
-	} else {
-		address_size = sizeof(uint32_t);
+	if (!fdt_address_size(&n, &addr_size)) {
+		return false;
 	}
 
-	if (!fdt_first_child(&n, &name)) {
+	if (!fdt_first_child(&n)) {
 		return false;
 	}
 
 	do {
-		const char *data;
-		uint32_t size;
+		struct memiter data;
 
-		if (!fdt_read_property(&n, "device_type", &data, &size) ||
-		    size != sizeof("cpu") ||
-		    memcmp(data, "cpu", sizeof("cpu")) != 0 ||
-		    !fdt_read_property(&n, "reg", &data, &size)) {
+		if (!fdt_read_property(&n, "device_type", &data) ||
+		    !string_eq(&str_cpu, &data) ||
+		    !fdt_read_property(&n, "reg", &data)) {
 			continue;
 		}
 
 		/* Get all entries for this CPU. */
-		while (size >= address_size) {
+		while (memiter_size(&data)) {
 			uint64_t value;
 
 			if (*cpu_count >= MAX_CPUS) {
@@ -154,71 +93,52 @@
 				return false;
 			}
 
-			if (!fdt_parse_number(data, address_size, &value)) {
+			if (!fdt_parse_number(&data, addr_size, &value)) {
 				dlog_error("Could not parse CPU id\n");
 				return false;
 			}
 			cpu_ids[(*cpu_count)++] = value;
-
-			size -= address_size;
-			data += address_size;
 		}
-	} while (fdt_next_sibling(&n, &name));
+	} while (fdt_next_sibling(&n));
 
 	return true;
 }
 
-bool fdt_find_memory_ranges(const struct fdt_node *root,
-			    struct string *device_type,
+bool fdt_find_memory_ranges(const struct fdt *fdt, struct string *device_type,
 			    struct mem_range *mem_ranges,
 			    size_t *mem_ranges_count, size_t mem_range_limit)
 {
-	struct fdt_node n = *root;
-	const char *name;
-	uint64_t address_size;
-	uint64_t size_size;
-	uint64_t entry_size;
+	struct fdt_node n;
+	size_t addr_size;
+	size_t size_size;
 	size_t mem_range_index = 0;
 
-	/* Get the sizes of memory range addresses and sizes. */
-	if (fdt_read_number(&n, "#address-cells", &address_size)) {
-		address_size *= sizeof(uint32_t);
-	} else {
-		address_size = sizeof(uint32_t);
+	if (!fdt_find_node(fdt, "/", &n) || !fdt_address_size(&n, &addr_size) ||
+	    !fdt_size_size(&n, &size_size)) {
+		return false;
 	}
 
-	if (fdt_read_number(&n, "#size-cells", &size_size)) {
-		size_size *= sizeof(uint32_t);
-	} else {
-		size_size = sizeof(uint32_t);
-	}
-
-	entry_size = address_size + size_size;
-
-	/* Look for nodes with the device_type set to "memory". */
-	if (!fdt_first_child(&n, &name)) {
+	/* Look for nodes with the device_type set to `device_type`. */
+	if (!fdt_first_child(&n)) {
 		return false;
 	}
 
 	do {
-		const char *data;
-		uint32_t size;
+		struct memiter data;
 
-		if (!fdt_read_property(&n, "device_type", &data, &size) ||
-		    strncmp(data, string_data(device_type), STRING_MAX_SIZE) !=
-			    0 ||
-		    !fdt_read_property(&n, "reg", &data, &size)) {
+		if (!fdt_read_property(&n, "device_type", &data) ||
+		    !string_eq(device_type, &data) ||
+		    !fdt_read_property(&n, "reg", &data)) {
 			continue;
 		}
 
 		/* Traverse all memory ranges within this node. */
-		while (size >= entry_size) {
+		while (memiter_size(&data)) {
 			uintpaddr_t addr;
 			size_t len;
 
-			CHECK(fdt_parse_number(data, address_size, &addr));
-			CHECK(fdt_parse_number(data + address_size, size_size,
-					       &len));
+			CHECK(fdt_parse_number(&data, addr_size, &addr));
+			CHECK(fdt_parse_number(&data, size_size, &len));
 
 			if (mem_range_index < mem_range_limit) {
 				mem_ranges[mem_range_index].begin =
@@ -234,151 +154,69 @@
 					string_data(device_type),
 					mem_range_index, mem_range_limit, len);
 			}
-
-			size -= entry_size;
-			data += entry_size;
 		}
-	} while (fdt_next_sibling(&n, &name));
+	} while (fdt_next_sibling(&n));
 	*mem_ranges_count = mem_range_index;
 
 	return true;
 }
 
-struct fdt_header *fdt_map(struct mm_stage1_locked stage1_locked,
-			   paddr_t fdt_addr, struct fdt_node *n,
-			   struct mpool *ppool)
+bool fdt_map(struct fdt *fdt, struct mm_stage1_locked stage1_locked,
+	     paddr_t fdt_addr, struct mpool *ppool)
 {
-	struct fdt_header *fdt;
+	const void *fdt_ptr;
+	size_t fdt_len;
 
 	/* Map the fdt header in. */
-	fdt = mm_identity_map(stage1_locked, fdt_addr,
-			      pa_add(fdt_addr, fdt_header_size()), MM_MODE_R,
-			      ppool);
-	if (!fdt) {
+	fdt_ptr = mm_identity_map(stage1_locked, fdt_addr,
+				  pa_add(fdt_addr, FDT_V17_HEADER_SIZE),
+				  MM_MODE_R, ppool);
+	if (!fdt_ptr) {
 		dlog_error("Unable to map FDT header.\n");
 		return NULL;
 	}
 
-	if (!fdt_root_node(n, fdt)) {
-		dlog_error("FDT failed validation.\n");
+	if (!fdt_size_from_header(fdt_ptr, &fdt_len)) {
+		dlog_error("FDT failed header validation.\n");
 		goto fail;
 	}
 
 	/* Map the rest of the fdt in. */
-	fdt = mm_identity_map(stage1_locked, fdt_addr,
-			      pa_add(fdt_addr, fdt_total_size(fdt)), MM_MODE_R,
-			      ppool);
-	if (!fdt) {
+	fdt_ptr = mm_identity_map(stage1_locked, fdt_addr,
+				  pa_add(fdt_addr, fdt_len), MM_MODE_R, ppool);
+	if (!fdt_ptr) {
 		dlog_error("Unable to map full FDT.\n");
 		goto fail;
 	}
 
-	return fdt;
+	if (!fdt_init_from_ptr(fdt, fdt_ptr, fdt_len)) {
+		dlog_error("FDT failed validation.\n");
+		goto fail_full;
+	}
+
+	return true;
+
+fail_full:
+	mm_unmap(stage1_locked, fdt_addr, pa_add(fdt_addr, fdt_len), ppool);
+	return false;
 
 fail:
-	mm_unmap(stage1_locked, fdt_addr, pa_add(fdt_addr, fdt_header_size()),
-		 ppool);
-	return NULL;
-}
-
-bool fdt_unmap(struct mm_stage1_locked stage1_locked, struct fdt_header *fdt,
-	       struct mpool *ppool)
-{
-	paddr_t fdt_addr = pa_from_va(va_from_ptr(fdt));
-
-	return mm_unmap(stage1_locked, fdt_addr,
-			pa_add(fdt_addr, fdt_total_size(fdt)), ppool);
-}
-
-bool fdt_patch(struct mm_stage1_locked stage1_locked, paddr_t fdt_addr,
-	       struct boot_params_update *p, struct mpool *ppool)
-{
-	struct fdt_header *fdt;
-	struct fdt_node n;
-	bool ret = false;
-	size_t i;
-
-	/* Map the fdt header in. */
-	fdt = mm_identity_map(stage1_locked, fdt_addr,
-			      pa_add(fdt_addr, fdt_header_size()), MM_MODE_R,
-			      ppool);
-	if (!fdt) {
-		dlog_error("Unable to map FDT header.\n");
-		return false;
-	}
-
-	if (!fdt_root_node(&n, fdt)) {
-		dlog_error("FDT failed validation.\n");
-		goto err_unmap_fdt_header;
-	}
-
-	/* Map the fdt (+ a page) in r/w mode in preparation for updating it. */
-	fdt = mm_identity_map(stage1_locked, fdt_addr,
-			      pa_add(fdt_addr, fdt_total_size(fdt) + PAGE_SIZE),
-			      MM_MODE_R | MM_MODE_W, ppool);
-	if (!fdt) {
-		dlog_error("Unable to map FDT in r/w mode.\n");
-		goto err_unmap_fdt_header;
-	}
-
-	if (!fdt_find_child(&n, "")) {
-		dlog_error("Unable to find FDT root node.\n");
-		goto out_unmap_fdt;
-	}
-
-	if (!fdt_find_child(&n, "chosen")) {
-		dlog_error("Unable to find 'chosen'\n");
-		goto out_unmap_fdt;
-	}
-
-	/* Patch FDT to point to new ramdisk. */
-	if (!fdt_write_number(&n, "linux,initrd-start",
-			      pa_addr(p->initrd_begin))) {
-		dlog_error("Unable to write linux,initrd-start\n");
-		goto out_unmap_fdt;
-	}
-
-	if (!fdt_write_number(&n, "linux,initrd-end", pa_addr(p->initrd_end))) {
-		dlog_error("Unable to write linux,initrd-end\n");
-		goto out_unmap_fdt;
-	}
-
-	/*
-	 * Patch FDT to reserve hypervisor memory so the primary VM doesn't try
-	 * to use it.
-	 */
-	fdt_add_mem_reservation(
-		fdt, pa_addr(layout_text_begin()),
-		pa_difference(layout_text_begin(), layout_text_end()));
-	fdt_add_mem_reservation(
-		fdt, pa_addr(layout_rodata_begin()),
-		pa_difference(layout_rodata_begin(), layout_rodata_end()));
-	fdt_add_mem_reservation(
-		fdt, pa_addr(layout_data_begin()),
-		pa_difference(layout_data_begin(), layout_data_end()));
-
-	/* Patch FDT to reserve memory for secondary VMs. */
-	for (i = 0; i < p->reserved_ranges_count; ++i) {
-		fdt_add_mem_reservation(
-			fdt, pa_addr(p->reserved_ranges[i].begin),
-			pa_addr(p->reserved_ranges[i].end) -
-				pa_addr(p->reserved_ranges[i].begin));
-	}
-
-	ret = true;
-
-out_unmap_fdt:
-	/* Unmap FDT. */
-	if (!mm_unmap(stage1_locked, fdt_addr,
-		      pa_add(fdt_addr, fdt_total_size(fdt) + PAGE_SIZE),
-		      ppool)) {
-		dlog_error("Unable to unmap writable FDT.\n");
-		return false;
-	}
-	return ret;
-
-err_unmap_fdt_header:
-	mm_unmap(stage1_locked, fdt_addr, pa_add(fdt_addr, fdt_header_size()),
+	mm_unmap(stage1_locked, fdt_addr, pa_add(fdt_addr, FDT_V17_HEADER_SIZE),
 		 ppool);
 	return false;
 }
+
+bool fdt_unmap(struct fdt *fdt, struct mm_stage1_locked stage1_locked,
+	       struct mpool *ppool)
+{
+	paddr_t begin = pa_from_va(va_from_ptr(fdt_base(fdt)));
+	paddr_t end = pa_add(begin, fdt_size(fdt));
+
+	if (!mm_unmap(stage1_locked, begin, end, ppool)) {
+		return false;
+	}
+
+	/* Invalidate pointer to the buffer. */
+	fdt_fini(fdt);
+	return true;
+}
diff --git a/src/fdt_handler_test.cc b/src/fdt_handler_test.cc
index 8788f77..21ab4d3 100644
--- a/src/fdt_handler_test.cc
+++ b/src/fdt_handler_test.cc
@@ -101,19 +101,16 @@
 	mpool_add_chunk(&ppool, test_heap.get(), TEST_HEAP_SIZE);
 	mm_init(&ppool);
 
-	struct fdt_header *fdt;
-	struct fdt_node n;
+	struct fdt fdt;
 	struct boot_params params = {};
 
 	struct mm_stage1_locked mm_stage1_locked = mm_lock_stage1();
 	struct string memory = STRING_INIT("memory");
-	fdt = fdt_map(mm_stage1_locked, pa_init((uintpaddr_t)&test_dtb), &n,
-		      &ppool);
-	ASSERT_THAT(fdt, NotNull());
-	ASSERT_TRUE(fdt_find_child(&n, ""));
-	fdt_find_memory_ranges(&n, &memory, params.mem_ranges,
+	ASSERT_TRUE(fdt_map(&fdt, mm_stage1_locked,
+			    pa_init((uintpaddr_t)&test_dtb), &ppool));
+	fdt_find_memory_ranges(&fdt, &memory, params.mem_ranges,
 			       &params.mem_ranges_count, MAX_MEM_RANGES);
-	ASSERT_TRUE(fdt_unmap(mm_stage1_locked, fdt, &ppool));
+	ASSERT_TRUE(fdt_unmap(&fdt, mm_stage1_locked, &ppool));
 	mm_unlock_stage1(&mm_stage1_locked);
 
 	EXPECT_THAT(params.mem_ranges_count, Eq(3));
diff --git a/src/fdt_patch.c b/src/fdt_patch.c
new file mode 100644
index 0000000..5161192
--- /dev/null
+++ b/src/fdt_patch.c
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2020 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/fdt_patch.h"
+
+#include <libfdt.h>
+
+#include "hf/boot_params.h"
+#include "hf/dlog.h"
+#include "hf/fdt.h"
+#include "hf/fdt_handler.h"
+#include "hf/layout.h"
+#include "hf/mm.h"
+
+static bool patch_uint(void *fdt, int off, const char *prop, uint64_t val)
+{
+	const void *data;
+	int lenp;
+
+	data = fdt_getprop(fdt, off, prop, &lenp);
+	if (data == NULL) {
+		return false;
+	}
+
+	switch (lenp) {
+	case sizeof(uint64_t): {
+		return fdt_setprop_inplace_u64(fdt, off, prop, val) == 0;
+	}
+	case sizeof(uint32_t): {
+		return (val <= UINT32_MAX) &&
+		       (fdt_setprop_inplace_u32(fdt, off, prop, val) == 0);
+	}
+	default: {
+		return false;
+	}
+	}
+}
+
+static bool add_mem_reservation(void *fdt, paddr_t begin, paddr_t end)
+{
+	size_t len = pa_difference(begin, end);
+
+	return fdt_add_mem_rsv(fdt, pa_addr(begin), len) == 0;
+}
+
+bool fdt_patch(struct mm_stage1_locked stage1_locked, paddr_t fdt_addr,
+	       struct boot_params_update *p, struct mpool *ppool)
+{
+	void *fdt;
+	size_t buf_size;
+	int off;
+	bool ret = false;
+	bool rsv;
+	size_t i;
+
+	/* Map the fdt header in. */
+	fdt = mm_identity_map(stage1_locked, fdt_addr,
+			      pa_add(fdt_addr, FDT_V17_HEADER_SIZE), MM_MODE_R,
+			      ppool);
+	if (!fdt) {
+		dlog_error("Unable to map FDT header.\n");
+		return false;
+	}
+
+	if (fdt_check_header(fdt) != 0) {
+		dlog_error("FDT failed header validation.\n");
+		goto err_unmap_fdt_header;
+	}
+
+	/* Map the fdt (+ a page) in r/w mode in preparation for updating it. */
+	buf_size = fdt_totalsize(fdt) + PAGE_SIZE;
+	fdt = mm_identity_map(stage1_locked, fdt_addr,
+			      pa_add(fdt_addr, buf_size), MM_MODE_R | MM_MODE_W,
+			      ppool);
+	if (!fdt) {
+		dlog_error("Unable to map FDT in r/w mode.\n");
+		goto err_unmap_fdt_header;
+	}
+
+	if (fdt_check_full(fdt, buf_size) != 0) {
+		dlog_error("FDT failed validation.\n");
+		goto out_unmap_fdt;
+	}
+
+	off = fdt_path_offset(fdt, "/chosen");
+	if (off < 0) {
+		dlog_error("Unable to find FDT '/chosen' node.\n");
+		goto out_unmap_fdt;
+	}
+
+	/* Patch FDT to point to new ramdisk. */
+	if (!patch_uint(fdt, off, FDT_PROP_INITRD_START,
+			pa_addr(p->initrd_begin))) {
+		dlog_error("Unable to write" FDT_PROP_INITRD_START "\n");
+		goto out_unmap_fdt;
+	}
+
+	if (!patch_uint(fdt, off, FDT_PROP_INITRD_END,
+			pa_addr(p->initrd_end))) {
+		dlog_error("Unable to write " FDT_PROP_INITRD_END "\n");
+		goto out_unmap_fdt;
+	}
+
+	/*
+	 * Patch FDT to reserve hypervisor memory so the primary VM doesn't try
+	 * to use it.
+	 */
+	rsv = true;
+	rsv &= add_mem_reservation(fdt, layout_text_begin(), layout_text_end());
+	rsv &= add_mem_reservation(fdt, layout_rodata_begin(),
+				   layout_rodata_end());
+	rsv &= add_mem_reservation(fdt, layout_data_begin(), layout_data_end());
+
+	/* Patch FDT to reserve memory for secondary VMs. */
+	for (i = 0; i < p->reserved_ranges_count; ++i) {
+		struct mem_range range = p->reserved_ranges[i];
+
+		rsv &= add_mem_reservation(fdt, range.begin, range.end);
+	}
+
+	if (!rsv) {
+		dlog_error("Unable to add memory reservations to FDT.\n");
+		goto out_unmap_fdt;
+	}
+
+	ret = true;
+
+out_unmap_fdt:
+	/* Unmap FDT. */
+	if (!mm_unmap(stage1_locked, fdt_addr,
+		      pa_add(fdt_addr, fdt_totalsize(fdt) + PAGE_SIZE),
+		      ppool)) {
+		dlog_error("Unable to unmap writable FDT.\n");
+		return false;
+	}
+	return ret;
+
+err_unmap_fdt_header:
+	mm_unmap(stage1_locked, fdt_addr, pa_add(fdt_addr, FDT_V17_HEADER_SIZE),
+		 ppool);
+	return false;
+}
diff --git a/src/fdt_test.cc b/src/fdt_test.cc
index 0ee534d..295821a 100644
--- a/src/fdt_test.cc
+++ b/src/fdt_test.cc
@@ -81,8 +81,10 @@
 
 TEST(fdt, total_size)
 {
-	EXPECT_THAT(fdt_total_size((struct fdt_header *)&test_dtb[0]),
-		    Eq(sizeof(test_dtb)));
+	size_t size;
+
+	EXPECT_TRUE(fdt_size_from_header(&test_dtb[0], &size));
+	EXPECT_THAT(size, Eq(sizeof(test_dtb)));
 }
 
 } /* namespace */
diff --git a/src/init.c b/src/init.c
index 9f8f829..702235b 100644
--- a/src/init.c
+++ b/src/init.c
@@ -72,8 +72,7 @@
 void one_time_init(void)
 {
 	struct string manifest_fname = STRING_INIT("manifest.dtb");
-	struct fdt_header *fdt;
-	struct fdt_node fdt_root;
+	struct fdt fdt;
 	struct manifest manifest;
 	enum manifest_return_code manifest_ret;
 	struct boot_params params;
@@ -92,17 +91,12 @@
 
 	mm_stage1_locked = mm_lock_stage1();
 
-	fdt = fdt_map(mm_stage1_locked, plat_boot_flow_get_fdt_addr(),
-		      &fdt_root, &ppool);
-	if (fdt == NULL) {
+	if (!fdt_map(&fdt, mm_stage1_locked, plat_boot_flow_get_fdt_addr(),
+		     &ppool)) {
 		panic("Unable to map FDT.");
 	}
 
-	if (!fdt_find_child(&fdt_root, "")) {
-		panic("Unable to find FDT root node.");
-	}
-
-	if (!boot_flow_get_params(&params, &fdt_root)) {
+	if (!boot_flow_get_params(&params, &fdt)) {
 		panic("Could not parse boot params.");
 	}
 
@@ -141,11 +135,11 @@
 		      manifest_strerror(manifest_ret));
 	}
 
-	if (!plat_iommu_init(&fdt_root, mm_stage1_locked, &ppool)) {
+	if (!plat_iommu_init(&fdt, mm_stage1_locked, &ppool)) {
 		panic("Could not initialize IOMMUs.");
 	}
 
-	if (!fdt_unmap(mm_stage1_locked, fdt, &ppool)) {
+	if (!fdt_unmap(&fdt, mm_stage1_locked, &ppool)) {
 		panic("Unable to unmap FDT.");
 	}
 
diff --git a/src/iommu/absent.c b/src/iommu/absent.c
index 80f3883..aa2ebdc 100644
--- a/src/iommu/absent.c
+++ b/src/iommu/absent.c
@@ -16,10 +16,10 @@
 
 #include "hf/plat/iommu.h"
 
-bool plat_iommu_init(const struct fdt_node *fdt_root,
+bool plat_iommu_init(const struct fdt *fdt,
 		     struct mm_stage1_locked stage1_locked, struct mpool *ppool)
 {
-	(void)fdt_root;
+	(void)fdt;
 	(void)stage1_locked;
 	(void)ppool;
 
diff --git a/src/manifest.c b/src/manifest.c
index 7a8474b..f83c0f9 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -31,20 +31,39 @@
 		}                                            \
 	} while (0)
 
-#define VM_NAME_BUF_SIZE (2 + 5 + 1) /* "vm" + number + null terminator */
-static_assert(MAX_VMS <= 99999, "Insufficient VM_NAME_BUF_SIZE");
-static_assert(HF_TEE_VM_ID > MAX_VMS,
+#define VM_ID_MAX (HF_VM_ID_OFFSET + MAX_VMS - 1)
+#define VM_ID_MAX_DIGITS (5)
+#define VM_NAME_EXTRA_CHARS (3) /* "vm" + number + '\0' */
+#define VM_NAME_MAX_SIZE (VM_ID_MAX_DIGITS + VM_NAME_EXTRA_CHARS)
+static_assert(VM_NAME_MAX_SIZE <= STRING_MAX_SIZE,
+	      "VM name does not fit into a struct string.");
+static_assert(VM_ID_MAX <= 99999, "Insufficient VM_NAME_BUF_SIZE");
+static_assert(HF_TEE_VM_ID > VM_ID_MAX,
 	      "TrustZone VM ID clashes with normal VM range.");
 
+static inline size_t count_digits(spci_vm_id_t vm_id)
+{
+	size_t digits = 0;
+
+	do {
+		digits++;
+		vm_id /= 10;
+	} while (vm_id);
+	return digits;
+}
+
 /**
  * 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 void generate_vm_node_name(struct string *str, spci_vm_id_t vm_id)
 {
 	static const char *digits = "0123456789";
-	char *ptr = buf + VM_NAME_BUF_SIZE;
+	size_t vm_id_digits = count_digits(vm_id);
+	char *base = str->data;
+	char *ptr = base + (VM_NAME_EXTRA_CHARS + vm_id_digits);
 
+	CHECK(vm_id_digits <= VM_ID_MAX_DIGITS);
 	*(--ptr) = '\0';
 	do {
 		*(--ptr) = digits[vm_id % 10];
@@ -52,8 +71,7 @@
 	} while (vm_id);
 	*(--ptr) = 'm';
 	*(--ptr) = 'v';
-
-	return ptr;
+	CHECK(ptr == base);
 }
 
 /**
@@ -63,11 +81,10 @@
 static enum manifest_return_code read_bool(const struct fdt_node *node,
 					   const char *property, bool *out)
 {
-	const char *data;
-	uint32_t size;
-	bool present = fdt_read_property(node, property, &data, &size);
+	struct memiter data;
+	bool present = fdt_read_property(node, property, &data);
 
-	if (present && size != 0) {
+	if (present && memiter_size(&data) != 0) {
 		return MANIFEST_ERROR_MALFORMED_BOOLEAN;
 	}
 
@@ -79,14 +96,13 @@
 					     const char *property,
 					     struct string *out)
 {
-	const char *data;
-	uint32_t size;
+	struct memiter data;
 
-	if (!fdt_read_property(node, property, &data, &size)) {
+	if (!fdt_read_property(node, property, &data)) {
 		return MANIFEST_ERROR_PROPERTY_NOT_FOUND;
 	}
 
-	switch (string_init(out, data, size)) {
+	switch (string_init(out, &data)) {
 	case STRING_SUCCESS:
 		return MANIFEST_SUCCESS;
 	case STRING_ERROR_INVALID_INPUT:
@@ -113,14 +129,13 @@
 					     const char *property,
 					     uint64_t *out)
 {
-	const char *data;
-	uint32_t size;
+	struct memiter data;
 
-	if (!fdt_read_property(node, property, &data, &size)) {
+	if (!fdt_read_property(node, property, &data)) {
 		return MANIFEST_ERROR_PROPERTY_NOT_FOUND;
 	}
 
-	if (!fdt_parse_number(data, size, out)) {
+	if (!fdt_parse_number(&data, memiter_size(&data), out)) {
 		return MANIFEST_ERROR_MALFORMED_INTEGER;
 	}
 
@@ -165,19 +180,18 @@
 	const struct fdt_node *node, const char *property,
 	struct uint32list_iter *out)
 {
-	const char *data;
-	uint32_t size;
+	struct memiter data;
 
-	if (!fdt_read_property(node, property, &data, &size)) {
+	if (!fdt_read_property(node, property, &data)) {
 		memiter_init(&out->mem_it, NULL, 0);
 		return MANIFEST_SUCCESS;
 	}
 
-	if ((size % sizeof(uint32_t)) != 0) {
+	if ((memiter_size(&data) % sizeof(uint32_t)) != 0) {
 		return MANIFEST_ERROR_MALFORMED_INTEGER_LIST;
 	}
 
-	memiter_init(&out->mem_it, data, size);
+	out->mem_it = data;
 	return MANIFEST_SUCCESS;
 }
 
@@ -193,22 +207,27 @@
 						 const char *property,
 						 struct stringlist_iter *out)
 {
-	const char *data;
-	uint32_t size;
+	struct memiter data;
+	const char *str;
+	size_t size;
 
-	if (!fdt_read_property(node, property, &data, &size)) {
+	if (!fdt_read_property(node, property, &data)) {
 		return MANIFEST_ERROR_PROPERTY_NOT_FOUND;
 	}
 
+	str = memiter_base(&data);
+	size = memiter_size(&data);
+
 	/*
 	 * Require that the value ends with a NULL terminator. Other NULL
 	 * characters separate the string list entries.
 	 */
-	if (data[size - 1] != '\0') {
+	if ((size < 1) || (str[size - 1] != '\0')) {
 		return MANIFEST_ERROR_MALFORMED_STRING_LIST;
 	}
 
-	memiter_init(&out->mem_it, data, size - 1);
+	CHECK(memiter_restrict(&data, 1));
+	out->mem_it = data;
 	return MANIFEST_SUCCESS;
 }
 
@@ -220,16 +239,13 @@
 static enum manifest_return_code uint32list_get_next(
 	struct uint32list_iter *list, uint32_t *out)
 {
-	const char *mem_base = memiter_base(&list->mem_it);
 	uint64_t num;
 
 	CHECK(uint32list_has_next(list));
-
-	if (!fdt_parse_number(mem_base, sizeof(uint32_t), &num)) {
+	if (!fdt_parse_number(&list->mem_it, sizeof(uint32_t), &num)) {
 		return MANIFEST_ERROR_MALFORMED_INTEGER;
 	}
 
-	memiter_advance(&list->mem_it, sizeof(uint32_t));
 	*out = (uint32_t)num;
 	return MANIFEST_SUCCESS;
 }
@@ -284,7 +300,7 @@
 	return false;
 }
 
-static enum manifest_return_code parse_vm(struct fdt_node *node,
+static enum manifest_return_code parse_vm(const struct fdt_node *node,
 					  struct manifest_vm *vm,
 					  spci_vm_id_t vm_id)
 {
@@ -328,8 +344,8 @@
 enum manifest_return_code manifest_init(struct manifest *manifest,
 					struct memiter *manifest_fdt)
 {
-	char vm_name_buf[VM_NAME_BUF_SIZE];
-	const struct fdt_header *fdt;
+	struct string vm_name;
+	struct fdt fdt;
 	struct fdt_node hyp_node;
 	struct stringlist_iter compatible_list;
 	size_t i = 0;
@@ -337,19 +353,12 @@
 
 	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;
+	if (!fdt_init_from_memiter(&fdt, manifest_fdt)) {
+		return MANIFEST_ERROR_FILE_SIZE; /* TODO */
 	}
 
 	/* Find hypervisor node. */
-	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")) {
+	if (!fdt_find_node(&fdt, "/hypervisor", &hyp_node)) {
 		return MANIFEST_ERROR_NO_HYPERVISOR_FDT_NODE;
 	}
 
@@ -363,9 +372,9 @@
 	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)) {
+		generate_vm_node_name(&vm_name, vm_id);
+		if (fdt_find_child(&vm_node, &vm_name)) {
 			return MANIFEST_ERROR_RESERVED_VM_ID;
 		}
 	}
@@ -374,9 +383,9 @@
 	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)) {
+		generate_vm_node_name(&vm_name, vm_id);
+		if (!fdt_find_child(&vm_node, &vm_name)) {
 			break;
 		}
 
diff --git a/src/memiter.c b/src/memiter.c
index d72250c..577bc61 100644
--- a/src/memiter.c
+++ b/src/memiter.c
@@ -16,6 +16,7 @@
 
 #include "hf/memiter.h"
 
+#include "hf/check.h"
 #include "hf/dlog.h"
 #include "hf/std.h"
 
@@ -136,7 +137,41 @@
 	}
 
 	it->next = p;
+	return true;
+}
 
+/**
+ * Decrements the limit of iterator by the given number of bytes. Returns true
+ * if the limit was reduced without going over the base; returns false and
+ * leaves the iterator unmodified otherwise.
+ */
+bool memiter_restrict(struct memiter *it, size_t v)
+{
+	size_t s = memiter_size(it);
+
+	if (v > s) {
+		return false;
+	}
+
+	it->limit = it->next + (s - v);
+	return true;
+}
+
+/**
+ * Initializes `newit` with the first `v` bytes of `it` and advances `it` by
+ * the same number of bytes. This splits the original range into two iterators
+ * after `v` bytes.
+ * Returns true on success; returns false and leaves `it` unmodified and `newit`
+ * uninitialized otherwise.
+ */
+bool memiter_consume(struct memiter *it, size_t v, struct memiter *newit)
+{
+	if (v > memiter_size(it)) {
+		return false;
+	}
+
+	memiter_init(newit, memiter_base(it), v);
+	CHECK(memiter_advance(it, v));
 	return true;
 }
 
diff --git a/src/string.c b/src/string.c
index 4876a3f..f64d87c 100644
--- a/src/string.c
+++ b/src/string.c
@@ -30,14 +30,17 @@
  * The constructor checks that it fits into the internal buffer and copies
  * the string there.
  */
-enum string_return_code string_init(struct string *str, const char *data,
-				    size_t size)
+enum string_return_code string_init(struct string *str,
+				    const struct memiter *data)
 {
+	const char *base = memiter_base(data);
+	size_t size = memiter_size(data);
+
 	/*
 	 * Require that the value contains exactly one NULL character and that
 	 * it is the last byte.
 	 */
-	if (size < 1 || memchr(data, '\0', size) != &data[size - 1]) {
+	if (size < 1 || memchr(base, '\0', size) != &base[size - 1]) {
 		return STRING_ERROR_INVALID_INPUT;
 	}
 
@@ -45,7 +48,7 @@
 		return STRING_ERROR_TOO_LONG;
 	}
 
-	memcpy_s(str->data, sizeof(str->data), data, size);
+	memcpy_s(str->data, sizeof(str->data), base, size);
 	return STRING_SUCCESS;
 }
 
@@ -58,3 +61,16 @@
 {
 	return str->data;
 }
+
+/**
+ * Returns true if the iterator `data` contains string `str`.
+ * Only characters until the first null terminator are compared.
+ */
+bool string_eq(const struct string *str, const struct memiter *data)
+{
+	const char *base = memiter_base(data);
+	size_t len = memiter_size(data);
+
+	return (len <= sizeof(str->data)) &&
+	       (strncmp(str->data, base, len) == 0);
+}
diff --git a/src/string_test.cc b/src/string_test.cc
index f814636..222f5d8 100644
--- a/src/string_test.cc
+++ b/src/string_test.cc
@@ -25,13 +25,15 @@
 TEST(string, valid)
 {
 	struct string str;
+	struct memiter it;
 	constexpr const char data[] = "test";
 
 	string_init_empty(&str);
 	ASSERT_TRUE(string_is_empty(&str));
 	ASSERT_STREQ(string_data(&str), "");
 
-	ASSERT_EQ(string_init(&str, data, sizeof(data)), STRING_SUCCESS);
+	memiter_init(&it, data, sizeof(data));
+	ASSERT_EQ(string_init(&str, &it), STRING_SUCCESS);
 	ASSERT_FALSE(string_is_empty(&str));
 	ASSERT_STRNE(string_data(&str), "");
 	ASSERT_STREQ(string_data(&str), "test");
@@ -40,27 +42,31 @@
 TEST(string, data_zero_size)
 {
 	struct string str;
+	struct memiter it;
 	constexpr const char data[] = "test";
 
-	ASSERT_EQ(string_init(&str, data, 0), STRING_ERROR_INVALID_INPUT);
+	memiter_init(&it, data, 0);
+	ASSERT_EQ(string_init(&str, &it), STRING_ERROR_INVALID_INPUT);
 }
 
 TEST(string, data_no_null_terminator)
 {
 	struct string str;
+	struct memiter it;
 	constexpr const char data[] = {'t', 'e', 's', 't'};
 
-	ASSERT_EQ(string_init(&str, data, sizeof(data)),
-		  STRING_ERROR_INVALID_INPUT);
+	memiter_init(&it, data, sizeof(data));
+	ASSERT_EQ(string_init(&str, &it), STRING_ERROR_INVALID_INPUT);
 }
 
 TEST(string, data_two_null_terminators)
 {
 	struct string str;
+	struct memiter it;
 	constexpr const char data[] = {'\0', 't', 'e', 's', 't', '\0'};
 
-	ASSERT_EQ(string_init(&str, data, sizeof(data)),
-		  STRING_ERROR_INVALID_INPUT);
+	memiter_init(&it, data, sizeof(data));
+	ASSERT_EQ(string_init(&str, &it), STRING_ERROR_INVALID_INPUT);
 }
 
 } /* namespace */
diff --git a/test/hftest/common.c b/test/hftest/common.c
index 877f1db..9a9d306 100644
--- a/test/hftest/common.c
+++ b/test/hftest/common.c
@@ -135,7 +135,7 @@
 }
 
 static void run_test(hftest_test_fn set_up, hftest_test_fn test,
-		     hftest_test_fn tear_down, const struct fdt_header *fdt)
+		     hftest_test_fn tear_down, const struct fdt *fdt)
 {
 	/* Prepare the context. */
 	struct hftest_context *ctx = hftest_get_context();
@@ -172,7 +172,7 @@
  * Runs the given test case.
  */
 void hftest_run(struct memiter suite_name, struct memiter test_name,
-		const struct fdt_header *fdt)
+		const struct fdt *fdt)
 {
 	size_t i;
 	hftest_test_fn suite_set_up = NULL;
@@ -247,8 +247,7 @@
 uintptr_t hftest_get_cpu_id(size_t index)
 {
 	struct boot_params params;
-	struct fdt_node n;
-	const struct fdt_header *fdt = hftest_get_context()->fdt;
+	const struct fdt *fdt = hftest_get_context()->fdt;
 
 	if (fdt == NULL) {
 		/*
@@ -259,13 +258,7 @@
 	}
 
 	/* Find physical CPU ID from FDT. */
-	if (!fdt_root_node(&n, fdt)) {
-		FAIL("FDT failed validation.");
-	}
-	if (!fdt_find_child(&n, "")) {
-		FAIL("Unable to find FDT root node.");
-	}
-	fdt_find_cpus(&n, params.cpu_ids, &params.cpu_count);
+	fdt_find_cpus(fdt, params.cpu_ids, &params.cpu_count);
 
 	return params.cpu_ids[index];
 }
diff --git a/test/hftest/ctrl_fdt.c b/test/hftest/ctrl_fdt.c
index 85fe1cb..eb47272 100644
--- a/test/hftest/ctrl_fdt.c
+++ b/test/hftest/ctrl_fdt.c
@@ -14,36 +14,28 @@
  * limitations under the License.
  */
 
+#include "hf/check.h"
+
 #include "test/hftest.h"
 
-bool hftest_ctrl_start(const struct fdt_header *fdt, struct memiter *cmd)
+bool hftest_ctrl_start(const struct fdt *fdt, struct memiter *cmd)
 {
 	struct fdt_node n;
-	const char *bootargs;
-	uint32_t bootargs_size;
+	struct memiter bootargs;
 
-	if (!fdt_root_node(&n, fdt)) {
-		HFTEST_LOG("FDT failed validation.");
+	if (!fdt_find_node(fdt, "/chosen", &n)) {
+		HFTEST_LOG("Could not find '/chosen' node.");
 		return false;
 	}
 
-	if (!fdt_find_child(&n, "")) {
-		HFTEST_LOG("Unable to find root node in FDT.");
-		return false;
-	}
-
-	if (!fdt_find_child(&n, "chosen")) {
-		HFTEST_LOG("Unable to find 'chosen' node in FDT.");
-		return false;
-	}
-
-	if (!fdt_read_property(&n, "bootargs", &bootargs, &bootargs_size)) {
+	if (!fdt_read_property(&n, "bootargs", &bootargs)) {
 		HFTEST_LOG("Unable to read bootargs.");
 		return false;
 	}
 
 	/* Remove null terminator. */
-	memiter_init(cmd, bootargs, bootargs_size - 1);
+	CHECK(memiter_restrict(&bootargs, 1));
+	*cmd = bootargs;
 	return true;
 }
 
diff --git a/test/hftest/ctrl_uart.c b/test/hftest/ctrl_uart.c
index 4bddf81..b72e29f 100644
--- a/test/hftest/ctrl_uart.c
+++ b/test/hftest/ctrl_uart.c
@@ -53,7 +53,7 @@
 	}
 }
 
-bool hftest_ctrl_start(const struct fdt_header *fdt, struct memiter *cmd)
+bool hftest_ctrl_start(const struct fdt *fdt, struct memiter *cmd)
 {
 	(void)fdt;
 
diff --git a/test/hftest/hftest_common.h b/test/hftest/hftest_common.h
index fba4a54..a4bbcbe 100644
--- a/test/hftest/hftest_common.h
+++ b/test/hftest/hftest_common.h
@@ -24,5 +24,5 @@
 
 void hftest_json(void);
 void hftest_run(struct memiter suite_name, struct memiter test_name,
-		const struct fdt_header *fdt);
+		const struct fdt *fdt);
 void hftest_help(void);
diff --git a/test/hftest/standalone_main.c b/test/hftest/standalone_main.c
index 8f0cec9..c7c008d 100644
--- a/test/hftest/standalone_main.c
+++ b/test/hftest/standalone_main.c
@@ -29,8 +29,10 @@
 extern struct hftest_test hftest_begin[];
 extern struct hftest_test hftest_end[];
 
-void kmain(const struct fdt_header *fdt)
+void kmain(const void *fdt_ptr)
 {
+	struct fdt fdt;
+	size_t fdt_len;
 	struct memiter command_line;
 	struct memiter command;
 
@@ -50,7 +52,13 @@
 
 	hftest_use_list(hftest_begin, hftest_end - hftest_begin);
 
-	if (!hftest_ctrl_start(fdt, &command_line)) {
+	if (!fdt_size_from_header(fdt_ptr, &fdt_len) ||
+	    !fdt_init_from_ptr(&fdt, fdt_ptr, fdt_len)) {
+		HFTEST_LOG("Unable to init FDT.");
+		goto out;
+	}
+
+	if (!hftest_ctrl_start(&fdt, &command_line)) {
 		HFTEST_LOG("Unable to read the command line.");
 		goto out;
 	}
@@ -83,7 +91,7 @@
 			HFTEST_LOG("Unable to parse test.");
 			goto out;
 		}
-		hftest_run(suite_name, test_name, fdt);
+		hftest_run(suite_name, test_name, &fdt);
 		goto out;
 	}
 
diff --git a/test/inc/test/hftest.h b/test/inc/test/hftest.h
index d4779d0..af25041 100644
--- a/test/inc/test/hftest.h
+++ b/test/inc/test/hftest.h
@@ -119,7 +119,7 @@
  * Inform a host that this is the start of a test run and obtain the command
  * line arguments for it.
  */
-bool hftest_ctrl_start(const struct fdt_header *fdt, struct memiter *cmd);
+bool hftest_ctrl_start(const struct fdt *fdt, struct memiter *cmd);
 
 /** Inform a host that this test run has finished and clean up. */
 void hftest_ctrl_finish(void);
diff --git a/test/inc/test/hftest_impl.h b/test/inc/test/hftest_impl.h
index 2c997c8..55bb88c 100644
--- a/test/inc/test/hftest_impl.h
+++ b/test/inc/test/hftest_impl.h
@@ -138,7 +138,7 @@
 	noreturn void (*abort)(void);
 
 	/* These are used in primary VMs. */
-	const struct fdt_header *fdt;
+	const struct fdt *fdt;
 
 	/* These are used in services. */
 	void *send;