diff --git a/v4/Makefile b/v4/Makefile index fdcbb97b..756fb8aa 100644 --- a/v4/Makefile +++ b/v4/Makefile @@ -49,6 +49,39 @@ HOST_DATA_RING := 30 HOST_RET_RING := 31 node_size = $(if $(findstring /test_host_,$(1)),-DV4_NODE_WORDS=$(HOST_WORDS) -DV4_DATA_RING=$(HOST_DATA_RING) -DV4_RET_RING=$(HOST_RET_RING)) +# STORAGE (docs/v4.0.0/MESH.md section 8). What is on a storage port is +# system/storage.c, held to this model's warnings; behind it is the kernel's +# block subsystem, which is v3's, built as v3 builds it and reached through +# system/store_v3.c. Tests named test_store*.c and test_host_*.c link them. +# V3_BLOCK_SRCS is all of v3 that the block subsystem needs: its two device +# back ends, the log and the clock. +ROOT_DIR := $(abspath $(HERE)/..) +V3_BLOCK_SRCS := $(addprefix $(ROOT_DIR)/v3/src/,block_subsystem.c blkio_ram.c blkio_file.c log.c platform/platform_init.c platform/linux/time.c) +V3_CFLAGS := -std=gnu99 -Wall $(OPT) -I$(ROOT_DIR)/v3/include -I$(ROOT_DIR)/kernel/include +STORE_SRC := $(HERE)/system/storage.c +STORE_V3_SRC := $(HERE)/system/store_v3.c +V3_INC := -isystem $(ROOT_DIR)/v3/include -isystem $(ROOT_DIR)/kernel/include +uses_store = $(or $(findstring /test_store,$(1)),$(findstring /test_host_,$(1))) +store_src = $(if $(call uses_store,$(1)),$(STORE_SRC)) +store_inc = $(if $(call uses_store,$(1)),$(V3_INC)) + +$(BINDIR)/v3blocks.o: $(V3_BLOCK_SRCS) $(wildcard $(ROOT_DIR)/v3/include/*.h) $(HERE)/Makefile + @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 + 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 $@ +define STORE_V3_RULE +$(BINDIR)/store_v3-$(1).o: $(STORE_V3_SRC) $$(wildcard $(HERE)/include/v4/*.h) $(HERE)/Makefile + @mkdir -p $(BINDIR) + $$(CC) $(V3_CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=$(1) -c $(STORE_V3_SRC) -o $$@ +endef +$(foreach w,32 64,$(eval $(call STORE_V3_RULE,$(w)))) +store_objs = $(if $(call uses_store,$(2)),$(BINDIR)/store_v3-$(1).o $(BINDIR)/v3blocks.o) +san_store_objs = $(if $(call uses_store,$(2)),$(BINDIR)/store_v3-$(1).o $(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 # find the files whatever directory make was run from. @@ -191,16 +224,16 @@ endif # # Both binaries depend on this Makefile, so a flag change rebuilds them. define TEST_RULE -$(BINDIR)/$(1)-$(notdir $(2)): $(2) $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile +$(BINDIR)/$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call store_src,$(2)) $(call store_objs,$(1),$(2)) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile @mkdir -p $(BINDIR) - $$(CC) $$(CFLAGS) -I$(HERE)/include -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \ - $(2) $$(SRCS) -o $$@ + $$(CC) $$(CFLAGS) -I$(HERE)/include $(call store_inc,$(2)) -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \ + $(2) $$(SRCS) $(call store_src,$(2)) $(call store_objs,$(1),$(2)) -o $$@ -$(BINDIR)/san-$(1)-$(notdir $(2)): $(2) $$(SRCS) $$(wildcard $(HERE)/include/v4/*.h) $$(wildcard $(HERE)/tests/*.h) $(HERE)/Makefile +$(BINDIR)/san-$(1)-$(notdir $(2)): $(2) $$(SRCS) $(call store_src,$(2)) $(call san_store_objs,$(1),$(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 -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \ - $(2) $$(SRCS) -o $$@ + -fsanitize=address,undefined -fno-sanitize-recover=all -I$(HERE)/include $(call store_inc,$(2)) -DV4_CELL_BITS=$(1) $(call node_size,$(2)) $(capsule_dir) \ + $(2) $$(SRCS) $(call store_src,$(2)) $(call san_store_objs,$(1),$(2)) -o $$@ endef # One run target per (width, test) and one prerequisite line per width, so diff --git a/v4/include/v4/message.h b/v4/include/v4/message.h index 02b3dd18..86e4b37d 100644 --- a/v4/include/v4/message.h +++ b/v4/include/v4/message.h @@ -34,6 +34,18 @@ #define V4_MSG_OUTPUT 2 /* what a node printed */ #define V4_MSG_DONE 3 /* how the text ended: one word, V4_TEXT_* */ +/* Storage (docs/v4.0.0/MESH.md 8.3). A block's number is one word of + * payload, written as it is; a block is 1024 characters. */ +#define V4_MSG_BLOCK_READ 4 /* the block's number; answered by BLOCK_DATA, or BLOCK_DONE with an error */ +#define V4_MSG_BLOCK_WRITE 5 /* the block's number; a BLOCK_DATA follows; answered by BLOCK_DONE */ +#define V4_MSG_BLOCK_DATA 6 /* 1024 characters */ +#define V4_MSG_BLOCK_DONE 7 /* one word, V4_STORE_* */ + +#define V4_STORE_OK 0 +#define V4_STORE_NONE 1 /* there is no storage for that number */ +#define V4_STORE_REFUSED 2 +#define V4_STORE_RANGE 3 /* there is no such block */ + /* how text ended */ #define V4_TEXT_QUIT 0 /* QUIT: nothing is to be said */ #define V4_TEXT_COMPLETED 1 /* a console says " ok" */ diff --git a/v4/include/v4/storage.h b/v4/include/v4/storage.h new file mode 100644 index 00000000..b50c229a --- /dev/null +++ b/v4/include/v4/storage.h @@ -0,0 +1,83 @@ +/* storage.h -- what is on a storage port. docs/v4.0.0/MESH.md section 8. + * + * A device (fabric.h) that speaks the block messages of message.h and + * answers them from one or two chains of blocks: a view. The common chain + * is the store its nodes share; the private chain is one node's own. What + * a chain is, is its owner's affair: the device reaches it only through + * v4_store_ops, and the kernel's block subsystem stands behind those + * (system/store_v3.c). + * + * WHICH CHAIN A NUMBER MEANS. Block numbers are 1 to V4_STORE_TOP. + * - Private block k is number V4_STORE_TOP - k, and is the private + * chain's block V4_STORE_FIRST + k, its first on a device. A number + * is the private chain's when k is less than the blocks that chain has + * on devices. + * - Any other number is the common chain's, as it is. + * - With no common chain, a number below V4_STORE_FIRST is the private + * chain's, as it is: its fast RAM. + * - A number that belongs to no chain the view has: V4_STORE_NONE. + * - 0, a number above V4_STORE_TOP, or one past the end of its chain: + * V4_STORE_RANGE. + * + * It has a number, as a node has: messages for it are addressed to that + * number and its answers come from it. A message for any other number is + * let go. + * + * A write is two messages, BLOCK_WRITE with the number and BLOCK_DATA with + * the block; they are paired by who sent them. BLOCK_DATA from a sender + * with no BLOCK_WRITE before it is answered V4_STORE_REFUSED. + * + * The device always takes what it is sent, so that a node writing to it is + * never left waiting on a device that is waiting to be read: answers queue, + * V4_STORE_PENDING of them. Only when that many wait is the first word of + * another message not taken yet. + * + * Nothing here uses the C library. + */ +#ifndef V4_STORAGE_H +#define V4_STORAGE_H + +#include "v4/message.h" + +#define V4_STORE_TOP 0xffffffffUL +#define V4_STORE_FIRST 2048UL +#define V4_STORE_PENDING 64u +#define V4_STORE_BYTES 1024u + +/* A chain of blocks. read and write return V4_STORE_*; `blocks` is how + * many block numbers the chain answers to, counting from 0, its fast RAM + * included. */ +typedef struct { + int (*read)(void *chain, uint32_t block, unsigned char *bytes); + int (*write)(void *chain, uint32_t block, const unsigned char *bytes); + uint32_t (*blocks)(void *chain); +} v4_store_ops; + +typedef struct { + const v4_store_ops *ops; + void *common, *private_chain; /* 0: the view has none */ + v4_cell number; + + v4_message in; /* the message arriving */ + struct { int used; v4_cell from; uint64_t block; } writing[V4_STORE_PENDING]; /* BLOCK_WRITEs whose data has not come */ + struct { v4_cell to; int read; uint64_t block; int status; } answer[V4_STORE_PENDING]; + unsigned head, count; /* the answers waiting: the oldest, and how many */ + v4_message out; /* the answer being given */ + unsigned out_at; + int out_ready; + unsigned char bytes[V4_STORE_BYTES]; + + unsigned long taken; /* whole messages taken, for whoever counts */ +} v4_store; + +void v4_store_init(v4_store *s, const v4_store_ops *ops, void *common, void *private_chain, v4_cell number); + +/* The two functions of a v4_device; `self` is the v4_store. */ +int v4_store_take(void *self, v4_cell value); +int v4_store_give(void *self, v4_cell *value); + +/* The kernel's block subsystem as chains: a chain is a blk_chain_t * + * (v3/include/block_subsystem.h). system/store_v3.c. */ +extern const v4_store_ops v4_store_v3; + +#endif /* V4_STORAGE_H */ diff --git a/v4/system/storage.c b/v4/system/storage.c new file mode 100644 index 00000000..497aba97 --- /dev/null +++ b/v4/system/storage.c @@ -0,0 +1,161 @@ +/* storage.c -- what is on a storage port. See storage.h. + * docs/v4.0.0/MESH.md section 8. + * + * Nothing here uses the C library: the bare-metal kernel links this file. + */ +#include "v4/storage.h" + +void v4_store_init(v4_store *s, const v4_store_ops *ops, void *common, void *private_chain, v4_cell number) +{ + unsigned i; + + s->ops = ops; + s->common = common; + s->private_chain = private_chain; + s->number = number; + s->in.count = 0; + for (i = 0; i < V4_STORE_PENDING; i++) s->writing[i].used = 0; + s->head = 0; + s->count = 0; + s->out.count = 0; + s->out_at = 0; + s->out_ready = 0; + s->taken = 0; +} + +/* The chain a number means and the block in it; or 0, with why in *status. */ +static void *chain_for(const v4_store *s, uint64_t n, uint32_t *block, int *status) +{ + *status = V4_STORE_RANGE; + if (n == 0 || n > V4_STORE_TOP) return 0; + if (s->private_chain) { + uint64_t k = V4_STORE_TOP - n, have = s->ops->blocks(s->private_chain); + if (have > V4_STORE_FIRST && k < have - V4_STORE_FIRST) { *block = (uint32_t)(V4_STORE_FIRST + k); return s->private_chain; } + } + *block = (uint32_t)n; + if (s->common) return s->common; + if (s->private_chain && n < V4_STORE_FIRST) return s->private_chain; + *status = V4_STORE_NONE; + return 0; +} + +static void queue(v4_store *s, v4_cell to, int read, uint64_t block, int status) +{ + unsigned at = (s->head + s->count) % V4_STORE_PENDING; + + if (s->count == V4_STORE_PENDING) return; /* take does not let it come to this */ + s->answer[at].to = to; + s->answer[at].read = read; + s->answer[at].block = block; + s->answer[at].status = status; + s->count++; +} + +/* A block of data has come from `from`: it is written where that sender's + * BLOCK_WRITE said, if there was one. */ +static int write_block(v4_store *s, v4_cell from) +{ + unsigned i, k; + uint32_t block; + int status; + void *chain; + + for (i = 0; i < V4_STORE_PENDING && !(s->writing[i].used && s->writing[i].from == from); i++) { } + if (i == V4_STORE_PENDING) return V4_STORE_REFUSED; + s->writing[i].used = 0; + if (v4_message_length(&s->in) != V4_STORE_BYTES) return V4_STORE_REFUSED; + chain = chain_for(s, s->writing[i].block, &block, &status); + if (!chain) return status; + for (k = 0; k < V4_STORE_BYTES; k++) s->bytes[k] = (unsigned char)v4_message_char(&s->in, k); + return s->ops->write(chain, block, s->bytes); +} + +/* The message in s->in is whole. */ +static void served(v4_store *s) +{ + v4_cell from = v4_message_from(&s->in), type = v4_message_type(&s->in); + uint64_t block = (uint64_t)(v4_ucell)s->in.word[V4_MSG_HEADER]; + unsigned i, free_at = V4_STORE_PENDING; + + s->taken++; + if (v4_message_to(&s->in) != s->number) return; + if (type == V4_MSG_BLOCK_DATA) { queue(s, from, 0, 0, write_block(s, from)); return; } + if (type != V4_MSG_BLOCK_READ && type != V4_MSG_BLOCK_WRITE) return; + if (v4_message_length(&s->in) != 4) { queue(s, from, 0, 0, V4_STORE_REFUSED); return; } + if (type == V4_MSG_BLOCK_READ) { queue(s, from, 1, block, 0); return; } + + /* a write: its number is kept until its block comes. A sender's second + * write before then takes the place of its first. */ + for (i = 0; i < V4_STORE_PENDING; i++) { + if (s->writing[i].used && s->writing[i].from == from) { free_at = i; break; } + if (!s->writing[i].used && free_at == V4_STORE_PENDING) free_at = i; + } + if (free_at == V4_STORE_PENDING) { queue(s, from, 0, 0, V4_STORE_REFUSED); return; } + s->writing[free_at].used = 1; + s->writing[free_at].from = from; + s->writing[free_at].block = block; +} + +int v4_store_take(void *self, v4_cell value) +{ + v4_store *s = (v4_store *)self; + + if (s->in.count == 0 && s->count == V4_STORE_PENDING) return 0; + if (v4_message_word(&s->in, value)) { + served(s); + s->in.count = 0; + } + return 1; +} + +/* The oldest answer waiting becomes the message in s->out. */ +static void answer(v4_store *s) +{ + v4_cell to = s->answer[s->head].to; + int status = s->answer[s->head].status; + unsigned i; + + if (s->answer[s->head].read) { + uint32_t block; + void *chain = chain_for(s, s->answer[s->head].block, &block, &status); + if (chain) status = s->ops->read(chain, block, s->bytes); + } else if (status == V4_STORE_OK) { + status = -1; /* a write that worked: said below */ + } + s->head = (s->head + 1) % V4_STORE_PENDING; + s->count--; + + s->out.word[0] = to; + s->out.word[1] = s->number; + s->out.word[3] = 0; + s->out.word[4] = 0; + s->out.word[5] = 0; + if (status == V4_STORE_OK) { /* a read that worked: the block */ + s->out.word[2] = V4_MSG_BLOCK_DATA; + s->out.word[6] = (v4_cell)V4_STORE_BYTES; + for (i = 0; i < V4_STORE_BYTES / 4u; i++) + s->out.word[V4_MSG_HEADER + i] = (v4_cell)((v4_ucell)s->bytes[4u * i] | (v4_ucell)s->bytes[4u * i + 1u] << 8 + | (v4_ucell)s->bytes[4u * i + 2u] << 16 | (v4_ucell)s->bytes[4u * i + 3u] << 24); + s->out.count = V4_MSG_HEADER + V4_STORE_BYTES / 4u; + } else { + s->out.word[2] = V4_MSG_BLOCK_DONE; + s->out.word[6] = 4; + s->out.word[V4_MSG_HEADER] = status < 0 ? V4_STORE_OK : status; + s->out.count = V4_MSG_HEADER + 1u; + } + s->out_at = 0; + s->out_ready = 1; +} + +int v4_store_give(void *self, v4_cell *value) +{ + v4_store *s = (v4_store *)self; + + if (!s->out_ready) { + if (s->count == 0) return 0; + answer(s); + } + *value = s->out.word[s->out_at++]; + if (s->out_at == s->out.count) s->out_ready = 0; + return 1; +} diff --git a/v4/system/store_v3.c b/v4/system/store_v3.c new file mode 100644 index 00000000..e9d4f3bc --- /dev/null +++ b/v4/system/store_v3.c @@ -0,0 +1,55 @@ +/* store_v3.c -- the kernel's block subsystem as the chains behind a storage + * port. docs/v4.0.0/MESH.md 8.4, docs/v4.0.0/V3-PARITY.md 1d. + * + * A chain is a blk_chain_t (v3/include/block_subsystem.h). Every block goes + * through v3's own calls, so the header, the allocation map, the block + * cards and relocation are v3's, as they were. Who may have a block is not + * checked here yet (MESH.md 8.5). + * + * This is the one file of v4's that includes v3's headers. + */ +#include "v4/storage.h" +#include "block_subsystem.h" + +static int v3_read(void *chain, uint32_t block, unsigned char *bytes) +{ + blk_chain_t *was = blk_chain_select((blk_chain_t *)chain); + int status = V4_STORE_RANGE; + unsigned i; + + if (blk_is_valid(block)) { + const uint8_t *b = blk_get_buffer(block, 0); + status = b ? V4_STORE_OK : V4_STORE_REFUSED; + if (b) for (i = 0; i < V4_STORE_BYTES; i++) bytes[i] = b[i]; + } + blk_chain_select(was); + return status; +} + +static int v3_write(void *chain, uint32_t block, const unsigned char *bytes) +{ + blk_chain_t *was = blk_chain_select((blk_chain_t *)chain); + int status = V4_STORE_RANGE; + unsigned i; + + if (blk_is_valid(block)) { + uint8_t *b = blk_get_buffer(block, 1); + status = V4_STORE_REFUSED; + if (b) { + for (i = 0; i < V4_STORE_BYTES; i++) b[i] = bytes[i]; + if (blk_update(block) == BLK_OK && blk_flush(block) == BLK_OK) status = V4_STORE_OK; + } + } + blk_chain_select(was); + return status; +} + +static uint32_t v3_blocks(void *chain) +{ + blk_chain_t *was = blk_chain_select((blk_chain_t *)chain); + uint32_t n = blk_get_total_blocks(); + blk_chain_select(was); + return n; +} + +const v4_store_ops v4_store_v3 = { v3_read, v3_write, v3_blocks }; diff --git a/v4/tests/test_store.c b/v4/tests/test_store.c new file mode 100644 index 00000000..91e82aa3 --- /dev/null +++ b/v4/tests/test_store.c @@ -0,0 +1,175 @@ +/* test_store.c -- what is on a storage port, spoken to in messages. + * docs/v4.0.0/MESH.md sections 8.3 and 8.4. + * + * No node: the test is the neighbour. The chains are v3's block subsystem + * (v3/src/block_subsystem.c), two of them, each with its fast RAM and a + * device of 64 blocks. + */ +#include "v4/storage.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 STORE 90 +#define DEVICE_BLOCKS 64u +#define RAM_BYTES ((size_t)BLK_RAM_BLOCKS * BLK_FORTH_SIZE) + +static blk_chain_t *make_chain(blk_chain_t *c) +{ + blk_chain_t *was = blk_chain_select(c); + int ok = blk_subsys_init((uint8_t *)calloc(1, RAM_BYTES), RAM_BYTES) == BLK_OK + && blk_subsys_add_raw_device((uint8_t *)calloc(DEVICE_BLOCKS, BLK_FORTH_SIZE), DEVICE_BLOCKS) == BLK_OK; + blk_chain_select(was); + return ok ? c : 0; +} + +/* give the device a whole message; 1 if every word was taken */ +static int send(v4_store *s, const v4_message *m) +{ + unsigned i; + for (i = 0; i < m->count; i++) if (!v4_store_take(s, m->word[i])) return 0; + return 1; +} +static int ask(v4_store *s, v4_cell from, v4_cell type, v4_cell block) +{ + v4_message m; + v4_message_text(&m, STORE, from, type, "\0\0\0\0", 4); + m.word[V4_MSG_HEADER] = block; + return send(s, &m); +} +static int data(v4_store *s, v4_cell from, char fill) +{ + static char block[V4_STORE_BYTES]; + v4_message m; + memset(block, fill, sizeof block); + v4_message_text(&m, STORE, from, V4_MSG_BLOCK_DATA, block, sizeof block); + return send(s, &m); +} +/* the device's next answer, whole; 0 if it has none */ +static int answer(v4_store *s, v4_message *m) +{ + v4_cell w; + m->count = 0; + if (!v4_store_give(s, &w)) return 0; + while (!v4_message_word(m, w)) if (!v4_store_give(s, &w)) return 0; + return 1; +} +static int is_done(const v4_message *m, v4_cell to, v4_cell status) +{ + return v4_message_to(m) == to && v4_message_from(m) == STORE && v4_message_type(m) == V4_MSG_BLOCK_DONE + && v4_message_length(m) == 4 && m->word[V4_MSG_HEADER] == status; +} +static int is_data(const v4_message *m, v4_cell to, char fill) +{ + unsigned i; + if (v4_message_to(m) != to || v4_message_from(m) != STORE || v4_message_type(m) != V4_MSG_BLOCK_DATA + || v4_message_length(m) != V4_STORE_BYTES) return 0; + for (i = 0; i < V4_STORE_BYTES; i++) if (v4_message_char(m, i) != fill) return 0; + return 1; +} + +int main(void) +{ + static v4_store both, common_only, private_only; + static v4_message m; + blk_chain_t *common, *own; + v4_cell top = (v4_cell)(v4_ucell)V4_STORE_TOP; + + printf("v4 storage tests: V4_CELL_BITS=%d\n", V4_CELL_BITS); + sf_time_init(); + log_set_level(-1); + common = make_chain(blk_chain_default()); + own = make_chain(blk_chain_new()); + CHECK(common && own && common != own, "two chains are made"); + if (!common || !own) return 1; + v4_store_init(&both, &v4_store_v3, common, own, STORE); + v4_store_init(&common_only, &v4_store_v3, common, 0, STORE); + v4_store_init(&private_only, &v4_store_v3, 0, own, STORE); + + /* ---- a block is written and read ---- */ + CHECK(!answer(&both, &m), "with nothing asked there is nothing to read"); + CHECK(ask(&both, 11, V4_MSG_BLOCK_WRITE, 2050) && !answer(&both, &m), "a write is not answered until its block has come"); + CHECK(data(&both, 11, 'A') && answer(&both, &m) && is_done(&m, 11, V4_STORE_OK), "then it is answered done, to who sent it, from storage"); + CHECK(ask(&both, 12, V4_MSG_BLOCK_READ, 2050) && answer(&both, &m) && is_data(&m, 12, 'A'), "another reads what was written"); + CHECK(ask(&common_only, 12, V4_MSG_BLOCK_READ, 2050) && answer(&common_only, &m) && is_data(&m, 12, 'A'), "and so does one on another port of the same chain"); + CHECK(ask(&both, 12, V4_MSG_BLOCK_WRITE, 20) && data(&both, 12, 'R') && answer(&both, &m) && is_done(&m, 12, V4_STORE_OK) + && ask(&common_only, 13, V4_MSG_BLOCK_READ, 20) && answer(&common_only, &m) && is_data(&m, 13, 'R'), "a block below 2048 is the common chain's fast RAM"); + + /* ---- private blocks, from the top down ---- */ + CHECK(ask(&both, 13, V4_MSG_BLOCK_WRITE, top) && data(&both, 13, 'P') && answer(&both, &m) && is_done(&m, 13, V4_STORE_OK), "the top number is written"); + CHECK(ask(&both, 13, V4_MSG_BLOCK_READ, top) && answer(&both, &m) && is_data(&m, 13, 'P'), "and read back"); + CHECK(ask(&private_only, 13, V4_MSG_BLOCK_READ, top) && answer(&private_only, &m) && is_data(&m, 13, 'P'), "it is the private chain's"); + { + blk_chain_t *was = blk_chain_select(own); + uint8_t *b = blk_get_buffer((uint32_t)V4_STORE_FIRST, 0); + CHECK(b && b[0] == 'P' && b[1023] == 'P', "private block 0 is that chain's block 2048"); + blk_chain_select(common); + b = blk_get_buffer((uint32_t)V4_STORE_FIRST, 0); + CHECK(b && b[0] != 'P', "and is not in the common chain"); + blk_chain_select(was); + } + CHECK(ask(&both, 13, V4_MSG_BLOCK_WRITE, top - (v4_cell)(DEVICE_BLOCKS - 1)) && data(&both, 13, 'L') && answer(&both, &m) && is_done(&m, 13, V4_STORE_OK), "the last private block is written"); + CHECK(ask(&both, 13, V4_MSG_BLOCK_READ, top - (v4_cell)DEVICE_BLOCKS) && answer(&both, &m) && is_done(&m, 13, V4_STORE_RANGE), "one below the private blocks is no block of the common chain"); + CHECK(ask(&common_only, 12, V4_MSG_BLOCK_READ, top) && answer(&common_only, &m) && is_done(&m, 12, V4_STORE_RANGE), "with no private chain the top number is no such block"); + CHECK(ask(&private_only, 13, V4_MSG_BLOCK_READ, 2050) && answer(&private_only, &m) && is_done(&m, 13, V4_STORE_NONE), "with no common chain a common number has no storage"); + CHECK(ask(&private_only, 13, V4_MSG_BLOCK_WRITE, 20) && data(&private_only, 13, 'r') && answer(&private_only, &m) && is_done(&m, 13, V4_STORE_OK) + && ask(&private_only, 13, V4_MSG_BLOCK_READ, 20) && answer(&private_only, &m) && is_data(&m, 13, 'r'), "and a number below 2048 is the private chain's fast RAM"); + CHECK(ask(&common_only, 12, V4_MSG_BLOCK_READ, 20) && answer(&common_only, &m) && is_data(&m, 12, 'R'), "which is not the common chain's"); + + /* ---- numbers that are no block ---- */ + CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, 0) && answer(&both, &m) && is_done(&m, 11, V4_STORE_RANGE), "0 is no block"); + CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, (v4_cell)(V4_STORE_FIRST + DEVICE_BLOCKS)) && answer(&both, &m) && is_done(&m, 11, V4_STORE_RANGE), "one past the end of the common chain is no block"); + CHECK(ask(&both, 11, V4_MSG_BLOCK_WRITE, (v4_cell)(V4_STORE_FIRST + DEVICE_BLOCKS)) && data(&both, 11, 'X') && answer(&both, &m) && is_done(&m, 11, V4_STORE_RANGE), "nor can it be written"); +#if V4_CELL_BITS > 32 + CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, (v4_cell)0x100000000LL) && answer(&both, &m) && is_done(&m, 11, V4_STORE_RANGE), "a number 32 bits do not hold is no block"); + CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, -1) && answer(&both, &m) && is_done(&m, 11, V4_STORE_RANGE), "nor is a negative one, on cells wider than 32 bits"); +#else + CHECK(top == -1, "on 32-bit cells the top number is -1"); +#endif + + /* ---- data nobody announced ---- */ + CHECK(data(&both, 14, 'Z') && answer(&both, &m) && is_done(&m, 14, V4_STORE_REFUSED), "a block of data with no write before it is refused"); + CHECK(ask(&both, 12, V4_MSG_BLOCK_READ, 2050) && answer(&both, &m) && is_data(&m, 12, 'A'), "and nothing was written"); + CHECK(ask(&both, 11, V4_MSG_BLOCK_WRITE, 2051) && data(&both, 11, 'B') && answer(&both, &m) && is_done(&m, 11, V4_STORE_OK) + && data(&both, 11, 'C') && answer(&both, &m) && is_done(&m, 11, V4_STORE_REFUSED) + && ask(&both, 11, V4_MSG_BLOCK_READ, 2051) && answer(&both, &m) && is_data(&m, 11, 'B'), "a write is for one block of data and no more"); + + /* ---- two senders at once ---- */ + CHECK(ask(&both, 11, V4_MSG_BLOCK_WRITE, 2060) && ask(&both, 12, V4_MSG_BLOCK_WRITE, 2061) + && data(&both, 12, 'b') && data(&both, 11, 'a'), "two writes are begun before either's block comes, and the blocks come the other way round"); + CHECK(answer(&both, &m) && is_done(&m, 12, V4_STORE_OK) && answer(&both, &m) && is_done(&m, 11, V4_STORE_OK), "each is answered, in the order the blocks came"); + CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, 2060) && ask(&both, 12, V4_MSG_BLOCK_READ, 2061), "a second request is taken while the answer to the first has not been read"); + CHECK(answer(&both, &m) && is_data(&m, 11, 'a') && answer(&both, &m) && is_data(&m, 12, 'b'), "and each block is its own sender's"); + + /* ---- what is not for storage ---- */ + { + v4_message other; + unsigned long before = both.taken; + v4_message_text(&other, 55, 11, V4_MSG_BLOCK_READ, "\0\0\0\0", 4); + other.word[V4_MSG_HEADER] = 2050; + CHECK(send(&both, &other) && both.taken == before + 1 && !answer(&both, &m), "a message for another number is taken and let go"); + v4_message_text(&other, STORE, 11, V4_MSG_TEXT, "1 2 +", 5); + CHECK(send(&both, &other) && !answer(&both, &m), "and so is text"); + } + + /* ---- what v3 wrote reads through the port ---- */ + { + blk_chain_t *was = blk_chain_select(common); + uint8_t *b = blk_get_buffer(2070, 1); + CHECK(b != 0, "v3 gives a buffer for block 2070"); + if (b) { memset(b, 'V', BLK_FORTH_SIZE); CHECK(blk_update(2070) == BLK_OK && blk_flush(2070) == BLK_OK, "and v3 writes it"); } + blk_chain_select(was); + CHECK(ask(&both, 11, V4_MSG_BLOCK_READ, 2070) && answer(&both, &m) && is_data(&m, 11, 'V'), "what v3's own calls wrote is read through the port"); + CHECK(blk_chain_select(0) == was, "the chain selected is the one that was, after the device has been used"); + } + + printf(" %d checks, %d failures\n", checks, failures); + return failures ? 1 : 0; +}