diff options
-rw-r--r-- | Makefile.am | 6 | ||||
-rw-r--r-- | common.h | 58 | ||||
-rw-r--r-- | configure.ac | 4 | ||||
-rw-r--r-- | dbus.h | 65 | ||||
-rw-r--r-- | mbox.h | 193 | ||||
-rw-r--r-- | mboxd.c | 760 | ||||
-rw-r--r-- | mboxd_dbus.c | 372 | ||||
-rw-r--r-- | mboxd_dbus.h | 24 | ||||
-rw-r--r-- | mboxd_flash.c | 278 | ||||
-rw-r--r-- | mboxd_flash.h | 34 | ||||
-rw-r--r-- | mboxd_lpc.c | 192 | ||||
-rw-r--r-- | mboxd_lpc.h | 26 | ||||
-rw-r--r-- | mboxd_msg.c | 802 | ||||
-rw-r--r-- | mboxd_msg.h | 44 | ||||
-rw-r--r-- | mboxd_windows.c | 588 | ||||
-rw-r--r-- | mboxd_windows.h | 48 |
16 files changed, 2959 insertions, 535 deletions
diff --git a/Makefile.am b/Makefile.am index 2b86e66..0947425 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,6 +1,6 @@ ACLOCAL_AMFLAGS = -I m4 sbin_PROGRAMS = mboxd -mboxd_SOURCES = mboxd.c common.c -mboxd_LDFLAGS = $(SYSTEMD_LIBS) -mboxd_CFLAGS = $(SYSTEMD_CFLAGS) +mboxd_SOURCES = mboxd.c common.c mboxd_dbus.c mboxd_flash.c mboxd_lpc.c mboxd_msg.c mboxd_windows.c +mboxd_LDFLAGS = $(LIBSYSTEMD_LIBS) +mboxd_CFLAGS = $(LIBSYSTEMD_CFLAGS) @@ -4,16 +4,19 @@ * 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 + * 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. + * 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 COMMON_H +#define COMMON_H + #ifndef PREFIX #define PREFIX "" #endif @@ -24,6 +27,13 @@ enum { MBOX_LOG_DEBUG = 2 } verbosity; +#define MSG_OUT(f_, ...) do { if (verbosity >= MBOX_LOG_DEBUG) { \ + mbox_log(LOG_INFO, f_, ##__VA_ARGS__); \ + } } while (0) +#define MSG_ERR(f_, ...) do { if (verbosity >= MBOX_LOG_VERBOSE) { \ + mbox_log(LOG_ERR, f_, ##__VA_ARGS__); \ + } } while (0) + void (*mbox_vlog)(int p, const char *fmt, va_list args); void mbox_log_console(int p, const char *fmt, va_list args); @@ -39,4 +49,40 @@ uint32_t get_u32(uint8_t *ptr); void put_u32(uint8_t *ptr, uint32_t val); +static inline uint32_t align_up(uint32_t val, uint32_t size) +{ + return (((val) + (size) - 1) & ~((size) - 1)); +} + +static inline uint32_t align_down(uint32_t val, uint32_t size) +{ + return ((val) & ~(((size) - 1))); +} + +static inline uint32_t min_u32(uint32_t a, uint32_t b) +{ + if (a <= b) { + return a; + } + + return b; +} + +static inline int log_2(int val) +{ + int ret = 0; + + if (val <= 0) { + return -1; + } + + while (val >>= 1) { + ret++; + } + + return ret; +} + char *get_dev_mtd(void); + +#endif /* COMMON_H */ diff --git a/configure.ac b/configure.ac index f3376e1..707eefe 100644 --- a/configure.ac +++ b/configure.ac @@ -42,6 +42,10 @@ AS_IF([test "x$enable_oe_sdk" == "xyes"], AC_SUBST([OESDK_TESTCASE_FLAGS], [$testcase_flags]) ) +PKG_CHECK_MODULES(LIBSYSTEMD, libsystemd, , AC_MSG_ERROR([libsytemd not found])) +AC_SUBST([LIBSYSTEMD_CFLAGS]) +AC_SUBST([LIBSYSTEMD_LIBS]) + # Create configured output AC_CONFIG_FILES([Makefile]) AC_OUTPUT @@ -0,0 +1,65 @@ +/* + * Copyright 2016 IBM + * + * 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 MBOX_DBUS_H +#define MBOX_DBUS_H + +#define DBUS_NAME "org.openbmc.mboxd" +#define DOBJ_NAME "/org/openbmc/mboxd" + +/* Commands */ +#define DBUS_C_PING 0x00 +#define DBUS_C_DAEMON_STATE 0x01 +#define DBUS_C_RESET 0x02 +#define DBUS_C_SUSPEND 0x03 +#define DBUS_C_RESUME 0x04 +#define DBUS_C_MODIFIED 0x05 +#define DBUS_C_KILL 0x06 +#define DBUS_C_LPC_STATE 0x07 +#define NUM_DBUS_CMDS (DBUS_C_LPC_STATE + 1) + +/* Command Args */ +/* Resume */ +#define RESUME_NUM_ARGS 1 +#define RESUME_NOT_MODIFIED 0x00 +#define RESUME_FLASH_MODIFIED 0x01 + +/* Return Values */ +#define DBUS_SUCCESS 0x00 /* Command Succeded */ +#define E_DBUS_INTERNAL 0x01 /* Internal DBUS Error */ +#define E_DBUS_INVAL 0x02 /* Invalid Command */ +#define E_DBUS_REJECTED 0x03 /* Daemon Rejected Request */ +#define E_DBUS_HARDWARE 0x04 /* BMC Hardware Error */ + +/* Response Args */ +/* Status */ +#define DAEMON_STATE_NUM_ARGS 1 +#define DAEMON_STATE_ACTIVE 0x00 /* Daemon Active */ +#define DAEMON_STATE_SUSPENDED 0x01 /* Daemon Suspended */ +/* LPC State */ +#define LPC_STATE_NUM_ARGS 1 +#define LPC_STATE_INVALID 0x00 /* Invalid State */ +#define LPC_STATE_FLASH 0x01 /* LPC Maps Flash Directly */ +#define LPC_STATE_MEM 0x02 /* LPC Maps Memory */ + +struct mbox_dbus_msg { + uint8_t cmd; + size_t num_args; + uint8_t *args; +}; + +#endif /* MBOX_DBUS_H */ @@ -1,53 +1,168 @@ -/* Copyright 2016 IBM +/* + * Copyright 2016 IBM * * 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 + * 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. + * 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 MBOX_C_RESET_STATE 0x01 -#define MBOX_C_GET_MBOX_INFO 0x02 -#define MBOX_C_GET_FLASH_INFO 0x03 -#define MBOX_C_READ_WINDOW 0x04 -#define MBOX_C_CLOSE_WINDOW 0x05 -#define MBOX_C_WRITE_WINDOW 0x06 -#define MBOX_C_WRITE_DIRTY 0x07 -#define MBOX_C_WRITE_FENCE 0x08 -#define MBOX_C_ACK 0x09 -#define MBOX_C_COMPLETED_COMMANDS 0x0a - -#define MBOX_R_SUCCESS 0x01 -#define MBOX_R_PARAM_ERROR 0x02 -#define MBOX_R_WRITE_ERROR 0x03 -#define MBOX_R_SYSTEM_ERROR 0x4 -#define MBOX_R_TIMEOUT 0x05 - -#define MBOX_HOST_PATH "/dev/aspeed-mbox" -#define MBOX_HOST_TIMEOUT_SEC 1 -#define MBOX_DATA_BYTES 11 -#define MBOX_REG_BYTES 16 -#define MBOX_HOST_BYTE 14 -#define MBOX_BMC_BYTE 15 - -struct mbox_msg { - uint8_t command; - uint8_t seq; - uint8_t data[MBOX_DATA_BYTES]; - uint8_t response; +#ifndef MBOX_H +#define MBOX_H + +#include <mtd/mtd-abi.h> +#include <systemd/sd-bus.h> + +enum api_version { + API_VERSION_INVAL = 0, + API_VERSION_1 = 1, + API_VERSION_2 = 2 +}; + +#define API_MIN_VERSION API_VERSION_1 +#define API_MAX_VERSION API_VERSION_2 + +#define THIS_NAME "Mailbox Daemon" +#define SUB_VERSION 0 + +/* Command Values */ +#define MBOX_C_RESET_STATE 0x01 +#define MBOX_C_GET_MBOX_INFO 0x02 +#define MBOX_C_GET_FLASH_INFO 0x03 +#define MBOX_C_READ_WINDOW 0x04 +#define MBOX_C_CLOSE_WINDOW 0x05 +#define MBOX_C_WRITE_WINDOW 0x06 +#define MBOX_C_WRITE_DIRTY 0x07 +#define MBOX_C_WRITE_FLUSH 0x08 +#define MBOX_C_ACK 0x09 +#define MBOX_C_WRITE_ERASE 0x0a +#define NUM_MBOX_CMDS MBOX_C_WRITE_ERASE + +/* Response Values */ +#define MBOX_R_SUCCESS 0x01 +#define MBOX_R_PARAM_ERROR 0x02 +#define MBOX_R_WRITE_ERROR 0x03 +#define MBOX_R_SYSTEM_ERROR 0x04 +#define MBOX_R_TIMEOUT 0x05 +#define MBOX_R_BUSY 0x06 +#define MBOX_R_WINDOW_ERROR 0x07 + +/* Argument Flags */ +#define FLAGS_NONE 0x00 +#define FLAGS_SHORT_LIFETIME 0x01 + +/* BMC Event Notification */ +#define BMC_EVENT_REBOOT 0x01 +#define BMC_EVENT_WINDOW_RESET 0x02 +#define BMC_EVENT_ACK_MASK (BMC_EVENT_REBOOT | \ + BMC_EVENT_WINDOW_RESET) +#define BMC_EVENT_FLASH_CTRL_LOST 0x40 +#define BMC_EVENT_DAEMON_READY 0x80 +#define BMC_EVENT_V1_MASK BMC_EVENT_REBOOT +#define BMC_EVENT_V2_MASK (BMC_EVENT_REBOOT | \ + BMC_EVENT_WINDOW_RESET | \ + BMC_EVENT_FLASH_CTRL_LOST | \ + BMC_EVENT_DAEMON_READY) + +/* MBOX Registers */ +#define MBOX_HOST_PATH "/dev/aspeed-mbox" +#define MBOX_HOST_TIMEOUT_SEC 1 +#define MBOX_ARGS_BYTES 11 +#define MBOX_REG_BYTES 16 +#define MBOX_HOST_EVENT 14 +#define MBOX_BMC_EVENT 15 + +#define BLOCK_SIZE_SHIFT_V1 12 /* 4K */ + +/* Window Dirty/Erase bytemap masks */ +#define WINDOW_CLEAN 0x00 +#define WINDOW_DIRTY 0x01 +#define WINDOW_ERASED 0x02 + +/* Put polled file descriptors first */ +#define DBUS_FD 0 +#define MBOX_FD 1 +#define SIG_FD 2 +#define POLL_FDS 3 /* Number of FDs we poll on */ +#define LPC_CTRL_FD 3 +#define MTD_FD 4 +#define TOTAL_FDS 5 + +#define MAPS_FLASH (1 << 0) +#define MAPS_MEM (1 << 1) +#define STATE_SUSPENDED (1 << 7) +enum mbox_state { + /* Still Initing */ + UNINITIALISED = 0, + /* Active and LPC Maps Flash */ + ACTIVE_MAPS_FLASH = MAPS_FLASH, + /* Suspended and LPC Maps Flash */ + SUSPEND_MAPS_FLASH = STATE_SUSPENDED | MAPS_FLASH, + /* Active and LPC Maps Memory */ + ACTIVE_MAPS_MEM = MAPS_MEM, + /* Suspended and LPC Maps Memory */ + SUSPEND_MAPS_MEM = STATE_SUSPENDED | MAPS_MEM +}; + +#define FLASH_OFFSET_UNINIT 0xFFFFFFFF + +struct window_context { + void *mem; /* Portion of Reserved Memory Region */ + uint32_t flash_offset; /* Flash area the window maps (bytes) */ + uint32_t size; /* Window Size (bytes) power-of-2 */ + uint8_t *dirty_bmap; /* Bytemap of the dirty/erased state */ + uint32_t age; /* Used for LRU eviction scheme */ }; -union mbox_regs { - char raw[MBOX_REG_BYTES]; - struct mbox_msg msg; +struct window_list { + uint32_t num; + uint32_t max_age; + uint32_t default_size; + struct window_context *window; }; +struct mbox_context { +/* System State */ + enum mbox_state state; + enum api_version version; + struct pollfd fds[TOTAL_FDS]; + sd_bus *bus; + bool terminate; + uint8_t bmc_events; + +/* Window State */ + /* The window list struct containing all current "windows" */ + struct window_list windows; + /* The window the host is currently pointed at */ + struct window_context *current; + /* Is the current window a write one */ + bool current_is_write; + +/* Memory & Flash State */ + /* Reserved Memory Region */ + void *mem; + /* Reserved Mem Size (bytes) */ + uint32_t mem_size; + /* LPC Bus Base Address (bytes) */ + uint32_t lpc_base; + /* Flash size from command line (bytes) */ + uint32_t flash_size; + /* Bytemap of the erased state of the entire flash */ + uint8_t *flash_bmap; + /* Erase size (as a shift) */ + uint32_t erase_size_shift; + /* Block size (as a shift) */ + uint32_t block_size_shift; + /* Actual Flash Info */ + struct mtd_info_user mtd_info; +}; +#endif /* MBOX_H */ @@ -1,19 +1,23 @@ -/* Copyright 2016 IBM +/* + * Mailbox Daemon Implementation + * + * Copyright 2016 IBM * * 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 + * 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. + * 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 _GNU_SOURCE #include <assert.h> #include <errno.h> #include <fcntl.h> @@ -32,577 +36,359 @@ #include <sys/stat.h> #include <sys/timerfd.h> #include <sys/types.h> +#include <sys/signalfd.h> #include <time.h> #include <unistd.h> #include <inttypes.h> - -#include <mtd/mtd-abi.h> - -#include <linux/aspeed-lpc-ctrl.h> +#include <systemd/sd-bus.h> #include "mbox.h" #include "common.h" +#include "dbus.h" +#include "mboxd_dbus.h" +#include "mboxd_flash.h" +#include "mboxd_lpc.h" +#include "mboxd_msg.h" +#include "mboxd_windows.h" + +#define USAGE \ +"\nUsage: %s [-V | --version] [-h | --help] [-v[v] | --verbose] [-s | --syslog]\n" \ +"\t\t-n | --window-num <num>\n" \ +"\t\t-w | --window-size <size>M\n" \ +"\t\t-f | --flash <size>[K|M]\n\n" \ +"\t-v | --verbose\t\tBe [more] verbose\n" \ +"\t-s | --syslog\t\tLog output to syslog (pointless without -v)\n" \ +"\t-n | --window-num\tThe number of windows\n" \ +"\t-w | --window-size\tThe window size (power of 2) in MB\n" \ +"\t-f | --flash\t\tSize of flash in [K|M] bytes\n\n" + +static int poll_loop(struct mbox_context *context) +{ + int rc = 0, i; -#define LPC_CTRL_PATH "/dev/aspeed-lpc-ctrl" - - -/* Put pulled fds first */ -#define MBOX_FD 0 -#define POLL_FDS 1 -#define LPC_CTRL_FD 1 -#define MTD_FD 2 -#define TOTAL_FDS 3 + /* Set POLLIN on polling file descriptors */ + for (i = 0; i < POLL_FDS; i++) { + context->fds[i].events = POLLIN; + } -#define ALIGN_UP(_v, _a) (((_v) + (_a) - 1) & ~((_a) - 1)) + while (1) { + rc = poll(context->fds, POLL_FDS, -1); -#define MSG_OUT(f_, ...) do { if (verbosity != MBOX_LOG_NONE) { mbox_log(LOG_INFO, f_, ##__VA_ARGS__); } } while(0) -#define MSG_ERR(f_, ...) do { if (verbosity != MBOX_LOG_NONE) { mbox_log(LOG_ERR, f_, ##__VA_ARGS__); } } while(0) + if (rc < 0) { /* Error */ + MSG_ERR("Error from poll(): %s\n", strerror(errno)); + break; /* This should mean we clean up nicely */ + } -#define BOOT_HICR7 0x30000e00U -#define BOOT_HICR8 0xfe0001ffU + /* Event on Polled File Descriptor - Handle It */ + if (context->fds[SIG_FD].revents & POLLIN) { /* Signal */ + struct signalfd_siginfo info = { 0 }; -struct mbox_context { - struct pollfd fds[TOTAL_FDS]; - void *lpc_mem; - uint32_t base; - uint32_t size; - uint32_t pgsize; - bool dirty; - uint32_t dirtybase; - uint32_t dirtysize; - struct mtd_info_user mtd_info; - uint32_t flash_size; -}; + rc = read(context->fds[SIG_FD].fd, (void *) &info, + sizeof(info)); + if (rc != sizeof(info)) { + MSG_ERR("Error reading signal event: %s\n", + strerror(errno)); + } -static int running = 1; -static int sighup = 0; + switch (info.ssi_signo) { + case SIGINT: + case SIGTERM: + MSG_OUT("Caught Signal - Exiting...\n"); + context->terminate = true; + break; + case SIGHUP: + /* Host didn't request reset -> Notify it */ + reset_all_windows(context, SET_BMC_EVENT); + rc = point_to_flash(context); + if (rc < 0) { + MSG_ERR("WARNING: Failed to point the " + "LPC bus back to flash on " + "SIGHUP\nIf the host requires " + "this expect problems...\n"); + } + break; + default: + MSG_ERR("Unhandled Signal: %d\n", + info.ssi_signo); + break; + } + } + if (context->fds[DBUS_FD].revents & POLLIN) { /* DBUS */ + while ((rc = sd_bus_process(context->bus, NULL)) > 0); + if (rc < 0) { + MSG_ERR("Error handling DBUS event: %s\n", + strerror(-rc)); + } + } + if (context->terminate) { + break; /* This should mean we clean up nicely */ + } + if (context->fds[MBOX_FD].revents & POLLIN) { /* MBOX */ + rc = dispatch_mbox(context); + if (rc < 0) { + MSG_ERR("Error handling MBOX event\n"); + } + } + } -static int point_to_flash(struct mbox_context *context) -{ - struct aspeed_lpc_ctrl_mapping map = { 0 }; - int r = 0; - - /* - * Point it to the real flash for sanity. - * - * This function assumes 32MB of flash which means that that - * hostboot expects flash to be at 0x0e000000 - 0x0fffffff on the - * LPC bus. If the machine actually has 64MB of flash then the - * map.addr should be 0x0c000000. TODO - * - * Until hostboot learns how to talk to this daemon this hardcode will - * get hostboot going. Furthermore, when hostboot does learn to talk - * then this mapping is unnecessary and this code should be removed. - */ - - /* - * The mask is because the top nibble is the host LPC FW space, we - * want space 0. The struct has been zeroed, best to be explicit - * though. - */ - map.addr = (0UL - context->flash_size) & 0x0fffffff; - map.size = context->flash_size; - map.offset = 0; - map.window_type = ASPEED_LPC_CTRL_WINDOW_FLASH; - map.window_id = 0; /* Theres only one */ - - MSG_OUT("Pointing HOST LPC bus at the actual flash\n"); - MSG_OUT("Assuming %dMB of flash: HOST LPC 0x%08x\n", context->flash_size >> 20, - map.addr); - - if (ioctl(context->fds[LPC_CTRL_FD].fd, ASPEED_LPC_CTRL_IOCTL_MAP, &map) == -1) { - r = -errno; - MSG_ERR("Couldn't MAP the host LPC bus to the platform flash\n"); + /* Best to reset windows and point back to flash for safety */ + /* Host didn't request reset -> Notify it */ + reset_all_windows(context, SET_BMC_EVENT); + rc = point_to_flash(context); + /* Not much we can do if this fails */ + if (rc < 0) { + MSG_ERR("WARNING: Failed to point the LPC bus back to flash\n" + "If the host requires this expect problems...\n"); } - return r; + return rc; } -static int flash_write(struct mbox_context *context, uint32_t pos, uint32_t len) +static int init_signals(struct mbox_context *context, sigset_t *set) { int rc; - struct erase_info_user erase_info = { - .start = pos, - }; - - assert(context); - - erase_info.length = ALIGN_UP(len, context->mtd_info.erasesize); - MSG_OUT("Erasing 0x%08x for 0x%08x (aligned: 0x%08x)\n", pos, len, erase_info.length); - if (ioctl(context->fds[MTD_FD].fd, MEMERASE, &erase_info) == -1) { - MSG_ERR("Couldn't MEMERASE ioctl, flash write lost: %s\n", strerror(errno)); - return -1; + /* Block SIGHUPs, SIGTERMs and SIGINTs */ + sigemptyset(set); + sigaddset(set, SIGHUP); + sigaddset(set, SIGINT); + sigaddset(set, SIGTERM); + rc = sigprocmask(SIG_BLOCK, set, NULL); + if (rc < 0) { + MSG_ERR("Failed to set SIG_BLOCK mask %s\n", strerror(errno)); + return rc; } - if (lseek(context->fds[MTD_FD].fd, pos, SEEK_SET) == (off_t) -1) { - MSG_ERR("Couldn't seek to 0x%08x into MTD, flash write lost: %s\n", pos, strerror(errno)); - return -1; - } - - while (erase_info.length) { - rc = write(context->fds[MTD_FD].fd, context->lpc_mem + pos, erase_info.length); - if (rc == -1) { - MSG_ERR("Couldn't write to flash! Flash write lost: %s\n", strerror(errno)); - return -1; - } - erase_info.length -= rc; - pos += rc; + /* Get Signal File Descriptor */ + rc = signalfd(-1, set, SFD_NONBLOCK); + if (rc < 0) { + MSG_ERR("Failed to get signalfd %s\n", strerror(errno)); + return rc; } + context->fds[SIG_FD].fd = rc; return 0; } -/* TODO: Add come consistency around the daemon exiting and either - * way, ensuring it responds. - * I'm in favour of an approach where it does its best to stay alive - * and keep talking, the hacky prototype was written the other way. - * This function is now inconsistent - */ -static int dispatch_mbox(struct mbox_context *context) +static void usage(const char *name) { - int r = 0; - int len; - off_t pos; - uint8_t byte; - union mbox_regs resp, req = { 0 }; - uint16_t sizepg, basepg, dirtypg; - uint32_t dirtycount; - struct aspeed_lpc_ctrl_mapping map = { 0 }; - - assert(context); - - map.addr = context->base; - map.size = context->size; - map.offset = 0; - map.window_type = ASPEED_LPC_CTRL_WINDOW_MEMORY; - map.window_id = 0; /* Theres only one */ - - MSG_OUT("Dispatched to mbox\n"); - r = read(context->fds[MBOX_FD].fd, &req, sizeof(req.raw)); - if (r < 0) { - r = -errno; - MSG_ERR("Couldn't read: %s\n", strerror(errno)); - goto out; - } - if (r < sizeof(req.msg)) { - MSG_ERR("Short read: %d expecting %zu\n", r, sizeof(req.msg)); - r = -1; - goto out; - } + printf(USAGE, name); +} - /* We are NOT going to update the last two 'status' bytes */ - memcpy(&resp, &req, sizeof(req.msg)); - - sizepg = context->size >> context->pgsize; - basepg = context->base >> context->pgsize; - MSG_OUT("Got data in with command %d\n", req.msg.command); - switch (req.msg.command) { - case MBOX_C_RESET_STATE: - /* Called by early hostboot? TODO */ - resp.msg.response = MBOX_R_SUCCESS; - r = point_to_flash(context); - if (r) { - resp.msg.response = MBOX_R_SYSTEM_ERROR; - MSG_ERR("Couldn't point the LPC BUS back to actual flash\n"); - } - break; - case MBOX_C_GET_MBOX_INFO: - /* TODO Freak if data.data[0] isn't 1 */ - resp.msg.data[0] = 1; - put_u16(&resp.msg.data[1], sizepg); - put_u16(&resp.msg.data[3], sizepg); - resp.msg.response = MBOX_R_SUCCESS; - /* Wow that can't stay negated thats horrible */ - MSG_OUT("LPC_CTRL_IOCTL_MAP to 0x%08x for 0x%08x\n", map.addr, map.size); - r = ioctl(context->fds[LPC_CTRL_FD].fd, - ASPEED_LPC_CTRL_IOCTL_MAP, &map); - if (r < 0) { - r = -errno; - resp.msg.response = MBOX_R_SYSTEM_ERROR; - MSG_ERR("Couldn't MAP ioctl(): %s\n", strerror(errno)); - } - break; - case MBOX_C_GET_FLASH_INFO: - put_u32(&resp.msg.data[0], context->flash_size); - put_u32(&resp.msg.data[4], context->mtd_info.erasesize); - resp.msg.response = MBOX_R_SUCCESS; +static bool parse_cmdline(int argc, char **argv, + struct mbox_context *context) +{ + char *endptr; + int opt, i; + + static const struct option long_options[] = { + { "flash", required_argument, 0, 'f' }, + { "window-size", optional_argument, 0, 'w' }, + { "window-num", optional_argument, 0, 'n' }, + { "verbose", no_argument, 0, 'v' }, + { "syslog", no_argument, 0, 's' }, + { "version", no_argument, 0, 'V' }, + { "help", no_argument, 0, 'h' }, + { 0, 0, 0, 0 } + }; + + verbosity = MBOX_LOG_NONE; + mbox_vlog = &mbox_log_console; + + /* Default to 1 window of size flash_size */ + context->windows.default_size = context->flash_size; + context->windows.num = 1; + context->current = NULL; /* No current window */ + + while ((opt = getopt_long(argc, argv, "f:w::n::vsVh", long_options, NULL)) + != -1) { + switch (opt) { + case 0: break; - case MBOX_C_READ_WINDOW: - /* - * We could probably play tricks with LPC mapping. - * That would require kernel involvement. - * We could also always copy the relevant flash part to - * context->base even if it turns out that offset is in - * the window... - * This approach is easiest. - */ - if (context->dirty) { - r = read(context->fds[MTD_FD].fd, context->lpc_mem, context->size); - if (r != context->size) { - MSG_ERR("Short read: %d expecting %"PRIu32"\n", r, context->size); - goto out; - } + case 'f': + context->flash_size = strtol(optarg, &endptr, 10); + if (optarg == endptr) { + fprintf(stderr, "Unparseable flash size\n"); + return false; } - basepg += get_u16(&req.msg.data[0]); - put_u16(&resp.msg.data[0], basepg); - resp.msg.response = MBOX_R_SUCCESS; - context->dirty = false; - break; - case MBOX_C_CLOSE_WINDOW: - context->dirty = true; - break; - case MBOX_C_WRITE_WINDOW: - basepg += get_u16(&req.msg.data[0]); - put_u16(&resp.msg.data[0], basepg); - resp.msg.response = MBOX_R_SUCCESS; - context->dirtybase = basepg << context->pgsize; - break; - /* Optimise these later */ - case MBOX_C_WRITE_DIRTY: - case MBOX_C_WRITE_FENCE: - dirtypg = get_u16(&req.msg.data[0]); - dirtycount = get_u32(&req.msg.data[2]); - if (dirtycount == 0) { - resp.msg.response = MBOX_R_PARAM_ERROR; + switch (*endptr) { + case '\0': break; - } - /* - * dirtypg is actually offset within window so we probs - * need to know if the window isn't at zero - */ - if (flash_write(context, dirtypg << context->pgsize, dirtycount) != 0) { - resp.msg.response = MBOX_R_WRITE_ERROR; + case 'M': + context->flash_size <<= 10; + case 'K': + context->flash_size <<= 10; break; + default: + fprintf(stderr, "Unknown units '%c'\n", + *endptr); + return false; } - resp.msg.response = MBOX_R_SUCCESS; break; - case MBOX_C_ACK: - resp.msg.response = MBOX_R_SUCCESS; - pos = lseek(context->fds[MBOX_FD].fd, MBOX_BMC_BYTE, SEEK_SET); - if (pos != MBOX_BMC_BYTE) { - r = -errno; - MSG_ERR("Couldn't lseek() to byte %d: %s\n", MBOX_BMC_BYTE, - strerror(errno)); - } - /* - * NAND what is in the hardware and the request. - * This prevents the host being able to SET bits, it can - * only request set ones be cleared. - */ - byte = ~(req.msg.data[0] & req.raw[MBOX_BMC_BYTE]); - len = write(context->fds[MBOX_FD].fd, &byte, 1); - if (len != 1) { - r = -errno; - MSG_ERR("Couldn't write to BMC status reg: %s\n", - strerror(errno)); + case 'n': + context->windows.num = strtol(argv[optind], &endptr, + 10); + if (optarg == endptr || *endptr != '\0') { + fprintf(stderr, "Unparseable window num\n"); + return false; } - pos = lseek(context->fds[MBOX_FD].fd, 0, SEEK_SET); - if (pos != 0) { - r = -errno; - MSG_ERR("Couldn't reset MBOX offset to zero\n"); + break; + case 'w': + context->windows.default_size = strtol(argv[optind], + &endptr, 10); + context->windows.default_size <<= 20; /* Given in MB */ + if (optarg == endptr || (*endptr != '\0' && + *endptr != 'M')) { + fprintf(stderr, "Unparseable window size\n"); + return false; } break; - case MBOX_C_COMPLETED_COMMANDS: - /* This implementation always completes before responding */ - resp.msg.data[0] = 0; - resp.msg.response = MBOX_R_SUCCESS; + case 'v': + verbosity++; break; + case 's': + /* Avoid a double openlog() */ + if (mbox_vlog != &vsyslog) { + openlog(PREFIX, LOG_ODELAY, LOG_DAEMON); + mbox_vlog = &vsyslog; + } + break; + case 'V': + printf("%s v%d.%.2d\n", THIS_NAME, API_MAX_VERSION, + SUB_VERSION); + exit(0); + case 'h': + return false; /* This will print the usage message */ default: - MSG_ERR("UNKNOWN MBOX COMMAND\n"); - resp.msg.response = MBOX_R_PARAM_ERROR; - r = -1; + return false; + } } - MSG_OUT("Writing response to MBOX regs\n"); - len = write(context->fds[MBOX_FD].fd, &resp, sizeof(resp.msg)); - if (len < sizeof(resp.msg)) { - r = -errno; - MSG_ERR("Didn't write the full response\n"); + if (!context->flash_size) { + fprintf(stderr, "Must specify a non-zero flash size\n"); + return false; } -out: - return r; -} + if (!context->windows.num) { + fprintf(stderr, "Must specify a non-zero number of windows\n" + "If unsure - select 4 (-n 4)\n"); + return false; + } + if (!context->windows.default_size) { + fprintf(stderr, "Must specify a non-zero window size\n" + "If unsure - select 1M (-w 1)\n"); + return false; + } -#define CHUNKSIZE (64 * 1024) + MSG_OUT("Flash size: 0x%.8x\n", context->flash_size); + MSG_OUT("Number of Windows: %d\n", context->windows.num); + MSG_OUT("Window size: 0x%.8x\n", context->windows.default_size); -int copy_flash(struct mbox_context *context) -{ - int r; - int readsize = CHUNKSIZE; - int offset = 0; - - /* - * Copy flash into RAM early, same time. - * The kernel has created the LPC->AHB mapping also, which means - * flash should work. - * Ideally we tell the kernel whats up and when to do stuff... - */ - MSG_OUT("Loading flash into ram at %p for 0x%08x bytes\n", - context->lpc_mem, context->size); - if (lseek(context->fds[MTD_FD].fd, 0, SEEK_SET) != 0) { - r = -errno; - MSG_ERR("Couldn't reset MBOX pos to zero\n"); - return r; + context->windows.window = calloc(context->windows.num, + sizeof(*context->windows.window)); + if (!context->windows.window) { + MSG_ERR("Memory allocation failed\n"); + free(context); + exit(1); } - while (readsize) { - r = read(context->fds[MTD_FD].fd, context->lpc_mem + offset, readsize); - if (r != readsize) { - r = -errno; - MSG_ERR("Couldn't copy mtd into ram: %d\n", r); - return r; - } - offset += readsize; - readsize = context->size - offset; - if (readsize > CHUNKSIZE) - readsize = CHUNKSIZE; + + for (i = 0; i < context->windows.num; i++) { + init_window_state(&context->windows.window[i], + context->windows.default_size); } - return 0; -} -void signal_hup(int signum, siginfo_t *info, void *uc) -{ - sighup = 1; -} + if (verbosity) { + MSG_OUT("%s logging\n", verbosity == MBOX_LOG_DEBUG ? "Debug" : + "Verbose"); + } -static void usage(const char *name) -{ - fprintf(stderr, "Usage %s [ -v[v] | --syslog ] --flash=size[K | M]\n", name); - fprintf(stderr, "\t--flash size[K | M]\t Map the flash for the according to 'size' in Kilobytes or Megabytes\n"); - fprintf(stderr, "\t--verbose\t Be [more] verbose\n"); - fprintf(stderr, "\t--syslog\t Log output to syslog (pointless without -v)\n\n"); + return true; } -int main(int argc, char *argv[]) +int main(int argc, char **argv) { struct mbox_context *context; - const char *name = argv[0]; - char *pnor_filename = NULL; - int opt, polled, r, i; - struct aspeed_lpc_ctrl_mapping map = { 0 }; - struct sigaction act; - char *endptr; - - static const struct option long_options[] = { - { "flash", required_argument, 0, 'f' }, - { "verbose", no_argument, 0, 'v' }, - { "syslog", no_argument, 0, 's' }, - { 0, 0, 0, 0 } - }; + char *name = argv[0]; + sigset_t set; + int rc, i; context = calloc(1, sizeof(*context)); - for (i = 0; i < TOTAL_FDS; i++) - context->fds[i].fd = -1; - - mbox_vlog = &mbox_log_console; - while ((opt = getopt_long(argc, argv, "fv", long_options, NULL)) != -1) { - switch (opt) { - case 0: - break; - case 'f': - context->flash_size = strtol(optarg, &endptr, 0); - if (optarg == endptr) { - fprintf(stderr, "Unparseable flash size\n"); - usage(name); - exit(EXIT_FAILURE); - } - if (*endptr == 'K') { - context->flash_size <<= 10; - } else if (*endptr == 'M') { - context->flash_size <<= 20; - } else if (*endptr != '\0') { /* Unknown units */ - fprintf(stderr, "Unknown units '%c'\n", *endptr); - usage(name); - exit(EXIT_FAILURE); - } - break; - case 'v': - verbosity++; - break; - case 's': - /* Avoid a double openlog() */ - if (mbox_vlog != &vsyslog) { - openlog(PREFIX, LOG_ODELAY, LOG_DAEMON); - mbox_vlog = &vsyslog; - } - break; - default: - usage(name); - exit(EXIT_FAILURE); - } + if (!context) { + fprintf(stderr, "Memory allocation failed\n"); + exit(1); } - if (context->flash_size == 0) { - fprintf(stderr, "Must specify a non-zero flash size\n"); + if (!parse_cmdline(argc, argv, context)) { usage(name); - exit(EXIT_FAILURE); + free(context); + exit(0); } - if (verbosity == MBOX_LOG_VERBOSE) - MSG_OUT("Verbose logging\n"); - - if (verbosity == MBOX_LOG_DEBUG) - MSG_OUT("Debug logging\n"); - - MSG_OUT("Registering SigHUP hander\n"); - act.sa_sigaction = signal_hup; - sigemptyset(&act.sa_mask); - act.sa_flags = SA_SIGINFO; - if (sigaction(SIGHUP, &act, NULL) < 0) { - perror("Registering SIGHUP"); - exit(1); + for (i = 0; i < TOTAL_FDS; i++) { + context->fds[i].fd = -1; } - sighup = 0; - MSG_OUT("Starting\n"); + MSG_OUT("Starting Daemon\n"); - MSG_OUT("Opening %s\n", MBOX_HOST_PATH); - context->fds[MBOX_FD].fd = open(MBOX_HOST_PATH, O_RDWR | O_NONBLOCK); - if (context->fds[MBOX_FD].fd < 0) { - r = -errno; - MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", - MBOX_HOST_PATH, strerror(errno)); + rc = init_signals(context, &set); + if (rc) { goto finish; } - MSG_OUT("Opening %s\n", LPC_CTRL_PATH); - context->fds[LPC_CTRL_FD].fd = open(LPC_CTRL_PATH, O_RDWR | O_SYNC); - if (context->fds[LPC_CTRL_FD].fd < 0) { - r = -errno; - MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", - LPC_CTRL_PATH, strerror(errno)); + rc = init_mbox_dev(context); + if (rc) { goto finish; } - MSG_OUT("Getting buffer size...\n"); - /* This may become more variable in the future */ - context->pgsize = 12; /* 4K */ - map.window_type = ASPEED_LPC_CTRL_WINDOW_MEMORY; - map.window_id = 0; /* Theres only one */ - if (ioctl(context->fds[LPC_CTRL_FD].fd, ASPEED_LPC_CTRL_IOCTL_GET_SIZE, - &map) < 0) { - r = -errno; - MSG_OUT("fail\n"); - MSG_ERR("Couldn't get lpc control buffer size: %s\n", strerror(-r)); - goto finish; - } - /* And strip the first nibble, LPC access speciality */ - context->size = map.size; - context->base = -context->size & 0x0FFFFFFF; - - /* READ THE COMMENT AT THE START OF THIS FUNCTION! */ - r = point_to_flash(context); - if (r) { - MSG_ERR("Failed to point the LPC BUS at the actual flash: %s\n", - strerror(-r)); + rc = init_lpc_dev(context); + if (rc) { goto finish; } - MSG_OUT("Mapping %s for %u\n", LPC_CTRL_PATH, context->size); - context->lpc_mem = mmap(NULL, context->size, PROT_READ | PROT_WRITE, MAP_SHARED, - context->fds[LPC_CTRL_FD].fd, 0); - if (context->lpc_mem == MAP_FAILED) { - r = -errno; - MSG_ERR("Didn't manage to mmap %s: %s\n", LPC_CTRL_PATH, strerror(errno)); + /* We've found the reserved memory region -> we can assign to windows */ + rc = init_window_mem(context); + if (rc) { goto finish; } - pnor_filename = get_dev_mtd(); - if (!pnor_filename) { - MSG_ERR("Couldn't find the PNOR /dev/mtd partition\n"); - r = -1; + rc = init_flash_dev(context); + if (rc) { goto finish; } - MSG_OUT("Opening %s\n", pnor_filename); - context->fds[MTD_FD].fd = open(pnor_filename, O_RDWR); - if (context->fds[MTD_FD].fd < 0) { - r = -errno; - MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", - pnor_filename, strerror(errno)); + rc = init_mboxd_dbus(context); + if (rc) { goto finish; } - if (ioctl(context->fds[MTD_FD].fd, MEMGETINFO, &context->mtd_info) == -1) { - MSG_ERR("Couldn't get information about MTD: %s\n", strerror(errno)); - return -1; - } - - if (copy_flash(context)) + /* Set the LPC bus mapping to point to the physical flash device */ + rc = point_to_flash(context); + if (rc) { goto finish; - - context->fds[MBOX_FD].events = POLLIN; - - /* Test the single write facility by setting all the regs to 0xFF */ - MSG_OUT("Setting all MBOX regs to 0xff individually...\n"); - for (i = 0; i < MBOX_REG_BYTES; i++) { - uint8_t byte = 0xff; - off_t pos; - int len; - - pos = lseek(context->fds[MBOX_FD].fd, i, SEEK_SET); - if (pos != i) { - MSG_ERR("Couldn't lseek() to byte %d: %s\n", i, - strerror(errno)); - break; - } - len = write(context->fds[MBOX_FD].fd, &byte, 1); - if (len != 1) { - MSG_ERR("Couldn't write MBOX reg %d: %s\n", i, - strerror(errno)); - break; - } } - if (lseek(context->fds[MBOX_FD].fd, 0, SEEK_SET) != 0) { - r = -errno; - MSG_ERR("Couldn't reset MBOX pos to zero\n"); + + rc = set_bmc_events(context, BMC_EVENT_DAEMON_READY, SET_BMC_EVENT); + if (rc) { goto finish; } - MSG_OUT("Entering polling loop\n"); - while (running) { - polled = poll(context->fds, POLL_FDS, 1000); - if (polled == 0) - continue; - if ((polled == -1) && (errno != -EINTR) && (sighup == 1)) { - /* Got sighup. reset to point to flash and - * reread flash */ - r = point_to_flash(context); - if (r) { - goto finish; - } - r = copy_flash(context); - if (r) - goto finish; - sighup = 0; - continue; - } - if (polled < 0) { - r = -errno; - MSG_ERR("Error from poll(): %s\n", strerror(errno)); - break; - } - r = dispatch_mbox(context); - if (r < 0) { - MSG_ERR("Error handling MBOX event: %s\n", strerror(-r)); - break; - } - } + MSG_OUT("Entering Polling Loop\n"); + rc = poll_loop(context); - MSG_OUT("Exiting\n"); + MSG_OUT("Exiting Poll Loop: %d\n", rc); finish: - if (context->lpc_mem) - munmap(context->lpc_mem, context->size); - - free(pnor_filename); - close(context->fds[MTD_FD].fd); - close(context->fds[LPC_CTRL_FD].fd); - close(context->fds[MBOX_FD].fd); + MSG_OUT("Daemon Exiting...\n"); + clr_bmc_events(context, BMC_EVENT_DAEMON_READY, SET_BMC_EVENT); + + free_mboxd_dbus(context); + free_flash_dev(context); + free_lpc_dev(context); + free_mbox_dev(context); + free_window_dirty_bytemap(context); + free(context->windows.window); free(context); - return r; + return rc; } - diff --git a/mboxd_dbus.c b/mboxd_dbus.c new file mode 100644 index 0000000..3b09bb1 --- /dev/null +++ b/mboxd_dbus.c @@ -0,0 +1,372 @@ +/* + * Mailbox Daemon DBUS Helpers + * + * Copyright 2016 IBM + * + * 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 _GNU_SOURCE +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <poll.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <signal.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/timerfd.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> +#include <inttypes.h> +#include <systemd/sd-bus.h> + +#include "mbox.h" +#include "common.h" +#include "dbus.h" +#include "mboxd_dbus.h" +#include "mboxd_windows.h" +#include "mboxd_msg.h" +#include "mboxd_lpc.h" +#include "mboxd_flash.h" + +typedef int (*mboxd_dbus_handler)(struct mbox_context *, struct mbox_dbus_msg *, + struct mbox_dbus_msg *); + +/* DBUS Functions */ + +/* + * Command: DBUS Ping + * Ping the daemon + * + * Args: NONE + * Resp: NONE + */ +static int dbus_handle_ping(struct mbox_context *context, + struct mbox_dbus_msg *req, + struct mbox_dbus_msg *resp) +{ + return 0; +} + +/* + * Command: DBUS Status + * Get the status of the daemon + * + * Args: NONE + * Resp[0]: Status Code + */ +static int dbus_handle_daemon_state(struct mbox_context *context, + struct mbox_dbus_msg *req, + struct mbox_dbus_msg *resp) +{ + resp->num_args = DAEMON_STATE_NUM_ARGS; + resp->args = calloc(resp->num_args, sizeof(*resp->args)); + resp->args[0] = (context->state & STATE_SUSPENDED) ? + DAEMON_STATE_SUSPENDED : DAEMON_STATE_ACTIVE; + + return 0; +} + +/* + * Command: DBUS LPC State + * Get the state of the lpc bus mapping (whether it points to memory or flash + * + * Args: NONE + * Resp[0]: LPC Bus State Code + */ +static int dbus_handle_lpc_state(struct mbox_context *context, + struct mbox_dbus_msg *req, + struct mbox_dbus_msg *resp) +{ + resp->num_args = LPC_STATE_NUM_ARGS; + resp->args = calloc(resp->num_args, sizeof(*resp->args)); + if ((context->state & MAPS_MEM) && !(context->state & MAPS_FLASH)) { + resp->args[0] = LPC_STATE_MEM; + } else if (!(context->state & MAPS_MEM) && + (context->state & MAPS_FLASH)) { + resp->args[0] = LPC_STATE_FLASH; + } else { + resp->args[0] = LPC_STATE_INVALID; + } + + return 0; +} + +/* + * Command: DBUS Reset + * Reset the daemon state, final operation TBA. + * For now we just point the lpc mapping back at the flash. + * + * Args: NONE + * Resp: NONE + */ +static int dbus_handle_reset(struct mbox_context *context, + struct mbox_dbus_msg *req, + struct mbox_dbus_msg *resp) +{ + int rc; + + /* We don't let the host access flash if the daemon is suspened */ + if (context->state & STATE_SUSPENDED) { + return -E_DBUS_REJECTED; + } + + /* + * This will close (and flush) the current window and point the lpc bus + * mapping back to flash. Better set the bmc event to notify the host + * of this. + */ + reset_all_windows(context, SET_BMC_EVENT); + rc = point_to_flash(context); + if (rc < 0) { + return -E_DBUS_HARDWARE; + } + + return 0; +} + +/* + * Command: DBUS Kill + * Stop the daemon + * + * Args: NONE + * Resp: NONE + */ +static int dbus_handle_kill(struct mbox_context *context, + struct mbox_dbus_msg *req, + struct mbox_dbus_msg *resp) +{ + context->terminate = 1; + + MSG_OUT("DBUS Kill - Exiting...\n"); + + return 0; +} + +/* + * Command: DBUS Flash Modified + * Used to notify the daemon that the flash has been modified out from under + * it - We need to reset all out windows to ensure flash will be reloaded + * when a new window is opened. + * Note: We don't flush any previously opened windows + * + * Args: NONE + * Resp: NONE + */ +static int dbus_handle_modified(struct mbox_context *context, + struct mbox_dbus_msg *req, + struct mbox_dbus_msg *resp) +{ + /* Flash has been modified - can no longer trust our erased bytemap */ + set_flash_bytemap(context, 0, context->flash_size, FLASH_DIRTY); + + /* Force daemon to reload all windows -> Set BMC event to notify host */ + reset_all_windows(context, SET_BMC_EVENT); + + return 0; +} + +/* + * Command: DBUS Suspend + * Suspend the daemon to inhibit it from performing flash accesses. + * This is used to synchronise access to the flash between the daemon and + * directly from the BMC. + * + * Args: NONE + * Resp: NONE + */ +static int dbus_handle_suspend(struct mbox_context *context, + struct mbox_dbus_msg *req, + struct mbox_dbus_msg *resp) +{ + int rc; + + if (context->state & STATE_SUSPENDED) { + /* Already Suspended */ + return DBUS_SUCCESS; + } + + /* Nothing to check - Just set the bit to notify the host */ + rc = set_bmc_events(context, BMC_EVENT_FLASH_CTRL_LOST, SET_BMC_EVENT); + if (rc < 0) { + return -E_DBUS_HARDWARE; + } + + context->state |= STATE_SUSPENDED; + + return rc; +} + +/* + * Command: DBUS Resume + * Resume the daemon to let it perform flash accesses again. + * + * Args[0]: Flash Modified (0 - no | 1 - yes) + * Resp: NONE + */ +static int dbus_handle_resume(struct mbox_context *context, + struct mbox_dbus_msg *req, + struct mbox_dbus_msg *resp) +{ + int rc; + + if (req->num_args != 1) { + return -E_DBUS_INVAL; + } + + if (!(context->state & STATE_SUSPENDED)) { + /* We weren't suspended... */ + return DBUS_SUCCESS; + } + + if (req->args[0] == RESUME_FLASH_MODIFIED) { + /* Clear the bit and call the flash modified handler */ + clr_bmc_events(context, BMC_EVENT_FLASH_CTRL_LOST, + NO_BMC_EVENT); + rc = dbus_handle_modified(context, req, resp); + } else { + /* Flash wasn't modified - just clear the bit with writeback */ + rc = clr_bmc_events(context, BMC_EVENT_FLASH_CTRL_LOST, + SET_BMC_EVENT); + } + + if (rc < 0) { + rc = -E_DBUS_HARDWARE; + } + context->state &= ~STATE_SUSPENDED; + + return rc; +} + +static const mboxd_dbus_handler dbus_handlers[NUM_DBUS_CMDS] = { + dbus_handle_ping, + dbus_handle_daemon_state, + dbus_handle_reset, + dbus_handle_suspend, + dbus_handle_resume, + dbus_handle_modified, + dbus_handle_kill, + dbus_handle_lpc_state +}; + +static int method_cmd(sd_bus_message *m, void *userdata, + sd_bus_error *ret_error) +{ + struct mbox_dbus_msg req = { 0 }, resp = { 0 }; + struct mbox_context *context; + sd_bus_message *n; + int rc; + + context = (struct mbox_context *) userdata; + if (!context) { + MSG_ERR("DBUS Internal Error\n"); + rc = -E_DBUS_INTERNAL; + goto out; + } + + /* Read the command */ + rc = sd_bus_message_read(m, "y", &req.cmd); + if (rc < 0) { + MSG_ERR("DBUS error reading message: %s\n", strerror(-rc)); + rc = -E_DBUS_INTERNAL; + goto out; + } + + /* Read the args */ + rc = sd_bus_message_read_array(m, 'y', (const void **) &req.args, + &req.num_args); + if (rc < 0) { + MSG_ERR("DBUS error reading message: %s\n", strerror(-rc)); + rc = -E_DBUS_INTERNAL; + goto out; + } + + /* Handle the command */ + if (req.cmd >= NUM_DBUS_CMDS) { + rc = -E_DBUS_INVAL; + MSG_ERR("Received unknown dbus cmd: %d\n", req.cmd); + } else { + rc = dbus_handlers[req.cmd](context, &req, &resp); + } + +out: + if (rc < 0) { + resp.cmd = -rc; + } + sd_bus_message_new_method_return(m, &n); /* Generate response */ + sd_bus_message_append(n, "y", resp.cmd); /* Set return code */ + sd_bus_message_append_array(n, 'y', resp.args, resp.num_args); + sd_bus_send(sd_bus_message_get_bus(m), n, NULL); /* Send response */ + free(resp.args); + return 0; +} + +static const sd_bus_vtable mboxd_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD("cmd", "yay", "yay", &method_cmd, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END +}; + +int init_mboxd_dbus(struct mbox_context *context) +{ + int rc; + + rc = sd_bus_default_system(&context->bus); + if (rc < 0) { + MSG_ERR("Failed to connect to the system bus: %s\n", + strerror(-rc)); + return rc; + } + + rc = sd_bus_add_object_vtable(context->bus, NULL, DOBJ_NAME, DBUS_NAME, + mboxd_vtable, context); + if (rc < 0) { + MSG_ERR("Failed to register vtable: %s\n", strerror(-rc)); + return rc; + } + + rc = sd_bus_request_name(context->bus, DBUS_NAME, + SD_BUS_NAME_ALLOW_REPLACEMENT | + SD_BUS_NAME_REPLACE_EXISTING); + if (rc < 0) { + MSG_ERR("Failed to acquire service name: %s\n", strerror(-rc)); + return rc; + } + + rc = sd_bus_get_fd(context->bus); + if (rc < 0) { + MSG_ERR("Failed to get bus fd: %s\n", strerror(-rc)); + return rc; + } + + context->fds[DBUS_FD].fd = rc; + + return 0; +} + +void free_mboxd_dbus(struct mbox_context *context) +{ + sd_bus_unref(context->bus); +} diff --git a/mboxd_dbus.h b/mboxd_dbus.h new file mode 100644 index 0000000..8dbac5e --- /dev/null +++ b/mboxd_dbus.h @@ -0,0 +1,24 @@ +/* + * Copyright 2016 IBM + * + * 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 MBOXD_DBUS_H +#define MBOXD_DBUS_H + +int init_mboxd_dbus(struct mbox_context *context); +void free_mboxd_dbus(struct mbox_context *context); + +#endif /* MBOXD_DBUS_H */ diff --git a/mboxd_flash.c b/mboxd_flash.c new file mode 100644 index 0000000..606f63d --- /dev/null +++ b/mboxd_flash.c @@ -0,0 +1,278 @@ +/* + * Mailbox Daemon Flash Helpers + * + * Copyright 2016 IBM + * + * 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 _GNU_SOURCE +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <poll.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <signal.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/timerfd.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> +#include <inttypes.h> +#include <mtd/mtd-abi.h> + +#include "mbox.h" +#include "common.h" +#include "mboxd_flash.h" + +int init_flash_dev(struct mbox_context *context) +{ + char *filename = get_dev_mtd(); + int fd, rc = 0; + + if (!filename) { + MSG_ERR("Couldn't find the PNOR /dev/mtd partition\n"); + return -1; + } + + MSG_OUT("Opening %s\n", filename); + + /* Open Flash Device */ + fd = open(filename, O_RDWR); + if (fd < 0) { + MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", + filename, strerror(errno)); + rc = -errno; + goto out; + } + context->fds[MTD_FD].fd = fd; + + /* Read the Flash Info */ + if (ioctl(fd, MEMGETINFO, &context->mtd_info) == -1) { + MSG_ERR("Couldn't get information about MTD: %s\n", + strerror(errno)); + rc = -1; + goto out; + } + + /* We know the erase size so we can allocate the flash_erased bytemap */ + context->erase_size_shift = log_2(context->mtd_info.erasesize); + context->flash_bmap = calloc(context->flash_size >> + context->erase_size_shift, + sizeof(*context->flash_bmap)); + +out: + free(filename); + return rc; +} + +void free_flash_dev(struct mbox_context *context) +{ + free(context->flash_bmap); + close(context->fds[MTD_FD].fd); +} + +/* Flash Functions */ + +#define CHUNKSIZE (64 * 1024) + +/* + * copy_flash() - Copy data from the flash device into a provided buffer + * @context: The mbox context pointer + * @offset: The flash offset to copy from (bytes) + * @mem: The buffer to copy into (must be of atleast size) + * @size: The number of bytes to copy + * + * Return: 0 on success otherwise negative error code + */ +int copy_flash(struct mbox_context *context, uint32_t offset, void *mem, + uint32_t size) +{ + MSG_OUT("Loading flash at %p for 0x%08x bytes from offset 0x%.8x\n", + mem, size, offset); + if (lseek(context->fds[MTD_FD].fd, offset, SEEK_SET) != offset) { + MSG_ERR("Couldn't seek flash at pos: %u %s\n", offset, + strerror(errno)); + return -MBOX_R_SYSTEM_ERROR; + } + + while (size) { + uint32_t size_read = read(context->fds[MTD_FD].fd, mem, + min_u32(CHUNKSIZE, size)); + if (size_read < 0) { + MSG_ERR("Couldn't copy mtd into ram: %d. %s\n", + size_read, strerror(size_read)); + return -MBOX_R_SYSTEM_ERROR; + } + + size -= size_read; + mem += size_read; + } + + return 0; +} + +/* + * flash_is_erased() - Check if an offset into flash is erased + * @context: The mbox context pointer + * @offset: The flash offset to check (bytes) + * + * Return: true if erased otherwise false + */ +static inline bool flash_is_erased(struct mbox_context *context, + uint32_t offset) +{ + return context->flash_bmap[offset >> context->erase_size_shift] + == FLASH_ERASED; +} + +/* + * set_flash_bytemap() - Set the flash erased bytemap + * @context: The mbox context pointer + * @offset: The flash offset to set (bytes) + * @count: Number of bytes to set + * @val: Value to set the bytemap to + * + * The flash bytemap only tracks the erased status at the erase block level so + * this will update the erased state for an (or many) erase blocks + * + * Return: 0 if success otherwise negative error code + */ +int set_flash_bytemap(struct mbox_context *context, uint32_t offset, + uint32_t count, uint8_t val) +{ + if ((offset + count) > context->flash_size) { + return -MBOX_R_PARAM_ERROR; + } + + memset(context->flash_bmap + (offset >> context->erase_size_shift), + val, + align_up(count, 1 << context->erase_size_shift) >> + context->erase_size_shift); + + return 0; +} + +/* + * erase_flash() - Erase the flash + * @context: The mbox context pointer + * @offset: The flash offset to erase (bytes) + * @size: The number of bytes to erase + * + * Return: 0 on success otherwise negative error code + */ +int erase_flash(struct mbox_context *context, uint32_t offset, uint32_t count) +{ + const uint32_t erase_size = 1 << context->erase_size_shift; + struct erase_info_user erase_info = { 0 }; + int rc; + + MSG_OUT("Erasing 0x%.8x for 0x%.8x\n", offset, count); + + /* + * We have an erased_bytemap for the flash so we want to avoid erasing + * blocks which we already know to be erased. Look for runs of blocks + * which aren't erased and erase the entire run at once to avoid how + * often we have to call the erase ioctl. If the block is already + * erased then there's nothing we need to do. + */ + while (count) { + if (!flash_is_erased(context, offset)) { /* Need to erase */ + if (!erase_info.length) { /* Start of not-erased run */ + erase_info.start = offset; + } + erase_info.length += erase_size; + } else if (erase_info.length) { /* Already erased|end of run? */ + /* Erase the previous run which just ended */ + rc = ioctl(context->fds[MTD_FD].fd, MEMERASE, + &erase_info); + if (rc < 0) { + MSG_ERR("Couldn't erase flash at 0x%.8x\n", + erase_info.start); + return -MBOX_R_SYSTEM_ERROR; + } + /* Mark ERASED where we just erased */ + set_flash_bytemap(context, erase_info.start, + erase_info.length, FLASH_ERASED); + erase_info.start = 0; + erase_info.length = 0; + } + + offset += erase_size; + count -= erase_size; + } + + if (erase_info.length) { + rc = ioctl(context->fds[MTD_FD].fd, MEMERASE, &erase_info); + if (rc < 0) { + MSG_ERR("Couldn't erase flash at 0x%.8x\n", + erase_info.start); + return -MBOX_R_SYSTEM_ERROR; + } + /* Mark ERASED where we just erased */ + set_flash_bytemap(context, erase_info.start, erase_info.length, + FLASH_ERASED); + } + + return 0; +} + +/* + * write_flash() - Write the flash from a provided buffer + * @context: The mbox context pointer + * @offset: The flash offset to write to (bytes) + * @buf: The buffer to write from (must be of atleast size) + * @size: The number of bytes to write + * + * Return: 0 on success otherwise negative error code + */ +int write_flash(struct mbox_context *context, uint32_t offset, void *buf, + uint32_t count) +{ + uint32_t buf_offset = 0; + int rc; + + MSG_OUT("Writing 0x%.8x for 0x%.8x from %p\n", offset, count, buf); + + if (lseek(context->fds[MTD_FD].fd, offset, SEEK_SET) != offset) { + MSG_ERR("Couldn't seek flash at pos: %u %s\n", offset, + strerror(errno)); + return -MBOX_R_SYSTEM_ERROR; + } + + while (count) { + rc = write(context->fds[MTD_FD].fd, buf + buf_offset, count); + if (rc < 0) { + MSG_ERR("Couldn't write to flash, write lost: %s\n", + strerror(errno)); + return -MBOX_R_WRITE_ERROR; + } + /* Mark *NOT* erased where we just wrote */ + set_flash_bytemap(context, offset + buf_offset, rc, + FLASH_DIRTY); + count -= rc; + buf_offset += rc; + } + + return 0; +} diff --git a/mboxd_flash.h b/mboxd_flash.h new file mode 100644 index 0000000..69bc121 --- /dev/null +++ b/mboxd_flash.h @@ -0,0 +1,34 @@ +/* + * Copyright 2016 IBM + * + * 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 MBOXD_FLASH_H +#define MBOXD_FLASH_H + +#define FLASH_DIRTY 0x00 +#define FLASH_ERASED 0x01 + +int init_flash_dev(struct mbox_context *context); +void free_flash_dev(struct mbox_context *context); +int copy_flash(struct mbox_context *context, uint32_t offset, void *mem, + uint32_t size); +int set_flash_bytemap(struct mbox_context *context, uint32_t offset, + uint32_t count, uint8_t val); +int erase_flash(struct mbox_context *context, uint32_t offset, uint32_t count); +int write_flash(struct mbox_context *context, uint32_t offset, void *buf, + uint32_t count); + +#endif /* MBOXD_FLASH_H */ diff --git a/mboxd_lpc.c b/mboxd_lpc.c new file mode 100644 index 0000000..42e4328 --- /dev/null +++ b/mboxd_lpc.c @@ -0,0 +1,192 @@ +/* + * Mailbox Daemon LPC Helpers + * + * Copyright 2016 IBM + * + * 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 _GNU_SOURCE +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <poll.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <signal.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/timerfd.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> +#include <inttypes.h> + +#include "mbox.h" +#include "common.h" +#include "mboxd_lpc.h" +#include "mboxd_flash.h" +#include <linux/aspeed-lpc-ctrl.h> + +#define LPC_CTRL_PATH "/dev/aspeed-lpc-ctrl" + +int init_lpc_dev(struct mbox_context *context) +{ + struct aspeed_lpc_ctrl_mapping map = { + .window_type = ASPEED_LPC_CTRL_WINDOW_MEMORY, + .window_id = 0, /* There's only one */ + .flags = 0, + .addr = 0, + .offset = 0, + .size = 0 + }; + int fd; + + /* Open LPC Device */ + MSG_OUT("Opening %s\n", LPC_CTRL_PATH); + fd = open(LPC_CTRL_PATH, O_RDWR | O_SYNC); + if (fd < 0) { + MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", + LPC_CTRL_PATH, strerror(errno)); + return -errno; + } + + context->fds[LPC_CTRL_FD].fd = fd; + + /* Find Size of Reserved Memory Region */ + MSG_OUT("Getting buffer size...\n"); + if (ioctl(fd, ASPEED_LPC_CTRL_IOCTL_GET_SIZE, &map) < 0) { + MSG_ERR("Couldn't get lpc control buffer size: %s\n", + strerror(errno)); + return -errno; + } + + context->mem_size = map.size; + /* Map at the top of the 28-bit LPC firmware address space-0 */ + context->lpc_base = 0x0FFFFFFF & -context->mem_size; + + /* mmap the Reserved Memory Region */ + MSG_OUT("Mapping %s for %u\n", LPC_CTRL_PATH, context->mem_size); + context->mem = mmap(NULL, context->mem_size, PROT_READ | PROT_WRITE, + MAP_SHARED, fd, 0); + if (context->mem == MAP_FAILED) { + MSG_ERR("Didn't manage to mmap %s: %s\n", LPC_CTRL_PATH, + strerror(errno)); + return -errno; + } + + return 0; +} + +void free_lpc_dev(struct mbox_context *context) +{ + if (context->mem) { + munmap(context->mem, context->mem_size); + } + close(context->fds[LPC_CTRL_FD].fd); +} + +/* + * point_to_flash() - Point the lpc bus mapping to the actual flash device + * @context: The mbox context pointer + * + * Return: 0 on success otherwise negative error code + */ +int point_to_flash(struct mbox_context *context) +{ + struct aspeed_lpc_ctrl_mapping map = { + .window_type = ASPEED_LPC_CTRL_WINDOW_FLASH, + .window_id = 0, /* Theres only one */ + .flags = 0, + /* + * The mask is because the top nibble is the host LPC FW space, + * we want space 0. + */ + .addr = 0x0FFFFFFF & -context->flash_size, + .offset = 0, + .size = context->flash_size + }; + + if (context->state & MAPS_FLASH) { + return 0; /* LPC Bus already points to flash */ + } + /* Don't let the host access flash while we're suspended */ + if (context->state & STATE_SUSPENDED) { + MSG_ERR("Can't point lpc mapping to flash while suspended\n"); + return -MBOX_R_PARAM_ERROR; + } + + MSG_OUT("Pointing HOST LPC bus at the actual flash\n"); + MSG_OUT("Assuming %dMB of flash: HOST LPC 0x%08x\n", + context->flash_size >> 20, map.addr); + + if (ioctl(context->fds[LPC_CTRL_FD].fd, ASPEED_LPC_CTRL_IOCTL_MAP, &map) + == -1) { + MSG_ERR("Failed to point the LPC BUS at the actual flash: %s\n", + strerror(errno)); + return -MBOX_R_SYSTEM_ERROR; + } + + context->state = ACTIVE_MAPS_FLASH; + /* + * Since the host now has access to the flash it can change it out from + * under us + */ + return set_flash_bytemap(context, 0, context->flash_size, FLASH_DIRTY); +} + +/* + * point_to_memory() - Point the lpc bus mapping to the reserved memory region + * @context: The mbox context pointer + * + * Return: 0 on success otherwise negative error code + */ +int point_to_memory(struct mbox_context *context) +{ + struct aspeed_lpc_ctrl_mapping map = { + .window_type = ASPEED_LPC_CTRL_WINDOW_MEMORY, + .window_id = 0, /* There's only one */ + .flags = 0, + .addr = context->lpc_base, + .offset = 0, + .size = context->mem_size + }; + + if (context->state & MAPS_MEM) { + return 0; /* LPC Bus already points to reserved memory area */ + } + + MSG_OUT("Pointing HOST LPC bus at memory region %p of size 0x%.8x\n", + context->mem, context->mem_size); + MSG_OUT("LPC address 0x%.8x\n", map.addr); + + if (ioctl(context->fds[LPC_CTRL_FD].fd, ASPEED_LPC_CTRL_IOCTL_MAP, + &map)) { + MSG_ERR("Failed to point the LPC BUS to memory: %s\n", + strerror(errno)); + return -MBOX_R_SYSTEM_ERROR; + } + + /* LPC now maps memory (keep suspended state) */ + context->state = MAPS_MEM | (context->state & STATE_SUSPENDED); + + return 0; +} diff --git a/mboxd_lpc.h b/mboxd_lpc.h new file mode 100644 index 0000000..bb288a9 --- /dev/null +++ b/mboxd_lpc.h @@ -0,0 +1,26 @@ +/* + * Copyright 2016 IBM + * + * 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 MBOXD_LPC_H +#define MBOXD_LPC_H + +int init_lpc_dev(struct mbox_context *context); +void free_lpc_dev(struct mbox_context *context); +int point_to_flash(struct mbox_context *context); +int point_to_memory(struct mbox_context *context); + +#endif /* MBOXD_LPC_H */ diff --git a/mboxd_msg.c b/mboxd_msg.c new file mode 100644 index 0000000..179af60 --- /dev/null +++ b/mboxd_msg.c @@ -0,0 +1,802 @@ +/* + * Mailbox Daemon MBOX Message Helpers + * + * Copyright 2016 IBM + * + * 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 _GNU_SOURCE +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <poll.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <signal.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/timerfd.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> +#include <inttypes.h> + +#include "mbox.h" +#include "common.h" +#include "mboxd_msg.h" +#include "mboxd_windows.h" +#include "mboxd_lpc.h" + +static int mbox_handle_flush_window(struct mbox_context *context, union mbox_regs *req, + struct mbox_msg *resp); + +typedef int (*mboxd_mbox_handler)(struct mbox_context *, union mbox_regs *, + struct mbox_msg *); + +/* + * write_bmc_event_reg() - Write to the BMC controlled status register (reg 15) + * @context: The mbox context pointer + * + * Return: 0 on success otherwise negative error code + */ +static int write_bmc_event_reg(struct mbox_context *context) +{ + int rc; + + /* Seek mbox registers */ + rc = lseek(context->fds[MBOX_FD].fd, MBOX_BMC_EVENT, SEEK_SET); + if (rc != MBOX_BMC_EVENT) { + MSG_ERR("Couldn't lseek mbox to byte %d: %s\n", MBOX_BMC_EVENT, + strerror(errno)); + return -MBOX_R_SYSTEM_ERROR; + } + + /* Write to mbox status register */ + rc = write(context->fds[MBOX_FD].fd, &context->bmc_events, 1); + if (rc != 1) { + MSG_ERR("Couldn't write to BMC status reg: %s\n", + strerror(errno)); + return -MBOX_R_SYSTEM_ERROR; + } + + /* Reset to start */ + rc = lseek(context->fds[MBOX_FD].fd, 0, SEEK_SET); + if (rc) { + MSG_ERR("Couldn't reset MBOX offset to zero: %s\n", + strerror(errno)); + return -MBOX_R_SYSTEM_ERROR; + } + + return 0; +} + +/* + * set_bmc_events() - Set BMC events + * @context: The mbox context pointer + * @bmc_event: The bits to set + * @write_back: Whether to write back to the register -> will interrupt host + * + * Return: 0 on success otherwise negative error code + */ +int set_bmc_events(struct mbox_context *context, uint8_t bmc_event, + bool write_back) +{ + uint8_t mask = 0x00; + + switch (context->version) { + case API_VERSION_1: + mask = BMC_EVENT_V1_MASK; + break; + default: + mask = BMC_EVENT_V2_MASK; + break; + } + + context->bmc_events |= (bmc_event & mask); + + return write_back ? write_bmc_event_reg(context) : 0; +} + +/* + * clr_bmc_events() - Clear BMC events + * @context: The mbox context pointer + * @bmc_event: The bits to clear + * @write_back: Whether to write back to the register -> will interrupt host + * + * Return: 0 on success otherwise negative error code + */ +int clr_bmc_events(struct mbox_context *context, uint8_t bmc_event, + bool write_back) +{ + context->bmc_events &= ~bmc_event; + + return write_back ? write_bmc_event_reg(context) : 0; +} + +/* Command Handlers */ + +/* + * Command: RESET_STATE + * Reset the LPC mapping to point back at the flash + */ +static int mbox_handle_reset(struct mbox_context *context, + union mbox_regs *req, struct mbox_msg *resp) +{ + /* Host requested it -> No BMC Event */ + reset_all_windows(context, NO_BMC_EVENT); + return point_to_flash(context); +} + +/* + * Command: GET_MBOX_INFO + * Get the API version, default window size and block size + * We also set the LPC mapping to point to the reserved memory region here so + * this command must be called before any window manipulation + * + * V1: + * ARGS[0]: API Version + * + * RESP[0]: API Version + * RESP[1:2]: Default read window size (number of blocks) + * RESP[3:4]: Default write window size (number of blocks) + * RESP[5]: Block size (as shift) + * + * V2: + * ARGS[0]: API Version + * + * RESP[0]: API Version + * RESP[1:2]: Default read window size (number of blocks) + * RESP[3:4]: Default write window size (number of blocks) + * RESP[5]: Block size (as shift) + */ +static int mbox_handle_mbox_info(struct mbox_context *context, + union mbox_regs *req, struct mbox_msg *resp) +{ + uint8_t mbox_api_version = req->msg.args[0]; + uint8_t old_api_version = context->version; + int rc; + + /* Check we support the version requested */ + if (mbox_api_version < API_MIN_VERSION || + mbox_api_version > API_MAX_VERSION) { + return -MBOX_R_PARAM_ERROR; + } + context->version = mbox_api_version; + MSG_OUT("Using Protocol Version: %d\n", context->version); + + /* + * The reset state is currently to have the LPC bus point directly to + * flash, since we got a mbox_info command we know the host can talk + * mbox so point the LPC bus mapping to the reserved memory region now + * so the host can access what we put in it. + */ + rc = point_to_memory(context); + if (rc < 0) { + return rc; + } + + switch (context->version) { + case API_VERSION_1: + context->block_size_shift = BLOCK_SIZE_SHIFT_V1; + break; + default: + context->block_size_shift = log_2(context->mtd_info.erasesize); + break; + } + MSG_OUT("Block Size Shift: %d\n", context->block_size_shift); + + /* Now we know the blocksize we can allocate the window dirty_bytemap */ + if (mbox_api_version != old_api_version) { + alloc_window_dirty_bytemap(context); + } + /* Reset if we were V1 since this required exact window mapping */ + if (old_api_version == API_VERSION_1) { + /* + * This will only set the BMC event if there was a current + * window -> In which case we are better off notifying the + * host. + */ + reset_all_windows(context, SET_BMC_EVENT); + } + + resp->args[0] = mbox_api_version; + if (context->version == API_VERSION_1) { + put_u16(&resp->args[1], context->windows.default_size >> + context->block_size_shift); + put_u16(&resp->args[3], context->windows.default_size >> + context->block_size_shift); + } + if (context->version >= API_VERSION_2) { + resp->args[5] = context->block_size_shift; + } + + return 0; +} + +/* + * Command: GET_FLASH_INFO + * Get the flash size and erase granularity + * + * V1: + * RESP[0:3]: Flash Size (bytes) + * RESP[4:7]: Erase Size (bytes) + * V2: + * RESP[0:1]: Flash Size (number of blocks) + * RESP[2:3]: Erase Size (number of blocks) + */ +static int mbox_handle_flash_info(struct mbox_context *context, + union mbox_regs *req, struct mbox_msg *resp) +{ + switch (context->version) { + case API_VERSION_1: + /* Both Sizes in Bytes */ + put_u32(&resp->args[0], context->flash_size); + put_u32(&resp->args[4], context->mtd_info.erasesize); + break; + case API_VERSION_2: + /* Both Sizes in Block Size */ + put_u16(&resp->args[0], + context->flash_size >> context->block_size_shift); + put_u16(&resp->args[2], + context->mtd_info.erasesize >> + context->block_size_shift); + break; + default: + MSG_ERR("API Version Not Valid - Invalid System State\n"); + return -MBOX_R_SYSTEM_ERROR; + break; + } + + return 0; +} + +/* + * get_lpc_addr_shifted() - Get lpc address of the current window + * @context: The mbox context pointer + * + * Return: The lpc address to access that offset shifted by block size + */ +static inline uint16_t get_lpc_addr_shifted(struct mbox_context *context) +{ + uint32_t lpc_addr, mem_offset; + + /* Offset of the current window in the reserved memory region */ + mem_offset = context->current->mem - context->mem; + /* Total LPC Address */ + lpc_addr = context->lpc_base + mem_offset; + + return lpc_addr >> context->block_size_shift; +} + +/* + * Command: CREATE_READ_WINDOW + * Opens a read window + * First checks if any current window with the requested data, if so we just + * point the host to that. Otherwise we read the request data in from flash and + * point the host there. + * + * V1: + * ARGS[0:1]: Window Location as Offset into Flash (number of blocks) + * + * RESP[0:1]: LPC bus address for host to access this window (number of blocks) + * + * V2: + * ARGS[0:1]: Window Location as Offset into Flash (number of blocks) + * ARGS[2:3]: Requested window size (number of blocks) + * + * RESP[0:1]: LPC bus address for host to access this window (number of blocks) + * RESP[2:3]: Actual window size that the host can access (number of blocks) + */ +static int mbox_handle_read_window(struct mbox_context *context, + union mbox_regs *req, struct mbox_msg *resp) +{ + uint32_t flash_offset; + int rc; + + /* Close the current window if there is one */ + if (context->current) { + /* There is an implicit flush if it was a write window */ + if (context->current_is_write) { + rc = mbox_handle_flush_window(context, NULL, NULL); + if (rc < 0) { + MSG_ERR("Couldn't Flush Write Window\n"); + return rc; + } + } + close_current_window(context, NO_BMC_EVENT, FLAGS_NONE); + } + + /* Offset the host has requested */ + flash_offset = get_u16(&req->msg.args[0]) << context->block_size_shift; + MSG_OUT("Host Requested Flash @ 0x%.8x\n", flash_offset); + /* Check if we have an existing window */ + context->current = search_windows(context, flash_offset, + context->version == API_VERSION_1); + + if (!context->current) { /* No existing window */ + rc = create_map_window(context, &context->current, flash_offset, + context->version == API_VERSION_1); + if (rc < 0) { /* Unable to map offset */ + MSG_ERR("Couldn't create window mapping for offset 0x%.8x\n" + , flash_offset); + return rc; + } + } + + put_u16(&resp->args[0], get_lpc_addr_shifted(context)); + if (context->version >= API_VERSION_2) { + put_u16(&resp->args[2], context->current->size >> + context->block_size_shift); + put_u16(&resp->args[4], context->current->flash_offset >> + context->block_size_shift); + } + + context->current_is_write = false; + + return 0; +} + +/* + * Command: CREATE_WRITE_WINDOW + * Opens a write window + * First checks if any current window with the requested data, if so we just + * point the host to that. Otherwise we read the request data in from flash and + * point the host there. + * + * V1: + * ARGS[0:1]: Window Location as Offset into Flash (number of blocks) + * + * RESP[0:1]: LPC bus address for host to access this window (number of blocks) + * + * V2: + * ARGS[0:1]: Window Location as Offset into Flash (number of blocks) + * ARGS[2:3]: Requested window size (number of blocks) + * + * RESP[0:1]: LPC bus address for host to access this window (number of blocks) + * RESP[2:3]: Actual window size that was mapped/host can access (n.o. blocks) + */ +static int mbox_handle_write_window(struct mbox_context *context, + union mbox_regs *req, struct mbox_msg *resp) +{ + int rc; + + /* + * This is very similar to opening a read window (exactly the same + * for now infact) + */ + rc = mbox_handle_read_window(context, req, resp); + if (rc < 0) { + return rc; + } + + context->current_is_write = true; + return rc; +} + +/* + * Commands: MARK_WRITE_DIRTY + * Marks a portion of the current (write) window dirty, informing the daemon + * that is has been written to and thus must be at some point written to the + * backing store + * These changes aren't written back to the backing store unless flush is then + * called or the window closed + * + * V1: + * ARGS[0:1]: Where within flash to start (number of blocks) + * ARGS[2:5]: Number to mark dirty (number of bytes) + * + * V2: + * ARGS[0:1]: Where within window to start (number of blocks) + * ARGS[2:3]: Number to mark dirty (number of blocks) + */ +static int mbox_handle_dirty_window(struct mbox_context *context, + union mbox_regs *req, struct mbox_msg *resp) +{ + uint32_t offset, size; + + if (!(context->current && context->current_is_write)) { + MSG_ERR("Tried to call mark dirty without open write window\n"); + return context->version >= API_VERSION_2 ? -MBOX_R_WINDOW_ERROR + : -MBOX_R_PARAM_ERROR; + } + + offset = get_u16(&req->msg.args[0]); + + if (context->version >= API_VERSION_2) { + size = get_u16(&req->msg.args[2]); + } else { + uint32_t off; + /* For V1 offset given relative to flash - we want the window */ + off = offset - ((context->current->flash_offset) >> + context->block_size_shift); + if (off > offset) { /* Underflow - before current window */ + MSG_ERR("Tried to mark dirty before start of window\n"); + MSG_ERR("requested offset: 0x%x window start: 0x%x\n", + offset << context->block_size_shift, + context->current->flash_offset); + return -MBOX_R_PARAM_ERROR; + } + offset = off; + size = get_u32(&req->msg.args[2]); + /* + * We only track dirty at the block level. + * For protocol V1 we can get away with just marking the whole + * block dirty. + */ + size = align_up(size, 1 << context->block_size_shift); + size >>= context->block_size_shift; + } + + return set_window_bytemap(context, context->current, offset, size, + WINDOW_DIRTY); +} + +/* + * Commands: MARK_WRITE_ERASE + * Erases a portion of the current window + * These changes aren't written back to the backing store unless flush is then + * called or the window closed + * + * V1: + * Unimplemented + * + * V2: + * ARGS[0:1]: Where within window to start (number of blocks) + * ARGS[2:3]: Number to erase (number of blocks) + */ +static int mbox_handle_erase_window(struct mbox_context *context, + union mbox_regs *req, struct mbox_msg *resp) +{ + uint32_t offset, size; + int rc; + + if (context->version < API_VERSION_2) { + MSG_ERR("Protocol Version invalid for Erase Command\n"); + return -MBOX_R_PARAM_ERROR; + } + + if (!(context->current && context->current_is_write)) { + MSG_ERR("Tried to call erase without open write window\n"); + return -MBOX_R_WINDOW_ERROR; + } + + offset = get_u16(&req->msg.args[0]); + size = get_u16(&req->msg.args[2]); + + rc = set_window_bytemap(context, context->current, offset, size, + WINDOW_ERASED); + if (rc < 0) { + return rc; + } + + /* Write 0xFF to mem -> This ensures consistency between flash & ram */ + memset(context->current->mem + (offset << context->block_size_shift), + 0xFF, size << context->block_size_shift); + + return 0; +} + +/* + * Command: WRITE_FLUSH + * Flushes any dirty or erased blocks in the current window back to the backing + * store + * NOTE: For V1 this behaves much the same as the dirty command in that it + * takes an offset and number of blocks to dirty, then also performs a flush as + * part of the same command. For V2 this will only flush blocks already marked + * dirty/erased with the appropriate commands and doesn't take any arguments + * directly. + * + * V1: + * ARGS[0:1]: Where within window to start (number of blocks) + * ARGS[2:5]: Number to mark dirty (number of bytes) + * + * V2: + * NONE + */ +static int mbox_handle_flush_window(struct mbox_context *context, + union mbox_regs *req, struct mbox_msg *resp) +{ + int rc, i, offset, count; + uint8_t prev; + + if (!(context->current && context->current_is_write)) { + MSG_ERR("Tried to call flush without open write window\n"); + return context->version >= API_VERSION_2 ? -MBOX_R_WINDOW_ERROR + : -MBOX_R_PARAM_ERROR; + } + + /* + * For V1 the Flush command acts much the same as the dirty command + * except with a flush as well. Only do this on an actual flush + * command not when we call flush because we've implicitly closed a + * window because we might not have the required args in req. + */ + if (context->version == API_VERSION_1 && req && + req->msg.command == MBOX_C_WRITE_FLUSH) { + rc = mbox_handle_dirty_window(context, req, NULL); + if (rc < 0) { + return rc; + } + } + + offset = 0; + count = 0; + prev = WINDOW_CLEAN; + + /* + * We look for streaks of the same type and keep a count, when the type + * (dirty/erased) changes we perform the required action on the backing + * store and update the current streak-type + */ + for (i = 0; i < (context->current->size >> context->block_size_shift); + i++) { + uint8_t cur = context->current->dirty_bmap[i]; + if (cur != WINDOW_CLEAN) { + if (cur == prev) { /* Same as previous block, incrmnt */ + count++; + } else if (prev == WINDOW_CLEAN) { /* Start of run */ + offset = i; + count++; + } else { /* Change in streak type */ + rc = write_from_window(context, offset, count, + prev); + if (rc < 0) { + return rc; + } + offset = i; + count = 1; + } + } else { + if (prev != WINDOW_CLEAN) { /* End of a streak */ + rc = write_from_window(context, offset, count, + prev); + if (rc < 0) { + return rc; + } + offset = 0; + count = 0; + } + } + prev = cur; + } + + if (prev != WINDOW_CLEAN) { /* Still the last streak to write */ + rc = write_from_window(context, offset, count, prev); + if (rc < 0) { + return rc; + } + } + + /* Clear the dirty bytemap since we have written back all changes */ + return set_window_bytemap(context, context->current, 0, + context->current->size >> + context->block_size_shift, + WINDOW_CLEAN); +} + +/* + * Command: CLOSE_WINDOW + * Close the current window + * NOTE: There is an implicit flush + * + * V1: + * NONE + * + * V2: + * ARGS[0]: FLAGS + */ +static int mbox_handle_close_window(struct mbox_context *context, + union mbox_regs *req, struct mbox_msg *resp) +{ + uint8_t flags = 0; + int rc; + + /* Close the current window if there is one */ + if (context->current) { + /* There is an implicit flush if it was a write window */ + if (context->current_is_write) { + rc = mbox_handle_flush_window(context, NULL, NULL); + if (rc < 0) { + MSG_ERR("Couldn't Flush Write Window\n"); + return rc; + } + } + + if (context->version >= API_VERSION_2) { + flags = req->msg.args[0]; + } + + /* Host asked for it -> Don't set the BMC Event */ + close_current_window(context, NO_BMC_EVENT, flags); + } + + return 0; +} + +/* + * Command: BMC_EVENT_ACK + * Sent by the host to acknowledge BMC events supplied in mailbox register 15 + * + * ARGS[0]: Bitmap of bits to ack (by clearing) + */ +static int mbox_handle_ack(struct mbox_context *context, union mbox_regs *req, + struct mbox_msg *resp) +{ + uint8_t bmc_events = req->msg.args[0]; + + return clr_bmc_events(context, (bmc_events & BMC_EVENT_ACK_MASK), + SET_BMC_EVENT); +} + +/* + * check_cmd_valid() - Check if the given command is a valid mbox command code + * @context: The mbox context pointer + * @cmd: The command code + * + * Return: 0 if command is valid otherwise negative error code + */ +static int check_cmd_valid(struct mbox_context *context, int cmd) +{ + if (cmd <= 0 || cmd > NUM_MBOX_CMDS) { + MSG_ERR("UNKNOWN MBOX COMMAND: %d\n", cmd); + return -MBOX_R_PARAM_ERROR; + } + if (context->state & STATE_SUSPENDED) { + if (cmd != MBOX_C_GET_MBOX_INFO && cmd != MBOX_C_ACK) { + MSG_ERR("Cannot use that cmd while suspended: %d\n", + cmd); + return context->version >= API_VERSION_2 ? -MBOX_R_BUSY + : -MBOX_R_PARAM_ERROR; + } + } + if (!(context->state & MAPS_MEM)) { + if (cmd != MBOX_C_RESET_STATE && cmd != MBOX_C_GET_MBOX_INFO + && cmd != MBOX_C_ACK) { + MSG_ERR("Must call GET_MBOX_INFO before that cmd: %d\n", + cmd); + return -MBOX_R_PARAM_ERROR; + } + } + + return 0; +} + +static const mboxd_mbox_handler mbox_handlers[] = { + mbox_handle_reset, + mbox_handle_mbox_info, + mbox_handle_flash_info, + mbox_handle_read_window, + mbox_handle_close_window, + mbox_handle_write_window, + mbox_handle_dirty_window, + mbox_handle_flush_window, + mbox_handle_ack, + mbox_handle_erase_window +}; + +/* + * handle_mbox_req() - Handle an incoming mbox command request + * @context: The mbox context pointer + * @req: The mbox request message + * + * Return: 0 if handled successfully otherwise negative error code + */ +static int handle_mbox_req(struct mbox_context *context, union mbox_regs *req) +{ + struct mbox_msg resp = { + .command = req->msg.command, + .seq = req->msg.seq, + .args = { 0 }, + .response = MBOX_R_SUCCESS + }; + int rc = 0, len; + + MSG_OUT("Got data in with command %d\n", req->msg.command); + rc = check_cmd_valid(context, req->msg.command); + if (rc < 0) { + resp.response = -rc; + } else { + /* Commands start at 1 so we have to subtract 1 from the cmd */ + rc = mbox_handlers[req->msg.command - 1](context, req, &resp); + if (rc < 0) { + MSG_ERR("Error handling mbox cmd: %d\n", + req->msg.command); + resp.response = -rc; + } + } + + MSG_OUT("Writing response to MBOX regs: %d\n", resp.response); + len = write(context->fds[MBOX_FD].fd, &resp, sizeof(resp)); + if (len < sizeof(resp)) { + MSG_ERR("Didn't write the full response\n"); + rc = -errno; + } + + return rc; +} + +/* + * get_message() - Read an mbox request message from the mbox registers + * @context: The mbox context pointer + * @msg: Where to put the received message + * + * Return: 0 if read successfully otherwise negative error code + */ +static int get_message(struct mbox_context *context, union mbox_regs *msg) +{ + int rc; + + rc = read(context->fds[MBOX_FD].fd, msg, sizeof(msg->raw)); + if (rc < 0) { + MSG_ERR("Couldn't read: %s\n", strerror(errno)); + return -errno; + } else if (rc < sizeof(msg->raw)) { + MSG_ERR("Short read: %d expecting %zu\n", rc, sizeof(msg->raw)); + return -1; + } + + return 0; +} + +/* + * dispatch_mbox() - handle an mbox interrupt + * @context: The mbox context pointer + * + * Return: 0 if handled successfully otherwise negative error code + */ +int dispatch_mbox(struct mbox_context *context) +{ + int rc = 0; + union mbox_regs req = { 0 }; + + assert(context); + + MSG_OUT("Dispatched to mbox\n"); + rc = get_message(context, &req); + if (rc) { + return rc; + } + + return handle_mbox_req(context, &req); +} + +int init_mbox_dev(struct mbox_context *context) +{ + int fd; + + /* Open MBOX Device */ + fd = open(MBOX_HOST_PATH, O_RDWR | O_NONBLOCK); + if (fd < 0) { + MSG_ERR("Couldn't open %s with flags O_RDWR: %s\n", + MBOX_HOST_PATH, strerror(errno)); + return -errno; + } + + context->fds[MBOX_FD].fd = fd; + + return 0; +} + +void free_mbox_dev(struct mbox_context *context) +{ + close(context->fds[MBOX_FD].fd); +} diff --git a/mboxd_msg.h b/mboxd_msg.h new file mode 100644 index 0000000..44a2f91 --- /dev/null +++ b/mboxd_msg.h @@ -0,0 +1,44 @@ +/* + * Copyright 2016 IBM + * + * 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 MBOXD_MSG_H +#define MBOXD_MSG_H + +#define NO_BMC_EVENT false +#define SET_BMC_EVENT true + +struct mbox_msg { + uint8_t command; + uint8_t seq; + uint8_t args[MBOX_ARGS_BYTES]; + uint8_t response; +}; + +union mbox_regs { + char raw[MBOX_REG_BYTES]; + struct mbox_msg msg; +}; + +int set_bmc_events(struct mbox_context *context, uint8_t bmc_event, + bool write_back); +int clr_bmc_events(struct mbox_context *context, uint8_t bmc_event, + bool write_back); +int dispatch_mbox(struct mbox_context *context); +int init_mbox_dev(struct mbox_context *context); +void free_mbox_dev(struct mbox_context *context); + +#endif /* MBOXD_MSG_H */ diff --git a/mboxd_windows.c b/mboxd_windows.c new file mode 100644 index 0000000..fd46770 --- /dev/null +++ b/mboxd_windows.c @@ -0,0 +1,588 @@ +/* + * Mailbox Daemon Window Helpers + * + * Copyright 2016 IBM + * + * 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 _GNU_SOURCE +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <getopt.h> +#include <limits.h> +#include <poll.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <syslog.h> +#include <signal.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/timerfd.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> +#include <inttypes.h> +#include <mtd/mtd-abi.h> + +#include "mbox.h" +#include "common.h" +#include "mboxd_msg.h" +#include "mboxd_windows.h" +#include "mboxd_flash.h" + +/* Initialisation Functions */ + +/* + * init_window_state() - Initialise a new window to a known state + * @window: The window to initialise + * @size: The size of the window + */ +void init_window_state(struct window_context *window, uint32_t size) +{ + window->mem = NULL; + window->flash_offset = FLASH_OFFSET_UNINIT; + window->size = size; + window->dirty_bmap = NULL; + window->age = 0; +} + +/* + * init_window_mem() - Divide the reserved memory region among the windows + * @context: The mbox context pointer + * + * Return: 0 on success otherwise negative error code + */ +int init_window_mem(struct mbox_context *context) +{ + void *mem_location = context->mem; + int i; + + /* + * Carve up the reserved memory region and allocate it to each of the + * windows. The windows are placed one after the other in ascending + * order, so the first window will be first in memory and so on. We + * shouldn't have allocated more windows than we have memory, but if we + * did we will error out here + */ + for (i = 0; i < context->windows.num; i++) { + context->windows.window[i].mem = mem_location; + mem_location += context->windows.window[i].size; + if (mem_location > (context->mem + context->mem_size)) { + /* Tried to allocate window past the end of memory */ + MSG_ERR("Total size of windows exceeds reserved mem\n"); + MSG_ERR("Try smaller or fewer windows\n"); + MSG_ERR("Mem size: 0x%.8x\n", context->mem_size); + return -1; + } + } + + return 0; +} + +/* Write from Window Functions */ + +/* + * write_from_window_v1() - Handle writing when erase and block size differ + * @context: The mbox context pointer + * @offset_bytes: The offset in the current window to write from (bytes) + * @count_bytes: Number of bytes to write + * + * Handle a write_from_window for dirty memory when block_size is less than the + * flash erase size + * This requires us to be a bit careful because we might have to erase more + * than we want to write which could result in data loss if we don't have the + * entire portion of flash to be erased already saved in memory (for us to + * write back after the erase) + * + * Return: 0 on success otherwise negative error code + */ +int write_from_window_v1(struct mbox_context *context, + uint32_t offset_bytes, uint32_t count_bytes) +{ + int rc; + uint32_t flash_offset; + struct window_context low_mem = { 0 }, high_mem = { 0 }; + + /* Find where in phys flash this is based on the window.flash_offset */ + flash_offset = context->current->flash_offset + offset_bytes; + + /* + * low_mem.flash_offset = erase boundary below where we're writing + * low_mem.size = size from low_mem.flash_offset to where we're writing + * + * high_mem.flash_offset = end of where we're writing + * high_mem.size = size from end of where we're writing to next erase + * boundary + */ + low_mem.flash_offset = align_down(flash_offset, + context->mtd_info.erasesize); + low_mem.size = flash_offset - low_mem.flash_offset; + high_mem.flash_offset = flash_offset + count_bytes; + high_mem.size = align_up(high_mem.flash_offset, + context->mtd_info.erasesize) - + high_mem.flash_offset; + + /* + * Check if we already have a copy of the required flash areas in + * memory as part of the existing window + */ + if (low_mem.flash_offset < context->current->flash_offset) { + /* Before the start of our current window */ + low_mem.mem = malloc(low_mem.size); + if (!low_mem.mem) { + MSG_ERR("Unable to allocate memory\n"); + return -MBOX_R_SYSTEM_ERROR; + } + rc = copy_flash(context, low_mem.flash_offset, + low_mem.mem, low_mem.size); + if (rc < 0) { + goto out; + } + } + if ((high_mem.flash_offset + high_mem.size) > + (context->current->flash_offset + context->current->size)) { + /* After the end of our current window */ + high_mem.mem = malloc(high_mem.size); + if (!high_mem.mem) { + MSG_ERR("Unable to allocate memory\n"); + rc = -MBOX_R_SYSTEM_ERROR; + goto out; + } + rc = copy_flash(context, high_mem.flash_offset, + high_mem.mem, high_mem.size); + if (rc < 0) { + goto out; + } + } + + /* + * We need to erase the flash from low_mem.flash_offset-> + * high_mem.flash_offset + high_mem.size + */ + rc = erase_flash(context, low_mem.flash_offset, + (high_mem.flash_offset - low_mem.flash_offset) + + high_mem.size); + if (rc < 0) { + MSG_ERR("Couldn't erase flash\n"); + goto out; + } + + /* Write back over the erased area */ + if (low_mem.mem) { + /* Exceed window at the start */ + rc = write_flash(context, low_mem.flash_offset, low_mem.mem, + low_mem.size); + if (rc < 0) { + goto out; + } + } + rc = write_flash(context, flash_offset, + context->current->mem + offset_bytes, count_bytes); + if (rc < 0) { + goto out; + } + /* + * We still need to write the last little bit that we erased - it's + * either in the current window or the high_mem window. + */ + if (high_mem.mem) { + /* Exceed window at the end */ + rc = write_flash(context, high_mem.flash_offset, high_mem.mem, + high_mem.size); + if (rc < 0) { + goto out; + } + } else { + /* Write from the current window - it's atleast that big */ + rc = write_flash(context, high_mem.flash_offset, + context->current->mem + offset_bytes + + count_bytes, high_mem.size); + if (rc < 0) { + goto out; + } + } + +out: + free(low_mem.mem); + free(high_mem.mem); + return rc; +} + +/* + * write_from_window() - Write back to the flash from the current window + * @context: The mbox context pointer + * @offset_bytes: The offset in the current window to write from (blocks) + * @count_bytes: Number of blocks to write + * @type: Whether this is an erase & write or just an erase + * + * Return: 0 on success otherwise negative error code + */ +int write_from_window(struct mbox_context *context, uint32_t offset, + uint32_t count, uint8_t type) +{ + int rc; + uint32_t flash_offset, count_bytes = count << context->block_size_shift; + uint32_t offset_bytes = offset << context->block_size_shift; + + switch (type) { + case WINDOW_ERASED: /* >= V2 ONLY -> block_size == erasesize */ + flash_offset = context->current->flash_offset + offset_bytes; + rc = erase_flash(context, flash_offset, count_bytes); + if (rc < 0) { + MSG_ERR("Couldn't erase flash\n"); + return rc; + } + break; + case WINDOW_DIRTY: + /* + * For protocol V1, block_size may be smaller than erase size + * so we have a special function to make sure that we do this + * correctly without losing data. + */ + if (log_2(context->mtd_info.erasesize) != + context->block_size_shift) { + return write_from_window_v1(context, offset_bytes, + count_bytes); + } + flash_offset = context->current->flash_offset + offset_bytes; + + /* Erase the flash */ + rc = erase_flash(context, flash_offset, count_bytes); + if (rc < 0) { + return rc; + } + + /* Write to the erased flash */ + rc = write_flash(context, flash_offset, + context->current->mem + offset_bytes, + count_bytes); + if (rc < 0) { + return rc; + } + + break; + default: + /* We shouldn't be able to get here */ + MSG_ERR("Write from window with invalid type: %d\n", type); + return -MBOX_R_SYSTEM_ERROR; + } + + return 0; +} + +/* Window Management Functions */ + +/* + * alloc_window_dirty_bytemap() - (re)allocate all the window dirty bytemaps + * @context: The mbox context pointer + */ +void alloc_window_dirty_bytemap(struct mbox_context *context) +{ + struct window_context *cur; + int i; + + for (i = 0; i < context->windows.num; i++) { + cur = &context->windows.window[i]; + /* There may already be one allocated */ + free(cur->dirty_bmap); + /* Allocate the new one */ + cur->dirty_bmap = calloc((cur->size >> + context->block_size_shift), + sizeof(*cur->dirty_bmap)); + } +} + +/* + * free_window_dirty_bytemap() - free all window dirty bytemaps + * @context: The mbox context pointer + */ +void free_window_dirty_bytemap(struct mbox_context *context) +{ + int i; + + for (i = 0; i < context->windows.num; i++) { + free(context->windows.window[i].dirty_bmap); + } +} + +/* + * set_window_bytemap() - Set the window bytemap + * @context: The mbox context pointer + * @cur: The window to set the bytemap of + * @offset: Where in the window to set the bytemap (blocks) + * @size: The number of blocks to set + * @val: The value to set the bytemap to + * + * Return: 0 on success otherwise negative error code + */ +int set_window_bytemap(struct mbox_context *context, struct window_context *cur, + uint32_t offset, uint32_t size, uint8_t val) +{ + if (offset + size > (cur->size >> context->block_size_shift)) { + MSG_ERR("Tried to set window bytemap past end of window\n"); + MSG_ERR("Requested offset: 0x%x size: 0x%x window size: 0x%x\n", + offset << context->block_size_shift, + size << context->block_size_shift, + cur->size << context->block_size_shift); + return -MBOX_R_PARAM_ERROR; + } + + memset(cur->dirty_bmap + offset, val, size); + return 0; +} + +/* + * close_current_window() - Close the current (active) window + * @context: The mbox context pointer + * @set_bmc_event: Whether to set the bmc event bit + * @flags: Flags as defined for a close command in the protocol + * + * This closes the current window. If the host has requested the current window + * be closed then we don't need to set the bmc event bit + * (set_bmc_event == false), otherwise if the current window has been closed + * without the host requesting it the bmc event bit must be set to indicate this + * to the host (set_bmc_event == true). + */ +void close_current_window(struct mbox_context *context, bool set_bmc_event, + uint8_t flags) +{ + if (set_bmc_event) { + set_bmc_events(context, BMC_EVENT_WINDOW_RESET, SET_BMC_EVENT); + } + + if (flags & FLAGS_SHORT_LIFETIME) { + context->current->age = 0; + } + + context->current->size = context->windows.default_size; + context->current = NULL; + context->current_is_write = false; +} + +/* + * reset_window() - Reset a window context to a well defined default state + * @context: The mbox context pointer + * @window: The window to reset + */ +void reset_window(struct mbox_context *context, struct window_context *window) +{ + window->flash_offset = FLASH_OFFSET_UNINIT; + window->size = context->windows.default_size; + if (window->dirty_bmap) { /* Might not have been allocated */ + set_window_bytemap(context, window, 0, + window->size >> context->block_size_shift, + WINDOW_CLEAN); + } + window->age = 0; +} + +/* + * reset_all_windows() - Reset all windows to a well defined default state + * @context: The mbox context pointer + * @set_bmc_event: If any state change should be indicated to the host + */ +void reset_all_windows(struct mbox_context *context, bool set_bmc_event) +{ + int i; + + /* We might have an open window which needs closing */ + if (context->current) { + close_current_window(context, set_bmc_event, FLAGS_NONE); + } + for (i = 0; i < context->windows.num; i++) { + reset_window(context, &context->windows.window[i]); + } + + context->windows.max_age = 0; +} + +/* + * find_oldest_window() - Find the oldest (Least Recently Used) window + * @context: The mbox context pointer + * + * Return: Pointer to the least recently used window + */ +struct window_context *find_oldest_window(struct mbox_context *context) +{ + struct window_context *oldest = NULL, *cur; + uint32_t min_age = context->windows.max_age + 1; + int i; + + for (i = 0; i < context->windows.num; i++) { + cur = &context->windows.window[i]; + + if (cur->age < min_age) { + min_age = cur->age; + oldest = cur; + } + } + + return oldest; +} + +/* + * search_windows() - Search the window cache for a window containing offset + * @context: The mbox context pointer + * @offset: Absolute flash offset to search for (bytes) + * @exact: If the window must exactly map the requested offset + * + * This will search the cache of windows for one containing the requested + * offset. For V1 of the protocol windows must exactly map the offset since we + * can't tell the host how much of its request we actually mapped and it will + * thus assume it can access window->size from the offset we give it. + * + * Return: Pointer to a window containing the requested offset otherwise + * NULL + */ +struct window_context *search_windows(struct mbox_context *context, + uint32_t offset, bool exact) +{ + struct window_context *cur; + int i; + + for (i = 0; i < context->windows.num; i++) { + cur = &context->windows.window[i]; + if (cur->flash_offset == FLASH_OFFSET_UNINIT) { + /* Uninitialised Window */ + if (offset == FLASH_OFFSET_UNINIT) { + return cur; + } + continue; + } + if ((offset >= cur->flash_offset) && + (offset < (cur->flash_offset + cur->size))) { + if (exact && (cur->flash_offset != offset)) { + continue; + } + /* This window contains the requested offset */ + cur->age = ++(context->windows.max_age); + return cur; + } + } + + return NULL; +} + +/* + * create_map_window() - Create a window mapping which maps the requested offset + * @context: The mbox context pointer + * @this_window: A pointer to update to the "new" window + * @offset: Absolute flash offset to create a mapping for (bytes) + * @exact: If the window must exactly map the requested offset + * + * This is used to create a window mapping for the requested offset when there + * is no existing window in the cache which satisfies the offset. This involves + * choosing an existing window from the window cache to evict so we can use it + * to store the flash contents from the requested offset, we then point the + * caller to that window since it now maps their request. + * + * Return: 0 on success otherwise negative error code + */ +int create_map_window(struct mbox_context *context, + struct window_context **this_window, uint32_t offset, + bool exact) +{ + struct window_context *cur = NULL; + int rc; + + + /* Search for an uninitialised window, use this before evicting */ + cur = search_windows(context, FLASH_OFFSET_UNINIT, true); + + /* No uninitialised window found, we need to choose one to "evict" */ + if (!cur) { + cur = find_oldest_window(context); + } + + if (!exact) { + /* + * It would be nice to align the offsets which we map to window + * size, this will help prevent overlap which would be an + * inefficient use of our reserved memory area (we would like + * to "cache" as much of the acutal flash as possible in + * memory). If we're protocol V1 however we must ensure the + * offset requested is exactly mapped. + */ + offset &= ~(cur->size - 1); + } + + if ((offset + cur->size) > context->flash_size) { + /* + * There is V1 skiboot implementations out there which don't + * mask offset with window size, meaning when we have + * window size == flash size we will never allow the host to + * open a window except at 0x0, which isn't always where the + * host requests it. Thus we have to ignore this check and just + * hope the host doesn't access past the end of the window + * (which it shouldn't) for V1 implementations to get around + * this. + */ + if (context->version == API_VERSION_1) { + cur->size = align_down(context->flash_size - offset, + 1 << context->block_size_shift); + } else { + /* Trying to read past the end of flash */ + MSG_ERR("Tried to open read window past flash limit\n"); + return -MBOX_R_PARAM_ERROR; + } + } + + /* Copy from flash into the window buffer */ + rc = copy_flash(context, offset, cur->mem, cur->size); + if (rc < 0) { + /* We don't know how much we've copied -> better reset window */ + reset_window(context, cur); + return rc; + } + + /* + * Since for V1 windows aren't constrained to start at multiples of + * window size it's possible that something already maps this offset. + * Reset any windows which map this offset to avoid coherency problems. + * We just have to check for anything which maps the start or the end + * of the window since all windows are the same size so another window + * cannot map just the middle of this window. + */ + if (context->version == API_VERSION_1) { + uint32_t i; + + for (i = offset; i < (offset + cur->size); i += (cur->size - 1)) { + struct window_context *tmp = NULL; + do { + tmp = search_windows(context, i, false); + if (tmp) { + reset_window(context, tmp); + } + } while (tmp); + } + } + + /* Clear the bytemap of the window just loaded -> we know it's clean */ + set_window_bytemap(context, cur, 0, + cur->size >> context->block_size_shift, + WINDOW_CLEAN); + + /* Update so we know what's in the window */ + cur->flash_offset = offset; + cur->age = ++(context->windows.max_age); + *this_window = cur; + + return 0; +} diff --git a/mboxd_windows.h b/mboxd_windows.h new file mode 100644 index 0000000..90e1d46 --- /dev/null +++ b/mboxd_windows.h @@ -0,0 +1,48 @@ +/* + * Copyright 2016 IBM + * + * 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 MBOXD_WINDOWS_H +#define MBOXD_WINDOWS_H + +#define NO_FLUSH false +#define WITH_FLUSH true + +/* Initialisation Functions */ +void init_window_state(struct window_context *window, uint32_t size); +int init_window_mem(struct mbox_context *context); +/* Write From Window Functions */ +int write_from_window_v1(struct mbox_context *context, + uint32_t offset_bytes, uint32_t count_bytes); +int write_from_window(struct mbox_context *context, uint32_t offset, + uint32_t count, uint8_t type); +/* Window Management Functions */ +void alloc_window_dirty_bytemap(struct mbox_context *context); +void free_window_dirty_bytemap(struct mbox_context *context); +int set_window_bytemap(struct mbox_context *context, struct window_context *cur, + uint32_t offset, uint32_t size, uint8_t val); +void close_current_window(struct mbox_context *context, bool set_bmc_event, + uint8_t flags); +void reset_window(struct mbox_context *context, struct window_context *window); +void reset_all_windows(struct mbox_context *context, bool set_bmc_event); +struct window_context *find_oldest_window(struct mbox_context *context); +struct window_context *search_windows(struct mbox_context *context, + uint32_t offset, bool exact); +int create_map_window(struct mbox_context *context, + struct window_context **this_window, + uint32_t offset, bool exact); + +#endif /* MBOXD_WINDOWS_H */ |