Bug-fix sweep: repl reentrancy, virtio/blocksys bounds, identity CRCs, LOG_LINE_MAX
Code review fixes, all compile clean (hosted gcc + aarch64/riscv64 kernel flags):
- repl.c (H1): reentrancy guards on the MSG-TICK idle pump. sk_repl_idle()
now defers when Hera is mid-interpret (g_mama_interpreting) or when its
own vm_interpret is on the stack (g_idle_pump_active), so a blocking
KEY/EXPECT/QUERY inside a dispatched line can no longer re-enter the
interpreter and clobber the in-flight input buffer.
- virtio_rng.c: clamp device-returned used_len to VRNG_BUF_SIZE before the
caller's data_buf copy, closing a device-controlled OOB read.
- block_subsystem.c: first-write path now keys off created_time==0 instead
of dead magic==0 so fresh blocks get a real created_time stamp; first_free/
last_allocated fixed to absolute Forth LBNs (set in blk_compute_fresh_geometry
from slot->start_lbn, no longer the wrong physical-BAM-index values from
compute_totals_from_B); physical-bounds guard on blk_meta_zone_read/write
prevents unsigned underflow on a corrupt fence >= device size.
- capsule_zuse_boot.c / capsule_wirebind.c: identity seed validated magic ->
version -> CRC-64 (compute_crc64 over offsetof(crc)) before trusting it,
so a corrupt/format-mismatched record is refused, never loaded.
- log.h / starkernel/log.h: unused LOG_LINE_MAX 256 renamed LOG_MSG_LINE_MAX
to lift the include-order collision with vm.h's LOG_LINE_MAX 64; stale
include-order comments dropped (kernel_main.c, shim.c, capsule_birth.c).
- FABRIC-3.md: three stale-doc carry-forward items closed [x] with cbe7b49
notes.
Real KEY/?TERMINAL/QUERY/EXPECT bodies (console WIP):
- repl.h/repl.c: sk_console_getkey()/sk_console_key_available()/
sk_console_readline() public bodies; non-destructive peek buffers the
found byte so a following KEY returns it.
- shim.c: getchar()/fgetc()/fgets()/sf_terminal_ready() routed through the
real console paths instead of stubs; sf_terminal_ready() in platform_io.h
with sf_terminal_ready() implemented for the hosted build (linux/io.c,
POSIX select on fd 0) wired into Makefile.
- io_words.c: ?TERMINAL now returns actual terminal-readiness, not constant 0.
Artifacts: minted disk/artemis.img + rebuilt lfs kernel; BLOCK_MAP.md,
doe csv + qemu log regenerated.
This commit is contained in:
+12
-6
@@ -70,18 +70,24 @@ decisions get added here, not to `FABRIC-2.md`. Follow the same discipline `FABR
|
||||
|
||||
### From FABRIC-2.md §J — Maintainability sweep (2026-08-18)
|
||||
|
||||
- [ ] `docs/lithosananke/ROADMAP.md` and `M7.1.md` — stale `Branch: lithosananke` (no such
|
||||
- [x] **Fixed 2026-08-26 (commit `cbe7b49`), stale carry-forward closed 2026-08-28.**
|
||||
`docs/lithosananke/ROADMAP.md` and `M7.1.md` — stale `Branch: lithosananke` (no such
|
||||
branch exists post-split), `M7.1.md`'s "Status: Design Complete" (shipped and live, not
|
||||
just designed), `ROADMAP.md`'s self-contradiction (M8 marked OBSOLETE in one place, still
|
||||
a live success criterion in another), and its stale "AHCI driver" claim for M9 (real
|
||||
implementation is `virtio_blk.c`) — not fixed, flagged.
|
||||
implementation is `virtio_blk.c`) — all corrected by `cbe7b49`; `ROADMAP.md` also gained
|
||||
corrected `BLOCK`/`BUFFER`/`UPDATE`/`FLUSH` and block-device-abstraction `[x]` marks.
|
||||
|
||||
- [ ] Top-level `ROADMAP.md` (StarForth-era, "Phase 0 Complete... Phase 1 Starting," dated
|
||||
2025-12-14) — badly stale, no historical/superseded banner to warn a reader. Not fixed.
|
||||
- [x] **Fixed 2026-08-26 (commit `cbe7b49`), stale carry-forward closed 2026-08-28.** Top-level
|
||||
`ROADMAP.md` (StarForth-era, "Phase 0 Complete... Phase 1 Starting," dated
|
||||
2025-12-14) — marked OBSOLETE by `cbe7b49` (Captain Bob's call: more than stale), pointing
|
||||
to `docs/lithosananke/ROADMAP.md` and `FABRIC*.md` for current status.
|
||||
|
||||
- [ ] `docs/03-architecture/word-acl/DESIGN.md` says ACL Phase 7 (LithosAnanke kernel parity)
|
||||
- [x] **Fixed 2026-08-26 (commit `cbe7b49`), stale carry-forward closed 2026-08-28.**
|
||||
`docs/03-architecture/word-acl/DESIGN.md` says ACL Phase 7 (LithosAnanke kernel parity)
|
||||
is still "remaining" — direct contradiction with `.claude/CLAUDE.md`, which states Phase 7
|
||||
is independently verified complete. Not fixed.
|
||||
is independently verified complete. Phase 7 restated as independently-verified complete and
|
||||
the stale lithosananke-branch-parity framing removed by `cbe7b49`.
|
||||
|
||||
- [x] Confirmed accurate, not stale (2026-08-26): `VM-FLEET-ATTRACTOR-DESIGN-20260705.md`'s
|
||||
claim that `doe-campaign.4th` is "broken and being superseded." Live-ran `SMOKE-CAMPAIGN`
|
||||
|
||||
@@ -433,7 +433,7 @@ LDFLAGS += -nostdlib
|
||||
PLATFORM_SRC = src/platform/starforth_minimal.c
|
||||
PLATFORM_ALLOC_SRC = src/platform/alloc_kernel.c
|
||||
else
|
||||
PLATFORM_TIME_SRC = src/platform/linux/time.c src/platform/platform_init.c
|
||||
PLATFORM_TIME_SRC = src/platform/linux/time.c src/platform/linux/io.c src/platform/platform_init.c
|
||||
PLATFORM_ALLOC_SRC = src/platform/alloc_host.c
|
||||
endif
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Capsule Block Manifest — Auto-generated
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-29T02:03:32Z -->
|
||||
<!-- Generated by mkcapsule --manifest 2026-08-29T02:41:11Z -->
|
||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||
<!-- Hand-written justifications and immutability notes live -->
|
||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||
|
||||
Binary file not shown.
+7
-2
@@ -44,8 +44,13 @@
|
||||
#define LOG_H
|
||||
|
||||
/* Maximum log line size for persistent logging */
|
||||
#ifndef LOG_LINE_MAX
|
||||
#define LOG_LINE_MAX 256
|
||||
/* Renamed from LOG_LINE_MAX: vm.h owns that name for the persistent
|
||||
* block-log line width (64, unrelated concept). This is the in-memory log
|
||||
* message-formatting line length; keeping a distinct name removes the
|
||||
* include-order collision that forced a fragile "vm.h before log.h"
|
||||
* convention across the kernel (kernel_main.c/shim.c/capsule_birth.c). */
|
||||
#ifndef LOG_MSG_LINE_MAX
|
||||
#define LOG_MSG_LINE_MAX 256
|
||||
#endif
|
||||
|
||||
/* Forward declaration */
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
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.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
* platform_io.h - Portable terminal-input-readiness check for StarForth
|
||||
*
|
||||
* One function, same shape as platform_lock.h/platform_time.h: a single
|
||||
* portable declaration, implemented once per platform (POSIX select() on
|
||||
* fd 0 for the hosted build, src/platform/linux/io.c; the kernel's own
|
||||
* console/keyboard-event bridge for the freestanding build, shim.c).
|
||||
* Backs the standard dictionary's ?TERMINAL word (io_words.c).
|
||||
*/
|
||||
|
||||
#ifndef STARFORTH_PLATFORM_IO_H
|
||||
#define STARFORTH_PLATFORM_IO_H
|
||||
|
||||
/**
|
||||
* @brief Non-blocking check for whether a key/character is available to read.
|
||||
*
|
||||
* Must not block and must not consume the byte if one is found (a
|
||||
* following KEY/getchar() must still return that same byte).
|
||||
*
|
||||
* @return 1 if input is ready, 0 otherwise
|
||||
*/
|
||||
int sf_terminal_ready(void);
|
||||
|
||||
#endif /* STARFORTH_PLATFORM_IO_H */
|
||||
@@ -29,8 +29,13 @@
|
||||
/* Forward declaration — matches hosted log.h */
|
||||
struct VM;
|
||||
|
||||
#ifndef LOG_LINE_MAX
|
||||
#define LOG_LINE_MAX 256
|
||||
/* Renamed from LOG_LINE_MAX: vm.h owns that name for the persistent
|
||||
* block-log line width (64, unrelated concept). This is the in-memory log
|
||||
* message-formatting line length; keeping a distinct name removes the
|
||||
* include-order collision that forced a fragile "vm.h before log.h"
|
||||
* convention across the kernel. */
|
||||
#ifndef LOG_MSG_LINE_MAX
|
||||
#define LOG_MSG_LINE_MAX 256
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
@@ -89,6 +89,44 @@ const homeblocks_sig_t *sk_repl_get_homeblocks_sig(void);
|
||||
*/
|
||||
struct blkio_dev *sk_repl_get_attached_blk_dev(void);
|
||||
|
||||
/**
|
||||
* sk_console_getkey - Real body of the standard dictionary's KEY word
|
||||
* (called from shim.c's getchar()). Blocks until a key is available from
|
||||
* either input source (serial console or the PS2/virtio keyboard-event
|
||||
* bridge), servicing the heartbeat/idle loop while waiting so a KEY call
|
||||
* from inside any word never stalls the heartbeat. No echo -- that's the
|
||||
* caller's responsibility, same as any standard KEY.
|
||||
*
|
||||
* @param active_vm VM whose idle dispatch runs while waiting (see
|
||||
* sk_repl_idle()'s own doc comment on why this is a
|
||||
* parameter rather than read via sk_repl_get_active_vm())
|
||||
* @return the key read, as an unsigned byte value
|
||||
*/
|
||||
int sk_console_getkey(VM *active_vm);
|
||||
|
||||
/**
|
||||
* sk_console_key_available - Real body of the standard dictionary's
|
||||
* ?TERMINAL word (called from sf_terminal_ready()). Non-blocking peek:
|
||||
* returns 1 if a key is ready without consuming it (a following
|
||||
* sk_console_getkey() returns that exact key), 0 otherwise.
|
||||
*/
|
||||
int sk_console_key_available(void);
|
||||
|
||||
/**
|
||||
* sk_console_readline - Real body of the standard dictionary's
|
||||
* QUERY/EXPECT words (called from shim.c's fgets()). Reads one line from
|
||||
* the console with echo and backspace support, servicing the heartbeat/
|
||||
* idle loop while waiting -- the same line editor the REPL's own prompt
|
||||
* uses internally, so a mid-word EXPECT behaves identically to typing at
|
||||
* "ok>" itself.
|
||||
*
|
||||
* @param buf Destination buffer
|
||||
* @param size Buffer capacity, including the NUL terminator
|
||||
* @param active_vm VM whose idle dispatch runs while waiting
|
||||
* @return number of characters placed in buf, not counting the NUL
|
||||
*/
|
||||
int sk_console_readline(char *buf, int size, VM *active_vm);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
+32
-5
@@ -367,10 +367,13 @@ static void compute_totals_from_B(blk_volume_meta_t *m) {
|
||||
uint64_t payload4k = (m->total_devblocks > (1+B+R+F)) ? (m->total_devblocks - 1 - B - R - F) : 0;
|
||||
uint64_t storable = 3ULL * payload4k;
|
||||
m->total_blocks = (m->tracked_blocks < storable) ? m->tracked_blocks : storable;
|
||||
uint64_t reserved = (uint64_t) BLK_DISK_SYS_RESERVED;
|
||||
if (reserved > m->total_blocks) reserved = m->total_blocks;
|
||||
m->first_free = BLK_DISK_SYS_RESERVED + reserved;
|
||||
m->last_allocated = BLK_DISK_SYS_RESERVED + reserved - 1;
|
||||
/* NOTE: first_free/last_allocated are intentionally NOT set here. They
|
||||
* are absolute Forth LBN hints (readers in blk_allocate()/blk_mark_free()
|
||||
* subtract slot->start_lbn), which this pure-geometry function cannot
|
||||
* know -- it has no slot/LBN-base context. Setting them to a physical
|
||||
* BAM index here made the first-free hint point at the wrong block; the
|
||||
* slot's owner establishes them from slot->start_lbn instead (see
|
||||
* blk_compute_fresh_geometry()). */
|
||||
}
|
||||
|
||||
/* ===== physical BAM I/O (sync to/from slot->bam[]) ===== */
|
||||
@@ -530,6 +533,14 @@ static void blk_compute_fresh_geometry(blk_dev_slot_t *slot) {
|
||||
slot->vol_meta.meta_fence_blocks = BLK_META_FENCE_INIT;
|
||||
compute_totals_from_B(&slot->vol_meta);
|
||||
|
||||
/* Allocation hints are absolute Forth LBNs (see the readers' note at
|
||||
* compute_totals_from_B()). This slot's first user-visible block is at
|
||||
* slot->start_lbn, so that is the true "next free" hint on a fresh
|
||||
* volume -- anything else (e.g. a physical BAM index) made the hint
|
||||
* point into the wrong block and left blk_allocate() guessing. */
|
||||
slot->vol_meta.first_free = (uint64_t) slot->start_lbn;
|
||||
slot->vol_meta.last_allocated = (uint64_t) slot->start_lbn - 1u;
|
||||
|
||||
if (sf_has_rtc()) slot->vol_meta.created_time = sf_realtime_ns();
|
||||
else slot->vol_meta.created_time = sf_monotonic_ns();
|
||||
|
||||
@@ -941,7 +952,14 @@ int blk_update(uint32_t block_num) {
|
||||
uint8_t *blkdata = c->data + pack * BLK_FORTH_SIZE;
|
||||
c->meta[pack].checksum = compute_crc64(blkdata, BLK_FORTH_SIZE);
|
||||
uint64_t now = blk_get_timestamp();
|
||||
if (c->meta[pack].magic == 0) {
|
||||
/* cache_load_devblock() already force-stamps magic on every load, so
|
||||
* testing magic==0 here is dead code that silently dropped the
|
||||
* first-write created_time stamp. A block that has never been written
|
||||
* before has created_time==0 (the loaded slot was memset); an existing
|
||||
* block carries its stamped-on-first-write non-zero created_time. Use
|
||||
* that as the first-write signal so a fresh block records its true
|
||||
* creation time. */
|
||||
if (c->meta[pack].created_time == 0) {
|
||||
c->meta[pack].magic = 0x424C4B5F5354524BULL;
|
||||
c->meta[pack].created_time = now;
|
||||
}
|
||||
@@ -1135,6 +1153,11 @@ int blk_meta_zone_read(uint32_t devblock_from_top, uint8_t buf[4096]) {
|
||||
blk_dev_slot_t *slot = first_disk_slot();
|
||||
if (!slot || !slot->dev) return BLK_ENODEV;
|
||||
if (devblock_from_top >= slot->vol_meta.meta_fence_blocks) return BLK_EINVAL;
|
||||
/* The fence index must also sit inside the physical device -- a corrupt
|
||||
* volume whose meta_fence_blocks >= total_devblocks would otherwise pass
|
||||
* the fence check and let (total_devblocks - 1 - devblock_from_top)
|
||||
* underflow on the unsigned subtraction below, targeting a wild devblock. */
|
||||
if ((uint64_t) devblock_from_top >= slot->vol_meta.total_devblocks) return BLK_EINVAL;
|
||||
|
||||
uint64_t devblock_idx = slot->vol_meta.total_devblocks - 1ULL - devblock_from_top;
|
||||
uint32_t base1k = (uint32_t) devblock_idx * 4u;
|
||||
@@ -1150,6 +1173,10 @@ int blk_meta_zone_write(uint32_t devblock_from_top, const uint8_t buf[4096]) {
|
||||
blk_dev_slot_t *slot = first_disk_slot();
|
||||
if (!slot || !slot->dev) return BLK_ENODEV;
|
||||
if (devblock_from_top >= slot->vol_meta.meta_fence_blocks) return BLK_EINVAL;
|
||||
/* Same physical-bounds guard as blk_meta_zone_read(): prevents the
|
||||
* unsigned underflow of (total_devblocks - 1 - devblock_from_top) on a
|
||||
* corrupt fence >= device size. */
|
||||
if ((uint64_t) devblock_from_top >= slot->vol_meta.total_devblocks) return BLK_EINVAL;
|
||||
|
||||
uint64_t devblock_idx = slot->vol_meta.total_devblocks - 1ULL - devblock_from_top;
|
||||
uint32_t base1k = (uint32_t) devblock_idx * 4u;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
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 Platform IO - Linux/POSIX Backend
|
||||
*
|
||||
* Implements platform_io.h's terminal-readiness check using select() on
|
||||
* fd 0, the standard POSIX non-blocking-stdin-peek idiom. Zero timeout:
|
||||
* a single poll, never blocks.
|
||||
*
|
||||
* License: See LICENSE file
|
||||
*/
|
||||
|
||||
#include "../../../include/platform_io.h"
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int sf_terminal_ready(void) {
|
||||
fd_set readfds;
|
||||
struct timeval tv;
|
||||
|
||||
FD_ZERO(&readfds);
|
||||
FD_SET(STDIN_FILENO, &readfds);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 0;
|
||||
|
||||
int r = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
|
||||
return (r > 0 && FD_ISSET(STDIN_FILENO, &readfds)) ? 1 : 0;
|
||||
}
|
||||
@@ -36,10 +36,11 @@
|
||||
#include "starkernel/vm/stadium.h" /* item 4.1a -- stadium_grant_quota() */
|
||||
#include "vm.h"
|
||||
#include "platform_alloc.h"
|
||||
/* log.h after vm.h: vm.h's own LOG_LINE_MAX (persistent block-log,
|
||||
* unrelated concept, unconditional #define) must win before log.h's
|
||||
* #ifndef-guarded one sees it -- reversed order redefines and fails
|
||||
* -Werror (found 2026-08-26 wiring in capsule signature logging). */
|
||||
/* No LOG_LINE_MAX include-order constraint anymore: vm.h's own
|
||||
* LOG_LINE_MAX (persistent block-log, 64) and log.h's in-memory line
|
||||
* length (renamed LOG_MSG_LINE_MAX, 256) are distinct names, so include
|
||||
* order no longer redefines anything (the -Werror collision found
|
||||
* 2026-08-26 wiring capsule signature logging is structurally gone). */
|
||||
#include "log.h"
|
||||
|
||||
/*===========================================================================
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include "starkernel/user_identity_seed.h"
|
||||
#include "starkernel/console.h"
|
||||
#include "blkio.h"
|
||||
#include "block_subsystem.h" /* compute_crc64() */
|
||||
#include "freestanding/stdio.h"
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/* WIREBIND_CERT_MAX_DEVBLOCKS: a sane upper bound on how much cert
|
||||
* content this reads, independent of whatever sig->cert_devblocks
|
||||
@@ -97,11 +99,24 @@ void capsule_wirebind_try_attach(struct blkio_dev *dev,
|
||||
return;
|
||||
}
|
||||
user_identity_seed_t idrec;
|
||||
if (read_devblock(dev, sig->identity_src_offset, (uint8_t *)&idrec) != 0 ||
|
||||
idrec.magic != USER_IDENTITY_SEED_MAGIC) {
|
||||
if (read_devblock(dev, sig->identity_src_offset, (uint8_t *)&idrec) != 0) {
|
||||
console_println("WIREBIND: verified cert but identity record unreadable -- refusing");
|
||||
return;
|
||||
}
|
||||
/* Same magic -> version -> CRC-64 discipline as capsule_zuse_boot.c:
|
||||
* the identity record is the same on-disk format, so a corrupt or
|
||||
* format-mismatched record must be refused rather than trusted. */
|
||||
if (idrec.magic != USER_IDENTITY_SEED_MAGIC ||
|
||||
idrec.version != USER_IDENTITY_SEED_VERSION) {
|
||||
console_println("WIREBIND: verified cert but identity record unreadable -- refusing");
|
||||
return;
|
||||
}
|
||||
uint64_t want_crc = compute_crc64((const uint8_t *)&idrec,
|
||||
offsetof(user_identity_seed_t, crc));
|
||||
if (want_crc != idrec.crc) {
|
||||
console_println("WIREBIND: verified cert but identity record corrupt -- refusing");
|
||||
return;
|
||||
}
|
||||
|
||||
char username[USER_IDENTITY_USERNAME_MAX];
|
||||
memcpy(username, idrec.username, sizeof(username));
|
||||
|
||||
@@ -103,7 +103,15 @@ void capsule_zuse_boot_try_attach(struct blkio_dev *dev,
|
||||
|
||||
user_identity_seed_t idrec;
|
||||
if (read_devblock(dev, sig->identity_src_offset, (uint8_t *)&idrec) != 0) return;
|
||||
/* Same magic -> version -> CRC-64 discipline as genesis_marker_read():
|
||||
* this record carries Zuse's private key (the seed), so a corrupt or
|
||||
* format-mismatched record must be refused, never loaded -- a bad CRC
|
||||
* could otherwise install a garbage seed as Zuse's identity. */
|
||||
if (idrec.magic != USER_IDENTITY_SEED_MAGIC) return;
|
||||
if (idrec.version != USER_IDENTITY_SEED_VERSION) return;
|
||||
uint64_t want_crc = compute_crc64((const uint8_t *)&idrec,
|
||||
offsetof(user_identity_seed_t, crc));
|
||||
if (want_crc != idrec.crc) return;
|
||||
if (memcmp(idrec.pubkey, marker.zuse_pubkey, 32) != 0) return; /* not Zuse's drive */
|
||||
|
||||
console_println("Zuse: identity confirmed from attached thumbdrive");
|
||||
|
||||
@@ -70,8 +70,10 @@ EFI_RUNTIME_SERVICES *g_sk_runtime_services = NULL;
|
||||
#include "starkernel/xhci_driver.h"
|
||||
#include "block_subsystem.h"
|
||||
#include "vm.h" /* DictEntry, vm_find_word, ACL_MODE_STRICT */
|
||||
#include "log.h" /* must follow vm.h: vm.h's LOG_LINE_MAX has no
|
||||
include guard, log.h's does */
|
||||
#include "log.h" /* no include-order constraint anymore: vm.h's
|
||||
LOG_LINE_MAX (persistent block-log, 64) and
|
||||
log.h's line length (LOG_MSG_LINE_MAX, 256)
|
||||
are distinct names */
|
||||
#include "version.h"
|
||||
#endif
|
||||
|
||||
|
||||
+131
-14
@@ -106,7 +106,7 @@ blkio_dev_t *sk_repl_get_attached_blk_dev(void) {
|
||||
/*===========================================================================
|
||||
* Idle heartbeat service
|
||||
*
|
||||
* Called from sk_readline when heartbeat_ticks() has advanced by at least
|
||||
* Called from sk_console_readline()/sk_console_getkey() when heartbeat_ticks() has advanced by at least
|
||||
* SK_IDLE_BEAT_INTERVAL since the last service call. Extend this function
|
||||
* as higher-level subsystems (msg_fabric, capsule scheduler) come online.
|
||||
*
|
||||
@@ -118,6 +118,27 @@ blkio_dev_t *sk_repl_get_attached_blk_dev(void) {
|
||||
|
||||
static uint64_t g_last_beat_tick; /* zero-initialized (BSS) */
|
||||
|
||||
/* Reentrancy guards for the MSG-TICK pump inside sk_repl_idle().
|
||||
*
|
||||
* sk_repl_idle() runs vm_interpret(mama, ...) (below) to VM-EXEC MSG-TICK
|
||||
* into every live child VM (FABRIC-3.md Phase C). But sk_repl_idle() is
|
||||
* itself called from the blocking KEY/EXPECT/QUERY reads (sk_console_getkey()
|
||||
* / sk_console_readline(), which run *from inside* the executing VM's own
|
||||
* vm_interpret once a FORTH word reads input mid-line). vm_interpret() is
|
||||
* not reentrant -- it resets the VM's single input_buffer/input_pos/
|
||||
* input_length (vm_core.c) on entry. Calling vm_interpret(mama, ...) at that
|
||||
* point would re-enter mama's interpreter mid-parse and silently truncate
|
||||
* the rest of the line; if the pump's own MSG-TICK work then triggers another
|
||||
* blocking read, it would also recurse unboundedly. Two flags keep the pump
|
||||
* off every path that could re-enter an interpreter:
|
||||
* - g_mama_interpreting: set while Hera is executing a dispatched line, so
|
||||
* the pump defers to the next safe (prompt) boundary.
|
||||
* - g_idle_pump_active: belt-and-suspenders; stops recursive re-entry
|
||||
* from inside the pump's own vm_interpret call.
|
||||
*/
|
||||
static int g_mama_interpreting; /* zero-initialized (BSS) */
|
||||
static int g_idle_pump_active; /* zero-initialized (BSS) */
|
||||
|
||||
static void sk_repl_idle(VM *active_vm)
|
||||
{
|
||||
/* Artemis Milestone 2d: xHCI Event Ring servicing. This is exactly the
|
||||
@@ -247,7 +268,7 @@ static void sk_repl_idle(VM *active_vm)
|
||||
* unless something genuinely needs writing -- so no separate
|
||||
* "is anything dirty" pre-check is needed here.
|
||||
*
|
||||
* active_vm is passed in by the caller (sk_readline(), itself passed
|
||||
* active_vm is passed in by the caller (sk_console_readline(), itself passed
|
||||
* through from sk_repl_run()/sk_repl_step()'s own already-resolved
|
||||
* VM) rather than read via sk_repl_get_active_vm() here -- that
|
||||
* accessor returns NULL whenever Tripod's USE word hasn't redirected
|
||||
@@ -270,9 +291,21 @@ static void sk_repl_idle(VM *active_vm)
|
||||
* absent there, not just untried. She is the only VM with a
|
||||
* persistent idle tick, so she walks the registry once per idle beat
|
||||
* and VM-EXECs MSG-TICK into every OTHER live VM's own dictionary. */
|
||||
VM *mama = (VM *)sk_get_mama_vm();
|
||||
|
||||
/* Reentrancy guard: never run the pump while mama is mid-interpret
|
||||
* (a dispatched line, or recursively from within the pump's own
|
||||
* vm_interpret). vm_interpret() clobbers the VM's single input
|
||||
* buffer, so re-entering it here while KEY/EXPECT/QUERY blocks inside
|
||||
* a live parse truncates the rest of that line. Deferring the MSG-TICK
|
||||
* drain to the next prompt boundary is safe -- draining is best-effort
|
||||
* and simply resumes next beat. */
|
||||
if (g_mama_interpreting || g_idle_pump_active)
|
||||
return;
|
||||
|
||||
g_idle_pump_active = 1;
|
||||
{
|
||||
VM *mama = (VM *)sk_get_mama_vm();
|
||||
uint32_t count = capsule_vm_registry_count();
|
||||
uint32_t count = capsule_vm_registry_count();
|
||||
uint32_t i;
|
||||
for (i = 0; i < count; i++) {
|
||||
VMRegistryEntry ent;
|
||||
@@ -295,6 +328,7 @@ static void sk_repl_idle(VM *active_vm)
|
||||
vm_interpret(mama, cmd);
|
||||
}
|
||||
}
|
||||
g_idle_pump_active = 0;
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
@@ -303,7 +337,7 @@ static void sk_repl_idle(VM *active_vm)
|
||||
* Translates sk_key_event_poll()'s converged Linux-keycode-namespace
|
||||
* stream (keyboard_words.c -- one implementation shared with KEY-EVENT,
|
||||
* live-verified on all three architectures per item 4.3.5f) into the same
|
||||
* byte stream sk_readline() already reads from console_getc(): -1 for
|
||||
* byte stream sk_console_readline() already reads from console_getc(): -1 for
|
||||
* "nothing ready", else a raw ASCII byte with '\n'/0x7F meaning the same
|
||||
* thing they mean for the serial path below.
|
||||
*
|
||||
@@ -393,16 +427,80 @@ static int sk_kbd_getc(void)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* One raw byte from either input source (serial console or the keyboard-
|
||||
* event bridge), non-blocking, -1 if neither has one ready right now. Not
|
||||
* itself a FORTH word -- the shared byte-fetch underneath sk_console_getkey()/
|
||||
* sk_console_key_available() (the standard dictionary's KEY/?TERMINAL, wired
|
||||
* through shim.c's getchar()) and sk_console_readline() (QUERY/EXPECT, wired
|
||||
* through shim.c's fgets()) alike. */
|
||||
static int sk_console_getc_raw(void)
|
||||
{
|
||||
int c = console_getc();
|
||||
if (c < 0) c = sk_kbd_getc(); /* FABRIC.md 4.4v: second source, same buffer */
|
||||
return c;
|
||||
}
|
||||
|
||||
/* One-byte pushback so sk_console_key_available() can peek without losing
|
||||
* the byte -- ?TERMINAL must be non-destructive (a caller checks readiness,
|
||||
* then still expects KEY to return that same key). */
|
||||
static int g_console_pending_key = -1;
|
||||
|
||||
/* KEY's real body (shim.c's getchar() calls this): blocks until a key is
|
||||
* available, servicing the heartbeat/idle loop while waiting -- same
|
||||
* cadence sk_console_readline() already uses below, so a KEY call mid-word
|
||||
* never stalls the heartbeat or Hera's own idle dispatch. No echo -- that's
|
||||
* the caller's job, same as any standard KEY implementation. */
|
||||
int sk_console_getkey(VM *active_vm)
|
||||
{
|
||||
for (;;) {
|
||||
int c;
|
||||
if (g_console_pending_key >= 0) {
|
||||
c = g_console_pending_key;
|
||||
g_console_pending_key = -1;
|
||||
} else {
|
||||
c = sk_console_getc_raw();
|
||||
}
|
||||
if (c >= 0) return c;
|
||||
|
||||
heartbeat_service();
|
||||
uint64_t now = heartbeat_ticks();
|
||||
if (now - g_last_beat_tick >= SK_IDLE_BEAT_INTERVAL) {
|
||||
g_last_beat_tick = now;
|
||||
sk_repl_idle(active_vm);
|
||||
}
|
||||
arch_relax();
|
||||
}
|
||||
}
|
||||
|
||||
/* ?TERMINAL's real body (sf_terminal_ready(), shim.c): non-blocking peek --
|
||||
* a single poll, no idle-servicing loop (a false result must return
|
||||
* immediately, not block). Buffers a found byte in g_console_pending_key so
|
||||
* a following sk_console_getkey() returns the exact same key, not a
|
||||
* different/later one. */
|
||||
int sk_console_key_available(void)
|
||||
{
|
||||
if (g_console_pending_key >= 0) return 1;
|
||||
int c = sk_console_getc_raw();
|
||||
if (c >= 0) { g_console_pending_key = c; return 1; }
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
* sk_readline - line read from serial console with echo
|
||||
* sk_console_readline - line read from serial console with echo
|
||||
*
|
||||
* Non-blocking poll of console_getc(). While no character is ready the idle
|
||||
* spin services the adaptive heartbeat at SK_IDLE_BEAT_INTERVAL tick cadence.
|
||||
* Supports backspace (0x7F and \b) and ignores other control characters.
|
||||
* Returns the number of characters placed in buf (not counting '\0').
|
||||
*
|
||||
* Public (declared in repl.h): shim.c's fgets()/QUERY's own real body call
|
||||
* this directly -- same line-editing behavior for a mid-word EXPECT/QUERY as
|
||||
* for the REPL's own top-level prompt, since it's the same underlying
|
||||
* console. Any g_console_pending_key left over from a ?TERMINAL peek is
|
||||
* consumed first so a line read never drops a byte ?TERMINAL already saw.
|
||||
*===========================================================================*/
|
||||
|
||||
static int sk_readline(char *buf, int size, VM *active_vm)
|
||||
int sk_console_readline(char *buf, int size, VM *active_vm)
|
||||
{
|
||||
int n = 0;
|
||||
|
||||
@@ -410,8 +508,13 @@ static int sk_readline(char *buf, int size, VM *active_vm)
|
||||
console_fb_draw_cursor(); /* show the cursor at the bare prompt, before any input */
|
||||
|
||||
for (;;) {
|
||||
int c = console_getc(); /* non-blocking poll */
|
||||
if (c < 0) c = sk_kbd_getc(); /* FABRIC.md 4.4v: second source, same buffer */
|
||||
int c;
|
||||
if (g_console_pending_key >= 0) {
|
||||
c = g_console_pending_key;
|
||||
g_console_pending_key = -1;
|
||||
} else {
|
||||
c = sk_console_getc_raw();
|
||||
}
|
||||
|
||||
if (c < 0) {
|
||||
/* Service the heartbeat bottom half every idle iteration, not
|
||||
@@ -483,7 +586,7 @@ static int sk_readline(char *buf, int size, VM *active_vm)
|
||||
* that's a distinct, narrower mechanism this REPL no longer touches.
|
||||
*
|
||||
* Mirrors vm_repl() from src/repl.c:
|
||||
* - Reads a line via sk_readline (non-blocking, heartbeat-serviced)
|
||||
* - Reads a line via sk_console_readline (non-blocking, heartbeat-serviced)
|
||||
* - Calls vm_interpret
|
||||
* - Prints " ok" or " ERROR"
|
||||
* - When EMERGENCY_CONSOLE_ENABLED=1: resets vm->error and loops (recovery)
|
||||
@@ -536,9 +639,20 @@ static int sk_repl_line_calls_use(const char *input)
|
||||
|
||||
static void sk_repl_dispatch_line(VM *vm, const char *input)
|
||||
{
|
||||
/* H1 reentrancy guard: while this dispatched line executes on Hera
|
||||
* herself, sk_repl_idle() must defer its MSG-TICK pump -- calling
|
||||
* vm_interpret(mama, ...) from inside a mid-line KEY/EXPECT would
|
||||
* re-enter mama's interpreter and clobber its in-flight input buffer
|
||||
* (see the guard's comment at sk_repl_idle()). A child VM's console
|
||||
* turn leaves mama idle, so the pump stays safe there and the guard is
|
||||
* only latched for Hera. */
|
||||
int on_mama = (vm == (VM *)sk_get_mama_vm());
|
||||
int saved = g_mama_interpreting;
|
||||
if (on_mama) g_mama_interpreting = 1;
|
||||
|
||||
if (sk_repl_line_calls_use(input)) {
|
||||
vm_interpret(vm, input);
|
||||
return;
|
||||
goto out;
|
||||
}
|
||||
const char *vn = console_get_vm_name();
|
||||
if (vn) {
|
||||
@@ -564,13 +678,16 @@ static void sk_repl_dispatch_line(VM *vm, const char *input)
|
||||
"CONSOLE-CMD-EVENT 0 3 S\" %s\" 0 MSG-SEND", input);
|
||||
if (n > 0 && (size_t)n < sizeof(cmd)) {
|
||||
vm_interpret(vm, cmd);
|
||||
return;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vm_interpret(vm, input);
|
||||
|
||||
out:
|
||||
g_mama_interpreting = saved;
|
||||
}
|
||||
|
||||
/*===========================================================================
|
||||
@@ -603,7 +720,7 @@ int sk_repl_step(VM *vm)
|
||||
console_puts(SK_PROMPT_TEXT);
|
||||
}
|
||||
|
||||
sk_readline(input, sizeof(input), vm);
|
||||
sk_console_readline(input, sizeof(input), vm);
|
||||
|
||||
if (input[0] == '\0') {
|
||||
console_puts(" ok\n");
|
||||
@@ -646,7 +763,7 @@ void sk_repl_run(VM *vm)
|
||||
* step()'s matching comment above. */
|
||||
console_puts(SK_PROMPT_TEXT);
|
||||
|
||||
sk_readline(input, sizeof(input), active);
|
||||
sk_console_readline(input, sizeof(input), active);
|
||||
|
||||
if (input[0] == '\0') {
|
||||
console_puts(" ok\n");
|
||||
|
||||
@@ -216,7 +216,13 @@ static int vrng_request(uint32_t *bytes_out) {
|
||||
}
|
||||
|
||||
uint32_t used_slot = (uint32_t)s->last_used_idx & (VQUEUE_SIZE - 1u);
|
||||
*bytes_out = s->used->ring[used_slot].len;
|
||||
/* The device-controlled used length is trusted after this point:
|
||||
* data_buf is only VRNG_BUF_SIZE bytes, so clamp anything larger to
|
||||
* the buffer size to keep the caller's later data_buf copy in-bounds
|
||||
* against a buggy or malicious device. */
|
||||
uint32_t used_len = s->used->ring[used_slot].len;
|
||||
if (used_len > VRNG_BUF_SIZE) used_len = VRNG_BUF_SIZE;
|
||||
*bytes_out = used_len;
|
||||
s->last_used_idx = s->used->idx;
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -58,10 +58,17 @@
|
||||
#endif
|
||||
#include "platform_time.h"
|
||||
#include "platform_lock.h"
|
||||
/* No LOG_LINE_MAX include-order constraint anymore: vm.h's own
|
||||
* LOG_LINE_MAX (persistent block-log line size, 64) and log.h's in-memory
|
||||
* message line length (renamed LOG_MSG_LINE_MAX, 256) no longer share a
|
||||
* name, so include order is irrelevant here. */
|
||||
#include "starkernel/repl.h"
|
||||
#include "starkernel/vm/bootstrap/sk_vm_bootstrap.h"
|
||||
#include "log.h"
|
||||
#include "vm_host.h"
|
||||
#include "console.h"
|
||||
#include "kmalloc.h"
|
||||
#include "platform_io.h"
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
@@ -1126,12 +1133,39 @@ void rewind(FILE *stream) { (void)stream; }
|
||||
int fscanf(FILE *stream, const char *fmt, ...) { (void)stream; (void)fmt; return -1; }
|
||||
/** @brief Kernel @c sscanf(): always returns -1 — not implemented in shim. */
|
||||
int sscanf(const char *str, const char *fmt, ...) { (void)str; (void)fmt; return -1; }
|
||||
/** @brief Kernel @c fgets(): always returns @c NULL — no filesystem in kernel. */
|
||||
char *fgets(char *s, int size, FILE *stream) { (void)s; (void)size; (void)stream; return NULL; }
|
||||
/* fgets()/getchar()'s real target: whichever VM the console is currently
|
||||
* addressing (Tripod's USE redirect), or Mama when nothing is redirected --
|
||||
* the same fallback sk_repl_run()/sk_repl_step() themselves use, since
|
||||
* neither fgets() nor getchar() has a VM* of its own to work with (unlike
|
||||
* a FORTH primitive, which always does). */
|
||||
static VM *shim_console_vm(void) {
|
||||
VM *active = sk_repl_get_active_vm();
|
||||
return active ? active : (VM *)sk_get_mama_vm();
|
||||
}
|
||||
|
||||
/** @brief Kernel @c fgets(): real body -- QUERY/EXPECT's underlying line
|
||||
* read, routed through sk_console_readline() (the same echo/backspace
|
||||
* line editor the REPL's own prompt uses). @p stream is ignored: the
|
||||
* kernel has exactly one input source, the attached console, regardless
|
||||
* of which stdio handle a caller passes. Returns NULL only if @p s is
|
||||
* NULL or @p size is non-positive, matching glibc's own fgets() contract;
|
||||
* an empty line (bare Enter) still returns @p s with @p s[0] == '\0',
|
||||
* same as glibc. */
|
||||
char *fgets(char *s, int size, FILE *stream) {
|
||||
(void)stream;
|
||||
if (!s || size <= 0) return NULL;
|
||||
sk_console_readline(s, size, shim_console_vm());
|
||||
return s;
|
||||
}
|
||||
/** @brief Kernel @c fputc(): ignores stream; emits @p c to kernel console. */
|
||||
int fputc(int c, FILE *stream) { (void)stream; console_putc((char)c); return c; }
|
||||
/** @brief Kernel @c fgetc(): always returns -1 (EOF) — no filesystem in kernel. */
|
||||
int fgetc(FILE *stream) { (void)stream; return -1; }
|
||||
/** @brief Kernel @c fgetc(): real body -- same one input source as
|
||||
* @c getchar(), @p stream ignored (see @c fgets() above). Forward-declared
|
||||
* here since @c getchar() itself isn't defined until further down this
|
||||
* file and shim.c has no shared stdio.h to declare it earlier (its own
|
||||
* @c FILE typedef, above, would collide with freestanding/stdio.h's). */
|
||||
int getchar(void);
|
||||
int fgetc(FILE *stream) { (void)stream; return getchar(); }
|
||||
|
||||
/**
|
||||
* @brief Kernel @c __isoc99_sscanf() stub (ISO C99 internal sscanf symbol).
|
||||
@@ -1207,14 +1241,22 @@ const unsigned short ** __ctype_b_loc(void) {
|
||||
return &p;
|
||||
}
|
||||
|
||||
/** @brief Kernel @c getchar(): always returns -1 (EOF) — no stdin in kernel. */
|
||||
int getchar(void) { return -1; }
|
||||
/** @brief Kernel @c getchar(): real body -- KEY's underlying single-key
|
||||
* read, routed through sk_console_getkey() (blocks with heartbeat/idle
|
||||
* servicing, no echo). See fgets()'s own doc comment on shim_console_vm(). */
|
||||
int getchar(void) { return sk_console_getkey(shim_console_vm()); }
|
||||
/** @brief Kernel @c getc(): ignores stream, same as @c getchar(). GCC's -O2
|
||||
* folds @c getchar() call sites into @c getc(stdin) (FABRIC.md item 4.5d) --
|
||||
* this symbol was never needed at -O0 because that fold pass is inactive
|
||||
* there. */
|
||||
int getc(FILE *stream) { (void)stream; return getchar(); }
|
||||
|
||||
/** @brief Kernel @c sf_terminal_ready(): real body of the standard
|
||||
* dictionary's ?TERMINAL word (platform_io.h) -- non-blocking peek via
|
||||
* sk_console_key_available(), which buffers any found byte so a
|
||||
* following KEY/getchar() still returns it. */
|
||||
int sf_terminal_ready(void) { return sk_console_key_available(); }
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* Misc platform stubs
|
||||
* ---------------------------------------------------------------------------*/
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include "include/io_words.h"
|
||||
#include "../../include/word_registry.h"
|
||||
#include "../../include/log.h"
|
||||
#include "../../include/platform_io.h"
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
@@ -104,8 +105,7 @@ static void io_word_question_terminal(VM *vm) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Simple implementation - always return false for now */
|
||||
vm->data_stack[++vm->dsp] = 0;
|
||||
vm->data_stack[++vm->dsp] = sf_terminal_ready() ? -1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user