From: George Chan <gchan9...@gmail.com> It was EL3->EL2 or EL2->EL1 when boot. But in many case such chain loading from bootloader is already run in EL1.
There is no practically way to raise EL1 to EL2. Thus theonly way to start kernel is direct jump. This apporach is similar to KEXEC purgatory idea but do not need to make an extra bulk of binary blob. This applied when current_el() is 1 only. Signed-off-by: George Chan <gchan9...@gmail.com> --- U-Boot usually run in EL3 or EL2 mode so assumed kernel will start at EL2, or switch from EL2 to enter EL1. Test shows in QCOM SM7125 phone environment chainloading U-Boot is already in EL1 so doing EL2 transition EL1 gone wrong. This patch enable the EL1 chain loading kernel by direct jump to image->ep address. This calling convention apply to bootm only. In short, borrow the logic from kexec-tool purgatory idea and issue a direct jump when current_el() is EL1. --- arch/arm/cpu/armv8/Makefile | 2 +- arch/arm/cpu/armv8/entry.S | 42 ++++++++++++++++++++++++++++++++++++++++++ arch/arm/lib/bootm.c | 18 ++++++++++++++++-- 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/arch/arm/cpu/armv8/Makefile b/arch/arm/cpu/armv8/Makefile index b4126c61df1..d5b0ffe8c87 100644 --- a/arch/arm/cpu/armv8/Makefile +++ b/arch/arm/cpu/armv8/Makefile @@ -5,7 +5,7 @@ extra-y := start.o -obj-y += cpu.o +obj-y += cpu.o entry.o ifndef CONFIG_$(PHASE_)TIMER obj-$(CONFIG_SYS_ARCH_TIMER) += generic_timer.o endif diff --git a/arch/arm/cpu/armv8/entry.S b/arch/arm/cpu/armv8/entry.S new file mode 100644 index 00000000000..19a7da2438e --- /dev/null +++ b/arch/arm/cpu/armv8/entry.S @@ -0,0 +1,42 @@ +/* + * ARM64 purgatory. + */ +#include <asm-offsets.h> +#include <config.h> +#include <linux/linkage.h> +#include <asm/macro.h> + +.macro size, sym:req + .size \sym, . - \sym +.endm + +.pushsection .text.purgatory_start, "ax" +ENTRY(purgatory_start) +purgatory_start: + +// adr x19, .Lstack +// mov sp, x19 + +// bl purgatory + + /* Start new image. */ + ldr x17, kernel_entry + ldr x0, dtb_addr + mov x1, xzr + mov x2, xzr + mov x3, xzr + br x17 +ENDPROC(purgatory_start) +.popsection +.align 3 + +.globl kernel_entry +kernel_entry: + .quad 0 +size kernel_entry + +.globl dtb_addr +dtb_addr: + .quad 0 +size dtb_addr +.end diff --git a/arch/arm/lib/bootm.c b/arch/arm/lib/bootm.c index 974cbfe8400..5a15725317c 100644 --- a/arch/arm/lib/bootm.c +++ b/arch/arm/lib/bootm.c @@ -269,6 +269,10 @@ __weak void update_os_arch_secondary_cores(uint8_t os_arch) { } +extern void (*kernel_entry)(void *fdt_addr, void *res0, void *res1, + void *res2); +extern void* dtb_addr; + #ifdef CONFIG_ARMV8_SWITCH_TO_EL1 static void switch_to_el1(void) { @@ -290,13 +294,13 @@ static void switch_to_el1(void) static void boot_jump_linux(struct bootm_headers *images, int flag) { #ifdef CONFIG_ARM64 - void (*kernel_entry)(void *fdt_addr, void *res0, void *res1, - void *res2); int fake = (flag & BOOTM_STATE_OS_FAKE_GO); kernel_entry = (void (*)(void *fdt_addr, void *res0, void *res1, void *res2))images->ep; + dtb_addr = images->ft_addr; + debug("## Transferring control to Linux (at address %lx)...\n", (ulong) kernel_entry); bootstage_mark(BOOTSTAGE_ID_RUN_OS); @@ -311,6 +315,16 @@ static void boot_jump_linux(struct bootm_headers *images, int flag) update_os_arch_secondary_cores(images->os.arch); + /* right now switch_to_el1() is hanging the devlice + * when it is already in EL1 so cant do anything about it. + */ + if (current_el() == 1) { + void purgatory_start(void); + purgatory_start(); + panic("nothing to do...\n"); + return; + } + #ifdef CONFIG_ARMV8_SWITCH_TO_EL1 armv8_switch_to_el2((u64)images->ft_addr, 0, 0, 0, (u64)switch_to_el1, ES_TO_AARCH64); --- base-commit: 848f7ffc64aa7c4cc2229095812625c12343c8c1 change-id: 20250407-el1-boot-792926ba6090 Best regards, -- George Chan <gchan9...@gmail.com>