blob: a454dd363047ab4ad8961f42879a67330f0ed898 [file] [log] [blame]
/*
* Copyright (c) 2012-2014 Wind River Systems, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file test memory pool and heap APIs
*
* This modules tests the following memory pool routines:
*
* - k_mem_pool_alloc(),
* - k_mem_pool_free(),
* - k_malloc(),
* - k_free()
*/
#include <zephyr.h>
#include <tc_util.h>
#include <misc/util.h>
#define ONE_SECOND (sys_clock_ticks_per_sec)
#define TENTH_SECOND (sys_clock_ticks_per_sec / 10)
#define NUM_BLOCKS 64
#define DEFRAG_BLK_TEST 2222
/* size of stack area used by each thread */
#define STACKSIZE 512
K_SEM_DEFINE(ALTERNATE_SEM, 0, 1);
K_SEM_DEFINE(DEFRAG_SEM, 0, 1);
K_SEM_DEFINE(REGRESS_SEM, 0, 1);
K_SEM_DEFINE(HELPER_SEM, 0, 1);
K_MEM_POOL_DEFINE(POOL_ID, 64, 4096, 1, 4);
K_MEM_POOL_DEFINE(SECOND_POOL_ID, 16, 1024, 5, 4);
struct TEST_CASE {
struct k_mem_block *block; /* pointer to block data */
struct k_mem_pool *pool_id; /* pool ID */
int size; /* request size in bytes */
int32_t timeout; /* # of ticks to wait */
int rcode; /* expected return code */
};
typedef int (*pool_block_get_func_t)(struct k_mem_block *, struct k_mem_pool *,
int, int32_t);
typedef int (*pool_move_block_func_t)(struct k_mem_block *, struct k_mem_pool *);
static volatile int evidence;
static struct k_mem_block block_list[NUM_BLOCKS];
static struct k_mem_block helper_block;
static struct TEST_CASE get_set[] = {
{ &block_list[0], &POOL_ID, 0, 0, 0 },
{ &block_list[1], &POOL_ID, 1, 0, 0 },
{ &block_list[2], &POOL_ID, 32, 0, 0 },
{ &block_list[3], &POOL_ID, 64, 0, 0 },
{ &block_list[4], &POOL_ID, 128, 0, 0 },
{ &block_list[5], &POOL_ID, 256, 0, 0 },
{ &block_list[6], &POOL_ID, 512, 0, 0 },
{ &block_list[7], &POOL_ID, 1024, 0, 0 },
{ &block_list[8], &POOL_ID, 2048, 0, -ENOMEM },
{ &block_list[9], &POOL_ID, 4096, 0, -ENOMEM }
};
static struct TEST_CASE get_set2[] = {
{ &block_list[0], &POOL_ID, 4096, 0, 0 },
{ &block_list[1], &POOL_ID, 2048, 0, -ENOMEM },
{ &block_list[2], &POOL_ID, 1024, 0, -ENOMEM },
{ &block_list[3], &POOL_ID, 512, 0, -ENOMEM },
{ &block_list[4], &POOL_ID, 256, 0, -ENOMEM }
};
static struct TEST_CASE getwt_set[] = {
{ &block_list[0], &POOL_ID, 4096, TENTH_SECOND, 0 },
{ &block_list[1], &POOL_ID, 2048, TENTH_SECOND, -EAGAIN },
{ &block_list[2], &POOL_ID, 1024, TENTH_SECOND, -EAGAIN },
{ &block_list[3], &POOL_ID, 512, TENTH_SECOND, -EAGAIN },
{ &block_list[4], &POOL_ID, 256, TENTH_SECOND, -EAGAIN }
};
static struct TEST_CASE defrag[] = {
{ &block_list[0], &POOL_ID, 64, 0, 0 },
{ &block_list[1], &POOL_ID, 64, 0, 0 },
{ &block_list[2], &POOL_ID, 64, 0, 0 },
{ &block_list[3], &POOL_ID, 64, 0, 0 },
{ &block_list[4], &POOL_ID, 256, 0, 0 },
{ &block_list[5], &POOL_ID, 256, 0, 0 },
{ &block_list[6], &POOL_ID, 256, 0, 0 },
{ &block_list[7], &POOL_ID, 1024, 0, 0 },
{ &block_list[8], &POOL_ID, 1024, 0, 0 },
{ &block_list[9], &POOL_ID, 1024, 0, 0 }
};
/**
*
* @brief Compare the two blocks
*
* @return 0 if the same, non-zero if not the same
*/
int block_compare(struct k_mem_block *b1, struct k_mem_block *b2)
{
char *p1 = (char *) b1;
char *p2 = (char *) b2;
int i;
int diff = 0;
for (i = 0; i < sizeof(struct k_mem_block); i++) {
diff = p2[i] - p1[i];
if (diff != 0) {
break;
}
}
return diff;
}
/**
*
* @brief Wrapper for k_mem_pool_alloc()
*
* @return k_mem_pool_alloc() return value
*/
int pool_block_get_func(struct k_mem_block *block, struct k_mem_pool *pool,
int size, int32_t unused)
{
ARG_UNUSED(unused);
return k_mem_pool_alloc(pool, block, size, K_NO_WAIT);
}
/**
*
* @brief Wrapper for k_mem_pool_alloc(K_FOREVER)
*
* @return k_mem_pool_alloc() return value
*/
int pool_block_get_w_func(struct k_mem_block *block, struct k_mem_pool *pool,
int size, int32_t unused)
{
ARG_UNUSED(unused);
return k_mem_pool_alloc(pool, block, size, K_FOREVER);
}
/**
*
* @brief Wrapper for k_mem_pool_alloc(timeout)
*
* @return k_mem_pool_alloc(timeout) return value
*/
int pool_block_get_wt_func(struct k_mem_block *block, struct k_mem_pool *pool,
int size, int32_t timeout)
{
return k_mem_pool_alloc(pool, block, size, timeout);
}
/**
*
* @brief Free any blocks allocated in the test set
*
* @return N/A
*/
void free_blocks(struct TEST_CASE *tests, int n_tests)
{
int i;
for (i = 0; i < n_tests; i++) {
if (tests[i].rcode == 0) {
k_mem_pool_free(tests[i].block);
}
}
}
/**
*
* @brief Perform the work of getting blocks
*
* @return TC_PASS on success, TC_FAIL on failure
*/
int pool_block_get_work(char *string, pool_block_get_func_t func,
struct TEST_CASE *tests, int n_tests)
{
int i;
int rv;
for (i = 0; i < n_tests; i++) {
rv = func(tests[i].block, tests[i].pool_id, tests[i].size,
tests[i].timeout);
if (rv != tests[i].rcode) {
TC_ERROR("%s() expected %d, got %d\n"
"size: %d, timeout: %d\n", string, tests[i].rcode, rv,
tests[i].size, tests[i].timeout);
return TC_FAIL;
}
}
return TC_PASS;
}
/**
*
* @brief Test the k_mem_pool_alloc(K_NO_WAIT) API
*
* The pool is 4 k_b in size.
*
* @return TC_PASS on success, TC_FAIL on failure
*/
int pool_block_get_test(void)
{
int rv; /* return value from k_mem_pool_alloc() */
int j; /* loop counter */
for (j = 0; j < 8; j++) {
rv = pool_block_get_work("k_mem_pool_alloc", pool_block_get_func,
get_set, ARRAY_SIZE(get_set));
if (rv != TC_PASS) {
return TC_FAIL;
}
free_blocks(get_set, ARRAY_SIZE(get_set));
rv = pool_block_get_work("k_mem_pool_alloc", pool_block_get_func,
get_set2, ARRAY_SIZE(get_set2));
if (rv != TC_PASS) {
return TC_FAIL;
}
free_blocks(get_set2, ARRAY_SIZE(get_set2));
}
return TC_PASS;
}
/**
*
* @brief Helper task to pool_block_get_timeout_test()
*
* @return N/A
*/
void helper_task(void)
{
k_sem_take(&HELPER_SEM, K_FOREVER);
k_sem_give(&REGRESS_SEM);
k_mem_pool_free(&helper_block);
}
/**
*
* @brief Test k_mem_pool_alloc(timeout)
*
* @return TC_PASS on success, TC_FAIL on failure
*/
int pool_block_get_timeout_test(void)
{
struct k_mem_block block;
int rv; /* return value from k_mem_pool_alloc() */
int j; /* loop counter */
for (j = 0; j < 8; j++) {
rv = pool_block_get_work("k_mem_pool_alloc", pool_block_get_wt_func,
getwt_set, ARRAY_SIZE(getwt_set));
if (rv != TC_PASS) {
return TC_FAIL;
}
free_blocks(getwt_set, ARRAY_SIZE(getwt_set));
}
rv = k_mem_pool_alloc(&POOL_ID, &helper_block, 3148, 5);
if (rv != 0) {
TC_ERROR("Failed to get size 3148 byte block from POOL_ID\n");
return TC_FAIL;
}
rv = k_mem_pool_alloc(&POOL_ID, &block, 3148, K_NO_WAIT);
if (rv != -ENOMEM) {
TC_ERROR("Unexpectedly got size 3148 "
"byte block from POOL_ID\n");
return TC_FAIL;
}
k_sem_give(&HELPER_SEM); /* Activate helper_task */
rv = k_mem_pool_alloc(&POOL_ID, &block, 3148, 20);
if (rv != 0) {
TC_ERROR("Failed to get size 3148 byte block from POOL_ID\n");
return TC_FAIL;
}
rv = k_sem_take(&REGRESS_SEM, K_NO_WAIT);
if (rv != 0) {
TC_ERROR("Failed to get size 3148 "
"byte block within 20 ticks\n");
return TC_FAIL;
}
k_mem_pool_free(&block);
return TC_PASS;
}
/**
*
* pool_block_get_wait_test -
*
* @return TC_PASS on success, TC_FAIL on failure
*/
int pool_block_get_wait_test(void)
{
int rv;
rv = k_mem_pool_alloc(&POOL_ID, &block_list[0], 3000, K_FOREVER);
if (rv != 0) {
TC_ERROR("k_mem_pool_alloc(3000) expected %d, got %d\n", 0, rv);
return TC_FAIL;
}
k_sem_give(&ALTERNATE_SEM); /* Wake alternate_task */
evidence = 0;
rv = k_mem_pool_alloc(&POOL_ID, &block_list[1], 128, K_FOREVER);
if (rv != 0) {
TC_ERROR("k_mem_pool_alloc(128) expected %d, got %d\n", 0, rv);
return TC_FAIL;
}
switch (evidence) {
case 0:
TC_ERROR("k_mem_pool_alloc(128) did not block!\n");
return TC_FAIL;
case 1:
break;
case 2:
default:
TC_ERROR("Rescheduling did not occur "
"after k_mem_pool_free()\n");
return TC_FAIL;
}
k_mem_pool_free(&block_list[1]);
return TC_PASS;
}
/**
*
* @brief Task responsible for defragmenting the pool POOL_ID
*
* @return N/A
*/
void defrag_task(void)
{
k_sem_take(&DEFRAG_SEM, K_FOREVER); /* Wait to be activated */
k_mem_pool_defrag(&POOL_ID);
k_sem_give(&REGRESS_SEM); /* defrag_task is finished */
}
/**
*
* pool_defrag_test -
*
* @return TC_PASS on success, TC_FAIL on failure
*/
int pool_defrag_test(void)
{
int rv;
struct k_mem_block new_block;
/* Get a bunch of blocks */
rv = pool_block_get_work("k_mem_pool_alloc", pool_block_get_func,
defrag, ARRAY_SIZE(defrag));
if (rv != TC_PASS) {
return TC_FAIL;
}
k_sem_give(&DEFRAG_SEM); /* Activate defrag_task */
/*
* Block on getting another block from the pool.
* This will allow defrag_task to execute so that we can get some
* better code coverage. 500 ms is expected to more than sufficient
* time for defrag_task to finish.
*/
rv = k_mem_pool_alloc(&POOL_ID, &new_block, DEFRAG_BLK_TEST, 500);
if (rv != -EAGAIN) {
TC_ERROR("k_mem_pool_alloc() returned %d, not %d\n", rv,
-EAGAIN);
return TC_FAIL;
}
rv = k_sem_take(&REGRESS_SEM, K_NO_WAIT);
if (rv != 0) {
TC_ERROR("defrag_task did not finish in allotted time!\n");
return TC_FAIL;
}
/* Free the allocated blocks */
free_blocks(defrag, ARRAY_SIZE(defrag));
return TC_PASS;
}
/**
*
* @brief Alternate task in the test suite
*
* This routine runs at a lower priority than Regression_task().
*
* @return N/A
*/
void alternate_task(void)
{
k_sem_take(&ALTERNATE_SEM, K_FOREVER);
evidence = 1;
k_mem_pool_free(&block_list[0]);
evidence = 2;
}
/**
*
* @brief Test the k_malloc() and k_free() APIs
*
* The heap memory pool is 256 bytes in size, and thus has only 4 blocks
* of 64 bytes or a single block of 256 bytes. (Each block has a lesser
* amount of usable space, due to the hidden block descriptor info the
* kernel adds at the start of any block allocated from this memory pool.)
*
* @return TC_PASS on success, TC_FAIL on failure
*/
int pool_malloc_test(void)
{
char *block[4];
int j; /* loop counter */
TC_PRINT("Testing k_malloc() and k_free() ...\n");
/* allocate a large block (which consumes the entire pool buffer) */
block[0] = k_malloc(150);
if (block[0] == NULL) {
TC_ERROR("150 byte allocation failed\n");
return TC_FAIL;
}
/* ensure a small block can no longer be allocated */
block[1] = k_malloc(16);
if (block[1] != NULL) {
TC_ERROR("16 byte allocation did not fail\n");
return TC_FAIL;
}
/* return the large block */
k_free(block[0]);
/* allocate a small block (triggers block splitting)*/
block[0] = k_malloc(16);
if (block[0] == NULL) {
TC_ERROR("16 byte allocation 0 failed\n");
return TC_FAIL;
}
/* ensure a large block can no longer be allocated */
block[1] = k_malloc(80);
if (block[1] != NULL) {
TC_ERROR("80 byte allocation did not fail\n");
return TC_FAIL;
}
/* ensure all remaining small blocks can be allocated */
for (j = 1; j < 4; j++) {
block[j] = k_malloc(16);
if (block[j] == NULL) {
TC_ERROR("16 byte allocation %d failed\n", j);
return TC_FAIL;
}
}
/* ensure a small block can no longer be allocated */
if (k_malloc(8) != NULL) {
TC_ERROR("8 byte allocation did not fail\n");
return TC_FAIL;
}
/* return the small blocks to pool in a "random" order */
k_free(block[2]);
k_free(block[0]);
k_free(block[3]);
k_free(block[1]);
/* allocate large block (triggers autodefragmentation) */
block[0] = k_malloc(100);
if (block[0] == NULL) {
TC_ERROR("100 byte allocation failed\n");
return TC_FAIL;
}
/* ensure a small block can no longer be allocated */
if (k_malloc(32) != NULL) {
TC_ERROR("32 byte allocation did not fail\n");
return TC_FAIL;
}
return TC_PASS;
}
/**
*
* @brief Main task in the test suite
*
* This is the entry point to the memory pool test suite.
*
* @return N/A
*/
void main(void)
{
int tc_rC; /* test case return code */
TC_START("Test Memory Pool and Heap APIs");
TC_PRINT("Testing k_mem_pool_alloc(K_NO_WAIT) ...\n");
tc_rC = pool_block_get_test();
if (tc_rC != TC_PASS) {
goto done_tests;
}
TC_PRINT("Testing k_mem_pool_alloc(timeout) ...\n");
tc_rC = pool_block_get_timeout_test();
if (tc_rC != TC_PASS) {
goto done_tests;
}
TC_PRINT("Testing k_mem_pool_alloc(K_FOREVER) ...\n");
tc_rC = pool_block_get_wait_test();
if (tc_rC != TC_PASS) {
goto done_tests;
}
TC_PRINT("Testing k_mem_pool_defragment() ...\n");
tc_rC = pool_defrag_test();
if (tc_rC != TC_PASS) {
goto done_tests;
}
tc_rC = pool_malloc_test();
if (tc_rC != TC_PASS) {
goto done_tests;
}
done_tests:
TC_END_RESULT(tc_rC);
TC_END_REPORT(tc_rC);
}
K_THREAD_DEFINE(t_alternate, STACKSIZE, alternate_task, NULL, NULL, NULL,
6, 0, K_NO_WAIT);
K_THREAD_DEFINE(t_defrag, STACKSIZE, defrag_task, NULL, NULL, NULL,
7, 0, K_NO_WAIT);
K_THREAD_DEFINE(t_helper, STACKSIZE, helper_task, NULL, NULL, NULL,
7, 0, K_NO_WAIT);