diff options
Diffstat (limited to 'roms/skiboot/libflash/test/mbox-server.c')
-rw-r--r-- | roms/skiboot/libflash/test/mbox-server.c | 514 |
1 files changed, 514 insertions, 0 deletions
diff --git a/roms/skiboot/libflash/test/mbox-server.c b/roms/skiboot/libflash/test/mbox-server.c new file mode 100644 index 000000000..8a68cfff6 --- /dev/null +++ b/roms/skiboot/libflash/test/mbox-server.c @@ -0,0 +1,514 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2017 IBM Corp. */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <stdarg.h> +#include <inttypes.h> + +#include <sys/mman.h> /* for mprotect() */ + +#define pr_fmt(fmt) "MBOX-SERVER: " fmt +#include "skiboot.h" +#include "opal-api.h" + +#include "mbox-server.h" +#include "stubs.h" + +#define ERASE_GRANULE 0x100 + +#define LPC_BLOCKS 256 + +#define __unused __attribute__((unused)) + +enum win_type { + WIN_CLOSED, + WIN_READ, + WIN_WRITE +}; + +typedef void (*mbox_data_cb)(struct bmc_mbox_msg *msg, void *priv); +typedef void (*mbox_attn_cb)(uint8_t reg, void *priv); + +struct { + mbox_data_cb fn; + void *cb_data; + struct bmc_mbox_msg *msg; + mbox_attn_cb attn; + void *cb_attn; +} mbox_data; + +static struct { + int api; + bool reset; + + void *lpc_base; + size_t lpc_size; + + uint8_t attn_reg; + + uint32_t block_shift; + uint32_t erase_granule; + + uint16_t def_read_win; /* default window size in blocks */ + uint16_t def_write_win; + + uint16_t max_read_win; /* max window size in blocks */ + uint16_t max_write_win; + + enum win_type win_type; + uint32_t win_base; + uint32_t win_size; + bool win_dirty; +} server_state; + + +static bool check_window(uint32_t pos, uint32_t size) +{ + /* If size is zero then all is well */ + if (size == 0) + return true; + + if (server_state.api == 1) { + /* + * Can actually be stricter in v1 because pos is relative to + * flash not window + */ + if (pos < server_state.win_base || + pos + size > server_state.win_base + server_state.win_size) { + fprintf(stderr, "pos: 0x%08x size: 0x%08x aren't in active window\n", + pos, size); + fprintf(stderr, "window pos: 0x%08x window size: 0x%08x\n", + server_state.win_base, server_state.win_size); + return false; + } + } else { + if (pos + size > server_state.win_base + server_state.win_size) + return false; + } + return true; +} + +/* skiboot test stubs */ +int64_t lpc_read(enum OpalLPCAddressType __unused addr_type, uint32_t addr, + uint32_t *data, uint32_t sz); +int64_t lpc_read(enum OpalLPCAddressType __unused addr_type, uint32_t addr, + uint32_t *data, uint32_t sz) +{ + /* Let it read from a write window... Spec says it ok! */ + if (!check_window(addr, sz) || server_state.win_type == WIN_CLOSED) + return 1; + + switch (sz) { + case 1: + *(uint8_t *)data = *(uint8_t *)(server_state.lpc_base + addr); + break; + case 2: + *(uint16_t *)data = be16_to_cpu(*(uint16_t *)(server_state.lpc_base + addr)); + break; + case 4: + *(uint32_t *)data = be32_to_cpu(*(uint32_t *)(server_state.lpc_base + addr)); + break; + default: + prerror("Invalid data size %d\n", sz); + return 1; + } + 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; + switch (sz) { + case 1: + *(uint8_t *)(server_state.lpc_base + addr) = data; + break; + case 2: + *(uint16_t *)(server_state.lpc_base + addr) = cpu_to_be16(data); + break; + case 4: + *(uint32_t *)(server_state.lpc_base + addr) = cpu_to_be32(data); + break; + default: + prerror("Invalid data size %d\n", sz); + return 1; + } + 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"); + server_state.win_type = WIN_CLOSED; + 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); +} |