/* * 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 * * 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 #include #include #include #include #include #include #define TB_VERSION "0.1" #define TB_INITRD_ADDR 0x46000000ULL #define TB_INITRD_FILE "initrd.cpio.gz" extern int tb_console_init(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_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; 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 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); dprintf(ALWAYS, "jumping\n"); tb_boot_linux(img.ep, fw_arg); }