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>
This commit is contained in:
+1380
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
# include/starkernel/
|
||||
|
||||
Headers for LithosAnanke, the bare-metal UEFI kernel (`src/starkernel/`).
|
||||
Built only via `Makefile.starkernel`; gated by `__STARKERNEL__` when shared
|
||||
with hosted code.
|
||||
|
||||
- `uefi.h` — UEFI protocol/type definitions consumed by the loader.
|
||||
- `elf64.h`, `elf_loader.h` — ELF64 parsing and kernel-image loading.
|
||||
- `boot_info_offsets.h` — struct-offset constants shared between the
|
||||
assembly bootstrap and the C boot path.
|
||||
- `arch.h`, `apic.h`, `timer.h` — architecture init, APIC interrupt
|
||||
controller, timer (TSC/HPET/APIC, 100 Hz heartbeat).
|
||||
- `console.h`, `framebuffer.h`, `vt100.h` — UART 16550 console, framebuffer
|
||||
driver, and VT100 terminal emulation over the framebuffer.
|
||||
- `pmm.h`, `vmm.h`, `kmalloc.h` — physical memory manager (bitmap
|
||||
allocator), 4-level x86_64 paging, kernel heap allocator.
|
||||
- `pci.h`, `virtio_blk.h` — PCI enumeration and the VirtIO block device
|
||||
driver (disk backend for the kernel block subsystem).
|
||||
- `capsule.h`, `capsule_birth.h`, `capsule_loader.h`, `capsule_run.h`,
|
||||
`capsule_vm_physics.h`, `capsule_generated.h` — capsule system types,
|
||||
birth protocol, physics-runtime capsule bindings, and the build-time-
|
||||
generated capsule directory (see `tools/mkcapsule.c`).
|
||||
- `kernel_args.h`, `cmdline.h` — boot-time kernel argument parsing
|
||||
(`starforth.cfg` / command line).
|
||||
- `repl.h` — kernel REPL.
|
||||
- `log.h`, `doe_log.h` — kernel logging and DoE metrics logging.
|
||||
- `q48_16.h` — kernel-build copy of Q48.16 fixed-point arithmetic.
|
||||
- `xxhash64.h` — content-addressing hash used for capsule IDs.
|
||||
- `hal_memory.h` — hardware-abstraction-layer memory interface.
|
||||
|
||||
Subdirectories:
|
||||
- `hal/` — top-level hardware-abstraction-layer interface.
|
||||
- `vm/` — kernel VM subsystem headers (capsule arena, parity logging,
|
||||
bootstrap wiring).
|
||||
- `freestanding/` — minimal libc-shim headers (`assert.h`, `ctype.h`,
|
||||
`errno.h`, `inttypes.h`, `math.h`, `sched.h`, `signal.h`, `stdio.h`,
|
||||
`stdlib.h`, `string.h`, `time.h`, `sys/time.h`, `sys/types.h`) for
|
||||
building shared VM code in the freestanding kernel environment, where no
|
||||
real libc is available.
|
||||
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* apic.h - Local APIC interface
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_APIC_H
|
||||
#define STARKERNEL_APIC_H
|
||||
|
||||
#include "uefi.h"
|
||||
#include <stdint.h>
|
||||
|
||||
/* Heartbeat timer vector (user-defined IRQ space starts at 0x20) */
|
||||
#define APIC_TIMER_VECTOR 0x20
|
||||
|
||||
/* Spurious-interrupt vector (APIC_REG_SIVR, set in apic_init()). A normal,
|
||||
* occasional hardware race per Intel SDM Vol.3 §10.9 -- not a fault. Must
|
||||
* be silently ignored with no EOI. Surfaced by item 4.3.5's I/O APIC work:
|
||||
* no real external interrupt had ever been delivered through the I/O APIC
|
||||
* before, so this path was previously dormant. */
|
||||
#define APIC_SPURIOUS_VECTOR 0xFF
|
||||
|
||||
/* Initialize Local APIC (enables APIC, sets spurious vector) */
|
||||
int apic_init(BootInfo *boot_info);
|
||||
|
||||
/* Return this CPU's xAPIC ID (APIC_REG_ID bits 31:24) — used as the
|
||||
* destination field when programming I/O APIC redirection entries. */
|
||||
uint8_t apic_id(void);
|
||||
|
||||
/* Send End-of-Interrupt signal */
|
||||
void apic_eoi(void);
|
||||
|
||||
/*
|
||||
* Initialize APIC timer for periodic heartbeat.
|
||||
* @param tsc_hz TSC frequency in Hz (for calibration)
|
||||
* @param tick_hz Desired tick frequency (e.g., 100 = 100 Hz = 10ms period)
|
||||
* @return 0 on success, -1 on failure
|
||||
*/
|
||||
int apic_timer_init(uint64_t tsc_hz, uint32_t tick_hz);
|
||||
|
||||
/**
|
||||
* Start the APIC timer (enables periodic interrupts).
|
||||
* Call this after IDT and heartbeat handler are set up.
|
||||
*/
|
||||
void apic_timer_start(void);
|
||||
|
||||
/**
|
||||
* Stop the APIC timer (disables periodic interrupts).
|
||||
*/
|
||||
void apic_timer_stop(void);
|
||||
|
||||
/**
|
||||
* Re-arm the APIC timer at the current adaptive period (item 0.8, §26).
|
||||
* Recomputes the initial count from heartbeat_next_period_ns() and writes
|
||||
* it to the ICR; a periodic-mode ICR write restarts the countdown
|
||||
* immediately at the new value. Called once per tick from the ISR, before
|
||||
* heartbeat_tick().
|
||||
*/
|
||||
void apic_timer_rearm(void);
|
||||
|
||||
/**
|
||||
* Get the configured timer period in TSC ticks.
|
||||
*/
|
||||
uint64_t apic_timer_period_tsc(void);
|
||||
|
||||
#endif /* STARKERNEL_APIC_H */
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* arch.h - Architecture abstraction layer for StarKernel
|
||||
*
|
||||
* Provides a minimal interface used across early boot, interrupt control,
|
||||
* low-power halting, timestamp reads, and MMU bring-up.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_ARCH_H
|
||||
#define STARKERNEL_ARCH_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Early CPU setup prior to enabling higher-level subsystems */
|
||||
void arch_early_init(void);
|
||||
|
||||
/* Interrupt control */
|
||||
void arch_enable_interrupts(void);
|
||||
void arch_disable_interrupts(void);
|
||||
void arch_interrupts_init(void);
|
||||
|
||||
/* Halt/idle CPU until the next interrupt */
|
||||
void arch_halt(void);
|
||||
|
||||
/* Hard reset the system (does not return) */
|
||||
void arch_cold_reset(void) __attribute__((noreturn));
|
||||
|
||||
/* Low-overhead timestamp counter (architecture-specific source) */
|
||||
uint64_t arch_read_timestamp(void);
|
||||
|
||||
/* MMU initialization hook (platform-specific implementation) */
|
||||
void arch_mmu_init(void);
|
||||
|
||||
/* Architecture-friendly pause/yield hint inside busy loops */
|
||||
static inline void arch_relax(void)
|
||||
{
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
__asm__ volatile ("pause");
|
||||
#elif defined(__aarch64__)
|
||||
__asm__ volatile ("yield");
|
||||
#elif defined(__riscv)
|
||||
__asm__ volatile ("nop");
|
||||
#else
|
||||
__asm__ volatile ("" ::: "memory");
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif /* STARKERNEL_ARCH_H */
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* artemis_sig.h - Artemis disk signature format (FABRIC-3.md §XXVI follow-on,
|
||||
* 2026-09-13)
|
||||
*
|
||||
* Identifies Artemis's own disk, distinct from an identity thumbdrive's
|
||||
* homeblocks_sig_t -- needed once Artemis's disk stops being found by a
|
||||
* hardcoded PCI virtio-blk vendor/device ID scan (QEMU-only; real hardware
|
||||
* has no reason to expose a virtio-blk PCI device at all, since virtio is a
|
||||
* paravirtualization standard, not something a physical storage controller
|
||||
* speaks) and starts being discovered generically instead, the same way
|
||||
* WIREBIND already discovers identity thumbdrives -- by content signature,
|
||||
* not by which bus happened to present the device. Without a distinct
|
||||
* signature, generic discovery on real hardware (where an identity
|
||||
* thumbdrive and Artemis's own disk could both be attached as USB-MSC
|
||||
* devices simultaneously) would have no way to tell them apart.
|
||||
*
|
||||
* Mirrors homeblocks_sig_t's own structural convention (magic + version +
|
||||
* CRC, one 4KiB header) -- a sibling format, not a field bolted onto
|
||||
* homeblocks_sig_t itself: homeblocks_sig_t's own header comment already
|
||||
* states it's "deliberately narrow in scope" (identity-drive fields only,
|
||||
* no spare room), and Artemis's disk is conceptually a different kind of
|
||||
* thing (one dedicated fleet-owned device, not one of many candidate
|
||||
* identity drives), not a variant of the same one.
|
||||
*
|
||||
* CORRECTION, same day: the first version of this format placed the header
|
||||
* at a fixed bottom-of-device forth-block (4, i.e. devblock 1), copying
|
||||
* homeblocks_sig_t's own devblock-1 convention. That convention is safe for
|
||||
* an identity thumbdrive (raw, dedicated storage -- capsule_mint.c writes
|
||||
* directly, no block-subsystem format involved), but Artemis's disk is
|
||||
* block_subsystem.c's own STFR/v2-formatted volume: devblock 0 holds that
|
||||
* format's header (read_header_4k()/write_header_4k(), block_subsystem.c)
|
||||
* and devblock 1 is the FIRST DEVBLOCK OF THE LIVE BAM
|
||||
* (blk_compute_fresh_geometry(): bam_start = 1). Writing artemis_sig_t
|
||||
* there would have overwritten Artemis's own live allocation map on the
|
||||
* very first real boot this ran against -- caught (via `git status`/`grep`
|
||||
* cross-reference against block_subsystem.c, not against a live disk)
|
||||
* before the genesis-stamp call site ever executed against the real image.
|
||||
*
|
||||
* The header now lives at a fixed offset from the END of the device
|
||||
* instead (ARTEMIS_SIG_DEVBLOCK_FROM_TOP, see below) -- the same
|
||||
* "top-of-device, outside the user-addressable LBN pool" region
|
||||
* block_subsystem.c's own meta_fence_blocks reservation (128 devblocks by
|
||||
* default, BLK_META_FENCE_INIT) already carves out for exactly this kind
|
||||
* of system metadata, and where Zuse's own genesis marker/eligibility list
|
||||
* already live (blk_meta_zone_read()/write(), devblock_from_top 0 and 1+
|
||||
* respectively). This format deliberately does NOT go through
|
||||
* blk_meta_zone_*, though: that accessor requires the device to already be
|
||||
* blk_subsys_attach_device()'d and format-detected (first_disk_slot()) --
|
||||
* exactly the state generic pre-attach discovery (repl.c's idle-loop
|
||||
* USB-MSC scan) doesn't have yet, which is the entire reason this format
|
||||
* exists. artemis_sig_check()/artemis_sig_genesis_stamp() instead compute
|
||||
* the same top-of-device arithmetic independently via blkio_info(), so
|
||||
* they work on a raw, not-yet-attached device exactly like
|
||||
* homeblocks_sig_check() already does. ARTEMIS_SIG_DEVBLOCK_FROM_TOP is
|
||||
* fixed well clear of Zuse's two tenants (0 and 1+, open-ended but
|
||||
* realistically small -- 127 pubkeys per chained devblock) so the two
|
||||
* subsystems' independent top-of-device math can never collide, without
|
||||
* this format needing to know how far the eligibility chain has actually
|
||||
* grown on any given boot.
|
||||
*
|
||||
* Reserves offset/size pointers to the growable per-VM log-persistence
|
||||
* region (FABRIC-3.md §XXVI follow-on's own log-record work), the same way
|
||||
* homeblocks_sig_t reserves pointers to where the cert and identity source
|
||||
* attach -- this format doesn't need revisiting when that region's own
|
||||
* internal layout is designed.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_ARTEMIS_SIG_H
|
||||
#define STARKERNEL_ARTEMIS_SIG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*===========================================================================
|
||||
* Magic Field Packing -- same bit layout convention as HOMEBLOCKS_SIG_PACK
|
||||
*
|
||||
* bits 0..31 : 'ARTM' (0x4D545241 little-endian) -- distinct from
|
||||
* homeblocks_sig_t's 'LAHB', so a generic scan can tell an
|
||||
* Artemis disk apart from an identity thumbdrive by content
|
||||
* alone, regardless of which bus either was found on.
|
||||
* bits 32..39 : version (0 for v0)
|
||||
* bits 40..63 : reserved (zero)
|
||||
*===========================================================================*/
|
||||
|
||||
#define ARTEMIS_SIG_MAGIC 0x4D545241ULL /* 'ARTM' */
|
||||
#define ARTEMIS_SIG_VERSION_0 0
|
||||
|
||||
#define ARTEMIS_SIG_PACK(ver) \
|
||||
(ARTEMIS_SIG_MAGIC | ((uint64_t)(ver) << 32))
|
||||
|
||||
#define ARTEMIS_SIG_GET_MAGIC(m) ((uint32_t)((m) & 0xFFFFFFFFULL))
|
||||
#define ARTEMIS_SIG_GET_VERSION(m) ((uint8_t)(((m) >> 32) & 0xFF))
|
||||
|
||||
/* Fence-relative top-of-device index -- see this header's own CORRECTION
|
||||
* comment above for why this replaced a fixed bottom-of-device forth-block.
|
||||
* Same "distance from the very last physical devblock" convention
|
||||
* block_subsystem.c's blk_meta_zone_read()/write() use internally (0 =
|
||||
* last devblock, 1 = second-to-last, ...), computed independently here via
|
||||
* blkio_info() rather than through that accessor (which needs an already-
|
||||
* attached, format-detected slot this code runs before). Fixed well past
|
||||
* Zuse's genesis marker (devblock_from_top 0) and eligibility list
|
||||
* (devblock_from_top 1, chained upward as needed) -- see
|
||||
* zuse_eligibility_list.h -- so the two subsystems' independent top-of-
|
||||
* device math can never collide regardless of how large the eligibility
|
||||
* chain grows in practice. Well inside BLK_META_FENCE_INIT (128 devblocks,
|
||||
* block_subsystem.h) on any real Artemis disk. */
|
||||
#define ARTEMIS_SIG_DEVBLOCK_FROM_TOP 64u
|
||||
|
||||
/*===========================================================================
|
||||
* artemis_sig_t - Artemis disk signature header (exactly one 4KiB devblock)
|
||||
*===========================================================================*/
|
||||
|
||||
typedef struct {
|
||||
uint64_t magic; /* ARTEMIS_SIG_PACK(...) */
|
||||
uint8_t disk_uuid[16]; /* Mirrors homeblocks_sig_t's drive_uuid --
|
||||
* one Artemis disk exists today, but costs
|
||||
* nothing to future-proof the same way. */
|
||||
uint64_t genesis_time_ns; /* Monotonic timestamp when this signature
|
||||
* was first stamped (the one-time genesis
|
||||
* step, not every boot). */
|
||||
uint64_t metadata_devblocks; /* Size of the metadata region at the start
|
||||
* of this raw device (sig header + log
|
||||
* region), in 4KiB devblocks -- everything
|
||||
* past this is Artemis's own general
|
||||
* block-storage pool, same "no partition
|
||||
* boundary" convention homeblocks_sig_t
|
||||
* uses for an identity's own pool. */
|
||||
|
||||
uint32_t log_region_offset; /* CORRECTION, Step 4 (log_region.h,
|
||||
* 2026-09-13): stays 0 -- informational
|
||||
* field only, never written or read by
|
||||
* the real implementation. The log
|
||||
* region ended up at a fixed, compile-
|
||||
* time devblock_from_top constant
|
||||
* (LOG_REGION_DEVBLOCK_FROM_TOP_BASE,
|
||||
* log_region.h) reached through
|
||||
* blk_meta_zone_*(), whose own control
|
||||
* header (log_region_ctrl_t) is the one
|
||||
* authoritative source of the region's
|
||||
* live offset/size/head/tail -- a second
|
||||
* writer of the same fact here would be
|
||||
* unnecessary drift risk, not a useful
|
||||
* summary. Left at 0/reserved rather
|
||||
* than deleted, in case a real second
|
||||
* reader (a host-side offline tool that
|
||||
* can't run blk_meta_zone_*() at all)
|
||||
* ever needs it. */
|
||||
uint32_t log_region_devblocks; /* See log_region_offset above -- same
|
||||
* reasoning, stays 0. */
|
||||
|
||||
uint64_t hdr_crc; /* Computed over every field above this
|
||||
* one, same boundary/discipline as
|
||||
* homeblocks_sig_compute_crc(). */
|
||||
|
||||
/* Padding to keep the header exactly one 4KiB devblock. */
|
||||
uint8_t _pad[4096 - (
|
||||
8 + /* magic */
|
||||
16 + /* disk_uuid */
|
||||
8 + /* genesis_time_ns */
|
||||
8 + /* metadata_devblocks */
|
||||
4 + 4 + /* log_region_offset, log_region_devblocks */
|
||||
8 /* hdr_crc */
|
||||
)];
|
||||
} artemis_sig_t;
|
||||
|
||||
/* C99-portable compile-time size assertion (no _Static_assert -- that's
|
||||
* C11), same discipline homeblocks_sig.h's own check uses. */
|
||||
typedef char artemis_sig_size_check[(sizeof(artemis_sig_t) == 4096) ? 1 : -1];
|
||||
|
||||
/*===========================================================================
|
||||
* Signature check (mirrors homeblocks_sig_result_t exactly)
|
||||
*===========================================================================*/
|
||||
|
||||
typedef enum {
|
||||
ARTEMIS_SIG_OK = 0, /* magic, version, and crc all check out */
|
||||
ARTEMIS_SIG_BLANK, /* magic does not match -- blank, foreign, or
|
||||
* an identity thumbdrive (different magic) */
|
||||
ARTEMIS_SIG_BAD_VERSION, /* magic matches, version unrecognized */
|
||||
ARTEMIS_SIG_BAD_CRC, /* magic+version match, crc fails -- corrupt
|
||||
* or tampered */
|
||||
ARTEMIS_SIG_READ_ERROR /* could not read from the device at all */
|
||||
} artemis_sig_result_t;
|
||||
|
||||
/* Forward-declared, not included here -- same reasoning as
|
||||
* homeblocks_sig.h's own forward declaration of struct blkio_dev. */
|
||||
struct blkio_dev;
|
||||
|
||||
/*
|
||||
* artemis_sig_check - Read and verify the Artemis disk signature header, at
|
||||
* the fixed ARTEMIS_SIG_DEVBLOCK_FROM_TOP offset from whatever `dev`
|
||||
* reports as its own total size (blkio_info()) -- no attach or format
|
||||
* detection required, same "works on a raw, not-yet-attached device"
|
||||
* contract homeblocks_sig_check() already has.
|
||||
*
|
||||
* @param dev Open block device to read from.
|
||||
* @param out_sig On ARTEMIS_SIG_OK, populated with the verified
|
||||
* header. Left unspecified on any other result.
|
||||
* @return ARTEMIS_SIG_OK, or the specific reason for refusal.
|
||||
*/
|
||||
artemis_sig_result_t artemis_sig_check(struct blkio_dev *dev,
|
||||
artemis_sig_t *out_sig);
|
||||
|
||||
/*
|
||||
* artemis_sig_compute_crc - CRC-64 over every field of `sig` up to but not
|
||||
* including hdr_crc itself and the trailing padding. Exposed publicly for
|
||||
* the same reason homeblocks_sig_compute_crc() is: both the check and the
|
||||
* future genesis-stamping step need the identical computation.
|
||||
*
|
||||
* @param sig Header to checksum. hdr_crc and _pad are not read.
|
||||
* @return The CRC-64 value that hdr_crc should hold for `sig` to verify.
|
||||
*/
|
||||
uint64_t artemis_sig_compute_crc(const artemis_sig_t *sig);
|
||||
|
||||
/*
|
||||
* artemis_sig_genesis_stamp - One-time write of a fresh artemis_sig_t onto
|
||||
* a disk already confirmed to be Artemis's own (never called speculatively
|
||||
* on an unidentified/blank device -- see the call site in kernel_main.c for
|
||||
* why that's always safe there: virtio_blk_find_artemis() only ever
|
||||
* succeeds against the one dedicated PCI device, so a BLANK read at this
|
||||
* fblock unambiguously means "this disk has never been stamped," not
|
||||
* "this might be some other blank drive"). log_region_offset/devblocks are
|
||||
* written as 0 (not yet allocated) -- step 4's own log-persistence design
|
||||
* allocates them later via a normal artemis_sig_t rewrite, same one-header
|
||||
* location.
|
||||
*
|
||||
* disk_uuid is drawn from rng_get_bytes(), same entropy source
|
||||
* capsule_mint.c already uses for an identity thumbdrive's drive_uuid.
|
||||
* genesis_time_ns is written as 0 -- no monotonic-ns source exists
|
||||
* anywhere in this codebase yet, same open item homeblocks_sig_t's own
|
||||
* minted_time_ns field already carries.
|
||||
*
|
||||
* Idempotent by construction: a caller must check artemis_sig_check()
|
||||
* returns ARTEMIS_SIG_BLANK first (this function does not re-check, to
|
||||
* avoid a second redundant read the caller already just performed).
|
||||
*
|
||||
* @param dev Open block device to write to. Must already be confirmed as
|
||||
* Artemis's own disk.
|
||||
* @return 0 on success (including read-back verification), -1 on any
|
||||
* entropy, write, or verify failure -- the disk is left however
|
||||
* the failed write left it, same as capsule_mint.c's own
|
||||
* write-then-verify discipline.
|
||||
*/
|
||||
int artemis_sig_genesis_stamp(struct blkio_dev *dev);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_ARTEMIS_SIG_H */
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* blkio_usb.h — USB Mass Storage (Bulk-Only Transport) blkio_dev backend
|
||||
* for StarKernel, Milestone 2h. Presents a blkio_dev_t interface for
|
||||
* attachment to the StarForth block subsystem via blk_subsys_attach_device(),
|
||||
* matching virtio_blk.h's own precedent — built on top of the xHCI BOT
|
||||
* driver's synchronous bridge (xhci_bot_wait_for_idle(), xhci_bot_get_capacity(),
|
||||
* xhci_bot_read_block()) from Milestone 2h's foundational increment.
|
||||
*
|
||||
* Read-write since 2026-08-28 (FABRIC-2.md §F.1): SCSI WRITE(10) is real
|
||||
* (xhci_bot_send_write10()/xhci_bot_write_block()/xhci_bot_write_data_out(),
|
||||
* xhci.c), mirroring READ(10)'s existing CBW/data-stage/CSW machinery with
|
||||
* the data direction flipped. Verified live on amd64: BLK-CONFIRM-FORMAT's
|
||||
* BAM/reloc zero-page writes and an explicit block content write both
|
||||
* survived a cold reboot and read back correctly.
|
||||
*
|
||||
* FABRIC-3.md §VII (2026-09-05): multiple simultaneously-attached USB MSC
|
||||
* devices are now supported -- each open() call is backed by its own
|
||||
* per-slot state (blkio_usb.c's own registry, keyed by xHCI slot ID,
|
||||
* mirroring xhci_dev_t's msc_slots[]). The underlying xHCI/BOT command
|
||||
* machinery still runs one bulk transfer at a time across the whole
|
||||
* controller (real xHCI semantics, not a limitation introduced here) --
|
||||
* see xhci_msc_slot_t's own doc comment for the persistent-vs-in-flight
|
||||
* distinction this rests on.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_BLKIO_USB_H
|
||||
#define STARKERNEL_BLKIO_USB_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "blkio.h"
|
||||
#include "starkernel/xhci_driver.h"
|
||||
|
||||
/*
|
||||
* blkio_usb_open_msc — synchronously query a confirmed Mass Storage/BOT
|
||||
* device's capacity (SCSI READ CAPACITY(10), via
|
||||
* xhci_bot_get_capacity() + xhci_bot_wait_for_idle())
|
||||
* and fill in *dev_out so the caller can pass it to
|
||||
* blk_subsys_attach_device().
|
||||
*
|
||||
* xdev/slot_id identify an already-enumerated, already-configured Mass
|
||||
* Storage/BOT device (SET_CONFIGURATION already succeeded) — this function
|
||||
* does not enumerate or configure anything itself.
|
||||
*
|
||||
* MUST be called from outside xhci_poll_events()'s own call frame, same
|
||||
* constraint as xhci_bot_wait_for_idle() itself (see its own doc comment
|
||||
* in xhci_driver.h) — this function calls it directly.
|
||||
*
|
||||
* dev_out must point to a zero-initialised blkio_dev_t.
|
||||
*
|
||||
* Returns 0 on success.
|
||||
* Returns -1 if xdev/slot_id are invalid, or the capacity query didn't PASS.
|
||||
* Returns -2 if the device's reported SCSI block size doesn't evenly divide
|
||||
* BLKIO_FORTH_BLOCK_SIZE (1024) — this backend has no way to
|
||||
* serve a partial Forth block, so it refuses rather than
|
||||
* silently misbehaving.
|
||||
*/
|
||||
int blkio_usb_open_msc(blkio_dev_t *dev_out, xhci_dev_t *xdev, uint32_t slot_id);
|
||||
|
||||
#endif /* STARKERNEL_BLKIO_USB_H */
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* boot_info_offsets.h — fixed byte offsets of BootInfo fields
|
||||
*
|
||||
* Read by kernel_entry.S (assembly) AND verified by _Static_assert in
|
||||
* uefi_loader.c. Both uses share this header; only #define directives
|
||||
* are used so the file is valid for the C preprocessor AND the GAS
|
||||
* preprocessor (which runs on .S files compiled via CC).
|
||||
*
|
||||
* If you change the BootInfo struct layout in uefi.h, update the
|
||||
* constants below AND re-check the _Static_asserts (search BOOT_INFO_OFFSETS).
|
||||
*
|
||||
* BootInfo layout (64-bit, natural alignment):
|
||||
* 0: memory_map ptr (8)
|
||||
* 8: memory_map_size u64 (8)
|
||||
* 16: memory_map_descriptor_size u64 (8)
|
||||
* 24: runtime_services ptr (8)
|
||||
* 32: acpi_table ptr (8)
|
||||
* 40: dtb ptr (8)
|
||||
* 48: framebuffer FramebufferInfo (32)
|
||||
* .base ptr (8)
|
||||
* .size u64 (8)
|
||||
* .width u32 (4)
|
||||
* .height u32 (4)
|
||||
* .pixels_per_scanline u32 (4)
|
||||
* .pixel_format u32 (4)
|
||||
* 80: uefi_boot_services_exited u8 (1)
|
||||
* 81: [7 bytes padding]
|
||||
* 88: kernel_stack_base ptr (8) ← BOOT_INFO_KERNEL_STACK_BASE_OFFSET
|
||||
* 96: kernel_stack_size u64 (8) ← BOOT_INFO_KERNEL_STACK_SIZE_OFFSET
|
||||
* 104: args KernelArgs
|
||||
*
|
||||
* 2026-08-03, punch-list item 0.3: `dtb` inserted at 40, shifting everything
|
||||
* below it by 8. The _Static_asserts in uefi_loader.c caught the stale
|
||||
* constants immediately — that is what they are for; do not silence them by
|
||||
* moving a field, fix the offsets.
|
||||
*/
|
||||
|
||||
#define BOOT_INFO_KERNEL_STACK_BASE_OFFSET 88
|
||||
#define BOOT_INFO_KERNEL_STACK_SIZE_OFFSET 96
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* capsule_console.h - Bare console-VM birth (FABRIC-2.md Phase F,
|
||||
* 2026-08-28): a minimal VM whose only job is loading
|
||||
* common:messaging.4th and being the physical REPL's relay target
|
||||
* (sk_repl_dispatch_line(), repl.c) for a paired user VM
|
||||
* (capsule_runcap_birth(), capsule_runcap.h). Not identity-bearing
|
||||
* content -- no thumbdrive read, fixed embedded source, same
|
||||
* heap-built-single-entry-directory shape RUNCAP already established
|
||||
* (§F.6/§F.18), just with compile-time content instead of a devblock
|
||||
* read.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_CONSOLE_H
|
||||
#define STARKERNEL_CAPSULE_CONSOLE_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include "starkernel/capsule_run.h"
|
||||
#include "starkernel/vm_uuid.h"
|
||||
|
||||
/**
|
||||
* capsule_console_birth - Birth a bare console VM.
|
||||
*
|
||||
* @param console_name Symbolic name for the new VM -- by convention
|
||||
* the same name a user's own identity uses (e.g.
|
||||
* "CaptBob"); sk_repl_dispatch_line() looks for a
|
||||
* live "<name>~user" counterpart to decide
|
||||
* whether a given active VM is a console.
|
||||
* @param parent Who is birthing this VM (FABRIC-2.md §H.12 step 7)
|
||||
* -- passed straight through to capsule_birth_baby().
|
||||
* @param out_vm_id Output: assigned VM ID.
|
||||
* @param out_vm_ctx Output: new VM context (may be NULL).
|
||||
* @return CAPSULE_RUN_OK on success, error code otherwise.
|
||||
*/
|
||||
CapsuleRunResult capsule_console_birth(const char *console_name, VMUuid parent,
|
||||
VMUuid *out_vm_id, void **out_vm_ctx);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_CONSOLE_H */
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* capsule_generated.h - Declarations for mkcapsule-generated capsule store.
|
||||
*
|
||||
* The matching capsule_generated.c is produced at build time by mkcapsule.
|
||||
* These three arrays plus the directory header are compiled into .rodata.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_GENERATED_H
|
||||
#define STARKERNEL_CAPSULE_GENERATED_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "starkernel/capsule.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Mark as hidden so GCC uses direct RIP-relative addressing in PIC/PE builds,
|
||||
* avoiding GOT references which do not exist in PE/COFF binaries. */
|
||||
__attribute__((visibility("hidden")))
|
||||
extern const uint8_t capsule_arena[];
|
||||
__attribute__((visibility("hidden")))
|
||||
extern const CapsuleDesc capsule_descriptors[];
|
||||
__attribute__((visibility("hidden")))
|
||||
extern const CapsuleNameEntry capsule_names[];
|
||||
__attribute__((visibility("hidden")))
|
||||
extern const CapsuleSigEntry capsule_signatures[];
|
||||
__attribute__((visibility("hidden")))
|
||||
extern const CapsuleDirHeader capsule_directory;
|
||||
|
||||
/*
|
||||
* Accessor functions — defined in the same TU as the symbols above.
|
||||
* PE/COFF builds with -fPIC do not convert GOTPCREL data references to
|
||||
* direct LEA the way the ELF linker does, so any cross-TU access to a
|
||||
* data symbol goes through GOT and reads garbage in a PE image.
|
||||
* These hidden accessor functions are called via a direct CALL (no PLT/GOT)
|
||||
* and access the symbols with direct RIP-relative addressing inside their TU.
|
||||
*/
|
||||
__attribute__((visibility("hidden")))
|
||||
uint32_t capsule_get_desc_count(void);
|
||||
__attribute__((visibility("hidden")))
|
||||
const CapsuleDirHeader *capsule_get_directory(void);
|
||||
__attribute__((visibility("hidden")))
|
||||
const CapsuleDesc *capsule_get_descriptors(void);
|
||||
__attribute__((visibility("hidden")))
|
||||
const CapsuleNameEntry *capsule_get_names(void);
|
||||
__attribute__((visibility("hidden")))
|
||||
const CapsuleSigEntry *capsule_get_signatures(void);
|
||||
__attribute__((visibility("hidden")))
|
||||
const uint8_t *capsule_get_arena(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_GENERATED_H */
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* capsule_loader.h - Block Capsule Loader and Executor (M7.1)
|
||||
*
|
||||
* Parses a capsule payload for "Block <num>" headers and writes each
|
||||
* block's content into the correct ramdrive slot, then executes the
|
||||
* entry block and zeros the ramdrive slots afterward.
|
||||
*
|
||||
* Format:
|
||||
* Block <decimal>\n
|
||||
* <content: up to 1024 bytes of FORTH source>
|
||||
* Block <decimal>\n
|
||||
* ...
|
||||
*
|
||||
* Blocks may appear in any order; block numbers are arbitrary.
|
||||
* Content exceeding 1024 bytes is truncated with a warning.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_LOADER_H
|
||||
#define STARKERNEL_CAPSULE_LOADER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "starkernel/capsule.h"
|
||||
#include "starkernel/capsule_run.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* capsule_load_blocks - Parse a capsule payload and write blocks to the ramdrive
|
||||
*
|
||||
* For each "Block <num>" header found in the payload:
|
||||
* 1. Zero the 1024-byte ramdrive slot for block <num>
|
||||
* 2. Copy content bytes into the slot (excess beyond 1024 silently dropped)
|
||||
* 3. Mark the slot dirty via blk_update()
|
||||
*
|
||||
* Content between headers may be text or binary.
|
||||
* Anything before the first "Block <num>" header is ignored.
|
||||
*
|
||||
* @param payload Raw capsule payload bytes (not null-terminated)
|
||||
* @param length Payload length in bytes
|
||||
* @param out_entry_block If non-NULL, receives the first block number found
|
||||
* (the execution entry point); set to 0 if no blocks found
|
||||
* @return Number of blocks written, or -1 on invalid input
|
||||
*/
|
||||
int capsule_load_blocks(const uint8_t *payload, uint64_t length,
|
||||
uint32_t *out_entry_block);
|
||||
|
||||
/**
|
||||
* capsule_clear_blocks - Zero all ramdrive slots referenced in a capsule payload
|
||||
*
|
||||
* Re-parses the payload for "Block <num>" headers and zeros each
|
||||
* corresponding 1024-byte ramdrive slot. NOT called by capsule_exec_init()
|
||||
* (fixed 2026-09-10 -- see its own doc comment): callers that specifically
|
||||
* want a capsule's block range freed for reuse (e.g. kernel_main.c, right
|
||||
* after Mama's own init.4th birth, to free that range for interactive
|
||||
* block-editor use) call this themselves, explicitly, after exec returns.
|
||||
*
|
||||
* @param payload Raw capsule payload bytes
|
||||
* @param length Payload length in bytes
|
||||
*/
|
||||
void capsule_clear_blocks(const uint8_t *payload, uint64_t length);
|
||||
|
||||
/**
|
||||
* capsule_exec_payload - Parse, populate block device, and execute a payload
|
||||
*
|
||||
* For each "Block <num>" section: write content to block device (warn+truncate
|
||||
* if > 1KB), then execute line-by-line via vm_interpret. No LOAD is injected.
|
||||
*
|
||||
* @param vm_opaque VM to execute on
|
||||
* @param payload Raw payload bytes
|
||||
* @param length Payload length in bytes
|
||||
* @return 0 on success, -1 on execution error
|
||||
*/
|
||||
int capsule_exec_payload(void *vm_opaque, const uint8_t *payload, uint64_t length);
|
||||
|
||||
/**
|
||||
* capsule_exec_init - Load and execute an init capsule
|
||||
*
|
||||
* Full init sequence:
|
||||
* 1. Locate capsule by colon-separated name in the capsule directory
|
||||
* 2. Validate content hash
|
||||
* 3. capsule_exec_payload: populate block device + execute content
|
||||
*
|
||||
* Block content is NOT cleared afterward (fixed 2026-09-10): it stays
|
||||
* resident in ramdrive storage so a subsequent Standard BLOCK/LOAD on the
|
||||
* same block number reads back what EXEC just wrote, matching FORTH-79
|
||||
* block-persistence semantics. Callers that specifically want the old
|
||||
* "free this capsule's block range" behavior (e.g. kernel_main.c freeing
|
||||
* init.4th's range for later interactive block-editor use) call
|
||||
* capsule_clear_blocks() themselves, explicitly, after this returns.
|
||||
*
|
||||
* @param vm VM to execute on
|
||||
* @param capsule_name Colon-separated capsule name, e.g. "init.4th"
|
||||
* @param dir Capsule directory header
|
||||
* @param descs Capsule descriptor array
|
||||
* @param names Capsule name entry array (parallel to descs)
|
||||
* @param arena Capsule payload arena
|
||||
* @return CAPSULE_RUN_OK on success, error code otherwise
|
||||
*/
|
||||
CapsuleRunResult capsule_exec_init(
|
||||
void *vm,
|
||||
const char *capsule_name,
|
||||
const CapsuleDirHeader *dir,
|
||||
const CapsuleDesc *descs,
|
||||
const CapsuleNameEntry *names,
|
||||
const uint8_t *arena
|
||||
);
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
/**
|
||||
* capsule_blk_init - Initialize block subsystem and attach the kernel ramdrive.
|
||||
*
|
||||
* Establishes the unified block address space:
|
||||
* LBN 0..2047: fast RAM (ram_buf)
|
||||
* LBN 2048..2048+1023: ramdrive (krd_buf, volatile, 1024 blocks)
|
||||
*
|
||||
* @param vm Opaque VM pointer (mama VM)
|
||||
* @param ram_buf Buffer for fast RAM blocks (must be >= BLK_RAM_BLOCKS * 1024 bytes)
|
||||
* @param ram_size Size of ram_buf in bytes
|
||||
* @param krd_buf Pre-zeroed buffer for ramdrive (must be >= 1024 * 1024 bytes)
|
||||
* @return 0 on success, non-zero on failure
|
||||
*/
|
||||
int capsule_blk_init(void *vm, uint8_t *ram_buf, size_t ram_size, uint8_t *krd_buf);
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_LOADER_H */
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* capsule_mint.h - MINT: mint a fresh identity onto a blank thumbdrive
|
||||
* (FABRIC-2.md §F.8/§F.19), the last piece of the original Tripod
|
||||
* vision. Writes a real keypair, a Zuse-signed cert, and a minimal
|
||||
* working default personality -- everything RUNCAP (capsule_runcap.h)
|
||||
* and CERTVERIFY need at a later attach.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_MINT_H
|
||||
#define STARKERNEL_CAPSULE_MINT_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "vm.h"
|
||||
|
||||
struct blkio_dev;
|
||||
|
||||
typedef enum {
|
||||
MINT_OK = 0,
|
||||
MINT_ERR_ALREADY_MINTED, /* dev already reads as a recognized home-blocks
|
||||
* drive -- refuses rather than overwrite,
|
||||
* mirroring WRITE(10)'s own blank-media
|
||||
* posture (§F.8, decided 2026-08-28). */
|
||||
MINT_ERR_NO_ZUSE_CERT, /* issuer_vm->zuse_cert_installed is 0 -- no
|
||||
* key to sign the new cert with. */
|
||||
MINT_ERR_NO_ENTROPY, /* virtio_rng not ready. */
|
||||
MINT_ERR_CERT_BUILD, /* x509_build_user_cert() failed (shouldn't
|
||||
* happen with fixed-size fields, but not
|
||||
* assumed away). */
|
||||
MINT_ERR_WRITE_FAIL, /* a devblock write failed partway through --
|
||||
* the drive may be left partially minted. */
|
||||
MINT_ERR_INVALID_PROFILE, /* full_name/username missing or too long for
|
||||
* user_identity_seed_t's fixed fields, or
|
||||
* email/phone too long (both may be NULL/empty
|
||||
* -- that's "null", not invalid). */
|
||||
MINT_ERR_VERIFY_FAILED, /* every devblock write reported success, but a
|
||||
* post-write read-back (2026-09-06) found the
|
||||
* drive doesn't actually read back as a valid,
|
||||
* complete home-blocks identity -- caught
|
||||
* live: a device that enumerates and accepts
|
||||
* writes can still fail to read back correctly
|
||||
* under real hardware/emulation conditions
|
||||
* (e.g. concurrent multi-device USB load), and
|
||||
* blkio_write() returning BLK_OK is not by
|
||||
* itself proof the bytes landed. The drive may
|
||||
* be left partially or incorrectly minted --
|
||||
* treat identically to MINT_ERR_WRITE_FAIL for
|
||||
* retry purposes. */
|
||||
} MintResult;
|
||||
|
||||
/**
|
||||
* MintPersonality - which personality-source template gets written to the
|
||||
* new identity's devblock (identity_src_offset+1). Purely a template
|
||||
* *selection* -- the actual restriction logic (the FORTH-79/83 allowlist,
|
||||
* the walk-and-deny loop) lives entirely in capsules/acl-std79.4th, per
|
||||
* the standing rule that ACL policy belongs in FORTH, never in C. This
|
||||
* enum just picks which few-line bootstrap stub gets written; that stub
|
||||
* is the only thing capsule_mint.c itself owns.
|
||||
*/
|
||||
typedef enum {
|
||||
MINT_PERSONALITY_DEFAULT = 0, /* unrestricted -- today's only behavior until this enum existed */
|
||||
MINT_PERSONALITY_STD79_LOCKDOWN = 1 /* EXECs acl-std79.4th then ACL-LOCKDOWN-STD79 as its last steps */
|
||||
} MintPersonality;
|
||||
|
||||
/**
|
||||
* capsule_mint_identity - Mint a fresh identity onto dev.
|
||||
*
|
||||
* Layout written (devblock offsets from dev's own start; devblock 0 is
|
||||
* left alone, reserved for the block-subsystem's own generic header):
|
||||
* devblock 1 homeblocks_sig_t (HOMEBLOCKS_SIG_START_FBLOCK)
|
||||
* devblock 2 DER cert, Zuse-signed (cert_offset)
|
||||
* devblock 3 user_identity_seed_t (identity_src_offset)
|
||||
* devblock 4 personality source (selected by `personality`)
|
||||
* (identity_src_offset+1)
|
||||
*
|
||||
* @param dev Already-open block device for the target drive.
|
||||
* @param issuer_vm The signing identity -- in practice always Hera's own
|
||||
* VM (Zuse's cert lives there, vm.h's zuse_cert_seed).
|
||||
* NULL means genesis mode (§F.21): no cert is built or
|
||||
* written (cert_offset/cert_devblocks stay 0) and
|
||||
* issuer_vm->zuse_cert_installed is never checked --
|
||||
* used exactly once, to mint Zuse's own root identity,
|
||||
* which by definition has no existing Zuse to sign it.
|
||||
* @param full_name Required, NUL-terminated, fits user_identity_seed_t's
|
||||
* full_name field (§F.20).
|
||||
* @param username Required, NUL-terminated, fits its username field.
|
||||
* @param email NULL or empty string = null (field stays empty).
|
||||
* @param phone NULL or empty string = null (field stays empty).
|
||||
* @param out_pubkey Optional (may be NULL): filled with the newly
|
||||
* generated identity's own Ed25519 public key on
|
||||
* success. Genesis mode's only caller needs this, to
|
||||
* write it into the system-resident zuse_genesis_
|
||||
* marker_t.
|
||||
* @param out_seed Optional (may be NULL): filled with the newly
|
||||
* generated identity's own Ed25519 seed on success.
|
||||
* Genesis mode's only caller needs this too, to
|
||||
* install the cert into Hera's own VM immediately
|
||||
* (vm_zuse_cert_install()) -- the seed otherwise only
|
||||
* ever lives on the minted thumbdrive.
|
||||
* @param personality Which personality-source template to write -- see
|
||||
* MintPersonality's own doc comment above.
|
||||
* @param drive_known_blank Pass 1 when the caller has *already* just run
|
||||
* homeblocks_sig_check() on dev and confirmed
|
||||
* HOMEBLOCKS_SIG_BLANK (e.g. capsule_zuse_boot_try_
|
||||
* attach(), which must check sig_rc before it can even
|
||||
* decide to call this) -- skips this function's own
|
||||
* internal "refuse to overwrite" re-check, which
|
||||
* otherwise repeats the exact same full BOT read
|
||||
* sequence a second time for no reason (found live,
|
||||
* FABRIC-2.md §F.25/§F.26: the redundant check was
|
||||
* mistaken for a hang before the real cause -- leaked
|
||||
* `tail -f` processes from repeated hard kills during
|
||||
* the same debugging session -- was found). Pass 0 from
|
||||
* any caller (like MINT, mama_forth_words.c) that has
|
||||
* not already checked -- the safety check still applies
|
||||
* there.
|
||||
* @return MINT_OK on success, an error code otherwise.
|
||||
*/
|
||||
MintResult capsule_mint_identity(struct blkio_dev *dev, VM *issuer_vm,
|
||||
const char *full_name, const char *username,
|
||||
const char *email, const char *phone,
|
||||
uint8_t out_pubkey[32], uint8_t out_seed[32],
|
||||
MintPersonality personality,
|
||||
int drive_known_blank);
|
||||
|
||||
/**
|
||||
* capsule_mint_identity_scratch - Mint into a throwaway RAM-backed device
|
||||
* instead of a real thumbdrive (FABRIC-3.md §XXXII.2, unattended-identity
|
||||
* punch list item 1). Runs capsule_mint_identity() completely unmodified
|
||||
* against a scratch blkio_dev built from the shared RAM backend
|
||||
* (blkio_ram.c) -- same live Zuse-signing operation, same rng_get_bytes()
|
||||
* draw for drive_uuid a real thumbdrive gets. "Scratch" describes only
|
||||
* where the bytes are written; nothing about verification changes, and
|
||||
* vm_identity_from_cert() needs no changes to consume the result later.
|
||||
*
|
||||
* The seed devblock capsule_mint_identity() writes internally is never
|
||||
* read back here -- out_uuid/out_cert are the only two fields an
|
||||
* unattended identity needs (FABRIC-3.md §XXXII.2 decision, 2026-09-16:
|
||||
* no seed is ever baked into a capsule).
|
||||
*
|
||||
* @param issuer_vm Same meaning as capsule_mint_identity()'s own --
|
||||
* Zuse's cert lives here (in practice Hera).
|
||||
* @param out_uuid 16 bytes, populated with the minted drive_uuid.
|
||||
* @param out_cert 4096 bytes, populated with the raw (zero-padded)
|
||||
* cert devblock -- matches capsule_wirebind_verify_
|
||||
* cert()'s own read shape, so the same DER-length
|
||||
* handling (parsed from the ASN.1 header, trailing
|
||||
* padding ignored) applies at birth time later.
|
||||
* @return Same MintResult capsule_mint_identity() itself returns.
|
||||
*/
|
||||
MintResult capsule_mint_identity_scratch(VM *issuer_vm,
|
||||
const char *full_name, const char *username,
|
||||
const char *email, const char *phone,
|
||||
MintPersonality personality,
|
||||
uint8_t out_uuid[16], uint8_t out_cert[4096]);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_MINT_H */
|
||||
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* capsule_run.h - DoE Run Logging (M7.1)
|
||||
*
|
||||
* Structures for logging capsule execution runs and VM births.
|
||||
* Supports provenance tracking and experiment reproducibility.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_RUN_H
|
||||
#define STARKERNEL_CAPSULE_RUN_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "starkernel/vm_uuid.h" /* VMUuid -- FABRIC-0.md item 3.8 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*===========================================================================
|
||||
* Run Log Configuration
|
||||
*===========================================================================*/
|
||||
|
||||
/** Phase A: Fixed ring buffer size */
|
||||
#define CAPSULE_MAX_RUN_RECORDS 1024
|
||||
|
||||
/*===========================================================================
|
||||
* Result Codes
|
||||
*===========================================================================*/
|
||||
|
||||
typedef enum {
|
||||
CAPSULE_RUN_OK = 0,
|
||||
CAPSULE_RUN_ERR_INVALID, /* Invalid capsule */
|
||||
CAPSULE_RUN_ERR_NOT_ELIGIBLE, /* Capsule not eligible for operation */
|
||||
CAPSULE_RUN_ERR_EXEC_FAIL, /* Execution failed */
|
||||
CAPSULE_RUN_ERR_HASH_MISMATCH, /* Post-run hash mismatch */
|
||||
CAPSULE_RUN_ERR_STILLBORN, /* VM birth failed */
|
||||
CAPSULE_RUN_ERR_FLEET_FULL, /* Outer Stadium at stadium_max_vm_count() (FABRIC-0.md item 1.5/2.2) */
|
||||
} CapsuleRunResult;
|
||||
|
||||
/*===========================================================================
|
||||
* CapsuleRunRecord - DoE Execution Log Entry
|
||||
*===========================================================================*/
|
||||
|
||||
typedef struct {
|
||||
uint64_t run_id; /* Sequential run identifier */
|
||||
VMUuid vm_id; /* Which VM executed this (item 3.8) */
|
||||
uint32_t reserved; /* Padding */
|
||||
uint64_t capsule_id; /* Which capsule was run */
|
||||
uint64_t capsule_hash; /* Hash at time of execution */
|
||||
uint64_t pre_dict_hash; /* Dictionary state before run */
|
||||
uint64_t post_dict_hash; /* Dictionary state after run */
|
||||
uint64_t started_ns; /* Monotonic start time */
|
||||
uint64_t ended_ns; /* Monotonic end time */
|
||||
uint32_t result_code; /* CapsuleRunResult */
|
||||
uint32_t flags; /* Run flags (mode, etc.) */
|
||||
} CapsuleRunRecord;
|
||||
|
||||
/*===========================================================================
|
||||
* VM Registry Entry
|
||||
*===========================================================================*/
|
||||
|
||||
/** Maximum length of a VM symbolic name, including null terminator */
|
||||
#define VM_NAME_MAX 64
|
||||
|
||||
typedef enum {
|
||||
VM_STATE_EMBRYO = 0, /* Allocated but not yet born */
|
||||
VM_STATE_LIVE, /* Successfully born, operational */
|
||||
VM_STATE_STOPPED, /* Suspended — execution state saved. Set by
|
||||
* the START word after STOP cleanly unwinds
|
||||
* its C stack back to START's own frame: no
|
||||
* live native frame remains, safe to free. */
|
||||
VM_STATE_STILLBORN, /* Birth failed */
|
||||
VM_STATE_DEAD, /* Terminated */
|
||||
VM_STATE_SWITCHED_OUT, /* FABRIC-3.md §XXVIII, Stage 2 (2026-09-13):
|
||||
* a live saved register/stack context is
|
||||
* parked on this VM's own native stack
|
||||
* (SWITCH-TO switched control away mid-
|
||||
* execution). Deliberately distinct from
|
||||
* VM_STATE_STOPPED -- that state means "no
|
||||
* live native frame," this one means the
|
||||
* opposite. KILL must refuse/defer here: the
|
||||
* parked frame still points into vm->memory
|
||||
* and the native stack, both of which a free
|
||||
* would invalidate out from under it. */
|
||||
} VMState;
|
||||
|
||||
typedef struct {
|
||||
VMUuid vm_id; /* Assigned at birth, immutable (item 3.8) */
|
||||
uint32_t state; /* VMState */
|
||||
uint64_t birth_capsule_id; /* Which capsule birthed this VM */
|
||||
uint64_t birth_timestamp_ns; /* When VM was born */
|
||||
uint64_t birth_dict_hash; /* Dictionary hash after birth */
|
||||
uint32_t flags; /* VM flags */
|
||||
VMUuid parent_vm_id; /* Who birthed this VM. Set once at birth,
|
||||
* never rewritten. Hera's own entry is
|
||||
* self-referential (parent_vm_id == vm_id
|
||||
* == vm_uuid_hera(), all-zero) -- the
|
||||
* sentinel a heat-fanout walk up the
|
||||
* parent chain stops at. */
|
||||
void *vm_ptr; /* Pointer to live VM object; NULL when dead */
|
||||
char name[VM_NAME_MAX]; /* Symbolic name, e.g. "Hera", "Hermes" */
|
||||
size_t stadium_patron_cell; /* FABRIC-2.md SS B, VM-COOL: this VM's own
|
||||
* Stadium cell index (STADIUM_CELL_NONE,
|
||||
* i.e. (size_t)-1, if never admitted or
|
||||
* already reaped) -- admitted into the VM's
|
||||
* own quota at birth, explicitly evicted at
|
||||
* KILL. Not Hera's; she is pinned and never
|
||||
* reaches this field's purpose. */
|
||||
int pending_reap; /* FABRIC-3.md §XXVIII Stage 4 (2026-09-14):
|
||||
* set (capsule_vm_set_pending_reap()) when
|
||||
* this VM is VM_STATE_SWITCHED_OUT and its
|
||||
* owning WIREBIND device has physically
|
||||
* detached -- capsule_vm_kill() correctly
|
||||
* refuses to free a parked context, but the
|
||||
* memory still needs reclaiming once it's
|
||||
* no longer coming back. Deliberately a
|
||||
* plain flag, not a new VMState: `state`
|
||||
* still accurately reads SWITCHED_OUT (a
|
||||
* live context genuinely is parked there)
|
||||
* until the Stage 3 checkpoint
|
||||
* (vm_core.c) notices this flag instead of
|
||||
* attempting to resume it, and calls
|
||||
* capsule_vm_force_reap() there instead --
|
||||
* a safe point the switcher itself
|
||||
* controls, not the async detach handler. */
|
||||
} VMRegistryEntry;
|
||||
|
||||
/*===========================================================================
|
||||
* Run Log Functions
|
||||
*===========================================================================*/
|
||||
|
||||
/**
|
||||
* capsule_run_log_init - Initialize run log
|
||||
*/
|
||||
void capsule_run_log_init(void);
|
||||
|
||||
/**
|
||||
* capsule_run_log_record - Log a run record
|
||||
*
|
||||
* @param record Record to log
|
||||
* @return Run ID assigned, or 0 on failure
|
||||
*/
|
||||
uint64_t capsule_run_log_record(const CapsuleRunRecord *record);
|
||||
|
||||
/**
|
||||
* capsule_run_log_get - Get a run record by ID
|
||||
*
|
||||
* @param run_id Run ID to retrieve
|
||||
* @param out Output record
|
||||
* @return 0 on success, -1 if not found
|
||||
*/
|
||||
int capsule_run_log_get(uint64_t run_id, CapsuleRunRecord *out);
|
||||
|
||||
/**
|
||||
* capsule_run_log_count - Get number of logged runs
|
||||
*/
|
||||
uint32_t capsule_run_log_count(void);
|
||||
|
||||
/*===========================================================================
|
||||
* Parity Logging
|
||||
*===========================================================================*/
|
||||
|
||||
/**
|
||||
* capsule_parity_log_birth - Log VM birth parity record
|
||||
*
|
||||
* Output format:
|
||||
* PARITY:BIRTH vm_id=N capsule_id=X mode=p capsule_hash=H dict_hash=D
|
||||
*/
|
||||
void capsule_parity_log_birth(
|
||||
VMUuid vm_id,
|
||||
uint64_t capsule_id,
|
||||
uint64_t capsule_hash,
|
||||
uint64_t dict_hash
|
||||
);
|
||||
|
||||
/**
|
||||
* capsule_parity_log_birth_failed - Log failed birth
|
||||
*
|
||||
* Output format:
|
||||
* PARITY:BIRTH_FAILED vm_id=N capsule_id=X error=E partial_dict_hash=H
|
||||
*/
|
||||
void capsule_parity_log_birth_failed(
|
||||
VMUuid vm_id,
|
||||
uint64_t capsule_id,
|
||||
CapsuleRunResult error,
|
||||
uint64_t partial_dict_hash
|
||||
);
|
||||
|
||||
/**
|
||||
* capsule_parity_log_run - Log DoE run parity record
|
||||
*
|
||||
* Output format:
|
||||
* PARITY:RUN vm_id=N run_id=R capsule_id=X mode=e pre_dict=P post_dict=Q
|
||||
*/
|
||||
void capsule_parity_log_run(
|
||||
VMUuid vm_id,
|
||||
uint64_t run_id,
|
||||
uint64_t capsule_id,
|
||||
uint64_t pre_dict_hash,
|
||||
uint64_t post_dict_hash
|
||||
);
|
||||
|
||||
/**
|
||||
* capsule_parity_log_mama_init - Log Mama init parity record
|
||||
*
|
||||
* Output format:
|
||||
* PARITY:MAMA_INIT capsule_id=X mode=m capsule_hash=H dict_hash=D
|
||||
*/
|
||||
void capsule_parity_log_mama_init(
|
||||
uint64_t capsule_id,
|
||||
uint64_t capsule_hash,
|
||||
uint64_t dict_hash
|
||||
);
|
||||
|
||||
/**
|
||||
* capsule_parity_log_kill - Log VM kill parity record
|
||||
*
|
||||
* Output format:
|
||||
* PARITY:KILL vm_id=N name=X
|
||||
*/
|
||||
void capsule_parity_log_kill(
|
||||
VMUuid vm_id,
|
||||
const char *name
|
||||
);
|
||||
|
||||
/**
|
||||
* capsule_parity_set_output - Set output hooks for parity logging
|
||||
*
|
||||
* @param putc_fn Function to output a single character
|
||||
* @param puts_fn Function to output a string
|
||||
*/
|
||||
void capsule_parity_set_output(
|
||||
void (*putc_fn)(char),
|
||||
void (*puts_fn)(const char *)
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_RUN_H */
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* capsule_runcap.h - RUNCAP: runtime capsule construction from thumbdrive
|
||||
* content (FABRIC-2.md §F.6/§F.18).
|
||||
*
|
||||
* A user's identity source (raw FORTH init/personality text, minted by
|
||||
* MINT into a home-blocks drive's identity_src region) never exists at
|
||||
* build time, so it can never appear in the compile-time-baked capsule
|
||||
* directory. This builds a heap-only, single-entry CapsuleDirHeader +
|
||||
* CapsuleDesc + CapsuleNameEntry + arena from that region and hands it to
|
||||
* the existing, unmodified capsule_birth_baby() -- no new birth mechanism,
|
||||
* per §F.6's own trace ("capsule_birth_baby() is already generic").
|
||||
*
|
||||
* Does not verify the caller has already run CERTVERIFY -- that's the
|
||||
* caller's responsibility (WIREBIND, not yet built). This function's own
|
||||
* job is narrow: read the region, construct the directory, birth it.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_RUNCAP_H
|
||||
#define STARKERNEL_CAPSULE_RUNCAP_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "starkernel/capsule_run.h" /* CapsuleRunResult */
|
||||
#include "starkernel/vm_uuid.h" /* VMUuid */
|
||||
#include "starkernel/homeblocks_sig.h" /* homeblocks_sig_t */
|
||||
|
||||
struct blkio_dev;
|
||||
|
||||
/**
|
||||
* capsule_runcap_birth - Birth a VM from a home-blocks drive's own
|
||||
* identity_src region.
|
||||
*
|
||||
* Reads sig->identity_src_devblocks devblocks starting at
|
||||
* sig->identity_src_offset. The first devblock is the identity's own
|
||||
* user_identity_seed_t record (MINT, §F.8) and is skipped here -- RUNCAP
|
||||
* only cares about the FORTH source that follows it. Refuses cleanly
|
||||
* (CAPSULE_RUN_ERR_INVALID) if identity_src_offset is 0 (never minted) or
|
||||
* identity_src_devblocks < 2 (no source content beyond the seed record).
|
||||
*
|
||||
* The heap-allocated directory/descriptor/name/arena are never freed --
|
||||
* deliberate, matching kernel_main.c's own compile-time-directory-to-heap
|
||||
* copy at Mama's own birth (also never freed): a VM's IDENTITY exec reads
|
||||
* directly from this arena, and nothing in this codebase frees capsule
|
||||
* arenas after a successful birth today.
|
||||
*
|
||||
* @param dev Already-open block device for the attached drive.
|
||||
* @param sig Already-verified homeblocks_sig_t read from it.
|
||||
* @param vm_name Symbolic name for the new VM (becomes both the
|
||||
* capsule's own single directory entry name and the
|
||||
* VM registry name).
|
||||
* @param parent Who is birthing this VM (FABRIC-2.md §H.12 step 7) --
|
||||
* passed straight through to capsule_birth_baby().
|
||||
* @param out_vm_id Output: assigned VM ID.
|
||||
* @param out_vm_ctx Output: new VM context (may be NULL if not needed).
|
||||
* @return CAPSULE_RUN_OK on success, error code otherwise.
|
||||
*/
|
||||
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
|
||||
);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_RUNCAP_H */
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* capsule_sig.h -- per-capsule Ed25519 signature verification
|
||||
* (Milestone 6, Phase 8). Deliberately kept separate from
|
||||
* capsule_validate.c: that function is already tested and its
|
||||
* signature/behavior stays untouched; this is a new, additive check
|
||||
* called alongside it, not folded into it.
|
||||
*
|
||||
* Enforced ONLY on CAPSULE_SIG_INVALID (2026-08-26, after landing
|
||||
* WARN-only and proving correct on all three architectures against both
|
||||
* a valid and a deliberately-corrupted capsule -- see FABRIC-2.md's
|
||||
* Milestone 6 writeup). CAPSULE_SIG_MISSING and CAPSULE_SIG_NO_ROOT_KEY
|
||||
* stay WARN-only, deliberately: MISSING is the normal state on every
|
||||
* machine without access to the offline signing key (CI, any other
|
||||
* checkout) -- refusing on it would brick boot everywhere but the one
|
||||
* machine that minted the key, not catch anything real. Only INVALID
|
||||
* (a signature that IS present but does not verify) is unambiguous
|
||||
* tampering/corruption evidence, safe to refuse on regardless of who's
|
||||
* building.
|
||||
*/
|
||||
#ifndef STARKERNEL_CAPSULE_SIG_H
|
||||
#define STARKERNEL_CAPSULE_SIG_H
|
||||
|
||||
#include "starkernel/capsule.h"
|
||||
|
||||
typedef enum {
|
||||
CAPSULE_SIG_OK = 0, /* has_sig=1, and it verifies */
|
||||
CAPSULE_SIG_MISSING, /* has_sig=0 -- not signed at all */
|
||||
CAPSULE_SIG_INVALID, /* has_sig=1 but verification failed */
|
||||
CAPSULE_SIG_NO_ROOT_KEY, /* couldn't find/parse the embedded intermediate cert */
|
||||
} CapsuleSigResult;
|
||||
|
||||
/*
|
||||
* Verify capsule descs[index]'s Ed25519 signature against the embedded
|
||||
* snakeoil intermediate cert's public key (capsule name
|
||||
* "pki:snakeoil-intermediate.der", found and parsed once, cached for
|
||||
* every later call this boot -- the cert doesn't change mid-boot).
|
||||
*
|
||||
* descs/names/sigs must be the same three parallel arrays
|
||||
* (capsule_get_descriptors()/capsule_get_names()/capsule_get_signatures()),
|
||||
* desc_count their shared length, arena_base the payload arena
|
||||
* (capsule_get_arena()). index must be < desc_count.
|
||||
*/
|
||||
CapsuleSigResult capsule_verify_signature(
|
||||
const CapsuleDesc *descs, const CapsuleNameEntry *names,
|
||||
const CapsuleSigEntry *sigs, const uint8_t *arena_base,
|
||||
uint32_t desc_count, int index);
|
||||
|
||||
/* Human-readable string for logging, mirroring
|
||||
* capsule_validate_result_str()'s existing shape. */
|
||||
const char *capsule_sig_result_str(CapsuleSigResult result);
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_SIG_H */
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* capsule_vm_physics.h - Dynamic VM Fleet Physics
|
||||
*
|
||||
* Structural parallel to the word-level physics engine (execution heat,
|
||||
* decay, rolling-window history, regression-inferred slope) applied to
|
||||
* VMs instead of dictionary words. Kernel-only: no hosted-build
|
||||
* equivalent, since the hosted build has no multi-VM fleet.
|
||||
*
|
||||
* sum(execution_heat_q48 for all LIVE VMs) == Q.1 is a conservation
|
||||
* invariant, held by construction: every state change is a balanced
|
||||
* transfer (see design doc VM-PHYSICS-DYNAMIC-FLEET-DESIGN-20260705.md).
|
||||
*
|
||||
* This is a passive observer of VM activity, never a driver of it —
|
||||
* there is no scheduler here. Recording happens only at existing
|
||||
* dispatch points (BIRTH, KILL, VM-EXEC, VM-CALL, VM-STEP).
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_VM_PHYSICS_H
|
||||
#define STARKERNEL_CAPSULE_VM_PHYSICS_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "starkernel/vm_uuid.h" /* VMUuid -- FABRIC-0.md item 3.8 */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* vm_physics_init - Register a newly-born VM at zero heat
|
||||
*
|
||||
* Called from mama_word_birth once a VM reaches VM_STATE_LIVE. Starts
|
||||
* at execution_heat_q48 = 0, so conservation holds trivially — no
|
||||
* fan-out from existing VMs is needed.
|
||||
*
|
||||
* @param vm_id Registry VM ID assigned at birth
|
||||
*/
|
||||
void vm_physics_init(VMUuid vm_id);
|
||||
|
||||
/**
|
||||
* vm_physics_retire - Remove a killed VM, returning its heat to Hera
|
||||
*
|
||||
* Called from mama_word_kill before the registry entry is torn down.
|
||||
* The dying VM's entire remaining heat is transferred up its
|
||||
* parent_vm_id chain (VMRegistryEntry, capsule_run.h) to the fleet's
|
||||
* single structural root, Hera -- no division, no per-survivor
|
||||
* weighting. The chain walk tolerates already-dead intermediate
|
||||
* parents (a parent's own parent_vm_id was set once at its birth and
|
||||
* never rewritten, so the walk continues through it).
|
||||
*
|
||||
* @param vm_id Registry VM ID of the VM being killed
|
||||
*/
|
||||
void vm_physics_retire(VMUuid vm_id);
|
||||
|
||||
/**
|
||||
* vm_physics_touch - Record real dispatch activity against a VM
|
||||
*
|
||||
* Called from the three existing dispatch primitives (VM-EXEC, VM-CALL,
|
||||
* VM-STEP) at the point they've already confirmed the target VM is
|
||||
* live. Pulls heat from the rest of the fleet toward the touched VM
|
||||
* (amount = elapsed_ticks * fleet_transfer_slope_q48 >> 16) and appends
|
||||
* vm_id to the fleet's rolling touch-history window.
|
||||
*
|
||||
* Restated on the virtual tick (FABRIC-0.md item 2.1, 2026-08-04): no
|
||||
* longer takes a wall-clock timestamp. Reads fleet_heartbeat_tick_count
|
||||
* internally, which is execution-paced (advanced once per vm_tick()
|
||||
* call, see vm_physics_heartbeat_tick), so the transfer this produces is
|
||||
* a deterministic function of the execution stream, not of wall time.
|
||||
*
|
||||
* @param vm_id Registry VM ID being dispatched to
|
||||
*/
|
||||
void vm_physics_touch(VMUuid vm_id);
|
||||
|
||||
/**
|
||||
* vm_physics_tick - Heartbeat-gated inference pass
|
||||
*
|
||||
* Mirrors vm_tick_inference_engine: when the fleet touch-history window
|
||||
* is warm, replays it to re-fit fleet_transfer_slope_q48 via the same
|
||||
* closed-form log-linear OLS regression used for word-level decay.
|
||||
* Skips the update (does not substitute a default) when unwarmed or
|
||||
* when fit quality is not usable.
|
||||
*
|
||||
* @param now_ns Current monotonic time in nanoseconds
|
||||
*/
|
||||
void vm_physics_tick(uint64_t now_ns);
|
||||
|
||||
/**
|
||||
* vm_physics_heartbeat_tick - Fleet-wide heartbeat, gates vm_physics_tick()
|
||||
*
|
||||
* Call from every VM's own vm_tick(), not just Hera's. Increments a
|
||||
* single fleet-wide tick counter (distinct from any VM's own per-VM
|
||||
* HeartbeatState.tick_count) and calls vm_physics_tick() once that
|
||||
* shared counter has advanced by HEARTBEAT_INFERENCE_FREQUENCY since
|
||||
* the last fit.
|
||||
*
|
||||
* Fixes a real defect (VM-FLEET-ATTRACTOR-DESIGN-20260705.md rev k):
|
||||
* gating this on Hera's own tick_count meant the inference pass almost
|
||||
* never fired, because Hera-as-orchestrator mostly blocks on VM-EXEC/
|
||||
* VM-CALL dispatch -- work that accrues to the *target* VM's own tick
|
||||
* count, not hers. VM-PHYSICS-DYNAMIC-FLEET-DESIGN-20260705.md already
|
||||
* specified the fix's direction: fleet_transfer_slope_q48 must be
|
||||
* inferred "from aggregate fleet statistics... not one per VM" -- the
|
||||
* same principle applies to the readiness signal that gates computing
|
||||
* it, not just the regression's own input trajectory.
|
||||
*
|
||||
* @param now_ns Current monotonic time in nanoseconds (of whichever
|
||||
* VM is calling; unused by vm_physics_tick() today)
|
||||
*/
|
||||
void vm_physics_heartbeat_tick(uint64_t now_ns);
|
||||
|
||||
/**
|
||||
* vm_physics_fleet_heat_sum - Sum of execution_heat_q48 over all LIVE VMs
|
||||
*
|
||||
* Diagnostic / verification primitive. Should always read Q.1 (65536)
|
||||
* if the conservation invariant holds.
|
||||
*/
|
||||
uint64_t vm_physics_fleet_heat_sum(void);
|
||||
|
||||
/**
|
||||
* vm_physics_heat_of - Current execution_heat_q48 for one VM
|
||||
*
|
||||
* Transparency primitive (VM-FLEET-ATTRACTOR-DESIGN-20260705.md): the
|
||||
* fleet-wide conservation invariant always reads Q.1 by construction, so
|
||||
* it carries no information about how heat is actually distributed among
|
||||
* live VMs. This is the per-VM fact that sum alone can't provide.
|
||||
*
|
||||
* @param vm_id Registry VM ID
|
||||
* @return execution_heat_q48, or 0 if vm_id is unknown or not live
|
||||
*/
|
||||
uint64_t vm_physics_heat_of(VMUuid vm_id);
|
||||
|
||||
/**
|
||||
* vm_physics_conserved - Conservation check
|
||||
*
|
||||
* @return 1 if |vm_physics_fleet_heat_sum() - Q.1| < epsilon, else 0
|
||||
*/
|
||||
int vm_physics_conserved(void);
|
||||
|
||||
/**
|
||||
* vm_physics_status - Print a diagnostic status report
|
||||
*
|
||||
* Replaces K-STATUS/VM-STATUS. Reports fleet heat sum, conservation
|
||||
* verdict, and the current fleet-wide inferred slope / fit quality /
|
||||
* warm-up state -- the fleet-level analog of K-STATUS's per-VM report,
|
||||
* adapted to a mechanism with no fixed VM count to enumerate.
|
||||
*/
|
||||
void vm_physics_status(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_VM_PHYSICS_H */
|
||||
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* capsule_vm_switch_signal.h - New, purpose-built "who runs next" signal
|
||||
* for preemptive context switching (FABRIC-3.md §XXVIII, Stage 3,
|
||||
* 2026-09-13).
|
||||
*
|
||||
* Deliberately NOT a repurposing of capsule_vm_physics.c's execution-heat
|
||||
* engine -- that measures word-level dispatch fairness over millions of
|
||||
* executions on a different timescale, and its own header explicitly
|
||||
* documents it as never touched from interrupt context (unlocked, by
|
||||
* design). This is a different physical quantity: instant-by-instant
|
||||
* run-readiness, consulted from real ISR context (heartbeat_tick()) every
|
||||
* timer tick.
|
||||
*
|
||||
* Concurrency discipline mirrors heartbeat.c's own §21.1-sanctioned
|
||||
* pattern for heartbeat_next_period_ns(): single-writer-ISR (tick()) /
|
||||
* single-reader-mainline (take_pending(), called from the cooperative
|
||||
* checkpoint in execute_colon_word()), no lock, because nothing on this
|
||||
* single hart is concurrent with the ISR while it runs.
|
||||
*
|
||||
* NOT truly interrupt-driven register/stack swapping (that was
|
||||
* considered and rejected for this stage -- see FABRIC-3.md §XXVIII
|
||||
* Stage 3 for why): the ISR only ever sets a flag. The actual switch
|
||||
* (Stage 2's already-proven sk_vm_context_switch()) happens later, at a
|
||||
* safe cooperative checkpoint on the mainline, once per word dispatch.
|
||||
*
|
||||
* Slot table is sized with headroom, not hardcoded to exactly today's 3
|
||||
* 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
|
||||
#define STARKERNEL_CAPSULE_VM_SWITCH_SIGNAL_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#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 (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_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.
|
||||
* No-op (returns -1) if vm_id was never registered. */
|
||||
int sk_vm_switch_signal_unregister(VMUuid vm_id);
|
||||
|
||||
/* Called from heartbeat_tick() (ISR context) every timer tick. Cheap:
|
||||
* iterates only the registered slots (bounded, small). */
|
||||
void sk_vm_switch_signal_tick(void);
|
||||
|
||||
/* Called from the cooperative checkpoint (execute_colon_word(), mainline,
|
||||
* once per word dispatch). Returns the VMUuid of a VM that should now be
|
||||
* switched to, or vm_uuid_none() if nothing is pending. Clears the
|
||||
* pending flag as a side effect -- call at most once per checkpoint. */
|
||||
VMUuid sk_vm_switch_signal_take_pending(void);
|
||||
|
||||
/* Call once, from the same checkpoint, immediately after a switch
|
||||
* sk_vm_switch_signal_take_pending() requested actually executes (not if
|
||||
* the target turned out invalid/self) -- feeds the DoE CSV counters
|
||||
* below. Also resets `target_id`'s own readiness/has_work directly
|
||||
* (Stage 3 follow-on correction, 2026-09-14) -- see the .c file's own doc
|
||||
* comment on why this can't be left to tick()'s current-slot polling. */
|
||||
void sk_vm_switch_signal_note_switch_performed(VMUuid target_id);
|
||||
|
||||
/* Message-arrival eligibility hook (FABRIC-3.md §XXVIII Stage 3 follow-on,
|
||||
* 2026-09-14): mark that VM `vm_id` was just sent a message (the FORTH-side
|
||||
* MSG-SEND hook in capsules/common/messaging.4th calls this via the new
|
||||
* SWITCH-MARK-WORK primitive, mainline, single-writer). Consulted by
|
||||
* sk_vm_switch_signal_tick()'s own readiness->pending decision so a VM
|
||||
* with nothing recently sent to it never becomes a switch target purely by
|
||||
* sitting idle long enough -- closes the wasteful (but, since the Stage 3
|
||||
* stack-ownership fix, no longer corrupting) trampoline-bounce cycle for
|
||||
* an idle participant. No-op for an unregistered vm_id. */
|
||||
void sk_vm_switch_signal_mark_work(VMUuid vm_id);
|
||||
|
||||
/* DoE CSV read-only exposure (FABRIC-3.md §XXVIII Stage 3 follow-on,
|
||||
* 2026-09-13) -- all of this state already existed for the switch
|
||||
* decision itself; these just make it observable. */
|
||||
uint64_t sk_vm_switch_signal_switch_count(void); /* cumulative, since boot */
|
||||
uint32_t sk_vm_switch_signal_ticks_since_switch(void);
|
||||
int sk_vm_switch_signal_current_slot(void); /* -1 = none/unregistered */
|
||||
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 */
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* capsule_wirebind.h - WIREBIND: the real thumbdrive-attach call site
|
||||
* (FABRIC-2.md §F.5/§F.23). Assembles pieces already built and
|
||||
* individually verified this session -- CERTVERIFY (vm_identity.h's
|
||||
* vm_identity_from_cert()), RUNCAP (capsule_runcap.h), the console-VM +
|
||||
* user-VM pair (capsule_console.h, sk_repl_dispatch_line() in repl.c) --
|
||||
* into one automatic sequence, replacing the RUNCAP-TEST/PAIR-TEST
|
||||
* diagnostic words that exercised each piece by hand.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_WIREBIND_H
|
||||
#define STARKERNEL_CAPSULE_WIREBIND_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include "starkernel/homeblocks_sig.h"
|
||||
#include "starkernel/vm_identity.h"
|
||||
#include "vm.h"
|
||||
|
||||
struct blkio_dev;
|
||||
|
||||
/**
|
||||
* capsule_wirebind_verify_cert - Read the cert region off dev and verify
|
||||
* it against mama_vm's own Zuse identity. Shared by both
|
||||
* capsule_wirebind_try_attach() (the original attach) and BINDSTEP
|
||||
* (mama_word_use(), mama_forth_words.c -- re-verifies live on every USE
|
||||
* of an identity-locked VM, per FABRIC-2.md §F.9 decision 1) so both
|
||||
* call sites check the exact same thing the exact same way.
|
||||
*
|
||||
* No-op-and-fail (-1) if sig->cert_offset is 0 (no cert region -- a
|
||||
* genesis-mode Zuse drive, or simply not a regular identity drive) or
|
||||
* mama_vm has no installed Zuse cert yet.
|
||||
*
|
||||
* @param dev Already-open block device to read the cert from.
|
||||
* @param sig Its already-checked homeblocks_sig_t.
|
||||
* @param mama_vm Hera's own VM -- the trust root (zuse_cert_pubkey).
|
||||
* @param out Filled with the verified identity on success.
|
||||
* @return 0 on success, -1 on any failure (read, verify, or precondition).
|
||||
*/
|
||||
int capsule_wirebind_verify_cert(struct blkio_dev *dev,
|
||||
const homeblocks_sig_t *sig,
|
||||
VM *mama_vm, VMIdentity *out);
|
||||
|
||||
/**
|
||||
* capsule_wirebind_try_attach - Try to verify and bind a just-attached
|
||||
* regular (non-Zuse) identity drive.
|
||||
*
|
||||
* No-op if sig->cert_offset is 0 (a genesis-mode Zuse drive has no cert
|
||||
* region -- that's capsule_zuse_boot_try_attach()'s own job, not this
|
||||
* one's) or if mama_vm has no installed Zuse cert yet (nothing to verify
|
||||
* the attached cert against). Otherwise: reads the cert devblock(s),
|
||||
* calls vm_identity_from_cert() against mama_vm's own zuse_cert_pubkey
|
||||
* and sig->drive_uuid. On success, reads the drive's own
|
||||
* user_identity_seed_t for its username and 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, looks for this). Does NOT USE the
|
||||
* new console automatically -- that stays an explicit, later,
|
||||
* ACL-gated step (BINDSTEP, §F.9), not something a bare attach should
|
||||
* trigger silently.
|
||||
*
|
||||
* @param dev The just-attached, already-open block device.
|
||||
* @param sig Its already-checked homeblocks_sig_t.
|
||||
* @param mama_vm Hera's own VM (the verifier -- her zuse_cert_pubkey is
|
||||
* the trust root regular user certs are checked against).
|
||||
*/
|
||||
void capsule_wirebind_try_attach(struct blkio_dev *dev,
|
||||
const homeblocks_sig_t *sig,
|
||||
VM *mama_vm);
|
||||
|
||||
/**
|
||||
* capsule_wirebind_eject - Graceful detach of whatever VM is currently
|
||||
* attached via the home-blocks USB path (FABRIC-2.md §F.10, decision 1).
|
||||
* The drive is still physically present when this runs.
|
||||
*
|
||||
* Sequence: resolve the tracked attached-VM id to a live registry entry
|
||||
* (no-op, returns -1, if nothing is tracked or the entry is already
|
||||
* dead/gone -- capsule_vm_kill()'s own idempotency covers a VM already
|
||||
* killed by some other path); blk_vm_flush_all() while the VM is still
|
||||
* alive; if the console's active VM is this same VM, reset it to Hera
|
||||
* (sk_repl_set_active_vm(NULL)) *before* teardown -- required, not
|
||||
* optional, to avoid a dangling console pointer; capsule_vm_kill() by
|
||||
* name; clear the tracked state.
|
||||
*
|
||||
* EJECT is inherently about "whichever identity is currently paired to
|
||||
* the one physical console" -- targets that singleton, no name argument.
|
||||
* (Corrected 2026-09-14: this used to claim a "single-USB-device
|
||||
* constraint (§F.8)" meant there was never more than one candidate --
|
||||
* stale even at the time this correction was written; §XV/§XVI
|
||||
* (2026-09-11/12) proved 9 identities genuinely simultaneously live via
|
||||
* this same attach path. EJECT staying console-singleton-scoped is a
|
||||
* deliberate UX choice now, not a hardware constraint -- see
|
||||
* capsule_wirebind_unclean_detach()'s own doc below for the function
|
||||
* that DOES need to reach every live identity, not just this one.)
|
||||
*
|
||||
* @return 0 on success, -1 if nothing was attached to eject.
|
||||
*/
|
||||
int capsule_wirebind_eject(void);
|
||||
|
||||
/**
|
||||
* capsule_wirebind_unclean_detach - Abrupt-path counterpart to
|
||||
* capsule_wirebind_eject() (FABRIC-2.md §F.10, decision 2 -- the UNCLEAN
|
||||
* node, closed alongside EJECT). Called from the existing
|
||||
* bot_msc_detach_pending hot-unplug signal (repl.c) -- the device is
|
||||
* already gone by the time this runs, so no flush is attempted; data
|
||||
* since the last flush is lost, which is correct unclean-removal
|
||||
* semantics.
|
||||
*
|
||||
* FABRIC-3.md §VII follow-on, 2026-09-06: requires the departing device
|
||||
* to actually be one WIREBIND is tracking -- a real bug otherwise, found
|
||||
* live once genuine multi-device attach made a *different* device's
|
||||
* detach reachable while a WIREBIND user's own stayed attached.
|
||||
*
|
||||
* FABRIC-3.md §XXVIII Stage 4, 2026-09-14: resolves the departing device
|
||||
* against a per-device live-identity table now, not the single
|
||||
* console-pairing global capsule_wirebind_eject() uses -- with several
|
||||
* identities simultaneously live (§XV/§XVI), any one of them can be the
|
||||
* device that just disappeared, not only the most recently attached.
|
||||
* Refuses (rather than freeing) a VM currently VM_STATE_SWITCHED_OUT --
|
||||
* see capsule_vm_kill()'s own guard -- leaving it tracked for the Stage 3
|
||||
* switcher to reap on its own next resume attempt instead.
|
||||
*
|
||||
* @param dev The device that just detached; every other value is a no-op.
|
||||
*/
|
||||
void capsule_wirebind_unclean_detach(struct blkio_dev *dev);
|
||||
|
||||
/**
|
||||
* capsule_wirebind_attached_username - The plain username (no "~user"
|
||||
* registry-name suffix) of whichever identity is currently tracked as
|
||||
* attached, or NULL if none is (FABRIC-2.md §I.1/4.4s -- the `(user)`
|
||||
* console prompt segment reads this). Points into WIREBIND's own
|
||||
* internal storage; valid only until the next attach/eject/detach, same
|
||||
* caveat as console_get_vm_name().
|
||||
*/
|
||||
const char *capsule_wirebind_attached_username(void);
|
||||
|
||||
/**
|
||||
* capsule_wirebind_overflow_idle_check - FABRIC-2.md §I.2's own "overflow
|
||||
* trigger," decided and built 2026-09-05. Called once per idle tick
|
||||
* (sk_repl_idle(), repl.c, alongside blk_migration_idle_check() -- same
|
||||
* ~1 Hz cadence), same as that function's own convention.
|
||||
*
|
||||
* No-op if nothing is attached via WIREBIND. Otherwise reads the attached
|
||||
* drive's own free/total via blk_get_device_free_blocks() (real numbers:
|
||||
* a WIREBIND-attached drive is always HOMEBLOCKS_SIG_OK, i.e. already
|
||||
* STFR/v2-formatted, by the time blk_subsys_attach_device() runs on the
|
||||
* same dev pointer right after WIREBIND itself -- not PROVISIONAL, not
|
||||
* raw). If free space is below a fixed threshold AND the attached
|
||||
* identity does not already own a claim (blk_owner_has_claim() -- a disk
|
||||
* scan, not a RAM flag, so this decision survives reboot/reattach for
|
||||
* free), claims a fixed number of additional devblocks on Artemis's own
|
||||
* device via blk_firsttouch_claim() -- a one-time-per-identity extension,
|
||||
* not a growth loop, deliberately: this does not free space on the
|
||||
* user's own drive, it only extends their pool onto system-resident
|
||||
* space, so re-claiming every tick once already extended would walk
|
||||
* Artemis's device to exhaustion for no benefit.
|
||||
*/
|
||||
void capsule_wirebind_overflow_idle_check(void);
|
||||
|
||||
/**
|
||||
* capsule_wirebind_reap_idle_check - FABRIC-3.md §XXVIII Stage 4
|
||||
* (2026-09-14): sweeps the per-device live-identity table for entries
|
||||
* whose VM has been reaped (capsule_vm_force_reap(), called from the
|
||||
* Stage 3 checkpoint on a pending_reap target -- see that function's own
|
||||
* doc comment) and removes the now-stale table entry. Does NOT itself
|
||||
* free anything or call capsule_vm_kill()/force_reap() -- purely
|
||||
* bookkeeping cleanup after the fact. Called at the same ~1 Hz idle
|
||||
* cadence as capsule_wirebind_overflow_idle_check().
|
||||
*/
|
||||
void capsule_wirebind_reap_idle_check(void);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_WIREBIND_H */
|
||||
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* capsule_zuse_boot.h - Thumbdrive-resident Zuse genesis/attach
|
||||
* (FABRIC-2.md §F.20/§F.21). Replaces kernel_main.c's old one-shot
|
||||
* block-fence mint-or-load: Zuse's own identity now lives only on her
|
||||
* own minted thumbdrive, never system-resident. Since USB attach
|
||||
* detection only happens inside the idle loop (sk_repl_idle(), not at
|
||||
* a fixed point in the boot sequence), this runs per-attach from there
|
||||
* instead of once at boot.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_ZUSE_BOOT_H
|
||||
#define STARKERNEL_CAPSULE_ZUSE_BOOT_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include "starkernel/homeblocks_sig.h"
|
||||
#include "vm.h"
|
||||
|
||||
struct blkio_dev;
|
||||
|
||||
/**
|
||||
* capsule_zuse_boot_try_attach - Try to genesis-mint or authenticate
|
||||
* Zuse from a just-attached drive.
|
||||
*
|
||||
* No-op if mama_vm->zuse_cert_installed is already 1 (Zuse already has a
|
||||
* real identity this boot, from an earlier attach). Otherwise:
|
||||
* - No genesis marker yet in the fence, drive reads HOMEBLOCKS_SIG_BLANK:
|
||||
* mint Zuse's own identity onto it (capsule_mint_identity(), genesis
|
||||
* mode), record the pubkey in the fence, install the cert, and
|
||||
* re-run ACL-ZUSE-BOOT (zuse.4th) so zuse_session activates exactly
|
||||
* like it always has for a same-boot-installed cert.
|
||||
* - Genesis marker present, drive reads HOMEBLOCKS_SIG_OK: read its
|
||||
* own user_identity_seed_t, compare pubkey against the marker: if it
|
||||
* matches, install the cert and re-run ACL-ZUSE-BOOT the same way.
|
||||
* If it doesn't match, this is some other identity's drive -- no-op
|
||||
* here, that's a regular attach for BINDSTEP to handle later.
|
||||
* - Anything else (foreign/corrupt media, no marker and non-blank
|
||||
* drive): no-op.
|
||||
*
|
||||
* @param dev The just-attached, already-open block device.
|
||||
* @param sig_rc homeblocks_sig_check()'s own result for this attach.
|
||||
* @param sig The checked homeblocks_sig_t (only meaningful if
|
||||
* sig_rc == HOMEBLOCKS_SIG_OK; may be NULL otherwise).
|
||||
* @param mama_vm Hera's own VM (zuse_cert_seed/installed/session live
|
||||
* here; also the target of the ACL-ZUSE-BOOT re-run).
|
||||
*/
|
||||
void capsule_zuse_boot_try_attach(struct blkio_dev *dev,
|
||||
homeblocks_sig_result_t sig_rc,
|
||||
const homeblocks_sig_t *sig,
|
||||
VM *mama_vm);
|
||||
|
||||
/**
|
||||
* capsule_zuse_boot_logout - End Zuse's session when her own attached
|
||||
* drive detaches (FABRIC-2.md §I.8, re-scoped 2026-09-04: no identity is
|
||||
* different here -- Zuse logs out on device removal exactly like a
|
||||
* WIREBIND user does, not via a Stadium-patron TTL. She has no separate
|
||||
* VM or blocks of her own, so unlike capsule_wirebind_eject()/
|
||||
* _unclean_detach() there is no flush step to skip on the abrupt path --
|
||||
* one function covers both the graceful (EJECT) and abrupt (hot-unplug)
|
||||
* call sites identically.
|
||||
*
|
||||
* No-op if `dev` isn't the device currently tracked as Zuse's own (nothing
|
||||
* to do -- some other identity's drive is what's leaving, or nothing is
|
||||
* attached at all) -- FABRIC-3.md §VII follow-on, 2026-09-06: this doc
|
||||
* comment always claimed that no-op, but the check itself was missing
|
||||
* until now (the function took no device parameter at all) -- confirmed
|
||||
* live as a real bug once genuine multi-device attach made it reachable
|
||||
* (detaching an unrelated device logged Zuse out too). Clears
|
||||
* mama_vm->zuse_session only -- zuse_cert_installed and the cert itself
|
||||
* stay put, permanently, per vm_zuse_cert_install()'s own one-way design;
|
||||
* re-attaching her own drive re-authenticates via
|
||||
* capsule_zuse_boot_try_attach() without re-minting anything.
|
||||
*
|
||||
* @param mama_vm Hera's own VM (zuse_session lives here).
|
||||
* @param dev The device that just detached -- compared against the one
|
||||
* tracked as hers; every other value is a no-op.
|
||||
*/
|
||||
void capsule_zuse_boot_logout(VM *mama_vm, struct blkio_dev *dev);
|
||||
|
||||
/**
|
||||
* capsule_zuse_boot_attached_dev - The device currently tracked as Zuse's
|
||||
* own, or NULL if she isn't attached this boot. FABRIC-3.md §VII follow-on,
|
||||
* 2026-09-06: exists so an explicit, operator-initiated logout (EJECT,
|
||||
* mama_forth_words.c) can pass her own device back into
|
||||
* capsule_zuse_boot_logout() without needing to already know it -- unlike
|
||||
* the abrupt hot-unplug path, EJECT isn't reacting to any specific
|
||||
* device's detach event, so there is no other device value available at
|
||||
* that call site to check against.
|
||||
*/
|
||||
struct blkio_dev *capsule_zuse_boot_attached_dev(void);
|
||||
|
||||
/**
|
||||
* capsule_zuse_boot_load_root_pubkey - Populate mama_vm->zuse_root_pubkey/
|
||||
* zuse_root_pubkey_known from the persistent genesis-marker fence
|
||||
* (zuse_genesis_marker_t, block_subsystem.h's blk_meta_zone_read()),
|
||||
* independently of whether Zuse's own thumbdrive is attached this boot.
|
||||
*
|
||||
* Call once, early -- as soon as the block subsystem and Artemis's own
|
||||
* resident storage are up (the fence lives there, not on any removable
|
||||
* drive) -- from kernel_main.c. No-op (leaves zuse_root_pubkey_known 0)
|
||||
* if no genesis has ever happened yet (no marker in the fence): there is
|
||||
* no root identity to verify against, so no WIREBIND identity could have
|
||||
* a cert chained to one either.
|
||||
*
|
||||
* Deliberately does not touch zuse_cert_installed/zuse_cert_seed/
|
||||
* zuse_cert_pubkey -- that triple stays reserved for Zuse's own live,
|
||||
* authenticated session (capsule_zuse_boot_try_attach()), gating MINT
|
||||
* (needs her private seed). This function only ever loads her already-
|
||||
* public key, for WIREBIND cert *verification* (capsule_wirebind.c),
|
||||
* which needs nothing else -- see zuse_root_pubkey_known's own doc
|
||||
* comment (vm.h) for the full reasoning.
|
||||
*
|
||||
* @param mama_vm Hera's own VM (zuse_root_pubkey/_known live here).
|
||||
*/
|
||||
void capsule_zuse_boot_load_root_pubkey(VM *mama_vm);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_ZUSE_BOOT_H */
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* cmdline.h — kernel command-line parser API
|
||||
*
|
||||
* Pure C99 / freestanding — no UEFI types, no libc.
|
||||
* The UEFI loader converts LoadOptions (UCS-2) to ASCII before calling
|
||||
* cmdline_parse_ascii(); the parser itself never sees CHAR16.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CMDLINE_H
|
||||
#define STARKERNEL_CMDLINE_H
|
||||
|
||||
#include "kernel_args.h"
|
||||
|
||||
/*
|
||||
* cmdline_parse_ascii — parse a NUL-terminated ASCII cmdline into *args.
|
||||
*
|
||||
* Fills all default values first; unknown flags are silently ignored so
|
||||
* future kernels can add flags without breaking old boot entries.
|
||||
* Safe to call with NULL or empty cmdline (returns all defaults).
|
||||
*
|
||||
* Recognised flags:
|
||||
* --doe set run_doe = 1
|
||||
* --log-level=<level> debug / info / warn / error
|
||||
* --stack=<N>[KMG] stack_size in bytes
|
||||
* --heap=<N>[KMG] heap_size in bytes
|
||||
*/
|
||||
void cmdline_parse_ascii(const char *cmdline, KernelArgs *args);
|
||||
|
||||
/*
|
||||
* cmdline_parse_size — parse a size token with optional K/M/G suffix.
|
||||
*
|
||||
* Examples: "2M" -> 2*1024*1024, "4G" -> 4*1024^3, "65536" -> 65536.
|
||||
* Returns 0 on parse error (empty string, non-digit start, etc.).
|
||||
*/
|
||||
uint64_t cmdline_parse_size(const char *s);
|
||||
|
||||
#endif /* STARKERNEL_CMDLINE_H */
|
||||
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* console.h - Serial console + framebuffer VT100 interface for StarKernel
|
||||
*
|
||||
* Output policy:
|
||||
* - Serial UART is always active (initialized by console_init).
|
||||
* - When console_fb_init() has been called and the framebuffer is ready,
|
||||
* every character is also rendered through the VT100 terminal on screen.
|
||||
* - Both outputs are always live simultaneously; serial cannot be disabled.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CONSOLE_H
|
||||
#define STARKERNEL_CONSOLE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "uefi.h"
|
||||
|
||||
/**
|
||||
* Initialize serial console (UART).
|
||||
* Must be called once during early kernel boot.
|
||||
*/
|
||||
void console_init(void);
|
||||
|
||||
/**
|
||||
* Initialize the framebuffer VT100 terminal.
|
||||
* Call after UEFI boot services have been exited and the GOP framebuffer
|
||||
* address is known (from BootInfo). Safe to call with info==NULL (no-op).
|
||||
* fmt: FB_PIXEL_BGRX32 is correct for most QEMU / real hardware GOP.
|
||||
*/
|
||||
#include "framebuffer.h"
|
||||
void console_fb_init(const FramebufferInfo *info, FbPixelFormat fmt);
|
||||
|
||||
/**
|
||||
* FABRIC-0.md item 4.4j: switch the framebuffer console's glyph backend from
|
||||
* font_8x16.c to TTF-TEXT's rasterizer. Thin wrapper over
|
||||
* vt100_enable_ttf() -- see that function's doc comment for the full
|
||||
* contract (lazy font load, cell-geometry/cols/rows recompute, screen
|
||||
* clear, one-shot). No-op if the framebuffer console was never
|
||||
* initialized (console_fb_init() not called, or it no-op'd on a NULL
|
||||
* framebuffer).
|
||||
*/
|
||||
void console_fb_enable_ttf(void);
|
||||
|
||||
/**
|
||||
* FABRIC-0.md item 4.4q: thin wrappers over vt100_scroll_back()/
|
||||
* vt100_scroll_fwd() -- see those functions' doc comments for the full
|
||||
* contract. No-op if the framebuffer console was never initialized.
|
||||
*/
|
||||
void console_fb_scroll_back(uint32_t n);
|
||||
void console_fb_scroll_fwd(uint32_t n);
|
||||
|
||||
/**
|
||||
* FABRIC-0.md item 4.4y-revised: thin wrapper over vt100_toggle_graphics()
|
||||
* -- see that function's doc comment for the full contract (the
|
||||
* Alt+TAB graphics/text state machine). No-op if the framebuffer console
|
||||
* was never initialized.
|
||||
*/
|
||||
void console_fb_toggle_graphics(void);
|
||||
|
||||
/**
|
||||
* Thin wrapper over vt100_draw_cursor() -- see that function's doc
|
||||
* comment for the full contract (a static block cursor at the terminal's
|
||||
* current position). No-op if the framebuffer console was never
|
||||
* initialized.
|
||||
*/
|
||||
void console_fb_draw_cursor(void);
|
||||
|
||||
/**
|
||||
* Thin wrapper over vt100_erase_cursor(). No-op if the framebuffer
|
||||
* console was never initialized.
|
||||
*/
|
||||
void console_fb_erase_cursor(void);
|
||||
|
||||
/**
|
||||
* Write a single character to serial console
|
||||
*/
|
||||
void console_putc(char c);
|
||||
|
||||
/**
|
||||
* Write a null-terminated string to serial console
|
||||
*/
|
||||
void console_puts(const char *s);
|
||||
|
||||
/**
|
||||
* Write a string with newline to serial console
|
||||
*/
|
||||
void console_println(const char *s);
|
||||
|
||||
/**
|
||||
* console_ensure_line_start - Make sure the *next real output* starts at
|
||||
* the beginning of a fresh output line, closing off whatever is currently
|
||||
* mid-line (e.g. a dangling prompt). No-op if already at line start.
|
||||
*
|
||||
* FABRIC-2.md §I.9 fix, 2026-09-05: the newline is DEFERRED, not emitted
|
||||
* immediately -- it only actually reaches the console on the next real
|
||||
* console_putc() call, and is silently dropped (never emitted at all) if
|
||||
* console_cancel_deferred_line_start() is called first instead. Before this
|
||||
* fix, an immediate, unconditional newline here meant any caller invoking
|
||||
* this function "just in case" (sk_repl_idle() being the one real caller)
|
||||
* would visibly snap a bare, unfinished prompt line to a fresh blank line
|
||||
* even when nothing was actually about to be printed -- indistinguishable
|
||||
* from Enter having already been pressed at that prompt. Deferring means a
|
||||
* caller that turns out to have nothing to print can cancel cleanly, with
|
||||
* zero visible effect, while a caller that does print gets the correct
|
||||
* "close the old line first" behavior for free, still counted by
|
||||
* console_tx_count() as real output (unlike the old always-silent inner
|
||||
* form) since it is realized through the normal console_putc() path.
|
||||
*/
|
||||
void console_ensure_line_start(void);
|
||||
|
||||
/**
|
||||
* console_cancel_deferred_line_start - Discard a pending deferred newline
|
||||
* from console_ensure_line_start() without ever emitting it. No-op if no
|
||||
* newline is currently deferred. Callers that speculatively deferred a line
|
||||
* break before checking whether they actually have anything to print
|
||||
* (sk_repl_idle()'s idle-beat check being the motivating case, FABRIC-2.md
|
||||
* §I.9) call this when the check comes back negative, so the console is
|
||||
* left exactly as it was -- no stray newline, no phantom blank line.
|
||||
*/
|
||||
void console_cancel_deferred_line_start(void);
|
||||
|
||||
/**
|
||||
* console_tx_count - Monotonic count of console_putc() calls delivered to
|
||||
* either output (serial and/or framebuffer). In use by the REPL to detect
|
||||
* that an idle bottom half (heartbeat, USB attach/detach) wrote to the
|
||||
* console while the top-level prompt was showing, so it can re-anchor the
|
||||
* prompt afterward. Never decreases.
|
||||
*/
|
||||
uint64_t console_tx_count(void);
|
||||
|
||||
/**
|
||||
* Read a single character from serial console (non-blocking)
|
||||
* Returns -1 if no character available
|
||||
*/
|
||||
int console_getc(void);
|
||||
|
||||
/**
|
||||
* Check if character is available for reading
|
||||
*/
|
||||
int console_poll(void);
|
||||
|
||||
/**
|
||||
* Set the active VM name shown as [Name] prefix on each output line.
|
||||
* Pass NULL to suppress the prefix (kernel-only output before any VM).
|
||||
* Copies into internal storage (FABRIC-2.md Phase F, 2026-08-28) -- the
|
||||
* caller's own pointer does not need to remain valid afterward.
|
||||
*/
|
||||
void console_set_vm_name(const char *name);
|
||||
const char *console_get_vm_name(void);
|
||||
|
||||
/**
|
||||
* console_save_vm_name - Copy the current active-VM name into the
|
||||
* caller's own buffer, for a later console_set_vm_name() restore.
|
||||
*
|
||||
* console_get_vm_name() alone is NOT safe for save-then-restore: it
|
||||
* returns a pointer into the single internal buffer console_set_vm_name()
|
||||
* copies into, so an intervening console_set_vm_name() call (the normal
|
||||
* "switch, do work, switch back" pattern every BIRTH/VM-EXEC/CONNECT-*
|
||||
* call site uses) overwrites the very bytes the saved pointer points at
|
||||
* before the restore ever runs -- found live 2026-08-28, the restore
|
||||
* silently no-ops. Copies at most cap-1 bytes plus a NUL terminator;
|
||||
* writes "" if there was no active name (NULL) to save.
|
||||
*
|
||||
* @param out Caller-owned buffer.
|
||||
* @param cap Its size in bytes.
|
||||
*/
|
||||
void console_save_vm_name(char *out, size_t cap);
|
||||
|
||||
/**
|
||||
* FABRIC-3.md SXXV follow-up (2026-09-13): the line prefix used to be a
|
||||
* bare "[VMName] ", ambiguous between a console-proxy VM and the actual
|
||||
* identity VM behind it (e.g. "rajames" the console vs. "rajames~user"
|
||||
* the real WIREBIND-restricted identity) -- confirmed live to cause real
|
||||
* confusion during testing. Unified to "[user@VMName] " when a user
|
||||
* context is available, still "[VMName] " when not (unauthenticated/
|
||||
* pre-login, unchanged from today).
|
||||
*
|
||||
* console.c is a clean HAL module with no dependency on capsule/WIREBIND
|
||||
* logic (zuse_session, capsule_wirebind_attached_username()) -- pulling
|
||||
* either in directly here would be a real layering violation, not just a
|
||||
* style preference. This callback lets repl.c (which already computes
|
||||
* exactly this for sk_print_prompt()) supply the "user" half without
|
||||
* console.c knowing anything about VMs, sessions, or WIREBIND. Returns
|
||||
* NULL/empty for "no user context" (falls back to the bare "[VMName] "
|
||||
* form); the returned pointer must remain valid until the next call
|
||||
* (matches console_get_vm_name()'s own single-buffer convention).
|
||||
*/
|
||||
typedef const char *(*console_user_prefix_fn)(void);
|
||||
void console_set_user_prefix_provider(console_user_prefix_fn fn);
|
||||
|
||||
/* Last FORTH word name set by the dispatcher before entry->func(vm).
|
||||
* Printed by the #GP fault handler to identify the faulting word. */
|
||||
extern volatile const char *g_sk_fault_word;
|
||||
|
||||
#endif /* STARKERNEL_CONSOLE_H */
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* starkernel/doe_log.h — DoE (Design of Experiments) CSV logger
|
||||
*
|
||||
* Emits per-heartbeat-tick CSV rows to the serial log, tagged with
|
||||
* [HADES][DOE ] so they can be grepped cleanly from the QEMU log.
|
||||
*
|
||||
* Each row contains the 12 standard heartbeat snapshot fields plus
|
||||
* 3 APIC timer fields from TimeTrustState:
|
||||
* apic_ticks, time_trust_q48, variance_q48
|
||||
*
|
||||
* A header row is printed automatically before the first data row.
|
||||
*
|
||||
* Always compiled in -- gating moved from a build-time flag
|
||||
* (HEARTBEAT_DOE_LOG) to a runtime one (g_doe_log_enabled below) so the
|
||||
* same build can run with or without per-tick instrumentation, toggled
|
||||
* live via the HB-ON/HB-OFF FORTH words (register_doe_log_words()),
|
||||
* no rebuild required.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_DOE_LOG_H
|
||||
#define STARKERNEL_DOE_LOG_H
|
||||
|
||||
#include "vm.h"
|
||||
|
||||
/**
|
||||
* Runtime enable flag for doe_log_tick_row() below. Defaults to 1
|
||||
* (matches the old HEARTBEAT_DOE_LOG=1 default -- instrumentation on
|
||||
* unless explicitly turned off). Toggled by HB-ON/HB-OFF; read, not
|
||||
* meant to be written directly outside those two words.
|
||||
*/
|
||||
extern int g_doe_log_enabled;
|
||||
|
||||
/**
|
||||
* Emit one CSV row for the current heartbeat tick, unless
|
||||
* g_doe_log_enabled is 0 (a no-op then). Pulls APIC TimeTrustState via
|
||||
* heartbeat_state(). Prints the column header once, before the first row
|
||||
* ever emitted -- not re-printed on every HB-ON, so a session toggled
|
||||
* off and back on stays one continuous CSV rather than getting a second
|
||||
* header embedded partway through.
|
||||
*
|
||||
* @param vm VM instance (used for snapshot data)
|
||||
* @param snap Populated HeartbeatTickSnapshot from heartbeat_capture_tick_snapshot()
|
||||
*/
|
||||
void doe_log_tick_row(VM *vm, const HeartbeatTickSnapshot *snap);
|
||||
|
||||
/**
|
||||
* Registers HB-ON ( -- ) and HB-OFF ( -- ), which set g_doe_log_enabled
|
||||
* to 1 and 0 respectively.
|
||||
*/
|
||||
void register_doe_log_words(VM *vm);
|
||||
|
||||
#endif /* STARKERNEL_DOE_LOG_H */
|
||||
@@ -0,0 +1,47 @@
|
||||
/* ed25519.h -- EdDSA (RFC 8032), freestanding C99.
|
||||
*
|
||||
* Originally verify-only ("this kernel never signs; signing happens in
|
||||
* the host-side build tool") -- that was correct for capsule signing
|
||||
* (build-time, offline, a normal Linux binary can link libsodium/
|
||||
* OpenSSL) but conflicts with an on-device Zuse session minting new
|
||||
* user certs live at runtime, which requires the kernel itself to sign.
|
||||
* Decided (Phase 8, 2026-08-26): add real keygen/signing rather than
|
||||
* reshape that flow around verify-only. Entropy for keygen comes from
|
||||
* virtio_rng.h -- this header still has no RNG of its own, and takes a
|
||||
* caller-supplied seed rather than generating one, deliberately: keygen
|
||||
* has no business deciding how the seed's randomness quality is
|
||||
* guaranteed, that's the caller's job.
|
||||
*
|
||||
* Signing is NOT constant-time (same non-constant-time double-and-add
|
||||
* scalar_mult() verify already used) -- acceptable for this project's
|
||||
* actual threat model (an emulated/embedded kernel with no untrusted
|
||||
* co-tenant able to observe timing), not acceptable if this code is
|
||||
* ever reused somewhere with a real timing-attack surface.
|
||||
*/
|
||||
#ifndef ED25519_H
|
||||
#define ED25519_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* Returns 1 if signature (64 bytes: R || S) is a valid Ed25519 signature
|
||||
* by pubkey (32 bytes, compressed point) over msg, else 0. Rejects
|
||||
* malformed inputs (S >= L, an undecodable point) as invalid rather than
|
||||
* faulting. */
|
||||
int ed25519_verify(const uint8_t pubkey[32], const uint8_t *msg, size_t msg_len,
|
||||
const uint8_t sig[64]);
|
||||
|
||||
/* Derive the public key (compressed point A = [a]B) from a 32-byte
|
||||
* seed. seed must be real, uniformly random entropy -- see this file's
|
||||
* header comment; ed25519_keygen() does not check or generate it. */
|
||||
void ed25519_keygen(const uint8_t seed[32], uint8_t pubkey_out[32]);
|
||||
|
||||
/* Sign msg with the keypair derived from seed (the same seed passed to
|
||||
* ed25519_keygen() to obtain the matching public key). Deterministic
|
||||
* per RFC 8032 (the nonce is derived from seed + message, not fresh
|
||||
* randomness at sign time) -- only keygen needs real entropy, signing
|
||||
* needs none. */
|
||||
void ed25519_sign(const uint8_t seed[32], const uint8_t *msg, size_t msg_len,
|
||||
uint8_t sig_out[64]);
|
||||
|
||||
#endif /* ED25519_H */
|
||||
@@ -0,0 +1,226 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* elf64.h - ELF64 structures for kernel loading
|
||||
* Minimal ELF64 definitions for the UEFI loader
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_ELF64_H
|
||||
#define STARKERNEL_ELF64_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* ELF identification */
|
||||
#define EI_MAG0 0
|
||||
#define EI_MAG1 1
|
||||
#define EI_MAG2 2
|
||||
#define EI_MAG3 3
|
||||
#define EI_CLASS 4
|
||||
#define EI_DATA 5
|
||||
#define EI_VERSION 6
|
||||
#define EI_OSABI 7
|
||||
#define EI_ABIVERSION 8
|
||||
#define EI_PAD 9
|
||||
#define EI_NIDENT 16
|
||||
|
||||
/* ELF magic number */
|
||||
#define ELFMAG0 0x7F
|
||||
#define ELFMAG1 'E'
|
||||
#define ELFMAG2 'L'
|
||||
#define ELFMAG3 'F'
|
||||
|
||||
/* ELF class */
|
||||
#define ELFCLASS32 1
|
||||
#define ELFCLASS64 2
|
||||
|
||||
/* ELF data encoding */
|
||||
#define ELFDATA2LSB 1 /* Little-endian */
|
||||
#define ELFDATA2MSB 2 /* Big-endian */
|
||||
|
||||
/* ELF version */
|
||||
#define EV_CURRENT 1
|
||||
|
||||
/* ELF OS/ABI */
|
||||
#define ELFOSABI_NONE 0 /* UNIX System V ABI */
|
||||
|
||||
/* ELF types */
|
||||
#define ET_NONE 0
|
||||
#define ET_REL 1
|
||||
#define ET_EXEC 2
|
||||
#define ET_DYN 3
|
||||
#define ET_CORE 4
|
||||
|
||||
/* ELF machine types */
|
||||
#define EM_X86_64 62 /* AMD x86-64 */
|
||||
#define EM_AARCH64 183 /* ARM AARCH64 */
|
||||
#define EM_RISCV 243 /* RISC-V */
|
||||
|
||||
/* Program header types */
|
||||
#define PT_NULL 0
|
||||
#define PT_LOAD 1
|
||||
#define PT_DYNAMIC 2
|
||||
#define PT_INTERP 3
|
||||
#define PT_NOTE 4
|
||||
#define PT_SHLIB 5
|
||||
#define PT_PHDR 6
|
||||
#define PT_TLS 7
|
||||
|
||||
/* Program header flags */
|
||||
#define PF_X 0x1 /* Execute */
|
||||
#define PF_W 0x2 /* Write */
|
||||
#define PF_R 0x4 /* Read */
|
||||
|
||||
/* Section header types */
|
||||
#define SHT_NULL 0
|
||||
#define SHT_PROGBITS 1
|
||||
#define SHT_SYMTAB 2
|
||||
#define SHT_STRTAB 3
|
||||
#define SHT_RELA 4
|
||||
#define SHT_HASH 5
|
||||
#define SHT_DYNAMIC 6
|
||||
#define SHT_NOTE 7
|
||||
#define SHT_NOBITS 8
|
||||
#define SHT_REL 9
|
||||
#define SHT_SHLIB 10
|
||||
#define SHT_DYNSYM 11
|
||||
|
||||
/* Relocation types (x86_64) */
|
||||
#define R_X86_64_NONE 0
|
||||
#define R_X86_64_64 1
|
||||
#define R_X86_64_PC32 2
|
||||
#define R_X86_64_RELATIVE 8
|
||||
#define R_X86_64_32 10
|
||||
#define R_X86_64_32S 11
|
||||
#define R_X86_64_PLT32 4
|
||||
|
||||
/* Relocation types (aarch64) */
|
||||
#define R_AARCH64_NONE 0
|
||||
#define R_AARCH64_ABS64 257
|
||||
#define R_AARCH64_RELATIVE 1027
|
||||
|
||||
/* Relocation types (riscv64) */
|
||||
#define R_RISCV_NONE 0
|
||||
#define R_RISCV_64 2
|
||||
#define R_RISCV_RELATIVE 3
|
||||
|
||||
/* ELF64 types */
|
||||
typedef uint64_t Elf64_Addr;
|
||||
typedef uint64_t Elf64_Off;
|
||||
typedef uint16_t Elf64_Half;
|
||||
typedef uint32_t Elf64_Word;
|
||||
typedef int32_t Elf64_Sword;
|
||||
typedef uint64_t Elf64_Xword;
|
||||
typedef int64_t Elf64_Sxword;
|
||||
|
||||
/* ELF64 header */
|
||||
typedef struct {
|
||||
unsigned char e_ident[EI_NIDENT]; /* ELF identification */
|
||||
Elf64_Half e_type; /* Object file type */
|
||||
Elf64_Half e_machine; /* Machine type */
|
||||
Elf64_Word e_version; /* Object file version */
|
||||
Elf64_Addr e_entry; /* Entry point address */
|
||||
Elf64_Off e_phoff; /* Program header offset */
|
||||
Elf64_Off e_shoff; /* Section header offset */
|
||||
Elf64_Word e_flags; /* Processor-specific flags */
|
||||
Elf64_Half e_ehsize; /* ELF header size */
|
||||
Elf64_Half e_phentsize; /* Size of program header entry */
|
||||
Elf64_Half e_phnum; /* Number of program header entries */
|
||||
Elf64_Half e_shentsize; /* Size of section header entry */
|
||||
Elf64_Half e_shnum; /* Number of section header entries */
|
||||
Elf64_Half e_shstrndx; /* Section name string table index */
|
||||
} Elf64_Ehdr;
|
||||
|
||||
/* ELF64 program header */
|
||||
typedef struct {
|
||||
Elf64_Word p_type; /* Segment type */
|
||||
Elf64_Word p_flags; /* Segment flags */
|
||||
Elf64_Off p_offset; /* Segment file offset */
|
||||
Elf64_Addr p_vaddr; /* Segment virtual address */
|
||||
Elf64_Addr p_paddr; /* Segment physical address */
|
||||
Elf64_Xword p_filesz; /* Segment size in file */
|
||||
Elf64_Xword p_memsz; /* Segment size in memory */
|
||||
Elf64_Xword p_align; /* Segment alignment */
|
||||
} Elf64_Phdr;
|
||||
|
||||
/* ELF64 section header */
|
||||
typedef struct {
|
||||
Elf64_Word sh_name; /* Section name (string table index) */
|
||||
Elf64_Word sh_type; /* Section type */
|
||||
Elf64_Xword sh_flags; /* Section flags */
|
||||
Elf64_Addr sh_addr; /* Section virtual addr at execution */
|
||||
Elf64_Off sh_offset; /* Section file offset */
|
||||
Elf64_Xword sh_size; /* Section size in bytes */
|
||||
Elf64_Word sh_link; /* Link to another section */
|
||||
Elf64_Word sh_info; /* Additional section information */
|
||||
Elf64_Xword sh_addralign; /* Section alignment */
|
||||
Elf64_Xword sh_entsize; /* Entry size if section holds table */
|
||||
} Elf64_Shdr;
|
||||
|
||||
/* ELF64 relocation with addend */
|
||||
typedef struct {
|
||||
Elf64_Addr r_offset; /* Address */
|
||||
Elf64_Xword r_info; /* Relocation type and symbol index */
|
||||
Elf64_Sxword r_addend; /* Addend */
|
||||
} Elf64_Rela;
|
||||
|
||||
typedef struct {
|
||||
Elf64_Word st_name;
|
||||
unsigned char st_info;
|
||||
unsigned char st_other;
|
||||
Elf64_Half st_shndx;
|
||||
Elf64_Addr st_value;
|
||||
Elf64_Xword st_size;
|
||||
} Elf64_Sym;
|
||||
|
||||
/* ELF64 relocation without addend */
|
||||
typedef struct {
|
||||
Elf64_Addr r_offset; /* Address */
|
||||
Elf64_Xword r_info; /* Relocation type and symbol index */
|
||||
} Elf64_Rel;
|
||||
|
||||
/* Extract relocation symbol and type from r_info */
|
||||
#define ELF64_R_SYM(i) ((i) >> 32)
|
||||
#define ELF64_R_TYPE(i) ((i) & 0xffffffffL)
|
||||
#define ELF64_R_INFO(s,t) (((Elf64_Xword)(s) << 32) + ((Elf64_Xword)(t) & 0xffffffffL))
|
||||
|
||||
#endif /* STARKERNEL_ELF64_H */
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
This file is part of the StarForth project.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
You may obtain a copy of the License at:
|
||||
https://github.com/star.4th@proton.me/StarForth/LICENSE.txt
|
||||
|
||||
This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
express or implied, including but not limited to the warranties of
|
||||
merchantability, fitness for a particular purpose, and noninfringement.
|
||||
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* elf_loader.h - ELF64 kernel loader interface
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_ELF_LOADER_H
|
||||
#define STARKERNEL_ELF_LOADER_H
|
||||
|
||||
#include "elf64.h"
|
||||
|
||||
/*
|
||||
* Load and relocate the StarKernel ELF binary
|
||||
*
|
||||
* @param elf_data Pointer to ELF file in memory
|
||||
* @param elf_size Size of ELF file in bytes (unused but for future validation)
|
||||
* @param entry_out Output: kernel entry point address
|
||||
* @return 1 on success, 0 on failure
|
||||
*/
|
||||
int elf_load_kernel(const uint8_t *elf_data, uint64_t elf_size,
|
||||
Elf64_Addr *entry_out);
|
||||
|
||||
#endif /* STARKERNEL_ELF_LOADER_H */
|
||||
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* fdt.h - Minimal flattened-devicetree reader
|
||||
*
|
||||
* Just enough of the Devicetree Specification v0.4 §5 to pull values out of
|
||||
* the blob the UEFI firmware publishes under EFI_DTB_TABLE_GUID, or that a
|
||||
* native (non-UEFI) boot entry passes directly. Read-only, no allocation, no
|
||||
* tree construction — it walks the structure block each call, which is fine
|
||||
* for the handful of boot-time lookups the kernel needs.
|
||||
*
|
||||
* Deliberately not a general devicetree library. Added for punch-list item
|
||||
* 0.3 (riscv64 timebase-frequency); extended (FABRIC-3.md §IV.3/§V.3,
|
||||
* 2026-09-04) with node-scoped lookup, for exactly the case this header
|
||||
* originally flagged as a future need (item 0.6's aarch64 GIC) plus its
|
||||
* real, concrete consumers as of this pass: the Raspberry Pi 5's UART/
|
||||
* mailbox register addresses (native boot, no ACPI) and the Milk-V Mars's
|
||||
* real PLIC base address (currently hardcoded to QEMU-virt's own value,
|
||||
* `arch/riscv64/plic.c`'s own doc comment already warned this isn't
|
||||
* assumed stable across configurations).
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_FDT_H
|
||||
#define STARKERNEL_FDT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Test whether @p fdt points at a valid flattened devicetree.
|
||||
*
|
||||
* Checks the 0xd00dfeed magic and that the structure and strings blocks lie
|
||||
* inside totalsize. Does not validate the token stream.
|
||||
*
|
||||
* @param fdt Candidate blob; NULL is safe and returns 0.
|
||||
* @return 1 if the header is usable, 0 otherwise.
|
||||
*/
|
||||
int fdt_valid(const void* fdt);
|
||||
|
||||
/**
|
||||
* @brief Find the first property with @p name anywhere in the tree.
|
||||
*
|
||||
* Scans the structure block in document order and returns the first match
|
||||
* regardless of which node it belongs to. That is sufficient for properties
|
||||
* which are uniform across a machine (timebase-frequency being the case this
|
||||
* was written for) and is *not* sufficient for anything node-scoped.
|
||||
*
|
||||
* @param fdt Blob, already checked with @c fdt_valid().
|
||||
* @param name Property name, NUL-terminated.
|
||||
* @param len_out Receives the property length in bytes; may be NULL.
|
||||
* @return Pointer to the property value inside @p fdt, or NULL if not found.
|
||||
* The value is big-endian as stored in the blob.
|
||||
*/
|
||||
const void* fdt_find_prop(const void* fdt, const char* name, uint32_t* len_out);
|
||||
|
||||
/**
|
||||
* @brief Read a single-cell (32-bit) property by name.
|
||||
*
|
||||
* Convenience over @c fdt_find_prop() that also handles the big-endian
|
||||
* conversion. Fails if the property is absent or not exactly 4 bytes.
|
||||
*
|
||||
* @param fdt Blob, already checked with @c fdt_valid().
|
||||
* @param name Property name, NUL-terminated.
|
||||
* @param out Receives the host-order value on success; untouched on failure.
|
||||
* @return 1 on success, 0 on failure.
|
||||
*/
|
||||
int fdt_prop_u32(const void* fdt, const char* name, uint32_t* out);
|
||||
|
||||
/**
|
||||
* @brief Find the first node whose "compatible" property matches @p compatible.
|
||||
*
|
||||
* "compatible" is a NUL-separated list of strings (DT spec §2.3.1) — matches
|
||||
* if @p compatible equals any one entry in the list, not just the whole
|
||||
* property verbatim. Scans the whole tree in document order; the first
|
||||
* matching node wins if more than one exists.
|
||||
*
|
||||
* @param fdt Blob, already checked with @c fdt_valid().
|
||||
* @param compatible Compatible string to match, NUL-terminated.
|
||||
* @return An opaque handle to the matched node, for use with
|
||||
* @c fdt_find_prop_in_node() only (not a raw offset or a pointer
|
||||
* to anything else meaningful) — or NULL if no node matches.
|
||||
*/
|
||||
const void* fdt_find_node_by_compatible(const void* fdt, const char* compatible);
|
||||
|
||||
/**
|
||||
* @brief Find the first node whose "device_type" property equals @p type.
|
||||
*
|
||||
* Some standard nodes (`/memory` per DT spec §3.4) are identified by
|
||||
* `device_type`, not `compatible` — unlike `compatible`, `device_type` is a
|
||||
* single NUL-terminated string, not a list, so this matches the whole
|
||||
* property value rather than scanning entries within it. Scans the whole
|
||||
* tree in document order; the first matching node wins if more than one
|
||||
* exists.
|
||||
*
|
||||
* @param fdt Blob, already checked with @c fdt_valid().
|
||||
* @param type device_type value to match, NUL-terminated.
|
||||
* @return An opaque handle to the matched node, for use with
|
||||
* @c fdt_find_prop_in_node() only — or NULL if no node matches.
|
||||
*/
|
||||
const void* fdt_find_node_by_device_type(const void* fdt, const char* type);
|
||||
|
||||
/**
|
||||
* @brief Find the first node whose own name matches @p name.
|
||||
*
|
||||
* Node names follow the DT spec §2.2.1 `name[@unit-address]` convention —
|
||||
* matches if @p name equals the node's name up to (not including) an `@`
|
||||
* suffix, or the whole name if there is none. For a singleton node with
|
||||
* no unit address (`/reserved-memory` being the concrete case this was
|
||||
* added for), this is an exact match. Scans the whole tree in document
|
||||
* order; the first matching node wins if more than one exists.
|
||||
*
|
||||
* @param fdt Blob, already checked with @c fdt_valid().
|
||||
* @param name Node name to match, NUL-terminated, no `@` suffix.
|
||||
* @return An opaque handle to the matched node, for use with
|
||||
* @c fdt_find_prop_in_node() / @c fdt_next_child_node() only —
|
||||
* or NULL if no node matches.
|
||||
*/
|
||||
const void* fdt_find_node_by_name(const void* fdt, const char* name);
|
||||
|
||||
/**
|
||||
* @brief Iterate the direct children of one node.
|
||||
*
|
||||
* Pass @p prev_child as NULL to get the first child; pass a previous
|
||||
* result back in to get the next one. Stops (returns NULL) once there are
|
||||
* no more children. Skips over each child's own descendants correctly
|
||||
* (so a child with grandchildren doesn't confuse the scan), but does not
|
||||
* itself descend into them — only direct children of @p parent are ever
|
||||
* returned.
|
||||
*
|
||||
* @param fdt Blob, already checked with @c fdt_valid().
|
||||
* @param parent Handle from one of the `fdt_find_node_by_*()`
|
||||
* functions.
|
||||
* @param prev_child NULL for the first child, or a handle previously
|
||||
* returned by this function for @p parent to
|
||||
* continue from.
|
||||
* @return Handle to the next direct child, for use with
|
||||
* @c fdt_find_prop_in_node() / @c fdt_next_child_node() only —
|
||||
* or NULL once @p parent's children are exhausted.
|
||||
*/
|
||||
const void* fdt_next_child_node(const void* fdt, const void* parent,
|
||||
const void* prev_child);
|
||||
|
||||
/**
|
||||
* @brief Find a property by name, scoped to one node.
|
||||
*
|
||||
* Like @c fdt_find_prop(), but scans only @p node's own direct properties
|
||||
* (as returned by @c fdt_find_node_by_compatible()) — stops at the first
|
||||
* child node or the end of @p node's property list, never descends into
|
||||
* children, never continues into a sibling. This is the difference that
|
||||
* matters for a property name like "reg", which is not unique across the
|
||||
* tree the way "timebase-frequency" (the whole reason @c fdt_find_prop()
|
||||
* was originally sufficient) happens to be.
|
||||
*
|
||||
* @param fdt Blob, already checked with @c fdt_valid().
|
||||
* @param node Handle from @c fdt_find_node_by_compatible(); NULL is
|
||||
* safe and returns NULL (propagates a failed node lookup
|
||||
* without a separate caller-side check).
|
||||
* @param name Property name, NUL-terminated.
|
||||
* @param len_out Receives the property length in bytes; may be NULL.
|
||||
* @return Pointer to the property value inside @p fdt, or NULL if not
|
||||
* found (or if @p node is NULL). The value is big-endian as
|
||||
* stored in the blob.
|
||||
*/
|
||||
const void* fdt_find_prop_in_node(const void* fdt, const void* node,
|
||||
const char* name, uint32_t* len_out);
|
||||
|
||||
#endif /* STARKERNEL_FDT_H */
|
||||
@@ -0,0 +1,60 @@
|
||||
/* fe25519.h -- arithmetic mod p = 2^255-19, for Ed25519.
|
||||
*
|
||||
* Five-limb representation, uniform radix 2^51 (value =
|
||||
* sum(limb[i] * 2^(51*i)), limb i in roughly [0, 2^51)) -- the standard
|
||||
* Ed25519 reference layout (matches the widely-reviewed "amd64-51"-style
|
||||
* implementations), not something invented for this codebase. 51*5=255
|
||||
* exactly, so unlike a mismatched limb-count/width choice, the reduction
|
||||
* constant is the clean 2^255 mod p = 19 with no extra scaling.
|
||||
*
|
||||
* Uses __int128 for multiply-accumulate (product of two ~51-bit limbs is
|
||||
* up to ~102 bits, summed across up to 5 terms per bucket -- needs a
|
||||
* wide type). Confirmed safe in this kernel's freestanding -nostdlib
|
||||
* build by direct toolchain testing (gcc/aarch64-linux-gnu-gcc/
|
||||
* riscv64-linux-gnu-gcc, matching Makefile.starkernel's exact flags):
|
||||
* __int128 multiply, add, and shift-by-constant all compile with zero
|
||||
* undefined symbols on all three target architectures. This is DIFFERENT
|
||||
* from __int128 DIVISION, which src/starkernel/arch/amd64/timer.c
|
||||
* documents as broken (needs libgcc's __udivti3, undefined in this
|
||||
* -nostdlib build) -- this file never divides __int128 values, so that
|
||||
* restriction doesn't apply here. An earlier draft of this file avoided
|
||||
* __int128 entirely (10 limbs, radix 2^26, int64_t only) out of
|
||||
* over-caution before this was checked directly; abandoned after running
|
||||
* into real bugs from that scheme's own complexity, not from __int128
|
||||
* unavailability -- __int128 was never actually the constraint once
|
||||
* verified.
|
||||
*/
|
||||
#ifndef FE25519_H
|
||||
#define FE25519_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* int64_t, not uint64_t: fe25519_sub produces negative intermediate
|
||||
* limbs (a[i] - b[i] can be < 0 for a specific limb even when the total
|
||||
* value a-b, mod p, is what's wanted), and fe25519_carry() relies on
|
||||
* arithmetic right shift to propagate negative "borrows" the same way
|
||||
* it propagates positive carries -- proven correct by the property-based
|
||||
* host test, not just assumed. */
|
||||
typedef struct { int64_t v[5]; } fe25519;
|
||||
|
||||
void fe25519_0(fe25519 *r);
|
||||
void fe25519_1(fe25519 *r);
|
||||
void fe25519_copy(fe25519 *r, const fe25519 *a);
|
||||
void fe25519_add(fe25519 *r, const fe25519 *a, const fe25519 *b);
|
||||
void fe25519_sub(fe25519 *r, const fe25519 *a, const fe25519 *b);
|
||||
void fe25519_neg(fe25519 *r, const fe25519 *a);
|
||||
void fe25519_mul(fe25519 *r, const fe25519 *a, const fe25519 *b);
|
||||
void fe25519_sq(fe25519 *r, const fe25519 *a);
|
||||
void fe25519_invert(fe25519 *r, const fe25519 *a);
|
||||
void fe25519_mul_small(fe25519 *r, const fe25519 *a, uint32_t c);
|
||||
|
||||
/* Pack to 32 little-endian bytes (fully reduced mod p) / unpack from same. */
|
||||
void fe25519_pack(uint8_t out[32], const fe25519 *a);
|
||||
void fe25519_unpack(fe25519 *r, const uint8_t in[32]);
|
||||
|
||||
/* 1 if a == b (as field elements, after full reduction), else 0. */
|
||||
int fe25519_eq(const fe25519 *a, const fe25519 *b);
|
||||
/* Parity of the fully-reduced value's low bit (used for point decompression's sign bit). */
|
||||
int fe25519_parity(const fe25519 *a);
|
||||
|
||||
#endif /* FE25519_H */
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* framebuffer.h — UEFI GOP framebuffer driver interface
|
||||
*
|
||||
* Pixel format: BGRX32 (PixelBlueGreenRedReserved8BitPerColor),
|
||||
* the dominant format for QEMU virt GOP. RGBX32 is also supported.
|
||||
*
|
||||
* Color values throughout this API are packed as 0x00RRGGBB.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_FRAMEBUFFER_H
|
||||
#define STARKERNEL_FRAMEBUFFER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "uefi.h"
|
||||
|
||||
/* Pixel format of the GOP framebuffer */
|
||||
typedef enum {
|
||||
FB_PIXEL_BGRX32 = 0, /* PixelBlueGreenRedReserved8BitPerColor (default) */
|
||||
FB_PIXEL_RGBX32 = 1, /* PixelRedGreenBlueReserved8BitPerColor */
|
||||
} FbPixelFormat;
|
||||
|
||||
/* Pack / unpack 24-bit 0x00RRGGBB color */
|
||||
#define FB_RGB(r, g, b) \
|
||||
(((uint32_t)(r) << 16) | ((uint32_t)(g) << 8) | (uint32_t)(b))
|
||||
#define FB_R(c) (((uint32_t)(c) >> 16) & 0xFFu)
|
||||
#define FB_G(c) (((uint32_t)(c) >> 8) & 0xFFu)
|
||||
#define FB_B(c) ( (uint32_t)(c) & 0xFFu)
|
||||
|
||||
/* Standard ANSI 16-color palette (indices 0–15) */
|
||||
extern const uint32_t FB_ANSI_PALETTE[16] __attribute__((visibility("hidden")));
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Lifecycle
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Initialize the framebuffer from UEFI bootloader GOP data.
|
||||
* Must be called before any other fb_* function.
|
||||
* fmt: pixel layout reported by GOP (default FB_PIXEL_BGRX32).
|
||||
*/
|
||||
void fb_init(const FramebufferInfo *info, FbPixelFormat fmt);
|
||||
|
||||
/** Returns 1 if the framebuffer has been successfully initialized. */
|
||||
int fb_is_available(void);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Geometry queries
|
||||
* --------------------------------------------------------------------- */
|
||||
uint32_t fb_width(void);
|
||||
uint32_t fb_height(void);
|
||||
|
||||
/** Effective character cell width/height in pixels (8/16 × scale factor). */
|
||||
uint32_t fb_cell_w(void);
|
||||
uint32_t fb_cell_h(void);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Pixel-level primitives
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/** Write a single pixel. Out-of-bounds writes are silently ignored. */
|
||||
void fb_put_pixel(uint32_t x, uint32_t y, uint32_t rgb);
|
||||
|
||||
/** Fill a rectangle with a solid color. */
|
||||
void fb_fill_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h,
|
||||
uint32_t rgb);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Glyph rendering (8 × 16 font cells)
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Draw one 8×16 character glyph at pixel position (px, py).
|
||||
* fg / bg are packed 0x00RRGGBB colors.
|
||||
*/
|
||||
void fb_draw_glyph(uint32_t px, uint32_t py, uint8_t ch,
|
||||
uint32_t fg, uint32_t bg);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Boot diagnostic
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* One-time boot diagnostic (FABRIC-0.md item 4.3.1): fills each raster corner
|
||||
* with a distinct solid color so a screendump reveals orientation. Not part
|
||||
* of the Console drawing fabric -- diagnostic-only.
|
||||
*/
|
||||
void fb_draw_orientation_test(void);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Scrolling
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Scroll the whole framebuffer up by `pixel_rows` pixel rows. The vacated
|
||||
* rows at the bottom are filled with bg. Takes an explicit pixel-row count
|
||||
* (not a hardcoded 8x16-cell assumption) so callers with a non-8x16 cell
|
||||
* height (e.g. TTF mode, 24px) pass their own cell height directly --
|
||||
* same convention fb_scroll_rect() below already uses, for the same reason
|
||||
* (a caller-computed char_rows * fixed-16px assumption drifts out of sync
|
||||
* with the text model's own row height in TTF mode, and that drift
|
||||
* compounds with every scroll).
|
||||
*/
|
||||
void fb_scroll_rows(uint32_t pixel_rows, uint32_t bg);
|
||||
|
||||
/**
|
||||
* Scroll a sub-rectangle of the framebuffer up by `pixel_rows` pixel rows
|
||||
* (FABRIC-0.md item 4.4t: box-confined REPL scrolling). Unlike fb_scroll_rows()
|
||||
* (whole-framebuffer), this is bounded to
|
||||
* [x, x+w) x [y, y+h). Both take an explicit pixel-row count so callers with
|
||||
* a non-8x16 cell height (e.g. TTF mode) pass their own cell height directly.
|
||||
* Pixels outside the rect are untouched. The vacated rows at the bottom of
|
||||
* the rect are filled with bg.
|
||||
*/
|
||||
void fb_scroll_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h,
|
||||
uint32_t pixel_rows, uint32_t bg);
|
||||
|
||||
#endif /* STARKERNEL_FRAMEBUFFER_H */
|
||||
@@ -0,0 +1,15 @@
|
||||
# include/starkernel/freestanding/
|
||||
|
||||
Minimal libc-shim headers so shared VM code (`src/vm.c`, `src/word_source/`,
|
||||
etc.) can be compiled into the freestanding kernel build, which has no real
|
||||
libc.
|
||||
|
||||
- `assert.h`, `ctype.h`, `errno.h`, `inttypes.h`, `math.h`, `sched.h`,
|
||||
`signal.h`, `stdio.h`, `stdlib.h`, `string.h`, `time.h` — narrow,
|
||||
kernel-appropriate substitutes for the corresponding standard headers,
|
||||
implementing only what the shared VM code actually calls.
|
||||
- `sys/` — `sys/time.h`, `sys/types.h` shims.
|
||||
|
||||
These are intentionally minimal, not general-purpose libc replacements —
|
||||
extend only when a specific shared-code call site needs a symbol that
|
||||
isn't here yet.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user