diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 18:49:45 +0000 |
commit | 2c3c1048746a4622d8c89a29670120dc8fab93c4 (patch) | |
tree | 848558de17fb3008cdf4d861b01ac7781903ce39 /tools/testing/memblock | |
parent | Initial commit. (diff) | |
download | linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.tar.xz linux-2c3c1048746a4622d8c89a29670120dc8fab93c4.zip |
Adding upstream version 6.1.76.upstream/6.1.76
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'tools/testing/memblock')
26 files changed, 6786 insertions, 0 deletions
diff --git a/tools/testing/memblock/.gitignore b/tools/testing/memblock/.gitignore new file mode 100644 index 000000000..4cc7cd5aa --- /dev/null +++ b/tools/testing/memblock/.gitignore @@ -0,0 +1,5 @@ +main +memblock.c +linux/memblock.h +asm/asm.h +asm/cmpxchg.h diff --git a/tools/testing/memblock/Makefile b/tools/testing/memblock/Makefile new file mode 100644 index 000000000..575e98fdd --- /dev/null +++ b/tools/testing/memblock/Makefile @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: GPL-2.0 + +# Memblock simulator requires AddressSanitizer (libasan) and liburcu development +# packages installed +CFLAGS += -I. -I../../include -Wall -O2 -fsanitize=address \ + -fsanitize=undefined -D CONFIG_PHYS_ADDR_T_64BIT +LDFLAGS += -fsanitize=address -fsanitize=undefined +TARGETS = main +TEST_OFILES = tests/alloc_nid_api.o tests/alloc_helpers_api.o tests/alloc_api.o \ + tests/basic_api.o tests/common.o +DEP_OFILES = memblock.o lib/slab.o mmzone.o slab.o +OFILES = main.o $(DEP_OFILES) $(TEST_OFILES) +EXTR_SRC = ../../../mm/memblock.c + +ifeq ($(BUILD), 32) + CFLAGS += -m32 + LDFLAGS += -m32 +endif + +# Process user parameters +include scripts/Makefile.include + +main: $(OFILES) + +$(OFILES): include + +include: ../../../include/linux/memblock.h ../../include/linux/*.h \ + ../../include/asm/*.h + + @mkdir -p linux + test -L linux/memblock.h || ln -s ../../../../include/linux/memblock.h linux/memblock.h + test -L asm/asm.h || ln -s ../../../arch/x86/include/asm/asm.h asm/asm.h + test -L asm/cmpxchg.h || ln -s ../../../arch/x86/include/asm/cmpxchg.h asm/cmpxchg.h + +memblock.c: $(EXTR_SRC) + test -L memblock.c || ln -s $(EXTR_SRC) memblock.c + +clean: + $(RM) $(TARGETS) $(OFILES) linux/memblock.h memblock.c asm/asm.h asm/cmpxchg.h + +help: + @echo 'Memblock simulator' + @echo '' + @echo 'Available targets:' + @echo ' main - Build the memblock simulator' + @echo ' clean - Remove generated files and symlinks in the directory' + @echo '' + @echo 'Configuration:' + @echo ' make MEMBLOCK_DEBUG=1 - enable memblock_dbg() messages' + @echo ' make NUMA=1 - simulate enabled NUMA' + @echo ' make 32BIT_PHYS_ADDR_T=1 - Use 32 bit physical addresses' + +vpath %.c ../../lib + +.PHONY: clean include help diff --git a/tools/testing/memblock/README b/tools/testing/memblock/README new file mode 100644 index 000000000..7ca437d81 --- /dev/null +++ b/tools/testing/memblock/README @@ -0,0 +1,118 @@ +================== +Memblock simulator +================== + +Introduction +============ + +Memblock is a boot time memory allocator[1] that manages memory regions before +the actual memory management is initialized. Its APIs allow to register physical +memory regions, mark them as available or reserved, allocate a block of memory +within the requested range and/or in specific NUMA node, and many more. + +Because it is used so early in the booting process, testing and debugging it is +difficult. This test suite, usually referred as memblock simulator, is +an attempt at testing the memblock mechanism. It runs one monolithic test that +consist of a series of checks that exercise both the basic operations and +allocation functionalities of memblock. The main data structure of the boot time +memory allocator is initialized at the build time, so the checks here reuse its +instance throughout the duration of the test. To ensure that tests don't affect +each other, region arrays are reset in between. + +As this project uses the actual memblock code and has to run in user space, +some of the kernel definitions were stubbed by the initial commit that +introduced memblock simulator (commit 16802e55dea9 ("memblock tests: Add +skeleton of the memblock simulator")) and a few preparation commits just +before it. Most of them don't match the kernel implementation, so one should +consult them first before making any significant changes to the project. + +Usage +===== + +To run the tests, build the main target and run it: + +$ make && ./main + +A successful run produces no output. It is possible to control the behavior +by passing options from command line. For example, to include verbose output, +append the `-v` options when you run the tests: + +$ ./main -v + +This will print information about which functions are being tested and the +number of test cases that passed. + +For the full list of options from command line, see `./main --help`. + +It is also possible to override different configuration parameters to change +the test functions. For example, to simulate enabled NUMA, use: + +$ make NUMA=1 + +For the full list of build options, see `make help`. + +Project structure +================= + +The project has one target, main, which calls a group of checks for basic and +allocation functions. Tests for each group are defined in dedicated files, as it +can be seen here: + +memblock +|-- asm ------------------, +|-- lib |-- implement function and struct stubs +|-- linux ------------------' +|-- scripts +| |-- Makefile.include -- handles `make` parameters +|-- tests +| |-- alloc_api.(c|h) -- memblock_alloc tests +| |-- alloc_helpers_api.(c|h) -- memblock_alloc_from tests +| |-- alloc_nid_api.(c|h) -- memblock_alloc_try_nid tests +| |-- basic_api.(c|h) -- memblock_add/memblock_reserve/... tests +| |-- common.(c|h) -- helper functions for resetting memblock; +|-- main.c --------------. dummy physical memory definition +|-- Makefile `- test runner +|-- README +|-- TODO +|-- .gitignore + +Simulating physical memory +========================== + +Some allocation functions clear the memory in the process, so it is required for +memblock to track valid memory ranges. To achieve this, the test suite registers +with memblock memory stored by test_memory struct. It is a small wrapper that +points to a block of memory allocated via malloc. For each group of allocation +tests, dummy physical memory is allocated, added to memblock, and then released +at the end of the test run. The structure of a test runner checking allocation +functions is as follows: + +int memblock_alloc_foo_checks(void) +{ + reset_memblock_attributes(); /* data structure reset */ + dummy_physical_memory_init(); /* allocate and register memory */ + + (...allocation checks...) + + dummy_physical_memory_cleanup(); /* free the memory */ +} + +There's no need to explicitly free the dummy memory from memblock via +memblock_free() call. The entry will be erased by reset_memblock_regions(), +called at the beginning of each test. + +Known issues +============ + +1. Requesting a specific NUMA node via memblock_alloc_node() does not work as + intended. Once the fix is in place, tests for this function can be added. + +2. Tests for memblock_alloc_low() can't be easily implemented. The function uses + ARCH_LOW_ADDRESS_LIMIT marco, which can't be changed to point at the low + memory of the memory_block. + +References +========== + +1. Boot time memory management documentation page: + https://www.kernel.org/doc/html/latest/core-api/boot-time-mm.html diff --git a/tools/testing/memblock/TODO b/tools/testing/memblock/TODO new file mode 100644 index 000000000..33044c634 --- /dev/null +++ b/tools/testing/memblock/TODO @@ -0,0 +1,17 @@ +TODO +===== + +1. Add tests trying to memblock_add() or memblock_reserve() 129th region. + This will trigger memblock_double_array(), make sure it succeeds. + *Important:* These tests require valid memory ranges, use dummy physical + memory block from common.c to implement them. It is also very + likely that the current MEM_SIZE won't be enough for these + test cases. Use realloc to adjust the size accordingly. + +2. Add test cases using this functions (implement them for both directions): + + memblock_alloc_raw() + + memblock_alloc_exact_nid_raw() + + memblock_alloc_try_nid_raw() + +3. Add tests for memblock_alloc_node() to check if the correct NUMA node is set + for the new region diff --git a/tools/testing/memblock/asm/dma.h b/tools/testing/memblock/asm/dma.h new file mode 100644 index 000000000..13ff8e5d2 --- /dev/null +++ b/tools/testing/memblock/asm/dma.h @@ -0,0 +1,5 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _TOOLS_DMA_H +#define _TOOLS_DMA_H + +#endif diff --git a/tools/testing/memblock/internal.h b/tools/testing/memblock/internal.h new file mode 100644 index 000000000..fdb7f5db7 --- /dev/null +++ b/tools/testing/memblock/internal.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _MM_INTERNAL_H +#define _MM_INTERNAL_H + +/* + * Enable memblock_dbg() messages + */ +#ifdef MEMBLOCK_DEBUG +static int memblock_debug = 1; +#endif + +#define pr_warn_ratelimited(fmt, ...) printf(fmt, ##__VA_ARGS__) + +bool mirrored_kernelcore = false; + +struct page {}; + +void memblock_free_pages(struct page *page, unsigned long pfn, + unsigned int order) +{ +} + +#endif diff --git a/tools/testing/memblock/lib/slab.c b/tools/testing/memblock/lib/slab.c new file mode 100644 index 000000000..6be602032 --- /dev/null +++ b/tools/testing/memblock/lib/slab.c @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/slab.h> + +enum slab_state slab_state; + +bool slab_is_available(void) +{ + return slab_state >= UP; +} diff --git a/tools/testing/memblock/linux/init.h b/tools/testing/memblock/linux/init.h new file mode 100644 index 000000000..828e0ee0b --- /dev/null +++ b/tools/testing/memblock/linux/init.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_INIT_H +#define _LINUX_INIT_H + +#include <linux/compiler.h> +#include <asm/export.h> +#include <linux/memory_hotplug.h> + +#define __section(section) __attribute__((__section__(section))) + +#define __initconst +#define __meminit +#define __meminitdata +#define __refdata +#define __initdata + +struct obs_kernel_param { + const char *str; + int (*setup_func)(char *st); + int early; +}; + +#define __setup_param(str, unique_id, fn, early) \ + static const char __setup_str_##unique_id[] __initconst \ + __aligned(1) = str; \ + static struct obs_kernel_param __setup_##unique_id \ + __used __section(".init.setup") \ + __aligned(__alignof__(struct obs_kernel_param)) = \ + { __setup_str_##unique_id, fn, early } + +#define early_param(str, fn) \ + __setup_param(str, fn, fn, 1) + +#endif diff --git a/tools/testing/memblock/linux/kernel.h b/tools/testing/memblock/linux/kernel.h new file mode 100644 index 000000000..d2f148bd8 --- /dev/null +++ b/tools/testing/memblock/linux/kernel.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _MEMBLOCK_LINUX_KERNEL_H +#define _MEMBLOCK_LINUX_KERNEL_H + +#include <../../include/linux/kernel.h> +#include <linux/errno.h> +#include <string.h> +#include <linux/printk.h> +#include <linux/linkage.h> +#include <linux/kconfig.h> + +#endif diff --git a/tools/testing/memblock/linux/kmemleak.h b/tools/testing/memblock/linux/kmemleak.h new file mode 100644 index 000000000..5fed13bb9 --- /dev/null +++ b/tools/testing/memblock/linux/kmemleak.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +#ifndef _KMEMLEAK_H +#define _KMEMLEAK_H + +static inline void kmemleak_free_part_phys(phys_addr_t phys, size_t size) +{ +} + +static inline void kmemleak_alloc_phys(phys_addr_t phys, size_t size, + gfp_t gfp) +{ +} + +static inline void dump_stack(void) +{ +} + +#endif diff --git a/tools/testing/memblock/linux/memory_hotplug.h b/tools/testing/memblock/linux/memory_hotplug.h new file mode 100644 index 000000000..dabe2c556 --- /dev/null +++ b/tools/testing/memblock/linux/memory_hotplug.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LINUX_MEMORY_HOTPLUG_H +#define _LINUX_MEMORY_HOTPLUG_H + +#include <linux/numa.h> +#include <linux/pfn.h> +#include <linux/cache.h> +#include <linux/types.h> + +extern bool movable_node_enabled; + +static inline bool movable_node_is_enabled(void) +{ + return movable_node_enabled; +} + +#endif diff --git a/tools/testing/memblock/linux/mmzone.h b/tools/testing/memblock/linux/mmzone.h new file mode 100644 index 000000000..e65f89b12 --- /dev/null +++ b/tools/testing/memblock/linux/mmzone.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _TOOLS_MMZONE_H +#define _TOOLS_MMZONE_H + +#include <linux/atomic.h> + +struct pglist_data *first_online_pgdat(void); +struct pglist_data *next_online_pgdat(struct pglist_data *pgdat); + +#define for_each_online_pgdat(pgdat) \ + for (pgdat = first_online_pgdat(); \ + pgdat; \ + pgdat = next_online_pgdat(pgdat)) + +enum zone_type { + __MAX_NR_ZONES +}; + +#define MAX_NR_ZONES __MAX_NR_ZONES +#define MAX_ORDER 11 +#define MAX_ORDER_NR_PAGES (1 << (MAX_ORDER - 1)) + +#define pageblock_order (MAX_ORDER - 1) +#define pageblock_nr_pages BIT(pageblock_order) +#define pageblock_align(pfn) ALIGN((pfn), pageblock_nr_pages) +#define pageblock_start_pfn(pfn) ALIGN_DOWN((pfn), pageblock_nr_pages) + +struct zone { + atomic_long_t managed_pages; +}; + +typedef struct pglist_data { + struct zone node_zones[MAX_NR_ZONES]; + +} pg_data_t; + +#endif diff --git a/tools/testing/memblock/linux/printk.h b/tools/testing/memblock/linux/printk.h new file mode 100644 index 000000000..61af424d8 --- /dev/null +++ b/tools/testing/memblock/linux/printk.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _PRINTK_H +#define _PRINTK_H + +#include <stdio.h> +#include <asm/bug.h> + +/* + * memblock_dbg is called with u64 arguments that don't match the "%llu" + * specifier in printf. This results in warnings that cannot be fixed without + * modifying memblock.c, which we wish to avoid. As these messaged are not used + * in testing anyway, the mismatch can be ignored. + */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#define printk printf +#pragma GCC diagnostic push + +#define pr_info printk +#define pr_debug printk +#define pr_cont printk +#define pr_err printk +#define pr_warn printk + +#endif diff --git a/tools/testing/memblock/main.c b/tools/testing/memblock/main.c new file mode 100644 index 000000000..4ca102434 --- /dev/null +++ b/tools/testing/memblock/main.c @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "tests/basic_api.h" +#include "tests/alloc_api.h" +#include "tests/alloc_helpers_api.h" +#include "tests/alloc_nid_api.h" +#include "tests/common.h" + +int main(int argc, char **argv) +{ + parse_args(argc, argv); + memblock_basic_checks(); + memblock_alloc_checks(); + memblock_alloc_helpers_checks(); + memblock_alloc_nid_checks(); + + return 0; +} diff --git a/tools/testing/memblock/mmzone.c b/tools/testing/memblock/mmzone.c new file mode 100644 index 000000000..7b0909e8b --- /dev/null +++ b/tools/testing/memblock/mmzone.c @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include <linux/mmzone.h> + +struct pglist_data *first_online_pgdat(void) +{ + return NULL; +} + +struct pglist_data *next_online_pgdat(struct pglist_data *pgdat) +{ + return NULL; +} + +void reserve_bootmem_region(phys_addr_t start, phys_addr_t end) +{ +} + +void atomic_long_set(atomic_long_t *v, long i) +{ +} diff --git a/tools/testing/memblock/scripts/Makefile.include b/tools/testing/memblock/scripts/Makefile.include new file mode 100644 index 000000000..998281723 --- /dev/null +++ b/tools/testing/memblock/scripts/Makefile.include @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 +# Definitions for user-provided arguments + +# Simulate CONFIG_NUMA=y +ifeq ($(NUMA), 1) + CFLAGS += -D CONFIG_NUMA -D CONFIG_NODES_SHIFT=4 +endif + +# Use 32 bit physical addresses. +# Remember to install 32-bit version of dependencies. +ifeq ($(32BIT_PHYS_ADDR_T), 1) + CFLAGS += -m32 -U CONFIG_PHYS_ADDR_T_64BIT + LDFLAGS += -m32 +endif + +# Enable memblock_dbg() messages +ifeq ($(MEMBLOCK_DEBUG), 1) + CFLAGS += -D MEMBLOCK_DEBUG +endif diff --git a/tools/testing/memblock/tests/alloc_api.c b/tools/testing/memblock/tests/alloc_api.c new file mode 100644 index 000000000..68f1a75cd --- /dev/null +++ b/tools/testing/memblock/tests/alloc_api.c @@ -0,0 +1,884 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "alloc_api.h" + +static int alloc_test_flags = TEST_F_NONE; + +static inline const char * const get_memblock_alloc_name(int flags) +{ + if (flags & TEST_F_RAW) + return "memblock_alloc_raw"; + return "memblock_alloc"; +} + +static inline void *run_memblock_alloc(phys_addr_t size, phys_addr_t align) +{ + if (alloc_test_flags & TEST_F_RAW) + return memblock_alloc_raw(size, align); + return memblock_alloc(size, align); +} + +/* + * A simple test that tries to allocate a small memory region. + * Expect to allocate an aligned region near the end of the available memory. + */ +static int alloc_top_down_simple_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_2; + phys_addr_t expected_start; + + PREFIX_PUSH(); + setup_memblock(); + + expected_start = memblock_end_of_DRAM() - SMP_CACHE_BYTES; + + allocated_ptr = run_memblock_alloc(size, SMP_CACHE_BYTES); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, expected_start); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory next to a reserved region that starts at + * the misaligned address. Expect to create two separate entries, with the new + * entry aligned to the provided alignment: + * + * + + * | +--------+ +--------| + * | | rgn2 | | rgn1 | + * +------------+--------+---------+--------+ + * ^ + * | + * Aligned address boundary + * + * The allocation direction is top-down and region arrays are sorted from lower + * to higher addresses, so the new region will be the first entry in + * memory.reserved array. The previously reserved region does not get modified. + * Region counter and total size get updated. + */ +static int alloc_top_down_disjoint_check(void) +{ + /* After allocation, this will point to the "old" region */ + struct memblock_region *rgn1 = &memblock.reserved.regions[1]; + struct memblock_region *rgn2 = &memblock.reserved.regions[0]; + struct region r1; + void *allocated_ptr = NULL; + phys_addr_t r2_size = SZ_16; + /* Use custom alignment */ + phys_addr_t alignment = SMP_CACHE_BYTES * 2; + phys_addr_t total_size; + phys_addr_t expected_start; + + PREFIX_PUSH(); + setup_memblock(); + + r1.base = memblock_end_of_DRAM() - SZ_2; + r1.size = SZ_2; + + total_size = r1.size + r2_size; + expected_start = memblock_end_of_DRAM() - alignment; + + memblock_reserve(r1.base, r1.size); + + allocated_ptr = run_memblock_alloc(r2_size, alignment); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r2_size, alloc_test_flags); + + ASSERT_EQ(rgn1->size, r1.size); + ASSERT_EQ(rgn1->base, r1.base); + + ASSERT_EQ(rgn2->size, r2_size); + ASSERT_EQ(rgn2->base, expected_start); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when there is enough space at the end + * of the previously reserved block (i.e. first fit): + * + * | +--------+--------------| + * | | r1 | r2 | + * +--------------+--------+--------------+ + * + * Expect a merge of both regions. Only the region size gets updated. + */ +static int alloc_top_down_before_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + /* + * The first region ends at the aligned address to test region merging + */ + phys_addr_t r1_size = SMP_CACHE_BYTES; + phys_addr_t r2_size = SZ_512; + phys_addr_t total_size = r1_size + r2_size; + + PREFIX_PUSH(); + setup_memblock(); + + memblock_reserve(memblock_end_of_DRAM() - total_size, r1_size); + + allocated_ptr = run_memblock_alloc(r2_size, SMP_CACHE_BYTES); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r2_size, alloc_test_flags); + + ASSERT_EQ(rgn->size, total_size); + ASSERT_EQ(rgn->base, memblock_end_of_DRAM() - total_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when there is not enough space at the + * end of the previously reserved block (i.e. second fit): + * + * | +-----------+------+ | + * | | r2 | r1 | | + * +------------+-----------+------+-----+ + * + * Expect a merge of both regions. Both the base address and size of the region + * get updated. + */ +static int alloc_top_down_after_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + struct region r1; + void *allocated_ptr = NULL; + phys_addr_t r2_size = SZ_512; + phys_addr_t total_size; + + PREFIX_PUSH(); + setup_memblock(); + + /* + * The first region starts at the aligned address to test region merging + */ + r1.base = memblock_end_of_DRAM() - SMP_CACHE_BYTES; + r1.size = SZ_8; + + total_size = r1.size + r2_size; + + memblock_reserve(r1.base, r1.size); + + allocated_ptr = run_memblock_alloc(r2_size, SMP_CACHE_BYTES); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r2_size, alloc_test_flags); + + ASSERT_EQ(rgn->size, total_size); + ASSERT_EQ(rgn->base, r1.base - r2_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when there are two reserved regions with + * a gap too small to fit the new region: + * + * | +--------+----------+ +------| + * | | r3 | r2 | | r1 | + * +-------+--------+----------+---+------+ + * + * Expect to allocate a region before the one that starts at the lower address, + * and merge them into one. The region counter and total size fields get + * updated. + */ +static int alloc_top_down_second_fit_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + struct region r1, r2; + void *allocated_ptr = NULL; + phys_addr_t r3_size = SZ_1K; + phys_addr_t total_size; + + PREFIX_PUSH(); + setup_memblock(); + + r1.base = memblock_end_of_DRAM() - SZ_512; + r1.size = SZ_512; + + r2.base = r1.base - SZ_512; + r2.size = SZ_256; + + total_size = r1.size + r2.size + r3_size; + + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + allocated_ptr = run_memblock_alloc(r3_size, SMP_CACHE_BYTES); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r3_size, alloc_test_flags); + + ASSERT_EQ(rgn->size, r2.size + r3_size); + ASSERT_EQ(rgn->base, r2.base - r3_size); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when there are two reserved regions with + * a gap big enough to accommodate the new region: + * + * | +--------+--------+--------+ | + * | | r2 | r3 | r1 | | + * +-----+--------+--------+--------+-----+ + * + * Expect to merge all of them, creating one big entry in memblock.reserved + * array. The region counter and total size fields get updated. + */ +static int alloc_in_between_generic_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + struct region r1, r2; + void *allocated_ptr = NULL; + phys_addr_t gap_size = SMP_CACHE_BYTES; + phys_addr_t r3_size = SZ_64; + /* + * Calculate regions size so there's just enough space for the new entry + */ + phys_addr_t rgn_size = (MEM_SIZE - (2 * gap_size + r3_size)) / 2; + phys_addr_t total_size; + + PREFIX_PUSH(); + setup_memblock(); + + r1.size = rgn_size; + r1.base = memblock_end_of_DRAM() - (gap_size + rgn_size); + + r2.size = rgn_size; + r2.base = memblock_start_of_DRAM() + gap_size; + + total_size = r1.size + r2.size + r3_size; + + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + allocated_ptr = run_memblock_alloc(r3_size, SMP_CACHE_BYTES); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r3_size, alloc_test_flags); + + ASSERT_EQ(rgn->size, total_size); + ASSERT_EQ(rgn->base, r1.base - r2.size - r3_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when the memory is filled with reserved + * regions with memory gaps too small to fit the new region: + * + * +-------+ + * | new | + * +--+----+ + * | +-----+ +-----+ +-----+ | + * | | res | | res | | res | | + * +----+-----+----+-----+----+-----+----+ + * + * Expect no allocation to happen. + */ +static int alloc_small_gaps_generic_check(void) +{ + void *allocated_ptr = NULL; + phys_addr_t region_size = SZ_1K; + phys_addr_t gap_size = SZ_256; + phys_addr_t region_end; + + PREFIX_PUSH(); + setup_memblock(); + + region_end = memblock_start_of_DRAM(); + + while (region_end < memblock_end_of_DRAM()) { + memblock_reserve(region_end + gap_size, region_size); + region_end += gap_size + region_size; + } + + allocated_ptr = run_memblock_alloc(region_size, SMP_CACHE_BYTES); + + ASSERT_EQ(allocated_ptr, NULL); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when all memory is reserved. + * Expect no allocation to happen. + */ +static int alloc_all_reserved_generic_check(void) +{ + void *allocated_ptr = NULL; + + PREFIX_PUSH(); + setup_memblock(); + + /* Simulate full memory */ + memblock_reserve(memblock_start_of_DRAM(), MEM_SIZE); + + allocated_ptr = run_memblock_alloc(SZ_256, SMP_CACHE_BYTES); + + ASSERT_EQ(allocated_ptr, NULL); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when the memory is almost full, + * with not enough space left for the new region: + * + * +-------+ + * | new | + * +-------+ + * |-----------------------------+ | + * | reserved | | + * +-----------------------------+---+ + * + * Expect no allocation to happen. + */ +static int alloc_no_space_generic_check(void) +{ + void *allocated_ptr = NULL; + phys_addr_t available_size = SZ_256; + phys_addr_t reserved_size = MEM_SIZE - available_size; + + PREFIX_PUSH(); + setup_memblock(); + + /* Simulate almost-full memory */ + memblock_reserve(memblock_start_of_DRAM(), reserved_size); + + allocated_ptr = run_memblock_alloc(SZ_1K, SMP_CACHE_BYTES); + + ASSERT_EQ(allocated_ptr, NULL); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when the memory is almost full, + * but there is just enough space left: + * + * |---------------------------+---------| + * | reserved | new | + * +---------------------------+---------+ + * + * Expect to allocate memory and merge all the regions. The total size field + * gets updated. + */ +static int alloc_limited_space_generic_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t available_size = SZ_256; + phys_addr_t reserved_size = MEM_SIZE - available_size; + + PREFIX_PUSH(); + setup_memblock(); + + /* Simulate almost-full memory */ + memblock_reserve(memblock_start_of_DRAM(), reserved_size); + + allocated_ptr = run_memblock_alloc(available_size, SMP_CACHE_BYTES); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, available_size, alloc_test_flags); + + ASSERT_EQ(rgn->size, MEM_SIZE); + ASSERT_EQ(rgn->base, memblock_start_of_DRAM()); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, MEM_SIZE); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when there is no available memory + * registered (i.e. memblock.memory has only a dummy entry). + * Expect no allocation to happen. + */ +static int alloc_no_memory_generic_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + + PREFIX_PUSH(); + + reset_memblock_regions(); + + allocated_ptr = run_memblock_alloc(SZ_1K, SMP_CACHE_BYTES); + + ASSERT_EQ(allocated_ptr, NULL); + ASSERT_EQ(rgn->size, 0); + ASSERT_EQ(rgn->base, 0); + ASSERT_EQ(memblock.reserved.total_size, 0); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a region that is larger than the total size of + * available memory (memblock.memory): + * + * +-----------------------------------+ + * | new | + * +-----------------------------------+ + * | | + * | | + * +---------------------------------+ + * + * Expect no allocation to happen. + */ +static int alloc_too_large_generic_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + + PREFIX_PUSH(); + setup_memblock(); + + allocated_ptr = run_memblock_alloc(MEM_SIZE + SZ_2, SMP_CACHE_BYTES); + + ASSERT_EQ(allocated_ptr, NULL); + ASSERT_EQ(rgn->size, 0); + ASSERT_EQ(rgn->base, 0); + ASSERT_EQ(memblock.reserved.total_size, 0); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries to allocate a small memory region. + * Expect to allocate an aligned region at the beginning of the available + * memory. + */ +static int alloc_bottom_up_simple_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + + PREFIX_PUSH(); + setup_memblock(); + + allocated_ptr = run_memblock_alloc(SZ_2, SMP_CACHE_BYTES); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, SZ_2, alloc_test_flags); + + ASSERT_EQ(rgn->size, SZ_2); + ASSERT_EQ(rgn->base, memblock_start_of_DRAM()); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, SZ_2); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory next to a reserved region that starts at + * the misaligned address. Expect to create two separate entries, with the new + * entry aligned to the provided alignment: + * + * + + * | +----------+ +----------+ | + * | | rgn1 | | rgn2 | | + * +----+----------+---+----------+-----+ + * ^ + * | + * Aligned address boundary + * + * The allocation direction is bottom-up, so the new region will be the second + * entry in memory.reserved array. The previously reserved region does not get + * modified. Region counter and total size get updated. + */ +static int alloc_bottom_up_disjoint_check(void) +{ + struct memblock_region *rgn1 = &memblock.reserved.regions[0]; + struct memblock_region *rgn2 = &memblock.reserved.regions[1]; + struct region r1; + void *allocated_ptr = NULL; + phys_addr_t r2_size = SZ_16; + /* Use custom alignment */ + phys_addr_t alignment = SMP_CACHE_BYTES * 2; + phys_addr_t total_size; + phys_addr_t expected_start; + + PREFIX_PUSH(); + setup_memblock(); + + r1.base = memblock_start_of_DRAM() + SZ_2; + r1.size = SZ_2; + + total_size = r1.size + r2_size; + expected_start = memblock_start_of_DRAM() + alignment; + + memblock_reserve(r1.base, r1.size); + + allocated_ptr = run_memblock_alloc(r2_size, alignment); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r2_size, alloc_test_flags); + + ASSERT_EQ(rgn1->size, r1.size); + ASSERT_EQ(rgn1->base, r1.base); + + ASSERT_EQ(rgn2->size, r2_size); + ASSERT_EQ(rgn2->base, expected_start); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when there is enough space at + * the beginning of the previously reserved block (i.e. first fit): + * + * |------------------+--------+ | + * | r1 | r2 | | + * +------------------+--------+---------+ + * + * Expect a merge of both regions. Only the region size gets updated. + */ +static int alloc_bottom_up_before_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t r1_size = SZ_512; + phys_addr_t r2_size = SZ_128; + phys_addr_t total_size = r1_size + r2_size; + + PREFIX_PUSH(); + setup_memblock(); + + memblock_reserve(memblock_start_of_DRAM() + r1_size, r2_size); + + allocated_ptr = run_memblock_alloc(r1_size, SMP_CACHE_BYTES); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r1_size, alloc_test_flags); + + ASSERT_EQ(rgn->size, total_size); + ASSERT_EQ(rgn->base, memblock_start_of_DRAM()); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when there is not enough space at + * the beginning of the previously reserved block (i.e. second fit): + * + * | +--------+--------------+ | + * | | r1 | r2 | | + * +----+--------+--------------+---------+ + * + * Expect a merge of both regions. Only the region size gets updated. + */ +static int alloc_bottom_up_after_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + struct region r1; + void *allocated_ptr = NULL; + phys_addr_t r2_size = SZ_512; + phys_addr_t total_size; + + PREFIX_PUSH(); + setup_memblock(); + + /* + * The first region starts at the aligned address to test region merging + */ + r1.base = memblock_start_of_DRAM() + SMP_CACHE_BYTES; + r1.size = SZ_64; + + total_size = r1.size + r2_size; + + memblock_reserve(r1.base, r1.size); + + allocated_ptr = run_memblock_alloc(r2_size, SMP_CACHE_BYTES); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r2_size, alloc_test_flags); + + ASSERT_EQ(rgn->size, total_size); + ASSERT_EQ(rgn->base, r1.base); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory when there are two reserved regions, the + * first one starting at the beginning of the available memory, with a gap too + * small to fit the new region: + * + * |------------+ +--------+--------+ | + * | r1 | | r2 | r3 | | + * +------------+-----+--------+--------+--+ + * + * Expect to allocate after the second region, which starts at the higher + * address, and merge them into one. The region counter and total size fields + * get updated. + */ +static int alloc_bottom_up_second_fit_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[1]; + struct region r1, r2; + void *allocated_ptr = NULL; + phys_addr_t r3_size = SZ_1K; + phys_addr_t total_size; + + PREFIX_PUSH(); + setup_memblock(); + + r1.base = memblock_start_of_DRAM(); + r1.size = SZ_512; + + r2.base = r1.base + r1.size + SZ_512; + r2.size = SZ_256; + + total_size = r1.size + r2.size + r3_size; + + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + allocated_ptr = run_memblock_alloc(r3_size, SMP_CACHE_BYTES); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r3_size, alloc_test_flags); + + ASSERT_EQ(rgn->size, r2.size + r3_size); + ASSERT_EQ(rgn->base, r2.base); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* Test case wrappers */ +static int alloc_simple_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_top_down_simple_check(); + memblock_set_bottom_up(true); + alloc_bottom_up_simple_check(); + + return 0; +} + +static int alloc_disjoint_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_top_down_disjoint_check(); + memblock_set_bottom_up(true); + alloc_bottom_up_disjoint_check(); + + return 0; +} + +static int alloc_before_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_top_down_before_check(); + memblock_set_bottom_up(true); + alloc_bottom_up_before_check(); + + return 0; +} + +static int alloc_after_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_top_down_after_check(); + memblock_set_bottom_up(true); + alloc_bottom_up_after_check(); + + return 0; +} + +static int alloc_in_between_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_in_between_generic_check); + run_bottom_up(alloc_in_between_generic_check); + + return 0; +} + +static int alloc_second_fit_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_top_down_second_fit_check(); + memblock_set_bottom_up(true); + alloc_bottom_up_second_fit_check(); + + return 0; +} + +static int alloc_small_gaps_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_small_gaps_generic_check); + run_bottom_up(alloc_small_gaps_generic_check); + + return 0; +} + +static int alloc_all_reserved_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_all_reserved_generic_check); + run_bottom_up(alloc_all_reserved_generic_check); + + return 0; +} + +static int alloc_no_space_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_no_space_generic_check); + run_bottom_up(alloc_no_space_generic_check); + + return 0; +} + +static int alloc_limited_space_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_limited_space_generic_check); + run_bottom_up(alloc_limited_space_generic_check); + + return 0; +} + +static int alloc_no_memory_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_no_memory_generic_check); + run_bottom_up(alloc_no_memory_generic_check); + + return 0; +} + +static int alloc_too_large_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_too_large_generic_check); + run_bottom_up(alloc_too_large_generic_check); + + return 0; +} + +static int memblock_alloc_checks_internal(int flags) +{ + const char *func = get_memblock_alloc_name(flags); + + alloc_test_flags = flags; + prefix_reset(); + prefix_push(func); + test_print("Running %s tests...\n", func); + + reset_memblock_attributes(); + dummy_physical_memory_init(); + + alloc_simple_check(); + alloc_disjoint_check(); + alloc_before_check(); + alloc_after_check(); + alloc_second_fit_check(); + alloc_small_gaps_check(); + alloc_in_between_check(); + alloc_all_reserved_check(); + alloc_no_space_check(); + alloc_limited_space_check(); + alloc_no_memory_check(); + alloc_too_large_check(); + + dummy_physical_memory_cleanup(); + + prefix_pop(); + + return 0; +} + +int memblock_alloc_checks(void) +{ + memblock_alloc_checks_internal(TEST_F_NONE); + memblock_alloc_checks_internal(TEST_F_RAW); + + return 0; +} diff --git a/tools/testing/memblock/tests/alloc_api.h b/tools/testing/memblock/tests/alloc_api.h new file mode 100644 index 000000000..585b085ba --- /dev/null +++ b/tools/testing/memblock/tests/alloc_api.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _MEMBLOCK_ALLOCS_H +#define _MEMBLOCK_ALLOCS_H + +#include "common.h" + +int memblock_alloc_checks(void); + +#endif diff --git a/tools/testing/memblock/tests/alloc_helpers_api.c b/tools/testing/memblock/tests/alloc_helpers_api.c new file mode 100644 index 000000000..3ef9486da --- /dev/null +++ b/tools/testing/memblock/tests/alloc_helpers_api.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "alloc_helpers_api.h" + +/* + * A simple test that tries to allocate a memory region above a specified, + * aligned address: + * + * + + * | +-----------+ | + * | | rgn | | + * +----------+-----------+---------+ + * ^ + * | + * Aligned min_addr + * + * Expect to allocate a cleared region at the minimal memory address. + */ +static int alloc_from_simple_generic_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_16; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_end_of_DRAM() - SMP_CACHE_BYTES; + + allocated_ptr = memblock_alloc_from(size, SMP_CACHE_BYTES, min_addr); + + ASSERT_NE(allocated_ptr, NULL); + ASSERT_MEM_EQ(allocated_ptr, 0, size); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, min_addr); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region above a certain address. + * The minimal address here is not aligned: + * + * + + + * | + +---------+ | + * | | | rgn | | + * +------+------+---------+------------+ + * ^ ^------. + * | | + * min_addr Aligned address + * boundary + * + * Expect to allocate a cleared region at the closest aligned memory address. + */ +static int alloc_from_misaligned_generic_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_32; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_memblock(); + + /* A misaligned address */ + min_addr = memblock_end_of_DRAM() - (SMP_CACHE_BYTES * 2 - 1); + + allocated_ptr = memblock_alloc_from(size, SMP_CACHE_BYTES, min_addr); + + ASSERT_NE(allocated_ptr, NULL); + ASSERT_MEM_EQ(allocated_ptr, 0, size); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, memblock_end_of_DRAM() - SMP_CACHE_BYTES); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region above an address that is too + * close to the end of the memory: + * + * + + + * | +--------+---+ | + * | | rgn + | | + * +-----------+--------+---+------+ + * ^ ^ + * | | + * | min_addr + * | + * Aligned address + * boundary + * + * Expect to prioritize granting memory over satisfying the minimal address + * requirement. + */ +static int alloc_from_top_down_high_addr_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_32; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_memblock(); + + /* The address is too close to the end of the memory */ + min_addr = memblock_end_of_DRAM() - SZ_16; + + allocated_ptr = memblock_alloc_from(size, SMP_CACHE_BYTES, min_addr); + + ASSERT_NE(allocated_ptr, NULL); + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, memblock_end_of_DRAM() - SMP_CACHE_BYTES); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region when there is no space + * available above the minimal address above a certain address: + * + * + + * | +---------+-------------| + * | | rgn | | + * +--------+---------+-------------+ + * ^ + * | + * min_addr + * + * Expect to prioritize granting memory over satisfying the minimal address + * requirement and to allocate next to the previously reserved region. The + * regions get merged into one. + */ +static int alloc_from_top_down_no_space_above_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t r1_size = SZ_64; + phys_addr_t r2_size = SZ_2; + phys_addr_t total_size = r1_size + r2_size; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_end_of_DRAM() - SMP_CACHE_BYTES * 2; + + /* No space above this address */ + memblock_reserve(min_addr, r2_size); + + allocated_ptr = memblock_alloc_from(r1_size, SMP_CACHE_BYTES, min_addr); + + ASSERT_NE(allocated_ptr, NULL); + ASSERT_EQ(rgn->base, min_addr - r1_size); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region with a minimal address below + * the start address of the available memory. As the allocation is top-down, + * first reserve a region that will force allocation near the start. + * Expect successful allocation and merge of both regions. + */ +static int alloc_from_top_down_min_addr_cap_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t r1_size = SZ_64; + phys_addr_t min_addr; + phys_addr_t start_addr; + + PREFIX_PUSH(); + setup_memblock(); + + start_addr = (phys_addr_t)memblock_start_of_DRAM(); + min_addr = start_addr - SMP_CACHE_BYTES * 3; + + memblock_reserve(start_addr + r1_size, MEM_SIZE - r1_size); + + allocated_ptr = memblock_alloc_from(r1_size, SMP_CACHE_BYTES, min_addr); + + ASSERT_NE(allocated_ptr, NULL); + ASSERT_EQ(rgn->base, start_addr); + ASSERT_EQ(rgn->size, MEM_SIZE); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, MEM_SIZE); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region above an address that is too + * close to the end of the memory: + * + * + + * |-----------+ + | + * | rgn | | | + * +-----------+--------------+-----+ + * ^ ^ + * | | + * Aligned address min_addr + * boundary + * + * Expect to prioritize granting memory over satisfying the minimal address + * requirement. Allocation happens at beginning of the available memory. + */ +static int alloc_from_bottom_up_high_addr_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_32; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_memblock(); + + /* The address is too close to the end of the memory */ + min_addr = memblock_end_of_DRAM() - SZ_8; + + allocated_ptr = memblock_alloc_from(size, SMP_CACHE_BYTES, min_addr); + + ASSERT_NE(allocated_ptr, NULL); + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, memblock_start_of_DRAM()); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region when there is no space + * available above the minimal address above a certain address: + * + * + + * |-----------+ +-------------------| + * | rgn | | | + * +-----------+----+-------------------+ + * ^ + * | + * min_addr + * + * Expect to prioritize granting memory over satisfying the minimal address + * requirement and to allocate at the beginning of the available memory. + */ +static int alloc_from_bottom_up_no_space_above_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t r1_size = SZ_64; + phys_addr_t min_addr; + phys_addr_t r2_size; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM() + SZ_128; + r2_size = memblock_end_of_DRAM() - min_addr; + + /* No space above this address */ + memblock_reserve(min_addr - SMP_CACHE_BYTES, r2_size); + + allocated_ptr = memblock_alloc_from(r1_size, SMP_CACHE_BYTES, min_addr); + + ASSERT_NE(allocated_ptr, NULL); + ASSERT_EQ(rgn->base, memblock_start_of_DRAM()); + ASSERT_EQ(rgn->size, r1_size); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, r1_size + r2_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region with a minimal address below + * the start address of the available memory. Expect to allocate a region + * at the beginning of the available memory. + */ +static int alloc_from_bottom_up_min_addr_cap_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t r1_size = SZ_64; + phys_addr_t min_addr; + phys_addr_t start_addr; + + PREFIX_PUSH(); + setup_memblock(); + + start_addr = (phys_addr_t)memblock_start_of_DRAM(); + min_addr = start_addr - SMP_CACHE_BYTES * 3; + + allocated_ptr = memblock_alloc_from(r1_size, SMP_CACHE_BYTES, min_addr); + + ASSERT_NE(allocated_ptr, NULL); + ASSERT_EQ(rgn->base, start_addr); + ASSERT_EQ(rgn->size, r1_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, r1_size); + + test_pass_pop(); + + return 0; +} + +/* Test case wrappers */ +static int alloc_from_simple_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_from_simple_generic_check); + run_bottom_up(alloc_from_simple_generic_check); + + return 0; +} + +static int alloc_from_misaligned_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_from_misaligned_generic_check); + run_bottom_up(alloc_from_misaligned_generic_check); + + return 0; +} + +static int alloc_from_high_addr_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_from_top_down_high_addr_check(); + memblock_set_bottom_up(true); + alloc_from_bottom_up_high_addr_check(); + + return 0; +} + +static int alloc_from_no_space_above_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_from_top_down_no_space_above_check(); + memblock_set_bottom_up(true); + alloc_from_bottom_up_no_space_above_check(); + + return 0; +} + +static int alloc_from_min_addr_cap_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_from_top_down_min_addr_cap_check(); + memblock_set_bottom_up(true); + alloc_from_bottom_up_min_addr_cap_check(); + + return 0; +} + +int memblock_alloc_helpers_checks(void) +{ + const char *func_testing = "memblock_alloc_from"; + + prefix_reset(); + prefix_push(func_testing); + test_print("Running %s tests...\n", func_testing); + + reset_memblock_attributes(); + dummy_physical_memory_init(); + + alloc_from_simple_check(); + alloc_from_misaligned_check(); + alloc_from_high_addr_check(); + alloc_from_no_space_above_check(); + alloc_from_min_addr_cap_check(); + + dummy_physical_memory_cleanup(); + + prefix_pop(); + + return 0; +} diff --git a/tools/testing/memblock/tests/alloc_helpers_api.h b/tools/testing/memblock/tests/alloc_helpers_api.h new file mode 100644 index 000000000..c9e4827b1 --- /dev/null +++ b/tools/testing/memblock/tests/alloc_helpers_api.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _MEMBLOCK_ALLOC_HELPERS_H +#define _MEMBLOCK_ALLOC_HELPERS_H + +#include "common.h" + +int memblock_alloc_helpers_checks(void); + +#endif diff --git a/tools/testing/memblock/tests/alloc_nid_api.c b/tools/testing/memblock/tests/alloc_nid_api.c new file mode 100644 index 000000000..2c2d60f4e --- /dev/null +++ b/tools/testing/memblock/tests/alloc_nid_api.c @@ -0,0 +1,2673 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "alloc_nid_api.h" + +static int alloc_nid_test_flags = TEST_F_NONE; + +/* + * contains the fraction of MEM_SIZE contained in each node in basis point + * units (one hundredth of 1% or 1/10000) + */ +static const unsigned int node_fractions[] = { + 2500, /* 1/4 */ + 625, /* 1/16 */ + 1250, /* 1/8 */ + 1250, /* 1/8 */ + 625, /* 1/16 */ + 625, /* 1/16 */ + 2500, /* 1/4 */ + 625, /* 1/16 */ +}; + +static inline const char * const get_memblock_alloc_try_nid_name(int flags) +{ + if (flags & TEST_F_RAW) + return "memblock_alloc_try_nid_raw"; + return "memblock_alloc_try_nid"; +} + +static inline void *run_memblock_alloc_try_nid(phys_addr_t size, + phys_addr_t align, + phys_addr_t min_addr, + phys_addr_t max_addr, int nid) +{ + if (alloc_nid_test_flags & TEST_F_RAW) + return memblock_alloc_try_nid_raw(size, align, min_addr, + max_addr, nid); + return memblock_alloc_try_nid(size, align, min_addr, max_addr, nid); +} + +/* + * A simple test that tries to allocate a memory region within min_addr and + * max_addr range: + * + * + + + * | + +-----------+ | + * | | | rgn | | + * +----+-------+-----------+------+ + * ^ ^ + * | | + * min_addr max_addr + * + * Expect to allocate a region that ends at max_addr. + */ +static int alloc_try_nid_top_down_simple_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_128; + phys_addr_t min_addr; + phys_addr_t max_addr; + phys_addr_t rgn_end; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM() + SMP_CACHE_BYTES * 2; + max_addr = min_addr + SZ_512; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + rgn_end = rgn->base + rgn->size; + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, max_addr - size); + ASSERT_EQ(rgn_end, max_addr); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries to allocate a memory region within min_addr and + * max_addr range, where the end address is misaligned: + * + * + + + + * | + +---------+ + | + * | | | rgn | | | + * +------+-------+---------+--+----+ + * ^ ^ ^ + * | | | + * min_add | max_addr + * | + * Aligned address + * boundary + * + * Expect to allocate an aligned region that ends before max_addr. + */ +static int alloc_try_nid_top_down_end_misaligned_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_128; + phys_addr_t misalign = SZ_2; + phys_addr_t min_addr; + phys_addr_t max_addr; + phys_addr_t rgn_end; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM() + SMP_CACHE_BYTES * 2; + max_addr = min_addr + SZ_512 + misalign; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + rgn_end = rgn->base + rgn->size; + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, max_addr - size - misalign); + ASSERT_LT(rgn_end, max_addr); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries to allocate a memory region, which spans over the + * min_addr and max_addr range: + * + * + + + * | +---------------+ | + * | | rgn | | + * +------+---------------+-------+ + * ^ ^ + * | | + * min_addr max_addr + * + * Expect to allocate a region that starts at min_addr and ends at + * max_addr, given that min_addr is aligned. + */ +static int alloc_try_nid_exact_address_generic_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_1K; + phys_addr_t min_addr; + phys_addr_t max_addr; + phys_addr_t rgn_end; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM() + SMP_CACHE_BYTES; + max_addr = min_addr + size; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + rgn_end = rgn->base + rgn->size; + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, min_addr); + ASSERT_EQ(rgn_end, max_addr); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region, which can't fit into + * min_addr and max_addr range: + * + * + + + + * | +----------+-----+ | + * | | rgn + | | + * +--------+----------+-----+----+ + * ^ ^ ^ + * | | | + * Aligned | max_addr + * address | + * boundary min_add + * + * Expect to drop the lower limit and allocate a memory region which + * ends at max_addr (if the address is aligned). + */ +static int alloc_try_nid_top_down_narrow_range_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_256; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM() + SZ_512; + max_addr = min_addr + SMP_CACHE_BYTES; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, max_addr - size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region, which can't fit into + * min_addr and max_addr range, with the latter being too close to the beginning + * of the available memory: + * + * +-------------+ + * | new | + * +-------------+ + * + + + * | + | + * | | | + * +-------+--------------+ + * ^ ^ + * | | + * | max_addr + * | + * min_addr + * + * Expect no allocation to happen. + */ +static int alloc_try_nid_low_max_generic_check(void) +{ + void *allocated_ptr = NULL; + phys_addr_t size = SZ_1K; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM(); + max_addr = min_addr + SMP_CACHE_BYTES; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_EQ(allocated_ptr, NULL); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region within min_addr min_addr range, + * with min_addr being so close that it's next to an allocated region: + * + * + + + * | +--------+---------------| + * | | r1 | rgn | + * +-------+--------+---------------+ + * ^ ^ + * | | + * min_addr max_addr + * + * Expect a merge of both regions. Only the region size gets updated. + */ +static int alloc_try_nid_min_reserved_generic_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t r1_size = SZ_128; + phys_addr_t r2_size = SZ_64; + phys_addr_t total_size = r1_size + r2_size; + phys_addr_t min_addr; + phys_addr_t max_addr; + phys_addr_t reserved_base; + + PREFIX_PUSH(); + setup_memblock(); + + max_addr = memblock_end_of_DRAM(); + min_addr = max_addr - r2_size; + reserved_base = min_addr - r1_size; + + memblock_reserve(reserved_base, r1_size); + + allocated_ptr = run_memblock_alloc_try_nid(r2_size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r2_size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, total_size); + ASSERT_EQ(rgn->base, reserved_base); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region within min_addr and max_addr, + * with max_addr being so close that it's next to an allocated region: + * + * + + + * | +-------------+--------| + * | | rgn | r1 | + * +----------+-------------+--------+ + * ^ ^ + * | | + * min_addr max_addr + * + * Expect a merge of regions. Only the region size gets updated. + */ +static int alloc_try_nid_max_reserved_generic_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t r1_size = SZ_64; + phys_addr_t r2_size = SZ_128; + phys_addr_t total_size = r1_size + r2_size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_memblock(); + + max_addr = memblock_end_of_DRAM() - r1_size; + min_addr = max_addr - r2_size; + + memblock_reserve(max_addr, r1_size); + + allocated_ptr = run_memblock_alloc_try_nid(r2_size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r2_size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, total_size); + ASSERT_EQ(rgn->base, min_addr); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_add range, when + * there are two reserved regions at the borders, with a gap big enough to fit + * a new region: + * + * + + + * | +--------+ +-------+------+ | + * | | r2 | | rgn | r1 | | + * +----+--------+---+-------+------+--+ + * ^ ^ + * | | + * min_addr max_addr + * + * Expect to merge the new region with r1. The second region does not get + * updated. The total size field gets updated. + */ + +static int alloc_try_nid_top_down_reserved_with_space_check(void) +{ + struct memblock_region *rgn1 = &memblock.reserved.regions[1]; + struct memblock_region *rgn2 = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + struct region r1, r2; + phys_addr_t r3_size = SZ_64; + phys_addr_t gap_size = SMP_CACHE_BYTES; + phys_addr_t total_size; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_memblock(); + + r1.base = memblock_end_of_DRAM() - SMP_CACHE_BYTES * 2; + r1.size = SMP_CACHE_BYTES; + + r2.size = SZ_128; + r2.base = r1.base - (r3_size + gap_size + r2.size); + + total_size = r1.size + r2.size + r3_size; + min_addr = r2.base + r2.size; + max_addr = r1.base; + + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + allocated_ptr = run_memblock_alloc_try_nid(r3_size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r3_size, alloc_nid_test_flags); + + ASSERT_EQ(rgn1->size, r1.size + r3_size); + ASSERT_EQ(rgn1->base, max_addr - r3_size); + + ASSERT_EQ(rgn2->size, r2.size); + ASSERT_EQ(rgn2->base, r2.base); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_add range, when + * there are two reserved regions at the borders, with a gap of a size equal to + * the size of the new region: + * + * + + + * | +--------+--------+--------+ | + * | | r2 | r3 | r1 | | + * +-----+--------+--------+--------+-----+ + * ^ ^ + * | | + * min_addr max_addr + * + * Expect to merge all of the regions into one. The region counter and total + * size fields get updated. + */ +static int alloc_try_nid_reserved_full_merge_generic_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + struct region r1, r2; + phys_addr_t r3_size = SZ_64; + phys_addr_t total_size; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_memblock(); + + r1.base = memblock_end_of_DRAM() - SMP_CACHE_BYTES * 2; + r1.size = SMP_CACHE_BYTES; + + r2.size = SZ_128; + r2.base = r1.base - (r3_size + r2.size); + + total_size = r1.size + r2.size + r3_size; + min_addr = r2.base + r2.size; + max_addr = r1.base; + + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + allocated_ptr = run_memblock_alloc_try_nid(r3_size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r3_size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, total_size); + ASSERT_EQ(rgn->base, r2.base); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_add range, when + * there are two reserved regions at the borders, with a gap that can't fit + * a new region: + * + * + + + * | +----------+------+ +------+ | + * | | r3 | r2 | | r1 | | + * +--+----------+------+----+------+---+ + * ^ ^ + * | | + * | max_addr + * | + * min_addr + * + * Expect to merge the new region with r2. The second region does not get + * updated. The total size counter gets updated. + */ +static int alloc_try_nid_top_down_reserved_no_space_check(void) +{ + struct memblock_region *rgn1 = &memblock.reserved.regions[1]; + struct memblock_region *rgn2 = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + struct region r1, r2; + phys_addr_t r3_size = SZ_256; + phys_addr_t gap_size = SMP_CACHE_BYTES; + phys_addr_t total_size; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_memblock(); + + r1.base = memblock_end_of_DRAM() - SMP_CACHE_BYTES * 2; + r1.size = SMP_CACHE_BYTES; + + r2.size = SZ_128; + r2.base = r1.base - (r2.size + gap_size); + + total_size = r1.size + r2.size + r3_size; + min_addr = r2.base + r2.size; + max_addr = r1.base; + + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + allocated_ptr = run_memblock_alloc_try_nid(r3_size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r3_size, alloc_nid_test_flags); + + ASSERT_EQ(rgn1->size, r1.size); + ASSERT_EQ(rgn1->base, r1.base); + + ASSERT_EQ(rgn2->size, r2.size + r3_size); + ASSERT_EQ(rgn2->base, r2.base - r3_size); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_add range, but + * it's too narrow and everything else is reserved: + * + * +-----------+ + * | new | + * +-----------+ + * + + + * |--------------+ +----------| + * | r2 | | r1 | + * +--------------+------+----------+ + * ^ ^ + * | | + * | max_addr + * | + * min_addr + * + * Expect no allocation to happen. + */ + +static int alloc_try_nid_reserved_all_generic_check(void) +{ + void *allocated_ptr = NULL; + struct region r1, r2; + phys_addr_t r3_size = SZ_256; + phys_addr_t gap_size = SMP_CACHE_BYTES; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_memblock(); + + r1.base = memblock_end_of_DRAM() - SMP_CACHE_BYTES; + r1.size = SMP_CACHE_BYTES; + + r2.size = MEM_SIZE - (r1.size + gap_size); + r2.base = memblock_start_of_DRAM(); + + min_addr = r2.base + r2.size; + max_addr = r1.base; + + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + allocated_ptr = run_memblock_alloc_try_nid(r3_size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_EQ(allocated_ptr, NULL); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region, where max_addr is + * bigger than the end address of the available memory. Expect to allocate + * a region that ends before the end of the memory. + */ +static int alloc_try_nid_top_down_cap_max_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_256; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_end_of_DRAM() - SZ_1K; + max_addr = memblock_end_of_DRAM() + SZ_256; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, memblock_end_of_DRAM() - size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region, where min_addr is + * smaller than the start address of the available memory. Expect to allocate + * a region that ends before the end of the memory. + */ +static int alloc_try_nid_top_down_cap_min_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_1K; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM() - SZ_256; + max_addr = memblock_end_of_DRAM(); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, memblock_end_of_DRAM() - size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries to allocate a memory region within min_addr and + * max_addr range: + * + * + + + * | +-----------+ | | + * | | rgn | | | + * +----+-----------+-----------+------+ + * ^ ^ + * | | + * min_addr max_addr + * + * Expect to allocate a region that ends before max_addr. + */ +static int alloc_try_nid_bottom_up_simple_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_128; + phys_addr_t min_addr; + phys_addr_t max_addr; + phys_addr_t rgn_end; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM() + SMP_CACHE_BYTES * 2; + max_addr = min_addr + SZ_512; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + rgn_end = rgn->base + rgn->size; + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, min_addr); + ASSERT_LT(rgn_end, max_addr); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries to allocate a memory region within min_addr and + * max_addr range, where the start address is misaligned: + * + * + + + * | + +-----------+ + | + * | | | rgn | | | + * +-----+---+-----------+-----+-----+ + * ^ ^----. ^ + * | | | + * min_add | max_addr + * | + * Aligned address + * boundary + * + * Expect to allocate an aligned region that ends before max_addr. + */ +static int alloc_try_nid_bottom_up_start_misaligned_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_128; + phys_addr_t misalign = SZ_2; + phys_addr_t min_addr; + phys_addr_t max_addr; + phys_addr_t rgn_end; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM() + misalign; + max_addr = min_addr + SZ_512; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + rgn_end = rgn->base + rgn->size; + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, min_addr + (SMP_CACHE_BYTES - misalign)); + ASSERT_LT(rgn_end, max_addr); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region, which can't fit into min_addr + * and max_addr range: + * + * + + + * |---------+ + + | + * | rgn | | | | + * +---------+---------+----+------+ + * ^ ^ + * | | + * | max_addr + * | + * min_add + * + * Expect to drop the lower limit and allocate a memory region which + * starts at the beginning of the available memory. + */ +static int alloc_try_nid_bottom_up_narrow_range_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_256; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM() + SZ_512; + max_addr = min_addr + SMP_CACHE_BYTES; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, memblock_start_of_DRAM()); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_add range, when + * there are two reserved regions at the borders, with a gap big enough to fit + * a new region: + * + * + + + * | +--------+-------+ +------+ | + * | | r2 | rgn | | r1 | | + * +----+--------+-------+---+------+--+ + * ^ ^ + * | | + * min_addr max_addr + * + * Expect to merge the new region with r2. The second region does not get + * updated. The total size field gets updated. + */ + +static int alloc_try_nid_bottom_up_reserved_with_space_check(void) +{ + struct memblock_region *rgn1 = &memblock.reserved.regions[1]; + struct memblock_region *rgn2 = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + struct region r1, r2; + phys_addr_t r3_size = SZ_64; + phys_addr_t gap_size = SMP_CACHE_BYTES; + phys_addr_t total_size; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_memblock(); + + r1.base = memblock_end_of_DRAM() - SMP_CACHE_BYTES * 2; + r1.size = SMP_CACHE_BYTES; + + r2.size = SZ_128; + r2.base = r1.base - (r3_size + gap_size + r2.size); + + total_size = r1.size + r2.size + r3_size; + min_addr = r2.base + r2.size; + max_addr = r1.base; + + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + allocated_ptr = run_memblock_alloc_try_nid(r3_size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r3_size, alloc_nid_test_flags); + + ASSERT_EQ(rgn1->size, r1.size); + ASSERT_EQ(rgn1->base, max_addr); + + ASSERT_EQ(rgn2->size, r2.size + r3_size); + ASSERT_EQ(rgn2->base, r2.base); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_add range, when + * there are two reserved regions at the borders, with a gap of a size equal to + * the size of the new region: + * + * + + + * |----------+ +------+ +----+ | + * | r3 | | r2 | | r1 | | + * +----------+----+------+---+----+--+ + * ^ ^ + * | | + * | max_addr + * | + * min_addr + * + * Expect to drop the lower limit and allocate memory at the beginning of the + * available memory. The region counter and total size fields get updated. + * Other regions are not modified. + */ + +static int alloc_try_nid_bottom_up_reserved_no_space_check(void) +{ + struct memblock_region *rgn1 = &memblock.reserved.regions[2]; + struct memblock_region *rgn2 = &memblock.reserved.regions[1]; + struct memblock_region *rgn3 = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + struct region r1, r2; + phys_addr_t r3_size = SZ_256; + phys_addr_t gap_size = SMP_CACHE_BYTES; + phys_addr_t total_size; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_memblock(); + + r1.base = memblock_end_of_DRAM() - SMP_CACHE_BYTES * 2; + r1.size = SMP_CACHE_BYTES; + + r2.size = SZ_128; + r2.base = r1.base - (r2.size + gap_size); + + total_size = r1.size + r2.size + r3_size; + min_addr = r2.base + r2.size; + max_addr = r1.base; + + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + allocated_ptr = run_memblock_alloc_try_nid(r3_size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, r3_size, alloc_nid_test_flags); + + ASSERT_EQ(rgn3->size, r3_size); + ASSERT_EQ(rgn3->base, memblock_start_of_DRAM()); + + ASSERT_EQ(rgn2->size, r2.size); + ASSERT_EQ(rgn2->base, r2.base); + + ASSERT_EQ(rgn1->size, r1.size); + ASSERT_EQ(rgn1->base, r1.base); + + ASSERT_EQ(memblock.reserved.cnt, 3); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region, where max_addr is + * bigger than the end address of the available memory. Expect to allocate + * a region that starts at the min_addr. + */ +static int alloc_try_nid_bottom_up_cap_max_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_256; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM() + SZ_1K; + max_addr = memblock_end_of_DRAM() + SZ_256; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, min_addr); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region, where min_addr is + * smaller than the start address of the available memory. Expect to allocate + * a region at the beginning of the available memory. + */ +static int alloc_try_nid_bottom_up_cap_min_check(void) +{ + struct memblock_region *rgn = &memblock.reserved.regions[0]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_1K; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_memblock(); + + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM() - SZ_256; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(rgn->size, size); + ASSERT_EQ(rgn->base, memblock_start_of_DRAM()); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* Test case wrappers for range tests */ +static int alloc_try_nid_simple_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_simple_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_simple_check(); + + return 0; +} + +static int alloc_try_nid_misaligned_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_end_misaligned_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_start_misaligned_check(); + + return 0; +} + +static int alloc_try_nid_narrow_range_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_narrow_range_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_narrow_range_check(); + + return 0; +} + +static int alloc_try_nid_reserved_with_space_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_reserved_with_space_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_reserved_with_space_check(); + + return 0; +} + +static int alloc_try_nid_reserved_no_space_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_reserved_no_space_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_reserved_no_space_check(); + + return 0; +} + +static int alloc_try_nid_cap_max_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_cap_max_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_cap_max_check(); + + return 0; +} + +static int alloc_try_nid_cap_min_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_cap_min_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_cap_min_check(); + + return 0; +} + +static int alloc_try_nid_min_reserved_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_try_nid_min_reserved_generic_check); + run_bottom_up(alloc_try_nid_min_reserved_generic_check); + + return 0; +} + +static int alloc_try_nid_max_reserved_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_try_nid_max_reserved_generic_check); + run_bottom_up(alloc_try_nid_max_reserved_generic_check); + + return 0; +} + +static int alloc_try_nid_exact_address_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_try_nid_exact_address_generic_check); + run_bottom_up(alloc_try_nid_exact_address_generic_check); + + return 0; +} + +static int alloc_try_nid_reserved_full_merge_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_try_nid_reserved_full_merge_generic_check); + run_bottom_up(alloc_try_nid_reserved_full_merge_generic_check); + + return 0; +} + +static int alloc_try_nid_reserved_all_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_try_nid_reserved_all_generic_check); + run_bottom_up(alloc_try_nid_reserved_all_generic_check); + + return 0; +} + +static int alloc_try_nid_low_max_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_try_nid_low_max_generic_check); + run_bottom_up(alloc_try_nid_low_max_generic_check); + + return 0; +} + +static int memblock_alloc_nid_range_checks(void) +{ + test_print("Running %s range tests...\n", + get_memblock_alloc_try_nid_name(alloc_nid_test_flags)); + + alloc_try_nid_simple_check(); + alloc_try_nid_misaligned_check(); + alloc_try_nid_narrow_range_check(); + alloc_try_nid_reserved_with_space_check(); + alloc_try_nid_reserved_no_space_check(); + alloc_try_nid_cap_max_check(); + alloc_try_nid_cap_min_check(); + + alloc_try_nid_min_reserved_check(); + alloc_try_nid_max_reserved_check(); + alloc_try_nid_exact_address_check(); + alloc_try_nid_reserved_full_merge_check(); + alloc_try_nid_reserved_all_check(); + alloc_try_nid_low_max_check(); + + return 0; +} + +/* + * A test that tries to allocate a memory region in a specific NUMA node that + * has enough memory to allocate a region of the requested size. + * Expect to allocate an aligned region at the end of the requested node. + */ +static int alloc_try_nid_top_down_numa_simple_check(void) +{ + int nid_req = 3; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + void *allocated_ptr = NULL; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + ASSERT_LE(SZ_4, req_node->size); + size = req_node->size / SZ_4; + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM(); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, region_end(req_node) - size); + ASSERT_LE(req_node->base, new_rgn->base); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region in a specific NUMA node that + * does not have enough memory to allocate a region of the requested size: + * + * | +-----+ +------------------+ | + * | | req | | expected | | + * +---+-----+----------+------------------+-----+ + * + * | +---------+ | + * | | rgn | | + * +-----------------------------+---------+-----+ + * + * Expect to allocate an aligned region at the end of the last node that has + * enough memory (in this case, nid = 6) after falling back to NUMA_NO_NODE. + */ +static int alloc_try_nid_top_down_numa_small_node_check(void) +{ + int nid_req = 1; + int nid_exp = 6; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + struct memblock_region *exp_node = &memblock.memory.regions[nid_exp]; + void *allocated_ptr = NULL; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + size = SZ_2 * req_node->size; + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM(); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, region_end(exp_node) - size); + ASSERT_LE(exp_node->base, new_rgn->base); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region in a specific NUMA node that + * is fully reserved: + * + * | +---------+ +------------------+ | + * | |requested| | expected | | + * +--------------+---------+------------+------------------+-----+ + * + * | +---------+ +---------+ | + * | | reserved| | new | | + * +--------------+---------+---------------------+---------+-----+ + * + * Expect to allocate an aligned region at the end of the last node that is + * large enough and has enough unreserved memory (in this case, nid = 6) after + * falling back to NUMA_NO_NODE. The region count and total size get updated. + */ +static int alloc_try_nid_top_down_numa_node_reserved_check(void) +{ + int nid_req = 2; + int nid_exp = 6; + struct memblock_region *new_rgn = &memblock.reserved.regions[1]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + struct memblock_region *exp_node = &memblock.memory.regions[nid_exp]; + void *allocated_ptr = NULL; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + size = req_node->size; + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM(); + + memblock_reserve(req_node->base, req_node->size); + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, region_end(exp_node) - size); + ASSERT_LE(exp_node->base, new_rgn->base); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, size + req_node->size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region in a specific NUMA node that + * is partially reserved but has enough memory for the allocated region: + * + * | +---------------------------------------+ | + * | | requested | | + * +-----------+---------------------------------------+----------+ + * + * | +------------------+ +-----+ | + * | | reserved | | new | | + * +-----------+------------------+--------------+-----+----------+ + * + * Expect to allocate an aligned region at the end of the requested node. The + * region count and total size get updated. + */ +static int alloc_try_nid_top_down_numa_part_reserved_check(void) +{ + int nid_req = 4; + struct memblock_region *new_rgn = &memblock.reserved.regions[1]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + void *allocated_ptr = NULL; + struct region r1; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + ASSERT_LE(SZ_8, req_node->size); + r1.base = req_node->base; + r1.size = req_node->size / SZ_2; + size = r1.size / SZ_4; + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM(); + + memblock_reserve(r1.base, r1.size); + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, region_end(req_node) - size); + ASSERT_LE(req_node->base, new_rgn->base); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, size + r1.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region in a specific NUMA node that + * is partially reserved and does not have enough contiguous memory for the + * allocated region: + * + * | +-----------------------+ +----------------------| + * | | requested | | expected | + * +-----------+-----------------------+---------+----------------------+ + * + * | +----------+ +-----------| + * | | reserved | | new | + * +-----------------+----------+---------------------------+-----------+ + * + * Expect to allocate an aligned region at the end of the last node that is + * large enough and has enough unreserved memory (in this case, + * nid = NUMA_NODES - 1) after falling back to NUMA_NO_NODE. The region count + * and total size get updated. + */ +static int alloc_try_nid_top_down_numa_part_reserved_fallback_check(void) +{ + int nid_req = 4; + int nid_exp = NUMA_NODES - 1; + struct memblock_region *new_rgn = &memblock.reserved.regions[1]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + struct memblock_region *exp_node = &memblock.memory.regions[nid_exp]; + void *allocated_ptr = NULL; + struct region r1; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + ASSERT_LE(SZ_4, req_node->size); + size = req_node->size / SZ_2; + r1.base = req_node->base + (size / SZ_2); + r1.size = size; + + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM(); + + memblock_reserve(r1.base, r1.size); + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, region_end(exp_node) - size); + ASSERT_LE(exp_node->base, new_rgn->base); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, size + r1.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region that spans over the min_addr + * and max_addr range and overlaps with two different nodes, where the first + * node is the requested node: + * + * min_addr + * | max_addr + * | | + * v v + * | +-----------------------+-----------+ | + * | | requested | node3 | | + * +-----------+-----------------------+-----------+--------------+ + * + + + * | +-----------+ | + * | | rgn | | + * +-----------------------+-----------+--------------------------+ + * + * Expect to drop the lower limit and allocate a memory region that ends at + * the end of the requested node. + */ +static int alloc_try_nid_top_down_numa_split_range_low_check(void) +{ + int nid_req = 2; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_512; + phys_addr_t min_addr; + phys_addr_t max_addr; + phys_addr_t req_node_end; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + req_node_end = region_end(req_node); + min_addr = req_node_end - SZ_256; + max_addr = min_addr + size; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, req_node_end - size); + ASSERT_LE(req_node->base, new_rgn->base); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region that spans over the min_addr + * and max_addr range and overlaps with two different nodes, where the second + * node is the requested node: + * + * min_addr + * | max_addr + * | | + * v v + * | +--------------------------+---------+ | + * | | expected |requested| | + * +------+--------------------------+---------+----------------+ + * + + + * | +---------+ | + * | | rgn | | + * +-----------------------+---------+--------------------------+ + * + * Expect to drop the lower limit and allocate a memory region that + * ends at the end of the first node that overlaps with the range. + */ +static int alloc_try_nid_top_down_numa_split_range_high_check(void) +{ + int nid_req = 3; + int nid_exp = nid_req - 1; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *exp_node = &memblock.memory.regions[nid_exp]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_512; + phys_addr_t min_addr; + phys_addr_t max_addr; + phys_addr_t exp_node_end; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + exp_node_end = region_end(exp_node); + min_addr = exp_node_end - SZ_256; + max_addr = min_addr + size; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, exp_node_end - size); + ASSERT_LE(exp_node->base, new_rgn->base); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region that spans over the min_addr + * and max_addr range and overlaps with two different nodes, where the requested + * node ends before min_addr: + * + * min_addr + * | max_addr + * | | + * v v + * | +---------------+ +-------------+---------+ | + * | | requested | | node1 | node2 | | + * +----+---------------+--------+-------------+---------+----------+ + * + + + * | +---------+ | + * | | rgn | | + * +----------+---------+-------------------------------------------+ + * + * Expect to drop the lower limit and allocate a memory region that ends at + * the end of the requested node. + */ +static int alloc_try_nid_top_down_numa_no_overlap_split_check(void) +{ + int nid_req = 2; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + struct memblock_region *node2 = &memblock.memory.regions[6]; + void *allocated_ptr = NULL; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + size = SZ_512; + min_addr = node2->base - SZ_256; + max_addr = min_addr + size; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, region_end(req_node) - size); + ASSERT_LE(req_node->base, new_rgn->base); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_add range when + * the requested node and the range do not overlap, and requested node ends + * before min_addr. The range overlaps with multiple nodes along node + * boundaries: + * + * min_addr + * | max_addr + * | | + * v v + * |-----------+ +----------+----...----+----------+ | + * | requested | | min node | ... | max node | | + * +-----------+-----------+----------+----...----+----------+------+ + * + + + * | +-----+ | + * | | rgn | | + * +---------------------------------------------------+-----+------+ + * + * Expect to allocate a memory region at the end of the final node in + * the range after falling back to NUMA_NO_NODE. + */ +static int alloc_try_nid_top_down_numa_no_overlap_low_check(void) +{ + int nid_req = 0; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *min_node = &memblock.memory.regions[2]; + struct memblock_region *max_node = &memblock.memory.regions[5]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_64; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + min_addr = min_node->base; + max_addr = region_end(max_node); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, max_addr - size); + ASSERT_LE(max_node->base, new_rgn->base); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_add range when + * the requested node and the range do not overlap, and requested node starts + * after max_addr. The range overlaps with multiple nodes along node + * boundaries: + * + * min_addr + * | max_addr + * | | + * v v + * | +----------+----...----+----------+ +-----------+ | + * | | min node | ... | max node | | requested | | + * +-----+----------+----...----+----------+--------+-----------+---+ + * + + + * | +-----+ | + * | | rgn | | + * +---------------------------------+-----+------------------------+ + * + * Expect to allocate a memory region at the end of the final node in + * the range after falling back to NUMA_NO_NODE. + */ +static int alloc_try_nid_top_down_numa_no_overlap_high_check(void) +{ + int nid_req = 7; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *min_node = &memblock.memory.regions[2]; + struct memblock_region *max_node = &memblock.memory.regions[5]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_64; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + min_addr = min_node->base; + max_addr = region_end(max_node); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, max_addr - size); + ASSERT_LE(max_node->base, new_rgn->base); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region in a specific NUMA node that + * has enough memory to allocate a region of the requested size. + * Expect to allocate an aligned region at the beginning of the requested node. + */ +static int alloc_try_nid_bottom_up_numa_simple_check(void) +{ + int nid_req = 3; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + void *allocated_ptr = NULL; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + ASSERT_LE(SZ_4, req_node->size); + size = req_node->size / SZ_4; + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM(); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, req_node->base); + ASSERT_LE(region_end(new_rgn), region_end(req_node)); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region in a specific NUMA node that + * does not have enough memory to allocate a region of the requested size: + * + * |----------------------+-----+ | + * | expected | req | | + * +----------------------+-----+----------------+ + * + * |---------+ | + * | rgn | | + * +---------+-----------------------------------+ + * + * Expect to allocate an aligned region at the beginning of the first node that + * has enough memory (in this case, nid = 0) after falling back to NUMA_NO_NODE. + */ +static int alloc_try_nid_bottom_up_numa_small_node_check(void) +{ + int nid_req = 1; + int nid_exp = 0; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + struct memblock_region *exp_node = &memblock.memory.regions[nid_exp]; + void *allocated_ptr = NULL; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + size = SZ_2 * req_node->size; + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM(); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, exp_node->base); + ASSERT_LE(region_end(new_rgn), region_end(exp_node)); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region in a specific NUMA node that + * is fully reserved: + * + * |----------------------+ +-----------+ | + * | expected | | requested | | + * +----------------------+-----+-----------+--------------------+ + * + * |-----------+ +-----------+ | + * | new | | reserved | | + * +-----------+----------------+-----------+--------------------+ + * + * Expect to allocate an aligned region at the beginning of the first node that + * is large enough and has enough unreserved memory (in this case, nid = 0) + * after falling back to NUMA_NO_NODE. The region count and total size get + * updated. + */ +static int alloc_try_nid_bottom_up_numa_node_reserved_check(void) +{ + int nid_req = 2; + int nid_exp = 0; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + struct memblock_region *exp_node = &memblock.memory.regions[nid_exp]; + void *allocated_ptr = NULL; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + size = req_node->size; + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM(); + + memblock_reserve(req_node->base, req_node->size); + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, exp_node->base); + ASSERT_LE(region_end(new_rgn), region_end(exp_node)); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, size + req_node->size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region in a specific NUMA node that + * is partially reserved but has enough memory for the allocated region: + * + * | +---------------------------------------+ | + * | | requested | | + * +-----------+---------------------------------------+---------+ + * + * | +------------------+-----+ | + * | | reserved | new | | + * +-----------+------------------+-----+------------------------+ + * + * Expect to allocate an aligned region in the requested node that merges with + * the existing reserved region. The total size gets updated. + */ +static int alloc_try_nid_bottom_up_numa_part_reserved_check(void) +{ + int nid_req = 4; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + void *allocated_ptr = NULL; + struct region r1; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + phys_addr_t total_size; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + ASSERT_LE(SZ_8, req_node->size); + r1.base = req_node->base; + r1.size = req_node->size / SZ_2; + size = r1.size / SZ_4; + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM(); + total_size = size + r1.size; + + memblock_reserve(r1.base, r1.size); + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, total_size); + ASSERT_EQ(new_rgn->base, req_node->base); + ASSERT_LE(region_end(new_rgn), region_end(req_node)); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region in a specific NUMA node that + * is partially reserved and does not have enough contiguous memory for the + * allocated region: + * + * |----------------------+ +-----------------------+ | + * | expected | | requested | | + * +----------------------+-------+-----------------------+---------+ + * + * |-----------+ +----------+ | + * | new | | reserved | | + * +-----------+------------------------+----------+----------------+ + * + * Expect to allocate an aligned region at the beginning of the first + * node that is large enough and has enough unreserved memory (in this case, + * nid = 0) after falling back to NUMA_NO_NODE. The region count and total size + * get updated. + */ +static int alloc_try_nid_bottom_up_numa_part_reserved_fallback_check(void) +{ + int nid_req = 4; + int nid_exp = 0; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + struct memblock_region *exp_node = &memblock.memory.regions[nid_exp]; + void *allocated_ptr = NULL; + struct region r1; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + ASSERT_LE(SZ_4, req_node->size); + size = req_node->size / SZ_2; + r1.base = req_node->base + (size / SZ_2); + r1.size = size; + + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM(); + + memblock_reserve(r1.base, r1.size); + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, exp_node->base); + ASSERT_LE(region_end(new_rgn), region_end(exp_node)); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, size + r1.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region that spans over the min_addr + * and max_addr range and overlaps with two different nodes, where the first + * node is the requested node: + * + * min_addr + * | max_addr + * | | + * v v + * | +-----------------------+-----------+ | + * | | requested | node3 | | + * +-----------+-----------------------+-----------+--------------+ + * + + + * | +-----------+ | + * | | rgn | | + * +-----------+-----------+--------------------------------------+ + * + * Expect to drop the lower limit and allocate a memory region at the beginning + * of the requested node. + */ +static int alloc_try_nid_bottom_up_numa_split_range_low_check(void) +{ + int nid_req = 2; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_512; + phys_addr_t min_addr; + phys_addr_t max_addr; + phys_addr_t req_node_end; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + req_node_end = region_end(req_node); + min_addr = req_node_end - SZ_256; + max_addr = min_addr + size; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, req_node->base); + ASSERT_LE(region_end(new_rgn), req_node_end); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region that spans over the min_addr + * and max_addr range and overlaps with two different nodes, where the second + * node is the requested node: + * + * min_addr + * | max_addr + * | | + * v v + * |------------------+ +----------------------+---------+ | + * | expected | | previous |requested| | + * +------------------+--------+----------------------+---------+------+ + * + + + * |---------+ | + * | rgn | | + * +---------+---------------------------------------------------------+ + * + * Expect to drop the lower limit and allocate a memory region at the beginning + * of the first node that has enough memory. + */ +static int alloc_try_nid_bottom_up_numa_split_range_high_check(void) +{ + int nid_req = 3; + int nid_exp = 0; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + struct memblock_region *exp_node = &memblock.memory.regions[nid_exp]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_512; + phys_addr_t min_addr; + phys_addr_t max_addr; + phys_addr_t exp_node_end; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + exp_node_end = region_end(req_node); + min_addr = req_node->base - SZ_256; + max_addr = min_addr + size; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, exp_node->base); + ASSERT_LE(region_end(new_rgn), exp_node_end); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region that spans over the min_addr + * and max_addr range and overlaps with two different nodes, where the requested + * node ends before min_addr: + * + * min_addr + * | max_addr + * | | + * v v + * | +---------------+ +-------------+---------+ | + * | | requested | | node1 | node2 | | + * +----+---------------+--------+-------------+---------+---------+ + * + + + * | +---------+ | + * | | rgn | | + * +----+---------+------------------------------------------------+ + * + * Expect to drop the lower limit and allocate a memory region that starts at + * the beginning of the requested node. + */ +static int alloc_try_nid_bottom_up_numa_no_overlap_split_check(void) +{ + int nid_req = 2; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + struct memblock_region *node2 = &memblock.memory.regions[6]; + void *allocated_ptr = NULL; + phys_addr_t size; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + size = SZ_512; + min_addr = node2->base - SZ_256; + max_addr = min_addr + size; + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, req_node->base); + ASSERT_LE(region_end(new_rgn), region_end(req_node)); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_add range when + * the requested node and the range do not overlap, and requested node ends + * before min_addr. The range overlaps with multiple nodes along node + * boundaries: + * + * min_addr + * | max_addr + * | | + * v v + * |-----------+ +----------+----...----+----------+ | + * | requested | | min node | ... | max node | | + * +-----------+-----------+----------+----...----+----------+------+ + * + + + * | +-----+ | + * | | rgn | | + * +-----------------------+-----+----------------------------------+ + * + * Expect to allocate a memory region at the beginning of the first node + * in the range after falling back to NUMA_NO_NODE. + */ +static int alloc_try_nid_bottom_up_numa_no_overlap_low_check(void) +{ + int nid_req = 0; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *min_node = &memblock.memory.regions[2]; + struct memblock_region *max_node = &memblock.memory.regions[5]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_64; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + min_addr = min_node->base; + max_addr = region_end(max_node); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, min_addr); + ASSERT_LE(region_end(new_rgn), region_end(min_node)); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_add range when + * the requested node and the range do not overlap, and requested node starts + * after max_addr. The range overlaps with multiple nodes along node + * boundaries: + * + * min_addr + * | max_addr + * | | + * v v + * | +----------+----...----+----------+ +---------+ | + * | | min node | ... | max node | |requested| | + * +-----+----------+----...----+----------+---------+---------+---+ + * + + + * | +-----+ | + * | | rgn | | + * +-----+-----+---------------------------------------------------+ + * + * Expect to allocate a memory region at the beginning of the first node + * in the range after falling back to NUMA_NO_NODE. + */ +static int alloc_try_nid_bottom_up_numa_no_overlap_high_check(void) +{ + int nid_req = 7; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *min_node = &memblock.memory.regions[2]; + struct memblock_region *max_node = &memblock.memory.regions[5]; + void *allocated_ptr = NULL; + phys_addr_t size = SZ_64; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + min_addr = min_node->base; + max_addr = region_end(max_node); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, size); + ASSERT_EQ(new_rgn->base, min_addr); + ASSERT_LE(region_end(new_rgn), region_end(min_node)); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate a memory region in a specific NUMA node that + * does not have enough memory to allocate a region of the requested size. + * Additionally, none of the nodes have enough memory to allocate the region: + * + * +-----------------------------------+ + * | new | + * +-----------------------------------+ + * |-------+-------+-------+-------+-------+-------+-------+-------| + * | node0 | node1 | node2 | node3 | node4 | node5 | node6 | node7 | + * +-------+-------+-------+-------+-------+-------+-------+-------+ + * + * Expect no allocation to happen. + */ +static int alloc_try_nid_numa_large_region_generic_check(void) +{ + int nid_req = 3; + void *allocated_ptr = NULL; + phys_addr_t size = MEM_SIZE / SZ_2; + phys_addr_t min_addr; + phys_addr_t max_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + min_addr = memblock_start_of_DRAM(); + max_addr = memblock_end_of_DRAM(); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + ASSERT_EQ(allocated_ptr, NULL); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_addr range when + * there are two reserved regions at the borders. The requested node starts at + * min_addr and ends at max_addr and is the same size as the region to be + * allocated: + * + * min_addr + * | max_addr + * | | + * v v + * | +-----------+-----------------------+-----------------------| + * | | node5 | requested | node7 | + * +------+-----------+-----------------------+-----------------------+ + * + + + * | +----+-----------------------+----+ | + * | | r2 | new | r1 | | + * +-------------+----+-----------------------+----+------------------+ + * + * Expect to merge all of the regions into one. The region counter and total + * size fields get updated. + */ +static int alloc_try_nid_numa_reserved_full_merge_generic_check(void) +{ + int nid_req = 6; + int nid_next = nid_req + 1; + struct memblock_region *new_rgn = &memblock.reserved.regions[0]; + struct memblock_region *req_node = &memblock.memory.regions[nid_req]; + struct memblock_region *next_node = &memblock.memory.regions[nid_next]; + void *allocated_ptr = NULL; + struct region r1, r2; + phys_addr_t size = req_node->size; + phys_addr_t total_size; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + r1.base = next_node->base; + r1.size = SZ_128; + + r2.size = SZ_128; + r2.base = r1.base - (size + r2.size); + + total_size = r1.size + r2.size + size; + min_addr = r2.base + r2.size; + max_addr = r1.base; + + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, nid_req); + + ASSERT_NE(allocated_ptr, NULL); + assert_mem_content(allocated_ptr, size, alloc_nid_test_flags); + + ASSERT_EQ(new_rgn->size, total_size); + ASSERT_EQ(new_rgn->base, r2.base); + + ASSERT_LE(new_rgn->base, req_node->base); + ASSERT_LE(region_end(req_node), region_end(new_rgn)); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to allocate memory within min_addr and max_add range, + * where the total range can fit the region, but it is split between two nodes + * and everything else is reserved. Additionally, nid is set to NUMA_NO_NODE + * instead of requesting a specific node: + * + * +-----------+ + * | new | + * +-----------+ + * | +---------------------+-----------| + * | | prev node | next node | + * +------+---------------------+-----------+ + * + + + * |----------------------+ +-----| + * | r1 | | r2 | + * +----------------------+-----------+-----+ + * ^ ^ + * | | + * | max_addr + * | + * min_addr + * + * Expect no allocation to happen. + */ +static int alloc_try_nid_numa_split_all_reserved_generic_check(void) +{ + void *allocated_ptr = NULL; + struct memblock_region *next_node = &memblock.memory.regions[7]; + struct region r1, r2; + phys_addr_t size = SZ_256; + phys_addr_t max_addr; + phys_addr_t min_addr; + + PREFIX_PUSH(); + setup_numa_memblock(node_fractions); + + r2.base = next_node->base + SZ_128; + r2.size = memblock_end_of_DRAM() - r2.base; + + r1.size = MEM_SIZE - (r2.size + size); + r1.base = memblock_start_of_DRAM(); + + min_addr = r1.base + r1.size; + max_addr = r2.base; + + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + allocated_ptr = run_memblock_alloc_try_nid(size, SMP_CACHE_BYTES, + min_addr, max_addr, + NUMA_NO_NODE); + + ASSERT_EQ(allocated_ptr, NULL); + + test_pass_pop(); + + return 0; +} + +/* Test case wrappers for NUMA tests */ +static int alloc_try_nid_numa_simple_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_numa_simple_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_numa_simple_check(); + + return 0; +} + +static int alloc_try_nid_numa_small_node_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_numa_small_node_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_numa_small_node_check(); + + return 0; +} + +static int alloc_try_nid_numa_node_reserved_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_numa_node_reserved_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_numa_node_reserved_check(); + + return 0; +} + +static int alloc_try_nid_numa_part_reserved_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_numa_part_reserved_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_numa_part_reserved_check(); + + return 0; +} + +static int alloc_try_nid_numa_part_reserved_fallback_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_numa_part_reserved_fallback_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_numa_part_reserved_fallback_check(); + + return 0; +} + +static int alloc_try_nid_numa_split_range_low_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_numa_split_range_low_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_numa_split_range_low_check(); + + return 0; +} + +static int alloc_try_nid_numa_split_range_high_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_numa_split_range_high_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_numa_split_range_high_check(); + + return 0; +} + +static int alloc_try_nid_numa_no_overlap_split_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_numa_no_overlap_split_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_numa_no_overlap_split_check(); + + return 0; +} + +static int alloc_try_nid_numa_no_overlap_low_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_numa_no_overlap_low_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_numa_no_overlap_low_check(); + + return 0; +} + +static int alloc_try_nid_numa_no_overlap_high_check(void) +{ + test_print("\tRunning %s...\n", __func__); + memblock_set_bottom_up(false); + alloc_try_nid_top_down_numa_no_overlap_high_check(); + memblock_set_bottom_up(true); + alloc_try_nid_bottom_up_numa_no_overlap_high_check(); + + return 0; +} + +static int alloc_try_nid_numa_large_region_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_try_nid_numa_large_region_generic_check); + run_bottom_up(alloc_try_nid_numa_large_region_generic_check); + + return 0; +} + +static int alloc_try_nid_numa_reserved_full_merge_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_try_nid_numa_reserved_full_merge_generic_check); + run_bottom_up(alloc_try_nid_numa_reserved_full_merge_generic_check); + + return 0; +} + +static int alloc_try_nid_numa_split_all_reserved_check(void) +{ + test_print("\tRunning %s...\n", __func__); + run_top_down(alloc_try_nid_numa_split_all_reserved_generic_check); + run_bottom_up(alloc_try_nid_numa_split_all_reserved_generic_check); + + return 0; +} + +int __memblock_alloc_nid_numa_checks(void) +{ + test_print("Running %s NUMA tests...\n", + get_memblock_alloc_try_nid_name(alloc_nid_test_flags)); + + alloc_try_nid_numa_simple_check(); + alloc_try_nid_numa_small_node_check(); + alloc_try_nid_numa_node_reserved_check(); + alloc_try_nid_numa_part_reserved_check(); + alloc_try_nid_numa_part_reserved_fallback_check(); + alloc_try_nid_numa_split_range_low_check(); + alloc_try_nid_numa_split_range_high_check(); + + alloc_try_nid_numa_no_overlap_split_check(); + alloc_try_nid_numa_no_overlap_low_check(); + alloc_try_nid_numa_no_overlap_high_check(); + alloc_try_nid_numa_large_region_check(); + alloc_try_nid_numa_reserved_full_merge_check(); + alloc_try_nid_numa_split_all_reserved_check(); + + return 0; +} + +static int memblock_alloc_nid_checks_internal(int flags) +{ + alloc_nid_test_flags = flags; + + prefix_reset(); + prefix_push(get_memblock_alloc_try_nid_name(flags)); + + reset_memblock_attributes(); + dummy_physical_memory_init(); + + memblock_alloc_nid_range_checks(); + memblock_alloc_nid_numa_checks(); + + dummy_physical_memory_cleanup(); + + prefix_pop(); + + return 0; +} + +int memblock_alloc_nid_checks(void) +{ + memblock_alloc_nid_checks_internal(TEST_F_NONE); + memblock_alloc_nid_checks_internal(TEST_F_RAW); + + return 0; +} diff --git a/tools/testing/memblock/tests/alloc_nid_api.h b/tools/testing/memblock/tests/alloc_nid_api.h new file mode 100644 index 000000000..92d07d230 --- /dev/null +++ b/tools/testing/memblock/tests/alloc_nid_api.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _MEMBLOCK_ALLOC_NID_H +#define _MEMBLOCK_ALLOC_NID_H + +#include "common.h" + +int memblock_alloc_nid_checks(void); +int __memblock_alloc_nid_numa_checks(void); + +#ifdef CONFIG_NUMA +static inline int memblock_alloc_nid_numa_checks(void) +{ + __memblock_alloc_nid_numa_checks(); + return 0; +} + +#else +static inline int memblock_alloc_nid_numa_checks(void) +{ + return 0; +} + +#endif /* CONFIG_NUMA */ + +#endif diff --git a/tools/testing/memblock/tests/basic_api.c b/tools/testing/memblock/tests/basic_api.c new file mode 100644 index 000000000..7ce628e31 --- /dev/null +++ b/tools/testing/memblock/tests/basic_api.c @@ -0,0 +1,1959 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "basic_api.h" +#include <string.h> +#include <linux/memblock.h> + +#define EXPECTED_MEMBLOCK_REGIONS 128 +#define FUNC_ADD "memblock_add" +#define FUNC_RESERVE "memblock_reserve" +#define FUNC_REMOVE "memblock_remove" +#define FUNC_FREE "memblock_free" +#define FUNC_TRIM "memblock_trim_memory" + +static int memblock_initialization_check(void) +{ + PREFIX_PUSH(); + + ASSERT_NE(memblock.memory.regions, NULL); + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.max, EXPECTED_MEMBLOCK_REGIONS); + ASSERT_EQ(strcmp(memblock.memory.name, "memory"), 0); + + ASSERT_NE(memblock.reserved.regions, NULL); + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.memory.max, EXPECTED_MEMBLOCK_REGIONS); + ASSERT_EQ(strcmp(memblock.reserved.name, "reserved"), 0); + + ASSERT_EQ(memblock.bottom_up, false); + ASSERT_EQ(memblock.current_limit, MEMBLOCK_ALLOC_ANYWHERE); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that adds a memory block of a specified base address + * and size to the collection of available memory regions (memblock.memory). + * Expect to create a new entry. The region counter and total memory get + * updated. + */ +static int memblock_add_simple_check(void) +{ + struct memblock_region *rgn; + + rgn = &memblock.memory.regions[0]; + + struct region r = { + .base = SZ_1G, + .size = SZ_4M + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_add(r.base, r.size); + + ASSERT_EQ(rgn->base, r.base); + ASSERT_EQ(rgn->size, r.size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, r.size); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that adds a memory block of a specified base address, size, + * NUMA node and memory flags to the collection of available memory regions. + * Expect to create a new entry. The region counter and total memory get + * updated. + */ +static int memblock_add_node_simple_check(void) +{ + struct memblock_region *rgn; + + rgn = &memblock.memory.regions[0]; + + struct region r = { + .base = SZ_1M, + .size = SZ_16M + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_add_node(r.base, r.size, 1, MEMBLOCK_HOTPLUG); + + ASSERT_EQ(rgn->base, r.base); + ASSERT_EQ(rgn->size, r.size); +#ifdef CONFIG_NUMA + ASSERT_EQ(rgn->nid, 1); +#endif + ASSERT_EQ(rgn->flags, MEMBLOCK_HOTPLUG); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, r.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to add two memory blocks that don't overlap with one + * another: + * + * | +--------+ +--------+ | + * | | r1 | | r2 | | + * +--------+--------+--------+--------+--+ + * + * Expect to add two correctly initialized entries to the collection of + * available memory regions (memblock.memory). The total size and + * region counter fields get updated. + */ +static int memblock_add_disjoint_check(void) +{ + struct memblock_region *rgn1, *rgn2; + + rgn1 = &memblock.memory.regions[0]; + rgn2 = &memblock.memory.regions[1]; + + struct region r1 = { + .base = SZ_1G, + .size = SZ_8K + }; + struct region r2 = { + .base = SZ_1G + SZ_16K, + .size = SZ_8K + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_add(r2.base, r2.size); + + ASSERT_EQ(rgn1->base, r1.base); + ASSERT_EQ(rgn1->size, r1.size); + + ASSERT_EQ(rgn2->base, r2.base); + ASSERT_EQ(rgn2->size, r2.size); + + ASSERT_EQ(memblock.memory.cnt, 2); + ASSERT_EQ(memblock.memory.total_size, r1.size + r2.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to add two memory blocks r1 and r2, where r2 overlaps + * with the beginning of r1 (that is r1.base < r2.base + r2.size): + * + * | +----+----+------------+ | + * | | |r2 | r1 | | + * +----+----+----+------------+----------+ + * ^ ^ + * | | + * | r1.base + * | + * r2.base + * + * Expect to merge the two entries into one region that starts at r2.base + * and has size of two regions minus their intersection. The total size of + * the available memory is updated, and the region counter stays the same. + */ +static int memblock_add_overlap_top_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.memory.regions[0]; + + struct region r1 = { + .base = SZ_512M, + .size = SZ_1G + }; + struct region r2 = { + .base = SZ_256M, + .size = SZ_512M + }; + + PREFIX_PUSH(); + + total_size = (r1.base - r2.base) + r1.size; + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_add(r2.base, r2.size); + + ASSERT_EQ(rgn->base, r2.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to add two memory blocks r1 and r2, where r2 overlaps + * with the end of r1 (that is r2.base < r1.base + r1.size): + * + * | +--+------+----------+ | + * | | | r1 | r2 | | + * +--+--+------+----------+--------------+ + * ^ ^ + * | | + * | r2.base + * | + * r1.base + * + * Expect to merge the two entries into one region that starts at r1.base + * and has size of two regions minus their intersection. The total size of + * the available memory is updated, and the region counter stays the same. + */ +static int memblock_add_overlap_bottom_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.memory.regions[0]; + + struct region r1 = { + .base = SZ_128M, + .size = SZ_512M + }; + struct region r2 = { + .base = SZ_256M, + .size = SZ_1G + }; + + PREFIX_PUSH(); + + total_size = (r2.base - r1.base) + r2.size; + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_add(r2.base, r2.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to add two memory blocks r1 and r2, where r2 is + * within the range of r1 (that is r1.base < r2.base && + * r2.base + r2.size < r1.base + r1.size): + * + * | +-------+--+-----------------------+ + * | | |r2| r1 | + * +---+-------+--+-----------------------+ + * ^ + * | + * r1.base + * + * Expect to merge two entries into one region that stays the same. + * The counter and total size of available memory are not updated. + */ +static int memblock_add_within_check(void) +{ + struct memblock_region *rgn; + + rgn = &memblock.memory.regions[0]; + + struct region r1 = { + .base = SZ_8M, + .size = SZ_32M + }; + struct region r2 = { + .base = SZ_16M, + .size = SZ_1M + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_add(r2.base, r2.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, r1.size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, r1.size); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries to add the same memory block twice. Expect + * the counter and total size of available memory to not be updated. + */ +static int memblock_add_twice_check(void) +{ + struct region r = { + .base = SZ_16K, + .size = SZ_2M + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + + memblock_add(r.base, r.size); + memblock_add(r.base, r.size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, r.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to add two memory blocks that don't overlap with one + * another and then add a third memory block in the space between the first two: + * + * | +--------+--------+--------+ | + * | | r1 | r3 | r2 | | + * +--------+--------+--------+--------+--+ + * + * Expect to merge the three entries into one region that starts at r1.base + * and has size of r1.size + r2.size + r3.size. The region counter and total + * size of the available memory are updated. + */ +static int memblock_add_between_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.memory.regions[0]; + + struct region r1 = { + .base = SZ_1G, + .size = SZ_8K + }; + struct region r2 = { + .base = SZ_1G + SZ_16K, + .size = SZ_8K + }; + struct region r3 = { + .base = SZ_1G + SZ_8K, + .size = SZ_8K + }; + + PREFIX_PUSH(); + + total_size = r1.size + r2.size + r3.size; + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_add(r2.base, r2.size); + memblock_add(r3.base, r3.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries to add a memory block r when r extends past + * PHYS_ADDR_MAX: + * + * +--------+ + * | r | + * +--------+ + * | +----+ + * | | rgn| + * +----------------------------+----+ + * + * Expect to add a memory block of size PHYS_ADDR_MAX - r.base. Expect the + * total size of available memory and the counter to be updated. + */ +static int memblock_add_near_max_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.memory.regions[0]; + + struct region r = { + .base = PHYS_ADDR_MAX - SZ_1M, + .size = SZ_2M + }; + + PREFIX_PUSH(); + + total_size = PHYS_ADDR_MAX - r.base; + + reset_memblock_regions(); + memblock_add(r.base, r.size); + + ASSERT_EQ(rgn->base, r.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +static int memblock_add_checks(void) +{ + prefix_reset(); + prefix_push(FUNC_ADD); + test_print("Running %s tests...\n", FUNC_ADD); + + memblock_add_simple_check(); + memblock_add_node_simple_check(); + memblock_add_disjoint_check(); + memblock_add_overlap_top_check(); + memblock_add_overlap_bottom_check(); + memblock_add_within_check(); + memblock_add_twice_check(); + memblock_add_between_check(); + memblock_add_near_max_check(); + + prefix_pop(); + + return 0; +} + +/* + * A simple test that marks a memory block of a specified base address + * and size as reserved and to the collection of reserved memory regions + * (memblock.reserved). Expect to create a new entry. The region counter + * and total memory size are updated. + */ +static int memblock_reserve_simple_check(void) +{ + struct memblock_region *rgn; + + rgn = &memblock.reserved.regions[0]; + + struct region r = { + .base = SZ_2G, + .size = SZ_128M + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_reserve(r.base, r.size); + + ASSERT_EQ(rgn->base, r.base); + ASSERT_EQ(rgn->size, r.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to mark two memory blocks that don't overlap as reserved: + * + * | +--+ +----------------+ | + * | |r1| | r2 | | + * +--------+--+------+----------------+--+ + * + * Expect to add two entries to the collection of reserved memory regions + * (memblock.reserved). The total size and region counter for + * memblock.reserved are updated. + */ +static int memblock_reserve_disjoint_check(void) +{ + struct memblock_region *rgn1, *rgn2; + + rgn1 = &memblock.reserved.regions[0]; + rgn2 = &memblock.reserved.regions[1]; + + struct region r1 = { + .base = SZ_256M, + .size = SZ_16M + }; + struct region r2 = { + .base = SZ_512M, + .size = SZ_512M + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + ASSERT_EQ(rgn1->base, r1.base); + ASSERT_EQ(rgn1->size, r1.size); + + ASSERT_EQ(rgn2->base, r2.base); + ASSERT_EQ(rgn2->size, r2.size); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, r1.size + r2.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to mark two memory blocks r1 and r2 as reserved, + * where r2 overlaps with the beginning of r1 (that is + * r1.base < r2.base + r2.size): + * + * | +--------------+--+--------------+ | + * | | r2 | | r1 | | + * +--+--------------+--+--------------+--+ + * ^ ^ + * | | + * | r1.base + * | + * r2.base + * + * Expect to merge two entries into one region that starts at r2.base and + * has size of two regions minus their intersection. The total size of the + * reserved memory is updated, and the region counter is not updated. + */ +static int memblock_reserve_overlap_top_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.reserved.regions[0]; + + struct region r1 = { + .base = SZ_1G, + .size = SZ_1G + }; + struct region r2 = { + .base = SZ_128M, + .size = SZ_1G + }; + + PREFIX_PUSH(); + + total_size = (r1.base - r2.base) + r1.size; + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + ASSERT_EQ(rgn->base, r2.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to mark two memory blocks r1 and r2 as reserved, + * where r2 overlaps with the end of r1 (that is + * r2.base < r1.base + r1.size): + * + * | +--------------+--+--------------+ | + * | | r1 | | r2 | | + * +--+--------------+--+--------------+--+ + * ^ ^ + * | | + * | r2.base + * | + * r1.base + * + * Expect to merge two entries into one region that starts at r1.base and + * has size of two regions minus their intersection. The total size of the + * reserved memory is updated, and the region counter is not updated. + */ +static int memblock_reserve_overlap_bottom_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.reserved.regions[0]; + + struct region r1 = { + .base = SZ_2K, + .size = SZ_128K + }; + struct region r2 = { + .base = SZ_128K, + .size = SZ_128K + }; + + PREFIX_PUSH(); + + total_size = (r2.base - r1.base) + r2.size; + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to mark two memory blocks r1 and r2 as reserved, + * where r2 is within the range of r1 (that is + * (r1.base < r2.base) && (r2.base + r2.size < r1.base + r1.size)): + * + * | +-----+--+---------------------------| + * | | |r2| r1 | + * +-+-----+--+---------------------------+ + * ^ ^ + * | | + * | r2.base + * | + * r1.base + * + * Expect to merge two entries into one region that stays the same. The + * counter and total size of available memory are not updated. + */ +static int memblock_reserve_within_check(void) +{ + struct memblock_region *rgn; + + rgn = &memblock.reserved.regions[0]; + + struct region r1 = { + .base = SZ_1M, + .size = SZ_8M + }; + struct region r2 = { + .base = SZ_2M, + .size = SZ_64K + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, r1.size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, r1.size); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries to reserve the same memory block twice. + * Expect the region counter and total size of reserved memory to not + * be updated. + */ +static int memblock_reserve_twice_check(void) +{ + struct region r = { + .base = SZ_16K, + .size = SZ_2M + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + + memblock_reserve(r.base, r.size); + memblock_reserve(r.base, r.size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, r.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to mark two memory blocks that don't overlap as reserved + * and then reserve a third memory block in the space between the first two: + * + * | +--------+--------+--------+ | + * | | r1 | r3 | r2 | | + * +--------+--------+--------+--------+--+ + * + * Expect to merge the three entries into one reserved region that starts at + * r1.base and has size of r1.size + r2.size + r3.size. The region counter and + * total for memblock.reserved are updated. + */ +static int memblock_reserve_between_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.reserved.regions[0]; + + struct region r1 = { + .base = SZ_1G, + .size = SZ_8K + }; + struct region r2 = { + .base = SZ_1G + SZ_16K, + .size = SZ_8K + }; + struct region r3 = { + .base = SZ_1G + SZ_8K, + .size = SZ_8K + }; + + PREFIX_PUSH(); + + total_size = r1.size + r2.size + r3.size; + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + memblock_reserve(r3.base, r3.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries to reserve a memory block r when r extends past + * PHYS_ADDR_MAX: + * + * +--------+ + * | r | + * +--------+ + * | +----+ + * | | rgn| + * +----------------------------+----+ + * + * Expect to reserve a memory block of size PHYS_ADDR_MAX - r.base. Expect the + * total size of reserved memory and the counter to be updated. + */ +static int memblock_reserve_near_max_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.reserved.regions[0]; + + struct region r = { + .base = PHYS_ADDR_MAX - SZ_1M, + .size = SZ_2M + }; + + PREFIX_PUSH(); + + total_size = PHYS_ADDR_MAX - r.base; + + reset_memblock_regions(); + memblock_reserve(r.base, r.size); + + ASSERT_EQ(rgn->base, r.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +static int memblock_reserve_checks(void) +{ + prefix_reset(); + prefix_push(FUNC_RESERVE); + test_print("Running %s tests...\n", FUNC_RESERVE); + + memblock_reserve_simple_check(); + memblock_reserve_disjoint_check(); + memblock_reserve_overlap_top_check(); + memblock_reserve_overlap_bottom_check(); + memblock_reserve_within_check(); + memblock_reserve_twice_check(); + memblock_reserve_between_check(); + memblock_reserve_near_max_check(); + + prefix_pop(); + + return 0; +} + +/* + * A simple test that tries to remove a region r1 from the array of + * available memory regions. By "removing" a region we mean overwriting it + * with the next region r2 in memblock.memory: + * + * | ...... +----------------+ | + * | : r1 : | r2 | | + * +--+----+----------+----------------+--+ + * ^ + * | + * rgn.base + * + * Expect to add two memory blocks r1 and r2 and then remove r1 so that + * r2 is the first available region. The region counter and total size + * are updated. + */ +static int memblock_remove_simple_check(void) +{ + struct memblock_region *rgn; + + rgn = &memblock.memory.regions[0]; + + struct region r1 = { + .base = SZ_2K, + .size = SZ_4K + }; + struct region r2 = { + .base = SZ_128K, + .size = SZ_4M + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_add(r2.base, r2.size); + memblock_remove(r1.base, r1.size); + + ASSERT_EQ(rgn->base, r2.base); + ASSERT_EQ(rgn->size, r2.size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, r2.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to remove a region r2 that was not registered as + * available memory (i.e. has no corresponding entry in memblock.memory): + * + * +----------------+ + * | r2 | + * +----------------+ + * | +----+ | + * | | r1 | | + * +--+----+------------------------------+ + * ^ + * | + * rgn.base + * + * Expect the array, regions counter and total size to not be modified. + */ +static int memblock_remove_absent_check(void) +{ + struct memblock_region *rgn; + + rgn = &memblock.memory.regions[0]; + + struct region r1 = { + .base = SZ_512K, + .size = SZ_4M + }; + struct region r2 = { + .base = SZ_64M, + .size = SZ_1G + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_remove(r2.base, r2.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, r1.size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, r1.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to remove a region r2 that overlaps with the + * beginning of the already existing entry r1 + * (that is r1.base < r2.base + r2.size): + * + * +-----------------+ + * | r2 | + * +-----------------+ + * | .........+--------+ | + * | : r1 | rgn | | + * +-----------------+--------+--------+--+ + * ^ ^ + * | | + * | rgn.base + * r1.base + * + * Expect that only the intersection of both regions is removed from the + * available memory pool. The regions counter and total size are updated. + */ +static int memblock_remove_overlap_top_check(void) +{ + struct memblock_region *rgn; + phys_addr_t r1_end, r2_end, total_size; + + rgn = &memblock.memory.regions[0]; + + struct region r1 = { + .base = SZ_32M, + .size = SZ_32M + }; + struct region r2 = { + .base = SZ_16M, + .size = SZ_32M + }; + + PREFIX_PUSH(); + + r1_end = r1.base + r1.size; + r2_end = r2.base + r2.size; + total_size = r1_end - r2_end; + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_remove(r2.base, r2.size); + + ASSERT_EQ(rgn->base, r1.base + r2.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to remove a region r2 that overlaps with the end of + * the already existing region r1 (that is r2.base < r1.base + r1.size): + * + * +--------------------------------+ + * | r2 | + * +--------------------------------+ + * | +---+..... | + * | |rgn| r1 : | + * +-+---+----+---------------------------+ + * ^ + * | + * r1.base + * + * Expect that only the intersection of both regions is removed from the + * available memory pool. The regions counter and total size are updated. + */ +static int memblock_remove_overlap_bottom_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.memory.regions[0]; + + struct region r1 = { + .base = SZ_2M, + .size = SZ_64M + }; + struct region r2 = { + .base = SZ_32M, + .size = SZ_256M + }; + + PREFIX_PUSH(); + + total_size = r2.base - r1.base; + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_remove(r2.base, r2.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to remove a region r2 that is within the range of + * the already existing entry r1 (that is + * (r1.base < r2.base) && (r2.base + r2.size < r1.base + r1.size)): + * + * +----+ + * | r2 | + * +----+ + * | +-------------+....+---------------+ | + * | | rgn1 | r1 | rgn2 | | + * +-+-------------+----+---------------+-+ + * ^ + * | + * r1.base + * + * Expect that the region is split into two - one that ends at r2.base and + * another that starts at r2.base + r2.size, with appropriate sizes. The + * region counter and total size are updated. + */ +static int memblock_remove_within_check(void) +{ + struct memblock_region *rgn1, *rgn2; + phys_addr_t r1_size, r2_size, total_size; + + rgn1 = &memblock.memory.regions[0]; + rgn2 = &memblock.memory.regions[1]; + + struct region r1 = { + .base = SZ_1M, + .size = SZ_32M + }; + struct region r2 = { + .base = SZ_16M, + .size = SZ_1M + }; + + PREFIX_PUSH(); + + r1_size = r2.base - r1.base; + r2_size = (r1.base + r1.size) - (r2.base + r2.size); + total_size = r1_size + r2_size; + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_remove(r2.base, r2.size); + + ASSERT_EQ(rgn1->base, r1.base); + ASSERT_EQ(rgn1->size, r1_size); + + ASSERT_EQ(rgn2->base, r2.base + r2.size); + ASSERT_EQ(rgn2->size, r2_size); + + ASSERT_EQ(memblock.memory.cnt, 2); + ASSERT_EQ(memblock.memory.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries to remove a region r1 from the array of + * available memory regions when r1 is the only available region. + * Expect to add a memory block r1 and then remove r1 so that a dummy + * region is added. The region counter stays the same, and the total size + * is updated. + */ +static int memblock_remove_only_region_check(void) +{ + struct memblock_region *rgn; + + rgn = &memblock.memory.regions[0]; + + struct region r1 = { + .base = SZ_2K, + .size = SZ_4K + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_remove(r1.base, r1.size); + + ASSERT_EQ(rgn->base, 0); + ASSERT_EQ(rgn->size, 0); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, 0); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries remove a region r2 from the array of available + * memory regions when r2 extends past PHYS_ADDR_MAX: + * + * +--------+ + * | r2 | + * +--------+ + * | +---+....+ + * | |rgn| | + * +------------------------+---+----+ + * + * Expect that only the portion between PHYS_ADDR_MAX and r2.base is removed. + * Expect the total size of available memory to be updated and the counter to + * not be updated. + */ +static int memblock_remove_near_max_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.memory.regions[0]; + + struct region r1 = { + .base = PHYS_ADDR_MAX - SZ_2M, + .size = SZ_2M + }; + + struct region r2 = { + .base = PHYS_ADDR_MAX - SZ_1M, + .size = SZ_2M + }; + + PREFIX_PUSH(); + + total_size = r1.size - (PHYS_ADDR_MAX - r2.base); + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_remove(r2.base, r2.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.memory.cnt, 1); + ASSERT_EQ(memblock.memory.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to remove a region r3 that overlaps with two existing + * regions r1 and r2: + * + * +----------------+ + * | r3 | + * +----------------+ + * | +----+..... ........+--------+ + * | | |r1 : : |r2 | | + * +----+----+----+---+-------+--------+-----+ + * + * Expect that only the intersections of r1 with r3 and r2 with r3 are removed + * from the available memory pool. Expect the total size of available memory to + * be updated and the counter to not be updated. + */ +static int memblock_remove_overlap_two_check(void) +{ + struct memblock_region *rgn1, *rgn2; + phys_addr_t new_r1_size, new_r2_size, r2_end, r3_end, total_size; + + rgn1 = &memblock.memory.regions[0]; + rgn2 = &memblock.memory.regions[1]; + + struct region r1 = { + .base = SZ_16M, + .size = SZ_32M + }; + struct region r2 = { + .base = SZ_64M, + .size = SZ_64M + }; + struct region r3 = { + .base = SZ_32M, + .size = SZ_64M + }; + + PREFIX_PUSH(); + + r2_end = r2.base + r2.size; + r3_end = r3.base + r3.size; + new_r1_size = r3.base - r1.base; + new_r2_size = r2_end - r3_end; + total_size = new_r1_size + new_r2_size; + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_add(r2.base, r2.size); + memblock_remove(r3.base, r3.size); + + ASSERT_EQ(rgn1->base, r1.base); + ASSERT_EQ(rgn1->size, new_r1_size); + + ASSERT_EQ(rgn2->base, r3_end); + ASSERT_EQ(rgn2->size, new_r2_size); + + ASSERT_EQ(memblock.memory.cnt, 2); + ASSERT_EQ(memblock.memory.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +static int memblock_remove_checks(void) +{ + prefix_reset(); + prefix_push(FUNC_REMOVE); + test_print("Running %s tests...\n", FUNC_REMOVE); + + memblock_remove_simple_check(); + memblock_remove_absent_check(); + memblock_remove_overlap_top_check(); + memblock_remove_overlap_bottom_check(); + memblock_remove_within_check(); + memblock_remove_only_region_check(); + memblock_remove_near_max_check(); + memblock_remove_overlap_two_check(); + + prefix_pop(); + + return 0; +} + +/* + * A simple test that tries to free a memory block r1 that was marked + * earlier as reserved. By "freeing" a region we mean overwriting it with + * the next entry r2 in memblock.reserved: + * + * | ...... +----+ | + * | : r1 : | r2 | | + * +--------------+----+-----------+----+-+ + * ^ + * | + * rgn.base + * + * Expect to reserve two memory regions and then erase r1 region with the + * value of r2. The region counter and total size are updated. + */ +static int memblock_free_simple_check(void) +{ + struct memblock_region *rgn; + + rgn = &memblock.reserved.regions[0]; + + struct region r1 = { + .base = SZ_4M, + .size = SZ_1M + }; + struct region r2 = { + .base = SZ_8M, + .size = SZ_1M + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + memblock_free((void *)r1.base, r1.size); + + ASSERT_EQ(rgn->base, r2.base); + ASSERT_EQ(rgn->size, r2.size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, r2.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to free a region r2 that was not marked as reserved + * (i.e. has no corresponding entry in memblock.reserved): + * + * +----------------+ + * | r2 | + * +----------------+ + * | +----+ | + * | | r1 | | + * +--+----+------------------------------+ + * ^ + * | + * rgn.base + * + * The array, regions counter and total size are not modified. + */ +static int memblock_free_absent_check(void) +{ + struct memblock_region *rgn; + + rgn = &memblock.reserved.regions[0]; + + struct region r1 = { + .base = SZ_2M, + .size = SZ_8K + }; + struct region r2 = { + .base = SZ_16M, + .size = SZ_128M + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_free((void *)r2.base, r2.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, r1.size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, r1.size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to free a region r2 that overlaps with the beginning + * of the already existing entry r1 (that is r1.base < r2.base + r2.size): + * + * +----+ + * | r2 | + * +----+ + * | ...+--------------+ | + * | : | r1 | | + * +----+--+--------------+---------------+ + * ^ ^ + * | | + * | rgn.base + * | + * r1.base + * + * Expect that only the intersection of both regions is freed. The + * regions counter and total size are updated. + */ +static int memblock_free_overlap_top_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.reserved.regions[0]; + + struct region r1 = { + .base = SZ_8M, + .size = SZ_32M + }; + struct region r2 = { + .base = SZ_1M, + .size = SZ_8M + }; + + PREFIX_PUSH(); + + total_size = (r1.size + r1.base) - (r2.base + r2.size); + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_free((void *)r2.base, r2.size); + + ASSERT_EQ(rgn->base, r2.base + r2.size); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to free a region r2 that overlaps with the end of + * the already existing entry r1 (that is r2.base < r1.base + r1.size): + * + * +----------------+ + * | r2 | + * +----------------+ + * | +-----------+..... | + * | | r1 | : | + * +----+-----------+----+----------------+ + * + * Expect that only the intersection of both regions is freed. The + * regions counter and total size are updated. + */ +static int memblock_free_overlap_bottom_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.reserved.regions[0]; + + struct region r1 = { + .base = SZ_8M, + .size = SZ_32M + }; + struct region r2 = { + .base = SZ_32M, + .size = SZ_32M + }; + + PREFIX_PUSH(); + + total_size = r2.base - r1.base; + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_free((void *)r2.base, r2.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to free a region r2 that is within the range of the + * already existing entry r1 (that is + * (r1.base < r2.base) && (r2.base + r2.size < r1.base + r1.size)): + * + * +----+ + * | r2 | + * +----+ + * | +------------+....+---------------+ + * | | rgn1 | r1 | rgn2 | + * +----+------------+----+---------------+ + * ^ + * | + * r1.base + * + * Expect that the region is split into two - one that ends at r2.base and + * another that starts at r2.base + r2.size, with appropriate sizes. The + * region counter and total size fields are updated. + */ +static int memblock_free_within_check(void) +{ + struct memblock_region *rgn1, *rgn2; + phys_addr_t r1_size, r2_size, total_size; + + rgn1 = &memblock.reserved.regions[0]; + rgn2 = &memblock.reserved.regions[1]; + + struct region r1 = { + .base = SZ_1M, + .size = SZ_8M + }; + struct region r2 = { + .base = SZ_4M, + .size = SZ_1M + }; + + PREFIX_PUSH(); + + r1_size = r2.base - r1.base; + r2_size = (r1.base + r1.size) - (r2.base + r2.size); + total_size = r1_size + r2_size; + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_free((void *)r2.base, r2.size); + + ASSERT_EQ(rgn1->base, r1.base); + ASSERT_EQ(rgn1->size, r1_size); + + ASSERT_EQ(rgn2->base, r2.base + r2.size); + ASSERT_EQ(rgn2->size, r2_size); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries to free a memory block r1 that was marked + * earlier as reserved when r1 is the only available region. + * Expect to reserve a memory block r1 and then free r1 so that r1 is + * overwritten with a dummy region. The region counter stays the same, + * and the total size is updated. + */ +static int memblock_free_only_region_check(void) +{ + struct memblock_region *rgn; + + rgn = &memblock.reserved.regions[0]; + + struct region r1 = { + .base = SZ_2K, + .size = SZ_4K + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_free((void *)r1.base, r1.size); + + ASSERT_EQ(rgn->base, 0); + ASSERT_EQ(rgn->size, 0); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, 0); + + test_pass_pop(); + + return 0; +} + +/* + * A simple test that tries free a region r2 when r2 extends past PHYS_ADDR_MAX: + * + * +--------+ + * | r2 | + * +--------+ + * | +---+....+ + * | |rgn| | + * +------------------------+---+----+ + * + * Expect that only the portion between PHYS_ADDR_MAX and r2.base is freed. + * Expect the total size of reserved memory to be updated and the counter to + * not be updated. + */ +static int memblock_free_near_max_check(void) +{ + struct memblock_region *rgn; + phys_addr_t total_size; + + rgn = &memblock.reserved.regions[0]; + + struct region r1 = { + .base = PHYS_ADDR_MAX - SZ_2M, + .size = SZ_2M + }; + + struct region r2 = { + .base = PHYS_ADDR_MAX - SZ_1M, + .size = SZ_2M + }; + + PREFIX_PUSH(); + + total_size = r1.size - (PHYS_ADDR_MAX - r2.base); + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_free((void *)r2.base, r2.size); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, total_size); + + ASSERT_EQ(memblock.reserved.cnt, 1); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to free a reserved region r3 that overlaps with two + * existing reserved regions r1 and r2: + * + * +----------------+ + * | r3 | + * +----------------+ + * | +----+..... ........+--------+ + * | | |r1 : : |r2 | | + * +----+----+----+---+-------+--------+-----+ + * + * Expect that only the intersections of r1 with r3 and r2 with r3 are freed + * from the collection of reserved memory. Expect the total size of reserved + * memory to be updated and the counter to not be updated. + */ +static int memblock_free_overlap_two_check(void) +{ + struct memblock_region *rgn1, *rgn2; + phys_addr_t new_r1_size, new_r2_size, r2_end, r3_end, total_size; + + rgn1 = &memblock.reserved.regions[0]; + rgn2 = &memblock.reserved.regions[1]; + + struct region r1 = { + .base = SZ_16M, + .size = SZ_32M + }; + struct region r2 = { + .base = SZ_64M, + .size = SZ_64M + }; + struct region r3 = { + .base = SZ_32M, + .size = SZ_64M + }; + + PREFIX_PUSH(); + + r2_end = r2.base + r2.size; + r3_end = r3.base + r3.size; + new_r1_size = r3.base - r1.base; + new_r2_size = r2_end - r3_end; + total_size = new_r1_size + new_r2_size; + + reset_memblock_regions(); + memblock_reserve(r1.base, r1.size); + memblock_reserve(r2.base, r2.size); + memblock_free((void *)r3.base, r3.size); + + ASSERT_EQ(rgn1->base, r1.base); + ASSERT_EQ(rgn1->size, new_r1_size); + + ASSERT_EQ(rgn2->base, r3_end); + ASSERT_EQ(rgn2->size, new_r2_size); + + ASSERT_EQ(memblock.reserved.cnt, 2); + ASSERT_EQ(memblock.reserved.total_size, total_size); + + test_pass_pop(); + + return 0; +} + +static int memblock_free_checks(void) +{ + prefix_reset(); + prefix_push(FUNC_FREE); + test_print("Running %s tests...\n", FUNC_FREE); + + memblock_free_simple_check(); + memblock_free_absent_check(); + memblock_free_overlap_top_check(); + memblock_free_overlap_bottom_check(); + memblock_free_within_check(); + memblock_free_only_region_check(); + memblock_free_near_max_check(); + memblock_free_overlap_two_check(); + + prefix_pop(); + + return 0; +} + +static int memblock_set_bottom_up_check(void) +{ + prefix_push("memblock_set_bottom_up"); + + memblock_set_bottom_up(false); + ASSERT_EQ(memblock.bottom_up, false); + memblock_set_bottom_up(true); + ASSERT_EQ(memblock.bottom_up, true); + + reset_memblock_attributes(); + test_pass_pop(); + + return 0; +} + +static int memblock_bottom_up_check(void) +{ + prefix_push("memblock_bottom_up"); + + memblock_set_bottom_up(false); + ASSERT_EQ(memblock_bottom_up(), memblock.bottom_up); + ASSERT_EQ(memblock_bottom_up(), false); + memblock_set_bottom_up(true); + ASSERT_EQ(memblock_bottom_up(), memblock.bottom_up); + ASSERT_EQ(memblock_bottom_up(), true); + + reset_memblock_attributes(); + test_pass_pop(); + + return 0; +} + +static int memblock_bottom_up_checks(void) +{ + test_print("Running memblock_*bottom_up tests...\n"); + + prefix_reset(); + memblock_set_bottom_up_check(); + prefix_reset(); + memblock_bottom_up_check(); + + return 0; +} + +/* + * A test that tries to trim memory when both ends of the memory region are + * aligned. Expect that the memory will not be trimmed. Expect the counter to + * not be updated. + */ +static int memblock_trim_memory_aligned_check(void) +{ + struct memblock_region *rgn; + const phys_addr_t alignment = SMP_CACHE_BYTES; + + rgn = &memblock.memory.regions[0]; + + struct region r = { + .base = alignment, + .size = alignment * 4 + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_add(r.base, r.size); + memblock_trim_memory(alignment); + + ASSERT_EQ(rgn->base, r.base); + ASSERT_EQ(rgn->size, r.size); + + ASSERT_EQ(memblock.memory.cnt, 1); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to trim memory when there are two available regions, r1 and + * r2. Region r1 is aligned on both ends and region r2 is unaligned on one end + * and smaller than the alignment: + * + * alignment + * |--------| + * | +-----------------+ +------+ | + * | | r1 | | r2 | | + * +--------+-----------------+--------+------+---+ + * ^ ^ ^ ^ ^ + * |________|________|________| | + * | Unaligned address + * Aligned addresses + * + * Expect that r1 will not be trimmed and r2 will be removed. Expect the + * counter to be updated. + */ +static int memblock_trim_memory_too_small_check(void) +{ + struct memblock_region *rgn; + const phys_addr_t alignment = SMP_CACHE_BYTES; + + rgn = &memblock.memory.regions[0]; + + struct region r1 = { + .base = alignment, + .size = alignment * 2 + }; + struct region r2 = { + .base = alignment * 4, + .size = alignment - SZ_2 + }; + + PREFIX_PUSH(); + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_add(r2.base, r2.size); + memblock_trim_memory(alignment); + + ASSERT_EQ(rgn->base, r1.base); + ASSERT_EQ(rgn->size, r1.size); + + ASSERT_EQ(memblock.memory.cnt, 1); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to trim memory when there are two available regions, r1 and + * r2. Region r1 is aligned on both ends and region r2 is unaligned at the base + * and aligned at the end: + * + * Unaligned address + * | + * v + * | +-----------------+ +---------------+ | + * | | r1 | | r2 | | + * +--------+-----------------+----------+---------------+---+ + * ^ ^ ^ ^ ^ ^ + * |________|________|________|________|________| + * | + * Aligned addresses + * + * Expect that r1 will not be trimmed and r2 will be trimmed at the base. + * Expect the counter to not be updated. + */ +static int memblock_trim_memory_unaligned_base_check(void) +{ + struct memblock_region *rgn1, *rgn2; + const phys_addr_t alignment = SMP_CACHE_BYTES; + phys_addr_t offset = SZ_2; + phys_addr_t new_r2_base, new_r2_size; + + rgn1 = &memblock.memory.regions[0]; + rgn2 = &memblock.memory.regions[1]; + + struct region r1 = { + .base = alignment, + .size = alignment * 2 + }; + struct region r2 = { + .base = alignment * 4 + offset, + .size = alignment * 2 - offset + }; + + PREFIX_PUSH(); + + new_r2_base = r2.base + (alignment - offset); + new_r2_size = r2.size - (alignment - offset); + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_add(r2.base, r2.size); + memblock_trim_memory(alignment); + + ASSERT_EQ(rgn1->base, r1.base); + ASSERT_EQ(rgn1->size, r1.size); + + ASSERT_EQ(rgn2->base, new_r2_base); + ASSERT_EQ(rgn2->size, new_r2_size); + + ASSERT_EQ(memblock.memory.cnt, 2); + + test_pass_pop(); + + return 0; +} + +/* + * A test that tries to trim memory when there are two available regions, r1 and + * r2. Region r1 is aligned on both ends and region r2 is aligned at the base + * and unaligned at the end: + * + * Unaligned address + * | + * v + * | +-----------------+ +---------------+ | + * | | r1 | | r2 | | + * +--------+-----------------+--------+---------------+---+ + * ^ ^ ^ ^ ^ ^ + * |________|________|________|________|________| + * | + * Aligned addresses + * + * Expect that r1 will not be trimmed and r2 will be trimmed at the end. + * Expect the counter to not be updated. + */ +static int memblock_trim_memory_unaligned_end_check(void) +{ + struct memblock_region *rgn1, *rgn2; + const phys_addr_t alignment = SMP_CACHE_BYTES; + phys_addr_t offset = SZ_2; + phys_addr_t new_r2_size; + + rgn1 = &memblock.memory.regions[0]; + rgn2 = &memblock.memory.regions[1]; + + struct region r1 = { + .base = alignment, + .size = alignment * 2 + }; + struct region r2 = { + .base = alignment * 4, + .size = alignment * 2 - offset + }; + + PREFIX_PUSH(); + + new_r2_size = r2.size - (alignment - offset); + + reset_memblock_regions(); + memblock_add(r1.base, r1.size); + memblock_add(r2.base, r2.size); + memblock_trim_memory(alignment); + + ASSERT_EQ(rgn1->base, r1.base); + ASSERT_EQ(rgn1->size, r1.size); + + ASSERT_EQ(rgn2->base, r2.base); + ASSERT_EQ(rgn2->size, new_r2_size); + + ASSERT_EQ(memblock.memory.cnt, 2); + + test_pass_pop(); + + return 0; +} + +static int memblock_trim_memory_checks(void) +{ + prefix_reset(); + prefix_push(FUNC_TRIM); + test_print("Running %s tests...\n", FUNC_TRIM); + + memblock_trim_memory_aligned_check(); + memblock_trim_memory_too_small_check(); + memblock_trim_memory_unaligned_base_check(); + memblock_trim_memory_unaligned_end_check(); + + prefix_pop(); + + return 0; +} + +int memblock_basic_checks(void) +{ + memblock_initialization_check(); + memblock_add_checks(); + memblock_reserve_checks(); + memblock_remove_checks(); + memblock_free_checks(); + memblock_bottom_up_checks(); + memblock_trim_memory_checks(); + + return 0; +} diff --git a/tools/testing/memblock/tests/basic_api.h b/tools/testing/memblock/tests/basic_api.h new file mode 100644 index 000000000..1873faa54 --- /dev/null +++ b/tools/testing/memblock/tests/basic_api.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _MEMBLOCK_BASIC_H +#define _MEMBLOCK_BASIC_H + +#include "common.h" + +int memblock_basic_checks(void); + +#endif diff --git a/tools/testing/memblock/tests/common.c b/tools/testing/memblock/tests/common.c new file mode 100644 index 000000000..3f795047b --- /dev/null +++ b/tools/testing/memblock/tests/common.c @@ -0,0 +1,206 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "tests/common.h" +#include <string.h> +#include <getopt.h> +#include <linux/memory_hotplug.h> +#include <linux/build_bug.h> + +#define INIT_MEMBLOCK_REGIONS 128 +#define INIT_MEMBLOCK_RESERVED_REGIONS INIT_MEMBLOCK_REGIONS +#define PREFIXES_MAX 15 +#define DELIM ": " +#define BASIS 10000 + +static struct test_memory memory_block; +static const char __maybe_unused *prefixes[PREFIXES_MAX]; +static int __maybe_unused nr_prefixes; + +static const char *short_opts = "hmv"; +static const struct option long_opts[] = { + {"help", 0, NULL, 'h'}, + {"movable-node", 0, NULL, 'm'}, + {"verbose", 0, NULL, 'v'}, + {NULL, 0, NULL, 0} +}; + +static const char * const help_opts[] = { + "display this help message and exit", + "disallow allocations from regions marked as hotplugged\n\t\t\t" + "by simulating enabling the \"movable_node\" kernel\n\t\t\t" + "parameter", + "enable verbose output, which includes the name of the\n\t\t\t" + "memblock function being tested, the name of the test,\n\t\t\t" + "and whether the test passed or failed." +}; + +static int verbose; + +/* sets global variable returned by movable_node_is_enabled() stub */ +bool movable_node_enabled; + +void reset_memblock_regions(void) +{ + memset(memblock.memory.regions, 0, + memblock.memory.cnt * sizeof(struct memblock_region)); + memblock.memory.cnt = 1; + memblock.memory.max = INIT_MEMBLOCK_REGIONS; + memblock.memory.total_size = 0; + + memset(memblock.reserved.regions, 0, + memblock.reserved.cnt * sizeof(struct memblock_region)); + memblock.reserved.cnt = 1; + memblock.reserved.max = INIT_MEMBLOCK_RESERVED_REGIONS; + memblock.reserved.total_size = 0; +} + +void reset_memblock_attributes(void) +{ + memblock.memory.name = "memory"; + memblock.reserved.name = "reserved"; + memblock.bottom_up = false; + memblock.current_limit = MEMBLOCK_ALLOC_ANYWHERE; +} + +static inline void fill_memblock(void) +{ + memset(memory_block.base, 1, MEM_SIZE); +} + +void setup_memblock(void) +{ + reset_memblock_regions(); + memblock_add((phys_addr_t)memory_block.base, MEM_SIZE); + fill_memblock(); +} + +/** + * setup_numa_memblock: + * Set up a memory layout with multiple NUMA nodes in a previously allocated + * dummy physical memory. + * @node_fracs: an array representing the fraction of MEM_SIZE contained in + * each node in basis point units (one hundredth of 1% or 1/10000). + * For example, if node 0 should contain 1/8 of MEM_SIZE, + * node_fracs[0] = 1250. + * + * The nids will be set to 0 through NUMA_NODES - 1. + */ +void setup_numa_memblock(const unsigned int node_fracs[]) +{ + phys_addr_t base; + int flags; + + reset_memblock_regions(); + base = (phys_addr_t)memory_block.base; + flags = (movable_node_is_enabled()) ? MEMBLOCK_NONE : MEMBLOCK_HOTPLUG; + + for (int i = 0; i < NUMA_NODES; i++) { + assert(node_fracs[i] <= BASIS); + phys_addr_t size = MEM_SIZE * node_fracs[i] / BASIS; + + memblock_add_node(base, size, i, flags); + base += size; + } + fill_memblock(); +} + +void dummy_physical_memory_init(void) +{ + memory_block.base = malloc(MEM_SIZE); + assert(memory_block.base); + fill_memblock(); +} + +void dummy_physical_memory_cleanup(void) +{ + free(memory_block.base); +} + +static void usage(const char *prog) +{ + BUILD_BUG_ON(ARRAY_SIZE(help_opts) != ARRAY_SIZE(long_opts) - 1); + + printf("Usage: %s [-%s]\n", prog, short_opts); + + for (int i = 0; long_opts[i].name; i++) { + printf(" -%c, --%-12s\t%s\n", long_opts[i].val, + long_opts[i].name, help_opts[i]); + } + + exit(1); +} + +void parse_args(int argc, char **argv) +{ + int c; + + while ((c = getopt_long_only(argc, argv, short_opts, long_opts, + NULL)) != -1) { + switch (c) { + case 'm': + movable_node_enabled = true; + break; + case 'v': + verbose = 1; + break; + default: + usage(argv[0]); + } + } +} + +void print_prefixes(const char *postfix) +{ + for (int i = 0; i < nr_prefixes; i++) + test_print("%s%s", prefixes[i], DELIM); + test_print(postfix); +} + +void test_fail(void) +{ + if (verbose) { + ksft_test_result_fail(": "); + print_prefixes("failed\n"); + } +} + +void test_pass(void) +{ + if (verbose) { + ksft_test_result_pass(": "); + print_prefixes("passed\n"); + } +} + +void test_print(const char *fmt, ...) +{ + if (verbose) { + int saved_errno = errno; + va_list args; + + va_start(args, fmt); + errno = saved_errno; + vprintf(fmt, args); + va_end(args); + } +} + +void prefix_reset(void) +{ + memset(prefixes, 0, PREFIXES_MAX * sizeof(char *)); + nr_prefixes = 0; +} + +void prefix_push(const char *prefix) +{ + assert(nr_prefixes < PREFIXES_MAX); + prefixes[nr_prefixes] = prefix; + nr_prefixes++; +} + +void prefix_pop(void) +{ + if (nr_prefixes > 0) { + prefixes[nr_prefixes - 1] = 0; + nr_prefixes--; + } +} diff --git a/tools/testing/memblock/tests/common.h b/tools/testing/memblock/tests/common.h new file mode 100644 index 000000000..4c33ce04c --- /dev/null +++ b/tools/testing/memblock/tests/common.h @@ -0,0 +1,167 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +#ifndef _MEMBLOCK_TEST_H +#define _MEMBLOCK_TEST_H + +#include <stdlib.h> +#include <assert.h> +#include <linux/types.h> +#include <linux/seq_file.h> +#include <linux/memblock.h> +#include <linux/sizes.h> +#include <linux/printk.h> +#include <../selftests/kselftest.h> + +#define MEM_SIZE SZ_16K +#define NUMA_NODES 8 + +enum test_flags { + /* No special request. */ + TEST_F_NONE = 0x0, + /* Perform raw allocations (no zeroing of memory). */ + TEST_F_RAW = 0x1, +}; + +/** + * ASSERT_EQ(): + * Check the condition + * @_expected == @_seen + * If false, print failed test message (if running with --verbose) and then + * assert. + */ +#define ASSERT_EQ(_expected, _seen) do { \ + if ((_expected) != (_seen)) \ + test_fail(); \ + assert((_expected) == (_seen)); \ +} while (0) + +/** + * ASSERT_NE(): + * Check the condition + * @_expected != @_seen + * If false, print failed test message (if running with --verbose) and then + * assert. + */ +#define ASSERT_NE(_expected, _seen) do { \ + if ((_expected) == (_seen)) \ + test_fail(); \ + assert((_expected) != (_seen)); \ +} while (0) + +/** + * ASSERT_LT(): + * Check the condition + * @_expected < @_seen + * If false, print failed test message (if running with --verbose) and then + * assert. + */ +#define ASSERT_LT(_expected, _seen) do { \ + if ((_expected) >= (_seen)) \ + test_fail(); \ + assert((_expected) < (_seen)); \ +} while (0) + +/** + * ASSERT_LE(): + * Check the condition + * @_expected <= @_seen + * If false, print failed test message (if running with --verbose) and then + * assert. + */ +#define ASSERT_LE(_expected, _seen) do { \ + if ((_expected) > (_seen)) \ + test_fail(); \ + assert((_expected) <= (_seen)); \ +} while (0) + +/** + * ASSERT_MEM_EQ(): + * Check that the first @_size bytes of @_seen are all equal to @_expected. + * If false, print failed test message (if running with --verbose) and then + * assert. + */ +#define ASSERT_MEM_EQ(_seen, _expected, _size) do { \ + for (int _i = 0; _i < (_size); _i++) { \ + ASSERT_EQ(((char *)_seen)[_i], (_expected)); \ + } \ +} while (0) + +/** + * ASSERT_MEM_NE(): + * Check that none of the first @_size bytes of @_seen are equal to @_expected. + * If false, print failed test message (if running with --verbose) and then + * assert. + */ +#define ASSERT_MEM_NE(_seen, _expected, _size) do { \ + for (int _i = 0; _i < (_size); _i++) { \ + ASSERT_NE(((char *)_seen)[_i], (_expected)); \ + } \ +} while (0) + +#define PREFIX_PUSH() prefix_push(__func__) + +/* + * Available memory registered with memblock needs to be valid for allocs + * test to run. This is a convenience wrapper for memory allocated in + * dummy_physical_memory_init() that is later registered with memblock + * in setup_memblock(). + */ +struct test_memory { + void *base; +}; + +struct region { + phys_addr_t base; + phys_addr_t size; +}; + +static inline phys_addr_t __maybe_unused region_end(struct memblock_region *rgn) +{ + return rgn->base + rgn->size; +} + +void reset_memblock_regions(void); +void reset_memblock_attributes(void); +void setup_memblock(void); +void setup_numa_memblock(const unsigned int node_fracs[]); +void dummy_physical_memory_init(void); +void dummy_physical_memory_cleanup(void); +void parse_args(int argc, char **argv); + +void test_fail(void); +void test_pass(void); +void test_print(const char *fmt, ...); +void prefix_reset(void); +void prefix_push(const char *prefix); +void prefix_pop(void); + +static inline void test_pass_pop(void) +{ + test_pass(); + prefix_pop(); +} + +static inline void run_top_down(int (*func)()) +{ + memblock_set_bottom_up(false); + prefix_push("top-down"); + func(); + prefix_pop(); +} + +static inline void run_bottom_up(int (*func)()) +{ + memblock_set_bottom_up(true); + prefix_push("bottom-up"); + func(); + prefix_pop(); +} + +static inline void assert_mem_content(void *mem, int size, int flags) +{ + if (flags & TEST_F_RAW) + ASSERT_MEM_NE(mem, 0, size); + else + ASSERT_MEM_EQ(mem, 0, size); +} + +#endif |