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

Suraj Jitindar Singh sjitindarsingh at gmail.com
Tue May 2 14:48:06 AEST 2017


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?

> +			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 :)

> +				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...

> */
> +	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