From patchwork Tue Dec 5 01:01:13 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Cyril Bur X-Patchwork-Id: 844525 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.ozlabs.org (lists.ozlabs.org [103.22.144.68]) (using TLSv1.2 with cipher ADH-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 3yrNpP6Ylbz9s7g for ; Tue, 5 Dec 2017 12:05:33 +1100 (AEDT) Received: from lists.ozlabs.org (lists.ozlabs.org [IPv6:2401:3900:2:1::3]) by lists.ozlabs.org (Postfix) with ESMTP id 3yrNpP45mzzF07N for ; Tue, 5 Dec 2017 12:05:33 +1100 (AEDT) X-Original-To: skiboot@lists.ozlabs.org Delivered-To: skiboot@lists.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=au1.ibm.com (client-ip=148.163.156.1; helo=mx0a-001b2d01.pphosted.com; envelope-from=cyril.bur@au1.ibm.com; receiver=) Received: from mx0a-001b2d01.pphosted.com (mx0a-001b2d01.pphosted.com [148.163.156.1]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by lists.ozlabs.org (Postfix) with ESMTPS id 3yrNk40X30zDsF8 for ; Tue, 5 Dec 2017 12:01:47 +1100 (AEDT) Received: from pps.filterd (m0098409.ppops.net [127.0.0.1]) by mx0a-001b2d01.pphosted.com (8.16.0.21/8.16.0.21) with SMTP id vB50wwmV056965 for ; Mon, 4 Dec 2017 20:01:45 -0500 Received: from e06smtp12.uk.ibm.com (e06smtp12.uk.ibm.com [195.75.94.108]) by mx0a-001b2d01.pphosted.com with ESMTP id 2enh1mgrm1-1 (version=TLSv1.2 cipher=AES256-SHA bits=256 verify=NOT) for ; Mon, 04 Dec 2017 20:01:45 -0500 Received: from localhost by e06smtp12.uk.ibm.com with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted for from ; Tue, 5 Dec 2017 01:01:42 -0000 Received: from b06cxnps4076.portsmouth.uk.ibm.com (9.149.109.198) by e06smtp12.uk.ibm.com (192.168.101.142) with IBM ESMTP SMTP Gateway: Authorized Use Only! Violators will be prosecuted; Tue, 5 Dec 2017 01:01:39 -0000 Received: from d06av25.portsmouth.uk.ibm.com (d06av25.portsmouth.uk.ibm.com [9.149.105.61]) by b06cxnps4076.portsmouth.uk.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id vB511daT37486622; Tue, 5 Dec 2017 01:01:39 GMT Received: from d06av25.portsmouth.uk.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 3762E11C058; Tue, 5 Dec 2017 00:56:07 +0000 (GMT) Received: from d06av25.portsmouth.uk.ibm.com (unknown [127.0.0.1]) by IMSVA (Postfix) with ESMTP id 0874311C052; Tue, 5 Dec 2017 00:56:06 +0000 (GMT) Received: from ozlabs.au.ibm.com (unknown [9.192.253.14]) by d06av25.portsmouth.uk.ibm.com (Postfix) with ESMTP; Tue, 5 Dec 2017 00:56:05 +0000 (GMT) Received: from camb691.ozlabs.ibm.com (haven.au.ibm.com [9.192.254.114]) (using TLSv1.2 with cipher DHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by ozlabs.au.ibm.com (Postfix) with ESMTPSA id ADDB7A03A5; Tue, 5 Dec 2017 12:01:34 +1100 (AEDT) From: Cyril Bur To: skiboot@lists.ozlabs.org Date: Tue, 5 Dec 2017 12:01:13 +1100 X-Mailer: git-send-email 2.15.1 In-Reply-To: <20171205010113.23263-1-cyril.bur@au1.ibm.com> References: <20171205010113.23263-1-cyril.bur@au1.ibm.com> X-TM-AS-GCONF: 00 x-cbid: 17120501-0008-0000-0000-000004B35EBA X-IBM-AV-DETECTION: SAVI=unused REMOTE=unused XFE=unused x-cbparentid: 17120501-0009-0000-0000-00001E465094 Message-Id: <20171205010113.23263-12-cyril.bur@au1.ibm.com> X-Proofpoint-Virus-Version: vendor=fsecure engine=2.50.10432:, , definitions=2017-12-04_07:, , signatures=0 X-Proofpoint-Spam-Details: rule=outbound_notspam policy=outbound score=0 priorityscore=1501 malwarescore=0 suspectscore=4 phishscore=0 bulkscore=0 spamscore=0 clxscore=1015 lowpriorityscore=0 impostorscore=0 adultscore=0 classifier=spam adjust=0 reason=mlx scancount=1 engine=8.0.1-1709140000 definitions=main-1712050012 Subject: [Skiboot] [PATCH v3 11/11] libflash/test: Add tests for mbox-flash X-BeenThere: skiboot@lists.ozlabs.org X-Mailman-Version: 2.1.24 Precedence: list List-Id: Mailing list for skiboot development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: alistair@popple.id.au MIME-Version: 1.0 Errors-To: skiboot-bounces+incoming=patchwork.ozlabs.org@lists.ozlabs.org Sender: "Skiboot" A first basic set of tests for mbox-flash. These tests do their testing by stubbing out or otherwise replacing functions not in libflash/mbox-flash.c. The stubbed out version of the function can then be used to emulate a BMC mbox daemon talking to back to the code in mbox-flash and it can ensure that there is some adherence to the protocol and that from a blocklevel api point of view the world appears sane. This makes these tests simple to run and they have been integrated into `make check`. The down side is that these tests rely on duplicated feature incomplete BMC daemon behaviour. Therefore these tests are a strong indicator of broken behaviour but a very unreliable indicator of correctness. Full integration tests with a 'real' BMC daemon are probably beyond the scope of this repository. Signed-off-by: Cyril Bur --- .gitignore | 1 + include/skiboot.h | 2 + libflash/mbox-flash.c | 2 + libflash/test/Makefile.check | 17 +- libflash/test/mbox-server.c | 499 +++++++++++++++++++++++++++++++++++++++++++ libflash/test/mbox-server.h | 10 + libflash/test/stubs.c | 78 ++++++- libflash/test/stubs.h | 27 +++ libflash/test/test-mbox.c | 341 +++++++++++++++++++++++++++++ 9 files changed, 970 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 2c4635cd..df85870d 100644 --- a/.gitignore +++ b/.gitignore @@ -81,6 +81,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 db913258..a31cb9d3 100644 --- a/include/skiboot.h +++ b/include/skiboot.h @@ -146,6 +146,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) { @@ -160,6 +161,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 8af2251a..e15fecf0 100644 --- a/libflash/mbox-flash.c +++ b/libflash/mbox-flash.c @@ -34,8 +34,10 @@ #include #ifndef __SKIBOOT__ +#ifndef __TEST__ #error "This libflash backend must be compiled with skiboot" #endif +#endif /* Same technique as BUILD_BUG_ON from linux */ #define CHECK_HANDLER_SIZE(handlers) ((void)sizeof(char[1 - 2*!!(ARRAY_SIZE(handlers) != (MBOX_COMMAND_COUNT + 1))])) diff --git a/libflash/test/Makefile.check b/libflash/test/Makefile.check index 1f92b9d2..a50f47ce 100644 --- a/libflash/test/Makefile.check +++ b/libflash/test/Makefile.check @@ -1,5 +1,7 @@ # -*-Makefile-*- -LIBFLASH_TEST := libflash/test/test-flash libflash/test/test-ecc libflash/test/test-blocklevel +TEST_FLAGS = -D__TEST__ + +LIBFLASH_TEST := libflash/test/test-flash libflash/test/test-ecc libflash/test/test-blocklevel libflash/test/test-mbox LCOV_EXCLUDE += $(LIBFLASH_TEST:%=%.c) @@ -10,19 +12,22 @@ libflash-coverage: $(LIBFLASH_TEST:%=%-gcov-run) check: libflash-check libc-coverage coverage: libflash-coverage +strict-check: TEST_FLAGS += -D__STRICT_TEST__ +strict-check: check + $(LIBFLASH_TEST:%=%-gcov-run) : %-run: % $(call QTEST, TEST-COVERAGE ,$< , $<) $(LIBFLASH_TEST:%=%-check) : %-check: % $(call QTEST, RUN-TEST ,$(VALGRIND) $<, $<) -libflash/test/stubs.o: libflash/test/stubs.c - $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) -g -c -o $@ $<, $<) - -$(LIBFLASH_TEST) : libflash/test/stubs.o libflash/libflash.c libflash/ecc.c libflash/blocklevel.c +LIBFLASH_TEST_EXTRA := libflash/test/stubs.o libflash/test/mbox-server.o +$(LIBFLASH_TEST_EXTRA) : %.o : %.c + $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) $(TEST_FLAGS) -Wno-suggest-attribute=const -g -c -o $@ $<, $<) +$(LIBFLASH_TEST) : libflash/libflash.c libflash/ecc.c libflash/blocklevel.c $(LIBFLASH_TEST_EXTRA) $(LIBFLASH_TEST) : % : %.c - $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) -O0 -g -I include -I . -o $@ $< libflash/test/stubs.o, $<) + $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) $(TEST_FLAGS) -Wno-suggest-attribute=const -O0 -g -I include -I . -o $@ $< $(LIBFLASH_TEST_EXTRA), $<) $(LIBFLASH_TEST:%=%-gcov): %-gcov : %.c % $(call Q, HOSTCC ,$(HOSTCC) $(HOSTCFLAGS) $(HOSTGCOVCFLAGS) -I include -I . -o $@ $< libflash/test/stubs.o, $<) diff --git a/libflash/test/mbox-server.c b/libflash/test/mbox-server.c new file mode 100644 index 00000000..e0357883 --- /dev/null +++ b/libflash/test/mbox-server.c @@ -0,0 +1,499 @@ +/* Copyright 2017 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include /* for mprotect() */ + +#define pr_fmt(fmt) "MBOX-SERVER: " fmt +#include "skiboot.h" +#include "opal-api.h" + +#include "mbox-server.h" +#include "stubs.h" + +#define ERASE_GRANULE 0x100 + +#define LPC_BLOCKS 256 + +#define __unused __attribute__((unused)) + +enum win_type { + WIN_CLOSED, + WIN_READ, + WIN_WRITE +}; + +typedef void (*mbox_data_cb)(struct bmc_mbox_msg *msg, void *priv); +typedef void (*mbox_attn_cb)(uint8_t reg, void *priv); + +struct { + mbox_data_cb fn; + void *cb_data; + struct bmc_mbox_msg *msg; + mbox_attn_cb attn; + void *cb_attn; +} mbox_data; + +static struct { + int api; + bool reset; + + void *lpc_base; + size_t lpc_size; + + uint8_t attn_reg; + + uint32_t block_shift; + uint32_t erase_granule; + + uint16_t def_read_win; /* default window size in blocks */ + uint16_t def_write_win; + + uint16_t max_read_win; /* max window size in blocks */ + uint16_t max_write_win; + + enum win_type win_type; + uint32_t win_base; + uint32_t win_size; + bool win_dirty; +} server_state; + + +static bool check_window(uint32_t pos, uint32_t size) +{ + /* If size is zero then all is well */ + if (size == 0) + return true; + + if (server_state.api == 1) { + /* + * Can actually be stricter in v1 because pos is relative to + * flash not window + */ + if (pos < server_state.win_base || + pos + size > server_state.win_base + server_state.win_size) { + fprintf(stderr, "pos: 0x%08x size: 0x%08x aren't in active window\n", + pos, size); + fprintf(stderr, "window pos: 0x%08x window size: 0x%08x\n", + server_state.win_base, server_state.win_size); + return false; + } + } else { + if (pos + size > server_state.win_base + server_state.win_size) + return false; + } + return true; +} + +/* skiboot test stubs */ +int64_t lpc_read(enum OpalLPCAddressType __unused addr_type, uint32_t addr, + uint32_t *data, uint32_t sz); +int64_t lpc_read(enum OpalLPCAddressType __unused addr_type, uint32_t addr, + uint32_t *data, uint32_t sz) +{ + /* Let it read from a write window... Spec says it ok! */ + if (!check_window(addr, sz) || server_state.win_type == WIN_CLOSED) + return 1; + memcpy(data, server_state.lpc_base + addr, sz); + return 0; +} + +int64_t lpc_write(enum OpalLPCAddressType __unused addr_type, uint32_t addr, + uint32_t data, uint32_t sz); +int64_t lpc_write(enum OpalLPCAddressType __unused addr_type, uint32_t addr, + uint32_t data, uint32_t sz) +{ + if (!check_window(addr, sz) || server_state.win_type != WIN_WRITE) + return 1; + memcpy(server_state.lpc_base + addr, &data, sz); + return 0; +} + +int bmc_mbox_register_attn(mbox_attn_cb handler, void *drv_data) +{ + mbox_data.attn = handler; + mbox_data.cb_attn = drv_data; + + return 0; +} + +uint8_t bmc_mbox_get_attn_reg(void) +{ + return server_state.attn_reg; +} + +int bmc_mbox_register_callback(mbox_data_cb handler, void *drv_data) +{ + mbox_data.fn = handler; + mbox_data.cb_data = drv_data; + + return 0; +} + +static int close_window(bool check) +{ + /* + * This isn't strictly prohibited and some daemons let you close + * windows even if none are open. + * I've made the test fail because closing with no windows open is + * a sign that something 'interesting' has happened. + * You should investigate why + * + * If check is false it is because we just want to do the logic + * because open window has been called - you can open a window + * over a closed window obviously + */ + if (check && server_state.win_type == WIN_CLOSED) + return MBOX_R_PARAM_ERROR; + + server_state.win_type = WIN_CLOSED; + mprotect(server_state.lpc_base, server_state.lpc_size, PROT_NONE); + + return MBOX_R_SUCCESS; +} + +static int do_dirty(uint32_t pos, uint32_t size) +{ + pos <<= server_state.block_shift; + if (server_state.api > 1) + size <<= server_state.block_shift; + if (!check_window(pos, size)) { + prlog(PR_ERR, "Trying to dirty not in open window range\n"); + return MBOX_R_PARAM_ERROR; + } + if (server_state.win_type != WIN_WRITE) { + prlog(PR_ERR, "Trying to dirty not write window\n"); + return MBOX_R_PARAM_ERROR; + } + + /* Thats about all actually */ + return MBOX_R_SUCCESS; +} + +void check_timers(bool __unused unused) +{ + /* now that we've handled the message, holla-back */ + if (mbox_data.msg) { + mbox_data.fn(mbox_data.msg, mbox_data.cb_data); + mbox_data.msg = NULL; + } +} + +static int open_window(struct bmc_mbox_msg *msg, bool write, u32 offset, u32 size) +{ + int max_size = server_state.max_read_win << server_state.block_shift; + //int win_size = server_state.def_read_win; + enum win_type type = WIN_READ; + int prot = PROT_READ; + + assert(server_state.win_type == WIN_CLOSED); + + /* Shift params up */ + offset <<= server_state.block_shift; + size <<= server_state.block_shift; + + if (!size || server_state.api == 1) + size = server_state.def_read_win << server_state.block_shift; + + if (write) { + max_size = server_state.max_write_win << server_state.block_shift; + //win_size = server_state.def_write_win; + prot |= PROT_WRITE; + type = WIN_WRITE; + /* Use the default size if zero size is set */ + if (!size || server_state.api == 1) + size = server_state.def_write_win << server_state.block_shift; + } + + + prlog(PR_INFO, "Opening range %#.8x, %#.8x for %s\n", + offset, offset + size - 1, write ? "writing" : "reading"); + + /* XXX: Document this behaviour */ + if ((size + offset) > server_state.lpc_size) { + prlog(PR_INFO, "tried to open beyond end of flash\n"); + return MBOX_R_PARAM_ERROR; + } + + /* XXX: should we do this before or after checking for errors? + * Doing it afterwards ensures consistency between + * implementations + */ + if (server_state.api == 2) + size = MIN(size, max_size); + + mprotect(server_state.lpc_base + offset, size, prot); + server_state.win_type = type; + server_state.win_base = offset; + server_state.win_size = size; + + memset(msg->args, 0, sizeof(msg->args)); + bmc_put_u16(msg, 0, offset >> server_state.block_shift); + if (server_state.api == 1) { + /* + * Put nonsense in here because v1 mbox-flash shouldn't know about it. + * If v1 mbox-flash does read this, 0xffff should trigger a big mistake. + */ + bmc_put_u16(msg, 2, 0xffff >> server_state.block_shift); + bmc_put_u16(msg, 4, 0xffff >> server_state.block_shift); + } else { + bmc_put_u16(msg, 2, size >> server_state.block_shift); + bmc_put_u16(msg, 4, offset >> server_state.block_shift); + } + return MBOX_R_SUCCESS; +} + +int bmc_mbox_enqueue(struct bmc_mbox_msg *msg, + unsigned int __unused timeout_sec) +{ + /* + * FIXME: should we be using the same storage for message + * and response? + */ + int rc = MBOX_R_SUCCESS; + uint32_t start, size; + + if (server_state.reset && msg->command != MBOX_C_GET_MBOX_INFO && + msg->command != MBOX_C_BMC_EVENT_ACK) { + /* + * Real daemons should return an error, but for testing we'll + * be a bit more strict + */ + prlog(PR_EMERG, "Server was in reset state - illegal command %d\n", + msg->command); + exit(1); + } + + switch (msg->command) { + case MBOX_C_RESET_STATE: + prlog(PR_INFO, "RESET_STATE\n"); + rc = open_window(msg, false, 0, LPC_BLOCKS); + memset(msg->args, 0, sizeof(msg->args)); + break; + + case MBOX_C_GET_MBOX_INFO: + prlog(PR_INFO, "GET_MBOX_INFO version = %d, block_shift = %d\n", + server_state.api, server_state.block_shift); + msg->args[0] = server_state.api; + if (server_state.api == 1) { + prlog(PR_INFO, "\tread_size = 0x%08x, write_size = 0x%08x\n", + server_state.def_read_win, server_state.def_write_win); + bmc_put_u16(msg, 1, server_state.def_read_win); + bmc_put_u16(msg, 3, server_state.def_write_win); + msg->args[5] = 0xff; /* If v1 reads this, 0xff will force the mistake */ + } else { + msg->args[5] = server_state.block_shift; + } + server_state.reset = false; + break; + + case MBOX_C_GET_FLASH_INFO: + prlog(PR_INFO, "GET_FLASH_INFO: size: 0x%" PRIu64 ", erase: 0x%08x\n", + server_state.lpc_size, server_state.erase_granule); + if (server_state.api == 1) { + bmc_put_u32(msg, 0, server_state.lpc_size); + bmc_put_u32(msg, 4, server_state.erase_granule); + } else { + bmc_put_u16(msg, 0, server_state.lpc_size >> server_state.block_shift); + bmc_put_u16(msg, 2, server_state.erase_granule >> server_state.block_shift); + } + break; + + case MBOX_C_CREATE_READ_WINDOW: + start = bmc_get_u16(msg, 0); + size = bmc_get_u16(msg, 2); + prlog(PR_INFO, "CREATE_READ_WINDOW: pos: 0x%08x, len: 0x%08x\n", start, size); + rc = close_window(false); + if (rc != MBOX_R_SUCCESS) + break; + rc = open_window(msg, false, start, size); + break; + + case MBOX_C_CLOSE_WINDOW: + rc = close_window(true); + break; + + case MBOX_C_CREATE_WRITE_WINDOW: + start = bmc_get_u16(msg, 0); + size = bmc_get_u16(msg, 2); + prlog(PR_INFO, "CREATE_WRITE_WINDOW: pos: 0x%08x, len: 0x%08x\n", start, size); + rc = close_window(false); + if (rc != MBOX_R_SUCCESS) + break; + rc = open_window(msg, true, start, size); + break; + + /* TODO: make these do something */ + case MBOX_C_WRITE_FLUSH: + prlog(PR_INFO, "WRITE_FLUSH\n"); + /* + * This behaviour isn't strictly illegal however it could + * be a sign of bad behaviour + */ + if (server_state.api > 1 && !server_state.win_dirty) { + prlog(PR_EMERG, "Version >1 called FLUSH without a previous DIRTY\n"); + exit (1); + } + server_state.win_dirty = false; + if (server_state.api > 1) + break; + + /* This is only done on V1 */ + start = bmc_get_u16(msg, 0); + if (server_state.api == 1) + size = bmc_get_u32(msg, 2); + else + size = bmc_get_u16(msg, 2); + prlog(PR_INFO, "\tpos: 0x%08x len: 0x%08x\n", start, size); + rc = do_dirty(start, size); + break; + case MBOX_C_MARK_WRITE_DIRTY: + start = bmc_get_u16(msg, 0); + if (server_state.api == 1) + size = bmc_get_u32(msg, 2); + else + size = bmc_get_u16(msg, 2); + prlog(PR_INFO, "MARK_WRITE_DIRTY: pos: 0x%08x, len: %08x\n", start, size); + server_state.win_dirty = true; + rc = do_dirty(start, size); + break; + case MBOX_C_BMC_EVENT_ACK: + /* + * Clear any BMC notifier flags. Don't clear the server + * reset state here, it is a permitted command but only + * GET_INFO should clear it. + * + * Make sure that msg->args[0] is only acking bits we told + * it about, in server_state.attn_reg. The caveat is that + * it could NOT ack some bits... + */ + prlog(PR_INFO, "BMC_EVENT_ACK 0x%02x\n", msg->args[0]); + if ((msg->args[0] | server_state.attn_reg) != server_state.attn_reg) { + prlog(PR_EMERG, "Tried to ack bits we didn't say!\n"); + exit(1); + } + msg->bmc &= ~msg->args[0]; + server_state.attn_reg &= ~msg->args[0]; + break; + case MBOX_C_MARK_WRITE_ERASED: + start = bmc_get_u16(msg, 0) << server_state.block_shift; + size = bmc_get_u16(msg, 2) << server_state.block_shift; + /* If we've negotiated v1 this should never be called */ + if (server_state.api == 1) { + prlog(PR_EMERG, "Version 1 protocol called a V2 only command\n"); + exit(1); + } + /* + * This will likely result in flush (but not + * dirty) being called. This is the point. + */ + server_state.win_dirty = true; + /* This should really be done when they call flush */ + memset(server_state.lpc_base + server_state.win_base + start, 0xff, size); + break; + default: + prlog(PR_EMERG, "Got unknown command code from mbox: %d\n", msg->command); + } + + prerror("command response = %d\n", rc); + msg->response = rc; + + mbox_data.msg = msg; + + return 0; +} + +int mbox_server_memcmp(int off, const void *buf, size_t len) +{ + return memcmp(server_state.lpc_base + off, buf, len); +} + +void mbox_server_memset(int c) +{ + memset(server_state.lpc_base, c, server_state.lpc_size); +} + +uint32_t mbox_server_total_size(void) +{ + /* Not actually but for this server we don't differentiate */ + return server_state.lpc_size; +} + +uint32_t mbox_server_erase_granule(void) +{ + return server_state.erase_granule; +} + +int mbox_server_version(void) +{ + return server_state.api; +} + +int mbox_server_reset(unsigned int version, uint8_t block_shift) +{ + if (version > 3) + return 1; + + server_state.api = version; + if (block_shift) + server_state.block_shift = block_shift; + if (server_state.erase_granule < (1 << server_state.block_shift)) + server_state.erase_granule = 1 << server_state.block_shift; + server_state.lpc_size = LPC_BLOCKS * (1 << server_state.block_shift); + free(server_state.lpc_base); + server_state.lpc_base = malloc(server_state.lpc_size); + server_state.attn_reg = MBOX_ATTN_BMC_REBOOT | MBOX_ATTN_BMC_DAEMON_READY; + server_state.win_type = WIN_CLOSED; + server_state.reset = true; + mbox_data.attn(MBOX_ATTN_BMC_REBOOT, mbox_data.cb_attn); + + return 0; +} + +int mbox_server_init(void) +{ + server_state.api = 1; + server_state.reset = true; + + /* We're always ready! */ + server_state.attn_reg = MBOX_ATTN_BMC_DAEMON_READY; + + /* setup server */ + server_state.block_shift = 12; + server_state.erase_granule = 0x1000; + server_state.lpc_size = LPC_BLOCKS * (1 << server_state.block_shift); + server_state.lpc_base = malloc(server_state.lpc_size); + + server_state.def_read_win = 1; /* These are in units of block shift "= 1 is 4K" */ + server_state.def_write_win = 1; /* These are in units of block shift "= 1 is 4K" */ + + server_state.max_read_win = LPC_BLOCKS; + server_state.max_write_win = LPC_BLOCKS; + server_state.win_type = WIN_CLOSED; + + return 0; +} + +void mbox_server_destroy(void) +{ + free(server_state.lpc_base); +} diff --git a/libflash/test/mbox-server.h b/libflash/test/mbox-server.h new file mode 100644 index 00000000..0a961ba9 --- /dev/null +++ b/libflash/test/mbox-server.h @@ -0,0 +1,10 @@ +#include + +uint32_t mbox_server_total_size(void); +uint32_t mbox_server_erase_granule(void); +int mbox_server_version(void); +void mbox_server_memset(int c); +int mbox_server_memcmp(int off, const void *buf, size_t len); +int mbox_server_reset(unsigned int version, uint8_t block_shift); +int mbox_server_init(void); +void mbox_server_destroy(void); diff --git a/libflash/test/stubs.c b/libflash/test/stubs.c index aabf018d..e09c8f57 100644 --- a/libflash/test/stubs.c +++ b/libflash/test/stubs.c @@ -1,4 +1,4 @@ -/* Copyright 2013-2014 IBM Corp. +/* Copyright 2013-2017 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,3 +14,79 @@ * limitations under the License. */ /* Stubs for libflash test */ +#include +#include +#include +#include +#include +#include +#include /* for usleep */ + +#include "../../include/lpc-mbox.h" +#include "stubs.h" + +#define __unused __attribute__((unused)) + +__attribute__((weak)) void check_timers(bool __unused unused) +{ + return; +} + +void time_wait_ms(unsigned long ms) +{ + usleep(ms * 1000); +} + +/* skiboot stubs */ +unsigned long mftb(void) +{ + return 42; +} +unsigned long tb_hz = 512000000ul; + +void _prlog(int __unused log_level, const char* fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +/* accessor junk */ + +void bmc_put_u16(struct bmc_mbox_msg *msg, int offset, uint16_t data) +{ + msg->args[offset + 0] = data & 0xff; + msg->args[offset + 1] = data >> 8; +} + +void bmc_put_u32(struct bmc_mbox_msg *msg, int offset, uint32_t data) +{ + msg->args[offset + 0] = (data) & 0xff; + msg->args[offset + 1] = (data >> 8) & 0xff; + msg->args[offset + 2] = (data >> 16) & 0xff; + msg->args[offset + 3] = (data >> 24) & 0xff; +} + +u32 bmc_get_u32(struct bmc_mbox_msg *msg, int offset) +{ + u32 data = 0; + + data |= msg->args[offset + 0]; + data |= msg->args[offset + 1] << 8; + data |= msg->args[offset + 2] << 16; + data |= msg->args[offset + 3] << 24; + + return data; +} + +u16 bmc_get_u16(struct bmc_mbox_msg *msg, int offset) +{ + u16 data = 0; + + data |= msg->args[offset + 0]; + data |= msg->args[offset + 1] << 8; + + return data; +} diff --git a/libflash/test/stubs.h b/libflash/test/stubs.h new file mode 100644 index 00000000..bc6d3f38 --- /dev/null +++ b/libflash/test/stubs.h @@ -0,0 +1,27 @@ +/* Copyright 2013-2017 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include + +#include "../../include/lpc-mbox.h" + +void check_timers(bool unused); +void time_wait_ms(unsigned long ms); +unsigned long mftb(void); +void _prlog(int log_level, const char* fmt, ...); +void bmc_put_u16(struct bmc_mbox_msg *msg, int offset, uint16_t data); +void bmc_put_u32(struct bmc_mbox_msg *msg, int offset, uint32_t data); +u16 bmc_get_u16(struct bmc_mbox_msg *msg, int offset); +u32 bmc_get_u32(struct bmc_mbox_msg *msg, int offset); diff --git a/libflash/test/test-mbox.c b/libflash/test/test-mbox.c new file mode 100644 index 00000000..b148483a --- /dev/null +++ b/libflash/test/test-mbox.c @@ -0,0 +1,341 @@ +/* Copyright 2017 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "stubs.h" +#include "mbox-server.h" + +#define zalloc(n) calloc(1, n) +#define __unused __attribute__((unused)) + +#undef pr_fmt + +#include "../libflash.c" +#include "../mbox-flash.c" +#include "../ecc.c" +#include "../blocklevel.c" + +#undef pr_fmt +#define pr_fmt(fmt) "MBOX-PROXY: " fmt + +/* client interface */ + +#include "../../include/lpc-mbox.h" + +#define ERR(...) FL_DBG(__VA_ARGS__) + +static int run_flash_test(struct blocklevel_device *bl) +{ + struct mbox_flash_data *mbox_flash; + char hello[] = "Hello World"; + uint32_t erase_granule; + uint64_t total_size; + const char *name; + uint16_t *test; + char *tmp; + int i, rc; + + mbox_flash = container_of(bl, struct mbox_flash_data, bl); + + /* + * Do something first so that if it has been reset it does that + * before we check versions + */ + rc = blocklevel_get_info(bl, &name, &total_size, &erase_granule); + if (rc) { + ERR("blocklevel_get_info() failed with err %d\n", rc); + return 1; + } + if (total_size != mbox_server_total_size()) { + ERR("Total flash size is incorrect: 0x%08lx vs 0x%08x\n", + total_size, mbox_server_total_size()); + return 1; + } + if (erase_granule != mbox_server_erase_granule()) { + ERR("Erase granule is incorrect 0x%08x vs 0x%08x\n", + erase_granule, mbox_server_erase_granule()); + return 1; + } + + + /* Sanity check that mbox_flash has inited correctly */ + if (mbox_flash->version != mbox_server_version()) { + ERR("MBOX Flash didn't agree with the server version\n"); + return 1; + } + if (mbox_flash->version == 1 && mbox_flash->shift != 12) { + ERR("MBOX Flash version 1 isn't using a 4K shift\n"); + return 1; + } + + mbox_server_memset(0xff); + + test = calloc(erase_granule * 20, 1); + + /* Make up a test pattern */ + for (i = 0; i < erase_granule * 10; i++) + test[i] = i; + + /* Write 64k of stuff at 0 and at 128k */ + printf("Writing test patterns...\n"); + rc = blocklevel_write(bl, 0, test, erase_granule * 10); + if (rc) { + ERR("blocklevel_write(0, erase_granule * 10) failed with err %d\n", rc); + return 1; + } + rc = blocklevel_write(bl, erase_granule * 20, test, erase_granule * 10); + if (rc) { + ERR("blocklevel_write(0x20000, 0x10000) failed with err %d\n", rc); + return 1; + } + + if (mbox_server_memcmp(0, test, erase_granule * 10)) { + ERR("Test pattern mismatch !\n"); + return 1; + } + + /* Write "Hello world" straddling the 64k boundary */ + printf("Writing test string...\n"); + rc = blocklevel_write(bl, (erase_granule * 10) - 8, hello, sizeof(hello)); + if (rc) { + ERR("blocklevel_write(0xfffc, %s, %lu) failed with err %d\n", + hello, sizeof(hello), rc); + return 1; + } + + /* Check result */ + if (mbox_server_memcmp((erase_granule * 10) - 8, hello, sizeof(hello))) { + ERR("Test string mismatch!\n"); + return 1; + } + + /* Erase granule is something but never 0x50, this shouldn't succeed */ + rc = blocklevel_erase(bl, 0, 0x50); + if (!rc) { + ERR("blocklevel_erase(0, 0x50) didn't fail!\n"); + return 1; + } + + /* Check it didn't silently erase */ + if (mbox_server_memcmp(0, test, (erase_granule * 10) - 8)) { + ERR("Test pattern mismatch !\n"); + return 1; + } + + /* + * For v1 protocol this should NOT call MARK_WRITE_ERASED! + * The server MARK_WRITE_ERASED will call exit(1) if it gets a + * MARK_WRITE_ERASED and version == 1 + */ + rc = blocklevel_erase(bl, 0, erase_granule); + if (rc) { + ERR("blocklevel_erase(0, erase_granule) failed with err %d\n", rc); + return 1; + } + + /* + * Version 1 doesn't specify that the buffer actually becomes 0xff + * It is up to the daemon to do what it wants really - there are + * implementations that do nothing but writes to the same region + * work fine + */ + + /* This check is important for v2 */ + /* Check stuff got erased */ + tmp = malloc(erase_granule * 2); + if (!tmp) { + ERR("malloc failed\n"); + return 1; + } + if (mbox_server_version() > 1) { + memset(tmp, 0xff, erase_granule); + if (mbox_server_memcmp(0, tmp, erase_granule)) { + ERR("Buffer not erased\n"); + rc = 1; + goto out; + } + } + + /* Read beyond the end of flash */ + rc = blocklevel_read(bl, total_size, tmp, 0x1000); + if (!rc) { + ERR("blocklevel_read(total_size, 0x1000) (read beyond the end) succeeded\n"); + goto out; + } + + /* Test some simple write/read cases, avoid first page */ + rc = blocklevel_write(bl, erase_granule * 2, test, erase_granule / 2); + if (rc) { + ERR("blocklevel_write(erase_granule, erase_granule / 2) failed with err %d\n", rc); + goto out; + } + rc = blocklevel_write(bl, erase_granule * 2 + erase_granule / 2, test, erase_granule / 2); + if (rc) { + ERR("blocklevel_write(erase_granule * 2 + erase_granule / 2, erase_granule) failed with err %d\n", rc); + goto out; + } + + rc = mbox_server_memcmp(erase_granule * 2, test, erase_granule / 2); + if (rc) { + ERR("%s:%d mbox_server_memcmp miscompare\n", __FILE__, __LINE__); + goto out; + } + rc = mbox_server_memcmp(erase_granule * 2 + erase_granule / 2, test, erase_granule / 2); + if (rc) { + ERR("%s:%d mbox_server_memcmp miscompare\n", __FILE__, __LINE__); + goto out; + } + + /* Great so the writes made it, can we read them back? Do it in + * four small reads */ + for (i = 0; i < 4; i++) { + rc = blocklevel_read(bl, erase_granule * 2 + (i * erase_granule / 4), tmp + (i * erase_granule / 4), erase_granule / 4); + if (rc) { + ERR("blocklevel_read(0x%08x, erase_granule / 4) failed with err %d\n", + 2 * erase_granule + (i * erase_granule / 4), rc); + goto out; + } + } + rc = memcmp(test, tmp, erase_granule / 2); + if (rc) { + ERR("%s:%d read back miscompare\n", __FILE__, __LINE__); + goto out; + } + rc = memcmp(test, tmp + erase_granule / 2, erase_granule / 2); + if (rc) { + ERR("%s:%d read back miscompare\n", __FILE__, __LINE__); + goto out; + } + + /* + * Make sure we didn't corrupt other stuff, also make sure one + * blocklevel call will understand how to read from two windows + */ + for (i = 3; i < 9; i = i + 2) { + printf("i:%d erase: 0x%08x\n", i, erase_granule); + rc = blocklevel_read(bl, i * erase_granule, tmp, 2 * erase_granule); + if (rc) { + ERR("blocklevel_read(0x%08x, 2 * erase_granule) failed with err: %d\n", i * erase_granule, rc); + goto out; + } + rc = memcmp(((char *)test) + (i * erase_granule), tmp, 2 * erase_granule); + if (rc) { + ERR("%s:%d read back miscompare (pos: 0x%08x)\n", __FILE__, __LINE__, i * erase_granule); + goto out; + } + } + + srand(1); + /* + * Try to jump around the place doing a tonne of small reads. + * Worth doing the same with writes TODO + */ +#ifdef __STRICT_TEST__ + for (i = 0; i < 1000; i++) { +#else + for (i = 0; i < 100; i++) { +#endif + int r = rand(); + + printf("Loop %d of 1000\n", i); + /* Avoid reading too far, just skip it */ + if ((r % erase_granule * 10) + (r % erase_granule * 2) > erase_granule * 10) + continue; + + rc = blocklevel_read(bl, erase_granule * 20 + (r % erase_granule * 10), tmp, r % erase_granule * 2); + if (rc) { + ERR("blocklevel_read(0x%08x, 0x%08x) failed with err %d\n", 0x20000 + (r % 0x100000), r % 0x2000, rc); + goto out; + } + rc = memcmp(((char *)test) + (r % erase_granule * 10), tmp, r % erase_granule * 2); + if (rc) { + ERR("%s:%d read back miscompare (pos: 0x%08x)\n", __FILE__, __LINE__, 0x20000 + (r % 0x10000)); + goto out; + } + } +out: + free(tmp); + return rc; +} + +int main(void) +{ + struct blocklevel_device *bl; + int rc; + + libflash_debug = true; + + mbox_server_init(); + +#ifdef __STRICT_TEST__ + printf("Found __STRICT_TEST__, this may take time time.\n"); +#else + printf("__STRICT_TEST__ not found, use make strict-check for a more\n"); + printf("thorough test, it will take significantly longer.\n"); +#endif + + printf("Doing mbox-flash V1 tests\n"); + + /* run test */ + mbox_flash_init(&bl); + rc = run_flash_test(bl); + if (rc) + goto out; + /* + * Trick mbox-flash into thinking there was a reboot so we can + * switch to v2 + */ + + printf("Doing mbox-flash V2 tests\n"); + + mbox_server_reset(2, 12); + + /* Do all the tests again */ + rc = run_flash_test(bl); + if (rc) + goto out; + + mbox_server_reset(2, 17); + + /* Do all the tests again */ + rc = run_flash_test(bl); + if (rc) + goto out; + + + printf("Doing mbox-flash V3 tests\n"); + + mbox_server_reset(3, 20); + + /* Do all the tests again */ + rc = run_flash_test(bl); + + +out: + mbox_flash_exit(bl); + + mbox_server_destroy(); + + return rc; +}