[PATCH 1/3] firmware: fw_base.S: add a formatted OpenSBI firmware header
Zong Li
zong.li at sifive.com
Thu Sep 17 00:33:16 PDT 2026
On Wed, Sep 16, 2026 at 1:09 PM Anup Patel <anup at brainfault.org> wrote:
>
> 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.
Thank you for your suggestion and reminder. Because the header is
shared by all harts, it cannot show each hart's ID correctly. I will
fix this problem in the next patch.
However, I also have a question. In the original flow, could we also
let each hart read its own mhartid CSR into $a0 at the beginning?
If I understand correctly, the previous stage must pass each hart's
mhartid to OpenSBI using $a0. Since a hart should only use its own ID,
I am wondering why OpenSBI doesn't just read the ID by itself in the
original flow.
If you agree that we can do this in the original flow, I can also add
this change to the next patch.
Thanks
>
> > + * 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