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/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;
+}