Skip to content

Commit 5f4177b

Browse files
lyakhnashif
authored andcommitted
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 <[email protected]>
1 parent d8195c0 commit 5f4177b

File tree

6 files changed

+270
-2
lines changed

6 files changed

+270
-2
lines changed

include/zephyr/llext/llext.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,53 @@ int llext_heap_init(void *mem, size_t bytes);
448448
* @retval -EBUSY On heap not empty
449449
*/
450450
int llext_heap_uninit(void);
451+
452+
/**
453+
* @brief Relink dependencies to prepare for suspend
454+
*
455+
* For suspend-resume use-cases, when LLEXT context should be saved in a
456+
* non-volatile buffer, the user can save most LLEXT support data, but they have
457+
* to use @ref llext_restore() to re-allocate objects, which will also have to
458+
* restore dependency pointers. To make sure dependency saving and restoring is
459+
* done consistently, we provide a helper function for the former too.
460+
*
461+
* @warning this is a part of an experimental API, it WILL change in the future!
462+
* Its availability depends on CONFIG_LLEXT_EXPERIMENTAL, which is disabled by
463+
* default.
464+
*
465+
* @param[in] ext Extension array
466+
* @param[in] n_ext Number of extensions
467+
* @retval 0 Success
468+
* @retval -ENOENT Some dependencies not found
469+
*/
470+
int llext_relink_dependency(struct llext *ext, unsigned int n_ext);
471+
472+
/**
473+
* @brief Restore LLEXT context from saved data
474+
*
475+
* During suspend the user has saved all the extension and loader descriptors
476+
* and related objects and called @ref llext_relink_dependency() to prepare
477+
* dependency pointers.
478+
* When resuming llext_alloc() has to be used to re-allocate all the objects,
479+
* therefore the user needs support from LLEXT core to accomplish that.
480+
* This function takes arrays of pointers to saved copies of extensions and
481+
* loaders as arguments and re-allocates all the objects, while also adding them
482+
* to the global extension list. At the same time it relinks dependency pointers
483+
* to newly allocated extensions.
484+
*
485+
* @warning this is a part of an experimental API, it WILL change in the future!
486+
* Its availability depends on CONFIG_LLEXT_EXPERIMENTAL, which is disabled by
487+
* default.
488+
*
489+
* @param[in,out] ext Extension pointer array - replaced with re-allocated copies
490+
* @param[in,out] ldr Array of loader pointers to restore section maps
491+
* @param[in] n_ext Number of extensions
492+
* @retval 0 Success
493+
* @retval -ENOMEM No memory
494+
* @retval -EINVAL Stored dependency out of range
495+
* @retval -EFAULT Internal algorithmic error
496+
*/
497+
int llext_restore(struct llext **ext, struct llext_loader **ldr, unsigned int n_ext);
451498
/**
452499
* @}
453500
*/

subsys/llext/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ if(CONFIG_LLEXT)
1717
fs_loader.c
1818
)
1919
zephyr_library_sources_ifdef(CONFIG_LLEXT_SHELL shell.c)
20+
zephyr_library_sources_ifdef(CONFIG_LLEXT_EXPERIMENTAL llext_experimental.c)
2021

2122
if(CONFIG_RISCV AND CONFIG_USERSPACE)
2223
message(WARNING "Running LLEXT extensions from user-space threads on RISC-V is not supported!")

subsys/llext/Kconfig

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,12 @@ config LLEXT_IMPORT_ALL_GLOBALS
133133
used by the main application. This is useful to load basic extensions
134134
that have been compiled without the full Zephyr EDK.
135135

136+
config LLEXT_EXPERIMENTAL
137+
bool "LLEXT experimental functionality"
138+
help
139+
Include support for LLEXT experimental and unstable functionality that
140+
has a very high likelihood to change in the future.
141+
136142
module = LLEXT
137143
module-str = llext
138144
source "subsys/logging/Kconfig.template.log_config"

subsys/llext/llext.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ LOG_MODULE_REGISTER(llext, CONFIG_LLEXT_LOG_LEVEL);
1919

2020
#include "llext_priv.h"
2121

22-
static sys_slist_t llext_list = SYS_SLIST_STATIC_INIT(&llext_list);
22+
sys_slist_t llext_list = SYS_SLIST_STATIC_INIT(&llext_list);
2323

24-
static struct k_mutex llext_lock = Z_MUTEX_INITIALIZER(llext_lock);
24+
struct k_mutex llext_lock = Z_MUTEX_INITIALIZER(llext_lock);
2525

2626
int llext_section_shndx(const struct llext_loader *ldr, const struct llext *ext,
2727
const char *sect_name)

subsys/llext/llext_experimental.c

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
/*
2+
* Copyright (c) 2025 Intel Corporation
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/llext/loader.h>
8+
#include <zephyr/llext/llext.h>
9+
#include <zephyr/llext/llext_internal.h>
10+
#include <zephyr/kernel.h>
11+
#include <zephyr/sys/slist.h>
12+
13+
#include <zephyr/logging/log.h>
14+
LOG_MODULE_DECLARE(llext, CONFIG_LLEXT_LOG_LEVEL);
15+
16+
#include <string.h>
17+
18+
#include "llext_priv.h"
19+
20+
/*
21+
* Prepare a set of extension copies for future restoring. The user has copied
22+
* multiple extensions and their dependencies into a flat array. We have to
23+
* relink dependency pointers to copies in this array for later restoring. Note,
24+
* that all dependencies have to be in this array, if any are missing, restoring
25+
* will fail, so we return an error in such cases.
26+
*/
27+
int llext_relink_dependency(struct llext *ext, unsigned int n_ext)
28+
{
29+
unsigned int i, j, k;
30+
31+
for (i = 0; i < n_ext; i++) {
32+
for (j = 0; ext[i].dependency[j] && j < ARRAY_SIZE(ext[i].dependency); j++) {
33+
for (k = 0; k < n_ext; k++) {
34+
if (!strncmp(ext[k].name, ext[i].dependency[j]->name,
35+
sizeof(ext[k].name))) {
36+
ext[i].dependency[j] = ext + k;
37+
LOG_DBG("backup %s depends on %s",
38+
ext[i].name, ext[k].name);
39+
break;
40+
}
41+
}
42+
43+
if (k == n_ext) {
44+
return -ENOENT;
45+
}
46+
}
47+
}
48+
49+
return 0;
50+
}
51+
52+
int llext_restore(struct llext **ext, struct llext_loader **ldr, unsigned int n_ext)
53+
{
54+
struct llext_elf_sect_map **map = llext_alloc(sizeof(*map) * n_ext);
55+
struct llext *next, *tmp, *first = ext[0], *last = ext[n_ext - 1];
56+
unsigned int i, j, n_map, n_exp_tab;
57+
int ret;
58+
59+
if (!map) {
60+
LOG_ERR("cannot allocate list of maps of %zu", sizeof(*map) * n_ext);
61+
return -ENOMEM;
62+
}
63+
64+
/*
65+
* Each extension has a section map, so if this loop completes
66+
* successfully in the end n_map == n_ext. But if it's terminated
67+
* because of an allocation failure, we need to know how many maps have
68+
* to be freed.
69+
*/
70+
for (n_map = 0, n_exp_tab = 0; n_map < n_ext; n_map++) {
71+
/* Need to allocate individually, because that's how they're freed */
72+
map[n_map] = llext_alloc(sizeof(**map) * ext[n_map]->sect_cnt);
73+
if (!map[n_map]) {
74+
LOG_ERR("cannot allocate section map of %zu",
75+
sizeof(**map) * ext[n_map]->sect_cnt);
76+
ret = -ENOMEM;
77+
goto free_map;
78+
}
79+
80+
/* Not every extension exports symbols, count those that do */
81+
if (ext[n_map]->exp_tab.sym_cnt) {
82+
n_exp_tab++;
83+
}
84+
}
85+
86+
/* Array of pointers to exported symbol tables. Can be NULL if n_exp_tab == 0 */
87+
struct llext_symbol **exp_tab = llext_alloc(sizeof(*exp_tab) * n_exp_tab);
88+
89+
if (n_exp_tab) {
90+
if (!exp_tab) {
91+
LOG_ERR("cannot allocate list of exported symbol tables of %zu",
92+
sizeof(*exp_tab) * n_exp_tab);
93+
ret = -ENOMEM;
94+
goto free_map;
95+
}
96+
97+
/* Now actually allocate new exported symbol lists */
98+
for (i = 0, j = 0; i < n_ext; i++) {
99+
if (ext[i]->exp_tab.sym_cnt) {
100+
size_t size = sizeof(**exp_tab) * ext[i]->exp_tab.sym_cnt;
101+
102+
exp_tab[j] = llext_alloc(size);
103+
if (!exp_tab[j]) {
104+
LOG_ERR("cannot allocate exported symbol table of %zu",
105+
size);
106+
ret = -ENOMEM;
107+
goto free_sym;
108+
}
109+
memcpy(exp_tab[j++], ext[i]->exp_tab.syms, size);
110+
}
111+
}
112+
}
113+
114+
k_mutex_lock(&llext_lock, K_FOREVER);
115+
116+
/* Allocate extensions and add them to the global list */
117+
for (i = 0, j = 0; i < n_ext; i++) {
118+
next = llext_alloc(sizeof(*next));
119+
if (!next) {
120+
LOG_ERR("cannot allocate LLEXT of %zu", sizeof(*next));
121+
ret = -ENOMEM;
122+
goto free_llext;
123+
}
124+
125+
/* Copy the extension and return a pointer to it to the user */
126+
*next = *ext[i];
127+
ext[i] = next;
128+
if (next->exp_tab.sym_cnt) {
129+
next->exp_tab.syms = exp_tab[j++];
130+
}
131+
132+
sys_slist_append(&llext_list, &next->llext_list);
133+
}
134+
135+
k_mutex_unlock(&llext_lock);
136+
137+
/* Copy section maps */
138+
for (i = 0; i < n_ext; i++) {
139+
size_t map_size = sizeof(struct llext_elf_sect_map) * ext[i]->sect_cnt;
140+
141+
memcpy(map[i], ldr[i]->sect_map, map_size);
142+
ldr[i]->sect_map = map[i];
143+
}
144+
145+
llext_free(map);
146+
llext_free(exp_tab);
147+
148+
/* Restore dependencies previously saved by llext_relink_dependency() */
149+
SYS_SLIST_FOR_EACH_CONTAINER(&llext_list, next, llext_list) {
150+
for (j = 0; next->dependency[j] && j < ARRAY_SIZE(next->dependency); j++) {
151+
if (next->dependency[j] < first || next->dependency[j] >= last) {
152+
/* Inconsistency detected */
153+
LOG_ERR("dependency out of range");
154+
ret = -EINVAL;
155+
goto free_locked;
156+
}
157+
158+
next->dependency[j] = llext_by_name(next->dependency[j]->name);
159+
if (!next->dependency[j]) {
160+
/* Bug in the algorithm */
161+
LOG_ERR("dependency not found");
162+
ret = -EFAULT;
163+
goto free_locked;
164+
}
165+
166+
LOG_DBG("restore %s depends on %s",
167+
next->name, next->dependency[j]->name);
168+
}
169+
}
170+
171+
return 0;
172+
173+
free_locked:
174+
k_mutex_lock(&llext_lock, K_FOREVER);
175+
176+
free_llext:
177+
/* Free only those extensions, that we've saved into ext[] */
178+
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&llext_list, next, tmp, llext_list) {
179+
for (i = 0; i < n_ext; i++) {
180+
if (ext[i] == next) {
181+
sys_slist_remove(&llext_list, NULL, &next->llext_list);
182+
llext_free(next);
183+
ext[i] = NULL;
184+
break;
185+
}
186+
}
187+
}
188+
189+
k_mutex_unlock(&llext_lock);
190+
191+
free_sym:
192+
for (i = 0; i < n_exp_tab && exp_tab[i]; i++) {
193+
llext_free(exp_tab[i]);
194+
}
195+
196+
llext_free(exp_tab);
197+
198+
free_map:
199+
for (i = 0; i < n_map; i++) {
200+
llext_free(map[i]);
201+
}
202+
203+
llext_free(map);
204+
205+
return ret;
206+
}

subsys/llext/llext_priv.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@
1010
#include <zephyr/kernel.h>
1111
#include <zephyr/llext/llext.h>
1212
#include <zephyr/llext/llext_internal.h>
13+
#include <zephyr/sys/slist.h>
14+
15+
/*
16+
* Global extension list
17+
*/
18+
19+
extern sys_slist_t llext_list;
20+
extern struct k_mutex llext_lock;
1321

1422
/*
1523
* Memory management (llext_mem.c)

0 commit comments

Comments
 (0)