[Skiboot] [PATCH 5/5] libflash/test: Add tests for mbox-flash

Cyril Bur cyril.bur at au1.ibm.com
Fri May 12 23:55:58 AEST 2017


On Tue, 2017-05-02 at 14:48 +1000, Suraj Jitindar Singh wrote:
> On Mon, 2017-04-24 at 19:14 +1000, Cyril Bur wrote:
> > 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 adherance 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 behavaiour but a very unreliable indicator of
> > correctness.
> > 
> > Full integration tests with a 'read' BMC daemon are probably beyond
> > the
> > scope of this repository.
> 
> Comments below
> 
> Suraj
> 
> > 
> > Signed-off-by: Cyril Bur <cyril.bur at au1.ibm.com>
> > ---
> >  .gitignore                   |   1 +
> >  include/skiboot.h            |   2 +
> >  libflash/mbox-flash.c        |   2 +
> >  libflash/test/Makefile.check |  12 +-
> >  libflash/test/mbox-server.c  | 480
> > +++++++++++++++++++++++++++++++++++++++++++
> >  libflash/test/mbox-server.h  |  10 +
> >  libflash/test/stubs.c        |  78 ++++++-
> >  libflash/test/stubs.h        |  27 +++
> >  libflash/test/test-mbox.c    | 308 +++++++++++++++++++++++++++
> >  9 files changed, 913 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
> > 
> > diff --git a/.gitignore b/.gitignore
> > index 17e71f9c..0de1bb0a 100644
> > --- a/.gitignore
> > +++ b/.gitignore
> > @@ -73,6 +73,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 2b1f8a57..8289c4ca 100644
> > --- a/include/skiboot.h
> > +++ b/include/skiboot.h
> > @@ -137,6 +137,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)
> >  {
> > @@ -151,6 +152,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 5d29215c..79358f31 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..29283fd8 100644
> > --- a/libflash/test/Makefile.check
> > +++ b/libflash/test/Makefile.check
> > @@ -1,5 +1,5 @@
> >  # -*-Makefile-*-
> > -LIBFLASH_TEST := libflash/test/test-flash libflash/test/test-ecc
> > libflash/test/test-blocklevel
> > +LIBFLASH_TEST := libflash/test/test-flash libflash/test/test-ecc
> > libflash/test/test-blocklevel libflash/test/test-mbox
> >  
> >  LCOV_EXCLUDE += $(LIBFLASH_TEST:%=%.c)
> >  
> > @@ -16,13 +16,13 @@ $(LIBFLASH_TEST:%=%-gcov-run) : %-run: %
> >  $(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) -D__TEST__ -Wno-
> > error=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) -D__TEST__ -Wno-
> > error=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..47585370
> > --- /dev/null
> > +++ b/libflash/test/mbox-server.c
> > @@ -0,0 +1,480 @@
> > +/* 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 <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 LPC_SIZE (LPC_BLOCKS * (1 << server_state.block_shift))
> > +
> > +#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;
> > +	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)
> 
> Given pos is a window index this should be pos + size > win_size?
> 

Ah damn window indexes, also yes, also I think theres actually bigger
problems - I'm using it wrt 'lpc addresses' and in do_dirty() which I
think they're only the same by chance and by the fact that these tests
use zero a lot. In theory they could be quite different.

> > +			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, 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;
> > +}
> > +
> > +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) > 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)
> > +{
> > +	/*
> > +	 * 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_rea
> > d_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%08x, erase: 0x%08x\n",
> > +					LPC_SIZE,
> > server_state.erase_granule);
> > +			if (server_state.api == 1) {
> > +				bmc_put_u32(msg, 0, LPC_SIZE);
> > +				bmc_put_u32(msg, 4,
> > server_state.erase_granule);
> > +			} else {
> > +				bmc_put_u16(msg, 0, 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");
> > +			if (server_state.api > 1 &&
> > !server_state.win_dirty) {
> > +				prlog(PR_EMERG, "Version >1 called
> > FLUSH without a previous DIRTY\n");
> 
> I think this is technically allowed, but if you're just trying to catch
> weird behaviour then makes sense to leave it in :)
> 

Yes, theres nothing in the spec that says it can't be done so no actual
daemon should have this check, however, if there wasn't a previous
dirty I have some questions as to why flush was called at all (on >
version 1).

> > +				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 sever_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;
> > +
> > +	/* now that we've handled the message, holla-back */
> > +	mbox_data.fn(msg, mbox_data.cb_data);
> > +
> > +	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 differenciate 
> 
> differentiate...
> 

Thanks.

> > */
> > +	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)
> > +{
> > +	if (version > 2)
> > +		return 1;
> > +
> > +	server_state.api = version;
> > +	server_state.erase_granule = 0x1000; /* Thats the minmum
> > size for !v1 */
> > +	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; /* TODO add tests to change
> > the block_shift */
> > +	server_state.erase_granule = ERASE_GRANULE;
> > +	server_state.lpc_size = LPC_SIZE;
> > +	server_state.lpc_base = malloc(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..fa17d85e
> > --- /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);
> > +int mbox_server_init(void);
> > +void mbox_server_destroy(void);
> > diff --git a/libflash/test/stubs.c b/libflash/test/stubs.c
> > index aabf018d..d3cde856 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))
> > +
> > +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..45f37672
> > --- /dev/null
> > +++ b/libflash/test/test-mbox.c
> > @@ -0,0 +1,308 @@
> > +/* 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 v
> > 0x%08x\n",
> > +				total_size,
> > mbox_server_total_size());
> > +		return 1;
> > +	}
> > +	if (erase_granule != mbox_server_erase_granule()) {
> > +		ERR("Erase granule is incorrect\n");
> > +		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 = malloc(0x10000 * 2);
> > +
> > +	/* Make up a test pattern */
> > +	for (i = 0; i < 0x10000; i++)
> > +		test[i] = i;
> > +
> > +	/* Write 64k of stuff at 0 and at 128k */
> > +	printf("Writing test patterns...\n");
> > +	rc = blocklevel_write(bl, 0, test, 0x10000);
> > +	if (rc) {
> > +		ERR("blocklevel_write(0, 0x10000) failed with err
> > %d\n", rc);
> > +		return 1;
> > +	}
> > +	rc = blocklevel_write(bl, 0x20000, test, 0x10000);
> > +	if (rc) {
> > +		ERR("blocklevel_write(0x20000, 0x10000) failed with
> > err %d\n", rc);
> > +		return 1;
> > +	}
> > +
> > +	if (mbox_server_memcmp(0, test, 0xfffc)) {
> > +		ERR("Test pattern mismatch !\n");
> > +		return 1;
> > +	}
> > +
> > +	/* Write "Hello world" straddling the 64k boundary */
> > +	printf("Writing test string...\n");
> > +	rc = blocklevel_write(bl, 0xfffc, 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(0xfffc, hello, sizeof(hello))) {
> > +		ERR("Test string mismatch!\n");
> > +		return 1;
> > +	}
> > +
> > +	/* Erase granule is 0x100, 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, 0xfffc)) {
> > +		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, 0x1000);
> > +	if (rc) {
> > +		ERR("blocklevel_erase(0, 0x1000) 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(0x2000);
> > +	if (mbox_server_version() > 1) {
> > +		if (!tmp) {
> > +			ERR("malloc(0x1000) failed\n");
> > +			return 1;
> > +		}
> > +		memset(tmp, 0xff, 0x1000);
> > +		if (mbox_server_memcmp(0, tmp, 0x1000)) {
> > +			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, 0x2000, test, 0x800);
> > +	if (rc) {
> > +		ERR("blocklevel_write(0x2000, 0x800) failed with err
> > %d\n", rc);
> > +		goto out;
> > +	}
> > +	rc = blocklevel_write(bl, 0x2800, test, 0x800);
> > +	if (rc) {
> > +		ERR("blocklevel_write(0x2800, 0x800) failed with err
> > %d\n", rc);
> > +		goto out;
> > +	}
> > +
> > +	rc = mbox_server_memcmp(0x2000, test, 0x800);
> > +	if (rc) {
> > +		ERR("%s:%d mbox_server_memcmp miscompare\n",
> > __FILE__, __LINE__);
> > +		goto out;
> > +	}
> > +	rc = mbox_server_memcmp(0x2800, test, 0x800);
> > +	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, 0x2000 + (i * 0x400), tmp +
> > (i * 0x400), 0x400);
> > +		if (rc) {
> > +			ERR("blocklevel_read(0x%08x, 0x400) failed
> > with err %d\n",
> > +					0x2000 + (i * 0x400), rc);
> > +			goto out;
> > +		}
> > +	}
> > +	rc = memcmp(test, tmp, 0x800);
> > +	if (rc) {
> > +		ERR("%s:%d read back miscompare\n", __FILE__,
> > __LINE__);
> > +		goto out;
> > +	}
> > +	rc = memcmp(test, tmp + 0x800, 0x800);
> > +	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 < 10; i = i + 2) {
> > +		rc = blocklevel_read(bl, i * 0x1000, tmp, 0x2000);
> > +		if (rc) {
> > +			ERR("blocklevel_read(0x%08x, 0x1000 failed
> > with err: %d\n", i * 0x1000, rc);
> > +			goto out;
> > +		}
> > +		rc = memcmp(((char *)test) + (i * 0x1000), tmp,
> > 0x2000);
> > +		if (rc) {
> > +			ERR("%s:%d read back miscompare (pos:
> > 0x%08x)\n", __FILE__, __LINE__, i * 0x1000);
> > +			goto out;
> > +		}
> > +	}
> > +
> > +	srand(1);
> > +	/*
> > +	 * Try to jump around the place doing a tonne of small
> > reads.
> > +	 * Worth doing the same with writes TODO
> > +	 */
> > +	for (i = 0; i < 1000; i++) {
> > +		int r = rand();
> > +		/* Avoid reading too far, just skip it */
> > +		if ((r % 0x10000) + (r % 0x2000) > 0x10000)
> > +			continue;
> > +
> > +		rc = blocklevel_read(bl, 0x20000 + (r % 0x10000),
> > tmp, r % 0x2000);
> > +		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 % 0x10000), tmp, r %
> > 0x2000);
> > +		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();
> > +
> > +	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);
> > +
> > +	/* Do all the tests again */
> > +	rc = run_flash_test(bl);
> > +
> > +out:
> > +	mbox_flash_exit(bl);
> > +
> > +	mbox_server_destroy();
> > +
> > +	return rc;
> > +}
> 
> 



More information about the Skiboot mailing list