Move fdt handling logic out of main.c.

This is in preparation for having archs/platforms that don't use FDT.
diff --git a/inc/boot_params.h b/inc/boot_params.h
new file mode 100644
index 0000000..1b4fe06
--- /dev/null
+++ b/inc/boot_params.h
@@ -0,0 +1,26 @@
+#ifndef _BOOT_PARAMS_H
+#define _BOOT_PARAMS_H
+
+#include <stdbool.h>
+
+#include "mm.h"
+
+struct boot_params {
+	paddr_t mem_begin;
+	paddr_t mem_end;
+	paddr_t initrd_begin;
+	paddr_t initrd_end;
+	size_t kernel_arg;
+};
+
+struct boot_params_update {
+	paddr_t reserved_begin;
+	paddr_t reserved_end;
+	paddr_t initrd_begin;
+	paddr_t initrd_end;
+};
+
+bool plat_get_boot_params(struct boot_params *p);
+bool plat_update_boot_params(struct boot_params_update *p);
+
+#endif /* _BOOT_PARAMS_H */
diff --git a/inc/cpu.h b/inc/cpu.h
index 58df21d..30433ad 100644
--- a/inc/cpu.h
+++ b/inc/cpu.h
@@ -17,11 +17,11 @@
 
 /* TODO: Update alignment such that cpus are in different cache lines. */
 struct cpu {
-	struct vcpu *current;
-
 	/* CPU identifier. Doesn't have to be contiguous. */
 	size_t id;
 
+	struct vcpu *current;
+
 	/* Pointer to bottom of the stack. */
 	void *stack_bottom;
 
diff --git a/inc/dlog.h b/inc/dlog.h
index fca8103..1d514a3 100644
--- a/inc/dlog.h
+++ b/inc/dlog.h
@@ -1,12 +1,14 @@
 #ifndef _DLOG_H
 #define _DLOG_H
 
+#include <stdarg.h>
+
 #if DEBUG
 void dlog(const char *fmt, ...);
+void vdlog(const char *fmt, va_list args);
 #else
 #define dlog(...)
+#define vdlog(fmt, args)
 #endif
 
-void dlog_init(void (*pchar)(char));
-
 #endif /* _DLOG_H */
diff --git a/inc/fdt.h b/inc/fdt.h
index 44d629e..147c0dc 100644
--- a/inc/fdt.h
+++ b/inc/fdt.h
@@ -6,7 +6,6 @@
 #include <stdint.h>
 
 struct fdt_node {
-	/* TODO: What do we need here? */
 	const struct fdt_header *hdr;
 	const char *begin;
 	const char *end;
@@ -16,7 +15,7 @@
 size_t fdt_header_size(void);
 size_t fdt_total_size(struct fdt_header *hdr);
 void fdt_dump(struct fdt_header *hdr);
-void fdt_root_node(struct fdt_node *node, 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);
diff --git a/inc/fdt_handler.h b/inc/fdt_handler.h
new file mode 100644
index 0000000..9ac6853
--- /dev/null
+++ b/inc/fdt_handler.h
@@ -0,0 +1,10 @@
+#ifndef _FDT_HANDLER_H
+#define _FDT_HANDLER_H
+
+#include "boot_params.h"
+#include "fdt.h"
+
+bool fdt_get_boot_params(struct fdt_header *fdt, struct boot_params *p);
+bool fdt_patch(struct fdt_header *fdt, struct boot_params_update *p);
+
+#endif /* _FDT_HANDLER_H */
diff --git a/inc/load.h b/inc/load.h
new file mode 100644
index 0000000..93aa535
--- /dev/null
+++ b/inc/load.h
@@ -0,0 +1,13 @@
+#ifndef _LOAD_H
+#define _LOAD_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "cpio.h"
+#include "memiter.h"
+
+bool load_primary(struct cpio *c, size_t kernel_arg, struct memiter *initrd);
+bool load_secondary(struct cpio *c, uint64_t mem_begin, uint64_t *mem_end);
+
+#endif /* _LOAD_H */
diff --git a/inc/memiter.h b/inc/memiter.h
new file mode 100644
index 0000000..ffaa822
--- /dev/null
+++ b/inc/memiter.h
@@ -0,0 +1,18 @@
+#ifndef _MEMITER_H
+#define _MEMITER_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+struct memiter {
+	const char *next;
+	const char *limit;
+};
+
+void memiter_init(struct memiter *it, const void *data, size_t size);
+bool memiter_parse_uint(struct memiter *it, uint64_t *value);
+bool memiter_parse_str(struct memiter *it, struct memiter *str);
+bool memiter_iseq(const struct memiter *it, const char *str);
+
+#endif /* _MEMITER_H */
diff --git a/inc/mm.h b/inc/mm.h
index 9c129d4..3a5c691 100644
--- a/inc/mm.h
+++ b/inc/mm.h
@@ -38,6 +38,11 @@
 		   paddr_t paddr, int mode);
 bool mm_ptable_map_page(struct mm_ptable *t, vaddr_t va, paddr_t pa, int mode);
 bool mm_ptable_unmap(struct mm_ptable *t, vaddr_t begin, vaddr_t end, int mode);
-void mm_ptable_defrag(struct mm_ptable *t);
+void mm_ptable_defrag(struct mm_ptable *t, int mode);
+
+bool mm_init(void);
+bool mm_map(vaddr_t begin, vaddr_t end, paddr_t paddr, int mode);
+bool mm_unmap(vaddr_t begin, vaddr_t end, int mode);
+void mm_defrag(void);
 
 #endif /* _MM_H */
diff --git a/src/arch/aarch64/hikey.mk b/src/arch/aarch64/hikey.mk
index 1bf62c1..ec9eff9 100644
--- a/src/arch/aarch64/hikey.mk
+++ b/src/arch/aarch64/hikey.mk
@@ -1,3 +1,5 @@
+USE_FDT := 1
+
 LOAD_ADDRESS := 0x1000000
 PL011_BASE := 0xf8015000
 PL011 := 1
diff --git a/src/arch/aarch64/params.c b/src/arch/aarch64/params.c
new file mode 100644
index 0000000..d044d69
--- /dev/null
+++ b/src/arch/aarch64/params.c
@@ -0,0 +1,20 @@
+#include "boot_params.h"
+#include "fdt_handler.h"
+
+struct fdt_header *fdt;
+
+/*
+ * The following are declared weak so that they can overwritten by platform code
+ * if desired.
+ */
+#pragma weak plat_get_boot_params
+bool plat_get_boot_params(struct boot_params *p)
+{
+	return fdt_get_boot_params(fdt, p);
+}
+
+#pragma weak plat_update_boot_params
+bool plat_update_boot_params(struct boot_params_update *p)
+{
+	return fdt_patch(fdt, p);
+}
diff --git a/src/arch/aarch64/qemu.mk b/src/arch/aarch64/qemu.mk
index c137ca8..41bcd3c 100644
--- a/src/arch/aarch64/qemu.mk
+++ b/src/arch/aarch64/qemu.mk
@@ -1,3 +1,5 @@
+USE_FDT := 1
+
 LOAD_ADDRESS := 0x40001000
 PL011_BASE := 0x09000000
 PL011 := 1
diff --git a/src/arch/aarch64/rules.mk b/src/arch/aarch64/rules.mk
index fd6b737..4abf817 100644
--- a/src/arch/aarch64/rules.mk
+++ b/src/arch/aarch64/rules.mk
@@ -2,6 +2,7 @@
 SRCS += exceptions.S
 SRCS += handler.c
 SRCS += mm.c
+SRCS += params.c
 
 OFFSET_SRCS += offsets.c
 
diff --git a/src/dlog.c b/src/dlog.c
index 5382096..db619ee 100644
--- a/src/dlog.c
+++ b/src/dlog.c
@@ -1,6 +1,5 @@
 #include "dlog.h"
 
-#include <stdarg.h>
 #include <stdbool.h>
 #include <stddef.h>
 
@@ -21,7 +20,7 @@
 
 /* clang-format on */
 
-/*
+/**
  * Prints a raw string to the debug log and returns its length.
  */
 static size_t print_raw_string(const char *str)
@@ -33,7 +32,7 @@
 	return c - str;
 }
 
-/*
+/**
  * Prints a formatted string to the debug log. The format includes a minimum
  * width, the fill character, and flags (whether to align to left or right).
  *
@@ -73,7 +72,7 @@
 	print_raw_string(suffix);
 }
 
-/*
+/**
  * Prints a number to the debug log. The caller specifies the base, its minimum
  * width and printf-style flags.
  */
@@ -126,7 +125,7 @@
 	}
 }
 
-/*
+/**
  * Parses the optional flags field of a printf-style format. It returns the spot
  * on the string where a non-flag character was found.
  */
@@ -160,20 +159,17 @@
 	}
 }
 
-/*
- * Prints the given format string to the debug log.
+/**
+ * Same as "dlog", except that arguments are passed as a va_list.
  */
-void dlog(const char *fmt, ...)
+void vdlog(const char *fmt, va_list args)
 {
 	static struct spinlock sl = SPINLOCK_INIT;
 	const char *p;
-	va_list args;
 	size_t w;
 	int flags;
 	char buf[2];
 
-	va_start(args, fmt);
-
 	sl_lock(&sl);
 
 	for (p = fmt; *p; p++) {
@@ -271,6 +267,16 @@
 	}
 
 	sl_unlock(&sl);
+}
 
+/**
+ * Prints the given format string to the debug log.
+ */
+void dlog(const char *fmt, ...)
+{
+	va_list args;
+
+	va_start(args, fmt);
+	vdlog(fmt, args);
 	va_end(args);
 }
diff --git a/src/fdt.c b/src/fdt.c
index fbcf641..faf42b3 100644
--- a/src/fdt.c
+++ b/src/fdt.c
@@ -110,7 +110,7 @@
 	return false;
 }
 
-void fdt_root_node(struct fdt_node *node, const struct fdt_header *hdr)
+bool fdt_root_node(struct fdt_node *node, const struct fdt_header *hdr)
 {
 	uint32_t max_ver;
 	uint32_t min_ver;
@@ -121,14 +121,14 @@
 
 	/* Check the magic number before anything else. */
 	if (hdr->magic != be32toh(FDT_MAGIC)) {
-		return;
+		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;
+		return false;
 	}
 
 	/* TODO: Verify that it is all within the fdt. */
@@ -137,6 +137,8 @@
 
 	/* 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,
diff --git a/src/fdt_handler.c b/src/fdt_handler.c
new file mode 100644
index 0000000..fa2f9b6
--- /dev/null
+++ b/src/fdt_handler.c
@@ -0,0 +1,284 @@
+#include "fdt_handler.h"
+
+#include "boot_params.h"
+#include "dlog.h"
+#include "fdt.h"
+#include "mm.h"
+#include "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(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)
+{
+	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):
+		*value = convert_number(data, size);
+		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((void *)data, t.a, sizeof(uint64_t));
+		break;
+
+	default:
+		return false;
+	}
+
+	return true;
+}
+
+/**
+ * Finds the memory region where initrd is stored, and udpates the fdt node
+ * cursor to the node called "chosen".
+ */
+static bool find_initrd(struct fdt_node *n, struct boot_params *p)
+{
+	if (!fdt_find_child(n, "chosen")) {
+		dlog("Unable to find 'chosen'\n");
+		return false;
+	}
+
+	if (!fdt_read_number(n, "linux,initrd-start", &p->initrd_begin)) {
+		dlog("Unable to read linux,initrd-start\n");
+		return false;
+	}
+
+	if (!fdt_read_number(n, "linux,initrd-end", &p->initrd_end)) {
+		dlog("Unable to read linux,initrd-end\n");
+		return false;
+	}
+
+	return true;
+}
+
+static void find_memory_range(const struct fdt_node *root,
+			      struct boot_params *p)
+{
+	struct fdt_node n = *root;
+	const char *name;
+	uint64_t address_size;
+	uint64_t size_size;
+	uint64_t entry_size;
+
+	/* 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_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)) {
+		return;
+	}
+
+	do {
+		const char *data;
+		uint32_t size;
+		if (!fdt_read_property(&n, "device_type", &data, &size) ||
+		    size != sizeof("memory") ||
+		    memcmp(data, "memory", sizeof("memory")) != 0 ||
+		    !fdt_read_property(&n, "reg", &data, &size)) {
+			continue;
+		}
+
+		/* Traverse all memory ranges within this node. */
+		while (size >= entry_size) {
+			uint64_t addr = convert_number(data, address_size);
+			uint64_t len =
+				convert_number(data + address_size, size_size);
+
+			if (len > p->mem_end - p->mem_begin) {
+				/* Remember the largest range we've found. */
+				p->mem_begin = addr;
+				p->mem_end = addr + len;
+			}
+
+			size -= entry_size;
+			data += entry_size;
+		}
+	} while (fdt_next_sibling(&n, &name));
+
+	/* TODO: Check for "reserved-memory" nodes. */
+}
+
+bool fdt_get_boot_params(struct fdt_header *fdt, struct boot_params *p)
+{
+	struct fdt_node n;
+	bool ret = false;
+
+	/* Map the fdt header in. */
+	if (!mm_map((vaddr_t)fdt, (vaddr_t)fdt + fdt_header_size(),
+		    (paddr_t)fdt, MM_MODE_R)) {
+		dlog("Unable to map FDT header.\n");
+		goto err_unmap_fdt_header;
+	}
+
+	if (!fdt_root_node(&n, fdt)) {
+		dlog("FDT failed validation.\n");
+		goto err_unmap_fdt_header;
+	}
+
+	/* Map the rest of the fdt in. */
+	if (!mm_map((vaddr_t)fdt, (vaddr_t)fdt + fdt_total_size(fdt),
+		    (paddr_t)fdt, MM_MODE_R)) {
+		dlog("Unable to map full FDT.\n");
+		goto err_unmap_fdt_header;
+	}
+
+	if (!fdt_find_child(&n, "")) {
+		dlog("Unable to find FDT root node.\n");
+		goto out_unmap_fdt;
+	}
+
+	p->mem_begin = 0;
+	p->mem_end = 0;
+	find_memory_range(&n, p);
+
+	if (!find_initrd(&n, p)) {
+		goto out_unmap_fdt;
+	}
+
+	p->kernel_arg = (size_t)fdt;
+	ret = true;
+
+out_unmap_fdt:
+	mm_unmap((vaddr_t)fdt, (vaddr_t)fdt + fdt_total_size(fdt), 0);
+	return ret;
+
+err_unmap_fdt_header:
+	mm_unmap((vaddr_t)fdt, (vaddr_t)fdt + fdt_header_size(), 0);
+	return false;
+}
+
+bool fdt_patch(struct fdt_header *fdt, struct boot_params_update *p)
+{
+	struct fdt_node n;
+	bool ret = false;
+
+	/* Map the fdt header in. */
+	if (!mm_map((vaddr_t)fdt, (vaddr_t)fdt + fdt_header_size(),
+		    (paddr_t)fdt, MM_MODE_R)) {
+		dlog("Unable to map FDT header.\n");
+		return false;
+	}
+
+	if (!fdt_root_node(&n, fdt)) {
+		dlog("FDT failed validation.\n");
+		goto err_unmap_fdt_header;
+	}
+
+	/* Map the fdt (+ a page) in r/w mode in preparation for updating it. */
+	if (!mm_map((vaddr_t)fdt,
+		    (vaddr_t)fdt + fdt_total_size(fdt) + PAGE_SIZE,
+		    (paddr_t)fdt, MM_MODE_R | MM_MODE_W)) {
+		dlog("Unable to map FDT in r/w mode.\n");
+		goto err_unmap_fdt_header;
+	}
+
+	if (!fdt_find_child(&n, "")) {
+		dlog("Unable to find FDT root node.\n");
+		goto out_unmap_fdt;
+	}
+
+	if (!fdt_find_child(&n, "chosen")) {
+		dlog("Unable to find 'chosen'\n");
+		goto out_unmap_fdt;
+	}
+
+	/* Patch FDT to point to new ramdisk. */
+	if (!fdt_write_number(&n, "linux,initrd-start", p->initrd_begin)) {
+		dlog("Unable to write linux,initrd-start\n");
+		goto out_unmap_fdt;
+	}
+
+	if (!fdt_write_number(&n, "linux,initrd-end", p->initrd_end)) {
+		dlog("Unable to write linux,initrd-end\n");
+		goto out_unmap_fdt;
+	}
+
+	/* Patch fdt to reserve primary VM memory. */
+	{
+		size_t tmp = (size_t)&plat_update_boot_params;
+		tmp = (tmp + 0x80000 - 1) & ~(0x80000 - 1);
+		fdt_add_mem_reservation(fdt, tmp & ~0xfffff, 0x80000);
+	}
+
+	/* Patch fdt to reserve memory for secondary VMs. */
+	fdt_add_mem_reservation(fdt, p->reserved_begin,
+				p->reserved_end - p->reserved_begin);
+
+	ret = true;
+
+out_unmap_fdt:
+	/* Unmap FDT. */
+	if (!mm_unmap((vaddr_t)fdt,
+		      (vaddr_t)fdt + fdt_total_size(fdt) + PAGE_SIZE, 0)) {
+		dlog("Unable to unmap writable FDT.\n");
+		return false;
+	}
+	return ret;
+
+err_unmap_fdt_header:
+	mm_unmap((vaddr_t)fdt, (vaddr_t)fdt + fdt_header_size(), 0);
+	return false;
+}
diff --git a/src/load.c b/src/load.c
new file mode 100644
index 0000000..e0949d6
--- /dev/null
+++ b/src/load.c
@@ -0,0 +1,184 @@
+#include "load.h"
+
+#include <stdbool.h>
+
+#include "api.h"
+#include "dlog.h"
+#include "memiter.h"
+#include "mm.h"
+#include "std.h"
+#include "vm.h"
+
+/**
+ * Copies data to an unmapped location by mapping it for write, copying the
+ * data, then unmapping it.
+ */
+static bool copy_to_unmaped(paddr_t to, const void *from, size_t size)
+{
+	if (!mm_map((vaddr_t)to, (vaddr_t)to + size, to, MM_MODE_W)) {
+		return false;
+	}
+
+	memcpy((void *)to, from, size);
+
+	mm_unmap(to, to + size, 0);
+
+	return true;
+}
+
+/**
+ * Moves the kernel of the primary VM to its final destination.
+ */
+static bool relocate(const char *from, size_t size)
+{
+	/* TODO: This is a hack. We must read the alignment from the binary. */
+	extern char bin_end[];
+	size_t tmp = (size_t)&bin_end[0];
+	paddr_t dest = (tmp + 0x80000 - 1) & ~(0x80000 - 1);
+	dlog("bin_end is at %p, copying to %p\n", &bin_end[0], dest);
+	return copy_to_unmaped(dest, from, size);
+}
+
+/**
+ * Looks for a file in the given cpio archive. The filename is not
+ * null-terminated, so we use a memory iterator to represent it. The file, if
+ * found, is returned in the "it" argument.
+ */
+static bool memiter_find_file(struct cpio *c, const struct memiter *filename,
+			      struct memiter *it)
+{
+	const char *fname;
+	const void *fcontents;
+	size_t fsize;
+	struct cpio_iter iter;
+
+	cpio_init_iter(c, &iter);
+
+	while (cpio_next(&iter, &fname, &fcontents, &fsize)) {
+		if (memiter_iseq(filename, fname)) {
+			memiter_init(it, fcontents, fsize);
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/**
+ * Looks for a file in the given cpio archive. The file, if found, is returned
+ * in the "it" argument.
+ */
+static bool find_file(struct cpio *c, const char *name, struct memiter *it)
+{
+	const char *fname;
+	const void *fcontents;
+	size_t fsize;
+	struct cpio_iter iter;
+
+	cpio_init_iter(c, &iter);
+
+	while (cpio_next(&iter, &fname, &fcontents, &fsize)) {
+		if (!strcmp(fname, name)) {
+			memiter_init(it, fcontents, fsize);
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/**
+ * Loads the primary VM.
+ */
+bool load_primary(struct cpio *c, size_t kernel_arg, struct memiter *initrd)
+{
+	struct memiter it;
+
+	if (!find_file(c, "vmlinuz", &it)) {
+		dlog("Unable to find vmlinuz\n");
+		return false;
+	}
+
+	if (!relocate(it.next, it.limit - it.next)) {
+		dlog("Unable to relocate kernel for primary vm.\n");
+		return false;
+	}
+
+	if (!find_file(c, "initrd.img", initrd)) {
+		dlog("Unable to find initrd.img\n");
+		return false;
+	}
+
+	{
+		size_t tmp = (size_t)&load_primary;
+		tmp = (tmp + 0x80000 - 1) & ~(0x80000 - 1);
+		vm_init(&primary_vm, MAX_CPUS);
+		vm_start_vcpu(&primary_vm, 0, tmp, kernel_arg, true);
+	}
+
+	arch_set_vm_mm(&primary_vm.page_table);
+
+	return true;
+}
+
+/**
+ * Loads all secondary VMs in the given memory range. "mem_end" is updated to
+ * reflect the fact that some of the memory isn't available to the primary VM
+ * anymore.
+ */
+bool load_secondary(struct cpio *c, uint64_t mem_begin, uint64_t *mem_end)
+{
+	struct memiter it;
+	struct memiter str;
+	uint64_t mem;
+	uint64_t cpu;
+	uint32_t count;
+
+	if (!find_file(c, "vms.txt", &it)) {
+		dlog("vms.txt is missing\n");
+		return true;
+	}
+
+	for (count = 0;
+	     memiter_parse_uint(&it, &mem) && memiter_parse_uint(&it, &cpu) &&
+	     memiter_parse_str(&it, &str) && count < MAX_VMS;
+	     count++) {
+		struct memiter kernel;
+
+		if (!memiter_find_file(c, &str, &kernel)) {
+			dlog("Unable to load kernel for vm %u\n", count);
+			continue;
+		}
+
+		if (mem > *mem_end - mem_begin) {
+			dlog("Not enough memory for vm %u (%u bytes)\n", count,
+			     mem);
+			continue;
+		}
+
+		if (mem < kernel.limit - kernel.next) {
+			dlog("Kernel is larger than available memory for vm "
+			     "%u\n",
+			     count);
+			continue;
+		}
+
+		*mem_end -= mem;
+		if (!copy_to_unmaped(*mem_end, kernel.next,
+				     kernel.limit - kernel.next)) {
+			dlog("Unable to copy kernel for vm %u\n", count);
+			continue;
+		}
+
+		dlog("Loaded VM%u with %u vcpus, entry at 0x%x\n", count, cpu,
+		     *mem_end);
+
+		/* TODO: Update page table to reflect the memory range. */
+		vm_init(secondary_vm + count, cpu);
+		vm_start_vcpu(secondary_vm + count, 0, *mem_end, 0, false);
+	}
+
+	secondary_vm_count = count;
+
+	return true;
+}
diff --git a/src/main.c b/src/main.c
index e4957fd..7792c8a 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1,438 +1,41 @@
 #include <stdalign.h>
 #include <stddef.h>
+#include <stdnoreturn.h>
 
 #include "alloc.h"
 #include "api.h"
+#include "boot_params.h"
 #include "cpio.h"
 #include "cpu.h"
 #include "dlog.h"
-#include "fdt.h"
+#include "load.h"
 #include "mm.h"
 #include "std.h"
 #include "vm.h"
 
-void *fdt;
 char ptable_buf[PAGE_SIZE * 20];
-struct mm_ptable ptable;
-
-bool fdt_find_node(struct fdt_node *node, const char *path)
-{
-	while (*path) {
-		if (!fdt_find_child(node, path)) {
-			return false;
-		}
-		path += strlen(path);
-	}
-
-	return true;
-}
-
-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(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)
-{
-	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):
-		*value = convert_number(data, size);
-		break;
-
-	default:
-		return false;
-	}
-
-	return true;
-}
-
-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((void *)data, t.a, sizeof(uint64_t));
-		break;
-
-	default:
-		return false;
-	}
-
-	return true;
-}
 
 /**
- * Copies data to an unmapped location by mapping it for write, copying the
- * data, then unmapping it.
+ * Blocks the hypervisor.
+ *
+ * TODO: Determine if we want to omit strings on non-debug builds.
  */
-static bool copy_to_unmaped(paddr_t to, const void *from, size_t size)
+noreturn void panic(const char *fmt, ...)
 {
-	if (!mm_ptable_map(&ptable, (vaddr_t)to, (vaddr_t)to + size, to,
-			   MM_MODE_W | MM_MODE_STAGE1)) {
-		return false;
+	va_list args;
+
+	/* TODO: Block all CPUs. */
+
+	dlog("Panic: ");
+
+	va_start(args, fmt);
+	vdlog(fmt, args);
+	va_end(args);
+
+	dlog("\n");
+
+	for (;;) {
 	}
-
-	memcpy((void *)to, from, size);
-
-	mm_ptable_unmap(&ptable, to, to + size, MM_MODE_STAGE1);
-
-	return true;
-}
-
-static bool relocate(const char *from, size_t size)
-{
-	/* TODO: This is a hack. We must read the alignment from the binary. */
-	extern char bin_end[];
-	size_t tmp = (size_t)&bin_end[0];
-	paddr_t dest = (tmp + 0x80000 - 1) & ~(0x80000 - 1);
-	dlog("bin_end is at %p, copying to %p\n", &bin_end[0], dest);
-	return copy_to_unmaped(dest, from, size);
-}
-
-static void find_memory_range(const struct fdt_node *root,
-			      uint64_t *block_start, uint64_t *block_size)
-{
-	struct fdt_node n = *root;
-	const char *name;
-	uint64_t address_size;
-	uint64_t size_size;
-	uint64_t entry_size;
-
-	/* 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_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)) {
-		return;
-	}
-
-	do {
-		const char *data;
-		uint32_t size;
-		if (!fdt_read_property(&n, "device_type", &data, &size) ||
-		    size != sizeof("memory") ||
-		    memcmp(data, "memory", sizeof("memory")) != 0 ||
-		    !fdt_read_property(&n, "reg", &data, &size)) {
-			continue;
-		}
-
-		/* Traverse all memory ranges within this node. */
-		while (size >= entry_size) {
-			uint64_t addr = convert_number(data, address_size);
-			uint64_t len =
-				convert_number(data + address_size, size_size);
-
-			if (len > *block_size) {
-				/* Remember the largest range we've found. */
-				*block_start = addr;
-				*block_size = len;
-			}
-
-			size -= entry_size;
-			data += entry_size;
-		}
-	} while (fdt_next_sibling(&n, &name));
-
-	/* TODO: Check for "reserved-memory" nodes. */
-}
-
-/**
- * Finds the memory region where initrd is stored, and udpates the fdt node
- * cursor to the node called "chosen".
- */
-static bool find_initrd(struct fdt_node *n, uint64_t *begin, uint64_t *end)
-{
-	if (!fdt_find_node(n, "chosen\0")) {
-		dlog("Unable to find 'chosen'\n");
-		return false;
-	}
-
-	if (!fdt_read_number(n, "linux,initrd-start", begin)) {
-		dlog("Unable to read linux,initrd-start\n");
-		return false;
-	}
-
-	if (!fdt_read_number(n, "linux,initrd-end", end)) {
-		dlog("Unable to read linux,initrd-end\n");
-		return false;
-	}
-
-	return true;
-}
-
-struct memiter {
-	const char *next;
-	const char *limit;
-};
-
-static void memiter_init(struct memiter *it, const void *data, size_t size)
-{
-	it->next = data;
-	it->limit = it->next + size;
-}
-
-static bool memiter_isspace(struct memiter *it)
-{
-	switch (*it->next) {
-	case ' ':
-	case '\t':
-	case '\n':
-	case '\r':
-		return true;
-	default:
-		return false;
-	}
-}
-
-static void memiter_skip_space(struct memiter *it)
-{
-	while (it->next < it->limit && memiter_isspace(it)) {
-		it->next++;
-	}
-}
-
-static bool memiter_iseq(const struct memiter *it, const char *str)
-{
-	size_t len = strlen(str);
-	if (len != it->limit - it->next) {
-		return false;
-	}
-	return memcmp(it->next, str, len) == 0;
-}
-
-static bool memiter_parse_str(struct memiter *it, struct memiter *str)
-{
-	/* Skip all white space and fail if we reach the end of the buffer. */
-	memiter_skip_space(it);
-	if (it->next >= it->limit) {
-		return false;
-	}
-
-	str->next = it->next;
-
-	/* Find the end of the string. */
-	while (it->next < it->limit && !memiter_isspace(it)) {
-		it->next++;
-	}
-
-	str->limit = it->next;
-
-	return true;
-}
-
-static bool memiter_parse_uint(struct memiter *it, uint64_t *value)
-{
-	uint64_t v = 0;
-
-	/* Skip all white space and fail if we reach the end of the buffer. */
-	memiter_skip_space(it);
-	if (it->next >= it->limit) {
-		return false;
-	}
-
-	/* Fail if it's not a number. */
-	if (*it->next < '0' || *it->next > '9') {
-		return false;
-	}
-
-	/* Parse the number. */
-	do {
-		v = v * 10 + *it->next - '0';
-		it->next++;
-	} while (it->next < it->limit && *it->next >= '0' && *it->next <= '9');
-
-	*value = v;
-
-	return true;
-}
-
-static bool memiter_find_file(struct cpio *c, const struct memiter *filename,
-			      struct memiter *it)
-{
-	const char *fname;
-	const void *fcontents;
-	size_t fsize;
-	struct cpio_iter iter;
-
-	cpio_init_iter(c, &iter);
-
-	while (cpio_next(&iter, &fname, &fcontents, &fsize)) {
-		if (memiter_iseq(filename, fname)) {
-			memiter_init(it, fcontents, fsize);
-			return true;
-		}
-	}
-
-	return false;
-}
-
-static bool find_file(struct cpio *c, const char *name, struct memiter *it)
-{
-	const char *fname;
-	const void *fcontents;
-	size_t fsize;
-	struct cpio_iter iter;
-
-	cpio_init_iter(c, &iter);
-
-	while (cpio_next(&iter, &fname, &fcontents, &fsize)) {
-		if (!strcmp(fname, name)) {
-			memiter_init(it, fcontents, fsize);
-			return true;
-		}
-	}
-
-	return false;
-}
-
-static bool load_secondary(struct cpio *c, uint64_t mem_start,
-			   uint64_t *mem_size)
-{
-	struct memiter it;
-	struct memiter str;
-	uint64_t mem;
-	uint64_t cpu;
-	uint32_t count;
-
-	if (!find_file(c, "vms.txt", &it)) {
-		dlog("Unable to find vms.txt\n");
-		return false;
-	}
-
-	for (count = 0;
-	     memiter_parse_uint(&it, &mem) && memiter_parse_uint(&it, &cpu) &&
-	     memiter_parse_str(&it, &str) && count < MAX_VMS;
-	     count++) {
-		struct memiter kernel;
-
-		if (!memiter_find_file(c, &str, &kernel)) {
-			dlog("Unable to load kernel for vm %u\n", count);
-			continue;
-		}
-
-		if (mem > *mem_size) {
-			dlog("Not enough memory for vm %u (%u bytes)\n", count,
-			     mem);
-			continue;
-		}
-
-		if (mem < kernel.limit - kernel.next) {
-			dlog("Kernel is larger than available memory for vm "
-			     "%u\n",
-			     count);
-			continue;
-		}
-
-		*mem_size -= mem;
-		if (!copy_to_unmaped(mem_start + *mem_size, kernel.next,
-				     kernel.limit - kernel.next)) {
-			dlog("Unable to copy kernel for vm %u\n", count);
-			continue;
-		}
-
-		dlog("Loaded VM%u with %u vcpus, entry at 0x%x\n", count, cpu,
-		     mem_start + *mem_size);
-		vm_init(secondary_vm + count, cpu);
-		vm_start_vcpu(secondary_vm + count, 0, mem_start + *mem_size, 0,
-			      false);
-	}
-
-	secondary_vm_count = count;
-
-	return true;
-}
-
-static bool load_primary(struct cpio *c, struct fdt_node *chosen)
-{
-	struct memiter it;
-
-	if (!find_file(c, "vmlinuz", &it)) {
-		dlog("Unable to find vmlinuz\n");
-		return false;
-	}
-
-	if (!relocate(it.next, it.limit - it.next)) {
-		dlog("Unable to relocate kernel for primary vm.\n");
-		return false;
-	}
-
-	if (!find_file(c, "initrd.img", &it)) {
-		dlog("Unable to find initrd.img\n");
-		return false;
-	}
-
-	/* Patch FDT to point to new ramdisk. */
-	if (!fdt_write_number(chosen, "linux,initrd-start", (size_t)it.next)) {
-		dlog("Unable to write linux,initrd-start\n");
-		return false;
-	}
-
-	if (!fdt_write_number(chosen, "linux,initrd-end", (size_t)it.limit)) {
-		dlog("Unable to write linux,initrd-end\n");
-		return false;
-	}
-
-	/*
-	 * Patch fdt to reserve memory.
-	 */
-	{
-		size_t tmp = (size_t)&relocate;
-		tmp = (tmp + 0x80000 - 1) & ~(0x80000 - 1);
-		fdt_add_mem_reservation(fdt, tmp & ~0xfffff, 0x80000);
-		vm_init(&primary_vm, MAX_CPUS);
-		vm_start_vcpu(&primary_vm, 0, tmp, (size_t)fdt, true);
-	}
-
-	return true;
 }
 
 /**
@@ -440,126 +43,58 @@
  */
 static void one_time_init(void)
 {
-	extern char text_begin[];
-	extern char text_end[];
-	extern char rodata_begin[];
-	extern char rodata_end[];
-	extern char data_begin[];
-	extern char data_end[];
+	struct boot_params params;
+	struct boot_params_update update;
+	uint64_t new_mem_end;
+	struct memiter primary_initrd;
+	struct cpio c;
 
-	dlog("Initializing hafnium\n");
+	dlog("Initialising hafnium\n");
 
 	cpu_module_init();
 	halloc_init((size_t)ptable_buf, sizeof(ptable_buf));
 
-	if (!mm_ptable_init(&ptable, MM_MODE_NOSYNC | MM_MODE_STAGE1)) {
-		dlog("Unable to allocate memory for page table.\n");
-		for (;;) {
-			/* do nothing */
-		}
+	if (!mm_init()) {
+		panic("mm_init failed");
 	}
 
-	dlog("text: 0x%x - 0x%x\n", text_begin, text_end);
-	dlog("rodata: 0x%x - 0x%x\n", rodata_begin, rodata_end);
-	dlog("data: 0x%x - 0x%x\n", data_begin, data_end);
+	if (!plat_get_boot_params(&params)) {
+		panic("unable to retrieve boot params");
+	}
 
-	/* Map page for uart. */
-	mm_ptable_map_page(&ptable, PL011_BASE, PL011_BASE,
-			   MM_MODE_R | MM_MODE_W | MM_MODE_D | MM_MODE_NOSYNC |
-				   MM_MODE_STAGE1);
+	dlog("Memory range:  0x%x - 0x%x\n", params.mem_begin,
+	     params.mem_end - 1);
+	dlog("Ramdisk range: 0x%x - 0x%x\n", params.initrd_begin,
+	     params.initrd_end - 1);
 
-	/* Map each section. */
-	mm_ptable_map(&ptable, (vaddr_t)text_begin, (vaddr_t)text_end,
-		      (paddr_t)text_begin,
-		      MM_MODE_X | MM_MODE_NOSYNC | MM_MODE_STAGE1);
+	/* Map initrd in, and initialise cpio parser. */
+	if (!mm_map(params.initrd_begin, params.initrd_end, params.initrd_begin,
+		    MM_MODE_R)) {
+		panic("unable to map initrd in");
+	}
 
-	mm_ptable_map(&ptable, (vaddr_t)rodata_begin, (vaddr_t)rodata_end,
-		      (paddr_t)rodata_begin,
-		      MM_MODE_R | MM_MODE_NOSYNC | MM_MODE_STAGE1);
+	cpio_init(&c, (void *)params.initrd_begin,
+		  params.initrd_end - params.initrd_begin);
 
-	mm_ptable_map(&ptable, (vaddr_t)data_begin, (vaddr_t)data_end,
-		      (paddr_t)data_begin,
-		      MM_MODE_R | MM_MODE_W | MM_MODE_NOSYNC | MM_MODE_STAGE1);
+	/* Load all VMs. */
+	new_mem_end = params.mem_end;
+	load_secondary(&c, params.mem_begin, &new_mem_end);
+	if (!load_primary(&c, params.kernel_arg, &primary_initrd)) {
+		panic("unable to load primary VM");
+	}
 
-	arch_mm_init((paddr_t)ptable.table);
+	/* Prepare to run by updating bootparams as seens by primary VM. */
+	update.initrd_begin = (paddr_t)primary_initrd.next;
+	update.initrd_end = (paddr_t)primary_initrd.limit;
+	update.reserved_begin = new_mem_end;
+	update.reserved_end = params.mem_end - new_mem_end;
+	if (!plat_update_boot_params(&update)) {
+		panic("plat_update_boot_params failed");
+	}
 
-	/* TODO: Code below this point should be removed from this function. */
-	do {
-		struct fdt_node n;
-		uint64_t mem_start = 0;
-		uint64_t mem_size = 0;
-		uint64_t new_mem_size;
+	mm_defrag();
 
-		/* Map in the fdt header. */
-		if (!mm_ptable_map(&ptable, (vaddr_t)fdt,
-				   (vaddr_t)fdt + fdt_header_size(),
-				   (paddr_t)fdt, MM_MODE_R | MM_MODE_STAGE1)) {
-			dlog("Unable to map FDT header.\n");
-			break;
-		}
-
-		/*
-		 * Map the rest of the fdt plus an extra page for adding new
-		 * memory reservations.
-		 */
-		if (!mm_ptable_map(&ptable, (vaddr_t)fdt,
-				   (vaddr_t)fdt + fdt_total_size(fdt),
-				   (paddr_t)fdt, MM_MODE_R | MM_MODE_STAGE1)) {
-			dlog("Unable to map FDT.\n");
-			break;
-		}
-
-		fdt_root_node(&n, fdt);
-		fdt_find_child(&n, "");
-
-		find_memory_range(&n, &mem_start, &mem_size);
-		dlog("Memory range: 0x%x - 0x%x\n", mem_start,
-		     mem_start + mem_size - 1);
-
-		uint64_t begin;
-		uint64_t end;
-
-		if (!find_initrd(&n, &begin, &end)) {
-			break;
-		}
-
-		dlog("Ramdisk range: 0x%x - 0x%x\n", begin, end - 1);
-		mm_ptable_map(&ptable, begin, end, begin,
-			      MM_MODE_R | MM_MODE_STAGE1);
-
-		struct cpio c;
-		cpio_init(&c, (void *)begin, end - begin);
-
-		/* Map the fdt in r/w mode in preparation for extending it. */
-		if (!mm_ptable_map(
-			    &ptable, (vaddr_t)fdt,
-			    (vaddr_t)fdt + fdt_total_size(fdt) + PAGE_SIZE,
-			    (paddr_t)fdt,
-			    MM_MODE_R | MM_MODE_W | MM_MODE_STAGE1)) {
-			dlog("Unable to map FDT in r/w mode.\n");
-			break;
-		}
-		new_mem_size = mem_size;
-		load_secondary(&c, mem_start, &new_mem_size);
-		load_primary(&c, &n);
-
-		/* Patch fdt to reserve memory for secondary VMs. */
-		fdt_add_mem_reservation(fdt, mem_start + new_mem_size,
-					mem_size - new_mem_size);
-
-		/* Unmap FDT. */
-		if (!mm_ptable_unmap(
-			    &ptable, (vaddr_t)fdt,
-			    (vaddr_t)fdt + fdt_total_size(fdt) + PAGE_SIZE,
-			    MM_MODE_STAGE1)) {
-			dlog("Unable to unmap the FDT.\n");
-			break;
-		}
-	} while (0);
-
-	mm_ptable_defrag(&ptable);
-
-	arch_set_vm_mm(&primary_vm.page_table);
+	dlog("Hafnium initialisation completed\n");
 }
 
 /**
@@ -570,8 +105,10 @@
 {
 	struct cpu *c = cpu();
 
-	/* Do global one-time initialization just once. We avoid using atomics
-	 * by only touching the variable from cpu 0. */
+	/*
+	 * Do global one-time initialisation just once. We avoid using atomics
+	 * by only touching the variable from cpu 0.
+	 */
 	static volatile bool inited = false;
 	if (cpu_index(c) == 0 && !inited) {
 		inited = true;
diff --git a/src/memiter.c b/src/memiter.c
new file mode 100644
index 0000000..d3d37d2
--- /dev/null
+++ b/src/memiter.c
@@ -0,0 +1,103 @@
+#include "memiter.h"
+
+#include "std.h"
+
+/**
+ * Initialises the given memory iterator.
+ */
+void memiter_init(struct memiter *it, const void *data, size_t size)
+{
+	it->next = data;
+	it->limit = it->next + size;
+}
+
+/**
+ * Determines if the next character is a whitespace.
+ */
+static bool memiter_isspace(struct memiter *it)
+{
+	switch (*it->next) {
+	case ' ':
+	case '\t':
+	case '\n':
+	case '\r':
+		return true;
+	default:
+		return false;
+	}
+}
+
+/**
+ * Moves iterator to the next non-whitespace character.
+ */
+static void memiter_skip_space(struct memiter *it)
+{
+	while (it->next < it->limit && memiter_isspace(it)) {
+		it->next++;
+	}
+}
+
+/**
+ * Compares the iterator to a null-terminated string.
+ */
+bool memiter_iseq(const struct memiter *it, const char *str)
+{
+	size_t len = strlen(str);
+	if (len != it->limit - it->next) {
+		return false;
+	}
+	return memcmp(it->next, str, len) == 0;
+}
+
+/**
+ * Retrieves the next string that is delimited by whitespaces. The result is
+ * stored in "str".
+ */
+bool memiter_parse_str(struct memiter *it, struct memiter *str)
+{
+	/* Skip all white space and fail if we reach the end of the buffer. */
+	memiter_skip_space(it);
+	if (it->next >= it->limit) {
+		return false;
+	}
+
+	str->next = it->next;
+
+	/* Find the end of the string. */
+	while (it->next < it->limit && !memiter_isspace(it)) {
+		it->next++;
+	}
+
+	str->limit = it->next;
+
+	return true;
+}
+
+/**
+ * Parses the next string that represents a 64-bit number.
+ */
+bool memiter_parse_uint(struct memiter *it, uint64_t *value)
+{
+	uint64_t v = 0;
+
+	/* Skip all white space and fail if we reach the end of the buffer. */
+	memiter_skip_space(it);
+	if (it->next >= it->limit) {
+		return false;
+	}
+
+	/* Fail if it's not a number. */
+	if (*it->next < '0' || *it->next > '9') {
+		return false;
+	}
+
+	/* Parse the number. */
+	do {
+		v = v * 10 + *it->next - '0';
+		it->next++;
+	} while (it->next < it->limit && *it->next >= '0' && *it->next <= '9');
+
+	*value = v;
+
+	return true;
+}
diff --git a/src/mm.c b/src/mm.c
index 6662bae..236e763 100644
--- a/src/mm.c
+++ b/src/mm.c
@@ -14,6 +14,8 @@
 
 /* clang-format on */
 
+static struct mm_ptable ptable;
+
 /**
  * Calculates the size of the address space represented by a page table entry at
  * the given level.
@@ -309,10 +311,11 @@
  * Defragments the given page table by converting page table references to
  * blocks whenever possible.
  */
-void mm_ptable_defrag(struct mm_ptable *t)
+void mm_ptable_defrag(struct mm_ptable *t, int mode)
 {
 	/* TODO: Implement. */
 	(void)t;
+	(void)mode;
 }
 
 /**
@@ -342,3 +345,72 @@
 
 	return true;
 }
+
+/**
+ * Updates the hypervisor page table such that the given virtual address range
+ * is mapped to the given physical address range in the architecture-agnostic
+ * mode provided.
+ */
+bool mm_map(vaddr_t begin, vaddr_t end, paddr_t paddr, int mode)
+{
+	return mm_ptable_map(&ptable, begin, end, paddr, mode | MM_MODE_STAGE1);
+}
+
+/**
+ * Updates the hypervisor table such that the given virtual address range is not
+ * mapped to any physical address.
+ */
+bool mm_unmap(vaddr_t begin, vaddr_t end, int mode)
+{
+	return mm_ptable_unmap(&ptable, begin, end, mode | MM_MODE_STAGE1);
+}
+
+/**
+ * Initialises memory management for the hypervisor itself.
+ */
+bool mm_init(void)
+{
+	extern char text_begin[];
+	extern char text_end[];
+	extern char rodata_begin[];
+	extern char rodata_end[];
+	extern char data_begin[];
+	extern char data_end[];
+
+	dlog("text: 0x%x - 0x%x\n", text_begin, text_end);
+	dlog("rodata: 0x%x - 0x%x\n", rodata_begin, rodata_end);
+	dlog("data: 0x%x - 0x%x\n", data_begin, data_end);
+
+	if (!mm_ptable_init(&ptable, MM_MODE_NOSYNC | MM_MODE_STAGE1)) {
+		dlog("Unable to allocate memory for page table.\n");
+		return false;
+	}
+
+	/* Map page for uart. */
+	/* TODO: We may not want to map this. */
+	mm_ptable_map_page(&ptable, PL011_BASE, PL011_BASE,
+			   MM_MODE_R | MM_MODE_W | MM_MODE_D | MM_MODE_NOSYNC |
+				   MM_MODE_STAGE1);
+
+	/* Map each section. */
+	mm_map((vaddr_t)text_begin, (vaddr_t)text_end, (paddr_t)text_begin,
+	       MM_MODE_X | MM_MODE_NOSYNC);
+
+	mm_map((vaddr_t)rodata_begin, (vaddr_t)rodata_end,
+	       (paddr_t)rodata_begin, MM_MODE_R | MM_MODE_NOSYNC);
+
+	mm_map((vaddr_t)data_begin, (vaddr_t)data_end, (paddr_t)data_begin,
+	       MM_MODE_R | MM_MODE_W | MM_MODE_NOSYNC);
+
+	arch_mm_init((paddr_t)ptable.table);
+
+	return true;
+}
+
+/**
+ * Defragments the hypervisor page table.
+ */
+void mm_defrag(void)
+{
+	mm_ptable_defrag(&ptable, MM_MODE_STAGE1);
+}
diff --git a/src/rules.mk b/src/rules.mk
index 0426fba..7d56def 100644
--- a/src/rules.mk
+++ b/src/rules.mk
@@ -2,8 +2,9 @@
 SRCS += api.c
 SRCS += cpio.c
 SRCS += cpu.c
-SRCS += fdt.c
+SRCS += load.c
 SRCS += main.c
+SRCS += memiter.c
 SRCS += mm.c
 SRCS += std.c
 SRCS += vm.c
@@ -11,3 +12,8 @@
 ifeq ($(DEBUG),1)
   SRCS += dlog.c
 endif
+
+ifeq ($(USE_FDT),1)
+  SRCS += fdt.c
+  SRCS += fdt_handler.c
+endif