/* 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 #include #include #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; }