Files
LithosAnanke/kernel/src/capsule/capsule_runcap.c
T
rajamesandJunie a8b70e88d3 Reorganize source tree: kernel/, v3/, v4/ split and board infrastructure
Source tree reorganization:
- Move StarForth v3 engine to v3/ (src/, include/, Makefile)
- Move kernel to kernel/ (src/, include/, linker/, Makefile)
- Create v4/ skeleton for F18-ISA golden model (DECOMPOSITION.md, JUSTIFICATION.md)
- Move FABRIC-0..4.md to docs/fabric/
- Move ONTOLOGY.md and ROADMAP.md to docs/

Board infrastructure:
- Add boards/ser5/, boards/raspi/, boards/milkv/, boards/zynq7020/
- Each board has board.mk (ISA, CPU flags, boot recipe) and README.md
- Root Makefile becomes thin dispatcher: boot_image, all, clean, docs take TARGET
- make boot_image TARGET=SER5|RASPI|MILKV builds one GPT/MBR image per board
- ZYNQ7020 target exists but stops with clear error (ARMv7 port not built yet)
- scripts/mkdiskimage.sh builds disk images for all boards

Docs pipeline:
- docs/book/ with LaTeX master (main.tex) and Makefile
- pandoc converts Markdown to LaTeX at build time
- Two Lua filters: table-widths.lua (wide tables wrap), code-breaks.lua (inline code breaks)
- make docs builds single PDF (754 pages, 0 missing characters)
- make docs TARGET=<board> adds board appendix
- build/docs/<book|board>/meta.tex stamps git commit into PDF

Bug fixes:
- 42 include paths that only worked by accident now use correct relative paths
- clang-18 hardcode replaced with configurable CC variable (fixed aarch64 build)
- Pi 5: kernel_2712.img linked at 0x80000, .bss zeroed, memory reserved
- Doxyfile, .clang-tidy, README.md, Kconfig paths updated

Verified:
- Hosted v3 build passes 1012 tests, 0 failures
- SER5 image boots in QEMU (OVMF), POST passes, K exact (65536 = Q48_ONE)
- Milk-V image boots in QEMU (OpenSBI + U-Boot + bootefi), POST passes
- make clean TARGET=<board> removes only that board and its ISA objects
- make all builds all boards, hosted v3, and docs in one run

Co-authored-by: Junie <junie@jetbrains.com>
2026-10-01 15:40:09 -04:00

145 lines
6.1 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) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
#ifndef __STARKERNEL__
#error "capsule_runcap.c is kernel-only"
#endif
#include "starkernel/capsule_runcap.h"
#include "starkernel/capsule.h"
#include "starkernel/capsule_birth.h"
#include "starkernel/xxhash64.h"
#include "starkernel/kmalloc.h"
#include "blkio.h"
#include <string.h>
CapsuleRunResult capsule_runcap_birth(
struct blkio_dev *dev,
const homeblocks_sig_t *sig,
const char *vm_name,
VMUuid parent,
VMUuid *out_vm_id,
void **out_vm_ctx)
{
if (!dev || !sig || !vm_name || !out_vm_id) return CAPSULE_RUN_ERR_INVALID;
/* 0 = never minted (homeblocks_sig.h's own field doc). */
if (sig->identity_src_offset == 0) return CAPSULE_RUN_ERR_INVALID;
/* First devblock is the user_identity_seed_t record (MINT, §F.8);
* everything after it is raw FORTH source. Need at least one devblock
* of actual source beyond the seed. */
if (sig->identity_src_devblocks < 2) return CAPSULE_RUN_ERR_INVALID;
uint32_t source_devblocks = sig->identity_src_devblocks - 1u;
uint64_t source_len = (uint64_t)source_devblocks * 4096u;
uint8_t *arena = (uint8_t *)kmalloc((size_t)source_len);
if (!arena) return CAPSULE_RUN_ERR_STILLBORN;
/* homeblocks_sig_t's own offset/devblocks fields are in 4KiB
* devblocks; blkio_read() works in 1KiB forth-blocks (same *4
* conversion homeblocks_sig_check() already uses for its own
* sig_start_fblock). Skip the seed devblock (+1 devblock = +4
* forth-blocks) before reading source content. */
uint32_t base_fblock = (sig->identity_src_offset + 1u) * 4u;
uint32_t source_fblocks = source_devblocks * 4u;
uint32_t i;
for (i = 0; i < source_fblocks; i++) {
if (blkio_read((blkio_dev_t *)dev, base_fblock + i,
arena + (size_t)i * BLKIO_FORTH_BLOCK_SIZE) != BLKIO_OK) {
return CAPSULE_RUN_ERR_INVALID;
}
}
/* FABRIC-3.md SXXV follow-up (2026-09-13): source_len above is the
* FULL reserved region (source_devblocks worth of 4KiB devblocks) --
* MINT (capsule_mint.c) only ever writes a short personality string
* into it and memset()s the rest of that one devblock to 0 first, but
* never touches whatever devblocks beyond the first were reserved for
* identity_src, and this loop reads all of them regardless. Real
* content is a handful of lines; everything past it, for the whole
* remaining reserved region, is zero bytes with no "Block N" header
* of its own to stop capsule_exec_payload()'s own block-flush scan --
* confirmed live as the actual cause of "WARN: block 4998 exceeds
* 1KB, truncating", firing for every std79-locked identity, every
* boot, since before this was found: the parser had no way to tell
* "real content ends here" from "just more of this same block," so it
* walked the whole zero-padded region as one oversized block. FORTH
* source never legitimately contains a raw NUL, so it's a safe,
* ordinary C-string-style terminator here: trim source_len down to
* the first NUL actually found, restoring the block-parser's normal
* behavior instead of teaching it a special case for this one
* caller's own padding convention. */
{
uint64_t real_len = source_len;
uint64_t k;
for (k = 0; k < source_len; k++) {
if (arena[k] == '\0') { real_len = k; break; }
}
source_len = real_len;
}
/* Heap-built single-entry directory -- exact shape §F.6 traced
* against capsule_birth_baby()'s own parameters, not a new mechanism.
* Never freed: matches kernel_main.c's own compile-time-directory
* heap copy at Mama's birth, which is also never freed -- the VM's
* IDENTITY exec reads directly from this arena. */
CapsuleNameEntry name_entry;
memset(name_entry.name, 0, sizeof(name_entry.name));
{
size_t n = strlen(vm_name);
if (n >= CAPSULE_NAME_MAX) n = CAPSULE_NAME_MAX - 1u;
memcpy(name_entry.name, vm_name, n);
}
CapsuleDesc desc;
memset(&desc, 0, sizeof(desc));
desc.magic = CAPSULE_MAGIC_PACK(CAPSULE_VERSION_0, CAPSULE_HASH_XXHASH64);
desc.content_hash = xxhash64_capsule(arena, (size_t)source_len);
desc.capsule_id = desc.content_hash; /* content-addressed invariant */
desc.offset = 0;
desc.length = source_len;
desc.flags = CAPSULE_FLAG_ACTIVE | CAPSULE_FLAG_PRODUCTION;
desc.owner_vm = 0;
desc.birth_count = 0;
desc.created_ns = 0; /* no monotonic-ns source exists anywhere in this
* codebase yet, §F.8's own open item -- matches
* CapsuleDesc.created_ns's existing hardcoded-0
* precedent at mkcapsule generation time. */
CapsuleDirHeader dir;
memset(&dir, 0, sizeof(dir));
dir.magic = CAPSULE_DIR_MAGIC;
dir.arena_base = (uint64_t)(uintptr_t)arena;
dir.arena_size = source_len;
dir.desc_count = 1;
dir.desc_capacity = 1;
dir.name_count = 1;
dir.dir_hash = 0; /* not verified anywhere in the birth path today */
CapsuleRunResult r = capsule_birth_baby(
vm_name, &dir, &desc, &name_entry, arena, parent,
1 /* skip_pki_sig -- trust comes from CERTVERIFY, a separate root */,
out_vm_id, out_vm_ctx);
/* capsule_birth_baby() itself never sets the registry entry's own
* .name -- every existing caller (mama_word_birth, CONNECT-HERMES/
* -ARTEMIS) does this as a separate step after a successful birth,
* via capsule_vm_registry_set_name(). Found live: skipping this left
* a freshly RUNCAP-born VM's registry name empty, which the idle-loop
* pump (FABRIC-2.md Phase C) then read as a zero-length name and
* refused ("VM name too long or empty") every tick. */
if (r == CAPSULE_RUN_OK && out_vm_id) {
capsule_vm_registry_set_name(*out_vm_id, vm_name);
}
return r;
}