From: Zong Li <zong.li@sifive.com>
To: opensbi@lists.infradead.org, anup@brainfault.org
Cc: Zong Li <zong.li@sifive.com>
Subject: [PATCH 3/3] docs: firmware: document the OpenSBI firmware header
Date: Mon, 31 Aug 2026 19:22:11 -0700 [thread overview]
Message-ID: <20260901022211.2619560-4-zong.li@sifive.com> (raw)
In-Reply-To: <20260901022211.2619560-1-zong.li@sifive.com>
Add docs/firmware/fw_header.md describing the layout of the 128-byte
OpenSBI firmware header, the a0/a1/a2 override flags, what a previous
booting stage is expected to put in each register, and an example of how
to patch the header. Also point at it from fw.md, next to the
description of the registers the previous booting stage passes.
Signed-off-by: Zong Li <zong.li@sifive.com>
---
docs/firmware/fw.md | 9 +++
| 127 +++++++++++++++++++++++++++++++++++++
2 files changed, 136 insertions(+)
create mode 100644 docs/firmware/fw_header.md
diff --git a/docs/firmware/fw.md b/docs/firmware/fw.md
index d2980967..6b5ff153 100644
--- a/docs/firmware/fw.md
+++ b/docs/firmware/fw.md
@@ -16,6 +16,11 @@ of RISC-V CPU:
* device tree blob address in memory via *a1* register. The address must
be aligned to 8 bytes.
+A previous booting stage which cannot set up these registers can instead pass
+the same information by patching the OpenSBI firmware header, which is present
+at the beginning of every OpenSBI firmware image. See *[FW_HEADER]* for
+details.
+
OpenSBI currently supports three different types of firmwares.
Firmware with Dynamic Information (*FW_DYNAMIC*)
@@ -82,9 +87,13 @@ following documents.
* *[FW_PAYLOAD]*: The *Firmware with Payload (FW_PAYLOAD)* is described in more
details in the file *fw_payload.md*.
+The *OpenSBI firmware header*, which is common to all firmware types, is
+described in *[FW_HEADER]*.
+
[FW_DYNAMIC]: fw_dynamic.md
[FW_JUMP]: fw_jump.md
[FW_PAYLOAD]: fw_payload.md
+[FW_HEADER]: fw_header.md
Providing different payloads to OpenSBI Firmware
------------------------------------------------
--git a/docs/firmware/fw_header.md b/docs/firmware/fw_header.md
new file mode 100644
index 00000000..7dfca111
--- /dev/null
+++ b/docs/firmware/fw_header.md
@@ -0,0 +1,127 @@
+OpenSBI Firmware Header
+=======================
+
+Every OpenSBI firmware image, regardless of its type (*FW_DYNAMIC*, *FW_JUMP*
+or *FW_PAYLOAD*), starts with a fixed 128-byte header. The header serves two
+purposes:
+
+1. it lets the previous booting stage recognize an OpenSBI firmware image and
+ find out what it was built for, and
+2. it gives the previous booting stage a well-known place inside the image to
+ hand over the boot arguments by patching a few words, instead of setting up
+ the *a0*, *a1* and *a2* registers.
+
+The second point matters when the previous booting stage cannot set up those
+registers at all. A typical example is a system where the previous booting
+stage runs on a dedicated boot processor: it can load and patch the OpenSBI
+image in DRAM and then release the application hart that runs OpenSBI, but it
+can never execute on that hart and therefore cannot place anything in its
+registers.
+
+The first word of the header is a jump over the header, so the entry point of
+the firmware is unchanged: the previous booting stage still jumps to the first
+byte of the image.
+
+Header layout
+-------------
+
+| Offset | Size | Field |
+|--------|------|---------------------------------------------------|
+| 0x00 | 4 | Jump over the header (a 4-byte `j` instruction) |
+| 0x04 | 4 | Magic value, 'OSBI' (0x4942534f) |
+| 0x08 | 4 | Header version, currently 1 |
+| 0x0c | 4 | XLEN the firmware was built for, 32 or 64 |
+| 0x10 | 4 | Firmware size in bytes |
+| 0x14 | 4 | Flags, see below |
+| 0x18 | 8 | Override value for *a0* |
+| 0x20 | 8 | Reserved (upper half of the *a0* value on RV128) |
+| 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 |
+
+All fields are little-endian. The layout is deliberately the same for RV32 and
+RV64, so the previous booting stage can parse the header, and in particular
+check the *XLEN* field, without knowing the XLEN of the firmware upfront. The
+override values are 8 bytes wide on both; on RV32 only the lower 4 bytes are
+used and the upper 4 bytes must be zero. Each override value is followed by a
+reserved 8-byte slot so that the same layout can be extended to RV128 later.
+
+The *firmware size* field is `_fw_end - _fw_start`, i.e. how much memory the
+firmware image occupies once loaded, including the *.bss* section. Note that
+this is not the size of the flat binary file, which is smaller because
+*.bss* is not stored in the file. OpenSBI also needs some scratch space
+beyond the firmware image, so this field alone does not describe everything
+the previous booting stage has to reserve.
+
+Overriding a0, a1 and a2
+------------------------
+
+The following flags are defined:
+
+| Flag | Value | Description |
+|-----------------------------|-------|---------------------------------|
+| FW_HEADER_FLAGS_OVERRIDE_A0 | 1 << 0| Override *a0* from offset 0x18 |
+| FW_HEADER_FLAGS_OVERRIDE_A1 | 1 << 1| Override *a1* from offset 0x28 |
+| FW_HEADER_FLAGS_OVERRIDE_A2 | 1 << 2| Override *a2* from offset 0x38 |
+
+For every flag that is set, OpenSBI replaces the corresponding register with
+the value from the header before it looks at the boot arguments. Registers
+whose flag is clear are used exactly as passed by the previous booting stage.
+
+The flags word is zero in a freshly built image, so a previous booting stage
+that already passes *a0*, *a1* and *a2* in registers is unaffected and needs
+to know nothing about the header.
+
+The meaning of the registers is unchanged, so the previous booting stage
+should patch:
+
+* *a0* with the hart id of the hart that will enter OpenSBI,
+* *a1* with the device tree blob address, which must be 8-byte aligned, and
+* *a2* with the address of a *struct fw_dynamic_info*, for a *FW_DYNAMIC*
+ firmware. The structure itself is not part of the OpenSBI image; the
+ previous booting stage has to place it in memory that OpenSBI does not
+ overwrite, which notably excludes the OpenSBI *.bss* section and the
+ scratch space above the firmware.
+
+Patching the header
+-------------------
+
+The header is at the very beginning of the image, so all offsets above are
+relative to the address the image was loaded at. For example, on RV64:
+
+```c
+#define FW_HEADER_MAGIC_VALUE 0x4942534f
+#define FW_HEADER_FLAGS_OVERRIDE_A0 (1 << 0)
+#define FW_HEADER_FLAGS_OVERRIDE_A1 (1 << 1)
+#define FW_HEADER_FLAGS_OVERRIDE_A2 (1 << 2)
+
+struct fw_header {
+ uint32_t jump;
+ uint32_t magic;
+ uint32_t version;
+ uint32_t xlen;
+ uint32_t size;
+ uint32_t flags;
+ uint64_t override_a0;
+ uint64_t reserved0;
+ uint64_t override_a1;
+ uint64_t reserved1;
+ uint64_t override_a2;
+ uint64_t reserved2;
+ uint64_t reserved[7];
+};
+
+struct fw_header *hdr = (struct fw_header *)opensbi_load_addr;
+
+if (hdr->magic != FW_HEADER_MAGIC_VALUE || hdr->xlen != 64)
+ return -EINVAL;
+
+hdr->override_a0 = hartid;
+hdr->override_a1 = dtb_addr;
+hdr->override_a2 = (uint64_t)dynamic_info;
+hdr->flags = FW_HEADER_FLAGS_OVERRIDE_A0 |
+ FW_HEADER_FLAGS_OVERRIDE_A1 |
+ FW_HEADER_FLAGS_OVERRIDE_A2;
+```
--
2.43.7
--
opensbi mailing list
opensbi@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/opensbi
prev parent reply other threads:[~2026-09-01 2:22 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-01 2:22 [PATCH 0/3] Support OpenSBI firmware header Zong Li
2026-09-01 2:22 ` [PATCH 1/3] firmware: fw_base.S: add a formatted " Zong Li
2026-09-01 2:22 ` [PATCH 2/3] firmware: fw_base.S: override a0, a1 and a2 from the " Zong Li
2026-09-01 2:22 ` Zong Li [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260901022211.2619560-4-zong.li@sifive.com \
--to=zong.li@sifive.com \
--cc=anup@brainfault.org \
--cc=opensbi@lists.infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox