universalisos/kernel/docs/PARITY_PUSH_LEDGER.md

184 lines
11 KiB
Markdown

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