9.7 KiB
UniversalisOS RISC-V / Icicle Kit — POSTPONED: HSS firmware-boot path
Status: POSTPONED (not deleted). The kernel-side support is implemented, built, and verified as far as a host without Microchip tooling allows. The live HSS boot — and therefore real SMP + preemptive SBI timer ticks on the Icicle Kit — is the postponed part. This doc lets any agent resume cold.
Read this first, then universalisos-riscv-bringup skill (the canonical RISC-V
bring-up notes). Both are authoritative; where they disagree, prefer the skill.
Why this exists / the ceiling it lifts
-bios none on QEMU microchip-icicle-kit:
- Only the BSP (hart 1) runs. Harts 0/2/3/4 are held in reset (proven via
qemu -d int: every trap line ishart:0). CLINT-MSIP from the BSP does nothing — secondaries are not polling, they're in reset. - CLINT
mtimecmpis M-mode-only. S-mode cannot arm the timer without SBIset_timer. So no preemptive ticks under-bios none. - OpenSBI generic firmware (
/usr/share/qemu/opensbi-riscv64-generic-fw_dynamic.bin) does NOT chain onmicrochip-icicle-kit: QEMU 10.2.2 reports "ROM regions are overlapping … mrom.reset" at0x20220000. The firmware path that DOES work on this machine is a Microchip HSS payload.
The HSS payload path (Microchip boot flow) releases all four U54 harts into
S-mode at 0x80200000 with SBI live — giving live SMP (HSM hart_start) and a
reachable supervisor timer (set_timer). This is exactly the
tear-de-silicio polarstar-fpga flow, retargeted at UniversalisOS.
Reference: tear-de-silicio "perfect MVP" (the model to replicate)
Repo: /home/fabiorafaelcoutada/portugalfuturista/tear-de-silicio/
Branch mined: origin/0.1.0-alpha.1 (a "perfect MVP": VectorBlox + buildroot
distro running a baremetal C++ app on the Icicle Kit).
Two files define the boot contract we copy:
-
sdk/linux/buildroot-external-polarstar-fpga/board/microchip/polarstar_fpga_qemu/run-qemu.txtboots the kit under QEMU as (paraphrased):qemu-system-riscv64 -M microchip-icicle-kit -smp 5 -m 2G -bios output/images/payload.bin ...— i.e. the HSS payload is the machine BIOS; the app image is a payload at0x80200000. -
sdk/linux/buildroot-external-polarstar-fpga/board/microchip/polarstar_fpga_qemu/config.yamlis the HSS Payload Generator input: all four U54 harts @0x80200000, single payloadsrc.binat0x80200000, owneru54_1, secondariesu54_2/3/4,priv-mode: prv_s. Their payload is U-Boot; ours isuniversalisos.bin.
Our copy is kernel/config/icicle_hss_payload.yaml (same shape, payload =
universalisos.bin).
What's already implemented (kernel side) — keep this
All gated so -bios none is byte-for-byte unaffected on its path.
kernel/src/arch/riscv/boot.S- New entry
_start_firmwarein section.text.boot.firmware: sets__uos_has_sbi = 1, then falls into the common S-mode path at the new_start_smode_checklabel. The M-mode_starttrampoline still clears__uos_has_sbi = 0.
- New entry
kernel/src/arch/riscv/linker_firmware.ld(NEW)ENTRY(_start_firmware);.text.boot.firmwareplaced first so the byte at0x80200000is_start_firmware. Defaultlinker.ldunchanged (_startat base, plus*(.text.boot.firmware)kept so the symbol still links).
kernel/src/arch/riscv/timer.cppriscv_timer_{init,set,clear}route throughsbi_set_timer()when__uos_has_sbi, else direct CLINT.riscv_timer_get_timealways reads CLINTmtime(S-mode-readable on both paths).
kernel/src/arch/riscv/smp.cpp(already existed, now meaningful)riscv_cpu_wake_secondary/_ipi_sendgo through SBI HSM/IPI when__uos_has_sbi. Under the firmware image that path is live.
kernel/Makefilemake ARCH=riscv PLATFORM=polarfire UOS_BOOT=firmwareselectslinker_firmware.ld.- New targets:
bin(objcopy flat binary →universalisos.bin),run-qemu(-bios nonesmoke),run-qemu-firmware FIRMWARE=…, andhss-payload(builds the firmware bin and wrapshss-payload-generator, or prints the exact command if the tool is absent).
kernel/config/icicle_hss_payload.yaml(NEW) — tear-de-silicio-derived HSS payload config retargeted atuniversalisos.bin.
Verification already done (ad-hoc, not a suite)
A locale-agnostic script (/tmp/hermes-verify-icicle-fw-*.sh, since deleted)
gave 15/15:
- Default image builds;
-bios none -smp 2boots to "Starting scheduler" →Partition 0/1 says hello!(repeats), no panic/TRAP/STORM. - Firmware image builds + objcopy
.bin(20,720 B);_start_firmware @ 0x80200000,_start @ 0x80200020, ELF entry0x80200000. __uos_has_sbisymbol present in both ELFs.- ARMv7 (
make ARCH=armv7 PLATFORM=qemu-arm-virt) still builds (1,085,344 B).
To re-run the same checks, regenerate the script from the skill or re-create the one in this doc's appendix, or simply: make -C kernel ARCH=riscv PLATFORM=polarfire UOS_BOOT=firmware bin riscv64-linux-gnu-readelf -h kernel/build/riscv/polarfire/universalisos.elf | grep -i entry # want 0x80200000 riscv64-linux-gnu-nm kernel/build/riscv/polarfire/universalisos.elf | grep -E '_start_firmware| _start$'
The postponed part — and exactly how to resume
Blocker: a live HSS boot needs Microchip's hss-payload-generator, which is NOT
installed on this host and is NOT in Fedora/Debian repos (it ships with
Microchip SoftConsole / the HSS source tree, or as pip install hss-payload-generator
from Microchip's channel — confirm current source before relying on either name).
Resume steps, in order:
-
Get the generator on a host with network + Python: pip install hss-payload-generator # if/when available, else
build from Microchip's HSS tree (PolarFire SoC HSS) — see their docs.
-
Build the firmware bin (S-mode entry at 0x80200000): make -C kernel ARCH=riscv PLATFORM=polarfire UOS_BOOT=firmware bin
-
Wrap it as an HSS payload: make -C kernel hss-payload
which runs:
hss-payload-generator -c kernel/config/icicle_hss_payload.yaml \
kernel/build/riscv/polarfire/universalisos.bin \
kernel/build/riscv/polarfire/payload.bin
-
Boot it as the machine BIOS in QEMU (all 5 harts released into S-mode): make -C kernel run-qemu-firmware FIRMWARE=kernel/build/riscv/polarfire/payload.bin
equivalent:
qemu-system-riscv64 -machine microchip-icicle-kit -smp 5 -m 2G -nographic \
-bios kernel/build/riscv/polarfire/payload.bin \
-device loader,file=kernel/build/riscv/polarfire/universalisos.elf,addr=0x80200000
-
What "working" looks like (the success criteria
-bios nonecan never meet):- Banner prints from hart 1, then "[HSM] Hart N online" / equivalent for
harts 2/3/4 (i.e. more than one hart reaches
kernel_main). - Timer-driven preemption: P0/P1 interleave WITHOUT cooperative
NEXT_TIMEPART — i.e. the major-frame tick fires via the SBI supervisor
timer and
riscv_sched_rescheduleis called fromuos_trap.S's timer branch, not only from the guest hcall. riscv_cpu_wake_secondaryreturns with the secondary harts' online flags set (gate on__uos_has_sbi, which is 1 on this path).
- Banner prints from hart 1, then "[HSM] Hart N online" / equivalent for
harts 2/3/4 (i.e. more than one hart reaches
-
If step 4 instead shows no output:
- Confirm the payload was generated from the firmware bin
(
UOS_BOOT=firmware), whose ELF entry must be0x80200000and_start_firmwareat the image base (see verification above). A default (M-mode) bin wrapped as an HSS payload will silently do nothing — HSS drops the harts into S-mode but the image's first instruction is the M-mode trampoline that immediately writes M-mode-only CSRs and traps. - Confirm HSS boot mode on the (emulated) board allows an unsigned payload;
for QEMU
microchip-icicle-kitthe-bios payload.binpath bypasses the real eNVM secure-boot checks.
- Confirm the payload was generated from the firmware bin
(
Known constraints (icicle-kit -bios none)
-smp 1is rejected by QEMU itself ("min CPUs supported by machine 'microchip-icicle-kit' is 2"). The hypervisor BSP is hardwired to hart 1 (U54_1); hart 0 is the E51 monitor and is not used. So the smallest bootable config is-smp 2.-smp 2..5all boot to P0+P1 under-bios none(BSP only). The secondary U54 harts are held in reset by QEMU and are NOT released by CLINT MSIP from the BSP — andriscv_cpu_wake_secondary()is intentionally a no-op under-bios none(__uos_has_sbi==0) because there is no safe per-hart PMP/delegation path for them (see kernel.cpp gate + smp.cpp).- PLIC:
riscv_plic_init()must only touch the BSP's own S-mode context. Walking allPLIC_NUM_CONTEXTS(10) wedges the BSP — QEMU's sifive_plic model logs "sifive_plic_write: Invalid register write 0x2090xx" and stalls the access for contexts whose hart is not instantiated / whose aperture the model rejects. This was the-smp 5hang (fixed 2026-07-10). Other harts initialise their own context inriscv_plic_cpu_init()as they come online under firmware. - If a
virtRISC-V platform target is added later, OpenSBI chains cleanly there (ROM layout differs) and would let the SBI path be exercised in QEMU without Microchip tooling — useful as a CI stand-in, but not a substitute for the Icicle Kit HSS flow.
Files in this change set (do not revert when postponing)
- kernel/src/arch/riscv/boot.S
- kernel/src/arch/riscv/linker.ld
- kernel/src/arch/riscv/linker_firmware.ld (new)
- kernel/src/arch/riscv/timer.cpp
- kernel/Makefile
- kernel/config/icicle_hss_payload.yaml (new)
- AGENTS.md (RISC-V boot commands + reality check)
- ~/.hermes/skills/universalisos/universalisos-riscv-bringup/SKILL.md (firmware section)
- THIS FILE: kernel/docs/RISCV_HSS_FIRMWARE_POSTPONED.md
Last verified: 2026-07-10, QEMU 10.2.2, riscv64-linux-gnu gcc, host locale pt-PT.