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
+1380
View File
File diff suppressed because it is too large Load Diff
+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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,49 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/**
* capsule_console.h - Bare console-VM birth (FABRIC-2.md Phase F,
* 2026-08-28): a minimal VM whose only job is loading
* common:messaging.4th and being the physical REPL's relay target
* (sk_repl_dispatch_line(), repl.c) for a paired user VM
* (capsule_runcap_birth(), capsule_runcap.h). Not identity-bearing
* content -- no thumbdrive read, fixed embedded source, same
* heap-built-single-entry-directory shape RUNCAP already established
* (§F.6/§F.18), just with compile-time content instead of a devblock
* read.
*/
#ifndef STARKERNEL_CAPSULE_CONSOLE_H
#define STARKERNEL_CAPSULE_CONSOLE_H
#ifdef __STARKERNEL__
#include "starkernel/capsule_run.h"
#include "starkernel/vm_uuid.h"
/**
* capsule_console_birth - Birth a bare console VM.
*
* @param console_name Symbolic name for the new VM -- by convention
* the same name a user's own identity uses (e.g.
* "CaptBob"); sk_repl_dispatch_line() looks for a
* live "<name>~user" counterpart to decide
* whether a given active VM is a console.
* @param parent Who is birthing this VM (FABRIC-2.md §H.12 step 7)
* -- passed straight through to capsule_birth_baby().
* @param out_vm_id Output: assigned VM ID.
* @param out_vm_ctx Output: new VM context (may be NULL).
* @return CAPSULE_RUN_OK on success, error code otherwise.
*/
CapsuleRunResult capsule_console_birth(const char *console_name, VMUuid parent,
VMUuid *out_vm_id, void **out_vm_ctx);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_CAPSULE_CONSOLE_H */
@@ -0,0 +1,63 @@
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 2023–2025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0
*/
/**
* capsule_generated.h - Declarations for mkcapsule-generated capsule store.
*
* The matching capsule_generated.c is produced at build time by mkcapsule.
* These three arrays plus the directory header are compiled into .rodata.
*/
#ifndef STARKERNEL_CAPSULE_GENERATED_H
#define STARKERNEL_CAPSULE_GENERATED_H
#include <stdint.h>
#include "starkernel/capsule.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Mark as hidden so GCC uses direct RIP-relative addressing in PIC/PE builds,
* avoiding GOT references which do not exist in PE/COFF binaries. */
__attribute__((visibility("hidden")))
extern const uint8_t capsule_arena[];
__attribute__((visibility("hidden")))
extern const CapsuleDesc capsule_descriptors[];
__attribute__((visibility("hidden")))
extern const CapsuleNameEntry capsule_names[];
__attribute__((visibility("hidden")))
extern const CapsuleSigEntry capsule_signatures[];
__attribute__((visibility("hidden")))
extern const CapsuleDirHeader capsule_directory;
/*
* Accessor functions — defined in the same TU as the symbols above.
* PE/COFF builds with -fPIC do not convert GOTPCREL data references to
* direct LEA the way the ELF linker does, so any cross-TU access to a
* data symbol goes through GOT and reads garbage in a PE image.
* These hidden accessor functions are called via a direct CALL (no PLT/GOT)
* and access the symbols with direct RIP-relative addressing inside their TU.
*/
__attribute__((visibility("hidden")))
uint32_t capsule_get_desc_count(void);
__attribute__((visibility("hidden")))
const CapsuleDirHeader *capsule_get_directory(void);
__attribute__((visibility("hidden")))
const CapsuleDesc *capsule_get_descriptors(void);
__attribute__((visibility("hidden")))
const CapsuleNameEntry *capsule_get_names(void);
__attribute__((visibility("hidden")))
const CapsuleSigEntry *capsule_get_signatures(void);
__attribute__((visibility("hidden")))
const uint8_t *capsule_get_arena(void);
#ifdef __cplusplus
}
#endif
#endif /* STARKERNEL_CAPSULE_GENERATED_H */
+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.

Some files were not shown because too many files have changed in this diff Show More