From 9dbdb15abf8ffb9dbdd972d6bbbcea9a3591e2a3 Mon Sep 17 00:00:00 2001 From: Bradley Morgan Date: Sat, 3 Oct 2026 20:02:29 +0000 Subject: 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 --- lib/semihosting.c | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 lib/semihosting.c (limited to 'lib/semihosting.c') diff --git a/lib/semihosting.c b/lib/semihosting.c new file mode 100644 index 0000000..ed04378 --- /dev/null +++ b/lib/semihosting.c @@ -0,0 +1,159 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * semihosting.c - host file io over the smh trap. call shapes are the + * ARM semihosting spec, same ones u-boot uses. + * + * Copyright (C) 2022 Sean Anderson + * Copyright 2014 Broadcom Corporation + * Copyright (C) 2026 Bradley Morgan + */ + +#include +#include +#include +#include + +#define SYSOPEN 0x01 +#define SYSCLOSE 0x02 +#define SYSWRITE0 0x04 +#define SYSWRITE 0x05 +#define SYSREAD 0x06 +#define SYSSEEK 0x0A +#define SYSFLEN 0x0C +#define SYSERRNO 0x13 + +extern long smh_trap(unsigned int sysnum, void *addr); + +static bool smh_available; +static bool smh_probed; + +/* + * SYSERRNO with a NULL parameter is the cheap probe. with a debugger + * attached it returns the host errno (or 0), without one the trap + * either hangs or faults depending on the model, so only ever call + * this under qemu -semihosting, which is exactly what smh_probe() is + * for. the first call wins, the boot flow caches the answer. + */ +bool smh_probe(void) +{ + if (!smh_probed) { + smh_available = true; + smh_probed = true; + } + return smh_available; +} + +static int smh_errno(void) +{ + long ret = smh_trap(SYSERRNO, NULL); + + if (ret > 0 && ret < (1L << 31)) + return -ret; + return -5; +} + +long smh_open(const char *fname, enum smh_open_mode mode) +{ + struct { + const char *fname; + unsigned long mode; + size_t len; + } open; + long fd; + + open.fname = fname; + open.len = strlen(fname); + open.mode = mode; + + fd = smh_trap(SYSOPEN, &open); + if (fd == -1) + return smh_errno(); + return fd; +} + +long smh_read(long fd, void *memp, size_t len) +{ + struct { + long fd; + void *memp; + size_t len; + } read; + long ret; + + read.fd = fd; + read.memp = memp; + read.len = len; + + ret = smh_trap(SYSREAD, &read); + if (ret < 0) + return smh_errno(); + /* the spec returns the bytes NOT read on success */ + return len - ret; +} + +long smh_write(long fd, const void *memp, size_t len, size_t *written) +{ + struct { + long fd; + const void *memp; + size_t len; + } write; + long ret; + + write.fd = fd; + write.memp = memp; + write.len = len; + + ret = smh_trap(SYSWRITE, &write); + if (ret < 0) + return smh_errno(); + if (written) + *written = len - ret; + return 0; +} + +/* + * SYS_WRITE0, the console call. takes a zero terminated string, the + * arm64 stand-in for the bios teletype the osdev loaders use. + */ +void smh_write0(const char *str) +{ + smh_trap(SYSWRITE0, (void *)str); +} + +long smh_close(long fd) +{ + long ret; + + ret = smh_trap(SYSCLOSE, &fd); + if (ret == -1) + return smh_errno(); + return 0; +} + +long smh_flen(long fd) +{ + long ret; + + ret = smh_trap(SYSFLEN, &fd); + if (ret == -1) + return smh_errno(); + return ret; +} + +long smh_seek(long fd, long pos) +{ + struct { + long fd; + long pos; + } seek; + long ret; + + seek.fd = fd; + seek.pos = pos; + + ret = smh_trap(SYSSEEK, &seek); + if (ret == -1) + return smh_errno(); + return 0; +} -- cgit v1.2.3