Ramdisk in the manifest, simplify initrd generation

We currently support loading a ramdisk for the primary. In fact, all
primaries are required to have one called 'initrd.img'. This makes
the ramdisk optional and specifies its filename in the manifest.

With that, generate_initrd.py loses the last remaining hardcoded
assumption about the structure of the Hafnium ramdisk. Refactor the
script and the build so that generate_initrd.py simply accepts a list of
(name in ramdisk, path on host) pairs.

Bug: 117551352
Change-Id: Iff2d70205940a1740c473d1cac34d5a05d6d6791
diff --git a/build/image/generate_initrd.py b/build/image/generate_initrd.py
index 4d8f281..aba5ec1 100644
--- a/build/image/generate_initrd.py
+++ b/build/image/generate_initrd.py
@@ -26,41 +26,27 @@
 import subprocess
 import sys
 
-
 def Main():
     parser = argparse.ArgumentParser()
-    parser.add_argument("--primary_name", required=True)
-    parser.add_argument("--primary_vm", required=True)
-    parser.add_argument("--primary_vm_initrd")
-    parser.add_argument(
-        "--secondary_vm",
-        action="append",
-        nargs=2,
-        metavar=("NAME", "IMAGE"))
-    parser.add_argument("--staging", required=True)
-    parser.add_argument("--output", required=True)
+    parser.add_argument("-f", "--file",
+        action="append", nargs=2,
+        metavar=("NAME", "PATH"),
+        help="File at host location PATH to be added to the RAM disk as NAME")
+    parser.add_argument("-s", "--staging", required=True)
+    parser.add_argument("-o", "--output", required=True)
     args = parser.parse_args()
-    staged_files = [args.primary_name, "initrd.img"]
 
     # Create staging folder if needed.
     if not os.path.isdir(args.staging):
         os.makedirs(args.staging)
 
-    # Prepare the primary VM image.
-    shutil.copyfile(args.primary_vm,
-                    os.path.join(args.staging, args.primary_name))
-    # Prepare the primary VM's initrd.
-    if args.primary_vm_initrd:
-        shutil.copyfile(args.primary_vm_initrd,
-                        os.path.join(args.staging, "initrd.img"))
-    else:
-        open(os.path.join(args.staging, "initrd.img"), "w").close()
-    # Prepare the secondary VMs.
-    if args.secondary_vm:
-        for vm in args.secondary_vm:
-            (vm_name, vm_image) = vm
-            staged_files.append(vm_name)
-            shutil.copy(vm_image, os.path.join(args.staging, vm_name))
+    # Copy files into the staging folder.
+    staged_files = []
+    for name, path in args.file:
+        shutil.copyfile(path, os.path.join(args.staging, name))
+        assert name not in staged_files
+        staged_files.append(name)
+
     # Package files into an initial RAM disk.
     with open(args.output, "w") as initrd:
         # Move into the staging directory so the file names taken by cpio don't
@@ -74,6 +60,5 @@
         cpio.communicate(input="\n".join(staged_files).encode("utf-8"))
     return 0
 
-
 if __name__ == "__main__":
     sys.exit(Main())
diff --git a/build/image/image.gni b/build/image/image.gni
index 2c29f4a..ebde0c2 100644
--- a/build/image/image.gni
+++ b/build/image/image.gni
@@ -205,9 +205,8 @@
       invoker.primary_vm,
     ]
     args = [
-      "--primary_name",
+      "--file",
       invoker.primary_name,
-      "--primary_vm",
       rebase_path(primary_vm_image),
       "--staging",
       rebase_path(initrd_staging),
@@ -219,7 +218,8 @@
       deps += [ invoker.primary_initrd ]
       primary_initrd_outputs = get_target_outputs(invoker.primary_initrd)
       args += [
-        "--primary_vm_initrd",
+        "--file",
+        "initrd.img",
         rebase_path(primary_initrd_outputs[0]),
       ]
     }
@@ -233,7 +233,7 @@
       foreach(vm, invoker.secondary_vms) {
         deps += [ vm[1] ]
         args += [
-          "--secondary_vm",
+          "--file",
           vm[0],
           rebase_path(get_label_info(vm[1], "target_out_dir") + "/" +
                       get_label_info(vm[1], "name") + ".bin"),
diff --git a/docs/Manifest.md b/docs/Manifest.md
index ff1cf0e..4f69076 100644
--- a/docs/Manifest.md
+++ b/docs/Manifest.md
@@ -13,6 +13,7 @@
 		vm1 {
 			debug_name = "name";
 			kernel_filename = "vmlinuz";
+			ramdisk_filename = "initrd.img";
 		};
 
 		vm2 {
@@ -37,7 +38,8 @@
 (matches filename in Hafnium's [ramdisk](HafniumRamDisk.md)). The second has 2MB
 of memory, 4 CPUs and, by omitting the `kernel_filename` property, a kernel
 preloaded into memory. The primary VM is given all remaining memory, the same
-number of CPUs as the hardware and a kernel image called `vmlinuz`.
+number of CPUs as the hardware, a kernel image called `vmlinuz` and a ramdisk
+`initrd.img`. Secondaries cannot have a ramdisk.
 
 ```
 /dts-v1/;
@@ -48,6 +50,7 @@
 		vm1 {
 			debug_name = "primary VM";
 			kernel_filename = "vmlinuz";
+			ramdisk_filename = "initrd.img";
 		};
 
 		vm2 {
diff --git a/inc/hf/boot_flow.h b/inc/hf/boot_flow.h
index 3ecf25f..3307fb0 100644
--- a/inc/hf/boot_flow.h
+++ b/inc/hf/boot_flow.h
@@ -26,5 +26,6 @@
 		    struct mpool *ppool);
 
 bool boot_flow_update(struct mm_stage1_locked stage1_locked,
+		      const struct manifest *manifest,
 		      struct boot_params_update *p, struct memiter *cpio,
 		      struct mpool *ppool);
diff --git a/inc/hf/cpio.h b/inc/hf/cpio.h
index d67f8d4..aebe1d5 100644
--- a/inc/hf/cpio.h
+++ b/inc/hf/cpio.h
@@ -17,7 +17,6 @@
 #pragma once
 
 #include <stdbool.h>
-#include <stddef.h>
 
 #include "hf/memiter.h"
 #include "hf/string.h"
diff --git a/inc/hf/manifest.h b/inc/hf/manifest.h
index 23689d9..3e9f76f 100644
--- a/inc/hf/manifest.h
+++ b/inc/hf/manifest.h
@@ -29,11 +29,17 @@
 	struct string debug_name;
 	struct string kernel_filename;
 
-	/* Properties specific to secondary VMs. */
-	struct {
-		uint64_t mem_size;
-		spci_vcpu_count_t vcpu_count;
-	} secondary;
+	union {
+		/* Properties specific to the primary VM. */
+		struct {
+			struct string ramdisk_filename;
+		} primary;
+		/* Properties specific to secondary VMs. */
+		struct {
+			uint64_t mem_size;
+			spci_vcpu_count_t vcpu_count;
+		} secondary;
+	};
 };
 
 /**
diff --git a/inc/hf/plat/boot_flow.h b/inc/hf/plat/boot_flow.h
index 5a6b02b..b5d8145 100644
--- a/inc/hf/plat/boot_flow.h
+++ b/inc/hf/plat/boot_flow.h
@@ -19,6 +19,7 @@
 #include "hf/addr.h"
 #include "hf/boot_params.h"
 #include "hf/fdt.h"
+#include "hf/manifest.h"
 #include "hf/memiter.h"
 #include "hf/mm.h"
 
@@ -27,5 +28,6 @@
 bool plat_boot_flow_get_initrd_range(const struct fdt_node *fdt_root,
 				     paddr_t *begin, paddr_t *end);
 bool plat_boot_flow_update(struct mm_stage1_locked stage1_locked,
+			   const struct manifest *manifest,
 			   struct boot_params_update *p, struct memiter *cpio,
 			   struct mpool *ppool);
diff --git a/inc/vmapi/hf/types.h b/inc/vmapi/hf/types.h
index f54d7d7..1612099 100644
--- a/inc/vmapi/hf/types.h
+++ b/inc/vmapi/hf/types.h
@@ -40,12 +40,13 @@
 #define HF_VM_ID_OFFSET 1
 
 /**
- * The ID of the primary VM, which is responsible for scheduling.
+ * The index and ID of the primary VM, which is responsible for scheduling.
  *
- * Starts at the offset because ID 0 is reserved for the hypervisor itself.
- * All other VM IDs come after the primary.
+ * These are not equal because ID 0 is reserved for the hypervisor itself.
+ * Primary VM therefore gets ID 1 and all other VMs come after that.
  */
-#define HF_PRIMARY_VM_ID HF_VM_ID_OFFSET
+#define HF_PRIMARY_VM_INDEX 0
+#define HF_PRIMARY_VM_ID (HF_VM_ID_OFFSET + HF_PRIMARY_VM_INDEX)
 
 /** Sleep value for an indefinite period of time. */
 #define HF_SLEEP_INDEFINITE 0xffffffffffffff
diff --git a/src/boot_flow/android.c b/src/boot_flow/android.c
index cf7246e..0221674 100644
--- a/src/boot_flow/android.c
+++ b/src/boot_flow/android.c
@@ -51,10 +51,12 @@
  * Android boot flow does not change based on the updates.
  */
 bool plat_boot_flow_update(struct mm_stage1_locked stage1_locked,
+			   const struct manifest *manifest,
 			   struct boot_params_update *p, struct memiter *cpio,
 			   struct mpool *ppool)
 {
 	(void)stage1_locked;
+	(void)manifest;
 	(void)p;
 	(void)cpio;
 	(void)ppool;
diff --git a/src/boot_flow/common.c b/src/boot_flow/common.c
index 586d3d2..b295824 100644
--- a/src/boot_flow/common.c
+++ b/src/boot_flow/common.c
@@ -87,8 +87,9 @@
  * Takes action on any updates that were generated.
  */
 bool boot_flow_update(struct mm_stage1_locked stage1_locked,
+		      const struct manifest *manifest,
 		      struct boot_params_update *p, struct memiter *cpio,
 		      struct mpool *ppool)
 {
-	return plat_boot_flow_update(stage1_locked, p, cpio, ppool);
+	return plat_boot_flow_update(stage1_locked, manifest, p, cpio, ppool);
 }
diff --git a/src/boot_flow/linux.c b/src/boot_flow/linux.c
index 9d4d886..e1e257c 100644
--- a/src/boot_flow/linux.c
+++ b/src/boot_flow/linux.c
@@ -52,14 +52,19 @@
 }
 
 bool plat_boot_flow_update(struct mm_stage1_locked stage1_locked,
+			   const struct manifest *manifest,
 			   struct boot_params_update *update,
 			   struct memiter *cpio, struct mpool *ppool)
 {
-	static struct string filename = STRING_INIT("initrd.img");
 	struct memiter primary_initrd;
+	const struct string *filename =
+		&manifest->vm[HF_PRIMARY_VM_INDEX].primary.ramdisk_filename;
 
-	if (!cpio_get_file(cpio, &filename, &primary_initrd)) {
-		dlog("Unable to find initrd.img\n");
+	if (string_is_empty(filename)) {
+		memiter_init(&primary_initrd, NULL, 0);
+	} else if (!cpio_get_file(cpio, filename, &primary_initrd)) {
+		dlog("Unable to find primary initrd \"%s\".\n",
+		     string_data(filename));
 		return false;
 	}
 
diff --git a/src/init.c b/src/init.c
index 4948697..6d9e8ad 100644
--- a/src/init.c
+++ b/src/init.c
@@ -116,7 +116,8 @@
 		panic("Unable to load VMs.");
 	}
 
-	if (!boot_flow_update(mm_stage1_locked, &update, &cpio, &ppool)) {
+	if (!boot_flow_update(mm_stage1_locked, &manifest, &update, &cpio,
+			      &ppool)) {
 		panic("Unable to update boot flow.");
 	}
 
diff --git a/src/load.c b/src/load.c
index 7138824..01640f8 100644
--- a/src/load.c
+++ b/src/load.c
@@ -100,8 +100,8 @@
 			 struct mpool *ppool)
 {
 	paddr_t primary_begin = layout_primary_begin();
-	const struct manifest_vm *manifest_vm =
-		&manifest->vm[HF_PRIMARY_VM_ID - HF_VM_ID_OFFSET];
+	struct vm *vm;
+	struct vcpu_locked vcpu_locked;
 
 	/*
 	 * TODO: This bound is currently meaningless but will be addressed when
@@ -109,46 +109,41 @@
 	 */
 	paddr_t primary_end = pa_add(primary_begin, 0x8000000);
 
-	if (!load_kernel(stage1_locked, primary_begin, primary_end, manifest_vm,
-			 cpio, ppool)) {
+	if (!load_kernel(stage1_locked, primary_begin, primary_end,
+			 &manifest->vm[HF_PRIMARY_VM_INDEX], cpio, ppool)) {
 		dlog("Unable to load primary kernel.");
 		return false;
 	}
 
-	{
-		struct vm *vm;
-		struct vcpu_locked vcpu_locked;
-
-		if (!vm_init(MAX_CPUS, ppool, &vm)) {
-			dlog("Unable to initialise primary vm\n");
-			return false;
-		}
-
-		if (vm->id != HF_PRIMARY_VM_ID) {
-			dlog("Primary vm was not given correct id\n");
-			return false;
-		}
-
-		/* Map the 1TB of memory. */
-		/* TODO: We should do a whitelist rather than a blacklist. */
-		if (!mm_vm_identity_map(
-			    &vm->ptable, pa_init(0),
-			    pa_init(UINT64_C(1024) * 1024 * 1024 * 1024),
-			    MM_MODE_R | MM_MODE_W | MM_MODE_X, NULL, ppool)) {
-			dlog("Unable to initialise memory for primary vm\n");
-			return false;
-		}
-
-		if (!mm_vm_unmap_hypervisor(&vm->ptable, ppool)) {
-			dlog("Unable to unmap hypervisor from primary vm\n");
-			return false;
-		}
-
-		vcpu_locked = vcpu_lock(vm_get_vcpu(vm, 0));
-		vcpu_on(vcpu_locked, ipa_from_pa(primary_begin), kernel_arg);
-		vcpu_unlock(&vcpu_locked);
+	if (!vm_init(MAX_CPUS, ppool, &vm)) {
+		dlog("Unable to initialise primary vm\n");
+		return false;
 	}
 
+	if (vm->id != HF_PRIMARY_VM_ID) {
+		dlog("Primary vm was not given correct id\n");
+		return false;
+	}
+
+	/* Map the 1TB of memory. */
+	/* TODO: We should do a whitelist rather than a blacklist. */
+	if (!mm_vm_identity_map(&vm->ptable, pa_init(0),
+				pa_init(UINT64_C(1024) * 1024 * 1024 * 1024),
+				MM_MODE_R | MM_MODE_W | MM_MODE_X, NULL,
+				ppool)) {
+		dlog("Unable to initialise memory for primary vm\n");
+		return false;
+	}
+
+	if (!mm_vm_unmap_hypervisor(&vm->ptable, ppool)) {
+		dlog("Unable to unmap hypervisor from primary vm\n");
+		return false;
+	}
+
+	vcpu_locked = vcpu_lock(vm_get_vcpu(vm, 0));
+	vcpu_on(vcpu_locked, ipa_from_pa(primary_begin), kernel_arg);
+	vcpu_unlock(&vcpu_locked);
+
 	return true;
 }
 
diff --git a/src/manifest.c b/src/manifest.c
index 6d8b4ba..222716f 100644
--- a/src/manifest.c
+++ b/src/manifest.c
@@ -209,7 +209,10 @@
 	TRY(read_string(node, "debug_name", &vm->debug_name));
 	TRY(read_optional_string(node, "kernel_filename",
 				 &vm->kernel_filename));
-	if (vm_id != HF_PRIMARY_VM_ID) {
+	if (vm_id == HF_PRIMARY_VM_ID) {
+		TRY(read_optional_string(node, "ramdisk_filename",
+					 &vm->primary.ramdisk_filename));
+	} 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 d7690b4..902cf21 100644
--- a/src/manifest_test.cc
+++ b/src/manifest_test.cc
@@ -161,6 +161,11 @@
 		return StringProperty("kernel_filename", value);
 	}
 
+	ManifestDtBuilder &RamdiskFilename(const std::string_view &value)
+	{
+		return StringProperty("ramdisk_filename", value);
+	}
+
 	ManifestDtBuilder &VcpuCount(uint64_t value)
 	{
 		return IntegerProperty("vcpu_count", value);
@@ -398,6 +403,29 @@
 		  MANIFEST_ERROR_INTEGER_OVERFLOW);
 }
 
+TEST(manifest, no_ramdisk_primary)
+{
+	struct manifest m;
+	struct fdt_node fdt_root;
+
+	/* clang-format off */
+	std::vector<char> dtb = ManifestDtBuilder()
+		.StartChild("hypervisor")
+			.Compatible()
+			.StartChild("vm1")
+				.DebugName("primary_vm")
+			.EndChild()
+		.EndChild()
+		.Build();
+	/* clang-format on */
+
+	ASSERT_TRUE(get_fdt_root(dtb, &fdt_root));
+	ASSERT_EQ(manifest_init(&m, &fdt_root), MANIFEST_SUCCESS);
+	ASSERT_EQ(m.vm_count, 1);
+	ASSERT_STREQ(string_data(&m.vm[0].debug_name), "primary_vm");
+	ASSERT_STREQ(string_data(&m.vm[0].primary.ramdisk_filename), "");
+}
+
 TEST(manifest, valid)
 {
 	struct manifest m;
@@ -411,6 +439,7 @@
 			.StartChild("vm1")
 				.DebugName("primary_vm")
 				.KernelFilename("primary_kernel")
+				.RamdiskFilename("primary_ramdisk")
 			.EndChild()
 			.StartChild("vm3")
 				.DebugName("second_secondary_vm")
@@ -435,6 +464,8 @@
 	vm = &m.vm[0];
 	ASSERT_STREQ(string_data(&vm->debug_name), "primary_vm");
 	ASSERT_STREQ(string_data(&vm->kernel_filename), "primary_kernel");
+	ASSERT_STREQ(string_data(&vm->primary.ramdisk_filename),
+		     "primary_ramdisk");
 
 	vm = &m.vm[1];
 	ASSERT_STREQ(string_data(&vm->debug_name), "first_secondary_vm");
diff --git a/test/linux/manifest.dts b/test/linux/manifest.dts
index 71c891e..29e2075 100644
--- a/test/linux/manifest.dts
+++ b/test/linux/manifest.dts
@@ -23,6 +23,7 @@
 		vm1 {
 			debug_name = "linux_test";
 			kernel_filename = "vmlinuz";
+			ramdisk_filename = "initrd.img";
 		};
 
 		vm2 {