summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBill Richardson <wfrichar@chromium.org>2016-11-28 11:38:20 -0800
committerchrome-bot <chrome-bot@chromium.org>2016-12-21 19:42:37 -0800
commit971e758ddec1b04b4a5479d716ec5a53ea48950f (patch)
tree489332a65ce2c781efb1692c53e81cb6fe994a20
parent0a97a6cf4bbc052e715080a9634078396645b733 (diff)
downloadchrome-ec-971e758ddec1b04b4a5479d716ec5a53ea48950f.tar.gz
Add nvmem-backed key=value variable storage
The CONFIG_FLASH_NVMEM option implements persistent, reliable storage regions in flash. This adds CONFIG_FLASH_NVMEM_VARS, which uses one of those storage regions for free-form variables. Refer to the comments in include/nvmem_vars.h and common/nvmem_vars.c for usage and implementation details. BUG=chrome-os-partner:61107 BRANCH=none TEST=make runtests This CL includes a number of new tests, specifically for this feature. No target boards use this feature yet so there's nothing to test on actual hardware, but the test/nvmem_vars executable includes console commands ("get", "set", "print") to try it out. Change-Id: I8597415dc3b00a1462f5b164eeb5073129030525 Signed-off-by: Bill Richardson <wfrichar@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/414194 Reviewed-by: Randall Spangler <rspangler@chromium.org> Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
-rw-r--r--common/build.mk1
-rw-r--r--common/nvmem_vars.c436
-rw-r--r--include/config.h10
-rw-r--r--include/nvmem_vars.h87
-rw-r--r--test/build.mk3
-rw-r--r--test/nvmem_vars.c526
-rw-r--r--test/nvmem_vars.tasklist17
-rw-r--r--test/test_config.h13
8 files changed, 1092 insertions, 1 deletions
diff --git a/common/build.mk b/common/build.mk
index 541dc55ed2..401a607c20 100644
--- a/common/build.mk
+++ b/common/build.mk
@@ -49,6 +49,7 @@ common-$(CONFIG_EXTPOWER_GPIO)+=extpower_gpio.o
common-$(CONFIG_FANS)+=fan.o pwm.o
common-$(CONFIG_FLASH)+=flash.o
common-$(CONFIG_FLASH_NVMEM)+=nvmem.o
+common-$(CONFIG_FLASH_NVMEM_VARS)+=nvmem_vars.o
common-$(CONFIG_FMAP)+=fmap.o
common-$(CONFIG_GESTURE_SW_DETECTION)+=gesture.o
common-$(CONFIG_HOSTCMD_EVENTS)+=host_event_commands.o
diff --git a/common/nvmem_vars.c b/common/nvmem_vars.c
new file mode 100644
index 0000000000..92da5d2981
--- /dev/null
+++ b/common/nvmem_vars.c
@@ -0,0 +1,436 @@
+/*
+ * Copyright 2016 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 "common.h"
+#include "nvmem.h"
+#include "nvmem_vars.h"
+#include "printf.h"
+#include "util.h"
+
+/****************************************************************************/
+/* Obtain/release a RAM copy of the persistent variable store */
+
+/*
+ * NOTE: It would be nice to allocate this at need, but shared memory is
+ * currently all or nothing and it's used elsewhere when writing to flash, so
+ * we have to allocate it statically until/unless that changes.
+ */
+static uint8_t rbuf[CONFIG_FLASH_NVMEM_VARS_USER_SIZE];
+static int rbuf_in_use;
+
+test_mockable_static
+void release_local_copy(void)
+{
+ rbuf_in_use = 0;
+}
+
+test_mockable_static
+int get_local_copy(void)
+{
+ int rv;
+
+ if (rbuf_in_use)
+ return EC_SUCCESS;
+
+ rbuf_in_use = 1;
+
+ rv = nvmem_read(0, CONFIG_FLASH_NVMEM_VARS_USER_SIZE,
+ rbuf, CONFIG_FLASH_NVMEM_VARS_USER_NUM);
+ if (rv)
+ release_local_copy();
+
+ return rv;
+}
+
+/****************************************************************************/
+/* Implementation notes
+ *
+ * The data_ member of struct tuple is simply the key and val blobs
+ * concatenated together.
+ *
+ * We store the variable entries in flash (and RAM) using the struct tuple
+ * defined in nvmem_vars.h. The entries are written sequentially with no
+ * padding, starting at offset 0 of the CONFIG_FLASH_NVMEM_VARS_USER_NUM user
+ * region. A single uint8_t zero byte will ALWAYS follow the valid entries.
+ * Since valid entries have nonzero key_len, we can always detect the presence
+ * of valid entries.
+ *
+ * A valid entry has both key_len and val_len between 1 and 255. The following
+ * bytes represent these tuples: <"A","ab">, <"B","cde">:
+ *
+ * Offset Content Meaning
+ * 0 0x01 length of key
+ * 1 0x02 length of val
+ * 2 0x00 variable flags (unused at present)
+ * 3 0x41 'A' (key)
+ * 4 0x61 'a' (val byte 1)
+ * 5 0x62 'b' (val byte 2)
+ * 6 0x01 length of key
+ * 7 0x03 length of val
+ * 8 0x00 variable flags (unused at present)
+ * 9 0x42 'B' (key)
+ * 10 0x63 'c' (val byte 1)
+ * 11 0x64 'd' (val byte 2)
+ * 12 0x65 'e' (val byte 3)
+ * 13 0x00 End of variables
+ *
+ * Note that the keys and values are not null-terminated since they're not
+ * strings, just binary blobs. The length of each entry is the size of the
+ * struct tuple header, plus the length of its key and value blobs.
+ *
+ * The .flags field is not currently used (and so is set to zero). It could be
+ * used in the future to for per-variable attributes, such as read-only,
+ * clear-on-reset, extended-length value, etc.
+ */
+
+/****************************************************************************/
+/* Helper functions */
+
+/* Return true if the tuple at rbuf+idx matches the key */
+static int match_key_at(uint32_t idx, const uint8_t *key, uint8_t key_len)
+{
+ struct tuple *tuple = (struct tuple *)(rbuf + idx);
+ uint32_t i, max_len;
+ uint8_t diffs;
+
+ /* Don't try to look past the 0 at the end of the user region */
+ max_len = MIN(key_len, CONFIG_FLASH_NVMEM_VARS_USER_SIZE - idx - 1);
+
+ /* Do constant-time comparision, since AP sets key_len to look for */
+ diffs = max_len ^ key_len;
+ diffs |= tuple->key_len ^ key_len;
+ for (i = 0; i < max_len; i++)
+ diffs |= tuple->data_[i] ^ key[i];
+
+ return !diffs;
+}
+
+/*
+ * Find the start of the next tuple in rbuf. Return false if there isn't one.
+ * The idx arg tracks where to start looking and where the next tuple was
+ * expected to be found.
+ */
+static int next_tuple(uint32_t *idx)
+{
+ struct tuple *tuple = (struct tuple *)(rbuf + *idx);
+
+ /* Not at a valid tuple now, so there aren't any more */
+ if (!tuple->key_len)
+ return 0;
+
+ /* Advance to the next one */
+ *idx += sizeof(struct tuple) + tuple->key_len + tuple->val_len;
+ tuple = (struct tuple *)(rbuf + *idx);
+
+ /* Do we have one or not? */
+ return tuple->key_len;
+}
+
+/*
+ * Look for the key in rbuf. If a match is found, set the index to the start of
+ * the tuple and return true. If the key is not found, set the index to the
+ * location where a new tuple should be added (0 if no tuples exist at all,
+ * else at the '\0' at the end of the tuples) and return false.
+ */
+test_mockable_static
+int getvar_idx(uint32_t *idx, const uint8_t *key, uint8_t key_len)
+{
+ uint32_t i = *idx;
+
+ do {
+ if (match_key_at(i, key, key_len)) {
+ *idx = i;
+ return 1;
+ }
+ } while (next_tuple(&i));
+
+ *idx = i;
+ return 0;
+}
+
+static inline int bogus_blob(const uint8_t *blob, uint8_t blob_len)
+{
+ return !blob || !blob_len;
+}
+
+/****************************************************************************/
+/* API functions */
+
+/* This MUST be called first. The internal functions assume valid entries */
+int initvars(void)
+{
+ struct tuple *tuple;
+ int rv, i, len;
+
+ rv = get_local_copy();
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ for (i = len = 0; /* FOREVER */ 1; i += len) {
+ tuple = (struct tuple *)(rbuf + i);
+
+ /* Zero key_len indicates end of tuples, we're done */
+ if (!tuple->key_len)
+ break;
+
+ /* Empty values are not allowed */
+ if (!tuple->val_len)
+ goto fixit;
+
+ /* See how big the tuple is */
+ len = sizeof(struct tuple) + tuple->key_len + tuple->val_len;
+
+ /* Oops, it's off the end (leave one byte for final 0) */
+ if (i + len >= CONFIG_FLASH_NVMEM_VARS_USER_SIZE)
+ goto fixit;
+ }
+
+ /* Found the end of variables. Now make sure the rest is all 0xff. */
+ for (i++ ; i < CONFIG_FLASH_NVMEM_VARS_USER_SIZE; i++)
+ if (rbuf[i] != 0xff)
+ goto fixit;
+
+ release_local_copy();
+ return EC_SUCCESS;
+
+fixit:
+ /* No variables */
+ rbuf[0] = 0;
+ /* Everything else is 0xff */
+ memset(rbuf + 1, 0xff, CONFIG_FLASH_NVMEM_VARS_USER_SIZE - 1);
+
+ return writevars();
+}
+
+const struct tuple *getvar(const uint8_t *key, uint8_t key_len)
+{
+ uint32_t i = 0;
+
+ if (bogus_blob(key, key_len))
+ return 0;
+
+ if (get_local_copy() != EC_SUCCESS)
+ return 0;
+
+ if (!getvar_idx(&i, key, key_len))
+ return 0;
+
+ return (const struct tuple *)(rbuf + i);
+}
+
+const uint8_t *tuple_key(const struct tuple *t)
+{
+ return t->data_;
+}
+
+const uint8_t *tuple_val(const struct tuple *t)
+{
+ return t->data_ + t->key_len;
+}
+
+int setvar(const uint8_t *key, uint8_t key_len,
+ const uint8_t *val, uint8_t val_len)
+{
+ struct tuple *tuple;
+ int rv, i, j;
+
+ if (bogus_blob(key, key_len))
+ return EC_ERROR_INVAL;
+
+ rv = get_local_copy();
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ i = 0;
+ if (getvar_idx(&i, key, key_len)) {
+ /* Found the match at position i */
+ j = i;
+ if (next_tuple(&j)) {
+ /*
+ * Now j is the start of the tuple after ours. Delete
+ * our entry by shifting left from there to the end of
+ * rbuf, so that it covers ours up.
+ *
+ * Before:
+ * i j
+ * <foo,bar><KEY,VAL><hey,splat>0
+ *
+ * After:
+ * i
+ * <foo,bar><hey,splat>0...
+ */
+ memmove(rbuf + i, rbuf + j,
+ CONFIG_FLASH_NVMEM_VARS_USER_SIZE - j);
+ /* Advance i to point to the end of all tuples */
+ while (next_tuple(&i))
+ ;
+ }
+ /* Whether we found a match or not, it's not there now */
+ }
+ /*
+ * Now i is where the new tuple should be written.
+ *
+ * Either this:
+ * i
+ * <foo,bar><hey,splat>0
+ *
+ * or there are no tuples at all and i == 0
+ *
+ */
+
+ /* If there's no value to save, we're done. */
+ if (bogus_blob(val, val_len))
+ goto done;
+
+ /*
+ * We'll always write the updated entry at the end of any existing
+ * tuples, and we mark the end with an additional 0. Make sure all that
+ * will all fit. If it doesn't, we've already removed the previous
+ * entry but we still need to mark the end.
+ */
+ if (i + sizeof(struct tuple) + key_len + val_len + 1 >
+ CONFIG_FLASH_NVMEM_VARS_USER_SIZE) {
+ rv = EC_ERROR_OVERFLOW;
+ goto done;
+ }
+
+ /* write the tuple */
+ tuple = (struct tuple *)(rbuf + i);
+ tuple->key_len = key_len;
+ tuple->val_len = val_len;
+ tuple->flags = 0; /* UNUSED, set to zero */
+ memcpy(tuple->data_, key, key_len);
+ memcpy(tuple->data_ + key_len, val, val_len);
+ /* move past it */
+ next_tuple(&i);
+
+done:
+ /* mark the end */
+ rbuf[i++] = 0;
+ /* erase the rest */
+ memset(rbuf + i, 0xff, CONFIG_FLASH_NVMEM_VARS_USER_SIZE - i);
+
+ return rv;
+}
+
+int writevars(void)
+{
+ int rv;
+
+ if (!rbuf_in_use)
+ return EC_SUCCESS;
+
+ rv = nvmem_write(0, CONFIG_FLASH_NVMEM_VARS_USER_SIZE,
+ rbuf, CONFIG_FLASH_NVMEM_VARS_USER_NUM);
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ rv = nvmem_commit();
+ if (rv != EC_SUCCESS)
+ return rv;
+
+ release_local_copy();
+
+ return rv;
+}
+
+/****************************************************************************/
+#ifdef TEST_BUILD
+#include "console.h"
+
+static void print_blob(const uint8_t *blob, int blob_len)
+{
+ int i;
+
+ for (i = 0; i < blob_len; i++)
+ ccprintf("%c", isprint(blob[i]) ? blob[i] : '.');
+}
+
+static int command_get(int argc, char **argv)
+{
+ const struct tuple *tuple;
+
+ if (argc != 2)
+ return EC_ERROR_PARAM_COUNT;
+
+ tuple = getvar(argv[1], strlen(argv[1]));
+ if (!tuple)
+ return EC_SUCCESS;
+
+ print_blob(tuple_val(tuple), tuple->val_len);
+ ccprintf("\n");
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(get, command_get,
+ "VARIABLE",
+ "Show the value of the specified variable");
+
+static int command_set(int argc, char **argv)
+{
+ int rc;
+
+ if (argc != 2 && argc != 3)
+ return EC_ERROR_PARAM_COUNT;
+
+ if (argc == 2)
+ rc = setvar(argv[1], strlen(argv[1]), 0, 0);
+ else
+ rc = setvar(argv[1], strlen(argv[1]),
+ argv[2], strlen(argv[2]));
+ if (rc)
+ return rc;
+
+ return writevars();
+}
+DECLARE_CONSOLE_COMMAND(set, command_set,
+ "VARIABLE [VALUE]",
+ "Set/clear the value of the specified variable");
+
+static int command_print(int argc, char **argv)
+{
+ const struct tuple *tuple;
+ int rv, i = 0;
+
+ rv = get_local_copy();
+ if (rv)
+ return rv;
+
+ tuple = (const struct tuple *)(rbuf + i);
+ if (!tuple->key_len)
+ return EC_SUCCESS;
+
+ do {
+ tuple = (const struct tuple *)(rbuf + i);
+ print_blob(tuple_key(tuple), tuple->key_len);
+ ccprintf("=");
+ print_blob(tuple_val(tuple), tuple->val_len);
+ ccprintf("\n");
+ } while (next_tuple(&i));
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(print, command_print,
+ "",
+ "Print all defined variables");
+
+static int command_dump(int argc, char **argv)
+{
+ int i, rv;
+
+ rv = get_local_copy();
+ if (rv)
+ return rv;
+
+ for (i = 0; i < CONFIG_FLASH_NVMEM_VARS_USER_SIZE; i++)
+ ccprintf(" %02x", rbuf[i]);
+ ccprintf("\n");
+
+ return EC_SUCCESS;
+}
+DECLARE_CONSOLE_COMMAND(dump, command_dump,
+ "",
+ "Dump the variable memory");
+#endif
diff --git a/include/config.h b/include/config.h
index 43b7c141bf..3ab6765b99 100644
--- a/include/config.h
+++ b/include/config.h
@@ -987,6 +987,16 @@
/* Size in bytes of NvMem area */
#undef CONFIG_FLASH_NVMEM_SIZE
+/* Enable <key,value> variable support (requires CONFIG_FLASH_NVMEM) */
+#undef CONFIG_FLASH_NVMEM_VARS
+/*
+ * We already have to define nvmem_user_sizes[] to specify the order and size
+ * of the user regions. CONFIG_FLASH_NVMEM_VARS looks for two symbols to
+ * specify the region number and size for the variable region.
+ */
+#undef CONFIG_FLASH_NVMEM_VARS_USER_NUM
+#undef CONFIG_FLASH_NVMEM_VARS_USER_SIZE
+
/*****************************************************************************/
/* Include a flashmap in the compiled firmware image */
diff --git a/include/nvmem_vars.h b/include/nvmem_vars.h
new file mode 100644
index 0000000000..867ea32018
--- /dev/null
+++ b/include/nvmem_vars.h
@@ -0,0 +1,87 @@
+/* Copyright 2016 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 __EC_INCLUDE_NVMEM_VARS_H
+#define __EC_INCLUDE_NVMEM_VARS_H
+
+/*
+ * CONFIG_FLASH_NVMEM provides persistent, atomic-update storage in
+ * flash. The storage is logically divided into one or more "user regions", as
+ * configured in board.h and board.c
+ *
+ * CONFIG_FLASH_NVMEM_VARS stores a set of <KEY, VALUE> tuples in the nvmem
+ * user region designated by CONFIG_FLASH_NVMEM_VARS_USER_NUM (in board.h)
+ *
+ * Tuples are stored and managed using this struct:
+ */
+
+struct tuple {
+ uint8_t key_len; /* 1 - 255 */
+ uint8_t val_len; /* 1 - 255 */
+ uint8_t flags; /* RESERVED, will be zeroed */
+ uint8_t data_[0]; /* Opaque. Don't look here. */
+};
+
+/*
+ * Both KEY and VALUE can be any binary blob between 1 and 255 bytes (flash
+ * memory is limited, so if you need longer values just use two keys and
+ * concatenate the blobs). Zero-length KEYs or VALUEs are not allowed.
+ * Assigning a zero-length VALUE to a KEY just deletes that tuple (if it
+ * existed).
+ *
+ * The expected usage is:
+ *
+ * 1. At boot, call initvars() to ensure that the variable storage region is
+ * valid. If it isn't, this will initialize it to an empty set.
+ *
+ * 2. Call getenv() or setenv() as needed. The first call to either will copy
+ * the storage regsion from flash into a RAM buffer. Any changes made with
+ * setenv() will affect only that RAM buffer.
+ *
+ * 3. Call writevars() to commit the RAM buffer to flash and free it.
+ *
+ * CAUTION: The underlying CONFIG_FLASH_NVMEM implementation allows access by
+ * multiple tasks, provided each task access only one user region. There is no
+ * support for simultaneous access to the *same* user region by multiple tasks.
+ * CONFIG_FLASH_NVMEM_VARS stores all variables in one user region, so if
+ * variable access by multiple tasks is required, the tasks should establish
+ * their own locks or mutexes to fit their usage. In general that would mean
+ * aquiring a lock before calling getvar() or setvar(), and releasing it after
+ * calling writevars().
+ */
+
+/*
+ * Initialize the persistent storage. This checks the user region to ensure
+ * that all tuples are valid and that there is one additional '\0' at the end.
+ * If any discrepancies are found, it erases all values. This should return
+ * EC_SUCCESS unless there is a problem writing to flash.
+ */
+int initvars(void);
+
+/*
+ * Look up a key, return a pointer to the tuple. If the key is not found,
+ * return NULL. WARNING: The returned pointer is only valid until the next call
+ * to setvar() or writevars(). Use it or lose it.
+ */
+const struct tuple *getvar(const uint8_t *key, uint8_t key_len);
+
+/* Use these to access the data components of a valid struct tuple pointer */
+const uint8_t *tuple_key(const struct tuple *);
+const uint8_t *tuple_val(const struct tuple *);
+
+/*
+ * Save the tuple in the RAM buffer. If val is NULL or val_len is 0, the
+ * tuple is deleted (if it existed). Returns EC_SUCCESS or error code.
+ */
+int setvar(const uint8_t *key, uint8_t key_len,
+ const uint8_t *val, uint8_t val_len);
+
+/*
+ * Commit any changes made with setvar() to persistent memory, and invalidate
+ * the RAM buffer. Return EC_SUCCESS or error code on failure.
+ */
+int writevars(void);
+
+#endif /* __EC_INCLUDE_NVMEM_VARS_H */
diff --git a/test/build.mk b/test/build.mk
index cc2c369c9f..54873cecf9 100644
--- a/test/build.mk
+++ b/test/build.mk
@@ -42,7 +42,7 @@ test-list-host+=bklight_lid bklight_passthru interrupt timer_dos button
test-list-host+=math_util motion_lid sbs_charging_v2 battery_get_params_smart
test-list-host+=lightbar inductive_charging usb_pd fan charge_manager
test-list-host+=charge_manager_drp_charging charge_ramp
-test-list-host+=rsa rsa3
+test-list-host+=nvmem_vars rsa rsa3
endif
battery_get_params_smart-y=battery_get_params_smart.o
@@ -68,6 +68,7 @@ math_util-y=math_util.o
motion_lid-y=motion_lid.o
mutex-y=mutex.o
nvmem-y=nvmem.o
+nvmem_vars-y=nvmem_vars.o
pingpong-y=pingpong.o
power_button-y=power_button.o
powerdemo-y=powerdemo.o
diff --git a/test/nvmem_vars.c b/test/nvmem_vars.c
new file mode 100644
index 0000000000..7a0333e525
--- /dev/null
+++ b/test/nvmem_vars.c
@@ -0,0 +1,526 @@
+/* Copyright 2016 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 of the key=val variable implementation (set, get, delete, etc).
+ */
+
+#include <string.h>
+
+#include "common.h"
+#include "compile_time_macros.h"
+#include "nvmem.h"
+#include "nvmem_vars.h"
+#include "printf.h"
+#include "shared_mem.h"
+#include "test_util.h"
+
+/* Declare the user regions (see test_config.h) */
+uint32_t nvmem_user_sizes[] = {
+ CONFIG_FLASH_NVMEM_VARS_USER_SIZE,
+};
+BUILD_ASSERT(ARRAY_SIZE(nvmem_user_sizes) == NVMEM_NUM_USERS);
+
+/* Internal functions exported for test */
+void release_local_copy(void);
+
+/****************************************************************************/
+/* Mock the flash storage */
+
+static uint8_t ram_buffer[CONFIG_FLASH_NVMEM_VARS_USER_SIZE];
+static uint8_t flash_buffer[CONFIG_FLASH_NVMEM_VARS_USER_SIZE];
+
+int nvmem_read(uint32_t startOffset, uint32_t size,
+ void *data_, enum nvmem_users user)
+{
+ /* Our mocks make some assumptions */
+ if (startOffset != 0 ||
+ size > CONFIG_FLASH_NVMEM_VARS_USER_SIZE ||
+ user != CONFIG_FLASH_NVMEM_VARS_USER_NUM)
+ return EC_ERROR_UNIMPLEMENTED;
+
+ if (!data_)
+ return EC_ERROR_INVAL;
+
+ memcpy(data_, flash_buffer, size);
+
+ return EC_SUCCESS;
+}
+
+int nvmem_write(uint32_t startOffset, uint32_t size,
+ void *data_, enum nvmem_users user)
+{
+ /* Our mocks make some assumptions */
+ if (startOffset != 0 ||
+ size > CONFIG_FLASH_NVMEM_VARS_USER_SIZE ||
+ user != CONFIG_FLASH_NVMEM_VARS_USER_NUM)
+ return EC_ERROR_UNIMPLEMENTED;
+
+ if (!data_)
+ return EC_ERROR_INVAL;
+
+ memcpy(ram_buffer, data_, size);
+
+ return EC_SUCCESS;
+}
+
+int nvmem_commit(void)
+{
+ memcpy(flash_buffer, ram_buffer, CONFIG_FLASH_NVMEM_VARS_USER_SIZE);
+ return EC_SUCCESS;
+}
+
+/****************************************************************************/
+/* Helper routines */
+
+static void erase_flash(void)
+{
+ /* Invalidate the RAM cache */
+ release_local_copy();
+
+ /* Zero flash */
+ memset(flash_buffer, 0xff, sizeof(flash_buffer));
+}
+
+/* Erase flash, then copy data_ over it */
+static void load_flash(const uint8_t *data_, size_t data_len)
+{
+ erase_flash();
+ memcpy(flash_buffer, data_, data_len);
+}
+
+/* Return true if flash matches data_, and is followed by 0xff to the end */
+static int verify_flash(const uint8_t *data_, size_t data_len)
+{
+ size_t i;
+
+ /* mismatch means false */
+ if (memcmp(flash_buffer, data_, data_len))
+ return 0;
+
+ for (i = data_len;
+ i < CONFIG_FLASH_NVMEM_VARS_USER_SIZE - data_len;
+ i++)
+ if (flash_buffer[i] != 0xff)
+ return 0;
+ return 1;
+}
+
+/*
+ * Treating both as strings, save the <key, value> pair.
+ */
+int str_setvar(const char *key, const char *val)
+{
+ /* Only for tests, so assume the length will fit */
+ uint8_t key_len, val_len;
+
+ key_len = strlen(key);
+ val_len = val ? strlen(val) : 0;
+
+ return setvar(key, key_len, val, val_len);
+}
+
+/*
+ * Treating both as strings, lookup the key and compare the result with the
+ * expected value. Return true if they match.
+ */
+static int str_matches(const char *key, const char *expected_val)
+{
+ const struct tuple *t = getvar(key, strlen(key));
+ uint8_t expected_len;
+
+ if (!expected_val && !t)
+ return 1;
+
+ if (expected_val && !t)
+ return 0;
+
+ if (!expected_val && t)
+ return 0;
+
+ expected_len = strlen(expected_val);
+ return !memcmp(tuple_val(t), expected_val, expected_len);
+}
+
+/****************************************************************************/
+/* Tests */
+
+static int check_init(void)
+{
+ /* Valid entries */
+ const uint8_t good[] = { 0x01, 0x01, 0x00, 'A', 'a',
+ 0x01, 0x01, 0x00, 'B', 'b',
+ 0x00 };
+
+ /* Empty variables are 0x00, followed by all 0xff */
+ const uint8_t empty[] = { 0x00 };
+
+ /*
+ * This is parsed as though there's only one variable, but it's wrong
+ * because the rest of the storage isn't 0xff.
+ */
+ const uint8_t bad_key[] = { 0x01, 0x01, 0x00, 'A', 'a',
+ 0x00, 0x01, 0x00, 'B', 'b',
+ 0x00 };
+
+ /* Zero-length variables are not allowed */
+ const uint8_t bad_val[] = { 0x01, 0x01, 0x00, 'A', 'a',
+ 0x01, 0x00, 0x00, 'B', 'b',
+ 0x00 };
+
+ /* The next constants use magic numbers based on on the region size */
+ BUILD_ASSERT(CONFIG_FLASH_NVMEM_VARS_USER_SIZE == 600);
+
+ /* This is one byte too large */
+ const uint8_t too_big[] = { [0] = 0xff, [1] = 0xff, /* 0 - 512 */
+ [513] = 0x01, [514] = 0x53, /* 513 - 599 */
+ [599] = 0x00 };
+
+ /* This should just barely fit */
+ const uint8_t just_right[] = { [0] = 0xff, [1] = 0xff, /* 0-512 */
+ [513] = 0x01, [514] = 0x52, /* 513-598 */
+ [599] = 0x00 };
+
+ /* No end marker */
+ const uint8_t not_right[] = { [0] = 0xff, [1] = 0xff, /* 0-512 */
+ [513] = 0x01, [514] = 0x52, /* 513-598 */
+ [599] = 0xff };
+
+ erase_flash();
+ load_flash(good, sizeof(good));
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(good, sizeof(good)));
+
+ erase_flash();
+ load_flash(empty, sizeof(empty));
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(empty, sizeof(empty)));
+
+ /* All 0xff quickly runs off the end of the storage */
+ erase_flash();
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(empty, sizeof(empty)));
+
+ erase_flash();
+ load_flash(bad_key, sizeof(bad_key));
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(empty, sizeof(empty)));
+
+ erase_flash();
+ load_flash(bad_val, sizeof(bad_val));
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(empty, sizeof(empty)));
+
+ erase_flash();
+ load_flash(too_big, sizeof(too_big));
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(empty, sizeof(empty)));
+
+ erase_flash();
+ load_flash(just_right, sizeof(just_right));
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(just_right, sizeof(just_right)));
+
+ erase_flash();
+ load_flash(not_right, sizeof(not_right));
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(empty, sizeof(empty)));
+
+ return EC_SUCCESS;
+}
+
+static int simple_search(void)
+{
+ const uint8_t preload[] = {
+ 0x02, 0x02, 0x00, 'h', 'o', 'y', 'o',
+ 0x02, 0x4, 0x00, 'y', 'o', 'h', 'o', 'y', 'o',
+ 0x02, 0x06, 0x00, 'm', 'o', 'y', 'o', 'h', 'o', 'y', 'o',
+ 0x00 };
+
+ load_flash(preload, sizeof(preload));
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(preload, sizeof(preload)));
+
+ TEST_ASSERT(str_matches("no", 0));
+ TEST_ASSERT(str_matches("ho", "yo"));
+ TEST_ASSERT(str_matches("yo", "hoyo"));
+ TEST_ASSERT(str_matches("mo", "yohoyo"));
+
+ return EC_SUCCESS;
+}
+
+static int simple_write(void)
+{
+ const uint8_t after_one[] = {
+ 0x02, 0x02, 0x00, 'h', 'o', 'y', 'o',
+ 0x00 };
+
+ const uint8_t after_two[] = {
+ 0x02, 0x02, 0x00, 'h', 'o', 'y', 'o',
+ 0x02, 0x4, 0x00, 'y', 'o', 'h', 'o', 'y', 'o',
+ 0x00 };
+
+ const uint8_t after_three[] = {
+ 0x02, 0x02, 0x00, 'h', 'o', 'y', 'o',
+ 0x02, 0x4, 0x00, 'y', 'o', 'h', 'o', 'y', 'o',
+ 0x02, 0x06, 0x00, 'm', 'o', 'y', 'o', 'h', 'o', 'y', 'o',
+ 0x00 };
+
+ erase_flash();
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+
+ TEST_ASSERT(setvar("ho", 2, "yo", 2) == EC_SUCCESS);
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(after_one, sizeof(after_one)));
+
+ TEST_ASSERT(setvar("yo", 2, "hoyo", 4) == EC_SUCCESS);
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(after_two, sizeof(after_two)));
+
+ TEST_ASSERT(setvar("mo", 2, "yohoyo", 6) == EC_SUCCESS);
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(after_three, sizeof(after_three)));
+
+ return EC_SUCCESS;
+}
+
+static int simple_delete(void)
+{
+ const char start_with[] = {
+ 0x01, 0x05, 0x00, 'A', 'a', 'a', 'a', 'a', 'a',
+ 0x02, 0x03, 0x00, 'B', 'B', 'b', 'b', 'b',
+ 0x03, 0x06, 0x00, 'C', 'C', 'C', 'x', 'y', 'z', 'p', 'd', 'q',
+ 0x01, 0x03, 0x00, 'M', 'm', '0', 'm',
+ 0x04, 0x01, 0x00, 'N', 'N', 'N', 'N', 'n',
+ 0x00 };
+
+ const char after_one[] = {
+ 0x02, 0x03, 0x00, 'B', 'B', 'b', 'b', 'b',
+ 0x03, 0x06, 0x00, 'C', 'C', 'C', 'x', 'y', 'z', 'p', 'd', 'q',
+ 0x01, 0x03, 0x00, 'M', 'm', '0', 'm',
+ 0x04, 0x01, 0x00, 'N', 'N', 'N', 'N', 'n',
+ 0x00 };
+
+ const char after_two[] = {
+ 0x02, 0x03, 0x00, 'B', 'B', 'b', 'b', 'b',
+ 0x03, 0x06, 0x00, 'C', 'C', 'C', 'x', 'y', 'z', 'p', 'd', 'q',
+ 0x01, 0x03, 0x00, 'M', 'm', '0', 'm',
+ 0x00 };
+
+ const char after_three[] = {
+ 0x02, 0x03, 0x00, 'B', 'B', 'b', 'b', 'b',
+ 0x01, 0x03, 0x00, 'M', 'm', '0', 'm',
+ 0x00 };
+
+ const char empty[] = { 0x00 };
+
+ erase_flash();
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+
+ TEST_ASSERT(setvar("A", 1, "aaaaa", 5) == EC_SUCCESS);
+ TEST_ASSERT(setvar("BB", 2, "bbb", 3) == EC_SUCCESS);
+ TEST_ASSERT(setvar("CCC", 3, "xyzpdq", 6) == EC_SUCCESS);
+ TEST_ASSERT(setvar("M", 1, "m0m", 3) == EC_SUCCESS);
+ TEST_ASSERT(setvar("NNNN", 4, "n", 1) == EC_SUCCESS);
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(start_with, sizeof(start_with)));
+
+ /* Zap first variable by setting var_len to 0 */
+ TEST_ASSERT(setvar("A", 1, "yohoyo", 0) == EC_SUCCESS);
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(after_one, sizeof(after_one)));
+
+ /* Zap last variable by passing null pointer */
+ TEST_ASSERT(setvar("NNNN", 4, 0, 3) == EC_SUCCESS);
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(after_two, sizeof(after_two)));
+
+ /* Ensure that zapping nonexistant variable does nothing */
+ TEST_ASSERT(setvar("XXX", 3, 0, 0) == EC_SUCCESS);
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(after_two, sizeof(after_two)));
+
+ /* Zap variable in the middle */
+ TEST_ASSERT(setvar("CCC", 3, 0, 0) == EC_SUCCESS);
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(after_three, sizeof(after_three)));
+
+ /* Zap the rest */
+ TEST_ASSERT(setvar("BB", 2, 0, 0) == EC_SUCCESS);
+ TEST_ASSERT(setvar("M", 1, 0, 0) == EC_SUCCESS);
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(empty, sizeof(empty)));
+
+ /* Zapping a nonexistant variable still does nothing */
+ TEST_ASSERT(setvar("XXX", 3, 0, 0) == EC_SUCCESS);
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+ TEST_ASSERT(verify_flash(empty, sizeof(empty)));
+
+ return EC_SUCCESS;
+}
+
+static int complex_write(void)
+{
+ erase_flash();
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+
+ /* Do a bunch of writes and erases */
+ str_setvar("ho", "aa");
+ str_setvar("zo", "nn");
+ str_setvar("yo", "CCCCCCCC");
+ str_setvar("zooo", "yyyyyyy");
+ str_setvar("yo", "AA");
+ str_setvar("ho", 0);
+ str_setvar("yi", "BBB");
+ str_setvar("yi", "AA");
+ str_setvar("hixx", 0);
+ str_setvar("yo", "BBB");
+ str_setvar("zo", "");
+ str_setvar("hi", "bbb");
+ str_setvar("ho", "cccccc");
+ str_setvar("yo", "");
+ str_setvar("zo", "ggggg");
+
+ /* What do we expect to find? */
+ TEST_ASSERT(str_matches("hi", "bbb"));
+ TEST_ASSERT(str_matches("hixx", 0));
+ TEST_ASSERT(str_matches("ho", "cccccc"));
+ TEST_ASSERT(str_matches("yi", "AA"));
+ TEST_ASSERT(str_matches("yo", 0));
+ TEST_ASSERT(str_matches("zo", "ggggg"));
+ TEST_ASSERT(str_matches("zooo", "yyyyyyy"));
+
+ return EC_SUCCESS;
+}
+
+static int weird_keys(void)
+{
+ uint8_t keyA[255];
+ uint8_t keyB[255];
+ const char *valA = "this is A";
+ const char *valB = "THIS IS b";
+ int i;
+ const struct tuple *t;
+
+ erase_flash();
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+
+ for (i = 0; i < 255; i++) {
+ keyA[i] = i;
+ keyB[i] = 255 - i;
+ }
+
+ TEST_ASSERT(setvar(keyA, sizeof(keyA),
+ valA, strlen(valA)) == EC_SUCCESS);
+
+ TEST_ASSERT(setvar(keyB, sizeof(keyB),
+ valB, strlen(valB)) == EC_SUCCESS);
+
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+
+ t = getvar(keyA, sizeof(keyA));
+ TEST_ASSERT(t);
+ TEST_ASSERT(t->val_len == strlen(valA));
+ TEST_ASSERT(memcmp(tuple_val(t), valA, strlen(valA)) == 0);
+
+ t = getvar(keyB, sizeof(keyB));
+ TEST_ASSERT(t);
+ TEST_ASSERT(t->val_len == strlen(valB));
+ TEST_ASSERT(memcmp(tuple_val(t), valB, strlen(valB)) == 0);
+
+ return EC_SUCCESS;
+}
+
+static int weird_values(void)
+{
+ const char *keyA = "this is A";
+ const char *keyB = "THIS IS b";
+ char valA[255];
+ char valB[255];
+ int i;
+ const struct tuple *t;
+
+ erase_flash();
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+
+ for (i = 0; i < 255; i++) {
+ valA[i] = i;
+ valB[i] = 255 - i;
+ }
+
+ TEST_ASSERT(setvar(keyA, strlen(keyA),
+ valA, sizeof(valA)) == EC_SUCCESS);
+ TEST_ASSERT(str_setvar("c", "CcC") == EC_SUCCESS);
+ TEST_ASSERT(setvar(keyB, strlen(keyB),
+ valB, sizeof(valB)) == EC_SUCCESS);
+ TEST_ASSERT(str_setvar("d", "dDd") == EC_SUCCESS);
+
+ TEST_ASSERT(writevars() == EC_SUCCESS);
+
+ t = getvar(keyA, strlen(keyA));
+ TEST_ASSERT(t);
+ TEST_ASSERT(memcmp(tuple_val(t), valA, sizeof(valA)) == 0);
+
+ t = getvar(keyB, strlen(keyB));
+ TEST_ASSERT(t);
+ TEST_ASSERT(memcmp(tuple_val(t), valB, sizeof(valB)) == 0);
+
+ TEST_ASSERT(str_matches("c", "CcC"));
+ TEST_ASSERT(str_matches("d", "dDd"));
+
+ return EC_SUCCESS;
+}
+
+static int fill_it_up(void)
+{
+ int i, n;
+ char key[20];
+
+ erase_flash();
+ TEST_ASSERT(initvars() == EC_SUCCESS);
+
+ /*
+ * Some magic numbers here, because we want to use up 10 bytes at a
+ * time and end up with exactly 9 free bytes left.
+ */
+ TEST_ASSERT(CONFIG_FLASH_NVMEM_VARS_USER_SIZE % 10 == 0);
+ n = CONFIG_FLASH_NVMEM_VARS_USER_SIZE / 10;
+ TEST_ASSERT(n < 1000);
+
+ /* Fill up the storage */
+ for (i = 0; i < n - 1; i++) {
+ /* 3-byte header, 5-char key, 2-char val, == 10 chars */
+ snprintf(key, sizeof(key), "kk%03d", i);
+ TEST_ASSERT(setvar(key, 5, "aa", 2) == EC_SUCCESS);
+ }
+
+ /*
+ * Should be nine bytes left in rbuf (because we need one more '\0' at
+ * the end). This won't fit.
+ */
+ TEST_ASSERT(setvar("kk999", 5, "aa", 2) == EC_ERROR_OVERFLOW);
+ /* But this will. */
+ TEST_ASSERT(setvar("kk999", 5, "a", 1) == EC_SUCCESS);
+ /* And this, because it replaces a previous entry */
+ TEST_ASSERT(setvar("kk000", 5, "bc", 2) == EC_SUCCESS);
+ /* But this still won't fit */
+ TEST_ASSERT(setvar("kk999", 5, "de", 2) == EC_ERROR_OVERFLOW);
+
+ return EC_SUCCESS;
+}
+
+void run_test(void)
+{
+ test_reset();
+
+ RUN_TEST(check_init);
+ RUN_TEST(simple_write);
+ RUN_TEST(simple_search);
+ RUN_TEST(simple_delete);
+ RUN_TEST(complex_write);
+ RUN_TEST(weird_keys);
+ RUN_TEST(weird_values);
+ RUN_TEST(fill_it_up);
+
+ test_print_result();
+}
diff --git a/test/nvmem_vars.tasklist b/test/nvmem_vars.tasklist
new file mode 100644
index 0000000000..cc500f5e8f
--- /dev/null
+++ b/test/nvmem_vars.tasklist
@@ -0,0 +1,17 @@
+/* Copyright 2016 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 /* No test task */
diff --git a/test/test_config.h b/test/test_config.h
index a453d04638..8eddcb9714 100644
--- a/test/test_config.h
+++ b/test/test_config.h
@@ -194,6 +194,19 @@ enum nvmem_users {
#endif
#endif
+#ifdef TEST_NVMEM_VARS
+#define NVMEM_PARTITION_SIZE 0x4000
+#define CONFIG_FLASH_NVMEM_VARS
+#ifndef __ASSEMBLER__
+/* Define the user region numbers */
+enum nvmem_users {
+ CONFIG_FLASH_NVMEM_VARS_USER_NUM,
+ NVMEM_NUM_USERS
+};
+#endif
+#define CONFIG_FLASH_NVMEM_VARS_USER_SIZE 600
+#endif /* TEST_NVMEM_VARS */
+
#ifndef __ASSEMBLER__
/* Callback function from charge_manager to send host event */
static inline void pd_send_host_event(int mask) { }