blob: c4ffbf9ac19166af3df8ebd46fd8da7f058ef2c6 [file] [log] [blame]
/*
* Copyright (c) 2019-2020, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <arch.h>
#include <arch_helpers.h>
#include <debug.h>
#include <errno.h>
#include <platform_def.h>
#include <stdlib.h>
#include <string.h>
#include <tftf_lib.h>
#include <xlat_tables_v2.h>
/*
* NOTE: In order to make the tests as generic as possible, the tests don't
* actually access the mapped memory, the instruction AT is used to verify that
* the mapping is correct. It is likely that the memory that ends up being
* mapped isn't physically there, so the memory is mapped as device memory to
* prevent the CPU from speculatively reading from it.
*
* Also, it is very likely that a failure in any of the tests would leave the
* translation tables in a state from which the system can't be recovered. This
* is why in some cases the tests don't try to unmap regions that have been
* successfully mapped.
*/
#define STRESS_TEST_ITERATIONS 1000
#define SIZE_L1 XLAT_BLOCK_SIZE(1)
#define SIZE_L2 XLAT_BLOCK_SIZE(2)
#define SIZE_L3 XLAT_BLOCK_SIZE(3) /* PAGE_SIZE */
#define MASK_L1 XLAT_BLOCK_MASK(1)
#define MASK_L2 XLAT_BLOCK_MASK(2)
#define MASK_L3 XLAT_BLOCK_MASK(3)
static const struct {
size_t size;
size_t expected_va_mask;
} mem_tests[] = {
{ SIZE_L1 + 2 * SIZE_L2 + 2 * SIZE_L3, MASK_L3 },
{ SIZE_L1 + SIZE_L2 + SIZE_L3, MASK_L3 },
{ SIZE_L1 + 2 * SIZE_L2, MASK_L2 },
{ SIZE_L1 + SIZE_L2, MASK_L2 },
{ SIZE_L1 + 2 * SIZE_L3, MASK_L3 },
{ SIZE_L1 + SIZE_L3, MASK_L3 },
{ SIZE_L1, MASK_L1 },
{ SIZE_L2 + 2 * SIZE_L3, MASK_L3 },
{ SIZE_L2 + SIZE_L3, MASK_L3 },
{ SIZE_L2, MASK_L2 },
{ SIZE_L3, MASK_L3 }
};
/*
* Translate the given virtual address into a physical address in the current
* translation regime. Returns the resulting physical address on success,
* otherwise UINT64_MAX.
*/
static unsigned long long va2pa(uintptr_t base_va)
{
uint64_t par;
/*
* Performs stage 1 address translation for the current EL, with
* read permissions.
*/
#ifndef __aarch64__
if (IS_IN_HYP())
write_ats1hr(base_va);
else
write_ats1cpr(base_va);
isb();
par = read64_par();
#else
if (IS_IN_EL2())
ats1e2r(base_va);
else
ats1e1r(base_va);
isb();
par = read_par_el1();
#endif
/*
* If PAR_EL1.F == 1 then the address translation was aborted.
* In this case, return an invalid VA.
*/
if (par & PAR_F_MASK)
return UINT64_MAX;
/*
* If PAR_EL1.F == 0 then the address translation completed
* successfully. In this case, bits 47-12 hold bits 47-12 of the
* resulting physical address.
*/
return par & (PAR_ADDR_MASK << PAR_ADDR_SHIFT);
}
/*
* Checks that the given region has been mapped correctly. Returns 0 on success,
* 1 otherwise.
*/
static int verify_region_mapped(unsigned long long base_pa, uintptr_t base_va,
size_t size)
{
uintptr_t end_va = base_va + size;
unsigned long long addr;
VERBOSE("Checking: PA = 0x%llx, VA = 0x%lx, size = 0x%zx\n",
base_pa, base_va, size);
while (base_va < end_va) {
addr = va2pa(base_va);
if (base_pa != addr) {
ERROR("Error: 0x%lx => 0x%llx (expected 0x%llx)\n",
base_va, addr, base_pa);
return 1;
}
base_va += PAGE_SIZE;
base_pa += PAGE_SIZE;
}
return 0;
}
/*
* Checks that the given region has been unmapped correctly. Returns 0 on
* success, 1 otherwise.
*/
static int verify_region_unmapped(uintptr_t base_va, size_t size)
{
uintptr_t end_va = base_va + size;
VERBOSE("Checking: VA = 0x%lx, size = 0x%zx\n", base_va, size);
while (base_va < end_va) {
unsigned long long phys_addr = va2pa(base_va);
if (phys_addr != UINT64_MAX) {
ERROR("Error: 0x%lx => 0x%llx (expected UINT64_MAX)\n",
base_va, phys_addr);
return 1;
}
base_va += PAGE_SIZE;
}
return 0;
}
/*
* Ask to map a given region of physical memory with a given set of memory
* attributes. Returns 0 on success, otherwise an error code returned by
* mmap_add_dynamic_region(). On success, it also verifies that the mapping has
* been done correctly.
*/
static int add_region(unsigned long long base_pa, uintptr_t base_va,
size_t size, unsigned int attr)
{
int ret;
if (size == 0U) {
return -EPERM;
}
VERBOSE("mmap_add_dynamic_region(0x%llx, 0x%lx, 0x%zx, 0x%x)\n",
base_pa, base_va, size, attr);
ret = mmap_add_dynamic_region(base_pa, base_va, size, attr);
VERBOSE(" = %d\n", ret);
if (ret == 0) {
return verify_region_mapped(base_pa, base_va, size);
}
return ret;
}
/*
* Ask to map a given region of physical memory with a given set of memory
* attributes. On success it returns a VA with the VA allocated for the new
* region and 0 as error code. Otherwise, the error code returned by
* mmap_add_dynamic_region_alloc_va().
*/
static int add_region_alloc_va(unsigned long long base_pa, uintptr_t *base_va,
size_t size, unsigned int attr)
{
int ret;
VERBOSE("mmap_add_dynamic_region_alloc_va(0x%llx, 0x%zx, 0x%x)\n",
base_pa, size, attr);
ret = mmap_add_dynamic_region_alloc_va(base_pa, base_va, size, attr);
VERBOSE(" = %d VA=0x%lx\n", ret, *base_va);
if (ret == 0) {
return verify_region_mapped(base_pa, *base_va, size);
}
return ret;
}
/*
* Unmap a given memory region given its virtual address and size.
*/
static int remove_region(uintptr_t base_va, size_t size)
{
int ret;
VERBOSE("mmap_remove_dynamic_region(0x%lx, 0x%zx)\n", base_va, size);
ret = mmap_remove_dynamic_region(base_va, size);
VERBOSE(" = %d\n", ret);
if (ret == 0) {
return verify_region_unmapped(base_va, size);
}
return ret;
}
/*
* Number of individual chunks of memory that can be mapped and unmaped in the
* region that we use for testing. The size of each block is total_size /
* num_blocks. The test tries to allocate as much memory as possible.
*/
#define STRESS_TEST_NUM_BLOCKS 1024
/* Memory region to be used by the stress test */
static uintptr_t memory_base_va;
static size_t memory_size;
/* Block size to be used by the stress test */
static size_t block_size;
/*
* Each element of the array can have one of the following states:
* - 0: Free
* - 1: Used
* - 2: Used, and it is the start of a region
*/
static int block_used[STRESS_TEST_NUM_BLOCKS];
/* Returns -1 if error, 1 if chunk added, 0 if not added */
static int alloc_random_chunk(void)
{
int rc;
int start = rand() % STRESS_TEST_NUM_BLOCKS;
int blocks = rand() % STRESS_TEST_NUM_BLOCKS;
bool is_free = true;
if (start + blocks > STRESS_TEST_NUM_BLOCKS) {
blocks = STRESS_TEST_NUM_BLOCKS - start;
}
/* Check if it's free */
for (int i = start; i < start + blocks; i++) {
if (block_used[i] != 0) {
is_free = false;
break;
}
}
uintptr_t base_va = memory_base_va + start * block_size;
unsigned long long base_pa = base_va;
size_t size = blocks * block_size;
if (is_free) {
/*
* Allocate a region, it should succeed. Use a non 1:1 mapping
* by adding an arbitrary offset to the base PA.
*/
rc = add_region(base_pa + 0x20000U, base_va, size, MT_DEVICE);
if ((rc == -ENOMEM) || (rc == -EPERM)) {
/*
* Not enough memory or partial overlap, don't consider
* this a hard failure.
*/
return 0;
} else if (rc != 0) {
tftf_testcase_printf("%d: add_region failed: %d\n",
__LINE__, rc);
return -1;
}
/* Flag as used */
block_used[start] = 2;
for (int i = start + 1; i < start + blocks; i++)
block_used[i] = 1;
return 1;
} else {
/* Allocate, it should fail */
rc = add_region(base_pa, base_va, size, MT_DEVICE);
if (rc == 0) {
tftf_testcase_printf("%d: add_region succeeded\n",
__LINE__);
return -1;
}
return 0;
}
}
/* Returns -1 if error, 1 if chunk removed, 0 if not removed */
static int free_random_chunk(void)
{
int start = -1;
int end = -1;
int seek = rand() % STRESS_TEST_NUM_BLOCKS;
int i = seek;
for (;;) {
if (start == -1) { /* Look for the start of a block */
/* Search the start of a chunk */
if (block_used[i] == 2) {
start = i;
}
} else { /* Look for the end of the block */
/* Search free space or the start of another chunk */
if (block_used[i] != 1) {
end = i;
break;
}
}
i++;
if (start == -1) { /* Look for the start of a block */
/* Looking for the start of a block so wrap around */
if (i == STRESS_TEST_NUM_BLOCKS) {
i = 0;
}
} else { /* Look for the end of the block */
/* If the end of the region is reached, this must be
* the end of the chunk as well.*/
if (i == STRESS_TEST_NUM_BLOCKS) {
end = i;
break;
}
}
/* Back to the starting point of the search: no chunk found */
if (i == seek) {
break;
}
}
/* No chunks found */
if ((start == -1) || (end == -1)) {
return 0;
}
int blocks = end - start;
bool is_correct_size = true;
if ((rand() % 5) == 0) { /* Make it fail sometimes */
blocks++;
is_correct_size = false;
}
uintptr_t base_va = memory_base_va + start * block_size;
size_t size = blocks * block_size;
if (is_correct_size) {
/* Remove, it should succeed */
int rc = remove_region(base_va, size);
if (rc != 0) {
tftf_testcase_printf("%d: remove_region failed: %d\n",
__LINE__, rc);
return 1;
}
/* Flag as unused */
for (int i = start; i < start + blocks; i++) {
block_used[i] = 0;
}
return 1;
} else {
/* Remove, it should fail */
int rc = remove_region(base_va, size);
if (rc == 0) {
tftf_testcase_printf("%d: remove_region succeeded\n",
__LINE__);
return 1;
}
return 0;
}
}
/* Returns number of allocated chunks */
static int get_num_chunks(void)
{
int count = 0;
for (int i = 0; i < STRESS_TEST_NUM_BLOCKS; i++) {
if (block_used[i] == 2) {
count++;
}
}
return count;
}
/**
* @Test_Aim@ Perform dynamic translation tables API basic tests
*
* This test checks for invalid uses of the dynamic translation tables library.
*/
test_result_t xlat_lib_v2_basic_test(void)
{
uintptr_t memory_base_va;
int rc, i;
/*
* 1) Try to allocate a region with size 0.
*
* The allocation should "succeed" but not allocate anything, and it
* still should return the top VA.
*/
rc = add_region_alloc_va(0, &memory_base_va, 0, 0);
if (rc != 0) {
tftf_testcase_printf("%d: add_region_alloc_va: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
/*
* Try do deallocate this region. It should fail because it hasn't been
* allocated in the first place.
*/
rc = remove_region(memory_base_va, 0);
if (rc != -EINVAL) {
if (rc == 0) {
tftf_testcase_printf("%d: Deallocation should have failed.\n",
__LINE__);
} else {
tftf_testcase_printf("%d: remove_region: %d\n",
__LINE__, rc);
}
return TEST_RESULT_FAIL;
}
/* 2) Allocate and deallocate a small region */
rc = add_region_alloc_va(0, &memory_base_va, SIZE_L3, MT_DEVICE);
if (rc != 0) {
tftf_testcase_printf("%d: add_region_alloc_va: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
rc = remove_region(memory_base_va, SIZE_L3);
if (rc != 0) {
tftf_testcase_printf("%d: remove_region: %d\n", __LINE__, rc);
return TEST_RESULT_FAIL;
}
/*
* 3) Try to allocate the last page of the virtual address space, which
* can lead to wraparound problems (specially in AArch32).
*/
rc = add_region(PLAT_VIRT_ADDR_SPACE_SIZE - PAGE_SIZE,
PLAT_VIRT_ADDR_SPACE_SIZE - PAGE_SIZE,
PAGE_SIZE, MT_DEVICE);
if (rc != 0) {
tftf_testcase_printf("%d: add_region: %d\n", __LINE__, rc);
return TEST_RESULT_FAIL;
}
rc = remove_region(PLAT_VIRT_ADDR_SPACE_SIZE - PAGE_SIZE, PAGE_SIZE);
if (rc != 0) {
tftf_testcase_printf("%d: remove_region: %d\n", __LINE__, rc);
return TEST_RESULT_FAIL;
}
/*
* 4) Try to allocate an invalid region. It should fail, but it will
* return the address of memory that can be used for the following
* tests.
*/
rc = add_region_alloc_va(0, &memory_base_va, SIZE_MAX, MT_DEVICE);
if (rc == 0) {
tftf_testcase_printf("%d: add_region_alloc_va() didn't fail\n",
__LINE__);
return TEST_RESULT_FAIL;
}
#ifdef __aarch64__
unsigned long long memory_base_pa;
/*
* Get address of memory region over the max used VA that is aligned to
* a L1 block for the next tests.
*/
memory_base_pa = (memory_base_va + SIZE_L1 - 1ULL) & ~MASK_L1;
INFO("Using 0x%llx as base address for tests.\n", memory_base_pa);
/*
* 5) Try to allocate memory over the virtual address space limit. This
* test can't run in AArch32 because size_t is 32-bit wide, and the
* address space used by the TFTF is also 32-bit wide, so it is not
* possible to go over the limit.
*/
rc = add_region(memory_base_pa, memory_base_va,
PLAT_VIRT_ADDR_SPACE_SIZE + PAGE_SIZE - memory_base_pa,
MT_DEVICE);
if (rc != -ERANGE) {
tftf_testcase_printf("%d: Allocation succeeded: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
/* Try to wrap around 64 bit */
rc = add_region(1ULL << 32, 1ULL << 32,
UINT64_MAX - PAGE_SIZE + 1ULL,
MT_DEVICE);
if (rc != -ERANGE) {
tftf_testcase_printf("%d: Allocation succeeded: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
#else
/* Try to wrap around 32 bit */
rc = add_region((1ULL << 32) - PAGE_SIZE, (1ULL << 32) - PAGE_SIZE,
2 * PAGE_SIZE, MT_DEVICE);
if (rc != -ERANGE) {
tftf_testcase_printf("%d: Allocation succeeded: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
#endif
/*
* 6) Try to allocate too many regions. There is only room for at most
* MAX_MMAP_REGIONS, and some of the regions are already used for
* devices, code, BSS, etc. Trying to allocate MAX_MMAP_REGIONS here
* should fail.
*/
for (i = 0; i < MAX_MMAP_REGIONS; i++) {
uintptr_t addr = memory_base_va + PAGE_SIZE * i;
rc = add_region(addr, addr, PAGE_SIZE, MT_DEVICE);
if (rc == -ENOMEM) {
/* The limit has been reached as expected */
break;
} else if (rc != 0) {
tftf_testcase_printf("%d: add_region: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
}
if (i == MAX_MMAP_REGIONS) {
tftf_testcase_printf("%d: Too many regions allocated\n",
__LINE__);
return TEST_RESULT_FAIL;
}
i--;
/* Cleanup */
for (; i >= 0; i--) {
uintptr_t addr = memory_base_va + PAGE_SIZE * i;
rc = remove_region(addr, PAGE_SIZE);
if (rc != 0) {
tftf_testcase_printf("%d: remove_region: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
}
return TEST_RESULT_SUCCESS;
}
/**
* @Test_Aim@ Perform dynamic translation tables API alignment tests
*
* This test makes sure that the alloc VA APIs return addresses aligned as
* expected.
*/
test_result_t xlat_lib_v2_alignment_test(void)
{
uintptr_t memory_base_va;
unsigned long long memory_base_pa;
int rc, i;
/*
* 1) Try to allocate an invalid region. It should fail, but it will
* return the address of memory that can be used for the following
* tests.
*/
rc = add_region_alloc_va(0, &memory_base_va, SIZE_MAX, MT_DEVICE);
if (rc == 0) {
tftf_testcase_printf("%d: add_region_alloc_va() didn't fail\n",
__LINE__);
return TEST_RESULT_FAIL;
}
/*
* Get address of memory region over the max used VA that is aligned to
* a L1 block for the next tests.
*/
memory_base_pa = (memory_base_va + SIZE_L1 - 1ULL) & ~MASK_L1;
INFO("Using 0x%llx as base address for tests.\n", memory_base_pa);
/*
* 2) Try to allocate regions that have an unaligned base VA or PA, or
* a size that isn't multiple of PAGE_SIZE.
*/
rc = add_region(memory_base_va + 1, memory_base_va, PAGE_SIZE, MT_DEVICE);
if (rc != -EINVAL) {
tftf_testcase_printf("%d: add_region: %d\n", __LINE__, rc);
return TEST_RESULT_FAIL;
}
rc = add_region(memory_base_va, memory_base_va + 1, PAGE_SIZE, MT_DEVICE);
if (rc != -EINVAL) {
tftf_testcase_printf("%d: add_region: %d\n", __LINE__, rc);
return TEST_RESULT_FAIL;
}
rc = add_region(memory_base_va, memory_base_va, PAGE_SIZE + 1, MT_DEVICE);
if (rc != -EINVAL) {
tftf_testcase_printf("%d: add_region: %d\n", __LINE__, rc);
return TEST_RESULT_FAIL;
}
#ifdef __aarch64__
/*
* 3) Try to allocate at least 1 GB aligned. There is only room for this
* in AArch64.
*/
rc = add_region_alloc_va(memory_base_pa, &memory_base_va, SIZE_L1,
MT_DEVICE);
if (rc == -ENOMEM) {
tftf_testcase_printf("%d: Not enough memory\n", __LINE__);
return TEST_RESULT_FAIL;
} else if (rc != 0) {
tftf_testcase_printf("%d: add_region_alloc_va: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
rc = remove_region(memory_base_va, SIZE_L1);
if (rc != 0) {
tftf_testcase_printf("%d: remove_region: %d\n", __LINE__, rc);
return TEST_RESULT_FAIL;
}
#else
/*
* 4) Try to allocate an absurdly large amount of misaligned memory,
* which should fail. In AArch64 there's enough memory to map 4GB of
* virtual memory so skip it.
*/
rc = add_region_alloc_va(memory_base_pa + PAGE_SIZE, &memory_base_va,
PLAT_VIRT_ADDR_SPACE_SIZE - (memory_base_pa + PAGE_SIZE),
MT_DEVICE);
if (rc != -ENOMEM) {
tftf_testcase_printf("%d: add_region_alloc_va: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
#endif
/*
* 5) Try to allocate hand-picked regions of different sizes and make
* sure that the resulting address is aligned to the correct boundary.
*/
for (i = 0; i < ARRAY_SIZE(mem_tests); i++) {
/* Allocate to a correct PA boundary. */
unsigned long long base_pa = memory_base_pa;
rc = add_region_alloc_va(base_pa, &memory_base_va,
mem_tests[i].size, MT_DEVICE);
if ((rc == -ENOMEM) || (rc == -ERANGE)) {
/*
* Skip regions that are too big for the address space.
* This is a problem specially in AArch32, when the max
* virtual address space width is 32 bit.
*/
WARN("%d: Not enough memory for case %d\n",
__LINE__, i);
continue;
} else if (rc != 0) {
tftf_testcase_printf("%d: add_region_alloc_va: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
rc = remove_region(memory_base_va, mem_tests[i].size);
if (rc != 0) {
tftf_testcase_printf("%d: remove_region: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
if (memory_base_va & mem_tests[i].expected_va_mask) {
tftf_testcase_printf("%d: Invalid alignment for case %d\n",
__LINE__, i);
return TEST_RESULT_FAIL;
}
/*
* Try to allocate to an incorrect PA boundary (a smaller one).
* This only makes sense for regions that are aligned to
* boundaries bigger than 4 KB, as there cannot be an incorrect
* alignment for 4 KB aligned regions.
*/
if (mem_tests[i].expected_va_mask != MASK_L3) {
base_pa = memory_base_pa;
if (mem_tests[i].expected_va_mask == MASK_L1) {
base_pa += SIZE_L2;
} else if (mem_tests[i].expected_va_mask == MASK_L2) {
base_pa += SIZE_L3;
}
rc = add_region_alloc_va(base_pa, &memory_base_va,
mem_tests[i].size, MT_DEVICE);
if (rc == 0) {
rc = remove_region(memory_base_va,
mem_tests[i].size);
if (rc != 0) {
tftf_testcase_printf("%d: remove_region: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
} else if ((rc != -ENOMEM) && (rc != -ERANGE)) {
/*
* It could happen that we run out of memory, so
* it doesn't make sense to fail because of
* that. However, any other error is a
* legitimate error.
*/
tftf_testcase_printf("%d: add_region_alloc_va: %d\n",
__LINE__, rc);
return TEST_RESULT_FAIL;
}
}
}
return TEST_RESULT_SUCCESS;
}
/**
* @Test_Aim@ Perform dynamic translation tables API stress tests
*
* This test performs a stress test in the library APIs.
*/
test_result_t xlat_lib_v2_stress_test(void)
{
test_result_t test_result = TEST_RESULT_SUCCESS;
uintptr_t memory_base;
int rc;
/*
* 1) Try to allocate an invalid region. It should fail, but it will
* return the address of memory that can be used for the following
* tests.
*/
rc = add_region_alloc_va(0, &memory_base, SIZE_MAX, MT_DEVICE);
if (rc == 0) {
tftf_testcase_printf("%d: add_region_alloc_va() didn't fail\n",
__LINE__);
return TEST_RESULT_FAIL;
}
/*
* Get address of memory region over the max used VA that is aligned to
* a L1 block for the next tests.
*/
memory_base = (memory_base + SIZE_L1 - 1UL) & ~MASK_L1;
INFO("Using 0x%lx as base address for tests.\n", memory_base);
/* 2) Get a region of memory that we can use for testing. */
/*
* Try with blocks 64 times the size of a page and reduce the size until
* it fits. PAGE_SIZE can only be 4, 16 or 64KB.
*/
block_size = PAGE_SIZE * 64;
for (;;) {
memory_size = block_size * STRESS_TEST_NUM_BLOCKS;
rc = add_region(memory_base, memory_base, memory_size,
MT_DEVICE);
if (rc == 0) {
break;
}
block_size >>= 1;
if (block_size < PAGE_SIZE) {
tftf_testcase_printf("%d: Couldn't allocate enough memory\n",
__LINE__);
return TEST_RESULT_FAIL;
}
}
rc = remove_region(memory_base, memory_size);
if (rc != 0) {
tftf_testcase_printf("%d: remove_region: %d\n", __LINE__, rc);
return TEST_RESULT_FAIL;
}
/* 3) Start stress test with the calculated top VA and space */
memset(block_used, 0, sizeof(block_used));
for (int i = 0; i < STRESS_TEST_ITERATIONS; i++) {
if ((rand() % 4) > 0) {
rc = alloc_random_chunk();
} else {
rc = free_random_chunk();
}
if (rc == -1) {
test_result = TEST_RESULT_FAIL;
break;
}
}
/* Cleanup of regions left allocated by the stress test */
while (get_num_chunks() > 0) {
rc = free_random_chunk();
if (rc == -1) {
test_result = TEST_RESULT_FAIL;
}
}
return test_result;
}