Files
LithosAnanake/src/starkernel/kernel_main.c
T
Robert Allan JamesandClaude Sonnet 5 5a28458b21 starkernel: item 4.2 -- Hermes native on the Stadium (complete)
Migrates Hermes's message/channel lifecycle onto the Stadium's unified
heat/capacity economy: MSG-ALLOC/FREE-NODE and CH-ALLOC/FREE-NODE now
route entirely through stadium_admit()/stadium_evict(), replacing the
old local free-list + independent heat-field mechanism. Eight
kernel-only STADIUM-* FORTH primitives (ADMIT, EVICT, RES@, RES-PULL,
RES-PUSH, HEAT@, HEAT!, WORD-HEAT), VM.stadium_vm_id threaded through
all three vm_core.c dispatch sites (replacing item 4.1's hardcoded
vm_uuid_hera()), and the stadium_owner[idx] fix so evict-credit lands
in the VM that actually admitted a patron, not whoever owned cell 0.

This session's own contribution, on top of that pre-existing
implementation: found and fixed two bugs blocking the item's own K≡1.0
conservation self-check (HERMES-K was reading 0, not 65536):

- Q.SLOT admission-heat fix (capsules/hermes/init.4th): MSG-SEND/
  CH-ACCEPT admitted with Q.1 (the entire fleet-wide "1.0" unit) per
  item, a leftover from before the Stadium migration when each
  message/channel had its own unconstrained heat field. Instantly
  drained the shared, finite reservoir.

- Reservoir floor for word-execution admission (stadium_words.c):
  stadium_word_dispatch() (item 4.1) pulls STADIUM_WORD_HEAT_QUANTUM on
  every word dispatch, not just first admission -- exhausts a VM's
  entire reservoir in ~32 dispatches, starving any application-level
  economy sharing that VM's reservoir before it gets a chance to pull
  anything. word_dispatch_pull() now clamps word-execution's own pulls
  to leave a Q48_ONE/3 floor (same fair-share figure COMMON-CH's own
  floor already uses); application-level pulls are unaffected.

- STADIUM-WORD-HEAT primitive + stadium_words_resident_heat(): the
  floor deliberately leaves word-execution residents holding real
  heat, invisible to HERMES-K's original formula (MSG+CH+reservoir,
  no term for word patrons). Adding this term closes K to exactly
  65536 on all three architectures.

Also rules on two open scope questions in FABRIC.md: MBR-ALLOC/
MBR-FREE-NODE stay off the Stadium (membership records have no heat
field, never did -- the acceptance bullet's inclusion of them was a
completeness gesture predating a check of the actual layout), and
records the effort number (12 implementation files, +759/-120 lines).

Verified: all three architectures boot clean, full self-test passes,
Stadium conservation closes exactly (resident_sum + reservoir =
Q48_ONE) at both the C/Stadium level and the FORTH-level HERMES-K
check.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 01:49:23 -04:00

881 lines
37 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0 (the "License");
you may not use this file except in compliance with the License.
*/
/**
* kernel_main.c - StarKernel main entry point (LithosAnanke branch)
*
* Milestone status:
* M0-M5: Complete (build, boot, PMM, VMM, interrupts, timer)
* M6: Infrastructure present (kmalloc exists, validation deferred)
* M7: Not started (VM integration pending)
*/
#ifndef __STARKERNEL__
#error "__STARKERNEL__ must be defined for kernel build"
#endif
#include "uefi.h"
#include "console.h"
#include "arch.h"
#include "pmm.h"
#include "vmm.h"
#include "apic.h"
#include "timer.h"
#include "kmalloc.h"
#include "starkernel/kernel_args.h"
/**
* @brief UEFI Runtime Services pointer — set once at M6 init, valid for kernel lifetime.
*
* Populated from @c boot_info->runtime_services just before the kernel heap is
* initialised (between the M5 timer init and @c kernel_main_deep()). Declared
* @c extern in the UEFI header so kernel FORTH words (e.g. @c REBOOT) can
* access it without including the full @c kernel_main.c translation unit.
*
* Validity note: UEFI Runtime Services remain valid in physical mode after
* @c ExitBootServices(). This kernel does not call @c SetVirtualAddressMap(),
* so the pointer is the raw physical address returned by firmware. On QEMU/OVMF
* this is always usable; on real hardware it is valid as long as the CPU is in
* physical mode (identity-mapped) — which it is for the duration of LithosAnanke,
* since the VMM uses a separate TTBR/CR3 but does not remap the EFI reserved
* regions.
*/
EFI_RUNTIME_SERVICES *g_sk_runtime_services = NULL;
#ifdef STARFORTH_ENABLE_VM
#include "starkernel/vm/bootstrap/sk_vm_bootstrap.h"
#include "starkernel/vm/parity.h"
#include "starkernel/vm/stadium.h"
#include "starkernel/vm/stadium_words.h"
#include "starkernel/capsule_generated.h"
#include "starkernel/capsule_loader.h"
#include "starkernel/capsule_birth.h" /* capsule_birth_mama, capsule_find_mama_init */
#include "starkernel/kmalloc.h"
#include "starkernel/repl.h"
#include "starkernel/pci.h"
#include "starkernel/virtio_blk.h"
#include "block_subsystem.h"
#include "vm.h" /* DictEntry, vm_find_word, ACL_MODE_STRICT */
#include "version.h"
#endif
/* Forward declaration — kernel_main_deep contains everything from heartbeat
* init onward. The 2 MB BSS stack is set up by kernel_entry.S before
* kernel_main_impl is called, so no further stack switch is needed. */
static void kernel_main_deep(BootInfo *boot_info);
/**
* @brief Return non-zero if the EFI memory type represents usable or reclaimable RAM.
*
* Covers all UEFI memory types that either are immediately usable by the PMM or
* can be reclaimed once Boot Services have exited:
* - @c EfiConventionalMemory — general purpose RAM.
* - @c EfiLoaderCode / @c EfiLoaderData — UEFI loader pages (reclaimed post-EBS).
* - @c EfiBootServicesCode / @c EfiBootServicesData — boot-service pages (reclaimed post-EBS).
* - @c EfiRuntimeServicesCode / @c EfiRuntimeServicesData — pages the firmware
* still uses for runtime calls (kept mapped, counted as physical RAM).
* - @c EfiACPIReclaimMemory — ACPI tables; may be freed after OS has parsed them.
* - @c EfiACPIMemoryNVS — non-volatile ACPI storage; kept reserved but is RAM.
*
* Returns 0 for all device-memory, MMIO, persistent-memory, and special types.
* Used by @c print_boot_info() to compute the total physical RAM visible in the
* EFI memory map.
*
* @param type @c EFI_MEMORY_TYPE value from an @c EFI_MEMORY_DESCRIPTOR.
* @return Non-zero if the type is RAM; 0 otherwise.
*/
static int is_ram_type(uint32_t type) {
return type == EfiConventionalMemory ||
type == EfiLoaderCode ||
type == EfiLoaderData ||
type == EfiBootServicesCode ||
type == EfiBootServicesData ||
type == EfiRuntimeServicesCode ||
type == EfiRuntimeServicesData ||
type == EfiACPIReclaimMemory ||
type == EfiACPIMemoryNVS;
}
/**
* @brief Convert a @c uint64_t to a NUL-terminated string in the given base.
*
* Produces a freestanding (no libc) integer-to-string conversion for the
* kernel console paths. Handles bases 216; digits above 9 are lowercase
* alphabetic (@c 'a'@c 'f' for hex). Special case: @p value == 0 writes
* the string @c "0" and returns immediately.
*
* The algorithm builds the digit string in reverse order into a 64-byte
* local @c temp[] buffer, then reverses it into @p buf. @p buf must be at
* least 65 bytes to hold a 64-bit binary string plus NUL; in practice all
* callers pass 64-byte buffers and use base 10 or 16, where the maximum
* length is 20 or 16 digits respectively.
*
* @param value Non-negative integer to convert.
* @param buf Caller-allocated output buffer (minimum 65 bytes for binary).
* @param base Numeric base (216).
*/
static void itoa_simple(uint64_t value, char *buf, int base) {
char temp[64];
int i = 0;
int j;
if (value == 0) {
buf[0] = '0';
buf[1] = '\0';
return;
}
while (value > 0) {
int digit = (int)(value % (uint64_t)base);
temp[i++] = (digit < 10) ? (char)('0' + digit) : (char)('a' + digit - 10);
value /= (uint64_t)base;
}
for (j = 0; j < i; j++) {
buf[j] = temp[i - j - 1];
}
buf[j] = '\0';
}
/**
* @brief Print an optional label followed by a @c uint64_t in decimal to the console.
*
* Converts @p value to a decimal string via @c itoa_simple() and emits it with
* a trailing newline via @c console_println(). If @p label is non-NULL, it is
* emitted first via @c console_puts() (no newline between label and value).
* Used by @c print_pmm_stats() and @c print_heap_stats() to avoid repeating
* the convert-and-print pattern for each statistic line.
*
* @param label Optional NUL-terminated prefix string; NULL to omit.
* @param value 64-bit unsigned integer to display in decimal.
*/
static void print_uint(const char *label, uint64_t value) {
char buf[64];
if (label) {
console_puts(label);
}
itoa_simple(value, buf, 10);
console_println(buf);
}
#ifdef STARFORTH_ENABLE_VM
/**
* @brief Verify a known word is still reachable in a VM's dictionary.
*
* Diagnostic-only: confirms @c vm_find_word() can still walk the chain
* from @c vm->latest to a word defined early in Hermes's capsule
* (MSG-COOL-ALL, block 4108). Added after item 4.2's self-test bisection
* found an amd64-only in-place corruption of an existing dictionary entry
* during MSG-DELIVER-ALL -- here/latest stay unchanged (no reallocation),
* so a lookup is a cheap signal. NOTE: an earlier version of this comment
* attributed the corruption to GDB perturbing execution timing; that is
* unconfirmed and more likely just a parity/dict-hash boot-gate failure
* triggered by the debugger session itself (a software breakpoint's 0xCC
* patch landing in memory the loader then overwrote) -- don't propagate
* "timing-sensitive" as an established finding.
*/
static void hermes_dict_check(VM *hermes_vm, const char *checkpoint) {
DictEntry *e = vm_find_word(hermes_vm, "MSG-COOL-ALL", 12);
console_puts(" Dict-check ");
console_puts(checkpoint);
console_println(e ? ": OK" : ": FAIL (MSG-COOL-ALL unreachable)");
}
#endif
/**
* @brief Print a boot-information summary from the UEFI memory map to the console.
*
* Iterates over every @c EFI_MEMORY_DESCRIPTOR in @c boot_info->memory_map and
* accumulates:
* - @c total_memory — sum of page sizes for all RAM-type regions
* (via @c is_ram_type()).
* - @c usable_memory — sum of page sizes for @c EfiConventionalMemory only.
*
* Emits a three-field report box to the kernel serial console:
* - "Memory map entries: N"
* - "Total memory: N MB" (rounds down to whole MiB)
* - "Usable memory: N MB"
*
* Called from @c kernel_main_impl() / @c kernel_main() immediately after M1
* console initialisation, so the memory map must still be intact (it always
* is — the map was captured by @c uefi_loader.c before @c ExitBootServices()).
*
* @param boot_info @c BootInfo structure populated by @c uefi_loader.c; provides
* @c memory_map, @c memory_map_size, and
* @c memory_map_descriptor_size.
*/
static void print_boot_info(BootInfo *boot_info) {
char buf[64];
UINTN num_entries;
UINTN total_memory = 0;
UINTN usable_memory = 0;
UINTN i;
console_println("\n=== StarKernel Boot Information ===");
num_entries = boot_info->memory_map_size / boot_info->memory_map_descriptor_size;
for (i = 0; i < num_entries; i++) {
EFI_MEMORY_DESCRIPTOR *desc =
(EFI_MEMORY_DESCRIPTOR *)((uint8_t *)boot_info->memory_map +
i * boot_info->memory_map_descriptor_size);
UINTN size = desc->NumberOfPages * 4096u;
if (is_ram_type(desc->Type)) {
total_memory += size;
}
if (desc->Type == EfiConventionalMemory) {
usable_memory += size;
}
}
console_puts("Memory map entries: ");
itoa_simple(num_entries, buf, 10);
console_println(buf);
console_puts("Total memory: ");
itoa_simple((uint64_t)(total_memory / (1024u * 1024u)), buf, 10);
console_puts(buf);
console_println(" MB");
console_puts("Usable memory: ");
itoa_simple((uint64_t)(usable_memory / (1024u * 1024u)), buf, 10);
console_puts(buf);
console_println(" MB");
console_println("===================================\n");
}
/**
* @brief Print Physical Memory Manager statistics to the kernel console.
*
* Calls @c pmm_get_stats() to obtain a @c pmm_stats_t snapshot and then emits
* six lines via @c print_uint():
* - Total pages, free pages, used pages (in 4 KiB page units).
* - Total MB, free MB, used MB (bytes ÷ 1 MiB, truncated).
*
* Called from @c kernel_main_impl() / @c kernel_main() immediately after
* @c pmm_init() completes (M2), providing a sanity check that the PMM saw the
* expected quantity of physical RAM.
*/
static void print_pmm_stats(void) {
pmm_stats_t stats = pmm_get_stats();
console_println("PMM statistics:");
print_uint(" Total pages: ", stats.total_pages);
print_uint(" Free pages : ", stats.free_pages);
print_uint(" Used pages : ", stats.used_pages);
print_uint(" Total MB : ", stats.total_bytes / (1024u * 1024u));
print_uint(" Free MB : ", stats.free_bytes / (1024u * 1024u));
print_uint(" Used MB : ", stats.used_bytes / (1024u * 1024u));
console_println("");
}
/**
* @brief Print kernel heap (kmalloc) statistics to the kernel console.
*
* Calls @c kmalloc_get_stats() to obtain a @c kmalloc_stats_t snapshot and
* emits four lines via @c print_uint():
* - Total bytes allocated to the heap arena.
* - Free bytes currently available.
* - Used bytes currently allocated by callers.
* - Peak bytes — the high-water mark since @c kmalloc_init().
*
* Called from @c kernel_main_impl() / @c kernel_main() immediately after
* @c kmalloc_init() (M6) to confirm that the heap was sized correctly from the
* @c --heap= boot argument or its 2 GiB default.
*/
static void print_heap_stats(void) {
kmalloc_stats_t stats = kmalloc_get_stats();
console_println("Heap statistics:");
print_uint(" Total bytes: ", stats.total_bytes);
print_uint(" Free bytes: ", stats.free_bytes);
print_uint(" Used bytes: ", stats.used_bytes);
print_uint(" Peak bytes: ", stats.peak_bytes);
console_println("");
}
/**
* @brief Print the StarKernel ASCII-art banner and build metadata to the console.
*
* Emits:
* - The "StarKernel" ASCII-art logotype (six-line block font).
* - @c LITHOS_VERSION_STR — the @c LithosAnanke version string from @c version.h.
* - Target ISA: "amd64", "aarch64", "riscv64", or "unknown", selected by
* compile-time @c ARCH_* / @c __riscv preprocessor guards.
* - Build date and time from @c __DATE__ / @c __TIME__ (compiler intrinsics).
* - "UEFI BootServices: EXITED" — confirmation that the kernel is running
* after @c ExitBootServices() and owns all hardware.
*
* Called first in @c kernel_main_impl() / @c kernel_main() after
* @c console_init() so the banner is the first visible output on the serial
* port, matching the @c QEMU_BASELINE.log reference.
*/
static void print_banner(void) {
console_println("");
console_println("");
console_println(" _____ _ _ __ _ ");
console_println(" / ____| | | |/ / | |");
console_println(" | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| |");
console_println(" \\___ \\| __/ _` | '__| < / _ \\ '__| '_ \\ / _ \\ |");
console_println(" ____) | || (_| | | | . \\ __/ | | | | | __/ |");
console_println(" |_____/ \\__\\__,_|_| |_|\\_\\___|_| |_| |_|\\___|_|");
console_println("");
console_println(LITHOS_VERSION_STR);
#if defined(ARCH_AMD64)
console_println("Architecture: amd64");
#elif defined(ARCH_AARCH64)
console_println("Architecture: aarch64");
#elif defined(__riscv)
console_println("Architecture: riscv64");
#else
console_println("Architecture: unknown");
#endif
console_puts("Build: ");
console_puts(__DATE__);
console_puts(" ");
console_println(__TIME__);
console_println("");
console_println("UEFI BootServices: EXITED");
}
/**
* @brief Main kernel entry point after UEFI handoff — executes milestones M0M6.
*
* On amd64 and riscv64, @c kernel_entry.S switches the stack from UEFI's default
* to a 2 MiB zero-initialised BSS stack and tail-calls this function as
* @c kernel_main_impl. On aarch64 the assembly trampoline is not yet implemented
* and the UEFI loader calls @c kernel_main directly.
*
* Milestone sequence:
* - **M0 — Architecture early init** (@c arch_early_init()): On amd64, installs a
* minimal GDT with a proper 64-bit code segment at selector 0x08 and reloads CS
* via @c lretq. Without this, UEFI's 64-bit segment at 0x38 is in scope and the
* ISR's @c INT gate (which expects CS 0x08) would fault silently.
* - **M1 — Console** (@c console_init()): brings up UART 16550 at 115200 8N1 and
* the framebuffer VT100 terminal. Then prints banner and memory map.
* - **M2 — PMM** (@c pmm_init()): initialises the physical memory manager's 4 KiB
* page bitmap from the EFI memory map.
* - **M3 — VMM** (@c vmm_init()): builds 4-level x86-64 page tables, maps all
* conventional RAM at the kernel virtual base, and loads CR3.
* - **M4 — IDT + APIC** (@c arch_interrupts_init() + @c apic_init()): programs the
* 64-entry IDT, masks the legacy 8259A PIC, and initialises the Local APIC in
* xAPIC MMIO mode at 0xFEE00000.
* - **M5 — Timer** (@c timer_init()): calibrates the TSC and HPET.
* - **M6 — Heap** (@c kmalloc_init()): initialises the kernel slab allocator with
* @c heap_size from the boot args or @c KARGS_DEFAULT_HEAP_SIZE (2 GiB).
*
* Stashes @c boot_info->runtime_services in @c g_sk_runtime_services for later
* use by kernel FORTH words (e.g. @c REBOOT). Then tail-calls
* @c kernel_main_deep() for M7 and the REPL.
*
* @param boot_info @c BootInfo populated by @c uefi_loader.c before
* @c ExitBootServices(); provides the memory map, ACPI pointer,
* framebuffer descriptor, runtime services pointer, and parsed
* kernel command-line arguments.
*/
#if defined(__x86_64__) || defined(__riscv)
void kernel_main_impl(BootInfo *boot_info) {
#else
void kernel_main(BootInfo *boot_info) {
#endif
/*
* Establish our own GDT before anything else. UEFI hands us CS=0x38
* (OVMF's 64-bit segment at GDT[7]). Our IDT entries use selector 0x08,
* so if UEFI's GDT[1] (0x08) is not a valid 64-bit code descriptor the
* ISR will run with the wrong CS type and all serial output from the ISR
* will fail silently. arch_early_init() installs a minimal GDT with a
* proper 64-bit code segment at 0x08 and reloads CS via lretq.
*/
arch_early_init();
/* M1: Console initialization — serial UART first */
console_init();
print_banner();
print_boot_info(boot_info);
/* M2: Physical Memory Manager */
pmm_init(boot_info);
console_println("PMM initialized.");
print_pmm_stats();
/* M3: Virtual Memory Manager */
vmm_init(boot_info);
console_println("VMM initialized (mapped RAM, CR3 switched)");
console_println("VMM self-test: mapped OK at 0xffff800000000000");
console_println("VMM self-test complete.\n");
/* M4: Interrupt handling */
arch_interrupts_init();
console_println("IDT installed.\n");
/* M4: APIC */
console_println("APIC: init...");
apic_init(boot_info);
console_println("APIC: init done\n");
/* M5: Timer subsystem */
console_println("Timer: init...");
timer_init(boot_info);
console_println("Timer: init done\n");
/* Stash runtime services for REBOOT word and other kernel FORTH words */
g_sk_runtime_services = boot_info->runtime_services;
/* M6: Kernel heap — sized from --heap= flag, default 2 GiB */
{
uint64_t heap_sz = boot_info->args.heap_size
? boot_info->args.heap_size
: KARGS_DEFAULT_HEAP_SIZE;
kmalloc_init(heap_sz);
}
console_println("Kernel heap initialized.");
print_heap_stats();
/* Hand off to the deep initialization path. The 2 MiB BSS stack was
* already set up by kernel_entry.S (amd64) before this function was
* called, so no further stack switch is needed here. */
kernel_main_deep(boot_info);
}
/**
* @brief Deep kernel initialisation — M5 heartbeat, M7 VM bootstrap, and REPL.
*
* Called as the final act of @c kernel_main_impl() / @c kernel_main() after
* all hardware milestones M0M6 are complete. Runs on the 2 MiB BSS stack on
* amd64 (set up by @c kernel_entry.S before @c kernel_main_impl() was called)
* or the UEFI-provided stack on aarch64 and riscv64.
*
* **M5 — Heartbeat subsystem:**
* Calls @c apic_timer_init(tsc_hz, 100) to configure the APIC timer for 100 Hz
* periodic delivery to vector 32, then @c heartbeat_init(tsc_hz, 100) to
* initialise the rolling-window heartbeat state.
*
* **M7 — VM bootstrap (when @c STARFORTH_ENABLE_VM is defined):**
* 1. @c sk_vm_bootstrap_parity() — allocates the Mama VM and validates the
* capsule directory parity.
* 2. Allocates 1 MiB @c blk_ram_buf (LBN 0991) and 1 MiB @c krd_buf
* (LBN 20483071 = capsule ramdrive) from @c kmalloc, then calls
* @c capsule_blk_init() to wire them into the Mama VM's block subsystem.
* 3. Copies the read-only @c capsule_arena to heap and calls
* @c capsule_exec_init() to load and execute @c init.4th.
* 4. Pins @c CAPSULE-BIRTH and @c BIRTH with @c ACL_MODE_STRICT via
* @c vm_find_word() so that ACL policy cannot downgrade them.
*
* After M7, the APIC timer is started via @c apic_timer_start() and
* @c arch_enable_interrupts() enables IRQs.
*
* **REPL (when @c STARFORTH_ENABLE_VM is defined):**
* - If @c boot_info->args.run_doe is set, injects @c "12345 3 EXEC-DOE BYE"
* before the interactive REPL.
* - If @c SK_STARTUP_FORTH is defined at build time, executes it as a
* compile-time startup script (lowest priority — overridden by @c --doe).
* - Activates the framebuffer VT100 terminal (if the framebuffer descriptor
* is valid) so the REPL output appears on screen as well as the serial port.
* - Clears the @c StarForthRebootTries NVRAM variable to signal a clean boot.
* - Calls @c sk_repl() — the interactive FORTH REPL loop. Returns when the
* user executes @c BYE or @c vm->halted is set.
*
* Terminates with an infinite @c arch_halt() idle loop regardless of the
* @c STARFORTH_ENABLE_VM build configuration.
*
* @param boot_info The @c BootInfo passed from @c kernel_main_impl().
*/
static void kernel_main_deep(BootInfo *boot_info) {
/* M5: Initialize heartbeat subsystem */
console_println("Heartbeat: init...");
uint64_t tsc_hz = timer_tsc_hz();
if (apic_timer_init(tsc_hz, 100) != 0) {
console_println("APIC Timer initialization failed.");
}
heartbeat_init(tsc_hz, 100); /* 100 Hz tick rate */
console_println("Heartbeat: init done");
console_println("Kernel initialization complete.");
console_println("Boot successful!\n");
#ifdef STARFORTH_ENABLE_VM
/* Stadium: boot-time allocation (FABRIC.md item 3.2), before any VM
* exists (§6). Soft failure -- nothing downstream consumes the Stadium
* yet, so a failed allocation logs and boot continues. */
(void)stadium_boot_init();
/* item 4.1, FABRIC.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
* ever dispatches -- capsule birth below runs init.4th, which dispatches
* words. */
(void)stadium_birth_hera();
stadium_words_init();
/* M7: VM Bootstrap and Parity Validation */
console_println("VM: bootstrap parity...");
ParityPacket parity_pkt;
int vm_rc = sk_vm_bootstrap_parity(&parity_pkt);
if (vm_rc != 0) {
console_println("VM: parity bootstrap FAILED");
} else {
console_println("VM: parity bootstrap complete");
}
/* Wire parity log so PARITY:MAMA_INIT/BIRTH/RUN/KILL reach serial */
capsule_parity_set_output(NULL, console_puts);
/* M7.1: Execute init.4th via the proper Mama birth protocol.
* capsule_arena lives in .rodata; copy to heap so the interpreter
* can safely read payload bytes after VMM takeover. */
void *mama_vm = sk_get_mama_vm();
/* Block subsystem: fast RAM (LBN 0..2047) + ramdrive (LBN 2048..3071).
* BLK_RAM_SIZE must cover BLK_RAM_BLOCKS × BLK_FORTH_SIZE. */
#define BLK_RAM_SIZE (BLK_RAM_BLOCKS * BLK_FORTH_SIZE)
uint8_t *blk_ram_buf = (uint8_t *)kmalloc(BLK_RAM_SIZE);
/* Kernel ramdrive: 1024 blocks × 1 KiB covering LBN 2048-3071 */
#define KRD_BUF_SIZE (1024u * 1024u)
uint8_t *krd_buf = (uint8_t *)kmalloc(KRD_BUF_SIZE);
if (!blk_ram_buf || !krd_buf) {
console_println("Init: blk alloc FAILED");
} else {
/* Pre-zero the ramdrive buffer (no memset in freestanding context) */
size_t krd_i;
for (krd_i = 0; krd_i < KRD_BUF_SIZE; krd_i++) krd_buf[krd_i] = 0;
/* Init block subsystem (RAM + ramdrive) */
capsule_blk_init(mama_vm, blk_ram_buf, BLK_RAM_SIZE, krd_buf);
}
/* M7.pre: PCI + Artemis virtio-blk disk — attached AFTER block subsystem init */
console_println("PCI: init...");
pci_init(boot_info->acpi_table);
{
static blkio_dev_t artemis_dev;
int vrc = virtio_blk_find_artemis(&artemis_dev);
if (vrc == 0) {
console_println("Artemis: virtio-blk attached");
blk_subsys_attach_device(&artemis_dev);
} else {
console_println("Artemis: no virtio-blk disk (continuing without)");
}
}
/* Copy capsule directory header to heap (has pointer field needing update) */
CapsuleDirHeader *live_dir = (CapsuleDirHeader *)kmalloc(sizeof(CapsuleDirHeader));
if (!live_dir) {
console_println("Init: dir alloc FAILED");
} else {
const CapsuleDirHeader *src_dir = &capsule_directory;
live_dir->magic = src_dir->magic;
live_dir->arena_base = src_dir->arena_base;
live_dir->arena_size = src_dir->arena_size;
live_dir->desc_count = src_dir->desc_count;
live_dir->desc_capacity = src_dir->desc_capacity;
live_dir->name_count = src_dir->name_count;
live_dir->reserved = src_dir->reserved;
live_dir->dir_hash = src_dir->dir_hash;
uint8_t *arena_copy = (uint8_t *)kmalloc((size_t)live_dir->arena_size);
if (!arena_copy) {
console_println("Init: arena alloc FAILED");
} else {
const uint8_t *src = capsule_arena;
uint8_t *dst = arena_copy;
size_t n = (size_t)live_dir->arena_size;
while (n--) *dst++ = *src++;
live_dir->arena_base = (uint64_t)(uintptr_t)arena_copy;
console_println("Init: Mama birth...");
CapsuleRunResult cr = capsule_birth_mama(
mama_vm,
live_dir,
capsule_descriptors,
capsule_names,
arena_copy);
if (cr == CAPSULE_RUN_OK) {
console_println("Init: Mama birth OK");
/* Free ramdrive slots so init.4th blocks are available for userspace */
const CapsuleDesc *mama_cap =
capsule_find_mama_init(live_dir, capsule_descriptors);
if (mama_cap)
capsule_clear_blocks(arena_copy + mama_cap->offset,
mama_cap->length);
} else {
console_println("Init: Mama birth FAILED");
}
/* Pin kernel-only privileged words that ACL.4th cannot reach
* portably (BIRTH/CAPSULE-BIRTH do not exist in the hosted VM).
* Done in C after capsule load so ACL.4th stays host-portable. */
VM *mama_vm_ptr = (VM *)sk_get_mama_vm();
DictEntry *capsule_birth = vm_find_word(mama_vm_ptr, "CAPSULE-BIRTH", 13);
if (capsule_birth) {
capsule_birth->acl_mode = ACL_MODE_STRICT;
capsule_birth->acl_pinned = 1;
console_println("ACL: CAPSULE-BIRTH pinned STRICT");
}
DictEntry *birth = vm_find_word(mama_vm_ptr, "BIRTH", 5);
if (birth) {
birth->acl_mode = ACL_MODE_STRICT;
birth->acl_pinned = 1;
console_println("ACL: BIRTH pinned STRICT");
}
}
}
#else
console_println("=== LithosAnanke Checkpoint ===");
console_println("M0-M6: Complete");
console_println("M7: Disabled (build with STARFORTH_ENABLE_VM=1)");
console_println("================================\n");
#endif
/* Start heartbeat and enable interrupts */
console_println("Starting heartbeat...");
apic_timer_start();
arch_enable_interrupts();
console_println("Heartbeat running.");
#ifdef STARFORTH_ENABLE_VM
VM *mama = (VM *)sk_get_mama_vm();
/* item 4.1 diagnostic (§25.5 acceptance: "observable via a diagnostic
* word or boot console output"): word patrons already dispatched during
* capsule birth above, so this is non-vacuous by this point. */
stadium_words_print_boot_diagnostics(vm_uuid_hera());
/* item 4.1a self-test: exercises stadium_grant_quota() with a synthetic
* identity, NOT vm_uuid_next()'s real birth pool (would perturb the
* deterministic ID stream real BIRTH calls draw from) and NOT a real
* capsule birth (item 0.1 pruned automatic Hermes birth from init.4th;
* restoring it is item 4.2's job, not this one's). Diagnostic only --
* the synthetic VM is never used for anything else. */
{
VMUuid test_id;
test_id.hi = 0;
test_id.lo = 1; /* distinct from vm_uuid_hera() (all-zero) and
* vm_uuid_none() (all-ones) */
int grant_rc = stadium_grant_quota(test_id, vm_uuid_hera());
console_puts("Stadium quota grant self-test: ");
console_println(grant_rc == 0 ? "OK" : "REFUSED");
if (grant_rc == 0) {
print_uint(" Hera reservoir=", stadium_reservoir_peek(vm_uuid_hera()));
print_uint(" test-vm reservoir=", stadium_reservoir_peek(test_id));
}
}
/* item 4.2 self-test: a REAL birth (not synthetic, unlike 4.1a's --
* this exercises capsules/hermes/init.4th's actual migrated code),
* exercised then killed again so the resting boot state stays
* Hera-alone, per item 0.1's intent. Diagnostic only -- production
* boot still never auto-births Hermes (init.4th's BIRTH stays
* commented out). */
console_println("Hermes 4.2 migration self-test: birthing...");
vm_interpret(mama, "S\" Hermes\" BIRTH");
{
VMRegistryEntry entry;
if (capsule_vm_find_by_name_nocase("Hermes", &entry) == 0 &&
entry.state == VM_STATE_LIVE) {
VM *hermes_vm = (VM *)entry.vm_ptr;
console_println("Hermes 4.2 self-test: exercising migrated words...");
hermes_dict_check(hermes_vm, "at self-test start");
hermes_vm->error = 0;
vm_interpret(hermes_vm, "CD-INIT");
print_uint(" DBG err after CD-INIT=", (uint64_t)hermes_vm->error);
hermes_dict_check(hermes_vm, "after CD-INIT");
hermes_vm->error = 0;
vm_interpret(hermes_vm, "HERMES-MSG-TEST . CR");
print_uint(" DBG err after MSG-TEST=", (uint64_t)hermes_vm->error);
hermes_dict_check(hermes_vm, "after MSG-TEST");
hermes_vm->error = 0;
vm_interpret(hermes_vm, "HERMES-STATUS");
print_uint(" DBG err after STATUS=", (uint64_t)hermes_vm->error);
hermes_dict_check(hermes_vm, "after STATUS");
hermes_vm->error = 0;
vm_interpret(hermes_vm, "MSG-DELIVER-ALL");
print_uint(" DBG err after DELIVER-ALL=", (uint64_t)hermes_vm->error);
hermes_dict_check(hermes_vm, "after DELIVER-ALL");
hermes_vm->error = 0;
vm_interpret(hermes_vm, "MSG-REDELIVER-NACKED");
print_uint(" DBG err after REDELIVER=", (uint64_t)hermes_vm->error);
hermes_vm->error = 0;
vm_interpret(hermes_vm, "MSG-COOL-ALL");
print_uint(" DBG err after MSG-COOL-ALL=", (uint64_t)hermes_vm->error);
hermes_vm->error = 0;
vm_interpret(hermes_vm, "MSG-REAP");
print_uint(" DBG err after MSG-REAP=", (uint64_t)hermes_vm->error);
hermes_vm->error = 0;
vm_interpret(hermes_vm, "CH-COOL-ALL CH-REAP-SAFE");
print_uint(" DBG err after CH-COOL/REAP=", (uint64_t)hermes_vm->error);
hermes_vm->error = 0;
vm_interpret(hermes_vm, "Q.1 3 / COMMON-CH @ CH-HEAT!");
print_uint(" DBG err after CH-HEAT!=", (uint64_t)hermes_vm->error);
hermes_vm->error = 0;
vm_interpret(hermes_vm, "HERMES-STATUS");
print_uint(" DBG err after TICK=", (uint64_t)hermes_vm->error);
hermes_vm->error = 0;
console_puts("Hermes 4.2 self-test: HERMES-K=");
vm_interpret(hermes_vm, "HERMES-K .");
console_println("");
print_uint(" DBG err after HERMES-K=", (uint64_t)hermes_vm->error);
/* Side-by-side with the Stadium's own view, same checkpoint --
* if these two disagree, HERMES-K's FORTH-side arena scan is
* seeing different residents than stadium_resident_sum()'s
* ownership+bitmap view (a real, separate finding, not the
* Q.SLOT admission-heat fix's job to explain). */
print_uint(" DBG stadium_resident_sum(Hermes)=", stadium_resident_sum(entry.vm_id));
print_uint(" DBG stadium_reservoir_peek(Hermes)=", stadium_reservoir_peek(entry.vm_id));
/* item 4.2 Done-when: "a resident cell's evict-credit landing
* in the correct VM's reservoir ... not just asserted from
* reading the code" -- explicitly evict the common channel
* (CH-FREE-NODE -> STADIUM-EVICT), a known resident from
* CD-INIT's own COMMON-INIT, same credit path stadium_admit()'s
* density-fallback eviction uses. resident_sum dropping and
* reservoir rising by the same amount is the proof: the freed
* cell's heat landed back in Hermes's own reservoir, not lost
* or credited to Hera. */
console_println("Hermes 4.2 self-test: before eviction:");
print_uint(" Hermes resident_sum=", stadium_resident_sum(entry.vm_id));
print_uint(" Hermes reservoir=", stadium_reservoir_peek(entry.vm_id));
console_println("Hermes 4.2 self-test: forcing an explicit eviction (COMMON-CH)...");
hermes_vm->error = 0;
vm_interpret(hermes_vm, "COMMON-CH @ . CR");
print_uint(" DBG err after COMMON-CH@=", (uint64_t)hermes_vm->error);
hermes_vm->error = 0;
vm_interpret(hermes_vm, "COMMON-CH @ CH-FREE-NODE");
print_uint(" DBG err after CH-FREE-NODE=", (uint64_t)hermes_vm->error);
console_println("Hermes 4.2 self-test: after eviction:");
print_uint(" Hermes resident_sum=", stadium_resident_sum(entry.vm_id));
print_uint(" Hermes reservoir=", stadium_reservoir_peek(entry.vm_id));
/* item 4.2 Done-when: both VMs' conservation checks close
* independently -- Hermes's own resident+reservoir sum first,
* then Hera's again (unaffected by Hermes's activity above). */
stadium_words_print_boot_diagnostics(entry.vm_id);
vm_interpret(mama, "S\" Hermes\" KILL");
console_println("Hermes 4.2 self-test: killed, resting state restored");
stadium_words_print_boot_diagnostics(vm_uuid_hera());
} else {
console_println("Hermes 4.2 self-test: birth registry lookup FAILED");
}
}
/*
* Runtime --doe flag: inject "EXEC-DOE BYE" if requested via boot args.
* Checked before SK_STARTUP_FORTH so a runtime --doe takes precedence.
*/
if (boot_info->args.run_doe) {
console_println("Startup: --doe flag set — running EXEC-DOE");
vm_interpret(mama, "12345 3 EXEC-DOE BYE");
if (mama->error) {
console_println("Startup: EXEC-DOE ERROR");
mama->error = 0;
}
if (mama->halted) goto idle;
}
/*
* SK_STARTUP_FORTH — compile-time script injection (lowest priority).
* Usage: make -f Makefile.starkernel qemu SK_CMD="TIME-TICKS . BYE"
*/
#ifdef SK_STARTUP_FORTH
console_puts("Startup: ");
console_println(SK_STARTUP_FORTH);
vm_interpret(mama, SK_STARTUP_FORTH);
if (mama->error) {
console_puts("Startup: ERROR\n");
mama->error = 0;
}
if (mama->halted) goto idle;
#endif
/* Activate framebuffer VT100 now that POST is done — REPL-only */
if (boot_info->framebuffer.base != NULL && boot_info->framebuffer.size > 0) {
FbPixelFormat fb_fmt;
switch (boot_info->framebuffer.pixel_format) {
case (UINT32)PixelRedGreenBlueReserved8BitPerColor: fb_fmt = FB_PIXEL_RGBX32; break;
case (UINT32)PixelBlueGreenRedReserved8BitPerColor: fb_fmt = FB_PIXEL_BGRX32; break;
default: fb_fmt = FB_PIXEL_BGRX32; break;
}
console_fb_init(&boot_info->framebuffer, fb_fmt);
}
/* Clear reboot-tries counter: we reached the REPL cleanly */
if (g_sk_runtime_services) {
EFI_GUID vendor_guid = STARFORTH_VENDOR_GUID;
EFI_SET_VARIABLE SetVariable =
(EFI_SET_VARIABLE)g_sk_runtime_services->SetVariable;
SetVariable(
(CHAR16 *)SF_VAR_REBOOT_TRIES,
&vendor_guid,
EFI_VARIABLE_NON_VOLATILE |
EFI_VARIABLE_BOOTSERVICE_ACCESS |
EFI_VARIABLE_RUNTIME_ACCESS,
0, NULL);
}
/* Phase 0 acceptance (§25.1 item 0.10): "tick count non-zero" has to be
* true, not merely likely -- the timer was just armed above, so with no
* wait here the count depends on how much boot work happened to run
* concurrently with interrupts enabled, which measured 1 tick on amd64
* and 0 on riscv64 in practice. Bounded busy-wait for a few real ticks
* (not a virtual-tick construct; §16.4/§18.5 govern patron state, not
* this one-time boot diagnostic) rather than reporting whatever count
* happened to land. */
{
uint64_t wait_start = heartbeat_ticks();
uint64_t spins = 0;
while (heartbeat_ticks() - wait_start < 3 && spins < 100000000ULL) {
arch_relax();
spins++;
}
}
console_puts("Heartbeat: ");
{
char buf[24]; uint64_t v = heartbeat_ticks(); int i = 0, j = 0; char t[24];
if (v == 0) buf[i++] = '0';
else { while (v > 0) { t[j++] = (char)('0' + (v % 10)); v /= 10; } while (j > 0) buf[i++] = t[--j]; }
buf[i] = '\0';
console_puts(buf);
}
console_puts(" ticks, trust=0x");
{
char buf[9]; uint32_t v = (uint32_t)heartbeat_trust();
for (int k = 7; k >= 0; k--) {
int nib = (int)((v >> (k * 4)) & 0xF);
buf[7 - k] = (char)(nib < 10 ? '0' + nib : 'a' + nib - 10);
}
buf[8] = '\0';
console_puts(buf);
}
console_puts(", variance=0x");
{
char buf[9]; uint32_t v = (uint32_t)heartbeat_state()->variance;
for (int k = 7; k >= 0; k--) {
int nib = (int)((v >> (k * 4)) & 0xF);
buf[7 - k] = (char)(nib < 10 ? '0' + nib : 'a' + nib - 10);
}
buf[8] = '\0';
console_puts(buf);
}
console_println("");
sk_repl(mama);
#endif
/* Idle loop (reached if sk_repl exits via BYE or vm->halted) */
#ifdef STARFORTH_ENABLE_VM
idle:
#endif
for (;;) {
arch_halt();
}
}