/* 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 "v4/message.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 part of the boot: v4:post79.4th is loaded after the vocabulary it * tests and must end with a clean tally. -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 static const char *const boot_capsules[] = { "v4:forth79.4th", #if V4_POST_AT_BOOT "v4:post79.4th", #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 ----------------- */ #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]); } /* 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 ---------------------------------------------- */ /* 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; } /* 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 (ENGINE.md * 3.3). On port 1 it is the console: it writes the node a message of text * and reads back 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 */ int v4_boot_line(const v4_boot *b, const char *text, unsigned len) { static v4_message out, in; /* one line at a time: the boot is not re-entered */ v4_node *n = b->n; uint64_t steps = 0; unsigned sent = 0, i; if (!v4_message_text(&out, 0, CONSOLE_ID, V4_MSG_TEXT, text, len)) return V4_BOOT_LINE_TOO_LONG; in.count = 0; for (;;) { (void)v4_exec_step_word(n, b->es, b->h); if (n->stopped) return V4_BOOT_LINE_STOPPED; if (n->asking && n->ask_port == KERNEL_PORT) { /* a request: the kernel's turn */ 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 && n->ask_port == CONSOLE_PORT) { /* a word of a message from the node */ v4_cell word = n->request; v4_node_port_served(n); if (v4_message_word(&in, word)) { unsigned chars = v4_message_length(&in); if (v4_message_type(&in) == V4_MSG_OUTPUT) { for (i = 0; i < chars; i++) { char c = v4_message_char(&in, i); post_watch(c); b->out(&c, 1); } } else if (v4_message_type(&in) == V4_MSG_DONE) { return (int)in.word[V4_MSG_HEADER]; } in.count = 0; } continue; } if (n->asking) return V4_BOOT_LINE_STOPPED; /* a port with nothing on it */ if (n->reading && !n->given) { if (sent < out.count && (n->read_port == CONSOLE_PORT || n->read_port == V4_PORT_ANY)) { v4_node_port_give(n, CONSOLE_PORT, out.word[sent++]); /* a word of the message to the node */ 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; } } /* ---- 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 waiting for a message: reading * its ports again, but from its memory now and not from the port */ while (at < words || !(n->reading && !n->given && n->p != b->im->port + (v4_cell)V4_PORT_ANY)) { (void)v4_exec_step_word(n, b->es, b->h); 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) { if (n->p != b->im->port + (v4_cell)V4_PORT_ANY) break; /* it is waiting for a message */ 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 char *disk, unsigned blocks) { unsigned i; post_len = 0; post_seen = 0; post_clean = 0; /* 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, disk, blocks)) { say(b, "V4: the nucleus is not for this build of the engine\nPARITY:FAIL\nPOST: FAILED\n"); return 0; } 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; } #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 /* 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 say(b, "PARITY:OK\nPOST: PASSED\n"); #else say(b, "PARITY:OK\n"); #endif return 1; }