device_tree: Add function to get top of memory from a FDT blob

coreboot needs to figure out top of memory to place CBMEM data. On some
non-x86 QEMU virtual machines, this is achieved by probing the RAM space
to find where the VM starts discarding data since it's not backed by
actual RAM. This behaviour seems to have changed on the QEMU side since
then, VMs using the "virt" model have started raising exceptions/errors
instead of silently discarding data (likely [1] for example) which has
previously broken coreboot on these emulation boards.

The qemu-aarch64 and qemu-riscv mainboards are intended for the "virt"
models and had this issue, which were mostly fixed by using exception
handlers in the RAM detection process [2][3]. But on 32-bit RISC-V we
fail to initialize CBMEM if we have 2048 MiB or more of RAM, and on
64-bit RISC-V we had to limit probing to 16383 MiB because it can run
into MMIO regions otherwise.

The qemu-armv7 mainboard code is intended for the "vexpress-a9" model VM
which doesn't appear to suffer from this issue. Still, the issue can be
observed on the ARMv7 "virt" model via a port based on qemu-aarch64.

QEMU docs for ARM and RISC-V "virt" models [4][5] recommend reading the
device tree blob it provides for device information (incl. RAM size).
Implement functions that parse the device tree blob to find described
memory regions and calculate the top of memory in order to use it in
mainboard code as an alternative to probing RAM space. ARM64 code
initializes CBMEM in romstage where malloc isn't available, so take care
to do parsing without unflattening the blob and make the code available
in romstage as well.

[1] https://lore.kernel.org/qemu-devel/1504626814-23124-1-git-send-email-peter.maydell@linaro.org/T/#u
[2] https://review.coreboot.org/c/coreboot/+/34774
[3] https://review.coreboot.org/c/coreboot/+/36486
[4] https://qemu-project.gitlab.io/qemu/system/arm/virt.html
[5] https://qemu-project.gitlab.io/qemu/system/riscv/virt.html

Change-Id: I8bef09bc1bc4e324ebeaa37f78d67d3aa315f52c
Signed-off-by: Alper Nebi Yasak <alpernebiyasak@gmail.com>
Reviewed-on: https://review.coreboot.org/c/coreboot/+/80322
Reviewed-by: Maximilian Brune <maximilian.brune@9elements.com>
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Julius Werner <jwerner@chromium.org>
diff --git a/src/include/device_tree.h b/src/include/device_tree.h
index 6d2d656..bb522bf 100644
--- a/src/include/device_tree.h
+++ b/src/include/device_tree.h
@@ -129,6 +129,12 @@
  */
 int fdt_next_node_name(const void *blob, uint32_t node_offset, const char **name);
 
+ /* Read memory regions from a flat device-tree. */
+size_t fdt_read_memory_regions(const void *blob, struct device_tree_region regions[],
+			       size_t regions_count);
+ /* Find top of memory from a flat device-tree. */
+uint64_t fdt_get_memory_top(const void *blob);
+
 /* Read a flattened device tree into a hierarchical structure which refers to
    the contents of the flattened tree in place. Modifying the flat tree
    invalidates the unflattened one. */
diff --git a/src/lib/Makefile.mk b/src/lib/Makefile.mk
index e22fd08..59e2116 100644
--- a/src/lib/Makefile.mk
+++ b/src/lib/Makefile.mk
@@ -160,10 +160,12 @@
 ramstage-$(CONFIG_GENERIC_UDELAY) += timer.c
 ramstage-y += b64_decode.c
 ramstage-$(CONFIG_ACPI_NHLT) += nhlt.c
-ramstage-$(CONFIG_FLATTENED_DEVICE_TREE) += device_tree.c
 ramstage-$(CONFIG_PAYLOAD_FIT_SUPPORT) += fit.c
 ramstage-$(CONFIG_PAYLOAD_FIT_SUPPORT) += fit_payload.c
 
+romstage-$(CONFIG_FLATTENED_DEVICE_TREE) += device_tree.c
+ramstage-$(CONFIG_FLATTENED_DEVICE_TREE) += device_tree.c
+
 romstage-$(CONFIG_TIMER_QUEUE) += timer_queue.c
 ramstage-$(CONFIG_TIMER_QUEUE) += timer_queue.c
 
diff --git a/src/lib/device_tree.c b/src/lib/device_tree.c
index 4f5cc07..5087d39 100644
--- a/src/lib/device_tree.c
+++ b/src/lib/device_tree.c
@@ -12,9 +12,12 @@
 #include <string.h>
 #include <stddef.h>
 #include <stdlib.h>
+#include <limits.h>
 
 #define FDT_PATH_MAX_DEPTH 10 // should be a good enough upper bound
 #define FDT_PATH_MAX_LEN 128 // should be a good enough upper bound
+#define FDT_MAX_MEMORY_NODES 4 // should be a good enough upper bound
+#define FDT_MAX_MEMORY_REGIONS 16 // should be a good enough upper bound
 
 /*
  * Functions for picking apart flattened trees.
@@ -504,6 +507,96 @@
 }
 
 /*
+ * fdt_read_memory_regions finds memory ranges from a flat device-tree
+ *
+ * @params blob	          address of FDT
+ * @params regions        all regions that are read inside the reg property of
+ *                        memory nodes are saved inside this array
+ * @params regions_count  maximum number of entries that can be saved inside
+ *                        the regions array.
+ *
+ * Returns: Either 0 on error or returns the number of regions put into the regions array.
+ */
+size_t fdt_read_memory_regions(const void *blob,
+			       struct device_tree_region regions[],
+			       size_t regions_count)
+{
+	u32 node, root, addrcp, sizecp;
+	u32 nodes[FDT_MAX_MEMORY_NODES] = {0};
+	size_t region_idx = 0;
+	size_t node_count = 0;
+
+	if (!fdt_is_valid(blob))
+		return 0;
+
+	node = fdt_find_node_by_path(blob, "/memory",  &addrcp, &sizecp);
+	if (node) {
+		region_idx += fdt_read_reg_prop(blob, node, addrcp, sizecp,
+						regions, regions_count);
+		if (region_idx >= regions_count) {
+			printk(BIOS_WARNING, "FDT: Too many memory regions\n");
+			goto out;
+		}
+	}
+
+	root = fdt_find_node_by_path(blob, "/",  &addrcp, &sizecp);
+	node_count = fdt_find_subnodes_by_prefix(blob, root, "memory@",
+						 &addrcp, &sizecp, nodes,
+						 FDT_MAX_MEMORY_NODES);
+	if (node_count >= FDT_MAX_MEMORY_NODES) {
+		printk(BIOS_WARNING, "FDT: Too many memory nodes\n");
+		/* Can still reading the regions for those we got */
+	}
+
+	for (size_t i = 0; i < MIN(node_count, FDT_MAX_MEMORY_NODES); i++) {
+		region_idx += fdt_read_reg_prop(blob, nodes[i], addrcp, sizecp,
+						&regions[region_idx],
+						regions_count - region_idx);
+		if (region_idx >= regions_count) {
+			printk(BIOS_WARNING, "FDT: Too many memory regions\n");
+			goto out;
+		}
+	}
+
+out:
+	for (size_t i = 0; i < MIN(region_idx, regions_count); i++) {
+		printk(BIOS_DEBUG, "FDT: Memory region [%#llx - %#llx]\n",
+		       regions[i].addr, regions[i].addr + regions[i].size);
+	}
+
+	return region_idx;
+}
+
+/*
+ * fdt_get_memory_top finds top of memory from a flat device-tree
+ *
+ * @params blob	          address of FDT
+ *
+ * Returns: Either 0 on error or returns the maximum memory address
+ */
+uint64_t fdt_get_memory_top(const void *blob)
+{
+	struct device_tree_region regions[FDT_MAX_MEMORY_REGIONS] = {0};
+	uint64_t top = 0;
+	uint64_t total = 0;
+	size_t count;
+
+	if (!fdt_is_valid(blob))
+		return 0;
+
+	count = fdt_read_memory_regions(blob, regions, FDT_MAX_MEMORY_REGIONS);
+	for (size_t i = 0; i < MIN(count, FDT_MAX_MEMORY_REGIONS); i++) {
+		top = MAX(top, regions[i].addr + regions[i].size);
+		total += regions[i].size;
+	}
+
+	printk(BIOS_DEBUG, "FDT: Found %u MiB of RAM\n",
+	       (uint32_t)(total / MiB));
+
+	return top;
+}
+
+/*
  * Functions to turn a flattened tree into an unflattened one.
  */