| /* 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 "task.h" |
| #include "timer.h" |
| #include "util.h" |
| |
| #define CPRINTF(format, args...) cprintf(CC_COMMAND, format, ## args) |
| #define CPRINTS(format, args...) cprints(CC_COMMAND, format, ## args) |
| |
| #define NVMEM_NOT_INITIALIZED (-1) |
| |
| /* |
| * The NVMEM contents are stored in flash memory. At run time there is an SRAM |
| * cache and two instances of the contents in the flash in two partitions. |
| * |
| * Each instance is protected by a 16 bytes hash and has a 'generation' value |
| * associated with it. When NVMEM module is initialized it checks the flash |
| * stored instances. If both of them are valid, it considers the newer one |
| * (younger generation) to be the proper NVMEM contents and copies it to the |
| * SRAM cache. If only one instance is valid, it is used, and if no instances |
| * are valid - a new valid partition is created and copied into the SRAM |
| * cache. |
| * |
| * When stored in flash, the contents are encrypted, the hash value is used as |
| * the IV for the encryption routine. |
| * |
| * There is a mutex controlling access to the NVMEM. There are two levels |
| * of protection - for read only accesses and for write accesses. When the |
| * module is initialized the mutex is opened. |
| * |
| * If there are no pending writes, each read access locks the mutex, reads out |
| * the data and unlocks the mutex, thus multiple tasks could be reading NVMEM, |
| * blocking access momentarily. |
| * |
| * If a write access ever occurs things get more complicated. The write access |
| * leaves the mutex locked and stores the flag, indicating that the |
| * contents have changed and need to be saved, and stores the task id of the |
| * task performing the write access. |
| * |
| * The mutex remains locked in this case. Next time a read access happens, |
| * if it comes from the same task, the unlock in the end of the read is |
| * bypassed because the 'write in progress' flag is set. If a read or write |
| * request comes from another task, they will be blocked until the first |
| * task to write commits. |
| * |
| * nvmem_commit() calls the nvmem_save() function which checks if the cache |
| * contents indeed changed (by calculating the hash again). If there is no |
| * change - the mutex is released and the function exits. If there is a |
| * change, the new generation value is set, the new hash is calculated |
| * and the copy is saved in the least recently used flash partition, and |
| * then the lock is released. |
| */ |
| |
| /* Table of start addresses for each partition */ |
| static const uintptr_t nvmem_base_addr[NVMEM_NUM_PARTITIONS] = { |
| CONFIG_FLASH_NVMEM_BASE_A, |
| CONFIG_FLASH_NVMEM_BASE_B |
| }; |
| |
| /* 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 structure */ |
| struct nvmem_mutex_ { |
| task_id_t task; |
| int write_in_progress; |
| struct mutex mtx; |
| }; |
| |
| static struct nvmem_mutex_ nvmem_mutex = { .task = TASK_ID_COUNT }; |
| static uint8_t nvmem_cache[NVMEM_PARTITION_SIZE] __aligned(4); |
| |
| static uint8_t commits_enabled; |
| |
| /* NvMem error state */ |
| static int nvmem_error_state; |
| /* Flag to track if an Nv write/move is not completed */ |
| static int nvmem_write_error; |
| |
| static void nvmem_release_cache(void); |
| |
| /* |
| * Given the nvmem tag address calculate the sha value of the nvmem buffer and |
| * save it in the provided space. The caller is expected to provide enough |
| * space to store CIPHER_SALT_SIZE bytes. |
| */ |
| static void nvmem_compute_sha(struct nvmem_tag *tag, void *sha_buf) |
| { |
| app_compute_hash(tag->padding, NVMEM_PARTITION_SIZE - NVMEM_SHA_SIZE, |
| sha_buf, sizeof(tag->sha)); |
| } |
| |
| static int nvmem_save(void) |
| { |
| struct nvmem_partition *part; |
| size_t nvmem_offset; |
| int dest_partition; |
| uint8_t sha_comp[NVMEM_SHA_SIZE]; |
| int rv = EC_SUCCESS; |
| |
| part = (struct nvmem_partition *)nvmem_cache; |
| |
| /* Has anything changed in the cache? */ |
| nvmem_compute_sha(&part->tag, sha_comp); |
| |
| if (!memcmp(part->tag.sha, sha_comp, sizeof(part->tag.sha))) { |
| CPRINTF("%s: Nothing changed, skipping flash write\n", |
| __func__); |
| goto release_cache; |
| } |
| |
| /* Get flash offset of the partition to save to. */ |
| dest_partition = (nvmem_act_partition + 1) % NVMEM_NUM_PARTITIONS; |
| nvmem_offset = nvmem_base_addr[dest_partition] - |
| CONFIG_PROGRAM_MEMORY_BASE; |
| |
| /* Erase partition */ |
| rv = flash_physical_erase(nvmem_offset, NVMEM_PARTITION_SIZE); |
| if (rv != EC_SUCCESS) { |
| CPRINTF("%s flash erase failed\n", __func__); |
| goto release_cache; |
| } |
| |
| part->tag.layout_version = NVMEM_LAYOUT_VERSION; |
| part->tag.generation++; |
| |
| /* Calculate sha of the whole thing. */ |
| nvmem_compute_sha(&part->tag, part->tag.sha); |
| |
| /* Encrypt actual payload. */ |
| if (!app_cipher(part->tag.sha, part->buffer, part->buffer, |
| sizeof(part->buffer))) { |
| CPRINTF("%s encryption failed\n", __func__); |
| rv = EC_ERROR_UNKNOWN; |
| goto release_cache; |
| } |
| |
| rv = flash_physical_write(nvmem_offset, |
| NVMEM_PARTITION_SIZE, |
| nvmem_cache); |
| if (rv != EC_SUCCESS) { |
| CPRINTF("%s flash write failed\n", __func__); |
| goto release_cache; |
| } |
| |
| /* Restore payload. */ |
| if (!app_cipher(part->tag.sha, part->buffer, part->buffer, |
| sizeof(part->buffer))) { |
| CPRINTF("%s decryption failed\n", __func__); |
| rv = EC_ERROR_UNKNOWN; |
| goto release_cache; |
| } |
| |
| nvmem_act_partition = dest_partition; |
| |
| release_cache: |
| nvmem_mutex.write_in_progress = 0; |
| nvmem_release_cache(); |
| return rv; |
| } |
| |
| /* |
| * Read from flash and verify partition. |
| * |
| * @param index - index of the partition to verify |
| * |
| * Returns EC_SUCCESS on verification success |
| * EC_ERROR_BUSY in case of malloc failure |
| * EC_ERROR_UNKNOWN on failure to decrypt of verify. |
| */ |
| static int nvmem_partition_read_verify(int index) |
| { |
| uint8_t sha_comp[NVMEM_SHA_SIZE]; |
| struct nvmem_partition *p_part; |
| struct nvmem_partition *p_copy; |
| int ret; |
| |
| p_part = (struct nvmem_partition *)nvmem_base_addr[index]; |
| p_copy = (struct nvmem_partition *)nvmem_cache; |
| memcpy(p_copy, p_part, NVMEM_PARTITION_SIZE); |
| |
| /* Then decrypt it. */ |
| if (!app_cipher(p_copy->tag.sha, &p_copy->tag + 1, |
| &p_copy->tag + 1, |
| NVMEM_PARTITION_SIZE - sizeof(struct nvmem_tag))) { |
| CPRINTF("%s: decryption failure\n", __func__); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| /* |
| * Check if computed value matches stored value. Nonzero 'ret' value |
| * means there was a match. |
| */ |
| nvmem_compute_sha(&p_copy->tag, sha_comp); |
| ret = !memcmp(p_copy->tag.sha, sha_comp, NVMEM_SHA_SIZE); |
| |
| return ret ? EC_SUCCESS : EC_ERROR_UNKNOWN; |
| } |
| |
| static void nvmem_lock_cache(void) |
| { |
| /* |
| * Need to protect the cache contents value from other tasks |
| * attempting to do nvmem write operations. However, since this |
| * function may be called mutliple times prior to the mutex lock being |
| * released, there is a check first to see if the current task holds |
| * the lock. If it does then the task number will equal the value in |
| * cache.task, no need to wait. |
| * |
| * If the lock is held by a different task then mutex_lock function |
| * will operate as normal. |
| */ |
| if (nvmem_mutex.task == task_get_current()) |
| return; |
| |
| mutex_lock(&nvmem_mutex.mtx); |
| nvmem_mutex.task = task_get_current(); |
| } |
| |
| static void nvmem_release_cache(void) |
| { |
| if (nvmem_mutex.write_in_progress || !commits_enabled) |
| return; /* It will have to be saved first. */ |
| |
| /* Reset task number to max value */ |
| nvmem_mutex.task = TASK_ID_COUNT; |
| /* Release mutex lock here */ |
| mutex_unlock(&nvmem_mutex.mtx); |
| } |
| |
| static int nvmem_reinitialize(void) |
| { |
| nvmem_lock_cache(); /* Unlocked by nvmem_save() below. */ |
| /* |
| * NvMem is not properly initialized. Let's just erase everything and |
| * start over, so that at least 1 partition is ready to be used. |
| */ |
| nvmem_act_partition = 0; |
| |
| memset(nvmem_cache, 0xff, NVMEM_PARTITION_SIZE); |
| |
| /* Start with generation zero in the current active partition. */ |
| return nvmem_save(); |
| } |
| |
| static int nvmem_compare_generation(void) |
| { |
| struct nvmem_partition *p_part; |
| uint16_t ver0, ver1; |
| uint32_t delta; |
| |
| p_part = (struct nvmem_partition *)nvmem_base_addr[0]; |
| ver0 = p_part->tag.generation; |
| p_part = (struct nvmem_partition *)nvmem_base_addr[1]; |
| ver1 = p_part->tag.generation; |
| |
| /* Compute generation difference accounting for wrap condition */ |
| delta = (ver0 - ver1 + (1<<NVMEM_GENERATION_BITS)) & |
| NVMEM_GENERATION_MASK; |
| /* |
| * If generation number delta is positive in a circular sense then |
| * partition 0 has the newest generation number. Otherwise, it's |
| * partition 1. |
| */ |
| return delta < (1<<(NVMEM_GENERATION_BITS-1)) ? 0 : 1; |
| } |
| |
| static int nvmem_find_partition(void) |
| { |
| int n; |
| int newest; |
| |
| /* Don't know which partition to use yet */ |
| nvmem_act_partition = NVMEM_NOT_INITIALIZED; |
| |
| /* Find the newest partition available in flash. */ |
| newest = nvmem_compare_generation(); |
| |
| /* |
| * Find a partition with a valid sha, starting with the newest one. |
| */ |
| for (n = 0; n < NVMEM_NUM_PARTITIONS; n++) { |
| int check_part = (n + newest) % NVMEM_NUM_PARTITIONS; |
| |
| if (nvmem_partition_read_verify(check_part) == EC_SUCCESS) { |
| nvmem_act_partition = check_part; |
| return EC_SUCCESS; |
| } |
| ccprintf("%s:%d partiton %d verification FAILED\n", |
| __func__, __LINE__, check_part); |
| } |
| |
| /* |
| * If active_partition is still not selected, then neither partition |
| * is valid. Let's reinitialize the NVMEM - there is nothing else we |
| * can do. |
| */ |
| CPRINTS("%s: No Valid Partition found, will reinitialize!", __func__); |
| |
| if (nvmem_reinitialize() != EC_SUCCESS) { |
| CPRINTS("%s: Reinitialization failed!!"); |
| 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_erase_user_data(enum nvmem_users user) |
| { |
| int part; |
| int ret; |
| uint32_t user_offset, user_size; |
| |
| if (user >= NVMEM_NUM_USERS) |
| return EC_ERROR_INVAL; |
| |
| CPRINTS("Erasing NVMEM Flash Partition user: %d", user); |
| |
| ret = EC_SUCCESS; |
| |
| /* Find offset within cache. */ |
| user_offset = nvmem_user_start_offset[user]; |
| user_size = nvmem_user_sizes[user]; |
| |
| for (part = 0; part < NVMEM_NUM_PARTITIONS; part++) { |
| int rv; |
| |
| /* Lock the cache buffer. */ |
| nvmem_lock_cache(); |
| /* Erase the user's data. */ |
| memset(nvmem_cache + user_offset, 0xFF, user_size); |
| |
| /* |
| * Make sure the contents change between runs of |
| * nvmem_save() so that all flash partitions are |
| * written with empty contents and different |
| * generation numbers. |
| */ |
| ((struct nvmem_partition *)nvmem_cache)->tag.generation = part; |
| |
| /* Make a best effort to clear each partition. */ |
| rv = nvmem_save(); |
| if (rv != EC_SUCCESS) |
| ret = rv; |
| } |
| |
| return ret; |
| } |
| |
| 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; |
| } |
| /* Initialize error state, assume everything is good */ |
| nvmem_error_state = EC_SUCCESS; |
| nvmem_write_error = 0; |
| |
| /* |
| * Default policy is to allow all commits. This ensures reinitialization |
| * succeeds to bootstrap the nvmem area. |
| */ |
| commits_enabled = 1; |
| ret = nvmem_find_partition(); |
| |
| if (ret != EC_SUCCESS) { |
| /* Change error state to non-zero */ |
| nvmem_error_state = ret; |
| CPRINTF("%s:%d\n", __func__, __LINE__); |
| return ret; |
| } |
| |
| CPRINTS("Active Nvmem partition set to %d", nvmem_act_partition); |
| |
| return EC_SUCCESS; |
| } |
| |
| int nvmem_get_error_state(void) |
| { |
| return nvmem_error_state; |
| } |
| |
| int nvmem_is_different(uint32_t offset, uint32_t size, void *data, |
| enum nvmem_users user) |
| { |
| int ret; |
| uint32_t src_offset; |
| |
| nvmem_lock_cache(); |
| |
| /* 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 */ |
| |
| /* Compare NvMem with data */ |
| ret = memcmp(nvmem_cache + src_offset, data, size); |
| |
| nvmem_release_cache(); |
| |
| return ret; |
| } |
| |
| int nvmem_read(uint32_t offset, uint32_t size, |
| void *data, enum nvmem_users user) |
| { |
| int ret; |
| uint32_t src_offset; |
| |
| nvmem_lock_cache(); |
| |
| /* Get partition offset for this read operation */ |
| ret = nvmem_get_partition_off(user, offset, size, &src_offset); |
| |
| if (ret == EC_SUCCESS) |
| /* Copy from src into the caller's destination buffer */ |
| memcpy(data, nvmem_cache + src_offset, size); |
| |
| nvmem_release_cache(); |
| |
| return ret; |
| } |
| |
| int nvmem_write(uint32_t offset, uint32_t size, |
| void *data, enum nvmem_users user) |
| { |
| int ret; |
| uint8_t *p_dest; |
| uint32_t dest_offset; |
| |
| /* Make sure that the cache buffer is active */ |
| nvmem_lock_cache(); |
| nvmem_mutex.write_in_progress = 1; |
| |
| /* Compute partition offset for this write operation */ |
| ret = nvmem_get_partition_off(user, offset, size, &dest_offset); |
| if (ret != EC_SUCCESS) { |
| nvmem_write_error = 1; |
| return ret; |
| } |
| |
| /* Advance to correct offset within data buffer */ |
| p_dest = nvmem_cache + dest_offset; |
| |
| /* Copy data from caller into destination buffer */ |
| memcpy(p_dest, data, size); |
| |
| return EC_SUCCESS; |
| } |
| |
| int nvmem_move(uint32_t src_offset, uint32_t dest_offset, uint32_t size, |
| enum nvmem_users user) |
| { |
| int ret; |
| uint8_t *p_src, *p_dest; |
| uintptr_t base_addr; |
| uint32_t s_buff_offset, d_buff_offset; |
| |
| /* Make sure that the cache buffer is active */ |
| nvmem_lock_cache(); |
| nvmem_mutex.write_in_progress = 1; |
| |
| /* Compute partition offset for source */ |
| ret = nvmem_get_partition_off(user, src_offset, size, &s_buff_offset); |
| if (ret != EC_SUCCESS) { |
| nvmem_write_error = 1; |
| return ret; |
| } |
| |
| /* Compute partition offset for destination */ |
| ret = nvmem_get_partition_off(user, dest_offset, size, &d_buff_offset); |
| if (ret != EC_SUCCESS) { |
| nvmem_write_error = 1; |
| return ret; |
| } |
| |
| base_addr = (uintptr_t)nvmem_cache; |
| /* Create pointer to src location within partition */ |
| p_src = (uint8_t *)(base_addr + s_buff_offset); |
| /* Create pointer to dest location within partition */ |
| p_dest = (uint8_t *)(base_addr + d_buff_offset); |
| /* Move the data block in NvMem */ |
| memmove(p_dest, p_src, size); |
| |
| return EC_SUCCESS; |
| } |
| |
| int nvmem_enable_commits(void) |
| { |
| if (commits_enabled) |
| return EC_SUCCESS; |
| |
| if (nvmem_mutex.task != task_get_current()) { |
| CPRINTF("%s: locked by task %d, attempt to unlock by task %d\n", |
| __func__, nvmem_mutex.task, task_get_current()); |
| return EC_ERROR_INVAL; |
| } |
| |
| commits_enabled = 1; |
| CPRINTS("Committing NVMEM changes."); |
| return nvmem_commit(); |
| } |
| |
| void nvmem_disable_commits(void) |
| { |
| /* Will be unlocked when nvmem_enable_commits() is called. */ |
| nvmem_lock_cache(); |
| |
| commits_enabled = 0; |
| } |
| |
| int nvmem_commit(void) |
| { |
| if (nvmem_mutex.task == TASK_ID_COUNT) { |
| CPRINTF("%s: attempt to commit in unlocked state\n", |
| __func__, nvmem_mutex.task); |
| return EC_ERROR_OVERFLOW; /* Noting to commit. */ |
| } |
| |
| if (nvmem_mutex.task != task_get_current()) { |
| CPRINTF("%s: locked by task %d, attempt to unlock by task %d\n", |
| __func__, nvmem_mutex.task, task_get_current()); |
| return EC_ERROR_INVAL; |
| } |
| |
| /* Ensure that all writes/moves prior to commit call succeeded */ |
| if (nvmem_write_error) { |
| CPRINTS("%s: Write Error, commit abandoned", __func__); |
| /* Clear error state */ |
| nvmem_write_error = 0; |
| commits_enabled = 1; |
| nvmem_release_cache(); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| if (!commits_enabled) { |
| CPRINTS("Skipping commit"); |
| return EC_SUCCESS; |
| } |
| |
| /* Write active partition to NvMem */ |
| return nvmem_save(); |
| } |