09857b7228d52df70f2bf8cfb6ac0a804b9d8e87
87
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
09857b7228 |
G.2 (v2.0.0): unified rng_get_bytes() entropy entry point; virtio-rng sole backend
The QEMU-verifiable slice of the real-hardware RNG driver (per FABRIC-3.md §G.2). New include/starkernel/rng.h + src/starkernel/rng/rng.c provide the single entropy entry point: rng_init() probes the backend set (v2.0.0: virtio-rng only) and, on no backend, prints a loud boot-time warning while rng_get_bytes() returns RNG_ERR_NO_BACKEND - never silently degrading to a deterministic seed. The backend-selection switch in rng.c is the exact seam v2.5.0's per-arch drivers (amd64 RDRAND, riscv64 Zkr, aarch64 peripheral) plug into without touching the call path. Consumers route through the unified layer instead of virtio-rng directly: capsule_mint.c (identity seed + drive_uuid) and kernel_main.c phase 8 (rng_init()). virtio_rng.c stays as the sole backend. Built clean on amd64/aarch64/riscv64. QEMU amd64 boot: POST 1012/0/0 + ok>, "rng: backend = virtio-rng" + "entropy: ready", Zuse identity confirmed from thumbdrive - mint/cert behavior unchanged. FABRIC-3.md §G.2 v2.0.0 slice marked BUILT+VERIFIED. |
||
|
|
dc2f38a1e1 |
G.4 (2h): bounded xHCI event-ring drain fixes boot-attach livelock
Root cause of the G.1 follow-up boot-time attach race: on pathological controller behavior the xhci_poll_events() drain loop had no hard ceiling. ERDP is written back only when the loop exits, so the controller cannot reclaim event TRBs mid-drain; if it keeps producing events the head can chase the software dequeue pointer forever. xhci_poll_events() never returns, sk_repl_idle() never reaches its bot_msc_attach_pending check, and a fresh USB BOT device that finished SET_CONFIGURATION is left flagged-but-never- attached while the guest appears hung. Fix: bound the drain to a full ring (XHCI_EVT_RING_MAX_DRAIN = 256), so xhci_poll_events() always terminates and always writes ERDP each call. Unprocessed events keep their cycle bit and are re-read next poll; nothing is dropped. On the healthy path one drain processes only the one-or-few events the controller posts per chained command, so the bound never triggers except in the pathological case it breaks. Beyond the G.1 additions: a new macro in include/starkernel/xhci.h and a bounded loop in src/starkernel/usb/xhci.c. Builds clean on amd64. Verified across six consecutive fresh QEMU boots (previously intermittently hung). |
||
|
|
49a3faa331 |
G.1: xHCI bulk-endpoint stall recovery (per F.14), built + verified
Full BOT-spec stall recovery per FABRIC-3.md F.14: new STALL_ERROR handling, Reset Endpoint + Set TR Dequeue Pointer commands, CLEAR_FEATURE(ENDPOINT_HALT), escalating to Bulk-Only Mass Storage Reset, capped retries (XHCI_BOT_STALL_MAX_RECOVERIES=2) mirroring bot_tur_retries, clean terminal failure via xhci_stall_fail(). Purely additive recovery path off the non-success transfer-event branch; the normal path is unchanged. Builds clean on amd64/aarch64/riscv64. QEMU amd64 boot regression passes: zero stalls, BOT attach (READ CAPACITY10 -> READ10 -> home-blocks) completes, normal-path xHCI trace identical to baseline. Live stall injection is not provable under qemu-xhci; deferred to v2.5.0 hardware. FABRIC-3.md G.1 documented; ROADMAP release-versioning policy folded in. |
||
|
|
5689c397fc |
Bug-fix sweep: repl reentrancy, virtio/blocksys bounds, identity CRCs, LOG_LINE_MAX
Code review fixes, all compile clean (hosted gcc + aarch64/riscv64 kernel flags):
- repl.c (H1): reentrancy guards on the MSG-TICK idle pump. sk_repl_idle()
now defers when Hera is mid-interpret (g_mama_interpreting) or when its
own vm_interpret is on the stack (g_idle_pump_active), so a blocking
KEY/EXPECT/QUERY inside a dispatched line can no longer re-enter the
interpreter and clobber the in-flight input buffer.
- virtio_rng.c: clamp device-returned used_len to VRNG_BUF_SIZE before the
caller's data_buf copy, closing a device-controlled OOB read.
- block_subsystem.c: first-write path now keys off created_time==0 instead
of dead magic==0 so fresh blocks get a real created_time stamp; first_free/
last_allocated fixed to absolute Forth LBNs (set in blk_compute_fresh_geometry
from slot->start_lbn, no longer the wrong physical-BAM-index values from
compute_totals_from_B); physical-bounds guard on blk_meta_zone_read/write
prevents unsigned underflow on a corrupt fence >= device size.
- capsule_zuse_boot.c / capsule_wirebind.c: identity seed validated magic ->
version -> CRC-64 (compute_crc64 over offsetof(crc)) before trusting it,
so a corrupt/format-mismatched record is refused, never loaded.
- log.h / starkernel/log.h: unused LOG_LINE_MAX 256 renamed LOG_MSG_LINE_MAX
to lift the include-order collision with vm.h's LOG_LINE_MAX 64; stale
include-order comments dropped (kernel_main.c, shim.c, capsule_birth.c).
- FABRIC-3.md: three stale-doc carry-forward items closed [x] with
|
||
|
|
849b83b727 |
Zuse default-attach: xHCI initial-port-scan fix, ZUSEDISK wiring, mismatched-marker resync
xhci_bringup() now scans for already-connected ports at bring-up (xhci_scan_ports_for_already_connected()), not just later hotplug events, so a USB device present on the QEMU command line at launch is detected. Makefile.starkernel attaches disk/zuse.img on the xhci0 bus by default in all three arch qemu targets (ZUSEDISK=, empties for a bare boot). capsule_mint_identity() gained a drive_known_blank param to skip a fully redundant second homeblocks_sig_check() when the caller already confirmed HOMEBLOCKS_SIG_BLANK itself. Root-caused what looked like a hang after the drive attached: Artemis's fence still carried a genesis marker from before a mid-session reformat, while the reformatted disk/zuse.img read back BLANK -- a mismatched pair capsule_zuse_boot_try_attach() correctly declined to act on, leaving the boot idling at a plain ok> with nothing left to log (indistinguishable from a hang under slow TCG). Fixed by zeroing both disk/artemis.img and disk/zuse.img at their original sizes, giving a matched blank pair. Verified full three-architecture acceptance: amd64 fresh genesis-mint, aarch64/riscv64 clean reload against the same now-minted images. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KBjfeLPo71sUQ8zC7V7P5m |
||
|
|
09d78c99d0 |
BINDSTEP + fence-persistence fix: identity arc closed end to end
Two items, closed together per direct instruction.
1. Fence-persistence root cause, found and fixed: meta_fence_blocks
(the field gating whether blk_meta_zone_write() can succeed at all)
was carved out of what used to be unused padding in blk_volume_meta_t
-- the code's own comment already documented this. disk/artemis.img
was formatted before that field existed, so its on-disk bytes there
have always read back as 0, and the existing-volume load path
(blk_format_or_load_disk()) never recomputes it -- only a fresh
format does. Every "fence write FAILED" message this entire session,
old block-fence flow and new zuse_genesis_marker_t alike, traces to
this one thing. Patching the field in place without redoing the rest
of the geometry would risk corrupting whatever's already allocated
near the top of the volume, so the only safe fix is a genuine
reformat -- done, with explicit confirmation, since it discards
disk/artemis.img's accumulated persistent test state (regenerated
fresh at next boot regardless, not real data). Verified: fence write
now succeeds with no failure suffix, and the full mint-once ->
reboot -> reattach -> re-authenticate cycle works for the first time
this session ("Zuse: identity confirmed from attached thumbdrive",
ZUSE-SESSION? goes 0 -> -1 without re-minting).
2. BINDSTEP (FABRIC-3.md §F.9): capsule_wirebind_verify_cert() extracted
as a shared function so WIREBIND (the original attach) and BINDSTEP
(every USE of an identity-locked VM) check the exact same thing the
exact same way. mama_word_use() now re-verifies live, not cached,
whenever the target VM has VMIdentity.installed=1 -- reads whatever
drive is CURRENTLY attached, re-verifies its cert, compares owner
pubkey against the target's own installed identity, refuses on any
mismatch or no drive attached. A target with installed=0 (Hera,
Hermes, Artemis, any console VM) stays freely targetable, unchanged.
Two related bugs found and fixed live while testing BINDSTEP, not
assumed away: USE was Mama-only, so a console-paired session (§F.22)
had no way back to Hera at all -- any attempt to call USE from inside
a console VM hit "UNKNOWN WORD: USE", a genuine dead end. Per direct
instruction, USE isn't console-specific -- it should work VM-to-VM
universally, same as VM-EXEC already does -- so it's now registered in
register_child_vm_words() too. That alone wasn't enough: the console
relay (sk_repl_dispatch_line()) would have captured a bare USE call and
sent it to the paired user VM as a message instead of running it.
Fixed with a small suffix-match guard (sk_repl_line_calls_use()) --
real FORTH syntax always puts USE last, so a trailing-token check
reliably recognizes it without needing a full tokenizer, and it always
runs directly, never relayed.
Verified live end to end: USE on an unlocked VM works unconditionally;
USE escaping a console back to Hera now works; USE on an identity-
locked VM succeeds while its own drive is attached and is refused
once detached ("USE: FinT~user refused -- no matching identity
currently attached"). Clean 3-architecture regression, including
confirming disk/artemis.img's reformatted geometry loads correctly as
an already-recognized volume ("Artemis: LithosAnanke disk -- resuming")
on aarch64 and riscv64 too, not just the amd64 boot it was reformatted
under.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
|
||
|
|
6fc0ee33a9 |
WIREBIND: real thumbdrive-attach call site, no manual steps
Assembles pieces already built and individually verified this session -- CERTVERIFY (vm_identity_from_cert(), Phase A/B), RUNCAP, the console-VM + user-VM pair (§F.22) -- into one automatic sequence, replacing the RUNCAP-TEST/PAIR-TEST diagnostic words that exercised each piece by hand. New capsule_wirebind_try_attach() (capsule_wirebind.h/.c), called from sk_repl_idle() alongside capsule_zuse_boot_try_attach() on every HOMEBLOCKS_SIG_OK attach: sig->cert_offset==0 means this is Zuse's own genesis-mode drive (no cert region) -- that's already capsule_zuse_boot_try_attach()'s job, skip. Otherwise, with Zuse already authenticated this boot (nothing to verify a regular cert against otherwise), reads the cert devblock(s) and calls vm_identity_from_cert() against mama_vm's own zuse_cert_pubkey and the drive's own drive_uuid. On success: reads the drive's own user_identity_seed_t for its username, births a console VM + RUNCAP-born user VM pair (idempotent -- no-ops if that username is already live this session), installs the verified VMIdentity onto the user VM, and registers the "<username>~user" pairing sk_repl_dispatch_line() (repl.c, §F.22) looks for. Deliberately does NOT auto-USE the new console -- that stays an explicit, ACL-gated step (BINDSTEP, §F.9), not something a bare attach should trigger silently. Verified end-to-end live in QEMU, including a genuine negative case: attached disk/user1.img (signed by a different, earlier-session Zuse instance) and got a correct "cert verification FAILED -- drive refused" -- proof the check is real, not a rubber stamp. Minted a fresh identity with this boot's own Zuse, reattached, and got "WIREBIND: SamS attached and ready" printed with zero manual commands, followed by a working USE + async WELCOME relay end to end (queued, no UNKNOWN WORD, delivered and executed in the paired user VM on the next idle tick). Clean 3-architecture regression: Hermes/Artemis both birth live, no unexpected ACL denials or UNKNOWN WORD. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD |
||
|
|
b0f12710bb |
Console-VM + user-VM pair: real async message-passing relay
Console sessions now route through the same general VM-to-VM messaging system (Phase C) any VM can already use for its own reasons -- not a synchronous shortcut. Per direct instruction: real async MSG-SEND/ MSG-DELIVER (Option B), not a VM-EXEC-based synchronous relay, because messaging is a general capability, not a console-specific mechanism. New CONSOLE-CMD-EVENT message type (common:messaging.4th). New sk_repl_dispatch_line() (repl.c), called from both sk_repl_step and sk_repl_run in place of a direct vm_interpret(): if the active VM's own name has a live "<name>~user" counterpart registered, the raw input line is wrapped as an S"-embedded CONSOLE-CMD-EVENT MSG-SEND and interpreted on the console VM instead of being run directly -- the console's own next MSG-TICK (Hera's idle pump) delivers it into the paired user VM via VM-EXEC, same mechanism every other message already uses. Falls back to direct interpretation if there's no pairing, or if the line contains a `"` (known v1 limitation, warned about explicitly rather than silently mishandled). New capsule_console_birth() (capsule_console.h/.c): a bare VM whose only content is loading common:messaging.4th -- the console side of a pairing, parallel in shape to RUNCAP's user-VM birth but with fixed embedded content instead of a devblock read (no identity, no thumbdrive involved). New PAIR-TEST diagnostic word (mama_forth_words.c, matches RUNCAP-TEST's own precedent): births both halves of a pairing and registers the "<name>~user" mapping. Not the real pairing call site -- that's the eventual attach/onboarding flow -- this exists to exercise the relay live before that flow exists. Found and fixed a real, serious bug live: console_set_vm_name() stored the caller's raw pointer instead of copying it. mama_word_use() (USE) passes a VMRegistryEntry field living on its own stack frame -- once USE returns, that pointer dangles, corrupting every console tag after the first USE (observed directly as garbled "[[]" / binary-looking prefixes instead of "[CaptBob]"). Fixed at the source: console_set_ vm_name() now copies into internal storage. That surfaced a second, related bug across every console_get_vm_name()-based save/restore call site in mama_forth_words.c (BIRTH, VM-STEP, VM-EXEC, CONNECT-HERMES, CONNECT-ARTEMIS): saving just a pointer into the single internal buffer meant an intervening console_set_vm_name() call silently corrupted the saved value before the restore ever ran. New console_save_vm_name() copies into caller-owned storage; every save/restore site updated. Verified end-to-end, live in QEMU: typed WELCOME at a paired console VM -- it did not execute directly (no UNKNOWN WORD), printed ok immediately (queued, async), and on the next idle tick "[CaptBob~user] Minted identity -- default personality" appeared on its own -- genuine delivery and execution in the paired user VM through the real MSG-SEND/MSG-DELIVER pipeline. Console tags confirmed clean (no garbling) across all three architectures' full regression boot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD |
||
|
|
cc9521d2cc |
Retire emergency CLI: Zuse goes thumbdrive-resident, ACL.4th activated
Three tightly-coupled changes, verified together per Captain Bob's own "getting rid of the emergency cli" direction: 1. Zuse's identity is thumbdrive-resident, never system-resident. New zuse_genesis_marker_t (magic/version/zuse_pubkey[32]/crc) replaces zuse_cert_devblock_t's slot in the top-of-device fence -- the system now remembers only that a root identity exists and its pubkey, never a seed. zuse_cert_devblock_t is kept in the repo, marked superseded, no longer written by any code path. capsule_mint_identity() grows a genesis mode (issuer_vm=NULL): no cert is built or written (Zuse isn't verified against a separate signer -- she's recognized by pubkey match against the marker) and two new optional out-params (out_pubkey/out_seed) let the caller install the cert immediately after a genesis mint. New capsule_zuse_boot_try_attach() (capsule_zuse_boot.c), called from sk_repl_idle() on every fresh USB attach (the only point in the boot lifecycle a thumbdrive can actually be detected -- attach polling doesn't exist yet at kernel_main.c's old one-shot mint point, which is why that whole block is gone): no marker + blank drive -> genesis-mint; marker present + matching drive -> read its own user_identity_seed_t, install the cert. Either way, re-runs ACL-ZUSE-BOOT (zuse.4th) so zuse_session activates exactly like it always has for a same-boot cert install -- ACL-PIN only blocks redefinition, not re-execution, so no new C-side auth logic needed. 2. ACL.4th activated (capsules/init.4th) -- inactive all session until now. Found and fixed a real bug this immediately surfaced: zuse.4th's ACL-ZUSE-BOOT tried `['] ACL-ZUSE-BOOT ACL-PIN` from inside its own still-compiling definition -- the word isn't findable yet at that point, so the whole definition silently failed to compile every previous boot this session (dormant, since ACL.4th never loaded). Fixed: pin after the definition closes, not from within it -- it only needs to happen once anyway, and pinning doesn't block the re-invocation genesis/attach needs. 3. The unauthenticated emergency-CLI ACL bypass is retired (repl.c): `emergency_console = is_hera ? (zuse_session ? 0 : 1) : 0` deleted from both sk_repl_step and sk_repl_run. Every word run from Hera's own bare prompt now goes through ordinary ACL enforcement; emergency_console is driven only by the genuine C-level fault handler again. Added ZUSE-SESSION? (starforth_words.c), a read-only diagnostic matching ZUSE-PUBKEY@'s own precedent, to verify the whole chain directly rather than by inference. Verified end-to-end live in QEMU: fresh boot, no thumbdrive -> ZUSE-SESSION? reads 0. Attach a genuinely blank drive via QMP -> genesis mint fires automatically (no typing) -> ZUSE-SESSION? reads -1 (true). Hermes/Artemis both birth clean on all three architectures with ACL now actually enforced for the first time all session -- no denials, no UNKNOWN WORD beyond the deliberate POST self-test cases. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD |
||
|
|
6fd87923a5 |
MINT: parameterize with full name, username, email, phone
Extends user_identity_seed_t (version 2) with fixed-size full_name/ username/email/phone fields -- plenty of unused pad space (4016 bytes) was already there. Deliberately NOT encoded into the DER cert's Subject field: that would mean building a real X.509 RDNSequence (OIDs for commonName/emailAddress, PrintableString/UTF8String tagging), well past this project's own stated "deliberately not a general ASN.1/X.509 [builder]" scope. This human-readable profile data isn't security-relevant the way pubkey/serial are (the only two fields CERTVERIFY/BINDSTEP actually check) -- it travels alongside the keypair in the plain identity record instead. capsule_mint_identity() takes full_name/username (required, validated non-empty and within their fixed field widths) and email/phone (NULL or empty = null, matching the schema's own nullable convention). The MINT FORTH word's stack signature grows to 4 string pairs ( fname-c fname-u uname-c uname-u email-c email-u phone-c phone-u -- ok? ). Verified live in QEMU: minted two real identities with real profile data -- Zuse (full_name "Konrad Suse", username "Zuse", zuse@pantheon.org) onto disk/zuse.img, and a regular user (full_name "Captain Bob", username "CaptBob", capt.bob@pantheon.org) onto disk/user1.img -- then read the raw devblock bytes back off both images directly and confirmed every field byte-exact at its correct struct offset. Clean 3-architecture regression boot confirms no side effects. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD |
||
|
|
f6e2737f1e |
Phase E: MINT -- real keypair, Zuse-signed DER cert, working default identity
capsule_mint_identity() (new capsule_mint.h/.c): mints a fresh identity onto a blank/unminted thumbdrive -- real Ed25519 keypair from virtio_rng, a fresh drive_uuid (independent random draw, not derived from the identity seed, per FABRIC-3.md §F.8 decision 3), a Zuse-signed DER cert in the CERTVERIFY format, and a small working default personality (a real WELCOME word, not a stub -- FABRIC-3.md §F.6/§F.8's own "default personality content" question stays open, but whatever mints today must actually do something when RUNCAP births it). Refuses to overwrite a drive that already reads as a recognized home-blocks drive, mirroring WRITE(10)'s own refuse-on-non-blank posture (decided now, not just "reasonable by analogy" as §F.8 left it). x509_build_user_cert() (x509_ed25519.h/.c): the encode-side counterpart to the existing decode functions (x509_extract_ed25519_pubkey(), x509_verify_signature(), x509_extract_serial()) -- a minimal DER TLV writer producing exactly the fields those functions read. Host-tested round-trip against the real decoder before trusting it in the kernel, including a high-bit-serial case that exercises the DER integer-padding rule; all assertions pass (pubkey/serial round-trip, signature verifies against the real issuer, correctly rejects the wrong key and a corrupted signature). New user_identity_seed_t (user_identity_seed.h): the on-disk record for a minted identity's own keypair, same magic+version+fields+pad-to-4096+ real-CRC convention as zuse_cert_devblock_t and homeblocks_sig_t. Fixed devblock layout: sig(1), cert(2), seed record(3), default personality(4). New MINT word (mama_forth_words.c) and a small accessor (sk_repl_get_attached_blk_dev(), repl.h/.c) exposing the currently attached USB device regardless of home-blocks recognition -- MINT's own target is a blank drive, which by definition never sets Phase D's sk_repl_get_homeblocks_dev(). Verified end-to-end live in QEMU: MINT on a genuinely blank test drive, then (after a detach/reattach so the sig cache picks up the fresh header -- a known workflow gap, not fixed here, flagged for whoever builds the real Console onboarding flow) RUNCAP birthed a VM from that drive's own newly-minted content, and VM-EXECing its WELCOME word printed the default personality banner. The full mint-to-birth Tripod identity flow works end to end for the first time. Clean 3-architecture regression boot confirms no side effects on normal boot. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD |
||
|
|
e1e839258d |
Phase D: RUNCAP -- runtime capsule construction from thumbdrive content
capsule_runcap_birth() (new capsule_runcap.h/.c): builds a heap-only, single-entry CapsuleDirHeader + CapsuleDesc + CapsuleNameEntry + arena from a home-blocks drive's identity_src region (skipping the first devblock, reserved for MINT's user_identity_seed_t record) and hands it to the existing, unmodified capsule_birth_baby() -- no new birth mechanism, matching FABRIC-3.md §F.6's own trace. Found and closed a real gap in that trace along the way: capsule_birth_baby()'s signature check calls capsule_get_signatures(), which unconditionally returns the compile-time-baked global array -- meaningless for a heap-built directory, where index 0 would compare RUNCAP's own content against whatever real capsule happens to occupy that slot in the baked array (guaranteed-wrong, not a security check). Added an explicit skip_pki_sig flag (0 for all 4 existing call sites, 1 for RUNCAP): that content's trust comes from CERTVERIFY, a separate root, not the capsule-PKI chain. Also found live: capsule_birth_baby() never sets the registry entry's own .name (every existing caller does this itself afterward via capsule_vm_registry_set_name() -- RUNCAP now does too), and capsule_exec_payload() requires a "Block NNNN" header per chunk of content or it's silently skipped, never executed -- not a bug, but necessary context for whoever authors MINT's default personality content next. Added a small accessor pair (repl.h/.c) exposing the currently attached home-blocks device/sig -- the same gap F.9's own BINDSTEP scoping had already flagged, needed by both. Verified end-to-end live in QEMU: synthetic identity-source content written directly to a thumbdrive image's raw devblocks (no capsule build, no mkcapsule) was read, compiled, and executed by a genuinely new VM via a diagnostic RUNCAP-TEST word -- confirmed via VM-EXEC invoking a word defined only in that source. Clean 3-architecture regression boot (no RUNCAP drive attached) confirms no side effects. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD |
||
|
|
21bca315ff |
Phase C: distributed messaging capsule + idle-loop pump
Extract the messaging vocabulary (arenas, MSG-*/CH-*/MBR-* words) out of capsules/hermes/init.4th into a new shared capsules/common/messaging.4th that Hermes and Artemis each load at birth, giving every VM its own private MSG-ARENA/CH-ARENA instead of only Hermes having one. Hermes stays the owner of the one real, canonical COMMON-CH; Artemis subscribes into it via VM-EXEC at her own birth, and Hermes proactively subscribes Hera (idx 0) since Hera always exists first. Hera does NOT get her own copy: register_child_vm_words()'s own doc comment explains why the STADIUM-* primitives common:messaging.4th depends on are deliberately never registered in her dictionary (keeps her dict_hash off item 4.1's baseline). Confirmed live by loading it into her dictionary anyway first -- every colon-definition referencing an unregistered Stadium primitive was silently dropped (MSG-HEAT@/!, CH-HEAT@/!, MSG-COOL-ALL, MSG-TICK all missing after boot). Reverted that path; she orchestrates via BIRTH/VM-EXEC/VM-CALL instead. Added capsule_vm_registry_get_by_index() (capsule_birth.c/.h) for registry enumeration by birth-order position, and a pump in repl.c's existing idle hook that walks every live VM once per idle beat and VM-EXECs MSG-TICK into each one except Hera's own entry. Verified clean (no UNKNOWN WORD / VM-EXEC errors after birth) on all three architectures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD |
||
|
|
0ec91b517a |
Implement CERTVERIFY -- real DER cert verification, tested against OpenSSL
Phase B of the identity pipeline (FABRIC-3.md §F.7/§F.17): - x509_ed25519.c/.h: two new DER walkers alongside the existing pubkey extractor -- x509_verify_signature() (verifies a cert's outer Ed25519 signature over the raw, exactly-as-encoded tbsCertificate bytes, real signature verification against issuer_pubkey, rejects non-Ed25519 signatureAlgorithm) and x509_extract_serial() (extracts the serialNumber INTEGER, stripping a DER padding byte if present, for the drive_uuid binding decided in §F.7). - vm_identity.c: vm_identity_from_cert(), ties the three DER primitives together into the actual CERTVERIFY check -- signature verifies against issuer_pubkey, serialNumber matches this drive's own drive_uuid, subject pubkey extracts cleanly -- and populates a VMIdentity on success. acl_caps is caller-supplied, not read from the cert (nothing in the decided cert fields encodes capabilities); deciding what a verified identity is allowed to do is policy for the caller (WIREBIND, not yet built), not this function's job. Verified two ways: a standalone host-side test harness (not part of the kernel build) links the real source files against a real openssl- generated Ed25519 X.509 cert -- extracted pubkey, extracted serial, and signature verification all match ground truth, plus two negative tests (wrong issuer pubkey, corrupted signature) both correctly rejected. Then the actual kernel build verified live on all three architectures: clean compile, clean boot to ok>, Hermes/Artemis both live with no KILL. Same pre-existing, unrelated Zuse fence-write anomaly observed on all three (not caused by this change, not chased here). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD |
||
|
|
c07184e984 |
Add VMIdentity type, relocate homeblocks_sig_t off devblock 0
Phase A of the identity pipeline (FABRIC-3.md §F.2/§F.16, §F.13):
- New include/starkernel/vm_identity.h: VMIdentity{owner_pubkey[32],
installed, acl_caps}, its own header per the project's "give real-shaped
data its own header" convention (VMUuid's own precedent), embedded by
value on struct VM. acl_caps is an independent capability bitmask, not
an ordered privilege tier (decided 2026-08-28) -- Zuse isn't a
structurally special VM, her identity just has every bit set
(VM_IDENTITY_CAP_ALL). No individual capability bits assigned yet,
deliberate slack matching blk_meta_t's own acl_reserved precedent --
real bits get names once the operation they gate (BINDSTEP, MINT, ...)
actually gets built.
- Applied the devblock-1 relocation decided in §F.13: new
HOMEBLOCKS_SIG_START_FBLOCK constant (homeblocks_sig.h), repl.c's
homeblocks_sig_check() call site updated from the literal 0. Also
applied the still-owed blockmap_offset/blockmap_devblocks ->
identity_src_offset/identity_src_devblocks rename decided in §F.6 (no
other code referenced the old names). Updated the header's own stale
GPT-relative doc comments to match -- GPT was dropped permanently, this
location is final, not an interim stand-in.
Verified live on all three architectures: clean compile, clean boot to
ok>, Hermes/Artemis both confirmed live with no KILL (no regression from
last session's session-less fix). One pre-existing, unrelated anomaly
observed consistently on all three arches, not caused by this change (no
code touched here intersects Zuse's own fence-write path): "Zuse: minted
but fence write FAILED (not persistent)" -- flagged for its own
investigation, not chased here.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD
|
||
|
|
5e9802845a |
Implement SCSI WRITE(10), closing the graph's highest-leverage blocker
Direct mirror of the existing READ(10) implementation (FABRIC-3.md §F.1), data direction flipped: new XHCI_XFER_BOT_DATA_OUT/XHCI_NEXT_ACTION_BOT_DATA_OUT states, xhci_bot_send_write10()/xhci_bot_write_block()/xhci_bot_write_data_out() in xhci.c, new SCSI_CMD_WRITE10 opcode and BOT_CMD_WRITE10/BOT_TUR_CHAIN_WRITE10 enum values. usb_blk_write() in blkio_usb.c is real now, no longer the BLKIO_ENOSUP stub. read_only flips to 0 in blkio_info() now that it's proven. Verified live end-to-end on all three architectures with a genuine cold-reboot round-trip (not just a same-session read): BLK-CONFIRM-FORMAT's BAM/reloc writes and an explicit block content write both completed via clean WRITE10 cycles (CSW PASS), and the written byte read back correctly after a full kernel rebuild + fresh boot -- amd64=65, aarch64=170, riscv64=201, each at LBN 32734 on a disposable usbwrite-test.img attached via QEMU usb-storage. Added Makefile.starkernel's QEMU_EXTRA (empty by default, no behavior change) to attach the disposable test image for this validation; the drive must be hotplugged via QMP after boot reaches ok>, not attached at QEMU launch -- attaching before xhci_bringup()'s controller reset means no fresh Port Status Change event fires (see project_xhci_milestone_2d_polling memory). Found and reported, not fixed, during testing: EMPTY-BUFFERS (empty_all_buffers(), block_words.c) does not implement standard Forth-79 semantics -- it force-writes zero to every block on every attached device instead of discarding cache assignments. This corrupted disk/artemis.img during an earlier test run; restored from git, confirmed byte-identical. Avoided in the final validation runs (detach/reattach used instead to force a fresh read). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZGkimpfyh63EZyRkNbkPD |
||
|
|
030a3e6dc0 |
Milestone 6: hard-refuse flip, scoped to INVALID only -- closes Milestone 6
Before flipping, found CAPSULE_SIG_MISSING (no signature at all) is the normal state everywhere except this machine -- CI and any other checkout have no access to the offline key, by design. Refusing on MISSING the same as INVALID would brick boot everywhere but here. Decided (on request): enforce ONLY on CAPSULE_SIG_INVALID (a signature that IS present but doesn't verify -- unambiguous tampering/corruption evidence). MISSING/NO_ROOT_KEY stay WARN-only permanently. All three capsule_birth.c call sites now return CAPSULE_RUN_ERR_INVALID on CAPSULE_SIG_INVALID, after logging the same WARN as before. Verified on all three architectures, both directions, per the original rollout commitment: positive case (real signed capsules) reboots clean with zero warnings on amd64/aarch64/riscv64. Negative case (same one-byte signature corruption used for the WARN-only proof, on Mama's own init.4th) now genuinely refuses identically on all three: "capsule sig: init.4th: INVALID" then "Init: Mama birth FAILED". The feared "no ok> at all" blast radius didn't materialize -- kernel_main.c already had graceful error handling for a failed Mama birth (log and continue, pre-existing code); the kernel reaches a degraded ok> rather than crashing, on all three architectures. Final acceptance pass (real signed capsules, tampering reverted) clean on all three. Milestone 6 is now fully closed except magic-number content-type detection (shared with Milestone 4, separate scope, not started). Documented in FABRIC-3.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd |
||
|
|
2fc55f47e1 |
Milestone 6: mkcapsule signing + capsule_birth.c wiring, WARN-only
First attempt shelled out to `openssl pkeyutl -sign` (fork/execlp, not system() -- avoided shell string interpolation of the key path). Corrected on request: no new external host binary dependency when the repo's own code can do the job -- same standing preference as the earlier anti-file correction. Rewritten to link ed25519_sign() (already verified against OpenSSL in Phase B) directly into mkcapsule. New tools/pkcs8_ed25519.c: a narrow DER walker (same shape as x509_ed25519.c, deliberately not shared -- small enough that duplicating a few TLV-walking lines beat threading a header between the kernel crypto tree and host tooling) extracting the raw seed from the intermediate's PKCS#8 private key, plus a minimal self-written base64 decoder (PEM is openssl genpkey's default output; no decoder existed anywhere in the repo). Verified end-to-end before wiring anything in: the extracted seed's derived pubkey matches the cert's exactly, and a full self-contained sign+verify round-trip (zero openssl) passes. CapsuleDesc had no spare bytes, so signatures live in a new parallel CapsuleSigEntry array, emitted by a new `mkcapsule --sign-key <path>` flag (omitted/missing key -> has_sig=0 everywhere, graceful, not a build failure -- CI has no access to the offline key). New capsule_sig.c/.h: capsule_verify_signature(), a separate function, not folded into the already-tested capsule_validate(). Finds and caches the embedded intermediate cert's pubkey once per boot, then verifies against it. Wired into all three capsule_validate() call sites in capsule_birth.c via log_message(LOG_WARN, ...) -- never refuses yet, per the earlier staged-rollout decision. Verified independently, both directions, live in the real kernel: a full clean build (38 signed capsules) boots clean on all three architectures with zero warnings. Separately, hand-corrupted one byte of Mama's own init.4th capsule's stored signature (not its payload/hash, which capsule_validate() already catches and would have masked the test) and rebuilt just the changed object: produced exactly "capsule sig: init.4th: INVALID -- signature does not verify" on boot, and the kernel still reached ok> -- proving warn-only doesn't refuse anything yet. Reverted before the final, untampered 3-arch acceptance pass. Still open: flipping WARN to hard-refuse (separate, deliberate step) and the BLOCK_MAP.md signature-status column. Documented in FABRIC-3.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd |
||
|
|
431bcb1f34 |
Milestone 6: root CA generated, snakeoil intermediate embedded, DER parser
Root CA + snakeoil intermediate generated entirely offline (/home/rajames/CLionProjects/lithosananke-ca/, outside this repo, private keys chmod 600) per this milestone's own requirement: Ed25519, root self-signed 20-year validity, intermediate real-CA-signed (CA:TRUE, pathlen:0), chain verified via openssl. Snakeoil intermediate embedded as a capsule (capsules/pki/ snakeoil-intermediate.der) -- confirmed the font-capsule precedent needed zero new infrastructure, any non-.4th file under capsules/ embeds verbatim already. New x509_ed25519.c: a from-scratch, narrow DER walker (not general ASN.1/X.509, per this milestone's design decision) extracting the raw Ed25519 pubkey from a cert's SubjectPublicKeyInfo -- handles the optional v3 version field, verifies the AlgorithmIdentifier OID is Ed25519 rather than assuming, handles both DER length forms. Verified against ground truth: the extracted key from the real embedded cert matches openssl's own reported pubkey byte-for-byte; refusal path checked against truncated/garbage/empty/wrong-algorithm (real RSA cert) input. Compiles clean on all three architectures. Still open: mkcapsule signing step, wiring ed25519_verify() into capsule_birth.c's three validate call sites (landing warn-only first, per decision -- a bug here could stop every capsule from birthing, including Mama's own, on all three arches), and the BLOCK_MAP.md signature-status column. Documented in FABRIC-3.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd |
||
|
|
a8692681a8 |
Phase 8 C (5/n): Zuse's cert wired to the fence -- first-boot mint works
Replaces the crashed NVRAM approach entirely. New include/starkernel/zuse_cert_devblock.h: a standalone on-disk record (magic + version + 32-byte seed + 32-byte pubkey + a real CRC-64/ISO from day one, same discipline homeblocks_sig_t established) occupying devblock_from_top=0 of the fence. Its own header, not inlined at the boot call site, since the still-open MINT word will be a second consumer of this exact format. kernel_main.c's mint-or-load logic now reads the fence, installs an existing valid cert, or mints fresh via virtio_rng+ed25519_keygen and writes it. Runs right after virtio_rng_init(), before capsule_birth_mama() -- unlike the crashed NVRAM attempt, raw block I/O against Artemis's already-proven device has no boot-timing risk, so the earlier "re-invoke ACL-ZUSE-BOOT after Mama birth" workaround is gone; ACL.4th's self-activating ACL-ZUSE-BOOT sees a correct cert on its one ordinary pass. Verified independently across every real scenario, never trusting the kernel's own report: fresh mint decodes correctly on disk with a CRC confirmed by a from-scratch Python re-implementation of the algorithm; a reboot without reformatting loads back byte-for-byte identical seed/pubkey (genuinely "mint once, ever"); a pre-fence volume refuses cleanly (no crash, no silent data loss, honest "not persistent" reporting); the real, untouched disk/artemis.img exercises the same graceful-refusal path identically on all three architectures. Phase 8's core arc is now functionally complete: real entropy -> real signing -> real anti-file block-native persistence -> a first-boot mint that survives reboots. Still open: the ongoing MINT word for minting additional regular users. Documented in FABRIC-3.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd |
||
|
|
e5cbc71f46 |
Phase 8 C (2/n): expand cert storage; NVRAM persistence crashed, reverted
Cert storage expanded from the old 16-byte placeholder to a real 32-byte seed + 32-byte pubkey. vm_zuse_cert_install() now has a kernel-side duplicate in src/starkernel/vm/vm_core.c -- the kernel build's VM_EXCLUDE list drops src/vm.c entirely (same reason vm_set_base() already has two independent copies), so the hosted-only version added earlier this session was never actually linked into the kernel. FORTH-side ZUSE-CERT-LO@/HI@ replaced with ZUSE-PUBKEY@ (i -- u) over the public half only; ACL-ZUSE-BOOT now checks ZUSE-CERT-INSTALLED? before authenticating instead of unconditionally. Attempted NVRAM-based persistence (GetVariable/SetVariable) for the first-boot mint flow: page-faulted inside OVMF's variable service (CR2 in the flash MMIO window). Moving the call site to match the one proven-safe existing SetVariable call site in this codebase produced the identical crash -- not a timing issue. Localized with debug markers (one boot): GetVariable works; SetVariable with real data never returns. The existing "working" precedent call is actually a delete-of-nonexistent-variable (size=0, data=NULL), a cheaper path that never touches flash, so it proved nothing about real writes. Root cause: this kernel's VMM never maps the region OVMF's variable service needs for real flash writes -- a genuine gap in UEFI runtime- services support, not Zuse-specific, and not obviously fixable in a 3-arch-uniform way (flash window location is firmware/arch-specific). Independently, storing the raw seed in RUNTIME_ACCESS NVRAM would have been a real security defect regardless of the crash -- readable by any later-loaded UEFI app or the booted OS. Reverted to a known-safe state: all NVRAM/mint code removed from kernel_main.c, init.4th's ACL.4th line back to its documented commented-out default. Verified clean compile and clean boot on all three architectures. Cert storage expansion (the part that works) stays. A dedicated system-identity disk (virtio-blk, already proven for writes via Artemis) is the recommended next substrate -- not yet decided or built. Full investigation documented in FABRIC-3.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd |
||
|
|
53e6c5709f |
Phase 8 B: real Ed25519 keygen/signing, verified against OpenSSL
Extends the previously verify-only ed25519.c with ed25519_keygen() and ed25519_sign() per RFC 8032 5.1.5/5.1.6, reusing every point-arithmetic primitive verify already had -- only seed expansion/clamping and per-message nonce derivation are new. Signing is deterministic; only keygen ever touches entropy, via a caller-supplied seed (virtio_rng, Phase A) -- keygen still generates nothing itself. New scalar_muladd() (scalar25519.c) for signing's S = (k*a + r) mod L, the one scalar op verify never needed. Schoolbook multiply into a u128 wide accumulator with one final carry pass -- deliberately the same shape as fe25519.c's existing multiply, which has a documented history of a real bug from carrying mid-accumulation instead of in one pass. Verified against an independent implementation, not self-consistency: a throwaway host harness against Python's cryptography library (OpenSSL- backed) across 6 trials (5 random seed/message pairs + the empty-message case) produced byte-for-byte identical pubkeys and signatures every time. Clean compile on all three architectures and a full 3-arch QEMU acceptance boot, conservation intact, no panics or guest errors. Nothing calls the new functions from the live kernel path yet -- that's Phase C (the MINT word itself), still open, documented in FABRIC-3.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd |
||
|
|
309e792f07 |
Phase 8 A: virtio-rng entropy source for real Ed25519 signing
The kernel's ed25519_verify() is deliberately verify-only -- no signing, no keygen, no entropy source. That conflicts with the on-device MINT word vision (Zuse signing new user certs live at runtime), so this reopens that constraint on request rather than reshaping MINT around verify-only. vm_uuid.h already found the real gap: amd64 has RDRAND, riscv64 has Zkr, but QEMU's aarch64 CPU models have neither -- confirmed against QEMU 10.2.1. A deterministic PRNG (fine for VM UUIDs) is not safe for key generation, so this adds a virtio-rng device instead of a per-arch split: real host entropy, identical guest-side protocol on all three arches. New src/starkernel/virtio/virtio_rng.c + include/starkernel/virtio_rng.h, transport plumbing mirroring the existing virtio_blk.c driver exactly. Wired into kernel_main.c boot, -device virtio-rng-pci added to all three QEMU targets. Verified live (temp probe, written/run/captured/reverted): 16 real bytes pulled through the full request/notify/poll round trip on all three arches, three different values confirming real entropy. Final boot against the reverted, permanent code: clean compile, clean boot to ok> on amd64/aarch64/riscv64, Stadium conservation intact, no panics or guest errors. Ed25519 keygen/signing itself (Phase B) and the MINT word design (Phase C) remain open, documented in FABRIC-3.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U14ET9CWAtbQMbYqomKgXd |
||
|
|
2c45744995 |
Implement homeblocks_sig_check(): the drive signature check (Phase 8)
Real, complete verification logic -- not yet wired to any write path. homeblocks_sig_check(dev, sig_start_fblock, out_sig) reads the 4 consecutive 1KB blkio forth-blocks the 4KB header spans, verifies magic -> version -> CRC-64 in order, returns HOMEBLOCKS_SIG_OK/_BLANK/ _BAD_VERSION/_BAD_CRC/_READ_ERROR. Reuses block_subsystem.c's existing CRC-64/ISO (compute_crc64, previously static/file-local, now exposed via block_subsystem.h) rather than a second CRC implementation -- same algorithm already proven via per-block checksums. Takes the header's starting block as a plain parameter rather than resolving it internally: verifies a signature given a location, finding that location (GPT-partition-relative) stays the caller's job. Verified against the actual shipped code, not a reimplementation: a standalone host test links the real homeblocks_sig.c against a fake in-memory blkio_dev and exercises all four outcomes -- blank media, a correctly-minted header (round-trips drive_uuid/minted_time_ns), a flipped CRC, an unrecognized version. All four pass. A full QEMU-hotplug live test isn't proportionate yet since nothing calls this function from the live kernel path -- wiring it into the attach path is the next punch-list item. Clean zero-warning compile and clean boot on all three architectures confirms no build/link regression from exposing compute_crc64 and adding the new source file to every kernel build. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn |
||
|
|
10b96870c5 |
Add homeblocks_sig.h: drive signature header struct (Phase 8 kickoff)
Implements the design from the previous commit as-is: homeblocks_sig_t (4096 bytes, magic+version+drive_uuid+timestamp+cert/blockmap offset reservations+real hdr_crc), HOMEBLOCKS_SIG_PACK/_GET_MAGIC/_GET_VERSION macros mirroring CAPSULE_MAGIC_PACK's bit layout, and a C99 compile-time size assertion matching stadium.h's own discipline. Verified standalone (sizeof == 4096, clean under -std=c99 -Wall -Wextra -Werror) -- nothing consumes this header yet, so no functional kernel change and no 3-arch acceptance boot needed for this step. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn |
||
|
|
3e0966095c |
Stadium: real VM-patron admission + real COOL dispatch at KILL (FABRIC-3.md §B)
capsule_vm_kill() had zero Stadium involvement (vm_cleanup()/sf_free() only), and child-VM birth only ever called stadium_grant_quota() -- a resource pool for the VM's own future word/block patrons, never stadium_admit() for the VM itself. stadium_birth_hera() looked like a precedent but admits Hera into her own quota as a permanently pinned cell 0, which can never reach stadium_evict() -- not a working example of COOL firing for a VM. Adds size_t stadium_patron_cell to VMRegistryEntry. At birth, right after the existing stadium_grant_quota() call, admits a candidate into the new VM's own quota mirroring stadium_birth_hera()'s shape (identity=0, mass=1, behaviour=COOL) but deliberately unpinned -- pinning would need a new "unpin" primitive (none exists) to ever evict it later, and unpinned costs nothing since nothing wires COOL's dispatch body to kill anything; the worst case of an unrelated natural eviction is stale bookkeeping, tolerated the same way stadium_word_forget() already tolerates staleness elsewhere. At capsule_vm_kill() and capsule_vm_kill_all_nonmama(): stadium_evict() the tracked cell if still resident, silently tolerating refusal (already gone). stadium_dispatch()'s COOL case needed no new payload body -- same as it already is for words, where COOL has no defined extra action beyond stadium_evict()'s own universal reservoir credit. On investigation this turned out not to be entangled with the still-iterating Tripod/Zuse/messaging vision after all -- birth and kill already funnel through two single choke points, so the earlier deferral (previous commit) was overcautious. Verified live: a second, new "Stadium: dispatch cell=... behaviour= COOL" now fires immediately before every PARITY:KILL line, for both Hermes and Artemis, distinct from the pre-existing COMMON-CH word-eviction self-test's own COOL print. Conservation (resident_sum + reservoir == Q48_ONE) intact throughout. Clean zero-warning compile and clean boot on all three architectures. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn |
||
|
|
c7c9332321 |
Stadium: real block-patron admission + MIGRATE dispatch (FABRIC-3.md §B)
stadium_admit()'s mass==1 refusal looked like a hard blocker for 1024-byte blocks, but stadium_word_dispatch()'s real candidate construction proves Stadium cells carry pure identity/heat/bookkeeping, never the resident's actual content -- a block patron follows the same shape (identity=LBN, payload unused), so this was real, scoped work, not a case for stubbing. New stadium_blocks.h/.c mirror stadium_words.c's admission/cooling shape, keyed by (quota_slot, lbn) in a fixed-capacity open-addressing hash table (tombstone deletion) instead of a dense array, since LBN space isn't densely bounded like word_id. Wired into block_word_block()/buffer()/ update() (block_words.c), __STARKERNEL__-guarded. stadium_dispatch()'s MIGRATE case now calls blk_flush(lbn) for real instead of printing "(stub)". Three new Kconfig constants (STADIUM_BLOCK_HEAT_QUANTUM/ STADIUM_BLOCK_COOL_RATE_Q48/STADIUM_BLOCK_TRACK_CAP_MULT) mirror the word-patron ones, same three-layer wiring. VM-COOL/DELIVER/EXPIRE stay explicit punch-list items -- VM-COOL deferred pending the still-iterating Tripod/Zuse/messaging vision, DELIVER/EXPIRE are their own future subsystem integrations per FABRIC.md's own "open, not resolved" notes. Verified clean compile (zero warnings) and clean boot to REPL with conservation intact (resident_sum + reservoir == Q48_ONE) on all three architectures (amd64/aarch64/riscv64); BLOCK/BUFFER touches exercised live from the REPL with no crash; a 22,000-distinct-block flood loop against an artificially shrunk Stadium ran clean under heavy admission load. A live MIGRATE console fire was not directly observed this session (root-caused to a pre-existing reservoir-floor/density-eviction interaction unrelated to this change, documented in FABRIC-3.md) -- flagged as an honest follow-up, not silently claimed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn |
||
|
|
af267a52a6 |
Artemis Milestone 2h: hot-detach -- 2h complete
blk_subsys_detach_device() (block_subsystem.c) walks the device chain, refuses removal of anything but the current tail (a mid-chain removal would corrupt every later slot's start_lbn -- this architecture's own doc already argues USB stays last specifically to avoid that), unlinks, shrinks total_user_lbn, closes and frees the slot. Discards rather than flushes dirty state -- the device is physically gone by the time this runs (PORTSC disconnect only). Trigger wiring mirrors the attach path: bot_msc_attached (set only once attach actually succeeds) gates a new bot_msc_detach_pending flag set at PORTSC disconnect (not Disable Slot completion, which is conditionally skipped and would miss concurrent connect/disconnect pairs), consumed in sk_repl_idle(). Advisor flagged the real hazard ahead of time: block_words.c's VM block window (blk_vm_lbn[]/blk_vm_cbuf[]) can go stale across a detach then a same-LBN re-attach, and suggested a pointer-identity re-check in blk_vm_load() as a minimal fix. That fix was implemented, then directly falsified by its own designed-for-this test: attach a blank device, read a block (populating the cache), detach, re-attach a device with distinct content at the identical LBN, read again -- served stale content from the first device. Root cause, confirmed live: glibc's allocator hands free(slot) straight back to the very next same-size calloc(), so the "fresh" and stale pointers were bitwise identical despite being two different devices. Fixed properly with a monotonic blk_subsys_epoch() counter (bumped on every attach/detach) checked by a new blk_vm_check_epoch() helper at the one choke point (blk_vm_find(), plus blk_vm_flush_all() which reads the same arrays directly) that covers every path touching the window cache -- unfooled by address reuse. Verified live with a new disk/usb-thumbdrive-test2.img fixture (distinct content from the existing blank test image): attach A, read (cache hit populated), detach, re-attach B at the same LBN, read again -- correctly ran a fresh device read and returned B's real content, not A's stale cached zeros. The failing pointer-comparison attempt's own capture log kept as evidence, not deleted. All three architectures re-verified clean. FABRIC-2.md Section X 2h marked complete -- enumeration through hot-detach all live and verified; only WRITE(10) (2g's own still-open item) remains unimplemented in the driver, not blocking anything here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn |
||
|
|
3b085dd875 |
Artemis Milestone 2h: blkio_usb.c backend -- USB thumb drive is now a real block device
Wires a hot-plugged USB Mass Storage device into the block subsystem's unified LBN chain. blkio_usb.c/blkio_usb.h mirror virtio_blk.c/ virtio_blk.h's established shape exactly (singleton state, blkio_vtable_t, a blkio_usb_open_msc() "find" function playing virtio_blk_find_artemis()'s role): read() translates a Forth block into a SCSI LBA/count pair and calls xhci_bot_read_block() + xhci_bot_wait_for_idle(); write() returns BLKIO_ENOSUP (no SCSI WRITE(10) exists yet, and blk_format_or_load_disk() never writes at attach time, so read-only is sufficient -- confirmed by reading that function first, not assumed). Refuses (-2) if the reported SCSI block size doesn't evenly divide the 1024-byte Forth block size. Connect-time wiring reuses the bot_msc_attach_pending/consume-in- sk_repl_idle() shape the prior increment's temp probe already validated, now made permanent: SET_CONFIGURATION sets the flag, sk_repl_idle() (strictly after its own xhci_poll_events() call returns) calls blkio_usb_open_msc() then blk_subsys_attach_device(). Verified live via hot-attach: full chain from USB connect through 'blkio_usb: MSC device ready' to 'blk: disk 'StarForth Volume' v2 LBN 26074..75184 (49111 user blocks)' -- real attachment, disk image confirmed byte-for-byte untouched after. Chased a real debugging detour along the way: the attach initially appeared silent (no blk: log line) -- traced to LOG_INFO filtering at the default LOG_WARN boot level, not a functional bug (settled via a temporary log-level bump, reverted after capture; also found and reported, but did not fix, a pre-existing unrelated Makefile.starkernel bug where --log-level=info via KERNEL_ARGS breaks printf parsing). All three architectures re-verified clean. FABRIC-2.md Section X 2h updated -- only hot-detach remains for 2h. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn |
||
|
|
d686f28853 |
Artemis Milestone 2h (foundational): sync wait bridge + SCSI READ CAPACITY(10)
Closes the gap block_subsystem.c needs before any of 2h's real work (blkio_usb.c, attach wiring, hot-detach) can start: this driver is fully async/polled with no way for a synchronous caller (blkio_read()/ blkio_info() etc.) to get a result back. xhci_bot_wait_for_idle() is a bounded busy-wait over xhci_poll_events() -- MUST be called only from outside xhci_poll_events()'s own call frame, never from within it or a next_action dispatch (recursion into live Event Ring/ERDP processing, same class of hazard already documented for doorbell rings in this driver). xhci_get_dev() exposes the module-static device handle to outside callers that didn't observe the original hotplug event. SCSI READ CAPACITY(10) (opcode 0x25) is the other half -- nothing could learn a device's block size/capacity before this. First attempt sent it bare and hit the classic first-command UNIT ATTENTION (CSW FAILED); fixed with the same TUR-guard pattern READ(10) already used, generalized via a new bot_tur_chain_target field so TEST UNIT READY's PASS handling can chain into either command. bot_data_buf grown 512->1024 bytes (one Forth block = two 512-byte SCSI blocks, per block_subsystem.c's own 1KiB-unit convention). Verified live via a temporary probe (hot-attached disk/usb-thumbdrive- test.img via QMP, reverted after capture): TUR-guarded READ CAPACITY10 correctly reported last LBA=0x1ffff, block size=0x200 -- exactly 64MiB, matching the test image byte for byte -- followed by a TUR-guarded 1024-byte/2-block READ10, both PASS. All three architectures re-verified clean, probe-free boot to ok> on the reverted tree. FABRIC-2.md Section X 2h updated with the writeup; the blkio_usb.c backend itself is next. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn |
||
|
|
65effbd1ba |
Artemis Milestone 2g: TEST UNIT READY unit-init sequence -- READ(10) now PASSes
Roots out the CSW status FAILED left unexplained in the prior increment: a freshly attached SCSI target's standing UNIT ATTENTION condition, which a bare READ(10) with no retry can never clear. xhci_bot_send_test_unit_ready() sends SCSI TEST UNIT READY (SPC-4 6.33) ahead of the real command; the CSW handler now tags command kind (bot_cmd_kind) to distinguish a TUR completion from a READ10 completion, chains TUR PASS into the real READ(10), and bounded-retries TUR on FAILED/PHASE ERROR (bot_tur_retries, capped at XHCI_BOT_TUR_MAX_RETRIES). xhci_bot_read_block() is the new intended entry point tying lba/num_blocks/block_size + the TUR-first sequencing together. Verified live via a temporary probe (hot-attached disk/usb-thumbdrive-test.img through the running instance's QMP socket), captured on amd64: full chain CBW(TUR) -> FAILED -> retry -> PASS -> CBW(READ10) -> Data-In -> CSW PASS. Probe reverted after capture; all three architectures re-verified clean, probe-free boot to ok>. FABRIC-2.md Section X 2g updated with the writeup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CXjAPTEKrgY2Mrk25KoLDn |
||
|
|
c54ea24aaf |
Artemis Milestone 2g: Data-In stage read and CSW receive/validation
Completes the CBW -> Data-In -> CSW chain for READ(10) started last commit. xhci_bot_read_data_in() and xhci_bot_receive_csw(), each a single Normal TRB on the bulk IN Transfer Ring via a new xhci_bulk_in_enqueue_and_ring() helper (mirrors the OUT-side helper from CBW send). All three stages now chain automatically via the existing deferred next_action pattern: CBW completion defers into Data-In, Data-In completion defers into CSW receive, CSW completion is where signature/tag/status validation happens. Data-In reads into a new fixed 512-byte bot_data_buf -- single-block scope for this increment, matches QEMU's usb-storage reported block size; xhci_bot_send_read10() now refuses rather than overflow/truncate if a request exceeds it. CSW validation (BOT spec section 5.2) checks dCSWSignature and dCSWTag (a new bot_last_tag field, latched from the CBW) before trusting bCSWStatus at all, so a garbled/misaligned CSW read can't be misread as a clean pass. usb_bot_csw_t follows the same struct-with-explicit-length-not-sizeof discipline as usb_bot_cbw_t. Verified live via a temporary probe (written, run once, log captured, reverted per this project's own probe convention), all three architectures, byte-identical: the full CBW -> Data-In -> CSW exchange completes cleanly, well-formed CSW with correct signature and echoed tag, no wedge, clean disconnect immediately after. The SCSI command itself reports CSW status FAILED against the current test fixture -- expected at this stage (no TEST UNIT READY / UNIT ATTENTION handling implemented yet, consistent with a fresh-attach unit-attention condition, not a transport-layer defect) and not root-caused further here; the BOT mechanism itself is confirmed correct end to end. Probe-free re-verification afterward on all three architectures. FABRIC-2.md Section X Milestone 2g's CSW checklist item marked done; "get one real READ(10) working end to end" stays explicitly open, distinguishing "the mechanism works" from "the SCSI command succeeds." Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R4VMX6VSKCten8nGgaMkq4 |
||
|
|
a88c004ecb |
Artemis Milestone 2g: CBW construction and send for SCSI READ(10)
First real use of the bulk Transfer Rings Configure Endpoint wired up. xhci_bot_send_read10() builds a 31-byte Command Block Wrapper (USB Mass Storage Class Bulk-Only Transport spec section 5.1) and submits it as a single Normal TRB on the bulk OUT ring via a new xhci_bulk_out_enqueue_and_ring() helper -- a CBW is always exactly one TRB, so unlike the EP0 helper this one rings its own doorbell rather than leaving that to a caller assembling a group. usb_bot_cbw_t is a real struct (every field up to the CDB array is naturally aligned, and this driver's targets are all little-endian already assumed everywhere else), but its DMA length is the explicit USB_BOT_CBW_LENGTH (31) constant, never sizeof(*cbw), since the compiler may pad the struct to 32 bytes. The SCSI READ(10) CDB itself is written byte-by-byte since its LBA/Transfer Length fields are big-endian on the wire, unlike everything else in this driver -- the one place two byte orders are both live in the same function. Completion is correlated via the existing pending_transfer_slot_id/ transfer_purpose gate (new XHCI_XFER_CBW_SENT purpose) -- no ring-specific dispatch needed, since this driver's single-outstanding- transfer scope already implies which ring produced an event. This covers construction and send only (one third of a full READ(10): CBW -> Data-In stage -> CSW) -- reading the Data-In stage and CSW receive/validation are separate, explicitly not-yet-implemented items. Verified live via a temporary probe (written, run once, log captured, reverted per this project's own probe convention) -- all three architectures, byte-identical: CBW submitted -> CBW send completed, then a clean disconnect even with the Data-In stage never drained (confirms no wedge on a dangling BOT transaction). Probe-free re-verification afterward on all three architectures. FABRIC-2.md Section X Milestone 2g's CBW checklist item marked done. Also records a monitoring gotcha hit three times this session: `ls -t` over the logs/ tree can return a stale leftover log from an earlier run in the same session -- fixed going forward by reading the log path off the actual running QEMU process's own command line instead, and a memory note added so it doesn't recur next session. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R4VMX6VSKCten8nGgaMkq4 |
||
|
|
92ce1f85dd |
Artemis Milestone 2g: Configure Endpoint command
Adds the xHCI Configure Endpoint command for the two bulk endpoints identified by the previous increment, and fixes control-transfer sequencing to match the spec: xHCI 1.2 section 4.3.5 requires Configure Endpoint before SET_CONFIGURATION is sent to the device, the reverse of the order this driver used through 2f (which happened to work against QEMU's lenient qemu-xhci emulation but wasn't spec-correct). New XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD, EP Context type constants for Bulk IN/OUT, and an XHCI_EP_ADDR_TO_DCI() macro (DCI = 2*EndpointNumber + Direction) in xhci.h. xhci_cmd_configure_endpoint() builds the Input Context (Slot + one EP Context per DCI up to the highest bulk endpoint in use) and submits the command via the existing next_action deferral mechanism, correlated on completion via a new XHCI_CONN_AWAIT_CONFIGURE_ENDPOINT connect_state, then chains into the existing SET_CONFIGURATION path. Two allocations had to grow beyond what Address Device sized them for: the Input Context (previously room for one EP Context only) and, less obviously, the Device Context that DCBAA[slot_id] itself points at -- the controller only touches DCIs named in a command's own Add/Drop flags, so growing that buffer required copying its existing Slot+EP0 content forward rather than zeroing it, to avoid handing the controller a blank EP0 out from under an endpoint this command isn't touching. Bulk Transfer Rings (bulk_in_ring/bulk_out_ring) are allocated and wired into the new EP Contexts but not yet exercised by an actual transfer -- CBW/CSW submission is next. Verified live via QMP hotplug, all three architectures, byte-identical: bulk endpoint identification -> configure endpoint command submitted -> configure endpoint succeeded -> the existing set configuration -> device configured chain, then a clean disconnect/disable-slot teardown afterward with the larger Device Context installed. FABRIC-2.md Section X Milestone 2g's endpoint identify+configure checklist item marked fully done. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R4VMX6VSKCten8nGgaMkq4 |
||
|
|
96d55fcd87 |
Artemis Milestone 2g (partial): bulk endpoint discovery + 2e disconnect teardown
Picked up from a crashed session: xhci_driver.h/xhci.h already had the bulk_in/out_ep_addr/max_packet fields and Endpoint-descriptor offset macros scaffolded, but the actual walk that populates them was never written. Added it: after 2f confirms a Mass Storage/SCSI/BOT interface, a nested walk continues through the Endpoint descriptors that follow it (bDescriptorType==5, stopping at the next Interface descriptor or end of stream), keeping only Bulk-type endpoints and splitting IN/OUT by bEndpointAddress bit 7. Also reset the four new fields in xhci_bringup(), which the scaffolding had missed. Also completed 2e's disconnect teardown, which was fully implemented this session (not scaffolded): a Disable Slot command is now submitted on a real disconnect, with the port's tracked slot ID captured and cleared from port_slot_id[] immediately (before the command completes) so a fresh connect on the same port isn't confused for one already in progress, and DCBAA[slot_id] cleared only on a successful completion. Verified live via QMP hotplug (deliberate device_add/device_del against freshly launched, individually-tracked instances -- not whatever happened to be attached at boot), all three architectures, byte-identical: bulk IN endpoint=0x81, bulk OUT endpoint=0x02, then a clean disconnect -> disable slot succeeded, no wedge. Caught and fixed a documentation near-miss in the same pass: an initial draft cited the probe-free three-arch acceptance boots as this feature's verification evidence, but a stale leftover log directory from a pre-crash orphaned QEMU process had been picked up by an `ls -dt | head -1` glob during monitoring and mistaken for this session's own result -- the real acceptance logs never had a device attached at all. Re-verified against real PIDs and real log paths before writing FABRIC-2.md's final writeup. FABRIC-2.md Section X Milestone 2 updated: 2e's disconnect-teardown checklist item marked done, 2g's endpoint-identification item marked partially done (identification only -- Configure Endpoint / EP Context wiring is still open). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R4VMX6VSKCten8nGgaMkq4 |
||
|
|
b4bbd043d0 |
Artemis Milestone 2f: SET_CONFIGURATION -- 2f complete
Chains off a confirmed Mass Storage/SCSI/BOT interface match via the existing next_action deferral mechanism: device descriptor -> config descriptor -> SET_CONFIGURATION is now a single automatic sequence. bConfigurationValue is read directly out of the already-fetched config_descriptor buffer, no extra transfer needed. First write control transfer this driver has issued (every prior one was a read), so it needed its own submission helper, xhci_ep0_control_write_nodata() -- SET_CONFIGURATION has no Data Stage (wLength=0), and per USB 2.0 spec 8.5.3 a no-data control transfer's Status Stage is always IN, the reverse of an OUT-data request's status stage. XHCI_SETUP_TRT_NO_DATA already existed in xhci.h, unused until now. Verified live via QMP hotplug, all three architectures, worked first try, byte-identical: "set configuration submitted" -> "device configured", guest stays running throughout (checked via QMP query-status). Disconnect confirmed clean on every arch afterward, no wedge. FABRIC-2.md Section X Milestone 2f updated -- 2f is now fully complete, 2g (Bulk-Only Transport) can start. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPfdtaXs9ay1nbwuMnrscu |
||
|
|
b9c540a78b |
Artemis Milestone 2f: Configuration descriptor read + Mass Storage/BOT class confirmation
Chains off the device descriptor request via a new deferred-action mechanism on xhci_dev_t (next_action/next_action_slot_id/next_action_length): a short 9-byte Configuration descriptor read learns wTotalLength, then a full read retrieves Config+Interface+Endpoint descriptors, walked for the Interface descriptor to confirm bInterfaceClass/SubClass/Protocol == Mass Storage/ SCSI/Bulk-Only Transport. The deferral exists because ringing the next doorbell synchronously inside xhci_poll_events()'s event-processing loop -- before the current event's ERDP write -- hung the guest outright (confirmed live via checkpoint logging, amd64). Fixed by moving the actual control-transfer submission to a small dispatch at the end of xhci_poll_events(), after ERDP is updated. A debug hack that shipped mid-session (forcing a repeated 9-byte read instead of chaining into the real 44-byte length, to isolate whether the hang was doorbell-ordering or length-specific) has been reverted: restored the real length and re-verified live. The doorbell-ordering fix was the whole story -- the 44-byte read completes cleanly. Verified live via QMP hotplug, all three architectures, byte-identical results: wTotalLength=0x2c, bInterfaceClass=0x08, bInterfaceSubClass=0x06, bInterfaceProtocol=0x50 -- confirmed Mass Storage/SCSI/BOT. Disconnect confirmed clean on every arch, no wedge. FABRIC-2.md Section X Milestone 2f updated with the full writeup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPfdtaXs9ay1nbwuMnrscu |
||
|
|
2c34e45d05 |
Artemis Milestone 2f: EP0 control transfer, device descriptor request
Adds Setup/Data/Status stage TRB types and control bits (IDT, TRT, DIR) to xhci.h, and xhci_ep0_enqueue_trb()/xhci_ep0_get_device_descriptor() to xhci.c -- the first real control transfer this driver has issued. Follows the same enqueue-then-doorbell-once pattern as the Command Ring, operating on the EP0 Transfer Ring built during 2e's Address Device work. Setup Stage uses Immediate Data (parameter IS the 8-byte setup packet); Data Stage reads into a reused 18-byte device_descriptor buffer; Status Stage alone carries IOC, so exactly one Transfer Event signals transfer completion, correlated via a new pending_transfer_slot_id (same single-outstanding-operation pattern as connect/Enable Slot/Address Device). Automatically triggered once Address Device succeeds. Verified live via QMP hotplug, all three architectures, worked first try with identical results everywhere: idVendor=0x46f4, idProduct=0x0001, bDeviceClass=0x00 -- the class=0 confirms Mass Storage class detection needs the Configuration/Interface descriptor (2f's next item), not the device descriptor. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm |
||
|
|
2e7e957680 |
Milestone 6 (ACL/PKI): Ed25519 verify + SHA-512, built from scratch
New freestanding, verify-only Ed25519 (RFC 8032) implementation:
include/starkernel/{sha512,fe25519,scalar25519,ed25519}.h +
src/starkernel/crypto/{sha512,fe25519,scalar25519,ed25519}.c, wired into
Makefile.starkernel. Kernel never signs or generates keys -- only
ed25519_verify() is needed; signing happens in the host-side mkcapsule
build tool via libsodium/OpenSSL.
Confirmed __int128 multiply/add/shift-by-constant compile with zero
undefined symbols on all three target toolchains (only division needs
libgcc's __udivti3, per timer.c's existing documented finding -- that
file's comment updated to narrow the claim, since it had been read as
"avoid __int128 entirely"). This enabled the standard 5-limb radix-2^51
field arithmetic representation.
An abandoned first attempt (10-limb radix-2^26, avoiding __int128 out of
premature caution) hit two real bugs, both invisible on inspection and
found only by property-based testing against Python's own bignum
arithmetic: a non-uniform-radix limb misalignment in multiplication, and
a double-counted carry. Verification chain: SHA-512 against known +
boundary vectors (7/7); field arithmetic property-tested 25,045 cases;
scalar-mod-L arithmetic 300 cases (L confirmed prime via Miller-Rabin
first); full verify() end-to-end against 110 real signatures from
Python's cryptography library, including tampered inputs and the RFC
8032 S>=L malleability attack -- all correctly accepted/rejected.
Compiles clean (zero warnings) and links on all three architectures,
confirmed via the mandatory three-arch QEMU boot. The code is linked but
not yet called from anywhere -- wiring into capsule_birth.c needs a
from-scratch X.509/DER parser first (Captain Bob chose real X.509 over a
raw-blob cert format this session), which is the next open item.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
|
||
|
|
2b7743027c |
Artemis Milestone 2e: Address Device implemented, worked first try, 3-arch
Adds Slot/Endpoint/Input Control Context structs (32-byte layout only -- HCCPARAMS1.CSZ checked live and confirmed 0 against this driver's QEMU target; 64-byte contexts refuse rather than silently mis-laying-out), xhci_cmd_address_device(), and a new dev->connect_state (idle/await-enable-slot/await-address-device) sequencing Enable Slot and Address Device per connect. Input Context (what the command TRB's parameter points at) and Device Context (what DCBAA[slot_id] points at) are separate 64-byte-aligned allocations, lazily created once and reused across every connect -- single-device driver scope, no free path needed. A new EP0 Transfer Ring uses the same fixed-ring-plus-Link-TRB pattern as the Command Ring. Two facts checked live before writing any context code, not assumed: HCCPARAMS1.CSZ (32-byte, confirmed) and PORTSC.PED at connect time (already set -- PORTSC=0x00021203, SuperSpeed -- the test device self-enables via USB3 link training, so no port-reset state machine was needed this increment; USB2 would need one, untested). Both diagnostics also added console_puts/println-based hex logging (xhci_log_hex32()) -- console_println() only takes string literals, no formatted print existed on this driver's console path before now. Verified live via QMP hotplug, all three architectures, succeeded on the first attempt with no debugging needed: "enable slot succeeded" -> "address device command submitted" -> "address device succeeded" on every boot. Also fixes a FABRIC-2.md dependency-direction error from the previous commit (Address Device is 2f's prerequisite, not the reverse). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm |
||
|
|
6d330efdd8 |
Artemis Milestone 2e: real connect drives Enable Slot, slot ID correlated
xhci_poll_events()'s Port Status Change connect branch now calls xhci_cmd_enable_slot() directly (the earlier boot-time smoke test call is gone), tracked via a new dev->pending_connect_port_id -- since this driver only ever has one command outstanding at a time, that alone identifies which port a later Command Completion Event answers, without needing to match the Command TRB Pointer yet. On success the returned Slot ID is recorded in a new dev->port_slot_id[], a fixed uint32_t[XHCI_MAX_TRACKED_PORTS] (32) indexed by port. Disconnect clears the port's tracked slot (real teardown -- Disable Slot, DCBAA clear, Section U callback -- is still a later increment). Fixed array, not heap-allocated: a first attempt sized port_slot_id dynamically via kmalloc_aligned(dev->max_ports * sizeof(uint32_t), 64) inside xhci_bringup() and it crashed amd64 with a page fault (IFETCH at RIP=CR2=0xA0000, the legacy VGA hole) during the unrelated Mama-VM-birth phase afterward -- a heap-corruption signature, not chased to root cause. Switching to a fixed array (matching this driver's existing preference for fixed over dynamic allocation) made the crash go away; the crashing boot's log is kept (logs/20260822-102516/) as the evidence trail. Verified live via QMP hotplug, all three architectures: connect -> "enable slot command submitted" -> "enable slot succeeded", with a disconnect/reconnect cycle repeating cleanly and no port wedge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm |
||
|
|
dd043bbfeb |
Artemis Milestone 2e: PORTSC connect/disconnect detection, verified live
xhci_poll_events()'s Port Status Change branch now decodes the Port ID from the event TRB (XHCI_PSC_EVT_PORT_ID, new in xhci.h), reads that port's PORTSC.CCS via a new xhci_port_regs() helper, and logs connect vs. disconnect. Acknowledges by writing back only PP (preserved) and CSC (the bit being cleared) -- PED/PR/other _C bits written 0 so nothing is accidentally disabled, reset, or silently cleared, matching the RW1C discipline already used for ERDP.EHB in 2d. Verified with the real target scenario via QMP hotplug on all three architectures: boot with the xHCI controller present but no USB device attached (confirmed zero port activity at ok>), then live attach/detach/re-attach of a virtual USB thumb drive (disk/usb-thumbdrive-test.img via usb-storage on xhci0.0). Full connect->disconnect->connect cycle confirmed clean (no port wedge) on amd64; single connect confirmed on aarch64 and riscv64. Still open: correlating Command Completion Events back to their issuing command, driving Enable Slot/Address Device from this connect path (currently only a boot-time smoke test), and the callback surface into Section U's higher-level code. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm |
||
|
|
9c8ad8f6ff |
Artemis Milestone 2e (in progress): xHCI Command Ring write path proven live
Enable Slot command TRB submitted via a new xhci_submit_command()/ xhci_cmd_enable_slot(), ring doorbell 0, confirmed by a real Command Completion Event on all three architectures -- the first time this driver has written a TRB rather than only reading the Event Ring (2d). Added the Command Ring's previously-missing Link TRB (xHCI 1.2 spec sec 4.9.2) for wraparound correctness. Port Register connect/disconnect handling, slot-ID/context bookkeeping, Address Device, and the callback surface into Section U's code are still open -- this is the discriminating first step, not full 2e. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm |
||
|
|
2b16daba16 |
Artemis Milestone 2d: xHCI Event Ring servicing, polled not interrupt-driven
Implements Event Ring TRB parsing and ERDP dequeue-pointer update
(xhci_poll_events(), src/starkernel/usb/xhci.c), called from
sk_repl_idle()'s existing ~1s idle cadence rather than a per-arch
interrupt handler.
A first attempt wired real interrupt delivery (PCI->IOAPIC GSI routing,
a dedicated isr_stub34/vector 0x22, GIC/PLIC routing mirroring
virtio_input.c). Checked live via QMP query-pci before trusting it: the
amd64 PIRQ swizzle formula predicted GSI 16 for the xHCI controller at
PCI slot 4; the real QEMU-assigned IRQ was 10, and embedded ICH9
functions contradicted the same formula too. Reverted all of it back to
the exact committed baseline rather than chasing chipset PIRQ routing
further, and reframed around Section U item 6's own design intent
("interrupt-driven, coarse cadence, cheap early-exit... quick check
blocks... done") via sk_repl_idle() instead -- USB insertion is a
human-timescale event, not a hot path.
Added -device qemu-xhci to all three QEMU launch targets (required for
any of this to be testable). Verified end to end via genuine post-boot
hotplug (QMP device_add/device_del usb-storage): all three architectures
detect a live attach within seconds. A false-alarm heartbeat "freeze"
found mid-verification traced to querying the wrong counter
(vm->heartbeat.tick_count, which only advances during word execution,
not the kernel's real ISR-driven heartbeat_ticks()) -- confirmed via a
temporary diagnostic word, captured and reverted.
Full writeup, including the discarded interrupt-routing attempt and the
false-alarm investigation, in FABRIC-2.md's Milestone 2c/2d entries.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm
|
||
|
|
c2f1d94c97 |
Artemis Milestone 2c: xHCI controller bring-up wired, DoE CSV export off by default
xhci_bringup() (HC reset, DCBAA, Command/Event rings, RUN/STOP) was uncommitted and referenced an XHCI_WAIT_FOR macro that was never defined, breaking the build. Wired all four wait sites to the existing xhci_wait_bit() helper instead, matching each register/bit/polarity needed (halt-before-reset waits for HCH set; HCRST, CNR, and post-RUN HCH waits all wait for their bit to clear). Also flipped g_doe_log_enabled's default from 1 to 0 -- the per-tick [HADES][DOE] CSV export was flooding every boot log and slowing interactive verification for no reason during ordinary acceptance runs; HB-ON still re-enables it at the REPL for anyone running an actual DoE campaign. Three-arch acceptance: amd64/aarch64/riscv64 all boot clean to ok>, zero DoE rows in any log. aarch64 and riscv64 both exited cleanly via BYE with no exception, confirming the earlier SMC->HVC PSCI fix still holds. Logs and DoE CSV artifacts from this run included. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HZ8kNoTuP63pbQtro4qvrm |
||
|
|
5970c54912 |
Artemis Milestone 2b complete: xHCI PCI discovery and BAR0 mapping
New src/starkernel/usb/ subsystem directory (added to both LOADER_SRCS_BASE and KERNEL_SRCS_BASE wildcards in Makefile.starkernel, matching the existing virtio/*.c pattern). xhci_find_and_map() locates the controller via the already-generic pci_find_first(), enables it, maps BAR0 via the already-generic pci_map_bar(), and fills in all four register-region pointers (cap/op/runtime/doorbell) plus max_slots/ max_ports/max_intrs from HCSPARAMS1 -- ready for controller bring-up (2c) to consume directly. No pci.c extension needed, per 2a's finding that PCI discovery here is ID-based lookup (already generic), not class-code scanning. Verified: clean standalone syntax check, full amd64 kernel build with zero warnings, live boot still reaches POST 1012/0/0 unaffected (nothing calls xhci_find_and_map() yet, so this is purely additive). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
94345c24b7 |
Artemis Milestone 2a complete: xHCI register header, PCI ID confirmed
live via QMP, ring sizing decided include/starkernel/xhci.h: Capability/Operational/Runtime register layouts, Port Register Set, Interrupter Register Set, Doorbell Array, 16-byte TRB struct -- all from the xHCI 1.2 spec, no existing reference in this tree to build from (unlike virtio-blk). volatile fields, no packed attribute, matching virtio_blk.c's documented riscv64/QEMU-MMIO precedent. Compile-checked clean, sizeof(xhci_trb_t) verified == 16. QEMU qemu-xhci's PCI vendor:device ID (0x1B36:0x000D) confirmed live via QMP query-pci against a real running instance -- not assumed from memory, matches the Milestone 1 QMP infrastructure just built. Ring sizing decided: fixed 256-TRB (one page) Command Ring and Event Ring, single interrupter -- documented rationale in the header. Bonus finding: src/starkernel/pci/pci.c already has more reusable infrastructure than Milestone 2b assumed (pci_find_first is ID-based lookup already existing; pci_bar/pci_map_bar/pci_enable are already generic) -- 2b is smaller than originally scoped, noted in the punch list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
89d8c08582 |
stadium: wire STADIUM_CAPACITY_TICK in as a flat threshold, not a scheduler
Closes FABRIC-2.md's last open §12 Q5 question. fleet_heartbeat_tick_count is fed by every live VM's own vm_tick(), not one VM's, so it was reaching HEARTBEAT_INFERENCE_FREQUENCY (shared/borrowed from the per-VM inference gate) several times faster than intended with more than one VM live - backwards from FABRIC.md §22.4's required ~1000:1 separation. What's actually gated turned out to be low-stakes: vm_physics_tick() (capsule_vm_physics.c:397) is a passive statistics refit - re-sorts a window of past heat-transfer samples and recomputes a median rate estimate. It doesn't move heat or arbitrate capacity. Firing too often just meant a noisier statistic recomputed more frequently than planned, not incorrect behavior. Considered and explicitly rejected: scaling the threshold by live VM count at the check site. That's the first brick of a scheduler - reading fleet state to adjust a rate dynamically - which this project has deliberately avoided building. Implemented instead: STADIUM_CAPACITY_TICK (existing Kconfig symbol, defined but never read by any code path) now gates vm_physics_heartbeat_tick()'s call directly, replacing the borrowed HEARTBEAT_INFERENCE_FREQUENCY. Default bumped 1000 -> 4000, a flat constant picked once for Tripod's known 4-VM topology, same kind of placeholder as every other frequency knob in Kconfig.kernel - not computed from anything at runtime. Renamed fleet_last_inference_tick -> fleet_last_capacity_tick to match. Still one clock, one counter (fleet_heartbeat_tick_count) - just a bigger flat divisor on it. Three-arch QEMU acceptance: all clean to ok>, identical Stadium conservation invariant on all three (resident_sum=43691 reservoir=21845 sum=65536). logs/20260815-093425/amd64, logs/20260815-093521/aarch64, logs/20260815-093641/riscv64. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
00e657019e |
stadium: make VM population bound RAM-derived, not a static array of 4
Replaces STADIUM_MAX_VM_COUNT (Kconfig, hardcoded default 4) with a boot-time computation, mirroring the pattern stadium_boot_init() already used for the cell pool. New Kconfig STADIUM_VM_MEMORY_PERCENT (default 50): max_vm_count = (kmalloc_get_stats().free_bytes after the cell array * STADIUM_VM_MEMORY_PERCENT / 100) / VM_MEMORY_SIZE, floored to 1, no ceiling (population is not knowable in advance - could be 4, could be 4000). stadium_quotas and word_slots (plus stat_promotions/stat_evictions) are now kmalloc'd to the computed count instead of declared with a macro. New accessor stadium_max_vm_count() replaces every STADIUM_MAX_VM_COUNT reference, including capsule_birth.c's birth-refusal gate. Two things found and fixed along the way: - The existing cell-pool budget was sourced from pmm_get_stats(), which reflects physical pages PMM hasn't handed to any subsystem yet - but the actual allocation is kmalloc(), which draws from the separate, fixed-size heap kmalloc_init() (M6) already carved out of PMM before stadium_boot_init() ever runs. Budgeting against PMM's leftover and allocating from the kmalloc heap are two different pools. Both the cell budget and the new VM-count budget now source from kmalloc_get_stats() instead. - stadium_owner[] (which VM's quota owns each cell) was uint8_t, capped at 255 slots by a compile-time assert tied to the old macro. Widened to uint16_t (65535 slots of headroom) with a runtime clamp + log if the computed count ever exceeds that, since there's no ceiling anymore. Three-arch QEMU acceptance: all clean to ok>, computed VM count genuinely differs by actual available RAM (amd64/riscv64: 50 slots at -m 1024, aarch64: 101 slots), Stadium conservation invariant identical across all three (resident_sum=43691 reservoir=21845 sum=65536). logs/20260815-080526/amd64, logs/20260815-080826/aarch64, logs/20260815-080952/riscv64. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
59458a0a16 |
Cursor indicator + HB-ON/HB-OFF runtime DoE instrumentation toggle
Cursor (Captain Bob: "the only thing we need is a cursor"): vt100_draw_cursor() draws a solid block at the terminal's current position, called from repl.c after the prompt prints and after every keystroke/backspace. vt100_erase_cursor() cleans up the one gap a static cursor has -- Enter/newline moves away from the cursor cell without a character draw ever overwriting it, which left a stray block behind until this fix. HB-ON/HB-OFF (Captain Bob: run a program with or without instrumentation without rebuilding): Converted per-tick DoE logging from a build-time flag (HEARTBEAT_DOE_LOG) to a runtime one. doe_log_tick_row() now self-gates on g_doe_log_enabled (default 1, matching the old default) instead of being compiled out entirely; the call site in vm_runtime.c is unconditional. Two new FORTH words, HB-ON and HB-OFF, flip the flag live. Removed the now-dead HEARTBEAT_DOE_LOG plumbing: the Kconfig symbol, and the -D forwarding in both LOADER_CFLAGS and KERNEL_CFLAGS. Verified: three-arch clean QEMU boot + logs; dictionary word count 466 (463 baseline + ALT+TAB + HB-ON + HB-OFF, exactly the three words added across this session); amd64 screendump confirms the cursor renders correctly after real interactive typing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |