| /* |
| * 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" |
| |
| static sys_slist_t _llext_list = SYS_SLIST_STATIC_INIT(&_llext_list); |
| |
| static struct k_mutex llext_lock = Z_MUTEX_INITIALIZER(llext_lock); |
| |
| int llext_get_section_header(struct llext_loader *ldr, struct llext *ext, const char *search_name, |
| elf_shdr_t *shdr) |
| { |
| const elf_shdr_t *tmp; |
| unsigned int i; |
| |
| for (i = 0, tmp = ext->sect_hdrs; |
| i < ext->sect_cnt; |
| i++, tmp++) { |
| const char *name = llext_peek(ldr, |
| ldr->sects[LLEXT_MEM_SHSTRTAB].sh_offset + |
| tmp->sh_name); |
| |
| if (!name) { |
| return -ENOTSUP; |
| } |
| |
| if (!strcmp(name, search_name)) { |
| *shdr = *tmp; |
| return 0; |
| } |
| } |
| |
| return -ENOENT; |
| } |
| |
| 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, sizeof(ext->name)) == 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; |
| } |
| |
| strncpy((*ext)->name, name, sizeof((*ext)->name)); |
| (*ext)->name[sizeof((*ext)->name) - 1] = '\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> |
| |
| static void llext_log_flush(void) |
| { |
| #ifdef CONFIG_LOG_MODE_DEFERRED |
| extern struct k_thread logging_thread; |
| int cur_prio = k_thread_priority_get(k_current_get()); |
| int log_prio = k_thread_priority_get(&logging_thread); |
| int target_prio; |
| bool adjust_cur, adjust_log; |
| |
| /* |
| * Our goal is to raise the logger thread priority above current, but if |
| * current has the highest possble priority, both need to be adjusted, |
| * particularly if the logger thread has the lowest possible priority |
| */ |
| if (log_prio < cur_prio) { |
| adjust_cur = false; |
| adjust_log = false; |
| target_prio = 0; |
| } else if (cur_prio == K_HIGHEST_THREAD_PRIO) { |
| adjust_cur = true; |
| adjust_log = true; |
| target_prio = cur_prio; |
| k_thread_priority_set(k_current_get(), cur_prio + 1); |
| } else { |
| adjust_cur = false; |
| adjust_log = true; |
| target_prio = cur_prio - 1; |
| } |
| |
| /* adjust logging thread priority if needed */ |
| if (adjust_log) { |
| k_thread_priority_set(&logging_thread, target_prio); |
| } |
| |
| log_thread_trigger(); |
| k_yield(); |
| |
| if (adjust_log) { |
| k_thread_priority_set(&logging_thread, log_prio); |
| } |
| if (adjust_cur) { |
| k_thread_priority_set(&logging_thread, cur_prio); |
| } |
| #endif |
| } |
| |
| int llext_unload(struct llext **ext) |
| { |
| __ASSERT(*ext, "Expected non-null extension"); |
| struct llext *tmp = *ext; |
| |
| k_mutex_lock(&llext_lock, K_FOREVER); |
| |
| llext_log_flush(); |
| |
| __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; |
| } |
| } |