978 lines
23 KiB
C
978 lines
23 KiB
C
/* Copyright 2018 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) "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:
|
|
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 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)
|
|
*(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 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)) {
|
|
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;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
void ipmi_hiomap_exit(struct blocklevel_device *bl)
|
|
{
|
|
struct ipmi_hiomap *ctx;
|
|
if (bl) {
|
|
ctx = container_of(bl, struct ipmi_hiomap, bl);
|
|
free(ctx);
|
|
}
|
|
}
|