mirror of
https://github.com/riscv-software-src/opensbi.git
synced 2026-09-20 15:21:42 +01:00
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> Reviewed-by: Anup Patel <anup@brainfault.org> Link: https://lore.kernel.org/r/20260918081313.659655-4-zong.li@sifive.com Signed-off-by: Anup Patel <anup@brainfault.org>
138 lines
6.0 KiB
Markdown
138 lines
6.0 KiB
Markdown
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 *a1* |
|
|
| 0x20 | 8 | Reserved (upper half of the *a1* value on RV128) |
|
|
| 0x28 | 8 | Override value for *a2* |
|
|
| 0x30 | 8 | Reserved (upper half of the *a2* value on RV128) |
|
|
| 0x38 | 72 | 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.
|
|
|
|
There is no override value for *a0*. Every hart runs the header independently
|
|
when it enters the firmware, so a header field could only ever hold a single
|
|
value shared by all harts, which does not fit *a0*'s role as the current
|
|
hart's own hart id. See "Overriding a0, a1 and a2" below for how *a0* is
|
|
handled instead.
|
|
|
|
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* with the hart's mhartid |
|
|
| FW_HEADER_FLAGS_OVERRIDE_A1 | 1 << 1| Override *a1* from offset 0x18 |
|
|
| FW_HEADER_FLAGS_OVERRIDE_A2 | 1 << 2| Override *a2* from offset 0x28 |
|
|
|
|
For every flag that is set, OpenSBI replaces the corresponding register
|
|
before it looks at the boot arguments. Registers whose flag is clear are used
|
|
exactly as passed by the previous booting stage.
|
|
|
|
*a0* is handled differently from *a1* and *a2*: instead of being patched with
|
|
a value from the header, it is set to the value of the *mhartid* CSR read by
|
|
the hart that is currently executing the header. This runs once per hart, so
|
|
each hart gets its own hart id, which a single shared header field could not
|
|
provide.
|
|
|
|
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:
|
|
|
|
* set FW_HEADER_FLAGS_OVERRIDE_A0 if it cannot set up *a0* with the hart id
|
|
of the hart that will enter OpenSBI on every hart itself; OpenSBI then
|
|
derives it from *mhartid* directly and no header field needs patching,
|
|
* patch *a1* with the device tree blob address, which must be 8-byte
|
|
aligned, and
|
|
* patch *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_a1;
|
|
uint64_t reserved1;
|
|
uint64_t override_a2;
|
|
uint64_t reserved2;
|
|
uint64_t reserved[9];
|
|
};
|
|
|
|
struct fw_header *hdr = (struct fw_header *)opensbi_load_addr;
|
|
|
|
if (hdr->magic != FW_HEADER_MAGIC_VALUE || hdr->xlen != 64)
|
|
return -EINVAL;
|
|
|
|
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;
|
|
```
|