capsules/v4/post79.4th: 537 of v3's POST cases for the FORTH-79 Required Word Set, each carrying what the hosted v3 binary did with the same line (error or not, the stack, the length and checksum of what it printed). Written by v4/tools/mkpost.py. The harness is FORTH-79 plus the two nucleus hooks. docs/v4.0.0/NUCLEUS.md section 6. - NODE-ERROR has a FORTH name: how a definition in FORTH raises an error - the boot passes POST only on seeing its tally line with fail=0 - POST is not in the boot yet (V4_POST_AT_BOOT=0); make -C v4 post runs it Result: tests=537 pass=439 fail=98. The 98 are not yet sorted into v4 defects and differences needing a ruling; v4/README.md has a first reading. Verified: make -C v4 test passes; hosted-check passes on three ISAs; clean qemu with STARFORTH_V4=1 on amd64, aarch64 and riscv64 reaches ok> with the same hashes as hosted (logs/20261005-1601xx..1604xx). Nothing was typed at a bare-metal prompt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
311 lines
10 KiB
C
311 lines
10 KiB
C
/* boot.c -- the nucleus, then its capsules, then the prompt. See boot.h.
|
|
*
|
|
* Nothing here uses the C library: the bare-metal kernel links this file.
|
|
* It is not part of the engine (v4/src): it needs the capsule directory,
|
|
* which only a whole system has.
|
|
*/
|
|
#include "v4/boot.h"
|
|
#include "starkernel/capsule.h"
|
|
#include "starkernel/capsule_generated.h"
|
|
#include "starkernel/capsule_sig.h"
|
|
#include "starkernel/capsule_blocks.h"
|
|
|
|
/* The capsules, in the order they are loaded. */
|
|
/* POST is not yet part of the boot: v4:post79.4th does not pass yet
|
|
* (v4/README.md). Build with -DV4_POST_AT_BOOT=1 to load it and to require
|
|
* its clean tally; `make -C v4 post` does. */
|
|
#ifndef V4_POST_AT_BOOT
|
|
#define V4_POST_AT_BOOT 0
|
|
#endif
|
|
static const char *const boot_capsules[] = {
|
|
"v4:forth79.4th",
|
|
#if V4_POST_AT_BOOT
|
|
"v4:post79.4th",
|
|
#endif
|
|
};
|
|
|
|
#define LINE_MAX 80u /* QUERY takes 80 characters, the new-line among them */
|
|
#define STEP_LIMIT 4000000000ULL /* instruction words one line may take */
|
|
|
|
/* ---- printing ------------------------------------------------------------ */
|
|
|
|
static void say(const v4_boot *b, const char *s)
|
|
{
|
|
unsigned len = 0;
|
|
while (s[len]) len++;
|
|
b->out(s, len);
|
|
}
|
|
|
|
static void say_dec(const v4_boot *b, uint32_t v)
|
|
{
|
|
char buf[10];
|
|
unsigned i = sizeof buf;
|
|
do { buf[--i] = (char)('0' + v % 10u); v /= 10u; } while (v);
|
|
b->out(buf + i, (unsigned)sizeof buf - i);
|
|
}
|
|
|
|
static void say_hex(const v4_boot *b, uint64_t v)
|
|
{
|
|
char buf[18];
|
|
unsigned i;
|
|
buf[0] = '0'; buf[1] = 'x';
|
|
for (i = 0; i < 16; i++) buf[2 + i] = "0123456789abcdef"[(v >> (60 - 4 * i)) & 15u];
|
|
b->out(buf, sizeof buf);
|
|
}
|
|
|
|
/* ---- hashing: FNV-1a, 64 bits, as kernel/src/vm/parity.c ----------------- */
|
|
|
|
#define FNV_OFFSET 0xcbf29ce484222325ULL
|
|
#define FNV_PRIME 0x00000100000001b3ULL
|
|
|
|
static uint64_t hash_cell(uint64_t h, v4_cell c)
|
|
{
|
|
v4_ucell u = (v4_ucell)c;
|
|
unsigned i;
|
|
for (i = 0; i < V4_CELL_BITS / 8; i++) {
|
|
h ^= (uint64_t)((u >> (8 * i)) & 0xffu);
|
|
h *= FNV_PRIME;
|
|
}
|
|
return h;
|
|
}
|
|
|
|
uint64_t v4_boot_dict_hash(const v4_node *n, const v4_image *im)
|
|
{
|
|
uint64_t h = FNV_OFFSET;
|
|
v4_cell here = (n->mem[im->dp] + 3) / 4, k;
|
|
|
|
if (here < 0) here = 0;
|
|
if (here > (v4_cell)V4_NODE_WORDS) here = (v4_cell)V4_NODE_WORDS;
|
|
for (k = 0; k < here; k++) h = hash_cell(h, n->mem[k]);
|
|
return hash_cell(h, n->mem[im->latest]);
|
|
}
|
|
|
|
static uint64_t image_hash(const v4_image *im)
|
|
{
|
|
uint64_t h = FNV_OFFSET;
|
|
unsigned i;
|
|
for (i = 0; i < im->count; i++) {
|
|
h = hash_cell(h, im->cells[i].addr);
|
|
h = hash_cell(h, im->cells[i].value);
|
|
}
|
|
return h;
|
|
}
|
|
|
|
/* how many words FORTH holds: the list from LATEST, each entry's link in the
|
|
* cell before its code */
|
|
static uint32_t word_count(const v4_node *n, const v4_image *im)
|
|
{
|
|
v4_cell xt = n->mem[im->latest];
|
|
uint32_t count = 0;
|
|
while (xt > 0 && xt < (v4_cell)V4_NODE_WORDS && count < (uint32_t)V4_NODE_WORDS) {
|
|
count++;
|
|
xt = n->mem[xt - 1];
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/* ---- running the node ---------------------------------------------------- */
|
|
|
|
/* The node ends every line it has taken with " ok" and the next prompt.
|
|
* Those eight characters are held back from the console, so that what is
|
|
* shown is only what the line itself printed. */
|
|
static const char accepted[8] = { ' ', 'o', 'k', '\n', 'o', 'k', '>', ' ' };
|
|
|
|
typedef struct {
|
|
char held[8];
|
|
unsigned len;
|
|
} tail;
|
|
|
|
/* POST's verdict. The POST capsule ends by printing one line,
|
|
* PARITY:V4_POST tests=N pass=N fail=N
|
|
* and the boot passes only if it has seen that line with fail=0. That no
|
|
* line was refused is not enough: a POST that was broken half way would
|
|
* refuse nothing. */
|
|
static char post_line[96];
|
|
static unsigned post_len;
|
|
static int post_seen, post_clean;
|
|
|
|
static int text_at(const char *line, unsigned len, unsigned at, const char *want)
|
|
{
|
|
unsigned i;
|
|
for (i = 0; want[i]; i++)
|
|
if (at + i >= len || line[at + i] != want[i]) return 0;
|
|
return 1;
|
|
}
|
|
|
|
static void post_watch(char c)
|
|
{
|
|
static const char clean[] = " fail=0";
|
|
if (c != '\n') {
|
|
if (post_len < sizeof post_line) post_line[post_len++] = c;
|
|
return;
|
|
}
|
|
if (text_at(post_line, post_len, 0, "PARITY:V4_POST ")) {
|
|
post_seen = 1;
|
|
post_clean = post_len >= sizeof clean - 1 && text_at(post_line, post_len, post_len - (unsigned)(sizeof clean - 1), clean);
|
|
}
|
|
post_len = 0;
|
|
}
|
|
|
|
static void tail_put(const v4_boot *b, tail *t, int show, char c)
|
|
{
|
|
unsigned i;
|
|
if (t->len == sizeof t->held) {
|
|
if (show) b->out(t->held, 1);
|
|
for (i = 1; i < sizeof t->held; i++) t->held[i - 1] = t->held[i];
|
|
t->len--;
|
|
}
|
|
t->held[t->len++] = c;
|
|
}
|
|
|
|
/* Run until the node waits for a character. Returns 1 if what it printed
|
|
* ended with " ok" and the prompt; 0 if not -- the rest is then shown too --
|
|
* or if the node stopped or never came back. */
|
|
static int run_to_prompt(const v4_boot *b, int show)
|
|
{
|
|
v4_node *n = b->n;
|
|
uint64_t steps = 0;
|
|
unsigned i;
|
|
tail t;
|
|
|
|
t.len = 0;
|
|
for (;;) {
|
|
(void)v4_exec_step_word(n, b->es, b->h);
|
|
if (n->console_len) {
|
|
for (i = 0; i < n->console_len; i++) {
|
|
post_watch((char)n->console[i]);
|
|
tail_put(b, &t, show, (char)n->console[i]);
|
|
}
|
|
n->console_len = 0;
|
|
}
|
|
if (n->stopped) { say(b, "\nV4: the node stopped on a fault\n"); return 0; }
|
|
if (v4_image_waiting(n, b->im)) break;
|
|
if (++steps > STEP_LIMIT) { say(b, "\nV4: the node did not come back to its prompt\n"); return 0; }
|
|
}
|
|
if (t.len == sizeof t.held) {
|
|
for (i = 0; i < sizeof t.held && t.held[i] == accepted[i]; i++) { }
|
|
if (i == sizeof t.held) return 1;
|
|
}
|
|
if (show) b->out(t.held, t.len);
|
|
return 0;
|
|
}
|
|
|
|
/* ---- one capsule --------------------------------------------------------- */
|
|
|
|
static void say_where(const v4_boot *b, const char *name, uint32_t block, uint32_t line)
|
|
{
|
|
say(b, "\nV4: capsule "); say(b, name);
|
|
say(b, " block "); say_dec(b, block);
|
|
say(b, " line "); say_dec(b, line);
|
|
}
|
|
|
|
static int load_capsule(const v4_boot *b, const char *name)
|
|
{
|
|
const CapsuleDirHeader *dir = capsule_get_directory();
|
|
const CapsuleDesc *descs = capsule_get_descriptors();
|
|
const CapsuleNameEntry *names = capsule_get_names();
|
|
const uint8_t *arena = capsule_get_arena();
|
|
const CapsuleDesc *cap;
|
|
const uint8_t *p, *end;
|
|
CapsuleValidateResult vr;
|
|
CapsuleSigResult sr;
|
|
uint32_t block = 0, line = 0;
|
|
int in_block = 0;
|
|
|
|
cap = capsule_find_by_name(dir, descs, names, name);
|
|
if (!cap) { say(b, "V4: capsule "); say(b, name); say(b, " is not in this binary\n"); return 0; }
|
|
|
|
vr = capsule_validate(cap, arena, dir->arena_size, 1);
|
|
if (vr != CAPSULE_VALID) {
|
|
say(b, "V4: capsule "); say(b, name); say(b, ": "); say(b, capsule_validate_result_str(vr)); say(b, "\n");
|
|
return 0;
|
|
}
|
|
|
|
sr = capsule_verify_signature(descs, names, capsule_get_signatures(), arena, dir->desc_count, (int)(cap - descs));
|
|
if (sr != CAPSULE_SIG_OK) {
|
|
say(b, "V4: capsule "); say(b, name); say(b, " signature: "); say(b, capsule_sig_result_str(sr)); say(b, "\n");
|
|
if (sr == CAPSULE_SIG_INVALID) return 0;
|
|
}
|
|
|
|
p = capsule_get_payload(cap, arena);
|
|
if (!p) { say(b, "V4: capsule "); say(b, name); say(b, " has no payload\n"); return 0; }
|
|
end = p + cap->length;
|
|
|
|
while (p < end) {
|
|
const uint8_t *after, *nl;
|
|
uint32_t num;
|
|
unsigned len, i;
|
|
char text[LINE_MAX];
|
|
|
|
if (capsule_block_header(p, end, &num, &after)) {
|
|
block = num; line = 0; in_block = 1; p = after;
|
|
continue;
|
|
}
|
|
for (nl = p; nl < end && *nl != '\n'; nl++) { }
|
|
len = (unsigned)(nl - p);
|
|
if (len && p[len - 1] == '\r') len--;
|
|
if (in_block) {
|
|
line++;
|
|
if (len >= LINE_MAX) { say_where(b, name, block, line); say(b, " is too long\n"); return 0; }
|
|
if (len) {
|
|
for (i = 0; i < len; i++) text[i] = (char)p[i];
|
|
text[len] = '\n';
|
|
if (v4_node_console_feed(b->n, text, len + 1u) != len + 1u) {
|
|
say_where(b, name, block, line); say(b, ": the node's input is full\n");
|
|
return 0;
|
|
}
|
|
if (!run_to_prompt(b, 1)) {
|
|
say_where(b, name, block, line); say(b, " was not accepted: ");
|
|
b->out(text, len); say(b, "\n");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
p = (nl < end) ? nl + 1 : end;
|
|
}
|
|
|
|
say(b, "PARITY:V4_CAPSULE name="); say(b, name);
|
|
say(b, " capsule_id="); say_hex(b, cap->capsule_id);
|
|
say(b, " capsule_hash="); say_hex(b, cap->content_hash);
|
|
say(b, " dict_hash="); say_hex(b, v4_boot_dict_hash(b->n, b->im));
|
|
say(b, "\n");
|
|
return 1;
|
|
}
|
|
|
|
/* ---- the whole boot ------------------------------------------------------ */
|
|
|
|
int v4_boot_run(const v4_boot *b, unsigned char *disk, unsigned blocks)
|
|
{
|
|
unsigned i;
|
|
|
|
post_len = 0; post_seen = 0; post_clean = 0;
|
|
if (!v4_image_boot(b->n, b->es, b->h, b->im, disk, blocks)) {
|
|
say(b, "V4: the nucleus image is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n");
|
|
return 0;
|
|
}
|
|
say(b, "PARITY:V4_NUCLEUS words="); say_dec(b, word_count(b->n, b->im));
|
|
say(b, " image_hash="); say_hex(b, image_hash(b->im));
|
|
say(b, "\n");
|
|
|
|
/* the node's own first prompt is not shown: the boot prints one at the end */
|
|
(void)run_to_prompt(b, 0);
|
|
if (b->n->stopped) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
|
|
|
|
for (i = 0; i < sizeof boot_capsules / sizeof boot_capsules[0]; i++)
|
|
if (!load_capsule(b, boot_capsules[i])) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
|
|
#if V4_POST_AT_BOOT
|
|
if (!post_seen || !post_clean) {
|
|
say(b, post_seen ? "V4: POST reported failures\n" : "V4: POST did not report\n");
|
|
say(b, "PARITY:FAIL\nPOST: FAILED\n");
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if V4_POST_AT_BOOT
|
|
say(b, "PARITY:OK\nPOST: PASSED\nok> ");
|
|
#else
|
|
say(b, "PARITY:OK\nok> ");
|
|
#endif
|
|
return 1;
|
|
}
|