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