1178 lines
31 KiB
C
1178 lines
31 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.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "MBOX-FLASH: " fmt
|
|
|
|
#define _GNU_SOURCE
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <skiboot.h>
|
|
#include <inttypes.h>
|
|
#include <timebase.h>
|
|
#include <timer.h>
|
|
#include <libflash/libflash.h>
|
|
#include <libflash/mbox-flash.h>
|
|
#include <lpc.h>
|
|
#include <lpc-mbox.h>
|
|
|
|
#include <ccan/container_of/container_of.h>
|
|
|
|
#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))]))
|
|
|
|
#define MBOX_DEFAULT_TIMEOUT 3 /* seconds */
|
|
|
|
#define MSG_CREATE(init_command) { .command = init_command }
|
|
|
|
struct mbox_flash_data;
|
|
typedef void (mbox_handler)(struct mbox_flash_data *, struct bmc_mbox_msg *);
|
|
|
|
struct lpc_window {
|
|
uint32_t lpc_addr; /* Offset into LPC space */
|
|
uint32_t cur_pos; /* Current position of the window in the flash */
|
|
uint32_t size; /* Size of the window into the flash */
|
|
bool open;
|
|
};
|
|
|
|
struct mbox_flash_data {
|
|
int version;
|
|
uint16_t timeout;
|
|
uint32_t shift;
|
|
struct lpc_window read;
|
|
struct lpc_window write;
|
|
struct blocklevel_device bl;
|
|
uint32_t total_size;
|
|
uint32_t erase_granule;
|
|
int rc;
|
|
bool reboot;
|
|
bool pause;
|
|
bool busy;
|
|
bool ack;
|
|
mbox_handler **handlers;
|
|
};
|
|
|
|
static mbox_handler mbox_flash_do_nop;
|
|
static mbox_handler mbox_flash_do_illegal;
|
|
|
|
/* Version 1, 2, 3 compatible */
|
|
static mbox_handler mbox_flash_do_get_mbox_info;
|
|
|
|
/* Version 2 and 3 compatible */
|
|
static mbox_handler mbox_flash_do_get_flash_info;
|
|
static mbox_handler mbox_flash_do_get_flash_info_v1;
|
|
|
|
/* Version 2 and 3 compatible */
|
|
static mbox_handler mbox_flash_do_create_read_window;
|
|
static mbox_handler mbox_flash_do_create_read_window_v1;
|
|
|
|
/* Version 2 and 3 compatible */
|
|
static mbox_handler mbox_flash_do_create_write_window;
|
|
static mbox_handler mbox_flash_do_create_write_window_v1;
|
|
|
|
/* Version 1, 2, 3 compatible */
|
|
static mbox_handler mbox_flash_do_close_window;
|
|
|
|
/* Plus one, commands start at 1 */
|
|
static mbox_handler *handlers_v3[] = {
|
|
NULL,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_get_mbox_info,
|
|
&mbox_flash_do_get_flash_info,
|
|
&mbox_flash_do_create_read_window,
|
|
&mbox_flash_do_close_window,
|
|
&mbox_flash_do_create_write_window,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_nop
|
|
};
|
|
|
|
/* Plus one, commands start at 1 */
|
|
static mbox_handler *handlers_v2[] = {
|
|
NULL,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_get_mbox_info,
|
|
&mbox_flash_do_get_flash_info,
|
|
&mbox_flash_do_create_read_window,
|
|
&mbox_flash_do_close_window,
|
|
&mbox_flash_do_create_write_window,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_illegal,
|
|
&mbox_flash_do_illegal
|
|
};
|
|
|
|
/*
|
|
* Plus one, commands start at 1.
|
|
* V2 adds a command so there should never be a response for the last
|
|
* command.
|
|
* Ensure we print an error message with mbox_flash_do_illegal().
|
|
*/
|
|
static mbox_handler *handlers_v1[] = {
|
|
NULL,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_get_mbox_info,
|
|
&mbox_flash_do_get_flash_info_v1,
|
|
&mbox_flash_do_create_read_window_v1,
|
|
&mbox_flash_do_close_window,
|
|
&mbox_flash_do_create_write_window_v1,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_nop,
|
|
&mbox_flash_do_illegal,
|
|
&mbox_flash_do_illegal,
|
|
&mbox_flash_do_illegal
|
|
};
|
|
|
|
|
|
static void mbox_flash_callback(struct bmc_mbox_msg *msg, void *priv);
|
|
static void mbox_flash_attn(uint8_t attn, void *priv);
|
|
|
|
static int protocol_init(struct mbox_flash_data *mbox_flash, uint8_t shift);
|
|
|
|
static int lpc_window_read(struct mbox_flash_data *mbox_flash, uint32_t pos,
|
|
void *buf, uint32_t len)
|
|
{
|
|
uint32_t off = mbox_flash->read.lpc_addr + (pos - mbox_flash->read.cur_pos);
|
|
int rc;
|
|
|
|
prlog(PR_TRACE, "Reading at 0x%08x for 0x%08x offset: 0x%08x\n",
|
|
pos, len, off);
|
|
|
|
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)
|
|
*(uint32_t *)buf = dat;
|
|
chunk = 4;
|
|
} else {
|
|
rc = lpc_read(OPAL_LPC_FW, off, &dat, 1);
|
|
if (!rc)
|
|
*(uint8_t *)buf = dat;
|
|
chunk = 1;
|
|
}
|
|
if (rc) {
|
|
prlog(PR_ERR, "lpc_read failure %d to FW 0x%08x\n", rc, off);
|
|
return rc;
|
|
}
|
|
len -= chunk;
|
|
off += chunk;
|
|
buf += chunk;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int lpc_window_write(struct mbox_flash_data *mbox_flash, uint32_t pos,
|
|
const void *buf, uint32_t len)
|
|
{
|
|
uint32_t off = mbox_flash->write.lpc_addr + (pos - mbox_flash->write.cur_pos);
|
|
int rc;
|
|
|
|
|
|
prlog(PR_TRACE, "Writing at 0x%08x for 0x%08x offset: 0x%08x\n",
|
|
pos, len, off);
|
|
|
|
while(len) {
|
|
uint32_t chunk;
|
|
|
|
if (len > 3 && !(off & 3)) {
|
|
rc = lpc_write(OPAL_LPC_FW, off,
|
|
*(uint32_t *)buf, 4);
|
|
chunk = 4;
|
|
} else {
|
|
rc = lpc_write(OPAL_LPC_FW, off,
|
|
*(uint8_t *)buf, 1);
|
|
chunk = 1;
|
|
}
|
|
if (rc) {
|
|
prlog(PR_ERR, "lpc_write failure %d to FW 0x%08x\n", rc, off);
|
|
return rc;
|
|
}
|
|
len -= chunk;
|
|
off += chunk;
|
|
buf += chunk;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint64_t mbox_flash_mask(struct mbox_flash_data *mbox_flash)
|
|
{
|
|
return (1ULL << mbox_flash->shift) - 1;
|
|
}
|
|
|
|
__unused static uint8_t msg_get_u8(struct bmc_mbox_msg *msg, int i)
|
|
{
|
|
return msg->args[i];
|
|
}
|
|
|
|
static void msg_put_u8(struct bmc_mbox_msg *msg, int i, uint8_t val)
|
|
{
|
|
msg->args[i] = val;
|
|
}
|
|
|
|
static uint16_t msg_get_u16(struct bmc_mbox_msg *msg, int i)
|
|
{
|
|
return le16_to_cpu(*(uint16_t *)(&msg->args[i]));
|
|
}
|
|
|
|
static void msg_put_u16(struct bmc_mbox_msg *msg, int i, uint16_t val)
|
|
{
|
|
uint16_t tmp = cpu_to_le16(val);
|
|
memcpy(&msg->args[i], &tmp, sizeof(val));
|
|
}
|
|
|
|
static uint32_t msg_get_u32(struct bmc_mbox_msg *msg, int i)
|
|
{
|
|
return le32_to_cpu(*(uint32_t *)(&msg->args[i]));
|
|
}
|
|
|
|
static void msg_put_u32(struct bmc_mbox_msg *msg, int i, uint32_t val)
|
|
{
|
|
uint32_t tmp = cpu_to_le32(val);
|
|
memcpy(&msg->args[i], &tmp, sizeof(val));
|
|
}
|
|
|
|
static uint32_t blocks_to_bytes(struct mbox_flash_data *mbox_flash, uint16_t blocks)
|
|
{
|
|
return blocks << mbox_flash->shift;
|
|
}
|
|
|
|
static uint16_t bytes_to_blocks(struct mbox_flash_data *mbox_flash,
|
|
uint32_t bytes)
|
|
{
|
|
return bytes >> mbox_flash->shift;
|
|
}
|
|
|
|
/*
|
|
* The BMC may send is an out of band message to say that it doesn't
|
|
* own the flash anymore.
|
|
* It guarantees we can still access our (open) windows but it does
|
|
* not guarantee their contents until it clears the bit without
|
|
* sending us a corresponding bit to say that the windows are bad
|
|
* first.
|
|
* Since this is all things that will happen in the future, we should
|
|
* not perform any calls speculatively as its almost impossible to
|
|
* rewind.
|
|
*/
|
|
static bool is_paused(struct mbox_flash_data *mbox_flash)
|
|
{
|
|
return mbox_flash->pause;
|
|
}
|
|
|
|
/*
|
|
* After a read or a write it is wise to check that the window we just
|
|
* read/write to/from is still valid otherwise it is possible some of
|
|
* the data didn't make it.
|
|
* This check is an optimisation as we'll close all our windows on any
|
|
* notification from the BMC that the windows are bad. See the above
|
|
* comment about is_paused().
|
|
* A foolproof (but much closer) method of validating reads/writes
|
|
* would be to attempt to close the window, if that fails then we can
|
|
* be sure that the read/write was no good.
|
|
*/
|
|
static bool is_valid(struct mbox_flash_data *mbox_flash, struct lpc_window *win)
|
|
{
|
|
return !is_paused(mbox_flash) && win->open;
|
|
}
|
|
|
|
/*
|
|
* Check if we've received a BMC reboot notification.
|
|
* The strategy is to check on entry to mbox-flash and return a
|
|
* failure accordingly. Races will be handled by the fact that the BMC
|
|
* won't respond so timeouts will occur. As an added precaution
|
|
* msg_send() checks right before sending a message (to make the race
|
|
* as small as possible to avoid needless timeouts).
|
|
*/
|
|
static bool is_reboot(struct mbox_flash_data *mbox_flash)
|
|
{
|
|
return mbox_flash->reboot;
|
|
}
|
|
|
|
static int msg_send(struct mbox_flash_data *mbox_flash, struct bmc_mbox_msg *msg,
|
|
unsigned int timeout_sec)
|
|
{
|
|
if (is_reboot(mbox_flash))
|
|
return FLASH_ERR_AGAIN;
|
|
mbox_flash->busy = true;
|
|
mbox_flash->rc = 0;
|
|
return bmc_mbox_enqueue(msg, timeout_sec);
|
|
}
|
|
|
|
static int wait_for_bmc(struct mbox_flash_data *mbox_flash, unsigned int timeout_sec)
|
|
{
|
|
unsigned long last = 1, start = tb_to_secs(mftb());
|
|
prlog(PR_TRACE, "Waiting for BMC\n");
|
|
while (mbox_flash->busy && timeout_sec > last) {
|
|
long now = tb_to_secs(mftb());
|
|
if (now - start > last) {
|
|
if (last < timeout_sec / 2)
|
|
prlog(PR_TRACE, "Been waiting for the BMC for %lu secs\n", last);
|
|
else
|
|
prlog(PR_ERR, "BMC NOT RESPONDING %lu second wait\n", last);
|
|
last++;
|
|
}
|
|
/*
|
|
* Both functions are important.
|
|
* Well time_wait_ms() relaxes the spin... so... its nice
|
|
*/
|
|
check_timers(false);
|
|
if (mbox_flash->busy)
|
|
time_wait_ms(MBOX_DEFAULT_POLL_MS);
|
|
asm volatile ("" ::: "memory");
|
|
}
|
|
|
|
if (mbox_flash->busy) {
|
|
prlog(PR_ERR, "Timeout waiting for BMC\n");
|
|
mbox_flash->busy = false;
|
|
return MBOX_R_TIMEOUT;
|
|
}
|
|
|
|
return mbox_flash->rc;
|
|
}
|
|
|
|
static int mbox_flash_ack(struct mbox_flash_data *mbox_flash, uint8_t reg)
|
|
{
|
|
struct bmc_mbox_msg msg = MSG_CREATE(MBOX_C_BMC_EVENT_ACK);
|
|
int rc;
|
|
|
|
msg_put_u8(&msg, 0, reg);
|
|
|
|
/* Clear this first so msg_send() doesn't freak out */
|
|
mbox_flash->reboot = false;
|
|
|
|
/*
|
|
* Use a lower timeout - there is strong evidence to suggest the
|
|
* BMC won't respond, don't waste time spinning here just have the
|
|
* high levels retry when the BMC might be back
|
|
*/
|
|
rc = msg_send(mbox_flash, &msg, 3);
|
|
|
|
/* Still need to deal with it, we've only acked it now. */
|
|
mbox_flash->reboot = true;
|
|
|
|
if (rc) {
|
|
prlog(PR_ERR, "Failed to enqueue/send BMC MBOX message\n");
|
|
return rc;
|
|
}
|
|
|
|
/*
|
|
* Use a lower timeout - there is strong evidence to suggest the
|
|
* BMC won't respond, don't waste time spinning here just have the
|
|
* high levels retry when the BMC might be back
|
|
*/
|
|
rc = wait_for_bmc(mbox_flash, 3);
|
|
if (rc)
|
|
prlog(PR_ERR, "Error waiting for BMC\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int do_acks(struct mbox_flash_data *mbox_flash)
|
|
{
|
|
int rc;
|
|
|
|
if (!mbox_flash->ack)
|
|
return 0; /* Nothing to do */
|
|
|
|
rc = mbox_flash_ack(mbox_flash, bmc_mbox_get_attn_reg() & MBOX_ATTN_ACK_MASK);
|
|
if (!rc)
|
|
mbox_flash->ack = false;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static void mbox_flash_do_nop(struct mbox_flash_data *mbox_flash __unused,
|
|
struct bmc_mbox_msg *msg __unused)
|
|
{
|
|
}
|
|
|
|
static void mbox_flash_do_illegal(struct mbox_flash_data *mbox_flash __unused,
|
|
struct bmc_mbox_msg *msg __unused)
|
|
{
|
|
prlog(PR_CRIT, "Got response to unknown message type\n");
|
|
}
|
|
|
|
/* Version 1, 2 and 3 compatible */
|
|
static void mbox_flash_do_get_mbox_info(struct mbox_flash_data *mbox_flash,
|
|
struct bmc_mbox_msg *msg)
|
|
{
|
|
|
|
mbox_flash->version = msg_get_u8(msg, 0);
|
|
switch (mbox_flash->version) {
|
|
case 1:
|
|
/* Not all version 1 daemons set argument 5 correctly */
|
|
mbox_flash->shift = 12; /* Protocol hardcodes to 4K anyway */
|
|
mbox_flash->read.size = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 1));
|
|
mbox_flash->write.size = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 3));
|
|
break;
|
|
case 3:
|
|
case 2:
|
|
mbox_flash->shift = msg_get_u8(msg, 5);
|
|
mbox_flash->timeout = msg_get_u16(msg, 6);
|
|
if (mbox_flash->timeout == 0)
|
|
mbox_flash->timeout = MBOX_DEFAULT_TIMEOUT;
|
|
break;
|
|
}
|
|
/* Callers will handle the case where the version is not known
|
|
*
|
|
* Here we deliberately ignore the 'default' sizes.
|
|
* All windows opened will not provide a hint and we're
|
|
* happy to let the BMC figure everything out.
|
|
* Future optimisations may use the default size.
|
|
*/
|
|
}
|
|
|
|
/* Version 2 and 3 compatible */
|
|
static void mbox_flash_do_get_flash_info(struct mbox_flash_data *mbox_flash,
|
|
struct bmc_mbox_msg *msg)
|
|
{
|
|
mbox_flash->total_size = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 0));
|
|
mbox_flash->erase_granule = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 2));
|
|
}
|
|
|
|
static void mbox_flash_do_get_flash_info_v1(struct mbox_flash_data *mbox_flash,
|
|
struct bmc_mbox_msg *msg)
|
|
{
|
|
mbox_flash->total_size = msg_get_u32(msg, 0);
|
|
mbox_flash->erase_granule = msg_get_u32(msg, 4);
|
|
}
|
|
|
|
/* Version 2 and 3 compatible */
|
|
static void mbox_flash_do_create_read_window(struct mbox_flash_data *mbox_flash,
|
|
struct bmc_mbox_msg *msg)
|
|
{
|
|
mbox_flash->read.lpc_addr = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 0));
|
|
mbox_flash->read.size = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 2));
|
|
mbox_flash->read.cur_pos = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 4));
|
|
mbox_flash->read.open = true;
|
|
mbox_flash->write.open = false;
|
|
}
|
|
|
|
static void mbox_flash_do_create_read_window_v1(struct mbox_flash_data *mbox_flash,
|
|
struct bmc_mbox_msg *msg)
|
|
{
|
|
mbox_flash->read.lpc_addr = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 0));
|
|
mbox_flash->read.open = true;
|
|
mbox_flash->write.open = false;
|
|
}
|
|
|
|
/* Version 2 and 3 compatible */
|
|
static void mbox_flash_do_create_write_window(struct mbox_flash_data *mbox_flash,
|
|
struct bmc_mbox_msg *msg)
|
|
{
|
|
mbox_flash->write.lpc_addr = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 0));
|
|
mbox_flash->write.size = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 2));
|
|
mbox_flash->write.cur_pos = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 4));
|
|
mbox_flash->write.open = true;
|
|
mbox_flash->read.open = false;
|
|
}
|
|
|
|
static void mbox_flash_do_create_write_window_v1(struct mbox_flash_data *mbox_flash,
|
|
struct bmc_mbox_msg *msg)
|
|
{
|
|
mbox_flash->write.lpc_addr = blocks_to_bytes(mbox_flash, msg_get_u16(msg, 0));
|
|
mbox_flash->write.open = true;
|
|
mbox_flash->read.open = false;
|
|
}
|
|
|
|
/* Version 1 and Version 2 compatible */
|
|
static void mbox_flash_do_close_window(struct mbox_flash_data *mbox_flash,
|
|
struct bmc_mbox_msg *msg __unused)
|
|
{
|
|
mbox_flash->read.open = false;
|
|
mbox_flash->write.open = false;
|
|
}
|
|
|
|
static int handle_reboot(struct mbox_flash_data *mbox_flash)
|
|
{
|
|
int rc;
|
|
|
|
/*
|
|
* If the BMC ready bit isn't present then we're basically
|
|
* guaranteed to timeout trying to talk to it so just fail
|
|
* whatever is trying to happen.
|
|
* Importantly, we can't trust that the presence of the bit means
|
|
* the daemon is ok - don't assume it is going to respond at all
|
|
* from here onwards
|
|
*/
|
|
if (!(bmc_mbox_get_attn_reg() & MBOX_ATTN_BMC_DAEMON_READY))
|
|
return FLASH_ERR_AGAIN;
|
|
|
|
/* Clear this first so msg_send() doesn't freak out */
|
|
mbox_flash->reboot = false;
|
|
|
|
rc = do_acks(mbox_flash);
|
|
if (rc) {
|
|
if (rc == MBOX_R_TIMEOUT)
|
|
rc = FLASH_ERR_AGAIN;
|
|
mbox_flash->reboot = true;
|
|
return rc;
|
|
}
|
|
|
|
rc = protocol_init(mbox_flash, 0);
|
|
if (rc)
|
|
mbox_flash->reboot = true;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static bool do_delayed_work(struct mbox_flash_data *mbox_flash)
|
|
{
|
|
return is_paused(mbox_flash) || do_acks(mbox_flash) ||
|
|
(is_reboot(mbox_flash) && handle_reboot(mbox_flash));
|
|
}
|
|
|
|
static int mbox_flash_mark_write(struct mbox_flash_data *mbox_flash,
|
|
uint64_t pos, uint64_t len, int type)
|
|
{
|
|
struct bmc_mbox_msg msg = MSG_CREATE(type);
|
|
int rc;
|
|
|
|
if (mbox_flash->version == 1) {
|
|
uint32_t start = ALIGN_DOWN(pos, 1 << mbox_flash->shift);
|
|
msg_put_u16(&msg, 0, bytes_to_blocks(mbox_flash, pos));
|
|
/*
|
|
* We need to make sure that we mark dirty until up to atleast
|
|
* pos + len.
|
|
*/
|
|
msg_put_u32(&msg, 2, pos + len - start);
|
|
} else {
|
|
uint64_t window_pos = pos - mbox_flash->write.cur_pos;
|
|
uint16_t start = bytes_to_blocks(mbox_flash, window_pos);
|
|
uint16_t end = bytes_to_blocks(mbox_flash,
|
|
ALIGN_UP(window_pos + len,
|
|
1 << mbox_flash->shift));
|
|
|
|
msg_put_u16(&msg, 0, start);
|
|
msg_put_u16(&msg, 2, end - start); /* Total Length */
|
|
}
|
|
|
|
rc = msg_send(mbox_flash, &msg, mbox_flash->timeout);
|
|
if (rc) {
|
|
prlog(PR_ERR, "Failed to enqueue/send BMC MBOX message\n");
|
|
return rc;
|
|
}
|
|
|
|
rc = wait_for_bmc(mbox_flash, mbox_flash->timeout);
|
|
if (rc)
|
|
prlog(PR_ERR, "Error waiting for BMC\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int mbox_flash_dirty(struct mbox_flash_data *mbox_flash, uint64_t pos,
|
|
uint64_t len)
|
|
{
|
|
if (!mbox_flash->write.open) {
|
|
prlog(PR_ERR, "Attempting to dirty without an open write window\n");
|
|
return FLASH_ERR_DEVICE_GONE;
|
|
}
|
|
|
|
return mbox_flash_mark_write(mbox_flash, pos, len,
|
|
MBOX_C_MARK_WRITE_DIRTY);
|
|
}
|
|
|
|
static int mbox_flash_erase(struct mbox_flash_data *mbox_flash, uint64_t pos,
|
|
uint64_t len)
|
|
{
|
|
if (!mbox_flash->write.open) {
|
|
prlog(PR_ERR, "Attempting to erase without an open write window\n");
|
|
return FLASH_ERR_DEVICE_GONE;
|
|
}
|
|
|
|
return mbox_flash_mark_write(mbox_flash, pos, len,
|
|
MBOX_C_MARK_WRITE_ERASED);
|
|
}
|
|
|
|
static int mbox_flash_flush(struct mbox_flash_data *mbox_flash)
|
|
{
|
|
struct bmc_mbox_msg msg = MSG_CREATE(MBOX_C_WRITE_FLUSH);
|
|
int rc;
|
|
|
|
if (!mbox_flash->write.open) {
|
|
prlog(PR_ERR, "Attempting to flush without an open write window\n");
|
|
return FLASH_ERR_DEVICE_GONE;
|
|
}
|
|
|
|
rc = msg_send(mbox_flash, &msg, mbox_flash->timeout);
|
|
if (rc) {
|
|
prlog(PR_ERR, "Failed to enqueue/send BMC MBOX message\n");
|
|
return rc;
|
|
}
|
|
|
|
rc = wait_for_bmc(mbox_flash, mbox_flash->timeout);
|
|
if (rc)
|
|
prlog(PR_ERR, "Error waiting for BMC\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
/* Is the current window able perform the complete operation */
|
|
static bool mbox_window_valid(struct lpc_window *win, uint64_t pos,
|
|
uint64_t len)
|
|
{
|
|
if (!win->open)
|
|
return false;
|
|
if (pos < win->cur_pos) /* start */
|
|
return false;
|
|
if ((pos + len) > (win->cur_pos + win->size)) /* end */
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static int mbox_window_move(struct mbox_flash_data *mbox_flash,
|
|
struct lpc_window *win, uint8_t command,
|
|
uint64_t pos, uint64_t len, uint64_t *size)
|
|
{
|
|
struct bmc_mbox_msg msg = MSG_CREATE(command);
|
|
int rc;
|
|
|
|
/* Is the window currently open valid */
|
|
if (mbox_window_valid(win, pos, len)) {
|
|
*size = len;
|
|
return 0;
|
|
}
|
|
|
|
/* V1 needs to remember where it has opened the window, note it
|
|
* here.
|
|
* If we're running V2 the response to the CREATE_*_WINDOW command
|
|
* will overwrite what we've noted here.
|
|
*/
|
|
win->cur_pos = pos & ~mbox_flash_mask(mbox_flash);
|
|
|
|
msg_put_u16(&msg, 0, bytes_to_blocks(mbox_flash, pos));
|
|
rc = msg_send(mbox_flash, &msg, mbox_flash->timeout);
|
|
if (rc) {
|
|
prlog(PR_ERR, "Failed to enqueue/send BMC MBOX message\n");
|
|
return rc;
|
|
}
|
|
|
|
mbox_flash->read.open = false;
|
|
mbox_flash->write.open = false;
|
|
|
|
rc = wait_for_bmc(mbox_flash, mbox_flash->timeout);
|
|
if (rc) {
|
|
prlog(PR_ERR, "Error waiting for BMC\n");
|
|
return rc;
|
|
}
|
|
|
|
*size = len;
|
|
/* Is length past the end of the window? */
|
|
if ((pos + len) > (win->cur_pos + win->size))
|
|
/* Adjust size to meet current window */
|
|
*size = (win->cur_pos + win->size) - pos;
|
|
|
|
/*
|
|
* It doesn't make sense for size to be zero if len isn't zero.
|
|
* If this condition happens we're most likely going to spin since
|
|
* the caller will likely decerement pos by zero then call this
|
|
* again.
|
|
* Debateable as to if this should return non zero. At least the
|
|
* bug will be obvious from the barf.
|
|
*/
|
|
if (len != 0 && *size == 0) {
|
|
prlog(PR_ERR, "Failed read/write!\n");
|
|
prlog(PR_ERR, "Please update your BMC firmware\n");
|
|
prlog(PR_ERR, "Move window is indicating size zero!\n");
|
|
prlog(PR_ERR, "pos: 0x%" PRIx64 ", len: 0x%" PRIx64 "\n", pos, len);
|
|
prlog(PR_ERR, "win pos: 0x%08x win size: 0x%08x\n", win->cur_pos, win->size);
|
|
/*
|
|
* In practice skiboot gets stuck and this eventually
|
|
* brings down the host. Just fail pass the error back
|
|
* up and hope someone makes a good decision
|
|
*/
|
|
return MBOX_R_SYSTEM_ERROR;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int mbox_flash_write(struct blocklevel_device *bl, uint64_t pos,
|
|
const void *buf, uint64_t len)
|
|
{
|
|
struct mbox_flash_data *mbox_flash;
|
|
uint64_t size;
|
|
|
|
int rc = 0;
|
|
|
|
/* LPC is only 32bit */
|
|
if (pos > UINT_MAX || len > UINT_MAX)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
mbox_flash = container_of(bl, struct mbox_flash_data, bl);
|
|
|
|
if (do_delayed_work(mbox_flash))
|
|
return FLASH_ERR_AGAIN;
|
|
|
|
prlog(PR_TRACE, "Flash write at %#" PRIx64 " for %#" PRIx64 "\n", pos, len);
|
|
while (len > 0) {
|
|
/* Move window and get a new size to read */
|
|
rc = mbox_window_move(mbox_flash, &mbox_flash->write,
|
|
MBOX_C_CREATE_WRITE_WINDOW, pos, len,
|
|
&size);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Perform the read for this window */
|
|
rc = lpc_window_write(mbox_flash, pos, buf, size);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = mbox_flash_dirty(mbox_flash, pos, size);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/*
|
|
* Must flush here as changing the window contents
|
|
* without flushing entitles the BMC to throw away the
|
|
* data. Unlike the read case there isn't a need to explicitly
|
|
* validate the window, the flush command will fail if the
|
|
* window was compromised.
|
|
*/
|
|
rc = mbox_flash_flush(mbox_flash);
|
|
if (rc)
|
|
return rc;
|
|
|
|
len -= size;
|
|
pos += size;
|
|
buf += size;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int mbox_flash_read(struct blocklevel_device *bl, uint64_t pos,
|
|
void *buf, uint64_t len)
|
|
{
|
|
struct mbox_flash_data *mbox_flash;
|
|
uint64_t size;
|
|
|
|
int rc = 0;
|
|
|
|
/* LPC is only 32bit */
|
|
if (pos > UINT_MAX || len > UINT_MAX)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
mbox_flash = container_of(bl, struct mbox_flash_data, bl);
|
|
|
|
if (do_delayed_work(mbox_flash))
|
|
return FLASH_ERR_AGAIN;
|
|
|
|
prlog(PR_TRACE, "Flash read at %#" PRIx64 " for %#" PRIx64 "\n", pos, len);
|
|
while (len > 0) {
|
|
/* Move window and get a new size to read */
|
|
rc = mbox_window_move(mbox_flash, &mbox_flash->read,
|
|
MBOX_C_CREATE_READ_WINDOW, pos,
|
|
len, &size);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/* Perform the read for this window */
|
|
rc = lpc_window_read(mbox_flash, pos, buf, size);
|
|
if (rc)
|
|
return rc;
|
|
|
|
len -= size;
|
|
pos += size;
|
|
buf += size;
|
|
/*
|
|
* Ensure my window is still open, if it isn't we can't trust
|
|
* what we read
|
|
*/
|
|
if (!is_valid(mbox_flash, &mbox_flash->read))
|
|
return FLASH_ERR_AGAIN;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
static int mbox_flash_get_info(struct blocklevel_device *bl, const char **name,
|
|
uint64_t *total_size, uint32_t *erase_granule)
|
|
{
|
|
struct bmc_mbox_msg msg = MSG_CREATE(MBOX_C_GET_FLASH_INFO);
|
|
struct mbox_flash_data *mbox_flash;
|
|
int rc;
|
|
|
|
mbox_flash = container_of(bl, struct mbox_flash_data, bl);
|
|
|
|
if (do_delayed_work(mbox_flash))
|
|
return FLASH_ERR_AGAIN;
|
|
|
|
/*
|
|
* We want to avoid runtime mallocs in skiboot. The expected
|
|
* behavour to uses of libflash is that one can free() the memory
|
|
* returned.
|
|
* NULL will do for now.
|
|
*/
|
|
if (name)
|
|
*name = NULL;
|
|
|
|
mbox_flash->busy = true;
|
|
rc = msg_send(mbox_flash, &msg, mbox_flash->timeout);
|
|
if (rc) {
|
|
prlog(PR_ERR, "Failed to enqueue/send BMC MBOX message\n");
|
|
return rc;
|
|
}
|
|
|
|
if (wait_for_bmc(mbox_flash, mbox_flash->timeout)) {
|
|
prlog(PR_ERR, "Error waiting for BMC\n");
|
|
return rc;
|
|
}
|
|
|
|
mbox_flash->bl.erase_mask = mbox_flash->erase_granule - 1;
|
|
|
|
if (total_size)
|
|
*total_size = mbox_flash->total_size;
|
|
if (erase_granule)
|
|
*erase_granule = mbox_flash->erase_granule;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int mbox_flash_erase_v2(struct blocklevel_device *bl, uint64_t pos,
|
|
uint64_t len)
|
|
{
|
|
struct mbox_flash_data *mbox_flash;
|
|
|
|
/* LPC is only 32bit */
|
|
if (pos > UINT_MAX || len > UINT_MAX)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
mbox_flash = container_of(bl, struct mbox_flash_data, bl);
|
|
|
|
prlog(PR_TRACE, "Flash erase at 0x%08x for 0x%08x\n", (u32) pos, (u32) len);
|
|
while (len > 0) {
|
|
uint64_t size;
|
|
int rc;
|
|
|
|
/* Move window and get a new size to erase */
|
|
rc = mbox_window_move(mbox_flash, &mbox_flash->write,
|
|
MBOX_C_CREATE_WRITE_WINDOW, pos, len, &size);
|
|
if (rc)
|
|
return rc;
|
|
|
|
rc = mbox_flash_erase(mbox_flash, pos, size);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/*
|
|
* Flush directly, don't mark that region dirty otherwise it
|
|
* isn't clear if a write happened there or not
|
|
*/
|
|
|
|
rc = mbox_flash_flush(mbox_flash);
|
|
if (rc)
|
|
return rc;
|
|
|
|
len -= size;
|
|
pos += size;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mbox_flash_erase_v1(struct blocklevel_device *bl __unused,
|
|
uint64_t pos __unused, uint64_t len __unused)
|
|
{
|
|
/*
|
|
* We can probably get away with doing nothing.
|
|
* TODO: Rethink this, causes interesting behaviour in pflash.
|
|
* Users do expect pflash -{e,E} to do something. This is because
|
|
* on real flash this would have set that region to all 0xFF but
|
|
* really the erase at the blocklevel interface was only designed
|
|
* to be "please make this region writeable".
|
|
* It may be wise (despite the large performance penalty) to
|
|
* actually write all 0xFF here. I'll leave that as an exercise
|
|
* for the future.
|
|
*/
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Called from interrupt handler, don't send any mbox messages */
|
|
static void mbox_flash_attn(uint8_t attn, void *priv)
|
|
{
|
|
struct mbox_flash_data *mbox_flash = priv;
|
|
|
|
if (attn & MBOX_ATTN_ACK_MASK)
|
|
mbox_flash->ack = true;
|
|
if (attn & MBOX_ATTN_BMC_REBOOT) {
|
|
mbox_flash->reboot = true;
|
|
mbox_flash->read.open = false;
|
|
mbox_flash->write.open = false;
|
|
attn &= ~MBOX_ATTN_BMC_REBOOT;
|
|
}
|
|
|
|
if (attn & MBOX_ATTN_BMC_WINDOW_RESET) {
|
|
mbox_flash->read.open = false;
|
|
mbox_flash->write.open = false;
|
|
attn &= ~MBOX_ATTN_BMC_WINDOW_RESET;
|
|
}
|
|
|
|
if (attn & MBOX_ATTN_BMC_FLASH_LOST) {
|
|
mbox_flash->pause = true;
|
|
attn &= ~MBOX_ATTN_BMC_FLASH_LOST;
|
|
} else {
|
|
mbox_flash->pause = false;
|
|
}
|
|
}
|
|
|
|
static void mbox_flash_callback(struct bmc_mbox_msg *msg, void *priv)
|
|
{
|
|
struct mbox_flash_data *mbox_flash = priv;
|
|
|
|
prlog(PR_TRACE, "BMC OK command %u\n", msg->command);
|
|
|
|
if (msg->response != MBOX_R_SUCCESS) {
|
|
prlog(PR_ERR, "Bad response code from BMC %d\n", msg->response);
|
|
mbox_flash->rc = msg->response;
|
|
goto out;
|
|
}
|
|
|
|
if (msg->command > MBOX_COMMAND_COUNT) {
|
|
prlog(PR_ERR, "Got response to unknown command %02x\n", msg->command);
|
|
mbox_flash->rc = -1;
|
|
goto out;
|
|
}
|
|
|
|
if (!mbox_flash->handlers[msg->command]) {
|
|
prlog(PR_ERR, "Couldn't find handler for message! command: %u, seq: %u\n",
|
|
msg->command, msg->seq);
|
|
mbox_flash->rc = MBOX_R_SYSTEM_ERROR;
|
|
goto out;
|
|
}
|
|
|
|
mbox_flash->rc = 0;
|
|
|
|
mbox_flash->handlers[msg->command](mbox_flash, msg);
|
|
|
|
out:
|
|
mbox_flash->busy = false;
|
|
}
|
|
|
|
static int protocol_init(struct mbox_flash_data *mbox_flash, uint8_t shift)
|
|
{
|
|
struct bmc_mbox_msg msg = MSG_CREATE(MBOX_C_GET_MBOX_INFO);
|
|
int rc;
|
|
|
|
mbox_flash->read.open = false;
|
|
mbox_flash->write.open = false;
|
|
|
|
/* Assume V2+ */
|
|
mbox_flash->bl.read = &mbox_flash_read;
|
|
mbox_flash->bl.write = &mbox_flash_write;
|
|
mbox_flash->bl.erase = &mbox_flash_erase_v2;
|
|
mbox_flash->bl.get_info = &mbox_flash_get_info;
|
|
|
|
/* Assume V3 */
|
|
mbox_flash->handlers = handlers_v3;
|
|
|
|
bmc_mbox_register_callback(&mbox_flash_callback, mbox_flash);
|
|
bmc_mbox_register_attn(&mbox_flash_attn, mbox_flash);
|
|
|
|
/*
|
|
* For V1 of the protocol this is fixed.
|
|
* V2+: The init code will update this
|
|
*/
|
|
mbox_flash->shift = 12;
|
|
|
|
/*
|
|
* For V1 we'll use this value.
|
|
* V2+: The init code (may) update this
|
|
*/
|
|
mbox_flash->timeout = MBOX_DEFAULT_TIMEOUT;
|
|
|
|
/*
|
|
* Always attempt init with highest version known.
|
|
* The GET_MBOX_INFO response will confirm that the other side can
|
|
* talk the highest version, we'll update this variable then if
|
|
* our highest version is not supported
|
|
*/
|
|
mbox_flash->version = 3;
|
|
|
|
negotiate_version:
|
|
msg_put_u8(&msg, 0, mbox_flash->version);
|
|
msg_put_u8(&msg, 1, shift);
|
|
rc = msg_send(mbox_flash, &msg, mbox_flash->timeout);
|
|
if (rc) {
|
|
prlog(PR_ERR, "Failed to enqueue/send BMC MBOX message\n");
|
|
return rc;
|
|
}
|
|
|
|
rc = wait_for_bmc(mbox_flash, mbox_flash->timeout);
|
|
if (rc) {
|
|
prlog(PR_ERR, "Error waiting for BMC\n");
|
|
if (mbox_flash->version > 1) {
|
|
mbox_flash->version--;
|
|
prlog(PR_INFO, "Retrying MBOX negotiation with BMC"
|
|
" with MBOXv%d\n", mbox_flash->version);
|
|
goto negotiate_version;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
prlog(PR_INFO, "Detected mbox protocol version %d\n", mbox_flash->version);
|
|
switch (mbox_flash->version) {
|
|
case 1:
|
|
mbox_flash->bl.erase = &mbox_flash_erase_v1;
|
|
mbox_flash->handlers = handlers_v1;
|
|
break;
|
|
case 2:
|
|
mbox_flash->handlers = handlers_v2;
|
|
break;
|
|
case 3:
|
|
/* Nothing to do we assumed it would be V3 */
|
|
break;
|
|
default:
|
|
/*
|
|
* The BMC is can only lower the requested version not do
|
|
* anything else. FWIW there is no verion 0.
|
|
*/
|
|
prlog(PR_CRIT, "Bad version: %u\n", mbox_flash->version);
|
|
rc = FLASH_ERR_PARM_ERROR;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int mbox_flash_lock(struct blocklevel_device *bl, uint64_t pos, uint64_t len)
|
|
{
|
|
struct mbox_flash_data *mbox_flash;
|
|
struct bmc_mbox_msg msg = MSG_CREATE(MBOX_C_MARK_LOCKED);
|
|
int rc;
|
|
|
|
/* mbox-flash only talks 32bit for now */
|
|
if (pos > UINT_MAX || len > UINT_MAX)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
/*
|
|
* If the region isn't at least 4k aligned and in size then bail
|
|
* out, the protocol won't allow for smaller block sizes.
|
|
*/
|
|
if (pos & ((1 << 12) - 1) || len & ((1 << 12) - 1))
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
mbox_flash = container_of(bl, struct mbox_flash_data, bl);
|
|
if ((pos & mbox_flash_mask(mbox_flash)) || (len & mbox_flash_mask(mbox_flash))) {
|
|
uint8_t shift = 0;
|
|
/*
|
|
* The current block size won't work for locking the requested
|
|
* region must reinit.
|
|
*/
|
|
while (!((1 << shift) & pos) && !((1 << shift) & len))
|
|
shift++;
|
|
|
|
prlog(PR_INFO, "Locking flash requires re-init from shift of %d to shift of %d\n",
|
|
mbox_flash->shift, shift);
|
|
|
|
rc = protocol_init(mbox_flash, shift);
|
|
if (rc)
|
|
return rc;
|
|
|
|
/*
|
|
* The daemon didn't agree with the requested shift - the
|
|
* flash won't be able to be locked
|
|
*/
|
|
if (mbox_flash->shift > shift)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
}
|
|
|
|
msg_put_u16(&msg, 0, bytes_to_blocks(mbox_flash, pos));
|
|
msg_put_u16(&msg, 2, bytes_to_blocks(mbox_flash, len));
|
|
rc = msg_send(mbox_flash, &msg, mbox_flash->timeout);
|
|
if (rc) {
|
|
prlog(PR_ERR, "Failed to enqueue/send BMC MBOX message\n");
|
|
return rc;
|
|
}
|
|
|
|
rc = wait_for_bmc(mbox_flash, mbox_flash->timeout);
|
|
if (rc)
|
|
prlog(PR_ERR, "Error waiting for BMC\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
int mbox_flash_init(struct blocklevel_device **bl)
|
|
{
|
|
struct mbox_flash_data *mbox_flash;
|
|
int rc;
|
|
|
|
CHECK_HANDLER_SIZE(handlers_v3);
|
|
CHECK_HANDLER_SIZE(handlers_v2);
|
|
CHECK_HANDLER_SIZE(handlers_v1);
|
|
|
|
if (!bl)
|
|
return FLASH_ERR_PARM_ERROR;
|
|
|
|
/* XXX: We only support one blocklevel flash device over mbox. If we
|
|
* ever support more than one, move this out. The chances of that are
|
|
* slim though due to circumstances.
|
|
*/
|
|
mbox_init();
|
|
|
|
*bl = NULL;
|
|
|
|
mbox_flash = zalloc(sizeof(struct mbox_flash_data));
|
|
if (!mbox_flash)
|
|
return FLASH_ERR_MALLOC_FAILED;
|
|
|
|
/* Assume V2+ */
|
|
mbox_flash->bl.read = &mbox_flash_read;
|
|
mbox_flash->bl.write = &mbox_flash_write;
|
|
mbox_flash->bl.erase = &mbox_flash_erase_v2;
|
|
mbox_flash->bl.get_info = &mbox_flash_get_info;
|
|
|
|
if (bmc_mbox_get_attn_reg() & MBOX_ATTN_BMC_REBOOT)
|
|
rc = handle_reboot(mbox_flash);
|
|
else
|
|
rc = protocol_init(mbox_flash, 0);
|
|
if (rc) {
|
|
free(mbox_flash);
|
|
return rc;
|
|
}
|
|
|
|
mbox_flash->bl.keep_alive = 0;
|
|
|
|
*bl = &(mbox_flash->bl);
|
|
return 0;
|
|
}
|
|
|
|
void mbox_flash_exit(struct blocklevel_device *bl)
|
|
{
|
|
struct mbox_flash_data *mbox_flash;
|
|
if (bl) {
|
|
mbox_flash = container_of(bl, struct mbox_flash_data, bl);
|
|
free(mbox_flash);
|
|
}
|
|
}
|