From f3ec788933049347bb44394d712283f323565414 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 12 Feb 2026 16:20:37 +0100 Subject: [PATCH 1/8] loader: sketch heap size config defined Defined config for sketch heap size, now LLEXT_HEAP_SIZE defaults to this value Signed-off-by: Andrea Gilardoni --- loader/Kconfig | 10 ++++++++++ .../arduino_nano_33_ble_nrf52840_sense.conf | 2 +- .../arduino_nano_matter_mgm240sd22vna.conf | 2 +- .../arduino_nicla_sense_me_nrf52832.conf | 2 +- .../arduino_portenta_c33_r7fa6m5bh3cfc.conf | 2 +- .../arduino_portenta_h7_stm32h747xx_m7.conf | 2 +- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/loader/Kconfig b/loader/Kconfig index c7d6c4069..7b87232e9 100644 --- a/loader/Kconfig +++ b/loader/Kconfig @@ -4,6 +4,10 @@ # SPDX-License-Identifier: Apache-2.0 # +config LLEXT_HEAP_SIZE + int + default SKETCH_HEAP_SIZE + source "Kconfig.zephyr" config LLEXT_HEAP_REGION @@ -17,3 +21,9 @@ config MAIN_STACK_REGION depends on CODE_DATA_RELOCATION help Specify the memory region for main stack. + +config SKETCH_HEAP_SIZE + int "Sketch heap size in KB" + default 32 + help + Specify the size in KB for the sketch heap diff --git a/variants/arduino_nano_33_ble_nrf52840_sense/arduino_nano_33_ble_nrf52840_sense.conf b/variants/arduino_nano_33_ble_nrf52840_sense/arduino_nano_33_ble_nrf52840_sense.conf index 687962fe7..0d7267ecc 100644 --- a/variants/arduino_nano_33_ble_nrf52840_sense/arduino_nano_33_ble_nrf52840_sense.conf +++ b/variants/arduino_nano_33_ble_nrf52840_sense/arduino_nano_33_ble_nrf52840_sense.conf @@ -35,7 +35,7 @@ CONFIG_PWM=y CONFIG_LLEXT_STORAGE_WRITABLE=n CONFIG_HEAP_MEM_POOL_SIZE=16384 -CONFIG_LLEXT_HEAP_SIZE=128 +CONFIG_SKETCH_HEAP_SIZE=128 CONFIG_MAIN_STACK_SIZE=16384 CONFIG_FPU=y diff --git a/variants/arduino_nano_matter_mgm240sd22vna/arduino_nano_matter_mgm240sd22vna.conf b/variants/arduino_nano_matter_mgm240sd22vna/arduino_nano_matter_mgm240sd22vna.conf index 6cf91d986..690319345 100644 --- a/variants/arduino_nano_matter_mgm240sd22vna/arduino_nano_matter_mgm240sd22vna.conf +++ b/variants/arduino_nano_matter_mgm240sd22vna/arduino_nano_matter_mgm240sd22vna.conf @@ -3,7 +3,7 @@ CONFIG_MAIN_STACK_SIZE=16000 CONFIG_HEAP_MEM_POOL_SIZE=112000 -CONFIG_LLEXT_HEAP_SIZE=64 +CONFIG_SKETCH_HEAP_SIZE=64 CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048 CONFIG_FPU=y diff --git a/variants/arduino_nicla_sense_me_nrf52832/arduino_nicla_sense_me_nrf52832.conf b/variants/arduino_nicla_sense_me_nrf52832/arduino_nicla_sense_me_nrf52832.conf index ff9bebbad..d19cdb882 100644 --- a/variants/arduino_nicla_sense_me_nrf52832/arduino_nicla_sense_me_nrf52832.conf +++ b/variants/arduino_nicla_sense_me_nrf52832/arduino_nicla_sense_me_nrf52832.conf @@ -26,5 +26,5 @@ CONFIG_ADC=y CONFIG_LLEXT_STORAGE_WRITABLE=n CONFIG_HEAP_MEM_POOL_SIZE=8192 -CONFIG_LLEXT_HEAP_SIZE=15 +CONFIG_SKETCH_HEAP_SIZE=15 CONFIG_MAIN_STACK_SIZE=2048 diff --git a/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.conf b/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.conf index b2a4b13d4..8e08851d7 100644 --- a/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.conf +++ b/variants/arduino_portenta_c33_r7fa6m5bh3cfc/arduino_portenta_c33_r7fa6m5bh3cfc.conf @@ -28,7 +28,7 @@ CONFIG_LOG_BACKEND_UART_AUTOSTART=n CONFIG_LOG_DEFAULT_LEVEL=2 CONFIG_MAIN_STACK_SIZE=32768 -CONFIG_LLEXT_HEAP_SIZE=128 +CONFIG_SKETCH_HEAP_SIZE=128 CONFIG_HEAP_MEM_POOL_SIZE=32768 CONFIG_ISR_STACK_SIZE=8192 CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=8192 diff --git a/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.conf b/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.conf index ce75edafc..476d51ccf 100644 --- a/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.conf +++ b/variants/arduino_portenta_h7_stm32h747xx_m7/arduino_portenta_h7_stm32h747xx_m7.conf @@ -31,7 +31,7 @@ CONFIG_ARM_MPU=y CONFIG_MAX_THREAD_BYTES=4 CONFIG_MAIN_STACK_SIZE=32768 -CONFIG_LLEXT_HEAP_SIZE=128 +CONFIG_SKETCH_HEAP_SIZE=128 CONFIG_CODE_DATA_RELOCATION=y CONFIG_MAIN_STACK_REGION="DTCM" From 10e6d57b9d7a4e94df8882319278fb0775c625d8 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 12 Feb 2026 16:15:24 +0100 Subject: [PATCH 2/8] cmake: fixing include recursion when llext=n When llext is disabled lext-edk include file generation had a recursion bug Signed-off-by: Andrea Gilardoni --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 47fd59e7e..3f5d8142d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ if (CONFIG_ARDUINO_API) endif() endif() -if (CONFIG_ARDUINO_API AND NOT CONFIG_LLEXT) +if (CONFIG_ARDUINO_API AND NOT CONFIG_LLEXT_EDK) add_subdirectory(cores) add_subdirectory(libraries) zephyr_include_directories(${variant_dir}) From 7a1e2f280213f69948951ff2695dd19623ba3512 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Thu, 12 Feb 2026 16:19:20 +0100 Subject: [PATCH 3/8] llext: adding missing heap when llext is disabled When CONFIG_LLEXT=n llext_heap is not defined, thus raising compile time error when compiling with that option disabled. NOTE that the only way to use a sketch with a loader compiled with llext disabled is to use static linkage mode. This could help save up some space. Signed-off-by: Andrea Gilardoni --- loader/main.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/loader/main.c b/loader/main.c index 7be8a34fd..b1b141489 100644 --- a/loader/main.c +++ b/loader/main.c @@ -110,6 +110,14 @@ __attribute__((retain)) const uintptr_t sketch_max_size = DT_REG_SIZE(DT_NODELAB #endif __attribute__((retain)) const uintptr_t loader_max_size = LOADER_MAX_SIZE; +#ifdef CONFIG_LLEXT +extern struct k_heap llext_heap; +#define SKETCH_HEAP llext_heap +#else +K_HEAP_DEFINE(_sketch_heap, CONFIG_SKETCH_HEAP_SIZE << 10); +#define SKETCH_HEAP _sketch_heap +#endif + static int loader(const struct shell *sh) { const struct flash_area *fa; int rc; @@ -266,16 +274,15 @@ static int loader(const struct shell *sh) { #endif #endif - extern struct k_heap llext_heap; typedef void (*entry_point_t)(struct k_heap *heap, size_t heap_size); entry_point_t entry_point = (entry_point_t)(base_addr + HEADER_LEN + 1); - entry_point(&llext_heap, llext_heap.heap.init_bytes); + entry_point(&SKETCH_HEAP, CONFIG_SKETCH_HEAP_SIZE << 10); // should never reach here for (;;) { k_sleep(K_FOREVER); } } - +#ifdef CONFIG_LLEXT #if defined(CONFIG_LLEXT_STORAGE_WRITABLE) uint8_t *sketch_buf = k_aligned_alloc(4096, sketch_buf_len); @@ -294,7 +301,6 @@ static int loader(const struct shell *sh) { uint8_t *sketch_buf = (uint8_t *)base_addr; #endif -#ifdef CONFIG_LLEXT struct llext_buf_loader buf_loader = LLEXT_BUF_LOADER(sketch_buf, sketch_buf_len); struct llext_loader *ldr = &buf_loader.loader; @@ -315,7 +321,6 @@ static int loader(const struct shell *sh) { printk("Failed to find main function\n"); return -ENOENT; } -#endif #ifdef CONFIG_USERSPACE /* @@ -350,12 +355,10 @@ static int loader(const struct shell *sh) { k_thread_join(&llext_thread, K_FOREVER); #else -#ifdef CONFIG_LLEXT llext_bootstrap(ext, main_fn, NULL); #endif -#endif - +#endif // CONFIG_LLEXT return 0; } From ae3967762c668f02ea822d5be851d7da1e85897c Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Fri, 13 Feb 2026 11:05:27 +0100 Subject: [PATCH 4/8] build: exporting symbols when llext is disabled This commits make it possible to have the same exported symbols as an llext build, when llext is disabled by means of: - ephemeral symbols in section .exported_sym - gen_provides: mechanism to include symbols through list of regex Signed-off-by: Andrea Gilardoni --- extra/build.sh | 5 ++++- extra/gen_provides.py | 7 +++++++ loader/CMakeLists.txt | 4 ++++ loader/llext_exports.c | 12 ++++++++++++ loader/loader-nollext.ld | 7 +++++++ 5 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 loader/loader-nollext.ld diff --git a/extra/build.sh b/extra/build.sh index e490e1c3f..381d94e2a 100755 --- a/extra/build.sh +++ b/extra/build.sh @@ -101,9 +101,12 @@ cp ${BUILD_DIR}/zephyr/zephyr.dts firmwares/zephyr-$variant.dts cp ${BUILD_DIR}/zephyr/.config firmwares/zephyr-$variant.config # Generate the provides.ld file for linked builds -echo "Generating exported symbol scripts" +echo "Generating exported symbol scripts for dynamic builds" extra/gen_provides.py "${BUILD_DIR}/zephyr/zephyr.elf" -L > ${VARIANT_DIR}/syms-dynamic.ld + +echo "Generating exported symbol scripts for static builds" extra/gen_provides.py "${BUILD_DIR}/zephyr/zephyr.elf" -LF \ + -R '__device_dts_ord_\d+' \ "+kheap_llext_heap" \ "+kheap__system_heap" \ "*sketch_base_addr=_sketch_start" \ diff --git a/extra/gen_provides.py b/extra/gen_provides.py index 39bfa8efd..f0103d14e 100755 --- a/extra/gen_provides.py +++ b/extra/gen_provides.py @@ -122,6 +122,10 @@ def main(): argparser.add_argument('-F', '--funcs', action='store_true', help='Extract all public functions') + argparser.add_argument('-R', '--regexp', + action='append', + default=[], + help='Extract all public symbols matching regexp') argparser.add_argument('file', help='ELF file to parse') argparser.add_argument('syms', nargs='*', @@ -198,6 +202,9 @@ def main(): llext_sym_addr = sym['st_value'] name = get_str_at(elf, get_ptr_at(elf, llext_sym_addr)) value = get_ptr_at(elf, llext_sym_addr + NativePtr.length) + elif args.regexp and any(map(lambda x: re.match(x, name), args.regexp)): + comment = "regexp_sym" + value = sym['st_value'] if name in deref_syms: value = get_ptr_at(elf, value) diff --git a/loader/CMakeLists.txt b/loader/CMakeLists.txt index 428885a70..9e2b99893 100644 --- a/loader/CMakeLists.txt +++ b/loader/CMakeLists.txt @@ -40,3 +40,7 @@ target_sources(app PRIVATE ${app_sources}) target_compile_definitions(app PUBLIC _XOPEN_SOURCE=700) target_link_libraries(app PUBLIC blobs) + +if (CONFIG_ARDUINO_API AND CONFIG_LLEXT_EDK AND NOT CONFIG_LLEXT) + zephyr_linker_sources(SECTIONS loader-nollext.ld) +endif() diff --git a/loader/llext_exports.c b/loader/llext_exports.c index 34703fc4c..301245db4 100644 --- a/loader/llext_exports.c +++ b/loader/llext_exports.c @@ -6,7 +6,19 @@ #include #include + +#if CONFIG_LLEXT #include +#else +#include + +#define EXPORT_SYMBOL_NAMED(sym_ident, sym_name) \ + Z_GENERIC_DOT_SECTION(exported_sym) static const void * __exported_sym_ ## sym_name __used\ + = (const void*) &sym_ident + +#define EXPORT_SYMBOL(x) EXPORT_SYMBOL_NAMED(x, x) +#endif + #include #include #include diff --git a/loader/loader-nollext.ld b/loader/loader-nollext.ld new file mode 100644 index 000000000..38c603496 --- /dev/null +++ b/loader/loader-nollext.ld @@ -0,0 +1,7 @@ +#ifndef CONFIG_LLEXT + SECTION_PROLOGUE(exported_sym, 0 (INFO),) + { + KEEP(*(.exported_sym*)) + KEEP(*(.exported_sym.*)) + } +#endif From b3cfd2f4a41527b91d89c568f773771bd89041b7 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 2 Mar 2026 21:21:35 +0100 Subject: [PATCH 5/8] fixup! build: exporting symbols when llext is disabled --- loader/CMakeLists.txt | 3 --- loader/llext_exports.c | 12 ------------ loader/loader-nollext.ld | 7 ------- 3 files changed, 22 deletions(-) delete mode 100644 loader/loader-nollext.ld diff --git a/loader/CMakeLists.txt b/loader/CMakeLists.txt index 9e2b99893..78d83984a 100644 --- a/loader/CMakeLists.txt +++ b/loader/CMakeLists.txt @@ -41,6 +41,3 @@ target_compile_definitions(app PUBLIC _XOPEN_SOURCE=700) target_link_libraries(app PUBLIC blobs) -if (CONFIG_ARDUINO_API AND CONFIG_LLEXT_EDK AND NOT CONFIG_LLEXT) - zephyr_linker_sources(SECTIONS loader-nollext.ld) -endif() diff --git a/loader/llext_exports.c b/loader/llext_exports.c index 301245db4..34703fc4c 100644 --- a/loader/llext_exports.c +++ b/loader/llext_exports.c @@ -6,19 +6,7 @@ #include #include - -#if CONFIG_LLEXT #include -#else -#include - -#define EXPORT_SYMBOL_NAMED(sym_ident, sym_name) \ - Z_GENERIC_DOT_SECTION(exported_sym) static const void * __exported_sym_ ## sym_name __used\ - = (const void*) &sym_ident - -#define EXPORT_SYMBOL(x) EXPORT_SYMBOL_NAMED(x, x) -#endif - #include #include #include diff --git a/loader/loader-nollext.ld b/loader/loader-nollext.ld deleted file mode 100644 index 38c603496..000000000 --- a/loader/loader-nollext.ld +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef CONFIG_LLEXT - SECTION_PROLOGUE(exported_sym, 0 (INFO),) - { - KEEP(*(.exported_sym*)) - KEEP(*(.exported_sym.*)) - } -#endif From 5a9b30a94c18d8033b1d8cf993f191c8d2c08d87 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 24 Feb 2026 17:07:09 +0100 Subject: [PATCH 6/8] gen_provides: avoid fail when no symbol is found Making script gen_provides avoid failing when no symbol is provided, instead it just prints a warning message. This is required to avoid extra/build.sh fail when the loader is build without llext support Signed-off-by: Andrea Gilardoni --- extra/gen_provides.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extra/gen_provides.py b/extra/gen_provides.py index f0103d14e..6f612d112 100755 --- a/extra/gen_provides.py +++ b/extra/gen_provides.py @@ -228,8 +228,7 @@ def main(): out_syms[name + "_size"] = (sym['st_size'], [f"size of {name}"]) if not out_syms: - sys.stderr.write("No symbols found matching the criteria.\n") - fail = True + sys.stderr.write("Warning: No symbols found matching the criteria.\n") if fail: sys.exit(1) From 0c02a21e92257c6e16753053f198d1d411d531e7 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Tue, 24 Feb 2026 17:53:43 +0100 Subject: [PATCH 7/8] kheap_llext: rename kheap_llext_heap symbol rename kheap_llext_heap and kheap_llext_heap_size symbols to kheap__sketch_heap Signed-off-by: Andrea Gilardoni --- cores/arduino/main.cpp | 6 +++--- extra/build.sh | 8 ++++++-- variants/_ldscripts/memory-check.ld | 4 ++-- variants/_ldscripts/memory-static.ld | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/cores/arduino/main.cpp b/cores/arduino/main.cpp index bab3b4100..0b3905cfd 100644 --- a/cores/arduino/main.cpp +++ b/cores/arduino/main.cpp @@ -89,14 +89,14 @@ extern "C" __attribute__((section(".entry_point"), used)) void entry_point(struc extern uintptr_t _ebss; extern uintptr_t __heap_start; extern uintptr_t __heap_end; - extern uintptr_t kheap_llext_heap; - extern uintptr_t kheap_llext_heap_size; + extern uintptr_t kheap__sketch_heap; + extern uintptr_t kheap__sketch_heap_size; //__asm volatile ("cpsie i"); printk("System Heap end: %p\n", &__heap_end); printk("System Heap start: %p\n", &__heap_start); - printk("Sketch Heap start: %p, size %p\n", &kheap_llext_heap, &kheap_llext_heap_size); + printk("Sketch Heap start: %p, size %p\n", &kheap__sketch_heap, &kheap__sketch_heap_size); memcpy(&_sdata, &_sidata, (&_edata - &_sdata) * sizeof(uint32_t)); memset(&_sbss, 0, (&_ebss - &_sbss) * sizeof(uint32_t)); diff --git a/extra/build.sh b/extra/build.sh index 381d94e2a..4227265eb 100755 --- a/extra/build.sh +++ b/extra/build.sh @@ -102,13 +102,17 @@ cp ${BUILD_DIR}/zephyr/.config firmwares/zephyr-$variant.config # Generate the provides.ld file for linked builds echo "Generating exported symbol scripts for dynamic builds" -extra/gen_provides.py "${BUILD_DIR}/zephyr/zephyr.elf" -L > ${VARIANT_DIR}/syms-dynamic.ld +extra/gen_provides.py "${BUILD_DIR}/zephyr/zephyr.elf" -L \ + "kheap_llext_heap=kheap__sketch_heap" \ + "kheap_llext_heap_size=kheap__sketch_heap_size" > ${VARIANT_DIR}/syms-dynamic.ld echo "Generating exported symbol scripts for static builds" extra/gen_provides.py "${BUILD_DIR}/zephyr/zephyr.elf" -LF \ -R '__device_dts_ord_\d+' \ - "+kheap_llext_heap" \ + "+kheap__sketch_heap" \ "+kheap__system_heap" \ + "kheap_llext_heap=kheap__sketch_heap" \ + "kheap_llext_heap_size=kheap__sketch_heap_size" \ "*sketch_base_addr=_sketch_start" \ "*sketch_max_size=_sketch_max_size" \ "*loader_max_size=_loader_max_size" \ diff --git a/variants/_ldscripts/memory-check.ld b/variants/_ldscripts/memory-check.ld index 47fc8d679..c7ff5e33c 100644 --- a/variants/_ldscripts/memory-check.ld +++ b/variants/_ldscripts/memory-check.ld @@ -15,5 +15,5 @@ PROVIDE(__heap_end = 0x50000000); PROVIDE(kheap__system_heap = 0x40000000); PROVIDE(kheap__system_heap_size = 0x10000000); -PROVIDE(kheap_llext_heap = 0x60000000); -PROVIDE(kheap_llext_heap_size = 0x10000000); +PROVIDE(kheap__sketch_heap = 0x60000000); +PROVIDE(kheap__sketch_heap_size = 0x10000000); diff --git a/variants/_ldscripts/memory-static.ld b/variants/_ldscripts/memory-static.ld index 85a3c48fe..f552ce408 100644 --- a/variants/_ldscripts/memory-static.ld +++ b/variants/_ldscripts/memory-static.ld @@ -8,7 +8,7 @@ MEMORY { /* 16 below compensates for the zsk header that is added before the .bin */ FLASH (rx) : ORIGIN = _sketch_start + 16, LENGTH = _sketch_max_size - 16 - RAM (rwx) : ORIGIN = kheap_llext_heap, LENGTH = kheap_llext_heap_size + RAM (rwx) : ORIGIN = kheap__sketch_heap, LENGTH = kheap__sketch_heap_size } PROVIDE(__heap_start = kheap__system_heap); From 34b37077b1540bb7f6fc8977f800700ddd54d2c2 Mon Sep 17 00:00:00 2001 From: Andrea Gilardoni Date: Mon, 2 Mar 2026 21:22:33 +0100 Subject: [PATCH 8/8] llext_edk: enable symbol export when LLEXT=n In this way wew are able to use symbol linkage statically without the need of the entire LLEXT runtime --- loader/prj.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/loader/prj.conf b/loader/prj.conf index 023da8fd3..3c0fbbf13 100644 --- a/loader/prj.conf +++ b/loader/prj.conf @@ -22,6 +22,7 @@ CONFIG_LLEXT_EXPORT_DEVICES=y CONFIG_LLEXT_RODATA_NO_RELOC=y CONFIG_LLEXT_EDK=y +CONFIG_LLEXT_EDK_EXPORT_SYMS=y CONFIG_LLEXT_EDK_FORMAT_TAR_ZSTD=y CONFIG_GPIO=y