A block write the device refuses after the kernel has taken the node's copy: the node was told "Storage refused" while the kernel's cache kept the new data, to be read back and perhaps written later. The kernel now puts its own copy back as it was. Tested with a device whose writes can be made to fail; it failed first. POST's runner: a case whose starting state could not be set says so, with what the node said, where it showed nothing; and a case that ends with QUIT fails, as it did when the cases were a capsule, where the runner had taken it for a completed line. Both tests failed first. The v4 boot says "Artemis: virtio-blk attached" only when the attach worked. make -C v4 test, sanitize and hosted-check pass; amd64, aarch64 and riscv64 boot, POST 538 of 538, dict_hash 0x5f0a949a6fc8ef2b on all six: logs/20261007-135741, -140004, -140329. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
220 lines
13 KiB
C
220 lines
13 KiB
C
/* test_blocks.c -- the kernel serves a node's block requests.
|
|
* docs/v4.0.0/MESH.md sections 8.2 and 8.3.
|
|
*
|
|
* A host node, stopped, with what a request takes on its data stack; the blocks
|
|
* are v3's block subsystem (v3/src/block_subsystem.c): its fast RAM and a
|
|
* device of 64 blocks. The window is four slots of 256 cells in the node's
|
|
* memory, and the kernel keeps the record of what is in them.
|
|
*/
|
|
#include "v4/blocks.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "block_subsystem.h"
|
|
|
|
extern void sf_time_init(void);
|
|
extern void log_set_level(int level);
|
|
|
|
static int failures = 0, checks = 0;
|
|
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
|
|
|
|
#define DEVICE_BLOCKS 64u
|
|
#define WINDOW 200
|
|
static unsigned char ram[(size_t)BLK_RAM_BLOCKS * BLK_FORTH_SIZE], dev[DEVICE_BLOCKS * BLK_FORTH_SIZE];
|
|
static v4_node n;
|
|
static v4_blocks w;
|
|
static v4_cell got_addr;
|
|
|
|
/* a request that takes a block's number: its status, and the address in got_addr */
|
|
static v4_cell ask(v4_cell request, v4_cell block)
|
|
{
|
|
v4_cell status;
|
|
v4_dstack_reset(&n.ds);
|
|
v4_dstack_push(&n.ds, 7777);
|
|
v4_dstack_push(&n.ds, block);
|
|
if (!v4_blocks_serve(&w, &n, request)) return -100;
|
|
status = v4_dstack_pop(&n.ds);
|
|
got_addr = v4_dstack_pop(&n.ds);
|
|
return (v4_dstack_pop(&n.ds) == 7777 && n.ds.depth == 0) ? status : -200; /* what was under its argument is still there */
|
|
}
|
|
/* a request that takes nothing: its status */
|
|
static v4_cell tell(v4_cell request)
|
|
{
|
|
v4_dstack_reset(&n.ds);
|
|
v4_dstack_push(&n.ds, 7777);
|
|
if (!v4_blocks_serve(&w, &n, request)) return -100;
|
|
if (request == V4_REQ_EMPTY_BUFFERS) return (v4_dstack_pop(&n.ds) == 7777 && n.ds.depth == 0) ? 0 : -200;
|
|
{
|
|
v4_cell status = v4_dstack_pop(&n.ds);
|
|
return (v4_dstack_pop(&n.ds) == 7777 && n.ds.depth == 0) ? status : -200;
|
|
}
|
|
}
|
|
static int in_window(v4_cell a) { return a >= WINDOW && a < WINDOW + (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS) && (a - WINDOW) % (v4_cell)V4_BLOCK_CELLS == 0; }
|
|
static unsigned char *chain_block(unsigned num) { return num < 2048u ? ram + ((size_t)num + BLK_FORTH_SYS_RESERVED) * 1024u : dev + (size_t)(num - 2048u) * 1024u; }
|
|
|
|
/* A device whose writes can be made to fail: 2048 blocks of RAM behind v3's
|
|
* device interface (v3/include/blkio.h). */
|
|
#define FAILING_BLOCKS 2048u
|
|
static unsigned char failing_bytes[FAILING_BLOCKS * 1024u];
|
|
static int failing;
|
|
static int f_open(struct blkio_dev *d, const blkio_params_t *p) { (void)d; (void)p; return BLKIO_OK; }
|
|
static int f_close(struct blkio_dev *d) { (void)d; return BLKIO_OK; }
|
|
static int f_read(struct blkio_dev *d, uint32_t b, void *dst) { (void)d; memcpy(dst, failing_bytes + (size_t)b * 1024u, 1024); return BLKIO_OK; }
|
|
static int f_write(struct blkio_dev *d, uint32_t b, const void *src)
|
|
{
|
|
(void)d;
|
|
if (failing) return BLKIO_EIO;
|
|
memcpy(failing_bytes + (size_t)b * 1024u, src, 1024);
|
|
return BLKIO_OK;
|
|
}
|
|
static int f_flush(struct blkio_dev *d) { (void)d; return failing ? BLKIO_EIO : BLKIO_OK; }
|
|
static int f_info(struct blkio_dev *d, blkio_info_t *out) { (void)d; memset(out, 0, sizeof *out); return BLKIO_OK; }
|
|
static const blkio_vtable_t failing_vt = { f_open, f_close, f_read, f_write, f_flush, f_info };
|
|
|
|
int main(void)
|
|
{
|
|
unsigned i;
|
|
v4_cell a, b;
|
|
|
|
printf("v4 block request tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
|
|
sf_time_init();
|
|
log_set_level(-1);
|
|
CHECK(blk_subsys_init(ram, sizeof ram) == BLK_OK && blk_subsys_add_raw_device(dev, DEVICE_BLOCKS) == BLK_OK, "the kernel's chain: fast RAM and a device of 64 blocks");
|
|
v4_node_reset(&n);
|
|
v4_blocks_init(&w, WINDOW);
|
|
|
|
/* ---- BLOCK: the block is copied into a slot of the window ---- */
|
|
memset(chain_block(2050), 'A', 1024);
|
|
chain_block(2050)[1] = 'B';
|
|
n.mem[WINDOW - 1] = 111; n.mem[WINDOW + V4_BLOCK_SLOTS * V4_BLOCK_CELLS] = 222;
|
|
CHECK(ask(V4_REQ_BLOCK, 2050) == V4_BLOCK_OK && in_window(got_addr), "BLOCK gives the address of a slot of the window");
|
|
a = got_addr;
|
|
CHECK(n.mem[a] == (v4_cell)0x41414241 && n.mem[a + 255] == (v4_cell)0x41414141, "holding the block, four bytes to a cell, the first lowest, the rest of each cell zero");
|
|
chain_block(2050)[0] = 'Z';
|
|
CHECK(ask(V4_REQ_BLOCK, 2050) == V4_BLOCK_OK && got_addr == a && n.mem[a] == (v4_cell)0x41414241, "a block already in a slot is given as it is there, and not read again");
|
|
CHECK(ask(V4_REQ_BLOCK, 20) == V4_BLOCK_OK && in_window(got_addr) && got_addr != a, "another block is put in another slot");
|
|
CHECK(n.mem[WINDOW - 1] == 111 && n.mem[WINDOW + V4_BLOCK_SLOTS * V4_BLOCK_CELLS] == 222, "nothing outside the window is written");
|
|
|
|
/* ---- UPDATE marks; nothing is written until SAVE-BUFFERS ---- */
|
|
CHECK(ask(V4_REQ_BLOCK, 2051) == V4_BLOCK_OK, "block 2051 is asked for");
|
|
b = got_addr;
|
|
n.mem[b] = (v4_cell)0x64636261;
|
|
#if V4_CELL_BITS > 32
|
|
n.mem[b + 1] = (v4_cell)0x7766554433221100LL; /* only its low 32 bits are the block's */
|
|
#endif
|
|
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK && chain_block(2051)[0] == 0, "UPDATE marks it, and it is not written yet");
|
|
CHECK(tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK && memcmp(chain_block(2051), "abcd", 4) == 0, "SAVE-BUFFERS writes it");
|
|
#if V4_CELL_BITS > 32
|
|
CHECK(chain_block(2051)[4] == 0x00 && chain_block(2051)[7] == 0x33 && chain_block(2051)[8] == 0, "the low 32 bits of each cell, and no more");
|
|
#endif
|
|
CHECK(chain_block(2050)[0] == 'Z' && chain_block(2050)[1] == 'B', "a block that was not UPDATEd is not written");
|
|
n.mem[b] = (v4_cell)0x31313131;
|
|
CHECK(tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK && chain_block(2051)[0] == 'a', "nor is one changed again and not UPDATEd again");
|
|
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK, "UPDATE is of the block most recently asked for");
|
|
CHECK(ask(V4_REQ_BLOCK, 2050) == V4_BLOCK_OK && tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK
|
|
&& chain_block(2051)[0] == '1' && chain_block(2050)[0] == 'A', "two UPDATEd blocks are both written, each from its own slot");
|
|
|
|
/* ---- EMPTY-BUFFERS forgets, and writes nothing ---- */
|
|
n.mem[a] = (v4_cell)0x58585858;
|
|
CHECK(ask(V4_REQ_BLOCK, 2050) == V4_BLOCK_OK && tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_EMPTY_BUFFERS) == 0 && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK
|
|
&& chain_block(2050)[0] == 'A', "EMPTY-BUFFERS forgets an UPDATEd block: it is not written");
|
|
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK, "UPDATE with no block asked for does nothing");
|
|
chain_block(2050)[0] = 'Q';
|
|
CHECK(ask(V4_REQ_BLOCK, 2050) == V4_BLOCK_OK && (n.mem[got_addr] & 0xFF) == 'Q', "and the block is read again when it is next asked for");
|
|
|
|
/* ---- BUFFER: a slot for the block, not read ---- */
|
|
(void)tell(V4_REQ_EMPTY_BUFFERS);
|
|
CHECK(ask(V4_REQ_BUFFER, 2060) == V4_BLOCK_OK && in_window(got_addr) && n.mem[got_addr] == 0 && n.mem[got_addr + 255] == 0, "BUFFER gives a slot of zeros, as v3's does");
|
|
n.mem[got_addr] = (v4_cell)0x62626262;
|
|
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK && chain_block(2060)[0] == 'b' && chain_block(2060)[4] == 0, "and what is put in it is written");
|
|
CHECK(ask(V4_REQ_BLOCK, 2060) == V4_BLOCK_OK && n.mem[got_addr] == (v4_cell)0x62626262, "BLOCK then finds it in its slot");
|
|
|
|
/* ---- more blocks than slots ---- */
|
|
(void)tell(V4_REQ_EMPTY_BUFFERS);
|
|
for (i = 0; i < V4_BLOCK_SLOTS; i++) {
|
|
CHECK(ask(V4_REQ_BLOCK, 2100 + (v4_cell)i) == V4_BLOCK_OK, "block %u is asked for", 2100 + i);
|
|
n.mem[got_addr] = (v4_cell)(0x30303030 + i);
|
|
}
|
|
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK, "the last of them is UPDATEd");
|
|
CHECK(ask(V4_REQ_BLOCK, 2110) == V4_BLOCK_OK && chain_block(2100 + V4_BLOCK_SLOTS - 1)[0] == 0, "one more takes a slot whose block was not UPDATEd, and nothing is written");
|
|
for (i = 0; i < V4_BLOCK_SLOTS; i++) { (void)ask(V4_REQ_BLOCK, 2100 + (v4_cell)i); n.mem[got_addr] = (v4_cell)(0x30303030 + i); (void)tell(V4_REQ_UPDATE); }
|
|
for (i = 0; i < V4_BLOCK_SLOTS && chain_block(2100 + i)[0] == 0; i++) { }
|
|
CHECK(i == V4_BLOCK_SLOTS, "with every slot UPDATEd nothing is written until a slot is wanted");
|
|
{
|
|
unsigned written = 0;
|
|
CHECK(ask(V4_REQ_BLOCK, 2111) == V4_BLOCK_OK, "one more block is asked for");
|
|
for (i = 0; i < V4_BLOCK_SLOTS; i++) if (chain_block(2100 + i)[0] != 0) written++;
|
|
CHECK(written >= 1, "and the block whose slot it took was written first: %u written", written);
|
|
}
|
|
(void)tell(V4_REQ_EMPTY_BUFFERS);
|
|
|
|
/* ---- numbers that are no block ---- */
|
|
CHECK(ask(V4_REQ_BLOCK, 0) == V4_BLOCK_RANGE && got_addr == 0, "0 is no block");
|
|
CHECK(ask(V4_REQ_BLOCK, -1) == V4_BLOCK_RANGE && ask(V4_REQ_BUFFER, -5) == V4_BLOCK_RANGE, "nor is a number below it");
|
|
CHECK(ask(V4_REQ_BLOCK, 2111) == V4_BLOCK_OK && ask(V4_REQ_BLOCK, 2112) == V4_BLOCK_RANGE && ask(V4_REQ_BUFFER, 2112) == V4_BLOCK_RANGE, "the device's last block is, and the one after it is not");
|
|
#if V4_CELL_BITS > 32
|
|
CHECK(ask(V4_REQ_BLOCK, (v4_cell)0x100000014LL) == V4_BLOCK_RANGE, "a number 32 bits do not hold is no block, whatever its low bits are");
|
|
#endif
|
|
|
|
/* ---- a request with too little on the stack ---- */
|
|
v4_dstack_reset(&n.ds);
|
|
CHECK(v4_blocks_serve(&w, &n, V4_REQ_BLOCK) && n.ds.depth == 2 && v4_dstack_pop(&n.ds) == V4_BLOCK_REFUSED && v4_dstack_pop(&n.ds) == 0,
|
|
"BLOCK with nothing on the stack is refused: no address, and the status");
|
|
|
|
/* ---- a window that is not in the node's memory ---- */
|
|
{
|
|
v4_blocks bad;
|
|
v4_cell top = (v4_cell)V4_NODE_WORDS, status;
|
|
v4_blocks_init(&bad, top - (v4_cell)(V4_BLOCK_SLOTS * V4_BLOCK_CELLS) + 1);
|
|
n.mem[top - 1] = 123;
|
|
v4_dstack_reset(&n.ds); v4_dstack_push(&n.ds, 2050);
|
|
CHECK(v4_blocks_serve(&bad, &n, V4_REQ_BLOCK), "a request with a window that runs past the end of memory is served");
|
|
status = v4_dstack_pop(&n.ds);
|
|
CHECK(status == V4_BLOCK_REFUSED && v4_dstack_pop(&n.ds) == 0 && n.mem[top - 1] == 123, "by being refused, with nothing written");
|
|
}
|
|
|
|
/* ---- the chain changes: what is in the slots is let go, as v3 does ---- */
|
|
(void)tell(V4_REQ_EMPTY_BUFFERS);
|
|
CHECK(ask(V4_REQ_BLOCK, 2070) == V4_BLOCK_OK && (n.mem[got_addr] = (v4_cell)0x59595959) != 0 && tell(V4_REQ_UPDATE) == V4_BLOCK_OK, "a block is in a slot, UPDATEd");
|
|
{
|
|
static unsigned char more[4 * 1024];
|
|
CHECK(blk_subsys_add_raw_device(more, 4) == BLK_OK, "a device joins the chain");
|
|
memset(chain_block(2070), 'N', 1024);
|
|
CHECK(tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK && chain_block(2070)[0] == 'N', "what was in the slots is not written to the chain as it now is");
|
|
CHECK(ask(V4_REQ_BLOCK, 2070) == V4_BLOCK_OK && (n.mem[got_addr] & 0xFF) == 'N', "and the block is read again");
|
|
}
|
|
|
|
/* ---- a write that fails on the device leaves the block as it was ---- */
|
|
{
|
|
static blkio_dev_t fdev;
|
|
blkio_params_t params;
|
|
uint32_t first = 0, n_blocks = 0;
|
|
params.forth_block_size = 0; params.total_blocks = FAILING_BLOCKS; params.opaque = 0;
|
|
CHECK(blkio_open(&fdev, &failing_vt, ¶ms) == BLKIO_OK && blk_subsys_attach_device(&fdev) == BLK_OK
|
|
&& blk_get_device_range(&fdev, &first, &n_blocks) == BLK_OK && n_blocks > 8 && blk_subsys_confirm_format(first) == BLK_OK,
|
|
"a disk joins the chain and is formatted: %u blocks from %u", n_blocks, first);
|
|
CHECK(ask(V4_REQ_BLOCK, (v4_cell)first + 3) == V4_BLOCK_OK, "one of its blocks is asked for");
|
|
for (i = 0; i < V4_BLOCK_CELLS; i++) n.mem[got_addr + (v4_cell)i] = (v4_cell)0x4F4F4F4F;
|
|
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_OK, "and written: all O");
|
|
CHECK(ask(V4_REQ_BLOCK, (v4_cell)first + 3) == V4_BLOCK_OK, "it is asked for again");
|
|
for (i = 0; i < V4_BLOCK_CELLS; i++) n.mem[got_addr + (v4_cell)i] = (v4_cell)0x4E4E4E4E;
|
|
failing = 1;
|
|
CHECK(tell(V4_REQ_UPDATE) == V4_BLOCK_OK && tell(V4_REQ_SAVE_BUFFERS) == V4_BLOCK_REFUSED, "with the device failing, a write of all N is refused");
|
|
failing = 0;
|
|
(void)tell(V4_REQ_EMPTY_BUFFERS);
|
|
CHECK(ask(V4_REQ_BLOCK, (v4_cell)first + 3) == V4_BLOCK_OK && n.mem[got_addr] == (v4_cell)0x4F4F4F4F && n.mem[got_addr + 255] == (v4_cell)0x4F4F4F4F,
|
|
"and the block is what it was before: the kernel does not keep what the device refused: %lx", (unsigned long)n.mem[got_addr]);
|
|
CHECK(blk_flush(0) == BLK_OK && ask(V4_REQ_BLOCK, (v4_cell)first + 3) == V4_BLOCK_OK && n.mem[got_addr] == (v4_cell)0x4F4F4F4F,
|
|
"nor does it write it later, when the device works again");
|
|
}
|
|
|
|
/* ---- what is not a block request ---- */
|
|
v4_dstack_reset(&n.ds);
|
|
v4_dstack_push(&n.ds, 11); v4_dstack_push(&n.ds, 22);
|
|
CHECK(!v4_blocks_serve(&w, &n, 5) && !v4_blocks_serve(&w, &n, 0) && !v4_blocks_serve(&w, &n, -6) && v4_dstack_pop(&n.ds) == 22 && v4_dstack_pop(&n.ds) == 11,
|
|
"any other request is not served here, and the stack is as it was");
|
|
|
|
printf(" %d checks, %d failures\n", checks, failures);
|
|
return failures ? 1 : 0;
|
|
}
|