Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,5 @@
main
memblock.c
linux/memblock.h
asm/asm.h
asm/cmpxchg.h
@@ -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 tests/alloc_exact_nid_api.o
DEP_OFILES = memblock.o lib/slab.o mmzone.o slab.o cmdline.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
+115
View File
@@ -0,0 +1,115 @@
==================
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. 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
+5
View File
@@ -0,0 +1,5 @@
TODO
=====
1. Add tests for memblock_alloc_low() once the simulator can model
ARCH_LOW_ADDRESS_LIMIT against the low memory in memory_block
@@ -0,0 +1,5 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _TOOLS_DMA_H
#define _TOOLS_DMA_H
#endif
@@ -0,0 +1,69 @@
/* 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__)
#define K(x) ((x) << (PAGE_SHIFT-10))
bool mirrored_kernelcore = false;
struct page {};
static inline void *page_address(struct page *page)
{
BUG();
return page;
}
static inline struct page *virt_to_page(void *virt)
{
BUG();
return virt;
}
void memblock_free_pages(unsigned long pfn, unsigned int order)
{
}
static inline void accept_memory(phys_addr_t start, unsigned long size)
{
}
unsigned long free_reserved_area(void *start, void *end, int poison, const char *s);
void free_reserved_page(struct page *page);
static inline bool deferred_pages_enabled(void)
{
return false;
}
#define for_each_valid_pfn(pfn, start_pfn, end_pfn) \
for ((pfn) = (start_pfn); (pfn) < (end_pfn); (pfn)++)
static inline void *kasan_reset_tag(const void *addr)
{
return (void *)addr;
}
static inline bool __is_kernel(unsigned long addr)
{
return false;
}
#define for_each_valid_pfn(pfn, start_pfn, end_pfn) \
for ((pfn) = (start_pfn); (pfn) < (end_pfn); (pfn)++)
static inline void init_deferred_page(unsigned long pfn, int nid)
{
}
#define __SetPageReserved(p) ((void)(p))
#endif
@@ -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;
}
@@ -0,0 +1,14 @@
/* 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>
#include <linux/string.h>
#include <linux/ctype.h>
#endif
@@ -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
@@ -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
@@ -0,0 +1,42 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _TOOLS_MMZONE_H
#define _TOOLS_MMZONE_H
#include <linux/atomic.h>
#include <linux/memory_hotplug.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_PAGE_ORDER 10
#define MAX_ORDER_NR_PAGES (1 << MAX_PAGE_ORDER)
#define pageblock_order MAX_PAGE_ORDER
#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;
enum migratetype {
MIGRATE_CMA,
};
#endif
@@ -0,0 +1,14 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _MUTEX_H
#define _MUTEX_H
#define DEFINE_MUTEX(name) int name
static inline void dummy_mutex_guard(int *name)
{
}
#define guard(mutex) \
dummy_##mutex##_guard
#endif /* _MUTEX_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
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _LINUX_STRING_HELPERS_H_
#define _LINUX_STRING_HELPERS_H_
/*
* Header stub to avoid test build breakage; we don't need to
* actually implement string_get_size() as it's not used in the tests.
*/
#endif
+19
View File
@@ -0,0 +1,19 @@
// 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/alloc_exact_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();
memblock_alloc_exact_nid_checks();
return 0;
}
@@ -0,0 +1,16 @@
// 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 atomic_long_set(atomic_long_t *v, long i)
{
}
@@ -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
@@ -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_kern(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_kern(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_kern(r1.base, r1.size);
memblock_reserve_kern(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_kern(r1.base, r1.size);
memblock_reserve_kern(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_kern(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_kern(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_kern(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_kern(r1.base, r1.size);
memblock_reserve_kern(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;
}
@@ -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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,25 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#ifndef _MEMBLOCK_ALLOC_EXACT_NID_H
#define _MEMBLOCK_ALLOC_EXACT_NID_H
#include "common.h"
int memblock_alloc_exact_nid_checks(void);
int __memblock_alloc_exact_nid_numa_checks(void);
#ifdef CONFIG_NUMA
static inline int memblock_alloc_exact_nid_numa_checks(void)
{
__memblock_alloc_exact_nid_numa_checks();
return 0;
}
#else
static inline int memblock_alloc_exact_nid_numa_checks(void)
{
return 0;
}
#endif /* CONFIG_NUMA */
#endif
@@ -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_kern(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_kern(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;
}
@@ -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
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,26 @@
/* 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_exact_nid_range_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
File diff suppressed because it is too large Load Diff
@@ -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
@@ -0,0 +1,209 @@
// 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 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 = 0;
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 = 0;
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, PHYS_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(PHYS_MEM_SIZE);
assert(memory_block.base);
fill_memblock();
}
void dummy_physical_memory_cleanup(void)
{
free(memory_block.base);
}
phys_addr_t dummy_physical_memory_base(void)
{
return (phys_addr_t)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--;
}
}
@@ -0,0 +1,177 @@
/* 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_32K
#define PHYS_MEM_SIZE SZ_16M
#define NUMA_NODES 8
#define INIT_MEMBLOCK_REGIONS 128
#define INIT_MEMBLOCK_RESERVED_REGIONS INIT_MEMBLOCK_REGIONS
enum test_flags {
/* No special request. */
TEST_F_NONE = 0x0,
/* Perform raw allocations (no zeroing of memory). */
TEST_F_RAW = 0x1,
/* Perform allocations on the exact node specified. */
TEST_F_EXACT = 0x2
};
/**
* 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)
#define ASSERT_TRUE(_seen) ASSERT_EQ(true, _seen)
#define ASSERT_FALSE(_seen) ASSERT_EQ(false, _seen)
/**
* 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);
phys_addr_t dummy_physical_memory_base(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