[Skiboot] [PATCH 3/4] libflash: blocklevel backend for MBOX flash access
Michael Neuling
mikey at neuling.org
Thu Feb 9 15:13:08 AEDT 2017
From: Cyril Bur <cyril.bur at au1.ibm.com>
The use MBOX protocol to request flash access from the BMC. Then
read/write to the 'flash' through windows it creates on LPC FW space.
Reference implementation of the mbox flash daemon for BMC userspace:
https://github.com/cyrilbur-ibm/mboxbridge
Signed-off-by: Cyril Bur <cyril.bur at au1.ibm.com>
Signed-off-by: Michael Neuling <mikey at neuling.org>
---
libflash/Makefile.inc | 2 +-
libflash/mbox-flash.c | 576 ++++++++++++++++++++++++++++++++++++++++++++++++++
libflash/mbox-flash.h | 24 +++
3 files changed, 601 insertions(+), 1 deletion(-)
create mode 100644 libflash/mbox-flash.c
create mode 100644 libflash/mbox-flash.h
diff --git a/libflash/Makefile.inc b/libflash/Makefile.inc
index 4db02a1fac..ea64eb462c 100644
--- a/libflash/Makefile.inc
+++ b/libflash/Makefile.inc
@@ -1,4 +1,4 @@
-LIBFLASH_SRCS = libflash.c libffs.c ecc.c blocklevel.c
+LIBFLASH_SRCS = libflash.c libffs.c ecc.c blocklevel.c mbox-flash.c
LIBFLASH_OBJS = $(LIBFLASH_SRCS:%.c=%.o)
SUBDIRS += libflash
diff --git a/libflash/mbox-flash.c b/libflash/mbox-flash.c
new file mode 100644
index 0000000000..7bf731d070
--- /dev/null
+++ b/libflash/mbox-flash.c
@@ -0,0 +1,576 @@
+/* Copyright 2017 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define pr_fmt(fmt) "MBOX-FLASH: " fmt
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <skiboot.h>
+#include <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__
+#error "This libflash backend must be compiled with skiboot"
+#endif
+
+#define MBOX_DEFAULT_TIMEOUT 30
+
+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 {
+ 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 busy;
+ uint8_t seq;
+ struct bmc_mbox_msg msg_mem;
+};
+
+static uint64_t mbox_flash_mask(struct mbox_flash_data *mbox_flash)
+{
+ return (1 << mbox_flash->shift) - 1;
+}
+
+__unused static uint8_t msg_get_u8(struct bmc_mbox_msg *msg, int i)
+{
+ return msg->args[i];
+}
+
+static void msg_put_u8(struct bmc_mbox_msg *msg, int i, uint8_t val)
+{
+ msg->args[i] = val;
+}
+
+static uint16_t msg_get_u16(struct bmc_mbox_msg *msg, int i)
+{
+ return le16_to_cpu(*(uint16_t *)(&msg->args[i]));
+}
+
+static void msg_put_u16(struct bmc_mbox_msg *msg, int i, uint16_t val)
+{
+ uint16_t tmp = cpu_to_le16(val);
+ memcpy(&msg->args[i], &tmp, sizeof(val));
+}
+
+static uint32_t msg_get_u32(struct bmc_mbox_msg *msg, int i)
+{
+ return le32_to_cpu(*(uint32_t *)(&msg->args[i]));
+}
+
+static void msg_put_u32(struct bmc_mbox_msg *msg, int i, uint32_t val)
+{
+ uint32_t tmp = cpu_to_le32(val);
+ memcpy(&msg->args[i], &tmp, sizeof(val));
+}
+
+static struct bmc_mbox_msg *msg_alloc(struct mbox_flash_data *mbox_flash,
+ uint8_t command)
+{
+ /*
+ * Yes this causes *slow*.
+ * This file and lpc-mbox have far greater slow points, zeroed
+ * data regs are VERY useful for debugging. Think twice if this is
+ * really the performance optimisation you want to make.
+ */
+ memset(&mbox_flash->msg_mem, 0, sizeof(mbox_flash->msg_mem));
+ mbox_flash->msg_mem.seq = ++mbox_flash->seq;
+ mbox_flash->msg_mem.command = command;
+ return &mbox_flash->msg_mem;
+}
+
+static void msg_free_memory(struct bmc_mbox_msg *mem __unused)
+{
+ /* Allocation is so simple this isn't required */
+}
+
+static int msg_send(struct mbox_flash_data *mbox_flash, struct bmc_mbox_msg *msg)
+{
+ mbox_flash->busy = true;
+ mbox_flash->rc = 0;
+ return bmc_mbox_enqueue(msg);
+}
+
+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) {
+ long now = tb_to_secs(mftb());
+ if (now - start > last) {
+ timeout_sec--;
+ last = now - start;
+ 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);
+ }
+ /*
+ * Both functions are important.
+ * Well time_wait_ms() relaxes the spin... so... its nice
+ */
+ time_wait_ms(MBOX_DEFAULT_POLL_MS);
+ check_timers(false);
+ 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 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;
+
+ /* Choose access size */
+ if (len > 3 && !(off & 3)) {
+ rc = lpc_read(OPAL_LPC_FW, off, &dat, 4);
+ if (!rc)
+ *(uint32_t *)buf = dat;
+ chunk = 4;
+ } else {
+ rc = lpc_read(OPAL_LPC_FW, off, &dat, 1);
+ if (!rc)
+ *(uint8_t *)buf = dat;
+ chunk = 1;
+ }
+ if (rc) {
+ prlog(PR_ERR, "lpc_read failure %d to FW 0x%08x\n", rc, off);
+ return rc;
+ }
+ len -= chunk;
+ off += chunk;
+ buf += chunk;
+ }
+
+ return 0;
+}
+
+static int lpc_window_write(struct mbox_flash_data *mbox_flash, uint32_t pos,
+ const void *buf, uint32_t len)
+{
+ uint32_t off = mbox_flash->write.lpc_addr + (pos - mbox_flash->write.cur_pos);
+ int rc;
+
+
+ prlog(PR_TRACE, "Writing at 0x%08x for 0x%08x offset: 0x%08x\n",
+ pos, len, off);
+
+ while(len) {
+ uint32_t chunk;
+
+ if (len > 3 && !(off & 3)) {
+ rc = lpc_write(OPAL_LPC_FW, off,
+ *(uint32_t *)buf, 4);
+ chunk = 4;
+ } else {
+ rc = lpc_write(OPAL_LPC_FW, off,
+ *(uint8_t *)buf, 1);
+ chunk = 1;
+ }
+ if (rc) {
+ prlog(PR_ERR, "lpc_write failure %d to FW 0x%08x\n", rc, off);
+ return rc;
+ }
+ len -= chunk;
+ off += chunk;
+ buf += chunk;
+ }
+
+ return 0;
+}
+
+static int mbox_flash_flush(struct mbox_flash_data *mbox_flash, uint64_t pos,
+ uint64_t len)
+{
+ struct bmc_mbox_msg *msg;
+ int rc;
+
+ msg = msg_alloc(mbox_flash, MBOX_C_WRITE_FLUSH);
+ if (!msg)
+ return FLASH_ERR_MALLOC_FAILED;
+ msg_put_u16(msg, 0, pos >> mbox_flash->shift);
+ msg_put_u32(msg, 2, len);
+
+ rc = msg_send(mbox_flash, msg);
+ if (rc) {
+ prlog(PR_ERR, "Failed to enqueue/send BMC MBOX message\n");
+ goto out;
+ }
+
+ rc = wait_for_bmc(mbox_flash, MBOX_DEFAULT_TIMEOUT);
+ if (rc)
+ prlog(PR_ERR, "Error waiting for BMC\n");
+
+out:
+ msg_free_memory(msg);
+ 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;
+ int rc;
+
+ /* Is the window currently open valid */
+ if (mbox_window_valid(win, pos, len)) {
+ *size = len;
+ return 0;
+ }
+
+ prlog(PR_DEBUG, "Adjusting the window\n");
+
+ msg = msg_alloc(mbox_flash, command);
+ if (!msg)
+ return FLASH_ERR_MALLOC_FAILED;
+
+ win->cur_pos = pos & ~(mbox_flash_mask(mbox_flash));
+ msg_put_u16(msg, 0, pos >> mbox_flash->shift);
+ rc = msg_send(mbox_flash, msg);
+ if (rc) {
+ prlog(PR_ERR, "Failed to enqueue/send BMC MBOX message\n");
+ goto out;
+ }
+
+ rc = wait_for_bmc(mbox_flash, MBOX_DEFAULT_TIMEOUT);
+ if (rc) {
+ prlog(PR_ERR, "Error waiting for BMC\n");
+ goto out;
+ }
+
+ *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;
+
+out:
+ msg_free_memory(msg);
+ 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);
+
+ prlog(PR_TRACE, "Flash write at 0x%08llx for 0x%08llx\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;
+
+ /*
+ * Must flush here as changing the window contents
+ * without flushing entitles the BMC to throw away the
+ * data
+ */
+ rc = mbox_flash_flush(mbox_flash, pos, size);
+ 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);
+
+ prlog(PR_TRACE, "Flash read at 0x%08llx for 0x%08llx\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;
+ }
+ return rc;
+}
+
+static int mbox_flash_get_info(struct blocklevel_device *bl, const char **name,
+ uint64_t *total_size, uint32_t *erase_granule)
+{
+ struct mbox_flash_data *mbox_flash;
+ struct bmc_mbox_msg *msg;
+ int rc;
+
+ mbox_flash = container_of(bl, struct mbox_flash_data, bl);
+ msg = msg_alloc(mbox_flash, MBOX_C_GET_FLASH_INFO);
+ if (!msg)
+ return FLASH_ERR_MALLOC_FAILED;
+
+ /*
+ * 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);
+ if (rc) {
+ prlog(PR_ERR, "Failed to enqueue/send BMC MBOX message\n");
+ goto out;
+ }
+
+ if (wait_for_bmc(mbox_flash, MBOX_DEFAULT_TIMEOUT)) {
+ prlog(PR_ERR, "Error waiting for BMC\n");
+ goto out;
+ }
+
+ 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;
+
+out:
+ msg_free_memory(msg);
+ return rc;
+}
+
+static int mbox_flash_erase(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 extersise
+ * for the future.
+ */
+ return 0;
+}
+
+static void mbox_flash_callback(struct bmc_mbox_msg *msg, void *priv)
+{
+ struct mbox_flash_data *mbox_flash = priv;
+
+ prlog(PR_TRACE, "%s: BMC OK\n", __func__);
+
+ 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->seq != mbox_flash->seq) {
+ /* Uhoh */
+ prlog(PR_ERR, "Sequence numbers don't match! Got: %02x Expected: %02x\n",
+ msg->seq, mbox_flash->seq);
+ mbox_flash->rc = MBOX_R_SYSTEM_ERROR;
+ goto out;
+ }
+
+ mbox_flash->rc = 0;
+
+ switch (msg->command) {
+ case MBOX_C_RESET_STATE:
+ break;
+ case MBOX_C_GET_MBOX_INFO:
+ mbox_flash->read.size = msg_get_u16(msg, 1) << mbox_flash->shift;
+ mbox_flash->write.size = msg_get_u16(msg, 3) << mbox_flash->shift;
+ break;
+ case MBOX_C_GET_FLASH_INFO:
+ mbox_flash->total_size = msg_get_u32(msg, 0);
+ mbox_flash->erase_granule = msg_get_u32(msg, 4);
+ break;
+ case MBOX_C_CREATE_READ_WINDOW:
+ mbox_flash->read.lpc_addr = msg_get_u16(msg, 0) << mbox_flash->shift;
+ mbox_flash->read.open = true;
+ mbox_flash->write.open = false;
+ break;
+ case MBOX_C_CLOSE_WINDOW:
+ break;
+ case MBOX_C_CREATE_WRITE_WINDOW:
+ mbox_flash->write.lpc_addr = msg_get_u16(msg, 0) << mbox_flash->shift;
+ mbox_flash->write.open = true;
+ mbox_flash->read.open = false;
+ break;
+ case MBOX_C_MARK_WRITE_DIRTY:
+ break;
+ case MBOX_C_WRITE_FLUSH:
+ break;
+ case MBOX_C_BMC_EVENT_ACK:
+ break;
+ default:
+ prlog(PR_ERR, "Got response to unknown command %02x\n", msg->command);
+ mbox_flash->rc = -1;
+ }
+
+out:
+ mbox_flash->busy = false;
+}
+
+int mbox_flash_init(struct blocklevel_device **bl)
+{
+ struct mbox_flash_data *mbox_flash;
+ struct bmc_mbox_msg *msg;
+ int rc;
+
+ if (!bl)
+ return FLASH_ERR_PARM_ERROR;
+
+ *bl = NULL;
+
+ mbox_flash = zalloc(sizeof(struct mbox_flash_data));
+ if (!mbox_flash)
+ return FLASH_ERR_MALLOC_FAILED;
+
+ /* For V1 of the protocol this is fixed. This could change */
+ mbox_flash->shift = 12;
+
+ bmc_mbox_register_callback(&mbox_flash_callback, mbox_flash);
+
+ msg = msg_alloc(mbox_flash, MBOX_C_GET_MBOX_INFO);
+ if (!msg) {
+ rc = FLASH_ERR_MALLOC_FAILED;
+ goto out;
+ }
+
+ msg_put_u8(msg, 0, 1); /* V1, do better */
+
+ rc = msg_send(mbox_flash, msg);
+ if (rc) {
+ prlog(PR_ERR, "Failed to enqueue/send BMC MBOX message\n");
+ goto out_msg;
+ }
+
+ rc = wait_for_bmc(mbox_flash, MBOX_DEFAULT_TIMEOUT);
+ if (rc) {
+ prlog(PR_ERR, "Error waiting for BMC\n");
+ goto out_msg;
+ }
+
+ mbox_flash->bl.keep_alive = 0;
+ mbox_flash->bl.read = &mbox_flash_read;
+ mbox_flash->bl.write = &mbox_flash_write;
+ mbox_flash->bl.erase = &mbox_flash_erase;
+ mbox_flash->bl.get_info = &mbox_flash_get_info;
+
+ *bl = &(mbox_flash->bl);
+ return 0;
+
+out_msg:
+ msg_free_memory(msg);
+out:
+ free(mbox_flash);
+ return rc;
+}
+
+void mbox_flash_exit(struct blocklevel_device *bl)
+{
+ struct mbox_flash_data *mbox_flash;
+ if (bl) {
+ mbox_flash = container_of(bl, struct mbox_flash_data, bl);
+ free(mbox_flash);
+ }
+}
diff --git a/libflash/mbox-flash.h b/libflash/mbox-flash.h
new file mode 100644
index 0000000000..cd587d4ca8
--- /dev/null
+++ b/libflash/mbox-flash.h
@@ -0,0 +1,24 @@
+/* Copyright 2017 IBM Corp.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ * implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __LIBFLASH_MBOX_FLASH_H
+#define __LIBFLASH_MBOX_FLASH_H
+
+int mbox_flash_init(struct blocklevel_device **bl);
+void mbox_flash_exit(struct blocklevel_device *bl);
+#endif /* __LIBFLASH_MBOX_FLASH_H */
+
+
--
2.9.3
More information about the Skiboot
mailing list