500 lines
14 KiB
C
500 lines
14 KiB
C
|
/* 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 <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;
|
||
|
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);
|
||
|
}
|