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:
Tom Burdick 2024-03-01 10:17:27 -06:00 committed by Fabio Baltieri
parent ad7086a2df
commit 0650a88bed
11 changed files with 225 additions and 125 deletions

View file

@ -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
)

View file

@ -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)
{

View file

@ -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})

View 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);

View 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()

View file

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

View file

@ -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);

View 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);

View 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);

View file

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