35 Commits
Author SHA1 Message Date
Robert Allan JamesandClaude Sonnet 5 4ce28edf8f 3-arch QEMU acceptance + live diagnostic logs for the WIREBIND overflow trigger
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Per .claude/CLAUDE.md's non-negotiable acceptance criteria, ran
`make -f Makefile.starkernel ARCH=<amd64|aarch64|riscv64> clean qemu`
sequentially (one instance at a time, TCG) against the preceding commit.
All three booted cleanly to `(zuse) ok>` with identical dictionary hashes,
no regression from the new idle-tick overflow check.

Also includes the amd64 run (20260905-161517) that carried the temporary
TEST-OWNER-CLAIM diagnostic via SK_CMD, confirming blk_owner_has_claim()
live before the diagnostic word itself was reverted: PASS, no FAIL lines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018EjXFo7mPXjUMjfJeuUUz4
2026-09-05 16:17:35 -04:00
Robert Allan JamesandClaude Sonnet 5 4d4ab59189 Build the FIRSTTOUCH overflow trigger flagged in FABRIC-2.md §I.2
The overflow-triggered migration path (a WIREBIND-attached identity's own
drive running low on space) was scoped but never built -- only the
trigger-detection call site was missing, per this section's own text.

- capsule_wirebind.c now tracks the attached blkio_dev* alongside the
  already-tracked VM id, set in try_attach() and cleared in both
  EJECT/UNCLEAN paths.
- New capsule_wirebind_overflow_idle_check(), called once per idle tick
  in repl.c right alongside blk_migration_idle_check() (same cadence):
  reads the attached drive's free/total via blk_get_device_free_blocks(),
  and if free space is below a fixed 10% threshold, extends the
  identity's pool with a one-time blk_firsttouch_claim() of 8 additional
  devblocks on Artemis's system-resident device.
- New blk_owner_has_claim(owner_fp) in block_subsystem.c answers the
  debounce question blk_firsttouch_claim()'s own doc comment had left
  open: a disk scan, not a RAM flag, so the already-extended answer
  survives reboot/reattach, matching BMAPFMT's "ownership travels with
  the block" model.

Premise checked before building (does a WIREBIND-attached drive actually
give a real free/total signal, or does it stay PROVISIONAL/raw): traced
repl.c's attach sequence and confirmed blk_subsys_attach_device() runs on
the same dev pointer right after WIREBIND, and a WIREBIND-eligible drive
is always already STFR/v2-formatted, so the signal is real. Premise held,
unlike the BAM item's overstated one.

Verified with the mandatory 3-arch QEMU acceptance (identical dictionary
hashes, no regression) plus a live logic test of blk_owner_has_claim():
a temporary TEST-OWNER-CLAIM word, run once via SK_CMD and reverted,
confirmed it correctly detects the claiming owner and rejects an
unrelated one. The low-disk-space-triggers-a-claim path itself is not
verified end-to-end -- that needs a real minted WIREBIND-user thumbdrive
with deliberately tiny capacity, out of scope for this pass; noted as
such in the FABRIC-2.md §I.2 closure note rather than overclaimed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018EjXFo7mPXjUMjfJeuUUz4
2026-09-05 16:17:19 -04:00
Robert Allan JamesandClaude Sonnet 5 d8ac27abb2 3-arch QEMU acceptance + live diagnostic logs for the BAM reconciliation fix
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Per .claude/CLAUDE.md's non-negotiable acceptance criteria, ran
`make -f Makefile.starkernel ARCH=<amd64|aarch64|riscv64> clean qemu`
sequentially (one instance at a time, TCG) against the preceding commit.
All three booted cleanly to `(zuse) ok>` with the full Tripod fleet
(Hera/Hermes/Artemis) alive and identical dictionary hashes:

  Mama:    dict_hash=0x8d712e07c690d713
  Hermes:  dict_hash=0xa07a57d794c1665d
  Artemis: dict_hash=0x9cc9a7e72b590250

Also includes the amd64 run (20260905-155329) that carried the temporary
TEST-BAM-RECON diagnostic via SK_CMD, confirming the fix live before the
diagnostic word itself was reverted: TEST-BAM-RECON: PASS, no FAIL lines.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018EjXFo7mPXjUMjfJeuUUz4
2026-09-05 15:57:21 -04:00
Robert Allan JamesandClaude Sonnet 5 aa33aedca8 Fix blk_meta_t/BAM accounting reconciliation flagged in FABRIC-2.md §I.2
FIRSTTOUCH ownership (blk_meta_t's BLK_FLAG_CLAIMED/owner_fp) and the
generic block-allocation bitmap (BAM, blk_bam_entry_t) were two parallel,
unreconciled accounting systems: blk_firsttouch_claim() never touched the
BAM, and blk_allocate()/devblock_is_free() never checked BLK_FLAG_CLAIMED.
A FIRSTTOUCH claim could be silently overwritten by a later blk_allocate()
call, or could itself steal a devblock already in ordinary use via
BLOCK/UPDATE.

- devblock_is_free() (shared by blk_firsttouch_claim() and
  blk_migration_idle_check()) now also checks the BAM entries of all
  BLK_PACK_RATIO member LBNs, not just blk_meta_t.
- New devblock_claimed_by_lbn() helper wired into blk_allocate()'s
  free-scan, so it skips any LBN whose devblock is BLK_FLAG_CLAIMED.
- blk_firsttouch_claim() now marks the BAM allocated for all 3 member
  LBNs of each devblock it claims, which also fixes vol_meta.free_blocks
  never decrementing for FIRSTTOUCH claims.
- blk_meta_relocate_devblock() traced and confirmed NOT part of the bug —
  it already keeps BAM in sync via blk_subsys_relocate_block()'s own
  blk_mark_free()/blk_update() calls.

Both boundary cases (the reserved/user LBN split at a slot's start_lbn,
and BAM-array bounds) are guarded explicitly.

Verified with the mandatory 3-arch QEMU acceptance (identical dictionary
hashes, clean BYE) plus a live logic test: a temporary TEST-BAM-RECON
word, run once via SK_CMD and fully reverted, confirmed on running code
that ordinary allocation and a FIRSTTOUCH claim land on disjoint LBN
ranges in both directions. FABRIC-2.md §I.2 updated in place with the
closure note, per this project's documentation discipline.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018EjXFo7mPXjUMjfJeuUUz4
2026-09-05 15:57:03 -04:00
Claude 04cc17920e 3-arch QEMU acceptance logs for the FINDINGS.md defect-repair commits
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Per .claude/CLAUDE.md's non-negotiable acceptance criteria, ran
`make -f Makefile.starkernel ARCH=<amd64|aarch64|riscv64> clean qemu`
sequentially (one instance at a time, TCG) against the two preceding
commits. All three booted cleanly to `(zuse) ok>` with the full Tripod
fleet (Hera/Hermes/Artemis) alive, Stadium conservation holding
(resident_sum=43691 reservoir=21845 sum=65536), and identical dictionary
hashes across all three ISAs:

  Mama:    dict_hash=0x8d712e07c690d713
  Hermes:  dict_hash=0xa07a57d794c1665d
  Artemis: dict_hash=0x9cc9a7e72b590250

Confirms cross-arch determinism holds with the EXECUTE/format/TYPE/
vocabulary/control-flow fixes applied.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qf6YcnHgaEtEygq3knx19
2026-09-05 14:46:24 +00:00
Claude 7191ce5a56 Refresh lfs/amd64/starforth binary artifact
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Rebuilt after the EXECUTE/format/TYPE/vocabulary/control-flow fixes in
the two preceding commits, per lfs/README.md's convention (this binary
is synced whenever the hosted amd64 build runs).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qf6YcnHgaEtEygq3knx19
2026-09-05 14:07:51 +00:00
Claude d6895bdd15 Move vocabulary and control-flow state off file-scope statics onto VM
proof/FINDINGS.md's Isabelle/HOL word-source sweep (§1) found the two
defects severe enough to actively corrupt the live Tripod multi-VM fleet:
file-scope C statics standing in for state that belongs on struct VM.

- vocabulary_words.c (highest severity in the sweep): forth_vocab/
  context_vocab/current_vocab, context_var_addr/current_var_addr, the
  ctx_fc/forth_fc first-char search index, and the `initialized` guard
  were all process-wide statics. Only the first VM to touch any
  vocabulary word ever ran setup; every VM after that silently shared
  VM #1's dictionary-chain pointers and reused VM #1's byte-offset
  addresses as if valid in its own vm->memory. One VM's VOCABULARY/
  DEFINITIONS/FORTH silently changed where every other VM looked up and
  defined words.

- control_words.c: cf_stack/cf_sp/cf_last_mode (IF/THEN/BEGIN/DO/CASE
  compile-time nesting) and the LEAVE/ENDOF patch-site bookkeeping
  (leave_addrs/leave_sp/leave_mark_*, endof_addrs/endof_sp/endof_mark_*)
  were also process-wide statics. Two VMs compiling colon definitions at
  overlapping times would corrupt each other's nesting state.

Both moved onto struct VM, following the existing hold_addr/hold_pos
precedent in include/vm.h ("lives in each VM's own memory... so child
VMs never alias Hera's buffer"):

- New VocabularyState struct (vm->vocab): chain heads, VM-cell addresses,
  first-char index, initialized flag.
- New ControlFlowState struct (vm->cf): cf_stack/cf_sp/cf_last_mode plus
  the LEAVE/ENDOF patch-site stacks. cf_tag_t/cf_item_t/CF_STACK_MAX
  moved from control_words.c into include/vm.h since they're now part of
  the struct VM field's type.
- Sentinel fields (-1/-999, meaning "empty") explicitly initialized in
  both vm_init_with_host() implementations (hosted src/vm_bootstrap.c and
  kernel src/starkernel/vm/vm_bootstrap.c) alongside the existing
  dsp/rsp = -1 initialization, since the preceding zero-init leaves them
  at 0 rather than their empty sentinel.

Every word function in both files already took VM *vm, so no call sites
outside these two files needed to change; cf_push_item/cf_pop_item/
cf_peek_item gained a VM* parameter to reach vm->cf.

Verified: hosted (amd64) and kernel (amd64, __STARKERNEL__) both build
clean with -Wall -Werror after a full clean rebuild (struct VM's layout
changed size, and this Makefile has no header-dependency tracking, so a
stale incremental build would have linked mismatched object layouts).
Hosted POST suite 1012/1012 passing (0 regressions). Manually exercised
VOCABULARY/DEFINITIONS/FORTH/ORDER, and IF/ELSE, DO/LOOP/LEAVE,
BEGIN/WHILE/REPEAT, and CASE/OF/ENDOF/ENDCASE (including nested DO with
I/J) in the REPL -- all correct and unchanged from pre-refactor behavior.

Note: a pre-existing CASE/ENDCASE default-clause bug (the code after the
last OF...ENDOF pair does not correctly become the "default" value once
DROP runs) was found while testing this refactor and confirmed present
on unmodified master too -- not touched here, out of scope for this pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qf6YcnHgaEtEygq3knx19
2026-09-05 14:07:41 +00:00
Claude c36bd99e1e Fix EXECUTE/?/DUMP/TYPE/DECIMAL-HEX-OCTAL/ALIGN defects from proof sweep
proof/FINDINGS.md's Isabelle/HOL word-source sweep (§4) flagged five real
defects; this fixes all five and records resolution in that doc:

- EXECUTE (system_words.c): cast a popped cell straight to a DictEntry*
  and called through it with only a null check. Now validates via a new
  shared vm_dict_entry_ok(), promoted out of starforth_words.c's
  ENTROPY@/ENTROPY! guard (dictionary_management.c) so EXECUTE gets the
  same live-entry check.

- ? and DUMP (format_words.c): dereferenced the popped cell as a raw host
  pointer, bypassing vm_addr_ok entirely (out-of-VM-bounds read). Both now
  go through VM_ADDR/vm_addr_ok/vm_load_cell/vm_ptr like every other
  memory word (@, `,`, editor_words.c).

- TYPE (io_words.c): bounds check computed addr+count in signed 64-bit
  arithmetic, which can overflow and bypass the check on large operands.
  Replaced with vm_addr_ok(), which is written to avoid that overflow.

- DECIMAL/HEX/OCTAL (format_words.c): wrote only the BASE memory cell,
  never vm->base, the host-mirror field number-output words actually read
  via current_base() -- so these words silently affected number parsing
  but never printing. Now call the existing vm_set_base() (previously
  only used at boot init), which updates both. vm_get_base/vm_set_base
  promoted to public declarations in include/vm.h.

- ALIGN vs ALLOT/,/C,/2, (dictionary_words.c): disagreed on dictionary
  growth ceiling (2MB vs 5MB). Investigated which was correct rather than
  blindly widening: vm_get_block_addr() maps block N to
  vm->memory + N*BLOCK_SIZE across the full 5MB arena, and
  USER_BLOCKS_START (block 2048) lines up exactly with
  DICTIONARY_MEMORY_SIZE -- so ALLOT/,/C,/2, letting `here` grow past 2MB
  could silently corrupt live block/user data sharing that memory.
  Tightened ALLOT/,/C,/2, to DICTIONARY_MEMORY_SIZE to match ALIGN.

Verified: hosted (amd64) and kernel (amd64, __STARKERNEL__) both build
clean with -Wall -Werror; hosted POST suite 1012/1012 passing (0
regressions); manually exercised EXECUTE, ?/DUMP, TYPE, HEX/DECIMAL/OCTAL,
and large-ALLOT rejection in the REPL.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Qf6YcnHgaEtEygq3knx19
2026-09-05 14:07:11 +00:00
Robert Allan JamesandClaude Sonnet 5 70dc8beba4 FABRIC-2.md §I.9 follow-on: blinking | cursor instead of static block
Build / build-riscv64-img (push) Canceled after 0s
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Captain Bob asked for the framebuffer cursor to render as a blinking
vertical bar rather than the previous static solid-block glyph.

vt100_draw_cursor() (hal/vt100.c) now fills a thin bar (cell_w()/8, min
1px, full cell height) at the cursor's left edge instead of the whole
cell -- an I-beam shape. vt100_erase_cursor() is unchanged (clearing the
whole cell already safely covers the narrower bar).

Blinking is new in repl.c: sk_console_readline()'s idle branch toggles the
cursor on/off every SK_CURSOR_BLINK_INTERVAL (50 ticks, 500ms at 100Hz)
via alternating console_fb_draw_cursor()/console_fb_erase_cursor() calls,
independent of the heartbeat/idle-beat mechanism the §I.9 fix just touched
(deliberately not reused, to avoid recoupling to that path). Runs
regardless of n, so it blinks whether sitting at a bare prompt or paused
mid-edit. Every deterministic draw site (initial prompt, prompt reanchor,
backspace, character echo) now goes through a new helper, sk_cursor_show(),
which resets the blink cycle to "on" and redraws -- typing always shows a
solid cursor, never mid-blink.

Verified via the mandatory foreground 3-arch QEMU acceptance boot: amd64
(logs/20260905-021054, extensive live interactive typing including
multi-line : / ; word definitions and error cases, prompts stayed
correctly attached throughout), aarch64 (logs/20260905-021551), riscv64
(logs/20260905-022324) -- all three reached (zuse) ok> and shut down
cleanly via BYE.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 02:24:51 -04:00
Robert Allan JamesandClaude Sonnet 5 8edb95b65d FABRIC-2.md §I.9: fix the terminal phantom-linebreak defect
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
console_ensure_line_start() (hal/console.c) used to emit its newline
immediately via a path that deliberately skipped the tx-byte counter, so
that sk_repl_idle()'s unconditional per-beat call to it (repl.c, ~1s idle
heartbeat) could force a real newline the REPL's own prompt-reanchor logic
never noticed -- the prompt was never reprinted, and the next real
keystroke echoed onto the now-blank line, indistinguishable from Enter
having already been pressed at a bare prompt. Root-caused in the previous
commit (704573b); this commit applies the fix per explicit go-ahead.

Fix: defer the newline instead of emitting it eagerly.
console_ensure_line_start() now only sets a flag (g_pending_line_close);
the newline is realized -- for real, and counted by g_console_tx_count
like any other output -- on the next actual console_putc() call, or
silently discarded via the new console_cancel_deferred_line_start() if the
caller decides nothing was actually printed. sk_repl_idle() captures
tx_before_idle right after its console_ensure_line_start() call and cancels
the deferred newline at both of its exit points when console_tx_count()
hasn't moved. A beat with nothing to report now leaves the console
untouched; a beat that does print still closes the dangling prompt line
first, properly counted this time. The pre-existing n > 0 mid-edit gate in
sk_console_readline() is untouched -- independent purpose, not the bug.

Verified via the mandatory foreground 3-arch QEMU acceptance boot
(clean qemu, amd64 -> aarch64 -> riscv64, one at a time): all three reached
(zuse) ok>, all echoed the first real input on the same log line as the
prompt rather than a fresh line, all shut down cleanly via BYE. amd64's log
additionally shows live human backspace-correction still glued to the same
prompt line. Logs: logs/20260905-015854 (amd64), logs/20260905-020248
(aarch64), logs/20260905-020552 (riscv64); logs/20260905-015813 is a
foreground-rule-violation retry killed and redone correctly, kept per this
project's "never delete logs/" convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 02:08:06 -04:00
Robert Allan JamesandClaude Sonnet 5 704573bdfc FABRIC-2.md §I.9: root-cause the terminal defect, not fixed (identify-only)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
No code changed. This is a closure annotation on an existing open item
where it lives (FABRIC-2.md, marked archival by CLAUDE.md, but §I's punch
list landed there after the FABRIC series rename and genuinely still
lives there) -- not new design content in an archival doc.

Used QMP send-key to inject a real keypress into a running headless QEMU
guest -- no physical keyboard needed, overturning this item's own
"no way to observe the live QEMU GTK window from this environment"
assumption. A first pass (amd64 + aarch64, real first keypress after a
fresh boot, pre/post log captures) actually reproduced the defect but
misread it as clean: both logs showed the echoed key on a bare new
"[Hera]" line instead of appended to the still-visible "(zuse) ok> "
line, which looks like normal REPL output unless you know the prompt
should still be there. QMP screendump on aarch64 confirmed it visually.

Root cause traced to two specific interacting lines, not guessed:
console_ensure_line_start() (hal/console.c:312) emits its newline via
the tx-count-exempt console_putc_inner(), by design, so idle chatter
doesn't spam the REPL's prompt reanchor. But sk_repl_idle() (repl.c:178)
calls it unconditionally on every ~1s idle beat, including at a bare
prompt with nothing typed (n==0, repl.c:610) -- forcing a real but
tx-count-invisible newline that the reanchor check (repl.c:633) never
notices, so the prompt is never reprinted. The next real keystroke
echoes onto the now-blank line with a lazily-emitted "[VMName] " prefix,
indistinguishable from Enter having already been pressed. Not a
first-keypress race -- it fires on any ~1s+ human pause at a bare
prompt, i.e. nearly always, matching "consistent and reproducible."

repl.c's own comment at lines 598-609 already half-diagnosed this
exact failure mode and gates it for n>0 (mid-edit); the gap is the
identical n==0 case (bare prompt) was treated as harmless. Likely fix
location noted, not designed here.

Verified reproducible on both amd64 (i8042) and aarch64 (virtio-input)
input paths; riscv64 shares aarch64's virtio-input code path. Evidence:
three fresh boot logs plus before/after screendump PNGs
(evidence/aarch64/qemu-screenshot-20260905-013835-i9-*.png). No 3-arch
acceptance boot run for this commit -- no code changed, and these
logs/screenshots are themselves the evidentiary artifact, not a
generic regression check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 01:47:53 -04:00
Robert Allan JamesandClaude Sonnet 5 dbaead0af2 xhci: route driver chatter through log_message(), silence at default log level
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
xhci.c and repl.c's USB attach/detach path printed every xHCI command
submission, completion, and BOT transfer step unconditionally via
console_println()/console_puts() -- floods the serial log on every boot,
regardless of whether anyone is debugging the USB stack.

Converted every "xhci:"-prefixed line to log_message() with a level
chosen by what it reports, not blanket debug:
- LOG_ERROR: allocation/mapping failures, timeouts, command failures,
  CSW signature/tag mismatches, CSW FAILED/PHASE ERROR, "not implemented"
  refusals, every "deferred ... setup failed" path
- LOG_WARN: dropped/skipped conditions (command ring busy, tracked-port
  range exceeded), unrecognized media (bad version/CRC), TUR retry
- LOG_DEBUG: routine progress (command submitted, succeeded, transfer
  completed, port connected) and expected outcomes (recognized/blank
  media)

Default log level is LOG_INFO, so the LOG_DEBUG chatter that was the
actual complaint is now silent by default and re-enabled with
--log-level=debug; LOG_ERROR/LOG_WARN stay visible so real faults aren't
buried.

xhci_log_hex32() now routes through log_message(LOG_DEBUG, ...) instead
of console_puts()/console_println() directly -- kept its own zero-padded
8-digit hex formatting rather than switching to log_message()'s %x
(which has no width control), since register values lining up in the
log is the reason this helper exists. console.h dropped from xhci.c,
no longer used directly.

Verified functionally unchanged, not just "still boots": all three
architectures reach zuse)ok>, log_message()-instrumented lines are gone
from the default-level log (grep -c xhci == 0 on all three, versus dozens
before), and the USB thumbdrive path still works end to end --
"Zuse: identity confirmed from attached thumbdrive" appears on all three
boots exactly as before.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 01:25:50 -04:00
Robert Allan JamesandClaude Sonnet 5 1bd041e84d FABRIC-3.md §IV.3 item 5: pci_init() DTB path re-scoped, doc-only, nothing implementable this pass
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Re-examined the Pi 5 RP1 PCIe access re-scoping (§IV.3 item 5) to see what
was actually buildable before real hardware arrives (2026-09-17), and
found nothing safely implementable this pass -- recorded three corrections:

1. The "vmm_map_range() needed for native boot" secondary finding is
   wrong as stated: arch_mmu_init() is a stub on both non-amd64 ISAs,
   vmm.c's load_cr3() is a no-op outside __x86_64__, and riscv64's satp
   is explicitly left at Bare (this session's own §V.3 item 7 fix). No
   non-amd64 ISA ever turns its own MMU on today, so VA==PA and there is
   nothing to map. Reframed as a future concern conditional on MMU
   bring-up, not a current prerequisite.

2. No silent-wrong-answer risk exists today: with no ACPI on native Pi 5
   boot, pci_init()'s MCFG parse already fails cleanly and aarch64 takes
   no ECAM fallback, rather than misreading RP1's 37 KB indirect window
   as flat ECAM.

3. The "make config-space dispatch runtime instead of compile-time" half
   of the re-scoped fix, considered on its own, isn't separable:
   portio_read32() and siblings use outl/inl inline asm that cannot exist
   in an aarch64/riscv64 translation unit, so any case referencing them
   stays #ifdef-gated regardless -- an enum wearing the same compile-time
   selection. It only becomes real dispatch once a second non-amd64
   mechanism is actually compiled in.

Broadcom indirect access itself remains unimplemented: no QEMU model
exists for "brcm,bcm2712-pcie" (zero branches would ever execute before
real hardware, unlike the guard-exercised GIC/PLIC DTB fixes), and
config-space access alone is insufficient for RP1 to enumerate without
the real driver's controller bring-up (link training, PERST, window
setup) -- building only the index/data window would compile and boot
while silently never working.

No code changed. 3-arch acceptance run anyway per convention: all three
reach zuse)ok> with identical virtio-blk/xHCI PCI device discovery to
the pre-change baseline (commit 9b6de5d), confirming no regression from
a change that touched no source.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 01:06:24 -04:00
Robert Allan JamesandClaude Sonnet 5 9b6de5d6c7 riscv64: PLIC base address DTB-discovered, QEMU-virt constant as fallback (§V.3 item 3)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
plic_init() now takes boot_info->dtb (threaded through apic_init()) and
tries fdt_find_node_by_compatible(dtb, "sifive,plic-1.0.0") ->
fdt_find_prop_in_node(..., "reg", ...) before falling back to the
QEMU-virt-specific constant it previously hardcoded unconditionally.
Reuses the node-scoped DTB lookup primitive built for the aarch64 GIC
base fix unchanged. s_plic_base is now a runtime uintptr_t, same shape
as apic.c's s_gicd_base/s_gicc_base.

This system's QEMU/UEFI riscv64 firmware does not forward a DTB to the
guest (timer.c's own timebase-frequency read falls back too, confirmed
in this boot's own log), so only the no-DTB fallback branch is exercised
here -- the success branch (a real DTB with a matching PLIC node) stays
unverified until real Milk-V Mars hardware. FABRIC-3.md's first-drafted
claim that the success branch would run (based on a stale comment in
plic.c's own pre-fix header) was checked against the actual log and
corrected before this commit.

3-arch acceptance: amd64/aarch64 don't compile these files, so their
runs are non-regression on untouched files only. riscv64's own boot log
confirms the fallback path prints exactly as designed and boot reaches
zuse)ok> unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 00:56:25 -04:00
Robert Allan JamesandClaude Sonnet 5 90ee8deb6d riscv64: skip satp Bare-mode switch when already Bare (§V.3 item 7 audit fix)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
arch_early_init() unconditionally cleared satp on every boot, justified
only by behavior observed under QEMU/EDK2 firmware (satp.MODE=10/Sv57,
kernel identity-mapped within it). That reasoning never applied to the
native U-Boot+OpenSBI boot path, where satp is conventionally already 0
at S-mode handoff -- the unconditional clear was likely a harmless no-op
there, but on an unverified assumption.

Fix: read satp.MODE first and return early when it's already 0 (nothing
to switch away from, no safety argument needed). The unconditional
csrw/sfence pair still runs unchanged for the confirmed QEMU/EDK2 case.
No Sv39/Sv48/Sv57 page-table walker built -- out of proportion to this
finding's severity.

3-arch acceptance: amd64/aarch64 don't compile this file, so their runs
are non-regression on untouched files only. riscv64's own boot log
confirms satp.MODE = 0xa at entry, so the mode != 0 branch ran and
"satp cleared -- Bare mode, explicit" printed exactly as before the fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 00:17:35 -04:00
Robert Allan JamesandClaude Sonnet 5 350287850f arch/aarch64: fix VBAR_EL2 stale comment + hardcode HVC PSCI conduit
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
FABRIC-3.md §IV.3 item 7's code audit found two more findings; fixed both
per direct instruction naming them specifically.

Comments only, no behavior change:
interrupts.c's arch_interrupts_init() doc comment (and its matching
runtime EL2 console message) claimed VBAR_EL2 was "a known gap... not yet
wired." Checked against isr.S's aarch64_install_vectors() and found that
claim stale: the assembly already branches on aarch64_current_el() and
writes vbar_el2/vbar_el1 correctly, threading the same answer through
el2_mode_flag for the IRQ trampoline's ELR/SPSR selection too. There was
no gap to close -- only the comment was wrong. Also fixed a same-vintage
one-word staleness in arch.c's aarch64_install_vectors extern comment
("installs VBAR_EL1" -> EL-aware), caught while touching this.

Real behavior change, gated to preserve existing QEMU behavior:
arch_cold_reset() hardcoded the PSCI conduit to HVC, a documented
QEMU-specific workaround (QEMU's AAVMF has no genuine EL3 to answer SMC).
Real Pi 5 hardware's ATF means EL3 exists there; confirmed further this
pass that bcm2712.dtsi's own /psci node declares method="smc" directly,
not just inferred from ATF's presence.

New aarch64_psci_conduit_init(dtb) in arch.c: fdt_valid(dtb) ->
fdt_find_node_by_compatible(dtb, "arm,psci-1.0") ->
fdt_find_prop_in_node(..., "method", ...) -- sets a module-static
s_psci_use_smc flag to 1 only when method reads exactly "smc"; every
other outcome (no DTB, no PSCI node, method="hvc", property absent)
leaves it at its default 0/HVC, preserving this system's existing QEMU
behavior exactly. arch_cold_reset() now branches on that flag between
smc #0/hvc #0. Called once from apic_init() (arch/aarch64/apic.c), the
one point in boot with boot_info->dtb already in hand -- collocated with
its consumer in arch.c rather than defined in apic.c, since
arch_cold_reset() has no boot_info of its own by the time it runs (called
from deep in VM execution, via BYE).

Three-arch QEMU acceptance run exercised both new guard branches: the
aarch64 boot log shows "PSCI: no DTB -- using HVC (QEMU virt-machine
default)" immediately before the existing GIC discovery lines, then
boots clean to zuse)ok> -- confirming the QEMU/UEFI path is unchanged.
The SMC success branch itself stays unverified until real Pi 5 hardware
runs BYE.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-05 00:06:41 -04:00
Robert Allan JamesandClaude Sonnet 5 dc9445abec apic.c (aarch64): fix GIC base address for real Pi 5 hardware
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
FABRIC-3.md §IV.3 item 7's code audit found GICD_BASE_PA/GICC_BASE_PA
hardcoded to QEMU virt-machine constants (0x08000000/0x08010000),
self-documented as a deliberate exception because QEMU's aarch64 UEFI
firmware never forwards a DTB. That premise doesn't hold on the native
Pi 5 boot path (rpi5_native_boot.c), which does have a real DTB and calls
this same, unmodified apic_init() -- which ignored boot_info entirely and
always programmed the QEMU addresses. Real BCM2712 GIC-400 is at
0x10_7fff9000 (confirmed against bcm2712.dtsi's axi/gicv2 nodes), a
completely different region of the address space. Fixed per direct
instruction, naming this finding specifically.

apic_init() now calls gic_bases_from_dtb() first: fdt_valid(dtb) ->
fdt_find_node_by_compatible(dtb, "arm,gic-400") ->
fdt_find_prop_in_node(..., "reg", ...), reading the first two
2-address-cell/2-size-cell entries (GICD, then GICC -- the standard
arm,gic-400 binding order). Falls back to the QEMU constants on any
failure (no DTB, no matching node), so the existing QEMU/UEFI path is
unchanged. GICD_BASE_PA/GICC_BASE_PA became s_gicd_base/s_gicc_base
(module-static uintptr_t, no longer compile-time constants on this path)
-- every MMIO call site (apic_init, apic_spi_enable, apic_read_iar,
apic_eoi_intid) now reads through them.

Three-arch QEMU acceptance run exercised the guard itself, not just
compiled it: the aarch64 boot log shows "GICv2: no DTB GIC node -- using
QEMU virt-machine defaults" followed by the unchanged "distributor+CPU
interface enabled, PPI 30" line, then boots clean to zuse)ok>. The
success branch (a real DTB with a matching node) stays unverified until
real Pi 5 hardware -- this build never has a devicetree to exercise it
against.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 23:54:39 -04:00
Robert Allan JamesandClaude Sonnet 5 0f798256a0 FABRIC-3.md: aarch64/riscv64 boot-path code audit pass (§IV.3/§V.3 item 7)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Reviewed arch/aarch64/{apic,arch,timer,interrupts}.c and
arch/riscv64/{apic,plic,arch,interrupts,timer}.c for the same class of
QEMU-virt-vs-real-hardware assumption §III item 6's amd64 audit looked for.
Report only, per this project's "identify, don't fix unless asked" rule --
no source files changed.

Findings, aarch64:
- Severe, confirmed live: apic.c's GICD/GICC base addresses are hardcoded
  QEMU-virt constants, self-documented as a deliberate exception because
  QEMU's aarch64 firmware never forwards a DTB. That premise no longer
  holds -- the native Pi 5 boot path (item 2) receives a real DTB and
  calls the same, unmodified kernel_main(), whose M4 sequence calls
  apic_init(boot_info) unconditionally (kernel_main.c:413); apic_init()
  still ignores boot_info entirely. Real BCM2712 GIC-400 is at
  0x10_7fff9000, confirmed against bcm2712.dtsi -- a different region of
  the address space entirely from the hardcoded 0x08000000. With the MMU
  off at this point in boot, this blocks reaching ok> on real hardware as
  the code stands.
- Doc-only correction: interrupts.c's own comment claims VBAR_EL2 is never
  installed ("a known gap"). Checked against isr.S and found stale -- the
  actual implementation already branches on aarch64_current_el() and
  installs vbar_el2/vbar_el1 correctly. No functional gap; the comment
  describes one the code already closed.
- arch_cold_reset() hardcodes the PSCI conduit to HVC, a QEMU-specific
  workaround (QEMU's AAVMF has no genuine EL3). The Pi 5's real ATF means
  EL3 firmware exists there, making SMC the conventional real-hardware
  conduit -- no runtime detection exists.

Findings, riscv64:
- plic.c's QEMU-virt-hardcoded PLIC_BASE is already tracked (§V.3 item 3);
  this pass confirms rather than rediscovers it.
- arch_early_init()'s satp clear is justified entirely by behavior
  observed under QEMU's EDK2 firmware; the native boot path (U-Boot+
  OpenSBI, no EDK2) doesn't share that observation, though the action is
  likely still safe since OpenSBI's handoff conventionally leaves satp=0
  already. Lowest-severity finding in the set.
- Clean: the SBI timer path and arch_cold_reset()'s SBI SRST call are both
  genuinely hardware-independent -- named as the portable pattern amd64's
  i8042-pulse reset and aarch64's hardcoded-HVC PSCI call both lack.

Doc-only change. Three-arch QEMU acceptance (amd64/aarch64/riscv64, in
order) run to confirm non-regression only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 23:31:38 -04:00
Robert Allan JamesandClaude Sonnet 5 6a6ff9353f FABRIC-3.md: amd64 boot-path code audit pass (§III item 6)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Reviewed src/starkernel/boot/uefi_loader.c and every file in arch/amd64/
(arch.c, apic.c, ioapic.c, interrupts.c, i8042.c, timer.c) for anything that
assumes SER5-specific hardware rather than standard UEFI/ACPI. Report only,
per this project's "identify, don't fix unless asked" rule -- no source
files changed.

Verified clean: uefi_loader.c's COM1 presence probe, its two-pass ACPI
2.0-then-1.0 GUID preference, the GetMemoryMap/ExitBootServices golden
path, and GOP BltOnly degradation. apic.c/ioapic.c derive LAPIC mode,
APIC timer frequency, and I/O APIC base/GSI at runtime, not from hardcoded
constants.

Four findings recorded (not fixed):
1. HPET base (timer.c) hardcoded at 0xFED00000, never cross-checked against
   the ACPI "HPET" table -- the same bug class this file's own comment
   already documents and fixed for the PM_TMR port.
2. LAPIC base always 0xFEE00000; apic_init()'s own doc comment flags MADT
   relocation as unimplemented. Narrower than that suggests: ioapic.c's
   parser already reads MadtHeader.local_apic_address, just never plumbs
   it to apic.c. MADT type 5 (Local APIC Address Override) isn't parsed
   at all.
3. i8042 PS/2 controller (i8042.c) and the legacy 8259 PIC (interrupts.c)
   are poked unconditionally with no ACPI FADT presence check, unlike this
   same file's own raw_serial_init() COM1 probe.
4. arch_cold_reset() (confirmed live via BYE, mama_forth_words.c:1437)
   pulses the i8042 reset line instead of using the FADT's standards-defined
   RESET_REG/RESET_VALUE, which doesn't depend on i8042 existing at all.

None are certain to bite on the real SER5 -- the conventions assumed hold
on the large majority of PC-compatible x86_64 systems -- but the decision
in FABRIC-3.md §III requires arguing genericity from standards compliance,
not from "it booted," and these are the concrete gaps.

Doc-only change. Three-arch QEMU acceptance (amd64/aarch64/riscv64, in
order) run to confirm non-regression only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 23:15:09 -04:00
Robert Allan JamesandClaude Sonnet 5 ecdb32e0d6 boot_media/rpi5/config.txt: Pi 5 native boot config (FABRIC-3.md §IV.3 item 6)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Researched against the official raspberrypi.com config.txt reference and one
real, working Pi 5 bare-metal project's own checked-in config.txt
(leopoldch/BatMetal), not assumed from general Pi knowledge:

- kernel=kernel_2712.img, os_check=0, device_tree=bcm2712-rpi-5-b.dtb
- enable_uart=1 added (correction to the item's original text): costs
  nothing when unused, and is the only diagnostic channel that would survive
  a framebuffer failure on the untested mailbox path (rpi5_native_boot.c).
- arm_64bit=1 dropped (correction to the item's original text): confirmed
  inert on Pi 5 by official docs.
- dtparam=pciex1 researched, deliberately omitted with rationale recorded --
  community-reported only, not needed until item 5's re-scoped RP1/PCIe work.
- os_check=0 vs. BatMetal's own config (which omits it) recorded as an open,
  unresolved discrepancy rather than silently picked.

boot_media/rpi5/README.md notes the one remaining gap this exposes:
kernel_2712.img names a file nothing currently builds (item 1's
separate-image build target is still outstanding future work).

Doc-and-config only, no compiled code changed. Three-arch QEMU acceptance
(amd64/aarch64/riscv64, in order) run to confirm non-regression only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 23:05:39 -04:00
Robert Allan JamesandClaude Sonnet 5 11c93773d0 FABRIC-3.md: pci_init() DTB path item was wrong -- re-scope, don't build the ECAM version
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Investigated before writing code, per this document's own discipline, and
found the original item 5 text's assumption doesn't hold: it framed this
as "swap the ACPI MCFG lookup for a DTB one, same flat-ECAM access
underneath." Confirmed against three primary sources that this is wrong:

- bcm2712-rpi-5-b.dts: RP1 sits under pcie2 ("brcm,bcm2712-pcie"),
  reg = <0x10 0x00120000 0x00 0x9310> -- a ~37KB window, far too small
  for a flat 256MB ECAM region.
- pcie-brcmstb.c (the real Linux driver for this compatible string):
  brcm_pcie_map_bus() computes a standard ECAM-shaped offset but accesses
  it through an IDX_ADDR/DATA_ADDR indirect index+window pair, not a
  direct MMIO read. A real third config-access mechanism, not a bigger
  version of the DTB-lookup task.

Re-scoped item 5 (§IV.3) to record the real shape: pci.c's cfg_read32/
cfg_write32 family dispatches today via #ifdef ARCH_AMD64 vs. everything
else (flat ECAM) -- but QEMU aarch64 (flat ECAM, real ACPI MCFG) and Pi 5
aarch64 (Broadcom indirect windowing) are the same build, so this needs a
runtime dispatch change to a file all three architectures currently boot
through, not a small addition. Also flagged: pci_init()'s vmm_map_range()
for the ECAM window is amd64-only today (aarch64/riscv64 rely on UEFI's
identity map); native boot has no UEFI identity map at all, so any PCIe
work here needs its own explicit mapping regardless of access mechanism.

Also corrected §V.3 item 5's now-stale "shares the same code" cross-
reference -- the Mars's JH7110 PCIe controller needs its own primary-
source check, not an assumed-shared implementation with RP1's.

Not implemented this pass -- recording the real shape is the deliverable;
building the runtime-dispatch version is a materially larger, separate
task. Doc-only change; 3-arch boot verified per the project's standing
rule, proving only that nothing regressed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 22:47:18 -04:00
Robert Allan JamesandClaude Sonnet 5 7187d68082 rpi5_native_boot.c: carve /reserved-memory out of the Pi 5 memory map
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Previously deferred (rpi5_native_boot.c's own header comment flagged this
as needing interval-splitting logic written blind against hardware not
yet in hand) -- revisited by fetching bcm2712-ds.dtsi directly rather than
assuming reserved-memory was empty or absent. It has one static child
(atf@0, ARM Trusted Firmware's own region) and one dynamic child
(linux,cma, size/alloc-ranges only, no fixed reg) -- the dynamic one is
skipped, nothing fixed to carve and no allocator this early to service it
against anyway.

Adds two fdt.c primitives: fdt_find_node_by_name() (reserved-memory has
neither compatible nor device_type per DT spec) and fdt_next_child_node()
-- one exported symbol, not the two-primitive general sibling-walker
originally sketched, collapsed after review since the only real use here
is "iterate one node's direct children."

collect_reserved_ranges() reads each child's own #address-cells/
#size-cells with a fallback to root's only if absent -- confirmed
necessary, not just defensive: reserved-memory's own declared <2>/<1>
genuinely differs from root's <2>/<2>. emit_region_with_carveouts()
clips a sorted reserved-range list against each RAM region, emitting
alternating EfiConventionalMemory gaps and EfiReservedMemoryType
carve-outs (insertion sort, no libc qsort in freestanding).
RPI5_MAX_MEMMAP_ENTRIES is the exact worst-case count, recomputed rather
than estimated -- the rpi5_mailbox.c buffer-size bug is the standing
lesson for this pattern.

Compile-only-verified; nothing in the existing UEFI/QEMU path calls
rpi5_native_boot(), so this cannot be exercised until real hardware.
Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 22:24:57 -04:00
Robert Allan JamesandClaude Sonnet 5 67e3fb7459 rpi5_native_boot.c: Pi 5 DTB->BootInfo constructor (FABRIC-3.md §IV.3 item 2)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
rpi5_native_boot() populates the existing BootInfo struct from the
devicetree instead of UEFI protocols, then calls the existing, unmodified
kernel_main() -- the crux of why most of M1-M9 stays shared between the
UEFI and native boot paths. native_rpi5_entry.S now tail-calls into it
instead of halting.

memory_map is built from /memory's own reg, honoring the *root* node's
#address-cells/#size-cells (confirmed against bcm2712.dtsi's actual root
node -- <2>/<2> -- not assumed; a wrong cell width here would compile and
boot clean in QEMU while silently corrupting the real memory map on real
silicon). Required a new fdt_find_node_by_device_type() since /memory is
identified by device_type = "memory" per DT spec, not compatible.

args comes from /chosen's bootargs fed into the existing
cmdline_parse_ascii() (confirmed pure C99 with no UEFI coupling before
reusing it). framebuffer comes from the already-built
rpi5_mailbox_get_framebuffer() at a fixed 1920x1080x32 default -- no EDID
query exists in this codebase, flagged rather than guessed past.

Deliberately scoped out, not silently skipped: /reserved-memory is not
parsed. Carving reserved sub-ranges out of /memory's span needs
interval-splitting logic that would be written blind against hardware not
yet in hand -- exactly the kind of code that hides a bug until real
silicon. pmm.c's Pass 3 only ever clears pages this file lists as
EfiConventionalMemory, so the gap is "less usable RAM than optimal," never
"reserved RAM wrongly marked free." Left as its own future item.

Also fixes a real link failure this work surfaced: boot/cmdline.c was only
in LOADER_SRCS_BASE (the .efi target), not KERNEL_SRCS_BASE (the separate
.elf target arch/aarch64/*.c also wildcards into) -- added it there too.

Compile-only-verified; nothing in the existing UEFI/QEMU path calls
rpi5_native_boot(), so this cannot be exercised until real hardware.
Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 14:42:32 -04:00
Robert Allan JamesandClaude Sonnet 5 281de9547c native_rpi5_entry.S: Pi 5 native (non-UEFI) boot entry stub (FABRIC-3.md §IV.3 item 1)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
rpi5_native_start masks x0 down to the documented 32-bit DTB-pointer range
(the firmware's own entry protocol leaves the upper 32 bits unspecified),
stores it into g_rpi5_dtb_ptr for the still-open DTB->BootInfo constructor
(item 2) to read, then switches sp to a dedicated 2 MiB BSS stack -- this
path has no EDK2 boot stack to inherit, unlike every other entry path in
this codebase.

Intentionally halts (wfe/b loop) afterward rather than tail-calling into
item 2's constructor, which doesn't exist yet -- no stub function pretending
to be more than it is.

Not yet linked at the real 0x80000 load address; that needs its own linker
script/build target, not scoped into this item. Compiles and links into the
existing ARCH=aarch64 QEMU/UEFI acceptance build as dead code (ELF kernel
build's KERNEL_ASM wildcards every *.S in arch/aarch64/; nothing there
branches to it), same as rpi5_dtb.c/rpi5_mailbox.c before it.

Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 14:12:42 -04:00
Robert Allan JamesandClaude Sonnet 5 a32b0ebcbe rpi5_mailbox.c: wire the VideoCore mailbox message protocol (FABRIC-3.md §IV.3 item 3)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
New rpi5_mailbox_get_framebuffer() sends one property-tag request buffer
(phys size, virt size, depth, pixel order, virtual offset, allocate-buffer,
get-pitch) over the register layout rpi5_dtb.c already discovers, populating
an Rpi5FramebufferInfo kept in exact field-for-field sync with uefi.h's
FramebufferInfo so console.c/vt100.c/framebuffer.c need no downstream changes
once this is wired into a real entry stub.

Register offsets (+0x00/+0x18 MBOX0 read/status, +0x20/+0x38 MBOX1
write/status) confirmed against a Pi-5-specific bare-metal reference,
independently cross-checked against this codebase's own rpi5_dtb.c
translated base address.

Caught and fixed a real buffer-overflow bug before compiling: the static
request buffer was sized 32 words against an actual 35-word requirement for
the 7-tag sequence, recomputed exactly rather than re-estimated; resized to
40 words.

Two things flagged as genuinely unverified against real hardware (not
guessed past the comment): the allocate-buffer tag's request-size field
value, and whether its response address needs classic bus-alias masking on
Pi 5 specifically.

Compile-only-verified -- no caller yet (that's the still-open entry-stub/DTB
constructor items). Verified 3-arch boot to ok>/zuse)ok>.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 14:00:32 -04:00
Robert Allan JamesandClaude Sonnet 5 ca52ce8243 rpi5_dtb.c: wire fdt.c's node-scoped lookup into the Pi 5 UART/mailbox addresses
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
New include/starkernel/rpi5_dtb.h / src/starkernel/arch/aarch64/rpi5_dtb.c:
rpi5_uart_base()/rpi5_mailbox_base(), each finding their peripheral by
compatible string ("arm,pl011" / "brcm,bcm2835-mbox") via fdt_find_node_by_
compatible() then reading its "reg" via fdt_find_prop_in_node().

Found and fixed a real translation gap before it could have silently
produced a wrong address: confirmed directly against bcm2712.dtsi
(raspberrypi/linux) that both peripherals live under one "soc"
simple-bus node whose ranges property adds a fixed 0x10_0000_0000
offset to every child reg value. fdt.c's reader deliberately doesn't
apply ranges translation generally (not a general devicetree
library); this file applies that one, fixed, SoC-wide offset
explicitly, documented with the exact devicetree excerpt that
confirmed it.

Compile-only verification -- no caller wired in yet, these two
functions are what the still-open entry-stub and mailbox-framebuffer-
driver punch-list items will call. Verified 3-arch boot to ok>
(amd64/aarch64/riscv64, each in the foreground; rpi5_dtb.o confirmed
built on aarch64, the only arch that compiles this file).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 13:36:08 -04:00
Robert Allan JamesandClaude Sonnet 5 5e46f18fd9 fdt.c: node-scoped lookup extension (FABRIC-3.md SSIV.3/SSV.3 shared item)
Build / build-riscv64-img (push) Canceled after 0s
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Adds fdt_find_node_by_compatible() and fdt_find_prop_in_node() to the
minimal FDT reader -- the extension fdt.h's own header comment already
flagged as a known future need ("item 0.6 will need node-scoped reg
lookups"), now with real consumers: the Pi 5's UART/mailbox register
addresses (native boot, no ACPI) and the Milk-V Mars's real PLIC base
address (currently hardcoded to QEMU-virt's own value).

fdt_find_node_by_compatible() matches any entry in a node's
NUL-separated "compatible" list, first match in document order.
fdt_find_prop_in_node() scopes to that one node's own direct
properties only -- stops at the first child node or the node's own
end, per the DT spec's ordering guarantee that a node's properties
always precede its children. Same minimal, non-tree-building,
single-linear-scan-per-call style as the existing reader; no new
state, no allocation.

Compile-only verification -- no caller wired in yet, this is the
shared primitive both boards' own punch-list items will call once
built. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in
the foreground).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 13:05:42 -04:00
Robert Allan JamesandClaude Sonnet 5 bffd87615d FABRIC-3.md: riscv64 (Milk-V Mars) planning decided + punch list
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Resolved SS V.1's flagged gap: standard RISC-V SBI boot protocol,
confirmed via OpenSBI's own docs -- a0=hart ID, a1=DTB pointer, S-mode
entry, universal across FW_DYNAMIC firmware regardless of vendor, not
chain-specific guesswork.

Decided: observation is HDMI-only, same reasoning and constraint as
the Pi 5 (no bridge hardware for this board's own first bring-up
either).

Traced boot_info->acpi_table's real riscv64 consumers the same way as
aarch64: pci_init() again (Mars's M.2 slot is PCIe-attached, same
shape of gap as the Pi 5's RP1); timer.c is already fully DTB-driven,
no work needed there.

One real, already-flagged risk found while tracing this: PLIC_BASE is
a QEMU-virt-specific constant, not DTB-discovered -- arch/riscv64/
plic.c's own doc comment already warned about this; it becomes
concrete now that real hardware is actually in scope. Real punch-list
item, not hypothetical.

6-item no-hardware-needed punch list (entry stub, DTB->BootInfo
constructor, DTB-discovered PLIC base, unresearched JH7110 framebuffer
flagged honestly rather than assumed, shared pci_init() DTB path with
the Pi 5, image-packaging tooling) plus 5 items deferred to
2026-09-17.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:55:28 -04:00
Robert Allan JamesandClaude Sonnet 5 1ec2a29384 FABRIC-3.md: aarch64 (Pi 5) planning decided + punch list
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Decisions from conversation: observation is HDMI-only for this
board's own bring-up (no second Pi, no dedicated USB-serial adapter;
using the Milk-V Mars as a bridge before it's independently validated
would be circular). Both boards arrive 2026-09-17, giving real runway
to finish design/code work first ("plan well before doing").

Traced boot_info->acpi_table's actual aarch64 consumers against real
code before writing the punch list: only pci_init() (unconditional,
relevant since RP1 is PCIe-attached) and this session's own
running_under_hypervisor() (already degrades safely on NULL). GIC
init (arch/aarch64/apic.c) already only tries boot_info->dtb, never
acpi_table -- Pi 5 native boot removes its stated blocker for free.

6-item no-hardware-needed punch list: entry stub at 0x80000, a DTB ->
BootInfo constructor calling the existing unmodified kernel_main()
(the crux of why most of M1-M9 stays shared), the mailbox-interface
framebuffer driver, an fdt.c node-scoped-lookup extension (its own
header already flagged this as a known future need), a DTB-based
pci_init() path for RP1, and config.txt contents. Plus 5 items
deliberately deferred until hardware is in hand 2026-09-17.

Also fixed a formatting slip from an in-progress edit (a bullet
accidentally turned into a malformed heading) before it could compound.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:52:37 -04:00
Robert Allan JamesandClaude Sonnet 5 9142d50b73 FABRIC-3.md: Zynq-7000 (Puzhi PZ7010/PZ7020 StarLite) added to hardware reference
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Recorded only, no work scoped -- but unlike BeagleBone Black, this
one isn't a random addition: ROADMAP.md already names Zynq FPGA as
the next big milestone beyond v2.5.0 (the "configurable silicon"
step, tied to the three-product split's HOL-proven sealed-execution
hardware product). This puts a concrete, purchasable board under that
already-named milestone: Puzhi PZ7010-StarLite (XC7Z010) or
PZ7020-StarLite (XC7Z020), dual-core Cortex-A9 PS + Artix-7/Kintex-7
PL fabric, DDR3/JTAG/UART/HDMI/GbE/USB2.0/40-pin, MIPI CSI on the
7020 variant only.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:44:57 -04:00
Robert Allan JamesandClaude Sonnet 5 5eeb993ca2 FABRIC-3.md: riscv64 boot chain resolved, BeagleBone Black noted, header bug fixed
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Milk-V Mars boot chain resolved via VisionFive 2 research: the Mars is
a documented mainline U-Boot target using the exact same binaries as
the VisionFive 2 (same StarFive JH7110 SoC) -- U-Boot + OpenSBI +
devicetree, not UEFI, same fork already decided for aarch64. Entry
point 0x40000000, mhartid CSR for core ID, UART at 0x10000000/115200 --
DTB pointer/hart-ID register convention at actual kernel entry not yet
confirmed for this specific chain.

Fixes a real bug from the earlier aarch64 edit: the "## V. riscv64 --
Milk-V Mars" section header had been accidentally dropped, leaving a
stale duplicate "Already true"/"Genuinely open" block sitting where
the real section should have started. Removed the duplicate, restored
the header, folded today's research into the section properly.

BeagleBone Black added to SS VI's hardware-identification reference per
direct instruction -- recorded only, no work scoped around it: it's a
32-bit ARM (TI AM335x, Cortex-A8) SoC, a genuinely different, fourth
architecture this kernel has no support for, not another board under
an existing one.

FABRIC-4.md SS2: captured the pinned-GPIO-VM idea raised in conversation
(Pi 5 needs one, Milk-V Mars explicitly undecided) as a theory-stage
note, cross-referenced from SSIV/SSV's own open-questions lists.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:43:15 -04:00
Robert Allan JamesandClaude Sonnet 5 987c751352 FABRIC-3.md: per-board hardware-identification reference (SS VI)
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Consolidated SoC/CPU/RAM/I-O facts for all three real-hardware
targets, researched via web search with sources cited rather than
assumed from memory:

- amd64 (Beelink SER5): Ryzen 7 family confirmed; exact SKU
  (5700U/5800H/7735HS all shipped under this branding) not yet
  confirmed against the actual unit -- flagged, not guessed.
- aarch64 (Raspberry Pi 5): BCM2712, quad-core Cortex-A76 @ 2.4GHz,
  VideoCore VII GPU, LPDDR4X-4267. FEAT_RNG (RNDR) presence on this
  core flagged as unconfirmed either way.
- riscv64 (Milk-V Mars): StarFive JH7110, 4x SiFive U74-MC + 1x S7
  monitor core, up to 1.5GHz. Noted it shares its SoC with the
  StarFive VisionFive 2 -- existing VisionFive 2 bring-up material
  may transfer directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:37:29 -04:00
Robert Allan JamesandClaude Sonnet 5 a66af477ac FABRIC-3.md: aarch64 boot-chain decision -- native boot flow, not UEFI
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Researched both options before deciding. The UEFI path (rpi5-uefi,
TF-A+EDK2, SBBR-compliant) is real but archived since 2025-02-04 --
support ended when newer Pi EEPROM firmware broke compatibility, and
its own README says ACPI support is limited/incomplete. Decided:
native boot flow instead (config.txt/kernel_2712.img/DTB, x0=DTB
pointer at entry, no ACPI at all).

Named the real scope rather than estimating it small: a new,
non-UEFI aarch64 entry path, a DTB-driven BootInfo equivalent, and a
new mailbox-property-interface framebuffer driver (no precedent in
this codebase). The one genuine piece of reusable groundwork:
starkernel/hal/fdt.c's minimal FDT reader, already built for
riscv64's timebase-frequency lookup, extends directly to Pi 5
peripheral discovery.

Also documents the peripheral-RNG research: BCM2712 has no
brcm,bcm2712-rng200 (or equivalent) entry anywhere in current
mainline Linux, and RP1's own published peripheral list doesn't
mention an RNG -- genuinely unresolved, not just under-researched.

Not yet turned into a punch list -- the boot-chain fork's own shape
needs thinking through first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:34:30 -04:00
Robert Allan JamesandClaude Sonnet 5 e5e28d5198 FABRIC-3.md: amd64 bare-metal-boot planning decided + punch list
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Decisions from conversation: observation is HDMI (interactive) +
serial via the Raspberry Pi's own GPIO UART as the bridge, if the SER5
exposes a UART header (not yet confirmed); genericity is verified by a
code audit against real UEFI/ACPI standards rather than a second
physical machine (none available); Secure Boot is already disabled on
this SER5, so no signed-loader work is needed for this pass.

9-item punch list follows: build+flash the thumbdrive image, physically
inspect for a UART header, connect HDMI, boot, a code-audit pass for
SER5-specific assumptions (the actual genericity bar), capture the
boot (POST/ok>/rdrand backend/serial transcript), mint+re-attach Zuse
on real hardware, then update this section with results before moving
to aarch64.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:27:38 -04:00
Robert Allan JamesandClaude Sonnet 5 7f9a4d4d4a FABRIC-3.md: three per-architecture bare-metal-boot planning sections
Build / build-amd64-iso (push) Canceled after 0s
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s
Per direct instruction: amd64's real goal is genericity (any x86_64
laptop/desktop/tower/mini, not just the Beelink SER5 reference
machine); aarch64 targets the Raspberry Pi 5 exclusively; riscv64
targets the Milk-V Mars exclusively -- no cross-board genericity
requirement for the latter two, unlike amd64.

Each section starts from what's already true (ROADMAP.md's existing
v2.2.0/v2.4.0/v2.5.0 board-by-board gates, the already-built
thumbdrive/iso-usb Makefile targets, the amd64 RDRAND backend) and
names what's genuinely still unknown rather than assuming -- most
notably whether the Milk-V Mars boots via UEFI (like this project's
QEMU riscv64 target) or via U-Boot+OpenSBI+devicetree, which would
need a different boot entry path, not just different peripheral
addresses.

Doc-only change, no acceptance build needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var
2026-09-04 12:02:45 -04:00
186 changed files with 624060 additions and 483 deletions
+192
View File
@@ -4431,6 +4431,35 @@ stale the way the original carry-forwards did.
scoped, mirroring `blk_subsys_detach_device()`'s own internal lookup, now exposed) exist
specifically so that call site is a small addition when someone picks it up, not a redesign.
**CLOSED 2026-09-05.** Premise checked before building, not trusted from this item's own
text (same discipline that caught the BAM item's overstatement): traced `repl.c`'s USB-attach
sequence and confirmed `capsule_wirebind_try_attach(&usb_blk_dev, ...)` (the regular-identity
WIREBIND path, `HOMEBLOCKS_SIG_OK` only) runs immediately before `blk_subsys_attach_device
(&usb_blk_dev)` on the same pointer — since a WIREBIND-eligible drive is already a real
STFR/v2-formatted home-blocks drive, `blk_format_or_load_disk()` takes the recognized-header
branch, not PROVISIONAL, so `blk_get_device_free_blocks()` returns real numbers by idle-check
time. Premise held. **Built**: `capsule_wirebind.c` now tracks the attached `blkio_dev*`
(`g_wirebind_attached_dev`, set alongside `g_wirebind_attached_vm_id` in `try_attach()`,
cleared in both `EJECT`/`UNCLEAN`); new `capsule_wirebind_overflow_idle_check()`, called once
per idle tick in `repl.c` right alongside `blk_migration_idle_check()` (same cadence), reads
the attached drive's free/total via `blk_get_device_free_blocks()` and, if free space is
below a fixed 10% (`WIREBIND_OVERFLOW_FREE_THRESHOLD_PCT`, "fixed first" per this project's
own standing sequencing), extends the identity's pool with a **one-time**
`blk_firsttouch_claim()` of 8 additional devblocks (`WIREBIND_OVERFLOW_CLAIM_DEVBLOCKS`) on
Artemis's system-resident device. **New `blk_owner_has_claim(owner_fp)`** (`block_subsystem.c`)
answers the debounce question this section's own doc comment on `blk_firsttouch_claim()`
had left as "a separate, not-yet-decided question" — decided on the reboot-survival axis: a
disk scan (same shape as `blk_firsttouch_claim()`'s own scan), not a RAM flag, matching
BMAPFMT's "ownership travels with the block, no centralized table" model, so the
already-extended answer survives reboot/reattach for free. **Verified**: full 3-arch QEMU
acceptance (identical dictionary hashes, no regression) plus a live logic test of
`blk_owner_has_claim()` itself — a temporary `TEST-OWNER-CLAIM` word, run once via `SK_CMD`
and reverted, confirmed it correctly detects the claiming owner and correctly rejects an
unrelated one. **Not verified end-to-end**: the actual low-disk-space-triggers-a-claim path
itself, which needs a real minted WIREBIND-user thumbdrive with deliberately tiny capacity to
reproduce naturally — out of scope for this pass; the plumbing exercises its no-op branch
every idle tick with no crash, but the "drive actually runs low" branch is unexercised.
**Also left open, a real correctness gap, not swept under**: `blk_meta_t`'s `owner_fp`/
`BLK_FLAG_CLAIMED` (BMAPFMT's distributed ownership) and the pre-existing, separate BAM
(`blk_bam_entry_t{allocated,dirty}`, the generic free/allocated bitmap `blk_allocate()`/
@@ -4441,6 +4470,30 @@ stale the way the original carry-forwards did.
Not fixed in this pass; flagged precisely so it isn't rediscovered as a mystery later.
*(Same location as above.)*
**CLOSED 2026-09-05.** Traced against current code before writing anything, per this
document's own discipline: `blk_meta_relocate_devblock()` turned out **not** to be part of
the gap after all — it calls `blk_subsys_relocate_block()` per member block, which already
calls `blk_mark_free()` on the home LBN and `blk_update()` (which sets the BAM bit) on the
target, so relocation was already keeping BAM in sync. The real gap was entirely in
`blk_firsttouch_claim()`'s side (never touched BAM at all) and in the reverse direction
(`devblock_is_free()`/`blk_allocate()` never checked `BLK_FLAG_CLAIMED`), confirmed by a
full-documentation audit ([[project_fabric012_full_audit_20260905]] in memory) before
fixing. **Fix** (`block_subsystem.c`): `devblock_is_free()` (shared by
`blk_firsttouch_claim()` and `blk_migration_idle_check()`) now also checks the BAM entries of
all `BLK_PACK_RATIO` member LBNs, not just `blk_meta_t`; a new `devblock_claimed_by_lbn()`
helper is wired into `blk_allocate()`'s free-scan so it skips any LBN whose devblock is
`BLK_FLAG_CLAIMED`; `blk_firsttouch_claim()` now calls `blk_mark_allocated()` on all 3 member
LBNs of each devblock it claims, which also fixes a quiet side effect where
`vol_meta.free_blocks` never decremented for FIRSTTOUCH claims. Both boundary edge cases
(the reserved/user LBN split at a slot's own `start_lbn`, and BAM-array bounds) are guarded
explicitly rather than assumed safe. **Verified, not just compiled clean**: full 3-arch QEMU
acceptance (identical dictionary hashes, clean `BYE`, no regression) plus a live logic test —
a temporary `TEST-BAM-RECON` word (written, run once via `SK_CMD`, then fully reverted,
per this project's own probe discipline) ordinary-allocated 4 LBNs, `FIRSTTOUCH`-claimed 2
devblocks, and confirmed on real running code that neither direction of the gap reproduces:
the ordinary allocations and the claim landed on disjoint LBN ranges, and `blk_allocate()`
never returned an LBN from the claimed range afterward. `PASS`, no `FAIL` lines.
### I.3 — Milestone 5 remainder (BINDSTEP) — CLOSED 2026-09-04
- [x] **`EJECT` / detach behavior.** Scoped 2026-08-27 (§F.10): flush via
@@ -4638,3 +4691,142 @@ terminal emulator rendering the serial log stream, unrelated to kernel code at a
moment of the first keypress after a fresh boot, ideally with the specific key pressed and
architecture noted, so the byte sequence that actually reached the console can be read back
directly rather than inferred from code review alone.
**Update, 2026-09-05, first pass — SUPERSEDED by the second pass below, kept for the record
because the misread itself is instructive.** QMP's `send-key` command can inject a real key
event into a running QEMU guest headlessly — exactly the capture this item said it needed,
without a human at a physical keyboard. Procedure: boot to `(zuse) ok>`, capture the log tail
(confirming no artifact already present), then `send-key` one printable key (`a`), capture
again, then `send-key` `ret`. On both amd64 (`i8042_pop_scancode()`) and aarch64
(`virtio_input_pop_event()`), the raw log showed `a` followed by `UNKNOWN WORD: 'a'` and a
fresh prompt, which was read as clean — the defect was declared not reproduced, and the
remaining candidates named were QEMU's GTK input layer and the local terminal emulator, both
outside this repository.
**That reading was wrong. The defect was already sitting in those same logs.** Both captures
actually showed the echoed key on a **new, bare `[Hera]` line**, not appended to the still-
visible `(zuse) ok> ` line — e.g. `logs/20260905-012840/amd64/qemu-amd64-20260905-012840.log`'s
tail reads:
```
[Hera] (zuse) ok>
[Hera] a
```
not the expected `[Hera] (zuse) ok> a`. That line break, appearing before any character was
typed, *is* the reported defect — indistinguishable from Enter having already been pressed at
the bare prompt. It reads as "normal" unless you already know the prompt should still be on
that line, which is exactly the trap the first pass fell into. Confirmed visually too, via
QMP `screendump` on aarch64 immediately before and ~3s after a keypress:
`evidence/aarch64/qemu-screenshot-20260905-013835-i9-before-keypress.png` and
`...-i9-after-keypress-phantom-linebreak.png`.
**Root cause, traced to two specific lines, not guessed:**
1. `console_ensure_line_start()` (`hal/console.c:312`) calls `console_putc_inner('\n')` — the
variant that deliberately skips `g_console_tx_count++` (by design, per that function's own
doc comment: "so its format-only newline does not read as 'real output' to the REPL's
prompt re-anchor"). The newline is real (reaches `raw_putc()` and `vt100_putc()`) but
invisible to `console_tx_count()`.
2. `sk_repl_idle()` (`repl.c:178`) calls `console_ensure_line_start()` unconditionally, first
thing, every time it runs — including when it has nothing else to print.
3. `sk_console_readline()`'s idle branch (`repl.c:610`) calls `sk_repl_idle()` once
`SK_IDLE_BEAT_INTERVAL` (~1s at 100Hz, `repl.c:143`) has elapsed, gated only on `n == 0`
(nothing typed yet) — i.e. exactly the state of sitting at a freshly-printed bare prompt.
4. The reanchor check right after (`repl.c:633`, `console_tx_count() != prompt_tx_mark`)
never fires, because step 1's newline didn't move the counter it's watching. The prompt is
never reprinted.
5. `g_line_start` is now 1 (mid-console.c bookkeeping) with no prompt text on the new line.
The next real `console_putc()` — the actual echo, `repl.c:676` — sees `g_line_start` set
and lazily emits `[Hera] ` before the typed character (`console_putc_inner()`,
`console.c:280`). Net visible result: `[Hera] a` alone, exactly as observed.
Because the trigger is simply "one human-scale pause (~1s+) at a bare prompt with nothing
typed yet," this fires on essentially every prompt a person actually looks at before typing —
consistent with Captain Bob's own description ("consistent and reproducible"), not a rare
race. It is not really a "first keypress" bug; it's an idle-beat-vs-prompt-invariant bug that
happens to surface at the first keypress because that's the first character typed after the
silent break already occurred.
**This project's own comment already half-diagnosed this.** `repl.c:598-609`'s comment on the
`n == 0` gate explicitly describes this exact failure mode — an unconditional
`console_ensure_line_start()` "visually snapping the in-progress input line to a fresh (empty)
line -- indistinguishable from Enter having been pressed" — and gates it for `n > 0` (mid-edit,
characters already typed). The gap is that the identical forced break at `n == 0` (nothing
typed yet, sitting at a bare prompt) was treated as harmless, because a bare prompt "isn't
being edited" — but it's exactly where the tx-count exemption then hides the break from the
reanchor logic. The original author saw half of this problem; a fix likely belongs near that
same gate or the tx-count exemption in `console_ensure_line_start()`, not somewhere new.
**Secondary, unverified observation:** the framebuffer VT100 cursor glyph may also render
stale across this same event — `console_fb_draw_cursor()` is called after the prompt is first
printed (`repl.c:576`) and after a real character echoes (`repl.c:677`), but *not* after
`sk_repl_idle()`'s forced newline, so the drawn cursor block plausibly stays visually anchored
at the old end-of-prompt position until the next real keystroke redraws it. Consistent with
the "before" screenshot still showing the cursor glyph at the end of `(zuse) ok> ` even though
the underlying line-start state may have already flipped — but this was not traced through
`vt100.c`'s own cursor-rendering path, so record it as a plausible secondary symptom of the
same root cause, not a separately confirmed one.
**Not fixed — reported per this project's "identify, don't fix unless asked" rule.** The
diagnosis is complete and reproducible on demand (QMP `send-key` + a screenshot, no physical
keyboard needed, on any of the three architectures); whether and how to fix it is a decision
for whoever picks this item up next.
**FIXED, 2026-09-05, explicit go-ahead given ("Yes, fix it").** Deferred-newline mechanism:
`console_ensure_line_start()` (`include/starkernel/console.h`, `src/starkernel/hal/console.c`)
no longer calls `console_putc_inner('\n')` immediately. It now only sets a flag
(`g_pending_line_close`); the newline is realized — for real, and counted by
`g_console_tx_count` like any other output — the next time `console_putc()` actually runs, or
silently discarded via the new `console_cancel_deferred_line_start()` if the caller decides it
has nothing to print after all. `sk_repl_idle()` (`repl.c`) now captures
`tx_before_idle = console_tx_count()` right after its `console_ensure_line_start()` call, and
calls `console_cancel_deferred_line_start()` at both of its exit points if
`console_tx_count() == tx_before_idle` (nothing was actually printed that beat). This means a
beat with nothing to report leaves the console completely untouched — no stray newline, no
phantom blank line — while a beat that does print (xhci attach/detach, block-sync notices)
still gets the correct "close the dangling prompt line first" behavior, now properly counted so
the reanchor logic sees it. The pre-existing `n > 0` mid-edit gate at `sk_console_readline()`
(`repl.c`, originally documented at old lines 598-609) is untouched — it serves an independent
purpose (deferring bottom-half servicing during active typing) and was never the bug.
Verified via the mandatory 3-arch QEMU acceptance boot, all foreground, `clean qemu` each
(amd64 `logs/20260905-015854/`, aarch64 `logs/20260905-020248/`, riscv64
`logs/20260905-020552/`). All three reached `(zuse) ok>` and, per the raw serial log bytes,
echoed the first real input on the *same log line* as the prompt — e.g. aarch64's
`(zuse) ok> 1 1 + .r CR` and riscv64's `(zuse) ok> 1 1 . CR BYE`, not a fresh `[Hera]`-prefixed
line as before the fix — then shut down cleanly via `BYE`. amd64's raw log additionally shows
genuine human backspace-correction (`11^H ^H 1 + .`) still glued to the same prompt line,
confirming the fix holds under real typing, not just a scripted single keystroke. No separate
QMP send-key/screendump re-run was needed — these boots were driven live at the actual QEMU GTK
window.
The secondary, unverified cursor-glyph-staleness observation above was not independently
re-checked — out of scope for this fix, and about to be superseded anyway by a separate
requested change (blinking `|` cursor instead of a static block), tracked as a new item, not
here.
**Follow-on, 2026-09-05, same day: cursor style changed to a blinking `|`.** Captain Bob asked
for a blinking vertical bar instead of the previous static solid-block cursor. Two changes:
1. `vt100_draw_cursor()` (`hal/vt100.c`) now fills a thin vertical bar (`cell_w()/8`, minimum 1
px, full cell height) at the cursor's left edge instead of the whole cell — an I-beam shape
rather than a block. `vt100_erase_cursor()` is unchanged (still clears the whole cell, which
safely covers the narrower bar too).
2. Blinking (`repl.c`): `sk_console_readline()`'s idle branch now toggles the cursor on/off
every `SK_CURSOR_BLINK_INTERVAL` (50 ticks, 500 ms at 100 Hz) via alternating
`console_fb_draw_cursor()`/`console_fb_erase_cursor()` calls, tracked by
`g_cursor_visible`/`g_cursor_blink_tick`. This runs regardless of `n` (blinks whether sitting
at a bare prompt or paused mid-edit, like a real terminal). Every deterministic draw site
(initial prompt, prompt reanchor, backspace, character echo) now calls a new helper,
`sk_cursor_show()`, instead of `console_fb_draw_cursor()` directly — it resets the blink
cycle to "on" and redraws, so typing always shows a solid cursor rather than possibly
landing mid-blink.
Verified via the mandatory 3-arch foreground `clean qemu` boot: amd64 (`logs/20260905-021054/`,
extensive live interactive typing including multi-line `:`/`;` word definitions and error
cases, all prompts stayed correctly attached with no phantom breaks), aarch64
(`logs/20260905-021551/`), riscv64 (`logs/20260905-022324/`) — all three reached `(zuse) ok>`
and shut down cleanly via `BYE`. Blink timing itself isn't independently visible in a serial
log (it's a framebuffer-only effect, gated the same way the previous static cursor was — no-op
outside TTF/graphics mode); the boots above confirm the change doesn't regress the console
otherwise. No separate screenshot capture was done for this follow-on since the earlier §I.9
fix already covers the correctness property that actually mattered (no phantom linebreak) and
this change only touches cursor *rendering*, not console line/newline logic.
+847
View File
@@ -117,3 +117,850 @@ functional rollback.
the prior tag placement was itself part of the same "got ahead of myself" correction, not a
separate decision. No remote tag existed yet (`git ls-remote --tags origin` was empty for
`v2.0.0`), so no destructive remote operation was needed, only a local move-and-push.
5. **Follow-up, same day**: `v2.0.1` (the working branch this and Task 1 happened on) deleted,
local and `origin` — confirmed a strict ancestor of `master`'s new HEAD first, so nothing
was lost. `master` is the repo's only branch from here on.
---
## II. Three architectures, three different hardware scopes
Per direct instruction, 2026-09-04. The real-hardware targets are **not** symmetric across
architectures — each gets its own section below because the actual scope of "done" is
different for each:
- **amd64 — genericity is the goal, not just the SER5.** The Beelink SER5 is the machine in
hand and the development/reference target, but the real requirement is broader: this needs
to boot on *any* x86_64 machine — laptop, desktop, tower, or mini PC — not just one vendor's
quirks. SER5-only success is necessary but not sufficient; anything that works only because
of an SER5-specific assumption (a particular ACPI table shape, a specific UEFI
implementation's quirks) is a bug against this goal, not a deferred nice-to-have.
- **aarch64 — Raspberry Pi 5, and only the Raspberry Pi 5.** No genericity requirement across
aarch64 boards — this is the one and only target for this architecture.
- **riscv64 — Milk-V Mars, and only the Milk-V Mars.** Same as aarch64: one specific board,
not a generic riscv64-SBC goal.
**How to use sections III–V below.** Same discipline as everything else in this series: plan
before building, one section at a time, iterating — not all three architectures in parallel,
and not front-loading a complete plan before any real hardware is in front of us. Each section
starts with what's already true (existing repo infrastructure, already-decided policy) and
what's still genuinely unknown, not assumed.
## III. amd64 — generic x86_64 bare metal (reference hardware: Beelink SER5)
**Already true, not to be re-derived:**
- `ROADMAP.md`'s "Board-by-board hardware rollout" already names this `v2.2.0`'s gate: the
generic GPT/FAT32 thumbdrive image (`make -f Makefile.starkernel ARCH=amd64 thumbdrive`,
already built — `Makefile.starkernel:1018`) flashes to and boots on the real SER5 via its
real UEFI, reaching POST + `ok>`, with the amd64 RDRAND entropy backend
(`src/starkernel/rng/rng.c`, already built and part of `master`) serving live entropy.
- `iso-usb` (`Makefile.starkernel:1060`) is the alternate, novice-friendly path (UEFI
isohybrid ISO for tools like GNOME Disks "Restore Disk Image...") — same underlying image,
different flashing UX.
- `FABRIC-2.md` §I.6's own 8-step physical-boot sequence (build ISO, identify the target
device, flash it, physically boot, decide an observation method, confirm POST, confirm
`ok>`, document) is the closest thing to an existing plan — but it predates the genericity
requirement and was written with no hardware in hand yet.
**Decided in conversation, 2026-09-04:**
- **Observation: HDMI (interactive) + serial (logged transcript), both.** The kernel's own
VT100 framebuffer console (`console.c`/`vt100.c`/`framebuffer.c`) already gives a real
interactive display over HDMI — no new code needed there. Serial capture, if the SER5
exposes a UART header, uses the Raspberry Pi's own GPIO UART as the USB-serial bridge
(already available hardware, not a purchase blocker) — this needs the SER5's own UART pins
physically identified first (not yet confirmed it has an accessible header at all).
- **Genericity is verified by standards-compliance, not a second machine** — no second x86_64
box is available right now. The bar is: nothing in the boot path may depend on an
SER5-specific assumption (a particular ACPI table shape, a specific UEFI implementation's
quirk) — argued by code audit against real UEFI/ACPI standards, not by testing on a second
board, until one becomes available. This is a real constraint on the punch list below (item
6), not a deferred nice-to-have.
- **Secure Boot: already disabled on this SER5.** No signed-loader work needed for this pass —
"Secure Boot disabled in firmware setup" is the supported path, documented as such rather
than built around.
**Punch list, this cadence's actual next steps:**
1. Build the generic thumbdrive image: `make -f Makefile.starkernel ARCH=amd64 thumbdrive`.
2. Flash it to a USB stick (`dd`, per the target's own existing usage message).
3. Physically inspect the SER5 for an exposed UART header/pins; if present, wire the
Raspberry Pi's GPIO UART to it as the serial bridge. If absent, HDMI-only for this pass —
not a blocker, just a scope note for step 7's log.
4. Connect HDMI + keyboard to the SER5.
5. Boot the SER5 from the flashed stick (firmware boot-order menu as needed — Secure Boot
already disabled, confirmed above, so no signing prompt expected).
6. **Code audit pass — DONE, 2026-09-04.** Reviewed `src/starkernel/boot/uefi_loader.c` and
every file in `arch/amd64/` (`arch.c`, `apic.c`, `ioapic.c`, `interrupts.c`, `i8042.c`,
`timer.c`) for anything that assumes SER5-specific hardware rather than standard UEFI/ACPI.
Report only, per this project's own "identify, don't fix unless asked" rule — nothing
below was changed.
**Clean (verified standards-correct, not just "worked in QEMU"):**
- `uefi_loader.c`: COM1 presence is probed (scratch-register readback + bounded THRE spin)
before any code trusts it, so a board with no legacy serial decode — the documented SER5
case — degrades to silently dropping bytes rather than hanging. The ACPI table search
does two explicit passes (2.0 GUID first, 1.0 only as fallback) rather than a single
first-match loop, avoiding a real bug class (silently preferring the RSDT-only 1.0
pointer). `GetMemoryMap`/`ExitBootServices` follows the UEFI "golden path" (nothing
between the two calls) with the required `EFI_INVALID_PARAMETER` retry loop. GOP
framebuffer degrades to `PixelBltOnly` cleanly when no linear framebuffer exists.
- `apic.c`: LAPIC presence, x2APIC-vs-xAPIC mode, and the APIC timer's own tick rate are
all determined at runtime (MSR read, empirical TSC-timed calibration) — no hardcoded
bus-frequency assumption anywhere in the timer path.
- `ioapic.c`: I/O APIC base and GSI base come from the ACPI MADT, not a hardcoded address.
- `timer.c`'s PM_TMR_BLK port: already fixed correctly (`fadt_find_pm_tmr_port()`,
2026-08-11 per this file's own comment) — discovered from the FADT, not assumed from a
single machine type's legacy port.
**Findings (real, not fixed — reported per policy):**
1. **HPET base address hardcoded, same bug class as the already-fixed PM_TMR port.**
`timer.c`'s `HPET_PHYS_BASE 0xFED00000` is never cross-checked against the ACPI `"HPET"`
table (confirmed absent — no `"HPET"` signature lookup exists anywhere in the tree,
unlike the FADT/`"FACP"` walk `fadt_find_pm_tmr_port()` already does and could extend to
reach it with the same RSDP→XSDT→table-by-signature walk). 0xFED00000 is the
near-universal legacy convention, not a guaranteed one — this file's own comment already
documents the *identical* failure mode for the PM Timer port and how it was fixed;
the same reasoning was never applied to HPET.
2. **LAPIC relocation: MADT data already parsed one file over, never plumbed through.**
`apic_init()`'s own doc comment self-flags `boot_info` as unused, "reserved for future
use (e.g., reading the ACPI MADT for the physical LAPIC base address)" — always uses
`LAPIC_DEFAULT_PHYS = 0xFEE00000`. Narrower than "unimplemented": `ioapic.c`'s
`MadtHeader.local_apic_address` field is already read into a parsed struct by
`parse_madt()`, just never passed to `apic.c`. `parse_madt()` also only handles MADT
entry types 1 (I/O APIC) and 2 (Interrupt Source Override) — type 5 (Local APIC Address
Override), the entry that matters on a system that actually relocates it, isn't parsed
at all.
3. **i8042 PS/2 controller poked unconditionally, no presence check.** `i8042_init()` and
`i8042_drain_stale()` read/write ports 0x60/0x64 with no probe of any kind — contrast
with this same codebase's own `raw_serial_init()`, which explicitly probes COM1's
scratch register before trusting it exists. The ACPI FADT carries an IA-PC
boot-architecture flags field that reports 8042 presence; it is never consulted. Same
root gap in `pic_disable()` (`interrupts.c`): unconditionally masks the legacy 8259 PIC
at ports 0x21/0xA1 with no corresponding FADT check.
4. **`arch_cold_reset()` uses the legacy i8042 reset-pulse hack instead of the ACPI Reset
Register.** Confirmed live, not dormant: called directly from `BYE`
(`mama_forth_words.c:1437`, Hera-only cold restart). Pulses port 0x64 with 0xFE — the
pre-ACPI convention that assumes the same i8042 controller finding #3 already flags —
instead of the FADT's `RESET_REG`/`RESET_VALUE` fields, which are the actual
standards-defined generic reset mechanism (confirmed absent from the tree — no
`RESET_REG` reference exists anywhere) and don't depend on i8042 existing at all.
Degrades gracefully (falls through to a `cli;hlt` spin loop, doesn't hang) if the
pulse has no effect — the honest risk here is a silent no-op reset on an i8042-less
board, not a hang.
None of these four are certain to actually bite on the real SER5 — the near-universal
conventions they assume (0xFED00000, 0xFEE00000, i8042-at-0x60/0x64) hold on the large
majority of real PC-compatible x86_64 systems including consumer mini-PCs, and only
finding 4 has a confirmed live call path. Recorded here because the decision above
requires arguing genericity from standards compliance, not from "it booted" — these are
the concrete gaps between what the code assumes and what UEFI/ACPI actually guarantees.
7. Capture the boot: confirm POST reaches the same `1012/0/0` result QEMU shows, confirm
`ok>`/`zuse)ok>`, confirm `rng: backend = rdrand` (live entropy, not the QEMU-only
`virtio-rng` path), save the serial transcript (if wired) the same way `logs/` already
keeps QEMU's.
8. Mint a Zuse identity on a second thumbdrive on the real SER5, confirm it re-attaches —
the same real-hardware round-trip `ROADMAP.md`'s `v2.2.0` gate already names.
9. Update this section with results — pass/fail per step, any SER5-specific or genuinely
generic-UEFI finding either way, before moving to aarch64.
## IV. aarch64 — Raspberry Pi 5
**Already true:** `ROADMAP.md` names this `v2.4.0`'s gate: boots on the real board, aarch64
peripheral-RNG backend live, Zuse mint/attach on real media. The peripheral-RNG backend itself
is **not yet built** — today's `rng_get_bytes()` (`src/starkernel/rng/rng.c`) only has a
`virtio-rng` path, real on QEMU, meaningless on real Pi 5 hardware (no virtio device there).
**Decided in conversation, 2026-09-04:**
- **Observation: HDMI-only for this board's own bring-up.** No second Pi, no dedicated
USB-serial adapter available. The Milk-V Mars could in principle serve as a GPIO-UART
bridge once it arrives (same 40-pin-header shape as the SER5 plan), but using it to observe
the Pi 5 before the Mars has been independently validated itself would be a chicken-and-egg
dependency, not a real plan. Revisit serial capture later if genuinely needed, once at least
one board is proven working — not a blocker for this pass.
- **Both boards (Pi 5, Milk-V Mars) arrive 2026-09-17.** Real runway exists to finish the
design/code work below *before* any hardware is in hand — "plan well before doing," per
direct instruction.
**Still genuinely open, not yet decided:**
- **A pinned GPIO VM, theory-stage** — see `FABRIC-4.md` §2. Raised in conversation, not yet
scoped; downstream of §IV.1's own native-boot-flow work (a GPIO VM needs GPIO addresses
from the DTB the same way the rest of this bring-up does).
### IV.1 — Boot-chain decision: UEFI vs. native, researched 2026-09-04
**Researched, not assumed** (web search, current as of this session):
**UEFI option investigated and found weak.** A real UEFI+ACPI firmware for Pi 5 exists —
[`rpi5-uefi`](https://github.com/worproject/rpi5-uefi) (TF-A + EDK2, SBBR-compliant). But:
it's **archived as of 2025-02-04**, support ended because newer Pi EEPROM firmware broke
compatibility with it; its own README says ACPI support is "under development and limited to
a few devices"; RP1 Ethernet/GPIO/PWM/EEPROM don't work under it. This kernel's whole
aarch64 boot path (`boot/uefi_loader.c`, `BootInfo->acpi_table`) assumes UEFI+ACPI the same
way amd64 and the QEMU aarch64 target do — but that assumption may not hold on a real,
current-firmware Pi 5 at all.
**Native boot flow — the real alternative, researched concretely:**
- Boot partition needs `bcm2712-rpi-5-b.dtb`, `config.txt`, and the kernel image itself —
Pi 5 firmware defaults to loading `kernel_2712.img`, falling back to `kernel8.img` if that's
absent.
- `config.txt` needs `os_check=0` for a non-Linux image, or the firmware assumes Linux and
loads from `0x200000` instead of the classic Pi bare-metal load address `0x80000`.
- Entry protocol: `x0` = 32-bit DTB pointer (upper 32 bits of the 64-bit register
unspecified — must mask before use), `x1`–`x3` reserved/zero. **No UEFI PE loader, no ACPI
at all** — a completely different entry shape from `boot/uefi_loader.c`.
- Framebuffer: the VideoCore **mailbox property interface** (channel 8) — a real, different
mechanism from UEFI GOP, no precedent anywhere in this codebase today.
**Decision, per direct instruction 2026-09-04: native boot flow.** Not UEFI. The archived,
partially-working UEFI project is too fragile a foundation to build a real-hardware release
on top of.
**What this actually means for the codebase, named honestly rather than estimated small:**
- A **new, non-UEFI entry path** for aarch64 real hardware — this kernel's boot sequence
currently assumes `uefi_loader.c`'s PE-loader shape unconditionally on aarch64; a Pi 5
native boot needs its own entry point (linked at `0x80000`, receiving `x0` = DTB pointer
directly, no `BootInfo` from UEFI at all).
- A **DTB-driven `BootInfo` equivalent** replacing ACPI-sourced data for this path — memory
map, peripheral addresses (UART, etc.) all come from the devicetree instead.
- **One real, genuine piece of reusable groundwork**: `starkernel/hal/fdt.c`/`fdt.h`, the
minimal FDT reader already built for riscv64's `timebase-frequency` lookup
(`arch/riscv64/timer.c`), is directly extensible for this — parsing `bcm2712-rpi-5-b.dtb`
for peripheral addresses is the same kind of lookup, not a new mechanism.
- A **new mailbox-property-interface framebuffer driver** — genuinely new code, no existing
precedent in this codebase, needed before the VT100 console framework
(`console.c`/`vt100.c`/`framebuffer.c`) has anything to draw onto for this board.
- This is a real architectural fork for aarch64, not a small per-board addition — QEMU
aarch64 keeps its existing UEFI+ACPI path unchanged; Pi 5 real hardware gets a second,
parallel entry path. **Not yet scoped into a punch list** — that's the next step, once this
fork's own shape (how much of `kernel_main.c`'s post-entry sequence can stay shared between
the two paths vs. needs its own branch) is thought through.
### IV.2 — Peripheral RNG: unresolved, not just under-researched
`ROADMAP.md` names an "aarch64 peripheral-RNG backend" as part of `v2.4.0`'s gate. Researched
directly rather than assumed still-TODO: Broadcom's `iproc-rng200` block (real, on Pi 4/BCM2711
as `brcm,bcm2711-rng200`) has **no `bcm2712` compatible-string entry anywhere in current
mainline Linux** (checked the actual driver's `of_device_id` table directly). The RP1
companion chip's own published peripheral list (GPIO/USB/Ethernet/DMA/ADC/PLLs/SRAM/
UARTs/SPIs) doesn't mention an RNG either. Two real possibilities, not yet distinguished:
BCM2712 still has the RNG200 block but Linux hasn't wired it into a devicetree binding yet, or
it genuinely isn't exposed to the ARM cores this generation. No public register address exists
to target right now — this needs either a Broadcom datasheet (if one becomes available) or
direct hardware probing once the board is in hand (scan the known BCM2711 RNG200 offset region
on the BCM2712 memory map and see if anything responds — risky without a datasheet confirming
it's safe to touch, so likely a "board in hand, careful probe" task, not a today task).
Deliberately **not a blocker for the first native boot** — reaching `ok>` doesn't require a
live entropy backend; `rng_get_bytes()` already has a "no entropy backend available" WARNING
path (`rng.c`) rather than a hard failure, so this can land after boot succeeds.
### IV.3 — Punch list: design/code work, no hardware needed (before 2026-09-17)
Traced against real code before writing this, not estimated: `boot_info->acpi_table`'s only
aarch64-relevant consumers today are `pci_init()` (`kernel_main.c:589`, unconditional, not
amd64-gated — relevant because RP1 is PCIe-attached on real Pi 5 hardware) and this session's
own `running_under_hypervisor()` (`arch/aarch64/timer.c`, already degrades safely to "not a
hypervisor" when `acpi_table` is `NULL` — no fix needed there).
`ioapic_init()`/`i8042_init()` are `#ifdef ARCH_AMD64`-gated, irrelevant here.
`arch/aarch64/apic.c` (GIC init) already only ever tries `boot_info->dtb`, never
`acpi_table` — its own doc comment already anticipated DTB-based discovery, just blocked
until now because QEMU's own UEFI firmware never publishes one; Pi 5 native boot removes that
blocker for free.
1. **Entry stub — DONE 2026-09-04.** New
`src/starkernel/arch/aarch64/native_rpi5_entry.S` / `include/starkernel/rpi5_native_entry.h`:
`rpi5_native_start` masks `x0` down to the documented 32-bit DTB-pointer range (§IV.1: the
firmware leaves the upper 32 bits of the register unspecified), stores it into
`g_rpi5_dtb_ptr` for item 2's still-open constructor to read, then switches `sp` to a
dedicated 2 MiB BSS stack (this path has no EDK2 boot stack to inherit — there is no EDK2 at
all here, unlike every other entry path this codebase has). Intentionally halts (`wfe`/`b`
loop) afterward rather than tail-calling into item 2's constructor, which doesn't exist yet.
**Not yet linked at `0x80000`** — that needs its own linker script/build target (item 6's own
`config.txt` work is the sibling piece; the separate-image build itself is not scoped into
this item). Now tail-calls item 2's constructor (below) instead of halting — updated
2026-09-04 when that item landed. Verified 3-arch boot to `ok>`/`zuse)ok>` —
`Makefile.starkernel`'s `KERNEL_ASM` wildcards every `*.S` in `arch/aarch64/`, so this file
compiles and links into the existing QEMU/UEFI acceptance build as dead code (unreferenced
symbol, nothing there ever branches to it), same as `rpi5_dtb.c`/`rpi5_mailbox.c` before it.
2. **DTB → `BootInfo` constructor — DONE 2026-09-04.** New
`include/starkernel/rpi5_native_boot.h` / `src/starkernel/arch/aarch64/rpi5_native_boot.c`:
`rpi5_native_boot()` populates the *existing* `BootInfo` struct from the devicetree instead
of UEFI protocols (`dtb` = the real pointer, `acpi_table`/`runtime_services` = `NULL`,
`kernel_stack_base` = `NULL`/BSS-fallback — aarch64 has no `kernel_entry.S` trampoline at
all, so item 1's own BSS stack already *is* the stack `kernel_main_impl` runs on),
`args` via `/chosen`'s `bootargs` fed straight into the *existing*
`cmdline_parse_ascii()` (pure C99, no UEFI coupling — confirmed before reusing it, not
assumed), `framebuffer` via item 3's `rpi5_mailbox_get_framebuffer()` at a fixed
1920x1080x32 default (no EDID query exists in this codebase — flagged, not guessed past
this comment, revisit once real hardware and a real attached display are in hand), then
calls the **existing, unmodified** `kernel_main()` — this is the crux of why most of M1–M9
stays shared. `memory_map` comes from `/memory`'s own `reg`, honoring the *root* node's
`#address-cells`/`#size-cells` (confirmed against `bcm2712.dtsi`'s actual root node — `<2>`/
`<2>` — not assumed; a hardcoded-wrong cell width here would compile clean and boot clean in
QEMU while silently corrupting the real memory map on real silicon, so this was verified
from the source rather than recalled). Required a new `fdt_find_node_by_device_type()`
(`fdt.c`/`fdt.h`) since `/memory` is identified by `device_type = "memory"` per DT spec
§3.4, not `compatible`. Required a Makefile fix: `boot/cmdline.c` was only in `LOADER_SRCS_BASE`
(the `.efi` target), not `KERNEL_SRCS_BASE` (the separate `.elf` target `arch/aarch64/*.c`
also wildcards into) — added it there too, a real link failure caught before it could ship.
**`/reserved-memory` carving — DONE 2026-09-04.** Originally deferred here as needing
interval-splitting logic written blind against hardware not yet in hand — revisited once an
actual reservation was confirmed to exist rather than assumed either way: fetched
`bcm2712-ds.dtsi` directly and found a real `reserved-memory` node with one static child
(`atf@0`, `reg`-addressed, ARM Trusted Firmware's own region) and one dynamic child
(`linux,cma`, `size`/`alloc-ranges` only, no fixed address — skipped, nothing fixed to carve
and no allocator this early to service it against anyway). `collect_reserved_ranges()`
walks `reserved-memory`'s children via two new `fdt.c` primitives —
`fdt_find_node_by_name()` (needed since `/reserved-memory` has neither `compatible` nor
`device_type` per DT spec §3.5.4) and `fdt_next_child_node()` (one exported symbol, not the
two-primitive general sibling-walker originally sketched — collapsed after review, since
this codebase's only real use is "iterate one node's direct children," not general tree
navigation) — reading each child's own `#address-cells`/`#size-cells` with a fallback to
root's only if absent (confirmed necessary, not just defensive:
`reserved-memory`'s own declared `<2>`/`<1>` genuinely differs from root's `<2>`/`<2>`).
`emit_region_with_carveouts()` clips a sorted reserved-range list against each RAM region,
emitting alternating `EfiConventionalMemory` gaps and `EfiReservedMemoryType` carve-outs
(insertion sort, not `qsort` — freestanding, no libc). `no-map`/`reusable` flags are not
distinguished; every static reservation is excluded from `EfiConventionalMemory` regardless.
`RPI5_MAX_MEMMAP_ENTRIES` is the exact worst-case count
(`RAM_REGIONS * (2*RESERVED_RANGES + 1)`), recomputed rather than estimated — the
`rpi5_mailbox.c` buffer-size bug is the standing lesson for this pattern. Verified 3-arch
boot to `ok>`/`zuse)ok>` — compile-only, same caveat as every item in this list: nothing in
the existing UEFI/QEMU path calls `rpi5_native_boot()`, so this cannot be exercised until
real hardware.
3. **Mailbox-property-interface framebuffer driver — DONE 2026-09-04.** New
`include/starkernel/rpi5_mailbox.h` / `src/starkernel/arch/aarch64/rpi5_mailbox.c`:
`rpi5_mailbox_get_framebuffer()` builds and sends one property-tag buffer (phys size, virt
size, depth, pixel order, virtual offset, allocate-buffer, get-pitch), populating an
`Rpi5FramebufferInfo` kept in exact field-for-field sync with `uefi.h`'s `FramebufferInfo`
so `console.c`/`vt100.c`/`framebuffer.c` need no changes downstream. Register layout
(`+0x00`/`+0x18` MBOX0 read/status, `+0x20`/`+0x38` MBOX1 write/status) confirmed against a
Pi-5-specific bare-metal reference (main.lv), independently cross-checked against this
codebase's own `rpi5_dtb.c` translated base address — two independent sources agreeing. A
real buffer-overflow bug was found and fixed before compiling (the static request buffer
was sized 32 words against an actual 35-word requirement, recomputed exactly rather than
re-estimated; resized to 40 words for margin). **Two things flagged, not guessed, as
genuinely unverified against real hardware:** the `TAG_ALLOCATE_BUFFER` tag's request-size
field value (set to the response size, matching common practice across surveyed reference
implementations, not a single spec-quoted number); and whether the allocate-buffer
response address needs the classic `& 0x3FFFFFFF` bus-alias masking on Pi 5 specifically —
kept defensively even though the same Pi-5-specific send-side reference found no bus-alias
bit in play there. Verified 3-arch boot to `ok>`/`zuse)ok>` (compile-only — no caller yet;
that's the entry-stub/DTB-constructor items above, still open).
4. **`fdt.c`/`fdt.h` extension — DONE 2026-09-04.** Added `fdt_find_node_by_compatible()`
(matches any entry in a node's NUL-separated `compatible` list, first match in document
order) and `fdt_find_prop_in_node()` (scoped to that one node's own direct properties only
— stops at the first child node or the node's own end, never descends or continues into a
sibling). Same minimal, non-tree-building style as the existing reader — no new state, no
allocation, one linear scan per call. Verified 3-arch boot to `ok>`.
**4a. UART + mailbox address lookup — DONE 2026-09-04.** New
`include/starkernel/rpi5_dtb.h` / `src/starkernel/arch/aarch64/rpi5_dtb.c`:
`rpi5_uart_base()`/`rpi5_mailbox_base()`, each `fdt_find_node_by_compatible()` (`"arm,pl011"`
/ `"brcm,bcm2835-mbox"`) → `fdt_find_prop_in_node(..., "reg", ...)`. **A real translation
gap found and fixed before this could have been silently wrong**: confirmed directly
against `bcm2712.dtsi` (raspberrypi/linux) that both peripherals live under one `soc`
simple-bus node whose `ranges` property adds a fixed `0x10_0000_0000` offset to every
child `reg` value — `fdt.c`'s reader deliberately does not apply `ranges` translation
generally (not a general devicetree library), so this file applies that one, fixed,
SoC-wide offset explicitly by name (`BCM2712_SOC_RANGES_OFFSET`), documented with the exact
devicetree excerpt that confirmed it. Verified 3-arch boot to `ok>` (compile-only — these
two functions have no caller yet; that's the entry-stub/framebuffer-driver items above,
still open).
5. **`pci_init()` DTB path — RE-SCOPED 2026-09-04, this item's own original text was wrong.**
Investigated before writing any code (per this document's own discipline) rather than
building the originally-assumed "swap the ACPI MCFG lookup for a DTB one, same flat-ECAM
access underneath" version — that assumption does not hold, confirmed against three primary
sources, not guessed:
- `bcm2712-rpi-5-b.dts` (real board file): RP1 is attached under `pcie2`
(`pcie2: pcie@1000120000`, `compatible = "brcm,bcm2712-pcie"`) via
`rp1_target: &pcie2 { status = "okay"; };`.
- `pcie2`'s own `reg` is `<0x10 0x00120000 0x00 0x9310>` — a ~37 KB window, far too small to
be a flat 256 MB ECAM region (256 buses × 1 MB) the way `pci.c`'s existing `ecam_*`
functions assume.
- The real Linux driver for `"brcm,bcm2712-pcie"` (`drivers/pci/controller/pcie-brcmstb.c`)
confirms why: `brcm_pcie_map_bus()` computes a standard ECAM-shaped offset
(`PCIE_ECAM_OFFSET(bus, devfn, 0)`) but does **not** read/write it directly — it writes
that offset to an `IDX_ADDR` index register, then accesses the config data through a small
windowed `DATA_ADDR` region. Root-complex-local accesses (`devfn == 0` on the root bus)
skip the indirection and hit `base + PCIE_ECAM_REG(where)` directly; every downstream
device access (RP1 included) goes through the index/data window.
- This is a real **third config-access mechanism**, not a bigger version of "find the ECAM
base in the DTB instead of ACPI." `pci.c`'s `cfg_read32`/`cfg_write32` family
(`pci.c:188–232`) today dispatches exactly two ways, selected by `#ifdef ARCH_AMD64` vs.
everything else (flat `ecam_*`) — and that `#ifdef` selection is itself now wrong for this
case: QEMU aarch64 (flat ECAM via ACPI MCFG, real and working) and Pi 5 aarch64
(Broadcom indirect windowing) are the **same build** (`ARCH=aarch64`), so the dispatch
must become a runtime choice, not a compile-time one — a real change to a shared file all
three architectures currently boot clean through, not a small addition.
- **Secondary finding, corrected 2026-09-05 — not a current blocker.** This item originally
claimed native boot's lack of a UEFI identity map means "**any** PCIe work on this path...
needs its own explicit `vmm_map_range()` call." Traced against actual source rather than
assumed: `arch_mmu_init()` is a stub on both non-amd64 ISAs (aarch64 and riscv64 alike,
`arch/{aarch64,riscv64}/arch.c`), `vmm.c`'s own `load_cr3()` is a no-op outside
`__x86_64__`, and — confirmed directly by this session's own riscv64 satp fix (§V.3
item 7) — `satp` is explicitly left at Bare mode on that ISA. **No non-amd64 ISA ever
turns its own MMU on today**, native or QEMU/UEFI boot alike; with translation off,
VA==PA everywhere and there is nothing for a mapping call to do. The concern is real but
conditional — it becomes a genuine prerequisite only once a future milestone actually
brings up aarch64/riscv64 paging (`arch_mmu_init()`'s own stub comment: "deferred to a
later milestone"), not before.
- **Also checked while re-scoping, not previously stated: no silent-wrong-answer risk
today.** With no ACPI table on the native Pi 5 boot path, `pci_init()`'s existing MCFG
parse fails cleanly (`rc != 0`) and aarch64 takes no ECAM fallback (unlike riscv64's own
QEMU-constant fallback) — it already prints `"PCI: MCFG parse failed on aarch64 — no
ECAM"` and stops, rather than misreading RP1's 37 KB indirect-window `reg` as if it were
flat 256 MB ECAM. No diagnostic gap to close here.
- **Still not implemented, and the runtime-dispatch prerequisite re-examined 2026-09-05 —
not separable from the Broadcom mode itself.** Considered building the "make dispatch
runtime, not compile-time" half alone (reusing the DTB node-scoped lookup primitive that
closed the aarch64 GIC base and riscv64 PLIC base findings, §IV.3/§V.3 item 7 and item 3)
— rejected on inspection, not scope: `portio_read32()` and its siblings use `outl`/`inl`
inline asm that cannot exist in an aarch64 or riscv64 translation unit at all, so any
`case PCI_CFG_MODE_PORTIO:` referencing them must itself stay `#ifdef ARCH_AMD64`-gated.
That leaves exactly one reachable case per architecture's build either way — an enum
wearing the same compile-time selection, fixing nothing. The dispatch can only become
genuinely runtime once a second real non-amd64 mechanism is actually compiled in, which
means this "prerequisite" and the Broadcom mode below are one change, not two sequenceable
ones.
- **Broadcom indirect access: not implemented, reason is unverifiability and incompleteness,
not size.** No QEMU model exists for `"brcm,bcm2712-pcie"` — no boot on this system can
execute a new branch built for it, so unlike the GIC/PLIC DTB-discovery fixes (guard
exercised, success branch pending hardware) this would be *zero* branches ever exercised
before 2026-09-17. Separately, config-space access alone is not sufficient for RP1 to
enumerate: `pcie-brcmstb.c`'s real driver does controller bring-up (link training, PERST,
inbound/outbound window setup) before `brcm_pcie_map_bus()` returns anything but
`0xFFFFFFFF` — implementing only the `IDX_ADDR`/`DATA_ADDR` index-window mechanism would
compile and boot cleanly while silently never working, exactly the half-finished
implementation this project's own conventions rule out. Recording the real shape remains
this pass's deliverable; building it needs real Pi 5 + RP1 hardware to verify against, not
available until 2026-09-17.
6. **`config.txt` contents — DONE, 2026-09-04.** Written to `boot_media/rpi5/config.txt`
(new directory — `configs/` is Kconfig defconfigs, `img/` is banners/docs, neither fits).
Researched against the official current `config.txt` reference
(raspberrypi.com/documentation/computers/config_txt.html) and one real, working Pi 5
bare-metal project's own checked-in `config.txt` (`leopoldch/BatMetal`), not assumed from
general Pi knowledge. Final contents, with the reasoning as comments in the file itself:
- `kernel=kernel_2712.img` — the real Pi-5-specific default filename firmware looks for
first (falls back to `kernel8.img` if absent); naming our image this exactly removes
ambiguity, so the item's own `kernel8.img`/`os_check=0` fallback alternative wasn't
needed.
- `os_check=0` — official docs name "bare-metal development" as the explicit use case for
this flag. Kept despite BatMetal's own config.txt *not* setting it and still booting —
recorded as an open discrepancy rather than silently resolved; no hardware in hand yet to
confirm which behavior is actually gated.
- `device_tree=bcm2712-rpi-5-b.dtb` — pins the DTB explicitly rather than relying on
board-revision auto-selection (confirmed via WebSearch of `config.txt` syntax docs).
- `enable_uart=1` — **correction to this item's own original text, which didn't have
this.** Added per review: costs nothing when nothing is listening, and is the only
diagnostic channel that survives a framebuffer failure on the first real boot (the
mailbox framebuffer path, `rpi5_native_boot.c`, has two documented-unverified details
and has never run on silicon). Enables the Pi 5's dedicated JST debug UART, not the
GPIO 14/15 header (`dtoverlay=uart0`, deliberately left out — needs a USB-serial
adapter this HDMI-only bring-up doesn't have). `rpi5_uart_base()` already exists
and is currently uncalled — this is the hook a UART fallback console would use.
- `arm_64bit=1` — **dropped, correction to this item's own original text.** Official
docs: "Models that only support a 64-bit kernel ignore this flag" — confirmed inert on
Pi 5, not wrong to include, just meaningless.
- `dtparam=pciex1` — researched (BatMetal's own comment: "Required for RP1 access"), not
included. Community-reported only, not confirmed against official docs, and surprising
if true (RP1 is normally always-on). Not needed yet — item 5's real RP1/PCIe work is
still re-scoped, not implemented. Revisit if that work needs it.
- `boot_media/rpi5/README.md` also added, noting the one remaining gap this item's
`config.txt` exposes but doesn't itself close: `kernel_2712.img` names a file nothing
currently builds — item 1's separate-image build target (linking at `0x80000`) is still
outstanding future work, same gap that item's own text already flagged.
7. **Code audit pass — DONE, 2026-09-04.** Reviewed `arch/aarch64/apic.c`, `arch.c`,
`timer.c`, `interrupts.c` for the same class of QEMU-virt-vs-real-hardware assumption
§III item 6's amd64 audit looked for. Report-only by default, per this project's own
"identify, don't fix unless asked" rule — **all three findings below were fixed in code
across two follow-up rounds, each named specifically by direct instruction after this
audit landed**: the GIC base address (severe), the stale VBAR_EL2 doc comment (doc-only),
and the hardcoded HVC PSCI conduit. §V.3's own parallel audit found a fourth finding of
this same class (riscv64's `satp`-clear reasoning, lowest severity of the whole set) that
remains report-only, not asked for. See each finding's own
own text for what changed.
**Finding — severe, confirmed live, blocked reaching `ok>` on real hardware as the code
stood — FIXED 2026-09-04, per direct instruction (this item's own findings are normally
report-only; this one was explicitly asked for by name).** `apic.c`'s own file header
already self-documented `GICD_BASE_PA`/`GICC_BASE_PA` (`0x08000000`/`0x08010000`) as
"QEMU-virt-machine constants... a deliberate, recorded exception," reasoned correctly at
the time it was written: no DTB was ever available to discover them from, because QEMU's
own aarch64 UEFI firmware doesn't forward one. That premise no longer held — the native
boot path (item 2, DONE) receives a real DTB directly and calls the same, unmodified
`kernel_main()` M4 sequence, which calls `apic_init(boot_info)` unconditionally
(confirmed: `kernel_main.c:413`, no arch- or boot-path gating) — `apic_init()` itself
ignored `boot_info` entirely and always programmed the QEMU addresses. The real BCM2712
GIC-400 is at `0x10_7fff9000` (distributor) / `0x10_7fffa000` (CPU interface 0) —
confirmed directly against `bcm2712.dtsi`'s own `axi`/`gicv2` nodes
(`compatible = "arm,gic-400"`, `#address-cells = <2>`/`#size-cells = <2>`, `axi`'s own
`ranges` identity-mapping this region — no offset needed, unlike `rpi5_dtb.c`'s
1-cell/offset `soc` peripherals), not recalled.
**Fix**: `apic.c` now tries `gic_bases_from_dtb()` first — `fdt_valid(dtb)` →
`fdt_find_node_by_compatible(dtb, "arm,gic-400")` → `fdt_find_prop_in_node(..., "reg",
...)`, reading the first two 2-address-cell/2-size-cell entries (GICD, then GICC — the
standard arm,gic-400 binding order) — and falls back to the QEMU constants only when no
DTB or no matching node is found, so the existing QEMU/UEFI path's behaviour (verified via
the mandatory 3-arch boot) is unchanged. `GICD_BASE_PA`/`GICC_BASE_PA` became
`s_gicd_base`/`s_gicc_base` (module-static `uintptr_t`, not `#define`s, since they're no
longer compile-time constants on this path) — every MMIO call site (`apic_init()`,
`apic_spi_enable()`, `apic_read_iar()`, `apic_eoi_intid()`) now reads through them.
The 3-arch acceptance run exercised the *guard*, not just compiled it: `apic_init()`'s
`gic_bases_from_dtb()` call runs unconditionally on every aarch64 boot, including this
one, and its `fdt_valid()` check correctly returned 0 on this system's QEMU/UEFI firmware
— confirmed via the boot log's own `GICv2: no DTB GIC node -- using QEMU virt-machine
defaults` line, followed by the unchanged `distributor+CPU interface enabled, PPI 30`.
What remains genuinely unexercised is the *success* branch (a real DTB with a matching
`"arm,gic-400"` node) — that needs a devicetree this build never has, so it stays
unverified until real Pi 5 hardware, same caveat as every other native-path item in this
list.
**Finding — doc-only, not a functional gap — FIXED (comment corrected) 2026-09-04, per
direct instruction naming it specifically.** `arch_interrupts_init()`'s own doc comment
in `interrupts.c` claimed "`VBAR_EL1` is written unconditionally regardless of the
detected level... if EL2, this is a known gap" — checked against `isr.S`
(`aarch64_install_vectors`, lines 124–146) and found **stale**: the actual implementation
already branches on `aarch64_current_el()` and writes `vbar_el2`/`vbar_el1` correctly,
with `el2_mode_flag` threading the same answer through the IRQ trampoline for
`ELR_EL1`/`SPSR_EL1` vs `ELR_EL2`/`SPSR_EL2` selection. Rewrote the doc comment and the
matching runtime console message (which made the identical stale claim — "not yet
wired, see item 0.5/0.7") to describe what `isr.S` actually does, and fixed a
same-vintage one-word staleness in `arch.c`'s own `aarch64_install_vectors` extern
comment ("installs `VBAR_EL1`" → EL-aware) caught while touching this. No behavior
changed anywhere — this was purely three comments correcting themselves to match code
that was already right. Which EL the Pi 5's ATF hands the kernel off at under the native
path is itself still unconfirmed (the `atf@0` reserved-memory region found during item
2's work only establishes that EL3 firmware exists, not which EL the kernel lands at) —
moot for this specific fix now that both paths are correctly wired regardless of which EL
is chosen, but worth knowing for other EL-dependent code.
**Finding — same defect class as amd64's `arch_cold_reset()` finding, on the QEMU side of
this arch specifically — FIXED 2026-09-04, per direct instruction naming it
specifically.** `arch/aarch64/arch.c`'s `arch_cold_reset()` issued PSCI `SYSTEM_RESET` via
`HVC` unconditionally — a fix already documented in-file as a QEMU-specific workaround,
because QEMU's AAVMF firmware has no genuine EL3/TrustZone secure monitor to answer an
`SMC`. The Pi 5's real ATF (confirmed present via the `atf@0` reservation) is exactly the
kind of genuine EL3 firmware PSCI's `SMC` conduit assumes — confirmed further this pass:
`bcm2712.dtsi`'s own `/psci` node declares `method = "smc"` directly, not inferred from
ATF's mere presence. **Fix**: new `aarch64_psci_conduit_init(dtb)` (`arch.c`) —
`fdt_valid(dtb)` → `fdt_find_node_by_compatible(dtb, "arm,psci-1.0")` →
`fdt_find_prop_in_node(..., "method", ...)` — sets a module-static `s_psci_use_smc` flag
to 1 only when the DTB's own `method` property reads exactly `"smc"`; every other
outcome (no DTB, no PSCI node, `method="hvc"`, property absent) leaves it at its default
0, preserving the exact HVC behaviour this system's QEMU boot already relies on.
`arch_cold_reset()` now branches on that flag between the `smc #0`/`hvc #0` trap
instructions; the function ID and calling convention are identical either way, matching
the file's own existing comment about that. Called once from `apic_init()`
(`arch/aarch64/apic.c`), the one point in boot with `boot_info->dtb` already in hand —
collocated in `arch.c`, not `apic.c`, because `arch_cold_reset()` is the actual consumer
and has no `boot_info` of its own by the time it runs (called from deep in VM execution,
via `BYE`). Riscv64's equivalent (`arch/riscv64/arch.c`, SBI SRST) remains the one cold-
reset implementation across all three architectures that never needed this kind of fix —
still worth naming as the standard the other two moved toward, one now fixed, amd64's
i8042-pulse version still open. 3-arch acceptance exercised the guard (this system's QEMU
AAVMF forwards no DTB, so `aarch64_psci_conduit_init()` hits its own "no DTB" branch and
`arch_cold_reset()` keeps using HVC, unchanged) but not the `SMC` success branch itself —
that stays unverified until real Pi 5 hardware runs `BYE`.
**Hardware-dependent, after 2026-09-17 (not started until then):**
8. Build the boot media (SD card: `config.txt`, `bcm2712-rpi-5-b.dtb`, kernel image).
9. Connect HDMI + keyboard (observation decision above).
10. Boot; confirm `ok>`/`zuse)ok>` reached.
11. Mint a Zuse identity on real media, confirm re-attach — the `v2.4.0` gate's own
requirement, same shape as amd64's.
12. Update this section with results before moving to riscv64's own hardware-dependent steps.
## V. riscv64 — Milk-V Mars
**Already true:** `ROADMAP.md` names this (generically, "Milk-V") as part of `v2.5.0`'s gate:
boots on the real board, the Zkr (RNDR) entropy backend live. Same gap as aarch64:
`rng_get_bytes()` has no riscv64 hardware-RNG path today, only `virtio-rng`.
### V.1 — Boot chain: resolved, researched 2026-09-04
**Resolved, not left open.** The Mars is a documented mainline U-Boot board target in its own
right ([U-Boot docs — Milk-V Mars](https://docs.u-boot.org/en/latest/board/starfive/milk-v_mars.html)),
and it uses **the exact same U-Boot binaries as the StarFive VisionFive 2** — same SoC
(StarFive JH7110), board identity detected at SPL time, devicetree patched accordingly, no
separate Mars-specific firmware. This directly answers §V's own previously-open question:
**U-Boot + OpenSBI + devicetree, not UEFI** — same fork this kernel already decided for
aarch64 (§IV.1), now confirmed for riscv64 too.
**Boot chain, concretely:**
1. BootROM (ZSBL), StarFive's on-chip loader at `0x2A000000`, selects boot media by GPIO pins.
2. U-Boot SPL (FSBL) — initializes DRAM, configures PLLs.
3. OpenSBI (`fw_dynamic.bin`) — M-mode runtime services.
4. U-Boot main, S-mode, depends on OpenSBI.
5. Boot media: QSPI flash (recommended) or UART XMODEM (recovery). SD/eMMC boot modes are
deprecated in current U-Boot.
**Entry protocol, from real VisionFive 2 bare-metal work (same SoC, directly applicable per
§VI's own cross-reference):**
- Entry point `0x40000000`.
- Core identification via the `mhartid` CSR — the SiFive S7 monitor core is hart 0, the four
U74 application cores are harts 1–4 (matches the QEMU riscv64 target's own hart numbering
convention already assumed elsewhere in this codebase — worth double-checking, not
assuming, once real hardware is in hand).
- UART at `0x10000000`, 115200 baud, already initialized by firmware before handoff.
- Custom bare-metal images package via `vf2-imager` (invokes U-Boot's `mkimage`) into a FIT
image — same tooling should apply to the Mars, unconfirmed until tried.
- **Not yet found**: what registers carry the DTB pointer/hart ID at the actual kernel entry
point under this specific chain (the source consulted covered the image-packaging tooling,
not the OpenSBI→kernel handoff register convention). **Resolved 2026-09-04**: standard
RISC-V SBI boot protocol, confirmed via OpenSBI's own docs — `a0`=hart ID, `a1`=DTB pointer,
S-mode entry. Not chain-specific guesswork; this is the universal convention OpenSBI's
`FW_DYNAMIC` firmware type uses regardless of vendor, so it applies to this chain directly.
**What this means for the codebase** — same shape of fork as aarch64 (§IV.1): a non-UEFI
entry path, a DTB-driven `BootInfo` equivalent (the existing `starkernel/hal/fdt.c` reader
extends here too, same as for the Pi 5), no ACPI.
**Decided in conversation, 2026-09-04: observation is HDMI-only**, same reasoning and same
constraint as the Pi 5 (§IV) — no bridge hardware available for this board's own first
bring-up either; the Mars has its own HDMI 2.0 output (§VI).
### V.2 — Peripheral RNG and Zkr: still genuinely open
- Zkr/RNDR instruction availability on the Mars's actual CPU (riscv64 Scalar Crypto extension
support varies by implementation) — not yet confirmed; the VisionFive 2 bare-metal research
above didn't surface this either, would need its own targeted look (or a real-hardware
probe of `misa`/the Zkr extension discovery mechanism). Deliberately **not a blocker for
first boot**, same reasoning as §IV.2's aarch64 RNG gap — `rng_get_bytes()` already
WARNs rather than hard-fails with no backend.
- **Whether the Mars needs the same pinned-GPIO-VM treatment as the Pi 5** — explicitly
**not decided either way**, per direct instruction ("same for Milk-V (? not sure here)").
See `FABRIC-4.md` §2. The Mars does have its own 40-pin GPIO header (§VI), so the open
question is the VM architecture around it, not whether the hardware exists.
### V.3 — Punch list: design/code work, no hardware needed (before 2026-09-17)
Traced against real code before writing this, same discipline as §IV.3: `pci_init()`
(`kernel_main.c:589`, unconditional) is the one real `acpi_table` consumer relevant here too
— the Mars's M.2 E-Key slot (§VI) is PCIe-attached, same shape of gap as the Pi 5's RP1.
`riscv64/timer.c` is **already** fully DTB-driven (both `timebase-frequency` and this
session's own hypervisor-detection check) — no further work needed there; it was built DTB-
first from the start, unlike aarch64's timer which needed a new ACPI-based check today.
**One real, already-flagged risk found while tracing this — FIXED 2026-09-05, see item 3
below.** `arch/riscv64/apic.c`'s own doc comment said the PLIC base address was "a constant,
not discovered from `boot_info->dtb`" — and `arch/riscv64/plic.c`'s own doc comment
(predating this document) already warned `PLIC_BASE`/`PLIC_CONTEXT_S` are "QEMU-virt-
specific... not assumed stable across" other configurations. That warning was concrete: the
JH7110's real PLIC address on the Mars was not confirmed to match QEMU-virt's, and the
interrupt controller would not have worked correctly if it didn't.
1. **Entry stub**: new native riscv64 entry point at `0x40000000` (§V.1), receiving `a0`=hart
ID, `a1`=DTB pointer directly (now-confirmed SBI convention) — no UEFI, no PE loader.
2. **DTB → `BootInfo` constructor**: same shape as aarch64's (§IV.3 item 2) — `dtb`=real
pointer, `acpi_table`=`NULL`, memory map from DTB `/memory`+`/reserved-memory`, `args` from
`/chosen`/`bootargs`.
3. **PLIC base address: make it DTB-discovered — DONE 2026-09-05.** `plic_init()`
(`arch/riscv64/plic.c`) now takes `boot_info->dtb`, passed through from `apic_init()`
(`arch/riscv64/apic.c`), and tries `fdt_find_node_by_compatible(dtb, "sifive,plic-1.0.0")`
→ `fdt_find_prop_in_node(..., "reg", ...)` before falling back to the QEMU-virt constant —
exactly the primitive built for the aarch64 GIC base fix (§IV.3 item 7), reused unchanged.
`s_plic_base` is now a runtime `uintptr_t`, not a `#define`, same shape as apic.c's
`s_gicd_base`/`s_gicc_base`. **Correction, same day**: this document's own text at this
point originally predicted the success branch would run under QEMU, on the strength of a
stale claim in `plic.c`'s own pre-fix header ("riscv64 DTB access does work, unlike
aarch64") — checked against the actual boot log rather than trusted, and it doesn't hold
for this system's QEMU/UEFI riscv64 firmware specifically: `timer.c`'s own
`timebase-frequency` read falls back too (`"Timer: RISC-V time CSR @ 10000000 Hz
(FALLBACK, no devicetree)"`), and the new PLIC line confirms the same thing —
`"PLIC: no DTB PLIC node -- using QEMU virt-machine default (base=0x0c000000)"`. So this
fix is evidentially in the same position as the GIC fix: the guard (no-DTB fallback) is
exercised and correct, the success branch (a real DTB with a matching PLIC node) remains
unverified until real hardware. The stale claim that misled this paragraph's first draft
lived only in `plic.c`'s old header and was already replaced by this fix's own comment —
not re-introduced. The Mars's real JH7110 `reg` value stays unconfirmed either way — the
compatible string is architectural (SiFive PLIC-1.0.0 is a standard binding), not
board-specific, but no
hardware exists yet to check the Mars's own DTB against it.
4. **Framebuffer for HDMI output**: JH7110's display path is genuinely unresearched this
pass — unlike the Pi 5's mailbox interface (well-documented, reused across many Pi bare-
metal projects), no equivalent research done yet for JH7110's own display controller.
Flagged here rather than assumed simple.
5. **`pci_init()` DTB path — the "shares §IV.3 item 5's code" framing is now wrong, corrected
2026-09-04.** §IV.3 item 5's own investigation found the Pi 5's RP1 sits behind a
Broadcom-specific indirect config-access window (`"brcm,bcm2712-pcie"`, confirmed against
`pcie-brcmstb.c`), not flat ECAM — a SoC-specific mechanism with no reason to assume it
matches JH7110's own PCIe controller. The "one implementation, two consumers" plan this line
originally stated no longer holds; the Mars's M.2 E-Key slot needs its **own** primary-source
check of JH7110's actual PCIe controller (compatible string, `reg` window size, and whether
its Linux driver uses flat ECAM or its own indirect scheme) before any implementation
decision, same discipline §IV.3 item 5 itself just applied. Not yet done.
6. **Boot image packaging**: `vf2-imager`/`mkimage`-based FIT image (§V.1) — confirm this
tooling's actual invocation once building the first real image, not just cited from
VisionFive 2 research.
7. **Code audit pass — DONE, 2026-09-04.** Reviewed `arch/riscv64/apic.c`, `plic.c`,
`arch.c`, `interrupts.c`, `timer.c` for the same class of QEMU-virt-vs-real-hardware
assumption §III item 6's amd64 audit and item 7 above's aarch64 audit looked for.
Report-only by default — the `satp`-clear finding below was fixed in code, per direct
instruction naming it specifically after this audit landed. The other findings (PLIC
base, already tracked as item 3 above; the clean SBI/SBI-SRST findings) needed no code
change either way.
**Clean, and the most portable pattern of all three architectures**: the SBI timer path
(`apic.c`) is genuinely hardware-independent — it probes for the TIME extension at
runtime and reports loudly rather than assuming it's present, so it depends on nothing
but OpenSBI itself, present on both QEMU and (per §V.1's own research) the Mars's real
U-Boot+OpenSBI chain. `arch_cold_reset()` (`arch.c`) uses the SBI SRST extension — a real
standards-defined mechanism, not a board-specific hack — making it the one of the three
architectures' cold-reset implementations that does **not** need a finding here (contrast
amd64's i8042-pulse hack and aarch64's HVC-hardcoded PSCI call, both flagged above/in
§III). `timer.c` is confirmed DTB-first as this section's own preamble already stated:
`timebase-frequency` is read via `fdt_prop_u32()` with a *named* QEMU-only fallback
(`RISCV_TIMEBASE_HZ_FALLBACK`) used only when the DTB is absent or invalid.
**Finding — already tracked, this pass confirms rather than discovers it**: `plic.c`'s
`PLIC_BASE`/`PLIC_CONTEXT_S` hardcoded-to-QEMU-virt situation is exactly item 3 above,
already flagged as "a real punch-list item, not a hypothetical" before this audit ran.
Nothing new to add beyond confirming the file's own header comment is accurate and the
risk is real, not overstated.
**Finding — minor, reasoning didn't transfer to the native path — FIXED 2026-09-04, per
direct instruction naming it specifically.** `arch_early_init()`'s explicit `satp` clear
(Bare-mode switch) was justified in its own comment entirely by behavior *observed under
QEMU's EDK2 RISC-V firmware* (confirmed `satp.MODE=10`/Sv57 live, kernel identity-mapped
within it). Under the native boot path (U-Boot+OpenSBI, no UEFI/EDK2 at all per §V.1)
that specific observation cannot apply — OpenSBI's S-mode handoff conventionally already
leaves `satp=0` (Bare mode), most likely making the unconditional switch a harmless no-op
there, but on an unverified assumption rather than a checked one.
**Fix**: rather than build a general Sv39/Sv48/Sv57 page-table walker just to *prove* the
running address is identity-mapped before switching (out of proportion to this finding's
own severity), `arch_early_init()` now reads `satp.MODE` first and **skips the switch
entirely when it's already 0** — the expected case on a from-Bare native boot, needing no
safety argument at all since there's nothing to switch away from. The unconditional
`csrw satp, x0`/`sfence.vma` pair still runs, unchanged, for the confirmed QEMU/EDK2 case
(`mode != 0`), where the identity-mapping argument remains the actual justification it
always was. If the native path somehow reaches the non-zero branch anyway (OpenSBI
leaving a non-Bare `satp`, not confirmed either way), the code says so explicitly in its
own comment now rather than silently relying on the QEMU-only observation. Verified against
the riscv64 boot log's own `satp.MODE` print, not assumed: this system's QEMU/EDK2 boot
shows `satp.MODE = 0x000000000000000a` (Sv57) at kernel entry, so the `mode != 0` branch
ran and printed `satp cleared -- Bare mode, explicit` exactly as before the fix — the
unconditional-switch path is unchanged on the one boot path that exercises it. amd64 and
aarch64 don't compile this file at all, so their part of the 3-arch acceptance run is pure
non-regression on files untouched by this fix, not evidence about the guard itself.
**Hardware-dependent, after 2026-09-17:**
8. Build and flash the boot image to QSPI flash (or attempt UART XMODEM recovery boot if QSPI
flashing isn't set up yet — both are real supported paths per §V.1).
9. Connect HDMI + keyboard.
10. Boot; confirm `ok>`/`zuse)ok>` reached.
11. Mint a Zuse identity on real media, confirm re-attach — the `v2.5.0` gate's own
requirement.
12. Update this section with results.
---
## VI. Hardware identification reference
Per-board SoC/CPU facts, consolidated here so later sections don't have to re-derive them.
Researched 2026-09-04 (web search, sources cited); anything not directly confirmed against
the actual unit in hand is flagged as such rather than assumed.
### amd64 — Beelink SER5 (reference/development machine)
- **CPU: AMD Ryzen 7 family.** Beelink has shipped the "SER5" name with several different
Ryzen 7 SKUs over its product life (5700U, 5800H, 7735HS all confirmed to exist under this
branding) — **exact SKU on this unit not yet confirmed**; check `dmesg`/BIOS/the physical
unit when convenient (`cat /proc/cpuinfo` or the BIOS splash screen under Linux/before
LithosAnanke boots, since LithosAnanke itself has no CPU-identification word yet). Not
load-bearing for this document's own genericity requirement (§III) — the boot path must not
depend on which SKU this is, by design — but worth pinning down for this reference's own
accuracy.
- **Architecture generation**: Zen2 (5700U/5800H) or Zen3 (7735HS) depending on the SKU above
— matters for any future CPU-feature-detection work (e.g. RDRAND is present on all of
these; that part's already confirmed live via `rng: backend = rdrand`, §III).
- Sources: [Gentoo wiki — SER5 5560U](https://wiki.gentoo.org/wiki/Beelink_SER5_AMD_Ryzen_5_5560U_Mini_PC),
[Starry Hope — SER5](https://www.starryhope.com/minipcs/models/beelink-ser5-mini-pc/),
[Starry Hope — SER5 Pro](https://www.starryhope.com/minipcs/models/beelink-ser5-pro-mini-pc/),
[Minixpc — SER5 Max](https://minixpc.com/blogs/news/beelink-ser5-max-review-powered-by-amd-ryzen7-5800h-processor).
### aarch64 — Raspberry Pi 5 (sole target)
- **SoC: Broadcom BCM2712.**
- **CPU**: quad-core 64-bit Arm Cortex-A76, 2.4 GHz, 512 KB per-core L2 cache, 2 MB shared L3.
- **GPU**: VideoCore VII, 12-core, 800 MHz, OpenGL ES 3.1 + Vulkan 1.2 (not relevant to
LithosAnanke's own framebuffer work — that goes through the mailbox property interface,
§IV.1 — but recorded here for completeness).
- **RAM**: LPDDR4X-4267, board variants at 1/2/4/8/16 GB, 32-bit memory interface, ~17 GB/s
bandwidth.
- **I/O**: RP1 companion chip (PCIe 2.0 x4-attached) handles GPIO, USB 2.0/3.0, Gigabit
Ethernet, CSI/DSI, analog video — confirmed separately (§IV.2) to have no RNG peripheral in
its own published peripheral list.
- **Cortex-A76 and `FEAT_RNG` (ARMv8.5 `RNDR`/`RNDRRS`)**: not confirmed present — A76 is not
among the cores that typically implement this feature (more common on newer cores like
Cortex-X2/A710); if this matters for any future entropy-source decision, verify via `ID_AA64ISAR0_EL1`
directly on the real board rather than assuming either way.
- Sources: [CNX Software — Pi 5 launch](https://www.cnx-software.com/2023/09/28/raspberry-pi-5-sbc-broadcom-bcm2712-quad-core-cortex-a76-soc/),
[Raspberry Pi — Processors doc](https://www.raspberrypi.com/documentation/computers/processors.html),
[sbcwiki — BCM2712](https://sbcwiki.com/docs/soc-manufacturers/broadcom/bcm2712/boards/rasperrypi-5/).
### riscv64 — Milk-V Mars (sole target)
- **SoC: StarFive JH7110**, 28 nm.
- **CPU**: 4× SiFive U74-MC application cores (RV64GC) + 1× SiFive S7 monitor core, up to
1.5 GHz.
- **RAM**: up to 8 GB LPDDR4; storage via eMMC slot + microSD slot.
- **I/O**: 3× USB 3.0, 1× USB 2.0, HDMI 2.0 (4K), Gigabit Ethernet with PoE support, M.2 E-Key
(WiFi/BT), 4-lane + 2-lane MIPI CSI, 40-pin GPIO header.
- **Physical**: designed to Raspberry Pi 3B dimensions — cases/heatsinks/fans for that form
factor are compatible.
- **Multimedia**: H.264/H.265 4K@60fps decode, H.265 1080p@30fps encode (not relevant to
LithosAnanke's own bring-up, recorded for completeness).
- Same JH7110 SoC as the StarFive VisionFive 2 — any VisionFive 2 bring-up material found
while researching §V's own boot-chain question is likely directly applicable here too, worth
checking first before assuming Mars-specific research is needed from scratch.
- Sources: [milkv.io — Mars overview](https://milkv.io/docs/mars/overview),
[milkv.io — Mars product page](https://milkv.io/mars),
[TinyComputers.io — Mars review](https://tinycomputers.io/posts/milk-v-mars-review.html).
### Noted for later, not yet in scope — BeagleBone Black
Added to this reference per direct instruction 2026-09-04, **recorded only — no work scoped
around it yet.** Genuinely different from the three targets above: the BeagleBone Black's
SoC is a **32-bit ARM** part, not aarch64 — a fourth architecture this kernel has no support
for at all today (amd64/aarch64/riscv64 only), not another board under an existing one.
- **SoC: TI Sitara AM335x.**
- **CPU**: single-core ARM Cortex-A8, 1 GHz, armv7-a (32-bit) — up to ~2000 MIPS.
- **RAM**: 512 MB DDR3L. Storage: 4 GB eMMC (default boot source) + microSD (secondary/
overridable to primary).
- **Other on-die units**: PowerVR SGX530 3D GPU; 2× PRU (Programmable Realtime Unit) 32-bit
200 MHz microcontrollers — real-time I/O coprocessors, no equivalent on any of the three
boards above; crypto accelerators.
- **Boot modes**: eMMC, microSD, serial, USB.
- Sources: [element14 — BBB product page](https://www.element14.com/community/docs/DOC-84108/l/beaglebone-black-development-board-with-1ghz-am335x-arm%C3%A3%C3%A2-cortex-a8-processor),
[TI.com — BEAGL-BONE-BLACK](https://www.ti.com/tool/BEAGL-BONE-BLACK).
### Noted for later, not yet in scope — Zynq-7000 (Puzhi PZ7010/PZ7020 "StarLite")
Added per direct instruction 2026-09-04, **recorded only — no work scoped around it yet.**
Unlike BeagleBone Black above, this one isn't a random addition: `ROADMAP.md` already names
**Zynq FPGA as the next big milestone beyond v2.5.0** — "the step where the battle-tested
amd64/aarch64/riscv64 story rides on configurable silicon," and the three-product split
decided alongside it names "hardware steady-state machinery with sealed executions,
HOL-proven" as the FPGA-native product this board would ultimately serve. This entry just
puts a concrete, purchasable board under that already-named milestone.
- **Board**: Puzhi PZ7010-StarLite (XC7Z010) or PZ7020-StarLite (XC7Z020) — same board design,
two SoC variants. 90×60mm, black PCB, immersion gold finish.
- **SoC: Xilinx/AMD Zynq-7000**, combining a **Processing System (PS)** — dual-core ARM
Cortex-A9, up to 667 MHz (`-1` speed grade) or 800 MHz (`-2`, XC7Z020 only) — with
**Programmable Logic (PL)**, 28 nm Artix-7/Kintex-7-based FPGA fabric. Genuinely a fifth
architecture class in this reference: ARMv7-A again (like BeagleBone Black), but a
different core (Cortex-A9 vs. A8) *and* an FPGA fabric with no equivalent on any board
above — this is the "configurable silicon" milestone `ROADMAP.md` already flagged as
reshaping the hardware story (soft/hard CPU cores, PL fabric, non-standard memory map,
custom peripherals), not a small per-board addition even in concept.
- **PS details** (identical between both variants): 256 KB on-chip memory, DDR3 controller,
32 KB I-cache + 32 KB D-cache per core, 512 KB shared L2.
- **PL resources (the actual XC7Z010 vs. XC7Z020 difference)**: XC7Z010 — 4,400 logic slices,
17,600 6-input LUTs, 35,200 flip-flops, 270 KB block RAM, 80 DSP slices. XC7Z020 — 13,300
logic slices, 53,200 LUTs, 106,400 flip-flops, 630 KB block RAM, 220 DSP slices.
- **RAM/storage**: 512 MB/1 GB DDR3, QSPI flash, EEPROM, SD boot.
- **I/O**: JTAG, UART, HDMI out, Gigabit Ethernet, USB 2.0 host, 40-pin expansion; MIPI CSI on
the 7020 variant only.
- Sources: [Puzhi — PZ7010-StarLite](https://www.en.puzhi.com/Product/AMD-FPGA-Development-Board/Zynq-7000-SoC/PZ7010-StarLite),
[Puzhi — PZ7020-StarLite](https://www.en.puzhi.com/Product/AMD-FPGA-Development-Board/Zynq-7000-SoC/PZ7020-StarLite),
[Xilinx/AMD — Zynq-7000 SoC Data Sheet (DS190)](https://www.mouser.com/datasheet/2/903/ds190-Zynq-7000-Overview-1595492.pdf),
[PCBSync — XC7Z010 vs XC7Z020 comparison](https://pcbsync.com/xilinx-xc7z010/).
+31
View File
@@ -73,3 +73,34 @@ level up the stack.
build the one wheel concretely first, generalize only if/when a second one is actually
needed (this project's own standing convention — three similar lines beat a premature
abstraction).
## 2. A pinned GPIO VM per real-hardware board (2026-09-04)
Raised in conversation with Captain Bob during `FABRIC-3.md` §IV/§V's bare-metal-boot
planning — theory stage, no code, no scope, deliberately not punch-listed yet.
**The idea, as stated:** the Raspberry Pi 5 will need a special pinned VM dedicated to GPIO —
presumably following the same Tripod precedent Hermes/Artemis already established (a
permanently-pinned, born-at-boot VM with a fixed role, per `FABRIC-2.md` §H.1's "pinned
sessions never leave" model). Whether the Milk-V Mars needs the same treatment is explicitly
**not yet decided** — Captain Bob's own framing: "same for Milk-V (? not sure here)." Worth
noting the Mars does have its own 40-pin GPIO header (§VI), so the question isn't "does it
have GPIO" but whether it needs its own *dedicated pinned VM* for it the same way the Pi 5
does, or whether GPIO access there fits some other shape.
**Why a dedicated VM, not just a C driver, presumably (not yet confirmed as the actual
reasoning — inferred from the Tripod precedent, not stated outright):** GPIO is shared,
stateful hardware that multiple capsules/patrons might want to touch — the same category of
problem Hermes (messaging) and Artemis (block storage) already solve by being a dedicated,
addressable VM other patrons talk to, rather than a bare C primitive anyone can call directly.
**Explicitly not decided yet:**
- Whether Milk-V Mars needs the same pinned-VM treatment, or something else.
- What this VM's own vocabulary/interface would look like (a `GPIO-READ`/`GPIO-WRITE`-shaped
word set? something richer, given the Pi 5's GPIO header also muxes UART/SPI per §IV.1?).
- Whether this is one pinned VM per board doing double duty (GPIO + whatever else that board's
bring-up needs) or GPIO specifically warrants its own separate VM distinct from, say, a
future UART-handling VM.
- How this interacts with §IV.1's own still-open native-boot-flow work — a GPIO VM needs GPIO
register addresses from the DTB the same way the rest of that bring-up does, so this idea
isn't independent of that work, just downstream of it.
+1
View File
@@ -479,6 +479,7 @@ endif
KERNEL_SRCS_BASE := \
$(KERNEL_SRC)/kernel_main.c \
$(KERNEL_SRC)/boot/cmdline.c \
$(wildcard $(KERNEL_SRC)/hal/*.c) \
$(wildcard $(KERNEL_SRC)/memory/*.c) \
$(wildcard $(KERNEL_SRC)/math/*.c) \
+12
View File
@@ -0,0 +1,12 @@
# Raspberry Pi 5 native boot media
What goes on the SD card for the native (non-UEFI) boot path (FABRIC-3.md §IV):
- `config.txt` — checked in here, done (§IV.3 item 6).
- `bcm2712-rpi-5-b.dtb` — Raspberry Pi firmware's own stock DTB; not built by this
project, copied from the firmware release the card is otherwise built from.
- `kernel_2712.img` — **does not exist yet.** `config.txt` names it, but no build target
in `Makefile.starkernel` currently emits a raw image by this name at load address
0x80000 — item 1 (entry stub) explicitly scoped a separate-image build target as future
work, not part of that item. Building this file is the remaining prerequisite before
item 7 (assembling the card) is possible.
+62
View File
@@ -0,0 +1,62 @@
# LithosAnanke native (non-UEFI) boot config for Raspberry Pi 5 (BCM2712).
# FABRIC-3.md §IV.3 item 6 -- see that section's own doc comment for the
# research trail behind each line (confirmed against raspberrypi.com's
# current config.txt reference and a real, working Pi 5 bare-metal
# project's own checked-in config.txt, not assumed from general Pi
# knowledge).
# Load our own kernel image directly -- no UEFI, no Linux. kernel_2712.img
# is the real filename Pi 5/500/CM5 firmware looks for first (its own
# 16K-page-size build); naming our own image this exactly, rather than
# falling back to kernel8.img, removes any ambiguity about which file the
# firmware picks up.
kernel=kernel_2712.img
# Disable the firmware's Linux-compatible-image sanity check. Without
# this, official docs describe it as checking for "a compatible Device
# Tree file before attempting to boot" and warn that "older non-compatible
# kernels would be loaded and then hang" -- documented specifically as the
# bare-metal-development escape hatch. One real working Pi 5 bare-metal
# project's own config.txt does NOT set this and still boots, so it may
# not be strictly required for every non-Linux image; kept here anyway
# since the official doc names this exact use case and there is no
# hardware in hand yet to verify which behavior is actually gated.
os_check=0
# Pin the exact devicetree explicitly rather than relying on the
# firmware's own board-revision auto-selection (which would also resolve
# to this file on real Pi 5 hardware, per general config.txt convention --
# explicit is certain, not just probably-correct).
device_tree=bcm2712-rpi-5-b.dtb
# Enable the dedicated debug UART (Pi 5's own JST connector, not the GPIO
# 14/15 header pins). Not because serial capture hardware is set up for
# this bring-up (that decision is HDMI-only, FABRIC-3.md §IV) -- this is
# a firmware-side switch that costs nothing when nothing is listening, and
# it is the only diagnostic channel that survives a framebuffer failure.
# rpi5_uart_base() (rpi5_dtb.c) already exists and is currently uncalled;
# this is the hook a UART-based fallback console would use if the mailbox
# framebuffer driver's two documented unverified details turn out wrong on
# real hardware.
enable_uart=1
# Deliberately NOT set, researched but not included this pass:
# dtoverlay=uart0 -- GPIO 14/15 header UART; needs a USB-serial
# adapter this bring-up doesn't have (HDMI-only
# decision). enable_uart=1 above covers the JST
# debug UART instead, which needs no extra cable.
# dtparam=pciex1 -- one real Pi 5 bare-metal project's own config.txt
# comments this as "Required for RP1 access", which
# would be surprising (RP1 is always-on in stock Pi
# 5 boots) and is community-reported, not confirmed
# against official docs. Not needed for this boot
# (no RP1 access yet -- FABRIC-3.md §IV.3 item 5 is
# still re-scoped, not implemented). Revisit if that
# item's own real Broadcom-indirect PCIe work needs
# it.
# arm_64bit=1 -- researched and dropped: official docs state
# "Models that only support a 64-bit kernel ignore
# this flag" -- inert on Pi 5, not wrong to include,
# just meaningless. The original item 6 wording
# named this; corrected here rather than carried
# forward.
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-04T15:51:18Z -->
<!-- Generated by mkcapsule --manifest 2026-09-05T06:22:51Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
BIN
View File
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

+21
View File
@@ -444,6 +444,27 @@ int blk_get_first_disk_range(uint32_t *out_start_lbn, uint32_t *out_count);
*/
int blk_firsttouch_claim(const uint8_t owner_fp[8], uint32_t count, uint32_t *out_chain_head);
/* blk_owner_has_claim - FABRIC-2.md §I.2's own "an existing claim is a
* separate, not-yet-decided question" (see blk_firsttouch_claim()'s doc
* comment above), decided 2026-09-05 for the overflow-trigger call site
* (capsule_wirebind.c): answered by a disk scan, not a RAM flag, matching
* BMAPFMT's own "ownership travels with the block, no centralized table"
* model -- the answer survives a reboot/reattach for free, the same way
* blk_firsttouch_claim()'s own free/claimed scan already does.
*
* One linear scan over blk_get_first_disk_range()'s representative LBNs
* (same shape as blk_firsttouch_claim()'s own scan); returns as soon as
* any devblock's blk_meta_t has BLK_FLAG_CLAIMED set and owner_fp equal
* to the argument.
*
* @param owner_fp 8-byte fingerprint, same convention as
* blk_firsttouch_claim()'s own owner_fp argument.
* @return 1 if owner_fp already owns at least one claimed devblock, 0 if
* not (including "no disk-backed device attached" -- nothing to
* own in that case).
*/
int blk_owner_has_claim(const uint8_t owner_fp[8]);
/* blk_meta_relocate_devblock - the real migration primitive (FABRIC-2.md
* §I.2, migration state machine, 2026-09-04): moves one devblock's full
* content and blk_meta_t ownership from home_devblock to target_devblock.
+23
View File
@@ -122,6 +122,29 @@ void capsule_wirebind_unclean_detach(void);
*/
const char *capsule_wirebind_attached_username(void);
/**
* capsule_wirebind_overflow_idle_check - FABRIC-2.md §I.2's own "overflow
* trigger," decided and built 2026-09-05. Called once per idle tick
* (sk_repl_idle(), repl.c, alongside blk_migration_idle_check() -- same
* ~1 Hz cadence), same as that function's own convention.
*
* No-op if nothing is attached via WIREBIND. Otherwise reads the attached
* drive's own free/total via blk_get_device_free_blocks() (real numbers:
* a WIREBIND-attached drive is always HOMEBLOCKS_SIG_OK, i.e. already
* STFR/v2-formatted, by the time blk_subsys_attach_device() runs on the
* same dev pointer right after WIREBIND itself -- not PROVISIONAL, not
* raw). If free space is below a fixed threshold AND the attached
* identity does not already own a claim (blk_owner_has_claim() -- a disk
* scan, not a RAM flag, so this decision survives reboot/reattach for
* free), claims a fixed number of additional devblocks on Artemis's own
* device via blk_firsttouch_claim() -- a one-time-per-identity extension,
* not a growth loop, deliberately: this does not free space on the
* user's own drive, it only extends their pool onto system-resident
* space, so re-claiming every tick once already extended would walk
* Artemis's device to exhaustion for no benefit.
*/
void capsule_wirebind_overflow_idle_check(void);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_CAPSULE_WIREBIND_H */
+29 -5
View File
@@ -129,14 +129,38 @@ void console_puts(const char *s);
void console_println(const char *s);
/**
* console_ensure_line_start - Make sure the next console_putc() starts at
* the beginning of a fresh output line, emitting a newline if output is
* currently mid-line (e.g. dangling behind a re-anchored prompt).
* No-op if already at line start. Mirrors console_putc()'s dual
* serial+framebuffer behavior (a bare '\n' reaches both).
* console_ensure_line_start - Make sure the *next real output* starts at
* the beginning of a fresh output line, closing off whatever is currently
* mid-line (e.g. a dangling prompt). No-op if already at line start.
*
* FABRIC-2.md §I.9 fix, 2026-09-05: the newline is DEFERRED, not emitted
* immediately -- it only actually reaches the console on the next real
* console_putc() call, and is silently dropped (never emitted at all) if
* console_cancel_deferred_line_start() is called first instead. Before this
* fix, an immediate, unconditional newline here meant any caller invoking
* this function "just in case" (sk_repl_idle() being the one real caller)
* would visibly snap a bare, unfinished prompt line to a fresh blank line
* even when nothing was actually about to be printed -- indistinguishable
* from Enter having already been pressed at that prompt. Deferring means a
* caller that turns out to have nothing to print can cancel cleanly, with
* zero visible effect, while a caller that does print gets the correct
* "close the old line first" behavior for free, still counted by
* console_tx_count() as real output (unlike the old always-silent inner
* form) since it is realized through the normal console_putc() path.
*/
void console_ensure_line_start(void);
/**
* console_cancel_deferred_line_start - Discard a pending deferred newline
* from console_ensure_line_start() without ever emitting it. No-op if no
* newline is currently deferred. Callers that speculatively deferred a line
* break before checking whether they actually have anything to print
* (sk_repl_idle()'s idle-beat check being the motivating case, FABRIC-2.md
* §I.9) call this when the check comes back negative, so the console is
* left exactly as it was -- no stray newline, no phantom blank line.
*/
void console_cancel_deferred_line_start(void);
/**
* console_tx_count - Monotonic count of console_putc() calls delivered to
* either output (serial and/or framebuffer). In use by the REPL to detect
+110 -5
View File
@@ -8,13 +8,20 @@
* fdt.h - Minimal flattened-devicetree reader
*
* Just enough of the Devicetree Specification v0.4 §5 to pull values out of
* the blob the UEFI firmware publishes under EFI_DTB_TABLE_GUID. Read-only,
* no allocation, no tree construction — it walks the structure block each
* call, which is fine for the handful of boot-time lookups the kernel needs.
* the blob the UEFI firmware publishes under EFI_DTB_TABLE_GUID, or that a
* native (non-UEFI) boot entry passes directly. Read-only, no allocation, no
* tree construction — it walks the structure block each call, which is fine
* for the handful of boot-time lookups the kernel needs.
*
* Deliberately not a general devicetree library. Added for punch-list item
* 0.3 (riscv64 timebase-frequency); item 0.6 will need node-scoped `reg`
* lookups for the aarch64 GIC and may extend this.
* 0.3 (riscv64 timebase-frequency); extended (FABRIC-3.md §IV.3/§V.3,
* 2026-09-04) with node-scoped lookup, for exactly the case this header
* originally flagged as a future need (item 0.6's aarch64 GIC) plus its
* real, concrete consumers as of this pass: the Raspberry Pi 5's UART/
* mailbox register addresses (native boot, no ACPI) and the Milk-V Mars's
* real PLIC base address (currently hardcoded to QEMU-virt's own value,
* `arch/riscv64/plic.c`'s own doc comment already warned this isn't
* assumed stable across configurations).
*/
#ifndef STARKERNEL_FDT_H
@@ -62,4 +69,102 @@ const void* fdt_find_prop(const void* fdt, const char* name, uint32_t* len_out);
*/
int fdt_prop_u32(const void* fdt, const char* name, uint32_t* out);
/**
* @brief Find the first node whose "compatible" property matches @p compatible.
*
* "compatible" is a NUL-separated list of strings (DT spec §2.3.1) — matches
* if @p compatible equals any one entry in the list, not just the whole
* property verbatim. Scans the whole tree in document order; the first
* matching node wins if more than one exists.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param compatible Compatible string to match, NUL-terminated.
* @return An opaque handle to the matched node, for use with
* @c fdt_find_prop_in_node() only (not a raw offset or a pointer
* to anything else meaningful) — or NULL if no node matches.
*/
const void* fdt_find_node_by_compatible(const void* fdt, const char* compatible);
/**
* @brief Find the first node whose "device_type" property equals @p type.
*
* Some standard nodes (`/memory` per DT spec §3.4) are identified by
* `device_type`, not `compatible` — unlike `compatible`, `device_type` is a
* single NUL-terminated string, not a list, so this matches the whole
* property value rather than scanning entries within it. Scans the whole
* tree in document order; the first matching node wins if more than one
* exists.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param type device_type value to match, NUL-terminated.
* @return An opaque handle to the matched node, for use with
* @c fdt_find_prop_in_node() only — or NULL if no node matches.
*/
const void* fdt_find_node_by_device_type(const void* fdt, const char* type);
/**
* @brief Find the first node whose own name matches @p name.
*
* Node names follow the DT spec §2.2.1 `name[@unit-address]` convention —
* matches if @p name equals the node's name up to (not including) an `@`
* suffix, or the whole name if there is none. For a singleton node with
* no unit address (`/reserved-memory` being the concrete case this was
* added for), this is an exact match. Scans the whole tree in document
* order; the first matching node wins if more than one exists.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param name Node name to match, NUL-terminated, no `@` suffix.
* @return An opaque handle to the matched node, for use with
* @c fdt_find_prop_in_node() / @c fdt_next_child_node() only —
* or NULL if no node matches.
*/
const void* fdt_find_node_by_name(const void* fdt, const char* name);
/**
* @brief Iterate the direct children of one node.
*
* Pass @p prev_child as NULL to get the first child; pass a previous
* result back in to get the next one. Stops (returns NULL) once there are
* no more children. Skips over each child's own descendants correctly
* (so a child with grandchildren doesn't confuse the scan), but does not
* itself descend into them — only direct children of @p parent are ever
* returned.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param parent Handle from one of the `fdt_find_node_by_*()`
* functions.
* @param prev_child NULL for the first child, or a handle previously
* returned by this function for @p parent to
* continue from.
* @return Handle to the next direct child, for use with
* @c fdt_find_prop_in_node() / @c fdt_next_child_node() only —
* or NULL once @p parent's children are exhausted.
*/
const void* fdt_next_child_node(const void* fdt, const void* parent,
const void* prev_child);
/**
* @brief Find a property by name, scoped to one node.
*
* Like @c fdt_find_prop(), but scans only @p node's own direct properties
* (as returned by @c fdt_find_node_by_compatible()) — stops at the first
* child node or the end of @p node's property list, never descends into
* children, never continues into a sibling. This is the difference that
* matters for a property name like "reg", which is not unique across the
* tree the way "timebase-frequency" (the whole reason @c fdt_find_prop()
* was originally sufficient) happens to be.
*
* @param fdt Blob, already checked with @c fdt_valid().
* @param node Handle from @c fdt_find_node_by_compatible(); NULL is
* safe and returns NULL (propagates a failed node lookup
* without a separate caller-side check).
* @param name Property name, NUL-terminated.
* @param len_out Receives the property length in bytes; may be NULL.
* @return Pointer to the property value inside @p fdt, or NULL if not
* found (or if @p node is NULL). The value is big-endian as
* stored in the blob.
*/
const void* fdt_find_prop_in_node(const void* fdt, const void* node,
const char* name, uint32_t* len_out);
#endif /* STARKERNEL_FDT_H */
+11 -1
View File
@@ -14,6 +14,11 @@
* at all before this item. Pure substrate: this item wires the mechanism
* (claim/dispatch/complete) with no permanent source enabled by default;
* a real consumer (4.3.5b, virtio-keyboard) enables its own source later.
*
* @c plic_init() takes the boot DTB as of FABRIC-3.md §V.3 item 3's fix,
* 2026-09-05: the base address was previously a QEMU-virt-specific
* hardcoded constant, unconditionally wrong on the Milk-V Mars's real
* JH7110 PLIC. See @c plic.c's file header for the discovery mechanism.
*/
#ifndef STARKERNEL_PLIC_H
@@ -28,9 +33,14 @@
* Also sets sie.SEIE. Does not touch sstatus.SIE -- arch_enable_interrupts()
* still owns that, same as the timer.
*
* @param dtb Candidate devicetree blob (@c BootInfo->dtb); NULL-safe. When
* a real @c "sifive,plic-1.0.0" node is found, the PLIC base
* address is read from its @c reg property; otherwise falls
* back to the QEMU-virt-machine constant, unchanged from this
* function's previous unconditional behaviour.
* @return 0 on success.
*/
int plic_init(void);
int plic_init(const void *dtb);
/** Set a source's interrupt priority (1-7; 0 means "never interrupt"). */
void plic_set_priority(uint32_t irq, uint32_t priority);
+48
View File
@@ -0,0 +1,48 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_dtb.h - Raspberry Pi 5 (BCM2712) devicetree-based peripheral
* discovery, for the native (non-UEFI) boot path (FABRIC-3.md §IV.3).
*
* Two lookups: the PL011 UART (early console) and the VideoCore mailbox
* property interface (framebuffer setup, §IV.3 item 3). Both peripherals
* live under BCM2712's own devicetree "soc" simple-bus node, which
* applies one fixed address translation to every child `reg` value —
* see `rpi5_dtb.c`'s own doc comment for the confirmed offset and where
* it was verified. `fdt.c`'s reader deliberately does not do general
* `ranges`-property translation (it is "not a general devicetree
* library"); this file applies the one, fixed, SoC-wide offset by name
* instead of teaching `fdt.c` a general mechanism for a single known
* hardware fact.
*/
#ifndef STARKERNEL_RPI5_DTB_H
#define STARKERNEL_RPI5_DTB_H
#include <stdint.h>
/**
* @brief Find the PL011 UART's CPU-physical base address from the DTB.
*
* @param dtb Devicetree blob, as passed to the native boot entry (or
* `BootInfo->dtb`); NULL is safe.
* @return Final CPU-physical MMIO base address, or 0 if the node is
* absent, malformed, or @p dtb is invalid.
*/
uint64_t rpi5_uart_base(const void* dtb);
/**
* @brief Find the VideoCore mailbox property interface's CPU-physical
* base address from the DTB.
*
* @param dtb Devicetree blob; NULL is safe.
* @return Final CPU-physical MMIO base address, or 0 if the node is
* absent, malformed, or @p dtb is invalid.
*/
uint64_t rpi5_mailbox_base(const void* dtb);
#endif /* STARKERNEL_RPI5_DTB_H */
+68
View File
@@ -0,0 +1,68 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_mailbox.h - VideoCore mailbox property-interface framebuffer
* setup, for the native (non-UEFI) Raspberry Pi 5 boot path
* (FABRIC-3.md §IV.3 item 3).
*
* Register layout, message/tag format, and property-tag IDs confirmed
* against multiple sources before writing `rpi5_mailbox.c` — see that
* file's own doc comment for exactly which, and what (if anything)
* remains unverified against real hardware (not in hand until
* 2026-09-17; this driver has never run on real silicon).
*/
#ifndef STARKERNEL_RPI5_MAILBOX_H
#define STARKERNEL_RPI5_MAILBOX_H
#include <stdint.h>
/* Mirrors uefi.h's FramebufferInfo exactly -- populated by
* rpi5_mailbox_get_framebuffer() the same shape UEFI GOP already
* populates it, so console.c/vt100.c/framebuffer.c need no changes at
* all for this path. Not #include-ing uefi.h here (that header is a
* large UEFI-protocol grab-bag; this driver only needs this one
* struct's shape) -- callers that already have a `FramebufferInfo*`
* (from uefi.h) can pass it directly, since the two struct
* definitions are kept in exact field-for-field sync by convention.
*/
typedef struct {
void* base;
uint64_t size;
uint32_t width;
uint32_t height;
uint32_t pixels_per_scanline;
uint32_t pixel_format; /* 0 = RGB, matches uefi.h's
* PixelRedGreenBlueReserved8BitPerColor --
* this driver always requests RGB pixel
* order explicitly (tag 0x00048006), never
* leaves it at hardware/firmware default. */
} Rpi5FramebufferInfo;
/**
* @brief Request a framebuffer from the VideoCore firmware via the
* mailbox property interface, at the requested resolution/depth.
*
* Sends one buffer with all six setup tags (physical size, virtual
* size, depth, pixel order, virtual offset, allocate) plus a
* get-pitch tag, in one request/response round trip.
*
* @param dtb Devicetree blob (passed to `rpi5_mailbox_base()`).
* @param width Requested physical+virtual width, pixels.
* @param height Requested physical+virtual height, pixels.
* @param bpp Requested bits per pixel (32 is the only depth this
* driver has been designed against; others are not
* refused outright but are unverified).
* @param out Populated on success; untouched on failure.
* @return 0 on success, negative on failure (mailbox node not found
* in the DTB, VC firmware rejected the request, or a response
* tag came back with an unexpected size).
*/
int rpi5_mailbox_get_framebuffer(const void* dtb, uint32_t width, uint32_t height,
uint32_t bpp, Rpi5FramebufferInfo* out);
#endif /* STARKERNEL_RPI5_MAILBOX_H */
+26
View File
@@ -0,0 +1,26 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_native_boot.h - Raspberry Pi 5 DTB->BootInfo constructor
* (FABRIC-3.md §IV.3 item 2).
*
* `native_rpi5_entry.S`'s `rpi5_native_start` tail-calls
* `rpi5_native_boot()` with the masked DTB pointer it captured. This
* function builds the *existing*, unmodified `BootInfo` struct
* (`include/starkernel/uefi.h`) from the devicetree instead of UEFI
* protocols, then calls the *existing*, unmodified `kernel_main()` — see
* this file's own `.c` for exactly what is and is not populated, and why.
*/
#ifndef STARKERNEL_RPI5_NATIVE_BOOT_H
#define STARKERNEL_RPI5_NATIVE_BOOT_H
#include <stdint.h>
void rpi5_native_boot(uint64_t dtb) __attribute__((noreturn));
#endif /* STARKERNEL_RPI5_NATIVE_BOOT_H */
+32
View File
@@ -0,0 +1,32 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* rpi5_native_entry.h - Raspberry Pi 5 native (non-UEFI) boot entry point
* (FABRIC-3.md §IV.3 item 1).
*
* `native_rpi5_entry.S`'s `rpi5_native_start` is the very first code that
* runs on this path -- entered directly by Pi 5 firmware, no UEFI, no
* ACPI, none of `boot/uefi_loader.c`'s PE-loader shape applies. It masks
* the firmware's raw entry register down to the documented 32-bit DTB
* pointer (§IV.1: upper 32 bits of the 64-bit register are unspecified)
* and stores it here, then establishes its own dedicated stack (this path
* has no EDK2 boot stack to inherit) and halts.
*
* `g_rpi5_dtb_ptr` is this stub's one real output -- the still-open
* DTB->BootInfo constructor (§IV.3 item 2) reads it from here once it
* exists; nothing calls that constructor yet, so `rpi5_native_start`
* halts rather than tail-calling into a function that isn't real.
*/
#ifndef STARKERNEL_RPI5_NATIVE_ENTRY_H
#define STARKERNEL_RPI5_NATIVE_ENTRY_H
#include <stdint.h>
extern uint64_t g_rpi5_dtb_ptr;
#endif /* STARKERNEL_RPI5_NATIVE_ENTRY_H */
+91
View File
@@ -138,6 +138,13 @@ void vm_store_u8(struct VM* vm, vaddr_t addr, uint8_t v);
cell_t vm_load_cell(struct VM* vm, vaddr_t addr); /* requires alignment */
void vm_store_cell(struct VM* vm, vaddr_t addr, cell_t v);
/* Numeric BASE accessors: keep the FORTH-visible BASE cell (vm->base_addr)
* and the host-mirror field (vm->base, read by number-output words via
* current_base()) in sync. Always use these instead of writing base_addr
* directly. */
unsigned vm_get_base(const struct VM* vm);
void vm_set_base(struct VM* vm, unsigned b);
/* Explicit stack<->offset conversions (keep intent obvious) */
static inline vaddr_t VM_ADDR(cell_t c) { return (vaddr_t)(uint64_t)c; }
static inline cell_t CELL(vaddr_t a) { return (cell_t)(int64_t)a; }
@@ -357,6 +364,81 @@ typedef enum
MODE_COMPILE = 1
} vm_mode_t;
/* Per-VM vocabulary (VOCABULARY/DEFINITIONS/CONTEXT/CURRENT/FORTH) state.
* Was a set of file-scope statics in vocabulary_words.c, shared by every VM
* in the process -- one VM's VOCABULARY/DEFINITIONS/FORTH silently changed
* where every other VM looked up and defined words (proof/FINDINGS.md #1,
* item #5, "highest severity in the sweep"). Moved onto struct VM so each
* VM in the fleet keeps its own vocabulary chains, search order, and VM-cell
* addresses -- same shape as hold_addr/hold_pos below. */
#define VOCAB_FC_BUCKETS 256
typedef struct {
int initialized;
/* Host-side vocabulary chain heads */
DictEntry *forth_vocab; /* FORTH vocabulary head (root of system) */
DictEntry *context_vocab; /* CONTEXT vocabulary head */
DictEntry *current_vocab; /* CURRENT vocabulary head */
/* VM-visible variables: cell addresses in this VM's own memory */
vaddr_t context_var_addr; /* cell containing (DictEntry*) CONTEXT */
vaddr_t current_var_addr; /* cell containing (DictEntry*) CURRENT */
/* First-character search index per chain, lazily rebuilt when that
* chain's head changes (see vocab_find_word in vocabulary_words.c) */
DictEntry **ctx_fc[VOCAB_FC_BUCKETS];
size_t ctx_n[VOCAB_FC_BUCKETS];
DictEntry *ctx_cached_head;
DictEntry **forth_fc[VOCAB_FC_BUCKETS];
size_t forth_n[VOCAB_FC_BUCKETS];
DictEntry *forth_cached_head;
} VocabularyState;
/* Compile-time control-flow bookkeeping for IF/ELSE/THEN, BEGIN/WHILE/REPEAT/
* UNTIL/AGAIN, DO/?DO/LOOP/+LOOP/LEAVE, and CASE/OF/ENDOF/ENDCASE.
* Was a set of file-scope statics in control_words.c, shared by every VM in
* the process -- two VMs compiling control structures at overlapping times
* would corrupt each other's nesting state (proof/FINDINGS.md #1, item #1,
* "the two that would actually corrupt VM behavior in the live Tripod fleet
* today"). Moved onto struct VM so each VM keeps its own compile-time stacks. */
#define CF_STACK_MAX 64
typedef enum {
CF_BEGIN, /* Address of BEGIN target */
CF_IF, /* Address of IF's 0BRANCH literal */
CF_ELSE, /* Address of ELSE's BRANCH literal */
CF_WHILE, /* Address of WHILE's 0BRANCH literal (paired with prior BEGIN) */
CF_DO, /* Address of loop body start (back target for LOOP/+LOOP) */
CF_CASE, /* Marker for CASE statement start */
CF_OF /* Address of OF's 0BRANCH literal */
} cf_tag_t;
typedef struct {
size_t addr; /* byte offset in vm->memory used for patching/back edges */
cf_tag_t tag;
} cf_item_t;
typedef struct {
cf_item_t cf_stack[CF_STACK_MAX];
int cf_sp;
int cf_last_mode; /* reset cf_stack on mode transitions (INTERPRET/COMPILE) */
/* LEAVE patch sites: one entry per LEAVE, collected until LOOP/+LOOP */
size_t leave_addrs[CF_STACK_MAX];
int leave_sp;
/* One mark per DO nesting: leave_sp at DO/?DO entry, restored at LOOP/+LOOP */
int leave_mark_stack[CF_STACK_MAX];
int leave_mark_sp;
/* ENDOF patch sites: one entry per ENDOF, collected until ENDCASE */
size_t endof_addrs[CF_STACK_MAX];
int endof_sp;
/* One mark per CASE nesting: endof_sp at CASE entry, restored at ENDCASE */
int endof_mark_stack[CF_STACK_MAX];
int endof_mark_sp;
} ControlFlowState;
#ifdef __STARKERNEL__
#include "starkernel/vm_uuid.h" /* VMUuid -- item 4.2, VM.stadium_vm_id */
#include "starkernel/vm_identity.h" /* VMIdentity -- FABRIC-2.md §F.2/§F.16 */
@@ -465,6 +547,9 @@ typedef struct VM
vaddr_t hold_addr; /* VM memory offset of the 64-byte pictured-number hold buffer */
int hold_pos; /* Current fill count in hold buffer (<# ... #> state) */
VocabularyState vocab; /* Per-VM VOCABULARY/DEFINITIONS/CONTEXT/CURRENT state */
ControlFlowState cf; /* Per-VM compile-time control-flow bookkeeping */
/* Block I/O window — BLK_VM_SLOTS slots at BLK_VM_WINDOW_BASE in vm->memory.
* BLOCK/BUFFER copy C-layer buffers here; UPDATE copies back; lbn==0 means empty. */
uint32_t blk_vm_lbn[BLK_VM_SLOTS]; /* LBN currently in each slot (0 = empty) */
@@ -638,6 +723,12 @@ DictEntry* vm_dictionary_find_latest_by_func(VM* vm, word_func_t func);
DictEntry* vm_dictionary_lookup_by_word_id(VM* vm, uint32_t word_id);
/* Validate that `candidate` is a live, currently-registered dictionary entry
* for this VM (walks vm->latest under dict_lock; never dereferences an
* unverified pointer). Use before executing/inspecting through any xt/addr
* popped off the data stack (EXECUTE, ENTROPY@/!, etc). */
int vm_dict_entry_ok(VM* vm, DictEntry* candidate);
void vm_dictionary_track_entry(VM* vm, DictEntry* entry);
void vm_dictionary_untrack_entry(VM* vm, DictEntry* entry);
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More