[PATCH 1/3] firmware: fw_base.S: add a formatted OpenSBI firmware header
Zong Li
zong.li at sifive.com
Thu Sep 17 02:00:26 PDT 2026
On Thu, Sep 17, 2026 at 4:34 PM Anup Patel <anup at brainfault.org> wrote:
>
> On Thu, Sep 17, 2026 at 1:03 PM Zong Li <zong.li at sifive.com> wrote:
> >
> > 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?
>
> My suggestion is that each hart will override $a0 with its own mhartid only
> when OpenSBI header flags have FW_HEADER_FLAGS_OVERRIDE_A0
> set so this will allow us to drop _fw_header_override_a0 from the OpenSBI
> header.
>
> > 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.
>
> Even though OpenSBI does not use the value passed on $a0 as hart ID,
> we should still stay consistent with boot protocol which has been around
> for many years.
Thank you for the elaboration. I will override $a0 with its own
mhartid only for the firmware header flow in the next version. Thanks
>
> Regards,
> Anup
More information about the opensbi
mailing list