From fe573ebec6f78fcf88183d5bf13f748ecd36c418 Mon Sep 17 00:00:00 2001 From: Ederson de Souza Date: Thu, 11 Jul 2024 10:06:00 -0700 Subject: [PATCH] tests/misc/llext-edk: Do run the test, instead of only building it As commit 6a1d9877efa8 ("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 --- tests/misc/llext-edk/CMakeLists.txt | 7 +- tests/misc/llext-edk/extension/CMakeLists.txt | 11 +++ tests/misc/llext-edk/extension/src/main.c | 6 +- tests/misc/llext-edk/prj.conf | 4 +- tests/misc/llext-edk/pytest/test_edk.py | 53 +++++++++++- tests/misc/llext-edk/src/foo.c | 4 +- tests/misc/llext-edk/src/main.c | 85 +++++++++++++++++++ tests/misc/llext-edk/testcase.yaml | 2 +- 8 files changed, 162 insertions(+), 10 deletions(-) diff --git a/tests/misc/llext-edk/CMakeLists.txt b/tests/misc/llext-edk/CMakeLists.txt index 788326a774598f..67d3c73061734d 100644 --- a/tests/misc/llext-edk/CMakeLists.txt +++ b/tests/misc/llext-edk/CMakeLists.txt @@ -4,6 +4,11 @@ cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) 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(${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() diff --git a/tests/misc/llext-edk/extension/CMakeLists.txt b/tests/misc/llext-edk/extension/CMakeLists.txt index 1b1a9142ee48ae..d9ddfbb2f88c96 100644 --- a/tests/misc/llext-edk/extension/CMakeLists.txt +++ b/tests/misc/llext-edk/extension/CMakeLists.txt @@ -1,5 +1,10 @@ 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) include($ENV{LLEXT_EDK_INSTALL_DIR}/cmake.cflags) @@ -15,9 +20,15 @@ get_property(COMPILE_OPTIONS_PROP DIRECTORY PROPERTY COMPILE_OPTIONS) add_custom_command( OUTPUT ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext + ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.inc COMMAND ${CMAKE_C_COMPILER} ${COMPILE_OPTIONS_PROP} -o ${PROJECT_BINARY_DIR}/${PROJECT_NAME}.llext ${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) diff --git a/tests/misc/llext-edk/extension/src/main.c b/tests/misc/llext-edk/extension/src/main.c index c4843bee333fb5..59038de2cdf054 100644 --- a/tests/misc/llext-edk/extension/src/main.c +++ b/tests/misc/llext-edk/extension/src/main.c @@ -5,13 +5,13 @@ */ #include +#include #include -int start(void) +int start(int bar) { - int bar = 42; - printk("foo(%d) is %d\n", bar, foo(bar)); return 0; } +LL_EXTENSION_SYMBOL(start); diff --git a/tests/misc/llext-edk/prj.conf b/tests/misc/llext-edk/prj.conf index a479f3a569cd6e..6f2519abf25295 100644 --- a/tests/misc/llext-edk/prj.conf +++ b/tests/misc/llext-edk/prj.conf @@ -1,3 +1,5 @@ CONFIG_APPLICATION_DEFINED_SYSCALL=y -#CONFIG_USERSPACE=y CONFIG_LLEXT=y +CONFIG_USERSPACE=y + +CONFIG_MAIN_STACK_SIZE=2048 diff --git a/tests/misc/llext-edk/pytest/test_edk.py b/tests/misc/llext-edk/pytest/test_edk.py index d85a138d48b177..383e0b421a7d85 100644 --- a/tests/misc/llext-edk/pytest/test_edk.py +++ b/tests/misc/llext-edk/pytest/test_edk.py @@ -4,6 +4,7 @@ import logging import os +import pytest import shutil import tempfile @@ -13,15 +14,23 @@ 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? command = [ "west", "build", + "-b", + unlaunched_dut.device_config.platform, "-t", "llext-edk", "--build-dir", - dut.device_config.build_dir, + unlaunched_dut.device_config.build_dir, ] output = check_output(command, text=True) logger.info(output) @@ -30,7 +39,7 @@ def test_edk(dut: DeviceAdapter): with tempfile.TemporaryDirectory() as tempdir: # Copy the edk to the temporary directory using python methods 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) # 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" 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 # knows where the EDK is installed edk_dir = Path(tempdir) / "llext-edk" @@ -64,3 +79,35 @@ def test_edk(dut: DeviceAdapter): # Check if the extension was built 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() diff --git a/tests/misc/llext-edk/src/foo.c b/tests/misc/llext-edk/src/foo.c index e7a3d221e0d0c3..3751ecc34c6ef0 100644 --- a/tests/misc/llext-edk/src/foo.c +++ b/tests/misc/llext-edk/src/foo.c @@ -5,7 +5,9 @@ */ #include -#include + +#include +#include int z_impl_foo(int bar) { diff --git a/tests/misc/llext-edk/src/main.c b/tests/misc/llext-edk/src/main.c index a5e7ab2e1980f8..cc13b8ddafa28b 100644 --- a/tests/misc/llext-edk/src/main.c +++ b/tests/misc/llext-edk/src/main.c @@ -6,9 +6,94 @@ #include +#include #include +#include +#include + +#ifdef LOAD_AND_RUN_EXTENSION +static const unsigned char extension_llext[] = { + #include +}; +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) { +#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; } diff --git a/tests/misc/llext-edk/testcase.yaml b/tests/misc/llext-edk/testcase.yaml index 81f7ce0bd567ce..596d3ec5e59b78 100644 --- a/tests/misc/llext-edk/testcase.yaml +++ b/tests/misc/llext-edk/testcase.yaml @@ -5,5 +5,5 @@ tests: - pytest - edk platform_allow: - - native_sim + - qemu_cortex_r5 toolchain_exclude: llvm