d8a195b8d86aa56a1badb1171cb61dfbf4729335
222
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d8a195b8d8 |
WIREBIND: kill the orphaned console VM when the user VM birth fails
Found live during identity-heap-capacity testing (2026-09-07, hotplugging Zuse + 8 identities one at a time and measuring the kernel heap arena via a temporary allocator-stats probe, since reverted): every WIREBIND identity attach births two VMs in sequence -- a "console" VM, then the real "user" VM. When the second birth failed (arena fragmentation under concurrent VM load, a separate, not-yet-fixed capacity issue), capsule_wirebind_try_attach() logged the failure and returned, but the console VM that had *already succeeded* was never torn down. It stays live and registered under the identity's username, consuming its own ~228KB of the fixed 4MB kernel heap arena forever -- nothing ever points a real user at it, since WIREBIND only ever hands the caller the user VM's id. This turns every failed identity attach into a permanent net loss of heap rather than a neutral retry: confirmed live that a failed attach left the arena 228,576 bytes worse off than before the attempt, and every subsequent attempt starts from that worse baseline, compounding. Fix: call capsule_vm_kill(username) on the now-orphaned console VM before returning from the failure path -- the same teardown capsule_wirebind_eject()/capsule_wirebind_unclean_detach() already use elsewhere in this file (vm_cleanup() + sf_free(), confirmed live to actually reclaim per-word dictionary allocations, FABRIC-3.md §IX.2/§IX.3). Verified live with the same allocator-stats probe (written, captured, reverted -- not part of this commit): after the fix, a forced user-VM birth failure now returns the arena to exactly its pre-attempt byte count (3,526,256, matching the baseline precisely) instead of leaking 228,576 bytes. Three-arch clean qemu acceptance (single Zuse device, the standard regression case) passed on amd64, aarch64, and riscv64. The underlying capacity/fragmentation question (why the 7th concurrent identity's arena allocation fails at all despite technically-sufficient free bytes) is a separate, open architecture question -- not addressed here. See project memory for the full measured numbers. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ec88YKxxhZGG1RNnune78 |
||
|
|
e10fb76fb2 |
xhci: fix Configure Endpoint completion drop under concurrent multi-device enumeration
Root cause of the FABRIC-3.md §IX.5 follow-on: with 9 devices attached concurrently at boot (Zuse + 8 identities), only 1 of 9 ever completed enumeration and reached blkio_usb: MSC device ready -- the other 8 produced no error and no success, just silence. xhci_poll_events()'s deferred per-slot dispatch loop submitted a Configure Endpoint command (a Command Ring op) unconditionally for every slot with that action pending in a single pass -- unlike every other Command Ring op in this driver (Enable Slot, Address Device, Disable Slot), which is correctly gated behind dev->connect_state == XHCI_CONN_IDLE before ever submitting. With 2+ devices enumerating concurrently, this let multiple Configure Endpoint commands sit outstanding on the Command Ring at once. Their completion is correlated purely via the single shared dev->connect_state field (== XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT), not the completion event's own Slot ID -- so whichever slot's completion happened to land while connect_state still read AWAIT_CONFIGURE_ENDPOINT got correctly chained into SET_CONFIG, and every other slot's completion arrived after connect_state had already moved on, silently swallowed by the handler's generic "unrelated command completion" catch-all. No error path exists for this, which is why it produced total silence rather than a diagnosable failure. Root-caused live via temporary WARN-level diagnostic probes (written, captured, and fully reverted per the project's own probe convention -- this commit contains only the functional fix and its explanatory comment, no probe code) added at four points: the initial port scan, the connect handler, the Command Completion Event handler, and the deferred dispatch loop itself. The probes showed all 9 devices correctly completing Enable Slot + Address Device (ruling out the connect-state queue as the cause, the original hypothesis), then all 9 correctly submitting Configure Endpoint and all 9 commands completing successfully in hardware (code= SUCCESS, no errors logged) -- but only 1 of 9 ever got its next_action chained to SET_CONFIG. Fix: apply the same single-in-flight discipline this driver already uses for every other Command Ring op. If the Ring isn't free when a slot's Configure Endpoint action is due, put the action back on that slot instead of submitting a second command onto a busy Ring -- the next tick's dispatch pass retries it once the Ring frees up. Verified live: booting Zuse + all 8 identity drives concurrently (9 devices, one per real xHCI port via the XHCI_PORTS fix from the previous commit) now produces 9 "MSC device ready" lines and zero xHCI errors, where it previously produced exactly 1. Three-arch clean qemu acceptance (single Zuse device, the standard regression case) passed on amd64, aarch64, and riscv64 -- no change in that baseline behavior. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ec88YKxxhZGG1RNnune78 |
||
|
|
30c26ade3d |
Give each xHCI usb-storage device its own port; fix stale ZUSEDISK default
FABRIC-3.md §IX.5's "4th-device enumeration failure" was never a driver
bug: QEMU's default qemu-xhci controller (p2=4,p3=4) exposes only 4 real
dual-role ports, not 8 as the parameter names suggest. Attaching more
devices than that on bus=xhci0.0 without an explicit port= makes QEMU
silently auto-insert a USB2 hub past the 4th slot; the xHCI/BOT driver
correctly reports that hub as "not a Mass Storage/SCSI/BOT device" because
it genuinely isn't one, and every drive behind it is unreachable (no hub
descent in this driver). Confirmed live via QEMU's own `info usb` before
touching any kernel code.
Fix is entirely in the QEMU test harness, not the kernel:
- New XHCI_PORTS Make variable (default 16, overridable) sizes p2/p3 on
all three arches' qemu-xhci controller with real headroom above the
current 9-device identity roster, per Bob's standing ruling against
hardcoding a bound to today's scale (FABRIC-3.md §VII.4).
- ZUSEDISK_QEMU_ARGS now gives Zuse's drive an explicit port=1.
- QEMU_EXTRA's own doc comment shows the port= pattern for additional
devices.
Also fixed in passing: ZUSEDISK's default path (disk/zuse.img) was stale
-- that file was deleted from git at
|
||
|
|
2c1b3cd695 |
Four bugs found live verifying the 8 identity thumbdrives (FABRIC-3.md §IX)
All found by actually running the identity workflow §VII/§VIII made possible, not by code review: 1. Zuse/WIREBIND cross-contamination on detach: capsule_zuse_boot_logout() and capsule_wirebind_unclean_detach() both had no device parameter, so an unrelated device detaching (while the real owner's own stayed attached) incorrectly tore down the wrong session. Both now compare the departing device against their own tracked one, mirroring capsule_wirebind.c's pre-existing g_wirebind_attached_dev precedent. 2. Dictionary-entry memory leak: vm_create_word()'s sf_malloc()'d DictEntry (plus a second per-entry allocation for transition_metrics) was never freed by vm_cleanup(), in both the hosted and kernel implementations. Caused a real kernel PANIC after 8-9 repeated VM birth/kill cycles in one boot. Fixed by walking vm->latest in both. 3. sf_malloc/sf_free (alloc_kernel.c) was a 4MB bump arena with a deliberate no-op free, sized on "VM born once, never killed" -- fix #2 alone didn't stop the panic because free() itself discarded the pointer regardless. Given a real free list (first-fit reuse). 4. Headless-console gate didn't re-engage after a mid-boot logout: the original fix (sk_console_mark_login(), one-way sticky) only gated the first login of the boot. Replaced with a live check (sk_console_identity_present()) re-evaluated continuously, including inside sk_console_readline()'s own blocking idle loop -- the console is normally sitting blocked there when a hot-unplug logout happens, so checking only at the top of the REPL loop wasn't enough. Also: MINT now verifies its own write (verify_mint(), capsule_mint.c) by reading back through the same check a real attach performs, rather than trusting blkio_write()'s BLK_OK alone -- logged via log_message(), not console_println(), per direct instruction. Verified live, amd64: the full 8-identity repeated attach/detach cycle that previously panicked at the same point every time now completes clean, and a full serial-log sweep found zero bare unauthenticated prompts anywhere in the run. Three-arch clean-qemu acceptance passed. Still open, not fixed here: a 3+-simultaneous-device USB enumeration failure found in a separate live test, not yet root-caused. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018EjXFo7mPXjUMjfJeuUUz4 |
||
|
|
9e81de3f43 |
xHCI/BOT driver: genuine multi-device support (FABRIC-3.md §VII)
Per-slot registry (xhci_msc_slot_t/dev->msc_slots, sized off the controller's own reported max_slots) replaces the single-device scalar fields the driver carried since Milestones 2e-2h. Boot-time port scan no longer stops at the first connected device; a connect/disconnect that arrives while the Command Ring is busy is now queued and drained instead of dropped. blkio_usb.c and repl.c's own single-device state (device descriptor buffers, blkio_dev_t, attach bookkeeping) became per-slot registries the same way. Live multi-device testing (not just compiling) surfaced a second, more severe bug outside the original plan: transfer_purpose and next_action were also single scalars shared across the whole controller. Two devices enumerating concurrently could have one's completion silently overwrite the other's still-outstanding one, permanently stalling it with no error. Fixed by moving both per-slot and, critically, reading the Transfer Event TRB's own real Slot ID field instead of trusting external bookkeeping. Verified live, all three architectures, mandatory clean-qemu acceptance: existing single-device path unchanged, and two devices attached simultaneously (amd64) both progress independently through enumeration without corrupting or stalling each other. Also in this pass (implemented and verified in earlier turns this session, committed together per direct instruction): - Headless-until-login console policy: no prompt/banner until a real identity logs in via an attached thumbdrive (WIREBIND or Zuse, neither special), reusing EMERGENCY_CONSOLE_ENABLED as the debug/recovery escape hatch (now default-off). - KILL/g_repl_active_vm dangling-pointer fix: killing the VM the console is currently USE'd onto now detaches back to Hera first, matching the existing EJECT/UNCLEAN precedent. FABRIC-3.md §VII/§VIII carry full closure notes for all three. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018EjXFo7mPXjUMjfJeuUUz4 |
||
|
|
70dc8beba4 |
FABRIC-2.md §I.9 follow-on: blinking | cursor instead of static block
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 |
||
|
|
8edb95b65d |
FABRIC-2.md §I.9: fix the terminal phantom-linebreak defect
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 (
|
||
|
|
704573bdfc |
FABRIC-2.md §I.9: root-cause the terminal defect, not fixed (identify-only)
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 |
||
|
|
dbaead0af2 |
xhci: route driver chatter through log_message(), silence at default log level
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 |
||
|
|
1bd041e84d |
FABRIC-3.md §IV.3 item 5: pci_init() DTB path re-scoped, doc-only, nothing implementable this pass
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
|
||
|
|
9b6de5d6c7 |
riscv64: PLIC base address DTB-discovered, QEMU-virt constant as fallback (§V.3 item 3)
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 |
||
|
|
90ee8deb6d |
riscv64: skip satp Bare-mode switch when already Bare (§V.3 item 7 audit fix)
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 |
||
|
|
350287850f |
arch/aarch64: fix VBAR_EL2 stale comment + hardcode HVC PSCI conduit
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
|
||
|
|
dc9445abec |
apic.c (aarch64): fix GIC base address for real Pi 5 hardware
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 |
||
|
|
0f798256a0 |
FABRIC-3.md: aarch64/riscv64 boot-path code audit pass (§IV.3/§V.3 item 7)
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
|
||
|
|
6a6ff9353f |
FABRIC-3.md: amd64 boot-path code audit pass (§III item 6)
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 |
||
|
|
ecdb32e0d6 |
boot_media/rpi5/config.txt: Pi 5 native boot config (FABRIC-3.md §IV.3 item 6)
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 |
||
|
|
11c93773d0 |
FABRIC-3.md: pci_init() DTB path item was wrong -- re-scope, don't build the ECAM version
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
|
||
|
|
7187d68082 |
rpi5_native_boot.c: carve /reserved-memory out of the Pi 5 memory map
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 |
||
|
|
67e3fb7459 |
rpi5_native_boot.c: Pi 5 DTB->BootInfo constructor (FABRIC-3.md §IV.3 item 2)
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 |
||
|
|
281de9547c |
native_rpi5_entry.S: Pi 5 native (non-UEFI) boot entry stub (FABRIC-3.md §IV.3 item 1)
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 |
||
|
|
a32b0ebcbe |
rpi5_mailbox.c: wire the VideoCore mailbox message protocol (FABRIC-3.md §IV.3 item 3)
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 |
||
|
|
ca52ce8243 |
rpi5_dtb.c: wire fdt.c's node-scoped lookup into the Pi 5 UART/mailbox addresses
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
|
||
|
|
5e46f18fd9 |
fdt.c: node-scoped lookup extension (FABRIC-3.md SSIV.3/SSV.3 shared item)
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
|
||
|
|
8717416d36 |
FABRIC-3.md: version correction -- LITHOS_VERSION back to 2.0.0, plus a rename-gap fix
LITHOS_VERSION 2.0.1 was premature: per this project's own versioning policy, 2.0.1 claims SER5 hardware-track progress (RDRAND backend + thumbdrive image) that was never actually verified on real hardware -- that verification is FABRIC-3.md's own open topic. Reset to 2.0.0 (still a QEMU-only release, correctly). Verified 3-arch boot shows "LithosAnanke v2.0.0" in each serial log directly, not assumed from the Makefile edit alone. Also closes a real gap found in today's earlier FABRIC-series rename: Makefile.starkernel, Kconfig.kernel, scripts/bleach_zuse_img.sh, four proof/*.thy files, and isr.S were never swept -- the original file list only matched *.md/*.c/*.h/*.4th, silently skipping every other extension. Fixed with the same safe placeholder substitution. .claude/settings.local.json's historical permission-grant log and ClaudeEXPORT/'s frozen export were deliberately left untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var |
||
|
|
fcba528273 |
FABRIC-3.md: Task 1 closed -- master fast-forwarded to v2.0.1, verified
master ( |
||
|
|
b031b802e3 |
Rename FABRIC series: FABRIC.md->0, FABRIC-2.md->1, FABRIC-3.md->2, FABRIC-4.md unchanged
FABRIC.md -> FABRIC-0.md FABRIC-2.md -> FABRIC-1.md FABRIC-3.md -> FABRIC-2.md (the current/living document) FABRIC-4.md unchanged (new #3 to follow separately) Every cross-reference repo-wide updated to match, including doc-comment citations inside kernel source (.c/.h) files -- done via an ordered placeholder substitution (FABRIC-3.md->placeholder2, FABRIC-2.md-> placeholder1, FABRIC.md->placeholder0, then placeholders resolved to final names) in a single pass per file to avoid double-shifting already-renamed references. One line in capsules/font.4th grew past the 64-char block-format limit as a side effect of the longer filename; shortened it and reverified with mkcapsule --lint (34/34 pass) before rebuilding. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the foreground) after the fix; logs and DoE CSVs from this session's verification runs included per this repo's own audit-artifact convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var |
||
|
|
eeceec21a5 |
FABRIC-3.md §I.5: Milestone 7 trust tiers (QEMU-vs-real-hardware), closing it
Closes the contributor-capsule/trust-tier punch-list item. Decided direction: QEMU-vs-real-hardware conditional enforcement. Found before building on that decision: the obvious mechanism (expose TimerInfo.vm_mode) only works on amd64 -- aarch64 and riscv64 both had vm_mode hardcoded to 1 unconditionally, meaning they'd always report "running under QEMU" even on real hardware. Built real detection for both instead of shipping that: aarch64 checks the ACPI RSDP's OEM ID for QEMU's "BOCHS " SeaBIOS-heritage signature; riscv64 checks the devicetree root compatible property for "qemu". Confirmed vm_mode was otherwise unread anywhere else in either file first -- zero risk to existing timing behavior. CAPSULE_FLAG_CONTRIB (mkcapsule.c: FLAG_CONTRIB) path-matches on capsules/contrib/, mirroring FLAG_MAMA_INIT's exact-match pattern. contrib_capsule_refused() (capsule_birth.c) enforces: no additional check under QEMU (same WARN-only as everything else); on real hardware, a contrib capsule additionally requires CAPSULE_SIG_OK, since it has no other provenance to fall back on. Wired into capsule_birth_baby() and capsule_run_experiment(). Also updates §I.7 (Milestone 9): its stated precondition (Milestone 7 closing) is now met, flagged as stale rather than treated as a green light to design networking from nothing. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the foreground) -- compile/boot verification only; the real-hardware enforcement branch is unverifiable from this environment, same as all of §I.6. logs and DoE CSVs from this session's verification runs included per this repo's own audit-artifact convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var |
||
|
|
5567d03c12 |
FABRIC-3.md §I.4: magic-number content-type detection, closing Milestone 6
Closes both remaining Milestone 6 (PKI) punch-list items. Signature-status column: doc-only closure -- the underlying need was already redirected to capsules/BLOCK_MAP.md's real Signed column (2026-08-26); checking the box off as moot-as-worded rather than leaving an accurate-but-permanently-unchecked marker. Magic-number content-type detection (Section U item 14): built in tools/mkcapsule.c. detect_content_type() classifies a file's actual leading bytes (TTF/OpenType sfnt tags, DER's 0x30 SEQUENCE tag, or a printable-ASCII/TAB/CR/LF heuristic for text) against expected_type_from_ext()'s .4th/.md/.der/.ttf mapping; process_file() warns on mismatch, never refuses -- same WARN-first rollout this project already used for capsule signing. Verified against every real capsule in the repo (38 files) with zero false positives. This is the shared primitive Milestone 7's contrib-capsule validation can reuse next. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the foreground); logs and DoE CSVs from this session's verification runs included per this repo's own audit-artifact convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var |
||
|
|
4018fe8b04 |
FABRIC-3.md §I.2: FIRSTTOUCH + migration state machine (blk_meta_relocate_devblock)
Closes the block-subsystem punch-list item -- built exactly to §F.11's already-decided algorithm after re-verifying it against current blk_meta_t (a 2026-09-03 re-scoping note had wrongly claimed the chain fields no longer existed; they do, untouched by BMAPFMT). blk_firsttouch_claim(): one linear scan of Artemis's own device (new blk_get_first_disk_range(), correctly bounding the scan instead of the global multi-device LBN space), scattered-chain claim via prev_block/next_block/chain_length, owner_fp stamped on every member devblock, fails outright with no partial claim. blk_meta_relocate_devblock(): the real migration primitive -- bridges the existing FORTH-block-granularity blk_subsys_relocate_block() up to devblock granularity (BLK_PACK_RATIO=3, corrected mid-design), running it 3x and transferring blk_meta_t ownership fields. The "migration state machine" turned out to be just the 2 states BLK_FLAG_MIGRATING already reserved; the real design work was the trigger. Two were scoped in conversation (overflow onto Artemis; heat-based wear leveling); heat/wear-leveling is built and wired into sk_repl_idle() via blk_meta_t.write_count. Overflow is deliberately left open, precisely scoped (needs a slot-lookup-by-device-pointer call site threaded from WIREBIND) rather than guessed at. Also flagged, not fixed: BMAPFMT's owner_fp/CLAIMED and the pre-existing BAM allocator are two parallel, unreconciled accounting systems -- FIRSTTOUCH/relocate only touch the former. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the foreground); logs and DoE CSVs from this session's verification runs included per this repo's own audit-artifact convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var |
||
|
|
1d468a65b1 |
FABRIC-3.md §I.1: (user) console prompt segment, closing the 4.4s->4.3->1.11 chain
Extends the REPL prompt to "[VM name] (user) ok>" (e.g. "[Hera] (zuse) ok>") per the locked FABRIC.md §4.4s spec, unblocked by this session's own §I.3/§I.8 identity-tracking work. Adds capsule_wirebind_attached_username() alongside the existing tracked VMUuid, and a new sk_print_prompt() helper (repl.c) that all three prompt call sites now go through -- checks Zuse first, then a WIREBIND user, prints nothing when neither is attached. Closes 4.4s, 4.3 (console umbrella), and formally settles 1.11 (dirty-event granularity) as region-based per FABRIC-2.md's own "no independent path" ruling -- a decision closure only, not an implementation, so §17.4 (framebuffer heat/decay physics) stays open, re-scoped precisely: blocked on the dirty-region-tracking mechanism existing, not on 1.11's decision. Documents a reported-but-unreproduced terminal defect (§I.9) as a new punch-list item -- investigated the readline/keyboard-bridge code paths, found nothing conclusive, needs a live repro with a serial log before it's actionable. Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the foreground); logs and DoE CSVs from this session's verification runs included per this repo's own audit-artifact convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var |
||
|
|
60d9c2520e |
FABRIC-3.md §I.3/§I.8: WIREBIND EJECT/detach + EXPIRE re-scoped as logout
Closes §I.3 (Milestone 5 remainder): WIREBIND now tracks which VM is attached via the home-blocks USB path, and a new EJECT word plus the existing hot-unplug signal both flush/reset-console/kill through it (FABRIC-3.md §F.10). Closes §I.8 (EXPIRE/ACL), re-scoped: the original "admit the zuse session as a Stadium patron and reap on TTL" plan was invalidated a second time -- Zuse authenticates directly onto Hera, who is patron zero and permanently pinned, so there is no patron for a reap sweep to ever find. Built instead as a detach-triggered logout (capsule_zuse_boot_logout()), the same trigger EJECT/hot-unplug use for regular WIREBIND users, so neither identity is a special case. Required a companion fix: install_and_activate() used to skip re-running ACL-ZUSE-BOOT whenever the cert was already installed, which made a logout permanent for the rest of the boot; the outer re-attach gate now checks zuse_session (clears on logout) instead of zuse_cert_installed (a deliberate permanent one-way ratchet, left untouched). Verified 3-arch boot to ok> (amd64/aarch64/riscv64, each in the foreground) after both steps; logs and DoE CSVs from this session's verification runs included per this repo's own audit-artifact convention. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019YcT3H2PQeyujrzjqS3Var |
||
|
|
2b9fa02354 |
§H.12 steps 21-22: ELEVATE-REQUEST + ELEVATE-GRANT + SEND-ELEVATE-REQUEST
Phase 7 complete, closing out §H.12's punch list. MSG-DELIVER turned out to VM-EXEC payload text directly rather than dispatching by type, so the "handler" is ELEVATE-GRANT, a word the delivered text calls. New Hera-only C primitives (ZUSE-ELIGIBLE?, NAME>XT, ELEVATE-PUBKEY-UNPACK) stay plain and unconditional; capsules/zuse-eligibility.4th composes the actual eligibility check + ACL-ALLOW!/ACL-TTL! grant in FORTH. SEND-ELEVATE-REQUEST (common:messaging.4th) builds the payload text and sends it via the item-20-gated CH-REQUEST. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QgooKd5hJNtTYqB6CyK5f9 |
||
|
|
cb6e079a73 |
§H.12 step 20: CH-REQUEST initiator-only gate (MY-CH-ID)
Added VARIABLE MY-CH-ID to messaging.4th (fail-closed -1 default) and set it per-VM in hermes/init.4th and artemis/init.4th. CH-REQUEST now refuses if the caller-supplied 'from' doesn't match the calling VM's own id, closing a real spoofing gap found while implementing this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QgooKd5hJNtTYqB6CyK5f9 |
||
|
|
7d53344875 |
§H.12 step 19: ZUSE-ELIGIBILITY-ADD word, no gating (corrected mid-step)
Plain FORTH word wrapping zuse_eligibility_add() unconditionally. Two wrong first attempts (C-level zuse_session check, then a FORTH wrapper checking it) both corrected: zuse_session isn't a special axis needing its own gate anywhere -- Zuse's authority is the absence of any ACL restricting her, not a flag any word checks. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QgooKd5hJNtTYqB6CyK5f9 |
||
|
|
f4615cf605 |
§H.12 step 18: zuse_eligibility.c -- read/add/membership-check
is_member() (fail-closed) and add() (idempotent, chains new devblocks onto the tail as needed) over the item-17 eligibility-list devblock chain, mirroring capsule_zuse_boot.c's magic/version/CRC-64 validation convention. No callers yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QgooKd5hJNtTYqB6CyK5f9 |
||
|
|
21ad5f7373 |
§H.12 step 17: zuse_eligibility_list.h -- eligibility-list record format
Growable owner_pubkey[32]-list devblock type for the metadata fence, one slot over from zuse_genesis_marker_t (§H.5 Phase 6). Type only, no read/add/check logic yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QgooKd5hJNtTYqB6CyK5f9 |
||
|
|
46d89dd5be |
§H.12 step 16: block-acl.4th policy capsule -- Phase 5 (BMAPFMT) complete
capsules/block-acl.4th (blocks 4019-4020, next free range per BLOCK_MAP.md): BLK-ACL-CHECK (block# -- allow?), a real fast-deny check mirroring vm.c:611-624's pattern for blocks instead of words. First touch lazily claims the block (allow=1, TTL=256, same base as ACL.4th's own), matching the word card's default-permissive baseline. Not a stub -- genuinely does something on every call. Loaded via a new EXEC line in init.4th right after ACL.4th's own; confirmed ACL.4th's own activation is unaffected. Passed mkcapsule --lint. Live-tested via QMP keystrokes: 1 BLK-ACL-CHECK executed cleanly. Phase 5 (BMAPFMT) is now fully complete -- field layout, flags bits, C accessors, FORTH wrappers, and a real policy word. Verified 3-arch boot to ok> (amd64/aarch64/riscv64). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
405c713c4a |
§H.12 step 15: FORTH wrappers for BMAPFMT block-ACL fields
BLK-ACL-ALLOW@/!, BLK-ACL-TTL@/!, BLK-OWNER@ registered in block_words.c. BLK-OWNER@ packs the 8-byte owner fingerprint into one cell (cell_t is int64_t). No BLK-OWNER! -- ownership stays a controlled C-only operation. Live-tested via QMP keystrokes on a running instance: 1 BLK-ACL-ALLOW@ executed cleanly against a real block. Verified 3-arch boot to ok> (amd64/aarch64/riscv64) plus a hosted sanity build (shared source). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
15e6836ca3 |
§H.12 step 14: C accessors for BMAPFMT block-ACL fields
blk_owner_fp_get/_set, blk_acl_allow_get/_set, blk_acl_ttl_get/_set, blk_flags_get/_set -- thin read-modify-write wrappers over the existing blk_get_meta()/blk_set_meta() (caching/dirty-tracking already owned there). Sets up the C-primitive layer FORTH wrappers (step 15) will call, mirroring the word-level ACL system's own split. Verified 3-arch boot to ok> (amd64/aarch64/riscv64). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
c19cc07ee3 |
§H.12 step 13: blk_meta_t flags bit constants
BLK_FLAG_CLAIMED/BLK_FLAG_MIGRATING/BLK_FLAG_STALE (bits 0/1/2), matching the decided §F.4/§H.6 layout. Orthogonal bits, not a mutually-exclusive enum. Verified 3-arch boot to ok> (amd64/aarch64/riscv64). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
edd7effb5a |
§H.12 step 12: BMAPFMT field layout in blk_meta_t
Replaced the old 40-byte owner_id/permissions/acl_block/signature[2] with owner_fp[8]/acl_allow/acl_ttl (u32)/acl_reserved[3]/reserved_future, matching the decided §F.4/§H.6 layout. Found a pre-existing bug via a real offsetof/sizeof compile check (not hand math, per this step's own instruction): sizeof(blk_meta_t) was already 344, not the 341 its own BLK_META_PER_BLOCK constant and "341-byte slice" comment claimed -- harmless since that constant has zero callers anywhere. New size after this edit's own alignment padding is 336. Added a _Static_assert matching blk_volume_meta_t's existing precedent, and fixed the stale comment to point at it. BLK_META_PER_BLOCK itself untouched -- unused, out of scope. Verified 3-arch boot to ok> (amd64/aarch64/riscv64). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a268abe925 |
§H.12 steps 10-11: creator-ceiling enforcement, birth-time ACL snapshot
dictionary_snapshot_acl_from_parent(child, parent): walks the child's dictionary, copies acl_allow/acl_mode/acl_pinned/acl_ttl from the parent's matching word (by name, via vm_find_word() -- FIND's own lookup, not modified) onto the child's entry. One-time snapshot at birth, no live sync, matching H.3's decided rationale (a program developed against one ACL set must not have it silently changed by later parent changes). Called once, after dict_hash/parity logging rather than before -- the snapshot depends on the parent's current ACL state, which can vary run-to-run once Zuse elevations exist, so applying it earlier would break the "same capsule twice produces the same dict hash" determinism invariant. Verified 3-arch boot to ok> (amd64/aarch64/riscv64). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
ed86a759e1 |
§H.12 steps 7-9: thread real parent VMUuid through the birth call chain
Session.parent now comes from the actual birthing VM's own stadium_vm_id, not a hardcoded vm_uuid_hera(). Added a VMUuid parent parameter to capsule_birth_baby() and, one level up, to capsule_console_birth()/capsule_runcap_birth() (neither had a VM* in their own signature, but every caller did). Updated all 6 real call sites: BIRTH, CAPSULE-BIRTH, CONNECT-ARTEMIS, CONNECT-HERMES, RUNCAP-TEST, PAIR-TEST (mama_forth_words.c) and the console+user birth pair in capsule_wirebind_try_attach() (capsule_wirebind.c). Two functions had their vm parameter marked __attribute__((unused)), now genuinely used -- attribute removed. Steps 8 (Session.name from capsule name) and 9 (identity defaults to installed=0) were already satisfied by step 5's existing session_register() call and its identity-zeroing -- confirmed by inspection, no further code needed. Verified 3-arch boot to ok> (amd64/aarch64/riscv64). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
09998af999 |
§H.12 steps 5-6: pin Hera/Hermes/Artemis in generic capsule-birth admission
capsule_birth.c's generic admission block now registers a session for every born VM (session_register) and pins it (session_set_pinned) when the birthing capsule is Hera/Hermes/Artemis. Bug found and fixed via a temporary probe (written, run, captured, reverted): the fleet-foundation name check first used an exact-match comparison against "Hermes"/"Artemis", but capsule_name is actually "hermes:init.4th"/"artemis:init.4th" (the real namespace:filename convention) -- the check silently never matched, both would-be-pinned VMs stayed unpinned. Fixed with a new vm_name_prefix_eq_nocase() helper matching everything before a literal ':'. Probe confirmed pinned=0 before the fix, pinned=1 after, on all relevant VMs. Session.parent is hardcoded to vm_uuid_hera() for now (every birth through this path is Hera-initiated today); step 7 generalizes this to the actual birthing VM's own id. Verified 3-arch boot to ok> (amd64/aarch64/riscv64) on the final, probe-free code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
67793ea4a1 |
§H.12 step 4: Hera registers as session zero; punch list to checkboxes
Rewired stadium_birth_hera() to admit unpinned then register through session_register()/session_set_pinned() instead of setting STADIUM_FLAG_PIN directly on the candidate header. Self-referential parent (vm_uuid_hera(), vm_uuid_hera()), matching capsule_run.h's parent_vm_id == vm_id root convention. Soft-fail, non-fatal, if session_register() fails -- Hera's actual Stadium admission is what the patron-zero invariant is about. Wired session_boot_init() into kernel_main.c right after stadium_boot_init(), before stadium_birth_hera(). Also converted §H.12's punch list from bold "DONE" markers to this document's established - [ ]/- [x] checkbox convention (already used throughout §A), for consistency. Verified 3-arch boot to ok> (amd64/aarch64/riscv64), no soft-fail message on any arch, Hermes/Artemis births unaffected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
a621131ef6 |
§H.12 step 3: session_set_pinned/session_is_pinned pin-authority choke point
session_is_pinned() reads Session.pinned directly (authoritative, no Stadium re-derivation); session_set_pinned() writes both Session.pinned and the mirrored STADIUM_FLAG_PIN bit on the session's own patron cell, keeping Stadium's internal eviction/admission logic (which must stay self-contained) in sync without it calling back into session.c. Added Session.stadium_cell (index into stadium_cells()) -- necessary plumbing not in the original H.2 field list; the choke point can't reach the right patron header without it. Moved STADIUM_FLAG_PIN from a stadium.c-private #define to stadium.h (public) so session.c can reference it without a duplicate definition. Verified 3-arch boot to ok> (amd64/aarch64/riscv64). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
6d9fe3f515 |
§H.12 step 2: session-slot table, session_find/session_register
src/starkernel/vm/session.c: kmalloc'd-at-boot slot table sized from stadium_max_vm_count() (mirrors stadium.c's own StadiumVMQuota, not a fixed compile-time array as originally planned -- that table was already moved off a fixed array for the same "population isn't knowable in advance" reason). session_boot_init()/session_find()/session_register() implemented for real, no stubs; session_register() zeroes identity and leaves pinned=0, matching VMIdentity's own documented default and deferring pin policy to callers. Added session.c to Makefile.starkernel's explicit source lists. No callers yet. Verified 3-arch boot to ok> (amd64/aarch64/riscv64). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
668e36bb17 |
§H.12 step 1: add Session struct (type only)
include/starkernel/session.h: vm_id (VMUuid), pinned (int, authoritative over Stadium's STADIUM_FLAG_PIN per H.10), parent (VMUuid), name (fixed 64-byte buffer), identity (embedded VMIdentity, reusing the existing type rather than inventing a new one). No logic yet, no callers -- next step wires the session-slot array and register/find functions. Verified 3-arch boot to ok> (amd64/aarch64/riscv64). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
f81e9c92bc |
Fix framebuffer console: scroll drift causes progressive line overlap in TTF mode
fb_scroll_rows() hardcoded the pixel distance it physically shifts the framebuffer by as char_rows * 16 * scale -- the bitmap-font (font_8x16.c) cell height -- regardless of which glyph mode vt100.c actually had active. In TTF mode (the REPL's default, cell height 24px via VT100_TTF_CELL_H_PX) this meant every scroll_up(1) call physically shifted the framebuffer by only 16px while the text model (g_vt.rows, py_of()) placed each row 24px apart. That 8px-per-scroll shortfall compounds with every subsequent scroll: a few scrolls barely show it, but enough scrolls -- or scrolling quickly, which is just many scrolls in a short span -- accumulates into visible pixel overlap between rows, with newer lines drawn on top of the tail end of older ones. fb_scroll_rect() (the box-confined scroll added later for 4.4t) already carried a doc comment calling this out explicitly, describing its own explicit pixel_rows parameter as the fix for fb_scroll_rows()'s "fixed 16px-row assumption" -- fb_scroll_rows() itself was just never updated to match. Fixed by changing fb_scroll_rows()'s parameter from an implicit char_rows count to an explicit pixel_rows count (matching fb_scroll_rect()'s existing convention), and having its one caller (vt100.c's scroll_up()) pass lines * cell_h() -- the real active cell height -- instead of a raw line count for the callee to guess at. Verified: booted amd64 to the REPL (TTF mode active per sk_repl()'s own console_fb_enable_ttf() call), let boot chatter + WORDS output scroll the screen through thousands of accumulated scroll_up() calls, then measured every visible line's y-position via a QMP screendump. Spacing held at a perfectly consistent 24px (TTF cell height) top to bottom with zero drift -- the old hardcoded-16px bug could not have produced that after this many scrolls. Re-verified boot to ok> on all three architectures (amd64/aarch64/riscv64) per repo acceptance policy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |