Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

llext: convert "modules" tests to sample #74060

Merged
merged 3 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmake/modules/extensions.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -5394,6 +5394,7 @@ function(add_llext_target target_name)

target_compile_definitions(${llext_lib_target} PRIVATE
$<TARGET_PROPERTY:zephyr_interface,INTERFACE_COMPILE_DEFINITIONS>
LL_EXTENSION_BUILD
)
target_compile_options(${llext_lib_target} PRIVATE
${zephyr_filtered_flags}
Expand Down
4 changes: 4 additions & 0 deletions include/zephyr/llext/symbol.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,16 @@ struct llext_symtable {
*
* @param x Extension symbol to export to the base image
*/
#if defined(CONFIG_LLEXT) && defined(LL_EXTENSION_BUILD)
#define LL_EXTENSION_SYMBOL(x) \
static const struct llext_const_symbol \
Z_GENERIC_SECTION(".exported_sym") __used \
x ## _sym = { \
.name = STRINGIFY(x), .addr = (const void *)&x, \
}
#else
#define LL_EXTENSION_SYMBOL(x)
#endif

/**
* @}
Expand Down
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, and definitely out of this PR's scope: I find this Kconfig option's name quite confusing at first glance.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed it is. We can't change it unless we modify kconfiglib internals, though, since that is the name that triggers the whole "tristate" stuff in Kconfig. 🙁


# 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If CONFIG_LLEXT_HELLO_WORLD=y, this file will be built as part of the Zephyr application. However, there is nothing special in the LL_EXTENSION_SYMBOL macro that makes it not emit the export table entry.
Is there no risk to blow up CI with orphan section linker warnings due to something being emitted to the .exported_sym section which, AFAICT, is not handled in Zephyr linker script?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting... it did not in my tests, but I can't be sure it won't everywhere. I will fix this as we did for the EXPORT_SYMBOL macro: make it a NOP if LLEXT is not enabled, and disable LLEXT for the =y case.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this would work in this case though; AFAICT, CONFIG_LLEXT is always y for this sample. Maybe switching according to the tristate's value would work though (but I don't know what CONFIG_LLEXT_HELLO_WORLD=m yields once converted to C macro...)

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);
}
12 changes: 0 additions & 12 deletions tests/subsys/llext/simple/Kconfig

This file was deleted.

Loading
Loading