diff options
-rw-r--r-- | common/build.mk | 1 | ||||
-rw-r--r-- | common/nvmem_vars.c | 436 | ||||
-rw-r--r-- | include/config.h | 10 | ||||
-rw-r--r-- | include/nvmem_vars.h | 87 | ||||
-rw-r--r-- | test/build.mk | 3 | ||||
-rw-r--r-- | test/nvmem_vars.c | 526 | ||||
-rw-r--r-- | test/nvmem_vars.tasklist | 17 | ||||
-rw-r--r-- | test/test_config.h | 13 |
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) { } |