diff --git a/arch/arm/core/elf.c b/arch/arm/core/elf.c index e3c137e49f2a..3c21d4cffb4d 100644 --- a/arch/arm/core/elf.c +++ b/arch/arm/core/elf.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2023 Intel Corporation + * Copyright (c) 2024 Schneider Electric * * SPDX-License-Identifier: Apache-2.0 */ @@ -11,79 +12,331 @@ LOG_MODULE_REGISTER(elf, CONFIG_LLEXT_LOG_LEVEL); -#define ARM_BL_BLX_UPPER_S_BIT BIT(10) -#define ARM_BL_BLX_ADDEND_OFFSET 0 -#define ARM_BL_BLX_ADDEND_SIZE 11 -#define ARM_BL_BLX_ADDEND_MASK 0x7FF -#define ARM_BL_BLX_HDR_MASK 0xF800 -#define ARM_BL_BLX_LOWER_T1T2_BIT BIT(12) +#define OPCODE2ARMMEM(x) opcode_identity32(x) +#define OPCODE2THM16MEM(x) opcode_identity16(x) +#define MEM2ARMOPCODE(x) OPCODE2ARMMEM(x) +#define MEM2THM16OPCODE(x) OPCODE2THM16MEM(x) +#define JUMP_UPPER_BOUNDARY ((int32_t)0xfe000000) +#define JUMP_LOWER_BOUNDARY ((int32_t)0x2000000) +#define PREL31_UPPER_BOUNDARY ((int32_t)0x40000000) +#define PREL31_LOWER_BOUNDARY ((int32_t)-0x40000000) +#define THM_JUMP_UPPER_BOUNDARY ((int32_t)0xff000000) +#define THM_JUMP_LOWER_BOUNDARY ((int32_t)0x01000000) +#define MASK_BRANCH_COND GENMASK(31, 28) +#define MASK_BRANCH_101 GENMASK(27, 25) +#define MASK_BRANCH_L BIT(24) +#define MASK_BRANCH_OffSET GENMASK(23, 0) +#define MASK_MOV_COND GENMASK(31, 28) +#define MASK_MOV_00 GENMASK(27, 26) +#define MASK_MOV_I BIT(25) +#define MASK_MOV_OPCODE GENMASK(24, 21) +#define MASK_MOV_S BIT(20) +#define MASK_MOV_RN GENMASK(19, 16) +#define MASK_MOV_RD GENMASK(15, 12) +#define MASK_MOV_OPERAND2 GENMASK(11, 0) +#define BIT_THM_BW_S 10 +#define MASK_THM_BW_11110 GENMASK(15, 11) +#define MASK_THM_BW_S BIT(10) +#define MASK_THM_BW_IMM10 GENMASK(9, 0) +#define BIT_THM_BL_J1 13 +#define BIT_THM_BL_J2 11 +#define MASK_THM_BL_10 GENMASK(15, 14) +#define MASK_THM_BL_J1 BIT(13) +#define MASK_THM_BL_1 BIT(12) +#define MASK_THM_BL_J2 BIT(11) +#define MASK_THM_BL_IMM11 GENMASK(10, 0) +#define MASK_THM_MOV_11110 GENMASK(15, 11) +#define MASK_THM_MOV_I BIT(10) +#define MASK_THM_MOV_100100 GENMASK(9, 4) +#define MASK_THM_MOV_IMM4 GENMASK(3, 0) +#define MASK_THM_MOV_0 BIT(15) +#define MASK_THM_MOV_IMM3 GENMASK(14, 12) +#define MASK_THM_MOV_RD GENMASK(11, 8) +#define MASK_THM_MOV_IMM8 GENMASK(7, 0) -static int32_t arm_bl_blx_decode_addend(uintptr_t opaddr) +static inline uint32_t opcode_identity16(uintptr_t x) { - uint16_t upper = *((uint16_t *)opaddr); - uint16_t lower = *(((uint16_t *)opaddr) + 1); + return *(uint16_t *)x; +} + +static inline uint32_t opcode_identity32(uintptr_t x) +{ + return *(uint32_t *)x; +} - int32_t addend = upper & ARM_BL_BLX_UPPER_S_BIT ? UINT32_MAX : 0; +static int decode_prel31(uint32_t rel_index, elf_word reloc_type, uint32_t loc, + uint32_t sym_base_addr, const char *symname, int32_t *offset) +{ + int ret; - addend <<= ARM_BL_BLX_ADDEND_SIZE; - addend |= upper & ARM_BL_BLX_ADDEND_MASK; - addend <<= ARM_BL_BLX_ADDEND_SIZE; - addend |= lower & ARM_BL_BLX_ADDEND_MASK; + *offset = sign_extend(*(int32_t *)loc, 30); + *offset += sym_base_addr - loc; + if (*offset >= PREL31_UPPER_BOUNDARY || *offset < PREL31_LOWER_BOUNDARY) { + LOG_ERR("sym '%s': relocation %u out of range (%#x -> %#x)\n", + symname, rel_index, loc, sym_base_addr); + ret = -ENOEXEC; + } else { + *(uint32_t *)loc &= BIT(31); + *(uint32_t *)loc |= *offset & GENMASK(30, 0); - return lower & ARM_BL_BLX_LOWER_T1T2_BIT ? addend << 1 : addend << 2; + ret = 0; + } + + return ret; } -static void arm_bl_blx_encode_addend(uintptr_t opaddr, int32_t addend) +static int decode_jumps(uint32_t rel_index, elf_word reloc_type, uint32_t loc, + uint32_t sym_base_addr, const char *symname, int32_t *offset) { - uint16_t upper = *((uint16_t *)opaddr); - uint16_t lower = *(((uint16_t *)opaddr) + 1); + int ret; + + *offset = MEM2ARMOPCODE(*(uint32_t *)loc); + *offset = (*offset & MASK_BRANCH_OffSET) << 2; + *offset = sign_extend(*offset, 25); + *offset += sym_base_addr - loc; + if (*offset <= JUMP_UPPER_BOUNDARY || *offset >= JUMP_LOWER_BOUNDARY) { + LOG_ERR("sym '%s': relocation %u out of range (%#x -> %#x)\n", + symname, rel_index, loc, sym_base_addr); + ret = -ENOEXEC; + } else { + *offset >>= 2; + *offset &= MASK_BRANCH_OffSET; - addend = upper & ARM_BL_BLX_UPPER_S_BIT ? addend >> 1 : addend >> 2; + *(uint32_t *)loc &= OPCODE2ARMMEM(MASK_BRANCH_COND|MASK_BRANCH_101|MASK_BRANCH_L); + *(uint32_t *)loc |= OPCODE2ARMMEM(*offset); - upper &= ARM_BL_BLX_HDR_MASK; - lower &= ARM_BL_BLX_HDR_MASK; - upper |= (addend >> ARM_BL_BLX_ADDEND_SIZE) & ARM_BL_BLX_ADDEND_MASK; - lower |= addend & ARM_BL_BLX_ADDEND_MASK; + ret = 0; + } - *((uint16_t *)opaddr) = upper; - *(((uint16_t *)opaddr) + 1) = lower; + return ret; +} + +static void decode_movs(uint32_t rel_index, elf_word reloc_type, uint32_t loc, + uint32_t sym_base_addr, const char *symname, int32_t *offset) +{ + uint32_t tmp; + + *offset = tmp = MEM2ARMOPCODE(*(uint32_t *)loc); + *offset = ((*offset & MASK_MOV_RN) >> 4) | (*offset & MASK_MOV_OPERAND2); + *offset = sign_extend(*offset, 15); + + *offset += sym_base_addr; + if (reloc_type == R_ARM_MOVT_PREL || reloc_type == R_ARM_MOVW_PREL_NC) { + *offset -= loc; + } + if (reloc_type == R_ARM_MOVT_ABS || reloc_type == R_ARM_MOVT_PREL) { + *offset >>= 16; + } + + tmp &= (MASK_MOV_COND | MASK_MOV_00 | MASK_MOV_I | MASK_MOV_OPCODE | MASK_MOV_RD); + tmp |= ((*offset & MASK_MOV_RD) << 4) | + (*offset & (MASK_MOV_RD|MASK_MOV_OPERAND2)); + + *(uint32_t *)loc = OPCODE2ARMMEM(tmp); +} + +static int decode_thm_jumps(uint32_t rel_index, elf_word reloc_type, uint32_t loc, + uint32_t sym_base_addr, const char *symname, int32_t *offset) +{ + int ret; + uint32_t j1, j2, upper, lower, sign; + + /** + * For function symbols, only Thumb addresses are + * allowed (no interworking). + * + * For non-function symbols, the destination + * has no specific ARM/Thumb disposition, so + * the branch is resolved under the assumption + * that interworking is not required. + */ + upper = MEM2THM16OPCODE(*(uint16_t *)loc); + lower = MEM2THM16OPCODE(*(uint16_t *)(loc + 2)); + + /* sign is bit10 */ + sign = (upper >> BIT_THM_BW_S) & 1; + j1 = (lower >> BIT_THM_BL_J1) & 1; + j2 = (lower >> BIT_THM_BL_J2) & 1; + *offset = (sign << 24) | ((~(j1 ^ sign) & 1) << 23) | + ((~(j2 ^ sign) & 1) << 22) | + ((upper & MASK_THM_BW_IMM10) << 12) | + ((lower & MASK_THM_BL_IMM11) << 1); + *offset = sign_extend(*offset, 24); + *offset += sym_base_addr - loc; + + if (*offset <= THM_JUMP_UPPER_BOUNDARY || *offset >= THM_JUMP_LOWER_BOUNDARY) { + LOG_ERR("sym '%s': relocation %u out of range (%#x -> %#x)\n", + symname, rel_index, loc, sym_base_addr); + ret = -ENOEXEC; + } else { + sign = (*offset >> 24) & 1; + j1 = sign ^ (~(*offset >> 23) & 1); + j2 = sign ^ (~(*offset >> 22) & 1); + upper = (uint16_t)((upper & MASK_THM_BW_11110) | (sign << BIT_THM_BW_S) | + ((*offset >> 12) & MASK_THM_BW_IMM10)); + lower = (uint16_t)((lower & (MASK_THM_BL_10|MASK_THM_BL_1)) | + (j1 << BIT_THM_BL_J1) | (j2 << BIT_THM_BL_J2) | + ((*offset >> 1) & MASK_THM_BL_IMM11)); + + *(uint16_t *)loc = OPCODE2THM16MEM(upper); + *(uint16_t *)(loc + 2) = OPCODE2THM16MEM(lower); + ret = 0; + } + + return ret; +} + +static void decode_thm_movs(uint32_t rel_index, elf_word reloc_type, uint32_t loc, + uint32_t sym_base_addr, const char *symname, int32_t *offset) +{ + uint32_t upper, lower; + + upper = MEM2THM16OPCODE(*(uint16_t *)loc); + lower = MEM2THM16OPCODE(*(uint16_t *)(loc + 2)); + + /** + * MOVT/MOVW instructions encoding in Thumb-2 + */ + *offset = ((upper & MASK_THM_MOV_IMM4) << 12) | + ((upper & MASK_THM_MOV_I) << 1) | + ((lower & MASK_THM_MOV_IMM3) >> 4) | (lower & MASK_THM_MOV_IMM8); + *offset = sign_extend(*offset, 15); + *offset += sym_base_addr; + + if (reloc_type == R_ARM_THM_MOVT_PREL || reloc_type == R_ARM_THM_MOVW_PREL_NC) { + *offset -= loc; + } + if (reloc_type == R_ARM_THM_MOVT_ABS || reloc_type == R_ARM_THM_MOVT_PREL) { + *offset >>= 16; + } + + upper = (uint16_t)((upper & (MASK_THM_MOV_11110|MASK_THM_MOV_100100)) | + ((*offset & (MASK_THM_MOV_IMM4<<12)) >> 12) | + ((*offset & (MASK_THM_MOV_I<<1)) >> 1)); + lower = (uint16_t)((lower & (MASK_THM_MOV_0|MASK_THM_MOV_RD)) | + ((*offset & MASK_THM_MOV_IMM3) << 4) | + (*offset & MASK_THM_MOV_IMM8)); + *(uint16_t *)loc = OPCODE2THM16MEM(upper); + *(uint16_t *)(loc + 2) = OPCODE2THM16MEM(lower); } /** - * @brief Architecture specific function for relocating partially linked (static) elf - * - * Elf files contain a series of relocations described in a section. These relocation - * instructions are architecture specific and each architecture supporting extensions - * must implement this. + * loc = dstsec->sh_addr + rel->r_offset; * - * The relocation codes for arm are well documented - * https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst#relocation + * loc : addr of opcode + * sym_base_addr : addr of symbol if undef else addr of symbol section + sym value + * symname: symbol name */ -void arch_elf_relocate(elf_rela_t *rel, uintptr_t opaddr, uintptr_t opval) +static int apply_relocate(uint32_t rel_index, elf_word reloc_type, uint32_t loc, + uint32_t sym_base_addr, const char *symname, + uintptr_t load_bias) { - elf_word reloc_type = ELF32_R_TYPE(rel->r_info); + int ret = 0; + int32_t offset; + + LOG_DBG("%s:%u %d %x %x %s", __func__, rel_index, reloc_type, loc, sym_base_addr, symname); switch (reloc_type) { + case R_ARM_NONE: + /* no-op */ + break; + case R_ARM_ABS32: - /* Add the addend stored at opaddr to opval */ - opval += *((uint32_t *)opaddr); + /* fall-through */ + case R_ARM_TARGET1: + *(uint32_t *)loc += sym_base_addr; + break; + + case R_ARM_PC24: + /* fall-through */ + case R_ARM_CALL: + /* fall-through */ + case R_ARM_JUMP24: + ret = decode_jumps(rel_index, reloc_type, loc, sym_base_addr, symname, &offset); + break; + + case R_ARM_V4BX: +#ifdef CONFIG_LLEXT_ARM_V4BX + /** + * Preserve Rm and the condition code. Alter + * other bits to re-code instruction as + * MOV PC,Rm. + */ + *(uint32_t *)loc &= OPCODE2ARMMEM(0xf000000f); + *(uint32_t *)loc |= OPCODE2ARMMEM(0x01a0f000); +#endif + break; + + case R_ARM_PREL31: + ret = decode_prel31(rel_index, reloc_type, loc, sym_base_addr, + symname, &offset); + break; + + case R_ARM_REL32: + *(uint32_t *)loc += sym_base_addr - loc; + break; - /* Update the absolute address of a load/store instruction */ - *((uint32_t *)opaddr) = (uint32_t)opval; + case R_ARM_MOVW_ABS_NC: + /* fall-through */ + case R_ARM_MOVT_ABS: + /* fall-through */ + case R_ARM_MOVW_PREL_NC: + /* fall-through */ + case R_ARM_MOVT_PREL: + decode_movs(rel_index, reloc_type, loc, sym_base_addr, symname, &offset); break; + case R_ARM_THM_CALL: - /* Decode the initial addend */ - int32_t addend = arm_bl_blx_decode_addend(opaddr); + /* fall-through */ + case R_ARM_THM_JUMP24: + ret = decode_thm_jumps(rel_index, reloc_type, loc, sym_base_addr, symname, &offset); + break; - /* Calculate and add the branch offset (addend) */ - addend += ((int32_t)opval) - ((int32_t)opaddr); + case R_ARM_THM_MOVW_ABS_NC: + /* fall-through */ + case R_ARM_THM_MOVT_ABS: + /* fall-through */ + case R_ARM_THM_MOVW_PREL_NC: + /* fall-through */ + case R_ARM_THM_MOVT_PREL: + decode_thm_movs(rel_index, reloc_type, loc, sym_base_addr, symname, &offset); + break; - /* Encode the addend */ - arm_bl_blx_encode_addend(opaddr, addend); + case R_ARM_RELATIVE: + *(uint32_t *)loc += load_bias; break; - default: - LOG_DBG("Unsupported ARM elf relocation type %d at address %lx", - reloc_type, opaddr); + + case R_ARM_GLOB_DAT: + *(uint32_t *)loc = sym_base_addr; break; + + case R_ARM_JUMP_SLOT: + *(uint32_t *)loc = sym_base_addr; + break; + + default: + LOG_ERR("unknown relocation: %u\n", reloc_type); + ret = -ENOEXEC; } + + return ret; +} + +/** + * @brief Architecture specific function for relocating partially linked (static) elf + * + * Elf files contain a series of relocations described in a section. These relocation + * instructions are architecture specific and each architecture supporting extensions + * must implement this. + * + * The relocation codes for arm are well documented + * https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst#relocation + */ +int32_t arch_elf_relocate(elf_rela_t *rel, uint32_t rel_index, uintptr_t loc, + uintptr_t sym_base_addr, const char *symname, uintptr_t load_bias) +{ + elf_word reloc_type = ELF32_R_TYPE(rel->r_info); + + return apply_relocate(rel_index, reloc_type, (uint32_t)loc, + (uint32_t)sym_base_addr, symname, load_bias); } diff --git a/arch/arm/core/mpu/arm_core_mpu.c b/arch/arm/core/mpu/arm_core_mpu.c index 2fb0f141125b..d8bdd611d2e1 100644 --- a/arch/arm/core/mpu/arm_core_mpu.c +++ b/arch/arm/core/mpu/arm_core_mpu.c @@ -62,6 +62,10 @@ extern char __ram_text_reloc_start[]; extern char __ram_text_reloc_size[]; #endif +#if defined(CONFIG_LLEXT) +extern char __llext_heap_start[]; +#endif + static const struct z_arm_mpu_partition static_regions[] = { #if defined(CONFIG_COVERAGE_GCOV) && defined(CONFIG_USERSPACE) { diff --git a/cmake/modules/llext.cmake b/cmake/modules/llext.cmake new file mode 100644 index 000000000000..ae928ec137ef --- /dev/null +++ b/cmake/modules/llext.cmake @@ -0,0 +1,135 @@ +# Copyright (c) 2024 Schneider Electric +# SPDX-License-Identifier: Apache-2.0 + +include_guard(GLOBAL) + +# Add a macro that add a llext extension. +# macro is based on zephyr_library template +# +# output an elf file usable by llext +# +# Code can be linked by 2 differents ways: +# 1. using partial link (-r flag in gcc and -fno-pic) (default) +# 2. using shared link (-shared flag in gcc and -fpic) +# +# The code will be compiled with mostly the same C compiler flags used +# in the Zephyr build, but with some important modifications. +# Toolchain flag are automatically inherited and filtered. +# +# NOPIC: +# -mlong-calls flag is always added +# libc is detected from zephyr configuration and libc function code is embedded in extension +# PIC: +# code is compiled and linked with -fpic flag +# libc is detected from zephyr configuration and libc function are called. They are external symbol for extension. +# +# Example usage: +# zephyr_llext(hello_world) +# zephyr_llext_sources(hello_world.c hello_world2.c) +# zephyr_llext_include_directories(.) +# will compile the source file hello_world.c and hello_world2.c to +# ${PROJECT_BINARY_DIR}/hello_world.llext +# + + +macro(zephyr_llext name) + set(LLEXT_IS_PIC no) + set(ZEPHYR_CURRENT_LLEXT ${name}) + set(ZEPHYR_LLEXT_LINKER_SCRIPT ${ZEPHYR_BASE}/include/zephyr/linker/llext.ld) + + # ARGN is not a variable: assign its value to a variable + set(ExtraMacroArgs ${ARGN}) + # Get the length of the list + list(LENGTH ExtraMacroArgs NumExtraMacroArgs) + # Execute the following block only if the length is > 0 + if(NumExtraMacroArgs GREATER 0) + foreach(ExtraArg ${ExtraMacroArgs}) + if(${ExtraArg} STREQUAL PIC) + set(LLEXT_IS_PIC yes) + endif() + endforeach() + endif() + + add_executable(${ZEPHYR_CURRENT_LLEXT}) + + set_property(TARGET ${ZEPHYR_CURRENT_LLEXT} APPEND PROPERTY LINK_DEPENDS ${ZEPHYR_LLEXT_LINKER_SCRIPT}) + + target_compile_definitions(${ZEPHYR_CURRENT_LLEXT} + PRIVATE $ + PRIVATE ZEPHYR_LLEXT + ) + + target_compile_options(${ZEPHYR_CURRENT_LLEXT} + PRIVATE $ + ) + + if(LLEXT_IS_PIC) + target_compile_options(${ZEPHYR_CURRENT_LLEXT} PRIVATE -fpic -fpie) + else() + if("${ARCH}" STREQUAL "arm") + target_compile_options(${ZEPHYR_CURRENT_LLEXT} PRIVATE -mlong-calls) + endif() + endif() + + target_include_directories(${ZEPHYR_CURRENT_LLEXT} + PUBLIC $ + ) + target_include_directories(${ZEPHYR_CURRENT_LLEXT} SYSTEM + PUBLIC $ + ) + + # copy toolchain link flag + set(LD_FLAGS ${TOOLCHAIN_LD_FLAGS}) + list(REMOVE_ITEM LD_FLAGS NO_SPLIT) + + set_target_properties(${ZEPHYR_CURRENT_LLEXT} PROPERTIES LINK_DEPENDS ${ZEPHYR_LLEXT_LINKER_SCRIPT}) + get_property(LIBC_LINK_LIBRARIES TARGET zephyr_interface PROPERTY LIBC_LINK_LIBRARIES) + + if(LLEXT_IS_PIC) + # to use -shared, we need libc build with -fpic or not used libc + target_link_options(${ZEPHYR_CURRENT_LLEXT} PRIVATE -shared) + else() + target_link_options(${ZEPHYR_CURRENT_LLEXT} PRIVATE -r) + endif() + + target_link_options(${ZEPHYR_CURRENT_LLEXT} PRIVATE + -n # disable section alignment + -e start # no entry point + -T ${ZEPHYR_LLEXT_LINKER_SCRIPT} + -Wl,-Map=$.map + ${LD_FLAGS} + ) + if(LLEXT_IS_PIC) + # libc is not linked shared so we cannot link with it + target_link_options(${ZEPHYR_CURRENT_LLEXT} PRIVATE -nolibc -nostartfiles -fpic -fpie) + target_link_libraries(${ZEPHYR_CURRENT_LLEXT} PRIVATE gcc) + else() + # TODO add newlib and newlib nano support + if(CONFIG_PICOLIBC) + target_sources(${ZEPHYR_CURRENT_LLEXT} PRIVATE ${ZEPHYR_BASE}/lib/libc/picolibc/libc-hooks.c) + target_link_options(${ZEPHYR_CURRENT_LLEXT} PRIVATE -DPICOLIBC_INTEGER_PRINTF_SCANF) + endif() + + target_link_libraries(${ZEPHYR_CURRENT_LLEXT} PRIVATE ${LIBC_LINK_LIBRARIES}) + endif() + + # strip llext + add_custom_target(${ZEPHYR_CURRENT_LLEXT}.llext ALL + COMMAND ${CMAKE_OBJCOPY} --strip-unneeded $ ${ZEPHYR_CURRENT_LLEXT}.llext + DEPENDS ${ZEPHYR_CURRENT_LLEXT} + ) + + add_dependencies(${ZEPHYR_CURRENT_LLEXT} + zephyr_interface + zephyr_generated_headers + ) + +endmacro() + +function(zephyr_llext_sources source) + target_sources(${ZEPHYR_CURRENT_LLEXT} PRIVATE ${source} ${ARGN}) +endfunction() + +function(zephyr_llext_include_directories) + target_include_directories(${ZEPHYR_CURRENT_LLEXT} PRIVATE ${ARGN}) +endfunction() diff --git a/cmake/modules/zephyr_default.cmake b/cmake/modules/zephyr_default.cmake index 7472331255b8..ae8869e7f6e8 100644 --- a/cmake/modules/zephyr_default.cmake +++ b/cmake/modules/zephyr_default.cmake @@ -112,6 +112,7 @@ list(APPEND zephyr_cmake_modules kconfig) list(APPEND zephyr_cmake_modules arch_v2) list(APPEND zephyr_cmake_modules soc_v1) list(APPEND zephyr_cmake_modules soc_v2) +list(APPEND zephyr_cmake_modules llext) foreach(component ${SUB_COMPONENTS}) if(NOT ${component} IN_LIST zephyr_cmake_modules) diff --git a/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld b/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld index 947c18de3134..e934cc96be4b 100644 --- a/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld +++ b/include/zephyr/arch/arm/cortex_a_r/scripts/linker.ld @@ -274,6 +274,18 @@ SECTIONS _app_smem_rom_start = LOADADDR(_APP_SMEM_SECTION_NAME); #endif /* CONFIG_USERSPACE */ +#ifdef CONFIG_LLEXT + SECTION_PROLOGUE(_LLEXT_SECTION_NAME,(NOLOAD),) + { + . = ALIGN(4); + __llext_heap_start = .; + KEEP(*(*.kheap_buf_llext_heap)); + __llext_heap_end = .; + __llext_heap_size = __llext_heap_end - __llext_heap_start; + } GROUP_NOLOAD_LINK_IN(RAMABLE_REGION, RAMABLE_REGION) +#endif + + SECTION_DATA_PROLOGUE(_BSS_SECTION_NAME,(NOLOAD), BSS_ALIGN) { /* diff --git a/include/zephyr/arch/arm/cortex_m/scripts/linker.ld b/include/zephyr/arch/arm/cortex_m/scripts/linker.ld index d061468d2ac1..d9bba6354659 100644 --- a/include/zephyr/arch/arm/cortex_m/scripts/linker.ld +++ b/include/zephyr/arch/arm/cortex_m/scripts/linker.ld @@ -338,6 +338,17 @@ SECTIONS __data_region_end = .; +#ifdef CONFIG_LLEXT + SECTION_PROLOGUE(_LLEXT_SECTION_NAME,(NOLOAD),) + { + . = ALIGN(4); + __llext_heap_start = .; + KEEP(*(*.kheap_buf_llext_heap)); + __llext_heap_end = .; + __llext_heap_size = __llext_heap_end - __llext_heap_start; + } GROUP_NOLOAD_LINK_IN(RAMABLE_REGION, RAMABLE_REGION) +#endif + #ifndef CONFIG_USERSPACE SECTION_DATA_PROLOGUE(_BSS_SECTION_NAME,(NOLOAD),) { diff --git a/include/zephyr/linker/llext.ld b/include/zephyr/linker/llext.ld new file mode 100644 index 000000000000..466532399a54 --- /dev/null +++ b/include/zephyr/linker/llext.ld @@ -0,0 +1,140 @@ +/* Specify the memory areas */ +MEMORY +{ + all (RWX) : ORIGIN = 0x00000000, LENGTH = 128k +} + +/* Define output sections */ +SECTIONS +{ + /* The startup code goes first into internal flash */ + .text : + { + . = ALIGN(4); + *(.literal .literal.*) + *(.text) /* .text sections (code) */ + *(.text*) /* .text* sections (code) */ + . = ALIGN(4); + KEEP(*(SORT_BY_NAME(._log_*.static.*))) + . = ALIGN(4); + } > all + + .rodata : + { + . = ALIGN(4); + __init_APPLICATION_start = .; KEEP(*(SORT(.z_init_APPLICATION?_*))); KEEP(*(SORT(.z_init_APPLICATION??_*))); + __init_end = .; + . = ALIGN(4); __static_thread_data_list_start = .; KEEP(*(SORT_BY_NAME(.__static_thread_data.static.*))); __static_thread_data_list_end = .; + *(.rodata) /* .rodata sections (constants, strings, etc.) */ + *(.rodata*) /* .rodata* sections (constants, strings, etc.) */ + . = ALIGN(4); + } > all + + .data : + { + . = ALIGN(4); + *(.data) /* .data sections */ + *(.data*) /* .data* sections */ + . = ALIGN(4); + *(SORT_BY_NAME(._k_*.static.*)) + . = ALIGN(4); + } > all + + .got : + { + . = ALIGN(4); + app_sgot = .; + *(.got) + *(.got.plt) + app_egot = .; + . = ALIGN(4); + } > all + + /* .got.loc section (xtensa only) */ + .got.loc : + { + . = ALIGN(4); + *(.got.loc) + . = ALIGN(4); + } >all + + + /* PLT section contains code for accessing the dynamically linked functions + * ie funtions from shared libraries in a postion independent manner */ + .plt : + { + . = ALIGN(4); + *(.plt) + . = ALIGN(4); + } >all + + /* List of exported symbols in extension */ + .exported_sym : + { + . = ALIGN(4); + KEEP(*(.exported_sym)) + . = ALIGN(4); + } > all + + .bss : + { + . = ALIGN(4); + *(.bss) + *(.bss*) + *(.noinit) + *(.noinit*) + . = ALIGN(4); + } > all + + /** This section will be used by the debugger and disassembler to get more information + * about raw data present in the code. + * Indeed, it may be required to add some padding at some points in the code + * in order to align a branch/jump destination on a particular bound. + * Padding these instructions will generate null bytes that shall be + * interpreted as data, and not code by the debugger or disassembler. + * This section will only be present in the ELF file, not in the final binary + * For more details, check GCC-212 + */ + .xt.prop 0 : + { + KEEP (*(.xt.prop)) + } + + .xt.lit 0 : + { + KEEP (*(.xt.lit)) + KEEP (*(.xt.lit.*)) + } + + + + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + /* useless for llext but usefull for inspecting elf with standard tools (nm, readelf, objdump). */ + .hash : { *(.hash) } + .dynamic : { *(.dynamic) } + + /* Remove information from the standard libraries */ + /*/DISCARD/ : + { + libc.a ( * ) + libm.a ( * ) + libgcc.a ( * ) + }*/ + + /* https://github.com/ARM-software/abi-aa/blob/main/aaelf32/aaelf32.rst#build-attributes */ + /DISCARD/ : { *(.ARM.attributes) } + + /* + .ARM.extab and .ARM.exidx are related to unwinding. + You can find more information here http://infocenter.arm.com/help/topic/com.arm.doc.ihi0044e/index.html. + You don't need them if you don't care about unwinding (unwinding is useful for C++ exception and for debugging) + */ + /DISCARD/ : { *(.ARM.extab* .gnu.linkonce.armextab.*) } + /DISCARD/ : { *(.ARM.exidx*) } + + /* discard .comment section */ + /DISCARD/ : { *(.comment) } + +} + diff --git a/include/zephyr/linker/sections.h b/include/zephyr/linker/sections.h index 2f1049d09f31..c63a5981bfca 100644 --- a/include/zephyr/linker/sections.h +++ b/include/zephyr/linker/sections.h @@ -134,6 +134,10 @@ #endif /* _ASMLANGUAGE */ +#ifdef CONFIG_LLEXT +#define _LLEXT_SECTION_NAME llext_heap_noinit +#endif + #include #endif /* ZEPHYR_INCLUDE_LINKER_SECTIONS_H_ */ diff --git a/include/zephyr/llext/elf.h b/include/zephyr/llext/elf.h index 53445bfb6bdb..612e5bc8c607 100644 --- a/include/zephyr/llext/elf.h +++ b/include/zephyr/llext/elf.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2023 Intel Corporation + * Copyright (c) 2024 Schneider Electric * * SPDX-License-Identifier: Apache-2.0 * @@ -370,10 +371,29 @@ struct elf64_rela { #define R_ARM_PC24 1 #define R_ARM_ABS32 2 #define R_ARM_REL32 3 -#define R_ARM_COPY 4 -#define R_ARM_THM_CALL 10 +#define R_ARM_COPY 20 +#define R_ARM_GLOB_DAT 21 +#define R_ARM_JUMP_SLOT 22 +#define R_ARM_RELATIVE 23 #define R_ARM_CALL 28 +#define R_ARM_JUMP24 29 +#define R_ARM_TARGET1 38 #define R_ARM_V4BX 40 +#define R_ARM_PREL31 42 +#define R_ARM_MOVW_ABS_NC 43 +#define R_ARM_MOVT_ABS 44 +#define R_ARM_MOVW_PREL_NC 45 +#define R_ARM_MOVT_PREL 46 +#define R_ARM_ALU_PC_G0_NC 57 +#define R_ARM_ALU_PC_G1_NC 59 +#define R_ARM_LDR_PC_G2 63 + +#define R_ARM_THM_CALL 10 +#define R_ARM_THM_JUMP24 30 +#define R_ARM_THM_MOVW_ABS_NC 47 +#define R_ARM_THM_MOVT_ABS 48 +#define R_ARM_THM_MOVW_PREL_NC 49 +#define R_ARM_THM_MOVT_PREL 50 #define R_XTENSA_NONE 0 #define R_XTENSA_32 1 diff --git a/include/zephyr/llext/file_loader.h b/include/zephyr/llext/file_loader.h new file mode 100644 index 000000000000..ca0c749f96de --- /dev/null +++ b/include/zephyr/llext/file_loader.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 Schneider Electric + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_LLEXT_FILE_LOADER_H +#define ZEPHYR_LLEXT_FILE_LOADER_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief LLEXT file loader + * @defgroup llext_file_loader Linkable loadable extensions file loader + * @ingroup llext + * @{ + */ + +/** + * @brief An extension loader from a provided file containing an ELF + */ +struct llext_file_loader { + /** Extension loader */ + struct llext_loader loader; + + /** @cond ignore */ + struct fs_file_t *fd; + /** @endcond */ +}; + +/** @cond ignore */ +int llext_file_read(struct llext_loader *ldr, void *buf, size_t len); +int llext_file_seek(struct llext_loader *ldr, size_t pos); +/** @endcond */ + +/** + * @brief Initialize an extension buf loader + * + * @param _fd File descriptor containing an ELF binary + * @param _buf_len Buffer length in bytes + */ +#define LLEXT_FILE_LOADER(_fd) \ + { \ + .loader = { \ + .read = llext_file_read, \ + .seek = llext_file_seek \ + }, \ + .fd = (_fd), \ + } + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_LLEXT_FILE_LOADER_H */ diff --git a/include/zephyr/llext/llext.h b/include/zephyr/llext/llext.h index 0a006dc53b74..f312ab49d9e6 100644 --- a/include/zephyr/llext/llext.h +++ b/include/zephyr/llext/llext.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2023 Intel Corporation + * Copyright (c) 2024 Schneider Electric * * SPDX-License-Identifier: Apache-2.0 */ @@ -30,8 +31,10 @@ extern "C" { */ enum llext_mem { LLEXT_MEM_TEXT, - LLEXT_MEM_DATA, LLEXT_MEM_RODATA, + LLEXT_MEM_DATA, + LLEXT_MEM_GOT, + LLEXT_MEM_PLT, LLEXT_MEM_BSS, LLEXT_MEM_EXPORT, LLEXT_MEM_SYMTAB, @@ -176,6 +179,15 @@ const void * const llext_find_sym(const struct llext_symtable *sym_table, const */ int llext_call_fn(struct llext *ext, const char *sym_name); +#if CONFIG_LLEXT_HEAP_STAT +/** + * @brief Heap info + * + * Printk LLEXT heap info + */ +void llext_print_heap_info(void); +#endif + /** * @brief Add the known memory partitions of the extension to a memory domain * @@ -200,10 +212,15 @@ int llext_add_domain(struct llext *ext, struct k_mem_domain *domain); * or object. * * @param[in] rel Relocation data provided by elf - * @param[in] opaddr Address of operation to rewrite with relocation - * @param[in] opval Value of looked up symbol to relocate - */ -void arch_elf_relocate(elf_rela_t *rel, uintptr_t opaddr, uintptr_t opval); + * @param[in] rel_index Index of relocation + * @param[in] loc Address of operation to rewrite with relocation + * @param[in] sym_base_addr Symbol address + * @param[in] symname Symbol name + * @param[in] load_bias .text load address + * @retval -ENOEXEC invalid relocation + */ +int32_t arch_elf_relocate(elf_rela_t *rel, uint32_t rel_index, uintptr_t loc, + uintptr_t sym_base_addr, const char *symname, uintptr_t load_bias); /** * @brief Find an ELF section diff --git a/include/zephyr/llext/loader.h b/include/zephyr/llext/loader.h index 7eb49d611bed..1089a178f824 100644 --- a/include/zephyr/llext/loader.h +++ b/include/zephyr/llext/loader.h @@ -70,6 +70,7 @@ struct llext_loader { /** @cond ignore */ elf_ehdr_t hdr; + elf_phdr_t phdr; elf_shdr_t sects[LLEXT_MEM_COUNT]; enum llext_mem *sect_map; uint32_t sect_cnt; diff --git a/include/zephyr/llext/symbol.h b/include/zephyr/llext/symbol.h index 993e2c4a5192..588d8109d520 100644 --- a/include/zephyr/llext/symbol.h +++ b/include/zephyr/llext/symbol.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2023 Intel Corporation + * Copyright (c) 2024 Schneider Electric * * SPDX-License-Identifier: Apache-2.0 */ @@ -8,7 +9,6 @@ #define ZEPHYR_LLEXT_SYMBOL_H #include -#include #include #ifdef __cplusplus @@ -74,9 +74,11 @@ struct llext_symtable { * * @param x Symbol to export */ +#define PASTER(x, y) x ## y +#define EVALUATOR(x, y) PASTER(x, y) #define EXPORT_SYMBOL(x) \ - static const STRUCT_SECTION_ITERABLE(llext_const_symbol, x ## _sym) = { \ - .name = STRINGIFY(x), .addr = (const void *)&x, \ + static const STRUCT_SECTION_ITERABLE(llext_const_symbol, EVALUATOR(x, _sym)) = { \ + .name = STRINGIFY(x), .addr = (const void*)&x, \ } #define LL_EXTENSION_SYMBOL(x) \ diff --git a/samples/subsys/llext/fs_loader/CMakeLists.txt b/samples/subsys/llext/fs_loader/CMakeLists.txt new file mode 100644 index 000000000000..79edbd392e65 --- /dev/null +++ b/samples/subsys/llext/fs_loader/CMakeLists.txt @@ -0,0 +1,27 @@ +# Copyright (c) 2024 Schneider Electric +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) + +project(fs_loader) + +target_sources(app PRIVATE src/main.c src/log_extension.c src/export.c) + +# declare extension +zephyr_llext(hello_world) +zephyr_llext_sources(hello_world.llext/hello_world.c) + +zephyr_llext(hello_world_pic PIC) +zephyr_llext_sources(hello_world.llext/hello_world.c) + +set(FAT_IMG ${CMAKE_CURRENT_BINARY_DIR}/llext_vfat.img) + +# generate image +add_custom_target(vfat.img ALL + COMMAND ${CMAKE_CURRENT_LIST_DIR}/makeimg.sh ${FAT_IMG} ${CMAKE_CURRENT_BINARY_DIR}/hello_world.llext ${CMAKE_CURRENT_BINARY_DIR}/hello_world_pic.llext + WORKING_DIRECTORY ${ZEPHYR_BASE} + SOURCES ${CMAKE_CURRENT_LIST_DIR}/makeimg.sh + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/hello_world.llext ${CMAKE_CURRENT_BINARY_DIR}/hello_world_pic.llext +) diff --git a/samples/subsys/llext/fs_loader/Kconfig b/samples/subsys/llext/fs_loader/Kconfig new file mode 100644 index 000000000000..fabec4ef85c0 --- /dev/null +++ b/samples/subsys/llext/fs_loader/Kconfig @@ -0,0 +1,17 @@ +# +# Copyright (c) 2024 Schneider Electric +# +# SPDX-License-Identifier: Apache-2.0 +# + +config APP_WIPE_STORAGE + bool "Option to clear the flash area before mounting" + help + Use this to force an existing file system to be created. + +config CONFIG_LLEXT_PIC + bool "Build LL extension with PIC enabled" + help + Use this to force building LL extension in Position Independant Code. + +source "Kconfig.zephyr" diff --git a/samples/subsys/llext/fs_loader/README.rst b/samples/subsys/llext/fs_loader/README.rst new file mode 100644 index 000000000000..6f79152c9d46 --- /dev/null +++ b/samples/subsys/llext/fs_loader/README.rst @@ -0,0 +1,71 @@ +.. zephyr:code-sample:: llext-fs-loader + :name: Linkable loadable extensions filesystem module + :relevant-api: llext + + Manage loadable extensions using fielsystem. + +Overview +******** + +This example provides filesystem access to the :ref:`llext` system and provides the +ability to manage loadable code extensions in the filesystem. + +Requirements +************ + +A board with a supported llext architecture and filesystem. + +Building +******** + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/llext/fs_loader + :board: nucleo_l496zg + :goals: build + :compact: + +Running +******* + +Once the board has booted, you will be presented with a shell console. +All the llext system related commands are available as sub-commands of llext +which can be seen with llext help + +.. code-block:: console + + uart:~$ llext help + llext - Loadable extension commands + Subcommands: + list :List loaded extensions and their size in memory + load_hex :Load an elf file encoded in hex directly from the shell input. + Syntax: + + load_fs :Load an elf file encoded in elf directly from a filesystem. + Syntax: + + unload :Unload an extension by name. Syntax: + + list_symbols :List extension symbols. Syntax: + + call_fn :Call extension function with prototype void fn(void). Syntax: + + +A hello world llext extension can be found in hello_world.llext folder. + +This is built in parallel of zephyr and must be copied in filesystem. + + + +.. code-block:: console + + uart:~$ llext load_fs hello_world /NAND:/hello_world.llext + +This extension can then be seen in the list of loaded extensions (`list`), its symbols printed +(`list_symbols`), and the hello_world function which the extension exports can be called and +run (`call_fn`). + +.. code-block:: console + + uart:~$ llext call_fn hello_world start + hello world from new thread + thread id is .... diff --git a/samples/subsys/llext/fs_loader/VERSION b/samples/subsys/llext/fs_loader/VERSION new file mode 100644 index 000000000000..ba0612991f4c --- /dev/null +++ b/samples/subsys/llext/fs_loader/VERSION @@ -0,0 +1,5 @@ +VERSION_MAJOR = 0 +VERSION_MINOR = 1 +PATCHLEVEL = 0 +VERSION_TWEAK = 0 +EXTRAVERSION = fs_llext \ No newline at end of file diff --git a/samples/subsys/llext/fs_loader/boards/nucleo_l496zg.overlay b/samples/subsys/llext/fs_loader/boards/nucleo_l496zg.overlay new file mode 100644 index 000000000000..0bd2802ccdd9 --- /dev/null +++ b/samples/subsys/llext/fs_loader/boards/nucleo_l496zg.overlay @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2024 Schneider Electric + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&usbotg_fs { + pinctrl-0 = <&usb_otg_fs_dm_pa11 &usb_otg_fs_dp_pa12>; + pinctrl-names = "default"; + status = "okay"; +}; +/* hsi48 must be enabled for usb */ +&clk_hsi48 { + status = "okay"; +}; + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + code_partition: partition@0 { + label = "code"; + reg = <0x0000000 DT_SIZE_K(1024-128)>; + read-only; + }; + + /* + * The final 128 KiB is reserved for the application. + * Storage partition will be used by FCB/LittleFS/NVS + * if enabled. + */ + storage_partition: partition@E0000 { + label = "storage"; + reg = <0x00E0000 DT_SIZE_K(128)>; + }; + }; +}; + +/ { + msc_disk0 { + status="okay"; + compatible = "zephyr,flash-disk"; + partition = <&storage_partition>; + disk-name = "NAND"; + /* cache-size == page erase size */ + cache-size = <2048>; + }; +}; \ No newline at end of file diff --git a/samples/subsys/llext/fs_loader/boards/stm32f4_disco.overlay b/samples/subsys/llext/fs_loader/boards/stm32f4_disco.overlay new file mode 100644 index 000000000000..ea0d42fb06d5 --- /dev/null +++ b/samples/subsys/llext/fs_loader/boards/stm32f4_disco.overlay @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Schneider Electric + * + * SPDX-License-Identifier: Apache-2.0 + */ + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + code_partition: partition@0 { + label = "code"; + reg = <0x0000000 DT_SIZE_K(1024-128)>; + read-only; + }; + + /* + * The final 128 KiB is reserved for the application. + * Storage partition will be used by FCB/LittleFS/NVS + * if enabled. + */ + storage_partition: partition@E0000 { + label = "storage"; + reg = <0x00E0000 DT_SIZE_K(128)>; + read-only; + }; + }; +}; + +/ { + msc_disk0 { + status="okay"; + compatible = "zephyr,flash-disk"; + partition = <&storage_partition>; + disk-name = "NAND"; + /* cache-size == page erase size */ + cache-size = <2048>; + }; +}; \ No newline at end of file diff --git a/samples/subsys/llext/fs_loader/hello_world.llext/hello_world.c b/samples/subsys/llext/fs_loader/hello_world.llext/hello_world.c new file mode 100644 index 000000000000..bb6814c24dd6 --- /dev/null +++ b/samples/subsys/llext/fs_loader/hello_world.llext/hello_world.c @@ -0,0 +1,70 @@ +/** + * Copyright (c) 2024 Schneider Electric + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include + + +#include +/* In all files comprising the module but one */ +LOG_MODULE_DECLARE(extension, LOG_LEVEL_INF); + + +/* semaphore to end extension thread */ +struct k_sem sem_end_thread; + + +static void extension_thread(void *p1, void *p2, void *p3) +{ + LOG_INF("llext thread: start"); + + while (true) { + int ret = k_sem_take(&sem_end_thread, K_MSEC(1000)); + if (ret != 0) { + /* timeout */ + k_tid_t thread_id = k_sched_current_thread_query(); + printk("Hello World from extension thread %p\n", thread_id); + } else { + /* quit thread */ + break; + } + } + + LOG_INF("llext thread: end"); +} + +#define STACK_SIZE 2048 +#define PRIORITY 5 + +K_THREAD_STACK_DEFINE(hello_world_stack, STACK_SIZE); +struct k_thread new_thread; + +void start(void) +{ + LOG_INF("Starting extension..."); + + k_sem_init(&sem_end_thread, 0, 10); + + k_tid_t tid = k_thread_create(&new_thread, hello_world_stack, STACK_SIZE, + extension_thread, NULL, NULL, NULL, PRIORITY, + K_ESSENTIAL, K_MSEC(0)); +#ifdef CONFIG_THREAD_NAME + strncpy(new_thread.name, "extension", CONFIG_THREAD_MAX_NAME_LEN - 1); + /* Ensure NULL termination, truncate if longer */ + new_thread.name[CONFIG_THREAD_MAX_NAME_LEN - 1] = '\0'; + +#endif + + LOG_INF("Extension Started tid=%p", tid); +} + +void stop(void) +{ + k_sem_give(&sem_end_thread); +} + +LL_EXTENSION_SYMBOL(start); +LL_EXTENSION_SYMBOL(stop); diff --git a/samples/subsys/llext/fs_loader/makeimg.sh b/samples/subsys/llext/fs_loader/makeimg.sh new file mode 100644 index 000000000000..b6dfaf372127 --- /dev/null +++ b/samples/subsys/llext/fs_loader/makeimg.sh @@ -0,0 +1,47 @@ +#!/bin/sh + +# +# Copyright (c) 2024 Schneider Electric. +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Create a FAT12 disk image to flash in memory +# This tool requires the following to be available on the host system: +# +# - dosfstools +# - mtools +set -e + +PROGNAME=$(basename "$0") +OUTPUT=$1 +LOGICAL_SECTOR_SIZE=512 +DISK_SIZE_KB=128 + +usage() { + printf "Usage:\n\t%s output.img input1 [input2] [...]" "$PROGNAME" +} + +die() { + >&2 printf "%s ERROR: " "$PROGNAME" + # We want die() to be usable exactly like printf + # shellcheck disable=SC2059 + >&2 printf "%s\n" "$@" + exit 1 +} + +if [ $# -lt 2 ]; then + usage + die "Not enough arguments." +fi + +shift +INPUTS="$*" + +printf "Creating empty '%s' image..." "$(basename "$OUTPUT")" +dd if=/dev/zero of="$OUTPUT" bs=1k count=${DISK_SIZE_KB} status=none || die "dd to $OUTPUT" +printf "done\nCreating FAT partition image..." +mkfs.fat -F12 -S"$LOGICAL_SECTOR_SIZE" "$OUTPUT" >/dev/null || die "mkfs.vfat failed" +printf "done\nCopying input files..." +mcopy -i "$OUTPUT" "$INPUTS" "::/" || die "mcopy $OUTPUT $INPUTS" +printf "done\n" diff --git a/samples/subsys/llext/fs_loader/prj.conf b/samples/subsys/llext/fs_loader/prj.conf new file mode 100644 index 000000000000..df4380d98ae4 --- /dev/null +++ b/samples/subsys/llext/fs_loader/prj.conf @@ -0,0 +1,64 @@ +# heap size +CONFIG_HEAP_MEM_POOL_SIZE=2048 +CONFIG_SYS_HEAP_RUNTIME_STATS=y + +# log config +CONFIG_LOG=y +CONFIG_LOG_BUFFER_SIZE=512 +CONFIG_LOG_BLOCK_IN_THREAD=y +CONFIG_LOG_BLOCK_IN_THREAD_TIMEOUT_MS=1000 +CONFIG_LOG_PROCESS_THREAD_SLEEP_MS=10 +CONFIG_LOG_PROCESS_THREAD_CUSTOM_PRIORITY=y +CONFIG_LOG_PROCESS_THREAD_PRIORITY=10 + +CONFIG_GPIO=y + + +# Optionally force the file system to be recreated +#CONFIG_APP_WIPE_STORAGE=y +CONFIG_FLASH=y +CONFIG_FLASHDISK_LOG_LEVEL_WRN=y +CONFIG_FILE_SYSTEM=y +CONFIG_FILE_SYSTEM_SHELL=y +CONFIG_DISK_ACCESS=y +CONFIG_DISK_DRIVERS=y +CONFIG_DISK_DRIVER_FLASH=y +CONFIG_FAT_FILESYSTEM_ELM=y +CONFIG_FS_FATFS_LFN=y + +CONFIG_FLASH_MAP=y +CONFIG_FLASH_PAGE_LAYOUT=y +CONFIG_FLASH_MAP_SHELL=y +CONFIG_FS_LOG_LEVEL_WRN=y + +# main stack size +CONFIG_MAIN_STACK_SIZE=2048 + + +CONFIG_REBOOT=y + +#shell +CONFIG_SHELL=y +CONFIG_SHELL_CMD_BUFF_SIZE=512 +CONFIG_SHELL_LOG_LEVEL_INF=y +CONFIG_SHELL_STACK_SIZE=2048 + + +#USB related configs +CONFIG_USB_DEVICE_STACK=y +CONFIG_USB_DEVICE_PRODUCT="Zephyr" +CONFIG_USB_DEVICE_PID=0x0008 +CONFIG_USB_DRIVER_LOG_LEVEL_ERR=y +CONFIG_USB_MASS_STORAGE=y +CONFIG_MASS_STORAGE_DISK_NAME="NAND" +CONFIG_USB_DEVICE_LOG_LEVEL_ERR=y +CONFIG_USB_MASS_STORAGE_LOG_LEVEL_ERR=y +CONFIG_USB_DEVICE_INITIALIZE_AT_BOOT=n + + +CONFIG_LLEXT=y +CONFIG_LLEXT_LOG_LEVEL_INF=y + # LLEXT heap size in Kilobytes +CONFIG_LLEXT_HEAP_SIZE=32 +CONFIG_LLEXT_SHELL=y +CONFIG_LLEXT_HEAP_STAT=y diff --git a/samples/subsys/llext/fs_loader/sample.yaml b/samples/subsys/llext/fs_loader/sample.yaml new file mode 100644 index 000000000000..eb335b08928b --- /dev/null +++ b/samples/subsys/llext/fs_loader/sample.yaml @@ -0,0 +1,11 @@ +sample: + description: Loadable extensions with filesystem sample + name: Extension loader filesystem +tests: + sample.llext.filesystem: + tags: llext + harness: keyboard + arch_allow: arm + depends_on: + - usb_device + - filesystem diff --git a/samples/subsys/llext/fs_loader/src/export.c b/samples/subsys/llext/fs_loader/src/export.c new file mode 100644 index 000000000000..82c3b28f9a7f --- /dev/null +++ b/samples/subsys/llext/fs_loader/src/export.c @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 Schneider Electric + * + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +/* export symbol kernel */ +EXPORT_SYMBOL(printk); +EXPORT_SYMBOL(sys_clock_cycle_get_32); +EXPORT_SYSCALL(k_mutex_lock); +EXPORT_SYSCALL(k_mutex_unlock); +EXPORT_SYSCALL(k_sleep); +EXPORT_SYSCALL(k_thread_create); +EXPORT_SYSCALL(k_thread_name_set); +EXPORT_SYSCALL(k_timer_start); +EXPORT_SYSCALL(k_timer_stop); +EXPORT_SYSCALL(k_uptime_ticks); +EXPORT_SYSCALL(z_log_msg_static_create); +EXPORT_SYSCALL(z_log_msg_simple_create_0); +EXPORT_SYSCALL(z_log_msg_simple_create_1); +EXPORT_SYSCALL(z_log_msg_simple_create_2); +EXPORT_SYMBOL(z_timer_expiration_handler); +EXPORT_SYSCALL(k_sched_current_thread_query); +EXPORT_SYMBOL(k_object_access_revoke); +EXPORT_SYSCALL(k_sem_init); +EXPORT_SYSCALL(k_sem_take); +EXPORT_SYSCALL(k_sem_give); + +#ifdef CONFIG_USERSPACE +EXPORT_SYMBOL(z_arm_thread_is_in_user_mode); +EXPORT_SYSCALL(k_object_access_grant); +EXPORT_SYMBOL(k_thread_user_mode_enter); +#ifdef CONFIG_DYNAMIC_THREAD +EXPORT_SYSCALL(k_thread_stack_alloc); +#endif +#ifdef CONFIG_DYNAMIC_OBJECTS +EXPORT_SYSCALL(k_object_alloc); +#endif +#endif + +#ifdef CONFIG_TRACING_USER +EXPORT_SYMBOL(sys_trace_poc_log); +#endif + +#ifdef CONFIG_SEGGER_SYSTEMVIEW +EXPORT_SYMBOL(SEGGER_SYSVIEW_RecordEndCall); +EXPORT_SYMBOL(SEGGER_SYSVIEW_RecordString); +EXPORT_SYMBOL(SEGGER_SYSVIEW_MarkStart); +EXPORT_SYMBOL(SEGGER_SYSVIEW_MarkStop); +EXPORT_SYMBOL(SEGGER_SYSVIEW_PrintfHost); +#endif diff --git a/samples/subsys/llext/fs_loader/src/log_extension.c b/samples/subsys/llext/fs_loader/src/log_extension.c new file mode 100644 index 000000000000..40b5962c4dec --- /dev/null +++ b/samples/subsys/llext/fs_loader/src/log_extension.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2024 Schneider Electric + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define LOG_LEVEL LOG_LEVEL_DBG +#include +LOG_MODULE_REGISTER(extension, LOG_LEVEL); + +#ifdef CONFIG_LLEXT +#include +EXPORT_SYMBOL(log_dynamic_extension); +#endif diff --git a/samples/subsys/llext/fs_loader/src/main.c b/samples/subsys/llext/fs_loader/src/main.c new file mode 100644 index 000000000000..e5ab1320304c --- /dev/null +++ b/samples/subsys/llext/fs_loader/src/main.c @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2024 Schneider Electric + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#if CONFIG_USB_DEVICE_STACK +#include +#include +#include +#endif +#include +#include +#if CONFIG_LLEXT +#include +#include +#include +#endif + + +#define LOG_LEVEL LOG_LEVEL_DBG +#include +LOG_MODULE_REGISTER(app); + + +#if CONFIG_DISK_DRIVER_FLASH +#include +#endif + +#if CONFIG_FAT_FILESYSTEM_ELM +#include +#endif + +#if CONFIG_FILE_SYSTEM_LITTLEFS +#include +FS_LITTLEFS_DECLARE_DEFAULT_CONFIG(storage); +#endif + + +#define CAN_INTERFACE DEVICE_DT_GET(DT_CHOSEN(zephyr_canbus)) +#define CAN_BITRATE (DT_PROP(DT_CHOSEN(zephyr_canbus), bus_speed) / 1000) + + +#define STORAGE_PARTITION storage_partition +#define STORAGE_PARTITION_ID FIXED_PARTITION_ID(STORAGE_PARTITION) + + +static struct fs_mount_t fs_mnt; + +static int setup_flash(struct fs_mount_t *mnt) +{ + int rc = 0; +#if CONFIG_DISK_DRIVER_FLASH + unsigned int id; + const struct flash_area *pfa; + + mnt->storage_dev = (void *)STORAGE_PARTITION_ID; + id = STORAGE_PARTITION_ID; + + rc = flash_area_open(id, &pfa); + printk("Area %u at 0x%x on %s for %u bytes\n", + id, (unsigned int)pfa->fa_off, pfa->fa_dev->name, + (unsigned int)pfa->fa_size); + + if (rc < 0 && IS_ENABLED(CONFIG_APP_WIPE_STORAGE)) { + printk("Erasing flash area ... "); + rc = flash_area_erase(pfa, 0, pfa->fa_size); + printk("%d\n", rc); + } + + if (rc < 0) { + flash_area_close(pfa); + } +#endif + return rc; +} + +static int mount_app_fs(struct fs_mount_t *mnt) +{ + int rc; + +#if CONFIG_FAT_FILESYSTEM_ELM + static FATFS fat_fs; + + mnt->type = FS_FATFS; + mnt->fs_data = &fat_fs; + if (IS_ENABLED(CONFIG_DISK_DRIVER_RAM)) { + mnt->mnt_point = "/RAM:"; + } else if (IS_ENABLED(CONFIG_DISK_DRIVER_SDMMC)) { + mnt->mnt_point = "/SD:"; + } else { + mnt->mnt_point = "/NAND:"; + } + +#elif CONFIG_FILE_SYSTEM_LITTLEFS + mnt->type = FS_LITTLEFS; + mnt->mnt_point = "/lfs"; + mnt->fs_data = &storage; +#endif + rc = fs_mount(mnt); + + return rc; +} + +static void setup_disk(void) +{ + struct fs_mount_t *mp = &fs_mnt; + struct fs_dir_t dir; + struct fs_statvfs sbuf; + int rc; + + fs_dir_t_init(&dir); + + if (IS_ENABLED(CONFIG_DISK_DRIVER_FLASH)) { + rc = setup_flash(mp); + if (rc < 0) { + LOG_ERR("Failed to setup flash area"); + return; + } + } + + if (!IS_ENABLED(CONFIG_FILE_SYSTEM_LITTLEFS) && + !IS_ENABLED(CONFIG_FAT_FILESYSTEM_ELM)) { + LOG_INF("No file system selected"); + return; + } + + rc = mount_app_fs(mp); + if (rc < 0) { + LOG_ERR("Failed to mount filesystem"); + return; + } + + /* Allow log messages to flush to avoid interleaved output */ + k_sleep(K_MSEC(50)); + + printk("Mount %s: %d\n", fs_mnt.mnt_point, rc); + + rc = fs_statvfs(mp->mnt_point, &sbuf); + if (rc < 0) { + printk("FAIL: statvfs: %d\n", rc); + return; + } + + printk("%s: bsize = %lu ; frsize = %lu ;" + " blocks = %lu ; bfree = %lu\n", + mp->mnt_point, + sbuf.f_bsize, sbuf.f_frsize, + sbuf.f_blocks, sbuf.f_bfree); + + rc = fs_opendir(&dir, mp->mnt_point); + printk("%s opendir: %d\n", mp->mnt_point, rc); + + if (rc < 0) { + LOG_ERR("Failed to open directory"); + } + + while (rc >= 0) { + struct fs_dirent ent = { 0 }; + + rc = fs_readdir(&dir, &ent); + if (rc < 0) { + LOG_ERR("Failed to read directory entries"); + break; + } + if (ent.name[0] == 0) { + printk("End of files\n"); + break; + } + printk(" %c %u %s\n", + (ent.type == FS_DIR_ENTRY_FILE) ? 'F' : 'D', + ent.size, + ent.name); + } + + (void)fs_closedir(&dir); +} + +/** + * find all elf file and load it + */ +void load_extension(void) +{ + struct fs_file_t fd; + char extension_names[2][16] = {"hello_world","hello_world_pic"}; + char extension_paths[2][64] = {"/NAND:/hello_world.llext","/NAND:/hello_world_pic.llext"}; + + for (int i=0; i<2; i++) { + char * extension_name = extension_names[i]; + char * extension_path = extension_paths[i]; + LOG_INF("Loading extension %s %s", extension_name, extension_path); + + fs_file_t_init(&fd); + int rc = fs_open(&fd, extension_path, FS_O_READ); + if(rc < 0) { + LOG_ERR("%d extension not found: %s", rc, extension_path); + } else { + struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT; + struct llext_file_loader file_stream = LLEXT_FILE_LOADER(&fd); + struct llext_loader *stream = &file_stream.loader; + + struct llext *m; + int res = llext_load(stream, extension_name, &m, &ldr_parm); + + fs_close(&fd); + + if (res == 0) { + + LOG_INF("Successfully loaded extension %s @ %p", m->name, m->mem[LLEXT_MEM_TEXT]); + // start extension + LOG_INF("Starting extension %s", m->name); + int rc = llext_call_fn(m, "start"); + if(rc != 0) + { + LOG_ERR("Failed to start extension %s", m->name); + } + } else { + LOG_ERR("Failed to load extension %s, return code %d", extension_name, res); + } + } + } +} + + + +/** + * @brief Main application entry point. + * + */ +int main(void) +{ + int ret; + + printf("\n\t\t\x1B[36m====== LLEXT fs_loader sample %s ======\x1B[0m", APP_VERSION_STRING); + + setup_disk(); + +#if defined(CONFIG_USB_DEVICE_STACK_NEXT) + ret = enable_usb_device_next(); +#else + ret = usb_enable(NULL); +#endif + if (ret != 0) { + LOG_ERR("Failed to enable USB"); + return 0; + } +#if defined(CONFIG_USB_MASS_STORAGE) + LOG_INF("The device is put in USB mass storage mode."); +#endif + + LOG_INF("End of boot"); + +#ifdef CONFIG_LLEXT + LOG_INF("Start extension loading"); + load_extension(); + LOG_INF("End of extension loading"); +#endif + + return 0; +} diff --git a/subsys/llext/CMakeLists.txt b/subsys/llext/CMakeLists.txt index b129dc7f9431..2cd91065ad3a 100644 --- a/subsys/llext/CMakeLists.txt +++ b/subsys/llext/CMakeLists.txt @@ -1,5 +1,5 @@ if(CONFIG_LLEXT) zephyr_library() - zephyr_library_sources(llext.c llext_export.c buf_loader.c) + zephyr_library_sources(llext.c llext_export.c buf_loader.c file_loader.c) zephyr_library_sources_ifdef(CONFIG_LLEXT_SHELL shell.c) endif() diff --git a/subsys/llext/Kconfig b/subsys/llext/Kconfig index 59a4129a1ade..acc1f769c486 100644 --- a/subsys/llext/Kconfig +++ b/subsys/llext/Kconfig @@ -34,6 +34,12 @@ config LLEXT_STORAGE_WRITABLE Select if LLEXT storage is writable, i.e. if extensions are stored in RAM and can be modified in place +config LLEXT_HEAP_STAT + bool "llext info on llext heap support" + select SYS_HEAP_INFO + help + Enable shell command to print information about llext private heap + module = LLEXT module-str = llext source "subsys/logging/Kconfig.template.log_config" diff --git a/subsys/llext/file_loader.c b/subsys/llext/file_loader.c new file mode 100644 index 000000000000..27cf70904bce --- /dev/null +++ b/subsys/llext/file_loader.c @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 Schneider Electric + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include +#include +#include +#include + +int llext_file_read(struct llext_loader *l, void *buf, size_t len) +{ + struct llext_file_loader *file_l = CONTAINER_OF(l, struct llext_file_loader, loader); + + int nb_read = fs_read(file_l->fd, buf, len); + int rc = 0; + + if(nb_read != len) { + rc = -ENOMEM; + } + + return rc; +} + +int llext_file_seek(struct llext_loader *l, size_t pos) +{ + struct llext_file_loader *file_l = CONTAINER_OF(l, struct llext_file_loader, loader); + + return fs_seek(file_l->fd, pos, FS_SEEK_SET); +} diff --git a/subsys/llext/llext.c b/subsys/llext/llext.c index aaafe578f143..073a945635f6 100644 --- a/subsys/llext/llext.c +++ b/subsys/llext/llext.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2023 Intel Corporation + * Copyright (c) 2024 Schneider Electric * * SPDX-License-Identifier: Apache-2.0 * @@ -24,7 +25,60 @@ LOG_MODULE_REGISTER(llext, CONFIG_LLEXT_LOG_LEVEL); #define LLEXT_PAGE_SIZE 32 #endif -K_HEAP_DEFINE(llext_heap, CONFIG_LLEXT_HEAP_SIZE * 1024); +#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 + +/** + * @brief Define a static k_heap in the specified linker section + * + * This macro defines and initializes a static memory region and + * k_heap of the requested size in the specified linker section. + * After kernel start, &name can be used as if k_heap_init() had + * been called. + * + * Note that this macro enforces a minimum size on the memory region + * to accommodate metadata requirements. Very small heaps will be + * padded to fit. + * + * @param name Symbol name for the struct k_heap object + * @param bytes Size of memory region, in bytes + * @param in_section __attribute__((section(name)) + */ +#define Z_HEAP_DEFINE_IN_SECT_MPU_ALIGNED(name, bytes, in_section) \ + char in_section \ + __aligned(MAX(8, MAX(bytes, Z_HEAP_MIN_SIZE))) /* CHUNK_UNIT */ \ + kheap_##name[MAX(bytes, Z_HEAP_MIN_SIZE)]; \ + STRUCT_SECTION_ITERABLE(k_heap, name) = { \ + .heap = { \ + .init_mem = kheap_##name, \ + .init_bytes = MAX(bytes, Z_HEAP_MIN_SIZE), \ + }, \ + } + +/** + * @brief Define a static k_heap + * + * This macro defines and initializes a static memory region and + * k_heap of the requested size. After kernel start, &name can be + * used as if k_heap_init() had been called. + * + * Note that this macro enforces a minimum size on the memory region + * to accommodate metadata requirements. Very small heaps will be + * padded to fit. + * + * @param name Symbol name for the struct k_heap object + * @param bytes Size of memory region, in bytes + */ +#define K_HEAP_DEFINE_MPU_ALIGNED(name, bytes) \ + Z_HEAP_DEFINE_IN_SECT_MPU_ALIGNED(name, bytes, \ + __noinit_named(kheap_buf_##name)) + + +K_HEAP_DEFINE_MPU_ALIGNED(llext_heap, CONFIG_LLEXT_HEAP_SIZE * 1024); static const char ELF_MAGIC[] = {0x7f, 'E', 'L', 'F'}; @@ -246,6 +300,10 @@ static int llext_map_sections(struct llext_loader *ldr, struct llext *ext) mem_idx = LLEXT_MEM_BSS; } else if (strcmp(name, ".exported_sym") == 0) { mem_idx = LLEXT_MEM_EXPORT; + } else if (strcmp(name, ".got") == 0) { + mem_idx = LLEXT_MEM_GOT; + } else if (strcmp(name, ".plt") == 0) { + mem_idx = LLEXT_MEM_PLT; } else { LOG_DBG("Not copied section %s", name); continue; @@ -294,6 +352,7 @@ static int llext_copy_section(struct llext_loader *ldr, struct llext *ext, enum llext_mem mem_idx) { int ret; + void *pmem = NULL; if (!ldr->sects[mem_idx].sh_size) { return 0; @@ -327,39 +386,60 @@ static int llext_copy_section(struct llext_loader *ldr, struct llext *ext, 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 (ldr->phdr.p_memsz != 0) { + /** + * For this case, enum LLEXT_MEM_COUNT must be in same order + * as section in elf file to preserve relative addr + */ + pmem = (uint8_t*)ext->mem[LLEXT_MEM_TEXT] + ldr->sects[mem_idx].sh_addr; + ext->mem[mem_idx] = pmem; + ext->mem_on_heap[mem_idx] = false; + } else { + /** + * llext_heap : llext memory pool + * ldr->sects[mem_idx].sh_addralign: section alignment + * ldr->sects[mem_idx].sh_size : section size + */ + if(sect_align < ldr->sects[mem_idx].sh_addralign) { + LOG_WRN("alloc Alignment smaller than ELF section aligment needed for %d", mem_idx); + } + ext->mem[mem_idx] = k_heap_aligned_alloc(&llext_heap, sect_align, + sect_alloc, + K_NO_WAIT); - if (!ext->mem[mem_idx]) { - return -ENOMEM; - } + if (!ext->mem[mem_idx]) { + return -ENOMEM; + } - ext->alloc_size += sect_alloc; + ext->mem_on_heap[mem_idx] = true; + ext->alloc_size += sect_alloc; + pmem = ext->mem[mem_idx]; - llext_init_mem_part(ext, mem_idx, (uintptr_t)ext->mem[mem_idx], - 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 { + /* copy data */ + if (ldr->sects[mem_idx].sh_type & SHT_PROGBITS ) { 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); + ret = llext_read(ldr, pmem, ldr->sects[mem_idx].sh_size); if (ret != 0) { goto err; } + } else { + /* zero initialized */ + memset(pmem, 0, ldr->sects[mem_idx].sh_size); } - - ext->mem_on_heap[mem_idx] = true; - return 0; err: - k_heap_free(&llext_heap, ext->mem[mem_idx]); + if (ext->mem_on_heap[mem_idx]) { + k_heap_free(&llext_heap, ext->mem[mem_idx]); + } return ret; } @@ -376,13 +456,77 @@ static int llext_copy_strings(struct llext_loader *ldr, struct llext *ext) static int llext_copy_sections(struct llext_loader *ldr, struct llext *ext) { + int ret; + + /* use first program header to allocate all section in one buffer*/ + if (ldr->hdr.e_phnum > 0) { + /* Manage program headers (shared link) */ + size_t pos = ldr->hdr.e_phoff; + + ret = llext_seek(ldr, pos); + if (ret != 0) { + LOG_ERR("failed seeking to position %u\n", pos); + return ret; + } + /* read program header */ + ret = llext_read(ldr, &ldr->phdr, sizeof(elf_phdr_t)); + if (ret != 0) { + LOG_ERR("failed reading section header at position %u\n", pos); + return ret; + } + pos += ldr->hdr.e_phentsize; + + /** + * alloc mem + * llext_heap: llext memory pool + * ldr->phdr.p_align: segment alignment + * ldr->phdr.p_memsz: segment size + */ +#ifndef CONFIG_ARM_MPU + const uintptr_t sect_alloc = ROUND_UP(ldr->sects[LLEXT_MEM_TEXT].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[LLEXT_MEM_TEXT].sh_size) { + sect_alloc *= 2; + } + uintptr_t sect_align = sect_alloc; +#endif + + if(sect_align < ldr->phdr.p_align) { + LOG_WRN("alloc Alignment smaller than ELF segment aligment needed"); + } + + ext->mem[LLEXT_MEM_TEXT] = k_heap_aligned_alloc(&llext_heap, sect_align, + sect_alloc, K_NO_WAIT); + if (ext->mem[LLEXT_MEM_TEXT] == NULL) { + ret = -ENOMEM; + return ret; + } + + llext_init_mem_part(ext, LLEXT_MEM_TEXT, (uintptr_t)ext->mem[LLEXT_MEM_TEXT], + sect_alloc); + + /* copy text section */ + ret = llext_copy_section(ldr, ext, LLEXT_MEM_TEXT); + if (ret < 0) { + return ret; + } + } + + /* copy all section */ 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; } + /* symtab section is managed before */ + if( LLEXT_MEM_SYMTAB == mem_idx) { + continue; + } - int ret = llext_copy_section(ldr, ext, mem_idx); + ret = llext_copy_section(ldr, ext, mem_idx); if (ret < 0) { return ret; @@ -464,6 +608,7 @@ static int llext_export_symbols(struct llext_loader *ldr, struct llext *ext) if (shdr->sh_size < sizeof(struct llext_symbol)) { /* Not found, no symbols exported */ + LOG_WRN("No symbols exported"); return 0; } @@ -517,22 +662,22 @@ static int llext_copy_symbols(struct llext_loader *ldr, struct llext *ext) uint32_t stt = ELF_ST_TYPE(sym.st_info); uint32_t stb = ELF_ST_BIND(sym.st_info); - unsigned int sect = sym.st_shndx; - - if (stt == STT_FUNC && stb == STB_GLOBAL && sect != SHN_UNDEF) { - enum llext_mem mem_idx = ldr->sect_map[sect]; - const char *name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym.st_name); - - __ASSERT(j <= sym_tab->sym_cnt, "Miscalculated symbol number %u\n", j); - - sym_tab->syms[j].name = name; - sym_tab->syms[j].addr = (void *)((uintptr_t)ext->mem[mem_idx] + + uint32_t sect = sym.st_shndx; + enum llext_mem mem_idx = ldr->sect_map[sect]; + const char *name = llext_string(ldr, ext, LLEXT_MEM_STRTAB, sym.st_name); + if (NULL != name) { + if ((stt == STT_FUNC && stb == STB_GLOBAL && sect != SHN_UNDEF + && sect != SHN_ABS && sect != SHN_COMMON) ) { + __ASSERT(j <= sym_tab->sym_cnt, "Miscalculated symbol number %u\n", j); + sym_tab->syms[j].name = name; + sym_tab->syms[j].addr = (void *)((uintptr_t)ext->mem[mem_idx] + sym.st_value - (ldr->hdr.e_type == ET_REL ? 0 : ldr->sects[mem_idx].sh_addr)); - LOG_DBG("function symbol %d name %s addr %p", - j, name, sym_tab->syms[j].addr); - j++; + LOG_DBG("function symbol %d name %s addr %p", + j, name, sym_tab->syms[j].addr); + j++; + } } } @@ -660,8 +805,11 @@ static void llext_link_plt(struct llext_loader *ldr, struct llext *ext, } } -__weak void arch_elf_relocate(elf_rela_t *rel, uintptr_t opaddr, uintptr_t opval) +__weak int32_t arch_elf_relocate(elf_rela_t *rel, uint32_t rel_index, + uintptr_t loc, uintptr_t sym_base_addr, const char *symname, + uintptr_t load_bias) { + return 0; } static int llext_link(struct llext_loader *ldr, struct llext *ext, bool do_local) @@ -715,6 +863,9 @@ static int llext_link(struct llext_loader *ldr, struct llext *ext, bool do_local strcmp(name, ".rela.dyn") == 0) { llext_link_plt(ldr, ext, &shdr, do_local); continue; + } else if (strcmp(name, ".rel.dyn") == 0) { + // we assume that first load segment starts at MEM_TEXT + loc = (uintptr_t)ext->mem[LLEXT_MEM_TEXT]; } LOG_DBG("relocation section %s (%d) linked to section %d has %zd relocations", @@ -757,22 +908,27 @@ static int llext_link(struct llext_loader *ldr, struct llext *ext, bool do_local op_loc = loc + rel.r_offset; - /* If symbol is undefined, then we need to look it up */ - if (sym.st_shndx == SHN_UNDEF) { + if (ELF_R_SYM(rel.r_info) == 0) { + /* no symbol ex: R_ARM_V4BX relocation, R_ARM_RELATIVE */ + link_addr = 0; + } else if (sym.st_shndx == SHN_UNDEF) { + /* If symbol is undefined, then we need to look it up */ link_addr = (uintptr_t)llext_find_sym(NULL, name); if (link_addr == 0) { LOG_ERR("Undefined symbol with no entry in " - "symbol table %s, offset %zd, link section %d", - name, (size_t)rel.r_offset, shdr.sh_link); + "symbol table %s, offset %zd, link section %d", + name, (size_t)rel.r_offset, shdr.sh_link); return -ENODATA; + } else { + LOG_INF("found symbol %s at 0x%lx", name, link_addr); } } else if (ELF_ST_TYPE(sym.st_info) == STT_SECTION || - ELF_ST_TYPE(sym.st_info) == STT_FUNC || - ELF_ST_TYPE(sym.st_info) == STT_OBJECT) { + ELF_ST_TYPE(sym.st_info) == STT_FUNC || + ELF_ST_TYPE(sym.st_info) == STT_OBJECT) { /* Link address is relative to the start of the section */ link_addr = (uintptr_t)ext->mem[ldr->sect_map[sym.st_shndx]] - + sym.st_value; + + sym.st_value; LOG_INF("found section symbol %s addr 0x%lx", name, link_addr); } else { @@ -792,7 +948,11 @@ static int llext_link(struct llext_loader *ldr, struct llext *ext, bool do_local op_loc, link_addr); /* relocation */ - arch_elf_relocate(&rel, op_loc, link_addr); + ret = arch_elf_relocate(&rel, j, op_loc, link_addr, name, + (uintptr_t)ext->mem[LLEXT_MEM_TEXT]); + if (ret != 0) { + return ret; + } } } @@ -1069,3 +1229,11 @@ int llext_add_domain(struct llext *ext, struct k_mem_domain *domain) return -ENOSYS; #endif } + +#if CONFIG_LLEXT_HEAP_STAT +void llext_print_heap_info() +{ + bool dump_chunks = true; + sys_heap_print_info(&llext_heap.heap, dump_chunks); +} +#endif diff --git a/subsys/llext/shell.c b/subsys/llext/shell.c index 033a0a91ce25..2330d1324b2a 100644 --- a/subsys/llext/shell.c +++ b/subsys/llext/shell.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2023 Intel Corporation + * Copyright (c) 2024 Schneider Electric * * SPDX-License-Identifier: Apache-2.0 * @@ -11,6 +12,10 @@ #include #include #include +#ifdef CONFIG_FILE_SYSTEM +#include +#include +#endif #include LOG_MODULE_REGISTER(llext_shell, CONFIG_LLEXT_LOG_LEVEL); @@ -25,6 +30,10 @@ LOG_MODULE_REGISTER(llext_shell, CONFIG_LLEXT_LOG_LEVEL); "Unload an extension by name. Syntax:\n" \ "" +#define LLEXT_LOAD_FILE_HELP \ + "Load an elf file from file system. Syntax:\n" \ + " " + #define LLEXT_LIST_SYMBOLS_HELP \ "List extension symbols. Syntax:\n" \ "" @@ -33,6 +42,11 @@ LOG_MODULE_REGISTER(llext_shell, CONFIG_LLEXT_LOG_LEVEL); "Call extension function with prototype void fn(void). Syntax:\n" \ " " +#if CONFIG_LLEXT_HEAP_STAT +#define LLEXT_HEAP_INFO_HELP \ + "Show llext heap info." +#endif + static int cmd_llext_list_symbols(const struct shell *sh, size_t argc, char *argv[]) { struct llext *m = llext_by_name(argv[1]); @@ -44,9 +58,9 @@ static int cmd_llext_list_symbols(const struct shell *sh, size_t argc, char *arg shell_print(sh, "Extension: %s symbols", m->name); shell_print(sh, "| Symbol | Address |"); - for (elf_word i = 0; i < m->sym_tab.sym_cnt; i++) { - shell_print(sh, "| %16s | %p |", m->sym_tab.syms[i].name, - m->sym_tab.syms[i].addr); + for (size_t i = 0; i < m->exp_tab.sym_cnt; i++) { + shell_print(sh, "| %16s | %p |", m->exp_tab.syms[i].name, + m->exp_tab.syms[i].addr); } return 0; @@ -153,6 +167,41 @@ static int cmd_llext_load_hex(const struct shell *sh, size_t argc, char *argv[]) return 0; } +#ifdef CONFIG_FILE_SYSTEM +static int cmd_llext_load_file(const struct shell *sh, size_t argc, char *argv[]) +{ + char name[16]; + struct fs_file_t fd; + + // get extension name + strncpy(name, argv[1], sizeof(name)); + + fs_file_t_init(&fd); + int rc = fs_open(&fd, argv[2], FS_O_READ); + if(rc < 0) { + shell_print(sh, "%d File not found: %s\n", rc, argv[2]); + return rc; + } + + struct llext_load_param ldr_parm = LLEXT_LOAD_PARAM_DEFAULT; + struct llext_file_loader file_stream = LLEXT_FILE_LOADER(&fd); + struct llext_loader *stream = &file_stream.loader; + + struct llext *m; + int res = llext_load(stream, name, &m, &ldr_parm); + + fs_close(&fd); + + if (res == 0) { + shell_print(sh, "Successfully loaded extension %s, addr %p\n", m->name, m); + } else { + shell_print(sh, "Failed to load extension %s, return code %d\n", name, res); + } + + return 0; +} +#endif + static int cmd_llext_unload(const struct shell *sh, size_t argc, char *argv[]) { struct llext *ext = llext_by_name(argv[1]); @@ -177,11 +226,22 @@ static int cmd_llext_call_fn(const struct shell *sh, size_t argc, char *argv[]) return -EINVAL; } - llext_call_fn(ext, argv[2]); + int rc = llext_call_fn(ext, argv[2]); + if(rc==-EINVAL) + { + shell_print(sh, "No such symbol '%s' in extension %s", argv[2], argv[1]); + } - return 0; + return rc; } +#if CONFIG_LLEXT_HEAP_STAT +static int cmd_llext_heap_info(const struct shell *sh, size_t argc, char *argv[]) +{ + llext_print_heap_info(); + return 0; +} +#endif /* clang-format off */ SHELL_STATIC_SUBCMD_SET_CREATE(sub_llext, @@ -192,7 +252,14 @@ SHELL_STATIC_SUBCMD_SET_CREATE(sub_llext, cmd_llext_list_symbols, 2, 0), SHELL_CMD_ARG(call_fn, &msub_llext_name_arg, LLEXT_CALL_FN_HELP, cmd_llext_call_fn, 3, 0), - +#if CONFIG_FILE_SYSTEM + SHELL_CMD_ARG(load_file, NULL, LLEXT_LOAD_FILE_HELP, cmd_llext_load_file, + 3, 0), +#endif +#if CONFIG_LLEXT_HEAP_STAT + SHELL_CMD_ARG(heap_info, NULL, LLEXT_HEAP_INFO_HELP, cmd_llext_heap_info, + 0, 0), +#endif SHELL_SUBCMD_SET_END ); /* clang-format on */ diff --git a/tests/subsys/llext/simple/CMakeLists.txt b/tests/subsys/llext/simple/CMakeLists.txt index 4a9f311d8faa..09ed587d3690 100644 --- a/tests/subsys/llext/simple/CMakeLists.txt +++ b/tests/subsys/llext/simple/CMakeLists.txt @@ -18,11 +18,10 @@ target_include_directories(app PRIVATE # generate extension targets foreach extension given by name foreach(ext_name hello_world logging relative_jump object) set(ext_src ${PROJECT_SOURCE_DIR}/src/${ext_name}_ext.c) - set(ext_bin ${ZEPHYR_BINARY_DIR}/${ext_name}.llext) + zephyr_llext(${ext_name}) + zephyr_llext_sources(${ext_src}) + + set(ext_bin ${CMAKE_CURRENT_BINARY_DIR}/${ext_name}.llext) set(ext_inc ${ZEPHYR_BINARY_DIR}/include/generated/${ext_name}.inc) - add_llext_target(${ext_name}_ext - OUTPUT ${ext_bin} - SOURCES ${ext_src} - ) generate_inc_file_for_target(app ${ext_bin} ${ext_inc}) endforeach() diff --git a/tests/subsys/llext/simple/src/hello_world_ext.c b/tests/subsys/llext/simple/src/hello_world_ext.c index f3cc2106a11c..f3ba730b30c0 100644 --- a/tests/subsys/llext/simple/src/hello_world_ext.c +++ b/tests/subsys/llext/simple/src/hello_world_ext.c @@ -17,9 +17,9 @@ static const uint32_t number = 42; -void test_entry(void) +void start(void) { printk("hello world\n"); printk("A number is %u\n", number); } -LL_EXTENSION_SYMBOL(test_entry); +LL_EXTENSION_SYMBOL(start); diff --git a/tests/subsys/llext/simple/src/logging_ext.c b/tests/subsys/llext/simple/src/logging_ext.c index 94298ce2f150..5fcfb69903e6 100644 --- a/tests/subsys/llext/simple/src/logging_ext.c +++ b/tests/subsys/llext/simple/src/logging_ext.c @@ -18,9 +18,9 @@ LOG_MODULE_REGISTER(logging_ext); static const uint32_t number = 42; -void test_entry(void) +void start(void) { LOG_INF("hello world"); LOG_INF("A number is %" PRIu32, number); } -LL_EXTENSION_SYMBOL(test_entry); +LL_EXTENSION_SYMBOL(start); diff --git a/tests/subsys/llext/simple/src/test_llext_simple.c b/tests/subsys/llext/simple/src/test_llext_simple.c index dcb31ac9e4ad..bb54ce5a5833 100644 --- a/tests/subsys/llext/simple/src/test_llext_simple.c +++ b/tests/subsys/llext/simple/src/test_llext_simple.c @@ -55,9 +55,9 @@ void load_call_unload(struct llext_test *test_case) zassert_ok(res, "load should succeed"); - void (*test_entry_fn)() = llext_find_sym(&ext->exp_tab, "test_entry"); + void (*test_entry_fn)() = llext_find_sym(&ext->exp_tab, "start"); - zassert_not_null(test_entry_fn, "test_entry should be an exported symbol"); + zassert_not_null(test_entry_fn, "start should be an exported symbol"); #ifdef CONFIG_USERSPACE /* @@ -114,8 +114,8 @@ void load_call_unload(struct llext_test *test_case) #else /* CONFIG_USERSPACE */ - zassert_ok(llext_call_fn(ext, "test_entry"), - "test_entry call should succeed"); + zassert_ok(llext_call_fn(ext, "start"), + "start call should succeed"); #endif /* CONFIG_USERSPACE */ llext_unload(&ext);