From 57011e4c1a38a8066ea1234b0714f51b47dc3658 Mon Sep 17 00:00:00 2001 From: Luca Burelli Date: Fri, 7 Jun 2024 19:10:55 +0200 Subject: [PATCH] llext: add a "modules" Kconfig tristate example This adds a new sample to demonstrate the use of tristate symbols in Kconfig to build a function as an llext module or as a built-in part of Zephyr. Signed-off-by: Luca Burelli --- samples/subsys/llext/modules/CMakeLists.txt | 39 ++++++++++++ samples/subsys/llext/modules/Kconfig | 18 ++++++ samples/subsys/llext/modules/README.rst | 63 +++++++++++++++++++ samples/subsys/llext/modules/prj.conf | 14 +++++ samples/subsys/llext/modules/sample.yaml | 35 +++++++++++ .../llext/modules/src/hello_world_ext.c | 27 ++++++++ .../subsys/llext/modules/src/main_builtin.c | 20 ++++++ .../subsys/llext/modules/src/main_module.c | 46 ++++++++++++++ 8 files changed, 262 insertions(+) create mode 100644 samples/subsys/llext/modules/CMakeLists.txt create mode 100644 samples/subsys/llext/modules/Kconfig create mode 100644 samples/subsys/llext/modules/README.rst create mode 100644 samples/subsys/llext/modules/prj.conf create mode 100644 samples/subsys/llext/modules/sample.yaml create mode 100644 samples/subsys/llext/modules/src/hello_world_ext.c create mode 100644 samples/subsys/llext/modules/src/main_builtin.c create mode 100644 samples/subsys/llext/modules/src/main_module.c diff --git a/samples/subsys/llext/modules/CMakeLists.txt b/samples/subsys/llext/modules/CMakeLists.txt new file mode 100644 index 00000000000..9e5f0e427a5 --- /dev/null +++ b/samples/subsys/llext/modules/CMakeLists.txt @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(fs_shell) + +if(CONFIG_HELLO_WORLD_MODE STREQUAL "m") + + # Build the llext ... + + set(ext_name hello_world) + set(ext_src src/${ext_name}_ext.c) + set(ext_bin ${ZEPHYR_BINARY_DIR}/${ext_name}.llext) + set(ext_inc ${ZEPHYR_BINARY_DIR}/include/generated/${ext_name}_ext.inc) + add_llext_target(${ext_name}_ext + OUTPUT ${ext_bin} + SOURCES ${ext_src} + ) + generate_inc_file_for_target(app ${ext_bin} ${ext_inc}) + + # ...and the code for loading and running it + + target_sources(app PRIVATE + src/main_module.c + ) + +elseif(CONFIG_HELLO_WORLD_MODE STREQUAL "y") + + # Just build the two files together + + target_sources(app PRIVATE + src/main_builtin.c + src/hello_world_ext.c + ) + +else() + message(FATAL_ERROR "Please choose 'y' or 'm' for CONFIG_HELLO_WORLD_MODE") +endif() diff --git a/samples/subsys/llext/modules/Kconfig b/samples/subsys/llext/modules/Kconfig new file mode 100644 index 00000000000..243d892e1fc --- /dev/null +++ b/samples/subsys/llext/modules/Kconfig @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: Apache-2.0 +# Copyright (c) 2024 Intel Corporation. + +mainmenu "LLEXT functionality test" + +source "Kconfig.zephyr" + +config HELLO_WORLD_MODE + tristate "Include the hello_world function" + default m + help + This enables building the hello_world function, implemented in + hello_world_ext.c, either as an llext module or as a built-in part of + Zephyr. + + If you select 'm', the hello_world function will be built as an llext + "module". If you select 'y', the hello_world function will be directly + linked in the Zephyr image. diff --git a/samples/subsys/llext/modules/README.rst b/samples/subsys/llext/modules/README.rst new file mode 100644 index 00000000000..a825d1cc65c --- /dev/null +++ b/samples/subsys/llext/modules/README.rst @@ -0,0 +1,63 @@ +.. zephyr:code-sample:: llext-modules + :name: Linkable loadable extensions "module" sample + :relevant-api: llext + + Call a function in a loadable extension module, + either built-in or loaded at runtime. + +Overview +******** + +This sample demonstrates the use of the :ref:`llext` subsystem in Zephyr. The +llext subsystem allows for the loading of relocatable ELF files at runtime; +their symbols can be accessed and functions called. + +Specifically, this shows how to call a simple "hello world" function, +implemented in :zephyr_file:`samples/subsys/llext/modules/src/hello_world_ext.c`. +This is achieved in two different ways, depending on the value of the Kconfig +symbol ``CONFIG_HELLO_WORLD_MODE``: + +- if it is ``y``, the function is directly compiled and called by the Zephyr + application. The caller code used in this case is in + :zephyr_file:`samples/subsys/llext/modules/src/main_builtin.c`. + +- if it is ``m``, the function is compiled as an llext and it is included in + the application as a binary blob. At runtime, the llext subsystem is used to + load the extension and call the function. The caller code is in + :zephyr_file:`samples/subsys/llext/modules/src/main_module.c`. + +Requirements +************ + +A board with a supported llext architecture and console. This can also be +executed in QEMU emulation on the :ref:`qemu_xtensa ` or +:ref:`qemu_cortex_r5 ` virtual boards. + +Building and running +******************** + +- The following commands build and run the sample so that the files are linked + together in the same binary: + + .. zephyr-app-commands:: + :zephyr-app: samples/subsys/llext/modules + :board: qemu_xtensa + :goals: build run + :west-args: -T sample.llext.modules.builtin_build + :compact: + +- The following commands build and run the sample so that the extension code is + compiled separately and included in the Zephyr image as a binary blob: + + .. zephyr-app-commands:: + :zephyr-app: samples/subsys/llext/modules + :board: qemu_xtensa + :goals: build run + :west-args: -T sample.llext.modules.module_build + :compact: + + Take a look at :zephyr_file:`samples/subsys/llext/modules/sample.yaml` for the + additional architecture-specific configurations required in this case. + +To build for a different board, replace ``qemu_xtensa`` in the commands above +with the desired board name. diff --git a/samples/subsys/llext/modules/prj.conf b/samples/subsys/llext/modules/prj.conf new file mode 100644 index 00000000000..b097e9fdb23 --- /dev/null +++ b/samples/subsys/llext/modules/prj.conf @@ -0,0 +1,14 @@ +CONFIG_LOG=y +CONFIG_LOG_MODE_IMMEDIATE=y + +CONFIG_MODULES=y + +# LLEXT is only required when loading the extension at runtime. Since in this +# basic example there's only one llext, leaving it in when building the +# extension as a built-in is redundant; in a real application, however, there +# could be other uses of the llext subsystem. + +CONFIG_LLEXT=y +CONFIG_LLEXT_LOG_LEVEL_DBG=y +CONFIG_LLEXT_HEAP_SIZE=64 +CONFIG_LLEXT_TYPE_ELF_RELOCATABLE=y diff --git a/samples/subsys/llext/modules/sample.yaml b/samples/subsys/llext/modules/sample.yaml new file mode 100644 index 00000000000..cdb8aaff64c --- /dev/null +++ b/samples/subsys/llext/modules/sample.yaml @@ -0,0 +1,35 @@ +common: + tags: llext + arch_allow: + - arm + - xtensa + platform_exclude: + - apollo4p_evb # See #73443 + - apollo4p_blue_kxr_evb # See #73443 + - numaker_pfm_m487 # See #63167 + integration_platforms: + - qemu_xtensa + - qemu_cortex_r5 + - mps2/an385 + harness: console +sample: + name: CONFIG_MODULES test + description: Call code directly and from extensions +tests: + sample.llext.modules.module_build: + filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE + extra_configs: + - CONFIG_HELLO_WORLD_MODE=m + - arch:arm:CONFIG_ARM_MPU=n + - arch:xtensa:CONFIG_LLEXT_STORAGE_WRITABLE=y + harness_config: + type: one_line + regex: + - "Hello, world, from an llext!" + sample.llext.modules.builtin_build: + extra_configs: + - CONFIG_HELLO_WORLD_MODE=y + harness_config: + type: one_line + regex: + - "Hello, world, from the main binary!" diff --git a/samples/subsys/llext/modules/src/hello_world_ext.c b/samples/subsys/llext/modules/src/hello_world_ext.c new file mode 100644 index 00000000000..41220b02725 --- /dev/null +++ b/samples/subsys/llext/modules/src/hello_world_ext.c @@ -0,0 +1,27 @@ +/* + * 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 +#include + +void hello_world(void) +{ +#if defined(CONFIG_HELLO_WORLD_MODE) + /* HELLO_WORLD_MODE=y: CONFIG_* is defined */ + printk("Hello, world, from the main binary!\n"); +#else + /* HELLO_WORLD_MODE=m: CONFIG_*_MODULE is defined instead */ + printk("Hello, world, from an llext!\n"); +#endif +} +LL_EXTENSION_SYMBOL(hello_world); diff --git a/samples/subsys/llext/modules/src/main_builtin.c b/samples/subsys/llext/modules/src/main_builtin.c new file mode 100644 index 00000000000..e740c5b80e5 --- /dev/null +++ b/samples/subsys/llext/modules/src/main_builtin.c @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2024 Arduino SA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL +#include +LOG_MODULE_REGISTER(app); + +extern void hello_world(void); + +int main(void) +{ + LOG_INF("Calling hello world as a builtin"); + + hello_world(); + + return 0; +} diff --git a/samples/subsys/llext/modules/src/main_module.c b/samples/subsys/llext/modules/src/main_module.c new file mode 100644 index 00000000000..fd7f3dd5bf7 --- /dev/null +++ b/samples/subsys/llext/modules/src/main_module.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2024 Arduino SA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL +#include +LOG_MODULE_REGISTER(app); + +#include +#include + +static uint8_t llext_buf[] = { +#include "hello_world_ext.inc" +}; + +int main(void) +{ + LOG_INF("Calling hello world as a module"); + + size_t llext_buf_len = ARRAY_SIZE(llext_buf); + struct llext_buf_loader buf_loader = LLEXT_BUF_LOADER(llext_buf, llext_buf_len); + struct llext_loader *ldr = &buf_loader.loader; + + struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT; + struct llext *ext; + int res; + + res = llext_load(ldr, "ext", &ext, &ldr_parm); + if (res != 0) { + LOG_ERR("Failed to load extension, return code %d\n", res); + return res; + } + + void (*hello_world_fn)() = llext_find_sym(&ext->exp_tab, "hello_world"); + + if (hello_world_fn == NULL) { + LOG_ERR("Failed to find symbol\n"); + return -1; + } + + hello_world_fn(); + + return llext_unload(&ext); +}