| /* |
| * Copyright (c) 2023 Intel Corporation |
| * Copyright (c) 2024 Arduino SA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/sys/util.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_DECLARE(llext, CONFIG_LLEXT_LOG_LEVEL); |
| |
| #include <string.h> |
| |
| #include "llext_priv.h" |
| |
| #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); |
| |
| /* |
| * Initialize the memory partition associated with the specified memory region |
| */ |
| 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; |
| } |
| } |
| #endif |
| |
| LOG_DBG("region %d: start 0x%zx, size %zd", mem_idx, (size_t)start, len); |
| } |
| |
| static int llext_copy_section(struct llext_loader *ldr, struct llext *ext, |
| enum llext_mem mem_idx, const struct llext_load_param *ldr_parm) |
| { |
| int ret; |
| |
| if (!ldr->sects[mem_idx].sh_size) { |
| return 0; |
| } |
| ext->mem_size[mem_idx] = ldr->sects[mem_idx].sh_size; |
| |
| if (IS_ENABLED(CONFIG_LLEXT_STORAGE_WRITABLE)) { |
| if (ldr->sects[mem_idx].sh_type != SHT_NOBITS) { |
| /* Directly use data from the ELF buffer if peek() is supported */ |
| 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; |
| } |
| } else if (ldr_parm && ldr_parm->pre_located) { |
| /* |
| * ldr_parm cannot be NULL here with the current flow, but |
| * we add a check to make it future-proof |
| */ |
| ext->mem[mem_idx] = NULL; |
| ext->mem_on_heap[mem_idx] = false; |
| return 0; |
| } |
| } |
| |
| if (ldr_parm && ldr_parm->pre_located) { |
| return -EFAULT; |
| } |
| |
| /* 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] = llext_aligned_alloc(sect_align, sect_alloc); |
| if (!ext->mem[mem_idx]) { |
| return -ENOMEM; |
| } |
| |
| 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); |
| } else { |
| ret = llext_seek(ldr, ldr->sects[mem_idx].sh_offset); |
| if (ret != 0) { |
| goto err; |
| } |
| |
| ret = llext_read(ldr, ext->mem[mem_idx], ldr->sects[mem_idx].sh_size); |
| if (ret != 0) { |
| goto err; |
| } |
| } |
| |
| ext->mem_on_heap[mem_idx] = true; |
| |
| return 0; |
| |
| err: |
| llext_free(ext->mem[mem_idx]); |
| ext->mem[mem_idx] = NULL; |
| return ret; |
| } |
| |
| int llext_copy_strings(struct llext_loader *ldr, struct llext *ext) |
| { |
| int ret = llext_copy_section(ldr, ext, LLEXT_MEM_SHSTRTAB, NULL); |
| |
| if (!ret) { |
| ret = llext_copy_section(ldr, ext, LLEXT_MEM_STRTAB, NULL); |
| } |
| |
| return ret; |
| } |
| |
| int llext_copy_regions(struct llext_loader *ldr, struct llext *ext, |
| const struct llext_load_param *ldr_parm) |
| { |
| for (enum llext_mem mem_idx = 0; mem_idx < LLEXT_MEM_COUNT; mem_idx++) { |
| /* strings have already been copied */ |
| if (ext->mem[mem_idx]) { |
| continue; |
| } |
| |
| int ret = llext_copy_section(ldr, ext, mem_idx, ldr_parm); |
| |
| if (ret < 0) { |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void llext_adjust_mmu_permissions(struct llext *ext) |
| { |
| #ifdef CONFIG_MMU |
| void *addr; |
| size_t size; |
| uint32_t flags; |
| |
| for (enum llext_mem mem_idx = 0; mem_idx < LLEXT_MEM_PARTITIONS; mem_idx++) { |
| addr = ext->mem[mem_idx]; |
| size = ROUND_UP(ext->mem_size[mem_idx], LLEXT_PAGE_SIZE); |
| if (size == 0) { |
| continue; |
| } |
| switch (mem_idx) { |
| case LLEXT_MEM_TEXT: |
| sys_cache_instr_invd_range(addr, size); |
| flags = K_MEM_PERM_EXEC; |
| break; |
| case LLEXT_MEM_DATA: |
| case LLEXT_MEM_BSS: |
| /* memory is already K_MEM_PERM_RW by default */ |
| continue; |
| case LLEXT_MEM_RODATA: |
| flags = 0; |
| break; |
| default: |
| continue; |
| } |
| sys_cache_data_flush_range(addr, size); |
| k_mem_update_flags(addr, size, flags); |
| } |
| #endif |
| } |
| |
| void llext_free_regions(struct llext *ext) |
| { |
| for (int i = 0; i < LLEXT_MEM_COUNT; i++) { |
| #ifdef CONFIG_MMU |
| if (ext->mem_size[i] != 0) { |
| /* restore default RAM permissions */ |
| k_mem_update_flags(ext->mem[i], |
| ROUND_UP(ext->mem_size[i], LLEXT_PAGE_SIZE), |
| K_MEM_PERM_RW); |
| } |
| #endif |
| if (ext->mem_on_heap[i]) { |
| LOG_DBG("freeing memory region %d", i); |
| llext_free(ext->mem[i]); |
| ext->mem[i] = NULL; |
| } |
| } |
| } |
| |
| 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 |
| } |