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:
rajames
2026-10-01 15:40:09 -04:00
co-authored by Junie
parent 3c709c115b
commit a8b70e88d3
630 changed files with 2646 additions and 2036 deletions
+39
View File
@@ -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.
+106
View File
@@ -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 */
+89
View File
@@ -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 */
+274
View File
@@ -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 */
+59
View File
@@ -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
+304
View File
@@ -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 */
+347
View File
@@ -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 */
+153
View File
@@ -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 */
+169
View File
@@ -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 */
+263
View File
@@ -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 */
+52
View File
@@ -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 */
+37
View File
@@ -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 */
+237
View File
@@ -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 */
+61
View File
@@ -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 */
+47
View File
@@ -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 */
+226
View File
@@ -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 */
+63
View File
@@ -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 */
+170
View File
@@ -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 */
+60
View File
@@ -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 */
+126
View File
@@ -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 */
+9
View File
@@ -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`.
+80
View File
@@ -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 */
+56
View File
@@ -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 */
+195
View File
@@ -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 */
+81
View File
@@ -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 */
+68
View File
@@ -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 */
+51
View File
@@ -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 */
+86
View File
@@ -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 */
+69
View File
@@ -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 */
+64
View File
@@ -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 */
+191
View File
@@ -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 */
+104
View File
@@ -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 */
+63
View File
@@ -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 */
+79
View File
@@ -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 */
+166
View File
@@ -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 */
+187
View File
@@ -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 */
+65
View File
@@ -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 */
+48
View File
@@ -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 */
+68
View File
@@ -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 */
+37
View File
@@ -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 */
+169
View File
@@ -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 */
+30
View File
@@ -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 */
+226
View File
@@ -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 */
+322
View File
@@ -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 */
+661
View File
@@ -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 */
+57
View File
@@ -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 */
+68
View File
@@ -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 */
+58
View File
@@ -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 */
+13
View File
@@ -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.
+96
View File
@@ -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 */
+169
View File
@@ -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 */
+589
View File
@@ -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 */
+60
View File
@@ -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 */
+125
View File
@@ -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 */
+101
View File
@@ -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 */
+76
View File
@@ -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 */
+139
View File
@@ -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 */
+94
View File
@@ -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 */
+517
View File
@@ -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
+122
View File
@@ -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 */