| // 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_read */ |
| 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; |
| } |