diff --git a/.gitignore b/.gitignore
index e7fa4a08..02e9b576 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,6 +75,7 @@ libc/test/run-time-gcov
 libflash/test/test-blocklevel
 libflash/test/test-flash
 libflash/test/test-ecc
+libflash/test/test-mbox
 libflash/test/test-flash-gcov
 libstb/create-container
 libstb/test/run-stb-container
diff --git a/include/skiboot.h b/include/skiboot.h
index 1a153b02..bc458225 100644
--- a/include/skiboot.h
+++ b/include/skiboot.h
@@ -139,6 +139,7 @@ extern unsigned int pcie_max_link_speed;
 /* Convert a 4-bit number to a hex char */
 extern char __attrconst tohex(uint8_t nibble);
 
+#ifndef __TEST__
 /* Bit position of the most significant 1-bit (LSB=0, MSB=63) */
 static inline int ilog2(unsigned long val)
 {
@@ -153,6 +154,7 @@ static inline bool is_pow2(unsigned long val)
 {
 	return val == (1ul << ilog2(val));
 }
+#endif
 
 #define lo32(x)	((x) & 0xffffffff)
 #define hi32(x)	(((x) >> 32) & 0xffffffff)
diff --git a/libflash/mbox-flash.c b/libflash/mbox-flash.c
index 486e917e..f3dd0d42 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
 
 #define MBOX_DEFAULT_TIMEOUT 30
 
diff --git a/libflash/test/Makefile.check b/libflash/test/Makefile.check
index 1f92b9d2..a50f47ce 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 00000000..e0357883
--- /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 00000000..0a961ba9
--- /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 aabf018d..e09c8f57 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 00000000..bc6d3f38
--- /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 00000000..b148483a
--- /dev/null
+++ b/libflash/test/test-mbox.c
@@ -0,0 +1,341 @@
+/* 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__
+	for (i = 0; i < 1000; i++) {
+#else
+	for (i = 0; i < 100; i++) {
+#endif
+		int r = rand();
+
+		printf("Loop %d of 1000\n", i);
+		/* 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;
+}
