docs: reorganise -- current reference gathered, finished work archived, superseded and dead ideas marked as such

docs/CLAUDE.md Phase 2, 2026-10-14.  All moves by git mv; every archived
file carries a header saying what it is, why, and where to look instead.

- docs/reference/ (new): roadmap and versioning policy, M7.1, word-ACL
  design, kernel args, hosted acceptance test, dictionary, primitives,
  ontology, L8 table -- previously spread over docs/, docs/lithosananke/
  and docs/03-architecture/.
- docs/working/archive/historical/: finished work (session plan, test-gap
  audit, birthing plan, fix write-ups, the January architecture draft).
- docs/working/archive/superseded/: the Hermes-VM Tripod docs and July
  Hermes designs (-> FABRIC-3.5/3.6), the StarForth roadmap, every working
  doc already flagged superseded or obsolete.
- docs/working/archive/dead/: the hal_* target contract (never adopted),
  L4Re, metrics-and-knobs.
- Papers and proposals into docs/working/papers/.
- References rewritten across the repo (not logs/, not docs/patent/);
  CLAUDE.md's documentation section rewritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rajames
2026-10-14 16:03:56 -04:00
co-authored by Claude Opus 5.5
parent 1a4b163861
commit 7aed588f78
116 changed files with 305 additions and 175 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-195
View File
@@ -1,195 +0,0 @@
# LithosAnanke — StarForth Kernel Branch
> **⚠ PARTIALLY STALE (flagged 2026-10-09).** This page predates the repo split and the
> Tripod/kernel reshuffle. There is no `lithosananke` branch in this repo — `master` is the
> sole production line — and the VM fleet is now **Hera / Artemis / Hestia** (the Hermes VM
> was removed; messaging moved into the kernel as kernel-Hermes). M7.1 is **live**, not
> merely designed. For authoritative status see `.claude/CLAUDE.md` and
> `docs/fabric/FABRIC-2.md` / `FABRIC-3.md` / `FABRIC-3.5.md` / `FABRIC-3.6.md`; for the
> milestone plan see `ROADMAP.md` (this file is an orientation page).
**Line:** `master` (sole production line post-split)
**Status:** M7 Complete (VM Parity) · M7.1 Live (capsules, Tripod, Stadium, word-level ACL Phases 1–7)
---
## What is LithosAnanke?
LithosAnanke is the kernel branch of StarForth — a UEFI-bootable microkernel with the StarForth VM as its native execution engine.
**Etymology:** *Lithos* (stone/foundation) + *Ananke* (necessity/inevitability)
The name reflects the project's nature: a necessary foundation for StarshipOS.
---
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ UEFI Firmware │
│ • ExitBootServices → handoff to kernel │
└─────────────────────┬───────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ LithosAnanke Boot (M0) │
│ • UEFI loader collects BootInfo │
│ • Memory map, ACPI tables, framebuffer │
└─────────────────────┬───────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ LithosAnanke HAL (M1-M6) │
│ • PMM: Physical Memory Manager (bitmap) │
│ • VMM: Virtual Memory Manager (4-level paging) │
│ • IDT: Interrupt Descriptor Table │
│ • APIC: Timer + IRQ routing │
│ • Heap: kmalloc/kfree │
│ • Console: Serial UART │
└─────────────────────┬───────────────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────────────────┐
│ StarForth VM (M7+) │
│ • 295 FORTH-79 words │
│ • Parity validation (hash=0x684bbf2fa1d96d55) │
│ • Physics-driven adaptive runtime │
│ • Init capsule system (M7.1) │
└─────────────────────────────────────────────────────────────┘
```
---
## Current Milestones
| Milestone | Description | Status |
|-----------|-------------|--------|
| M0 | UEFI Boot + Serial Output | Complete |
| M1 | Physical Memory Manager | Complete |
| M2 | Virtual Memory Manager | Complete |
| M3 | IDT + Interrupts | Complete |
| M4 | APIC Timer | Complete |
| M5 | Timer Calibration | Complete |
| M6 | Kernel Heap | Complete |
| **M7** | **VM Parity Validation** | **Complete** |
| **M7.1** | **Capsules · Tripod · Stadium · ACL** | **Live** (see FABRIC docs) |
| M8 | REPL (keyboard input) | Superseded — see FABRIC-0 §25.5 item 4.4 |
| M9 | Block Storage / Block I/O | Live (virtio-blk + block subsystem) |
---
## Build & Run
```bash
# Build kernel with VM integration (loader + kernel)
make -f kernel/Makefile ARCH=amd64 STARFORTH_ENABLE_VM=1
# Run in QEMU
make -f kernel/Makefile ARCH=amd64 STARFORTH_ENABLE_VM=1 qemu
# Other architectures
make -f kernel/Makefile ARCH=aarch64 qemu
make -f kernel/Makefile ARCH=riscv64 qemu
```
**Output artifacts:**
- `build/amd64/kernel/starkernel_loader.efi` — UEFI loader
- `build/amd64/kernel/starkernel_kernel.elf` — Kernel binary
---
## Documentation
### Core Design
| Document | Description |
|----------|-------------|
| [M7.1.md](M7.1.md) | Init Capsule Architecture |
| [ROADMAP.md](ROADMAP.md) | Milestone roadmap |
| [amd64-isr-fix/README.md](amd64-isr-fix/README.md) | APIC timer ISR fix + kernel 3-arch acceptance test |
| [hosted-acceptance-test/README.md](hosted-acceptance-test/README.md) | Hosted VM 3-arch acceptance test (amd64 native, aarch64/riscv64 under QEMU user-mode) |
### HAL (Hardware Abstraction Layer)
| Document | Description |
|----------|-------------|
| [hal/overview.md](hal/overview.md) | HAL architecture and principles |
| [hal/interfaces.md](hal/interfaces.md) | HAL interface specifications |
| [hal/lithosananke-integration.md](hal/lithosananke-integration.md) | Kernel-specific HAL implementation |
| [hal/platform-implementations.md](hal/platform-implementations.md) | Platform implementation guide |
| [hal/migration-plan.md](hal/migration-plan.md) | Migration from hosted to kernel |
---
## Key Concepts
### One Truth Per VM
Every VM has exactly one birth capsule — its identity and provenance.
```
PARITY:BIRTH vm_id=42 capsule_id=0xA13F mode=p capsule_hash=0x... dict_hash=0x...
```
### DOMAIN vs PERSONALITY
**DOMAIN** is a construction concept that exists **only in Mama FORTH**. It's how Mama classifies, composes, and selects capsules. DOMAIN is never visible to baby VMs.
**PERSONALITY** is the result of executing a production `(p)` INIT capsule. It's what the baby VM *is* — its vocabulary, invariants, capabilities. PERSONALITY is immutable and defines the VM's entire worldview.
### Production (p) vs Experiment (e)
- **(p) capsules** are truth-bearing — they can birth VMs
- **(e) capsules** are Mama-only workloads — experiments that never touch babies
A baby VM never sees, executes, or knows about (e) capsules.
### Content-Addressed Capsules
Capsules are identified by content hash, not names. Names lie. Hashes don't.
### Mama FORTH
The root VM that holds all truths, manages child VMs, and enforces the birth protocol. Mama:
- Maintains the capsule directory
- Selects (p) capsules for birth
- Executes (e) capsules for experimentation
- Logs all births and runs for provenance
---
## Directory Structure
```
src/starkernel/
├── boot/ # UEFI loader, ELF loader
├── hal/ # Console, host services
├── memory/ # PMM, VMM, kmalloc
├── arch/amd64/ # x86_64 specific (APIC, IDT, timer)
├── vm/ # VM integration
│ ├── arena.c # VM memory arena
│ ├── parity.c # Parity validation
│ ├── bootstrap/ # sk_vm_bootstrap
│ └── host/ # Host services shim
└── kernel_main.c # Entry point
```
---
## Contributing
The contributing rules are:
1. Maintain parity validation (M7 baseline)
2. Pass the three-arch QEMU boot test (`ARCH={amd64,aarch64,riscv64}`)
3. Not break the hosted VM build (plain `make` at the repo root)
---
## References
- [StarForth README](../../README.md) — Main project
- [FORTH-79 Standard](http://www.forth.org/forth-79.html)
- [OSDev Wiki](https://wiki.osdev.org/)
- [UEFI Specification](https://uefi.org/specifications)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-97
View File
@@ -1,97 +0,0 @@
# APIC Timer ISR Fix — amd64
**Date**: 2026-06-03
**Branch**: `claude/starforth-architecture-review-oubN0`
**Commits**: `fb0c256`, `901f827`
---
## Problem
The LithosAnanke amd64 kernel was triple-faulting on the first APIC timer interrupt,
causing QEMU to exit immediately after printing `ok>`. The adaptive heartbeat never
fired and `Heartbeat: N ticks` never appeared in serial logs.
## Root Cause
The kernel is compiled with `-fPIC -fvisibility=hidden` (required for UEFI PE
relocation). Assembly symbols `isr_stub_table` and `isr_stub0` in `isr.S` were
declared `.global` with **default visibility**. The C compiler therefore accessed
them through GOT indirection. However, the linker — building a final non-shared
binary — collapsed the GOT references to point directly at the symbols themselves.
The net effect: "load from GOT[`isr_stub_table`]" loaded the 8-byte instruction
bytes of `isr_stub0` (`0x006a90eb006a006a`) rather than its address. Both
`link_time_addr` and `runtime_addr` in `arch_interrupts_init()` became the same
wrong value (instruction bytes), so `reloc_offset = 0` and all 256 IDT entries
received garbage handler addresses. IDT[32] was set to `0x6a006afffffe91e9`
(non-canonical), which triggered #GP → double fault → triple fault on the first
timer delivery.
The bug was confirmed by disassembling `arch_interrupts_init()` in the loader PE:
the compiler emitted a `mov` (load from memory) for `&isr_stub0` instead of a
`lea` (compute address), reading the function's instruction bytes.
## Fix
Two changes, one line each:
**`src/starkernel/arch/amd64/isr.S`** — Added `.hidden` directives for all symbols
referenced from C:
```asm
.hidden isr_stub_table
.hidden isr_stub0
.hidden isr_stub32
.hidden isr_stub_default
.hidden isr_common_entry
```
**`src/starkernel/arch/amd64/interrupts.c`** — Added visibility attributes to the
`extern` declarations:
```c
extern void *isr_stub_table[IDT_ENTRIES] __attribute__((visibility("hidden")));
extern void isr_stub0(void) __attribute__((visibility("hidden")));
```
With hidden visibility, the compiler uses direct RIP-relative `lea` instead of GOT
indirection, yielding correct runtime addresses for all 256 IDT entries.
## Result
| Criterion | Before | After |
|---|---|---|
| IDT[32] handler | `0x6a006afffffe91e9` (corrupt) | `0x3d90251c` (valid canonical address) |
| First timer interrupt | #GP → triple fault → QEMU exit | ISR fires cleanly, returns via `iretq` |
| `PARITY:OK` | ✅ (unaffected) | ✅ |
| `Init: init.4th OK` | ✅ (unaffected) | ✅ |
| `ok>` REPL | ✅ reached, then immediate crash | ✅ runs indefinitely |
| `Heartbeat: N ticks` | Never | Every second at 100 Hz, TIME-TRUST ≈ 0.999 |
## Secondary Fix
`repl_print_dec32`, `repl_lapic_ccr`, `repl_print_hex32`, and `idle_iters` in
`repl.c` are x86-64 specific (LAPIC MMIO, `pushfq`). They were unguarded, causing
`-Werror=unused-function` / `unused-variable` failures on aarch64 (clang) and
riscv64 (gcc). All moved inside `#ifdef ARCH_AMD64`.
## Acceptance Testing
All three architectures must boot to `ok>` with `PARITY:OK` and `Init: init.4th OK`.
amd64 additionally shows `Heartbeat: N ticks TIME-TRUST≈0.999` repeating at 1 Hz.
```bash
make -f Makefile.starkernel ARCH=amd64 clean qemu
make -f Makefile.starkernel ARCH=aarch64 clean qemu
make -f Makefile.starkernel ARCH=riscv64 clean qemu
```
Verify each log:
```bash
grep "PARITY:OK" logs/qemu-<arch>-*.log
grep "Init: init.4th OK" logs/qemu-<arch>-*.log
grep "ok>" logs/qemu-<arch>-*.log
grep "Heartbeat:" logs/qemu-amd64-*.log | head -5
```
@@ -1,178 +0,0 @@
# Hosted StarForth — 3-Architecture Acceptance Test
**Date**: 2026-07-24 (original), corrected 2026-08-02
**Branch**: `master`
**Commits**: `edced063` (original block-subsystem fix), `4485c38` (doc +
3 clang-surfaced bug fixes), `e287334` (asm register-reuse fix), this pass
(`riscv64-clang` Makefile target)
---
## What this verifies
The **hosted** StarForth VM (`make`, not `Makefile.starkernel`) is a static
binary. This test confirms it starts cleanly — reaches the `ok>` REPL prompt
with no errors — on all three architectures the project targets:
- **amd64** — native execution on the build host
- **aarch64** — cross-compiled, run under QEMU user-mode emulation
- **riscv64** — cross-compiled, run under QEMU user-mode emulation
This is distinct from the kernel (`Makefile.starkernel`) 3-arch acceptance
test documented in
[`../amd64-isr-fix/README.md`](../amd64-isr-fix/README.md), which boots a
full UEFI bare-metal image under system-mode QEMU. This test only exercises
the hosted Linux binary, using QEMU **user-mode** emulation to run a
foreign-architecture static binary directly on the x86_64 host — no kernel,
no UEFI firmware, no system emulation.
---
## Prerequisites
Cross toolchains, QEMU user-mode emulation, and clang (needed for the
riscv64 leg — see below):
```bash
sudo apt-get install -y \
gcc-aarch64-linux-gnu \
gcc-riscv64-linux-gnu \
clang-18 lld-18 \
qemu-user
```
`qemu-user` alone is enough — the binaries built below are statically
linked (`-static`), so the non-static `qemu-<arch>` emulators run them
directly with no sysroot. (The `qemu-user-static` package, if you see it
referenced elsewhere, provides statically-linked *emulators*; it's not
required just because the *guest* binary is static.)
---
## Invocation note
None of the three binaries accept a `-c "<script>"` flag — earlier
revisions of this doc showed one, but `src/cli.c`/`src/main.c` have never
had one in this repo's history. Pipe the script via stdin with `-s`
(script/silent mode) instead. Startup always runs the full internal POST
test suite before the REPL takes stdin, so expect substantial test-suite
output before your script's result appears at the very end.
---
## amd64 — native
```bash
make clean && make
echo "1 2 + . BYE" | ./build/amd64/standard/starforth -s
```
Expect `3 Goodbye!` at the end of the output, then a clean exit. No
emulation needed — this is the build host's native architecture.
---
## aarch64 — cross-compile + QEMU user-mode
```bash
make rpi4-cross
echo "1 2 + . BYE" | qemu-aarch64 ./build/raspi/fastest/starforth -s
```
`make rpi4-cross` cross-compiles with `aarch64-linux-gnu-gcc`
(`ARCH=raspi`, `TARGET=fastest`, `-static`); see `Makefile:547-554`. The
output binary is statically linked, so `qemu-aarch64` can execute it
directly with no sysroot.
---
## riscv64 — cross-compile + QEMU user-mode
```bash
make riscv64-clang
echo "1 2 + . BYE" | qemu-riscv64 ./build/riscv64/fastest/starforth -s
```
`make riscv64-clang` cross-compiles with `clang-18` (`ARCH=riscv64`,
`TARGET=fastest`, `-static`); see `Makefile:566-579`. **This leg must use
clang, not GCC**: `riscv64-linux-gnu-gcc` fails to build this tree
(`nanosleep` visibility failure under `-std=c99`, unrelated to any project
change — root cause not yet chased down, see
`docs/working/archive/session-logs/2026-07-24-punch-list.md` item #3). The
target's `CFLAGS` deliberately does not reuse the Makefile's
`$(BASE_CFLAGS)` (which hardcodes `-std=c99`) — clang needs `-std=c11
-pthread` here instead, plus `-fuse-ld=lld` in `LDFLAGS` (folding it into
`CC` makes clang reject it as an unused argument during `-c`/compile-only
invocations under `-Werror`).
---
## Pass criteria
For each architecture:
1. Build completes with no errors.
2. `blk_subsys_init` succeeds — no `"Failed to initialize block subsystem"`
error. (This was broken on every architecture, including amd64, prior to
`edced063` — see below.)
3. The internal POST suite runs (substantial log output — expected, not a
failure by itself) and the piped `1 2 + . BYE` script prints `3 Goodbye!`
at the end.
---
## Background: the block-subsystem bug this depends on
Before `edced063`, the hosted VM could not start on **any** architecture on
this branch. `src/main.c` allocated a hardcoded `blk_ram[1024 * 1024]`
buffer, stale relative to `BLK_RAM_BLOCKS=2080` (`include/block_subsystem.h`)
— a deliberate redesign that moved the RAM/ramdrive boundary to physical
block 2080 (user-visible LBN 2048), per `BLK_FORTH_SYS_RESERVED=32`. The
kernel-side buffer in `src/starkernel/vm/bootstrap/sk_vm_bootstrap.c` already
derived its size correctly from the shared constant; `main.c` was the one
remaining hardcoded holdout. The fix:
```c
#include "block_subsystem.h"
...
static uint8_t blk_ram[BLK_RAM_BLOCKS * BLK_FORTH_SIZE]; /* was: blk_ram[1024 * 1024] */
```
This is why the acceptance test above is meaningful on this branch
specifically: it did not pass before this fix, on any of the three
architectures.
---
## Background: the riscv64 clang build (2026-08-02 correction)
The riscv64 command above was broken in this doc from 2026-07-24 until this
correction — `docs/working/archive/session-logs/2026-07-24-punch-list.md`
(item #2) recorded that the working clang invocation had been found and
fixed in the old pre-split monorepo's `master` branch's `.claude/CLAUDE.md`,
but the correction was never copied into this doc. Verifying it end-to-end
for this correction surfaced that the clang build also needs three small
source fixes from that same punch list (item #1), previously ported to the
old monorepo's `master` but not to this line:
- `src/math_portable.c` — `-100LL << 16` is UB (shifting a negative value)
under `-Wshift-negative-value`; changed to `-(100LL << 16)`.
- `src/physics_pipelining_metrics.c` — removed dead `q48_mul_q48()`
(`-Wunused-function` under clang; GCC doesn't catch this by default).
- `src/word_source/editor_words.c` — removed dead `set_scr()` (same reason).
The punch list's highest-priority item — a genuine SIGSEGV-causing
register-reuse hazard in `include/vm_asm_opt_riscv64.h`'s `vm_pop_asm`/
`vm_rpop_asm` (clang's register allocator can pick the same register for a
memory operand referenced both before and after a write-back, corrupting
the result; GCC happens not to hit it) was *not* required just to get this
acceptance test's `-O3` build to compile and pass, but has since been
fixed too (commit `e287334`) — and turned out not to be merely a latent
risk: it was silently corrupting 10 `CASE.*` control-flow tests under this
exact build (`955`/`10 failed` → `965`/`0 failed`, `ALL IMPLEMENTED TESTS
PASSED!`, with nothing else changed).
Item #3 of the same punch list — no `make` target for this leg, only the
long manual invocation — is also resolved as of this pass: `make
riscv64-clang` wires in the same recipe verified above directly into the
Makefile.
File diff suppressed because it is too large Load Diff
-18
View File
@@ -1,18 +0,0 @@
# docs/lithosananke/kernel-args/
Design doc for kernel command-line arguments (`KernelArgs`).
- **[DESIGN.md](DESIGN.md)** — Design for runtime-configurable memory sizing
and behavior (heap size, stack depth, DoE self-start, log verbosity)
currently baked in as compile-time constants. Covers the UEFI
`EFI_LOADED_IMAGE_PROTOCOL->LoadOptions` mechanism, the `KernelArgs`
struct, `BootInfo` changes, the assembly trampoline
(`kernel_entry.S`), the `cmdline.c` parser, loader/`kernel_main.c`/
Makefile integration, passing args under QEMU, and the `REBOOT` FORTH
word (including its NVRAM one-shot mechanism and hosted-VM stub). Status:
design complete, awaiting implementation.
## See also
- [`docs/lithosananke/README.md`](../README.md) — kernel branch overview.
- [Project root](../../../README.md)
@@ -1,183 +0,0 @@
# Kernel Stack Trampoline Fix — riscv64
**Date**: 2026-08-02
**Branch**: `master`
**Commit**: `7366275`
---
## Problem
The riscv64 kernel crashed partway through VM bootstrap during the 2026-08-01
DoE campaign run, well after amd64 and aarch64 had both booted cleanly to
`ok>` in the same session. The crash was a wild jump: execution landed at an
address nowhere inside the kernel's own linked image, then almost immediately
faulted trying to load from a small, near-null address.
```
Registering FORTH-79 Standard word set...
Registering FORTH-79 arithmetic words...
FORTH-79 Standard word set registration complete
*** EXCEPTION (riscv64) ***
scause = 0x000000000000000d
sepc = 0x00000000be27c3c2
stval = 0x00000000000074d8
```
`scause = 0xd` is a supervisor-mode load page fault. `sepc` (~0xbe27c3c2) sat
near the top of QEMU's 1 GB RAM window — nowhere close to the kernel's own
`.text`/`.data`/`.bss` footprint (`0x400000`–`0xcdf9a0`, confirmed via `nm`/
`readelf` on the exact build that produced the log). `stval` (0x74d8, ~30 KB)
stayed fixed across rebuilds even as `sepc` drifted — the signature of a
corrupted return address rather than a fixed logic bug at one source line.
## Investigation
Two dead ends were ruled out before the real cause surfaced:
1. **ELF relocation / buffer overlap in the split-build loader.** The
codebase has a `!MONOLITHIC_BUILD` path (`elf_load_kernel()` in
`elf_loader.c`) that loads `kernel.elf` from the ESP into a UEFI
`AllocateAnyPages` buffer. This looked like a plausible overlap candidate
at first, but `Makefile.starkernel` defaults to `MONOLITHIC=1`, meaning
`kernel_main` is linked directly into the loader and that code path never
runs. Confirmed by checking the built binary for the investigation's own
diagnostic strings — absent, because the compilation unit was built with
`-DMONOLITHIC_BUILD` and the code was preprocessed out.
2. **`RAW_LOG()` silently no-ops on every non-amd64 arch.** All of
`load_kernel_from_esp()`'s narration (and an early ConOut-based
diagnostic) never reached the captured serial log, on riscv64, in any
build. `RAW_LOG` was defined only for `ARCH_AMD64`; everywhere else it
expanded to `((void)0)`. This made straightforward diagnosis impossible
until a real raw-UART writer was added for riscv64 (see Secondary Fix).
With real output available, `console_println()` checkpoints were bisected
through `vm_init_with_host()` and `sk_vm_bootstrap_parity()`. Every checkpoint
inside `vm_init_with_host()` printed successfully, including the very last
line of the function — but the checkpoint immediately after its *return*
never appeared. Re-running with a trivial, unrelated local-variable addition
shifted the crash earlier and changed the exception type entirely (from a
load fault to an instruction-fetch fault at `sepc == stval`, i.e. the CPU
tried to *execute* an unmapped address). A crash whose location and even
category shift under unrelated code changes is the standard signature of
stack corruption, not a fixed bug at a specific call site.
## Root Cause
`kernel_main.c` documented the gap directly, once read closely:
> On amd64, `kernel_entry.S` switches the stack from UEFI's default to a
> 2 MiB zero-initialised BSS stack and tail-calls this function as
> `kernel_main_impl`. On aarch64 and riscv64 the assembly trampoline is not
> yet implemented and the UEFI loader calls `kernel_main` directly.
amd64 has carried a dedicated stack-switch trampoline
(`src/starkernel/arch/amd64/kernel_entry.S`) since early on, with its own
comment explaining why: *"UEFI hands control to kernel_main() running on a
small UEFI-provided stack (typically 128 KB–256 KB). The FORTH interpreter +
DOE experiment loop can easily exceed that depth."* riscv64 never had the
equivalent. It ran the entire VM bootstrap — 27 chained word-registration
modules, physics/SSM init, Tripod capsule birth — directly on whatever small
stack EDK2's riscv64 firmware handed the loader, overflowing it and
corrupting a return address. aarch64 has the same theoretical exposure but
happens not to trip it, apparently because its firmware's default boot stack
is larger — incidental, not a guarantee.
## Fix
**`src/starkernel/arch/riscv64/kernel_entry.S`** (new) — RISC-V stack-switch
trampoline mirroring amd64's:
```asm
.section .bss
.align 4
.global g_kernel_stack
g_kernel_stack:
.space 0x200000
g_kernel_stack_top:
.global g_kernel_stack_top
.section .text
.extern kernel_main_impl
.global kernel_main
kernel_main:
ld t0, BOOT_INFO_KERNEL_STACK_BASE_OFFSET(a0)
bnez t0, .Ldynamic_stack
la t0, g_kernel_stack_top
j .Lstack_ready
.Ldynamic_stack:
ld t1, BOOT_INFO_KERNEL_STACK_SIZE_OFFSET(a0)
add t0, t0, t1
.Lstack_ready:
andi t0, t0, -16
mv sp, t0
mv s0, zero
tail kernel_main_impl
```
**`src/starkernel/kernel_main.c`** — riscv64 now builds `kernel_main_impl`
(invoked via the trampoline) instead of `kernel_main` directly, same pattern
as amd64:
```c
#if defined(__x86_64__) || defined(__riscv)
void kernel_main_impl(BootInfo *boot_info) {
#else
void kernel_main(BootInfo *boot_info) {
#endif
```
**`Makefile.starkernel`** — wires the new file into the riscv64 loader build:
```makefile
ifeq ($(ARCH),riscv64)
LOADER_ASM += $(KERNEL_SRC)/arch/$(ARCH)/kernel_entry.S
endif
```
## Secondary Fix
`RAW_LOG()` in `src/starkernel/boot/uefi_loader.c` was a no-op on every arch
except amd64, silencing all of the loader's own diagnostic messages on
riscv64 (and aarch64). Added a real raw-UART writer for riscv64, targeting
QEMU virt's 16550-compatible `uart8250` at MMIO `0x10000000` (matches
`Domain0 Region03` in the OpenSBI boot banner) — the same byte-polling
protocol as the existing amd64 port-I/O version, just MMIO instead of `outb`/
`inb`. Existing and future `RAW_LOG()` call sites in the loader now actually
produce output on riscv64.
## Result
| Criterion | Before | After |
|---|---|---|
| `sepc` after word registration | Wild address outside kernel image (`0xbe27c3c2`-ish, drifting per build) | N/A — no longer occurs |
| Exception | Load or instruction-fetch page fault, non-deterministic between builds | None |
| `[Hera] ok>` (riscv64) | Never reached | ✅ reached, Tripod fleet (Hera/Hermes/Artemis) runs to completion |
| `PASS: E2E msg flow` | Never logged | ✅ |
| `PARITY:MAMA_INIT` / `Init: Mama birth OK` | Never logged | ✅ |
| Loader `RAW_LOG()` output on riscv64 | Silent no-op | ✅ visible in serial log |
## Acceptance Testing
All three architectures must boot to `ok>`, run the Tripod fleet, and reach
`[Hera] ok>`. Per repo convention, run in this exact order, one QEMU instance
at a time, always `clean` before `qemu`:
```bash
make -f Makefile.starkernel ARCH=amd64 clean qemu
make -f Makefile.starkernel ARCH=aarch64 clean qemu
make -f Makefile.starkernel ARCH=riscv64 clean qemu
```
Verify each log:
```bash
grep "ok>" logs/<session>/<arch>/qemu-<arch>-*.log
grep "PASS: E2E msg flow" logs/<session>/<arch>/qemu-<arch>-*.log
grep "PARITY:MAMA_INIT" logs/<session>/<arch>/qemu-<arch>-*.log
grep "EXCEPTION" logs/<session>/<arch>/qemu-<arch>-*.log # must be empty
```
All three architectures confirmed clean on 2026-08-02 (see
`logs/20260802-064050/riscv64/`, `logs/20260802-064424/amd64/`,
`logs/20260802-064656/aarch64/`, committed alongside this fix).