summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--Makefile53
-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/kernel/boot.S43
-rw-r--r--arch/arm64/kernel/exceptions.c67
-rw-r--r--arch/arm64/kernel/halt.c18
-rw-r--r--arch/arm64/kernel/start.S259
-rw-r--r--arch/arm64/kernel/tashaboot.lds67
-rw-r--r--arch/arm64/lib/cache.S100
-rw-r--r--arch/arm64/lib/semihosting.S18
-rw-r--r--common/console.c74
-rw-r--r--common/image.c77
-rw-r--r--common/load.c57
-rw-r--r--common/main.c75
-rw-r--r--include/assert.h41
-rw-r--r--include/boot.h32
-rw-r--r--include/compiler.h133
-rw-r--r--include/ctype.h43
-rw-r--r--include/debug.h109
-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
52 files changed, 3715 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..be2cb63
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,53 @@
+# 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/exceptions.o \
+ arch/arm64/kernel/halt.o \
+ arch/arm64/kernel/boot.o \
+ arch/arm64/lib/cache.o \
+ arch/arm64/lib/semihosting.o \
+ common/main.o common/console.o common/image.o common/load.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 $< $@
+
+%.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/kernel/boot.S b/arch/arm64/kernel/boot.S
new file mode 100644
index 0000000..615be06
--- /dev/null
+++ b/arch/arm64/kernel/boot.S
@@ -0,0 +1,43 @@
+/* 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
+
+ /* 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
+
+ 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/start.S b/arch/arm64/kernel/start.S
new file mode 100644
index 0000000..705721f
--- /dev/null
+++ b/arch/arm64/kernel/start.S
@@ -0,0 +1,259 @@
+/* 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:
+ b reset
+
+ .balign 8
+.globl _text_base
+_text_base:
+ .quad 0x40000000
+
+reset:
+ /* keep the dtb pointer before anything clobbers x0 */
+ mov x19, x0
+
+ /* park secondary cores, they have nothing to do yet */
+ mrs x0, mpidr_el1
+ and x0, x0, #0xff
+ cbnz x0, park
+
+ /* which EL are we in, 0x8 per level shifted into bits 3:2 */
+ 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
+
+from_el3:
+ /*
+ * EL3 holds the security state. the kernel runs non-secure, so
+ * set SCR_EL3.NS before dropping to EL2, which the kernel
+ * prefers (booting.rst, EL2 RECOMMENDED).
+ */
+ 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:
+ /*
+ * stay at EL2: the kernel wants it for the virtualization
+ * extensions and hands off from there. everything below scrubs
+ * the EL2 state so the kernel starts clean.
+ */
+
+ /* EL1 will be aarch64 when the kernel drops itself down */
+ 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
+
+ b mmu_check
+
+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, grows down from the image end */
+ ldr x0, =__image_end
+ mov sp, x0
+
+ /* 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 */
+park:
+ wfe
+ b park
+
+/*
+ * 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
+
+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 x1, far_el2
+ b 2f
+1:
+ mrs x0, esr_el1
+ mrs x1, far_el1
+2:
+ mov x2, lr
+ bl exc_report
+ ldp x29, x30, [sp], #16
+ b park
+
+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
+ 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..4f8dfb1
--- /dev/null
+++ b/arch/arm64/kernel/tashaboot.lds
@@ -0,0 +1,67 @@
+/* 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
+{
+ . = 0x40000000;
+
+ __image_copy_start = .;
+ _text_start = .;
+
+ .text :
+ {
+ arch/arm64/kernel/start.o (.text.boot)
+ *(.text.boot)
+ *(.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 = .;
+
+ __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/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/common/console.c b/common/console.c
new file mode 100644
index 0000000..a7108ba
--- /dev/null
+++ b/common/console.c
@@ -0,0 +1,74 @@
+/*
+ * console.c - the console dprintf writes to. semihosting SYS_WRITE0,
+ * the firmware service on the qemu dev path, the arm64 stand-in for
+ * the bios teletype the osdev loaders use. on real hardware this is
+ * the one file that changes.
+ *
+ * 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 <debug.h>
+#include <semihosting.h>
+
+/*
+ * lk's _dprintf sink. printf buffers a line here then hands it to the
+ * host, 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)
+{
+ if (console_len == 0)
+ return;
+ 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') {
+ /* the host terminal wants 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)
+{
+ /*
+ * the probe is one harmless call: SYS_GET_ERRNO with no file
+ * handle open. a host answers, bare metal ignores the trap.
+ */
+ if (!smh_probe())
+ return -1;
+ console_len = 0;
+ return 0;
+}
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..cb3b41d
--- /dev/null
+++ b/common/load.c
@@ -0,0 +1,57 @@
+/* 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;
+}
diff --git a/common/main.c b/common/main.c
new file mode 100644
index 0000000..fb388cb
--- /dev/null
+++ b/common/main.c
@@ -0,0 +1,75 @@
+/*
+ * 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"
+
+extern int tb_console_init(void);
+
+/*
+ * fixed load address, the osdev way. past the bootloader at the
+ * bottom of RAM, the image header decides its final resting place.
+ */
+#define TB_LOAD_ADDR 0x40200000
+
+/* 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);
+
+void tashaboot_main(uintptr_t fw_arg)
+{
+ struct tb_image img;
+ int ret;
+
+ if (tb_console_init())
+ return;
+
+ dprintf(ALWAYS, "tashaboot " TB_VERSION "\n");
+
+ ret = tb_load_semihosting(TB_BOOTFILE, TB_LOAD_ADDR, &img);
+ if (ret) {
+ dprintf(ALWAYS, "load failed (%d), halting\n", ret);
+ platform_halt();
+ }
+
+ dprintf(ALWAYS, "loaded %llu bytes at %lx, entry %lx\n",
+ (unsigned long long)img.size, img.load, img.ep);
+ dprintf(ALWAYS, "jumping\n");
+
+ tb_boot_linux(img.ep, fw_arg);
+}
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..a5fe3ae
--- /dev/null
+++ b/include/boot.h
@@ -0,0 +1,32 @@
+/* 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_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/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: