[PATCH 3/7] firmware: add fw_dynamic_append firmware type

Zong Li zong.li at sifive.com
Mon Jul 6 20:39:50 PDT 2026


Add a fourth firmware type, FW_DYNAMIC_APPEND, alongside FW_DYNAMIC,
FW_JUMP and FW_PAYLOAD.

fw_dynamic_append.S reuses all the dynamic-info hook functions from
fw_dynamic.S, and additionally reserves a struct fw_dynamic_info
instance in the .fw_dynamic_info section, and provides
'fw_dynamic_append_boot_args', which clears the GPRs and sets
a0=mhartid, a2=&struct and a1=boot_dtb. boot_dtb is read only when
the struct reports version >= FW_DYNAMIC_INFO_VERSION_3. For an
older layout a1 is left at 0 instead of reading past it. That section
is laid out right before .bss by fw_base.ldS, i.e. at the very tail of
the flat binary, where the previous booting stage can find and patch it.

The previous booting stage needs to be responsible for patching the
whole structure at runtime. Because the section is %progbits and KEEP().
the zero-initialised reservation is still emitted to the tail of the
.bin. Defining FW_DYNAMIC_APPEND before including fw_dynamic.S turns on
the matching entry fixup in fw_base.S for this object only.

fw_dynamic_append.elf.ldS mirrors fw_dynamic.elf.ldS, but additionally
defines 'FW_DYNAMIC_APPEND' before including fw_base.ldS; that is what
turns on the .fw_dynamic_info output section for this firmware type only.

Signed-off-by: Zong Li <zong.li at sifive.com>
---
 firmware/fw_dynamic_append.S       | 106 +++++++++++++++++++++++++++++
 firmware/fw_dynamic_append.elf.ldS |  24 +++++++
 2 files changed, 130 insertions(+)
 create mode 100644 firmware/fw_dynamic_append.S
 create mode 100644 firmware/fw_dynamic_append.elf.ldS

diff --git a/firmware/fw_dynamic_append.S b/firmware/fw_dynamic_append.S
new file mode 100644
index 00000000..31eee099
--- /dev/null
+++ b/firmware/fw_dynamic_append.S
@@ -0,0 +1,106 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 SiFive
+ *
+ * Authors:
+ *   Zong Li <zong.li at sifive.com>
+ *
+ * This firmware type behaves like FW_DYNAMIC, except that the
+ * struct fw_dynamic_info is appended at the tail of the OpenSBI binary
+ * (placed before .bss so that it is part of the flat binary file size)
+ * instead of being passed by pointer in a2 by the previous booting stage.
+ *
+ * The previous booting stage is expected to locate the structure at the
+ * end of the .bin image and patch ALL of its fields at runtime (magic,
+ * version, next_addr, next_mode, options, boot_hart and boot_dtb) before
+ * jumping to _start. fw_base.S then synthesizes a0 (mhartid), a1 (boot_dtb)
+ * and a2 (&fw_dynamic_info_append) from this appended structure.
+ */
+
+#include <sbi/fw_dynamic.h>
+
+/* Enable the FW_DYNAMIC_APPEND entry fixup inside fw_base.S */
+#define FW_DYNAMIC_APPEND
+
+/*
+ * Reuse every fw_* hook implementation (fw_boot_hart, fw_save_info,
+ * fw_next_arg1, fw_next_addr, fw_next_mode, fw_options) from fw_dynamic.S.
+ * fw_dynamic.S pulls in fw_base.S for us, with FW_DYNAMIC_APPEND defined.
+ */
+#include "fw_dynamic.S"
+
+	/*
+	 * fw_dynamic_append_boot_args - synthesize the boot register state.
+	 *
+	 * Called once from the cold _start path in fw_base.S (guarded by
+	 * FW_DYNAMIC_APPEND). It runs BEFORE relocation, so only PC-relative
+	 * addressing (lla) and CSR reads are used.
+	 *
+	 * It establishes a0-a4 (the registers _reset_regs does NOT clear) and
+	 * rebuilds the same arguments the other firmware types receive from
+	 * the previous stage; the remaining GPRs are cleared by _reset_regs
+	 * later in the cold path:
+	 *   a0 = mhartid
+	 *   a2 = &fw_dynamic_info_append (the appended structure)
+	 *   a1 = boot_dtb loaded from that structure
+	 *
+	 * ra is intentionally NOT zeroed: it holds the return address for the
+	 * ret below. It is overwritten by the next call in fw_base.S before
+	 * being used, so no stale value is ever observed.
+	 */
+	.section .entry, "ax", %progbits
+	.align 3
+	.global fw_dynamic_append_boot_args
+fw_dynamic_append_boot_args:
+	/* a0 = mhartid */
+	csrr	a0, CSR_MHARTID
+
+	/* a2 = address of the appended fw_dynamic_info */
+	lla	a2, fw_dynamic_info_append
+
+	/*
+	 * a0-a4 are exactly the registers _reset_regs leaves untouched, so
+	 * they are established here. Every other GPR is cleared later by
+	 * _reset_regs in the cold path. a1 defaults to 0 (no dtb) and a3/a4
+	 * are reserved boot args that this firmware type does not use.
+	 */
+	li	a1, 0
+	li	a3, 0
+	li	a4, 0
+
+	/*
+	 * a1 = boot_dtb, but only when the appended structure is version 3
+	 * or newer. boot_dtb was introduced in FW_DYNAMIC_INFO_VERSION_3, so
+	 * an older layout does not carry that field. In that case a1 stays 0
+	 * instead of reading past the end of the struct and handing a garbage
+	 * pointer to the next stage.
+	 */
+	li	t0, FW_DYNAMIC_INFO_VERSION_3
+	REG_L	t1, FW_DYNAMIC_INFO_VERSION_OFFSET(a2)
+	blt	t1, t0, 1f
+	REG_L	a1, FW_DYNAMIC_INFO_BOOT_DTB_OFFSET(a2)
+1:	ret
+
+	/*
+	 * Reserve one struct fw_dynamic_info at the tail of the .bin image.
+	 *
+	 * The previous booting stage finds this block at the end of the file
+	 * and fills in every field at runtime.
+	 * The zeros below are placeholders that only reserve file space.
+	 * The section is %progbits (so the bytes are stored in the falt
+	 * binary, unlike .bss) and is KEEP() in fw_base.ldS (so it is never
+	 * garbage-collected), which guarantees the reservation always
+	 * lands at the very tail of the .bin.
+	 */
+	.section .fw_dynamic_info, "aw", %progbits
+	.align 3
+	.global fw_dynamic_info_append
+fw_dynamic_info_append:
+	RISCV_PTR 0x0	/* magic     (patched by prev stage) */
+	RISCV_PTR 0x0	/* version   (patched by prev stage) */
+	RISCV_PTR 0x0	/* next_addr (patched by prev stage) */
+	RISCV_PTR 0x0	/* next_mode (patched by prev stage) */
+	RISCV_PTR 0x0	/* options   (patched by prev stage) */
+	RISCV_PTR 0x0	/* boot_hart (patched by prev stage) */
+	RISCV_PTR 0x0	/* boot_dtb  (patched by prev stage) */
diff --git a/firmware/fw_dynamic_append.elf.ldS b/firmware/fw_dynamic_append.elf.ldS
new file mode 100644
index 00000000..fbc27fc1
--- /dev/null
+++ b/firmware/fw_dynamic_append.elf.ldS
@@ -0,0 +1,24 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2026 SiFive
+ *
+ * Authors:
+ *   Zong Li <zong.li at sifive.com>
+ */
+
+OUTPUT_ARCH(riscv)
+ENTRY(_start)
+
+/*
+ * Enable the .fw_dynamic_info output section inside fw_base.ldS. This must
+ * be defined before the #include below so the #ifdef in fw_base.ldS sees it
+ */
+#define FW_DYNAMIC_APPEND
+
+SECTIONS
+{
+	#include "fw_base.ldS"
+
+	PROVIDE(_fw_reloc_end = .);
+}
-- 
2.43.7




More information about the opensbi mailing list