/* 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; static int lock_test_started; int app_cipher(const void *salt_p, void *out_p, const void *in_p, size_t size) { const uint8_t *in = in_p; uint8_t *out = out_p; const uint8_t *salt = salt_p; size_t i; for (i = 0; i < size; i++) out[i] = in[i] ^ salt[i % CIPHER_SALT_SIZE]; return 1; } void app_compute_hash(uint8_t *p_buf, size_t num_bytes, uint8_t *p_hash, size_t hash_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(); for (n = 0; n < hash_bytes; n += sizeof(crc)) { size_t copy_bytes = MIN(sizeof(crc), hash_bytes - n); memcpy(p_hash + n, &crc, copy_bytes); } } /* 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); /* 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 */ nvmem_write(offset, len, &write_buffer[offset], user); /* 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_A, NVMEM_PARTITION_SIZE); flash_physical_erase(CONFIG_FLASH_NVMEM_OFFSET_B, NVMEM_PARTITION_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. */ /* Call NvMem initialization */ return nvmem_init(); } /* Verify that nvmem_erase_user_data only erases the given user's data. */ static int test_nvmem_erase_user_data(void) { uint32_t write_value; uint32_t read_value; int i; nvmem_init(); /* Make sure all partitions have data in them. */ for (i = 0; i < NVMEM_NUM_PARTITIONS; i++) { write_value = i; nvmem_write(0, sizeof(write_value), &write_value, NVMEM_USER_0); write_value = 2; nvmem_write(0, sizeof(write_value), &write_value, NVMEM_USER_1); write_value = 3; nvmem_write(0, sizeof(write_value), &write_value, NVMEM_USER_2); nvmem_commit(); } /* Check that the writes took place. */ read_value = ~write_value; nvmem_read(0, sizeof(read_value), &read_value, NVMEM_USER_0); TEST_ASSERT(read_value == i-1); nvmem_read(0, sizeof(read_value), &read_value, NVMEM_USER_1); TEST_ASSERT(read_value == 2); nvmem_read(0, sizeof(read_value), &read_value, NVMEM_USER_2); TEST_ASSERT(read_value == 3); /* * nvmem_erase_user_data() is supposed to erase the user's data across * all partitions. */ nvmem_erase_user_data(NVMEM_USER_0); for (i = 0; i < NVMEM_NUM_PARTITIONS; i++) { /* Make sure USER 0's data is (still) gone. */ nvmem_read(0, sizeof(read_value), &read_value, NVMEM_USER_0); TEST_ASSERT(read_value == 0xffffffff); /* Make sure the other users' data has been untouched. */ nvmem_read(0, sizeof(read_value), &read_value, NVMEM_USER_1); TEST_ASSERT(read_value == 2); /* * The active partition changes when the contents of the cache * changes. Therefore, in order to examine all the paritions, * we'll keep modifying one of the user's data. */ nvmem_read(0, sizeof(read_value), &read_value, NVMEM_USER_2); TEST_ASSERT(read_value == (3+i)); write_value = 4 + i; nvmem_write(0, sizeof(write_value), &write_value, NVMEM_USER_2); nvmem_commit(); } return EC_SUCCESS; } static int test_corrupt_nvmem(void) { uint8_t invalid_value = 0x55; int ret; struct nvmem_tag *p_part; uint8_t *p_data; /* * 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, the initialization create one new valid partition. */ /* Overwrite each partition will all 0s */ memset(write_buffer, invalid_value, NVMEM_PARTITION_SIZE); flash_physical_write(CONFIG_FLASH_NVMEM_OFFSET_A, NVMEM_PARTITION_SIZE, (const char *)write_buffer); flash_physical_write(CONFIG_FLASH_NVMEM_OFFSET_B, NVMEM_PARTITION_SIZE, (const char *)write_buffer); /* * The initialization function will look for a valid partition and if * none is found, it will create one, and save it at partition index * 1. */ ret = nvmem_init(); if (ret) return ret; /* * nvmem_init() called on uninitialized flash will create the first * valid partition with generation set to 0 at flash partition 1. * * Check here that partition 1 has a generation number of 0. */ p_part = (struct nvmem_tag *)CONFIG_FLASH_NVMEM_BASE_B; TEST_ASSERT(p_part->generation == 0); p_data = (uint8_t *)p_part + sizeof(struct nvmem_tag); /* Verify that partition 0 is still empty. */ memset(write_buffer, invalid_value, NVMEM_PARTITION_SIZE); p_data = (void *)CONFIG_FLASH_NVMEM_BASE_A; TEST_ASSERT_ARRAY_EQ(write_buffer, p_data, NVMEM_PARTITION_SIZE); /* Now let's write a different value into user NVMEM_CR50 */ invalid_value ^= ~0; TEST_ASSERT(nvmem_write(0, sizeof(invalid_value), &invalid_value, NVMEM_USER_0) == EC_SUCCESS); TEST_ASSERT(nvmem_commit() == EC_SUCCESS); /* Verify that partition 1 generation did not change. */ TEST_ASSERT(p_part->generation == 0); /* * Now verify that partition 0 generation is set to 1; */ p_part = (struct nvmem_tag *)CONFIG_FLASH_NVMEM_BASE_A; TEST_ASSERT(p_part->generation == 1); return EC_SUCCESS; } 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_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 int test_move(void) { uint32_t len = 0x100; uint32_t nv1_offset; uint32_t nv2_offset; int user = 0; int n; int ret; /* * The purpose of this test is to check that nvmem_move() behaves * properly. This test only uses one user buffer as accessing multiple * user buffers is tested separately. This test uses writes a set of * test data then test move operations with full overlap, half overlap * and no overlap. Folliwng these tests, the boundary conditions for * move operations are checked for the giver user buffer. */ nv1_offset = 0; for (n = 0; n < 3; n++) { /* Generate Test data */ generate_random_data(nv1_offset, len); nv2_offset = nv1_offset + (len / 2) * n; /* Write data to Nvmem cache memory */ nvmem_write(nv1_offset, len, &write_buffer[nv1_offset], user); nvmem_commit(); /* Test move while data is in cache area */ nvmem_move(nv1_offset, nv2_offset, len, user); nvmem_read(nv2_offset, len, read_buffer, user); if (memcmp(write_buffer, read_buffer, len)) return EC_ERROR_UNKNOWN; ccprintf("Memmove nv1 = 0x%x, nv2 = 0x%x\n", nv1_offset, nv2_offset); } /* Test invalid buffer offsets */ /* Destination offset is equal to length of buffer */ nv1_offset = 0; nv2_offset = nvmem_user_sizes[user]; /* Attempt to move just 1 byte */ ret = nvmem_move(nv1_offset, nv2_offset, 1, user); if (!ret) return EC_ERROR_UNKNOWN; /* Source offset is equal to length of buffer */ nv1_offset = nvmem_user_sizes[user]; nv2_offset = 0; /* Attempt to move just 1 byte */ ret = nvmem_move(nv1_offset, nv2_offset, 1, user); if (!ret) return EC_ERROR_UNKNOWN; nv1_offset = 0; nv2_offset = nvmem_user_sizes[user] - len; /* Move data chunk from start to end of buffer */ ret = nvmem_move(nv1_offset, nv2_offset, len, user); if (ret) return ret; /* Attempt to move data chunk 1 byte beyond end of user buffer */ nv1_offset = 0; nv2_offset = nvmem_user_sizes[user] - len + 1; ret = nvmem_move(nv1_offset, nv2_offset, len, user); if (!ret) return EC_ERROR_UNKNOWN; /* nvmem_move returned an error, need to clear internal error state */ nvmem_commit(); return EC_SUCCESS; } static int test_is_different(void) { uint32_t len = 0x41; uint32_t nv1_offset = 0; int user = 1; int ret; /* * The purpose of this test is to verify nv_is_different(). Test data is * written to a location in user buffer 1, then a case that's expected * to pass along with a case that is expected to fail are checked. Next * the same tests are repeated when the NvMem write is followed by a * commit operation. */ /* Generate test data */ generate_random_data(nv1_offset, len); /* Write to NvMem cache buffer */ nvmem_write(nv1_offset, len, &write_buffer[nv1_offset], user); /* Expected to be the same */ ret = nvmem_is_different(nv1_offset, len, &write_buffer[nv1_offset], user); if (ret) return EC_ERROR_UNKNOWN; /* Expected to be different */ ret = nvmem_is_different(nv1_offset + 1, len, &write_buffer[nv1_offset], user); if (!ret) return EC_ERROR_UNKNOWN; /* Commit cache buffer and retest */ nvmem_commit(); /* Expected to be the same */ ret = nvmem_is_different(nv1_offset, len, &write_buffer[nv1_offset], user); if (ret) return EC_ERROR_UNKNOWN; /* Expected to be different */ write_buffer[nv1_offset] ^= 0xff; ret = nvmem_is_different(nv1_offset, len, &write_buffer[nv1_offset], user); if (!ret) return EC_ERROR_UNKNOWN; return EC_SUCCESS; } int nvmem_first_task(void *unused) { uint32_t offset = 0; uint32_t num_bytes = WRITE_SEGMENT_LEN; int user = NVMEM_USER_0; task_wait_event(0); /* Generate source data */ generate_random_data(0, num_bytes); nvmem_write(0, num_bytes, &write_buffer[offset], user); /* Read from cache memory */ nvmem_read(0, num_bytes, read_buffer, user); /* Verify that write to nvmem was successful */ TEST_ASSERT_ARRAY_EQ(write_buffer, read_buffer, num_bytes); /* Wait here with mutex held by this task */ task_wait_event(0); /* Write to flash which releases nvmem mutex */ nvmem_commit(); nvmem_read(0, 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; } int nvmem_second_task(void *unused) { uint32_t offset = WRITE_SEGMENT_LEN; uint32_t num_bytes = WRITE_SEGMENT_LEN; int user = NVMEM_USER_0; task_wait_event(0); /* Gen test data and don't overwite test data generated by 1st task */ generate_random_data(offset, num_bytes); /* Write test data at offset 0 nvmem user buffer */ nvmem_write(0, num_bytes, &write_buffer[offset], user); /* Write to flash */ nvmem_commit(); /* Read from nvmem */ nvmem_read(0, num_bytes, read_buffer, user); /* Verify that write to nvmem was successful */ TEST_ASSERT_ARRAY_EQ(&write_buffer[offset], read_buffer, num_bytes); /* Clear flag to indicate lock test is complete */ lock_test_started = 0; return EC_SUCCESS; } static int test_lock(void) { /* * This purpose of this test is to verify the mutex lock portion of the * nvmem module. There are two additional tasks utilized. The first task * is woken and it creates some test data and does an * nvmem_write(). This will cause the mutex to be locked by the 1st * task. The 1st task then waits and control is returned to this * function and the 2nd task is woken, the 2nd task also attempts to * write data to nvmem. The 2nd task should stall waiting for the mutex * to be unlocked. * * When control returns to this function, the 1st task is woken again * and the nvmem operation is completed. This will allow the 2nd task to * grab the lock and finish its nvmem operation. The test will not * complete until the 2nd task finishes the nvmem write. A static global * flag is used to let this function know when the 2nd task is complete. * * Both tasks write to the same location in nvmem so the test will only * pass if the 2nd task can't write until the nvmem write in the 1st * task is completed. */ /* Set flag for start of test */ lock_test_started = 1; /* Wake first_task */ task_wake(TASK_ID_NV_1); task_wait_event(1000); /* Wake second_task. It should stall waiting for mutex */ task_wake(TASK_ID_NV_2); task_wait_event(1000); /* Go back to first_task so it can complete its nvmem operation */ task_wake(TASK_ID_NV_1); /* Wait for 2nd task to complete nvmem operation */ while (lock_test_started) task_wait_event(100); return EC_SUCCESS; } static int test_nvmem_save(void) { /* * The purpose of this test is to verify that if the written value * did not change the cache contents there is no actual write * happening at the commit time. */ int dummy_value; int offset = 0x10; uint8_t generation_a; uint8_t generation_b; uint8_t prev_generation; uint8_t new_generation; const struct nvmem_tag *part_a; const struct nvmem_tag *part_b; const struct nvmem_tag *new_gen_part; const struct nvmem_tag *prev_gen_part; part_a = (const struct nvmem_tag *)CONFIG_FLASH_NVMEM_BASE_A; part_b = (const struct nvmem_tag *)CONFIG_FLASH_NVMEM_BASE_B; /* * Make sure nvmem is initialized and both partitions have been * written. */ nvmem_init(); /* * Make sure something is changed at offset 0x10 into the second user * space. */ nvmem_read(offset, sizeof(dummy_value), &dummy_value, NVMEM_USER_1); dummy_value ^= ~0; nvmem_write(0x10, sizeof(dummy_value), &dummy_value, NVMEM_USER_1); nvmem_commit(); /* Verify that the two generation values are different. */ generation_a = part_a->generation; generation_b = part_b->generation; TEST_ASSERT(generation_a != generation_b); /* * Figure out which one should change next, we are close to the * beginnig of the test, no wrap is expected. */ if (generation_a > generation_b) { prev_generation = generation_a; new_generation = generation_a + 1; new_gen_part = part_b; prev_gen_part = part_a; } else { prev_generation = generation_b; new_generation = generation_b + 1; new_gen_part = part_a; prev_gen_part = part_b; } /* Write a new value, this should trigger generation switch. */ dummy_value += 1; TEST_ASSERT(nvmem_write(0x10, sizeof(dummy_value), &dummy_value, NVMEM_USER_1) == EC_SUCCESS); TEST_ASSERT(nvmem_commit() == EC_SUCCESS); TEST_ASSERT(prev_gen_part->generation == prev_generation); TEST_ASSERT(new_gen_part->generation == new_generation); /* Write the same value, this should NOT trigger generation switch. */ TEST_ASSERT(nvmem_write(0x10, sizeof(dummy_value), &dummy_value, NVMEM_USER_1) == EC_SUCCESS); TEST_ASSERT(nvmem_commit() == EC_SUCCESS); TEST_ASSERT(prev_gen_part->generation == prev_generation); TEST_ASSERT(new_gen_part->generation == new_generation); 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(); RUN_TEST(test_corrupt_nvmem); RUN_TEST(test_fully_erased_nvmem); RUN_TEST(test_configured_nvmem); RUN_TEST(test_write_read_sequence); RUN_TEST(test_write_full_multi); RUN_TEST(test_write_fail); RUN_TEST(test_buffer_overflow); RUN_TEST(test_move); RUN_TEST(test_is_different); RUN_TEST(test_lock); RUN_TEST(test_nvmem_erase_user_data); RUN_TEST(test_nvmem_save); test_print_result(); }