treewide: use architecture-specific UEFI boot path

The default UEFI boot path depends on the architecture. This change is
needed for aarch64 support.

Change-Id: I69916ac5063e963b67ecdcdd814b0678c61da9fb
Reviewed-on: https://review.monogon.dev/c/monogon/+/4195
Reviewed-by: Tim Windelschmidt <tim@monogon.tech>
Tested-by: Jenkins CI
diff --git a/osbase/build/mkimage/def.bzl b/osbase/build/mkimage/def.bzl
index 33323d7..b8239f5 100644
--- a/osbase/build/mkimage/def.bzl
+++ b/osbase/build/mkimage/def.bzl
@@ -3,6 +3,8 @@
 
     arguments = ctx.actions.args()
     arguments.add_all([
+        "-architecture",
+        ctx.attr.architecture,
         "-efi",
         ctx.file.kernel.path,
         "-system",
@@ -31,13 +33,14 @@
 
     return [DefaultInfo(files = depset([img_file]), runfiles = ctx.runfiles(files = [img_file]))]
 
-node_image = rule(
+_node_image = rule(
     implementation = _node_image_impl,
     doc = """
         Build a disk image from an EFI kernel payload, ABLoader and system partition
         contents. See //osbase/build/mkimage for more information.
     """,
     attrs = {
+        "architecture": attr.string(mandatory = True),
         "kernel": attr.label(
             doc = "EFI binary containing a kernel.",
             mandatory = True,
@@ -70,3 +73,18 @@
         ),
     },
 )
+
+def _node_image_macro_impl(**kwargs):
+    _node_image(
+        architecture = select({
+            "@platforms//cpu:x86_64": "x86_64",
+            "@platforms//cpu:aarch64": "aarch64",
+        }),
+        **kwargs
+    )
+
+node_image = macro(
+    inherit_attrs = _node_image,
+    attrs = {"architecture": None},
+    implementation = _node_image_macro_impl,
+)
diff --git a/osbase/build/mkimage/main.go b/osbase/build/mkimage/main.go
index bef7517..3e8d740 100644
--- a/osbase/build/mkimage/main.go
+++ b/osbase/build/mkimage/main.go
@@ -42,6 +42,7 @@
 	flag.StringVar(&biosBootCodePayload, "bios_bootcode", "", "Optional path to the BIOS bootcode which gets placed at the start of the first block of the image. Limited to 440 bytes, padding is not required. It is only used by legacy BIOS boot.")
 	flag.StringVar(&nodeParams, "node_parameters", "", "Path to Node Parameters to be written to the ESP (default: don't write Node Parameters)")
 	flag.StringVar(&outputPath, "out", "", "Path to the resulting disk image or block device")
+	flag.StringVar(&cfg.Architecture, "architecture", "", "CPU architecture")
 	flag.Int64Var(&cfg.PartitionSize.Data, "data_partition_size", 2048, "Override the data partition size (default 2048 MiB). Used only when generating image files.")
 	flag.Int64Var(&cfg.PartitionSize.ESP, "esp_partition_size", 128, "Override the ESP partition size (default: 128MiB)")
 	flag.Int64Var(&cfg.PartitionSize.System, "system_partition_size", 1024, "Override the System partition size (default: 1024MiB)")
diff --git a/osbase/build/mkimage/osimage/osimage.go b/osbase/build/mkimage/osimage/osimage.go
index 4edb077..611c2ed 100644
--- a/osbase/build/mkimage/osimage/osimage.go
+++ b/osbase/build/mkimage/osimage/osimage.go
@@ -31,12 +31,27 @@
 	DataLabel    = "METROPOLIS-NODE-DATA"
 	ESPLabel     = "ESP"
 
-	EFIPayloadPath = "EFI/BOOT/BOOTx64.EFI"
 	EFIBootAPath   = "EFI/metropolis/boot-a.efi"
 	EFIBootBPath   = "EFI/metropolis/boot-b.efi"
 	nodeParamsPath = "metropolis/parameters.pb"
 )
 
+var EFIBootName = map[string]string{
+	"x86_64":  "BOOTx64.EFI",
+	"aarch64": "BOOTAA64.EFI",
+}
+
+// EFIBootPath returns the default file path according to the UEFI Specification
+// v2.11 Section 3.5.1.1. This file is booted by any compliant UEFI firmware in
+// absence of another bootable boot entry.
+func EFIBootPath(architecture string) (string, error) {
+	bootName, ok := EFIBootName[architecture]
+	if !ok {
+		return "", fmt.Errorf("unsupported architecture %q", architecture)
+	}
+	return "EFI/BOOT/" + bootName, nil
+}
+
 // PartitionSizeInfo contains parameters used during partition table
 // initialization and, in case of image files, space allocation.
 type PartitionSizeInfo struct {
@@ -58,6 +73,8 @@
 type Params struct {
 	// Output is the block device to which the OS image is written.
 	Output blockdev.BlockDev
+	// Architecture is the CPU architecture of the OS image.
+	Architecture string
 	// ABLoader provides the A/B loader which then loads the EFI loader for the
 	// correct slot.
 	ABLoader structfs.Blob
@@ -86,6 +103,7 @@
 
 type plan struct {
 	*Params
+	efiBootPath      string
 	efiRoot          structfs.Tree
 	tbl              *gpt.Table
 	efiPartition     *gpt.Partition
@@ -134,7 +152,7 @@
 					PartitionUUID: i.efiPartition.ID,
 				},
 			},
-			efivarfs.FilePath("/" + EFIPayloadPath),
+			efivarfs.FilePath("/" + i.efiBootPath),
 		},
 	}, nil
 }
@@ -165,7 +183,11 @@
 		return nil, err
 	}
 	// Place the A/B loader at the EFI bootloader autodiscovery path.
-	if err := params.efiRoot.PlaceFile(EFIPayloadPath, params.ABLoader); err != nil {
+	params.efiBootPath, err = EFIBootPath(p.Architecture)
+	if err != nil {
+		return nil, err
+	}
+	if err := params.efiRoot.PlaceFile(params.efiBootPath, params.ABLoader); err != nil {
 		return nil, err
 	}
 	if params.NodeParameters != nil {