[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