Linkable loadable extensions EDK

Overview

This sample demonstrates how to use the Zephyr LLEXT EDK (Extension Development Kit). It is composed of one Zephyr application, which provides APIs for the extensions that it loads. The provided API is a simple publish/subscribe system, based on Zbus, which extensions use to communicate with each other.

The application is composed of a subscriber thread, which listens for events published and republishes them via Zbus to the extensions that are subscribers. There are four extensions, which are loaded by the application and run in different contexts. Extensions ext1, ext2 and ext3 run in userspace, each demonstrating different application and Zephyr API usage, such as semaphores, spawning threads to listen for events or simply publishing or subscribing to events. Extension kext1 runs in a kernel thread, albeit similar to ext3.

The application also creates different memory domains for each extension, thus providing some level of isolation - although the kernel one still has access to all of Zephyr kernel.

Note that the kernel extension is only available when the EDK is built with the CONFIG_LLEXT_EDK_USERSPACE_ONLY option disabled.

The application is built using the Zephyr build system. The EDK is built using the Zephyr build system as well, via llext-edk target. The EDK is then extracted and the extensions are built using CMake.

Finally, the way the application loads the extensions is by including them during build time, which is not really practical. This sample is about the EDK providing the ability to build extensions independently from the application. One could build the extensions in different directories, not related to the Zephyr application - even on different machines, using only the EDK. At the limit, one could even imagine a scenario where the extensions are built by different teams, using the EDK provided by the application developer.

Building the EDK

To build the EDK, use the llext-edk target. For example:

west build -b qemu_cortex_r5 -p=always samples/subsys/llext/edk/app
west build -t llext-edk

Copy the EDK to some place and extract it:

mkdir /tmp/edk
cp build/zephyr/llext-edk.tar.xz /tmp/edk
cd /tmp/edk
tar -xf llext-edk.tar.xz

Then set LLEXT_EDK_INSTALL_DIR to the extracted directory:

export LLEXT_EDK_INSTALL_DIR=/tmp/edk/llext-edk

This variable is used by the extensions to find the EDK.

Building the extensions

The ZEPHYR_SDK_INSTALL_DIR environment variable is used by the extensions to find the Zephyr SDK, so you need to ensure it’s properly set:

export ZEPHYR_SDK_INSTALL_DIR=</path/to/zephyr-sdk>

To build the extensions, in the ext1, ext2, ext3 and kext1 directories:

cmake -B build
make -C build

Alternatively, you can set the LLEXT_EDK_INSTALL_DIR directly in the CMake invocation:

cmake -B build -DLLEXT_EDK_INSTALL_DIR=/tmp/edk/llext-edk
make -C build

Building the application

Now, build the application, including the extensions, and run it:

west build -b qemu_cortex_r5 -p=always samples/subsys/llext/edk/app
west build -t run

You should see something like:

[app]Subscriber thread [0x20b28] started.
[app]Loading extension [kext1].
[app]Thread 0x20840 created to run extension [kext1], at privileged mode.
[k-ext1]Waiting sem
[app]Thread [0x222a0] registered event [0x223c0]
[k-ext1]Waiting event
[app]Loading extension [ext1].
[app]Thread 0x20a30 created to run extension [ext1], at userspace.
[app]Thread [0x20a30] registered event [0x26060]
[ext1]Waiting event
[app]Loading extension [ext2].
[app]Thread 0x20938 created to run extension [ext2], at userspace.
[ext2]Publishing tick
[app][subscriber_thread]Got channel tick_chan
[ext1]Got event, reading channel
[ext1]Read val: 0
[ext1]Waiting event
[k-ext1]Got event, giving sem
[k-ext1]Got sem, reading channel
[k-ext1]Read val: 0
[k-ext1]Waiting sem
(...)