summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chip/g/config_chip.h1
-rw-r--r--common/build.mk1
-rw-r--r--common/flash_log.c549
-rw-r--r--include/config.h5
-rw-r--r--include/flash_log.h107
-rw-r--r--test/build.mk2
-rw-r--r--test/flash_log.c218
-rw-r--r--test/flash_log.tasklist17
-rw-r--r--test/test_config.h9
9 files changed, 909 insertions, 0 deletions
diff --git a/chip/g/config_chip.h b/chip/g/config_chip.h
index a79ecf2ee0..365154fd5b 100644
--- a/chip/g/config_chip.h
+++ b/chip/g/config_chip.h
@@ -28,6 +28,7 @@
/* Describe the flash layout */
#define CONFIG_PROGRAM_MEMORY_BASE 0x40000
#define CONFIG_FLASH_SIZE (512 * 1024)
+#define CONFIG_FLASH_ERASED_VALUE32 (-1U)
#define CONFIG_RO_HEAD_ROOM 1024 /* Room for ROM signature. */
#define CONFIG_RW_HEAD_ROOM CONFIG_RO_HEAD_ROOM /* same for RW */
diff --git a/common/build.mk b/common/build.mk
index a52173a385..0b639b8316 100644
--- a/common/build.mk
+++ b/common/build.mk
@@ -63,6 +63,7 @@ common-$(CONFIG_EXTPOWER_GPIO)+=extpower_gpio.o
common-$(CONFIG_FANS)+=fan.o pwm.o
common-$(CONFIG_FACTORY_MODE)+=factory_mode.o
common-$(CONFIG_FLASH)+=flash.o
+common-$(CONFIG_FLASH_LOG)+=flash_log.o
common-$(CONFIG_FLASH_NVCOUNTER)+=nvcounter.o
common-$(CONFIG_FLASH_NVMEM)+=nvmem.o
common-$(CONFIG_FLASH_NVMEM_VARS)+=nvmem_vars.o
diff --git a/common/flash_log.c b/common/flash_log.c
new file mode 100644
index 0000000000..f5a138516a
--- /dev/null
+++ b/common/flash_log.c
@@ -0,0 +1,549 @@
+/* Copyright 2019 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "console.h"
+#include "crc8.h"
+#include "flash_log.h"
+#include "flash.h"
+#include "hooks.h"
+#include "shared_mem.h"
+#include "task.h"
+#include "timer.h"
+#include "util.h"
+
+/*
+ * A few assumptions this log facility design is based on are:
+ *
+ * - the log is stored in a flash space configured per board/chip combination.
+ * Chip level physical access functions are used for writing and erasing.
+ *
+ * - flash space access control is transparent for the log facility, if
+ * necessary, chip driver can register a callback for flash access control.
+ *
+ * - log events are rare, attempts to log concurrent events could fail.
+ *
+ * - log events are retrieved by the host periodically, much sooner than log
+ * overflows
+ *
+ * - as presented this facility is not suitable for saving panics'
+ * information, because flash drivers usually require OS services like
+ * interrupts, events, etc.
+ *
+ * - at the point of logging an entry there is still 200 bytes or so of stack
+ * is available.
+ *
+ * With the above in mind, here is the basic design:
+ *
+ * Entries in the log are of variable size, this layer is completely oblivious
+ * to the entries' contents. Each entry is saved in the log prepended by a
+ * header, which includes the following fields:
+ *
+ * - entry type, 1 byte
+ * - the timestamp the entry is saved at, 4 bytes, if real time is not
+ * available a monotonously increasing number is used
+ * - entry size, one byte, size is limited to 63 bytes maximum, two top bits
+ * of the size byte could be used as flags.
+ * - the entry crc, 1 byte
+ *
+ * To satisfy flash access limitations, this facility pads log entries to a
+ * multiple of the physical flash write size. Padding bytes value is set to
+ * FE_LOG_PAD. Having a fixed padding value will make it easier to examine log
+ * space snapshots by third party software. The users of this service are
+ * oblivious to the padding, they write and read back entries of arbitrary not
+ * necessarily aligned sizes in 0..MAX_FLASH_LOG_PAYLOAD_SIZE range.
+ *
+ * The log is kept in one flash page. Entries are of variable size, as defined
+ * by entry header. For read accesses log is mapped directly into the address
+ * space, write accesses are handled by chip specific drivers.
+ *
+ * On each startup, if the log is more than three quarters full, the log flash
+ * space is erased and a quarter space worth from top of the log is written
+ * back at the bottom of the erased space.
+ *
+ * If an entry would not fit into the log it is silently dropped.
+ *
+ * Log entries can not be written or read from within interrupt processing
+ * routines.
+ *
+ * Only one read or write access can be in progress at a time. Attempts to log
+ * new events while a log entry is being saved or retrieved will be ignored.
+ * Attempts to retrieve an entry while another entry is being saved or
+ * retrieved will return the appropriate return value.
+ *
+ * At run time log compaction is attempted if a request to add an entry is
+ * made and the log is more than 90% full. If compaction is not possible (for
+ * example, if memory allocation fails) and the new entry does not fit, it
+ * would be dropped.
+ *
+ * The above mentioned failures are tracked and when log becomes operational
+ * again (for instance memory heap grew back), log entries are added to record
+ * previously encountered failures.
+ *
+ * The API to retrieve log entries gets the timestamp of the last retrieved
+ * entry as an input parameter and returns the next entry exists. Sequence of
+ * invocations of the log entry retrieval API starting with timestamp of zero
+ * and then repeating with the timestamp of the previously retrieved entry
+ * allows to traverse the entire log.
+ *
+ * Initialization function verifies log integrity. When initializing from an
+ * erased space, the initialization function saves a new entry of type
+ * FE_LOG_START. If log corruption is detected, the initialization function
+ * tries to compact the log and adds a new entry of type FE_LOG_CORRUPTED on
+ * top of the compacted log.
+ */
+
+/*
+ * Structure keeping the context of the last entry retrieval access. If the
+ * next retrieval passed in timestamp saved in prev_timestamp, log search
+ * starts at read_cursor.
+ */
+static struct {
+ uint16_t read_cursor;
+ uint32_t prev_timestamp;
+} log_read_context;
+
+/* Location where next entry is going to be added. */
+static uint16_t log_write_cursor;
+/* Timestamp of the next entry (when real time is not available). */
+static uint32_t log_stamp;
+/* Set to True after log has been initialized. */
+static uint8_t log_inited;
+test_mockable_static uint8_t log_event_in_progress;
+test_mockable_static uint32_t lock_failures_count;
+static uint32_t overflow_failures_count;
+
+/*
+ * Callback set by the chip if flash log space access requires additional
+ * access control.
+ */
+static void (*platform_flash_control)(int enable);
+
+/*
+ * Helper function, convert offset in the log into a physical address in
+ * flash.
+ */
+static const void *log_offset_to_addr(uint16_t log_offset)
+{
+ return (const void *)(CONFIG_FLASH_LOG_BASE + log_offset);
+}
+
+/* Wrappers around chip flash access functions. */
+static void flash_log_erase(void)
+{
+ flash_physical_erase(CONFIG_FLASH_LOG_BASE - CONFIG_PROGRAM_MEMORY_BASE,
+ CONFIG_FLASH_LOG_SPACE);
+}
+
+static void flash_log_write(uint16_t log_offset, const void *data,
+ size_t data_size)
+{
+ flash_physical_write(log_offset + CONFIG_FLASH_LOG_BASE -
+ CONFIG_PROGRAM_MEMORY_BASE,
+ data_size, data);
+}
+
+/* Wrappers around platform flash control function, if registered. */
+static void flash_log_write_enable(void)
+{
+ if (platform_flash_control)
+ platform_flash_control(1);
+}
+
+static void flash_log_write_disable(void)
+{
+ if (platform_flash_control)
+ platform_flash_control(0);
+}
+
+/*
+ * Wrapper around crc8 calculation to avoid excessive typecasting throughout
+ * the rest of the file.
+ */
+static uint8_t calc_crc8(const void *buf, size_t size, uint8_t prev)
+{
+ return crc8_arg((const uint8_t *)buf, size, prev);
+}
+
+/* Try grabbing the lock, non blocking, return True if succeeded. */
+static int flash_log_lock_successful(void)
+{
+ if (in_interrupt_context())
+ return 0;
+
+ if (!log_inited)
+ return 0;
+
+ interrupt_disable();
+ if (log_event_in_progress) {
+ /* What a coincidence! */
+ interrupt_enable();
+ return 0;
+ }
+ log_event_in_progress = 1;
+ interrupt_enable();
+ return 1;
+}
+
+/*
+ * Verify entry validity, i.e. that it does fit into the log, has size within
+ * range and its crc8 matches.
+ */
+static int entry_is_valid(const struct flash_log_entry *r)
+{
+ size_t entry_size;
+ uint32_t entry_offset;
+ struct flash_log_entry copy;
+
+ entry_size = FLASH_LOG_ENTRY_SIZE(r->size);
+ entry_offset = (uintptr_t)r - CONFIG_FLASH_LOG_BASE;
+
+ if ((entry_offset + entry_size) > CONFIG_FLASH_LOG_SPACE)
+ return 0;
+
+ /* Crc of the entry is calculated with the crc field set to zero. */
+ copy = *r;
+ copy.crc = 0;
+ copy.crc = calc_crc8(&copy, sizeof(copy), 0);
+ copy.crc = calc_crc8(r + 1, FLASH_LOG_PAYLOAD_SIZE(r->size), copy.crc);
+ return (copy.crc == r->crc);
+}
+
+/* Attempt compacting the log. Could fail if memory allocation fails. */
+static void try_compacting(void)
+{
+ char *buf;
+ uint16_t read_cursor = 0;
+ uint16_t compac_cursor = 0;
+
+ /* Try rewriting the top 25% of the log into its bottom. */
+ /*
+ * Fist allocate a buffer large enough to keep a quarter of the
+ * log.
+ */
+ if (shared_mem_acquire(COMPACTION_SPACE_PRESERVE, &buf) != EC_SUCCESS)
+ return;
+
+ while (read_cursor < log_write_cursor) {
+ const struct flash_log_entry *r;
+ size_t entry_space;
+
+ r = log_offset_to_addr(read_cursor);
+ if (!entry_is_valid(r))
+ break;
+
+ entry_space = FLASH_LOG_ENTRY_SIZE(r->size);
+
+ if ((log_write_cursor - read_cursor) <=
+ COMPACTION_SPACE_PRESERVE) {
+ memcpy(buf + compac_cursor, r, entry_space);
+ compac_cursor += entry_space;
+ }
+
+ read_cursor += entry_space;
+ }
+
+ flash_log_write_enable();
+ flash_log_erase();
+ flash_log_write(0, buf, compac_cursor);
+ log_write_cursor = compac_cursor;
+ flash_log_write_disable();
+
+ shared_mem_release(buf);
+
+ log_read_context.read_cursor = 0;
+ log_read_context.prev_timestamp = 0;
+}
+
+static enum ec_error_list flash_log_add_event_core(uint8_t type, uint8_t size,
+ void *payload)
+{
+ union entry_u e;
+ size_t padded_entry_size;
+ size_t entry_size;
+ enum ec_error_list rv = EC_ERROR_INVAL;
+
+ if (size > MAX_FLASH_LOG_PAYLOAD_SIZE)
+ return rv;
+
+ if (!flash_log_lock_successful()) {
+ lock_failures_count++;
+ return rv;
+ }
+
+ /* The entry will take this much space in the flash. */
+ padded_entry_size = FLASH_LOG_ENTRY_SIZE(size);
+
+ if (log_write_cursor > RUN_TIME_LOG_FULL_WATERMARK)
+ try_compacting();
+
+ if (padded_entry_size > (CONFIG_FLASH_LOG_SPACE - log_write_cursor)) {
+ /*
+ * Compaction must have failed or was not allowed, and no room
+ * to log.
+ */
+ overflow_failures_count++;
+ goto log_add_exit;
+ }
+
+ /* Copy the payload into the entry if necessary. */
+ if (size)
+ memcpy(e.r.payload, payload, size);
+
+ entry_size = sizeof(e.r) + size;
+
+ e.r.timestamp = ++log_stamp;
+ e.r.size = size;
+ e.r.type = type;
+ e.r.crc = 0;
+ e.r.crc = calc_crc8(e.entry, entry_size, 0);
+
+ /* Add padding if necessary. */
+ while (entry_size < padded_entry_size)
+ e.entry[entry_size++] = FE_LOG_PAD;
+
+ flash_log_write_enable();
+ flash_log_write(log_write_cursor, e.entry, padded_entry_size);
+ flash_log_write_disable();
+
+ log_write_cursor += padded_entry_size;
+
+ rv = EC_SUCCESS;
+
+log_add_exit:
+ log_event_in_progress = 0;
+
+ return rv;
+}
+
+/*
+ * Report the failure count, using the passed in type. If report attempt is
+ * successful, reset the counter.
+ *
+ * Even though the counter is 4 bytes in size, the log entry payload is a one
+ * byte value capped at 255: the failure counter is extremely unlikely to
+ * exceed this value, and if it does - we don't really care about the exact
+ * number.
+ */
+static void report_failure(enum flash_event_type type, uint32_t *counter)
+{
+ uint8_t reported_counter;
+
+ /*
+ * Let's truncate the value at one byte, it is extremely unlikely to
+ * exceed it.
+ */
+ reported_counter = *counter;
+ if (reported_counter > 255)
+ reported_counter = 255;
+
+ if (flash_log_add_event_core(type, sizeof(reported_counter),
+ &reported_counter) == EC_SUCCESS)
+ *counter = 0;
+}
+
+void flash_log_add_event(uint8_t type, uint8_t size, void *payload)
+{
+ if (lock_failures_count)
+ report_failure(FE_LOG_LOCKS, &lock_failures_count);
+
+ if (overflow_failures_count)
+ report_failure(FE_LOG_OVERFLOWS, &overflow_failures_count);
+
+ flash_log_add_event_core(type, size, payload);
+}
+
+int flash_log_dequeue_event(uint32_t event_after, void *buffer,
+ size_t buffer_size)
+{
+ const struct flash_log_entry *r;
+ int rv = 0;
+ size_t copy_size;
+
+ if (!flash_log_lock_successful())
+ return -EC_ERROR_BUSY;
+
+ if (!event_after || (event_after < log_read_context.prev_timestamp)) {
+ /* Will have to start over. */
+ log_read_context.read_cursor = 0;
+ log_read_context.prev_timestamp = 0;
+ }
+
+ if (log_read_context.read_cursor >
+ (CONFIG_FLASH_LOG_SPACE - sizeof(*r)))
+ /* No more room in the log. */
+ goto log_read_exit;
+
+ do {
+ r = log_offset_to_addr(log_read_context.read_cursor);
+ if (r->timestamp == CONFIG_FLASH_ERASED_VALUE32)
+ /* Points at erased space, no more entries. */
+ goto log_read_exit;
+
+ if (!entry_is_valid(r)) {
+ rv = -EC_ERROR_INVAL;
+ goto log_read_exit;
+ }
+
+ log_read_context.read_cursor += FLASH_LOG_ENTRY_SIZE(r->size);
+
+ } while (r->timestamp <= event_after);
+
+ /*
+ * If we are here, we found the next event, let's see if it fits into
+ * the buffer.
+ */
+ copy_size = FLASH_LOG_PAYLOAD_SIZE(r->size) + sizeof(*r);
+ if (copy_size > buffer_size) {
+ rv = -EC_ERROR_MEMORY_ALLOCATION;
+ /* To be on the safe side will start over next time. */
+ log_read_context.read_cursor = 0;
+ log_read_context.prev_timestamp = 0;
+ goto log_read_exit;
+ }
+
+ log_read_context.prev_timestamp = r->timestamp;
+ memcpy(buffer, r, copy_size);
+ rv = copy_size;
+
+log_read_exit:
+ log_event_in_progress = 0;
+ return rv;
+}
+
+void flash_log_register_flash_control_callback(
+ void (*flash_control)(int enable))
+{
+ platform_flash_control = flash_control;
+}
+
+test_export_static void flash_log_init(void)
+{
+ uint16_t read_cursor = 0;
+ const struct flash_log_entry *r;
+
+ r = log_offset_to_addr(read_cursor);
+ while (entry_is_valid(r)) {
+ log_stamp = r->timestamp + 1;
+ read_cursor += FLASH_LOG_ENTRY_SIZE(r->size);
+ r = log_offset_to_addr(read_cursor);
+ }
+
+ log_write_cursor = read_cursor;
+ log_inited = 1;
+
+ flash_log_write_enable();
+ if (r->timestamp != CONFIG_FLASH_ERASED_VALUE32) {
+ /* Log space must be corrupted, compact it. */
+ try_compacting();
+ flash_log_add_event(FE_LOG_CORRUPTED, 0, NULL);
+ flash_log_write_disable();
+ return;
+ }
+
+ /*
+ * Timestamp field is set to all ones, presumably this points to free
+ * space in the log.
+ *
+ * Is there anything at all in the log?
+ */
+ if (read_cursor) {
+ /*
+ * Next write will have to come here unless compacting changes
+ * that.
+ */
+ if (read_cursor > STARTUP_LOG_FULL_WATERMARK)
+ try_compacting();
+ } else {
+ flash_log_add_event(FE_LOG_START, 0, NULL);
+ }
+ flash_log_write_disable();
+}
+DECLARE_HOOK(HOOK_INIT, flash_log_init, HOOK_PRIO_DEFAULT);
+
+#ifdef CONFIG_CMD_FLASH_LOG
+/*
+ * Display Flash event log.
+ */
+static int command_flash_log(int argc, char **argv)
+{
+ uint32_t stamp = 0;
+ union entry_u e;
+ int rv;
+ uint32_t type;
+ size_t size;
+ size_t i;
+
+ if (argc > 1) {
+ if (!strcasecmp(argv[1], "-e")) {
+ ccprintf("Erasing flash log\n");
+ flash_log_write_enable();
+ flash_log_erase();
+ flash_log_write_disable();
+
+ log_read_context.read_cursor = 0;
+ log_read_context.prev_timestamp = 0;
+ log_write_cursor = 0;
+
+ argc--;
+ argv++;
+ }
+ }
+ if (argc < 3) {
+ if (argc == 2)
+ stamp = atoi(argv[1]);
+
+ /* Retrieve entries newer than 'stamp'. */
+ while ((rv = flash_log_dequeue_event(stamp, e.entry,
+ sizeof(e))) > 0) {
+ size_t i;
+
+ ccprintf("%08x:%02x", e.r.timestamp, e.r.type);
+ for (i = 0; i < FLASH_LOG_PAYLOAD_SIZE(e.r.size); i++) {
+ if (i && !(i % 16)) {
+ ccprintf("\n ");
+ cflush();
+ }
+ ccprintf(" %02x", e.r.payload[i]);
+ }
+ ccprintf("\n");
+ stamp = e.r.timestamp;
+ }
+ if (rv)
+ ccprintf("Warning: Last attempt to dequeue returned "
+ "%d\n",
+ rv);
+ return EC_SUCCESS;
+ }
+
+ if (argc != 3) {
+ ccprintf("type and size of the entry are required\n");
+ return EC_ERROR_PARAM_COUNT;
+ }
+
+ type = atoi(argv[1]);
+ size = atoi(argv[2]);
+
+ if (type >= FLASH_LOG_NO_ENTRY) {
+ ccprintf("type must not exceed %d\n", FLASH_LOG_NO_ENTRY - 1);
+ return EC_ERROR_PARAM2;
+ }
+
+ if (size > MAX_FLASH_LOG_PAYLOAD_SIZE) {
+ ccprintf("size must not exceed %d\n",
+ MAX_FLASH_LOG_PAYLOAD_SIZE);
+ return EC_ERROR_PARAM3;
+ }
+
+ for (i = 0; i < size; i++)
+ e.r.payload[i] = type + i;
+ flash_log_add_event(type, size, e.r.payload);
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(flog, command_flash_log,
+ "[-e] ][[stamp]|[<type> <size>]]",
+ "Dump on the console the flash log contents,"
+ "optionally erasing it\n"
+ "or add a new entry of <type> and <size> bytes");
+#endif
diff --git a/include/config.h b/include/config.h
index bd31e034fd..8f39c41621 100644
--- a/include/config.h
+++ b/include/config.h
@@ -1044,6 +1044,7 @@
#define CONFIG_CMD_FASTCHARGE
#undef CONFIG_CMD_FLASH
#define CONFIG_CMD_FLASHINFO
+#undef CONFIG_CMD_FLASH_LOG
#undef CONFIG_CMD_FLASH_TRISTATE
#undef CONFIG_CMD_FORCETIME
#define CONFIG_CMD_GETTIME
@@ -1476,6 +1477,10 @@
/* This enables chip-specific access functions */
#define CONFIG_FLASH_PHYSICAL
#undef CONFIG_FLASH_BANK_SIZE
+/* Provide event log stored in flash memory. */
+#undef CONFIG_FLASH_LOG
+#undef CONFIG_FLASH_LOG_BASE
+#undef CONFIG_FLASH_LOG_SPACE
#undef CONFIG_FLASH_ERASED_VALUE32
#undef CONFIG_FLASH_ERASE_SIZE
#undef CONFIG_FLASH_ROW_SIZE
diff --git a/include/flash_log.h b/include/flash_log.h
new file mode 100644
index 0000000000..19093ba40b
--- /dev/null
+++ b/include/flash_log.h
@@ -0,0 +1,107 @@
+/* Copyright 2017 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef __CROS_EC_EVENT_LOG_H
+#define __CROS_EC_EVENT_LOG_H
+
+#include "config.h"
+#include "common.h"
+#include "compile_time_macros.h"
+#include "stddef.h"
+
+enum flash_event_type {
+ FE_LOG_START = 0,
+ FE_LOG_CORRUPTED = 1,
+ FE_TPM_I2C_ERROR = 2,
+ FE_LOG_OVERFLOWS = 3, /* A single byte, overflow counter. */
+ FE_LOG_LOCKS = 4, /* A single byte, lock failures counter. */
+
+ /*
+ * Fixed padding value makes it easier to parse log space
+ * snapshots.
+ */
+ FE_LOG_PAD = 253,
+ /* A test event, the highest possible event type value. */
+ FE_LOG_TEST = 254,
+};
+struct flash_log_entry {
+ /*
+ * Until real wall clock time is available this is a monotonically
+ * increasing entry number.
+ *
+ * TODO(vbendeb): however unlikely, there could be multiple events
+ * logged within the same 1 second interval. There needs to be a
+ * way to handle this. Maybe storing incremental time, having only
+ * the very first entry in the log carry the real time. Maybe
+ * enhancing the log traversion function to allow multiple entries
+ * with the same timestamp value.
+ */
+ uint32_t timestamp;
+ uint8_t size; /* [7:6] caller-def'd [5:0] payload size in bytes. */
+ uint8_t type; /* event type, caller-defined */
+ uint8_t crc;
+ uint8_t payload[0]; /* optional additional data payload: 0..63 bytes. */
+} __packed;
+
+/* Returned in the "type" field, when there is no entry available */
+#define FLASH_LOG_NO_ENTRY 0xff
+#define MAX_FLASH_LOG_PAYLOAD_SIZE ((1 << 6) - 1)
+#define FLASH_LOG_PAYLOAD_SIZE_MASK (MAX_FLASH_LOG_PAYLOAD_SIZE)
+
+#define FLASH_LOG_PAYLOAD_SIZE(size) ((size)&FLASH_LOG_PAYLOAD_SIZE_MASK)
+/* Size of log entry for a specific payload size. */
+#define FLASH_LOG_ENTRY_SIZE(payload_sz) \
+ ((FLASH_LOG_PAYLOAD_SIZE(payload_sz) + \
+ sizeof(struct flash_log_entry) + CONFIG_FLASH_WRITE_SIZE - 1) & \
+ ~(CONFIG_FLASH_WRITE_SIZE - 1))
+
+/*
+ * Flash log implementation expects minimum flash write size not to exceed the
+ * log header structure size.
+ *
+ * It will be easy to extend implementation to cover larger write sizes if
+ * necessary.
+ */
+BUILD_ASSERT(sizeof(struct flash_log_entry) >= CONFIG_FLASH_WRITE_SIZE);
+
+/* A helper structure to represent maximum size flash elog event entry. */
+union entry_u {
+ uint8_t entry[FLASH_LOG_ENTRY_SIZE(MAX_FLASH_LOG_PAYLOAD_SIZE)];
+ struct flash_log_entry r;
+};
+
+#define COMPACTION_SPACE_PRESERVE (CONFIG_FLASH_LOG_SPACE / 4)
+#define STARTUP_LOG_FULL_WATERMARK (CONFIG_FLASH_LOG_SPACE * 3 / 4)
+#define RUN_TIME_LOG_FULL_WATERMARK (CONFIG_FLASH_LOG_SPACE * 9 / 10)
+
+/*
+ * Add an entry to the event log. No errors are reported, as there is little
+ * we can do if logging attempt fails.
+ */
+void flash_log_add_event(uint8_t type, uint8_t size, void *payload);
+
+/*
+ * Report the next event after the passed in number.
+ *
+ * Return
+ * - positive integer - the size of the retrieved event
+ * - 0 if there is no more events
+ * - -EC_ERROR_BUSY if event logging is in progress
+ * - -EC_ERROR_MEMORY_ALLOCATION if event body does not fit into the buffer
+ * - -EC_ERROR_INVAL in case log storage is corrupted
+ */
+int flash_log_dequeue_event(uint32_t event_after, void *buffer,
+ size_t buffer_size);
+
+void flash_log_register_flash_control_callback(
+ void (*flash_control)(int enable));
+
+#if defined(TEST_BUILD)
+void flash_log_init(void);
+extern uint8_t log_event_in_progress;
+extern uint32_t lock_failures_count;
+#endif
+
+#endif /* __CROS_EC_EVENT_LOG_H */
diff --git a/test/build.mk b/test/build.mk
index 362667a17e..979fc352fe 100644
--- a/test/build.mk
+++ b/test/build.mk
@@ -29,6 +29,7 @@ test-list-host += entropy
test-list-host += extpwr_gpio
test-list-host += fan
test-list-host += flash
+test-list-host += flash_log
test-list-host += float
test-list-host += fp
test-list-host += hooks
@@ -88,6 +89,7 @@ entropy-y=entropy.o
extpwr_gpio-y=extpwr_gpio.o
fan-y=fan.o
flash-y=flash.o
+flash_log-y=flash_log.o
hooks-y=hooks.o
host_command-y=host_command.o
inductive_charging-y=inductive_charging.o
diff --git a/test/flash_log.c b/test/flash_log.c
new file mode 100644
index 0000000000..20ae939f23
--- /dev/null
+++ b/test/flash_log.c
@@ -0,0 +1,218 @@
+/* Copyright 2019 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Test Cr-50 Non-Voltatile memory module
+ */
+
+#include <stdlib.h>
+
+#include "common.h"
+#include "flash_log.h"
+#include "test_util.h"
+#include "util.h"
+
+struct log_stats {
+ size_t total_size;
+ size_t entry_count;
+};
+
+static int verify_single_entry(uint8_t fill_byte, int expected_type)
+{
+ int entry_size;
+ union entry_u e;
+ size_t i;
+ uint8_t *log_base = (void *)CONFIG_FLASH_LOG_BASE;
+
+ memset(log_base, fill_byte, CONFIG_FLASH_LOG_SPACE);
+ flash_log_init();
+
+ /* After initialization there should be a single log entry. */
+ entry_size = flash_log_dequeue_event(0, e.entry, sizeof(e.entry));
+ TEST_ASSERT(entry_size == sizeof(e.r));
+ TEST_ASSERT(e.r.type == expected_type);
+
+ entry_size = flash_log_dequeue_event(e.r.timestamp, e.entry,
+ sizeof(e.entry));
+ TEST_ASSERT(entry_size == 0);
+
+ /* Verify proper entry padding. */
+ i = sizeof(e.r);
+ TEST_ASSERT(i % CONFIG_FLASH_WRITE_SIZE);
+ for (; i % CONFIG_FLASH_WRITE_SIZE; i++)
+ TEST_ASSERT(log_base[i] == FE_LOG_PAD);
+
+ TEST_ASSERT(log_base[i] == 0xff); /* First byte above padding. */
+
+ return EC_SUCCESS;
+}
+
+static int test_init_from_scratch(void)
+{
+ return verify_single_entry(0xff, FE_LOG_START);
+}
+
+static int test_init_from_corrupted(void)
+{
+ /* Let's mess up the log space. */
+ return verify_single_entry(0x55, FE_LOG_CORRUPTED);
+}
+
+static int verify_log(struct log_stats *stats)
+{
+ union entry_u e;
+ size_t actual_size;
+ size_t actual_count;
+ int entry_size;
+
+ e.r.timestamp = 0;
+ actual_size = 0;
+ actual_count = 0;
+
+ while ((entry_size = flash_log_dequeue_event(e.r.timestamp, e.entry,
+ sizeof(e))) > 0) {
+ actual_count++;
+ actual_size += FLASH_LOG_ENTRY_SIZE(e.r.size);
+ }
+
+ TEST_ASSERT(entry_size == 0);
+
+ stats->total_size = actual_size;
+ stats->entry_count = actual_count;
+
+ return EC_SUCCESS;
+}
+
+static int fill_to_threshold(size_t threshold, struct log_stats *stats)
+{
+ int i;
+ uint8_t entry_type;
+ uint8_t payload_size;
+ uint8_t p[MAX_FLASH_LOG_PAYLOAD_SIZE];
+ size_t total_size;
+ size_t entry_count;
+
+ /* Start with an only entry in the log. */
+ TEST_ASSERT(verify_single_entry(0xff, FE_LOG_START) == EC_SUCCESS);
+
+ srand(0); /* Let's make sure it is consistent. */
+ entry_count = 1;
+ total_size = FLASH_LOG_ENTRY_SIZE(0);
+
+ /* Let's fill up the log to compaction limit. */
+ do {
+ entry_type = rand() % 0xfe;
+ payload_size = rand() % MAX_FLASH_LOG_PAYLOAD_SIZE;
+ for (i = 0; i < payload_size; i++)
+ p[i] = (i + entry_type) & 0xff;
+
+ flash_log_add_event(entry_type, payload_size, p);
+ total_size += FLASH_LOG_ENTRY_SIZE(payload_size);
+ entry_count++;
+ } while (total_size <= threshold);
+
+ TEST_ASSERT(verify_log(stats) == EC_SUCCESS);
+ TEST_ASSERT(stats->total_size == total_size);
+ TEST_ASSERT(stats->entry_count == entry_count);
+
+ /* This should get the log over the compaction threshold. */
+ flash_log_add_event(entry_type, payload_size, p);
+ TEST_ASSERT(verify_log(stats) == EC_SUCCESS);
+
+ return EC_SUCCESS;
+}
+
+static int test_run_time_compaction(void)
+{
+ struct log_stats stats;
+
+ TEST_ASSERT(fill_to_threshold(RUN_TIME_LOG_FULL_WATERMARK, &stats) ==
+ EC_SUCCESS);
+
+ /*
+ * Compacted space is guaranteed not to exceed the threshold plus the
+ * size of the largest possible entry.
+ */
+ TEST_ASSERT(stats.total_size <
+ (COMPACTION_SPACE_PRESERVE +
+ FLASH_LOG_ENTRY_SIZE(MAX_FLASH_LOG_PAYLOAD_SIZE)));
+
+ return EC_SUCCESS;
+}
+
+static int test_init_time_compaction(void)
+{
+ struct log_stats stats;
+
+ TEST_ASSERT(fill_to_threshold(STARTUP_LOG_FULL_WATERMARK, &stats) ==
+ EC_SUCCESS);
+
+ /*
+ * Init should roll the log back below the compaction preservation
+ * threshold.
+ */
+ flash_log_init();
+ TEST_ASSERT(verify_log(&stats) == EC_SUCCESS);
+
+ /*
+ * Compacted space is guaranteed not to exceed the threshold plus the
+ * size of the largest possible entry.
+ */
+ TEST_ASSERT(stats.total_size <
+ (COMPACTION_SPACE_PRESERVE +
+ FLASH_LOG_ENTRY_SIZE(MAX_FLASH_LOG_PAYLOAD_SIZE)));
+
+ return EC_SUCCESS;
+}
+
+static int test_lock_failure_reporting(void)
+{
+ union entry_u e;
+
+ TEST_ASSERT(test_init_from_scratch() == EC_SUCCESS);
+ lock_failures_count = 0;
+ log_event_in_progress = 1;
+
+ /* This should fail. */
+ flash_log_add_event(FE_LOG_TEST, 0, NULL);
+
+ /* Lock count should have been incremented. */
+ TEST_ASSERT(lock_failures_count == 1);
+
+ /* This should also fail. */
+ TEST_ASSERT(flash_log_dequeue_event(0, e.entry, sizeof(e.entry)) ==
+ -EC_ERROR_BUSY);
+
+ log_event_in_progress = 0;
+ /* This should succeed. */
+ flash_log_add_event(FE_LOG_TEST, 0, NULL);
+
+ TEST_ASSERT(lock_failures_count == 0);
+
+ /* There should be three entries in the log now. */
+ flash_log_dequeue_event(0, e.entry, sizeof(e.entry));
+ TEST_ASSERT(e.r.type == FE_LOG_START);
+
+ flash_log_dequeue_event(e.r.timestamp, e.entry, sizeof(e.entry));
+ TEST_ASSERT(e.r.type == FE_LOG_LOCKS);
+ TEST_ASSERT(FLASH_LOG_PAYLOAD_SIZE(e.r.size) == 1);
+ TEST_ASSERT(e.r.payload[0] == 1);
+
+ flash_log_dequeue_event(e.r.timestamp, e.entry, sizeof(e.entry));
+ TEST_ASSERT(e.r.type == FE_LOG_TEST);
+
+ return EC_SUCCESS;
+}
+
+void run_test(void)
+{
+ test_reset();
+
+ RUN_TEST(test_init_from_scratch);
+ RUN_TEST(test_init_from_corrupted);
+ RUN_TEST(test_run_time_compaction);
+ RUN_TEST(test_init_time_compaction);
+ RUN_TEST(test_lock_failure_reporting);
+
+ test_print_result();
+}
diff --git a/test/flash_log.tasklist b/test/flash_log.tasklist
new file mode 100644
index 0000000000..503898de80
--- /dev/null
+++ b/test/flash_log.tasklist
@@ -0,0 +1,17 @@
+/* Copyright 2019 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * List of enabled tasks in the priority order
+ *
+ * The first one has the lowest priority.
+ *
+ * For each task, use the macro TASK_TEST(n, r, d, s) where :
+ * 'n' in the name of the task
+ * 'r' in the main routine of the task
+ * 'd' in an opaque parameter passed to the routine at startup
+ * 's' is the stack size in bytes; must be a multiple of 8
+ */
+#define CONFIG_TEST_TASK_LIST
diff --git a/test/test_config.h b/test/test_config.h
index b5f81f4e1e..22f497bcc0 100644
--- a/test/test_config.h
+++ b/test/test_config.h
@@ -36,6 +36,15 @@
#define CONFIG_BACKLIGHT_REQ_GPIO GPIO_PCH_BKLTEN
#endif
+#ifdef TEST_FLASH_LOG
+#define CONFIG_CRC8
+#define CONFIG_FLASH_ERASED_VALUE32 (-1U)
+#define CONFIG_FLASH_LOG
+#define CONFIG_FLASH_LOG_BASE (CONFIG_PROGRAM_MEMORY_BASE + 0x800)
+#define CONFIG_FLASH_LOG_SPACE 0x800
+#define CONFIG_MALLOC
+#endif
+
#ifdef TEST_KB_8042
#define CONFIG_KEYBOARD_PROTOCOL_8042
#endif