aboutsummaryrefslogtreecommitdiff
path: root/libflash
diff options
context:
space:
mode:
authorCyril Bur <cyril.bur@au1.ibm.com>2017-12-05 12:01:13 +1100
committerStewart Smith <stewart@linux.vnet.ibm.com>2017-12-18 17:58:32 -0600
commitb9774c47eecd0c90e503919432ec1e4a86355398 (patch)
treefcd3840bc3570c1364093db423692b15964677cc /libflash
parent31f2c03b0abd43f3dadcecd6c00159ed52938081 (diff)
downloadskiboot-b9774c47eecd0c90e503919432ec1e4a86355398.zip
skiboot-b9774c47eecd0c90e503919432ec1e4a86355398.tar.gz
skiboot-b9774c47eecd0c90e503919432ec1e4a86355398.tar.bz2
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 <cyril.bur@au1.ibm.com> [stewart: fix TESTS_LOOPS printf] Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
Diffstat (limited to 'libflash')
-rw-r--r--libflash/mbox-flash.c2
-rw-r--r--libflash/test/Makefile.check17
-rw-r--r--libflash/test/mbox-server.c499
-rw-r--r--libflash/test/mbox-server.h10
-rw-r--r--libflash/test/stubs.c78
-rw-r--r--libflash/test/stubs.h27
-rw-r--r--libflash/test/test-mbox.c342
7 files changed, 968 insertions, 7 deletions
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 <ccan/container_of/container_of.h>
#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 <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;
+ 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 <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/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 <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;
+}
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 <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);
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 <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
+
+#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;
+}