Release notes
HFI BIOS 1.4
A fourth board, and a processor nobody was using.
The StarFive VisionFive 2 boots HFI BIOS on real silicon, lights an HDMI panel
from the SoC's own display controller, and hands a bootloader — and then an operating
system — a text console they can repaint for themselves. What repaints it is the JH7110's
S7 monitor core: a hart with no supervisor mode, marked
status = "disabled" in every device tree in its family, which no operating
system will ever schedule, and which firmware used to abandon inside an SPL image it had
already dissolved.
That the display needed a character generator was known from the K3, whose VideoBIOS module borrows an application hart for the job. What was new here is that the standard way to get a processor could not be used at all: SBI's hart-state extension starts a hart at the caller's privilege, so a core without supervisor mode cannot be started through it — the request is not refused, it is unaskable. The answer was to stop asking. The SPL hands that hart a permanent home before firmware disappears, and the VideoBIOS module adopts it afterwards.
The component set
| Component | What it is | 1.3 | 1.4 |
|---|---|---|---|
| HFI BIOS | the firmware layer over U-Boot: POST, Set-Up, menus, console, the module loader | 1.3 | 1.4 |
vf2-videobios |
StarFive JH7110 — DC8200 + Innosilicon HDMI; the generator on the S7 | — | 0.2 |
k3-videobios |
SpaceMiT K3 — the SoC display over DisplayPort; the generator on a borrowed hart | 0.6 | 0.7 |
gk208-videobios |
NVIDIA GK208 over PCIe, for the HiFive Unmatched; the generator on the card | 0.17 | 0.18 |
virtio-gpu-videobios |
QEMU virt + virtio-gpu, the hardware-free platform |
0.2 | 0.2 |
libhfivbios |
the runtime every module shares: relocation, font, text mode, the character generator | — | 0.3 |
mr-bml |
QSOE's bootloader, the reference consumer of the handoff block | 0.9 | 0.9 |
Each VideoBIOS module is a separate repository and a separate blob; the
firmware image embeds the one for its board. libhfivbios did not exist at 1.3 — the
shared half of every module was factored out of them during this cycle.
On this page
Highlights
What makes 1.4 a different firmware to run than 1.3.
A fourth board. make TARGET=visionfive2, selectable in Set-Up
beside the K3, the Unmatched and QEMU, with its own U-Boot patch series and file manifest.
The display is the DC8200 through an Innosilicon HDMI 2.0 transmitter, driven by the
vf2-videobios module: EDID read over the transmitter's own DDC master, the
sink's preferred timing programmed, and no firmware blob anywhere in the path. Verified on
a v1.3B at the sink's native 1280×1024 and at 1920×1080.
A character generator on a core no operating system can use. The JH7110's S7 has no MMU, no supervisor mode and no data cache; it is the core the boot ROM starts, and until now the SPL ran on it, lost the boot lottery, and left it spinning inside an image about to be handed back to the cache. Now the SPL parks it on a mailbox in the S7's own 8 KiB of tightly coupled RAM — memory that belongs to no operating system, appears in no memory node, and needs no reservation from anybody. The VideoBIOS module adopts that hart in machine mode, with no SBI beneath it, and leaves it waiting on the handoff block's doorbell. A consumer writes cells, fences, stores one word, and a processor that belongs to nobody else turns them into pixels.
It survives into the operating system. The point of publishing a handoff
block was always that an OS which understands it gets a text console with no display driver
at all. 1.4 is the first release where that is demonstrated rather than described: QSOE's
devc-hficon rings the doorbell after ExitBootServices, the S7 answers, and both
QSOE variants have a monitor-and-keyboard console on the VisionFive 2 without a line of
display code in the OS. The bootloader asks the firmware to keep the controller alive and
withholds the processor from the device tree the OS receives — in that order, and never the
second without the first.
Two facts measured, not assumed. The S7 executes from DRAM at whatever address
the firmware relocated the module to; and the JH7110's core complex is coherent from the S7
to a U74 including a line the U74 left dirty in its L1 — four tokens out of four,
read with no cache maintenance at all. So a consumer owes nothing before ringing the doorbell
but a fence. The new spare command in the System Console is the instrument that
answered both questions, and it stays in the firmware for the next board.
The mechanism is generic. CONFIG_SPL_RISCV_SPARE_HART describes a
shape the whole SiFive-derived family shares — the FU740's S7, the FU540's E51 — and it
guards itself: the SPL will not stand down unless a hart the device tree actually endorses is
available to take the boot. The VideoBIOS runtime gained a second hart provider beside SBI
HSM, chosen at build time, so a module says CHARGEN_HART := parked and nothing
else in it has to know which world it lives in.
Damage is the host's to report. A firmware console that named "something changed" and got "repaint everything" back was free where the repaint is one store — a GPFIFO kick, a doorbell to a hart — and ruinous where the module rasterizes on the calling CPU: a full screen of glyphs plus an 8 MB writeback per character, and a menu that took 49 seconds to appear. The host now names the cells it touched, and a keystroke costs a character.
The whole machine, again. Ethernet brought to a negotiated link by firmware and handed over still running, with the link state written into the device tree the OS receives; the SoC temperature sensor on the PC Health Status page; the Flash Utility taught this board's NOR — two images with the environment between them and no partition table anywhere on the device; and a way to run a build over the serial cable alone that writes nothing to the machine, which is both the bring-up path and the recovery path.
Two point releases folded in. 1.3.1 found the M.2 slot's NVMe drive, invisible to firmware and OS alike: its PCIe port was being configured as an endpoint rather than a root complex — the vendor's own header comments the mode bit the wrong way round — and the fault hid behind three working ports only because port A alone implements that bit. 1.3.2 fixed the machine resetting instead of booting whenever the Ethernet controller was enabled — U-Boot kept a watchdog's servicing schedule inside memory that ExitBootServices freed, and the next pass through that schedule called through whatever had been allocated over it. That one is generic U-Boot, and was submitted upstream.
The full change list
Everything that changed since 1.3, by area. Open the parts you want —
Open every section · the same ground is covered in
CHANGELOG.md in the
source repository.
The VisionFive 2 port
A JH7110 board, brought from nothing to a whole machine.
- Target and build.
TARGET=visionfive2with its own U-Boot patch series and file manifest; the build refuses a VideoBIOS module belonging to a different target rather than embedding it and failing on the glass. - Display. DC8200 modeset, the Innosilicon HDMI PHY (pre- and post-PLL, LDO, serializer, TMDS drivers), EDID over the transmitter's DDC master, the sink's preferred timing. The VOUT power island, clocks, resets and the DDC pad mux, in the order the hardware requires.
- The console. The text grid and Controller Handoff Block, with damage reporting so a keystroke costs a character and not a screen.
- Storage, USB, sensors. NVMe in the M.2 slot, a second USB pass so the keyboard is found, the SoC temperature sensor with the clocks it needs registered so it can be read at all.
- Ethernet. The JH7110 PHY is woken before the MAC reset — resetting the MAC while the PHY still slept left the link down after a cold start and reliably up after a warm one, which is the tell of an ordering fault. The link is negotiated by firmware and handed over live.
- A device tree of our own. Upstream U-Boot's control FDT for this board has no display subsystem in it; the image now carries a full board DTB and installs it at hand-off, so an OS is not handed a tree that cannot drive the panel the firmware just lit.
- The Flash Utility. Two images in SPI NOR — SPL at 0 and the FIT at 0x100000 — with the environment between them and no GPT anywhere. The packaged SPL is the one the boot ROM will actually take: the header-carrying image, not the raw binary, which bricks a NOR boot with "Main section boot fail".
- Booting over the serial cable alone.
scripts/vf2-uart-boot.shsends the SPL by XMODEM and the FIT by YMODEM, waits for the ROM's poll to go clean before speaking (the ROM's second greeting lands exactly where an ACK belongs, and the result is indistinguishable from a bad cable), and writes nothing to the board. SD and eMMC boot are gone from upstream for this SoC, so this is the recovery path.
The character generator on the S7
A processor the machine was not otherwise using, and how it was had.
- The SPL parks it.
CONFIG_SPL_RISCV_SPARE_HART: before entering OpenSBI — the last moment the SPL still controls the machine — a small machine-mode stub is copied into memory that outlives firmware and the spare hart is sent there, parked on a mailbox. On the JH7110 that memory is the S7's own DTIM at0x0110_0000. The spare is the hart the SPL is standing on, so it names no boot-hart preference, sends the U74s into OpenSBI, and walks into the stub itself. It refuses to stand down unless a device-tree-endorsed hart is available to take the boot. - The stub is careful.
llaand neverla: U-Boot is built with a global offset table, andlaresolves through it — a load from the very image this hart must stop depending on. It finds its area from its own program counter rather than from an argument register, so the area is still findable after a call returns. It records which harts reached the SPL at all, and each one'smisa— the only place a monitor core's missing supervisor mode is visible. - The module adopts it.
libhfivbiosgainedCHARGEN_HART:sbiasks firmware for an idle hart, in supervisor mode, and goes on calling SBI for its timer;parkedadopts one an earlier stage left waiting, in machine mode, with the memory-mapped CLINT as its clock. Chosen at build time, for the same reason the wait strategy is — a blob carries one strategy, not two. - Which hart is calling is part of the contract. The generator runs in machine mode and may read the CLINT; the firmware that dispatched it runs in supervisor mode, where OpenSBI PMP-protects the CLINT precisely so supervisor code cannot bypass the SBI timer. Giving the whole provider one clock put a machine-mode device read on the supervisor-mode hart and reset-looped a board. The split is by privilege, not by provider.
- Retiring is a
return. A hart with no supervisor mode has no HART_STOP to call. The loop returns, the park stub takes itsret, clears the mailbox entry, and the hart is back on its mailbox — which is how an OS gives it back throughretire_pa, and how the firmware retires it at ExitBootServices. - The blob is built for both cores. Everything in the module may run on the
S7, which has no FPU, so it is compiled for the intersection of the two
(
rv64imac_zicsr_zifencei_zba_zbb,lp64) rather than the U74'srv64gc. A floating-point instruction emitted for something as ordinary as a struct copy would be an illegal instruction with no handler. The build fails if one ever appears. - A hart that will not answer is recorded as stuck, not left active: those are two different facts. Not active means publish no doorbell and render inline; stuck means the module's memory must never be released. Conflating them was a frozen console rather than a slow one.
spare statusandspare probe. Whether a hart is parked, whether its beat advances, what the SPL saw on its way out — and a routine run on the hart itself that answers whether it observes a write another hart left dirty in its cache. Four rounds, four tokens, no flush: it does.
The handoff block and the operating system
What a consumer is told, and what it can rely on.
FIRMWARE_ONLY, a fourth doorbell kind. "Only the producer's CPU can turn a cell into a pixel." It is notNONE, and a consumer that reads it asNONEloses the display: mr-bml did, on a v1.3B, silencing the firmware console to write a menu into cells nobody would rasterize. The rule for a consumer is that an unrecognized kind reads asFIRMWARE_ONLY, never asNONE.present_rect()in the VideoBIOS module ABI: the host names the cells it touched and the module rasterizes exactly those. A module must not narrow the rectangle by diffing the grid — the host also writes pixels straight into the framebuffer, under cells it marks transparent, and a diff cannot see them. Re-blitting an emblem changed no cell, so nothing was flushed, and half of it stayed in the cache: a torn logo on the glass.- A retired processor is no longer advertised. When a module gave its hart back at ExitBootServices it withdrew the doorbell — from the loader's temporary copy of the block, not the one the OS reads, which went on naming a doorbell for a processor that had gone home. The firmware owns the published copy, so the firmware now withdraws it, only after the module confirms it let go. This fixes the same latent fault on the K3.
- "Reserve video memory" in Set-Up. Whether the OS is told which memory the
controller owns — cell array, palette, control block, doorbell, module image — as
/reserved-memory. It was a bare environment variable reachable only bysetenv; it is now a row under Advanced Settings, and the refusal at hand-off names it. Default Disabled: those nodes cost an OS that has never heard of us a few megabytes and buy it nothing. - Keeping the controller alive across the hand-off is requested by the
bootloader — mr-bml's
hfi_vidctrl_persist=1— and honored only when that memory was reserved. The bootloader withholds the controller's harts from the OS's device tree first and asks the firmware second, and never the second if the first fails. That ordering is why a Set-Up row for the request itself was built and then deliberately removed: firmware cannot do the first half, and a switch whose safety depends on which board it is on is worse than no switch.
Set-Up and the console
- Advanced Settings sizes itself to its own help text. A fixed width clipped a description to "…which memory the video controller ow", and the person reading that is the one person who cannot know what was cut off. The page now measures its longest help line and opens far enough for it.
- The text grid's cell size comes from the controller that was just programmed, not from a constant — which was only correct while every board used the same font.
- A console device is never held across a command. Resetting USB frees the keyboard's stdio device; code that kept a pointer to it was still holding one afterwards, and crashed the firmware at address zero.
- U.S. English throughout the source, comments and documentation.
Fixed — including 1.3.1 and 1.3.2
The two point releases, and what was found on the way to 1.4.
1.3.1 — the NVMe drive that hid behind three working ports
- The K3's M.2 slot enumerated no drive — not in the BIOS, not in
lspciunder Linux — while the other three PCIe ports worked. Port A was being configured as an endpoint: bit 31 of its command register selects the role, the vendor's header comments it0: EP, 1: RC, and on this part it is the comment that is inverted. An endpoint waits to be handed link and lane numbers rather than handing them out, so training fell back to Detect forever. Only port A implements that bit, which is why the other three never noticed. And because Linux's own driver never writes it on this SoC, an endpoint left behind by firmware was inherited by the OS — one line in a bootloader took a disk away from the operating system as well.
1.3.2 — the machine reset instead of booting, whenever Ethernet was enabled
- U-Boot keeps a watchdog's servicing schedule inside the watchdog device's own private data, and nothing takes that entry off the schedule — while ExitBootServices removes every active device and frees exactly that memory. The next pass through the schedule called through whatever had since been allocated over it. Enabling Ethernet put one more device into the teardown, and that was enough to change what landed on the freed block. The watchdog is now taken off the schedule before its memory goes. Generic U-Boot, and submitted upstream.
- Why one payload booted and another did not: nothing in the failure depended on the payload's code, only on the order and size of the allocations each boot happened to make. A kernel loaded one way survived what a kernel loaded another way did not.
- Why a crash this loud presented as a silent reset: the firmware printed a complete exception dump — and to the monitor, an instant before the reset cleared it, because a payload that drives the display hands the console back just before launching the OS. The dump was never lost; it was on the glass.
In 1.4
- The JH7110 Ethernet PHY reset out from under itself (see the VisionFive 2 section).
- A console device remembered for longer than it existed (see Set-Up).
- A retired processor still advertised as able to repaint (see the handoff block).
- The K3's keyboard poll mode named with a symbol that exists.
- The two vendored ABI headers brought to the version the block actually reports.
Known gaps
Stated rather than implied away.
- The S7 spins. Its generator waits with a paced poll, not a timer: the first
bring-up depended on no CLINT programming, no interrupt enable and no
wfi— three things that fail in the same silent way. It burns a core nothing else in the machine can use, so this is a power question, not a feature one. The timer comes next. - Every ring repaints the whole screen. That is the K3's contract too, and on
the S7 — no data cache, every pixel a store to the shared L2, then an 8 MB writeback —
it costs tens of milliseconds. It is asynchronous, the firmware stores and walks away, rings
coalesce, and scrolling keeps up with a repeated
help; but a damage rectangle in the handoff block would let the hart repaint only what changed, and it is not there yet. - Keeping the controller alive needs a bootloader that knows how. mr-bml does. An operating system started through a boot protocol cannot ask for itself, and the firmware deliberately does not decide which processors an OS may online. A different bootloader has to do what mr-bml does, in the same order.
- The VisionFive 2 boots from NOR or over the serial cable, and nothing else. Upstream U-Boot dropped the SD and eMMC first-stage path for this SoC; a modern SPL has no MMC in it. The Flash Utility and the UART script are the two ways in.
- The K3 still runs the vendor's first stage. Its OpenSBI delegates hart state to
coprocessors that the first stage has to load and start, and ours does not. The HFI deliverable
on that board is the one image in the
ubootpartition, and the vendor's stages below it are left exactly as shipped. - One diagnostic on the K3 cries wolf. "PHY reset line 15 will not go high" is printed at probe on every boot and contradicted by the next lines, which negotiate the link. It was true once, during bring-up; the readback now samples a register that does not reflect the pad's level under its final mux. It will be corrected in a point release.
Getting it
One firmware package per board, written with the BIOS Flash Utility from Set-Up — or, on a board already running Linux, by the board's own means. Each is a FIT carrying every stage the board's flash takes, and the Flash Utility refuses one built for a different board.
HFI-StarFive-VisionFive2-SysBIOS-1.4.hfifw— StarFive VisionFive 2 (v1.3B): SPL and FIT for the SPI NOR.HFI-SpaceMiT-K3-PicoITX-SysBIOS-1.4.hfifw— SpacemiT K3 Pico-ITX: the FIT for theubootNOR partition. The vendor's first stage and OpenSBI stay as shipped.HFI-SiFive-Unmatched-SysBIOS-1.4.hfifw— SiFive HiFive Unmatched: SPL and FIT, for a NOR laid out with the two loader partitions.
Extracting the stages
A package is a FIT — a flattened image tree — and dumpimage from
u-boot-tools lists and extracts it. dumpimage -l on any of the
three names its images; the extraction lines below are what that listing
gives. Which partition or offset each stage belongs at is described above, per
board, and is the reader's business from there.
# SpacemiT K3 Pico-ITX: one image, U-Boot proper with the board's device tree dumpimage -T flat_dt -p 0 -o u-boot.itb HFI-SpaceMiT-K3-PicoITX-SysBIOS-1.4.hfifw # SiFive HiFive Unmatched: the SPL, then the OpenSBI + U-Boot FIT dumpimage -T flat_dt -p 0 -o u-boot-spl.bin HFI-SiFive-Unmatched-SysBIOS-1.4.hfifw dumpimage -T flat_dt -p 1 -o u-boot.itb HFI-SiFive-Unmatched-SysBIOS-1.4.hfifw # StarFive VisionFive 2: the same two -- but this SPL carries the boot ROM's header # (the "sfspl" form, u-boot-spl.bin.normal.out); the raw SPL is not what the ROM loads dumpimage -T flat_dt -p 0 -o u-boot-spl.bin.normal.out HFI-StarFive-VisionFive2-SysBIOS-1.4.hfifw dumpimage -T flat_dt -p 1 -o u-boot.itb HFI-StarFive-VisionFive2-SysBIOS-1.4.hfifw
SHA-256 of each package and of every stage inside it, with the stage sizes
the listing reports. (Two of the packages extract a file called
u-boot.itb; they are not the same file.)
b5e39666e0127bedb59cab921366dc1aa8756b5aa1bb702220a9f4cde265beb7 HFI-SpaceMiT-K3-PicoITX-SysBIOS-1.4.hfifw f438d962a3685c606cff57c89097b00c1b316d188e4e08e5591247f63714e6c8 u-boot.itb # K3, 886649 bytes ca8836ec2aadabe719b45f27332ae08bb11e199dbf0e19b3250f267ed75e9dd9 HFI-SiFive-Unmatched-SysBIOS-1.4.hfifw b9c33d57565b1cf692c8d98c70b68ac9449cf18d0c2c99979a8ec4f2a8e900fe u-boot-spl.bin # Unmatched, 94195 bytes 9f3fe2203f0287e28ef3d39494bea32a74f5a5f96ef9384386f1d4950b0d5972 u-boot.itb # Unmatched, 943685 bytes abc3347868f4840423e295d96803094b8aedb8f240e56f8aa812492aac6005ce HFI-StarFive-VisionFive2-SysBIOS-1.4.hfifw 40478fa89e2392b7ed79803619485be47bd24fcdb9e5ddeb0e8de4492762dae8 u-boot-spl.bin.normal.out # VisionFive 2, 149385 bytes e7671850fcdd3f5c43e5a5440fc96661cfa596ff855941085b8e3e17c0764636 u-boot.itb # VisionFive 2, 1205849 bytes
The packages live at github.com/qsoe-dev/hfi-bios, one directory per release. To build from source:
git clone https://gitlab.com/bios-riscv/hfi-bios cd hfi-bios make TARGET=visionfive2 # or k3, unmatched, qemu -- fetches U-Boot and OpenSBI itself make fwpkg TARGET=visionfive2 # the .hfifw for the Flash Utility
The VideoBIOS modules are their own repositories under
gitlab.com/bios-riscv/videobios — one per
display controller, plus libhfivbios, the runtime they share. The build looks for
the module beside the firmware tree and embeds it; the two ABI headers a consumer needs,
hfi_vidctrl_handoff.h and hfi_vbm.h, are MIT so that any bootloader
or operating system can carry them verbatim.
Next: the S7's generator moves from a paced poll to a timer, and the handoff block learns to carry a damage rectangle so a hart repaints what changed rather than the screen. Both are performance work on a path that is already correct.
Back to HFI