From b9774c47eecd0c90e503919432ec1e4a86355398 Mon Sep 17 00:00:00 2001 From: Cyril Bur Date: Tue, 5 Dec 2017 12:01:13 +1100 Subject: libflash/test: Add tests for mbox-flash A first basic set of tests for mbox-flash. These tests do their testing by stubbing out or otherwise replacing functions not in libflash/mbox-flash.c. The stubbed out version of the function can then be used to emulate a BMC mbox daemon talking to back to the code in mbox-flash and it can ensure that there is some adherence to the protocol and that from a blocklevel api point of view the world appears sane. This makes these tests simple to run and they have been integrated into `make check`. The down side is that these tests rely on duplicated feature incomplete BMC daemon behaviour. Therefore these tests are a strong indicator of broken behaviour but a very unreliable indicator of correctness. Full integration tests with a 'real' BMC daemon are probably beyond the scope of this repository. Signed-off-by: Cyril Bur [stewart: fix TESTS_LOOPS printf] Signed-off-by: Stewart Smith --- libflash/mbox-flash.c | 2 + libflash/test/Makefile.check | 17 +- libflash/test/mbox-server.c | 499 +++++++++++++++++++++++++++++++++++++++++++ libflash/test/mbox-server.h | 10 + libflash/test/stubs.c | 78 ++++++- libflash/test/stubs.h | 27 +++ libflash/test/test-mbox.c | 342 +++++++++++++++++++++++++++++ 7 files changed, 968 insertions(+), 7 deletions(-) create mode 100644 libflash/test/mbox-server.c create mode 100644 libflash/test/mbox-server.h create mode 100644 libflash/test/stubs.h create mode 100644 libflash/test/test-mbox.c (limited to 'libflash') diff --git a/libflash/mbox-flash.c b/libflash/mbox-flash.c index 8af2251..e15fecf 100644 --- a/libflash/mbox-flash.c +++ b/libflash/mbox-flash.c @@ -34,8 +34,10 @@ #include #ifndef __SKIBOOT__ +#ifndef __TEST__ #error "This libflash backend must be compiled with skiboot" #endif +#endif /* Same technique as BUILD_BUG_ON from linux */ #define CHECK_HANDLER_SIZE(handlers) ((void)sizeof(char[1 - 2*!!(ARRAY_SIZE(handlers) != (MBOX_COMMAND_COUNT + 1))])) diff --git a/libflash/test/Makefile.check b/libflash/test/Makefile.check index 1f92b9d..a50f47c 100644 --- a/libflash/test/Makefile.check +++ b/libflash/test/Makefile.check @@ -1,5 +1,7 @@ # -*-Makefile-*- -LIBFLASH_TEST := libflash/test/test-flash libflash/test/test-ecc libflash/test/test-blocklevel +TEST_FLAGS = -D__TEST__ + +LIBFLASH_TEST := libflash/test/test-flash libflash/test/test-ecc libflash/test/test-blocklevel libflash/test/test-mbox LCOV_EXCLUDE += $(LIBFLASH_TEST:%=%.c) @@ -10,19 +12,22 @@ libflash-coverage: $(LIBFLASH_TEST:%=%-gcov-run) check: libflash-check libc-coverage coverage: libflash-coverage +strict-check: TEST_FLAGS += -D__STRICT_TEST__ +strict-check: check + $(LIBFLASH_TEST:%=%-gcov-run) : %-run: % $(call QTEST, TEST-COVERAGE ,$< , $<) $(LIBFLASH_TEST:%=%-check) : %-check: % $(call QTEST, RUN-TEST ,$(VALGRIND) $<, $<) -libflash/test/stubs.o: libflash/test/stubs.c - $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) -g -c -o $@ $<, $<) - -$(LIBFLASH_TEST) : libflash/test/stubs.o libflash/libflash.c libflash/ecc.c libflash/blocklevel.c +LIBFLASH_TEST_EXTRA := libflash/test/stubs.o libflash/test/mbox-server.o +$(LIBFLASH_TEST_EXTRA) : %.o : %.c + $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) $(TEST_FLAGS) -Wno-suggest-attribute=const -g -c -o $@ $<, $<) +$(LIBFLASH_TEST) : libflash/libflash.c libflash/ecc.c libflash/blocklevel.c $(LIBFLASH_TEST_EXTRA) $(LIBFLASH_TEST) : % : %.c - $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) -O0 -g -I include -I . -o $@ $< libflash/test/stubs.o, $<) + $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) $(TEST_FLAGS) -Wno-suggest-attribute=const -O0 -g -I include -I . -o $@ $< $(LIBFLASH_TEST_EXTRA), $<) $(LIBFLASH_TEST:%=%-gcov): %-gcov : %.c % $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) $(HOSTGCOVCFLAGS) -I include -I . -o $@ $< libflash/test/stubs.o, $<) diff --git a/libflash/test/mbox-server.c b/libflash/test/mbox-server.c new file mode 100644 index 0000000..e035788 --- /dev/null +++ b/libflash/test/mbox-server.c @@ -0,0 +1,499 @@ +/* Copyright 2017 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include /* 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; + memcpy(data, server_state.lpc_base + addr, sz); + 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; + memcpy(server_state.lpc_base + addr, &data, sz); + 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"); + 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/libflash/test/mbox-server.h b/libflash/test/mbox-server.h new file mode 100644 index 0000000..0a961ba --- /dev/null +++ b/libflash/test/mbox-server.h @@ -0,0 +1,10 @@ +#include + +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/libflash/test/stubs.c b/libflash/test/stubs.c index aabf018..e09c8f5 100644 --- a/libflash/test/stubs.c +++ b/libflash/test/stubs.c @@ -1,4 +1,4 @@ -/* Copyright 2013-2014 IBM Corp. +/* Copyright 2013-2017 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,3 +14,79 @@ * limitations under the License. */ /* Stubs for libflash test */ +#include +#include +#include +#include +#include +#include +#include /* 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; +} diff --git a/libflash/test/stubs.h b/libflash/test/stubs.h new file mode 100644 index 0000000..bc6d3f3 --- /dev/null +++ b/libflash/test/stubs.h @@ -0,0 +1,27 @@ +/* Copyright 2013-2017 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#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); diff --git a/libflash/test/test-mbox.c b/libflash/test/test-mbox.c new file mode 100644 index 0000000..74df983 --- /dev/null +++ b/libflash/test/test-mbox.c @@ -0,0 +1,342 @@ +/* Copyright 2017 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "stubs.h" +#include "mbox-server.h" + +#define zalloc(n) calloc(1, n) +#define __unused __attribute__((unused)) + +#undef pr_fmt + +#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; +} -- cgit v1.1