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:
Luca Burelli 2024-06-07 19:10:55 +02:00 committed by Alberto Escolar
commit 57011e4c1a
8 changed files with 262 additions and 0 deletions

View 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()

View 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.

View 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.

View 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

View 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!"

View 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);

View 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;
}

View 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);
}