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,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