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:
@@ -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
|
||||
@@ -0,0 +1,304 @@
|
||||
/*
|
||||
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.h - Init Capsule Architecture (M7.1)
|
||||
*
|
||||
* Content-addressed, immutable init capsules for VM birth.
|
||||
* See docs/lithosananke/M7.1.md for full specification.
|
||||
*
|
||||
* Key invariants:
|
||||
* - Exactly ONE production (p) INIT defines a baby VM
|
||||
* - capsule_id == content_hash (content-addressed)
|
||||
* - No shared/implicit base INITs
|
||||
* - DOMAIN is Mama-only, PERSONALITY is baby-only
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_H
|
||||
#define STARKERNEL_CAPSULE_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*===========================================================================
|
||||
* Constants
|
||||
*===========================================================================*/
|
||||
|
||||
/** Magic signatures */
|
||||
#define CAPSULE_DESC_MAGIC 0x53504143ULL /* 'CAPS' little-endian */
|
||||
#define CAPSULE_DIR_MAGIC 0x44504143ULL /* 'CAPD' little-endian */
|
||||
|
||||
/** Version */
|
||||
#define CAPSULE_VERSION_0 0
|
||||
|
||||
/** Limits */
|
||||
#define CAPSULE_MAX_COUNT 256
|
||||
|
||||
/** Maximum capsule name length (colon-separated path, null-terminated) */
|
||||
#define CAPSULE_NAME_MAX 512
|
||||
|
||||
/*===========================================================================
|
||||
* Hash Algorithm Enum
|
||||
*===========================================================================*/
|
||||
|
||||
typedef enum {
|
||||
CAPSULE_HASH_XXHASH64 = 0,
|
||||
CAPSULE_HASH_SHA256 = 1,
|
||||
CAPSULE_HASH_BLAKE3 = 2,
|
||||
} CapsuleHashAlg;
|
||||
|
||||
/*===========================================================================
|
||||
* Flags
|
||||
*===========================================================================*/
|
||||
|
||||
/** State flags */
|
||||
#define CAPSULE_FLAG_ACTIVE 0x00000001 /* Eligible for use */
|
||||
#define CAPSULE_FLAG_REVOKED 0x00000002 /* Birth-blocked forever */
|
||||
#define CAPSULE_FLAG_DEPRECATED 0x00000004 /* Eligible but discouraged */
|
||||
#define CAPSULE_FLAG_PINNED 0x00000008 /* Immune to GC */
|
||||
|
||||
/** Mode flags (D2: babies carry both) */
|
||||
#define CAPSULE_FLAG_PRODUCTION 0x00000010 /* (p) truth-bearing */
|
||||
#define CAPSULE_FLAG_EXPERIMENT 0x00000020 /* (e) workload only */
|
||||
|
||||
/** Mama init flag (exactly one capsule must have this) */
|
||||
#define CAPSULE_FLAG_MAMA_INIT 0x00000040 /* (m) Mama's init */
|
||||
|
||||
/** Contributor capsule flag (FABRIC-2.md §I.5, 2026-09-04) -- path-match
|
||||
* on capsules/contrib/, mirrors FLAG_MAMA_INIT's own exact-match pattern
|
||||
* in mkcapsule.c's flags_from_name(). Trust-tier enforcement (QEMU-vs-
|
||||
* real-hardware, decided in conversation) is a runtime check in
|
||||
* capsule_validate()'s callers, not encoded in this bit itself -- the
|
||||
* bit only marks "this capsule's provenance is a contributor, not this
|
||||
* project's own source," same as CAPSULE_FLAG_PRODUCTION/_EXPERIMENT
|
||||
* mark mode, not policy. */
|
||||
#define CAPSULE_FLAG_CONTRIB 0x00000080 /* (c) contributor-submitted */
|
||||
|
||||
/** Validate mode flags.
|
||||
* Mama: neither (p) nor (e) may be set.
|
||||
* Babies: at least one of (p) or (e) must be set (both is fine — D2). */
|
||||
#define CAPSULE_MODE_VALID(f) \
|
||||
((((f) & CAPSULE_FLAG_MAMA_INIT) != 0) ? \
|
||||
(!((f) & (CAPSULE_FLAG_PRODUCTION | CAPSULE_FLAG_EXPERIMENT))) : \
|
||||
(((f) & CAPSULE_FLAG_PRODUCTION) || ((f) & CAPSULE_FLAG_EXPERIMENT)))
|
||||
|
||||
/** Check if capsule is Mama's init */
|
||||
#define CAPSULE_IS_MAMA_INIT(f) \
|
||||
(((f) & CAPSULE_FLAG_MAMA_INIT) && ((f) & CAPSULE_FLAG_ACTIVE))
|
||||
|
||||
/** Birth eligibility: active and not revoked (flag type irrelevant — D2) */
|
||||
#define CAPSULE_BIRTH_ELIGIBLE(f) \
|
||||
(((f) & CAPSULE_FLAG_ACTIVE) && \
|
||||
!((f) & CAPSULE_FLAG_REVOKED))
|
||||
|
||||
/** DoE eligibility: experiment, active, not revoked */
|
||||
#define CAPSULE_DOE_ELIGIBLE(f) \
|
||||
(((f) & CAPSULE_FLAG_EXPERIMENT) && \
|
||||
((f) & CAPSULE_FLAG_ACTIVE) && \
|
||||
!((f) & CAPSULE_FLAG_REVOKED))
|
||||
|
||||
/*===========================================================================
|
||||
* Magic Field Packing
|
||||
*
|
||||
* bits 0..31 : 'CAPS' (0x53504143 little-endian)
|
||||
* bits 32..39 : version (0 for v0)
|
||||
* bits 40..47 : hashAlg (enum CapsuleHashAlg)
|
||||
* bits 48..63 : reserved (zero)
|
||||
*===========================================================================*/
|
||||
|
||||
#define CAPSULE_MAGIC_PACK(ver, alg) \
|
||||
(CAPSULE_DESC_MAGIC | ((uint64_t)(ver) << 32) | ((uint64_t)(alg) << 40))
|
||||
|
||||
#define CAPSULE_MAGIC_GET_SIG(m) ((uint32_t)((m) & 0xFFFFFFFFULL))
|
||||
#define CAPSULE_MAGIC_GET_VERSION(m) ((uint8_t)(((m) >> 32) & 0xFF))
|
||||
#define CAPSULE_MAGIC_GET_HASHALG(m) ((uint8_t)(((m) >> 40) & 0xFF))
|
||||
|
||||
/*===========================================================================
|
||||
* CapsuleDesc - Capsule Descriptor (64 bytes, cache-line aligned)
|
||||
*===========================================================================*/
|
||||
|
||||
typedef struct __attribute__((aligned(64))) {
|
||||
uint64_t magic; /* 0x00: 'CAPS' | ver | hashAlg | reserved */
|
||||
uint64_t capsule_id; /* 0x08: == content_hash (content-addressed) */
|
||||
uint64_t content_hash; /* 0x10: hash of payload bytes */
|
||||
uint64_t offset; /* 0x18: byte offset into payload arena */
|
||||
uint64_t length; /* 0x20: payload length in bytes */
|
||||
uint32_t flags; /* 0x28: state + policy bits */
|
||||
uint32_t owner_vm; /* 0x2C: 0 = mama, else child VM ID */
|
||||
uint64_t birth_count; /* 0x30: how many VMs born from this */
|
||||
uint64_t created_ns; /* 0x38: monotonic timestamp at registration */
|
||||
} CapsuleDesc; /* 0x40 = 64 bytes */
|
||||
|
||||
/*===========================================================================
|
||||
* CapsuleNameEntry - Capsule Name (parallel array to CapsuleDesc[])
|
||||
*
|
||||
* Indexed 1:1 with capsule_descriptors[]. Name is the full relative path
|
||||
* from the capsule root with '/' replaced by ':', e.g.:
|
||||
* "core:init.4th"
|
||||
* "experiments:doe-l8:init-l8-diverse.4th"
|
||||
* "production:myvm.4th"
|
||||
*===========================================================================*/
|
||||
|
||||
typedef struct {
|
||||
char name[CAPSULE_NAME_MAX]; /* null-terminated, colon-separated path */
|
||||
} CapsuleNameEntry;
|
||||
|
||||
/*===========================================================================
|
||||
* CapsuleSigEntry - Ed25519 signature (parallel array to CapsuleDesc[])
|
||||
*
|
||||
* Milestone 6 (Phase 8): each capsule's payload bytes (the same bytes
|
||||
* content_hash already covers), signed by mkcapsule at build time with
|
||||
* the snakeoil intermediate's private key. has_sig=0 for a capsule built
|
||||
* before this feature existed or otherwise unsigned -- a real, distinct
|
||||
* state, not "signature is all-zero bytes" (which sig[64] full of 0x00
|
||||
* would otherwise look ambiguous with). Indexed 1:1 with
|
||||
* capsule_descriptors[], same convention as CapsuleNameEntry.
|
||||
*===========================================================================*/
|
||||
|
||||
typedef struct {
|
||||
uint8_t sig[64]; /* raw Ed25519 R||S, see ed25519_sign()/ed25519_verify() */
|
||||
uint8_t has_sig; /* 0 = no signature present, 1 = sig[] is real */
|
||||
uint8_t _pad[7];
|
||||
} CapsuleSigEntry;
|
||||
|
||||
/*===========================================================================
|
||||
* CapsuleDirHeader - Directory Header
|
||||
*===========================================================================*/
|
||||
|
||||
typedef struct {
|
||||
uint64_t magic; /* 'CAPD' | ver | reserved */
|
||||
uint64_t arena_base; /* phys or virt base of payload arena */
|
||||
uint64_t arena_size; /* bytes */
|
||||
uint32_t desc_count; /* current number of descriptors */
|
||||
uint32_t desc_capacity; /* max (fixed at compile time for Phase A) */
|
||||
uint32_t name_count; /* == desc_count, kept separate for validation */
|
||||
uint32_t reserved; /* padding */
|
||||
uint64_t dir_hash; /* hash of descriptor table (for parity) */
|
||||
} CapsuleDirHeader;
|
||||
|
||||
/*===========================================================================
|
||||
* Validation
|
||||
*===========================================================================*/
|
||||
|
||||
typedef enum {
|
||||
CAPSULE_VALID = 0,
|
||||
CAPSULE_ERR_BAD_MAGIC,
|
||||
CAPSULE_ERR_BAD_VERSION,
|
||||
CAPSULE_ERR_BAD_HASH_ALG,
|
||||
CAPSULE_ERR_BOUNDS,
|
||||
CAPSULE_ERR_MODE_INVALID,
|
||||
CAPSULE_ERR_REVOKED_ACTIVE,
|
||||
CAPSULE_ERR_HASH_MISMATCH,
|
||||
CAPSULE_ERR_NULL_PTR,
|
||||
} CapsuleValidateResult;
|
||||
|
||||
/**
|
||||
* capsule_validate - Validate a capsule descriptor
|
||||
*
|
||||
* @param desc Capsule descriptor to validate
|
||||
* @param arena_base Base address of payload arena
|
||||
* @param arena_size Size of payload arena in bytes
|
||||
* @param verify_hash If true, recompute and compare content hash
|
||||
* @return CAPSULE_VALID on success, error code otherwise
|
||||
*/
|
||||
CapsuleValidateResult capsule_validate(
|
||||
const CapsuleDesc *desc,
|
||||
const uint8_t *arena_base,
|
||||
uint64_t arena_size,
|
||||
int verify_hash
|
||||
);
|
||||
|
||||
/**
|
||||
* capsule_validate_result_str - Get string for validation result
|
||||
*/
|
||||
const char *capsule_validate_result_str(CapsuleValidateResult result);
|
||||
|
||||
/*===========================================================================
|
||||
* Lookup
|
||||
*===========================================================================*/
|
||||
|
||||
/**
|
||||
* capsule_find_by_id - Find capsule by content hash ID
|
||||
*
|
||||
* @param dir Directory header
|
||||
* @param descs Descriptor array
|
||||
* @param id Capsule ID (content hash) to find
|
||||
* @return Pointer to descriptor, or NULL if not found
|
||||
*/
|
||||
const CapsuleDesc *capsule_find_by_id(
|
||||
const CapsuleDirHeader *dir,
|
||||
const CapsuleDesc *descs,
|
||||
uint64_t id
|
||||
);
|
||||
|
||||
/**
|
||||
* capsule_find_by_name - Find capsule by colon-separated name
|
||||
*
|
||||
* @param dir Directory header
|
||||
* @param descs Descriptor array
|
||||
* @param names Name entry array (parallel to descs)
|
||||
* @param name Colon-separated capsule name, e.g. "core:init.4th"
|
||||
* @return Pointer to descriptor, or NULL if not found
|
||||
*/
|
||||
const CapsuleDesc *capsule_find_by_name(
|
||||
const CapsuleDirHeader *dir,
|
||||
const CapsuleDesc *descs,
|
||||
const CapsuleNameEntry *names,
|
||||
const char *name
|
||||
);
|
||||
|
||||
/**
|
||||
* capsule_get_payload - Get pointer to capsule payload bytes
|
||||
*
|
||||
* @param desc Capsule descriptor
|
||||
* @param arena_base Base address of payload arena
|
||||
* @return Pointer to payload bytes, or NULL on error
|
||||
*/
|
||||
const uint8_t *capsule_get_payload(
|
||||
const CapsuleDesc *desc,
|
||||
const uint8_t *arena_base
|
||||
);
|
||||
|
||||
/**
|
||||
* capsule_find_mama_init - Find the Mama init capsule
|
||||
*
|
||||
* Searches the descriptor array for the capsule with CAPSULE_FLAG_MAMA_INIT.
|
||||
* There must be exactly one such capsule.
|
||||
*
|
||||
* @param dir Directory header
|
||||
* @param descs Descriptor array
|
||||
* @return Pointer to Mama's init descriptor, or NULL if not found
|
||||
*/
|
||||
const CapsuleDesc *capsule_find_mama_init(
|
||||
const CapsuleDirHeader *dir,
|
||||
const CapsuleDesc *descs
|
||||
);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_H */
|
||||
@@ -0,0 +1,347 @@
|
||||
/*
|
||||
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_birth.h - VM Birth Protocol (M7.1)
|
||||
*
|
||||
* Functions for birthing VMs from capsules:
|
||||
* - Mama init: Execute core/init.4th to establish Mama's PERSONALITY
|
||||
* - Baby birth: Create new VM from (p) capsule
|
||||
* - Experiment run: Execute (e) capsule on Mama
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_CAPSULE_BIRTH_H
|
||||
#define STARKERNEL_CAPSULE_BIRTH_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "starkernel/capsule.h"
|
||||
#include "starkernel/capsule_run.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*===========================================================================
|
||||
* VM Execution Hook
|
||||
*
|
||||
* The birth protocol needs to execute FORTH code on a VM.
|
||||
* This hook is provided by the VM layer.
|
||||
*===========================================================================*/
|
||||
|
||||
/**
|
||||
* VM execution function type
|
||||
*
|
||||
* @param vm_ctx Opaque pointer to VM context
|
||||
* @param code FORTH source code to execute
|
||||
* @param code_len Length of code in bytes
|
||||
* @return 0 on success, non-zero on error
|
||||
*/
|
||||
typedef int (*CapsuleExecFn)(void *vm_ctx, const char *code, uint64_t code_len);
|
||||
|
||||
/**
|
||||
* Dictionary hash function type
|
||||
*
|
||||
* @param vm_ctx Opaque pointer to VM context
|
||||
* @return 64-bit hash of dictionary state
|
||||
*/
|
||||
typedef uint64_t (*CapsuleDictHashFn)(void *vm_ctx);
|
||||
|
||||
/**
|
||||
* VM allocation function type (for baby birth)
|
||||
*
|
||||
* @return Opaque pointer to new VM context, or NULL on failure
|
||||
*/
|
||||
typedef void *(*CapsuleVMAllocFn)(void);
|
||||
|
||||
/**
|
||||
* capsule_birth_set_hooks - Configure VM execution hooks
|
||||
*
|
||||
* Must be called before any birth operations.
|
||||
*
|
||||
* @param exec_fn Function to execute FORTH code on VM
|
||||
* @param dict_hash_fn Function to compute dictionary hash
|
||||
* @param vm_alloc_fn Function to allocate new VM (for babies)
|
||||
*/
|
||||
void capsule_birth_set_hooks(
|
||||
CapsuleExecFn exec_fn,
|
||||
CapsuleDictHashFn dict_hash_fn,
|
||||
CapsuleVMAllocFn vm_alloc_fn
|
||||
);
|
||||
|
||||
/*===========================================================================
|
||||
* Mama Init
|
||||
*===========================================================================*/
|
||||
|
||||
/**
|
||||
* capsule_birth_mama - Execute Mama's init capsule
|
||||
*
|
||||
* Finds the MAMA_INIT capsule by flag, validates it, executes it on Mama's VM.
|
||||
*
|
||||
* @param mama_vm Mama's VM context
|
||||
* @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_birth_mama(
|
||||
void *mama_vm,
|
||||
const CapsuleDirHeader *dir,
|
||||
const CapsuleDesc *descs,
|
||||
const CapsuleNameEntry *names,
|
||||
const uint8_t *arena
|
||||
);
|
||||
|
||||
/*===========================================================================
|
||||
* Baby Birth
|
||||
*===========================================================================*/
|
||||
|
||||
/**
|
||||
* capsule_birth_baby - Birth a new VM from a named (p) capsule
|
||||
*
|
||||
* Finds the capsule by colon-separated name, validates it, allocates a new
|
||||
* VM, executes the capsule payload as IDENTITY, then (if present) executes
|
||||
* unit.4th from the baby's block space as PERSONALITY.
|
||||
*
|
||||
* @param capsule_name Colon-separated capsule name, e.g. "production:myvm.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
|
||||
* @param parent Who is birthing this VM (FABRIC-2.md §H.12 step 7) --
|
||||
* the caller's own VMUuid (e.g. vm->stadium_vm_id for
|
||||
* a FORTH word handler), recorded on the new VM's
|
||||
* Session.parent. Every current call site has one in
|
||||
* scope, directly or one level up; traced live rather
|
||||
* than assumed (checked all 6 call sites across
|
||||
* mama_forth_words.c/capsule_console.c/
|
||||
* capsule_runcap.c/capsule_wirebind.c).
|
||||
* @param skip_pki_sig 0 for every build-time capsule (the normal case --
|
||||
* checked against the compile-time-baked signature
|
||||
* array via capsule_get_signatures()). Non-zero only
|
||||
* for RUNCAP (FABRIC-2.md §F.6/F.18): a heap-built,
|
||||
* single-entry directory sourced from a user's own
|
||||
* thumbdrive has no entry in that array at all --
|
||||
* index 0 would silently compare against whatever
|
||||
* real capsule happens to occupy slot 0, which is
|
||||
* not a security check, just a guaranteed-wrong one.
|
||||
* Trust for that content comes from CERTVERIFY (a
|
||||
* separate root, the user's own Zuse-signed cert)
|
||||
* already having run before RUNCAP is ever called,
|
||||
* not from this flag -- this only skips a check that
|
||||
* was never meaningful for that content in the first
|
||||
* place. Deliberately a plain flag, not a new entry
|
||||
* point, so the policy is one call-site decision,
|
||||
* trivially reversible.
|
||||
* @param out_vm_id Output: assigned VM ID
|
||||
* @param out_vm_ctx Output: new VM context
|
||||
* @return CAPSULE_RUN_OK on success, error code otherwise
|
||||
*/
|
||||
CapsuleRunResult capsule_birth_baby(
|
||||
const char *capsule_name,
|
||||
const CapsuleDirHeader *dir,
|
||||
const CapsuleDesc *descs,
|
||||
const CapsuleNameEntry *names,
|
||||
const uint8_t *arena,
|
||||
VMUuid parent,
|
||||
int skip_pki_sig,
|
||||
VMUuid *out_vm_id,
|
||||
void **out_vm_ctx
|
||||
);
|
||||
|
||||
/*===========================================================================
|
||||
* Experiment Execution
|
||||
*===========================================================================*/
|
||||
|
||||
/**
|
||||
* capsule_run_experiment - Execute a named (e) capsule on Mama
|
||||
*
|
||||
* Finds the experiment capsule by colon-separated name, validates it,
|
||||
* executes it on Mama's VM without creating a new VM.
|
||||
*
|
||||
* @param mama_vm Mama's VM context
|
||||
* @param capsule_name Colon-separated capsule name, e.g. "experiments:doe-l8:init-l8-stable.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
|
||||
* @param out_run_id Output: assigned run ID
|
||||
* @return CAPSULE_RUN_OK on success, error code otherwise
|
||||
*/
|
||||
CapsuleRunResult capsule_run_experiment(
|
||||
void *mama_vm,
|
||||
const char *capsule_name,
|
||||
const CapsuleDirHeader *dir,
|
||||
const CapsuleDesc *descs,
|
||||
const CapsuleNameEntry *names,
|
||||
const uint8_t *arena,
|
||||
uint64_t *out_run_id
|
||||
);
|
||||
|
||||
/*===========================================================================
|
||||
* VM Hook Registration
|
||||
*===========================================================================*/
|
||||
|
||||
/**
|
||||
* capsule_vm_hooks_register - Wire concrete VM hooks into the capsule subsystem
|
||||
*
|
||||
* Registers capsule_exec_hook, capsule_dict_hash_hook, and capsule_vm_alloc_hook.
|
||||
* Must be called after vm_init() on Mama's VM and before any birth operations.
|
||||
*/
|
||||
void capsule_vm_hooks_register(void);
|
||||
|
||||
/*===========================================================================
|
||||
* VM Registry
|
||||
*===========================================================================*/
|
||||
|
||||
/**
|
||||
* capsule_vm_registry_init - Initialize VM registry
|
||||
*
|
||||
* Allocates Mama's registry node (VM 0, name "Hera") via kmalloc and
|
||||
* stores mama_vm_ptr so KILL can guard against destroying Mama.
|
||||
* Must be called after kmalloc_init().
|
||||
*
|
||||
* @param mama_vm_ptr Pointer to Mama's VM object (e.g. &sk_mama_vm)
|
||||
*/
|
||||
void capsule_vm_registry_init(void *mama_vm_ptr);
|
||||
|
||||
/**
|
||||
* capsule_vm_kill - Destroy a named VM and release all its resources
|
||||
*
|
||||
* Looks up the VM by name (case-insensitive). Hera (VM 0) cannot be
|
||||
* killed. If the VM is already DEAD the call is a no-op.
|
||||
* On success: vm_cleanup + sf_free, state → VM_STATE_DEAD, name cleared.
|
||||
*
|
||||
* @param name Symbolic name of the VM to kill (case-insensitive)
|
||||
* @return 0 on success (or already dead), -1 if not found or refused
|
||||
*/
|
||||
int capsule_vm_kill(const char *name);
|
||||
|
||||
/**
|
||||
* capsule_vm_registry_get - Get VM registry entry by ID
|
||||
*
|
||||
* @param vm_id VM ID to look up
|
||||
* @param out Output: registry entry copy
|
||||
* @return 0 on success, -1 if not found
|
||||
*/
|
||||
int capsule_vm_registry_get(VMUuid vm_id, VMRegistryEntry *out);
|
||||
|
||||
/**
|
||||
* capsule_vm_registry_count - Get number of registered VMs
|
||||
*/
|
||||
uint32_t capsule_vm_registry_count(void);
|
||||
|
||||
/**
|
||||
* capsule_vm_registry_get_by_index - Get registry entry by list position
|
||||
* (birth order, stable within a boot session -- the registry is
|
||||
* append-only). For enumeration (e.g. the idle-loop messaging pump,
|
||||
* FABRIC-2.md Phase C, 2026-08-28), where no vm_id is known up front.
|
||||
* Index range is [0, capsule_vm_registry_count()).
|
||||
*
|
||||
* @param index Zero-based position in birth order
|
||||
* @param out Output: registry entry copy
|
||||
* @return 0 on success, -1 if index is out of range
|
||||
*/
|
||||
int capsule_vm_registry_get_by_index(uint32_t index, VMRegistryEntry *out);
|
||||
|
||||
/**
|
||||
* capsule_vm_find_by_name - Find VM registry entry by symbolic name
|
||||
*
|
||||
* Case-sensitive. Returns the first match.
|
||||
*
|
||||
* @param name Symbolic VM name, e.g. "Hermes"
|
||||
* @param out Output: registry entry copy
|
||||
* @return 0 if found, -1 if not found
|
||||
*/
|
||||
int capsule_vm_find_by_name(const char *name, VMRegistryEntry *out);
|
||||
|
||||
/**
|
||||
* capsule_vm_find_by_name_nocase - Find VM registry entry by name (case-insensitive)
|
||||
*
|
||||
* Used by BIRTH for idempotency: prevents birthing a second VM with the
|
||||
* same name regardless of case differences.
|
||||
*
|
||||
* @param name Symbolic VM name (compared case-insensitively)
|
||||
* @param out Output: registry entry copy
|
||||
* @return 0 if found, -1 if not found
|
||||
*/
|
||||
int capsule_vm_find_by_name_nocase(const char *name, VMRegistryEntry *out);
|
||||
|
||||
/**
|
||||
* capsule_vm_set_state - Update a VM's state in the registry
|
||||
*
|
||||
* @param vm_id VM ID to update
|
||||
* @param state New VMState value
|
||||
*/
|
||||
void capsule_vm_set_state(VMUuid vm_id, uint32_t state);
|
||||
|
||||
/**
|
||||
* capsule_vm_set_pending_reap - FABRIC-3.md §XXVIII Stage 4 (2026-09-14):
|
||||
* mark vm_id for deferred teardown once it is no longer worth resuming --
|
||||
* see VMRegistryEntry.pending_reap's own doc comment for the full
|
||||
* rationale. No-op if vm_id isn't registered.
|
||||
*
|
||||
* @param vm_id VM ID to mark.
|
||||
* @param pending 1 to mark, 0 to clear (e.g. a re-attach of the same
|
||||
* identity before the switcher ever reaped it).
|
||||
*/
|
||||
void capsule_vm_set_pending_reap(VMUuid vm_id, int pending);
|
||||
|
||||
/**
|
||||
* capsule_vm_force_reap - FABRIC-3.md §XXVIII Stage 4 (2026-09-14):
|
||||
* unconditionally tear down vm_id regardless of VM_STATE_SWITCHED_OUT --
|
||||
* the one caller allowed to bypass capsule_vm_kill()'s own refusal there,
|
||||
* because this is called *by* the Stage 3 switcher itself (vm_core.c's
|
||||
* checkpoint, on noticing pending_reap set), at the one point that
|
||||
* genuinely knows the parked native-stack context will never be resumed.
|
||||
* Also releases the VM's own switch-signal slot
|
||||
* (sk_vm_switch_signal_unregister()) -- generic cleanup, independent of
|
||||
* whatever subsystem (WIREBIND today) set pending_reap in the first
|
||||
* place. No-op if vm_id isn't registered or is already DEAD.
|
||||
*
|
||||
* @param vm_id VM ID to reap.
|
||||
*/
|
||||
void capsule_vm_force_reap(VMUuid vm_id);
|
||||
|
||||
/**
|
||||
* capsule_vm_registry_set_name - Assign a symbolic name to a registered VM
|
||||
*
|
||||
* Truncates to VM_NAME_MAX-1 characters. No-op if vm_id not found.
|
||||
*
|
||||
* @param vm_id VM ID to name
|
||||
* @param name Symbolic name string
|
||||
*/
|
||||
void capsule_vm_registry_set_name(VMUuid vm_id, const char *name);
|
||||
|
||||
/**
|
||||
* capsule_vm_kill_all_nonmama - Kill every non-Mama VM in the registry.
|
||||
*
|
||||
* Sets halted, calls vm_cleanup + sf_free, marks state DEAD. Used by
|
||||
* Hera's BYE immediately before arch_cold_reset() to reap all children.
|
||||
*/
|
||||
void capsule_vm_kill_all_nonmama(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_CAPSULE_BIRTH_H */
|
||||
@@ -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.
|
||||
@@ -0,0 +1,6 @@
|
||||
/* Freestanding assert.h shim — asserts become no-ops in kernel builds. */
|
||||
#ifndef FREESTANDING_ASSERT_H
|
||||
#define FREESTANDING_ASSERT_H
|
||||
#define assert(expr) ((void)(expr))
|
||||
#define static_assert _Static_assert
|
||||
#endif /* FREESTANDING_ASSERT_H */
|
||||
@@ -0,0 +1,16 @@
|
||||
/* Freestanding ctype.h shim — character classification for kernel builds. */
|
||||
#ifndef FREESTANDING_CTYPE_H
|
||||
#define FREESTANDING_CTYPE_H
|
||||
static inline int isdigit(int c) { return c >= '0' && c <= '9'; }
|
||||
static inline int isspace(int c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f' || c == '\v'; }
|
||||
static inline int isalpha(int c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); }
|
||||
static inline int isalnum(int c) { return isalpha(c) || isdigit(c); }
|
||||
static inline int isupper(int c) { return c >= 'A' && c <= 'Z'; }
|
||||
static inline int islower(int c) { return c >= 'a' && c <= 'z'; }
|
||||
static inline int isprint(int c) { return c >= 0x20 && c < 0x7F; }
|
||||
static inline int isxdigit(int c) { return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); }
|
||||
static inline int toupper(int c) { return islower(c) ? c - 32 : c; }
|
||||
static inline int tolower(int c) { return isupper(c) ? c + 32 : c; }
|
||||
static inline int ispunct(int c) { return isprint(c) && !isalnum(c) && c != ' '; }
|
||||
static inline int iscntrl(int c) { return (unsigned)c < 0x20 || c == 0x7F; }
|
||||
#endif /* FREESTANDING_CTYPE_H */
|
||||
@@ -0,0 +1,10 @@
|
||||
/* Freestanding errno.h shim — no OS errno in kernel builds. */
|
||||
#ifndef FREESTANDING_ERRNO_H
|
||||
#define FREESTANDING_ERRNO_H
|
||||
static int _freestanding_errno = 0;
|
||||
#define errno _freestanding_errno
|
||||
#define EPERM 1
|
||||
#define ENOENT 2
|
||||
#define EINVAL 22
|
||||
#define ENOMEM 12
|
||||
#endif /* FREESTANDING_ERRNO_H */
|
||||
@@ -0,0 +1,25 @@
|
||||
/* Freestanding inttypes.h shim — PRI* format macros for kernel builds. */
|
||||
#ifndef FREESTANDING_INTTYPES_H
|
||||
#define FREESTANDING_INTTYPES_H
|
||||
#include <stdint.h>
|
||||
#define PRId8 "d"
|
||||
#define PRId16 "d"
|
||||
#define PRId32 "d"
|
||||
#define PRId64 "lld"
|
||||
#define PRIu8 "u"
|
||||
#define PRIu16 "u"
|
||||
#define PRIu32 "u"
|
||||
#define PRIu64 "llu"
|
||||
#define PRIx8 "x"
|
||||
#define PRIx16 "x"
|
||||
#define PRIx32 "x"
|
||||
#define PRIx64 "llx"
|
||||
#define PRIX8 "X"
|
||||
#define PRIX16 "X"
|
||||
#define PRIX32 "X"
|
||||
#define PRIX64 "llX"
|
||||
#define PRIi8 "d"
|
||||
#define PRIi16 "d"
|
||||
#define PRIi32 "d"
|
||||
#define PRIi64 "lld"
|
||||
#endif /* FREESTANDING_INTTYPES_H */
|
||||
@@ -0,0 +1,19 @@
|
||||
/* Freestanding math.h shim — floating-point math for kernel builds.
|
||||
* These functions are declared but should not be called; physics feedback
|
||||
* loops use Q48.16 integer arithmetic, not float. */
|
||||
#ifndef FREESTANDING_MATH_H
|
||||
#define FREESTANDING_MATH_H
|
||||
double sqrt(double x);
|
||||
double pow(double base, double exp);
|
||||
double fabs(double x);
|
||||
double ceil(double x);
|
||||
double floor(double x);
|
||||
double log(double x);
|
||||
double exp(double x);
|
||||
double sin(double x);
|
||||
double cos(double x);
|
||||
double atan2(double y, double x);
|
||||
#define HUGE_VAL __builtin_huge_val()
|
||||
#define NAN __builtin_nanf("")
|
||||
#define INFINITY __builtin_inff()
|
||||
#endif /* FREESTANDING_MATH_H */
|
||||
@@ -0,0 +1,5 @@
|
||||
/* Freestanding sched.h shim — no POSIX scheduling in kernel builds. */
|
||||
#ifndef FREESTANDING_SCHED_H
|
||||
#define FREESTANDING_SCHED_H
|
||||
static inline int sched_yield(void) { return 0; }
|
||||
#endif /* FREESTANDING_SCHED_H */
|
||||
@@ -0,0 +1,16 @@
|
||||
/* Freestanding signal.h shim — no POSIX signals in kernel builds. */
|
||||
#ifndef FREESTANDING_SIGNAL_H
|
||||
#define FREESTANDING_SIGNAL_H
|
||||
#define SIGINT 2
|
||||
#define SIGABRT 6
|
||||
#define SIGSEGV 11
|
||||
#define SIGTERM 15
|
||||
typedef void (*sighandler_t)(int);
|
||||
#define SIG_DFL ((sighandler_t)0)
|
||||
#define SIG_IGN ((sighandler_t)1)
|
||||
struct sigaction { sighandler_t sa_handler; int sa_flags; };
|
||||
#define SA_RESETHAND 0
|
||||
static inline sighandler_t signal(int sig, sighandler_t handler) { (void)sig; (void)handler; return SIG_DFL; }
|
||||
static inline int sigaction(int sig, const struct sigaction *act, struct sigaction *old)
|
||||
{ (void)sig; (void)act; (void)old; return 0; }
|
||||
#endif /* FREESTANDING_SIGNAL_H */
|
||||
@@ -0,0 +1,36 @@
|
||||
/* Freestanding stdio.h shim for aarch64 COFF kernel builds.
|
||||
* printf/fprintf/snprintf/vfprintf all provided by shim.c (route to console). */
|
||||
#ifndef FREESTANDING_STDIO_H
|
||||
#define FREESTANDING_STDIO_H
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
typedef struct { int _dummy; } FILE;
|
||||
#define stderr ((FILE *)0)
|
||||
#define stdout ((FILE *)1)
|
||||
#define stdin ((FILE *)2)
|
||||
int printf(const char *fmt, ...);
|
||||
int fprintf(FILE *stream, const char *fmt, ...);
|
||||
int vfprintf(FILE *stream, const char *fmt, va_list args);
|
||||
int snprintf(char *buf, size_t n, const char *fmt, ...);
|
||||
int vsnprintf(char *buf, size_t n, const char *fmt, va_list args);
|
||||
int puts(const char *s);
|
||||
int fputs(const char *s, FILE *stream);
|
||||
#define SEEK_SET 0
|
||||
#define SEEK_CUR 1
|
||||
#define SEEK_END 2
|
||||
int fflush(FILE *stream);
|
||||
int putchar(int c);
|
||||
int fseek(FILE *stream, long offset, int whence);
|
||||
long ftell(FILE *stream);
|
||||
int sscanf(const char *str, const char *fmt, ...);
|
||||
int getchar(void);
|
||||
int fgetc(FILE *stream);
|
||||
int fputc(int c, FILE *stream);
|
||||
char *fgets(char *s, int n, FILE *stream);
|
||||
FILE *fopen(const char *path, const char *mode);
|
||||
int fclose(FILE *stream);
|
||||
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
|
||||
int fscanf(FILE *stream, const char *fmt, ...);
|
||||
void rewind(FILE *stream);
|
||||
#endif /* FREESTANDING_STDIO_H */
|
||||
@@ -0,0 +1,18 @@
|
||||
/* Freestanding stdlib.h shim for aarch64 COFF kernel builds.
|
||||
* malloc/free/calloc/realloc provided by shim.c; abort by shim.c. */
|
||||
#ifndef FREESTANDING_STDLIB_H
|
||||
#define FREESTANDING_STDLIB_H
|
||||
#include <stddef.h>
|
||||
void *malloc(size_t size);
|
||||
void free(void *ptr);
|
||||
void *calloc(size_t n, size_t size);
|
||||
void *realloc(void *ptr, size_t size);
|
||||
void abort(void);
|
||||
void qsort(void *base, size_t nmemb, size_t size,
|
||||
int (*compar)(const void *, const void *));
|
||||
long strtol(const char *str, char **endptr, int base);
|
||||
unsigned long strtoul(const char *str, char **endptr, int base);
|
||||
long long strtoll(const char *str, char **endptr, int base);
|
||||
int atoi(const char *str);
|
||||
long atol(const char *str);
|
||||
#endif /* FREESTANDING_STDLIB_H */
|
||||
@@ -0,0 +1,19 @@
|
||||
/* Freestanding string.h shim for aarch64 COFF kernel builds.
|
||||
* Implementations are provided by src/starkernel/vm/host/shim.c. */
|
||||
#ifndef FREESTANDING_STRING_H
|
||||
#define FREESTANDING_STRING_H
|
||||
#include <stddef.h>
|
||||
void *memcpy(void *dest, const void *src, size_t n);
|
||||
void *memmove(void *dest, const void *src, size_t n);
|
||||
void *memset(void *s, int c, size_t n);
|
||||
int memcmp(const void *s1, const void *s2, size_t n);
|
||||
void *memchr(const void *s, int c, size_t n);
|
||||
size_t strlen(const char *s);
|
||||
char *strcpy(char *dest, const char *src);
|
||||
char *strncpy(char *dest, const char *src, size_t n);
|
||||
int strcmp(const char *s1, const char *s2);
|
||||
int strncmp(const char *s1, const char *s2, size_t n);
|
||||
char *strchr(const char *s, int c);
|
||||
char *strrchr(const char *s, int c);
|
||||
char *strerror(int errnum);
|
||||
#endif /* FREESTANDING_STRING_H */
|
||||
@@ -0,0 +1,4 @@
|
||||
# include/starkernel/freestanding/sys/
|
||||
|
||||
- `time.h`, `types.h` — minimal `sys/time.h`/`sys/types.h` shims for the
|
||||
freestanding kernel build. See `include/starkernel/freestanding/README.md`.
|
||||
@@ -0,0 +1,15 @@
|
||||
/* Freestanding sys/time.h shim — no gettimeofday in kernel builds. */
|
||||
#ifndef FREESTANDING_SYS_TIME_H
|
||||
#define FREESTANDING_SYS_TIME_H
|
||||
#include <stddef.h>
|
||||
typedef long time_t;
|
||||
typedef long suseconds_t;
|
||||
struct timeval {
|
||||
time_t tv_sec;
|
||||
suseconds_t tv_usec;
|
||||
};
|
||||
struct timezone { int tz_minuteswest; int tz_dsttime; };
|
||||
static inline int gettimeofday(struct timeval *tv, struct timezone *tz) {
|
||||
(void)tv; (void)tz; return 0;
|
||||
}
|
||||
#endif /* FREESTANDING_SYS_TIME_H */
|
||||
@@ -0,0 +1,12 @@
|
||||
/* Freestanding sys/types.h shim — POSIX types for kernel builds. */
|
||||
#ifndef FREESTANDING_SYS_TYPES_H
|
||||
#define FREESTANDING_SYS_TYPES_H
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
typedef long ssize_t;
|
||||
typedef long off_t;
|
||||
typedef unsigned int mode_t;
|
||||
typedef unsigned int uid_t;
|
||||
typedef unsigned int gid_t;
|
||||
typedef int pid_t;
|
||||
#endif /* FREESTANDING_SYS_TYPES_H */
|
||||
@@ -0,0 +1,11 @@
|
||||
/* Freestanding time.h shim — no POSIX time in kernel builds. */
|
||||
#ifndef FREESTANDING_TIME_H
|
||||
#define FREESTANDING_TIME_H
|
||||
#include <stddef.h>
|
||||
#include "sys/time.h" /* provides time_t, suseconds_t, struct timeval */
|
||||
struct timespec {
|
||||
time_t tv_sec;
|
||||
long tv_nsec;
|
||||
};
|
||||
int nanosleep(const struct timespec *req, struct timespec *rem);
|
||||
#endif /* FREESTANDING_TIME_H */
|
||||
@@ -0,0 +1,9 @@
|
||||
# include/starkernel/hal/
|
||||
|
||||
Top-level hardware-abstraction-layer interface for LithosAnanke.
|
||||
|
||||
- `hal.h` — declares the HAL entry points implemented by
|
||||
`src/starkernel/hal/{hal,console,memory,host_services,framebuffer,vt100}.c`:
|
||||
console I/O, framebuffer/VT100 output, memory queries, and host-service
|
||||
shims consumed by the rest of the kernel so architecture-specific
|
||||
details stay out of `kernel_main.c`.
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Kernel HAL surface exposed to the StarForth VM integration layer.
|
||||
*
|
||||
* Hosted builds never include this header; kernel code funnels all VM-facing
|
||||
* services through here so the VM core does not depend on kernel internals.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_HAL_HAL_H
|
||||
#define STARKERNEL_HAL_HAL_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct VMHostServices;
|
||||
|
||||
void sk_hal_init(void);
|
||||
void *sk_hal_alloc(size_t size, size_t align);
|
||||
void sk_hal_free(void *ptr);
|
||||
uint64_t sk_hal_time_ns(void);
|
||||
uint64_t sk_hal_heartbeat_ticks(void);
|
||||
size_t sk_hal_console_write(const char *buf, size_t len);
|
||||
int sk_hal_console_putc(int c);
|
||||
void sk_hal_panic(const char *message) __attribute__((noreturn));
|
||||
bool sk_hal_is_executable_ptr(const void *ptr);
|
||||
const struct VMHostServices *sk_hal_host_services(void);
|
||||
void sk_hal_whitelist_exec_region(uint64_t start, uint64_t end, const char *name);
|
||||
void sk_hal_freeze_exec_range(void);
|
||||
|
||||
/* Section address getters - use inline asm to avoid GOT indirection issues with -fPIC */
|
||||
uint64_t sk_hal_text_start(void);
|
||||
uint64_t sk_hal_text_end(void);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_HAL_HAL_H */
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* hal_memory.h - HAL memory allocation wrappers
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_HAL_MEMORY_H
|
||||
#define STARKERNEL_HAL_MEMORY_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
void *hal_mem_alloc(size_t size);
|
||||
void *hal_mem_alloc_aligned(size_t size, size_t align);
|
||||
void hal_mem_free(void *ptr);
|
||||
|
||||
#endif /* STARKERNEL_HAL_MEMORY_H */
|
||||
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* homeblocks_sig.h - Home-blocks drive signature format (FABRIC-2.md,
|
||||
* Milestone 4, Phase 8 kickoff; relocated + GPT dropped §F.13/§F.8,
|
||||
* 2026-08-28)
|
||||
*
|
||||
* Identifies and authenticates a physical thumb drive as a legitimate
|
||||
* LithosAnanke home-blocks drive, before any write path touches it.
|
||||
* Lives at forth-block HOMEBLOCKS_SIG_START_FBLOCK (devblock 1) of the raw
|
||||
* device -- GPT partitioning was decided against permanently (§F.8): this
|
||||
* is the real, final on-disk location, not an interim stand-in. Devblock 0
|
||||
* is left alone for the block-subsystem's own generic 'STFR'/v2 volume
|
||||
* header (block_subsystem.h) -- the two formats would otherwise collide
|
||||
* (§F.13, found while scoping BMAPREAD).
|
||||
*
|
||||
* Mirrors two existing precedents exactly, not invented fresh:
|
||||
* - CAPSULE_MAGIC_PACK's bit-packed magic (starkernel/capsule.h)
|
||||
* - blk_volume_meta_t's magic+version+fields+pad-to-4096 structural
|
||||
* convention (block_subsystem.h)
|
||||
*
|
||||
* Deliberately narrow in scope: this header identifies/authenticates the
|
||||
* DRIVE only. It reserves offset/size pointers to where the cert
|
||||
* (CERTVERIFY, §F.7) and this identity's own personality/init source
|
||||
* (RUNCAP/MINT, §F.6/§F.8) attach, so this format doesn't need revisiting
|
||||
* when those get built -- it does not itself decide their content.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_HOMEBLOCKS_SIG_H
|
||||
#define STARKERNEL_HOMEBLOCKS_SIG_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*===========================================================================
|
||||
* Magic Field Packing -- same bit layout convention as CAPSULE_MAGIC_PACK
|
||||
*
|
||||
* bits 0..31 : 'LAHB' (0x4248414C little-endian) -- LithosAnanke Home Blocks
|
||||
* bits 32..39 : version (0 for v0)
|
||||
* bits 40..63 : reserved (zero)
|
||||
*===========================================================================*/
|
||||
|
||||
#define HOMEBLOCKS_SIG_MAGIC 0x4248414CULL /* 'LAHB', same little-endian ASCII
|
||||
* packing as CAPSULE_DESC_MAGIC's 'CAPS' */
|
||||
#define HOMEBLOCKS_SIG_VERSION_0 0
|
||||
|
||||
#define HOMEBLOCKS_SIG_PACK(ver) \
|
||||
(HOMEBLOCKS_SIG_MAGIC | ((uint64_t)(ver) << 32))
|
||||
|
||||
#define HOMEBLOCKS_SIG_GET_MAGIC(m) ((uint32_t)((m) & 0xFFFFFFFFULL))
|
||||
#define HOMEBLOCKS_SIG_GET_VERSION(m) ((uint8_t)(((m) >> 32) & 0xFF))
|
||||
|
||||
/* Where this header actually lives on a home-blocks drive: forth-block 4
|
||||
* (devblock 1), NOT devblock 0 -- FABRIC-2.md §F.13, decided 2026-08-28.
|
||||
* Devblock 0 is reserved for the block-subsystem's own generic 'STFR'/v2
|
||||
* volume header (block_subsystem.c); the two formats collide if both try
|
||||
* to occupy devblock 0 of the same raw device. GPT is permanently dropped
|
||||
* (§F.8) -- this is not an interim stand-in pending a GPT parser, it's the
|
||||
* real, final location. */
|
||||
#define HOMEBLOCKS_SIG_START_FBLOCK 4u
|
||||
|
||||
/*===========================================================================
|
||||
* homeblocks_sig_t - drive signature header (exactly one 4KiB devblock)
|
||||
*===========================================================================*/
|
||||
|
||||
typedef struct {
|
||||
uint64_t magic; /* HOMEBLOCKS_SIG_PACK(...) */
|
||||
uint8_t drive_uuid[16]; /* Unique per-mint instance id -- Phase 8 mints multiple
|
||||
* distinct drives, needs something to tell them apart. */
|
||||
uint64_t minted_time_ns; /* Monotonic timestamp at mint time. */
|
||||
uint64_t metadata_devblocks; /* Size of the metadata region at the start of this raw
|
||||
* device (sig header + cert + identity-source regions),
|
||||
* in 4KiB devblocks -- everything past this is the owning
|
||||
* identity's own general block-storage pool directly (§F.6
|
||||
* decision 3), no partition boundary involved. */
|
||||
|
||||
uint32_t cert_offset; /* Devblock offset from this device's start where this
|
||||
* identity's Zuse-signed cert blob starts (§F.7); 0 = not
|
||||
* yet minted. */
|
||||
uint32_t cert_devblocks; /* Size reserved for the cert blob, in devblocks. */
|
||||
|
||||
uint32_t identity_src_offset; /* Devblock offset where this identity's own record
|
||||
* starts (RUNCAP/MINT, FABRIC-2.md §F.6/§F.8): first
|
||||
* devblock is a user_identity_seed_t, remainder is raw
|
||||
* FORTH personality/init source. Renamed from
|
||||
* blockmap_offset -- BMAPFMT (§F.4) repurposed blk_meta_t
|
||||
* instead of a centralized block-map, making the original
|
||||
* field unnecessary; this reuses the same reserved bytes
|
||||
* rather than adding new ones. 0 = not yet minted. */
|
||||
uint32_t identity_src_devblocks; /* Size reserved for the identity record, in devblocks. */
|
||||
|
||||
uint64_t hdr_crc; /* REAL from day one, not a placeholder like
|
||||
* blk_volume_meta_t's "unused yet" hdr_crc -- this header's
|
||||
* whole job is gating a warn-and-refuse security check
|
||||
* against blank/foreign/unrecognized media, so the crc has
|
||||
* to actually work. Computed over every field above this
|
||||
* one; callers must fill every other field before computing
|
||||
* or verifying it. */
|
||||
|
||||
/* Padding to keep the header exactly one 4KiB devblock. */
|
||||
uint8_t _pad[4096 - (
|
||||
8 + /* magic */
|
||||
16 + /* drive_uuid */
|
||||
8 + /* minted_time_ns */
|
||||
8 + /* metadata_devblocks */
|
||||
4 + 4 + /* cert_offset, cert_devblocks */
|
||||
4 + 4 + /* identity_src_offset, identity_src_devblocks */
|
||||
8 /* hdr_crc */
|
||||
)];
|
||||
} homeblocks_sig_t;
|
||||
|
||||
/* C99-portable compile-time size assertion (no _Static_assert -- that's C11),
|
||||
* same discipline stadium.h's own header-size checks already use. */
|
||||
typedef char homeblocks_sig_size_check[(sizeof(homeblocks_sig_t) == 4096) ? 1 : -1];
|
||||
|
||||
/*===========================================================================
|
||||
* Signature check (FABRIC-2.md, Milestone 4)
|
||||
*===========================================================================*/
|
||||
|
||||
typedef enum {
|
||||
HOMEBLOCKS_SIG_OK = 0, /* magic, version, and crc all check out */
|
||||
HOMEBLOCKS_SIG_BLANK, /* magic does not match -- blank or foreign media */
|
||||
HOMEBLOCKS_SIG_BAD_VERSION, /* magic matches, version unrecognized */
|
||||
HOMEBLOCKS_SIG_BAD_CRC, /* magic+version match, crc fails -- corrupt or tampered */
|
||||
HOMEBLOCKS_SIG_READ_ERROR /* could not read from the device at all */
|
||||
} homeblocks_sig_result_t;
|
||||
|
||||
/* Forward-declared, not included here -- avoids a hard dependency from this
|
||||
* small format header onto blkio.h's full device/vtable machinery for
|
||||
* callers that only need the struct layout (e.g. a future minting tool). */
|
||||
struct blkio_dev;
|
||||
|
||||
/*
|
||||
* homeblocks_sig_check - Read and verify the drive signature header.
|
||||
*
|
||||
* Reads 4 consecutive 1KB "forth blocks" (dev->read()'s own unit) starting
|
||||
* at sig_start_fblock into a local 4KB buffer and interprets it as a
|
||||
* homeblocks_sig_t. Deliberately takes the starting block as a plain
|
||||
* parameter rather than resolving it internally, even though every real
|
||||
* caller now passes the same fixed HOMEBLOCKS_SIG_START_FBLOCK (GPT was
|
||||
* dropped, §F.8) -- keeps this function's own job (verify a signature
|
||||
* given a location) separate from callers deciding what that location is.
|
||||
*
|
||||
* @param dev Open block device to read from.
|
||||
* @param sig_start_fblock First of 4 consecutive forth-blocks holding the
|
||||
* 4KB header -- HOMEBLOCKS_SIG_START_FBLOCK for
|
||||
* every real caller today.
|
||||
* @param out_sig On HOMEBLOCKS_SIG_OK, populated with the verified
|
||||
* header. Left unspecified on any other result.
|
||||
* @return HOMEBLOCKS_SIG_OK, or the specific reason for refusal.
|
||||
*/
|
||||
homeblocks_sig_result_t homeblocks_sig_check(struct blkio_dev *dev,
|
||||
uint32_t sig_start_fblock,
|
||||
homeblocks_sig_t *out_sig);
|
||||
|
||||
/*
|
||||
* homeblocks_sig_compute_crc - CRC-64 over every field of `sig` up to but
|
||||
* not including hdr_crc itself and the trailing padding -- the same
|
||||
* boundary homeblocks_sig_check() verifies against and any future minting
|
||||
* code must use when writing a fresh header. Exposed publicly since both
|
||||
* directions (check and future mint) 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 homeblocks_sig_compute_crc(const homeblocks_sig_t *sig);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_HOMEBLOCKS_SIG_H */
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* i8042.h - PS/2 keyboard controller interface (amd64 only)
|
||||
*
|
||||
* Item 4.3.5 (FABRIC-0.md §27.5). Interrupt-driven only — no polling of the
|
||||
* status port (0x64) anywhere in this path. Groundwork only: this captures
|
||||
* and prints raw scancodes. Scancode-to-keycode translation and a consumer
|
||||
* API belong to the REPL keyboard-input work noted in FABRIC-0.md, not here.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_I8042_H
|
||||
#define STARKERNEL_I8042_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* IDT vector the I/O APIC delivers legacy IRQ1 to. */
|
||||
#define I8042_KEYBOARD_VECTOR 0x21
|
||||
|
||||
/**
|
||||
* Enable IRQ1 delivery in the i8042 controller's command byte. Assumes the
|
||||
* controller was already brought up by firmware (OVMF) — does not run the
|
||||
* 0xAA self-test or the full two-port init sequence, since that is more
|
||||
* than this item's scope requires.
|
||||
*/
|
||||
void i8042_init(void);
|
||||
|
||||
/**
|
||||
* Drain any byte sitting in the output buffer right before unmasking IRQ1.
|
||||
* Edge-triggered lines only assert on a rising edge; if OBF is already set
|
||||
* (from real wall-clock time elapsing between i8042_init() and unmask
|
||||
* while OBF was already high), there is no edge left to fire on, ever.
|
||||
*/
|
||||
void i8042_drain_stale(void);
|
||||
|
||||
/* Count of real keyboard IRQs serviced since boot (diagnostic). */
|
||||
extern volatile uint32_t g_i8042_isr_count;
|
||||
|
||||
/**
|
||||
* Called from the keyboard IRQ dispatch path (interrupts.c) on every
|
||||
* I8042_KEYBOARD_VECTOR interrupt. Trivial by design: reads the scancode
|
||||
* from port 0x60 (must always be read, or the controller never clears
|
||||
* OBF and stops delivering further interrupts) and pushes it onto a small
|
||||
* ring buffer. No printing, no translation, no other work — that all
|
||||
* happens outside interrupt context via i8042_pop_scancode(). Does not
|
||||
* issue apic_eoi() — the caller does.
|
||||
*/
|
||||
void i8042_handle_irq(void);
|
||||
|
||||
/**
|
||||
* Pop one raw scancode off the ring buffer, from non-interrupt context.
|
||||
* Single producer (the ISR), single reader (this function) — same shape
|
||||
* already established safe on one hart elsewhere in this tree (§21.1).
|
||||
*
|
||||
* @param out Written with the popped scancode on success.
|
||||
* @return 1 if a scancode was popped, 0 if the buffer was empty.
|
||||
*/
|
||||
int i8042_pop_scancode(uint8_t *out);
|
||||
|
||||
#endif /* STARKERNEL_I8042_H */
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* ioapic.h - I/O APIC interface (amd64 only)
|
||||
*
|
||||
* Item 4.3.5 (FABRIC-0.md §27.5): no I/O APIC driver existed anywhere in this
|
||||
* tree before this item. The Local APIC (apic.h) self-interrupts for the
|
||||
* timer and needs no routing; any *legacy* IRQ (i8042 keyboard's IRQ1
|
||||
* included) requires the I/O APIC to redirect it to a Local APIC vector.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_IOAPIC_H
|
||||
#define STARKERNEL_IOAPIC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Locate and map the I/O APIC via the ACPI MADT (parsed from @p acpi_rsdp).
|
||||
* Also captures any Interrupt Source Override entries so legacy ISA IRQs
|
||||
* with a non-default GSI/polarity/trigger mapping route correctly.
|
||||
*
|
||||
* @param acpi_rsdp BootInfo->acpi_table (RSDP), or NULL.
|
||||
* @return 0 on success, -1 if no MADT/I/O APIC entry was found.
|
||||
*/
|
||||
int ioapic_init(void *acpi_rsdp);
|
||||
|
||||
/**
|
||||
* Program a redirection entry for a legacy ISA IRQ, initially masked.
|
||||
* Honors an Interrupt Source Override for @p isa_irq if the MADT had one
|
||||
* (different GSI, polarity, or trigger mode); otherwise uses the ISA
|
||||
* default (GSI == isa_irq, active-high, edge-triggered) per the ACPI
|
||||
* specification's "conforms to bus" default.
|
||||
*
|
||||
* @param isa_irq Legacy IRQ number (0-15).
|
||||
* @param vector IDT vector to deliver (e.g. 0x21).
|
||||
* @param dest_apic_id Destination Local APIC ID (see apic_id()).
|
||||
* @return 0 on success, -1 if ioapic_init() has not succeeded.
|
||||
*/
|
||||
int ioapic_route_legacy_irq(uint8_t isa_irq, uint8_t vector, uint8_t dest_apic_id);
|
||||
|
||||
/**
|
||||
* Clear the mask bit on a previously routed legacy IRQ's redirection entry.
|
||||
* Call only after ioapic_route_legacy_irq() has programmed it.
|
||||
*/
|
||||
void ioapic_unmask_legacy_irq(uint8_t isa_irq);
|
||||
|
||||
#endif /* STARKERNEL_IOAPIC_H */
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* kernel_args.h — boot command-line argument structure and defaults
|
||||
*
|
||||
* KernelArgs is populated by the UEFI loader from LoadOptions (or the
|
||||
* one-shot StarForthBootArgs NVRAM variable) and carried to kernel_main
|
||||
* via BootInfo.args. All fields have safe defaults so the kernel boots
|
||||
* normally when no arguments are present.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_KERNEL_ARGS_H
|
||||
#define STARKERNEL_KERNEL_ARGS_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* ---- Buffer size -------------------------------------------------------- */
|
||||
/* Shared by BootInfo.args.raw, the NVRAM variable payload, and cmdline.c. */
|
||||
#define KERNEL_ARGS_CMDLINE_MAX 512
|
||||
|
||||
/* ---- Defaults ----------------------------------------------------------- */
|
||||
#define KARGS_DEFAULT_STACK_SIZE (2ULL * 1024ULL * 1024ULL) /* 2 MB */
|
||||
#define KARGS_DEFAULT_HEAP_SIZE (2ULL * 1024ULL * 1024ULL * 1024ULL) /* 2 GB */
|
||||
#define KARGS_DEFAULT_LOG_LEVEL 2 /* warn — quiet by default; --log-level=info/debug re-enables ECW word-execution tracing */
|
||||
#define KARGS_DEFAULT_RUN_DOE 0
|
||||
|
||||
/* ---- Log level constants ------------------------------------------------ */
|
||||
#define KARGS_LOG_DEBUG 0
|
||||
#define KARGS_LOG_INFO 1
|
||||
#define KARGS_LOG_WARN 2
|
||||
#define KARGS_LOG_ERROR 3
|
||||
|
||||
/* ---- REBOOT escape hatch ------------------------------------------------ */
|
||||
/* Max consecutive REBOOT attempts before the escape hatch fires.
|
||||
* On the Nth boot the counter is checked; if >= REBOOT_MAX_TRIES the
|
||||
* REBOOT word drops to REPL instead of calling ResetSystem. */
|
||||
#define REBOOT_MAX_TRIES 3
|
||||
|
||||
/* ---- Struct ------------------------------------------------------------ */
|
||||
typedef struct {
|
||||
/* Memory sizing — 0 means "use default" */
|
||||
uint64_t stack_size; /* --stack=<N>[KMG] */
|
||||
uint64_t heap_size; /* --heap=<N>[KMG] */
|
||||
|
||||
/* Runtime behaviour */
|
||||
int run_doe; /* --doe (0 = off) */
|
||||
int log_level; /* --log-level=... (KARGS_LOG_*) */
|
||||
|
||||
/* Raw unparsed cmdline; always NUL-terminated */
|
||||
char raw[KERNEL_ARGS_CMDLINE_MAX];
|
||||
} KernelArgs;
|
||||
|
||||
#endif /* STARKERNEL_KERNEL_ARGS_H */
|
||||
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* kmalloc.h - Kernel heap allocator interface
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_KMALLOC_H
|
||||
#define STARKERNEL_KMALLOC_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
uint64_t total_bytes;
|
||||
uint64_t used_bytes;
|
||||
uint64_t peak_bytes;
|
||||
uint64_t free_bytes;
|
||||
} kmalloc_stats_t;
|
||||
|
||||
int kmalloc_init(uint64_t heap_size_bytes);
|
||||
int kmalloc_is_initialized(void);
|
||||
void *kmalloc(size_t size);
|
||||
void *kmalloc_aligned(size_t size, size_t align);
|
||||
void kfree(void *ptr);
|
||||
kmalloc_stats_t kmalloc_get_stats(void);
|
||||
uintptr_t kmalloc_heap_base_addr(void);
|
||||
uintptr_t kmalloc_heap_end_addr(void);
|
||||
|
||||
/* Debug/diagnostic probe -- free-list census (largest free block, count of
|
||||
free blocks, count of allocated blocks, total blocks). Kept as permanent
|
||||
infrastructure (FABRIC-3.md SXIII, 2026-09-10): a cheap O(n) walk over
|
||||
the free list, useful for any future heap-shape investigation, not just
|
||||
the one that introduced it. Not called anywhere in the normal boot path
|
||||
today -- callers add their own log_message() call sites when debugging. */
|
||||
void kmalloc_debug_census(size_t *out_largest_free, size_t *out_free_count,
|
||||
size_t *out_used_count, size_t *out_total_blocks);
|
||||
|
||||
/* Same precedent as kmalloc_debug_census() above -- sum of free-block bytes
|
||||
and used-block bytes across the whole free list. The pairing of the two
|
||||
(block-count census + byte-sum census) is what let FABRIC-3.md SXIII tell
|
||||
real heap corruption apart from ordinary fragmentation: fragmentation
|
||||
grows free_count while total_free_bytes stays flat; corruption drops
|
||||
both together. */
|
||||
void kmalloc_debug_census_bytes(size_t *out_total_free_bytes, size_t *out_total_used_bytes);
|
||||
|
||||
#endif /* STARKERNEL_KMALLOC_H */
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
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/log.h — Kernel-native logger for LithosAnanke
|
||||
*
|
||||
* Provides the same API as the hosted include/log.h so all kernel VM
|
||||
* code calling log_message() works without source changes.
|
||||
*
|
||||
* Output routes through console_puts() → UART → serial log.
|
||||
* Implementation lives in src/starkernel/vm/host/shim.c.
|
||||
*
|
||||
* This header shadows include/log.h for kernel TUs because
|
||||
* -Iinclude/starkernel precedes -Iinclude in KERNEL_CFLAGS and
|
||||
* LOADER_CFLAGS, so #include "log.h" finds this file first.
|
||||
*
|
||||
* No <stdio.h> dependency — freestanding safe.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_LOG_H
|
||||
#define STARKERNEL_LOG_H
|
||||
|
||||
/* Forward declaration — matches hosted log.h */
|
||||
struct VM;
|
||||
|
||||
/* Renamed from LOG_LINE_MAX: vm.h owns that name for the persistent
|
||||
* block-log line width (64, unrelated concept). This is the in-memory log
|
||||
* message-formatting line length; keeping a distinct name removes the
|
||||
* include-order collision that forced a fragile "vm.h before log.h"
|
||||
* convention across the kernel. */
|
||||
#ifndef LOG_MSG_LINE_MAX
|
||||
#define LOG_MSG_LINE_MAX 256
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Log levels — identical to the hosted enumeration so kernel VM code
|
||||
* compiled against either header produces compatible call sites.
|
||||
*/
|
||||
typedef enum {
|
||||
LOG_NONE = -1, /* Disable all logging */
|
||||
LOG_ERROR = 0, /* Errors only */
|
||||
LOG_WARN, /* Warnings and errors */
|
||||
LOG_INFO, /* Informational, warnings, and errors */
|
||||
LOG_TEST, /* Test results and all above */
|
||||
LOG_DEBUG /* Full verbosity */
|
||||
} LogLevel;
|
||||
|
||||
typedef enum {
|
||||
TEST_PASS = 0,
|
||||
TEST_FAIL,
|
||||
TEST_SKIP,
|
||||
TEST_ERROR
|
||||
} TestResult;
|
||||
|
||||
void log_set_level(LogLevel level);
|
||||
LogLevel log_get_level(void);
|
||||
void log_message(LogLevel level, const char *fmt, ...);
|
||||
void log_test_result(const char *word_name, TestResult result);
|
||||
|
||||
/* TODO: persistent logging via VM-backed ring buffer — stub for now */
|
||||
void log_set_vm(struct VM *vm);
|
||||
|
||||
#endif /* STARKERNEL_LOG_H */
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* log_attrib.h - "Which VM is currently executing" for log persistence
|
||||
* attribution (FABRIC-3.md §XXVI follow-on, Step 4, 2026-09-13)
|
||||
*
|
||||
* log_message() (shim.c) is a global function called from hundreds of
|
||||
* existing sites -- most with no VM in scope at all (boot, PCI, xHCI,
|
||||
* driver code). Rather than thread a VM parameter through every one of
|
||||
* those call sites (a large, invasive change for no benefit to the ones
|
||||
* that genuinely have no VM), vm_interpret() (vm_core.c, the single
|
||||
* dispatch entry point for ALL FORTH execution -- interactive lines,
|
||||
* LOAD'd block content, and every VM-EXEC/MSG-DELIVER dispatch into a
|
||||
* target VM) sets this at entry and restores the previous value at exit,
|
||||
* save/restore style so nested vm_interpret() calls (VM-EXEC dispatching
|
||||
* into a different VM's own dictionary, mid-interpret) attribute
|
||||
* correctly to whichever VM is actually running at the moment
|
||||
* log_message() fires -- not the outermost caller.
|
||||
*
|
||||
* NULL means "no VM is currently interpreting" -- boot sequence, PCI/
|
||||
* xHCI/driver code, anything running outside a vm_interpret() call frame.
|
||||
* log_message()'s own persistence hook (shim.c) treats NULL as the fixed
|
||||
* "HADES" pseudo-source, matching the console's own existing
|
||||
* "[HADES][LEVEL]" prefix convention for exactly this class of message.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_LOG_ATTRIB_H
|
||||
#define STARKERNEL_LOG_ATTRIB_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct VM;
|
||||
|
||||
/* vm_log_attributed_vm - The VM whose dictionary context is currently
|
||||
* executing, or NULL if none (see this header's own doc comment). */
|
||||
struct VM *vm_log_attributed_vm(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_LOG_ATTRIB_H */
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* log_region.h - Growable per-VM log-persistence ring on Artemis's own disk
|
||||
* (FABRIC-3.md §XXVI follow-on, Step 4, 2026-09-13)
|
||||
*
|
||||
* Records real log_message() output (INFO and above, independent of the
|
||||
* console's own current_level filter) so a run survives past the QEMU
|
||||
* serial log -- the motivating case is comparing bare-metal and QEMU
|
||||
* results directly as a DoE factor once bare-metal boot lands, where the
|
||||
* only artifact both platforms share is whatever got persisted to disk.
|
||||
*
|
||||
* Lives in the SAME top-of-device system-metadata fence artemis_sig_t and
|
||||
* Zuse's genesis marker/eligibility list already use
|
||||
* (block_subsystem.h's blk_meta_zone_read()/write(), devblock_from_top
|
||||
* addressing) -- reached the SAME way, deliberately NOT through
|
||||
* artemis_sig.c's own independent blkio_info()-based arithmetic. That
|
||||
* arithmetic exists only because artemis_sig_t must be discoverable on a
|
||||
* device that isn't attached yet (bus-agnostic discovery, repl.c's
|
||||
* idle-loop USB-MSC scan); this ring is only ever read or written once
|
||||
* Artemis's disk is already attached and formatted (LOG-APPEND runs
|
||||
* inside Artemis's own already-live dictionary context, dispatched by a
|
||||
* sender's MSG-SEND/MSG-TICK), so blk_meta_zone_*() -- which requires
|
||||
* exactly that already-attached state -- is the correct, simpler,
|
||||
* already-tested tool, not a limitation to work around.
|
||||
*
|
||||
* Fixed devblock_from_top allocation (NOT persisted in artemis_sig_t's own
|
||||
* log_region_offset/log_region_devblocks fields -- those stay at their
|
||||
* genesis-time value of 0/0, "informational only, control header below is
|
||||
* authoritative," documented in artemis_sig.h; two writers of the same
|
||||
* fact was rejected as unnecessary drift risk):
|
||||
* 64 -- artemis_sig_t itself (ARTEMIS_SIG_DEVBLOCK_FROM_TOP)
|
||||
* 65 -- this ring's control header (LOG_REGION_DEVBLOCK_FROM_TOP_BASE)
|
||||
* 66-96 -- this ring's slot devblocks, growable up to LOG_REGION_MAX_DEVBLOCKS
|
||||
* Chosen clear of Zuse's genesis marker (devblock_from_top=0) and
|
||||
* eligibility list (chains upward from 1, unbounded in code -- see
|
||||
* zuse_eligibility_list.h's own CORRECTION comment, updated alongside this
|
||||
* file, for the honest real-world headroom that leaves: devblocks 1-63,
|
||||
* ~8000 possible eligible identities before ever reaching 64).
|
||||
*
|
||||
* Ring granularity is one whole devblock-quarter (LOG_SLOT_SIZE, 1024
|
||||
* bytes = exactly one blkio forth-block) per record -- avoids any
|
||||
* partial-forth-block read-modify-write for the write itself (a slot
|
||||
* write is one aligned blkio_write()-equivalent-sized unit); the
|
||||
* surrounding devblock (4 slots) still needs a read-modify-write via
|
||||
* blk_meta_zone_read()/write() since that accessor's own unit is one full
|
||||
* 4 KiB devblock, but that cost is the same regardless of slot size.
|
||||
* Records are truncated to fit one slot rather than spanning multiple --
|
||||
* simple, real, and sufficient for what's actually persisted (see
|
||||
* vm_log_buffer.h's own tighter caps, chosen to fit a whole batch of
|
||||
* several records inside one VM-EXEC/MSG-SEND line, INPUT_BUFFER_SIZE=1025).
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_LOG_REGION_H
|
||||
#define STARKERNEL_LOG_REGION_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "starkernel/artemis_sig.h" /* ARTEMIS_SIG_DEVBLOCK_FROM_TOP */
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*===========================================================================
|
||||
* Fence allocation
|
||||
*===========================================================================*/
|
||||
|
||||
#define LOG_REGION_DEVBLOCK_FROM_TOP_BASE (ARTEMIS_SIG_DEVBLOCK_FROM_TOP + 1u) /* 65 */
|
||||
#define LOG_REGION_INITIAL_DEVBLOCKS 4u /* slot devblocks at first use, excludes control header */
|
||||
#define LOG_REGION_GROWTH_INCREMENT 4u
|
||||
#define LOG_REGION_MAX_DEVBLOCKS 32u /* ceiling -- devblock_from_top stays within [66,96] */
|
||||
|
||||
/* FABRIC-3.md §XXXII.4, 2026-09-15: level-aware eviction. Deliberately a
|
||||
* raw numeric threshold, not log.h's LogLevel enum -- this file stays
|
||||
* decoupled from log_message() entirely (see this header's own top-level
|
||||
* doc comment on why). Matches LogLevel's own ordering (LOG_ERROR=0,
|
||||
* LOG_WARN=1, LOG_INFO=2...): a slot at this level or lower is "protected"
|
||||
* -- eviction refuses to discard it for an incoming record above this
|
||||
* threshold, dropping the incoming record instead. */
|
||||
#define LOG_REGION_PROTECTED_MAX_LEVEL 1u /* LOG_WARN and below (ERROR, WARN) */
|
||||
|
||||
#define LOG_SLOTS_PER_DEVBLOCK 4u /* 4 x 1 KiB forth-blocks per 4 KiB devblock */
|
||||
#define LOG_SLOT_SIZE 1024u
|
||||
|
||||
/*===========================================================================
|
||||
* log_region_ctrl_t - ring control header, one devblock at
|
||||
* LOG_REGION_DEVBLOCK_FROM_TOP_BASE. head_slot/tail_slot/record_count are
|
||||
* the authoritative, live ring state -- nothing outside this header (not
|
||||
* even artemis_sig_t) needs to track it.
|
||||
*===========================================================================*/
|
||||
|
||||
#define LOG_REGION_MAGIC 0x474C474Cull /* 'LGLG' */
|
||||
#define LOG_REGION_VERSION_0 0
|
||||
|
||||
#define LOG_REGION_PACK(ver) \
|
||||
(LOG_REGION_MAGIC | ((uint64_t)(ver) << 32))
|
||||
#define LOG_REGION_GET_MAGIC(m) ((uint32_t)((m) & 0xFFFFFFFFull))
|
||||
#define LOG_REGION_GET_VERSION(m) ((uint8_t)(((m) >> 32) & 0xFF))
|
||||
|
||||
typedef struct {
|
||||
uint64_t magic; /* LOG_REGION_PACK(...) */
|
||||
uint32_t devblocks; /* current slot-area size, in devblocks (excludes this header) */
|
||||
uint32_t head_slot; /* index of the oldest live record */
|
||||
uint32_t tail_slot; /* index where the NEXT record will be written */
|
||||
uint32_t record_count; /* live records, <= devblocks * LOG_SLOTS_PER_DEVBLOCK */
|
||||
uint64_t hdr_crc; /* covers every field above this one */
|
||||
uint8_t _pad[4096 - (8 + 4 + 4 + 4 + 4 + 8)];
|
||||
} log_region_ctrl_t;
|
||||
|
||||
typedef char log_region_ctrl_size_check[(sizeof(log_region_ctrl_t) == 4096) ? 1 : -1];
|
||||
|
||||
/*===========================================================================
|
||||
* log_slot_t - one record, exactly one 1 KiB forth-block.
|
||||
*===========================================================================*/
|
||||
|
||||
#define LOG_SLOT_SOURCE_MAX 16u /* NUL-padded VM/source tag, e.g. "Hera", "HADES" */
|
||||
#define LOG_SLOT_MSG_MAX (LOG_SLOT_SIZE - 8u - 2u - 1u - LOG_SLOT_SOURCE_MAX) /* 997 */
|
||||
|
||||
/* Field order deliberate: uint64_t, uint16_t, uint8_t, then char arrays --
|
||||
* every fixed field lands on its natural alignment with zero compiler-
|
||||
* inserted padding (offsets 0, 8, 10, 11), so sizeof() == the hand-summed
|
||||
* byte count the static assert below checks, and LOG_SLOT_SIZE (1024,
|
||||
* already a multiple of 8) needs no trailing padding either. */
|
||||
typedef struct {
|
||||
uint64_t timestamp; /* shim.c's own KRELTSC-style relative tick */
|
||||
uint16_t msg_len; /* used length of msg[], <= LOG_SLOT_MSG_MAX (997, needs 16 bits) */
|
||||
uint8_t level; /* LogLevel */
|
||||
char source[LOG_SLOT_SOURCE_MAX];
|
||||
char msg[LOG_SLOT_MSG_MAX];
|
||||
} log_slot_t;
|
||||
|
||||
typedef char log_slot_size_check[(sizeof(log_slot_t) == LOG_SLOT_SIZE) ? 1 : -1];
|
||||
|
||||
/*===========================================================================
|
||||
* API
|
||||
*===========================================================================*/
|
||||
|
||||
/*
|
||||
* log_region_append - Write one record to the ring, growing it (within
|
||||
* LOG_REGION_MAX_DEVBLOCKS) or evicting the oldest record (ring full and
|
||||
* already at the growth ceiling) as needed. Initializes the ring on first
|
||||
* use (control header blank). Never calls log_message() or anything that
|
||||
* might (this runs inside Artemis's own dictionary context during message
|
||||
* delivery -- see this header's own note on why; a log call here could
|
||||
* recurse into this same append path via Artemis's own buffered flush).
|
||||
*
|
||||
* @param level LogLevel of this record.
|
||||
* @param timestamp Caller-supplied relative timestamp (same KRELTSC base
|
||||
* shim.c's own log_message() uses).
|
||||
* @param source VM/source tag, e.g. "Hera", "HADES", an identity name.
|
||||
* @param source_len Length of source (truncated to LOG_SLOT_SOURCE_MAX-1).
|
||||
* @param msg Message text (not NUL-terminated required).
|
||||
* @param msg_len Length of msg (truncated to LOG_SLOT_MSG_MAX).
|
||||
* @return 0 on success. 1 if this record was dropped BY DESIGN, not a
|
||||
* failure -- writing it would have evicted a higher-priority
|
||||
* record still in the ring (LOG_REGION_PROTECTED_MAX_LEVEL,
|
||||
* §XXXII.4); the ring is left completely unchanged. -1 on any
|
||||
* genuine read/write failure (ring left however the failed
|
||||
* operation left it -- blk_meta_zone_write() itself never
|
||||
* partially writes a devblock). Callers that treat any nonzero
|
||||
* return as an error must not conflate these two cases.
|
||||
*/
|
||||
int log_region_append(uint8_t level, uint64_t timestamp,
|
||||
const char *source, uint32_t source_len,
|
||||
const char *msg, uint32_t msg_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_LOG_REGION_H */
|
||||
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* pci.h — Generic PCI/PCIe subsystem for StarKernel
|
||||
*
|
||||
* Config space access:
|
||||
* amd64 — Type 1 port I/O (CF8/CFC); no memory mapping required.
|
||||
* others — ECAM via ACPI MCFG; UEFI identity-maps the ECAM window.
|
||||
*
|
||||
* BAR MMIO:
|
||||
* Call pci_map_bar() before the first MMIO access. On amd64 this
|
||||
* adds a VMM page-table mapping (cache-disabled); on other arches
|
||||
* the UEFI tables already cover the region and the call is a no-op.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_PCI_H
|
||||
#define STARKERNEL_PCI_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* PCI standard config-space offsets */
|
||||
#define PCI_CFG_VENDOR_ID 0x00
|
||||
#define PCI_CFG_DEVICE_ID 0x02
|
||||
#define PCI_CFG_COMMAND 0x04
|
||||
#define PCI_CFG_STATUS 0x06
|
||||
#define PCI_CFG_CLASS_REV 0x08
|
||||
#define PCI_CFG_HEADER_TYPE 0x0E
|
||||
#define PCI_CFG_BAR0 0x10
|
||||
#define PCI_CFG_BAR1 0x14
|
||||
#define PCI_CFG_BAR2 0x18
|
||||
#define PCI_CFG_BAR3 0x1C
|
||||
#define PCI_CFG_BAR4 0x20
|
||||
#define PCI_CFG_BAR5 0x24
|
||||
#define PCI_CFG_CAP_PTR 0x34
|
||||
#define PCI_CFG_INT_LINE 0x3C
|
||||
#define PCI_CFG_INT_PIN 0x3D /* 0=none, 1=INTA .. 4=INTD (item 4.3.5c/4.3.5d) */
|
||||
|
||||
/* PCI command register bits */
|
||||
#define PCI_CMD_IO_SPACE (1u << 0)
|
||||
#define PCI_CMD_MEM_SPACE (1u << 1)
|
||||
#define PCI_CMD_BUS_MASTER (1u << 2)
|
||||
#define PCI_CMD_INTX_DISABLE (1u << 10) /* item 4.3.5c/4.3.5e: pci_enable() never
|
||||
* clears this -- if firmware left it set,
|
||||
* INTx never asserts. Check explicitly. */
|
||||
|
||||
/* BAR type flags */
|
||||
#define PCI_BAR_TYPE_MASK 0x1u
|
||||
#define PCI_BAR_IO 0x1u
|
||||
#define PCI_BAR_MEM_64 0x4u /* bits [2:1] == 10 in memory BAR */
|
||||
|
||||
/* Identified PCI device */
|
||||
typedef struct {
|
||||
uint8_t bus;
|
||||
uint8_t device;
|
||||
uint8_t function;
|
||||
uint16_t vendor_id;
|
||||
uint16_t device_id;
|
||||
} PciDevice;
|
||||
|
||||
/*
|
||||
* pci_init — parse ACPI RSDP → XSDT → MCFG to find ECAM base.
|
||||
* On amd64, also maps the ECAM window via the VMM.
|
||||
* May be called with rsdp==NULL; port-I/O fallback still works
|
||||
* on amd64. On aarch64/riscv64 ECAM is required.
|
||||
* Returns 0 on success, -1 if no ECAM found (amd64 still functional via
|
||||
* port I/O).
|
||||
*/
|
||||
int pci_init(void *rsdp);
|
||||
|
||||
/*
|
||||
* pci_find_first — linear scan of bus 0 for the first device matching
|
||||
* (vendor_id, device_id). Fill *out on match.
|
||||
* Returns 0 on found, -1 if not found.
|
||||
*/
|
||||
int pci_find_first(uint16_t vendor_id, uint16_t device_id, PciDevice *out);
|
||||
|
||||
/* Config space accessors */
|
||||
uint32_t pci_read32 (const PciDevice *d, uint16_t offset);
|
||||
uint16_t pci_read16 (const PciDevice *d, uint16_t offset);
|
||||
uint8_t pci_read8 (const PciDevice *d, uint16_t offset);
|
||||
void pci_write32(const PciDevice *d, uint16_t offset, uint32_t val);
|
||||
void pci_write16(const PciDevice *d, uint16_t offset, uint16_t val);
|
||||
void pci_write8 (const PciDevice *d, uint16_t offset, uint8_t val);
|
||||
|
||||
/*
|
||||
* pci_enable — set I/O space + memory space + bus-master bits in the
|
||||
* PCI command register.
|
||||
*/
|
||||
void pci_enable(const PciDevice *d);
|
||||
|
||||
/*
|
||||
* pci_bar — return the base address (physical) of BAR bar_idx.
|
||||
* Handles 32-bit and 64-bit memory BARs; returns 0 for I/O BARs.
|
||||
* Does NOT map the region — call pci_map_bar() separately.
|
||||
*/
|
||||
uint64_t pci_bar(const PciDevice *d, int bar_idx);
|
||||
|
||||
/*
|
||||
* pci_map_bar — make the BAR MMIO region accessible.
|
||||
* amd64: calls vmm_map_range(phys, phys, size, WR|CD).
|
||||
* others: no-op (UEFI identity map covers it).
|
||||
* Returns 0 on success, -1 on failure.
|
||||
*/
|
||||
int pci_map_bar(uint64_t phys_addr, uint64_t size);
|
||||
|
||||
#endif /* STARKERNEL_PCI_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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* plic.h - Platform-Level Interrupt Controller interface (riscv64 only)
|
||||
*
|
||||
* Item 4.3.5a (FABRIC-0.md §27.5): Phase 0 (0.2/0.3) only ever enabled the
|
||||
* S-mode *timer* interrupt (sie.STIE). External interrupts (sie.SEIE) were
|
||||
* never touched, and the PLIC -- the only external-interrupt path on
|
||||
* RISC-V, there is no legacy PIC or I/O APIC equivalent -- had no driver
|
||||
* at all before this item. Pure substrate: this item wires the mechanism
|
||||
* (claim/dispatch/complete) with no permanent source enabled by default;
|
||||
* a real consumer (4.3.5b, virtio-keyboard) enables its own source later.
|
||||
*
|
||||
* @c plic_init() takes the boot DTB as of FABRIC-3.md §V.3 item 3's fix,
|
||||
* 2026-09-05: the base address was previously a QEMU-virt-specific
|
||||
* hardcoded constant, unconditionally wrong on the Milk-V Mars's real
|
||||
* JH7110 PLIC. See @c plic.c's file header for the discovery mechanism.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_PLIC_H
|
||||
#define STARKERNEL_PLIC_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* Map the PLIC and set the S-mode hart-0 context's priority threshold to 0
|
||||
* (maximally permissive -- safe because nothing is enabled at any source
|
||||
* by default; enabling a source is what actually lets it reach claim()).
|
||||
* Also sets sie.SEIE. Does not touch sstatus.SIE -- arch_enable_interrupts()
|
||||
* still owns that, same as the timer.
|
||||
*
|
||||
* @param dtb Candidate devicetree blob (@c BootInfo->dtb); NULL-safe. When
|
||||
* a real @c "sifive,plic-1.0.0" node is found, the PLIC base
|
||||
* address is read from its @c reg property; otherwise falls
|
||||
* back to the QEMU-virt-machine constant, unchanged from this
|
||||
* function's previous unconditional behaviour.
|
||||
* @return 0 on success.
|
||||
*/
|
||||
int plic_init(const void *dtb);
|
||||
|
||||
/** Set a source's interrupt priority (1-7; 0 means "never interrupt"). */
|
||||
void plic_set_priority(uint32_t irq, uint32_t priority);
|
||||
|
||||
/** Enable a source for the S-mode hart-0 context. */
|
||||
void plic_enable(uint32_t irq);
|
||||
|
||||
/** Disable a source for the S-mode hart-0 context. */
|
||||
void plic_disable(uint32_t irq);
|
||||
|
||||
/**
|
||||
* Claim the highest-priority pending interrupt for the S-mode hart-0
|
||||
* context. Returns the source ID, or 0 if none is pending.
|
||||
*/
|
||||
uint32_t plic_claim(void);
|
||||
|
||||
/** Signal completion of the source previously returned by plic_claim(). */
|
||||
void plic_complete(uint32_t irq);
|
||||
|
||||
#endif /* STARKERNEL_PLIC_H */
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* pmm.h - Physical Memory Manager interface for StarKernel
|
||||
*
|
||||
* Bitmap-based allocator for 4KB physical pages. Initialization parses the
|
||||
* UEFI memory map provided in BootInfo and exposes simple allocation and
|
||||
* statistics helpers for early kernel bring-up.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_PMM_H
|
||||
#define STARKERNEL_PMM_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "uefi.h"
|
||||
|
||||
#define PMM_PAGE_SIZE 4096u
|
||||
|
||||
typedef struct {
|
||||
uint64_t total_pages;
|
||||
uint64_t used_pages;
|
||||
uint64_t free_pages;
|
||||
uint64_t total_bytes;
|
||||
uint64_t used_bytes;
|
||||
uint64_t free_bytes;
|
||||
} pmm_stats_t;
|
||||
|
||||
int pmm_init(BootInfo *boot_info);
|
||||
int pmm_is_initialized(void);
|
||||
|
||||
uint64_t pmm_alloc_page(void);
|
||||
uint64_t pmm_alloc_contiguous(uint64_t num_pages);
|
||||
void pmm_free_page(uint64_t paddr);
|
||||
void pmm_free_contiguous(uint64_t paddr, uint64_t num_pages);
|
||||
|
||||
pmm_stats_t pmm_get_stats(void);
|
||||
|
||||
#endif /* STARKERNEL_PMM_H */
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* q48_16.h - Q48.16 Fixed-Point Arithmetic for StarKernel
|
||||
*
|
||||
* Format: uint64_t with fixed decimal point after bit 15
|
||||
* - Bits 0-15: Fractional part (1/65536 resolution)
|
||||
* - Bits 16-63: Integer part (up to 2^48-1)
|
||||
*
|
||||
* Example: 0x00010000 = 1.0, 0x00018000 = 1.5
|
||||
*
|
||||
* All operations are integer-only. NO FLOATING-POINT.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_Q48_16_H
|
||||
#define STARKERNEL_Q48_16_H
|
||||
|
||||
/* If the hosted-VM q48_16.h is already pulled in (e.g. via inference_engine.h),
|
||||
* skip the duplicate static-inline definitions — clang rejects them as errors. */
|
||||
#ifndef Q48_16_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef uint64_t q48_16_t;
|
||||
|
||||
/* Q48.16 representation of 1.0 */
|
||||
#define Q48_ONE ((q48_16_t)0x10000ULL)
|
||||
|
||||
/* ============================================================================
|
||||
* Core Arithmetic Operations
|
||||
* ============================================================================ */
|
||||
|
||||
/*
|
||||
* Multiply two Q48.16 values: (a * b) >> 16
|
||||
*/
|
||||
q48_16_t q48_mul(q48_16_t a, q48_16_t b);
|
||||
|
||||
/*
|
||||
* Divide two Q48.16 values: (a << 16) / b
|
||||
* Returns 0 if b == 0.
|
||||
*/
|
||||
q48_16_t q48_div(q48_16_t a, q48_16_t b);
|
||||
|
||||
/**
|
||||
* Add two Q48.16 values
|
||||
*/
|
||||
static inline q48_16_t q48_add(q48_16_t a, q48_16_t b) {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract two Q48.16 values
|
||||
*/
|
||||
static inline q48_16_t q48_sub(q48_16_t a, q48_16_t b) {
|
||||
return a - b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute value of Q48.16 (treats as unsigned, so just returns a)
|
||||
*/
|
||||
static inline q48_16_t q48_abs(q48_16_t a) {
|
||||
return a; /* Q48.16 is unsigned; for signed use, caller handles */
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Conversion Operations
|
||||
* ============================================================================ */
|
||||
|
||||
/**
|
||||
* Convert unsigned 64-bit integer to Q48.16: u << 16
|
||||
*/
|
||||
static inline q48_16_t q48_from_u64(uint64_t u) {
|
||||
return u << 16;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Q48.16 to a 64-bit integer (truncate fractional): q >> 16,
|
||||
* arithmetic (signed) shift so negative q sign-extends correctly instead
|
||||
* of producing garbage from an unsigned logical shift. Bit-identical to
|
||||
* the old behavior for non-negative q.
|
||||
*/
|
||||
static inline uint64_t q48_to_u64(q48_16_t q) {
|
||||
return (uint64_t)(((int64_t)q) >> 16);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
* Approximation Operations (Integer-Only)
|
||||
* ============================================================================ */
|
||||
|
||||
/*
|
||||
* Approximate natural logarithm in Q48.16 (integer-only)
|
||||
* Input: x in Q48.16 format (x > 0)
|
||||
* Output: ln(x) in Q48.16 format
|
||||
*/
|
||||
q48_16_t q48_log_approx(q48_16_t x);
|
||||
|
||||
/*
|
||||
* Approximate exponential e^x in Q48.16 (integer-only)
|
||||
* Input: q in Q48.16 format
|
||||
* Output: e^q in Q48.16 format
|
||||
*/
|
||||
q48_16_t q48_exp_approx(q48_16_t q);
|
||||
|
||||
/*
|
||||
* Approximate square root in Q48.16 (integer-only, Newton-Raphson)
|
||||
* Input: q in Q48.16 format
|
||||
* Output: sqrt(q) in Q48.16 format
|
||||
*/
|
||||
q48_16_t q48_sqrt_approx(q48_16_t q);
|
||||
|
||||
/*
|
||||
* Approximate sin(q) in Q48.16 (integer-only, Taylor series, radians)
|
||||
* Input: q in Q48.16 format (any magnitude)
|
||||
* Output: sin(q) in Q48.16 format
|
||||
*/
|
||||
q48_16_t q48_sin_approx(q48_16_t q);
|
||||
|
||||
/*
|
||||
* Approximate cos(q) in Q48.16 (integer-only, Taylor series, radians)
|
||||
* Input: q in Q48.16 format (any magnitude)
|
||||
* Output: cos(q) in Q48.16 format
|
||||
*/
|
||||
q48_16_t q48_cos_approx(q48_16_t q);
|
||||
|
||||
#endif /* !Q48_16_H */
|
||||
#endif /* STARKERNEL_Q48_16_H */
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* repl.h - Emergency FORTH REPL for LithosAnanke kernel
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_REPL_H
|
||||
#define STARKERNEL_REPL_H
|
||||
|
||||
#include "vm.h"
|
||||
#include "starkernel/homeblocks_sig.h"
|
||||
|
||||
struct blkio_dev;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* sk_repl - Run the emergency FORTH REPL on the serial console.
|
||||
*
|
||||
* Blocks until vm->halted is set (BYE word) or the VM encounters a halt.
|
||||
* Runs with interrupts enabled; the APIC heartbeat continues to fire.
|
||||
*
|
||||
* @param vm Mama VM instance (must be fully initialised)
|
||||
*/
|
||||
void sk_repl(VM *vm);
|
||||
|
||||
/**
|
||||
* sk_repl_run - Bare REPL loop (no banner).
|
||||
*
|
||||
* Same as sk_repl but skips the version/welcome banner. Used by START
|
||||
* to enter a child VM's interpreter loop without reprinting the header.
|
||||
*
|
||||
* @param vm Fully initialised VM instance
|
||||
*/
|
||||
void sk_repl_run(VM *vm);
|
||||
|
||||
/**
|
||||
* sk_repl_step - Execute one REPL turn on a VM and return.
|
||||
*
|
||||
* Prints the VM's prompt, reads one line, interprets it, prints ok/ERROR,
|
||||
* then returns. Used by the Compudynamics VM-STEP primitive so Hera can
|
||||
* give a single REPL quantum to a child VM without surrendering control
|
||||
* for the full sk_repl_run() loop.
|
||||
*
|
||||
* @param vm Fully initialised VM instance
|
||||
* @return 1 if the VM is still running, 0 if it halted during this turn
|
||||
*/
|
||||
int sk_repl_step(VM *vm);
|
||||
|
||||
/**
|
||||
* sk_repl_set_active_vm - Redirect REPL input to a different VM (USE word).
|
||||
*
|
||||
* Pass NULL to restore default dispatch (Mama's VM).
|
||||
* The change takes effect on the next REPL iteration.
|
||||
*
|
||||
* @param vm Target VM, or NULL for default
|
||||
*/
|
||||
void sk_repl_set_active_vm(VM *vm);
|
||||
|
||||
/**
|
||||
* sk_repl_get_active_vm - Return the current USE-redirected VM, or NULL.
|
||||
*/
|
||||
VM *sk_repl_get_active_vm(void);
|
||||
|
||||
/**
|
||||
* sk_repl_get_homeblocks_dev / sk_repl_get_homeblocks_sig - The currently
|
||||
* attached home-blocks USB drive, or NULL if none is attached / the
|
||||
* attached drive didn't check out as HOMEBLOCKS_SIG_OK (FABRIC-2.md
|
||||
* §F.6/§F.9/§F.18). Both return NULL together; never one without the
|
||||
* other.
|
||||
*/
|
||||
struct blkio_dev *sk_repl_get_homeblocks_dev(void);
|
||||
const homeblocks_sig_t *sk_repl_get_homeblocks_sig(void);
|
||||
|
||||
/**
|
||||
* sk_repl_get_attached_blk_dev - The currently attached USB block
|
||||
* device, regardless of home-blocks recognition (FABRIC-2.md
|
||||
* §F.8/§F.19) -- MINT's own target, since a blank/unminted drive never
|
||||
* sets sk_repl_get_homeblocks_dev() above. NULL if nothing is attached.
|
||||
*/
|
||||
struct blkio_dev *sk_repl_get_attached_blk_dev(void);
|
||||
|
||||
/**
|
||||
* sk_repl_register_words - Registers repl.c's own FORTH-visible words
|
||||
* (currently just BLK-ATTACH-ACK, Artemis's storage-attach reply target --
|
||||
* see sk_word_blk_attach_ack()'s doc comment in repl.c). Call once from
|
||||
* register_forth79_words() alongside the other __STARKERNEL__-only
|
||||
* registration calls.
|
||||
*/
|
||||
void sk_repl_register_words(VM *vm);
|
||||
|
||||
/**
|
||||
* sk_console_getkey - Real body of the standard dictionary's KEY word
|
||||
* (called from shim.c's getchar()). Blocks until a key is available from
|
||||
* either input source (serial console or the PS2/virtio keyboard-event
|
||||
* bridge), servicing the heartbeat/idle loop while waiting so a KEY call
|
||||
* from inside any word never stalls the heartbeat. No echo -- that's the
|
||||
* caller's responsibility, same as any standard KEY.
|
||||
*
|
||||
* @param active_vm VM whose idle dispatch runs while waiting (see
|
||||
* sk_repl_idle()'s own doc comment on why this is a
|
||||
* parameter rather than read via sk_repl_get_active_vm())
|
||||
* @return the key read, as an unsigned byte value
|
||||
*/
|
||||
int sk_console_getkey(VM *active_vm);
|
||||
|
||||
/**
|
||||
* sk_console_key_available - Real body of the standard dictionary's
|
||||
* ?TERMINAL word (called from sf_terminal_ready()). Non-blocking peek:
|
||||
* returns 1 if a key is ready without consuming it (a following
|
||||
* sk_console_getkey() returns that exact key), 0 otherwise.
|
||||
*/
|
||||
int sk_console_key_available(void);
|
||||
|
||||
/**
|
||||
* sk_console_readline - Real body of the standard dictionary's
|
||||
* QUERY/EXPECT words (called from shim.c's fgets()). Reads one line from
|
||||
* the console with echo and backspace support, servicing the heartbeat/
|
||||
* idle loop while waiting -- the same line editor the REPL's own prompt
|
||||
* uses internally, so a mid-word EXPECT behaves identically to typing at
|
||||
* "ok>" itself.
|
||||
*
|
||||
* @param buf Destination buffer
|
||||
* @param size Buffer capacity, including the NUL terminator
|
||||
* @param active_vm VM whose idle dispatch runs while waiting
|
||||
* @param reanchor_prompt Nonzero to re-print the "ok> " prompt whenever
|
||||
* an idle bottom half (heartbeat, USB attach/detach) writes
|
||||
* to the console while this readline blocks at a bare,
|
||||
* untyped prompt -- keeps the top-level prompt as the last
|
||||
* thing shown once the chatter dies down. Callers whose
|
||||
* prompt line is their own (shim.c's fgets(), i.e.
|
||||
* QUERY/EXPECT/ACCEPT) pass 0 so "ok> " never gets stamped
|
||||
* onto their mid-word input context.
|
||||
* @return number of characters placed in buf, not counting the NUL, or -1
|
||||
* (2026-09-06) when reanchor_prompt is nonzero and the attached
|
||||
* identity logged out while this call was blocked waiting for
|
||||
* input with nothing typed yet -- see repl.c's own doc comment
|
||||
* on this function for what a caller must do with -1.
|
||||
*/
|
||||
int sk_console_readline(char* buf, int size, VM* active_vm, int reanchor_prompt);
|
||||
|
||||
/**
|
||||
* sk_repl_headless_wait - Idle-service loop with no interactive surface
|
||||
* at all: no banner, no prompt, no console_getc()/readline. Runs
|
||||
* heartbeat_service() and the same SK_IDLE_BEAT_INTERVAL-gated
|
||||
* sk_repl_idle(mama) cadence sk_console_readline()'s own idle branch
|
||||
* uses -- so USB/WIREBIND/Zuse-attach detection, the heartbeat, and all
|
||||
* other idle-tick subsystems keep running -- until a real identity is
|
||||
* currently attached (Zuse's own session, or a WIREBIND user), at which
|
||||
* point it returns.
|
||||
*
|
||||
* Revised 2026-09-06: originally exited on a one-way sticky "has anyone
|
||||
* ever logged in this boot" flag (sk_console_mark_login()/sk_console_
|
||||
* login_occurred(), both retired) -- that let a real gap through, found
|
||||
* live: once the flag tripped once, it never reset, so a later full
|
||||
* logout (nobody attached at all) fell through to a bare, unauthenticated
|
||||
* prompt instead of going silent again. This now checks live attach
|
||||
* state instead (repl.c's own sk_console_identity_present()), and is
|
||||
* called from two places: once from kernel_main.c in place of an
|
||||
* immediate sk_repl(mama) call when EMERGENCY_CONSOLE_ENABLED is off (the
|
||||
* default, 2026-09-05) -- no thumbdrive, no prompt, at boot -- and again
|
||||
* from inside sk_repl_run()'s own main loop, every time nobody is
|
||||
* currently attached, so the same silence re-engages after any later
|
||||
* logout mid-boot too. When EMERGENCY_CONSOLE_ENABLED is on (the debug/
|
||||
* recovery escape hatch), neither call site applies -- the console shows
|
||||
* immediately and stays visible regardless of attach state, exactly as
|
||||
* before this change.
|
||||
*
|
||||
* @param mama Hera's own VM instance -- the idle-dispatch target,
|
||||
* same as every other sk_repl_idle() caller uses.
|
||||
*/
|
||||
void sk_repl_headless_wait(VM *mama);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_REPL_H */
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* rng.h — Unified entropy entry point for StarKernel
|
||||
*
|
||||
* The single place any kernel consumer (keygen, identity mint, drive_uuid,
|
||||
* certificate serials, ...) asks for entropy. All entropy flows through
|
||||
* rng_get_bytes() and never touches a backend directly.
|
||||
*
|
||||
* The set of active backends is determined at rng_init() time by probing,
|
||||
* in order, until one (or more) come up:
|
||||
* - v2.0.0 (QEMU): virtio-rng is the sole backend — there is no virtio-rng
|
||||
* on real hardware, but QEMU exposes it uniformly on all three arches
|
||||
* (amd64/aarch64/riscv64), and the paravirtualized device sidesteps the
|
||||
* per-ISA gap where no single CPU RNG covers all three models (amd64 has
|
||||
* RDRAND, riscv64 has Zkr, but QEMU's aarch64 CPU models expose neither —
|
||||
* see virtio_rng.h / vm_uuid.h for the identical finding).
|
||||
* - v2.5.0 (real hardware): real per-arch backends are inserted here without
|
||||
* touching the call path — amd64 RDRAND, riscv64 Zkr (RNDR), aarch64
|
||||
* peripheral RNG — each handled by a case in rng_init() and rng_get_bytes()
|
||||
* (grid §G.4). On QEMU all three arches stay on virtio-rng; nothing changes.
|
||||
*
|
||||
* Probe-and-refuse-loudly contract (§G.2): if no backend comes up at
|
||||
* rng_init(), the kernel prints a loud boot-time message. A later
|
||||
* rng_get_bytes() call with no backend returns -1 (RNG_ERR_NO_BACKEND) rather
|
||||
* than ever silently degrading to a deterministic throwaway — the exact failure
|
||||
* Phases A/G call out as unacceptable. Callers (e.g. capsule_mint_identity)
|
||||
* must surface that refusal as an explicit no-entropy error, never proceed with
|
||||
* a deterministic seed.
|
||||
*
|
||||
* Important ordering: rng_init() must run before any rng_get_bytes()/mint call
|
||||
* (it already does in kernel_main phase 8, ahead of Zuse boot attach, which is
|
||||
* the only mint path in v2.0.0). rng_get_bytes() with rng_init() never
|
||||
* successful returns RNG_ERR_NO_BACKEND, never blocks.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_RNG_H
|
||||
#define STARKERNEL_RNG_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Return codes (negative = failure). */
|
||||
#define RNG_ERR_NO_BACKEND (-1) /* rng_init() found no working entropy source */
|
||||
|
||||
/*
|
||||
* rng_init — probe and bring up the entropy backends. Returns 0 if at least
|
||||
* one backend is active (rng_get_bytes() will succeed), nonzero otherwise.
|
||||
* Prints a loud boot-time message when no backend comes up. Call once, early.
|
||||
*/
|
||||
int rng_init(void);
|
||||
|
||||
/*
|
||||
* rng_ready — 1 if at least one backend is active, 0 otherwise.
|
||||
*/
|
||||
int rng_ready(void);
|
||||
|
||||
/*
|
||||
* rng_get_bytes — fill buf with n bytes of real entropy, blocking until all
|
||||
* n bytes are obtained.
|
||||
*
|
||||
* Returns 0 on success (buf fully filled).
|
||||
* Returns RNG_ERR_NO_BACKEND (-1) if no backend is active.
|
||||
*/
|
||||
int rng_get_bytes(uint8_t *buf, size_t n);
|
||||
|
||||
#endif /* STARKERNEL_RNG_H */
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* rpi5_dtb.h - Raspberry Pi 5 (BCM2712) devicetree-based peripheral
|
||||
* discovery, for the native (non-UEFI) boot path (FABRIC-3.md §IV.3).
|
||||
*
|
||||
* Two lookups: the PL011 UART (early console) and the VideoCore mailbox
|
||||
* property interface (framebuffer setup, §IV.3 item 3). Both peripherals
|
||||
* live under BCM2712's own devicetree "soc" simple-bus node, which
|
||||
* applies one fixed address translation to every child `reg` value —
|
||||
* see `rpi5_dtb.c`'s own doc comment for the confirmed offset and where
|
||||
* it was verified. `fdt.c`'s reader deliberately does not do general
|
||||
* `ranges`-property translation (it is "not a general devicetree
|
||||
* library"); this file applies the one, fixed, SoC-wide offset by name
|
||||
* instead of teaching `fdt.c` a general mechanism for a single known
|
||||
* hardware fact.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_RPI5_DTB_H
|
||||
#define STARKERNEL_RPI5_DTB_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Find the PL011 UART's CPU-physical base address from the DTB.
|
||||
*
|
||||
* @param dtb Devicetree blob, as passed to the native boot entry (or
|
||||
* `BootInfo->dtb`); NULL is safe.
|
||||
* @return Final CPU-physical MMIO base address, or 0 if the node is
|
||||
* absent, malformed, or @p dtb is invalid.
|
||||
*/
|
||||
uint64_t rpi5_uart_base(const void* dtb);
|
||||
|
||||
/**
|
||||
* @brief Find the VideoCore mailbox property interface's CPU-physical
|
||||
* base address from the DTB.
|
||||
*
|
||||
* @param dtb Devicetree blob; NULL is safe.
|
||||
* @return Final CPU-physical MMIO base address, or 0 if the node is
|
||||
* absent, malformed, or @p dtb is invalid.
|
||||
*/
|
||||
uint64_t rpi5_mailbox_base(const void* dtb);
|
||||
|
||||
#endif /* STARKERNEL_RPI5_DTB_H */
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* rpi5_mailbox.h - VideoCore mailbox property-interface framebuffer
|
||||
* setup, for the native (non-UEFI) Raspberry Pi 5 boot path
|
||||
* (FABRIC-3.md §IV.3 item 3).
|
||||
*
|
||||
* Register layout, message/tag format, and property-tag IDs confirmed
|
||||
* against multiple sources before writing `rpi5_mailbox.c` — see that
|
||||
* file's own doc comment for exactly which, and what (if anything)
|
||||
* remains unverified against real hardware (not in hand until
|
||||
* 2026-09-17; this driver has never run on real silicon).
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_RPI5_MAILBOX_H
|
||||
#define STARKERNEL_RPI5_MAILBOX_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Mirrors uefi.h's FramebufferInfo exactly -- populated by
|
||||
* rpi5_mailbox_get_framebuffer() the same shape UEFI GOP already
|
||||
* populates it, so console.c/vt100.c/framebuffer.c need no changes at
|
||||
* all for this path. Not #include-ing uefi.h here (that header is a
|
||||
* large UEFI-protocol grab-bag; this driver only needs this one
|
||||
* struct's shape) -- callers that already have a `FramebufferInfo*`
|
||||
* (from uefi.h) can pass it directly, since the two struct
|
||||
* definitions are kept in exact field-for-field sync by convention.
|
||||
*/
|
||||
typedef struct {
|
||||
void* base;
|
||||
uint64_t size;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
uint32_t pixels_per_scanline;
|
||||
uint32_t pixel_format; /* 0 = RGB, matches uefi.h's
|
||||
* PixelRedGreenBlueReserved8BitPerColor --
|
||||
* this driver always requests RGB pixel
|
||||
* order explicitly (tag 0x00048006), never
|
||||
* leaves it at hardware/firmware default. */
|
||||
} Rpi5FramebufferInfo;
|
||||
|
||||
/**
|
||||
* @brief Request a framebuffer from the VideoCore firmware via the
|
||||
* mailbox property interface, at the requested resolution/depth.
|
||||
*
|
||||
* Sends one buffer with all six setup tags (physical size, virtual
|
||||
* size, depth, pixel order, virtual offset, allocate) plus a
|
||||
* get-pitch tag, in one request/response round trip.
|
||||
*
|
||||
* @param dtb Devicetree blob (passed to `rpi5_mailbox_base()`).
|
||||
* @param width Requested physical+virtual width, pixels.
|
||||
* @param height Requested physical+virtual height, pixels.
|
||||
* @param bpp Requested bits per pixel (32 is the only depth this
|
||||
* driver has been designed against; others are not
|
||||
* refused outright but are unverified).
|
||||
* @param out Populated on success; untouched on failure.
|
||||
* @return 0 on success, negative on failure (mailbox node not found
|
||||
* in the DTB, VC firmware rejected the request, or a response
|
||||
* tag came back with an unexpected size).
|
||||
*/
|
||||
int rpi5_mailbox_get_framebuffer(const void* dtb, uint32_t width, uint32_t height,
|
||||
uint32_t bpp, Rpi5FramebufferInfo* out);
|
||||
|
||||
#endif /* STARKERNEL_RPI5_MAILBOX_H */
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* rpi5_native_boot.h - Raspberry Pi 5 DTB->BootInfo constructor
|
||||
* (FABRIC-3.md §IV.3 item 2).
|
||||
*
|
||||
* `native_rpi5_entry.S`'s `rpi5_native_start` tail-calls
|
||||
* `rpi5_native_boot()` with the masked DTB pointer it captured. This
|
||||
* function builds the *existing*, unmodified `BootInfo` struct
|
||||
* (`include/starkernel/uefi.h`) from the devicetree instead of UEFI
|
||||
* protocols, then calls the *existing*, unmodified `kernel_main()` — see
|
||||
* this file's own `.c` for exactly what is and is not populated, and why.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_RPI5_NATIVE_BOOT_H
|
||||
#define STARKERNEL_RPI5_NATIVE_BOOT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void rpi5_native_boot(uint64_t dtb) __attribute__((noreturn));
|
||||
|
||||
#endif /* STARKERNEL_RPI5_NATIVE_BOOT_H */
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
Copyright (c) 2023–2025 Robert A. James. All rights reserved.
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* rpi5_native_entry.h - Raspberry Pi 5 native (non-UEFI) boot entry point
|
||||
* (FABRIC-3.md §IV.3 item 1).
|
||||
*
|
||||
* `native_rpi5_entry.S`'s `rpi5_native_start` is the very first code that
|
||||
* runs on this path -- entered directly by Pi 5 firmware, no UEFI, no
|
||||
* ACPI, none of `boot/uefi_loader.c`'s PE-loader shape applies. It masks
|
||||
* the firmware's raw entry register down to the documented 32-bit DTB
|
||||
* pointer (§IV.1: upper 32 bits of the 64-bit register are unspecified)
|
||||
* and stores it here, then establishes its own dedicated stack (this path
|
||||
* has no EDK2 boot stack to inherit) and halts.
|
||||
*
|
||||
* `g_rpi5_dtb_ptr` is this stub's one real output -- the still-open
|
||||
* DTB->BootInfo constructor (§IV.3 item 2) reads it from here once it
|
||||
* exists; nothing calls that constructor yet, so `rpi5_native_start`
|
||||
* halts rather than tail-calling into a function that isn't real.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_RPI5_NATIVE_ENTRY_H
|
||||
#define STARKERNEL_RPI5_NATIVE_ENTRY_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
extern uint64_t g_rpi5_dtb_ptr;
|
||||
|
||||
#endif /* STARKERNEL_RPI5_NATIVE_ENTRY_H */
|
||||
@@ -0,0 +1,37 @@
|
||||
/* scalar25519.h -- arithmetic mod L (the Ed25519 base point's order),
|
||||
* for reducing SHA-512 output to a valid scalar and checking a
|
||||
* signature's S component for the RFC 8032 malleability requirement
|
||||
* (S < L, not just S < 2^256).
|
||||
*
|
||||
* Deliberately NOT the intricate hand-tuned "sc_reduce" reduction most
|
||||
* reference implementations use (a bespoke Barrett-style reduction with
|
||||
* constants specific to L, notoriously easy to transcribe wrong) --
|
||||
* this is a plain binary long-division reduction, one bit at a time.
|
||||
* O(512) steps per reduction; this is a verify-only, non-hot-path
|
||||
* library (one reduction per signature check), so the simpler,
|
||||
* more obviously-correct approach is the right tradeoff here.
|
||||
*/
|
||||
#ifndef SCALAR25519_H
|
||||
#define SCALAR25519_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* 32-byte little-endian scalars, reduced mod L where noted. */
|
||||
|
||||
/* Reduce a 64-byte little-endian value (e.g. raw SHA-512 output) mod L,
|
||||
* producing a 32-byte little-endian result < L. */
|
||||
void scalar_reduce512(uint8_t out[32], const uint8_t in[64]);
|
||||
|
||||
/* 1 if the 32-byte little-endian scalar is < L (a well-formed,
|
||||
* non-malleable signature component per RFC 8032), else 0. */
|
||||
int scalar_lt_L(const uint8_t s[32]);
|
||||
|
||||
/* out = (a*b + c) mod L, all 32-byte little-endian scalars (a, b, c need
|
||||
* not already be reduced mod L, though every caller in this codebase
|
||||
* passes already-reduced inputs). Needed for EdDSA signing's
|
||||
* S = (k*a + r) mod L step -- verify never needed scalar multiplication,
|
||||
* only reduction, so this didn't exist until signing did. */
|
||||
void scalar_muladd(uint8_t out[32], const uint8_t a[32], const uint8_t b[32],
|
||||
const uint8_t c[32]);
|
||||
|
||||
#endif /* SCALAR25519_H */
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* session.h - Per-VM session (FABRIC-2.md §H, decided 2026-09-02/03)
|
||||
*
|
||||
* A session is a Stadium patron (FABRIC-2.md §H.1) -- registering a session
|
||||
* IS admitting a patron to the Stadium, not a new parallel bookkeeping
|
||||
* structure. This struct is the piece that sits ALONGSIDE the patron,
|
||||
* referencing it by VMUuid rather than being indexed by Stadium cell index
|
||||
* or grown as inline fields on StadiumPatronHeader/struct VM (deliberately
|
||||
* its own header, mirroring VMUuid's/VMIdentity's own precedent -- standing
|
||||
* instruction: give real-shaped data its own header and integrate as a
|
||||
* field, don't grow existing structs ad hoc).
|
||||
*
|
||||
* Fields (FABRIC-2.md §H.2, all five confirmed 2026-09-02/03):
|
||||
* vm_id -- the patron this session references.
|
||||
* pinned -- session is AUTHORITATIVE over Stadium's STADIUM_FLAG_PIN
|
||||
* bit (§H.10): the sole read/write path for pin state is
|
||||
* session_set_pinned()/session_is_pinned() below, nothing
|
||||
* else (including existing Stadium code) may touch
|
||||
* STADIUM_FLAG_PIN directly.
|
||||
* parent -- who birthed this session (Hera -> Hermes/Artemis, etc).
|
||||
* name -- canonical human-readable name; feeds console.c's
|
||||
* g_active_vm_name prefix, does not replace the
|
||||
* console-binding mechanism itself.
|
||||
* identity -- embedded VMIdentity (FABRIC-2.md §H.4's VM card is
|
||||
* effectively VMIdentity's existing ownership check; reused
|
||||
* directly here, not reinvented).
|
||||
*
|
||||
* Explicit user framing (2026-09-02), still true: "we're gonna be
|
||||
* revisiting this part of it around and around for a while" -- treat this
|
||||
* shape as a live working draft, not permanently locked.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_SESSION_H
|
||||
#define STARKERNEL_SESSION_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#include "starkernel/vm_uuid.h"
|
||||
#include "starkernel/vm_identity.h"
|
||||
|
||||
/* Matches console.c's CONSOLE_VM_NAME_BUF precedent -- same order of
|
||||
* magnitude for the same kind of data (a short human-readable VM name). */
|
||||
#define SESSION_NAME_BUF 64
|
||||
|
||||
/* Sentinel meaning "no Stadium cell recorded yet" -- same shape as
|
||||
* STADIUM_CELL_NONE (stadium.c), duplicated here rather than pulled in via
|
||||
* stadium.h to avoid this header depending on Stadium's internal cell-index
|
||||
* type. Session's own callers set stadium_cell after their own
|
||||
* stadium_admit() call returns a real index (§H.12 step 3 doc). */
|
||||
#define SESSION_STADIUM_CELL_NONE ((size_t)-1)
|
||||
|
||||
typedef struct {
|
||||
VMUuid vm_id; /* the patron this session references */
|
||||
int pinned; /* authoritative over STADIUM_FLAG_PIN; see
|
||||
* session_set_pinned()/session_is_pinned() */
|
||||
VMUuid parent; /* who birthed this session */
|
||||
char name[SESSION_NAME_BUF]; /* canonical human-readable name */
|
||||
VMIdentity identity; /* embedded, not referenced -- see vm_identity.h */
|
||||
size_t stadium_cell; /* index of this session's own patron cell in
|
||||
* stadium_cells() -- SESSION_STADIUM_CELL_NONE
|
||||
* until the caller that admits this session's
|
||||
* patron (stadium_admit()'s return value) sets it.
|
||||
* session_set_pinned()/session_is_pinned() need
|
||||
* this to reach the right patron header; added
|
||||
* §H.12 step 3, not part of the original H.2 field
|
||||
* list -- necessary plumbing, not a new session-
|
||||
* level concept, so not itself renegotiated. */
|
||||
} Session;
|
||||
|
||||
/*
|
||||
* session_boot_init - Boot-time allocation, mirroring stadium_boot_init()'s
|
||||
* own kmalloc-sized-from-budget shape rather than a fixed compile-time
|
||||
* array (stadium.c's own StadiumVMQuota table was moved off a fixed array
|
||||
* for the same reason -- population isn't knowable in advance). Must run
|
||||
* after stadium_boot_init() (session slot count is sized from
|
||||
* stadium_max_vm_count()) and before the first session is registered.
|
||||
* No callers yet (§H.12 step 2) -- wiring into the boot sequence happens
|
||||
* in a later punch-list step.
|
||||
*
|
||||
* @return 0 on success, -1 if kmalloc failed or stadium_max_vm_count() is 0
|
||||
* (Stadium not yet initialized).
|
||||
*/
|
||||
int session_boot_init(void);
|
||||
|
||||
/*
|
||||
* session_find - Look up a session by the VMUuid of the patron it
|
||||
* references. Linear scan, same shape as stadium.c's own
|
||||
* quota_slot_for_vm() -- the population this searches is small (one entry
|
||||
* per VM, not per word/block).
|
||||
*
|
||||
* @return Pointer to the live session, or NULL if none is registered for
|
||||
* vm_id.
|
||||
*/
|
||||
Session *session_find(VMUuid vm_id);
|
||||
|
||||
/*
|
||||
* session_register - Register a new session for vm_id. identity starts
|
||||
* zeroed (VMIdentity's own documented default: installed=0, "no lock,
|
||||
* allow freely" -- §H.12 Correction 2). pinned starts 0 (unpinned); use
|
||||
* session_set_pinned() separately to pin, keeping this function's job to
|
||||
* "create the session record" only, not "create and also decide pin
|
||||
* policy" -- callers (e.g. the capsule-birth admission path, §H.12 phase 2)
|
||||
* decide pinning themselves.
|
||||
*
|
||||
* @param vm_id The patron this session references. Must not already have
|
||||
* a registered session (session_find(vm_id) must be NULL).
|
||||
* @param parent Who birthed this session (vm_uuid_hera() for Hera's own
|
||||
* self-registration -- self-referential, matching the
|
||||
* existing parent_vm_id convention documented in
|
||||
* capsule_run.h).
|
||||
* @param name Copied into the new session's name buffer, truncated to
|
||||
* SESSION_NAME_BUF - 1 if longer.
|
||||
* @return Pointer to the new session, or NULL if the slot table is full,
|
||||
* not yet initialized, or vm_id is already registered.
|
||||
*/
|
||||
Session *session_register(VMUuid vm_id, VMUuid parent, const char *name);
|
||||
|
||||
/*
|
||||
* session_set_pinned / session_is_pinned - The pin-authority choke point
|
||||
* (FABRIC-2.md §H.2/§H.10, decided 2026-09-02: "full choke point at the
|
||||
* session level, both directions"). Session is authoritative for every
|
||||
* EXTERNAL reader -- nothing else, including existing Stadium code, reads
|
||||
* or writes STADIUM_FLAG_PIN on a patron header directly anymore.
|
||||
*
|
||||
* session_is_pinned() answers from the session's own `pinned` field
|
||||
* directly (the authoritative copy) -- it does not re-derive the answer
|
||||
* from Stadium. session_set_pinned() writes both: the session's own
|
||||
* `pinned` field (authoritative) AND the mirrored STADIUM_FLAG_PIN bit on
|
||||
* the session's own patron header (stadium_cells()[session->stadium_cell]),
|
||||
* so the Stadium engine's own internal eviction/admission logic -- which
|
||||
* must stay self-contained and cannot call back into session.c -- keeps
|
||||
* seeing a correct, in-sync bit.
|
||||
*
|
||||
* Both no-op (return 0 / do nothing) if vm_id has no registered session, or
|
||||
* if stadium_cell is still SESSION_STADIUM_CELL_NONE (patron not admitted
|
||||
* yet) for the set path.
|
||||
*/
|
||||
void session_set_pinned(VMUuid vm_id, int pinned);
|
||||
int session_is_pinned(VMUuid vm_id);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_SESSION_H */
|
||||
@@ -0,0 +1,30 @@
|
||||
/* sha512.h -- freestanding SHA-512 (FIPS 180-4 / RFC 6234), C99, no libc
|
||||
* beyond memcpy/memset (both available in the kernel via
|
||||
* src/starkernel/vm/host/shim.c). No __int128 used -- 64-bit words only,
|
||||
* portable to amd64/aarch64/riscv64 without libgcc helpers.
|
||||
*/
|
||||
#ifndef SHA512_H
|
||||
#define SHA512_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
uint64_t state[8];
|
||||
uint64_t bitlen; /* total message length in bits, low 64 bits
|
||||
* (SHA-512 defines a 128-bit length field; a
|
||||
* single uint64_t of bit-length is enough for
|
||||
* any message this kernel will ever hash --
|
||||
* capsules and certs, not exabyte streams) */
|
||||
uint8_t buf[128];
|
||||
size_t buf_len;
|
||||
} sha512_ctx_t;
|
||||
|
||||
void sha512_init(sha512_ctx_t *ctx);
|
||||
void sha512_update(sha512_ctx_t *ctx, const uint8_t *data, size_t len);
|
||||
void sha512_final(sha512_ctx_t *ctx, uint8_t out[64]);
|
||||
|
||||
/* Convenience one-shot. */
|
||||
void sha512(const uint8_t *data, size_t len, uint8_t out[64]);
|
||||
|
||||
#endif /* SHA512_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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* timer.h - Timer and Heartbeat Interface
|
||||
*
|
||||
* M5 Time Model:
|
||||
* - TIME-TICKS (Q64.0): Monotonic heartbeat counter, never decreases
|
||||
* - TIME-TRUST (Q48.16): Continuous confidence metric [0.0, 1.0]
|
||||
* - No discrete modes (NONE/REL/ABS are legacy, being phased out)
|
||||
* - Trust is a measurement, never gates execution
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_TIMER_H
|
||||
#define STARKERNEL_TIMER_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "uefi.h"
|
||||
#include "q48_16.h"
|
||||
|
||||
/* ============================================================================
|
||||
* M5 Time Model (New)
|
||||
* ============================================================================ */
|
||||
|
||||
/* TIME-TRUST: Continuous confidence metric in Q48.16 format */
|
||||
typedef q48_16_t time_trust_t;
|
||||
|
||||
/* Rolling window size for timestamp variance computation */
|
||||
#define TIME_WINDOW_SIZE 64
|
||||
|
||||
/* TIME-TRUST thresholds in Q48.16 (for diagnostics, NOT for gating) */
|
||||
#define TIME_TRUST_HIGH Q48_ONE /* 1.0 = full confidence */
|
||||
#define TIME_TRUST_LOW (Q48_ONE >> 2) /* 0.25 = low confidence */
|
||||
|
||||
/*
|
||||
* Rolling window of timestamp deltas for variance computation.
|
||||
* Each entry is (actual_tsc_delta - expected_tsc_delta) in TSC ticks.
|
||||
*/
|
||||
typedef struct time_window {
|
||||
int64_t deltas[TIME_WINDOW_SIZE]; /* Signed: can be early or late */
|
||||
uint32_t pos; /* Current write position */
|
||||
uint32_t count; /* Number of valid samples (up to SIZE) */
|
||||
} TimeWindow;
|
||||
|
||||
/*
|
||||
* M5 Heartbeat State: Holds all time-related metrics.
|
||||
* Updated every heartbeat tick by the ISR.
|
||||
*/
|
||||
typedef struct time_trust_state {
|
||||
/* Core counters */
|
||||
volatile uint64_t ticks; /* TIME-TICKS: monotonic heartbeat count --
|
||||
* written directly in ISR context
|
||||
* (heartbeat_tick(), all three archs) and
|
||||
* read directly by mainline
|
||||
* (heartbeat_ticks()); genuinely
|
||||
* concurrent, unlike every other field in
|
||||
* this struct (FABRIC-0.md item 4.5a/4.5b,
|
||||
* 2026-08-11). */
|
||||
uint64_t last_tsc; /* TSC at last heartbeat */
|
||||
uint64_t expected_delta; /* Expected TSC ticks per heartbeat */
|
||||
|
||||
/* Rolling window for variance */
|
||||
TimeWindow window;
|
||||
|
||||
/* Derived metrics (Q48.16) */
|
||||
q48_16_t variance; /* Variance of deltas */
|
||||
q48_16_t trust; /* TIME-TRUST: derived from variance */
|
||||
|
||||
/* Statistics */
|
||||
uint64_t total_samples; /* Lifetime sample count */
|
||||
} TimeTrustState;
|
||||
|
||||
/* ============================================================================
|
||||
* Legacy M4 Interface (To Be Phased Out)
|
||||
* ============================================================================ */
|
||||
|
||||
/*
|
||||
* Timer trust levels (LEGACY - discrete modes violate M5 spec):
|
||||
* - NONE: no usable time base
|
||||
* - RELATIVE: monotonic-ish, not for claims
|
||||
* - ABSOLUTE: invariant + calibrated
|
||||
*/
|
||||
typedef enum timer_trust_level {
|
||||
TIMER_TRUST_NONE = 0,
|
||||
TIMER_TRUST_RELATIVE = 1,
|
||||
TIMER_TRUST_ABSOLUTE = 2
|
||||
} timer_trust_level_t;
|
||||
|
||||
/*
|
||||
* Timer calibration record for logging / DoE traceability.
|
||||
* Keep it minimal and serial-friendly.
|
||||
*/
|
||||
typedef struct timer_calibration_record {
|
||||
uint64_t hpet_hz; /* HPET frequency derived from period_fs (if available) */
|
||||
uint64_t tsc_hz_mean; /* Locked TSC Hz (final) */
|
||||
uint64_t pit_hz_mean; /* PIT-based estimate (if used) */
|
||||
uint64_t cv_hpet_ppm; /* HPET window CV in ppm (bare metal convergence) */
|
||||
uint64_t cv_pit_ppm; /* PIT window CV in ppm (bare metal convergence) */
|
||||
uint64_t diff_ppm; /* HPET vs PIT mean diff in ppm (bare metal convergence) */
|
||||
uint32_t windows_used; /* number of windows consumed to converge */
|
||||
uint8_t converged; /* 1 if converged/locked, 0 otherwise */
|
||||
uint8_t vm_mode; /* 1 if hypervisor policy path used */
|
||||
uint8_t trust; /* timer_trust_level_t (NONE/RELATIVE/ABSOLUTE) */
|
||||
uint8_t reserved[1];
|
||||
} timer_calibration_record_t;
|
||||
|
||||
/* Legacy API (still works, wraps M5 internals) */
|
||||
int timer_init(BootInfo *boot_info);
|
||||
uint64_t timer_tsc_hz(void);
|
||||
uint64_t timer_now_ns(void);
|
||||
int timer_check_drift_now(void);
|
||||
const timer_calibration_record_t *timer_calibration_record(void);
|
||||
|
||||
/* ============================================================================
|
||||
* M5 Heartbeat API (New)
|
||||
* ============================================================================ */
|
||||
|
||||
/*
|
||||
* Initialize the heartbeat subsystem.
|
||||
* Called after timer_init(), before enabling APIC timer.
|
||||
*/
|
||||
void heartbeat_init(uint64_t tsc_hz, uint64_t tick_hz);
|
||||
|
||||
/**
|
||||
* Top half. Called directly from each architecture's ISR (punch-list item
|
||||
* 0.8) -- one call site per architecture, unchanged from before this item.
|
||||
* Does exactly three things: reads the raw counter via
|
||||
* @c heartbeat_read_counter(), increments TIME-TICKS, and latches the
|
||||
* sample for @c heartbeat_service() to pick up. No window math, no
|
||||
* variance, no loops -- this must stay cheap enough for interrupt context.
|
||||
*/
|
||||
void heartbeat_tick(void);
|
||||
|
||||
/**
|
||||
* Bottom half (punch-list item 0.8). Services one pending sample if
|
||||
* @c heartbeat_tick() has latched one since the last call: computes the
|
||||
* inter-tick deviation, updates the rolling window, and (architecture
|
||||
* permitting -- see @c heartbeat.c) recomputes variance and TIME-TRUST.
|
||||
* Never runs in interrupt context. Call from the mainline, as frequently
|
||||
* as convenient -- a stale/skipped service call degrades the window's
|
||||
* fidelity but affects nothing else, since TIME-TRUST is diagnostic only
|
||||
* and never gates execution.
|
||||
*/
|
||||
void heartbeat_service(void);
|
||||
|
||||
/**
|
||||
* Read the raw hardware counter this architecture's heartbeat is paced
|
||||
* against -- the same clock @c timer_now_ns() and calibration already use
|
||||
* internally (rdtsc on amd64, the `time` CSR on riscv64, CNTPCT_EL0 on
|
||||
* aarch64), not a separate/different source. Implemented once per
|
||||
* architecture in that architecture's timer.c; consumed only by
|
||||
* @c heartbeat_tick() in the shared heartbeat.c.
|
||||
*/
|
||||
uint64_t heartbeat_read_counter(void);
|
||||
|
||||
/**
|
||||
* Get current TIME-TICKS (monotonic heartbeat count).
|
||||
*/
|
||||
uint64_t heartbeat_ticks(void);
|
||||
|
||||
/**
|
||||
* Get current TIME-TRUST (Q48.16 confidence metric).
|
||||
*/
|
||||
time_trust_t heartbeat_trust(void);
|
||||
|
||||
/**
|
||||
* Get pointer to full heartbeat state (for diagnostics).
|
||||
*/
|
||||
const TimeTrustState *heartbeat_state(void);
|
||||
|
||||
/**
|
||||
* Set the adaptive re-arm period, in nanoseconds (punch-list item 0.8,
|
||||
* FABRIC-0.md §26). Called from the mainline execution path only (Loop #7's
|
||||
* site in vm_runtime.c) -- never from interrupt context. Clamped to
|
||||
* [1/4x, 4x] of the kernel's base period internally; a caller need not
|
||||
* pre-clamp.
|
||||
*/
|
||||
void heartbeat_set_adaptive_period_ns(uint64_t ns);
|
||||
|
||||
/**
|
||||
* Read the period the next hardware re-arm should use. Called from
|
||||
* interrupt context by each architecture's re-arm function in place of a
|
||||
* fixed constant.
|
||||
*/
|
||||
uint64_t heartbeat_next_period_ns(void);
|
||||
|
||||
#endif /* STARKERNEL_TIMER_H */
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* ttf.h - TrueType font parser core (Freestanding)
|
||||
*
|
||||
* FABRIC-0.md item 4.3.7. Reads a TTF's sfnt directory plus head/maxp/loca/
|
||||
* glyf/cmap tables, resolving a Unicode codepoint to a glyph index and its
|
||||
* outline header (contour count, bounding box). Does NOT extract outline
|
||||
* points or rasterize — that is 4.3.7a/4.3.7c. No floating point; all
|
||||
* fields read here are raw integers straight from the font's own
|
||||
* big-endian on-disk format (see FABRIC-0.md §27.7 decision #2 for why the
|
||||
* Q48.16-vs-float call was made, and why it doesn't bind this file, which
|
||||
* never scales anything).
|
||||
*
|
||||
* cmap: only format 4 (Windows/Unicode BMP) subtables are resolved. This
|
||||
* covers ASCII and all of the BMP, which is what the v1 glyph repertoire
|
||||
* (§27.6.4) needs. Format 12 (supplementary planes) is deferred — no
|
||||
* v1 glyph requires it.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_TTF_H
|
||||
#define STARKERNEL_TTF_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "q48_16.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define TTF_OK 0
|
||||
#define TTF_ERR_BAD_SFNT -1
|
||||
#define TTF_ERR_TABLE_MISSING -2
|
||||
#define TTF_ERR_BAD_TABLE -3
|
||||
#define TTF_ERR_BAD_GLYPH_INDEX -4
|
||||
#define TTF_ERR_OUT_OF_BOUNDS -5
|
||||
#define TTF_ERR_TOO_MANY_POINTS -6
|
||||
#define TTF_ERR_TOO_MANY_CONTOURS -7
|
||||
#define TTF_ERR_TOO_DEEP -8 /* composite glyph nesting exceeded TTF_MAX_COMPOSITE_DEPTH */
|
||||
#define TTF_ERR_UNSUPPORTED -9 /* e.g. a non-identity composite transform or point-matched
|
||||
* component args — see ttf.c's file header comment; not a
|
||||
* malformed font, just a code path this parser doesn't
|
||||
* implement yet */
|
||||
|
||||
/* Recursion guard for nested composite glyphs (a component referencing a
|
||||
* component). The TTF spec doesn't hard-cap this; this is a defensive
|
||||
* limit for freestanding/kernel-stack safety. */
|
||||
#define TTF_MAX_COMPOSITE_DEPTH 8
|
||||
|
||||
/** Glyph index returned for "no mapping" by ttf_codepoint_to_glyph(). */
|
||||
#define TTF_GLYPH_MISSING 0
|
||||
|
||||
/**
|
||||
* Parsed font handle. Borrows the caller's buffer (does not copy or own
|
||||
* it) — the buffer must outlive the ttf_font_t.
|
||||
*/
|
||||
typedef struct {
|
||||
const uint8_t *data;
|
||||
uint32_t size;
|
||||
|
||||
uint32_t head_off;
|
||||
uint32_t maxp_off;
|
||||
uint32_t loca_off;
|
||||
uint32_t loca_len;
|
||||
uint32_t glyf_off;
|
||||
uint32_t glyf_len;
|
||||
|
||||
uint16_t units_per_em;
|
||||
int16_t index_to_loc_format; /* 0 = Offset16 (x2), 1 = Offset32 */
|
||||
uint16_t num_glyphs;
|
||||
|
||||
/* Selected cmap subtable (format 4 only, see file header comment). */
|
||||
uint32_t cmap_subtable_off;
|
||||
int has_cmap;
|
||||
|
||||
/* hhea/hmtx, for ttf_glyph_advance_width() -- proportional spacing
|
||||
* (FABRIC-0.md item 4.3.7e). Mandatory tables per the TrueType spec,
|
||||
* so their absence fails ttf_parse() same as head/maxp/loca/glyf. */
|
||||
uint32_t hmtx_off;
|
||||
uint16_t num_h_metrics;
|
||||
} ttf_font_t;
|
||||
|
||||
/** Raw glyf record header, per §27.6/4.3.7's "done when" clause. */
|
||||
typedef struct {
|
||||
int16_t num_contours; /* >= 0 simple glyph, < 0 composite glyph */
|
||||
int16_t x_min;
|
||||
int16_t y_min;
|
||||
int16_t x_max;
|
||||
int16_t y_max;
|
||||
uint32_t glyf_offset; /* absolute file offset of this glyph's record */
|
||||
uint32_t glyf_length; /* bytes; 0 for an empty glyph (e.g. space) */
|
||||
} ttf_glyph_header_t;
|
||||
|
||||
/**
|
||||
* ttf_parse - Locate and validate the sfnt directory and the head/maxp/
|
||||
* loca/glyf tables (cmap is optional; ttf_codepoint_to_glyph() fails
|
||||
* cleanly if absent). Does not copy `data` — `out` borrows it.
|
||||
*
|
||||
* @param data Whole .ttf file contents
|
||||
* @param size Length of data in bytes
|
||||
* @param out Parsed handle to populate
|
||||
* @return TTF_OK, or a TTF_ERR_* code
|
||||
*/
|
||||
int ttf_parse(const uint8_t *data, uint32_t size, ttf_font_t *out);
|
||||
|
||||
/**
|
||||
* ttf_codepoint_to_glyph - Resolve a Unicode codepoint via the font's
|
||||
* format-4 cmap subtable.
|
||||
*
|
||||
* @return glyph index, or TTF_GLYPH_MISSING if unmapped or no cmap
|
||||
*/
|
||||
uint32_t ttf_codepoint_to_glyph(const ttf_font_t *font, uint32_t codepoint);
|
||||
|
||||
/**
|
||||
* ttf_glyph_header - Read a glyph's outline header (contour count,
|
||||
* bounding box) via loca + glyf. Does not extract contour points.
|
||||
*
|
||||
* @param glyph_index As returned by ttf_codepoint_to_glyph()
|
||||
* @param out Header to populate
|
||||
* @return TTF_OK, or a TTF_ERR_* code
|
||||
*/
|
||||
int ttf_glyph_header(const ttf_font_t *font, uint32_t glyph_index,
|
||||
ttf_glyph_header_t *out);
|
||||
|
||||
/**
|
||||
* ttf_glyph_advance_width - Horizontal advance width via hmtx, in raw
|
||||
* font design units (NOT scaled by unitsPerEm -- caller's job, e.g.
|
||||
* `q48_mul(q48_from_u64(width), scale)`; always non-negative, so the
|
||||
* shared q48_mul is fine here unlike the signed cases documented in
|
||||
* ttf.c's rasterizer). Glyphs past hmtx's numberOfHMetrics entries share
|
||||
* the last entry's width, per spec.
|
||||
*
|
||||
* @param glyph_index As returned by ttf_codepoint_to_glyph()
|
||||
* @return advance width, or 0 if glyph_index is out of range
|
||||
*/
|
||||
uint16_t ttf_glyph_advance_width(const ttf_font_t *font, uint32_t glyph_index);
|
||||
|
||||
/** One outline point, in raw font design units (NOT scaled by unitsPerEm —
|
||||
* that's the caller's job, same convention as ttf_glyph_header_t's bbox),
|
||||
* expressed in Q48.16. Two's-complement negative values are expected and
|
||||
* correct for q48_add/q48_sub and for q48_from_u64-style left-shift
|
||||
* conversion; this module never calls q48_mul/q48_div on outline
|
||||
* coordinates (see ttf.c's file header comment for why). */
|
||||
typedef struct {
|
||||
q48_16_t x, y;
|
||||
uint8_t on_curve;
|
||||
} ttf_point_t;
|
||||
|
||||
/** Simple- and composite-glyph outline, flattened to one point list plus
|
||||
* per-contour end indices (TrueType convention: contour_ends[c] is the
|
||||
* index of the LAST point of contour c, inclusive; points are shared
|
||||
* across contours only in the sense that contour c+1 starts right after
|
||||
* contour_ends[c]). Caller supplies both backing arrays — this module
|
||||
* never allocates. */
|
||||
typedef struct {
|
||||
ttf_point_t *points;
|
||||
uint32_t max_points;
|
||||
uint32_t point_count;
|
||||
|
||||
uint16_t *contour_ends;
|
||||
uint32_t max_contours;
|
||||
uint32_t contour_count;
|
||||
} ttf_outline_t;
|
||||
|
||||
/**
|
||||
* ttf_glyph_outline - Extract a glyph's outline (simple or composite,
|
||||
* recursively resolving composite components) into caller-supplied
|
||||
* buffers.
|
||||
*
|
||||
* Composite components with a non-identity transform (any scale/rotation/
|
||||
* skew, i.e. anything but a pure (dx,dy) translation) or with
|
||||
* point-matched (rather than xy-offset) placement args return
|
||||
* TTF_ERR_UNSUPPORTED rather than silently producing a wrong outline —
|
||||
* see ttf.c's file header comment for why, and check that limitation
|
||||
* before relying on this for an arbitrary font.
|
||||
*
|
||||
* @return TTF_OK, or a TTF_ERR_* code (including TTF_ERR_TOO_MANY_POINTS/
|
||||
* _CONTOURS if a caller buffer is too small, and TTF_ERR_TOO_DEEP
|
||||
* if composite nesting exceeds TTF_MAX_COMPOSITE_DEPTH)
|
||||
*/
|
||||
int ttf_glyph_outline(const ttf_font_t *font, uint32_t glyph_index, ttf_outline_t *out);
|
||||
|
||||
/** A caller-owned 8-bit-per-pixel bitmap. `ttf_rasterize_glyph()` writes
|
||||
* `fill_value` into covered pixels and leaves everything else untouched
|
||||
* (it does not clear the buffer first — caller's job, so repeated
|
||||
* rasterization into the same bitmap, e.g. for a text run, composites
|
||||
* correctly without an extra clear between glyphs). */
|
||||
typedef struct {
|
||||
uint8_t *pixels;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
} ttf_bitmap_t;
|
||||
|
||||
/**
|
||||
* ttf_rasterize_glyph - Flatten a glyph's outline (quadratic Bezier
|
||||
* contours, fixed segment count per curve — see ttf.c's file header
|
||||
* comment) and fill it into `out` using the even-odd rule (FABRIC-0.md item
|
||||
* 4.3.7c; see that item's completion note for why even-odd rather than
|
||||
* nonzero winding — correct for the v1 glyph repertoire's non-self-
|
||||
* intersecting nested contours, not necessarily for an arbitrary font).
|
||||
* No antialiasing (explicitly deferred, per 4.3.7c's own "done when"
|
||||
* clause). Never allocates — flattening uses a fixed-size local buffer
|
||||
* bounded by TTF_RASTER_MAX_POINTS/TTF_RASTER_MAX_CONTOURS.
|
||||
*
|
||||
* @param scale Font-design-units-to-pixels scale, Q48.16, e.g.
|
||||
* q48_div(q48_from_u64(size_px), q48_from_u64(font->units_per_em))
|
||||
* @param origin_x Pixel-space X (Q48.16) of the glyph's (0,0) font origin
|
||||
* within `out`
|
||||
* @param origin_y Pixel-space Y (Q48.16) of the glyph's baseline (font
|
||||
* y=0) within `out`; font Y-up is flipped to raster
|
||||
* Y-down internally
|
||||
* @return TTF_OK, or a TTF_ERR_* code (including TTF_ERR_TOO_MANY_POINTS/
|
||||
* _CONTOURS if the flattened outline exceeds the local buffer)
|
||||
*/
|
||||
int ttf_rasterize_glyph(const ttf_font_t *font, uint32_t glyph_index,
|
||||
q48_16_t scale, q48_16_t origin_x, q48_16_t origin_y,
|
||||
uint8_t fill_value, ttf_bitmap_t *out);
|
||||
|
||||
/* Glyph raster cache (FABRIC-0.md item 4.3.7d). Fixed-size, caller-owned
|
||||
* slot array -- no allocation, same convention as the rest of this
|
||||
* module. Every cached bitmap is a fixed TTF_CACHE_BITMAP_DIM square,
|
||||
* rasterized with the fixed origin (TTF_CACHE_MARGIN,
|
||||
* size_px + TTF_CACHE_MARGIN) -- i.e. glyph (0,0)/baseline sits at that
|
||||
* pixel within the bitmap on every cache entry, not just fitted to each
|
||||
* glyph's own bounding box. Callers positioning text (4.3.7e) need to
|
||||
* know this fixed convention. */
|
||||
#define TTF_CACHE_MAX_SIZE_PX 64
|
||||
#define TTF_CACHE_MARGIN 8
|
||||
#define TTF_CACHE_BITMAP_DIM (TTF_CACHE_MAX_SIZE_PX + TTF_CACHE_MARGIN * 2)
|
||||
#define TTF_CACHE_BITMAP_BYTES (TTF_CACHE_BITMAP_DIM * TTF_CACHE_BITMAP_DIM)
|
||||
|
||||
typedef struct {
|
||||
int valid;
|
||||
const ttf_font_t *font;
|
||||
uint32_t codepoint;
|
||||
uint32_t size_px;
|
||||
uint32_t width, height;
|
||||
uint32_t hits; /* incremented on every cache hit; 4.3.7d's own
|
||||
* "measurable" verification reads this. */
|
||||
uint8_t pixels[TTF_CACHE_BITMAP_BYTES];
|
||||
} ttf_raster_cache_slot_t;
|
||||
|
||||
typedef struct {
|
||||
ttf_raster_cache_slot_t *slots;
|
||||
uint32_t slot_count;
|
||||
uint32_t evict_next; /* round-robin index used once every slot is full */
|
||||
} ttf_raster_cache_t;
|
||||
|
||||
/** ttf_raster_cache_init - Bind a caller-supplied slot array to `cache`
|
||||
* and mark every slot empty. */
|
||||
void ttf_raster_cache_init(ttf_raster_cache_t *cache, ttf_raster_cache_slot_t *slots,
|
||||
uint32_t slot_count);
|
||||
|
||||
/**
|
||||
* ttf_raster_cache_get - Look up (font, codepoint, size_px); on a miss,
|
||||
* rasterize and insert (evicting round-robin if every slot is full).
|
||||
* `out` borrows the winning slot's buffer directly -- valid until that
|
||||
* slot is evicted by a later call.
|
||||
*
|
||||
* @param was_hit If non-NULL, set to 1 on a cache hit, 0 if this call
|
||||
* rasterized and inserted
|
||||
* @return TTF_OK, TTF_ERR_UNSUPPORTED if size_px > TTF_CACHE_MAX_SIZE_PX,
|
||||
* or a ttf_rasterize_glyph() TTF_ERR_* code on a miss that failed
|
||||
* to rasterize
|
||||
*/
|
||||
int ttf_raster_cache_get(ttf_raster_cache_t *cache, const ttf_font_t *font,
|
||||
uint32_t codepoint, uint32_t size_px,
|
||||
ttf_bitmap_t *out, int *was_hit);
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
#include "capsule.h"
|
||||
|
||||
/**
|
||||
* ttf_load_from_capsule - Resolve a font capsule by name and parse it,
|
||||
* zero-copy (FABRIC-0.md item 4.3.7b). `out` borrows the capsule payload
|
||||
* directly from `arena` — no kmalloc, no decode step, since the capsule
|
||||
* is already a raw-byte match of the source `.ttf` (see
|
||||
* capsules/fonts/README.md and FABRIC-0.md §27.7's 2026-08-10 correction:
|
||||
* capsule storage needs no hex/base64 text-encoding, `tools/mkcapsule.c`
|
||||
* already embeds arbitrary files as raw bytes). Validates the capsule's
|
||||
* content hash (`capsule_validate(..., verify_hash=1)`) before parsing.
|
||||
*
|
||||
* @param capsule_name Colon-separated capsule name, e.g.
|
||||
* "fonts:JetBrainsMono-Regular.ttf"
|
||||
* @return TTF_OK, TTF_ERR_TABLE_MISSING if the capsule isn't found, or a
|
||||
* capsule-validation/ttf_parse TTF_ERR_* code
|
||||
*/
|
||||
int ttf_load_from_capsule(
|
||||
const CapsuleDirHeader *dir,
|
||||
const CapsuleDesc *descs,
|
||||
const CapsuleNameEntry *names,
|
||||
const uint8_t *arena,
|
||||
const char *capsule_name,
|
||||
ttf_font_t *out);
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_TTF_H */
|
||||
@@ -0,0 +1,661 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* uefi.h - UEFI definitions for StarKernel
|
||||
* Minimal UEFI interface for bare-metal boot
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_UEFI_H
|
||||
#define STARKERNEL_UEFI_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* UEFI Basic Types */
|
||||
typedef uint64_t UINTN;
|
||||
typedef int64_t INTN;
|
||||
typedef uint8_t UINT8;
|
||||
typedef uint16_t UINT16;
|
||||
typedef uint32_t UINT32;
|
||||
typedef uint64_t UINT64;
|
||||
typedef int8_t INT8;
|
||||
typedef int16_t INT16;
|
||||
typedef int32_t INT32;
|
||||
typedef int64_t INT64;
|
||||
typedef uint8_t BOOLEAN;
|
||||
typedef void VOID;
|
||||
typedef uint16_t CHAR16;
|
||||
typedef UINTN EFI_TPL;
|
||||
|
||||
#define TRUE ((BOOLEAN)1)
|
||||
#define FALSE ((BOOLEAN)0)
|
||||
|
||||
#define TPL_APPLICATION 4
|
||||
#define TPL_CALLBACK 8
|
||||
#define TPL_NOTIFY 16
|
||||
#define TPL_HIGH_LEVEL 31
|
||||
|
||||
/* Calling convention */
|
||||
#if defined(__x86_64__) || defined(__i386__)
|
||||
#define EFIAPI __attribute__((ms_abi))
|
||||
#else
|
||||
#define EFIAPI
|
||||
#endif
|
||||
|
||||
/* EFI Status Codes */
|
||||
typedef UINTN EFI_STATUS;
|
||||
#define EFI_SUCCESS 0
|
||||
#define EFI_LOAD_ERROR (1 | (1ULL << 63))
|
||||
#define EFI_INVALID_PARAMETER (2 | (1ULL << 63))
|
||||
#define EFI_UNSUPPORTED (3 | (1ULL << 63))
|
||||
#define EFI_BAD_BUFFER_SIZE (4 | (1ULL << 63))
|
||||
#define EFI_BUFFER_TOO_SMALL (5 | (1ULL << 63))
|
||||
#define EFI_NOT_READY (6 | (1ULL << 63))
|
||||
#define EFI_DEVICE_ERROR (7 | (1ULL << 63))
|
||||
#define EFI_WRITE_PROTECTED (8 | (1ULL << 63))
|
||||
#define EFI_OUT_OF_RESOURCES (9 | (1ULL << 63))
|
||||
#define EFI_NOT_FOUND (14 | (1ULL << 63))
|
||||
#define EFI_ABORTED (21 | (1ULL << 63))
|
||||
|
||||
/* EFI Handle */
|
||||
typedef void* EFI_HANDLE;
|
||||
|
||||
/* EFI Physical Address */
|
||||
typedef UINT64 EFI_PHYSICAL_ADDRESS;
|
||||
|
||||
/* Allocation types for AllocatePages */
|
||||
typedef enum {
|
||||
AllocateAnyPages,
|
||||
AllocateMaxAddress,
|
||||
AllocateAddress,
|
||||
MaxAllocateType
|
||||
} EFI_ALLOCATE_TYPE;
|
||||
|
||||
/* EFI GUID */
|
||||
typedef struct {
|
||||
UINT32 Data1;
|
||||
UINT16 Data2;
|
||||
UINT16 Data3;
|
||||
UINT8 Data4[8];
|
||||
} EFI_GUID;
|
||||
|
||||
/* EFI Memory Types */
|
||||
typedef enum {
|
||||
EfiReservedMemoryType,
|
||||
EfiLoaderCode,
|
||||
EfiLoaderData,
|
||||
EfiBootServicesCode,
|
||||
EfiBootServicesData,
|
||||
EfiRuntimeServicesCode,
|
||||
EfiRuntimeServicesData,
|
||||
EfiConventionalMemory,
|
||||
EfiUnusableMemory,
|
||||
EfiACPIReclaimMemory,
|
||||
EfiACPIMemoryNVS,
|
||||
EfiMemoryMappedIO,
|
||||
EfiMemoryMappedIOPortSpace,
|
||||
EfiPalCode,
|
||||
EfiPersistentMemory,
|
||||
EfiMaxMemoryType
|
||||
} EFI_MEMORY_TYPE;
|
||||
|
||||
/* EFI Memory Descriptor */
|
||||
typedef struct {
|
||||
UINT32 Type;
|
||||
UINT64 PhysicalStart;
|
||||
UINT64 VirtualStart;
|
||||
UINT64 NumberOfPages;
|
||||
UINT64 Attribute;
|
||||
} EFI_MEMORY_DESCRIPTOR;
|
||||
|
||||
/* Memory Attributes */
|
||||
#define EFI_MEMORY_UC 0x0000000000000001ULL
|
||||
#define EFI_MEMORY_WC 0x0000000000000002ULL
|
||||
#define EFI_MEMORY_WT 0x0000000000000004ULL
|
||||
#define EFI_MEMORY_WB 0x0000000000000008ULL
|
||||
#define EFI_MEMORY_UCE 0x0000000000000010ULL
|
||||
#define EFI_MEMORY_WP 0x0000000000001000ULL
|
||||
#define EFI_MEMORY_RP 0x0000000000002000ULL
|
||||
#define EFI_MEMORY_XP 0x0000000000004000ULL
|
||||
#define EFI_MEMORY_RUNTIME 0x8000000000000000ULL
|
||||
|
||||
/* EFI Table Header */
|
||||
typedef struct {
|
||||
UINT64 Signature;
|
||||
UINT32 Revision;
|
||||
UINT32 HeaderSize;
|
||||
UINT32 CRC32;
|
||||
UINT32 Reserved;
|
||||
} EFI_TABLE_HEADER;
|
||||
|
||||
/* Forward declarations */
|
||||
struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;
|
||||
struct _EFI_BOOT_SERVICES;
|
||||
struct _EFI_RUNTIME_SERVICES;
|
||||
|
||||
/* Simple Text Output Protocol */
|
||||
typedef EFI_STATUS (EFIAPI *EFI_TEXT_STRING)(
|
||||
struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
|
||||
CHAR16 *String
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_TEXT_RESET)(
|
||||
struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *This,
|
||||
BOOLEAN ExtendedVerification
|
||||
);
|
||||
|
||||
typedef struct _EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL {
|
||||
EFI_TEXT_RESET Reset;
|
||||
EFI_TEXT_STRING OutputString;
|
||||
void *TestString;
|
||||
void *QueryMode;
|
||||
void *SetMode;
|
||||
void *SetAttribute;
|
||||
void *ClearScreen;
|
||||
void *SetCursorPosition;
|
||||
void *EnableCursor;
|
||||
void *Mode;
|
||||
} EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL;
|
||||
|
||||
/* Boot Services */
|
||||
typedef EFI_STATUS (EFIAPI *EFI_GET_MEMORY_MAP)(
|
||||
UINTN *MemoryMapSize,
|
||||
EFI_MEMORY_DESCRIPTOR *MemoryMap,
|
||||
UINTN *MapKey,
|
||||
UINTN *DescriptorSize,
|
||||
UINT32 *DescriptorVersion
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_EXIT_BOOT_SERVICES)(
|
||||
EFI_HANDLE ImageHandle,
|
||||
UINTN MapKey
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_ALLOCATE_POOL)(
|
||||
EFI_MEMORY_TYPE PoolType,
|
||||
UINTN Size,
|
||||
void **Buffer
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_FREE_POOL)(
|
||||
void *Buffer
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_ALLOCATE_PAGES)(
|
||||
EFI_ALLOCATE_TYPE Type,
|
||||
EFI_MEMORY_TYPE MemoryType,
|
||||
UINTN Pages,
|
||||
EFI_PHYSICAL_ADDRESS *Memory
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_FREE_PAGES)(
|
||||
EFI_PHYSICAL_ADDRESS Memory,
|
||||
UINTN Pages
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_HANDLE_PROTOCOL)(
|
||||
EFI_HANDLE Handle,
|
||||
EFI_GUID *Protocol,
|
||||
void **Interface
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_STALL)(
|
||||
UINTN Microseconds
|
||||
);
|
||||
|
||||
/* Search types for LocateHandle */
|
||||
typedef enum {
|
||||
AllHandles,
|
||||
ByRegisterNotify,
|
||||
ByProtocol
|
||||
} EFI_LOCATE_SEARCH_TYPE;
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_LOCATE_HANDLE)(
|
||||
EFI_LOCATE_SEARCH_TYPE SearchType,
|
||||
EFI_GUID *Protocol,
|
||||
void *SearchKey,
|
||||
UINTN *BufferSize,
|
||||
EFI_HANDLE *Buffer
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_LOCATE_PROTOCOL)(
|
||||
EFI_GUID *Protocol,
|
||||
void *Registration,
|
||||
void **Interface
|
||||
);
|
||||
|
||||
typedef EFI_TPL (EFIAPI *EFI_RAISE_TPL)(
|
||||
EFI_TPL NewTpl
|
||||
);
|
||||
|
||||
typedef void (EFIAPI *EFI_RESTORE_TPL)(
|
||||
EFI_TPL OldTpl
|
||||
);
|
||||
|
||||
typedef struct _EFI_BOOT_SERVICES {
|
||||
EFI_TABLE_HEADER Hdr;
|
||||
|
||||
/* Task Priority Services */
|
||||
EFI_RAISE_TPL RaiseTPL;
|
||||
EFI_RESTORE_TPL RestoreTPL;
|
||||
|
||||
/* Memory Services */
|
||||
EFI_ALLOCATE_PAGES AllocatePages;
|
||||
EFI_FREE_PAGES FreePages;
|
||||
EFI_GET_MEMORY_MAP GetMemoryMap;
|
||||
EFI_ALLOCATE_POOL AllocatePool;
|
||||
EFI_FREE_POOL FreePool;
|
||||
|
||||
/* Event & Timer Services */
|
||||
void *CreateEvent;
|
||||
void *SetTimer;
|
||||
void *WaitForEvent;
|
||||
void *SignalEvent;
|
||||
void *CloseEvent;
|
||||
void *CheckEvent;
|
||||
|
||||
/* Protocol Handler Services */
|
||||
void *InstallProtocolInterface;
|
||||
void *ReinstallProtocolInterface;
|
||||
void *UninstallProtocolInterface;
|
||||
EFI_HANDLE_PROTOCOL HandleProtocol;
|
||||
void *Reserved;
|
||||
void *RegisterProtocolNotify;
|
||||
EFI_LOCATE_HANDLE LocateHandle;
|
||||
void *LocateDevicePath;
|
||||
void *InstallConfigurationTable;
|
||||
|
||||
/* Image Services */
|
||||
void *LoadImage;
|
||||
void *StartImage;
|
||||
void *Exit;
|
||||
void *UnloadImage;
|
||||
EFI_EXIT_BOOT_SERVICES ExitBootServices;
|
||||
|
||||
/* Misc Services */
|
||||
void *GetNextMonotonicCount;
|
||||
EFI_STALL Stall;
|
||||
void *SetWatchdogTimer;
|
||||
|
||||
/* Driver Support Services */
|
||||
void *ConnectController;
|
||||
void *DisconnectController;
|
||||
|
||||
/* Open/Close Protocol Services */
|
||||
void *OpenProtocol;
|
||||
void *CloseProtocol;
|
||||
void *OpenProtocolInformation;
|
||||
|
||||
/* Library Services */
|
||||
void *ProtocolsPerHandle;
|
||||
void *LocateHandleBuffer;
|
||||
EFI_LOCATE_PROTOCOL LocateProtocol;
|
||||
} EFI_BOOT_SERVICES;
|
||||
|
||||
/* Runtime Services */
|
||||
typedef struct _EFI_RUNTIME_SERVICES {
|
||||
EFI_TABLE_HEADER Hdr;
|
||||
|
||||
/* Time Services */
|
||||
void *GetTime;
|
||||
void *SetTime;
|
||||
void *GetWakeupTime;
|
||||
void *SetWakeupTime;
|
||||
|
||||
/* Virtual Memory Services */
|
||||
void *SetVirtualAddressMap;
|
||||
void *ConvertPointer;
|
||||
|
||||
/* Variable Services */
|
||||
void *GetVariable;
|
||||
void *GetNextVariableName;
|
||||
void *SetVariable;
|
||||
|
||||
/* Misc Services */
|
||||
void *GetNextHighMonotonicCount;
|
||||
void *ResetSystem;
|
||||
} EFI_RUNTIME_SERVICES;
|
||||
|
||||
/* System Table */
|
||||
typedef struct _EFI_SYSTEM_TABLE {
|
||||
EFI_TABLE_HEADER Hdr;
|
||||
CHAR16 *FirmwareVendor;
|
||||
UINT32 FirmwareRevision;
|
||||
EFI_HANDLE ConsoleInHandle;
|
||||
void *ConIn;
|
||||
EFI_HANDLE ConsoleOutHandle;
|
||||
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *ConOut;
|
||||
EFI_HANDLE StandardErrorHandle;
|
||||
EFI_SIMPLE_TEXT_OUTPUT_PROTOCOL *StdErr;
|
||||
EFI_RUNTIME_SERVICES *RuntimeServices;
|
||||
EFI_BOOT_SERVICES *BootServices;
|
||||
UINTN NumberOfTableEntries;
|
||||
void *ConfigurationTable;
|
||||
} EFI_SYSTEM_TABLE;
|
||||
|
||||
/* Configuration Table */
|
||||
typedef struct {
|
||||
EFI_GUID VendorGuid;
|
||||
void *VendorTable;
|
||||
} EFI_CONFIGURATION_TABLE;
|
||||
|
||||
/* ACPI GUIDs */
|
||||
static const EFI_GUID EFI_ACPI_20_TABLE_GUID = {0x8868e871,0xe4f1,0x11d3,{0xbc,0x22,0x00,0x80,0xc7,0x3c,0x88,0x81}};
|
||||
static const EFI_GUID EFI_ACPI_TABLE_GUID = {0xeb9d2d30,0x2d88,0x11d3,{0x9a,0x16,0x00,0x90,0x27,0x3f,0xc1,0x4d}};
|
||||
|
||||
/* Devicetree Blob GUID (UEFI 2.10 §4.6, "Devicetree Tables").
|
||||
* On QEMU virt for riscv64 and aarch64 the firmware publishes the FDT here;
|
||||
* it is the only route to timebase-frequency (riscv64, item 0.3) and to the
|
||||
* GIC base addresses and timer PPI (aarch64, item 0.6). */
|
||||
static const EFI_GUID EFI_DTB_TABLE_GUID = {
|
||||
0xb1b621d5, 0xf19c, 0x41a5, {0x83, 0x0b, 0xd9, 0x15, 0x2c, 0x69, 0xaa, 0xe0}
|
||||
};
|
||||
|
||||
/* Graphics Output Protocol */
|
||||
typedef enum {
|
||||
PixelRedGreenBlueReserved8BitPerColor,
|
||||
PixelBlueGreenRedReserved8BitPerColor,
|
||||
PixelBitMask,
|
||||
PixelBltOnly,
|
||||
PixelFormatMax
|
||||
} EFI_GRAPHICS_PIXEL_FORMAT;
|
||||
|
||||
typedef struct {
|
||||
UINT32 RedMask;
|
||||
UINT32 GreenMask;
|
||||
UINT32 BlueMask;
|
||||
UINT32 ReservedMask;
|
||||
} EFI_PIXEL_BITMASK;
|
||||
|
||||
typedef struct {
|
||||
UINT32 Version;
|
||||
UINT32 HorizontalResolution;
|
||||
UINT32 VerticalResolution;
|
||||
EFI_GRAPHICS_PIXEL_FORMAT PixelFormat;
|
||||
EFI_PIXEL_BITMASK PixelInformation;
|
||||
UINT32 PixelsPerScanLine;
|
||||
} EFI_GRAPHICS_OUTPUT_MODE_INFORMATION;
|
||||
|
||||
typedef struct {
|
||||
UINT32 MaxMode;
|
||||
UINT32 Mode;
|
||||
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION *Info;
|
||||
UINTN SizeOfInfo;
|
||||
EFI_PHYSICAL_ADDRESS FrameBufferBase;
|
||||
UINTN FrameBufferSize;
|
||||
} EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE;
|
||||
|
||||
struct _EFI_GRAPHICS_OUTPUT_PROTOCOL;
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE)(
|
||||
struct _EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
|
||||
UINT32 ModeNumber,
|
||||
UINTN *SizeOfInfo,
|
||||
EFI_GRAPHICS_OUTPUT_MODE_INFORMATION **Info
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE)(
|
||||
struct _EFI_GRAPHICS_OUTPUT_PROTOCOL *This,
|
||||
UINT32 ModeNumber
|
||||
);
|
||||
|
||||
typedef struct _EFI_GRAPHICS_OUTPUT_PROTOCOL {
|
||||
EFI_GRAPHICS_OUTPUT_PROTOCOL_QUERY_MODE QueryMode;
|
||||
EFI_GRAPHICS_OUTPUT_PROTOCOL_SET_MODE SetMode;
|
||||
void *Blt;
|
||||
EFI_GRAPHICS_OUTPUT_PROTOCOL_MODE *Mode;
|
||||
} EFI_GRAPHICS_OUTPUT_PROTOCOL;
|
||||
|
||||
static const EFI_GUID EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID =
|
||||
{0x9042a9de, 0x23dc, 0x4a38, {0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a}};
|
||||
|
||||
/* Framebuffer info (from GOP, passed to kernel) */
|
||||
typedef struct {
|
||||
void *base;
|
||||
UINTN size;
|
||||
UINT32 width;
|
||||
UINT32 height;
|
||||
UINT32 pixels_per_scanline;
|
||||
UINT32 pixel_format; /* EFI_GRAPHICS_PIXEL_FORMAT value */
|
||||
} FramebufferInfo;
|
||||
|
||||
/* File System Protocols (needed for loader) */
|
||||
static const EFI_GUID EFI_LOADED_IMAGE_PROTOCOL_GUID =
|
||||
{0x5B1B31A1,0x9562,0x11d2, {0x8E,0x3F,0x00,0xA0,0xC9,0x69,0x72,0x3B}};
|
||||
|
||||
static const EFI_GUID EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_GUID =
|
||||
{0x0964e5b22,0x6459,0x11d2, {0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b}};
|
||||
|
||||
static const EFI_GUID EFI_FILE_INFO_GUID =
|
||||
{0x09576e92,0x6d3f,0x11d2, {0x8e,0x39,0x00,0xa0,0xc9,0x69,0x72,0x3b}};
|
||||
|
||||
#define EFI_FILE_MODE_READ 0x0000000000000001ULL
|
||||
|
||||
struct _EFI_FILE_PROTOCOL;
|
||||
struct _EFI_SIMPLE_FILE_SYSTEM_PROTOCOL;
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_FILE_OPEN)(
|
||||
struct _EFI_FILE_PROTOCOL *This,
|
||||
struct _EFI_FILE_PROTOCOL **NewHandle,
|
||||
CHAR16 *FileName,
|
||||
UINT64 OpenMode,
|
||||
UINT64 Attributes
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_FILE_CLOSE)(
|
||||
struct _EFI_FILE_PROTOCOL *This
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_FILE_READ)(
|
||||
struct _EFI_FILE_PROTOCOL *This,
|
||||
UINTN *BufferSize,
|
||||
VOID *Buffer
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_FILE_GET_INFO)(
|
||||
struct _EFI_FILE_PROTOCOL *This,
|
||||
EFI_GUID *InformationType,
|
||||
UINTN *BufferSize,
|
||||
VOID *Buffer
|
||||
);
|
||||
|
||||
typedef struct _EFI_FILE_PROTOCOL {
|
||||
UINT64 Revision;
|
||||
EFI_FILE_OPEN Open;
|
||||
EFI_FILE_CLOSE Close;
|
||||
VOID *Delete;
|
||||
EFI_FILE_READ Read;
|
||||
VOID *Write;
|
||||
VOID *GetPosition;
|
||||
VOID *SetPosition;
|
||||
EFI_FILE_GET_INFO GetInfo;
|
||||
VOID *SetInfo;
|
||||
VOID *Flush;
|
||||
} EFI_FILE_PROTOCOL;
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME)(
|
||||
struct _EFI_SIMPLE_FILE_SYSTEM_PROTOCOL *This,
|
||||
EFI_FILE_PROTOCOL **Root
|
||||
);
|
||||
|
||||
typedef struct _EFI_SIMPLE_FILE_SYSTEM_PROTOCOL {
|
||||
UINT64 Revision;
|
||||
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL_OPEN_VOLUME OpenVolume;
|
||||
} EFI_SIMPLE_FILE_SYSTEM_PROTOCOL;
|
||||
|
||||
typedef struct {
|
||||
UINT16 Year;
|
||||
UINT8 Month;
|
||||
UINT8 Day;
|
||||
UINT8 Hour;
|
||||
UINT8 Minute;
|
||||
UINT8 Second;
|
||||
UINT8 Pad1;
|
||||
UINT32 Nanosecond;
|
||||
INT16 TimeZone;
|
||||
UINT8 Daylight;
|
||||
UINT8 Pad2;
|
||||
} EFI_TIME;
|
||||
|
||||
typedef struct {
|
||||
UINT64 Size;
|
||||
UINT64 FileSize;
|
||||
UINT64 PhysicalSize;
|
||||
EFI_TIME CreateTime;
|
||||
EFI_TIME LastAccessTime;
|
||||
EFI_TIME ModificationTime;
|
||||
UINT64 Attribute;
|
||||
CHAR16 FileName[256];
|
||||
} EFI_FILE_INFO;
|
||||
|
||||
/* EFI_LOADED_IMAGE_PROTOCOL — spec-compliant layout (UEFI 2.x §8.1)
|
||||
* Offsets (64-bit):
|
||||
* 0: Revision (4) + 4 pad
|
||||
* 8: ParentHandle (8)
|
||||
* 16: SystemTable (8)
|
||||
* 24: DeviceHandle (8)
|
||||
* 32: FilePath (8)
|
||||
* 40: Reserved (8)
|
||||
* 48: LoadOptionsSize (4) + 4 pad
|
||||
* 56: LoadOptions (8)
|
||||
* 64: ImageBase (8)
|
||||
* 72: ImageSize (8)
|
||||
* 80: ImageCodeType (4)
|
||||
* 84: ImageDataType (4)
|
||||
* 88: Unload (8)
|
||||
*/
|
||||
typedef struct {
|
||||
UINT32 Revision;
|
||||
EFI_HANDLE ParentHandle;
|
||||
void *SystemTable;
|
||||
EFI_HANDLE DeviceHandle;
|
||||
VOID *FilePath;
|
||||
VOID *Reserved;
|
||||
UINT32 LoadOptionsSize;
|
||||
VOID *LoadOptions;
|
||||
VOID *ImageBase;
|
||||
UINT64 ImageSize;
|
||||
EFI_MEMORY_TYPE ImageCodeType;
|
||||
EFI_MEMORY_TYPE ImageDataType;
|
||||
VOID *Unload;
|
||||
} EFI_LOADED_IMAGE_PROTOCOL;
|
||||
|
||||
/* ---- UEFI Variable Services typedefs ----------------------------------- */
|
||||
|
||||
#define EFI_VARIABLE_NON_VOLATILE 0x00000001U
|
||||
#define EFI_VARIABLE_BOOTSERVICE_ACCESS 0x00000002U
|
||||
#define EFI_VARIABLE_RUNTIME_ACCESS 0x00000004U
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_GET_VARIABLE)(
|
||||
CHAR16 *VariableName,
|
||||
EFI_GUID *VendorGuid,
|
||||
UINT32 *Attributes,
|
||||
UINTN *DataSize,
|
||||
void *Data
|
||||
);
|
||||
|
||||
typedef EFI_STATUS (EFIAPI *EFI_SET_VARIABLE)(
|
||||
CHAR16 *VariableName,
|
||||
EFI_GUID *VendorGuid,
|
||||
UINT32 Attributes,
|
||||
UINTN DataSize,
|
||||
void *Data
|
||||
);
|
||||
|
||||
typedef enum {
|
||||
EfiResetCold,
|
||||
EfiResetWarm,
|
||||
EfiResetShutdown
|
||||
} EFI_RESET_TYPE;
|
||||
|
||||
typedef void (EFIAPI *EFI_RESET_SYSTEM)(
|
||||
EFI_RESET_TYPE ResetType,
|
||||
EFI_STATUS ResetStatus,
|
||||
UINTN DataSize,
|
||||
void *ResetData
|
||||
);
|
||||
|
||||
/* ---- StarForth vendor GUID (for NVRAM variables) ----------------------- */
|
||||
/* {b7e42c1a-4f3d-4e8b-9c5a-1d2f6a8b3e70} */
|
||||
#define STARFORTH_VENDOR_GUID \
|
||||
{0xb7e42c1aU, 0x4f3dU, 0x4e8bU, \
|
||||
{0x9cU, 0x5aU, 0x1dU, 0x2fU, 0x6aU, 0x8bU, 0x3eU, 0x70U}}
|
||||
|
||||
/* NVRAM variable names (UCS-2 string literals) */
|
||||
#define SF_VAR_BOOT_ARGS L"StarForthBootArgs"
|
||||
#define SF_VAR_REBOOT_TRIES L"StarForthRebootTries"
|
||||
#define SF_VAR_ZUSE_CERT L"StarForthZuseCert" /* 64 bytes: 32-byte Ed25519
|
||||
* seed || 32-byte pubkey.
|
||||
* Written exactly once
|
||||
* (Phase 8 first-boot mint,
|
||||
* see FABRIC-2.md) --
|
||||
* presence means the fuse
|
||||
* is already blown. */
|
||||
|
||||
/* ---- BootInfo extension ------------------------------------------------ */
|
||||
#include "kernel_args.h"
|
||||
|
||||
/* Boot Info Structure (passed to kernel) */
|
||||
typedef struct {
|
||||
EFI_MEMORY_DESCRIPTOR *memory_map;
|
||||
UINTN memory_map_size;
|
||||
UINTN memory_map_descriptor_size;
|
||||
EFI_RUNTIME_SERVICES *runtime_services;
|
||||
void *acpi_table;
|
||||
/* Devicetree blob, located by EFI_DTB_TABLE_GUID in the configuration
|
||||
* table; NULL when the firmware publishes none (amd64 typically, and any
|
||||
* platform that is ACPI-only). Consumers must handle NULL rather than
|
||||
* assume presence. */
|
||||
void* dtb;
|
||||
FramebufferInfo framebuffer;
|
||||
UINT8 uefi_boot_services_exited;
|
||||
|
||||
/* Loader-allocated kernel stack (zero = fall back to 2 MiB BSS stack) */
|
||||
void *kernel_stack_base;
|
||||
uint64_t kernel_stack_size;
|
||||
|
||||
/* Parsed boot command-line arguments */
|
||||
KernelArgs args;
|
||||
} BootInfo;
|
||||
|
||||
#endif /* STARKERNEL_UEFI_H */
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* user_identity_seed.h -- on-disk record format for a minted user
|
||||
* identity's own keypair and profile (FABRIC-2.md §F.8/§F.20), stored in
|
||||
* the first devblock of a home-blocks drive's identity_src region
|
||||
* (homeblocks_sig_t.identity_src_offset). The devblocks that follow it
|
||||
* (identity_src_offset+1 .. identity_src_offset+identity_src_devblocks-1)
|
||||
* hold this identity's own raw FORTH personality/init source, read by
|
||||
* RUNCAP (capsule_runcap.h) at birth.
|
||||
*
|
||||
* Same raw-devblock, magic+version+fields+pad-to-4096, real-CRC-from-
|
||||
* day-one convention as zuse_cert_devblock_t (zuse_cert_devblock.h) and
|
||||
* homeblocks_sig_t -- a new, dedicated type rather than reusing
|
||||
* zuse_cert_devblock_t, per this project's "give real-shaped data its
|
||||
* own header" convention (Zuse's own record has no analogous public
|
||||
* cert field; a regular user's does, stored separately in the cert
|
||||
* region MINT also writes -- see homeblocks_sig_t.cert_offset).
|
||||
*
|
||||
* full_name/username/email/phone (added §F.20, 2026-08-28): deliberately
|
||||
* NOT encoded into the DER cert's Subject field -- that would mean
|
||||
* building a real X.509 RDNSequence (AttributeTypeAndValue, OIDs for
|
||||
* commonName/emailAddress, PrintableString/UTF8String tagging), well
|
||||
* past this project's own stated "deliberately NOT a general ASN.1/X.509
|
||||
* parser [or builder]" scope (x509_ed25519.h). This human-readable
|
||||
* profile data isn't security-relevant the way pubkey/serial are (those
|
||||
* two alone are what CERTVERIFY/BINDSTEP actually check) -- it travels
|
||||
* alongside the keypair in this plain record instead. email/phone are
|
||||
* nullable (empty string, first byte 0x00); full_name/username are not.
|
||||
*/
|
||||
#ifndef STARKERNEL_USER_IDENTITY_SEED_H
|
||||
#define STARKERNEL_USER_IDENTITY_SEED_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define USER_IDENTITY_SEED_MAGIC \
|
||||
((uint32_t)'U' | ((uint32_t)'I' << 8) | ((uint32_t)'D' << 16) | ((uint32_t)'S' << 24))
|
||||
|
||||
#define USER_IDENTITY_SEED_VERSION 2u
|
||||
|
||||
#define USER_IDENTITY_FULL_NAME_MAX 64u
|
||||
#define USER_IDENTITY_USERNAME_MAX 32u
|
||||
#define USER_IDENTITY_EMAIL_MAX 64u
|
||||
#define USER_IDENTITY_PHONE_MAX 24u
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic; /* USER_IDENTITY_SEED_MAGIC; anything else means
|
||||
* "not yet minted" (blank/foreign bytes), not a
|
||||
* format-corruption error. */
|
||||
uint32_t version; /* USER_IDENTITY_SEED_VERSION */
|
||||
uint8_t seed[32]; /* Ed25519 seed -- this identity's own private key.
|
||||
* Regular users are thumbdrive-resident (unlike
|
||||
* Zuse's system-resident one) -- this is the
|
||||
* only copy. */
|
||||
uint8_t pubkey[32]; /* Ed25519 public key derived from seed at mint
|
||||
* time -- same value the cert region's
|
||||
* SubjectPublicKeyInfo holds. */
|
||||
char full_name[USER_IDENTITY_FULL_NAME_MAX]; /* NUL-terminated, required. */
|
||||
char username[USER_IDENTITY_USERNAME_MAX]; /* NUL-terminated, required. */
|
||||
char email[USER_IDENTITY_EMAIL_MAX]; /* NUL-terminated; empty = null. */
|
||||
char phone[USER_IDENTITY_PHONE_MAX]; /* NUL-terminated; empty = null. */
|
||||
uint64_t crc; /* CRC-64/ISO (block_subsystem.h's compute_crc64())
|
||||
* over every byte of this struct up to (not
|
||||
* including) this field. */
|
||||
uint8_t _pad[4096 - (4 + 4 + 32 + 32 +
|
||||
USER_IDENTITY_FULL_NAME_MAX + USER_IDENTITY_USERNAME_MAX +
|
||||
USER_IDENTITY_EMAIL_MAX + USER_IDENTITY_PHONE_MAX + 8)];
|
||||
} user_identity_seed_t;
|
||||
|
||||
typedef char user_identity_seed_size_check[
|
||||
(sizeof(user_identity_seed_t) == 4096) ? 1 : -1];
|
||||
|
||||
#endif /* STARKERNEL_USER_IDENTITY_SEED_H */
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* virtio_blk.h — Virtio 1.0 block device driver for StarKernel
|
||||
*
|
||||
* Presents a blkio_dev_t interface for attachment to the StarForth
|
||||
* block subsystem via blk_subsys_attach_device().
|
||||
*
|
||||
* Only one virtio-blk device is supported (the Artemis disk).
|
||||
* I/O is synchronous (polling) — no interrupts, no DMA descriptors
|
||||
* beyond the split virtqueue shared memory.
|
||||
*
|
||||
* Artemis disk identification heuristic:
|
||||
* The Artemis disk image is 30 MB (61440 sectors). The first virtio-blk
|
||||
* device matching PCI vendor=0x1AF4 / device=0x1042 or 0x1001 whose
|
||||
* sector count equals 61440 is selected. If no exact match, the first
|
||||
* device found is used.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VIRTIO_BLK_H
|
||||
#define STARKERNEL_VIRTIO_BLK_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "blkio.h"
|
||||
|
||||
/* StarForth block size and sector ratio */
|
||||
#define VBLK_SECTOR_SIZE 512u
|
||||
#define VBLK_SECTORS_PER_BLOK 2u /* 1 KiB Forth block = 2 × 512-byte sectors */
|
||||
#define VBLK_ARTEMIS_SECTORS 61440u /* 30 MB */
|
||||
|
||||
/* virtio PCI vendor */
|
||||
#define VIRTIO_PCI_VENDOR_ID 0x1AF4u
|
||||
/* device IDs */
|
||||
#define VIRTIO_BLK_DEVICE_MODERN 0x1042u
|
||||
#define VIRTIO_BLK_DEVICE_LEGACY 0x1001u
|
||||
|
||||
/* virtio-blk request types */
|
||||
#define VIRTIO_BLK_T_IN 0u
|
||||
#define VIRTIO_BLK_T_OUT 1u
|
||||
|
||||
/* virtio-blk status codes (device → driver) */
|
||||
#define VIRTIO_BLK_S_OK 0u
|
||||
#define VIRTIO_BLK_S_IOERR 1u
|
||||
#define VIRTIO_BLK_S_UNSUPP 2u
|
||||
|
||||
/*
|
||||
* virtio_blk_find_artemis — locate the Artemis virtio-blk disk, initialise
|
||||
* the driver, and fill in *dev_out so the caller
|
||||
* can pass it to blk_subsys_attach_device().
|
||||
*
|
||||
* dev_out must point to a zero-initialised blkio_dev_t.
|
||||
*
|
||||
* Returns 0 on success.
|
||||
* Returns -1 if no virtio-blk device was found on the PCI bus.
|
||||
* Returns -2 on driver initialisation failure (bad BAR, queue setup, etc.).
|
||||
*/
|
||||
int virtio_blk_find_artemis(blkio_dev_t *dev_out);
|
||||
|
||||
#endif /* STARKERNEL_VIRTIO_BLK_H */
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* virtio_input.h — Virtio 1.0 input device driver for StarKernel (item 4.3.5c)
|
||||
*
|
||||
* riscv64-only today (PCI transport, PLIC interrupt routing). aarch64's
|
||||
* 4.3.5e reuses this device identity/event shape over the same PCI
|
||||
* transport with GIC routing instead; riscv64/aarch64-specific pieces stay
|
||||
* in their own arch trees, not here.
|
||||
*
|
||||
* Interrupt-driven end to end: the driver pre-posts empty event buffers
|
||||
* into the eventq and the device fills+posts them asynchronously, signalled
|
||||
* by the PLIC (via 4.3.5b's claim/complete substrate) — no polling.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VIRTIO_INPUT_H
|
||||
#define STARKERNEL_VIRTIO_INPUT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* virtio PCI vendor (same device family as virtio-blk) */
|
||||
#define VIRTIO_INPUT_PCI_VENDOR_ID 0x1AF4u
|
||||
/* Modern-ID formula 0x1040 + VIRTIO_ID_INPUT, VIRTIO_ID_INPUT = 18, per
|
||||
* this build host's /usr/include/linux/virtio_ids.h. No legacy/transitional
|
||||
* ID exists for virtio-input (postdates the legacy 0.9.5 spec). */
|
||||
#define VIRTIO_INPUT_PCI_DEVICE_ID 0x1052u
|
||||
|
||||
/* struct virtio_input_event, per /usr/include/linux/virtio_input.h (BSD
|
||||
* licensed, same wire format this driver decodes). */
|
||||
typedef struct {
|
||||
uint16_t type;
|
||||
uint16_t code;
|
||||
uint32_t value;
|
||||
} VirtioInputEvent;
|
||||
|
||||
/* EV_KEY, per /usr/include/linux/input-event-codes.h */
|
||||
#define VIRTIO_INPUT_EV_KEY 0x01u
|
||||
|
||||
/*
|
||||
* virtio_input_find_keyboard — locate a virtio-keyboard-pci device on the
|
||||
* PCI bus, initialise the driver, compute and
|
||||
* enable its PLIC interrupt source (item
|
||||
* 4.3.5c's own derivation, FABRIC-0.md §27.5.2),
|
||||
* and pre-post the eventq's receive buffers.
|
||||
*
|
||||
* Returns 0 on success.
|
||||
* Returns -1 if no virtio-keyboard-pci device was found on the PCI bus.
|
||||
* Returns -2 on driver initialisation failure (bad BAR, queue setup, etc.).
|
||||
*/
|
||||
int virtio_input_find_keyboard(void);
|
||||
|
||||
/*
|
||||
* virtio_input_isr — called from riscv64_interrupt_handler() when the PLIC
|
||||
* claim matches this driver's computed source. Reads the
|
||||
* ISR-status capability (mandatory — the routed source
|
||||
* is level-triggered; skipping this leaves it latched),
|
||||
* drains the used ring, pushes decoded EV_KEY events into
|
||||
* the diagnostic ring buffer (keyboard_words.c), and
|
||||
* re-posts drained buffers to the eventq's avail ring.
|
||||
*/
|
||||
void virtio_input_isr(void);
|
||||
|
||||
/*
|
||||
* virtio_input_pop_event — diagnostic-ring consumer for keyboard_words.c's
|
||||
* VKBD-EVENT, same shape as i8042_pop_scancode().
|
||||
* Returns 1 and fills *code and *value if an event was available, 0 otherwise.
|
||||
*/
|
||||
int virtio_input_pop_event(uint16_t *code, uint32_t *value);
|
||||
|
||||
#endif /* STARKERNEL_VIRTIO_INPUT_H */
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* virtio_rng.h — Virtio 1.0 entropy source driver for StarKernel
|
||||
*
|
||||
* Real hardware/host entropy, uniformly across all three architectures.
|
||||
* Exists specifically because this kernel has no per-arch RNG that covers
|
||||
* all three ISAs: amd64 has RDRAND and riscv64 has the Zkr extension, but
|
||||
* QEMU's aarch64 CPU models (including "max") expose neither RNDR nor any
|
||||
* other RNG property (checked directly against QEMU 10.2.1, see
|
||||
* vm_uuid.h's identical finding). A paravirtualized virtio-rng device
|
||||
* sidesteps the per-ISA gap entirely: the host supplies the entropy, the
|
||||
* guest-side protocol is identical on all three arches.
|
||||
*
|
||||
* QEMU-only: there is no virtio-rng on real hardware (Milestone 8, bare-
|
||||
* metal boot). A real per-arch RNG driver is a separate, later problem.
|
||||
*
|
||||
* Only one virtio-rng device is supported.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VIRTIO_RNG_H
|
||||
#define STARKERNEL_VIRTIO_RNG_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* virtio PCI vendor (shared with every virtio device) */
|
||||
#define VIRTIO_RNG_PCI_VENDOR_ID 0x1AF4u
|
||||
/* device IDs: virtio device id 4 (entropy source) */
|
||||
#define VIRTIO_RNG_DEVICE_MODERN 0x1044u
|
||||
#define VIRTIO_RNG_DEVICE_LEGACY 0x1005u
|
||||
|
||||
/*
|
||||
* virtio_rng_init — locate the virtio-rng device on the PCI bus and bring
|
||||
* the driver up. Call once, before the first virtio_rng_get_bytes() call.
|
||||
*
|
||||
* Returns 0 on success.
|
||||
* Returns -1 if no virtio-rng device was found on the PCI bus.
|
||||
* Returns -2 on driver initialisation failure (bad BAR, queue setup, etc.).
|
||||
*/
|
||||
int virtio_rng_init(void);
|
||||
|
||||
/*
|
||||
* virtio_rng_ready — 1 if virtio_rng_init() has already succeeded.
|
||||
*/
|
||||
int virtio_rng_ready(void);
|
||||
|
||||
/*
|
||||
* virtio_rng_get_bytes — fill buf with n bytes of real host-supplied
|
||||
* entropy, blocking (polling) until all n bytes are obtained. The device
|
||||
* may return fewer bytes than requested per request; this loops
|
||||
* internally until the buffer is full.
|
||||
*
|
||||
* Returns 0 on success (buf fully filled).
|
||||
* Returns -1 if virtio_rng_init() has not succeeded.
|
||||
* Returns -2 on a device/timeout error partway through.
|
||||
*/
|
||||
int virtio_rng_get_bytes(uint8_t *buf, size_t n);
|
||||
|
||||
#endif /* STARKERNEL_VIRTIO_RNG_H */
|
||||
@@ -0,0 +1,13 @@
|
||||
# include/starkernel/vm/
|
||||
|
||||
Headers for the kernel-side VM subsystem (`src/starkernel/vm/`).
|
||||
|
||||
- `arena.h` — the capsule arena allocator: fixed-region allocation for
|
||||
capsule payload data, separate from `kmalloc`'s general kernel heap.
|
||||
- `parity.h` — the birth/execution parity-record logger: declares the
|
||||
logging interface used to record VM ID, capsule content hash, and
|
||||
dictionary hash on every capsule birth, enabling offline
|
||||
determinism verification across independent kernel runs.
|
||||
|
||||
See `include/starkernel/vm/bootstrap/README.md` for the VM bootstrap
|
||||
subsystem.
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* arena.h - VM arena management for StarKernel builds.
|
||||
*
|
||||
* Hosted builds never include this header. Kernel integration layers use it
|
||||
* to provision the PMM-backed VM arena before handing memory to the VM core.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VM_ARENA_H
|
||||
#define STARKERNEL_VM_ARENA_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
uint64_t sk_vm_arena_alloc(void);
|
||||
void sk_vm_arena_free(void);
|
||||
uint8_t *sk_vm_arena_ptr(void);
|
||||
size_t sk_vm_arena_size(void);
|
||||
int sk_vm_arena_is_initialized(void);
|
||||
void sk_vm_arena_assert_guards(const char *tag);
|
||||
|
||||
/**
|
||||
* sk_vm_native_stack_t - one VM's own dedicated native (C) stack.
|
||||
*
|
||||
* FABRIC-3.md §XXVIII (Stage 1, preemptive-context-switch per-VM native
|
||||
* stacks, 2026-09-13). Unlike sk_vm_arena_alloc() -- whose PMM+guard-page
|
||||
* path is exercised only for Hera; every baby VM's "arena" is actually a
|
||||
* plain kmalloc block, per host_services.c's kernel_alloc() -- every native
|
||||
* stack, for every VM without exception, gets its own real, independent
|
||||
* pmm_alloc_contiguous() allocation with guard pages. A stack overflow is
|
||||
* exactly the failure mode guard pages exist for, and unlike the dictionary
|
||||
* arena, a corrupted stack can also corrupt whatever saved context Stage 2
|
||||
* later trusts -- worth the extra PMM pages every VM, not just Hera.
|
||||
*
|
||||
* Caller owns this struct (stored directly on the VM, mirroring how
|
||||
* vm->memory already holds its own arena pointer directly rather than going
|
||||
* through any module-level registry) and passes it back unchanged to
|
||||
* sk_vm_native_stack_free().
|
||||
*/
|
||||
typedef struct {
|
||||
uint64_t paddr; /* physical base (for pmm_free_contiguous) */
|
||||
uint64_t guard_vaddr; /* virtual base of the whole guarded region */
|
||||
uint64_t stack_top; /* initial SP value -- stacks grow down on all
|
||||
* 3 arches, so this is guard_vaddr + one guard
|
||||
* page + the full stack size */
|
||||
} sk_vm_native_stack_t;
|
||||
|
||||
int sk_vm_native_stack_alloc(sk_vm_native_stack_t *out);
|
||||
void sk_vm_native_stack_free(const sk_vm_native_stack_t *stack);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_VM_ARENA_H */
|
||||
@@ -0,0 +1,6 @@
|
||||
# include/starkernel/vm/bootstrap/
|
||||
|
||||
- `sk_vm_bootstrap.h` — declares the kernel VM bootstrap entry point
|
||||
implemented in `src/starkernel/vm/bootstrap/sk_vm_bootstrap.c`. Owns
|
||||
capsule loading and VM initialization wiring; deliberately kept separate
|
||||
from `kernel_main.c`, which owns only hardware milestones.
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* sk_vm_bootstrap.h - Kernel VM bootstrap/parity entrypoints
|
||||
*
|
||||
* Gated by STARFORTH_ENABLE_VM so kernel images stay lean by default.
|
||||
* Provides a single entry point that initializes the VM using kernel
|
||||
* HAL services and emits the M7 parity packet.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VM_BOOTSTRAP_SK_VM_BOOTSTRAP_H
|
||||
#define STARKERNEL_VM_BOOTSTRAP_SK_VM_BOOTSTRAP_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include "starkernel/vm/parity.h"
|
||||
|
||||
/**
|
||||
* sk_vm_bootstrap_parity - Initialize VM and emit parity packet.
|
||||
*
|
||||
* @param out Optional parity packet to fill (may be NULL to use a local)
|
||||
* @return 0 on success, -1 on failure (see parity packet for details)
|
||||
*/
|
||||
int sk_vm_bootstrap_parity(ParityPacket *out);
|
||||
|
||||
/**
|
||||
* sk_get_mama_vm - Return opaque pointer to Mama's VM context.
|
||||
*
|
||||
* Valid after sk_vm_bootstrap_parity() succeeds.
|
||||
*/
|
||||
void *sk_get_mama_vm(void);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_VM_BOOTSTRAP_SK_VM_BOOTSTRAP_H */
|
||||
@@ -0,0 +1,850 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* kernel_hermes.h - Kernel-resident Hermes: message and membership
|
||||
* structures (FABRIC-3.6.md task 2.1, item 28; design: FABRIC-3.5.md
|
||||
* SIII/SXXXIII/SXXXIV).
|
||||
*
|
||||
* Phase 2, task 2.1 ONLY: type definitions, wired to nothing, drawing no
|
||||
* heat. No allocator, no send/deliver/reap logic, no registration
|
||||
* anywhere -- those are tasks 2.2 onward, each its own commit. This file
|
||||
* existing and compiling changes no VM's dictionary and no runtime
|
||||
* behaviour; that is deliberate (FABRIC-3.5.md SXXII.4: Phase 2 structures
|
||||
* come first and prove nothing until the allocator is built on top, SXL.4
|
||||
* item 41).
|
||||
*
|
||||
* SkHermesMessage mirrors capsules/common/messaging.4th's live MSG-CELLS
|
||||
* layout (9 cells: MSG-TYPE@/FROM@/TO@/PADDR@/PLEN@/STADIUM-CELL@/SEQ@/
|
||||
* CH@/ORIG-TYPE@) field-for-field, per FABRIC-3.5.md SXXXIII.4 item 1 --
|
||||
* "roughly half the file is accessors that become struct fields." The
|
||||
* Stadium-cell field is the heat coupling itself: a message's heat is not
|
||||
* a field of its own, it IS the Stadium cell it occupies (SXL.4's
|
||||
* consumption model, SXXXIX.4's per-VM invariant) -- there is deliberately
|
||||
* no separate heat field here to keep that single-source-of-truth.
|
||||
*
|
||||
* SkHermesMembership is the "one broadcast membership list" SXXXIII.4
|
||||
* item 3 and SXXXIII.5 recommend in place of messaging.4th's 28-word
|
||||
* channel abstraction (CH-REQUEST/ACCEPT/CONFIRM/CLOSE/MINT-ID and the
|
||||
* CH-NEGOTIATING/OPEN/CLOSING state machine) -- traced to have exactly one
|
||||
* live caller, CH-ADD-MBR, everything else channel-shaped is unexercised.
|
||||
* Whether kernel-Hermes ever adds negotiation on top is item 27, an open
|
||||
* Phase 3 ruling (FABRIC-3.6.md B1) -- this structure does not answer
|
||||
* that question, it only holds a flat list, which is correct either way.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VM_KERNEL_HERMES_H
|
||||
#define STARKERNEL_VM_KERNEL_HERMES_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "starkernel/vm_uuid.h" /* VMUuid */
|
||||
#include "starkernel/q48_16.h" /* Q48_ONE */
|
||||
|
||||
/*
|
||||
* SK_HERMES_MSG_MAX / SK_HERMES_MEMBER_MAX - sizing. Mirrors
|
||||
* messaging.4th's own MSG-MAX (32) and MBR-MAX (64) as a starting point --
|
||||
* kernel-Hermes is a single central pool rather than N per-VM arenas, so
|
||||
* these may need revisiting once real traffic exists to size against. Not
|
||||
* a ruling, just where the FORTH precedent already was.
|
||||
*/
|
||||
#define SK_HERMES_MSG_MAX 32
|
||||
#define SK_HERMES_MEMBER_MAX 64
|
||||
|
||||
/* SK_HERMES_CHUNK_MAX_PAYLOAD - one block (1024 bytes, 64x16, SXLIV.1),
|
||||
* moved up here (was originally defined much further down, next to
|
||||
* SkHermesChunkHeader) so SkHermesMessage's own payload_buf field below
|
||||
* can be sized against it. See this file's chunking section, further
|
||||
* down, for the full sizing rationale -- not restated here. */
|
||||
#define SK_HERMES_CHUNK_MAX_PAYLOAD 1024
|
||||
|
||||
/*
|
||||
* SK_HERMES_Q_SLOT - per-message admission heat, task 2.2 (item 28).
|
||||
* messaging.4th:44-46 derives Q.SLOT as the reservoir remaining after
|
||||
* COMMON-CH's own Q.1/3 floor, split across MSG-MAX + (CH-MAX-1) slots --
|
||||
* a floor that exists to reserve heat for the one live channel object
|
||||
* itself. Kernel-Hermes has no such object: SXXXIII.4 item 3 and
|
||||
* SXXXIII.5 replace the channel abstraction with a flat membership list
|
||||
* that carries no Stadium heat of its own, so there is nothing left for a
|
||||
* floor to protect. Deliberately simpler here rather than carrying the
|
||||
* old formula's now-unmotivated term forward: reservoir split evenly
|
||||
* across message slots only.
|
||||
*/
|
||||
#define SK_HERMES_Q_SLOT ((uint64_t)Q48_ONE / SK_HERMES_MSG_MAX)
|
||||
|
||||
/*
|
||||
* SkHermesMessage - one message slot, field-for-field mirror of
|
||||
* messaging.4th's 9-cell MSG layout.
|
||||
*
|
||||
* @field type Message type code (SPAWN/PAUSE/RESUME/KILL-style
|
||||
* codes are Category A/dead per task 0.1/0.4; live
|
||||
* types today are CONSOLE-CMD-EVENT(7),
|
||||
* ELEVATE-REQUEST(8), BLK-ATTACH-EVENT(9), messaging.4th
|
||||
* reserved sentinels MSG-NACKED(253)/MSG-DELIVERED(255)).
|
||||
* @field from Sending VM (mirrors MSG-FROM@).
|
||||
* @field to Target VM (mirrors MSG-TO@).
|
||||
* @field payload_addr Payload address a reader dereferences (mirrors
|
||||
* MSG-PADDR@). CORRECTED 2026-09-22 (real defect,
|
||||
* FABRIC-3.6.md task 3.8's own findings log, "a real
|
||||
* payload-aliasing defect"): this used to be the
|
||||
* caller's own out-of-line pointer, stored as-is --
|
||||
* two sends before either drained meant both
|
||||
* messages pointed at the same caller-owned buffer,
|
||||
* whichever send wrote last silently winning. Now
|
||||
* always points at this SAME message's own
|
||||
* `payload_buf` below, which sk_hermes_send_one()
|
||||
* copies into at send time -- every message owns its
|
||||
* payload bytes, independent of whatever the caller
|
||||
* does with its own buffer afterward. Every existing
|
||||
* reader (sk_hermes_drain_checkpoint(),
|
||||
* sk_hermes_reassemble()) is unaffected: `payload_addr`
|
||||
* still points at valid payload bytes of the same
|
||||
* length, just message-owned instead of caller-owned.
|
||||
* FABRIC-3.5.md SXLIII.6.1 (item 44, still open): a
|
||||
* payload above INPUT_BUFFER_SIZE-1 (1024) bytes cannot
|
||||
* be drained in one interpret call -- bound or chunk it
|
||||
* before Phase 3, not here.
|
||||
* @field payload_buf Backing storage for `payload_addr` above -- see its
|
||||
* own doc comment. Sized to SK_HERMES_CHUNK_MAX_PAYLOAD,
|
||||
* the uniform bound sk_hermes_send_one()/
|
||||
* sk_hermes_publish() already enforce on `payload_len`
|
||||
* (defined just above, moved up in this same fix so it
|
||||
* is in scope here). No reader should touch this field
|
||||
* directly -- go through `payload_addr`, which is what
|
||||
* every existing reader already does.
|
||||
* @field payload_len Payload length in bytes (mirrors MSG-PLEN@).
|
||||
* @field stadium_cell Index into the Stadium cell array this message's
|
||||
* heat currently occupies, or a sentinel meaning "none"
|
||||
* (mirrors MSG-STADIUM-CELL@) -- the heat coupling
|
||||
* itself; see this file's own top comment.
|
||||
* @field seq Monotonic send sequence (mirrors MSG-SEQ@).
|
||||
* @field channel Broadcast/channel marker; unused today (mirrors
|
||||
* MSG-CH@) -- item 27 territory, not decided here.
|
||||
* @field orig_type Original type before a NACK/redeliver rewrite
|
||||
* (mirrors MSG-ORIG-TYPE@).
|
||||
* @field in_use Free-list occupancy flag for task 2.2's allocator.
|
||||
* Not present in the FORTH layout (which uses
|
||||
* MSG-TYPE@ 0<> as its own live/free test) -- kept
|
||||
* explicit here rather than overloading `type == 0`,
|
||||
* since kernel-Hermes's held/pulled/returned/consumed
|
||||
* ledger (task 2.4, SXL.4) needs an unambiguous
|
||||
* occupancy bit independent of the type field's value.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t type;
|
||||
VMUuid from;
|
||||
VMUuid to;
|
||||
void *payload_addr;
|
||||
uint8_t payload_buf[SK_HERMES_CHUNK_MAX_PAYLOAD];
|
||||
uint32_t payload_len;
|
||||
int32_t stadium_cell;
|
||||
uint32_t seq;
|
||||
uint32_t channel;
|
||||
uint32_t orig_type;
|
||||
int in_use;
|
||||
VMUuid owner; /* VM whose reservoir funded this message (task 2.7) */
|
||||
} SkHermesMessage;
|
||||
|
||||
/*
|
||||
* SkHermesMembership - one flat broadcast membership list: every VM that
|
||||
* has joined, no per-member state beyond identity. Replaces the 28-word
|
||||
* channel abstraction; see this file's own top comment for why.
|
||||
*
|
||||
* @field members Member VM identities, valid for indices < count.
|
||||
* @field count Number of valid entries in members[].
|
||||
*/
|
||||
typedef struct {
|
||||
VMUuid members[SK_HERMES_MEMBER_MAX];
|
||||
size_t count;
|
||||
} SkHermesMembership;
|
||||
|
||||
/*
|
||||
* sk_hermes_alloc - Heat-coupled allocate (task 2.2, item 28): pull
|
||||
* SK_HERMES_Q_SLOT from vm_id's own Stadium reservoir, admit a real
|
||||
* Stadium-floor patron with that heat (behaviour DELIVER, matching
|
||||
* messaging.4th's own `SB-DELIVER STADIUM-ADMIT`), and claim a free
|
||||
* message slot recording the admitted cell. Refuses cleanly, rolling
|
||||
* back anything already pulled/admitted, if reservoir, arena, or the
|
||||
* Stadium floor itself refuses.
|
||||
*
|
||||
* CORRECTED in task 2.3 (see this file's .c counterpart's own top
|
||||
* comment): the first cut of this function never admitted a real
|
||||
* Stadium patron, which left held message heat invisible to SXL.4's
|
||||
* conservation invariant while allocated. It now does, which is what
|
||||
* makes sk_hermes_release()'s eviction meaningful.
|
||||
*
|
||||
* Wired to nothing outside this file's own self-test (kernel_main.c) as
|
||||
* of task 2.2/2.3 -- no FORTH word, no capsule interaction, no protocol
|
||||
* logic (send/deliver/reap are later tasks). Exercised only by a
|
||||
* synthetic-VM self-test; does not change any real VM's dictionary.
|
||||
*
|
||||
* @param vm_id Caller whose reservoir is charged.
|
||||
* @param out_msg On success, set to the claimed slot. Untouched on
|
||||
* refusal.
|
||||
* @return 0 on success, -1 on refusal (insufficient reservoir, no free
|
||||
* slot, or the Stadium floor itself refused admission -- this
|
||||
* function does not distinguish the three in the return value;
|
||||
* all three leave all state exactly as it was).
|
||||
*/
|
||||
int sk_hermes_alloc(VMUuid vm_id, SkHermesMessage **out_msg);
|
||||
|
||||
/*
|
||||
* sk_hermes_release - Return a message's heat via the Stadium eviction
|
||||
* path (task 2.3, item 28), mirroring MSG-FREE-NODE
|
||||
* ("DUP 5 CELLS + @ STADIUM-EVICT DROP"). stadium_evict() itself returns
|
||||
* the departing patron's remaining heat to its owning VM's reservoir --
|
||||
* this function does not touch the reservoir directly, matching the
|
||||
* FORTH shape exactly. Clears the slot (in_use = 0) whether or not
|
||||
* eviction succeeds, since a message this function was asked to release
|
||||
* should not remain allocated either way.
|
||||
*
|
||||
* @param msg A slot previously returned by sk_hermes_alloc(). Refused
|
||||
* (returns -1, no effect) if NULL or already released.
|
||||
* @return 0 on successful eviction, -1 if msg was invalid or eviction
|
||||
* itself was refused by the Stadium floor.
|
||||
*/
|
||||
int sk_hermes_release(SkHermesMessage *msg);
|
||||
|
||||
/*
|
||||
* sk_hermes_ledger - Task 2.4 (item 28), the four counters FABRIC-3.5.md
|
||||
* SXXXVII.3/SXL.4 name: held, pulled, returned, consumed. Each is touched
|
||||
* at exactly one call site:
|
||||
*
|
||||
* held += pulled amount -- sk_hermes_alloc(), on success only
|
||||
* held -= returned amount -- sk_hermes_release(), on success only
|
||||
* pulled += pulled amount -- sk_hermes_alloc(), same site as held's
|
||||
* returned += returned amount -- sk_hermes_release(), same site as held's
|
||||
* held -= decay delta -- sk_hermes_decay(), same site as consumed's
|
||||
* consumed += decay delta -- sk_hermes_decay() (task 2.5)
|
||||
*
|
||||
* The audit invariant this ledger exists to make checkable (task 2.6,
|
||||
* epsilon zero): held == pulled - returned - consumed. All four are
|
||||
* exact integers (SXXXVII.3: "Nothing here is a measurement. There is no
|
||||
* noise to tolerate.") -- this accessor is how task 2.6's audit, task
|
||||
* 2.7's Stage B proof, and task 2.8's scan cross-check all read the same
|
||||
* four numbers, rather than each keeping its own copy.
|
||||
*
|
||||
* @param held Set to the current live total (may be NULL).
|
||||
* @param pulled Set to the cumulative total ever pulled (may be NULL).
|
||||
* @param returned Set to the cumulative total ever returned (may be
|
||||
* NULL).
|
||||
* @param consumed Set to the cumulative total ever consumed by decay
|
||||
* (may be NULL).
|
||||
*/
|
||||
void sk_hermes_ledger(uint64_t *held, uint64_t *pulled, uint64_t *returned, uint64_t *consumed);
|
||||
|
||||
/*
|
||||
* SK_HERMES_Q_DECAY - per-application decay factor, Q48.16. Identical to
|
||||
* messaging.4th's `65208 CONSTANT Q-DECAY` (65208/65536 ~= 0.99499):
|
||||
* FABRIC-3.5.md SXL.4 rules that the consumption economy is preserved
|
||||
* exactly, not re-tuned.
|
||||
*/
|
||||
#define SK_HERMES_Q_DECAY ((uint64_t)65208)
|
||||
|
||||
/*
|
||||
* sk_hermes_decay - Apply one decay step to a live message (task 2.5,
|
||||
* item 28), mirroring MSG-COOL-ONE (`MSG-HEAT@ Q-DECAY Q.* MSG-HEAT!`):
|
||||
* the message's Stadium-cell heat becomes q48_mul(heat, Q_DECAY). Unlike
|
||||
* MSG-COOL-ONE, the destroyed difference is RECORDED (SXXXVII.3): the
|
||||
* delta heat_before - heat_after is added to `consumed` and subtracted
|
||||
* from `held`, so held == pulled - returned - consumed stays exact.
|
||||
*
|
||||
* @param msg A live slot from sk_hermes_alloc(). Refused (returns -1, no
|
||||
* effect) if NULL, not in use, or holding no Stadium cell.
|
||||
* @return 0 on success, -1 if refused.
|
||||
*/
|
||||
int sk_hermes_decay(SkHermesMessage *msg);
|
||||
|
||||
/*
|
||||
* Task 2.6 (item 28): the self-audit, epsilon zero (SXXXVII.3).
|
||||
*
|
||||
* sk_hermes_audit_values - pure predicate: nonzero iff
|
||||
* held == pulled - returned - consumed exactly. Separate from the live
|
||||
* ledger so the check can be exercised with deliberately corrupted values
|
||||
* without mutating real state.
|
||||
*
|
||||
* sk_hermes_audit - checks the live ledger (O(1), SXXXVII.4); on failure
|
||||
* prints a console line and bumps a failure count. Called at the end of
|
||||
* every successful mutation: alloc, release, decay.
|
||||
*
|
||||
* sk_hermes_audit_failure_count - cumulative failures seen by the live
|
||||
* audit; must be 0 in a healthy kernel.
|
||||
*/
|
||||
int sk_hermes_audit_values(uint64_t held, uint64_t pulled, uint64_t returned, uint64_t consumed);
|
||||
int sk_hermes_audit(void);
|
||||
uint64_t sk_hermes_audit_failure_count(void);
|
||||
|
||||
/*
|
||||
* Task 2.8 (item 28), SXXXVII.4: the scan that verifies the counters
|
||||
* themselves. DIAGNOSTIC ONLY -- O(SK_HERMES_MSG_MAX), never called from
|
||||
* alloc/release/decay.
|
||||
*
|
||||
* sk_hermes_scan_held - walks the message arena and sums the live Stadium
|
||||
* cell heat of every in-use message (the ground truth `held` claims to
|
||||
* track). Sets *live_count (may be NULL) to the messages counted.
|
||||
*
|
||||
* sk_hermes_scan_check - nonzero iff the scan sum equals the `held`
|
||||
* counter exactly.
|
||||
*/
|
||||
uint64_t sk_hermes_scan_held(size_t *live_count);
|
||||
int sk_hermes_scan_check(void);
|
||||
|
||||
/*
|
||||
* Channel table (FABRIC-3.6.md task 3.2, B1 / FABRIC-3.5.md SXLV.1): B1
|
||||
* overrules SXXXIII.5's single flat broadcast list -- messaging is
|
||||
* publish/subscribe, one permanent common channel every VM joins at
|
||||
* birth, private channels created by request/grant/deny (task 3.6, not
|
||||
* this one). SkHermesMembership (task 2.1) becomes per-channel: one
|
||||
* instance per SkHermesChannel table entry instead of one global list.
|
||||
*
|
||||
* INERT, same posture as task 2.1: this task wires channel existence and
|
||||
* membership bookkeeping only. No publish, no dispatch, no ACK/NACK, no
|
||||
* ACL hook (tasks 3.3, 3.6, 3.7) -- creating/destroying/subscribing a
|
||||
* channel here changes no VM's dictionary and delivers no message.
|
||||
*
|
||||
* Sizing: SXLV.1 says the channel table has "no fixed channel maximum,
|
||||
* same reasoning as SXLV.2" -- SXLV.2/task 3.1's own ruling is boot-time,
|
||||
* RAM-derived sizing (Stadium's stadium_max_vm_count_val pattern), not a
|
||||
* literal unbounded/growable table. No separate numeric sizing rule was
|
||||
* ruled for the channel table specifically (task 3.0's five sub-items
|
||||
* covered ACK cadence/ACL-hook-location/switch-table-sizing/chunk-
|
||||
* framing/drain-cadence, not this). Extending task 3.1's already-ruled
|
||||
* pattern directly -- same stadium_max_vm_count() bound, same
|
||||
* kmalloc-at-boot shape -- is the smallest choice consistent with what
|
||||
* was ruled, flagged here as an engineering extrapolation, not restated
|
||||
* as a separate Captain Bob ruling.
|
||||
*/
|
||||
|
||||
/* SK_HERMES_CHANNEL_COMMON - the permanent common channel's fixed index.
|
||||
* Exists (in_use, empty membership) from sk_hermes_channels_boot_init()
|
||||
* itself, before any VM is born -- every VM joins it at birth (task 3.2's
|
||||
* own kernel_main.c/capsule_birth.c wiring), never destroyed. */
|
||||
#define SK_HERMES_CHANNEL_COMMON 0
|
||||
|
||||
/*
|
||||
* SkHermesChannel - one channel/topic: its membership plus an occupancy
|
||||
* flag for the dynamic table below. No name field -- messaging.4th's own
|
||||
* channel abstraction (CH-ARENA) has none either; a channel is identified
|
||||
* by its table index, the same way a Stadium quota slot or a switch-
|
||||
* signal slot is identified by index, not by string.
|
||||
*/
|
||||
typedef struct {
|
||||
SkHermesMembership membership;
|
||||
int in_use;
|
||||
} SkHermesChannel;
|
||||
|
||||
/* Boot-time allocation: kmalloc's the channel table to
|
||||
* stadium_max_vm_count() entries (see this section's own sizing note
|
||||
* above) and creates the common channel (index SK_HERMES_CHANNEL_COMMON,
|
||||
* in_use, empty membership -- members join at birth, not pre-populated
|
||||
* here). Must run after stadium_boot_init() (that bound is 0, and this
|
||||
* fails, until Stadium has computed it). Soft failure -- returns -1 and
|
||||
* leaves the table unallocated (capacity 0, so every call below simply
|
||||
* refuses) rather than halting boot, same posture as
|
||||
* stadium_boot_init()/session_boot_init()/sk_vm_switch_signal_boot_init().
|
||||
* Idempotent-unsafe: calling twice leaks the first allocation, so callers
|
||||
* must call it exactly once. */
|
||||
int sk_hermes_channels_boot_init(void);
|
||||
|
||||
/* sk_hermes_channel_create - allocate a new, empty, inert channel.
|
||||
* Returns its table index, or -1 if the table is full or
|
||||
* sk_hermes_channels_boot_init() was never called/failed. */
|
||||
int sk_hermes_channel_create(void);
|
||||
|
||||
/* sk_hermes_channel_destroy - tear down a channel created by
|
||||
* sk_hermes_channel_create(). Refuses (-1, no effect) if channel_id is
|
||||
* SK_HERMES_CHANNEL_COMMON (the common channel is permanent, SXLV.1),
|
||||
* out of range, or not in use. Clears membership. */
|
||||
int sk_hermes_channel_destroy(int channel_id);
|
||||
|
||||
/* sk_hermes_channel_subscribe - add vm_id to channel_id's membership.
|
||||
* Refuses (-1, no effect) if channel_id is invalid/not in use, vm_id is
|
||||
* already a member, or the channel's membership is already at
|
||||
* SK_HERMES_MEMBER_MAX. Idempotent in effect (a second call with the
|
||||
* same args refuses rather than duplicating), not in return value. */
|
||||
int sk_hermes_channel_subscribe(int channel_id, VMUuid vm_id);
|
||||
|
||||
/* sk_hermes_channel_unsubscribe - remove vm_id from channel_id's
|
||||
* membership (compacts the list, same shape as
|
||||
* sk_vm_switch_signal_unregister()). Refuses (-1, no effect) if
|
||||
* channel_id is invalid/not in use or vm_id is not a member. */
|
||||
int sk_hermes_channel_unsubscribe(int channel_id, VMUuid vm_id);
|
||||
|
||||
/* sk_hermes_channel_is_member - nonzero iff vm_id is currently a member
|
||||
* of channel_id. Zero (not an error signal) if channel_id is invalid/not
|
||||
* in use. */
|
||||
int sk_hermes_channel_is_member(int channel_id, VMUuid vm_id);
|
||||
|
||||
/* sk_hermes_channel_member_count - current membership size of channel_id,
|
||||
* or -1 if channel_id is invalid/not in use. */
|
||||
int sk_hermes_channel_member_count(int channel_id);
|
||||
|
||||
/* sk_hermes_channel_capacity - the table's boot-time-computed capacity
|
||||
* (0 if sk_hermes_channels_boot_init() was never called or failed) --
|
||||
* DoE/test observability, mirrors sk_vm_switch_signal_slot_capacity(). */
|
||||
int sk_hermes_channel_capacity(void);
|
||||
|
||||
/* Boot-time allocation for the per-subscriber pending-queue table
|
||||
* (FABRIC-3.6.md task 3.3), kmalloc'd to stadium_max_vm_count() entries --
|
||||
* same sizing pattern as the channel table (task 3.2) and switch table
|
||||
* (task 3.1); see kernel_hermes.c's own comment on this call for why. Must
|
||||
* run after stadium_boot_init(). Soft failure -- returns -1 and leaves the
|
||||
* table unallocated (every queue lookup then finds nothing) rather than
|
||||
* halting boot. Idempotent-unsafe: call exactly once. */
|
||||
int sk_hermes_queues_boot_init(void);
|
||||
|
||||
/*
|
||||
* sk_hermes_send_one - point-to-point delivery to exactly one VM
|
||||
* (FABRIC-3.6.md task 3.6, extracted from sk_hermes_publish()'s own
|
||||
* per-subscriber body, which now calls this once per channel member).
|
||||
* One allocation, one queue push, same rollback-on-refusal discipline
|
||||
* sk_hermes_publish() already had -- one code path, so the ledger can
|
||||
* never diverge between the two callers.
|
||||
*
|
||||
* Exists because task 3.6's negotiation messages (request/grant/deny/
|
||||
* close/ACK/NACK) are inherently addressed to ONE specific VM, and
|
||||
* sk_hermes_publish()'s fan-out cannot express that: it sets msg->to to
|
||||
* whichever member it is currently iterating, so a negotiation message
|
||||
* "published" to the common channel would be delivered to (and drawn
|
||||
* heat against) every common-channel member, each believing it was the
|
||||
* addressee, not just the real target. `messaging.4th`'s own
|
||||
* `CH-REQUEST ( type from to paddr plen -- )` carried an explicit `to`
|
||||
* for exactly this reason -- point-to-point addressing was in the
|
||||
* original protocol from the start.
|
||||
*
|
||||
* @param from Sender, whose reservoir funds the allocation.
|
||||
* @param to The one recipient.
|
||||
* @param type Message type code.
|
||||
* @param channel_id Tag copied into the message's own `channel`
|
||||
* field -- purely informational at this level (no
|
||||
* membership is consulted or required), letting a
|
||||
* response encode context (e.g. task 3.6's GRANT
|
||||
* uses this to tell the requester which private
|
||||
* channel was just created).
|
||||
* @param payload_addr Caller-owned buffer, read and copied into the
|
||||
* message's own storage before this function
|
||||
* returns (2026-09-22 fix, this struct's own
|
||||
* payload_addr field doc comment has the full
|
||||
* account) -- the caller does not need to keep
|
||||
* it alive past this call.
|
||||
* @param payload_len Refused (-1) if it exceeds
|
||||
* SK_HERMES_CHUNK_MAX_PAYLOAD.
|
||||
* @return 0 on success, -1 on any refusal (bound, reservoir, arena, or
|
||||
* destination queue full -- all leave state exactly as found).
|
||||
*/
|
||||
int sk_hermes_send_one(VMUuid from, VMUuid to, uint32_t type, uint32_t channel_id,
|
||||
void *payload_addr, uint32_t payload_len);
|
||||
|
||||
/*
|
||||
* Publish path, no dispatch (FABRIC-3.6.md task 3.3, SXLIII.3; heat-cost
|
||||
* ruling 2026-09-21: one message per subscriber, separate heat draw each --
|
||||
* matches the existing heat-coupled allocator 1:1, no refcount machinery).
|
||||
*
|
||||
* sk_hermes_publish() loops sk_hermes_send_one() (task 3.6 extraction,
|
||||
* above) once per channel member, funded by the publisher's own
|
||||
* reservoir each time. It does not interpret, deliver, or otherwise
|
||||
* dispatch anything -- draining a queue at a VM's own outermost
|
||||
* interpret checkpoint is task 3.4's scope, not this one's.
|
||||
*
|
||||
* Best-effort, not atomic across subscribers: if a given subscriber's
|
||||
* send_one() call is refused (reservoir exhausted, message arena full,
|
||||
* or that subscriber's own pending queue full), that one subscriber is
|
||||
* skipped -- send_one() has already rolled back its own allocation
|
||||
* internally, so nothing here needs to -- but delivery to every OTHER
|
||||
* subscriber already queued is not undone. This was not a
|
||||
* separate Captain Bob ruling; it is the natural reading of "ledger audit
|
||||
* and stadium_conserved() hold across N publishes to M subscribers" (task
|
||||
* 3.3's own check) -- those invariants hold under partial delivery just
|
||||
* as well as under all-or-nothing, and requiring atomicity across M
|
||||
* independent reservoir-funded allocations would need a two-phase
|
||||
* commit/rollback this task's inert scope does not call for.
|
||||
*
|
||||
* @param from Publisher, whose reservoir funds every allocation.
|
||||
* @param channel_id Target channel (SK_HERMES_CHANNEL_COMMON or a
|
||||
* channel from sk_hermes_channel_create()). Refused
|
||||
* (-1) if invalid/not in use.
|
||||
* @param type Message type code, passed through unchanged.
|
||||
* @param payload_addr Caller-owned buffer, read once per subscriber via
|
||||
* sk_hermes_send_one() (its own doc comment has the
|
||||
* full account) -- the caller does not need to keep
|
||||
* it alive past this call.
|
||||
* @param payload_len Payload length in bytes. Refused (-1, no
|
||||
* allocation attempted) if it exceeds
|
||||
* SK_HERMES_CHUNK_MAX_PAYLOAD (task 3.5, added here
|
||||
* since task 3.3 deliberately parked this check --
|
||||
* see that section's own doc comment below).
|
||||
* @return Count of subscribers successfully enqueued to (0..member count),
|
||||
* or -1 if channel_id itself was invalid or payload_len exceeded
|
||||
* the bound.
|
||||
*/
|
||||
int sk_hermes_publish(VMUuid from, int channel_id, uint32_t type,
|
||||
void *payload_addr, uint32_t payload_len);
|
||||
|
||||
/* SK_HERMES_PENDING_MAX - per-subscriber pending-queue depth. The global
|
||||
* message arena (SK_HERMES_MSG_MAX) is the real ceiling on how many
|
||||
* messages can ever be in flight system-wide, so sizing each VM's own
|
||||
* queue to that same bound is a safe, simple upper limit rather than a
|
||||
* new number to justify. */
|
||||
#define SK_HERMES_PENDING_MAX SK_HERMES_MSG_MAX
|
||||
|
||||
/* sk_hermes_pending_count - number of messages currently queued for
|
||||
* vm_id (0 if vm_id has no queue yet -- never having received a message
|
||||
* is not an error). */
|
||||
int sk_hermes_pending_count(VMUuid vm_id);
|
||||
|
||||
/* sk_hermes_pending_peek - the oldest still-queued message for vm_id, or
|
||||
* NULL if vm_id has no queue or an empty one. Does not remove it -- task
|
||||
* 3.4's drain logic is expected to peek, interpret, then pop. */
|
||||
SkHermesMessage *sk_hermes_pending_peek(VMUuid vm_id);
|
||||
|
||||
/* sk_hermes_pending_pop - removes (does not release/interpret) the
|
||||
* oldest queued entry for vm_id. Callers that also want the message's
|
||||
* heat returned must call sk_hermes_release() on the value
|
||||
* sk_hermes_pending_peek() returned, themselves, before or after popping
|
||||
* -- this function only advances the queue. Refused (-1, no effect) if
|
||||
* vm_id has no queue or an empty one. */
|
||||
int sk_hermes_pending_pop(VMUuid vm_id);
|
||||
|
||||
/* Forward declaration only -- kernel_hermes.h deliberately does not
|
||||
* include vm.h (kept decoupled from the full VM struct, same posture as
|
||||
* every other type in this file), but sk_hermes_drain_checkpoint() below
|
||||
* needs a VM* parameter. Matches vm.h's own `typedef struct VM VM;`
|
||||
* shape exactly, so no redefinition conflict. */
|
||||
typedef struct VM VM;
|
||||
|
||||
/*
|
||||
* Drain at the outermost checkpoint (FABRIC-3.6.md task 3.4, SXLIII.3-.5;
|
||||
* one message per checkpoint, ruled 2026-09-21). Called from
|
||||
* execute_colon_word()'s existing cooperative checkpoint in vm_core.c,
|
||||
* gated the same way the switch-signal checkpoint already is
|
||||
* (sk_vm_at_outermost_interpret()).
|
||||
*
|
||||
* AMENDS SXLIII.5's own claim: "recursive drain is prevented for free"
|
||||
* via g_vm_interpret_depth is true (the SAME message cannot drain twice),
|
||||
* but that counter says nothing about a SEPARATE, real hazard SXLIII.5
|
||||
* never named -- calling vm_interpret() on this same vm, from inside its
|
||||
* own currently-running vm_interpret() call, overwrites vm->input_buffer/
|
||||
* input_length/input_pos with the drained payload's own state.
|
||||
* VMCallState (vm_state_push()/vm_state_pop(), mama_forth_words.c) saves
|
||||
* only rsp/exit_colon/ecw_nesting -- never these three fields (the exact
|
||||
* gap FABRIC-3.md SXX documents: "the same class... for input_buffer/
|
||||
* input_pos not being saved by vm_state_push/pop"). Left unaddressed,
|
||||
* the enclosing vm_interpret() call's own while loop would silently lose
|
||||
* the rest of its input line/block the moment a drain fires mid-line --
|
||||
* the same failure shape as the INPUT_BUFFER_SIZE 256 defect
|
||||
* .claude/CLAUDE.md calls non-negotiable, and trap #1 in this document's
|
||||
* own START HERE. Not a divergence from SXLIII.3's ruling (the mechanism
|
||||
* is exactly as ruled); cursor/mode/error/abort preservation is this
|
||||
* function's own implementation obligation, not a new design question.
|
||||
*
|
||||
* sk_hermes_drain_checkpoint() therefore snapshots vm->input_buffer/
|
||||
* input_length/input_pos/mode/error/abort_requested before calling
|
||||
* vm_interpret() on the queued payload, forces vm->mode to
|
||||
* MODE_INTERPRET for the duration (a checkpoint reached mid-colon-
|
||||
* definition must not let the payload's words compile into the
|
||||
* enclosing definition), and restores all six afterward -- fully
|
||||
* isolating the drain from whatever the enclosing execution was doing.
|
||||
*
|
||||
* Payload convention: payload_addr is trusted to point at a
|
||||
* NUL-terminated C string (vm_interpret()'s own signature takes no
|
||||
* length) -- payload_len is not consulted here. Bounding/validating that
|
||||
* is task 3.5's scope (payload bound and chunking), not this one's.
|
||||
*
|
||||
* Fast path: sk_hermes_publish()/sk_hermes_pending_pop() maintain a
|
||||
* single system-wide pending-total counter; this function reads it
|
||||
* first and returns immediately if it is 0, so the common case (nothing
|
||||
* in flight) costs one integer read on every word dispatch, not a
|
||||
* stadium_max_vm_count()-sized queue-table scan.
|
||||
*
|
||||
* @param vm The VM at its own outermost checkpoint. NULL is refused.
|
||||
* @return 1 if a message was drained cleanly, 0 if nothing was pending
|
||||
* for this vm, -1 if a message was drained but interpreting its
|
||||
* payload set vm->error (restored to its pre-drain value either
|
||||
* way -- a bad message must not abort the enclosing execution).
|
||||
*/
|
||||
int sk_hermes_drain_checkpoint(VM *vm);
|
||||
|
||||
/*
|
||||
* Payload bound and chunking (FABRIC-3.6.md task 3.5, B4 / FABRIC-3.5.md
|
||||
* SXLV.3; chunk framing ruled 2026-09-21: each chunk carries (msg_id,
|
||||
* seq, is_last) ahead of the real content).
|
||||
*
|
||||
* Sizing decision (not itself ruled -- see SXLV.4's own note that the
|
||||
* concrete framing was a task-writing prerequisite, not a ruling):
|
||||
* SK_HERMES_CHUNK_MAX_PAYLOAD (1024, one block, SXLIV.1) bounds a
|
||||
* message's payload_len UNIFORMLY -- chunked or not. A chunk carrier's
|
||||
* own payload is [SkHermesChunkHeader][content slice], so the slice
|
||||
* itself is capped at 1024 - sizeof(SkHermesChunkHeader), not 1024,
|
||||
* keeping every message on the wire under the same one-block bound
|
||||
* SXLIII.6.1 already requires for vm_interpret()'s own drain limit.
|
||||
* (The alternative -- slice up to 1024, carrier up to
|
||||
* 1024+sizeof(header) -- was considered and rejected: it would mean a
|
||||
* chunk carrier can never be handed to vm_interpret() as-is, making a
|
||||
* later chunk-aware drain a special case instead of the same drain path
|
||||
* every other message already uses.)
|
||||
*
|
||||
* sk_hermes_publish() (task 3.3, above) enforces this bound directly --
|
||||
* refuses (-1) any single payload_len over SK_HERMES_CHUNK_MAX_PAYLOAD,
|
||||
* whether or not it's a chunk carrier, since both cases hold the same
|
||||
* invariant now.
|
||||
*
|
||||
* SENDING a payload over SK_HERMES_CHUNK_MAX_SLICE bytes is deliberately
|
||||
* NOT a kernel-Hermes API here -- it is a loop a caller writes with the
|
||||
* primitives below plus sk_hermes_publish(), the same way this task's
|
||||
* own self-test proves reassembly (kernel_main.c: build N static chunk
|
||||
* buffers, sk_hermes_publish() each). Building a chunking sender would
|
||||
* need kernel-Hermes to own chunk-buffer memory with a real lifetime
|
||||
* (kept alive until every subscriber has drained it, freed only once
|
||||
* kernel-Hermes has no way to know that) -- a genuine new question this
|
||||
* task's inert scope does not call for. A real multi-chunk sender is a
|
||||
* later task's problem, when a real message type actually needs one
|
||||
* (3.8+, the same boundary chunk-aware drain is already deferred past).
|
||||
*/
|
||||
|
||||
/* SK_HERMES_CHUNK_MAX_PAYLOAD (1024 bytes, 64x16, SXLIV.1) -- the
|
||||
* uniform bound every message's payload_len must satisfy, chunked or
|
||||
* not (see this section's own sizing note above) -- is defined earlier
|
||||
* in this file now, next to SkHermesMessage's own payload_buf field,
|
||||
* which needs it in scope. Not redefined here. */
|
||||
|
||||
/* SkHermesChunkHeader - prepended to a chunk carrier's own payload,
|
||||
* ahead of up to SK_HERMES_CHUNK_MAX_SLICE bytes of real content.
|
||||
* msg_id is caller-chosen and must be unique per logical multi-chunk
|
||||
* send from a given sender -- reassembly groups chunks by it. seq is
|
||||
* 0-based, ascending, no gaps, {0..n_chunks-1}. is_last is set on
|
||||
* exactly the chunk whose seq is n_chunks-1, no other. */
|
||||
typedef struct {
|
||||
uint32_t msg_id;
|
||||
uint32_t seq;
|
||||
int is_last;
|
||||
} SkHermesChunkHeader;
|
||||
|
||||
/* Content bytes per chunk -- the one-block bound minus header overhead. */
|
||||
#define SK_HERMES_CHUNK_MAX_SLICE (SK_HERMES_CHUNK_MAX_PAYLOAD - (uint32_t)sizeof(SkHermesChunkHeader))
|
||||
|
||||
/* sk_hermes_chunk_count - number of SK_HERMES_CHUNK_MAX_SLICE-sized
|
||||
* chunks `len` bytes needs (ceiling division). 0 bytes needs 0 chunks;
|
||||
* 1..SK_HERMES_CHUNK_MAX_SLICE needs 1; and so on. Pure arithmetic, no
|
||||
* side effects -- a caller sizing its own chunk-send loop calls this
|
||||
* first. */
|
||||
uint32_t sk_hermes_chunk_count(uint32_t len);
|
||||
|
||||
/* sk_hermes_reassemble - concatenates n_chunks chunk carrier messages
|
||||
* (already drained off a subscriber's own pending queue via
|
||||
* sk_hermes_pending_peek()/sk_hermes_pending_pop(), task 3.3) sharing
|
||||
* one msg_id back into one byte-exact buffer at out_buf, in ascending
|
||||
* seq order regardless of the order chunks[] itself is passed in.
|
||||
*
|
||||
* Refuses (-1, *out_len untouched, no partial copy ever lands in
|
||||
* out_buf) if: n_chunks is 0 or exceeds SK_HERMES_MSG_MAX (the system-
|
||||
* wide message arena size -- chunking can never need more chunk-carrier
|
||||
* messages than the whole arena holds); any chunk is NULL, has no
|
||||
* payload, or its payload is shorter than sizeof(SkHermesChunkHeader);
|
||||
* the chunks' msg_ids disagree; seq values are not exactly
|
||||
* {0..n_chunks-1} (a duplicate or a gap); is_last is not set on exactly
|
||||
* the seq==n_chunks-1 chunk and no other; a non-final chunk's content
|
||||
* slice is not exactly SK_HERMES_CHUNK_MAX_SLICE bytes, or the final
|
||||
* chunk's is 0 or over that bound; or the total reassembled length
|
||||
* would exceed out_buf_cap (checked once, from validated seq
|
||||
* completeness, before any memcpy -- never order-dependent on which
|
||||
* chunk happens to overflow first).
|
||||
*
|
||||
* @param chunks Array of n_chunks chunk carrier message pointers.
|
||||
* @param n_chunks Chunk count (from sk_hermes_chunk_count() at send
|
||||
* time, or simply len(chunks[])).
|
||||
* @param out_buf Caller-owned destination buffer.
|
||||
* @param out_buf_cap Capacity of out_buf in bytes.
|
||||
* @param out_len Set to the reassembled length on success only.
|
||||
* @return 0 on success, -1 on any refusal above.
|
||||
*/
|
||||
int sk_hermes_reassemble(SkHermesMessage **chunks, int n_chunks,
|
||||
uint8_t *out_buf, uint32_t out_buf_cap,
|
||||
uint32_t *out_len);
|
||||
|
||||
/*
|
||||
* ACK/NACK and private-channel negotiation (FABRIC-3.6.md task 3.6, B1 /
|
||||
* FABRIC-3.5.md SXLV.1). "A private channel is created by request ->
|
||||
* grant/deny over the common channel" -- read as: every VM is a
|
||||
* common-channel member from birth (task 3.2), so a requester can
|
||||
* always REACH a target without a prior private channel; the exchange
|
||||
* itself is point-to-point (sk_hermes_send_one(), task 3.6's own
|
||||
* extraction above), not a fan-out to the whole common-channel
|
||||
* membership -- see sk_hermes_send_one()'s own doc comment for why
|
||||
* fan-out is structurally wrong for an addressed request.
|
||||
* `messaging.4th`'s own `CH-REQUEST` carried an explicit `to` for the
|
||||
* same reason; this is the same shape, not a new one.
|
||||
*
|
||||
* "A deny is a NACK" (SXLV.1) -- there is no separate CH_DENY type;
|
||||
* denial IS SK_HERMES_MSG_TYPE_NACK. ACK is sent once, for the
|
||||
* channel-open + delivery moment (the ruled cadence, 2026-09-21), not
|
||||
* for every message that follows on the new channel.
|
||||
*
|
||||
* The GRANT/DENY *decision* is task 3.7's scope -- the ACL policy hook,
|
||||
* "kernel-Hermes asks ACL.4th, never decides in C, never gates on
|
||||
* zuse_session" (CLAUDE.md, SXLV.1). sk_hermes_channel_respond() below
|
||||
* takes that decision as an explicit caller-supplied `approved` flag
|
||||
* (named for what it is, not `allow`, so it reads as a caller decision
|
||||
* passed in, never as policy living in C) -- task 3.7 replaces the CALL
|
||||
* SITE that produces this flag with a real ACL.4th query; this
|
||||
* function's own signature does not change.
|
||||
*/
|
||||
|
||||
#define SK_HERMES_MSG_TYPE_CH_REQUEST 20
|
||||
#define SK_HERMES_MSG_TYPE_CH_GRANT 21
|
||||
#define SK_HERMES_MSG_TYPE_ACK 22
|
||||
#define SK_HERMES_MSG_TYPE_NACK 23
|
||||
#define SK_HERMES_MSG_TYPE_CH_CLOSE 24
|
||||
|
||||
/* SK_HERMES_MSG_TYPE_BLK_ATTACH (FABRIC-3.6.md task 3.8, Stage C) --
|
||||
* DELIBERATELY the same numeric value as messaging.4th's own
|
||||
* BLK-ATTACH-EVENT constant (9), not a fresh kernel-Hermes-space number
|
||||
* like the five above. SXXXIV.2's partition rule is "one owner per
|
||||
* message type, never shared" -- it is about which LAYER holds the
|
||||
* message, not about the two layers needing disjoint numbering. Once
|
||||
* kernel-Hermes is this type's sole owner, keeping the same value
|
||||
* documents that this is a cutover of the SAME logical message, not a
|
||||
* new one invented alongside it. */
|
||||
#define SK_HERMES_MSG_TYPE_BLK_ATTACH 9
|
||||
|
||||
/* SK_HERMES_MSG_TYPE_CONSOLE_CMD (FABRIC-3.6.md task 3.9, Stage D) --
|
||||
* same reasoning as SK_HERMES_MSG_TYPE_BLK_ATTACH above: deliberately
|
||||
* reuses messaging.4th's own CONSOLE-CMD-EVENT value (7), since this is
|
||||
* that type's real cutover, not a fresh kernel-Hermes-space number. */
|
||||
#define SK_HERMES_MSG_TYPE_CONSOLE_CMD 7
|
||||
|
||||
/* SK_HERMES_MSG_TYPE_ELEVATE_REQUEST (FABRIC-3.6.md task 3.10, Stage D)
|
||||
* -- reuses messaging.4th's own now-historical ELEVATE-REQUEST value (8).
|
||||
* At task 3.10, SEND-ELEVATE-REQUEST/ELEVATE-GRANT/CH-REQUEST all FIND
|
||||
* live in Hera's own dictionary and SEND-ELEVATE-REQUEST was a real,
|
||||
* complete, directly-callable entrypoint (H.5/H.8's own design: any VM
|
||||
* wanting word-ACL elevation calls it with its own pubkey and the target
|
||||
* word name). FABRIC-3.6.md Phase 4, Stage E deleted messaging.4th
|
||||
* (SEND-ELEVATE-REQUEST's only definition) -- KH-ELEVATE-SEND below has
|
||||
* had no FORTH caller since, and ELEVATE-GRANT (zuse-eligibility.4th,
|
||||
* still loaded at boot) is unreachable. Found, not fixed -- Phase 8 PKI
|
||||
* decision needed before Phase 5 close-out, see FABRIC-3.6.md's own
|
||||
* Phase 4 entry. */
|
||||
#define SK_HERMES_MSG_TYPE_ELEVATE_REQUEST 8
|
||||
|
||||
/* The five negotiation types above (20-24) were chosen clear of
|
||||
* messaging.4th's own live/reserved type space (PAUSE-EVENT=2,
|
||||
* RESUME-EVENT=3, KILL-EVENT=4, CONSOLE-CMD-EVENT=7, ELEVATE-REQUEST=8,
|
||||
* BLK-ATTACH-EVENT=9, MSG-NACKED=253, MSG-DELIVERED=255) precisely
|
||||
* because none of them had a real cutover yet -- kernel-Hermes stayed
|
||||
* its own inert, parallel type space for those. SK_HERMES_MSG_TYPE_
|
||||
* BLK_ATTACH, _CONSOLE_CMD, and _ELEVATE_REQUEST above are the
|
||||
* deliberate exceptions: tasks 3.8/3.9/3.10 ARE those types' real
|
||||
* cutovers, so they reuse the FORTH-side value rather than picking a
|
||||
* fresh one. */
|
||||
|
||||
/* sk_hermes_channel_request - requester asks target for a new private
|
||||
* channel. A point-to-point CH_REQUEST (sk_hermes_send_one(), tagged
|
||||
* with SK_HERMES_CHANNEL_COMMON purely for reachability context),
|
||||
* funded by requester's own reservoir. Creates nothing -- the responder
|
||||
* decides via sk_hermes_channel_respond() below.
|
||||
* @return 0 on success, -1 if the send itself was refused. */
|
||||
int sk_hermes_channel_request(VMUuid requester, VMUuid target);
|
||||
|
||||
/* sk_hermes_channel_respond - target answers a pending request from
|
||||
* requester, `approved` supplied by the caller (see this section's own
|
||||
* top comment on why that is task 3.7's future call site, not a policy
|
||||
* decision living here).
|
||||
*
|
||||
* Approved: creates a new private channel, subscribes both requester
|
||||
* and target, sends CH_GRANT to requester (msg->channel = the new
|
||||
* channel id -- how the requester learns which channel to use) followed
|
||||
* by one ACK (the ruled channel-open+delivery cadence). If channel
|
||||
* creation fails (table full) or either subscription fails, the
|
||||
* partial channel is destroyed and this call falls through to the
|
||||
* denied path below instead of leaving a half-open channel or a silent
|
||||
* false grant -- the same "leave no half-state" discipline
|
||||
* sk_hermes_publish()'s own per-subscriber rollback already uses.
|
||||
*
|
||||
* Denied (approved==0, or a failed grant attempt as above): sends NACK
|
||||
* to requester ("a deny is a NACK", SXLV.1 -- no separate type). No
|
||||
* channel exists afterward either way.
|
||||
*
|
||||
* @return The new channel id (>= 0) on a real grant, or -1 on any deny
|
||||
* -- both a policy deny and a failed grant attempt leave no
|
||||
* channel behind, which is what this task's own check asks for.
|
||||
*/
|
||||
int sk_hermes_channel_respond(VMUuid target, VMUuid requester, int approved);
|
||||
|
||||
/* sk_hermes_channel_close - tears down a private channel. Authority is
|
||||
* membership alone: either party may close a channel it belongs to
|
||||
* (narrowest defensible rule given both are already trusted members of
|
||||
* it -- not gated on anything beyond that, and not itself a ruling).
|
||||
* Refuses (-1, no effect) if closer is not a member of channel_id, or
|
||||
* channel_id is SK_HERMES_CHANNEL_COMMON (sk_hermes_channel_destroy()
|
||||
* itself already refuses the common channel).
|
||||
* @return 0 on success, -1 on refusal. */
|
||||
int sk_hermes_channel_close(VMUuid closer, int channel_id);
|
||||
|
||||
/*
|
||||
* Channel-open policy hook (FABRIC-3.6.md task 3.7, B1, ruling 3.0b:
|
||||
* "kernel-Hermes asks ACL.4th, never decides in C, never gates on
|
||||
* zuse_session"). sk_hermes_channel_open_policy() is the CALL SITE task
|
||||
* 3.6's own doc comment named as replacing sk_hermes_channel_respond()'s
|
||||
* caller-supplied `approved` bool with a real query -- this function IS
|
||||
* that query; sk_hermes_channel_respond()'s own signature stays
|
||||
* unchanged, as promised.
|
||||
*
|
||||
* Deliberately NOT vm_interpret() (contrast task 3.4's drain, which
|
||||
* genuinely needs to run arbitrary payload text): HERMES-CHANNEL-OPEN?
|
||||
* is a FIXED, known word name, so this uses the plain-word-dispatch
|
||||
* pattern FABRIC-3.md SXX already established for Hera's own MSG-TICK
|
||||
* self-drain ("a plain word dispatch in her own dictionary, not a
|
||||
* VM-EXEC dispatch into anyone else's input buffer -- no reentrancy
|
||||
* risk") -- vm_find_word() + calling the entry's own func pointer
|
||||
* directly, with the requester passed via the target's own data stack.
|
||||
* This touches neither target_vm->input_buffer nor input_pos, so it
|
||||
* carries none of task 3.4's cursor-preservation hazard; it DOES still
|
||||
* touch target_vm's own data stack (dsp), which is the correct and
|
||||
* expected thing for a word call to do. Calling this from within a live
|
||||
* checkpoint on a VM that is itself mid-dispatch is deferred, undecided
|
||||
* territory -- this task (like every Phase 3 task before Stage C/3.8+)
|
||||
* is self-test only, not wired into any real checkpoint yet.
|
||||
*
|
||||
* Fails CLOSED, not open: if target_vm has no HERMES-CHANNEL-OPEN? word
|
||||
* (e.g. ACL.4th was never loaded into it) or the word itself sets
|
||||
* target_vm->error or leaves too few stack items, this returns 0
|
||||
* (denied) -- absence of policy must never silently mean "always
|
||||
* allow" (CLAUDE.md's own posture on ACL policy). A policy-word bug's
|
||||
* own error state is cleared on target_vm before returning, so a
|
||||
* broken policy word cannot leak an error into whatever else target_vm
|
||||
* is doing.
|
||||
*
|
||||
* @param target_vm The VM being asked -- its own dictionary is
|
||||
* consulted, its own data stack is used for the call.
|
||||
* @param requester Passed to HERMES-CHANNEL-OPEN? as (hi, lo) on
|
||||
* target_vm's own stack.
|
||||
* @return 1 if HERMES-CHANNEL-OPEN? approved (returned a nonzero top of
|
||||
* stack), 0 otherwise (denied, missing, or errored).
|
||||
*/
|
||||
int sk_hermes_channel_open_policy(VM *target_vm, VMUuid requester);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_VM_KERNEL_HERMES_H */
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* parity.h - Parity packet for VM validation
|
||||
*
|
||||
* Defines structures and functions for comparing hosted vs kernel VM state.
|
||||
* Parity is verified via canonical dictionary hash, not raw memory comparison.
|
||||
*
|
||||
* M7 Normative Rules:
|
||||
* - word_id is monotonic creation index starting at 0
|
||||
* - Dictionary traversal is creation order (oldest to newest)
|
||||
* - Hash excludes pointers, padding, runtime fields
|
||||
* - Colon bodies are hashed as word_id sequences, not addresses
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_PARITY_H
|
||||
#define STARKERNEL_PARITY_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* Bootstrap result codes
|
||||
*/
|
||||
#define SK_BOOTSTRAP_OK 0
|
||||
#define SK_BOOTSTRAP_ARENA_FAIL 1
|
||||
#define SK_BOOTSTRAP_INIT_FAIL 2
|
||||
#define SK_BOOTSTRAP_DICT_FAIL 3
|
||||
|
||||
/**
|
||||
* ParityPacket - Summary of VM state for comparison
|
||||
*
|
||||
* M7.1a fields are sufficient for bootstrap validation.
|
||||
* M7.1b fields are added for POST validation.
|
||||
*/
|
||||
typedef struct {
|
||||
/* === M7.1a: Bootstrap Parity === */
|
||||
uint32_t word_count; /* Number of dictionary entries */
|
||||
uint32_t here_offset; /* vm->here (bytes used in dictionary) */
|
||||
uint32_t latest_word_id; /* vm->latest->word_id (stable ID) */
|
||||
uint64_t header_hash64; /* Canonical dictionary hash (FNV-1a) */
|
||||
|
||||
/* === M7.1b: POST Parity === */
|
||||
uint32_t tests_total; /* Total tests executed */
|
||||
uint32_t tests_passed; /* Tests passed */
|
||||
uint32_t tests_failed; /* Tests failed */
|
||||
uint32_t tests_skipped; /* Tests skipped */
|
||||
uint32_t tests_errors; /* Tests with errors */
|
||||
|
||||
/* === Optional: Rolling Window Hash === */
|
||||
uint64_t window_hash64; /* Hash of execution history (if deterministic) */
|
||||
|
||||
/* === Status === */
|
||||
int bootstrap_result; /* SK_BOOTSTRAP_* code */
|
||||
} ParityPacket;
|
||||
|
||||
/**
|
||||
* Forward declaration of VM struct
|
||||
*/
|
||||
struct VM;
|
||||
|
||||
/*
|
||||
* sk_parity_collect - Collect parity data from VM
|
||||
*
|
||||
* Traverses dictionary in creation order, computes canonical hash.
|
||||
* Does NOT include runtime fields (execution_heat, physics, etc.).
|
||||
*
|
||||
* @param vm Pointer to VM instance
|
||||
* @param out Pointer to ParityPacket to fill
|
||||
*/
|
||||
void sk_parity_collect(struct VM *vm, ParityPacket *out);
|
||||
|
||||
/*
|
||||
* sk_parity_print - Print parity packet to console
|
||||
*
|
||||
* Output format:
|
||||
* PARITY:M7.1a word_count=N here=0xHHHH latest_id=N hash=0xHHHHHHHHHHHHHHHH
|
||||
* PARITY:M7.1b tests=N pass=N fail=N skip=N err=N
|
||||
*
|
||||
* @param pkt Pointer to ParityPacket to print
|
||||
*/
|
||||
void sk_parity_print(const ParityPacket *pkt);
|
||||
|
||||
/*
|
||||
* sk_dict_canonical_hash - Compute canonical dictionary hash
|
||||
*
|
||||
* Hashes structural fields only:
|
||||
* - flags, name_len, name[], acl_default, word_id
|
||||
* - For colon words: body as word_id sequence
|
||||
*
|
||||
* Excludes:
|
||||
* - link (pointer)
|
||||
* - func (function pointer)
|
||||
* - execution_heat (runtime)
|
||||
* - physics (runtime)
|
||||
* - transition_metrics (pointer)
|
||||
*
|
||||
* @param vm Pointer to VM instance
|
||||
* @return 64-bit FNV-1a hash
|
||||
*/
|
||||
uint64_t sk_dict_canonical_hash(struct VM *vm);
|
||||
|
||||
/*
|
||||
* sk_dict_word_count - Count dictionary entries
|
||||
*
|
||||
* Traverses from vm->latest to NULL.
|
||||
*
|
||||
* @param vm Pointer to VM instance
|
||||
* @return Number of dictionary entries
|
||||
*/
|
||||
uint32_t sk_dict_word_count(struct VM *vm);
|
||||
|
||||
/**
|
||||
* FNV-1a constants
|
||||
*/
|
||||
#define FNV1A_64_OFFSET_BASIS 0xCBF29CE484222325ULL
|
||||
#define FNV1A_64_PRIME 0x100000001B3ULL
|
||||
|
||||
/*
|
||||
* fnv1a_64 - FNV-1a 64-bit hash function
|
||||
*
|
||||
* @param data Pointer to data to hash
|
||||
* @param len Length of data
|
||||
* @param hash Current hash value (use FNV1A_64_OFFSET_BASIS for initial)
|
||||
* @return Updated hash value
|
||||
*/
|
||||
uint64_t fnv1a_64(const uint8_t *data, size_t len, uint64_t hash);
|
||||
|
||||
#endif /* STARKERNEL_PARITY_H */
|
||||
@@ -0,0 +1,589 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* stadium.h - The Stadium cell and header (FABRIC-0.md §3, punch list item 3.1)
|
||||
*
|
||||
* A cell is one of exactly two things: a patron header, or a continuation
|
||||
* cell owned by exactly one patron. The union is closed, two-valued, and
|
||||
* fixed at build time -- not a type field. See FABRIC-0.md §3.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VM_STADIUM_H
|
||||
#define STARKERNEL_VM_STADIUM_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include "starforth_config.h" /* STADIUM_CONTAINS_DEPTH_MAX, STADIUM_CAPACITY_TICK, STADIUM_MEMORY_PERCENT */
|
||||
#include "starkernel/vm_uuid.h" /* VMUuid -- FABRIC-0.md item 3.8 */
|
||||
|
||||
#define STADIUM_CELL_BYTES 64
|
||||
|
||||
/* Sentinel for `contains` meaning "holds no patron." Not 0 -- cell index 0 is
|
||||
* a valid index (Hera, item 3.6), so 0 cannot double as "none" without
|
||||
* conflating "contains Hera" with "contains nothing." */
|
||||
#define STADIUM_CONTAINS_NONE ((uint32_t)-1)
|
||||
|
||||
/* `flags` bit 0 -- pinned, exempt from eviction/reap. Moved here from a
|
||||
* stadium.c-private #define (FABRIC-2.md §H.12 step 3) so session.c's pin-
|
||||
* authority choke point (session_set_pinned()/session_is_pinned()) can
|
||||
* write/read this same bit without a duplicate definition. Session is
|
||||
* authoritative for every EXTERNAL reader (FABRIC-2.md §H.10) -- this bit
|
||||
* on the raw patron header stays a mirrored copy purely for the Stadium
|
||||
* engine's own internal eviction/admission logic (stadium.c), which must
|
||||
* stay self-contained and not call back into session.c. */
|
||||
#define STADIUM_FLAG_PIN 0x01u
|
||||
|
||||
/*
|
||||
* StadiumPatronHeader - one member of the closed two-valued cell union
|
||||
* (FABRIC-0.md §3). Nine wires: identity, heat, TTL, pin (a bit in `flags`),
|
||||
* link, code field (`behaviour`), mass, payload, contains. `flags` bit 0 is
|
||||
* `pin`; the remaining bits are reserved. `behaviour` is the closed code-field
|
||||
* enumeration (§18.3) -- not yet defined, item 3.3's scope.
|
||||
*
|
||||
* Field order is largest-to-smallest so natural C99 alignment adds zero
|
||||
* padding: every offset below is already a multiple of that field's own
|
||||
* alignment, and the struct's total size (64) is a multiple of its max
|
||||
* alignment (8), so no compiler inserts trailing padding either. Do not
|
||||
* reorder without re-checking this holds on all three ISAs.
|
||||
*/
|
||||
typedef struct {
|
||||
uint64_t identity; /* offset 0 -- handle or name, never a content hash while resident (§24.4) */
|
||||
uint64_t heat; /* offset 8 -- Q48.16, conserved share of 1.0 (§19.1) */
|
||||
uint32_t ttl; /* offset 16 -- remaining lifetime; messages and ACLs only (§17.1) */
|
||||
uint32_t link; /* offset 20 -- index into the Stadium, not a pointer */
|
||||
uint32_t contains; /* offset 24 -- index of the patron held inside this one, or
|
||||
* STADIUM_CONTAINS_NONE (item 1.1). Cell index 0 is a valid
|
||||
* index (Hera, item 3.6) so 0 cannot mean "none" -- item 3.5
|
||||
* caught this and picked UINT32_MAX instead. Chains up to
|
||||
* STADIUM_CONTAINS_DEPTH_MAX deep; reap-gating enforcement of
|
||||
* that bound is item 3.5's scope. */
|
||||
uint16_t mass; /* offset 28 -- cells this patron occupies (§19.2) */
|
||||
uint8_t flags; /* offset 30 -- bit 0 = pin; remaining bits reserved */
|
||||
uint8_t behaviour; /* offset 31 -- code field. Valid values are StadiumBehaviour (§18.3)
|
||||
* tags cast to uint8_t -- kept as uint8_t rather than the enum type
|
||||
* itself since C does not guarantee an enum's underlying type, and
|
||||
* this field's offset is load-bearing for the 64-byte layout item
|
||||
* 3.1 validated. */
|
||||
uint8_t payload[32]; /* offset 32 -- inline payload, used when mass == 1 */
|
||||
} StadiumPatronHeader;
|
||||
|
||||
/*
|
||||
* StadiumContinuationCell - the other member of the union. Owned by exactly
|
||||
* one patron header, chained by `next`. Never ranked, never reaped, never
|
||||
* dispatched (§3) -- pure floor space, accounted for in its owner's mass.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t next; /* offset 0 -- index of the next continuation cell, or none */
|
||||
uint8_t payload[60]; /* offset 4 */
|
||||
} StadiumContinuationCell;
|
||||
|
||||
/*
|
||||
* StadiumCell - the closed two-valued union itself (§3). Which member is
|
||||
* valid for a given array slot is NOT stored in the cell -- FABRIC-0.md's item
|
||||
* 3.1 amendment to §3 rules this an external side bitmap, one bit per cell,
|
||||
* kept outside the cell array. Declared here as the indexing contract this
|
||||
* type expects; item 3.2 (boot-time allocation) allocates the bitmap itself.
|
||||
*/
|
||||
typedef union {
|
||||
StadiumPatronHeader header;
|
||||
StadiumContinuationCell continuation;
|
||||
} StadiumCell;
|
||||
|
||||
/* C99-portable compile-time size assertions (no _Static_assert -- that's C11). */
|
||||
typedef char stadium_header_size_check[(sizeof(StadiumPatronHeader) == STADIUM_CELL_BYTES) ? 1 : -1];
|
||||
typedef char stadium_continuation_size_check[(sizeof(StadiumContinuationCell) == STADIUM_CELL_BYTES) ? 1 : -1];
|
||||
typedef char stadium_cell_size_check[(sizeof(StadiumCell) == STADIUM_CELL_BYTES) ? 1 : -1];
|
||||
|
||||
/*
|
||||
* Items 1.1 and 1.4 named this item as where their Kconfig symbols would be
|
||||
* implemented. STADIUM_CONTAINS_DEPTH_MAX still has no consumer (item 3.5
|
||||
* for the depth cap, not yet implemented). STADIUM_CAPACITY_TICK was wired
|
||||
* in 2026-08-15 (capsule_vm_physics.c's vm_physics_heartbeat_tick(), see
|
||||
* FABRIC-1.md F.2/§12 Q5) -- this check now proves a real, live constant
|
||||
* is sane, not just a placeholder, same discipline already applied to the
|
||||
* byte-count checks above.
|
||||
*/
|
||||
typedef char stadium_contains_depth_configured_check[(STADIUM_CONTAINS_DEPTH_MAX > 0) ? 1 : -1];
|
||||
typedef char stadium_capacity_tick_configured_check[(STADIUM_CAPACITY_TICK > 0) ? 1 : -1];
|
||||
|
||||
/*
|
||||
* Item 3.7, revised 2026-08-15: the per-cell owner array stores a quota-slot
|
||||
* index. The VM population bound is no longer a compile-time constant (see
|
||||
* stadium_max_vm_count() below), so this can no longer be a compile-time
|
||||
* assert -- the owner element type is now uint16_t (65535 slots of
|
||||
* headroom), and stadium_boot_init() itself clamps the computed count to
|
||||
* that range at runtime, logging if it ever has to.
|
||||
*/
|
||||
|
||||
/*
|
||||
* stadium_boot_init - Boot-time allocation (FABRIC-0.md item 3.2, §17.6 position
|
||||
* (b)). Sizes the global cell array from the memory budget actually observed
|
||||
* at boot -- STADIUM_MEMORY_PERCENT of kmalloc_get_stats().free_bytes at the
|
||||
* point of the call, rounded down to whole STADIUM_CELL_BYTES cells -- rather
|
||||
* than a hardcoded count. (Corrected 2026-08-15 from pmm_get_stats(): PMM's
|
||||
* free-byte figure reflects physical pages not yet handed to any subsystem,
|
||||
* but kmalloc_init() (M6) already carved its own fixed-size heap out of PMM
|
||||
* before this ever runs, and every allocation in this function actually
|
||||
* draws from that kmalloc heap, not raw PMM -- pmm_get_stats() was budgeting
|
||||
* against a pool nothing here actually allocates from.) Also computes the
|
||||
* outer Stadium's VM population bound the same way, from the kmalloc heap's
|
||||
* *remaining* free bytes after the cell array's own allocation: see
|
||||
* stadium_max_vm_count() below. Also allocates the header/continuation
|
||||
* discriminator bitmap item 3.1 declared but did not allocate: one bit per
|
||||
* cell, bit set means the cell at that index is a patron header, clear means
|
||||
* continuation or not yet in use. Both are kmalloc'd (freestanding kernel,
|
||||
* no separate PMM-backed region needed for this) and explicitly zero-filled,
|
||||
* since kmalloc does not zero.
|
||||
*
|
||||
* (Item 3.7) Also allocates a per-cell owner array (which VM's quota a cell
|
||||
* belongs to; uint16_t as of 2026-08-15, see the note above) and chains
|
||||
* every cell into a single free list, in ascending index order, granted in
|
||||
* full to vm_id 0 (Hera) -- the only VM that exists (item 0.1). Ascending
|
||||
* order guarantees the first-ever admission pops cell 0, preserving item
|
||||
* 3.6's "Hera is patron zero" invariant once real birth-wiring calls
|
||||
* stadium_admit() for the first time. The free-list next-pointer reuses
|
||||
* each cell's own `link` field while unresident -- a repurposing of
|
||||
* documented-but-unspecified storage, not a header change; see
|
||||
* stadium_admit()'s doc for why this doesn't answer the separate,
|
||||
* still-open continuation-chain question.
|
||||
*
|
||||
* Also allocates the VM quota array (stadium_quotas), sized to the
|
||||
* computed stadium_max_vm_count() rather than a compile-time bound.
|
||||
*
|
||||
* Must be called after M6 (kmalloc_init) and before any VM is born (§6). Does
|
||||
* not halt boot on failure -- nothing downstream consumes the Stadium yet.
|
||||
*
|
||||
* @return 0 on success, -1 if kmalloc failed for any of the four allocations.
|
||||
*/
|
||||
int stadium_boot_init(void);
|
||||
|
||||
/* stadium_is_initialized - Whether stadium_boot_init() has succeeded. */
|
||||
int stadium_is_initialized(void);
|
||||
|
||||
/* stadium_cell_count - Number of cells in the array, 0 if not initialized. */
|
||||
size_t stadium_cell_count(void);
|
||||
|
||||
/*
|
||||
* stadium_max_vm_count - The outer Stadium's VM population bound, computed
|
||||
* at stadium_boot_init() from the kmalloc heap's remaining free bytes
|
||||
* (replaces the old compile-time STADIUM_MAX_VM_COUNT, 2026-08-15 -- see
|
||||
* stadium_boot_init()'s own doc). 0 if not initialized. capsule_birth.c's
|
||||
* birth gate reads this instead of a macro.
|
||||
*/
|
||||
size_t stadium_max_vm_count(void);
|
||||
|
||||
/* stadium_cells - Pointer to the cell array, NULL if not initialized. */
|
||||
StadiumCell *stadium_cells(void);
|
||||
|
||||
/*
|
||||
* stadium_header_bitmap - Pointer to the discriminator bitmap declared in
|
||||
* item 3.1, NULL if not initialized. ceil(stadium_cell_count() / 8) bytes.
|
||||
*/
|
||||
uint8_t *stadium_header_bitmap(void);
|
||||
|
||||
/*
|
||||
* StadiumBehaviour - the closed code-field enumeration (FABRIC-0.md §13, §18.3).
|
||||
* The engine dispatches on this tag and never asks a patron what kind it is
|
||||
* -- §3's entire point. Two patrons may share a tag: a VM's tag is COOL, the
|
||||
* same tag a word carries (§18.3). Mapped from §17.1's patron table:
|
||||
*
|
||||
* MIGRATE -- blocks: reap event is migration back to Artemis (§17.2)
|
||||
* DELIVER -- messages: reap event is delivery
|
||||
* EXPIRE -- ACLs: reap event is TTL expiry
|
||||
* COOL -- words and VMs: reap event is cooling off the floor
|
||||
*
|
||||
* Closed and fixed at build time -- see stadium_dispatch()'s exhaustive
|
||||
* switch for how the compiler enforces that.
|
||||
*/
|
||||
typedef enum {
|
||||
STADIUM_BEHAVIOUR_MIGRATE = 0,
|
||||
STADIUM_BEHAVIOUR_DELIVER,
|
||||
STADIUM_BEHAVIOUR_EXPIRE,
|
||||
STADIUM_BEHAVIOUR_COOL
|
||||
} StadiumBehaviour;
|
||||
|
||||
/*
|
||||
* stadium_dispatch - Calls the behaviour handler for a patron's code field.
|
||||
* The engine calls this at reap and never asks what kind of patron departed
|
||||
* (§3, §18.3) -- only cell_index and behaviour cross this boundary.
|
||||
*
|
||||
* Handlers are stubs today: the real migrate-to-Artemis / deliver / expire /
|
||||
* cool actions belong to their own subsystems, which have not been migrated
|
||||
* onto the Stadium yet (Phase 4, §25.5). Nothing calls stadium_dispatch()
|
||||
* yet either -- that begins with item 3.5 (admission and eviction).
|
||||
*
|
||||
* @param cell_index Index into the Stadium of the patron header dispatching.
|
||||
* @param behaviour Which of the closed tag set to invoke.
|
||||
*/
|
||||
void stadium_dispatch(size_t cell_index, StadiumBehaviour behaviour);
|
||||
|
||||
/*
|
||||
* stadium_density - Heat / mass for the patron header at cell_index (FABRIC-0.md
|
||||
* §19.2, §19.3). Read, not computed by a scheduler: both operands already
|
||||
* live in the header, so this is a division on demand, not maintained
|
||||
* bookkeeping. Result stays valid Q48.16, since heat is already Q48.16 and
|
||||
* mass is a plain integer divisor.
|
||||
*
|
||||
* Returns 0 if mass is 0 -- an empty or never-admitted slot (everything is
|
||||
* zero-initialized by stadium_boot_init() until something is actually born
|
||||
* into the Stadium, which nothing yet does) has no footprint to be dense
|
||||
* within, rather than a division by zero.
|
||||
*
|
||||
* Does not validate that cell_index actually holds a header rather than a
|
||||
* continuation cell or an out-of-range index -- callers are expected to
|
||||
* consult the item-3.1 discriminator bitmap first. Ranking (finding the
|
||||
* densest or least-dense resident) is item 3.5's scope, not this one's;
|
||||
* this function only supplies the per-cell value that comparison reads.
|
||||
*
|
||||
* @param cell_index Index into the Stadium of the patron header to measure.
|
||||
* @return Density in Q48.16, or 0 if the header's mass is 0.
|
||||
*/
|
||||
uint64_t stadium_density(size_t cell_index);
|
||||
|
||||
/* Sentinel returned by stadium_admit() on refusal -- no cell index is this large. */
|
||||
#define STADIUM_CELL_NONE ((size_t)-1)
|
||||
|
||||
/*
|
||||
* Hera is patron zero by construction of §6's boot order: she is the first
|
||||
* entry admitted into the Stadium. This is a positional invariant, not a
|
||||
* runtime check of who currently occupies cell 0 -- nothing yet births
|
||||
* anything, Hera included, so today this index is never actually occupied.
|
||||
* Used only by stadium_evict()'s item-3.6 assertion below.
|
||||
*/
|
||||
#define STADIUM_HERA_CELL_INDEX ((size_t)0)
|
||||
|
||||
/*
|
||||
* stadium_birth_hera - Admits Hera as a real resident of cell 0 (FABRIC-0.md
|
||||
* item 3.6's invariant, actually enforced -- item 4.1 found that nothing had
|
||||
* ever called this until a word patron was about to become the first-ever
|
||||
* occupant of cell 0 by accident via the free list). Candidate: identity 0,
|
||||
* heat 0, mass 1, pinned (STADIUM_FLAG_PIN), behaviour COOL. Heat 0 means no
|
||||
* reservoir transfer is needed -- conservation holds trivially (the
|
||||
* reservoir keeps the VM's whole share; Hera's own cell contributes 0).
|
||||
* Being pinned excludes her from every eviction-candidate scan (§3), so the
|
||||
* stadium_evict() panic guard at STADIUM_HERA_CELL_INDEX stays correctly
|
||||
* dormant rather than reachable-by-accident.
|
||||
*
|
||||
* Idempotent: a second call is a no-op (returns 0) if she is already
|
||||
* resident. Must be called after stadium_boot_init() and before any word
|
||||
* ever dispatches (§6) -- kernel_main.c calls it immediately after
|
||||
* stadium_boot_init(), before M7's VM bootstrap.
|
||||
*
|
||||
* @return 0 on success (or already born), -1 if the Stadium is not
|
||||
* initialized or the admission was refused (should not happen: her
|
||||
* quota is granted in full, empty, at stadium_boot_init()).
|
||||
*/
|
||||
int stadium_birth_hera(void);
|
||||
|
||||
/*
|
||||
* stadium_reservoir_pull - Transfers up to `amount` (Q48.16) out of vm_id's
|
||||
* reservoir (FABRIC-0.md §17.7's reservoir mechanism). Clamped to what the
|
||||
* reservoir actually holds -- never goes negative, never invents heat.
|
||||
* Returns the amount actually pulled, which may be less than requested (or
|
||||
* 0, e.g. a drained reservoir or an unknown vm_id). Callers that go on to
|
||||
* fail their own operation (e.g. a refused stadium_admit()) MUST push the
|
||||
* pulled amount back via stadium_reservoir_push() to preserve
|
||||
* Σ(resident heat) + reservoir == Q48_ONE across the failed attempt.
|
||||
*
|
||||
* @param vm_id Owning VM's id.
|
||||
* @param amount Requested Q48.16 amount.
|
||||
* @return Amount actually pulled (0..amount).
|
||||
*/
|
||||
uint64_t stadium_reservoir_pull(VMUuid vm_id, uint64_t amount);
|
||||
|
||||
/*
|
||||
* stadium_reservoir_push - Credits `amount` (Q48.16) back into vm_id's
|
||||
* reservoir. The other half of every reservoir transfer (§17.7): cooling
|
||||
* returns heat here, a refused starter-grant rolls back here, and
|
||||
* stadium_evict() credits a departing patron's remaining heat here before
|
||||
* the cell returns to the free list -- the invariant is a transfer, never a
|
||||
* reset. No-op if vm_id has no quota (caller contract; mirrors
|
||||
* stadium_admit()'s silent refusal for the same case).
|
||||
*
|
||||
* @param vm_id Owning VM's id.
|
||||
* @param amount Q48.16 amount to credit.
|
||||
*/
|
||||
void stadium_reservoir_push(VMUuid vm_id, uint64_t amount);
|
||||
|
||||
/*
|
||||
* stadium_reservoir_peek - Read-only: vm_id's current reservoir balance
|
||||
* (Q48.16), for diagnostics/conservation checks. Does not mutate state.
|
||||
* Returns 0 for an unknown vm_id -- indistinguishable from a genuinely
|
||||
* drained reservoir, same as stadium_reservoir_pull()'s 0 return; callers
|
||||
* that need to tell those apart must already know whether vm_id has a
|
||||
* quota (e.g. via the same check they'd use before calling stadium_admit()).
|
||||
*
|
||||
* @param vm_id Owning VM's id.
|
||||
* @return Current reservoir balance, or 0 if vm_id has no quota.
|
||||
*/
|
||||
uint64_t stadium_reservoir_peek(VMUuid vm_id);
|
||||
|
||||
/*
|
||||
* stadium_quota_slot_for_vm - Read-only: vm_id's quota slot index (0 to
|
||||
* stadium_max_vm_count()-1), for callers outside stadium.c that need to key
|
||||
* their own per-VM state the same way stadium.c's internal arrays already
|
||||
* do (FABRIC-0.md §25.5 item 4.2 -- stadium_words.c's word_id -> cell_index
|
||||
* map needs this to stop colliding across VMs; word_id is scoped per-VM,
|
||||
* not globally unique, so a single shared map aliases different VMs' words
|
||||
* onto each other's Stadium cells and reservoirs).
|
||||
*
|
||||
* @param vm_id VM to look up.
|
||||
* @return Quota slot index, or -1 if vm_id holds no quota.
|
||||
*/
|
||||
int stadium_quota_slot_for_vm(VMUuid vm_id);
|
||||
|
||||
/*
|
||||
* stadium_resident_sum - Read-only: sum of heat across every cell currently
|
||||
* resident AND owned by vm_id's own quota (FABRIC-0.md §25.5 item 4.2 --
|
||||
* boot diagnostics need this filtered per-VM once a second VM holds a
|
||||
* quota; summing every resident cell regardless of owner, as the pre-4.2
|
||||
* diagnostic did, mixes two VMs' conservation totals together).
|
||||
* Returns 0 for an unknown vm_id, same convention as stadium_reservoir_peek().
|
||||
*
|
||||
* @param vm_id Owning VM's id.
|
||||
* @return Sum of resident heat owned by vm_id (Q48.16), or 0 if vm_id has no quota.
|
||||
*/
|
||||
uint64_t stadium_resident_sum(VMUuid vm_id);
|
||||
|
||||
/*
|
||||
* stadium_conserved - Boolean analogue of vm_physics_conserved(), for the
|
||||
* Stadium per-VM quota invariant rather than fleet-wide execution heat
|
||||
* (FABRIC-3.5.md §XXXIX.4, item 41). Checks, epsilon zero:
|
||||
*
|
||||
* stadium_resident_sum(vm_id) + stadium_reservoir_peek(vm_id) == Q48_ONE
|
||||
*
|
||||
* FABRIC-3.5.md §XL.4: the full invariant also has a per-VM `consumed`
|
||||
* term (task 2.7), recorded by kernel-Hermes when its messages decay:
|
||||
*
|
||||
* stadium_resident_sum + stadium_reservoir_peek + stadium_consumed_peek
|
||||
* == Q48_ONE
|
||||
*
|
||||
* The two-term form above is the special case consumed == 0.
|
||||
*
|
||||
* Returns 0 (not conserved) for an unknown vm_id, same convention as
|
||||
* stadium_reservoir_peek()/stadium_resident_sum() returning 0 for one.
|
||||
*
|
||||
* @param vm_id VM to check.
|
||||
* @return Non-zero if vm_id's Stadium quota is exactly conserved, 0 otherwise.
|
||||
*/
|
||||
int stadium_conserved(VMUuid vm_id);
|
||||
|
||||
/*
|
||||
* stadium_consumed_record - Add amount to vm_id's per-VM consumed total
|
||||
* (heat destroyed by decay; FABRIC-3.5.md §XL.4). No-op if vm_id has no
|
||||
* quota. Caller (kernel-Hermes decay) must have removed the same amount
|
||||
* from a resident patron's heat.
|
||||
*/
|
||||
void stadium_consumed_record(VMUuid vm_id, uint64_t amount);
|
||||
|
||||
/* stadium_consumed_peek - vm_id's consumed total, 0 for an unknown vm_id. */
|
||||
uint64_t stadium_consumed_peek(VMUuid vm_id);
|
||||
|
||||
/*
|
||||
* stadium_evict - Reap the patron header at cell_index (FABRIC-0.md §17.2:
|
||||
* "reap means leaves the floor, not destroyed"). Dispatches its behaviour
|
||||
* (§18.3), clears its item-3.1 discriminator bit, zeroes its header, and
|
||||
* (item 3.7) returns the freed cell to the free list of whichever VM's
|
||||
* quota it was drawn from -- looked up via the internal per-cell owner
|
||||
* record, not passed by the caller.
|
||||
*
|
||||
* PANICS (does not return) if cell_index == STADIUM_HERA_CELL_INDEX and the
|
||||
* cell is actually resident -- FABRIC-0.md §20.5 #3: Hera is pinned (§3), but
|
||||
* pinning alone is a silent guarantee, and item 3.6 requires a hard
|
||||
* assertion at the eviction site rather than relying on pin holding. This
|
||||
* check runs BEFORE the pin/contains checks below, deliberately: if pin were
|
||||
* ever wrongly cleared, the ordinary pin-refusal path would quietly return
|
||||
* -1 instead of surfacing the break, defeating the point of a second,
|
||||
* independent check. Selecting patron zero for eviction means the invariant
|
||||
* is already broken; continuing would run the system without a governor.
|
||||
*
|
||||
* Refuses (returns -1, does not panic) if the header is pinned (`flags` bit
|
||||
* 0, §3's invariance wire) or has a non-none `contains` (item 1.1: a patron
|
||||
* holding another cannot be reaped, full stop). Also refuses for an
|
||||
* out-of-range index or a cell whose discriminator bit is not set (nothing
|
||||
* resident there to reap).
|
||||
*
|
||||
* @param cell_index Index of the patron header to reap.
|
||||
* @return 0 on success, -1 if refused.
|
||||
*/
|
||||
int stadium_evict(size_t cell_index);
|
||||
|
||||
/*
|
||||
* StadiumVMQuota - per-VM ownership of a subset of the global cell array
|
||||
* (FABRIC-0.md §22.3, item 3.7: "each VM holds its own free-list head index
|
||||
* into the global array"). Linearly searched by vm_id -- a VMUuid (item 3.8)
|
||||
* can't be used as a direct array index anyway. Was a small, compile-time-
|
||||
* bounded table (linear scan "costs nothing" at the old default of 4);
|
||||
* since 2026-08-15 the table is sized at boot from stadium_max_vm_count()
|
||||
* and could genuinely be large, so this scan is no longer assumed free --
|
||||
* flagged here rather than silently carried forward as still-obviously-fine,
|
||||
* though no algorithmic change was made in this pass. Not exposed outside
|
||||
* stadium.c: nothing outside needs to inspect
|
||||
* quota state directly yet. Slot emptiness is tracked by an internal
|
||||
* `in_use` flag, not a vm_id sentinel value -- there is no unused vm_id bit
|
||||
* pattern to reserve for it.
|
||||
*/
|
||||
|
||||
/*
|
||||
* stadium_admit - Place a candidate patron header into the Stadium, scoped
|
||||
* to vm_id's quota (FABRIC-0.md §19.3, §22.3, item 3.7).
|
||||
*
|
||||
* Pops vm_id's free-list head first (O(1)) if non-empty. Only if that VM's
|
||||
* free list is exhausted does this fall back to eviction -- scoped to that
|
||||
* SAME VM's own resident patrons only (quota isolation: a VM's admission can
|
||||
* never evict another VM's patron), finding the least-dense evictable
|
||||
* resident (not pinned, not `contains`-gated -- per §3 and item 1.1) and
|
||||
* evicting it via stadium_evict() only if the candidate is strictly denser
|
||||
* (§19.3: "denser than," not "at least as dense as"). Otherwise refuses.
|
||||
*
|
||||
* Does not itself assert anything about which resident this turns out to be
|
||||
* -- the item-3.6 rule that patron zero (Hera) must never actually be
|
||||
* selected is a separate, later check at the eviction site.
|
||||
*
|
||||
* REFUSES if vm_id has no quota granted (only Hera, vm_uuid_hera(), has one
|
||||
* today -- granted the entire array at stadium_boot_init(), since she is the
|
||||
* only VM that exists per item 0.1). Granting quota to additional VMs, and
|
||||
* transferring capacity between them, is capacity ARBITRATION -- item 1.3
|
||||
* left "how much capacity moves per eligible transfer" explicitly open, so
|
||||
* this item does not invent it. Only the boot-time all-to-Hera grant exists.
|
||||
*
|
||||
* REFUSES any candidate with mass != 1. A multi-cell patron (mass > 1, e.g.
|
||||
* §23.3's 1024-byte block at mass 19) needs a continuation chain, and no
|
||||
* header field is documented anywhere as carrying the index of a patron's
|
||||
* first continuation cell -- `link` is described only as generic "index
|
||||
* into the Stadium, not a pointer." This item repurposes `link` for a
|
||||
* different, non-conflicting use (the free-list next-pointer, while a cell
|
||||
* is unresident -- see stadium.c), but does not invent an answer to the
|
||||
* continuation-chain question, which stays open. Item 3.5's refusal
|
||||
* therefore stands exactly as it was.
|
||||
*
|
||||
* REQUIRES candidate->contains to be either STADIUM_CONTAINS_NONE or a valid
|
||||
* index (< the current cell count) -- refuses otherwise. This does NOT catch
|
||||
* a zero-initialized candidate that was meant to contain nothing: 0 is a
|
||||
* valid index (Hera), so a caller that forgets to set `contains` explicitly
|
||||
* to STADIUM_CONTAINS_NONE will admit a patron that reads as "contains
|
||||
* Hera" and is therefore permanently un-evictable. There is no way to tell
|
||||
* "meant to be 0" from "forgot to set it" from inside this function --
|
||||
* callers must set every field, `contains` included.
|
||||
*
|
||||
* @param vm_id Owning VM's id (capsule_birth.c's registry). Allocation
|
||||
* is scoped to this VM's own quota.
|
||||
* @param candidate Header to admit. Copied into the winning cell as-is;
|
||||
* caller fills in every field including mass and heat.
|
||||
* @return The cell index admitted into, or STADIUM_CELL_NONE if refused
|
||||
* (vm_id has no quota, mass != 1, invalid contains, that VM's
|
||||
* quota full and candidate not denser than its least-dense
|
||||
* evictable resident, or it has no evictable resident at all).
|
||||
*/
|
||||
size_t stadium_admit(VMUuid vm_id, const StadiumPatronHeader *candidate);
|
||||
|
||||
/*
|
||||
* stadium_grant_quota - One-time initial quota grant for a newly born VM
|
||||
* (FABRIC-0.md item 4.1a). NOT item 1.3's recurring capacity-transfer
|
||||
* arbitration -- that mechanism (density-gradient-driven, per capacity-tick)
|
||||
* stays unbuilt and its "how much moves" question stays open. This is the
|
||||
* narrower, one-time event: the same shape as Hera's own whole-pool grant at
|
||||
* stadium_boot_init(), just from an existing VM's free list instead of the
|
||||
* boot-time global one.
|
||||
*
|
||||
* Splits from_vm_id's free list evenly by cell count (new VM gets the first
|
||||
* half by list-walk order; from_vm_id keeps the remainder, including any odd
|
||||
* cell). Reassigns stadium_owner[] for every cell that moves. Touches no
|
||||
* resident cell on either side -- only free-list cells move, so from_vm_id's
|
||||
* residents (including a pinned cell 0, if from_vm_id is Hera) are
|
||||
* unaffected. Grants new_vm_id a fresh Q48_ONE reservoir -- NOT a fraction of
|
||||
* from_vm_id's, since conservation is per-VM (see StadiumVMQuota.reservoir's
|
||||
* doc in stadium.c), not a shared pool split across VMs.
|
||||
*
|
||||
* REFUSES (returns -1, does not crash) if: the Stadium is not initialized;
|
||||
* new_vm_id already holds a quota; from_vm_id holds no quota; from_vm_id's
|
||||
* free list has fewer than 2 cells (nothing to split); or no empty quota
|
||||
* slot remains (stadium_max_vm_count() exhausted).
|
||||
*
|
||||
* @param new_vm_id The VM receiving a fresh quota. Must not already have one.
|
||||
* @param from_vm_id The VM whose free list is split. Must already hold a quota.
|
||||
* @return 0 on success, -1 if refused.
|
||||
*/
|
||||
int stadium_grant_quota(VMUuid new_vm_id, VMUuid from_vm_id);
|
||||
|
||||
/*
|
||||
* stadium_best_donor - FABRIC-3.md SXVI donor-floor fix: the currently
|
||||
* in-use VM with the largest free (unclaimed) cell count right now, i.e.
|
||||
* the VM stadium_grant_quota()'s `from_vm_id` argument should be for a new
|
||||
* VM's initial grant. Callers should NOT hardcode vm_uuid_hera() here --
|
||||
* always splitting from Hera specifically converges her own free list
|
||||
* toward empty after a bounded number of grants (each halves what remains)
|
||||
* even while other, previously-granted VMs still hold nearly all of their
|
||||
* own share untouched, silently starving later births though the Stadium
|
||||
* as a whole has plenty of spare capacity. O(stadium_max_vm_count()) --
|
||||
* a scan over quota slots (bounded by live VM population), not cells.
|
||||
*
|
||||
* @return The VM with the most free cells, or vm_uuid_none() if no VM
|
||||
* holds a quota yet (Stadium not initialized, or called before
|
||||
* Hera's own boot-time grant/admission).
|
||||
*/
|
||||
VMUuid stadium_best_donor(void);
|
||||
|
||||
/*
|
||||
* stadium_cell_heat_get - Read a resident cell's own heat (FABRIC-0.md item
|
||||
* 4.2's fourth ruling). Requires cell_index to be resident AND owned by
|
||||
* vm_id's quota -- returns 0 otherwise (out of range, not resident, or
|
||||
* belongs to a different VM), same ambiguity-with-a-genuine-zero already
|
||||
* accepted by stadium_reservoir_peek()'s doc: callers that need to
|
||||
* distinguish "refused" from "actually zero" must already know the cell is
|
||||
* theirs (e.g. from their own resident-cell tracking), same contract as
|
||||
* every other implicit-self primitive here.
|
||||
*
|
||||
* @param vm_id Calling VM's own identity.
|
||||
* @param cell_index Index of the resident patron header to read.
|
||||
* @return The cell's current heat (Q48.16), or 0 if refused.
|
||||
*/
|
||||
uint64_t stadium_cell_heat_get(VMUuid vm_id, size_t cell_index);
|
||||
|
||||
/*
|
||||
* stadium_cell_heat_set - Write a resident cell's own heat, reconciling the
|
||||
* reservoir delta atomically (FABRIC-0.md item 4.2's fourth ruling). Same
|
||||
* ownership requirement as stadium_cell_heat_get(). If new_heat is higher
|
||||
* than the cell's current heat, pulls the exact difference from vm_id's own
|
||||
* reservoir first -- refuses (returns -1, no mutation) if the reservoir
|
||||
* cannot cover the full increase, never a partial credit that would invent
|
||||
* heat. If new_heat is lower, pushes the exact difference back to the
|
||||
* reservoir after writing. Equal is a no-op success. This is the only
|
||||
* sanctioned way to change a resident cell's heat post-admission -- doing
|
||||
* the reservoir accounting here, not leaving it to the FORTH caller, is the
|
||||
* whole reason this primitive exists rather than a raw field poke.
|
||||
*
|
||||
* @param vm_id Calling VM's own identity.
|
||||
* @param cell_index Index of the resident patron header to write.
|
||||
* @param new_heat The heat value to set (Q48.16).
|
||||
* @return 0 on success, -1 if refused (not owned/resident, or insufficient
|
||||
* reservoir for an increase).
|
||||
*/
|
||||
int stadium_cell_heat_set(VMUuid vm_id, size_t cell_index, uint64_t new_heat);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_VM_STADIUM_H */
|
||||
@@ -0,0 +1,124 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* stadium_blocks.h - Block patrons on the Stadium (FABRIC-2.md §B/§D,
|
||||
* MIGRATE punch-list item)
|
||||
*
|
||||
* The block-specific layer on top of the generic L0 engine (stadium.h), same
|
||||
* relationship stadium_words.h/.c already has: nothing in stadium.c/.h knows
|
||||
* a block patron exists -- it only ever sees cell_index, VMUuid, and
|
||||
* StadiumPatronHeader. This file is where "block" becomes a concrete
|
||||
* meaning: an (owning quota slot, LBN) -> cell_index map, the starter-grant
|
||||
* admission rule (mirrors stadium_word_dispatch()'s Option B exactly), and
|
||||
* the reservoir-quantum touch/cool that feeds and drains a resident block's
|
||||
* Stadium heat.
|
||||
*
|
||||
* Unlike words, LBN is not densely bounded (block_subsystem.c's unified LBN
|
||||
* space spans RAM/RAMDRIVE/DISK/USB and can be large), so the map here is a
|
||||
* fixed-capacity open-addressing hash table sized off stadium_cell_count()
|
||||
* at init, not a dense per-VM array -- see stadium_blocks.c for the layout.
|
||||
* A block's actual 1024 content bytes are never copied into a Stadium cell;
|
||||
* they stay exactly where block_subsystem.c already keeps them. The cell
|
||||
* only ever carries identity (the LBN) and heat/bookkeeping, same as a word
|
||||
* patron's cell never carries the word's own dictionary entry.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VM_STADIUM_BLOCKS_H
|
||||
#define STARKERNEL_VM_STADIUM_BLOCKS_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "starkernel/vm_uuid.h"
|
||||
|
||||
/*
|
||||
* stadium_blocks_init - Allocates and zeroes the (quota slot, LBN) ->
|
||||
* cell_index hash table (capacity computed from stadium_cell_count() *
|
||||
* STADIUM_BLOCK_TRACK_CAP_MULT at call time, kmalloc'd). Must be called
|
||||
* after stadium_boot_init() -- so stadium_cell_count() is non-zero -- and
|
||||
* before any block ever dispatches; the real boot site is immediately after
|
||||
* the existing stadium_words_init() call (kernel_main.c), same M7/M7.1
|
||||
* ordering. Not safe to call twice -- guarded internally as a no-op if
|
||||
* already initialized, same convention as stadium_words_init().
|
||||
*/
|
||||
void stadium_blocks_init(void);
|
||||
|
||||
/*
|
||||
* stadium_block_dispatch - The per-touch entry point, called from
|
||||
* block_word_block()/block_word_buffer()/block_word_update()
|
||||
* (src/word_source/block_words.c) -- mirroring stadium_word_dispatch()'s
|
||||
* call pattern and cooling/admission logic 1:1, keyed by LBN instead of
|
||||
* word_id.
|
||||
*
|
||||
* If (vm_id, lbn) is already resident: applies the same redirected Loop #3
|
||||
* cooling stadium_word_dispatch() applies (fraction of the cell's own
|
||||
* current heat, scaled by elapsed_ticks since this block's own last touch --
|
||||
* STADIUM_BLOCK_COOL_RATE_Q48), crediting the cooled amount back to vm_id's
|
||||
* reservoir, then pulls STADIUM_BLOCK_HEAT_QUANTUM from the reservoir into
|
||||
* the cell -- clamped to the reservoir's actual balance AND to the same
|
||||
* Q48_ONE / 3 floor stadium_word_dispatch() enforces, so block-touch
|
||||
* admission alone can never starve other reservoir consumers sharing the
|
||||
* same VM.
|
||||
*
|
||||
* If not resident (or the table's entry is stale -- self-healing check
|
||||
* against the cell's discriminator bit and identity, same pattern
|
||||
* resolve_resident_cell() uses in stadium_words.c): attempts starter-grant
|
||||
* admission -- pulls STADIUM_BLOCK_HEAT_QUANTUM (same floor), builds an
|
||||
* unpinned MIGRATE candidate (identity = lbn, mass = 1, payload unused --
|
||||
* the block's real content is never copied here), calls stadium_admit(). On
|
||||
* refusal, pushes the pulled quantum back (rollback). On success, records
|
||||
* the mapping. If the hash table itself is full and has no slot for this
|
||||
* (vm_id, lbn) pair, this touch is silently skipped -- Stadium's own
|
||||
* capacity already bounds real residency, so a table miss under load is
|
||||
* graceful degradation, not an error.
|
||||
*
|
||||
* No-op if vm_id holds no Stadium quota, or stadium_blocks_init() has not
|
||||
* run.
|
||||
*
|
||||
* @param vm_id Owning VM -- vm->stadium_vm_id at every call site,
|
||||
* same quota-isolation reasoning stadium_word_
|
||||
* dispatch() already documents (LBN numbering is
|
||||
* global, not per-VM, but quota scoping still keeps
|
||||
* two VMs' admissions from evicting each other).
|
||||
* @param lbn Logical block number being touched.
|
||||
* @param heartbeat_ticks Current vm->heartbeat.tick_count.
|
||||
*/
|
||||
void stadium_block_dispatch(VMUuid vm_id, uint32_t lbn, uint64_t heartbeat_ticks);
|
||||
|
||||
/*
|
||||
* stadium_blocks_print_boot_diagnostics - Console output mirroring
|
||||
* stadium_words_print_boot_diagnostics(): promotions/evictions for vm_id's
|
||||
* own block-touch table, plus a conservation check
|
||||
* (Σ(resident block heat) + reservoir against Q48_ONE is NOT a standalone
|
||||
* invariant here -- word-execution heat and any other resident shares the
|
||||
* same reservoir, so this prints the block-only resident sum as a
|
||||
* diagnostic term, not a claim that it alone should equal Q48_ONE).
|
||||
*
|
||||
* @param vm_id The VM whose block-touch table/reservoir to read.
|
||||
*/
|
||||
void stadium_blocks_print_boot_diagnostics(VMUuid vm_id);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_VM_STADIUM_BLOCKS_H */
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* stadium_words.h - Word patrons on the Stadium (FABRIC-0.md §17.3/§17.7,
|
||||
* punch list item 4.1)
|
||||
*
|
||||
* The word-specific layer on top of the generic L0 engine (stadium.h).
|
||||
* Nothing in stadium.c/.h knows a word patron exists -- it only ever sees
|
||||
* cell_index, VMUuid, and StadiumPatronHeader. This file is where "word"
|
||||
* becomes a concrete meaning: a word_id -> cell_index map (kernel-side,
|
||||
* deliberately NOT a DictEntry field, decided 2026-08-05), the starter-grant
|
||||
* admission rule (Option B), and the reservoir-quantum touch/cool that feed
|
||||
* and drain a resident word's Stadium heat.
|
||||
*
|
||||
* `execution_heat` and `dict_hash` are untouched by anything in this file
|
||||
* (§17.7) -- this is a second, independent conserved quantity living in the
|
||||
* Stadium cell's `heat` field, not a representation of the first.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VM_STADIUM_WORDS_H
|
||||
#define STARKERNEL_VM_STADIUM_WORDS_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stdint.h>
|
||||
#include "starkernel/vm_uuid.h"
|
||||
|
||||
/*
|
||||
* stadium_words_init - Allocates and zeroes the word_id -> cell_index map
|
||||
* (DICTIONARY_SIZE entries per VM quota slot, stadium_max_vm_count() slots,
|
||||
* kmalloc'd -- was a flat static array before 2026-08-15, when the VM count
|
||||
* bound became RAM-derived rather than a compile-time constant). Must be
|
||||
* called after stadium_boot_init() and stadium_birth_hera(), before any word
|
||||
* ever dispatches. NOT safe to call twice -- unlike the old zero-only
|
||||
* version, a second call would kmalloc a second set of tables and leak the
|
||||
* first; guarded internally as a no-op if already initialized. Nothing
|
||||
* calls it twice today.
|
||||
*
|
||||
* item 4.2 (FABRIC-0.md §25.5): the map is keyed by quota slot, not just
|
||||
* word_id -- word_id is assigned per-VM (vm->next_word_id), not globally
|
||||
* unique, so a single shared word_id -> cell_index map aliased different
|
||||
* VMs' words onto each other's Stadium cells and reservoirs the moment a
|
||||
* second VM (Hermes) held a quota. One system-wide init call still covers
|
||||
* every slot; no per-VM init call is needed.
|
||||
*/
|
||||
void stadium_words_init(void);
|
||||
|
||||
/*
|
||||
* stadium_word_dispatch - The per-dispatch entry point (FABRIC-0.md §17.7),
|
||||
* called once per DictEntry touched at each of vm_core.c's three
|
||||
* physics_execution_heat_increment() call sites -- deliberately mirroring
|
||||
* that function's existing call pattern 1:1, including the entry != canon
|
||||
* double-touch case, rather than inventing a different shape.
|
||||
*
|
||||
* If word_id is already resident: applies the redirected Loop #3 cooling
|
||||
* (fraction of the cell's own current heat, scaled by elapsed_ticks since
|
||||
* this word's own last touch -- STADIUM_WORD_COOL_RATE_Q48) crediting the
|
||||
* cooled amount back to vm_id's reservoir, then pulls
|
||||
* STADIUM_WORD_HEAT_QUANTUM from the reservoir into the cell -- clamped to
|
||||
* what the reservoir actually holds AND to a floor of Q48_ONE / 3 that
|
||||
* word-execution admission alone may never dip the reservoir below
|
||||
* (FABRIC-0.md §25.7, Captain Bob's ruling 2026-08-06: this pull fires on
|
||||
* EVERY dispatch, not just first admission, and without a floor exhausts a
|
||||
* VM's entire reservoir in ~32 dispatches, starving any application-level
|
||||
* economy -- e.g. item 4.2's Hermes -- sharing the same VM's reservoir).
|
||||
* Application-level pulls (stadium_reservoir_pull() called directly) are
|
||||
* not subject to this floor.
|
||||
*
|
||||
* If word_id is not resident (or the map's entry is stale -- self-healing
|
||||
* check against the cell's discriminator bit and identity, covers both a
|
||||
* prior eviction and a FORGET/redefine word_id reuse this function did not
|
||||
* itself clear): attempts Option B starter-grant admission -- pulls
|
||||
* STADIUM_WORD_HEAT_QUANTUM from the reservoir (same floor as above),
|
||||
* builds an unpinned COOL candidate, calls stadium_admit(). On refusal,
|
||||
* pushes the pulled quantum back (rollback, preserves conservation across
|
||||
* the failed attempt). On success, records the mapping and increments the
|
||||
* promotion counter.
|
||||
*
|
||||
* No-op if word_id == WORD_ID_INVALID, word_id >= DICTIONARY_SIZE, or vm_id
|
||||
* holds no Stadium quota.
|
||||
*
|
||||
* @param vm_id Owning VM -- vm->stadium_vm_id at every call site.
|
||||
* Scopes the word_id -> cell_index lookup to this
|
||||
* VM's own quota slot (item 4.2, FABRIC-0.md §25.5) so
|
||||
* two VMs' independently-numbered word_ids cannot
|
||||
* alias onto each other's cells/reservoirs.
|
||||
* @param word_id The dispatching DictEntry's stable word_id.
|
||||
* @param heartbeat_ticks Current vm->heartbeat.tick_count (virtual tick,
|
||||
* never wall-clock -- same convention as every other
|
||||
* decay computation in this tree).
|
||||
*/
|
||||
void stadium_word_dispatch(VMUuid vm_id, uint32_t word_id, uint64_t heartbeat_ticks);
|
||||
|
||||
/*
|
||||
* stadium_word_forget - Coherence hook for FORGET (word_id recycling).
|
||||
* vm_dictionary_untrack_entry() must call this BEFORE the word_id is pushed
|
||||
* onto vm->recycled_word_ids -- otherwise the next word assigned the same
|
||||
* recycled id would alias onto the forgotten word's still-resident cell and
|
||||
* its stale heat (same failure class as the 2026-08-02 block_words.c
|
||||
* aliasing bug). Evicts the cell if word_id is resident (crediting its heat
|
||||
* back to the reservoir via stadium_evict()'s own credit path) and clears
|
||||
* the map entry. No-op if word_id is not resident, out of range, or vm_id
|
||||
* holds no Stadium quota.
|
||||
*
|
||||
* @param vm_id Owning VM -- vm->stadium_vm_id (item 4.2, FABRIC-0.md §25.5:
|
||||
* scopes the lookup to this VM's own word_slots, same
|
||||
* reason stadium_word_dispatch() takes it).
|
||||
* @param word_id The DictEntry's word_id, about to be recycled.
|
||||
*/
|
||||
void stadium_word_forget(VMUuid vm_id, uint32_t word_id);
|
||||
|
||||
/*
|
||||
* stadium_words_resident_heat - Sum of heat held by vm_id's own
|
||||
* word-execution residents only (item 4.1's cells, tracked in this file's
|
||||
* own word_slots map) -- NOT messages/channels/other application residents,
|
||||
* which stadium_resident_sum() (stadium.h, item 4.2) mixes in alongside
|
||||
* everything else a VM owns. Exists so a VM's own application-level
|
||||
* conservation check (e.g. Hermes's HERMES-K, FABRIC-0.md §25.7, Captain
|
||||
* Bob's ruling 2026-08-06) can add this as an explicit term instead of
|
||||
* silently omitting word-execution heat it has no other way to see.
|
||||
*
|
||||
* Walks all DICTIONARY_SIZE word_slots for vm_id's quota slot; each
|
||||
* resident entry contributes its cell's current heat, verified live against
|
||||
* the discriminator bitmap (same self-healing pattern as
|
||||
* resolve_resident_cell() -- a stale map entry contributes 0, not garbage).
|
||||
*
|
||||
* @param vm_id The VM whose word-execution residents to sum.
|
||||
*/
|
||||
uint64_t stadium_words_resident_heat(VMUuid vm_id);
|
||||
|
||||
/*
|
||||
* stadium_words_stats - Promotion/eviction counters (same shape as the old
|
||||
* cache's HotwordsStats.promotions/.evictions, not that struct -- §25.5's
|
||||
* acceptance for item 4.1). Promotion = a successful starter-grant
|
||||
* admission. Eviction = this word's cell was reaped by another admission's
|
||||
* eviction fallback (stadium_admit()'s density comparison), detected
|
||||
* lazily via the self-healing stale check in stadium_word_dispatch(), or
|
||||
* explicitly via stadium_word_forget(). Scoped to vm_id's own quota slot
|
||||
* (item 4.2) -- counters are no longer system-wide.
|
||||
*/
|
||||
void stadium_words_stats(VMUuid vm_id, uint64_t *promotions, uint64_t *evictions);
|
||||
|
||||
/*
|
||||
* stadium_words_print_boot_diagnostics - Console output satisfying item
|
||||
* 4.1's "observable via a diagnostic word or boot console output"
|
||||
* acceptance line. Prints promotions/evictions, then
|
||||
* Σ(resident heat) + reservoir against Q48_ONE as a conservation check --
|
||||
* not required by the acceptance text, but the mechanism proves nothing if
|
||||
* this silently doesn't hold. The heat sum is scoped to vm_id's own quota
|
||||
* (stadium_resident_sum(), item 4.2, FABRIC-0.md §25.5) so two VMs' checks
|
||||
* close independently instead of mixing both VMs' resident heat together.
|
||||
*
|
||||
* @param vm_id The VM whose reservoir to read (vm_uuid_hera() today).
|
||||
*/
|
||||
void stadium_words_print_boot_diagnostics(VMUuid vm_id);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_VM_STADIUM_WORDS_H */
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* switch.h - Cooperative VM context switch (FABRIC-3.md §XXVIII, Stage 2,
|
||||
* 2026-09-13).
|
||||
*
|
||||
* sk_vm_context_switch() suspends `from`'s execution exactly where it is
|
||||
* (mid-C-call-stack, on from's own native stack -- Stage 1) and resumes
|
||||
* `to` -- either for the first time ever (a synthesized initial frame,
|
||||
* entering sk_vm_switch_entry()) or exactly where `to` was itself last
|
||||
* switched out. Returns to the caller only once something later switches
|
||||
* back to `from` -- from from's own point of view, this call simply
|
||||
* takes a while to return, like any blocking call.
|
||||
*
|
||||
* Cooperative only: nothing here is interrupt-driven yet (that's Stage
|
||||
* 3), so this is safe to call only from ordinary FORTH word dispatch,
|
||||
* never from ISR context.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VM_SWITCH_H
|
||||
#define STARKERNEL_VM_SWITCH_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
struct VM;
|
||||
|
||||
int sk_vm_context_switch(struct VM *from, struct VM *to);
|
||||
|
||||
/* FABRIC-3.md §XXVIII Stage 3 follow-on (2026-09-14): which VM is
|
||||
* currently physically running, tracked by the switch mechanism itself --
|
||||
* see switch.c's own doc comment on g_switch_current_vm for why this
|
||||
* exists instead of reusing vm_log_attributed_vm(). sk_vm_switch_set_
|
||||
* current() seeds the initial value (whoever is running before any
|
||||
* switch has ever happened) -- call once, at Stage 3 registration time. */
|
||||
struct VM *sk_vm_switch_current_vm(void);
|
||||
void sk_vm_switch_set_current(struct VM *vm);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_VM_SWITCH_H */
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* vm_identity.h - Per-VM owner identity + ACL capabilities (FABRIC-2.md
|
||||
* §F.2/§F.16, decided 2026-08-27/28)
|
||||
*
|
||||
* Holds only what a VM needs to prove *who owns it* and *what that owner
|
||||
* is allowed to do* -- never a private key. A regular VM's lock never
|
||||
* signs anything itself, so no seed/private material belongs here at all
|
||||
* (unlike Zuse's own zuse_cert_seed/zuse_cert_pubkey pair, which does need
|
||||
* one because she actively signs). Deliberately its own header rather
|
||||
* than inline fields on struct VM, mirroring VMUuid's own precedent
|
||||
* (vm_uuid.h) -- standing instruction: give real-shaped data its own
|
||||
* header and integrate as a field, don't grow struct VM ad hoc.
|
||||
*
|
||||
* acl_caps is a capability bitmask, not an ordered privilege tier
|
||||
* (decided 2026-08-28) -- independent bits, not a nested hierarchy. Zuse
|
||||
* is not a structurally special VM: her identity just has every bit set.
|
||||
* No bit values are assigned yet -- deliberate slack, per this project's
|
||||
* "flexibility until we understand the recipe" precedent (see
|
||||
* blk_meta_t's own acl_reserved bytes, FABRIC-2.md §F.4) -- real bits get
|
||||
* names only once the operation they gate actually gets built (BINDSTEP,
|
||||
* MINT, ...), not speculatively here.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VM_IDENTITY_H
|
||||
#define STARKERNEL_VM_IDENTITY_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct {
|
||||
uint8_t owner_pubkey[32]; /**< Ed25519 public key of this VM's owning
|
||||
* identity. Meaningless unless installed
|
||||
* is set. */
|
||||
uint8_t installed; /**< 0 = no identity installed yet (e.g.
|
||||
* Hera/Hermes/Artemis today, before
|
||||
* D.5's per-VM-identity work lands) --
|
||||
* BINDSTEP-style checks must treat this
|
||||
* as "no lock, allow freely," matching
|
||||
* §F.9 decision 2. 1 = owner_pubkey/
|
||||
* acl_caps are real. */
|
||||
uint32_t acl_caps; /**< Capability bitmask. All-zero until a
|
||||
* real caller defines and checks a bit;
|
||||
* VM_IDENTITY_CAP_ALL for Zuse's own
|
||||
* identity ("her ACL just grants
|
||||
* everything," not a special VM type). */
|
||||
} VMIdentity;
|
||||
|
||||
/** Every capability bit set -- Zuse's own identity uses this, not a
|
||||
* distinct "is this Zuse" flag anywhere else in the system. */
|
||||
#define VM_IDENTITY_CAP_ALL 0xFFFFFFFFu
|
||||
|
||||
/**
|
||||
* vm_identity_has_cap - Check whether an installed identity holds a
|
||||
* capability. Returns 0 (denied) if identity isn't installed at all --
|
||||
* callers that mean "no lock, allow freely" (§F.9 decision 2) must check
|
||||
* installed themselves first, not call this and treat 0 as a denial in
|
||||
* that case.
|
||||
*
|
||||
* @param id Identity to check.
|
||||
* @param cap A single capability bit (or bits) to test for.
|
||||
* @return Non-zero if id is installed and every bit in cap is set.
|
||||
*/
|
||||
int vm_identity_has_cap(const VMIdentity *id, uint32_t cap);
|
||||
|
||||
/**
|
||||
* vm_identity_from_cert - CERTVERIFY (FABRIC-2.md §F.7/§F.17): verify a
|
||||
* DER-encoded, Zuse-signed X.509 cert and populate a VMIdentity from it.
|
||||
*
|
||||
* Three checks, all must pass: the cert's own signature verifies against
|
||||
* issuer_pubkey (Zuse's own on-device key -- a separate trust root from
|
||||
* the capsule-PKI chain, no chain walk needed); the cert's serialNumber
|
||||
* equals drive_uuid byte-for-byte (binds this cert to one physical drive,
|
||||
* §F.7 decision 2 -- a copied cert on different media will not verify);
|
||||
* the subject's Ed25519 public key extracts cleanly.
|
||||
*
|
||||
* acl_caps is *not* read from the cert -- nothing in the decided cert
|
||||
* fields encodes capabilities (§F.16). It's supplied by the caller, whose
|
||||
* job it is to decide what this verified identity is allowed to do (e.g.
|
||||
* comparing the extracted pubkey against Zuse's own system-resident
|
||||
* pubkey to decide VM_IDENTITY_CAP_ALL vs. a lesser default) -- that
|
||||
* policy decision doesn't belong inside a pure verification function.
|
||||
*
|
||||
* @param out Populated on success; left untouched on failure.
|
||||
* @param der DER-encoded certificate bytes.
|
||||
* @param der_len Their length.
|
||||
* @param issuer_pubkey Zuse's own Ed25519 public key.
|
||||
* @param drive_uuid This physical drive's own 16-byte drive_uuid
|
||||
* (homeblocks_sig_t), compared against the cert's
|
||||
* serialNumber.
|
||||
* @param acl_caps Capability bitmask to install, caller-decided.
|
||||
* @return 0 on success, -1 if any check fails (malformed DER, wrong
|
||||
* signature algorithm, signature doesn't verify, serial mismatch,
|
||||
* or the extracted key isn't a valid Ed25519 point).
|
||||
*/
|
||||
int vm_identity_from_cert(VMIdentity *out, const uint8_t *der, size_t der_len,
|
||||
const uint8_t issuer_pubkey[32],
|
||||
const uint8_t drive_uuid[16], uint32_t acl_caps);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_VM_IDENTITY_H */
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* vm_uuid.h - 128-bit VM identifiers (FABRIC-0.md punch list item 3.8)
|
||||
*
|
||||
* Replaces capsule_birth.c's monotonic uint32_t vm_id with a wider,
|
||||
* RFC-4122-shaped identifier. NOT real randomness: this kernel has no RNG
|
||||
* source at all (checked directly against QEMU 10.2.1's actual CPU feature
|
||||
* set -- amd64 RDRAND and riscv64 Zkr are both available, aarch64 has
|
||||
* neither RNDR nor any RNG property on any CPU model including "max"), and
|
||||
* Captain Bob ruled a uniform fallback across all three ISAs rather than a
|
||||
* per-architecture split. Values are generated by a deterministic PRNG
|
||||
* (splitmix64) seeded from the Mama capsule's content hash -- the same
|
||||
* capsule booted twice produces the same ID sequence, preserving the
|
||||
* run-to-run reproducibility this project has relied on everywhere else
|
||||
* (the dict_hash regression check after every prior item this session).
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VM_UUID_H
|
||||
#define STARKERNEL_VM_UUID_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef struct {
|
||||
uint64_t hi;
|
||||
uint64_t lo;
|
||||
} VMUuid;
|
||||
|
||||
/*
|
||||
* vm_uuid_hera - The fixed, reserved identifier for Hera (patron zero,
|
||||
* item 3.6; VM 0 in the pre-item-3.8 scheme). Not drawn from the pool --
|
||||
* capsule_birth.c's KILL logic ("Hera cannot be killed") and the fleet
|
||||
* heat-fanout parent-chain sentinel (capsule_run.h's `parent_vm_id`
|
||||
* comment: "self-referential, parent_vm_id == vm_id == 0") both depend on
|
||||
* Hera's id being a fixed, cheaply-comparable value, exactly as 0 was
|
||||
* before this item. All-zero.
|
||||
*/
|
||||
VMUuid vm_uuid_hera(void);
|
||||
int vm_uuid_is_hera(VMUuid id);
|
||||
|
||||
/*
|
||||
* vm_uuid_none - Sentinel meaning "no id" / "slot not in use." All-ones --
|
||||
* NOT all-zero, because all-zero is Hera's reserved value (item 3.8 caught
|
||||
* this collision before writing any code that could have repeated the
|
||||
* STADIUM_CONTAINS_NONE mistake at a new site).
|
||||
*/
|
||||
VMUuid vm_uuid_none(void);
|
||||
int vm_uuid_is_none(VMUuid id);
|
||||
|
||||
int vm_uuid_equal(VMUuid a, VMUuid b);
|
||||
|
||||
/*
|
||||
* vm_uuid_pool_init - Seed the generator. Call once, after the Mama
|
||||
* capsule's content hash is known (right after capsule_birth_mama()
|
||||
* succeeds) and before the first non-Hera VM birth. Idempotent to call
|
||||
* again (re-seeds and refills), though nothing does today.
|
||||
*/
|
||||
void vm_uuid_pool_init(uint64_t seed);
|
||||
|
||||
/*
|
||||
* vm_uuid_next - Pop the next id from the pre-filled FIFO pool, refilling
|
||||
* with a fresh batch (continuing the same deterministic splitmix64 stream)
|
||||
* when empty. If called before vm_uuid_pool_init() (not the intended path),
|
||||
* self-seeds from a fixed default rather than returning garbage -- flagged
|
||||
* as a safety net, not normal use.
|
||||
*/
|
||||
VMUuid vm_uuid_next(void);
|
||||
|
||||
/*
|
||||
* vm_uuid_format - Writes the RFC-4122-shaped string
|
||||
* "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" (36 chars + NUL) into buf.
|
||||
* @param buf Caller-provided buffer, at least 37 bytes.
|
||||
*/
|
||||
void vm_uuid_format(VMUuid id, char *buf);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_VM_UUID_H */
|
||||
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* vmm.h - Virtual Memory Manager interface (x86_64 4-level paging)
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VMM_H
|
||||
#define STARKERNEL_VMM_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define VMM_PAGE_SIZE 4096ull
|
||||
|
||||
/* Page table entry flags */
|
||||
#define VMM_FLAG_PRESENT (1ull << 0)
|
||||
#define VMM_FLAG_WRITABLE (1ull << 1)
|
||||
#define VMM_FLAG_USER (1ull << 2)
|
||||
#define VMM_FLAG_CACHE_DISABLE (1ull << 4) /* maps to PTE_PCD — required for MMIO */
|
||||
#define VMM_FLAG_NX (1ull << 63)
|
||||
|
||||
#include "uefi.h"
|
||||
|
||||
typedef struct {
|
||||
int present;
|
||||
int writable;
|
||||
int executable;
|
||||
} vmm_page_info_t;
|
||||
|
||||
int vmm_init(BootInfo *boot_info);
|
||||
int vmm_map_page(uint64_t vaddr, uint64_t paddr, uint64_t flags);
|
||||
int vmm_unmap_page(uint64_t vaddr);
|
||||
uint64_t vmm_get_paddr(uint64_t vaddr);
|
||||
int vmm_map_range(uint64_t vaddr, uint64_t paddr, uint64_t size, uint64_t flags);
|
||||
int vmm_query_page(uint64_t vaddr, vmm_page_info_t *info);
|
||||
|
||||
#endif /* STARKERNEL_VMM_H */
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0.
|
||||
*/
|
||||
|
||||
/**
|
||||
* vt100.h — Full-color ANSI / VT100 terminal state machine
|
||||
*
|
||||
* Sits on top of the framebuffer driver. Call vt100_init() once the
|
||||
* framebuffer is ready, then route all character output through vt100_putc().
|
||||
*
|
||||
* Supported escape sequences
|
||||
* ──────────────────────────
|
||||
* Cursor movement ESC[H ESC[n;mH ESC[nA ESC[nB ESC[nC ESC[nD
|
||||
* Cursor save/restore ESC[s ESC[u (also ESC7 / ESC8)
|
||||
* Erase ESC[2J ESC[K ESC[0K ESC[1K ESC[2K
|
||||
* SGR attributes ESC[…m (see below)
|
||||
*
|
||||
* SGR codes
|
||||
* ─────────
|
||||
* 0 reset all attributes
|
||||
* 1 bold (doubles fg brightness)
|
||||
* 4 underline
|
||||
* 7 reverse video
|
||||
* 22 normal intensity
|
||||
* 24 underline off
|
||||
* 27 reverse off
|
||||
* 30–37 set fg to standard ANSI color 0–7
|
||||
* 38;2;r;g;b set fg to 24-bit RGB truecolor
|
||||
* 38;5;n set fg to 256-color index
|
||||
* 39 reset fg to default
|
||||
* 40–47 set bg to standard ANSI color 0–7
|
||||
* 48;2;r;g;b set bg to 24-bit RGB truecolor
|
||||
* 48;5;n set bg to 256-color index
|
||||
* 49 reset bg to default
|
||||
* 90–97 set fg to bright ANSI color 8–15
|
||||
* 100–107 set bg to bright ANSI color 8–15
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_VT100_H
|
||||
#define STARKERNEL_VT100_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Default terminal colors */
|
||||
#define VT100_DEFAULT_FG FB_RGB(0xAA, 0xAA, 0xAA) /* light gray */
|
||||
#define VT100_DEFAULT_BG FB_RGB(0x00, 0x00, 0x00) /* black */
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Lifecycle
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Initialize the VT100 terminal.
|
||||
* Must be called after fb_init(). Clears the screen and homes the cursor.
|
||||
*/
|
||||
void vt100_init(void);
|
||||
|
||||
/**
|
||||
* FABRIC-0.md item 4.4j: switch the glyph-draw backend from font_8x16.c to
|
||||
* TTF-TEXT's rasterizer for everything drawn from this call onward --
|
||||
* boot/POST output before this call stays font_8x16.c, unaffected.
|
||||
* Lazily loads the font capsule and its raster cache on first call
|
||||
* (no-op on later calls). Recomputes cols/rows for the new cell size and
|
||||
* clears the screen, since the two glyph backends use different cell
|
||||
* dimensions. TTF point size/cell dimensions decided final by FABRIC-0.md
|
||||
* item 4.4m (20px text, 96px REPL strip). No-op if the font capsule
|
||||
* fails to load (stays on font_8x16.c; logged, not fatal).
|
||||
*/
|
||||
void vt100_enable_ttf(void);
|
||||
|
||||
/**
|
||||
* FABRIC-0.md item 4.4q: move the REPL scrollback view back/forward by
|
||||
* @p n lines and redraw. Offset 0 (the default, and where every call
|
||||
* eventually returns to) is the live view -- the same content already on
|
||||
* screen. Clamped at both ends: back cannot pass the oldest stored line,
|
||||
* forward cannot pass the live view. No-op (including no redraw) if the
|
||||
* scrollback buffers failed to allocate at vt100_enable_ttf() time, or
|
||||
* before TTF mode is active at all.
|
||||
*
|
||||
* Known limitation: redraw uses the terminal's current default fg/bg,
|
||||
* not each line's original SGR color state at the time it was printed
|
||||
* (colors aren't stored per-cell) -- text content is recovered exactly,
|
||||
* color is not.
|
||||
*/
|
||||
void vt100_scroll_back(uint32_t n);
|
||||
void vt100_scroll_fwd(uint32_t n);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Character output
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/** Feed one byte into the terminal (handles escape sequences transparently). */
|
||||
void vt100_putc(char c);
|
||||
|
||||
/** Feed a null-terminated string. */
|
||||
void vt100_puts(const char *s);
|
||||
|
||||
/**
|
||||
* FABRIC-0.md item 4.4y-revised: toggle the full-screen vt100 terminal
|
||||
* between visible (normal operation) and hidden (graphics mode -- the
|
||||
* terminal stops drawing, letting direct framebuffer/TTF-TEXT calls show
|
||||
* through undisturbed). Toggling back to visible does a full redraw of
|
||||
* the terminal's current on-screen content, restoring it exactly as it
|
||||
* was. No-op before TTF mode is active.
|
||||
*/
|
||||
void vt100_toggle_graphics(void);
|
||||
|
||||
/**
|
||||
* Draw a solid block cursor at the terminal's current position. Static,
|
||||
* not blinking. Idempotent -- safe to call after every keystroke, since
|
||||
* whatever gets typed next naturally overwrites it. No-op before TTF mode
|
||||
* is active or while in graphics mode (vt100_toggle_graphics()).
|
||||
*/
|
||||
void vt100_draw_cursor(void);
|
||||
|
||||
/**
|
||||
* Erases whatever vt100_draw_cursor() last drew, restoring the cell to
|
||||
* plain background. Call before moving away from a cursor-drawn cell
|
||||
* without also drawing a character there (e.g. Enter/newline). No-op
|
||||
* before TTF mode is active or while in graphics mode.
|
||||
*/
|
||||
void vt100_erase_cursor(void);
|
||||
|
||||
/* -----------------------------------------------------------------------
|
||||
* Queries
|
||||
* --------------------------------------------------------------------- */
|
||||
|
||||
/** Terminal width in character columns. */
|
||||
uint32_t vt100_cols(void);
|
||||
|
||||
/** Terminal height in character rows. */
|
||||
uint32_t vt100_rows(void);
|
||||
|
||||
#endif /* STARKERNEL_VT100_H */
|
||||
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* x509_ed25519.h -- minimal, targeted DER walkers for Ed25519-signed X.509
|
||||
* certificates (RFC 8410). Deliberately NOT a general ASN.1/X.509 parser
|
||||
* (Milestone 6 decision, FABRIC-1.md): each function walks exactly as far
|
||||
* into the DER structure as its own job needs, nothing more.
|
||||
*
|
||||
* x509_extract_ed25519_pubkey() only ever reads SubjectPublicKeyInfo --
|
||||
* no signature verification, no chain validation, no extension parsing.
|
||||
* That's the capsule-PKI use case (Milestone 6): the embedded snakeoil
|
||||
* intermediate cert is trusted because it's baked into the trusted build,
|
||||
* never re-verified against the offline root CA at boot.
|
||||
*
|
||||
* x509_verify_signature()/x509_extract_serial() (added 2026-08-28,
|
||||
* FABRIC-2.md §F.7/§F.17) are for CERTVERIFY -- a regular user's cert,
|
||||
* which unlike the capsule-PKI chain is signed by Zuse's own on-device
|
||||
* key and genuinely needs its signature checked at attach time, not just
|
||||
* trusted by embedding. Two separate trust roots, two separate reasons
|
||||
* to exist in the same small file (shared DER-walking internals only).
|
||||
*
|
||||
* Freestanding C99, no libc beyond memcmp/memcpy (already provided by
|
||||
* src/starkernel/vm/host/shim.c in the kernel build).
|
||||
*/
|
||||
#ifndef STARKERNEL_X509_ED25519_H
|
||||
#define STARKERNEL_X509_ED25519_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* Returns 0 on success (pubkey_out[32] filled), -1 on any malformed
|
||||
* encoding, unexpected structure, or non-Ed25519 algorithm. Never
|
||||
* faults on malformed input -- every DER length/tag is bounds-checked
|
||||
* against der_len before use. */
|
||||
int x509_extract_ed25519_pubkey(const uint8_t *der, size_t der_len,
|
||||
uint8_t pubkey_out[32]);
|
||||
|
||||
/* Verify a DER-encoded certificate's own outer Ed25519 signature (the
|
||||
* signatureValue field) was produced by issuer_pubkey signing the raw,
|
||||
* exactly-as-encoded tbsCertificate bytes (DER signs the octets, not a
|
||||
* re-derived hash of "the fields" -- the TLV framing is part of what's
|
||||
* signed). Rejects a non-Ed25519 signatureAlgorithm rather than guessing.
|
||||
*
|
||||
* Returns 0 if the signature verifies, -1 on any malformed encoding,
|
||||
* unexpected structure, non-Ed25519 signature algorithm, or a signature
|
||||
* that does not verify. Never faults on malformed input. */
|
||||
int x509_verify_signature(const uint8_t *der, size_t der_len,
|
||||
const uint8_t issuer_pubkey[32]);
|
||||
|
||||
/* Extract the raw serialNumber INTEGER content bytes from a DER-encoded
|
||||
* certificate's tbsCertificate. A single leading 0x00 pad byte (DER adds
|
||||
* one when the value's high bit would otherwise read as a negative
|
||||
* INTEGER) is stripped before copying, so a 16-byte drive_uuid compares
|
||||
* byte-for-byte regardless of whether DER happened to pad it.
|
||||
*
|
||||
* @param serial_out Caller-provided buffer.
|
||||
* @param serial_out_cap Its size in bytes; returns -1 if the real
|
||||
* (pad-stripped) serial is larger than this.
|
||||
* @param serial_len_out Set to the real length actually copied.
|
||||
* @return 0 on success, -1 on any malformed encoding or unexpected
|
||||
* structure. Never faults on malformed input. */
|
||||
int x509_extract_serial(const uint8_t *der, size_t der_len,
|
||||
uint8_t *serial_out, size_t serial_out_cap,
|
||||
size_t *serial_len_out);
|
||||
|
||||
/*
|
||||
* x509_build_user_cert (added 2026-08-28, FABRIC-2.md §F.8/§F.19, MINT):
|
||||
* the encode-side counterpart to x509_verify_signature()/x509_extract_*
|
||||
* above. Builds a minimal DER-encoded X.509 certificate exercising
|
||||
* exactly the fields those functions read -- serialNumber, an Ed25519
|
||||
* SubjectPublicKeyInfo, an Ed25519-signed signatureValue -- and nothing
|
||||
* else. issuer/validity/subject are each encoded as an empty SEQUENCE
|
||||
* (valid, zero-length DER TLVs the decode side only ever skips, never
|
||||
* reads the content of); version is omitted entirely (implicit v1,
|
||||
* matching the decode side's own optional-version handling). This is
|
||||
* deliberately not a general-purpose X.509 builder -- same scope
|
||||
* discipline as the decode side's own doc comment above.
|
||||
*
|
||||
* @param out Caller-provided output buffer.
|
||||
* @param out_cap Its size in bytes.
|
||||
* @param subject_pubkey The new identity's own Ed25519 public key --
|
||||
* becomes the cert's SubjectPublicKeyInfo.
|
||||
* @param serial 16 bytes -- becomes the cert's serialNumber
|
||||
* (CERTVERIFY/BINDSTEP compare this against
|
||||
* homeblocks_sig_t.drive_uuid).
|
||||
* @param issuer_seed The signer's own Ed25519 seed (Zuse's
|
||||
* vm->zuse_cert_seed) -- signs the resulting
|
||||
* tbsCertificate bytes.
|
||||
* @return Number of bytes written to out, or 0 if out_cap was too small.
|
||||
*/
|
||||
size_t x509_build_user_cert(uint8_t *out, size_t out_cap,
|
||||
const uint8_t subject_pubkey[32],
|
||||
const uint8_t serial[16],
|
||||
const uint8_t issuer_seed[32]);
|
||||
|
||||
#endif /* STARKERNEL_X509_ED25519_H */
|
||||
@@ -0,0 +1,517 @@
|
||||
/*
|
||||
* xhci.h — xHCI (Extensible Host Controller Interface, USB 3.x) register
|
||||
* layout and shared constants for StarKernel's USB host controller driver.
|
||||
*
|
||||
* Register layout from the xHCI 1.2 specification. Four MMIO regions, each
|
||||
* reached via an offset from PCI BAR0:
|
||||
* Capability Registers — at BAR0 + 0, fixed layout, CAPLENGTH gives the
|
||||
* offset to Operational Registers
|
||||
* Operational Registers — at BAR0 + CAPLENGTH
|
||||
* Runtime Registers — at BAR0 + RTSOFF (read from Capability Registers)
|
||||
* Doorbell Array — at BAR0 + DBOFF (read from Capability Registers)
|
||||
*
|
||||
* Struct fields are `volatile`, naturally aligned, NOT __attribute__((packed))
|
||||
* — matching virtio_blk.c's precedent and its documented riscv64 lesson:
|
||||
* packed structs force byte-wise loads/stores on strict-alignment targets,
|
||||
* and QEMU's MMIO handlers for exact-width registers can misbehave under
|
||||
* byte-wise access. The xHCI spec's register layout is naturally aligned at
|
||||
* every offset, so this translates directly without padding tricks.
|
||||
*
|
||||
* QEMU's `qemu-xhci` device identifies as PCI vendor 0x1B36 (Red Hat, Inc.),
|
||||
* device 0x000D — confirmed live via QMP `query-pci` against a real running
|
||||
* instance (not assumed from memory), 2026-08-22.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_XHCI_H
|
||||
#define STARKERNEL_XHCI_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* PCI identification
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define XHCI_PCI_VENDOR_ID 0x1B36u /* Red Hat, Inc. (QEMU qemu-xhci) */
|
||||
#define XHCI_PCI_DEVICE_ID 0x000Du
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Capability Registers (BAR0 + 0)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
volatile uint8_t cap_length; /* offset to Operational Registers */
|
||||
volatile uint8_t reserved0;
|
||||
volatile uint16_t hci_version; /* BCD xHCI spec version */
|
||||
volatile uint32_t hcs_params1; /* MaxSlots[7:0], MaxIntrs[18:8], MaxPorts[31:24] */
|
||||
volatile uint32_t hcs_params2; /* IST, ERST Max, scratchpad buffer counts */
|
||||
volatile uint32_t hcs_params3; /* U1/U2 device exit latencies */
|
||||
volatile uint32_t hcc_params1; /* AC64, BNC, CSZ, xECP pointer[31:16], etc. */
|
||||
volatile uint32_t db_off; /* Doorbell Array offset (low 2 bits reserved) */
|
||||
volatile uint32_t rts_off; /* Runtime Register Space offset (low 5 bits reserved) */
|
||||
volatile uint32_t hcc_params2;
|
||||
} xhci_cap_regs_t;
|
||||
|
||||
#define XHCI_HCSPARAMS1_MAX_SLOTS(v) ((uint32_t)(v) & 0xFFu)
|
||||
#define XHCI_HCSPARAMS1_MAX_INTRS(v) (((uint32_t)(v) >> 8) & 0x7FFu)
|
||||
#define XHCI_HCSPARAMS1_MAX_PORTS(v) (((uint32_t)(v) >> 24) & 0xFFu)
|
||||
|
||||
/* HCSPARAMS2: Max Scratchpad Buffers is a 10-bit field split across two
|
||||
* non-adjacent locations (xHCI 1.2 spec table 5-13) — Hi bits[25:21],
|
||||
* Lo bits[31:27]. Zero means the controller needs no scratchpad buffers
|
||||
* (common for simple emulated controllers, but verify live, not assumed). */
|
||||
#define XHCI_HCSPARAMS2_MAX_SCRATCHPAD_BUFS(v) \
|
||||
((((uint32_t)(v) >> 21) & 0x1Fu) << 5 | (((uint32_t)(v) >> 27) & 0x1Fu))
|
||||
|
||||
/* HCCPARAMS1.CSZ (bit 2): 0 = 32-byte Slot/Endpoint/Input Contexts,
|
||||
* 1 = 64-byte. Every context field offset shifts with this bit -- must be
|
||||
* read live, never assumed, before laying out any context structure. */
|
||||
#define XHCI_HCCPARAMS1_CSZ(v) (((uint32_t)(v) >> 2) & 0x1u)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Operational Registers (BAR0 + cap_length)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t usb_cmd; /* Run/Stop, HC Reset, Interrupter Enable, ... */
|
||||
volatile uint32_t usb_sts; /* HCHalted, HSE, EINT, PCD, CNR, HCE */
|
||||
volatile uint32_t page_size; /* bit N set => 2^(N+12)-byte pages supported */
|
||||
volatile uint32_t reserved0[2];
|
||||
volatile uint32_t dn_ctrl; /* Device Notification Control */
|
||||
volatile uint64_t crcr; /* Command Ring Control Register */
|
||||
volatile uint32_t reserved1[4];
|
||||
volatile uint64_t dcbaap; /* Device Context Base Address Array Pointer */
|
||||
volatile uint32_t config; /* MaxSlotsEn[7:0] */
|
||||
/* Port Register Sets follow at a fixed offset (0x400 from Operational
|
||||
* base), not contiguous with the fields above — accessed via
|
||||
* xhci_port_regs() below, not as a struct member. */
|
||||
} xhci_op_regs_t;
|
||||
|
||||
/* USBCMD bits */
|
||||
#define XHCI_USBCMD_RUN (1u << 0) /* Run/Stop: 1 = run */
|
||||
#define XHCI_USBCMD_HCRST (1u << 1) /* HC Reset */
|
||||
#define XHCI_USBCMD_INTE (1u << 2) /* Interrupter Enable */
|
||||
#define XHCI_USBCMD_HSEE (1u << 3) /* Host System Error Enable */
|
||||
|
||||
/* USBSTS bits */
|
||||
#define XHCI_USBSTS_HCH (1u << 0) /* HC Halted */
|
||||
#define XHCI_USBSTS_HSE (1u << 2) /* Host System Error */
|
||||
#define XHCI_USBSTS_EINT (1u << 3) /* Event Interrupt */
|
||||
#define XHCI_USBSTS_PCD (1u << 4) /* Port Change Detect */
|
||||
#define XHCI_USBSTS_CNR (1u << 11) /* Controller Not Ready */
|
||||
#define XHCI_USBSTS_HCE (1u << 12) /* Host Controller Error */
|
||||
|
||||
/* CRCR bits (low bits of the 64-bit register; pointer occupies bits[63:6]) */
|
||||
#define XHCI_CRCR_RCS (1ull << 0) /* Ring Cycle State */
|
||||
#define XHCI_CRCR_CS (1ull << 1) /* Command Stop */
|
||||
#define XHCI_CRCR_CA (1ull << 2) /* Command Abort */
|
||||
#define XHCI_CRCR_CRR (1ull << 3) /* Command Ring Running (read-only) */
|
||||
#define XHCI_CRCR_PTR_MASK (~0x3Full) /* pointer must be 64-byte aligned */
|
||||
|
||||
/* CONFIG */
|
||||
#define XHCI_CONFIG_MAX_SLOTS_EN(n) ((uint32_t)(n) & 0xFFu)
|
||||
|
||||
/* Port Register Set — array at Operational base + 0x400, 0x10 bytes each,
|
||||
* indexed 0..(MaxPorts-1) for ports numbered 1..MaxPorts. */
|
||||
typedef struct {
|
||||
volatile uint32_t portsc; /* Port Status and Control */
|
||||
volatile uint32_t portpmsc; /* Port Power Management Status and Control */
|
||||
volatile uint32_t portli; /* Port Link Info */
|
||||
volatile uint32_t porthlpmc; /* Port Hardware LPM Control */
|
||||
} xhci_port_regs_t;
|
||||
|
||||
#define XHCI_PORT_REGS_OFFSET 0x400u
|
||||
|
||||
/* PORTSC bits (subset needed for hotplug + reset) */
|
||||
#define XHCI_PORTSC_CCS (1u << 0) /* Current Connect Status */
|
||||
#define XHCI_PORTSC_PED (1u << 1) /* Port Enabled/Disabled */
|
||||
#define XHCI_PORTSC_PR (1u << 4) /* Port Reset */
|
||||
#define XHCI_PORTSC_PLS_MASK (0xFu << 5) /* Port Link State */
|
||||
#define XHCI_PORTSC_PP (1u << 9) /* Port Power */
|
||||
#define XHCI_PORTSC_SPEED_MASK (0xFu << 10)
|
||||
#define XHCI_PORTSC_SPEED(v) (((uint32_t)(v) >> 10) & 0xFu) /* xHCI 1.2 spec table 7-13 speed IDs */
|
||||
#define XHCI_PORTSC_CSC (1u << 17) /* Connect Status Change */
|
||||
#define XHCI_PORTSC_PEC (1u << 18) /* Port Enabled/Disabled Change */
|
||||
#define XHCI_PORTSC_PRC (1u << 21) /* Port Reset Change */
|
||||
/* Writing 1 to a _C (change) bit clears it (RW1CS) — writing 0 has no effect.
|
||||
* PORTSC also has RW1CS bits interleaved with RW bits; always read-modify-
|
||||
* write with the change bits masked to 0 unless intentionally clearing one,
|
||||
* to avoid accidentally acknowledging an event by a stray read-modify-write. */
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Runtime Registers (BAR0 + rts_off)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t iman; /* Interrupt Management: bit0=IP, bit1=IE */
|
||||
volatile uint32_t imod; /* Interrupt Moderation */
|
||||
volatile uint32_t erstsz; /* Event Ring Segment Table Size */
|
||||
volatile uint32_t reserved0;
|
||||
volatile uint64_t erstba; /* Event Ring Segment Table Base Address */
|
||||
volatile uint64_t erdp; /* Event Ring Dequeue Pointer; bit3=EHB */
|
||||
} xhci_intr_regs_t;
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t mf_index; /* Microframe Index */
|
||||
volatile uint32_t reserved0[7];
|
||||
/* Interrupter Register Sets follow, one xhci_intr_regs_t per interrupter,
|
||||
* starting immediately after this 0x20-byte header. Interrupter 0 is
|
||||
* accessed via xhci_intr_regs_t at (runtime_base + 0x20). */
|
||||
} xhci_runtime_regs_t;
|
||||
|
||||
#define XHCI_IMAN_IP (1u << 0) /* Interrupt Pending */
|
||||
#define XHCI_IMAN_IE (1u << 1) /* Interrupt Enable */
|
||||
#define XHCI_ERDP_EHB (1ull << 3) /* Event Handler Busy */
|
||||
#define XHCI_ERDP_PTR_MASK (~0xFull) /* pointer occupies bits[63:4] */
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Doorbell Array (BAR0 + db_off) — array of uint32_t, one per device slot
|
||||
* plus doorbell 0 for the Command Ring. Write-only.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef volatile uint32_t xhci_doorbell_t;
|
||||
|
||||
#define XHCI_DB_TARGET(ep) ((uint32_t)(ep) & 0xFFu) /* 0 = command ring */
|
||||
#define XHCI_DB_STREAM_ID(sid) (((uint32_t)(sid) & 0xFFFFu) << 16)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* TRB (Transfer Request Block) — 16 bytes, the unit of both Command Ring
|
||||
* and Event Ring entries (and Transfer Rings, used later for BOT I/O).
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
volatile uint64_t parameter;
|
||||
volatile uint32_t status;
|
||||
volatile uint32_t control;
|
||||
} xhci_trb_t;
|
||||
|
||||
#define XHCI_TRB_CONTROL_CYCLE (1u << 0) /* Cycle bit */
|
||||
#define XHCI_TRB_CONTROL_TYPE_SHIFT 10
|
||||
#define XHCI_TRB_CONTROL_TYPE_MASK (0x3Fu << XHCI_TRB_CONTROL_TYPE_SHIFT)
|
||||
#define XHCI_TRB_TYPE(ctrl) (((ctrl) & XHCI_TRB_CONTROL_TYPE_MASK) >> XHCI_TRB_CONTROL_TYPE_SHIFT)
|
||||
|
||||
/* TRB types used by this driver (subset — xHCI defines many more) */
|
||||
#define XHCI_TRB_TYPE_NORMAL 1 /* Transfer Ring, bulk/interrupt/isoch -- not EP0 */
|
||||
#define XHCI_TRB_TYPE_LINK 6 /* ring-wraparound marker, Command/Transfer Rings only */
|
||||
#define XHCI_TRB_TYPE_ENABLE_SLOT_CMD 9
|
||||
#define XHCI_TRB_TYPE_DISABLE_SLOT_CMD 10
|
||||
#define XHCI_TRB_TYPE_ADDRESS_DEVICE_CMD 11
|
||||
#define XHCI_TRB_TYPE_CONFIGURE_ENDPOINT_CMD 12
|
||||
#define XHCI_TRB_TYPE_RESET_ENDPOINT_CMD 14
|
||||
#define XHCI_TRB_TYPE_SET_TR_DEQUEUE_POINTER_CMD 16
|
||||
#define XHCI_TRB_TYPE_SETUP_STAGE 2 /* Transfer Ring, control transfers only */
|
||||
#define XHCI_TRB_TYPE_DATA_STAGE 3
|
||||
#define XHCI_TRB_TYPE_STATUS_STAGE 4
|
||||
#define XHCI_TRB_TYPE_TRANSFER_EVENT 32
|
||||
#define XHCI_TRB_TYPE_COMMAND_COMPLETION_EVT 33
|
||||
#define XHCI_TRB_TYPE_PORT_STATUS_CHANGE_EVT 34
|
||||
|
||||
/* Control bits used only by Link TRBs */
|
||||
#define XHCI_TRB_CONTROL_TC (1u << 1) /* Toggle Cycle */
|
||||
|
||||
/* Control bits for control-transfer TRBs (xHCI 1.2 spec section 4.11.2.2 /
|
||||
* table 6-23..6-25). IDT (Immediate Data) tells the controller the Setup
|
||||
* Stage TRB's parameter field IS the 8-byte setup packet, not a pointer
|
||||
* to one -- required for every Setup Stage TRB. TRT/DIR select data
|
||||
* direction: TRT=3 (IN Data Stage) for the standard "read a descriptor"
|
||||
* case this driver needs first; DIR must match TRT's direction on the
|
||||
* Data Stage TRB, and the Status Stage TRB's DIR is the OPPOSITE
|
||||
* direction of the Data Stage (status is always the reverse handshake). */
|
||||
#define XHCI_TRB_CONTROL_IDT (1u << 6)
|
||||
#define XHCI_TRB_CONTROL_IOC (1u << 5) /* Interrupt On Completion */
|
||||
#define XHCI_TRB_CONTROL_DIR_IN (1u << 16) /* Data/Status Stage: 1=IN, 0=OUT */
|
||||
#define XHCI_SETUP_TRT_NO_DATA 0u
|
||||
#define XHCI_SETUP_TRT_OUT_DATA 2u
|
||||
#define XHCI_SETUP_TRT_IN_DATA 3u
|
||||
#define XHCI_TRB_CONTROL_TRT_SHIFT 16 /* Setup Stage TRB only; Data/Status Stage overlays DIR at the same bit */
|
||||
|
||||
/* Standard USB Setup packet (8 bytes) -- the exact bytes placed in a
|
||||
* Setup Stage TRB's parameter field via IDT. */
|
||||
typedef struct {
|
||||
uint8_t bmRequestType;
|
||||
uint8_t bRequest;
|
||||
uint16_t wValue;
|
||||
uint16_t wIndex;
|
||||
uint16_t wLength;
|
||||
} usb_setup_packet_t;
|
||||
|
||||
#define USB_REQ_GET_DESCRIPTOR 6u
|
||||
#define USB_REQ_SET_CONFIGURATION 9u
|
||||
#define USB_REQ_CLEAR_FEATURE 1u
|
||||
#define USB_DESC_TYPE_DEVICE 1u
|
||||
#define USB_DESC_TYPE_CONFIG 2u
|
||||
#define USB_DIR_DEVICE_TO_HOST 0x80u
|
||||
#define USB_DIR_HOST_TO_DEVICE 0x00u
|
||||
|
||||
/* Standard USB Device/Endpoint feature selectors (USB 2.0 spec table 9-6) --
|
||||
* ENDPOINT_HALT (0) is the halt condition on a specific endpoint, cleared
|
||||
* (and the endpoint's data toggle reset) by a CLEAR_FEATURE request whose
|
||||
* wValue is this selector and whose wIndex is the endpoint's own address --
|
||||
* the USB-level half of G.1's stall recovery (xHCI Reset Endpoint +
|
||||
* SET_TR_DEQUEUE_POINTER clear the xHC-side state; this clears the device-
|
||||
* side halt so the endpoint will actually drive new transfers again).
|
||||
* bmRequestType type field (bits 6:5 of the request type) -- 0 = standard,
|
||||
* 1 = class, and the recipient field (bits 4:0) -- 0 = device, 2 = endpoint.
|
||||
* BOT Mass Storage Reset (USB Mass Storage Class Bulk-Only Transport spec
|
||||
* section 3.1) is a class, interface-recipient (recipient 1) request, the
|
||||
* BOT-spec-mandated full teardown + restart of a stalled command sequence. */
|
||||
#define USB_REQ_TYPE_STANDARD 0u
|
||||
#define USB_REQ_TYPE_CLASS 1u
|
||||
#define USB_RECIP_DEVICE 0u
|
||||
#define USB_RECIP_INTERFACE 1u
|
||||
#define USB_RECIP_ENDPOINT 2u
|
||||
#define USB_FEATURE_ENDPOINT_HALT 0u
|
||||
#define USB_BOT_MASS_STORAGE_RESET 0xFFu
|
||||
|
||||
/* Standard USB Interface descriptor field offsets (9 bytes, USB 2.0 spec
|
||||
* table 9-12) -- Mass Storage class detection reads these three fields.
|
||||
* Not decoded via a struct like usb_setup_packet_t: the Interface
|
||||
* descriptor's exact position within a Configuration descriptor's byte
|
||||
* stream isn't fixed (depends on the device's actual interface/endpoint
|
||||
* layout), so it's found by walking the byte stream looking for
|
||||
* bDescriptorType == USB_DESC_TYPE_INTERFACE, not by a fixed struct
|
||||
* offset into the whole buffer. */
|
||||
/* Every standard USB descriptor starts with these two bytes (bLength,
|
||||
* bDescriptorType) -- used to walk the concatenated descriptor stream a
|
||||
* full Configuration descriptor read returns (Config + Interface +
|
||||
* Endpoint descriptors back to back), not just the Interface one. */
|
||||
#define USB_DESC_OFF_LENGTH 0u
|
||||
#define USB_DESC_OFF_TYPE 1u
|
||||
#define USB_CONFIG_OFF_TOTAL_LENGTH 2u /* wTotalLength, 2 bytes, Configuration descriptor only */
|
||||
#define USB_CONFIG_OFF_CONFIG_VALUE 5u /* bConfigurationValue -- the value SET_CONFIGURATION needs in wValue */
|
||||
#define USB_DESC_TYPE_INTERFACE 4u
|
||||
#define USB_IFACE_OFF_CLASS 5u
|
||||
#define USB_IFACE_OFF_SUBCLASS 6u
|
||||
#define USB_IFACE_OFF_PROTOCOL 7u
|
||||
#define USB_CLASS_MASS_STORAGE 0x08u
|
||||
#define USB_SUBCLASS_SCSI 0x06u /* SCSI transparent command set */
|
||||
#define USB_PROTOCOL_BOT 0x50u /* Bulk-Only Transport */
|
||||
|
||||
/* Standard USB Endpoint descriptor field offsets (7 bytes, USB 2.0 spec
|
||||
* table 9-13) -- found the same way as the Interface descriptor: walked
|
||||
* by bDescriptorType within the concatenated stream, not a fixed offset,
|
||||
* since the number of endpoints on the interface isn't known up front. */
|
||||
#define USB_DESC_TYPE_ENDPOINT 5u
|
||||
#define USB_EP_OFF_ADDRESS 2u /* bEndpointAddress: bit 7 = direction, bits 3:0 = number */
|
||||
#define USB_EP_OFF_ATTRIBUTES 3u /* bmAttributes: bits 1:0 = transfer type */
|
||||
#define USB_EP_OFF_MAX_PACKET_SIZE 4u /* wMaxPacketSize, 2 bytes */
|
||||
#define USB_EP_ADDR_DIR_MASK 0x80u
|
||||
#define USB_EP_ADDR_NUM_MASK 0x0Fu
|
||||
#define USB_EP_ATTR_TYPE_MASK 0x03u
|
||||
#define USB_EP_TYPE_BULK 0x02u
|
||||
|
||||
/* Bulk-Only Transport Command Block Wrapper (USB Mass Storage Class Bulk-
|
||||
* Only Transport spec, section 5.1) -- sent host-to-device on the bulk OUT
|
||||
* endpoint ahead of every SCSI command's data phase. Fixed 31-byte wire
|
||||
* layout; every multi-byte field is little-endian, which this driver's
|
||||
* targets (amd64/aarch64/riscv64, all little-endian) already assume
|
||||
* throughout (no htole32-style conversions anywhere in this codebase) --
|
||||
* direct field assignment is wire-correct as-is. sizeof() is NOT used as
|
||||
* this struct's DMA length anywhere (may be padded to 32 by the compiler
|
||||
* to satisfy the uint32_t members' alignment) -- USB_BOT_CBW_LENGTH (31)
|
||||
* is the correct, explicit wire length, matching the same
|
||||
* offset-constant-not-sizeof discipline already used for the USB
|
||||
* descriptor field offsets above. */
|
||||
typedef struct {
|
||||
uint32_t dCBWSignature;
|
||||
uint32_t dCBWTag;
|
||||
uint32_t dCBWDataTransferLength;
|
||||
uint8_t bmCBWFlags;
|
||||
uint8_t bCBWLUN;
|
||||
uint8_t bCBWCBLength;
|
||||
uint8_t CBWCB[16];
|
||||
} usb_bot_cbw_t;
|
||||
|
||||
#define USB_BOT_CBW_SIGNATURE 0x43425355u /* "USBC", wire byte order U,S,B,C as an LE dword */
|
||||
#define USB_BOT_CBW_LENGTH 31u
|
||||
#define USB_BOT_CBW_FLAG_DATA_IN 0x80u /* bmCBWFlags: device-to-host data stage */
|
||||
#define USB_BOT_CBW_LUN_DEFAULT 0u /* no multi-LUN support -- single-LUN devices only */
|
||||
|
||||
#define SCSI_CMD_READ10 0x28u
|
||||
#define SCSI_CDB_LEN_READ10 10u
|
||||
|
||||
/* SCSI WRITE(10) (SBC-3 section 5.32) -- direct mirror of READ(10): same
|
||||
* 10-byte CDB layout (opcode, LBA, transfer length), opposite data
|
||||
* direction (host -> device). See xhci_bot_send_write10()'s own doc
|
||||
* comment for the CBW-level difference (bmCBWFlags clears the DATA_IN
|
||||
* bit instead of setting it). */
|
||||
#define SCSI_CMD_WRITE10 0x2Au
|
||||
#define SCSI_CDB_LEN_WRITE10 10u
|
||||
|
||||
/* SCSI TEST UNIT READY (SPC-4 section 6.33) -- 6-byte CDB, all-zero apart
|
||||
* from the opcode, no data stage. Convention (not spec-mandated, but
|
||||
* standard SCSI target behavior): the first command a target sees after
|
||||
* attach fails with CHECK CONDITION/UNIT ATTENTION (media/reset notice),
|
||||
* clearing on the next command -- issuing this ahead of a real data
|
||||
* command and retrying it a bounded number of times on failure is the
|
||||
* standard way to drain that condition before trusting a READ/WRITE. */
|
||||
#define SCSI_CMD_TEST_UNIT_READY 0x00u
|
||||
#define SCSI_CDB_LEN_TEST_UNIT_READY 6u
|
||||
|
||||
/* Bounded retry count for SCSI TEST UNIT READY before giving up -- see
|
||||
* xhci_dev_t's bot_tur_retries doc comment. */
|
||||
#define XHCI_BOT_TUR_MAX_RETRIES 3u
|
||||
|
||||
/* Bounded recovery count for a stalled bulk endpoint before giving up on
|
||||
* it -- see xhci_dev_t's bot_stall_recoveries doc comment (G.1 / §F.14).
|
||||
* Mirrors the shape of XHCI_BOT_TUR_MAX_RETRIES: a small fixed budget of
|
||||
* full recoveries, each of which is itself the multi-step xHCI Reset
|
||||
* Endpoint -> Set TR Dequeue Pointer -> CLEAR_FEATURE(ENDPOINT_HALT)
|
||||
* sequence (escalating to a BOT Mass Storage Reset on the last try),
|
||||
* after which the original SCSI command is retried from scratch. Two
|
||||
* full recoveries, then escalation and terminal failure, is a deliberately
|
||||
* tight bound chosen to match this driver's "recover or fail clean, never
|
||||
* wedge the controller, never loop forever" contract -- a genuinely
|
||||
* wedged device gets two chances to clear, then the block layer sees a
|
||||
* clean BOT_STATUS_FAILED. */
|
||||
#define XHCI_BOT_STALL_MAX_RECOVERIES 2u
|
||||
|
||||
/* SCSI READ CAPACITY(10) (SBC-3 section 5.14) -- 10-byte CDB, opcode 0x25,
|
||||
* every other CDB byte reserved/zero for the standard "report capacity"
|
||||
* form (LBA field left 0, PMI bit left clear). 8-byte Data-In reply:
|
||||
* bytes 0-3 = Returned Logical Block Address (the *last* valid LBA, not a
|
||||
* block count) big-endian, bytes 4-7 = Block Length in Bytes big-endian.
|
||||
* This is Milestone 2h's prerequisite for everything else -- there is no
|
||||
* other way for this driver to learn a device's block size or capacity,
|
||||
* and block_subsystem.c's blk_subsys_attach_device() needs exactly that
|
||||
* (via blkio_info()) before it can do anything with a device. */
|
||||
#define SCSI_CMD_READ_CAPACITY10 0x25u
|
||||
#define SCSI_CDB_LEN_READ_CAPACITY10 10u
|
||||
#define SCSI_READ_CAPACITY10_DATA_LEN 8u
|
||||
|
||||
/* Bulk-Only Transport Command Status Wrapper (same spec, section 5.2) --
|
||||
* received device-to-host on the bulk IN endpoint after the Data-In
|
||||
* stage, closing out every SCSI command. Fixed 13-byte wire layout; same
|
||||
* little-endian-direct-assignment and explicit-length-not-sizeof
|
||||
* discipline as usb_bot_cbw_t above (this struct's natural size is
|
||||
* likely padded to 16 by the compiler for the same alignment reason --
|
||||
* USB_BOT_CSW_LENGTH (13) is the real wire length). */
|
||||
typedef struct {
|
||||
uint32_t dCSWSignature;
|
||||
uint32_t dCSWTag;
|
||||
uint32_t dCSWDataResidue;
|
||||
uint8_t bCSWStatus;
|
||||
} usb_bot_csw_t;
|
||||
|
||||
#define USB_BOT_CSW_SIGNATURE 0x53425355u /* "USBS", wire byte order U,S,B,S as an LE dword */
|
||||
#define USB_BOT_CSW_LENGTH 13u
|
||||
#define USB_BOT_CSW_STATUS_PASS 0u
|
||||
#define USB_BOT_CSW_STATUS_FAILED 1u
|
||||
#define USB_BOT_CSW_STATUS_PHASE_ERROR 2u
|
||||
|
||||
/* Command Completion Event TRB layout (xHCI 1.2 spec table 6-32):
|
||||
* parameter[63:4] = Command TRB Pointer, status[31:24] = Completion Code,
|
||||
* status[23:0] = unused here, control[31:24] = Slot ID (Enable Slot's
|
||||
* result, also present on Address Device completions). */
|
||||
#define XHCI_EVT_COMPLETION_CODE(status) (((uint32_t)(status) >> 24) & 0xFFu)
|
||||
#define XHCI_EVT_SLOT_ID(control) (((uint32_t)(control) >> 24) & 0xFFu)
|
||||
#define XHCI_COMPLETION_CODE_SUCCESS 1u
|
||||
#define XHCI_COMPLETION_CODE_STALL_ERROR 6u
|
||||
|
||||
/* Port Status Change Event TRB layout (xHCI 1.2 spec table 6-34):
|
||||
* parameter[31:24] = Port ID (1-based, matches PORTSC array indexing
|
||||
* 1..MaxPorts); parameter[23:0] and the rest of the TRB are reserved. */
|
||||
#define XHCI_PSC_EVT_PORT_ID(parameter) (((uint32_t)(parameter) >> 24) & 0xFFu)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Slot Context, Endpoint Context, Input Control Context — 32-byte layout
|
||||
* only (xHCI 1.2 spec tables 6-6, 6-9, 6-5). HCCPARAMS1.CSZ selects 32- vs
|
||||
* 64-byte contexts; confirmed live (CSZ=0) against this driver's target
|
||||
* QEMU qemu-xhci controller (2026-08-22) before writing these -- 64-byte
|
||||
* contexts (CSZ=1) are NOT implemented here. xhci_cmd_address_device()
|
||||
* checks CSZ itself and refuses rather than silently mis-laying-out a
|
||||
* 64-byte-context controller as 32-byte.
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t dword0; /* Route String[19:0] Speed[23:20] MTT[25] Hub[26] Context Entries[31:27] */
|
||||
volatile uint32_t dword1; /* Max Exit Latency[15:0] Root Hub Port Number[23:16] Number of Ports[31:24] */
|
||||
volatile uint32_t dword2; /* Parent Hub Slot ID[7:0] Parent Port Number[15:8] TTT[17:16] Interrupter Target[31:22] */
|
||||
volatile uint32_t dword3; /* USB Device Address[7:0] Slot State[31:27] */
|
||||
volatile uint32_t reserved[4];
|
||||
} xhci_slot_ctx32_t;
|
||||
|
||||
#define XHCI_SLOT_CTX_SPEED_SHIFT 20
|
||||
#define XHCI_SLOT_CTX_CONTEXT_ENTRIES_SHIFT 27
|
||||
#define XHCI_SLOT_CTX_ROOT_PORT_SHIFT 16
|
||||
#define XHCI_SLOT_CTX_INTR_TARGET_SHIFT 22
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t dword0; /* EP State[2:0] Interval[23:16] */
|
||||
volatile uint32_t dword1; /* CErr[2:1] EP Type[5:3] Max Packet Size[31:16] */
|
||||
volatile uint64_t tr_dequeue_ptr; /* [0]=DCS (Dequeue Cycle State), [63:4]=pointer */
|
||||
volatile uint32_t dword4; /* Average TRB Length[15:0] */
|
||||
volatile uint32_t reserved[3];
|
||||
} xhci_ep_ctx32_t;
|
||||
|
||||
#define XHCI_EP_CTX_TYPE_SHIFT 3
|
||||
#define XHCI_EP_CTX_TYPE_CONTROL_BIDI 4u /* EP0 */
|
||||
#define XHCI_EP_CTX_TYPE_BULK_OUT 2u /* xHCI 1.2 spec table 6-9 */
|
||||
#define XHCI_EP_CTX_TYPE_BULK_IN 6u
|
||||
#define XHCI_EP_CTX_CERR_SHIFT 1
|
||||
#define XHCI_EP_CTX_MAX_PACKET_SHIFT 16
|
||||
|
||||
/* Device Context Index (DCI) for a given endpoint, xHCI 1.2 spec section
|
||||
* 4.5.1: DCI = 2*EndpointNumber + Direction (Direction=1 for IN, 0 for
|
||||
* OUT/control) -- EP0 is always DCI 1 regardless of direction, a special
|
||||
* case this macro does not need to handle since EP0's DCI is hardcoded
|
||||
* (XHCI_INPUT_CTRL_ADD_EP0's bit position) everywhere it's used. Takes a
|
||||
* full bEndpointAddress (bit 7 = direction, bits 3:0 = number), matching
|
||||
* how bulk_in_ep_addr/bulk_out_ep_addr are stored. */
|
||||
#define XHCI_EP_ADDR_TO_DCI(addr) \
|
||||
((2u * ((uint32_t)(addr) & USB_EP_ADDR_NUM_MASK)) + \
|
||||
(((uint32_t)(addr) & USB_EP_ADDR_DIR_MASK) ? 1u : 0u))
|
||||
|
||||
typedef struct {
|
||||
volatile uint32_t drop_flags; /* D0..D31 -- unused for Address Device (nothing to drop) */
|
||||
volatile uint32_t add_flags; /* A0..A31 -- bit0=Slot, bit1=EP0 for Address Device */
|
||||
volatile uint32_t reserved[5];
|
||||
volatile uint32_t config_word; /* Configuration Value/Interface/AltSetting -- unused here */
|
||||
} xhci_input_ctrl_ctx32_t;
|
||||
|
||||
#define XHCI_INPUT_CTRL_ADD_SLOT (1u << 0)
|
||||
#define XHCI_INPUT_CTRL_ADD_EP0 (1u << 1)
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Ring sizing — decided up front per Milestone 2's punch list (2a).
|
||||
*
|
||||
* Fixed, single-page rings: 256 TRBs x 16 bytes = 4096 bytes = one page.
|
||||
* This project's usage (MSC hotplug detection + read/write to one drive at
|
||||
* a time) does not need a high-throughput, dynamically-growable ring —
|
||||
* matches this codebase's existing preference for fixed, page-sized
|
||||
* allocations over dynamic growth (e.g. KRD_MAX_BLOCKS's fixed 1024-block
|
||||
* RAMDRIVE). One Command Ring, one Event Ring (Interrupter 0 only — this
|
||||
* driver does not use multiple interrupters).
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#define XHCI_RING_TRB_COUNT 256u
|
||||
#define XHCI_RING_BYTES (XHCI_RING_TRB_COUNT * sizeof(xhci_trb_t))
|
||||
|
||||
/* Bounded per-call event-ring drain (Milestone 2h boot-attach hardening).
|
||||
* xhci_poll_events()'s drain loop is otherwise terminated only by the
|
||||
* ring's cycle-bit match, which is a fine early-exit on the normal,
|
||||
* quiescent path (each beat drains the one-or-few events the controller
|
||||
* posts per chained command) but has no hard ceiling. If the controller
|
||||
* keeps producing events across the whole drain -- ERDP is not written
|
||||
* back until the loop exits, so the controller cannot reclaim event TRBs
|
||||
* mid-drain, and on pathological controller behavior the head can chase
|
||||
* the software dequeue pointer indefinitely -- the loop can livelock:
|
||||
* xhci_poll_events() never returns, sk_repl_idle() never reaches its
|
||||
* bot_msc_attach_pending check, and a fresh USB BOT device that finished
|
||||
* SET_CONFIGURATION is left flagged-but-never-attached while the guest,
|
||||
* though alive, appears hung. Bounding the drain makes xhci_poll_events()
|
||||
* always terminate and always write ERDP each call; any events not yet
|
||||
* processed keep their cycle bit and are simply re-read on the next poll,
|
||||
* so nothing is dropped. Equal to a full ring: on the healthy path one
|
||||
* drain never processes anywhere near this many events, so this bound
|
||||
* only ever triggers in the pathological case it exists to break. */
|
||||
#define XHCI_EVT_RING_MAX_DRAIN XHCI_RING_TRB_COUNT
|
||||
|
||||
/* Milestone 2e: upper bound on ports tracked for connect/disconnect ->
|
||||
* Enable Slot correlation (xhci_dev_t.port_slot_id). PORTSC's own field
|
||||
* width allows up to 255 ports (XHCI_HCSPARAMS1_MAX_PORTS is 8 bits), but
|
||||
* no real or QEMU-emulated root hub this driver targets comes close to
|
||||
* that; 32 is comfortably generous and keeps this a fixed, not
|
||||
* heap-allocated, array. */
|
||||
#define XHCI_MAX_TRACKED_PORTS 32u
|
||||
|
||||
#endif /* STARKERNEL_XHCI_H */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* xxhash64.h - xxHash64 Implementation (Freestanding)
|
||||
*
|
||||
* Fast, deterministic 64-bit hash function for capsule content addressing.
|
||||
* No libc dependency - works in kernel context.
|
||||
*
|
||||
* Based on xxHash by Yann Collet (BSD-2-Clause license).
|
||||
* https://github.com/Cyan4973/xxHash
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_XXHASH64_H
|
||||
#define STARKERNEL_XXHASH64_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*===========================================================================
|
||||
* Constants
|
||||
*===========================================================================*/
|
||||
|
||||
/** Default seed for capsule hashing (deterministic) */
|
||||
#define XXHASH64_CAPSULE_SEED 0ULL
|
||||
|
||||
/*===========================================================================
|
||||
* One-Shot Hashing
|
||||
*===========================================================================*/
|
||||
|
||||
/**
|
||||
* xxhash64 - Compute xxHash64 of a buffer
|
||||
*
|
||||
* @param data Input data
|
||||
* @param len Length in bytes
|
||||
* @param seed Hash seed (use XXHASH64_CAPSULE_SEED for capsules)
|
||||
* @return 64-bit hash value
|
||||
*/
|
||||
uint64_t xxhash64(const void *data, size_t len, uint64_t seed);
|
||||
|
||||
/**
|
||||
* xxhash64_capsule - Hash capsule payload with standard seed
|
||||
*
|
||||
* Convenience wrapper using XXHASH64_CAPSULE_SEED.
|
||||
*
|
||||
* @param data Capsule payload bytes
|
||||
* @param len Payload length
|
||||
* @return Content hash (capsule_id)
|
||||
*/
|
||||
static inline uint64_t xxhash64_capsule(const void *data, size_t len) {
|
||||
return xxhash64(data, len, XXHASH64_CAPSULE_SEED);
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
* Streaming API (for large data)
|
||||
*===========================================================================*/
|
||||
|
||||
/** Streaming state structure */
|
||||
typedef struct {
|
||||
uint64_t total_len;
|
||||
uint64_t v1;
|
||||
uint64_t v2;
|
||||
uint64_t v3;
|
||||
uint64_t v4;
|
||||
uint8_t buffer[32];
|
||||
uint32_t buffer_size;
|
||||
uint64_t seed;
|
||||
} XXHash64State;
|
||||
|
||||
/**
|
||||
* xxhash64_reset - Initialize streaming state
|
||||
*
|
||||
* @param state State to initialize
|
||||
* @param seed Hash seed
|
||||
*/
|
||||
void xxhash64_reset(XXHash64State *state, uint64_t seed);
|
||||
|
||||
/**
|
||||
* xxhash64_update - Feed data into streaming hash
|
||||
*
|
||||
* @param state Streaming state
|
||||
* @param data Input data
|
||||
* @param len Length in bytes
|
||||
*/
|
||||
void xxhash64_update(XXHash64State *state, const void *data, size_t len);
|
||||
|
||||
/**
|
||||
* xxhash64_digest - Finalize and get hash value
|
||||
*
|
||||
* @param state Streaming state
|
||||
* @return 64-bit hash value
|
||||
*/
|
||||
uint64_t xxhash64_digest(const XXHash64State *state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* STARKERNEL_XXHASH64_H */
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* zuse_cert_devblock.h -- SUPERSEDED 2026-08-28 (FABRIC-2.md §F.20/§F.21).
|
||||
* Zuse is now thumbdrive-resident, not system-resident: her seed lives
|
||||
* only on her own minted thumbdrive, never written to the fence. The
|
||||
* fence's devblock_from_top=0 slot this type used to occupy now holds
|
||||
* zuse_genesis_marker_t (zuse_genesis_marker.h) instead -- pubkey only,
|
||||
* no seed. This type is no longer written by any code path; kept in the
|
||||
* repo as historical record of the format it replaced, per this
|
||||
* project's own convention for superseded design (see e.g. the
|
||||
* TRIPOD.md/HERMES.md/ARTEMIS.md/CONSOLE.md superseded-header pattern).
|
||||
* Do not resurrect writes to this format.
|
||||
*
|
||||
* Original doc, kept for context:
|
||||
*
|
||||
* on-disk record format for Zuse's cert, stored
|
||||
* in devblock_from_top=0 of the top-of-device system-metadata fence
|
||||
* (block_subsystem.h's blk_meta_zone_read()/write(), Phase 8, FABRIC-2.md
|
||||
* §C). Raw, unpacked 4 KiB devblock -- same convention as the volume
|
||||
* header itself (magic + version + fields + pad-to-4096, real CRC from
|
||||
* day one, matching homeblocks_sig_t's own precedent for exactly this
|
||||
* reason: this gates a real security check, not a placeholder).
|
||||
*
|
||||
* Deliberately its own header, not inlined at the one call site that
|
||||
* uses it today (kernel_main.c's first-boot mint-or-load): the ongoing
|
||||
* `MINT` word (still open, FABRIC-2.md) will be a second consumer of
|
||||
* this exact format later, and the format should be stable and
|
||||
* documented once rather than ad-hoc.
|
||||
*/
|
||||
#ifndef STARKERNEL_ZUSE_CERT_DEVBLOCK_H
|
||||
#define STARKERNEL_ZUSE_CERT_DEVBLOCK_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Packed via shifts, not a hand-computed hex literal -- this project's
|
||||
* own standing lesson about hand-derived numeric constants in this
|
||||
* class of code (see FABRIC-2.md's Ed25519/scalar25519 writeups). */
|
||||
#define ZUSE_CERT_DEVBLOCK_MAGIC \
|
||||
((uint32_t)'Z' | ((uint32_t)'U' << 8) | ((uint32_t)'S' << 16) | ((uint32_t)'E' << 24))
|
||||
|
||||
#define ZUSE_CERT_DEVBLOCK_VERSION 1u
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic; /* ZUSE_CERT_DEVBLOCK_MAGIC; anything else means
|
||||
* "not a real cert yet" (blank/foreign bytes),
|
||||
* not a format-corruption error */
|
||||
uint32_t version; /* ZUSE_CERT_DEVBLOCK_VERSION */
|
||||
uint8_t seed[32]; /* Ed25519 seed -- the private identity */
|
||||
uint8_t pubkey[32]; /* Ed25519 public key derived from seed at mint time */
|
||||
uint64_t crc; /* CRC-64/ISO (block_subsystem.h's compute_crc64())
|
||||
* over every byte of this struct up to (not
|
||||
* including) this field -- real from day one,
|
||||
* this gates a real security check */
|
||||
uint8_t _pad[4096 - (4 + 4 + 32 + 32 + 8)];
|
||||
} zuse_cert_devblock_t;
|
||||
|
||||
_Static_assert(sizeof(zuse_cert_devblock_t) == 4096,
|
||||
"zuse_cert_devblock_t must be exactly one 4 KiB devblock");
|
||||
|
||||
#endif /* STARKERNEL_ZUSE_CERT_DEVBLOCK_H */
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
StarForth — Steady-State Virtual Machine Runtime
|
||||
|
||||
Copyright (c) 2023–2025 Robert A. James
|
||||
All rights reserved.
|
||||
|
||||
Licensed under the StarForth License, Version 1.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* zuse_eligibility.h - Read/add/membership-check over the on-disk
|
||||
* elevation eligibility list (FABRIC-2.md §H.5/§H.12 Phase 6,
|
||||
* zuse_eligibility_list.h's zuse_eligibility_devblock_t chain). Zuse
|
||||
* checks zuse_eligibility_is_member() before honoring any
|
||||
* ELEVATE-REQUEST (§H.7/§H.8) -- a gating layer on top of the
|
||||
* message-based elevation trigger, not a replacement for it. Adding an
|
||||
* entry (zuse_eligibility_add()) is meant to be reachable only from a
|
||||
* Zuse-only FORTH word (§H.12 item 19, gated by zuse_session), not
|
||||
* called directly from arbitrary session code.
|
||||
*/
|
||||
|
||||
#ifndef STARKERNEL_ZUSE_ELIGIBILITY_H
|
||||
#define STARKERNEL_ZUSE_ELIGIBILITY_H
|
||||
|
||||
#ifdef __STARKERNEL__
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* zuse_eligibility_is_member - Check whether pubkey appears anywhere in
|
||||
* the eligibility list chain. Fail-closed: an empty/nonexistent list, a
|
||||
* corrupt/foreign devblock encountered mid-chain, or any I/O error all
|
||||
* read as "not eligible" (0), never "eligible" -- this gates elevation,
|
||||
* so an unreadable list must never be treated as permissive.
|
||||
*
|
||||
* @param pubkey 32-byte Ed25519 public key to look up.
|
||||
* @return 1 if found, 0 otherwise (including on any error).
|
||||
*/
|
||||
int zuse_eligibility_is_member(const uint8_t pubkey[32]);
|
||||
|
||||
/**
|
||||
* zuse_eligibility_add - Add pubkey to the eligibility list, creating
|
||||
* the list's head devblock if it doesn't exist yet and chaining a fresh
|
||||
* devblock onto the tail if the current tail is full. Idempotent: adding
|
||||
* an already-present pubkey is a no-op success, not a duplicate entry.
|
||||
*
|
||||
* @param pubkey 32-byte Ed25519 public key to add.
|
||||
* @return 0 on success (added, or already present), -1 on
|
||||
* failure (fence write failed, fence budget exhausted, or
|
||||
* a corrupt devblock was encountered mid-chain).
|
||||
*/
|
||||
int zuse_eligibility_add(const uint8_t pubkey[32]);
|
||||
|
||||
#endif /* __STARKERNEL__ */
|
||||
|
||||
#endif /* STARKERNEL_ZUSE_ELIGIBILITY_H */
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* zuse_eligibility_list.h -- on-disk record format for Zuse's word/block
|
||||
* elevation eligibility list (FABRIC-2.md §H.5/§H.12 Phase 6): a simple
|
||||
* growable list of owner_pubkey[32] entries, no extra per-entry metadata
|
||||
* ("simple list, no extra metadata, unless we find a reason this won't
|
||||
* work"). Zuse checks this list before honoring any ELEVATE-REQUEST
|
||||
* (§H.7/§H.8) -- gating layer on top of the message-based elevation
|
||||
* trigger, not a replacement for it.
|
||||
*
|
||||
* Lives in the same top-of-device system-metadata fence as
|
||||
* zuse_genesis_marker_t (block_subsystem.h's blk_meta_zone_read()/
|
||||
* write(), Phase 8/§C), one slot over: the genesis marker owns
|
||||
* devblock_from_top=0, this list starts at devblock_from_top=1. Growable
|
||||
* across multiple devblocks via a singly-linked chain (each devblock's
|
||||
* `next_devblock_from_top` points at the next one, ZULIST_NO_NEXT means
|
||||
* "this is the last devblock in the chain") -- entries are appended by
|
||||
* filling the current tail devblock, then chaining a fresh one out of the
|
||||
* already-reserved BLK_META_FENCE_INIT budget once it's full. No fence
|
||||
* growth logic is needed yet.
|
||||
*
|
||||
* CORRECTION (FABRIC-3.md SXXVI follow-on, Step 4, 2026-09-13): the fence
|
||||
* is no longer this list's alone to grow into. artemis_sig_t now owns
|
||||
* devblock_from_top=64 (include/starkernel/artemis_sig.h), and the
|
||||
* per-VM log-persistence ring now owns devblock_from_top 65-96
|
||||
* (include/starkernel/log_region.h) -- both fixed, compile-time
|
||||
* constants, chosen deliberately clear of this list's own growth
|
||||
* direction (chaining upward from devblock_from_top=1). This list's real
|
||||
* remaining headroom is devblocks 1-63 (not "126 more slots" as this
|
||||
* comment used to say): 63 devblocks x ZUSE_ELIGIBILITY_ENTRIES_PER_DEVBLOCK
|
||||
* (127) = 8001 possible eligible identities before ever reaching
|
||||
* devblock_from_top=64 -- vastly beyond any plausible real deployment of
|
||||
* this project, but genuinely unenforced: there is no code-level ceiling
|
||||
* stopping this chain from growing into devblock 64+ if that number were
|
||||
* ever actually approached. Noted here, not silently assumed safe, after
|
||||
* a fixed-offset collision was caught and fixed once already this session
|
||||
* (Artemis's own disk vs. its live BAM, artemis_sig.h's own CORRECTION
|
||||
* comment) -- the lesson being to state the real bound in writing rather
|
||||
* than trust "it'll never get that big."
|
||||
*
|
||||
* Raw, unpacked 4 KiB devblock -- same convention as
|
||||
* zuse_genesis_marker_t/homeblocks_sig_t: real CRC from day one, this
|
||||
* gates a real security check, not a placeholder.
|
||||
*/
|
||||
#ifndef STARKERNEL_ZUSE_ELIGIBILITY_LIST_H
|
||||
#define STARKERNEL_ZUSE_ELIGIBILITY_LIST_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Packed via shifts, not a hand-computed hex literal -- see
|
||||
* zuse_genesis_marker.h's own note on this project's standing
|
||||
* numeric-constant convention. */
|
||||
#define ZUSE_ELIGIBILITY_LIST_MAGIC \
|
||||
((uint32_t)'Z' | ((uint32_t)'E' << 8) | ((uint32_t)'L' << 16) | ((uint32_t)'G' << 24))
|
||||
|
||||
#define ZUSE_ELIGIBILITY_LIST_VERSION 1u
|
||||
|
||||
/* devblock_from_top of this list's head devblock -- one past the genesis
|
||||
* marker's devblock_from_top=0. */
|
||||
#define ZUSE_ELIGIBILITY_LIST_HEAD_DEVBLOCK 1u
|
||||
|
||||
/* next_devblock_from_top value meaning "no further devblock -- this is
|
||||
* the tail of the chain." 0xFFFFFFFF can never be a real
|
||||
* devblock_from_top (BLK_META_FENCE_INIT is 128), so it is unambiguous. */
|
||||
#define ZUSE_ELIGIBILITY_LIST_NO_NEXT 0xFFFFFFFFu
|
||||
|
||||
/* How many owner_pubkey[32] entries fit in one devblock alongside the
|
||||
* header/crc/next-pointer overhead below. */
|
||||
#define ZUSE_ELIGIBILITY_ENTRIES_PER_DEVBLOCK 127u
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic; /* ZUSE_ELIGIBILITY_LIST_MAGIC; anything else on
|
||||
* the head devblock means "list not created
|
||||
* yet" (blank/foreign bytes), not a
|
||||
* format-corruption error. A non-head devblock
|
||||
* is only ever read by following a real
|
||||
* next_devblock_from_top link, so its own
|
||||
* magic is still checked the same way to catch
|
||||
* a corrupt/foreign devblock mid-chain. */
|
||||
uint32_t version; /* ZUSE_ELIGIBILITY_LIST_VERSION */
|
||||
uint32_t count; /* Number of valid entries in this devblock's
|
||||
* own entries[] (0..ZUSE_ELIGIBILITY_ENTRIES_
|
||||
* PER_DEVBLOCK), not a running total across the
|
||||
* whole chain. */
|
||||
uint32_t next_devblock_from_top; /* Next devblock in the chain, or
|
||||
* ZUSE_ELIGIBILITY_LIST_NO_NEXT if this is the
|
||||
* tail. */
|
||||
uint8_t entries[ZUSE_ELIGIBILITY_ENTRIES_PER_DEVBLOCK][32]; /* Ed25519
|
||||
* public keys eligible for elevation. Only the
|
||||
* first `count` entries are meaningful. */
|
||||
uint64_t crc; /* CRC-64/ISO (block_subsystem.h's
|
||||
* compute_crc64()) over every byte of this
|
||||
* struct up to (not including) this field. */
|
||||
uint8_t _pad[4096 - (4 + 4 + 4 + 4 + (ZUSE_ELIGIBILITY_ENTRIES_PER_DEVBLOCK * 32) + 8)];
|
||||
} zuse_eligibility_devblock_t;
|
||||
|
||||
_Static_assert(sizeof(zuse_eligibility_devblock_t) == 4096,
|
||||
"zuse_eligibility_devblock_t must be exactly one 4 KiB devblock");
|
||||
|
||||
#endif /* STARKERNEL_ZUSE_ELIGIBILITY_LIST_H */
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* zuse_genesis_marker.h -- on-disk record format for the system-resident
|
||||
* "a root Zuse identity already exists" marker (FABRIC-2.md §F.21),
|
||||
* stored in devblock_from_top=0 of the top-of-device system-metadata
|
||||
* fence (block_subsystem.h's blk_meta_zone_read()/write(), same location
|
||||
* zuse_cert_devblock_t used to occupy).
|
||||
*
|
||||
* Supersedes zuse_cert_devblock_t (zuse_cert_devblock.h, kept in the repo
|
||||
* as historical record, no longer written): that type stored Zuse's own
|
||||
* *seed* system-resident. Under the thumbdrive-resident Zuse design
|
||||
* (§F.20/§F.21), the seed lives only on Zuse's own minted thumbdrive --
|
||||
* this record deliberately holds only her *public* key, enough to (a)
|
||||
* know genesis has already happened, so a second blank thumbdrive
|
||||
* attached on some later boot never mints a second competing root, and
|
||||
* (b) recognize which attached identity is genuinely hers. Losing this
|
||||
* fence record is not a security problem (it's not a secret); losing the
|
||||
* thumbdrive itself is what actually loses the identity.
|
||||
*/
|
||||
#ifndef STARKERNEL_ZUSE_GENESIS_MARKER_H
|
||||
#define STARKERNEL_ZUSE_GENESIS_MARKER_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define ZUSE_GENESIS_MARKER_MAGIC \
|
||||
((uint32_t)'Z' | ((uint32_t)'G' << 8) | ((uint32_t)'E' << 16) | ((uint32_t)'N' << 24))
|
||||
|
||||
#define ZUSE_GENESIS_MARKER_VERSION 1u
|
||||
|
||||
typedef struct {
|
||||
uint32_t magic; /* ZUSE_GENESIS_MARKER_MAGIC; anything else means
|
||||
* "genesis has not happened yet" (blank/foreign
|
||||
* bytes), not a format-corruption error. */
|
||||
uint32_t version; /* ZUSE_GENESIS_MARKER_VERSION */
|
||||
uint8_t zuse_pubkey[32]; /* Ed25519 public key of the root Zuse
|
||||
* identity minted at genesis. No seed here --
|
||||
* that lives only on Zuse's own thumbdrive. */
|
||||
uint64_t crc; /* CRC-64/ISO (block_subsystem.h's compute_crc64())
|
||||
* over every byte of this struct up to (not
|
||||
* including) this field. */
|
||||
uint8_t _pad[4096 - (4 + 4 + 32 + 8)];
|
||||
} zuse_genesis_marker_t;
|
||||
|
||||
_Static_assert(sizeof(zuse_genesis_marker_t) == 4096,
|
||||
"zuse_genesis_marker_t must be exactly one 4 KiB devblock");
|
||||
|
||||
#endif /* STARKERNEL_ZUSE_GENESIS_MARKER_H */
|
||||
Reference in New Issue
Block a user