The final review of step 6e, by a fresh reviewer told to break it, and what was ruled: fix them all, test first. Each has a test that failed before the mend. - Two texts from one node could be begun in the wrong order when room for their answers came between the node's look at the first and at the second. Texts whose answers go by the same port are begun in the order they came: (D-MASK), quit.v4. 102 of 700 timings were wrong before. - A word written with PORT! to a node that speaks whole messages, or to a console's port, blocked its writer for ever and cut off every node beyond it. It is error 18 now, whether that node is asleep, running or stuck: fabric.c, node.c. - A node asleep for room on one wire is woken when another of its wires is cut. - A line there was no room for on the lone node's wire was reported as a fault and ended the hosted program. It is Message refused, and the prompt comes back: V4_BOOT_LINE_REFUSED. hosted-check has the case; no bare-metal boot has typed it. - Ruled: text behind held output for the same port is refused though it would fit. MESH.md 7d.4 said otherwise and is corrected. - The removal test also cuts and re-makes a wire, and has an exchange in which output is held at a full wire: 26,814 disturbances in all. - MESH.md and v4/README.md no longer say that a stuck node holds up no other node, that only printing waits, or that no node is left waiting on one that is gone; 7d.6 lists the limits and what the products do that they did not before. make -C v4 test and sanitize at both widths, hosted-check, lint. Three bare-metal boots: logs/20261013-194827 (amd64), -195131 (aarch64), -195518 (riscv64). POST 538 of 538, word_count=317, dict_hash=0xe5a57f3eb72ff94a on all six; the three sessions are the same character for character. The boots were built with the working tree's uncommitted change to kernel/Makefile that adds the ISA to the kernel's version line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
542 lines
23 KiB
C
542 lines
23 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/post.h"
|
|
#include "v4/blocks.h"
|
|
#include "v4/boot.h"
|
|
#include "v4/message.h"
|
|
#include "v4/wire.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. */
|
|
static const char *const boot_capsules[] = {
|
|
"v4:forth79.4th",
|
|
};
|
|
|
|
/* POST is part of the boot, and is the kernel's (v4/include/v4/post.h;
|
|
* docs/v4.0.0/NUCLEUS.md 6.3): when the capsules are in and the system is
|
|
* sealed, the kernel feeds its cases to the node and judges them, puts the
|
|
* node back to the system as sealed, and the boot passes only if none
|
|
* failed. -DV4_POST_AT_BOOT=0 leaves it out, for work on a vocabulary
|
|
* that does not pass yet. */
|
|
#ifndef V4_POST_AT_BOOT
|
|
#define V4_POST_AT_BOOT 1
|
|
#endif
|
|
|
|
#define LINE_MAX 1025u /* a line is at most 1024 characters, a block */
|
|
#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 ----------------- */
|
|
|
|
uint64_t v4_boot_dict_hash(const v4_node *n, const v4_image *im)
|
|
{
|
|
return v4_image_dict_hash(n, im);
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/* ---- handing the node a line ---------------------------------------------- */
|
|
|
|
/* The boot is the node's neighbour on two of its ports. On port 0 it is
|
|
* the kernel: the node writes the number of a request there, a word
|
|
* (ENGINE.md 3.3) -- for a block (blocks.h), or for one of the host's
|
|
* words. On port 1 it is the console: it puts a message of text on the
|
|
* node's wire and takes off it the messages of what the node printed and
|
|
* of how the text ended (message.h). */
|
|
#define KERNEL_PORT 0u
|
|
#define CONSOLE_PORT 1u
|
|
#define CONSOLE_ID 1 /* the console's number as a sender; the node's is 0 until it is given one */
|
|
|
|
/* What the kernel keeps of the node's block window (blocks.h). */
|
|
static v4_blocks boot_blocks;
|
|
|
|
/* THE WIRE (docs/v4.0.0/MESH.md 7d.5). The lone node has no fabric: the
|
|
* boot keeps the two queues of its console's wire, one each way (wire.h),
|
|
* and does what the node asks of them, as a fabric does for a node of a
|
|
* mesh (fabric.c).
|
|
* THE KERNEL'S PORT HAS NO QUEUE (ruled 2026-10-08). The kernel is asked by
|
|
* words written to port 0 and takes no messages: a message for that port
|
|
* -- SEND-ON to it, or what a text prints when the way to its console has
|
|
* been set there -- is refused at once, as on any port with no one to take
|
|
* messages: the node's error 18, "No one on that port". */
|
|
static v4_wire_queue boot_wire[2]; /* the console's: [0] to the node, [1] from it */
|
|
#define TO_NODE (&boot_wire[0])
|
|
#define FROM_NODE (&boot_wire[1])
|
|
|
|
/* 1 if `count` words from `addr` are the node's memory and not its ports
|
|
* or the addresses after them: they may be copied to and from. */
|
|
static int span_ok(const v4_node *n, v4_cell addr, unsigned count)
|
|
{
|
|
if (count == 0) return 1;
|
|
if (count > V4_NODE_WORDS || addr < 0 || (v4_ucell)addr > (v4_ucell)(V4_NODE_WORDS - count)) return 0;
|
|
if (n->port >= 0 && addr <= n->port + (v4_cell)V4_PORT_HAVE && addr + (v4_cell)count > n->port) return 0;
|
|
return 1;
|
|
}
|
|
|
|
|
|
/* The node is blocked at an operation on a wire (node.h, v4_node_doing):
|
|
* do it, whole, and let the node go on; the operations and their answers
|
|
* are MESH.md 7d.3's, as fabric.c does them. Returns 0 if the node sleeps
|
|
* on: it asked to sleep (8), and no message has come since it last asked
|
|
* which wires have one; or for room (9) that is not there, and nothing
|
|
* has come off its wire either. */
|
|
static int boot_do(const v4_boot *bt)
|
|
{
|
|
v4_node *n = bt->n;
|
|
v4_wire_queue *rx = n->wire == CONSOLE_PORT ? TO_NODE : 0, *tx = n->wire == CONSOLE_PORT ? FROM_NODE : 0;
|
|
v4_cell a = n->wire_a, b = n->wire_b, h[V4_WIRE_HEADER], how = V4_WIRE_NO_ONE;
|
|
unsigned cells;
|
|
|
|
switch (n->do_op) {
|
|
case 1: /* look */
|
|
if (!rx) break;
|
|
if (rx->mark >= rx->used) how = V4_WIRE_NO_MESSAGE;
|
|
else how = span_ok(n, a, V4_WIRE_HEADER) ? v4_wire_look(rx, &n->mem[a]) : V4_WIRE_NOT_A_MESSAGE;
|
|
break;
|
|
case 2: /* take */
|
|
if (!rx) break;
|
|
if (!rx->looked || v4_wire_look(rx, h) != V4_WIRE_DONE) { how = V4_WIRE_NO_MESSAGE; break; } /* only the message last looked at (wire.h) */
|
|
cells = v4_wire_cells(h[6]) - V4_WIRE_HEADER;
|
|
if (!span_ok(n, a, V4_WIRE_HEADER) || !span_ok(n, b, cells)) how = V4_WIRE_NOT_A_MESSAGE;
|
|
else how = v4_wire_take(rx, &n->mem[a], cells ? &n->mem[b] : &n->mem[0], cells);
|
|
break;
|
|
case 3: /* move */
|
|
if (!rx) break;
|
|
if (rx->mark >= rx->used || !rx->looked) how = V4_WIRE_NO_MESSAGE;
|
|
else how = a == (v4_cell)CONSOLE_PORT ? v4_wire_move(rx, FROM_NODE) : V4_WIRE_NO_ONE;
|
|
break;
|
|
case 4: /* drop */
|
|
if (rx) how = rx->looked ? v4_wire_drop(rx) : V4_WIRE_NO_MESSAGE;
|
|
break;
|
|
case 5: /* put */
|
|
if (!tx) break;
|
|
cells = span_ok(n, a, V4_WIRE_HEADER) ? v4_wire_cells(n->mem[a + 6]) : 0;
|
|
if (cells == 0 || !span_ok(n, b, cells - V4_WIRE_HEADER)) how = V4_WIRE_NOT_A_MESSAGE;
|
|
else how = v4_wire_put(tx, &n->mem[a], cells > V4_WIRE_HEADER ? &n->mem[b] : &n->mem[0]);
|
|
break;
|
|
case 6: /* first */
|
|
if (rx) { v4_wire_first(rx); how = V4_WIRE_DONE; }
|
|
break;
|
|
case 7: /* next */
|
|
if (rx) how = v4_wire_next(rx);
|
|
break;
|
|
case 8: /* sleep */
|
|
if (!TO_NODE->arrived) return 0;
|
|
how = V4_WIRE_DONE;
|
|
break;
|
|
case 9: /* sleep for room */
|
|
if (!tx) break;
|
|
if (a < 0 || a > (v4_cell)V4_WIRE_CELLS) { how = V4_WIRE_NO_ROOM; break; }
|
|
if (v4_wire_room(tx, (unsigned)a) == V4_WIRE_DONE) { how = V4_WIRE_DONE; break; }
|
|
if (!TO_NODE->arrived && !FROM_NODE->freed) return 0; /* a message has come, or the console has taken one */
|
|
FROM_NODE->freed = 0;
|
|
how = V4_WIRE_NO_ROOM;
|
|
break;
|
|
case 10: /* room */
|
|
if (!tx) break;
|
|
how = a < 0 ? V4_WIRE_NOT_A_MESSAGE : a <= (v4_cell)V4_WIRE_CELLS ? v4_wire_room(tx, (unsigned)a) : V4_WIRE_NO_ROOM;
|
|
break;
|
|
default: /* not an operation */
|
|
how = V4_WIRE_NOT_A_MESSAGE;
|
|
break;
|
|
}
|
|
v4_node_done(n, how);
|
|
return 1;
|
|
}
|
|
|
|
/* The node is told which of its wires have a message and executes one
|
|
* instruction word; if it fetched which wires have one, it has seen what
|
|
* had come. The first half of a fabric's step (fabric.c). */
|
|
static void boot_exec(const v4_boot *b)
|
|
{
|
|
v4_node *n = b->n;
|
|
v4_node_have(n, TO_NODE->used ? 1u << CONSOLE_PORT : 0u);
|
|
(void)v4_exec_step_word(n, b->es, b->h);
|
|
if (n->have_fetched) {
|
|
TO_NODE->arrived = 0;
|
|
n->have_fetched = 0;
|
|
}
|
|
}
|
|
|
|
/* Time passes for the node, in the order a fabric's step has: that, and
|
|
* then what it asked of a wire is done. Returns 0 if it is asleep at a
|
|
* wire and sleeps on. */
|
|
static int boot_step(const v4_boot *b)
|
|
{
|
|
boot_exec(b);
|
|
return v4_node_doing(b->n) ? boot_do(b) : 1;
|
|
}
|
|
|
|
/* Hand the node a line and run it to its end. What it prints goes to the
|
|
* console; or, if `keep` is given, into `keep`, at most `cap` characters,
|
|
* and none of it is shown: *printed is then how many it printed in all.
|
|
* The console takes every message off the node's wire each time round, so
|
|
* what the node prints never waits for room for long. */
|
|
static int run_line(const v4_boot *b, const char *text, unsigned len, char *keep, unsigned cap, unsigned *printed)
|
|
{
|
|
static v4_message out; /* one line at a time: the boot is not re-entered */
|
|
static v4_cell in[V4_WIRE_HEADER], in_text[V4_MSG_MAX_CHARS / 4u];
|
|
v4_node *n = b->n;
|
|
uint64_t steps = 0;
|
|
unsigned i;
|
|
|
|
if (text) { /* with none, the node has a line waiting on its wire (boot.h) */
|
|
if (!v4_message_text(&out, 0, CONSOLE_ID, V4_MSG_TEXT, text, len)) return V4_BOOT_LINE_TOO_LONG;
|
|
if (v4_wire_put(TO_NODE, out.word, out.word + V4_MSG_HEADER) != V4_WIRE_DONE) return V4_BOOT_LINE_REFUSED; /* no room: refused at once, as any message is */
|
|
}
|
|
for (;;) {
|
|
int awake = boot_step(b);
|
|
if (n->stopped) return V4_BOOT_LINE_STOPPED;
|
|
|
|
v4_wire_first(FROM_NODE); /* what the node has sent its console, in the order it was sent */
|
|
while (v4_wire_take(FROM_NODE, in, in_text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) {
|
|
if (in[2] == V4_MSG_OUTPUT) {
|
|
for (i = 0; i < (unsigned)in[6]; i++) {
|
|
char c = (char)(((v4_ucell)in_text[i / 4u] >> (8u * (i % 4u))) & 0xFFu);
|
|
if (!keep) b->out(&c, 1);
|
|
else { if (*printed < cap) keep[*printed] = c; (*printed)++; }
|
|
}
|
|
} else if (in[2] == V4_MSG_DONE) {
|
|
/* The text has ended. The node goes on to where it next asks something of a wire, with
|
|
* nothing of its own on either stack: whoever called may then look at its stacks, or
|
|
* empty them. */
|
|
for (i = 0; i < 4096u && !v4_node_doing(n) && !n->stopped && !n->asking; i++) boot_exec(b);
|
|
return (int)in_text[0];
|
|
}
|
|
}
|
|
if (!awake) {
|
|
if (n->do_op == 9) { /* waiting for room, which the console has just made */
|
|
if (++steps > STEP_LIMIT) return V4_BOOT_LINE_STOPPED;
|
|
continue;
|
|
}
|
|
return V4_BOOT_LINE_WAITING; /* waiting for an answer: only the console can end that */
|
|
}
|
|
|
|
if (n->asking && n->ask_port == KERNEL_PORT) { /* a request: the kernel's turn */
|
|
if (v4_blocks_serve(&boot_blocks, n, n->request)) { /* a block, as for any node */
|
|
v4_node_port_served(n);
|
|
} else if (b->serve && n->request >= 1 && n->request <= (v4_cell)b->word_count) {
|
|
b->serve(n, (unsigned)n->request);
|
|
v4_node_port_served(n);
|
|
} else {
|
|
/* a request no one serves is an error, as any other (D-18):
|
|
* 12, Argument out of range */
|
|
v4_node_port_served(n);
|
|
v4_node_store(n, n->error_reg, 12); /* a store to NODE-ERROR raises it */
|
|
}
|
|
continue;
|
|
}
|
|
if (n->asking) { /* a word written to a port with no one on it: an error on the node */
|
|
if (!v4_node_port_gone(n, V4_ERROR_NO_ONE)) return V4_BOOT_LINE_STOPPED;
|
|
continue;
|
|
}
|
|
if (n->reading && !n->given) { /* a word read from a port: no one here writes one */
|
|
if (n->read_port != V4_PORT_ANY && v4_node_port_gone(n, V4_ERROR_NO_ONE)) continue;
|
|
return V4_BOOT_LINE_STOPPED; /* waiting for what will not come */
|
|
}
|
|
if (v4_image_waiting(n, b->im)) { /* the text is reading the keyboard */
|
|
int c = b->key ? b->key() : V4_BOOT_KEY_END;
|
|
unsigned char ch = (unsigned char)c;
|
|
if (c == V4_BOOT_KEY_END) return V4_BOOT_LINE_NO_INPUT;
|
|
if (c >= 0) (void)v4_node_console_feed(n, &ch, 1u);
|
|
continue;
|
|
}
|
|
if (++steps > STEP_LIMIT) return V4_BOOT_LINE_STOPPED;
|
|
}
|
|
}
|
|
|
|
int v4_boot_line(const v4_boot *b, const char *text, unsigned len)
|
|
{
|
|
return run_line(b, text, len, 0, 0u, 0);
|
|
}
|
|
|
|
#if V4_POST_AT_BOOT
|
|
/* POST's host (post.h): a line is handed to the node as any line is, and
|
|
* what the node prints is kept for the runner and not shown. */
|
|
static int post_line(void *self, const char *text, unsigned text_len, char *out, unsigned cap, unsigned *len)
|
|
{
|
|
int how;
|
|
*len = 0;
|
|
how = run_line((const v4_boot *)self, text, text_len, out, cap, len);
|
|
return (how == V4_TEXT_QUIT || how == V4_TEXT_COMPLETED || how == V4_TEXT_ERROR) ? how : -1;
|
|
}
|
|
static void post_say(void *self, const char *text, unsigned len)
|
|
{
|
|
((const v4_boot *)self)->out(text, len);
|
|
}
|
|
#endif
|
|
|
|
/* ---- 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];
|
|
int how = v4_boot_line(b, text, len);
|
|
if (how != V4_TEXT_COMPLETED) {
|
|
say_where(b, name, block, line);
|
|
say(b, how == V4_TEXT_ERROR ? " ended in an error: "
|
|
: how == V4_TEXT_QUIT ? " ended with QUIT: "
|
|
: how == V4_BOOT_LINE_NO_INPUT ? " reads the keyboard: "
|
|
: " stopped the node: ");
|
|
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 nucleus ----------------------------------------------------------- */
|
|
|
|
/* The nucleus is a capsule of F18 code (v4/include/v4/capsule.h): the words
|
|
* themselves, to be written to the port of a node that has nothing in it.
|
|
* The boot is that node's neighbour. It finds the capsule, checks its hash
|
|
* and its signature as it does any capsule's, and gives the node a word
|
|
* each time it reads its port, until the node has taken them all and is
|
|
* where the nucleus waits to be handed a line. */
|
|
#define NUCLEUS_NAME_(bits) "v4:nucleus-" #bits ".f18"
|
|
#define NUCLEUS_NAME__(bits) NUCLEUS_NAME_(bits)
|
|
#define NUCLEUS_NAME NUCLEUS_NAME__(V4_CELL_BITS)
|
|
#define CELL_BYTES (V4_CELL_BITS / 8)
|
|
|
|
static int load_nucleus(const v4_boot *b)
|
|
{
|
|
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;
|
|
CapsuleValidateResult vr;
|
|
CapsuleSigResult sr;
|
|
v4_node *n = b->n;
|
|
uint64_t words, at = 0, steps = 0;
|
|
|
|
cap = capsule_find_by_name(dir, descs, names, NUCLEUS_NAME);
|
|
if (!cap) { say(b, "V4: capsule " NUCLEUS_NAME " 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 " NUCLEUS_NAME ": "); 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 " NUCLEUS_NAME " 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 || cap->length % CELL_BYTES != 0) { say(b, "V4: capsule " NUCLEUS_NAME " is not whole words\n"); return 0; }
|
|
words = cap->length / CELL_BYTES;
|
|
|
|
/* until it has taken every word and is where the nucleus waits for a
|
|
* message: asleep, no message having come */
|
|
for (;;) {
|
|
if (!boot_step(b)) {
|
|
if (at >= words && n->do_op == 8) break;
|
|
say(b, "V4: the node did not take the nucleus in\n");
|
|
return 0;
|
|
}
|
|
if (n->stopped || n->asking || ++steps > STEP_LIMIT) {
|
|
say(b, "V4: the node did not take the nucleus in\n");
|
|
return 0;
|
|
}
|
|
if (n->reading && !n->given) {
|
|
v4_ucell word = 0;
|
|
unsigned i;
|
|
if (at >= words) {
|
|
say(b, "V4: the node wants more than the nucleus holds\n");
|
|
return 0;
|
|
}
|
|
for (i = 0; i < CELL_BYTES; i++) word |= (v4_ucell)p[at * CELL_BYTES + i] << (8 * i);
|
|
v4_node_port_give(n, 0, (v4_cell)word);
|
|
at++;
|
|
}
|
|
}
|
|
|
|
say(b, "PARITY:V4_NUCLEUS name=" NUCLEUS_NAME);
|
|
say(b, " capsule_id="); say_hex(b, cap->capsule_id);
|
|
say(b, " capsule_hash="); say_hex(b, cap->content_hash);
|
|
say(b, " words="); say_dec(b, word_count(n, b->im));
|
|
say(b, "\n");
|
|
return 1;
|
|
}
|
|
|
|
/* ---- the whole boot ------------------------------------------------------ */
|
|
|
|
int v4_boot_run(const v4_boot *b)
|
|
{
|
|
unsigned i;
|
|
|
|
/* a node is born with nothing in it, and is sent the nucleus */
|
|
if (!v4_image_born(b->n, b->es, b->h, b->im)) {
|
|
say(b, "V4: the nucleus is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n");
|
|
return 0;
|
|
}
|
|
/* its console's wire, with nothing on it */
|
|
v4_wire_reset(TO_NODE); v4_wire_reset(FROM_NODE);
|
|
v4_blocks_init(&boot_blocks, b->im->block_window);
|
|
if (!load_nucleus(b)) { say(b, "PARITY:FAIL\nPOST: FAILED\n"); return 0; }
|
|
|
|
/* the kernel's words, before any capsule that might use one */
|
|
for (i = 0; i < b->word_count; i++) {
|
|
char text[64];
|
|
unsigned at = 0, k, num = i + 1, div = 1;
|
|
while (num / div >= 10) div *= 10;
|
|
for (; div; div /= 10) text[at++] = (char)('0' + num / div % 10);
|
|
for (k = 0; " KERNEL-WORD "[k]; k++) text[at++] = " KERNEL-WORD "[k];
|
|
for (k = 0; b->words[i][k] && at < sizeof text; k++) text[at++] = b->words[i][k];
|
|
if (b->words[i][k] || v4_boot_line(b, text, at) != V4_TEXT_COMPLETED) {
|
|
say(b, "V4: kernel word "); say(b, b->words[i]); say(b, " could not be made\nPARITY: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; }
|
|
/* The kernel's words and the capsules are the system: COLD comes back
|
|
* to here, and FORGET will not go below it. */
|
|
v4_image_seal(b->n, b->im);
|
|
|
|
#if V4_POST_AT_BOOT
|
|
{
|
|
static const char cold[] = "COLD";
|
|
v4_post_host host;
|
|
v4_post_tally tally;
|
|
unsigned printed = 0;
|
|
int how;
|
|
|
|
host.self = (void *)b; host.n = b->n; host.line = post_line; host.say = post_say;
|
|
if (!v4_post_run(&host, v4_post_cases, v4_post_case_count, &tally)) {
|
|
say(b, "V4: POST reported failures\nPARITY:FAIL\nPOST: FAILED\n");
|
|
return 0;
|
|
}
|
|
/* POST leaves nothing: the node is put back to the system as it was
|
|
* sealed, as v3 puts the dictionary back after each of its suites
|
|
* (v3/src/test_runner/test_common.c, run_test_suite). What COLD
|
|
* prints is not shown. */
|
|
v4_dstack_reset(&b->n->ds);
|
|
how = run_line(b, cold, (unsigned)sizeof cold - 1u, (char *)cold, 0u, &printed);
|
|
if (how != V4_TEXT_COMPLETED && how != V4_TEXT_QUIT) {
|
|
say(b, "V4: the node did not come back to the system after POST\nPARITY:FAIL\nPOST: FAILED\n");
|
|
return 0;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
say(b, "PARITY:V4_SYSTEM word_count="); say_dec(b, word_count(b->n, b->im));
|
|
say(b, " dict_hash="); say_hex(b, v4_boot_dict_hash(b->n, b->im));
|
|
say(b, "\n");
|
|
|
|
#if V4_POST_AT_BOOT
|
|
say(b, "PARITY:OK\nPOST: PASSED\n");
|
|
#else
|
|
say(b, "PARITY:OK\n");
|
|
#endif
|
|
return 1;
|
|
}
|