summaryrefslogtreecommitdiff
path: root/common/image.c
diff options
context:
space:
mode:
authorBradley Morgan <brads@mainlining.org>2026-10-03 20:02:29 +0000
committerBradley Morgan <brads@mainlining.org>2026-10-03 21:37:00 +0000
commit9dbdb15abf8ffb9dbdd972d6bbbcea9a3591e2a3 (patch)
tree0fc03f69c4ca60b3306afc7d3a7c86c1fee8437b /common/image.c
tashaboot: arm64 bootloader
A small arm64 bootloader. No board code, no device tree porting, the architecture manual is the whole story: exception vectors in the fixed 16 slot layout (Table D1-7), ESR_ELx decoded by exception class (D1-2172), EL entry and eret chains per the programmers model (D1-2146), cache maintenance by set/way over the CLIDR_EL1 levels, semihosting for console and file io per DUI 0203, and the A64 boot protocol from Documentation/arch/arm64/booting.rst. The loader boots a stock mainline Image end to end on the qemu virt machine. Boot receipt with 7.3-rc3 (42MB Image): tashaboot 0.1 loaded 43450368 bytes at 40200000, entry 40200000 jumping [ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd070] [ 0.000000] Linux version 7.3.0-rc3 [ 0.000000] Machine model: linux,dummy-virt [ 0.000000] earlycon: pl11 MMIO32:0x0000000009000000 ... ---[ end Kernel panic - not syncing: VFS: Unable to mount root fs ]--- The panic is the expected end state, no root filesystem is handed over yet. The boot chain, state per stage, start to payload: +-----------+-----+--------------+----------------------------------+ | stage | EL | state | work | +-----------+-----+--------------+----------------------------------+ | firmware | any | MMU maybe on | x0 = dtb, jump in | +-----------+-----+--------------+----------------------------------+ | tashaboot | 3-2 | | SCR_EL3.NS = 1, eret to EL2 | +-----------+-----+--------------+----------------------------------+ | | 2 | virt scrub | HCR/CNTHCTL/CPTR/HSTR, CNTFRQ, | | | | | VBAR_EL2, MMU off, tlbi alle2 | +-----------+-----+--------------+----------------------------------+ | | 2 | | load Image over semihosting, | | | | | validate header, place per | | | | | booting.rst | +-----------+-----+--------------+----------------------------------+ | | 2 | caches clean | flush dcache, inval icache, | | | | | args ride x20/x21, regs last | +-----------+-----+--------------+----------------------------------+ | payload | 2 | fresh start | x0 = dtb, x1-x3 = 0, DAIF | | | | | masked, br to image entry | +-----------+-----+--------------+----------------------------------+ Two handoff bugs the kernel caught, both AAPCS clobbers in the final jump. Cache maintenance was called after the register setup, x0-x18 are caller saved, so tb_flush_dcache_all() wiped the dtb pointer and the kernel spun in setup_machine_fdt() with an invalid device tree blob. The flush helpers also clobbered x1 (u-boot's void call convention left mov x1, x0 in cache.S) which handed the kernel a wild x0. The arguments ride in x20/x21 across the cache calls now, callee saved, and the register setup is the last thing before the branch. What is missing on purpose: no SMP bringup (secondary cores park), no PSCI, no initrd or root filesystem handoff, single serial console. Those come next. Signed-off-by: Bradley Morgan <brads@mainlining.org>
Diffstat (limited to 'common/image.c')
-rw-r--r--common/image.c77
1 files changed, 77 insertions, 0 deletions
diff --git a/common/image.c b/common/image.c
new file mode 100644
index 0000000..640eab4
--- /dev/null
+++ b/common/image.c
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * image.c - arm64 kernel Image validation and placement.
+ *
+ * The relocation rules are the ones from the kernel boot protocol,
+ * including the pre a2c1d73b94ed quirks, same math u-boot's
+ * booti_setup() runs.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <endian.h>
+#include <boot.h>
+
+#define LINUX_ARM64_IMAGE_MAGIC 0x644d5241 /* "ARM\x64" */
+
+#define SZ_16M 0x01000000
+#define SZ_2M 0x00200000
+
+/* the 64 byte header from Documentation/arch/arm64/booting.rst */
+struct Image_header {
+ uint32 code0; /* executable */
+ uint32 code1; /* unused */
+ uint64 text_offset; /* load offset, LE */
+ uint64 image_size; /* size, LE */
+ uint64 flags; /* bit 3: relocatable */
+ uint64 res1;
+ uint64 res2;
+ uint64 res3;
+ uint32 magic; /* "ARM\x64" */
+ uint32 res4;
+};
+
+int tb_image_setup(uintptr_t image, struct tb_image *img)
+{
+ const struct Image_header *ih = (const struct Image_header *)image;
+ uint64_t image_size, text_offset;
+
+ if (le32_to_cpu(ih->magic) != LINUX_ARM64_IMAGE_MAGIC)
+ return -1;
+
+ if (le64_to_cpu(ih->image_size) == 0) {
+ /* ancient image, no size field, assume the old defaults */
+ image_size = SZ_16M;
+ text_offset = 0x80000;
+ } else {
+ image_size = le64_to_cpu(ih->image_size);
+ text_offset = le64_to_cpu(ih->text_offset);
+ }
+
+ /*
+ * flag bit 3 says the image can live anywhere, honour where it
+ * already is. otherwise the base must be 2MB aligned, the
+ * physical offset from there is text_offset.
+ */
+ if (le64_to_cpu(ih->flags) & (1ULL << 3)) {
+ uintptr_t base = image - text_offset;
+
+ img->load = ((base + SZ_2M - 1) & ~(uintptr_t)(SZ_2M - 1)) +
+ text_offset;
+ } else {
+ /*
+ * no relocate flag: the image must sit text_offset from a
+ * 2MB aligned base. it is already staged at the fixed
+ * address, treat its own position as the answer.
+ */
+ img->load = image;
+ }
+
+ /* the whole image, header included, lives at load, entry is code0 */
+ img->ep = img->load;
+ img->size = image_size;
+
+ return 0;
+}