/ tests / lib / bootmem-test.c
bootmem-test.c
  1  /* SPDX-License-Identifier: GPL-2.0-only */
  2  
  3  #include <bootmem.h>
  4  #include <commonlib/coreboot_tables.h>
  5  #include <device/device.h>
  6  #include <memrange.h>
  7  #include <stdlib.h>
  8  #include <string.h>
  9  #include <symbols.h>
 10  #include <tests/test.h>
 11  
 12  /* Stubs defined to satisfy linker dependencies */
 13  void cbmem_add_bootmem(void)
 14  {
 15  }
 16  
 17  void bootmem_arch_add_ranges(void)
 18  {
 19  }
 20  
 21  struct bootmem_ranges_t {
 22  	uint64_t start;
 23  	uint64_t size;
 24  	uint32_t type;
 25  };
 26  
 27  /* Define symbols for regions required by bootmem.
 28     Define constants for regions that do not need to be defined in the executable.
 29     There is no need for region memory, just start, end and size symbols are required.
 30     Only used values are defined. */
 31  #define ZERO_REGION_START ((uintptr_t)0x0)
 32  #define ZERO_REGION_SIZE  ((uintptr_t)0x10000)
 33  
 34  TEST_REGION_UNALLOCATED(program, 0x10000000, 0x40000);
 35  #define PROGRAM_START ((uintptr_t)_program)
 36  #define PROGRAM_SIZE  REGION_SIZE(program)
 37  
 38  #define CACHEABLE_START	((uintptr_t)0x10000000ULL)
 39  #define CACHEABLE_SIZE	((uintptr_t)0x100000000ULL)
 40  #define CACHEABLE_END	((uintptr_t)(CACHEABLE_START + CACHEABLE_SIZE))
 41  
 42  /* Stack region end address is hardcoded because `<const> - <symbol>` does not work in GCC */
 43  TEST_REGION_UNALLOCATED(stack, 0x10040000, 0x1000);
 44  #define STACK_START ((uintptr_t)_stack)
 45  #define STACK_SIZE  REGION_SIZE(stack)
 46  #define STACK_END   ((uintptr_t)(0x10040000 + 0x1000))
 47  
 48  #define RESERVED_START	((uintptr_t)0x100000000ULL)
 49  #define RESERVED_SIZE	((uintptr_t)0x100000)
 50  #define RESERVED_END	((uintptr_t)(RESERVED_START + RESERVED_SIZE))
 51  
 52  TEST_REGION_UNALLOCATED(ramstage, 0x10000000, 0x41000);
 53  #define RAMSTAGE_START  ((uintptr_t)_ramstage)
 54  #define RAMSTAGE_SIZE	REGION_SIZE(ramstage)
 55  
 56  #define CACHEABLE_START_TO_RESERVED_START_SIZE (RESERVED_START - CACHEABLE_START)
 57  #define RESERVED_END_TO_CACHEABLE_END_SIZE (CACHEABLE_END - RESERVED_END)
 58  #define STACK_END_TO_RESERVED_START_SIZE (RESERVED_START - STACK_END)
 59  
 60  
 61  /* Bootmem layout for tests
 62   *
 63   * Regions marked with asterisks (***) are not visible for OS
 64   *
 65   *     +------------------ZERO-----------------+ <-0x0
 66   *     |                                       |
 67   *     +---------------------------------------+ <-0x10000
 68   *
 69   *     +-------+----CACHEABLE_MEMORY---------+-+ <-0x10000000
 70   *     |       |        ***PROGRAM***        | |
 71   *     |       +-----------------------------+ | <-0x10040000
 72   *     |       |         ***STACK***         | |
 73   *     |       +-----------------------------+ | <-0x10041000
 74   *     |                                       |
 75   *     |                                       |
 76   *     |                                       |
 77   *     |       +-------RESERVED_MEMORY-------+ | <-0x100000000
 78   *     |       |                             | |
 79   *     |       |                             | |
 80   *     |       |                             | |
 81   *     |       +-----------------------------+ | <-0x100100000
 82   *     |                                       |
 83   *     |                                       |
 84   *     +---------------------------------------+ <-0x110000000
 85   *
 86   * Ramstage covers PROGRAM and STACK regions.
 87   */
 88  struct bootmem_ranges_t os_ranges_mock[] = {
 89  	[0] = { .start = ZERO_REGION_START, .size = ZERO_REGION_SIZE,
 90  		.type = BM_MEM_RAM},
 91  	[1] = { .start = CACHEABLE_START, .size = CACHEABLE_START_TO_RESERVED_START_SIZE,
 92  		.type = BM_MEM_RAM },
 93  	[2] = { .start = RESERVED_START, .size = RESERVED_SIZE,
 94  		.type = BM_MEM_RESERVED },
 95  	[3] = { .start = RESERVED_END, .size = RESERVED_END_TO_CACHEABLE_END_SIZE,
 96  		.type = BM_MEM_RAM },
 97  };
 98  
 99  struct bootmem_ranges_t ranges_mock[] = {
100  	[0] = { .start = ZERO_REGION_START, .size = ZERO_REGION_SIZE,
101  		.type = BM_MEM_RAM },
102  	[1] = { .start = RAMSTAGE_START, .size = RAMSTAGE_SIZE,
103  		.type = BM_MEM_RAMSTAGE },
104  	[2] = { .start = STACK_END, .size = STACK_END_TO_RESERVED_START_SIZE,
105  		.type = BM_MEM_RAM },
106  	[3] = { .start = RESERVED_START, .size = RESERVED_SIZE,
107  		.type = BM_MEM_RESERVED },
108  	[4] = { .start = RESERVED_END, .size = RESERVED_END_TO_CACHEABLE_END_SIZE,
109  		.type = BM_MEM_RAM },
110  };
111  
112  struct bootmem_ranges_t *os_ranges = os_ranges_mock;
113  struct bootmem_ranges_t *ranges = ranges_mock;
114  
115  /* Note that second region overlaps first */
116  struct resource res_mock[] = {
117  	{ .base = ZERO_REGION_START, .size = ZERO_REGION_SIZE, .next = &res_mock[1],
118  	  .flags = IORESOURCE_CACHEABLE | IORESOURCE_MEM | IORESOURCE_ASSIGNED },
119  	{ .base = CACHEABLE_START, .size = CACHEABLE_SIZE, .next = &res_mock[2],
120  	  .flags = IORESOURCE_CACHEABLE | IORESOURCE_MEM | IORESOURCE_ASSIGNED },
121  	{ .base = RESERVED_START, .size = RESERVED_SIZE, .next = NULL,
122  	  .flags = IORESOURCE_RESERVE | IORESOURCE_MEM | IORESOURCE_ASSIGNED }
123  };
124  
125  /* Device simulating RAM */
126  struct device mem_device_mock = {
127  	.enabled = 1,
128  	.resource_list = res_mock,
129  	.next = NULL
130  };
131  
132  struct device *all_devices = &mem_device_mock;
133  
134  /* Simplified version for the purpose of tests */
135  static uint32_t bootmem_to_lb_tag(const enum bootmem_type tag)
136  {
137  	switch (tag) {
138  	case BM_MEM_RAM:
139  		return LB_MEM_RAM;
140  	case BM_MEM_RESERVED:
141  		return LB_MEM_RESERVED;
142  	default:
143  		return LB_MEM_RESERVED;
144  	}
145  }
146  
147  static void test_bootmem_write_mem_table(void **state)
148  {
149  	/* Space for 10 lb_mem entries to be safe */
150  	const size_t lb_mem_max_size = sizeof(struct lb_memory)
151  					+ 10 * sizeof(struct lb_memory_range);
152  	const size_t expected_allocation_size =
153  			(sizeof(struct lb_memory)
154  				+ ARRAY_SIZE(os_ranges_mock) * sizeof(struct lb_memory_range));
155  	const size_t required_unused_space_size = lb_mem_max_size - expected_allocation_size;
156  	int i;
157  	struct lb_memory *lb_mem;
158  	/* Allocate buffer and fill it. Use it to ensure correct size of space used
159  	   by bootmem_write_memory_table() */
160  	u8 sentinel_value_buffer[required_unused_space_size];
161  	memset(sentinel_value_buffer, 0x77, required_unused_space_size);
162  
163  	lb_mem = malloc(lb_mem_max_size);
164  	lb_mem->tag = LB_TAG_MEMORY;
165  	lb_mem->size = sizeof(*lb_mem);
166  	/* Fill rest of buffer with sentinel value */
167  	memset(((u8 *)lb_mem) + expected_allocation_size, 0x77, required_unused_space_size);
168  
169  	bootmem_write_memory_table(lb_mem);
170  
171  	/* There should be only `os_ranges_mock` entries visible in coreboot table */
172  	assert_int_equal(lb_mem->size, sizeof(*lb_mem) +
173  			ARRAY_SIZE(os_ranges_mock) * sizeof(struct lb_memory_range));
174  	assert_memory_equal(sentinel_value_buffer,
175  			((u8 *)lb_mem) + expected_allocation_size,
176  			required_unused_space_size);
177  
178  	for (i = 0; i < lb_mem->size / sizeof(struct lb_memory_range); i++) {
179  		assert_int_equal(lb_mem->map[i].start, os_ranges[i].start);
180  		assert_int_equal(lb_mem->map[i].size, os_ranges[i].size);
181  		assert_int_equal(lb_mem->map[i].type, bootmem_to_lb_tag(os_ranges[i].type));
182  	}
183  
184  	free(lb_mem);
185  }
186  
187  int os_bootmem_walk_cnt;
188  int bootmem_walk_cnt;
189  
190  static bool verify_os_bootmem_walk(const struct range_entry *r, void *arg)
191  {
192  	assert_int_equal(range_entry_base(r), os_ranges[os_bootmem_walk_cnt].start);
193  	assert_int_equal(range_entry_size(r), os_ranges[os_bootmem_walk_cnt].size);
194  	assert_int_equal(range_entry_tag(r), os_ranges[os_bootmem_walk_cnt].type);
195  
196  	os_bootmem_walk_cnt++;
197  
198  	return true;
199  }
200  
201  static bool verify_bootmem_walk(const struct range_entry *r, void *arg)
202  {
203  	assert_int_equal(range_entry_base(r), ranges[bootmem_walk_cnt].start);
204  	assert_int_equal(range_entry_size(r), ranges[bootmem_walk_cnt].size);
205  	assert_int_equal(range_entry_tag(r), ranges[bootmem_walk_cnt].type);
206  
207  	bootmem_walk_cnt++;
208  
209  	return true;
210  }
211  
212  static bool count_entries_os_bootmem_walk(const struct range_entry *r, void *arg)
213  {
214  	os_bootmem_walk_cnt++;
215  
216  	return true;
217  }
218  
219  static bool count_entries_bootmem_walk(const struct range_entry *r, void *arg)
220  {
221  	bootmem_walk_cnt++;
222  
223  	return true;
224  }
225  
226  /* This function initializes bootmem using bootmem_write_memory_table().
227     bootmem_init() is not accessible directly because it is static. */
228  static void init_memory_table_library(void)
229  {
230  	struct lb_memory *lb_mem;
231  
232  	/* Allocate space for 10 lb_mem entries to be safe */
233  	lb_mem = malloc(sizeof(*lb_mem) + 10 * sizeof(struct lb_memory_range));
234  	lb_mem->tag = LB_TAG_MEMORY;
235  	lb_mem->size = sizeof(*lb_mem);
236  
237  	/* We need to call this only to initialize library */
238  	bootmem_write_memory_table(lb_mem);
239  
240  	free(lb_mem);
241  }
242  
243  static void test_bootmem_add_range(void **state)
244  {
245  	init_memory_table_library();
246  
247  	os_bootmem_walk_cnt = 0;
248  	bootmem_walk_os_mem(count_entries_os_bootmem_walk, NULL);
249  	assert_int_equal(os_bootmem_walk_cnt, 4);
250  
251  	bootmem_walk_cnt = 0;
252  	bootmem_walk(count_entries_bootmem_walk, NULL);
253  	assert_int_equal(bootmem_walk_cnt, 5);
254  
255  	expect_assert_failure(
256  		bootmem_add_range(ALIGN_UP(PROGRAM_START, 4096),
257  				  ALIGN_DOWN(PROGRAM_SIZE / 2, 4096),
258  				  BM_MEM_ACPI)
259  	);
260  
261  	os_bootmem_walk_cnt = 0;
262  	bootmem_walk_os_mem(count_entries_os_bootmem_walk, NULL);
263  	assert_int_equal(os_bootmem_walk_cnt, 4);
264  
265  	bootmem_walk_cnt = 0;
266  	bootmem_walk(count_entries_bootmem_walk, NULL);
267  	assert_int_equal(bootmem_walk_cnt, 6);
268  
269  	/* Do not expect assert failure as BM_MEM_RAMSTAGE should not be added to os_bootmem */
270  	bootmem_add_range(ALIGN_UP(STACK_END + 4096, 4096),
271  			  ALIGN_DOWN(STACK_END_TO_RESERVED_START_SIZE / 2, 4096),
272  			  BM_MEM_RAMSTAGE);
273  
274  	os_bootmem_walk_cnt = 0;
275  	bootmem_walk_os_mem(count_entries_os_bootmem_walk, NULL);
276  	assert_int_equal(os_bootmem_walk_cnt, 4);
277  
278  	/* Two entries are added because added range is in middle of another */
279  	bootmem_walk_cnt = 0;
280  	bootmem_walk(count_entries_bootmem_walk, NULL);
281  	assert_int_equal(bootmem_walk_cnt, 8);
282  }
283  
284  static void test_bootmem_walk(void **state)
285  {
286  	init_memory_table_library();
287  
288  	os_bootmem_walk_cnt = 0;
289  	bootmem_walk_os_mem(verify_os_bootmem_walk, NULL);
290  	assert_int_equal(os_bootmem_walk_cnt, 4);
291  
292  	bootmem_walk_cnt = 0;
293  	bootmem_walk(verify_bootmem_walk, NULL);
294  	assert_int_equal(bootmem_walk_cnt, 5);
295  }
296  
297  static void test_bootmem_region_targets_type(void **state)
298  {
299  	int ret;
300  	u64 subregion_start;
301  	u64 subregion_size;
302  
303  	init_memory_table_library();
304  
305  	/* Single whole region */
306  	ret = bootmem_region_targets_type(RAMSTAGE_START, RAMSTAGE_SIZE, BM_MEM_RAMSTAGE);
307  	assert_int_equal(ret, 1);
308  
309  	/* Expect fail because of incorrect bootmem_type */
310  	ret = bootmem_region_targets_type(RAMSTAGE_START, RAMSTAGE_SIZE, BM_MEM_RESERVED);
311  	assert_int_equal(ret, 0);
312  
313  	/* Range covering one more byte than one region */
314  	ret = bootmem_region_targets_type(RAMSTAGE_START, RAMSTAGE_SIZE + 1, BM_MEM_RAMSTAGE);
315  	assert_int_equal(ret, 0);
316  
317  	/* Expect success for subregion of ramstage stretching from point in program range
318  	   to point in stack range. */
319  	subregion_start = PROGRAM_START + PROGRAM_SIZE / 4;
320  	subregion_size = STACK_END - STACK_SIZE / 4 - subregion_start;
321  	ret = bootmem_region_targets_type(subregion_start, subregion_size, BM_MEM_RAMSTAGE);
322  	assert_int_equal(ret, 1);
323  
324  	/* Expect fail for range covering more than one tag as there is no BM_MEM_CACHEABLE */
325  	subregion_start = STACK_START + STACK_SIZE / 2;
326  	subregion_size = RESERVED_START + RESERVED_SIZE / 4 * 3 - subregion_start;
327  	ret = bootmem_region_targets_type(subregion_start, subregion_size, BM_MEM_RAM);
328  	assert_int_equal(ret, 0);
329  
330  	/* Middle of range should not fail */
331  	ret = bootmem_region_targets_type(RESERVED_START + RESERVED_SIZE / 4,
332  					  RESERVED_SIZE / 2, BM_MEM_RESERVED);
333  	assert_int_equal(ret, 1);
334  
335  	/* Subsection of range bordering end edge */
336  	ret = bootmem_region_targets_type(RESERVED_END + RESERVED_END_TO_CACHEABLE_END_SIZE / 2,
337  					  RESERVED_END_TO_CACHEABLE_END_SIZE / 2, BM_MEM_RAM);
338  	assert_int_equal(ret, 1);
339  
340  	/* Region touching zero */
341  	ret = bootmem_region_targets_type(ZERO_REGION_START, ZERO_REGION_SIZE, BM_MEM_RAM);
342  	assert_int_equal(ret, 1);
343  
344  	/* Expect failure when passing zero as size. */
345  	ret = bootmem_region_targets_type(ZERO_REGION_START, 0, BM_MEM_RAM);
346  	assert_int_equal(ret, 0);
347  	ret = bootmem_region_targets_type(RESERVED_START, 0, BM_MEM_RESERVED);
348  	assert_int_equal(ret, 0);
349  }
350  
351  /* Action function used to check alignment of size and base of allocated ranges */
352  static bool verify_bootmem_allocate_buffer(const struct range_entry *r, void *arg)
353  {
354  	if (range_entry_tag(r) == BM_MEM_PAYLOAD) {
355  		assert_true(IS_ALIGNED(range_entry_base(r), 4096));
356  		assert_true(IS_ALIGNED(range_entry_size(r), 4096));
357  	}
358  
359  	return true;
360  }
361  
362  
363  static void test_bootmem_allocate_buffer(void **state)
364  {
365  	void *buf;
366  	void *prev;
367  
368  	init_memory_table_library();
369  
370  	/* All allocated buffers should be below 32bit boundary */
371  	buf = bootmem_allocate_buffer(1ULL << 32);
372  	assert_null(buf);
373  
374  	/* Try too big size for our BM_MEM_RAM range below 32bit boundary */
375  	buf = bootmem_allocate_buffer(RESERVED_START - PROGRAM_START);
376  	assert_null(buf);
377  
378  	/* Two working cases */
379  	buf = bootmem_allocate_buffer(0xE0000000);
380  	assert_non_null(buf);
381  	assert_int_equal(1, bootmem_region_targets_type((uintptr_t)buf,
382  							0xE0000000, BM_MEM_PAYLOAD));
383  	assert_in_range((uintptr_t)buf, CACHEABLE_START + RAMSTAGE_SIZE, RESERVED_START);
384  	/* Check if allocated (payload) ranges have their base and size aligned */
385  	bootmem_walk(verify_bootmem_allocate_buffer, NULL);
386  
387  	prev = buf;
388  	buf = bootmem_allocate_buffer(0xF000000);
389  	assert_non_null(buf);
390  	assert_int_equal(1, bootmem_region_targets_type((uintptr_t)buf,
391  							0xF000000, BM_MEM_PAYLOAD));
392  	assert_in_range((uintptr_t)buf, CACHEABLE_START + RAMSTAGE_SIZE, RESERVED_START);
393  	/* Check if newly allocated buffer does not overlap with previously allocated range */
394  	assert_not_in_range((uintptr_t)buf, (uintptr_t)prev, (uintptr_t)prev + 0xE0000000);
395  	/* Check if allocated (payload) ranges have their base and size aligned */
396  	bootmem_walk(verify_bootmem_allocate_buffer, NULL);
397  
398  	/* Run out of memory for new allocations */
399  	buf = bootmem_allocate_buffer(0x1000000);
400  	assert_null(buf);
401  }
402  
403  int main(void)
404  {
405  	const struct CMUnitTest tests[] = {
406  		cmocka_unit_test(test_bootmem_write_mem_table),
407  		cmocka_unit_test(test_bootmem_add_range),
408  		cmocka_unit_test(test_bootmem_walk),
409  		cmocka_unit_test(test_bootmem_allocate_buffer),
410  		cmocka_unit_test(test_bootmem_region_targets_type)
411  	};
412  
413  	return cb_run_group_tests(tests, NULL, NULL);
414  }