From 9681d96147322dd6d7a4ea1fb26d28b51e1e0491 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 13 Feb 2026 17:44:14 +0200 Subject: [PATCH 1/6] schedule: zephyr_ll_user: make the heap accessible from user-space Rework the heap allocation. Instead of reusing module_driver_heap_init(), allocate the heap in zephyr_ll_user_resources_init(). A copy of heap pointer is stored into user-space accessible memory and zephyr_ll_user_heap_verify() is added to allow kernel code verify the heap object. CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE is added to Kconfig to control size of the LL user heap. This will initially cover all user-space audio pipeline allocations. Signed-off-by: Kai Vehmanen --- src/include/sof/schedule/ll_schedule_domain.h | 1 + src/schedule/zephyr_ll_user.c | 83 ++++++++++++++++--- zephyr/Kconfig | 11 +++ 3 files changed, 83 insertions(+), 12 deletions(-) diff --git a/src/include/sof/schedule/ll_schedule_domain.h b/src/include/sof/schedule/ll_schedule_domain.h index dc28e43c7461..4338e04bdfe5 100644 --- a/src/include/sof/schedule/ll_schedule_domain.h +++ b/src/include/sof/schedule/ll_schedule_domain.h @@ -100,6 +100,7 @@ static inline struct ll_schedule_domain *dma_domain_get(void) #ifdef CONFIG_SOF_USERSPACE_LL struct task *zephyr_ll_task_alloc(void); struct k_heap *zephyr_ll_user_heap(void); +bool zephyr_ll_user_heap_verify(struct k_heap *heap); void zephyr_ll_user_resources_init(void); #endif /* CONFIG_SOF_USERSPACE_LL */ diff --git a/src/schedule/zephyr_ll_user.c b/src/schedule/zephyr_ll_user.c index aa33807b4aa3..b6b00691d7c9 100644 --- a/src/schedule/zephyr_ll_user.c +++ b/src/schedule/zephyr_ll_user.c @@ -17,25 +17,51 @@ LOG_MODULE_DECLARE(ll_schedule, CONFIG_SOF_LOG_LEVEL); * * This structure encapsulates the memory management resources required for the * low-latency (LL) scheduler in userspace mode. It provides memory isolation - * and heap management for LL scheduler threads. + * and heap management for LL scheduler threads. Only kernel accessible. */ struct zephyr_ll_mem_resources { struct k_mem_domain mem_domain; /**< Memory domain for LL thread isolation */ - struct k_heap *heap; /**< Heap allocator for LL scheduler memory */ + struct k_heap heap; /**< Heap allocator for LL scheduler memory */ }; static struct zephyr_ll_mem_resources ll_mem_resources; -static struct k_heap *zephyr_ll_heap_init(void) +/** + * Heap allocator for LL scheduler memory (user accessible pointer) + * + * Note: this is also user-writable, so kernel must not rely on this to + * be correct and must always validate it separately. + */ +APP_SYSUSER_DATA static struct k_heap *zephyr_ll_heap; + +static struct k_heap *ll_heap_alloc(void) { - struct k_heap *heap = module_driver_heap_init(); - struct k_mem_partition mem_partition; - int ret; + const size_t alloc_size = CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE; + + BUILD_ASSERT(CONFIG_SOF_ZEPHYR_SYS_USER_HEAP_SIZE % CONFIG_MM_DRV_PAGE_SIZE == 0); + + void *mem = rballoc_align(SOF_MEM_FLAG_USER | SOF_MEM_FLAG_COHERENT, alloc_size, + CONFIG_MM_DRV_PAGE_SIZE); + if (!mem) + return NULL; + + k_heap_init(&ll_mem_resources.heap, mem, alloc_size); /* - * TODO: the size of LL heap should be independently configurable and - * not tied to CONFIG_SOF_ZEPHYR_USERSPACE_MODULE_HEAP_SIZE + * k_heap_init() does not set these, so set the values + * manually here */ + ll_mem_resources.heap.heap.init_mem = mem; + ll_mem_resources.heap.heap.init_bytes = alloc_size; + + return &ll_mem_resources.heap; +} + +static void ll_heap_init(void) +{ + struct k_heap *heap = ll_heap_alloc(); + struct k_mem_partition mem_partition; + int ret; if (!heap) { tr_err(&ll_tr, "heap alloc fail"); @@ -60,25 +86,58 @@ static struct k_heap *zephyr_ll_heap_init(void) (void *)mem_partition.start, heap->heap.init_bytes, ret); if (ret) k_panic(); - - return heap; } void zephyr_ll_user_resources_init(void) { + int ret; + k_mem_domain_init(&ll_mem_resources.mem_domain, 0, NULL); - ll_mem_resources.heap = zephyr_ll_heap_init(); + ll_heap_init(); + + /* store a user-accessible pointer */ + zephyr_ll_heap = &ll_mem_resources.heap; /* attach common partition to LL domain */ user_memory_attach_common_partition(zephyr_ll_mem_domain()); + + ret = user_memory_attach_system_user_partition(zephyr_ll_mem_domain()); + if (ret) + k_panic(); +} + +/** + * Check if 'heap' is a valid heap pointer. + * + * Available only in kernel mode. + * + * @return true if valid + */ +bool zephyr_ll_user_heap_verify(struct k_heap *heap) +{ + return heap == &ll_mem_resources.heap; } +/** + * Returns heap object to use in user-space LL code. + * + * Can be called from user-space. + * + * @return heap pointer that can be passed to sof_heap_alloc() + */ struct k_heap *zephyr_ll_user_heap(void) { - return ll_mem_resources.heap; + return zephyr_ll_heap; } +/** + * Returns pointer to LL user-space memory domain. + * + * Available only in kernel mode. + * + * @return pointer to memory domain + */ struct k_mem_domain *zephyr_ll_mem_domain(void) { return &ll_mem_resources.mem_domain; diff --git a/zephyr/Kconfig b/zephyr/Kconfig index f1d1896c4234..c93b2411736e 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -116,6 +116,17 @@ config SOF_ZEPHYR_USERSPACE_MODULE_HEAP_SIZE module has its own independent heap to which only it has access. This heap is shared between instances of the same module. +config SOF_ZEPHYR_SYS_USER_HEAP_SIZE + hex "Size of the shared LL user-space heap" + default 0x20000 + depends on SOF_USERSPACE_LL + help + The size of the shared heap used by the low-latency (LL) scheduler + user-space thread. This heap is shared across all pipelines running + on the LL thread and must be large enough to hold all host DMA + buffers, chain DMA data, SG elements, and module adapter buffers + for all simultaneously active pipelines. + config SOF_USERSPACE_PROXY bool "Use userspace proxy to support userspace modules" select SOF_USERSPACE_USE_DRIVER_HEAP From a363950ee5a7f59e1fe8ec137f8035cce6fa67cf Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 27 May 2026 13:23:20 +0300 Subject: [PATCH 2/6] schedule: zephyr_ll_user: make double-mapping conditional Only double-map the LL resources if CONFIG_CACHE_HAS_MIRRORED_MEMORY_REGIONS is set. Signed-off-by: Kai Vehmanen --- src/schedule/zephyr_ll_user.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/schedule/zephyr_ll_user.c b/src/schedule/zephyr_ll_user.c index b6b00691d7c9..bad2f6c9cbb6 100644 --- a/src/schedule/zephyr_ll_user.c +++ b/src/schedule/zephyr_ll_user.c @@ -79,6 +79,7 @@ static void ll_heap_init(void) if (ret) k_panic(); +#ifdef CONFIG_CACHE_HAS_MIRRORED_MEMORY_REGIONS mem_partition.start = (uintptr_t)sys_cache_uncached_ptr_get(heap->heap.init_mem); mem_partition.attr = K_MEM_PARTITION_P_RW_U_RW; ret = k_mem_domain_add_partition(&ll_mem_resources.mem_domain, &mem_partition); @@ -86,6 +87,7 @@ static void ll_heap_init(void) (void *)mem_partition.start, heap->heap.init_bytes, ret); if (ret) k_panic(); +#endif } void zephyr_ll_user_resources_init(void) From c1ccb870870e196e7edfcebb5bd607a9f34f26ca Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Fri, 8 May 2026 16:46:17 +0300 Subject: [PATCH 3/6] zephyr: lib: make sof_heap_alloc/free system calls Add a built option to make sof_heap_alloc/free available as system calls to user-space. Add a test case for the functions that runs in a user-space thread. Signed-off-by: Kai Vehmanen --- zephyr/CMakeLists.txt | 2 + zephyr/Kconfig | 7 +++ zephyr/include/rtos/alloc.h | 17 ++++++ zephyr/lib/alloc.c | 28 ++++++++-- zephyr/syscall/alloc.c | 36 +++++++++++++ zephyr/test/CMakeLists.txt | 3 ++ zephyr/test/userspace/test_heap_alloc.c | 72 +++++++++++++++++++++++++ 7 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 zephyr/syscall/alloc.c create mode 100644 zephyr/test/userspace/test_heap_alloc.c diff --git a/zephyr/CMakeLists.txt b/zephyr/CMakeLists.txt index 91598a776db0..4c035a1e40a6 100644 --- a/zephyr/CMakeLists.txt +++ b/zephyr/CMakeLists.txt @@ -592,6 +592,8 @@ zephyr_library_sources_ifdef(CONFIG_SHELL zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/audio/module_adapter/module/generic.h) zephyr_syscall_header(${SOF_SRC_PATH}/include/sof/lib/fast-get.h) +zephyr_syscall_header(include/rtos/alloc.h) +zephyr_library_sources_ifdef(CONFIG_SOF_USERSPACE_INTERFACE_ALLOC syscall/alloc.c) zephyr_library_link_libraries(SOF) target_link_libraries(SOF INTERFACE zephyr_interface) diff --git a/zephyr/Kconfig b/zephyr/Kconfig index c93b2411736e..272a41baa08c 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -29,6 +29,13 @@ config SOF_USERSPACE_INTERFACE_DMA help Allow user-space threads to use the SOF DMA interface. +config SOF_USERSPACE_INTERFACE_ALLOC + bool "Enable SOF heap alloc interface to userspace threads" + depends on USERSPACE + help + Allow user-space threads to use sof_heap_alloc/sof_heap_free + as Zephyr system calls. + config SOF_USERSPACE_LL bool "Run Low-Latency pipelines in userspace threads" depends on USERSPACE diff --git a/zephyr/include/rtos/alloc.h b/zephyr/include/rtos/alloc.h index e21e498f0471..658fc94c9819 100644 --- a/zephyr/include/rtos/alloc.h +++ b/zephyr/include/rtos/alloc.h @@ -99,9 +99,26 @@ void rfree(void *ptr); */ void l3_heap_save(void); +void *z_impl_sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment); + +void z_impl_sof_heap_free(struct k_heap *heap, void *addr); + +/* + * This is ugly to define the signatures twice, but this + * is required to support userspace builds that do not export alloc. + */ +#ifdef CONFIG_SOF_USERSPACE_INTERFACE_ALLOC +__syscall void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment); +__syscall void sof_heap_free(struct k_heap *heap, void *addr); +#include +#else void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, size_t alignment); void sof_heap_free(struct k_heap *heap, void *addr); +#endif + #if CONFIG_SOF_FULL_ZEPHYR_APPLICATION struct k_heap *sof_sys_heap_get(void); #else diff --git a/zephyr/lib/alloc.c b/zephyr/lib/alloc.c index 8192c2caff0f..6fcae7505f96 100644 --- a/zephyr/lib/alloc.c +++ b/zephyr/lib/alloc.c @@ -628,8 +628,8 @@ EXPORT_SYMBOL(rfree); * To match the fall-back SOF main heap all private heaps should also be in the * uncached address range. */ -void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, - size_t alignment) +void *z_impl_sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment) { if (flags & (SOF_MEM_FLAG_LARGE_BUFFER | SOF_MEM_FLAG_USER_SHARED_BUFFER)) return rballoc_align(flags, bytes, alignment); @@ -643,7 +643,7 @@ void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, return (__sparse_force void *)heap_alloc_aligned_cached(heap, alignment, bytes); } -void sof_heap_free(struct k_heap *heap, void *addr) +void z_impl_sof_heap_free(struct k_heap *heap, void *addr) { if (heap && addr && is_heap_pointer(heap, addr)) heap_free(heap, addr); @@ -651,6 +651,28 @@ void sof_heap_free(struct k_heap *heap, void *addr) rfree(addr); } +#ifndef CONFIG_SOF_USERSPACE_INTERFACE_ALLOC + +/* + * Putting these as inlines in alloc.h breaks Zephyr native + * ztests like sof/test/ztest/unit/fast-get that include rtos/alloc.h + * but do not link lib/alloc.c . To to keep the tests happy, + * implement the functions here. + */ + +void *sof_heap_alloc(struct k_heap *heap, uint32_t flags, size_t bytes, + size_t alignment) +{ + return z_impl_sof_heap_alloc(heap, flags, bytes, alignment); +} + +void sof_heap_free(struct k_heap *heap, void *addr) +{ + return z_impl_sof_heap_free(heap, addr); +} + +#endif /* CONFIG_SOF_USERSPACE_INTERFACE_ALLOC */ + static int heap_init(void) { sys_heap_init(&sof_heap.heap, heapmem, HEAPMEM_SIZE - SHARED_BUFFER_HEAP_MEM_SIZE); diff --git a/zephyr/syscall/alloc.c b/zephyr/syscall/alloc.c new file mode 100644 index 000000000000..fd3e486018fb --- /dev/null +++ b/zephyr/syscall/alloc.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: BSD-3-Clause +// +// Copyright(c) 2026 Intel Corporation. + +#include +#include +#include +#include + +static inline void *z_vrfy_sof_heap_alloc(struct k_heap *heap, uint32_t flags, + size_t bytes, size_t alignment) +{ + /* reject flags that bypass heap isolation */ + K_OOPS(flags & (SOF_MEM_FLAG_LARGE_BUFFER | SOF_MEM_FLAG_USER_SHARED_BUFFER)); + + /* user-space use of sof_heap_alloc() limited to this single heap */ + K_OOPS(!zephyr_ll_user_heap_verify(heap)); + + return z_impl_sof_heap_alloc(heap, flags, bytes, alignment); +} +#include + +static inline void z_vrfy_sof_heap_free(struct k_heap *heap, void *addr) +{ + /* user-space use of sof_heap_alloc() limited to this single heap */ + K_OOPS(!zephyr_ll_user_heap_verify(heap)); + + if (addr) { + uintptr_t start = (uintptr_t)heap->heap.init_mem; + uintptr_t addr_uc = (uintptr_t)sys_cache_uncached_ptr_get(addr); + K_OOPS(addr_uc < start || addr_uc >= start + heap->heap.init_bytes); + K_OOPS(K_SYSCALL_MEMORY_WRITE(addr, 1)); + } + z_impl_sof_heap_free(heap, addr); +} +#include diff --git a/zephyr/test/CMakeLists.txt b/zephyr/test/CMakeLists.txt index f548c98c5e73..452da5ca6569 100644 --- a/zephyr/test/CMakeLists.txt +++ b/zephyr/test/CMakeLists.txt @@ -8,6 +8,9 @@ if(CONFIG_SOF_BOOT_TEST) zephyr_library_sources_ifdef(CONFIG_USERSPACE userspace/ksem.c ) + if(CONFIG_USERSPACE AND CONFIG_SOF_USERSPACE_INTERFACE_ALLOC) + zephyr_library_sources(userspace/test_heap_alloc.c) + endif() endif() if(CONFIG_SOF_BOOT_TEST_STANDALONE AND CONFIG_SOF_USERSPACE_INTERFACE_DMA) diff --git a/zephyr/test/userspace/test_heap_alloc.c b/zephyr/test/userspace/test_heap_alloc.c new file mode 100644 index 000000000000..8018e4b27b9d --- /dev/null +++ b/zephyr/test/userspace/test_heap_alloc.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright(c) 2026 Intel Corporation. + */ + +/* + * Test case for sof_heap_alloc() / sof_heap_free() use from a Zephyr + * user-space thread. + */ + +#include +#include +#include + +#include +#include +#include + +LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); + +#define USER_STACKSIZE 2048 + +static struct k_thread user_thread; +static K_THREAD_STACK_DEFINE(user_stack, USER_STACKSIZE); + +static void user_function(void *p1, void *p2, void *p3) +{ + struct k_heap *heap = (struct k_heap *)p1; + void *ptr; + + __ASSERT(k_is_user_context(), "isn't user"); + + LOG_INF("SOF thread %s (%s)", + k_is_user_context() ? "UserSpace!" : "privileged mode.", + CONFIG_BOARD_TARGET); + + /* allocate a block from the user heap */ + ptr = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, 128, 0); + zassert_not_null(ptr, "sof_heap_alloc returned NULL"); + + LOG_INF("sof_heap_alloc returned %p", ptr); + + /* free the block */ + sof_heap_free(heap, ptr); + + LOG_INF("sof_heap_free done"); +} + +static void test_user_thread_heap_alloc(void) +{ + struct k_heap *heap; + + heap = zephyr_ll_user_heap(); + zassert_not_null(heap, "user heap not found"); + + k_thread_create(&user_thread, user_stack, USER_STACKSIZE, + user_function, heap, NULL, NULL, + -1, K_USER, K_FOREVER); + + /* Add thread to LL memory domain so it can access the user heap */ + k_mem_domain_add_thread(zephyr_ll_mem_domain(), &user_thread); + + k_thread_start(&user_thread); + k_thread_join(&user_thread, K_FOREVER); +} + +ZTEST(sof_boot, user_space_heap_alloc) +{ + test_user_thread_heap_alloc(); + + ztest_test_pass(); +} From 214fe9732028b71d2b0575b2dddc904319ad2bfc Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 27 May 2026 12:07:50 +0300 Subject: [PATCH 4/6] zephyr: boot_test: add capability to handle negative test Add infrastructure to handle boot tests that cause a Zephyr fatal error. sof_boot_test_set_fault_valid() can be used by tests to inform the SOF fault handler that a fault is expected and SOF execution should not be stopped. Signed-off-by: Kai Vehmanen --- src/include/sof/boot_test.h | 16 ++++++++++++++++ zephyr/wrapper.c | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/include/sof/boot_test.h b/src/include/sof/boot_test.h index 1af6e7f2d8e0..dfa8671ee55f 100644 --- a/src/include/sof/boot_test.h +++ b/src/include/sof/boot_test.h @@ -13,6 +13,8 @@ #endif #include +struct k_thread; + #if CONFIG_SOF_BOOT_TEST #define TEST_RUN_ONCE(fn, ...) do { \ static bool once; \ @@ -36,4 +38,18 @@ void sof_run_boot_tests(void); +/** + * Mark a boot-test thread as expected to trigger a fatal error. + * + * @param thread Thread that is allowed to fault once, or NULL to clear. + */ +#if CONFIG_SOF_BOOT_TEST +void sof_boot_test_set_fault_valid(struct k_thread *thread); +#else +static inline void sof_boot_test_set_fault_valid(struct k_thread *thread) +{ + (void)thread; +} +#endif + #endif diff --git a/zephyr/wrapper.c b/zephyr/wrapper.c index c0c167b930fe..f43d2ed19a6b 100644 --- a/zephyr/wrapper.c +++ b/zephyr/wrapper.c @@ -6,6 +6,7 @@ */ #include +#include #include #include #include @@ -325,11 +326,36 @@ volatile int *_sof_fatal_null = NULL; struct arch_esf; +#if CONFIG_SOF_BOOT_TEST +static struct k_thread *sof_boot_test_fault_thread; + +void sof_boot_test_set_fault_valid(struct k_thread *thread) +{ + sof_boot_test_fault_thread = thread; +} + +static bool sof_boot_test_fault_expected(void) +{ + if (sof_boot_test_fault_thread != k_current_get()) + return false; + + sof_boot_test_fault_thread = NULL; + return true; +} +#endif + void k_sys_fatal_error_handler(unsigned int reason, const struct arch_esf *esf) { ARG_UNUSED(esf); +#if CONFIG_SOF_BOOT_TEST + if (sof_boot_test_fault_expected()) { + LOG_ERR("Expected fatal error as part of boot test"); + return; + } +#endif + /* flush and switch to immediate mode */ LOG_PANIC(); From 5985e246c736bee70035f734452030d86122ae88 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Wed, 27 May 2026 12:09:49 +0300 Subject: [PATCH 5/6] zephyr: test: userspace: add negative tests for sof_heap_alloc() Add negative tests to cover system call validation for heap allocation. Signed-off-by: Kai Vehmanen --- zephyr/test/userspace/test_heap_alloc.c | 133 ++++++++++++++++++++---- 1 file changed, 114 insertions(+), 19 deletions(-) diff --git a/zephyr/test/userspace/test_heap_alloc.c b/zephyr/test/userspace/test_heap_alloc.c index 8018e4b27b9d..54f9aa7e6d43 100644 --- a/zephyr/test/userspace/test_heap_alloc.c +++ b/zephyr/test/userspace/test_heap_alloc.c @@ -4,7 +4,7 @@ */ /* - * Test case for sof_heap_alloc() / sof_heap_free() use from a Zephyr + * Test cases for sof_heap_alloc() / sof_heap_free() use from a Zephyr * user-space thread. */ @@ -15,6 +15,7 @@ #include #include #include +#include LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); @@ -23,9 +24,22 @@ LOG_MODULE_DECLARE(sof_boot_test, LOG_LEVEL_DBG); static struct k_thread user_thread; static K_THREAD_STACK_DEFINE(user_stack, USER_STACKSIZE); +K_APPMEM_PARTITION_DEFINE(heap_alloc_test_part); +K_APP_BMEM(heap_alloc_test_part) static uint8_t non_heap_byte; + +enum heap_alloc_test_case { + HEAP_ALLOC_VALID, + HEAP_ALLOC_NULL_HEAP, + HEAP_ALLOC_FORBIDDEN_FLAGS, + HEAP_FREE_NON_HEAP_POINTER, + HEAP_FREE_INACCESSIBLE_POINTER, +}; + static void user_function(void *p1, void *p2, void *p3) { - struct k_heap *heap = (struct k_heap *)p1; + struct k_heap *heap = p1; + enum heap_alloc_test_case test_case = (enum heap_alloc_test_case)(uintptr_t)p2; + void *free_ptr = p3; void *ptr; __ASSERT(k_is_user_context(), "isn't user"); @@ -34,39 +48,120 @@ static void user_function(void *p1, void *p2, void *p3) k_is_user_context() ? "UserSpace!" : "privileged mode.", CONFIG_BOARD_TARGET); - /* allocate a block from the user heap */ - ptr = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, 128, 0); - zassert_not_null(ptr, "sof_heap_alloc returned NULL"); + switch (test_case) { + case HEAP_ALLOC_VALID: + ptr = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, 128, 0); + zassert_not_null(ptr, "sof_heap_alloc returned NULL"); + sof_heap_free(heap, ptr); + return; + case HEAP_ALLOC_NULL_HEAP: + (void)sof_heap_alloc(NULL, SOF_MEM_FLAG_USER, 128, 0); + break; + case HEAP_ALLOC_FORBIDDEN_FLAGS: + (void)sof_heap_alloc(heap, SOF_MEM_FLAG_LARGE_BUFFER, 128, 0); + break; + case HEAP_FREE_NON_HEAP_POINTER: + sof_heap_free(heap, &non_heap_byte); + break; + case HEAP_FREE_INACCESSIBLE_POINTER: + sof_heap_free(heap, free_ptr); + break; + default: + zassert_unreachable("unknown heap allocation test case"); + } + + zassert_unreachable("syscall security check did not fault"); +} - LOG_INF("sof_heap_alloc returned %p", ptr); +static void run_user_heap_alloc_case(enum heap_alloc_test_case test_case, struct k_heap *heap, + void *ptr, bool grant_ll_domain, bool expect_fault) +{ + int ret; - /* free the block */ - sof_heap_free(heap, ptr); + k_thread_create(&user_thread, user_stack, USER_STACKSIZE, + user_function, heap, (void *)(uintptr_t)test_case, ptr, + -1, K_USER, K_FOREVER); + + if (grant_ll_domain) + k_mem_domain_add_thread(zephyr_ll_mem_domain(), &user_thread); - LOG_INF("sof_heap_free done"); + if (expect_fault) + sof_boot_test_set_fault_valid(&user_thread); + + k_thread_start(&user_thread); + ret = k_thread_join(&user_thread, K_FOREVER); + zassert_equal(ret, 0, "user thread join failed: %d", ret); } -static void test_user_thread_heap_alloc(void) +static void test_user_thread_heap_alloc(enum heap_alloc_test_case test_case) { + bool grant_ll_domain = true; + bool expect_fault = false; + void *ptr = NULL; struct k_heap *heap; heap = zephyr_ll_user_heap(); zassert_not_null(heap, "user heap not found"); - k_thread_create(&user_thread, user_stack, USER_STACKSIZE, - user_function, heap, NULL, NULL, - -1, K_USER, K_FOREVER); + switch (test_case) { + case HEAP_ALLOC_VALID: + break; + case HEAP_ALLOC_NULL_HEAP: + case HEAP_ALLOC_FORBIDDEN_FLAGS: + case HEAP_FREE_NON_HEAP_POINTER: + expect_fault = true; + break; + case HEAP_FREE_INACCESSIBLE_POINTER: + ptr = sof_heap_alloc(heap, SOF_MEM_FLAG_USER, 128, 0); + zassert_not_null(ptr, "kernel heap allocation failed"); + grant_ll_domain = false; + expect_fault = true; + break; + default: + zassert_unreachable("unknown heap allocation test case"); + } + + run_user_heap_alloc_case(test_case, heap, ptr, grant_ll_domain, expect_fault); + + if (ptr) + sof_heap_free(heap, ptr); +} - /* Add thread to LL memory domain so it can access the user heap */ - k_mem_domain_add_thread(zephyr_ll_mem_domain(), &user_thread); +ZTEST(sof_boot, user_space_heap_alloc) +{ + test_user_thread_heap_alloc(HEAP_ALLOC_VALID); - k_thread_start(&user_thread); - k_thread_join(&user_thread, K_FOREVER); + ztest_test_pass(); } -ZTEST(sof_boot, user_space_heap_alloc) +ZTEST(sof_boot, user_space_heap_alloc_rejects_null_heap) +{ + test_user_thread_heap_alloc(HEAP_ALLOC_NULL_HEAP); + + ztest_test_pass(); +} + +ZTEST(sof_boot, user_space_heap_alloc_rejects_forbidden_flags) +{ + test_user_thread_heap_alloc(HEAP_ALLOC_FORBIDDEN_FLAGS); + + ztest_test_pass(); +} + +ZTEST(sof_boot, user_space_heap_free_rejects_non_heap_pointer) +{ + k_mem_domain_add_partition(zephyr_ll_mem_domain(), &heap_alloc_test_part); + + test_user_thread_heap_alloc(HEAP_FREE_NON_HEAP_POINTER); + + k_mem_domain_remove_partition(zephyr_ll_mem_domain(), &heap_alloc_test_part); + + ztest_test_pass(); +} + +ZTEST(sof_boot, user_space_heap_free_rejects_inaccessible_pointer) { - test_user_thread_heap_alloc(); + test_user_thread_heap_alloc(HEAP_FREE_INACCESSIBLE_POINTER); ztest_test_pass(); } From a18b5abed63c35de37176c5d88104434c39dce75 Mon Sep 17 00:00:00 2001 From: Kai Vehmanen Date: Tue, 26 May 2026 19:35:35 +0300 Subject: [PATCH 6/6] zephyr: set SOF_USERSPACE_INTERFACE_ALLOC for LL user builds Enable CONFIG_SOF_USERSPACE_INTERFACE_ALLOC to allow user-space pipeline code to allocate memory with sof_heap_alloc(). Signed-off-by: Kai Vehmanen --- zephyr/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/zephyr/Kconfig b/zephyr/Kconfig index 272a41baa08c..9bc9bea7c687 100644 --- a/zephyr/Kconfig +++ b/zephyr/Kconfig @@ -39,6 +39,7 @@ config SOF_USERSPACE_INTERFACE_ALLOC config SOF_USERSPACE_LL bool "Run Low-Latency pipelines in userspace threads" depends on USERSPACE + select SOF_USERSPACE_INTERFACE_ALLOC select SOF_USERSPACE_INTERFACE_DMA help Run Low-Latency (LL) pipelines in userspace threads. This adds