diff options
Diffstat (limited to 'roms/skiboot/libflash/mbox-flash.c')
-rw-r--r-- | roms/skiboot/libflash/mbox-flash.c | 1199 |
1 files changed, 1199 insertions, 0 deletions
diff --git a/roms/skiboot/libflash/mbox-flash.c b/roms/skiboot/libflash/mbox-flash.c new file mode 100644 index 000000000..6da77d7fc --- /dev/null +++ b/roms/skiboot/libflash/mbox-flash.c @@ -0,0 +1,1199 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* Copyright 2017-2018 IBM Corp. */ + +#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) { + /* + * 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) { + 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)) { + /* 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) { + 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(*(__le16 *)(&msg->args[i])); +} + +static void msg_put_u16(struct bmc_mbox_msg *msg, int i, uint16_t val) +{ + __le16 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(*(__le32 *)(&msg->args[i])); +} + +static void msg_put_u32(struct bmc_mbox_msg *msg, int i, uint32_t val) +{ + __le32 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 bool mbox_flash_reset(struct blocklevel_device *bl) +{ + int rc; + struct mbox_flash_data *mbox_flash; + struct bmc_mbox_msg msg = MSG_CREATE(MBOX_C_RESET_STATE); + + prlog(PR_NOTICE, "MBOX reset\n"); + mbox_flash = container_of(bl, struct mbox_flash_data, bl); + + rc = msg_send(mbox_flash, &msg, mbox_flash->timeout); + if (rc) { + prlog(PR_ERR, "Failed to enqueue/send BMC MBOX RESET msg\n"); + return false; + } + if (wait_for_bmc(mbox_flash, mbox_flash->timeout)) { + prlog(PR_ERR, "Error waiting for BMC\n"); + return false; + } + + return true; +} + +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; + mbox_flash->bl.exit = &mbox_flash_exit; + + 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; +} + +bool mbox_flash_exit(struct blocklevel_device *bl) +{ + bool status = true; + struct mbox_flash_data *mbox_flash; + if (bl) { + status = mbox_flash_reset(bl); + mbox_flash = container_of(bl, struct mbox_flash_data, bl); + free(mbox_flash); + } + + return status; +} |