diff --git a/include/zephyr/llext/llext.h b/include/zephyr/llext/llext.h index 832aba25cac..0a006dc53b7 100644 --- a/include/zephyr/llext/llext.h +++ b/include/zephyr/llext/llext.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -40,6 +41,8 @@ enum llext_mem { LLEXT_MEM_COUNT, }; +#define LLEXT_MEM_PARTITIONS (LLEXT_MEM_BSS+1) + struct llext_loader; /** @@ -48,6 +51,12 @@ struct llext_loader; struct llext { /** @cond ignore */ sys_snode_t _llext_list; + +#ifdef CONFIG_USERSPACE + struct k_mem_partition mem_parts[LLEXT_MEM_PARTITIONS]; + struct k_mem_domain mem_domain; +#endif + /** @endcond */ /** Name of the llext */ @@ -167,6 +176,20 @@ const void * const llext_find_sym(const struct llext_symtable *sym_table, const */ int llext_call_fn(struct llext *ext, const char *sym_name); +/** + * @brief Add the known memory partitions of the extension to a memory domain + * + * Allows an extension to be executed in supervisor or user mode threads when + * memory protection hardware is enabled. + * + * @param[in] ext Extension to add to a domain + * @param[in] domain Memory domain to add partitions to + * + * @retval 0 success + * @retval -errno error + */ +int llext_add_domain(struct llext *ext, struct k_mem_domain *domain); + /** * @brief Architecture specific function for updating op codes given a relocation * diff --git a/subsys/llext/llext.c b/subsys/llext/llext.c index cbafa134eaa..e36dbd86633 100644 --- a/subsys/llext/llext.c +++ b/subsys/llext/llext.c @@ -17,6 +17,13 @@ LOG_MODULE_REGISTER(llext, CONFIG_LLEXT_LOG_LEVEL); #include +#ifdef CONFIG_MMU_PAGE_SIZE +#define LLEXT_PAGE_SIZE CONFIG_MMU_PAGE_SIZE +#else +/* Arm's MPU wants a 32 byte minimum mpu region */ +#define LLEXT_PAGE_SIZE 32 +#endif + K_HEAP_DEFINE(llext_heap, CONFIG_LLEXT_HEAP_SIZE * 1024); static const char ELF_MAGIC[] = {0x7f, 'E', 'L', 'F'}; @@ -251,6 +258,38 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext) return 0; } +/* + * Initialize the memory partition associated with the extension memory + */ +static void llext_init_mem_part(struct llext *ext, enum llext_mem mem_idx, + uintptr_t start, size_t len) +{ +#ifdef CONFIG_USERSPACE + if (mem_idx < LLEXT_MEM_PARTITIONS) { + ext->mem_parts[mem_idx].start = start; + ext->mem_parts[mem_idx].size = len; + + switch (mem_idx) { + case LLEXT_MEM_TEXT: + ext->mem_parts[mem_idx].attr = K_MEM_PARTITION_P_RX_U_RX; + break; + case LLEXT_MEM_DATA: + case LLEXT_MEM_BSS: + ext->mem_parts[mem_idx].attr = K_MEM_PARTITION_P_RW_U_RW; + break; + case LLEXT_MEM_RODATA: + ext->mem_parts[mem_idx].attr = K_MEM_PARTITION_P_RO_U_RO; + break; + default: + break; + } + LOG_DBG("mem partition %d start 0x%lx, size %d", mem_idx, + ext->mem_parts[mem_idx].start, + ext->mem_parts[mem_idx].size); + } +#endif +} + static int llext_copy_section(struct llext_loader *ldr, struct llext *ext, enum llext_mem mem_idx) { @@ -265,18 +304,41 @@ static int llext_copy_section(struct llext_loader *ldr, struct llext *ext, IS_ENABLED(CONFIG_LLEXT_STORAGE_WRITABLE)) { ext->mem[mem_idx] = llext_peek(ldr, ldr->sects[mem_idx].sh_offset); if (ext->mem[mem_idx]) { + llext_init_mem_part(ext, mem_idx, (uintptr_t)ext->mem[mem_idx], + ldr->sects[mem_idx].sh_size); ext->mem_on_heap[mem_idx] = false; return 0; } } - ext->mem[mem_idx] = k_heap_aligned_alloc(&llext_heap, sizeof(uintptr_t), - ldr->sects[mem_idx].sh_size, + /* On ARM with an MPU a pow(2, N)*32 sized and aligned region is needed, + * otherwise its typically an mmu page (sized and aligned memory region) + * we are after that we can assign memory permission bits on. + */ +#ifndef CONFIG_ARM_MPU + const uintptr_t sect_alloc = ROUND_UP(ldr->sects[mem_idx].sh_size, LLEXT_PAGE_SIZE); + const uintptr_t sect_align = LLEXT_PAGE_SIZE; +#else + uintptr_t sect_alloc = LLEXT_PAGE_SIZE; + + while (sect_alloc < ldr->sects[mem_idx].sh_size) { + sect_alloc *= 2; + } + uintptr_t sect_align = sect_alloc; +#endif + + ext->mem[mem_idx] = k_heap_aligned_alloc(&llext_heap, sect_align, + sect_alloc, K_NO_WAIT); + if (!ext->mem[mem_idx]) { return -ENOMEM; } - ext->alloc_size += ldr->sects[mem_idx].sh_size; + + ext->alloc_size += sect_alloc; + + llext_init_mem_part(ext, mem_idx, (uintptr_t)ext->mem[mem_idx], + sect_alloc); if (ldr->sects[mem_idx].sh_type == SHT_NOBITS) { memset(ext->mem[mem_idx], 0, ldr->sects[mem_idx].sh_size); @@ -769,6 +831,14 @@ static int do_llext_load(struct llext_loader *ldr, struct llext *ext, ldr->sect_cnt = ldr->hdr.e_shnum; ext->alloc_size += sect_map_sz; +#ifdef CONFIG_USERSPACE + ret = k_mem_domain_init(&ext->mem_domain, 0, NULL); + if (ret != 0) { + LOG_ERR("Failed to initialize extenion memory domain %d", ret); + goto out; + } +#endif + LOG_DBG("Finding ELF tables..."); ret = llext_find_tables(ldr); if (ret != 0) { @@ -974,3 +1044,26 @@ int llext_call_fn(struct llext *ext, const char *sym_name) return 0; } + +int llext_add_domain(struct llext *ext, struct k_mem_domain *domain) +{ +#ifdef CONFIG_USERSPACE + int ret = 0; + + for (int i = 0; i < LLEXT_MEM_PARTITIONS; i++) { + if (ext->mem_size[i] == 0) { + continue; + } + ret = k_mem_domain_add_partition(domain, &ext->mem_parts[i]); + if (ret != 0) { + LOG_ERR("Failed adding memory partition %d to domain %p", + i, domain); + return ret; + } + } + + return ret; +#else + return -ENOSYS; +#endif +} diff --git a/tests/subsys/llext/hello_world/prj.conf b/tests/subsys/llext/hello_world/prj.conf index ed104f56b08..ad4d6c5a0b7 100644 --- a/tests/subsys/llext/hello_world/prj.conf +++ b/tests/subsys/llext/hello_world/prj.conf @@ -1,7 +1,6 @@ CONFIG_ZTEST=y CONFIG_ZTEST_STACK_SIZE=8192 CONFIG_LOG=y -CONFIG_LOG_MODE_IMMEDIATE=y CONFIG_LLEXT=y CONFIG_LLEXT_HEAP_SIZE=32 CONFIG_LLEXT_LOG_LEVEL_DBG=y diff --git a/tests/subsys/llext/hello_world/src/test/test_llext_simple.c b/tests/subsys/llext/hello_world/src/test/test_llext_simple.c index b78f2809009..ee68e72d73a 100644 --- a/tests/subsys/llext/hello_world/src/test/test_llext_simple.c +++ b/tests/subsys/llext/hello_world/src/test/test_llext_simple.c @@ -18,6 +18,19 @@ static uint8_t hello_world_elf[] __aligned(4) = { }; #endif +K_THREAD_STACK_DEFINE(llext_stack, 1024); +struct k_thread llext_thread; + +#ifdef CONFIG_USERSPACE +void llext_entry(void *arg0, void *arg1, void *arg2) +{ + struct llext *ext = arg0; + + zassert_ok(llext_call_fn(ext, "hello_world"), + "hello_world call should succeed"); +} +#endif /* CONFIG_USERSPACE */ + /** * Attempt to load, list, list symbols, call a fn, and unload a * hello world extension for each supported architecture @@ -45,9 +58,29 @@ ZTEST(llext, test_llext_simple) zassert_not_null(hello_world_fn, "hello_world should be an exported symbol"); - res = llext_call_fn(ext, "hello_world"); +#ifdef CONFIG_USERSPACE + struct k_mem_domain domain; - zassert_ok(res, "calling hello world should succeed"); + k_mem_domain_init(&domain, 0, NULL); + + res = llext_add_domain(ext, &domain); + zassert_ok(res, "adding partitions to domain should succeed"); + + /* Should be runnable from newly created thread */ + k_thread_create(&llext_thread, llext_stack, + K_THREAD_STACK_SIZEOF(llext_stack), + &llext_entry, ext, NULL, NULL, + 1, 0, K_FOREVER); + + k_mem_domain_add_thread(&domain, &llext_thread); + + k_thread_start(&llext_thread); + k_thread_join(&llext_thread, K_FOREVER); + +#else /* CONFIG_USERSPACE */ + zassert_ok(llext_call_fn(ext, "hello_world"), + "hello_world call should succeed"); +#endif /* CONFIG_USERSPACE */ llext_unload(&ext); } diff --git a/tests/subsys/llext/hello_world/testcase.yaml b/tests/subsys/llext/hello_world/testcase.yaml index 35370a65ece..c977dc5f94f 100644 --- a/tests/subsys/llext/hello_world/testcase.yaml +++ b/tests/subsys/llext/hello_world/testcase.yaml @@ -3,20 +3,28 @@ common: arch_allow: - arm - xtensa - filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE_R52 platform_exclude: - nuvoton_pfm_m487 # See #63167 tests: llext.simple.readonly: arch_exclude: xtensa # for now + filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE_R52 extra_configs: - arch:arm:CONFIG_ARM_MPU=n - CONFIG_LLEXT_STORAGE_WRITABLE=n + llext.simple.readonly_mpu: + arch_exclude: xtensa # for now + filter: CONFIG_CPU_HAS_MPU + extra_configs: + - CONFIG_USERSPACE=y + - CONFIG_LLEXT_STORAGE_WRITABLE=n llext.simple.writable: + filter: not CONFIG_MPU and not CONFIG_MMU and not CONFIG_SOC_SERIES_S32ZE_R52 extra_configs: - arch:arm:CONFIG_ARM_MPU=n - CONFIG_LLEXT_STORAGE_WRITABLE=y llext.simple.modules_enabled_writable: + filter: not CONFIG_MPU and not CONFIG_MMU platform_key: - simulation - arch @@ -28,6 +36,7 @@ tests: - CONFIG_LLEXT_STORAGE_WRITABLE=y - CONFIG_LLEXT_TEST_HELLO=m llext.simple.modules_enabled_readonly: + filter: not CONFIG_MPU and not CONFIG_MMU arch_exclude: xtensa # for now platform_key: - simulation @@ -38,3 +47,13 @@ tests: - arch:arm:CONFIG_ARM_MPU=n - CONFIG_MODULES=y - CONFIG_LLEXT_TEST_HELLO=m + llext.simple.modules_enabled_readonly_mpu: + filter: CONFIG_CPU_HAS_MPU + arch_exclude: xtensa # for now + platform_key: + - simulation + - arch + extra_configs: + - CONFIG_USERSPACE=y + - CONFIG_MODULES=y + - CONFIG_LLEXT_TEST_HELLO=m