tests/misc/llext-edk: Do run the test, instead of only building it
As commit 6a1d9877ef
("cmake: Add LL_EXTENSION_BUILD to EDK flags")
made clear, only building an application with the EDK is not enough to
prevent regressions. This patch address that by also running the
application using the extension built with the EDK.
To provide a more comprehensive test, the application runs the extension
code on both kernel and user space, using a parameter sent by the
application. Assertions on the output from the application are used to
ensure expected results.
For now, tests only run on qemu_cortex_r5, but this can be expanded in
the future.
Signed-off-by: Ederson de Souza <ederson.desouza@intel.com>
This commit is contained in:
parent
1893a12d37
commit
fe573ebec6
8 changed files with 162 additions and 10 deletions
|
@ -4,6 +4,11 @@ cmake_minimum_required(VERSION 3.20.0)
|
||||||
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
project(llext_edk_test LANGUAGES C)
|
project(llext_edk_test LANGUAGES C)
|
||||||
|
|
||||||
target_sources(app PRIVATE src/main.c)
|
target_sources(app PRIVATE src/main.c src/foo.c)
|
||||||
zephyr_include_directories(include)
|
zephyr_include_directories(include)
|
||||||
zephyr_include_directories(${ZEPHYR_BASE}/boards/native/common)
|
zephyr_include_directories(${ZEPHYR_BASE}/boards/native/common)
|
||||||
|
|
||||||
|
if(EXTENSION_DIR)
|
||||||
|
target_include_directories(app PRIVATE ${EXTENSION_DIR})
|
||||||
|
target_compile_definitions(app PRIVATE LOAD_AND_RUN_EXTENSION)
|
||||||
|
endif()
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
cmake_minimum_required(VERSION 3.20.0)
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
|
||||||
|
set(CMAKE_C_COMPILER arm-zephyr-eabi-gcc)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH $ENV{ZEPHYR_SDK_INSTALL_DIR}/arm-zephyr-eabi)
|
||||||
|
set(CMAKE_C_COMPILER_FORCED TRUE)
|
||||||
|
set(CMAKE_CXX_COMPILER_FORCED TRUE)
|
||||||
|
|
||||||
project(extension)
|
project(extension)
|
||||||
|
|
||||||
include($ENV{LLEXT_EDK_INSTALL_DIR}/cmake.cflags)
|
include($ENV{LLEXT_EDK_INSTALL_DIR}/cmake.cflags)
|
||||||
|
@ -15,9 +20,15 @@ get_property(COMPILE_OPTIONS_PROP DIRECTORY PROPERTY COMPILE_OPTIONS)
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT
|
OUTPUT
|
||||||
${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
||||||
|
${PROJECT_BINARY_DIR}/${PROJECT_NAME}.inc
|
||||||
COMMAND ${CMAKE_C_COMPILER} ${COMPILE_OPTIONS_PROP}
|
COMMAND ${CMAKE_C_COMPILER} ${COMPILE_OPTIONS_PROP}
|
||||||
-o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
-o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext
|
||||||
${PROJECT_SOURCE_DIR}/src/main.c
|
${PROJECT_SOURCE_DIR}/src/main.c
|
||||||
|
COMMAND
|
||||||
|
${PYTHON_EXECUTABLE}
|
||||||
|
${PROJECT_SOURCE_DIR}/file2hex.py
|
||||||
|
--file ${PROJECT_NAME}.llext
|
||||||
|
> ${PROJECT_NAME}.inc
|
||||||
)
|
)
|
||||||
|
|
||||||
add_custom_target(extension ALL DEPENDS ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext)
|
add_custom_target(extension ALL DEPENDS ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext)
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <zephyr/kernel.h>
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/llext/symbol.h>
|
||||||
|
|
||||||
#include <app_api.h>
|
#include <app_api.h>
|
||||||
|
|
||||||
int start(void)
|
int start(int bar)
|
||||||
{
|
{
|
||||||
int bar = 42;
|
|
||||||
|
|
||||||
printk("foo(%d) is %d\n", bar, foo(bar));
|
printk("foo(%d) is %d\n", bar, foo(bar));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
LL_EXTENSION_SYMBOL(start);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
CONFIG_APPLICATION_DEFINED_SYSCALL=y
|
CONFIG_APPLICATION_DEFINED_SYSCALL=y
|
||||||
#CONFIG_USERSPACE=y
|
|
||||||
CONFIG_LLEXT=y
|
CONFIG_LLEXT=y
|
||||||
|
CONFIG_USERSPACE=y
|
||||||
|
|
||||||
|
CONFIG_MAIN_STACK_SIZE=2048
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pytest
|
||||||
import shutil
|
import shutil
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
|
@ -13,15 +14,23 @@ from twister_harness import DeviceAdapter
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def test_edk(dut: DeviceAdapter):
|
def test_edk(unlaunched_dut: DeviceAdapter):
|
||||||
|
# Need to have the ZEPHYR_SDK_INSTALL_DIR environment variable set,
|
||||||
|
# otherwise can't actually build the edk
|
||||||
|
if os.environ.get("ZEPHYR_SDK_INSTALL_DIR") is None:
|
||||||
|
logger.warning("ZEPHYR_SDK_INSTALL_DIR is not set, skipping test")
|
||||||
|
pytest.skip("ZEPHYR_SDK_INSTALL_DIR is not set")
|
||||||
|
|
||||||
# Can we build the edk?
|
# Can we build the edk?
|
||||||
command = [
|
command = [
|
||||||
"west",
|
"west",
|
||||||
"build",
|
"build",
|
||||||
|
"-b",
|
||||||
|
unlaunched_dut.device_config.platform,
|
||||||
"-t",
|
"-t",
|
||||||
"llext-edk",
|
"llext-edk",
|
||||||
"--build-dir",
|
"--build-dir",
|
||||||
dut.device_config.build_dir,
|
unlaunched_dut.device_config.build_dir,
|
||||||
]
|
]
|
||||||
output = check_output(command, text=True)
|
output = check_output(command, text=True)
|
||||||
logger.info(output)
|
logger.info(output)
|
||||||
|
@ -30,7 +39,7 @@ def test_edk(dut: DeviceAdapter):
|
||||||
with tempfile.TemporaryDirectory() as tempdir:
|
with tempfile.TemporaryDirectory() as tempdir:
|
||||||
# Copy the edk to the temporary directory using python methods
|
# Copy the edk to the temporary directory using python methods
|
||||||
logger.debug(f"Copying llext-edk.tar.xz to {tempdir}")
|
logger.debug(f"Copying llext-edk.tar.xz to {tempdir}")
|
||||||
edk_path = Path(dut.device_config.build_dir) / "zephyr/llext-edk.tar.xz"
|
edk_path = Path(unlaunched_dut.device_config.build_dir) / "zephyr/llext-edk.tar.xz"
|
||||||
shutil.copy(edk_path, tempdir)
|
shutil.copy(edk_path, tempdir)
|
||||||
|
|
||||||
# Extract the edk using tar
|
# Extract the edk using tar
|
||||||
|
@ -45,6 +54,12 @@ def test_edk(dut: DeviceAdapter):
|
||||||
ext_dir = Path(os.environ["ZEPHYR_BASE"]) / "tests/misc/llext-edk/extension"
|
ext_dir = Path(os.environ["ZEPHYR_BASE"]) / "tests/misc/llext-edk/extension"
|
||||||
shutil.copytree(ext_dir, tempdir_extension, dirs_exist_ok=True)
|
shutil.copytree(ext_dir, tempdir_extension, dirs_exist_ok=True)
|
||||||
|
|
||||||
|
# Also copy file2hex.py to the extension directory, so that it's possible
|
||||||
|
# to generate a hex file from the extension binary
|
||||||
|
logger.debug(f"Copying file2hex.py to {tempdir_extension}")
|
||||||
|
file2hex = Path(os.environ["ZEPHYR_BASE"]) / "scripts/build/file2hex.py"
|
||||||
|
shutil.copy(file2hex, tempdir_extension)
|
||||||
|
|
||||||
# Set the LLEXT_EDK_INSTALL_DIR environment variable so that the extension
|
# Set the LLEXT_EDK_INSTALL_DIR environment variable so that the extension
|
||||||
# knows where the EDK is installed
|
# knows where the EDK is installed
|
||||||
edk_dir = Path(tempdir) / "llext-edk"
|
edk_dir = Path(tempdir) / "llext-edk"
|
||||||
|
@ -64,3 +79,35 @@ def test_edk(dut: DeviceAdapter):
|
||||||
|
|
||||||
# Check if the extension was built
|
# Check if the extension was built
|
||||||
assert os.path.exists(Path(tempdir_extension) / "build/extension.llext")
|
assert os.path.exists(Path(tempdir_extension) / "build/extension.llext")
|
||||||
|
|
||||||
|
# Can we run it? First, rebuild the application, now including the extension
|
||||||
|
# build directory in the include path, so that the application can find the
|
||||||
|
# extension code.
|
||||||
|
logger.debug(f"Running application with extension in {tempdir_extension} - west build")
|
||||||
|
command = [
|
||||||
|
"west",
|
||||||
|
"build",
|
||||||
|
"-b",
|
||||||
|
unlaunched_dut.device_config.platform,
|
||||||
|
"--build-dir",
|
||||||
|
unlaunched_dut.device_config.build_dir,
|
||||||
|
"--",
|
||||||
|
f"-DEXTENSION_DIR={tempdir_extension}/build/"
|
||||||
|
]
|
||||||
|
logger.debug(f"west command: {command}")
|
||||||
|
output = check_output(command, text=True)
|
||||||
|
logger.info(output)
|
||||||
|
|
||||||
|
# Now that the application is built, run it
|
||||||
|
logger.debug(f"Running application with extension in {tempdir_extension}")
|
||||||
|
try:
|
||||||
|
unlaunched_dut.launch()
|
||||||
|
lines = unlaunched_dut.readlines_until("Done")
|
||||||
|
|
||||||
|
assert "Calling extension from kernel" in lines
|
||||||
|
assert "Calling extension from user" in lines
|
||||||
|
assert "foo(42) is 1764" in lines
|
||||||
|
assert "foo(43) is 1849" in lines
|
||||||
|
|
||||||
|
finally:
|
||||||
|
unlaunched_dut.close()
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <app_api.h>
|
#include <app_api.h>
|
||||||
#include <app_api.h>
|
|
||||||
|
#include <zephyr/llext/symbol.h>
|
||||||
|
#include <zephyr/internal/syscall_handler.h>
|
||||||
|
|
||||||
int z_impl_foo(int bar)
|
int z_impl_foo(int bar)
|
||||||
{
|
{
|
||||||
|
|
|
@ -6,9 +6,94 @@
|
||||||
|
|
||||||
#include <app_api.h>
|
#include <app_api.h>
|
||||||
|
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
#include <zephyr/llext/llext.h>
|
#include <zephyr/llext/llext.h>
|
||||||
|
#include <zephyr/llext/buf_loader.h>
|
||||||
|
#include <zephyr/app_memory/mem_domain.h>
|
||||||
|
|
||||||
|
#ifdef LOAD_AND_RUN_EXTENSION
|
||||||
|
static const unsigned char extension_llext[] = {
|
||||||
|
#include <extension.inc>
|
||||||
|
};
|
||||||
|
static const size_t extension_llext_len = ARRAY_SIZE(extension_llext);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define STACK_SIZE 1024
|
||||||
|
#define HEAP_SIZE 1024
|
||||||
|
|
||||||
|
#ifdef LOAD_AND_RUN_EXTENSION
|
||||||
|
struct k_thread kernel_thread, user_thread;
|
||||||
|
|
||||||
|
K_THREAD_STACK_DEFINE(stack_kernel, STACK_SIZE);
|
||||||
|
K_THREAD_STACK_DEFINE(stack_user, STACK_SIZE);
|
||||||
|
|
||||||
|
K_HEAP_DEFINE(heap_kernel, HEAP_SIZE);
|
||||||
|
K_HEAP_DEFINE(heap_user, HEAP_SIZE);
|
||||||
|
|
||||||
|
static void thread_entry(void *p1, void *p2, void *p3)
|
||||||
|
{
|
||||||
|
int bar;
|
||||||
|
int (*start_fn)(int) = p1;
|
||||||
|
|
||||||
|
printk("Calling extension from %s\n",
|
||||||
|
k_is_user_context() ? "user" : "kernel");
|
||||||
|
|
||||||
|
if (k_is_user_context()) {
|
||||||
|
bar = 42;
|
||||||
|
} else {
|
||||||
|
bar = 43;
|
||||||
|
}
|
||||||
|
|
||||||
|
start_fn(bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
void load_and_run_extension(int thread_flags, struct k_thread *thread,
|
||||||
|
struct k_mem_domain *domain,
|
||||||
|
k_thread_stack_t *stack, struct k_heap *heap,
|
||||||
|
struct llext **ext)
|
||||||
|
{
|
||||||
|
struct llext_buf_loader buf_loader = LLEXT_BUF_LOADER(extension_llext,
|
||||||
|
extension_llext_len);
|
||||||
|
struct llext_loader *loader = &buf_loader.loader;
|
||||||
|
struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT;
|
||||||
|
int (*start_fn)(int bar);
|
||||||
|
|
||||||
|
llext_load(loader, "extension", ext, &ldr_parm);
|
||||||
|
start_fn = llext_find_sym(&(*ext)->exp_tab, "start");
|
||||||
|
|
||||||
|
llext_add_domain(*ext, domain);
|
||||||
|
|
||||||
|
k_thread_create(thread, stack, STACK_SIZE,
|
||||||
|
thread_entry, start_fn, NULL, NULL, -1,
|
||||||
|
K_INHERIT_PERMS | thread_flags,
|
||||||
|
K_FOREVER);
|
||||||
|
k_mem_domain_add_thread(domain, thread);
|
||||||
|
k_thread_heap_assign(thread, heap);
|
||||||
|
|
||||||
|
k_thread_start(thread);
|
||||||
|
k_thread_join(thread, K_FOREVER);
|
||||||
|
|
||||||
|
llext_unload(ext);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
|
#ifdef LOAD_AND_RUN_EXTENSION
|
||||||
|
struct k_mem_domain domain_kernel, domain_user;
|
||||||
|
struct llext *ext_kernel, *ext_user;
|
||||||
|
|
||||||
|
k_mem_domain_init(&domain_kernel, 0, NULL);
|
||||||
|
k_mem_domain_init(&domain_user, 0, NULL);
|
||||||
|
|
||||||
|
load_and_run_extension(0, &kernel_thread, &domain_kernel,
|
||||||
|
stack_kernel, &heap_kernel, &ext_kernel);
|
||||||
|
load_and_run_extension(K_USER, &user_thread, &domain_user,
|
||||||
|
stack_user, &heap_user, &ext_user);
|
||||||
|
|
||||||
|
printk("Done\n");
|
||||||
|
#else
|
||||||
|
printk("Extension not loaded\n");
|
||||||
|
#endif
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,5 +5,5 @@ tests:
|
||||||
- pytest
|
- pytest
|
||||||
- edk
|
- edk
|
||||||
platform_allow:
|
platform_allow:
|
||||||
- native_sim
|
- qemu_cortex_r5
|
||||||
toolchain_exclude: llvm
|
toolchain_exclude: llvm
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue