blob: 053be989497ae0e338d71ae24ccc3db862607bfd [file] [log] [blame]
// 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;
}
int64_t lpc_fw_read(uint32_t off, void *buf, uint32_t len);
int64_t lpc_fw_read(uint32_t off, void *buf, uint32_t len)
{
int rc;
while (len) {
uint32_t chunk;
uint32_t dat;
/* XXX: make this read until it's aligned */
if (len > 3 && !(off & 3)) {
rc = lpc_read(OPAL_LPC_FW, off, &dat, 4);
if (!rc) {
/*
* lpc_read swaps to CPU endian but it's not
* really a 32-bit value, so convert back.
*/
*(__be32 *)buf = cpu_to_be32(dat);
}
chunk = 4;
} else {
rc = lpc_read(OPAL_LPC_FW, off, &dat, 1);
if (!rc)
*(uint8_t *)buf = dat;
chunk = 1;
}
if (rc)
return rc;
len -= chunk;
off += chunk;
buf += chunk;
}
return 0;
}
int64_t lpc_fw_write(uint32_t off, const void *buf, uint32_t len);
int64_t lpc_fw_write(uint32_t off, const void *buf, uint32_t len)
{
int rc;
while (len) {
uint32_t chunk;
if (len > 3 && !(off & 3)) {
/* endian swap: see lpc_window_write */
uint32_t dat = be32_to_cpu(*(__be32 *)buf);
rc = lpc_write(OPAL_LPC_FW, off, dat, 4);
chunk = 4;
} else {
uint8_t dat = *(uint8_t *)buf;
rc = lpc_write(OPAL_LPC_FW, off, dat, 1);
chunk = 1;
}
if (rc)
return rc;
len -= chunk;
off += chunk;
buf += chunk;
}
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);
}