Configuration

The following Kconfig options are available for the LLEXT subsystem:

Heap size

The LLEXT subsystem needs a heap to be allocated for extension related data. The following option controls this allocation, when allocating a static heap.

CONFIG_LLEXT_HEAP_SIZE

Size of the LLEXT heap in kilobytes.

For boards using the Harvard architecture, the LLEXT heap is split into two: one heap in instruction memory and another in data memory. The following options control these allocations.

CONFIG_LLEXT_INSTR_HEAP_SIZE

Size of the LLEXT heap in instruction memory in kilobytes.

CONFIG_LLEXT_DATA_HEAP_SIZE

Size of the LLEXT heap in data memory in kilobytes.

Alternatively the application can configure a dynamic heap using the following option.

CONFIG_LLEXT_HEAP_DYNAMIC

Some applications require loading extensions into the memory which does not exist during the boot time and cannot be allocated statically. Make the application responsible for LLEXT heap allocation. Do not allocate LLEXT heap statically.

Application must call llext_heap_init() in order to assign a buffer to be used as the LLEXT heap, otherwise LLEXT modules will not load. When the application does not need LLEXT functionality any more, it should call llext_heap_uninit() which releases control of the buffer back to the application.

Note

When user mode is enabled, the heap size must be large enough to allow the extension sections to be allocated with the alignment required by the architecture.

Note

On Harvard architectures, applications must call llext_heap_init_harvard().

The backing data structure for the LLEXT heap is by default a k_heap, but it can be changed to sys_mem_blocks_t for non-metadata (i.e., extension regions) by selecting CONFIG_LLEXT_HEAP_MEMBLK for CONFIG_LLEXT_HEAP_MANAGEMENT.

CONFIG_LLEXT_HEAP_MANAGEMENT

Select the memory management API used to store extension regions in LLEXT heap memory. This choice does not affect LLEXT metadata, which is always managed with k_heap.

CONFIG_LLEXT_HEAP_MEMBLK

Use sys_mem_blocks_t API to manage LLEXT heap memory for extension regions. A minimum of one block will be allocated per region. Block size must be selected with care to ensure proper alignment for extension regions.

The heap will be split into two, with the size of each sub-heap controlled by the following options.

CONFIG_LLEXT_EXT_HEAP_SIZE

Heap size in kilobytes available for LLEXT extension sections. Must be a multiple of CONFIG_LLEXT_HEAP_MEMBLK_BLOCK_SIZE. Replaced by CONFIG_LLEXT_INSTR_HEAP_SIZE and CONFIG_LLEXT_DATA_HEAP_SIZE if CONFIG_HARVARD is selected.

CONFIG_LLEXT_METADATA_HEAP_SIZE

Heap size in kilobytes available for LLEXT metadata.

Another option controls block size.

CONFIG_LLEXT_HEAP_MEMBLK_BLOCK_SIZE

Block size in bytes for LLEXT sys_mem_blocks_t heap(s). Must be equal to or a multiple of LLEXT_PAGE_SIZE. The block size must also be equal to or a multiple of the largest alignment needed for any extension region. If CONFIG_MPU_REQUIRES_POWER_OF_TWO_ALIGNMENT is selected and regions are large, an unreasonably large block size may be needed to satisfy alignment requirements.

Heap placement

The LLEXT heap(s) have custom sections. Non-Harvard heap sections (.llext_heap, or .llext_metadata_heap and .llext_ext_heap if CONFIG_LLEXT_HEAP_MEMBLK is selected) are placed alongside .noinit sections in the file include/zephyr/linker/common-noinit.ld. If none of your linker scripts include this file, you will need to place the non-Harvard LLEXT heap sections manually. One way to do this is by including snippets-noinit.ld in your linker script after your .noinit sections.

/* Located in generated directory. This file is populated by the
 * zephyr_linker_sources() CMake function.
 */
#include <snippets-noinit.ld>

Add the file as a linker source in your board, SoC, or architecture CMakeFiles.txt.

zephyr_linker_sources(NOINIT snippets-noinit.ld)

Then create a file in the same directory as your CMakeFiles.txt named noinit.ld.

#ifdef CONFIG_LLEXT
*(.llext_heap)
*(.llext_ext_heap)
*(.llext_metadata_heap)
#endif /* CONFIG_LLEXT */

For ARC, the Harvard instruction and data heap sections (.llext_instr_heap and .llext_data_heap) are placed in instruction and data memory at the architecture level. If you are using a non-ARC board with a Harvard architecture, you will need to manually place .llext_instr_heap and .llext_data_heap.

Warning

LLEXT will be unable to load extensions if the instruction memory .llext_instr_heap is placed in is not writable at the time the extensions are loaded and linked.

Placements can also be specified, or default placements overridden, by providing a custom linker script.

CONFIG_CUSTOM_LINKER_SCRIPT

Path to the linker script to be used instead of the one defined by the board.

The linker script must be based on a version provided by Zephyr since the kernel can expect a certain layout/certain regions.

This is useful when an application needs to add sections into the linker script and avoid having to change the script provided by Zephyr.

ELF object type

The LLEXT subsystem supports loading different types of extensions; the type can be set by choosing among the following Kconfig options:

CONFIG_LLEXT_TYPE_ELF_OBJECT

Build and expect relocatable files as binary object type for the LLEXT subsystem. A single compiler invocation is used to generate the object file.

CONFIG_LLEXT_TYPE_ELF_RELOCATABLE

Build and expect relocatable (partially linked) files as the binary object type for the LLEXT subsystem. These object files are generated by the linker by combining multiple object files into a single one.

CONFIG_LLEXT_TYPE_ELF_SHAREDLIB

Build and expect shared libraries as binary object type for the LLEXT subsystem. The standard linking process is used to generate the shared library from multiple object files.

Note

This is not currently supported on ARM architectures.

Minimize allocations

The LLEXT subsystem loading mechanism, by default, uses a seek/read abstraction and copies all data into allocated memory; this is done to allow the extension to be loaded from any storage medium. Sometimes, however, data is already in a buffer in RAM and copying it is not necessary. The following option allows the LLEXT subsystem to optimize memory footprint in this case.

CONFIG_LLEXT_STORAGE_WRITABLE

Allow the extension to be loaded by directly referencing section data into the ELF buffer. To be effective, this requires the use of an ELF loader that supports the peek functionality, such as the llext_buf_loader.

Warning

The application must ensure that the buffer used to load the extension remains allocated until the extension is unloaded.

Note

This will directly modify the contents of the buffer during the link phase. Once the extension is unloaded, the buffer must be reloaded before it can be used again in a call to llext_load().

Note

This is currently required by the Xtensa architecture. Further information on this topic is available on GitHub issue #75341.

Symbol Groups

All LLEXT symbols belong to a group, with the inclusion of each group in the exported symbol table controlled by a corresponding Kconfig symbol. Exporting a symbol as part of a group is done with the EXPORT_GROUP_SYMBOL and EXPORT_GROUP_SYMBOL_NAMED macros. For example the following exports the symbol memcpy as part of the LIBC group:

EXPORT_GROUP_SYMBOL(LIBC, memcpy);

Group names are arbitrary, but they must be all uppercase. For each group used in C code, there MUST be a corresponding Kconfig symbol of the form:

config LLEXT_EXPORT_SYMBOL_GROUP_{GROUP_NAME}
   bool "Export all symbols from the {GROUP_NAME} group"

The default group for symbols (those declared with EXPORT_SYMBOL or EXPORT_SYMBOL_NAMED) is the UNASSIGNED group. As per the above rules, the inclusion of this group is controlled by CONFIG_LLEXT_EXPORT_SYMBOL_GROUP_UNASSIGNED.

The groups currently defined by Zephyr are:

Zephyr LLEXT symbol groups

Group Name

Kconfig Symbol

Description

UNASSIGNED

CONFIG_LLEXT_EXPORT_SYMBOL_GROUP_UNASSIGNED

Symbols without an explicit group

SYSCALL

CONFIG_LLEXT_EXPORT_SYMBOL_GROUP_SYSCALL

Zephyr kernel system calls

LIBC

CONFIG_LLEXT_EXPORT_SYMBOL_GROUP_LIBC

C standard library functions (memcpy() etc)

DEVICE

CONFIG_LLEXT_EXPORT_SYMBOL_GROUP_DEVICE

Devicetree devices

Using SLID for symbol lookups

When an extension is loaded, the LLEXT subsystem must find the address of all the symbols residing in the main application that the extension references. To this end, the main binary contains a LLEXT-dedicated symbol table, filled with one symbol-name-to-address mapping entry for each symbol exported by the main application to extensions. This table can then be searched into by the LLEXT linker at extension load time. This process is pretty slow due to the nature of string comparisons, and the size consumed by the table can become significant as the number of exported symbols increases.

CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID

Perform an extra processing step on the Zephyr binary and on all extensions being built, converting every string in the symbol tables to a pointer-sized hash called Symbol Link Identifier (SLID), which is stored in the binary.

This speeds up the symbol lookup process by allowing usage of integer-based comparisons rather than string-based ones. Another benefit of SLID-based linking is that storing symbol names in the binary is no longer necessary, which provides a significant decrease in symbol table size.

Note

This option is not currently compatible with the LLEXT EDK.

Note

Using a different value for this option in the main binary and in extensions is not supported. For example, if the main application is built with CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=y, it is forbidden to load an extension that was compiled with CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID=n.

EDK configuration

Options influencing the generation and behavior of the LLEXT EDK are described in LLEXT EDK Kconfig options.