[PATCH 1/3] firmware: fw_base.S: add a formatted OpenSBI firmware header

Anup Patel anup at brainfault.org
Tue Sep 15 22:08:49 PDT 2026


On Tue, Sep 1, 2026 at 7:52 AM Zong Li <zong.li at sifive.com> wrote:
>
> Add a fixed 128-byte header at the very beginning of every OpenSBI
> firmware image, regardless of the firmware type. The header lets the
> previous booting stage identify an OpenSBI image and, more importantly,
> gives it a well-known place inside the image to hand over information
> to OpenSBI by patching a few words instead of setting up registers.
>
> This first patch only introduces the layout:
>
>   - a 4-byte jump over the header to _start_real, so that _start stays
>     the entry point of the image. The jump is assembled with
>     '.option norvc' so that the fields behind it are always at a fixed
>     offset, even for a build with compressed instructions enabled,
>
>   - the 'OSBI' magic, a header version, the XLEN the firmware was built
>     for and the size of the firmware image, so that the previous booting
>     stage can validate the image and figure out how much memory it
>     occupies,
>
>   - a flags word and three 8-byte override values, which are unused
>     (and zero) for now and are wired up by the next patch.
>
> The layout is deliberately identical for RV32 and RV64 so that the
> previous booting stage can parse the header without knowing the XLEN of
> the firmware upfront. Each override value is followed by a reserved
> 8-byte slot so that the same layout can be extended to RV128 later.
>
> Suggested-by: Anup Patel <anup at brainfault.org>
> Signed-off-by: Zong Li <zong.li at sifive.com>
> ---
>  firmware/fw_base.S | 68 ++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 68 insertions(+)
>
> diff --git a/firmware/fw_base.S b/firmware/fw_base.S
> index 0c5c65c1..dd2adb8a 100644
> --- a/firmware/fw_base.S
> +++ b/firmware/fw_base.S
> @@ -17,6 +17,15 @@
>  #define BOOT_LOTTERY_ACQUIRED          1
>  #define BOOT_STATUS_BOOT_HART_DONE     1
>
> +/* OpenSBI firmware header */
> +#define FW_HEADER_MAGIC_VALUE          0x4942534f /* ASCII string "OSBI" */
> +#define FW_HEADER_VERSION              0x1
> +#define FW_HEADER_SIZE                 128
> +#define FW_HEADER_RESERVED_OFFSET      0x48
> +#define FW_HEADER_FLAGS_OVERRIDE_A0    (1 << 0)
> +#define FW_HEADER_FLAGS_OVERRIDE_A1    (1 << 1)
> +#define FW_HEADER_FLAGS_OVERRIDE_A2    (1 << 2)
> +
>  .macro MOV_3R __d0, __s0, __d1, __s1, __d2, __s2
>         add     \__d0, \__s0, zero
>         add     \__d1, \__s1, zero
> @@ -44,8 +53,67 @@
>         .section .entry, "ax", %progbits
>         .align 3
>         .globl _start
> +       .globl _start_real
>         .globl _start_warm
>  _start:
> +       /*
> +        * OpenSBI firmware header
> +        *
> +        * Every OpenSBI firmware image starts with this fixed 128-byte
> +        * header. The layout is identical for RV32 and RV64 (and can be
> +        * extended for RV128 by using the reserved half of each override
> +        * field), so the previous booting stage can parse the header without
> +        * knowing the XLEN of the firmware upfront.
> +        *
> +        * Offset  Size  Field
> +        *   0x00     4  jump to _start_real
> +        *   0x04     4  magic ('OSBI')
> +        *   0x08     4  header version
> +        *   0x0c     4  firmware XLEN
> +        *   0x10     4  firmware size (_fw_end - _fw_start)
> +        *   0x14     4  flags (FW_HEADER_FLAGS_*)
> +        *   0x18     8  override value for a0
> +        *   0x20     8  reserved (upper half of the a0 value on RV128)

Thinking about this more, we don't need "override value of a0" in the
header instead whenever FW_HEADER_FLAGS_OVERRIDE_A0 is
set we can use mhartid CSR value as the value of a0. This will work
better for all CPUs entering at _start.

> +        *   0x28     8  override value for a1
> +        *   0x30     8  reserved (upper half of the a1 value on RV128)
> +        *   0x38     8  override value for a2
> +        *   0x40     8  reserved (upper half of the a2 value on RV128)
> +        *   0x48    56  reserved
> +        */
> +_fw_header_jump:
> +       /*
> +        * Must stay a 4-byte instruction so that the fields below are always
> +        * at a fixed offset. 'norvc' keeps the assembler from picking c.j and
> +        * 'norelax' keeps the linker from compressing it later on, which it
> +        * would otherwise happily do for a jump this short.
> +        */
> +       .option push
> +       .option norvc
> +       .option norelax
> +       j       _start_real
> +       .option pop
> +_fw_header_magic:
> +       .word   FW_HEADER_MAGIC_VALUE
> +_fw_header_version:
> +       .word   FW_HEADER_VERSION
> +_fw_header_xlen:
> +       .word   __riscv_xlen
> +_fw_header_size:
> +       .word   (_fw_end - _fw_start)
> +_fw_header_flags:
> +       .word   0
> +_fw_header_override_a0:
> +       .dword  0
> +       .dword  0                       /* reserved */
> +_fw_header_override_a1:
> +       .dword  0
> +       .dword  0                       /* reserved */
> +_fw_header_override_a2:
> +       .dword  0
> +       .dword  0                       /* reserved */
> +       /* Reserved, pads the header up to FW_HEADER_SIZE bytes */
> +       .fill   (FW_HEADER_SIZE - FW_HEADER_RESERVED_OFFSET), 1, 0
> +_start_real:
>         /* Find preferred boot HART id */
>         MOV_3R  s0, a0, s1, a1, s2, a2
>         call    fw_boot_hart
> --
> 2.43.7
>

Regards,
Anup



More information about the opensbi mailing list