Support custom boot address for primary VMs

A VM may be preloaded to a different address than the bottom of its
address space. Support providing a custom address to ERET to after
initialization. This is supported only for primary VMs for now.

Change-Id: I81bb43bf11a626d5fe8b5cdea6361b91d9eb5e46
diff --git a/inc/hf/manifest.h b/inc/hf/manifest.h
index 2609973..2c736e72 100644
--- a/inc/hf/manifest.h
+++ b/inc/hf/manifest.h
@@ -21,6 +21,8 @@
 #include "hf/string.h"
 #include "hf/vm.h"
 
+#define MANIFEST_INVALID_ADDRESS UINT64_MAX
+
 /**
  * Holds information about one of the VMs described in the manifest.
  */
@@ -33,6 +35,7 @@
 	union {
 		/* Properties specific to the primary VM. */
 		struct {
+			uint64_t boot_address;
 			struct string ramdisk_filename;
 		} primary;
 		/* Properties specific to secondary VMs. */
diff --git a/src/load.c b/src/load.c
index 2751d7f..d692f86 100644
--- a/src/load.c
+++ b/src/load.c
@@ -119,18 +119,17 @@
 			 const struct memiter *cpio,
 			 const struct boot_params *params, struct mpool *ppool)
 {
-	paddr_t primary_begin = layout_primary_begin();
 	struct vm *vm;
 	struct vm_locked vm_locked;
 	struct vcpu_locked vcpu_locked;
 	size_t i;
 	bool ret;
 
-	/*
-	 * TODO: This bound is currently meaningless but will be addressed when
-	 * the manifest specifies the load address.
-	 */
-	paddr_t primary_end = pa_add(primary_begin, 0x8000000);
+	paddr_t primary_begin =
+		(manifest_vm->primary.boot_address == MANIFEST_INVALID_ADDRESS)
+			? layout_primary_begin()
+			: pa_init(manifest_vm->primary.boot_address);
+	paddr_t primary_end = pa_add(primary_begin, RSIZE_MAX);
 
 	if (!load_kernel(stage1_locked, primary_begin, primary_end, manifest_vm,
 			 cpio, ppool)) {
diff --git a/src/manifest.c b/src/manifest.c
index db01612..bbff8a5 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -127,6 +127,20 @@
 	return MANIFEST_SUCCESS;
 }
 
+static enum manifest_return_code read_optional_uint64(
+	const struct fdt_node *node, const char *property,
+	uint64_t default_value, uint64_t *out)
+{
+	enum manifest_return_code ret;
+
+	ret = read_uint64(node, property, out);
+	if (ret == MANIFEST_ERROR_PROPERTY_NOT_FOUND) {
+		*out = default_value;
+		return MANIFEST_SUCCESS;
+	}
+	return ret;
+}
+
 static enum manifest_return_code read_uint16(const struct fdt_node *node,
 					     const char *property,
 					     uint16_t *out)
@@ -295,6 +309,9 @@
 	if (vm_id == HF_PRIMARY_VM_ID) {
 		TRY(read_optional_string(node, "ramdisk_filename",
 					 &vm->primary.ramdisk_filename));
+		TRY(read_optional_uint64(node, "boot_address",
+					 MANIFEST_INVALID_ADDRESS,
+					 &vm->primary.boot_address));
 	} else {
 		TRY(read_uint64(node, "mem_size", &vm->secondary.mem_size));
 		TRY(read_uint16(node, "vcpu_count", &vm->secondary.vcpu_count));
diff --git a/src/manifest_test.cc b/src/manifest_test.cc
index 097c93e..8273ae4 100644
--- a/src/manifest_test.cc
+++ b/src/manifest_test.cc
@@ -178,6 +178,11 @@
 		return StringProperty("ramdisk_filename", value);
 	}
 
+	ManifestDtBuilder &BootAddress(uint64_t value)
+	{
+		return Integer64Property("boot_address", value);
+	}
+
 	ManifestDtBuilder &VcpuCount(uint32_t value)
 	{
 		return IntegerProperty("vcpu_count", value);
@@ -239,6 +244,16 @@
 		return *this;
 	}
 
+	ManifestDtBuilder &Integer64Property(const std::string_view &name,
+					     uint64_t value)
+	{
+		uint32_t high = value >> 32;
+		uint32_t low = (uint32_t)value;
+		dts_ << name << " = <" << high << " " << low << ">;"
+		     << std::endl;
+		return *this;
+	}
+
 	ManifestDtBuilder &IntegerListProperty(
 		const std::string_view &name,
 		const std::vector<uint32_t> &value)
@@ -451,6 +466,50 @@
 	ASSERT_STREQ(string_data(&m.vm[0].primary.ramdisk_filename), "");
 }
 
+TEST(manifest, no_boot_address_primary)
+{
+	struct manifest m;
+
+	/* clang-format off */
+	std::vector<char> dtb = ManifestDtBuilder()
+		.StartChild("hypervisor")
+			.Compatible()
+			.StartChild("vm1")
+				.DebugName("primary_vm")
+			.EndChild()
+		.EndChild()
+		.Build();
+	/* clang-format on */
+
+	ASSERT_EQ(manifest_from_vec(&m, dtb), MANIFEST_SUCCESS);
+	ASSERT_EQ(m.vm_count, 1);
+	ASSERT_STREQ(string_data(&m.vm[0].debug_name), "primary_vm");
+	ASSERT_EQ(m.vm[0].primary.boot_address, MANIFEST_INVALID_ADDRESS);
+}
+
+TEST(manifest, boot_address_primary)
+{
+	struct manifest m;
+	const uint64_t addr = UINT64_C(0x12345678ABCDEFEF);
+
+	/* clang-format off */
+	std::vector<char> dtb = ManifestDtBuilder()
+		.StartChild("hypervisor")
+			.Compatible()
+			.StartChild("vm1")
+				.DebugName("primary_vm")
+				.BootAddress(addr)
+			.EndChild()
+		.EndChild()
+		.Build();
+	/* clang-format on */
+
+	ASSERT_EQ(manifest_from_vec(&m, dtb), MANIFEST_SUCCESS);
+	ASSERT_EQ(m.vm_count, 1);
+	ASSERT_STREQ(string_data(&m.vm[0].debug_name), "primary_vm");
+	ASSERT_EQ(m.vm[0].primary.boot_address, addr);
+}
+
 static std::vector<char> gen_malformed_boolean_dtb(
 	const std::string_view &value)
 {