universalisos/kernel/docs/PARITY_PUSH_LEDGER.md

11 KiB

Parity Push Ledger (ARMv7 + AArch64 + RISC-V)

Scope: independent re-implementation toward PikeOS-5.0 behavioural parity on the three in-tree architectures (ARMv7, AArch64, RISC-V). x86 is explicitly OUT of scope (no backend exists). No upstream source is copied verbatim; the p4/P4_ token is banned everywhere (renamed to uos_); new schemas follow the mycelium clean-room lane (conformance/ -> promote). The roadmap (UNIVERSALISOS_VS_PIKEOS_5.0.md) targets ~15 months for full parity; this ledger records concrete, verified deltas, not a claim of completion.

Verification rule: every delta must (a) build clean on every arch it touches, (b) pass the p4-token scrub, and (c) where the boot path is healthy, run on QEMU. aarch64 boots require virtualization=on; riscv BSP smoke uses -bios none; armv7 is the reference target (currently blocked from reaching the IPC demo by a concurrent-session guest-isolation regression).

P-1 — Cross-architecture monotonic time API (DONE, build-verified)

Goal: one time base for ARINC validity, HM time-bounds, RMS/DMS budgeting and the kdev CLOCK provider, identical semantics on all three arches.

Files:

  • include/universalisos/uos_arch_time.h (new) — uos_arch_time_now(), uos_arch_time_freq(), uos_arch_us_to_ticks().
  • src/core/uos_time.cpp — ARMv7 backend (CNTPCT physical, CNTFRQ).
  • src/arch/aarch64/timer.cpp — AArch64 backend (CNTPCT_EL0, CNTFRQ_EL0).
  • src/arch/riscv/timer.cpp — RISC-V backend (CLINT mtime @ 1 MHz).
  • src/core/ipc/uos_ipc_core.cpp — ARINC validity now uses the unified API (switches the sampling stamp from virtual CNTVCT to physical CNTPCT, matching the kdev clock and the other arches).
  • src/core/uos_kdev_prov.cpp — CLOCK provider reads uos_arch_time_now().

Verified (real tool output, 2026-07-11):

  • ARMv7: builds clean (isolated /tmp copy; POSIX layer + wasm3 include neutralised in-copy only). ELF exports uos_arch_time_now/_freq.
  • AArch64: builds clean in-tree; ELF exports both symbols.
  • RISC-V: builds clean in-tree; ELF exports both symbols.
  • p4-token scrub on all six files: 0 hits (one pre-existing "P4 ConnectionTable" comment in uos_ipc_core.cpp cleaned to "UOS").

NOT verified (host-blocked, not a code defect):

  • On-QEMU boot of RISC-V (-bios none) and AArch64 (virtualization=on) for THIS change. Host was memory-exhausted (swap 8186/8191 MB); new QEMU launches died at "Failed to initialize io_uring: Cannot allocate memory". ARMv7 counter liveness was already proven in PROV-1 (clock read ... t1=1446109 t2=1474973 (live, increasing), [kdev-prov] OK); AArch64/RISC-V use the same architectural counters that already drive their own scheduler ticks.

Resume (once host has RAM): cd kernel qemu-system-riscv64 -machine microchip-icicle-kit -smp 2 -m 2G -nographic -bios none
-kernel build/riscv/polarfire/universalisos.elf # expect banner qemu-system-aarch64 -M virt,gic-version=3,virtualization=on -cpu cortex-a53 -m 512M
-smp 4 -nographic -kernel build/aarch64/qemu-aarch64-virt/universalisos.elf

P-2 — ARINC-653 sampling-port IPC on RISC-V + AArch64 (DONE; RISC-V boot-verified, AArch64 build-verified)

Goal: give the two self-contained ports the same sampling-port semantics the ARMv7 core has (slot model, last-value-wins write, RefreshPeriod freshness on read via the P-1 unified time API), exposed through each port's native paravirt ABI.

ABI (append-only, stable numbering):

  • RISC-V ecall: UOS_HCALL_SAMPLING_CREATE=15 / WRITE=16 / READ=17 (src/arch/riscv/inc/hypercall.h, handlers in hypercall.cpp).
  • AArch64 hvc #0x5500: UOS_HV_SAMPLING_CREATE=0x12 / WRITE=0x13 / READ=0x14 (src/arch/aarch64/inc/uos_hv_abi.h, handlers in el2_guest.cpp; guest inlines uos_hv_sampling_create_src/_dst/_write/_read; INFO caps advertise UOS_HV_CAP_SAMPLING bit 6).

Semantics (identical on both): static pool (8 ports x 256 B, no dynamic alloc); CREATE(max_msg, refresh_us, dir, source_id) — a DESTINATION is bound to its SOURCE at create (the channel, mirroring the armv7 ConnectionTable); WRITE is SOURCE-only, copies + stamps write_cntpct = uos_arch_time_now(); READ is DESTINATION-only, resolves the connected source slot, computes fresh = msg_len!=0 && (now - write_cntpct) <= refresh_ticks, copies only when fresh, and always reports {len, valid} (stale -> len=0, valid=0, no copy).

Files:

  • src/arch/riscv/inc/hypercall.h, src/arch/riscv/hypercall.cpp (port table + 3 handlers, dispatch registered).
  • src/arch/aarch64/inc/uos_hv_abi.h, src/arch/aarch64/el2_guest.cpp (same).
  • src/arch/riscv/kernel.cpp — built-in demo guests rebuilt: P0 creates SOURCE + writes payload; P1 creates DESTINATION(source 0) + reads + prints "[sampling] VALID"/"INVALID". Guest asm sources kept in guests/riscv-sampling/guest_p{0,1}.s (assemble rv64ima, no RVC; words are patched at boot by patch_addr_at). IMPORTANT FIX: per-partition RAM layout moved all data/scratch to >= +0x800 (was +0x100); the old layout overlapped the greeting string onto guest code once the code grew past 64 words, which showed up as a bogus "illegal instruction" whose stval was literally the ASCII of the greeting ('n',' ' = 0x206E).
  • src/arch/aarch64/guest_payload/guest_main.c — guest self-test after the APEX block: create SOURCE, write "UOS-AARCH64-SAMP", create DESTINATION(src), read, check len==16/valid==1/payload -> "[sampling] OK"/"FAILED".

Verified (real tool output, 2026-07-11):

  • RISC-V: builds clean; QEMU (-bios none, -smp 2, -m 2G) prints "Partition 0 says hello!" "[apex] OK" "[sampling] wrote" "Partition 1 says hello!" "[apex] OK" "[sampling] VALID" — cross-partition round-trip through the HV, no traps (isolated /tmp copy; the concurrent session's untracked src/core/adt tree has an __assert_fail/NDEBUG gap that breaks the in-tree link for every arch — weak stub added in-copy only).
  • AArch64: builds clean in-tree; QEMU (virt,gic-version=3,virtualization=on, -smp 2, -m 512M) guest prints "... [apex] OK" "[sampling] OK" then the vTimer/vIRQ ack — full create/write/read round-trip through EL2.
  • ARMv7: still builds clean with the P-2 tree state (isolated /tmp copy).
  • p4-token scrub on all 8 changed/new files: 0 hits.

Host/workaround notes (2026-07-11):

  • Host was memory-exhausted (swap 8191/8191 MB, held by user IDE processes); QEMU died at "Failed to initialize io_uring: Cannot allocate memory". Workaround that got boots through: add -object iothread,id=io0 and RETRY (io_uring init is marginal; it succeeded within a few attempts).
  • GOTCHA (self-inflicted, not a code bug): -m 256M makes the guest-RAM window at 0x50000000 fall past the end of physical RAM, so the HV's payload copy faults at EL2 (ESR=0x96000050, EC=0x25, external abort on write). Use -m 512M for the AArch64 guest demo, exactly as AGENTS.md says.

P-3 — Health-monitor action injection on RISC-V (DONE, boot-verified)

Goal: close the HM "action" gap on the RISC-V port — riscv_hm_partition_action() was a TODO stub, so PARTITION_STOP/RESTART events were logged but never acted on (the faulting vCPU was re-entered and re-faulted forever).

What was added (RISC-V, self-contained port):

  • src/arch/riscv/hm.cpp — real action execution:
    • PARTITION_STOP: partition state -> RISCV_PART_STOPPED, every task of the faulting partition blocked + removed from the ready queue, then riscv_sched_reschedule() hands the CPU to the next eligible task (sret, non-returning — same contract as the timer-tick path). The faulting vCPU is never re-entered. This is the PAC-IDLE containment parity of the ARMv7 core (uos_hm_partition_idled). The log line also prints the surviving partitions' task states as evidence ("survivors: P0.state=1" = READY).
    • PARTITION_RESTART: riscv_partition_reset() + re-ready the partition's task(s) from their original entry (cold-start parity).
    • RESET/SHUTDOWN/SYSTEM_STOP: unchanged (riscv_hm_panic).
  • src/arch/riscv/task_sched.cpp — riscv_sched_pick_next() now skips tasks whose partition is STOPPED/ERROR (defense in depth; idle task partition 0xFFFFFFFF is exempt).
  • src/arch/riscv/exceptions.cpp — all four riscv_hm_event() fault sites (illegal instruction + fetch/load/store page faults) now charge the REAL current partition (hm_current_part_id() via riscv_current_task) instead of the hardcoded part=0. This was a genuine pre-existing mis-attribution bug: P1's fault was being charged to P0.
  • Demo: guests/riscv-sampling/guest_p1.s (and the generated array in src/arch/riscv/kernel.cpp) executes a deliberate .word 0xffffffff illegal instruction right after the "[sampling] VALID" print, so every boot exercises the HM STOP path end to end.

Verified (real tool output, 2026-07-11), QEMU -bios none -smp 2 -m 2G: Partition 0 says hello! / [apex] OK / [sampling] wrote Partition 1 says hello! / [apex] OK / [sampling] VALID [HM] event=5 level=6 part=1 task=0 msg=illegal instruction [HM] action=STOP part=1 -> partition idled (1 task(s) blocked); survivors: P0.state=1 [IDLE] running Evidence: /tmp/rv-hm-p3f.log. Illegal instruction fired exactly once (no re-fault loop — the vCPU was not re-entered), P1 never ran again, P0 stayed READY, the scheduler kept running (idle). aarch64 + riscv builds clean; p4-token scrub: 0 hits on all 5 changed files.

Known limitation (pre-existing, not P-3): under -bios none the demo runs one major-frame cycle (NEXT_TIMEPART is the only window-advance mechanism once both partitions yielded; the preemptive tick needs the SBI timer from the firmware boot path). So P0 does not print a second "wrote" within a boot — same as the pre-P-3 demo. The HSS/firmware path (SBI timer live) is the route to repeated major frames; see docs/RISCV_HSS_FIRMWARE_POSTPONED.md.

Backlog (next concrete items, each bounded + verifiable)

  1. P-4 Fix cfg_boot_armv7.cpp isolation-proof/wasm-memcpy ordering (coordination with the session that owns that file — do NOT edit unilaterally).
  2. P-5 Sampling-port staleness demo on RISC-V/AArch64 (read after the refresh window -> INVALID), needs a guest-side delay source (RISC-V: GETTIME hcall spin; AArch64: CNTVCT_EL0 spin).
  3. P-6 HM action injection on AArch64 (the EL2 port has no HM layer at all — port the RISC-V P-3 shape: event log + PARTITION_STOP idles the guest vCPU, exercised by a deliberate guest fault after "[sampling] OK").

Rules for resuming agents:

  • Never edit files owned by a concurrent session (cfg_boot_armv7.cpp, mm.cpp, core/abi/uos_posix_abi.cpp, guests/wasm3-app). Verify ARMv7 in a throwaway /tmp copy (POSIX stub + guests symlink) instead of the live tree when the link is broken by the POSIX layer.
  • Keep the p4/P4_ token out of every new file; scrub before declaring done.