Skip to content

Commit

Permalink
llext: add a "modules" Kconfig tristate example
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
pillo79 committed Jun 13, 2024
1 parent f3e5d9d commit 59931ec
Show file tree
Hide file tree
Showing 8 changed files with 262 additions and 0 deletions.
39 changes: 39 additions & 0 deletions samples/subsys/llext/modules/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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 changes: 18 additions & 0 deletions samples/subsys/llext/modules/Kconfig
Original file line number Diff line number Diff line change
@@ -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 changes: 63 additions & 0 deletions samples/subsys/llext/modules/README.rst
Original file line number Diff line number Diff line change
@@ -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 changes: 14 additions & 0 deletions samples/subsys/llext/modules/prj.conf
Original file line number Diff line number Diff line change
@@ -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 changes: 35 additions & 0 deletions samples/subsys/llext/modules/sample.yaml
Original file line number Diff line number Diff line change
@@ -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 changes: 27 additions & 0 deletions samples/subsys/llext/modules/src/hello_world_ext.c
Original file line number Diff line number Diff line change
@@ -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 changes: 20 additions & 0 deletions samples/subsys/llext/modules/src/main_builtin.c
Original file line number Diff line number Diff line change
@@ -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 changes: 46 additions & 0 deletions samples/subsys/llext/modules/src/main_module.c
Original file line number Diff line number Diff line change
@@ -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);
}

0 comments on commit 59931ec

Please sign in to comment.