feat(v4.0.0): storage speaks messages -- a device over v3's block chains, common and private
What is on a storage port (v4/system/storage.c): it takes Block read, Block write and Block data and answers Block data or Block done. It holds a view of a common chain, a private chain, or both; private block k is number 2^32 - 1 - k. Behind it is v3's block subsystem, reached through v4/system/store_v3.c, the one v4 file that includes v3's headers. v3's block code links here with its two device back ends, its log and its clock, and nothing else of v3. v4/tests/test_store.c: 36 checks at 64 bits, 35 at 32, and under the sanitizers. docs/v4.0.0/MESH.md 8.3, 8.4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
0e761cb117
commit
4a505a154d
+39
-6
@@ -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
|
||||
|
||||
@@ -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" */
|
||||
|
||||
@@ -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 */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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 <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 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;
|
||||
}
|
||||
Reference in New Issue
Block a user