llext: Rework hello_world test case to be "simple"
The simple test is there to test the API and simple extensions in unison. Hello world was intended to be the first not the only extension being tested. Also refactors the entry thread to allow for usermode potentially by passing the pointer to the function symbol rather than having it look it up directly. Signed-off-by: Tom Burdick <thomas.burdick@intel.com>
This commit is contained in:
parent
ad7086a2df
commit
0650a88bed
|
@ -51,12 +51,10 @@ set(LLEXT_REMOVE_FLAGS
|
|||
-fdata-sections
|
||||
-g.*
|
||||
-Os
|
||||
-mcpu=.*
|
||||
)
|
||||
|
||||
# Flags to be added to llext code compilation
|
||||
set(LLEXT_APPEND_FLAGS
|
||||
-mlong-calls
|
||||
-mthumb
|
||||
-mcpu=cortex-m33+nodsp
|
||||
)
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <ctype.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/printk.h>
|
||||
#include <zephyr/llext/symbol.h>
|
||||
|
||||
#define HEXDUMP_BYTES_IN_LINE 8U
|
||||
|
||||
|
@ -19,6 +20,7 @@ void z_log_minimal_printk(const char *fmt, ...)
|
|||
vprintk(fmt, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
EXPORT_SYMBOL(z_log_minimal_printk);
|
||||
|
||||
void z_log_minimal_vprintk(const char *fmt, va_list ap)
|
||||
{
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
# Copyright (c) 2023 Intel Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(llext_hello_world_test)
|
||||
|
||||
target_sources(app PRIVATE
|
||||
src/test/test_llext_simple.c
|
||||
)
|
||||
|
||||
target_include_directories(app PRIVATE
|
||||
${ZEPHYR_BASE}/include
|
||||
${ZEPHYR_BASE}/kernel/include
|
||||
${ZEPHYR_BASE}/arch/${ARCH}/include
|
||||
)
|
||||
|
||||
# Compile a simple hello world llext to an include file
|
||||
set(llext_src_file ${PROJECT_SOURCE_DIR}/src/llext/hello_world.c)
|
||||
set(llext_bin_file ${ZEPHYR_BINARY_DIR}/hello_world.llext)
|
||||
set(llext_inc_file ${ZEPHYR_BINARY_DIR}/include/generated/hello_world.inc)
|
||||
|
||||
add_llext_target(hello_world
|
||||
OUTPUT ${llext_bin_file}
|
||||
SOURCES ${llext_src_file}
|
||||
)
|
||||
|
||||
generate_inc_file_for_target(app ${llext_bin_file} ${llext_inc_file})
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/ztest.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/llext/llext.h>
|
||||
#include <zephyr/llext/buf_loader.h>
|
||||
|
||||
#if defined(CONFIG_ARM) /* ARMV7 */ || defined(CONFIG_XTENSA)
|
||||
#ifndef CONFIG_LLEXT_STORAGE_WRITABLE
|
||||
const
|
||||
#endif
|
||||
static uint8_t hello_world_elf[] __aligned(4) = {
|
||||
#include "hello_world.inc"
|
||||
};
|
||||
#endif
|
||||
|
||||
K_THREAD_STACK_DEFINE(llext_stack, 1024);
|
||||
struct k_thread llext_thread;
|
||||
|
||||
#ifdef CONFIG_USERSPACE
|
||||
void llext_entry(void *arg0, void *arg1, void *arg2)
|
||||
{
|
||||
struct llext *ext = arg0;
|
||||
|
||||
zassert_ok(llext_call_fn(ext, "hello_world"),
|
||||
"hello_world call should succeed");
|
||||
}
|
||||
#endif /* CONFIG_USERSPACE */
|
||||
|
||||
/**
|
||||
* Attempt to load, list, list symbols, call a fn, and unload a
|
||||
* hello world extension for each supported architecture
|
||||
*
|
||||
* This requires a single linked symbol (printk) and a single
|
||||
* exported symbol from the extension ( void hello_world(void))
|
||||
*/
|
||||
ZTEST(llext, test_llext_simple)
|
||||
{
|
||||
const char name[16] = "hello";
|
||||
struct llext_buf_loader buf_loader =
|
||||
LLEXT_BUF_LOADER(hello_world_elf, ARRAY_SIZE(hello_world_elf));
|
||||
struct llext_loader *loader = &buf_loader.loader;
|
||||
struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT;
|
||||
struct llext *ext = NULL;
|
||||
const void * const printk_fn = llext_find_sym(NULL, "printk");
|
||||
|
||||
zassert_equal(printk_fn, printk, "printk should be an exported symbol");
|
||||
|
||||
int res = llext_load(loader, name, &ext, &ldr_parm);
|
||||
|
||||
zassert_ok(res, "load should succeed");
|
||||
|
||||
const void * const hello_world_fn = llext_find_sym(&ext->exp_tab, "hello_world");
|
||||
|
||||
zassert_not_null(hello_world_fn, "hello_world should be an exported symbol");
|
||||
|
||||
#ifdef CONFIG_USERSPACE
|
||||
struct k_mem_domain domain;
|
||||
|
||||
k_mem_domain_init(&domain, 0, NULL);
|
||||
|
||||
res = llext_add_domain(ext, &domain);
|
||||
zassert_ok(res, "adding partitions to domain should succeed");
|
||||
|
||||
/* Should be runnable from newly created thread */
|
||||
k_thread_create(&llext_thread, llext_stack,
|
||||
K_THREAD_STACK_SIZEOF(llext_stack),
|
||||
&llext_entry, ext, NULL, NULL,
|
||||
1, 0, K_FOREVER);
|
||||
|
||||
k_mem_domain_add_thread(&domain, &llext_thread);
|
||||
|
||||
k_thread_start(&llext_thread);
|
||||
k_thread_join(&llext_thread, K_FOREVER);
|
||||
|
||||
#else /* CONFIG_USERSPACE */
|
||||
zassert_ok(llext_call_fn(ext, "hello_world"),
|
||||
"hello_world call should succeed");
|
||||
#endif /* CONFIG_USERSPACE */
|
||||
|
||||
llext_unload(&ext);
|
||||
}
|
||||
|
||||
ZTEST_SUITE(llext, NULL, NULL, NULL, NULL, NULL);
|
28
tests/subsys/llext/simple/CMakeLists.txt
Normal file
28
tests/subsys/llext/simple/CMakeLists.txt
Normal file
|
@ -0,0 +1,28 @@
|
|||
# Copyright (c) 2023 Intel Corporation.
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
cmake_minimum_required(VERSION 3.20.0)
|
||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(llext_simple_test)
|
||||
|
||||
target_sources(app PRIVATE
|
||||
src/test_llext_simple.c
|
||||
)
|
||||
|
||||
target_include_directories(app PRIVATE
|
||||
${ZEPHYR_BASE}/include
|
||||
${ZEPHYR_BASE}/kernel/include
|
||||
${ZEPHYR_BASE}/arch/${ARCH}/include
|
||||
)
|
||||
|
||||
# generate extension targets foreach extension given by name
|
||||
foreach(ext_name hello_world logging)
|
||||
set(ext_src ${PROJECT_SOURCE_DIR}/src/${ext_name}_ext.c)
|
||||
set(ext_bin ${ZEPHYR_BINARY_DIR}/${ext_name}.llext)
|
||||
set(ext_inc ${ZEPHYR_BINARY_DIR}/include/generated/${ext_name}.inc)
|
||||
add_llext_target(${ext_name}_ext
|
||||
OUTPUT ${ext_bin}
|
||||
SOURCES ${ext_src}
|
||||
)
|
||||
generate_inc_file_for_target(app ${ext_bin} ${ext_inc})
|
||||
endforeach()
|
|
@ -1,6 +1,6 @@
|
|||
CONFIG_ZTEST=y
|
||||
CONFIG_ZTEST_STACK_SIZE=8192
|
||||
CONFIG_ZTEST_STACK_SIZE=4096
|
||||
CONFIG_LOG=y
|
||||
CONFIG_LLEXT=y
|
||||
CONFIG_LLEXT_HEAP_SIZE=32
|
||||
CONFIG_LLEXT_HEAP_SIZE=16
|
||||
CONFIG_LLEXT_LOG_LEVEL_DBG=y
|
|
@ -13,14 +13,13 @@
|
|||
|
||||
#include <stdint.h>
|
||||
#include <zephyr/llext/symbol.h>
|
||||
|
||||
extern void printk(char *fmt, ...);
|
||||
#include <zephyr/sys/printk.h>
|
||||
|
||||
static const uint32_t number = 42;
|
||||
|
||||
void hello_world(void)
|
||||
void test_entry(void)
|
||||
{
|
||||
printk("hello world\n");
|
||||
printk("A number is %lu\n", number);
|
||||
printk("A number is %u\n", number);
|
||||
}
|
||||
LL_EXTENSION_SYMBOL(hello_world);
|
||||
LL_EXTENSION_SYMBOL(test_entry);
|
26
tests/subsys/llext/simple/src/logging_ext.c
Normal file
26
tests/subsys/llext/simple/src/logging_ext.c
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* This very simple hello world C code can be used as a test case for building
|
||||
* probably the simplest loadable extension. It requires a single symbol be
|
||||
* linked, section relocation support, and the ability to export and call out to
|
||||
* a function.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <zephyr/llext/symbol.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_REGISTER(logging_ext);
|
||||
|
||||
static const uint32_t number = 42;
|
||||
|
||||
void test_entry(void)
|
||||
{
|
||||
LOG_INF("hello world");
|
||||
LOG_INF("A number is %" PRIu32, number);
|
||||
}
|
||||
LL_EXTENSION_SYMBOL(test_entry);
|
162
tests/subsys/llext/simple/src/test_llext_simple.c
Normal file
162
tests/subsys/llext/simple/src/test_llext_simple.c
Normal file
|
@ -0,0 +1,162 @@
|
|||
/*
|
||||
* Copyright (c) 2023 Intel Corporation.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/ztest.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/llext/llext.h>
|
||||
#include <zephyr/llext/buf_loader.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/libc-hooks.h>
|
||||
|
||||
LOG_MODULE_REGISTER(test_llext_simple);
|
||||
|
||||
|
||||
#ifdef CONFIG_LLEXT_STORAGE_WRITABLE
|
||||
#define LLEXT_CONST
|
||||
#else
|
||||
#define LLEXT_CONST const
|
||||
#endif
|
||||
|
||||
struct llext_test {
|
||||
const char *name;
|
||||
bool try_userspace;
|
||||
size_t buf_len;
|
||||
|
||||
LLEXT_CONST uint8_t *buf;
|
||||
};
|
||||
|
||||
|
||||
|
||||
K_THREAD_STACK_DEFINE(llext_stack, 1024);
|
||||
struct k_thread llext_thread;
|
||||
|
||||
#ifdef CONFIG_USERSPACE
|
||||
void llext_entry(void *arg0, void *arg1, void *arg2)
|
||||
{
|
||||
void (*fn)(void) = arg0;
|
||||
|
||||
LOG_INF("calling fn %p from thread %p", fn, k_current_get());
|
||||
fn();
|
||||
}
|
||||
#endif /* CONFIG_USERSPACE */
|
||||
|
||||
void load_call_unload(struct llext_test *test_case)
|
||||
{
|
||||
struct llext_buf_loader buf_loader =
|
||||
LLEXT_BUF_LOADER(test_case->buf, test_case->buf_len);
|
||||
struct llext_loader *loader = &buf_loader.loader;
|
||||
struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT;
|
||||
struct llext *ext = NULL;
|
||||
|
||||
int res = llext_load(loader, test_case->name, &ext, &ldr_parm);
|
||||
|
||||
zassert_ok(res, "load should succeed");
|
||||
|
||||
void (*test_entry_fn)() = llext_find_sym(&ext->exp_tab, "test_entry");
|
||||
|
||||
zassert_not_null(test_entry_fn, "test_entry should be an exported symbol");
|
||||
|
||||
#ifdef CONFIG_USERSPACE
|
||||
/*
|
||||
* Due to the number of MPU regions on some parts with MPU (USERSPACE)
|
||||
* enabled we need to always call into the extension from a new dedicated
|
||||
* thread to avoid running out of MPU regions on some parts.
|
||||
*
|
||||
* This is part dependent behavior and certainly on MMU capable parts
|
||||
* this should not be needed! This test however is here to be generic
|
||||
* across as many parts as possible.
|
||||
*/
|
||||
struct k_mem_domain domain;
|
||||
|
||||
k_mem_domain_init(&domain, 0, NULL);
|
||||
|
||||
#ifdef Z_LIBC_PARTITION_EXISTS
|
||||
k_mem_domain_add_partition(&domain, &z_libc_partition);
|
||||
#endif
|
||||
|
||||
res = llext_add_domain(ext, &domain);
|
||||
if (res == -ENOSPC) {
|
||||
TC_PRINT("Too many memory partitions for this particular hardware\n");
|
||||
ztest_test_skip();
|
||||
return;
|
||||
}
|
||||
zassert_ok(res, "adding partitions to domain should succeed");
|
||||
|
||||
/* Should be runnable from newly created thread */
|
||||
k_thread_create(&llext_thread, llext_stack,
|
||||
K_THREAD_STACK_SIZEOF(llext_stack),
|
||||
&llext_entry, test_entry_fn, NULL, NULL,
|
||||
1, 0, K_FOREVER);
|
||||
|
||||
k_mem_domain_add_thread(&domain, &llext_thread);
|
||||
|
||||
k_thread_start(&llext_thread);
|
||||
k_thread_join(&llext_thread, K_FOREVER);
|
||||
|
||||
/* Some extensions may wish to be tried from the context
|
||||
* of a userspace thread along with the usual supervisor context
|
||||
* tried above.
|
||||
*/
|
||||
if (test_case->try_userspace) {
|
||||
k_thread_create(&llext_thread, llext_stack,
|
||||
K_THREAD_STACK_SIZEOF(llext_stack),
|
||||
&llext_entry, test_entry_fn, NULL, NULL,
|
||||
1, K_USER, K_FOREVER);
|
||||
|
||||
k_mem_domain_add_thread(&domain, &llext_thread);
|
||||
|
||||
k_thread_start(&llext_thread);
|
||||
k_thread_join(&llext_thread, K_FOREVER);
|
||||
}
|
||||
|
||||
|
||||
#else /* CONFIG_USERSPACE */
|
||||
zassert_ok(llext_call_fn(ext, "test_entry"),
|
||||
"test_entry call should succeed");
|
||||
#endif /* CONFIG_USERSPACE */
|
||||
|
||||
llext_unload(&ext);
|
||||
}
|
||||
/*
|
||||
* Attempt to load, list, list symbols, call a fn, and unload each
|
||||
* extension in the test table. This excercises loading, calling into, and
|
||||
* unloading each extension which may itself excercise various APIs provided by
|
||||
* Zephyr.
|
||||
*/
|
||||
#define LLEXT_LOAD_UNLOAD(_name, _userspace) \
|
||||
ZTEST(llext, test_load_unload_##_name) \
|
||||
{ \
|
||||
struct llext_test test_case = { \
|
||||
.name = STRINGIFY(_name), \
|
||||
.try_userspace = _userspace, \
|
||||
.buf_len = ARRAY_SIZE(_name ## _ext), \
|
||||
.buf = _name ## _ext, \
|
||||
}; \
|
||||
load_call_unload(&test_case); \
|
||||
}
|
||||
static LLEXT_CONST uint8_t hello_world_ext[] __aligned(4) = {
|
||||
#include "hello_world.inc"
|
||||
};
|
||||
LLEXT_LOAD_UNLOAD(hello_world, false)
|
||||
|
||||
static LLEXT_CONST uint8_t logging_ext[] __aligned(4) = {
|
||||
#include "logging.inc"
|
||||
};
|
||||
LLEXT_LOAD_UNLOAD(logging, true)
|
||||
|
||||
/*
|
||||
* Ensure that EXPORT_SYMBOL does indeed provide a symbol and a valid address
|
||||
* to it.
|
||||
*/
|
||||
ZTEST(llext, test_printk_exported)
|
||||
{
|
||||
const void * const printk_fn = llext_find_sym(NULL, "printk");
|
||||
|
||||
zassert_equal(printk_fn, printk, "printk should be an exported symbol");
|
||||
}
|
||||
|
||||
|
||||
ZTEST_SUITE(llext, NULL, NULL, NULL, NULL, NULL);
|
|
@ -5,6 +5,7 @@ common:
|
|||
- xtensa
|
||||
platform_exclude:
|
||||
- numaker_pfm_m487 # See #63167
|
||||
- qemu_cortex_r5 # unsupported relocations
|
||||
tests:
|
||||
llext.simple.readonly:
|
||||
arch_exclude: xtensa # for now
|
Loading…
Reference in a new issue