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