blob: 7ce804147e749762912ab449ad2d6172e7cd4e31 [file] [log] [blame]
/*
* 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
}