diff options
author | Cyril Bur <cyril.bur@au1.ibm.com> | 2017-02-22 20:31:01 +1100 |
---|---|---|
committer | Stewart Smith <stewart@linux.vnet.ibm.com> | 2017-02-23 16:04:27 +1100 |
commit | 23fe769115c41e8859ce3d23dc75953bfb290f45 (patch) | |
tree | b6a9de29eef704d45c71557480b5ee68279d988a | |
parent | c78cd1b4d6c14d17ec202f503b4182b6fd96afbd (diff) | |
download | skiboot-23fe769115c41e8859ce3d23dc75953bfb290f45.zip skiboot-23fe769115c41e8859ce3d23dc75953bfb290f45.tar.gz skiboot-23fe769115c41e8859ce3d23dc75953bfb290f45.tar.bz2 |
libflash: blocklevel backend for MBOX flash access
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@au1.ibm.com>
Signed-off-by: Michael Neuling <mikey@neuling.org>
Signed-off-by: Stewart Smith <stewart@linux.vnet.ibm.com>
-rw-r--r-- | libflash/Makefile.inc | 2 | ||||
-rw-r--r-- | libflash/mbox-flash.c | 576 | ||||
-rw-r--r-- | libflash/mbox-flash.h | 24 |
3 files changed, 601 insertions, 1 deletions
diff --git a/libflash/Makefile.inc b/libflash/Makefile.inc index 4db02a1..ea64eb4 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 0000000..7bf731d --- /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 0000000..cd587d4 --- /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 */ + + |