universalisos/kernel/docs/RISCV_HSS_FIRMWARE_POSTPONED.md

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 is hart:0). CLINT-MSIP from the BSP does nothing — secondaries are not polling, they're in reset.
  • CLINT mtimecmp is M-mode-only. S-mode cannot arm the timer without SBI set_timer. So no preemptive ticks under -bios none.
  • OpenSBI generic firmware (/usr/share/qemu/opensbi-riscv64-generic-fw_dynamic.bin) does NOT chain on microchip-icicle-kit: QEMU 10.2.2 reports "ROM regions are overlapping … mrom.reset" at 0x20220000. 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:

  1. sdk/linux/buildroot-external-polarstar-fpga/board/microchip/polarstar_fpga_qemu/run-qemu.txt boots 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 at 0x80200000.

  2. sdk/linux/buildroot-external-polarstar-fpga/board/microchip/polarstar_fpga_qemu/config.yaml is the HSS Payload Generator input: all four U54 harts @ 0x80200000, single payload src.bin at 0x80200000, owner u54_1, secondaries u54_2/3/4, priv-mode: prv_s. Their payload is U-Boot; ours is universalisos.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_firmware in section .text.boot.firmware: sets __uos_has_sbi = 1, then falls into the common S-mode path at the new _start_smode_check label. The M-mode _start trampoline still clears __uos_has_sbi = 0.
  • kernel/src/arch/riscv/linker_firmware.ld (NEW)
    • ENTRY(_start_firmware); .text.boot.firmware placed first so the byte at 0x80200000 is _start_firmware. Default linker.ld unchanged (_start at base, plus *(.text.boot.firmware) kept so the symbol still links).
  • kernel/src/arch/riscv/timer.cpp
    • riscv_timer_{init,set,clear} route through sbi_set_timer() when __uos_has_sbi, else direct CLINT. riscv_timer_get_time always reads CLINT mtime (S-mode-readable on both paths).
  • kernel/src/arch/riscv/smp.cpp (already existed, now meaningful)
    • riscv_cpu_wake_secondary / _ipi_send go through SBI HSM/IPI when __uos_has_sbi. Under the firmware image that path is live.
  • kernel/Makefile
    • make ARCH=riscv PLATFORM=polarfire UOS_BOOT=firmware selects linker_firmware.ld.
    • New targets: bin (objcopy flat binary → universalisos.bin), run-qemu (-bios none smoke), run-qemu-firmware FIRMWARE=…, and hss-payload (builds the firmware bin and wraps hss-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 at universalisos.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 2 boots 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 entry 0x80200000.
  • __uos_has_sbi symbol 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:

  1. 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.

  2. Build the firmware bin (S-mode entry at 0x80200000): make -C kernel ARCH=riscv PLATFORM=polarfire UOS_BOOT=firmware bin

  3. 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

  4. 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

  5. What "working" looks like (the success criteria -bios none can 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_reschedule is called from uos_trap.S's timer branch, not only from the guest hcall.
    • riscv_cpu_wake_secondary returns with the secondary harts' online flags set (gate on __uos_has_sbi, which is 1 on this path).
  6. If step 4 instead shows no output:

    • Confirm the payload was generated from the firmware bin (UOS_BOOT=firmware), whose ELF entry must be 0x80200000 and _start_firmware at 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-kit the -bios payload.bin path bypasses the real eNVM secure-boot checks.

Known constraints (icicle-kit -bios none)

  • -smp 1 is 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..5 all 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 — and riscv_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 all PLIC_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 5 hang (fixed 2026-07-10). Other harts initialise their own context in riscv_plic_cpu_init() as they come online under firmware.
  • If a virt RISC-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.