llext: add context save and restore
Some applications need to save LLEXT context, e.g. when suspending, to later restore it quickly without a full relinking. Add 2 functions for context saving and restoring. Since these functions are likely to change in the future, put them in llext_experimental.c, which depends on CONFIG_LLEXT_EXPERIMENTAL and is disabled by default. Signed-off-by: Guennadi Liakhovetski <guennadi.liakhovetski@linux.intel.com>
This commit is contained in:
parent
d8195c05c7
commit
5f4177b47c
6 changed files with 270 additions and 2 deletions
|
@ -448,6 +448,53 @@ int llext_heap_init(void *mem, size_t bytes);
|
|||
* @retval -EBUSY On heap not empty
|
||||
*/
|
||||
int llext_heap_uninit(void);
|
||||
|
||||
/**
|
||||
* @brief Relink dependencies to prepare for suspend
|
||||
*
|
||||
* For suspend-resume use-cases, when LLEXT context should be saved in a
|
||||
* non-volatile buffer, the user can save most LLEXT support data, but they have
|
||||
* to use @ref llext_restore() to re-allocate objects, which will also have to
|
||||
* restore dependency pointers. To make sure dependency saving and restoring is
|
||||
* done consistently, we provide a helper function for the former too.
|
||||
*
|
||||
* @warning this is a part of an experimental API, it WILL change in the future!
|
||||
* Its availability depends on CONFIG_LLEXT_EXPERIMENTAL, which is disabled by
|
||||
* default.
|
||||
*
|
||||
* @param[in] ext Extension array
|
||||
* @param[in] n_ext Number of extensions
|
||||
* @retval 0 Success
|
||||
* @retval -ENOENT Some dependencies not found
|
||||
*/
|
||||
int llext_relink_dependency(struct llext *ext, unsigned int n_ext);
|
||||
|
||||
/**
|
||||
* @brief Restore LLEXT context from saved data
|
||||
*
|
||||
* During suspend the user has saved all the extension and loader descriptors
|
||||
* and related objects and called @ref llext_relink_dependency() to prepare
|
||||
* dependency pointers.
|
||||
* When resuming llext_alloc() has to be used to re-allocate all the objects,
|
||||
* therefore the user needs support from LLEXT core to accomplish that.
|
||||
* This function takes arrays of pointers to saved copies of extensions and
|
||||
* loaders as arguments and re-allocates all the objects, while also adding them
|
||||
* to the global extension list. At the same time it relinks dependency pointers
|
||||
* to newly allocated extensions.
|
||||
*
|
||||
* @warning this is a part of an experimental API, it WILL change in the future!
|
||||
* Its availability depends on CONFIG_LLEXT_EXPERIMENTAL, which is disabled by
|
||||
* default.
|
||||
*
|
||||
* @param[in,out] ext Extension pointer array - replaced with re-allocated copies
|
||||
* @param[in,out] ldr Array of loader pointers to restore section maps
|
||||
* @param[in] n_ext Number of extensions
|
||||
* @retval 0 Success
|
||||
* @retval -ENOMEM No memory
|
||||
* @retval -EINVAL Stored dependency out of range
|
||||
* @retval -EFAULT Internal algorithmic error
|
||||
*/
|
||||
int llext_restore(struct llext **ext, struct llext_loader **ldr, unsigned int n_ext);
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
|
|
@ -17,6 +17,7 @@ if(CONFIG_LLEXT)
|
|||
fs_loader.c
|
||||
)
|
||||
zephyr_library_sources_ifdef(CONFIG_LLEXT_SHELL shell.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_LLEXT_EXPERIMENTAL llext_experimental.c)
|
||||
|
||||
if(CONFIG_RISCV AND CONFIG_USERSPACE)
|
||||
message(WARNING "Running LLEXT extensions from user-space threads on RISC-V is not supported!")
|
||||
|
|
|
@ -133,6 +133,12 @@ config LLEXT_IMPORT_ALL_GLOBALS
|
|||
used by the main application. This is useful to load basic extensions
|
||||
that have been compiled without the full Zephyr EDK.
|
||||
|
||||
config LLEXT_EXPERIMENTAL
|
||||
bool "LLEXT experimental functionality"
|
||||
help
|
||||
Include support for LLEXT experimental and unstable functionality that
|
||||
has a very high likelihood to change in the future.
|
||||
|
||||
module = LLEXT
|
||||
module-str = llext
|
||||
source "subsys/logging/Kconfig.template.log_config"
|
||||
|
|
|
@ -19,9 +19,9 @@ LOG_MODULE_REGISTER(llext, CONFIG_LLEXT_LOG_LEVEL);
|
|||
|
||||
#include "llext_priv.h"
|
||||
|
||||
static sys_slist_t llext_list = SYS_SLIST_STATIC_INIT(&llext_list);
|
||||
sys_slist_t llext_list = SYS_SLIST_STATIC_INIT(&llext_list);
|
||||
|
||||
static struct k_mutex llext_lock = Z_MUTEX_INITIALIZER(llext_lock);
|
||||
struct k_mutex llext_lock = Z_MUTEX_INITIALIZER(llext_lock);
|
||||
|
||||
int llext_section_shndx(const struct llext_loader *ldr, const struct llext *ext,
|
||||
const char *sect_name)
|
||||
|
|
206
subsys/llext/llext_experimental.c
Normal file
206
subsys/llext/llext_experimental.c
Normal file
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Copyright (c) 2025 Intel Corporation
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <zephyr/llext/loader.h>
|
||||
#include <zephyr/llext/llext.h>
|
||||
#include <zephyr/llext/llext_internal.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/sys/slist.h>
|
||||
|
||||
#include <zephyr/logging/log.h>
|
||||
LOG_MODULE_DECLARE(llext, CONFIG_LLEXT_LOG_LEVEL);
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "llext_priv.h"
|
||||
|
||||
/*
|
||||
* Prepare a set of extension copies for future restoring. The user has copied
|
||||
* multiple extensions and their dependencies into a flat array. We have to
|
||||
* relink dependency pointers to copies in this array for later restoring. Note,
|
||||
* that all dependencies have to be in this array, if any are missing, restoring
|
||||
* will fail, so we return an error in such cases.
|
||||
*/
|
||||
int llext_relink_dependency(struct llext *ext, unsigned int n_ext)
|
||||
{
|
||||
unsigned int i, j, k;
|
||||
|
||||
for (i = 0; i < n_ext; i++) {
|
||||
for (j = 0; ext[i].dependency[j] && j < ARRAY_SIZE(ext[i].dependency); j++) {
|
||||
for (k = 0; k < n_ext; k++) {
|
||||
if (!strncmp(ext[k].name, ext[i].dependency[j]->name,
|
||||
sizeof(ext[k].name))) {
|
||||
ext[i].dependency[j] = ext + k;
|
||||
LOG_DBG("backup %s depends on %s",
|
||||
ext[i].name, ext[k].name);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (k == n_ext) {
|
||||
return -ENOENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int llext_restore(struct llext **ext, struct llext_loader **ldr, unsigned int n_ext)
|
||||
{
|
||||
struct llext_elf_sect_map **map = llext_alloc(sizeof(*map) * n_ext);
|
||||
struct llext *next, *tmp, *first = ext[0], *last = ext[n_ext - 1];
|
||||
unsigned int i, j, n_map, n_exp_tab;
|
||||
int ret;
|
||||
|
||||
if (!map) {
|
||||
LOG_ERR("cannot allocate list of maps of %zu", sizeof(*map) * n_ext);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/*
|
||||
* Each extension has a section map, so if this loop completes
|
||||
* successfully in the end n_map == n_ext. But if it's terminated
|
||||
* because of an allocation failure, we need to know how many maps have
|
||||
* to be freed.
|
||||
*/
|
||||
for (n_map = 0, n_exp_tab = 0; n_map < n_ext; n_map++) {
|
||||
/* Need to allocate individually, because that's how they're freed */
|
||||
map[n_map] = llext_alloc(sizeof(**map) * ext[n_map]->sect_cnt);
|
||||
if (!map[n_map]) {
|
||||
LOG_ERR("cannot allocate section map of %zu",
|
||||
sizeof(**map) * ext[n_map]->sect_cnt);
|
||||
ret = -ENOMEM;
|
||||
goto free_map;
|
||||
}
|
||||
|
||||
/* Not every extension exports symbols, count those that do */
|
||||
if (ext[n_map]->exp_tab.sym_cnt) {
|
||||
n_exp_tab++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Array of pointers to exported symbol tables. Can be NULL if n_exp_tab == 0 */
|
||||
struct llext_symbol **exp_tab = llext_alloc(sizeof(*exp_tab) * n_exp_tab);
|
||||
|
||||
if (n_exp_tab) {
|
||||
if (!exp_tab) {
|
||||
LOG_ERR("cannot allocate list of exported symbol tables of %zu",
|
||||
sizeof(*exp_tab) * n_exp_tab);
|
||||
ret = -ENOMEM;
|
||||
goto free_map;
|
||||
}
|
||||
|
||||
/* Now actually allocate new exported symbol lists */
|
||||
for (i = 0, j = 0; i < n_ext; i++) {
|
||||
if (ext[i]->exp_tab.sym_cnt) {
|
||||
size_t size = sizeof(**exp_tab) * ext[i]->exp_tab.sym_cnt;
|
||||
|
||||
exp_tab[j] = llext_alloc(size);
|
||||
if (!exp_tab[j]) {
|
||||
LOG_ERR("cannot allocate exported symbol table of %zu",
|
||||
size);
|
||||
ret = -ENOMEM;
|
||||
goto free_sym;
|
||||
}
|
||||
memcpy(exp_tab[j++], ext[i]->exp_tab.syms, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
k_mutex_lock(&llext_lock, K_FOREVER);
|
||||
|
||||
/* Allocate extensions and add them to the global list */
|
||||
for (i = 0, j = 0; i < n_ext; i++) {
|
||||
next = llext_alloc(sizeof(*next));
|
||||
if (!next) {
|
||||
LOG_ERR("cannot allocate LLEXT of %zu", sizeof(*next));
|
||||
ret = -ENOMEM;
|
||||
goto free_llext;
|
||||
}
|
||||
|
||||
/* Copy the extension and return a pointer to it to the user */
|
||||
*next = *ext[i];
|
||||
ext[i] = next;
|
||||
if (next->exp_tab.sym_cnt) {
|
||||
next->exp_tab.syms = exp_tab[j++];
|
||||
}
|
||||
|
||||
sys_slist_append(&llext_list, &next->llext_list);
|
||||
}
|
||||
|
||||
k_mutex_unlock(&llext_lock);
|
||||
|
||||
/* Copy section maps */
|
||||
for (i = 0; i < n_ext; i++) {
|
||||
size_t map_size = sizeof(struct llext_elf_sect_map) * ext[i]->sect_cnt;
|
||||
|
||||
memcpy(map[i], ldr[i]->sect_map, map_size);
|
||||
ldr[i]->sect_map = map[i];
|
||||
}
|
||||
|
||||
llext_free(map);
|
||||
llext_free(exp_tab);
|
||||
|
||||
/* Restore dependencies previously saved by llext_relink_dependency() */
|
||||
SYS_SLIST_FOR_EACH_CONTAINER(&llext_list, next, llext_list) {
|
||||
for (j = 0; next->dependency[j] && j < ARRAY_SIZE(next->dependency); j++) {
|
||||
if (next->dependency[j] < first || next->dependency[j] >= last) {
|
||||
/* Inconsistency detected */
|
||||
LOG_ERR("dependency out of range");
|
||||
ret = -EINVAL;
|
||||
goto free_locked;
|
||||
}
|
||||
|
||||
next->dependency[j] = llext_by_name(next->dependency[j]->name);
|
||||
if (!next->dependency[j]) {
|
||||
/* Bug in the algorithm */
|
||||
LOG_ERR("dependency not found");
|
||||
ret = -EFAULT;
|
||||
goto free_locked;
|
||||
}
|
||||
|
||||
LOG_DBG("restore %s depends on %s",
|
||||
next->name, next->dependency[j]->name);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
free_locked:
|
||||
k_mutex_lock(&llext_lock, K_FOREVER);
|
||||
|
||||
free_llext:
|
||||
/* Free only those extensions, that we've saved into ext[] */
|
||||
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&llext_list, next, tmp, llext_list) {
|
||||
for (i = 0; i < n_ext; i++) {
|
||||
if (ext[i] == next) {
|
||||
sys_slist_remove(&llext_list, NULL, &next->llext_list);
|
||||
llext_free(next);
|
||||
ext[i] = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
k_mutex_unlock(&llext_lock);
|
||||
|
||||
free_sym:
|
||||
for (i = 0; i < n_exp_tab && exp_tab[i]; i++) {
|
||||
llext_free(exp_tab[i]);
|
||||
}
|
||||
|
||||
llext_free(exp_tab);
|
||||
|
||||
free_map:
|
||||
for (i = 0; i < n_map; i++) {
|
||||
llext_free(map[i]);
|
||||
}
|
||||
|
||||
llext_free(map);
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -10,6 +10,14 @@
|
|||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/llext/llext.h>
|
||||
#include <zephyr/llext/llext_internal.h>
|
||||
#include <zephyr/sys/slist.h>
|
||||
|
||||
/*
|
||||
* Global extension list
|
||||
*/
|
||||
|
||||
extern sys_slist_t llext_list;
|
||||
extern struct k_mutex llext_lock;
|
||||
|
||||
/*
|
||||
* Memory management (llext_mem.c)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue