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 <l.burelli@arduino.cc>
This commit is contained in:
parent
baa3b6a5ba
commit
57011e4c1a
8 changed files with 262 additions and 0 deletions
39
samples/subsys/llext/modules/CMakeLists.txt
Normal file
39
samples/subsys/llext/modules/CMakeLists.txt
Normal file
|
@ -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()
|
18
samples/subsys/llext/modules/Kconfig
Normal file
18
samples/subsys/llext/modules/Kconfig
Normal file
|
@ -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.
|
63
samples/subsys/llext/modules/README.rst
Normal file
63
samples/subsys/llext/modules/README.rst
Normal file
|
@ -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 <qemu_xtensa>` or
|
||||||
|
:ref:`qemu_cortex_r5 <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.
|
14
samples/subsys/llext/modules/prj.conf
Normal file
14
samples/subsys/llext/modules/prj.conf
Normal file
|
@ -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
|
35
samples/subsys/llext/modules/sample.yaml
Normal file
35
samples/subsys/llext/modules/sample.yaml
Normal file
|
@ -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!"
|
27
samples/subsys/llext/modules/src/hello_world_ext.c
Normal file
27
samples/subsys/llext/modules/src/hello_world_ext.c
Normal file
|
@ -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 <zephyr/llext/symbol.h>
|
||||||
|
#include <zephyr/sys/printk.h>
|
||||||
|
|
||||||
|
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);
|
20
samples/subsys/llext/modules/src/main_builtin.c
Normal file
20
samples/subsys/llext/modules/src/main_builtin.c
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Arduino SA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(app);
|
||||||
|
|
||||||
|
extern void hello_world(void);
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
LOG_INF("Calling hello world as a builtin");
|
||||||
|
|
||||||
|
hello_world();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
46
samples/subsys/llext/modules/src/main_module.c
Normal file
46
samples/subsys/llext/modules/src/main_module.c
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Arduino SA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_REGISTER(app);
|
||||||
|
|
||||||
|
#include <zephyr/llext/llext.h>
|
||||||
|
#include <zephyr/llext/buf_loader.h>
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue