From f83d6a730b083878e5dc5fa476908d2c23460918 Mon Sep 17 00:00:00 2001 From: Zong Li Date: Fri, 18 Sep 2026 01:13:12 -0700 Subject: [PATCH] firmware: fw_base.S: override a0, a1 and a2 from the firmware header Wire up the flags word and the three override values in the OpenSBI firmware header. When the previous booting stage sets one of the FW_HEADER_FLAGS_OVERRIDE_A[012] bits, the cold boot path replaces the matching register with the value stored in the header before doing anything else with the boot arguments. This lets a previous booting stage which cannot pass the boot arguments in registers hand them over by patching a few words in the firmware image instead. For example, a booting stage running on a dedicated boot processor can load the OpenSBI image, patch the header with the hart id, the DTB address and, for FW_DYNAMIC, the address of a struct fw_dynamic_info it built somewhere in DRAM, and then release the hart that runs OpenSBI, without ever being able to set up that hart's registers itself. The flags word is zero in a freshly built image, so nothing is overridden and every existing booting stage keeps passing a0, a1 and a2 in registers as before. Suggested-by: Anup Patel Signed-off-by: Zong Li Reviewed-by: Anup Patel Link: https://lore.kernel.org/r/20260918081313.659655-3-zong.li@sifive.com Signed-off-by: Anup Patel --- firmware/fw_base.S | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/firmware/fw_base.S b/firmware/fw_base.S index d01d0574..a76ddc1b 100644 --- a/firmware/fw_base.S +++ b/firmware/fw_base.S @@ -109,6 +109,37 @@ _fw_header_override_a2: /* Reserved, pads the header up to FW_HEADER_SIZE bytes */ .fill (FW_HEADER_SIZE - FW_HEADER_RESERVED_OFFSET), 1, 0 _start_real: + /* + * Override a0, a1 and a2 with the values from the firmware header. + * + * This is for a previous booting stage which cannot pass the boot + * arguments in registers, but can patch the firmware image before + * jumping to it. Nothing is overridden when the previous booting + * stage left the flags word alone, so the registers passed by all + * existing booting stages are used as-is. + * + * This runs before relocation, so only PC-relative addressing is + * used. t0, t1 and t2 are free to use here: the boot arguments live + * in a0-a4 and every other register is reset further down the cold + * boot path. + */ + lla t0, _fw_header_flags + lw t0, (t0) + andi t1, t0, FW_HEADER_FLAGS_OVERRIDE_A0 + beqz t1, _skip_override_a0 + csrr a0, CSR_MHARTID +_skip_override_a0: + andi t1, t0, FW_HEADER_FLAGS_OVERRIDE_A1 + beqz t1, _skip_override_a1 + lla t2, _fw_header_override_a1 + REG_L a1, (t2) +_skip_override_a1: + andi t1, t0, FW_HEADER_FLAGS_OVERRIDE_A2 + beqz t1, _skip_override_a2 + lla t2, _fw_header_override_a2 + REG_L a2, (t2) +_skip_override_a2: + /* Find preferred boot HART id */ MOV_3R s0, a0, s1, a1, s2, a2 call fw_boot_hart