zephyr/subsys/llext/llext.c
Guennadi Liakhovetski 5f4177b47c 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>
2025-06-10 12:52:38 -04:00

326 lines
7 KiB
C

/*
* Copyright (c) 2023 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*
*/
#include <zephyr/sys/util.h>
#include <zephyr/llext/elf.h>
#include <zephyr/llext/loader.h>
#include <zephyr/llext/llext.h>
#include <zephyr/kernel.h>
#include <zephyr/cache.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(llext, CONFIG_LLEXT_LOG_LEVEL);
#include <string.h>
#include "llext_priv.h"
sys_slist_t llext_list = SYS_SLIST_STATIC_INIT(&llext_list);
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)
{
unsigned int i;
for (i = 1; i < ext->sect_cnt; i++) {
const char *name = llext_section_name(ldr, ext, ext->sect_hdrs + i);
if (!strcmp(name, sect_name)) {
return i;
}
}
return -ENOENT;
}
int llext_get_section_header(struct llext_loader *ldr, struct llext *ext, const char *search_name,
elf_shdr_t *shdr)
{
int ret;
ret = llext_section_shndx(ldr, ext, search_name);
if (ret < 0) {
return ret;
}
*shdr = ext->sect_hdrs[ret];
return 0;
}
ssize_t llext_find_section(struct llext_loader *ldr, const char *search_name)
{
elf_shdr_t *shdr;
unsigned int i;
size_t pos;
for (i = 0, pos = ldr->hdr.e_shoff;
i < ldr->hdr.e_shnum;
i++, pos += ldr->hdr.e_shentsize) {
shdr = llext_peek(ldr, pos);
if (!shdr) {
/* The peek() method isn't supported */
return -ENOTSUP;
}
const char *name = llext_peek(ldr,
ldr->sects[LLEXT_MEM_SHSTRTAB].sh_offset +
shdr->sh_name);
if (!strcmp(name, search_name)) {
return shdr->sh_offset;
}
}
return -ENOENT;
}
/*
* Note, that while we protect the global llext list while searching, we release
* the lock before returning the found extension to the caller. Therefore it's
* a responsibility of the caller to protect against races with a freeing
* context when calling this function.
*/
struct llext *llext_by_name(const char *name)
{
k_mutex_lock(&llext_lock, K_FOREVER);
for (sys_snode_t *node = sys_slist_peek_head(&llext_list);
node != NULL;
node = sys_slist_peek_next(node)) {
struct llext *ext = CONTAINER_OF(node, struct llext, llext_list);
if (strncmp(ext->name, name, LLEXT_MAX_NAME_LEN) == 0) {
k_mutex_unlock(&llext_lock);
return ext;
}
}
k_mutex_unlock(&llext_lock);
return NULL;
}
int llext_iterate(int (*fn)(struct llext *ext, void *arg), void *arg)
{
sys_snode_t *node;
int ret = 0;
k_mutex_lock(&llext_lock, K_FOREVER);
for (node = sys_slist_peek_head(&llext_list);
node;
node = sys_slist_peek_next(node)) {
struct llext *ext = CONTAINER_OF(node, struct llext, llext_list);
ret = fn(ext, arg);
if (ret) {
break;
}
}
k_mutex_unlock(&llext_lock);
return ret;
}
const void *llext_find_sym(const struct llext_symtable *sym_table, const char *sym_name)
{
if (sym_table == NULL) {
/* Built-in symbol table */
#ifdef CONFIG_LLEXT_EXPORT_BUILTINS_BY_SLID
/* 'sym_name' is actually a SLID to search for */
uintptr_t slid = (uintptr_t)sym_name;
/* TODO: perform a binary search instead of linear.
* Note that - as of writing - the llext_const_symbol_area
* section is sorted in ascending SLID order.
* (see scripts/build/llext_prepare_exptab.py)
*/
STRUCT_SECTION_FOREACH(llext_const_symbol, sym) {
if (slid == sym->slid) {
return sym->addr;
}
}
#else
STRUCT_SECTION_FOREACH(llext_const_symbol, sym) {
if (strcmp(sym->name, sym_name) == 0) {
return sym->addr;
}
}
#endif
} else {
/* find symbols in module */
for (size_t i = 0; i < sym_table->sym_cnt; i++) {
if (strcmp(sym_table->syms[i].name, sym_name) == 0) {
return sym_table->syms[i].addr;
}
}
}
return NULL;
}
int llext_load(struct llext_loader *ldr, const char *name, struct llext **ext,
const struct llext_load_param *ldr_parm)
{
int ret;
*ext = llext_by_name(name);
k_mutex_lock(&llext_lock, K_FOREVER);
if (*ext) {
/* The use count is at least 1 */
ret = (*ext)->use_count++;
goto out;
}
*ext = llext_alloc(sizeof(struct llext));
if (*ext == NULL) {
LOG_ERR("Not enough memory for extension metadata");
ret = -ENOMEM;
goto out;
}
ret = do_llext_load(ldr, *ext, ldr_parm);
if (ret < 0) {
llext_free(*ext);
*ext = NULL;
goto out;
}
/* The (*ext)->name array is LLEXT_MAX_NAME_LEN + 1 bytes long */
strncpy((*ext)->name, name, LLEXT_MAX_NAME_LEN);
(*ext)->name[LLEXT_MAX_NAME_LEN] = '\0';
(*ext)->use_count++;
sys_slist_append(&llext_list, &(*ext)->llext_list);
LOG_INF("Loaded extension %s", (*ext)->name);
out:
k_mutex_unlock(&llext_lock);
return ret;
}
#include <zephyr/logging/log_ctrl.h>
int llext_unload(struct llext **ext)
{
__ASSERT(*ext, "Expected non-null extension");
struct llext *tmp = *ext;
/* Flush pending log messages, as the deferred formatting may be referencing
* strings/args in the extension we are about to unload
*/
log_flush();
k_mutex_lock(&llext_lock, K_FOREVER);
__ASSERT(tmp->use_count, "A valid LLEXT cannot have a zero use-count!");
if (tmp->use_count-- != 1) {
unsigned int ret = tmp->use_count;
k_mutex_unlock(&llext_lock);
return ret;
}
/* FIXME: protect the global list */
sys_slist_find_and_remove(&llext_list, &tmp->llext_list);
llext_dependency_remove_all(tmp);
*ext = NULL;
k_mutex_unlock(&llext_lock);
if (tmp->sect_hdrs_on_heap) {
llext_free(tmp->sect_hdrs);
}
llext_free_regions(tmp);
llext_free(tmp->sym_tab.syms);
llext_free(tmp->exp_tab.syms);
llext_free(tmp);
return 0;
}
int llext_call_fn(struct llext *ext, const char *sym_name)
{
void (*fn)(void);
fn = llext_find_sym(&ext->exp_tab, sym_name);
if (fn == NULL) {
return -ENOENT;
}
fn();
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", (void *)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)", (void *)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;
}
}