Files
LithosAnanke/kernel/src/v4/sk_v4.c
T
rajamesandClaude Opus 5.5 fd2088255d fix(v4): overlong lines are refused whole; a refused store moves neither A nor P
- hosted (v4/tools/hosted.c): a line longer than the buffer is read to its
  end and refused as too long. Before, fgets handed it over in 1025-byte
  pieces and the last piece ran as a line of its own.
- bare metal (kernel/src/v4/sk_v4.c): the line editor takes 1024
  characters, as the boot does, and a line typed past that is refused as
  too long instead of being cut at 255 and run. V4_BOOT_LINE_TOO_LONG is
  now reported at the prompt and while a line waits.
- engine (v4/src/exec.c): a store the node itself refuses (to "any port",
  "which port", How or Have) left the opcode running on: !+ advanced A,
  against node.h's FAULTS, and !p advanced P past the handler, into the
  next fault kind's entry. A refused store now does nothing more; a store
  that raises an error (D-18) still counts as made, but P stays the
  handler's.
- test_port.c: both cases; they fail on the old exec.c (A 1017, P 901).

Engine-level tests (all v4/tests/test_*.c but test_host_*) pass at 32 and
64 bits; test_port also under ASan/UBSan. Not yet run: make -C v4 test,
test-32, hosted-check, and the three QEMU acceptance boots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
2026-11-06 00:40:48 +00:00

224 lines
9.4 KiB
C

/* sk_v4.c -- StarForth v4 on bare metal: one host node at a prompt.
*
* The node is the golden model of the F18-derived engine (v4/src). It comes
* up as the hosted v4 system does, by v4_boot_run (v4/include/v4/boot.h):
* the machine's ROM assembles the system from its source, then the capsules
* from the directory baked into this kernel are loaded, each checked and its
* parity line printed, then the prompt.
* docs/v4.0.0/NUCLEUS.md. This file is all the kernel adds:
*
* - what the node prints goes to the kernel's console;
* - the prompt, and the line: it is read and edited here and handed to the
* node whole (docs/v4.0.0/ENGINE.md 3.1), and how it ended is said here;
* - blocks are kept in memory, and are gone at power-off;
* - the identity gate: with no identity drive the system is headless,
* and a console exists only while Zuse's drive is attached and checked
* (sk_v4_identity.c; ENGINE.md 2, ruled 2026-10-20).
*
* This is the lone node, started before the kernel's fleet and beside it.
* It goes when the node is brought up in the VM's place (ENGINE.md step 4).
*
* Nothing of the v3 VM is started. docs/v4.0.0/DECOMPOSITION.md.
*/
#include "starkernel/v4/sk_v4.h"
#include "starkernel/v4/sk_v4_identity.h"
#include "starkernel/kbd_input.h"
#include "starkernel/console.h"
#include "starkernel/arch.h"
#include "starkernel/timer.h"
#include "v4/boot.h"
#include "block_subsystem.h"
#define SK_V4_LINE 1024u /* what the line editor here takes: a line, as the boot takes one (v4/system/boot.c) */
/* v3's prompt (repl.c's SK_PROMPT_TEXT): "ok> " in bright cyan, after the
* console's own "[zuse@Hera] " line prefix. */
#define SK_V4_PROMPT "\x1b[38;2;85;255;255mok> \x1b[39m"
static v4_node sk_v4_node;
static v4_exec_state sk_v4_es;
static v4_heat sk_v4_heat;
static v4_image sk_v4_image;
static char sk_v4_text[SK_V4_LINE + 1u];
/* POST's block RAM, as the v3 path has (sk_vm_bootstrap.c, sk_post_blk_ram):
* the node boots and is POSTed with these blocks, 1 to 2047 and nothing
* above them, and the kernel's real chain is set up afterwards. */
static uint8_t sk_v4_post_blk_ram[BLK_RAM_BLOCKS * BLK_FORTH_SIZE];
static void sk_v4_out(const char *text, unsigned len)
{
unsigned i;
for (i = 0; i < len; i++) console_putc(text[i]);
}
/* a character for a line that reads the keyboard, when there is one: the
* serial line or any keyboard (docs/v4.0.0/STICK.md 5.4) */
static int sk_v4_key(void)
{
int c = sk_input_getc();
return c < 0 ? V4_BOOT_KEY_NONE : c;
}
/* THE KERNEL'S WORDS for the lone node. BYE is v3's (mama_word_bye): a cold
* restart. v3's reaps Hera's children first; the lone node is started
* before there is a fleet, so there are none. The rest of the kernel's
* words come when the node is brought up in the VM's place (ENGINE.md
* step 4), where v3's own functions serve them. */
static const char *const sk_v4_words[] = { "BYE" };
static void sk_v4_serve(v4_node *node, unsigned request)
{
(void)node;
if (request == 1) {
console_println("BYE: cold restart");
arch_cold_reset();
}
}
/* Read a line from the keyboard: characters are echoed, backspace rubs one
* out, Enter ends it. Returns its length; or -1 when, with nothing typed,
* Zuse's session has ended (her drive was pulled): the caller goes headless
* (not in a build with EMERGENCY_CONSOLE_ENABLED, whose console needs no
* login: v3's sk_console_readline() the same). While nothing is typed the
* idle beat runs, as v3's. A line typed past SK_V4_LINE characters is not
* cut short and run: what is typed after that is not kept, and
* SK_V4_LINE + 1 is returned, which the boot refuses as too long
* (V4_BOOT_LINE_TOO_LONG), running nothing. */
#define SK_V4_CURSOR_BLINK 50u /* ticks between blinks, as v3's (500 ms at 100 Hz) */
static int sk_v4_readline(void)
{
int len = 0, over = 0;
int cursor_on = 1;
uint64_t blink = heartbeat_ticks();
console_fb_draw_cursor();
for (;;) {
int c = sk_input_getc();
if (c < 0) {
uint64_t now = heartbeat_ticks();
if (now - blink >= SK_V4_CURSOR_BLINK) {
blink = now;
cursor_on = !cursor_on;
if (cursor_on) console_fb_draw_cursor();
else console_fb_erase_cursor();
}
if (len == 0) {
sk_v4_identity_beat();
#if !EMERGENCY_CONSOLE_ENABLED
if (!sk_v4_identity_present()) return -1;
#endif
} else {
heartbeat_service();
}
arch_relax();
continue;
}
if (c == '\r' || c == '\n') {
console_fb_erase_cursor(); console_putc('\n');
return over ? (int)SK_V4_LINE + 1 : len;
}
if (c == 8 || c == 127) {
if (over > 0) { over--; console_putc(8); console_putc(' '); console_putc(8); }
else if (len > 0) { len--; console_putc(8); console_putc(' '); console_putc(8); }
} else if (c >= 32 && c < 127) {
if (len < (int)SK_V4_LINE) sk_v4_text[len++] = (char)c;
else over++;
console_putc((char)c);
}
cursor_on = 1; /* typing shows a solid cursor, as v3's */
blink = heartbeat_ticks();
console_fb_draw_cursor();
}
}
void sk_v4_run(void (*chain)(void), const BootDisk *boot_disk)
{
v4_boot boot;
console_println("StarForth v4: one host node, the F18-derived engine");
sk_v4_image = v4_map_image;
boot.n = &sk_v4_node; boot.es = &sk_v4_es; boot.h = &sk_v4_heat; boot.im = &sk_v4_image;
boot.out = sk_v4_out; boot.key = sk_v4_key;
boot.words = sk_v4_words; boot.word_count = sizeof sk_v4_words / sizeof sk_v4_words[0]; boot.serve = sk_v4_serve;
blk_subsys_init(sk_v4_post_blk_ram, sizeof(sk_v4_post_blk_ram));
if (!v4_boot_run(&boot)) {
console_println("StarForth v4: not started");
for (;;) { }
}
if (chain) {
/* the blocks are other blocks now: what the node has in its two
* buffers is POST's, and is let go */
chain();
if (v4_boot_line(&boot, "EMPTY-BUFFERS", 13) != V4_TEXT_COMPLETED) {
console_println("StarForth v4: the node did not let go of POST's blocks");
for (;;) { }
}
}
sk_v4_identity_boot(boot_disk);
#if EMERGENCY_CONSOLE_ENABLED
/* a console that needs no login has the screen from the start */
console_fb_mirror(1);
console_fb_enable_ttf();
#endif
/* The prompt is the host's: read a line, hand it to the node, say how it
* ended. */
for (;;) {
int len;
int how;
int logged_out = 0;
#if !EMERGENCY_CONSOLE_ENABLED
/* no identity, no console: headless until Zuse's drive is checked.
* The drives are looked at first, now: one pulled while the last
* line ran is seen before a prompt is shown (the identity gate's
* final review, item 1). Not in a build with
* EMERGENCY_CONSOLE_ENABLED, whose console needs no login (v3's
* kernel_main.c and repl.c the same; item 3). */
sk_v4_identity_check_now();
if (!sk_v4_identity_present()) sk_v4_identity_wait();
#endif
console_puts(SK_V4_PROMPT);
len = sk_v4_readline();
if (len < 0) continue;
how = v4_boot_line(&boot, sk_v4_text, (unsigned)len);
/* The line is waiting for an answer no one here can send. The next
* line typed breaks the wait: the line that was waiting ends in an
* error, and then the line just typed is done (v4/boot.h). */
while (how == V4_BOOT_LINE_WAITING) {
len = sk_v4_readline();
if (len < 0) {
/* Zuse's session ended while the line waited: its wait is
* ended now, by an empty line, and not carried into the
* next session (Captain Bob, 2026-10-26: "end the waiting
* line at logout"). How it ended is shown; the empty line
* is then done, and shows nothing. */
how = v4_boot_line(&boot, "", 0u);
if (how == V4_TEXT_ERROR) console_puts(" ERROR\n");
else if (how == V4_TEXT_COMPLETED) console_puts(" ok\n");
if (how == V4_TEXT_ERROR || how == V4_TEXT_COMPLETED) how = v4_boot_line(&boot, 0, 0);
logged_out = 1;
break;
}
how = v4_boot_line(&boot, sk_v4_text, (unsigned)len);
if (how == V4_BOOT_LINE_TOO_LONG) { console_puts("Line too long\n ERROR\n"); how = V4_BOOT_LINE_WAITING; continue; }
if (how == V4_TEXT_ERROR) console_puts(" ERROR\n" SK_V4_PROMPT);
else if (how == V4_TEXT_COMPLETED) console_puts(" ok\n" SK_V4_PROMPT);
else break;
how = v4_boot_line(&boot, 0, 0);
}
if (logged_out && (how == V4_TEXT_ERROR || how == V4_TEXT_COMPLETED)) continue;
if (how == V4_TEXT_COMPLETED) console_puts(" ok\n");
else if (how == V4_TEXT_ERROR) console_puts(" ERROR\n");
else if (how == V4_BOOT_LINE_TOO_LONG) console_puts("Line too long\n ERROR\n");
else if (how == V4_BOOT_LINE_REFUSED) console_puts("Message refused\n ERROR\n"); /* the node's wire is full: error 19's words */
else if (how == V4_BOOT_LINE_STOPPED) {
console_println("StarForth v4: the node stopped on a fault");
for (;;) { }
}
}
}