[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