From d80a5837c47de5aa42707bd708f614a6f16e7313 Mon Sep 17 00:00:00 2001 From: Scott Date: Tue, 17 May 2016 13:17:23 -0700 Subject: 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 Reviewed-on: https://chromium-review.googlesource.com/345632 Commit-Ready: Scott Collyer Tested-by: Scott Collyer Reviewed-by: Bill Richardson --- chip/host/system.c | 2 +- common/build.mk | 1 + common/nvmem.c | 451 ++++++++++++++++++++++++++++++++++++++++++++++++++++ include/config.h | 11 ++ include/nvmem.h | 137 ++++++++++++++++ test/build.mk | 3 +- test/nvmem.c | 352 ++++++++++++++++++++++++++++++++++++++++ test/nvmem.tasklist | 17 ++ test/test_config.h | 27 ++++ 9 files changed, 999 insertions(+), 2 deletions(-) create mode 100644 common/nvmem.c create mode 100644 include/nvmem.h create mode 100644 test/nvmem.c create mode 100644 test/nvmem.tasklist 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_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 @@ -849,6 +849,17 @@ #undef CONFIG_EC_WRITABLE_STORAGE_OFF #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 */ 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) { } -- cgit v1.2.1