Support multiple memory ranges.

Bug: 114733240
Change-Id: Ifbad8688e0a0e17a4732b637711919fc18d68f7a
diff --git a/inc/hf/boot_params.h b/inc/hf/boot_params.h
index f338e84..0a6ab8c 100644
--- a/inc/hf/boot_params.h
+++ b/inc/hf/boot_params.h
@@ -4,17 +4,24 @@
 
 #include "hf/mm.h"
 
+#define MAX_MEM_RANGES 20
+
+struct mem_range {
+	paddr_t begin;
+	paddr_t end;
+};
+
 struct boot_params {
-	paddr_t mem_begin;
-	paddr_t mem_end;
+	struct mem_range mem_ranges[MAX_MEM_RANGES];
+	size_t mem_ranges_count;
 	paddr_t initrd_begin;
 	paddr_t initrd_end;
 	size_t kernel_arg;
 };
 
 struct boot_params_update {
-	paddr_t reserved_begin;
-	paddr_t reserved_end;
+	struct mem_range reserved_ranges[MAX_MEM_RANGES];
+	size_t reserved_ranges_count;
 	paddr_t initrd_begin;
 	paddr_t initrd_end;
 };
diff --git a/inc/hf/load.h b/inc/hf/load.h
index 5e4ead3..d1f239c 100644
--- a/inc/hf/load.h
+++ b/inc/hf/load.h
@@ -3,11 +3,13 @@
 #include <stddef.h>
 #include <stdint.h>
 
+#include "hf/boot_params.h"
 #include "hf/cpio.h"
 #include "hf/memiter.h"
 #include "hf/mm.h"
 
 bool load_primary(const struct memiter *cpio, size_t kernel_arg,
 		  struct memiter *initrd);
-bool load_secondary(const struct memiter *cpio, paddr_t mem_begin,
-		    paddr_t *mem_end);
+bool load_secondary(const struct memiter *cpio,
+		    const struct boot_params *params,
+		    struct boot_params_update *update);
diff --git a/src/fdt_handler.c b/src/fdt_handler.c
index 4a93e66..91a17d8 100644
--- a/src/fdt_handler.c
+++ b/src/fdt_handler.c
@@ -108,14 +108,15 @@
 	return true;
 }
 
-static void find_memory_range(const struct fdt_node *root,
-			      struct boot_params *p)
+static void find_memory_ranges(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;
+	size_t mem_range_index = 0;
 
 	/* Get the sizes of memory range addresses and sizes. */
 	if (fdt_read_number(&n, "#address-cells", &address_size)) {
@@ -153,16 +154,24 @@
 			size_t len =
 				convert_number(data + address_size, size_size);
 
-			if (len > pa_addr(p->mem_end) - pa_addr(p->mem_begin)) {
-				/* Remember the largest range we've found. */
-				p->mem_begin = pa_init(addr);
-				p->mem_end = pa_init(addr + len);
+			if (mem_range_index < MAX_MEM_RANGES) {
+				p->mem_ranges[mem_range_index].begin =
+					pa_init(addr);
+				p->mem_ranges[mem_range_index].end =
+					pa_init(addr + len);
+				++mem_range_index;
+			} else {
+				dlog("Found memory range %u in FDT but only "
+				     "%u supported, ignoring additional range "
+				     "of size %u.\n",
+				     mem_range_index, MAX_MEM_RANGES, len);
 			}
 
 			size -= entry_size;
 			data += entry_size;
 		}
 	} while (fdt_next_sibling(&n, &name));
+	p->mem_ranges_count = mem_range_index;
 
 	/* TODO: Check for "reserved-memory" nodes. */
 }
@@ -199,9 +208,8 @@
 		goto out_unmap_fdt;
 	}
 
-	p->mem_begin = pa_init(0);
-	p->mem_end = pa_init(0);
-	find_memory_range(&n, p);
+	p->mem_ranges_count = 0;
+	find_memory_ranges(&n, p);
 
 	if (!find_initrd(&n, p)) {
 		goto out_unmap_fdt;
@@ -224,6 +232,7 @@
 	struct fdt_header *fdt;
 	struct fdt_node n;
 	bool ret = false;
+	size_t i;
 
 	/* Map the fdt header in. */
 	fdt = mm_identity_map(fdt_addr, pa_add(fdt_addr, fdt_header_size()),
@@ -277,9 +286,12 @@
 	}
 
 	/* Patch fdt to reserve memory for secondary VMs. */
-	fdt_add_mem_reservation(
-		fdt, pa_addr(p->reserved_begin),
-		pa_addr(p->reserved_end) - pa_addr(p->reserved_begin));
+	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;
 
diff --git a/src/load.c b/src/load.c
index 6ed2d1b..0e45aba 100644
--- a/src/load.c
+++ b/src/load.c
@@ -1,8 +1,10 @@
 #include "hf/load.h"
 
+#include <assert.h>
 #include <stdbool.h>
 
 #include "hf/api.h"
+#include "hf/boot_params.h"
 #include "hf/dlog.h"
 #include "hf/memiter.h"
 #include "hf/mm.h"
@@ -146,26 +148,113 @@
 }
 
 /**
- * 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.
+ * Try to find a memory range of the given size within the given ranges, and
+ * remove it from them. Return true on success, or false if no large enough
+ * contiguous range is found.
  */
-bool load_secondary(const struct memiter *cpio, paddr_t mem_begin,
-		    paddr_t *mem_end)
+bool carve_out_mem_range(struct mem_range *mem_ranges, size_t mem_ranges_count,
+			 uint64_t size_to_find, paddr_t *found_begin,
+			 paddr_t *found_end)
+{
+	size_t i;
+
+	/* TODO(b/116191358): Consider being cleverer about how we pack VMs
+	 * together, with a non-greedy algorithm. */
+	for (i = 0; i < mem_ranges_count; ++i) {
+		if (size_to_find <=
+		    pa_addr(mem_ranges[i].end) - pa_addr(mem_ranges[i].begin)) {
+			/* This range is big enough, take some of it from the
+			 * end and reduce its size accordingly. */
+			*found_end = mem_ranges[i].end;
+			*found_begin = pa_init(pa_addr(mem_ranges[i].end) -
+					       size_to_find);
+			mem_ranges[i].end = *found_begin;
+			return true;
+		}
+	}
+	return false;
+}
+
+/**
+ * Given arrays of memory ranges before and after memory was removed for
+ * secondary VMs, add the difference to the reserved ranges of the given update.
+ * Return true on success, or false if there would be more than MAX_MEM_RANGES
+ * reserved ranges after adding the new ones.
+ * `before` and `after` must be arrays of exactly `mem_ranges_count` elements.
+ */
+bool update_reserved_ranges(struct boot_params_update *update,
+			    const struct mem_range *before,
+			    const struct mem_range *after,
+			    size_t mem_ranges_count)
+{
+	size_t i;
+
+	for (i = 0; i < mem_ranges_count; ++i) {
+		if (pa_addr(after[i].begin) > pa_addr(before[i].begin)) {
+			if (update->reserved_ranges_count >= MAX_MEM_RANGES) {
+				dlog("Too many reserved ranges after loading "
+				     "secondary VMs.\n");
+				return false;
+			}
+			update->reserved_ranges[update->reserved_ranges_count]
+				.begin = before[i].begin;
+			update->reserved_ranges[update->reserved_ranges_count]
+				.end = after[i].begin;
+			update->reserved_ranges_count++;
+		}
+		if (pa_addr(after[i].end) < pa_addr(before[i].end)) {
+			if (update->reserved_ranges_count >= MAX_MEM_RANGES) {
+				dlog("Too many reserved ranges after loading "
+				     "secondary VMs.\n");
+				return false;
+			}
+			update->reserved_ranges[update->reserved_ranges_count]
+				.begin = after[i].end;
+			update->reserved_ranges[update->reserved_ranges_count]
+				.end = before[i].end;
+			update->reserved_ranges_count++;
+		}
+	}
+
+	return true;
+}
+
+/**
+ * Loads all secondary VMs into the memory ranges from the given params.
+ * Memory reserved for the VMs is added to the `reserved_ranges` of `update`.
+ */
+bool load_secondary(const struct memiter *cpio,
+		    const struct boot_params *params,
+		    struct boot_params_update *update)
 {
 	struct memiter it;
 	struct memiter str;
 	uint64_t mem;
 	uint64_t cpu;
 	uint32_t count;
+	struct mem_range mem_ranges_available[MAX_MEM_RANGES];
+	size_t i;
+
+	static_assert(
+		sizeof(mem_ranges_available) == sizeof(params->mem_ranges),
+		"mem_range arrays must be the same size for memcpy.");
+	static_assert(sizeof(mem_ranges_available) < 500,
+		      "This will use too much stack, either make "
+		      "MAX_MEM_RANGES smaller or change this.");
+	memcpy(mem_ranges_available, params->mem_ranges,
+	       sizeof(mem_ranges_available));
 
 	if (!find_file(cpio, "vms.txt", &it)) {
 		dlog("vms.txt is missing\n");
 		return true;
 	}
 
-	/* Round the last address down to the page size. */
-	*mem_end = pa_init(pa_addr(*mem_end) & ~(PAGE_SIZE - 1));
+	/* Round the last addresses down to the page size. */
+	for (i = 0; i < params->mem_ranges_count; ++i) {
+		mem_ranges_available[i].end =
+			pa_init(pa_addr(mem_ranges_available[i].end) &
+				~(PAGE_SIZE - 1));
+	}
 
 	for (count = 0;
 	     memiter_parse_uint(&it, &mem) && memiter_parse_uint(&it, &cpu) &&
@@ -183,11 +272,6 @@
 
 		/* Round up to page size. */
 		mem = (mem + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
-		if (mem > pa_addr(*mem_end) - pa_addr(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 "
@@ -196,11 +280,15 @@
 			continue;
 		}
 
-		secondary_mem_end = *mem_end;
-		*mem_end = pa_init(pa_addr(*mem_end) - mem);
-		secondary_mem_begin = *mem_end;
+		if (!carve_out_mem_range(
+			    mem_ranges_available, params->mem_ranges_count, mem,
+			    &secondary_mem_begin, &secondary_mem_end)) {
+			dlog("Not enough memory for vm %u (%u bytes)\n", count,
+			     mem);
+			continue;
+		}
 
-		if (!copy_to_unmapped(*mem_end, kernel.next,
+		if (!copy_to_unmapped(secondary_mem_begin, kernel.next,
 				      kernel.limit - kernel.next)) {
 			dlog("Unable to copy kernel for vm %u\n", count);
 			continue;
@@ -237,12 +325,19 @@
 		}
 
 		dlog("Loaded VM%u with %u vcpus, entry at 0x%x\n", count, cpu,
-		     pa_addr(*mem_end));
+		     pa_addr(secondary_mem_begin));
 
 		vm_start_vcpu(&secondary_vm[count], 0, secondary_entry, 0);
 	}
 
 	secondary_vm_count = count;
 
-	return true;
+	/* Add newly reserved areas to update params by looking at the
+	 * difference between the available ranges from the original params and
+	 * the updated mem_ranges_available. We assume that the number and order
+	 * of available ranges is the same, i.e. we don't remove any ranges
+	 * above only make them smaller. */
+	return update_reserved_ranges(update, params->mem_ranges,
+				      mem_ranges_available,
+				      params->mem_ranges_count);
 }
diff --git a/src/main.c b/src/main.c
index 7326cb0..3b9018d 100644
--- a/src/main.c
+++ b/src/main.c
@@ -45,10 +45,10 @@
 {
 	struct boot_params params;
 	struct boot_params_update update;
-	paddr_t new_mem_end;
 	struct memiter primary_initrd;
 	struct memiter cpio;
 	void *initrd;
+	size_t i;
 
 	dlog("Initialising hafnium\n");
 
@@ -63,8 +63,12 @@
 		panic("unable to retrieve boot params");
 	}
 
-	dlog("Memory range:  0x%x - 0x%x\n", pa_addr(params.mem_begin),
-	     pa_addr(params.mem_end) - 1);
+	for (i = 0; i < params.mem_ranges_count; ++i) {
+		dlog("Memory range:  0x%x - 0x%x\n",
+		     pa_addr(params.mem_ranges[i].begin),
+		     pa_addr(params.mem_ranges[i].end) - 1);
+	}
+
 	dlog("Ramdisk range: 0x%x - 0x%x\n", pa_addr(params.initrd_begin),
 	     pa_addr(params.initrd_end) - 1);
 
@@ -79,20 +83,20 @@
 		     pa_addr(params.initrd_end) - pa_addr(params.initrd_begin));
 
 	/* Load all VMs. */
-	new_mem_end = params.mem_end;
 	if (!load_primary(&cpio, params.kernel_arg, &primary_initrd)) {
 		panic("unable to load primary VM");
 	}
 
-	if (!load_secondary(&cpio, params.mem_begin, &new_mem_end)) {
+	update.initrd_begin = pa_from_va(va_from_ptr(primary_initrd.next));
+	update.initrd_end = pa_from_va(va_from_ptr(primary_initrd.limit));
+	/* load_secondary will add regions assigned to the secondary VMs from
+	 * mem_ranges to reserved_ranges. */
+	update.reserved_ranges_count = 0;
+	if (!load_secondary(&cpio, &params, &update)) {
 		panic("unable to load secondary VMs");
 	}
 
-	/* Prepare to run by updating bootparams as seens by primary VM. */
-	update.initrd_begin = pa_from_va(va_from_ptr(primary_initrd.next));
-	update.initrd_end = pa_from_va(va_from_ptr(primary_initrd.limit));
-	update.reserved_begin = new_mem_end;
-	update.reserved_end = params.mem_end;
+	/* Prepare to run by updating bootparams as seen by primary VM. */
 	if (!plat_update_boot_params(&update)) {
 		panic("plat_update_boot_params failed");
 	}