summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott <scollyer@chromium.org>2016-05-17 13:17:23 -0700
committerchrome-bot <chrome-bot@chromium.org>2016-05-26 16:17:27 -0700
commitd80a5837c47de5aa42707bd708f614a6f16e7313 (patch)
treed4c318d9ec9fc8d9e31e2fd67e64d1972b2af958
parent56ee8aefc33505a7df4e4148001a11ac461907a3 (diff)
downloadchrome-ec-d80a5837c47de5aa42707bd708f614a6f16e7313.tar.gz
NvMem: Added NV Memory module to ec/common/
Full implementation of NvMem read, write, and commit functions. Includes partition definitions, shared memory allocation, and initialization function. Includes a set of unit tests located in ec/test/nvmem.c which verify functionality. This module is required by Cr50, however this CL does not include any Cr50 specific code. BUG=chrome-os-partner:44745 BRANCH=none TEST=manual make runtests TEST_LIST_HOST=nvmem and verify that all tests pass Change-Id: I515b094f2179dbcb75dd11ab5b14434caad37edd Signed-off-by: Scott <scollyer@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/345632 Commit-Ready: Scott Collyer <scollyer@chromium.org> Tested-by: Scott Collyer <scollyer@chromium.org> Reviewed-by: Bill Richardson <wfrichar@chromium.org>
-rw-r--r--chip/host/system.c2
-rw-r--r--common/build.mk1
-rw-r--r--common/nvmem.c451
-rw-r--r--include/config.h11
-rw-r--r--include/nvmem.h137
-rw-r--r--test/build.mk3
-rw-r--r--test/nvmem.c352
-rw-r--r--test/nvmem.tasklist17
-rw-r--r--test/test_config.h27
9 files changed, 999 insertions, 2 deletions
diff --git a/chip/host/system.c b/chip/host/system.c
index 3de4ab10cd..757d00fc51 100644
--- a/chip/host/system.c
+++ b/chip/host/system.c
@@ -16,7 +16,7 @@
#include "timer.h"
#include "util.h"
-#define SHARED_MEM_SIZE 512 /* bytes */
+#define SHARED_MEM_SIZE 0x2000 /* bytes */
uint8_t __shared_mem_buf[SHARED_MEM_SIZE];
#define RAM_DATA_SIZE (sizeof(struct panic_data) + 512) /* bytes */
diff --git a/common/build.mk b/common/build.mk
index 426f2d412f..be72a44979 100644
--- a/common/build.mk
+++ b/common/build.mk
@@ -42,6 +42,7 @@ common-$(CONFIG_EXTENSION_COMMAND)+=extension.o
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_FMAP)+=fmap.o
common-$(CONFIG_GESTURE_SW_DETECTION)+=gesture.o
common-$(CONFIG_HOSTCMD_EVENTS)+=host_event_commands.o
diff --git a/common/nvmem.c b/common/nvmem.c
new file mode 100644
index 0000000000..7fbf5ebada
--- /dev/null
+++ b/common/nvmem.c
@@ -0,0 +1,451 @@
+/* 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 "console.h"
+#include "flash.h"
+#include "nvmem.h"
+#include "shared_mem.h"
+#include "timer.h"
+#include "util.h"
+
+#define CPRINTF(format, args...) cprintf(CC_COMMAND, format, ## args)
+
+#define NVMEM_ACQUIRE_CACHE_SLEEP_MS 25
+#define NVMEM_ACQUIRE_CACHE_MAX_ATTEMPTS (250 / NVMEM_ACQUIRE_CACHE_SLEEP_MS)
+#define NVMEM_NOT_INITIALIZED (-1)
+
+/* Structure MvMem Partition */
+struct nvmem_partition {
+ struct nvmem_tag tag;
+ uint8_t buffer[NVMEM_PARTITION_SIZE -
+ sizeof(struct nvmem_tag)];
+};
+
+/* NvMem user buffer start offset table */
+static uint32_t nvmem_user_start_offset[NVMEM_NUM_USERS];
+
+/* A/B partion that is most up to date */
+static int nvmem_act_partition;
+
+/* NvMem Cache Memory pointer */
+static uint8_t *cache_base_ptr;
+
+static int nvmem_verify_partition_sha(int index)
+{
+ uint8_t sha_comp[NVMEM_SHA_SIZE];
+ struct nvmem_partition *p_part;
+ uint8_t *p_data;
+
+ p_part = (struct nvmem_partition *)CONFIG_FLASH_NVMEM_BASE;
+ p_part += index;
+ p_data = (uint8_t *)p_part;
+ p_data += sizeof(sha_comp);
+
+ /* Number of bytes to compute sha over */
+ nvmem_compute_sha(p_data,
+ (NVMEM_PARTITION_SIZE - NVMEM_SHA_SIZE),
+ sha_comp,
+ NVMEM_SHA_SIZE);
+ /* Check if computed value matches stored value. */
+ return memcmp(p_part->tag.sha, sha_comp, NVMEM_SHA_SIZE);
+}
+
+static int nvmem_acquire_cache(void)
+{
+ int attempts = 0;
+ int ret;
+
+ /* TODO Need to add mutex lock/unlock crosbug.com/p/52520 */
+
+ if (shared_mem_size() < NVMEM_PARTITION_SIZE) {
+ CPRINTF("Not enough shared mem! avail = 0x%x < reqd = 0x%x\n",
+ shared_mem_size(), NVMEM_PARTITION_SIZE);
+ return EC_ERROR_OVERFLOW;
+ }
+
+ while (attempts < NVMEM_ACQUIRE_CACHE_MAX_ATTEMPTS) {
+ ret = shared_mem_acquire(NVMEM_PARTITION_SIZE,
+ (char **)&cache_base_ptr);
+ if (ret == EC_SUCCESS)
+ return EC_SUCCESS;
+ else if (ret == EC_ERROR_BUSY) {
+ CPRINTF("Shared Mem not avail! Attempt %d\n", attempts);
+ /* wait NVMEM_ACQUIRE_CACHE_SLEEP_MS msec */
+ /* TODO: what time really makes sense? */
+ msleep(NVMEM_ACQUIRE_CACHE_SLEEP_MS);
+ }
+ attempts++;
+ }
+ /* Timeout Error condition */
+ CPRINTF("%s:%d\n", __func__, __LINE__);
+ return EC_ERROR_TIMEOUT;
+}
+
+static int nvmem_update_cache_ptr(void)
+{
+ uint8_t *p_src;
+
+ /*
+ * If cache_base_ptr is not NULL, then nothing to do. However, if NULL,
+ * then need to first acquire the shared memory buffer and the full
+ * partition needs to be copied from flash into the cache buffer.
+ */
+ if (cache_base_ptr == NULL) {
+ if (nvmem_acquire_cache() != EC_SUCCESS)
+ return EC_ERROR_TIMEOUT;
+ /* Copy partiion contents from flash into cache buffer */
+ p_src = (uint8_t *)(CONFIG_FLASH_NVMEM_BASE +
+ nvmem_act_partition *
+ NVMEM_PARTITION_SIZE);
+ memcpy(cache_base_ptr, p_src,
+ NVMEM_PARTITION_SIZE);
+ }
+
+ return EC_SUCCESS;
+}
+
+static void nvmem_release_cache(void)
+{
+ /* Done with shared memory buffer, release it. */
+ shared_mem_release(cache_base_ptr);
+ /* Inidicate cache is not available */
+ cache_base_ptr = NULL;
+ /* TODO Release mutex lock here crosbug.com/p/52520 */
+}
+
+static int nvmem_is_unitialized(void)
+{
+ int n;
+ int ret;
+ uint32_t *p_nvmem;
+ struct nvmem_partition *p_part;
+
+ /* Point to start of Nv Memory */
+ p_nvmem = (uint32_t *)CONFIG_FLASH_NVMEM_BASE;
+ /* Verify that each byte is 0xff (4 bytes at a time) */
+ for (n = 0; n < (CONFIG_FLASH_NVMEM_SIZE >> 2); n++)
+ if (p_nvmem[n] != 0xffffffff)
+ return EC_ERROR_CRC;
+
+ /*
+ * NvMem is fully unitialized. Need to initialize tag and write tag to
+ * flash so at least 1 partition is ready to be used.
+ */
+ nvmem_act_partition = 0;
+ /* Need to acquire the shared memory buffer */
+ ret = nvmem_update_cache_ptr();
+ if (ret != EC_SUCCESS)
+ return ret;
+ p_part = (struct nvmem_partition *)cache_base_ptr;
+ /* Start with version 0 */
+ p_part->tag.version = 0;
+ /* Compute sha with updated tag */
+ nvmem_compute_sha(&cache_base_ptr[NVMEM_SHA_SIZE],
+ NVMEM_PARTITION_SIZE - NVMEM_SHA_SIZE,
+ p_part->tag.sha,
+ NVMEM_SHA_SIZE);
+ /*
+ * Partition 0 is initialized, write tag only to flash. Since the
+ * partition was just verified to be fully erased, can just do write
+ * operation.
+ */
+ if (flash_physical_write(CONFIG_FLASH_NVMEM_OFFSET,
+ sizeof(struct nvmem_tag),
+ cache_base_ptr)) {
+ CPRINTF("%s:%d\n", __func__, __LINE__);
+ nvmem_release_cache();
+ return EC_ERROR_UNKNOWN;
+ }
+ /* Can release the cache buffer now */
+ nvmem_release_cache();
+
+ return EC_SUCCESS;
+}
+
+static int nvmem_compare_version(void)
+{
+ struct nvmem_partition *p_part;
+ uint16_t ver0, ver1;
+ uint32_t delta;
+
+ p_part = (struct nvmem_partition *)CONFIG_FLASH_NVMEM_BASE;
+ ver0 = p_part->tag.version;
+ p_part++;
+ ver1 = p_part->tag.version;
+
+ /* Compute version difference accounting for wrap condition */
+ delta = (ver0 - ver1 + (1<<NVMEM_VERSION_BITS)) & NVMEM_VERSION_MASK;
+ /*
+ * If version number delta is positive in a circular sense then
+ * partition 0 has the newest version number. Otherwise, it's
+ * partition 1.
+ */
+ return delta < (1<<(NVMEM_VERSION_BITS-1)) ? 0 : 1;
+}
+
+static int nvmem_find_partition(void)
+{
+ int n;
+
+ /* Don't know which partition to use yet */
+ nvmem_act_partition = NVMEM_NOT_INITIALIZED;
+ /*
+ * Check each partition to determine if the sha is good. If both
+ * partitions have valid sha(s), then compare version numbers to select
+ * the most recent one.
+ */
+ for (n = 0; n < NVMEM_NUM_PARTITIONS; n++)
+ if (nvmem_verify_partition_sha(n) == EC_SUCCESS) {
+ if (nvmem_act_partition == NVMEM_NOT_INITIALIZED)
+ nvmem_act_partition = n;
+ else
+ nvmem_act_partition = nvmem_compare_version();
+ }
+ /*
+ * If active_partition is still not selected, then neither partition is
+ * valid. In this case need to determine if they are simply erased or
+ * both are corrupt. If erased, then can initialze the tag for the first
+ * one. If not fully erased, then this is an error condition.
+ */
+ if (nvmem_act_partition != NVMEM_NOT_INITIALIZED)
+ return EC_SUCCESS;
+
+ if (nvmem_is_unitialized()) {
+ CPRINTF("NvMem: No Valid Paritions and not fully erased!!\n");
+ return EC_ERROR_UNKNOWN;
+ }
+
+ return EC_SUCCESS;
+}
+
+static int nvmem_generate_offset_table(void)
+{
+ int n;
+ uint32_t start_offset;
+
+ /*
+ * Create table of starting offsets within partition for each user
+ * buffer that's been defined.
+ */
+ start_offset = sizeof(struct nvmem_tag);
+ for (n = 0; n < NVMEM_NUM_USERS; n++) {
+ nvmem_user_start_offset[n] = start_offset;
+ start_offset += nvmem_user_sizes[n];
+ }
+ /* Verify that all defined user buffers fit within the partition */
+ if (start_offset > NVMEM_PARTITION_SIZE)
+ return EC_ERROR_OVERFLOW;
+
+ return EC_SUCCESS;
+}
+static int nvmem_get_partition_off(int user, uint32_t offset,
+ uint32_t len, uint32_t *p_buf_offset)
+{
+ uint32_t start_offset;
+
+ /* Sanity check for user */
+ if (user >= NVMEM_NUM_USERS)
+ return EC_ERROR_OVERFLOW;
+
+ /* Get offset within the partition for the start of user buffer */
+ start_offset = nvmem_user_start_offset[user];
+ /*
+ * Ensure that read/write operation that is calling this function
+ * doesn't exceed the end of its buffer.
+ */
+ if (offset + len > nvmem_user_sizes[user])
+ return EC_ERROR_OVERFLOW;
+ /* Compute offset within the partition for the rd/wr operation */
+ *p_buf_offset = start_offset + offset;
+
+ return EC_SUCCESS;
+}
+
+int nvmem_setup(uint8_t starting_version)
+{
+ struct nvmem_partition *p_part;
+ int part;
+ int ret;
+
+ CPRINTF("Configuring NVMEM FLash Partition\n");
+ /*
+ * Initialize NVmem partition. This function will only be called
+ * if during nvmem_init() fails which implies that NvMem is not fully
+ * erased and neither partion tag contains a valid sha meaning they are
+ * both corrupted
+ */
+ for (part = 0; part < NVMEM_NUM_PARTITIONS; part++) {
+ /* Set active partition variable */
+ nvmem_act_partition = part;
+ /* Get the cache buffer */
+ if (nvmem_update_cache_ptr() != EC_SUCCESS) {
+ CPRINTF("NvMem: Cache ram not available!\n");
+ return EC_ERROR_TIMEOUT;
+ }
+
+ /* Fill in tag info */
+ p_part = (struct nvmem_partition *)cache_base_ptr;
+ /* Commit function will increment version number */
+ p_part->tag.version = starting_version + part - 1;
+ nvmem_compute_sha(&cache_base_ptr[NVMEM_SHA_SIZE],
+ NVMEM_PARTITION_SIZE -
+ NVMEM_SHA_SIZE,
+ p_part->tag.sha,
+ NVMEM_SHA_SIZE);
+ /*
+ * TODO: Should erase parition area prior to this function being
+ * called, or could write all user buffer data to 0xff here
+ * before the commit() call.
+ */
+ /* Partition is now ready, write it to flash. */
+ ret = nvmem_commit();
+ if (ret != EC_SUCCESS)
+ return ret;
+ }
+
+ return EC_SUCCESS;
+}
+
+int nvmem_init(void)
+{
+ int ret;
+
+ /* Generate start offsets within partiion for user buffers */
+ ret = nvmem_generate_offset_table();
+ if (ret) {
+ CPRINTF("%s:%d\n", __func__, __LINE__);
+ return ret;
+ }
+ /* Default state for cache_base_ptr */
+ cache_base_ptr = NULL;
+ ret = nvmem_find_partition();
+ if (ret != EC_SUCCESS) {
+ CPRINTF("%s:%d\n", __func__, __LINE__);
+ return ret;
+ }
+
+ return EC_SUCCESS;
+}
+
+int nvmem_read(unsigned int offset, unsigned int size,
+ void *data, enum nvmem_users user)
+{
+ int ret;
+ uint8_t *p_src;
+ uintptr_t src_addr;
+ uint32_t src_offset;
+
+ /* Point to either NvMem flash or ram if that's active */
+ if (cache_base_ptr == NULL)
+ src_addr = CONFIG_FLASH_NVMEM_BASE + nvmem_act_partition *
+ NVMEM_PARTITION_SIZE;
+
+ else
+ src_addr = (uintptr_t)cache_base_ptr;
+ /* Get partition offset for this read operation */
+ ret = nvmem_get_partition_off(user, offset, size, &src_offset);
+ if (ret != EC_SUCCESS)
+ return ret;
+ /* Advance to the correct byte within the data buffer */
+ src_addr += src_offset;
+ p_src = (uint8_t *)src_addr;
+
+ /* Copy from src into the caller's destination buffer */
+ memcpy(data, p_src, size);
+
+ return EC_SUCCESS;
+}
+
+int nvmem_write(unsigned int offset, unsigned int size,
+ void *data, enum nvmem_users user)
+{
+ int ret;
+ uint8_t *p_dest;
+ uintptr_t dest_addr;
+ uint32_t dest_offset;
+
+ /* Make sure that the cache buffer is active */
+ ret = nvmem_update_cache_ptr();
+ if (ret)
+ /* TODO: What to do when can't access cache buffer? */
+ return ret;
+
+ /* Compute partition offset for this write operation */
+ ret = nvmem_get_partition_off(user, offset, size, &dest_offset);
+ if (ret != EC_SUCCESS)
+ return ret;
+
+ /* Advance to correct offset within data buffer */
+ dest_addr = (uintptr_t)cache_base_ptr;
+ dest_addr += dest_offset;
+ p_dest = (uint8_t *)dest_addr;
+ /* Copy data from caller into destination buffer */
+ memcpy(p_dest, data, size);
+
+ return EC_SUCCESS;
+}
+
+int nvmem_commit(void)
+{
+ int nvmem_offset;
+ int new_active_partition;
+ uint16_t version;
+ struct nvmem_partition *p_part;
+
+ /*
+ * All scratch buffer blocks must be written to physical flash
+ * memory. In addition, the scratch block buffer index table
+ * entries must be reset along with the index itself.
+ */
+
+ /* Update version number */
+ if (cache_base_ptr == NULL) {
+ CPRINTF("%s:%d\n", __func__, __LINE__);
+ return EC_ERROR_UNKNOWN;
+ }
+ p_part = (struct nvmem_partition *)cache_base_ptr;
+ version = p_part->tag.version + 1;
+ /* Check for restricted version number */
+ if (version == NVMEM_VERSION_MASK)
+ version = 0;
+ p_part->tag.version = version;
+ /* Update the sha */
+ nvmem_compute_sha(&cache_base_ptr[NVMEM_SHA_SIZE],
+ NVMEM_PARTITION_SIZE - NVMEM_SHA_SIZE,
+ p_part->tag.sha,
+ NVMEM_SHA_SIZE);
+
+ /* Toggle parition being used (always write to current spare) */
+ new_active_partition = nvmem_act_partition ^ 1;
+ /* Point to first block within active partition */
+ nvmem_offset = CONFIG_FLASH_NVMEM_OFFSET + new_active_partition *
+ NVMEM_PARTITION_SIZE;
+ /* Write partition to NvMem */
+
+ /* Erase partition */
+ if (flash_physical_erase(nvmem_offset,
+ NVMEM_PARTITION_SIZE)) {
+ CPRINTF("%s:%d\n", __func__, __LINE__);
+ /* Free up scratch buffers */
+ nvmem_release_cache();
+ return EC_ERROR_UNKNOWN;
+ }
+ /* Write partition */
+ if (flash_physical_write(nvmem_offset,
+ NVMEM_PARTITION_SIZE,
+ cache_base_ptr)) {
+ CPRINTF("%s:%d\n", __func__, __LINE__);
+ /* Free up scratch buffers */
+ nvmem_release_cache();
+ return EC_ERROR_UNKNOWN;
+ }
+
+ /* Free up scratch buffers */
+ nvmem_release_cache();
+ /* Update newest partition index */
+ nvmem_act_partition = new_active_partition;
+ return EC_SUCCESS;
+}
diff --git a/include/config.h b/include/config.h
index 3ecf920140..bedb99a6a3 100644
--- a/include/config.h
+++ b/include/config.h
@@ -850,6 +850,17 @@
#undef CONFIG_EC_WRITABLE_STORAGE_SIZE
/*****************************************************************************/
+/* NvMem Configuration */
+/* Enable NV Memory module within flash */
+#undef CONFIG_FLASH_NVMEM
+/* Offset to start of NvMem area from base of flash */
+#undef CONFIG_FLASH_NVMEM_OFFSET
+/* Address of start of Nvmem area */
+#undef CONFIG_FLASH_NVMEM_BASE
+/* Size in bytes of NvMem area */
+#undef CONFIG_FLASH_NVMEM_SIZE
+
+/*****************************************************************************/
/* Include a flashmap in the compiled firmware image */
#define CONFIG_FMAP
diff --git a/include/nvmem.h b/include/nvmem.h
new file mode 100644
index 0000000000..408e0002f4
--- /dev/null
+++ b/include/nvmem.h
@@ -0,0 +1,137 @@
+/* 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 __CROS_EC_NVMEM_UTILS_H
+#define __CROS_EC_NVMEM_UTILS_H
+
+/*
+ * In order to provide maximum robustness for NvMem operations, the NvMem space
+ * is divided into two equal sized partitions. A partition contains a tag
+ * and a buffer for each NvMem user.
+ *
+ * NvMem Partiion
+ * ---------------------------------------------------------------------
+ * |0x8 tag | User Buffer 0 | User Buffer 1 | .... | User Buffer N-1 |
+ * ---------------------------------------------------------------------
+ *
+ * Physical Block Tag details
+ * ---------------------------------------------------------------------
+ * | sha | version | reserved |
+ * ---------------------------------------------------------------------
+ * sha -> 4 bytes of sha1 digest
+ * version -> 1 byte version number (0 - 0xfe)
+ * reserved -> 3 bytes
+ *
+ * At initialization time, each partition is scanned to see if it has a good sha
+ * entry. One of the two partitions being valid is a supported condition. If
+ * however, neither partiion is valid, then a check is made to see if NvMem
+ * space is fully erased. If this is detected, then the tag for partion 0 is
+ * populated and written into flash. If neither partition is valid and they
+ * aren't fully erased, then NvMem is marked corrupt and this failure condition
+ * must be reported back to the caller.
+ *
+ * A version number is used to distinguish between two valid partitions with
+ * the newsest version number (in a circular sense) marking the correct
+ * partition to use. The parition number 0/1 is tracked via a static
+ * variable. When the NvMem contents need to be updated, the flash erase/write
+ * of the updated partition will use the inactive partition space in NvMem. This
+ * way if there is a critical failure (i.e. loss of power) during the erase or
+ * write operation, then the contents of the active partition prior the most
+ * recent writes will still be preserved.
+ *
+ * The following CONFIG_FLASH_NVMEM_ defines are required for this module:
+ * CONFIG_FLASH_NVMEM -> enable/disable the module
+ * CONFIG_FLASH_NVMEM_OFFSET -> offset to start of NvMem from base of flash
+ * CONFIG_FLASH_NVMEM_BASE -> address of start of NvMem area
+ *
+ * The board.h file must define a macro or enum named NVMEM_NUM_USERS.
+ * The board.c file must include 1 function and an array of user buffer lengths
+ * nvmem_user_sizes[] -> array of user buffer lengths
+ * nvmem_compute_sha() -> function used to compute 4 byte sha (or equivalent)
+ *
+ * Note that total length of user buffers must satisfy the following:
+ * sum(user sizes) <= (CONFIG_FLASH_NVMEM_SIZE / 2) - sizeof(struct nvmem_tag)
+ */
+
+/* NvMem user buffer length table */
+extern uint32_t nvmem_user_sizes[NVMEM_NUM_USERS];
+
+#define NVMEM_NUM_PARTITIONS 2
+#define NVMEM_SHA_SIZE 4
+#define NVMEM_VERSION_BITS 8
+#define NVMEM_VERSION_MASK ((1 << NVMEM_VERSION_BITS) - 1)
+
+/* Struct for NV block tag */
+struct nvmem_tag {
+ uint8_t sha[NVMEM_SHA_SIZE];
+ uint8_t version;
+ uint8_t reserved[3];
+};
+
+/**
+ * Initialize NVMem translation table and state variables
+ *
+ * @return EC_SUCCESS if a valid translation table is constructed, else
+ * error code.
+ */
+int nvmem_init(void);
+
+/**
+ * Read 'size' amount of bytes from NvMem
+ *
+ * @param startOffset: Offset (in bytes) into NVmem logical space
+ * @param size: Number of bytes to read
+ * @param data: Pointer to destination buffer
+ * @param user: Data section within NvMem space
+ * @return EC_ERROR_OVERFLOW (non-zero) if the read operation would exceed the
+ * buffer length of the given user, otherwise EC_SUCCESS.
+ */
+int nvmem_read(unsigned int startOffset, unsigned int size,
+ void *data, enum nvmem_users user);
+
+/**
+ * Write 'size' amount of bytes to NvMem
+ *
+ * @param startOffset: Offset (in bytes) into NVmem logical space
+ * @param size: Number of bytes to write
+ * @param data: Pointer to source buffer
+ * @param user: Data section within NvMem space
+ * @return EC_ERROR_OVERFLOW if write exceeds buffer length
+ * EC_ERROR_TIMEOUT if nvmem cache buffer is not available
+ * EC_SUCCESS if no errors.
+ */
+int nvmem_write(unsigned int startOffset, unsigned int size,
+ void *data, enum nvmem_users user);
+
+
+/**
+ * Commit all previous NvMem writes to flash
+ *
+ * @return EC_SUCCESS if flash erase/operations are successful.
+ * EC_ERROR_UNKNOWN otherwise.
+ */
+int nvmem_commit(void);
+
+/**
+ * One time initialization of NvMem partitions
+ * @param version: Starting version number of partition 0
+ *
+ * @return EC_SUCCESS if flash operations are successful.
+ * EC_ERROR_UNKNOWN otherwise.
+ */
+int nvmem_setup(uint8_t version);
+
+/**
+ * Compute sha1 (lower 4 bytes or equivalent checksum) for NvMem tag
+ *
+ * @param p_buf: pointer to beginning of data
+ * @param num_bytes: length of data in bytes
+ * @param p_sha: pointer to where computed sha will be stored
+ * @param sha_len: length in bytes to use from sha computation
+ */
+void nvmem_compute_sha(uint8_t *p_buf, int num_bytes, uint8_t *p_sha,
+ int sha_len);
+
+#endif /* __CROS_EC_NVMEM_UTILS_H */
diff --git a/test/build.mk b/test/build.mk
index 81efeaf58e..5ff20b3afb 100644
--- a/test/build.mk
+++ b/test/build.mk
@@ -38,7 +38,7 @@ test-list-host+=sbs_charging host_command
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+=charge_manager_drp_charging charge_ramp nvmem
endif
battery_get_params_smart-y=battery_get_params_smart.o
@@ -63,6 +63,7 @@ lid_sw-y=lid_sw.o
math_util-y=math_util.o
motion_lid-y=motion_lid.o
mutex-y=mutex.o
+nvmem-y=nvmem.o
pingpong-y=pingpong.o
power_button-y=power_button.o
powerdemo-y=powerdemo.o
diff --git a/test/nvmem.c b/test/nvmem.c
new file mode 100644
index 0000000000..096e0ed5b9
--- /dev/null
+++ b/test/nvmem.c
@@ -0,0 +1,352 @@
+/* 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 Cr-50 Non-Voltatile memory module
+ */
+
+#include "common.h"
+#include "console.h"
+#include "crc.h"
+#include "nvmem.h"
+#include "flash.h"
+#include "shared_mem.h"
+#include "task.h"
+#include "test_util.h"
+#include "timer.h"
+#include "util.h"
+
+#define WRITE_SEGMENT_LEN 200
+#define WRITE_READ_SEGMENTS 4
+
+uint32_t nvmem_user_sizes[NVMEM_NUM_USERS] = {
+ NVMEM_USER_0_SIZE,
+ NVMEM_USER_1_SIZE,
+ NVMEM_USER_2_SIZE
+};
+
+static uint8_t write_buffer[NVMEM_PARTITION_SIZE];
+static uint8_t read_buffer[NVMEM_PARTITION_SIZE];
+static int flash_write_fail;
+
+void nvmem_compute_sha(uint8_t *p_buf, int num_bytes, uint8_t *p_sha,
+ int sha_bytes)
+{
+ uint32_t crc;
+ uint32_t *p_data;
+ int n;
+
+ crc32_init();
+ /* Assuming here that buffer is 4 byte aligned and that num_bytes is
+ * divisible by 4
+ */
+ p_data = (uint32_t *)p_buf;
+ for (n = 0; n < num_bytes/4; n++)
+ crc32_hash32(*p_data++);
+ crc = crc32_result();
+
+ p_data = (uint32_t *)p_sha;
+ *p_data = crc;
+}
+
+/* Used to allow/prevent Flash erase/write operations */
+int flash_pre_op(void)
+{
+ return flash_write_fail ? EC_ERROR_UNKNOWN : EC_SUCCESS;
+}
+
+static int generate_random_data(int offset, int num_bytes)
+{
+ int m, n, limit;
+ uint32_t r_data;
+
+ /* Ensure it will fit in the write buffer */
+ TEST_ASSERT((num_bytes + offset) <= NVMEM_PARTITION_SIZE);
+ /* Seed random number sequence */
+ r_data = prng((uint32_t)clock());
+ m = 0;
+ while (m < num_bytes) {
+ r_data = prng(r_data);
+ limit = MIN(4, num_bytes - m);
+ /* No byte alignment assumptions */
+ for (n = 0; n < limit; n++)
+ write_buffer[offset + m + n] = (r_data >> (n*8)) & 0xff;
+ m += limit;
+ }
+
+ return EC_SUCCESS;
+}
+
+static int test_write_read(uint32_t offset, uint32_t num_bytes, int user)
+{
+ int ret;
+
+ /* Generate source data */
+ generate_random_data(0, num_bytes);
+ /* Write source data to NvMem */
+ ret = nvmem_write(offset, num_bytes, write_buffer, user);
+ if (ret)
+ return ret;
+ nvmem_read(offset, num_bytes, read_buffer, user);
+ /* Verify memory was written into cache ram buffer */
+ TEST_ASSERT_ARRAY_EQ(write_buffer, read_buffer, num_bytes);
+ /* Write to flash */
+ ret = nvmem_commit();
+ if (ret != EC_SUCCESS)
+ return ret;
+ /* Read from flash */
+ nvmem_read(offset, num_bytes, read_buffer, user);
+ /* Verify that write to flash was successful */
+ TEST_ASSERT_ARRAY_EQ(write_buffer, read_buffer, num_bytes);
+
+ return EC_SUCCESS;
+}
+
+static int write_full_buffer(uint32_t size, int user)
+{
+ uint32_t offset;
+ uint32_t len;
+ int ret;
+
+ /* Start at beginning of the user buffer */
+ offset = 0;
+ do {
+ /* User default segment length unless it will exceed */
+ len = MIN(WRITE_SEGMENT_LEN, size - offset);
+ /* Generate data for tx buffer */
+ generate_random_data(offset, len);
+ /* Write data to Nvmem cache memory */
+ ret = nvmem_write(offset, len, &write_buffer[offset], user);
+ if (ret != EC_SUCCESS)
+ return ret;
+ /* Write to flash */
+ ret = nvmem_commit();
+ if (ret != EC_SUCCESS)
+ return ret;
+ /* Adjust starting offset by segment length */
+ offset += len;
+ } while (offset < size);
+
+ /* Entire flash buffer should be full at this point */
+ nvmem_read(0, size, read_buffer, user);
+ /* Verify that write to flash was successful */
+ TEST_ASSERT_ARRAY_EQ(write_buffer, read_buffer, size);
+
+ return EC_SUCCESS;
+}
+
+static int test_fully_erased_nvmem(void)
+{
+ /*
+ * The purpose of this test is to check NvMem intialization when NvMem
+ * is completely erased (i.e. following SpiFlash write of program). In
+ * this configuration, nvmem_init() should be able to detect this case
+ * and configure an initial NvMem partition.
+ */
+
+ /* Erase full NvMem area */
+ flash_physical_erase(CONFIG_FLASH_NVMEM_OFFSET,
+ CONFIG_FLASH_NVMEM_SIZE);
+ /* Call NvMem initialization function */
+ return nvmem_init();
+}
+
+static int test_configured_nvmem(void)
+{
+ /*
+ * The purpose of this test is to check nvmem_init() when both
+ * partitions are configured and valid.
+ */
+
+ /* Configure all NvMem partitions with starting version number 0 */
+ nvmem_setup(0);
+ /* Call NvMem initialization */
+ return nvmem_init();
+}
+
+static int test_corrupt_nvmem(void)
+{
+ uint32_t offset;
+ int n;
+
+ /*
+ * The purpose of this test is to check nvmem_init() in the case when no
+ * vailid partition exists (not fully erased and no valid sha). In this
+ * case, NvMem can't be initialized and should return an error to the
+ * calling function.
+ */
+
+ /* Overwrite tags of each parition */
+ memset(write_buffer, 0, 8);
+ for (n = 0; n < NVMEM_NUM_PARTITIONS; n++) {
+ offset = NVMEM_PARTITION_SIZE * n;
+ flash_physical_write(CONFIG_FLASH_NVMEM_OFFSET + offset, 8,
+ (const char *)write_buffer);
+ }
+ /* In this case nvmem_init is expected to fail */
+ return !nvmem_init();
+}
+
+static int test_write_read_sequence(void)
+{
+ uint32_t offset;
+ uint32_t length;
+ int user;
+ int n;
+ int ret;
+
+ for (user = 0; user < NVMEM_NUM_USERS; user++) {
+ /* Length for each write/read segment */
+ length = nvmem_user_sizes[user] / WRITE_READ_SEGMENTS;
+ /* Start at beginning of user buffer */
+ offset = 0;
+ for (n = 0; n < WRITE_READ_SEGMENTS; n++) {
+ ret = test_write_read(offset, length, user);
+ if (ret != EC_SUCCESS)
+ return ret;
+ /* Adjust offset by segment length */
+ offset += length;
+ /* For 1st iteration only, adjust to create stagger */
+ if (n == 0)
+ offset -= length / 2;
+
+ }
+ }
+ return EC_SUCCESS;
+}
+
+static int test_write_full_multi(void)
+{
+ int n;
+ int ret;
+
+ /*
+ * The purpose of this test is to completely fill each user buffer in
+ * NvMem with random data a segment length at a time. The data written
+ * to NvMem is saved in write_buffer[] and then can be used to check the
+ * NvMem writes were successful by reading and then comparing each user
+ * buffer.
+ */
+ for (n = 0; n < NVMEM_NUM_USERS; n++) {
+ ret = write_full_buffer(nvmem_user_sizes[n], n);
+ if (ret != EC_SUCCESS)
+ return ret;
+ }
+ return EC_SUCCESS;
+}
+
+static int test_write_fail(void)
+{
+ uint32_t offset = 0;
+ uint32_t num_bytes = 0x200;
+ int ret;
+
+ /* Do write/read sequence that's expected to be successful */
+ if (test_write_read(offset, num_bytes, NVMEM_USER_0))
+ return EC_ERROR_UNKNOWN;
+
+ /* Prevent flash erase/write operations */
+ flash_write_fail = 1;
+ /* Attempt flash write */
+ ret = test_write_read(offset, num_bytes, NVMEM_USER_0);
+ /* Resume normal operation */
+ flash_write_fail = 0;
+
+ /* This test is successful if write attempt failed */
+ return !ret;
+}
+
+static int test_cache_not_available(void)
+{
+ char **p_shared;
+ int ret;
+ uint32_t offset = 0;
+ uint32_t num_bytes = 0x200;
+
+ /*
+ * The purpose of this test is to validate that NvMem writes behave as
+ * expected when the shared memory buffer (used for cache ram) is and
+ * isn't available.
+ */
+
+ /* Do write/read sequence that's expected to be successful */
+ if (test_write_read(offset, num_bytes, NVMEM_USER_1))
+ return EC_ERROR_UNKNOWN;
+
+ /* Acquire shared memory */
+ if (shared_mem_acquire(num_bytes, p_shared))
+ return EC_ERROR_UNKNOWN;
+
+ /* Attempt write/read sequence that should fail */
+ ret = test_write_read(offset, num_bytes, NVMEM_USER_1);
+ /* Release shared memory */
+ shared_mem_release(*p_shared);
+ if (!ret)
+ return EC_ERROR_UNKNOWN;
+
+ /* Write/read sequence should work now */
+ return test_write_read(offset, num_bytes, NVMEM_USER_1);
+}
+
+static int test_buffer_overflow(void)
+{
+ int ret;
+ int n;
+
+ /*
+ * The purpose of this test is to check that NvMem writes behave
+ * properly in relation to the defined length of each user buffer. A
+ * write operation to completely fill the buffer is done first. This
+ * should pass. Then the same buffer is written to with one extra byte
+ * and this operation is expected to fail.
+ */
+
+ /* Do test for each user buffer */
+ for (n = 0; n < NVMEM_NUM_USERS; n++) {
+ /* Write full buffer */
+ ret = write_full_buffer(nvmem_user_sizes[n], n);
+ if (ret != EC_SUCCESS)
+ return ret;
+ /* Attempt to write full buffer plus 1 extra byte */
+ ret = write_full_buffer(nvmem_user_sizes[n] + 1, n);
+ if (!ret)
+ return EC_ERROR_UNKNOWN;
+ }
+
+ /* Test case where user buffer number is valid */
+ ret = test_write_read(0, 0x100, NVMEM_USER_0);
+ if (ret != EC_SUCCESS)
+ return ret;
+ /* Attempt same write, but with invalid user number */
+ ret = test_write_read(0, 0x100, NVMEM_NUM_USERS);
+ if (!ret)
+ return ret;
+
+ return EC_SUCCESS;
+}
+
+static void run_test_setup(void)
+{
+ /* Allow Flash erase/writes */
+ flash_write_fail = 0;
+ test_reset();
+}
+
+void run_test(void)
+{
+ run_test_setup();
+ /* Test NvMem Initialization function */
+ RUN_TEST(test_corrupt_nvmem);
+ RUN_TEST(test_fully_erased_nvmem);
+ RUN_TEST(test_configured_nvmem);
+ /* Test Read/Write/Commit functions */
+ RUN_TEST(test_write_read_sequence);
+ RUN_TEST(test_write_full_multi);
+ /* Test flash erase/write fail case */
+ RUN_TEST(test_write_fail);
+ /* Test shared_mem not available case */
+ RUN_TEST(test_cache_not_available);
+ /* Test buffer overflow logic */
+ RUN_TEST(test_buffer_overflow);
+ test_print_result();
+}
diff --git a/test/nvmem.tasklist b/test/nvmem.tasklist
new file mode 100644
index 0000000000..30e5f7fc22
--- /dev/null
+++ b/test/nvmem.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
diff --git a/test/test_config.h b/test/test_config.h
index d7e4b7b172..5b21c8f61c 100644
--- a/test/test_config.h
+++ b/test/test_config.h
@@ -149,6 +149,33 @@ int ncp15wb_calculate_temp(uint16_t adc);
#define CONFIG_USB_PD_PORT_COUNT 2
#endif
+#ifdef TEST_NVMEM
+#define CONFIG_FLASH_NVMEM
+#define CONFIG_FLASH_NVMEM_OFFSET 0x1000
+#define CONFIG_FLASH_NVMEM_BASE (CONFIG_PROGRAM_MEMORY_BASE + \
+ CONFIG_FLASH_NVMEM_OFFSET)
+#define CONFIG_FLASH_NVMEM_SIZE 0x4000
+#define CONFIG_SW_CRC
+
+#define NVMEM_PARTITION_SIZE \
+ (CONFIG_FLASH_NVMEM_SIZE / NVMEM_NUM_PARTITIONS)
+/* User buffer definitions for test purposes */
+#define NVMEM_USER_2_SIZE 0x201
+#define NVMEM_USER_1_SIZE 0x402
+#define NVMEM_USER_0_SIZE (NVMEM_PARTITION_SIZE - \
+ NVMEM_USER_2_SIZE - NVMEM_USER_1_SIZE - \
+ sizeof(struct nvmem_tag))
+
+#ifndef __ASSEMBLER__
+enum nvmem_users {
+ NVMEM_USER_0,
+ NVMEM_USER_1,
+ NVMEM_USER_2,
+ NVMEM_NUM_USERS
+};
+#endif
+#endif
+
#ifndef __ASSEMBLER__
/* Callback function from charge_manager to send host event */
static inline void pd_send_host_event(int mask) { }