diff options
author | 2023-10-10 14:33:42 +0000 | |
---|---|---|
committer | 2023-10-10 14:33:42 +0000 | |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/libflash/test | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/libflash/test')
-rw-r--r-- | roms/skiboot/libflash/test/Makefile.check | 167 | ||||
-rw-r--r-- | roms/skiboot/libflash/test/mbox-server.c | 514 | ||||
-rw-r--r-- | roms/skiboot/libflash/test/mbox-server.h | 13 | ||||
-rw-r--r-- | roms/skiboot/libflash/test/stubs.c | 102 | ||||
-rw-r--r-- | roms/skiboot/libflash/test/stubs.h | 20 | ||||
-rw-r--r-- | roms/skiboot/libflash/test/test-blocklevel.c | 664 | ||||
-rw-r--r-- | roms/skiboot/libflash/test/test-ecc.c | 510 | ||||
-rw-r--r-- | roms/skiboot/libflash/test/test-flash.c | 448 | ||||
-rw-r--r-- | roms/skiboot/libflash/test/test-ipmi-hiomap.c | 3388 | ||||
-rw-r--r-- | roms/skiboot/libflash/test/test-mbox.c | 333 |
10 files changed, 6159 insertions, 0 deletions
diff --git a/roms/skiboot/libflash/test/Makefile.check b/roms/skiboot/libflash/test/Makefile.check new file mode 100644 index 000000000..4dbd7ee75 --- /dev/null +++ b/roms/skiboot/libflash/test/Makefile.check @@ -0,0 +1,167 @@ +# -*-Makefile-*- +libflash_test_test_ipmi_hiomap_SOURCES = \ + libflash/test/test-ipmi-hiomap.c \ + libflash/test/stubs.c \ + libflash/ipmi-hiomap.c + +libflash_test_test_blocklevel_SOURCES = \ + libflash/test/test-blocklevel.c \ + libflash/test/stubs.c + +libflash_test_test_flash_SOURCES = \ + libflash/test/test-flash.c \ + libflash/test/stubs.c \ + libflash/test/mbox-server.c + +libflash_test_test_ecc_SOURCES = \ + libflash/test/test-ecc.c \ + libflash/test/stubs.c \ + libflash/test/mbox-server.c + +libflash_test_test_mbox_SOURCES = \ + libflash/test/test-mbox.c \ + libflash/test/stubs.c \ + libflash/test/mbox-server.c + +check_PROGRAMS = \ + libflash/test/test-ipmi-hiomap \ + libflash/test/test-blocklevel \ + libflash/test/test-flash \ + libflash/test/test-ecc \ + libflash/test/test-mbox + +TEST_FLAGS = -D__TEST__ -MMD -MP + +.PHONY: libflash-check libflash-coverage +libflash-check: $(check_PROGRAMS:%=%-check) +libflash-coverage: $(check_PROGRAMS:%=%-gcov-run) +clean: libflash-test-clean +check: libflash-check +coverage: libflash-coverage +strict-check: TEST_FLAGS += -D__STRICT_TEST__ +strict-check: check + +LCOV_EXCLUDE += $(check_PROGRAMS:%=%.c) + +$(check_PROGRAMS:%=%-check) : %-check : % + $(call QTEST, RUN-TEST , $(VALGRIND) $<, $<) + +# Transform a prerequisite into something approximating a variable name. This +# is used to map check_PROGRAMS prerequisits to the corresponding _SOURCES +# variable. +# +# For example: +# +# $(call prereq2var,libflash/test/test-mbox) +# +# Will output: +# +# 'libflash_test_test_mbox' +# +prereq2var = $(subst /,_,$(subst -,_,$(1))) + +# Generate prerequisites from a target based on the target's corresponding +# _SOURCES variable. +# +# For example, with: +# +# libflash_test_test_mbox_SOURCES = \ +# libflash/test/test-mbox.c \ +# libflash/test/stubs.c \ +# libflash/test/mbox-server.c +# HOST_TRIPLE = x86_64-linux-gnu +# +# A call to target2prereq where the target is libflash/test/test-mbox: +# +# $(call target2prereq,$@,$(HOST_TRIPLE)/) +# +# Will output: +# +# x86_64-linux-gnu/libflash/test/test-mbox.o +# x86_64-linux-gnu/libflash/test/stubs.o +# x86_64-linux-gnu/libflash/test/mbox-server.o +target2prereq = $(patsubst %.c,%.o,$(addprefix $(2),$($(call prereq2var,$(1))_SOURCES))) + +# Generate path stems for all applications in check_PROGRAMS. This is usef +# +# For example, with: +# +# libflash_test_test_mbox_SOURCES = \ +# libflash/test/test-mbox.c \ +# libflash/test/stubs.c \ +# libflash/test/mbox-server.c +# libflash_test_test_ecc_SOURCES = \ +# libflash/test/test-ecc.c \ +# libflash/test/stubs.c \ +# libflash/test/mbox-server.c +# check_PROGRAMS = libflash/test/test-mbox libflash/test/test-ecc +# HOST_TRIPLE = x86_64-linux-gnu +# +# A call to: +# +# $(call objstem,$(check_PROGRAMS),$(HOST_TRIPLE)/) +# +# Will output: +# +# x86_64-linux-gnu/libflash/test/test-mbox +# x86_64-linux-gnu/libflash/test/stubs +# x86_64-linux-gnu/libflash/test/mbox-server +# x86_64-linux-gnu/libflash/test/test-ecc +# x86_64-linux-gnu/libflash/test/stubs +# x86_64-linux-gnu/libflash/test/mbox-server +objstem = $(patsubst %.c,%,$(addprefix $(2),$(foreach bin,$(1),$($(call prereq2var,$(bin))_SOURCES)))) + +# Record the host platform triple to separate test vs production objects. +HOST_TRIPLE = $(shell $(HOSTCC) -dumpmachine) + +# Mirror the skiboot directory structure under a directory named after the host +# triple in the skiboot root directory, and place the built objects in this +# mirrored structure. +$(HOST_TRIPLE)/%.o : %.c + @mkdir -p $(dir $@) + $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) $(TEST_FLAGS) -Wno-suggest-attribute=const -g -c -o $@ $<, $@) + +# Use GNU make metaprogramming dynamically define targets and prequisites for +# binaries listed in check_PROGRAMS. +# +# Secondary expansion[1] allows us to use the target automatic variable ($@) in +# the prequisite list. Knowing the target we can map to the corresponding +# _SOURCES variable to learn what to build and link. Finally, make sure the +# artifacts are output under the $(HOST_TRIPLE) directory to separate them from +# objects intended for skiboot proper. +# +# [1] https://www.gnu.org/software/make/manual/html_node/Secondary-Expansion.html#Secondary-Expansion +.SECONDEXPANSION: +$(check_PROGRAMS) : $$(call target2prereq,$$@,$(HOST_TRIPLE)/) + $(call Q, HOSTCC , $(HOSTCC) $(HOSTCFLAGS) $(TEST_FLAGS) -Wno-suggest-attribute=const -O0 -g -o $@ $^, $@) + +.PHONY: libflash-test-clean +libflash-test-clean: OBJ_STEMS = $(call objstem,$(check_PROGRAMS),$(HOST_TRIPLE)/) +libflash-test-clean: libflash-test-gcov-clean + $(RM) $(check_PROGRAMS) + $(RM) $(OBJ_STEMS:%=%.o) + $(RM) $(OBJ_STEMS:%=%.d) + +# gcov support: Build objects under $(HOST_TRIPLE)/gcov/ +$(check_PROGRAMS:%=%-gcov-run) : %-run: % + $(call QTEST, TEST-COVERAGE ,$< , $<) + +$(HOST_TRIPLE)/gcov/%.o : %.c + @mkdir -p $(dir $@) + $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) $(HOSTGCOVCFLAGS) $(TEST_FLAGS) -Wno-suggest-attribute=const -g -c -o $@ $<, $@) + +.SECONDEXPANSION: +$(check_PROGRAMS:%=%-gcov) : $$(call target2prereq,$$(patsubst %-gcov,%,$$@),$(HOST_TRIPLE)/gcov/) + $(call Q, HOSTCC , $(HOSTCC) $(HOSTCFLAGS) $(HOSTGCOVCFLAGS) $(TEST_FLAGS) -Wno-suggest-attribute=const -O0 -g -o $@ $^, $@) + +.PHONY: libflash-test-gcov-clean +libflash-test-gcov-clean: GCOV_OBJ_STEMS = $(call objstem,$(check_PROGRAMS),$(HOST_TRIPLE)/gcov/) +libflash-test-gcov-clean: + $(RM) $(check_PROGRAMS:%=%-gcov) + $(RM) $(GCOV_OBJ_STEMS:%=%.o) + $(RM) $(GCOV_OBJ_STEMS:%=%.d) + $(RM) $(GCOV_OBJ_STEMS:%=%.gcda) + $(RM) $(GCOV_OBJ_STEMS:%=%.gcno) + +-include $(patsubst %,%.d,$(call objstem,$(check_PROGRAMS),$(HOST_TRIPLE)/)) +-include $(patsubst %,%.d,$(call objstem,$(check_PROGRAMS),$(HOST_TRIPLE)/gcov/)) diff --git a/roms/skiboot/libflash/test/mbox-server.c b/roms/skiboot/libflash/test/mbox-server.c new file mode 100644 index 000000000..8a68cfff6 --- /dev/null +++ b/roms/skiboot/libflash/test/mbox-server.c @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2017 IBM Corp. */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <stdarg.h> +#include <inttypes.h> + +#include <sys/mman.h> /* for mprotect() */ + +#define pr_fmt(fmt) "MBOX-SERVER: " fmt +#include "skiboot.h" +#include "opal-api.h" + +#include "mbox-server.h" +#include "stubs.h" + +#define ERASE_GRANULE 0x100 + +#define LPC_BLOCKS 256 + +#define __unused __attribute__((unused)) + +enum win_type { + WIN_CLOSED, + WIN_READ, + WIN_WRITE +}; + +typedef void (*mbox_data_cb)(struct bmc_mbox_msg *msg, void *priv); +typedef void (*mbox_attn_cb)(uint8_t reg, void *priv); + +struct { + mbox_data_cb fn; + void *cb_data; + struct bmc_mbox_msg *msg; + mbox_attn_cb attn; + void *cb_attn; +} mbox_data; + +static struct { + int api; + bool reset; + + void *lpc_base; + size_t lpc_size; + + uint8_t attn_reg; + + uint32_t block_shift; + uint32_t erase_granule; + + uint16_t def_read_win; /* default window size in blocks */ + uint16_t def_write_win; + + uint16_t max_read_win; /* max window size in blocks */ + uint16_t max_write_win; + + enum win_type win_type; + uint32_t win_base; + uint32_t win_size; + bool win_dirty; +} server_state; + + +static bool check_window(uint32_t pos, uint32_t size) +{ + /* If size is zero then all is well */ + if (size == 0) + return true; + + if (server_state.api == 1) { + /* + * Can actually be stricter in v1 because pos is relative to + * flash not window + */ + if (pos < server_state.win_base || + pos + size > server_state.win_base + server_state.win_size) { + fprintf(stderr, "pos: 0x%08x size: 0x%08x aren't in active window\n", + pos, size); + fprintf(stderr, "window pos: 0x%08x window size: 0x%08x\n", + server_state.win_base, server_state.win_size); + return false; + } + } else { + if (pos + size > server_state.win_base + server_state.win_size) + return false; + } + return true; +} + +/* skiboot test stubs */ +int64_t lpc_read(enum OpalLPCAddressType __unused addr_type, uint32_t addr, + uint32_t *data, uint32_t sz); +int64_t lpc_read(enum OpalLPCAddressType __unused addr_type, uint32_t addr, + uint32_t *data, uint32_t sz) +{ + /* Let it read from a write window... Spec says it ok! */ + if (!check_window(addr, sz) || server_state.win_type == WIN_CLOSED) + return 1; + + switch (sz) { + case 1: + *(uint8_t *)data = *(uint8_t *)(server_state.lpc_base + addr); + break; + case 2: + *(uint16_t *)data = be16_to_cpu(*(uint16_t *)(server_state.lpc_base + addr)); + break; + case 4: + *(uint32_t *)data = be32_to_cpu(*(uint32_t *)(server_state.lpc_base + addr)); + break; + default: + prerror("Invalid data size %d\n", sz); + return 1; + } + return 0; +} + +int64_t lpc_write(enum OpalLPCAddressType __unused addr_type, uint32_t addr, + uint32_t data, uint32_t sz); +int64_t lpc_write(enum OpalLPCAddressType __unused addr_type, uint32_t addr, + uint32_t data, uint32_t sz) +{ + if (!check_window(addr, sz) || server_state.win_type != WIN_WRITE) + return 1; + switch (sz) { + case 1: + *(uint8_t *)(server_state.lpc_base + addr) = data; + break; + case 2: + *(uint16_t *)(server_state.lpc_base + addr) = cpu_to_be16(data); + break; + case 4: + *(uint32_t *)(server_state.lpc_base + addr) = cpu_to_be32(data); + break; + default: + prerror("Invalid data size %d\n", sz); + return 1; + } + return 0; +} + +int bmc_mbox_register_attn(mbox_attn_cb handler, void *drv_data) +{ + mbox_data.attn = handler; + mbox_data.cb_attn = drv_data; + + return 0; +} + +uint8_t bmc_mbox_get_attn_reg(void) +{ + return server_state.attn_reg; +} + +int bmc_mbox_register_callback(mbox_data_cb handler, void *drv_data) +{ + mbox_data.fn = handler; + mbox_data.cb_data = drv_data; + + return 0; +} + +static int close_window(bool check) +{ + /* + * This isn't strictly prohibited and some daemons let you close + * windows even if none are open. + * I've made the test fail because closing with no windows open is + * a sign that something 'interesting' has happened. + * You should investigate why + * + * If check is false it is because we just want to do the logic + * because open window has been called - you can open a window + * over a closed window obviously + */ + if (check && server_state.win_type == WIN_CLOSED) + return MBOX_R_PARAM_ERROR; + + server_state.win_type = WIN_CLOSED; + mprotect(server_state.lpc_base, server_state.lpc_size, PROT_NONE); + + return MBOX_R_SUCCESS; +} + +static int do_dirty(uint32_t pos, uint32_t size) +{ + pos <<= server_state.block_shift; + if (server_state.api > 1) + size <<= server_state.block_shift; + if (!check_window(pos, size)) { + prlog(PR_ERR, "Trying to dirty not in open window range\n"); + return MBOX_R_PARAM_ERROR; + } + if (server_state.win_type != WIN_WRITE) { + prlog(PR_ERR, "Trying to dirty not write window\n"); + return MBOX_R_PARAM_ERROR; + } + + /* Thats about all actually */ + return MBOX_R_SUCCESS; +} + +void check_timers(bool __unused unused) +{ + /* now that we've handled the message, holla-back */ + if (mbox_data.msg) { + mbox_data.fn(mbox_data.msg, mbox_data.cb_data); + mbox_data.msg = NULL; + } +} + +static int open_window(struct bmc_mbox_msg *msg, bool write, u32 offset, u32 size) +{ + int max_size = server_state.max_read_win << server_state.block_shift; + //int win_size = server_state.def_read_win; + enum win_type type = WIN_READ; + int prot = PROT_READ; + + assert(server_state.win_type == WIN_CLOSED); + + /* Shift params up */ + offset <<= server_state.block_shift; + size <<= server_state.block_shift; + + if (!size || server_state.api == 1) + size = server_state.def_read_win << server_state.block_shift; + + if (write) { + max_size = server_state.max_write_win << server_state.block_shift; + //win_size = server_state.def_write_win; + prot |= PROT_WRITE; + type = WIN_WRITE; + /* Use the default size if zero size is set */ + if (!size || server_state.api == 1) + size = server_state.def_write_win << server_state.block_shift; + } + + + prlog(PR_INFO, "Opening range %#.8x, %#.8x for %s\n", + offset, offset + size - 1, write ? "writing" : "reading"); + + /* XXX: Document this behaviour */ + if ((size + offset) > server_state.lpc_size) { + prlog(PR_INFO, "tried to open beyond end of flash\n"); + return MBOX_R_PARAM_ERROR; + } + + /* XXX: should we do this before or after checking for errors? + * Doing it afterwards ensures consistency between + * implementations + */ + if (server_state.api == 2) + size = MIN(size, max_size); + + mprotect(server_state.lpc_base + offset, size, prot); + server_state.win_type = type; + server_state.win_base = offset; + server_state.win_size = size; + + memset(msg->args, 0, sizeof(msg->args)); + bmc_put_u16(msg, 0, offset >> server_state.block_shift); + if (server_state.api == 1) { + /* + * Put nonsense in here because v1 mbox-flash shouldn't know about it. + * If v1 mbox-flash does read this, 0xffff should trigger a big mistake. + */ + bmc_put_u16(msg, 2, 0xffff >> server_state.block_shift); + bmc_put_u16(msg, 4, 0xffff >> server_state.block_shift); + } else { + bmc_put_u16(msg, 2, size >> server_state.block_shift); + bmc_put_u16(msg, 4, offset >> server_state.block_shift); + } + return MBOX_R_SUCCESS; +} + +int bmc_mbox_enqueue(struct bmc_mbox_msg *msg, + unsigned int __unused timeout_sec) +{ + /* + * FIXME: should we be using the same storage for message + * and response? + */ + int rc = MBOX_R_SUCCESS; + uint32_t start, size; + + if (server_state.reset && msg->command != MBOX_C_GET_MBOX_INFO && + msg->command != MBOX_C_BMC_EVENT_ACK) { + /* + * Real daemons should return an error, but for testing we'll + * be a bit more strict + */ + prlog(PR_EMERG, "Server was in reset state - illegal command %d\n", + msg->command); + exit(1); + } + + switch (msg->command) { + case MBOX_C_RESET_STATE: + prlog(PR_INFO, "RESET_STATE\n"); + server_state.win_type = WIN_CLOSED; + rc = open_window(msg, false, 0, LPC_BLOCKS); + memset(msg->args, 0, sizeof(msg->args)); + break; + + case MBOX_C_GET_MBOX_INFO: + prlog(PR_INFO, "GET_MBOX_INFO version = %d, block_shift = %d\n", + server_state.api, server_state.block_shift); + msg->args[0] = server_state.api; + if (server_state.api == 1) { + prlog(PR_INFO, "\tread_size = 0x%08x, write_size = 0x%08x\n", + server_state.def_read_win, server_state.def_write_win); + bmc_put_u16(msg, 1, server_state.def_read_win); + bmc_put_u16(msg, 3, server_state.def_write_win); + msg->args[5] = 0xff; /* If v1 reads this, 0xff will force the mistake */ + } else { + msg->args[5] = server_state.block_shift; + } + server_state.reset = false; + break; + + case MBOX_C_GET_FLASH_INFO: + prlog(PR_INFO, "GET_FLASH_INFO: size: 0x%" PRIu64 ", erase: 0x%08x\n", + server_state.lpc_size, server_state.erase_granule); + if (server_state.api == 1) { + bmc_put_u32(msg, 0, server_state.lpc_size); + bmc_put_u32(msg, 4, server_state.erase_granule); + } else { + bmc_put_u16(msg, 0, server_state.lpc_size >> server_state.block_shift); + bmc_put_u16(msg, 2, server_state.erase_granule >> server_state.block_shift); + } + break; + + case MBOX_C_CREATE_READ_WINDOW: + start = bmc_get_u16(msg, 0); + size = bmc_get_u16(msg, 2); + prlog(PR_INFO, "CREATE_READ_WINDOW: pos: 0x%08x, len: 0x%08x\n", start, size); + rc = close_window(false); + if (rc != MBOX_R_SUCCESS) + break; + rc = open_window(msg, false, start, size); + break; + + case MBOX_C_CLOSE_WINDOW: + rc = close_window(true); + break; + + case MBOX_C_CREATE_WRITE_WINDOW: + start = bmc_get_u16(msg, 0); + size = bmc_get_u16(msg, 2); + prlog(PR_INFO, "CREATE_WRITE_WINDOW: pos: 0x%08x, len: 0x%08x\n", start, size); + rc = close_window(false); + if (rc != MBOX_R_SUCCESS) + break; + rc = open_window(msg, true, start, size); + break; + + /* TODO: make these do something */ + case MBOX_C_WRITE_FLUSH: + prlog(PR_INFO, "WRITE_FLUSH\n"); + /* + * This behaviour isn't strictly illegal however it could + * be a sign of bad behaviour + */ + if (server_state.api > 1 && !server_state.win_dirty) { + prlog(PR_EMERG, "Version >1 called FLUSH without a previous DIRTY\n"); + exit (1); + } + server_state.win_dirty = false; + if (server_state.api > 1) + break; + + /* This is only done on V1 */ + start = bmc_get_u16(msg, 0); + if (server_state.api == 1) + size = bmc_get_u32(msg, 2); + else + size = bmc_get_u16(msg, 2); + prlog(PR_INFO, "\tpos: 0x%08x len: 0x%08x\n", start, size); + rc = do_dirty(start, size); + break; + case MBOX_C_MARK_WRITE_DIRTY: + start = bmc_get_u16(msg, 0); + if (server_state.api == 1) + size = bmc_get_u32(msg, 2); + else + size = bmc_get_u16(msg, 2); + prlog(PR_INFO, "MARK_WRITE_DIRTY: pos: 0x%08x, len: %08x\n", start, size); + server_state.win_dirty = true; + rc = do_dirty(start, size); + break; + case MBOX_C_BMC_EVENT_ACK: + /* + * Clear any BMC notifier flags. Don't clear the server + * reset state here, it is a permitted command but only + * GET_INFO should clear it. + * + * Make sure that msg->args[0] is only acking bits we told + * it about, in server_state.attn_reg. The caveat is that + * it could NOT ack some bits... + */ + prlog(PR_INFO, "BMC_EVENT_ACK 0x%02x\n", msg->args[0]); + if ((msg->args[0] | server_state.attn_reg) != server_state.attn_reg) { + prlog(PR_EMERG, "Tried to ack bits we didn't say!\n"); + exit(1); + } + msg->bmc &= ~msg->args[0]; + server_state.attn_reg &= ~msg->args[0]; + break; + case MBOX_C_MARK_WRITE_ERASED: + start = bmc_get_u16(msg, 0) << server_state.block_shift; + size = bmc_get_u16(msg, 2) << server_state.block_shift; + /* If we've negotiated v1 this should never be called */ + if (server_state.api == 1) { + prlog(PR_EMERG, "Version 1 protocol called a V2 only command\n"); + exit(1); + } + /* + * This will likely result in flush (but not + * dirty) being called. This is the point. + */ + server_state.win_dirty = true; + /* This should really be done when they call flush */ + memset(server_state.lpc_base + server_state.win_base + start, 0xff, size); + break; + default: + prlog(PR_EMERG, "Got unknown command code from mbox: %d\n", msg->command); + } + + prerror("command response = %d\n", rc); + msg->response = rc; + + mbox_data.msg = msg; + + return 0; +} + +int mbox_server_memcmp(int off, const void *buf, size_t len) +{ + return memcmp(server_state.lpc_base + off, buf, len); +} + +void mbox_server_memset(int c) +{ + memset(server_state.lpc_base, c, server_state.lpc_size); +} + +uint32_t mbox_server_total_size(void) +{ + /* Not actually but for this server we don't differentiate */ + return server_state.lpc_size; +} + +uint32_t mbox_server_erase_granule(void) +{ + return server_state.erase_granule; +} + +int mbox_server_version(void) +{ + return server_state.api; +} + +int mbox_server_reset(unsigned int version, uint8_t block_shift) +{ + if (version > 3) + return 1; + + server_state.api = version; + if (block_shift) + server_state.block_shift = block_shift; + if (server_state.erase_granule < (1 << server_state.block_shift)) + server_state.erase_granule = 1 << server_state.block_shift; + server_state.lpc_size = LPC_BLOCKS * (1 << server_state.block_shift); + free(server_state.lpc_base); + server_state.lpc_base = malloc(server_state.lpc_size); + server_state.attn_reg = MBOX_ATTN_BMC_REBOOT | MBOX_ATTN_BMC_DAEMON_READY; + server_state.win_type = WIN_CLOSED; + server_state.reset = true; + mbox_data.attn(MBOX_ATTN_BMC_REBOOT, mbox_data.cb_attn); + + return 0; +} + +int mbox_server_init(void) +{ + server_state.api = 1; + server_state.reset = true; + + /* We're always ready! */ + server_state.attn_reg = MBOX_ATTN_BMC_DAEMON_READY; + + /* setup server */ + server_state.block_shift = 12; + server_state.erase_granule = 0x1000; + server_state.lpc_size = LPC_BLOCKS * (1 << server_state.block_shift); + server_state.lpc_base = malloc(server_state.lpc_size); + + server_state.def_read_win = 1; /* These are in units of block shift "= 1 is 4K" */ + server_state.def_write_win = 1; /* These are in units of block shift "= 1 is 4K" */ + + server_state.max_read_win = LPC_BLOCKS; + server_state.max_write_win = LPC_BLOCKS; + server_state.win_type = WIN_CLOSED; + + return 0; +} + +void mbox_server_destroy(void) +{ + free(server_state.lpc_base); +} diff --git a/roms/skiboot/libflash/test/mbox-server.h b/roms/skiboot/libflash/test/mbox-server.h new file mode 100644 index 000000000..e7aec817f --- /dev/null +++ b/roms/skiboot/libflash/test/mbox-server.h @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2017 IBM Corp. */ + +#include <stdint.h> + +uint32_t mbox_server_total_size(void); +uint32_t mbox_server_erase_granule(void); +int mbox_server_version(void); +void mbox_server_memset(int c); +int mbox_server_memcmp(int off, const void *buf, size_t len); +int mbox_server_reset(unsigned int version, uint8_t block_shift); +int mbox_server_init(void); +void mbox_server_destroy(void); diff --git a/roms/skiboot/libflash/test/stubs.c b/roms/skiboot/libflash/test/stubs.c new file mode 100644 index 000000000..09f004359 --- /dev/null +++ b/roms/skiboot/libflash/test/stubs.c @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Stubs for libflash test + * + * Copyright 2013-2018 IBM Corp. + */ + +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <stdarg.h> +#include <sys/unistd.h> /* for usleep */ + +#include "../../include/lpc-mbox.h" +#include "stubs.h" + +#define __unused __attribute__((unused)) + +__attribute__((weak)) void check_timers(bool __unused unused) +{ + return; +} + +void time_wait_ms(unsigned long ms) +{ + usleep(ms * 1000); +} + +/* skiboot stubs */ +unsigned long mftb(void) +{ + return 42; +} +unsigned long tb_hz = 512000000ul; + +void _prlog(int __unused log_level, const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +/* accessor junk */ + +void bmc_put_u16(struct bmc_mbox_msg *msg, int offset, uint16_t data) +{ + msg->args[offset + 0] = data & 0xff; + msg->args[offset + 1] = data >> 8; +} + +void bmc_put_u32(struct bmc_mbox_msg *msg, int offset, uint32_t data) +{ + msg->args[offset + 0] = (data) & 0xff; + msg->args[offset + 1] = (data >> 8) & 0xff; + msg->args[offset + 2] = (data >> 16) & 0xff; + msg->args[offset + 3] = (data >> 24) & 0xff; +} + +u32 bmc_get_u32(struct bmc_mbox_msg *msg, int offset) +{ + u32 data = 0; + + data |= msg->args[offset + 0]; + data |= msg->args[offset + 1] << 8; + data |= msg->args[offset + 2] << 16; + data |= msg->args[offset + 3] << 24; + + return data; +} + +u16 bmc_get_u16(struct bmc_mbox_msg *msg, int offset) +{ + u16 data = 0; + + data |= msg->args[offset + 0]; + data |= msg->args[offset + 1] << 8; + + return data; +} + +void *__zalloc(size_t sz) +{ + return calloc(1, sz); +} + +void __free(const void *p) +{ + free((void *)p); +} + +void lock_caller(struct lock *l __attribute__((unused)), + const char *caller __attribute__((unused))) +{ +} + +void unlock(struct lock *l __attribute__((unused))) +{ +} diff --git a/roms/skiboot/libflash/test/stubs.h b/roms/skiboot/libflash/test/stubs.h new file mode 100644 index 000000000..3f890c930 --- /dev/null +++ b/roms/skiboot/libflash/test/stubs.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2018 IBM Corp. */ + +#include <lock.h> +#include <stdint.h> + +#include "../../include/lpc-mbox.h" + +void check_timers(bool unused); +void time_wait_ms(unsigned long ms); +unsigned long mftb(void); +void _prlog(int log_level, const char* fmt, ...); +void bmc_put_u16(struct bmc_mbox_msg *msg, int offset, uint16_t data); +void bmc_put_u32(struct bmc_mbox_msg *msg, int offset, uint32_t data); +u16 bmc_get_u16(struct bmc_mbox_msg *msg, int offset); +u32 bmc_get_u32(struct bmc_mbox_msg *msg, int offset); +void *__zalloc(size_t sz); +void __free(const void *p); +void lock_caller(struct lock *l, const char *caller); +void unlock(struct lock *l); diff --git a/roms/skiboot/libflash/test/test-blocklevel.c b/roms/skiboot/libflash/test/test-blocklevel.c new file mode 100644 index 000000000..7a4fe19ac --- /dev/null +++ b/roms/skiboot/libflash/test/test-blocklevel.c @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2018 IBM Corp. */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +#include <libflash/blocklevel.h> + +#include "../ecc.c" +#include "../blocklevel.c" + +#define __unused __attribute__((unused)) + +#define ERR(fmt...) fprintf(stderr, fmt) + +bool libflash_debug; + +static int bl_test_bad_read(struct blocklevel_device *bl __unused, uint64_t pos __unused, + void *buf __unused, uint64_t len __unused) +{ + return FLASH_ERR_PARM_ERROR; +} + +static int bl_test_read(struct blocklevel_device *bl, uint64_t pos, void *buf, uint64_t len) +{ + if (pos + len > 0x1000) + return FLASH_ERR_PARM_ERROR; + + memcpy(buf, bl->priv + pos, len); + + return 0; +} + +static int bl_test_bad_write(struct blocklevel_device *bl __unused, uint64_t pos __unused, + const void *buf __unused, uint64_t len __unused) +{ + return FLASH_ERR_PARM_ERROR; +} + +static int bl_test_write(struct blocklevel_device *bl, uint64_t pos, const void *buf, uint64_t len) +{ + if (pos + len > 0x1000) + return FLASH_ERR_PARM_ERROR; + + memcpy(bl->priv + pos, buf, len); + + return 0; +} + +static int bl_test_erase(struct blocklevel_device *bl, uint64_t pos, uint64_t len) +{ + if (pos + len > 0x1000) + return FLASH_ERR_PARM_ERROR; + + memset(bl->priv + pos, 0xff, len); + + return 0; +} + +static void dump_buf(uint8_t *buf, int start, int end, int miss) +{ + int i; + + printf("pos: value\n"); + for (i = start; i < end; i++) + printf("%04x: %c%s\n", i, buf[i] == 0xff ? '-' : buf[i], i == miss ? " <- First missmatch" : ""); +} + +/* + * Returns zero if the buffer is ok. Otherwise returns the position of + * the mismatch. If the mismatch is at zero -1 is returned + */ +static int check_buf(uint8_t *buf, int zero_start, int zero_end) +{ + int i; + + for (i = 0; i < 0x1000; i++) { + if (i >= zero_start && i < zero_end && buf[i] != 0xff) + return i == 0 ? -1 : i; + if ((i < zero_start || i >= zero_end) && buf[i] != (i % 26) + 'a') + return i == 0 ? -1 : i; + } + + return 0; +} + +static void reset_buf(uint8_t *buf) +{ + int i; + + for (i = 0; i < 0x1000; i++) { + /* This gives repeating a - z which will be nice to visualise */ + buf[i] = (i % 26) + 'a'; + } +} + +static void print_ptr(void *ptr, int len) +{ + int i; + char *p = ptr; + + printf("0x"); + for (i = 0; i < len; i++) { + putchar(*p); + if (i && i % 8 == 0) { + putchar('\n'); + if (len - i) + printf("0x"); + } + } + putchar('\n'); +} + +int main(void) +{ + struct blocklevel_device bl_mem = { 0 }; + struct blocklevel_device *bl = &bl_mem; + uint64_t with_ecc[10], without_ecc[10]; + char *buf = NULL, *data = NULL; + int i, rc, miss; + + if (blocklevel_ecc_protect(bl, 0, 0x1000)) { + ERR("Failed to blocklevel_ecc_protect!\n"); + return 1; + } + + /* 0x1000 -> 0x3000 should remain unprotected */ + + if (blocklevel_ecc_protect(bl, 0x3000, 0x1000)) { + ERR("Failed to blocklevel_ecc_protect(0x3000, 0x1000)\n"); + return 1; + } + if (blocklevel_ecc_protect(bl, 0x2f00, 0x1100)) { + ERR("Failed to blocklevel_ecc_protect(0x2f00, 0x1100)\n"); + return 1; + } + + /* Zero length protection */ + if (!blocklevel_ecc_protect(bl, 0x4000, 0)) { + ERR("Shouldn't have succeeded blocklevel_ecc_protect(0x4000, 0)\n"); + return 1; + } + + /* Minimum creatable size */ + if (blocklevel_ecc_protect(bl, 0x4000, BYTES_PER_ECC)) { + ERR("Failed to blocklevel_ecc_protect(0x4000, BYTES_PER_ECC)\n"); + return 1; + } + + /* Deal with overlapping protections */ + if (blocklevel_ecc_protect(bl, 0x100, 0x1000)) { + ERR("Failed to protect overlaping region blocklevel_ecc_protect(0x100, 0x1000)\n"); + return 1; + } + + /* Deal with overflow */ + if (!blocklevel_ecc_protect(bl, 1, 0xFFFFFFFF)) { + ERR("Added an 'overflow' protection blocklevel_ecc_protect(1, 0xFFFFFFFF)\n"); + return 1; + } + + /* Protect everything */ + if (blocklevel_ecc_protect(bl, 0, 0xFFFFFFFF)) { + ERR("Couldn't protect everything blocklevel_ecc_protect(0, 0xFFFFFFFF)\n"); + return 1; + } + + if (ecc_protected(bl, 0, 1, NULL) != 1) { + ERR("Invaid result for ecc_protected(0, 1)\n"); + return 1; + } + + if (ecc_protected(bl, 0, 0x1000, NULL) != 1) { + ERR("Invalid result for ecc_protected(0, 0x1000)\n"); + return 1; + } + + if (ecc_protected(bl, 0x100, 0x100, NULL) != 1) { + ERR("Invalid result for ecc_protected(0x0100, 0x100)\n"); + return 1; + } + + /* Clear the protections */ + bl->ecc_prot.n_prot = 0; + /* Reprotect */ + if (blocklevel_ecc_protect(bl, 0x3000, 0x1000)) { + ERR("Failed to blocklevel_ecc_protect(0x3000, 0x1000)\n"); + return 1; + } + /* Deal with overlapping protections */ + if (blocklevel_ecc_protect(bl, 0x100, 0x1000)) { + ERR("Failed to protect overlaping region blocklevel_ecc_protect(0x100, 0x1000)\n"); + return 1; + } + + if (ecc_protected(bl, 0x1000, 0, NULL) != 1) { + ERR("Invalid result for ecc_protected(0x1000, 0)\n"); + return 1; + } + + if (ecc_protected(bl, 0x1000, 0x1000, NULL) != -1) { + ERR("Invalid result for ecc_protected(0x1000, 0x1000)\n"); + return 1; + } + + if (ecc_protected(bl, 0x1000, 0x100, NULL) != 1) { + ERR("Invalid result for ecc_protected(0x1000, 0x100)\n"); + return 1; + } + + if (ecc_protected(bl, 0x2000, 0, NULL) != 0) { + ERR("Invalid result for ecc_protected(0x2000, 0)\n"); + return 1; + } + + if (ecc_protected(bl, 0x4000, 1, NULL) != 0) { + ERR("Invalid result for ecc_protected(0x4000, 1)\n"); + return 1; + } + + /* Check for asking for a region with mixed protection */ + if (ecc_protected(bl, 0x100, 0x2000, NULL) != -1) { + ERR("Invalid result for ecc_protected(0x100, 0x2000)\n"); + return 1; + } + + /* Test the auto extending of regions */ + if (blocklevel_ecc_protect(bl, 0x5000, 0x100)) { + ERR("Failed to blocklevel_ecc_protect(0x5000, 0x100)\n"); + return 1; + } + + if (blocklevel_ecc_protect(bl, 0x5100, 0x100)) { + ERR("Failed to blocklevel_ecc_protect(0x5100, 0x100)\n"); + return 1; + } + + if (blocklevel_ecc_protect(bl, 0x5200, 0x100)) { + ERR("Failed to blocklevel_ecc_protect(0x5200, 0x100)\n"); + return 1; + } + + if (ecc_protected(bl, 0x5120, 0x10, NULL) != 1) { + ERR("Invalid result for ecc_protected(0x5120, 0x10)\n"); + return 1; + } + + if (blocklevel_ecc_protect(bl, 0x4f00, 0x100)) { + ERR("Failed to blocklevel_ecc_protected(0x4900, 0x100)\n"); + return 1; + } + + if (blocklevel_ecc_protect(bl, 0x4900, 0x100)) { + ERR("Failed to blocklevel_ecc_protected(0x4900, 0x100)\n"); + return 1; + } + + if (ecc_protected(bl, 0x4920, 0x10, NULL) != 1) { + ERR("Invalid result for ecc_protected(0x4920, 0x10)\n"); + return 1; + } + + if (blocklevel_ecc_protect(bl, 0x5290, 0x10)) { + ERR("Failed to blocklevel_ecc_protect(0x5290, 0x10)\n"); + return 1; + } + + /* Test the auto extending of regions */ + if (blocklevel_ecc_protect(bl, 0x6000, 0x100)) { + ERR("Failed to blocklevel_ecc_protect(0x6000, 0x100)\n"); + return 1; + } + + if (blocklevel_ecc_protect(bl, 0x6200, 0x100)) { + ERR("Failed to blocklevel_ecc_protect(0x6200, 0x100)\n"); + return 1; + } + + /* Test ECC reading and writing being 100% transparent to the + * caller */ + buf = malloc(0x1000); + data = malloc(0x100); + if (!buf || !data) { + ERR("Malloc failed\n"); + rc = 1; + goto out; + } + memset(bl, 0, sizeof(*bl)); + bl_mem.read = &bl_test_read; + bl_mem.write = &bl_test_write; + bl_mem.erase = &bl_test_erase; + bl_mem.erase_mask = 0xff; + bl_mem.priv = buf; + reset_buf(buf); + + + /* + * Test 1: One full and exact erase block, this shouldn't call + * read or write, ensure this fails if it does. + */ + bl_mem.write = &bl_test_bad_write; + bl_mem.read = &bl_test_bad_read; + if (blocklevel_smart_erase(bl, 0x100, 0x100)) { + ERR("Failed to blocklevel_smart_erase(0x100, 0x100)\n"); + goto out; + } + miss = check_buf(buf, 0x100, 0x200); + if (miss) { + ERR("Buffer mismatch after blocklevel_smart_erase(0x100, 0x100) at 0x%0x\n", + miss == -1 ? 0 : miss); + dump_buf(buf, 0xfc, 0x105, miss == -1 ? 0 : miss); + dump_buf(buf, 0x1fc, 0x205, miss == -1 ? 0 : miss); + goto out; + } + bl_mem.read = &bl_test_read; + bl_mem.write = &bl_test_write; + + reset_buf(buf); + /* Test 2: Only touch one erase block */ + if (blocklevel_smart_erase(bl, 0x20, 0x40)) { + ERR("Failed to blocklevel_smart_erase(0x20, 0x40)\n"); + goto out; + } + miss = check_buf(buf, 0x20, 0x60); + if (miss) { + ERR("Buffer mismatch after blocklevel_smart_erase(0x20, 0x40) at 0x%x\n", + miss == -1 ? 0 : miss); + dump_buf(buf, 0x1c, 0x65, miss == -1 ? 0 : miss); + goto out; + } + + reset_buf(buf); + /* Test 3: Start aligned but finish somewhere in it */ + if (blocklevel_smart_erase(bl, 0x100, 0x50)) { + ERR("Failed to blocklevel_smart_erase(0x100, 0x50)\n"); + goto out; + } + miss = check_buf(buf, 0x100, 0x150); + if (miss) { + ERR("Buffer mismatch after blocklevel_smart_erase(0x100, 0x50) at 0x%0x\n", + miss == -1 ? 0 : miss); + dump_buf(buf, 0xfc, 0x105, miss == -1 ? 0 : miss); + dump_buf(buf, 0x14c, 0x155, miss == -1 ? 0 : miss); + goto out; + } + + reset_buf(buf); + /* Test 4: Start somewhere in it, finish aligned */ + if (blocklevel_smart_erase(bl, 0x50, 0xb0)) { + ERR("Failed to blocklevel_smart_erase(0x50, 0xb0)\n"); + goto out; + } + miss = check_buf(buf, 0x50, 0x100); + if (miss) { + ERR("Buffer mismatch after blocklevel_smart_erase(0x50, 0xb0) at 0x%x\n", + miss == -1 ? 0 : miss); + dump_buf(buf, 0x4c, 0x55, miss == -1 ? 0 : miss); + dump_buf(buf, 0x100, 0x105, miss == -1 ? 0 : miss); + goto out; + } + + reset_buf(buf); + /* Test 5: Cover two erase blocks exactly */ + if (blocklevel_smart_erase(bl, 0x100, 0x200)) { + ERR("Failed to blocklevel_smart_erase(0x100, 0x200)\n"); + goto out; + } + miss = check_buf(buf, 0x100, 0x300); + if (miss) { + ERR("Buffer mismatch after blocklevel_smart_erase(0x100, 0x200) at 0x%x\n", + miss == -1 ? 0 : miss); + dump_buf(buf, 0xfc, 0x105, miss == -1 ? 0 : miss); + dump_buf(buf, 0x2fc, 0x305, miss == -1 ? 0 : miss); + goto out; + } + + reset_buf(buf); + /* Test 6: Erase 1.5 blocks (start aligned) */ + if (blocklevel_smart_erase(bl, 0x100, 0x180)) { + ERR("Failed to blocklevel_smart_erase(0x100, 0x180)\n"); + goto out; + } + miss = check_buf(buf, 0x100, 0x280); + if (miss) { + ERR("Buffer mismatch after blocklevel_smart_erase(0x100, 0x180) at 0x%x\n", + miss == -1 ? 0 : miss); + dump_buf(buf, 0xfc, 0x105, miss == -1 ? 0 : miss); + dump_buf(buf, 0x27c, 0x285, miss == -1 ? 0 : miss); + goto out; + } + + reset_buf(buf); + /* Test 7: Erase 1.5 blocks (end aligned) */ + if (blocklevel_smart_erase(bl, 0x80, 0x180)) { + ERR("Failed to blocklevel_smart_erase(0x80, 0x180)\n"); + goto out; + } + miss = check_buf(buf, 0x80, 0x200); + if (miss) { + ERR("Buffer mismatch after blocklevel_smart_erase(0x80, 0x180) at 0x%x\n", + miss == -1 ? 0 : miss); + dump_buf(buf, 0x7c, 0x85, miss == -1 ? 0 : miss); + dump_buf(buf, 0x1fc, 0x205, miss == -1 ? 0 : miss); + goto out; + } + + reset_buf(buf); + /* Test 8: Erase a big section, not aligned */ + if (blocklevel_smart_erase(bl, 0x120, 0x544)) { + ERR("Failed to blocklevel_smart_erase(0x120, 0x544)\n"); + goto out; + } + miss = check_buf(buf, 0x120, 0x664); + if (miss) { + ERR("Buffer mismatch after blocklevel_smart_erase(0x120, 0x544) at 0x%x\n", + miss == -1 ? 0 : miss); + dump_buf(buf, 0x11c, 0x125, miss == -1 ? 0 : miss); + dump_buf(buf, 0x65f, 0x669, miss == -1 ? 0 : miss); + goto out; + } + + bl_mem.priv = buf; + reset_buf(buf); + + for (i = 0; i < 0x100; i++) + data[i] = i; + + /* This really shouldn't fail */ + rc = blocklevel_ecc_protect(bl, 0, 0x100); + if (rc) { + ERR("Couldn't blocklevel_ecc_protect(0, 0x100)\n"); + goto out; + } + + rc = blocklevel_write(bl, 0, data, 0x100); + if (rc) { + ERR("Couldn't blocklevel_write(0, 0x100)\n"); + goto out; + } + + rc = blocklevel_write(bl, 0x200, data, 0x100); + if (rc) { + ERR("Couldn't blocklevel_write(0x200, 0x100)\n"); + goto out; + } + + /* + * 0x50 once adjusted for the presence of ECC becomes 0x5a which + * is ECC aligned. + */ + rc = blocklevel_read(bl, 0x50, with_ecc, 8); + if (rc) { + ERR("Couldn't blocklevel_read(0x50, 8) with ecc rc=%d\n", rc); + goto out; + } + rc = blocklevel_read(bl, 0x250, without_ecc, 8); + if (rc) { + ERR("Couldn't blocklevel_read(0x250, 8) without ecc rc=%d\n", rc); + goto out; + } + if (memcmp(with_ecc, without_ecc, 8) || memcmp(with_ecc, &data[0x50], 8)) { + ERR("ECC read and non-ECC read don't match or are wrong line: %d\n", __LINE__); + print_ptr(with_ecc, 8); + print_ptr(without_ecc, 8); + print_ptr(&data[50], 8); + rc = 1; + goto out; + } + + /* + * 0x50 once adjusted for the presence of ECC becomes 0x5a which + * is ECC aligned. + * So 0x4f won't be aligned! + */ + rc = blocklevel_read(bl, 0x4f, with_ecc, 8); + if (rc) { + ERR("Couldn't blocklevel_read(0x4f, 8) with ecc %d\n", rc); + goto out; + } + rc = blocklevel_read(bl, 0x24f, without_ecc, 8); + if (rc) { + ERR("Couldn't blocklevel_read(0x24f, 8) without ecc %d\n", rc); + goto out; + } + if (memcmp(with_ecc, without_ecc, 8) || memcmp(with_ecc, &data[0x4f], 8)) { + ERR("ECC read and non-ECC read don't match or are wrong line: %d\n", __LINE__); + print_ptr(with_ecc, 8); + print_ptr(without_ecc, 8); + print_ptr(&data[0x4f], 8); + rc = 1; + goto out; + } + + /* + * 0x50 once adjusted for the presence of ECC becomes 0x5a which + * is ECC aligned. + */ + rc = blocklevel_read(bl, 0x50, with_ecc, 16); + if (rc) { + ERR("Couldn't blocklevel_read(0x50, 16) with ecc %d\n", rc); + goto out; + } + rc = blocklevel_read(bl, 0x250, without_ecc, 16); + if (rc) { + ERR("Couldn't blocklevel_read(0x250, 16) without ecc %d\n", rc); + goto out; + } + if (memcmp(with_ecc, without_ecc, 16)|| memcmp(with_ecc, &data[0x50], 16)) { + ERR("(long read )ECC read and non-ECC read don't match or are wrong line: %d\n", __LINE__); + print_ptr(with_ecc, 16); + print_ptr(without_ecc, 16); + print_ptr(&data[0x50], 16); + rc = 1; + goto out; + } + + /* + * 0x50 once adjusted for the presence of ECC becomes 0x5a which + * is ECC aligned. So 4f won't be. + */ + rc = blocklevel_read(bl, 0x4f, with_ecc, 24); + if (rc) { + ERR("Couldn't blocklevel_read(0x4f, 24) with ecc %d\n", rc); + goto out; + } + rc = blocklevel_read(bl, 0x24f, without_ecc, 24); + if (rc) { + ERR("Couldn't blocklevel_read(0x24f, 24) without ecc %d\n", rc); + goto out; + } + if (memcmp(with_ecc, without_ecc, 24)|| memcmp(with_ecc, &data[0x4f], 24)) { + ERR("(long read )ECC read and non-ECC read don't match or are wrong: %d\n", __LINE__); + print_ptr(with_ecc, 24); + print_ptr(without_ecc, 24); + print_ptr(&data[0x4f], 24); + rc = 1; + goto out; + } + + /* + * Now lets try to write at non ECC aligned positions + * Go easy first, 0x50 becomes 0x5a which is ECC byte aligned but + * not aligned to the start of the partition + */ + + rc = blocklevel_write(bl, 0x50, data, 0xb0); + if (rc) { + ERR("Couldn't blocklevel_write()\n"); + goto out; + } + /* Read 8 bytes before to make sure we didn't ruin that */ + rc = blocklevel_read(bl, 0x48, with_ecc, 24); + if (rc) { + ERR("Couldn't blocklevel_read() with ecc %d\n", rc); + goto out; + } + if (memcmp(with_ecc, data + 0x48, 8) || memcmp(with_ecc + 1, data, 16)) { + rc = 1; + ERR("Couldn't read back what we thought we wrote line: %d\n", __LINE__); + print_ptr(with_ecc, 24); + print_ptr(&data[0x48], 8); + print_ptr(data, 16); + goto out; + } + + /* Ok lets get tricky */ + rc = blocklevel_write(bl, 0x31, data, 0xcf); + if (rc) { + ERR("Couldn't blocklevel_write(0x31, 0xcf)\n"); + goto out; + } + /* Read 8 bytes before to make sure we didn't ruin that */ + rc = blocklevel_read(bl, 0x29, with_ecc, 24); + if (rc) { + ERR("Couldn't blocklevel_read(0x29, 24) with ecc rc=%d\n", rc); + goto out; + } + if (memcmp(with_ecc, &data[0x29], 8) || memcmp(with_ecc + 1, data, 16)) { + ERR("Couldn't read back what we thought we wrote line: %d\n", __LINE__); + print_ptr(with_ecc, 24); + print_ptr(&data[0x29], 8); + print_ptr(data, 16); + rc = 1; + goto out; + } + + /* + * Rewrite the pattern that we've messed up + */ + rc = blocklevel_write(bl, 0, data, 0x100); + if (rc) { + ERR("Couldn't blocklevel_write(0, 0x100) to reset\n"); + goto out; + } + + /* Be unalignmed as possible from now on, starting somewhat easy */ + rc = blocklevel_read(bl, 0, with_ecc, 5); + if (rc) { + ERR("Couldn't blocklevel_write(0, 5)\n"); + goto out; + } + if (memcmp(with_ecc, data, 5)) { + ERR("blocklevel_read 5, 0) didn't match line: %d\n", __LINE__); + print_ptr(with_ecc, 5); + print_ptr(data, 5); + rc = 1; + goto out; + } + + /* 39 is neither divisible by 8 or by 9 */ + rc = blocklevel_read(bl, 39, with_ecc, 5); + if (rc) { + ERR("Couldn't blocklevel_write(39, 5)\n"); + goto out; + } + if (memcmp(with_ecc, &data[39], 5)) { + ERR("blocklevel_read(5, 39() didn't match line: %d\n", __LINE__); + print_ptr(with_ecc, 5); + print_ptr(&data[39], 5); + rc = 1; + goto out; + } + + rc = blocklevel_read(bl, 0xb, &with_ecc, 39); + if (rc) { + ERR("Couldn't blocklevel_read(0xb, 39)\n"); + goto out; + } + if (memcmp(with_ecc, &data[0xb], 39)) { + ERR("Strange sized and positioned read failed, blocklevel_read(0xb, 39) line: %d\n", __LINE__); + print_ptr(with_ecc, 39); + print_ptr(&data[0xb], 39); + rc = 1; + goto out; + } + + rc = blocklevel_write(bl, 39, data, 50); + if (rc) { + ERR("Couldn't blocklevel_write(39, 50)\n"); + goto out; + } + + rc = blocklevel_read(bl, 32, with_ecc, 39); + if (rc) { + ERR("Couldn't blocklevel_read(32, 39)\n"); + goto out; + } + + if (memcmp(with_ecc, &data[32], 7) || memcmp(((char *)with_ecc) + 7, data, 32)) { + ERR("Read back of odd placed/odd sized write failed, blocklevel_read(32, 39) line: %d\n", __LINE__); + print_ptr(with_ecc, 39); + print_ptr(&data[32], 7); + print_ptr(data, 32); + rc = 1; + goto out; + } + +out: + free(buf); + free(data); +return rc; +} diff --git a/roms/skiboot/libflash/test/test-ecc.c b/roms/skiboot/libflash/test/test-ecc.c new file mode 100644 index 000000000..b9489f8ec --- /dev/null +++ b/roms/skiboot/libflash/test/test-ecc.c @@ -0,0 +1,510 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2018 IBM Corp. */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +#include <libflash/ecc.h> + +#include "../ecc.c" + +#define __unused __attribute__((unused)) + +#define ERR(fmt...) fprintf(stderr, fmt) + +#define NUM_ECC_ROWS 320 + +/* + * Note this data is big endian as this is what the ecc code expects. + * The ECC code returns IBM bit numbers assuming the word was in CPU + * endian! + */ + +/* 8 data bytes 1 ecc byte per row */ +struct ecc64 ecc_data[] = { + { 0xfeffffffffffffff, 0x00 }, /* This row will have ecc correct bit 63 */ + { 0xfdffffffffffffff, 0x00 }, /* This row will have ecc correct bit 62 */ + { 0xfbffffffffffffff, 0x00 }, /* This row will have ecc correct bit 61 */ + { 0xf7ffffffffffffff, 0x00 }, /* This row will have ecc correct bit 60 */ + { 0xefffffffffffffff, 0x00 }, /* This row will have ecc correct bit 59 */ + { 0xdfffffffffffffff, 0x00 }, /* This row will have ecc correct bit 58 */ + { 0xbfffffffffffffff, 0x00 }, /* This row will have ecc correct bit 57 */ + { 0x7fffffffffffffff, 0x00 }, /* This row will have ecc correct bit 56 */ + { 0xfffeffffffffffff, 0x00 }, /* This row will have ecc correct bit 55 */ + { 0xfffdffffffffffff, 0x00 }, /* This row will have ecc correct bit 54 */ + { 0xfffbffffffffffff, 0x00 }, /* This row will have ecc correct bit 53 */ + { 0xfff7ffffffffffff, 0x00 }, /* This row will have ecc correct bit 52 */ + { 0xffefffffffffffff, 0x00 }, /* This row will have ecc correct bit 51 */ + { 0xffdfffffffffffff, 0x00 }, /* This row will have ecc correct bit 50 */ + { 0xffbfffffffffffff, 0x00 }, /* This row will have ecc correct bit 49 */ + { 0xff7fffffffffffff, 0x00 }, /* This row will have ecc correct bit 48 */ + { 0xfffffeffffffffff, 0x00 }, /* This row will have ecc correct bit 47 */ + { 0xfffffdffffffffff, 0x00 }, /* This row will have ecc correct bit 46 */ + { 0xfffffbffffffffff, 0x00 }, /* This row will have ecc correct bit 45 */ + { 0xfffff7ffffffffff, 0x00 }, /* This row will have ecc correct bit 44 */ + { 0xffffefffffffffff, 0x00 }, /* This row will have ecc correct bit 43 */ + { 0xffffdfffffffffff, 0x00 }, /* This row will have ecc correct bit 42 */ + { 0xffffbfffffffffff, 0x00 }, /* This row will have ecc correct bit 41 */ + { 0xffff7fffffffffff, 0x00 }, /* This row will have ecc correct bit 40 */ + { 0xfffffffeffffffff, 0x00 }, /* This row will have ecc correct bit 39 */ + { 0xfffffffdffffffff, 0x00 }, /* This row will have ecc correct bit 38 */ + { 0xfffffffbffffffff, 0x00 }, /* This row will have ecc correct bit 37 */ + { 0xfffffff7ffffffff, 0x00 }, /* This row will have ecc correct bit 36 */ + { 0xffffffefffffffff, 0x00 }, /* This row will have ecc correct bit 35 */ + { 0xffffffdfffffffff, 0x00 }, /* This row will have ecc correct bit 34 */ + { 0xffffffbfffffffff, 0x00 }, /* This row will have ecc correct bit 33 */ + { 0xffffff7fffffffff, 0x00 }, /* This row will have ecc correct bit 32 */ + { 0xfffffffffeffffff, 0x00 }, /* This row will have ecc correct bit 31 */ + { 0xfffffffffdffffff, 0x00 }, /* This row will have ecc correct bit 30 */ + { 0xfffffffffbffffff, 0x00 }, /* This row will have ecc correct bit 29 */ + { 0xfffffffff7ffffff, 0x00 }, /* This row will have ecc correct bit 28 */ + { 0xffffffffefffffff, 0x00 }, /* This row will have ecc correct bit 27 */ + { 0xffffffffdfffffff, 0x00 }, /* This row will have ecc correct bit 26 */ + { 0xffffffffbfffffff, 0x00 }, /* This row will have ecc correct bit 25 */ + { 0xffffffff7fffffff, 0x00 }, /* This row will have ecc correct bit 24 */ + { 0xfffffffffffeffff, 0x00 }, /* This row will have ecc correct bit 23 */ + { 0xfffffffffffdffff, 0x00 }, /* This row will have ecc correct bit 22 */ + { 0xfffffffffffbffff, 0x00 }, /* This row will have ecc correct bit 21 */ + { 0xfffffffffff7ffff, 0x00 }, /* This row will have ecc correct bit 20 */ + { 0xffffffffffefffff, 0x00 }, /* This row will have ecc correct bit 19 */ + { 0xffffffffffdfffff, 0x00 }, /* This row will have ecc correct bit 18 */ + { 0xffffffffffbfffff, 0x00 }, /* This row will have ecc correct bit 17 */ + { 0xffffffffff7fffff, 0x00 }, /* This row will have ecc correct bit 16 */ + { 0xfffffffffffffeff, 0x00 }, /* This row will have ecc correct bit 15 */ + { 0xfffffffffffffdff, 0x00 }, /* This row will have ecc correct bit 14 */ + { 0xfffffffffffffbff, 0x00 }, /* This row will have ecc correct bit 13 */ + { 0xfffffffffffff7ff, 0x00 }, /* This row will have ecc correct bit 12 */ + { 0xffffffffffffefff, 0x00 }, /* This row will have ecc correct bit 11 */ + { 0xffffffffffffdfff, 0x00 }, /* This row will have ecc correct bit 10 */ + { 0xffffffffffffbfff, 0x00 }, /* This row will have ecc correct bit 9 */ + { 0xffffffffffff7fff, 0x00 }, /* This row will have ecc correct bit 8 */ + { 0xfffffffffffffffe, 0x00 }, /* This row will have ecc correct bit 7 */ + { 0xfffffffffffffffd, 0x00 }, /* This row will have ecc correct bit 6 */ + { 0xfffffffffffffffb, 0x00 }, /* This row will have ecc correct bit 5 */ + { 0xfffffffffffffff7, 0x00 }, /* This row will have ecc correct bit 4 */ + { 0xffffffffffffffef, 0x00 }, /* This row will have ecc correct bit 3 */ + { 0xffffffffffffffdf, 0x00 }, /* This row will have ecc correct bit 2 */ + { 0xffffffffffffffbf, 0x00 }, /* This row will have ecc correct bit 1 */ + { 0xffffffffffffff7f, 0x00 }, /* This row will have ecc correct bit 0 */ + /* + * 'Randomised' input into eccgenerate 0x54f7c5d1 was seeded to rand() + * Note: eccgenerate from skiboot commit 6cfaa3ba1015c6ac9cc4a06f878b4289022cff54 + * was used to generate these ecc numbers + */ + { 0x29d87c7c8ab7d46d, 0xb9 }, /* Use this row to check eccgenerate() */ + { 0x9064174098381641, 0x3b }, /* Use this row to check eccgenerate() */ + { 0x77fd7d2fc7d22154, 0xe4 }, /* Use this row to check eccgenerate() */ + { 0x6b02ba39b64a6168, 0xbf }, /* Use this row to check eccgenerate() */ + { 0x68fa9c633eef0544, 0x2a }, /* Use this row to check eccgenerate() */ + { 0xe814b258b3f92e55, 0x35 }, /* Use this row to check eccgenerate() */ + { 0xc3e2bd658db4db6d, 0xda }, /* Use this row to check eccgenerate() */ + { 0xe1dd487b6209876a, 0x45 }, /* Use this row to check eccgenerate() */ + { 0x309f9e6b91831433, 0xe4 }, /* Use this row to check eccgenerate() */ + { 0xd8b77d39f4d66410, 0x6c }, /* Use this row to check eccgenerate() */ + { 0x83ba293cf30a9e6a, 0xc9 }, /* Use this row to check eccgenerate() */ + { 0x3aeaef79af97ec1a, 0x09 }, /* Use this row to check eccgenerate() */ + { 0xa90ef431e4778c43, 0x91 }, /* Use this row to check eccgenerate() */ + { 0xa74bbf1e6b6fda00, 0xc5 }, /* Use this row to check eccgenerate() */ + { 0x67b5a872efa57c30, 0xb9 }, /* Use this row to check eccgenerate() */ + { 0x795d511e3605ff67, 0x03 }, /* Use this row to check eccgenerate() */ + { 0xce3d1529918d256f, 0x36 }, /* Use this row to check eccgenerate() */ + { 0x586047430ac2685e, 0xab }, /* Use this row to check eccgenerate() */ + { 0xc00cca46463b9358, 0x42 }, /* Use this row to check eccgenerate() */ + { 0x842a991cc362017d, 0xb2 }, /* Use this row to check eccgenerate() */ + { 0x765c30522807672a, 0x26 }, /* Use this row to check eccgenerate() */ + { 0xb5bb42186c3f4b75, 0x2b }, /* Use this row to check eccgenerate() */ + { 0xce48d25f393fee37, 0x90 }, /* Use this row to check eccgenerate() */ + { 0xcbc2026b96998b13, 0x40 }, /* Use this row to check eccgenerate() */ + { 0x8b70f023ffe7704b, 0x23 }, /* Use this row to check eccgenerate() */ + { 0xf2f20e36a37a8024, 0x19 }, /* Use this row to check eccgenerate() */ + { 0x52126d3f0e2b1a60, 0xa0 }, /* Use this row to check eccgenerate() */ + { 0xf2a2a6232dddfe2f, 0xc4 }, /* Use this row to check eccgenerate() */ + { 0x984cd930fb206171, 0xa5 }, /* Use this row to check eccgenerate() */ + { 0xeac6dd2199ee6542, 0xea }, /* Use this row to check eccgenerate() */ + { 0xd0f3642aff018223, 0x3b }, /* Use this row to check eccgenerate() */ + { 0x908fa71263242f40, 0x0a }, /* Use this row to check eccgenerate() */ + { 0x6de6971e9e317a53, 0xa6 }, /* Use this row to check eccgenerate() */ + { 0xe46c0d2ce8efee55, 0xa4 }, /* Use this row to check eccgenerate() */ + { 0xab52f0522df36165, 0x06 }, /* Use this row to check eccgenerate() */ + { 0x55fac80f6997a648, 0x9a }, /* Use this row to check eccgenerate() */ + { 0xd5d6f13d21af2025, 0xed }, /* Use this row to check eccgenerate() */ + { 0x5bee0e5d0bb60b28, 0x66 }, /* Use this row to check eccgenerate() */ + { 0xa14f973ba41fc41d, 0xa8 }, /* Use this row to check eccgenerate() */ + { 0xa307356926b11148, 0x5a }, /* Use this row to check eccgenerate() */ + { 0xc92b926c2cc0875f, 0x7e }, /* Use this row to check eccgenerate() */ + { 0x3aeba13f95fa431f, 0x92 }, /* Use this row to check eccgenerate() */ + { 0xc2d7424f1b3eff2b, 0xe6 }, /* Use this row to check eccgenerate() */ + { 0x165f601d2c8e4863, 0x2b }, /* Use this row to check eccgenerate() */ + { 0xc67cae255a241c00, 0x78 }, /* Use this row to check eccgenerate() */ + { 0x5a269e2300263e3f, 0x07 }, /* Use this row to check eccgenerate() */ + { 0x634a6d7f96701350, 0xe9 }, /* Use this row to check eccgenerate() */ + { 0x34a28d23eab54536, 0xd2 }, /* Use this row to check eccgenerate() */ + { 0xd3a5340cd130051e, 0x48 }, /* Use this row to check eccgenerate() */ + { 0xfe236703190f9b4f, 0x7e }, /* Use this row to check eccgenerate() */ + { 0x82a641187ef8245f, 0x20 }, /* Use this row to check eccgenerate() */ + { 0xa0a74504541e3013, 0xc7 }, /* Use this row to check eccgenerate() */ + { 0x5fd43b3b577d3356, 0x85 }, /* Use this row to check eccgenerate() */ + { 0xfb9cf773fb955461, 0x06 }, /* Use this row to check eccgenerate() */ + { 0x214766290024d376, 0x80 }, /* Use this row to check eccgenerate() */ + { 0x2de45a569ea42c5d, 0x22 }, /* Use this row to check eccgenerate() */ + { 0x349f707cea72f815, 0xf3 }, /* Use this row to check eccgenerate() */ + { 0x05b1f74167cffc15, 0xe9 }, /* Use this row to check eccgenerate() */ + { 0x945d4579f676b34b, 0x63 }, /* Use this row to check eccgenerate() */ + { 0x519bcf4b1b10585f, 0x47 }, /* Use this row to check eccgenerate() */ + { 0x1b36961e5adaf31e, 0x25 }, /* Use this row to check eccgenerate() */ + { 0xf04a076fabc16d6f, 0x20 }, /* Use this row to check eccgenerate() */ + { 0x9577b3257e80031e, 0xef }, /* Use this row to check eccgenerate() */ + { 0x4fb1083c24ed9412, 0x97 }, /* Use this row to check eccgenerate() */ + { 0x3dfc2f62681de831, 0x1f }, /* Use this row to check eccgenerate() */ + { 0xe7150d114ed56f3f, 0x10 }, /* Use this row to check eccgenerate() */ + { 0xa2f39f52bfa2717a, 0x40 }, /* Use this row to check eccgenerate() */ + { 0x1720a55087bd5215, 0xb3 }, /* Use this row to check eccgenerate() */ + { 0x8253a77601c8db0d, 0x45 }, /* Use this row to check eccgenerate() */ + { 0x01ecae0412bd9c44, 0x5f }, /* Use this row to check eccgenerate() */ + { 0xb161c921a39a0d20, 0x51 }, /* Use this row to check eccgenerate() */ + { 0x8d0d06362ed0095b, 0x94 }, /* Use this row to check eccgenerate() */ + { 0x969f0671e5003a1e, 0x9b }, /* Use this row to check eccgenerate() */ + { 0xdb77ed6992befd77, 0x63 }, /* Use this row to check eccgenerate() */ + { 0xadce55572afd4b6a, 0x3e }, /* Use this row to check eccgenerate() */ + { 0x84d73f092c13bd35, 0x50 }, /* Use this row to check eccgenerate() */ + { 0xd7d42a25c804ec75, 0x05 }, /* Use this row to check eccgenerate() */ + { 0x4685ef1374224778, 0x72 }, /* Use this row to check eccgenerate() */ + { 0x980fdc0a6d4cde4a, 0x9d }, /* Use this row to check eccgenerate() */ + { 0xd569c67c9636f84f, 0x81 }, /* Use this row to check eccgenerate() */ + { 0xe40b680fd60b0c6d, 0x2c }, /* Use this row to check eccgenerate() */ + { 0x95ae7d67bc7fd30d, 0x72 }, /* Use this row to check eccgenerate() */ + { 0x433d262386ff0762, 0xf4 }, /* Use this row to check eccgenerate() */ + { 0x87c7e36facce2238, 0x5a }, /* Use this row to check eccgenerate() */ + { 0xbf8bbf7cc590cd19, 0xe0 }, /* Use this row to check eccgenerate() */ + { 0x682bdb3988b39274, 0x4f }, /* Use this row to check eccgenerate() */ + { 0xb7839c4f70ed881e, 0x6b }, /* Use this row to check eccgenerate() */ + { 0x55eec23cf538e16f, 0x72 }, /* Use this row to check eccgenerate() */ + { 0x87f7de674d23a340, 0xb4 }, /* Use this row to check eccgenerate() */ + { 0x7720ef2a3066b026, 0x7c }, /* Use this row to check eccgenerate() */ + { 0x5d796d5c34c6343f, 0x5e }, /* Use this row to check eccgenerate() */ + { 0xfcca2035fbf72e34, 0xc6 }, /* Use this row to check eccgenerate() */ + { 0x6f1a762c344e9801, 0x87 }, /* Use this row to check eccgenerate() */ + { 0xa19a764c43501049, 0x35 }, /* Use this row to check eccgenerate() */ + { 0xd9860819072a5237, 0x6a }, /* Use this row to check eccgenerate() */ + { 0xdd355e2477043d49, 0x2d }, /* Use this row to check eccgenerate() */ + { 0x33841057bd927028, 0xaa }, /* Use this row to check eccgenerate() */ + { 0x4392780a73e4db0b, 0xfa }, /* Use this row to check eccgenerate() */ + { 0x1fb3fe4377c1367a, 0x47 }, /* Use this row to check eccgenerate() */ + { 0x3c520414ca595c7a, 0x58 }, /* Use this row to check eccgenerate() */ + { 0x520def6ede3ebe40, 0xac }, /* Use this row to check eccgenerate() */ + { 0x4e2c475fa57ddf4d, 0x5c }, /* Use this row to check eccgenerate() */ + { 0x9ab6c03d09918b3e, 0x95 }, /* Use this row to check eccgenerate() */ + { 0x56b42e7fa31a0a1c, 0x5d }, /* Use this row to check eccgenerate() */ + { 0xd480ba4222ae9f25, 0x87 }, /* Use this row to check eccgenerate() */ + { 0x5674d464cdd41d2a, 0xc7 }, /* Use this row to check eccgenerate() */ + { 0xc8cc4c5e31fa271f, 0x6e }, /* Use this row to check eccgenerate() */ + { 0x6548c020533ff519, 0x00 }, /* Use this row to check eccgenerate() */ + { 0x968f056337e7c20a, 0x0e }, /* Use this row to check eccgenerate() */ + { 0x3f11154207e3366d, 0xbe }, /* Use this row to check eccgenerate() */ + { 0x7ee773366f160e7c, 0x53 }, /* Use this row to check eccgenerate() */ + { 0x2ca97e241c477366, 0x1c }, /* Use this row to check eccgenerate() */ + { 0x8f2b4f72b16b840d, 0x88 }, /* Use this row to check eccgenerate() */ + { 0x282dbb076f3bf72e, 0xd0 }, /* Use this row to check eccgenerate() */ + { 0x39955329afde4d36, 0xc7 }, /* Use this row to check eccgenerate() */ + { 0x8d1d0c77657fbf1b, 0x22 }, /* Use this row to check eccgenerate() */ + { 0x0afd9e698ba24218, 0x1a }, /* Use this row to check eccgenerate() */ + { 0x9533ce56dc495356, 0x2a }, /* Use this row to check eccgenerate() */ + { 0x7f645d72a4b35f27, 0x80 }, /* Use this row to check eccgenerate() */ + { 0xc661ff4cebe7fc55, 0xe2 }, /* Use this row to check eccgenerate() */ + { 0xb9bc1a0053e51735, 0xff }, /* Use this row to check eccgenerate() */ + { 0x84df3f541dd6d331, 0x54 }, /* Use this row to check eccgenerate() */ + { 0x7015c94b8189675e, 0x02 }, /* Use this row to check eccgenerate() */ + { 0xb9702a69ea270075, 0x1f }, /* Use this row to check eccgenerate() */ + { 0xf10a376206a5ce2e, 0x6f }, /* Use this row to check eccgenerate() */ + { 0x75bbdc2af8813f2b, 0xb1 }, /* Use this row to check eccgenerate() */ + { 0x14c9b2116ff2aa18, 0x7a }, /* Use this row to check eccgenerate() */ + { 0x205e2f26a1645b4f, 0x2b }, /* Use this row to check eccgenerate() */ + { 0x10a0527ea4f40104, 0xf6 }, /* Use this row to check eccgenerate() */ + { 0x53d34f3a498bea2d, 0x93 }, /* Use this row to check eccgenerate() */ + { 0xae0aaa494935a627, 0xbf }, /* Use this row to check eccgenerate() */ + { 0xd4d7e83fe0f05b31, 0x58 }, /* Use this row to check eccgenerate() */ + { 0xbc3aaf07b8074933, 0x74 }, /* Use this row to check eccgenerate() */ + { 0x5cbba85a690bb716, 0xbf }, /* Use this row to check eccgenerate() */ + { 0x55f3b36c3c9f0c7a, 0x3a }, /* Use this row to check eccgenerate() */ + { 0x8f84242f231da827, 0x50 }, /* Use this row to check eccgenerate() */ + { 0x40f37b590eb0ce6c, 0x9c }, /* Use this row to check eccgenerate() */ + { 0x8f39364b14646403, 0x0b }, /* Use this row to check eccgenerate() */ + { 0xfe8b6478b0084525, 0x21 }, /* Use this row to check eccgenerate() */ + { 0xb6ad135448aa6034, 0x1c }, /* Use this row to check eccgenerate() */ + { 0x402ca05fef969b5a, 0x90 }, /* Use this row to check eccgenerate() */ + { 0x5e8946732b69f07e, 0xaa }, /* Use this row to check eccgenerate() */ + { 0xcccd4b4e55f55271, 0xe8 }, /* Use this row to check eccgenerate() */ + { 0xf9e954757ee77519, 0xf8 }, /* Use this row to check eccgenerate() */ + { 0xc7726047dc6d9e4c, 0x67 }, /* Use this row to check eccgenerate() */ + { 0x25a344744cbda42f, 0x77 }, /* Use this row to check eccgenerate() */ + { 0x2cae0061757d0a11, 0xca }, /* Use this row to check eccgenerate() */ + { 0x2d855344f97a2d34, 0x9b }, /* Use this row to check eccgenerate() */ + { 0x6386e44ae9e8af68, 0x6c }, /* Use this row to check eccgenerate() */ + { 0x2588bc628a40fc1e, 0x4c }, /* Use this row to check eccgenerate() */ + { 0xad5da446b8799837, 0x31 }, /* Use this row to check eccgenerate() */ + { 0xc6296724b40ce111, 0xde }, /* Use this row to check eccgenerate() */ + { 0xc8704515ed502020, 0x72 }, /* Use this row to check eccgenerate() */ + { 0x9d59654555639d6f, 0x16 }, /* Use this row to check eccgenerate() */ + { 0x9e0dfe23c6fca90d, 0x37 }, /* Use this row to check eccgenerate() */ + { 0xb593456853077919, 0xee }, /* Use this row to check eccgenerate() */ + { 0x7e706918de399e03, 0xe7 }, /* Use this row to check eccgenerate() */ + { 0x332ff174131d8c5b, 0x34 }, /* Use this row to check eccgenerate() */ + { 0x920402754a3eb566, 0x2f }, /* Use this row to check eccgenerate() */ + { 0x26ac53332c19466a, 0x0c }, /* Use this row to check eccgenerate() */ + { 0x78d6ea195977623c, 0x6f }, /* Use this row to check eccgenerate() */ + { 0xcff46c4d4b4f9827, 0x20 }, /* Use this row to check eccgenerate() */ + { 0x44cac55ba584eb7a, 0x5f }, /* Use this row to check eccgenerate() */ + { 0x8e6d9b63fc79c011, 0xc8 }, /* Use this row to check eccgenerate() */ + { 0x86babc30a750aa26, 0x20 }, /* Use this row to check eccgenerate() */ + { 0x5fca425eb3f55746, 0x12 }, /* Use this row to check eccgenerate() */ + { 0x6702395833186177, 0xaf }, /* Use this row to check eccgenerate() */ + { 0x2069811725f4a902, 0x87 }, /* Use this row to check eccgenerate() */ + { 0x7b57477230737e6d, 0xd9 }, /* Use this row to check eccgenerate() */ + { 0xf66f287bbdc2e65c, 0xfa }, /* Use this row to check eccgenerate() */ + { 0x10ca5f7619654516, 0x52 }, /* Use this row to check eccgenerate() */ + { 0xf79ee319ac036e63, 0x58 }, /* Use this row to check eccgenerate() */ + { 0xbf20fa3e8e3ac90e, 0x82 }, /* Use this row to check eccgenerate() */ + { 0xd8787e752bced40e, 0x54 }, /* Use this row to check eccgenerate() */ + { 0x57e71a795125fc33, 0xfe }, /* Use this row to check eccgenerate() */ + { 0xab9c5e70fe24d228, 0xfc }, /* Use this row to check eccgenerate() */ + { 0x49746a50d0bd0513, 0x9d }, /* Use this row to check eccgenerate() */ + { 0x7542f10d7a91cb3d, 0xb9 }, /* Use this row to check eccgenerate() */ + { 0x760b8c4f8e3e302c, 0x82 }, /* Use this row to check eccgenerate() */ + { 0x358fda5203b08c71, 0x23 }, /* Use this row to check eccgenerate() */ + { 0xb6a5e437fdc54800, 0xb6 }, /* Use this row to check eccgenerate() */ + { 0x30dea97795591d31, 0x7c }, /* Use this row to check eccgenerate() */ + { 0xba4dc7331da81d10, 0x11 }, /* Use this row to check eccgenerate() */ + { 0x4d1b9c7d51472b0f, 0x37 }, /* Use this row to check eccgenerate() */ + { 0x0e0a126c35a50e26, 0xd6 }, /* Use this row to check eccgenerate() */ + { 0x4e0a543c448bc478, 0x0f }, /* Use this row to check eccgenerate() */ + { 0xf08e325c1fd47162, 0x6b }, /* Use this row to check eccgenerate() */ + { 0xad0e3b7146a93756, 0x86 }, /* Use this row to check eccgenerate() */ + { 0x71770c65afaf2c1b, 0xae }, /* Use this row to check eccgenerate() */ + { 0x01d5284f8687b966, 0x37 }, /* Use this row to check eccgenerate() */ + { 0x84ac8b0fc85e275e, 0x86 }, /* Use this row to check eccgenerate() */ + { 0x981c2d71ac71873f, 0x4e }, /* Use this row to check eccgenerate() */ + { 0x2603537dce20f65f, 0xb5 }, /* Use this row to check eccgenerate() */ + { 0x5c5f260c0d5f1e7f, 0x0b }, /* Use this row to check eccgenerate() */ + { 0x100fab709c0edf4c, 0xc9 }, /* Use this row to check eccgenerate() */ + { 0x99d4274d91ee005f, 0x83 }, /* Use this row to check eccgenerate() */ + { 0x26481e10c6b48f28, 0x16 }, /* Use this row to check eccgenerate() */ + { 0xe45cad38cab2d144, 0x9c }, /* Use this row to check eccgenerate() */ + { 0x1bfafc53e195e543, 0x8e }, /* Use this row to check eccgenerate() */ + { 0x163bf46931784936, 0xdc }, /* Use this row to check eccgenerate() */ + { 0x75030e2f29040f40, 0x48 }, /* Use this row to check eccgenerate() */ + { 0x48d8802265454826, 0x2a }, /* Use this row to check eccgenerate() */ + { 0xabee7f7c6592400b, 0x2b }, /* Use this row to check eccgenerate() */ + { 0x15426d26f6e6bb13, 0x89 }, /* Use this row to check eccgenerate() */ + { 0x7c6e757a1c668c61, 0x6d }, /* Use this row to check eccgenerate() */ + { 0xe4c4b33f16179675, 0x74 }, /* Use this row to check eccgenerate() */ + { 0xc2881d35001b010a, 0xd4 }, /* Use this row to check eccgenerate() */ + { 0xce3bf7697de1e030, 0x65 }, /* Use this row to check eccgenerate() */ + { 0x8a40ff2fe88b7032, 0x19 }, /* Use this row to check eccgenerate() */ + { 0x849a4f7f2a9b1d76, 0x58 }, /* Use this row to check eccgenerate() */ + { 0xbc891e559b4faa20, 0x4c }, /* Use this row to check eccgenerate() */ + { 0x61043a491e6f774c, 0x28 }, /* Use this row to check eccgenerate() */ + { 0xe8214911e2d13c65, 0x9e }, /* Use this row to check eccgenerate() */ + { 0xc36722294561e701, 0x3d }, /* Use this row to check eccgenerate() */ + { 0x77d93038031c4665, 0x55 }, /* Use this row to check eccgenerate() */ + { 0x2c205525daa21613, 0x85 }, /* Use this row to check eccgenerate() */ + { 0x3fe85e39ecdc3e67, 0x20 }, /* Use this row to check eccgenerate() */ + { 0x526f7f7275f8d547, 0xa4 }, /* Use this row to check eccgenerate() */ + { 0x6bdf915bead6de35, 0xac }, /* Use this row to check eccgenerate() */ + { 0x063d6b1767b1ec18, 0x78 }, /* Use this row to check eccgenerate() */ + { 0x7dc8820ee74d0756, 0x31 }, /* Use this row to check eccgenerate() */ + { 0xe7680860ea011f57, 0x3f }, /* Use this row to check eccgenerate() */ + { 0x67e3ff073f51a043, 0xd6 }, /* Use this row to check eccgenerate() */ + { 0x27dd1076b6a4ff49, 0x10 }, /* Use this row to check eccgenerate() */ + { 0xe03f1d40f223ff37, 0xec }, /* Use this row to check eccgenerate() */ + { 0x8d73a958ab776075, 0x6f }, /* Use this row to check eccgenerate() */ + { 0xc9e6d7419cc93b15, 0x8f }, /* Use this row to check eccgenerate() */ + { 0x7f9b787aee77e321, 0xb7 }, /* Use this row to check eccgenerate() */ + { 0x34d9ca23b1082153, 0xa9 }, /* Use this row to check eccgenerate() */ + { 0xb424673842039b23, 0xe2 }, /* Use this row to check eccgenerate() */ + { 0x1ca6b136abb2fb5b, 0xe1 }, /* Use this row to check eccgenerate() */ + { 0x978f3a43e144bc5d, 0x64 }, /* Use this row to check eccgenerate() */ + { 0x563d92255b8e1070, 0x14 }, /* Use this row to check eccgenerate() */ + { 0x4565ef25e9feb935, 0x2d }, /* Use this row to check eccgenerate() */ + { 0x50b0a64ec11c2401, 0x3c }, /* Use this row to check eccgenerate() */ + { 0xa86a2b574ba25a3d, 0x8b }, /* Use this row to check eccgenerate() */ + { 0x36a47914cd78295d, 0xf1 }, /* Use this row to check eccgenerate() */ + { 0x0ccac9208fd33337, 0xe4 }, /* Use this row to check eccgenerate() */ + { 0x457833019d87791c, 0xc4 }, /* Use this row to check eccgenerate() */ + { 0x8fab785433a7da16, 0x0c }, /* Use this row to check eccgenerate() */ + { 0xdf1e3b0c26b85041, 0x94 }, /* Use this row to check eccgenerate() */ + { 0xc2818c561c1f222d, 0x9a }, /* Use this row to check eccgenerate() */ + { 0x0b97054fa805134e, 0xec }, /* Use this row to check eccgenerate() */ + { 0x5a0e3421411d0551, 0x57 }, /* Use this row to check eccgenerate() */ + { 0x8420a0743f70d072, 0xa8 }, /* Use this row to check eccgenerate() */ + { 0xea22cc4e0e339b59, 0x15 }, /* Use this row to check eccgenerate() */ + { 0xef775737a0c6512b, 0xe7 }, /* Use this row to check eccgenerate() */ + { 0xfc54621b81b20612, 0x9a }, /* Use this row to check eccgenerate() */ + { 0x6bb1c04745b5e95c, 0x1e }, /* Use this row to check eccgenerate() */ + { 0x06d20d5e41ba5141, 0x56 }, /* Use this row to check eccgenerate() */ + { 0x8d5cac7ebb616716, 0x43 }, /* Use this row to check eccgenerate() */ + { 0x89da9073ae3c3935, 0xb1 }, /* Use this row to check eccgenerate() */ + { 0x3e106d6cc3002613, 0xec }, /* Use this row to check eccgenerate() */ + { 0x60889f2f95a45a14, 0x69 }, /* Use this row to check eccgenerate() */ + { 0xc94b352b8388a06d, 0x53 }, /* Use this row to check eccgenerate() */ + { 0xa940f12ef0331804, 0x7a }, /* Use this row to check eccgenerate() */ + +}; + +int main(void) +{ + int i; + uint8_t ret_memcpy; + uint8_t ret_verify; + uint64_t dst; + uint64_t *buf; + struct ecc64 *ret_buf; + + /* + * Test that eccgenerate() still works, but skip the first 64 because they + * have intentional bitflips + */ + printf("Checking eccgenerate()\n"); + for (i = 64; i < NUM_ECC_ROWS; i++) { + if (eccgenerate(be64toh(ecc_data[i].data)) != ecc_data[i].ecc) { + ERR("ECC did not generate the correct value, expecting 0x%02x, got 0x%02x\n", + ecc_data[i].ecc, eccgenerate(be64toh(ecc_data[i].data))); + } + } + + /* Test that the ecc code can detect and recover bitflips */ + printf("Testing bitflip recovery\n"); + for (i = 0; i < 64; i++) { + ret_memcpy = memcpy_from_ecc(&dst, &ecc_data[i], sizeof(dst)); + if (dst != 0xffffffffffffffff || ret_memcpy) { + ERR("ECC code didn't correct bad bit %d in 0x%016lx\n", 63 - i, be64toh(ecc_data[i].data)); + exit(1); + } + + ret_verify = eccverify(be64toh(ecc_data[i].data), ecc_data[i].ecc); + if (ret_verify != 63 - i) { + ERR("ECC did not catch incorrect bit %d in row 0x%016lx 0x%02x, got 0x%02x\n", + i, ecc_data[i].data, ecc_data[i].ecc, ret_verify); + exit(1); + } + } + + buf = malloc(NUM_ECC_ROWS * sizeof(*buf)); + if (!buf) { + ERR("malloc #1 failed during ecc test\n"); + exit(1); + } + printf("pass\n"); + + /* Test a large memcpy */ + printf("Testing a large(ish) memcpy_from_ecc()\n"); + ret_memcpy = memcpy_from_ecc(buf, ecc_data, NUM_ECC_ROWS * sizeof(*buf)); + if (ret_memcpy) { + ERR("ECC Couldn't memcpy entire buffer\n"); + exit(1); + } + + for (i = 0; i < NUM_ECC_ROWS; i++) { + /* Large memcpy should have fixed the bitflips */ + if (i < 64 && buf[i] != 0xffffffffffffffff) { + ERR("memcpy_from_ecc got it wrong for uint64_t number %d, got 0x%016lx, expecting 0xffffffffffffffff\n", + i, buf[i]); + exit(1); + } + + /* But not changed any of the correct data */ + if (i > 63 && buf[i] != ecc_data[i].data) { + ERR("memcpy_from_ecc got it wrong for uint64_t number %d, git 0x%016lx, expecting 0x%016lx\n", + i, buf[i], ecc_data[i].data); + exit(1); + } + } + printf("pass\n"); + + /* Test a memcpy to add ecc data */ + printf("Testing a large(ish) memcpy_to_ecc()\n"); + ret_buf = malloc(ecc_buffer_size(NUM_ECC_ROWS * sizeof(*buf))); + if (!buf) { + ERR("malloc #2 failed during ecc test\n"); + exit(1); + } + + ret_memcpy = memcpy_to_ecc(ret_buf, buf, NUM_ECC_ROWS * sizeof(*buf)); + if (ret_memcpy) { + ERR("ECC Couldn't memcpy entire buffer\n"); + exit(1); + } + + for (i = 0; i < NUM_ECC_ROWS; i++) { + /* The data should be the same */ + if (ret_buf[i].data != buf[i]) { + ERR("memcpy_to_ecc got it wrong on uint64_t %d, expecting 0x%016lx, got 0x%016lx\n", + i, buf[i], ret_buf[i].data); + exit(1); + } + + /* Check the correctness of ecc bytes */ + if (ret_buf[i].ecc != ecc_data[i].ecc) { + ERR("memcpy_to_ecc got it on the ecc for uint64_t %d, expecting 0x%02x, got 0x%02x\n", + i, ecc_data[i].ecc, ret_buf[i].ecc); + exit(1); + } + } + printf("ECC tests pass\n"); + + printf("ECC test error conditions\n"); + if (memcpy_to_ecc(ret_buf, buf, 7) == 0) { + ERR("memcpy_to_ecc didn't detect bad size 7\n"); + exit(1); + } + + if (memcpy_to_ecc(ret_buf, buf, 15) == 0) { + ERR("memcpy_to_ecc didn't detect bad size 15\n"); + exit(1); + } + if (memcpy_from_ecc(buf, ret_buf, 7) == 0) { + ERR("memcpy_from_ecc didn't detect bad size 7\n"); + exit(1); + } + if (memcpy_from_ecc(buf, ret_buf, 15) == 0) { + ERR("memcpy_from_ecc didn't detect bad size 15\n"); + exit(1); + } + printf("ECC error conditions pass\n"); + + free(buf); + free(ret_buf); + + /* Check that unaligned address become aligned */ + if (ecc_buffer_align(0, 5) != 0) { + ERR("ecc_buffer_align(0, 5) not 0 -> %ld\n", ecc_buffer_align(0, 5)); + exit(1); + } + + if (ecc_buffer_align(0, 8) != 0) { + ERR("ecc_buffer_align(0, 8) not 0 -> %ld\n", ecc_buffer_align(0, 8)); + exit(1); + } + if (ecc_buffer_align(0, 9) != 9) { + ERR("ecc_buffer_align(0, 9) not 9 -> %ld\n", ecc_buffer_align(0, 9)); + exit(1); + } + if (ecc_buffer_align(0, 15) != 9) { + ERR("ecc_buffer_align(0, 15) not 9 -> %ld\n", ecc_buffer_align(0, 15)); + exit(1); + } + if (ecc_buffer_align(5, 10) != 5) { + ERR("ecc_buffer_align(5, 10) not 5 -> %ld\n", ecc_buffer_align(5, 10)); + exit(1); + } + if (ecc_buffer_align(5, 18) != 14) { + ERR("ecc_buffer_align(5, 18) not 14 -> %ld\n", ecc_buffer_align(5, 18)); + exit(1); + } + if (ecc_buffer_align(0, 50) != 45) { + ERR("ecc_buffer_align(0, 50) not 45 -> %ld\n", ecc_buffer_align(0, 50)); + exit(1); + } + return 0; +} diff --git a/roms/skiboot/libflash/test/test-flash.c b/roms/skiboot/libflash/test/test-flash.c new file mode 100644 index 000000000..3304195f8 --- /dev/null +++ b/roms/skiboot/libflash/test/test-flash.c @@ -0,0 +1,448 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2013-2017 IBM Corp. */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +#include <libflash/libflash.h> +#include <libflash/libflash-priv.h> + +#include "../libflash.c" +#include "../ecc.c" + +#define __unused __attribute__((unused)) + +#define ERR(fmt...) fprintf(stderr, fmt) + +/* Flash commands */ +#define CMD_PP 0x02 +#define CMD_READ 0x03 +#define CMD_WRDI 0x04 +#define CMD_RDSR 0x05 +#define CMD_WREN 0x06 +#define CMD_SE 0x20 +#define CMD_RDSCUR 0x2b +#define CMD_BE32K 0x52 +#define CMD_CE 0x60 +#define CMD_RDID 0x9f +#define CMD_EN4B 0xb7 +#define CMD_BE 0xd8 +#define CMD_RDDPB 0xe0 +#define CMD_RDSPB 0xe2 +#define CMD_EX4B 0xe9 + +/* Flash status bits */ +#define STAT_WIP 0x01 +#define STAT_WEN 0x02 + +static uint8_t *sim_image; +static uint32_t sim_image_sz = 0x100000; +static uint32_t sim_index; +static uint32_t sim_addr; +static uint32_t sim_er_size; +static uint8_t sim_sr; +static bool sim_fl_4b; +static bool sim_ct_4b; + +static enum sim_state { + sim_state_idle, + sim_state_rdid, + sim_state_rdsr, + sim_state_read_addr, + sim_state_read_data, + sim_state_write_addr, + sim_state_write_data, + sim_state_erase_addr, + sim_state_erase_done, +} sim_state; + +/* + * Simulated flash & controller + */ +static int sim_start_cmd(uint8_t cmd) +{ + if (sim_state != sim_state_idle) { + ERR("SIM: Command %02x in wrong state %d\n", cmd, sim_state); + return -1; + } + + sim_index = 0; + sim_addr = 0; + + switch(cmd) { + case CMD_RDID: + sim_state = sim_state_rdid; + break; + case CMD_RDSR: + sim_state = sim_state_rdsr; + break; + case CMD_EX4B: + sim_fl_4b = false; + break; + case CMD_EN4B: + sim_fl_4b = true; + break; + case CMD_WREN: + sim_sr |= STAT_WEN; + break; + case CMD_READ: + sim_state = sim_state_read_addr; + if (sim_ct_4b != sim_fl_4b) + ERR("SIM: 4b mode mismatch in READ !\n"); + break; + case CMD_PP: + sim_state = sim_state_write_addr; + if (sim_ct_4b != sim_fl_4b) + ERR("SIM: 4b mode mismatch in PP !\n"); + if (!(sim_sr & STAT_WEN)) + ERR("SIM: PP without WEN, ignoring... \n"); + break; + case CMD_SE: + case CMD_BE32K: + case CMD_BE: + if (sim_ct_4b != sim_fl_4b) + ERR("SIM: 4b mode mismatch in SE/BE !\n"); + if (!(sim_sr & STAT_WEN)) + ERR("SIM: SE/BE without WEN, ignoring... \n"); + sim_state = sim_state_erase_addr; + switch(cmd) { + case CMD_SE: sim_er_size = 0x1000; break; + case CMD_BE32K: sim_er_size = 0x8000; break; + case CMD_BE: sim_er_size = 0x10000; break; + } + break; + case CMD_CE: + if (!(sim_sr & STAT_WEN)) { + ERR("SIM: CE without WEN, ignoring... \n"); + break; + } + memset(sim_image, 0xff, sim_image_sz); + sim_sr |= STAT_WIP; + sim_sr &= ~STAT_WEN; + break; + default: + ERR("SIM: Unsupported command %02x\n", cmd); + return -1; + } + return 0; +} + +static void sim_end_cmd(void) +{ + /* For write and sector/block erase, set WIP & clear WEN here */ + if (sim_state == sim_state_write_data) { + sim_sr |= STAT_WIP; + sim_sr &= ~STAT_WEN; + } + sim_state = sim_state_idle; +} + +static bool sim_do_address(const uint8_t **buf, uint32_t *len) +{ + uint8_t asize = sim_fl_4b ? 4 : 3; + const uint8_t *p = *buf; + + while(*len) { + sim_addr = (sim_addr << 8) | *(p++); + *buf = p; + *len = *len - 1; + sim_index++; + if (sim_index >= asize) + return true; + } + return false; +} + +static int sim_wbytes(const void *buf, uint32_t len) +{ + const uint8_t *b = buf; + bool addr_complete; + + again: + switch(sim_state) { + case sim_state_read_addr: + addr_complete = sim_do_address(&b, &len); + if (addr_complete) { + sim_state = sim_state_read_data; + sim_index = 0; + if (len) + goto again; + } + break; + case sim_state_write_addr: + addr_complete = sim_do_address(&b, &len); + if (addr_complete) { + sim_state = sim_state_write_data; + sim_index = 0; + if (len) + goto again; + } + break; + case sim_state_write_data: + if (!(sim_sr & STAT_WEN)) + break; + while(len--) { + uint8_t c = *(b++); + if (sim_addr >= sim_image_sz) { + ERR("SIM: Write past end of flash\n"); + return -1; + } + /* Flash write only clears bits */ + sim_image[sim_addr] &= c; + sim_addr = (sim_addr & 0xffffff00) | + ((sim_addr + 1) & 0xff); + } + break; + case sim_state_erase_addr: + if (!(sim_sr & STAT_WEN)) + break; + addr_complete = sim_do_address(&b, &len); + if (addr_complete) { + memset(sim_image + sim_addr, 0xff, sim_er_size); + sim_sr |= STAT_WIP; + sim_sr &= ~STAT_WEN; + sim_state = sim_state_erase_done; + } + break; + default: + ERR("SIM: Write in wrong state %d\n", sim_state); + return -1; + } + return 0; +} + +static int sim_rbytes(void *buf, uint32_t len) +{ + uint8_t *b = buf; + + switch(sim_state) { + case sim_state_rdid: + while(len--) { + switch(sim_index) { + case 0: + *(b++) = 0x55; + break; + case 1: + *(b++) = 0xaa; + break; + case 2: + *(b++) = 0x55; + break; + default: + ERR("SIM: RDID index %d\n", sim_index); + *(b++) = 0; + break; + } + sim_index++; + } + break; + case sim_state_rdsr: + while(len--) { + *(b++) = sim_sr; + if (sim_index > 0) + ERR("SIM: RDSR index %d\n", sim_index); + sim_index++; + + /* If WIP was 1, clear it, ie, simulate write/erase + * completion + */ + sim_sr &= ~STAT_WIP; + } + break; + case sim_state_read_data: + while(len--) { + if (sim_addr >= sim_image_sz) { + ERR("SIM: Read past end of flash\n"); + return -1; + } + *(b++) = sim_image[sim_addr++]; + } + break; + default: + ERR("SIM: Read in wrong state %d\n", sim_state); + return -1; + } + return 0; +} + +static int sim_send_addr(uint32_t addr) +{ + const void *ap; + + /* Layout address MSB first in memory */ + addr = cpu_to_be32(addr); + + /* Send the right amount of bytes */ + ap = (char *)&addr; + + if (sim_ct_4b) + return sim_wbytes(ap, 4); + else + return sim_wbytes(ap + 1, 3); +} + +static int sim_cmd_rd(struct spi_flash_ctrl *ctrl __unused, uint8_t cmd, + bool has_addr, uint32_t addr, void *buffer, + uint32_t size) +{ + int rc; + + rc = sim_start_cmd(cmd); + if (rc) + goto bail; + if (has_addr) { + rc = sim_send_addr(addr); + if (rc) + goto bail; + } + if (buffer && size) + rc = sim_rbytes(buffer, size); + bail: + sim_end_cmd(); + return rc; +} + +static int sim_cmd_wr(struct spi_flash_ctrl *ctrl __unused, uint8_t cmd, + bool has_addr, uint32_t addr, const void *buffer, + uint32_t size) +{ + int rc; + + rc = sim_start_cmd(cmd); + if (rc) + goto bail; + if (has_addr) { + rc = sim_send_addr(addr); + if (rc) + goto bail; + } + if (buffer && size) + rc = sim_wbytes(buffer, size); + bail: + sim_end_cmd(); + return rc; +} + +static int sim_set_4b(struct spi_flash_ctrl *ctrl __unused, bool enable) +{ + sim_ct_4b = enable; + + return 0; +} + +static int sim_read(struct spi_flash_ctrl *ctrl __unused, uint32_t pos, + void *buf, uint32_t len) +{ + if (sim_ct_4b != sim_fl_4b) + ERR("SIM: 4b mode mismatch in autoread !\n"); + if ((pos + len) < pos) + return -1; + if ((pos + len) > sim_image_sz) + return -1; + memcpy(buf, sim_image + pos, len); + return 0; +}; + +struct spi_flash_ctrl sim_ctrl = { + .cmd_wr = sim_cmd_wr, + .cmd_rd = sim_cmd_rd, + .set_4b = sim_set_4b, + .read = sim_read, +}; + +int main(void) +{ + struct blocklevel_device *bl; + uint64_t total_size; + uint32_t erase_granule; + const char *name; + uint16_t *test; + struct ecc64 *ecc_test; + uint64_t *test64; + int i, rc; + + sim_image = malloc(sim_image_sz); + memset(sim_image, 0xff, sim_image_sz); + test = malloc(0x10000 * 2); + + rc = flash_init(&sim_ctrl, &bl, NULL); + if (rc) { + ERR("flash_init failed with err %d\n", rc); + exit(1); + } + rc = flash_get_info(bl, &name, &total_size, &erase_granule); + if (rc) { + ERR("flash_get_info failed with err %d\n", rc); + exit(1); + } + + /* Make up a test pattern */ + for (i=0; i<0x10000;i++) + test[i] = cpu_to_be16(i); + + /* Write 64k of stuff at 0 and at 128k */ + printf("Writing test patterns...\n"); + flash_smart_write(bl, 0, test, 0x10000); + flash_smart_write(bl, 0x20000, test, 0x10000); + + /* Write "Hello world" straddling the 64k boundary */ +#define HW "Hello World" + printf("Writing test string...\n"); + flash_smart_write(bl, 0xfffc, HW, sizeof(HW)); + + /* Check result */ + if (memcmp(sim_image + 0xfffc, HW, sizeof(HW))) { + ERR("Test string mismatch !\n"); + exit(1); + } + printf("Test string pass\n"); + if (memcmp(sim_image, test, 0xfffc)) { + ERR("Test pattern mismatch !\n"); + exit(1); + } + printf("Test pattern pass\n"); + + printf("Test ECC interfaces\n"); + flash_smart_write_corrected(bl, 0, test, 0x10000, 1); + ecc_test = (struct ecc64 *)sim_image; + test64 = (uint64_t *)test; + for (i = 0; i < 0x10000 / sizeof(*ecc_test); i++) { + if (test64[i] != ecc_test[i].data) { + ERR("flash_smart_write_corrected() pattern missmatch at %d: 0x%016lx vs 0x%016lx\n", + i, test64[i], ecc_test[i].data); + exit(1); + } + if (ecc_test[i].ecc != eccgenerate(be64toh(test64[i]))) { + ERR("ECCs don't match 0x%02x vs 0x%02x\n", ecc_test[i].ecc, eccgenerate(test64[i])); + exit(1); + } + } + printf("Test ECC interface pass\n"); + + printf("Test ECC erase\n"); + if (flash_erase(bl, 0, 0x10000) != 0) { + ERR("flash_erase didn't return 0\n"); + exit(1); + } + + for (i = 0; i < 0x10000 / sizeof(*ecc_test); i++) { + uint8_t zero = 0; + if (ecc_test[i].data != 0xFFFFFFFFFFFFFFFF) { + ERR("Data not properly cleared at %d\n", i); + exit(1); + } + rc = flash_write(bl, i * sizeof(*ecc_test) + 8, &zero, 1, 0); + if (rc || ecc_test[i].ecc != 0) { + ERR("Cleared data not correctly ECCed: 0x%02x (0x%016lx) expecting 0 at %d\n", ecc_test[i].ecc, ecc_test[i].data, i); + exit(1); + } + } + printf("Test ECC erase pass\n"); + + flash_exit(bl); + free(test); + + return 0; +} diff --git a/roms/skiboot/libflash/test/test-ipmi-hiomap.c b/roms/skiboot/libflash/test/test-ipmi-hiomap.c new file mode 100644 index 000000000..6117e9dd4 --- /dev/null +++ b/roms/skiboot/libflash/test/test-ipmi-hiomap.c @@ -0,0 +1,3388 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2018-2019 IBM Corp. */ + +#include <assert.h> +#include <ccan/container_of/container_of.h> +#include <libflash/blocklevel.h> +#include <lock.h> +#include <lpc.h> +#include <hiomap.h> +#include <ipmi.h> +#include <opal-api.h> +#include <platform.h> +#include <stdio.h> +#include <stdlib.h> + +#include "../ipmi-hiomap.h" +#include "../errors.h" + +/* Stub for blocklevel debug macros */ +bool libflash_debug; + +const struct bmc_sw_config bmc_sw_hiomap = { + .ipmi_oem_hiomap_cmd = IPMI_CODE(0x3a, 0x5a), +}; + +const struct bmc_platform _bmc_platform = { + .name = "generic:hiomap", + .sw = &bmc_sw_hiomap, +}; + +enum scenario_event_type { + scenario_sentinel = 0, + scenario_event_p, + scenario_cmd, + scenario_sel, + scenario_delay, +}; + +struct scenario_cmd_data { + uint8_t cmd; + uint8_t seq; + uint8_t args[13]; +} __attribute__((packed)); + +struct scenario_cmd { + struct scenario_cmd_data req; + struct scenario_cmd_data resp; + uint8_t cc; + size_t resp_size; +}; + +struct scenario_sel { + uint8_t bmc_state; +}; + +struct scenario_event { + enum scenario_event_type type; + union { + const struct scenario_event *p; + struct scenario_cmd c; + struct scenario_sel s; + }; +}; + +#define SCENARIO_SENTINEL { .type = scenario_sentinel } + +struct ipmi_sel { + void (*fn)(uint8_t data, void *context); + void *context; +}; + +struct ipmi_msg_ctx { + const struct scenario_event *scenario; + const struct scenario_event *cursor; + + struct ipmi_sel sel; + + struct ipmi_msg msg; +}; + +struct ipmi_msg_ctx ipmi_msg_ctx; + +const struct bmc_platform *bmc_platform = &_bmc_platform; + +static void scenario_enter(const struct scenario_event *scenario) +{ + ipmi_msg_ctx.scenario = scenario; + ipmi_msg_ctx.cursor = scenario; +} + +static void scenario_advance(void) +{ + struct ipmi_msg_ctx *ctx = &ipmi_msg_ctx; + + assert(ctx->cursor->type == scenario_delay); + ctx->cursor++; + + /* Deliver all the undelayed, scheduled SELs */ + while (ctx->cursor->type == scenario_sel) { + ctx->sel.fn(ctx->cursor->s.bmc_state, ctx->sel.context); + ctx->cursor++; + } +} + +static void scenario_exit(void) +{ + if (ipmi_msg_ctx.cursor->type != scenario_sentinel) { + ptrdiff_t d = ipmi_msg_ctx.cursor - ipmi_msg_ctx.scenario; + printf("%s: Exiting on event %tu with event type %d \n", + __func__, d, ipmi_msg_ctx.cursor->type); + assert(false); + } +} + +void ipmi_init_msg(struct ipmi_msg *msg, int interface __attribute__((unused)), + uint32_t code, void (*complete)(struct ipmi_msg *), + void *user_data, size_t req_size, size_t resp_size) +{ + msg->backend = NULL; + msg->cmd = IPMI_CMD(code); + msg->netfn = IPMI_NETFN(code) << 2; + msg->req_size = req_size; + msg->resp_size = resp_size; + msg->complete = complete; + msg->user_data = user_data; +} + +struct ipmi_msg *ipmi_mkmsg(int interface __attribute__((unused)), + uint32_t code, void (*complete)(struct ipmi_msg *), + void *user_data, void *req_data, size_t req_size, + size_t resp_size) +{ + struct ipmi_msg *msg = &ipmi_msg_ctx.msg; + + ipmi_init_msg(msg, 0 /* some bogus value */, code, complete, user_data, + req_size, resp_size); + + msg->data = malloc(req_size > resp_size ? req_size : resp_size); + if (req_data) + memcpy(msg->data, req_data, req_size); + + return msg; +} + +void ipmi_free_msg(struct ipmi_msg *msg __attribute__((unused))) +{ + if (msg) + free(msg->data); +} + +void ipmi_queue_msg_sync(struct ipmi_msg *msg) +{ + struct ipmi_msg_ctx *ctx = container_of(msg, struct ipmi_msg_ctx, msg); + const struct scenario_cmd *cmd; + + if (ctx->cursor->type == scenario_cmd) { + cmd = &ctx->cursor->c; + } else if (ctx->cursor->type == scenario_event_p) { + assert(ctx->cursor->p->type == scenario_cmd); + cmd = &ctx->cursor->p->c; + } else { + printf("Got unexpected request:\n"); + for (ssize_t i = 0; i < msg->req_size; i++) + printf("msg->data[%zd]: 0x%02x\n", i, msg->data[i]); + assert(false); + } + + assert((msg->netfn >> 2) == 0x3a); + assert(msg->cmd == 0x5a); + assert(msg->req_size >= 2); + + if (memcmp(msg->data, &cmd->req, msg->req_size)) { + printf("Comparing received vs expected message\n"); + for (ssize_t i = 0; i < msg->req_size; i++) { + printf("msg->data[%zd]: 0x%02x, cmd->req[%zd]: 0x%02x\n", + i, msg->data[i], i, ((uint8_t *)(&cmd->req))[i]); + } + assert(false); + } + + msg->cc = cmd->cc; + memcpy(msg->data, &cmd->resp, msg->resp_size); + + if (cmd->resp_size) + msg->resp_size = cmd->resp_size; + + msg->complete(msg); + + ctx->cursor++; + + /* Deliver all the scheduled SELs */ + while (ctx->cursor->type == scenario_sel) { + ctx->sel.fn(ctx->cursor->s.bmc_state, ctx->sel.context); + ctx->cursor++; + } +} + +int ipmi_sel_register(uint8_t oem_cmd __attribute__((unused)), + void (*fn)(uint8_t data, void *context), + void *context) +{ + ipmi_msg_ctx.sel.fn = fn; + ipmi_msg_ctx.sel.context = context; + + return 0; +} + +int64_t lpc_write(enum OpalLPCAddressType addr_type __attribute__((unused)), + uint32_t addr __attribute__((unused)), + uint32_t data __attribute__((unused)), + uint32_t sz) +{ + assert(sz != 0); + return 0; +} + +int64_t lpc_read(enum OpalLPCAddressType addr_type __attribute__((unused)), + uint32_t addr __attribute__((unused)), uint32_t *data, + uint32_t sz) +{ + memset(data, 0xaa, sz); + + return 0; +} + +static bool lpc_read_success(const uint8_t *buf, size_t len) +{ + if (len < 64) { + while (len--) + if (*buf++ != 0xaa) + return false; + return true; + } + + for (int i = 0; i < 64; i++) + if (buf[i] != 0xaa) + return false; + + return !memcmp(buf, buf + 64, len - 64); +} + +/* Commonly used messages */ + +static const struct scenario_event hiomap_ack_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 1, + .args = { + [0] = HIOMAP_E_ACK_MASK, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_ACK, + .seq = 1, + }, + }, +}; + +static const struct scenario_event hiomap_get_info_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 2, + .args = { + [0] = HIOMAP_V2, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 2, + .args = { + [0] = HIOMAP_V2, + [1] = 12, + [2] = 8, [3] = 0, + }, + }, + }, +}; + +static const struct scenario_event hiomap_get_flash_info_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 3, + .args = { + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 3, + .args = { + [0] = 0x00, [1] = 0x20, + [2] = 0x01, [3] = 0x00, + }, + }, + }, +}; + +static const struct scenario_event +hiomap_create_read_window_qs0l1_rs0l1_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + .args = { + [0] = 0xff, [1] = 0x0f, + [2] = 0x01, [3] = 0x00, + [4] = 0x00, [5] = 0x00, + }, + }, + }, +}; + +static const struct scenario_event +hiomap_create_read_window_qs0l2_rs0l1_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x02, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + .args = { + [0] = 0xff, [1] = 0x0f, + [2] = 0x01, [3] = 0x00, + [4] = 0x00, [5] = 0x00, + }, + }, + }, +}; + +static const struct scenario_event +hiomap_create_write_window_qs0l1_rs0l1_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 4, + .args = { + [0] = 0xff, [1] = 0x0f, + [2] = 0x01, [3] = 0x00, + [4] = 0x00, [5] = 0x00, + }, + }, + }, +}; + +static const struct scenario_event hiomap_mark_dirty_qs0l1_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_MARK_DIRTY, + .seq = 5, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_MARK_DIRTY, + .seq = 5, + }, + }, +}; + +static const struct scenario_event +hiomap_create_write_window_qs0l2_rs0l1_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x02, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 4, + .args = { + [0] = 0xff, [1] = 0x0f, + [2] = 0x01, [3] = 0x00, + [4] = 0x00, [5] = 0x00, + }, + }, + }, +}; + +static const struct scenario_event hiomap_flush_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_FLUSH, + .seq = 6, + }, + .resp = { + .cmd = HIOMAP_C_FLUSH, + .seq = 6, + }, + }, +}; + +static const struct scenario_event +hiomap_create_write_window_qs1l1_rs1l1_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 7, + .args = { + [0] = 0x01, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 7, + .args = { + [0] = 0xfe, [1] = 0x0f, + [2] = 0x01, [3] = 0x00, + [4] = 0x01, [5] = 0x00, + }, + }, + }, +}; + +static const struct scenario_event hiomap_erase_qs0l1_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ERASE, + .seq = 5, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp = { + .cmd = HIOMAP_C_ERASE, + .seq = 5, + }, + }, +}; + +static const struct scenario_event hiomap_reset_call_seq_4 = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_RESET, + .seq = 4, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_RESET, + .seq = 4, + }, + }, +}; + +static const struct scenario_event hiomap_reset_call_seq_5 = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_RESET, + .seq = 5, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_RESET, + .seq = 5, + }, + }, +}; + +static const struct scenario_event hiomap_reset_call_seq_6 = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_RESET, + .seq = 6, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_RESET, + .seq = 6, + }, + }, +}; + +static const struct scenario_event hiomap_reset_call_seq_7 = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_RESET, + .seq = 7, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_RESET, + .seq = 7, + }, + }, +}; + +static const struct scenario_event hiomap_reset_call_seq_9 = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_RESET, + .seq = 9, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_RESET, + .seq = 9, + }, + }, +}; + +static const struct scenario_event hiomap_reset_call_seq_a = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_RESET, + .seq = 0xa, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_RESET, + .seq = 0xa, + }, + }, +}; + +static const struct scenario_event scenario_hiomap_init[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_4, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_init(void) +{ + struct blocklevel_device *bl; + + scenario_enter(scenario_hiomap_init); + assert(!ipmi_hiomap_init(&bl)); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event scenario_hiomap_event_daemon_ready[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_4, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_event_daemon_ready(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + + scenario_enter(scenario_hiomap_event_daemon_ready); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + assert(ctx->bmc_state == HIOMAP_E_DAEMON_READY); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event scenario_hiomap_event_daemon_stopped[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } }, + { .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_PROTOCOL_RESET } }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_4, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_event_daemon_stopped(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + + scenario_enter(scenario_hiomap_event_daemon_stopped); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + assert(ctx->bmc_state == HIOMAP_E_PROTOCOL_RESET); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event scenario_hiomap_event_daemon_restarted[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } }, + { .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_PROTOCOL_RESET } }, + { .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_4, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_event_daemon_restarted(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + + scenario_enter(scenario_hiomap_event_daemon_restarted); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + assert(ctx->bmc_state == (HIOMAP_E_DAEMON_READY | HIOMAP_E_PROTOCOL_RESET)); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_event_daemon_lost_flash_control[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } }, + { + .type = scenario_sel, + .s = { + .bmc_state = (HIOMAP_E_DAEMON_READY + | HIOMAP_E_FLASH_LOST), + } + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_event_daemon_lost_flash_control(void) +{ + struct blocklevel_device *bl; + size_t len = 2 * (1 << 12); + void *buf; + + buf = malloc(len); + assert(buf); + + scenario_enter(scenario_hiomap_event_daemon_lost_flash_control); + assert(!ipmi_hiomap_init(&bl)); + assert(bl->read(bl, 0, buf, len) == FLASH_ERR_AGAIN); + ipmi_hiomap_exit(bl); + scenario_exit(); + + free(buf); +} + +static const struct scenario_event +scenario_hiomap_event_daemon_regained_flash_control_dirty[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x02, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + .args = { + [0] = 0xfe, [1] = 0x0f, + [2] = 0x02, [3] = 0x00, + [4] = 0x00, [5] = 0x00, + }, + }, + }, + }, + { + .type = scenario_delay + }, + { + .type = scenario_sel, + .s = { + .bmc_state = (HIOMAP_E_DAEMON_READY + | HIOMAP_E_FLASH_LOST), + } + }, + { + .type = scenario_sel, + .s = { + .bmc_state = (HIOMAP_E_DAEMON_READY + | HIOMAP_E_WINDOW_RESET), + } + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 5, + .args = { [0] = HIOMAP_E_WINDOW_RESET }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_ACK, + .seq = 5, + } + } + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 6, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x02, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 6, + .args = { + [0] = 0xfe, [1] = 0x0f, + [2] = 0x02, [3] = 0x00, + [4] = 0x00, [5] = 0x00, + }, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_event_daemon_regained_flash_control_dirty(void) +{ + struct blocklevel_device *bl; + size_t len = 2 * (1 << 12); + void *buf; + + buf = malloc(len); + assert(buf); + + scenario_enter(scenario_hiomap_event_daemon_regained_flash_control_dirty); + assert(!ipmi_hiomap_init(&bl)); + assert(!bl->read(bl, 0, buf, len)); + scenario_advance(); + assert(!bl->read(bl, 0, buf, len)); + ipmi_hiomap_exit(bl); + scenario_exit(); + + free(buf); +} + +static const struct scenario_event scenario_hiomap_protocol_reset_recovery[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_DAEMON_READY } }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x02, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + .args = { + [0] = 0xfe, [1] = 0x0f, + [2] = 0x02, [3] = 0x00, + [4] = 0x00, [5] = 0x00, + }, + }, + }, + }, + { + .type = scenario_delay + }, + { + .type = scenario_sel, + .s = { .bmc_state = HIOMAP_E_PROTOCOL_RESET, } + }, + { + .type = scenario_sel, + .s = { .bmc_state = HIOMAP_E_DAEMON_READY, } + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 5, + .args = { [0] = HIOMAP_E_PROTOCOL_RESET }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_ACK, + .seq = 5, + } + } + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 6, + .args = { + [0] = HIOMAP_V2, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 6, + .args = { + [0] = HIOMAP_V2, + [1] = 12, + [2] = 8, [3] = 0, + }, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 7, + .args = { + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 7, + .args = { + [0] = 0x00, [1] = 0x20, + [2] = 0x01, [3] = 0x00, + }, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 8, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x02, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 8, + .args = { + [0] = 0xfe, [1] = 0x0f, + [2] = 0x02, [3] = 0x00, + [4] = 0x00, [5] = 0x00, + }, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_9, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_reset_recovery(void) +{ + struct blocklevel_device *bl; + size_t len = 2 * (1 << 12); + void *buf; + + buf = malloc(len); + assert(buf); + + scenario_enter(scenario_hiomap_protocol_reset_recovery); + assert(!ipmi_hiomap_init(&bl)); + assert(!bl->read(bl, 0, buf, len)); + scenario_advance(); + assert(!bl->read(bl, 0, buf, len)); + ipmi_hiomap_exit(bl); + scenario_exit(); + + free(buf); +} + +static const struct scenario_event +scenario_hiomap_protocol_read_one_block[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_read_window_qs0l1_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_read_one_block(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + uint8_t *buf; + size_t len; + + scenario_enter(scenario_hiomap_protocol_read_one_block); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(!bl->read(bl, 0, buf, len)); + assert(lpc_read_success(buf, len)); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static void test_hiomap_protocol_read_one_byte(void) +{ + struct blocklevel_device *bl; + uint8_t *buf; + size_t len; + + scenario_enter(scenario_hiomap_protocol_read_one_block); + assert(!ipmi_hiomap_init(&bl)); + len = 1; + buf = calloc(1, len); + assert(buf); + assert(!bl->read(bl, 0, buf, len)); + assert(lpc_read_success(buf, len)); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_read_two_blocks[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_read_window_qs0l2_rs0l1_call, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 5, + .args = { + [0] = 0x01, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 5, + .args = { + [0] = 0xfe, [1] = 0x0f, + [2] = 0x01, [3] = 0x00, + [4] = 0x01, [5] = 0x00, + }, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_read_two_blocks(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + uint8_t *buf; + size_t len; + + scenario_enter(scenario_hiomap_protocol_read_two_blocks); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 2 * (1 << ctx->block_size_shift); + buf = calloc(1, len); + assert(buf); + assert(!bl->read(bl, 0, buf, len)); + assert(lpc_read_success(buf, len)); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static void test_hiomap_protocol_read_1block_1byte(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + uint8_t *buf; + size_t len; + + scenario_enter(scenario_hiomap_protocol_read_two_blocks); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = (1 << ctx->block_size_shift) + 1; + buf = calloc(1, len); + assert(buf); + assert(!bl->read(bl, 0, buf, len)); + assert(lpc_read_success(buf, len)); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_read_one_block_twice[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_read_window_qs0l1_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_read_one_block_twice(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + uint8_t *buf; + size_t len; + + scenario_enter(scenario_hiomap_protocol_read_one_block_twice); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(!bl->read(bl, 0, buf, len)); + assert(!bl->read(bl, 0, buf, len)); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_event_before_action[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_sel, + .s = { + .bmc_state = HIOMAP_E_DAEMON_READY | + HIOMAP_E_FLASH_LOST, + } + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_event_before_read(void) +{ + struct blocklevel_device *bl; + char buf; + int rc; + + scenario_enter(scenario_hiomap_protocol_event_before_action); + assert(!ipmi_hiomap_init(&bl)); + rc = bl->read(bl, 0, &buf, sizeof(buf)); + assert(rc == FLASH_ERR_AGAIN); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_event_during_read[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_read_window_qs0l1_rs0l1_call, + }, + { + .type = scenario_sel, + .s = { + .bmc_state = HIOMAP_E_DAEMON_READY | + HIOMAP_E_FLASH_LOST, + } + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_event_during_read(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + uint8_t *buf; + size_t len; + int rc; + + scenario_enter(scenario_hiomap_protocol_event_during_read); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + rc = bl->read(bl, 0, buf, len); + assert(rc == FLASH_ERR_AGAIN); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_write_one_block[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, }, + { .type = scenario_event_p, .p = &hiomap_flush_call, }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_write_one_block(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + uint8_t *buf; + size_t len; + + scenario_enter(scenario_hiomap_protocol_write_one_block); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(!bl->write(bl, 0, buf, len)); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static void test_hiomap_protocol_write_one_byte(void) +{ + struct blocklevel_device *bl; + uint8_t *buf; + size_t len; + + scenario_enter(scenario_hiomap_protocol_write_one_block); + assert(!ipmi_hiomap_init(&bl)); + len = 1; + buf = calloc(1, len); + assert(buf); + assert(!bl->write(bl, 0, buf, len)); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_write_two_blocks[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l2_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, }, + { .type = scenario_event_p, .p = &hiomap_flush_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs1l1_rs1l1_call, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_MARK_DIRTY, + .seq = 8, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp = { + .cmd = HIOMAP_C_MARK_DIRTY, + .seq = 8, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_FLUSH, + .seq = 9, + }, + .resp = { + .cmd = HIOMAP_C_FLUSH, + .seq = 9, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_a, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_write_two_blocks(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + uint8_t *buf; + size_t len; + + scenario_enter(scenario_hiomap_protocol_write_two_blocks); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 2 * (1 << ctx->block_size_shift); + buf = calloc(1, len); + assert(buf); + assert(!bl->write(bl, 0, buf, len)); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static void test_hiomap_protocol_write_1block_1byte(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + uint8_t *buf; + size_t len; + + scenario_enter(scenario_hiomap_protocol_write_two_blocks); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = (1 << ctx->block_size_shift) + 1; + buf = calloc(1, len); + assert(buf); + assert(!bl->write(bl, 0, buf, len)); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_write_one_block_twice[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, }, + { .type = scenario_event_p, .p = &hiomap_flush_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_MARK_DIRTY, + .seq = 7, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp = { + .cmd = HIOMAP_C_MARK_DIRTY, + .seq = 7, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_FLUSH, + .seq = 8, + }, + .resp = { + .cmd = HIOMAP_C_FLUSH, + .seq = 8, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_9, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_write_one_block_twice(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + uint8_t *buf; + size_t len; + + scenario_enter(scenario_hiomap_protocol_write_one_block_twice); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(!bl->write(bl, 0, buf, len)); + assert(!bl->write(bl, 0, buf, len)); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static void test_hiomap_protocol_event_before_write(void) +{ + struct blocklevel_device *bl; + char buf; + int rc; + + scenario_enter(scenario_hiomap_protocol_event_before_action); + assert(!ipmi_hiomap_init(&bl)); + rc = bl->write(bl, 0, &buf, sizeof(buf)); + assert(rc == FLASH_ERR_AGAIN); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_event_during_write[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { + .type = scenario_sel, + .s = { + .bmc_state = HIOMAP_E_DAEMON_READY | + HIOMAP_E_FLASH_LOST, + } + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_event_during_write(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + char *buf; + int rc; + + scenario_enter(scenario_hiomap_protocol_event_during_write); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + rc = bl->write(bl, 0, buf, len); + free(buf); + assert(rc == FLASH_ERR_AGAIN); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_erase_one_block[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { + .type = scenario_event_p, + .p = &hiomap_erase_qs0l1_call, + }, + { + .type = scenario_event_p, + .p = &hiomap_flush_call, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, }, + SCENARIO_SENTINEL, +}; + +static const struct scenario_event +scenario_hiomap_protocol_erase_two_blocks[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l2_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_erase_qs0l1_call, }, + { .type = scenario_event_p, .p = &hiomap_flush_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs1l1_rs1l1_call, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ERASE, + .seq = 8, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp = { + .cmd = HIOMAP_C_ERASE, + .seq = 8, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_FLUSH, + .seq = 9, + }, + .resp = { + .cmd = HIOMAP_C_FLUSH, + .seq = 9, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_a, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_erase_two_blocks(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + + scenario_enter(scenario_hiomap_protocol_erase_two_blocks); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 2 * (1 << ctx->block_size_shift); + assert(!bl->erase(bl, 0, len)); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_erase_one_block_twice[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_erase_qs0l1_call, }, + { .type = scenario_event_p, .p = &hiomap_flush_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ERASE, + .seq = 7, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp = { + .cmd = HIOMAP_C_ERASE, + .seq = 7, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_FLUSH, + .seq = 8, + }, + .resp = { + .cmd = HIOMAP_C_FLUSH, + .seq = 8, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_9, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_erase_one_block_twice(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + + scenario_enter(scenario_hiomap_protocol_erase_one_block_twice); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + assert(!bl->erase(bl, 0, len)); + assert(!bl->erase(bl, 0, len)); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static void test_hiomap_protocol_erase_one_block(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + + scenario_enter(scenario_hiomap_protocol_erase_one_block); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + assert(!bl->erase(bl, 0, len)); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static void test_hiomap_protocol_event_before_erase(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + int rc; + + scenario_enter(scenario_hiomap_protocol_event_before_action); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + rc = bl->erase(bl, 0, len); + assert(rc == FLASH_ERR_AGAIN); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_event_during_erase[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { + .type = scenario_sel, + .s = { + .bmc_state = HIOMAP_E_DAEMON_READY | + HIOMAP_E_FLASH_LOST, + } + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_event_during_erase(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + int rc; + + scenario_enter(scenario_hiomap_protocol_event_during_erase); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + rc = bl->erase(bl, 0, len); + assert(rc == FLASH_ERR_AGAIN); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event scenario_hiomap_protocol_bad_sequence[] = { + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 1, + .args = { + [0] = HIOMAP_E_ACK_MASK, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_ACK, + .seq = 0, + }, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_bad_sequence(void) +{ + struct blocklevel_device *bl; + + scenario_enter(scenario_hiomap_protocol_bad_sequence); + assert(ipmi_hiomap_init(&bl) > 0); + scenario_exit(); +} + +static const struct scenario_event scenario_hiomap_protocol_action_error[] = { + { + .type = scenario_cmd, + .c = { + /* Ack is legitimate, but we'll pretend it's invalid */ + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 1, + .args = { [0] = 0x3 }, + }, + .cc = IPMI_INVALID_COMMAND_ERR, + .resp = { + .cmd = HIOMAP_C_ACK, + .seq = 1, + }, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_action_error(void) +{ + struct blocklevel_device *bl; + + scenario_enter(scenario_hiomap_protocol_action_error); + assert(ipmi_hiomap_init(&bl) > 0); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_get_flash_info[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 4, + .args = { + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x20, + [2] = 0x01, [3] = 0x00, + }, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_get_flash_info(void) +{ + struct blocklevel_device *bl; + const char *name; + uint32_t granule; + uint64_t size; + + scenario_enter(scenario_hiomap_protocol_get_flash_info); + assert(!ipmi_hiomap_init(&bl)); + assert(!bl->get_info(bl, &name, &size, &granule)); + assert(!name); + assert(size == (32 * 1024 * 1024)); + assert(granule == (4 * 1024)); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_persistent_error[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { .type = scenario_sel, .s = { .bmc_state = HIOMAP_E_PROTOCOL_RESET } }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_persistent_error(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + char buf; + int rc; + + scenario_enter(scenario_hiomap_protocol_persistent_error); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + assert(ctx->bmc_state == HIOMAP_E_PROTOCOL_RESET); + rc = bl->read(bl, 0, &buf, sizeof(buf)); + assert(rc == FLASH_ERR_DEVICE_GONE); + rc = bl->read(bl, 0, &buf, sizeof(buf)); + assert(rc == FLASH_ERR_DEVICE_GONE); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_get_info_error[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 2, + .args = { + [0] = HIOMAP_V2, + }, + }, + .cc = IPMI_INVALID_COMMAND_ERR, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_get_info_error(void) +{ + struct blocklevel_device *bl; + + scenario_enter(scenario_hiomap_get_info_error); + assert(ipmi_hiomap_init(&bl) > 0); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_get_flash_info_error[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 3, + .args = { + [0] = HIOMAP_V2, + }, + }, + .cc = IPMI_INVALID_COMMAND_ERR, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_get_flash_info_error(void) +{ + struct blocklevel_device *bl; + + scenario_enter(scenario_hiomap_get_flash_info_error); + assert(ipmi_hiomap_init(&bl) > 0); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_create_read_window_error[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_INVALID_COMMAND_ERR, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_create_read_window_error(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_create_read_window_error); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->read(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_create_write_window_error[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_INVALID_COMMAND_ERR, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_create_write_window_error(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_create_write_window_error); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->write(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event scenario_hiomap_mark_dirty_error[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_MARK_DIRTY, + .seq = 5, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_INVALID_COMMAND_ERR, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_mark_dirty_error(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_mark_dirty_error); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->write(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event scenario_hiomap_flush_error[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_FLUSH, + .seq = 6, + }, + .cc = IPMI_INVALID_COMMAND_ERR, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_flush_error(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_flush_error); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->write(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static void test_hiomap_ack_error(void) +{ + /* Same thing at the moment */ + test_hiomap_protocol_action_error(); +} + +static const struct scenario_event scenario_hiomap_erase_error[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ERASE, + .seq = 5, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_INVALID_COMMAND_ERR, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_erase_error(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + + scenario_enter(scenario_hiomap_erase_error); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + assert(bl->erase(bl, 0, len) > 0); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event scenario_hiomap_ack_malformed_small[] = { + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 1, + .args = { [0] = 0x3 }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp_size = 1 + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_ack_malformed_small(void) +{ + struct blocklevel_device *bl; + + scenario_enter(scenario_hiomap_ack_malformed_small); + assert(ipmi_hiomap_init(&bl) > 0); + scenario_exit(); +} + +static const struct scenario_event scenario_hiomap_ack_malformed_large[] = { + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 1, + .args = { [0] = 0x3 }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp_size = 3, + .resp = { + .cmd = HIOMAP_C_ACK, + .seq = 1, + }, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_ack_malformed_large(void) +{ + struct blocklevel_device *bl; + + scenario_enter(scenario_hiomap_ack_malformed_large); + assert(ipmi_hiomap_init(&bl) > 0); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_get_info_malformed_small[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 2, + .args = { [0] = 0x2 }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp_size = 7, + .resp = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 2, + }, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_get_info_malformed_small(void) +{ + struct blocklevel_device *bl; + + scenario_enter(scenario_hiomap_get_info_malformed_small); + assert(ipmi_hiomap_init(&bl) > 0); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_get_info_malformed_large[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 2, + .args = { [0] = 0x2 }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp_size = 9, + .resp = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 2, + }, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_get_info_malformed_large(void) +{ + struct blocklevel_device *bl; + + scenario_enter(scenario_hiomap_get_info_malformed_large); + assert(ipmi_hiomap_init(&bl) > 0); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_get_flash_info_malformed_small[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 3, + }, + .cc = IPMI_CC_NO_ERROR, + .resp_size = 5, + .resp = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 3, + }, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_get_flash_info_malformed_small(void) +{ + struct blocklevel_device *bl; + + scenario_enter(scenario_hiomap_get_flash_info_malformed_small); + assert(ipmi_hiomap_init(&bl) > 0); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_get_flash_info_malformed_large[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 3, + }, + .cc = IPMI_CC_NO_ERROR, + .resp_size = 7, + .resp = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 3, + }, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_get_flash_info_malformed_large(void) +{ + struct blocklevel_device *bl; + + scenario_enter(scenario_hiomap_get_flash_info_malformed_large); + assert(ipmi_hiomap_init(&bl) > 0); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_create_read_window_malformed_small[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp_size = 7, + .resp = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_create_read_window_malformed_small(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_create_read_window_malformed_small); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->read(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); + +} + +static const struct scenario_event +scenario_hiomap_create_read_window_malformed_large[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp_size = 9, + .resp = { + .cmd = HIOMAP_C_CREATE_READ_WINDOW, + .seq = 4, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_create_read_window_malformed_large(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_create_read_window_malformed_large); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->read(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_create_write_window_malformed_small[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp_size = 7, + .resp = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 4, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_create_write_window_malformed_small(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_create_write_window_malformed_small); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->write(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); + +} + +static const struct scenario_event +scenario_hiomap_create_write_window_malformed_large[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 4, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp_size = 9, + .resp = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 4, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_5, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_create_write_window_malformed_large(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_create_write_window_malformed_large); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->write(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_mark_dirty_malformed_small[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_MARK_DIRTY, + .seq = 5, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp_size = 1, + .resp = { + .cmd = HIOMAP_C_MARK_DIRTY, + .seq = 5, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_mark_dirty_malformed_small(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_mark_dirty_malformed_small); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->write(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); + +} + +static const struct scenario_event +scenario_hiomap_mark_dirty_malformed_large[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_MARK_DIRTY, + .seq = 5, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp_size = 3, + .resp = { + .cmd = HIOMAP_C_MARK_DIRTY, + .seq = 5, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_mark_dirty_malformed_large(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_mark_dirty_malformed_large); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->write(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_flush_malformed_small[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_FLUSH, + .seq = 6, + }, + .resp_size = 1, + .resp = { + .cmd = HIOMAP_C_FLUSH, + .seq = 6, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_flush_malformed_small(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_flush_malformed_small); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->write(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); + +} + +static const struct scenario_event +scenario_hiomap_flush_malformed_large[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_mark_dirty_qs0l1_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_FLUSH, + .seq = 6, + }, + .resp_size = 3, + .resp = { + .cmd = HIOMAP_C_FLUSH, + .seq = 6, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_7, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_flush_malformed_large(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + void *buf; + + scenario_enter(scenario_hiomap_flush_malformed_large); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + buf = calloc(1, len); + assert(buf); + assert(bl->write(bl, 0, buf, len) > 0); + free(buf); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_erase_malformed_small[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ERASE, + .seq = 5, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp_size = 1, + .resp = { + .cmd = HIOMAP_C_ERASE, + .seq = 5, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_erase_malformed_small(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + + scenario_enter(scenario_hiomap_erase_malformed_small); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + assert(bl->erase(bl, 0, len) > 0); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_erase_malformed_large[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ERASE, + .seq = 5, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp_size = 3, + .resp = { + .cmd = HIOMAP_C_ERASE, + .seq = 5, + }, + }, + }, + { .type = scenario_event_p, .p = &hiomap_reset_call_seq_6, }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_erase_malformed_large(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + + scenario_enter(scenario_hiomap_erase_malformed_large); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + assert(bl->erase(bl, 0, len) > 0); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +/* Common recovery calls */ + +static const struct scenario_event hiomap_recovery_ack_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 7, + .args = { + [0] = HIOMAP_E_PROTOCOL_RESET, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_ACK, + .seq = 7, + }, + }, +}; + +static const struct scenario_event hiomap_recovery_get_info_call = { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 8, + .args = { + [0] = HIOMAP_V2, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 8, + .args = { + [0] = HIOMAP_V2, + [1] = 12, + [2] = 8, [3] = 0, + }, + }, + }, +}; + +static const struct scenario_event +scenario_hiomap_protocol_recovery_failure_ack[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_erase_qs0l1_call, }, + { .type = scenario_event_p, .p = &hiomap_flush_call, }, + { .type = scenario_delay }, + { + .type = scenario_sel, + .s = { + .bmc_state = HIOMAP_E_DAEMON_READY | + HIOMAP_E_PROTOCOL_RESET + } + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 7, + .args = { + [0] = HIOMAP_E_PROTOCOL_RESET, + }, + }, + .cc = IPMI_ERR_UNSPECIFIED, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 8, + .args = { + [0] = HIOMAP_E_PROTOCOL_RESET, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_ACK, + .seq = 8, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 9, + .args = { + [0] = HIOMAP_V2, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 9, + .args = { + [0] = HIOMAP_V2, + [1] = 12, + [2] = 8, [3] = 0, + }, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 10, + .args = { + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 10, + .args = { + [0] = 0x00, [1] = 0x20, + [2] = 0x01, [3] = 0x00, + }, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 11, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 11, + .args = { + [0] = 0xff, [1] = 0x0f, + [2] = 0x01, [3] = 0x00, + [4] = 0x00, [5] = 0x00, + }, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ERASE, + .seq = 12, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp = { + .cmd = HIOMAP_C_ERASE, + .seq = 12, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_FLUSH, + .seq = 13, + }, + .resp = { + .cmd = HIOMAP_C_FLUSH, + .seq = 13, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_RESET, + .seq = 14, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_RESET, + .seq = 14, + }, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_recovery_failure_ack(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + + scenario_enter(scenario_hiomap_protocol_recovery_failure_ack); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + /* + * We're erasing the same block 3 times - it's irrelevant, we're just + * trying to manipulate window state + */ + assert(!bl->erase(bl, 0, len)); + scenario_advance(); + assert(bl->erase(bl, 0, len) > 0); + assert(!bl->erase(bl, 0, len)); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_recovery_failure_get_info[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_erase_qs0l1_call, }, + { .type = scenario_event_p, .p = &hiomap_flush_call, }, + { .type = scenario_delay }, + { + .type = scenario_sel, + .s = { + .bmc_state = HIOMAP_E_DAEMON_READY | + HIOMAP_E_PROTOCOL_RESET + } + }, + { .type = scenario_event_p, .p = &hiomap_recovery_ack_call, }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 8, + .args = { + [0] = HIOMAP_V2, + }, + }, + .cc = IPMI_ERR_UNSPECIFIED, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 9, + .args = { + [0] = HIOMAP_E_PROTOCOL_RESET, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_ACK, + .seq = 9, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 10, + .args = { + [0] = HIOMAP_V2, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 10, + .args = { + [0] = HIOMAP_V2, + [1] = 12, + [2] = 8, [3] = 0, + }, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 11, + .args = { + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 11, + .args = { + [0] = 0x00, [1] = 0x20, + [2] = 0x01, [3] = 0x00, + }, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 12, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 12, + .args = { + [0] = 0xff, [1] = 0x0f, + [2] = 0x01, [3] = 0x00, + [4] = 0x00, [5] = 0x00, + }, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ERASE, + .seq = 13, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp = { + .cmd = HIOMAP_C_ERASE, + .seq = 13, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_FLUSH, + .seq = 14, + }, + .resp = { + .cmd = HIOMAP_C_FLUSH, + .seq = 14, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_RESET, + .seq = 15, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_RESET, + .seq = 15, + }, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_recovery_failure_get_info(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + + scenario_enter(scenario_hiomap_protocol_recovery_failure_get_info); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + /* + * We're erasing the same block 3 times - it's irrelevant, we're just + * trying to manipulate window state + */ + assert(!bl->erase(bl, 0, len)); + scenario_advance(); + assert(bl->erase(bl, 0, len) > 0); + assert(!bl->erase(bl, 0, len)); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +static const struct scenario_event +scenario_hiomap_protocol_recovery_failure_get_flash_info[] = { + { .type = scenario_event_p, .p = &hiomap_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_get_info_call, }, + { .type = scenario_event_p, .p = &hiomap_get_flash_info_call, }, + { + .type = scenario_event_p, + .p = &hiomap_create_write_window_qs0l1_rs0l1_call, + }, + { .type = scenario_event_p, .p = &hiomap_erase_qs0l1_call, }, + { .type = scenario_event_p, .p = &hiomap_flush_call, }, + { .type = scenario_delay }, + { + .type = scenario_sel, + .s = { + .bmc_state = HIOMAP_E_DAEMON_READY | + HIOMAP_E_PROTOCOL_RESET + } + }, + { .type = scenario_event_p, .p = &hiomap_recovery_ack_call, }, + { .type = scenario_event_p, .p = &hiomap_recovery_get_info_call}, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 9, + }, + .cc = IPMI_ERR_UNSPECIFIED, + }, + + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ACK, + .seq = 10, + .args = { + [0] = HIOMAP_E_PROTOCOL_RESET, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_ACK, + .seq = 10, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 11, + .args = { + [0] = HIOMAP_V2, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_INFO, + .seq = 11, + .args = { + [0] = HIOMAP_V2, + [1] = 12, + [2] = 8, [3] = 0, + }, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 12, + .args = { + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_GET_FLASH_INFO, + .seq = 12, + .args = { + [0] = 0x00, [1] = 0x20, + [2] = 0x01, [3] = 0x00, + }, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 13, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_CREATE_WRITE_WINDOW, + .seq = 13, + .args = { + [0] = 0xff, [1] = 0x0f, + [2] = 0x01, [3] = 0x00, + [4] = 0x00, [5] = 0x00, + }, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_ERASE, + .seq = 14, + .args = { + [0] = 0x00, [1] = 0x00, + [2] = 0x01, [3] = 0x00, + }, + }, + .resp = { + .cmd = HIOMAP_C_ERASE, + .seq = 14, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_FLUSH, + .seq = 15, + }, + .resp = { + .cmd = HIOMAP_C_FLUSH, + .seq = 15, + }, + }, + }, + { + .type = scenario_cmd, + .c = { + .req = { + .cmd = HIOMAP_C_RESET, + .seq = 16, + }, + .cc = IPMI_CC_NO_ERROR, + .resp = { + .cmd = HIOMAP_C_RESET, + .seq = 16, + }, + }, + }, + SCENARIO_SENTINEL, +}; + +static void test_hiomap_protocol_recovery_failure_get_flash_info(void) +{ + struct blocklevel_device *bl; + struct ipmi_hiomap *ctx; + size_t len; + + scenario_enter(scenario_hiomap_protocol_recovery_failure_get_flash_info); + assert(!ipmi_hiomap_init(&bl)); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + /* + * We're erasing the same block 3 times - it's irrelevant, we're just + * trying to manipulate window state + */ + assert(!bl->erase(bl, 0, len)); + scenario_advance(); + ctx = container_of(bl, struct ipmi_hiomap, bl); + len = 1 << ctx->block_size_shift; + assert(bl->erase(bl, 0, len) > 0); + assert(!bl->erase(bl, 0, len)); + ipmi_hiomap_exit(bl); + scenario_exit(); +} + +struct test_case { + const char *name; + void (*fn)(void); +}; + +#define TEST_CASE(x) { #x, x } + +struct test_case test_cases[] = { + TEST_CASE(test_hiomap_init), + TEST_CASE(test_hiomap_event_daemon_ready), + TEST_CASE(test_hiomap_event_daemon_stopped), + TEST_CASE(test_hiomap_event_daemon_restarted), + TEST_CASE(test_hiomap_event_daemon_lost_flash_control), + TEST_CASE(test_hiomap_event_daemon_regained_flash_control_dirty), + TEST_CASE(test_hiomap_protocol_reset_recovery), + TEST_CASE(test_hiomap_protocol_read_one_block), + TEST_CASE(test_hiomap_protocol_read_one_byte), + TEST_CASE(test_hiomap_protocol_read_two_blocks), + TEST_CASE(test_hiomap_protocol_read_1block_1byte), + TEST_CASE(test_hiomap_protocol_read_one_block_twice), + TEST_CASE(test_hiomap_protocol_event_before_read), + TEST_CASE(test_hiomap_protocol_event_during_read), + TEST_CASE(test_hiomap_protocol_write_one_block), + TEST_CASE(test_hiomap_protocol_write_one_byte), + TEST_CASE(test_hiomap_protocol_write_two_blocks), + TEST_CASE(test_hiomap_protocol_write_1block_1byte), + TEST_CASE(test_hiomap_protocol_write_one_block_twice), + TEST_CASE(test_hiomap_protocol_event_before_write), + TEST_CASE(test_hiomap_protocol_event_during_write), + TEST_CASE(test_hiomap_protocol_erase_one_block), + TEST_CASE(test_hiomap_protocol_erase_two_blocks), + TEST_CASE(test_hiomap_protocol_erase_one_block_twice), + TEST_CASE(test_hiomap_protocol_event_before_erase), + TEST_CASE(test_hiomap_protocol_event_during_erase), + TEST_CASE(test_hiomap_protocol_bad_sequence), + TEST_CASE(test_hiomap_protocol_action_error), + TEST_CASE(test_hiomap_protocol_persistent_error), + TEST_CASE(test_hiomap_protocol_get_flash_info), + TEST_CASE(test_hiomap_get_info_error), + TEST_CASE(test_hiomap_get_flash_info_error), + TEST_CASE(test_hiomap_create_read_window_error), + TEST_CASE(test_hiomap_create_write_window_error), + TEST_CASE(test_hiomap_mark_dirty_error), + TEST_CASE(test_hiomap_flush_error), + TEST_CASE(test_hiomap_ack_error), + TEST_CASE(test_hiomap_erase_error), + TEST_CASE(test_hiomap_ack_malformed_small), + TEST_CASE(test_hiomap_ack_malformed_large), + TEST_CASE(test_hiomap_get_info_malformed_small), + TEST_CASE(test_hiomap_get_info_malformed_large), + TEST_CASE(test_hiomap_get_flash_info_malformed_small), + TEST_CASE(test_hiomap_get_flash_info_malformed_large), + TEST_CASE(test_hiomap_create_read_window_malformed_small), + TEST_CASE(test_hiomap_create_read_window_malformed_large), + TEST_CASE(test_hiomap_create_write_window_malformed_small), + TEST_CASE(test_hiomap_create_write_window_malformed_large), + TEST_CASE(test_hiomap_mark_dirty_malformed_small), + TEST_CASE(test_hiomap_mark_dirty_malformed_large), + TEST_CASE(test_hiomap_flush_malformed_small), + TEST_CASE(test_hiomap_flush_malformed_large), + TEST_CASE(test_hiomap_erase_malformed_small), + TEST_CASE(test_hiomap_erase_malformed_large), + TEST_CASE(test_hiomap_protocol_recovery_failure_ack), + TEST_CASE(test_hiomap_protocol_recovery_failure_get_info), + TEST_CASE(test_hiomap_protocol_recovery_failure_get_flash_info), + { NULL, NULL }, +}; + +int main(void) +{ + struct test_case *tc = &test_cases[0]; + + do { + printf("%s\n", tc->name); + tc->fn(); + printf("\n"); + } while ((++tc)->fn); + + return 0; +} diff --git a/roms/skiboot/libflash/test/test-mbox.c b/roms/skiboot/libflash/test/test-mbox.c new file mode 100644 index 000000000..260a3c7d4 --- /dev/null +++ b/roms/skiboot/libflash/test/test-mbox.c @@ -0,0 +1,333 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2017-2018 IBM Corp. */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <stdarg.h> + +#include <libflash/libflash.h> +#include <libflash/libflash-priv.h> + +#include "stubs.h" +#include "mbox-server.h" + +#define zalloc(n) calloc(1, n) +#define __unused __attribute__((unused)) + +#undef pr_fmt + +void mbox_init(void) +{ +} + +#include "../libflash.c" +#include "../mbox-flash.c" +#include "../ecc.c" +#include "../blocklevel.c" + +#undef pr_fmt +#define pr_fmt(fmt) "MBOX-PROXY: " fmt + +/* client interface */ + +#include "../../include/lpc-mbox.h" + +#define ERR(...) FL_DBG(__VA_ARGS__) + +static int run_flash_test(struct blocklevel_device *bl) +{ + struct mbox_flash_data *mbox_flash; + char hello[] = "Hello World"; + uint32_t erase_granule; + uint64_t total_size; + const char *name; + uint16_t *test; + char *tmp; + int i, rc; + + mbox_flash = container_of(bl, struct mbox_flash_data, bl); + + /* + * Do something first so that if it has been reset it does that + * before we check versions + */ + rc = blocklevel_get_info(bl, &name, &total_size, &erase_granule); + if (rc) { + ERR("blocklevel_get_info() failed with err %d\n", rc); + return 1; + } + if (total_size != mbox_server_total_size()) { + ERR("Total flash size is incorrect: 0x%08lx vs 0x%08x\n", + total_size, mbox_server_total_size()); + return 1; + } + if (erase_granule != mbox_server_erase_granule()) { + ERR("Erase granule is incorrect 0x%08x vs 0x%08x\n", + erase_granule, mbox_server_erase_granule()); + return 1; + } + + + /* Sanity check that mbox_flash has inited correctly */ + if (mbox_flash->version != mbox_server_version()) { + ERR("MBOX Flash didn't agree with the server version\n"); + return 1; + } + if (mbox_flash->version == 1 && mbox_flash->shift != 12) { + ERR("MBOX Flash version 1 isn't using a 4K shift\n"); + return 1; + } + + mbox_server_memset(0xff); + + test = calloc(erase_granule * 20, 1); + + /* Make up a test pattern */ + for (i = 0; i < erase_granule * 10; i++) + test[i] = i; + + /* Write 64k of stuff at 0 and at 128k */ + printf("Writing test patterns...\n"); + rc = blocklevel_write(bl, 0, test, erase_granule * 10); + if (rc) { + ERR("blocklevel_write(0, erase_granule * 10) failed with err %d\n", rc); + return 1; + } + rc = blocklevel_write(bl, erase_granule * 20, test, erase_granule * 10); + if (rc) { + ERR("blocklevel_write(0x20000, 0x10000) failed with err %d\n", rc); + return 1; + } + + if (mbox_server_memcmp(0, test, erase_granule * 10)) { + ERR("Test pattern mismatch !\n"); + return 1; + } + + /* Write "Hello world" straddling the 64k boundary */ + printf("Writing test string...\n"); + rc = blocklevel_write(bl, (erase_granule * 10) - 8, hello, sizeof(hello)); + if (rc) { + ERR("blocklevel_write(0xfffc, %s, %lu) failed with err %d\n", + hello, sizeof(hello), rc); + return 1; + } + + /* Check result */ + if (mbox_server_memcmp((erase_granule * 10) - 8, hello, sizeof(hello))) { + ERR("Test string mismatch!\n"); + return 1; + } + + /* Erase granule is something but never 0x50, this shouldn't succeed */ + rc = blocklevel_erase(bl, 0, 0x50); + if (!rc) { + ERR("blocklevel_erase(0, 0x50) didn't fail!\n"); + return 1; + } + + /* Check it didn't silently erase */ + if (mbox_server_memcmp(0, test, (erase_granule * 10) - 8)) { + ERR("Test pattern mismatch !\n"); + return 1; + } + + /* + * For v1 protocol this should NOT call MARK_WRITE_ERASED! + * The server MARK_WRITE_ERASED will call exit(1) if it gets a + * MARK_WRITE_ERASED and version == 1 + */ + rc = blocklevel_erase(bl, 0, erase_granule); + if (rc) { + ERR("blocklevel_erase(0, erase_granule) failed with err %d\n", rc); + return 1; + } + + /* + * Version 1 doesn't specify that the buffer actually becomes 0xff + * It is up to the daemon to do what it wants really - there are + * implementations that do nothing but writes to the same region + * work fine + */ + + /* This check is important for v2 */ + /* Check stuff got erased */ + tmp = malloc(erase_granule * 2); + if (!tmp) { + ERR("malloc failed\n"); + return 1; + } + if (mbox_server_version() > 1) { + memset(tmp, 0xff, erase_granule); + if (mbox_server_memcmp(0, tmp, erase_granule)) { + ERR("Buffer not erased\n"); + rc = 1; + goto out; + } + } + + /* Read beyond the end of flash */ + rc = blocklevel_read(bl, total_size, tmp, 0x1000); + if (!rc) { + ERR("blocklevel_read(total_size, 0x1000) (read beyond the end) succeeded\n"); + goto out; + } + + /* Test some simple write/read cases, avoid first page */ + rc = blocklevel_write(bl, erase_granule * 2, test, erase_granule / 2); + if (rc) { + ERR("blocklevel_write(erase_granule, erase_granule / 2) failed with err %d\n", rc); + goto out; + } + rc = blocklevel_write(bl, erase_granule * 2 + erase_granule / 2, test, erase_granule / 2); + if (rc) { + ERR("blocklevel_write(erase_granule * 2 + erase_granule / 2, erase_granule) failed with err %d\n", rc); + goto out; + } + + rc = mbox_server_memcmp(erase_granule * 2, test, erase_granule / 2); + if (rc) { + ERR("%s:%d mbox_server_memcmp miscompare\n", __FILE__, __LINE__); + goto out; + } + rc = mbox_server_memcmp(erase_granule * 2 + erase_granule / 2, test, erase_granule / 2); + if (rc) { + ERR("%s:%d mbox_server_memcmp miscompare\n", __FILE__, __LINE__); + goto out; + } + + /* Great so the writes made it, can we read them back? Do it in + * four small reads */ + for (i = 0; i < 4; i++) { + rc = blocklevel_read(bl, erase_granule * 2 + (i * erase_granule / 4), tmp + (i * erase_granule / 4), erase_granule / 4); + if (rc) { + ERR("blocklevel_read(0x%08x, erase_granule / 4) failed with err %d\n", + 2 * erase_granule + (i * erase_granule / 4), rc); + goto out; + } + } + rc = memcmp(test, tmp, erase_granule / 2); + if (rc) { + ERR("%s:%d read back miscompare\n", __FILE__, __LINE__); + goto out; + } + rc = memcmp(test, tmp + erase_granule / 2, erase_granule / 2); + if (rc) { + ERR("%s:%d read back miscompare\n", __FILE__, __LINE__); + goto out; + } + + /* + * Make sure we didn't corrupt other stuff, also make sure one + * blocklevel call will understand how to read from two windows + */ + for (i = 3; i < 9; i = i + 2) { + printf("i:%d erase: 0x%08x\n", i, erase_granule); + rc = blocklevel_read(bl, i * erase_granule, tmp, 2 * erase_granule); + if (rc) { + ERR("blocklevel_read(0x%08x, 2 * erase_granule) failed with err: %d\n", i * erase_granule, rc); + goto out; + } + rc = memcmp(((char *)test) + (i * erase_granule), tmp, 2 * erase_granule); + if (rc) { + ERR("%s:%d read back miscompare (pos: 0x%08x)\n", __FILE__, __LINE__, i * erase_granule); + goto out; + } + } + + srand(1); + /* + * Try to jump around the place doing a tonne of small reads. + * Worth doing the same with writes TODO + */ +#ifdef __STRICT_TEST__ +#define TEST_LOOPS 1000 +#else +#define TEST_LOOPS 100 +#endif + for (i = 0; i < TEST_LOOPS; i++) { + int r = rand(); + + printf("Loop %d of %d\n", i, TEST_LOOPS); + /* Avoid reading too far, just skip it */ + if ((r % erase_granule * 10) + (r % erase_granule * 2) > erase_granule * 10) + continue; + + rc = blocklevel_read(bl, erase_granule * 20 + (r % erase_granule * 10), tmp, r % erase_granule * 2); + if (rc) { + ERR("blocklevel_read(0x%08x, 0x%08x) failed with err %d\n", 0x20000 + (r % 0x100000), r % 0x2000, rc); + goto out; + } + rc = memcmp(((char *)test) + (r % erase_granule * 10), tmp, r % erase_granule * 2); + if (rc) { + ERR("%s:%d read back miscompare (pos: 0x%08x)\n", __FILE__, __LINE__, 0x20000 + (r % 0x10000)); + goto out; + } + } +out: + free(tmp); + return rc; +} + +int main(void) +{ + struct blocklevel_device *bl; + int rc; + + libflash_debug = true; + + mbox_server_init(); + +#ifdef __STRICT_TEST__ + printf("Found __STRICT_TEST__, this may take time time.\n"); +#else + printf("__STRICT_TEST__ not found, use make strict-check for a more\n"); + printf("thorough test, it will take significantly longer.\n"); +#endif + + printf("Doing mbox-flash V1 tests\n"); + + /* run test */ + mbox_flash_init(&bl); + rc = run_flash_test(bl); + if (rc) + goto out; + /* + * Trick mbox-flash into thinking there was a reboot so we can + * switch to v2 + */ + + printf("Doing mbox-flash V2 tests\n"); + + mbox_server_reset(2, 12); + + /* Do all the tests again */ + rc = run_flash_test(bl); + if (rc) + goto out; + + mbox_server_reset(2, 17); + + /* Do all the tests again */ + rc = run_flash_test(bl); + if (rc) + goto out; + + + printf("Doing mbox-flash V3 tests\n"); + + mbox_server_reset(3, 20); + + /* Do all the tests again */ + rc = run_flash_test(bl); + + +out: + mbox_flash_exit(bl); + + mbox_server_destroy(); + + return rc; +} |