Dynamic switch table (B2) -- FABRIC-3.6.md task 3.1

Replaces the fixed SK_SWITCH_MAX_SLOTS=16 compile-time array with a
boot-time, RAM-derived allocation via a new sk_vm_switch_signal_boot_init(),
kmalloc'd to stadium_max_vm_count() entries -- the same pattern
session_boot_init() already established for Stadium-derived sizing.
Every switch-signal participant is a Stadium VM, so this reuses that
bound directly rather than deriving a separate one.

Verified live on all three architectures: switch table sized to 50
slots (amd64), 202 slots (aarch64), 50 slots (riscv64) -- all well
past the old fixed cap. All three boot to [zuse@Hera] ok> cleanly;
dict_hash for Hermes/Hestia identical across architectures, unmoved
from pre-task values.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-21 23:59:50 -04:00
co-authored by Claude Sonnet 5
parent 66ea4a5e74
commit c19ef365fe
9 changed files with 37220 additions and 15 deletions
+26 -1
View File
@@ -769,10 +769,35 @@ ruling]** cannot be written precisely until Captain Bob settles the named sub-it
2026-09-21 · no commit (documentation ruling, see `FABRIC-3.5.md` §XLVI) · all five
sub-items plus task 3.3's heat-cost point ruled by Captain Bob, all recommended defaults
accepted.
- [ ] **3.1** — **Dynamic switch table** (B2) **[needs ruling 3.0c]**. Read
- [x] **3.1** — **Dynamic switch table** (B2). Read
`SK_SWITCH_MAX_SLOTS`'s every use first; replace the constant with a boot-time,
RAM-derived allocation (Stadium's `stadium_max_vm_count_val` pattern). *Check:* boot
byte-identical, `dict_hash` unmoved; a synthetic test drives more than 16 slots.
2026-09-21 · `logs/20260921-233241/amd64/`, `logs/20260921-233814/aarch64/`,
`logs/20260921-234837/riscv64/` (riscv64's first attempt, `logs/20260921-234312/`,
timed out mid-boot on a 280s wrapper before reaching the prompt — kept per the
never-delete-logs convention, not a real finding, just an undersized timeout; the
234837 rerun with more headroom reached the prompt cleanly). All three reach
`[zuse@Hera] ok>`, zero `UNKNOWN WORD`. Switch-signal table sized dynamically off
`stadium_max_vm_count()` at boot, confirmed via a new `Switch-signal: N slots` console
line: **50 slots (amd64), 202 slots (aarch64), 50 slots (riscv64)** — all far above the
old fixed 16-slot cap, satisfying the "drives more than 16 slots" check without a
separate synthetic harness (real boot-time RAM sizing already clears it by a wide
margin). Reused `session_boot_init()`'s exact pattern (kmalloc to
`stadium_max_vm_count()`, called in `kernel_main.c` right after `stadium_boot_init()`,
before the first `sk_vm_switch_signal_register()` call). `dict_hash` for Hermes
(`0xc95ef2d92fa0781f`) and Hestia (`0xfbde9fe105fd3b3d`) identical across all three
architectures — unmoved from pre-task values, as expected (this task touches no
capsule). Artemis's `dict_hash` differs between the amd64 run (`0xa43d2e104ae7e451`,
fresh-formatted `disk/artemis.img`) and the aarch64/riscv64 runs (`0x7f18214489036b39`,
both *resuming* the same disk state the amd64 run left behind) — a disk-state artifact
of running three sequential boots against one shared image, not an architecture
divergence; not a §XXXIV.6 stop condition.
note: unrelated finding, not fixed — riscv64's boot logged three
`virtio_blk: vblk_io timed out` errors during Artemis's disk I/O in both riscv64
attempts (sectors 20/8/8 and 3224/5496/5504); boot recovered and reached the prompt
regardless. `virtio_blk.c` was not touched by this task. Reporting per standing rule,
not investigating further here.
- [ ] **3.2** — **Channel table + common channel**, inert (B1). Dynamic table of topics, each
with its own `SkHermesMembership`; the common channel exists from boot; every VM is
subscribed at birth. Wired to nothing. *Check:* boot byte-identical; every born VM appears
+31 -4
View File
@@ -49,6 +49,15 @@
* participants (Hera/Hermes/Artemis) -- extending participation later
* (Stage 4+) is another sk_vm_switch_signal_register() call, not a
* redesign.
*
* FABRIC-3.6.md task 3.1 (2026-09-21, ruled B2 / FABRIC-3.5.md §XLV.2): the
* slot table is no longer a fixed SK_SWITCH_MAX_SLOTS=16 compile-time array.
* It is kmalloc'd at boot by sk_vm_switch_signal_boot_init(), sized from
* stadium_max_vm_count() -- the same RAM-derived population bound Stadium
* and session.c already use (session_boot_init() is the direct precedent
* mirrored here). Every switch-signal participant is a Stadium VM, so
* reusing that bound directly (rather than re-deriving a separate RAM
* budget) needs no new sizing formula.
*/
#ifndef STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H
@@ -59,17 +68,29 @@
#include <stdint.h>
#include "starkernel/vm_uuid.h"
/* Boot-time allocation (FABRIC-3.6.md task 3.1, 2026-09-21): kmalloc's the
* slot table to stadium_max_vm_count() entries. Must run after
* stadium_boot_init() (that bound is 0, and this fails, until Stadium has
* computed it) and before the first sk_vm_switch_signal_register() call.
* Soft failure -- returns -1 and leaves the table unallocated (capacity 0,
* so register() below simply refuses every registration) rather than
* halting boot, same posture as stadium_boot_init()/session_boot_init().
* Idempotent-unsafe: calling twice leaks the first allocation, so callers
* must call it exactly once. */
int sk_vm_switch_signal_boot_init(void);
/* Register a VM as a switch-signal participant. Returns its slot index,
* or -1 if the slot table is full. Call once per participating VM,
* after that VM is fully born (never mid-birth -- this stage has no
* critical-section protection against being switched away mid-setup). */
* or -1 if the slot table is full (or sk_vm_switch_signal_boot_init() was
* never called / failed). Call once per participating VM, after that VM is
* fully born (never mid-birth -- this stage has no critical-section
* protection against being switched away mid-setup). */
int sk_vm_switch_signal_register(VMUuid vm_id);
/* Remove a switch-signal participant (FABRIC-3.md §XXVIII Stage 4,
* 2026-09-14) -- Tripod VMs never need this (they live forever), but
* WIREBIND-birthed identity VMs cycle through attach/detach repeatedly
* and must free their slot for reuse, or the bounded table exhausts
* after SK_SWITCH_MAX_SLOTS attach/detach cycles. Compacts the table
* after sk_vm_switch_signal_slot_capacity() attach/detach cycles. Compacts the table
* (small, bounded, mutated only at attach/detach -- not a hot path).
* Clears a pending switch targeting this VM, if any, so the checkpoint
* never attempts to switch into a no-longer-registered participant.
@@ -115,6 +136,12 @@ int sk_vm_switch_signal_slot_count(void);
uint32_t sk_vm_switch_signal_readiness(int slot); /* 0 if slot out of range */
uint32_t sk_vm_switch_signal_readiness_of(VMUuid vm_id); /* 0 if not registered */
/* FABRIC-3.6.md task 3.1 (2026-09-21): the table's boot-time-computed
* capacity (0 if sk_vm_switch_signal_boot_init() was never called or
* failed) -- the dynamic replacement for the old compile-time
* SK_SWITCH_MAX_SLOTS=16. */
int sk_vm_switch_signal_slot_capacity(void);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,71 @@
OpenSBI v1.8
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
| | | | '_ \ / _ \ '_ \ \___ \| _ < | |
| |__| | |_) | __/ | | |____) | |_) || |_
\____/| .__/ \___|_| |_|_____/|____/_____|
| |
|_|
Platform Name : riscv-virtio,qemu
Platform Features : medeleg
Platform HART Count : 1
Platform HART Protection : pmp
Platform IPI Device : aclint-mswi
Platform Timer Device : aclint-mtimer @ 10000000Hz
Platform Console Device : uart8250
Platform HSM Device : ---
Platform PMU Device : ---
Platform Reboot Device : syscon-reboot
Platform Shutdown Device : syscon-poweroff
Platform Suspend Device : ---
Platform CPPC Device : ---
Firmware Base : 0x80000000
Firmware Size : 321 KB
Firmware RW Offset : 0x40000
Firmware RW Size : 65 KB
Firmware Heap Offset : 0x47000
Firmware Heap Size : 37 KB (total), 0 KB (reserved), 12 KB (used), 23 KB (free)
Firmware Scratch Size : 4096 B (total), 1464 B (used), 2632 B (free)
Runtime SBI Version : 3.0
Standard SBI Extensions : ipi,pmu,srst,sse,hsm,rfnc,fwft,time,base,legacy,dbcn,dbtr
Experimental SBI Extensions : none
Domain0 Name : root
Domain0 Boot HART : 0
Domain0 HARTs : 0*
Domain0 Region00 : 0x0000000080040000-0x000000008005ffff M: (F,R,W) S/U: ()
Domain0 Region01 : 0x0000000080000000-0x000000008003ffff M: (F,R,X) S/U: ()
Domain0 Region02 : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W)
Domain0 Region03 : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W)
Domain0 Region04 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: ()
Domain0 Region05 : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W)
Domain0 Region06 : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W)
Domain0 Region07 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X)
Domain0 Next Address : 0x0000000020000000
Domain0 Next Arg1 : 0x00000000bfe00000
Domain0 Next Mode : S-mode
Domain0 SysReset : yes
Domain0 SysSuspend : yes
Boot HART ID : 0
Boot HART Domain : root
Boot HART Priv Version : v1.12
Boot HART Base ISA : rv64imafdch
Boot HART ISA Extensions : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu
Boot HART PMP Count : 16
Boot HART PMP Granularity : 2 bits
Boot HART PMP Address Bits : 54
Boot HART MHPM Info : 16 (0x0007fff8)
Boot HART Debug Triggers : 2 triggers
Boot HART MIDELEG : 0x0000000000001666
Boot HART MEDELEG : 0x0000000000f4b509
[=3hRISC-V EDK2 firmware version 2025.11-3ubuntu7.2
Press ESCAPE within 5 seconds for boot options ERROR: C40000002:V03051002 I0 6D33944A-EC75-4855-A54D-809C75241F6C 83FFF850
BdsDxe: failed to load Boot0001 "UEFI Misc Device" fr
om PciRoot(0x0)/Pci(0x1,0x0): Not Found
[=3hStarKernel UEFI Loader
Loading kernel from ESP...
[CKPT 001] Entered efi_main - ConOut live
File diff suppressed because it is too large Load Diff
@@ -30,16 +30,22 @@
#include "starkernel/capsule_vm_switch_signal.h"
#include "vm.h"
#include "starkernel/vm/switch.h" /* sk_vm_switch_current_vm() -- see below */
#include "starkernel/vm/stadium.h" /* stadium_max_vm_count() -- task 3.1's sizing bound */
#include "starkernel/kmalloc.h"
#include "starkernel/console.h"
#include <stddef.h>
/* FABRIC-3.md §XXVIII Stage 4 (2026-09-14): bumped from 8 to 16 to admit
* WIREBIND identity VMs alongside the fixed Tripod fleet. 16 is not a new
* guess -- it matches messaging.4th's own VM-MAX (the real, already-
* agreed system-wide ceiling: 3 permanent Tripod slots + 13 for
* identities, "identities get NEW slots 3-10, never renumbered" plus
* headroom to VM-MAX itself). See feedback_no_hardcoded_small_scale_bounds
* in project memory -- an invented cap here was rejected before. */
#define SK_SWITCH_MAX_SLOTS 16
/* FABRIC-3.6.md task 3.1 (2026-09-21, ruled B2 / FABRIC-3.5.md §XLV.2):
* replaces the old #define SK_SWITCH_MAX_SLOTS 16 (FABRIC-3.md §XXVIII
* Stage 4, 2026-09-14 -- itself a bump from 8, pinned to messaging.4th's
* VM-MAX constant). The table is now kmalloc'd at
* sk_vm_switch_signal_boot_init() to stadium_max_vm_count() entries --
* every switch-signal participant is a Stadium VM, so this reuses that
* bound directly rather than re-deriving a separate RAM budget, mirroring
* session.c's session_boot_init() (the direct precedent for this pattern,
* itself mirroring stadium.c's own StadiumVMQuota table). See
* feedback_no_hardcoded_small_scale_bounds in project memory -- a fixed
* cap here was rejected before; this closes the same class of bound. */
/* Ticks a non-running participant must accumulate readiness before a
* switch to it is requested. Simple linear accumulate-then-threshold,
@@ -61,7 +67,8 @@ typedef struct {
* function's own doc comment in the header. */
} sk_switch_slot_entry_t;
static sk_switch_slot_entry_t g_slots[SK_SWITCH_MAX_SLOTS];
static sk_switch_slot_entry_t *g_slots = (sk_switch_slot_entry_t *)0;
static int g_slot_capacity; /* 0 until sk_vm_switch_signal_boot_init() succeeds */
static int g_slot_count;
static int g_pending;
static VMUuid g_pending_target;
@@ -71,6 +78,24 @@ static int g_current_slot_cached = -1;
static uint64_t g_switch_count;
static uint32_t g_ticks_since_switch;
/* Freestanding: no libc printf. Prints an unsigned decimal, no leading
* zeros -- same small helper stadium.c/stadium_blocks.c each carry their
* own copy of. */
static void console_put_u64(uint64_t v) {
char buf[21];
int i = 20;
buf[20] = '\0';
if (v == 0) {
console_puts("0");
return;
}
while (v > 0 && i > 0) {
buf[--i] = (char)('0' + (v % 10));
v /= 10;
}
console_puts(&buf[i]);
}
static int slot_for_vm_id(VMUuid vm_id) {
int i;
for (i = 0; i < g_slot_count; i++) {
@@ -79,8 +104,35 @@ static int slot_for_vm_id(VMUuid vm_id) {
return -1;
}
int sk_vm_switch_signal_boot_init(void) {
size_t max_vm_count = stadium_max_vm_count();
sk_switch_slot_entry_t *slots;
size_t i;
if (max_vm_count == 0) return -1; /* Stadium not yet initialized */
slots = (sk_switch_slot_entry_t *)kmalloc(max_vm_count * sizeof(sk_switch_slot_entry_t));
if (!slots) return -1;
for (i = 0; i < max_vm_count; i++) {
slots[i].vm_id = vm_uuid_none();
slots[i].readiness = 0;
slots[i].has_work = 0;
}
g_slots = slots;
g_slot_capacity = (int)max_vm_count;
g_slot_count = 0;
console_puts("Switch-signal: ");
console_put_u64((uint64_t)max_vm_count);
console_println(" slots");
return 0;
}
int sk_vm_switch_signal_register(VMUuid vm_id) {
if (g_slot_count >= SK_SWITCH_MAX_SLOTS) return -1;
if (g_slot_count >= g_slot_capacity) return -1;
g_slots[g_slot_count].vm_id = vm_id;
g_slots[g_slot_count].readiness = 0;
g_slots[g_slot_count].has_work = 0;
@@ -212,6 +264,10 @@ int sk_vm_switch_signal_slot_count(void) {
return g_slot_count;
}
int sk_vm_switch_signal_slot_capacity(void) {
return g_slot_capacity;
}
uint32_t sk_vm_switch_signal_readiness(int slot) {
if (slot < 0 || slot >= g_slot_count) return 0;
return g_slots[slot].readiness;
+9
View File
@@ -526,6 +526,15 @@ static void kernel_main_deep(BootInfo *boot_info) {
* failed session_register() rather than treating it as fatal. */
(void)session_boot_init();
/* Switch-signal slot table: boot-time allocation (FABRIC-3.6.md task
* 3.1, 2026-09-21), sized from stadium_max_vm_count() so it must run
* after stadium_boot_init() above and before the first
* sk_vm_switch_signal_register() call below (Tripod fleet
* registration). Soft failure, same reasoning as stadium_boot_init()/
* session_boot_init() -- register() simply refuses every registration
* (capacity 0) rather than treating this as fatal. */
(void)sk_vm_switch_signal_boot_init();
/* item 4.1, FABRIC-0.md item 3.6/§17.7: actually enforce "Hera is patron
* zero" before anything else can land on cell 0 via the free list, then
* bring up the word layer's map. Both must happen before the first word