diff --git a/include/zephyr/llext/llext.h b/include/zephyr/llext/llext.h index 6ca06648fb5..887f2152f4f 100644 --- a/include/zephyr/llext/llext.h +++ b/include/zephyr/llext/llext.h @@ -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); /** * @} */ diff --git a/subsys/llext/CMakeLists.txt b/subsys/llext/CMakeLists.txt index 9baf900b100..d46ebbb75df 100644 --- a/subsys/llext/CMakeLists.txt +++ b/subsys/llext/CMakeLists.txt @@ -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!") diff --git a/subsys/llext/Kconfig b/subsys/llext/Kconfig index a18442bb92d..274e8edb3e2 100644 --- a/subsys/llext/Kconfig +++ b/subsys/llext/Kconfig @@ -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" diff --git a/subsys/llext/llext.c b/subsys/llext/llext.c index 2f490e7fdf7..fade0519fef 100644 --- a/subsys/llext/llext.c +++ b/subsys/llext/llext.c @@ -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) diff --git a/subsys/llext/llext_experimental.c b/subsys/llext/llext_experimental.c new file mode 100644 index 00000000000..b13e21c4bbe --- /dev/null +++ b/subsys/llext/llext_experimental.c @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2025 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#include +LOG_MODULE_DECLARE(llext, CONFIG_LLEXT_LOG_LEVEL); + +#include + +#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; +} diff --git a/subsys/llext/llext_priv.h b/subsys/llext/llext_priv.h index 3d711889004..6662436cad2 100644 --- a/subsys/llext/llext_priv.h +++ b/subsys/llext/llext_priv.h @@ -10,6 +10,14 @@ #include #include #include +#include + +/* + * Global extension list + */ + +extern sys_slist_t llext_list; +extern struct k_mutex llext_lock; /* * Memory management (llext_mem.c)