From bcfd6556a87b509f5858a5164e40cd74211c1d33 Mon Sep 17 00:00:00 2001 From: rajames Date: Wed, 7 Oct 2026 08:58:52 -0400 Subject: [PATCH] fix(v4.0.0): storage -- what the review of step 6 found A block request with fewer than two values on the stack is refused; it had acted on whatever the stack ring held and stopped the node. Bare metal: when the kernel's chain takes the place of POST's block RAM the node's two buffers are emptied, so it no longer holds POST's copy of a block; and the chain's fast RAM is cleared, so a node cannot read what was in the kernel's heap. blocks.c is built with each test under that test's own warnings and sanitizers; it had been left out of both. The hosted link cleans its object directory first: it had linked the withdrawn store_v3.o left there from the day before. node.h and DECOMPOSITION.md D-19 no longer describe the message device or the four registers as current. MESH.md 8.5 records two findings for ruling: a node's own copy of a block, and a block read over a node's code. From a clean build: make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and riscv64 boot, POST 538 of 538, same hashes, blocks 1 and 2047 clean at the prompt: logs/20261007-085017, -085254, -085636. Co-Authored-By: Claude Opus 5.5 --- capsules/BLOCK_MAP.md | 2 +- docs/v4.0.0/DECOMPOSITION.md | 4 +- docs/v4.0.0/MESH.md | 39 +++- docs/v4.0.0/V3-PARITY.md | 2 +- docs/v4.0.0/plans/2026-10-06-step6-storage.md | 2 +- kernel/src/kernel_main.c | 1 + kernel/src/v4/sk_v4.c | 10 +- .../amd64/qemu-amd64-20261007-085017.log | 148 ++++++++++++++ .../aarch64/qemu-aarch64-20261007-085254.log | 121 +++++++++++ .../riscv64/qemu-riscv64-20261007-085636.log | 191 ++++++++++++++++++ v4/Makefile | 30 ++- v4/include/v4/blocks.h | 4 +- v4/include/v4/node.h | 6 +- v4/system/blocks.c | 5 + v4/tests/test_blocks.c | 11 + 15 files changed, 543 insertions(+), 33 deletions(-) create mode 100644 logs/20261007-085017/amd64/qemu-amd64-20261007-085017.log create mode 100644 logs/20261007-085254/aarch64/qemu-aarch64-20261007-085254.log create mode 100644 logs/20261007-085636/riscv64/qemu-riscv64-20261007-085636.log diff --git a/capsules/BLOCK_MAP.md b/capsules/BLOCK_MAP.md index 0129ddf9..dde6ec6b 100644 --- a/capsules/BLOCK_MAP.md +++ b/capsules/BLOCK_MAP.md @@ -1,5 +1,5 @@ # Capsule Block Manifest — Auto-generated - + diff --git a/docs/v4.0.0/DECOMPOSITION.md b/docs/v4.0.0/DECOMPOSITION.md index 6d0ee2a2..bcc7dbae 100644 --- a/docs/v4.0.0/DECOMPOSITION.md +++ b/docs/v4.0.0/DECOMPOSITION.md @@ -180,7 +180,7 @@ definition below depends on one, it says so. | **D-16** | Stack overflow and underflow; `DEPTH`, `PICK`, `ROLL` (ruled 2026-10-04; revises D-2). | **Guarded: a stack fault.** Each stack counts what it holds. Before every opcode the node checks that the stacks hold what the opcode takes — including a `T` or `S` it only reads — and have room for what it leaves. If not, the opcode does nothing and the node faults exactly as for a bad address (D-14), to that kind's handler: `Stack overflow`, `Stack underflow`, `Return stack overflow`, `Return stack underflow`, then ` ERROR` and the prompt. **Every fault, D-14's included, empties both stacks**: the handler does not return, and what a word stopped part-way has left on the data stack is of no use to its caller. (v3 keeps what the failing word had not taken, and names the word: `DROP: Stack underflow`.) The fault handler is a table of five words, one per kind, each a jump (`(FAULTS)` in `v4/capsule/quit.v4`). Two registers (§7) are all a programme sees of the stacks: `DSTACK-DEPTH` and `RSTACK-DEPTH` read as the depth, and a store to one empties that stack. There is still no stack pointer and no address for a stack cell, so `SP@` and `SP!` stay retired; `.S` waits for number output to reach the capsule. The sizes were unchanged by this ruling, 10 and 9 (D-17 then deepens the host node's): one more value, or one more level of call, is now an error message where it used to be silent corruption. Executed on the golden model (2026-10-04): `tests/test_exec.c` for every opcode at every depth of both stacks, `tests/test_host_quit.c` from the prompt. | | **D-17** | Stack sizes on the host node (2026-10-04, following D-16). | **32 values and 32 return entries on the host node; a mesh node keeps the F18's 10 and 9.** Once the stacks are counted (D-16) their size is a parameter of the node, like its memory, and the host node is the one that runs the interpreter and the compiler underneath the user's programme: at 10 and 9 the prompt left a programme about six values, and `/` could be used only four words deep. The mechanism is the same at both sizes — top registers over a ring — and so is every word's definition. v3's stacks are deeper still. In the golden model the sizes are `V4_DATA_RING` and `V4_RET_RING` (`stack.h`), set for the host-node tests in `v4/Makefile`. | | **D-18** | Every other error (ruled 2026-10-04: "guard all errors"). | **A word that finds an error raises it, and the line ends there.** The word takes its own arguments off the stack and stores an error code in `NODE-ERROR` (§7). On a node with a prompt that store is a trap, a sixth kind of fault beside D-14's and D-16's: nothing after it executes, the return stack is emptied, and the prompt prints the code's message, ends any definition that was open, prints ` ERROR` and waits for the next line. Unlike the other faults it leaves the data stack as the word left it. The codes: 1 `Negative count` (`CMOVE`, `TYPE`), 2 `Not a number` (`NUMBER`), 3 `Number too long` (`HOLD` into a full buffer), 4 `Not a character` (`HOLD`), 5 `Dictionary full` (`,` `C,` `ALLOT`, any defining word), 6 `Name missing` (a defining word with nothing after it), 7 `Control structure mismatch`, 8 `Control structures too deep`, 9 `Shift count out of range`, 10 `Protected word`, 11 `Division by zero` (`Q./`), 12 `Argument out of range`, 13 `Block out of range`, 14 `No block is being loaded`, 15 `Deferred word not set`, 16 `Not a word`; −1 means the word printed its own message (`UNKNOWN WORD: 'xxx'`, `xxx: compile-only`). Before this ruling these set the flag and the line ran on to its end. v3 stops at once too; its messages name the word (`LOOP: missing DO`) where v4's name the fault. With the trap not attached `NODE-ERROR` is plain memory and the word returns, which is how the words are tested below the prompt. D-11, D-12 and D-13 said "set `NODE-ERROR`"; on a node with a prompt that now means this. Executed on the golden model (2026-10-04): `tests/test_exec.c`, `tests/test_host_quit.c`. | -| **D-19** | Block storage on the golden model (2026-10-05). | **Four memory-mapped registers on the host node, until the mesh carries the storage service.** `BLOCK-NUMBER`, `BLOCK-ADDRESS` (the word address of 256 cells), `BLOCK-COMMAND` (a store of 1 reads the block into those cells, 2 writes it from them) and `BLOCK-STATUS` (0 when the command worked, −1 for a block the device has not got or cells not all in memory). A block is 1024 bytes: 256 cells, four bytes to a cell, the first byte lowest, at either cell width. This is the console's arrangement (§7) applied to storage; like it, it is the model's stand-in and not the mesh protocol, which §5.11 still leaves to the device node. | +| **D-19** | Block storage on the golden model (2026-10-05). **Replaced 2026-10-07:** the four registers are gone; a node asks its kernel for a block by a request on port 0 (`MESH.md` section 8, `v4/include/v4/blocks.h`). What follows is the decision as it was. | **Four memory-mapped registers on the host node, until the mesh carries the storage service.** `BLOCK-NUMBER`, `BLOCK-ADDRESS` (the word address of 256 cells), `BLOCK-COMMAND` (a store of 1 reads the block into those cells, 2 writes it from them) and `BLOCK-STATUS` (0 when the command worked, −1 for a block the device has not got or cells not all in memory). A block is 1024 bytes: 256 cells, four bytes to a cell, the first byte lowest, at either cell width. This is the console's arrangement (§7) applied to storage; like it, it is the model's stand-in and not the mesh protocol, which §5.11 still leaves to the device node. | **Consequences of D-2 that every definition must respect.** The data stack holds 10 items and the return stack 9, and every `call`, `FOR`, `DO` loop frame and `push` uses return-stack slots. Nesting @@ -1105,4 +1105,4 @@ Addresses are assigned in the node memory map (D-4). Names only here. | `CONSOLE-STATUS` | R | Console receive status: a fetch gives −1 when a character is pending and 0 when not, and changes nothing. `?TERMINAL` reads it, and `KEY` polls it. On the mesh it is the console port's bit of `PORT-STATUS`. | | `DSTACK-DEPTH` | R/W | A fetch gives how many values the data stack holds, the fetch's own push not counted. A store empties the data stack; the value stored is taken off first and ignored. `DEPTH` reads it; `ABORT` stores to it (D-16). | | `RSTACK-DEPTH` | R/W | The same for the return stack. `QUIT`, `ABORT` and the error exits store to it before they call anything. | -| `BLOCK-NUMBER` `BLOCK-ADDRESS` `BLOCK-COMMAND` `BLOCK-STATUS` | R/W | The block storage device on the golden model's host node (D-19). | +| ~~`BLOCK-NUMBER` `BLOCK-ADDRESS` `BLOCK-COMMAND` `BLOCK-STATUS`~~ | | Removed 2026-10-07 (D-19): blocks are a kernel request, not registers. | diff --git a/docs/v4.0.0/MESH.md b/docs/v4.0.0/MESH.md index b46d3669..92b2c586 100644 --- a/docs/v4.0.0/MESH.md +++ b/docs/v4.0.0/MESH.md @@ -276,7 +276,7 @@ kernel-Hermes's work in v3 and is not decided for the mesh. **Ruled 2026-10-06 and 2026-10-07** (Captain Bob). On 2026-10-07 he found this section to have left the OS as designed, and brought it back: every node asks the kernel directly, as every v3 VM does. What that withdrew is -in 8.5, so that it is not proposed again. +in 8.6, so that it is not proposed again. ### 8.1 The rulings that stand @@ -376,7 +376,24 @@ block number not to exist, and on the real chain it does. step 6a. - **Cloud stores and real USB drives** wait for their drivers. -### 8.5 Withdrawn 2026-10-07 +### 8.5 Open, for ruling + +Found by review of step 6 on 2026-10-07; nothing is built for either. + +- **A node keeps its own copy of a block.** `BLOCK` gives the address of + one of the node's two buffers, and the kernel is asked only when a block + is not in one. In v3 a VM's `BLOCK` gives the kernel's buffer, so every + VM sees one copy. Here, if node A has block *n* in a buffer and node B + writes it, A goes on reading what it had; and if A then does `UPDATE` and + `SAVE-BUFFERS`, all of B's block is overwritten. One node alone cannot + see this, and the unit test's readers had never held the block they + read. +- **A block read over the node's own code.** The kernel checks that the + 256 cells are in the node's memory and nothing more, so a request made + by hand with an address in the nucleus overwrites it and the node stops. + `!` can do the same; whether the kernel should refuse it is not ruled. + +### 8.6 Withdrawn 2026-10-07 Each of these was ruled on 2026-10-06 or stood in this document, and each left v3's design. Captain Bob: "something is really off. it sounds like a @@ -595,7 +612,7 @@ Each is tested, committed and pushed before the next. above is step 5 as it was built. 6. **Storage (section 8): every node asks the kernel for its blocks.** **Done 2026-10-07.** It was first built another way and brought back; - 8.5 says what was withdrawn and why. + 8.6 says what was withdrawn and why. - *The requests* (`v4/include/v4/blocks.h`, `v4/system/blocks.c`). -1 reads a block and -2 writes one, `( n waddr -- status )`, for any node, from the kernel's block subsystem. `v4/tests/test_blocks.c`: 22 @@ -624,8 +641,20 @@ Each is tested, committed and pushed before the next. boots, POST 538 of 538, and typed at the prompt: block 2100 written and read back, block 3072 read from the disk, a write to it refused, a block that is not there. `logs/20261007-081603` (amd64), `-081839` - (aarch64), `-082226` (riscv64). The parity hashes are the same on the + (aarch64), `-082226` (riscv64); and again after the review's changes + below, with blocks 1 and 2047 read clean at the prompt: + `logs/20261007-085017`, `-085254`, `-085636`. The parity hashes are the same on the three hosted and the three bare-metal systems. + - *Review, 2026-10-07.* One reading of the whole step by a fresh + reviewer; no critical defect. Changed after it: a block request with + fewer than two values on the stack is refused (it had stopped the + node); on bare metal the node's two buffers are emptied when the + chain takes the place of POST's block RAM (it had gone on holding + POST's block 1), and the chain's fast RAM is cleared in the v4 path; + `blocks.c` is built under each test's own warnings and sanitizers; + the hosted link no longer takes whatever objects lie in its + directory (it had linked the withdrawn `store_v3.o`). Two findings + are for ruling, section 8.5. - **Not as intended yet.** - Who may have which block is not checked (8.4). - On bare metal the virtio disk is read and not written. v3's @@ -637,7 +666,7 @@ Each is tested, committed and pushed before the next. - The suite's unit test is still not run at 32 bits. - **Seen and not changed.** On the v3 path the chain's fast RAM comes from `kmalloc` and is not cleared (`kernel_main.c`, where the chain is - set up); only the ramdrive is. The v4 path does as v3 does. + set up); only the ramdrive is. The v4 path clears its own. **6a. Storage that changes while running.** Rulings 6 and 8 of section 8.1: chains of several devices, the device and chain diff --git a/docs/v4.0.0/V3-PARITY.md b/docs/v4.0.0/V3-PARITY.md index 0ff384fb..a1e0aaf1 100644 --- a/docs/v4.0.0/V3-PARITY.md +++ b/docs/v4.0.0/V3-PARITY.md @@ -194,7 +194,7 @@ space and the metadata are v3's. Still skipped: the owner and first-touch claim, the ACL, and the Stadium touch, which need the node's identity. A first build of that step had block requests passed from node to node and nodes with no storage; Captain Bob withdrew it as a divergence from this -ruling (`MESH.md` 8.5). +ruling (`MESH.md` 8.6). ## 1e. Messaging (discussed and ruled 2026-10-05) diff --git a/docs/v4.0.0/plans/2026-10-06-step6-storage.md b/docs/v4.0.0/plans/2026-10-06-step6-storage.md index 6622b32d..da436e2e 100644 --- a/docs/v4.0.0/plans/2026-10-06-step6-storage.md +++ b/docs/v4.0.0/plans/2026-10-06-step6-storage.md @@ -8,7 +8,7 @@ **Tech Stack:** C99 (engine, strict flags), v3's `block_subsystem.c` and `blkio_*.c` (gnu99), the v4 nucleus dialect (`v4/capsule/*.v4`), `make -C v4`, `make -f kernel/Makefile`. -**Spec:** `docs/v4.0.0/MESH.md` section 8 (8.1 to 8.4; 8.5 lists what was withdrawn), section 9's ruling of 2026-10-07, and step 6 in section 10. `docs/v4.0.0/ENGINE.md` 3.3. `docs/v4.0.0/V3-PARITY.md` 1d. +**Spec:** `docs/v4.0.0/MESH.md` section 8 (8.1 to 8.4; 8.6 lists what was withdrawn), section 9's ruling of 2026-10-07, and step 6 in section 10. `docs/v4.0.0/ENGINE.md` 3.3. `docs/v4.0.0/V3-PARITY.md` 1d. **History:** the first version of this plan built storage as a device speaking messages, with private drives and nodes that had none. Tasks 1 and 2 of it were committed (`0e761cb1`, `4a505a15`) and Tasks 3 and 4 were working in the tree when Captain Bob ruled, on 2026-10-07, that it had left the OS as designed. This version keeps what still serves and removes the rest. diff --git a/kernel/src/kernel_main.c b/kernel/src/kernel_main.c index 08f5b370..c8d713d1 100644 --- a/kernel/src/kernel_main.c +++ b/kernel/src/kernel_main.c @@ -482,6 +482,7 @@ static void sk_v4_block_chain(void) console_println("StarForth v4: no memory for the block chain"); for (;;) { } } + for (i = 0; i < ram_size; i++) blk_ram[i] = 0; /* a node is not to read what was in the kernel's heap */ for (i = 0; i < krd_size; i++) krd[i] = 0; if (capsule_blk_init(NULL, blk_ram, ram_size, krd) != 0) { console_println("StarForth v4: the block chain could not be set up"); diff --git a/kernel/src/v4/sk_v4.c b/kernel/src/v4/sk_v4.c index eb02413f..72ae6597 100644 --- a/kernel/src/v4/sk_v4.c +++ b/kernel/src/v4/sk_v4.c @@ -95,7 +95,15 @@ void sk_v4_run(void (*chain)(void)) console_println("StarForth v4: not started"); for (;;) { } } - if (chain) chain(); + 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 (;;) { } + } + } /* The prompt is the host's: read a line, hand it to the node, say how it * ended. */ diff --git a/logs/20261007-085017/amd64/qemu-amd64-20261007-085017.log b/logs/20261007-085017/amd64/qemu-amd64-20261007-085017.log new file mode 100644 index 00000000..892bbaad --- /dev/null +++ b/logs/20261007-085017/amd64/qemu-amd64-20261007-085017.log @@ -0,0 +1,148 @@ +[=3hBdsDxe: loading Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +BdsDxe: starting Boot0002 "UEFI QEMU DVD-ROM QM00005 " from PciRoot(0x0)/Pci(0x1F,0x2)/Sata(0x2,0xFFFF,0x0) +[=3hStarKernel UEFI Loader +Loading kernel from ESP... +[CKPT 001] Entered efi_main - ConOut live +RAW SERIAL UP +[CKPT 002] Serial (COM1) initialized +Monolithic build - kernel linked directly +Collecting boot information... +CmdLine: parsed OK +[CKPT 004] Command line parsed +[CKPT 005] Kernel stack allocation decided +[CKPT 006] Boot info collected (ACPI table located) +GOP: linear framebuffer found +[CKPT 007] GOP: linear framebuffer found +[CKPT 008] About to enter ExitBootServices retry loop +EBS... +EBS OK +Calling kernel_main (monolithic)... + + + _____ _ _ __ _ + / ____| | | |/ / | | + | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| | + \___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ | + ____) | || (_| | | | . \ __/ | | | | | __/ | + |_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_| + +LithosAnanke v2.1.0 +Architecture: amd64 +Build: Oct 7 2026 08:49:51 + +UEFI BootServices: EXITED + +=== StarKernel Boot Information === +Memory map entries: 135 +Total memory: 1023 MB +Usable memory: 966 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 248749 + Free pages : 247226 + Used pages : 1523 + Total MB : 971 + Free MB : 965 + Used MB : 5 + +VMM initialized (mapped RAM, CR3 switched) +VMM initialized (mapped RAM, CR3 switched) +VMM self-test: mapped OK at 0xffff800000000000 +VMM self-test complete. + +IDT installed. + +APIC: init... +APIC: IA32_APIC_BASE MSR=0x00000000fee00900 +APIC: stale-ISR drain: 0 EOI(s) issued +APIC: initialized (xAPIC MMIO, TPR=0, SIVR=0x1FF, EOI-clear) +APIC: init done + +I/O APIC: init... +I/O APIC: base=0xfec00000, gsi_base=0, overrides=5 + override: source=00 gsi=2 flags=0x0000 + override: source=05 gsi=5 flags=0x000d + override: source=09 gsi=9 flags=0x000d + override: source=0a gsi=10 flags=0x000d + override: source=0b gsi=11 flags=0x000d +i8042: keyboard ACKed enable-scanning +i8042: IRQ1 enabled in controller config byte +I/O APIC: keyboard IRQ1 routed (masked) + +Timer: init... +Timer: init start +Timer: PM_TMR_BLK discovered from FADT at port 1544 +Timer: VM mode detected (hypervisor present). +Timer: HPET calibration disabled (VM-exit MMIO would poison timing). +Timer: WARNING: invariant TSC not present under hypervisor. +Timer: continuing in RELATIVE mode (no determinism guarantees). +Timer: RDTSCP not present; using RDTSC (less serialized). +Timer: CPUID frequency unavailable; trying PM Timer... +Timer: trust=1 (0=NONE,1=REL,2=ABS), TSC=2102367869 Hz +Timer: init done + +Kernel heap initialized. +Heap statistics: + Total bytes: 536870872 + Free bytes: 536870872 + Used bytes: 0 + Peak bytes: 0 + Heap base addr: 24641536 + Heap end addr: 561512448 + +Heartbeat: init... +APIC Timer: calibrating... +APIC Timer: apic_hz=1009406533, tick_hz=100, initial_count=10094065 +APIC Timer: configured (masked, ready to start) +Heartbeat: init done +Kernel initialization complete. +Boot successful! + +StarForth v4: one host node, the F18-derived engine +V4: capsule v4:nucleus-64.f18 signature: missing (unsigned) +PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=0x4eb584bf6c169ea1 capsule_hash=0x4eb584bf6c169ea1 words=310 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0xe6453dd9449a3f64 +V4: capsule v4:post79.4th signature: missing (unsigned) +PARITY:V4_POST tests=538 pass=538 fail=0 +PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb26d1b974884179d capsule_hash=0xb26d1b974884179d dict_hash=0xab589e162cb5e638 +PARITY:OK +POST: PASSED +[HADES][INFO ] [KRELTSC: 0] blk: raw device LBN 2048..3071 (1024 blocks) +PCI: init... +PCI: ECAM mapped (amd64) +virtio-blk: found device +Artemis: virtio-blk attached +[HADES][INFO ] [KRELTSC: 77646744] blk: disk 'StarForth Volume' v2 LBN 3072..789009 (785938 user b +ok> : SQ DUP * ; + ok +ok> 7 SQ . 3 4 U* . . +49 0 12 ok +ok> COLD +FORTH-79 Cold Start +System initialized. + ok +ok> 3 4 U* . . +0 12 ok +ok> FORGET U* +Protected word + ERROR +ok> 9 KERNEL-WORD ASK9 ASK9 +Argument out of range + ERROR +ok> 1 BLOCK C@ . 2047 BLOCK C@ . +0 0 ok +ok> 2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ . +65 ok +ok> 3072 BLOCK C@ . +0 ok +ok> 3072 BLOCK DROP UPDATE SAVE-BUFFERS +Storage refused + ERROR +ok> 9999999 BLOCK +Block out of range + ERROR +ok> BYE +BYE: cold restart diff --git a/logs/20261007-085254/aarch64/qemu-aarch64-20261007-085254.log b/logs/20261007-085254/aarch64/qemu-aarch64-20261007-085254.log new file mode 100644 index 00000000..8580fc35 --- /dev/null +++ b/logs/20261007-085254/aarch64/qemu-aarch64-20261007-085254.log @@ -0,0 +1,121 @@ +UEFI firmware (version 2025.11-3ubuntu7.3 built at 15:40:26 on Sep 23 2026) +[=3hBdsDxe: failed to load Boot0002 "UEFI Misc Device" from PciRoot(0x0)/Pci(0x2,0x0): Not Found +BdsDxe: failed to load Boot0003 "UEFI QEMU QEMU USB HARDDRIVE 1-0000:00:04.0-1" from PciRoot(0x0)/Pci(0x4,0x0)/USB(0x0,0x0): Not Found +BdsDxe: loading Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0) +BdsDxe: starting Boot0004 "UEFI Misc Device 2" from PciRoot(0x0)/Pci(0x6,0x0) +[=3hStarKernel UEFI Loader +Loading kernel from ESP... +[CKPT 001] Entered efi_main - ConOut live +Collecting boot information... +[CKPT 004] Command line parsed +[CKPT 005] Kernel stack allocation decided +[CKPT 006] Boot info collected (ACPI table located) +[CKPT 007] GOP: linear framebuffer found +[CKPT 008] About to enter ExitBootServices retry loop + + + _____ _ _ __ _ + / ____| | | |/ / | | + | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| | + \___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ | + ____) | || (_| | | | . \ __/ | | | | | __/ | + |_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_| + +LithosAnanke v2.1.0 +Architecture: aarch64 +Build: Oct 7 2026 08:52:28 + +UEFI BootServices: EXITED + +=== StarKernel Boot Information === +Memory map entries: 108 +Total memory: 4093 MB +Usable memory: 4054 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 1039362 + Free pages : 1037951 + Used pages : 1411 + Total MB : 4060 + Free MB : 4054 + Used MB : 5 + +VMM initialized (mapped RAM, CR3 switched) +VMM initialized (mapped RAM, CR3 switched) +VMM self-test: mapped OK at 0xffff800000000000 +VMM self-test complete. + +AArch64: running at EL1 +IDT installed. + +APIC: init... +PSCI: no DTB -- using HVC (QEMU virt-machine default) +GICv2: no DTB GIC node -- using QEMU virt-machine defaults +GICv2: distributor+CPU interface enabled, PPI 30 +APIC: init done + +Timer: init... +Timer: AArch64 generic timer initialised. +Timer: init done + +Kernel heap initialized. +Heap statistics: + Total bytes: 2147483608 + Free bytes: 2147483608 + Used bytes: 0 + Peak bytes: 0 + Heap base addr: 1207959552 + Heap end addr: 3355443200 + +Heartbeat: init... +Heartbeat: init done +Kernel initialization complete. +Boot successful! + +StarForth v4: one host node, the F18-derived engine +V4: capsule v4:nucleus-64.f18 signature: missing (unsigned) +PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=0x4eb584bf6c169ea1 capsule_hash=0x4eb584bf6c169ea1 words=310 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0xe6453dd9449a3f64 +V4: capsule v4:post79.4th signature: missing (unsigned) +PARITY:V4_POST tests=538 pass=538 fail=0 +PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb26d1b974884179d capsule_hash=0xb26d1b974884179d dict_hash=0xab589e162cb5e638 +PARITY:OK +POST: PASSED +[HADES][INFO ] [KRELTSC: 0] blk: raw device LBN 2048..3071 (1024 blocks) +PCI: init... +virtio-blk: found device +Artemis: virtio-blk attached +[HADES][INFO ] [KRELTSC: 1372126] blk: disk 'StarForth Volume' v2 LBN 3072..789009 (785938 user b +ok> : SQ DUP * ; + ok +ok> 7 SQ . 3 4 U* . . +49 0 12 ok +ok> COLD +FORTH-79 Cold Start +System initialized. + ok +ok> 3 4 U* . . +0 12 ok +ok> FORGET U* +Protected word + ERROR +ok> 9 KERNEL-WORD ASK9 ASK9 +Argument out of range + ERROR +ok> 1 BLOCK C@ . 2047 BLOCK C@ . +0 0 ok +ok> 2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ . +65 ok +ok> 3072 BLOCK C@ . +0 ok +ok> 3072 BLOCK DROP UPDATE SAVE-BUFFERS +Storage refused + ERROR +ok> 9999999 BLOCK +Block out of range + ERROR +ok> BYE +BYE: cold restart diff --git a/logs/20261007-085636/riscv64/qemu-riscv64-20261007-085636.log b/logs/20261007-085636/riscv64/qemu-riscv64-20261007-085636.log new file mode 100644 index 00000000..4df7ba83 --- /dev/null +++ b/logs/20261007-085636/riscv64/qemu-riscv64-20261007-085636.log @@ -0,0 +1,191 @@ + +OpenSBI v1.8 + ____ _____ ____ _____ + / __ \ / ____| _ \_ _| + | | | |_ __ ___ _ __ | (___ | |_) || | + | | | | '_ \ / _ \ '_ \ \___ \| _ < | | + | |__| | |_) | __/ | | |____) | |_) || |_ + \____/| .__/ \___|_| |_|_____/|____/_____| + | | + |_| + +Platform Name : riscv-virtio,qemu +Platform Features : medeleg +Platform HART Count : 1 +Platform HART Protection : pmp +Platform IPI Device : aclint-mswi +Platform Timer Device : aclint-mtimer @ 10000000Hz +Platform Console Device : uart8250 +Platform HSM Device : --- +Platform PMU Device : --- +Platform Reboot Device : syscon-reboot +Platform Shutdown Device : syscon-poweroff +Platform Suspend Device : --- +Platform CPPC Device : --- +Firmware Base : 0x80000000 +Firmware Size : 321 KB +Firmware RW Offset : 0x40000 +Firmware RW Size : 65 KB +Firmware Heap Offset : 0x47000 +Firmware Heap Size : 37 KB (total), 0 KB (reserved), 12 KB (used), 23 KB (free) +Firmware Scratch Size : 4096 B (total), 1464 B (used), 2632 B (free) +Runtime SBI Version : 3.0 +Standard SBI Extensions : ipi,pmu,srst,sse,hsm,rfnc,fwft,time,base,legacy,dbcn,dbtr +Experimental SBI Extensions : none + +Domain0 Name : root +Domain0 Boot HART : 0 +Domain0 HARTs : 0* +Domain0 Region00 : 0x0000000080040000-0x000000008005ffff M: (F,R,W) S/U: () +Domain0 Region01 : 0x0000000080000000-0x000000008003ffff M: (F,R,X) S/U: () +Domain0 Region02 : 0x0000000000100000-0x0000000000100fff M: (I,R,W) S/U: (R,W) +Domain0 Region03 : 0x0000000010000000-0x0000000010000fff M: (I,R,W) S/U: (R,W) +Domain0 Region04 : 0x0000000002000000-0x000000000200ffff M: (I,R,W) S/U: () +Domain0 Region05 : 0x000000000c400000-0x000000000c5fffff M: (I,R,W) S/U: (R,W) +Domain0 Region06 : 0x000000000c000000-0x000000000c3fffff M: (I,R,W) S/U: (R,W) +Domain0 Region07 : 0x0000000000000000-0xffffffffffffffff M: () S/U: (R,W,X) +Domain0 Next Address : 0x0000000020000000 +Domain0 Next Arg1 : 0x00000000bfe00000 +Domain0 Next Mode : S-mode +Domain0 SysReset : yes +Domain0 SysSuspend : yes + +Boot HART ID : 0 +Boot HART Domain : root +Boot HART Priv Version : v1.12 +Boot HART Base ISA : rv64imafdch +Boot HART ISA Extensions : sstc,zicntr,zihpm,zicboz,zicbom,sdtrig,svadu +Boot HART PMP Count : 16 +Boot HART PMP Granularity : 2 bits +Boot HART PMP Address Bits : 54 +Boot HART MHPM Info : 16 (0x0007fff8) +Boot HART Debug Triggers : 2 triggers +Boot HART MIDELEG : 0x0000000000001666 +Boot HART MEDELEG : 0x0000000000f4b509 +[=3hRISC-V EDK2 firmware version 2025.11-3ubuntu7.3 +Press ESCAPE within 5 seconds for boot options ERROR: C40000002:V03051002 I0 6D33944A-EC75-4855-A54D-809C75241F6C 83FFF850 +BdsDxe: failed to load Boot0001 "UEFI Misc Device" fr +om PciRoot(0x0)/Pci(0x1,0x0): Not Found +[=3hStarKernel UEFI Loader +Loading kernel from ESP... +[CKPT 001] Entered efi_main - ConOut live +Monolithic build - kernel linked directly +Collecting boot information... +CmdLine: parsed OK +[CKPT 004] Command line parsed +[CKPT 005] Kernel stack allocation decided +[CKPT 006] Boot info collected (ACPI table located) +GOP: linear framebuffer found +[CKPT 007] GOP: linear framebuffer found +[CKPT 008] About to enter ExitBootServices retry loop +Calling kernel_main (monolithic)... +riscv64 item 4.3.5a: satp state at kernel entry (before switch) + satp.MODE = 0x000000000000000a + satp.PPN = 0x00000000000bf868 + __kernel_start = 0x00000000bdcab9f8 +riscv64: satp cleared -- Bare mode, explicit (item 4.3.5a) + + + _____ _ _ __ _ + / ____| | | |/ / | | + | (___ | |_ __ _ _ __| ' / ___ _ __ _ __ ___| | + \___ \| __/ _` | '__| < / _ \ '__| '_ \ / _ \ | + ____) | || (_| | | | . \ __/ | | | | | __/ | + |_____/ \__\__,_|_| |_|\_\___|_| |_| |_|\___|_| + +LithosAnanke v2.1.0 +Architecture: riscv64 +Build: Oct 7 2026 08:55:59 + +UEFI BootServices: EXITED + +=== StarKernel Boot Information === +Memory map entries: 100 +Total memory: 1020 MB +Usable memory: 975 MB +=================================== + +PMM initialized. +PMM statistics: + Total pages: 250276 + Free pages : 249624 + Used pages : 652 + Total MB : 977 + Free MB : 975 + Used MB : 2 + +VMM initialized (mapped RAM, CR3 switched) +VMM initialized (mapped RAM, CR3 switched) +VMM self-test: mapped OK at 0xffff800000000000 +VMM self-test complete. + +IDT installed. + +APIC: init... +PLIC: no DTB PLIC node -- using QEMU virt-machine default (base=0x0c000000) +PLIC: init (S-mode context 1, threshold=0) +APIC: init done + +Timer: init... +Timer: RISC-V time CSR @ 10000000 Hz (FALLBACK, no devicetree) +Timer: init done + +Kernel heap initialized. +Heap statistics: + Total bytes: 536870872 + Free bytes: 536870872 + Used bytes: 0 + Peak bytes: 0 + Heap base addr: 2214588416 + Heap end addr: 2751459328 + +Heartbeat: init... +Heartbeat: init done +Kernel initialization complete. +Boot successful! + +StarForth v4: one host node, the F18-derived engine +V4: capsule v4:nucleus-64.f18 signature: missing (unsigned) +PARITY:V4_NUCLEUS name=v4:nucleus-64.f18 capsule_id=0x4eb584bf6c169ea1 capsule_hash=0x4eb584bf6c169ea1 words=310 +V4: capsule v4:forth79.4th signature: missing (unsigned) +PARITY:V4_CAPSULE name=v4:forth79.4th capsule_id=0x4055641ee17d176b capsule_hash=0x4055641ee17d176b dict_hash=0xe6453dd9449a3f64 +V4: capsule v4:post79.4th signature: missing (unsigned) +PARITY:V4_POST tests=538 pass=538 fail=0 +PARITY:V4_CAPSULE name=v4:post79.4th capsule_id=0xb26d1b974884179d capsule_hash=0xb26d1b974884179d dict_hash=0xab589e162cb5e638 +PARITY:OK +POST: PASSED +[HADES][INFO ] [KRELTSC: 0] blk: raw device LBN 2048..3071 (1024 blocks) +PCI: init... +virtio-blk: found device +Artemis: virtio-blk attached +[HADES][INFO ] [KRELTSC: 33683055] blk: disk 'StarForth Volume' v2 LBN 3072..789009 (785938 user b +ok> : SQ DUP * ; + ok +ok> 7 SQ . 3 4 U* . . +49 0 12 ok +ok> COLD +FORTH-79 Cold Start +System initialized. + ok +ok> 3 4 U* . . +0 12 ok +ok> FORGET U* +Protected word + ERROR +ok> 9 KERNEL-WORD ASK9 ASK9 +Argument out of range + ERROR +ok> 1 BLOCK C@ . 2047 BLOCK C@ . +0 0 ok +ok> 2100 BLOCK 1024 BLANK 65 2100 BLOCK C! UPDATE SAVE-BUFFERS EMPTY-BUFFERS 2100 BLOCK C@ . +65 ok +ok> 3072 BLOCK C@ . +0 ok +ok> 3072 BLOCK DROP UPDATE SAVE-BUFFERS +Storage refused + ERROR +ok> 9999999 BLOCK +Block out of range + ERROR +ok> BYE +BYE: cold restart diff --git a/v4/Makefile b/v4/Makefile index 7c5e2465..2608683d 100644 --- a/v4/Makefile +++ b/v4/Makefile @@ -63,24 +63,18 @@ uses_blocks = $(or $(findstring /test_blocks,$(1)),$(findstring /test_host_,$(1) blocks_inc = $(if $(call uses_blocks,$(1)),$(V3_INC)) $(BINDIR)/v3blocks.o: $(V3_BLOCK_SRCS) $(wildcard $(ROOT_DIR)/v3/include/*.h) $(HERE)/Makefile - @mkdir -p $(BINDIR)/v3o + @rm -rf $(BINDIR)/v3o && mkdir -p $(BINDIR)/v3o cd $(BINDIR)/v3o && $(CC) $(V3_CFLAGS) -c $(V3_BLOCK_SRCS) $(LD) -r $(BINDIR)/v3o/*.o -o $@ $(BINDIR)/san-v3blocks.o: $(V3_BLOCK_SRCS) $(wildcard $(ROOT_DIR)/v3/include/*.h) $(HERE)/Makefile - @mkdir -p $(BINDIR)/san-v3o + @rm -rf $(BINDIR)/san-v3o && mkdir -p $(BINDIR)/san-v3o cd $(BINDIR)/san-v3o && $(CC) $(V3_CFLAGS) -O1 -g -fno-omit-frame-pointer -fsanitize=address,undefined -fno-sanitize-recover=all -c $(V3_BLOCK_SRCS) $(LD) -r $(BINDIR)/san-v3o/*.o -o $@ -# blocks.c holds a node, so it is built for each node a test may have: by -# width, and for the host node's size or the mesh node's. -define BLOCKS_RULE -$(BINDIR)/blocks-$(1)-$(2).o: $(BLOCKS_SRC) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile - @mkdir -p $(BINDIR) - $$(CC) $(V3_CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=$(1) $(if $(filter host,$(2)),-DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING)) -c $(BLOCKS_SRC) -o $$@ -endef -$(foreach w,32 64,$(foreach k,host mesh,$(eval $(call BLOCKS_RULE,$(w),$(k))))) -blocks_kind = $(if $(findstring /test_host_,$(1)),host,mesh) -blocks_objs = $(if $(call uses_blocks,$(2)),$(BINDIR)/blocks-$(1)-$(call blocks_kind,$(2)).o $(BINDIR)/v3blocks.o) -san_blocks_objs = $(if $(call uses_blocks,$(2)),$(BINDIR)/blocks-$(1)-$(call blocks_kind,$(2)).o $(BINDIR)/san-v3blocks.o) +# blocks.c is compiled with each test that uses it, under that test's own +# flags: its warnings, its sanitizers, its cell width and node size. +blocks_src = $(if $(call uses_blocks,$(1)),$(BLOCKS_SRC)) +blocks_objs = $(if $(call uses_blocks,$(1)),$(BINDIR)/v3blocks.o) +san_blocks_objs = $(if $(call uses_blocks,$(1)),$(BINDIR)/san-v3blocks.o) # The capsule sources: definitions as text (include/v4/text.h), which tests # assemble onto a node. The directory is passed to the tests so that they @@ -160,7 +154,7 @@ $(CAPSULE_DIR_C): $(BINDIR)/mkcapsule $(CAPSULE_FILES) $(BINDIR)/v4_image_64.c define HOSTED_RULE $(BINDIR)/starforth4-$(1): $(HERE)/tools/hosted.c $(BINDIR)/v4_image_64.c $(CAPSULE_DIR_C) $$(ENGINE_SRCS) $$(SYSTEM_SRCS) $(BLOCKS_SRC) $(V3_BLOCK_SRCS) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile $$(CC_$(1)) $$(CFLAGS) -D_POSIX_C_SOURCE=200809L -I$(HERE)/include $(V3_INC) -DV4_CELL_BITS=64 $(HOST_DEFS) -c $(HERE)/tools/hosted.c -o $(BINDIR)/hosted-$(1).o - @mkdir -p $(BINDIR)/v3o-$(1) + @rm -rf $(BINDIR)/v3o-$(1) && mkdir -p $(BINDIR)/v3o-$(1) cd $(BINDIR)/v3o-$(1) && $$(CC_$(1)) $(V3_CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=64 $(HOST_DEFS) -c $(V3_BLOCK_SRCS) $(BLOCKS_SRC) $$(CC_$(1)) $$(SYSTEM_CFLAGS) -static $(BINDIR)/hosted-$(1).o $(BINDIR)/v4_image_64.c $(CAPSULE_DIR_C) $$(ENGINE_SRCS) $$(SYSTEM_SRCS) $(BINDIR)/v3o-$(1)/*.o -o $$@ @@ -226,16 +220,16 @@ endif # # Both binaries depend on this Makefile, so a flag change rebuilds them. define TEST_RULE -$(BINDIR)/$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call blocks_objs,$(1),$(2)) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile +$(BINDIR)/$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call blocks_src,$(2)) $(call blocks_objs,$(2)) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile @mkdir -p $(BINDIR) $$(CC) $$(CFLAGS) -I$(HERE)/include $(call blocks_inc,$(2)) -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \ - $(2) $$(SRCS) $(call blocks_objs,$(1),$(2)) -o $$@ + $(2) $$(SRCS) $(call blocks_src,$(2)) $(call blocks_objs,$(2)) -o $$@ -$(BINDIR)/san-$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call san_blocks_objs,$(1),$(2)) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile +$(BINDIR)/san-$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call blocks_src,$(2)) $(call san_blocks_objs,$(2)) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile @mkdir -p $(BINDIR) $$(CC) $$(CSTD) $$(WARN) -O1 -g -fno-omit-frame-pointer \ -fsanitize=address,undefined -fno-sanitize-recover=all -I$(HERE)/include $(call blocks_inc,$(2)) -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \ - $(2) $$(SRCS) $(call san_blocks_objs,$(1),$(2)) -o $$@ + $(2) $$(SRCS) $(call blocks_src,$(2)) $(call san_blocks_objs,$(2)) -o $$@ endef # One run target per (width, test) and one prerequisite line per width, so diff --git a/v4/include/v4/blocks.h b/v4/include/v4/blocks.h index cd32ab79..d6465d99 100644 --- a/v4/include/v4/blocks.h +++ b/v4/include/v4/blocks.h @@ -12,7 +12,9 @@ * and their numbers are below zero: the requests a host names for its own * words (KERNEL-WORD) count up from 1. The status is 0, or * V4_BLOCK_REFUSED -- the cells are not all in the node's memory, or - * storage will not have it -- or V4_BLOCK_RANGE, there is no such block. + * storage will not have it, or the stack did not hold both arguments, in + * which case the status is all that is left on it -- or V4_BLOCK_RANGE, + * there is no such block. * * A block is 1024 bytes and 256 cells, four bytes to a cell, the first * lowest, whatever the cell width: a read leaves the rest of each cell diff --git a/v4/include/v4/node.h b/v4/include/v4/node.h index a63a963f..53a5f1c9 100644 --- a/v4/include/v4/node.h +++ b/v4/include/v4/node.h @@ -331,9 +331,9 @@ void v4_node_fault(v4_node *n, unsigned kind, v4_cell addr); * detaches both (-1); the addresses are the caller's choice (D-4). */ void v4_node_stack_regs_attach(v4_node *n, v4_cell d, v4_cell r); -/* A BLOCK (DECOMPOSITION.md D-19, 5.11). Mass storage is a device on a - * port that speaks messages (docs/v4.0.0/MESH.md section 8; storage.h); the - * node has nothing of it but these. A block is 1024 bytes, and a byte +/* A BLOCK (DECOMPOSITION.md D-19, 5.11). Mass storage is the kernel's: a + * node asks it for a block by a request on port 0 (docs/v4.0.0/MESH.md + * section 8; blocks.h), and has nothing of it but these. A block is 1024 bytes, and a byte * address is four times a word address plus 0 .. 3 (D-1), so a block is 256 * cells, four bytes to a cell, the first byte lowest, whatever the cell * width. */ diff --git a/v4/system/blocks.c b/v4/system/blocks.c index 2bc7a857..0971aab8 100644 --- a/v4/system/blocks.c +++ b/v4/system/blocks.c @@ -15,6 +15,11 @@ int v4_blocks_serve(v4_node *n, v4_cell request) int status; if (request != V4_REQ_BLOCK_READ && request != V4_REQ_BLOCK_WRITE) return 0; + if (n->ds.depth < 2u) { /* not both arguments: nothing is done with what is there */ + v4_dstack_reset(&n->ds); + v4_dstack_push(&n->ds, (v4_cell)V4_BLOCK_REFUSED); + return 1; + } addr = v4_dstack_pop(&n->ds); num = v4_dstack_pop(&n->ds); block = (uint32_t)num; diff --git a/v4/tests/test_blocks.c b/v4/tests/test_blocks.c index 45e9599f..d014a229 100644 --- a/v4/tests/test_blocks.c +++ b/v4/tests/test_blocks.c @@ -83,6 +83,17 @@ int main(void) CHECK(ask(V4_REQ_BLOCK_READ, 2050, -1) == V4_BLOCK_REFUSED && ask(V4_REQ_BLOCK_WRITE, 2050, top) == V4_BLOCK_REFUSED, "an address below 0, or at the end, is refused"); CHECK(dev[2 * 1024] == 0x44, "and nothing was written"); + /* ---- a request with too little on the stack ---- */ + { + unsigned char before = dev[2 * 1024]; + v4_cell low = n.mem[0]; + v4_dstack_reset(&n.ds); + CHECK(v4_blocks_serve(&n, V4_REQ_BLOCK_READ) && n.ds.depth == 1 && v4_dstack_pop(&n.ds) == V4_BLOCK_REFUSED, "a request with nothing on the stack is refused, and the status is all that is left"); + v4_dstack_push(&n.ds, 2050); + CHECK(v4_blocks_serve(&n, V4_REQ_BLOCK_WRITE) && n.ds.depth == 1 && v4_dstack_pop(&n.ds) == V4_BLOCK_REFUSED, "and so is one with a single value"); + CHECK(dev[2 * 1024] == before && n.mem[0] == low, "nothing was read or written"); + } + /* ---- what v3 wrote ---- */ { uint8_t *b = blk_get_buffer(2070, 1);