/* 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 */ #include .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