efi_loader: implement EFI_DT_FIXUP_PROTOCOL

A boot manager like GRUB can use the protocol to

* apply U-Boot's fix-ups to the a device-tree
* let U-Boot make memory reservations according to the device-tree
* install the device-tree as a configuration table

Signed-off-by: Heinrich Schuchardt <xypron.glpk@gmx.de>
diff --git a/lib/efi_loader/efi_dt_fixup.c b/lib/efi_loader/efi_dt_fixup.c
new file mode 100644
index 0000000..5f0ae5c
--- /dev/null
+++ b/lib/efi_loader/efi_dt_fixup.c
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * EFI_DT_FIXUP_PROTOCOL
+ *
+ * Copyright (c) 2020 Heinrich Schuchardt
+ */
+
+#include <common.h>
+#include <efi_dt_fixup.h>
+#include <efi_loader.h>
+#include <mapmem.h>
+
+static efi_status_t EFIAPI efi_dt_fixup(struct efi_dt_fixup_protocol *this,
+					void *dtb,
+					efi_uintn_t *buffer_size,
+					u32 flags);
+
+struct efi_dt_fixup_protocol efi_dt_fixup_prot = {
+	.revision = EFI_DT_FIXUP_PROTOCOL_REVISION,
+	.fixup = efi_dt_fixup
+};
+
+const efi_guid_t efi_guid_dt_fixup_protocol = EFI_DT_FIXUP_PROTOCOL_GUID;
+
+/**
+ * efi_reserve_memory() - add reserved memory to memory map
+ *
+ * @addr:	start address of the reserved memory range
+ * @size:	size of the reserved memory range
+ * @nomap:	indicates that the memory range shall not be accessed by the
+ *		UEFI payload
+ */
+static void efi_reserve_memory(u64 addr, u64 size, bool nomap)
+{
+	int type;
+	efi_uintn_t ret;
+
+	/* Convert from sandbox address space. */
+	addr = (uintptr_t)map_sysmem(addr, 0);
+
+	if (nomap)
+		type = EFI_RESERVED_MEMORY_TYPE;
+	else
+		type = EFI_BOOT_SERVICES_DATA;
+
+	ret = efi_add_memory_map(addr, size, type);
+	if (ret != EFI_SUCCESS)
+		log_err("Reserved memory mapping failed addr %llx size %llx\n",
+			addr, size);
+}
+
+/**
+ * efi_carve_out_dt_rsv() - Carve out DT reserved memory ranges
+ *
+ * The mem_rsv entries of the FDT are added to the memory map. Any failures are
+ * ignored because this is not critical and we would rather continue to try to
+ * boot.
+ *
+ * @fdt: Pointer to device tree
+ */
+void efi_carve_out_dt_rsv(void *fdt)
+{
+	int nr_rsv, i;
+	u64 addr, size;
+	int nodeoffset, subnode;
+
+	nr_rsv = fdt_num_mem_rsv(fdt);
+
+	/* Look for an existing entry and add it to the efi mem map. */
+	for (i = 0; i < nr_rsv; i++) {
+		if (fdt_get_mem_rsv(fdt, i, &addr, &size) != 0)
+			continue;
+		efi_reserve_memory(addr, size, false);
+	}
+
+	/* process reserved-memory */
+	nodeoffset = fdt_subnode_offset(fdt, 0, "reserved-memory");
+	if (nodeoffset >= 0) {
+		subnode = fdt_first_subnode(fdt, nodeoffset);
+		while (subnode >= 0) {
+			fdt_addr_t fdt_addr;
+			fdt_size_t fdt_size;
+
+			/* check if this subnode has a reg property */
+			fdt_addr = fdtdec_get_addr_size_auto_parent(
+						fdt, nodeoffset, subnode,
+						"reg", 0, &fdt_size, false);
+			/*
+			 * The /reserved-memory node may have children with
+			 * a size instead of a reg property.
+			 */
+			if (fdt_addr != FDT_ADDR_T_NONE &&
+			    fdtdec_get_is_enabled(fdt, subnode)) {
+				bool nomap;
+
+				nomap = !!fdt_getprop(fdt, subnode, "no-map",
+						      NULL);
+				efi_reserve_memory(fdt_addr, fdt_size, nomap);
+			}
+			subnode = fdt_next_subnode(fdt, subnode);
+		}
+	}
+}
+
+static efi_status_t EFIAPI efi_dt_fixup(struct efi_dt_fixup_protocol *this,
+					void *dtb,
+					efi_uintn_t *buffer_size,
+					u32 flags)
+{
+	efi_status_t ret;
+	size_t required_size;
+	bootm_headers_t img = { 0 };
+
+	EFI_ENTRY("%p, %p, %p, %d", this, dtb, buffer_size, flags);
+
+	if (this != &efi_dt_fixup_prot || !dtb || !buffer_size ||
+	    !flags || (flags & ~EFI_DT_ALL)) {
+		ret = EFI_INVALID_PARAMETER;
+		goto out;
+	}
+	if (fdt_check_header(dtb)) {
+		ret = EFI_INVALID_PARAMETER;
+		goto out;
+	}
+	if (flags & EFI_DT_APPLY_FIXUPS) {
+		required_size = fdt_off_dt_strings(dtb) +
+				fdt_size_dt_strings(dtb) +
+				0x3000;
+	} else {
+		required_size = fdt_totalsize(dtb);
+	}
+	if (required_size > *buffer_size) {
+		*buffer_size = required_size;
+		ret = EFI_BUFFER_TOO_SMALL;
+		goto out;
+	}
+	fdt_set_totalsize(dtb, *buffer_size);
+
+	if (flags & EFI_DT_APPLY_FIXUPS) {
+		if (image_setup_libfdt(&img, dtb, 0, NULL)) {
+			log_err("failed to process device tree\n");
+			ret = EFI_INVALID_PARAMETER;
+			goto out;
+		}
+	}
+	if (flags & EFI_DT_RESERVE_MEMORY)
+		efi_carve_out_dt_rsv(dtb);
+
+	if (EFI_DT_INSTALL_TABLE) {
+		ret = efi_install_configuration_table(&efi_guid_fdt, dtb);
+		if (ret != EFI_SUCCESS) {
+			log_err("ERROR: failed to install device tree\n");
+			goto out;
+		}
+	}
+
+	ret = EFI_SUCCESS;
+out:
+	return EFI_EXIT(ret);
+}