summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--Makefile61
-rw-r--r--README.md2
-rw-r--r--arch/arm64/include/asm/linkage.h14
-rw-r--r--arch/arm64/include/asm/macro.h347
-rw-r--r--arch/arm64/include/asm/mmu.h51
-rw-r--r--arch/arm64/include/asm/psci.h37
-rw-r--r--arch/arm64/kernel/boot.S66
-rw-r--r--arch/arm64/kernel/exceptions.c67
-rw-r--r--arch/arm64/kernel/halt.c18
-rw-r--r--arch/arm64/kernel/monitor.S132
-rw-r--r--arch/arm64/kernel/start.S456
-rw-r--r--arch/arm64/kernel/tashaboot.lds91
-rw-r--r--arch/arm64/lib/cache.S100
-rw-r--r--arch/arm64/lib/cache_va.c73
-rw-r--r--arch/arm64/lib/gic.c105
-rw-r--r--arch/arm64/lib/mmu.c205
-rw-r--r--arch/arm64/lib/psci.c118
-rw-r--r--arch/arm64/lib/semihosting.S18
-rw-r--r--arch/arm64/lib/system.c70
-rw-r--r--arch/arm64/lib/timer.c48
-rw-r--r--busybox-static_1%3a1.37.0-7ubuntu1_arm64.debbin0 -> 931776 bytes
-rw-r--r--common/console.c164
-rw-r--r--common/dtb_find.c178
-rw-r--r--common/dtb_grow.c177
-rw-r--r--common/dtb_patch.c288
-rw-r--r--common/dtb_reloc.c69
-rw-r--r--common/image.c77
-rw-r--r--common/load.c86
-rw-r--r--common/main.c300
-rw-r--r--common/mmutest.c77
-rw-r--r--include/assert.h41
-rw-r--r--include/boot.h33
-rw-r--r--include/compiler.h133
-rw-r--r--include/ctype.h43
-rw-r--r--include/debug.h109
-rw-r--r--include/dtb_patch.h27
-rw-r--r--include/endian.h54
-rw-r--r--include/limits.h121
-rw-r--r--include/platform/debug.h44
-rw-r--r--include/printf.h53
-rw-r--r--include/reg.h50
-rw-r--r--include/semihosting.h32
-rw-r--r--include/stdio.h52
-rw-r--r--include/string.h69
-rw-r--r--include/sys/types.h86
-rw-r--r--lib/itoa.c63
-rw-r--r--lib/printf.c383
-rw-r--r--lib/semihosting.c159
-rw-r--r--lib/string/bcopy.c34
-rw-r--r--lib/string/bzero.c35
-rw-r--r--lib/string/memchr.c45
-rw-r--r--lib/string/memcmp.c40
-rw-r--r--lib/string/memcpy.c69
-rw-r--r--lib/string/memmove.c93
-rw-r--r--lib/string/memset.c61
-rw-r--r--lib/string/strchr.c37
-rw-r--r--lib/string/strcmp.c41
-rw-r--r--lib/string/strcpy.c39
-rw-r--r--lib/string/strlcat.c55
-rw-r--r--lib/string/strlcpy.c50
-rw-r--r--lib/string/strlen.c41
-rw-r--r--lib/string/strncmp.c42
-rw-r--r--lib/string/strnlen.c38
-rw-r--r--lib/string/strrchr.c45
-rw-r--r--lib/string/strrev.c46
-rw-r--r--lib/string/strstr.c51
-rw-r--r--test/payload.S52
-rw-r--r--tools/fillsize.py13
69 files changed, 5980 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..30a676f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+build/
+*.o
+*.elf
+*.bin
+*.img
+*.dtb
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..eff00a7
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,61 @@
+# tashaboot
+#
+# build: make CROSS=aarch64-linux-gnu-
+# run: make run (qemu virt, semihosting, Image on the host)
+#
+# arm64 only. the architecture contract comes from the ARM ARM
+# (DDI 0487), the payload protocol from the kernel booting docs.
+
+CROSS ?= aarch64-linux-gnu-
+CC := $(CROSS)gcc
+LD := $(CROSS)ld
+OBJCOPY := $(CROSS)objcopy
+
+CFLAGS := -nostdlib -ffreestanding -mgeneral-regs-only \
+ -fno-builtin -fno-stack-protector -fno-pie -no-pie \
+ -Wall -Werror -O2 \
+ -Iinclude -Iarch/arm64/include
+
+LDFLAGS := -T arch/arm64/kernel/tashaboot.lds
+
+OBJS := arch/arm64/kernel/start.o arch/arm64/kernel/monitor.o \
+ arch/arm64/kernel/exceptions.o \
+ arch/arm64/kernel/halt.o \
+ arch/arm64/kernel/boot.o \
+ arch/arm64/lib/cache.o \
+ arch/arm64/lib/semihosting.o arch/arm64/lib/gic.o \
+ arch/arm64/lib/mmu.o \
+ arch/arm64/lib/psci.o \
+ arch/arm64/lib/system.o \
+ arch/arm64/lib/cache_va.o \
+ arch/arm64/lib/timer.o \
+ common/main.o common/console.o common/image.o common/load.o \
+ common/mmutest.o common/dtb_patch.o common/dtb_reloc.o \
+ common/dtb_grow.o common/dtb_find.o \
+ lib/printf.o lib/itoa.o lib/semihosting.o \
+ $(patsubst %.c,%.o,$(wildcard lib/string/*.c))
+
+all: build/tashaboot.bin
+
+build/tashaboot.elf: $(OBJS) arch/arm64/kernel/tashaboot.lds
+ mkdir -p build
+ $(LD) $(LDFLAGS) -o $@ $(OBJS)
+
+build/tashaboot.bin: build/tashaboot.elf
+ $(OBJCOPY) -O binary $< $@
+ python3 tools/fillsize.py $@
+
+%.o: %.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+%.o: %.S
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+run: all
+ qemu-system-aarch64 -M virt -cpu cortex-a57 -nographic -no-reboot \
+ -semihosting -kernel build/tashaboot.bin
+
+clean:
+ rm -rf build $(OBJS)
+
+.PHONY: all run clean
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..b03aa83
--- /dev/null
+++ b/README.md
@@ -0,0 +1,2 @@
+# tashaboot
+Coming soon :)
diff --git a/arch/arm64/include/asm/linkage.h b/arch/arm64/include/asm/linkage.h
new file mode 100644
index 0000000..b5b9706
--- /dev/null
+++ b/arch/arm64/include/asm/linkage.h
@@ -0,0 +1,14 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ASM_LINKAGE_H
+#define __ASM_LINKAGE_H
+
+#define ALIGN .p2align 4
+#define ENTRY(name) \
+ .globl name; \
+ ALIGN; \
+ name:
+#define ENDPROC(name) \
+ .type name, %function; \
+ .size name, .-name
+
+#endif
diff --git a/arch/arm64/include/asm/macro.h b/arch/arm64/include/asm/macro.h
new file mode 100644
index 0000000..1a1edc9
--- /dev/null
+++ b/arch/arm64/include/asm/macro.h
@@ -0,0 +1,347 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * include/asm-arm/macro.h
+ *
+ * Copyright (C) 2009 Jean-Christophe PLAGNIOL-VILLARD <plagnioj@jcrosoft.com>
+ */
+
+#ifndef __ASM_ARM_MACRO_H__
+#define __ASM_ARM_MACRO_H__
+
+#ifdef CONFIG_ARM64
+#include <asm/system.h>
+#endif
+
+#ifdef __ASSEMBLY__
+
+/*
+ * These macros provide a convenient way to write 8, 16 and 32 bit data
+ * to any address.
+ * Registers r4 and r5 are used, any data in these registers are
+ * overwritten by the macros.
+ * The macros are valid for any ARM architecture, they do not implement
+ * any memory barriers so caution is recommended when using these when the
+ * caches are enabled or on a multi-core system.
+ */
+
+.macro write32, addr, data
+ ldr r4, =\addr
+ ldr r5, =\data
+ str r5, [r4]
+.endm
+
+.macro write16, addr, data
+ ldr r4, =\addr
+ ldrh r5, =\data
+ strh r5, [r4]
+.endm
+
+.macro write8, addr, data
+ ldr r4, =\addr
+ ldrb r5, =\data
+ strb r5, [r4]
+.endm
+
+/*
+ * This macro generates a loop that can be used for delays in the code.
+ * Register r4 is used, any data in this register is overwritten by the
+ * macro.
+ * The macro is valid for any ARM architeture. The actual time spent in the
+ * loop will vary from CPU to CPU though.
+ */
+
+.macro wait_timer, time
+ ldr r4, =\time
+1:
+ nop
+ subs r4, r4, #1
+ bcs 1b
+.endm
+
+#ifdef CONFIG_ARM64
+/*
+ * Register aliases.
+ */
+lr .req x30
+
+/*
+ * Branch according to exception level
+ */
+.macro switch_el, xreg, el3_label, el2_label, el1_label
+ mrs \xreg, CurrentEL
+ cmp \xreg, #0x8
+ b.gt \el3_label
+ b.eq \el2_label
+ b.lt \el1_label
+.endm
+
+/*
+ * Branch if we are not in the highest exception level
+ */
+.macro branch_if_not_highest_el, xreg, label
+ switch_el \xreg, 3f, 2f, 1f
+
+2: mrs \xreg, ID_AA64PFR0_EL1
+ and \xreg, \xreg, #(ID_AA64PFR0_EL1_EL3)
+ cbnz \xreg, \label
+ b 3f
+
+1: mrs \xreg, ID_AA64PFR0_EL1
+ and \xreg, \xreg, #(ID_AA64PFR0_EL1_EL3 | ID_AA64PFR0_EL1_EL2)
+ cbnz \xreg, \label
+
+3:
+.endm
+
+/*
+ * Branch if current processor is a Cortex-A57 core.
+ */
+.macro branch_if_a57_core, xreg, a57_label
+ mrs \xreg, midr_el1
+ lsr \xreg, \xreg, #4
+ and \xreg, \xreg, #0x00000FFF
+ cmp \xreg, #0xD07 /* Cortex-A57 MPCore processor. */
+ b.eq \a57_label
+.endm
+
+/*
+ * Branch if current processor is a Cortex-A53 core.
+ */
+.macro branch_if_a53_core, xreg, a53_label
+ mrs \xreg, midr_el1
+ lsr \xreg, \xreg, #4
+ and \xreg, \xreg, #0x00000FFF
+ cmp \xreg, #0xD03 /* Cortex-A53 MPCore processor. */
+ b.eq \a53_label
+.endm
+
+/*
+ * Branch if current processor is a slave,
+ * choose processor with all zero affinity value as the master.
+ */
+.macro branch_if_slave, xreg, slave_label
+#ifdef CONFIG_ARMV8_MULTIENTRY
+ mrs \xreg, mpidr_el1
+ and \xreg, \xreg, 0xffffffffff /* clear bits [63:40] */
+ and \xreg, \xreg, ~0x00ff000000 /* also clear bits [31:24] */
+ cbnz \xreg, \slave_label
+#endif
+.endm
+
+/*
+ * Branch if current processor is a master,
+ * choose processor with all zero affinity value as the master.
+ */
+.macro branch_if_master, xreg, master_label
+#ifdef CONFIG_ARMV8_MULTIENTRY
+ mrs \xreg, mpidr_el1
+ and \xreg, \xreg, 0xffffffffff /* clear bits [63:40] */
+ and \xreg, \xreg, ~0x00ff000000 /* also clear bits [31:24] */
+ cbz \xreg, \master_label
+#else
+ b \master_label
+#endif
+.endm
+
+/*
+ * Switch from EL3 to EL2 for ARMv8
+ * @ep: kernel entry point
+ * @flag: The execution state flag for lower exception
+ * level, ES_TO_AARCH64 or ES_TO_AARCH32
+ * @tmp: temporary register
+ *
+ * For loading 32-bit OS, x1 is machine nr and x2 is ftaddr.
+ * For loading 64-bit OS, x0 is physical address to the FDT blob.
+ * They will be passed to the guest.
+ */
+.macro armv8_switch_to_el2_m, ep, flag, tmp
+ msr cptr_el3, xzr /* Disable coprocessor traps to EL3 */
+ mov \tmp, #CPTR_EL2_RES1
+ msr cptr_el2, \tmp /* Disable coprocessor traps to EL2 */
+
+ /* Initialize Generic Timers */
+ msr cntvoff_el2, xzr
+
+ /* Initialize SCTLR_EL2
+ *
+ * setting RES1 bits (29,28,23,22,18,16,11,5,4) to 1
+ * and RES0 bits (31,30,27,26,24,21,20,17,15-13,10-6) +
+ * EE,WXN,I,SA,C,A,M to 0
+ */
+ ldr \tmp, =(SCTLR_EL2_RES1 | SCTLR_EL2_EE_LE |\
+ SCTLR_EL2_WXN_DIS | SCTLR_EL2_ICACHE_DIS |\
+ SCTLR_EL2_SA_DIS | SCTLR_EL2_DCACHE_DIS |\
+ SCTLR_EL2_ALIGN_DIS | SCTLR_EL2_MMU_DIS)
+ msr sctlr_el2, \tmp
+
+ mov \tmp, sp
+ msr sp_el2, \tmp /* Migrate SP */
+ mrs \tmp, vbar_el3
+ msr vbar_el2, \tmp /* Migrate VBAR */
+
+ /* Check switch to AArch64 EL2 or AArch32 Hypervisor mode */
+ cmp \flag, #ES_TO_AARCH32
+ b.eq 1f
+
+ /*
+ * The next lower exception level is AArch64, 64bit EL2 | HCE |
+ * RES1 (Bits[5:4]) | Non-secure EL0/EL1.
+ * and the SMD depends on requirements.
+ */
+#ifdef CONFIG_ARMV8_PSCI
+ ldr \tmp, =(SCR_EL3_RW_AARCH64 | SCR_EL3_HCE_EN |\
+ SCR_EL3_RES1 | SCR_EL3_NS_EN)
+#else
+ ldr \tmp, =(SCR_EL3_RW_AARCH64 | SCR_EL3_HCE_EN |\
+ SCR_EL3_SMD_DIS | SCR_EL3_RES1 |\
+ SCR_EL3_NS_EN)
+#endif
+
+#ifdef CONFIG_ARMV8_EA_EL3_FIRST
+ orr \tmp, \tmp, #SCR_EL3_EA_EN
+#endif
+ msr scr_el3, \tmp
+
+ /* Return to the EL2_SP2 mode from EL3 */
+ ldr \tmp, =(SPSR_EL_DEBUG_MASK | SPSR_EL_SERR_MASK |\
+ SPSR_EL_IRQ_MASK | SPSR_EL_FIQ_MASK |\
+ SPSR_EL_M_AARCH64 | SPSR_EL_M_EL2H)
+ msr spsr_el3, \tmp
+ msr elr_el3, \ep
+ eret
+
+1:
+ /*
+ * The next lower exception level is AArch32, 32bit EL2 | HCE |
+ * SMD | RES1 (Bits[5:4]) | Non-secure EL0/EL1.
+ */
+ ldr \tmp, =(SCR_EL3_RW_AARCH32 | SCR_EL3_HCE_EN |\
+ SCR_EL3_SMD_DIS | SCR_EL3_RES1 |\
+ SCR_EL3_NS_EN)
+ msr scr_el3, \tmp
+
+ /* Return to AArch32 Hypervisor mode */
+ ldr \tmp, =(SPSR_EL_END_LE | SPSR_EL_ASYN_MASK |\
+ SPSR_EL_IRQ_MASK | SPSR_EL_FIQ_MASK |\
+ SPSR_EL_T_A32 | SPSR_EL_M_AARCH32 |\
+ SPSR_EL_M_HYP)
+ msr spsr_el3, \tmp
+ msr elr_el3, \ep
+ eret
+.endm
+
+/*
+ * Switch from EL2 to EL1 for ARMv8
+ * @ep: kernel entry point
+ * @flag: The execution state flag for lower exception
+ * level, ES_TO_AARCH64 or ES_TO_AARCH32
+ * @tmp: temporary register
+ *
+ * For loading 32-bit OS, x1 is machine nr and x2 is ftaddr.
+ * For loading 64-bit OS, x0 is physical address to the FDT blob.
+ * They will be passed to the guest.
+ */
+.macro armv8_switch_to_el1_m, ep, flag, tmp, tmp2
+ /* Initialize Generic Timers */
+ mrs \tmp, cnthctl_el2
+ /* Enable EL1 access to timers */
+ orr \tmp, \tmp, #(CNTHCTL_EL2_EL1PCEN_EN |\
+ CNTHCTL_EL2_EL1PCTEN_EN)
+ msr cnthctl_el2, \tmp
+ msr cntvoff_el2, xzr
+
+ /* Initilize MPID/MPIDR registers */
+ mrs \tmp, midr_el1
+ msr vpidr_el2, \tmp
+ mrs \tmp, mpidr_el1
+ msr vmpidr_el2, \tmp
+
+ /* Disable coprocessor traps */
+ mov \tmp, #CPTR_EL2_RES1
+ msr cptr_el2, \tmp /* Disable coprocessor traps to EL2 */
+ msr hstr_el2, xzr /* Disable coprocessor traps to EL2 */
+ mov \tmp, #CPACR_EL1_FPEN_EN
+ msr cpacr_el1, \tmp /* Enable FP/SIMD at EL1 */
+
+ /* SCTLR_EL1 initialization
+ *
+ * setting RES1 bits (29,28,23,22,20,11) to 1
+ * and RES0 bits (31,30,27,21,17,13,10,6) +
+ * UCI,EE,EOE,WXN,nTWE,nTWI,UCT,DZE,I,UMA,SED,ITD,
+ * CP15BEN,SA0,SA,C,A,M to 0
+ */
+ ldr \tmp, =(SCTLR_EL1_RES1 | SCTLR_EL1_UCI_DIS |\
+ SCTLR_EL1_EE_LE | SCTLR_EL1_WXN_DIS |\
+ SCTLR_EL1_NTWE_DIS | SCTLR_EL1_NTWI_DIS |\
+ SCTLR_EL1_UCT_DIS | SCTLR_EL1_DZE_DIS |\
+ SCTLR_EL1_ICACHE_DIS | SCTLR_EL1_UMA_DIS |\
+ SCTLR_EL1_SED_EN | SCTLR_EL1_ITD_EN |\
+ SCTLR_EL1_CP15BEN_DIS | SCTLR_EL1_SA0_DIS |\
+ SCTLR_EL1_SA_DIS | SCTLR_EL1_DCACHE_DIS |\
+ SCTLR_EL1_ALIGN_DIS | SCTLR_EL1_MMU_DIS)
+ msr sctlr_el1, \tmp
+
+ mov \tmp, sp
+ msr sp_el1, \tmp /* Migrate SP */
+ mrs \tmp, vbar_el2
+ msr vbar_el1, \tmp /* Migrate VBAR */
+
+ /* Check switch to AArch64 EL1 or AArch32 Supervisor mode */
+ cmp \flag, #ES_TO_AARCH32
+ b.eq 1f
+
+ /* Initialize HCR_EL2 */
+ /* Only disable PAuth traps if PAuth is supported */
+ mrs \tmp, id_aa64isar1_el1
+ ldr \tmp2, =(ID_AA64ISAR1_EL1_GPI | ID_AA64ISAR1_EL1_GPA | \
+ ID_AA64ISAR1_EL1_API | ID_AA64ISAR1_EL1_APA)
+ tst \tmp, \tmp2
+ mov \tmp2, #(HCR_EL2_RW_AARCH64 | HCR_EL2_HCD_DIS)
+ orr \tmp, \tmp2, #(HCR_EL2_APK | HCR_EL2_API)
+ csel \tmp, \tmp2, \tmp, eq
+ msr hcr_el2, \tmp
+
+ /* Return to the EL1_SP1 mode from EL2 */
+ ldr \tmp, =(SPSR_EL_DEBUG_MASK | SPSR_EL_SERR_MASK |\
+ SPSR_EL_IRQ_MASK | SPSR_EL_FIQ_MASK |\
+ SPSR_EL_M_AARCH64 | SPSR_EL_M_EL1H)
+ msr spsr_el2, \tmp
+ msr elr_el2, \ep
+ eret
+
+1:
+ /* Initialize HCR_EL2 */
+ ldr \tmp, =(HCR_EL2_RW_AARCH32 | HCR_EL2_HCD_DIS)
+ msr hcr_el2, \tmp
+
+ /* Return to AArch32 Supervisor mode from EL2 */
+ ldr \tmp, =(SPSR_EL_END_LE | SPSR_EL_ASYN_MASK |\
+ SPSR_EL_IRQ_MASK | SPSR_EL_FIQ_MASK |\
+ SPSR_EL_T_A32 | SPSR_EL_M_AARCH32 |\
+ SPSR_EL_M_SVC)
+ msr spsr_el2, \tmp
+ msr elr_el2, \ep
+ eret
+.endm
+
+#if defined(CONFIG_GICV3)
+.macro gic_wait_for_interrupt_m xreg1
+0 : wfi
+ mrs \xreg1, ICC_IAR1_EL1
+ msr ICC_EOIR1_EL1, \xreg1
+ cbnz \xreg1, 0b
+.endm
+#elif defined(CONFIG_GICV2)
+.macro gic_wait_for_interrupt_m xreg1, wreg2
+0 : wfi
+ ldr \wreg2, [\xreg1, GICC_AIAR]
+ str \wreg2, [\xreg1, GICC_AEOIR]
+ and \wreg2, \wreg2, #0x3ff
+ cbnz \wreg2, 0b
+.endm
+#endif
+
+#endif /* CONFIG_ARM64 */
+
+#endif /* __ASSEMBLY__ */
+#endif /* __ASM_ARM_MACRO_H__ */
diff --git a/arch/arm64/include/asm/mmu.h b/arch/arm64/include/asm/mmu.h
new file mode 100644
index 0000000..342a2ff
--- /dev/null
+++ b/arch/arm64/include/asm/mmu.h
@@ -0,0 +1,51 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ASM_MMU_H
+#define __ASM_MMU_H
+
+/*
+ * VMSAv8-64 stage 1 translation at EL2. descriptor layouts and
+ * attribute fields per the ARM ARM (DDI 0487), block and table
+ * descriptors D5-2444, page descriptors D5-2447, stage 1 attribute
+ * fields D5-2451, MAIR region attributes D5-2476.
+ */
+
+#include <stdint.h>
+
+/* descriptor bits[1:0]: 0b01 block (page at level 3), 0b11 table */
+#define TB_DESC_FAULT 0ULL
+#define TB_DESC_BLOCK 1ULL
+#define TB_DESC_TABLE 3ULL
+
+/* lower block/page attribute bits, D5-2451 */
+#define TB_DESC_AF (1ULL << 10) /* access flag, set by hand */
+#define TB_DESC_SH_IS (3ULL << 8) /* inner shareable */
+#define TB_DESC_XN (1ULL << 54) /* XN at EL2, no execute */
+
+/* MAIR_ELx attribute indices used by the maps below */
+#define TB_ATTR_NORMAL 0 /* writeback, read allocate */
+#define TB_ATTR_DEVICE 1 /* device nGnRE */
+
+/*
+ * TCR setup, 4KB granule. T0SZ 16 gives a 48-bit VA and the walk
+ * starts at level 0 (Address size configuration, D5-2399), which is
+ * what the three level table structure below assumes. a 39-bit VA
+ * (T0SZ 25) would start the walk at level 1 and misread the whole
+ * table.
+ */
+#define TB_TCR_T0SZ_48 16
+#define TB_TCR_SH0_IS (3ULL << 12)
+#define TB_TCR_TG0_4K (0ULL << 14)
+#define TB_TCR_IRGN0_WB (1ULL << 8)
+#define TB_TCR_ORGN0_WB (1ULL << 10)
+#define TB_TCR_IPS(x) ((uint64_t)(x) << 16) /* PA size from PARange */
+
+/* the map itself, PA == VA everywhere, identity */
+#define TB_MAP_MMIO_BASE 0x00000000ULL
+#define TB_MAP_MMIO_SIZE (1ULL << 30) /* low 1GB, devices live here */
+#define TB_MAP_RAM_BASE 0x40000000ULL
+#define TB_MAP_RAM_SIZE (128ULL << 20) /* qemu virt default, 128MB */
+
+int tb_mmu_enable(void);
+void tb_mmu_disable(void);
+
+#endif /* __ASM_MMU_H */
diff --git a/arch/arm64/include/asm/psci.h b/arch/arm64/include/asm/psci.h
new file mode 100644
index 0000000..d7ce0f3
--- /dev/null
+++ b/arch/arm64/include/asm/psci.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ASM_PSCI_H
+#define __ASM_PSCI_H
+
+#include <stdint.h>
+/*
+ * PSCI 0.2 handler at EL2, the Power State Coordination Interface
+ * per DEN 0022. the payload calls it through the conduit the dtb
+ * names, hvc here, the call traps to EL2 and this dispatches.
+ */
+
+/* standard function ids, DEN 0022 table 5-1 */
+#define PSCI_FN_VERSION 0x84000000
+#define PSCI_FN_CPU_OFF 0x84000002
+#define PSCI_FN_CPU_ON 0x84000003
+#define PSCI_FN_SYSTEM_OFF 0x84000008
+#define PSCI_FN_SYSTEM_RESET 0x84000009
+
+/* version 0.2, major 0 minor 2 */
+#define PSCI_VERSION_0_2 0x00000002
+
+/* error codes, DEN 0022 */
+#define PSCI_RET_SUCCESS 0
+#define PSCI_RET_NOT_SUPPORTED -1
+#define PSCI_RET_INVALID_PARAMS -2
+#define PSCI_RET_DENIED -3
+#define PSCI_RET_ALREADY_ON -4
+#define PSCI_RET_ON_PENDING -5
+#define PSCI_RET_INTERNAL_FAIL -6
+#define PSCI_RET_NOT_PRESENT -7
+#define PSCI_RET_DISABLED -8
+
+/* the asm HVC vector calls this with the caller's x0-x3 in place */
+uint64_t tb_psci_dispatch(uint64_t fn, uint64_t x1, uint64_t x2,
+ uint64_t x3);
+
+#endif /* __ASM_PSCI_H */
diff --git a/arch/arm64/kernel/boot.S b/arch/arm64/kernel/boot.S
new file mode 100644
index 0000000..d8b888f
--- /dev/null
+++ b/arch/arm64/kernel/boot.S
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * boot.S - the final jump to the payload. x0 = dtb, x1 = x2 = x3 = 0,
+ * MMU and caches off, D cache flushed, I cache invalidated. that is
+ * the whole contract from Documentation/arch/arm64/booting.rst.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <asm/linkage.h>
+
+.pushsection .text.tb_boot_linux, "ax"
+ENTRY(tb_boot_linux)
+ /* ep in x0, dtb in x1, per the kernel boot protocol */
+
+ /*
+ * cache maintenance first, register setup last. x0-x18 are
+ * caller saved per the AAPCS, the flush helpers are free to
+ * clobber them, so the args ride in x20/x21 across the calls.
+ */
+ mov x20, x0 /* entry point */
+ mov x21, x1 /* dtb */
+
+ bl tb_flush_dcache_all
+ bl tb_invalidate_icache_all
+
+ mov x8, x20
+ mov x0, x21
+ mov x1, xzr
+ mov x2, xzr
+ mov x3, xzr
+
+ /*
+ * raise to EL2 for the payload when EL2 exists, the kernel
+ * prefers it there (booting.rst). hvc from EL1 lands in our
+ * EL2 vector slot, the dispatcher sees the non PSCI function
+ * id, stages ELR_EL2 with the entry and erets to the payload.
+ * on an EL1 only machine this is a straight branch.
+ */
+ mrs x9, CurrentEL
+ lsr x9, x9, #2
+ cmp x9, #2
+ b.lt 5f
+ hvc #0
+5:
+
+ /* MMU off, caches off, the kernel sets up its own state */
+ mrs x9, sctlr_el1
+ bic x9, x9, #(1 << 0) /* M, MMU */
+ bic x9, x9, #(1 << 2) /* C, D-cache */
+ bic x9, x9, #(1 << 12) /* I, I-cache */
+ msr sctlr_el1, x9
+ isb
+
+ /*
+ * if we entered at EL2, the kernel prefers it there. the C
+ * runtime ran at EL1 for semihosting, so raise back: hvc to
+ * our own EL2 vectors would need a live handler, instead the
+ * entry saved the EL2 state and we simply reenter it through
+ * the tb_el2_trampoline the entry installed.
+ */
+
+
+ br x8
+ENDPROC(tb_boot_linux)
+.popsection
diff --git a/arch/arm64/kernel/exceptions.c b/arch/arm64/kernel/exceptions.c
new file mode 100644
index 0000000..7b40690
--- /dev/null
+++ b/arch/arm64/kernel/exceptions.c
@@ -0,0 +1,67 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * exceptions.c - report an abort through the console before parking,
+ * so a firmware handoff bug says why it died instead of hanging quiet.
+ * ESR/FAR decode follows armv8 DDI 0487, the EC and ISS fields.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <stdint.h>
+#include <debug.h>
+
+struct exc_frame {
+ uint64_t esr;
+ uint64_t far;
+ uint64_t lr;
+};
+
+/*
+ * exception class from ESR, bits 31:26. the classes a bootloader can
+ * actually hit with any frequency.
+ */
+static const char *exc_class_str(uint64_t esr)
+{
+ switch (esr >> 26) {
+ case 0x04: return "data abort, lower EL";
+ case 0x05: return "data abort, same EL";
+ case 0x25: return "data abort, same EL";
+ case 0x08: return "stack pointer misaligned";
+ case 0x11: return "instruction abort, same EL";
+ case 0x16: return "SError";
+ case 0x1a: return "unhandled exception";
+ case 0x22: return "pc alignment fault";
+ case 0x24: return "unknown trap";
+ case 0x26: return "same EL exception return";
+ default: return "unknown EC";
+ }
+}
+
+/*
+ * far is only meaningful for the abort and alignment classes, note it
+ * for those and skip it otherwise so the report does not mislead.
+ */
+static int exc_far_valid(uint64_t esr)
+{
+ switch (esr >> 26) {
+ case 0x04:
+ case 0x05:
+ case 0x25:
+ case 0x11:
+ case 0x22:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+void exc_report(uint64_t esr, uint64_t far, uint64_t lr)
+{
+ dprintf(CRITICAL, "tashaboot: exception %s\n", exc_class_str(esr));
+ dprintf(CRITICAL, "esr %016llx lr %016llx\n",
+ (unsigned long long)esr, (unsigned long long)lr);
+ if (exc_far_valid(esr))
+ dprintf(CRITICAL, "far %016llx\n", (unsigned long long)far);
+
+ /* nothing recovers from an abort here, park after reporting */
+}
diff --git a/arch/arm64/kernel/halt.c b/arch/arm64/kernel/halt.c
new file mode 100644
index 0000000..d91d39a
--- /dev/null
+++ b/arch/arm64/kernel/halt.c
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * halt.c - stop the core, the ARM ARM's WFI loop. nothing recovers
+ * from a halt, the machine needs a reset.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <debug.h>
+
+void platform_halt(void)
+{
+ dprintf(ALWAYS, "HALT: spinning forever...\n");
+
+ for (;;) {
+ asm volatile("wfi");
+ }
+}
diff --git a/arch/arm64/kernel/monitor.S b/arch/arm64/kernel/monitor.S
new file mode 100644
index 0000000..c6f5ec8
--- /dev/null
+++ b/arch/arm64/kernel/monitor.S
@@ -0,0 +1,132 @@
+/*
+ * monitor.S - the EL3 secure monitor, the resident layer real
+ * firmware ships. the loader drops to non-secure and never
+ * returns, but the kernel keeps calling into firmware: PSCI
+ * through the SMC conduit, and on hardware with the security
+ * extension the group routing of the interrupt controller is
+ * only writable from here.
+ *
+ * the entry path runs once per PE: configure EL3, install the
+ * monitor vectors, hand the next stage non-secure EL2 in the
+ * manual's boot state. SMCCC calls from the kernel trap into
+ * the SMC slot, the C dispatcher behind it is the same one the
+ * hvc path uses.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+/*
+ * the monitor stack. SP_EL3 needs memory no non-secure stage
+ * will touch, the region after the loader stack, sixteen
+ * bytes a call deep at most.
+ */
+.section .bss.el3stack, "aw", %nobits
+.align 4
+.globl __el3_stack_bottom
+__el3_stack_bottom:
+ .quad 0, 0, 0, 0
+ .quad 0, 0, 0, 0
+.globl __el3_stack_top
+__el3_stack_top:
+
+/*
+ * EL3 vectors, same sixteen slot layout every exception level
+ * uses. only the lower EL sync slot carries work, the SMC
+ * conduit, everything else parks.
+ */
+.balign 2048
+.globl tb_el3_vectors
+tb_el3_vectors:
+ /* 0x000: current EL, SP_EL0, unused */
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+
+ /* 0x200: current EL, SP_ELx, unused */
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+
+ /* 0x400: lower EL, AArch64, the SMC conduit lives here */
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_smc
+
+ /* 0x600: lower EL, AArch32, unused */
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+ .align 7
+ b el3_park
+
+/*
+ * one time per PE, from the reset path. x30 = the next stage
+ * entry in non-secure EL2, x0 = the dtb pointer.
+ */
+.globl tb_monitor_init
+tb_monitor_init:
+ /* SP_EL3 on its own region */
+ adr x1, __el3_stack_top
+ msr spsel, #0
+ mov sp, x1
+ msr spsel, #1
+
+ /* the monitor vectors */
+ adr x1, tb_el3_vectors
+ msr vbar_el3, x1
+ isb
+
+ /*
+ * SMC as the conduit, SVE traps off, no interrupt routing
+ * into EL3: FIQ/IRQ stay whatever SCR_EL3.SCR left them,
+ * the kernel owns the world below.
+ */
+ mrs x1, scr_el3
+ bic x1, x1, #(1 << 2) /* SMD, SMC enabled */
+ msr scr_el3, x1
+ isb
+
+ ret
+
+el3_park:
+ b el3_park
+
+/*
+ * the SMC trap from lower EL. the SMCCC calling convention is
+ * the SMC register set, function id in x0, arguments x1 to
+ * x3, results in x0 to x3. x17 and x18 are caller save in
+ * this convention, the dispatcher clobbers x0 to x18.
+ */
+el3_smc:
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+ stp x19, x20, [sp, #-16]!
+ stp x21, x22, [sp, #-16]!
+ stp x23, x24, [sp, #-16]!
+
+ bl tb_psci_dispatch
+
+ ldp x23, x24, [sp], #16
+ ldp x21, x22, [sp], #16
+ ldp x19, x20, [sp], #16
+ ldp x29, x30, [sp], #16
+
+ eret
diff --git a/arch/arm64/kernel/start.S b/arch/arm64/kernel/start.S
new file mode 100644
index 0000000..2a5e2ae
--- /dev/null
+++ b/arch/arm64/kernel/start.S
@@ -0,0 +1,456 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * tashaboot arm64 entry. handles whatever EL the firmware left us in,
+ * EL3, EL2 or EL1, with the MMU either on or off, and arrives at a
+ * clean EL1 with the MMU off before calling C.
+ *
+ * the secondary cores park, spin table bringup is a later problem.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <asm/macro.h>
+
+.section .text.boot
+.globl _start
+_start:
+ /* code0: branch over the 64 byte Image header to reset */
+ b reset
+
+ .balign 8
+/*
+ * the arm64 Image header fields, per Documentation/arch/arm64/
+ * booting.rst: text_offset 0x08, image_size 0x10, flags 0x18,
+ * magic 0x38. code0 above branches over all of it. text_offset 0
+ * and image_size filled after link by tools/fillsize.py, the
+ * magic pins it as a proper Image so qemu -kernel enters at
+ * RAMBASE instead of guessing +0x80000.
+ */
+ .quad 0x0 /* text_offset, 0x08, filled below */
+ .quad 0x0 /* image_size, 0x10, filled below */
+ .quad 0x0 /* flags, 0x18: LE, 4k pages, unset */
+ .quad 0x0 /* reserved 0x20 */
+ .quad 0x0 /* reserved 0x28 */
+ .quad 0x0 /* reserved 0x30 */
+ .quad 0x644d5241 /* magic, 0x38: ARM\x64 */
+
+reset:
+ /* keep the dtb pointer before anything clobbers x0 */
+ mov x19, x0
+
+ /*
+ * park secondary cores, they have nothing to do yet. at
+ * EL3 they still get the monitor: a firmware call on any
+ * PE must land in a handler, a secondary with no EL3
+ * vectors traps into nothing.
+ */
+ mrs x0, mpidr_el1
+ and x0, x0, #0xff
+ cbnz x0, secondary_boot
+
+ mrs x0, CurrentEL
+ lsr x0, x0, #2
+ cmp x0, #3
+ b.eq from_el3
+ cmp x0, #2
+ b.eq from_el2
+ cmp x0, #1
+ b.eq mmu_check
+ b park
+
+secondary_boot:
+ mrs x0, CurrentEL
+ lsr x0, x0, #2
+ cmp x0, #3
+ b.ne park
+ /*
+ * the same security state as the primary: SCR_EL3.NS
+ * clear leaves a PE secure, and a secondary released
+ * into the kernel secure is the inconsistent mode boot
+ * the kernel warns about, its calls trap to EL3 as if
+ * they were firmware's own.
+ */
+ mrs x0, scr_el3
+ orr x0, x0, #1
+ msr scr_el3, x0
+ isb
+ bl tb_monitor_init
+ b park
+
+from_el3:
+ /*
+ * EL3 holds the security state, so the monitor lives here:
+ * vectors, its own stack, the SMC conduit. it is resident
+ * after this, the kernel's firmware calls trap into it.
+ */
+ bl tb_monitor_init
+
+ /* the kernel runs non-secure, drop to the EL2 it prefers */
+ mrs x0, scr_el3
+ orr x0, x0, #1 /* SCR_EL3.NS = 1, non-secure */
+ msr scr_el3, x0
+ isb
+
+ mov x0, #0x3c9 /* EL2h, DAIF masked */
+ msr spsr_el3, x0
+ adr x0, from_el2
+ msr elr_el3, x0
+ eret
+
+from_el2:
+ /*
+ * scrub the EL2 state and drop to EL1 for the C runtime. the
+ * semihosting hlt trap is an EL1 service on qemu, calling it
+ * from EL2 corrupts the return state. the kernel handoff goes
+ * back to EL2, booting.rst prefers it there, through the
+ * trampoline in boot.S.
+ */
+
+ /* EL1 will be aarch64 */
+ mov x0, #(1 << 31) /* HCR_EL2.RW = 1 */
+ msr hcr_el2, x0
+
+ /* let EL1 reach the counter, booting.rst demands it */
+ mrs x0, cnthctl_el2
+ orr x0, x0, #(3 << 0) /* EL1PCTEN | EL1PCEN */
+ msr cnthctl_el2, x0
+
+ /* no traps to EL2 behind EL1's back */
+ msr cptr_el2, xzr
+ msr hstr_el2, xzr
+ msr vpidr_el2, xzr
+
+ /* drop to EL1, SPSR EL1h with DAIF masked */
+ mov x0, #0x3c5
+ msr spsr_el2, x0
+ adr x0, mmu_check
+ msr elr_el2, x0
+ eret
+
+mmu_check:
+ /*
+ * whether the firmware left an MMU on: M bit, bit 0, of sctlr at
+ * the current EL. writing the register off would not fault, but
+ * the page tables it built are in its own memory, better to kill
+ * it here than trip over a stale mapping.
+ */
+ mrs x0, CurrentEL
+ lsr x0, x0, #2
+ cmp x0, #2
+ b.lt mmu_el1
+ mrs x0, sctlr_el2
+ tbz x0, #0, c_entry
+
+ mov x0, xzr
+ msr sctlr_el2, x0
+ isb
+ tlbi alle2
+ dsb sy
+ isb
+ b c_entry
+
+mmu_el1:
+ mrs x0, sctlr_el1
+ tbz x0, #0, c_entry
+
+ mov x0, xzr
+ msr sctlr_el1, x0
+ isb
+ ic iallu
+ dsb sy
+ tlbi vmalle1
+ dsb sy
+ isb
+
+c_entry:
+ /*
+ * program the counter frequency, the kernel reads CNTFRQ right
+ * away (booting.rst). qemu virt runs the system counter at
+ * 62.5 MHz. the register is RW only at the highest implemented EL.
+ */
+ mrs x0, CurrentEL
+ lsr x0, x0, #2
+ cmp x0, #2
+ b.lt 1f
+ ldr x0, =62500000
+ msr cntfrq_el0, x0
+ isb
+1:
+ /* our own vectors, so aborts print instead of vanishing */
+ adr x0, vectors
+ mrs x1, CurrentEL
+ lsr x1, x1, #2
+ cmp x1, #2
+ b.lt 2f
+ msr vbar_el2, x0
+ b 3f
+2:
+ msr vbar_el1, x0
+3:
+ isb
+
+ /* stack for the bootloader, its own region above the bss */
+ ldr x0, =__stack_top
+ mov sp, x0
+
+ /* export the spin gate array address for the dtb patcher */
+ adr x0, tb_spin_gates
+ adrp x1, tb_spin_gates_ptr
+ str x0, [x1, #:lo12:tb_spin_gates_ptr]
+
+ /* clear bss */
+ ldr x0, =__bss_start
+ ldr x1, =__bss_end
+1: cmp x0, x1
+ b.hs 2f
+ str xzr, [x0], #8
+ b 1b
+2:
+
+ /* FP/SIMD access, some kernels assume it is on */
+ mov x0, #(3 << 20)
+ msr cpacr_el1, x0
+ isb
+
+ /* dtb pointer into C arg 0 */
+ mov x0, x19
+ bl tashaboot_main
+
+ /* if main returns there is nothing sensible to do */
+/*
+ * the spin table pen, the Wait For Event mechanism from the manual
+ * (B2-144, D1-2255). each secondary watches its own gate, the
+ * cpu-release-addr the dtb names. WFE clears the event register and
+ * sleeps, the kernel writes the secondary entry to the gate, makes
+ * it visible, then SEV sets the event register on every PE. the load
+ * recheck after each wake covers a release that lands between the
+ * load and the WFE. entered with MMU and caches off, left the same.
+ */
+.globl park_ret
+park_ret:
+park:
+ adr x0, tb_spin_gates
+ mrs x1, mpidr_el1
+ and x1, x1, #0xff /* affinity 0, the core number */
+ add x0, x0, x1, lsl #3 /* gate = gates + core * 8 */
+
+ /* diagnostic: stamp arrival, primary prints it later */
+ adr x3, tb_pen_stamps
+ strb w1, [x3, x1]
+ sevl
+ wfe
+ sevl
+ wfe
+
+1:
+ ldr x2, [x0]
+ cbnz x2, 2f
+ wfe
+ b 1b
+2:
+ /* interrupts masked at release, the manual's boot state */
+ msr daifset, #0xf
+ /*
+ * every PE must read the same virtual counter. whatever
+ * ran before this loader could have left a per cpu offset
+ * in the virtual counter view, the kernel has no way to
+ * repair that itself. CNTVOFF_EL2 is writable at EL2 and
+ * the write holds for the EL1 virtual timer the kernel
+ * runs on. below EL2 it is out of reach, the reset value
+ * is the best a lower EL can do.
+ */
+ mrs x4, CurrentEL
+ lsr x4, x4, #2
+ cmp x4, #2
+ b.lt 3f
+ msr cntvoff_el2, xzr
+ isb
+3:
+ mov x0, xzr /* secondaries enter with x0-x3 zero */
+ mov x1, xzr
+ mov x2, xzr
+ mov x3, xzr
+ dsb sy
+ isb
+ br x2
+
+/*
+ * exception vectors, the armv8 layout: 16 slots, 128 bytes each, in
+ * the order the manual fixes. taken from EL1h the interesting slots
+ * are 0x200 sync and 0x380 SError, irq and fiq just park, the
+ * bootloader never enables interrupts on purpose.
+ */
+ .balign 2048
+vectors:
+ /* 0x000: current EL, SP_EL0 */
+ .align 7
+ b exc_sync
+ .align 7
+ b exc_park_irq
+ .align 7
+ b exc_park_irq
+ .align 7
+ b exc_serr
+
+ /* 0x200: current EL, SP_ELx */
+ .align 7
+ b exc_sync
+ .align 7
+ b exc_park_irq
+ .align 7
+ b exc_park_irq
+ .align 7
+ b exc_serr
+
+ /* 0x400: lower EL, AArch64 */
+ .align 7
+ b exc_sync
+ .align 7
+ b exc_park_irq
+ .align 7
+ b exc_park_irq
+ .align 7
+ b exc_serr
+
+ /* 0x600: lower EL, AArch32 */
+ .align 7
+ b exc_sync
+ .align 7
+ b exc_park_irq
+ .align 7
+ b exc_park_irq
+ .align 7
+ b exc_serr
+
+.pushsection .data.tb_spin, "aw"
+.align 3
+.globl tb_spin_gates
+tb_spin_gates:
+ .quad 0, 0, 0, 0, 0, 0, 0, 0
+.globl tb_spin_gates_ptr
+tb_spin_gates_ptr:
+ .quad 0
+.globl tb_pen_stamps
+tb_pen_stamps:
+ .byte 0, 0, 0, 0, 0, 0, 0, 0
+.popsection
+
+exc_sync:
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+ mrs x3, CurrentEL
+ lsr x3, x3, #2
+ cmp x3, #2
+ b.lt 1f
+ mrs x0, esr_el2
+ mrs x2, elr_el2
+ lsr x1, x0, #26
+ cmp x1, #0x16 /* HVC from lower EL */
+ b.eq hvc_from_el1
+ mrs x1, far_el2
+ b 2f
+1:
+ mrs x0, esr_el1
+ mrs x1, far_el1
+2:
+ /* x2 = the faulting PC when it is the sync path */
+ mrs x4, CurrentEL
+ lsr x4, x4, #2
+ cmp x4, #2
+ b.lt 3f
+ mrs x2, elr_el2
+ b 4f
+3:
+ mrs x2, elr_el1
+4:
+ bl exc_report
+ ldp x29, x30, [sp], #16
+ b park
+
+/*
+ * HVC from EL1, the PSCI conduit. x0-x3 are the PSCI args in the
+ * caller registers, dispatch and return in x0. ELR_EL2 is already
+ * the resume point, eret takes it back.
+ */
+hvc_from_el1:
+ /*
+ * the lower EL sync slot. three arrivals share it: PSCI hvc
+ * from the kernel (EC 0x16, PSCI id in x0), our own boot
+ * handoff (hvc with the payload entry in x8), and semihosting
+ * hlt #0xf000 from the EL1 C runtime (EC 0x14). qemu only
+ * answers the hlt when it executes at EL2, so the handler
+ * replays the trap at EL2 and erets home with the result.
+ */
+ mrs x1, esr_el2
+ lsr x1, x1, #26 /* EC */
+ cmp x1, #0x14 /* HLT from lower EL, semihosting */
+ b.eq smh_replay
+
+ /*
+ * the hvc arrives with either a PSCI function id in x0 (the
+ * kernel calling) or the boot handoff staging the payload
+ * entry in x8 and the dtb in x0. PSCI ids have the 0x84/0xc4
+ * prefix, a dtb pointer never does.
+ */
+ lsr x1, x0, #24
+ cmp x1, #0x84
+ b.eq psci_call
+ cmp x1, #0xc4
+ b.eq psci_call
+
+ /* the boot handoff: ELR_EL2 = entry, eret to the payload */
+ msr elr_el2, x8
+ eret
+
+smh_replay:
+ /*
+ * x0 holds the semihosting syscall number, x1 the parameter
+ * block, both live in the caller's registers. replay the hlt
+ * here at EL2 where qemu answers it, then eret back.
+ */
+ hlt #0xf000
+ eret
+
+psci_call:
+ stp x4, x5, [sp, #-16]!
+ stp x6, x7, [sp, #-16]!
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+
+ bl tb_psci_dispatch
+
+ ldp x29, x30, [sp], #16
+ ldp x6, x7, [sp], #16
+ ldp x4, x5, [sp], #16
+ ldp x29, x30, [sp], #16
+ eret
+
+exc_serr:
+ stp x29, x30, [sp, #-16]!
+ mov x29, sp
+ mrs x3, CurrentEL
+ lsr x3, x3, #2
+ cmp x3, #2
+ b.lt 1f
+ mrs x0, esr_el2
+ b 2f
+1:
+ mrs x0, esr_el1
+2:
+ mov x1, #0
+ mov x2, lr
+ bl exc_report
+ /*
+ * an SError while this loader runs means the machine is
+ * broken. handing the kernel a cpu that already lost is
+ * worse than stopping: report, then drive the reset domain
+ * the same way PSCI SYSTEM_RESET does. the reset call does
+ * not return, the park below is the fallback if a reset
+ * domain ignores the request.
+ */
+ bl tb_system_reset
+ ldp x29, x30, [sp], #16
+ b park
+
+exc_park_irq:
+ b park
diff --git a/arch/arm64/kernel/tashaboot.lds b/arch/arm64/kernel/tashaboot.lds
new file mode 100644
index 0000000..e2b7954
--- /dev/null
+++ b/arch/arm64/kernel/tashaboot.lds
@@ -0,0 +1,91 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * tashaboot arm64 memory layout. one segment, loaded at the bottom of
+ * RAM, right where qemu -kernel drops a raw image on virt.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
+OUTPUT_ARCH(aarch64)
+ENTRY(_start)
+
+SECTIONS
+{
+ /*
+ * the first 64 bytes are the arm64 Image header: code0 'b' over
+ * it, magic ARM\x64, text_offset 0. qemu -kernel parses the
+ * header, loads the file at 0x40000000 and enters at
+ * 0x40000000, where the branch lands on reset at 0x40000040.
+ * without the header qemu guesses text_offset 0x80000 and runs
+ * the whole loader from the wrong address.
+ */
+ . = 0x40000000;
+
+ __image_copy_start = .;
+ _text_start = .;
+
+ .text :
+ {
+ arch/arm64/kernel/start.o (.text.boot)
+ *(.text.boot)
+
+ /* the Image header, code0 branches over it */
+ . = ALIGN(64);
+ *(.text.imgheader)
+ . = ALIGN(64);
+
+ *(.text*)
+ }
+
+ . = ALIGN(8);
+ __text_end = .;
+
+ .rodata :
+ {
+ *(SORT_BY_ALIGNMENT(.rodata*))
+ }
+
+ . = ALIGN(8);
+ __rodata_end = .;
+
+ .data :
+ {
+ *(.data*)
+ }
+
+ . = ALIGN(8);
+ __image_end = .;
+
+ __bss_start = .;
+ .bss :
+ {
+ *(.bss*)
+ *(COMMON)
+ }
+ . = ALIGN(8);
+ __bss_end = .;
+
+ /*
+ * the stack lives in its own region, clear of bss. page tables
+ * and buffers are bss objects, a stack sharing their address
+ * space grows down into them and the first deep call crushes
+ * whatever it meets.
+ */
+ . = ALIGN(4096);
+ __stack_bottom = .;
+ . += 0x4000;
+ __stack_top = .;
+ __image_copy_end = .;
+
+ /DISCARD/ : { *(.dynsym) }
+ /DISCARD/ : { *(.dynstr*) }
+ /DISCARD/ : { *(.dynamic*) }
+ /DISCARD/ : { *(.plt*) }
+ /DISCARD/ : { *(.interp*) }
+ /DISCARD/ : { *(.gnu*) }
+ /DISCARD/ : { *(.ARM.attributes) }
+ /DISCARD/ : { *(.comment) }
+ /DISCARD/ : { *(.note*) }
+ /DISCARD/ : { *(.eh_frame*) }
+}
diff --git a/arch/arm64/lib/cache.S b/arch/arm64/lib/cache.S
new file mode 100644
index 0000000..d8ccea2
--- /dev/null
+++ b/arch/arm64/lib/cache.S
@@ -0,0 +1,100 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * cache.S - set/way cache maintenance, walked off CLIDR_EL1 the same
+ * way u-boot and the kernel's own __flush_dcache_all do it. needed
+ * before jumping to the payload so it starts from memory, not cache.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <asm/linkage.h>
+
+.pushsection .text.tb_dcache_level, "ax"
+ENTRY(tb_dcache_level)
+ lsl x12, x0, #1
+ msr csselr_el1, x12 /* select cache level */
+ isb /* sync change of ccsidr_el1 */
+ mrs x6, ccsidr_el1 /* read the new ccsidr_el1 */
+ ubfx x2, x6, #0, #3 /* x2 <- log2(cache line size)-4 */
+ ubfx x3, x6, #3, #10 /* x3 <- number of cache ways - 1 */
+ ubfx x4, x6, #13, #15 /* x4 <- number of cache sets - 1 */
+ add x2, x2, #4 /* x2 <- log2(cache line size) */
+ clz w5, w3 /* x5 <- bit position of #ways */
+ /* x12 <- cache level << 1 */
+ /* x2 <- line length offset */
+ /* x3 <- number of cache ways - 1 */
+ /* x4 <- number of cache sets - 1 */
+ /* x5 <- bit position of #ways */
+
+loop_set:
+ mov x6, x3 /* x6 <- working copy of #ways */
+loop_way:
+ lsl x7, x6, x5
+ orr x9, x12, x7 /* map way and level to cisw value */
+ lsl x7, x4, x2
+ orr x9, x9, x7 /* map set number to cisw value */
+ dc cisw, x9 /* clean & invalidate by set/way */
+ subs x6, x6, #1 /* decrement the way */
+ b.ge loop_way
+ subs x4, x4, #1 /* decrement the set */
+ b.ge loop_set
+
+ ret
+ENDPROC(tb_dcache_level)
+.popsection
+
+/*
+ * void tb_flush_dcache_all(void)
+ *
+ * clean & invalidate the whole D cache by set/way.
+ */
+.pushsection .text.tb_flush_dcache_all, "ax"
+ENTRY(tb_flush_dcache_all)
+ mov x1, x0
+ dsb sy
+ mrs x10, clidr_el1 /* read clidr_el1 */
+ ubfx x11, x10, #24, #3 /* x11 <- loc */
+ cbz x11, finished /* if loc is 0, exit */
+ mov x15, lr
+ mov x0, #0 /* start flush at cache level 0 */
+ /* x0 <- cache level */
+ /* x10 <- clidr_el1 */
+ /* x11 <- loc */
+ /* x15 <- return address */
+
+loop_level:
+ add x12, x0, x0, lsl #1 /* x12 <- tripled cache level */
+ lsr x12, x10, x12
+ and x12, x12, #7 /* x12 <- cache type */
+ cmp x12, #2
+ b.lt skip /* skip if no cache or icache */
+ bl tb_dcache_level /* flush this level */
+skip:
+ add x0, x0, #1 /* increment cache level */
+ cmp x11, x0
+ b.gt loop_level
+
+ mov x0, #0
+ msr csselr_el1, x0 /* restore csselr_el1 */
+ dsb sy
+ isb
+ mov lr, x15
+
+finished:
+ ret
+ENDPROC(tb_flush_dcache_all)
+.popsection
+
+/*
+ * void tb_invalidate_icache_all(void)
+ *
+ * I cache invalidation to PoU, one ic iallu covers the local core.
+ */
+.pushsection .text.tb_invalidate_icache_all, "ax"
+ENTRY(tb_invalidate_icache_all)
+ ic iallu
+ dsb sy
+ isb
+ ret
+ENDPROC(tb_invalidate_icache_all)
+.popsection
diff --git a/arch/arm64/lib/cache_va.c b/arch/arm64/lib/cache_va.c
new file mode 100644
index 0000000..1fd7804
--- /dev/null
+++ b/arch/arm64/lib/cache_va.c
@@ -0,0 +1,73 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * cache_va.c - cache maintenance by virtual address, the operations
+ * the manual prescribes for boot handoff: clean to point of
+ * coherency (dc cvac), invalidate (dc ivac), and clean and
+ * invalidate (dc civac), plus icache invalidate by VA to the point
+ * of unification (ic ivau). by VA beats by set and way when the
+ * address range is known, the manual's own guidance, set and way
+ * only for the full flush cases in cache.S.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <stdint.h>
+#include <sys/types.h>
+
+#define CACHE_LINE_SHIFT 6 /* 64 byte lines on cortex-a class */
+#define CACHE_LINE_SIZE (1 << CACHE_LINE_SHIFT)
+
+void tb_clean_dcache_range(uintptr_t start, size_t len)
+{
+ uintptr_t line = start & ~(uintptr_t)(CACHE_LINE_SIZE - 1);
+ uintptr_t end = start + len;
+
+ while (line < end) {
+ asm volatile("dc cvac, %0" :: "r" (line) : "memory");
+ line += CACHE_LINE_SIZE;
+ }
+
+ asm volatile("dsb sy" ::: "memory");
+}
+
+void tb_inval_dcache_range(uintptr_t start, size_t len)
+{
+ uintptr_t line = start & ~(uintptr_t)(CACHE_LINE_SIZE - 1);
+ uintptr_t end = start + len;
+
+ while (line < end) {
+ asm volatile("dc ivac, %0" :: "r" (line) : "memory");
+ line += CACHE_LINE_SIZE;
+ }
+
+ asm volatile("dsb sy" ::: "memory");
+}
+
+void tb_clean_inval_dcache_range(uintptr_t start, size_t len)
+{
+ uintptr_t line = start & ~(uintptr_t)(CACHE_LINE_SIZE - 1);
+ uintptr_t end = start + len;
+
+ while (line < end) {
+ asm volatile("dc civac, %0" :: "r" (line) : "memory");
+ line += CACHE_LINE_SIZE;
+ }
+
+ asm volatile("dsb sy" ::: "memory");
+}
+
+void tb_inval_icache_range(uintptr_t start, size_t len)
+{
+ uintptr_t line = start & ~(uintptr_t)(CACHE_LINE_SIZE - 1);
+ uintptr_t end = start + len;
+
+ while (line < end) {
+ asm volatile("ic ivau, %0" :: "r" (line) : "memory");
+ line += CACHE_LINE_SIZE;
+ }
+
+ asm volatile(
+ "dsb ish\n"
+ "isb\n"
+ ::: "memory");
+}
diff --git a/arch/arm64/lib/gic.c b/arch/arm64/lib/gic.c
new file mode 100644
index 0000000..647e987
--- /dev/null
+++ b/arch/arm64/lib/gic.c
@@ -0,0 +1,105 @@
+/*
+ * gic.c - the interrupt controller state a bootloader owns. the
+ * kernel programs the gic itself for the running system, but it
+ * trusts the state it inherits: on real hardware the secure
+ * world configures which interrupts are visible to non-secure,
+ * and a bootloader that leaves random enables or secure group
+ * bits set hands the kernel a half-configured distributor that
+ * can fire before the kernel's irqchip driver is up.
+ *
+ * this is the gicv2 sequence from the TRM, the same shape
+ * u-boot leaves the machine in: distributor off, every
+ * interrupt in the non-secure group, all per interrupt enables
+ * cleared, pending state cleared, cpu interfaces off. defined
+ * state, nothing firing, the kernel starts from zero.
+ *
+ * GICv1 shows the same register map minus the security
+ * extension registers, the writes below are harmless there.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <string.h>
+#include <stdint.h>
+#include <boot.h>
+#include <reg.h>
+
+/* distributor registers, offsets from the GICD base */
+#define GICD_CTLR 0x000
+#define GICD_TYPER 0x004
+#define GICD_ISENABLER(n) (0x100 + (n) * 4)
+#define GICD_ICENABLER(n) (0x180 + (n) * 4)
+#define GICD_ICPENDR(n) (0x280 + (n) * 4)
+#define GICD_ICACTIVER(n) (0x380 + (n) * 4)
+
+/* cpu interface registers, offsets from the GICC base */
+#define GICC_CTLR 0x000
+#define GICC_PMR 0x004
+
+/* GICD_CTLR bits */
+#define GICD_CTLR_ENABLE_GRP1 (1 << 0)
+#define GICD_CTLR_ENABLE_GRP0 (1 << 1)
+
+/* GICC_CTLR bits */
+#define GICC_CTLR_ENABLE (1 << 0)
+
+#define GICD_TYPER_ITLINES_MASK 0x1f
+
+/*
+ * how many 32-irq lines the distributor carries, TYPER.ITLines
+ * holds count of (irqs / 32) - 1, clamped per the spec because
+ * the field is 5 bits and caps at 1020 irqs.
+ */
+static int gicd_irq_lines(uintptr_t gicd)
+{
+ uint32_t typer = readl(REG32(gicd + GICD_TYPER));
+
+ return ((typer & GICD_TYPER_ITLINES_MASK) + 1);
+}
+
+/*
+ * leave the gic in the defined state the kernel expects. the
+ * addresses come from the devicetree the caller walked, qemu
+ * virt carries a gicv2 at 0x08000000 with the cpu interface at
+ * +0x10000.
+ */
+int tb_gic_init(uintptr_t gicd, uintptr_t gicc)
+{
+ int lines;
+ int n;
+
+ if (!gicd || !gicc)
+ return -1;
+
+ /* the distributor is off while it is reconfigured */
+ writel(0, REG32(gicd + GICD_CTLR));
+ writel(0, REG32(gicc + GICC_CTLR));
+
+ lines = gicd_irq_lines(gicd);
+
+ /*
+ * the group routing is deliberately untouched. the group
+ * registers are the secure world's, a non-secure loader's
+ * writes are dropped on hardware that implements the
+ * security extension, and on emulators that accept them
+ * the timer's per cpu interrupts stop reaching the
+ * kernel. group config belongs to the EL3 monitor, this
+ * loader runs without one.
+ */
+
+ /* no per interrupt enables, nothing pending */
+ for (n = 0; n < lines; n++) {
+ writel(0xffffffff, REG32(gicd + GICD_ICENABLER(n)));
+ writel(0xffffffff, REG32(gicd + GICD_ICPENDR(n)));
+ }
+
+ /*
+ * the cpu interface stays off with the priority mask at
+ * the lowest priority, the kernel raises it when it
+ * brings its own irq handling up. off is the defined
+ * state, the enable is the kernel's decision to make.
+ */
+ writel(0, REG32(gicc + GICC_PMR));
+
+ return 0;
+}
diff --git a/arch/arm64/lib/mmu.c b/arch/arm64/lib/mmu.c
new file mode 100644
index 0000000..03eb355
--- /dev/null
+++ b/arch/arm64/lib/mmu.c
@@ -0,0 +1,205 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * mmu.c - VMSAv8-64 stage 1 identity map for EL2.
+ *
+ * One level 0 table plus the subtables for the low 1GB of MMIO and
+ * the RAM region. everything is identity mapped, the bootloader
+ * never needs a different VA view, it just needs caching rules that
+ * let the payload start from an architecture-defined state.
+ *
+ * The descriptor layouts are from the manual (DDI 0487), level 0/1/2
+ * and level 3 formats at D5-2444 and D5-2447, attribute fields at
+ * D5-2451, MAIR at D5-2476. feature bits come from the ID registers,
+ * never hardcoded, the PA size from ID_AA64MMFR0_EL1.PARange per
+ * "Address size configuration" D5-2399.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <asm/mmu.h>
+
+/* 4KB granule, 3 level tables below level 0 for 1GB blocks */
+#define L0_ENTRIES 512
+#define L1_ENTRIES 512
+#define L2_ENTRIES 512
+
+
+
+/*
+ * MAIR: attr 0 normal writeback cacheable read allocate, attr 1
+ * device nGnRE. encodings straight from D5-2476, B2-122 for the
+ * memory types.
+ */
+#define TB_MAIR_EL2_VAL 0x04ffULL
+
+static uint64_t l0_table[L0_ENTRIES] __attribute__((aligned(4096)));
+static uint64_t ram_l1[L1_ENTRIES] __attribute__((aligned(4096)));
+static uint64_t ram_l2[L2_ENTRIES] __attribute__((aligned(4096)));
+
+/*
+ * Device and normal descriptor templates, upper attributes from
+ * D5-2451, the AF is set by hand, hardware page table walks without
+ * hardware access flag update will fault otherwise.
+ */
+#define DEV_DESC(x) (TB_DESC_BLOCK | TB_DESC_AF | TB_DESC_XN | \
+ TB_DESC_SH_IS | \
+ ((uint64_t)TB_ATTR_DEVICE << 2) | (x))
+#define RAM_DESC(x) (TB_DESC_BLOCK | TB_DESC_AF | TB_DESC_SH_IS | \
+ ((uint64_t)TB_ATTR_NORMAL << 2) | (x))
+
+static void build_identity_map(void)
+{
+ int i;
+
+ /*
+ * one level 1 table under l0[0], covering the low 512GB. the
+ * MMIO hole and RAM are both in it, device block at index 0
+ * (0..1GB) and the RAM table at index 1 (1GB..2GB).
+ */
+ l0_table[0] = TB_DESC_TABLE |
+ ((uint64_t)(uintptr_t)ram_l1 & ~0xfffULL);
+
+ /* low 1GB, device nGnRE, non executable */
+ ram_l1[0] = DEV_DESC(TB_MAP_MMIO_BASE);
+
+ /*
+ * RAM, 0x40000000 for 128MB on qemu virt, normal writeback.
+ * the level 2 table splits the 1GB into 2MB blocks so the map
+ * can be carved later.
+ */
+ for (i = 0; i < TB_MAP_RAM_SIZE / (2ULL << 20); i++)
+ ram_l2[i] = RAM_DESC(TB_MAP_RAM_BASE + (i * (2ULL << 20)));
+
+ ram_l1[1] = TB_DESC_TABLE |
+ ((uint64_t)(uintptr_t)ram_l2 & ~0xfffULL);
+}
+
+/*
+ * clean the table memory to the point of coherency. the tables were
+ * written with the dcache off, the page table walker reads them as
+ * memory the TCR walk attributes describe, and a dirty line sitting
+ * in the cache would never reach RAM. dc cvac is by cache line, walk
+ * every page of table memory.
+ */
+static void tb_clean_tables(void)
+{
+ uint64_t addr;
+ uint64_t tables[] = { (uint64_t)(uintptr_t)l0_table,
+ (uint64_t)(uintptr_t)ram_l1,
+ (uint64_t)(uintptr_t)ram_l2 };
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ for (addr = tables[i]; addr < tables[i] + 4096; addr += 64) {
+ asm volatile("dc cvac, %0" :: "r" (addr) : "memory");
+ }
+ }
+
+ asm volatile("dsb sy" ::: "memory");
+}
+
+static uint64_t read_parange(void)
+{
+ uint64_t ips;
+
+ asm volatile("mrs %0, id_aa64mmfr0_el1" : "=r" (ips));
+ return (ips >> 0) & 0xf;
+}
+
+/*
+ * EL aware enable. the EL1&0 regime registers at EL1, the EL2 regime
+ * registers at EL2, one code path per the manual, one translation
+ * regime per exception level (D1-2146).
+ */
+int tb_mmu_enable(void)
+{
+ uint64_t tcr, mair;
+ uint64_t el;
+
+ build_identity_map();
+ tb_clean_tables();
+
+ asm volatile("mrs %0, CurrentEL" : "=r" (el));
+ el >>= 2;
+
+ /* tcr value and PA size, D5-2399 address size configuration */
+ tcr = TB_TCR_T0SZ_48 | TB_TCR_SH0_IS | TB_TCR_TG0_4K |
+ TB_TCR_IRGN0_WB | TB_TCR_ORGN0_WB | TB_TCR_IPS(read_parange());
+ mair = TB_MAIR_EL2_VAL;
+
+ if (el == 2) {
+ asm volatile(
+ "dsb sy\n"
+ "msr ttbr0_el2, %1\n"
+ "msr tcr_el2, %2\n"
+ "msr mair_el2, %3\n"
+ "isb\n"
+ "tlbi alle2\n"
+ "dsb sy\n"
+ "ic iallu\n"
+ "dsb sy\n"
+ "isb\n"
+ : "=r" (tcr)
+ : "r" (l0_table), "r" (tcr), "r" (mair)
+ : "memory");
+ asm volatile(
+ "mrs x0, sctlr_el2\n"
+ "orr x0, x0, #1\n"
+ "msr sctlr_el2, x0\n"
+ "isb\n"
+ ::: "x0", "memory");
+ } else {
+ asm volatile(
+ "dsb sy\n"
+ "msr ttbr0_el1, %1\n"
+ "msr tcr_el1, %2\n"
+ "msr mair_el1, %3\n"
+ "isb\n"
+ "tlbi vmalle1\n"
+ "dsb sy\n"
+ "ic iallu\n"
+ "dsb sy\n"
+ "isb\n"
+ : "=r" (tcr)
+ : "r" (l0_table), "r" (tcr), "r" (mair)
+ : "memory");
+ asm volatile(
+ "mrs x0, sctlr_el1\n"
+ "orr x0, x0, #1\n"
+ "msr sctlr_el1, x0\n"
+ "isb\n"
+ ::: "x0", "memory");
+ }
+
+ return 0;
+}
+
+void tb_mmu_disable(void)
+{
+ uint64_t el;
+
+ asm volatile("mrs %0, CurrentEL" : "=r" (el));
+ el >>= 2;
+
+ if (el == 2) {
+ asm volatile(
+ "mrs x0, sctlr_el2\n"
+ "bic x0, x0, #1\n"
+ "msr sctlr_el2, x0\n"
+ "dsb sy\n"
+ "tlbi alle2\n"
+ "dsb sy\n"
+ "isb\n"
+ ::: "x0", "memory");
+ } else {
+ asm volatile(
+ "mrs x0, sctlr_el1\n"
+ "bic x0, x0, #1\n"
+ "msr sctlr_el1, x0\n"
+ "dsb sy\n"
+ "tlbi vmalle1\n"
+ "dsb sy\n"
+ "isb\n"
+ ::: "x0", "memory");
+ }
+}
diff --git a/arch/arm64/lib/psci.c b/arch/arm64/lib/psci.c
new file mode 100644
index 0000000..ad5f461
--- /dev/null
+++ b/arch/arm64/lib/psci.c
@@ -0,0 +1,118 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * psci.c - PSCI 0.2 at EL2, the calls a payload makes to control
+ * cores and the system, per DEN 0022. the call arrives as an HVC
+ * trap at EL2 (EC 0x16), x0 holds the function id, x1 to x3 the
+ * arguments, the return value goes back in x0 and eret resumes the
+ * caller at EL1.
+ *
+ * CPU_ON writes the spin gate of the target core and SEVs, the pen
+ * from start.S does the release. CPU_OFF parks the calling core.
+ * SYSTEM_OFF and SYSTEM_RESET drive the architecture reset domain.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <asm/psci.h>
+#include <debug.h>
+
+extern void tb_system_reset(void);
+extern void tb_system_off(void);
+
+/* the gates and stamps from start.S, one per possible core */
+extern unsigned long tb_spin_gates[8];
+extern unsigned char tb_pen_stamps[8];
+
+static uint64_t psci_cpu_on(uint64_t target, uint64_t entry,
+ uint64_t ctx)
+{
+ unsigned long mpidr;
+ int cpu;
+
+ /* affinity 0 only, our gate array indexes cores 0..7 */
+ if (target > 7)
+ return PSCI_RET_INVALID_PARAMS;
+
+ asm volatile("mrs %0, mpidr_el1" : "=r" (mpidr));
+ if ((mpidr & 0xff) == target)
+ return PSCI_RET_ALREADY_ON;
+
+ cpu = (int)target;
+
+ /*
+ * the pen saves no context, CPU_ON per DEN 0022 passes an
+ * entry and a context id. the pen enters with x0 = ctx, the
+ * kernel secondary entry takes x0 as its context pointer.
+ * the gate holds the entry, the stamp array the ctx.
+ */
+ tb_spin_gates[cpu] = entry;
+ tb_pen_stamps[cpu] = (unsigned char)(ctx & 0xff);
+
+ /* make the gate write visible before the wake, D1-2255 */
+ asm volatile("dsb sy");
+ asm volatile("sev");
+
+ dprintf(ALWAYS, "psci: cpu_on %llu -> %llx\n",
+ (unsigned long long)target,
+ (unsigned long long)entry);
+
+ return PSCI_RET_SUCCESS;
+}
+
+extern void park_ret(void);
+
+static uint64_t psci_cpu_off(void)
+{
+ unsigned long mpidr;
+ int cpu;
+
+ asm volatile("mrs %0, mpidr_el1" : "=r" (mpidr));
+ cpu = (int)(mpidr & 0xff);
+
+ if (cpu > 7)
+ return PSCI_RET_NOT_SUPPORTED;
+
+ /* clear our own gate and go back to the pen */
+ tb_spin_gates[cpu] = 0;
+
+ dprintf(ALWAYS, "psci: cpu_off %d\n", cpu);
+
+ asm volatile(
+ "dsb sy\n"
+ "b park_ret\n"
+ );
+
+ return PSCI_RET_INTERNAL_FAIL; /* not reached */
+}
+
+/*
+ * the asm vector calls this with the caller x0-x3 still in place,
+ * function id in x0, arguments in x1-x3, the return lands in x0.
+ */
+uint64_t tb_psci_dispatch(uint64_t fn, uint64_t x1, uint64_t x2,
+ uint64_t x3)
+{
+ switch (fn) {
+ case PSCI_FN_VERSION:
+ return PSCI_VERSION_0_2;
+
+ case PSCI_FN_CPU_ON:
+ return psci_cpu_on(x1, x2, x3);
+
+ case PSCI_FN_CPU_OFF:
+ return psci_cpu_off();
+
+ case PSCI_FN_SYSTEM_OFF:
+ dprintf(ALWAYS, "psci: system off\n");
+ tb_system_off();
+ return PSCI_RET_SUCCESS;
+
+ case PSCI_FN_SYSTEM_RESET:
+ dprintf(ALWAYS, "psci: system reset\n");
+ tb_system_reset();
+ return PSCI_RET_INTERNAL_FAIL; /* not reached */
+
+ default:
+ return PSCI_RET_NOT_SUPPORTED;
+ }
+}
diff --git a/arch/arm64/lib/semihosting.S b/arch/arm64/lib/semihosting.S
new file mode 100644
index 0000000..6e3fc31
--- /dev/null
+++ b/arch/arm64/lib/semihosting.S
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * semihosting.S - the trap instruction itself. qemu answers this when
+ * it is started with -semihosting, and nothing happens without it, so
+ * every caller has to cope with the no-debugger case.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <asm/linkage.h>
+
+.pushsection .text.smh_trap, "ax"
+/* long smh_trap(unsigned int sysnum, void *addr); */
+ENTRY(smh_trap)
+ hlt #0xf000
+ ret
+ENDPROC(smh_trap)
+.popsection
diff --git a/arch/arm64/lib/system.c b/arch/arm64/lib/system.c
new file mode 100644
index 0000000..25af2bf
--- /dev/null
+++ b/arch/arm64/lib/system.c
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * system.c - system power control, the PSCI SYSTEM_OFF and
+ * SYSTEM_RESET backends. off parks the core in WFI forever, the
+ * manual's low power entry (D1-2255). reset drives the PE reset
+ * domain: RMR_EL2 reset request with the system reset bit, RR bit 1,
+ * followed by a barrier pair so the request retires before anything
+ * else observes the core.
+ *
+ * On real hardware a SoC also needs a watchdog or PMIC write for a
+ * full board reset, that is board territory, the arch part is this.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <debug.h>
+#include <stdint.h>
+
+void tb_system_off(void)
+{
+ dprintf(ALWAYS, "system off\n");
+
+ for (;;) {
+ asm volatile("wfi");
+ }
+}
+
+void tb_system_reset(void)
+{
+ uint64_t rmr;
+ uint64_t el;
+ register uint64_t r0 asm("x0") = 0x84000008;
+ register uint64_t r1 asm("x1") = 0;
+ register uint64_t r2 asm("x2") = 0;
+ register uint64_t r3 asm("x3") = 0;
+
+ dprintf(ALWAYS, "system reset\n");
+
+ /*
+ * the firmware conduit first, PSCI SYSTEM_RESET through
+ * the machine's own monitor. this is the only legal way
+ * up from EL1: RMR_EL1 is undefined on hardware that
+ * implements a higher exception level, the access traps
+ * and the machine never resets.
+ */
+ asm volatile("smc #0"
+ : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3));
+
+ /*
+ * no monitor answered, or it refused. ask the reset
+ * domain directly from a level that may write it.
+ */
+ asm volatile("mrs %0, CurrentEL" : "=r" (el));
+ el >>= 2;
+
+ if (el >= 2) {
+ asm volatile("mrs %0, rmr_el2" : "=r" (rmr));
+ rmr |= (1 << 1); /* RR, request reset */
+ asm volatile(
+ "msr rmr_el2, %0\n"
+ "dsb sy\n"
+ "isb\n"
+ :: "r" (rmr));
+ }
+
+ /* nothing worked, park */
+ for (;;) {
+ asm volatile("wfi");
+ }
+}
diff --git a/arch/arm64/lib/timer.c b/arch/arm64/lib/timer.c
new file mode 100644
index 0000000..605cd18
--- /dev/null
+++ b/arch/arm64/lib/timer.c
@@ -0,0 +1,48 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * timer.c - generic timer delays, the system counter from D10. the
+ * counter is a fixed frequency free running counter, CNTFRQ_EL0
+ * carries the frequency, CNTVCT_EL0 the 64 bit count. delays are a
+ * busy wait on the counter, no interrupts needed, microsecond and
+ * millisecond granularity.
+ *
+ * CNTVCT_EL0 is the virtual counter view; at EL2 with no offset
+ * configured it is the physical count. the read is not speculative
+ * and needs an isb to serialize against subsequent counter reads
+ * per the counter access rules.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <stdint.h>
+
+static uint64_t read_cntfrq(void)
+{
+ uint64_t v;
+
+ asm volatile("mrs %0, cntfrq_el0" : "=r" (v));
+ return v;
+}
+
+static uint64_t read_counter(void)
+{
+ uint64_t v;
+
+ asm volatile("isb\nmrs %0, cntvct_el0" : "=r" (v));
+ return v;
+}
+
+void tb_udelay(uint32_t us)
+{
+ uint64_t freq = read_cntfrq();
+ uint64_t start = read_counter();
+ uint64_t ticks = (uint64_t)us * freq / 1000000ULL;
+
+ while (read_counter() - start < ticks)
+ ;
+}
+
+void tb_mdelay(uint32_t ms)
+{
+ tb_udelay(ms * 1000);
+}
diff --git a/busybox-static_1%3a1.37.0-7ubuntu1_arm64.deb b/busybox-static_1%3a1.37.0-7ubuntu1_arm64.deb
new file mode 100644
index 0000000..9f011cb
--- /dev/null
+++ b/busybox-static_1%3a1.37.0-7ubuntu1_arm64.deb
Binary files differ
diff --git a/common/console.c b/common/console.c
new file mode 100644
index 0000000..64e5128
--- /dev/null
+++ b/common/console.c
@@ -0,0 +1,164 @@
+/*
+ * console.c - the console dprintf writes to. two sinks: the pl011
+ * PrimeCell uart every qemu virt and SBSA board carries (DDI 0183),
+ * and semihosting SYS_WRITE0, the firmware service on the qemu dev
+ * path. the uart is the real hardware path, semihosting the dev
+ * path, the probe at init picks whichever answers.
+ *
+ * Copyright (c) 2026 Bradley Morgan <brads@mainlining.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <stdint.h>
+#include <debug.h>
+#include <semihosting.h>
+
+/* pl011 register map, DDI 0183, offsets from the base */
+#define UART_DR 0x00 /* data register */
+#define UART_FR 0x18 /* flag register */
+#define UART_FR_BUSY (1 << 3)
+#define UART_FR_TXFF (1 << 5)
+#define UART_IBRD 0x24
+#define UART_FBRD 0x28
+#define UART_LCRH 0x2c
+#define UART_CR 0x30
+#define UART_CR_UARTEN (1 << 0)
+#define UART_CR_TXE (1 << 8)
+#define UART_CR_RXE (1 << 9)
+#define UART_IMSC 0x38
+#define UART_ICR 0x44
+
+/*
+ * 115200 8n1 at a 24 MHz reference clock. IBRD = 24e6 / (16 * 115200)
+ * = 13, FBRD = int(0.6875 * 64 + 0.5) = 44.
+ */
+#define UART_IBRD_VAL 13
+#define UART_FBRD_VAL 44
+
+/* the base comes from the devicetree walk, the qemu default
+ * only covers the dev path before the walk runs
+ */
+static uintptr_t pl011_base = 0x09000000UL;
+
+void tb_console_set_pl011(uintptr_t base)
+{
+ if (base)
+ pl011_base = base;
+}
+
+static int console_uart_ok;
+
+static void uart_putc(char c)
+{
+ volatile uint32_t *fr = (volatile uint32_t *)(pl011_base + UART_FR);
+ volatile uint32_t *dr = (volatile uint32_t *)(pl011_base + UART_DR);
+
+ /* TXFF can happen mid line on slow consoles, wait it out */
+ while (*fr & UART_FR_TXFF)
+ ;
+ *dr = (uint32_t)(unsigned char)c;
+}
+
+/*
+ * pl011 probe and bringup: uart off, baud divisor, fifo on, then
+ * enable tx. the clock here is the qemu virt reference, a real board
+ * overrides the divisors from its clock tree, that is board
+ * territory, the arch part is the sequence.
+ */
+static int uart_init(void)
+{
+ volatile uint32_t *cr = (volatile uint32_t *)(pl011_base + UART_CR);
+ volatile uint32_t *ibrd = (volatile uint32_t *)(pl011_base + UART_IBRD);
+ volatile uint32_t *fbrd = (volatile uint32_t *)(pl011_base + UART_FBRD);
+ volatile uint32_t *lcrh = (volatile uint32_t *)(pl011_base + UART_LCRH);
+ volatile uint32_t *imsc = (volatile uint32_t *)(pl011_base + UART_IMSC);
+ volatile uint32_t *icr = (volatile uint32_t *)(pl011_base + UART_ICR);
+
+ /* disable, mask irq, clear pending, divisors, fifo, enable tx */
+ *cr = 0;
+ *imsc = 0;
+ *icr = 0x7ff;
+ *ibrd = UART_IBRD_VAL;
+ *fbrd = UART_FBRD_VAL;
+ *lcrh = (3 << 5) | (1 << 4); /* 8n1, fifo enabled */
+ *cr = UART_CR_UARTEN | UART_CR_TXE | UART_CR_RXE;
+
+ /* self test write, TXFF clearing means the uart answers */
+ uart_putc('\0');
+ while (*(volatile uint32_t *)(pl011_base + UART_FR) & UART_FR_BUSY)
+ ;
+
+ return 0;
+}
+
+/*
+ * lk's _dprintf sink. printf buffers a line here then hands it to
+ * the sink, semihosting wants zero terminated strings not counts.
+ */
+#define TB_CONSOLE_MAX 256
+
+static char console_buf[TB_CONSOLE_MAX];
+static size_t console_len;
+
+static void console_flush(void)
+{
+ size_t i;
+
+ if (console_len == 0)
+ return;
+
+ if (console_uart_ok) {
+ for (i = 0; i < console_len; i++)
+ uart_putc(console_buf[i]);
+ } else {
+ console_buf[console_len] = '\0';
+ smh_write0(console_buf);
+ }
+ console_len = 0;
+}
+
+void _putchar(char c)
+{
+ if (console_len >= TB_CONSOLE_MAX - 1)
+ console_flush();
+ if (c == '\n') {
+ /* terminals want cr lf, not lf alone */
+ console_buf[console_len++] = '\r';
+ }
+ console_buf[console_len++] = c;
+ if (c == '\n')
+ console_flush();
+}
+
+int tb_console_init(void)
+{
+ /*
+ * try the uart first, real hardware. semihosting is the qemu
+ * dev path, SYS_GET_ERRNO with nothing open, a host answers,
+ * bare metal ignores the trap.
+ */
+ uart_init();
+ console_uart_ok = 1;
+ console_len = 0;
+ (void)smh_probe();
+ return 0;
+}
diff --git a/common/dtb_find.c b/common/dtb_find.c
new file mode 100644
index 0000000..29a67f3
--- /dev/null
+++ b/common/dtb_find.c
@@ -0,0 +1,178 @@
+/*
+ * dtb_find.c - locate nodes and read reg by walking the flat
+ * devicetree. the machine tells the firmware where its devices
+ * live, a bootloader that hardcodes the gic address breaks on
+ * the first board with a different map.
+ *
+ * the walk is the standard token scan, FDT_BEGIN_NODE with a
+ * matching name at any depth, then the reg property inside,
+ * the first address/size pair decoded per the parent's cell
+ * counts, which the root carries in #address-cells and
+ * #size-cells.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <string.h>
+#include <stdint.h>
+#include <boot.h>
+
+#define FDT_BEGIN_NODE 1
+#define FDT_END_NODE 2
+#define FDT_PROP 3
+#define FDT_NOP 4
+#define FDT_END 9
+
+static uint32_t be32(const void *p)
+{
+ const uint8_t *b = p;
+
+ return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) |
+ ((uint32_t)b[2] << 8) | (uint32_t)b[3];
+}
+
+static int name_eq(const char *a, const char *b)
+{
+ while (*a && *a != '@') {
+ if (*a != *b)
+ return 0;
+ a++;
+ b++;
+ }
+ return *b == '\0' || *b == '@';
+}
+
+/*
+ * find the first node whose name matches, at any depth. returns
+ * the offset of its FDT_BEGIN_NODE token or 0 when absent.
+ */
+static uint32_t fdt_find_node(uintptr_t dtb, const char *name)
+{
+ uint8_t *basep = (uint8_t *)dtb;
+ uint32_t off_struct = be32(basep + 8);
+ uint32_t totalsize = be32(basep + 4);
+ uint8_t *p = basep + off_struct;
+
+ if (be32(basep) != 0xd00dfeed)
+ return 0;
+
+ while (p < basep + totalsize) {
+ uint32_t token = be32(p);
+
+ if (token == FDT_BEGIN_NODE) {
+ char *n = (char *)(p + 4);
+ size_t nlen = strlen(n) + 1;
+
+ if (name_eq(n, name))
+ return (uint32_t)(p - basep);
+ p += 4 + ((nlen + 3) & ~3);
+ } else if (token == FDT_PROP) {
+ uint32_t plen = be32(p + 4);
+
+ p += 12 + ((plen + 3) & ~3);
+ } else if (token == FDT_END_NODE ||
+ token == FDT_NOP) {
+ p += 4;
+ } else if (token == FDT_END) {
+ break;
+ } else {
+ return 0;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * read the first reg pair of a node at the given token offset,
+ * honoring the root cell counts. pairs of 2 or 4 cells are the
+ * ones machines carry, anything else fails. the caller reads
+ * more pairs off the returned cursor if it needs them.
+ */
+int tb_dtb_reg0(uintptr_t dtb, uint32_t node_off, uintptr_t *addr,
+ size_t *size)
+{
+ uint8_t *basep = (uint8_t *)dtb;
+ uint32_t off_strings = be32(basep + 12);
+ uint8_t *p = basep + node_off;
+ uint32_t totalsize = be32(basep + 4);
+ uint32_t ac = 2;
+ uint32_t sc = 2;
+ /*
+ * zero, the node's own FDT_BEGIN_NODE below brings it to
+ * one and the props inside sit at depth one. starting at
+ * one instead skips every prop in the node.
+ */
+ int depth_open = 0;
+
+ while (p < basep + totalsize) {
+ uint32_t token = be32(p);
+
+ if (token == FDT_BEGIN_NODE) {
+ char *n = (char *)(p + 4);
+ size_t nlen = strlen(n) + 1;
+
+ depth_open++;
+ p += 4 + ((nlen + 3) & ~3);
+ } else if (token == FDT_END_NODE) {
+ depth_open--;
+ if (!depth_open)
+ return -1;
+ p += 4;
+ } else if (token == FDT_PROP) {
+ uint32_t plen = be32(p + 4);
+ const char *pname =
+ (char *)basep + off_strings + be32(p + 8);
+ uint8_t *val = p + 12;
+
+ if (depth_open == 1 &&
+ strcmp(pname, "#address-cells") == 0)
+ ac = be32(val);
+ if (depth_open == 1 &&
+ strcmp(pname, "#size-cells") == 0)
+ sc = be32(val);
+ if (depth_open == 1 && strcmp(pname, "reg") == 0) {
+ if (plen >= (ac + sc) * 4) {
+ uint64_t a = 0;
+ uint64_t s = 0;
+
+ for (uint32_t i = 0; i < ac; i++)
+ a = (a << 32) |
+ be32(val + i * 4);
+ for (uint32_t i = 0; i < sc; i++)
+ s = (s << 32) |
+ be32(val + (ac + i) * 4);
+ *addr = (uintptr_t)a;
+ if (size)
+ *size = (size_t)s;
+ return 0;
+ }
+ return -1;
+ }
+ p += 12 + ((plen + 3) & ~3);
+ } else if (token == FDT_NOP) {
+ p += 4;
+ } else if (token == FDT_END) {
+ return -1;
+ } else {
+ return -1;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * the whole lookup in one call: find the node, read its first
+ * reg pair.
+ */
+int tb_dtb_find_reg0(uintptr_t dtb, const char *name, uintptr_t *addr,
+ size_t *size)
+{
+ uint32_t off = fdt_find_node(dtb, name);
+
+ if (!off)
+ return -1;
+
+ return tb_dtb_reg0(dtb, off, addr, size);
+}
diff --git a/common/dtb_grow.c b/common/dtb_grow.c
new file mode 100644
index 0000000..309a43c
--- /dev/null
+++ b/common/dtb_grow.c
@@ -0,0 +1,177 @@
+/*
+ * dtb_grow.c - add properties to a node in a devicetree that has
+ * room, the relocated copy from dtb_reloc.c. the insert point is
+ * the node's FDT_END_NODE token, everything after it moves up by
+ * the inserted size, the header totalsize tracks it.
+ *
+ * the insert is safe when the node sits at the end of the struct
+ * block, which is the common shape, /chosen is created last by
+ * firmware and the tail behind it is two end tokens and the
+ * block end. the strings block sits after the grow room and
+ * never moves.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <string.h>
+#include <endian.h>
+#include <boot.h>
+#include <dtb_patch.h>
+
+#define FDT_BEGIN_NODE 1
+#define FDT_END_NODE 2
+#define FDT_PROP 3
+#define FDT_NOP 4
+#define FDT_END 9
+
+static uint32_t be32(const void *p)
+{
+ const uint8_t *b = p;
+
+ return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) |
+ ((uint32_t)b[2] << 8) | (uint32_t)b[3];
+}
+
+static void put_be32(void *p, uint32_t v)
+{
+ uint8_t *b = p;
+
+ b[0] = (uint8_t)(v >> 24);
+ b[1] = (uint8_t)(v >> 16);
+ b[2] = (uint8_t)(v >> 8);
+ b[3] = (uint8_t)v;
+}
+
+static int name_eq(const char *a, const char *b)
+{
+ while (*a && *a != '@') {
+ if (*a != *b)
+ return 0;
+ a++;
+ b++;
+ }
+ return *b == '\0' || *b == '@';
+}
+
+/*
+ * insert one property into /chosen before its end token. value is
+ * copied as raw cells, len the byte count. name lands in the free
+ * space after the strings block. returns 0 or -1.
+ */
+int tb_dtb_add_chosen_prop(uintptr_t dtb, const char *name,
+ const void *val, size_t len)
+{
+ uint8_t *basep = (uint8_t *)dtb;
+ uint32_t off_struct = be32(basep + 8);
+ uint32_t off_strings = be32(basep + 12);
+ uint32_t totalsize = be32(basep + 4);
+ uint8_t *p = basep + off_struct;
+ uint8_t *ins;
+ size_t name_len = strlen(name) + 1;
+ size_t prop_size;
+ int depth = 0;
+ int in_chosen = 0;
+
+ if (be32(basep) != 0xd00dfeed)
+ return -1;
+
+ /* find the chosen node's end token, one level under the root */
+ while (p < basep + totalsize) {
+ uint32_t token = be32(p);
+
+ if (token == FDT_BEGIN_NODE) {
+ char *n = (char *)(p + 4);
+ size_t nlen = strlen(n) + 1;
+
+ depth++;
+ if (depth == 2 && name_eq(n, "chosen"))
+ in_chosen = 1;
+ p += 4 + ((nlen + 3) & ~3);
+ } else if (token == FDT_END_NODE) {
+ if (in_chosen && depth == 2) {
+ ins = p;
+ break;
+ }
+ depth--;
+ p += 4;
+ } else if (token == FDT_PROP) {
+ uint32_t plen = be32(p + 4);
+
+ p += 12 + ((plen + 3) & ~3);
+ } else if (token == FDT_NOP) {
+ p += 4;
+ } else if (token == FDT_END) {
+ break;
+ } else {
+ return -1;
+ }
+ }
+
+ if (!ins)
+ return -2;
+
+ ins = p;
+
+ /*
+ * the insert: the strings block moves up by prop_size so the
+ * struct block can grow into its old place, the struct tail
+ * after chosen moves up by prop_size, the new name lands at
+ * the end of the moved strings block, and totalsize covers
+ * both. prop name offsets are strings relative so they keep
+ * resolving after the move.
+ */
+ {
+ prop_size = 12 + ((len + 3) & ~3);
+ size_t strings_len = (size_t)be32(basep + 32);
+
+ /* strings block up by prop_size */
+ for (size_t i = strings_len; i > 0; i--)
+ basep[off_strings + prop_size + i - 1] =
+ basep[off_strings + i - 1];
+
+ /* struct tail after the insert point up by prop_size */
+ {
+ size_t tail = (size_t)(basep + off_strings - ins);
+
+ for (size_t i = tail; i > 0; i--)
+ ins[i + prop_size - 1] = ins[i - 1];
+ }
+
+ /* the prop token, name offset = old strings length */
+ put_be32(ins, FDT_PROP);
+ put_be32(ins + 4, (uint32_t)len);
+ put_be32(ins + 8, (uint32_t)strings_len);
+ for (size_t i = 0; i < len; i++)
+ ins[12 + i] = ((const uint8_t *)val)[i];
+ for (size_t i = len; i < ((len + 3) & ~3); i++)
+ ins[12 + i] = 0;
+
+ /* the name at the end of the moved strings block */
+ for (size_t i = 0; i < name_len; i++)
+ basep[off_strings + prop_size + strings_len + i] =
+ name[i];
+
+ /*
+ * size_dt_struct bounds the token walk, libfdt
+ * rejects anything past it as BADSTRUCTURE. it grows
+ * by the prop size here, the strings size by the name
+ * length, totalsize by both.
+ */
+ put_be32(basep + 4, totalsize + (uint32_t)prop_size +
+ (uint32_t)name_len);
+ put_be32(basep + 12, off_strings + (uint32_t)prop_size);
+ put_be32(basep + 36, be32(basep + 36) + (uint32_t)prop_size);
+ /*
+ * size_dt_strings must grow too, libfdt validates
+ * name offsets against it and rejects the whole tree
+ * when the new names sit past the declared end. the
+ * kernel's early parser is the same libfdt, a stale
+ * field there means no memory node and a page table
+ * panic before the first print.
+ */
+ put_be32(basep + 32, (uint32_t)strings_len +
+ (uint32_t)name_len);
+ }
+
+ return 0;
+}
diff --git a/common/dtb_patch.c b/common/dtb_patch.c
new file mode 100644
index 0000000..cd9a1f3
--- /dev/null
+++ b/common/dtb_patch.c
@@ -0,0 +1,288 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * dtb_patch.c - rewrite cpu-release-addr values in a flattened
+ * devicetree, in place, no libfdt, no structural change. the walk
+ * follows the devicetree specification structure, FDT_BEGIN_NODE
+ * then name then properties then children then FDT_END_NODE, all
+ * tokens and lengths big endian, everything 4 byte aligned.
+ *
+ * The bootloader owns the spin gates, the dtb names them, this
+ * writes the real addresses over the build time placeholders.
+ * The enable-method conversion and the placeholder properties are
+ * done at build time on the host, a firmware dtb is a fixed blob,
+ * only the gate addresses depend on where the image actually landed.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <string.h>
+#include <endian.h>
+#include <dtb_patch.h>
+
+#define FDT_BEGIN_NODE 1
+#define FDT_END_NODE 2
+#define FDT_PROP 3
+#define FDT_NOP 4
+#define FDT_END 9
+
+static uint32_t be32(const void *p)
+{
+ const uint8_t *b = p;
+ return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) |
+ ((uint32_t)b[2] << 8) | (uint32_t)b[3];
+}
+
+static void put_be32(void *p, uint32_t v)
+{
+ uint8_t *b = p;
+ b[0] = (uint8_t)(v >> 24);
+ b[1] = (uint8_t)(v >> 16);
+ b[2] = (uint8_t)(v >> 8);
+ b[3] = (uint8_t)v;
+}
+
+static void put_be64(void *p, uint64_t v)
+{
+ uint8_t *b = p;
+ b[0] = (uint8_t)(v >> 56);
+ b[1] = (uint8_t)(v >> 48);
+ b[2] = (uint8_t)(v >> 40);
+ b[3] = (uint8_t)(v >> 32);
+ b[4] = (uint8_t)(v >> 24);
+ b[5] = (uint8_t)(v >> 16);
+ b[6] = (uint8_t)(v >> 8);
+ b[7] = (uint8_t)v;
+}
+
+static int name_eq(const char *node, const char *want)
+{
+ while (*node && *node != '@') {
+ if (*node != *want)
+ return 0;
+ node++;
+ want++;
+ }
+ return *want == '\0';
+}
+
+/*
+ * rewrite /memory reg with the RAM the bootloader actually sees.
+ * the value is two u32 cells, base and size, addresses above 4GB
+ * need the parent #address-cells respected, virt is below 4GB and
+ * 2 cells for size. returns 0 on success.
+ */
+int tb_dtb_patch_memory(uintptr_t dtb, uint64_t base, uint64_t size)
+{
+ uint8_t *basep = (uint8_t *)dtb;
+ uint32_t off_struct = be32(basep + 8);
+ uint32_t off_strings = be32(basep + 12);
+ uint8_t *p = basep + off_struct;
+ uint8_t *strings = basep + off_strings;
+ const char *cur_node = NULL;
+ int depth = 0;
+
+ if (be32(basep) != 0xd00dfeed)
+ return -1;
+
+ while (p < basep + be32(basep + 4)) {
+ uint32_t token = be32(p);
+
+ switch (token) {
+ case FDT_BEGIN_NODE: {
+ char *name = (char *)(p + 4);
+ size_t len = strlen(name) + 1;
+
+ p += 4 + ((len + 3) & ~3);
+ depth++;
+ cur_node = name;
+ break;
+ }
+ case FDT_END_NODE:
+ depth--;
+ p += 4;
+ break;
+ case FDT_PROP: {
+ uint32_t plen = be32(p + 4);
+ const char *pname = (char *)strings + be32(p + 8);
+ uint8_t *val = p + 12;
+
+ p += 12 + ((plen + 3) & ~3);
+
+ if (depth == 2 && name_eq(cur_node, "memory") &&
+ strcmp(pname, "reg") == 0 && plen >= 16) {
+ /*
+ * #address-cells 2, #size-cells 2, the
+ * reg is four cells, base hi lo and
+ * size hi lo, below 4GB the hi cells
+ * are zero.
+ */
+ put_be32(val, (uint32_t)(base >> 32));
+ put_be32(val + 4, (uint32_t)base);
+ put_be32(val + 8, (uint32_t)(size >> 32));
+ put_be32(val + 12, (uint32_t)size);
+ return 0;
+ }
+ break;
+ }
+ case FDT_NOP:
+ p += 4;
+ break;
+ case FDT_END:
+ return -2;
+ }
+ }
+
+ return -3;
+}
+
+/*
+ * walk and rewrite. returns the number of cpu-release-addr values
+ * written, negative on a malformed blob.
+ */
+int tb_dtb_patch_spin_table(uintptr_t dtb, uintptr_t *gates, int ngates)
+{
+ uint8_t *base = (uint8_t *)dtb;
+ uint32_t off_struct = be32(base + 8);
+ uint32_t off_strings = be32(base + 12);
+ uint8_t *p = base + off_struct;
+ uint8_t *strings = base + off_strings;
+ const char *cur_cpu = NULL;
+ int in_cpus = 0;
+ int written = 0;
+ int depth = 0;
+
+ if (be32(base) != 0xd00dfeed)
+ return -1;
+
+ while (p < base + be32(base + 4)) {
+ uint32_t token = be32(p);
+
+ switch (token) {
+ case FDT_BEGIN_NODE: {
+ char *name = (char *)(p + 4);
+ size_t len = strlen(name) + 1;
+
+ p += 4 + ((len + 3) & ~3);
+ depth++;
+
+ if (depth == 2 && name_eq(name, "cpus")) {
+ in_cpus = 1;
+ } else if (depth == 2) {
+ in_cpus = 0;
+ } else if (in_cpus && depth == 3) {
+ cur_cpu = name;
+ }
+ break;
+ }
+ case FDT_END_NODE:
+ depth--;
+ p += 4;
+ break;
+ case FDT_PROP: {
+ uint32_t plen = be32(p + 4);
+ const char *pname = (char *)strings + be32(p + 8);
+ uint8_t *val = p + 12;
+
+ p += 12 + ((plen + 3) & ~3);
+
+ if (in_cpus && depth == 3 &&
+ strcmp(pname, "cpu-release-addr") == 0 &&
+ plen == 8 && cur_cpu) {
+ long idx = -1;
+ const char *at = strchr(cur_cpu, '@');
+
+ if (at) {
+ idx = 0;
+ while (*at >= '0' && *at <= '9') {
+ at++;
+ }
+ at = strchr(cur_cpu, '@') + 1;
+ while (*at >= '0' && *at <= '9') {
+ idx = idx * 10 + (*at - '0');
+ at++;
+ }
+ }
+ if (idx >= 0 && idx < ngates) {
+ put_be64(val, (uint64_t)gates[idx]);
+ written++;
+ }
+ }
+ break;
+ }
+ case FDT_NOP:
+ p += 4;
+ break;
+ case FDT_END:
+ return written;
+ }
+ }
+
+ return written;
+}
+
+/*
+ * tell the kernel where the initrd landed. /chosen is created by
+ * the machine firmware, the two cells exist when an initrd was
+ * already staged, we overwrite them in place. depth 2 under the
+ * root, node name "chosen".
+ */
+int tb_dtb_patch_initrd(uintptr_t dtb, uint64_t start, uint64_t end)
+{
+ uint8_t *basep = (uint8_t *)dtb;
+ uint32_t off_struct = be32(basep + 8);
+ uint32_t off_strings = be32(basep + 12);
+ uint8_t *p = basep + off_struct;
+ uint8_t *strings = basep + off_strings;
+ const char *cur_node = NULL;
+ int depth = 0;
+
+ if (be32(basep) != 0xd00dfeed)
+ return -1;
+
+ while (p < basep + be32(basep + 4)) {
+ uint32_t token = be32(p);
+
+ switch (token) {
+ case FDT_BEGIN_NODE: {
+ char *name = (char *)(p + 4);
+ size_t len = strlen(name) + 1;
+
+ p += 4 + ((len + 3) & ~3);
+ depth++;
+ cur_node = name;
+ break;
+ }
+ case FDT_END_NODE:
+ depth--;
+ p += 4;
+ break;
+ case FDT_PROP: {
+ uint32_t plen = be32(p + 4);
+ const char *pname = (char *)strings + be32(p + 8);
+ uint8_t *val = p + 12;
+
+ p += 12 + ((plen + 3) & ~3);
+
+ if (depth == 2 && name_eq(cur_node, "chosen") &&
+ strcmp(pname, "linux,initrd-start") == 0 &&
+ plen >= 8) {
+ put_be64(val, start);
+ }
+ if (depth == 2 && name_eq(cur_node, "chosen") &&
+ strcmp(pname, "linux,initrd-end") == 0 &&
+ plen >= 8) {
+ put_be64(val, end);
+ return 0;
+ }
+ break;
+ }
+ case FDT_NOP:
+ p += 4;
+ break;
+ case FDT_END:
+ return -2;
+ }
+ }
+
+ return -2;
+}
diff --git a/common/dtb_reloc.c b/common/dtb_reloc.c
new file mode 100644
index 0000000..c3e0fe5
--- /dev/null
+++ b/common/dtb_reloc.c
@@ -0,0 +1,69 @@
+/*
+ * dtb_reloc.c - grow the devicetree the way libfdt does, in a
+ * buffer with room to spare. firmware cannot edit a packed fdt
+ * in place, new properties shift everything behind them, so the
+ * blob is copied into scratch verbatim, the free space after
+ * totalsize is the room the insert code shifts into, then the
+ * walkers patch the copy and the kernel gets its address.
+ *
+ * The layout follows the devicetree specification: header,
+ * struct block, strings block, free space. The rebuild copies
+ * header, struct, strings, fixes the offsets in the new header,
+ * and leaves the gap between struct and strings as the room new
+ * properties will consume.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <string.h>
+#include <endian.h>
+#include <boot.h>
+#include <dtb_patch.h>
+
+#define FDT_BEGIN_NODE 1
+#define FDT_END_NODE 2
+#define FDT_PROP 3
+#define FDT_NOP 4
+#define FDT_END 9
+
+static uint32_t be32(const void *p)
+{
+ const uint8_t *b = p;
+
+ return ((uint32_t)b[0] << 24) | ((uint32_t)b[1] << 16) |
+ ((uint32_t)b[2] << 8) | (uint32_t)b[3];
+}
+
+/*
+ * copy the blob into the scratch, grow bytes of headroom after
+ * the end. returns the new blob address or 0 on a short buffer.
+ */
+uintptr_t tb_dtb_relocate(uintptr_t dtb, void *scratch, size_t scratch_size,
+ size_t grow)
+{
+ uint8_t *in = (uint8_t *)dtb;
+ uint8_t *out = scratch;
+ uint32_t totalsize;
+
+ if (be32(in) != 0xd00dfeed)
+ return 0;
+
+ totalsize = be32(in + 4);
+
+ if (scratch_size < (size_t)totalsize + grow)
+ return 0;
+
+ /*
+ * verbatim copy, byte for byte. the grow room is the free
+ * scratch after totalsize, the insert code shifts the
+ * strings block into it. an interior gap between the
+ * struct and strings blocks only invites the walkers to
+ * count it as tree.
+ */
+ for (uint32_t i = 0; i < totalsize; i++)
+ out[i] = in[i];
+
+ (void)grow;
+
+ return (uintptr_t)out;
+}
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;
+}
diff --git a/common/load.c b/common/load.c
new file mode 100644
index 0000000..3a0f8b6
--- /dev/null
+++ b/common/load.c
@@ -0,0 +1,86 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * load.c - pull the payload into RAM. semihosting is the whole story
+ * for now, the bios INT 13h of this loader: the host serves the file,
+ * we read it at the fixed address and let the image header place it.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <debug.h>
+#include <semihosting.h>
+#include <boot.h>
+
+int tb_load_semihosting(const char *fname, uintptr_t load_addr,
+ struct tb_image *img)
+{
+ long fd, len, ret;
+
+ fd = smh_open(fname, MODE_READ | MODE_BINARY);
+ if (fd < 0)
+ return fd;
+
+ len = smh_flen(fd);
+ if (len < 0) {
+ smh_close(fd);
+ return len;
+ }
+
+ /* header first so placement is known before the big copy */
+ ret = smh_read(fd, (void *)load_addr, 64);
+ if (ret != 64) {
+ smh_close(fd);
+ return -3;
+ }
+
+ if (tb_image_setup(load_addr, img)) {
+ smh_close(fd);
+ return -4;
+ }
+
+ if (img->load != load_addr) {
+ /* the image wants to sit elsewhere, copy the header there */
+ memmove((void *)img->load, (void *)load_addr, 64);
+ }
+
+ ret = smh_read(fd, (void *)(img->load + 64), len - 64);
+ if (ret != len - 64) {
+ smh_close(fd);
+ return -5;
+ }
+
+ smh_close(fd);
+ return 0;
+}
+
+/*
+ * the initrd path, no header, no placement math, bytes to the
+ * address the dtb /chosen already names.
+ */
+int tb_load_raw(const char *fname, uintptr_t load_addr, size_t *sizep)
+{
+ long fd, len, ret;
+
+ fd = smh_open(fname, MODE_READ | MODE_BINARY);
+ if (fd < 0)
+ return fd;
+
+ len = smh_flen(fd);
+ if (len < 0) {
+ smh_close(fd);
+ return len;
+ }
+
+ ret = smh_read(fd, (void *)load_addr, len);
+ smh_close(fd);
+
+ if (ret != len)
+ return -6;
+
+ if (sizep)
+ *sizep = (size_t)len;
+ return 0;
+}
diff --git a/common/main.c b/common/main.c
new file mode 100644
index 0000000..ea85be0
--- /dev/null
+++ b/common/main.c
@@ -0,0 +1,300 @@
+/*
+ * main.c - the C entry. console up first, then load the payload and
+ * jump. called from start.S with x0 = whatever the firmware passed.
+ *
+ * the osdev model, arm64: the firmware services do the work, the
+ * kernel goes at a fixed known address, whatever x0 we were handed
+ * goes straight through to the payload.
+ *
+ * Copyright (c) 2026 Bradley Morgan <brads@mainlining.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <debug.h>
+#include <semihosting.h>
+#include <boot.h>
+
+#define TB_VERSION "0.1"
+#define TB_INITRD_ADDR 0x46000000ULL
+#define TB_INITRD_FILE "initrd.cpio.gz"
+
+extern int tb_console_init(void);
+extern void tb_system_reset(void);
+
+/*
+ * the payload goes 16MB clear of wherever this bootloader is
+ * actually running, ADR knows the runtime base and qemu is free
+ * to place us anywhere. hardcoding 0x40200000 smashed our own
+ * image when qemu loaded us there.
+ */
+extern char __image_copy_end[];
+#define TB_LOAD_ADDR ((uintptr_t)__image_copy_end + (16ULL << 20))
+
+/* the file semihosting serves as the payload */
+#define TB_BOOTFILE "Image"
+
+extern void __NO_RETURN tb_boot_linux(uintptr_t ep, uintptr_t fw_arg);
+extern uintptr_t tb_dtb_relocate(uintptr_t dtb, void *scratch,
+ size_t scratch_size, size_t grow);
+extern int tb_gic_init(uintptr_t gicd, uintptr_t gicc);
+extern int tb_dtb_add_chosen_prop(uintptr_t dtb, const char *name,
+ const void *val, size_t len);
+static size_t initrd_size;
+
+void tashaboot_main(uintptr_t fw_arg)
+{
+ struct tb_image img;
+ int ret;
+
+ /*
+ * the console uart the machine named, before the first
+ * print. the base is the first reg pair of the pl011
+ * node, the same walk the gic used, no board hardcodes.
+ */
+ {
+ extern int tb_dtb_find_reg0(uintptr_t dtb,
+ const char *name,
+ uintptr_t *addr, size_t *size);
+ extern void tb_console_set_pl011(uintptr_t base);
+ uintptr_t uart = 0;
+ size_t usz = 0;
+
+ if (tb_dtb_find_reg0(fw_arg, "pl011", &uart, &usz) == 0)
+ tb_console_set_pl011(uart);
+ }
+
+#ifdef TB_HW_RECEIPT
+ {
+ uint64_t midr, el, cntfrq, mpidr;
+
+ asm volatile("mrs %0, midr_el1" : "=r"(midr));
+ asm volatile("mrs %0, CurrentEL" : "=r"(el));
+ asm volatile("mrs %0, cntfrq_el0" : "=r"(cntfrq));
+ asm volatile("mrs %0, mpidr_el1" : "=r"(mpidr));
+
+ tb_console_init();
+ dprintf(ALWAYS, "tashaboot on real hardware\n");
+ dprintf(ALWAYS, "midr %llx el %llx cntfrq %llx mpidr %llx\n",
+ (unsigned long long)midr,
+ (unsigned long long)el >> 2,
+ (unsigned long long)cntfrq,
+ (unsigned long long)mpidr);
+
+ /* let the console drain before the reset domain hits */
+ for (volatile int i = 0; i < 100000000; i++)
+ ;
+
+ tb_system_reset();
+ }
+#endif
+
+ if (tb_console_init())
+ return;
+
+ dprintf(ALWAYS, "tashaboot " TB_VERSION "\n");
+
+#ifdef TB_ENABLE_MMU
+ {
+ extern int tb_mmu_enable(void);
+ extern int tb_mmu_selftest(void);
+ extern void tb_mmu_disable(void);
+
+ if (tb_mmu_enable() == 0) {
+ if (tb_mmu_selftest() == 0)
+ dprintf(ALWAYS, "mmu: identity map on\n");
+ else
+ dprintf(ALWAYS, "mmu: self test failed, "
+ "running unmapped\n");
+ tb_mmu_disable();
+ }
+ }
+#endif
+
+ {
+ /* report the RAM we actually live in, before any mmu */
+ extern int tb_dtb_patch_memory(uintptr_t dtb,
+ uint64_t base,
+ uint64_t size);
+ int r;
+
+ r = 0; (void)r;
+
+ {
+ uint32_t *cells = (uint32_t *)(fw_arg + 0x16c);
+ int i;
+
+ dprintf(ALWAYS, "cells after:");
+ for (i = 0; i < 4; i++)
+ dprintf(ALWAYS, " %08x", cells[i]);
+ dprintf(ALWAYS, "\n");
+ }
+ }
+
+ {
+ /* spin table gates into the dtb, one per cpu node */
+ extern unsigned long *tb_spin_gates_ptr;
+ extern int tb_dtb_patch_spin_table(uintptr_t dtb,
+ uintptr_t *gates,
+ int ngates);
+ int n;
+
+ if (tb_spin_gates_ptr) {
+ extern unsigned char tb_pen_stamps[8];
+ int c;
+
+ n = tb_dtb_patch_spin_table(fw_arg,
+ tb_spin_gates_ptr, 8);
+ dprintf(ALWAYS, "dtb: %d release addrs patched\n", n);
+
+ /* who made it to the pen */
+ for (c = 1; c < 8; c++) {
+ if (tb_pen_stamps[c])
+ break;
+ }
+ dprintf(ALWAYS, "pen: %s\n",
+ c < 8 ? "secondaries waiting" :
+ "no secondaries parked");
+ }
+ }
+
+ ret = tb_load_semihosting(TB_BOOTFILE, TB_LOAD_ADDR, &img);
+ if (ret) {
+ dprintf(ALWAYS, "load failed (%d), halting\n", ret);
+ platform_halt();
+ }
+
+ /*
+ * firmware owns the devicetree it hands the kernel. ours
+ * relocates into scratch with grow room, then the chosen
+ * properties are added there and the kernel gets the new
+ * address, the same flow libfdt firmware uses.
+ */
+ {
+ /*
+ * the scratch lives at a fixed free address, clear of
+ * our image, the payload, and the kernel relocation
+ * zone. a bss array would sit inside 0x40080000+ and
+ * the kernel overwrites it while copying itself.
+ */
+ uint8_t *dtb_scratch = (uint8_t *)0x45000000ULL;
+ uintptr_t newdtb;
+
+ newdtb = tb_dtb_relocate(fw_arg, dtb_scratch,
+ 0x10000, 0x200);
+ if (!newdtb) {
+ dprintf(ALWAYS, "dtb: relocate failed\n");
+ platform_halt();
+ }
+
+ fw_arg = newdtb;
+
+ /*
+ * the interrupt controller the machine told us
+ * about, found by name, the reg pair read with the
+ * root cell counts. the gic goes into the defined
+ * off state before the kernel brings its own irq
+ * handling up.
+ */
+ {
+ extern int tb_dtb_find_reg0(uintptr_t dtb,
+ const char *name,
+ uintptr_t *addr,
+ size_t *size);
+ uintptr_t gicd = 0;
+ uintptr_t gicc = 0;
+ size_t sz = 0;
+
+ if (tb_dtb_find_reg0(fw_arg, "intc", &gicd, &sz) == 0 &&
+ sz >= 0x10000) {
+ gicc = gicd + 0x10000;
+ tb_gic_init(gicd, gicc);
+ dprintf(ALWAYS, "gic: %lx off\n",
+ (unsigned long)gicd);
+ }
+ }
+ }
+
+ /*
+ * the initrd rides after the kernel, the dtb /chosen carries
+ * linux,initrd-start and -end, both already patched in place
+ * with this layout.
+ */
+ {
+ int r = tb_load_raw(TB_INITRD_FILE, TB_INITRD_ADDR,
+ &initrd_size);
+
+ if (r == 0)
+ dprintf(ALWAYS, "initrd at %lx, %lx bytes\n",
+ (unsigned long)TB_INITRD_ADDR,
+ (unsigned long)initrd_size);
+ else
+ dprintf(ALWAYS, "no initrd (%d)\n", r);
+ }
+
+ /*
+ * the chosen properties, written now that the initrd size
+ * is known. the cells are big endian, the fdt is a big
+ * endian format end to end.
+ */
+ {
+ uint8_t start_cells[8], end_cells[8];
+ uint64_t start = TB_INITRD_ADDR;
+ uint64_t end = TB_INITRD_ADDR + initrd_size;
+ int a, b;
+
+ for (int i = 0; i < 8; i++) {
+ start_cells[i] = (uint8_t)(start >> (56 - 8 * i));
+ end_cells[i] = (uint8_t)(end >> (56 - 8 * i));
+ }
+ a = tb_dtb_add_chosen_prop(fw_arg, "linux,initrd-start",
+ start_cells, 8);
+ b = tb_dtb_add_chosen_prop(fw_arg, "linux,initrd-end",
+ end_cells, 8);
+ dprintf(ALWAYS, "dtb: initrd props %d %d\n", a, b);
+ }
+
+
+
+ dprintf(ALWAYS, "loaded %llu bytes at %lx, entry %lx\n",
+ (unsigned long long)img.size, img.load, img.ep);
+
+#ifdef TB_TEST_SMC
+ {
+ register uint64_t r0 asm("x0") = 0x84000000;
+ register uint64_t r1 asm("x1") = 0;
+ register uint64_t r2 asm("x2") = 0;
+ register uint64_t r3 asm("x3") = 0;
+
+ asm volatile("smc #0"
+ : "+r"(r0), "+r"(r1), "+r"(r2), "+r"(r3));
+ dprintf(ALWAYS, "smc conduit: psci version %lx\n",
+ (unsigned long)r0);
+ }
+#endif
+
+ dprintf(ALWAYS, "jumping\n");
+
+ tb_boot_linux(img.ep, fw_arg);
+}
diff --git a/common/mmutest.c b/common/mmutest.c
new file mode 100644
index 0000000..1b60110
--- /dev/null
+++ b/common/mmutest.c
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * mmutest.c - self test for the identity map, AT S1E2R translates a
+ * VA through the tables and PAR_EL1 returns the walk result. if the
+ * map is wrong the instruction faults to our vectors instead, so a
+ * clean return with a valid PA in PAR means the tables walk.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <asm/mmu.h>
+#include <debug.h>
+
+#define PAR_F (1ULL << 0) /* fault, no translation */
+#define PAR_PA_MASK 0x000ffffffffff000ULL
+
+static uint64_t translate(uint64_t va)
+{
+ uint64_t par, el;
+
+ asm volatile("mrs %0, CurrentEL" : "=r" (el));
+ el >>= 2;
+
+ if (el == 2)
+ asm volatile(
+ "at s1e2r, %1\n"
+ "isb\n"
+ "mrs %0, par_el1\n"
+ : "=r" (par)
+ : "r" (va)
+ : "memory");
+ else
+ asm volatile(
+ "at s1e1r, %1\n"
+ "isb\n"
+ "mrs %0, par_el1\n"
+ : "=r" (par)
+ : "r" (va)
+ : "memory");
+ return par;
+}
+
+static int check(const char *name, uint64_t va)
+{
+ uint64_t par = translate(va);
+
+ if (par & PAR_F) {
+ dprintf(ALWAYS, "mmu: %s faulted (par 0x%016llx)\n",
+ name, (unsigned long long)par);
+ return 1;
+ }
+
+ if ((par & PAR_PA_MASK) != (va & PAR_PA_MASK)) {
+ dprintf(ALWAYS, "mmu: %s pa %llx != va %llx\n",
+ name, (unsigned long long)(par & PAR_PA_MASK),
+ (unsigned long long)va);
+ return 1;
+ }
+
+ dprintf(ALWAYS, "mmu: %s ok, pa %llx\n",
+ name, (unsigned long long)(par & PAR_PA_MASK));
+ return 0;
+}
+
+int tb_mmu_selftest(void)
+{
+ int ret = 0;
+
+ ret |= check("mmio 0x09000000 (uart)", 0x09000000);
+ ret |= check("mmio 0x00000000", 0x00000000);
+ ret |= check("ram 0x40200000 (load)", 0x40200000);
+ ret |= check("ram 0x41000000", 0x41000000);
+ ret |= check("self 0x40080000 (stack guard region, no map)",
+ 0x40080000);
+
+ return ret;
+}
diff --git a/include/assert.h b/include/assert.h
new file mode 100644
index 0000000..ecdfaca
--- /dev/null
+++ b/include/assert.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef __ASSERT_H
+#define __ASSERT_H
+
+#include <compiler.h>
+#include <debug.h>
+
+#define ASSERT(x) \
+ do { if (unlikely(!(x))) { panic("ASSERT FAILED at (%s:%d): %s\n", __FILE__, __LINE__, #x); } } while (0)
+#define assert(x) ASSERT(x)
+
+#if DEBUGLEVEL > 1
+#define DEBUG_ASSERT(x) \
+ do { if (unlikely(!(x))) { panic("DEBUG ASSERT FAILED at (%s:%d): %s\n", __FILE__, __LINE__, #x); } } while (0)
+#else
+#define DEBUG_ASSERT(x) \
+ do { } while(0)
+#endif
+
+#endif
diff --git a/include/boot.h b/include/boot.h
new file mode 100644
index 0000000..06b05d7
--- /dev/null
+++ b/include/boot.h
@@ -0,0 +1,33 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __BOOT_H
+#define __BOOT_H
+
+#include <sys/types.h>
+#include <stdint.h>
+
+/*
+ * payload loading and the arm64 boot protocol. See Documentation/
+ * arch/arm64/booting.rst in the kernel tree for the contract this
+ * implements.
+ */
+
+/*
+ * where an image landed after the loader placed it. ep is the first
+ * instruction executed, the whole image including its 64 byte header
+ * sits at load.
+ */
+struct tb_image {
+ uintptr_t load; /* image start in memory */
+ uintptr_t ep; /* entry point */
+ size_t size; /* total image size */
+};
+
+/* common/image.c, the arm64 Image header math */
+int tb_image_setup(uintptr_t image, struct tb_image *img);
+
+/* common/load.c */
+int tb_load_raw(const char *fname, uintptr_t load_addr, size_t *sizep);
+int tb_load_semihosting(const char *fname, uintptr_t load_addr,
+ struct tb_image *img);
+
+#endif
diff --git a/include/compiler.h b/include/compiler.h
new file mode 100644
index 0000000..e659047
--- /dev/null
+++ b/include/compiler.h
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef __COMPILER_H
+#define __COMPILER_H
+
+#ifndef __ASSEMBLY__
+
+#if __GNUC__
+#define likely(x) __builtin_expect(!!(x), 1)
+#define unlikely(x) __builtin_expect(!!(x), 0)
+#define __USED __attribute__((__used__))
+#define __UNUSED __attribute__((__unused__))
+#define __PACKED __attribute__((packed))
+#define __ALIGNED(x) __attribute__((aligned(x)))
+#define __PRINTFLIKE(__fmt,__varargs) __attribute__((__format__ (__printf__, __fmt, __varargs)))
+#define __SCANFLIKE(__fmt,__varargs) __attribute__((__format__ (__scanf__, __fmt, __varargs)))
+#define __SECTION(x) __attribute((section(x)))
+#define __PURE __attribute((pure))
+#define __CONST __attribute((const))
+#define __NO_RETURN __attribute__((noreturn))
+#define __MALLOC __attribute__((malloc))
+#define __WEAK __attribute__((weak))
+#define __GNU_INLINE __attribute__((gnu_inline))
+#define __GET_CALLER(x) __builtin_return_address(0)
+#define __GET_FRAME(x) __builtin_frame_address(0)
+
+#define INCBIN(symname, sizename, filename, section) \
+ __asm__ (".section " section "; .align 4; .globl "#symname); \
+ __asm__ (""#symname ":\n.incbin \"" filename "\""); \
+ __asm__ (".section " section "; .align 1;"); \
+ __asm__ (""#symname "_end:"); \
+ __asm__ (".section " section "; .align 4; .globl "#sizename); \
+ __asm__ (""#sizename ": .long "#symname "_end - "#symname " - 1"); \
+ extern unsigned char symname[]; \
+ extern unsigned int sizename
+
+#define INCFILE(symname, sizename, filename) INCBIN(symname, sizename, filename, ".rodata")
+
+/* look for gcc 3.0 and above */
+#if (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 0)
+#define __ALWAYS_INLINE __attribute__((always_inline))
+#else
+#define __ALWAYS_INLINE
+#endif
+
+/* look for gcc 3.1 and above */
+#if !defined(__DEPRECATED) // seems to be built in in some versions of the compiler
+#if (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)
+#define __DEPRECATED __attribute((deprecated))
+#else
+#define __DEPRECATED
+#endif
+#endif
+
+/* look for gcc 3.3 and above */
+#if (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3)
+/* the may_alias attribute was introduced in gcc 3.3; before that, there
+ * was no way to specify aliasiang rules on a type-by-type basis */
+#define __MAY_ALIAS __attribute__((may_alias))
+
+/* nonnull was added in gcc 3.3 as well */
+#define __NONNULL(x) __attribute((nonnull x))
+#else
+#define __MAY_ALIAS
+#define __NONNULL(x)
+#endif
+
+/* look for gcc 3.4 and above */
+#if (__GNUC__ > 3) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4)
+#define __WARN_UNUSED_RESULT __attribute((warn_unused_result))
+#else
+#define __WARN_UNUSED_RESULT
+#endif
+
+#if (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 1)
+#define __EXTERNALLY_VISIBLE __attribute__((externally_visible))
+#else
+#define __EXTERNALLY_VISIBLE
+#endif
+
+#else
+
+#define likely(x) (x)
+#define unlikely(x) (x)
+#define __USED
+#define __UNUSED
+#define __PACKED
+#define __ALIGNED(x)
+#define __PRINTFLIKE(__fmt,__varargs)
+#define __SCANFLIKE(__fmt,__varargs)
+#define __SECTION(x)
+#define __PURE
+#define __CONST
+#define __NONNULL(x)
+#define __DEPRECATED
+#define __WARN_UNUSED_RESULT
+#define __ALWAYS_INLINE
+#define __MAY_ALIAS
+#define __NO_RETURN
+#endif
+
+#endif
+
+/* TODO: add type check */
+#define countof(a) (sizeof(a) / sizeof((a)[0]))
+
+#define IS_ENABLED(define) _IS_ENABLED(define)
+#define _comma_if_enabled_1 ,
+#define _IS_ENABLED(value) __IS_ENABLED(_comma_if_enabled_##value)
+#define __IS_ENABLED(comma) ___IS_ENABLED(comma 1, 0)
+#define ___IS_ENABLED(_, enabled, ...) enabled
+
+#endif
diff --git a/include/ctype.h b/include/ctype.h
new file mode 100644
index 0000000..7f9982e
--- /dev/null
+++ b/include/ctype.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef __CTYPE_H
+#define __CTYPE_H
+
+int isalnum(int c);
+int isalpha(int c);
+int isblank(int c);
+int iscntrl(int c);
+int isdigit(int c);
+int isgraph(int c);
+int islower(int c);
+int isprint(int c);
+int ispunct(int c);
+int isspace(int c);
+int isupper(int c);
+int isxdigit(int c);
+
+int tolower(int c);
+int toupper(int c);
+
+#endif
+
diff --git a/include/debug.h b/include/debug.h
new file mode 100644
index 0000000..83b62b2
--- /dev/null
+++ b/include/debug.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef __DEBUG_H
+#define __DEBUG_H
+
+#include <assert.h>
+#include <stdarg.h>
+#include <compiler.h>
+#include <platform/debug.h>
+#include <printf.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#if defined(DEBUG)
+#define DEBUGLEVEL DEBUG
+#else
+#define DEBUGLEVEL 2
+#endif
+
+/* debug levels */
+#define CRITICAL 0
+#define ALWAYS 0
+#define INFO 1
+#define SPEW 2
+
+/* output */
+void _dputc(char c); // XXX for now, platform implements
+int _dputs(const char *str);
+int _dprintf(const char *fmt, ...) __PRINTFLIKE(1, 2);
+int _dvprintf(const char *fmt, va_list ap);
+
+#define dputc(level, str) do { if ((level) <= DEBUGLEVEL) { _dputc(str); } } while (0)
+#define dputs(level, str) do { if ((level) <= DEBUGLEVEL) { _dputs(str); } } while (0)
+#define dprintf(level, x...) do { if ((level) <= DEBUGLEVEL) { _dprintf(x); } } while (0)
+#define dvprintf(level, x...) do { if ((level) <= DEBUGLEVEL) { _dvprintf(x); } } while (0)
+
+/* input */
+int dgetc(char *c, bool wait);
+
+/* systemwide halts */
+void halt(void);
+
+void dump_frame(void *frame);
+
+void _panic(void *caller, const char *fmt, ...) __PRINTFLIKE(2, 3);
+#define panic(x...) _panic(__GET_CALLER(), x)
+
+#define PANIC_UNIMPLEMENTED panic("%s unimplemented\n", __PRETTY_FUNCTION__)
+
+extern uintptr_t __stack_chk_guard;
+
+/*
+* Initialize the stack protector canary value.
+*/
+#define __stack_chk_guard_setup() do { __stack_chk_guard = get_canary(); } while(0)
+
+void __attribute__ ((noreturn))
+ __stack_chk_fail (void);
+
+/* spin the cpu for a period of (short) time */
+void spin(uint32_t usecs);
+
+/* dump memory */
+void hexdump(const void *ptr, size_t len);
+void hexdump8(const void *ptr, size_t len);
+
+/* trace routines */
+#define TRACE_ENTRY printf("%s: entry\n", __PRETTY_FUNCTION__)
+#define TRACE_EXIT printf("%s: exit\n", __PRETTY_FUNCTION__)
+#define TRACE_ENTRY_OBJ printf("%s: entry obj %p\n", __PRETTY_FUNCTION__, this)
+#define TRACE_EXIT_OBJ printf("%s: exit obj %p\n", __PRETTY_FUNCTION__, this)
+#define TRACE printf("%s:%d\n", __PRETTY_FUNCTION__, __LINE__)
+#define TRACEF(x...) do { printf("%s:%d: ", __PRETTY_FUNCTION__, __LINE__); printf(x); } while (0)
+
+/* trace routines that work if LOCAL_TRACE is set */
+#define LTRACE_ENTRY do { if (LOCAL_TRACE) { TRACE_ENTRY; } } while (0)
+#define LTRACE_EXIT do { if (LOCAL_TRACE) { TRACE_EXIT; } } while (0)
+#define LTRACE do { if (LOCAL_TRACE) { TRACE; } } while (0)
+#define LTRACEF(x...) do { if (LOCAL_TRACE) { TRACEF(x); } } while (0)
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/include/dtb_patch.h b/include/dtb_patch.h
new file mode 100644
index 0000000..7fbe225
--- /dev/null
+++ b/include/dtb_patch.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __TB_DTB_PATCH_H
+#define __TB_DTB_PATCH_H
+
+#include <stdint.h>
+
+/*
+ * minimal dtb patcher, no libfdt. walks the flattened devicetree
+ * structure per the devicetree specification (devicetree.dtsi format:
+ * FDT_BEGIN_NODE, name, property, FDT_END_NODE) and rewrites the cpu
+ * nodes for spin table bringup.
+ *
+ * what it does:
+ * /cpus/cpu@N: enable-method = "spin-table"
+ * cpu-release-addr = gate address of core N
+ * /psci: status = "disabled" (so the kernel falls back to the
+ * spin table instead of trying hvc)
+ *
+ * properties are rewritten in place where the space fits, the
+ * enable-method string shrinks, cpu-release-addr reuses the space
+ * of an old value. new properties are appended to the last cpu node
+ * by growing the struct block and moving the strings block.
+ */
+
+int tb_dtb_patch_spin_table(uintptr_t dtb, uintptr_t *gates, int ngates);
+
+#endif /* __TB_DTB_PATCH_H */
diff --git a/include/endian.h b/include/endian.h
new file mode 100644
index 0000000..42f880c
--- /dev/null
+++ b/include/endian.h
@@ -0,0 +1,54 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * endian.h - byte order swaps. arm64 and ppc64le are both little
+ * endian, so the macros compile to nothing there, but the big endian
+ * fallbacks stay so a BE target builds without a rewrite.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#ifndef __ENDIAN_H
+#define __ENDIAN_H
+
+#include <stdint.h>
+
+static inline uint16_t __swap16(uint16_t x)
+{
+ return (uint16_t)((x << 8) | (x >> 8));
+}
+
+static inline uint32_t __swap32(uint32_t x)
+{
+ return ((x << 24) |
+ ((x & 0xff00) << 8) |
+ ((x & 0xff0000) >> 8) |
+ (x >> 24));
+}
+
+static inline uint64_t __swap64(uint64_t x)
+{
+ return ((uint64_t)__swap32((uint32_t)x) << 32) |
+ __swap32((uint32_t)(x >> 32));
+}
+
+#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+
+#define le16_to_cpu(x) __swap16(x)
+#define le32_to_cpu(x) __swap32(x)
+#define le64_to_cpu(x) __swap64(x)
+#define cpu_to_le16(x) __swap16(x)
+#define cpu_to_le32(x) __swap32(x)
+#define cpu_to_le64(x) __swap64(x)
+
+#else
+
+#define le16_to_cpu(x) (x)
+#define le32_to_cpu(x) (x)
+#define le64_to_cpu(x) (x)
+#define cpu_to_le16(x) (x)
+#define cpu_to_le32(x) (x)
+#define cpu_to_le64(x) (x)
+
+#endif
+
+#endif
diff --git a/include/limits.h b/include/limits.h
new file mode 100644
index 0000000..ec83068
--- /dev/null
+++ b/include/limits.h
@@ -0,0 +1,121 @@
+/* This administrivia gets added to the beginning of limits.h
+ if the system has its own version of limits.h. */
+
+/* We use _GCC_LIMITS_H_ because we want this not to match
+ any macros that the system's limits.h uses for its own purposes. */
+#ifndef _GCC_LIMITS_H_ /* Terminated in limity.h. */
+#define _GCC_LIMITS_H_
+
+#ifndef _LIMITS_H___
+#define _LIMITS_H___
+
+/* Number of bits in a `char'. */
+#undef CHAR_BIT
+#define CHAR_BIT __CHAR_BIT__
+
+/* Maximum length of a multibyte character. */
+#ifndef MB_LEN_MAX
+#define MB_LEN_MAX 1
+#endif
+
+/* Minimum and maximum values a `signed char' can hold. */
+#undef SCHAR_MIN
+#define SCHAR_MIN (-SCHAR_MAX - 1)
+#undef SCHAR_MAX
+#define SCHAR_MAX __SCHAR_MAX__
+
+/* Maximum value an `unsigned char' can hold. (Minimum is 0). */
+#undef UCHAR_MAX
+#if __SCHAR_MAX__ == __INT_MAX__
+# define UCHAR_MAX (SCHAR_MAX * 2U + 1U)
+#else
+# define UCHAR_MAX (SCHAR_MAX * 2 + 1)
+#endif
+
+/* Minimum and maximum values a `char' can hold. */
+#ifdef __CHAR_UNSIGNED__
+# undef CHAR_MIN
+# if __SCHAR_MAX__ == __INT_MAX__
+# define CHAR_MIN 0U
+# else
+# define CHAR_MIN 0
+# endif
+# undef CHAR_MAX
+# define CHAR_MAX UCHAR_MAX
+#else
+# undef CHAR_MIN
+# define CHAR_MIN SCHAR_MIN
+# undef CHAR_MAX
+# define CHAR_MAX SCHAR_MAX
+#endif
+
+/* Minimum and maximum values a `signed short int' can hold. */
+#undef SHRT_MIN
+#define SHRT_MIN (-SHRT_MAX - 1)
+#undef SHRT_MAX
+#define SHRT_MAX __SHRT_MAX__
+
+/* Maximum value an `unsigned short int' can hold. (Minimum is 0). */
+#undef USHRT_MAX
+#if __SHRT_MAX__ == __INT_MAX__
+# define USHRT_MAX (SHRT_MAX * 2U + 1U)
+#else
+# define USHRT_MAX (SHRT_MAX * 2 + 1)
+#endif
+
+/* Minimum and maximum values a `signed int' can hold. */
+#undef INT_MIN
+#define INT_MIN (-INT_MAX - 1)
+#undef INT_MAX
+#define INT_MAX __INT_MAX__
+
+/* Maximum value an `unsigned int' can hold. (Minimum is 0). */
+#undef UINT_MAX
+#define UINT_MAX (INT_MAX * 2U + 1U)
+
+/* Minimum and maximum values a `signed long int' can hold.
+ (Same as `int'). */
+#undef LONG_MIN
+#define LONG_MIN (-LONG_MAX - 1L)
+#undef LONG_MAX
+#define LONG_MAX __LONG_MAX__
+
+/* Maximum value an `unsigned long int' can hold. (Minimum is 0). */
+#undef ULONG_MAX
+#define ULONG_MAX (LONG_MAX * 2UL + 1UL)
+
+#if defined (__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
+/* Minimum and maximum values a `signed long long int' can hold. */
+# undef LLONG_MIN
+# define LLONG_MIN (-LLONG_MAX - 1LL)
+# undef LLONG_MAX
+# define LLONG_MAX __LONG_LONG_MAX__
+
+/* Maximum value an `unsigned long long int' can hold. (Minimum is 0). */
+# undef ULLONG_MAX
+# define ULLONG_MAX (LLONG_MAX * 2ULL + 1ULL)
+#endif
+
+#if defined (__GNU_LIBRARY__) ? defined (__USE_GNU) : !defined (__STRICT_ANSI__)
+/* Minimum and maximum values a `signed long long int' can hold. */
+# undef LONG_LONG_MIN
+# define LONG_LONG_MIN (-LONG_LONG_MAX - 1LL)
+# undef LONG_LONG_MAX
+# define LONG_LONG_MAX __LONG_LONG_MAX__
+
+/* Maximum value an `unsigned long long int' can hold. (Minimum is 0). */
+# undef ULONG_LONG_MAX
+# define ULONG_LONG_MAX (LONG_LONG_MAX * 2ULL + 1ULL)
+#endif
+
+#endif /* _LIMITS_H___ */
+/* This administrivia gets added to the end of limits.h
+ if the system has its own version of limits.h. */
+
+#else /* not _GCC_LIMITS_H_ */
+
+#ifdef _GCC_NEXT_LIMITS_H
+#include_next <limits.h> /* recurse down to the real one */
+#endif
+
+#endif /* not _GCC_LIMITS_H_ */
diff --git a/include/platform/debug.h b/include/platform/debug.h
new file mode 100644
index 0000000..38f30a7
--- /dev/null
+++ b/include/platform/debug.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ * Copyright (c) 2026 Bradley Morgan <brads@mainlining.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef __PLATFORM_DEBUG_H
+#define __PLATFORM_DEBUG_H
+
+#include <sys/types.h>
+#include <stdarg.h>
+#include <compiler.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+int _dprintf(const char *fmt, ...) __PRINTFLIKE(1, 2);
+int _dvprintf(const char *fmt, va_list ap);
+
+void platform_halt(void);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/include/printf.h b/include/printf.h
new file mode 100644
index 0000000..92981df
--- /dev/null
+++ b/include/printf.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef __LIB_PRINTF_H
+#define __LIB_PRINTF_H
+
+#include <stdarg.h>
+#include <compiler.h>
+#include <debug.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+int printf(const char *fmt, ...);
+int sprintf(char *str, const char *fmt, ...) __PRINTFLIKE(2, 3);
+int snprintf(char *str, size_t len, const char *fmt, ...) __PRINTFLIKE(3, 4);
+int vsprintf(char *str, const char *fmt, va_list ap);
+int vsnprintf(char *str, size_t len, const char *fmt, va_list ap);
+
+/* printf engine that parses the format string and generates output */
+
+/* function pointer to pass the engine,
+ * return code is remaining characters in destination (or INT_MAX for infinity)
+ */
+typedef int (*_printf_engine_output_func)(char c, void *state);
+
+int _printf_engine(_printf_engine_output_func out, void *state, const char *fmt, va_list ap);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif
diff --git a/include/reg.h b/include/reg.h
new file mode 100644
index 0000000..321074b
--- /dev/null
+++ b/include/reg.h
@@ -0,0 +1,50 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * reg.h - mmio accessors. ld/st straight to the device, nothing cute.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __REG_H
+#define __REG_H
+
+#include <sys/types.h>
+#include <stdint.h>
+
+#define REG32(addr) ((volatile uint32_t *)(uintptr_t)(addr))
+
+static inline void writel(uint32_t v, volatile void *addr)
+{
+ *(volatile uint32_t *)addr = v;
+}
+
+static inline uint32_t readl(const volatile void *addr)
+{
+ return *(const volatile uint32_t *)addr;
+}
+
+static inline void writew(uint16_t v, volatile void *addr)
+{
+ *(volatile uint16_t *)addr = v;
+}
+
+static inline uint16_t readw(const volatile void *addr)
+{
+ return *(const volatile uint16_t *)addr;
+}
+
+static inline void writeb(uint8_t v, volatile void *addr)
+{
+ *(volatile uint8_t *)addr = v;
+}
+
+static inline uint8_t readb(const volatile void *addr)
+{
+ return *(const volatile uint8_t *)addr;
+}
+
+#endif
diff --git a/include/semihosting.h b/include/semihosting.h
new file mode 100644
index 0000000..450848e
--- /dev/null
+++ b/include/semihosting.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __SEMIHOSTING_H
+#define __SEMIHOSTING_H
+
+#include <sys/types.h>
+
+/*
+ * semihosting file io, backed by the host when qemu runs with
+ * -semihosting. all calls return negative errno style errors so the
+ * boot flow can fall back to whatever the board has without semihosting.
+ */
+
+enum smh_open_mode {
+ MODE_READ = 0x0,
+ MODE_BINARY = 0x1,
+ MODE_PLUS = 0x2,
+ MODE_WRITE = 0x4,
+ MODE_APPEND = 0x8,
+};
+
+long smh_open(const char *fname, enum smh_open_mode mode);
+long smh_read(long fd, void *memp, size_t len);
+long smh_write(long fd, const void *memp, size_t len, size_t *written);
+void smh_write0(const char *str);
+long smh_close(long fd);
+long smh_flen(long fd);
+long smh_seek(long fd, long pos);
+
+/* probe once, a boot without a debugger attached must not trap */
+bool smh_probe(void);
+
+#endif
diff --git a/include/stdio.h b/include/stdio.h
new file mode 100644
index 0000000..8099fb3
--- /dev/null
+++ b/include/stdio.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2008-2013 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef __STDIO_H
+#define __STDIO_H
+
+#include <debug.h>
+#include <printf.h>
+
+/* fake FILE struct */
+typedef struct FILE {
+} FILE;
+
+#define stdin ((FILE *)1)
+#define stdout ((FILE *)2)
+#define stderr ((FILE *)3)
+
+int fputc(int c, FILE *fp);
+#define putc(c, fp) fputc(c, fp)
+int putchar(int c);
+void _putchar(char c); /* the dprintf console sink */
+
+int fputs(const char *s, FILE *fp);
+int puts(const char *str);
+
+int getc(FILE *fp);
+int getchar(void);
+
+size_t fwrite(const void *buf, size_t size, size_t count, FILE *stream);
+int sscanf(const char *str, const char *format, ...);
+
+#endif
+
diff --git a/include/string.h b/include/string.h
new file mode 100644
index 0000000..da2f229
--- /dev/null
+++ b/include/string.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef __LIB_STRING_H
+#define __LIB_STRING_H
+
+#include <sys/types.h>
+#include <compiler.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void *memchr (void const *, int, size_t) __PURE;
+int memcmp (void const *, const void *, size_t) __PURE;
+void *memcpy (void *, void const *, size_t);
+void *memmove(void *, void const *, size_t);
+void *memset (void *, int, size_t);
+
+char *strchr(char const *, int) __PURE;
+int strcmp(char const *, char const *) __PURE;
+char *strcpy(char *, char const *);
+size_t strlen(char const *) __PURE;
+int strncmp(char const *, char const *, size_t) __PURE;
+char *strrchr(char const *, int) __PURE;
+char *strstr(char const *, char const *) __PURE;
+char *strstrn(char const *s1, char const *s2, size_t l2) __PURE;
+size_t strnlen(char const *s, size_t count) __PURE;
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* non standard */
+void bcopy(void const *, void *, size_t);
+void bzero(void *, size_t);
+size_t strlcat(char *, char const *, size_t);
+size_t strlcpy(char *, char const *, size_t);
+void strrev(unsigned char *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/include/sys/types.h b/include/sys/types.h
new file mode 100644
index 0000000..70f6de1
--- /dev/null
+++ b/include/sys/types.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2008-2009 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#ifndef __SYS_TYPES_H
+#define __SYS_TYPES_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <limits.h>
+#include <stdint.h>
+
+typedef unsigned char uchar;
+typedef unsigned short ushort;
+typedef unsigned int uint;
+typedef unsigned long ulong;
+typedef unsigned char u_char;
+typedef unsigned short u_short;
+typedef unsigned int u_int;
+typedef unsigned long u_long;
+
+#ifndef _SIZE_T_DEFINED_
+typedef unsigned long size_t;
+#endif
+typedef long ssize_t;
+typedef long long off_t;
+
+typedef int status_t;
+
+typedef uintptr_t addr_t;
+typedef uintptr_t vaddr_t;
+typedef uintptr_t paddr_t;
+
+typedef int kobj_id;
+
+typedef unsigned long time_t;
+typedef unsigned long long bigtime_t;
+typedef uint8_t uint8;
+typedef uint16_t uint16;
+typedef uint32_t uint32;
+typedef uint64_t uint64;
+typedef int8_t int8;
+typedef int16_t int16;
+typedef int32_t int32;
+typedef bool boolean;
+
+#ifndef TRUE
+#define TRUE true
+#endif
+#ifndef FALSE
+#define FALSE false
+#endif
+
+#define INFINITE_TIME ULONG_MAX
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0]))
+
+#define TIME_GTE(a, b) ((long)((a) - (b)) >= 0)
+#define TIME_LTE(a, b) ((long)((a) - (b)) <= 0)
+#define TIME_GT(a, b) ((long)((a) - (b)) > 0)
+#define TIME_LT(a, b) ((long)((a) - (b)) < 0)
+
+enum handler_return {
+ INT_NO_RESCHEDULE = 0,
+ INT_RESCHEDULE,
+};
+
+#endif
diff --git a/lib/itoa.c b/lib/itoa.c
new file mode 100644
index 0000000..4fc2db6
--- /dev/null
+++ b/lib/itoa.c
@@ -0,0 +1,63 @@
+/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+/* freestanding, no stdlib */
+#include <string.h>
+#include <ctype.h>
+
+int
+itoa(int num, unsigned char* str, int len, int base)
+{
+ int sum = num;
+ int i = 0;
+ int digit;
+
+ if (len == 0)
+ return -1;
+
+ do
+ {
+ digit = sum % base;
+
+ if (digit < 0xA)
+ str[i++] = '0' + digit;
+ else
+ str[i++] = 'A' + digit - 0xA;
+
+ sum /= base;
+
+ }while (sum && (i < (len - 1)));
+
+ if (i == (len - 1) && sum)
+ return -1;
+
+ str[i] = '\0';
+ strrev(str);
+
+ return 0;
+}
diff --git a/lib/printf.c b/lib/printf.c
new file mode 100644
index 0000000..14fa452
--- /dev/null
+++ b/lib/printf.c
@@ -0,0 +1,383 @@
+/*
+ * Copyright (c) 2008-2013 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <debug.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <printf.h>
+#include <string.h>
+
+int printf(const char *fmt, ...)
+{
+ int err;
+
+ va_list ap;
+ va_start(ap, fmt);
+ err = _dvprintf(fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+int sprintf(char *str, const char *fmt, ...)
+{
+ int err;
+
+ va_list ap;
+ va_start(ap, fmt);
+ err = vsprintf(str, fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+int snprintf(char *str, size_t len, const char *fmt, ...)
+{
+ int err;
+
+ va_list ap;
+ va_start(ap, fmt);
+ err = vsnprintf(str, len, fmt, ap);
+ va_end(ap);
+
+ return err;
+}
+
+
+#define LONGFLAG 0x00000001
+#define LONGLONGFLAG 0x00000002
+#define HALFFLAG 0x00000004
+#define HALFHALFFLAG 0x00000008
+#define SIZETFLAG 0x00000010
+#define ALTFLAG 0x00000020
+#define CAPSFLAG 0x00000040
+#define SHOWSIGNFLAG 0x00000080
+#define SIGNEDFLAG 0x00000100
+#define LEFTFORMATFLAG 0x00000200
+#define LEADZEROFLAG 0x00000400
+
+static char *longlong_to_string(char *buf, unsigned long long n, int len, uint flag)
+{
+ int pos = len;
+ int negative = 0;
+
+ if((flag & SIGNEDFLAG) && (long long)n < 0) {
+ negative = 1;
+ n = -n;
+ }
+
+ buf[--pos] = 0;
+
+ /* only do the math if the number is >= 10 */
+ while(n >= 10) {
+ int digit = n % 10;
+
+ n /= 10;
+
+ buf[--pos] = digit + '0';
+ }
+ buf[--pos] = n + '0';
+
+ if(negative)
+ buf[--pos] = '-';
+ else if((flag & SHOWSIGNFLAG))
+ buf[--pos] = '+';
+
+ return &buf[pos];
+}
+
+static char *longlong_to_hexstring(char *buf, unsigned long long u, int len, uint flag)
+{
+ int pos = len;
+ static const char hextable[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+ static const char hextable_caps[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+ const char *table;
+
+ if((flag & CAPSFLAG))
+ table = hextable_caps;
+ else
+ table = hextable;
+
+ buf[--pos] = 0;
+ do {
+ unsigned int digit = u % 16;
+ u /= 16;
+
+ buf[--pos] = table[digit];
+ } while(u != 0);
+
+ return &buf[pos];
+}
+
+int vsprintf(char *str, const char *fmt, va_list ap)
+{
+ return vsnprintf(str, INT_MAX, fmt, ap);
+}
+
+struct _output_args {
+ char *outstr;
+ size_t len;
+ size_t pos;
+};
+
+static int _vsnprintf_output(char c, void *state)
+{
+ struct _output_args *args = state;
+
+ if (args->pos >= args->len)
+ return 0;
+
+ args->outstr[args->pos++] = c;
+
+ return args->len - args->pos;
+}
+
+int vsnprintf(char *str, size_t len, const char *fmt, va_list ap)
+{
+ struct _output_args args;
+ int wlen;
+
+ args.outstr = str;
+ args.len = len;
+ args.pos = 0;
+
+ wlen = _printf_engine(&_vsnprintf_output, (void *)&args, fmt, ap);
+ if (args.pos >= len)
+ str[len-1] = '\0';
+ else
+ str[wlen] = '\0';
+ return wlen;
+}
+
+int _printf_engine(_printf_engine_output_func out, void *state, const char *fmt, va_list ap)
+{
+ char c;
+ unsigned char uc;
+ const char *s;
+ unsigned long long n;
+ void *ptr;
+ int flags;
+ unsigned int format_num;
+ size_t chars_written = 0;
+ char num_buffer[32];
+
+#define OUTPUT_CHAR(c) do { chars_written++; out(c, state); } while(0)
+
+ for(;;) {
+ /* handle regular chars that aren't format related */
+ while((c = *fmt++) != 0) {
+ if(c == '%')
+ break; /* we saw a '%', break and start parsing format */
+ OUTPUT_CHAR(c);
+ }
+
+ /* make sure we haven't just hit the end of the string */
+ if(c == 0)
+ break;
+
+ /* reset the format state */
+ flags = 0;
+ format_num = 0;
+
+next_format:
+ /* grab the next format character */
+ c = *fmt++;
+ if(c == 0)
+ break;
+
+ switch(c) {
+ case '0'...'9':
+ if (c == '0' && format_num == 0)
+ flags |= LEADZEROFLAG;
+ format_num *= 10;
+ format_num += c - '0';
+ goto next_format;
+ case '.':
+ /* XXX for now eat numeric formatting */
+ goto next_format;
+ case '%':
+ OUTPUT_CHAR('%');
+ break;
+ case 'c':
+ uc = va_arg(ap, unsigned int);
+ OUTPUT_CHAR(uc);
+ break;
+ case 's':
+ s = va_arg(ap, const char *);
+ if(s == 0)
+ s = "<null>";
+ goto _output_string;
+ case '-':
+ flags |= LEFTFORMATFLAG;
+ goto next_format;
+ case '+':
+ flags |= SHOWSIGNFLAG;
+ goto next_format;
+ case '#':
+ flags |= ALTFLAG;
+ goto next_format;
+ case 'l':
+ if(flags & LONGFLAG)
+ flags |= LONGLONGFLAG;
+ flags |= LONGFLAG;
+ goto next_format;
+ case 'h':
+ if(flags & HALFFLAG)
+ flags |= HALFHALFFLAG;
+ flags |= HALFFLAG;
+ goto next_format;
+ case 'z':
+ flags |= SIZETFLAG;
+ goto next_format;
+ case 'D':
+ flags |= LONGFLAG;
+ /* fallthrough */
+ case 'i':
+ case 'd':
+ n = (flags & LONGLONGFLAG) ? va_arg(ap, long long) :
+ (flags & LONGFLAG) ? va_arg(ap, long) :
+ (flags & HALFHALFFLAG) ? (signed char)va_arg(ap, int) :
+ (flags & HALFFLAG) ? (short)va_arg(ap, int) :
+ (flags & SIZETFLAG) ? va_arg(ap, ssize_t) :
+ va_arg(ap, int);
+ flags |= SIGNEDFLAG;
+ s = longlong_to_string(num_buffer, n, sizeof(num_buffer), flags);
+ goto _output_string;
+ case 'U':
+ flags |= LONGFLAG;
+ /* fallthrough */
+ case 'u':
+ n = (flags & LONGLONGFLAG) ? va_arg(ap, unsigned long long) :
+ (flags & LONGFLAG) ? va_arg(ap, unsigned long) :
+ (flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) :
+ (flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) :
+ (flags & SIZETFLAG) ? va_arg(ap, size_t) :
+ va_arg(ap, unsigned int);
+ s = longlong_to_string(num_buffer, n, sizeof(num_buffer), flags);
+ goto _output_string;
+ case 'p':
+ flags |= LONGFLAG | ALTFLAG;
+ goto hex;
+ case 'X':
+ flags |= CAPSFLAG;
+ /* fallthrough */
+hex:
+ case 'x':
+ n = (flags & LONGLONGFLAG) ? va_arg(ap, unsigned long long) :
+ (flags & LONGFLAG) ? va_arg(ap, unsigned long) :
+ (flags & HALFHALFFLAG) ? (unsigned char)va_arg(ap, unsigned int) :
+ (flags & HALFFLAG) ? (unsigned short)va_arg(ap, unsigned int) :
+ (flags & SIZETFLAG) ? va_arg(ap, size_t) :
+ va_arg(ap, unsigned int);
+ s = longlong_to_hexstring(num_buffer, n, sizeof(num_buffer), flags);
+ if(flags & ALTFLAG) {
+ OUTPUT_CHAR('0');
+ OUTPUT_CHAR((flags & CAPSFLAG) ? 'X': 'x');
+ }
+ goto _output_string;
+ case 'n':
+ ptr = va_arg(ap, void *);
+ if(flags & LONGLONGFLAG)
+ *(long long *)ptr = chars_written;
+ else if(flags & LONGFLAG)
+ *(long *)ptr = chars_written;
+ else if(flags & HALFHALFFLAG)
+ *(signed char *)ptr = chars_written;
+ else if(flags & HALFFLAG)
+ *(short *)ptr = chars_written;
+ else if(flags & SIZETFLAG)
+ *(size_t *)ptr = chars_written;
+ else
+ *(int *)ptr = chars_written;
+ break;
+ default:
+ OUTPUT_CHAR('%');
+ OUTPUT_CHAR(c);
+ break;
+ }
+
+ /* move on to the next field */
+ continue;
+
+ /* shared output code */
+_output_string:
+ if (flags & LEFTFORMATFLAG) {
+ /* left justify the text */
+ uint count = 0;
+ while(*s != 0) {
+ OUTPUT_CHAR(*s++);
+ count++;
+ }
+
+ /* pad to the right (if necessary) */
+ for (; format_num > count; format_num--)
+ OUTPUT_CHAR(' ');
+ } else {
+ /* right justify the text (digits) */
+ size_t string_len = strlen(s);
+ char outchar = (flags & LEADZEROFLAG) ? '0' : ' ';
+ for (; format_num > string_len; format_num--)
+ OUTPUT_CHAR(outchar);
+
+ /* output the string */
+ while(*s != 0)
+ OUTPUT_CHAR(*s++);
+ }
+ continue;
+ }
+
+#undef OUTPUT_CHAR
+
+ return chars_written;
+}
+
+
+
+/*
+ * the dprintf console path. lk's printf engine writes one char at a
+ * time through _printf_engine, the console buffers a line and hands
+ * it to the host over semihosting SYS_WRITE0.
+ */
+static int _dprintf_output(char c, void *state)
+{
+ _putchar(c);
+ return 1;
+}
+
+int _dvprintf(const char *fmt, va_list ap)
+{
+ return _printf_engine(&_dprintf_output, NULL, fmt, ap);
+}
+
+int _dprintf(const char *fmt, ...)
+{
+ int err;
+
+ va_list ap;
+ va_start(ap, fmt);
+ err = _dvprintf(fmt, ap);
+ va_end(ap);
+
+ return err;
+}
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 <sean.anderson@seco.com>
+ * Copyright 2014 Broadcom Corporation
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <string.h>
+#include <sys/types.h>
+#include <stdint.h>
+#include <semihosting.h>
+
+#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;
+}
diff --git a/lib/string/bcopy.c b/lib/string/bcopy.c
new file mode 100644
index 0000000..e9d5462
--- /dev/null
+++ b/lib/string/bcopy.c
@@ -0,0 +1,34 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+void bcopy(void const *src, void *dest, size_t count)
+{
+ memcpy(dest, src, count);
+}
+
diff --git a/lib/string/bzero.c b/lib/string/bzero.c
new file mode 100644
index 0000000..32b43bb
--- /dev/null
+++ b/lib/string/bzero.c
@@ -0,0 +1,35 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+void
+bzero(void *dst, size_t count)
+{
+ memset(dst, 0, count);
+}
+
diff --git a/lib/string/memchr.c b/lib/string/memchr.c
new file mode 100644
index 0000000..094c2a1
--- /dev/null
+++ b/lib/string/memchr.c
@@ -0,0 +1,45 @@
+/*
+** Copyright 2001, Manuel J. Petit. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+void *
+memchr(void const *buf, int c, size_t len)
+{
+ size_t i;
+ unsigned char const *b= buf;
+ unsigned char x= (c&0xff);
+
+ for(i= 0; i< len; i++) {
+ if(b[i]== x) {
+ return (void*)(b+i);
+ }
+ }
+
+ return NULL;
+}
+
diff --git a/lib/string/memcmp.c b/lib/string/memcmp.c
new file mode 100644
index 0000000..a050bc7
--- /dev/null
+++ b/lib/string/memcmp.c
@@ -0,0 +1,40 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+int
+memcmp(const void *cs, const void *ct, size_t count)
+{
+ const unsigned char *su1, *su2;
+ signed char res = 0;
+
+ for(su1 = cs, su2 = ct; 0 < count; ++su1, ++su2, count--)
+ if((res = *su1 - *su2) != 0)
+ break;
+ return res;
+}
diff --git a/lib/string/memcpy.c b/lib/string/memcpy.c
new file mode 100644
index 0000000..00e547e
--- /dev/null
+++ b/lib/string/memcpy.c
@@ -0,0 +1,69 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+
+#if !_ASM_MEMCPY
+
+typedef long word;
+
+#define lsize sizeof(word)
+#define lmask (lsize - 1)
+
+void *memcpy(void *dest, const void *src, size_t count)
+{
+ char *d = (char *)dest;
+ const char *s = (const char *)src;
+ int len;
+
+ if(count == 0 || dest == src)
+ return dest;
+
+ if(((long)d | (long)s) & lmask) {
+ // src and/or dest do not align on word boundary
+ if((((long)d ^ (long)s) & lmask) || (count < lsize))
+ len = count; // copy the rest of the buffer with the byte mover
+ else
+ len = lsize - ((long)d & lmask); // move the ptrs up to a word boundary
+
+ count -= len;
+ for(; len > 0; len--)
+ *d++ = *s++;
+ }
+ for(len = count / lsize; len > 0; len--) {
+ *(word *)d = *(word *)s;
+ d += lsize;
+ s += lsize;
+ }
+ for(len = count & lmask; len > 0; len--)
+ *d++ = *s++;
+
+ return dest;
+}
+
+#endif
diff --git a/lib/string/memmove.c b/lib/string/memmove.c
new file mode 100644
index 0000000..cb08a6c
--- /dev/null
+++ b/lib/string/memmove.c
@@ -0,0 +1,93 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+#if !_ASM_MEMMOVE
+
+typedef long word;
+
+#define lsize sizeof(word)
+#define lmask (lsize - 1)
+
+void *
+memmove(void *dest, void const *src, size_t count)
+{
+ char *d = (char *)dest;
+ const char *s = (const char *)src;
+ int len;
+
+ if(count == 0 || dest == src)
+ return dest;
+
+ if((long)d < (long)s) {
+ if(((long)d | (long)s) & lmask) {
+ // src and/or dest do not align on word boundary
+ if((((long)d ^ (long)s) & lmask) || (count < lsize))
+ len = count; // copy the rest of the buffer with the byte mover
+ else
+ len = lsize - ((long)d & lmask); // move the ptrs up to a word boundary
+
+ count -= len;
+ for(; len > 0; len--)
+ *d++ = *s++;
+ }
+ for(len = count / lsize; len > 0; len--) {
+ *(word *)d = *(word *)s;
+ d += lsize;
+ s += lsize;
+ }
+ for(len = count & lmask; len > 0; len--)
+ *d++ = *s++;
+ } else {
+ d += count;
+ s += count;
+ if(((long)d | (long)s) & lmask) {
+ // src and/or dest do not align on word boundary
+ if((((long)d ^ (long)s) & lmask) || (count <= lsize))
+ len = count;
+ else
+ len = ((long)d & lmask);
+
+ count -= len;
+ for(; len > 0; len--)
+ *--d = *--s;
+ }
+ for(len = count / lsize; len > 0; len--) {
+ d -= lsize;
+ s -= lsize;
+ *(word *)d = *(word *)s;
+ }
+ for(len = count & lmask; len > 0; len--)
+ *--d = *--s;
+ }
+
+ return dest;
+}
+
+#endif
+
diff --git a/lib/string/memset.c b/lib/string/memset.c
new file mode 100644
index 0000000..1abee39
--- /dev/null
+++ b/lib/string/memset.c
@@ -0,0 +1,61 @@
+/*
+** Copyright 2005, Michael Noisternig. All rights reserved.
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+void *
+memset(void *s, int c, size_t count)
+{
+ char *xs = (char *) s;
+ size_t len = (-(size_t)s) & (sizeof(size_t)-1);
+ int cc = c & 0xff;
+
+ if ( count > len ) {
+ count -= len;
+ cc |= cc << 8;
+ cc |= cc << 16;
+
+ // write to non-aligned memory byte-wise
+ for ( ; len > 0; len-- )
+ *xs++ = c;
+
+ // write to aligned memory dword-wise
+ for ( len = count/sizeof(size_t); len > 0; len-- ) {
+ *((size_t *)xs) = cc;
+ xs += sizeof(size_t);
+ }
+
+ count &= sizeof(size_t)-1;
+ }
+
+ // write remaining bytes
+ for ( ; count > 0; count-- )
+ *xs++ = c;
+
+ return s;
+}
diff --git a/lib/string/strchr.c b/lib/string/strchr.c
new file mode 100644
index 0000000..4c6bb16
--- /dev/null
+++ b/lib/string/strchr.c
@@ -0,0 +1,37 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+char *
+strchr(const char *s, int c)
+{
+ for(; *s != (char) c; ++s)
+ if (*s == '\0')
+ return NULL;
+ return (char *) s;
+}
diff --git a/lib/string/strcmp.c b/lib/string/strcmp.c
new file mode 100644
index 0000000..5402718
--- /dev/null
+++ b/lib/string/strcmp.c
@@ -0,0 +1,41 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+int
+strcmp(char const *cs, char const *ct)
+{
+ signed char __res;
+
+ while(1) {
+ if((__res = *cs - *ct++) != 0 || !*cs++)
+ break;
+ }
+
+ return __res;
+}
diff --git a/lib/string/strcpy.c b/lib/string/strcpy.c
new file mode 100644
index 0000000..e6cc78e
--- /dev/null
+++ b/lib/string/strcpy.c
@@ -0,0 +1,39 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+char *
+strcpy(char *dest, char const *src)
+{
+ char *tmp = dest;
+
+ while((*dest++ = *src++) != '\0')
+ ;
+ return tmp;
+}
+
diff --git a/lib/string/strlcat.c b/lib/string/strlcat.c
new file mode 100644
index 0000000..c94e90d
--- /dev/null
+++ b/lib/string/strlcat.c
@@ -0,0 +1,55 @@
+/* $OpenBSD: strlcat.c,v 1.19 2019/01/25 00:19:25 millert Exp $ */
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Appends src to string dst of size dsize (unlike strncat, dsize is the
+ * full size of dst, not space left). At most dsize-1 characters
+ * will be copied. Always NUL terminates (unless dsize <= strlen(dst)).
+ * Returns strlen(src) + MIN(dsize, strlen(initial dst)).
+ * If retval >= dsize, truncation occurred.
+ */
+size_t
+strlcat(char *dst, const char *src, size_t dsize)
+{
+ const char *odst = dst;
+ const char *osrc = src;
+ size_t n = dsize;
+ size_t dlen;
+
+ /* Find the end of dst and adjust bytes left but don't go past end. */
+ while (n-- != 0 && *dst != '\0')
+ dst++;
+ dlen = dst - odst;
+ n = dsize - dlen;
+
+ if (n-- == 0)
+ return(dlen + strlen(src));
+ while (*src != '\0') {
+ if (n != 0) {
+ *dst++ = *src;
+ n--;
+ }
+ src++;
+ }
+ *dst = '\0';
+
+ return(dlen + (src - osrc)); /* count does not include NUL */
+}
diff --git a/lib/string/strlcpy.c b/lib/string/strlcpy.c
new file mode 100644
index 0000000..2fa498c
--- /dev/null
+++ b/lib/string/strlcpy.c
@@ -0,0 +1,50 @@
+/* $OpenBSD: strlcpy.c,v 1.16 2019/01/25 00:19:25 millert Exp $ */
+
+/*
+ * Copyright (c) 1998, 2015 Todd C. Miller <millert@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <string.h>
+
+/*
+ * Copy string src to buffer dst of size dsize. At most dsize-1
+ * chars will be copied. Always NUL terminates (unless dsize == 0).
+ * Returns strlen(src); if retval >= dsize, truncation occurred.
+ */
+size_t
+strlcpy(char *dst, const char *src, size_t dsize)
+{
+ const char *osrc = src;
+ size_t nleft = dsize;
+
+ /* Copy as many bytes as will fit. */
+ if (nleft != 0) {
+ while (--nleft != 0) {
+ if ((*dst++ = *src++) == '\0')
+ break;
+ }
+ }
+
+ /* Not enough room in dst, add NUL and traverse rest of src. */
+ if (nleft == 0) {
+ if (dsize != 0)
+ *dst = '\0'; /* NUL-terminate dst */
+ while (*src++)
+ ;
+ }
+
+ return(src - osrc - 1); /* count does not include NUL */
+}
diff --git a/lib/string/strlen.c b/lib/string/strlen.c
new file mode 100644
index 0000000..9f87fc0
--- /dev/null
+++ b/lib/string/strlen.c
@@ -0,0 +1,41 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+size_t
+strlen(char const *s)
+{
+ size_t i;
+
+ i= 0;
+ while(s[i]) {
+ i+= 1;
+ }
+
+ return i;
+}
diff --git a/lib/string/strncmp.c b/lib/string/strncmp.c
new file mode 100644
index 0000000..2f4d877
--- /dev/null
+++ b/lib/string/strncmp.c
@@ -0,0 +1,42 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+int
+strncmp(char const *cs, char const *ct, size_t count)
+{
+ signed char __res = 0;
+
+ while(count > 0) {
+ if ((__res = *cs - *ct++) != 0 || !*cs++)
+ break;
+ count--;
+ }
+
+ return __res;
+}
diff --git a/lib/string/strnlen.c b/lib/string/strnlen.c
new file mode 100644
index 0000000..afd19a7
--- /dev/null
+++ b/lib/string/strnlen.c
@@ -0,0 +1,38 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+size_t
+strnlen(char const *s, size_t count)
+{
+ const char *sc;
+
+ for(sc = s; count-- && *sc != '\0'; ++sc)
+ ;
+ return sc - s;
+}
diff --git a/lib/string/strrchr.c b/lib/string/strrchr.c
new file mode 100644
index 0000000..5327659
--- /dev/null
+++ b/lib/string/strrchr.c
@@ -0,0 +1,45 @@
+/*
+** Copyright 2001, Manuel J. Petit. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+char *
+strrchr(char const *s, int c)
+{
+ char const *last= c?0:s;
+
+
+ while(*s) {
+ if(*s== c) {
+ last= s;
+ }
+
+ s+= 1;
+ }
+
+ return (char *)last;
+}
diff --git a/lib/string/strrev.c b/lib/string/strrev.c
new file mode 100644
index 0000000..a961d79
--- /dev/null
+++ b/lib/string/strrev.c
@@ -0,0 +1,46 @@
+/* Copyright (c) 2012, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of The Linux Foundation nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include <string.h>
+#include <sys/types.h>
+
+void
+strrev(unsigned char *str)
+{
+ int i;
+ int j;
+ unsigned char a;
+ unsigned len = strlen((const char *)str);
+
+ for (i = 0, j = len - 1; i < j; i++, j--)
+ {
+ a = str[i];
+ str[i] = str[j];
+ str[j] = a;
+ }
+}
diff --git a/lib/string/strstr.c b/lib/string/strstr.c
new file mode 100644
index 0000000..9cb7604
--- /dev/null
+++ b/lib/string/strstr.c
@@ -0,0 +1,51 @@
+/*
+** Copyright 2001, Travis Geiselbrecht. All rights reserved.
+** Distributed under the terms of the NewOS License.
+*/
+/*
+ * Copyright (c) 2008 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <string.h>
+#include <sys/types.h>
+
+char *
+strstr(char const *s1, char const *s2)
+{
+ return strstrn(s1, s2, strlen(s2));
+}
+
+char *
+strstrn(char const *s1, char const *s2, size_t l2)
+{
+ size_t l1;
+
+ if (!l2)
+ return (char *)s1;
+ l1 = strlen(s1);
+ while(l1 >= l2) {
+ l1--;
+ if (!memcmp(s1,s2,l2))
+ return (char *)s1;
+ s1++;
+ }
+ return NULL;
+}
diff --git a/test/payload.S b/test/payload.S
new file mode 100644
index 0000000..d1891a2
--- /dev/null
+++ b/test/payload.S
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * payload.S - tiny arm64 image for testing tashaboot. carries the real
+ * Image header from Documentation/arch/arm64/booting.rst, prints one
+ * line on the virt uart, parks. built with the same conventions the
+ * kernel itself uses so the header math in tashaboot is exercised for
+ * real, not against a fake.
+ *
+ * Copyright (C) 2026 Bradley Morgan <brads@mainlining.org>
+ */
+
+#include <asm/linkage.h>
+
+/* the header the bootloader validates */
+#define ARM64_IMAGE_MAGIC 0x644d5241
+
+.section .text.head
+.globl _start
+_start:
+ /* code0/code1: branch over the header, like the kernel does */
+ b 1f
+ .long 0
+
+ /* text_offset, 0x80000 like every kernel since forever */
+ .quad 0x80000
+ /* image_size, filled at build time by scripts/mkpayload.sh */
+ .quad payload_end - _start
+ /* flags, bit 3 = anywhere in memory is fine */
+ .quad (1 << 3)
+ .quad 0
+ .quad 0
+ .quad 0
+ /* magic "ARM\x64" */
+ .long ARM64_IMAGE_MAGIC
+ .long 0
+1:
+ /* the payload entry: x0 = dtb from the bootloader */
+ ldr x1, =0x09000000 /* pl011 on qemu virt */
+ adr x2, msg
+2: ldrb w3, [x2], #1
+ cbz w3, 3f
+ str w3, [x1]
+ b 2b
+3: /* clean exit through semihosting, proof we got here */
+ mov x0, #0x18 /* SYS_EXIT */
+ ldr x1, =0x20026 /* ADP_Stopped_ApplicationExit */
+ hlt #0xF000
+ b 3b
+
+msg: .asciz "payload: alive, tashaboot jumped here\n"
+ .balign 8
+payload_end:
diff --git a/tools/fillsize.py b/tools/fillsize.py
new file mode 100644
index 0000000..fa472d1
--- /dev/null
+++ b/tools/fillsize.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python3
+# fillsize.py - stamp image_size into the arm64 Image header of a
+# built binary. the linker cannot know the final file size, the
+# header field stays 0 through the link, this runs after objcopy.
+import struct
+import sys
+
+path = sys.argv[1]
+d = bytearray(open(path, 'rb').read())
+assert d[0x38:0x3c] == b'ARM\x64', 'no Image magic, refusing to stamp'
+struct.pack_into('<Q', d, 0x10, len(d))
+open(path, 'wb').write(d)
+print('image_size %d stamped' % len(d))