aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/libflash/ipmi-hiomap.c
diff options
context:
space:
mode:
authorAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
committerAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
commitaf1a266670d040d2f4083ff309d732d648afba2a (patch)
tree2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/libflash/ipmi-hiomap.c
parente02cda008591317b1625707ff8e115a4841aa889 (diff)
Add submodule dependency filesHEADmaster
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/libflash/ipmi-hiomap.c')
-rw-r--r--roms/skiboot/libflash/ipmi-hiomap.c1012
1 files changed, 1012 insertions, 0 deletions
diff --git a/roms/skiboot/libflash/ipmi-hiomap.c b/roms/skiboot/libflash/ipmi-hiomap.c
new file mode 100644
index 000000000..c889d6316
--- /dev/null
+++ b/roms/skiboot/libflash/ipmi-hiomap.c
@@ -0,0 +1,1012 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2018-2019 IBM Corp. */
+
+#define pr_fmt(fmt) "HIOMAP: " fmt
+
+#include <hiomap.h>
+#include <inttypes.h>
+#include <ipmi.h>
+#include <lpc.h>
+#include <mem_region-malloc.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <ccan/container_of/container_of.h>
+
+#include "errors.h"
+#include "ipmi-hiomap.h"
+
+#define CMD_OP_HIOMAP_EVENT 0x0f
+
+struct ipmi_hiomap_result {
+ struct ipmi_hiomap *ctx;
+ int16_t cc;
+};
+
+#define RESULT_INIT(_name, _ctx) struct ipmi_hiomap_result _name = { _ctx, -1 }
+
+static inline uint32_t blocks_to_bytes(struct ipmi_hiomap *ctx, uint16_t blocks)
+{
+ return blocks << ctx->block_size_shift;
+}
+
+static inline uint16_t bytes_to_blocks(struct ipmi_hiomap *ctx, uint32_t bytes)
+{
+ return bytes >> ctx->block_size_shift;
+}
+
+static inline uint16_t bytes_to_blocks_align_up(struct ipmi_hiomap *ctx,
+ uint32_t pos, uint32_t len)
+{
+ uint32_t block_size = 1 << ctx->block_size_shift;
+ uint32_t delta = pos & (block_size - 1);
+ uint32_t aligned = ALIGN_UP((len + delta), block_size);
+ uint32_t blocks = aligned >> ctx->block_size_shift;
+ /* Our protocol can handle block count < sizeof(u16) */
+ uint32_t mask = ((1 << 16) - 1);
+
+ assert(!(blocks & ~mask));
+
+ return blocks & mask;
+}
+
+/* Call under ctx->lock */
+static int hiomap_protocol_ready(struct ipmi_hiomap *ctx)
+{
+ if (!(ctx->bmc_state & HIOMAP_E_DAEMON_READY))
+ return FLASH_ERR_DEVICE_GONE;
+ if (ctx->bmc_state & HIOMAP_E_FLASH_LOST)
+ return FLASH_ERR_AGAIN;
+
+ return 0;
+}
+
+static int hiomap_queue_msg_sync(struct ipmi_hiomap *ctx, struct ipmi_msg *msg)
+{
+ int rc;
+
+ /*
+ * There's an unavoidable TOCTOU race here with the BMC sending an
+ * event saying it's no-longer available right after we test but before
+ * we call into the IPMI stack to send the message.
+ * hiomap_queue_msg_sync() exists to capture the race in a single
+ * location.
+ */
+ lock(&ctx->lock);
+ rc = hiomap_protocol_ready(ctx);
+ unlock(&ctx->lock);
+ if (rc) {
+ ipmi_free_msg(msg);
+ return rc;
+ }
+
+ ipmi_queue_msg_sync(msg);
+
+ return 0;
+}
+
+/* Call under ctx->lock */
+static int hiomap_window_valid(struct ipmi_hiomap *ctx, uint64_t pos,
+ uint64_t len)
+{
+ if (ctx->bmc_state & HIOMAP_E_FLASH_LOST)
+ return FLASH_ERR_AGAIN;
+ if (ctx->bmc_state & HIOMAP_E_PROTOCOL_RESET)
+ return FLASH_ERR_AGAIN;
+ if (ctx->bmc_state & HIOMAP_E_WINDOW_RESET)
+ return FLASH_ERR_AGAIN;
+ if (ctx->window_state == closed_window)
+ return FLASH_ERR_PARM_ERROR;
+ if (pos < ctx->current.cur_pos)
+ return FLASH_ERR_PARM_ERROR;
+ if ((pos + len) > (ctx->current.cur_pos + ctx->current.size))
+ return FLASH_ERR_PARM_ERROR;
+
+ return 0;
+}
+
+static void ipmi_hiomap_cmd_cb(struct ipmi_msg *msg)
+{
+ struct ipmi_hiomap_result *res = msg->user_data;
+ struct ipmi_hiomap *ctx = res->ctx;
+
+ res->cc = msg->cc;
+ if (msg->cc != IPMI_CC_NO_ERROR) {
+ ipmi_free_msg(msg);
+ return;
+ }
+
+ /* We at least need the command and sequence */
+ if (msg->resp_size < 2) {
+ prerror("Illegal response size: %u\n", msg->resp_size);
+ res->cc = IPMI_ERR_UNSPECIFIED;
+ ipmi_free_msg(msg);
+ return;
+ }
+
+ if (msg->data[1] != ctx->seq) {
+ prerror("Unmatched sequence number: wanted %u got %u\n",
+ ctx->seq, msg->data[1]);
+ res->cc = IPMI_ERR_UNSPECIFIED;
+ ipmi_free_msg(msg);
+ return;
+ }
+
+ switch (msg->data[0]) {
+ case HIOMAP_C_GET_INFO:
+ {
+ struct hiomap_v2_info *parms;
+
+ if (msg->resp_size != 6) {
+ prerror("%u: Unexpected response size: %u\n", msg->data[0],
+ msg->resp_size);
+ res->cc = IPMI_ERR_UNSPECIFIED;
+ break;
+ }
+
+ ctx->version = msg->data[2];
+ if (ctx->version < 2) {
+ prerror("Failed to negotiate protocol v2 or higher: %d\n",
+ ctx->version);
+ res->cc = IPMI_ERR_UNSPECIFIED;
+ break;
+ }
+
+ parms = (struct hiomap_v2_info *)&msg->data[3];
+ ctx->block_size_shift = parms->block_size_shift;
+ ctx->timeout = le16_to_cpu(parms->timeout);
+ break;
+ }
+ case HIOMAP_C_GET_FLASH_INFO:
+ {
+ struct hiomap_v2_flash_info *parms;
+
+ if (msg->resp_size != 6) {
+ prerror("%u: Unexpected response size: %u\n", msg->data[0],
+ msg->resp_size);
+ res->cc = IPMI_ERR_UNSPECIFIED;
+ break;
+ }
+
+ parms = (struct hiomap_v2_flash_info *)&msg->data[2];
+ ctx->total_size =
+ blocks_to_bytes(ctx, le16_to_cpu(parms->total_size));
+ ctx->erase_granule =
+ blocks_to_bytes(ctx, le16_to_cpu(parms->erase_granule));
+ break;
+ }
+ case HIOMAP_C_CREATE_READ_WINDOW:
+ case HIOMAP_C_CREATE_WRITE_WINDOW:
+ {
+ struct hiomap_v2_create_window *parms;
+
+ if (msg->resp_size != 8) {
+ prerror("%u: Unexpected response size: %u\n", msg->data[0],
+ msg->resp_size);
+ res->cc = IPMI_ERR_UNSPECIFIED;
+ break;
+ }
+
+ parms = (struct hiomap_v2_create_window *)&msg->data[2];
+
+ ctx->current.lpc_addr =
+ blocks_to_bytes(ctx, le16_to_cpu(parms->lpc_addr));
+ ctx->current.size =
+ blocks_to_bytes(ctx, le16_to_cpu(parms->size));
+ ctx->current.cur_pos =
+ blocks_to_bytes(ctx, le16_to_cpu(parms->offset));
+
+ lock(&ctx->lock);
+ if (msg->data[0] == HIOMAP_C_CREATE_READ_WINDOW)
+ ctx->window_state = read_window;
+ else
+ ctx->window_state = write_window;
+ unlock(&ctx->lock);
+
+ break;
+ }
+ case HIOMAP_C_MARK_DIRTY:
+ case HIOMAP_C_FLUSH:
+ case HIOMAP_C_ACK:
+ case HIOMAP_C_ERASE:
+ case HIOMAP_C_RESET:
+ if (msg->resp_size != 2) {
+ prerror("%u: Unexpected response size: %u\n", msg->data[0],
+ msg->resp_size);
+ res->cc = IPMI_ERR_UNSPECIFIED;
+ break;
+ }
+ break;
+ default:
+ prlog(PR_WARNING, "Unimplemented command handler: %u\n",
+ msg->data[0]);
+ break;
+ };
+ ipmi_free_msg(msg);
+}
+
+static void hiomap_init(struct ipmi_hiomap *ctx)
+{
+ /*
+ * Speculatively mark the daemon as available so we attempt to perform
+ * the handshake without immediately bailing out.
+ */
+ lock(&ctx->lock);
+ ctx->bmc_state = HIOMAP_E_DAEMON_READY;
+ unlock(&ctx->lock);
+}
+
+static int hiomap_get_info(struct ipmi_hiomap *ctx)
+{
+ RESULT_INIT(res, ctx);
+ unsigned char req[3];
+ struct ipmi_msg *msg;
+ int rc;
+
+ /* Negotiate protocol version 2 */
+ req[0] = HIOMAP_C_GET_INFO;
+ req[1] = ++ctx->seq;
+ req[2] = HIOMAP_V2;
+
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE,
+ bmc_platform->sw->ipmi_oem_hiomap_cmd,
+ ipmi_hiomap_cmd_cb, &res, req, sizeof(req), 6);
+
+ rc = hiomap_queue_msg_sync(ctx, msg);
+ if (rc)
+ return rc;
+
+ if (res.cc != IPMI_CC_NO_ERROR) {
+ prerror("%s failed: %d\n", __func__, res.cc);
+ return FLASH_ERR_PARM_ERROR; /* XXX: Find something better? */
+ }
+
+ return 0;
+}
+
+static int hiomap_get_flash_info(struct ipmi_hiomap *ctx)
+{
+ RESULT_INIT(res, ctx);
+ unsigned char req[2];
+ struct ipmi_msg *msg;
+ int rc;
+
+ req[0] = HIOMAP_C_GET_FLASH_INFO;
+ req[1] = ++ctx->seq;
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE,
+ bmc_platform->sw->ipmi_oem_hiomap_cmd,
+ ipmi_hiomap_cmd_cb, &res, req, sizeof(req), 2 + 2 + 2);
+
+ rc = hiomap_queue_msg_sync(ctx, msg);
+ if (rc)
+ return rc;
+
+ if (res.cc != IPMI_CC_NO_ERROR) {
+ prerror("%s failed: %d\n", __func__, res.cc);
+ return FLASH_ERR_PARM_ERROR; /* XXX: Find something better? */
+ }
+
+ return 0;
+}
+
+static int hiomap_window_move(struct ipmi_hiomap *ctx, uint8_t command,
+ uint64_t pos, uint64_t len, uint64_t *size)
+{
+ enum lpc_window_state want_state;
+ struct hiomap_v2_range *range;
+ RESULT_INIT(res, ctx);
+ unsigned char req[6];
+ struct ipmi_msg *msg;
+ bool valid_state;
+ bool is_read;
+ int rc;
+
+ is_read = (command == HIOMAP_C_CREATE_READ_WINDOW);
+ want_state = is_read ? read_window : write_window;
+
+ lock(&ctx->lock);
+
+ valid_state = want_state == ctx->window_state;
+ rc = hiomap_window_valid(ctx, pos, len);
+ if (valid_state && !rc) {
+ unlock(&ctx->lock);
+ *size = len;
+ return 0;
+ }
+
+ ctx->window_state = closed_window;
+
+ unlock(&ctx->lock);
+
+ req[0] = command;
+ req[1] = ++ctx->seq;
+
+ range = (struct hiomap_v2_range *)&req[2];
+ range->offset = cpu_to_le16(bytes_to_blocks(ctx, pos));
+ range->size = cpu_to_le16(bytes_to_blocks_align_up(ctx, pos, len));
+
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE,
+ bmc_platform->sw->ipmi_oem_hiomap_cmd,
+ ipmi_hiomap_cmd_cb, &res, req, sizeof(req),
+ 2 + 2 + 2 + 2);
+
+ rc = hiomap_queue_msg_sync(ctx, msg);
+ if (rc)
+ return rc;
+
+ if (res.cc != IPMI_CC_NO_ERROR) {
+ prlog(PR_INFO, "%s failed: %d\n", __func__, res.cc);
+ return FLASH_ERR_PARM_ERROR; /* XXX: Find something better? */
+ }
+
+ lock(&ctx->lock);
+ *size = len;
+ /* Is length past the end of the window? */
+ if ((pos + len) > (ctx->current.cur_pos + ctx->current.size))
+ /* Adjust size to meet current window */
+ *size = (ctx->current.cur_pos + ctx->current.size) - pos;
+
+ if (len != 0 && *size == 0) {
+ unlock(&ctx->lock);
+ prerror("Invalid window properties: len: %"PRIu64", size: %"PRIu64"\n",
+ len, *size);
+ return FLASH_ERR_PARM_ERROR;
+ }
+
+ prlog(PR_DEBUG, "Opened %s window from 0x%x for %u bytes at 0x%x\n",
+ (command == HIOMAP_C_CREATE_READ_WINDOW) ? "read" : "write",
+ ctx->current.cur_pos, ctx->current.size, ctx->current.lpc_addr);
+
+ unlock(&ctx->lock);
+
+ return 0;
+}
+
+static int hiomap_mark_dirty(struct ipmi_hiomap *ctx, uint64_t offset,
+ uint64_t size)
+{
+ struct hiomap_v2_range *range;
+ enum lpc_window_state state;
+ RESULT_INIT(res, ctx);
+ unsigned char req[6];
+ struct ipmi_msg *msg;
+ uint32_t pos;
+ int rc;
+
+ lock(&ctx->lock);
+ state = ctx->window_state;
+ unlock(&ctx->lock);
+
+ if (state != write_window)
+ return FLASH_ERR_PARM_ERROR;
+
+ req[0] = HIOMAP_C_MARK_DIRTY;
+ req[1] = ++ctx->seq;
+
+ pos = offset - ctx->current.cur_pos;
+ range = (struct hiomap_v2_range *)&req[2];
+ range->offset = cpu_to_le16(bytes_to_blocks(ctx, pos));
+ range->size = cpu_to_le16(bytes_to_blocks_align_up(ctx, pos, size));
+
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE,
+ bmc_platform->sw->ipmi_oem_hiomap_cmd,
+ ipmi_hiomap_cmd_cb, &res, req, sizeof(req), 2);
+
+ rc = hiomap_queue_msg_sync(ctx, msg);
+ if (rc)
+ return rc;
+
+ if (res.cc != IPMI_CC_NO_ERROR) {
+ prerror("%s failed: %d\n", __func__, res.cc);
+ return FLASH_ERR_PARM_ERROR;
+ }
+
+ prlog(PR_DEBUG, "Marked flash dirty at 0x%" PRIx64 " for %" PRIu64 "\n",
+ offset, size);
+
+ return 0;
+}
+
+static int hiomap_flush(struct ipmi_hiomap *ctx)
+{
+ enum lpc_window_state state;
+ RESULT_INIT(res, ctx);
+ unsigned char req[2];
+ struct ipmi_msg *msg;
+ int rc;
+
+ lock(&ctx->lock);
+ state = ctx->window_state;
+ unlock(&ctx->lock);
+
+ if (state != write_window)
+ return FLASH_ERR_PARM_ERROR;
+
+ req[0] = HIOMAP_C_FLUSH;
+ req[1] = ++ctx->seq;
+
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE,
+ bmc_platform->sw->ipmi_oem_hiomap_cmd,
+ ipmi_hiomap_cmd_cb, &res, req, sizeof(req), 2);
+
+ rc = hiomap_queue_msg_sync(ctx, msg);
+ if (rc)
+ return rc;
+
+ if (res.cc != IPMI_CC_NO_ERROR) {
+ prerror("%s failed: %d\n", __func__, res.cc);
+ return FLASH_ERR_PARM_ERROR;
+ }
+
+ prlog(PR_DEBUG, "Flushed writes\n");
+
+ return 0;
+}
+
+static int hiomap_ack(struct ipmi_hiomap *ctx, uint8_t ack)
+{
+ RESULT_INIT(res, ctx);
+ unsigned char req[3];
+ struct ipmi_msg *msg;
+ int rc;
+
+ req[0] = HIOMAP_C_ACK;
+ req[1] = ++ctx->seq;
+ req[2] = ack;
+
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE,
+ bmc_platform->sw->ipmi_oem_hiomap_cmd,
+ ipmi_hiomap_cmd_cb, &res, req, sizeof(req), 2);
+
+ rc = hiomap_queue_msg_sync(ctx, msg);
+ if (rc)
+ return rc;
+
+ if (res.cc != IPMI_CC_NO_ERROR) {
+ prlog(PR_DEBUG, "%s failed: %d\n", __func__, res.cc);
+ return FLASH_ERR_PARM_ERROR;
+ }
+
+ prlog(PR_DEBUG, "Acked events: 0x%x\n", ack);
+
+ return 0;
+}
+
+static int hiomap_erase(struct ipmi_hiomap *ctx, uint64_t offset,
+ uint64_t size)
+{
+ struct hiomap_v2_range *range;
+ enum lpc_window_state state;
+ RESULT_INIT(res, ctx);
+ unsigned char req[6];
+ struct ipmi_msg *msg;
+ uint32_t pos;
+ int rc;
+
+ lock(&ctx->lock);
+ state = ctx->window_state;
+ unlock(&ctx->lock);
+
+ if (state != write_window)
+ return FLASH_ERR_PARM_ERROR;
+
+ req[0] = HIOMAP_C_ERASE;
+ req[1] = ++ctx->seq;
+
+ pos = offset - ctx->current.cur_pos;
+ range = (struct hiomap_v2_range *)&req[2];
+ range->offset = cpu_to_le16(bytes_to_blocks(ctx, pos));
+ range->size = cpu_to_le16(bytes_to_blocks_align_up(ctx, pos, size));
+
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE,
+ bmc_platform->sw->ipmi_oem_hiomap_cmd,
+ ipmi_hiomap_cmd_cb, &res, req, sizeof(req), 2);
+ rc = hiomap_queue_msg_sync(ctx, msg);
+ if (rc)
+ return rc;
+
+ if (res.cc != IPMI_CC_NO_ERROR) {
+ prerror("%s failed: %d\n", __func__, res.cc);
+ return FLASH_ERR_PARM_ERROR;
+ }
+
+ prlog(PR_DEBUG, "Erased flash at 0x%" PRIx64 " for %" PRIu64 "\n",
+ offset, size);
+
+ return 0;
+}
+
+static bool hiomap_reset(struct ipmi_hiomap *ctx)
+{
+ RESULT_INIT(res, ctx);
+ unsigned char req[2];
+ struct ipmi_msg *msg;
+
+ prlog(PR_NOTICE, "Reset\n");
+
+ req[0] = HIOMAP_C_RESET;
+ req[1] = ++ctx->seq;
+ msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE,
+ bmc_platform->sw->ipmi_oem_hiomap_cmd,
+ ipmi_hiomap_cmd_cb, &res, req, sizeof(req), 2);
+ ipmi_queue_msg_sync(msg);
+
+ if (res.cc != IPMI_CC_NO_ERROR) {
+ prlog(PR_ERR, "%s failed: %d\n", __func__, res.cc);
+ return false;
+ }
+
+ return true;
+}
+
+static void hiomap_event(uint8_t events, void *context)
+{
+ struct ipmi_hiomap *ctx = context;
+
+ prlog(PR_DEBUG, "Received events: 0x%x\n", events);
+
+ lock(&ctx->lock);
+ ctx->bmc_state = events | (ctx->bmc_state & HIOMAP_E_ACK_MASK);
+ unlock(&ctx->lock);
+}
+
+static int lpc_window_read(struct ipmi_hiomap *ctx, uint32_t pos,
+ void *buf, uint32_t len)
+{
+ uint32_t off = ctx->current.lpc_addr + (pos - ctx->current.cur_pos);
+ int rc;
+
+ if ((ctx->current.lpc_addr + ctx->current.size) < (off + len))
+ return FLASH_ERR_PARM_ERROR;
+
+ 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 ipmi_hiomap *ctx, uint32_t pos,
+ const void *buf, uint32_t len)
+{
+ uint32_t off = ctx->current.lpc_addr + (pos - ctx->current.cur_pos);
+ enum lpc_window_state state;
+ int rc;
+
+ lock(&ctx->lock);
+ state = ctx->window_state;
+ unlock(&ctx->lock);
+
+ if (state != write_window)
+ return FLASH_ERR_PARM_ERROR;
+
+ if ((ctx->current.lpc_addr + ctx->current.size) < (off + len))
+ return FLASH_ERR_PARM_ERROR;
+
+ 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;
+}
+
+/* Best-effort asynchronous event handling by blocklevel callbacks */
+static int ipmi_hiomap_handle_events(struct ipmi_hiomap *ctx)
+{
+ uint8_t status;
+ int rc;
+
+ lock(&ctx->lock);
+
+ status = ctx->bmc_state;
+
+ /*
+ * Immediately clear the ackable events to make sure we don't race to
+ * clear them after dropping the lock, as we may lose protocol or
+ * window state if a race materialises. In the event of a failure where
+ * we haven't completed the recovery, the state we mask out below gets
+ * OR'ed back in to avoid losing it.
+ */
+ ctx->bmc_state &= ~HIOMAP_E_ACK_MASK;
+
+ /*
+ * We won't be attempting to restore window state -
+ * ipmi_hiomap_handle_events() is followed by hiomap_window_move() in
+ * all cases. Attempting restoration after HIOMAP_E_PROTOCOL_RESET or
+ * HIOMAP_E_WINDOW_RESET can be wasteful if we immediately shift the
+ * window elsewhere, and if it does not need to be shifted with respect
+ * to the subsequent request then hiomap_window_move() will handle
+ * re-opening it from the closed state.
+ *
+ * Therefore it is enough to mark the window as closed to consider it
+ * recovered.
+ */
+ if (status & (HIOMAP_E_PROTOCOL_RESET | HIOMAP_E_WINDOW_RESET))
+ ctx->window_state = closed_window;
+
+ unlock(&ctx->lock);
+
+ /*
+ * If there's anything to acknowledge, do so in the one request to
+ * minimise overhead. By sending the ACK prior to performing the
+ * protocol recovery we ensure that even with coalesced resets we still
+ * end up in the recovered state and not unknowingly stuck in a reset
+ * state. We may receive reset events after the ACK but prior to the
+ * recovery procedures being run, but this just means that we will
+ * needlessly perform recovery on the following invocation of
+ * ipmi_hiomap_handle_events(). If the reset event is a
+ * HIOMAP_E_WINDOW_RESET it is enough that the window is already marked
+ * as closed above - future accesses will force it to be re-opened and
+ * the BMC's cache must be valid if opening the window is successful.
+ */
+ if (status & HIOMAP_E_ACK_MASK) {
+ /* ACK is unversioned, can send it if the daemon is ready */
+ rc = hiomap_ack(ctx, status & HIOMAP_E_ACK_MASK);
+ if (rc) {
+ prlog(PR_DEBUG, "Failed to ack events: 0x%x\n",
+ status & HIOMAP_E_ACK_MASK);
+ goto restore;
+ }
+ }
+
+ if (status & HIOMAP_E_PROTOCOL_RESET) {
+ prlog(PR_INFO, "Protocol was reset\n");
+
+ rc = hiomap_get_info(ctx);
+ if (rc) {
+ prerror("Failure to renegotiate after protocol reset\n");
+ goto restore;
+ }
+
+ rc = hiomap_get_flash_info(ctx);
+ if (rc) {
+ prerror("Failure to fetch flash info after protocol reset\n");
+ goto restore;
+ }
+
+ prlog(PR_INFO, "Restored state after protocol reset\n");
+ }
+
+ /*
+ * As there's no change to the protocol on HIOMAP_E_WINDOW_RESET we
+ * simply need to open a window to recover, which as mentioned above is
+ * handled by hiomap_window_move() after our cleanup here.
+ */
+
+ return 0;
+
+restore:
+ /*
+ * Conservatively restore the events to the un-acked state to avoid
+ * losing events due to races. It might cause us to restore state more
+ * than necessary, but never less than necessary.
+ */
+ lock(&ctx->lock);
+ ctx->bmc_state |= (status & HIOMAP_E_ACK_MASK);
+ unlock(&ctx->lock);
+
+ return rc;
+}
+
+static int ipmi_hiomap_read(struct blocklevel_device *bl, uint64_t pos,
+ void *buf, uint64_t len)
+{
+ struct ipmi_hiomap *ctx;
+ uint64_t size;
+ int rc = 0;
+
+ /* LPC is only 32bit */
+ if (pos > UINT_MAX || len > UINT_MAX)
+ return FLASH_ERR_PARM_ERROR;
+
+ ctx = container_of(bl, struct ipmi_hiomap, bl);
+
+ rc = ipmi_hiomap_handle_events(ctx);
+ if (rc)
+ return rc;
+
+ 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 = hiomap_window_move(ctx, HIOMAP_C_CREATE_READ_WINDOW, pos,
+ len, &size);
+ if (rc)
+ return rc;
+
+ /* Perform the read for this window */
+ rc = lpc_window_read(ctx, pos, buf, size);
+ if (rc)
+ return rc;
+
+ /* Check we can trust what we read */
+ lock(&ctx->lock);
+ rc = hiomap_window_valid(ctx, pos, size);
+ unlock(&ctx->lock);
+ if (rc)
+ return rc;
+
+ len -= size;
+ pos += size;
+ buf += size;
+ }
+ return rc;
+
+}
+
+static int ipmi_hiomap_write(struct blocklevel_device *bl, uint64_t pos,
+ const void *buf, uint64_t len)
+{
+ struct ipmi_hiomap *ctx;
+ uint64_t size;
+ int rc = 0;
+
+ /* LPC is only 32bit */
+ if (pos > UINT_MAX || len > UINT_MAX)
+ return FLASH_ERR_PARM_ERROR;
+
+ ctx = container_of(bl, struct ipmi_hiomap, bl);
+
+ rc = ipmi_hiomap_handle_events(ctx);
+ if (rc)
+ return rc;
+
+ 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 = hiomap_window_move(ctx, HIOMAP_C_CREATE_WRITE_WINDOW, pos,
+ len, &size);
+ if (rc)
+ return rc;
+
+ /* Perform the write for this window */
+ rc = lpc_window_write(ctx, pos, buf, size);
+ if (rc)
+ return rc;
+
+ /*
+ * Unlike ipmi_hiomap_read() we don't explicitly test if the
+ * window is still valid after completing the LPC accesses as
+ * the following hiomap_mark_dirty() will implicitly check for
+ * us. In the case of a read operation there's no requirement
+ * that a command that validates window state follows, so the
+ * read implementation explicitly performs a check.
+ */
+
+ rc = hiomap_mark_dirty(ctx, pos, size);
+ if (rc)
+ return rc;
+
+ /*
+ * The BMC *should* flush if the window is implicitly closed,
+ * but do an explicit flush here to be sure.
+ *
+ * XXX: Removing this could improve performance
+ */
+ rc = hiomap_flush(ctx);
+ if (rc)
+ return rc;
+
+ len -= size;
+ pos += size;
+ buf += size;
+ }
+ return rc;
+}
+
+static int ipmi_hiomap_erase(struct blocklevel_device *bl, uint64_t pos,
+ uint64_t len)
+{
+ struct ipmi_hiomap *ctx;
+ int rc;
+
+ /* LPC is only 32bit */
+ if (pos > UINT_MAX || len > UINT_MAX)
+ return FLASH_ERR_PARM_ERROR;
+
+ ctx = container_of(bl, struct ipmi_hiomap, bl);
+
+ rc = ipmi_hiomap_handle_events(ctx);
+ if (rc)
+ return rc;
+
+ prlog(PR_TRACE, "Flash erase at 0x%08x for 0x%08x\n", (u32) pos,
+ (u32) len);
+ while (len > 0) {
+ uint64_t size;
+
+ /* Move window and get a new size to erase */
+ rc = hiomap_window_move(ctx, HIOMAP_C_CREATE_WRITE_WINDOW, pos,
+ len, &size);
+ if (rc)
+ return rc;
+
+ rc = hiomap_erase(ctx, 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 = hiomap_flush(ctx);
+ if (rc)
+ return rc;
+
+ len -= size;
+ pos += size;
+ }
+
+ return 0;
+}
+
+static int ipmi_hiomap_get_flash_info(struct blocklevel_device *bl,
+ const char **name, uint64_t *total_size,
+ uint32_t *erase_granule)
+{
+ struct ipmi_hiomap *ctx;
+ int rc;
+
+ ctx = container_of(bl, struct ipmi_hiomap, bl);
+
+ rc = ipmi_hiomap_handle_events(ctx);
+ if (rc)
+ return rc;
+
+ rc = hiomap_get_flash_info(ctx);
+ if (rc)
+ return rc;
+
+ ctx->bl.erase_mask = ctx->erase_granule - 1;
+
+ if (name)
+ *name = NULL;
+ if (total_size)
+ *total_size = ctx->total_size;
+ if (erase_granule)
+ *erase_granule = ctx->erase_granule;
+
+ return 0;
+}
+
+int ipmi_hiomap_init(struct blocklevel_device **bl)
+{
+ struct ipmi_hiomap *ctx;
+ int rc;
+
+ if (!bmc_platform->sw->ipmi_oem_hiomap_cmd)
+ /* FIXME: Find a better error code */
+ return FLASH_ERR_DEVICE_GONE;
+
+ if (!bl)
+ return FLASH_ERR_PARM_ERROR;
+
+ *bl = NULL;
+
+ ctx = zalloc(sizeof(struct ipmi_hiomap));
+ if (!ctx)
+ return FLASH_ERR_MALLOC_FAILED;
+
+ init_lock(&ctx->lock);
+
+ ctx->bl.read = &ipmi_hiomap_read;
+ ctx->bl.write = &ipmi_hiomap_write;
+ ctx->bl.erase = &ipmi_hiomap_erase;
+ ctx->bl.get_info = &ipmi_hiomap_get_flash_info;
+ ctx->bl.exit = &ipmi_hiomap_exit;
+
+ hiomap_init(ctx);
+
+ /* Ack all pending ack-able events to avoid spurious failures */
+ rc = hiomap_ack(ctx, HIOMAP_E_ACK_MASK);
+ if (rc) {
+ prlog(PR_DEBUG, "Failed to ack events: 0x%x\n",
+ HIOMAP_E_ACK_MASK);
+ goto err;
+ }
+
+ rc = ipmi_sel_register(CMD_OP_HIOMAP_EVENT, hiomap_event, ctx);
+ if (rc < 0)
+ goto err;
+
+ /* Negotiate protocol behaviour */
+ rc = hiomap_get_info(ctx);
+ if (rc) {
+ prerror("Failed to get hiomap parameters: %d\n", rc);
+ goto err;
+ }
+
+ /* Grab the flash parameters */
+ rc = hiomap_get_flash_info(ctx);
+ if (rc) {
+ prerror("Failed to get flash parameters: %d\n", rc);
+ goto err;
+ }
+
+ prlog(PR_NOTICE, "Negotiated hiomap protocol v%u\n", ctx->version);
+ prlog(PR_NOTICE, "Block size is %uKiB\n",
+ 1 << (ctx->block_size_shift - 10));
+ prlog(PR_NOTICE, "BMC suggested flash timeout of %us\n", ctx->timeout);
+ prlog(PR_NOTICE, "Flash size is %uMiB\n", ctx->total_size >> 20);
+ prlog(PR_NOTICE, "Erase granule size is %uKiB\n",
+ ctx->erase_granule >> 10);
+
+ ctx->bl.keep_alive = 0;
+
+ *bl = &(ctx->bl);
+
+ return 0;
+
+err:
+ free(ctx);
+
+ return rc;
+}
+
+bool ipmi_hiomap_exit(struct blocklevel_device *bl)
+{
+ bool status = true;
+
+ struct ipmi_hiomap *ctx;
+ if (bl) {
+ ctx = container_of(bl, struct ipmi_hiomap, bl);
+ status = hiomap_reset(ctx);
+ free(ctx);
+ }
+
+ return status;
+}