summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVadim Bendebury <vbendeb@chromium.org>2019-03-14 10:14:20 -0700
committerchrome-bot <chrome-bot@chromium.org>2019-03-26 21:45:38 -0700
commit53b44b301d2d5000c7b73b04276b4ffa6d2e8efa (patch)
tree9fc3d6b915ecc3876df190f4f6dfb7bb1606faf2
parent05e9ae7330cfa6144cbc29c30065a58f667fd48e (diff)
downloadchrome-ec-53b44b301d2d5000c7b73b04276b4ffa6d2e8efa.tar.gz
common: add flash event log facility
This patch adds implementation and test for a generic logger saving log entries in the flash. The entries payload are limited to 64 bytes in size, each entry starts with a header, which includes - 8 bit type type to allow to interpret the payload - 6 bit size field (two top bits of the byte are left for user flags, not yet used) - 32 bit timestamp to allow to identify newer log entries (presently this is just a monotonically increasing number) - 8 bit crc field protecting the entire entry The entries are padded to make sure that they are ending on the flash write boundary. The location of the log is defined by the platform using it. There is a provision for allowing the platform to register a callback which is needed to be called to allow write access to the log (as is the case on H1). While the device is running, the log is growing until the allotted flash space is 90% full. If there is an attempt save another entry after that the log is compacted, namely the last 25% worth of flash space is preserved, the log space is erased and the saved contents written back. On restarts the log is compacted if its size exceeds 75% of the allotted flash space. An API is provided to add entries to the log and to retrieve an entry newer than a certain timestamp value. Thus starting with timestamp zero will result in reading the very first log entry. To read the next entry, the read function needs to be called with the timestamp value of the current entry. This allows to browse the entire log, one entry at a time. A CLI command compiled in when CONFIG_CMD_FLASH_LOG is defined, allows to add log and retrieve log entries. BUG=b:63760920 BRANCH=cr50, cr50-mp TEST=the included test case can be invoked by make run-flash_log and it passes. More tests are done when the rest of the patch stack is added. Change-Id: I3dcdf2704a1e08fd3101183e434ac4a4e4cf1b9a Signed-off-by: Vadim Bendebury <vbendeb@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/1525143 Reviewed-by: Randall Spangler <rspangler@chromium.org> Reviewed-by: Andrey Pronin <apronin@chromium.org>
-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