Patrick Georgi submitted this change.

View Change

Approvals: build bot (Jenkins): Verified Julius Werner: Looks good to me, approved Paul Fagerburg: Looks good to me, but someone else must approve
tests: Add lib/bootmem-test test case

Signed-off-by: Jan Dabros <jsd@semihalf.com>
Signed-off-by: Jakub Czapiga <jacz@semihalf.com>
Change-Id: Ic1e539061ee5051d4158712a8a981a475ea7458a
Reviewed-on: https://review.coreboot.org/c/coreboot/+/43510
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
Reviewed-by: Paul Fagerburg <pfagerburg@chromium.org>
Reviewed-by: Julius Werner <jwerner@chromium.org>
---
M tests/lib/Makefile.inc
A tests/lib/bootmem-test.c
2 files changed, 418 insertions(+), 0 deletions(-)

diff --git a/tests/lib/Makefile.inc b/tests/lib/Makefile.inc
index cba4361..3a43a64 100644
--- a/tests/lib/Makefile.inc
+++ b/tests/lib/Makefile.inc
@@ -24,6 +24,7 @@
tests-y += compute_ip_checksum-test
tests-y += memrange-test
tests-y += uuid-test
+tests-y += bootmem-test

string-test-srcs += tests/lib/string-test.c
string-test-srcs += src/lib/string.c
@@ -116,3 +117,10 @@
uuid-test-srcs += tests/lib/uuid-test.c
uuid-test-srcs += src/lib/hexstrtobin.c
uuid-test-srcs += src/lib/uuid.c
+
+bootmem-test-srcs += tests/lib/bootmem-test.c
+bootmem-test-srcs += tests/stubs/console.c
+bootmem-test-srcs += src/device/device_util.c
+bootmem-test-srcs += src/lib/bootmem.c
+bootmem-test-srcs += src/lib/memrange.c
+
diff --git a/tests/lib/bootmem-test.c b/tests/lib/bootmem-test.c
new file mode 100644
index 0000000..85a0136
--- /dev/null
+++ b/tests/lib/bootmem-test.c
@@ -0,0 +1,410 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+
+#include <bootmem.h>
+#include <commonlib/coreboot_tables.h>
+#include <device/device.h>
+#include <device/resource.h>
+#include <memrange.h>
+#include <stdlib.h>
+#include <string.h>
+#include <symbols.h>
+#include <tests/test.h>
+
+/* Stubs defined to satisfy linker dependencies */
+void cbmem_add_bootmem(void)
+{
+}
+
+void bootmem_arch_add_ranges(void)
+{
+}
+
+struct bootmem_ranges_t {
+ uint64_t start;
+ uint64_t size;
+ uint32_t type;
+};
+
+/* Define symbols for regions required by bootmem.
+ Define constants for regions that do not need to be defined in the executable.
+ There is no need for region memory, just start, end and size symbols are required.
+ Only used values are defined. */
+#define ZERO_REGION_START ((uintptr_t)0x0)
+#define ZERO_REGION_SIZE ((uintptr_t)0x10000)
+
+TEST_REGION_UNALLOCATED(program, 0x10000000, 0x40000);
+#define PROGRAM_START ((uintptr_t)_program)
+#define PROGRAM_SIZE REGION_SIZE(program)
+
+#define CACHEABLE_START ((uintptr_t)0x10000000ULL)
+#define CACHEABLE_SIZE ((uintptr_t)0x100000000ULL)
+#define CACHEABLE_END ((uintptr_t)(CACHEABLE_START + CACHEABLE_SIZE))
+
+/* Stack region end address is hardcoded because `<const> - <symbol>` does not work in GCC */
+TEST_REGION_UNALLOCATED(stack, 0x10040000, 0x1000);
+#define STACK_START ((uintptr_t)_stack)
+#define STACK_SIZE REGION_SIZE(stack)
+#define STACK_END ((uintptr_t)(0x10040000 + 0x1000))
+
+#define RESERVED_START ((uintptr_t)0x100000000ULL)
+#define RESERVED_SIZE ((uintptr_t)0x100000)
+#define RESERVED_END ((uintptr_t)(RESERVED_START + RESERVED_SIZE))
+
+TEST_REGION_UNALLOCATED(ramstage, 0x10000000, 0x41000);
+#define RAMSTAGE_START ((uintptr_t)_ramstage)
+#define RAMSTAGE_SIZE REGION_SIZE(ramstage)
+
+#define CACHEABLE_START_TO_RESERVED_START_SIZE (RESERVED_START - CACHEABLE_START)
+#define RESERVED_END_TO_CACHEABLE_END_SIZE (CACHEABLE_END - RESERVED_END)
+#define STACK_END_TO_RESERVED_START_SIZE (RESERVED_START - STACK_END)
+
+
+/* Bootmem layout for tests
+ *
+ * Regions marked with asterisks (***) are not visible for OS
+ *
+ * +------------------ZERO-----------------+ <-0x0
+ * | |
+ * +---------------------------------------+ <-0x10000
+ *
+ * +-------+----CACHEABLE_MEMORY---------+-+ <-0x10000000
+ * | | ***PROGRAM*** | |
+ * | +-----------------------------+ | <-0x10040000
+ * | | ***STACK*** | |
+ * | +-----------------------------+ | <-0x10041000
+ * | |
+ * | |
+ * | |
+ * | +-------RESERVED_MEMORY-------+ | <-0x100000000
+ * | | | |
+ * | | | |
+ * | | | |
+ * | +-----------------------------+ | <-0x100100000
+ * | |
+ * | |
+ * +---------------------------------------+ <-0x110000000
+ *
+ * Ramstage covers PROGRAM and STACK regions.
+ */
+struct bootmem_ranges_t os_ranges_mock[] = {
+ [0] = { .start = ZERO_REGION_START, .size = ZERO_REGION_SIZE,
+ .type = BM_MEM_RAM},
+ [1] = { .start = CACHEABLE_START, .size = CACHEABLE_START_TO_RESERVED_START_SIZE,
+ .type = BM_MEM_RAM },
+ [2] = { .start = RESERVED_START, .size = RESERVED_SIZE,
+ .type = BM_MEM_RESERVED },
+ [3] = { .start = RESERVED_END, .size = RESERVED_END_TO_CACHEABLE_END_SIZE,
+ .type = BM_MEM_RAM },
+};
+
+struct bootmem_ranges_t ranges_mock[] = {
+ [0] = { .start = ZERO_REGION_START, .size = ZERO_REGION_SIZE,
+ .type = BM_MEM_RAM },
+ [1] = { .start = RAMSTAGE_START, .size = RAMSTAGE_SIZE,
+ .type = BM_MEM_RAMSTAGE },
+ [2] = { .start = STACK_END, .size = STACK_END_TO_RESERVED_START_SIZE,
+ .type = BM_MEM_RAM },
+ [3] = { .start = RESERVED_START, .size = RESERVED_SIZE,
+ .type = BM_MEM_RESERVED },
+ [4] = { .start = RESERVED_END, .size = RESERVED_END_TO_CACHEABLE_END_SIZE,
+ .type = BM_MEM_RAM },
+};
+
+struct bootmem_ranges_t *os_ranges = os_ranges_mock;
+struct bootmem_ranges_t *ranges = ranges_mock;
+
+/* Note that second region overlaps first */
+struct resource res_mock[] = {
+ { .base = ZERO_REGION_START, .size = ZERO_REGION_SIZE, .next = &res_mock[1],
+ .flags = IORESOURCE_CACHEABLE | IORESOURCE_MEM },
+ { .base = CACHEABLE_START, .size = CACHEABLE_SIZE, .next = &res_mock[2],
+ .flags = IORESOURCE_CACHEABLE | IORESOURCE_MEM },
+ { .base = RESERVED_START, .size = RESERVED_SIZE, .next = NULL,
+ .flags = IORESOURCE_RESERVE | IORESOURCE_MEM }
+};
+
+/* Device simulating RAM */
+struct device mem_device_mock = {
+ .enabled = 1,
+ .resource_list = res_mock,
+ .next = NULL
+};
+
+struct device *all_devices = &mem_device_mock;
+
+/* Simplified version for the purpose of tests */
+static uint32_t bootmem_to_lb_tag(const enum bootmem_type tag)
+{
+ switch (tag) {
+ case BM_MEM_RAM:
+ return LB_MEM_RAM;
+ case BM_MEM_RESERVED:
+ return LB_MEM_RESERVED;
+ default:
+ return LB_MEM_RESERVED;
+ }
+}
+
+static void test_bootmem_write_mem_table(void **state)
+{
+ /* Space for 10 lb_mem entries to be safe */
+ const size_t lb_mem_max_size = sizeof(struct lb_memory)
+ + 10 * sizeof(struct lb_memory_range);
+ const size_t expected_allocation_size =
+ (sizeof(struct lb_memory)
+ + ARRAY_SIZE(os_ranges_mock) * sizeof(struct lb_memory_range));
+ const size_t required_unused_space_size = lb_mem_max_size - expected_allocation_size;
+ int i;
+ struct lb_memory *lb_mem;
+ /* Allocate buffer and fill it. Use it to ensure correct size of space used
+ by bootmem_write_memory_table() */
+ u8 sentinel_value_buffer[required_unused_space_size];
+ memset(sentinel_value_buffer, 0x77, required_unused_space_size);
+
+ lb_mem = malloc(lb_mem_max_size);
+ /* Fill rest of buffer with sentinel value */
+ memset(((u8 *)lb_mem) + expected_allocation_size, 0x77, required_unused_space_size);
+
+ bootmem_write_memory_table(lb_mem);
+
+ /* There should be only `os_ranges_mock` entries visible in coreboot table */
+ assert_int_equal(lb_mem->size,
+ ARRAY_SIZE(os_ranges_mock) * sizeof(struct lb_memory_range));
+ assert_memory_equal(sentinel_value_buffer,
+ ((u8 *)lb_mem) + expected_allocation_size,
+ required_unused_space_size);
+
+ for (i = 0; i < lb_mem->size / sizeof(struct lb_memory_range); i++) {
+ assert_int_equal(unpack_lb64(lb_mem->map[i].start), os_ranges[i].start);
+ assert_int_equal(unpack_lb64(lb_mem->map[i].size), os_ranges[i].size);
+ assert_int_equal(lb_mem->map[i].type, bootmem_to_lb_tag(os_ranges[i].type));
+ }
+
+ free(lb_mem);
+}
+
+int os_bootmem_walk_cnt;
+int bootmem_walk_cnt;
+
+static bool verify_os_bootmem_walk(const struct range_entry *r, void *arg)
+{
+ assert_int_equal(range_entry_base(r), os_ranges[os_bootmem_walk_cnt].start);
+ assert_int_equal(range_entry_size(r), os_ranges[os_bootmem_walk_cnt].size);
+ assert_int_equal(range_entry_tag(r), os_ranges[os_bootmem_walk_cnt].type);
+
+ os_bootmem_walk_cnt++;
+
+ return true;
+}
+
+static bool verify_bootmem_walk(const struct range_entry *r, void *arg)
+{
+ assert_int_equal(range_entry_base(r), ranges[bootmem_walk_cnt].start);
+ assert_int_equal(range_entry_size(r), ranges[bootmem_walk_cnt].size);
+ assert_int_equal(range_entry_tag(r), ranges[bootmem_walk_cnt].type);
+
+ bootmem_walk_cnt++;
+
+ return true;
+}
+
+static bool count_entries_os_bootmem_walk(const struct range_entry *r, void *arg)
+{
+ os_bootmem_walk_cnt++;
+
+ return true;
+}
+
+static bool count_entries_bootmem_walk(const struct range_entry *r, void *arg)
+{
+ bootmem_walk_cnt++;
+
+ return true;
+}
+
+/* This function initializes bootmem using bootmem_write_memory_table().
+ bootmem_init() is not accessible directly because it is static. */
+static void init_memory_table_library(void)
+{
+ struct lb_memory *lb_mem;
+
+ /* Allocate space for 10 lb_mem entries to be safe */
+ lb_mem = malloc(sizeof(*lb_mem) + 10 * sizeof(struct lb_memory_range));
+
+ /* We need to call this only to initialize library */
+ bootmem_write_memory_table(lb_mem);
+ free(lb_mem);
+}
+
+static void test_bootmem_add_range(void **state)
+{
+ init_memory_table_library();
+
+ os_bootmem_walk_cnt = 0;
+ bootmem_walk_os_mem(count_entries_os_bootmem_walk, NULL);
+ assert_int_equal(os_bootmem_walk_cnt, 4);
+
+ bootmem_walk_cnt = 0;
+ bootmem_walk(count_entries_bootmem_walk, NULL);
+ assert_int_equal(bootmem_walk_cnt, 5);
+
+ expect_assert_failure(
+ bootmem_add_range(ALIGN_UP(PROGRAM_START, 4096),
+ ALIGN_DOWN(PROGRAM_SIZE / 2, 4096),
+ BM_MEM_ACPI)
+ );
+
+ os_bootmem_walk_cnt = 0;
+ bootmem_walk_os_mem(count_entries_os_bootmem_walk, NULL);
+ assert_int_equal(os_bootmem_walk_cnt, 4);
+
+ bootmem_walk_cnt = 0;
+ bootmem_walk(count_entries_bootmem_walk, NULL);
+ assert_int_equal(bootmem_walk_cnt, 6);
+
+ /* Do not expect assert failure as BM_MEM_RAMSTAGE should not be added to os_bootmem */
+ bootmem_add_range(ALIGN_UP(STACK_END + 4096, 4096),
+ ALIGN_DOWN(STACK_END_TO_RESERVED_START_SIZE / 2, 4096),
+ BM_MEM_RAMSTAGE);
+
+ os_bootmem_walk_cnt = 0;
+ bootmem_walk_os_mem(count_entries_os_bootmem_walk, NULL);
+ assert_int_equal(os_bootmem_walk_cnt, 4);
+
+ /* Two entries are added because added range is in middle of another */
+ bootmem_walk_cnt = 0;
+ bootmem_walk(count_entries_bootmem_walk, NULL);
+ assert_int_equal(bootmem_walk_cnt, 8);
+}
+
+static void test_bootmem_walk(void **state)
+{
+ init_memory_table_library();
+
+ os_bootmem_walk_cnt = 0;
+ bootmem_walk_os_mem(verify_os_bootmem_walk, NULL);
+ assert_int_equal(os_bootmem_walk_cnt, 4);
+
+ bootmem_walk_cnt = 0;
+ bootmem_walk(verify_bootmem_walk, NULL);
+ assert_int_equal(bootmem_walk_cnt, 5);
+}
+
+static void test_bootmem_region_targets_type(void **state)
+{
+ int ret;
+ u64 subregion_start;
+ u64 subregion_size;
+
+ init_memory_table_library();
+
+ /* Single whole region */
+ ret = bootmem_region_targets_type(RAMSTAGE_START, RAMSTAGE_SIZE, BM_MEM_RAMSTAGE);
+ assert_int_equal(ret, 1);
+
+ /* Expect fail because of incorrect bootmem_type */
+ ret = bootmem_region_targets_type(RAMSTAGE_START, RAMSTAGE_SIZE, BM_MEM_RESERVED);
+ assert_int_equal(ret, 0);
+
+ /* Range covering one more byte than one region*/
+ ret = bootmem_region_targets_type(RAMSTAGE_START, RAMSTAGE_SIZE + 1, BM_MEM_RAMSTAGE);
+ assert_int_equal(ret, 0);
+
+ /* Expect success for subregion of ramstage stretching from point in program range
+ to point in stack range. */
+ subregion_start = PROGRAM_START + PROGRAM_SIZE / 4;
+ subregion_size = STACK_END - STACK_SIZE / 4 - subregion_start;
+ ret = bootmem_region_targets_type(subregion_start, subregion_size, BM_MEM_RAMSTAGE);
+ assert_int_equal(ret, 1);
+
+ /* Expect fail for range covering more than one tag as there is no BM_MEM_CACHEABLE */
+ subregion_start = STACK_START + STACK_SIZE / 2;
+ subregion_size = RESERVED_START + RESERVED_SIZE / 4 * 3 - subregion_start;
+ ret = bootmem_region_targets_type(subregion_start, subregion_size, BM_MEM_RAM);
+ assert_int_equal(ret, 0);
+
+ /* Middle of range should not fail */
+ ret = bootmem_region_targets_type(RESERVED_START + RESERVED_SIZE / 4,
+ RESERVED_SIZE / 2, BM_MEM_RESERVED);
+ assert_int_equal(ret, 1);
+
+ /* Subsection of range bordering end edge */
+ ret = bootmem_region_targets_type(RESERVED_END + RESERVED_END_TO_CACHEABLE_END_SIZE / 2,
+ RESERVED_END_TO_CACHEABLE_END_SIZE / 2, BM_MEM_RAM);
+ assert_int_equal(ret, 1);
+
+ /* Region touching zero */
+ ret = bootmem_region_targets_type(ZERO_REGION_START, ZERO_REGION_SIZE, BM_MEM_RAM);
+ assert_int_equal(ret, 1);
+
+ /* Expect failure when passing zero as size. */
+ ret = bootmem_region_targets_type(ZERO_REGION_START, 0, BM_MEM_RAM);
+ assert_int_equal(ret, 0);
+ ret = bootmem_region_targets_type(RESERVED_START, 0, BM_MEM_RESERVED);
+ assert_int_equal(ret, 0);
+}
+
+/* Action function used to check alignment of size and base of allocated ranges */
+static bool verify_bootmem_allocate_buffer(const struct range_entry *r, void *arg)
+{
+ if (range_entry_tag(r) == BM_MEM_PAYLOAD) {
+ assert_true(IS_ALIGNED(range_entry_base(r), 4096));
+ assert_true(IS_ALIGNED(range_entry_size(r), 4096));
+ }
+
+ return true;
+}
+
+
+static void test_bootmem_allocate_buffer(void **state)
+{
+ void *buf;
+ void *prev;
+
+ init_memory_table_library();
+
+ /* All allocated buffers should be below 32bit boundary */
+ buf = bootmem_allocate_buffer(1ULL << 32);
+ assert_null(buf);
+
+ /* Try too big size for our BM_MEM_RAM range below 32bit boundary */
+ buf = bootmem_allocate_buffer(RESERVED_START - PROGRAM_START);
+ assert_null(buf);
+
+ /* Two working cases */
+ buf = bootmem_allocate_buffer(0xE0000000);
+ assert_non_null(buf);
+ assert_int_equal(1, bootmem_region_targets_type((uintptr_t)buf,
+ 0xE0000000, BM_MEM_PAYLOAD));
+ assert_in_range((uintptr_t)buf, CACHEABLE_START + RAMSTAGE_SIZE, RESERVED_START);
+ /* Check if allocated (payload) ranges have their base and size aligned */
+ bootmem_walk(verify_bootmem_allocate_buffer, NULL);
+
+ prev = buf;
+ buf = bootmem_allocate_buffer(0xF000000);
+ assert_non_null(buf);
+ assert_int_equal(1, bootmem_region_targets_type((uintptr_t)buf,
+ 0xF000000, BM_MEM_PAYLOAD));
+ assert_in_range((uintptr_t)buf, CACHEABLE_START + RAMSTAGE_SIZE, RESERVED_START);
+ /* Check if newly allocated buffer does not overlap with previously allocated range */
+ assert_not_in_range((uintptr_t)buf, (uintptr_t)prev, (uintptr_t)prev + 0xE0000000);
+ /* Check if allocated (payload) ranges have their base and size aligned */
+ bootmem_walk(verify_bootmem_allocate_buffer, NULL);
+
+ /* Run out of memory for new allocations */
+ buf = bootmem_allocate_buffer(0x1000000);
+ assert_null(buf);
+}
+
+int main(void)
+{
+ const struct CMUnitTest tests[] = {
+ cmocka_unit_test(test_bootmem_write_mem_table),
+ cmocka_unit_test(test_bootmem_add_range),
+ cmocka_unit_test(test_bootmem_walk),
+ cmocka_unit_test(test_bootmem_allocate_buffer),
+ cmocka_unit_test(test_bootmem_region_targets_type)
+ };
+
+ return cmocka_run_group_tests(tests, NULL, NULL);
+}

To view, visit change 43510. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: coreboot
Gerrit-Branch: master
Gerrit-Change-Id: Ic1e539061ee5051d4158712a8a981a475ea7458a
Gerrit-Change-Number: 43510
Gerrit-PatchSet: 11
Gerrit-Owner: Jan Dabros <jsd@semihalf.com>
Gerrit-Reviewer: Anna Karaƛ <aka@semihalf.com>
Gerrit-Reviewer: Jakub Czapiga <jacz@semihalf.com>
Gerrit-Reviewer: Julius Werner <jwerner@chromium.org>
Gerrit-Reviewer: Martin Roth <martinroth@google.com>
Gerrit-Reviewer: Patrick Georgi <pgeorgi@google.com>
Gerrit-Reviewer: Paul Fagerburg <pfagerburg@chromium.org>
Gerrit-Reviewer: Paul Menzel <paulepanter@users.sourceforge.net>
Gerrit-Reviewer: build bot (Jenkins) <no-reply@coreboot.org>
Gerrit-MessageType: merged