Migrate Hera->Artemis storage-attach to a real message round-trip
Build / build-aarch64-iso (push) Canceled after 0s
Build / build-amd64-iso (push) Canceled after 0s
Build / build-riscv64-img (push) Canceled after 0s

Hera still polls xHCI and sig-checks attached drives, but the storage
registration step (blk_subsys_attach_device(), now wrapped as the
BLK-ATTACH primitive) moves to Artemis's own dictionary, reached via
HERA-BLK-ATTACH-REQ/BLK-ATTACH-ACK (VM-EXEC, since Hera can't load her
own messaging.4th -- see the doc comment in repl.c). Identity birth
(Zuse genesis / WIREBIND) is deferred until the ack confirms storage
actually succeeded, instead of running synchronously underneath a
storage call that might fail ("wait for ack, safer for identity data").

Caught and fixed a real bug live during acceptance testing: Artemis's
ACK-APPEND-NUM fed a single-cell value into <# #S #> (which expects a
double-cell pair), causing a stack underflow the first time
HERA-BLK-ATTACH-REQ ran. Fixed with the same `0 SWAP` convention every
other numeric-append helper in this codebase already uses.

Verified booting clean to (zuse) ok> with no VM-EXEC errors on all
three architectures (amd64/aarch64/riscv64).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014Ec88YKxxhZGG1RNnune78
This commit is contained in:
Robert Allan James
2026-09-07 21:22:04 -04:00
co-authored by Claude Sonnet 5
parent c4f94c409f
commit 63b8b3bc29
25 changed files with 149357 additions and 125 deletions
+127 -35
View File
@@ -47,6 +47,7 @@
#include "block_subsystem.h"
#include "word_source/include/keyboard_words.h"
#include "word_source/include/block_words.h"
#include "word_registry.h"
#include "freestanding/stdio.h"
#include <stdint.h>
#include <string.h>
@@ -153,6 +154,87 @@ blkio_dev_t *sk_repl_get_attached_blk_dev(void) {
return g_attached_blk_dev;
}
/* Storage-attach messaging migration (Bob, 2026-09-07): Hera keeps
* polling/sig-checking, but no longer registers a newly-attached drive
* into the block subsystem herself -- that's Artemis's own domain now,
* reached via a real message (HERA-BLK-ATTACH-REQ, artemis:init.4th)
* instead of a direct blk_subsys_attach_device() call. Hera cannot use
* her own MSG-SEND for the outbound leg (kernel_main.c's own comment,
* ~line 784: loading common:messaging.4th into her dictionary was
* already tried and confirmed to silently drop every colon-definition
* touching a STADIUM-* primitive) -- she uses VM-EXEC directly instead,
* the same mechanism she already pumps MSG-TICK through. The reply
* leg needs no such workaround: Artemis's own MSG-TICK delivers her
* ack via VM-EXEC into Hera, which only requires BLK-ATTACH-ACK below
* to exist as an ordinary word here -- not a full messaging vocabulary.
*
* usb_blk_dev_slots/usb_blk_dev_slot_count were function-local statics
* inside sk_repl_idle() until now -- promoted to file scope so
* sk_word_blk_attach_ack() below (a real dictionary word, called from a
* completely different call stack than the idle loop) can resolve an
* incoming ack's raw pointer back to the slot it belongs to. */
static blkio_dev_t *g_usb_blk_dev_slots = (void *)0;
static uint32_t g_usb_blk_dev_slot_count = 0;
/* One pending entry per slot, indexed the same way msc_slots[]/
* usb_blk_dev_slots[] already are (index 0 unused, matches precedent).
* Holds the sig-check result from the moment the storage-attach request
* was sent, so the deferred Zuse/WIREBIND birth calls -- which need that
* result -- can run once Artemis's ack confirms storage succeeded,
* without re-reading the drive a second time. */
typedef struct {
int pending;
homeblocks_sig_result_t sig_rc;
homeblocks_sig_t sig;
} sk_blk_attach_pending_t;
static sk_blk_attach_pending_t *g_blk_attach_pending = (void *)0;
/* BLK-ATTACH-ACK ( dev-addr ok? -- ): VM-EXEC'd into Hera by Artemis's
* own MSG-TICK once HERA-BLK-ATTACH-REQ's BLK-ATTACH call resolves.
* Finds which slot the raw pointer belongs to, and -- only on success --
* runs the same Zuse/WIREBIND attach logic sk_repl_idle() used to run
* immediately and synchronously, now deferred until storage is
* confirmed ("wait for ack, safer for identity data" -- Bob, 2026-09-07).
* On failure, logs the same error sk_repl_idle() already logged for a
* failed blk_subsys_attach_device() call, and simply never births
* anything for this attach. */
static void sk_word_blk_attach_ack(VM *vm) {
if (vm->dsp < 1) { vm->error = 1; return; }
cell_t ok_flag = vm_pop(vm);
cell_t dev_addr = vm_pop(vm);
blkio_dev_t *dev = (blkio_dev_t *)(uintptr_t)dev_addr;
if (!g_usb_blk_dev_slots || !g_blk_attach_pending) return;
uint32_t found_slot = 0;
for (uint32_t i = 1; i < g_usb_blk_dev_slot_count; i++) {
if (&g_usb_blk_dev_slots[i] == dev) { found_slot = i; break; }
}
if (found_slot == 0 || !g_blk_attach_pending[found_slot].pending) return;
g_blk_attach_pending[found_slot].pending = 0;
if (!ok_flag) {
log_message(LOG_ERROR, "xhci: USB MSC block-subsystem attach failed");
return;
}
xhci_dev_t *xdev = xhci_get_dev();
xhci_msc_slot_t *ms = xdev ? xhci_msc_slot_for(xdev, found_slot) : (void *)0;
if (ms) ms->bot_msc_attached = 1;
g_attached_blk_dev = dev;
homeblocks_sig_result_t sig_rc = g_blk_attach_pending[found_slot].sig_rc;
homeblocks_sig_t sig = g_blk_attach_pending[found_slot].sig;
capsule_zuse_boot_try_attach(dev, sig_rc, &sig, (VM *)sk_get_mama_vm());
if (sig_rc == HOMEBLOCKS_SIG_OK) {
capsule_wirebind_try_attach(dev, &sig, (VM *)sk_get_mama_vm());
}
}
void sk_repl_register_words(VM *vm) {
register_word(vm, "BLK-ATTACH-ACK", sk_word_blk_attach_ack);
}
/*===========================================================================
* Idle heartbeat service
*
@@ -261,22 +343,24 @@ static void sk_repl_idle(VM *active_vm)
* serviced this tick, not just one -- a single `if` here used to mean
* a second device's pending flag would sit unnoticed until the first's
* flag was consumed and cleared. */
static blkio_dev_t *usb_blk_dev_slots = (void *)0;
static uint32_t usb_blk_dev_slot_count = 0;
if (xdev && (!usb_blk_dev_slots || usb_blk_dev_slot_count < xdev->max_slots + 1)) {
if (xdev && (!g_usb_blk_dev_slots || g_usb_blk_dev_slot_count < xdev->max_slots + 1)) {
size_t bytes = (size_t)(xdev->max_slots + 1) * sizeof(blkio_dev_t);
blkio_dev_t *fresh = (blkio_dev_t *)kmalloc_aligned(bytes, 64);
if (fresh) {
size_t pending_bytes = (size_t)(xdev->max_slots + 1) * sizeof(sk_blk_attach_pending_t);
sk_blk_attach_pending_t *fresh_pending = (sk_blk_attach_pending_t *)kmalloc_aligned(pending_bytes, 64);
if (fresh && fresh_pending) {
memset(fresh, 0, bytes);
usb_blk_dev_slots = fresh;
usb_blk_dev_slot_count = xdev->max_slots + 1;
memset(fresh_pending, 0, pending_bytes);
g_usb_blk_dev_slots = fresh;
g_usb_blk_dev_slot_count = xdev->max_slots + 1;
g_blk_attach_pending = fresh_pending;
}
}
for (uint32_t slot_id = 1; xdev && usb_blk_dev_slots && slot_id <= xdev->max_slots; slot_id++) {
for (uint32_t slot_id = 1; xdev && g_usb_blk_dev_slots && slot_id <= xdev->max_slots; slot_id++) {
xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot_id);
if (!ms || !ms->bot_msc_attach_pending) continue;
ms->bot_msc_attach_pending = 0;
blkio_dev_t *usb_blk_dev = &usb_blk_dev_slots[slot_id];
blkio_dev_t *usb_blk_dev = &g_usb_blk_dev_slots[slot_id];
int rc = blkio_usb_open_msc(usb_blk_dev, xdev, slot_id);
if (rc == 0) {
@@ -330,33 +414,41 @@ static void sk_repl_idle(VM *active_vm)
break;
}
/* FABRIC-2.md §F.20/§F.21: Zuse is thumbdrive-resident now,
* not system-resident -- this is the only point in the boot
* lifecycle a just-attached drive's sig result is known, so
* genesis-mint/attach-authenticate has to happen from here,
* not as a one-shot kernel_main.c step (a thumbdrive can't
* be detected before the REPL's own idle polling exists to
* detect it). No-ops immediately if Zuse already has a real
* identity this boot. */
capsule_zuse_boot_try_attach(usb_blk_dev, sig_rc, &sig, (VM *)sk_get_mama_vm());
/* FABRIC-2.md §F.20/§F.21 / §F.5/§F.23 (WIREBIND): Zuse
* genesis-mint/attach-authenticate and regular-identity
* verify-then-birth-then-pair both used to run synchronously,
* right here, before storage was even registered. Moved
* (Bob, 2026-09-07, "wait for ack, safer for identity data")
* to sk_word_blk_attach_ack() above, run only once Artemis
* confirms the storage-attach succeeded -- identity birth
* no longer happens on top of storage that might not have
* registered. Stash what that deferred call needs. */
if (g_blk_attach_pending) {
g_blk_attach_pending[slot_id].pending = 1;
g_blk_attach_pending[slot_id].sig_rc = sig_rc;
g_blk_attach_pending[slot_id].sig = sig;
}
/* FABRIC-2.md §F.5/§F.23 (WIREBIND): the real thumbdrive-
* attach call site for a regular (non-Zuse) identity --
* verify-then-birth-then-pair, replacing the RUNCAP-TEST/
* PAIR-TEST diagnostic words that exercised each piece by
* hand. Only meaningful for a drive that actually checked
* out (HOMEBLOCKS_SIG_OK); capsule_wirebind_try_attach()
* itself no-ops for a genesis-mode Zuse drive (no cert
* region) or before Zuse has authenticated this boot. */
if (sig_rc == HOMEBLOCKS_SIG_OK) {
capsule_wirebind_try_attach(usb_blk_dev, &sig, (VM *)sk_get_mama_vm());
/* Storage-attach registration (blk_subsys_attach_device(),
* BLK-ATTACH C primitive) is Artemis's own domain now, not
* Hera's -- she keeps polling/sig-checking but no longer
* performs this step herself. Hera can't use her own
* MSG-SEND (see g_usb_blk_dev_slots's own doc comment
* above for why), so this is a direct VM-EXEC into
* Artemis's dictionary -- the same mechanism the MSG-TICK
* pump below already uses -- rather than a real enqueued
* message. HERA-BLK-ATTACH-REQ (capsules/artemis/init.4th)
* runs BLK-ATTACH then replies via her own real MSG-SEND,
* delivered back to Hera by the ordinary MSG-TICK pump. */
{
char cmd[96];
int n = snprintf(cmd, sizeof(cmd),
"S\" %llu HERA-BLK-ATTACH-REQ\" S\" Artemis\" VM-EXEC",
(unsigned long long)(uintptr_t)usb_blk_dev);
if (n > 0 && (size_t)n < sizeof(cmd)) {
vm_interpret((VM *)sk_get_mama_vm(), cmd);
}
}
}
if (rc == 0 && blk_subsys_attach_device(usb_blk_dev) == BLK_OK) {
ms->bot_msc_attached = 1;
g_attached_blk_dev = usb_blk_dev;
} else {
log_message(LOG_ERROR, "xhci: USB MSC block-subsystem attach failed");
}
}
@@ -369,11 +461,11 @@ static void sk_repl_idle(VM *active_vm)
* block_subsystem.c (see bot_msc_detach_pending's own doc comment).
* Per-slot loop now (FABRIC-3.md §VII, 2026-09-05), same reasoning as
* the attach loop above. */
for (uint32_t slot_id = 1; xdev && usb_blk_dev_slots && slot_id <= xdev->max_slots; slot_id++) {
for (uint32_t slot_id = 1; xdev && g_usb_blk_dev_slots && slot_id <= xdev->max_slots; slot_id++) {
xhci_msc_slot_t *ms = xhci_msc_slot_for(xdev, slot_id);
if (!ms || !ms->bot_msc_detach_pending) continue;
ms->bot_msc_detach_pending = 0;
blkio_dev_t *usb_blk_dev = &usb_blk_dev_slots[slot_id];
blkio_dev_t *usb_blk_dev = &g_usb_blk_dev_slots[slot_id];
blk_subsys_detach_device(usb_blk_dev);
if (g_homeblocks_dev == usb_blk_dev) {