llext: add bringup, teardown, and bootstrap APIs
llext_bringup() and llext_teardown() are intended to be used to call the extension's own initialization and cleanup functions, respectively. They are meant to be called by the developer after loading an extension and before unloading it. The list of function pointers to be called is obtained via the new llext_get_fn_table() syscall, so that they are compatible with user mode. llext_bootstrap() is intended to be used as the entry point for a thread created to run an extension, in either user or kernel contexts. It will call the extension's own initialization functions and then an additional entry point in the same context (if desired). The same function can also be called directly in the main thread, if only initialization is required. Signed-off-by: Luca Burelli <l.burelli@arduino.cc>
This commit is contained in:
parent
04d7e4f490
commit
af302cd5fe
4 changed files with 233 additions and 2 deletions
|
|
@ -186,6 +186,74 @@ int llext_load(struct llext_loader *loader, const char *name, struct llext **ext
|
||||||
*/
|
*/
|
||||||
int llext_unload(struct llext **ext);
|
int llext_unload(struct llext **ext);
|
||||||
|
|
||||||
|
/** @brief Entry point function signature for an extension. */
|
||||||
|
typedef void (*llext_entry_fn_t)(void *user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calls bringup functions for an extension.
|
||||||
|
*
|
||||||
|
* Must be called before accessing any symbol in the extension. Will execute
|
||||||
|
* the extension's own setup functions in the caller context.
|
||||||
|
* @see llext_bootstrap
|
||||||
|
*
|
||||||
|
* @param[in] ext Extension to initialize.
|
||||||
|
* @returns 0 on success, or a negative error code.
|
||||||
|
* @retval -EFAULT A relocation issue was detected
|
||||||
|
*/
|
||||||
|
int llext_bringup(struct llext *ext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calls teardown functions for an extension.
|
||||||
|
*
|
||||||
|
* Will execute the extension's own cleanup functions in the caller context.
|
||||||
|
* After this function completes, the extension is no longer usable and must be
|
||||||
|
* fully unloaded with @ref llext_unload.
|
||||||
|
* @see llext_bootstrap
|
||||||
|
*
|
||||||
|
* @param[in] ext Extension to de-initialize.
|
||||||
|
* @returns 0 on success, or a negative error code.
|
||||||
|
* @retval -EFAULT A relocation issue was detected
|
||||||
|
*/
|
||||||
|
int llext_teardown(struct llext *ext);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Bring up, execute, and teardown an extension.
|
||||||
|
*
|
||||||
|
* Calls the extension's own setup functions, an additional entry point and
|
||||||
|
* the extension's cleanup functions in the current thread context.
|
||||||
|
*
|
||||||
|
* This is a convenient wrapper around @ref llext_bringup and @ref
|
||||||
|
* llext_teardown that matches the @ref k_thread_entry_t signature, so it can
|
||||||
|
* be directly started as a new user or kernel thread via @ref k_thread_create.
|
||||||
|
*
|
||||||
|
* @param[in] ext Extension to execute. Passed as `p1` in @ref k_thread_create.
|
||||||
|
* @param[in] entry_fn Main entry point of the thread after performing
|
||||||
|
* extension setup. Passed as `p2` in @ref k_thread_create.
|
||||||
|
* @param[in] user_data Argument passed to @a entry_fn. Passed as `p3` in
|
||||||
|
* @ref k_thread_create.
|
||||||
|
*/
|
||||||
|
void llext_bootstrap(struct llext *ext, llext_entry_fn_t entry_fn, void *user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get pointers to setup or cleanup functions for an extension.
|
||||||
|
*
|
||||||
|
* This syscall can be used to get the addresses of all the functions that
|
||||||
|
* have to be called for full extension setup or cleanup.
|
||||||
|
*
|
||||||
|
* @see llext_bootstrap
|
||||||
|
*
|
||||||
|
* @param[in] ext Extension to initialize.
|
||||||
|
* @param[in] is_init `true` to get functions to be called at setup time,
|
||||||
|
* `false` to get the cleanup ones.
|
||||||
|
* @param[inout] buf Buffer to store the function pointers in. Can be `NULL`
|
||||||
|
* to only get the minimum required size.
|
||||||
|
* @param[in] size Allocated size of the buffer in bytes.
|
||||||
|
* @returns the size used by the array in bytes, or a negative error code.
|
||||||
|
* @retval -EFAULT A relocation issue was detected
|
||||||
|
* @retval -ENOMEM Array does not fit in the allocated buffer
|
||||||
|
*/
|
||||||
|
__syscall ssize_t llext_get_fn_table(struct llext *ext, bool is_init, void *buf, size_t size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Find the address for an arbitrary symbol.
|
* @brief Find the address for an arbitrary symbol.
|
||||||
*
|
*
|
||||||
|
|
@ -255,8 +323,8 @@ int arch_elf_relocate(elf_rela_t *rel, uintptr_t loc,
|
||||||
*
|
*
|
||||||
* Searches for a section by name in the ELF file and returns its offset.
|
* Searches for a section by name in the ELF file and returns its offset.
|
||||||
*
|
*
|
||||||
* @param loader Extension loader data and context
|
* @param[in] loader Extension loader data and context
|
||||||
* @param search_name Section name to search for
|
* @param[in] search_name Section name to search for
|
||||||
* @returns the section offset or a negative error code
|
* @returns the section offset or a negative error code
|
||||||
*/
|
*/
|
||||||
ssize_t llext_find_section(struct llext_loader *loader, const char *search_name);
|
ssize_t llext_find_section(struct llext_loader *loader, const char *search_name);
|
||||||
|
|
@ -281,4 +349,6 @@ void arch_elf_relocate_local(struct llext_loader *loader, struct llext *ext,
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <zephyr/syscalls/llext.h>
|
||||||
|
|
||||||
#endif /* ZEPHYR_LLEXT_H */
|
#endif /* ZEPHYR_LLEXT_H */
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
if(CONFIG_LLEXT)
|
if(CONFIG_LLEXT)
|
||||||
|
zephyr_syscall_header(${ZEPHYR_BASE}/include/zephyr/llext/llext.h)
|
||||||
|
|
||||||
zephyr_library()
|
zephyr_library()
|
||||||
|
|
||||||
# For strnlen()
|
# For strnlen()
|
||||||
|
|
@ -10,6 +12,7 @@ if(CONFIG_LLEXT)
|
||||||
llext_load.c
|
llext_load.c
|
||||||
llext_link.c
|
llext_link.c
|
||||||
llext_export.c
|
llext_export.c
|
||||||
|
llext_handlers.c
|
||||||
buf_loader.c
|
buf_loader.c
|
||||||
fs_loader.c
|
fs_loader.c
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -273,3 +273,66 @@ int llext_call_fn(struct llext *ext, const char *sym_name)
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int call_fn_table(struct llext *ext, bool is_init)
|
||||||
|
{
|
||||||
|
ssize_t ret;
|
||||||
|
|
||||||
|
ret = llext_get_fn_table(ext, is_init, NULL, 0);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOG_ERR("Failed to get table size: %d", (int)ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef void (*elf_void_fn_t)(void);
|
||||||
|
|
||||||
|
int fn_count = ret / sizeof(elf_void_fn_t);
|
||||||
|
elf_void_fn_t fn_table[fn_count];
|
||||||
|
|
||||||
|
ret = llext_get_fn_table(ext, is_init, &fn_table, sizeof(fn_table));
|
||||||
|
if (ret < 0) {
|
||||||
|
LOG_ERR("Failed to get function table: %d", (int)ret);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < fn_count; i++) {
|
||||||
|
LOG_DBG("calling %s function %p()",
|
||||||
|
is_init ? "bringup" : "teardown", fn_table[i]);
|
||||||
|
fn_table[i]();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int llext_bringup(struct llext *ext)
|
||||||
|
{
|
||||||
|
return call_fn_table(ext, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline int llext_teardown(struct llext *ext)
|
||||||
|
{
|
||||||
|
return call_fn_table(ext, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void llext_bootstrap(struct llext *ext, llext_entry_fn_t entry_fn, void *user_data)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* Call initialization functions */
|
||||||
|
ret = llext_bringup(ext);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOG_ERR("Failed to call init functions: %d", ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start extension main function */
|
||||||
|
LOG_DBG("calling entry function %p(%p)", entry_fn, user_data);
|
||||||
|
entry_fn(user_data);
|
||||||
|
|
||||||
|
/* Call de-initialization functions */
|
||||||
|
ret = llext_teardown(ext);
|
||||||
|
if (ret < 0) {
|
||||||
|
LOG_ERR("Failed to call de-init functions: %d", ret);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
95
subsys/llext/llext_handlers.c
Normal file
95
subsys/llext/llext_handlers.c
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 Arduino SA
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <zephyr/llext/llext.h>
|
||||||
|
#include <zephyr/llext/loader.h>
|
||||||
|
#include <zephyr/internal/syscall_handler.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
LOG_MODULE_DECLARE(llext, CONFIG_LLEXT_LOG_LEVEL);
|
||||||
|
|
||||||
|
#include "llext_priv.h"
|
||||||
|
|
||||||
|
ssize_t z_impl_llext_get_fn_table(struct llext *ext, bool is_init, void *buf, size_t buf_size)
|
||||||
|
{
|
||||||
|
size_t table_size;
|
||||||
|
|
||||||
|
if (!ext) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_init) {
|
||||||
|
table_size = ext->mem_size[LLEXT_MEM_PREINIT] +
|
||||||
|
ext->mem_size[LLEXT_MEM_INIT];
|
||||||
|
} else {
|
||||||
|
table_size = ext->mem_size[LLEXT_MEM_FINI];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf) {
|
||||||
|
char *byte_ptr = buf;
|
||||||
|
|
||||||
|
if (buf_size < table_size) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_init) {
|
||||||
|
/* setup functions from preinit_array and init_array */
|
||||||
|
memcpy(byte_ptr,
|
||||||
|
ext->mem[LLEXT_MEM_PREINIT], ext->mem_size[LLEXT_MEM_PREINIT]);
|
||||||
|
memcpy(byte_ptr + ext->mem_size[LLEXT_MEM_PREINIT],
|
||||||
|
ext->mem[LLEXT_MEM_INIT], ext->mem_size[LLEXT_MEM_INIT]);
|
||||||
|
} else {
|
||||||
|
/* cleanup functions from fini_array */
|
||||||
|
memcpy(byte_ptr,
|
||||||
|
ext->mem[LLEXT_MEM_FINI], ext->mem_size[LLEXT_MEM_FINI]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sanity check: pointers in this table must map inside the
|
||||||
|
* text region of the extension. If this fails, something went
|
||||||
|
* wrong during the relocation process.
|
||||||
|
* Using "char *" for these simplifies pointer arithmetic.
|
||||||
|
*/
|
||||||
|
const char *text_start = ext->mem[LLEXT_MEM_TEXT];
|
||||||
|
const char *text_end = text_start + ext->mem_size[LLEXT_MEM_TEXT];
|
||||||
|
const char **fn_ptrs = buf;
|
||||||
|
|
||||||
|
for (int i = 0; i < table_size / sizeof(void *); i++) {
|
||||||
|
if (fn_ptrs[i] < text_start || fn_ptrs[i] >= text_end) {
|
||||||
|
LOG_ERR("init function %i (%p) outside text region",
|
||||||
|
i, fn_ptrs[i]);
|
||||||
|
return -EFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return table_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_USERSPACE
|
||||||
|
|
||||||
|
static int ext_is_valid(struct llext *ext, void *arg)
|
||||||
|
{
|
||||||
|
return ext == arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline ssize_t z_vrfy_llext_get_fn_table(struct llext *ext, bool is_init,
|
||||||
|
void *buf, size_t size)
|
||||||
|
{
|
||||||
|
/* Test that ext matches a loaded extension */
|
||||||
|
K_OOPS(llext_iterate(ext_is_valid, ext) == 0);
|
||||||
|
|
||||||
|
if (buf) {
|
||||||
|
/* Test that buf is a valid user-accessible pointer */
|
||||||
|
K_OOPS(K_SYSCALL_MEMORY_WRITE(buf, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
return z_impl_llext_get_fn_table(ext, is_init, buf, size);
|
||||||
|
}
|
||||||
|
#include <zephyr/syscalls/llext_get_fn_table_mrsh.c>
|
||||||
|
|
||||||
|
#endif /* CONFIG_USERSPACE */
|
||||||
Loading…
Reference in a new issue