/* SPDX-License-Identifier: GPL-2.0-only */ /* * Include Unit Under Test source code directly instead of linking it. * This will allow access to internal structures and data without having * to extract them to another header file. */ #include "../lib/imd_cbmem.c" #include #include #include #include #include #include #include #include #define CBMEM_ENTRY_ID 0xA001 static void reset_imd(void) { imd.lg.limit = 0; imd.lg.r = NULL; imd.sm.limit = 0; imd.sm.r = NULL; cbmem_initialized = 0; } /* This implementation allows imd_cbmem module tests without linking lib/cbmem_common.c Function indicates to each hook if cbmem is being recovered or not. */ void cbmem_run_init_hooks(int is_recovery) { function_called(); } uintptr_t cbmem_top_chipset(void) { return _cbmem_top_ptr; } static void *get_cbmem_ptr(void) { void *cbmem_top_ptr = (void *)_cbmem_top_ptr; if (cbmem_top_ptr) return cbmem_top_ptr - CBMEM_SIZE; else return NULL; } static void clear_cbmem(void) { void *ptr = get_cbmem_ptr(); if (ptr) memset(ptr, 0, CBMEM_SIZE); } static void reset_and_clear_cbmem(void) { reset_imd(); clear_cbmem(); } void prepare_simple_cbmem(void) { reset_and_clear_cbmem(); expect_function_call(cbmem_run_init_hooks); cbmem_initialize_empty(); cbmem_entry_add(CBMEM_ENTRY_1_ID, CBMEM_ENTRY_1_SIZE); cbmem_entry_add(CBMEM_ENTRY_2_ID, CBMEM_ENTRY_2_SIZE); cbmem_entry_add(CBMEM_ENTRY_SM_1_ID, CBMEM_ENTRY_SM_1_SIZE); cbmem_entry_add(CBMEM_ENTRY_SM_2_ID, CBMEM_ENTRY_SM_2_SIZE); } static void test_cbmem_top(void **state) { cbmem_top_init_once(); if (ENV_CREATES_CBMEM) assert_ptr_equal(cbmem_top_chipset(), cbmem_top()); if (ENV_POSTCAR || ENV_RAMSTAGE) assert_ptr_equal((void *)_cbmem_top_ptr, cbmem_top()); } static void test_cbmem_initialize_empty(void **state) { const struct cbmem_entry *found; /* Expect clean call without recovery */ expect_function_call(cbmem_run_init_hooks); cbmem_initialize_empty(); found = cbmem_entry_find(SMALL_REGION_ID); assert_non_null(found); /* Check that cbmem has only root, large and small entry. */ assert_int_equal(2, ((struct imd_root *)imd.lg.r)->num_entries); assert_int_equal(1, ((struct imd_root *)imd.sm.r)->num_entries); } static void test_cbmem_initialize_empty_id_size(void **state) { const struct cbmem_entry *entry1, *entry2; expect_function_call(cbmem_run_init_hooks); cbmem_initialize_empty_id_size(CBMEM_ENTRY_ID, CBMEM_ROOT_SIZE); entry1 = cbmem_entry_find(SMALL_REGION_ID); entry2 = cbmem_entry_find(CBMEM_ENTRY_ID); assert_non_null(entry1); assert_non_null(entry2); assert_ptr_not_equal(entry1, entry2); /* Check that cbmem has root, large, small entries and entry with id passed to init function. */ assert_int_equal(3, ((struct imd_root *)imd.lg.r)->num_entries); assert_int_equal(1, ((struct imd_root *)imd.sm.r)->num_entries); } static void test_cbmem_initialize(void **state) { int res; /* Expect call to fail as there is no previous cbmem to recover */ res = cbmem_initialize(); assert_int_equal(1, res); /* Create cbmem with few entries and check if initialization will recover */ prepare_simple_cbmem(); reset_imd(); expect_function_call(cbmem_run_init_hooks); res = cbmem_initialize(); assert_int_equal(0, res); } void test_cbmem_initialize_id_size_ramstage(void **state) { int res; const struct cbmem_entry *entry1, *entry2; /* Expect call to fail as there is no previous cbmem to recover */ res = cbmem_initialize_id_size(0, 0); assert_int_equal(1, res); reset_and_clear_cbmem(); res = cbmem_initialize_id_size(CBMEM_ENTRY_ID, CBMEM_ROOT_SIZE); assert_int_equal(1, res); /* Initialize empty cbmem with small region and check if next initialization correctly recovers and creates its root entry with small region */ expect_function_call(cbmem_run_init_hooks); cbmem_initialize_empty_id_size(0, 0); expect_function_call(cbmem_run_init_hooks); res = cbmem_initialize_id_size(CBMEM_ENTRY_ID, CBMEM_ROOT_SIZE); assert_int_equal(0, res); entry1 = cbmem_entry_find(SMALL_REGION_ID); entry2 = cbmem_entry_find(CBMEM_ENTRY_ID); assert_non_null(entry1); assert_non_null(entry2); assert_ptr_not_equal(entry1, entry2); /* Check that cbmem has root, large, small entries and entry with id passed to init function. */ assert_int_equal(3, ((struct imd_root *)imd.lg.r)->num_entries); assert_int_equal(1, ((struct imd_root *)imd.sm.r)->num_entries); } void test_cbmem_initialize_id_size_romstage(void **state) { int res; const struct cbmem_entry *entry1, *entry2; /* Expect call to fail as there is no previous cbmem to recover */ res = cbmem_initialize_id_size(0, 0); assert_int_equal(1, res); /* Initialize empty cbmem with small region and check if next initialization correctly recovers and creates its root entry with small region */ expect_function_call(cbmem_run_init_hooks); cbmem_initialize_empty_id_size(0, 0); expect_function_call(cbmem_run_init_hooks); res = cbmem_initialize_id_size(CBMEM_ENTRY_ID, CBMEM_ROOT_SIZE); assert_int_equal(0, res); entry1 = cbmem_entry_find(SMALL_REGION_ID); assert_non_null(entry1); /* Romstage locks imd cbmem initialization after recovery, so entry with CBMEM_ENTRY_ID id is not present if it was not recovered. */ entry2 = cbmem_entry_find(CBMEM_ENTRY_ID); assert_null(entry2); /* Initialize cbmem with few large and small entries */ prepare_simple_cbmem(); assert_non_null(cbmem_entry_find(CBMEM_ENTRY_1_ID)); assert_non_null(cbmem_entry_find(CBMEM_ENTRY_2_ID)); assert_non_null(cbmem_entry_find(CBMEM_ENTRY_SM_1_ID)); assert_non_null(cbmem_entry_find(CBMEM_ENTRY_SM_2_ID)); reset_imd(); expect_function_call(cbmem_run_init_hooks); res = cbmem_initialize_id_size(CBMEM_ENTRY_ID, CBMEM_ROOT_SIZE); assert_int_equal(0, res); /* Initialization function should be able to recover entries left in cbmem while having imd structure clean */ entry1 = cbmem_entry_find(SMALL_REGION_ID); assert_non_null(entry1); assert_non_null(cbmem_entry_find(CBMEM_ENTRY_1_ID)); assert_non_null(cbmem_entry_find(CBMEM_ENTRY_2_ID)); assert_non_null(cbmem_entry_find(CBMEM_ENTRY_SM_1_ID)); assert_non_null(cbmem_entry_find(CBMEM_ENTRY_SM_2_ID)); } static void test_cbmem_recovery(void **state) { int is_wakeup = 1; /* Reset imd, initialize cbmem and add entries for recovery */ prepare_simple_cbmem(); expect_function_call(cbmem_run_init_hooks); assert_int_equal(0, cbmem_recovery(is_wakeup)); /* Check that entries have been correctly recovered */ assert_non_null(cbmem_entry_find(CBMEM_ENTRY_1_ID)); assert_non_null(cbmem_entry_find(CBMEM_ENTRY_2_ID)); assert_non_null(cbmem_entry_find(CBMEM_ENTRY_SM_1_ID)); assert_non_null(cbmem_entry_find(CBMEM_ENTRY_SM_2_ID)); is_wakeup = 0; expect_function_call(cbmem_run_init_hooks); assert_int_equal(0, cbmem_recovery(is_wakeup)); /* Check that after recovery with is_wakeup equal to 0 the cbmem is empty and in initial state. */ assert_null(cbmem_entry_find(CBMEM_ENTRY_1_ID)); assert_null(cbmem_entry_find(CBMEM_ENTRY_2_ID)); assert_null(cbmem_entry_find(CBMEM_ENTRY_SM_1_ID)); assert_null(cbmem_entry_find(CBMEM_ENTRY_SM_2_ID)); /* Check that cbmem has root, large and small entry. */ assert_int_equal(2, ((struct imd_root *)imd.lg.r)->num_entries); assert_int_equal(1, ((struct imd_root *)imd.sm.r)->num_entries); } static void test_cbmem_entry_add(void **state) { /* IDs used for testing. Don't have to be sequential. Must not be equal to SMALL_REGION_ID. */ const int id1 = 0x10; const int id2 = 0x11; const int id3 = 0x12; const struct cbmem_entry *entry1, *entry2; const struct cbmem_entry *entry_ret2, *entry_ret3; /* cbmem_run_init_hooks() will be called by init functions but this test does not aim to check it */ ignore_function_calls(cbmem_run_init_hooks); cbmem_initialize_empty_id_size(id1, CBMEM_ROOT_SIZE); /* Expect NULL while looking for nonexistent entries */ assert_null(cbmem_entry_find(id2)); assert_null(cbmem_entry_find(id3)); entry_ret2 = cbmem_entry_add(id2, CBMEM_ROOT_SIZE); /* Expect error when trying to add entry with zero size */ assert_null(cbmem_entry_add(id3, 0)); /* Check if entries have been added correctly and are not the same */ entry1 = cbmem_entry_find(id1); entry2 = cbmem_entry_find(id2); assert_non_null(entry1); assert_non_null(entry2); assert_ptr_not_equal(entry1, entry2); assert_ptr_equal(entry_ret2, entry2); /* Add entry again and make sure that it has been found instead of creating again. */ entry_ret3 = cbmem_entry_add(id2, CBMEM_ROOT_SIZE / 2); assert_ptr_equal(entry_ret2, entry_ret3); } static void test_cbmem_add(void **state) { const int id0 = 0x55; const int id1 = 0x66; const int id2 = 0x77; const int id3 = 0x88; const int entry1_size = 0x2000; const int entry2_size = 0x4d1; const int entry3_size = 0x30; void *entry1, *entry2, *entry3, *entry4; ignore_function_calls(cbmem_run_init_hooks); cbmem_initialize_empty_id_size(id1, entry1_size); entry2 = cbmem_add(id2, entry2_size); entry3 = cbmem_add(id3, entry3_size); entry1 = cbmem_find(id1); /* All pointers should be non-null and distinct. */ assert_non_null(entry1); assert_non_null(entry2); assert_non_null(entry3); assert_ptr_not_equal(entry1, entry2); assert_ptr_not_equal(entry1, entry3); assert_ptr_not_equal(entry2, entry3); /* Adding the same ID should yield the same entry pointer. */ entry4 = cbmem_add(id2, entry2_size); assert_ptr_equal(entry2, entry4); /* Expect error while trying to add range with zero size */ assert_null(cbmem_add(id0, 0)); } static void test_cbmem_entry_find(void **state) { const int id1 = 0xA0; const int id2 = 0xDD; const int id3 = 0xBD; const size_t entry1_size = CBMEM_ROOT_SIZE; const size_t entry2_size = CBMEM_ROOT_SIZE / 2; const size_t entry3_size = 6321; const struct cbmem_entry *cbm_e1, *cbm_e2, *cbm_e3; const struct cbmem_entry *entry1, *entry2, *entry3; ignore_function_calls(cbmem_run_init_hooks); cbmem_initialize_empty(); cbm_e1 = cbmem_entry_add(id1, entry1_size); cbm_e2 = cbmem_entry_add(id2, entry2_size); cbm_e3 = cbmem_entry_add(id3, entry3_size); /* Check pointers correctness and size for each entry */ entry1 = cbmem_entry_find(id1); assert_ptr_equal(cbm_e1, entry1); assert_int_equal(0, (uintptr_t)cbmem_entry_start(cbm_e1) % CBMEM_SM_ALIGN); assert_int_equal(entry1_size, cbmem_entry_size(entry1)); entry2 = cbmem_entry_find(id2); assert_ptr_equal(cbm_e2, entry2); assert_int_equal(0, (uintptr_t)cbmem_entry_start(cbm_e2) % CBMEM_SM_ALIGN); assert_int_equal(entry2_size, cbmem_entry_size(entry2)); entry3 = cbmem_entry_find(id3); assert_ptr_equal(cbm_e3, entry3); assert_int_equal(0, (uintptr_t)cbmem_entry_start(cbm_e3) % CBMEM_SM_ALIGN); assert_int_equal(entry3_size, cbmem_entry_size(entry3)); } static void test_cbmem_find(void **state) { const int id1 = 0x30; const int id2 = 0x22; const int id3 = 0x101; void *cbm_e1, *cbm_e2, *entry1, *entry2; ignore_function_calls(cbmem_run_init_hooks); cbmem_initialize_empty(); cbm_e1 = cbmem_add(id1, CBMEM_ROOT_SIZE); cbm_e2 = cbmem_add(id2, CBMEM_ROOT_SIZE); entry1 = cbmem_find(id1); assert_non_null(entry1); assert_ptr_equal(cbm_e1, entry1); entry2 = cbmem_find(id2); assert_non_null(entry2); assert_ptr_equal(cbm_e2, entry2); /* Expect error when looking for non-existent id */ assert_null(cbmem_find(id3)); } static void test_cbmem_entry_remove(void **state) { const int id1 = 0x2D; const int id2 = 0x3D; const int id3 = 0x4D; const struct cbmem_entry *cbm_e1, *cbm_e2; ignore_function_calls(cbmem_run_init_hooks); cbmem_initialize_empty(); cbm_e1 = cbmem_entry_add(id1, CBMEM_ROOT_SIZE); cbm_e2 = cbmem_entry_add(id2, CBMEM_ROOT_SIZE); /* Entries can be removed only in reverse order they have been added. */ assert_int_equal(-1, cbmem_entry_remove(cbm_e1)); assert_int_equal(0, cbmem_entry_remove(cbm_e2)); assert_int_equal(0, cbmem_entry_remove(cbm_e1)); /* Expect error when removing non-existent entry */ assert_int_equal(-1, cbmem_entry_remove(cbmem_entry_find(id3))); } static void test_cbmem_entry_size(void **state) { const int id1 = 0x4422; const int id2 = 0x2137; const int id3 = 0xb111; const size_t size1 = CBMEM_ROOT_SIZE * 4; const size_t size2 = 0x43; const size_t size3 = CBMEM_ROOT_SIZE * 8 + 7; ignore_function_calls(cbmem_run_init_hooks); cbmem_initialize_empty_id_size(id1, size1); assert_non_null(cbmem_entry_add(id2, size2)); assert_non_null(cbmem_entry_add(id3, size3)); /* Entry size needs not to be aligned. It has to be the same as provided while adding it. */ assert_int_equal(size1, cbmem_entry_size(cbmem_entry_find(id1))); assert_int_equal(size2, cbmem_entry_size(cbmem_entry_find(id2))); assert_int_equal(size3, cbmem_entry_size(cbmem_entry_find(id3))); } static void test_cbmem_entry_start(void **state) { const int id1 = 0x62; const int id2 = 0x26; ignore_function_calls(cbmem_run_init_hooks); cbmem_initialize_empty_id_size(CBMEM_ENTRY_ID, CBMEM_ROOT_SIZE); cbmem_entry_find(CBMEM_ENTRY_ID); cbmem_entry_add(id1, 0x40); cbmem_entry_add(id2, CBMEM_ROOT_SIZE * 2); /* Check if start address of found entry is the same as the one returned by cbmem_find() function */ assert_ptr_equal(cbmem_find(CBMEM_ENTRY_ID), cbmem_entry_start(cbmem_entry_find(CBMEM_ENTRY_ID))); assert_ptr_equal(cbmem_find(id1), cbmem_entry_start(cbmem_entry_find(id1))); assert_ptr_equal(cbmem_find(id2), cbmem_entry_start(cbmem_entry_find(id2))); } /* Reimplementation for testing purposes */ void bootmem_add_range(uint64_t start, uint64_t size, const enum bootmem_type tag) { check_expected(start); check_expected(size); check_expected(tag); } static void test_cbmem_add_bootmem(void **state) { void *base_ptr = NULL; size_t size = 0; const int id1 = 0xCA; const int id2 = 0xEA; const int id3 = 0xDA; const size_t size1 = 1024; const size_t size2 = 128; const size_t size3 = 8192; ignore_function_calls(cbmem_run_init_hooks); cbmem_initialize_empty_id_size(CBMEM_ENTRY_ID, CBMEM_ROOT_SIZE); cbmem_entry_add(id1, size1); cbmem_entry_add(id2, size2); cbmem_entry_add(id3, size3); cbmem_get_region(&base_ptr, &size); assert_int_equal(ALIGN_DOWN(_cbmem_top_ptr, LIMIT_ALIGN), base_ptr + size); expect_value(bootmem_add_range, start, base_ptr); expect_value(bootmem_add_range, size, size); expect_value(bootmem_add_range, tag, BM_MEM_TABLE); cbmem_add_bootmem(); /* Check that adding bootmem does not change base or size of cbmem */ cbmem_get_region(&base_ptr, &size); assert_int_equal(ALIGN_DOWN(_cbmem_top_ptr, LIMIT_ALIGN), base_ptr + size); } static void test_cbmem_get_region(void **state) { int i; void *base_ptr = NULL; size_t size = 0; size_t size_counter = 0; const size_t entry_size = 0x2000; const size_t alloc_num = 32; const size_t small_entry_size = 64; const size_t small_alloc_num = 3; ignore_function_calls(cbmem_run_init_hooks); cbmem_initialize_empty_id_size(CBMEM_ENTRY_ID, CBMEM_ROOT_SIZE); /* Check size and base pointer for empty initialized cbmem */ cbmem_get_region(&base_ptr, &size); assert_non_null(base_ptr); assert_int_not_equal(0, size); assert_int_equal(CBMEM_ROOT_SIZE + cbmem_overhead_size(), size); assert_int_equal(ALIGN_DOWN(_cbmem_top_ptr, LIMIT_ALIGN), base_ptr + size); /* Check for multiple big allocations */ for (i = 1; i <= alloc_num; ++i) { const struct cbmem_entry *e = cbmem_entry_add(i, entry_size); assert_non_null(e); size_counter += cbmem_entry_size(e); /* Check if size is correct after each big allocation */ cbmem_get_region(&base_ptr, &size); assert_int_equal(size_counter + cbmem_overhead_size() + CBMEM_ROOT_SIZE, size); } /* Check for few small allocations. */ for (; i <= alloc_num + small_alloc_num; ++i) { const struct cbmem_entry *e = cbmem_entry_add(i, small_entry_size); assert_non_null(e); /* Check if size is correct after each small allocation. It should not change as small entries have their region allocated and entry size is selected to fit in it couple of times */ cbmem_get_region(&base_ptr, &size); assert_int_equal(size_counter + cbmem_overhead_size() + CBMEM_ROOT_SIZE, size); } } static void test_general_data_structure(void **state) { /* Initialize cbmem with few big and small entries, then check if binary data structure is the same as stored in array containing hardcoded dumped cbmem */ prepare_simple_cbmem(); assert_memory_equal(get_cbmem_ptr(), test_cbmem_data, CBMEM_SIZE); } static int setup_teardown_test_imd_cbmem(void **state) { reset_and_clear_cbmem(); return 0; } static int setup_group_imd_cbmem(void **state) { /* Allocate more data to have space for alignment */ void *top_ptr = malloc(CBMEM_SIZE + DYN_CBMEM_ALIGN_SIZE); if (!top_ptr) return -1; *state = top_ptr; _cbmem_top_ptr = ALIGN_UP((uintptr_t)top_ptr + CBMEM_SIZE, DYN_CBMEM_ALIGN_SIZE); return 0; } static int teardown_group_imd_cbmem(void **state) { reset_imd(); free(*state); return 0; } int main(void) { const struct CMUnitTest tests[] = { cmocka_unit_test_setup_teardown(test_cbmem_top, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_initialize_empty, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_initialize_empty_id_size, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_initialize, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), #if ENV_ROMSTAGE_OR_BEFORE cmocka_unit_test_setup_teardown(test_cbmem_initialize_id_size_romstage, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), #else cmocka_unit_test_setup_teardown(test_cbmem_initialize_id_size_ramstage, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), #endif cmocka_unit_test_setup_teardown(test_cbmem_recovery, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_entry_add, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_add, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_entry_find, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_find, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_entry_remove, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_entry_size, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_entry_start, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_add_bootmem, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_cbmem_get_region, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), cmocka_unit_test_setup_teardown(test_general_data_structure, setup_teardown_test_imd_cbmem, setup_teardown_test_imd_cbmem), }; return cb_run_group_tests(tests, setup_group_imd_cbmem, teardown_group_imd_cbmem); }